Como criar uma lista vazia pra adicionar dados depois?

1. Como criar uma lista vazia pra adicionar dados depois?

Apprentice X
ApprenticeX

(usa Slackware)

Enviado em 06/02/2022 - 13:11h

Boa tarde a todos!

Estou com dificuldade pra declarar ou mesmo criar o que desejo. Meu Objetivo é ter uma simples lista vazia, onde usarei malloc para receber os dados.
Até o momento creio apenas ser possível trabalhar com ponteiros, visto que declarar vetor de char, terei que informar um tamanho inicial, além do que não sei se é possível o malloc alterar o tamanho de um vetor de char.
Minha_Lista; // Desejo algo assim! Uma Lista vazia, sem quantidade de ítens e sem tamanho!

char *Lista; // Creio que isso não funcione certo? Visto que isso somente serviria para um vetor de caracteres!

// Mas também não consigo declarar um vetor vazio!
char *Lista[]; // error: array size missing in ‘Lista’

Todos os exemplos que encontrei na internet, usam uma struct, visto que a struct pode ser declarada sem valores ou tamanho definido.
Será esse o único mode de fazer uma lista para adicionar valores depois? Usando uma struct? Ou dá pra fazer de alguma outra forma?

Então eu gostaria de saber se é possível criar uma lista vazia sem tamanho, para que eu aumente seu tamanho conforme a necessidade da forma acima, ou só é possível criar uma lista assim usando uma struct?


  


2. Re: Como criar uma lista vazia pra adicionar dados depois?

Paulo
paulo1205

(usa Ubuntu)

Enviado em 06/02/2022 - 17:51h

Você quer uma lista mesmo ou quer algo mais parecido como um array? Dos exemplos que você colocou aqui, não consegui entender.

Lista é uma estrutura de dados cujos elementos são percorridos de modo sequencial e na qual é fácil inserir e remover elementos em qualquer posição da lista, seja no início, no final ou entre quaisquer dois outros elementos preexistentes. Cada elemento da lista é chamado de , e contém ponteiros que apontam para os nós vizinhos (tipicamente para o nó seguinte, no caso de uma lista encadeada simples, que só pode ser percorrida do início para o final, ou, no caso de lista duplamente encadeada, para o nó anterior e para o nó seguinte, permitindo percorrer a lista em ambos os sentidos).

Arrays, inclusive, os alocados dinamicamente, costumam ter todos os seus elementos dispostos na mesma região de memória, cada um adjacente aos seus vizinhos, de modo que se pode fazer acesso a qualquer um deles num tempo constante, conhecendo-se apenas a sua posição relativa ao início do array (seu índice). O ingresso de novos elementos pode ser custoso caso tenha de ocorrer entre dois elementos previamente existentes ou caso todas as posições previstas já tenham sido ocupadas, pois ambas as situações costumam implicar a movimentação de alguns ou até de todos os elementos anteriores para outras posições na memória, a fim de criar espaço para acomodar o novo elemento. A remoção de um elemento em outra posição que não o final do array também pode ser um problema que requeira a movimentação de outros elementos.


... Então Jesus afirmou de novo: “(...) eu vim para que tenham vida, e a tenham plenamente.” (João 10:7-10)


3. Re: Como criar uma lista vazia pra adicionar dados depois?

Apprentice X
ApprenticeX

(usa Slackware)

Enviado em 06/02/2022 - 23:28h

paulo1205 escreveu:
Você quer uma lista mesmo ou quer algo mais parecido como um array? Dos exemplos que você colocou aqui, não consegui entender.

Lista é uma estrutura de dados cujos elementos são percorridos de modo sequencial e na qual é fácil inserir e remover elementos em qualquer posição da lista, seja no início, no final ou entre quaisquer dois outros elementos preexistentes. Cada elemento da lista é chamado de , e contém ponteiros que apontam para os nós vizinhos (tipicamente para o nó seguinte, no caso de uma lista encadeada simples, que só pode ser percorrida do início para o final, ou, no caso de lista duplamente encadeada, para o nó anterior e para o nó seguinte, permitindo percorrer a lista em ambos os sentidos).

Arrays, inclusive, os alocados dinamicamente, costumam ter todos os seus elementos dispostos na mesma região de memória, cada um adjacente aos seus vizinhos, de modo que se pode fazer acesso a qualquer um deles num tempo constante, conhecendo-se apenas a sua posição relativa ao início do array (seu índice). O ingresso de novos elementos pode ser custoso caso tenha de ocorrer entre dois elementos previamente existentes ou caso todas as posições previstas já tenham sido ocupadas, pois ambas as situações costumam implicar a movimentação de alguns ou até de todos os elementos anteriores para outras posições na memória, a fim de criar espaço para acomodar o novo elemento. A remoção de um elemento em outra posição que não o final do array também pode ser um problema que requeira a movimentação de outros elementos.

