Stoa :: C/C++ :: Blog

Setembro 03, 2010

user icon

Como usar argumentos na linha de comando em C/C++?

Vejamos alguns exemplos de um sistema GNU/Linux.

Primeiro: o comando ls lista o conteúdo de um diretório no GNU/Linux.

rborges@bio03:~/Desktop$ ls
alsa-driver-1.0.23  alsa-driver-1.0.23.tar.bz2  ati-driver-installer-10-7-x86.x86_64.run  firefox-3.6.8.tar.bz2  questionaire-glite32-V2.odt  virtualbox-3.2_3.2.8-64453~Debian~lenny_amd64.deb

Existem muitos parâmetros que podemos "passar" para o ls, e basta para isso adicionar o argumento após o nome do comando, separando com espaços. Por exemplo, -l mostra a listagem em formato longo:

rborges@bio03:~/Desktop$ ls -l
total 161676
drwxr-xr-x 28 root    root         4096 2010-08-24 22:19 alsa-driver-1.0.23
-rw-r--r--  1 rborges rborges   3337645 2010-08-24 22:15 alsa-driver-1.0.23.tar.bz2
-rwxr--r--  1 rborges rborges 101178081 2010-08-23 06:40 ati-driver-installer-10-7-x86.x86_64.run
-rw-r--r--  1 rborges rborges  10624411 2010-08-25 07:11 firefox-3.6.8.tar.bz2
-rw-r--r--  1 rborges rborges     48684 2010-09-02 06:25 questionaire-glite32-V2.odt
-rw-r--r--  1 rborges rborges  50178030 2010-08-25 21:13 virtualbox-3.2_3.2.8-64453~Debian~lenny_amd64.deb

Outro parãmetro é o -h, que mostra o tamanho dos arquivos em formato apropriado para humanos:

rborges@bio03:~/Desktop$ ls -l -h
total 158M
drwxr-xr-x 28 root    root    4.0K 2010-08-24 22:19 alsa-driver-1.0.23
-rw-r--r--  1 rborges rborges 3.2M 2010-08-24 22:15 alsa-driver-1.0.23.tar.bz2
-rwxr--r--  1 rborges rborges  97M 2010-08-23 06:40 ati-driver-installer-10-7-x86.x86_64.run
-rw-r--r--  1 rborges rborges  11M 2010-08-25 07:11 firefox-3.6.8.tar.bz2
-rw-r--r--  1 rborges rborges  48K 2010-09-02 06:25 questionaire-glite32-V2.odt
-rw-r--r--  1 rborges rborges  48M 2010-08-25 21:13 virtualbox-3.2_3.2.8-64453~Debian~lenny_amd64.deb

Uma outra técnica sobre parâmetros é que você muitas vezes precisa usar duas variáveis para um parâmetro: a primeira especifica a variável, a segunda o valor. Para o exemplo, vou usar o ping, que é um programa que manda um pacote de dados para uma máquina da rede e imprime quanto tempo levou para obter umna resposta.

rborges@bio03:~/Desktop$ ping www.google.com.br
PING www.l.google.com (173.194.34.104) 56(84) bytes of data.
64 bytes from lga15s15-in-f104.1e100.net (173.194.34.104): icmp_seq=1 ttl=54 time=179 ms
64 bytes from lga15s15-in-f104.1e100.net (173.194.34.104): icmp_seq=2 ttl=53 time=182 ms
64 bytes from lga15s15-in-f104.1e100.net (173.194.34.104): icmp_seq=3 ttl=52 time=178 ms
64 bytes from lga15s15-in-f104.1e100.net (173.194.34.104): icmp_seq=4 ttl=52 time=178 ms
64 bytes from lga15s15-in-f104.1e100.net (173.194.34.104): icmp_seq=5 ttl=52 time=178 ms
64 bytes from lga15s15-in-f104.1e100.net (173.194.34.104): icmp_seq=6 ttl=54 time=178 ms
^C
--- www.l.google.com ping statistics ---
6 packets transmitted, 6 received, 0% packet loss, time 5018ms
rtt min/avg/max/mdev = 178.139/179.419/182.864/1.580 ms

Note que precisei usar o Control+c para terminar o programa - ele por default roda infinitamente. (Quando se pára um programa com Control+c em GNU/Linux, em geral aparace a string "^C" na saída do programa).

Então vou usar o parâmetro -c, que especifica uma contagem de vezes a se pingar a outra maquina, e exige um valor numérico no parâmetro seguinte.

rborges@bio03:~/Desktop$ ping www.google.com.br -c 1
PING www.l.google.com (173.194.34.104) 56(84) bytes of data.
64 bytes from lga15s15-in-f104.1e100.net (173.194.34.104): icmp_seq=1 ttl=52 time=178 ms

--- www.l.google.com ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 178.929/178.929/178.929/0.000 ms

Agora o ping só manda um pacote e imprime o resultado. Se eu tentar usar só o -c, sem um valor, ou com um valor inválido, uma boa implementação do ping irá gerar uma mensagem de erro (uma má implementação vai travar seu micro e exigir que você reboote ;)

rborges@bio03:~/Desktop$ ping -c
ping: option requires an argument -- c
Usage: ping [-LRUbdfnqrvVaA] [-c count] [-i interval] [-w deadline]
            [-p pattern] [-s packetsize] [-t ttl] [-I interface or address]
            [-M mtu discovery hint] [-S sndbuf]
            [ -T timestamp option ] [ -Q tos ] [hop1 ...] destination
rborges@bio03:~/Desktop$ ping -c um_monte_de_vezes
ping: bad number of packets to transmit.

