Saltar para o conteúdo

C (linguagem de programação): diferenças entre revisões

Origem: Wikipédia, a enciclopédia livre.
Conteúdo apagado Conteúdo adicionado
imported>JEduardo
(Sem diferenças)

Revisão das 05h06min de 20 de maio de 2003

INTRODUÇÃO

1)ORIGEM DO C

Foi criada originalmente para o desenvolvimento do sistema operacional UNIX e implementada para este sistema no equipamento PDP-11 por Denis Ritchie. A maioria dos utilitários para este sistema operacional tais como editores de texto foram escritos em C. A linguagem não foi construída para rodar em qualquer equipamento em particular e com pequenas modificações um programa desenvolvido em um equipamento pode perfeitamente ser compilado e executado em outro equipamento.


2)CARACTERÍSTICAS DO C

C e uma linguagem de programação de uso geral que possui características de economia de expressão, estruturação de dados e controle de fluxos modernos, e um conjunto bastante grande de operadores. É uma linguagem de nível médio que não possui um conjunto de funções padrão muito grande, se compararmos com outras linguagens modernas como DBase. Algumas das funções que serão vistas aqui não são encontradas na linguagem originalmente desenvolvida.

Mesmo sendo uma linguagem moderna, C não possui características de algumas linguagens atuais como multiprogramação, operações em paralelo, operação sincronizada e co-rotinas.

Embora a ausência dessas características possa parecer uma grave deficiência, mantendo a linguagem com um conjunto de funções pequeno tem trazido realmente alguns benefícios. Desde que C é relativamente pequeno, ela pode ser descrita em um pequeno espaço e aprendida rapidamente. O compilador C é simples, compacto, gera um código eficiente e é facilmente desenvolvido. Como 80% do código de um novo compilador já existe em outros desenvolvidos para outras maquinas, isto resulta num alto grau de portabilidade entre vários sistemas.

Graças a seus conjunto de operadores de baixo nível que possibilitam a manipulação de operadores ate a nível de bit, os programas em C tendem a ser flexíveis e rápidos o suficiente para que muitas vezes não seja necessário escrever parte do código em ASSEMBLER. O exemplo mais óbvio disto e o sistema operacional UNIX que foi escrito quase que inteiramente em C. Das 13000 linhas de código deste sistema apenas 800 linhas de nível realmente muito baixo foram escritas em ASSEMBLER. Mais ainda, todos os softwares aplicativos essenciais para esse sistema operacional foram escritos em C. Por causa disso, a linguagem fundamental de desenvolvimento de qualquer software (inclusive aqueles em que o alto desempenho e flexibilidade aso características primordiais) para o sistema UNIX e o C.

C possui os principais controles de fluxo requeridos para programas bem estruturados: grupamento de comandos ( {} ); tomada de decisões (if); loops com decisões realizadas no inicio dos comandos (for, while) ou no final (do); e seleção de um entre vários casos possíveis (case/switch).

A linguagem também possui um excelente conjunto de operadores para a manipulação de ponteiros. Graças a esse conjunto de operadores, se torna muito fácil a montagem de estruturas apontadas tais como árvores, listas encadeadas, etc.

C não é uma linguagem fortemente tipada tais como PASCAL ou ALGOL. Ela é relativamente permissiva nas conversões de tipos. Deve-se tomar extremo cuidado nas operações de atribuição de valores para que os resultados das operações sejam aqueles desejados.

Por todas essas características, C esta sendo cada vez mais usada no desenvolvimento de softwares para o PC-DOS. Quando desempenho e flexibilidade na utilização são diretivas principais no projeto, a linguagem C é uma das que melhor atendem a estas prerrogativas. Alguns exemplos de aplicativos no PC-DOS construídos em linguagem C são esses:

 AutoCad comercializado pela AutoDesk Inc. que é um software de processamento gráfico utilizado largamente em empresas de engenharia e arquitetura.  Clipper comercializado pela Nantucket Corporation que é um software de gerenciamento de banco de dados muito utilizado no desenvolvimento de softwares comerciais.  Lotus 1-2-3 comercializado pela Lotus Corporation que é uma planilha eletrônica que dispensa apresentações.

Como se pode ver, pela variedade de aplicativos desenvolvidos, temos idéia da flexibilidade e velocidade de execução desta linguagem que foi desenvolvida sem maiores pretensões do que facilitar a criação de um sistema operacional que cada vez mais está se tornando um padrão no mundo.




O AMBIENTE VISUAL STUDIO

Para o aprendizado prático da linguagem C utilizaremos o produto Visual C++ na versão 6.0 que é um dos componentes do pacote Visual Studio também na versão 6.0, um produto da Microsoft Corporation. O Visual Studio é um pacote de desenvolvimento que permite o desenvolvimento de sistemas em várias linguagens de programação. Além do Visual C++, é composto das linguagens de programação Visual Basic, Visual J++ é Visual Fox Pro, que são respectivamente ambientes para as linguagens Basic, Java e DBase. A filosofia desse produto é criar um ambiente integrado que permita todo o desenvolvimento do programa incluindo a sua digitação, execução e depuração sem que haja a necessidade de abandonar o ambiente. Este produto possui um conjunto de funções e operadores bem completo gerando um código extremamente compacto. Não exploraremos toda a potencialidade deste pacote que foi desenvolvido para a utilização de C++, vendo apenas as características necessárias para a criação e execução de nossos programas em C.

UM EXEMPLO PRÁTICO

Vamos agora mostrar na pratica como o Visual C++ trabalha. Primeiramente vamos chamar o ambiente indo no botão Iniciar > Programas > Microsoft Visual Studio 6.0 > Microsof Visual C++ 6.0.

Deste modo você carrega o ambiente e ele passa a gerenciar o computador para você. Se você estiver rodando o sistema pela primeira vez ele mostra uma janela com a “dica do dia”. Clique com o mouse em close e o programa está pronto para operar.

Agora precisamos criar um projeto. Lembre-se que estamos em um ambiente integrado e por isso um sistema de vários arquivos de gerenciamento serão criados. O arquivo principal do projeto tem a extensão “.dsw”. Vamos criar um projeto com o nome “hello”.

