For more blog posts, see the complete catalog of the romantic carriage of audio and video systems learning

Although the first taste of THE WORLD of C++ is very long, but as the title of the “first taste” general, write a brief introduction to the differences between C++ and C language, this chapter will be aimed at C++ classes and objects, start to discuss inheritance, polymorphism related content. Everybody sit down and hold on, let’s continue to start ~

inheritance

JAVA programmers certainly know a lot about inheritance. The so-called inheritance is that a class evolves from another class, adds some members and methods on the basis of the original class, and can access the members and methods of the original class. The inherited class is called parent class or base class, and the inherited class is called subclass or derived class. The function of inheritance is to realize the reuse of code from the perspective of code. The code of the parent class can be shared by multiple subclasses, thus saving redundant code. From the perspective of macro development thought, inheritance is an upgrade of object orientation. The detailed classification of things is closer to the habit of thinking that people are not only good at classification, but also good at derivative changes (or more subdivision) of the same kind of things. Not only tyrannosaurus and Garulu, tyrannosaurus can also be upgraded to mechanical tyrannosaurus and combat Tyrannosaurus, which has the same combat skills as before, but also a new skill, but it’s still tyrannosaurus, so that’s the basis for the later polymorphism.

For example, there are animals:

class Animal {

private:
    char * name;

protected:
    int age;
    
public:
    char *getName(a);
    int getAge(a);

};
Copy the code

Create Dog from Animal:

class Dog : public Animal {
private:
    char *noseColor;
    
protected:
    char *tailColor;
    
public:
    void eat(a);
};
Copy the code

In this way, Dog can call not only its own eat method, but also its parent’s getName method.

Dog dog;
// Call the method of the parent class
dog.getName(a); dog.eat(a);Copy the code

Can’t say that, of course, Dog can have much in the way of all the members of the Animal, inheritance is permission problems should be paid more attention to, can have not represent certain (is to have the right to call for method to accurately), after all the parent class also have privacy, itself has the right to decide which can be used to subclass, which is opposite to one of the important characteristics: encapsulation. Obviously, a private member or method of a parent class should not be used by a child class. After all, it is private. The protected modifier itself is reserved for child classes, so they can use it. The difference between C++ and Java is that subclasses themselves can also determine how much control they have over the members or methods of their superclass. Note the “class Dog: “Public Animal”, adding “public” in the middle, this is the inheritance method, so the subclass can use the parent class member or method permissions are determined by the parent class modifier and the inheritance method, see below:

  1. Public Inheritance

All public members in the base class are public attributes in the derived class; All protected members of the base class are protected attributes in derived classes. All private members of the base class cannot be used in derived classes.

  1. Protected inheritance

All public members of the base class are protected in derived classes; All protected members of the base class are protected attributes in derived classes; All private members in the base class cannot be used in derived classes.

  1. Private Inheritance

All public members in the base class have private attributes in the derived class; All protected members of the base class are private in derived classes; All private members in the base class cannot be used in derived classes.

At first glance it seems very complicated, I do not know how to remember, in fact, in a word:

Public, protected, and private are used to specify the highest access rights of a base class member in a derived class (those higher are reduced to that, and those lower remain the same).

Is it suddenly clear?

For example, changing the Dog inheritance mode to private:

class Dog : private Animal {
private:
    char *noseColor;
    
protected:
    char *tailColor;
    
public:
    void eat(a);
};
Copy the code

The following statement returns an error because all Animal members and functions are private for Dog in the parent class, so the getName function is not callable for Dog.

dog.getName(a);Copy the code

Constructors and destructors in inheritance

Subclasses can inherit all the methods of their parent class (or, more accurately, get the right to call them), but there is one special method that cannot be inherited: constructors. Constructors are called automatically when the object is created, so it makes no sense to inherit the constructor from the parent class. But subclasses class member in subclasses to create objects inherited from the father is still need to be initialized, initialization logic is still in the superclass constructor even if want to be in subclasses initialization will members may not access for permission problems, so in order to make the parent class members to create objects in a subclass of the initialization, Here you need to manually call the parent class’s constructor in the subclass’s constructor.

