Função e ponteiro em C

1. Função e ponteiro em C

Paulo
Sr. F

(usa Ubuntu)

Enviado em 12/04/2016 - 22:23h

Boa noite, estou estudando lista encadeada em estrutura de dados e estou com uma pequena dúvida. Já me familiarizei por exemplo, com funções do tipo:

void inserir (int array, int n) {

(...)

return;
}


No entanto, me deparei com essa função hoje:

struct no *inserir (struct no *lista, int n) {

struct no *novo = (struct no*) malloc(sizeof(struct no));
novo->chave = n;
novo->prox = lista;

return novo;
}

Estou com dúvida no protótipo da segunda função
struct no *inserir (struct no *lista, int n); 
O nome da função é na verdade um ponteiro. Por que? Tem alguma relação com o fato dela retornar um ponteiro? Existiria outra forma de fazer essa mesma função? Gostaria que alguém me esclarece melhor isso.

Inté!


  


2. Re: Função e ponteiro em C

Perfil removido
removido

(usa Nenhuma)

Enviado em 13/04/2016 - 00:06h

A função cria um espaço na memória para um elemento de uma lista e retorna um ponteiro para este tipo de dado.
São como os elos de uma corrente.

----------------------------------------------------------------------------------------------------------------
# apt-get purge systemd (não é prá digitar isso!)

Encryption works. Properly implemented strong crypto systems are one of the few things that you can rely on. Unfortunately, endpoint security is so terrifically weak that NSA can frequently find ways around it. — Edward Snowden



3. Re: Função e ponteiro em C

Paulo
paulo1205

(usa Ubuntu)

Enviado em 13/04/2016 - 05:05h

O nome que designa uma função em C pode ser usado de dois modos:

- Para invocar a função: Nesse caso, o nome deve ser seguido de parênteses, que cercam uma lista (possivelmente vazia) de parâmetros de tipos compatíveis com os tipos de argumentos que a função foi declarada para receber.

- Para referir-se à função sem a invocar: Quando os parênteses com a lista de argumentos não está presente. Nesse caso, quase sempre ocorre uma conversão automática e implícita de tipo, de “função que recebe tal lista de argumentos e retorna valores do tipo tal” para “ponteiro para função que recebe tal lista de argumentos e retorna valores do tipo tal”. As duas únicas exceções da conversão implícita são quando o nome da função é passado como argumentos para o operadores & (endereço de), que faz conversão equivalente, porém explícita, e sizeof, cujo uso é proibido com funções (o compilador deveria dar erro se você tentasse).

A ocorrência da conversão implícita de “função” para “ponteiro para função” não depende do tipo do resultado devolvido nem dos tipos dos argumentos. Entretanto, o tipo resultante dessa conversão os leva em consideração (até porque tem de ser possível reconstruir o tipo da função a partir do ponteiro).


4. Re: Função e ponteiro em C

Paulo
Sr. F

(usa Ubuntu)

Enviado em 13/04/2016 - 11:23h

paulo1205 escreveu:

O nome que designa uma função em C pode ser usado de dois modos:

- Para invocar a função: Nesse caso, o nome deve ser seguido de parênteses, que cercam uma lista (possivelmente vazia) de parâmetros de tipos compatíveis com os tipos de argumentos que a função foi declarada para receber.

- Para referir-se à função sem a invocar: Quando os parênteses com a lista de argumentos não está presente. Nesse caso, quase sempre ocorre uma conversão automática e implícita de tipo, de “função que recebe tal lista de argumentos e retorna valores do tipo tal” para “ponteiro para função que recebe tal lista de argumentos e retorna valores do tipo tal”. As duas únicas exceções da conversão implícita são quando o nome da função é passado como argumentos para o operadores & (endereço de), que faz conversão equivalente, porém explícita, e sizeof, cujo uso é proibido com funções (o compilador deveria dar erro se você tentasse).

A ocorrência da conversão implícita de “função” para “ponteiro para função” não depende do tipo do resultado devolvido nem dos tipos dos argumentos. Entretanto, o tipo resultante dessa conversão os leva em consideração (até porque tem de ser possível reconstruir o tipo da função a partir do ponteiro).