Boa questão que vc levantou!
A princípio da forma como vc explica, eu estava pensando apenas em um array parecido com algo assim: char *Lista[]; de forma que eu possa aumentar seu tamanho 1 registro de cada vez, podendo aumentar apenas na última posição! Pensei no malloc pois só conheço ele pra aumentar o tamanho de alguma coisa! Não pensei em remoção inicialmente. Apenas adição, sendo que ele inicia vazio!

A princípio meu programa teste, vai carregar um vetor tipo: char Texts[10][10] = .....
Contendo 10 palavras, sendo 1 em cada posição do vetor.
Então eu vou varrer esse vetor Texts usando o malloc pra alocar 1 espaço no outro vetor vazio, adicionando o registro e repetir essa operação 10 vezes, aumentando o meu vetor que antes estava vazio para um vetor com 10 linhas prenchidos de dados.

Porque 1 espaço de cada vez? Porque poderia cair em uma situação onde fossem apenas 3 registros, apenas pra ter um controle básico!
Isso não trata-se de um programa útil, apenas uma forma de eu entender, aprender e como usar um vetor, variável, ou qualquer outro tipo dinâmico, no lugar de uma alocação fixa!

Acredito que malloc seja a única forma de alocar dinamicamente, por isso pensei em um vetor vazio (ponteiro) que não consegui declarar!

Quando você explica sobre Lista, achei interessante também, talvez eu não conheça o que é uma lista, fico pensando se uma lista seria uma struct? Eu tenho planos pra criar uma struct dinamicamente igual funciona em C++ com vector de vector, mas ainda estou engatinhando tentando fazer isso ainda com uma simples variável!


4. Re: Como criar uma lista vazia pra adicionar dados depois?

Paulo
paulo1205

(usa Ubuntu)

Enviado em 08/02/2022 - 02:13h

Você quer um array dinâmico de strings (que implicam um array de caracteres para cada uma), então?

Cada string vai ter tamanho fixo, ou também pode ser dinamicamente alocada?

Se forem strings de tamanho fixo, você tem de ter um ponteiro para array de caracteres. Esse ponteiro vai apontar não para um único array, mas para um conjunto de arrays dispostos em blocos de memória adjacentes. Quando você precisar criar espaço para uma nova string, você vai ter de realocar a memória apontada pelo ponteiro, de modo a caber mais essa string (que é mais um array com o tal tamanho fixo de caracteres).
#define NAMES_SIZE_INCREMENT 100
size_t names_size=0, names_used=0; // Medidores da quantidade alocada e da quantidade em uso.
char (*names)[NAME_MAX]=NULL; // Ponteiro para array(s) de tamanho fixo NAME_MAX.

// Tenta inserir um nome ao final do array ‘names’, estendendo o array, se necessário.
bool insert_name(const char *new_name){
if(names_used>=names_size){
void *new_ptr=realloc(names, (names_size+NAMES_SIZE_INCREMENT)*sizeof *names);
if(!new_ptr)
return false; // Falha de alocação. Sai indicando erro, mas o conteúdo anterior de ‘names’ está preservado.
// Caso contrário, prossegue abaixo.
names=new_ptr;
names_size+=NAMES_SIZE_INCREMENT;
}
strncpy(names[names_used], new_name, sizeof *names-1);
names[names_used][sizeof *names-1]='\0';
++names_used;
return true;
}

// Função para apagar todos os nomes.
void clear_names(void){
free(names); // Devolve ao sistema a memória alocada.
names=NULL;
names_used=names_size=0;
}


Se cada string puder ter seu próprio tamanho, sendo este alocado dinamicamente, então você teria de ter um array dinâmico (por de de um ponteiro) de arrays dinâmicos (um ponteiro para cada um), resultando em algo parecido com o seguinte.
#define NAMES_SIZE_INCREMENT 100
size_t names_size=0, names_used=0; // Medidores da quantidade alocada e da quantidade em uso.
char **names=NULL; // Cada string será um array dinâmico (ponteiro), então tenho um array dinâmico (requer ponteiro) de arrays dinâmicos (também ponteiros), por isso o ponteiro para ponteiro.

// Tenta inserir um nome ao final do array ‘names’, estendendo o array, se necessário.
bool insert_name(const char *new_name){
if(names_used>=names_size){
void *new_ptr=realloc(names, (names_size+NAMES_SIZE_INCREMENT)*sizeof *names);
if(!new_ptr)
return false; // Falha de alocação. Sai indicando erro, mas o conteúdo anterior de ‘names’ está preservado.
// Caso contrário, prossegue abaixo.
names=new_ptr;
names_size+=NAMES_SIZE_INCREMENT;
}
// Tenta alocar memória para uma cópia de new_name com a função strdup().
names[names_used]=strdup(new_name);
if(!names[named_used])
return false; // strdup() não conseguiu alocar espaço para a cópia da string, apesar do array de strings já ter aumentado de tamanho. Sai indicando erro e sem modificar a quantidade de nomes em uso.
// Caso contrário, prossegue abaixo.
++names_used;
return true;
}

