Programação com a Linguagem C

 

Cadeias de Caracteres,Variáveis Indexadas e Apontadores

 

1 - Cadeias de Caracteres

 

·       Uma cadeia de caracteres (string) é um conjunto de caracteres contíguos na memória terminado por um caractere ‘\0’ (null – normalmente um caractere com todos os bits iguais a zero).

·       Um  caractere de uma cadeia pode ser referenciado pelo nome definido para o conjunto de caracteres associado a um índice inteiro ou pelo endereço do byte que ocupa na memória.

·       Os índices numa cadeia começam em 0 (zero – índice do primeiro elemento) e vão até n-1 (variando de 1), sendo n o número de elementos da cadeia.

·       O ‘\0’ não pertence à cadeia, é apenas um terminador, porém ocupa uma posição na memória, o que leva a cadeia a ocupar n+1 posições de memória.

 

Exemplo:

 

char cadeia[] = “cadeia de caracteres”;

 

ou

     char cadeia[21]; /* reserva lugar para o ‘\0’ */

        

·       Modelo de representação na memória para a variável cadeia:

 

 

Referência a Caracteres de uma Cadeia Utilizando Índices

 

Exemplo:

 

/*

     Universidade Federal da Paraíba

     Departamento de Sistemas e Computação

 

     Programa para a disciplina <Introdução a Programação> - 2001.1

 

     Objetivo: Mostrar a composição de uma cadeia de caracteres.

*/

#include <stdio.h>

#include <conio.h>

 

void main() {

 

     char cadeia[] = "cadeia de caracteres";

     int indice = 15;

 

     clrscr();

     /* mostra a cadeia inteira */

     printf("\nCadeia = %s", cadeia);

     /* mostra alguns caracteres componentes da cadeia */

     printf("\nPrimeiro caractere de cadeia: %c", cadeia[0]);

     printf("\nCaractere em cadeia[%d]: %c", indice, cadeia[indice]);

     printf("\nÚltimo caractere de cadeia: %c", cadeia[19]);

 

     /* mostra todos os caracteres e o terminador */

     for (indice = 0; indice < 21; indice++) {

         printf(“O %2io. caractere de cadeia é %c = %3i”, indice, cadeia[indice], cadeia[indice]);

     }

     getch();

}

 

Funções que Manipulam Cadeias de Caracteres

 

·       A linguagem C possui um grande número de funções para manipulação de cadeias de caracteres. As funções mais comuns são:

-         strcpy(s1, s2)             Copia s2 em s1.

-         strcat(s1,s2)              Concatena s2 ao final de s1.

-         strlen(s1)                   Retorna o número de caracteres de s1.

-         strcmp(s1, s2)           Comparação lexicográfica - retorna 0 se s1 e s2 são iguais; menor que 0 se s1 < s2; e maior que 0 se s1 > s2.

-         strchr(s1, c)               Retorna um apontador para a (o endereço da) primeira ocorrência de c em s1.

-         strstr(s1, s2)              Retorna um apontador para a (o endereço da) primeira ocorrência de s2 em s1.

 

·       Estas funções requerem a inclusão do arquivo de definições string.h.

 

Outro exemplo:

/*

     Universidade Federal da Paraíba

     Departamento de Sistemas e Computação

 

     Programa para a disciplina <Introdução á Programação> - 2001.1

 

     Objetivo: Contar palavras de uma linha de texto

*/

#include <stdio.h>

#include <string.h>

 

#define MAXLINHA 85

#define BRANCO ' '

#define NULO '\0'

 

void main() {

     char linha[MAXLINHA];

     int indice, contador;

 

     clrscr();

 

     printf("\nDigite a linha de texto a ser processada: \n");

     gets(linha);

 

     if(strlen(linha) > 0){

         contador = 1;

     } else {

         contador = 0;

     }

 

     for(indice = 0; linha[indice] != NULO; indice++) {

         if(linha[indice] == BRANCO) {

              contador++;

         }

     }

 

     printf("\nNumero de palavras: %d", contador);

 

     getch();

}

 

Exercícios para discutir em sala:

-         encontrar a maior linha de um texto (código)

-         contar as palavras de um texto (código)

 

2 - Variáveis Indexadas

 

·       Uma variável indexada é um conjunto de elementos de mesmo tipo os quais são referenciados por um nome comum. Um elemento específico é acessado através de um índice.

·       As variáveis indexadas também são chamadas de vetores, arrays, arranjos ou agregados homogêneos.

·       Em C, todos os vetores consistem em posições contíguas na memória, onde o endereço mais baixo corresponde ao primeiro elemento e o mais alto, ao último elemento.

·       Um vetor pode ter uma ou mais dimensões, sendo unidimensionais ou multidimensionais.

 

Vetores Unidimensionais

 

·       Um vetor unidimensional é um conjunto de elementos de mesmo tipo onde cada elemento possui um único índice para referenciá-lo.

·       Os índices dos elementos são inteiros e seqüenciais. O primeiro tem o valor 0 (zero) e o último tem o valor n-1, onde n é o número total de elementos do vetor.

·       Uma cadeia de caracteres é um vetor unidimensional cujos elementos são do tipo char.

·       A forma geral para se declarar um vetor unidimensional é a seguinte:

 

tipo nome_variável [tamanho]

 

·       Exemplos de declaração:

 

. . .

     #define NUM_ELEM 100

     . . .

     char mensagem[] = “Inicializacao valida”;

     int inteiros[NUM_ELEM];

     float valores[5] = {0.5, 1.0, 6.0, 5.5, 10.0};

     char nome[31];

     int ultimo_dia[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

     . . .

 

·       Referencia-se os elementos de um vetor conjugando-se seu nome com o índice de um elemento:

 

. . .

int mes = 5;

int índice = 3;

printf("Ultimo dia do mes = %d\n", ultimo_dia[mes]);

scanf("%f", &valores[4]);

inteiros[0] += inteiros[indice];

 

·       O tamanho total em bytes para um vetor unidimensional é dado como mostrado abaixo:

Total em bytes = sizeof(tipo) * tamanho do vetor

 

·       Exemplo: (programa)

 

     . . .    

     /* Preenchimento de um vetor unidimensional */

 

     for (indice = 0; indice < NUM_ELEM; indice++) {

         scanf("%d", &inteiros[indice]);

     }

 

     /* Apresentacao dos elementos do arranjo */

 

     for (indice = 0; indice < NUM_ELEM; indice++) {

         printf("%d\n", inteiros[indice]);

     }

     . . .

 

Vetores Multidimensionais

 

·       A linguagem C provê vetores multidimensionais.

·       A forma geral para declarar um vetor multidimensional é a seguinte:

 

tipo nome_variável[tamanho1]..[tamanhoN];

 

·       A forma mais simples de vetor multidimensional é um vetor bidimensional – um vetor de vetores unidimensionais.

·       Como declarar um vetor bidimensional vetorInt de tamanho 10,20?

 

int vetorInt[10][20];

 

·       Para acessar o elemento 1,2 do vetor declarado acima teríamos:

 

int valor;

valor = vetorInt[1][2];

 

·       Outro exemplo de declaração e acesso a elementos de vetores multidimensionais:

 

#define NUM_LIN 4  /* numero de linhas do vetor */

#define NUM_COL 5  /* numero de colunas do vetor */

. . .

     int matriz[NUM_LIN][NUM_COL];

     float valores[100][150];

 

·       Vetores bidimensionais são armazenados em uma matriz linha-coluna, onde o primeiro índice indica a linha e o segundo, a coluna (representação gráfica).

·       O tamanho total em bytes para um vetor bidimensional é calculado da seguinte forma:

 

Total em bytes = tamanho 1o índice * tamanho 2o índice * sizeof(tipo)

 

·       Exemplo: espaço de memória para guardar o vetor vetorInt declarado anteriormente é: 10 * 20 * 2 = 400 bytes.

·       Exemplo: ler os elementos de uma matriz e mostrá-los na saída

 

     /* Leitura dos dados para matrizes*/

 

     /*Em ordem de linhas*/

 

     /* variacao mais externa de linhas */

     for (lin = 0; lin < NUM_LIN; lin++) {

         for (col = 0; col < NUM_COL; col++) {

              scanf("%d", &matriz[lin][col]);

         }

     }

 

     . . .

 

     /* Em ordem de colunas*/

 

     /* variacao mais externa de colunas */

     for (col = 0; col < NUM_COL; col++) {

         for (lin = 0; lin < NUM_LIN; lin++) {

              scanf("%d", &matriz[lin][col]);

         }

     }

 

 

     /* Apresentacao de matriz no formato padrão */

 

     for (lin = 0; lin < NUM_LIN; lin++) {

         for (col = 0; col < NUM_COL; col++) {

              printf("%5d", matriz[lin][col]);  /* mostra na mesma linha */

         }

         printf("\n"); /* muda de linha apos mostrar uma linha inteira */

     }

 

·       Vetores de strings (vetores de caracteres)

 

char mensagens[2][10];

 

·       Inicialização de vetores bidimensionais:

 

int matriz[5][3] = {

1,2,4,

2,3,1,

3,4,2,

5,10,2,

6,3,2

};

 

char nomes[][10] = {“maria”, “pedro”, “ana”};

 

·       Exercícios para discutir em sala:

-         Mostrar inteiros na ordem inversa a da leitura (código)

-         Mostrar valores acima/abaixo da média de um conjunto de inteiros (código)

-         Soma de 2 matrizes 4x5 (código)

-         Encontrar a matriz transposta de uma matriz com dimensão máxima 10x10 (código)

 

·       Outros exercícios:

-        Inverter a ordem dos caracteres de uma linha de texto

-         Encontrar a matriz produto de uma matriz 3x4 por um escalar

 

3 – Apontadores

 

·       Um apontador é uma variável que armazena um endereço de memória (endereço de um byte). Esse endereço é normalmente a posição de uma outra variável na memória. (...em termos gráficos.)

·       As principais razões para o uso de apontadores: modificar os argumentos de uma função, suportar alocação dinâmica de memória, aumentar eficiência do código.

·       Um apontador permite acessar diretamente a memória. Isto torna seu uso muito poderoso, porém, muito perigoso.

·       É fácil usar apontadores incorretamente, ocasionando erros que são muito difíceis de encontrar.

 

Definição de Variáveis Apontadores

 

·       Se uma variável irá conter um endereço de memória, então, esta variável deverá ser definida como um apontador.

·       A forma geral de definição de uma variável apontador é:

 

tipo *nome;

 

         onde:

-         tipo é qualquer tipo válido em C (tipo base).

-         * é o prefixo que indica a definição de um apontador.

-         nome é o identificador do apontador.

 

Os operadores de Apontadores

 

·       É possível acessar um objeto através de seu apontador, indiretamente.

         Exemplo:

 

     int x = 1, y = 2;

     int *apint;   /* apint é um apontador para int */

 

     apint = &x;   /* apint recebe o endereço da variável inteira x –

                      agora apint aponta para a variável inteira x */

 

     y = *apint;   /* y agora contém o mesmo conteúdo da variável

                      inteira apontada por apint - y tem valor 1 */

 

     *apint = 0;   /* o valor de x agora é 0 */

 

·       Existem dois operadores especiais para apontadores:

-         & é um operador unário que fornece o endereço de uma variável (operando). No exemplo abaixo, coloca-se em apint o endereço de memória que contém a variável x:

 

     apint = &x;

 

O operador & pode ser imaginado como retornando “o endereço de”.

 

-         * é um operador unário que fornece o conteúdo da variável apontada. No exemplo abaixo, coloca-se o valor de x na variável y:

 

     y = *apint;

 

O operador * pode ser imaginado como “o conteúdo no endereço apontado”.

 

-         Os operadores & e * têm maior precedência que os operadores aritméticos:

 

·       Uma variável apontador sempre deve apontar para o tipo de dado correto, evitando efeitos indesejados. Observe o exemplo abaixo:

 

     void main() {

         float x, y;

     int *ap;

 

     ap = &x;

     y = *ap;

}

 

·       Isto não irá atribuir o valor de x a y. Por quê?

·       Outra razão para escolher o tipo base correto para o apontadores é que toda a aritmética de apontadores é feita através do tamanho em bytes do tipo base.

 

Expressões com Apontadores

 

·       Em geral, expressões envolvendo apontadores concordam com as mesmas regras de qualquer outra expressão de C.

·       Se apx é um apontador para uma variável inteira x, *apx pode aparecer em qualquer lugar onde apareceria a variável x:

 

. . .

int x, *apx;

. . .

apx = &x;

. . .

     y = *apx + 1; /* y = x + 1 */

     printf("x = %d\n", *pi);

    

     y = *apx + 1; /* soma 1 com o valor do conteúdo

                      do objeto apontado por apx e

                      atribui o resultado a y */

     y = *(apx + 1); /* incrementa apx do valor do

                        tamanho do objeto para o qual apx

                        aponta e atribui a y o valor do

                        conteúdo apontado pelo resultado */

     *apx = 0; /* zera o conteúdo da variável

                  apontada por apx */

     *apx += 1; /* soma 1 ao valor do conteúdo

                   do objeto apontado por apx e

                   atribui o resultado ao objeto

                   apontado por apx */

     ++*apx;    /* idem */

     (*apx)++;  /* idem */

 

·       Se apx e apy são apontadores para int, então pode ser escrito:

 

     apx = apy;

     printf(“valor de apx: %p”, apx);

 

 

Aritmética de Apontadores

 

·       Existem duas operações aritméticas que podem ser usadas com apontadores: adição e subtração.

·       Seja apint é um apontador para um inteiro com valor atual 2000 (int -> 2 bytes), então, o efeito do comando:

 

     apint++;

 

será incrementar apint, fazendo o mesmo conter o valor 2002 (apint aponta para o próximo elemento inteiro).

·       Já o comando:

 

     apint--;

 

terá o efeito de modificar o valor de apint para 1998.

·       Além de incremento e decremento, é possível somar ou subtrair inteiros de apontadores. O comando:

 

     ap = ap + 12;

 

Faz ap apontar para o décimo segundo elemento do tipo apontado por ap adiante do elemento que ele está atualmente apontando.

·       Nenhuma outra operação além das descritas é permitida!!!

 

 

Apontadores e Vetores

 

·       Há uma estreita relação entre apontadores e vetores. Considere o código abaixo:

 

. . .

char str[80] ;

char *aps;

 

aps = str;

. . .

 

·       O apontador aps  irá conter o endereço do primeiro elemento do vetor (cadeia de caracteres ou string) str.

·       Para acessar o quinto elemento em str, teríamos:

 

     char ch;

     ch = str[4];

    

     /* outra forma */

     ch = *(aps + 4);

 

·       São conceitualmente diferentes as definições:

 

     char mensagem[] = "chegou a hora";

     char *mensagem = "chegou a hora";

 

-         A primeira define um vetor;

-         A segunda define um apontador para uma constante.

 

·       Exemplos:

-         Segunda versão do programa que mostra inteiros na ordem inversa a de leitura, utilizando apontador e contador (código).

-         Terceira versão do programa que mostra inteiros na ordem inversa a de leitura, utilizando só apontadores (código).

 

Rotinas para Alocação Dinâmica em C

 

·       Os apontadores fornecem o suporte necessário para implementar alocação dinâmica em C.

·       Alocação dinâmica é o meio através do qual um programa pode obter memória em tempo de execução, ou seja, criar variáveis sem prévia declaração, durante a execução do programa.

·       A importância da alocação dinâmica é não restringir o programa a utilizar apenas a memória já alocada nas declarações, fazendo assim, alocação de memória sob demanda.

·       O coração do sistema de alocação dinâmica de C consiste nas seguintes rotinas (funções):

-         malloc()  - para alocar memória

 

     void *malloc(size_t num_de_bytes)

 

-         free()  - para liberar memória

 

     void free(void *p)

 

·       O tipo size_t é definido em stdlib.h como um inteiro sem sinal.

·       Exemplos de uso das funções acima:

-         Quarta versão do programa que mostra inteiros na ordem inversa a de leitura, utilizando alocação dinâmica e apontadores (código).

 

Problemas com apontadores

 

·       Apontador não inicializado

 

void main() {

     int x;

     int apint*;

 

     x = 10;

     *apint = x;

}

 

·       Incorreta utilização de apontadores

 

/* mostra o valor de x através do apontador pi */

void main() {

     int x;

     int apint*;

 

     x = 10;

     apint = x; /* errado - deveria ser &x */

     printf(“conteudo de apint: %d”, *apint);

     }

 

·       Incorreta suposição sobre a localização das variáveis na memória

void main() {

     char str1[50], str2[50;

     int *aps1, *aps2;

 

     aps1 = str1;

     aps2 = str2;

     if (aps1 < aps2)... /* conceito inválido !! */

}

 

·       Reinicialização de apontadores

      

void main() {

     char *aps;

     char str[50];

     aps = str;

     do {

         get(str); /* lê uma cadeia de caracteres */

         while(*aps) {

                   printf(“%d”, *aps++); /* imprime o equivalente decimal */

                                           /* de cada caractere */

         }

     } while (strcmp(str, “fim”));

     }

 

Exercícios

·       Escreva um programa, usando apontadores, que remova todas as ocorrências de um caractere  ‘c’ qualquer da cadeia ‘s’.

·       Escreva uma nova versão do programa acima que remova cada caractere da cadeia ‘s1’ que se case a algum caractere presente na cadeia ‘s2’.