Paulo, foi bem esclarecedor, no entanto a minha dúvida não é bem essa, talvez eu não tenha sido claro, falei do protótipo com intuído de tentar expor melhor a minha dúvida mas o que me incomoda é *inserir de
struct no *inserir (struct no *lista, int n) {

struct no *novo = (struct no*) malloc(sizeof(struct no));
novo->chave = n;
novo->prox = lista;

return novo;
}

O que tá pegando é mais o ponteiro.



5. Re: Função e ponteiro em C

Paulo
paulo1205

(usa Ubuntu)

Enviado em 13/04/2016 - 17:56h

Sr. F escreveu:

Paulo, foi bem esclarecedor, no entanto a minha dúvida não é bem essa, talvez eu não tenha sido claro, falei do protótipo com intuído de tentar expor melhor a minha dúvida mas o que me incomoda é *inserir de
struct no *inserir (struct no *lista, int n) {

struct no *novo = (struct no*) malloc(sizeof(struct no));
novo->chave = n;
novo->prox = lista;

return novo;
}

O que tá pegando é mais o ponteiro.


Vamos por partes.

Introdução

Considere inicialmente a seguinte declaração simples.

int *ptr_int; 


Nessa declaração, você declara uma variável chamada ptr_int. Qual o tipo dessa variável?

Creio que, mesmo não tendo conhecimento muito avançado, você não terá dificuldade em responder que é “ponteiro para int”.

Agora repare com cuidado na pergunta que eu fiz. O foco da minha pergunta foi a variável que faz parte da declaração. Para respondê-la, você provavelmente olhou a declaração, isolou a parte que contém o nome da variável, e depois olhou em volta para ver as propriedades que o cercam, e constatou que a propriedade tipo é “ponteiro para int”.

Quem privilegia muito esse tipo de visão, centrada na variável e considerando seu tipo como mero atributo secundário, prefere muitas inclusive mudar a posição do espaço na declaração, deixando-a com a seguinte forma.

int* ptr_int;  // ptr_int é um “int*” 


Note que eu só mudei a posição do espaço, de antes do asterisco para depois dele, deixando-o colado no nome do tipo, em vez de no nome da variável. As duas declarações, no entanto, são sinônimas. E esta forma é mais prevalente no mundo C++ do que no mundo C.

Existe porém uma outra pergunta que pode ser feita a respeito de ambas as declarações, e tal pergunta tem a ver com o fato de que o ponteiro para um certo tipo de objeto eventualmente será usado para re referir a um objeto desse tipo. No caso da nossa declaração de exemplo, seria a seguinte pergunta: quem designa o dado final, cujo tipo é int (ou, de modo mais curto, “quem tem o tipo int”)? E a resposta é que “*ptr_int” tem o tipo int.

Antes de prosseguir, eis o que eu quero destacar desta introdução: você pode, tanto na sua mente quanto na forma de se expressar no código, dar ênfase ao ponteiro em si ou dar ênfase ao dado que eventualmente será apontado. Eu acredito que mudar o olhar, de vez em quando, pode ajudar você em algumas situações.

NOTA:
Eu não gosto da segunda forma apresentada acima. Em parte, meu gosto pode ter sido influenciado por eu ter começado com C, que que a primeira forma é tradicionalmente mais usada. Mas há uma aspecto de falso sentido mesmo, que fica mais fácil de ver quando você tem a declaração de múltiplas variáveis na mesma linha. Veja por si mesmo.

int *p1, i1, a1[10];  // p1 é ponteiro para int, i1 é int, a1 é array de int
int* p2, i2, a2[10]; // p2 é ponteiro para int, i2 é int, a2 é array de int


Percebeu? No segundo exemplo, mesmo estando colado na palavra int, e não no nome da variável, o asterisco se refere à primeira variável da lista, não se propagando para as demais. Isso é visualmente aberrante e, por isso mesmo, acho que deve ser totalmente evitado, mesmo que, na ocasião, a ênfase seja no ponteiro, e não no dado por ele referido.



Declaração de Tipos Agregados com Ponteiros

(A ser escriito.)


Funções que Retornam Ponteiros e Ponteiros para Função

