fredericopissarra Postado Março 25, 2019 em 19:17 Compartilhar Postado Março 25, 2019 em 19:17 Isto é uma declaração: char *p; // O asterisco diz que a variável p vai ser usada // para conter o endereço de um array de chars. // Ou seja, p é um ponteiro! Já isso é uma expressão: c = *p; // O asteristo é um OPERADOR que usa o endereço // contido em p para ler um caracter contido neste endereço. // É um acesso INDIRETO. Existe, claramente, diferenças entre declarar e usar símbolos em C (e em C++). E todo uso de símbolos é feito por expressões. É possível misturar os dois: int x = 10; // equivalente a: // int x; /* declaração */ // x = 10; /* expressão */ Mas, mesmo assim, são duas coisas separadas. Vejamos outra mistura usando o símbolo p, declarado acima: int *pi = (int *)p; // O mesmo que: // int *pi; /* declaração */ // pi = (int *)p; /* expressão */ Na expressão, temos 2 operadores: O de conversão de tipo (casting) e o de atribuição. Aqui o operador (int *) converte o tipo do ponteiro p para int * (ponteiro para um array de ints) e atribui o conteúdo de p (não de *p -- não tem operação de indireção aqui!) à variável pi, que também é um ponteiro, para um array de tipo diferente... Já a expressão: int x = *(int *)p; Tem 3 operadores (ou operações!): O operador de conversão (int *), como antes, o operador * (de indireção) e o operador = de atribuição. Esse asterisco ai, antes do casting, não é o mesmo que é usado na declaração de p... É uma operação (assim como soma, subtração, multiplicação, divisão, ...). O operador de indireção usa o conteúdo da variável p como endereço de memória.OPA! E se tivermos uma operação de indireção (*) e uma de multiplicação (também *) na mesma expressão? Assim como quanto você tem duas operações de multiplicação e uma de soma, como em x = a * b + c * d, o compilador determinará quais operações devem ser feitas primeiro pela precedência dos operadores. Um operador * depende do uso de uma variável ponteiro e tem precedência maior que o operador * multiplicativo... Assim, numa expressão do tipo: int x; x = 10 * *pi; A primeira sub-expressão a ser resolvida é a que usa * como indireção, depois o segundo * será usado como multiplicação (neste caso): // usando parenteses para mostrar a precedência... x = (10 * (*pi)); PS: A associatividade também deve ser levada em conta... Assim como na expressão x=a*b+c*d a precedência é das multiplicações, a associação é da direita para esquerda, ou seja, (a*b) é feita primeiro, depois (c*d)... No caso de inteiros isso não é importante porque a propriedade comutativa é obedecida sempre, mas em ponto flutuante isso importa (procure pela discussão sobre ponto flutuante aqui no forum!)... O operador [] é um atalho para operações com o operador de indireção Quando você declara e define: char s[5] = "fred"; Como vimos, isso é um atalho... O que fizemos aqui foi declarar um símbolo s que aponta para um array de 5 chars... Yep... s é um ponteiro! Isso é quase similar a fazer: char *s = "fred"; Isso só não é igual porque, no primeiro caso, você está pedindo para o compilador alocar espaço para o array e copiar o array literal contendo 5 chars para dentro dele (incluindo o '\0' final)... No segundo caso você diz para o compilador que s aponta para um array de 5 chars, mas ele é constante, imutável, read-only. Se tentar usar uma expressão como *s='\0', vai tomar um "segmentation fault" na cara! O detalhe é que se ambas as declarações declaram ponteiros, a expressão s[n] é a mesma coisa que *(s+n). Ou seja, o operador [] é um atalho para uma expressão usando o operador de indireção! Isso causa o interessante efeito colateral de que não precisarmos usar o operador [] numa ordem particular de ponteiro[índice]. Ao invés de escrevermos s[n] poderíamos escrever n, porque *(s+n) é a mesma coisa que *(n+s). Se um ponteiro é um valor inteiro "comum", porque o tipo na declaração? Lembre-se que te disse que um ponteiro aponta para um array. Um array tem um tipo associado porque o compilador precisa saber onde começa e termina cada um dos seus elementos... Num array de char, cada item tem 1 byte de tamanho. Num array de int, cada item tem 4 bytes... No caso de um ponteiro, o tipo serve para que o compilador saiba como vai fazer as operações aritméticas com o valor contido no ponteiro... Por exemplo: int a[3] = { 1, 2, 3}; int *p = a; // p = endereço do array a (ou a[0]). p++; // qual é o endereço contido em p agora? Vamos considerar do início do array como sendo 0x400000. Este é, obviamente, o endereço do elemento de índice 0, ou a[0] e o colocamos na variável p., simplesmente aproveitando-nos do fato de que o nome do array é o ponteiro para o primeiro elemento. Poderíamos fazer p = &a[0], que é a mesma coisa... Mas, depois de incrementar p, qual será o endereço contido nele? Não pode ser 0x400001 porque um int tem 4 bytes de tamanho. Ou seja, ao incrementar p, que foi declarado como um ponteiro para um array de int, o compilador sabe que deve, nas adições e subtrações envolvendo um ponteiro desse tipo, multiplicar o offset por 4 (ou sizeof(int)). Este é o motivo do tipo estar associado ao ponteiro.O que significa dois ou mais asteríscos na "declaração"? Ou, o que é: char **pp; Se um asterisco diz ao compilador que a variável contém o endereço de um array do tipo especificado, dois asteriscos diz que a variável contém o endereço de um array de endereços para arrays do tipo especificado (ou, ele contém o endereço de um array de endereços para arrays do tipo). Esse ponteiro é usado em uma dupla indireção... Três asteriscos (bem raro) é o endereço de um array de endereços para arrays de endereços para o tipo... Em teoria você pode ter tantas indireções quanto quiser, mas, é claro, isso vai ficando cada vez mais complicado de entender... Na prática, nunca vi mais que 3 níveis! Graças a esses níveis de indireção é que tanto faz declarar main() em qualquer uma dessas formas: int main(int argc, char *argv[]); int main(int argc, char **argv); Ambos os argv são ponteiros para arrays de ponteiros para chars. Neste caso, os argumentos são diretamente equivalentes porque não são declarações usadas para alocação de espaço, mas para estipular o tipo de argumento... Claro, esses argumentos serão convertidos em variáveis locais da função, mas mesmo assim, a alocação será feita na pilha, que nunca é read-only... Link para o comentário Compartilhar em outros sites More sharing options...
fredericopissarra Postado Março 25, 2019 em 19:52 Autor Compartilhar Postado Março 25, 2019 em 19:52 E, sim... uma chamada de função é uma expressão! Todo operador () que seja precedido por um símbolo ou ponteiro é uma chamada de função: Considere as declarações e usos: // DECLARAÇÃO de DEFINIÇÃO de uma função. int f(int x) { return 2*x; } int (*fptr)(int); // DECLARAÇÂO de um ponteiro para uma função. fptr = f; // EXPRESSÃO que inicializa o ponteiro fptr para o endereço da função f. x = f(2); // EXPRESSÃO: () é precedido do símbolo f, daí é uma subexpressão de chamada de função. y = fptr(2); // EXPRESSÃO: () é precedido do ponteiro para uma função, daí é uma subexpressão de chamda de função. fptr2 = (fptr + 1); // EXPRESSÃO: () NÃO é precedido de símbolo ou ponteiro, daí é um operador de resolução de precedência... :) Lembre-se... C trabalha sempre com DECLARAÇÃOES e USOS... as duas coisas são diferentes... Link para o comentário Compartilhar em outros sites More sharing options...
Posts Recomendados
Arquivado
Este tópico foi arquivado e está fechado para novas respostas.