Selecione a opção File > New para abrir um projeto ou arquivo. Como você ainda não tem nenhum projeto definido ele irá criar um projeto. Selecione a opção “Win32 Console Application” já que nós queremos criar uma aplicação que execute no modo console.

Selecione agora a opção “na empty project”. Isso indica que você não usará nenhuma das rotinas pre-programadas de C. A partir desse momento você terá criado o projeto. A partir de agora você pode criar os arquivos que fazem parte desse projeto. Vamos selecionar a opção File>new e agora o que será disponibilizado para você são os arquivos que você pode criar. Selecione a opção “C++ source file” e a tela de digitação do programa será apresentada a você. Repare que o programa terá a extensão .cpp. Para nós será indiferente usar a extensão .c ou .cpp. A extensão .cpp indica que o programa e da linguagem C++ (Programação Orientada a Objeto) o que é uma extensão da linguagem C. Em uma próxima etapa veremos quais as diferenças entre as duas.

Digite o programa abaixo usando os comandos de edição conhecidos e tenha o cuidado de só usar letras minúsculas em comandos já que a linguagem C faz diferença entre maiúsculas e minúsculas:

/* hello.c - Alo, mundo! */

  1. include <stdio.h>

main() {

   printf(“Alo, mundo!\n”);

}

Esse programa simplesmente imprime na tela a mensagem “Alo, mundo!” (sem aspas), mas com esse programa já podemos identificar uma serie de estruturas que fazem parte dos programas em C. Vamos descrevê-las agora:

Comentários - o conjunto de caracteres /* (inicio) e */ (fim) serve para informar ao compilador que ele deve ignorar a seqüência de caracteres ai compreendida. A colocação de comentários em qualquer linguagem e importante de forma a permitir o entendimento posterior da solução dada aquele caso.

Diretiva de inclusão - O símbolo # indica ao compilador que a linha de comando correspondente e uma diretiva ao preprocessador. O comando completo indica ao compilador que deve ser adicionado ao código fonte em momento de compilação o arquivo stdio.h. Veremos a função deste e outros arquivos .h mais tarde.

A função main() - Como todo o programa em C consiste de uma serie de funções, e necessário informar ao compilador qual será a primeira função a ser compilada. A função main() desempenha este papel, aonde quer que ela se encontre no programa. Logo, esta função existira em todo e qualquer programa em C.

Grupadores de comandos ({}) - As chaves são usadas para indicar o inicio e o fim de um bloco de código. São usados em conjunto com comandos de decisão, repetição e funções de forma a grupar uma serie de comandos pertencentes a essas estruturas. No nosso programa de exemplo, só existe uma função que e a função main(). Dessa forma o comando printf, que e o único comando desse programa, será envolvido pelas chaves. Uma observação importante a ser feita e que em um programa o numero de chaves que abrem deve ser igual ao numero de chaves que fecham.

Chamada a função printf() - Como você pode observar, o nosso programa exemplo contem uma linha de comandos que e uma chamada a função printf(). Esta função e chamada a partir deste ponto com o parâmetro “Alo, mundo!\n”. Esta função imprime o seu parâmetro string na tela. Já que esta função e importante para a apresentação de dados na tela, nos vamos vê-la com mais detalhes.

A função printf() e usada para mostrar na tela informações decorrentes de operações realizadas no programa, mensagens necessárias a comunicação com o usuário, etc. Esta função, alem de imprimir variáveis e constantes no formato string, também imprime dados no formato numérico.

Você deve estar se perguntando o que faz aquele \n no final da seqüência de caracteres da string de parâmetro e porque ele não foi impresso junto com a string. Esse caracter significa um new line na string indicando que após a sua impressão, o cursor deve ser posicionado na linha inferior. Como esse nos temos vários caracteres desse tipo todos precedidos pela barra invertida (\), também chamados seqüência de escape. Essas seqüências são necessárias já que não existe forma de representação via teclado desses caracteres. Abaixo temos uma relação deles:

Seqüência Valor ASCII Função \0 0 NUL Indica fim de string \a 0x07 BEL Beep \b 0x08 BS Retrocesso \f 0x0C FF Avanço de papel \n 0x0A LF Avanço de linha \r 0x0D CR Retorno de cursor \t 0x09 HT Tabulação horizontal \v 0x0B VT Tabulação vertical \\ 0x5C \ Barra invertida \’ 0x27 ‘ Apostrofo \” 0x22 “ Aspas duplas \? 0x3F ? Interrogação \ddd 0ddd Qualquer 1 a 3 dígitos de um valor octal \xhh 0xhh Qualquer 1 a 3 dígitos de um valor hexadecimal

Obs: Na coluna Valor, as constantes octais iniciam com 0 e as constantes hexa iniciam com 0x. Este 0 não e necessário após \.

Vamos ver agora como imprimir uma variável numérica dentro desta função. Vamos digitar um programa para que possamos testar essa forma de impressão. Mesmo o C tendo varias formas de representação de variáveis numéricas, vamos nos deter na impressão do tipo de variável inteira (int), que e a forma de representação numérica mais usada. Mais a frente nos veremos as outras representações possíveis. Digite agora o programa abaixo:

/* number.c - Numero = 1000 */

  1. include <stdio.h>

main() {

    int i = 1000;
    printf(“Numero = %d\n”,i);

}

No formato da função acima, vemos que o elemento %d e substituído pelo valor da variável i ou seja 1000. A presença deste elemento na string indica que ele será substituído pela variável identificada logo a seguir na seqüência de parâmetros. Tantas quanto forem as vezes que aparece este identificador serão as variáveis definidas logo a seguir na seqüência de parâmetros. Podemos alem disso montar estes valores em uma mascara de forma que ele seja colocado dentro de uma formatação desejada. Substitua o elemento %d por %4d e você vera que ira aparecer o mesmo valor. Mas ao substituirmos por %10d você ira verificar que a variável será impressa com 6 espaços em branco colocados a frente.

EXERCÍCIO:

Altere os programas acima utilizando outras seqüências de escape como as descritas na tabela acima e tente colocar outras mensagens após essa de “Alo, mundo!”.

Explore o ambiente e aprenda a executar o programa passo a passo, o que é importante quando nos formos atrás de erros no programa. A opção que nos permite realizar esta etapa e a opção build. Com o programa anterior teste estas opções para que você se familiarize com o ambiente.