// Função para apagar todos os nomes.
void clear_names(void){
// Como cada nome foi dinamicamente alocado, cada um deles precisa ser devolvido ao sistema.
while(names_used>0)
free(names[--names_used]);
free(names);
names=NULL;
names_size=0;
}



... Então Jesus afirmou de novo: “(...) eu vim para que tenham vida, e a tenham plenamente.” (João 10:7-10)


5. Re: Como criar uma lista vazia pra adicionar dados depois?

Apprentice X
ApprenticeX

(usa Slackware)

Enviado em 09/02/2022 - 01:39h

paulo1205 escreveu:Você quer um array dinâmico de strings (que implicam um array de caracteres para cada uma), então?

Cada string vai ter tamanho fixo, ou também pode ser dinamicamente alocada?

Se forem strings de tamanho fixo, você tem de ter um ponteiro para array de caracteres. Esse ponteiro vai apontar não para um único array, mas para um conjunto de arrays dispostos em blocos de memória adjacentes. Quando você precisar criar espaço para uma nova string, você vai ter de realocar a memória apontada pelo ponteiro, de modo a caber mais essa string (que é mais um array com o tal tamanho fixo de caracteres).
#define NAMES_SIZE_INCREMENT 100
size_t names_size=0, names_used=0; // Medidores da quantidade alocada e da quantidade em uso.
char (*names)[NAME_MAX]=NULL; // Ponteiro para array(s) de tamanho fixo NAME_MAX.

// Tenta inserir um nome ao final do array ‘names’, estendendo o array, se necessário.
bool insert_name(const char *new_name){
if(names_used>=names_size){
void *new_ptr=realloc(names, (names_size+NAMES_SIZE_INCREMENT)*sizeof *names);
if(!new_ptr)
return false; // Falha de alocação. Sai indicando erro, mas o conteúdo anterior de ‘names’ está preservado.
// Caso contrário, prossegue abaixo.
names=new_ptr;
names_size+=NAMES_SIZE_INCREMENT;
}
strncpy(names[names_used], new_name, sizeof *names-1);
names[names_used][sizeof *names-1]='\0';
++names_used;
return true;
}

// Função para apagar todos os nomes.
void clear_names(void){
free(names); // Devolve ao sistema a memória alocada.
names=NULL;
names_used=names_size=0;
}


Se cada string puder ter seu próprio tamanho, sendo este alocado dinamicamente, então você teria de ter um array dinâmico (por de de um ponteiro) de arrays dinâmicos (um ponteiro para cada um), resultando em algo parecido com o seguinte.
#define NAMES_SIZE_INCREMENT 100
size_t names_size=0, names_used=0; // Medidores da quantidade alocada e da quantidade em uso.
char **names=NULL; // Cada string será um array dinâmico (ponteiro), então tenho um array dinâmico (requer ponteiro) de arrays dinâmicos (também ponteiros), por isso o ponteiro para ponteiro.

// Tenta inserir um nome ao final do array ‘names’, estendendo o array, se necessário.
bool insert_name(const char *new_name){
if(names_used>=names_size){
void *new_ptr=realloc(names, (names_size+NAMES_SIZE_INCREMENT)*sizeof *names);
if(!new_ptr)
return false; // Falha de alocação. Sai indicando erro, mas o conteúdo anterior de ‘names’ está preservado.
// Caso contrário, prossegue abaixo.
names=new_ptr;
names_size+=NAMES_SIZE_INCREMENT;
}
// Tenta alocar memória para uma cópia de new_name com a função strdup().
names[names_used]=strdup(new_name);
if(!names[named_used])
return false; // strdup() não conseguiu alocar espaço para a cópia da string, apesar do array de strings já ter aumentado de tamanho. Sai indicando erro e sem modificar a quantidade de nomes em uso.
// Caso contrário, prossegue abaixo.
++names_used;
return true;
}

// Função para apagar todos os nomes.
void clear_names(void){
// Como cada nome foi dinamicamente alocado, cada um deles precisa ser devolvido ao sistema.
while(names_used>0)
free(names[--names_used]);
free(names);
names=NULL;
names_size=0;
}

O Primeiro código mais simples, acho que consegui entender, vou montar um exemplo pra mim, adicionando 10 nomes.

O Segundo código, também acho que consegui entender, visto que estou ainda me adaptando a ponteiro, e ele usa ponteiro de ponteiro. Pelo que entendi **names é um ponteiro que ao invés de armazenar uma cadeia de caracteres como: *names faria, Ele (**names) vai armazenar uma cadeia de ponteiros de caracteres! Por isso é um ponteiro de ponteiros! Como um array de strings.

Também entendi que um array de ponteiros, preciso liberar cada ponteiro desse array!

Obrigado!


