𝖂𝖎ƙ𝖎𝖊

C (linguagem de programação)

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; assim, 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 máquinas, 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 até 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 são características primordiais) para o sistema UNIX é 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 [[linguagem tipada|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 mais bem 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 prática 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!  */

 #include <stdio.h>

 int main(void)
 {
    printf("Alo, mundo!\n");
    return 0;
 }

Esse programa simplesmente imprime na tela a mensagem “Alo, mundo!” (sem aspas), mas com esse programa já podemos identificar uma série 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 aí compreendida. A colocação de comentários em qualquer linguagem é 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 é 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, onde quer que ela se encontre no programa. Logo, esta função existirá em todo e qualquer programa em C.

Grupadores de comandos ({}) - As chaves são usadas para indicar o início 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 é o único comando desse programa, será envolvido pelas chaves. Uma observação importante a ser feita é que em um programa o número de chaves que abrem deve ser igual ao número de chaves que fecham.

Chamada a função printf() - Como você pode observar, o nosso programa exemplo contém uma linha de comandos que é uma chamada à função printf(). Esta função é 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 é importante para a apresentação de dados na tela, vamos vê-la com mais detalhes.

A função printf() é 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, além 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 por que 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, existem vários caracteres desse tipo, todos precedidos pela barra invertida (\), também chamados seqüência de escape. Essas seqüências são necessárias, pois 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    '         Apóstrofe
\"             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 é 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 várias formas de representação de variáveis numéricas, vamos nos deter na impressão do tipo de variável inteira (int), que é a forma de representação numérica mais usada. Mais a frente veremos as outras representações possíveis. Digite agora o programa abaixo:

/*  number.c   -   Numero = 1000  */
 
#include <stdio.h>
 
int main(void)
{
    int i = 1000;
    printf("Numero = %d\n", i);
     
    return 0;
}

No formato da função acima, vemos que o elemento %d é 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 além disso montar estes valores em uma máscara, de forma que ele seja colocado dentro de uma formatação desejada. Substitua o elemento %d por %4d e você verá que irá aparecer o mesmo valor. Mas ao substituirmos por %10d você irá 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 */
 
#include <stdio.h>
 
int main(void)
{
  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);
   
  return 0;
}


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 */
 
#include <stdio.h>
 
int main(void)
{
  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);
   
  return 0;
}


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 */ 
 
#include <stdio.h>
 
#define I 100
#define R 100.0
 
int main(void)
{
    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);
     
    return 0;
}

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 */
 
#include <stdio.h>
#define I 100
 
int main(void)
{
  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]);
  }
   
  return 0;
}

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:

#include <string.h>
#include <stdio.h>
 
#define TAM 20
 
int main(void)
{
  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));
   
  return 0;
}

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

 #include <dos.h>
 #include <stdio.h>

 int main(void)
 {
  /* 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);

  return 0;
 }

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.



FUNCÕES E PONTEIROS

As funções em C, como em qualquer linguagem que se diga estruturada, quebra tarefas grandes e complicadas em tarefas menores e mais simples de serem entendidas. Se não existissem as funções seria necessário começarmos o trabalho de construção de um novo programa a partir do zero pois teríamos que ter em mente todas as variáveis utilizadas por ele (quando falamos em funções estamos nos referindo inclusive as funções internas da própria linguagem como calculo de logaritmos e senos).

Em C, alem de contarmos com as funções inerentes a própria linguagem, podemos construir as nossas próprias funções com todas as características dessas. Com a utilização de funções bem construídas, com o menor contato possível com variáveis externas a ela, podemos transportá-la inteiramente ou com pequenas modificações para outros programas sem nos importarmos em como ela realiza a sua tarefa, contanto que a realize corretamente. Isso facilita tremendamente a realização de mudanças em programas e a busca de erros graças a utilização do trabalho de desenvolvimento e teste anterior.

A linguagem C foi desenvolvida para fazer uso das funções de modo fácil e eficiente. As funções podem estar contidas no mesmo arquivo ou divididas em vários. Podem ser compiladas juntamente com o programa ou estarem armazenadas em bibliotecas, sendo montadas no programa após a linkedição.

A)A construção de funções - Vamos tomar como exemplo a criação de uma função que retorne com o valor máximo de dois elementos armazenados em variáveis. Se o nosso programa fosse construído sem o uso de funções teríamos que escrever o código para calcular o valor máximo tantas vezes quanto tivesse sido necessário na execução do programa. Vamos ver um exemplo:

 #include <stdio.h>

 int main(void)
 {
    int a = 1, b = 2, c = 5, d;

    /* Calcula o maximo entre a e b */
    d = (a > b) ? a : b;
    /* Calcula o maximo entre o maximo de a e b  e c */
    d = (d > c) ? d : c;
    printf("O valor maximo entre a=%d, b=%d e c=%d vale d=%d\n",
            a, b, c, d);

    return 0;
 }