TIPOS BÁSICOS DE VARIÁVEIS E ARRAYS


A) Descrição dos tipos - Para otimizar a alocação de memória, foram desenvolvidos vários tipos numéricos em que cada um deles ocupa um determinado numero de bytes. Esses tipos numéricos são divididos em inteiros e reais, e podem variar de acordo com o compilador e o equipamento utilizado. No Visual C++ temos os seguintes tipos definidos:

Especificador Bytes Faixa char 1 -128 a +127 unsigned char 1 0 a 255 short[int] 2 -32768 a +32767 unsigned short [int] 2 0 a 65535 int, long [int] 4 -21474883648 a +21474883647 unsigned int, unsigned long [int] 4 0 a 4294967295 float 4 -3.4e-38 a +3.4e-38 double 8 -1.7e-308 a +1.7e+308 long double 10 3.4e-4932 a 1.1e+4932

O identificador [int] na tabela acima e opcional.

Como você pode ver, existem especificadores que cobrem a mesma faixa de valores mas apresentam nomes diferentes. Isto é devido a compatibilidade que a linguagem deve exibir entre a variedade de equipamentos existentes que a utilizam. Em outros equipamentos, valores diferentes de faixas podem existir entre os vários especificadores mas, em numero de bits, a regra long >= int >= short é valida para todos os equipamentos. Para especificadores reais a ordem é long double >= double >= float.

Os especificadores do tipo inteiro ainda podem sofrer mais uma divisão: podem levar em conta o bit de sinal ou não. O default é fazer com que o bit mais significativo de uma variável inteira se torne bit de sinal. Caso definamos essas variáveis como sem sinal (unsigned), o bit de sinal fará parte do conjunto de valores significativos do numero e sua faixa de valores irá se estender na região positiva. Caso contrário seus valores se dividirão entre as faixas negativa e positiva.

Operações entre variáveis e constantes de tipos diferentes podem ser realizadas. O C irá assumir que a resposta será do tipo de maior precisão entre os operandos existentes. Podemos fazer com que as expressões retornem com o tipo que desejarmos utilizando o operador (tipo). Deve ser usado com cuidado pois uma resposta que não se adapte a faixa desejada terá seus valores não correspondentes a realidade. Suponha o programa:


/* tipo.c - Programa ERRADO de calculo de uma multiplicacao */

  1. include <stdio.h>

main() {

 int a, b;
 float c;
 a = 100000;
 b = 100000;
 /* atribuicao incorreta devido a faixa   */
 c = a*b;
 /*   %f e o formato de impressao de tipos float   */
 printf ("a = %d e b = %d ", a, b);
 printf ("Resp (a*b) = %f\n",c);

}


printf nos imprimirá que o resultado da operacao é 1410065408.000000 o que é um valor inteiramente absurdo. Vamos agora mudar os tipos de a e b para float e o resultado correto irá aparecer.


/* tipo.c - Programa CORRETO de calculo de uma multiplicacao */

  1. include <stdio.h>

main() {

 int a, b;
 float c;
 a = 100000;
 b = 100000;
 c = (float)a*(float)b;
 /*   %f e o formato de impressao de tipos float   */
 printf ("a = %d e b = %d ", a, b);
 printf ("Resp (a*b) = %f\n",c);

}


Repare que as variáveis são convertidas antes de efetuada a multiplicação porque após esta o resultado estará comprometido.

B) Representação de constantes - As constantes inteiras podem ser expressas em decimal, hexadecimal e octal. Os seus valores ditam o seus tipos de dados, a menos que sejam seguidas de um sufixo. Constantes reais só podem ser expressas em decimais.

Ex: 34 Tipo int 34U Tipo unsigned int 34L Tipo long int com sinal 34UL Tipo unsigned long int 034 034 é um octal 0x34 0x34 ou 0X34 são hexadecimais 0xB8000000L Constante hexadecimal do tipo long int 34.0F Tipo float 34.0 Tipo double

As constantes inteiras também podem ser expressas na forma de caracteres que devem ser representados sendo circundados por plics (por exemplo: ‘A’, ‘B’, ‘C’). Isso significa que seu valor numérico na tabela ASCII será tomado como valor desta constante.

A linguagem C faz uso de um outro tipo de constante que e a constante simbólica. Este tipo de constante, que não ocupa espaço de memória e não pode ter seu valor alterado pelo programa, informa ao compilador que deve proceder a substituição pelo valor correspondente aonde quer que encontre o identificador.

Ex:

  1. define PI 3.141592654
  2. define EGABASE 0xA0000000L

Repare que os identificadores (PI e EGABASE) estão colocados com letras maiúsculas. Isto e praxe nos programas em C para que possamos diferenciar este tipo de constante das variáveis comuns definidas por nos. Ao final do comando não é colocado ; de fim de comando.

As variáveis, quando alocadas na memória do computador, estão carregadas com um valor indeterminado. Podemos fazer com que estas variáveis assumam valores determinados por nós assim que forem criadas. Fazemos uma operação de atribuição assim que forem definidas.

Ex:

int a = 0, b = 100;

C) Impressão dos resultados - Para que possamos imprimir resultados na forma desejada, podemos utilizar a função printf com parâmetros adequados (para cada tipo ou formato especificado, um parâmetro %). Vamos ver como isso se realiza:


A especificação geral do formato da printf tem a seguinte forma:

  % [flag] [tamanho] [.precisão] [F|N|h|l] tipo

Onde cada um tem uma função especifica. Vamos ver essas funções por partes:

Tipo - Embora apareça por ultimo no formato acima, esse parâmetro é o mais importante. É escolhido em função do tipo de dado e a forma de impressão do resultado. É importante que não misturemos os tipos de impressão em ponto flutuante com os tipos inteiros pois isto nos conduzira a resultados incoerentes. A tabela abaixo nos fornece estes tipos:

Caracter tipo numérico Tipo de entrada Formato de saída d Inteiro Inteiro decimal com sinal i Inteiro Inteiro decimal com sinal o Inteiro Inteiro octal com sinal u Inteiro Inteiro decimal sem sinal x Inteiro Inteiro hexadecimal sem sinal com “a”, “b”, etc X Inteiro Inteiro hexadecimal sem sinal com “A”, “B”, etc f Ponto flutuante Valor float ou double com sinal no formato [-] dddd.dddddd e Ponto flutuante Valor float ou double com sinal no formato [-] d.dddd e [+/-]ddd (e minúsculo). g Ponto flutuante Valor float ou double com sinal no formato e ou f, baseado no valor e sua precisão. O ponto decimal e valores decimais só serão impressos se necessário. E Ponto flutuante Valor float ou double com sinal no formato [-] d.dddd E [+/-]ddd. G Ponto flutuante Mesmo que g só que imprimindo no formato cientifico com E (maiúsculo).


Caracter tipo caracter Tipo de entrada Formato de saída c Caracter Um único caracter s String Seqüência de caracteres até encontrar \0 (nulo) ou o limite de sua precisão % Nenhum Necessário para imprimir %


Caracter tipo ponteiro Tipo de entrada Formato de saída n Ponteiro para int Um único caracter p Ponteiro Seqüência de caracteres até encontrar \0 (nulo) ou o limite de sua precisão


Flag - Especifica a maneira de como será impresso o resultado. Os caracteres de flag são os seguintes:


Flag O que especifica - Justifica o resultado pela esquerda, preenchendo o espaço a direita com brancos. Se não especificado, o resultado é justificado a direita e o espaço a esquerda preenchido com zeros e brancos + O número com sinal irá sempre começar com o sinal correspondente (+ ou -) Branco Se o valor é positivo, o valor de saída é apresentado com um branco ao invés do sinal positivo. Valores negativos começarão ainda com o sinal negativo. Veja tabela abaixo.

