Programação II Strings (Cadeias de Caracteres) Bruno Feijó Dept. de Informática, PUC-Rio
Caracteres tipo char: tamanho de char = 1 byte = 8 bits = 256 valores distintos tabela de códigos: define correspondência entre caracteres e códigos numéricos exemplo: ASCII alguns alfabetos precisam de maior representatividade... alfabeto chinês tem mais de 256 caracteres!
Caracteres Código ASCII Códigos ASCII de alguns caracteres: space 0 1 2 3 4 5 6 7 8 9 30 rs us sp! " # $ % & ' 40 ( ) * +, -. / 0 1 50 2 3 4 5 6 7 8 9 : ; 60 < = >? @ A B C D E 70 F G H I J K L M N O 80 P Q R S T U V W X Y 90 Z [ \ ] ^ _ ` a b c 100 d e f g h i j k l m 110 n o p q r S t u v w 120 x y z ~ del Exemplo: o código de 0 é 48... e o número 0 representa o quê? 82 105 111 32 100 101 32 74 97 110 101 105 114 111 R i o d e J a n e i r o Em 1963, havia apenas letras maiúsculas e números. Em 1967, adicionaram letras minúsculas e caracteres de controle (indo de 0 a 127). Em 1981, a IBM (para o primeiro IBM-PC) adicionou mais 128 caracteres (Extended Characters), principalmente para permitir textos em outros idiomas, novos sinais gráficos e símbolos. Por exemplo: Ç (128), é (130), ½ (171), (168),.... Para exibir usando o teclado: Alt+número. 0 a 31 são caracteres de controle (não imprimem), e.g. null (0), Bell (9), tab horizontal (9),....
Caracteres códigos de controle Códigos ASCII de alguns caracteres de controle No. Código Nome: descrição White Spaces em C 0 nul null: nulo \0 7 bel bell: campainha \a 8 bs backspace: volta e apaga um caractere 9 ht tab: tabulação horizontal \t 10 nl newline ou line feed: muda de linha \n 13 cr carriage return: volta ao início da linha 127 del delete: apaga um caractere \b escape sequences são também chamados de white spaces porque eles não são exibidos na tela Em C, o código 10, na realidade, muda de linha e vai para o início. O código 13 apenas volta para o início. O seguinte trecho de programa: char n = 10; char c = 13; printf("abcdef%cghij%c,,\n",n,c); imprime: abcdef,,ij
Constante de Caractere Constante de caractere: É um caractere envolvido com aspas simples: a, 0,... exemplo: 'a' representa uma constante de caractere 'a' resulta no valor numérico associado ao caractere a char c = 'a'; printf("%d %c\n", c, c); printf imprime o conteúdo da variável c usando dois formatos: com o formato para inteiro, %d, imprime 97 com o formato de caractere, %c, imprime a (código 97 em ASCII)
Exercício Conversão de Caractere para Maiúscula int toupper(int c) converte c em letra maiúscula. Requer <ctype.h>. Implemente a sua versão int paramaiuscula(int c): int paramaiuscula(int c) if (c >= 'a' && c <= 'z') // verifica se e minuscula c = (c - 'a') + 'A'; // converte return c;
Vetor (array) de caracteres Exemplo de um vetor de 3 caracteres char a[3]; a[0] = 'p'; a[1] = 'u'; a[2] = 'c'; Os elementos do vetor estão em endereços contíguos de memória a p u c a Podemos definir um ponteiro para um vetor char * p; p = &a[0]; 101 102 103 ou char * p; p = a; char a[3] = 'p','u','c'; Note que *p, *(p+1) e *(p+2) são os valores p, u e c ou endereços a é o mesmo que &a[0], i.e. o endereço do 1o. elemento Qualquer expressão [] pode ser escrita com a sintaxe +. Portanto, são equivalentes: *(p+2) p[2] *(a+2) a[2] p++; está correto, mas a++; e a = p; não!! (pois nome de vetor é um ponteiro constante e não pode ser alterado) (equivale a: char * const a;) Defina void f(char * a) ao invés de void f(char a[]) p p u c 101 102 103
String (Cadeia de Caracteres) Um string é um vetor de caracteres com um terminador o terminador é o caractere '\0' cujo valor numérico é 0 maneiras de definir um string: como array: char a[4] = 'p','u','c','\0'; ou char a[] = "puc"; usando ponteiro: Na memória: a p u como array: c \0 201 202 203 204 char * a = "puc"; usando ponteiro: OBS: se você usar printf("abcdef\0gh\n"); a impressão ignora g, h e \n: abcdef Lembre que a sintaxe [] faz a aritmética de endereço para você, i.e. multiplica o índice pelo tamanho do elemento, soma o offset em bytes e dereferencia o ponteiro resultante para pegar o valor desejado: a[2] *(a+2). Escrever funções que manipulam strings é basicamente passear com o ponteiro e interromper quando o terminador é encontrado. Veja o exemplo a seguir. a p u c \0 201 202 203 204
Strings Exemplo de função strlen Escreva a função strlen que retorna o comprimento de um string: int strlen(char * s) // calcula comprimento de um string int n; O teste *s!= '\0' é uma expressão for (n=0; *s!= '\0'; s++) booleana (que retorna 0 se falso e diferente n++; de zero se verdadeiro). Portanto, lembrando return n; que o único caractere que tem valor zero é o terminador \0, basta verificar diretamente o valor de *s, isto é: for (n=0; *s; s++) Use while e reescreva strlen da forma mais concisa possível: int strlen(char * s) int n = 0; while (*s++) n++; return n; Se você acha isto estranho, o que você acha que acontece com: if (0) printf( ola ); Resposta: ola nunca será impresso. Troque 0 por 5, -5 ou 2-7 para imprimir o ola. // testa *s e depois incrementa s
Strings Exemplo de função stringcopy Função stringcopy(s,t) que copia t para s. A solução abaixo requer o cuidado de finalizar o s. void stringcopy(char * s, char *t) for(;*t;t++,s++) *s = *t; *s = '\0'; // finaliza s
Strings função stringcopy mais concisa void stringcopy(char * s, char * t) // copia t para s versao 1 while ((*s = *t)!= '\0') s++; t++; ou void stringcopy(char * s, char * t) // copia t para s versao 2 Primeiro copia *t para *s, depois verifica se *s while ((*s++ = *t++)!= '\0') é diferente de \0 e, por fim, incrementa t e s. ; Note que após o while, s está apontando para após o \0, o que não importa para quem ou chamou a função. void stringcopy(char * s, char * t) // copia t para s versao 3 while (*s++ = *t++) ; Note que nestas soluções, o terminador é sempre copiado.
Strings outras soluções para stringcopy void stringcopy(char * s, char * t) while (*t) *s++ = *t++; *s = '\0'; ou void stringcopy(char * s, char * t) do *s++ = *t; while(*t++); As formas deste slide e do slide anterior compilarão para basicamente o mesmo código com a mesma eficiência. Trata-se, muito mais, de uma questão de estilo e de uma maneira própria de falar C de um bom programador. O programador habituado com este estilo e com o uso de ponteiros vai naturalmente escrever programas de melhor qualidade e, portanto, mais eficientes.
Exemplo Converte String para Maiúsculas Destruindo Usando toupper, que converte um único caractere e está em <ctype.h>, escreva uma função strupper1 que converte um string para maiúsculas, destruindo a informação original que estava no string dado. #include <ctype.h> void strupper1(char * s) // destroi s for (;*s;s++) *s= toupper(*s);
Constante de String Constante de cadeia de caracteres: representada por seqüência de caracteres delimitada por aspas duplas comporta-se como uma expressão constante, cuja avaliação resulta no ponteiro para onde a cadeia de caracteres está armazenada
Constante de String... define um string literal (constante de string): char s1[] = "Rio de Janeiro"; s1 é um array (vetor) de char, inicializado com a cadeia Rio de Janeiro, seguida do caractere nulo s1 ocupa 15 bytes de memória é válido escrever s1[0]='x, alterando o conteúdo da cadeia para Xio de Janeiro, pois s1 é um vetor, permitindo alterar o valor de seus elementos char* s2 = "Rio de Janeiro"; s2 é um ponteiro para char, inicializado com o endereço da área de memória onde a constante Rio de Janeiro está armazenada s2 ocupa 4 bytes (espaço de um ponteiro) não é válido escrever s2[0]='x, pois não é possível alterar um valor constante. String literals são tipicamente armazenados em memória readonly. Assim, se um ponteiro é inicializado com o endereço de um string literal, qualquer tentativa de modificação dá erro de access violation. No primeiro caso, o string literal inicializa o array criando uma cópia do string literal no espaço alocado estaticamente para o array s1.
Strings e Manipulação de Memória int main(void) char a[] = "puc"; char * b = "puc"; char c[] = 'p','u','c','\0'; char * d; char m[4]; char * n = (char *)malloc(4*sizeof(char)); d = a; printf("a=%s b=%s c=%s d=%s\n", a,b,c,d); printf(" b[1]=%c *(b+1)=%c\n",b[1],*(b+1)); a[1] = 'a'; // o que daria: b[1] = 'a'; e *(b+1) = 'a';??? Tente entender os valores das variáveis e dos elementos de vetor neste programa a=puc b=puc c=puc d=puc b[1]=u strcpy(m,b); m[1] = 'a'; strcpy(n,b); n[1] = 'a'; c[1] = 'a'; d[1] = 'a'; a=pac m=pac n=pac c=pac printf("a=%s m=%s n=%s c=%s d=%s\n", a,m,n,c,d); return 0; *(b+1)=u // b[1] = 'a'; // ERRO! // *(b+1) = 'a'; // ERRO! d=pac Obs: a não é uma variável e não poderíamos fazer a++. Mas podemos fazer d++. Note que b é variável.
Biblioteca de Funções de Strings As funções de string requerem #include <string.h> Algumas das funções mais usadas estão listadas abaixo (onde s e t são char *, c é um int convertido para char, e n é int. char * strcpy(s,t) char * strncpy(s,t,n) char * strcat(s,t) char * strncat(s,t,n) char * strcmp(s,t) char * strncmp(s,t,n) char * strchr(s,c) char * strrchr(s,c) char * strstr(s,t) int strlen(s) copia t para s e retorna s copia n caracteres de t para s e retorna s concatena t para o final de s e retorna s concatena n caracteres de t para s e retorna s compara s a t e retorna <0 se s<t, 0 se s==t, e >0 se s>t compara n caracteres de s com t retorna ponteiro para a 1a. ocorrência de c em s (retorna NULL se não encontrar c) retorna ponteiro para a última ocorrência de c em s retorna pointer para 1a. ocorrência do string t em s retorna comprimento de s O tipo int usado acima (e.g. n e strlen) é, na realidade, o tipo size_t. Dependendo da implementação, size_t é unsigned int ou unsigned long. Para uma programação absolutamente segura, ou você faz um cast para unsigned long ou usa o tipo size_t provido pelo <stddef.h>.
Leitura de Caracteres
Leitura de Caracteres e Strings com scanf Especificadores de formato definem o comportamento do scanf scanf com o especificador de formato %c lê o valor de um único caractere fornecido via teclado para pular todos os caracteres brancos antes do caractere: um espaço em branco no formato, antes do especificador Cuidados com scanf char a,b;... scanf("%c",&a); carriage return (\n), assim como qualquer caractere branco (\n, \t,...) é caracter válido e será usado na próxima leitura. Uma solução é usar o fflush para limpar o stream de input (e.g. stdin): char a, b; scanf("%c",&a); scanf("%c",&b); // b ='\n' scanf(" %c",&a); char a, b; scanf("%c",&a); fflush(stdin); scanf("%c",&b); scanf é uma função pesada e mal comportada para caracteres e strings (porém bem comportada para números), o que exige atenção. Para caracteres, há programadores que recomendam getchar (mais leve e rápida). fflush em input stream tem efeito indefinido no ANSI C, ameaçando compatibilidade. Uma alternativa é evitar scanf e fflush para caracteres; neste caso opte por controlar tudo, e.g.: char a; Alternativa pior: scanf(" %c",&b); a = getchar(); while (getchar()!='\n );
Leitura de Caracteres e Strings com scanf cuidados com scanf e formato %c: não pula os caracteres brancos caractere branco = espaço (' '), tabulação ('\t') ou nova linha ('\n') se o usuário teclar um espaço antes da letra: o código do espaço será capturado a letra será capturada apenas na próxima chamada de scanf para pular todos os caracteres brancos antes do caractere: basta incluir um espaço em branco no formato, antes do especificador char a;... scanf(" %c", &a); //branco no formato pula brancos da entrada...
Leitura de Caracteres e Strings com scanf scanf com o especificador de formato %s lê uma cadeia de caracteres não brancos pula os eventuais caracteres brancos antes da cadeia exemplo: char cidade[81];... scanf("%s", cidade);... &cidade não é usado pois a cadeia é um vetor o código acima funciona apenas para capturar nomes simples se o usuário digitar Rio de Janeiro, apenas Rio será capturada, pois %s lê somente uma seqüência de caracteres não brancos
Leitura de Caracteres e Strings com scanf scanf com o especificador de formato %[...] %[...] lista entre os colchetes todos os caracteres aceitos na leitura %[^...] lista entre os colchetes todos os caracteres não aceitos na leitura exemplos: %[aeiou] lê seqüências de vogais leitura prossegue até encontrar um caractere que não seja uma vogal %[^aeiou] lê seqüências de caracteres que não são vogais leitura prossegue até encontrar um caractere que seja uma vogal
Exemplo: Leitura de Caracteres e Strings com scanf lê uma seqüência de caracteres até que seja encontrado o caractere de mudança de linha ('\n') captura linha fornecida pelo usuário até que ele tecle Enter inclusão do espaço no formato garante que eventuais caracteres brancos que precedam a cadeia de caracteres sejam descartados char cidade[81];... scanf(" %[^\n]", cidade);... também pode ser usado " %[^\n]s OBS: se, na leitura de um segundo string, você não usar o branco antes do %, isto fará com que o scanf capture os caracteres brancos deixados na leitura anterior e coloque um string inesperado na variável scanf(" %[^\n]",cidade1); scanf("%[^\n]",cidade2); use ou scanf(" %[^\n]",cidade1); scanf(" %[^\n]",cidade2); scanf("%[^\n]",cidade1); getchar(); scanf("%[^\n]",cidade2);