No nosso exemplo a rotina é pequena e só foi necessária sua utilização apenas 2 vezes. Mas suponhamos que a rotina tivesse 100 linhas e fosse utilizada 100 vezes. Isso resulta em um aumento da quantidade de código de aproximadamente 10000 linhas. Isto causa sérios problemas na depuração e teste de um programa.


Agora vamos montar o programa com uma função chamada max que englobe a rotina que calcule o valor máximo entre dois números. Como podemos ver acima essa função irá receber dois valores e retornar com o valor máximo entre eles. O programa ficaria da seguinte maneira:

 #include <stdio.h>

 int max(int a, int b);

 int main(void)
 {
   int a = 1, b = 2, c = 5, d = 15,
       e, f;

   /* Calcula o maximo entre a e b com o valor em e */
   e = max(a, b);
   /* Calcula o maximo novamente entre c e d com
      resultado em f */
   f = max(c, d);
   printf("O maximo entre a=%d, b=%d e %d \n",
       a, b, e);
   printf("O maximo entre c=%d, d=%d e %d \n",
       c, d, f);
   printf("O maximo entre a=%d, b=%d, c=%d e %d \n",
       a, b, c, max(max(a,b),c));
   printf("O maximo entre a=%d, b=%d, c=%d, d=%d e %d \n",
       a, b, c, d, max(max(a,b), max(c,d)));

   return 0;
 }

/* funcao que recebe 2 valores e retorna o maior deles */

int max(int a, int b)
{
   return (a > b) ? a : b;
}

Resultado da execução:

O maximo entre a=1, b=2, e 2 O maximo entre c=5, d=15, e 15 O maximo entre a=1, b=2, c=5 e 5 O maximo entre a=1, b=2, c=5, d=15, e 15

Vamos agora ver como a função e estruturada dividindo-a em partes:

1) Protótipo da função - A maneira usual de definir as funções de forma que elas possam ser utilizadas no programa é definir o seu protótipo antes. Definir o protótipo é simplesmente colocar o cabeçalho da função seguido por ponto e vírgula (;). A função do protótipo é estabelecer uma espécie de “porta de entrada”, já que sem ele teríamos que definir a função antes de utilizá-la e não usá-la após a função main como acontece no nosso programa. Nele o protótipo está definido entre o #include e o main().

2) Identificador de tipo - Este identificador e colocado antes do nome da função. Indica que a função pode assumir um valor e este valor irá substituir o próprio nome da função como exemplificado acima. Caso a função não retorne valor algum ela pode ser definida como void. No nosso exemplo é assumido o tipo int.

3) Nome da função - É o identificador que diferencia uma função das outras. Este nome de função deve ser único e não ser repetido para outras funções.

4) Lista de argumentos - São as informações passadas do exterior para a rotina de forma a permitir o seu trabalho. Caso não existam argumentos (como na nossa função main) nada deve ser colocado entre seus parênteses ou então o identificador void deverá ser usado. No nosso caso existem 2 argumentos e ambos são inteiros. A quantidade de argumentos mandadas a rotina deve ser a mesma a ser recebida com tipos compatíveis.

Existe uma outra maneira de representar funções em C que talvez você encontre em programas mais antigos que e a seguinte:

int max(a, b)
int a, b;
{
  return (a > b) ? a : b;
}

Como você pode ver a lista de argumentos esta definida abaixo do identificador da função. Esta função representada desta forma trabalha exatamente da mesma maneira que a anterior por isso não usaremos este tipo de representação.

5)Grupadores de comandos - Como vimos logo no primeiro capitulo quando fomos apresentados ao nosso primeiro programa em C, aqui também se torna necessário o uso dos caracteres { e } a fim de delimitar a nossa função. Estes caracteres sempre estarão presentes no inicio e no fim da função.

6)Retorno de valores - Se existe algum valor a ser retornado quando a função e encerrada, ira aparecer o identificador return colocado geralmente ao final da função. Você pode perceber que no nosso exemplo esta retornando como resultado o valor da expressão (a > b) ? a : b. O valor assumido pela expressão será retornado dentro da função permitindo que ele seja atribuído a uma variável ou seja inclusive usado como parâmetro de outra função.

Quando definimos uma função que retorne um valor inteiro, não é necessário reservar uma área de memória para retorno dos valores dessa função o que não ocorre quando usamos outros tipos para retorno de resultados. Por exemplo, se retornássemos um valor do tipo double, a função ficaria com a sua primeira linha assim:

