apostila 2pp, Notas de estudo de Cultura
ricardo-goncalves-10
ricardo-goncalves-10

apostila 2pp, Notas de estudo de Cultura

58 páginas
8Números de download
1000+Número de visitas
Descrição
20 pontos
Pontos de download necessários para baixar
este documento
Baixar o documento
Pré-visualização3 páginas / 58
Esta é apenas uma pré-visualização
3 mostrados em 58 páginas
Esta é apenas uma pré-visualização
3 mostrados em 58 páginas
Esta é apenas uma pré-visualização
3 mostrados em 58 páginas
Esta é apenas uma pré-visualização
3 mostrados em 58 páginas
apostila.doc

Projeto Assistido por Computador (PAC)

Prof. Alexandre Ramos Fonseca

Profa. Cássia Regina Santos Nunes

1 - INTRODUÇÃO

Vamos, neste curso, aprender os conceitos básicos da linguagem de programação C a qual tem se tornado cada dia mais popular, devido à sua versatilidade e ao seu poder. Uma das grandes vantagens do C é que ele possui tanto características de "alto nível" quanto de "baixo nível".

Apesar de ser bom, não é pré-requisito do curso um conhecimento anterior de linguagens de programação. É importante uma familiaridade com computadores. O que é importante é que você tenha vontade de aprender, dedicação ao curso.

O C nasceu na década de 70. Seu inventor, Dennis Ritchie, implementou-o pela primeira vez usando um DEC PDP-11 rodando o sistema operacional UNIX. O C é derivado de uma outra linguagem: o B, criado por Ken Thompson. O B, por sua vez, veio da linguagem BCPL, inventada por Martin Richards.

O C é uma linguagem de programação genérica que é utilizada para a criação de programas diversos como processadores de texto, planilhas eletrônicas, sistemas operacionais, programas de comunicação, programas para a automação industrial, gerenciadores de bancos de dados, programas de projeto assistido por computador, programas para a solução de problemas da Engenharia, Física, Química e outras Ciências, etc ... É bem provável que o Navegador que você está usando para ler este texto tenha sido escrito em C ou C++.

Sugerimos que o aluno realmente use o máximo possível dos exemplos, problemas e exercícios aqui apresentados, gerando os programas executáveis com o seu compilador. Quando utilizamos o compilador aprendemos a lidar com mensagens de aviso, mensagens de erro, bugs, etc. Apenas ler os exemplos não basta. O conhecimento de uma linguagem de programação transcende o conhecimento de estruturas e funções. O C exige, além do domínio da linguagem em si, uma familiaridade com o compilador e experiência em achar "bugs" nos programas. É importante então que o leitor digite, compile e execute os exemplos apresentados.

Primeiros Passos

O C++ é "Case Sensitive"

Vamos começar o nosso curso ressaltando um ponto de suma importância: o C é "Case Sensitive", isto é, maiúsculas e minúsculas fazem diferença. Se declarar uma variável com o nome soma ela será diferente de Soma, SOMA, SoMa ou sOmA. Da mesma maneira, os comandos do C if e for, por exemplo, só podem ser escritos em minúsculas pois senão o compilador não irá interpretá-los como sendo comandos, mas sim como variáveis.

Dois Primeiros Programas

Vejamos um primeiro programa em C:

#include <stdlib.h> #include <iostream> using namespace std;

/* Um Primeiro Programa */ int main () { cout << "Ola! Eu estou vivo!" << endl; system("pause"); return(0); }

Compilando e executando este programa você verá que ele coloca a mensagem Ola! Eu estou vivo! na tela.

Vamos analisar o programa por partes.

As linhas #include <stdlib.h> e #include <iostream> dizem ao compilador que ele deve incluir o arquivos-cabeçalho stdlib.h e iostream. No arquivo iostrem existem declarações de funções úteis para entrada e saída de dados.

stdlib (std = standard, padrão; lib = library, biblioteca; stdlib ==> biblioteca padrão).

iostream (io = Input/Output, entrada e saída; stream = fluxo de dados ==> iostream = Entrada e saída padronizadas).

Toda vez que você quiser usar uma destas funções deve-se incluir estes comandos. O C/C++ possui diversos Arquivos-cabeçalho.

Quando fazemos um programa, uma boa idéia é usar comentários que ajudem a elucidar o funcionamento do mesmo. No caso acima temos um comentário: /* Um Primeiro Programa */. O compilador C desconsidera qualquer coisa que esteja começando com /* e terminando com */. Um comentário pode, inclusive, ter mais de uma linha. Outra forma de se fazer comentários é utilizando //. O compilador considera que tudo que está à direita de // é comentário. Esse tipo de comentário tem apenas uma linha.

A linha int main() indica que estamos definindo uma função de nome main. Todos os programas em C/C++ têm que ter uma função main, pois é esta função que será chamada quando o programa for executado. O conteúdo da função é delimitado por chaves { }. O código que estiver dentro das chaves será executado seqüencialmente quando a função for chamada. A palavra int indica que esta função retorna um inteiro. O que significa este retorno será visto posteriormente, quando estudarmos um pouco mais detalhadamente as funções do C. A última linha do programa, return(0); , indica o número inteiro que está sendo retornado pela função, no caso o número 0.

A única coisa que o programa realmente faz é chamar a função cout, passando a string (uma string é uma seqüência de caracteres, como veremos brevemente) "Ola! Eu estou vivo!" como argumento. É por causa do uso da função cout pelo programa que devemos incluir o arquivo- cabeçalho iostream. A função cout neste caso irá apenas colocar a string na tela do computador. O endl (end of line, fim de linha) é um comando de mudança de linha, isto é, após imprimir Ola! Eu estou vivo! o cursor passará para a próxima linha. É importante observar também que os comandos do C terminam com ; .

A função system é definida no arquivo-cabeçacho stdlib.h. Essa função é capaz de fazer chamadas de comandos ao sistema operacional. Nesse caso, usamos a função para disparar o commando pause que faz uma pausa na execução do programa. Dessa forma, o programa só termina sua execução após precionarmos alguma tecla. Sem essa linha, nosso programa fecharia sua janela rapidamente, não sendo possível visualizar a saída.

Podemos agora tentar um programa mais complicado:

#include <stdlib.h> #include <iostream> using namespace std; int main () { int Dias; // Declaracao de Variaveis float Anos; cout << "Entre com o numero de dias: "; cin >> Dias; //Entrada de Dados Anos=Dias/365.25; // Conversao Dias->Anos cout << Dias << " dias equivalem a " << Anos << " anos." << endl; system("pause"); return(0); }

Vamos entender como o programa acima funciona. São declaradas duas variáveis chamadas Dias e Anos. A primeira é um int (inteiro) e a segunda um float (ponto flutuante). As variáveis declaradas como ponto flutuante existem para armazenar números que possuem casas decimais, como 5,1497.

É feita então uma chamada à função cout, que coloca uma mensagem na tela.

Queremos agora ler um dado que será fornecido pelo usuário e colocá-lo na variável inteira Dias. Para tanto usamos a função cin.

Temos então uma expressão matemática simples que atribui a Anos o valor de Dias dividido por 365.25 (365.25 é uma constante ponto flutuante 365,25). Como Anos é uma variável float o compilador fará uma conversão automática entre os tipos das variáveis (veremos isto com detalhes mais tarde).

A segunda chamada à função cout tem quatro argumentos. Podemos passar quantos argumentos quisermos para cout. Basta que esses argumentos estejam separados pelo operador de fluxo <<. Note que o operador de fluxo indica a direção do fluxo de dados. Para o cin o operador fluxo de dados é >>.

AUTO AVALIAÇÃO

1 - Veja como você está. O que faz o seguinte programa?

#include <stdlib.h> #include <iostream> using namespace std;

int main() { int x; cin >> x; cout << x; system("pause"); return(0); }

2 - Compile e execute os programas deste arquivo.

Se você não possui um compilador, uma opção é o devcpp. Esse é um compilador gratuido e está disponível para download em http://prdownloads.sourceforge.net/dev- cpp/devcpp-4.9.9.2_setup.exe

Existem outros compiladores gratuitos disponíveis na internet. Uma boa opção é o Visual C++ express da Microsoft. Também gratuito, disponível no em http://msdn.microsoft.com/vstudio/express/downloads/

Palavras Reservadas do C/C++

Todas as linguagens de programação têm palavras reservadas. As palavras reservadas não podem ser usadas a não ser nos seus propósitos originais, isto é, não podemos declarar funções ou variáveis com os mesmos nomes. Como o C é "case sensitive" podemos declarar uma variável For, apesar de haver uma palavra reservada for, mas isto não é uma coisa recomendável de se fazer pois pode gerar confusão.

Apresentamos a seguir as palavras reservadas do ANSI C. Veremos o significado destas palavras chave à medida em que o curso for progredindo:

asm float signed auto for sizeof

break friend static case goto struct catch if switch char inline template class int this const long throw

continue new try default operator typedef delete private union

do protected unsigned double public virtual

else register void enum return volatile extern short while

VARIÁVEIS, CONSTANTES, OPERADORES E EXPRESSÕES

Nomes de Variáveis

As variáveis no C podem ter qualquer nome se duas condições forem satisfeitas: o nome deve começar com uma letra ou sublinhado (_) e os caracteres subsequentes devem ser letras, números ou sublinhado (_). Há apenas mais duas restrições: o nome de uma variável não pode ser igual a uma palavra reservada, nem igual ao nome de uma função declarada pelo programador, ou pelas bibliotecas do C. Variáveis de até 32 caracteres são aceitas. Mais uma coisa: é bom sempre lembrar que o C é "case sensitive" e portanto deve-se prestar atenção às maiúsculas e minúsculas.

Dicas quanto aos nomes de variáveis...

• É uma prática tradicional do C, usar letras minúsculas para nomes de variáveis e maiúsculas para nomes de constantes. Isto facilita na hora da leitura do código;

• Quando se escreve código usando nomes de variáveis em português, evita-se possíveis conflitos com nomes de rotinas encontrados nas diversas bibliotecas, que são em sua maioria absoluta, palavras em inglês.

Os Tipos do C++

O C tem 6 tipos básicos: char, int, float, void, double e bool.

char - O tipo char ocupa 1 byte, e serve para armazenar caracteres ou inteiros que caibam em um byte. Isso significa que o programa reserva um espaço de 8 bits na memória RAM do computador para armazenar um valor (bytes de tamanho maior que 8 bits são permitidos pela linguagem, mas são raros). Com vetores do tipo char é possível armazenar strings, que são cadeias de caracteres.

int - O tipo de dados int (inteiro) serve para armazenar valores numéricos inteiros. Existem vários tipos de inteiros, cada um de um tamanho diferente (dependendo do Sistema Operacional).

float - O tipo de dados float serve para armazenar números de ponto flutuante, ou seja, com casas decimais.

void é o tipo vazio, ou um "tipo sem tipo". A aplicação deste "tipo" será vista posteriormente.

double - O tipo de dados double serve para armazenar números de ponto flutuante de dupla precisão, tem o dobro do tamanho do float e portanto o dobro da capacidade.

bool - Armazena apenas 2 valores: true (verdadeiro) ou false (false).

Para cada um dos tipos de variáveis existem os modificadores de tipo. Os modificadores de tipo do C são quatro: signed, unsigned, long e short. Ao float e bool não se podem aplicar nenhum e ao double pode-se aplicar apenas o long.

Os quatro modificadores podem ser aplicados a inteiros. A intenção é que short e long devam prover tamanhos diferentes de inteiros onde isto for prático. Inteiros menores (short) ou maiores (long). int normalmente terá o tamanho natural para uma determinada máquina. Assim, numa máquina de 16 bits, int provavelmente terá 16 bits. Numa máquina de 32, int deverá ter 32 bits. Na verdade, cada compilador é livre para escolher tamanhos adequados para o seu próprio hardware, com a única restrição de que shorts ints e ints devem ocupar pelo menos 16 bits, longs ints pelo menos 32 bits, e short int não pode ser maior que int, que não pode ser maior que long int. O modificador unsigned serve para especificar variáveis sem sinal. Um unsigned int será um inteiro que assumirá apenas valores positivos.

A seguir estão listados os tipos de dados permitidos e seu valores máximos e mínimos em um compilador típico para um hardware de 32 bits.

Tipo Num de bits Intervalo

Inicio Fim char 8 -128 127

unsigned char 8 0 255 signed char 8 -128 127

int 32 -2.147.483.648 2.147.483.647 unsigned int 32 0 4.294.967.295 signed int 32 -2.147.483.648 2.147.483.647 short int 16 -32.768 32.767

unsigned short int 16 0 65.535 signed short int 16 -32.768 32.767

long int 32 -2.147.483.648 2.147.483.647 signed long int 32 -2.147.483.648 2.147.483.647

unsigned long int 32 0 4.294.967.295 float 32 3,4E-38 3.4E+38

double 64 1,7E-308 1,7E+308 long double 80 3,4E-4932 3,4E+4932

O tipo long double é o tipo de ponto flutuante com maior precisão. É importante observar que os intervalos de ponto flutuante, na tabela acima, estão indicados em faixa de expoente, mas os números podem assumir valores tanto positivos quanto negativos.

Declaração e Inicialização de Variáveis

As variáveis no C devem ser declaradas antes de serem usadas. A forma geral da declaração de variáveis é:

tipo_da_variável lista_de_variáveis;

As variáveis da lista de variáveis terão todas o mesmo tipo e deverão ser separadas por vírgula. Como o tipo default do C é o int, quando vamos declarar variáveis int com algum dos modificadores de tipo, basta colocar o nome do modificador de tipo. Assim um long basta para declarar um long int.

Por exemplo, as declarações

char ch, letra; long count; float pi;

declaram duas variáveis do tipo char (ch e letra), uma variavel long int (count) e um float pi.

Há três lugares nos quais podemos declarar variáveis. O primeiro é fora de todas as funções do programa. Estas variáveis são chamadas variáveis globais e podem ser usadas a partir de qualquer lugar no programa. Pode-se dizer que, como elas estão fora de todas as funções, todas as funções as vêem. O segundo lugar no qual se pode declarar variáveis é dentrode um bloco de código. Estas variáveis são chamadas locais e só têm validade dentro do bloco no qual são declaradas, isto é, só a função à qual ela pertence sabe da existência desta variável, dentro do bloco no qual foram declaradas. O terceiro lugar onde se pode declarar variáveis é na lista de parâmetros de uma função. Mais uma vez, apesar de estas variáveis receberem valores externos, estas variáveis são conhecidas apenas pela função onde são declaradas.

Veja o programa abaixo:

#include <stdlib.h> #include <iostream> using namespace std; int contador; int func1(int j) { // aqui viria o código da funcao } int main() { char condicao; int i; for (i=0; i<100; i=i+1) { /* Bloco do for */ float f2; // etc ... func1(i); } // etc ... system("pause"); return(0); }

A variável contador é uma variável global, e é acessível de qualquer parte do programa. As variáveis condição e i, só existem dentro de main(), isto é são variáveis locais de main. A variável float f2 é um exemplo de uma variável de bloco, isto é, ela somente é conhecida

dentro do bloco do for, pertencente à função main. A variável inteira j é um exemplo de declaração na lista de parâmetros de uma função (a função func1).

As regras que regem onde uma variável é válida chamam-se regras de escopo da variável. Há mais dois detalhes que devem ser ressaltados. Duas variáveis globais não podem ter o mesmo nome. O mesmo vale para duas variáveis locais de uma mesma função. Já duas variáveis locais, de funções diferentes, podem ter o mesmo nome sem perigo algum de conflito.

Podemos inicializar variáveis no momento de sua declaração. Para fazer isto podemos usar a forma geral

tipo_da_variável nome_da_variável = constante;

Isto é importante pois quando o C cria uma variável ele não a inicializa. Isto significa que até que um primeiro valor seja atribuído à nova variável ela tem um valor indefinido e que não pode ser utilizado para nada. Nunca presuma que uma variável declarada vale zero ou qualquer outro valor. Exemplos de inicialização são dados abaixo :

char ch='D'; int count=0; float pi=3.141;

AUTO AVALIAÇÃO

Veja como você está:

Escreva um programa que declare uma variável inteira global e atribua o valor 10 a ela. Declare outras 5 variáveis inteiras locais ao programa principal e atribua os valores 20, 30, ..., 60 a elas. Declare 6 variáveis caracteres e atribua a elas as letras c, o, e, l, h, a . Finalmente, o programa deverá imprimir, usando todas as variáveis declaradas:

As variáveis inteiras contem os números: 10,20,30,40,50,60

O animal contido nas variáveis caracteres e' a coelha

Constantes

Constantes são valores que são mantidos fixos pelo compilador. Já usamos constantes neste curso. São consideradas constantes, por exemplo, os números e caracteres como 45.65 ou 'n', etc...

- Constantes dos tipos básicos

Abaixo vemos as constantes relativas aos tipos básicos do C:

Tipo de DadoExemplos de Constantes char 'b' '\n' '\0' Int 2 32000 -130

long int 100000 -467 short int 100 -30

unsigned int 50000 35678 float 0.0 23.7 -12.3e-10

double 12546354334.0 -0.0000034236556

- Constantes hexadecimais e octais

Muitas vezes precisamos inserir constantes hexadecimais (base dezesseis) ou octais (base oito) no nosso programa. O C permite que se faça isto. As constantes hexadecimais começam com 0x. As constantes octais começam em 0.

Alguns exemplos:

ConstanteTipo 0xEF Constante Hexadecimal (8 bits)

0x12A4 Constante Hexadecimal (16 bits) 03212 Constante Octal (12 bits)

034215432 Constante Octal (24 bits)

Nunca escreva portanto 013 achando que o C vai compilar isto como se fosse 13. Na linguagem C 013 é diferente de 13!

- Constantes strings

Já mostramos como o C trata strings. Vamos agora alertar para o fato de que uma string "Joao" é na realidade uma constante string. Isto implica, por exemplo, no fato de que 't' é diferente de "t", pois 't' é um char enquanto que "t" é uma constante string com dois chars onde o primeiro é 't' e o segundo é '\0'.

- Constantes de barra invertida

O C utiliza, para nos facilitar a tarefa de programar, vários códigos chamados códigos de barra invertida. Estes são caracteres que podem ser usados como qualquer outro. Uma lista com alguns dos códigos de barra invertida é dada a seguir:

CódigoSignificado \b Retrocesso ("back") \f Alimentação de formulário ("form feed") \n Nova linha ("new line") \t Tabulação horizontal ("tab") \" Aspas \' Apóstrofo \0 Nulo (0 em decimal) \\ Barra invertida \v Tabulação vertical \a Sinal sonoro ("beep") \N Constante octal (N é o valor da constante) \xN Constante hexadecimal (N é o valor da constante)

Operadores Aritméticos e de Atribuição

Os operadores aritméticos são usados para desenvolver operações matemáticas. A seguir apresentamos a lista dos operadores aritméticos do C:

OperadorAção+ Soma (inteira e ponto flutuante) - Subtração ou Troca de sinal (inteira e ponto flutuante) * Multiplicação (inteira e ponto flutuante) / Divisão (inteira e ponto flutuante)

% Resto de divisão (de inteiros) ++ Incremento (inteiro e ponto flutuante) -- Decremento (inteiro e ponto flutuante)

O C possui operadores unários e binários. Os unários agem sobre uma variável apenas, modificando ou não o seu valor, e retornam o valor final da variável. Os binários usam duas variáveis e retornam um terceiro valor, sem alterar as variáveis originais. A soma é um operador binário pois pega duas variáveis, soma seus valores, sem alterar as variáveis, e retorna esta soma. Outros operadores binários são os operadores - (subtração), *, / e %. O

operador - como troca de sinal é um operador unário que não altera a variável sobre a qual é aplicado, pois ele retorna o valor da variável multiplicado por -1.

O operador / (divisão) quando aplicado a variáveis inteiras, nos fornece o resultado da divisão inteira; quando aplicado a variáveis em ponto flutuante nos fornece o resultado da divisão "real". O operador % fornece o resto da divisão de dois inteiros.

Assim seja o seguinte trecho de código:

int a = 17, b = 3; int x, y; float z = 17. , z1, z2; x = a / b; y = a % b; z1 = z / b; z2 = a/b;

ao final da execução destas linhas, os valores calculados seriam x = 5, y = 2, z1 = 5.666666 e z2 = 5.0 . Note que, na linha correspondente a z2, primeiramente é feita uma divisão inteira (pois os dois operandos são inteiros). Somente após efetuada a divisão é que o resultado é atribuído a uma variável float.

Os operadores de incremento e decremento são unários que alteram a variável sobre a qual estão aplicados. O que eles fazem é incrementar ou decrementar, a variável sobre a qual estão aplicados, de 1. Então

x++; x--;

são equivalentes a

x=x+1; x=x-1;

Estes operadores podem ser pré-fixados ou pós- fixados. A diferença é que quando são pré-fixados eles incrementam e retornam o valor da variável já incrementada. Quando são pós-fixados eles retornam o valor da variável sem o incremento e depois incrementam a variável. Então, em

x=23; y=x++;

teremos, no final, y=23 e x=24. Em

x=23; y=++x;

teremos, no final, y=24 e x=24. Uma curiosidade: a linguagem de programação C++ tem este nome pois ela seria um "incremento" da linguagem C padrão. A linguagem C++ é igual à linguagem C só que com extensões que permitem a programação orientada a objeto, o que é um recurso extra.

O operador de atribuição do C é o =. O que ele faz é pegar o valor à direita e atribuir à variável da esquerda. Além disto ele retorna o valor que ele atribuiu. Isto faz com que as seguintes expressões sejam válidas:

x=y=z=1.5; /* Expressao 1 */

if (k=w) ... /* Expressão 2 */

A expressão 1 é válida, pois quando fazemos z=1.5 ela retorna 1.5, que é passado adiante, fazendo y = 1.5 e posteriormente x = 1.5. A expressão 2 será verdadeira se w for diferente de zero, pois este será o valor retornado por k=w. Pense bem antes de usar a expressão dois, pois ela pode gerar erros de interpretação. Você não está comparando k e w. Você está atribuindo o valor de w a k e usando este valor para tomar a decisão.

AUTO AVALIAÇÃO

Veja como você está:

Diga o resultado das variáveis x, y e z depois da seguinte seqüência de operações:

int x,y,z; x=y=10; z=++x; x=-x; y++; x=x+y-(z--);

- Operadores Relacionais e Lógicos

Os operadores relacionais do C realizam comparações entre variáveis. São eles:

OperadorAção> Maior do que

>= Maior ou igual a < Menor do que

<= Menor ou igual a == Igual a != Diferente de

Os operadores relacionais retornam verdadeiro ou falso . Para verificar o funcionamento dos operadores relacionais, execute o programa abaixo:

/* Este programa ilustra o funcionamento dos operadores relacionais. */ #include <stdlib.h> #include <iostream> using namespace std; int main() { int i, j; cout << "Entre com dois numeros inteiros: "; cin >> i >> j; cout << i << " == " << j << " eh " << (i==j) << endl; cout << i << " != " << j << " eh " << (i!=j) << endl; cout << i << " <= " << j << " eh " << (i<=j) << endl; cout << i << " >= " << j << " eh " << (i>=j) << endl; cout << i << " < " << j << " eh " << (i<j) << endl; cout << i << " > " << j << " eh " << (i>j) << endl; system("pause"); return(0); }

Você pode notar que o resultado dos operadores relacionais é sempre igual a 0 (falso) ou 1 (verdadeiro).

Para fazer operações com valores lógicos (verdadeiro e falso) temos os operadores lógicos:

OperadorAção&& AND (E) || OR (OU) ! NOT (NÃO)

Usando os operadores relacionais e lógicos podemos realizar uma grande gama de testes. A tabela-verdade destes operadores é dada a seguir:

p falso falso

verdadeiro verdadeiro

q falso

verdadeiro falso

verdadeiro

p AND q falso falso falso

verdadeiro

p OR q falso

verdadeiro verdadeiro verdadeiro

O programa a seguir ilustra o funcionamento dos operadores lógicos. Compile-o e faça testes com vários valores para i e j:

#include <stdlib.h> #include <iostream> using namespace std; int main() { int i, j; cout << "informe dois números(cada um sendo 0 ou 1): "; cin >> i >> j; cout << i << " AND " << j << " eh " << (i&&j) << endl; cout << i << " OR " << j << " eh " << (i||j) << endl; cout << " NOT " << i << " eh " << (!i) << endl; cout << " NOT " << j << " eh " << (!j) << endl; system("pause"); return(0); }

Exemplo: No trecho de programa abaixo a operação j++ será executada, pois o resultado da expressão lógica é verdadeiro:

int i = 5, j =7; if ( (i > 3) && ( j <= 7) && ( i != j) ) j++; V AND V AND V = V

Mais um exemplo. O programa abaixo, imprime na tela somente os números pares entre 1 e 100, apesar da variação de i ocorrer de 1 em 1:

/* Imprime os números pares entre 1 e 100. */ #include <stdlib.h> #include <iostream> using namespace std; int main() { int i; for(i=1; i<=100; i++) if(!(i%2)) printf("%d ",i); // o operador de resto dará falso (zero) quando usada c/ número // par. Esse resultado é invertido pelo ! system("pause"); return(0); }

- Operadores Lógicos Bit a Bit

O C permite que se faça operações lógicas "bit-a- bit" em números. Ou seja, neste caso, o número é representado por sua forma binária e as operações são feitas em cada bit dele. Imagine um número inteiro de 16 bits, a variável i, armazenando o valor 2. A representação binária de i, será: 0000000000000010 (quinze zeros e um único 1 na segunda posição da direita para a esquerda). Poderemos fazer operações em cada um dos bits deste número. Por exemplo, se fizermos a negação do número (operação binária NOT, ou operador binário ~ em C), isto é, ~i, o número se transformará em 1111111111111101. As operações binárias ajudam programadores que queiram trabalhar com o computador em "baixo nível". As operações lógicas bit a bit só podem ser usadas nos tipos char, int e long int. Os operadores são:

Operador Ação & AND | OR ^ XOR (OR exclusivo) ~ NOT

>> Deslocamento de bits à direita << Deslocamento de bits à esquerda

Os operadores &, |, ^ e ~ são as operações lógicas bit a bit. A forma geral dos operadores de deslocamento é:

valor>>número_de_deslocamentos

valor<<número_de_deslocamentos

O número_de_deslocamentos indica o quanto cada bit irá ser deslocado. Por exemplo, para a variável i anterior, armazenando o número 2:

i << 3;

fará com que i agora tenha a representação binária: 0000000000010000, isto é, o valor armazenado em i passa a ser igual a 16.

AUTO AVALIAÇÃO

Veja como você está:

Diga se as seguintes expressões serão verdadeiras ou falsas:

-> ((10>5)||(5>10)) -> (!(5==6)&&(5!=6)&&((2>1)||(5<=4)))

Expressões

Expressões são combinações de variáveis, constantes e operadores. Quando montamos expressões temos que levar em consideração a ordem com que os operadores são executados, conforme a tabela de precedências da linguagem C++.

Exemplos de expressões:

Anos=Dias/365.25; i = i+3; c= a*b + d/e; c= a*(b+d)/e;

- Conversão de tipos em expressões

Quando o C avalia expressões onde temos variáveis de tipos diferentes o compilador verifica se as conversões são possíveis. Se não são, ele não compilará o programa, dando uma mensagem de erro. Se as conversões forem possíveis ele as faz, seguindo as regras abaixo:

1. Todos os chars e short ints são convertidos para ints. Todos os floats são convertidos para doubles.

2. Para pares de operandos de tipos diferentes: se um deles é long double o outro é convertido para long double; se um deles é double o outro é convertido para double; se um é long o outro é convertido para long; se um é unsigned o outro é convertido para unsigned.

- Expressões que Podem ser Abreviadas

O C admite as seguintes equivalências, que podem ser usadas para simplificar expressões ou para facilitar o entendimento de um programa:

Expressão Original Expressão Equivalente x=x+k; x+=k; x=x-k; x-=k; x=x*k; x*=k; x=x/k; x/=k; x=x>>k; x>>=k; x=x<<k; x<<=k; x=x&k; x&=k;

etc...

- Encadeando expressões: o operador ,

O operador , determina uma lista de expressões que devem ser executadas seqüencialmente. Em síntese, a vírgula diz ao compilador: execute as duas expressões separadas pela vírgula, em seqüência. O valor retornado por uma expressão com o operador , é sempre dado pela expressão mais à direita. No exemplo abaixo:

x=(y=2,y+3);

o valor 2 vai ser atribuído a y, se somará 3 a y e o retorno (5) será atribuído à variável x . Pode-se encadear quantos operadores , forem necessários.

O exemplo a seguir mostra um outro uso para o operador , dentro de um for:

// Imprime os números pares entre 1 e 100. #include <stdlib.h> #include <iostream> using namespace std; int main() { int x, y; /* Duas variáveis de controle: x e y. Foi atribuído o valor zero a cada uma delas na inicialização do for e ambas são incrementadas na parte de incremento do for */ for(x=0 , y=0 ; x+y < 100 ; ++x , y++) cout << x+y << endl; //o programa imprimirá os números pares de 2 a 98 system("pause"); return(0); }

- Tabela de Precedências do C

Esta é a tabela de precedência dos operadores em C. Alguns (poucos) operadores ainda não foram estudados, e serão apresentados em aulas posteriores.

Maior precedência () [] ->

! ~ ++ -- . -(unário)

(cast) *(unário) &(unário) sizeof

* / % + - << >> <<= >>= == != & ^ | && || ? = += -= *= /= Menor precedência ,

Uma dica aos iniciantes: Você não precisa saber toda a tabela de precedências de cor. É útil que você conheça as principais relações, mas é aconselhável que ao escrever o seu código, você tente isolar as expressões com parênteses, para tornar o seu programa mais legível.

Modeladores (Casts) Um modelador é aplicado a uma expressão. Ele força a mesma a ser de um tipo

especificado. Sua forma geral é:

(tipo)expressão

int main () { int num; float f; num=10; f=(float)num/7; //Força a transformação de num em um float cout << f << endl; return(0); }

Se não tivéssemos usado o modelador no exemplo acima o C faria uma divisão inteira entre 10 e 7. O resultado seria 1 (um) e este seria depois convertido para float mas continuaria a ser 1.0. Com o modelador temos o resultado correto.

AUTO AVALIAÇÃO

Veja como você está:

Compile o exemplo acima sem usar o modelador, e verifique os resultados. Compile-o novamente usando o modelador e compare a saída com os resultados anteriores.

Introdução a Alguns Comandos de Controle de Fluxo

Os comandos de controle de fluxo são aqueles que permitem ao programador alterar a sequência de execução do programa. Vamos dar uma breve introdução a dois comandos de controle de fluxo. Outros comandos serão estudados posteriormente.

if

O comando if representa uma tomada de decisão do tipo "SE isto ENTÃO aquilo". A sua forma geral é:

if (condição) declaração;

A condição do comando if é uma expressão que será avaliada. Se o resultado for zero ou falso a declaração não será executada. Se o resultado for qualquer coisa diferente de zero ou verdadeiro a declaração será executada. A declaração pode ser um bloco de código ou apenas um comando. É interessante notar que, no caso da declaração ser um bloco de código, não é necessário (e nem permitido) o uso do ; no final do bloco. Isto é uma regra geral para blocos de código. Abaixo apresentamos um exemplo:

#include <stdlib.h> #include <iostream> using namespace std; int main () { int num; cout << "Digite um numero: "; cin >> num; if (num>10) cout << "O numero eh maior que 10" << endl; if (num==10) { cout << "Voce acertou!" << endl; cout << "O numero e igual a 10." << endl; } if (num<10) cout << "O numero e menor que 10" << endl; system("pause"); return (0); }

No programa acima a expressão num>10 é avaliada e retorna true, se verdadeira, e false, se falsa. No exemplo, se num for maior que 10, será impressa a frase: "O número e maior que 10". Repare que, se o número for igual a 10, estamos executando dois comandos.

Para que isto fosse possível, tivemos que agrupá-los em um bloco que se inicia logo após a comparação e termina após o segundo cout. Repare também que quando queremos testar igualdades usamos o operador == e não =. Isto porque o operador = representa apenas uma atribuição. Pode parecer estranho à primeira vista, mas se escrevêssemos

if (num=10) ... /* Isto esta errado */

o compilador iria atribuir o valor 10 à variável num e a expressão num=10 iria retornar 10, fazendo com que o nosso valor de num fosse modificado e fazendo com que a declaração fosse executada sempre. Este problema gera erros freqüentes entre iniciantes e, portanto, muita atenção deve ser tomada.

Os operadores de comparação são: == (igual), != (diferente de), > (maior que), < (menor que), >= (maior ou igual), <= (menor ou igual).

for

O loop (laço) for é usado para repetir um comando, ou bloco de comandos, diversas vezes, de maneira que se possa ter um bom controle sobre o loop. Sua forma geral é:

for (inicialização;condição;incremento) declaração;

A declaração no comando for também pode ser um bloco ({ }) e neste caso o ; é omitido. O melhor modo de se entender o loop for é ver de que maneira ele funciona "por dentro". O loop for é equivalente a se fazer o seguinte:

inicialização;if (condição){

declaração;incremento;"Volte para o comando if"

}

Podemos ver que o for executa a inicialização incondicionalmente e testa a condição. Se a condição for falsa ele não faz mais nada. Se a condição for verdadeira ele executa a declaração, o incremento e volta a testar a condição. Ele fica repetindo estas operações até que a condição seja falsa.

Abaixo vemos um programa que coloca os primeiros 100 números na tela:

#include <stdlib.h> #include <iostream> using namespace std; int main () { int count; for (count=1;count<=100;count=count+1) cout << count << endl; system("pause"); return(0); }

Outro exemplo interessante é mostrado a seguir: o programa lê uma string e conta quantos dos caracteres desta string são iguais à letra 'c'

#include <stdlib.h> #include <iostream> using namespace std;

int main () { char string[100]; //String, ate' 99 caracteres int i, cont;

cout << "Digite uma string: "; cin >> string; /* Le a string */ cout << "string digitada: " << string << endl; cont = 0; for (i=0; string[i] != '\0' ; i=i+1) { if ( string[i] == 'c' ) /* Se for a letra 'c' */ cont = cont +1; /* Incrementa o contador */ } cout << "Numero de caracteres c = " << cont << endl;

system("pause"); return(0); }

Note o teste que está sendo feito no for: o caractere armazenado em string[i] é comparado com '\0' (caractere final da string). Caso o caractere seja diferente de '\0', a

condição é verdadeira e o bloco do for é executado. Dentro do bloco existe um if que testa se o caractere é igual a 'c'. Caso seja, o contador de caracteres c é incrementado.

Mais um exemplo, agora envolvendo caracteres:

/* Este programa imprime o alfabeto: letras maiúsculas */ #include <stdlib.h> #include <iostream> using namespace std; int main() { char letra; for(letra = 'A' ; letra <= 'Z' ; letra =letra+1) cout << letra << endl;

system("pause"); return(0); }

Este programa funciona porque as letras maiúsculas de A a Z possuem código inteiro sequencial.

AUTO AVALIAÇÃO

Veja como você está.

a) Explique porque está errado fazer

if (num=10) ...

O que irá acontecer?

b) Escreva um programa que coloque os números de 1 a 100 na tela na ordem inversa (começando em 100 e terminando em 1).

c) Escreva um programa que leia uma string, conte quantos caracteres desta string são iguais a 'a' e substitua os que forem iguais a 'a' por 'b'. O programa deve imprimir o número de caracteres modificados e a string modificada.

d) Escreva comentários para os programas dos exercícios já realizados.

Caracteres e Strings

Caracteres

Os caracteres são um tipo de dado: o char. O C trata os caracteres ('a', 'b', 'x', etc ...) como sendo variáveis de um byte (8 bits). Um bit é a menor unidade de armazenamento de informações em um computador. Os inteiros (ints) têm um número maior de bytes. Dependendo da implementação do compilador, eles podem ter 2 bytes (16 bits) ou 4 bytes (32 bits). Isto será melhor explicado na aula 3. Na linguagem C, também podemos usar um char para armazenar valores numéricos inteiros, além de usá-lo para armazenar caracteres de texto. Para indicar um caractere de texto usamos apóstrofes.

Veja um exemplo de programa que usa caracteres:

#include <stdlib.h> #include <iostream> using namespace std; int main () { char Ch; Ch='D'; cout << Ch << endl; system("pause"); return(0); }

Um char também é usado para armazenar um número inteiro. Este número é conhecido como o código ASCII correspondente ao caractere. Veja o programa abaixo:

#include <stdlib.h> #include <iostream> using namespace std; int main () { char Ch; Ch='D'; cout << (int)Ch << endl; //converte para int system("pause"); return(0); }

Este programa vai imprimir o número 68 na tela, que é o código ASCII correspondente ao caractere 'D' (d maiúsculo).

ASCII (acrônimo para American Standard Code for Information Interchange) é um conjunto de códigos para o computador representar números, letras, pontuação e outros caracteres. Surgido em 1961, um dos seus inventores foi Robert W. Bemer.

ASCII é uma padronização da indústria de computadores, onde cada carácter é manipulado na memória discos etc, sob forma de código binário. O código ASCII é formado por todas as combinações possíveis de 8 bits.

Tabela ASCII

Caracteres normais

Binário Decimal Gráfico

Binário Decimal Gráfico

Binário Decimal Gráfico

0010 0000 32 (espaço) 0100 0000 64 @ 0110 0000 96 `

0010 0001 33 !

0100 0001 65 A

0110 0001 97 a

0010 0010 34 "

0100 0010 66 B

0110 0010 98 b

0010 0011 35 # 0100 0011 67 C 0110 0011 99 c

0010 0100 36 $ 0100 0100 68 D 0110 0100 100 d

0010 0101 37 % 0100 0101 69 E 0110 0101 101 e

0010 0110 38 & 0100 0110 70 F 0110 0110 102 f

0010 0111 39 ' 0100 0111 71 G 0110 0111 103 g

0010 1000 40 ( 0100 1000 72 H 0110 1000 104 h

0010 1001 41 ) 0100 1001 73 I 0110 1001 105 i

0010 1010 42 *

0100 1010 74 J

0110 1010 106 j

0010 1011 43 +

0100 1011 75 K

0110 1011 107 k

0010 1100 44 ,

0100 1100 76 L

0110 1100 108 l

0010 1101 45 -

0100 1101 77 M

0110 1101 109 m

0010 1110 46 . 0100 1110 78 N 0110 1110 110 n

0010 1111 47 / 0100 1111 79 O 0110 1111 111 o

0011 0000 48 0

0101 0000 80 P

0111 0000 112 p

0011 0001 49 1 0101 0001 81 Q 0111 0001 113 q

0011 0010 50 2

0101 0010 82 R

0111 0010 114 r

0011 0011 51 3 0101 0011 83 S 0111 0011 115 s

0011 0100 52 4 0101 0100 84 T 0111 0100 116 t

0011 0101 53 5

0101 0101 85 U

0111 0101 117 u

0011 0110 54 6 0101 0110 86 V 0111 0110 118 v

0011 0111 55 7

0101 0111 87 W

0111 0111 119 w

0011 1000 56 8 0101 1000 88 X 0111 1000 120 x

0011 1001 57 9

0101 1001 89 Y

0111 1001 121 y

0011 1010 58 :

0101 1010 90 Z

0111 1010 122 z

0011 1011 59 ;

0101 1011 91 [

0111 1011 123 {

0011 1100 60 < 0101 1100 92 \ 0111 1100 124 |

0011 1101 61 = 0101 1101 93 ] 0111 1101 125 }

0011 1110 62 > 0101 1110 94 ^ 0111 1110 126 ~

0011 1111 63 ? 0101 1111 95 _ 0111 1111 127 Delete

Caracteres de controle

Os caracteres de controle tiveram sua origem nos primórdios da computação, quando se usavam máquinas Teletype (como que máquinas de escrever eletro-mecânicas) fitas de papel

perfurado e impressoras de cilindro (drum printers), portanto muitos deles são dirigidos a este equipamento.

Binário Decimal Sigla Controle Gráfico 0000 0000 0 Null Null - Nulo 0000 0001 1 SOH Start of Header - Início do cabeçalho ☺ 0000 0010 2 STX Start of Text - Início do texto ☻ 0000 0011 3 ETX End of Text - Fim do texto ♥ 0000 0100 4 EOT End of Tape - Fim de fita ♦ 0000 0101 5 ENQ Enquire - Interroga identidade do terminal ♣ 0000 0110 6 ACK Acknowledge - Reconhecimento ♠ 0000 0111 7 BEL Bell - Campainha • 0000 1000 8 BS Back-space - Espaço atrás ◘ 0000 1001 9 HT Horizontal Tabulation - Tabulação horizontal ○ 0000 1010 10 LF Line-Feed - Alimenta linha ◙ 0000 1011 11 VT Vertical Tabulation - Tabulação vertical ♂ 0000 1100 12 FF Form-Feed - Alimenta formulário ♀ 0000 1101 13 CR Carriage-Return - Retorno do carro (enter) ♪

0000 1110 14 SO Shift-Out - Saída do shift (passa a usar caracteres de

baixo da tecla - minúsculas, etc.) ♫

0000 1111 15 SI Shift-In - Entrada no shift (passa a usar caracteres de cima da tecla: maiúsculas, caracteres especiais, etc.) ☼

0001 0000 16 DLE Data-Link Escape ► 0001 0001 17 DC1 Device-Control 1 ◄ 0001 0010 18 DC2 Device-Control 2 ↕ 0001 0011 19 DC3 Device-Control 3 ‼ 0001 0100 20 DC4 Device-Control 4 ¶ 0001 0101 21 NAK Neg-Acknowledge - Não-reconhecimento § 0001 0110 22 SYN Synchronous Idle ▬ 0001 0111 23 ETB End-of-Transmission Block ↨ 0001 1000 24 CAN Cancel ↑ 0001 1001 25 EM End-Of-Medium ↓ 0001 1010 26 SUB Substitute → 0001 1011 27 ESC Escape ← 0001 1100 28 FS File Separator ∟ 0001 1101 29 GS Group Separator ↔ 0001 1110 30 RS Record Separator ▲ 0001 1111 31 US Unit Separator ▼

Strings

Em C uma string é um vetor de caracteres terminado com um caractere nulo. O caracter nulo é um caractere com valor inteiro igual a zero (código ASCII igual a 0). O terminador nulo também pode ser escrito usando a convenção de barra invertida do C como sendo '\0'. Embora o assunto vetores seja discutido posteriormente, veremos aqui os fundamentos necessários para que possamos utilizar as strings. Para declarar uma string, podemos usar o seguinte formato geral:

char nome_da_string[tamanho];

Isto declara um vetor de caracteres (uma string) com número de posições igual a tamanho. Note que, como temos que reservar um caractere para ser o terminador nulo, temos que declarar o comprimento da string como sendo, no mínimo, um caractere maior que a maior string que pretendemos armazenar. Vamos supor que declaremos uma string de 7 posições e coloquemos a palavra João nela. Teremos:

J o a o \0 ... ...

No caso acima, as duas células não usadas têm valores indeterminados. Isto acontece porque o C não inicializa variáveis, cabendo ao programador esta tarefa. Portanto as únicas células que são inicializadas são as que contêm os caracteres 'J', 'o', 'a', 'o' e '\0'.

Um exemplo do uso de strings é dado abaixo.

#include <stdlib.h> #include <iostream> using namespace std; int main () { char string[100]; cout << "Digite uma string: "; cin >> string; cout << string << endl; system("pause"); return(0); }

Neste programa, o tamanho máximo da string que você pode entrar é uma string de 99 caracteres. Se você entrar com uma string de comprimento maior, o programa irá aceitar, mas os resultados podem ser desastrosos. Veremos porque posteriormente.

Como as strings são vetores de caracteres, para se acessar um determinado caracter de uma string, basta "indexarmos", ou seja, usarmos um índice para acessarmos o caracter

desejado dentro da string. Suponha uma string chamada str. Podemos acessar a segunda letra de str da seguinte forma:

str[1] = 'a'; Por que se está acessando a segunda letra e não a primeira? Na linguagem C, o índice

começa em zero. Assim, a primeira letra da string sempre estará na posição 0. A segunda letra sempre estará na posição 1 e assim sucessivamente. Segue um exemplo que imprimirá a segunda letra da string "Joao", apresentada acima. Em seguida, ele mudará esta letra e apresentará a string no final.

#include <stdlib.h> #include <iostream> using namespace std; int main () { char str[10] = "Joao"; cout << str << endl; cout << "Segunda letra: " << str[1] << endl; str[1] = 'U'; cout << "Agora a segunda letra eh: " << str[1] << endl; cout << "String resultante: " << str << endl; system("pause"); return(0); }

Nesta string, o terminador nulo está na posição 4. Das posições 0 a 4, sabemos que temos caracteres válidos, e portanto podemos escrevê-los. Note a forma como inicializamos a string str com os caracteres 'J' 'o' 'a' 'o' e '\0' simplesmente declarando char str[10] = "Joao". Veremos, posteriormente que "Joao" (uma cadeia de caracteres entre aspas) é o que chamamos de string constante, isto é, uma cadeia de caracteres que está pré-carregada com valores que não podem ser modificados. Já a string str é uma string variável, pois podemos modificar o que nela está armazenado, como de fato fizemos.

AUTO AVALIAÇÃO

Veja como você está:

a) Escreva um programa que leia um caracter digitado pelo usuário, imprima o caracter digitado e o código ASCII correspondente a este caracter.

b) Escreva um programa que leia duas strings e as coloque na tela. Imprima também a segunda letra de cada string.

Introdução às Funções

Uma função é um bloco de código de programa que pode ser usado diversas vezes em sua execução. O uso de funções permite que o programa fique mais legível, mais bem estruturado. Um programa em C consiste, no fundo, de várias funções colocadas juntas.

Abaixo o tipo mais simples de função:

#include <stdlib.h> #include <iostream> using namespace std; int mensagem () /* Funcao simples: so imprime Ola! */ { cout << "Ola! "; return(0); } int main () { mensagem(); cout << "Eu estou vivo!" << endl; system("pause"); return(0); }

Este programa terá o mesmo resultado que o primeiro exemplo estudado. O que ele faz é definir uma função mensagem() que coloca uma string na tela e retorna 0. Esta função é chamada a partir de main(), que, como já vimos, também é uma função. A diferença fundamental entre main e as demais funções do problema é que main é uma função especial, cujo diferencial é o fato de ser a primeira função a ser executada em um programa.

Argumentos

Argumentos são as entradas que a função recebe. É através dos argumentos que passamos parâmetros para a função. Vamos ver um exemplo simples de função com argumentos:

#include <stdlib.h> #include <iostream> using namespace std; int square (int x) // Calcula o quadrado de { cout << "O quadrado eh : " << x*x; return(0); } int main () { int num; cout << "Entre com um numero: "; cin >> num; cout << endl; square(num); system("pause"); return(0); }

Na definição de square() dizemos que a função receberá um argumento inteiro x. Quando fazemos a chamada à função, o inteiro num é passado como argumento. Há alguns pontos a observar. Em primeiro lugar temos de satisfazer aos requisitos da função quanto ao tipo e à quantidade de argumentos quando a chamamos. Apesar de existirem algumas conversões de tipo, que o C faz automaticamente, é importante ficar atento. Em segundo lugar, não é importante o nome da variável que se passa como argumento, ou seja, a variável num, ao ser passada como argumento para square() é copiada para a variável x. Dentro de square() trabalha-se apenas com x. Se mudarmos o valor de x dentro de square() o valor de num na função main() permanece inalterado.

Vamos dar um exemplo de função de mais de uma variável. Repare que, neste caso, os argumentos são separados por vírgula e que deve-se explicitar o tipo de cada um dos argumentos, um a um. Note, também, que os argumentos passados para a função não necessitam ser todos variáveis porque mesmo sendo constantes serão copiados para a variável de entrada da função.

#include <stdlib.h> #include <iostream> using namespace std; int mult (float a, float b,float c) // Multiplica 3 numeros { cout << a*b*c; return(0); } int main() { float x,y; x=23.5; y=12.9; mult (x,y,3.87); system("pause"); return(0); }

Retornando valores

Muitas vezes é necessário fazer com que uma função retorne um valor. As funções que vimos até aqui estavam retornando o número 0. Podemos especificar um tipo de retorno indicando-o antes do nome da função. Mas para dizer ao C o que vamos retornar precisamos da palavra reservada return. Sabendo disto fica fácil fazer uma função para multiplicar dois inteiros e que retorna o resultado da multiplicação. Veja:

#include <stdlib.h> #include <iostream> using namespace std; int prod (int x,int y) { return (x*y); } int main () { int saida; saida = prod (12,7); cout << "A saida eh: " << saída << endl; system("pause");

return(0); }

Veja que, como prod retorna o valor de 12 multiplicado por 7, este valor pode ser usado em uma expressão qualquer. No programa fizemos a atribuição deste resultado à variável saida, que posteriormente foi impressa usando o cout. Uma observação adicional: se não especificarmos o tipo de retorno de uma função, o compilador C automaticamente suporá que este tipo é inteiro. Porém, não é uma boa prática não se especificar o valor de retorno e, neste curso, este valor será sempre especificado.

Com relação à função main, o retorno sempre será inteiro. Normalmente faremos a função main retornar um zero quando ela é executada sem qualquer tipo de erro.

Mais um exemplo de função, que agora recebe dois double’s e também retorna um double:

#include <stdlib.h> #include <iostream> using namespace std; double prod (double x, double y) { return (x*y); } int main () { double saida; saida = prod (45.2,0.0067); cout << "A saida eh: " << saída << endl; system("pause");

return(0); }

Forma geral

Apresentamos aqui a forma geral de uma função:

tipo_de_retorno nome_da_função (lista_de_argumentos){ código_da_função}

AUTO AVALIAÇÃO

Veja como você está. Escreva uma função que some dois inteiros e retorne o valor da soma.

ESTRUTURAS DE CONTROLE DE FLUXO

As estruturas de controle de fluxo são fundamentais para qualquer linguagem de programação. Sem elas só haveria uma maneira do programa ser executado: de cima para baixo comando por comando. Não haveria condições, repetições ou saltos. A linguagem C possui diversos comandos de controle de fluxo. É possível resolver todos os problemas sem utilizar todas elas, mas devemos nos lembrar que a elegância e facilidade de entendimento de um programa dependem do uso correto das estruturas no local certo.

O Comando if

Já introduzimos o comando if. Sua forma geral é:

if (condição) declaração;

A expressão, na condição, será avaliada. Se ela for false (falso) ou zero, a declaração não será executada. Se a condição for true (verdadeiro) ou diferente de zero a declaração será executada. Aqui reapresentamos o exemplo de um uso do comando if :

#include <stdlib.h> #include <iostream> using namespace std; int main () { int num; cout << "Digite um numero: "; cin >> num; if (num>10) cout << "O numero eh maior que 10" << endl; if (num==10) { cout << "Voce acertou!" << endl; cout << "O numero e igual a 10." << endl; } if (num<10) cout << "O numero e menor que 10" << endl; system("pause"); return (0); }

- O else

Podemos pensar no comando else como sendo um complemento do comando if. O comando if completo tem a seguinte forma geral:

if (condição) declaração_1;

else declaração_2;

A expressão da condição será avaliada. Se ela for diferente de zero (true) a declaração 1 será executada. Se for zero (false) a declaração 2 será executada. É importante nunca esquecer que, quando usamos a estrutura if-else, estamos garantindo que uma das duas declarações será executada. Nunca serão executadas as duas ou nenhuma delas. Abaixo está um exemplo do uso do if-else que deve funcionar como o programa da seção anterior.

#include <stdlib.h> #include <iostream> using namespace std; int main () { int num; cout << "Digite um numero: "; cin >> num; if (num==10) { cout << "Voce acertou!" << endl; cout << "O numero e igual a 10." << endl; } else { cout << "Voce errou!" << endl; cout << "O numero e diferente de 10." << endl; } if (num<10) cout << "O numero e menor que 10" << endl; system("pause"); return (0); }

- O if-else-if

A estrutura if-else-if é apenas uma extensão da estrutura if-else. Sua forma geral pode ser escrita como sendo:

if (condição_1) declaração_1;else if (condição_2) declaração_2;else if (condição_3) declaração_3;

.

.

.else if (condição_n) declaração_n;

else declaração_default;

A estrutura acima funciona da seguinte maneira: o programa começa a testar as condições começando pela 1 e continua a testar até que ele ache uma expressão cujo resultado dê diferente de zero. Neste caso ele executa a declaração correspondente. Só uma declaração será executada, ou seja, só será executada a declaração equivalente à primeira condição que der diferente de zero. A última declaração (default) é a que será executada no caso de todas as condições darem zero e é opcional.

Um exemplo da estrutura acima:

#include <stdlib.h> #include <iostream> using namespace std; int main () { int num; cout << "Digite um numero: "; cin >> num; if (num>10) cout << "O numero eh maior que 10" << endl; else if (num==10) { cout << "Voce acertou!" << endl; cout << "O numero e igual a 10." << endl; } else if (num<10) cout << "O numero e menor que 10" << endl; system("pause"); return (0); }

- A expressão condicional

Quando o compilador avalia uma condição, ele quer um valor de retorno para poder tomar a decisão. Mas esta expressão não necessita ser uma expressão no sentido convencional. Uma variável sozinha pode ser uma "expressão" e esta retorna o seu próprio valor. Isto quer dizer que teremos as seguintes expressões:

int num; if (num!=0) .... if (num==0) .... for (i = 0; string[i] != '\0'; i++)

equivalem a

int num; if (num) .... if (!num) .... for (i = 0; string[i]; i++)

Isto quer dizer que podemos simplificar algumas expressões simples.

- ifs aninhados

O if aninhado é simplesmente um if dentro da declaração de um outro if externo. O único cuidado que devemos ter é o de saber exatamente a qual if um determinado else está ligado.

Vejamos um exemplo:

#include <stdlib.h> #include <iostream> using namespace std; int main () { int num; cout << "Digite um numero: "; cin >> num; if (num==10) { cout << "Voce acertou!" << endl; cout << "O numero e igual a 10." << endl; }

else { if (num>10) cout << "O numero eh maior que 10" << endl; else cout << "O numero e menor que 10" << endl; } system("pause"); return (0); }

- O Operador ?

Uma expressão como:

if (a>0) b=-150; else b=150;

pode ser simplificada usando-se o operador ? da seguinte maneira:

b=a>0?-150:150;

De uma maneira geral expressões do tipo:

if (condição)expressão_1;

elseexpressão_2;

podem ser substituídas por:

condição?expressão_1:expressão_2;

O operador ? é limitado (não atende a uma gama muito grande de casos) mas pode ser usado para simplificar expressões complicadas. Uma aplicação interessante é a do contador circular.

Veja o exemplo:

#include <stdlib.h> #include <iostream> using namespace std; int main() { int index = 0, contador; char letras[5] = "Joao"; for (contador=0; contador < 1000; contador++) { cout << letras[index] << "\n"; (index==3) ? index=0: ++index; } }

O nome Joao é escrito na tela verticalmente até a variável contador determinar o término do programa. Enquanto isto a variável index assume os valores 0, 1, 2, 3, , 0, 1, ... progressivamente.

AUTO-AVALIAÇÃO

Veja como você está:

Altere o último exemplo para que ele escreva cada letra 5 vezes seguidas. Para isto, use um 'if' para testar se o contador é divisível por cinco (utilize o operador %) e só então realizar a atualização em index.

O Comando switch

O comando if-else e o comando switch são os dois comandos de tomada de decisão. Sem dúvida alguma o mais importante dos dois é o if, mas o comando switch tem aplicações valiosas. Mais uma vez vale lembrar que devemos usar o comando certo no local certo. Isto assegura um código limpo e de fácil entendimento. O comando switch é próprio para se testar uma variável em relação a diversos valores pré-estabelecidos. Sua forma geral é:

switch (variável)

{case constante_1:declaração_1;break;case constante_2:declaração_2;break;...case constante_n:declaração_n;break;defaultdeclaração_default;}

Podemos fazer uma analogia entre o switch e a estrutura if-else-if apresentada anteriormente. A diferença fundamental é que a estrutura switchnão aceita expressões. Aceita apenas constantes. O switch testa a variável e executa a declaração cujo case corresponda ao valor atual da variável. A declaração default é opcional e será executada apenas se a variável, que está sendo testada, não for igual a nenhuma das constantes.

O comando break, faz com que o switch seja interrompido assim que uma das declarações seja executada. Mas ele não é essencial ao comando switch. Se após a execução da declaração não houver um break, o programa continuará executando. Isto pode ser útil em algumas situações, mas eu recomendo cuidado. Veremos agora um exemplo do comando switch:

#include <stdlib.h> #include <iostream> using namespace std; int main () { int num; cout << "Digite um numero: "; cin >> num; switch (num) { case 9: cout << "\n\nO numero e igual a 9.\n"; break; case 10: cout << "\n\nO numero e igual a 10.\n"; break; case 11: cout << "\n\nO numero e igual a 11.\n"; break; default: cout << "\n\nO numero e igual a 9 nem 10 nem 11.\n"; } return(0); }

AUTO AVALIAÇÃO

Veja como você está.

Escreva um programa que pede para o usuário entrar um número correspondente a um dia da semana e que então apresente na tela o nome do dia. utilizando o comando switch.

O Comando for

for é a primeira de uma série de três estruturas para se trabalhar com loops de repetição. As outras são while e do. As três compõem a segunda família de comandos de controle de fluxo. Podemos pensar nesta família como sendo a das estruturas de repetição controlada.

Como já foi dito, o loop for é usado para repetir um comando, ou bloco de comandos, diversas vezes, de maneira que se possa ter um bom controle sobre o loop. Sua forma geral é:

for (inicialização;condição;incremento) declaração;

O melhor modo de se entender o loop for é ver como ele funciona "por dentro". O loop for é equivalente a se fazer o seguinte:

inicialização;if (condição){

declaração; incremento; "Volte para o comando if"

}

Podemos ver, então, que o for executa a inicialização incondicionalmente e testa a condição. Se a condição for falsa ele não faz mais nada. Se a condição for verdadeira ele executa a declaração, faz o incremento e volta a testar a condição. Ele fica repetindo estas operações até que a condição seja falsa. Um ponto importante é que podemos omitir qualquer um dos elementos do for, isto é, se não quisermos uma inicialização poderemos omiti-la. Abaixo vemos um programa que coloca os primeiros 100 números inteiros na tela:

#include <stdlib.h> #include <iostream> using namespace std; int main () { int count; for (count=1; count<=100; count++) cout << count << endl; system("pause"); return(0); }

Note que, no exemplo acima, há uma diferença em relação ao exemplo anterior. O incremento da variável count é feito usando o operador de incremento que nós agora já conhecemos. Esta é a forma usual de se fazer o incremento (ou decremento) em um loop for.

O for na linguagem C é bastante flexível. Temos acesso à inicialização, à condição e ao incremento. Qualquer uma destas partes do for pode ser uma expressão qualquer do C, desde que ela seja válida. Isto nos permite fazer o que quisermos com o comando. As três formas do for abaixo são válidas:

for (count = 1; count < 100 ; count++) { ... } for (count = 1; count < NUMERO_DE_ELEMENTOS ; count++) { ... } for (count = 1; count < BusqueNumeroDeElementos() ; count+=2) { ... } etc ...

Preste atenção ao último exemplo: o incremento está sendo feito de dois em dois. Além disto, no teste está sendo utilizada uma função (BusqueNumeroDeElementos() ) que retorna um valor que está sendo comparado com count.

- O loop infinito

O loop infinito tem a forma

for (inicialização; ;incremento) declaração; Este loop chama-se loop infinito porque será executado para sempre (não existindo a

condição, ela será sempre considerada verdadeira), a não ser que ele seja interrompido. Para interromper um loop como este usamos o comando break. O comando break vai quebrar o loop infinito e o programa continuará sua execução normalmente.

Como exemplo vamos ver um programa que faz a leitura de uma tecla e sua impressão na tela, até que o usuario aperte uma tecla sinalizadora de final (um FLAG). O nosso FLAG será a letra 'X'. Repare que tivemos que usar dois scanf() dentro do for. Um busca o caractere que foi digitado e o outro busca o outro caracter digitado na seqüência, que é o caractere correspondente ao <ENTER>.

#include <stdlib.h> #include <iostream> using namespace std; int main () { int Count; char ch; cout << "Digite uma letra - <X para sair>: ";

for (Count=1;;Count++) { cin >> ch; if (ch == 'X') break; cout << ch << endl; } cout << "Numero de caracteres digitados = " << Count << endl; system("pause"); return (0); }

- O loop sem conteúdo

Loop sem conteúdo é aquele no qual se omite a declaração. Sua forma geral é (atenção ao ponto e vírgula!):

for (inicialização;condição;incremento); Uma das aplicações desta estrutura é gerar tempos de espera.

O programa

#include <stdlib.h> #include <iostream> using namespace std; int main () { long int i; cout << "\a"; //Imprime o caracter de alerta (um beep) for (i=0; i<10000000; i++); //Espera 10.000.000 iteracoes cout << "\a"; //Imprime outro caracter de alerta system("pause"); return(0); }

faz isto.

AUTO AVALIAÇÃO

Faça um programa que inverta uma string: leia a string com cin e armazene-a invertida em outra string. Use o comando for para varrer a string até o seu final.

O Comando while

O comando while tem a seguinte forma geral:

while (condição) declaração; Assim como fizemos para o comando for, vamos tentar mostrar como o while funciona

fazendo uma analogia. Então o while seria equivalente a:

if (condição)

{declaração;"Volte para o comando if"

}

Podemos ver que a estrutura while testa uma condição. Se esta for verdadeira a declaração é executada e faz-se o teste novamente, e assim por diante. Assim como no caso do for, podemos fazer um loop infinito. Para tanto basta colocar uma expressão eternamente verdadeira na condição. Pode-se também omitir a declaração e fazer um loop sem conteúdo. Vamos ver um exemplo do uso do while. O programa abaixo é executado enquanto i for menor que 100. Veja que ele seria implementado mais naturalmente com um for ...

#include <stdlib.h> #include <iostream> using namespace std; int main () { int i = 0; while ( i < 100) { cout << i << endl; i++; } system("pause"); return(0); }

O programa abaixo espera o usuário digitar a tecla 'q' e só depois finaliza:

#include <stdlib.h> #include <iostream> using namespace std; int main () { char Ch; Ch='\0'; while (Ch!='q') { cin >> Ch; } system("pause"); return(0); }

AUTO AVALIAÇÃO

Veja como você está.

Faça um programa que inverta uma string: leia a string com cin e armazene-a invertida em outra string. Use o comando while para varrer a string até o seu final.

O Comando do-while

A terceira estrutura de repetição que veremos é o do-while de forma geral:

do{

declaração;} while (condição);

Mesmo que a declaração seja apenas um comando é uma boa prática deixar as chaves. O ponto-e-vírgula final é obrigatório. Vamos, como anteriormente, ver o funcionamento da estrutura do-while "por dentro":

declaração;if (condição) "Volta para a declaração"

Vemos pela análise do bloco acima que a estrutura do-while executa a declaração, testa a condição e, se esta for verdadeira, volta para a declaração. A grande novidade no comando do-while é que ele, ao contrário do for e do while, garante que a declaração será executada pelo menos uma vez.

Um dos usos da extrutura do-while é em menus, nos quais você quer garantir que o valor digitado pelo usuário seja válido, conforme apresentado abaixo:

#include <stdlib.h> #include <iostream> using namespace std; int main () { int i; do { cout << "Escolha a fruta pelo numero:\n\n"); cout << "\t(1)...Mamao\n"; cout << "\t(2)...Abacaxi\n"; cout << "\t(3)...Laranja\n\n"; cin >> i; } while ((i<1)||(i>3));

switch (i) { case 1: cout << "\t\tVoce escolheu Mamao.\n"; break; case 2: cout << "\t\tVoce escolheu Abacaxi.\n"; break; case 3: cout << "\t\tVoce escolheu Laranja.\n"; break; } system("pause"); return(0); }

AUTO AVALIAÇÃO

Veja como você está.

Faça um programa que inverta uma string: leia a string com cin e armazene-a invertida em outra string. Use o comando do-while para varrer a string até o seu final.

O Comando break

Nós já vimos dois usos para o comando break: interrompendo os comandos switch e for. Na verdade, estes são os dois usos do comando break: ele pode quebrar a execução de um comando (como no caso do switch) ou interromper a execução de qualquer loop (como no caso do for, do while ou do do while). O break faz com que a execução do programa continue na primeira linha seguinte ao loop ou bloco que está sendo interrompido.

Observe que um break causará uma saída somente do laço mais interno. Por exemplo:

for(t=0; t<100; ++t) { count=1; for(;;) { cout << count << endl; count++; if(count==10) break; } }

O código acima imprimirá os números de 1 a 10 cem vezes na tela. Toda vez que o break é encontrado, o controle é devolvido para o laço for externo.

Outra observaçao é o fato que um break usado dentro de uma declaraçao switch afetará somente os dados relacionados com o switch e nao qualquer outro laço em que o switch estiver. O Comando continue

O comando continue pode ser visto como sendo o oposto do break. Ele só funciona dentro de um loop. Quando o comando continue é encontrado, o loop pula para a próxima iteração, sem o abandono do loop, ao contrário do que acontecia no comando break. O programa abaixo exemplifica o uso do continue:

#include <stdlib.h> #include <iostream> using namespace std; int main() { int opcao; while (opcao != 5) { cout << "\n\n Escolha uma opcao entre 1 e 5: "; cin >> opcao; if ((opcao > 5)||(opcao <1)) continue; /* Opcao invalida: volta ao inicio do loop */ switch (opcao) { case 1: cout << "\n --> Primeira opcao.."; break; case 2: cout << "\n --> Segunda opcao.."; break; case 3: cout << "\n --> Terceira opcao.."; break; case 4: cout << "\n --> Quarta opcao.."; break; case 5: cout << "\n --> Abandonando.."; break; } } cout << endl; system("pause"); return(0); }

O programa acima ilustra uma aplicação simples para o continue. Ele recebe uma opção do usuario. Se esta opção for inválida, o continue faz com que o fluxo seja desviado de volta ao início do loop. Caso a opção escolhida seja válida o programa segue normalmente.

O Comando goto

Vamos mencionar o goto apenas para que você saiba que ele existe. O goto é o último comando de controle de fluxo. Ele pertence a uma classe à parte: a dos comandos de salto incondicional. O goto realiza um salto para um local especificado. Este local é determinado por um rótulo. Um rótulo, na linguagem C, é uma marca no programa. Você dá o nome que quiser a esta marca. Podemos tentar escrever uma forma geral:

nome_do_rótulo:....goto nome_do_rótulo;....

Devemos declarar o nome do rótulo na posição para a qual vamos dar o salto seguido de :. O goto pode saltar para um rótulo que esteja mais à frente ou para trás no programa. Uma observação importante é que o rótulo e o goto devem estar dentro da mesma função. Como exemplo do uso do goto vamos reescrever o equivalente ao comando for apresentado na seção equivalente ao mesmo:

inicialização;

início_do_loop:

if (condição)

{ declaração; incremento; goto início_do_loop;}

O comando goto deve ser utilizado com parcimônia, pois o abuso no seu uso tende a tornar o código confuso. O goto não é um comando necessário, podendo sempre ser substituído por outras estruturas de controle. Recomendamos que o goto nunca seja usado.

Existem algumas situações muito específicas onde o comando goto pode tornar um código mais fácil de se entender se ele for bem empregado. Um caso em que ele pode ser útil é quando temos vários loops e ifs aninhados e se queira, por algum motivo, sair destes loops e ifs todos de uma vez. Neste caso um goto resolve o problema mais elegantemente que vários breaks, sem contar que os breaks exigiriam muito mais testes. Ou seja, neste caso o goto é mais elegante e mais rápido.

O exemplo da página anterior pode ser reescrito usando-se o goto:

#include <stdlib.h> #include <iostream> using namespace std; int main() { int opcao; while (opcao != 5) { REFAZ: cout << "\n Escolha uma opcao entre 1 e 5: "; cin >> opcao; if ((opcao > 5)||(opcao <1)) goto REFAZ; /* Opcao invalida: volta ao rotulo REFAZ */ switch (opcao) { case 1: cout << "\n --> Primeira opcao.."; break; case 2: cout << "\n --> Segunda opcao.."; break; case 3: cout << "\n --> Terceira opcao.."; break; case 4: cout << "\n --> Quarta opcao.."; break; case 5: cout << "\n --> Abandonando.."; break; } } cout << endl; system("pause"); return(0); }

AUTO AVALIAÇÃO

Escreva um programa que peça três inteiros, correspondentes a dia , mês e ano. Peça os números até conseguir valores que estejam na faixa correta (dias entre 1 e 31, mês entre 1 e 12 e ano entre 1900 e 2100). Verifique se o mês e o número de dias batem (incluindo verificação de anos bissextos). Se estiver tudo certo imprima o número que aquele dia corresponde no ano. Comente seu programa.

PS: Um ano é bissexto se for divisível por 4 e não for divisível por 100, exceto para os anos divisíveis por 400, que também são bissextos.

MATRIZES E STRINGS

Vetores

Vetores nada mais são que matrizes unidimensionais. Vetores são uma estrutura de dados muito utilizada. É importante notar que vetores, matrizes bidimensionais e matrizes de qualquer dimensão são caracterizadas por terem todos os elementos pertencentes ao mesmo tipo de dado. Para se declarar um vetor podemos utilizar a seguinte forma geral:

tipo_da_variável nome_da_variável [tamanho];

Quando o C vê uma declaração como esta ele reserva um espaço na memória suficientemente grande para armazenar o número de células especificadas em tamanho. Por exemplo, se declararmos:

float exemplo [20]; o C irá reservar 4x20=80 bytes. Estes bytes são reservados de maneira contígua.

Na linguagem C a numeração começa sempre em zero. Isto significa que, no exemplo acima, os dados serão indexados de 0 a 19. Para acessá-los vamos escrever:

exemplo[0] exemplo[1] . . . exemplo[19] Mas ninguém o impede de escrever:

exemplo[30] exemplo[103]

Por quê? Porque o C não verifica se o índice que você usou está dentro dos limites válidos. Este é um cuidado que você deve tomar. Se o programador não tiver atenção com os limites de validade para os índices ele corre o risco de ter variáveis sobreescritas ou de ver o computador travar. Bugs terríveis podem surgir. Vamos ver agora um exemplo de utilização de vetores:

int main () { int num[100]; //Declara um vetor de inteiros de 100 posicoes int count=0; int totalnums; do { cout << "\nEntre com um numero (-999 p/ terminar): "; cin >> num[count]; count++; } while (num[count-1]!=-999); totalnums=count-1; printf ("\n\t Os números que você digitou foram:\n\n"); for (count=0;count<totalnums;count++) cout << num[count] << '\t'; return(0); }

No exemplo acima, o inteiro count é inicializado em 0. O programa pede pela entrada de números até que o usuário entre com o Flag -999. Os números são armazenados no vetor num. A cada número armazenado, o contador do vetor é incrementado para na próxima iteração escrever na próxima posição do vetor. Quando o usuário digita o flag, o programa abandona o primeiro loop e armazena o total de números gravados. Por fim, todos os números são impressos. É bom lembrar aqui que nenhuma restrição é feita quanto a quantidade de números digitados. Se o usuário digitar mais de 100 números, o programa tentará ler normalmente, mas o programa os escreverá em uma parte não alocada de memória, pois o espaço alocado foi para somente 100 inteiros. Isto pode resultar nos mais variados erros no instante da execução do programa.

AUTO AVALIAÇÃO

Veja como você está.

Reescreva o exemplo acima, realizando a cada leitura um teste para ver se a dimensão do vetor não foi ultrapassada. Caso o usuário entre com 100 números, o programa deverá abortar o loop de leitura automaticamente. O uso do Flag (-999) não deve ser retirado.

Strings

Strings são vetores de chars. Nada mais e nada menos. As strings são o uso mais comum para os vetores. Devemos apenas ficar atentos para o fato de que as strings têm o seu último elemento como um '\0'. A declaração geral para uma string é:

char nome_da_string [tamanho]; Devemos lembrar que o tamanho da string deve incluir o '\0' final. A biblioteca padrão

do C possui diversas funções que manipulam strings. Estas funções são úteis pois não se pode, por exemplo, igualar duas strings:

string1=string2; /* NAO faca isto */

Fazer isto é um desastre. Quando você terminar de ler a seção que trata de ponteiros você entenderá porquê. As strings devem ser igualadas elemento a elemento.

Quando vamos fazer programas que tratam de string muitas vezes podemos fazer bom proveito do fato de que uma string termina com '\0' (isto é, o número inteiro 0). Veja, por exemplo, o programa abaixo que serve para igualar duas strings (isto é, copia os caracteres de uma string para o vetor da outra) :

int main () { int count; char str1[100],str2[100]; .... // Aqui o programa le str1 que sera copiada para str2 for (count=0;str1[count];count++) str2[count]=str1[count]; str2[count]='\0'; .... // Aqui o programa continua }

A condição no loop for acima é baseada no fato de que a string que está sendo copiada termina em '\0'. Quando o elemento encontrado em str1[count] é o '\0', o valor retornado para o teste condicional é falso (nulo). Desta forma a expressão que vinha sendo verdadeira (não zero) continuamente, torna-se falsa.

Vamos ver agora algumas funções básicas para manipulação de strings.

- gets

A função gets() lê uma string do teclado, para usá-la devemos colocar a biblioteca <stdio.h> no início do programa. Sua forma geral é:

gets (nome_da_string); O programa abaixo demonstra o funcionamento da função gets():

#include <stdio.h> #include <stdlib.h> #include <iostream> using namesace std; int main () { char string[100]; cout << "Digite o seu nome: "; gets (string); cout << "\n\n Ola" << string << endl; system("pause"); return(0); }

- strcpy

Sua forma geral é:

strcpy (string_destino,string_origem);

A função strcpy() copia a string-origem para a string- destino. Seu funcionamento é semelhante ao da rotina apresentada na seção anterior. As funções apresentadas nestas seções estão no arquivo cabeçalho string.h. A seguir apresentamos um exemplo de uso da função strcpy():

#include <stdio.h> #include <string.h> #include <stdlib.h> #include <iostream> using namesace std; int main () { char str1[100],str2[100],str3[100]; cout << "Entre com uma string: "; gets (str1); strcpy (str2,str1); /* Copia str1 em str2 */ strcpy (str3,"Voce digitou a string "); /* Copia "Voce digitou a string" em str3 */ cout << "\n\n" << str3 <<str2 << endl;

system("pause"); return(0); }

- strcat

A função strcat() tem a seguinte forma geral:

strcat (string_destino,string_origem); A string de origem permanecerá inalterada e será anexada ao fim da string de destino.

Um exemplo:

#include <stdio.h> #include <string.h> #include <stdlib.h> #include <iostream> using namesace std; int main () { char str1[100],str2[100]; cout << "Entre com uma string: "; gets (str1); strcpy (str2,"Voce digitou a string "); strcat (str2,str1); /* str2 armazenara' Voce digitou a string + o conteudo de str1 */ cout << "\n\n" << str2 << endl; system("pause"); return(0); }

- strlen

Sua forma geral é:

strlen (string);

A função strlen() retorna o comprimento da string fornecida. O terminador nulo não é contado. Isto quer dizer que, de fato, o comprimento do vetor da string deve ser um a mais que o inteiro retornado por strlen().

Um exemplo do seu uso:

#include <stdio.h> #include <string.h> #include <stdlib.h> #include <iostream> using namesace std; int main () { int size; char str[100]; cout << "Entre com uma string: "; gets (str); size=strlen (str); cout << "A string que voce digitou tem tamanho" << size << endl; system("pause"); return(0); }

- strcmp

Sua forma geral é:

strcmp (string1,string2); A função strcmp() compara a string 1 com a string 2. Se as duas forem idênticas a

função retorna zero. Se elas forem diferentes a função retorna não-zero. Um exemplo da sua utilização:

#include <stdio.h> #include <string.h> #include <stdlib.h> #include <iostream> using namesace std; int main () { char str1[100],str2[100]; cout << "Entre com uma string: "; gets (str1); cout << "Entre com outra string: "; gets (str2); if (strcmp(str1,str2)) cout << "As duas strings são diferentes."; else cout << "As duas strings são iguais."; system("pause"); return(0); }

AUTO AVALIAÇÃO

Veja como você está.

Faça um programa que leia quatro palavras pelo teclado, e armazene cada palavra em uma string. Depois, concatene todas as strings lidas numa única string. Por fim apresente esta como resultado ao final do programa.

Matrizes

- Matrizes bidimensionais

Já vimos como declarar matrizes unidimensionais (vetores). Vamos tratar agora de matrizes bidimensionais. A forma geral da declaração de uma matriz bidimensional é muito parecida com a declaração de um vetor:

tipo_da_variável nome_da_variável [altura][largura]; É muito importante ressaltar que, nesta estrutura, o índice da esquerda indexa as linhas

e o da direita indexa as colunas. Quando vamos preencher ou ler uma matriz no C o índice mais à direita varia mais rapidamente que o índice à esquerda. Mais uma vez é bom lembrar que, na linguagem C, os índices variam de zero ao valor declarado, menos um; mas o C não vai verificar isto para o usuário. Manter os índices na faixa permitida é tarefa do programador. Abaixo damos um exemplo do uso de uma matriz:

#include <stdlib.h> #include <iostream> using namesace std; int main () { int mtrx [20][10]; int i,j,count; count=1; for (i=0;i<20;i++) for (j=0;j<10;j++) { mtrx[i][j]=count; count++; } system("pause"); return(0); }

No exemplo acima, a matriz mtrx é preenchida, sequencialmente por linhas, com os números de 1 a 200. Você deve entender o funcionamento do programa acima antes de prosseguir.

- Matrizes de strings

Matrizes de strings são matrizes bidimensionais. Imagine uma string. Ela é um vetor. Se fizermos um vetor de strings estaremos fazendo uma lista de vetores. Esta estrutura é uma matriz bidimensional de chars. Podemos ver a forma geral de uma matriz de strings como sendo:

char nome_da_variável [num_de_strings][compr_das_strings];

Aí surge a pergunta: como acessar uma string individual? Fácil. É só usar apenas o primeiro índice. Então, para acessar uma determinada string faça:

nome_da_variável [índice]

Aqui está um exemplo de um programa que lê 5 strings e as exibe na tela:

#include <stdio.h> #include <string.h> #include <stdlib.h> #include <iostream> using namesace std; int main () { char strings [5][100]; int count; for (count=0;count<5;count++) { cout << "Digite uma string: "; gets (strings[count]); } cout << "\nAs strings que voce digitou foram:\n"; for (count=0;count<5;count++) cout << strings[count] <<endl; system("pause"); return(0); }

- Matrizes multidimensionais

O uso de matrizes multidimensionais na linguagem C é simples. Sua forma geral é:

tipo_da_variável nome_da_variável [tam1][tam2] ... [tamN];

Uma matriz N-dimensional funciona basicamente como outros tipos de matrizes. Basta lembrar que o índice que varia mais rapidamente é o índice mais à direita.

- Inicialização

Podemos inicializar matrizes, assim como podemos inicializar variáveis. A forma geral de uma matriz como inicialização é:

tipo_da_variável nome_da_variável [tam1][tam2] ... [tamN] = {lista_de_valores};

A lista de valores é composta por valores (do mesmo tipo da variável) separados por vírgula. Os valores devem ser dados na ordem em que serão colocados na matriz. Abaixo vemos alguns exemplos de inicializações de matrizes:

float vect [6] = { 1.3, 4.5, 2.7, 4.1, 0.0, 100.1 }; int matrx [3][4] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; char str [10] = { 'J', 'o', 'a', 'o', '\0' }; char str [10] = "Joao"; char str_vect [3][10] = { "Joao", "Maria", "Jose" };

O primeiro demonstra inicialização de vetores. O segundo exemplo demonstra a inicialização de matrizes multidimensionais, onde matrx está sendo inicializada com 1, 2, 3 e 4 em sua primeira linha, 5, 6, 7 e 8 na segunda linha e 9, 10, 11 e 12 na última linha. No terceiro exemplo vemos como inicializar uma string e, no quarto exemplo, um modo mais compacto de inicializar uma string. O quinto exemplo combina as duas técnicas para inicializar um vetor de strings. Repare que devemos incluir o ; no final da inicialização.

- Inicialização sem especificação de tamanho

Podemos, em alguns casos, inicializar matrizes das quais não sabemos o tamanho a priori. O compilador C vai, neste caso verificar o tamanho do que você declarou e considerar como sendo o tamanho da matriz. Isto ocorre na hora da compilação e não poderá mais ser mudado durante o programa, sendo muito útil, por exemplo, quando vamos inicializar uma string e não queremos contar quantos caracteres serão necessários. Alguns exemplos:

char mess [] = "Linguagem C: flexibilidade e poder."; int matrx [][2] = { 1,2,2,4,3,6,4,8,5,10 };

No primeiro exemplo, a string mess terá tamanho 36. Repare que o artifício para realizar a inicialização sem especificação de tamanho é não especificar o tamanho! No segundo exemplo o valor não especificado será 5.

AUTO AVALIAÇÃO

Veja como você está.

O que imprime o programa a seguir? Tente entendê-lo e responder. A seguir, execute-o e comprove o resultado.

int main() { int t, i, M[3][4]; for (t=0; t<3; ++t) for (i=0; i<4; ++i) M[t][i] = (t*4)+i+1; for (t=0; t<3; ++t) { for (i=0; i<4; ++i) cout << M[t][i] << '\t'; cout << endl; } system("pause");

return(0); }

Funções Em C/C++, diferentemente de outras linguagens como Pascal, todas as ações ocorrem dentro de funções. Nas linguagems C/C++ não há conceito de um programa principal, o que existe é uma função chamada main que é sempre a primeira a ser executada.

A forma geral de uma função em C/C++ é a seguinte:

tipo nome (tipo nome1, tipo nome2, ..., tipo nomeN ) { //declaração das variáveis //corpo da função }

O tipo na definição da função especifica o tipo do resultado que será devolvido ao final da execução da função. Caso nenhum tipo seja especificado o compilador assume que um tipo inteiro é retornado. O tipo void pode ser usado para declarar funções que não retornam valor algum.

Há duas maneiras básicas de terminar a execução de uma função. Normalmente usa-se o comando return para retornar o resultado da função. Portanto, quando o comando

return expressão; for executado, o valor da expressão é devolvido para a função que chamou. Quando não há valor para retornar o comando return não precisa ser usado e a função termina quando a chave que indica o término do corpo da função é atingido.

O nome da função é qualquer identificador válido. A lista de parâmetros é uma lista, separada por vírgulas, de variáveis com seus tipos associados. É possível que existam funções que não tenham lista de parâmetros, mas ainda assim é necessário que os parênteses sejam usados.

Os parâmetros são valores que a função recebe para realizar as tarefas para as quais foi programada. Por exemplo, uma função que calcule a raiz quadrada de um número do tipo float, deve declarar como parâmetro uma variável deste tipo para receber o valor.

É importante notar que diferentemente de declarações de variáveis onde podemos associar vários nomes de variáveis a uma declaração como em:

int a, dia, mes, i; na lista de parâmetros é necessário associar um tipo a cada variável como no exemplo abaixo:

float media(float n1, float n2, float n3);

Uma função que deseje usar a função media , cujo protótipo foi definido acima, para calcular a média de três valores, nota1, nota2 e nota3, deve escrever no local onde quer que a média seja calculada o seguinte comando:

resultado = media(nota1, nota2, nota3); onde resultado é a variável que vai receber a média calculada.

É importante notar que os tipos e o número de parâmetros que aparecem na declaração da função e na sua chamada devem estar na mesma ordem e ter tipos equivalentes. Se os tipos são incompatíveis, o compilador não gera um erro, mas podem ser gerados avisos na compilação e resultados estranhos.

Protótipos de Funções O padrão ANSI estendeu a declaração da função para permitir que o compilador faça uma verificação mais rígida da compatibilidade entre os tipos que a função espera receber e àqueles que são fornecidos.

Protótipos de funções ajudam a detectar erros antes que eles ocorram, impedindo que funções sejam chamadas com argumentos inconsistentes.

A forma geral de definição de um protótipo é a seguinte:

tipo nome (tipo nome1, tipo nome2, ..., tipo nomeN);

O exemplo exfun.c mostra a declaração de uma função e seu protótipo.

#include<iostream> #include<cstdlib> using namespace std; /* Prototipo da funcao */ int soma (int, int); /* Funcao Principal */ int main() { int a=5, b=9; cout<<soma(a,b); system(“PAUSE”); return 0; } /* Definicao da funcao */ int soma(int a, int b) { return (a+b); }

Escopo de Variáveis Variáveis podem ser usadas dentro de uma função particular, e somente dentro desta função, ou pode ocorrer que uma ou mais variáveis precisem ser acessíveis à diversas funções diferentes. Por esta razão temos que definir onde as variáveis de um programa podem ser definidas e a partir deste local inferir onde elas estarão disponíveis.

As variáveis podem ser declaradas basicamente em três lugares: dentro de funções, fora de todas as funções e na lista de parâmetros das funções. As variáveis dentro das funções são chamadas de variáveis locais, as que aparecem fora de todas as funções chamamos de variáveis globais e aquelas que aparecem na lista de parâmetros são os parâmetros formais.

É importante notar que em C/C++ todas as funções estão no mesmo nível, por isto é não é possível definir uma função dentro de outra função.

Variáveis Locais

As variáveis locais são aquelas declaradas dentro de uma função. Elas passam a existir quando do início da execução do bloco de comandos ou função onde foram definidas e são destruídas ao final da execução do bloco.

Uma variável local só pode ser referenciada, ou seja, usada, dentro das funções onde foram declaradas. Outro ponto muito importante é que como as variáveis locais deixam de existir ao final da execução da função, elas são invisíveis para outras funções do mesmo programa. O código que define uma função e os seus dados são particulares a função.

No programa potencia.c podemos ver alguns exemplos de variáveis locais. A função main tem cinco variáveis locais: numero, potencia, continua, para, linha. A função eleva possui somente a variável res, enquanto que paraMaiusculas não possui nenhuma variável local. A única variável nesta função e c que é um parâmetro.

#include<iostream> #include<cstdlib> using namespace std; float eleva (float, int); char paraMaiuscula (char ); int main() { float numero; int potencia; char continua;

do { cout<<"Entre com um numero: "; cin>>numero; cout<<"Entre com a potencia: "; cin>>potencia; cout<<numero<<" elevado a "<<potencia; cout<<" e igual a "<<eleva(numero, potencia)<<endl; cout<<"Continua? [S]im ou [N]ao? "; cin>>continua; } while (paraMaiuscula(continua) == 'S'); } float eleva(float a, int b) { float res = 1.0; for ( ; b>0; b--) res *= a; return res; } char paraMaiuscula ( char c ) { if ('a' <= c && c <= 'z') c = c - 'a' + 'A'; return c; }

Um bloco de comandos se inicia em um "{" e termina em um "}". O bloco de comandos mais usado para definir uma variável é a função. Todas as variáveis que serão usadas dentro de um bloco de comandos precisam ser declaradas antes do primeiro comando do bloco. Declarações de variáveis, incluindo sua inicialização, podem vir logo após o abre chaves que inícia um bloco de comandos, não somente o que começa uma função. O exemplo (c7exdec.c) abaixo ilustra este tipo de declaração:

#include<iostream> #include<cstdlib> using namespace std; int main() { int i; for (i=0; i<10; i++) { int t; cin>>t; cout<<i*t; } }

Existem algumas vantagens em se declarar variáveis dentro de blocos.

1. Como as variáveis somente passam a existir quando o bloco passa a ser executado, o programa ocupa menos espaço de memória. Isto porque se a execução do bloco for condicional a variável pode nem ser alocada.

2. Outra vantagem é que como a variável somente existe dentro do bloco pode-se controlar melhor o uso da variável, evitando erros de uso indevido da variável.

Variáveis Globais

As variáveis globais são definidas fora de qualquer função e são, portanto, disponíveis para qualquer função. Este tipo de variável pode servir como um canal de comunicação entre funções, uma maneira de transferir valores entre elas.

Por exemplo, se duas funções tem de partilhar dados mais uma não chama a outra, uma variável global tem de ser usada.

OBS: É desaconselhável o uso indiscriminado de variáveis globais!

Parâmetros Formais

As variáveis que aparecem na lista de parâmetros da função são chamadas de parâmetros formais da função. Eles são criados no início da execução da função e destruídos no final.

Parâmetros são valores que as funções recebem da função que a chamou. Portanto, os parâmetros permitem que uma função passe valores para outra. Normalmente os parâmetros são inicializados durante a chamada da função, pois para isto que foram criados. No entanto, as variáveis que atuam como parâmetros são iguais a todas as outras e podem ser modificadas, operadas, etc, sem nenhuma restrição.

Passagem de Parâmetros por Valor

Parâmetros podem ser passados para funções de duas maneiras: passagem por valor ou passagem por referência.

Na passagem por valor uma cópia do valor do argumento é passada para a função. Neste caso, a função que recebe não pode alterar o valor original que existe somente na função que a chamou.

Ex: A função eleva apresentada anteriormente, recebe dois parâmetros por valor.

float eleva(float a, int b) { float res = 1.0; for ( ; b>0; b--) res *= a; return res; }

Passagem de Parâmetros por Referência

Na passagem por referência o que é passado para a função é o endereço do parâmetro. Portanto, a função que recebe o parâmetro pode através do endereço modificar o valor do argumento na função que a chamou,

Para a passagem de parâmetros por referência é necessário o uso de ponteiros ou operadores de referência.

1º Exemplo utilizando o operador de referência (&):

#include<iostream> #include<cstdlib> using namespace std; /* Programa que realiza a troca do conteúdo das variáveis se a primeira é maior que a segunda utilizando o operador referência */ void troca1(int &, int &); int main() { int x,y; cout<<"Entre com o primeiro valor: "; cin>>x; cout<<"Entre com o segundo valor: "; cin>>y; if(x>y) troca1(x, y); cout<<"Os valores ordenados sao: "<<x<<" "<<y<<endl; system("Pause"); return 0; }

//Realiza a troca de conteudo das variaveis void troca1(int &a, int &b) { int temp; temp = a; a = b; b = temp; }

2º Exemplo de passagem de parâmetro por referência utilizando os operadores de ponteiros (* e &):

#include<iostream> #include<cstdlib> using namespace std; /* Programa que realiza a troca do conteúdo das variáveis se a primeira é maior que a segunda utilizando o operador conteúdo (*) e o operador endereço (&)*/ void troca2(int *, int *); int main() { int x,y; cout<<"Entre com o primeiro valor: "; cin>>x; cout<<"Entre com o segundo valor: "; cin>>y; if(x>y) troca2(&x, &y); cout<<"Os valores ordenados sao: "<<x<<" "<<y<<endl; system("Pause"); return 0; } //Realiza a troca de conteudo das variaveis void troca2(int *a, int *b) { int temp; temp = *a; *a = *b;

*b = temp; }

Passagem de Vetores e Matrizes como parâmentros O nome de um vetor corresponde ao endereço do primeiro elemento do array, Quando um vetor é passado como parâmetro, apenas o endereço do primeiro elemento é passado.

Existem basicamente três maneiras de declarar um vetor como um parâmetro de uma função. Na primeira ele é declarado como tem sido apresentado em todos os exemplos até agora. O exemplo c7exvet.c mostrado abaixo mostra um programa que usa uma função para descobrir quantas vezes um caractere ocorre em um vetor. A função c_str() retorna um vetor de caracteres com terminação ´\0´.

#include<iostream> #include<cstdlib> using namespace std; int conta (const char v[30], char c); int main() { char linha[30]; char c; int maiusculas[26], minusculas[26]; cout<<"Entre com uma linha: "; gets(linha); for(c='a'; c<='z'; c++) minusculas[c-'a'] = conta(linha, c); for(c='A'; c<='Z'; c++) maiusculas[c-'A'] = conta(linha, c); for(c='a'; c<='z'; c++) if(minusculas[c-'a']) cout<<c<<" apareceu "<<minusculas[c-'a']<<" vezes\n"; for(c='A'; c<='Z'; c++) if(maiusculas[c-'A']) cout<<c<<" apareceu "<<maiusculas[c-'A']<<" vezes\n"; system("PAUSE"); return 0; } int conta (const char v[30], char c) { int i=0, vezes=0; while(v[i] != '\0')

if (v[i++] == c) vezes++; return vezes; }

Uma outra maneira, leva em conta que apenas o endereço do vetor é passado. Neste modo o parâmetro é declarado como um vetor sem dimensão. Isto é perfeitamente possível porque a função somente precisa receber o endereço onde se encontra o vetor. Além disso, C/C++ não confere limites de vetores e, portanto, a função precisa do endereço inicial do vetor e uma maneira de descobrir o final do vetor. Esta maneira pode ser, por exemplo, uma constante, ou o caracter '\0' em um vetor de caracteres.

O exemplo c7exinv.c mostra este modo de passar vetores com um programa que inverte o conteúdo de um vetor.

#include<iostream> #include<cstdlib> using namespace std; const int DIM = 6; /* Programa para ler, inverter e imprimir vetores de tamanho constante */ //As três formas do parâmetro estão corretas e são equivalentes void le_vetor (int v[DIM], int tam); void imprime_vetor (int v[], int tam); void inverte_vetor (int *v, int tam); int main() { int v[DIM]; cout<<"Entre com os dados do vetor v: \n"; le_vetor(v, DIM); cout<<"Conteudo Incial:\n"; imprime_vetor (v, DIM); inverte_vetor (v, DIM); cout<<"Conteudo apos inversao dos dados:\n"; imprime_vetor (v, DIM); system("Pause"); return 0; }

void le_vetor (int v[DIM], int tam) { for(int i=0; i<tam; i++) { cout<<"v["<<i<<"] = "; cin>>v[i]; } } void imprime_vetor (int v[], int tam) { for(int i=0; i<tam; i++) cout<<"v["<<i<<"] = "<<v[i]<<endl; } void inverte_vetor (int *v, int tam) { int temp; for(int i=0; i<tam/2; i++) { temp = v[i]; v[i] = v[tam-i-1]; v[tam-i-1] = temp; } }

A terceira maneira implica no uso de ponteiros, que discutir em breve.

O Comando return O comando return é usado para retornar o valor calculado para a função que chamou. Qualquer expressão pode aparecer no comando, que tem a seguinte forma geral:

return expressão;

A função que chamou é livre para ignorar o valor retornado. Além disso, a função pode não conter o comando e portanto nenhum valor é retornado e neste caso a função termina quando o último comando da função é executado. Quando o comando return não existe o valor de retorno é considerado indefinido. As funções que não retornam valores devem ser declaradas como do tipo void.

É importante observar que funções que são declaradas com um tipo válido podem ser incluídas em qualquer expressão válidas em C/C++.

Recursividade Funções em C/C++ podem ser usadas recursivamente, isto é uma função pode chamar a si mesmo. É como se procurássemos no dicionário a definição da palavra recursão e encontrássemos o seguinte texto:

recursão: s.f. Veja a definição em recursão

Um exemplo simples função que pode ser usada com chamadas recursivas é o fatorial de um número inteiro. O fatorial de um número pode ser defindo como o produto deste número pelo fatorial de seu predecessor, ou seja:

n! = n * (n-1)!

A função fatorial implementada sem recursão está ilustrada em c5fat.c.

A alternativa é uma função recursiva em que cada chamada da função que calcula o fatorial chama a própria função fatorial. O exemplo c7fatr.c ilustrado abaixo mostra como a função pode ser escrita recursivamente.

#include<iostream> #include<cstdlib> using namespace std; /* Programa para calculo do fatorial de um numero positivo */ unsigned int fat(unsigned int num); int main() { unsigned int numero; cout<<"\nEntre com um numero positivo: "; cin>>numero; cout<<"O fatorial de "<<numero<<" vale "<<fat(numero)<<endl; system("Pause"); return 0; } /* Funcao que calcula recursivamente o fatorial de um numero positivo*/

unsigned int fat(unsigned int num) { unsigned int fato; if (num == 1) return 1; fato = num * fat(num-1); return fato; }

Quando a função fatorial recursiva é chamada, primeiro é verificado se o número recebido como parâmetro vale 1. Neste caso a função retorna o valor 1, caso contrário ela devolve o valor da expressão num * fat(num-1), ou seja o produto do número pelo valor do fatorial do número predecessor. Portanto, quando se inicia o processo: a função é chamada com o valor do número num, caso esse numero seja diferente de 1, a função fat é chamada novamente para num – 1. Quando o processo se reverte e as chamadas começam a ser respondidas.

Um ponto importante é que toda função recursiva deve prever cuidadosamente quando o processo de recursão deve ser interrompido. Caso contrário, pode-se gerar loops recursivos infinitos. No caso da função fat o processo é interrompido quando o valor do número vale 1.

Quando uma função chama a si mesmo recursivamente, ela recebe um conjunto novo de variáveis na pilha que é usada para transferência de valores entre funções. É importante notar que recursão não trás obrigatoriamente economia de memória porque todos as variáveis criadas durante o processo de recursão devem permanecer na pilha. Nem será mais rápido, podendo às vezes ser até mais lento, porque temos o custo de chamada as funções. As principais vantagens da recursão são códigos mais compactos e provavelmente mais fáceis de serem lidos.

Argumentos - argc e argv A função main como todas as funções podem ter parâmetros. Como a função main é sempre a primeira a ser executada, os parâmetros que ela recebe são fornecidos pela linha de comando. No caso dos compiladores da Borland no menu Run uma das opções são os argumentos (ou parâmetros) a serem passados para a função main. No caso da função main são usados dois argumentos especiais argc e argv.

O primeiro argumento, argc, é uma variável inteira que indica quantos argumentos foram fornecidos para a função. Observar que argc vale sempre pelo menos 1, porque o nome do programa é sempre o primeiro argumento fornecido ao programa. A partir do segundo argumento em diante é que aparecem os outros argumentos.

O outro parâmetro é um vetor de cadeias de caracteres. Todos os argumentos são fornecidos em forma de cadeias de caracteres e portanto, caso sejam fornecidos números, estes devem ser convertidos para o formato requerido. Cada um dos argumentos do programa é um elemento deste vetor.

A primeira linha da função main pode ter a seguinte forma.

void main (int argc, char *argv[])

O programa abaixo (c7arg.c) calcula o fatorial dos números fornecidos como argumentos.

#include<iostream> #include<cstdlib> using namespace std; /* Programa para calculo do fatorial dos parâmetros recebidos pelo programa através dos argumentos fa main */ unsigned int fat(unsigned int num); int main(int argc, char *argv[]) { unsigned long int numero, fatorial; unsigned int i; system("cls"); if(argc<2) { cout<<"Para rodar: "<<argv[0]<<" num1 num2 ... .\n\n"; system("Pause"); exit(0); } for( i=1; i<argc; i++) { numero = (unsigned long int) (atoi(argv[i])); fatorial = fat(numero); cout<<"O fatorial de "<<numero<<" vale "<<fatorial<<"\n"; } system("Pause"); return 0; }

/*Funcao que calcula recursivamente o fatorial de um numero positivo */ unsigned long int fat(unsigned long int num) { unsigned long int fato; if(num == 1) return 1; fato = num * fat(num-1); return fato; }

Os nomes argc e argv são comumente usados, mas o programador é livre para escolher os nomes mais apropriados

Ponteiros Ponteiros são usados em situações em que é necessário conhecer o endereço onde está armazenada a variável e não o seu conteúdo.

Um ponteiro é uma variável que contém um endereço de memória e não o conteúdo da posição.

A memória de um computador pode ser vista como uma sequência de bytes cada um com seu próprio endereço. Não há dois bytes com o mesmo endereço. O primeiro endereço é sempre 0 e o último geralmente é uma potência de 2. Por exemplo um computador com memória igual a 16 Mbytes tem 16x1024x1024 bytes.

A Tbela 1 mostra um exemplo de um trecho de memória que contém duas variáveis (num, res) inteiras de tipo longo (4 bytes cada uma). Observar que os endereços estão pulando de quatro em quatro já que as variáveis são inteiras de tipo longo. Uma possível declaração destas variáveis dentro de um programa C/C++ poderia ser:

long int num=10, res=120;

TABELA 1: Mapa de Memória

Endereços Conteúdo Variável

996 --- ---

1000 10 num

1004 120 res

Ponteiros são importantes, por exemplo, quando se deseja que uma função retorne mais de um valor.

Por exemplo, uma função pode receber os ponteiros que apontem para os endereços dos parâmetros. Assim, esta função pode modificar diretamente os conteúdos destas variáveis.

Uma outra aplicação importante de ponteiros é apontar para áreas de memória que são administradas durante a execução do programa. Com ponteiros é possível alocar as posições de memória necessárias para armazenamento de vetores somente quando o programa estiver rodando. O programador pode reservar o número exato de posições que o programa requer.

Operações com Ponteiros

Declaração de Ponteiros

Antes de serem usados os ponteiros precisam ser declarados, assim como variáveis. A forma geral da declaração de um ponteiro é a seguinte: tipo *nome; Onde tipo é qualquer tipo válido em C/C++ e nome é o nome da variável ponteiro. Por exemplo: int *res; /* ponteiro para uma variavel inteira */ float *div; /* ponteiro para uma variavel de ponto flutuante */

Os Operadores de Ponteiros

Existem dois operadores especiais para ponteiros: * e &. Os dois operadores são unários, isto é requerem somente um operando.

O operador & devolve o endereço de memória do seu operando. Por exemplo,

pint = &soma; /* o endereco de soma e carregado em pint */ No exemplo seguinte considere a Tabela 1. Após a execução do trecho de programa abaixo a variável ponteiro p termina com o valor 1000. p = &num; O operador * é o complemento de &. O operador * devolve o valor da variável localizada no endereço que o segue. Por exemplo, o comando num = *p; significa que a variável num recebe o valor apontado por p. Estes operadores não devem ser confundidos com os já estudados em capítulos anteriores. O operador * para ponteiros não tem nada a ver com o operador multiplicação. O operador ponteiro * é unário e, como o operador &, tem precedência maior que do que todos os operadores aritméticos. Observação: Nunca acesse o conteúdo de um ponteiro antes de inicializá-lo.

Aritmética de Ponteiros

Atribuição de Ponteiros

Do mesmo modo que uma variável comum, conteúdo de um ponteiro pode ser passado para outro ponteiro do mesmo tipo. As variáveis ponteiro devem sempre apontar para os tipos de dados corretos. Uma variável ponteiro declarada como apontador de dados inteiros deve sempre apontar para dados deste tipo.

Observar que em C/C++ possível atribuir qualquer endereço a uma variável ponteiro. Deste modo é possível atribuir o endereço de uma variável do tipo float a um ponteiro inteiro. No entanto, o programa não irá funcionar da maneira correta.

Por exemplo, no trecho de programa abaixo o endereço do terceiro elemento do vetor v é carregado em p1 e o endereço da variável i é carregado em p2. Além disso, no final o endereço apontado por p1 é carregado em p2. Os comandos cout imprimem os valores apontados pelos ponteiros respectivos.

#include<iostream> #include<cstdlib> using namespace std; int main() { int vetor[] = { 10, 20, 30, 40, 50 }; int *p1, *p2; int i = 100; p1 = &vetor[2]; cout<<"*p1 = "<<*p1<<endl; p2 = &i; cout<<"*p2 = "<<*p2<<endl; p2 = p1; cout<<"*p2 = "<<*p2<<endl; system("Pause"); return 0; }

Acessando os Endereços

O abaixo faz com que o endereço da variável x seja carregado no ponteiro p. Em seguida o programa imprime o que está apontado por p.

#include<iostream> #include<cstdlib> using namespace std; int main() { float x=3.14, *p; p = &x; cout<<"*p = "<<*p<<endl;; system("Pause"); return 0; } Incrementando e Decrementando Ponteiros

O exemplo abaixo mostra que operações de incremento e decremento podem ser aplicadas em operandos. O primeiro printf imprime 30 o segundo 40 e o terceiro 50.

int main() { int vetor[] = { 10, 20, 30, 40, 50 }; int *p1; p1 = &vetor[2]; cout<<*p1<<endl; p1++; cout<<*p1<<endl; p1 = p1 + 1; cout<<*p1<<endl; system("Pause"); return 0; } Pode parecer estranho que um endereço que aponte para um número inteiro, que é armazenado em dois bytes, seja incrementado por um e passe para apontar para o próximo número inteiro. A resposta para isto é que sempre que um ponteiro é incrementado (ou decrementado) ele passa a apontar para a posição do elemento seguinte (ou anterior). Do mesmo modo somar três a um ponteiro, faz com que ele passe apontar para o terceiro elemento após o atual. Portanto, um incremento em um ponteiro que aponta para um valor que é armazenado em n bytes faz que n seja somado ao endereço.

É possível se usar o seguinte comando

*(p+1)=10; Este comando armazena o valor 10 na posição seguinte àquela apontada por p. É possível somar-se e subtrair-se inteiros de ponteiros. A operação abaixo faz com que o ponteiro p passe a apontar para o terceiro elemento após o atual. p = p + 3; A diferença entre ponteiros fornece quantos elementos do tipo do ponteiro existem entre os dois ponteiros. No exemplo abaixo é impresso o valor 3. int main() { float vetor[] = { 1.0, 2.0, 3.0, 4.0, 5.0 }; float *p1, *p2; p1 = &vetor[2]; // endereco do terceiro elemento p2 = &vetor; // endereco do primeiro elemento cout<<"Diferenca entre ponteiros %d\n"<<p1-p2; system("Pause"); return 0; } Observação: Não é possível multiplicar ou dividir ponteiros, e não se pode adicionar ou subtrair o tipo float ou o tipo double a ponteiros. Comparação de Ponteiros

É possível comparar ponteiros em uma expressão relacional. Mas, só podemos comparar ponteiros de mesmo tipo. O trecho de programa abaixo ilustra um exemplo deste tipo de operações.

char *c, *v; cin>>*c>>*v; if (c == v) // Cuidado com esse tipo de comparação!!! cout<<"As variáveis estao na mesma posicao.\n"; else cout<<"As variaveis nao estao na mesma posicao.\n";

Ponteiros e Vetores Ponteiros e Vetores estão fortemente relacionados na linguagem C/C++. O nome de um vetor é um ponteiro que aponta para a primeira posição do vetor e todas as operações já mencionadas para ponteiros podem ser executadas com um nome de vetor. Por exemplo, a declaração: int v[100]; declara um vetor de inteiros de 100 posições. Se p recebe o ponteiro para a primeira posição de v como na declaração abaixo: int *p=v;

As seguintes declarações erão idênticas:

v[i] == *(p+i) &v[i] == p+i Também são idênticas as próximas declarações: v[i] == *(v+i) &v[i] == v+i

O exemplo ilustrado abaixo mostra as duas notações sendo usadas para imprimir o mesmo vetor.

int main() { float v[] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0}; int i; for (i=0; i<9; i++) cout<<v[i]; cout<<endl; for (i=0; i<9; i++) cout<<*(v+i); cout<<endl; system("Pause"); return 0; }

Existe uma diferença fundamental entre declarar um conjunto de dados como um vetor ou através de um ponteiro. Na declaração de vetor, o compilador automaticamente reserva um bloco de memória para que o vetor seja armazenado. Quando apenas um ponteiro é declarado a única coisa que o compilador faz é alocar um ponteiro para apontar para a memória, sem que espaço seja reservado.

O nome de um vetor é chamado de ponteiro constante e, portanto, não pode ter o seu valor alterado. Assim, os comandos abaixo não são válidos:

int main() { int list[5], i; list = &i; // O ponteiro list nao pode ser modificado //recebendo o endereco de i list++; // O ponteiro list nao pode ser incrementado }

Para percorrer um vetor além da maneira mostrada no exemplo é possível usar um ponteiro variável como ilustrado no exemplo abaixo.

int main() { float v[] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0}; int i; float *p; for(i=0; i<9; i++) cout<<v[i]<<" "; cout<<endl; for(i=0; i<9; i++) cout<<*(v+i)<<" "; cout<<endl; for(i=0, p=v; i<9; i++, p++) cout<<*p<<" "; }

Observe como o ponteiro p recebe seu valor inicial e a maneira que ele é incrementado.

Ponteiros e Strings

Um string constante é escrito como no exemplo:

"Este e um string".

Até agora um dos usos mais comuns de strings constantes tem sido na função cout, como no exemplo abaixo

cout<<"Acabou o programa.\n"; Quando um string como este é enviado para a função o que é passado é o ponteiro para o string. No exemplo abixo aparece uma função que conta o número de caracteres de um string. Observe o ponteiro para o string constante e na função o ponteiro *(s+tam++) apontando caracter a caracter.

#include<iostream> #include<cstdlib> using namespace std; int strtam(const char *s); int main() { char lista[]="Isto e um teste!"; cout<<"O tamanho do string \""<<lista<<"\" e "; cout<<strtam(lista)<<" caracteres.\n"; system("pause"); return 0; } int strtam(const char *s) { int tam=0; while(*(s + tam++) != '\0'); return (tam-1); } Um outro exemplo ilustrado abaixo mostra uma função que copia um string para outro. #include<iostream> #include<cstdlib> using namespace std; int strcop(char *d, const char *o); int main() { char destino[20]; char origem[]="string de origem"; strcop(destino, origem);//copia o string origem para o destino cout<<"Origem: "<<origem<<endl; cout<<"Destino: "<<destino<<endl; system("pause"); return 0; }

int strcop(char *d, const char *o) { while ((*d++ = *o++) != '\0'); return 1; }

Alocação Dinâmica de Memória

As funções básicas de alocação dinâmica de memória em C++ são new e delete.

O exemplo abaixo ilustra o uso das função new e delete.

#include<iostream> #include<cstdlib> using namespace std; int main() { float *v; int i, tam; cout<<"Qual o tamanho do vetor? "; cin>>tam; v = new float[tam]; //Aloca a quantidade pedida pelo usuario for (i=0; i<tam; i++) { cout<<"Elemento "<<i<<" ?"; cin>>v[i]; cout<<"Li valor "<<*(v+i)<<"\n"; } delete [] v; //Para todo new deve haver sempre um delete! system("pause"); return 0; }

Um outro exemplo, agora empregando a função new está mostrado no exemplo abaixo. Observe que neste exemplo também mostramos exemplos onde um endereço de variável foi passado para uma função de modo que a função main possa receber um valor (vezes).

#include<iostream> #include<cstdlib> using namespace std; void leVetor (float *v, int tam); float procuraMaior (float *v, int tam, int *vezes); int main() { float *v, maior; int i, tam, vezes; cout<<"Qual o tamanho do vetor? "; cin>>tam; v = new float[tam]; leVetor(v, tam); maior = procuraMaior (v, tam, &vezes); cout<<"O maior elemento e "<<maior; cout<<" e aparece "<<vezes<<" vezes.\n\n"; delete [] v; system("pause"); return 0; } void leVetor (float *v, int tam) { int i; for (i=0; i<tam; i++) { cout<<"Elemento "<<i<<": "; cin>>*(v+i); cout<<"Li valor "<<*(v+i)<<"\n"; } } float procuraMaior (float *v, int tam, int *vezes) { int i; float maior; maior = v[0]; *vezes = 1; for (i=1; i<tam; i++) { if (v[i] > maior) { maior = v[i]; *vezes = 1;

} else if (maior == v[i]) *vezes=*vezes+1;; } return maior; } Cuidado para não confundir a notação: int *p = new int(5); // aloca espaço para um int e armazena nele o //valor 5 int *q = new int[5]; // aloca espaço para um vetor com 5 //elementos do tipo int

Ponteiros e Matrizes

Um ponteiro aponta para uma área de memória que é endereçada de maneira linear. Deste modo, é necessário mapear o endereço de cada elemento na matriz, que é dado por linha coluna, em um endereço linear.

Considere uma matriz chamada matriz de tamanho LIN,COL que poderia ser declarada e ter um de seus elementos lidos da seguinte maneira:

#include<iostream> #include<cstdlib> using namespace std; const int LIN=3; const int COL=4; void leMatriz(float m[LIN][COL]); void imprimeMatriz(float m[LIN][COL]); int main() { float matriz[LIN][COL]; cout<<"Entre com os dados da Matriz: \n"; leMatriz(matriz); cout<<"\nMatriz digitada: \n"; imprimeMatriz(matriz); cout<<endl; system("pause"); return 0; }

void leMatriz(float m[LIN][COL]) { for(int i=0; i<LIN; i++) { for(int j=0; j<COL; j++) { cout<<"Elemento ["<<i<<"]["<<j<<"]: "; cin>>m[i][j]; } } } void imprimeMatriz(float m[LIN][COL]) { for(int i=0; i<LIN; i++) { for(int j=0; j<COL; j++) cout<<m[i][j]<<" "; cout<<endl; } }

Caso o programa utilizasse ponteiros ao invés de notação de matrizes constantes o programa ficaria maneira apresentada a seguir.

Neste exemplo iremos criar um vetor de ponteiros que irá armazenar o endereço inicial de cada linha. Portanto, para obter um elemento da matriz primeiro devemos descobrir onde está a linha no vetor que armazena os endereços das linhas, em seguida procuramos na linha o elemento. A figura 1 ilustra como será feito o armazenamento desta matriz.

Figura 1: Exemplo de como implementar uma matriz

#include<iostream> #include<cstdlib> using namespace std; void alocaMatriz(float **&m, int l, int c); void desalocaMatriz(float **&m, int lin); void leMatriz(float **m, int l, int c); void imprimeMatriz(float **m, int l, int c); int main() { int lin, col; float **matriz; cout<<"Entre com numero de linhas da Matriz: "; cin>>lin; while (lin<=0) { cout<<"Numero invalido! Digite valor maior que zero!"; cout<<"Entre com numero de linhas da Matriz: "; cin>>lin; } cout<<"Entre com numero de colunas da Matriz: "; cin>>col; while (col<=0) { cout<<"Numero invalido! Digite valor maior que zero!"; cout<<"Entre com numero de colunas da Matriz: "; cin>>col; } alocaMatriz(matriz, lin, col); cout<<"Entre com os dados da Matriz: \n"; leMatriz(matriz, lin, col); cout<<"\nMatriz digitada: \n"; imprimeMatriz(matriz, lin, col); cout<<endl; desalocaMatriz(matriz, lin); system("pause"); return 0; }

void alocaMatriz(float **&m, int l, int c) { //Aloca primeiro as linhas m = new float*[l]; //Aloca depois as colunas for (int i=0; i<l; i++) m[i]=new float[c]; } void desalocaMatriz(float **&m, int l) { //Desaloca primeiro as colunas for (int i=0; i<l; i++) delete [] m; //Desaloca as linhas delete [] m; m=NULL; } void leMatriz(float **m, int l, int c) { for(int i=0; i<l; i++) { for(int j=0; j<c; j++) { cout<<"Elemento ["<<i<<"]["<<j<<"]: "; cin>>m[i][j]; } } } void imprimeMatriz(float **m, int l, int c) { for(int i=0; i<l; i++) { for(int j=0; j<c; j++) cout<<m[i][j]<<" "; cout<<endl; } }

Arquivos Um fluxo, ou stream, é uma abstração representando a transmissão de dados entre:

• um emissor, aquele que produz informação; • um receptor, aquele que consome a informação produzida.

Em C++, as instruções de E/S não fazem parte da linguagem. Elas são oferecidas em uma biblioteca padrão, que implementa os fluxos a partir de uma hierarquia de classes. O conceito de arquivo é ampliado no sentido de considerar não somente os que existem em disco, mas também o teclado, o vídeo, a impressora e as portas de comunicação. Ex:

• cout: que corresponde à saída padrão. • cin: que corresponde à entrada padrão. • cerr: que corresponde à saída padrão de erros.

As classes iostream interagem com esses arquivos. Para utilizar outros fluxos, devemos criá-los e associá-los a arquivos, normais ou especiais (dispositivos), ou então a vetores de caracteres. Gravando um caracter no arquivo : #include <iostream> // para funções de entrada e saida #include <fstream> // para funções de arquivo #include <cstdlib> using namespace std; int main() { ofstream fout("a:teste.txt"); // Abre arquivo para gravação //em modo texto char ch; cout << "Digite um texto "; cout << "\nPressione CTRL_Z para encerrar "; while(cin.get(ch)) // Lê um caracter do teclado fout.put(ch); // Grava o caracter no arquivo system("Pause"); return 0; } Lendo um caracter do arquivo e exibindo no vídeo: #include <iostream> // para funções de entrada e saida #include <fstream> // para funções de arquivo #include <cstdlib> using namespace std;

int main() { ifstream fin("a:teste.txt"); // Abre arquivo para leitura //em modo texto char ch; // Enquanto não for fim de arquivo: while(fin.get(ch)) // lê um caracter do arquivo cout << ch; // imprime o caracter no vídeo system("Pause"); return 0; } Gravando uma linha no arquivo : #include <fstream> // para funções de arquivo using namespace std; int main() { ofstream fout("a:teste1.txt"); // Cria arquivo para gravação //em modo texto fout << "Texto gravado no arquivo por meio de programa C++"; return 0; } Lendo uma linha do arquivo e exibindo no vídeo: #include <iostream> // para funções de entrada e saida #include <fstream> // para funções de arquivo #include <cstdlib> using namespace std; int main() { const int MAX=80; char buff[MAX]; ifstream fin("a:teste1.txt"); // Abre arquivo para leitura //em modo texto while(fin) // Enquanto não for fim de arquivo { fin.getline(buff, MAX); // Lê uma linha do arquivo cout << buff << '\n'; } // Exibe no vídeo system("Pause"); return 0; }

A função open() : Quando usamos objetos das classes ofstream ou ifstream é necessário associá-lo a um arquivo. Esta associação pode ser feita usando o construtor da classe, como nos exemplos anteriores, ou usando a função open(), membro da classe fstream, numa instrução após a criação do objeto. Tanto o construtor como open() aceitam a inclusão de um segundo argumento indicando o modo de abertura do arquivo. Modos de aberturaDescrição ios::in Abre para leitura (default de ifstream) ios::out Abre para gravação (default de ofstream) ios::ate Abre e posiciona no final do arquivo

(Este modo trabalha com leitura e gravação) ios::app Grava a partir do fim do arquivo ios::trunc Abre e apaga todo o conteúdo do arquivo ios::nocreate Erro de abertura se o arquivo não existe ios::noreplace Erro de abertura se o arquivo existir ios::binary Abre em binário (default é texto) Modo texto e modo binário : Em uma operação de gravação em arquivos aberto em modo texto (default) o caracter ‘\n’ é expandido em dois bytes, carriage-return e line-feed (CR/LF), antes de ser gravado. Em operações de leitura, o par de bytes CR/LF é convertido para um único byte ‘\n’. Quando o arquivo é aberto em modo binário não há esta conversão. #include <iostream> // para funções de entrada e saida #include <fstream> // para funções de arquivo #include <cstdlib> using namespace std; int main(int argc, char **argv) { ifstream fin; char ch; int cont = 0; if(argc !=2) { cout << "\nForma de uso: c:\nome_programa nome_arquivo "; exit(1); } fin.open(argv[1]); // Abre o arquivo em modo texto (default) while(fin.get(ch)) cont++; // Lê um caracter do arquivo e conta cout << "\nNumero de caracteres : " << cont; // Imprime contador system("Pause"); return 0; }

Modifique o programa para que o arquivo seja aberto em modo binário substituindo a linha fin.open(argv[1]); por fin.open(argv[1], ios::binary); Além do operador de inserção (<<), a classe ostream possui os seguintes métodos: ostream & put(char c); insere um caracter num fluxo ostream & write(const char *, int n);

insere n caracteres num fluxo

streampos tellp(); retorna a posição corrente dentro do fluxo ostream & seekp(streampos n); Posiciona-se a n bytes a partir do início do

arquivo. O tipo streampos corresponde à uma posição do arquivo que inicia-se em 0

ostream & seekp(streamoff d, seek_dir r);

Posiciona-se a d bytes a partir do início do arquivo (r = beg), a posição corrente (r = cur) ou o final do fluxo (r = end)

ostream & flush(); esvazia o buffer do fluxo Exemplo: #include <iostream> #include <cstdlib> using namespace std; int main() { cout.write("Hello world!", 12); cout.put('\n'); system("Pause"); return 0; } Além do operador de extração, a classe istream oferece os seguintes métodos: int get() extrai e retorna um caracter (ou EOF) istream & get(char &c) extrai e armazena em c um caracter int peek() informa o próximo caracter, sem extraí-lo istream & get(char *ch, int n, char d='\n')

extrai n-1 caracteres do fluxo e os armazena a partir do endereço ch, parando assim que o delimitador é encontrado.

istream & getline(char *ch, int n, char d='\n')

como o método anterior, exceto que o delimitador é extraído e descartado

istream & read(char *ch, int n) extrai um bloco de pelo menos n bytes do fluxo e os armazena a partir do endereço ch.

O número de bytes efetivamente lidos é obtido através do método gcount()

int gcount() retorna o número de caracteres extraídos na última leitura

streampos tellg() retorna a posição corrente dentro do fluxo istream & seekg(streampos n) posiciona-se a n bytes a partir do início do

arquivo. O tipo streampos corresponde à uma posição do arquivo, iniciando em 0.

istream & seekg(streamoff d, seek_dir r)

Posiciona-se a d bytes a partir do início do arquivo (r = beg), a posição corrente (r = cur) ou o final do fluxo (r = end)

istream & flush() esvazia o buffer do fluxo Formatação de dados Cada fluxo conserva permanentemente um conjunto de indicadores especificando a formatação corrente. Isso permite dar um comportamento default ao fluxo. O indicador de formato do fluxo é um inteiro longo (protegido) definido na classe ios: class ios { public: //... enum { skipws, // ignora os espaços na entrada left, // justifica as saídas à esquerda right, // justifica as saídas à direita internal, // preenche entre o sinal e o valor dec, // conversão em decimal oct, // conversão em octal hex, // conversão em hexadecimal showbase, // exibe o indicador de base showpoint, // exibe ponto decimal nos números reais uppercase, // exibe hexadecimais em maiúsculas showpos, // exibe sinal em números positivos scientific, // notação científica (1.234000E2) para reais fixed, // notação fixa (123.4) para reais unitbuf, // esvazia o fluxo após uma inserção stdio // permite utilizar stdout et cout }; //... protected: long x_flags; // indicador de formato //... };

A classe ios também define constantes através das quais acessamos os indicadores: • static const long basefield : permite definir a base (dec, oct ou hex) • static const long adjustfield :permite definir o alinhamento (left, right ou internal) • static const long floatfield :permite definir a notação para reais (scientific ou

fixed) Os métodos seguintes (também definidos em ios) permitem ler ou modificar os valores dos indicadores de formato: long flags() retorna o valor do indicador de formato long flags(long f) modifica os indicadores com o valor de f long setf(long setbits, long field)

altera somente os bits do campo indicado por field

long setf(long f) modifica o valor do indicador de formato long unsetf(long) zera o valor do indicador de formato Exemplo: #include <iostream> #include <iomanip> #include <cstdlib> using namespace std; int main() { cout.setf(ios::dec,ios::basefield); cout << 255 << endl; cout.setf(ios::oct,ios::basefield); cout << 255 << endl; cout.setf(ios::hex,ios::basefield); cout << 255 << endl; system(“pause”); return 0; } A execução desse programa produzirá a seguinte saída: 255 377 ff

Os métodos a seguir permite definir tamanho de campo e caracter de preenchimento: int width(int) define a largura do campo para a próxima saída int width() devolve a largura do campo de saída char fill(char) define o caracter de preenchimento de campo char fill() devolve o caracter de preenchimento de campo int precision(int)

define o número de caracteres (menos o ponto) que um real ocupa

int precision() devolve o número de caracteres (menos o ponto) que um real ocupa Exemplo 1 #include <iostream> #include <iomanip> #include <cstdlib> using namespace std; int main() { cout << "("; cout.width(4); cout.setf(ios::right,ios::adjustfield); cout << -45 << ")" << endl; cout << "("; cout.width(4); cout.setf(ios::left,ios::adjustfield); cout << -45 << ")" << endl; cout << "("; cout.width(4); cout.setf(ios::internal,ios::adjustfield); cout << -45 << ")" << endl; system(“pause”); return 0; } Esse outro exibirá o seguinte: ( -45) (-45 ) (- 45) Exemplo 2 #include <iomanip> #include <cstdlib> using namespace std; int main() {

cout.setf(ios::scientific,ios::floatfield); cout << 1234.56789 << endl; cout.precision(2); cout.setf(ios::fixed,ios::floatfield); cout << 1234.56789 << endl; } A saída exibe o valor em notação científica e em notação fixa: 1.2345678e+03 1234.57 Os manipuladores O uso dos métodos de formatação leva a instruções um tanto longas. Por outro lado, usando manipuladores podemos escrever um código mais compacto e mais legível. Em vez de escrever: cout.width(10); cout.fill('*'); cout.setf(ios::hex,ios::basefield); cout << 123 ; cout.flush(); vamos preferir a instrução equivalente: cout << setw(10) << setfill('*') << hex << 123 << flush; As classes ios, istream e ostream implementam os manipuladores predefinidos. O arquivo de inclusão iomanip define um certo número desses manipuladores: dec a próxima operação de E/S usa base decimal oct a próxima operação de E/S usa base octal hex a próxima operação de E/S usa base hexadecimal endl escreve ‘\n’ e depois descarrega o buffer ends escreve ‘\0’ e depois descarrega o buffer flush descarrega o buffer ws descarta os espaços num fluxo de entrada setbase(int b) define a base para a próxima saída setfill(int c) define o caracter de preenchimento de campo setprecision(int p) define o número de dígitos na próxima saída de número real setw(int l) define a largura do campo para a próxima saída setiosflags(long n)

ativa os bits do indicador de formato especificado por n

resetiosflags(long b) desativa os bits do indicador de formato indicado por b Podemos também escrever nossos próprios manipuladores. Por exemplo, um dos manipuladores mais utilizados, endl, é definido da seguinte maneira:

ostream &flush(ostream &os) { return os.flush(); } ostream &endl(ostream &os) { return os << '\n' << flush; } Para utilizá-lo numa instrução do tipo: cout << endl; a função operator<< é sobrecarregada na classe ostream como: Exemplo: ostream &ostream::operator<<(ostream& (*f)(ostream &)) { (*f)(*this); return *this; } A função operator<< acima recebe como parâmetro um ponteiro para a função que implementa o manipulador a ser executado. Através desse ponteiro a função que implementa o manipulador é chamada e, depois, o objeto ostream é devolvido para que o operador possa ser usado de forma encadeada. Vamos implementar, como exemplo, um manipulador para tabulação: Exemplo: ostream &tab(ostream &os) { return os << '\t'; } Esse manipulador poderá ser usado assim: cout << 12 << tab << 34;

Tipos de Dados Definidos Pelo Usuário

Estruturas - Primeira parte

Uma estrutura agrupa várias variáveis numa só. Funciona como uma ficha pessoal que tenha nome, telefone e endereço. A ficha seria uma estrutura. A estrutura, então, serve para agrupar um conjunto de dados não similares, formando um novo tipo de dados.

- Criando

Para se criar uma estrutura usa-se o comando struct. Sua forma geral é:

struct nome_do_tipo_da_estrutura {

tipo_1 nome_1; tipo_2 nome_2; ... tipo_n nome_n;

};

O nome_do_tipo_da_estrutura é o nome para a estrutura. Para declarar uma variável do tipo estrutura terríamos que declarar dentro do programa: nome_do_tipo_da_estrutura variáveis_estrutura;

As variáveis_estrutura seriam nomes de variáveis que o usuário declara e que seriam do tipo nome_do_tipo_da_estrutura. Um primeiro exemplo:

struct est {

int i; float f;

}; est a, b;

Neste caso, est é uma estrutura com dois campos, i e f. Foram também declaradas duas variáveis, a e b que são do tipo da estrutura, isto é, a possui os campos i e f, o mesmo acontecendo com b.

Vamos criar uma estrutura de endereço:

struct tipo_endereco { string rua; int numero; string bairro; string cidade; string sigla_estado; long int CEP; };

Vamos agora criar uma estrutura chamada ficha_pessoal com os dados pessoais de uma pessoa:

struct ficha_pessoal { string nome; long int telefone; tipo_endereco endereco; };

Vemos, pelos exemplos acima, que uma estrutura pode fazer parte de outra ( a estrutura tipo_endereco é usada pela estrutura ficha_pessoal).

- Usando

Vamos agora utilizar as estruturas declaradas na seção anterior para escrever um programa que preencha uma ficha.

#include <iostream> #include <cstdlib> #include <string> using namespace std; struct tipo_endereco { string rua; int numero; string bairro; string cidade; string sigla_estado; long int CEP; };

struct ficha_pessoal { string nome; long int telefone; tipo_endereco endereco; }; int main () { ficha_pessoal ficha; ficha.nome="Luiz Osvaldo Silva"; ficha.telefone=4921234; ficha.endereco.rua="Rua das Flores"; ficha.endereco.numero=10; ficha.endereco.bairro="Cidade Velha"; ficha.endereco.cidade="Belo Horizonte"; ficha.endereco.sigla_estado="MG"; ficha.endereco.CEP=31340230;

cout<<endl<<"\t**** Dados da ficha pessoal ****"; cout<<endl<<endl; cout<<"Nome:\t"<<ficha.nome<<endl;

cout<<"Tel:\t"<<ficha.telefone<<endl; cout<<"Rua:\t"<<ficha.endereco.rua<<endl; cout<<"Numero:\t"<<ficha.endereco.numero<<endl; cout<<"Bairro:\t"<<ficha.endereco.bairro<<endl; cout<<"Cidade:\t"<<ficha.endereco.cidade<<endl; cout<<"Estado:\t"<<ficha.endereco.sigla_estado<<endl; cout<<"CEP:\t"<<ficha.endereco.CEP<<endl<<endl; system("pause"); return 0; }

O programa declara uma variável ficha do tipo ficha_pessoal e preenche os seus dados. O exemplo mostra como podemos acessar um elemento de uma estrutura: basta usar o ponto (.). Assim, para acessar o campo telefone de ficha, escrevemos:

ficha.telefone = 4921234;

Como a struct ficha pessoal possui um campo, endereco, que também é uma struct, podemos fazer acesso aos campos desta struct interna da seguinte maneira:

ficha.endereco.numero = 10;

ficha.endereco.CEP = 31340230;

Desta forma, estamos acessando, primeiramente, o campo endereco da struct ficha e, dentro deste campo, estamos acessando o campo numero e o campo CEP.

- Matrizes de estruturas

Um estrutura é como qualquer outro tipo de dado no C/C++. Podemos, portanto, criar matrizes de estruturas. Vamos ver como ficaria a declaração de um vetor de 100 fichas pessoais:

ficha_pessoal fichas [100];

Poderíamos então acessar a segunda letra da sigla de estado da décima terceira ficha fazendo:

fichas[12].endereco.sigla_estado[1]; Analise atentamente como isto está sendo feito ...

AUTO AVALIAÇÃO

Veja como você está. Escreva um programa fazendo o uso de struct's. Você deverá criar uma struct chamada Ponto, contendo apenas a posição x e y (inteiros) do ponto. Declare 2 pontos, leia a posição (coordenadas x e y) de cada um e calcule a distância entre eles. Apresente no final a distância entre os dois pontos.

Estruturas - Segunda parte

- Atribuindo

Podemos atribuir duas estruturas que sejam do mesmo tipo. O C irá, neste caso, copiar uma estrutura, campo por campo, na outra. Veja o programa abaixo:

#include <iostream> #include <cstdlib> #include <string> using namespace std; struct est1 { int i; float f; }; int main () { // Declara primeira e segunda como structs do tipo est1 est1 primeira, segunda;

primeira.i = 10; primeira.f = 3.1415; segunda = primeira; // A segunda struct e' agora igual a primeira cout<<"Os valores armazenasdos na segunda struct sao: "; cout<<segunda.i<<" e "<<segunda.f<<endl; system("pause"); return 0; }

São declaradas duas estruturas do tipo est1, uma chamada primeira e outra chamada segunda. Atribuem-se valores aos dois campos da struct primeira. Os valores de primeira são copiados em segunda apenas com a expressão de atribuição:

segunda = primeira;

Todos os campos de primeira serão copiados na segunda. Note que isto é diferente do que acontecia em vetores, onde, para fazer a cópia dos elementos de um vetor em outro, tínhamos que copiar elemento por elemento do vetor. Nas structs é muito mais fácil!

Porém, devemos tomar cuidado na atribuição de structs que contenham campos ponteiros. Veja abaixo:

/* Este programa é um exemplo INCORRETO para atribuir estruturas que contenham ponteiros */ #include <iostream> #include <cstdlib> #include <string.h> using namespace std; //Define a estrutura tipo_end com dois atributos struct tipo_end { char *rua; //A struct possui um campo que é um ponteiro int numero; }; //Função Principal int main() { tipo_end end1, end2; char buffer[50]; cout<<"\nEntre o nome da rua:"; gets(buffer); // Le o nome da rua em uma string de buffer /* Aloca a quantidade de memoria suficiente para armazenar a string */ end1.rua = new char[strlen(buffer)+1];

strcpy(end1.rua, buffer); // Copia a string cout<<"\nEntre o numero:"; cin>>end1.numero; /* ERRADO end2.rua e end1.rua estao apontando para a mesma regiao de memoria */ end2 = end1; cout<<"Depois da atribuicao:\n Endereco em end1 "; cout<<end1.rua<<" "<<end1.numero; cout<<"\n Endereco em end2 "<<end2.rua<<" "<<end2.numero; /* Uma modificacao na memoria apontada por end2.rua causara' a modificacao do que e' apontado por end1.rua, o que, esta' errado !!! */ strcpy(end2.rua, "Rua Mesquita"); end2.numero = 1100; //Nesta atribuicao nao ha problemas cout<<" \n\nApos modificar o endereco em end2:\n "; cout<<"Endereco em end1 "<<end1.rua<<" "<<end1.numero; cout<<"\n Endereco em end2 "<<end2.rua<<" "<<end2.numero; cout<<endl<<endl; system("pause"); return 0; }

Neste programa há um ERRO GRAVE, pois ao se fazer a atribuição end2 = end1, o campo rua de end2 estará apontando para a mesma posição de memória que o campo rua de end1. Assim, ao se modificar o conteúdo apontado por end2.rua estaremos também modificando o conteúdo apontado por end1.rua !!!

- Passando para funções

No exemplo apresentado no ítem usando, vimos o seguinte comando:

strcpy (ficha.nome,"Luiz Osvaldo Silva");

Neste comando um elemento de uma estrutura é passado para uma função. Este tipo de operação pode ser feita sem maiores considerações.

Podemos também passar para uma função uma estrutura inteira. Veja a seguinte função:

void PreencheFicha (ficha_pessoal ficha) { ... }

Como vemos acima é fácil passar a estrutura como um todo para a função. Devemos observar que, como em qualquer outra função no C/C++, a passagem da estrutura é feita por valor. A estrutura que está sendo passada, vai ser copiada, campo por campo, em uma variável local da função PreencheFicha. Isto significa que alterações na estrutura dentro da função não terão efeito na variável fora da função. Mais uma vez podemos contornar este pormenor usando ponteiros e passando para a função um ponteiro para a estrutura.

- Ponteiros

Podemos ter um ponteiro para uma estrutura. Vamos ver como poderia ser declarado um ponteiro para as estruturas de ficha que estamos usando nestas seções:

ficha_pessoal *p;

Os ponteiros para uma estrutura funcionam como os ponteiros para qualquer outro tipo de dados no C/C++. Para usá-lo, haveria duas possibilidades. A primeira é apontá-lo para uma variável struct já existente, da seguinte maneira:

ficha_pessoal ficha;

ficha_pessoal *p;

p = &ficha;

A segunda é alocando memória para ficha_pessoal usando, por exemplo, new:

/* Programa para cadastro de pessoal */ #include <iostream> #include <cstdlib> #include <cstdio> #include <string> using namespace std; //Definicao da estrutura para armazenar endereco struct tipo_endereco { string rua; int numero; string bairro; string cidade;

string sigla_estado; long int CEP; }; //Definicao da estrutura para armazenar dados do pessoal struct ficha_pessoal { string nome; long int telefone; tipo_endereco endereco; }; int main () { ficha_pessoal *p; // Faremos a alocacao dinamica de n fichas pessoais int n; cout<<"Entre com o numero de fichas:"; cin>>n; fflush(stdin); p = new ficha_pessoal[n]; for (int i=0; i<n; i++) { cout<<endl<<"\t**** Dados da ficha pessoal "<<i<<" ****"; cout<<endl; cout<<"Nome:\t"; getline(cin,p[i].nome); fflush(stdin); cout<<"Tel:\t"; cin>>p[i].telefone; fflush(stdin); cout<<"Rua:\t"; getline(cin,p[i].endereco.rua); fflush(stdin); cout<<"Numero:\t"; cin>>p[i].endereco.numero; fflush(stdin); cout<<"Bairro:\t"; getline(cin,p[i].endereco.bairro); fflush(stdin); cout<<"Cidade:\t"; getline(cin,p[i].endereco.cidade); fflush(stdin); cout<<"Estado:\t";

cin>>p[i].endereco.sigla_estado; fflush(stdin); cout<<"CEP:\t"; cin>>p[i].endereco.CEP; fflush(stdin); } for (int i=0; i<n; i++) { cout<<endl<<"\t**** Dados da ficha pessoal "<<i<<" ****"; cout<<endl; cout<<"Nome:\t"<<p[i].nome<<endl; cout<<"Tel:\t"<<p[i].telefone<<endl; cout<<"Rua:\t"<<p[i].endereco.rua<<endl; cout<<"Numero:\t"<<p[i].endereco.numero<<endl; cout<<"Bairro:\t"<<p[i].endereco.bairro<<endl; cout<<"Cidade:\t"<<p[i].endereco.cidade<<endl; cout<<"Estado:\t"<<p[i].endereco.sigla_estado<<endl; cout<<"CEP:\t"<<p[i].endereco.CEP<<endl; } cout<<endl; delete [] p; //Atenção a alocação dinamica system("pause"); return 0; }

Há mais um detalhe a ser considerado. Se apontarmos o ponteiro p para uma estrutura qualquer (como fizemos em p = &ficha; ) e quisermos acessar um elemento da estrutura poderíamos fazer:

(*p).nome

Os parênteses são necessários, porque o operador . tem precedência maior que o operador * . Porém, este formato não é muito usado. O que é comum de se fazer é acessar o elemento nome através do operador seta, que é formado por um sinal de "menos" (-) seguido por um sinal de "maior que" (>), isto é: -> . Assim faremos:

p->nome

A declaração acima é muito mais fácil e concisa. Para acessarmos o elemento CEP dentro de endereco faríamos:

p->endereco.CEP

Fácil, não?

AUTO AVALIAÇÃO

Seja a seguinte struct que é utilizada para descrever os produtos que estão no estoque de uma loja :

struct Produto { char nome[30]; // Nome do produto int codigo; // Codigo do produto double preco; // Preco do produto };

a) Escreva uma instrução que declare uma matriz de Produto com 10 itens de produtos; b) Atribua os valores "Pe de Moleque", 13205 e R$0,20 aos membros da posição 0 e os valores "Cocada Baiana", 15202 e R$0,50 aos membros da posição 1 da matriz anterior; c) Faça as mudanças que forem necessárias para usar um ponteiro para Produto ao invés de uma matriz de Produtos. Faça a alocação de memória de forma que se possa armazenar 10 produtos na área de memória apontada por este ponteiro e refaça as atribuições da letra b; d) Escreva as instruções para imprimir os campos que foram atribuídos na letra c.

Até o momento nenhum comentário
Esta é apenas uma pré-visualização
3 mostrados em 58 páginas