paulo1205
(usa Ubuntu)
Enviado em 07/11/2021 - 20:14h
ApprenticeX escreveu:
Boa Noite a todos!
Como devo declarar corretamente em C?
OBS:
O Espaço vazio é porque desejo preencher este campo depois via código.
Estou confuso porque muitos exemplos que vi com ponteiros, não é declarado um caracter terminador, porém vejo também exemplos que declaram com o caracter terminador que mostro abaixo.
Não declarei um caracter terminador
{"Texto1", "", 0}, Seria necessário no caso abaixo?
char *Matriz[3][2] = {
{"Texto1", ""}, // {"Texto1", "", 0}, Preciso declarar assim?
{"Texto2", ""},
{"Texto3", ""},
};
Com a declaração acima, você está dizendo que o identificador
Matriz tem o tipo “
array com três elementos do tipo «
array com dois elementos do tipo ‘ponteiro para caracteres’»”. Isso implica que
Matriz[n] (com
n inteiro maior ou igual a
0 e menor que
3 ) será um objeto do tipo “
array com dois elementos do tipo «ponteiro para caracteres»”, e
Matriz[n][m] (com
m inteiro maior ou igual a
0 e menor que
2 ) é do tipo “ponteiro para caracteres” (que é sinônimo de “endereço de região de memória onde há dado do tipo caráter”).
Note que a quantidade total de elementos individuais do tipo
char * (ou ponteiro para caracteres) de
Matriz é seis (
3*2==6 ).
Observando a lista de inicialização que se atribui a
Matriz junto com a declaração (que, por sinal, é o único momento em que é possível fazer atribuições diretas a
arrays em C e C++), vemos que há exatamente seis elementos, e que tais elementos estão dispostos como três blocos de dois elementos. Isso é consistente com a declaração:
Matriz tem três elementos, cada um deles sendo um
array com dois elementos, dispostos como abaixo.
•
Matriz[0] corresponde a
{"Texto1", ""} , implicando que
Matriz[0][0] é associado a
"Texto1" e
Matriz[0][1] a
"" ;
•
Matriz[1] corresponde a
{"Texto2", ""} , implicando que
Matriz[1][0] é associado a
"Texto2" e
Matriz[1][1] a
"" ;
•
Matriz[2] corresponde a
{"Texto3", ""} , implicando que
Matriz[2][0] é associado a
"Texto3" e
Matriz[2][1] a
"" .
Essa disposição, por si só, já seria suficiente para mostrar por que razão não poderia ser usada a lista de inicialização
{"Texto1", "", 0} , como na pergunta que você colocou num comentário no meio do código, pois ela tentaria colocar um terceiro elemento em
Matriz[0] , que é um
array que só tem dois elementos. Se você tentar fazê-lo deve receber um erro de compilação, indicando que a lista de inicialização excedeu o tamanho do
array .
Quando você levanta dúvidas sobre terminador, entendo que se refere ao
byte nulo que indica o fim de uma
string . Esse
byte nulo é automaticamente colocado ao final da
string quando você usa a notação de constantes literais entre aspas
† . Quando você usa, por exemplo,
"Texto1" no seu código, está provocando a alocação em algum lugar da memória para sete caracteres, sendo os seis do texto que você vê e mais um para acomodar o
byte terminador. De modo semelhante, uma suposta “
string vazia”, indicada como
"" , não representa uma região de memória de tamanho zero, mas sim uma região com tamanho um, a fim de acomodar o
byte nulo.
Outro ponto que eu quero destacar é que os elementos individuais (
Matriz[n][m] ) são ponteiros para caracteres, não os caracteres em si. As
strings "Texto1" ,
"Texto2" ,
"Texto3" e
"" (com seus respectivos terminadores) usados na declaração não estão dispostas dentro da memória alocada para os elementos de
Matriz , mas em alguma outra região de memória reservada para constantes, e os endereços dessas regiões de memória para dados constantes são copiados para os elementos individuais
Matriz[n][m] , já que, como vimos acima, os tipos desses elementos é “ponteiro para caracteres”, logo guardam endereços de onde os caracteres eventualmente residem, não os caracteres em si.
Um aspecto infeliz do C, que foi corrigido em C++, é o descasamento entre o caráter constante de uma constante literal entre aspas e a possibilidade de usar um ponteiro para caracteres não caracterizado como constante para referir-se a ela. Sua declaração, embora válida em C, seria inválida em C++, e por boas razões, porque ela pode lhe trazer problemas mesmo C. Considere o seguinte programa.
#include <stdio.h>
char *Matriz[3][2] = {
{"Texto1", ""}, // {"Texto1", "", 0}, Preciso declarar assim?
{"Texto2", ""},
{"Texto3", ""},
};
int main(void){
Matriz[0][0][2]='s'; // Tenta transformar "Texto1" em "Testo1". Posso fazê-lo?
}
Se eu tentar compilar o programa acima como programa em C++, não vou conseguir, porque estarei tentando usar atribuir dados constantes a ponteiros que não são constantes, o que poderia me causar problemas na hora de manipular esses dados (e note que há um erro para cada elemento individual da matriz).
$ g++ -Wall -Werror -O2 -pedantic-errors x.c -o x
x.c:7:1: error: ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings]
};
^
x.c:7:1: error: ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings]
x.c:7:1: error: ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings]
x.c:7:1: error: ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings]
x.c:7:1: error: ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings]
x.c:7:1: error: ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings]
Já uma tentativa de compilar o mesmo programa em C funciona sem nenhum problema, mas veja o que ocorre na hora em que eu executo o código produzido.
gcc -Wall -Werror -O2 -pedantic-errors x.c -o x # Compila sem erro.
$ ./x # Executa o código compilado acima.
Segmentation fault (core dumped)
Tal erro ocorre porque eu tento escrever numa região de memória protegida contra escrita, já que, afinal, o que está nela é uma constante, assim expressa no próprio código fonte do programa, e tal tentativa provoca uma violação de proteção de memória, o que faz com que o sistema operacional mate o programa.
Para corrigir a declaração no programa acima (inclusive em C, do ponto de vista semântico), poderíamos fazer com que cada elemento individual de
Matriz fosse um ponteiro para caracteres constantes.
#include <stdio.h>
const char *Matriz[3][2] = { {"Texto1", ""}, {"Texto2", ""}, {"Texto3", ""} };
int main(void){
// Se descomentada, a linha abaixo seria um erro, pois agora os caracteres apontados por cada elemento são constantes.
/*Matriz[0][0][2]='s';*/
// Isto é possível, pois usamos o ponteiro apenas para ler um caráter, não para modificá-lo.
return Matriz[0][0][2]; // Vai retornar o valor 120 (correspondente a 'x').
}
Note, porém, que isso transforma em constante apenas os dados apontados pelos elementos individuais de
Matriz , não os elementos em si. Se, no exemplo acima, eu tivesse feito algo como “
Matriz[0][0]="ABCDEF"; ” antes da linha contendo o comando
return , o valor retornado teria sido
67 (correspondente a
'C' ). Se quisesse impedir que as
strings referenciadas por cada elemento da matriz não pudessem ser alteradas, eu teria de informar isso na declaração.
Veja se o exemplo abaixo ajuda.
#include <stddef.h>
#include <string.h>
// Declara algumas strings (arrays de caracteres incluindo byte terminador) não-constantes.
char sv1[]="Teste1", sv2[]="Teste2";
// Declara algumas strings (arrays de caracteres incluindo byte terminador) constantes.
const char sc1[]="TesteC1", sc2[]="TesteC2";
// Declara alguns arrays com elementos variáveis que não são strings (falta o byte terminador).
char av1[4]={'T','S','T','1'}, av2[4]={'T','S','T','2'};
// Declara alguns arrays com elementos constantes que não são strings (falta o byte terminador).
const char ac1[4]={'C','N','T','1'}, ac2[4]={'C','N','T','2'};
// Declara matriz de ponteiros (não-constantes) para caracteres (não-constantes).
// Nas listas de inicialização de elementos, não convém (em C++, não podemos) usar constantes literais entre aspas,
// já que os ponteiros não indicam dados constantes, mas podemos usar ponteiros para dados não-constantes, tais como
// as conversões a partir de arrays de caracteres não-constantes (sejam eles strings ou não), e o ponteiro nulo.
char *Matriz1[3][2]={ {sv1, sv2}, {av1, av2}, {NULL, NULL} };
// Declara matriz de ponteiros (não-constantes) para caracteres constantes.
// Nas listas de inicialização, podemos usar constantes literais entre aspas, já que os ponteiros indicam dados
// constantes, além de todos os outros casos de ponteiros para dados constantes ou variáveis, tais como os obtidos
// a partir de arrays, quer constituam strings ou não, e mais o ponteiro nulo.
const char *Matriz2[3][2]={ {"Texto1", sc1}, {sv1, ac1}, {av1, NULL} };
// Declara matriz de ponteiros constantes para caracteres (não-constantes).
// Nas listas de inicialização de elementos, não convém (em C++, não podemos) usar constantes literais entre aspas,
// já que os ponteiros, embora constantes, não indicam dados constantes, mas podemos usar ponteiros para dados
// não-constantes, tais como as conversões a partir de arrays de caracteres não-constantes (sejam eles strings
// ou não), e o ponteiro nulo.
char *const Matriz3[3][2]={ {NULL, NULL}, {sv1, sv2}, {av1, av2} };
// Declara matriz de ponteiros constantes para caracteres constantes.
// Nas listas de inicialização, podemos usar constantes literais entre aspas, já que os ponteiros indicam dados
// constantes, além de todos os outros casos de ponteiros para dados constantes ou variáveis, tais como os obtidos
// a partir de arrays, quer constituam strings ou não, e mais o ponteiro nulo.
const char *const Matriz4[3][2]={ {"Texto1", sv2}, {sc2, av2}, {ac2, NULL} };
int main(void){
size_t l;
char c;
char *pc;
const char *pcc;
// Com Matriz1, posso fazer:
pc=Matriz1[0][0]; // Obter o valor de qualquer elemento da matriz e atribuí-lo a um ponteiro para dados não-constantes (mesmo tipo).
pcc=Matriz1[0][1]; // Obter o valor de qualquer elemento da matriz e atribuí-lo a um ponteiro para dados constantes (tipo compatível).
c=Matriz1[0][1][0]; // Obter um dado apontado por qualquer elemento que seja um ponteiro válido e que não exceda os limites do dado apontado.
Matriz1[1][0]=sv2; // Alterar ponteiro contido em um dos elementos da matriz, desde que o tipo do novo valor seja compatível (não pode ser ponteiro para dados constantes, por exemplo).
Matriz1[1][1][0]=c; // Alterar um dado apontado por qualquer elemento que seja um ponteiro válido e que não exceda os limites do dado apontado.
// Com Matriz1, não posso fazer:
Matriz1[2][0]="Texto5"; // Alterar ponteiro contido em um dos elementos da matriz para se referir ao conteúdo de uma constante literal entre aspas (o C até permite, mas é periogoso, como já se viu; em C++, dá erro).
Matriz1[2][0]=pcc; // Alterar ponteiro contido em um dos elementos da matriz para se referir dados indicados como constantes (aqui, nem mesmo o compilador C deixa passar).
// O compilador não vai alarmar, mas eu também não devo fazer com Matriz1:
l=strlen(Matriz1[1][0]); // Usar dados que não são strings (porque eu sei que não têm o byte terminador) como se fossem strings.
Matriz1[0][1][100]=c; // Exceder os limites do conteúdo apontado por um dos elementos da matriz, quer seja para gravar conteúdo...
c=Matriz1[0][1][100]; // ... quer para ler conteúdo.
Matriz1[2][1][0]=c; // Como eu sei que este elemento da matriz contém um ponteiro inválido, não posso usar tal ponteiro para gravar conteúdo...
c=Matriz1[2][1][0]; // ... nem para ler conteúdo.
// Com Matriz2, posso fazer:
pcc=Matriz2[0][0]; // Obter o valor de qualquer elemento da matriz e atribuí-lo a um ponteiro para dados constantes (mesmo tipo).
c=Matriz2[0][0][0]; // Obter um dado apontado por qualquer elemento que seja um ponteiro válido e que não exceda os limites do dado apontado.
Matriz2[0][1]="Texto2"; // Alterar ponteiro contido em um dos elementos da matriz para se referir ao conteúdo de uma constante literal entre aspas (tipo compatível).
Matriz2[1][0]=sv2; // Alterar ponteiro contido em um dos elementos da matriz (o ponteiro original pode apontar para dados constantes ou dados variáveis).
Matriz2[1][1]=pcc; // Alterar ponteiro contido em um dos elementos da matriz para se referir dados indicados como constantes (aqui, nem mesmo o compilador C deixa passar).
// Com Matriz2, não posso fazer:
pc=Matriz2[0][0]; // Obter o valor de qualquer elemento da matriz e atribuí-lo a um ponteiro para dados não-constantes (tipo incompatível).
Matriz2[1][1][0]=c; // Alterar um dado apontado por qualquer elemento, independentemente do valor do ponteiro.
// O compilador não vai alarmar, mas eu também não devo fazer com Matriz2:
l=strlen(Matriz2[2][0]); // Usar dados que não são strings (porque eu sei que não têm o byte terminador) como se fossem strings.
c=Matriz2[0][1][100]; // Exceder os limites do conteúdo apontado por um dos elementos da matriz, mesmo que somente para ler conteúdo.
c=Matriz2[2][1][0]; // Como eu sei que este elemento da matriz contém um ponteiro inválido, não posso usar tal ponteiro nem mesmo para ler conteúdo.
// Com Matriz3, posso fazer:
pc=Matriz3[1][0]; // Obter o valor de qualquer elemento da matriz e atribuí-lo a um ponteiro para dados não-constantes (mesmo tipo).
pcc=Matriz3[1][1]; // Obter o valor de qualquer elemento da matriz e atribuí-lo a um ponteiro para dados constantes (tipo compatível).
c=Matriz3[1][1][0]; // Obter um dado apontado por qualquer elemento que seja um ponteiro válido e que não exceda os limites do dado apontado.
Matriz3[2][1][0]=c; // Alterar um dado apontado por qualquer elemento que seja um ponteiro válido e que não exceda os limites do dado apontado, pois embora os elementos da matriz sejam constantes, os conteúdos apontados não o são.
// Com Matriz3, não posso fazer:
Matriz3[1][0]=sv2; // Alterar ponteiro contido em um dos elementos da matriz, mesmo que o tipo do novo valor seja compatível, pois os elementos são constantes, ainda que os dados apontados não o sejam.
// O compilador não vai alarmar, mas eu também não devo fazer com Matriz3:
l=strlen(Matriz3[2][1]); // Usar dados que não são strings (porque eu sei que não têm o byte terminador) como se fossem strings.
Matriz3[1][1][100]=c; // Exceder os limites do conteúdo apontado por um dos elementos da matriz, quer seja para gravar conteúdo...
c=Matriz3[1][1][100]; // ... quer para ler conteúdo.
Matriz3[0][0][0]=c; // Como eu sei que este elemento da matriz contém um ponteiro inválido, não posso usar tal ponteiro para gravar conteúdo...
c=Matriz3[0][0][0]; // ... nem para ler conteúdo.
// Com Matriz4, posso fazer:
pcc=Matriz4[0][0]; // Obter o valor de qualquer elemento da matriz e atribuí-lo a um ponteiro para dados constantes (mesmo tipo).
c=Matriz4[0][0][0]; // Obter um dado apontado por qualquer elemento que seja um ponteiro válido e que não exceda os limites do dado apontado.
// Com Matriz4, não posso fazer:
pc=Matriz4[0][0]; // Obter o valor de qualquer elemento da matriz e atribuí-lo a um ponteiro para dados não-constantes (tipo incompatível).
Matriz4[0][1]="Texto2"; // Alterar ponteiro contido em um dos elementos da matriz, mesmo que o tipo do novo valor seja compatível, pois os elementos são constantes, ainda que os dados apontados não o sejam.
Matriz4[1][1][0]=c; // Alterar um dado apontado por qualquer elemento, independentemente do valor do ponteiro.
// O compilador não vai alarmar, mas eu também não devo fazer com Matriz4:
l=strlen(Matriz4[2][0]); // Usar dados que não são strings (porque eu sei que não têm o byte terminador) como se fossem strings.
c=Matriz4[0][1][100]; // Exceder os limites do conteúdo apontado por um dos elementos da matriz, mesmo que somente para ler conteúdo.
c=Matriz4[2][1][0]; // Como eu sei que este elemento da matriz contém um ponteiro inválido, não posso usar tal ponteiro nem mesmo para ler conteúdo.
}
Antes de concluir, nos exemplos acima, eu uso construções semelhantes a
Matriz[0]0][2] , que dão a aparência de um
array tridimensional. Não é o caso: o
array é bidimensional, mas o elemento individual é um ponteiro, e ponteiros admitem a suposição de que as regiões de memória às quais se referem contêm não necessariamente apenas um dado do tipo apontado, mas múltiplos dados adjacentes desse mesmo tipo, e também uso do operador de indexação (
[] ) junto com um valor inteiro
k , a fim de obter acesso ao
k+1 -ésimo elemento de um desses possíveis blocos de vários valores adjacentes.
----------
† O C é um pouco mais tolerante que o C++ ao usar constantes literais de
strings entre aspas. No C++, o
byte é
sempre incluído após o último caráter entre aspas, ao passo que em C existe uma exceção para essa regra, que é no momento da declaração de um
array , conforme mostra o exemplo abaixo.
char str1[6]="Teste"; // Tamanho do array é 6, suficiente para os 5 do texto mais o byte nulo. Válido em C e em C++.
char str2[]="Teste"; // Tamanho do array calculado automaticamente como 6, a partir dois 5 visíveis entre aspas, mais o nulo implícito. Válido em C e em C++.
char arr3[5]="Teste"; // Tamanho do array é 5. Válido em C, que suprime o byte nulo (sem o nulo, esse array não pode ser considerado como string! ), mas ERRO em C++ por não acomodar os 6 caracteres referentes aos 5 do texto visível mais o nulo implícito.
char arr4[4]="Teste". // Tamanho do array é 4. ERRO tanto em C quanto em C++, por não acomodar nem mesmo os caracteres visíveis da constante literal entre aspas.
... Então Jesus afirmou de novo: “(...) eu vim para que tenham vida, e a tenham plenamente.” (João 10:7-10)