Tabela de formas alternativas (#)

Caracter de conversão Como # afeta o resultado c, s, d, i, u Sem efeito 0 0 irá preceder a resposta quando for impressa se o argumento for diferente de zero x ou X 0x ou 0X irá preceder a resposta quando for impressa e o argumento diferente de zero e, E ou f O resultado sempre apresenta um ponto decimal, mesmo que não existam dígitos decimais. Normalmente o ponto decimal só aparece nos resultados que possuam parte fracionária. g ou G O mesmo que e ou E não removendo os zeros decimais

Tamanho - Especifica o tamanho mínimo do campo de saída do valor.

Especificador de tamanho Como o tamanho da saída e afetado n No mínimo n caracteres serão impressos. Se o valor de saída tem menos que n caracteres, essa saída e preenchida com brancos (a esquerda ou a direita, em função do flag). 0n No mínimo n caracteres serão impressos. Se o valor de saída tem menos que n caracteres, essa saída é preenchida com zeros.

  • A seqüência de parâmetros passados apresenta o especificador de tamanho, o qual deve preceder o argumento a ser formatado

Precisão - Esta especificação começa sempre com um ponto, que a separa da especificação de tamanho. Estipula o valor de casas decimais a ser impresso.

Especificador de precisão Como a precisão de dados é afetada Ausente Precisão configurada com default. Default = 0 para os tipos d, i, o, u, x, X Default = 6 para os tipos e, E, f Default = todos os dígitos significativos para os tipos g e G. Imprime até o primeiro caracter nulo para o tipo s Sem efeito no tipo c .0 Para os tipos d, i, o, u, x a precisão é configurada para default. Para os tipos e, E, f o ponto decimal não é impresso .n N caracteres ou n casas decimais são impressas. Se o valor de saída possui mais que n caracteres o valor pode ser truncado ou arredondado (isto pode ou não acontecer dependendo do tipo de caracter)

  • A seqüência de parâmetros passados apresenta o especificador de precisão, o qual deve preceder o argumento a ser formatado.

Qualificador - São os caracteres F, N, h, l que afetam o modo de printf interpretar o argumento de entrada.

Qualificador de argumento Como o argumento é interpretado F O argumento e lido como um Far pointer. N O argumento e lido como um Near pointer. Não pode ser usado com o modelo de memória Huge. H O argumento e interpretado como short int para d, i, o, u, x ou X. L O argumento e interpretado como long int para d, i, o, u, x ou X. O argumento e interpretado como double para e, E, f, g ou G


O programa abaixo nos mostra um exemplo pratico da utilização dos formatos de printf.

/* printf.c - Programa de formatos de impressão */

  1. include <stdio.h>
  2. define I 100
  3. define R 100.0

main() {

    char a = 100;
    int b = 100;
    short c = 100;
    unsigned short d = 100;
    long e = 100;
    float f = 100.0;
    double g = 100.0;
    printf (“Todas as variáveis possuem valor 100\n”);
    printf (“Caracter %%c /%c/ \n”,I);
    printf (“Caracter %%5d /%5d/ \n”,a);
    printf (“Inteiro %%-14.0d /%-14.0d/ \n”,b);
    printf (“Inteiro %%#14X /%#14X/ \n”,b);
    printf (“Inteiro %%#14Np /%#14Np/ \n”,b);
    printf (“Curto %%#12hd /%#12hd/ \n”,c);
    printf (“Curto sem sinal %%+#07u /%+#07u/ \n”,d);
    printf (“Longo %%+11Fd /%+11Fd/ \n”,e);
    printf (“Longo %%+11Fp /%+11Fp/ \n”,e);
    printf (“Real %%*.*f /%*.*f/ \n”,10,1,f);
    printf (“Duplo %%#g /%#g/ \n”,g);

}

Do programa executado temos como saída:

  Todas as variáveis possuem valor 100
  Caracter %c /d/
  Caracter %5d /  100/
  Inteiro %-14.0d /100           /
  Inteiro %#14X /          0X64/
  Inteiro %#14Np /          0064/
  Curto %#12hd /         100/
  Curto sem sinal %+#07u /+000100/
  Longo %+11Fd /       +100/
  Longo %+11Fp / +0000:0064/
  Real %*.*f /     100.0/
  Duplo %#g /100.000/


D) Arrays - E um conjunto de componentes do mesmo tipo que ocupam uma região continua de memória. Seus elementos são acessados variando-se o componente chamado índice, de acordo com a posição do elemento na lista. Por exemplo, se quiséssemos acessar o segundo elemento da lista, era necessário fazer com que esse índice assuma o valor 1 (isto porque o elemento índice 0 também faz parte do array). Esse índice e colocado entre colchetes e posicionado no final da variável array (o array também e chamado de vetor).

Vamos ver agora um exemplo de como declarar um array e inicializá-lo. O programa abaixo faz a troca dos elementos de 2 vetores colocando-os em ordem decrescente. Será utilizado um comando que ainda não vimos que é o comando for. Explicações sobre este comando serão vistas no próximo capitulo.

/* vetor.c - troca de elementos de vetores colocando em ordem decrescente */

  1. include <stdio.h>
  2. define I 100

main() {

  int  i, j, temp,
       vet1[8] = {0, 1, 2, 3, 4, 5, 6, 7},
       vet2[8] = {I, I-1, I-2, I-3, I-4, I-5, I-6, I-7};
  printf (“Imp. dos vetores com os valores iniciais\n”);
  for (i = 0; i < 8; i++)
  {
     printf(“vet1[%d] = %d vet2[%d] = %d\n”, i, vet1[i], i, vet2[i]);
  }
  /* Troca dos elementos */
  for (i = 0, j = 8-1; i < 8; i++, j-—)
  {
     temp = vet1[i];
     vet1[i] = vet2[j];
     vet2[j] = temp;
  }
  printf (“\nImp. dos vetores com os valores trocados\n”);
  for (i = 0; i < 8; i++)
  {
     printf(“vet1[%d] = %d vet1[8+%d] = %d\n”,
     i, vet1[i], i, vet1[8+i]);
  }

}

Resultado da execução:

Imp. dos vetores com os valores iniciais vet1[0] = 0 vet2[0] = 100 vet1[1] = 1 vet2[1] = 99 vet1[2] = 2 vet2[2] = 98 vet1[3] = 3 vet2[3] = 97 vet1[4] = 4 vet2[4] = 96 vet1[5] = 5 vet2[5] = 95 vet1[6] = 6 vet2[6] = 94 vet1[7] = 7 vet2[7] = 93

Imp. dos vetores com os valores trocados vet1[0] = 93 vet2[0] = 7 vet1[1] = 94 vet2[1] = 6 vet1[2] = 95 vet2[2] = 5 vet1[3] = 96 vet2[3] = 4 vet1[4] = 97 vet2[4] = 3 vet1[5] = 98 vet2[5] = 2 vet1[6] = 99 vet2[6] = 1 vet1[7] = 100 vet2[7] = 0

Um cuidado deve ser tomado quando estamos trabalhando com vetores que é a faixa de valores que eles abrangem. Caso ultrapassemos esta faixa, podemos invadir a área de outras variáveis ou a área de código com efeitos talvez não desejados para o nosso programa.

Uma maneira de saber a quantidade de elementos do vetor e a utilização do operador sizeof(var). Com ele podemos saber a quantidade de bytes que esta sendo ocupado por qualquer variável, seja ela array ou uma variável comum. Insira as linhas:

int tamvet; tamvet = sizeof(vet1)/sizeof(vet1[0]);

Após as definições das variáveis e substitua o valor 8 utilizado nas condições do programa por tamvet e você terá uma condição variável dependente do tamanho definido para o vetor (não utilize a variável para definir esse tamanho).

Uma categoria especial de arrays e formada pelas strings. Strings são uma seqüência de caracteres terminadas pelo caracter nulo (\0). No programa as strings são representadas pela seqüência de caracteres iniciada e encerrada por aspas (“). Variáveis strings são vetores do tipo char.

Os caracteres da string são acessados individualmente pelos índices de sua posição no array. Uma string que contenha o valor “Alo mundo” será armazenada da seguinte forma:

A l o m u n d o \0 0 1 2 3 4 5 6 7 8 9

Repare no caracter \0 (nul) colocado no final da string. Ele e que marca que o final da string acontece na posição 9 não importando os caracteres que venham da posição 10 em diante.

Caso quiséssemos trocar o caracter m minúsculo para M maiúsculo era só atribuir ao elemento da string na posição 4 o valor ‘M’ entre plics (não esqueça que caracteres ou seqüência deles colocados entre aspas são strings, sempre sendo encerradas com um caracter nulo).

str[4] = ‘M’

Para tratar essas strings o C tem uma série de funções que nos permitem copiar a string de um lugar para o outro, calcular o seu comprimento, etc. Essas funções de manipulação de strings geralmente começam com str. As funções mais importantes são:

strcat(char str1[], char str2[]) - Concatena o valor de str2 em str1.

strcmp(char str1[], char str2[]) - Compara os valores da strings em str1 e str2. Caso str1 > str2, será retornado um valor positivo. Se str1 < str2, um valor negativo. Caso sejam iguais será retornado o valor 0.

strcpy(char str1[], char str2[]) - Copia a string de str2 em str1.

strlen(char str[]) - Retorna com o comprimento da string.


Abaixo nos damos um programa exemplo com o uso dessas funções:

  1. include <string.h>
  2. include <stdio.h>
  3. define TAM 20

main() {

 char str1[] = “AAAAA”,
 str2[TAM] = “BBBBB”; /* TAM e necessário porque
                será concatenada com str1 */
 strcat(str2, str1);
 str2[5] = ‘Z’;
 printf(“str2: %s\n”, str2);
 printf(“Comp de str2: %d\n”, strlen(str2));
 printf(“str2(%s) > str1(%s): %d\n”, str2, str1,
        strcmp(str1,str2));
 strcpy(str2,str1);
 printf(“str2(%s) > str1(%s): %d\n”, str1, str2,
        strcmp(str1,str2));

}

Resultado de execução:

str2: BBBBBZAAAA Comp de str2: 10 str2(BBBBBZAAAA) > str1(AAAAA): -1 str2(AAAAA) > str1(AAAAA): 0

EXERCÍCIO:

Digite os programas acima e faca alterações em printf testando os formatos de impressão vistos e crie programas que façam uso das funções de tratamento de strings vistas acima.


OPERADORES E COMANDOS DA LINGUAGEM C

1) OPERADORES

Como qualquer linguagem de programação, a linguagem C possui um conjunto de operadores que permite a realização de atribuições, comparações, etc. Nesse conjunto de operadores, que e bem extenso, e que esta um dos pontos fortes da linguagem. Vamos agora estudar esses operadores:

A) Atribuição - Em um programa, quase sempre se torna necessário que o valor em uma variável ou expressão seja transferido para outra. O operador = realiza esta operação.

B) Aritméticos - São os operadores aritméticos comuns existentes em todas as linguagens. Temos os operadores aritméticos binários +, -, * e / conhecidos de todos nós e o operador % de resto de divisão. Como operador aritmético unário temos o sinal - que inverte o valor em uma variável ou torna uma constante negativa.

C) Comparação - Esses operadores são utilizados para que tomemos decisões em nosso programa e possamos escolher o caminho a seguir. São os seguintes:

== igualdade != desigualdade >= maior ou igual que <= menor ou igual que > maior que < menor que

D) Lógicos - São os operadores AND, OR e NOT usados na construção de expressões lógicas. Possuem a seguinte representação:

&& And || Or ! Not

E)A nível de bit - E nesta classe de operadores que a linguagem C se diferencia das outras. Nenhuma outra linguagem possui tantos operadores a nível de bit (a não ser a linguagem assembler). Vamos ver agora quais são esses operadores:

And a nível de bit (&) - Realiza a operação lógica AND com todos os bits correspondentes (um a um) das variáveis.

Ex: Suponhamos que queiramos atribuir a uma variável inteira z o resultado da operação entre as variáveis x AND y, ambas inteiras, a nível de bit sendo que a variável x possui o valor 2 e y possui o valor 3. Qual seria o valor de z ?

Primeiramente, a nossa expressão ficaria assim:

z = x & y

Agora o valor de z. Vamos transformar o conteúdo de nossas variáveis para bit e ver como o C opera:

x 0 ... 010 y 0 ... 011 ————— z 0 ... 010

Após a execução do comando, você pode ver que o resultado da operação e o valor 2.

   Or a nível de bit (|) - Realiza a operação lógica OR com todos os bits correspondentes (um a um) das variáveis.

Ex: Suponhamos que no exemplo acima ao invés da operação AND realizássemos a operação OR. Qual seria o valor de z ?

Primeiramente, o formato da expressão:

z = x | y

Transformando os respectivos formatos em bit:

  x  0 ... 010
  y  0 ... 011
     ————-
  z  0 ... 011

Após a execução do comando, o resultado da operação e o valor 3.

Xor a nível de bit (^) - Realiza a operação lógica EXCLUSIVE OR com todos os bits correspondentes (um a um) da variável.

Ex: Suponhamos que agora realizássemos entre os dois operadores a operação XOR. Qual seria o valor de z ?

A expressão ficaria assim:

z = x ^ y

Transformando os respectivos formato em bit e realizando com cada um deles a operação XOR:

x 0 ... 010 y 0 ... 011 ————— z 0 ... 001

Após a execução do comando, o resultado da operação e o valor 1.

Not a nível de bit (~) - Inverte o valor de cada bit da variável a ser negada.

Ex: Realizaremos a operação AND com as variáveis y e NOT x e colocaremos o resultado em z. Qual será o valor de z?

Primeiramente, o formato da expressão:

      z = ~x & y

Transformando os respectivos valores em formato de bit, temos:

~x 1 ... 101

 y  0 ... 011

—————

 z  0 ... 001

Após a execução do comando, o resultado da operação e o valor 1.

   Deslocamento a esquerda (<<) - Executa a função com nome SHL em assembler, colocando um zero na posição do bit menos significativo a cada rotação executada (a cada rotação o numero e multiplicado por 2, mas tome cuidado com o bit de sinal).

Ex: Vamos rodar a variável x três casas a esquerda e colocar o resultado em z.

z = x << 3

x 0000 ... 010 << 3 ——————— z 0 ... 010000

z possui agora o valor 16.

Deslocamento a direita (>>) - Executa a função com nome SHR em assembler, colocando um zero na posição do bit mais significativo a cada rotação executada (a cada rotação o numero e dividido por 2, mas tome cuidado com o bit de sinal).

Ex: Vamos rodar a variável x três casas a direita e colocar o resultado em z.

z = x >> 3


x 0 ... 010 >> 3 ————— z 0 ... 000

z possui agora o valor 0.

F)Operadores de incremento - Esses operadores são utilizados para incrementar ou decrementar o conteúdo de variáveis numéricas. São representados por ++ para incremento e — para decremento e colocados junto da variável.

Esses operadores podem ser colocados antes ou depois das variáveis que estão sendo incrementadas. Sendo colocados antes, as variáveis serão incrementadas ANTES de terem seus valores utilizados na expressão. Caso contrario, as variáveis serão incrementadas DEPOIS de serem utilizadas na expressão.

Ex: Suponhamos que desejássemos atribuir a nossa variável z o valor de x antes e depois de seu incremento. Faríamos da seguinte maneira:

z = ++x;

Caso x continue com o mesmo valor 2 colocado anteriormente, a variável x será incrementada antes do seu valor ser atribuído a z, tendo esta no final da operação o valor 3 (x == z).

Caso desejássemos que z assumisse o valor de x antes do incremento, colocaríamos o operador após a variável, da seguinte forma:

z = x++;

Isto significa que a variável z recebera o valor 2 enquanto x será incrementado assumindo o valor 3 após a expressão (x != z).

Obs: Estas considerações serão validas também para o operador de decremento —.


2)EXPRESSÕES RESUMIDAS

Expressões aritméticas usando os operadores acima podem ser escritas de uma forma mais compacta em alguns casos. Suponhamos a expressão:

i = i + 2

No qual o seu lado esquerdo e repetido em seu lado direito. Podemos reescrevê-la, obtendo o mesmo valor em i, da seguinte forma:

i += 2

A maioria dos operadores binários (operadores como + que possuem operandos a esquerda e a direita) tem um operador de atribuição correspondente a op=, onde op e um dos seguintes operadores:

+     -     *    /    %    <<    >>    &    ^    |

Uma observação deve ser feita na utilização de expressões desse tipo. Suponhamos que encontremos uma operação deste tipo em um programa:

x *= y+1

Em um primeiro momento podemos pensar que a expressão acima tenha um correspondente na forma não compactada ao seguinte:

x = x * y+1