For example, add a constructor to Animal in the example above:

Animal(char * name);
Copy the code

When adding a constructor to Dog, it is similar to the initialization list described in the first world of C++, where it is required to call Animal’s constructor or clion will report an error. “Constructor for ‘Dog’ must explicitly initialize the base class ‘Animal’ which does not have a default Constructor”) :

Dog::Dog(char *noseColor) : Animal(noseColor) {
   
}
Copy the code

Yes, the first line of the Java constructor calls the superclass constructor for the same reason.

Another thing to note is the order in which the constructor and destructor are called:

Dog *dog = new Dog("aass");
delete dog;
Copy the code

The result is:

Animal constructor

Dog constructor Dog destructor Animal destructor

It is clear from this small example that the constructor calls the parent class first and then the child class, while the destructor does the opposite. If you think about it, it makes sense that a subclass inherits its parent class, which is its dependency, and must initialize the dependent part first. Destructors, on the other hand, must be released after the dependent part.

Multiple inheritance

C++ multiple inheritance seems to be powerful, but because of its complexity and chaos, so it is widely criticized by programmers, so that later generations of Java, C# and other descendants are categorically abandoned multiple inheritance, but as a feature of C++, here it should be mentioned.

Multiple inheritance, as the name suggests, means that one class inherits multiple classes.

Add a Cat class from Animal:

class Cat : public Animal {

private:
    char *noseColor;
    
protected:
    char *tailColor;
    
public:
    void eat(a);
    
    Cat(char *noseColor);
    
    ~Cat(a); };Copy the code

Add a Pet class that inherits both Dog and Cat:

class Pet : public Dog, Cat{
public:
    Pet(char *noseColor1, char *noseColor);

    void howOld(a);
};
Copy the code

The constructor of this class should call the constructors of both parent classes:

Pet::Pet(char *noseColor1, char *noseColor) : Cat(noseColor1), Dog(noseColor) {
}
Copy the code

The problem with multiple inheritance is that it uses members or methods of the same name as the parent class, so that the compiler does not know which parent class to use, so it has to specify which parent class to use:

Suppose the howOld method is implemented in Pet:

void Pet::howOld(a) {
    std::cout << "age" << age << std::endl;
}
Copy the code

Pet inherits Dog and Cat from Animal, so it has a diamond shape: Because in C++ multiple inheritance, even members of the same parent class will have multiple copies, that is, each path of the base class will have one copy of the member dataSo the age used in Pet does not know whether it is inherited from the Pet–Dog–Animal or Pet–Cat–Animal path.

The solution is also so easy, do not know which parent to specify ~~

void Pet::howOld(a) {
    std::cout << "age" << Dog::age << std::endl;
}
Copy the code

Add the class name and the domain resolver :: to solve the problem.

Virtual inheritance

As mentioned above, the ambiguity of multiple inheritance is a problem that the C++ creators found inconvenient and eliminated syntactically. It is called virtual inheritance.

Start by making a small tweak to the definitions of Dog and Cat by adding the “virtual” keyword before the inheritance modifier:

class Dog : **virtual** public Animal ```

```cpp
class Cat : **virtual** public Animal
Copy the code

Pet does not return an error even if age is not specified:

void Pet::howOld(a) {
    std::cout << "age" << age << std::endl;
}
Copy the code

Why? In fact, it is very simple. The root cause of the ambiguity problem is that multiple inheritance causes each path of the base class to have a copy of the member data. Virtual inheritance means that different inheritance paths share the data of the base class, that is, the member data of the base class keeps a copy.

Can be simply verified by previous procedures:

void Pet::howOld(a) {
    Cat::age = 10;
    Dog::age = 20;
    std::cout << "age:" << age << std::endl;
}
Copy the code

Age: 20

So whether it’s Cat::age or Dog::age, it’s the same age.

Because the path and level of multiple inheritance once complex, debugging and maintenance work becomes very difficult, so it is abandoned by many high-level languages, so here is a brief introduction, or to the next more worthy of our careful study of the topic.

