Ao fazer nova atribuição com variável com ponteiro, alteramos o conteúdo da variável apontada, por exemplo:
int numero = 40;
int *n;
n = №
printf("%d\n", numero); // Imprime o valor
printf("%d\n", *n); // Imprime o valor
*n = 50; // Alterando o conteúdo da variável apontada elemento, e não do ponteiro.
printf("%d\n", numero); // Aqui foi alterado
printf("%d\n", *n); // Mostra o valor alterado
Podemos manipular também arrays por ponteiros, por exemplo:
int *apont;
int pares[5] = {0, 2, 4, 6, 8};
apont = &pares[0];
// Pode ser assim também:
// apont = pares;
printf("Endereço: %p\n", apont);
printf("Conteúdo: %d\n", *apont);
No caso acima, o elemento pares[0] estará em um endereço específico, e no próximo endereço está o pares[1], depois pares[2] e etc., até o final do vetor. A regra do endereço é pelos bytes de cada tipo, seguindo uma sequência tipo 1, 2, 3 ou 4, 8, C, sempre em hexadecimal.
int *apont;
int pares[5] = {0, 2, 4, 6, 8};
int cont = 0;
while(cont < 5) {
apont = &pares[cont];
printf("Endereço: %p\n", apont);
printf("Conteúdo: %d\n", *apont);
printf("-----------------\n");
cont++;
}
Podemos manipular ponteiros em funções também, independente do escopo das variáveis, veja um exemplo de função sem ponteiros:
void somar(int var, int valor); // Não esqueça do protótipo
int main() {
int num = 0;
somar(num, 15);
printf("%d\n", num); // Não será alterado
return 0;
}
void somar(int var, int valor) {
var += valor;
}
No caso acima, ele continuará retornando 0 por causa do escopo das variáveis, que foram criadas em endereços diferentes.
Para utilizar sempre o mesmo endereço, precisaremos criar ponteiros, no caso, serão nas variáveis var dos parâmetros da função, e indicar o endereço de num, que ficará assim:
void somar(int *var, int valor); // Não esqueça do protótipo
int main() {
int num = 0;
somar(&num, 15);
printf("%d\n", num); // Não será alterado
return 0;
}
void somar(int *var, int valor) {
*var += valor;
}
Com arrays, não precisaremos indicar o endereço na invocação da função e nem ponteiros nas posições dos vetores, apenas nos parâmetros, dessa forma:
void iniVet(int *v);
int main() {
int vetor[5];
iniVet(vetor);
for(int i = 0; i < 5; i++) {
printf("%d\n", vetor[i]);
}
return 0;
}
void iniVet(int *v) {
v[0] = 1;
v[1] = 2;
v[2] = 3;
v[3] = 4;
v[4] = 5;
}
A função que recebe o vetor como parâmetro poderia ser declarada também como void iniVet(int v[])
.
Para entendermos que o uso de ponteiros e referência independe do escopo, veja esse código aqui sem ponteiros e referências:
int testar(int n1, int n2);
int main() {
setlocale(LC_ALL, "portuguese");
int n1 = 10;
int n2 = 20;
printf("Valores antes de chamar a função: ");
printf("n1 = %d e n2 = %d.\n", n1, n2);
testar(n1, n2);
printf("Valores depois de chamar a função: ");
printf("n1 = %d e n2 = %d.\n", n1, n2);
return 0;
}
int testar(int n1, int n2) {
n1 = -1;
n2 = -2;
printf("Valores dentro da função testar: ");
printf("n1 = %d e n2 = %d.\n", n1, n2);
return 0;
}
Agora o mesmo com os ponteiros e referências:
int testar(int *n1, int *n2);
int main() {
setlocale(LC_ALL, "portuguese");
int n1 = 10;
int n2 = 20;
printf("Valores antes de chamar a função: ");
printf("n1 = %d e n2 = %d.\n", n1, n2);
testar(&n1, &n2);
printf("Valores depois de chamar a função: ");
printf("n1 = %d e n2 = %d.\n", n1, n2);
return 0;
}
int testar(int *n1, int *n2) {
*n1 = -1;
*n2 = -2;
printf("Valores dentro da função testar: ");
printf("n1 = %d e n2 = %d.\n", *n1, *n2);
return 0;
}
Há duas operações aritméticas que podemos efetuar com ponteiros: adição de ponteiros e subtração de ponteiros. Além disso, também é possível comparar dois ponteiros entre si.
O tamanho de uma variável é um aspecto muito importante para o programador. Precisamos saber quanto espaço na memória uma variável ocupa quando é utilizada, para que seja possível escrever programas enxutos e que executem com boa performance. Isso é particularmente importante quando desenvolvemos programas para dispositivos embarcados, os quais, no geral, contam com quantidade restrita de memória disponível.
Dependendo da plataforma, uma variável pode ter tamanhos que variam de 8 a 64 bits. Por exemplo, em uma plataforma de 32 bits, uma variável do tipo int ocupará o espaço de 4 bytes (32 / 8 = 4).
O código a seguir nos permite verificar o tamanho na memória utilizado por ponteiros de diferentes tipos, usando o operador sizeof:
int main() {
int inteiro;
char caractere;
double duplo;
int *iPtr;
char *cPtr;
double *dPtr;
iPtr = &inteiro;
cPtr = &caractere;
dPtr = &duplo;
printf("Tamanho de um ponteiro de inteiro = %d, valor = %p\n", sizeof(iPtr), iPtr);
printf("Tamanho de um ponteiro de char = %d, valor = %p\n", sizeof(cPtr), cPtr);
printf("Tamanho de um ponteiro de double = %d, valor = %p\n", sizeof(dPtr), dPtr);
}
Não podemos efetuar outras operações com ponteiros além da adição e subtração, com multiplicação e divisão, e nem podemos usar operadores de deslocamento bit a bit.
Vejamos um código de exemplo que mostra as operações de adição e subtração de ponteiros:
int main() {
int a;
a = 30;
int *pont1;
pont1 = &a;
printf("Endereço de a: %p\n", pont1);
pont1++;
printf("Endereço do ponteiro incrementado: %p\n", pont1);
pont1 = pont1 + 10;
printf("Somando 40 ao ponteiro pont1: %p\n", pont1);
pont1--;
printf("Decrementando o ponteiro: %p\n", pont1);
}
Podemos efetuar a comparação de ponteiros usando os operadores de comparação padrão – apesar de esta técnica não ter tanta utilidade na prática. O código a seguir mostra a comparação entre ponteiros:
int main() {
int vetor[3] = {10, 30, 20};
int *p0 = vetor;
int *p1 = vetor + 1;
int *p2 = vetor + 2;
int *p3 = p2;
printf("p2 > p0: %d\n", p2 > p0);
printf("p0 > p1: %d\n", p0 > p1);
printf("p2 < p0: %d\n", p2 < p0);
printf("p3 == p2: %d\n", p3 == p2);
}
Ele retornará 1 pra comparações verdadeiras e 0 pra falsas.
Outro exemplo, mais complexo:
int pares[5] = {0, 2, 4, 6, 8};
int *ponteiro;
ponteiro = pares; // O mesmo que &pares[0]
printf("Endereço do vetor: %p\n", pares);
for(int i = 0; i < sizeof(pares) / sizeof(int); i++) {
printf("Conteúdo do %dº índice do vetor: %d\n", i + 1, pares[i]);
}
for(int i = 0; i < sizeof(pares) / sizeof(int); i++) {
printf("Endereço do %dº índice do vetor: %p\n", i + 1, &pares[i]);
}
for(int i = 0; i < sizeof(pares) / sizeof(int); i++) {
printf("Endereço apontado pelo %dº incremento do ponteiro: %p\n", i + 1, ponteiro++);
}
for(int i = 0; i < sizeof(pares) / sizeof(int); i++) {
ponteiro = &pares[i];
printf("Conteúdo apontado pelo %dº incremento do ponteiro: %d\n", i + 1, *ponteiro);
}
Uma função pode retornar um número inteiro, um real e um caractere, assim como também pode retornar um vetor. Para isso, devemos utilizar ponteiros (ou apontador). A única forma de retornar um vetor é por meio de um ponteiro, pois não é possível criar funções como por exemplo, int[10] calcular()
, onde int[10] quer dizer que a função retorna um vetor com 10 posições.
A seguir veja um exemplo de uso desse recurso através de uma função, que cria um vetor de dez posições e os preenche com valores aleatórios, imprime os valores, e posteriormente passa esse vetor para quem
chamar a função:
int *gerarRandomico();
int main() {
int *p;
int i;
p = gerarRandomico();
for(i = 0; i < 10; i++) {
printf("p[%d] = %d\n", i, p[i]);
}
return 0;
}
int *gerarRandomico() {
static int r[10];
int a;
for(a = 0; a < 10; a++) {
r[a] = rand() % 100;
printf("r[%d] = %d\n", a, r[a]);
}
return r;
}
PS: O modificador static
indica que a variável tem suas mudanças permanentes no programa todo.
Ao passar um ponteiro por um parâmetro, siga essas regras:
void funcao1(int *param);
void funcao2(int param);
int main() {
int n = 10;
int *p;
p = &n;
funcao1(p); // Caso a função tenha * e a variável de entrada seja ponteiro
funcao2(*p); // Caso a função não tenha * e a variável de entrada seja ponteiro
funcao1(&n); // Caso a função tenha * e a variável de entrada não seja ponteiro
funcao2(n); // Casos mais comuns, onde nem a função e nem a variável são ponteiros
return 0;
}
void funcao1(int *param) {
printf("Endereço de 1: %p\n", param);
printf("Conteúdo de 1: %d\n", *param);
}
void funcao2(int param) {
printf("Endereço de 2: %p\n", ¶m);
printf("Conteúdo de 2: %d\n", param);
}
A alocação de memória é útil para reservar a quantidade necessária de memória RAM para o nosso programa, sem desperdício nem falta.
Veja um programa sem alocação:
#include <stdio.h>
#include <stdlib.h>
int main() {
char nome[50];
printf("Digite o nome: ");
scanf("%s", &nome);
printf("%s", nome);
return 0;
}
Mas tem um problema, caso colocarmos um nome com espaço, ele imprimirá apenas o primeiro nome, já que o char ignora tudo que vem após espaços (espaço é considerado caractere terminador).
Para contornar isso, usaremos o fgets, dessa forma (importe stdio.h):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
char nome[50];
printf("Digite o nome: ");
fgets(nome, 50, stdin); // Importe stdio.h
// Isso é pra remover a quebra de linha:
size_t len;
len = strlen(nome); // Inclua string.h
if(nome[len - 1] == '\n') nome[--len] = 0;
printf("%s\n", nome);
return 0;
}
Mas até então, não mexemos com alocação de memória.
O problema da alocação, está no nosso array de char, que reservou espaço para 50 caracteres na memória, e ao colocar um nome com menos caracteres, teremos espaços sendo perdidos e ocupados inutilmente (no caso, seriam sempre 50 bytes), e se colocarmos algo com mais de 50 caracteres, ele ocupará mais espaço do que o reservado, que pode ser um problema para o sistema operacional.
Para trabalharmos com alocação de memória, precisaremos usar a função malloc, que necessita da biblioteca stdlib.h. Teremos também que transformar a variável char num ponteiro, sem indicar o número de espaços, dessa forma:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
char *nome;
nome = (char*)malloc(sizeof(char));
printf("Digite o nome: ");
fflush(stdin); // Isso é apenas para evitar que os dados inseridos não sejam lidos.
fgets(nome, sizeof(nome), stdin); // Importe stdio.h
// Isso é pra remover a quebra de linha:
size_t len;
len = strlen(nome); // Inclua string.h
if(nome[len - 1] == '\n') nome[--len] = 0;
printf("%s\n", nome);
return 0;
}
Pode ver que acima, precisamos colocar um ponteiro em char, e atribuir à variável a função malloc, que também precisou de um typecast com ponteiro para retornar um valor. A função sizeof retorna o tamanho da variável armazenada.
PS: Há quem coloque mais alguma multiplicação ou soma dentro do malloc para reservar um espacinho a mais na memória, mas cuidado pra não desperdicar espaço demais, prefira a soma nesse caso, assim:
nome = (char*)malloc(sizeof(char) + 1);
Ou podemos criar uma nova realocação após usar o malloc com o realloc
, assim:
nome = (char*)realloc(nome, sizeof(char) + 4);
Para excluir um ponteiro ou memória alocada, use o free, dessa forma:
free(nome);