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 10!
  • Tudo sobre C++ Parte 11!
  • Tudo sobre C++ Parte 12!
  • Tudo sobre C++ Parte 13!
  • Tudo sobre C++ Parte 14!
  • Tudo sobre C++ Parte 15!
  • Tudo sobre C++ Parte 16!
  • Tudo sobre C++ Parte 9

    Ponteiros - Parte 3

    Para trabalharmos com funções que alteram o valor de uma variável, precisamos usar os ponteiros, independente do escopo da função.

    Crie esse código com essa função aqui:

    
    #include <iostream>
    
    using namespace std;
    
    void somar(int var, int valor); // Não esqueça do protótipo
    
    int main() {
        int num = 0;
    
        somar(num, 15);
    
        cout << num << endl; // 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:

    
    #include <iostream>
    
    using namespace std;
    
    void somar(int *var, int valor); // Não esqueça do protótipo
    
    int main() {
        int num = 0;
    
        somar(&num, 15);
    
        cout << num << endl;
    
        return 0;
    }
    
    void somar(int *var, int valor) {
        *var += valor;
    }
    
    

    Da forma acima fará a função corretamente, já que ela trabalhará no mesmo endereço de num, alterando o valor corretamente.

    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:

    
    #include <iostream>
    
    using namespace std;
    
    void iniVet(int *v);
    
    int main() {
        int vetor[5];
    
        iniVet(vetor);
    
        for(int i = 0; i < 5; i++) {
            cout << vetor[i] << endl;
        }
    
        return 0;
    }
    
    
    void iniVet(int *v) {
        v[0] = 1;
        v[1] = 2;
        v[2] = 3;
        v[3] = 4;
        v[4] = 5;
    }
    
    

    Se tirar o ponteiro do parâmetro, ele dará erro.

    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:

    
    void testar(int n1, int n2);
    
    int main() {
        setlocale(LC_ALL, "portuguese");
        
        int n1 = 10;
        int n2 = 20;
    
        cout << "Valores antes de chamar a função: ";
        cout << "n1 = " << n1 << " e n2 = " << n2 << endl;
    
        testar(n1, n2);
    
        cout << "Valores depois de chamar a função: ";
        cout << "n1 = " << n1 << " e n2 = " << n2 << endl;
    
        return 0;
    }
    
    void testar(int n1, int n2) {
        n1 = -1;
        n2 = -2;
    
        cout << "Valores dentro da função testar: " << endl;
        cout << "n1 = " << n1 << " e n2 = " << n2 << endl;
    }
    
    

    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;
    
        cout << "Valores antes de chamar a função: ";
        cout << "n1 = " << n1 << " e n2 = " << n2 << endl;
    
        testar(&n1, &n2);
    
        cout << "Valores depois de chamar a função: ";
        cout << "n1 = " << n1 << " e n2 = " << n2 << endl;
    
        return 0;
    }
    
    int testar(int *n1, int *n2) {
        *n1 = -1;
        *n2 = -2;
    
        cout << "Valores dentro da função testar: " << endl;
        cout << "n1 = " << *n1 << " e n2 = " << *n2 << endl;
    
        return 0;
    }
    
    

    PS: Podemos ter também ponteiros duplos, isso é chamado de indireção múltipla, veja um exemplo abaixo:

    
    int num = 50;
    int *pont; // Ponteiro normal
    int **duplPont; // Ponteiro de ponteiro
    
    pont = &num; // Aponta pra num
    duplPont = &pont; // Aponta pra pont, que aponta pra num, ele não aponta diretamente pra num.
    
    cout << "Endereço de num.........................................: " << &num << endl;
    cout << "Conteúdo de num.........................................: " << num << endl;
    cout << "Endereço de pont........................................: " << &pont << endl;
    cout << "Endereço apontado por pont..............................: " << pont << endl;
    cout << "Conteúdo apontado por pont..............................: " << *pont << endl;
    cout << "Endereço de duplPont....................................: " << &duplPont << endl;
    cout << "Endereço apontado por duplPont..........................: " << duplPont << endl;
    cout << "Endereço apontado por pont que é apontado por duplPont..: " << *duplPont << endl;
    cout << "Conteúdo apontado por pont que é apontado por duplPont..: " << **duplPont << endl;
    
    delete pont;
    delete duplPont;
    
    

    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.

    Para começar, criaremos um programa sem alocação, dessa forma:

    
    #include <iostream>
    
    using namespace std;
    
    int main() {
        char nome[50];
    
        cout << "Digite o nome: ";
        cin >> nome;
        cout << 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 (também usado em C), dessa forma (importe cstdio):

    
    int main() {
        char nome[50];
    
        cout << "Digite o nome: ";
        fgets(nome, 50, stdin); // Importe cstdio
    
        // Isso é pra remover a quebra de linha:
        size_t len;
        len = strlen(nome); // Inclua cstring
    
        if(nome[len - 1] == '\n') nome[--len] = 0;
    
        cout << 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 cstdlib. Teremos também que transformar a variável char num ponteiro, sem indicar o número de espaços, dessa forma:

    
    #include <iostream>
    #include <cstdio> // Para fgets
    #include <cstdlib> // Para malloc
    #include <cstring> // Para strlen
    
    using namespace std;
    
    int main() {
        char *nome;
        nome = (char*)malloc(sizeof(char));
    
        cout << "Digite o nome: ";
        scanf("\n"); // Isso é apenas para evitar que os dados inseridos não sejam lidos, pode funcionar também colocando no lugar fflush(stdin).
        fgets(nome, sizeof(nome), stdin);
    
        // Isso é pra remover a quebra de linha:
        size_t len;
        len = strlen(nome); // Inclua cstring
    
        if(nome[len - 1] == '\n') nome[--len] = 0;
    
        cout << 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);
    
    

    Em C++, portanto, é mais comum usar o delete para excluir ponteiros e alocações, dessa forma:

    
    delete nome;
    
    

    Unions

    As unions se declaram de forma parecida com as structs, mas ela ocupa menos espaço na memória.

    Veja um exemplo de uso:

    
    union exemplo {
        int inteiro;
        char caractere;
    };
    
    

    As variáveis do unions ocupam o mesmo endereço de memória.

    Vamos comparar uma union e uma struct:

    
    union estrutura01 {
        int inteiro;
        char caractere;
        float decimal;
    };
    
    struct estrutura02 {
        int inteiro2;
        char caractere2;
        float decimal2;
    };
    
    

    E na função principal, faça isso:

    
    cout << "União: " << sizeof(estrutura01) << endl;
    cout << "Estrutura: " << sizeof(estrutura02) << endl;
    
    

    Dessa forma vemos a comparação entre o tamanho da union e da struct.

    PS: As unions podem ser utilizadas dentro de structs ou de outras unions também.

    Typedef em C++

    Como fizemos com as enuns, podemos criar nossos "tipos" próprio com qualquer tipo primitivo, struct, union, etc.

    Veja um exemplo de uso logo abaixo:

    
    typedef int inteiro;
    typedef float flutuante;
    typedef char caractere;
    typedef const char* texto;
    
    int main() {
        setlocale(LC_ALL, "portuguese");
    
        inteiro num = 50;
        flutuante real = 9.85;
        caractere letra = 'A';
        texto frase = "Introdução à Typedef em C++";
    
        cout << num << endl;
        cout << real << endl;
        cout << letra << endl;
        cout << frase << endl;
    
        return 0;
    }
    
    

    Printf e Scanf

    Os comandos printf e scanf na verdade são utilizados em C, mas eles podem ser usados em C++ também.

    Veja um exemplo abaixo (para usar eles usamos a biblioteca stdio.h ou cstdio):

    
    #include <iostream>
    #include <cstdio> // Para printf e scanf
    
    using namespace std;
    
    int main() {
        printf("Uma frase qualquer!\nCurso de C++");
    
        return 0;
    }
    
    

    Com o printf, podemos usar sequências de escape e máscaras de formatação também. A \n pula uma linha e a \0 delimita o final da string.

    Vamos trabalhar com variáveis em outro exemplo, onde colocamos as máscaras, que servem de referência da variável:

    
    #include <iostream>
    #include <cstdio> // Para printf e scanf
    
    using namespace std;
    
    int main() {
        int num1 = 123;
    
        printf("Valor da variável: %d\n", num1);
    
        return 0;
    }
    
    

    E podemos colocar mais de uma variável e máscara no mesmo printf, sempre respeitando a ordem (incluindo de tipos):

    
    #include <iostream>
    #include <cstdio> // Para printf e scanff
    
    using namespace std;
    
    int main() {
        int num1, num2, num3;
        num1 = 1;
        num2 = 2;
        num3 = 3;
    
        printf("Valor das variáveis: \n%d\n%d\n%d\n", num1, num2, num3);
    
        return 0;
    }
    
    

    Veja algumas máscaras de formatação, no C chamadas de especificadores de conversão:

    Máscara Tipo de Dado Descrição
    %d int Número Inteiro Decimal com Sinal
    %c char Caractere Único
    %f float ou double Número Decimal (Real ou de Ponto Flutuante)
    %i int Número Inteiro Decimal (Diferentes na Entrada e Saída de Dados)
    %ld long int Número Inteiro Longo
    %e float ou double Número Real Exponencial (Científico)
    %E float ou double Número Real Exponencial (Científico)
    %o int Número Inteiro em Formato Octal sem Sinal
    %x int Número Inteiro em Formato Hexadecimal sem Sinal. Caracteres de a a f
    %X int Número Inteiro em Formato Hexadecimal sem Sinal. Caracteres de A a F
    %u int Número Inteiro Decimal sem Sinal
    %h ou %l --- Colocado Antes de um Especificador Inteiro para Indicar Valor Short ou Long
    %L --- Colocado Antes de um Especificador de Ponto Flutuante para indicar valor Long Double
    %s char Cadeia de Caracteres (String ou Ponto pra char)
    %p pointer Ponteiros

    Veja outro exemplo:

    
    #include <iostream>
    #include <cstdio> // Para printf e scanf
    
    using namespace std;
    
    int main() {
        char nome[3]; // Array de char
    
        nome[0] = 'A';
        nome[1] = 'B';
        nome[2] = 'C';
    
        printf("Valor da variável: %s", nome);
    
        return 0;
    }
    
    

    Para ler do teclado usando scanf, a forma é parecida, mas devemos colocar a variável com o operador &, que indica o endereço da variável:

    
    #include <iostream>
    #include <cstdio> // Para printf e scanf
    
    using namespace std;
    
    int main() {
        int num1, num2, num3;
    
        printf("Digite um valor: ");
        scanf("%d", &num1);
    
        printf("Valor da variável: %d", num1);
    
        return 0;
    }
    
    

    Podemos também usar mais de uma entrada no mesmo scanf, mas não é recomendado:

    
    #include <iostream>
    #include <cstdio> // Para printf e scanf
    
    using namespace std;
    
    int main() {
        int num1, num2, num3;
        char nome[10];
    
        printf("Digite um número e uma string: ");
        scanf("%d %s", &num1, &nome);
    
        printf("Valor das variáveis: \n%d\n%s\n", num1, nome);
    
        return 0;
    }
    
    

    Veja mais um exemplo:

    
    #include <iostream>
    #include <cstdio> // Para printf e scanf
    
    using namespace std;
    
    int main() {
        char nome[10];
        int ano;
    
        printf("Digite seu nome: ");
        scanf("%s", &nome);
        printf("Informe o ano de nascimento: ");
        scanf("%d", &ano);
    
        printf("\nNome: %s\nAno: %d", nome, ano);
    
        return 0;
    }