Stoa :: C/C++ :: Blog :: Histórico

Novembro 2007

Novembro 07, 2007

user icon

Introdução à programação em C/C++ - parte 3

 

Retomemos o exemplo da parte 1 desta introdução:

 

1: #include<stdio.h>

2:

3: int main() {

4:     printf("\nOla, mundo da programacao!!!\n");

5:     return 0; }

6:

 

Na parte 2 da introdução vimos que a primeira linha deste código fonte refere-se à inserção, nesse ponto do programa, de um outro arquivo, o “stdio.h”. Vimos também que essa inserção corresponde à funcionalidade de uma biblioteca de funções. Caso você queira informações sobre esse tópico, leia a parte 2 desta introdução.

 

A segunda linha do código fonte do programa exemplificado acima está em branco; isso significa que nenhuma instrução será executada pelo computador, que ficará ocioso por um tempinho? Na verdade, não - o compilador C, como por exemplo o GCC, irá ignorar linhas em branco. Isto é, durante a tradução do código fonte para o arquivo executável, essa linha em branco será suprimida, e, para o computador, será como se ela não existisse.

 

Entretanto, essa linha em branco é uma das muitas coisas que um programador deve fazer que não tem um efeito computacional imediato: a linha em branco cumpre a função de tornar o código mais legível para humanos.

 

A vantagem dessa estratégia é que um código fonte mais legível para humanos é também um código fonte que será mais facilmente compreendido e modificado, e, inclusive, melhorado.

 

A legibilidade de um código fonte depende de dois fatores, essencialmente: do estilo de codificação e da documentação do código. Existem muitos estilos de codificação, e a maioria deles é muito boa, e, portanto, a principal preocupação estilística de um programador que quer escrever um código legível é a de usar apenas um estilo de codificação em cada projeto.

 

O estilo de codificação determina coisas como se as variáveis serão em maiúsculas, minúsculas ou com algumas letras maiúsculas e outras minúsculas; se os nomes de variáveis e de funções serão em português (ou em outra língua “estrangeira”) e não em inglês; se a “indentação” (ou seja, a distância entre a margem esquerda e o texto do código) será feita incrementalmente ou se será uniforme, entre outras opções que, inicialmente, parecem ser tolas, especialmente se formos avisados do fato de que essas opções não têm efeito na execução do programa. Entretanto, essas opções, e, mais importante que as opções específicas adotadas, sua uniformidade, levam a uma melhor legibilidade do código fonte, e portanto a uma maior qualidade do software como texto que deverá ser modificado de tempos em tempos.

 

Por exemplo: o código abaixo é muito ilegível:

 

int i = j =3;

for(;i<j*3;i++,j--) { printf(“\ni = %d, j = %d”,i,j); scanf(“%d”,&i);}

calcvalorMEDIO(n,i,j);

 

Já o mesmo código, escrito de maneira mais bem-comportada, é mais fácil de ser compreendido:

 

int i,j;

for(min = 3, max = 3; min < (max*3); max--) {

    printf(“\ni = %d, j = %d”, min, max);

    scanf(“%d”, &min); }

media(num, min, max);

 

A diferença entre os dois casos é que em um deles foram utilizados nomes de variáveis e de funções com um mesmo padrão, e procurando usar para seus nomes palavras que sugiram qual é seu conteúdo. As expressões dentro do laço for foram indentadas cada uma em uma linha. Algumas partes supérfluas como a atribuição de valor a i na instrução de incremento do laço for foram eliminadas.

 

Um outro recurso muito importante para a legibilidade do código fonte é a documentação desse código. O principal elemento da documentação do código fonte é formado pelos comentários inseridos no código fonte. Em C, um comentário tem a seguinte sintaxe: tudo o que estiver contido dentro de um par /* */ é considerado comentário. Exemplos:

 

/* Isto eh um comentario */

/* Este

tambem

eh um unico

comentario */

/* Comentario */ printf(“Isto eh codigo\n”); /* Outro comentario */

 

A única exceção é quando o par /* */ vem dentro de uma sequência de caracteres a ser impressa, como as sequências usadas nos comandos printf:

 

printf(“Isto sera impresso: /* Isto nao eh comentario */, tal como previsto\n”);

 

Por que comentar, se o código está ali para quem quiser ler?

Nem sempre o código é a maneira mais fácil de entender o que o código faz. Por exemplo, numa função muito comprida textualmente, pode ser difícil compreender o que a função faz lendo seu código, pois será necessário interpretar cada comando de um conjunto extenso de comandos. Se houver um comentário que diga, em bom português (ou em outra língua estrangeira, ou em bom inglês) o que faz a função, nossa tarefa será enormemente facilitada, pois poderemos “decifrar” a função um pouco de cada vez, sem ter que interpretar toda ela de uma só vez para descobrir o que faz.

 

Somente para reforçar o que já foi dito, é importantíssimo comentar seu código pois bons programas são atualizados, enquanto que os maus programas são abandonados - e portanto se você quer escrever bons programas, que sejam utilizados o máximo possível, você deve escreve-los com abundantes comentários, pois dessa maneira você irá facilitar a tarefa da pessoa que tiver que corrigir ou melhorar o seu código (que será, possivelmente, você mesmo!). Além disso, uma boa parte da programação em C/C++ é feita em torno de bibliotecas de funções, ou seja, com vistas ao reuso do código. De modo que se você escrever uma função bem-comentada hoje, e quiser utiliza-la num outro programa seu daqui a alguns meses, bastará ler os comentários que você escreveu para identificar qual o propósito daquela função e como ela funciona.

 

Tá bom, tá bom, você me convenceu. Mas quando comentar?

Sempre que houver um trecho complicado ou representativo em seu programa. Por complicado, eu quero dizer difícil de ler. Por exemplo, laços que utilizam variáveis de controle e realizam várias modificações nos dados, devem ser comentados. Por representativo, eu quero dizer partes do seu código que são “pedaços” razoavelmente bem delimitados do programa: a entrada de dados, o processamento e a saída devem ser identificados através de comentários. Além disso, as variáveis utilizadas devem ter nomes sugestivos e comentários explicativos de o que será armazenado nelas. As funções devem comentar o que fazem, quais suas entradas de dados e qual a sua saída.

 

Exemplo: Considere o seguinte código. O que ele faz?

 

#include<stdio.h>

 

int fat(int n);

 

int main() {

    int n;

    scanf(“%d”, &n);

    printf(“%d\n”, fat(n)); }

 

int fat(int n) {

    if (n == 1)

        return 1;

    else

        return n*fat(n-1); }

 

É verdade que ele também está um tanto ilegível devido aos nomes poucos sugestivos de variáveis e função. Então tente dizer o que faz o seguinte código:

 

/*****************************************************

* fatorial.c - calcula e imprime o fatorial de um inteiro *

*****************************************************/

 

#include<stdio.h>

 

int fatorial(int fatorando);

 

int main() {

/* Variaveis:

numero armazena o numero cujo fatorial iremos calcular.

fat armazena o valor do fatorial de numero. */

    int numero, fat;

 

    /* Entrada de dados */

    printf(“Digite um no. inteiro cujo fatorial quer calcular: “);

    scanf(“%d”, &numero);

 

    /* Processamento */

    fat = fatorial(numero);

 

    /* Saida de resultados */

    printf(“O fatorial de %d eh %d.”, numero, fat);

    return 0; }

 

int fatorial(int fatorando) {

    /* Funcao fatorial:

    Recebe um numero inteiro,

    Calcula seu fatorial,

    Retorna esse fatorial. */

 

    /* Se o inteiro recebido for 1, retorne 1, afinal 1! = 1.

    Se o inteiro recebido for maior que 1, retorne esse numero vezes o fatorial desse numero menos 1, afinal n! = n*(n-1)! */

    if (fatorando == 1)

        return 1;

    else

        return fatorando*fatorial(fatorando-1); }

 

A segunda versão do exemplo é muito mais legível do que a primeira versão. Isso significa que se eu quiser utilizar essa função fatorial em outros programas, ou caso eu queira melhorar esse programa para que, por exemplo, calcule o fatorial de número reais, eu terei muito maior facilidade de fazer isso, pois o código fonte está legível.

 

Palavras-chave: Documentação de Código, Estilo de Codificação, Introdução à Programação

Postado por Renato Callado Borges em C/C++ | 0 comentário

Novembro 21, 2007

user icon

Retomemos o exemplo da parte 1 desta introdução:

 

1: #include<stdio.h>

2:

3: int main() {

4:     printf("\nOla, mundo da programacao!!!\n");

5:     return 0; }

6:

 

 

Na parte 2 da introdução vimos que a primeira linha deste código fonte refere-se à inserção, nesse ponto do programa, de um outro arquivo, o “stdio.h”. Vimos também que essa inserção corresponde à funcionalidade de uma biblioteca de funções. Caso você queira informações sobre esse tópico, leia a parte 2 desta introdução.

 

 