O que e uma conclusão errada. Numa expressão resumida, as operações a direita serão todas efetuadas antes de realizarmos a operação situada ao lado do sinal de atribuição. Logo, o verdadeiro resultado da operação resumida acima será o seguinte:

x = x * (y+1)

Podemos fazer em C atribuições em série fazendo com que o valor de todos elementos colocados na seqüência de atribuições assumam o valor do elemento colocado mais a direita. Essa característica e interessante quando se torna necessário inicializarmos uma série de variáveis.

Ex: a = b = c = 0

Todas as variáveis assumirão o valor 0 (a, b, c).

3)PRECEDÊNCIA

Em todas as linguagens de programação, temos uma seqüência de execução das operações existentes numa expressão. Em C isto não é diferente. Podemos ter uma ordem de execução de acordo com o tipo de operador e de acordo com a posição deste na cadeia da expressão (o operador pode ser associado da esquerda para a direita ou vice-versa). A tabela abaixo nos dá a ordem de execução dos operadores em uma expressão e o sentido de sua associação:



Operador Associação () [] -> . Esq. para Dir. ! ~ ++ — - (tipo) * & sizeof Dir. para Esq.

  • /  % Esq. para Dir.

+ - Esq. para Dir. << >> Esq. para Dir. < <= > >= Esq. para Dir. ==  != Esq. para Dir. & Esq. para Dir. ^ Esq. para Dir. | Esq. para Dir. && Esq. para Dir. || Esq. para Dir. ?: Dir. para Esq. = += -= etc. Dir. para Esq. , Esq. para Dir.

Como você pode ver, na tabela acima existem operadores que ainda não vimos. A medida que formos prosseguindo no nosso estudo a tabela irá se tornando mais clara.


4)COMANDOS

Em todas as linguagens de computação existem comandos que permitem a seleção de uma determinada operação, executem determinado trecho de programa se determinadas condições forem preenchidas, etc. Esses comandos podem ser divididos em comandos condicionais, seletivos e repetitivos. Vamos agora ver esses tipos de comandos:

A) Condicionais - São comandos que permitem a seleção entre 2 séries de comandos dependendo do resultado de uma avaliação. Pode também ser usado para executar uma série de comandos se uma condição for satisfeita. Em C, este comando e representado pelo comando if. Sua sintaxe é a seguinte: if (condicao) {

   comando 1;
   comando 2;
      .
      .

} else {

   comando A;
   comando B;
      .
      .

} if (condicao) {

     comando 1;
     comando 2;
        .
        .

}

                            (A)                                    ou                              (B)

Na situação (A), caso a condição seja satisfeita, serão efetuados os comandos 1 e 2 da condição. Caso contrario, serão efetuados os comandos A e B. Se pegarmos a situação (B), iremos verificar que os comandos 1 e 2 só serão efetuados se a condição for satisfeita. Caso contrario, o programa será executado como se esses comandos não existissem. Repare na utilização dos grupadores { e }. No caso de existir apenas um comando para a decisão tomada (veja a situação abaixo), não e necessário usá-los. A utilização de parênteses para envolver a condição e de uso obrigatório.

Suponhamos agora o seguinte caso:

if (a > b)

     z = a;

else

     z = b;

Existe uma maneira mais resumida de realizar uma decisão como essa que e usando o comando ?:. Como você pode observar, o resultado da operação acima e que a variável z ira armazenar o valor máximo entre a e b. Usando essa nova forma de decisão, a nossa seqüência ficaria assim:

z = (a > b) ? a : b; /* z = max(a,b) */

Isso significa que se a condição for verdadeira, z irá receber o valor de a (colocado logo após ?), senão ira receber o valor b (colocado após o sinal :). Repare que ambos os elementos da decisão são expressões cujo resultado e, ao final da execução, atribuído a variável z.

Na nossa tabela de precedência vista anteriormente, vemos que este comando ocupa a antepenúltima posição. Logo não seria necessário o uso de parênteses envolvendo a condição já que pela ordem de precedência a condição seria avaliada primeiro. Mas por uma questão de visualização, e comum envolvermos as condições a serem verificadas neste comando por parênteses.

B) Seletivos - São comandos que selecionam uma entre varias condições a serem satisfeitas. São representados pelos comandos switch/case e else if. Vejamos primeiramente o caso else if.

Como comando de if anterior, esse comando também realiza decisões só que as realiza em serie a medida que as condições não forem satisfeitas. Sua sintaxe e a seguinte:

if (condicao 1)

   comandos 1

else if (condicao 2)

   comandos 2

else if (condicao 3)

   comandos 3

else

   comandos 4

As condições acima são executadas em ordem. Caso a condição 1 não seja atendida, será avaliada a condição 2 e assim por diante. Os comandos associados a condição satisfeita é que serão executados. O else correspondente a comandos 4 é opcional e é executado se nenhuma das condições acima for satisfeita.

O comando switch/case (na realidade são dois comandos) é um comando de seleção que verifica aonde o resultado de uma expressão se iguala dentro de uma serie de determinadas constantes. A sintaxe deste comando é a seguinte:

switch (expressao) {

   case const1:
       comandos 1;
   case const2:
       comandos 2;
   case const3:
       comandos 3;
      .
      .
   (outras condições, se necessário)
      .
      .
   default:
       comandos N;

}


A seleção se faz da seguinte forma: o resultado de expressão é examinado. Caso ele se iguale a const1, os comandos existentes daí para baixo serão executados. O mesmo acontece as outras condições determinadas. Caso nenhuma das respostas existentes se adapte aquela da expressão, será selecionada a opção default (opcional). Com o comando break colocado antes da próxima condição, apenas os comandos existentes da condição selecionada ate o comando break serão executados. Veremos um exemplo deste comando no final deste capitulo.


C) Repetição - Algumas vêzes se torna necessário a repetição de determinados trechos de programas até que ou enquanto determinada condição ocorra. A linguagem C nos fornece 2 mecanismos para executar essas repetições. São os comandos while e for.

O comando while irá executar a repetição dos comandos internos ao while enquanto determinada condição for atendida. Essa condição pode ser verificada no inicio do loop ou no final dele. Vamos ver agora essas duas sintaxes:

while (condicao) do {

   comandos;

} do

   comandos;

