Aprenda C

  • Página Inicial
  • Contato!
  • Tudo sobre C Parte 1!
  • Tudo sobre C Parte 2!
  • Tudo sobre C Parte 3!
  • Tudo sobre C Parte 4!
  • Tudo sobre C Parte 5!
  • Tudo sobre C Parte 6!
  • Tudo sobre C Parte 7!
  • Tudo sobre C Parte 8!
  • Tudo sobre C Parte 9!
  • Tudo sobre C Parte 6

    Mais Sobre Ponteiros

    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;
    }
    
    

    Aritmética de Ponteiros

    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);
    }
    
    

    Uso de Funções com Ponteiros

    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", &param);
        printf("Conteúdo de 2: %d\n", param);
    }
    
    

    Alocação Dinâmica de Memória

    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);