Na parte 3 da introdução explicamos qual a importância do estilo de programação e como documentar seu código fonte. Para maiores detalhes sobre esses temas, consulte a parte 3 da introdução.

 

 

Nesta parte da introdução, falaremos mais uma vez sobre reuso de código, mas desta vez falaremos da estrutura mais essencial da linguagem C, as funções.

 

 

Uma função tem o mesmo objetivo que uma biblioteca: ela é usada para evitar que um código que deve ser utilizado várias vezes tenha que ser escrito várias vezes. Em outras palavras, as funções permitem o reuso do código.

 

 

Uma função pode ser entendida como uma "caixa preta" que recebe algo, processa esse algo recebido, e então nos devolve alguma outra coisa. Podemos encarar muitas coisas como uma caixa preta. Por exemplo, se um garçom for uma caixa preta, nós enviamos para ele algum dinheiro e uma ordem, por exemplo, "Um guaraná" ele nos retorna uma lata de refrigerante. O que ocorre aqui é que o garçom faz algo de útil para nós, mas que poderíamos ter feito nós mesmos.

 

 

Vamos imaginar que um jornal seja uma caixa preta, uma função. Um jornal recebe nosso dinheiro e nos retorna informação. Poderíamos nós mesmos investigar cada coisa? Em teoria, sim, mas na prática não. O mesmo ocorre na programação: aquilo que está numa função poderia ser reescrito tantas vezes quanto for necessário, mas na prática isso se torna impraticável.

 

 

De modo que as funções têm como objetivo realizar um conjunto de operações cujo resultado seja útil para nós. No caso do C/C++, as funções fazem isso utilizando como material de construção as operações básicas da linguagem e outras funções.

 

 

No nosso exemplo, temos duas funções: a função main e a função printf. A função printf é uma função de biblioteca, ela está contida na biblioteca stdio. É para poder utilizar essa biblioteca que nós incluímos essa biblioteca na primeira linha do exemplo. No nosso exemplo, a função printf é chamada. Já a função main é definida. Existe uma terceira maneira de uma função "aparecer" no nosso código fonte que é quando uma função é declarada. Vejamos cada uma dessas maneira em que as funções podem aparecer.

 

 

O objetivo de criar funções é usa-las depois. Se não fosse assim, seria como se quisessemos escrever receitas que nunca iríamos cozinhar. Portanto, a maneira mais importante em que uma função pode aparecer no código fonte é quando ela é usada, ou seja, é quando a função é chamada.

 

 

No caso de nosso exemplo, a função printf é chamada. Leia abaixo uma cópia da linha relevante do exemplo:

 

 

4:     printf("\nOla, mundo da programacao!!!\n");

 

 

Em primeiro lugar, uma chamada a função é uma instrução como outra qualquer, e portanto ela é terminada com um ponto-e-vírgula. Em segundo lugar, note que esta chamada a função é, metaforicamente, como uma frase no imperativo: printf, faça tal coisa! Não há diálogo, isto é, não se espera que printf nos fale algo de útil, que é o mesmo que dizer que printf não tem valor de retorno*. Veremos daqui a pouco um exemplo de uma função que tem valor de retorno.

 

 

Outra coisa importantíssima a se observar na chamada a printf é que nós a chamamos com um certo parâmetro - que em programês é chamado de argumento. No nosso exemplo, o argumento é "\nOla, mundo da programacao!!!\n". Esse argumento é portanto uma frase**. Qualquer variável de C/C++ pode ser um argumento - um caractere, um número, etc., e não apenas frases.

 

 

O que acontece quando o computador encontra uma chamada a função? O controle é passado para a função, isto é, quando o computador encontra uma chamada a função, ele passa a executar as instruções encontradas na definição da função, atribuindo a cada um dos argumentos da função o valor correspondente na chamada à função. Quando o controle chega no final da função, ele é retornado para o ponto imediatamente posterior à chamada da função.

 

 

No nosso exemplo, também aparece a função main. No caso, o que aparece é a  definição dessa função. Uma definição é composta pelo cabeçalho, que identifica o tipo de retorno, o nome da função e seus argumentos. No nosso exemplo:

 

 

