Mandar um cafézinho para o programador:


Me ajude a transformar café em código!
Mostrando postagens com marcador Ponteiros. Mostrar todas as postagens
Mostrando postagens com marcador Ponteiros. Mostrar todas as postagens

Arrays e Ponteiros de Structs em C++


 Neste tutorial de nossa apostila de C++, vamos aprender como trabalhar tanto com arrays como com ponteiros, usando structs.

Array de Structs

Assim como qualquer outro tipo de variável, o tipo que você definiu, usando structs, pode ser declarado na forma de array, ou seja, várias instâncias de structs.

Nos tutoriais anteriores, estamos usando a struct Student, instanciando apenas um aluno para nosso estudo. Mas qual escola, faculdade ou curso que você conhece que só tem um aluno? O normal é ter 20, 30, 50...

Então vamos lá, para declarar 50 structs do tipo Student, basta fazer:

  • Student alumns[50];

E prontinho, está declarado seu array de nome 'alumns', do tipo 'Student', com 50 elementos, onde o índice varia de 0 até 49.

Para acessar os nomes dos alunos, basta acessar as variáveis:

  • alumn[0].name
  • alumn[1].name
  • alumn[2].name
  • ...
  • alumn[49].name

Para acessar as notas de matemática dos alunos, basta acessar as variáveis:

  • alumn[0].math
  • alumn[1].math
  • alumn[2].math
  • ...
  • alumn[49].math

Podemos também, se quisermos, declarar e já inicializar as structs do array, por exemplo, para 3 alunos:
Student alumns[3] = {
                                    {"Neil Peart", 10, 10, 10},
                                    {"Geddy Lee", 9.1, 9.5, 9.2},
                                    {"Alex Lifeson", 2.5, 3.5, 4.7},
                                 };

Ponteiros para Structs

E, por fim, também podemos criar ponteiros especiais, que apontam para structs!
Por exemplo, para criar um ponteiro do tipo Student e de nome 'alumn':
  • Student *alumn;
Prontinho, temos um ponteiro do tipo Student. Agora vamos criar uma instância dessa struct:
  • Student s = { "Neil Peart", 10.0, 10.0, 10.0 };
Podemos fazer:
  • alumn = &s;

E prontinho, nosso ponteiro está apontando para uma variável do tipo Student. Podemos trabalhar e alterar essa instância através do ponteiro, se desejarmos.

Vamos mudar o nome do aluno, através do ponteiro, basta fazer:
  • *alumn.name = "Alex Lifeson";
Certo?
Errado!

O operador ponto . tem precedência ao operador *, logo, você vai estar acessando a referência para o membro 'alumn.name' e não para o ponteiro 'alumn'.

Assim, o correto é:
  • (*alumn).name = "Alex Lifeson";
Porém, fica muito feio e trabalhoso escrever desse jeito aí. Então, criaram um operador especial para usar em ponteiros de structs. A linha de código acima pode ser escrita assim:
  • alumn->name = "Alex Lifeson";
Prontinho. Usando o operador -> significa que estamos acessando o conteúdo do local para onde o ponteiro está apontando.

O código a seguir declara um array com 3 elementos do tipo Student, preenche seus campos um por um com um laço while, usando ponteiros, e depois exibe os dados dos alunos:
#include <iostream>
using namespace std;

int main()
{
    Student s[3], *ptr_s;
    int i;

    for(i=0 ; i<3 ; i++)
    {
        ptr_s=&s[i];

        cout << "Nome do aluno "<<i+1<<": ";
        cin.getline(ptr_s->name, 50);

        cout << "Nota de Matemática do aluno "<<i+1<<": ";
        cin >> ptr_s->math;

        cout << "Nota de Física do aluno "<<i+1<<": ";
        cin >> ptr_s->physics;

        cout << "Nota de Ciências do aluno "<<i+1<<": ";
        cin >> ptr_s->science;

        cin.ignore();
        cout << endl;
    }

    cout << endl;

    for(i=0 ; i<3 ; i++)
    {
        ptr_s=&s[i];
        cout << "Aluno "<<i+1<<": "<<ptr_s->name<<endl;
        cout << "Nota em Matemática "<<i+1<<": "<<ptr_s->math<<endl;
        cout << "Nota em Física "<<i+1<<": "<<ptr_s->physics<<endl;
        cout << "Nota em Ciências "<<i+1<<": "<<ptr_s->science<<endl;
        cout << endl;
    }

    return 0;
}

Alocação dinâmica de objetos com ponteiros: O operador new e delete

 Neste tutorial de nosso Curso de Orientação a Objetos em C++, vamos aprender a usar ponteiros com objetos, e ver uma nova maneira de alocar memória.


Ponteiros de Objetos

Inicialmente, vamos criar um classe, chamada Student, que vai ter duas variáveis (math e english), que vai armazenar as notas do aluno. Também vai ter a get and set, e uma chamada getAverage() para calcular a média de cada aluno.

Nossa classe, toda completinha e bonitinha fica assim:

class Student
{
    private:
        double math, english;

    public:
        double getAverage() const;
        void setMath(double);
        void setEnglish(double);
        double getMath() const;
        double getEnglish() const;
};

double Student::getAverage() const
{
    return (getMath() + getEnglish())/2;
}

void Student::setMath(double m)
{
    math = m;
}
void Student::setEnglish(double e)
{
    english = e;
}
double Student::getMath() const
{
    return math;
}
double Student::getEnglish() const
{
    return english;
}

Vamos agora declarar um ponteiro de nome 'ptr' do tipo Student:

  • Student *ptrStudent
Agora vamos declarar de fato um objeto da classe Student:
  • Student s1;
Podemos associar o ponteiro com o objeto da seguinte maneira:
  • ptrStudent = &s1
    (não esqueça o &, lembre-se que ponteiros armazenam endereços de memória, e ao colocarmos o & antes de uma variável, ele fornece o endereço daquela variável)

Trabalhando com ponteiros com Classes e Objetos: ->

Como já estudando ponteiros, vimos que eles podem fazer mil maravilhas.
Pode exemplo, no exemplo anterior, para o ponteiro acessar os elementos de um objeto, devemos usar o operador ->

Vamos definir duas notas para um aluno:

ptrStudent->setMath(8);
ptrStudent->setEnglish(10);

E para sabermos a média, usando ponteiro, basta fazer:
ptrStudent->getAverage()

Nosso código completo fica:
#include <iostream>
using namespace std;

class Student
{
    private:
        double math, english;

    public:
        double getAverage() const;
        void setMath(double);
        void setEnglish(double);
        double getMath() const;
        double getEnglish() const;
};

double Student::getAverage() const
{
    return (getMath() + getEnglish())/2;
}

void Student::setMath(double m)
{
    math = m;
}
void Student::setEnglish(double e)
{
    english = e;
}
double Student::getMath() const
{
    return math;
}
double Student::getEnglish() const
{
    return english;
}

int main()
{
    Student *ptrStudent;
    Student s1;

    ptrStudent = &s1;
    
    ptrStudent->setMath(8);
    ptrStudent->setEnglish(10);

    cout << ptrStudent->getAverage()<<endl;

    return 0;
}
Note que usamos apenas um aluno, poderíamos ter criado mil alunos e ir fazendo o ponteiro apontar para cada um desses alunos, e o código seria sempre o mesmo, por exemplo:
Student s1, s2, s3, s4;

Faz: ptrStudent = &s1
Depois: ptrStudent = &s2
Depois: ptrStudent = &s3
Depois ptrStudent = &s4

E prontinho, depois é só usr
ptrStudent->setMath();
ptrStudent->setEnglish();
ptrStudent->getAverage()

Isso evita muito escrever código à toa.

Alocando espaço para objetos: Operador new

Existe uma nova maneira de instanciar objetos, é a partir do operador new.
Primeiro você declara seu ponteiro:
  • MyClasse *ptr;
Nesse ponto, temos um ponteiro que serve para apontar para um objeto? Que objeto é esse? Vamos criar ele agora, da seguinte maneira:
  • ptr = new MyClasse

Deletando objetos - delete

Quando criamos um objeto, via instanciamento normal, ele vai sempre existir e ocupar um espaço na memória. Uma das vantagens de fazer a alocação dinâmica de memória, através do new, é que podemos destruir esse ponteiro, destruindo esse objeto e liberando espaço na memória, para isto, basta usar o operador delete:
  • delete ptr;

E prontinho, temos um espaço livro na máquina. Isso pode parecer bobeira agora, mas quando você criar sistemas super complexos que exigem o máximo de eficiência e o menor uso possível de memória (como para programar um relógio de pulso ou microondas, que tem pouca memória), é uma técnica importantíssima.

Veja o código abaixo, apenas com um ponteiro, preenchemos a nota de dois alunos e calculamos sua média, e tudo isso apenas como um objeto/ponteiro da classe Student. Veja como ficou o código:

#include <iostream>
using namespace std;

class Student
{
    private:
        double math, english;

    public:
        double getAverage() const;
        void setMath(double);
        void setEnglish(double);
        double getMath() const;
        double getEnglish() const;
};

double Student::getAverage() const
{
    return (getMath() + getEnglish())/2;
}

void Student::setMath(double m)
{
    math = m;
}
void Student::setEnglish(double e)
{
    english = e;
}
double Student::getMath() const
{
    return math;
}
double Student::getEnglish() const
{
    return english;
}

int main()
{
    Student *ptrStudent;
    ptrStudent = new Student;

    ptrStudent->setMath(8);
    ptrStudent->setEnglish(10);
    cout << "Media do Estudante 1: " <<ptrStudent->getAverage()<<endl;

    ptrStudent->setMath(6);
    ptrStudent->setEnglish(7);
    cout << "Media do Estudante 2: " <<ptrStudent->getAverage()<<endl;

    delete ptrStudent;

    return 0;
}

Alocação Dinâmica de Memória e o Operador new e delete

Neste tutorial, vamos aprender uma nova maneira de declarar variáveis, usando o operador new e ponteiros, em C++.

Alocação Dinâmica de Memória

Vamos supor que você vai fazer um software para a multinacional que você trabalha.
Ela tem 1000 empregados.

Então, você vai declarar, dentre várias coisas, mil variáveis do tipo int para armazenar a idade de cada pessoa, mil variáveis do tipo double para armazenar o salário de cada um, mil strings para armazenar o nome de cada um, etc, etc.

Porém, sua empresa cresceu, e agora tem 2 mil funcionários.
E agora, volta lá no código e muda tudo?

Até o momento, em nosso curso de C++, declaramos valores fixos, valores exatos de blocos de memória, mas isso nem sempre é interessante, no mundo da computação. Muitas vezes você não sabe quantas variáveis vai precisar usar, logo não sabe quantas variáveis vai precisar declarar.

Por exemplo, se você for criar uma nova rede social, quanto espaço de memória vai precisar para armazenar os dados dos usuários? Ué, não sabe...afinal, pode ter mil ou 1 milhão de usuários, quem sabe.

O que você tem que fazer é, então, alocar memória dinamicamente. Ou seja, vai alocar a medida que for precisando.

Ponteiros e o Operador new

Vamos supor que você queira alocar espaço para uma variável do tipo double.
Você vai pedir, via código C++, para o computador que ele ache um bloco de memória na máquina que seja possível alocar para você, que esteja livre.

Ele então vai lá vasculhar no seu sistema, acha um bloco e te devolve o endereço inicial desse bloco, o endereço do primeiro byte. Ora, se ele te devolve um endereço, o que você precisa para guardar esse tipo de informação?

Sim, nosso amado tipo de ponteiro.

Esse pedido de alocação é feito com o operador new.
Veja um exemplo de código que alocamos um bloco de memória para uma variável do tipo double:
#include <iostream>
using namespace std;

int main()
{
    double *ptr;
    ptr = new double;
    *ptr = 3.14;

    cout << *ptr << endl;

    return 0;
}
Quando digitamos 'new double', a máquina devolve um endereço de memória, o primeiro do bloco reservado para o double, e armazena esse endereço no ponteiro 'ptr', que obviamente deve ser do tipo double também (ou seja, aponta para o endereço inicial de um bloco de 8 Bytes, espaço necessário para um double).

Veja, não definimos nenhuma variável puramente do tipo double. Temos apenas um ponteiro que aponta para um bloco que tem um valor do tipo double.

Podemos, inclusive alterar esse valor armazenado, usando *ptr:
#include <iostream>
using namespace std;

int main()
{
    double *ptr;
    ptr = new double;
    cin >> *ptr;

    cout << *ptr << endl;

    return 0;
}
Tudo que poderíamos fazer com uma variável do tipo double, podemos fazer com esse ponteiro.

Podemos também alocar arrays inteiros, com o operador new, veja:
#include <iostream>
using namespace std;

int main()
{
    int *ptr;
    ptr = new int[10];

    for(int aux=0 ; aux<10 ; aux++){
        ptr[aux] = aux+1;
    }

    for(int aux=0 ; aux<10 ; aux++){
        cout << ptr[aux] << endl;
    }

    return 0;
}
No código acima, alocamos um array de 10 inteiros, e preenchemos com os números de 1 até 10.
Ou seja, no primeiro exemplo, ptr 'virou' um double. Nesse exemplo de cima, ptr 'virou' um array de inteiros.

Flexíveis esses ponteiros, não são?

Liberando espaços de memória - Operador delete

Outra vantagem de usar alocação de memória dinamicamente, é a possibilidade de liberar também essa memória que foi alocada, bastando usar o operador delete.

No primeiro exemplo, desalocamos o bloco de memória assim:
#include <iostream>
using namespace std;

int main()
{
    double *ptr;
    ptr = new double;
    cin >> *ptr;

    cout << *ptr << endl;

    delete ptr;

    return 0;
}
No caso dele ser um array, colocamos o par de colchetes [ ] antes do nome do ponteiro:
#include <iostream>
using namespace std;

int main()
{
    int *ptr;
    ptr = new int[10];

    for(int aux=0 ; aux<10 ; aux++){
        ptr[aux] = aux+1;
    }

    for(int aux=0 ; aux<10 ; aux++){
        cout << ptr[aux] << endl;
    }

    delete [] ptr;

    return 0;
}
Isso pode parecer bobagem hoje em dia, se imaginar seu PC com vários e vários Terabytes, não é preciso ficar liberando memória alocada, durante a execução de um programa.

Porém, esse conhecimento e prática é essencial em sistemas críticos que consomem muita memória, como em um jogo de altíssimo nível ou em um sistema operacional, super eficiente, não pode deixar tudo alocado pra sempre, tem que ir liberando aos poucos.

Já em sistemas com pouco espaço de memória (como seu relógio digital, o painel de uma geladeira ou o timer de seu microondas), é vital ir desalocando e liberando memória, pois esse recurso é super escasso.

Estudaremos com mais detalhes o operador new quando estudarmos Classes e Objetos, em Programação Orientada à Objetos.

Comparação de Ponteiros e Ponteiro para Constantes

Neste tutorial, vamos aprender como fazer comparação entre ponteiros bem como usar variáveis constantes com eles.

Comparação entre ponteiros em C++

Do mesmo jeito que podemos comparar duas variáveis quaisquer (como int e double), também podemos comparar ponteiros.

E usando os mesmo operadores: > , >=, <, <=, == e !=

Dizemos, por exemplo, que um ponteiro é maior que outro, quando, por exemplo, seu endereço de memória em um array aponta para uma variável cujo índice é maior que outro.

Por exemplo:
int *ptr1 = array[0];
int *ptr2 = array[1];

Então, a comparação: ptr2 > ptr1 vai resultar em um resultado verdadeiro.
Já: ptr1 == ptr2 vai resultar em um resultado falso, pois apontam para endereços de memória diferente.

Ou seja, ponteiros são variáveis que armazenam endereços de memória.
Então, ao comparar dois ponteiros, estamos comparando dois endereços de memória, e não os valores para qual eles apontam, ok ?

Ponteiros e const C++

Sempre que usamos a palavra-chave const, estamos dizendo ao compilador que o valor armazenado naquela variável não deve ser alterado.

Muitas vezes, queremos passar uma variável como informação, mas não queremos que ela seja modificada de maneira alguma, nesses casos, é importante usar a keyword const.

Até o momento, usamos ponteiros não-constantes que apontam para variáveis não-constantes, ou seja, podemos mudar até o valor da variável via ponteiro que aponta para ela.

Você não pode, por exemplo, passar um ponteiro, que não é constante, para uma função que espera uma variável constante como argumento. Veja:
#include <iostream>
using namespace std;

int main()
{
    const double pi = 3.14;
    const double *ptr = &pi;

    cout << *ptr << endl;

    return 0;
}
Para apontarmos para uma variável constante (pi), tivemos que definir um ponteiro constante (const double *).
Tente tirar o 'const' da declaração do ponteiro, e veja o que acontece.

Poderemos, porém, ter um ponteiro constante que aponta para uma variável que não é constante:
#include <iostream>
using namespace std;

int main()
{
    double pi = 3.14;
    double * const ptr = &pi;

    cout << *ptr << endl;

    return 0;
}
A diferença é que esse ponteiro vai apontar SEMPRE para o mesmo endereço de memória. Você pode até alterar o valor para qual ele aponta. Mas jamais vai alterar o endereço para qual ele aponta. Ponteiros do tipo constante devem ser inicializados ao serem declarados.

Por fim, poderemos ter um ponteiro constante, que aponta para uma variável constante:
#include <iostream>
using namespace std;

int main()
{
    double pi = 3.14;
    const double *const ptr = &pi;

    cout << *ptr << endl;

    return 0;
}
Note que, neste caso, não podemos alterar o endereço do ponteiro (não podemos fazer ptr = &outra_variavel) e nem podemos alterar o valor armazenado no local que o ponteiro aponta (*ptr = 21.12)

Esses casos de constante e ponteiros, bem como de comparação entre ponteiros, é muito usado no estudo de strings, por exemplo.

Ponteiros, Arrays e Aritmética

Dando continuidade ao estudo dos ponteiros, vamos ver a importante relação deles com os arrays, bem como aprender a manipular aritmeticamente os ponteiros, com operações matemáticas.

Arrays e Ponteiros, Ponteiros e Arrays

Por curiosidade, vamos declarar um array de inteiros, de nome 'numbers', inicializar e depois simplesmente imprimir o valor '*numbers':
#include <iostream>
using namespace std;

int main()
{
    int numbers[]={1, 2, 3, 2112};

    cout << *numbers << endl;

    return 0;
}
Olha que interessante o resultado:
Curso de C++ online grátis, com apostila para download

Ou seja: o nome do array funciona como um ponteiro.
E para onde ele aponta? Para o primeiro elemento do array.

Assim, sempre que tivermos um array de nome: arr
Se usarmos o nome da variável do array, ela vai se comportar como um array que aponta para: arr[0]

E para apontar para os demais membros do array?
Lembre-se da seguinte regra:
  • arr[indice] = *(arr + indice)

Ou seja:
arr[0] pode ser referenciado por *(arr + 0)
arr[1] pode ser referenciado por *(arr + 1)
arr[2] pode ser referenciado por *(arr + 2)
...
arr[n] pode ser referenciado por *(arr + n)

Podemos fazer um ponteiro de nome 'ptr' apontar para o primeiro elemento de um array das seguintes formas:
  1. int *ptr = numbers;
  2. int *ptr = &numbers[0];

Vamos imprimir todo um array, usando apenas um ponteiro:
#include <iostream>
using namespace std;

int main()
{
    int numbers[6]={1, 2, 3, 4, 5, 2112},
        *ptr = numbers;

    for(int aux=0 ; aux<6 ; aux++)
        cout << *(ptr+aux) << endl;

    return 0;
}

Aritmética dos Ponteiros

No exemplo de código C++ anterior, você viu que fizemos várias vezes uma operação de adição com ponteiros: ptr+aux, onde aux é uma variável inteira que foi de 0 até 5, para percorrer o array.

Poderíamos ter feito o mesmo programa com o operador ++, veja:
#include <iostream>
using namespace std;

int main()
{
    int numbers[6]={1, 2, 3, 4, 5, 2112},
        *ptr = numbers;

    for(int aux=0 ; aux<6 ; aux++){
        cout << *ptr << endl;
        ptr++;
    }

    return 0;
}
Ou seja, cada vez que adicionamos uma unidade ao ponteiro (++), ele aponta para o próximo elemento do array. Podemos fazer o inverso, apontar para o último elemento do array e irmos decrementando:
#include <iostream>
using namespace std;

int main()
{
    int numbers[6]={1, 2, 3, 4, 5, 2112},
        *ptr = &numbers[5];

    for(int aux=5 ; aux>=0 ; aux--){
        cout << *ptr << endl;
        ptr--;
    }

    return 0;
}
Poderíamos usar também os operadores -= ou +=

A lógica é a seguinte.
Quando apontamos o ponteiro para o array, ele vai apontar para o endereço de memória do primeiro elemento. Vamos supor que seja o bloco de memória 1000.

Quando fazemos: ptr++
Ele não incrementa o endereço de memória em uma unidade, ele não vai pra 1.
Ele vai para: 1000 + sizeof(int)

Como o array é de inteiros, cada bloco do array ocupa 4 Kbytes, logo ptr vai apontar para o endereço 1004. Depois para o bloco 1008, depois 1012...

Se o array fosse de double, ele ia apontar para os endereços: 1000, 1008, 1016, 1024...a cada vez que incrementássemos, pois a variável double ocupa 8 Kbytes.

Veja como fica o código do programa que imprime apenas os elementos de índice par, do array (0, 2 e 4):
#include <iostream>
using namespace std;

int main()
{
    int numbers[6]={1, 2, 3, 4, 5, 2112},
        *ptr = numbers;

    for(int aux=0 ; aux<3 ; aux++){
        cout << *ptr << endl;
        ptr += 2;
    }

    return 0;
}
Essa é a lógica e matemática dos ponteiros. Por isso é tão importante definir o tipo de ponteiro (int, char, double...), pois os ponteiros apontam para blocos inteiros de memória, o tamanho desses blocos variam de acordo com o tipo de dado.

Ponteiros em Funções em C++

Neste tutorial de nossa apostila de C++, vamos aprender como trabalhar com ponteiros em funções.
Antes, vamos relembrar um pouco do que aprendemos na seção de Funções.

Passagem por Valor e Variável de Referência

Rode o seguinte programa, que eleva ao quadrado, um número digitado pelo usuário.
Ele mostra o número digitado, ele ao quadrado e depois o valor original, novamente:
#include <iostream>
using namespace std;

int square(int num)
{
    num = num*num;
    cout << "Quadrado: " << num <<endl;
}

int main()
{
    int num;
    cout <<"Digite um número: ";
    cin >> num;

    cout <<"Numero digitado: " << num << endl;
    square(num);
    cout <<"Valor de num: " << num << endl;

    return 0;
}

Apostila de C++ para download

Veja que passamos a variável 'num' para a função, e lá dentro ela é elevada ao quadrado. Mostramos esse valor dentro da função, e depois mostramos novamente quando a chamada da função é terminada.

Ela não mudou o valor armazenado em 'num'.
Isso ocorre porque quando passamos uma variável pra uma função, a função faz uma cópia de seu valor. Não vai usar a variável diretamente, e sim o seu valor apenas, por isso a variável original não é alterada.

Para dar acesso diretamente à variável, podemos usar as variáveis de referência.
Que é basicamente passar o nome da variável, com o símbolo & na frente, veja como fica:
#include <iostream>
using namespace std;

int square(int &num)
{
    num = num*num;
    cout << "Quadrado: " << num <<endl;
}

