paulo1205
(usa Ubuntu)
Enviado em 11/04/2016 - 10:32h
joaovirgili escreveu:
Realmente, não tinha me dado conta de que a estrutura representa apenas um nó e que é mais conveniente nomeá-la assim. Eu sabia que a estrutura não representava uma pilha inteira, porém não tive o costume especificar no código.
Uma Boa Prática -- com direito a letras maiúsculas -- é você ter um código que se auto-documente. Nomes adequados para tipos de dados, funções, variáveis e macros é uma coisa que ajuda a todos, inclusive a você mesmo. Acostume-se com isso. Nos seus programas, vá além da sintaxe correta; dê um sentido claro a tudo o que você codificar.
O principal erro do meu código é que a variável que eu alterava nas minhas funções só valiam para aquela função, então quando saísse dela, tudo seria perdido, é isso?
É um erro importante, mas não sei se dá para dizer que é o principal. Ele decorre de um problema de fundo que é maior: parece que você não sabe muito bem o que está fazendo.
Assim eu teria que declarar ponteiro e trabalhar com o ponteiro dentro da função?
O topo da pilha é móvel? É alocado dinamicamente? Se sim -- e é “sim” no seu caso --, então tem grandes chances de ter de ser um ponteiro.
Uma próxima pergunta seria esta: seu valor tem de ser modificado dentro de uma função? Se sim, ele tem de ser passado por referência. Como, porém, C não tem referências (todos os argumentos de funções são
cópias dos valores dos parâmetros), você tem de passar seu endereço (a cópia do endereço usado para chegar a um dado é o mesmo valor de endereço, logo permite chegar ao mesmo dado), e a função tem de ser declarada para receber tal endereço por meio de um ponteiro (ponteiros são tipos de dados que armazenam endereços de objetos). Mas como o dado original já é de um tipo que é ponteiro, então o argumento da função terá de ser um ponteiro para ponteiro, para receber o endereço do objeto ponteiro que se quer modificar.
void func1(int *arg){ /* ... */ }
void func2(int **arg){ /* ... */ }
int *p_var=malloc(sizeof(int)); // Aloca espaço para um inteiro e salva
// endereço do local alocado em em p_var.
func1(p_var); // func1() recebe uma _cópia_ do _valor_ de p_var. Com a
// cópia é possível modificar o conteúdo da memória apontada
// por p_var (usando “*arg” dentro da função), mas o valor de
// p_var não pode ser alterado pela função.
func2(&p_var); // func2() recebe uma cópia do _endereço_ de p_var. Com o
// endereço de p_var, é possível à função alterar tanto o
// valor de p_var (usando “*arg” dentro da função), como do
// conteúdo por ela apontado (usando “**arg”).
typedef struct no *ptr_no;
struct no {
int valor;
ptr_no prox;
};
Se eu não utilizar o "typedef struct no *ptr_no;" neste caso, para declarar um ponteiro pro topo da pilha, teria que fazer assim: "struct no *topoPilha;"?
Ao utilizar o typedef struct no*ptr_no, tudo que eu declarar ja será um ponteiro?
A vantagem de usar
typedef é dar clareza ao código por meio da redução do número de símbolos presentes no código, privilegiando visualmente a semântica, em lugar da sintaxe. Seu uso é mais comum quando há múltiplos níveis de ponteiros ou combinações de ponteiros para tipos compostos (estruturas e arrays) ou para funções, e mais ainda quando você têm funções que recebem essas coisas como argumentos.
Existe um benefício adicional, sobretudo quando se desenvolvem bibliotecas: esconder de quem usa o tipo de dados a possível complexidade interna que tal tipo de dados tem. Como isso, ele não se distrai com detalhes de implementação nem fica tentado a manipular diretamente componentes internos de objetos complexos. O tipo
FILE , declarado em <stdio.h> e usado pela biblioteca padrão para entrada e saída de dados, é exemplo de
typedef assim. Ele é um nome alternativo para uma estrutura interna complexa que mantém informações como buffers, indicadores de estado, e elementos usados para interagir com o sistema operacional. Mas ninguém precisa se preocupar com isso (nem eu mesmo, mexendo com C há 28 anos, jamais precisei).
Contudo, esses benefícios, no fim das contas, são puramente visuais: o novo nome de um tipo é um sinônimo exato do tipo que ele representa. Veja abaixo.
struct exemplo { /* ... */ };
typedef struct exemplo *p_exemplo;
typedef p_exemplo *pp_exemplo;
struct exemplo a, *b, **c, ***d;
p_exemplo e, *f, **g;
pp_exemplo h, *i;
/*
‘a’ tem o tipo “struct exemplo”.
‘b’ e ‘e’ têm o tipo “struct exemplo *”.
‘c’, ‘f’ e ‘h’ têm o tipo “struct exemplo **”.
‘d’, ‘g’ e ‘i’ têm o tipo “struct exemplo ***”.
Adicionalmente, se todos os níveis de ponteiros apontarem para
dados válidos, então podemos derreferenciar os ponteiros para
obter dados dos seguintes tipos:
- “struct exemplo **”: ‘*d’, ‘*g’ e ‘*i’.
- “struct exemplo *”: ‘*c’, ‘**d’, ‘*f’, ‘**g’, ‘*h’ e ‘**i’.
- “struct exemplo”: ‘*b’, ‘**c’, ‘***d’, ‘*e’, ‘**f’, ‘***g’, ‘**h’ e ‘***i’.
*/
E o modo que utilizei no outro código, está errado também?
Sim, também está errado. Prova disso é a falha de segmentação.
Tenho uma dificuldade ao mexer com ponteiros, e achei este método mais simples que o primeiro, porém neste está dando falha de segmentação.
Não se recrimine nem desista. No começo a gente acaba se confundindo um pouco. Nessa fase, quanto mais diagramas to tipo caixa com setinha que aponta para outras caixas você fizer, melhor para você.
Geralmente quando dá este erro comigo, é quando tento acessar uma memória não alocada, porém a meu ver a lógica está certa.
Seu código agora ficou muito parecido com o anterior. Em particular, todos os erros de modificar localmente as cópias recebidas pelas funções, quando o que se queria era modificar os originais, foram mantidos, assim como a falha lógica de pular o topo e seguir direto para o seguinte ao topo na hora de imprimir e os nomes que dizem uma coisa, mas representam outra.
Veja os exemplos que eu mostrei acima, principalmente aquele com as funções
func1 () e
func2 (). Todas as suas funções que alteram a pilha devem se parecer com
func2 (). As que apenas consultam, podem parecer com
func1 ().