double max(int a, int b)

Sugestão: Edite alguns dos programas.h do Visual C++ (por exemplo, o arquivo stdio.h) e você vera que existe uma série dessas definições com nomes de funções internas da linguagem (os arquivos estão localizados no diretório \Program Files\Microsoft Visual Studio\VC98\Include). Não se apavore ao ver a quantidade de definições aí existentes. São necessárias para que possamos utilizar as rotinas internas do Visual C++.


B)Conceitos de endereço e ponteiro - Esses conceitos são importantíssimos para que possamos extrair da linguagem todo o seu potencial. Talvez seja a facilidade de manipulação com essas estruturas que faca do C uma linguagem tão largamente usada para o desenvolvimento de software básico. Vamos ver estes conceitos separadamente:

1)Endereço - Toda a variável alocada para trabalhar no programa possui um endereço de memória ao qual nos referenciamos utilizando o nome da variável. Ate este ponto, uma vez definido o conjunto de variáveis que irão trabalhar no programa, não poderíamos criar mais variáveis em tempo de execução. Utilizando este conceito podemos alocar uma nova área de memória e acessá-la através de seu endereço de alocação ou acessar uma variável já existente diretamente através de seu endereço.

2)Ponteiro - E a estrutura que possuímos para armazenar endereços. Com isto podemos manipular o conteúdo de variáveis criadas por nos no programa ou acessar de forma indireta ou apontada variáveis que existam no programa.

Suponha que x e uma variável inteira e px seja um ponteiro criado de uma maneira ainda não especificada. O operador que especifica o endereço de uma variável é o operador unário &.

px = &x; /* o ponteiro px aponta para a variável x */

Agora em px esta armazenado o endereço da variável x. Este operador de endereço & só pode ser aplicado a variáveis e arrays de elementos; construções como &(x+1) e &3 são inválidas.

O operador unário * trata seu operando como ponteiro para a região de memória desejada. Supondo que a variável y também seja inteira na atribuição abaixo:

y = *px; /* y recebe o valor apontado por px */

Será atribuído a y a valor da região de memória apontada por px. Então a seqüência

px = &x; y = *px;

Atribui a y o valor de x da mesma maneira que

y = x;

A declaração do ponteiro acima e a seguinte:

int *px;

Esta declaração deve ser entendida como um mnemônico, indicando que se px ocorre no contexto *px, esta se torna equivalente a uma variável do tipo int. O ponteiro se apresenta com o tipo da variável em que foi declarado facilitando o entendimento de atribuições feitas a essas regiões de memória apontadas. Por exemplo:

double atof(char *vetor), *dp;

Informa que a função atof e *dp tem valores do tipo double.

Podemos utilizar os ponteiros em expressões. Por exemplo, se px aponta para o inteiro x, enato *px pode ocorrer em qualquer contexto em que se utilize a variável x

y = *px + 1;

Atribui a variável y o conteúdo de x adicionado de 1. O comando

printf("%d\n", *px);

Irá imprimir o conteúdo corrente da variável x e ainda o comando

d = sqrt((double) *px); /* função com parâmetro double */

Irá imprimir o valor da variável x. Os ponteiros também podem aparecer no lado esquerdo de atribuições. Se px aponta para x então

*px = 0;

Atribui a variável x o valor zero e o comando

*px += 1;

Incrementa x de uma unidade da mesma maneira que

(*px)++

Os parênteses são necessários neste ultimo exemplo; sem eles seria incrementado px ao invés de incrementar o valor apontado por px porque operadores unários como * e ++ possuem a mesma precedência e são executados da direita para a esquerda.

Como ponteiros também são variáveis, eles podem ser manipulados da mesma forma que qualquer variável podendo realizar atribuições e operações com outros ponteiros. Se py também for um outro ponteiro do tipo int então

py = px

Copia o conteúdo de px para py, fazendo com que py aponte também para a variável x.


C)Ponteiros e argumentos de funções - Até aqui vimos que a linguagem C passa os argumentos para as funções copiando seus valores para uma região de memória e transferindo esses valores para as variáveis de argumento da função. As variáveis transferidas dessa maneira nos chamamos de “passadas por valor”. Podemos alterar o valor dessas variáveis dentro da função que esses valores não se refletirão fora da função.

Isto é útil quando montamos subrotinas e desejamos fazer com que elas sejam o mais independente possíveis do programa principal. Mas muitas vezes desejamos fazer com que a função retorne mais do que um valor e ainda não sabemos como fazer isto. Por exemplo vamos ver uma função que permuta dois elementos inteiros retornando com seus valores a rotina que os chamou. Em um primeiro momento podemos pensar que e suficiente escrever:


troca(a, b);


