A maioria das linguagens lhe dá um tipo string que conhece o próprio comprimento. O C não. Em C uma string é apenas um array de char com uma regra anexa: o texto é seguido por um byte de valor zero, escrito '\0' e chamado de terminador nulo. Todo o resto sobre strings em C decorre dessa única convenção.
Uma string é um array de char mais '\0'
Declare uma string inicializando um array de char a partir de um literal:
O array tem quatro bytes, não três:
name: +-----+-----+-----+------+
| 'A' | 'd' | 'a' | '\0' |
+-----+-----+-----+------+
indice: 0 1 2 3
sizeof(name) é 4. O compilador contou os caracteres, somou um para o terminador e dimensionou o array para você. Esse byte extra é a razão de char name[3] = "Ada"; ser um bug esperando para acontecer - o texto cabe, mas o terminador não, então nada adiante consegue saber onde a string termina.
Quando você quer espaço para mudar o conteúdo depois, dê o tamanho você mesmo e deixe folga:
char name[32] = "Ada"; /* 3 caracteres, um terminador, 28 bytes de sobra */
Os bytes não usados são preenchidos com zero pelo inicializador, o que é inofensivo.
Por que o terminador importa
Nenhuma função do C recebe um comprimento. printf("%s", s) recebe um endereço e caminha adiante imprimindo bytes até encontrar '\0'. Então um array de char sem terminador não é uma string, e tratá-lo como uma lê qualquer memória que por acaso venha em seguida.
A regra para memorizar: o sizeof dá o armazenamento, o strlen dá o texto. Para char buf[32] = "Ada", sizeof buf é 32 e strlen(buf) é 3. Eles respondem a perguntas diferentes, e confundi-los é um dos bugs mais comuns do C.
Literais de string vs arrays de char
Estas duas linhas parecem semelhantes e se comportam de forma bem diferente:
char a[] = "hello"; /* um array seu, inicializado a partir do literal */
char *p = "hello"; /* um ponteiro apontado para o proprio literal */
a é um array de 6 bytes com uma cópia privada do texto. Você pode modificá-lo. p aponta para os dados de string somente leitura do programa; o literal é compartilhado, e escrever por p é comportamento indefinido que tipicamente trava em tempo de execução, e não em tempo de compilação.
Escreva const char * sempre que um ponteiro apontar para um literal. O compilador então rejeita a escrita em tempo de compilação em vez de deixá-la virar um travamento. Escolher entre os dois é, na verdade, uma questão sobre ponteiros e arrays, tratada em ponteiros e arrays.
Mais uma diferença que vale conhecer: sizeof a é 6, mas sizeof p é o tamanho de um ponteiro (8 na maioria dos sistemas), não importa o comprimento do texto.
Imprimindo e lendo strings
%s imprime uma string; %c imprime um único caractere.
Para entrada, evite o gets por completo - ele foi removido da linguagem porque não pode ser usado com segurança. Use fgets, que recebe o tamanho do buffer e não vai estourar:
O fgets mantém a quebra de linha que você digitou, que quase nunca é o que você quer na string. A linha do strcspn acha o primeiro '\n' e o sobrescreve com um terminador - uma forma compacta e padrão de aparar isso.
Percorrendo uma string caractere a caractere
Como o fim é marcado em vez de contado, o laço idiomático testa o próprio caractere:
A condição text[i] != '\0' é muitas vezes abreviada para apenas text[i], já que '\0' é zero e portanto falso. As duas estão corretas; a forma explícita é mais fácil de ler enquanto você está aprendendo.
Você pode percorrer com um ponteiro em vez de um índice, o que é igualmente idiomático em C:
A conversão para unsigned char antes de chamar toupper não é enfeite: as funções de <ctype.h> são indefinidas para valores negativos, e um char puro pode ser negativo para bytes acima de 127.
Você não pode copiar uma string com =
Este é o muro em que todo novato bate:
char a[10];
a = "hello"; /* erro: atribuicao a expressao de tipo array */
char b[10] = "hi";
char c[10];
c = b; /* erro - mesma razao */
O nome de um array não é um valor modificável, então o = não tem o que fazer. O único lugar em que o atalho funciona é a inicialização na declaração, que é uma operação diferente, feita pelo compilador.
Para copiar em tempo de execução, copie os bytes com strcpy, de <string.h>:
O mesmo raciocínio explica por que o == não compara strings. if (a == b) compara dois endereços, então é falso para dois arrays diferentes guardando texto idêntico. Use strcmp(a, b) == 0 no lugar. As duas funções e suas parentes mais seguras estão em funções de string.
Arrays de strings
Uma lista de strings é ou um array de ponteiros (para texto fixo que você só lê) ou um array bidimensional de char (quando cada entrada precisa ser modificável):
O array de ponteiros guarda três endereços de literais somente leitura - compacto, mas não modificável. A forma char[3][16] reserva 48 bytes da sua própria memória, então cada linha pode ser reescrita.
Erros comuns
- Esquecer o byte do terminador.
char s[5] = "hello"não tem espaço para o'\0'. Dimensione parastrlen + 1. - Usar
sizeofonde você queriastrlen. Osizeofnum ponteiro dá o tamanho do ponteiro, não o comprimento do texto. - Escrever através de um
char *para um literal. Declare esses ponteiros comoconst char *. - Comparar com
==. Isso compara endereços. Usestrcmp. - Ler com
scanf("%s", buf). Não há limite de tamanho e o estouro acontece alegremente; prefirafgets.
Perguntas frequentes
Como se declara uma string em C?
Como um array de char: char nome[] = "Ada";. O C não tem tipo string embutido - esse array guarda 'A', 'd', 'a' e um quarto byte oculto, '\0', que marca o fim. Se você quiser espaço para mudar o texto depois, dê ao array um tamanho explícito: char nome[32] = "Ada";.
O que é o terminador nulo em C?
É o byte '\0' (valor zero) que marca onde uma string termina. Toda função padrão - printf("%s"), strlen, strcpy - lê adiante até encontrar esse byte. Um array de char sem ele não é uma string, e passá-lo a essas funções lê além do fim da sua memória.
Por que não posso atribuir uma string com = em C?
Porque um array não é um valor que se possa copiar com =. char a[10]; a = "oi"; não compila. Copie os caracteres com strcpy(a, "oi"), de <string.h>, ou inicialize na declaração - char a[10] = "oi"; - que é o único lugar onde o atalho funciona.
Qual a diferença entre char * e char [] em C?
char s[] = "oi" cria um array modificável com uma cópia privada do texto. char *p = "oi" cria um ponteiro para um literal de string, que mora em memória somente leitura - lê-lo tudo bem, escrever por ele trava. Use o array quando pretende modificar, e o ponteiro quando só vai ler.