A pergunta agora é: como fazer a mesma coisa nos meus programas em C/C++?

Resposta: usando as variáveis argc e argv.

(Dica: para procurar outros documentos sobre esse tópico no Google, pesquise "c cpp argc argv")

Estas variáveis são variáveis da função main. Até onde sei, a maneira de implementá-las é sempre a mesma:

int main(int argc, char** argv)

argc é o número de argumentos passados na linha de comando - creio que o nome vem do inglês "argument count" - " contagem de argumentos".

argv é um vetor de ponteiros (por isso a dupla indireção com dois asteriscos) e contém o texto dos argumentos passados - creio queo nome vem do inglês "argument vector" - "vetor de argumentos".

Um exemplo bem simples, que apenas imprime os argumentos:

#include <iostream>

int main(int argc, char** argv) {

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

        std::cout << "argv[" << i << "] = " << argv[i] << std::endl;

    return 0; }

E algumas execuções:

rborges@bio03:~emacs exemplo-argcargv.cpp
rborges@bio03:~/Desktop$ g++ -o exemplo exemplo-argcargv.cpp
rborges@bio03:~/Desktop$ ./exemplo
argv[0] = ./exemplo
rborges@bio03:~/Desktop$ ./exemplo opcao1
argv[0] = ./exemplo
argv[1] = opcao1
rborges@bio03:~/Desktop$ ./exemplo opcao1 op2
argv[0] = ./exemplo
argv[1] = opcao1
argv[2] = op2
rborges@bio03:~/Desktop$ ./exemplo opcao1 op2       op3
argv[0] = ./exemplo
argv[1] = opcao1
argv[2] = op2
argv[3] = op3
rborges@bio03:~/Desktop$ ./exemplo -1 --2 ---3 ----4
argv[0] = ./exemplo
argv[1] = -1
argv[2] = --2
argv[3] = ---3
argv[4] = ----4

Note que o argv[0] é sempre o comando com que o usuário executou o programa.

Creio que vou finalizar este post com a questão "mas pra quê serve isso?"

Bem, certamente se a intenção é escrever um programa com interface gráfica, aonde a entrada de dados é feita pelo usuário, há pouca oportunidade de usar este recurso.

Mas para quem escreve programas que rodam da linha de comando, ou programas que são executados primordialmente por outro programa, este recurso é a maneira mais simples de passar dados entre um programa e outro.

Exercício: escreva um programa que recebe uma string e imprime essa string n vezes, sendo que n é um parâmetro da linha de comando.

Exercício: aprimore o programa do exercício anterior para que receba dois parâmetros: n (número de vezes para imprimir a string) e b (número de espaços em branco a imprimir antes da string).

Exercício: aprimore o programa do exercício anterior para que possa receber os dois parâmetros em qualquer ordem.

Palavras-chave: argc, argumentos, argv, CLI, linha de comando, ls, parâmetros, ping

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

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 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

Outubro 31, 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:

 

Este exemplo é típico das introduções à programação em C/C++, pois é um dos menores programas que é possível fazer nessa linguagem, e portanto serve como paradigma de qual é a estrutura que tem de estar necessariamente presente em um código-fonte de um programa em C.

 

As linhas do programa foram numeradas para facilitar o comentário sobre elas, que é o que será feito agora. Num código fonte de C para ser realmente compilado, não se deve numerar as linhas.

 

Na linha 1, o comando que é dado é um comando de pré-processador que ordena ao compilador que inclua, naquele ponto do código-fonte, todo o conteúdo do arquivo entre os símbolos < e >. Em outras palavras, é como se tivessemos aberto o arquivo "stdio.h" em nosso editor de textos e copiado e colado todo o seu conteúdo em nosso arquivo de código fonte.

 

Ora, portanto esse exemplo "mínimo" pode ser bem substancial, a depender do tamanho de "stdio.h"! Isso serve de aviso aos iniciantes: em programação muitas vezes são usados comandos como esse "include" (que, em português seria traduzido como "incluir") que com uma única linha "incham" o nosso programa com muitas e muitas páginas de código.

 

Mas por que incluir um outro arquivo?

A grande, na verdade a gigantesca vantagem de incluir um outro arquivo é o chamado "reuso" de código fonte. "Reuso" significa "utilizar novamente", ou seja, através da inclusão de outros arquivos em nosso código fonte, podemos utilizar comandos criados em outro momento, possivelmente por outras pessoas. É uma espécie de divisão do trabalho: João escreve as funções que lidam, por exemplo, com palavras, Mário utiliza essas funções escritas por João para escrever funções que lidam com frases, Zeca utiliza essas funções de João e de Mário para escrever funções que lidam com parágrafos e finalmente eu utilizo as funções de João, Mário e de Zeca para escrever um programa que lida com um texto de vários parágrafos, frases e palavras.

 

Isso que acabamos de ver são chamadas funções de biblioteca. Em inglês isso é chamado de library functions. A idéia por trás desse nome é que as funções seriam pequenos pedaços de código fonte que são armazenados como os livros de uma biblioteca, e que quando um programador precisa utilizar um desses livros (ou seja, uma dessas funções), ele "pede emprestado" esse livro da biblioteca e o utiliza.

 

Por exemplo, digamos que "motor" seja o nome de uma biblioteca que armazena as funções que efetuam comandos sobre um motor de um carrinho ligado ao computador por meio de rádio. Suas funções seriam "ligar", "acelerar", que pode ser negativa ou positiva e "desligar". Um programador poderá utilizar essa biblioteca de funções para criar os comandos de um carrinho mais inteligente, que por exemplo percorra uma pista de corrida predefinida, digamos um quadrado. Nesse caso, o programa será uma série de chamadas às funções da biblioteca, mais ou menos assim:

 