Onde a rotina esta definida do seguinte modo:

/* rotina ERRADA de troca de valores */

 void troca(int x, int y)
 {
    int temp;

    temp = x;
    x = y;
    y = temp;
 }

Por causa da chamada por valor a rotina troca não afeta os argumentos a e b passados quando a rotina foi chamada.

Felizmente temos uma maneira de obter o efeito desejado. Fazendo com que a função tenha como argumento os endereços das variáveis a serem trabalhadas, a chamada a função deve ser escrita da seguinte forma:

troca(&a, &b);

Desde que agora os argumentos a serem passados a função são ponteiros é necessário que alteremos a função propriamente dita de forma que possamos receber estes argumentos ponteiros e possamos trabalhar com eles. Então a função ficaria da seguinte forma:

 /* rotina CORRETA de troca de valores */

 void troca(int * px, int * py)
 {
    int temp;

    temp = *px;
    *px = *py;
    *py = temp;
 }

Agora os argumentos são os endereços das respectivas variáveis que serão manipuladas através desses endereços. Esta forma de passagem de parâmetros e chamada de “passagem por referencia”, já que não nos referimos diretamente á variável mas a seu endereço. Indiretamente estaremos alterando os valores das variáveis existentes na rotina que chamou troca o que resulta numa alteração efetiva dos valores.


D)Ponteiros e arrays - Em C existe um forte relacionamento entre ponteiros e arrays, forte o suficiente de modo que ponteiros e arrays devem ser tratados simultaneamente. Qualquer função que possa ser executada com o uso de arrays também poderá ser feita utilizando-se ponteiros. A versão com ponteiros será em geral mais rápida mas algumas vezes, se você não estiver familiarizado com o uso de ponteiros, um pouco mais difícil de entender.

A declaração

int a[10];

Define um array de tamanho 10, que e um bloco de 10 objetos consecutivos chamados a[0], a[1], ..., a[9]. A notação a[i] significa uma referência ao elemento afastado i posições do começo. Se pa e um ponteiro para inteiros, ele será declarado como:

int * pa;

E será inicializado com a atribuição:

pa = &a[0];

Que atribui ao ponteiro pa o endereço do primeiro elemento do array. Agora supondo que tenhamos declarado uma variável x inteira a atribuição

x = *pa;

Ira copiar o valor de a[0] para x.

Se pa aponta para um determinado elemento em um array a, então por definição pa + 1 aponta para o próximo elemento do vetor. Generalizando pa-i aponta para i elementos antes de pa e pa+i aponta para i elementos após pa. Se pa aponta para o elemento a[0] então

*(pa+1)

Refere-se ao conteúdo do elemento a[1], pa+i é o endereço do elemento a[i] e *(pa+i) é o conteúdo do elemento a[i].

Estes comentários são verdadeiros independente de qualquer que seja o tipo de elemento do array a. A definição de “adicionar 1 ao ponteiro” é que o incremento é feito de acordo com o tipo do objeto que esta sendo apontado. Ou seja, em pa+i, i é multiplicado pelo tamanho do objeto que pa aponta antes de ser adicionado a pa.

A relação entre indexação e ponteiros aritméticos é muito próxima. De fato a referencia para um array é convertida pelo compilador em um ponteiro para o começo do array fazendo com que o nome do array seja um sinônimo de sua localização. Já que isto é uma realidade então a atribuição:

pa = &a[0];

Pode ser escrita da seguinte forma:

pa = a;

Outra forma de escrever uma variável indexada a[i] é *(a+i). Na avaliação de a[i], C converte essa representação para *(a+i) imediatamente, fazendo com que as duas formas de escrever a operação sejam iguais. Aplicando o operador & a ambos os membros da equivalência temos que a representação de &a[i] pode também ser escrita da forma a+i indicando o endereço do elemento i afastado de a. Em resumo, qualquer array ou expressão indexada pode ser escrita como um ponteiro e vice-versa, inclusive no mesmo comando.

Uma diferença existente entre nome de array e ponteiro deve ser mantida em mente. Como ponteiro é uma variável operações como pa = a e pa++ são permitidas o que não ocorre com o nome de arrays. Esses nomes são constantes e operações como a=pa ou a++ ou pa=&a são ilegais.

Quando o nome de um array é passado para uma função, o que é passado é a localização do começo daquele array. Dentro da função podemos nos referenciar ao array existente na função que o chamou de forma indireta através desse endereço. Vamos agora escrever uma versão nossa de função para calcular o comprimento de uma variável string:

 int strlen(char * s)
 {
    int n;

    for (n=0; *s != '\0'; ++s)
        ++n;

    return n;
 }

O incremento de s é perfeitamente legal, desde que s é uma variável ponteiro. Incrementar s não tem nenhum efeito sobre a string de caracteres existente na função que chamou strlen pois o que é incrementado é uma variável interna a função strlen. Você pode representar os parâmetros na forma

strlen(char *s)

Ou

strlen(char s[])

Sendo que ambas são equivalentes, onde o uso de cada uma deve ser feito de acordo com uso das expressões que faremos na função. Quando um nome de array é mandado para a função, a função pode por conveniência aceitar o dado como um vetor ou como um ponteiro e manipulá-lo de acordo.

É possível também passar parte do array para a função, pela passagem do ponteiro para o começo do subarray. Por exemplo

f(&a[2])

E

f(a+2)

Transferem para a função f o endereço do elemento a[2] porque &a[2] e a+2 são ambos expressões apontadas que se referem ao terceiro elemento do vetor a. Em f, a declaração do argumento pode ser escrita como

void f(int vetor[])
{
   ...
}

Ou

void f(int * vetor)
{
   ...
}

Logo o nosso primeiro elemento no vetor dentro da função é o terceiro elemento no vetor original.

Arrays de caracteres são muitas vezes usados como argumentos de funções. Esses arrays como quaisquer outros são enviados as funções por meio de ponteiros. Se uma variável ponteiro e declarada da seguinte maneira:

char * mensagem;

Então o comando

mensagem = "Copia de ponteiros"

Atribui a mensagem um ponteiro para a seqüência de caracteres. Esta não é uma cópia de strings, já que C não prove operadores para isso. Apenas ponteiros são envolvidos.

Vamos ver agora um programa exemplo que manipula ponteiros:

 #include <stdio.h>
 #include <string.h>

 void strsub(char *str1, char *str2, int pos, int qtd);

 int main(void)
 {
    char veta[] = "Isto aqui e um teste",
          *vetb  = "Isto também e um teste",
          *vetc,
          vetd[50],
          *vete;

    void strsub(char *str1,char *str2, int pos, int qtd);

    /* O ponteiro c recebe o ponteiro para vetor a */
    /* O contrario não pode acontecer */
    vetc = veta;
    veta[15] = 'T';
    printf("vetor a = %s\n", veta);
    printf("vetor c = %s\n", vetc);
    vetc = "Temos aqui uma mensagem";
    vete = "Temos aqui uma outra mensagem";
    strsub(vetb, vetd, 5, 6);
    vetb = &vete[15];
    *vetb = 'O'; 
    *(vetc + 11) = 'U';
    printf("vetor b = %s\n", vetb);
    printf("vetor c = %s\n", vetc);
    printf("vetor d = %s\n", vetd);
    printf("vetor e = %s\n", vete);

    return 0;
 }

/*

STRSUB(char *str1, char *str2, int pos, int qtd)

Função que retorna com uma substring str2 retirada da string str1 começando da posição pos com qtd caracteres.

  • /
 void strsub(char *str1, char *str2, int pos, int qtd)
 {
    int i;

    if (pos > strlen(str1))
      *str2 = '\0';
    else
    {
        for (i=1, str1 += pos; *str1!='\0' && i<=qtd; ++i)
        {
            *str2++ = *str1++;
        }

        if (*str1 != '\0')
            *str2 = '\0';
    }
 }

Este programa produz a seguinte saída:

vetor a = Isto aqui e um Teste vetor c = Isto aqui e um Teste vetor b = Outra mensagem vetor c = Temos aqui Uma mensagem vetor d = também vetor e = Temos aqui uma Outra mensagem

Já que ponteiros são variáveis, podemos utilizar uma estrutura do tipo array para representar uma seqüência de ponteiros. A declaração de uma variável deste tipo e feita da seguinte maneira:

int *vetpont[10];

Que é um vetor de ponteiros com 10 posições (0 a 9) do tipo inteiro.

Abaixo temos um exemplo da utilização de um vetor de ponteiros e várias formas de acessar os seus elementos:

 #include <stdio.h>
 #include <string.h>

 int main(void)
 {
   char i,
        *mata[10],
        *matb[] = { 
                    "Janeiro",
                    "Fevereiro",
                    "Marco",
                    "Abril",
                    "Maio",
                    "Junho",
                    "Julho",
                    "Agosto",
                    "Setembro",
                    "Outubro",
                    "Novembro",
                    "Dezembro"
                  },
        matc[10][5];

   for (i = 0; i < 10; ++i)
   {
     mata[i]="Mensagem da matriz";
   }
   mata[1][1] = 'E';
   mata[5][2] = 'N';
   *(mata[7] + 3) = 'S';
   for (i = 0; i < 5; ++i)
   {
     printf("matriz mata = %s\n",mata[i]);
   }

   for (i = 0; i < 10; ++i)
   {
     mata[i] = matb[i+2];
   }
   for (i = 0; i < 5; ++i)
   {
     printf("matriz mata = %s\n", mata[i]);
   }
   for (i = 0; i < 9; ++i)
   {
     /* elementos copiados pois não podem ser referenciados
        pela matriz */
     strcpy(matc[i], matb[i+2]);
   }
   for (i = 0; i < 5; ++i)
   {
     printf("matriz matc = %s\n", matc[i]);
   }

   return 0;
 }

E a saída será impressa da seguinte forma:

matriz mata = MENSagem da matriz

       matriz mata = MENSagem da matriz
       matriz mata = MENSagem da matriz
       matriz mata = MENSagem da matriz
       matriz mata = MENSagem da matriz
       matriz mata = Marco
       matriz mata = Abril
       matriz mata = Maio
       matriz mata = Junho
       matriz mata = Julho
       matriz matc = MarcoAbrilMaio
       matriz matc = AbrilMaio
       matriz matc = Maio
       matriz matc = JunhoJulhoAgostSetemOutubNovembro
       matriz matc = JulhoAgostSetemOutubNovembro

Repare nas duas formas de atribuição de caracteres existentes no programa utilizadas para a atribuição na matriz mata.

mata[5][2] = 'N';
*(mata[7] + 3) = 'S';

As duas são perfeitamente equivalentes e não existe diferença na sua utilização neste caso. Enquanto a primeira pode ser usada indiferentemente com vetores de ponteiros ou matrizes a segunda só pode ser utilizada com vetores de ponteiros.


E)Leitura dos dados - Ate agora os nossos programas não possuem a capacidade de se comunicar com o usuário de forma a receber os dados digitados por ele. A função que permite realizar esta operação e a função scanf.

De uma forma geral, a função scanf e muito parecida com a função printf. O formato geral da scanf e o seguinte:

int scanf(char *format[, &arg1, &arg2, ...]);

Onde cada elemento acima tem a seguinte função:

A string format - Essa string controla a maneira de como a função ira ler, converter e armazenar os campos digitados. Deve haver argumentos endereçáveis suficientes para uma dada especificação de formato. Caso não haja, os resultados são imprevisíveis e normalmente desastrosos. Excesso de argumentos endereçáveis são meramente desprezados. A string format e uma string de caracteres que contem 3 tipos de objetos:

1)Caracteres espaço - Caracteres espaço são brancos (), tabulação (\t) ou newline (\n). Se a função scanf encontra um caracter deste tipo na string format ele será lido mas não será armazenado, desprezando esses caracteres ate encontrar um caracter não espaço.

2)Caracteres não espaço - Esses caracteres são todos os outros caracteres exceto o sinal de percentagem (%). Se a scanf encontra um caracter não espaço na string format ele será lido mas não será armazenado.

3)Especificações de formato - Essas especificações direcionam para a leitura e conversão de caracteres do campo de entrada em tipos específicos de valores, armazenando-os em locais dados por seus endereços de argumentos.

As especificações do formato da scanf tem a seguinte forma:

 % [*] [largura] [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 e o mais importante. E escolhido em função do tipo de dado de entrada esperado e a forma de como esse dado será armazenado. A tabela abaixo nos fornece estes tipos:


Caracter tipo numérico Entrada esperada Tipo de argumento d Dec. Inteiro Ponteiro para int (int *arg) D Dec. Inteiro Ponteiro para long (long *arg) o Octal Inteiro Ponteiro para int (int *arg) O Octal inteiro Ponteiro para long (long *arg) i Decimal, Octal Ponteiro para int (int *arg) ou hexadecimal ou inteiro I Decimal, Octal Ponteiro para long (long *arg) ou hexadecimal ou inteiro u Inteiro decimal Ponteiro para unsigned int sem sinal (unsigned int *arg) U Inteiro decimal Ponteiro para unsigned long sem sinal (unsigned long *arg) x Inteiro Ponteiro para int (int *arg) hexadecimal X Inteiro Ponteiro para long (long *arg) hexadecimal e Ponto flutuante Ponteiro para float (float *arg) E Ponto flutuante Ponteiro para float (float *arg) f Ponto flutuante Ponteiro para float (float *arg) F Ponto flutuante Ponteiro para float (float *arg)

Caracter tipo caracter Entrada esperada Tipo de argumento s Caracter string Ponteiro para um array de caracteres (char arg[]) c Caracter Ponteiro para caracter (char *arg). Se um campo de larg. L estiver especificado em conjunto com o tipo c (como %5c), deve apontar para um array de L caracteres (char arg[L]) % Caracter % Nenhuma conversão é feita; o caracter % é armazenado

Caracter tipo ponteiro Entrada esperada Tipo de argumento n Nenhuma Ponteiro para int (int *arg) O numero de caracteres lidos sucessivamente, até %n, é armazenado neste ponteiro. p Número em hexa no formato YYYY:ZZZZ ou ZZZZ Ponteiro para o objeto (far * ou near *) A conversão padrão de %p depende do modelo de memória adotado.


Qualquer um dos seguintes caracteres e considerado como uma entrada valida para o campo:

Todos os caracteres ate o próximo caracter espaço (este caracter não será incluído).

Todos os caracteres ate aquele que não possa ser convertido sob a especificação de formato corrente.

Ate n caracteres onde n e a largura especificada do campo.

Alguns procedimentos de conversão deverão ser entendidos para que possamos entrar com os dados desejados:

%c - Esta especificação lê o próximo caracter, incluindo um caracter espaço. Para pular o caracter de espaço e ler o próximo caracter não espaço, use %1s.

%Lc - O endereço do argumento e um ponteiro para um array de caracteres. O array consiste de L caracteres (char arg[L])

%s - O endereço do argumento e um ponteiro para um array de caracteres (char arg[]). O tamanho do array deve ser no mínimo (n+1) bytes, onde n e o comprimento da string s (em caracteres). Um espaço ou newline encerra a entrada do campo. Um caracter nulo e automaticamente colocado no final do vetor.

%[caracteres validos] - O conjunto de caracteres rodeados por colchetes pode ser substituído pelo caracter tipo s. O endereço do argumento e um ponteiro para o vetor de caracteres. A função scanf lê a entrada correspondente ate o primeiro caracter que não esteja incluído em “caracteres validos”. Dois exemplos do uso dessa conversão são

%[abcd] - Ira buscar por qualquer caracter a, b, c ou d no campo de entrada.

%[^abcd] - Ira buscar por qualquer caracter exceto a, b, c ou d no campo de entrada.


%e, %E, %f, %g, %G - Os números em ponto flutuante nos campos de entrada devem possuir o seguinte formato genérico:

[+/-] ddddddddd [.] dddd [E | e] [+/-] ddd

onde os colchetes indicam itens opcionais e ddd representam dígitos decimais, octais ou hexadecimais.

%d, %i, %o, %x, %D, %I, %O, %X, %c, %n - Um ponteiro para unsigned char, unsigned int, ou unsigned long pode ser usado em qualquer conversão onde um ponteiro para char, int ou long seja permitido.

O caracter * - O caracter para suprimir atribuições e um asterisco (*). Ele não deve ser confundido com o símbolo de indicação de ponteiro, que também e um asterisco. Se este caracter segue o caracter % na especificação de formato, o próximo campo de entrada será varrido mas não será atribuído ao endereço do próximo argumento. A entrada suprimida e do tipo especificado pelo caracter de tipo que segue o caracter asterisco.

O campo largura - E especificado por um numero decimal controlando o numero máximo de caracteres que será lido do campo de entrada de dados corrente. Se o campo de entrada contem uma quantidade de caracteres menor que a especificada, a função scanf lê todos os caracteres no campo para então processar o próximo campo e o próximo formato de especificação.

Os qualificadores de entrada - Os modificadores de entrada (N e F) e os modificadores de tipo de argumento afetam como a função scanf interpreta o endereço correspondente ao argumento arg.

Qualificador de entrada Como a entrada foi afetada F Despreza o tamanho padrão ou declarado; arg é interpretado como ponteiro far. N Despreza o tamanho padrão ou declarado; arg é interpretado como ponteiro near. H Para os tipos d, i, o, u, x: converte a entrada para short int, armazenando em um objeto short. Para os tipos D, I, O, U, X: sem efeito. Para os tipos e, f, c, s, n, p: sem efeito. L Para os tipos d, i, o, u, x: converte a entrada para long int, armazenado em um objeto long. Para os tipos e, f: converte a entrada em tipo double, armazenado em objeto double. Para os tipos D, I, O, U, X: sem efeito. Para os tipos c, s, n, p: sem efeito.


Abaixo temos um exemplo da utilização pratica da função scanf:


/* SCANF.C - Programa que exemplifica o uso da  função scanf */
 
#include <stdio.h>
#include <string.h>
 
int main(void)
{
  char i,
       *mata[10],
       matc[10][20];
   
  for (i = 0; i < 10; ++i)
  {
      strcpy(matc[i], “”);
      mata[i] = matc[i];
  }
   
  printf("Elemento 0 (string): \n");
  flushall();  /* O uso desta função será visto
                  posteriormente em arquivos  */
  scanf("%s", mata[0]);
  printf("matriz mata = %s*\n", mata[0]);
   
  printf("Elemento 1 (abcd): \n");
  flushall();
  scanf("%[abcd]", mata[1]);
  printf("matriz mata = %s*\n", mata[1]);
   
  printf("Elemento 2 (5 caracteres): \n");
  flushall();
  scanf("%5c", mata[2]);
  printf("matriz mata = %5s*\n", mata[2]);
   
  printf("Elemento 3 (sem entrada): \n");
  flushall();
  scanf("%*s", mata[3]);
  printf("matriz mata = %s*\n", mata[3]);
   
  printf("Elemento 4 (menos ABCD): \n");
  flushall();
  scanf("%[^ABCD]", mata[4]);
  printf("matriz mata = %s*\n", mata[4]);
   
  printf("\n");
   
  for (i=0; i < 5; ++i)
  {
      printf("matriz mata = %s*\n", mata[i]);
  }
   
  printf("\n");
  flushall();
  scanf("%s", mata[9]);
   
  return 0;
}


F) Argumentos na linha de comando - Em C temos uma maneira de passar parâmetros para a função main juntamente quando estamos chamando este programa para executa-lo. Quando a função main e chamada para começar a execução, ela e chamada com dois argumentos. O primeiro, convencionalmente chamado de argc, e o numero de argumentos na linha de comando existente quando o programa foi chamado e o segundo, chamado de argv, e um array de ponteiros de caracteres que aponta para os argumentos, um por elemento do vetor. A manipulação dessas strings e feita por meio de operações comuns de ponteiros.

Temos aqui um programa que utiliza essa facilidade para imprimir na tela os argumentos do programa.


/* Ecoa o argumento na tela */
#include "stdio.h"
 
int main(int argc, char *argv[])
{
   while (—argc > 0)
   printf((argc > 1) ? "%s " : "%s\n", *++argv);
    
   return 0;
}

G)Ponteiros para funções - Em C, a função ela mesma não e uma variável, mas e possível definir um ponteiro para a função, que pode ser manipulado, passado para funções, colocado em arrays, etc. Vamos fazer um programa que crie um ponteiro para a função strsub criada por nos anteriormente:

/* Programa de teste de ponteiro para função */
 
#include "stdio.h"
int main(void)
{
  char a[] = "Seqüência de caracteres";
  char b[20];
  void strsub(char *str1, char *str2, int pos, int qtd);
    
  void (* teste)(); /* declaração de ponteiro para função */
    
  clrscr();
  teste = &strsub; /* atribuição do endereço da função */
    
  (*teste)(a, b, 10, 2);
  printf("%s\n", b);
  getchar();
    
  return 0;
}

/*

STRSUB(char *str1, char *str2, int pos, int qtd)

Função que retorna com uma substring str2 retirada da string str1 começando da posição pos com qtd caracteres.

  • /
void strsub(char *str1, char *str2, int pos, int qtd)
{
  int i;
    
  if (pos > strlen(str1))
     *str2 = '\0';
  else
  {
    for (i=1, str1 += pos; *str1 != '\0' && i <= qtd; ++i)
    {
       *str2++ = *str1++;
    }
      
    if (*str1 != '\0')
      *str2 = '\0';
  }
}

A declaração do ponteiro para a função

void (*teste)(); 

diz que teste e um ponteiro para a função e que retorna um valor void (não existente). O primeiro conjunto de parênteses e necessário porque sem ele diríamos que teste e uma função que retorna um ponteiro do tipo void. Sem eles a declaração ficaria assim:

void *teste(); 

EXERCÍCIOS:

Monte uma função que realize as seguintes operações com strings digitadas por você:

strdel(char *str, int pos, int qtd) - Deleta qtd caracteres a partir da posição pos na string str. Caso pos tenha um valor maior que o comprimento da string, ela voltara sem alteração. Se pos + qtd superar também o comprimento da string, a string terá seus caracteres após pos deletados.

strins(char *str1, char *str2, int pos) - Insere a string str2 em str1 a partir da posição pos. Caso pos tenha um valor maior que o comprimento da string str1, str2 será concatenado a str1.

int strpos(char *str1, char *str2) - Retorna com a posição de inicio onde começa str2 em str1. Caso não encontre a str2 em str1 retornara com o valor -1.


ca:Llenguatge C de:C (Programmiersprache) en:C programming language eo:C Komputillingvo fi:C-ohjelmointikieli fr:Langage C hu:C Programozási nyelv ja:C言語 nl:Programmeertaal C no:C (programmeringsspråk) pl:C (język programowania) sv:Programspråket C zh-cn:C编程语言 zh-tw:C編程語言

talvez você goste