paulo1205
(usa Ubuntu)
Enviado em 02/04/2021 - 10:07h
adrisiq escreveu:
Algumas dicas:
- Evite ao máximo usar a biblioteca conio.h , ela é jurássica e seu uso não é recomendado.
- Dê nomes bem significativos para suas variáveis e suas funções, isso facilita a leitura e manutenção do código, o tornando quase auto explicativo.
- Evite usar fflush no stdin , de acordo com a documentação do C , isso possui comportamento imprevisível, sendo dependente da implementação do compilador. No caso, isso funciona para o compilador da Microsoft, porque ela fez. Não quer dizer que sempre vai funcionar. Existem outras técnicas para limpar o buffer do teclado.
- Evite declarar bibliotecas que você não está usando, isso polui o código e acho que impacta na performance da compilação.
Se você se refere a
#include s, acho que seria mais apropriado falar em
incluir , não
declarar . A expressão “declaração” tem outro sentido em C, sendo aplicada quando você apresenta um novo símbolo para o compilador, para que ele saiba qual o tipo de dados de tal símbolo.
- Escreva seu código com carinho, respeite a indentação e o espaçamento do código. Um código bonito e organizado ajuda no desenvolvimento e na manutenção do mesmo.
- Procure criar uma variável por linha, isso ajuda na organização e na leitura.
Aqui caberia falar em
declarar , e eu acho que seria melhor do que
criar .
Quanto a fazer apenas uma declaração por linha, acredito ser principalmente questão de gosto. Pessoalmente, não vejo nenhum problema em deixar numa mesma linha e como parte da mesma declaração múltiplos símbolos que tenham exatamente o mesmo tipo, particularmente quando tais símbolos estejam relacionados, mesmo que de modo não muito forte, Por outro lado, penso que tipos distintos e dados completamente díspares deveriam ficar em declarações distintas. Penso ainda que declarações de funções e de constantes também caem bem individualmente e em linhas distintas. Exemplifico abaixo.
/* Declarações válidas, mas inadequadas (ao meu gosto). */
double a, b, c, d, x, y, v1[2], v2[2], m[2][2], *pd, *const cpd, sin(double), cos(double);
const double PI=3.14159265, Euler=2.718281828, Avogadro=6.02214076e22;
/* Mesmas declarações mas num formato melhor (para o meu gosto). */
// Constantes primeiro, e uma por linha
const double PI=3.14159265;
const double Euler=2.718281828;
const double Avogadro=6.02214076e22;
// Tipos diferentes em declarações diferentes, assim como dados de um mesmo tipo mas não relacionados entre si.
double a, b, c, d; // Coeficientes.
double x, y; // Incógnitas.
double v1[2], v2[2]; // Vetores (supondo que são relacionados, por isso na mesma declaração).
double m[2][2]; // Matriz.
double *pd; // Ponteiro.
double *const cpd; // Ponteiro constante.
double sin(double); // Função que calcula o seno.
double cos(double); // Função que calcula o cosseno
- Sempre inicialize suas variáveis, mesmo que elas já irão receber um valor logo a frente. Isso ajuda a evitar erros causados por lixo de memória.
Aqui eu discordo, e não apenas por gosto, mas porque acho que a premissa está errada de mais de uma maneira.
Quando você declara variáveis locais com alocação automática (i.e. qualquer uma que não tenha sido declarada com o modificador
static ), qualquer atribuição de valores sobre elas implica gerar código compilado para explicitamente colocar tais valores nas variáveis durante a execução, e tal atribuição é repetida toda vez que o fluxo de execução passa pelo ponto em que a atribuição é feita. Se os valores atribuídos não forem efetivamente usados antes de serem sobrescritos com outra coisa, essa atribuição inicial apenas provoca desperdício, tanto no tamanho do código gerado quanto no tempo de processamento gasto com uma operação desnecessária (para não falar de código fonte escrito à toa).
Com um compilador simples, sem recursos de otimização de código, esse desperdício é evidente, e não tem como ser evitado.
Com um compiladores mais moderno, capaz de fazer otimizações, pode ser que ele consiga suprimir algumas das atribuições desnecessárias, mas certamente haverá casos em que ele não poderá saber se uma atribuição é necessária ou não, e será forçado a proceder da maneira mais “segura”, emitindo código possivelmente desnecessário.
A abordagem melhor não é essa que você sugeriu. Em vez de codificar manualmente proteções contra eventuais erros em outras partes do programa, é melhor aprender a escrever código de modo que não seja sujeito a tais erros, e usar técnicas e ferramentas que ajudem evitá-los.
Eis algumas dessas técnicas e ferramentas.
•
Procurar declarar variáveis o mais próximas possível do ponto em que serão usadas. Versões antigas do C, assim como outras linguagens dos anos 1960 e 1970, exigiam que todas as declarações de variáveis locais ocorressem no começo do bloco, antes de qualquer outro comando que não fosse uma declaração. Isso não é mais verdade em C desde o padrão de 1999 (e em C++ bem antes disso): declarações podem ocorrer em qualquer ponto do bloco, permitindo minimizar o hiato visual entre declaração e atribuições, e isso, por si só, já ajuda o programador a não cair na armadilha de usar uma variável que ainda não teve um valor atribuído.
•
Procurar atribuir à variável o valor que ela realmente deve ter. Isso é auxiliado pelo uso da dica anterior, e acho que fica mais bem explicado por meio de um exemplo, contrastando a “segurança feita à mão” do primeiro bloco abaixo com a segurança feita com a cabeça do segundo bloco.
double x=0.0; // Declara x e emite código para criar espaço em memória e para atribuir valor inicial.
double y=0.0; // Declara y e emite código para criar espaço em memória e para atribuir valor inicial.
/* ... trecho de código que não usa nem x nem y... */
scanf("%lf", &x); // Sobrescreve x com valor convertido em scanf ().
y=sin(x); // Sobrescreve y com o valor retornado pela função.
/* ... trecho de código que não usa nem x nem y... */
double x; // Declara x, emitindo código apenas criar espaço na memória, sem se preocupar com eventual conteúdo anterior dessa memória.
scanf("%lf", &x); // Sobrescreve x (até então com valor desconhecido) com valor convertido em scanf ().
double y=sin(x); // Declara y, emitindo código para criar espaço na memória, e imediatamente lhe atribui valor retornado pela função.
•
Use recursos do compilador para detectar e apontar eventuais falhas. Compiladores modernos contam com otimizadores de código que implementam diversas funcionalidades para compactação e eliminação de código executável redundante. Algumas das técnicas usadas para alcançar tais objetivos levam em consideração os ciclos de vida de variáveis, e parte da análise dos ciclos de vida ajuda a diagnosticar casos de variáveis usadas sem inicialização, inicializadas com valores desnecessários, e declaradas mas não utilizadas em lugar algum. Alguns desses casos podem ser corrigidos automaticamente (por exemplo, com o compilador suprimindo a atribuição do valor inicial que seja seguida, mais abaixo, por uma outra atribuição explícita sobre a mesma variável), ao passo que outras são sinalizadas para que o programador corrija.
No caso do GCC, as opções de compilação que eu sempre recomendo, a fim de contar com esse nível de diagnóstico de código, são “
-Wall -Werror -pedantic-errors -O2 ” (
-O2 liga a o segundo nível de otimização de código, que ativa a análise do ciclo de vida das variáveis, e as demais opções se encarregam de produzir as mensagens de diagnóstico sobre código suspeito).
Problemas:
- Para pegar o valor de retorno da sua função, você não precisa adicionar o & .
Não apenas não precisa: não deve mesmo. Todas as tentativas de usar
maior_sal () no programa original estavam erradas.
Opinião:
- Pelo que eu sei, você não precisa colocar o valor de retorno das funções dentro de parênteses.
Não é só opinião, mas fato. O comando
return não utiliza parênteses como parte de sua sintaxe. Se forem usados parênteses, eles são entendidos como parte da expressão que calcula o valor a ser retornado.
Seu código corrigido:
Lembre-se de que o espírito da comunidade não é o de resolver questões para outrem a fim de que tirem boas notas, mas mais de fornecer orientações a fim de que a pessoa possa aprender por conta própria, desse modo consolidando seu conhecimento.
Mas já que você postou o código, permita-me mais algumas críticas a respeito dele, além do que está relacionado ao que eu já disse, acima.
• A separação entre declaração e definição das funções
getMaiorSalario () e
limparBuffer () não é necessária num programa pequeno como esse, e acaba servido mais para aumentar o esforço de manutenção de código. Separar declaração e definição de funções faz mais sentido quando você tem compilação em separado das partes do programa, em que o código executável de alguns dos módulos do programa residem em bibliotecas ou arquivos objetos externos, e as declarações dos componentes dessas biblioteca residem em arquivos de cabeçalhos, contendo apenas as declarações de símbolos (funções e variáveis) e definições de tipos e macros. Não é o caso deste programa pequeno, de modo que seria melhor ter apenas as definições, que servem também como declarações se as funções ainda não tiverem sido usadas como parte do código do programa.
Outro caso em que a declaração separada é necessária é quando você tem um par de funções que chamam reciprocamente uma a outra. Nesse caso, pelo menos uma das funções tem de estar declarada antes da definição da outra, finalmente seguida pela definição da primeira, como ilusta o seguinte exemplo.
// Declaração apenas. Necessária para que o compilador conheça o tipo da função e seus parâmetros (no caso, apenas um) na hora em que ela for invocada.
void pong(unsigned);
// Declaração e definição numa tacada só.
void ping(unsigned n){
puts("ping");
pong(n);
}
// Definição, que tem de corresponder exatamente à declaração feita anteriormente.
void pong(unsigned n){
puts("pong");
ping(n-1);
}
Um aspecto que salta aos olhos de gente mais velha, tal como eu, é que o hábito de definir funções depois da definição de
main () (que, por sinal, você não declara à parte da definição) parece muito ser um resquício histórico da linguagem B, que os primeiros programadores em C, que tinham sido usuários de B, mantiveram por um tempo mas que não tem necessidade nenhuma hoje em dia (e não era estritamente necessária nem mesmo na própria B), e que também é distinto de diversas outras linguagens em uso. Esse hábito de B era porque essa função não tinha tipos de dados distintos, e prescindia de declarações em muitos casos, de modo que quando algo com cara de função aparecia no código fonte, o compilador já entendia que cedo ou tarde uma função correspondente apareceria. Eu falo um pouco a respeito disso numa resposta minha no seguinte tópico:
https://www.vivaolinux.com.br/topico/C-C++/Duvida-sobre-a-funcao-main-Iniciante .
• Funções de limpeza de
buffer devem ser sempre vistas com um pouco de suspeição, e usadas apenas quando estritamente necessário e com muito cuidado. Muitas versões, incluindo a sua, têm o problema de que se o
buffer já estiver supostamente “limpo”, ela vai provocar o descarte de uma linha inteira — uma função com esse comportamento provavelmente deveria ter outro nome, tal como
discard_until_eol () ou coisa parecida. E a sua versão tem um
bug adicional, que é o fato de não tratar eventuais erros de leitura ou de fim prematuro de dados de entrada; se tal situação ocorrer, ela vai travar o programa num
loop infinito.
• Criar a variável adicional
maiorSalario (na verdade, aproveitando o nome da função original, que você mudou para
getMaiorSalario ), dentro de
main () é meio redundante (ainda mais atribuindo um valor inicial que vai ser descartado em seguida). Acho inclusive que seria interessante mostrar que o valor de retorno da chamada à função pode ser utilizado como argumento de funções, incluindo
printf ().
• A implementação de
getMaiorSalario () está muito mais extensa do que o necessário. Fazendo com
if /
else , nenhuma daquelas chaves seria necessária. Mas o jeito mais simples de fazer é simplesmente o seguinte.
double getMaiorSalario(double s1, double s2){
return s1>=s2? s1: s2;
}
... Então Jesus afirmou de novo: “(...) eu vim para que tenham vida, e a tenham plenamente.” (João 10:7-10)