6. Re: Como criar uma lista vazia pra adicionar dados depois?

Apprentice X
ApprenticeX

(usa Slackware)

Enviado em 09/02/2022 - 02:05h

Consegui entender também no 2o Exemplo que NAMES_SIZE_INCREMENT informa a qtd de caracteres que estou aumentando por vez! Ou seja, caso não tenho espaço, vai me dar mais 100 de espaço alocado!

Entendi que new_ptr são meus novos ponteiros criados ou seja, minha string!
Entendi que preciso eu mesmo controlar o que existe no meu array e para isso names_used me informa o tamanho do meu array. Ou seja, não existe um comando em C que me diga informações sobre meu array como foi o caso do meu array de int da pergunta anterior! Sou eu quem preciso saber!

Obrigado Paulo, realmente ficou muito didádico as 2 respostas! Pois nas 2 consegui entender como funciona um ponteiro, como alocar dinamicamente, o que é ponteiro de ponteiro e que utilidade isso teria, pois só agora entendi neste exemplo que consigo reproduzir em uma utilidade real.

Gostei mesmo de entender isso!



7. Re: Como criar uma lista vazia pra adicionar dados depois?

Apprentice X
ApprenticeX

(usa Slackware)

Enviado em 10/02/2022 - 00:04h

paulo1205 escreveu: Se cada string puder ter seu próprio tamanho, sendo este alocado dinamicamente, então você teria de ter um array dinâmico (por de de um ponteiro) de arrays dinâmicos (um ponteiro para cada um)
char **names=NULL;  // Cada string será um array dinâmico (ponteiro), então tenho um array dinâmico (requer ponteiro) de arrays dinâmicos (também ponteiros), por isso o ponteiro para ponteiro.

// Tenta inserir um nome ao final do array ‘names’, estendendo o array, se necessário.
bool insert_name(const char *new_name){
if(names_used>=names_size){
void *new_ptr=realloc(names, (names_size+NAMES_SIZE_INCREMENT)*sizeof *names);

Entendi que realloc alocou espaço apenas para new_name ou seja a String que eu adicionei ao array *names. Mas não vejo ele adicionar linhas!
Então não entendi essa alocação, pq penso que o array = names[0][100] ou seja aloquei 100 espaços para a String, mas como estou conseguindo adicionar mais linhas ao meu array? por exemplo: names[1][100], names[2][100], names[3][100]

Eu consegui montar o exemplo e tudo funcionou direitinho, adicionei 10 linhas tendo cada uma 46 bytes. E consegui entender cada parte do que foi feito, cheguei até a tirar o bool, true, false para 0,1, para não adicionar a biblioteca #include <stdbool.h>

Mas não consegui entender onde e como foi que o código adicionou linhas ao vetor! Sendo ele inicialmente um array vazio tanto em linhas qto em qtd de caracteres

Eu a princípio anotei e entendi da forma abaixo como explico pra mim mesmo:
1) É como se criássemos um ponteiro de vetor de characteres: char *Tags[] = {"Text", "Text", "Text"};
2) Porém ao invés de termos uma cadeia de characteres teremos um ponteiro que é uma cadeia de characteres: char *Tags[] = {*Tag, *Tag, *Tag};
3) Assim sendo um ponteiro de vetor de ponteiros



8. Re: Como criar uma lista vazia pra adicionar dados depois?

Paulo
paulo1205

(usa Ubuntu)

Enviado em 12/02/2022 - 00:08h

ApprenticeX escreveu:

paulo1205 escreveu: Se cada string puder ter seu próprio tamanho, sendo este alocado dinamicamente, então você teria de ter um array dinâmico (por de de um ponteiro) de arrays dinâmicos (um ponteiro para cada um)
char **names=NULL;  // Cada string será um array dinâmico (ponteiro), então tenho um array dinâmico (requer ponteiro) de arrays dinâmicos (também ponteiros), por isso o ponteiro para ponteiro.