(A ser escrito.)


Voltando à Dúvida Original

struct no *inserir(struct no *lista, int n); 


(A ser escrito.)


6. Re: Função e ponteiro em C

Paulo
Sr. F

(usa Ubuntu)

Enviado em 16/04/2016 - 18:08h

paulo1205 escreveu:

Sr. F escreveu:

Paulo, foi bem esclarecedor, no entanto a minha dúvida não é bem essa, talvez eu não tenha sido claro, falei do protótipo com intuído de tentar expor melhor a minha dúvida mas o que me incomoda é *inserir de
struct no *inserir (struct no *lista, int n) {

struct no *novo = (struct no*) malloc(sizeof(struct no));
novo->chave = n;
novo->prox = lista;

return novo;
}

O que tá pegando é mais o ponteiro.


Vamos por partes.

Introdução

Considere inicialmente a seguinte declaração simples.

int *ptr_int; 


Nessa declaração, você declara uma variável chamada ptr_int. Qual o tipo dessa variável?

Creio que, mesmo não tendo conhecimento muito avançado, você não terá dificuldade em responder que é “ponteiro para int”.

Agora repare com cuidado na pergunta que eu fiz. O foco da minha pergunta foi a variável que faz parte da declaração. Para respondê-la, você provavelmente olhou a declaração, isolou a parte que contém o nome da variável, e depois olhou em volta para ver as propriedades que o cercam, e constatou que a propriedade tipo é “ponteiro para int”.

Quem privilegia muito esse tipo de visão, centrada na variável e considerando seu tipo como mero atributo secundário, prefere muitas inclusive mudar a posição do espaço na declaração, deixando-a com a seguinte forma.

int* ptr_int;  // ptr_int é um “int*” 


Note que eu só mudei a posição do espaço, de antes do asterisco para depois dele, deixando-o colado no nome do tipo, em vez de no nome da variável. As duas declarações, no entanto, são sinônimas. E esta forma é mais prevalente no mundo C++ do que no mundo C.

Existe porém uma outra pergunta que pode ser feita a respeito de ambas as declarações, e tal pergunta tem a ver com o fato de que o ponteiro para um certo tipo de objeto eventualmente será usado para re referir a um objeto desse tipo. No caso da nossa declaração de exemplo, seria a seguinte pergunta: quem designa o dado final, cujo tipo é int (ou, de modo mais curto, “quem tem o tipo int”)? E a resposta é que “*ptr_int” tem o tipo int.

Antes de prosseguir, eis o que eu quero destacar desta introdução: você pode, tanto na sua mente quanto na forma de se expressar no código, dar ênfase ao ponteiro em si ou dar ênfase ao dado que eventualmente será apontado. Eu acredito que mudar o olhar, de vez em quando, pode ajudar você em algumas situações.

NOTA:
Eu não gosto da segunda forma apresentada acima. Em parte, meu gosto pode ter sido influenciado por eu ter começado com C, que que a primeira forma é tradicionalmente mais usada. Mas há uma aspecto de falso sentido mesmo, que fica mais fácil de ver quando você tem a declaração de múltiplas variáveis na mesma linha. Veja por si mesmo.

int *p1, i1, a1[10];  // p1 é ponteiro para int, i1 é int, a1 é array de int
int* p2, i2, a2[10]; // p2 é ponteiro para int, i2 é int, a2 é array de int


Percebeu? No segundo exemplo, mesmo estando colado na palavra int, e não no nome da variável, o asterisco se refere à primeira variável da lista, não se propagando para as demais. Isso é visualmente aberrante e, por isso mesmo, acho que deve ser totalmente evitado, mesmo que, na ocasião, a ênfase seja no ponteiro, e não no dado por ele referido.



Declaração de Tipos Agregados com Ponteiros

(A ser escriito.)


Funções que Retornam Ponteiros e Ponteiros para Função

(A ser escrito.)


Voltando à Dúvida Original

struct no *inserir(struct no *lista, int n); 


(A ser escrito.)