int main()
{
    int num;
    cout <<"Digite um número: ";
    cin >> num;

    cout <<"Numero digitado: " << num << endl;
    square(num);
    cout <<"Valor de num: " << num << endl;

    return 0;
}
Agora veja que o valor de 'num' foi alterado dentro da função square():

Curso de C++ online grátis

Isso aconteceu porque usamos a variável de referência &num ao invés de apenas num.

E agora que você já estudou ponteiros e endereços de memória, sabe que ao usar o &, estamos lidando diretamente com endereços de memória. Ou seja, a função agora vai ter acesso ao endereço de memória da variável num e vai alterar esse bloco de memória diretamente.

Como usar Ponteiros em Funções no C++

Outra maneira de alterar o valor de uma variável, passando pra uma função, é usando ponteiros. De fato, é importante saber usar esta técnica, pois ela é bem útil em funções que lidam com Strings e em várias bibliotecas do C++, por exemplo.

Vamos criar uma função, chamada doub(), que vai dobrar o valor que ela receber, usando ponteiros:
#include <iostream>
using namespace std;

int doub(int*);

int main()
{
    int num;
    cout <<"Digite um número: ";
    cin >> num;

    cout <<"Numero digitado: " << num << endl;
    doub(&num);
    cout <<"Valor de num: " << num << endl;

    return 0;
}

int doub(int *ptr_num)
{
    (*ptr_num) = (*ptr_num) * 2;
    cout << "Dobro: " << *ptr_num <<endl;
}
O parâmetro dela é: int*
Ou seja, ela espera um ponteiro do tipo int. Se ela espera um ponteiro, temos que passar como argumento um...endereço de memória! Por isso, fizemos: doub(&num);
Lembrem-se: ponteiro é uma variável que armazena um endereço de memória!

Dentro da função, 'ptr_num'  é um ponteiro. Ponteiro para o endereço de memória da variável 'num' lá da função main().
Queremos dobrar o valor para qual ele aponta. Ora, como se acessa o valor armazenado no local para qual o ponteiro aponta? Usando asterisco: *ptr_num

Por isso, para dobrar o valor da variável 'num', através de ponteiro 'ptr_num' que aponta para ela, basta fazer:
*ptr_num = *ptr_num * 2;

Para não se confundir com os operadores, pode fazer assim que fica mais organizado:
(*ptr_num) = (*ptr_num) * 2;

O poder dos ponteiros

Uma grande vantagem de trabalhar com ponteiros, é que eles podem lidar de maneira mais 'global', digamos assim, com as variáveis para qual apontam.

Por exemplo, note que sempre pedimos um número ao usuário dentro da função main(), e a partir daí mandamos esse valor ou seu endereço para as mais diversas funções.

Usando ponteiros, podemos pegar valores do usuário dentro de uma função, como a getNum(), veja:
#include <iostream>
using namespace std;

void getNum(int *);
int doub(int*);

int main()
{
    int num;
    getNum(&num);
    cout <<"Numero digitado: " << num << endl;

    doub(&num);

    cout <<"Valor de num: " << num << endl;

    return 0;
}

void getNum(int *ptr_num)
{
    cout <<"Digite um número: ";
    cin >> *ptr_num;
}

int doub(int *ptr_num)
{
    (*ptr_num) = (*ptr_num) * 2;
    cout << "Dobro: " << *ptr_num <<endl;
}
Declaramos a variável 'num', e passamos seu endereço para a variável doub(), esse endereço vai ficar armazenado dentro do ponteiro ptr_num.
Agora, queremos alterar o valor de ptr_num, então basta usar: *ptr_num

E prontinho, magicamente, a variável que foi declarada lá na função main() foi alterada dentro da função getNum().

Poderosos, esses ponteiros, não?

Ajude o C++ Progressivo

Que tal apoiar e fazer crescer o ensino da programação no Brasil ?

Ajudar nosso país a crescer e se desenvolver cada vez mais, tecnologicamente?

Clica abaixo pra saber mais!

Apoiar o Projeto Progressivo