polymorphism

Always feel that polymorphism is facing the essence of the opposite, with polymorphism, can be very flexible at the time of the object pointer assignment, through the abstract class (Java can also use the interface) to achieve the decoupling of the class, making the system loose coupling, strong scalability.

With polymorphism, we can be invoked in the block of code that holds a base class pointer to call the base class a method, but in fact the base class pointer to the object is to determine at run time, so I decided to call at this time of the object is which method depends on how the external to the base class pointer assignment objects, is a typical dependency injection.

Dog and Cat inherit from Animal. WhoAmI = whoAmI

  char* whoAmI(a);
Copy the code

Implemented as:

char *Animal::whoAmI(a) {
    return "Animal";
}
Copy the code

Rewrite this method for Pet, Dog and Cat respectively:

Cat:

char *Cat::whoAmI(a) {
    return "Cat";
}
Copy the code

Dog:

char *Dog::whoAmI(a) {
    return "Dog";
}
Copy the code

Pet:

char *Pet::whoAmI(a) {
    return "Pet";
}
Copy the code

Suppose it were written like this:

Animal *animal = new Cat("aa");
std::cout << animal->whoAmI() << std::endl;
Copy the code

What happens when you print it?

Those of you who are familiar with Java will blurt out Cat

Congratulations, you got it wrong

Take a look at the results:

Animal

What happened to polymorphism? It’s still a superclass method, so what’s the point?

In C++, when a function is accessed through a pointer, the compiler determines the function to be called based on the type of the pointer unless specified. That is, the function of the class to which the pointer points is called. In fact, calling the base class pointer to call the base class method, is a very natural way, but familiar with Java children’s thinking has been used to it, so now it is not used to. So for C++ to truly implement polymorphism, it is important to use the keyword virtual to specify that a method is a method that can use polymorphism, that is, virtual functions.

Why Java can use polymorphism without specifying virtual? See the article Virtual Function in Java

In fact, it depends on this sentence:

By default, all the instance methods in Java are considered as the Virtual function except final, static, and private methods as these methods can be used to achieve polymorphism.

We know that Java is a lite version of C++, so Java doesn’t feel the need to drop its pants and fart. It calls methods on whatever object they are called on, so methods default to virtual functions, except for final, static, and private ones, because those functions can’t be overridden by derived classes. So it doesn’t have to be a virtual function.

So how does this example make Cat’s whoAmI method get called correctly? We simply add virtual to the declaration of the base whoAmI method (and the same to the whoAmI method of Cat, because if the base method is virtual, then the derived method overridden is also virtual) :

virtual char* whoAmI(a);
Copy the code

Run code:

Cat

This is polymorphic, where the whoAmI method of the Animal pointer is called, and the whoAmI method of the Cat object is actually called (Pet, Dog).

Polymorphic benefits:

Let’s look at the specific benefits of polymorphism:

Suppose there is a class People:

class People {
   
    Animal *animal = nullptr;
    // Inject specific animal species
    void setAnimal(Animal* animal);
    // Let the animal tell me its type
    void animalTellMeYourName(a);
};
Copy the code

Implementation:

void People::setAnimal(Animal *animal) {
    this->animal = animal;
}

void People::animalTellMeYourName(a) {
    this->animal->whoAmI(a); std::cout <<"I am:" << this->animal->whoAmI() << std::endl;
}
Copy the code

Here we only know that the person has an animal, but we don’t know what animal it is. This gives your application the flexibility to use the People class and give it different animals for specific scenarios without having to modify the People code.

If the specific scene needs to give People a cat, then:

People *people = new People(a); Cat *cat =new Cat("aa");
  // Give people a cat
  people->setAnimal(cat);
  // Let people hold the animal to tell me its category
  people->animalTellMeYourName(a);Copy the code

Running results:

I am:Cat

Injecting a Dog into People would do the same, as long as the whoAmI method is virtual. In the future, the external only needs to make sure that the Animal given to People is an Animal. People does not care what Animal it is. Therefore, People is loosely coupled with the external class, and no fixed Animal is written.

The principle of virtual function implementation will be introduced in detail in the principle post after introducing C++ syntax, please look forward to haha.

Virtual destructor

In our last taste of C++, we said that destruction of an object automatically calls the destructor, so what happens if we release the parent pointer (delete) when it points to a subclass object?

We print the name of each class (Animal, Dog, Cat, Pet) in the destructor of the above use-case program, and then execute it in the main function:

Animal *animal = new Cat("aa");
delete animal;
Copy the code

So what happens?

Running results:

Animal destructor

Animal destructor is called, Cat object is released, Cat destructor is not called Congratulations, you’ve stepped into one of the most common pits for C++ beginners, and it could cause a serious memory leak.

The destructor of Cat is a non-virtual function. The destructor of Cat is called as an Animal. The destructor of Cat is called as an Animal. (Do you feel powerless to make fun of, but still feel some sense = =)

Animal destructor = virtual; Cat destructor = virtual; Animal destructor = virtual; Cat destructor = virtual; Animal destructor = virtual;

virtual ~Animal(a);Copy the code

The destructors for Cat, Dog, and Pet are automatically virtual.

Run output:

Cat destructor

Animal destructor

The Cat destructor is then called normally

So when writing a class that needs to be inherited, remember to set the destructor to virtual, otherwise you may have a big memory leak.

Pure virtual function

Virtual function name is a special case of virtual functions — pure virtual functions.

When I first saw the name, I thought, how pure is this virtual function? Yes, it is pure, pure to the point where there is no implementation:

Suppose we add an shut virtual function to the lowest base Animal in the above example:

class Animal {

private:
    char * name;

protected:
    int age;
    
public:
    char *getName(a);
    int getAge(a);
    
    Animal(char * name);
    
    virtual ~Animal(a);virtual char* whoAmI(a);
    // Add pure virtual function shut
    virtual void shut(a) = 0;

};
Copy the code

As you can see, this function is not implemented and ends with “=0”, which is the legendary pure virtual function.

What can a pure virtual function do? Let’s go back to where we instantiated Cat in the polymorphic example above:

Animal *animal = new Cat("aa");
Copy the code

This line of code is not compiled.

Allocating an object of abstract class type ‘Cat’, unimplemented pure virtual method ‘shut’ in ‘Cat’

Those of you who have written Java should be familiar with the feeling that Java instantiates an abstract class error.

When a class has at least one pure virtual function, it becomes an abstract class. Because there are no real functions, it cannot be instantiated, so it can only be deferred to the implementation of derived classes. Moreover, a class must be guaranteed to have all pure virtual functions that implement it in the entire inheritance system before it can be instantiated.

Animal shut has a function shut, but we don’t know which Animal it is, so it’s up to the subclass, the Animal, to shut. We implement the shut method on Cat and Dog, respectively:

Cat.h:

void shut(a) override;
Copy the code

Cat.cpp:

void Cat::shut(a) {
    std::cout << "miao miao ~~" << std::endl;
}
Copy the code

Dog.h:

void shut(a) override;
Copy the code

Dog.cpp:

void Dog::shut(a) {
    std::cout << "wang wang ~~" << std::endl;
}
Copy the code

The main. CPP:

Animal *pCat = new Cat("aa");
  pCat->shut(a); Animal *pDog =new Dog("aa");
  pDog->shut(a);delete pCat;
  delete pDog;
Copy the code

Run:

Animal constructor

Dog Dog Dog Dog Dog Dog Dog Dog Dog Dog Dog Dog

Combined with the constructors, destructors, and polymorphisms we talked about earlier, it looks like there is no problem printing the results

conclusion

Inheritance in C++ is very similar to Java, except that the control of permissions is a little more complicated. The biggest difference is the ability to have multiple inheritance, but this makes it so complex and problematic that later languages have largely abandoned this syntactic feature.

In C++, the destructor of the subclass is not a virtual function, which causes the destructor to not be called, which causes the program to leak memory.

Finally, pure virtual functions, which are basically similar to Java abstract functions, are also very easy to understand for Java developers.