3: int main() {

 

 

Portanto o tipo de retorno é int (ou seja, uma variável número do tipo inteiro), o nome da função é main e os argumentos são o conjunto vazio, isto é, a função não recebe argumentos.

 

 

Tudo o que está dentro do abre e fecha chaves imediatamente posteior ao cabeçalho corresponde ao corpo da função, ou seja, às instruções que serão executadas sempre que a função for chamada. No nosso caso, este é o corpo da função:

 

 

{

4:     printf("\nOla, mundo da programacao!!!\n");

5:     return 0; }

 

 

Portanto a função main executa duas instruções: a printf (que é uma chamada a função, o que demonstra uma das maneiras em que funções podem ser aninhadas) e a instrução return, que faz com que a função atualmente sendo executada seja terminada retornando o valor imediatamente posterior à palavra return. Portanto, a função main de nosso exemplo termina retornando o valor 0 (zero). É isto o que significa uma função ter um valor de retorno: quando ela é encerrada, ela retorna para o ponto em que foi chamada um valor indicado pela palavra-chave return.

 

 

Para finalizar, é importante mencionar que em C/C++ um programa compilado sempre "começa" sua execução com uma chamada a uma função chamada main. Isto significa que todo programa em C/C++ deve possuir uma função main, e essa função é executada toda vez que o programa é chamado pelo sistema operacional. É por isso que ao compilarmos e executarmos o exemplo, a primeira coisa que o computador fará será a primeira instrução da função main, e irá seguir essa função instrução por instrução até que a função main termine ou uma chamada à função exit() seja encontrada (a função exit termina um programa imediatamente).

 

 

* Isso na verdade é falso - printf tem valor de retorno mas geralmente esse valor é ignorado. Printf retorna 0 se não foi possível imprimir a string em stdout e 1 se foi possível.

 

 

** Um aviso aos incautos: frases em C/C++ são coisas bem complicadas. Estou simplificando aqui apenas para explicar sobre funções com o mínimo de complicações.

Postado por Renato Callado Borges em C/C++ | 0 comentário

Novembro 28, 2007

user icon

Nesta parte da introdução discutiremos superficialmente um dos mais importantes recursos da linguagem C++, mas que não existe em C. Trata-se das classes.

 

Uma classe nada mais é do que uma estrutura que tem como partes tanto dados quanto funções. As classes são um recurso importante pois "encapsulam" as informações e as funcionalidades a respeito de uma certa "coisa" de maneira organizada e reusável.

 

Uma classe define algo que poderia ser descrito como sendo um padrão ou um plano: aquilo que uma classe descreve pode ser produzido muitas vezes, da mesma maneira que uma planta de um edifício pode corresponder a muitas construções, ou o desenho de uma máquina pode servir de base para a produção de muitas máquinas iguais. O desenho ou a planta é chamado de classe, e suas "realizações" são chamadas de objetos.

 

Agora você deve ser capaz de entender o que quer dizer a expressão "objetos são instâncias de classes". Essa expressão quer dizer que assim como várias casas iguais são feitas a partir de uma mesma planta, vários objetos iguais são feitos a partir de uma mesma classe. O verbo "instanciar" é utilizado em programês para indicar a instrução do programa que cria um objeto a partir de uma classe.

 

Por exemplo:

 

carro fusca;

 

Nessa instrução (que é uma "criação de instância de classe", em programês), um objeto de classe "carro" é criado e a esse objeto é atribuído o nome "fusca". Que dados e que funções tem o objeto fusca depende de quais são os membros da classe carro.

 

Por exemplo:

 

classe carro {

    int velocidade_maxima;

    char fabricante[30];

    void ir_ate_usp();

    void voltar_para_casa(); };

 

Neste caso, a classe carro tem como membros duas variáveis e duas funções. Portanto cada objeto de tipo carro terá essas duas variáveis e essas duas funções. Mas veja bem: cada variável de um objeto é única daquele objeto.

 

Por exemplo:

 

carro fusca;

fusca.velocidade_maxima = 90;

carro porsche;

porsche.velocidade_maxima = 290;

if (fusca.velocidade_maxima == porsche.velocidade_maxima)

    printf("Compre esse fusca!!!\n");

 

Neste exemplo, a instrução printf nunca será executada. Por que? O que este exemplo mostra é que a variável "velocidade_maxima" do objeto fusca é diferente da variável "velocidade_maxima" do objeto porsche. Isso é equivalente a dizer que se a classe fosse a planta de uma casa, e cada objeto uma casa realmente construída a partir daquela planta, a cozinha da casa A é diferente da cozinha da casa B, ainda que em ambas haja canos, ralos, torneiras, pias, etc, nas mesmas posições relativas. Em outras palavras, quando dizemos que objetos de uma mesma classe têm os mesmos membros, queremos dizer que cada objeto de uma mesma classe possui o mesmo formato, mas não que eles tenham a mesma variável com o mesmo conteúdo.

 

Apenas para reforçar (desculpem a insistência, mas é um erro muito comum!): os objetos de uma mesma classe são como os animais pertencentes a uma mesma espécie. Assim como dizemos que todo elefante tem tromba, dizemos que todo objeto da classe carro tem a variável velocidade_maxima. Mas cada elefante tem sua própria tromba, assim como cada objeto carro tem sua propria variável velocidade_maxima! Portanto os membros da classe são como os atributos de uma espécie: ter tromba, ser mamífero, ter orelhas grandes, etc. Mas cada elefante terá sua própria tromba de comprimento específico, será filhote e/ou pai/mãe de outros elefantes específicos, sua orelha poderá abana-lo em momentos diferentes daqueles em que um outro elefante irá abanar-se, etc.

 

Vamos agora explicar brevemente a utilidade de termos funções encapsuladas dentro de classes. Antes de mais nada, que fique bem claro que aquilo que é feito com classes pode ser feito com programação "normal" sem classes (que é geralmente chamada de programação estruturada*). O que ocorre é que certas coisas que podem ser feitas de maneira muito simples através de classes só podem ser feitas estruturadamente com muitas e complexas linhas de código.

 

Como dissemos antes, uma classe pode ter como membros tanto variáveis quanto funções.

 

Por exemplo:

 

classe investidor {

    float grana;

    float risco_aceitavel;

    bool grana_na_poupanca = false;

    void investir_poupanca(float juros);

    void investir_bolsa(float indice); };

 

Neste exemplo, podemos escrever uma função que, recebendo os índices de juros de poupança e o valor da expectativa de rendimentos da bolsa escolhe qual o melhor investimento a ser feito de acordo com a aceitação de riscos de cada investidor. Neste caso:

 

void fazer_investimentos(investidor clientes[], int nclientes, float juros_poupanca, float expectativa_bolsa, float risco_bolsa) {

for (int i = 0; i < nclientes; i++)

    if (clientes[i].grana_na_poupanca == false)

        if (clientes[i].risco_aceitavel <= risco_bolsa)

            clientes[i].investir_bolsa();

        else {

            clientes[i].investir_poupanca(juros_poupanca);

            clientes[i].grana_na_poupanca = true; } }

 

Esta função irá percorrer um vetor de clientes (note que esse vetor tem o tipo "investidor") e, para cada cliente, caso seu dinheiro não esteja investido na poupanca, irá verificar se o risco de investir na bolsa é aceitável e, caso seja, irá investir na bolsa. Caso o risco seja alto demais, irá investir na poupança. Para que o exemplo seja completo, seria necessário guardar a data do investimento na poupança, e verificar todo dia se algum cliente já completou um mês com seu dinheiro na poupança, e se portanto esse dinheiro já está liberado para ser investido na bolsa novamente.

 

A chamada a "investir na poupança" pode ser mais ou menos assim:

 

void investidor::investir_poupanca(float juros) {

    grana = grana * (1 + juros/100); }

 

O cabeçalho dessa função é ligeiramente diferente daquele que seria o cabeçalho da função caso ela não fosse membro de uma classe, mas isso se restringe à declaração de escopo (investidor::). Isso é um detalhe que exploraremos quando falarmos da sintaxe das classes. O importante é notar que na função investir_poupanca nós utilizamos a variável grana sem declará-la, ou seja, nós utilizamos a variável grana do objeto a partir do qual chamamos a função-membro.

 

Ou seja, se eu tiver um objeto investidor chamado bancoIrreal:

 

investidor bancoIrreal;

bancoIrreal.grana = 1000;

j = 0.5;

 

e chamar a função investir_poupanca da seguinte maneira:

 

bancoIrreal.investir_poupanca(j);

 

Essa função irá utilizar a variável grana definida para o objeto bancoIrreal, sem precisar declará-la. Isso decorre do fato de que os membros de uma classe são visíveis para todos os membros dessa classe.

 

A visibilidade de um membro é controlável em C++. Mas esse já é um tópico que deixaremos para uma discussão futura das classes. Basta, no momento, saber que o padrão é que o membro sejá visível, ou, em programês, público. (Ele também poderia ser privado ou restrito).

 

* A programação estruturada é toda programação na qual se utilizam estruturas como funções ou procedimentos, mas não classes ou objetos. Portanto C é uma linguagem estruturada, enquanto que C++ pode ser utilizada estruturadamente ou orientada a objetos.

Palavras-chave: classes, objetos

Postado por Renato Callado Borges em C/C++ | 0 comentário