Paulo, essas questões que você colocou nunca passou pela minha cabeça mas lendo, achei bacana e me identifiquei com você. Também prefiro colocar o * próximo da variável ao invés do tipo. Engraçado, mas eu realmente nunca pensei nisso que você falou mais no meu subconsciente as coisas ficam mais claras dessa forma. Talvez seja pelo fato de eu não possuir nenhum conhecimento em C++. E o mais apavorante é que na maioria das literaturas acontece exatamente o contrário.

Mas deixa eu te perguntar, qual a diferença entra as duas funções abaixo?


struct no *inserir (struct no *lista, int n) {

struct no *novo = (struct no*) malloc(sizeof(struct no));
novo->chave = n;
novo->prox = lista;

return novo;
}



struct no inserir (struct no *lista, int n) {

struct no *novo = (struct no*) malloc(sizeof(struct no));
novo->chave = n;
novo->prox = lista;

return novo;
}




7. Re: Função e ponteiro em C

Paulo
paulo1205

(usa Ubuntu)

Enviado em 16/04/2016 - 22:42h

Sr. F escreveu:

Mas deixa eu te perguntar, qual a diferença entra as duas funções abaixo?

struct no *inserir (struct no *lista, int n) {

struct no *novo = (struct no*) malloc(sizeof(struct no));
novo->chave = n;
novo->prox = lista;

return novo;
}


struct no inserir (struct no *lista, int n) {

struct no *novo = (struct no*) malloc(sizeof(struct no));
novo->chave = n;
novo->prox = lista;

return novo;
}



Desculpe por não ter terminado os blocos “A ser escrito”, mas existe um mundo real fora da Internet, que demanda um bocado de atenção também, e eu não quero sair escrevendo coisas on-line de qualquer maneira, e tentar caprichar na explicação toma muito tempo.

Especificamente sobre a pergunta de agora, parece-me que você cometeu um erro no segundo exemplo, pois você declarou a função retornando um dado do tipo “struct no”, mas mandou retornar um valor cujo tipo é “ponteiro para struct no”. Vou supor, então, que o comando return do segundo exemplo seja “return *no;”, de modo a compatibilizar os tipos, e responder de acordo.

Ao contrário de algumas outras linguagens, C não possui referências. Sempre que você passa parâmetros para uma função ou recebe um valor de retorno de função, você está na verdade recebendo cópias de valores. Veja, por exemplo, o código abaixo.

int f(int n){
return n*n;
}

int main(void){
int a, b;
a=5;
b=f(a);
}


No código acima, quando a função f() é invocada, ela recebe uma cópia do valor do parâmetro a, e esse valor copiado é armazenado no argumento n. Semelhantemente, o valor calculado n*n dentro da função não é diretamente guardado na variável b que espera recebê-lo(*), mas sim copiado para ela após a função terminar.

Sabendo disso, olhe para as duas funções. A primeira função aloca dinamicamente memória para um novo dado do tipo “struct no”, coloca nele os valores passados como parâmetros, e retorna uma cópia do endereço alocado para o novo dado (ou seja: uma cópia do ponteiro para o novo dado). Como você retorna o valor do endereço alocado, quem receber o valor retornado terá, depois, condições de acesso ao dado alocado (e ao restante da lista original, que passou a ser apontada pelo campo prox do novo nó).

A segunda função (com o reparo que eu mencionei acima) também aloca memória dinamicamente para dado do tipo “struct no” e preenche tais dados com valores recebidos pela função como parâmetros. Entretanto, ela retorna uma cópia de dado, não do seu endereço -- quem quer que receba o valor retornado vai copiar os campos internos da estrutura em outra região de memória. O ponteiro que faz referência ao novo dado dinamicamente alocado deixa de existir quando a função acaba, de modo que não será possível desalocá-lo (para desalocar memória dinamicamente alocada, o valor do endereço tem de ser o mesmo daquele recebido na hora da alocação).

---------

(*) Quando falo sobre não haver atribuição direta, refiro-me ao comportamento esperado da linguagem e de uma possível tradução direta para código executável. É possível que compiladores que realizam otimização de código até consigam minimizar o número de cópias. Se o fizerem, no entanto, devem fazê-lo de um modo que não interfira na semântica esperada pelo programador em C.






Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts