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:
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();
}
· 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.
· 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]);
}
. . .
· 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.
· 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;
- tipo é qualquer tipo válido em C (tipo base).
- * é o prefixo que indica a definição de um apontador.
- nome é o identificador do apontador.
· É 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.
· 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);
· 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!!!
· 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).
· 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’.