© FOTOGRIN/Shutterstock.com
Como uma linguagem de programação orientada a objetos, C++ frequentemente depende da implementação de herança. Esse recurso permite modificar e reutilizar atributos e outras características de uma classe, bem como reduzir a quantidade de código necessária. Continue lendo para descobrir exatamente o que é herança e os tipos de herança em C++ que você pode usar.
O que é herança?
Muito parecido com o que podemos “herdar” certas características de nossos pais, em C++, as classes podem herdar membros de outra classe. Dessa forma, a classe cujos membros são herdados é definida como a classe base ou pai, e a classe que herda esses recursos é conhecida como classe derivada, subclasse ou classe filha. Como isso permite que os membros da classe pai sejam reutilizados, esses membros não precisam ser redefinidos. Portanto, você pode economizar tempo implementando código desnecessário.
Existem semelhanças com a função de amigo e a classe de amigo, pois permitem que funções e classes acessem os membros de outras classes. No entanto, o amigo permite acesso a membros privados, enquanto a herança não.
Quais são os tipos de herança em C++?
Existem 5 tipos de herança em C++. São elas:
Herança única Herança multinível Herança múltipla Herança híbrida Herança hierárquica
Vamos dar uma olhada em cada uma delas.
Herança única
O método mais simples de herança é onde uma classe herda os membros de uma outra classe. Isso pode ser mostrado com o seguinte código:
#include class BaseClass { public: int x; }; class DerivedClass: public BaseClass{ public: int y; }; int main() { DerivedClass obj; obj.x=10 obj.y=20 std::cout”Variável de membro de classe base x=”std::endl; std::cout”Variável de membro de classe derivada y=”obk.y std::endl; retornar 0; }
Aqui, definimos a classe base como “BaseClass”, com o membro “x” que tem acesso público. A classe derivada é definida como “DerivedClass”, que herda publicamente de “BaseClass”. A própria classe derivada tem um membro público chamado “y”. Criamos uma instância da classe derivada chamada “DerivedClass obj” e definimos os valores x e y. Usando esse objeto, acessamos o membro “x” da classe pai e os valores x e y são impressos no console, conforme mostrado na captura de tela.
Herança única mostrada em um exemplo.
©”TNGD.com
Herdando membros privados
Vale a pena notar que a classe derivada pode herdar todos os membros da classe pai porque eles têm acesso público. No entanto, se os membros forem declarados privados, eles não poderão ser herdados diretamente. Uma maneira de contornar isso é se certas funções que agem nesses membros forem herdadas. Dessa forma, você pode acessar indiretamente esses membros. Veja o seguinte exemplo:
class Base{ private: int a; public: Base(int a) a(a) {} int getA() const { return a; } }; class Derived: public Base { public: Derived(int a): Base(A) {} }; int main() { Derivado d(5); std::cout”Valor do objeto derivado:”d.getA() std::endl; retornar 0; }
Aqui, definimos uma classe pai como”Base”e uma classe derivada como”Derivada”. O membro “a” da classe pai é privado, portanto não pode ser acessado diretamente pela classe derivada. Porém, temos um construtor na classe base que recebe o argumento inteiro de “a” e define o membro privado “a”. A classe derivada tem um construtor próprio, “Derived(int a): Base(a) {}” que pega o argumento inteiro “a” e o passa para o construtor da classe base. Como a função “getA()” foi herdada, o acesso indireto ao membro “a” da classe pai é alcançado.
Usando a função “main”, tomamos uma instância “d” de a classe derivada e defina-a com um argumento de 5. A função “getA()” é chamada nesta instância e a saída é impressa. Isso é mostrado na captura de tela.
Como herdar membros privados, mostrado em um exemplo.
©”TNGD.com
Private vs. classes protegidas
Seria útil nesta fase distinguir entre classes privadas e protegidas. Enquanto membros de classe privada só podem ser acessados indiretamente por classes derivadas, membros de classe protegidos podem ser totalmente acessados por uma classe derivada. Mas eles só podem ser acessados dentro dessa hierarquia de classes, e não fora dela (isso seria acesso público).
Herança somente de método
Esses exemplos também mostraram herança de campo, onde o campo “a” foi herdado. Mas você também pode ter herança somente de método, onde um método é usado para definir o comportamento da classe. Um exemplo disso é mostrado abaixo:
#inlude class Base { public: virtual void bar() { std::cout”Base::bar()”std::endl; } }; class Derived: public Base { public: virtual voide bar() { std::cout”Derived::bar()”std::endl; } }; int main() { Base* b=new Derivado(); b->bar();//imprime”Derived::bar()”delete b; retornar 0; }
Como antes, temos uma classe pai “Base” e uma classe derivada “Derived”. “Base” tem um método virtual “bar()”. Isso é chamado de virtual, pois não possui implementação na classe pai e pode ser substituído pela classe derivada. A implementação if “bar()” é fornecida na definição da classe derivada. Podemos ver que um ponteiro “b” é criado dentro da função “main()” e inicializado com o objeto “Derived”. “Derived::bar()” substitui “Base::bar()”, já que é uma função virtual. A implementação dentro da classe derivada é chamada, que imprime a saída “Derived::bar()” como visto na captura de tela.
Um exemplo mostrando herança somente de método.
©”TNGD.com
Herança multinível
Há muitos casos em que você pode querer criar uma classe derivada de uma classe que já foi derivada de uma classe pai. Isso é conhecido como herança multinível. No que diz respeito aos tipos de herança em C++, este é bastante intuitivo. Um desses exemplos é quando você está trabalhando com tipos de veículos. Nesta situação, você pode ter uma classe pai de “Veículos” que define certos métodos e parâmetros universais para os veículos com os quais você está trabalhando, como carros e motos. Ambas podem ser classes derivadas dessa classe pai, com suas próprias propriedades específicas. Mas como você pode ter tipos específicos deles, você pode criar classes derivadas como”PerformanceCar”, que teriam suas próprias propriedades aplicáveis.
Podemos ilustrar como isso funciona com o seguinte código:
#include #include class Vehicle { public: std::string type; std::cor da string; }; class Car: public Vehicle { public: std::string model; int ano; }; class ElectricCar: public Car { public: int batteryCapacity; }; int main() { ElectricCar tesla; tesla.type=”Carro elétrico”; tesla.color=”Vermelho”; tesla.model=”Modelo S”; tesla.ano=2021; tesla.batteryCapacity=100; std::cout”Tipo:”tesla.type std::endl; std::cout”Cor:”tesla.color std::endl; std::cout”Modelo:”tesla.model std::endl; std::cout”Ano:”tesla.year std::endl; std::cout”Capacidade da bateria:”tesla.batteryCapacity std::endl; retornar 0; }
Neste exemplo, temos a classe base “Vehicle”, a classe derivada “Car” e uma outra classe derivada chamada “ElectricCar”. A classe “Vehicle” possui os membros “type” e “color”, a classe “Car” possui os membros “model” e a variável inteira “year”, e a classe “ElectricCar” possui a variável inteira “batteryCapacity”. A função principal cria uma instância da classe “ElectricCar”, definindo todas as suas variáveis de membro herdadas com valores específicos. Essas variáveis são então impressas no console.
Herança multinível implementada em um exemplo, junto com sua saída.
©”TNGD.com
Herança múltipla
Assim como podemos ter uma classe derivada de uma classe já derivada, podemos ter uma classe derivada de mais de uma classe base. Podemos demonstrar isso continuando com nossa analogia com o carro:
#include #include class Vehicle { public: std::string type; std::cor da string; }; class ElectricEngine { public: int batteryCapacity; }; class SportsCar { public: int topSpeed; }; class ElectricSportsCar: public Vehicle, public ElectricEngine, public SportsCar { public: std::string model; int ano; }; int main() { ElectricSportsCar tesla; tesla.type=”Carro elétrico”; tesla.color=”Vermelho”; tesla.model=”Roadster”; tesla.ano=2022; tesla.batteryCapacity=200; tesla.topSpeed =250; std::cout”Tipo:”tesla.type std::endl; std::cout”Cor:”tesla.color std::endl; std::cout”Modelo:”tesla.model std::endl; std::cout”Ano:”tesla.year std::endl; std::cout”Capacidade da bateria:”tesla.batteryCapacity”kWh”std::endl; std::cout”Top Speed:”tesla.topSpeed ”mph”std::endl; retornar 0; }
Temos três classes aqui, “Vehicle”, “ElectricEngine” e “SportsCar”. Todas são classes base e têm suas propriedades herdadas pela classe derivada “ElectricSportsCar”. Como antes, cada uma das classes pai tem suas próprias variáveis. Uma instância de “ElectricSportsCar” é criada, os valores são atribuídos às suas propriedades e estes são impressos no console.
Um exemplo de herança múltipla.
©”TNGD.com
Herárquica Herança
Estamos chegando ao fim dos diferentes tipos de herança em C++. É aqui que a situação é um pouco mais matizada. Embora a herança multinível tecnicamente crie uma hierarquia, isso é diferente do que é conhecido como herança hierárquica. Se você estiver criando várias classes derivadas de uma classe pai, isso é conhecido como herança hierárquica. Isso é útil quando você precisa criar classes relacionadas a partir de uma classe pai, mas cada uma com suas próprias propriedades. O código abaixo mostra este exemplo.
class Vehicle { public: std::string type; std::cor da string; }; class Car: public Vehicle { public: std:: string model; int ano; }; class Truck: public Vehicle { public: int capacidade; };
Novamente, temos a classe pai “Vehicle” e as classes “Car” e “Truck” são derivadas dela. Ambas as classes herdam as variáveis “tipo” e “cor”, mas também adicionam suas próprias propriedades. Ou seja, “modelo” e “ano” para a classe “Carro” e “capacidade” para a classe “Caminhão”. Na primeira tela, podemos ver o código implementado com a função “main()”, com uma instância de cada classe criada e seus atributos definidos. A segunda captura de tela mostra a saída no console.
Herança hierárquica implementada em uma classe.
©”TNGD.com
A saída do exemplo acima.
©”TNGD.com
Hybrid Herança
A situação fica um pouco mais complexa quando se trata de herança híbrida. É aqui que diferentes tipos de herança em C++ são combinados em uma hierarquia de classes. Embora isso possa ser complexo, às vezes é necessário para as operações que você precisa realizar. Considere a seguinte situação:
#include #include class Vehicle { public: std::string type; std:: cor da string; }; class Engine { public: int cavalos de potência; }; classe Carro: Veículo público, Motor público { public: std::string model; int ano; }; class ElectricCar: public Car { public: int batteryCapacity; }; in main() { ElectricCar tesla; tesla.type=”Carro elétrico”; tesla.color=”Vermelho”; tesla.model=”Modelo S”; tesla.horsepower=300; tesla.ano=2021; tesla.batteryCapacity=100; tesla.type=”Carro elétrico”; std::cout”Tipo:”tesla.type std::endl; std::cout”Cor:”tesla.color std::endl; std::cout”Horsepower:”tesla.horsepower std::endl; std::cout”Modelo:”tesla.model std::endl; std::cout”Ano:”tesla.year std::endl; std::cout”Capacidade da bateria:”tesla.batteryCapacity std::endl; retornar 0; }
Como antes, temos a classe pai “Vehicle”, mas também a classe pai “Engine”. Temos um exemplo de herança múltipla, onde a classe “Car” é derivada das classes “Vehicle” e “Engine”. A classe “ElectricCar” é então derivada da classe “Car”, que é um exemplo de herança multinível, pois é um tipo de carro mais especializado. Portanto, temos um cenário onde a herança híbrida está ocorrendo. Como tal, a herança híbrida pode ser útil para ajudar a simplificar e entender relacionamentos complexos, bem como tornar o código mais fácil de manter.
A primeira imagem ilustra a implementação do código, onde criamos uma instância do Classe “ElectricCar” com seu conjunto de atributos de todas as suas classes pai. A saída impressa é mostrada na segunda imagem.
Hybrid Herança implementada em uma classe.
©”TNGD.com
A saída do exemplo acima.
©”TNGD.com
Uma nota sobre ambigüidade
Um problema muito comum que pode surgir ao lidar com várias classes é a questão da ambigüidade. É aqui que você tem variáveis ou funções de membro idênticas em duas ou mais classes. O compilador pode então apresentar erros, pois não pode decidir qual membro usar para a operação que você está tentando executar. Considere o seguinte código:
#include class A { public: void foo() {std::cout”A:foo()”std::endl;} }; class B { public: void foo() {std::cout”B::foo()”std::endl;} }; classe C: público A, público B { público: }; int main() { Cc; c.foo();//Erro do compilador: chamada ambígua para foo() de A e B return 0; }
Aqui, temos uma função chamada “foo()” tanto na classe “A” quanto na classe “B”. Como a classe “C” herdou de ambas as classes, ocorre um erro durante a compilação. Isso pode ser resolvido de algumas maneiras diferentes:
Renomear a função ou variável conflitante na classe pai ou derivada para evitar confusão. Usar o operador de resolução de escopo (::) para especificar a classe que contém a função de membro desejada. A herança virtual funciona com uma noção semelhante à função virtual mencionada anteriormente. Isso pode permitir que uma classe derivada herde das classes pai sem duplicar a classe base que esses pais compartilham. Substituir a função conflitante redefinindo a função na classe derivada com uma implementação especializada. Um erro de ambiguidade ilustrado.
©”TNGD”.com
Resumo
Existem 5 tipos principais de herança em C++ que você pode usar, cada um com seus próprios casos de uso específicos. Embora a herança única possa ser adequada para operações relativamente simples, ao trabalhar com objetos e relacionamentos complexos, outros tipos, como herança múltipla, multinível, hierárquica e híbrida, podem ser mais apropriados. Eles podem ser úteis para tornar o código mais intuitivo e fácil de manter, além de representar melhor o relacionamento entre as classes.
Herança em C++ explicada, com exemplos FAQs (perguntas frequentes)
O que são classes pai e classes derivadas?
Uma classe pai, também conhecida como classe base, é a classe herdada. Por outro lado, uma classe derivada, subclasse ou classe filha herda propriedades e atributos da classe pai.
O que é herança em C++?
Herança é um processo que permite criar uma classe derivada de uma classe pai ou base, que herda propriedades e funções de membro da classe pai. Isso pode ser útil para representar relacionamentos complicados e reduzir a necessidade de repetir a implementação do código.
Quais são os diferentes tipos de herança em C++?
Existem 5 principais tipos de herança em C++ – simples, múltipla, multinível, hierárquica e híbrida. Único e múltiplo referem-se a uma única classe derivada de uma ou mais de uma classe base, respectivamente. Herança multinível significa quando uma classe é derivada de uma classe derivada. Herança hierárquica refere-se a quando várias classes são derivadas de uma classe base, e herança híbrida significa que uma mistura desses outros tipos é usada.
Como você implementa herança em C++?
Para fazer uma classe herdar de uma classe pai, você usa dois pontos (:), seguido pelo especificador de acesso (público ou protegido neste caso) e o nome da classe base.
O que são membros públicos, protegidos e privados?
Os membros públicos são completamente visíveis e acessíveis de qualquer lugar dentro de um programa e podem ser acessados por qualquer função dentro ou fora da classe. Os membros protegidos podem ser acessados por classes derivadas, portanto, podem ser herdados. Membros privados não podem ser acessados por nenhuma função fora da classe e são invisíveis para classes derivadas. No entanto, membros privados podem ser indiretamente se uma função pública que atua sobre eles for herdada.
Qual é a diferença entre herança pública, privada e protegida?
A herança pública é onde todos os membros públicos da classe base se tornam membros públicos da classe derivada e, da mesma forma, com membros protegidos e privados. A herança protegida é onde os membros públicos e protegidos são herdados, mas os membros privados permanecem inacessíveis. Herança privada significa que membros públicos e protegidos se tornam membros privados da classe derivada e, como sempre, membros privados permanecem invisíveis.
Como você resolve a ambiguidade na herança?
Você pode resolver a ambigüidade usando o gerador de resolução de escopo, usando herança virtual ou renomeando ou redefinindo as funções conflitantes.