ligar

acelerar +10

acelerar -9

acelerar +9

acelerar -9

acelerar +9

acelerar -9

acelerar +9

acelerar -9

acelerar +9

acelerar -10 

desligar

 

É necessário desacelerar para virar nas curvas; acelera-se para percorrer as retas (os lados do quadrado) o mais rápido possível. Este programa não faria muito sentido se não houver uma forma de controlar a direção do carrinho, por exemplo usando a biblioteca "direcao", que exporta as funções "direita" e "esquerda". Aí nosso exemplo seria

 

ligar

acelerar +10

acelerar -9

esquerda

acelerar +9

acelerar -9

esquerda

acelerar +9

acelerar -9

esquerda

acelerar +9

acelerar -9

esquerda

acelerar +9

acelerar -10 

desligar

 

Para que o exemplo seja plenamente compreensível, seria necessário controlar quando o carrinho "vira à esquerda". Isto ocorreria após andar um espaço predefinido, por exemplo, o lado da pista, que seria, digamos, 10 cm. Isto poderia ser feito com a função "odômetro" da biblioteca "painel". Aí nosso exemplo seria:

 

ligar

acelerar +10

andar até odômetro = 5 cm

acelerar -9

esquerda

acelerar +9

andar até odômetro = 15 cm

acelerar -9

esquerda

acelerar +9

andar até odômetro = 25 cm

acelerar -9

esquerda

acelerar +9

andar até odômetro = 35 cm

acelerar -9

esquerda

acelerar +9

andar até odômetro = 40 cm

acelerar -10

desligar

 

Apenas para irmos nos acostumando com a sintaxe de C, aqui está o código fonte desse exemplo em C:

 

#include”motor.h”

#include”direcao.h”

#include”painel.h”

 

int main() {

            odometro odo;

 

ligar();

acelerar(10);

while(odo < 5);

acelerar(-9);

esquerda();

acelerar(9);

while(odo < 15);

acelerar(-9);

esquerda();

acelerar(9);

while(odo < 25);

acelerar(-9);

esquerda();

acelerar(9);

while(odo < 35);

acelerar(-9);

esquerda();

acelerar(9);

while(odo < 40);

acelerar(-10);

desligar();

return 0; }

 

Não se preocupe em entender completamente cada detalhe desse código; o importante é perceber que existe uma modularidade na programação em C: pequenos trechos de código são copiados de outros lugares e “justapostos” para produzir um novo programa.

 

Portanto, o uso de bibliotecas em um programa C faz com que a tarefa de programação se assemelhe à eletrônica, ou à mecânica, no aspecto de que o programador utiliza peças "prontas", feitas por outras pessoas, e combina essas peças para produzir um software novo, da mesma maneira que um engenheiro ou técnico eletrônico cria novos aparelhos utilizando componentes comercialmente disponíveis, ou um mecânico cria aparelhos com motores e engrenagens compradas prontas.

 

Quanto a esse último quesito, o da comercialização das peças, é importante fazer uma distinção entre o software comercial "normal" e o software livre: as bibliotecas comerciais são compráveis e só podem ser utilizadas legalmente caso o programador pague sua "licença de uso". Por exemplo, para utilizar uma biblioteca de sintetização de voz comercial é necessário pagar uma taxa para os detentores do direito autoral sobre esse software. Mas no caso de software livre, essa taxa de comercialização é opcional. Note bem: opcional. E quem determina a opção é o detentor dos direitos autorais, também. Isso significa que uma biblioteca de software livre pode exigir pagamento para ser utilizada, também. Afinal de contas, para produzir a biblioteca é necessário pagar programadores, então é razoável que quem crie bibliotecas como negócio, mesmo que o faça como software livre, o faça mediante pagamento.

 

Então qual a diferença entre uma biblioteca livre e uma comercial? A diferença é que a biblioteca comercial não oferece seu código fonte à consulta enquanto que a biblioteca livre permite a leitura e modificação de seu código fonte, desde que quaisquer modificações sejam disponibilizadas como software livre.

 

Se software fosse carro, e bibliotecas fossem motores, é como se as bibliotecas comerciais fossem motores em que é proibido abrir o capô e fuçar no motor; só o fabricante pode consertar o motor se ele quebrar. O software livre é como o carro que conhecemos: podemos abrir e mexer ou pagar a alguém que entenda de mecânica para que faça isso por nós. Isso significa que o software livre é o único software que tem chance de sobreviver por um longo tempo: porque mesmo que a empresa que o criou venha à falência, poderemos utilizar um programador para que faça a manutenção e até mesmo a melhoria desse software para nós.

 

Além disso, alguns softwares livres são também esforços de colaboração internacional, como, por exemplo, os software da Gnu. Por serem feitos colaborativamente, esses softwares são melhorados continuamente: todos os dias, para não dizer que muitas vezes todas as horas, é lançada uma nova versão de algum software Gnu. Isto significa robustez, segurança, confiabilidade: nenhum software comercial em toda a história da computação foi desenvolvido com tanta qualidade em tão pouco tempo.

 

Bom, chega de proselitismo. No próximo texto eu explico outras linhas do exemplo inicial.

Palavras-chave: Bibliotecas de Funções, Introdução à Programação, Introdução ao C

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

Outubro 24, 2007

user icon

Bem-vindo(a)!

 

Estou escrevendo esta série de mensagens para que exista no Stoa um registro em português e totalmente livre ("as in free speech") sobre programação em C/C++.

 