// Tenta inserir um nome ao final do array ‘names’, estendendo o array, se necessário.
bool insert_name(const char *new_name){
if(names_used>=names_size){
void *new_ptr=realloc(names, (names_size+NAMES_SIZE_INCREMENT)*sizeof *names);

Entendi que realloc alocou espaço apenas para new_name ou seja a String que eu adicionei ao array *names. Mas não vejo ele adicionar linhas!


Não. Nos dois exemplos, em cada realocação eu peço espaço para múltiplos (NAMES_SIZE_INCREMENT, que eu defini nos exemplos como sendo 100) elementos de uma só vez. E faço assim porque isso tende a ser mais eficiente ficar alocando espaço novo a cada nova inserção. Então, se eu noto que faltou memória, peço logo espaço para cem novos elementos, de modo que e não vou precisar pedir memória novamente até que eu tente incluir o centésimo-primeiro elemento.

Então não entendi essa alocação, pq penso que o array = names[0][100] ou seja aloquei 100 espaços para a String, mas como estou conseguindo adicionar mais linhas ao meu array? por exemplo: names[1][100], names[2][100], names[3][100]


Eu não sei se eu compreendi o que você não entendeu.

Vamos tentar ir devagar na ilustração.

INTENTO: O que você quer é um array de strings.

DADO: Strings em C são armazenadas em arrays de caracteres.

RESULTADO: Logo, você quer um array de arrays de caracteres (i.e. um array em que cada elemento é um array de caracteres).

DADO: Arrays nativos do C têm de ter tamanho fixo, já conhecido no momento da declaração, quer porque o tamanho está literalmente expresso dentro dos colchetes, quer deixando os colchetes vazios mas acompanhando a declaração uma lista de valores iniciais (ou, no caso de arrays de caracteres, de uma constante literal de string) que permita ao compilador calcular a quantidade fixa de elementos.

RESULTADO: Dadas essas características, usando apenas arrays nativos do C, um array de strings teria de ser modelado com algo parecido com o seguinte (onde N_STRINGS é a quantidade máxima e fixa de strings distintas, e SIZE_STRING é o comprimento fixo máximo que cada string pode ter):
char array_de_strings[N_STRINGS][SIZE_STRING]; 


DADO: Ponteiros em C não estão restritos a apontar para um único elemento, mas considera-se que podem apontar para um conjunto de objetos do mesmo tipo, dispostos em posições de memória adjacentes.

DADO: Após o momento da declaração, arrays em C têm comportamento semelhante ao de ponteiros (exceto pelo fato de que você pode alterar o valor de uma variável ponteiro, ao passo que não pode alterar a designação de um array).

RESULTADO: Por essas características e semelhança, pode-se, por construção e ainda que com alguns percalços (como, por exemplo, não poder usar sizeof para conseguir saber o número de elementos em uso), usar um ponteiro que aponte para uma região de memória devidamente preparada em lugar de um array nativo.

RESULTADO: A exceção na semelhança é particularmente interessante: poder alterar o valor de um ponteiro permite um conjunto de operações que arrays nativos não permitem, incluindo a possibilidade de ter uma quantidade de elementos que possa variar ao longo da execução do programa (mesmo que isso implique mover todos os dados para outra região de memória, a fim de obter espaço suficiente para a nova quantidade de elementos, caso a região ocupada anteriormente não pudesse acomodar esses elementos adicionais).

RESULTADO: O array de strings desejado acima ganhou três novas possibilidades de ser representado:

  • Array dinâmico (ponteiro) de strings de tamanho fixo (cada uma delas como array de comprimento fixo): char (*dynamicarray_de_strings)[SIZE_STRING];
  • Array de tamanho fixo de strings de tamanho dinâmico (cada uma delas indicada por um ponteiro): char *array_de_dynamicstrings[N_STRINGS];
  • Array dinâmico (ponteiro) de strings de tamanho dinâmico (cada uma delas indicada por um ponteiro): char **dynamicarray_de_dynamicstrings;

DADO: Quando você tem ponteiros, _cada_ ponteiro precisa de uma região de memória válida para onde apontar, e só depois é possível colocar dados nessa memória e consultá-los.

RESULTADO: Por simetria (e por construção), com qualquer das quatro representações, se o array de strings tiver o nome “nomes”, o valor da expressão nomes vai produzir o valor do endereço de onde reside a primeira string, nomes[0] vai produzir o valor do endereço de onde reside o primeiro caráter da primeira string, nomes[1] vai produzir o valor do endereço de onde reside o primeiro caráter da segunda string etc.
#include <stdio.h>
#include <stdlib.h>

#define OUT(x, y) \
do { \
printf("%s=%p\n", #x, x); \
for(int n=0; n<y; ++n) \
printf("\t&%s[%d]=%p; %s[%d]=%p; &%s[%d][0]=%p; %s[%d][0]=%d\n", #x, n, &x[n], #x, n, x[n], #x, n, &x[n][0], #x, n, x[n][0]); \
putchar('\n'); \
} while(0);

int main(void){
// Depois de faitas as devidas alocações, os quatro objetos abaixo vão conter ou
// apontar para arrays com 10 elementos contendo ou apontando para arrays com
// 25 caracteres cada.
char aac[10][25]={};
char *apc[10];
char (*pac)[25];
char **ppc;

OUT(aac, 10);

for(int n=0; n<10; ++n)
apc[n]=calloc(25, sizeof *apc[n]);
OUT(apc, 10);

pac=calloc(10, sizeof *pac);
OUT(pac, 10);

ppc=calloc(10, sizeof *ppc);
for(int n=0; n<10; ++n)
ppc[n]=calloc(25, sizeof *ppc[n]);
OUT(ppc, 10);

// Não me preocupo com a desalocação aqui porque o programa vai acabar logo em seguida.
}


Eu consegui montar o exemplo e tudo funcionou direitinho, adicionei 10 linhas tendo cada uma 46 bytes. E consegui entender cada parte do que foi feito, cheguei até a tirar o bool, true, false para 0,1, para não adicionar a biblioteca #include <stdbool.h>

Mas não consegui entender onde e como foi que o código adicionou linhas ao vetor! Sendo ele inicialmente um array vazio tanto em linhas qto em qtd de caracteres


Em ambos os exemplos da postagem anterior, quando se percebe que não há espaço para novos elementos, cada (re)alocação cria espaço para 100 novos elementos e aumenta em 100 unidades o contador de número total de elementos reservados; se ainda houver espaço suficiente, não realoca nada nem incrementa o contador. Em seguida, copia a a string informada à função: no caso da versão com strings de tamanho fixo na forma de arrays de caracteres, a cópia é feita com strncpy(); na versão em que cada string é dinamicamente alocada, a alocação e a cópia são deitas pela a função strdup(), que serve justamente para duplicar uma string origem numa nova região de memória com tamanho suficiente para conter essa cópia. Após copiar a string incrementa o contador de elementos ocupados (exceto, no caso da versão com strings dinâmicas, se strdup() falhar, caso em que a função sai indicando erro).

Eu a princípio anotei e entendi da forma abaixo como explico pra mim mesmo:
1) É como se criássemos um ponteiro de vetor de characteres: char *Tags[] = {"Text", "Text", "Text"};


Cuidado! Isso que você declarou não é um ponteiro de vetor de caracteres, mas sim um vetor de ponteiros. Veja os exemplos acima. Um ponteiro para vetores de carcteres teria a seguinte forma.
char (*ponteiro_para_array_de_char)[TAMANHO];  // O tamanho aqui é necessário! 


2) Porém ao invés de termos uma cadeia de characteres teremos um ponteiro que é uma cadeia de characteres: char *Tags[] = {*Tag, *Tag, *Tag};


Não entendi o que você quis dizer com isso.

3) Assim sendo um ponteiro de vetor de ponteiros


Também não entendi essa conclusão, como se uma coisa conduzisse logicamente a outra. Ponteiro para arrays não é a mesma coisa nem implica array de ponteiros.

Note que, quando eu falei, acima, sobre comportamento semelhante, eu falei sobre os cuidados que se tem para conseguir essa semelhança por construção.

Espero que esta mensagem ajude a entender melhor do que a mensagem anterior. E note que eu mesmo


... Então Jesus afirmou de novo: “(...) eu vim para que tenham vida, e a tenham plenamente.” (João 10:7-10)


9. Re: Como criar uma lista vazia pra adicionar dados depois?

Apprentice X
ApprenticeX

(usa Slackware)

Enviado em 13/02/2022 - 13:10h

Obrigado Paulo pela resposta.

O que eu estou tentando entender é justamente isso. As 4 declarações acima são semelhantes e produzem o mesmo resultado!
Vamos ver se entendi elas separadamente!
char aac[10][25]={};
char *apc[10];
char (*pac)[25];
char **ppc;


Entendo como uma lista de 10 linhas, onde cada linha possue 25 characteres. OK
char aac[10][25]={}; 


Entendo como um ponteiro para uma lista com 10 linhas. E por ser ponteiro posso colocar qts characteres eu quiser em cada linha visto que será alocado qdo eu colocar.
char *apc[10]; 


Acho que entendi que é um ponteiro para um simples array de 25 characteres, e entendi que por estar entre parênteses o transforma em uma lista onde no momento de sua declaração não possue nenhuma linha!
char (*pac)[25]; 


Aqui o que pude entender é que isso é equivalente a isso: char *ppc[] = {*Text, *Text, *Text};
Ou seria: char *ppc[*]
char **ppc; 

Eu não consegui entender graficamente a equivalência dele com um array.

Explicando de uma outra forma: No programa que foi criado, no meu jeito de ver temos o seguinte:
• Uma lista com textos
• Essa lista pode ser aumentada em qtd linhas ou em qtd de characteres em cada linha

Então minha lista acima é equivalente à: (Onde posso ter várias linhas com textos)
char List[];
char *List[];


Qual a equivalência mais próxima para isso: char **ppc;
char *ppc[] = {*Text, *Text, *Text}; // Ponteiro para array de ponteiro
char *ppc[] = {Text, Text, Text}; // Ponteiro de array simples
char ppc[] = {*Text, *Text, *Text}; // Array simples para ponteiros
char ppc[] = {Text, Text, Text}; // Simples array de strings (array de characteres)


Tipo, até o momento só consigo entender graficamente os seguintes ponteiros
char *Text; // Ponteiro para um array de characteres ou para uma simples string
char *List[]; // Ponteiro para uma lista de array de characteres ou Ponteiro para uma Lista de Strings




10. Re: Como criar uma lista vazia pra adicionar dados depois?

Paulo
paulo1205

(usa Ubuntu)

Enviado em 13/02/2022 - 19:26h

ApprenticeX escreveu:

Obrigado Paulo pela resposta.

O que eu estou tentando entender é justamente isso. As 4 declarações acima são semelhantes e produzem o mesmo resultado!
Vamos ver se entendi elas separadamente!
char aac[10][25]={};
char *apc[10];
char (*pac)[25];
char **ppc;


Entendo como uma lista de 10 linhas, onde cada linha possue 25 characteres. OK
char aac[10][25]={}; 


Cuidado com a terminologia! Evite usar “lista” para se referir a um array (ou vetor), pois lista é um outro tipo de estrutura de dado.

Também “linha” não é muito próprio. No contexto do C e sua biblioteca padrão, esse conceito está mais ligado a entrada e saída de dados em modo texto (não-binário), e um array com 25 elementos pode conter mais de uma linha de texto, ou mesmo não conter uma linha completa (e pode nem mesmo conter uma string).

O correto para a declaração acima é dizer que ela declara um array com dez elementos, no qual cada elemento é um array com vinte e cinco elementos do tipo char.

O tipo, portanto, do identificador aac é “array com 10 elementos do tipo array com 25 elementos do tipo char”. Isso implica que o tipo de cada elemento desse array, designados por aac[0], aac[1], …, aac[9] é “array com 25 elementos do tipo char”. Por conseguinte, para cada array na forma aac[n] (com 0<=n e n<10), cada uma das expressões aac[n][0], aac[n][1], …, aac[n][24] designam um único elemento do tipo char.

Entendo como um ponteiro para uma lista com 10 linhas. E por ser ponteiro posso colocar qts characteres eu quiser em cada linha visto que será alocado qdo eu colocar.
char *apc[10]; 


Errado. Isso é um array com dez elementos, no qual cada elemento é um ponteiro para caráter. No C, tanto no momento da declaração quanto também na hora de usar em expressões, os colchetes têm prioridade mais alta do que o asterisco. Por isso é que isso designa um array de ponteiros.

E você não pode “colocar caracteres”, muito menos “quantos [você] quiser”, só por ser ponteiro. O que você pode fazer é com que cada ponteiro que é elemento do array aponte para uma área de dados com uma quantidade de espaço devidamente alocado. No exemplo que eu construí na mensagem anterior, eu usei alocação dinâmica de memória para preencher o valor de endereço de cada um dos dez ponteiros que são elementos do array apc (apc[0] até apc[9]), e em cada uma dessas alocações (feitas com a função calloc() dentro de um laço de repetição) eu pedi espaço para vinte e cinco caracteres.

Assim, por construção, eu consegui produzir um cenário no qual apc, apc[m] e apc[m][n] têm comportamentos análogos aos de aac, aac[m] e aac[m][n].

Acho que entendi que é um ponteiro para um simples array de 25 characteres, e entendi que por estar entre parênteses o transforma em uma lista onde no momento de sua declaração não possue nenhuma linha!
char (*pac)[25]; 


De novo, você inverteu o que está dito. Como eu coloquei parênteses em volta da expressão contendo o asterisco, esta passou a ter prioridade maior do que os colchetes. Isso implica que agora temos em pac um ponteiro para (um ou mais) dado(s) cujo tipo é “array com vinte e cinco elementos do tipo char”.

Esse “um ou mais” é porque o C considera que o mesmo ponteiro pode ser usado para apontar para um bloco de elementos contíguos do mesmo tipo. Com isso, quando você aplica um deslocamento na forma de um valor inteiro ao endereço base do ponteiro, ele pode apontar para diferentes elementos dentro desse bloco de dados contíguos, de modo inteiramente análogo à aplicação de um índice inteiro sobre um array — tão análogo, na verdade, que usa inclusive a mesma sintaxe: “array[desloc]” ou “ponteiro[desloc]”.

No exemplo da postagem anterior, eu faço uma única chamada a calloc() para pac, justamente para alocar espaço suficiente para dez ponteiros (o tipo de cada ponteiro é inferido pelo compilador quando eu uso a expressão “sizeof *pac”).

Então, por construção, eu consegui novamente produzir um comportamento análogo para pac, pac[m] e pac[m][n] aos de aac, aac[m] e aac[m][n].

Aqui o que pude entender é que isso é equivalente a isso: char *ppc[] = {*Text, *Text, *Text};


Continuo sem entender o que você quis dizer com esse “*Text, *Text, *Text”.

Ou seria: char *ppc[*]


Também não. ppc (que é uma abreviação de “ponteiro para ponteiro para char”, assim como aac abrevia “array de array de char”, apc abrevia “array de ponteiros para char” e pac abrevia “ponteiro para array de char”).

Essa tentativa (inválida!) de declaração que você colocou acima faz pensar que você gostaria de um array com tamanho flexível de ponteiros para char. A sintaxe do C obviamente não permite isso, mas acaba que, POR CONSTRUÇÃO (com o perdão da ênfase repetitiva), é esse efeito que nós conseguimos para ppc no meu exemplo da postagem anterior.

(Se bem que, dadas as inversões de compreensão dos dois casos anteriores, pode ser que você tenha imaginado outra coisa; temo que, em vez de um suposto array com tamanho flexível de ponteiros para char, você tenha imaginado algo ainda mais exótico, tal como um ponteiro para arrays com tamanhos flexíveis.)

char **ppc; 

Eu não consegui entender graficamente a equivalência dele com um array.


De novo, eu vou usar a expressão “POR CONSTRUÇÃO” (com perdão para a ênfase em maiúsculas, mas é preciso que fique claro) para explicar que após alocar o espaço para dez ponteiros e atribuir esse espaço a ppc e após atribuir a cada um desses ponteiros um espaço dinamicamente alocado para conter vinte e cinco caracteres, podemos usar ppc, ppc[m] e ppc[m][n] de maneiras análogas a como usamos aac, aac[m] e aac[m][n], respectivamente.

Explicando de uma outra forma: No programa que foi criado, no meu jeito de ver temos o seguinte:
• Uma lista com textos
• Essa lista pode ser aumentada em qtd linhas ou em qtd de characteres em cada linha


Essa sua terminologia confunde. insisto para que você não use “lista” ou “list” para referir-se a um vetor, nem “texto” ou “text” para referir-se a strings ou a vetores de caracteres. Mude isso inclusive nos seus programas, para não criar nem arraigar esse mau hábito.

Então minha lista acima é equivalente à: (Onde posso ter várias linhas com textos)
char List[];
char *List[];


Você não pode ter as duas declarações ao mesmo tempo.

Além disso, nenhuma das duas declarações é válida em C fora do contexto de lista de parâmetros de uma função (que não é o caso, então são inválidas mesmo!).

Fora do contexto de lista de parâmetros, declarações de arrays são obrigadas enquadrar-se em pelo menos uma das duas situações: (1) o número de elementos tem de estar expresso entre os colchetes, ou (2) deve haver uma lista de inicialização dos elementos acompanhando a declaração do array.

No contexto de parâmetros de funções, os colchetes vazios não indicam arrays, mas sim ponteiros. Assim sendo, um parâmetro de função declarado com colchetes (vazios ou não!) na forma “char param[]” é totalmente sinônimo de “char *param”. De modo semelhante, nesse mesmo contexto, algo na forma “char *param[123]” é sinônimo de “char **param”.

Qual a equivalência mais próxima para isso: char **ppc;
char *ppc[] = {*Text, *Text, *Text}; // Ponteiro para array de ponteiro
char *ppc[] = {Text, Text, Text}; // Ponteiro de array simples
char ppc[] = {*Text, *Text, *Text}; // Array simples para ponteiros
char ppc[] = {Text, Text, Text}; // Simples array de strings (array de characteres)


Nenhuma das últimas quatro linhas é sintaticamente válida, e os comentários não têm relação com os tipos que poderiam ser conseguidos com uma sintaxe que fosse válida. Então fica difícil saber o que você quis dizer.

Mais acima, eu tentei desfazer o mal entendido, tentando detalhar cada construção. Espero que aquilo tenha ficado claro. Não se prenda ao seu entendimento anterior, mas procure entender o que eu expliquei novamente nesta postagem.

Tipo, até o momento só consigo entender graficamente os seguintes ponteiros
char *Text; // Ponteiro para um array de characteres ou para uma simples string 


Não! A declaração acima indica um ponteiro para caráter (que pode sem um único caráter ou o primeiro caráter de um bloco de vários caracteres em posições adjacentes na memória).

Se for um ponteiro para bloco, esse fato permite que o ponteiro seja usado de modo análogo à forma de se usar um array. Como arrays são usados para armazenar strings, pode-se dizer que um ponteiro que se refira a esse bloco (apontando para seu primeiro elemento) se refere a uma string.

Mas note o verbo que eu usei: “referir-se”. A qualificação de que o objeto apontado designa um array ou uma string é uma informação que não aparece sintaticamente no programa nem altera o tipo do dado. O tipo do identificador Text continua sendo ponteiro para caráter, não ponteiro para array de caracteres, pois isso teria uma sintaxe bem diferente, como já foi mostrado acima e em postagens anteriores.

char *List[]; // Ponteiro para uma lista de array de characteres ou Ponteiro para uma Lista de Strings 


De novo, essa sintaxe não é válida, e o comentário está errado (e com a terminologia ruim, já apontada). Onde algo parecido poderia ser usado seria na lista de parâmetros de função, e o sentido seria que o tipo do parâmetro seria ponteiro para ponteiro para caracteres.


... Então Jesus afirmou de novo: “(...) eu vim para que tenham vida, e a tenham plenamente.” (João 10:7-10)






Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts