Mandar um cafézinho para o programador:


Me ajude a transformar café em código!

Apostila C++ Progressivo

Já imaginou ter o melhor, maior e mais completo material sobre a linguagem de programação C++ ?
Agora já dá.

É a bíblia C++ Progressivo
E-Book C++ Progressivo

Quais as vantagens da
Apostila C++ Progressivo ?

São várias! Veja:
  • Todo conteúdo do site
  • Conteúdo extra (incluindo questões resolvidas e códigos comentados)
  • Poder ler o conteúdo offline
  • Não precisa de internet pra estudar
  • Nada de propagandas chatas
  • Tutoriais bem organizados, separados e bonitinhos
  • Pode ler no computador, celular, tablet...
  • Pode estudar em casa, na faculdade, no ônibus, na praia, no show do Iron Maiden...
  • Serve como referência e para posterior consulta, caso esqueça algo
  • DE LONGE, o melhor presente que alguém pode dar pra outra pessoa: conhecimento
  • E o mais bacana:
    Você nos ajuda e incentiva a criar cada vez mais conteúdo de graça, de qualidade, melhorando o ensino da computação no Brasil. Simbora desenvolver a tecnologia nesse país ?
Fazer download da apostila C++ Progressivo

A apostila (e-book em PDF), custa apenas R$ 25,99.
Você pode adquirir pelo PagSeguro:

Pague com PagSeguro - é rápido, grátis e seguro!


E pelo PayPal:



C++ Progressivo download

Apostila de C++ completa

Tutorial completo de C++


MEGA PACK:
 Todas Apostilas

Já imaginou ter em um pendrive ou na memória de seu celular  TUDO que você precisa para aprender computação, em todas as áreas, da melhor maneira, mais completa e voltada para iniciantes?

Estudar Java, a linguagem mais importante do mercado de trabalho, que vai te permitir criar aplicativos para Android.

Linguagem C, a linguagem que 'Deus' usou para criar o Universo? Aprender a língua que são feitos sistemas operacionais e entrar mais a fundo nas entranhas dos bits de seu computador?

E HTML + CSS, para aprender criar sites ?
Una isso ao JavaScript, linguagem para executar scripts e programas no navegador dos clientes?

De quebra, aprender Python, a linguagem mais simples e fácil de aprender, que com pouco código você criar coisas fantásticas e extremamente poderosas?

É nosso mega pack, todas apostilas:

Apostila Java ProgressivoApostila C Progressivo


Apostila JavaScript ProgressivoApostila HTML + CSS Progressivo


Fazer download da apostila C++ Progressivo
Apostila Python Progressivo




Preço: R$ 69,99


Pessoal, dá cerca de 10 reais por apostila.

Se não for o melhor e mais barato investimento da sua vida, entre em contato pois eu te estorno seu dinheiro, sem problemas.

Union em C++

 Neste pequeno tutorial de nosso curso de C++, vamos aprender sobre uma estrutura de dados chamada union.

O que é uma UNION

UNION é mais ou menos parecido com uma estrutura. Mas vamos entender melhor como funcionam as estruturas, para entender como funcionam as unions.

Quando temos uma struct com um char, um int e um float, essas três variáveis tem espaços reservados na memória e estão em posições sequenciais.

Ou seja, tem um local pro char, os bytes do lado são reservados pro inteiro e os bytes seguintes são alocados pro float, e ponto final.

No caso de uma union, as variáveis não ocupam regiões diferentes e muito menos sequenciais, elas ocupam a mesma posição.

As unions são usadas quando queremos usar uma informação OU outra, da estrutura de dado. Na struct, podemos usar quantas e quais quisermos. Na union, podemos declarar várias variáveis dentro dela, mas somente uma, de cada vez, vai ocupar a memória.

Na prática, elas são usadas para economizar memória, principalmente em sistemas que tem poucos recursos ou quando você quer fazer algum software bem otimizado.

Como declarar e usar UNION em C++

A declaração se faz da seguinte maneira:

union Name
{
   //variables
};

Ou seja, idêntico a maneira como definimos structs, mas usamos a palavra-chave union.

Por exemplo, vamos declarar uma union que vai ser usada para definir um item de um supermercado. Os itens podem ser definidos tanto por volume (água, cerveja, produtos de limpeza etc) como por unidades (papel higiênico, biscoito etc).

Então, podemos fazer:

#include <iostream>
using namespace std;

union Item
{
   float volume;
   int unit;
};


int main()
{
    Item item;
    item.volume = 1.1;

    cout << item.volume <<endl;

    return 0;
}

Ao redor, vemos que aparece o valor correto: 1.1
Agora rode o código abaixo:

#include <iostream>
using namespace std;

union Item
{
   float volume;
   int unit;
};


int main()
{
    Item item;
    item.volume = 1.1;
    item.unit = 3;

    cout << item.volume <<endl;

    return 0;
}

Aparece: 4.2039e-45 como resultado.
Estranho?

Ocorre o seguinte. Nossa union tem um float e um int. Como o float ocupa mais espaço, o tamanho da union é o tamanho de uma variável do tipo float na sua máquina.

Quando fizemos: item.volume = 1.1
Estamos colocando o valor '1.1' no bloco de memória da union.

Depois, colocamos o inteiro '3', nesse bloco de memória. Porém, quando vamos imprimir o resultado no cout, imprimimos o bloco de float. O correto seria fazer:
cout << item.unit <<endl;

Pois foi colocado um inteiro, por último, na union.

Vamos para um exemplo mais prático e realista. Vamos definir uma union que vai representar um Token, onde você vai pedir para o usuário um dígito numérico ou uma letra do alfabeto, vamos armazenar essa informação na variável 'var' do tipo char.

Usamos a função isdigit() pra saber se é digito ou não. Se for, armazenamos o valor na union como um inteiro, transformando a variável 'var' em inteiro (var-'0'), em mytoken.number

Se não for dígito, é letra, então armazenamos o valor digitado em mytoken.letter

E prontinho. O que o usuário escrever vai ser armazenado na 'number' OU na 'letter', jamais nas duas, economizando assim, memória. Veja o código:

#include <iostream>
using namespace std;

union Token
{
   int number;
   char letter;
};


int main()
{
    Token mytoken;
    char var;

    cout << "Digite um dígito ou caractere: ";
    cin >> var;

    if(isdigit(var)){
        mytoken.number =var-'0';
        cout << "Numero: " << mytoken.number<<endl;
    }
    else{
        mytoken.letter = var;
        cout << "Letra: " << mytoken.letter<<endl;
    }

    return 0;
}
E como dissemos, as unions se parecem muito com as structs. Assim, podemos criar um array de unions, passar union para função, fazer return de union, colocar ponteiro dentro de union e usar o operador ->, e tudo mais que você desejar.

Se um dia precisar fazer um código para um timer de Microondas ou sistema de um relógio digital ou calculadora, onde a memória é recurso SUPER escasso, você pode usar union.

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

Structs e Funções em C++

 Agora que já aprendemos como declarar e usar structs no tutorial passado, vamos ver neste como usar as estruturas de dados com as funções.

Structs em Parâmetros e Argumentos de uma Função

Quando explicamos que as structs e suas instâncias funcionam como variáveis (mas variáveis especiais, que você mesmo criou), isso vale também para funções.

Por exemplo, uma função que vai receber e retornar um inteiro, tem o seguinte cabeçalho:

  • int myFunction(int num);

Ou seja, especificamos o tipo de dado que a função vai receber. Até o momento trabalhamos com int, double, float, char, ponteiros...agora vamos usar o tipo de dado...que nós mesmos vamos criar!

Por exemplo, uma função que vai receber a struct Student, do tutorial anterior, e exibir seus elementos, tem o seguinte cabeçalho:

  • void showStudent(Student alumn);

Por exemplo, o programa abaixo exibe os dados de uma struct de nome 'alumn', do tipo 'Student':

#include <iostream>
using namespace std;

struct Student
{
   char name[50];
   float math,
         physics,
         science;
};

void showStudent(Student alumn)
{
    cout<<"\nDados:"<<endl;
    cout<<"Nome      : "<<alumn.name<<endl;
    cout<<"Matemática: "<<alumn.math<<endl;
    cout<<"Física    : "<<alumn.physics<<endl;
    cout<<"Ciência   : "<<alumn.science<<endl;
}

int main()
{
    Student Alex;

    cout<<"Nome: ";
    cin.get(Alex.name,50);

    cout<<"Nota em Matemática: ";
    cin >> Alex.math;

    cout<<"Nota em Física: ";
    cin >> Alex.physics;

    cout<<"Nota em Ciências: ";
    cin >> Alex.science;

    showStudent(Alex);

    return 0;
}

Só para dizer que não é exatamente igual como fizemos com outras variáveis, note que nossa função usar a struct Student...ou seja, ela já deve EXISTIR antes de declararmos a função, por isso colocamos a declaração da struct ANTES da função que vai usar ela, entendido?

Retornando uma Struct de uma Função

Assim como as funções servem para receber informações e performar tarefas específicas, elas também servem para retornar informações. Até o momento, fizemos retorno com int, double, char, void...mas as funções podem retornar qualquer tipo de dado, desde que a sua struct tenha sido declarada antes.

A função getStudent() não recebe nenhum parâmetro/argumento, mas retorna uma variável do tipo Student, e é usada para retornar uma struct preenchida, veja como funciona:

#include <iostream>
using namespace std;

struct Student
{
   char name[50];
   float math,
         physics,
         science;
};

void showStudent(Student alumn)
{
    cout<<"\nDados:"<<endl;
    cout<<"Nome      : "<<alumn.name<<endl;
    cout<<"Matemática: "<<alumn.math<<endl;
    cout<<"Física    : "<<alumn.physics<<endl;
    cout<<"Ciência   : "<<alumn.science<<endl;
}

Student getStudent()
{
    Student alumn;

    cout<<"Nome: ";
    cin.get(alumn.name,50);

    cout<<"Nota em Matemática: ";
    cin >> alumn.math;

    cout<<"Nota em Física: ";
    cin >> alumn.physics;

    cout<<"Nota em Ciências: ";
    cin >> alumn.science;

    return alumn;
}

int main()
{
    Student Geddy = getStudent();
    showStudent(Geddy);

    return 0;
}

Veja como nossa main() ficou enxuta, apenas declaramos um aluno, fazemos ele receber os dados da função getStudent(), e catapimbas, exibimos o seu conteúdo, tá feito.

Passagem por Valor e por Referência de Structs

Vamos criar uma função, chamada fillStudent() que tem como argumento uma instancia da struct Student, nessa função, vamos solicitar ao usuário os dados do aluno.

Nosso código fica:

#include <iostream>
using namespace std;

struct Student
{
   char name[50];
   float math,
         physics,
         science;
};

void showStudent(Student alumn)
{
    cout<<"\nDados:"<<endl;
    cout<<"Nome      : "<<alumn.name<<endl;
    cout<<"Matemática: "<<alumn.math<<endl;
    cout<<"Física    : "<<alumn.physics<<endl;
    cout<<"Ciência   : "<<alumn.science<<endl;
}

void fillStudent(Student alumn)
{
    cout<<"Nome: ";
    cin.get(alumn.name,50);

    cout<<"Nota em Matemática: ";
    cin >> alumn.math;

    cout<<"Nota em Física: ";
    cin >> alumn.physics;

    cout<<"Nota em Ciências: ";
    cin >> alumn.science;
}

int main()
{
    Student Geddy;
    fillStudent(Geddy);
    showStudent(Geddy);

    return 0;
}

Na main(), criamos o estudante 'Geddy' e enviamos para a fillStudent() para seus dados serem preenchidos. Porém, quando invocamos a função showStudent(), ela exibe o seguinte resultado:

Dados:
Nome      : Ћ���
Matemática: 3.0844e-41
Física    : 6.11017e-29
Ciência   : 3.0844e-41

Ou seja, os membros da struct 'Geddy' não foram preenchidos! O motivo disso é que a passagem de structs foi feita por valor!

Quando fazemos fillStudent(Geddy), a função pega essa instância, cria uma cópia internamente na função, e altera os valores nessa cópia, e não na struct original.

E como fazer para essa função alterar, de fato, os membros da struct? Simples, basta fazer uma passagem por referência, da struct:

#include <iostream>
using namespace std;

struct Student
{
   char name[50];
   float math,
         physics,
         science;
};

void showStudent(Student alumn)
{
    cout<<"\nDados:"<<endl;
    cout<<"Nome      : "<<alumn.name<<endl;
    cout<<"Matemática: "<<alumn.math<<endl;
    cout<<"Física    : "<<alumn.physics<<endl;
    cout<<"Ciência   : "<<alumn.science<<endl;
}

void fillStudent(Student &alumn)
{
    cout<<"Nome: ";
    cin.get(alumn.name,50);

    cout<<"Nota em Matemática: ";
    cin >> alumn.math;

    cout<<"Nota em Física: ";
    cin >> alumn.physics;

    cout<<"Nota em Ciências: ";
    cin >> alumn.science;
}

int main()
{
    Student Geddy;
    fillStudent(Geddy);
    showStudent(Geddy);

    return 0;
}

Apenas adicionamos & ao parâmetro 'alumn'. Agora, toda struct que mandarmos para a função fillStudent() será alterada.

structs: Como declarar, Acessar e Usar

 No tutorial anterior, aprendemos o que são e para que servem as structs, neste tutorial de C++ iremos colocar a mão na massa e aprender a usar essa estrutura de dados tão importante.


Como declarar uma struct em C++

Para usarmos uma struct, primeiros temos que declará-la. E fazemos isso assim:

struct name
{
   // declaração
   // de
   // variáveis
};

Ou seja, só escrever a palavra chave struct, dar um nome, abrir chaves, colocar todas as variáveis que deseja lá dentro e terminar com ponto-e-vírgula.

Vamos declarar uma como exemplo:

struct Student
{
   char name[50];
   float math,
         physics,
         science;
};

A struct acima é a Student, e ela armazena informações de algum estudante.
Essas informações são chamadas membros, e são as variáveis name (para armazenar o nome do estudante), math, physics e science (para armazenar as notas do estudante).

Agora podemos criar as instâncias do tipo Student:

  • Student NeilPeart;
  • Student BruceDickinson;

Ou seja, é como se declarássemos variáveis. A diferença é que 'NeilPeart' e BruceDickinson' não são do tipo int, float, double, char ou nenhum outro tipo base da linguagem C++, eles são do tipo 'Student'.

E que tipo de dado é esse?
Ora, é o tipo de dado que você criou! Você definiu sua própria estrutura, agora ela existe e pode ser instanciada quantas vezes você quiser.

Você pode inclusive instanciar quantas variáveis do tipo Student quiser, na forma de array:

  • Student students[30];

Prontinho. Aí tem 30 'variáveis' do tipo de dado Student, ou seja, são 30 structs, que podem ser usadas para representar 30 alunos de uma sala de aula.


Como acessar os membros de uma struct

Como dissemos, as variáveis empacotadas numa struct são chamadas de membros dessa estrutura, e para acessá-las basta usarmos o operador ponto: .

Por exemplo, para acessar o nome:

  • NeilPeart.name
  • BruceDickinson.name

As notas de Matemática são acessadas assim:

  • NeilPeart.math
  • BruceDickinson.math

Ou seja, acessamos as variáveis assim: nome_da_instancia.nome_da_variavel

Veja bem, não podemos acessar: Student.science
Pois "Student" é apenas um modelo, uma abstração, um molde para criar estruturas de dados, ela não existe na realidade, o que existe são suas instâncias, que criamos a partir dessa struct, ok?

Vamos criar um programa que usa essa struct, cria um instância de nome 'Lennon', preenche seus dados e depois exibe:

#include <iostream>
using namespace std;

struct Student
{
   char name[50];
   float math,
         physics,
         science;
};

int main()
{
    Student Lennon;

    cout<<"Nome: ";
    cin.get(Lennon.name,50);

    cout<<"Nota em Matemática: ";
    cin >> Lennon.math;

    cout<<"Nota em Física: ";
    cin >> Lennon.physics;

    cout<<"Nota em Ciências: ";
    cin >> Lennon.science;

    cout<<"\nDados:"<<endl;
    cout<<"Nome      : "<<Lennon.name<<endl;
    cout<<"Matemática: "<<Lennon.math<<endl;
    cout<<"Física    : "<<Lennon.physics<<endl;
    cout<<"Ciência   : "<<Lennon.science<<endl;

    return 0;
}

Como inicializar structs

No exemplo anterior, pedimos o input de  membro por membro da struct, pro usuário. Porém, isso não é obrigatório, assim como nas variáveis, você já pode declarar uma struct e ir inicializando ela, veja:

#include <iostream>
using namespace std;

struct Student
{
   char name[50];
   float math,
         physics,
         science;
};

int main()
{
    Student robert = {"Robert Plant", 9.5, 8.1, 9};

    cout<<"\nDados:"<<endl;
    cout<<"Nome      : "<<robert.name<<endl;
    cout<<"Matemática: "<<robert.math<<endl;
    cout<<"Física    : "<<robert.physics<<endl;
    cout<<"Ciência   : "<<robert.science<<endl;

    return 0;
}

Ou seja, basta abrir chaves e ir informando dado por dado, NA ORDEM que foi declarado na struct, ok?!

Você também pode inicializar apenas alguns membros, por exemplo:

  • Student robert = {"Robert Plant"};

No caso acima, só o membro 'name' vai ser inicializado.
No exemplo abaixo, os membros 'name' e 'math' que serão inicializados:

  • Student robert = {"Robert Plant", 9.5};

Os outros permanecem sem serem inicializados.

Estrutura de Dados: struct - O que é, Para que serve e Como usar ?

 Neste tutorial, vamos dar início ao estudo das estruturas de dados, em C++.

Abstração de Dados

Um dos conceitos mais importantes na Ciência da Computação, é o de abstração de dados. Também chamado de ADT, abstract data type.

Abstração, em computação, se refere a um modelo de algo, um protótipo, uma generalização de alguma coisa.

Por exemplo, Humano é uma abstração. Você não conhece um Humano, você na verdade conhece pessoas específicas, como seu pai, sua mãe, seus amigos. Humanos é um 'molde', pra representar um objeto que ter cabeça, troco, coração, pulmão, etc.

O mesmo vale para Carro. Você não chega na concessionária e diz 'Quero um carro'.
Você diz: quero um Corolla, um Civic, um Honda Fit etc.
Carro é uma generalização de um treco que tem todas, motor, câmbio etc.

Pois bem, o mesmo ocorre na nossa querida e amada programação, é suuuuuper comum em um projeto criarmos nossas abstrações, para representar 'coisas' do sistema que vamos implementar.

Estruturas de Dados: structs

Até o momento, usamos tipos de dados bem específicos, como int, double e float (para trabalhar com números) ou char, para trabalhar com caracteres.

Através dos arrays, conseguimos criar e trabalhar com qualquer quantidade desses tipos de dados de uma maneira bem fácil e poderosa.

Porém, tem um problema. Os tipos de dados de um array são sempre iguais. Não dá pra ter um array que tem um inteiro dentro representando a idade de um funcionário, uma string armazenando o nome e um float para guardar seu salário.

Bom, para isso que servem as structs: empacotar outros tipos de dados, para criar seu próprio tipo de dado. Através das structs, podemos criar novos tipos de dados.

Por exemplo, vamos criar o tipo de dado Carro, dentro dele tem inteiro pra armazenar o número de portas, tem float pra armazenar a potência do motor e uma string para armazenar o nome do modelo.

Estrutura de Dados em C++

Se antes fazíamos:

  • int number;
  • float price;

Agora vamos fazer:

  • Funcionario Jose;

Ou seja, estamos declarando uma variável de nome 'Jose', que é do tipo 'Funcionario'. Sim, do tipo 'Funcionario', assim como tipo de dado int, float, char, etc.

É um tipo de dado que você criou, uma abstração necessária no sue projeto, e isso é feito através das structs! Ele pode conter quantas variáveis quiser e de qualquer tipo, pode ter array, ponteiros, matriz, etc. Tudo isso 'empacotado', numa estrutura de dado que você nomeou.

No próximo tutorial, vamos aprender como criar, acessar e usar uma struct, em C++!

Associação, Composição e Agregação em C++

 Neste tutorial de nosso curso de C++, vamos aprender como diferentes Classes e Objetos se relacionam uns com os outros, e estudar melhor essa relação.

Classes e Objetos na vida real

Até o momento, em nosso curso, usamos classes de maneira bem isolada, criando e usando apenas uma classe. Mas o mundo real, isso raramente ocorre. Vivemos em um mundo de objetos.

Muitas vezes, só faz sentindo existir um objeto, se existirem outros. Por exemplo, podemos ter a classe Humano e a classe Alimento. Os objetos do tipo Humano é você, eu, nossos amigos, familiares...os objetos do tipo Alimento, é o arroz, feijão, carne...tem sentido objetos da classe Humano existirem sem os objetos da classe Alimento? Claro que não.

E mesmo que um objeto não dependa de outro pra existir, ele pode depender de outros pra 'funcionar'. Por exemplo, objetos das classes Motor, Câmbio, Freio etc, trabalham juntos, para fazer um objeto o tipo Carro funcionar.

Ou seja, no mundo real, os objetos não são isolados. Nos seus projetos, você vai sempre criar várias classes, instanciar vários objetos, fazer um criar outro, um enviar informação, outro receber um dado de outro objeto, o método de um objeto usar vários outros objetos, e por ai vai.

Associação, Composição e Agregação

Quando um objeto usa um método ou serviço de outro objeto, dizemos que há uma associação aí. Sim, é algo bem amplo e genérico mesmo. Vamos especificar mais os tipos de relação, em composição e agregação.

Em ambos casos, um objeto possui outro objeto. Porém, na composição, um objeto só existe se o outro existir.

Por exemplo, o objeto do tipo Carro só existe se tiver um objeto da classe Motor. Não existe carro sem motor.

Um objeto do tipo Humano, só existe se tiver objetos como Coração, Pulmão...não existe uma pessoa sem esses órgãos (pelo menos por enquanto, vai que algum programa projeta e cria órgãos no futuro - claro, iria usar C++).

Já na agregação, um objeto possui outro, mas poderia existir sem ele. Por exemplo, todo carro tem um dono. Ou seja, tem objetos do tipo Humano que possuem objetos do tipo Carro. Mas um ser humano não precisa, obrigatoriamente, de um carro para existir.

Você precisa de um coração, uma cabeça....obrigatoriamente. De um carro, não. A relação de um objeto do tipo Carro e de um objeto do tipo Humano, é agregação.

Associação, Composição e Agregação em C++

Na composição, quando um objeto-pai é destruído, os objetos-filhos também serão. Um não existe sem o outro. Na agregação, não. Ou seja, associação é algo bem genérico, depois vem agregação (um pode existir sem o outro) e por fim a composição, a relação mais específica, onde um objeto só existe se outro existir.

Mas vamos deixar essas conversas genéricas de lado e ver na prática algumas relações entre objetos.

Composição em C++

Vamos começar pelo mais específico. Pra facilitar, chamamos as classes Humano e Carro de classes pais. As classes Motor, Coração, Câmbio, Pulmão etc, são as classes filhas, membros ou componentes. Ou chamamos de Todo e Partes, ou Objeto e Membro.

Para se caracterizar como composição, a parte faz parte do todo. Cada parte pertence somente a um objeto (ou seja, o coração pertence somente a um humano, e o motor do carro pertence somente a um carro).

A classe que representa o Todo, é que vai gerenciar as partes, os componentes. Esses membros não sabem da existência do 'Todo'.

Vamos criar a classe Engine, no construtor avisamos que estamos ligando o motor e na função destruidor avisamos que estamos desligando o motor. Engine.h:

#ifndef ENGINE_H
#define ENGINE_H

class Engine
{
    public:
        Engine();
        ~Engine();
};

#endif // ENGINE_H

Engine.cpp

#include "Engine.h"
#include <iostream>
using namespace std;

Engine::Engine()
{
    cout << "Ligando o motor..."<<endl;
}

Engine::~Engine()
{
    cout << "Desligando o motor..."<<endl;
}

Agora vamos criar a classe Car, que vai fazer quase o mesmo da Engine, vai dizer 'ligando o carro' na construtor e 'desligando o carro' no destruidor. Porém, ele vai ter um membro, um ponteiro pro tipo Engine, a variável 'myEng'.

Na classe, apenas declaramos esse ponteiro, Engine.h:

#ifndef CAR_H
#define CAR_H
#include "Engine.h"

class Car
{
    public:
        Car();
        ~Car();
        Engine *myEng;
};

#endif // CAR_H

Mas já na implementação, nós instanciamos esse ponteiro, fazendo que o método construtor do objeto 'myEng' seja invocado. Na função destruidor da Car, deletamos o ponteiro do tipo Engine, invocando automaticamente a função destructor, Car.pp:

#include "Car.h"
#include <iostream>
using namespace std;

Car::Car()
{
    cout << "Vamos ligar o carro."<<endl;
    myEng = new Engine();
}

Car::~Car()
{
    delete myEng;
    cout << "Carro desligado."<<endl;
}

Nossa main.cpp:

#include <iostream>
#include "Car.h"
using namespace std;

int main()
{
    Car myCar;

    return 0;
}

O resultado é o seguinte:

Vamos ligar o carro.
Ligando o motor...
Desligando o motor...
Carro desligado.

O objeto 'myEng' SÓ EXISTE por causa do objeto 'myCar'. Sem o 'myCar', sem 'myEng'. 'myEng' É DO 'myCar'. Um objeto pertence a outro.

Quem 'controla' o 'myEng' também é o 'myCar'. Ele que cria o objeto, instancia...e morreu o objeto da classe Car, morre também o objeto da classe Engine. O objeto 'myEng' pertence somente ao objeto 'myCar', a nenhum outro mais. E ele também não sabe da existência de nenhum outro objeto e não sabe de nada da classe Car.

Essas coisas que caracterizam bem a composição.

Agregação em C++

Vamos agora criar a classe Human, para criar pessoas. Elas que vão dirigir o carro. Para criar um objeto dessa classe, precisamos passar uma string com o nome da pessoa e um objeto do tipo Car, veja como fica a Human.h:

#ifndef HUMAN_H
#define HUMAN_H
#include <string>
#include "Car.h"

class Human
{
    public:
        std::string name{};
        Car myCar;
        Human(const std::string& str, Car car);
        ~Human();

};

#endif // HUMAN_H

Agora a implementação Human.cpp:

#include "Human.h"
#include <iostream>
#include <string>
using namespace std;

Human::Human(const std::string& str, Car car)
{
    name = str;
    myCar = car;
    cout<<name;
    myCar.liga();
}

Human::~Human()
{
    cout<<name;
    myCar.desliga();
}

Fizemos algumas alterações na Car.h:

#ifndef CAR_H
#define CAR_H
#include "Engine.h"

class Car
{
    public:
        void liga();
        void desliga();
        Engine *myEng;
};

#endif // CAR_H

E na implementação Car.cpp:

#include "Car.h"
#include <iostream>
using namespace std;

void Car::liga()
{
    cout << " vai ligar o carro."<<endl;
    myEng = new Engine();
}

void Car::desliga()
{
    cout <<" desligou o carro."<<endl;
    delete myEng;
}

Ou seja, agora temos as funções liga() e desliga(), nos objetos do tipo Car (antes a gente ligava e desligava na construtor e destruidor).

Veja como ficou nossa main.cpp:

#include <iostream>
#include "Car.h"
#include "Human.h"
using namespace std;

int main()
{
    Car car;
    Human *h1 = new Human("Neil Peart", car);
    delete h1;

    cout << endl;

    Human *h2 = new Human("Bruce Dickinson", car);
    delete h2;

    return 0;
}

Ou seja, criamos um objeto da classe Car e dois da classe Human.
Vamos ver algumas coisas interessantes. O resultado se rodarmos o código acima é:

Neil Peart vai ligar o carro.
Motor ligado.
Neil Peart desligou o carro.
Motor desligado.

Bruce Dickinson vai ligar o carro.
Motor ligado.
Bruce Dickinson desligou o carro.
Motor desligado.

Primeiro, o mesmo objeto 'car' é usado tanto no objeto 'h1' quanto no 'h2'.
Segundo, são os objetos h1 e h2 que acionam as funções ligar() e desligar() do carro, ou seja, eles que manuseiam o objeto 'car'.
Por fim, o objeto 'car' não deixa de existir quando o objeto 'h1' deixou de existir. Ele continuou lá vivo, tanto que foi usado pelo objeto 'h2' logo em seguida. Ou seja, embora haja uma relação entre eles (uns objetos usam outros), eles são de certa forma independentes, diferente da relação entre Car e Engine, onde os motores SÓ EXISTEM enquanto existem os carros, e só é usado um motor para cada carro.

Essas são as características da agregação.

Entre Car e Engine, é composição.
Entre Human e Car, é agregação.

Associação em C++

Deixa eu te contar o maior segredo da linguagem C++: um sistema grande, complexo, como uma estação espacial ou Microsoft Excel, são nada mais nada menos que vários objetos pequenos, desempenhando papéis específicos.

Ou seja, um sistema complexo nada mais é que vários objetos pequenos, trabalhando juntos, como uma associação.

Assim, durante sua carreira como programador C++, procure criar sempre Classes e Objetos específicos, bem definidos, com funções certas e de preferências os mais simples e diretos possíveis.

Repetindo: um grande projeto ou sistema nada mais é que um mooooonte de objetos trabalhando juntos, em associação, seja por composição ou agregação.

Organização é a chave do sucesso para se trabalhar com orientação a objetos!

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