Eu programo amadoristicamente em C++ há cerca de 15 anos, e, tal como muitos programadores, aprendi diretamente o C++, deixando o C "para depois". Por isso eu escrevo que este material é sobre C/C++: o intuito é que seja documentada especialmente a linguagem C, mas como eu conheço melhor a linguagem C++, pode acontecer de eu cometer algum deslize e escrever algo em C++ achando que funciona em C mas na verdade não funciona. Então fique atento, teste tudo e tenha em mente que livros como o de Kernighan e Ritchie é que são as referências canônicas para C.

 

Antes de mais nada, me desculpem os que esperam acompanhar estas mensagens "semana a semana" como um curso, mas eu não tenho como estruturar um curso de C/C++ e seguir esse planejamento ao pé da letra. O que irei fazer é publicar a cada semana uma mensagem sobre algum aspecto da programação em C/C++, tentando ir mais ou menos dos aspectos mais básicos para os mais avançados. Entretanto, como o objetivo é criar um material de referência, mesmo no começo da redação do material, que será sobre a sintaxe de C/C++, certos usos mais complicados da sintaxe serão apresentados, pois futuramente alguém que queira se informar sobre um uso complicado de certa parte da linguagem irá consultar as mensagens sobre essa parte da linguagem - de outra forma seria um horror procurar a informação necessária.

 

Numa introdução a programação, geralmente há uma seção inicial sobre a história do computador, e sobre a história da linguagem a ser aprendida. Irei poupá-los desse intróito: vamos direto pôr as mãos na massa.

 

Para escrever programas em C/C++, tudo o que é necessário é papel e caneta. Entretanto, para que esses programas se transformem em software efetivamente "rodado" num computador, é necessário que esse programa seja escrito de forma digitalizada e que esse arquivo de texto com o código do programa (chamado código-fonte) seja enviado para um programa chamado compilador, que faz a tradução de um arquivo texto (o código-fonte) para um arquivo executável (seu software).

 

Na verdade, a coisa pode ser ligeiramente mais complicada: o código-fonte pode se "esparramar" em diversos arquivos de texto, e o executável final pode também ser um conjunto de arquivos executáveis ou não, de acordo com as especificações da chamada ao compilador. Entretanto, até que abordemos neste curso como compilar programas em C/C++ de maneira complexa, uma introdução simplificada será suficiente.

 

Como dito anteriormente, para fazer um programa é necessário escreve-lo numa linguagem de programação, no nosso caso em C, utilizando apenas expressões válidas nessa linguagem. Quais expressões são válidas será o tema de toda a primeira parte do curso: hoje veremos apenas como compilar um programa, que será uma atividade que você deverá realizar muitas e muitas vezes durante seu aprendizado de C.

 

Infelizmente, para cada compilador a maneira de compilar é diferente, e portanto seria impraticável explicar como se compila utilizando cada compilador existente (aliás, eu nem sei isso). Existem basicamente dois tipos de compiladores: os que são de "linha de comando", cujo principal exemplar é o GCC, e os de "IDE" (Integrated Development Environment - Ambiente Integrado de Desenvolvimento). Neste material, utilizaremos o GCC, e esperamos que você faça o mesmo, pelas seguintes razões:

 

O GCC é livre e é o padrão de facto da indústria de software.

O GCC é um compilador de muitas linguagens, o que significa que aprendendo a mexer com ele você já terá na manga um trunfo quando for aprender algumas outras linguagens. Além disso, o GCC é livre.

O GCC está disponível para praticamente qualquer computador, desde que esse computador possa rodar o linux, seja como sistema operacional seja como aplicativo (por exemplo, o Cygwin é um linux que roda dentro do Windows; você pode usar o GCC dentro do Cygwin, dentro do Windows). Isso sem contar que o GCC é livre.

E uma última grande razão: o GCC é software livre!

 

Pode ser encontrada a documentação do GCC no site da Gnu, afinal GCC quer dizer "Gnu Compiler Collection", ou seja, "Coleção de Compiladores Gnu". Recomendo que seja ao menos dada uma olhadela no manual do GCC, que pode ser obtida com o comando man no seu prompt de commando:

 

callado@tlaloc ~ $ man gcc

 

Para quem quer se aventurar com algum "outro" compilador, leia o manual e a documentação desse compilador. Para quem usa Windows, uma boa alternativa é utilizar o DJGPP, que é uma versão do GCC para Windows, mas que não é mantida pela Gnu.

 

Mão na massa: Abra um editor de textos e digite o texto abaixo:

 

#include<stdio.h>

 

int main() {

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

return 0; }

 

Salve o texto sem formatação, isto é, o arquivo deve ser de "texto puro", não podendo ser arquivo "do Word" nem nada parecido. Tampouco utilize acentos ou cedilhas. No Windows, um arquivo do Notepad serviria, mas depois você deverá eliminar a extensão ".txt" do arquivo. No linux, o Emacs salva em formato texto. Salve o arquivo com o nome "intro.c".

 

Abra sua linha de comando e digite "gcc -o intro intro.c":

 

callado@tlaloc ~ $ gcc -o into intro.c

 

E finalmente digite "./intro":

 

callado@tlaloc ~ $ ./intro

Ola, mundo da programacao!!!

No windows, você deve abrir uma janela de DOS e digitar o nome do programa sem o "./", ou seja, apenas "intro".

 

Se tudo deu certo, parabéns! Você acaba de compilar seu primeiro programa em C!

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

Outubro 17, 2007

user icon

Detalhe: o laço while nada mais é que um laço for sem a intrução de inicialização e sem a instrução de incremento. Ou seja, todo laço for pode ser convertido em um laço while com o seguinte formato:

 

instrução de inicialização;

while ( condição de término )

{

/* Instruções */

instrução de incremento;

}

 

Por outro lado, todo laço while pode ser escrito como um laço for da seguinte maneira:

 

for ( ; condição de execução; )

{

/* Instruções */

}

 

Quando usar while e quando usar for?

Isso pode ser decidido com o critério de utilizar o laço que tornará o código mais legível. Se um programador irá utilizar uma variável de controle que será incrementada, é mais razoável utilizar um laço for, pois dessa maneira um outro programador que ler seu código identificará imediatamente aonde estão e quais são as instruções de inicialização e incremento. Por outro lado, caso o programador não pretenda utilizar uma variável de controle, é mais legível utilizar um laço while, pois não haverão campos em branco (brancos como no exemplo acima).

Caso o programador queira utilizar o recurso de um bloco de instruções ser executado ao menos uma vez necessariamente, ele deve utilizar a instrução do ... while.

Palavras-chave: laço while, while

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

Outubro 10, 2007

user icon

Ao encontrar uma instrução while tal como a do código abaixo, o computador segue uma certa sequência de passos, descrita logo a seguir.

 

int soma = 0, a = 1;

while ( a != 0 )

{

   printf("\nDigite um numero inteiro para somar, ou zero para terminar a soma: ");

   scanf("%d", &a);

   soma = soma + a;

}

printf("\nA soma é %d.", soma);

 

A primeira coisa que o computador faz é verificar se a condição de execução é satisfeita. No caso, como atribuímos o valor 1 à variável a, ela é diferente de 0, e portanto o bloco de instruções será executado. Nesse bloco, é feita uma atribuição de valor à variável a pelo usuário - note que portanto o usuário pode entrar com números diferentes de zero um número indeterminado de vezes - e o bloco será executado até que o usuário entre com o valor 0 para a variável a. Assim que a condição de execução deixa de ser satisfeita, o fluxo passa para a primeira instrução após o laço while, que no exemplo acima imprime o conteúdo da variável soma.

 

Neste exemplo, tivemos de atribuir um valor não-nulo à variável a antes do laço while pois se apenas declarássemos esta variável, seu valor inicial, que é comumento chamado de "lixo", poderia eventualmente ser igual a zero, e nesse caso o laço não seria executado nem mesmo uma vez, resultando em o programa imprimir o valor que estiver contido ena variável soma, no caso, 0. Uma maneira alternativa de resolver esse problema é utilizando o laço do ... while:

 

int soma = 0, a;

do

{

   printf("\nDigite um número inteiro para somar, ou zero para terminar a soma: ");

   scanf("%d", &a);

   soma = soma + a;

}

while ( a != 0 );

printf("\nA soma é %d.", soma);

 

 

Neste último exemplo, só será impresso que a soma é zero se o usuário digitar 0 como o primeiro número ao ser pedido que digite um número para somar.

 

 

Exemplo: obter o primeiro fatorial que é maior que 1000.

 

 

int fatorial = 1, n = 1;

do

{

   fatorial = fatorial * n;

    n++;

}

while ( fatorial <= 1000);

 

 

Exercício: obtenha o primeiro quadrado maior que 2000.

 

 

Exemplo: imprimir o produto de dois números até que esse produto seja maior que 1000 ou menor que 10.

 

 

int produto, a, b;

do

{

   printf("\nDigite um numero inteiro: ");

   scanf("%d", &a);

   printf("\nDigite outro numero inteiro: ");

   scanf("%d", &b);

   produto = a * b;

   printf("\na * b = %d.", produto);

}

while ( produto >= 10 && produto <= 1000);

 

 

Exercício: imprima a soma de dois números até que ocorra de uma soma ser ímpar.

 

 

Exemplo: somar e imprimir a soma de uma sequência de números inteiros estritamente crescente.

 

 

int anterior = -1001, atual, soma = 0;

do

{

   printf("\nDigite um numero real entre -1000 e 1000:");

   scanf("%f", &atual);

   if (atual > anterior)

       soma = soma + atual;

   anterior = atual;

}

while ( atual > anterior ); 

printf("\nA soma da sequencia estritamente crescente eh: %d", &soma);

 

 

Exercício: some e imprima a soma de números entre -1000 e 1000 de uma sequencia crescente até que a soma dos dois últimos termos seja igual ao dobro do termo atual. Suponha que o usuário só entrará com números em sequência crescente.

Palavras-chave: do ... while, laço, laço do ... while, laço while, while

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

Outubro 03, 2007

user icon

O que são os laços while e do ... while?

A palavra while em inglês significa enquanto e a expressão do ... while significa faça ... enquanto. O laço while é uma estrutura de controle de fluxo que permite que um conjunto de instruções seja executado enquanto uma condição estiver satisfeita. É também comum que seja dito que os laços while e do ... while são instruções de loop (comumente traduzidos para o português como laço). O laço do ... while é, assim como o laço while, uma estrutura de controle de fluxo, que será executado uma vez e depois será executado enquanto uma condição for verdadeira. Portanto a diferença entre o laço while e o laço do ... while é que o laço do ... while é sempre executado ao menos uma vez, enquanto que o laço while pode não ser executado nenhuma vez, caso a condição de controle de sua execução não seja satisfeita na primeira passagem do processador por esse laço. A sintaxe do laço while é:

 

while ( condição de execução )

{

/* Instruções a serem executadas */

}

 

A sintaxe do laço do ... while é:

 

do

{

/* Instruções que serão executadas ao menos uma vez */

}

while ( condição de execução );

 

O laço while e utilizado quando um determinado conjunto de instruções deve ser executado um número indeterminado de vezes, mas somente até uma certa condição ocorrer. O laço do ... while é utilizado quando há um conjunto de instruções a ser executado um número indeterminado de vezes, mas no caso em que esse conjunto de instruções deve ser executado ao menos uma vez, necessariamente.

 

O que é fluxo? O que é controle de fluxo? O que é loop?

Estas questões foram respondidas em "O laço for" - Aspectos básicos - parte 1.

 

Como é a sintaxe da instrução while? E da instrução do ... while?

A sintaxe da instrução while é:

 

while ( condição de execução )

{

/* Instruções */

}

 

A condição de execução é a expressão lógica que será avaliada pelo computador para determinar se as instruções do bloco serão executadas ou não. Se a expressão for verdadeira, o bloco é executado. Se a expressão for falsa, o bloco não é executado. Por exemplo, o laço a seguir nunca é executado:

 

while ( 0 > 1 )

{

printf("\nEu lhe devo R$1.000,00.");

}

 

A sintaxe do laço do ... while é:

 

do

{

/* Instruções */

}

while ( condição de execução );

 

Note que a instrução while, que fica após o bloco de instruções, tem um ponto-e-vírgula após os parênteses de sua condição de execução. É muito comum esquecer desse ponto-e-vírgula, portanto se ocorrer um erro nessa linha de um programa seu, verifique se esqueceu do ponto-e-vírgula após o laço do ... while!

A condição de execução do laço do ... while tem a mesma função que a condição de execução do laço while: o laço será executado enquanto essa condição for verdadeira. A diferença é que, no laço do ... while, a condição de execução é testada após a execução do bloco de instruções, de modo que mesmo que a condição nunca seja satisfeita, o bloco de instruções será executado ao menos uma vez. Por exemplo, o bloco abaixo ser'executado exatamente uma vez:

 

do

{

printf("\nAlberto esqueceu de pagar sua divida.");

}

while ( 0 > 1 )

 

 

Palavras-chave: laço, laço while, while

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

Setembro 26, 2007

user icon

Detalhe: Em C, existe a restrição de que a instrução de inicialização não pode ser uma declaração de variável. Em C++ isso é permitido.  De modo que a instrução

 

for (int a=0; a < 10; a++) { }

 

 

está errada em C, mas é correta em C++.

 

 

Detalhe: A instrução de inicialização na verdade pode ser composta de mais de uma instrução, desde que as instruções sejam separadas por vírgulas. Por exemplo:

 

 

for (a=0, b=34, c=67; a < 100; a++) { /* Instruções a serem executadas a cada interação */ }

 

 

é um laço for perfeitamente válido. Existem algumas restrições, por exemplo eu testei e no compilador gcc 4.1.2, em C não é permitido criar um laço for como uma instrução de inicialização, mas não sei dizer com certeza qual é o critério que diz que um certo tipo de instrução é permitido e qual não. Mas tendo em vista que se trata de um recurso da linguagem que pode facilmente tornar o código menos legível, recomendo aos programadores que se restrinjam a utilizar as instruções múltiplas na instrução de inicialização apenas quando se tratarem de instruções de atribuição de valor a variáveis, tal como ilustrado no exemplo acima.

 

 

Detalhe: assim como a instrução de inicialização, a condição de término pode ter mais de uma expressão, desde que separada por vírgulas. Entretanto, o valor de verdadeiro ou falso será dado exclusivamente pela última expressão, e portanto essas expressões "extras" não fazem sentido a menos que se saiba do detalhe seguinte.

 

 

Detalhe: a condição de término não precisa ser uma expressão, podendo ser também uma instrução. Neste caso, uma atribuição recebe valor falso caso o valor atribuído seja igual a zero, e verdadeiro em qualquer outra situação. No caso de uma chamada a função, se esta função for do tipo void haverá um erro, e caso ela seja numérica o valor será falso para zero e verdadeiro para qualquer outro valor. Caso a função retorne um tipo definido pelo usuário ocorrerá erro. Caso a condição de término seja uma variável, o valor será falso para zero e verdadeiro para qualquer outro valor.

 

 

Exemplo: Considerando estes dois detalhes, a seguinte instrução (que, note bem, é pouco legível) é possível em C++:

 

 

for (int a=0, int b=10; a*=b, b; b--); /* Você sabe dizer o que esta instrução calcula? */

 

 

Detalhe: qualquer uma das expressões dentro dos parênteses do laço for pode ser omitida. A omissão de cada parte terá efeitos diferentes: um laço for sem instrução de inicialização apenas não executa nada antes do percurso normal do laço. Um laço for sem condição de término é executado infinitamente, de modo que para que o programa tenha fim ele terá que ter dentro do laço for ao menos uma das seguintes instruções: break, return, goto ou uma chamada a função que execute uma dessas instruções implicitamente. Uma função sem instrução de incremento não executa nada ao final da execução do bloco e antes do teste da condicional, de modo que a modificação da variável testada deve ser feita dentro do corpo do laço for, ou por meio de algum recurso externo, como uma chamada a função que possua variável static. Observe que apesar de pdoerem ser omitidos todos os itens da instrução for, os pontos-e-vírgula devem comparecer obrigatoriamente.

 

 

Exemplo: a instrução seguinte cria um programa infinito! Se você deixar ser computador rodando esse programa, ele só irá parar quando acabar a luz!

 

for ( ; ; );

 

 

Exemplo: o for abaixo é ligeiramente diferente: ele é a maneira de se escrever com um for algo que seria mais razoável escrever usando while. Mas essa é uma característica importante dos controles de fluxo: praticamente tudo o que pode ser feito com uma estrutura pode ser feito com as outras. Somente uns poucos casos excepcionais exigem uma solução ou outra, especificamente; a maioria dos problemas que pode ser resolvidos com um for pode ser resolvida com um while e vice-versa.

 

 

/* Fazer alguma coisa repetidamente até que o usuário entre com o valor zero */

int a;

for ( ; ; ) {

   scanf("%d", &a);

   if ( a == 0 )

       break;

   /* Faz alguma coisa */

}

 

Exercício: escreva um programa que calcula a soma dos fatoriais dos primeiros números naturais até que essa soma seja maior que mil.

 

 

Desafio: escreva um laço for que calcula a soma dos quadrados dos 20 primeiros números naturais. Reescreva o laço sem usar nenhuma instrução no bloco.

Palavras-chave: for, laço for

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

Setembro 17, 2007

user icon

Descrição: quando o computador encontrar uma instrução for tal como a descrita a seguir, ele irá realizar uma certa sequência de operações, descritas logo abaixo do código.

 

int a;

for ( a = 0; a <= 10; a = a + 1 )

{

   int b;

   printf("\nDigite um valor para b: ");

   scanf("%d", &b);

   printf("\na = %d, b = %d e sua soma = %d.", a, b, a + b);

}

printf("\nA variavel a ainda existe e vale %d.", a);

 

 

A declaração da variável a ocorre antes do laço for. Isto significa que o escopo no qual a está definida abrange todo o laço for considerado.

Assim que o computador encontra a instrução for, ele executa a instrução de inicialização. Neste caso, ele atribui o valor zero à variável a. Note que a execução dessa instrução de inicialização ocorre antes de executar qualquer instrução "dentro" do laço e antes mesmo do teste da condição de término. Em outras palavras, quando um laço for é executado, sua instrução de inicialização é sempre executada.

A segunda operação realizada pelo computador ao percorrer uma instrução for é verificar se a condição de término está satisfeita. Caso essa condição seja falsa, o laço for é encerrado e o computador executará a próxima instrução do programa, que no caso do exemplo seria a impressão da variável a fora do laço for. Mas como no exemplo, na primeira execução a variável a tem valor zero e o teste condiciona a execução do bloco de instruções aos casos em que a é menor ou igual a 10, o bloco será executado.

A execução do bloco segue as regras gerais de C ou C++ para a execução de instruções. Note que "dentro" de um laço for podemos ter outras estruturas de controle de fluxo, como laços for, while, do ... while, switch e if. Com esse recurso, podemos ter um bloco de código que é executado condicional e/ou controladamente dentro de um bloco que já é ele mesmo executado controladamente. Com esse recurso podemos escrever código para problemas que exigem que operações sejam repetidas repetidas vezes, como por exemplo somar vários fatoriais. (Cada fatorial repete a multiplicação de 1 a N, e cada soma soma os fatoriais de 1 a M, por exemplo).

No nosso exemplo, o bloco de instruções cria a variável b e pede que o usuário entre com o valor para b. Depois ele calcula e imprime o valor de a vezes b. Ao chegar no final do bloco - e isto é muito importante - a variável b é destruída, pois o bloco é encerrado. A variável a, como tem um escopo mais amplo que o do laço for, continua a existir.

A última etapa que o computador empreende ao executar uma instrução for é executar a instrução de incremento. No nosso exemplo, a variável a é aumentada em uma unidade. Em seguida o computador retorna à etapa de verificar se a condição de término é satisfeita - note que a instrução de inicialização não é mais executada. Se o teste for verdadeiro, como no nosso caso, o bloco é executado novamente, será destruído e a variável a incrementada novamente (a = 3, agora). Como o teste é verdadeiro, o bloco é executado e então destruído. a é novamente incrementada (a = 4). Esse ciclo ocorre até que a seja maior que 10, e aí o bloco não é executado e o processamento segue seu caminho "para fora" do laço for.

 

 

Exemplo: digamos que seu problema seja somar os números naturais de 1 a 1000. A solução seria criar e inicializar uma variável inteira (digamos a variável "soma") com valor zero, e depois somar a essa variável o valor 1 (soma = 1), depois somar a essa variável o valor 2 (soma = 3), depois somar a essa variável o valor 3 (soma = 6) e assim sucessivamente até mil.

Esse procedimento para obter a soma dos 1000 primeiros naturais consiste em uma operação, "adicione o valor do i-ésimo natural à variável soma", que deve ser repetida de i=1 até i=1000, aumentando i em uma unidade a cada execução. De modo que o laço for que implementa essa solução para esse problema seria (em C):

 

 

/* Soma os 1000 primeiros naturais e armazena em "soma" */

int a, soma = 0;

for ( a=1; a <= 1000; a++ )      /* Lembre-se: "a++" é o mesmo que "a = a + 1". */

   soma += a;

 

 

Exercício: escreva um laço for que calcula o fatorial de 10. (Lembre-se: dez fatorial = 10! = 10*9*8*7*6*5*4*3*2*1 = ?).

 

 

Exemplo: talvez seu problema seja somar os números naturais pares de 1 a 1000. Neste caso, a solução é mudar a instrução de incremento para que aumente a variável de controle em duas unidades a cada execução, e garantir que a instrução de inicialização inicializa a variável com o valor par correto. Em código:

 

 

/* Soma os números naturais pares de 1 a 1000 */

int a, soma = 0;

for ( a=2; a <= 1000; a += 2)     /* Lembre-se que "a += 2" é o mesmo que "a = a + 2". */

   soma += a;

 

 

Exercício: escreva um laço for que soma os números ímpares de 1 a 1000.

 

 

Desafio: escreva um laço for que conta o número de anos bissextos que ocorreram no século XX. Modifique esse laço para que conte os anos bissextos de qualquer século da era atual.

Palavras-chave: exemplos, exercícios, for, laço for

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

Setembro 13, 2007

user icon

"O laço for" - Aspectos básicos - parte 1

 

O que é a instrução for?

A instrução for (que, em português, poderia ser traduzida como a instrução para) é uma instrução de controle de fluxo. É também comumente dito que a instrução for é uma instrução de loop (que é comumente traduzido em português como laço). A sintaxe da instrução for é:

 

 

for ( instrução de inicialização; condição de término; instrução de incremento ) { }

 

 

A instrução for serve para executar as instruções dentro do bloco um número controlado de vezes, sendo que o controle é feito pela condição de término. Do ponto de vista algorítmico, usa-se um laço for quando se precisa realizar um operação um número determinado de vezes, especialmente se a cada vez que essa operação é realizada é necessário utilizar um valor numérico que aumenta ou diminui com taxa de variação constante.

 

 

Mas o que é o fluxo de um programa?

O fluxo é o "caminho" que o computador irá percorrer através de um programa em C/C++, isto é, o fluxo é a sequência das instruções de um programa. Dentro da conhecida metáfora da programação como culinária, o fluxo é equivalente à idéia de que, ao preparar uma receita, a ordem em que os ingredientes são adicionados geralmente tem grande importância (se não me engano, isso é especialmente verdadeiro ao se fazer bolos).

 

 

O que quer dizer "for é uma instrução de controle de fluxo"?

Afirmar que a instrução for é uma instrução de controle de fluxo significa dizer que a instrução for controla a sequência de instruções a serem executadas pelo computador. Metaforicamente, a instrução for é como o procedimento da receita que diz: "adicione um ovo; bata até ficar uma massa homogênea; repita esta operação três vezes". Esta parte do procedimento, "repita", faz com que o cozinheiro tenha que retornar a um ponto anterior da receita e executar novamente os procedimentos já feitos uma vez, ou seja, o cozinheiro adiciona um ovo e bate até a massa ficar homogênea; lê que deve fazer isso três vezes, e só fez uma, portanto, retorna ao procedimento de adicionar um ovo e bater até a massa ficar homogênea; lê que deve fazer isso três vezes, já fez duas, e portanto retorna ao procedimento de adicionar um ovo e bater até que a massa fique homogênea; aí lê que deve fazer isso três vezes, e como já fez três vezes, passa para o procedimento seguinte da receita (que deve ser algo como "ponha numa forma untada").

 

 

O que é uma instrução de loop?

Loop é a palavra em inglês para laço, no sentido de "dar uma volta num círculo". Portanto, percorrer um laço é percorrer um caminho que termina no ponto de início. Dizer que a instrução for é uma instrução de laço significa que a instrução for corresponde a um círculo ou a um trajeto que o computador irá percorrer mais de uma vez, retornando ao início da trajetória cada vez que percorrer essa trajetória "até o fim". Mas, tal como a expressão controle de fluxo indica, este laço é percorrido de maneira controlada - ou seja, a trajetória é percorrida tantas vezes quanto necessário, nem mais nem menos - ou pelo menos deveria ser assim. Na verdade, com a instrução for é possível construir um programa em C/C++ que tem execução infinita - basta instruir o computador a executar um procedimento um número de vezes tão grande que seria necessário um tempo muito grande para que o a instrução for seja terminada - digamos, um milhão de anos. Mas também seria possível criar um programa com uma instrução for que tem uma condição de controle de fluxo que nunca é satisfeita - de modo que a instrução for é executada para sempre, não terminando nem mesmo em um milhão de anos.

 

 

Como é a sintaxe da instrução for?

A sintaxe da instrução for é:

 

 

for ( instrução de inicialização; condição de término; instrução de incremento ) { }

 

 

A instrução de inicialização é uma instrução que será executada antes que a primeira execução das instruções dentro do bloco do laço for seja executada - em outras palavras, o bloco (as instruções dentro das chaves) do laço for será executado tantas vezes quanto o controle de fluxo determinar; mas, antes que esse bloco seja executado pela primeira vez, e antes mesmo que a condição de controle de fluxo seja verificada, a instrução de inicialização é executada.

 

 

A condição de término é uma expressão que, se for verdadeira, fará com que o laço for seja executado novamente; se a condição_de_término for falsa, o laço for será encerrado. É verdade que isso é um tanto anti-intuitivo, pois seria mais razoável que a condição de término verdadeira encerrasse o laço, mas isso se dá apenas devido ao nome incorreto dessa expressão, que o uso consagrou como sendo condição de término mas que deveria ser na verdade condição de execução. Nomeada dessa forma, a condição verdadeira faz com que o laço seja executado; se falsa, o laço é encerrado.

 

O bloco de instruções costuma-se dizer que está "dentro" do laço for. De modo que se algum colega seu mais adiantado no estudo do misticismo* disser que seu programa tem "algo errado dentro do segundo for" isso quer dizer que ele acredita que há um erro nas instruções contidas no bloco a ser executado controladamente pelo segundo laço for do seu programa.

 

 

*misticismo = informática.

 

A instrução de incremento é uma instrução que é executada uma vez para cada vez que o computador percorre o laço for. Ela é executada após a execução do bloco de instruções e antes da verificação da condição de término. A instrução de incremento, tal como poderá ser visto na segunda parte deste texto, pode ser qualquer instrução de C ou C++, mas geralmente se trata de uma atribuição de valor a variável, e muito comumente uma adição ou subtração de uma unidade. 

 

Palavras-chave: controle de fluxo, for, laço, sintaxe

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