5 vícios que devem largar na linguagem C
Introdução
Esse artigo tem como objetivo educar certos programadores evitarem técnicas ridículas e perigosas na linguagem C. Eu considero essas técnicas de vícios, pois até em universidades que conheço, existem trolhas (ops! professores) que ensinam "o mau caminho" aos futuros programadores profissionais.
Se este artigo for de tamanho interesse para si, peço a sua total atenção, pois acredito que vai ajudar a evitar e a superar certos problemas que alguns programadores encontram na linguagem C.
Vício 1 - Usar system("pause")
Bom, isso até o meu próprio professor de programação ensina . Como bem sabemos, o protótipo da função system() está declarada em stdlib.h, esta mesma função nos permite invocar comandos do sistema operativo em uso dentro de um código C/C++.
No Linux o comando clear por exemplo limpa a tela do terminal:
[code=rich]
system("clear");
[/code]
Já no Windows, usa-se o comando cls:
[code=rich]
system("cls");
[/code]
Mas, vamos nos focar no cerne deste tópico, o famigerado system("pause"). O pause é um comando interno do interpretador de comandos do Windows (aka cmd.exe), e tem o propósito de pausar ou interromper a janela de comandos (a tela preta) e emitir a mensagem "Prima qualquer tecla para continuar".
Muitos iniciantes na linguagem C usam essa aberração com o propósito de ver a saída ou o resultado de seus programas, antes da consola fechar e o programar terminar.
[code=rich]
#include
/* Este programa faz a soma entre 2 números e guarda o resultado em a*/
int a=2;
int b=2;
int main()
{
a = a+b;
printf("%i",a);
}
/* Este programa faz a soma entre 2 números e guarda o resultado em a*/
int a=2;
int b=2;
int main()
{
a = a+b;
printf("%i",a);
}
[/code]
Para a infelicidade de alguns, o código acima não dá a possibilidade do programador ver o resultado da soma na tela, "a tela pisca" e nem dá chance de eu ver os resultados. Foi daí que algum programador maluco, inventou a solução avassaladora que está no código abaixo:
[code=rich]
#include
/* Este programa faz a soma entre 2 números e guarda o resultado em a*/
int a=2;
int b=2;
int main()
{
a = a+b;
printf("%i",a);
system("pause"); //Mataram
}
/* Este programa faz a soma entre 2 números e guarda o resultado em a*/
int a=2;
int b=2;
int main()
{
a = a+b;
printf("%i",a);
system("pause"); //Mataram
}
[/code]
Até agora não expliquei o motivo desta técnica ser tão horrível, mas cá vai, primeiro é pelo simples fato de ser deselegante, pode não parecer, mas com o tempo irás perceber que sim.
O segundo motivo é a portabilidade, a linguagem C é muito elogiada em livros por sua portabilidade, ou seja, o código que você compilar e rodar em um processador ou sistema operativo provavelmente poderá ser compilado e rodado em outro.
Mas existem excessões, existem bibliotecas e funções que são desenhadas para um só sistema operativo ou processador e o uso das mesmas pode limitar o código de certo programa à um só sistema operativo.
O system("pause"), torna seu código não-portável pelo simples fato de ter o comando pause que é específico para sistemas operativos Windows, ou seja, essa invocação não irá funcionar no Linux, MacOS, FreeBSD, NetBSD, etc.
Porém, existe uma alternativa, usar a função getchar() que é portável pois faz parte do padrão ANSI C, observe o código abaixo:
[code=rich]
#include
/* Este programa faz a soma entre 2 números e guarda o resultado em a*/
int a=2;
int b=2;
int main()
{
a = a+b;
printf("%i",a);
getchar(); //Agora sim, está bem melhor
}
/* Este programa faz a soma entre 2 números e guarda o resultado em a*/
int a=2;
int b=2;
int main()
{
a = a+b;
printf("%i",a);
getchar(); //Agora sim, está bem melhor
}
[/code]
Conclusão, use getchar() e evite o system("pause").
Vício 2 - conio.h e gotoxy, o veredito final
A biblioteca conio.h foi criada pela Borland para o compilador Turbo C na época do antigão IBM-PC da era de 16 bits. Ele foi desenhado especificamente para possibilitar um programador trabalhar de maneira confortável com a entrada/saída de dados (input/output) usando a interface DOS.
Aqui no fórum já houve uma discussão calorosa acerca do conio: https://www.hardware.com.br/comunidade/definitiva-conclusao/796311/
A questão é, apesar de ser útil em certos casos, o conio.h é uma biblioteca bem antiga, obsoleta e não é multiplataforma, as bibliotecas construídas para Windows funcionam por cima da Win32 API. Apesar disso, eu não reprovo totalmente o seu uso, pois qualquer programador pode necessitar dela em casos onde a biblioteca padrão da linguagem C não supre certas pontos como: desenhar uma letra em posições específicas da consola (com gotoxy), desenhar carateres coloridos (com cprintf), detetar eventos do teclado (com kbhit), e por aí vai.
No entanto, existem bibliotecas que claramente são superiores ao conio, como o ncurses do projeto GNU: http://www.gnu.org/software/ncurses/. Que também possui um port para Windows: http://gnuwin32.sourceforge.net/packages/ncurses.htm.
Mais informações acerca: http://en.wikipedia.org/wiki/Ncurses.
Vício 3 - Usar goto e criar código espaguete
Se seu professor alguma vez te ensinar a usar essa instrução no código C, poste aqui e faça o favor de denunciar a polícia federal .
O uso de goto deve ser evitado a qualquer custo dentro de seu código C/C++, pois o goto cria código espaguete (spaghetti code), ou seja, código que desobedece os princípios de programação estruturada. Vamos parar de enrolar e vou mostrar como o uso de goto pode tornar seu código ilegível, feio e impossível de se ler.
[code=rich]
#include
#include
/* Este programa usa a instrução goto para mostrar o quanto é
* repugnante o uso da mesma num código C */
int numero;
char repetir;
int main()
{
inicio:
printf("Digite um número: ");
scanf("%i",&numero);
if(numero == 0)
goto numerozero;
else if(numero < 0)
goto numeromenor;
else
goto numeromaior;
numerozero:
printf("Numero eh zero!\n");
printf("Deseja repetir? [s/n]\n");
scanf(" %c",&repetir);
if((tolower(repetir)) == 's')
goto inicio;
else
goto fim;
numeromenor:
printf("Numero eh negativo!\n");
printf("Deseja repetir? [s/n]\n");
scanf(" %c",&repetir);
if((tolower(repetir)) == 's')
goto inicio;
else
goto fim;
numeromaior:
printf("Numero eh positivo!\n");
printf("Deseja repetir? [s/n]\n");
scanf(" %c",&repetir);
if((tolower(repetir)) == 's')
goto inicio;
else
goto fim;
fim:
return(0);
}
#include
/* Este programa usa a instrução goto para mostrar o quanto é
* repugnante o uso da mesma num código C */
int numero;
char repetir;
int main()
{
inicio:
printf("Digite um número: ");
scanf("%i",&numero);
if(numero == 0)
goto numerozero;
else if(numero < 0)
goto numeromenor;
else
goto numeromaior;
numerozero:
printf("Numero eh zero!\n");
printf("Deseja repetir? [s/n]\n");
scanf(" %c",&repetir);
if((tolower(repetir)) == 's')
goto inicio;
else
goto fim;
numeromenor:
printf("Numero eh negativo!\n");
printf("Deseja repetir? [s/n]\n");
scanf(" %c",&repetir);
if((tolower(repetir)) == 's')
goto inicio;
else
goto fim;
numeromaior:
printf("Numero eh positivo!\n");
printf("Deseja repetir? [s/n]\n");
scanf(" %c",&repetir);
if((tolower(repetir)) == 's')
goto inicio;
else
goto fim;
fim:
return(0);
}
[/code]
Me diga, código bonito né, tente compilar e executar, ele vai esperar que você digite um número e vai mostrar se o número é positivo, negativo ou neutro (zero).
Além de ser feio um programador usar o goto em C, é também anti-ético e ruim usar em projetos críticos com milhares de linhas de código, e mais, seu profissionalismo como programador pode facilmente ser posto em questão dentro de uma equipa de software.
Não use goto, a não ser que você programe em assembly (mnemônico jmp) onde é necessário (pois assembly não é uma linguagem estruturada).
Vicio 4 - Mal uso do scanf
Oh! scanf scanf meu amor, uma das maiores dores de cabeça do programador.
Um pouco de poesia para relaxar, pois essa parte do tutorial é muito importante, portanto preste muita atenção, muita concentração agora.
A função scanf, cujo protótipo está declarado em stdio.h, serve para efetuarmos a entrada (do teclado) formatada de dados nos nossos programas, daí o nome scan formated. É uma função muito poderosa, porém muito irritante e problemática para alguns iniciantes.
Eu classificaria os problemas básicos do uso de scanf em 3 tipos:
- Deixar sujeira no buffer do teclado
- Ler strings com espaço
- Mau uso do fflush
[code=rich]
#include
char carater;
int main()
{
printf("Escreve algum caráter\n");
scanf("%c",&carater);
printf("Mais um caráter\n");
scanf("%c",&carater);
}
[/code]
Antes de compilar e rodar o código acima, vamos tentar raciocinar de forma lógica como o programa acima deverá supostamente funcionar.
Declaramos uma variável carater do tipo char, que claramente usaremos para armazenar um (e só um) caráter.
O primeiro scanf vai tentar ler o primeiro caráter que você digitar e vai guardar na variável. O segundo scanf vai fazer exatamente o mesmo.
Isso é só uma dedução lógica, agora copie o código e tente compilar, você irá de certa maneira ficar surpreso com o comportamento do programa.
O primeiro scanf vai funcionar normalmente, já o segundo não vai funcionar e o programa vai terminar, será que fiquei louco ou o scanf é que está louco?
Agora chegou o momento de explicar porque é que isso ocorre, e o que é o buffer de teclado (também chamado buffer de entrada ou stdin).
Quando você invoca funções de entrada como scanf, getchar, etc, tais funções pausam a consola e esperam que você digite qualquer caráter terminado por ENTER.
Quando você digita esses dados, eles não são ainda enviados para a variável que irá guardar os mesmos. Os dados são enviados para um espaço de memória chamado buffer de teclado, isso explica o porquê de ser possível deletar os carateres com a tecla BACKSPACE.
Os dados só são realmente guardados na variável pretendida, quando você pressiona a tecla ENTER, pois essa tecla indica ao scanf que terminamos o ato de entrada de dados.
É importante saber que ENTER na memória é um caráter como qualquer outro, ele é o caráter LF(Line Feed) cujo código ASCII é igual à 10 (decimal).
http://pt.wikipedia.org/wiki/Tabela_ASCII
O que acontece basicamente no primeiro scanf é:
- Você digita um caráter e pressiona ENTER;
- Como só precisamos armazenar um caráter na variável, o caráter é armazenado na variável e o ENTER permanence no buffer de entrada;
- Como o buffer de entrada não está vazio (tem o ENTER lá), ele lê aquele ENTER, e como ENTER serve para terminar a entrada de dados pelo teclado, o scanf prossegue.
Como o ENTER serve para sinalizar o fim da entrada de dados pelo teclado, o caráter 'A'(ASCII 97) vai ser tirado do buffer e armazenado na variável.
Quando você invocar o segundo scanf, ele vai ler o próximo carater do buffer de entrada, que é o ENTER, mais uma vez repito que o ENTER sinaliza o fim de entrada de dados, portanto o segundo scanf vai prosseguir sem dar chance de digitar-se algo na consola.
Mas, há uma solução simples para esse problema, basta usar o scanset * no scanf. Tal operador diz para o scanf ignorar qualquer coisa por exemplo:
[code=rich]
scanf("%c%*c",&carater);
[/code]
No código acima o scanf irá ler um primeiro caráter e depois vai suprimir o seguinte (que normalmente é o ENTER), quer dizer que tal caráter não será armazenado no buffer de entrada.
O operador de supressão * pode ser normalmente usado em outros especificadores de formato, por exemplo:
[code=rich]
%*i - ignora um inteiro.
%*f - ignora um float.
[/code]
É recomendável também usar o scanset ou operador de supressão * em leituras de string via scanf, exemplo:
[code=rich]
char str[32];
scanf("%s%*c",str);
[/code]
O código acima irá ler uma string e armazenar em str, o ENTER será ignorado pelo scanf, ou seja, não será armazenado no buffer de entrada.
Correção para o código problemático:
[code=rich]
#include
char carater;
int main()
{
printf("Escreve algum caráter\n");
scanf("%c%*c",&carater);
printf("Mais um caráter\n");
scanf("%c%*c",&carater);
}
[/code]
O segundo problema associado ao uso do scanf é a leitura de strings com espaço em branco, repare o código abaixo:
[code=rich]
#include
int main()
{
char str[100];
printf("Digite uma string com espaços\n");
scanf("%s%*c",str);
printf("Você digitou: %s",str);
getchar();
}
int main()
{
char str[100];
printf("Digite uma string com espaços\n");
scanf("%s%*c",str);
printf("Você digitou: %s",str);
getchar();
}
[/code]
Compile e rode o programa, digite uma string com espaços em branco, por exemplo "Ola Mundo".
Repare que o scanf só irá ler a primeira palavra da string("Ola") e irá ignorar o resto.
Resolver este problema é relativamente fácil, basta usar o scanset [^carater] que diz ao scanf ler todos os elementos de uma string (até espaços) delimitados por um caráter especificado no scanset.
O código abaixo por exemplo, faz o scanf ler toda a string e ignorar os carateres após o caráter 'a'.
[code=rich]
#include
int main()
{
char str[100];
printf("Digite uma string que tenha o caráter 'a'\n");
scanf("%[^'a']s%*c",str);
printf("Você digitou: %s",str);
getchar();
}
int main()
{
char str[100];
printf("Digite uma string que tenha o caráter 'a'\n");
scanf("%[^'a']s%*c",str);
printf("Você digitou: %s",str);
getchar();
}
[/code]
Tente digitar por exemplo "Eu idolatro C", o scanf só vai ler "Eu idol", pois o scanf só leu parte da string delimitado pelo caráter 'a'.
Agora voltando ao cerne do problema inicial, nós podemos ler strings inteiras com espaços, se especificarmos ao scanf o caráter \n (que representa o ENTER ou quebra de linha) como delimitador da string que pretendemos ler.
[code=rich]
[/code]
#include
int main()
{
char str[100];
printf("Digite uma string\n");
scanf("%[^\n]s%*c",str);
printf("Você digitou: %s",str);
getchar();
}
int main()
{
char str[100];
printf("Digite uma string\n");
scanf("%[^\n]s%*c",str);
printf("Você digitou: %s",str);
getchar();
}
[/code]
Resolvido, recomendo ir ao final da página para dar uma olhada nos links referentes ao scanf.
O terceiro problema associado ao uso do scanf, é usar o fflush para limpar o buffer de entrada, é uma alternativa ao especificador de supressão *, pois elimina o ENTER ou qualquer outro caráter do buffer de entrada.
Mas apesar do fflush resolver o problema, não é recomendável o seu uso em buffers de entrada (stdin é o buffer do teclado), pois segundo a documentação o seu efeito em buffers de entrada é indefinido, ora pode funcionar ora pode não funcionar, ora pode acontecer algo grave.
Vicio 5 - Mal uso de ponteiros
Em um mundo fantasioso de super-heróis, um homem chamado Ben Parker disse que grandes poderes implicam grandes responsabilidades. Todo o poder tem um preço.
Ponteiros são provavelmente a maior arma que a linguagem C oferece ao programador, arma que dá flexibilidade e poder a quem aprende a manusea-lá bem. E quem abusa dele paga um preço (por vezes muito caro).
O ponteiro em C é um tipo de dado extremamente simples, invés de guardar um valor normal, ele guarda um especial, o endereço de memória de uma outra variável.
Eu particularmente não tive dificuldades em aprender a usar corretamente ponteiros, mas é verídico o fato de ponteiros serem um entrave ao entendimento e uso por parte de programadores novos na linguagem C.
Sem mais enrolações, vamos à um código simples, vamos simplesmente declarar um ponteiro que armazenará o endereço de uma variável inteira.
Lembrando que o uso do operador & retorna o endereço do operando que estiver à direita.
[code=rich]
[/code]Bem simples o código, podemos conhecer o endereço da variável apontada usando o especificador de formato %p na função printf.
[code=rich]
[/code]O endereço da variável apontada será mostrada em hexadecimal.
Vamos mais à fundo, invés de exibir o endereço da variável apontada, vamos exibir o valor da variável apontada, o operador * retorna não o endereço, mas sim o valor da variável apontada.
[code=rich]
[/code]
O terceiro problema associado ao uso do scanf, é usar o fflush para limpar o buffer de entrada, é uma alternativa ao especificador de supressão *, pois elimina o ENTER ou qualquer outro caráter do buffer de entrada.
[code=rich]
#include
char carater;
int main()
{
printf("Escreve algum caráter\n");
scanf("%c",&carater);
fflush(stdin);
printf("Mais um caráter\n");
scanf("%c",&carater);
}
[/code]
#include
char carater;
int main()
{
printf("Escreve algum caráter\n");
scanf("%c",&carater);
fflush(stdin);
printf("Mais um caráter\n");
scanf("%c",&carater);
}
[/code]
Mas apesar do fflush resolver o problema, não é recomendável o seu uso em buffers de entrada (stdin é o buffer do teclado), pois segundo a documentação o seu efeito em buffers de entrada é indefinido, ora pode funcionar ora pode não funcionar, ora pode acontecer algo grave.
Vicio 5 - Mal uso de ponteiros
Em um mundo fantasioso de super-heróis, um homem chamado Ben Parker disse que grandes poderes implicam grandes responsabilidades. Todo o poder tem um preço.
Ponteiros são provavelmente a maior arma que a linguagem C oferece ao programador, arma que dá flexibilidade e poder a quem aprende a manusea-lá bem. E quem abusa dele paga um preço (por vezes muito caro).
O ponteiro em C é um tipo de dado extremamente simples, invés de guardar um valor normal, ele guarda um especial, o endereço de memória de uma outra variável.
Eu particularmente não tive dificuldades em aprender a usar corretamente ponteiros, mas é verídico o fato de ponteiros serem um entrave ao entendimento e uso por parte de programadores novos na linguagem C.
Sem mais enrolações, vamos à um código simples, vamos simplesmente declarar um ponteiro que armazenará o endereço de uma variável inteira.
Lembrando que o uso do operador & retorna o endereço do operando que estiver à direita.
[code=rich]
#include
int main()
{
int numero = 5;
int *ptr_numero = №
}
int main()
{
int numero = 5;
int *ptr_numero = №
}
[/code]Bem simples o código, podemos conhecer o endereço da variável apontada usando o especificador de formato %p na função printf.
[code=rich]
#include
int main()
{
int numero = 5;
int *ptr_numero = №
printf("Endereco da variavel apontada: %p",ptr_numero);
getchar();
}
int main()
{
int numero = 5;
int *ptr_numero = №
printf("Endereco da variavel apontada: %p",ptr_numero);
getchar();
}
[/code]O endereço da variável apontada será mostrada em hexadecimal.
Vamos mais à fundo, invés de exibir o endereço da variável apontada, vamos exibir o valor da variável apontada, o operador * retorna não o endereço, mas sim o valor da variável apontada.
[code=rich]
#include
int main()
{
int numero = 5;
int *ptr_numero = №
printf("Valor da variavel apontada: %i",*ptr_numero);
getchar();
}
int main()
{
int numero = 5;
int *ptr_numero = №
printf("Valor da variavel apontada: %i",*ptr_numero);
getchar();
}
[/code]
Parece que ponteiros não são tão complicados. E que tal tentar o código abaixo?
[code=rich]
#include
int main()
{
int numero = 5;
int *ptr_numero = numero;
printf("Valor da variavel apontada: %i",*ptr_numero);
getchar();
}
int main()
{
int numero = 5;
int *ptr_numero = numero;
printf("Valor da variavel apontada: %i",*ptr_numero);
getchar();
}
[/code]Execute, se você não receber um Segmentation Fault no Linux ou um"O programa parou de responder" no Windows, você tem muita sorte.
O que fizemos basicamente no código acima foi tentar acessar uma região inválida ou protegida da memória e exibir o conteúdo desta região.
Observe que não usamos o operador & para obter o endereço da variável, desta forma, nós simplesmente gravamos o valor da variável(5) e não o endereço da variável.
Outro exemplo de mau uso de ponteiros:
[code=rich]
#include
int main()
{
int *ptr_numero;
*ptr_numero = 13;
}
int main()
{
int *ptr_numero;
*ptr_numero = 13;
}
[/code]Primeiramente nós declaramos um ponteiro para inteiro, como nós não atribuímos nenhum endereço ao mesmo, então o ponteiro estará a apontar para uma região aleatória e desconhecida da memória.
A próxima linha é um desastre de código, nós tentamos guardar o valor inteiro 13 para uma região desconhecida na memória, nos piores casos o programa pode travar, e se não travar, o valor 13 será guardado em uma região desconhecida na memória.
Esse tipo de ponteiro é conhecido por ponteiro selvagem, pois o endereço por onde ele aponta é desconhecido, e se algum valor for gravado por intermédio deste tipo de ponteiro, qualquer coisa pode acontecer, algo de imprevisível.
Quando você não sabe o que atribuir à um ponteiro, simplesmente passe à ele o valor NULL, é uma técnica segura no uso de ponteiros.
[code=rich]
#include
int main()
{
int *ptr_numero = NULL;
}
int main()
{
int *ptr_numero = NULL;
}
[/code]Para terminar a parte de ponteiros, só queria ressaltar que temos uma enorme sorte de não usarmos sistemas DOS que trabalhavam no modo de processamento real, onde não haviam proteções contra violações de memória, ou seja, qualquer programa poderia mexer com a memória de programas vizinhos. E nos piores dos casos o sistema poderia corromper-se.
Conclusão
É isso aí pessoal, espero que tenham gostado deste artigo, criei com o objetivo de fazer com que certos programadores evitem vícios ridículos e por vezes perigosos, qualquer comentário será benvindo, quer seja de agradecimento, incentivo ou correção.
Referências
Código espaguete
http://pt.wikipedia.org/wiki/C%C3%B3digo_espaguete
Biblioteca nCurses
http://en.wikipedia.org/wiki/Ncurses
Conclusão definitiva sobre conio
https://www.hardware.com.br/comunidade/definitiva-conclusao/796311
Tutorial system("pause") e fflush(stdin)
http://forum.imasters.com.br/topic/336835-tutorial-systempause-e-fflushstdin/
Documentação do scanf no cplusplus.com
http://cplusplus.com/reference/cstdio/scanf/
Ler espaços de uma string com scanf
http://gpraveenkumar.wordpress.com/2009/06/10/how-to-use-scanf-to-read-string-with-space/
Ponteiros
http://pt.wikipedia.org/wiki/Ponteiro_(programa%C3%A7%C3%A3o)