while (condicao);

                                (A)                                      ou                                 (B)

Como podemos ver, na sintaxe (A) a condição e testada antes do loop ser executado. Isto significa que se condicao não for atendida no início, os comandos não serão executados. Na sintaxe (B), ao contrário, a condição e testada no final do loop fazendo com que comandos seja executado pelo menos uma vez. Caso haja mais de um comando a ser executado na sintaxe (A), o grupador de comandos { e } deverá ser usado.


O comando for trabalha de forma parecida ao loop com while existente na sintaxe (A) da explicação anterior. Sua sintaxe e a seguinte:

for (expr1; expr2; expr3)

     comandos;

Que e funcionalmente equivalente ao loop com while escrito da seguinte forma:

expr1; while (expr2) {

      comandos;
      expr3;

}

Gramaticalmente, os três componentes de for são expressões. Mais comumente, expr1 e expr3 são atribuições ou chamadas de funções e expr2 e uma expressão relacional. Entretanto for e usado mais comumente que while pois mantém os comandos do loop visíveis no seu começo. Isto e muito importante para facilitar a legibilidade dos programas.

O programa abaixo executa uma contagem dos caracteres digitados classificando-os em numéricos, espaços em branco e outros. A função que realiza o trabalho de ler caracteres do teclado e acumula-los no buffer e a função getchar(). Após a entrada dos dados, tecle enter.

/* digits.c - conta digitos, espacos em branco e outros */

  1. include <dos.h>
  2. include <stdio.h>

main() {

 /* ndigito e um array de 10 posicoes */
 int c, i, nbranco, noutro, ndigito[10];
 nbranco = noutro = 0;
 for (i = 0; i < 10; i++)
   ndigito[i] = 0;
 while ((c = getchar()) != ‘F’)
   switch(c)
   {
     case ‘0’:
     case ‘1’:
     case ‘2’:
     case ‘3’:
     case ‘4’:
     case ‘5’:
     case ‘6’:
     case ‘7’:
     case ‘8’:
     case ‘9’:
         ndigito[c - ‘0’]++;
         break;
     case ‘ ‘:
     case ‘\n’:
     case ‘\t’:
         nbranco++;
         break;
     default:
         noutro++;
         break;
   }
 printf(“Digitos (0 a 9) =”);
 for (i = 0; i < 10; i++)
   printf(“ %d”, ndigito[i]);
 printf(“\nEspacos em branco = %d, outros = %d \n”,
      nbranco, noutro);

}


No programa acima você tem um exemplo das estruturas de controle vistas ate agora. Este programa lê caracteres vindos do teclado e coloca em ndigito a quantidade de caracteres numéricos lidos incrementando a sua posição correspondente no vetor. Caracteres em branco, tabulações e new lines são contados incrementando-se a variável nbranco. Faremos uma pausa agora para ver 2 comandos que não se classificam nas especificações acima que são o break e o continue.

break - O comando break e um outro modo de sair de loops e switches diferente dos testes no inicio e final dos loops. Ao ser executado, faz com que o programa saia de seu loop ou switch mais interno (veja o exemplo acima). Caso não fosse usado, o programa continuaria a executar os comandos determinados e executar algum código não desejado.

continue - e um outro comando de saída de loops mas diferentemente do comando break, este comando não forca a saída do loop mas a realização da próxima iteração do loop correspondente. Caso a condição retorne com o valor verdadeiro, significa que o loop continuara a ser executado. Este comando não se aplica ao comando switch. O exemplo abaixo mostra um pedaço de programa que realiza operações com números positivos abandonando os números negativos:

for (i=0; i < N; i++)
{
  if (a[i] < 0)  /* pula numeros negativos */
     continue;   /* forca o teste */
     .
     .
  /* processa numeros positivos */
     .
     .
}

D)Desvio - Comandos deste tipo são representados nessa e em outras linguagens pelo comando goto (vá para) e os rótulos que marcam as posições para onde será desviado. E um comando que quebra todas as regras de estruturação de programas e só deve ser usado como último recurso. Ele será visto aqui e depois não falaremos mais nele.

Este comando é usado quando ocorre um erro de proporções graves que não pode ser previsto ou quando uma chave e acionada pelo usuário de forma a interromper um processo no meio de sua execução. Caso este processo cause problemas se for interrompido pelo meio, deve ser dado um tratamento para interrompê-lo. Esse processo pode estar debaixo de dois ou três níveis de loop. Suponha a estrutura abaixo:

for (...)
     for (...)
    {
           ...
          if (tragedia || interrupcao)
               goto erro;
           ...
    }
    ...

erro:

      /* trata e mostra a mensagem de erro */

Ficaria muito trabalhoso montar uma estrutura de tratamento desse erro por isso fica muito mais fácil a utilização de uma instrução de desvio tipo goto.

EXERCÍCIOS:

1) Faça um programa que imprima uma tabela de conversão de graus Fahrenheit para graus Celsius usando a fórmula C = (5/9)(F - 32) com o intervalo de 20 graus e uma precisão de uma casa decimal do intervalo de 0 a 300 graus.

2) Faça um programa que permita a inclusão de uma variável string no meio de outra em uma posição desejada (numerada). Caso esta opção seja maior que o comprimento da string aonde ela será inserida, o programa irá se comportar como a função strcat.

3) Faça um programa que multiplique 2 matrizes fornecendo o resultado numa terceira. Para multiplicar duas matrizes o número de colunas da matriz A deve ser igual ao numero de linhas da matriz B. Veja o exemplo:

   A              B               C

|1 2 3| |3 5 4| |20 29 23| | | | | | | |4 5 6| X |7 3 5| = |53 71 59| | | | | | | |7 8 9| |1 6 3| |86 113 95|


C11 = A11 * B11 + A12 * B21 + A13 * B31

C12 = A11 * B12 + A12 * B22 + A13 * B32 etc


Inicialize as variáveis matriz da seguinte forma:

int a[3][3] =

    {
      {1, 2, 3},
      {4, 5, 6},
      {7, 8, 9}
    };


4) Faca um programa que converta todos os caracteres maiúsculos de uma string em caracteres minúsculos, exceto aqueles precedidos de espaços em branco. Esses devem ser convertidos em caracteres maiúsculos.