C++ condensed study notes (3) - object-oriented

 Main quotes from the article:

Axiu’s study notes (interviewguide.cn)

Nuke.com - Job search tool | Written test question bank | Interview experience | Recommended internship recruitment, one-stop solution for job hunting and employment_Nuke.com (nowcoder.com)

1. Object-oriented

Briefly describe what object-oriented is (Tencent)

Reference answer

  1. Object-oriented is a programming idea that regards everything as an object, such as people, headphones, mice, water cups, etc. Each of them has attributes, for example: headphones are white, the mouse is black, and the water cup is cylindrical. etc., package the attribute variables owned by these objects and the functions that operate these attribute variables into a class to represent

  2. The difference between process-oriented and object-oriented

    Process-oriented: Write code from top to bottom based on business logic , divide modules according to functions, each module is relatively independent , and the specific method of modular implementation is to use subroutines. However, its data and processing processes are independent entities. When the data changes, all related processing must be changed accordingly, resulting in poor program availability.

    Object-oriented: Bind data and functions together and encapsulate them, so that programs can be developed more quickly and the process of rewriting duplicate codes can be reduced.

Briefly describe the three major characteristics of object-oriented

Reference answer

The three major characteristics of object-oriented are encapsulation, inheritance, and polymorphism.

  1. Encapsulation: Organically combine data and methods of operating data , hide the properties and implementation details of the object , and only expose the interface to interact with the object. Encapsulation is essentially a form of management : How do we manage the Terracotta Warriors? For example, if nothing is taken care of, the Terracotta Warriors and Horses will be destroyed at will. So we first built a house to enclose the terracotta warriors and horses. But our purpose is completely encapsulated and not allowed to be seen by others. Therefore, we have opened the ticket sales channel, and you can buy tickets to visit under reasonable supervision mechanisms. The same goes for classes. If we don’t want others to see them, we use protected/private to encapsulate the members. Allow some shared member functions to have reasonable access to members. So encapsulation is essentially a kind of management.

  2. Inheritance: You can use all the functionality of an existing class and extend these functionality without having to rewrite the original class .

    Three inheritance methods

    Inheritance method private inheritance protected inheritance public inheritance
    private members of base class Invisible Invisible Invisible
    protected members of the base class Become a private member Still a protected member Still a protected member
    public members of base class Become a private member Become a protected member Still a public member Still a public member
  3. Polymorphism: includes static polymorphism (function overloading) and dynamic polymorphism (subclass overrides the virtual function of the parent class, which can only be a virtual function) , generally refers to dynamic polymorphism. Polymorphism is the reuse of functions , and its essence also comes down to the reuse of code.

2. Inheritance

Inheritance and derivation

Three inheritance methods: public, private, protected. 

Type compatibility rule: Anywhere a base class object is required, an object of a public derived class can be substituted. ( The public derived class has obtained all members of the base class except the constructor and destructor, and has all the functions of the base class. Whatever the base class can solve, the public derived class can solve.)

  1. Derived class objects can be implicitly converted to base class objects.

  2. Derived class objects can initialize references to base classes.

  3. A pointer to a derived class can be implicitly converted to a pointer to a base class.

Note: After substitution, the derived class object can only use members inherited from the base class and only play the role of the base class. P264

The scope identifier can also be used to uniquely identify the inherited members in the derived class to achieve access purposes and solve the problem of hidden members.

class Derived: public Base1, public Base2 {}  //
//Assume that both base class and derived class have fun();
Derived d;
d.fun(); //Access Derived’s fun()
d.Base1::fun(); //Access Base1’s fun()
d.Base2::fun(); //Access Base2’s fun()
Virtual base class:

Set the common base class to a virtual base class. At this time, data members with the same name inherited from different paths have only one copy in the memory , and the same function has only one mapping .

class Base0 {int var0};
class Base1: virtual public Baes0 {int var1}
class Base2: virtual public Baes0 {int var2}
class Derived : public Base1, public Base2 {int var};
Derived d;
d.var0 = 2; //Directly access the data members of the virtual base class.

What is the difference between composition and inheritance? ? ?

Combination: whole-part relationship. For example, a car (as a whole) is composed of (parts of) functions such as wheels and engines, and can move.

Inheritance: special-general relationship. For example, a car can run and carry people, but it can be derived as a truck, which has the general functions of a car and can also pull goods.

Let’s talk about class inheritance, the access rights of derived classes to base class methods modified by different keywords

C++ controls the access rights of member variables and member functions through three keywords: public, protected, and private. They represent public, protected, and private respectively, and are called member access qualifiers.

Keywords Permissions
public Can be accessed by any entity
protected Only allow access to member functions of subclasses and this class
private Only member functions of this class are allowed access

Members in a class can be divided into three types: public members, protected members, and public members. A class can directly access the public, protected, and private members of its own class, but a class object can only access the public members of its own class.

  1. Public inheritance: The derived class can access the public and protected members of the base class, but cannot access the private members of the base class; the derived class object can access the public members of the base class, but cannot access the protected and private members of the base class.

  2. Protected inheritance: Derived classes can access the public and protected members of the base class, but cannot access the private members of the base class; derived class objects cannot access the public, protected, and private members of the base class.

  3. Private inheritance: Derived classes can access the public and protected members of the base class, but cannot access the private members of the base class; derived class objects cannot access the public, protected, and private members of the base class.

Private, protected, public usage and differences

Public access rights: The public member variables and member functions of a class can be accessed through the member functions and instance variables of the class.

Protected access rights: Protected member variables and member functions of a class cannot be accessed through instance variables of the class. However, it can be accessed through the friend function and friend class of the class.

Private access rights: Private member variables and member functions of a class cannot be accessed through instance variables of the class. However, it can be accessed through the friend function and friend class of the class.

Through public inheritance, all base class members (except private), public, and protected are in the derived class. The public filter is relatively large and the access rights will not be changed.
Through protected inheritance, all base class members (except private), public, and protected are transferred to the derived class. The size of the protected mesh is moderate, and all passed members become protected.
Through private inheritance, all base class members (except private), public, and protected are transferred to the derived class. Private has the smallest mesh, and all passed members become private.

Private inheritance ends once, but protected can be inherited forever;

In an inheritance relationship, can subclass objects and parent class objects assign values ​​to each other?

(1) The subclass object contains not only the variables inherited from the parent class, but also its own unique variables. When assigning the subclass object to the parent class object, the parts common to both are assigned. (Up)

(2) On the contrary, when a parent class object is assigned to a subclass object, an error will be reported because the parent class object cannot provide variables unique to the subclass object. (Down)

(3) Object pointers also have such usage rules. Subclass pointers can be directly assigned to parent class pointers. When assigning parent class pointers to subclasses, display type conversion is required.

For example: Define a base class parent, and the subclass child inherits parent, and performs the following operations:

Parent = Child; Child = Parent; Is Child still the same as the original Child?

Child = Parent; //will report an error

Richter substitution principle:

  1. Subclasses can implement abstract methods of the parent class, but cannot override non-abstract methods of the parent class .

  2. Subclasses can add their own features .

  3. When a class method overrides a parent class method, the method's preconditions ( formal parameters ) are looser than the input parameters of the parent class method.

  4. The output ( return value ) can be reduced when overriding or implementing a parent class method .

 What are the problems with multiple inheritance?

  1. Increases the complexity of the program , making it difficult to write and maintain the program and prone to errors;

  2. During inheritance, when members have the same name between base classes or between a base class and a derived class, there will be uncertainty in member access, that is, ambiguity of the same name ;

    Solution: (1) Use scope identifiers ::, which uniquely identify inherited members in derived classes to achieve access purposes;

    ​ (2) Define members with the same name in the derived class and override related members in the base class;

  3. When a derived class is derived from multiple base classes, and these base classes are derived from the same base class, another type of uncertainty will arise when accessing members of this common base class, that is, path ambiguity ;

    Solution: Use virtual inheritance (virtual base class) so that members with the same name inherited from different paths have only one copy in memory.

Virtual base class: Add the virtual keyword in front of the inherited class to set the common base class as a virtual base class . At this time, data members with the same name inherited from different paths have only one copy in the memory, and there is only one copy of the same function. mapping. Virtual base classes can be instantiated.

Supplement: It can also be solved by solving the ambiguity of the same name, but there are several copies of the data member with the same name.

Let’s talk about what virtual inheritance is, what problems it solves, and how to implement it?

Virtual inheritance is a means to solve the problem of multiple inheritance in C++ . The same base class inherited from different ways will have multiple copies in subclasses. This will have two problems: first, it wastes storage space; second, there is an ambiguity problem. Usually, the address of the derived class object can be assigned to the base class object. The specific way to achieve this is to point the base class pointer to the inherited class. (The inherited class has a copy of the base class), but multiple inheritance may have multiple copies of a base class, which creates ambiguity. Virtual inheritance can solve the two problems mentioned earlier in multiple inheritances.

#include<iostream>
using namespace std;
class A{
public:
    int _a;
};
class B :virtual public A
{
public:
    int _b;
};
class C :virtual public A
{
public:
    int _c;
};
class D :public B, public C
{
public:
    int _d;
};
//Object model of diamond inheritance and diamond virtual inheritance
int main()
{
    D d;
    d.B::_a = 1;
    d.C::_a = 2;
    d._b = 3;
    d._c = 4;
    d._d = 5;
    cout << sizeof(D) << endl;
    return 0;
}



Analyze from diamond inheritance and virtual inheritance respectively:

In diamond inheritance, A has a copy among B, C, and D. In virtual inheritance, A shares.

The virtual inheritance table above is actually an array of pointers. B and C are actually virtual base table pointers, pointing to the virtual base table.

Virtual base table: stores relative offsets and is used to find virtual base classes

Let’s talk about what is the diamond inheritance problem in C++ and how to solve it

Reference answer

  1. The diagram below can be used to explain the diamond inheritance problem.

  • Suppose we have class B and class C, both inherit from the same class A. In addition, we have class D, which inherits classes B and C through the multiple inheritance mechanism. Because the shape of the above graph is similar to a rhombus, this problem is vividly called the rhombus inheritance problem. Now, we translate the above diagram into concrete code:

    /* *The Animal class corresponds to the chart class A* */ class Animal { /* ... */ }; // Base class { int weight; public: int getWeight() { return weight; } }; class Tiger : public Animal { /* ... */ }; class Lion : public Animal { /* ... */ } class Liger : public Tiger, public Lion { /* ... */ }
    
    
    

    In the code above, we give a concrete example of the diamond inheritance problem. The Animal class corresponds to the top-level class (A in the diagram), Tiger and Lion correspond to B and C in the diagram respectively, and the Liger class (liger, a hybrid of a tiger and a lion) corresponds to D.

    Now, the question is what kind of problems will arise if we have this inheritance structure.

    Take a look at the code below and then answer the question.

      int main( ) { Liger lg; /*Compilation error, the following code will not be passed by any C++ compiler*/ int weight = lg.getWeight(); }
    
    
    
  • In our inheritance structure, we can see that both the Tiger and Lion classes inherit from the Animal base class. So the question is: because Liger multiplely inherits the Tiger and Lion classes, the Liger class will have two members (data and methods) of the Animal class, and the Liger object "lg" will contain two sub-objects of the Animal base class.

    So, you ask, what's the problem with a Liger object having two sub-objects of the Animal base class? Look at the code above again - calling "lg.getWeight()" will cause a compilation error. This is because the compiler does not know whether to call getWeight() of the Tiger class or getWeight() of the Lion class. Therefore, calling the getWeight method is ambiguous and therefore fails to compile.

  1. We gave an explanation of the diamond inheritance problem, but now we want to give a solution to the diamond inheritance problem. If the Lion class and the Tiger class are marked with virtual when inheriting the Animal class respectively, for each Liger object, C++ will ensure that only one sub-object of the Animal class will be created. Take a look at the following code:

    class Tiger : virtual public Animal { /* ... */ }; class Lion : virtual public Animal { /* ... */ };
    
    
    
  • You can see that the only change is that we added the "virtual" keyword to the declarations of the Tiger and Lion classes. Now the Liger-like object will have only one Animal sub-object, and the following code compiles fine:

    int main( ) { Liger lg; /*Since we have declared the "virtual" keyword in the definition of the Tiger and Lion classes, the following code compiles OK */ int weight = lg.getWeight(); }
    

3. Polymorphism

What is RTTI?

RTTI is Runtime Type Identification . This mechanism is introduced in C++ so that the program can obtain the actual type of the object pointed to by the pointer or reference based on the pointer or reference of the base class at runtime.

RTTI is runtime type identification, and its function is implemented by two operators:

  1. typeidOperator, used to return the type of expression, can obtain the data type of the derived class through the pointer of the base class;

  2. dynamic_castOperator, with type checking function, is used to safely convert a pointer or reference from a base class to a pointer or reference from a derived class.

RTTI only applies to classes containing virtual functions . Because only for this kind of class hierarchy, the address of the derived class should be assigned to the base class pointer.

Briefly describe polymorphism in C++

In a narrow sense: Polymorphism is divided into static polymorphism and dynamic polymorphism :

  1. Static polymorphism : Completed by the compiler during compilation. The compiler will infer which function to call based on the actual parameter type. If there is a corresponding function, it will be called. If not, an error will be reported during compilation.

    For example, a simple addition function:

    include<iostream>
    using namespace std;
    
    int Add(int a,int b)//1
    {
        return a+b;
    }
    
    char Add(char a,char b)//2
    {
        return a+b;
    }
    
    int main()
    {
        cout<<Add(666,888)<<endl;//1
        cout<<Add('1','2');//2
        return 0;
    }
    
    
    
    

    Obviously, the first statement will call function 1, and the second statement will call function 2. This is not because of the declaration order of the functions. If you don’t believe it, you can try changing the order.

  2. Dynamic polymorphism : In fact, to achieve dynamic polymorphism, several conditions are required—namely, dynamic binding conditions:

    1. virtual function. There must be virtual functions in the base class, and virtual functions must be overridden in the derived class.

    2. A virtual function is called through a pointer or reference of the base class type.

    Speaking of this, we have to introduce a concept: rewriting - that is, there is a virtual function in the base class, and a virtual function with the same prototype (return value, name, parameters) must be rewritten in the derived class. The exception is covariance. Covariance is a special case of overriding. In a base class, the return value is a reference or pointer of the base class type. In a derived class, the return value is a reference or pointer of the derived class type.

    //covariance test function
    #include<iostream>
    using namespace std;
    
    class Base
    {
    public:
        virtual Base* FunTest()
        {
            cout << "victory" << endl;
            return this;
        }
    };
    
    class Derived :public Base
    {
    public:
        virtual Derived* FunTest()
        {
            cout << "yeah" << endl;
            return this;
        }
    };
    
    int main()
    {
        Base b;
        Derived d;
    
        b.FunTest();
        d.FunTest();
    
        return 0;
    }
    

Broadly speaking: Polymorphism refers to the ability of a program to handle multiple types of objects.

It can be implemented in four forms: forced polymorphism, overloaded polymorphism, inclusion polymorphism, and parameter polymorphism .

  1. Forced polymorphism: Achieved by converting one type of data into another type of data through data type conversion (implicit or explicit).

  2. Overloading polymorphism : function overloading, operator overloading.

  3. Contained polymorphism: Use virtual functions to implement contained polymorphism. Rewrite!

  4. Parametric polymorphism: implemented using templates, divided into function templates and class templates.

    The first two are special polymorphisms, which are superficial polymorphisms (static polymorphisms), and the latter two are general polymorphisms, which are real polymorphisms (dynamic polymorphisms).

  5. Necessary conditions for dynamic polymorphism:

    1. Inheritance is required;

    2. Virtual function coverage is required;

    3. A base class pointer/reference is required to point to the subclass object;

Briefly describe overloading and rewriting in C++ and their differences

Reference answer

  1. Overriding (dynamic polymorphism)

    Refers to the existence of redefined functions in derived classes . Its function name, parameter list, and return value type must all be consistent with the overridden function in the base class. Only the function body is different (in curly braces). When the derived class object calls it, the overridden function of the derived class will be called, and the overridden function will not be called. The overridden function in the overridden base class must have virtual modification.

    Examples are as follows:

#include<bits/stdc++.h> using namespace std; class A { public: virtual void fun() { cout << "A"; } }; class B :public A { public: virtual void fun() { cout < < "B"; } }; int main(void) { A* a = new B(); a->fun();//Output B, fun in class A is rewritten in class B }
  1. Overloading (static polymorphism)

    In C++, people have proposed using one function name to define multiple functions , which is the so-called function overloading. Function overloading refers to several functions of the same name with different parameter lists (different parameter types, numbers, and orders) declared in the same accessible area. Which function to call is determined based on the parameter list. Overloading does not care about the function return type.

#include<bits/stdc++.h>  using namespace std;  class A {  void fun() {};  void fun(int i) {};  void fun(int i, int j) {};     void fun1(int i,int j){}; };

Rewrite (late binding, dynamic polymorphism) underlying implementation (Baidu)

The address of a virtual function is bound at runtime , while the address of a normal function is determined at compile time, and the address of the object call is already determined.

The dynamic is reflected in finding the actual function step by step through pointers: when a member function of a class is declared as a virtual function, a virtual function pointer (4/8 bytes) will be added to the object of the class, pointing to the virtual function table (Each object has its own virtual function pointer, and there is only one virtual function table. All virtual function pointers point to the same virtual function table.) The virtual function table stores the address of the virtual function, and subclasses inherit this virtual function table . After rewriting the virtual function of the parent class, the virtual function entry address in the subclass's virtual function table is changed to the virtual function entry address in the subclass (it remains unchanged without rewriting, and still points to the virtual function of the parent class). Now a parent The pointer of a class points to an object of a subclass (a reference is also possible, and the reference is essentially a pointer constant). When the overridden virtual function is called with the parent class pointer, the object of the subclass can be found based on the pointer . According to the The virtual function pointer finds the virtual function table and the virtual function in the virtual function table .

​ Static/non-static functions do not occupy the memory of the object. Only non-static member variables occupy the memory of the object. When there are virtual functions in the class, you will find that there are 4/8 more bytes in the class. This 4/ The 8 bytes are the virtual function pointer (vptr), which points to the virtual function table.

Virtual functions are stored in the virtual function table in the order of their declaration; the
virtual functions of the parent class are stored in front of the virtual functions of the subclass;
in multiple inheritance, each parent class has its own virtual function table;
the member functions of the subclass are stored In the virtual function table of the first parent class;

When were the contents stored in the virtual function table written? (Baidu)

Reference answer

  1. The virtual function table is an array that stores virtual function addresses, terminated by NULL. The virtual table (vftable) is generated during the compilation phase. After the object memory space is opened, the vfptr in the object is written, and then the constructor is called. That is: the virtual table is written before the constructor

  2. In addition to writing before the constructor, we also need to consider the secondary writing mechanism of the virtual table. Through this mechanism, the virtual table pointer of each object can accurately point to the virtual table of its own class. In order to achieve dynamic Polymorphism is supported. When the program is running, the appropriate member function is selected.

Overloading (early binding, static polymorphism) underlying implementation (Baidu)

C++ uses name mangling technology to rename function names to distinguish functions with the same name with different parameters. Name warping is done during the compilation phase.

C++ defines an overloaded function with the same name:

#include<iostream> using namespace std; int func(int a,double b) {  return ((a)+(b)); } int func(double a,float b) {  return ((a)+(b)); } int func(float a,int b) {  return ((a)+(b)); } int main() {  return 0; }

From the above figure, we can see that d represents double, f represents float, and i represents int. The first letter of the parameter is added to distinguish the function with the same name.

Let’s talk about how C language implements overloading in C++ language

Reference answer

Functions with the same name are not allowed in the C language, because the function names are the same during compilation, unlike C++, which adds parameter types and return types as the compiled names of functions to achieve overloading. If you want to display function overloading in C language, you can do it in the following ways:

  1. It is implemented using function pointers. Overloaded functions cannot use the same name, but the function overloading function is implemented similarly.

  2. Overloaded functions use variable parameters, such as opening a file open function

  3. gcc has built-in functions, and the program can implement function overloading by using compiled functions.

Examples are as follows:

#include<stdio.h> void func_int(void * a) { printf("%d\n",*(int*)a); //Output int type, note that void * is converted to int } void func_double(void * b) { printf("%.2f\n",*(double*)b); } typedef void (*ptr)(void *); //typedef declares a function pointer void c_func(ptr p,void *param) { p(param); //Call the corresponding function} int main() { int a = 23; double b = 23.23; c_func(func_int,&a); c_func(func_double,&b); return 0; }

Understanding virtual functions

Virtual functions are the basis of dynamic binding, and virtual functions must be non-static member functions . After the virtual function is derived, dynamic polymorphism can be achieved in the class family.

Function rewriting (overwriting): The base class declares a virtual function, and the derived class declares a function with the same name, the same parameters, and the same return value to rewrite the virtual function to achieve polymorphism.

Dynamic binding occurs only when a virtual function is called through a pointer or reference to the base class . This is because: the pointer of the base class can point to the object of the derived class, and the reference of the base class can be used as an alias of the derived class, but the object of the base class cannot represent the object of the derived class .

class Base1{
  virtual display(); //virtual function
}
class Base2: public Base1 {
  virtual display(); //Override base class virtual function
}
class Derived: public Base2{
  virtual display(); //Override base class virtual function
}
void fun (Base1* ptr) {ptr->display();} //The parameter is the base class pointer
fun(&base1); //base1 is the object of Base1
fun(&base2); //base2 is the object of Base2
fun(&derived); //derived is the object of Derived
result:
Base1::display()
Base2::display()
Derived::display()

Virtual table: Each polymorphic class has a virtual table. The virtual table contains the entry addresses of each virtual function of the current class . Each object has a pointer (virtual pointer vptr) pointing to the virtual table of the current class . The contents of the virtual table are arranged by the compiler. (Advantage: Save space, each object only needs to save a virtual pointer. (Pointer to the first address of the virtual table)) The storage address of each object is different, but they all point to the same virtual function table.vptr

Implementation of dynamic binding P341: First, the constructor assigns a value to the virtual pointer of the object. Then, when calling a member function through a pointer or reference of a polymorphic type, the entry address of the virtual function called in the virtual table is found through the virtual pointer. Finally, the virtual function is called through the entry address.

Supplement: Non-static member functions and destructors can be virtual functions, but virtual constructors cannot be declared.

Where is the virtual function pointer placed in the class? On a 64-bit machine, there are 10 virtual functions in the class. What is the size of sizeof(CLASS A)?

The virtual pointer exists in the object header and is 8 bytes (64 bits) in size. In order to support virtual functions, the compiler will incur additional burdens. The size of the virtual pointer (vptr) pointing to the virtual table is 8 Bytes on a 64-bit machine and 4 bytes on a 32-bit machine. No matter how many virtual functions a class has, each class object has only one pointer.

sizeof (an object of A) = non-static data member size + virtual pointer size (8 bytes) + extra space for data alignment ; the usually declared class is just a type definition and has no size itself. What you get by using the sizeof operator is The size of objects of this type.

The virtual function table does not occupy the memory space of the class object.

If class A is an empty class, what is the value of sizeof(A)?

sizeof(A)The value is 1 because the compiler needs to distinguish different instances of this empty class and allocate a byte so that different instances of this empty class have unique addresses.

Can the constructor and destructor be virtual functions?

Constructors cannot be defined as virtual functions.

Can’t make things up:

  1. From the perspective of storage space: the virtual function corresponds to a vtale , and the address of this table is stored in the memory space of the object . If the constructor is set as a virtual function, it needs to be called in the vtable, but the object has not been instantiated and there is no memory space allocated . How to call it. (paradox)

  2. From the perspective of use: The role of a virtual function is that when it is called through a pointer or reference of the parent class, it can become the member function that calls the subclass . The constructor is automatically called when an object is created, and cannot be called through a pointer or reference from the parent class. Therefore, it is stipulated that the constructor cannot be a virtual function.

  3. From an implementation point of view, vbtl is created after the constructor is called , so the constructor cannot become a virtual function.

The calling of virtual functions depends on the virtual function table, and the virtual pointer vptrneeds to be initialized in the constructor, so the constructor defined as a virtual function cannot be called.

Why does the destructor of a base class need to be defined as a virtual function?

Virtual destructor: Setting the destructor of the parent class that may be inherited as a virtual function ensures that when we create a new subclass, and then use the base class pointer to point to the subclass object, the child can be released when the base class pointer is released. Class space to prevent memory leaks. If the destructor of the base class is not a virtual function, the derived class cannot be destructed under certain circumstances.

The default destructor in C++ is not a virtual function because virtual functions require additional virtual function tables and virtual table pointers, which occupy additional memory. For a class that will not be inherited, if its destructor is a virtual function, it will waste memory. Therefore, the default destructor in C++ is not a virtual function, but is set to a virtual function only when it needs to be used as a parent class.

  1. Use the derived class type pointer to bind the derived class instance. When it is destructed, it will be destructed normally regardless of whether the base class destructor is a virtual function or not.

  2. Use a base class type pointer to bind a derived class instance. During destruction, if the base class destructor is not a virtual function, only the base class will be destructed and the derived class object will not be destructed, thus causing a memory leak. Why does this phenomenon occur? I personally think that if there is no dynamic binding function of virtual functions during destruction, the compiler will implement static binding only based on the type of the pointer, not the object bound to the pointer. , so only the destructor of the base class is called ; if the destructor of the base class is a virtual function, the corresponding destructor must be called according to the object bound by the pointer during destruction.

  3. (Explanation of 2) Define a base class pointer p . When deleting p, if the destructor of the base class is a virtual function, only the object assigned by p will be looked at. If the object assigned by p is an object of the derived class , the destructor of the derived class will be called (there is no doubt that the constructor of the base class will be called first, then the constructor of the derived class will be called, and then the destructor of the derived class will be called, and the destructor of the base class will be called. function, so-called constructed first and then released); if the object assigned by p is an object of the base class , the destructor of the base class will be called, so that memory leaks will not occur.

    If the destructor of the base class is not a virtual function, when deleting p, when calling the destructor, it will only look at the data type of the pointer, but not the assigned object, which will cause a memory leak.

Can I call virtual methods in the constructor?

No, because the polymorphic vptr pointer is distributedly initialized . When the subclass is initialized, the constructor of the parent class is called first . At this time, the Vptr pointers of the subclass's parent class point to the parent class , so no more state.

Briefly describe virtual functions and pure virtual functions, as well as implementation principles

  1. The main function of virtual functions in C++ is to implement the polymorphism mechanism. Regarding polymorphism, in short, it is to use the pointer of the parent type to point to the instance of its subclass, and then call the member function of the actual subclass through the pointer of the parent class. This technology allows the parent class pointer to have "multiple shapes", which is a generic technology. If a non-virtual function is called, the function defined by the base class type is executed regardless of the type of the actual object. Non-virtual functions are always determined at compile time based on the type of object, reference, or pointer on which the function is called. If a virtual function is called, it is not known which function was called until runtime, when the virtual function is the version defined by the type to which the reference is bound or to which the pointer points . Virtual functions must be non-static member functions of the base class. The function of the virtual function is to realize dynamic binding, that is, to dynamically select the appropriate member function during the running phase of the program. After the virtual function is defined, the virtual function can be redefined in the derived class of the base class. In the derived class The redefined function should have the same number of formal parameters and parameter types as the virtual function. To achieve a unified interface and different definition processes. If a virtual function is not redefined in a derived class, it inherits the virtual function of its base class.

    class Person{
        public:
            //virtual function
            virtual void GetName(){
                cout<<"PersonName:xiaosi"<<endl;
            };
    };
    class Student:public Person{
        public:
            void GetName(){
                cout<<"StudentName:xiaosi"<<endl;
            };
    };
    int main(){
        //pointer
        Person *person = new Student();
        //The base class calls the function of the subclass
        person->GetName();//StudentName:xiaosi
    }
    
    
    
    

    Virtual function (Virtual Function) is implemented through a virtual function table (Virtual Table). Referred to as V-Table. In this table, we mainly need the address table of the virtual functions of a class. This table solves the problems of inheritance and coverage and ensures that it truly reflects the actual functions. In this way, in an instance of a class with virtual functions, this table is allocated in the memory of this instance. Therefore, when we use the pointer of the parent class to operate a subclass, this virtual function table becomes very important. , it is like a map, indicating the actual function that should be called.

  2. A pure virtual function is a virtual function declared in the base class. It is not defined in the base class, but requires any derived class to define its own implementation method. The way to implement a pure virtual function in a base class is to add "=0" after the function prototype virtualvoid GetName() =0. In many cases, it is unreasonable for the base class itself to generate objects. For example, as a base class, animals can derive subclasses such as tigers and peacocks, but the object generated by the animal itself is obviously unreasonable. In order to solve the above problem, if the function is defined as a pure virtual function, the compiler requires that it must be rewritten in the derived class to achieve polymorphism . A class that also contains pure virtual functions is called an abstract class and cannot generate objects . This solves the above two problems very well. Defining a function as pure virtual means that the function provides an interface that descendant types can override , but the function in this class will never be called. A class that declares pure virtual functions is an abstract class. Therefore, users cannot create instances of a class, only instances of its derived classes. Functions must be redeclared in the inherited class (do not follow =0) otherwise the derived class cannot be instantiated, and they are often not defined in abstract classes. The purpose of defining pure virtual functions is to make derived classes just inherit the interface of the function. The meaning of pure virtual functions is that all class objects (mainly derived class objects) can perform the actions of pure virtual functions, but classes cannot provide a reasonable default implementation for pure virtual functions. Therefore, the declaration of a pure virtual function in a class tells the design of the subclass

  3. or, "You have to provide an implementation of a pure virtual function, but I don't know how you would implement it."

//abstract class
class Person{
    public:
        //Pure virtual function
        virtual void GetName()=0;
};
class Student:public Person{
    public:
        Student(){
        };
        void GetName(){
            cout<<"StudentName:xiaosi"<<endl;
        };
};
int main(){
    Student student;
}


How to understand abstract classes?

Reference answer

  1. The definition of abstract class is as follows:

    A pure virtual function is a virtual function declared in the base class. It is not defined in the base class, but requires any derived class to define its own implementation method . The way to implement a pure virtual function in a base class is to add "=0" after the function prototype. A class with virtual functions is called an abstract class.

  2. Abstract classes have the following characteristics:

    1) Abstract classes can only be used as base classes for other classes, and abstract class objects cannot be created.

    2) Abstract classes cannot be used as parameter types, function return types, or types for explicit conversions .

    3) Pointers and references to abstract classes can be defined, and this pointer can point to its derived class, thus achieving polymorphism.

The application scenarios of pure virtual functions mainly include:

  • Design pattern: For example, in the template method pattern, the base class defines the skeleton of an algorithm and defers some steps to subclasses. These steps that need to be implemented in subclasses can be declared as pure virtual functions.

  • Interface definition: You can create an abstract class containing only pure virtual functions as an interface. All classes that implement this interface must provide implementations of these functions.

Pure virtual functions and abstract classes

Pure virtual function: A virtual function declared in the base class, but without the implementation part of the function. Each derived class needs to be defined according to actual needs.

virtual void display() const = 0; //Pure virtual function means adding "= 0" to the prototype of the general virtual function;

Note: A pure virtual function is different from a virtual function with an empty function body. A pure virtual function has no function body at all. The class in which the former is located is an abstract class and cannot be instantiated directly, while the class in which the latter is located can be instantiated.

A class with pure virtual functions is an abstract class , which provides a unified operating interface (establishes a public interface) for a class family . The purpose of establishing an abstract class is to use its member functions to take advantage of polymorphism. An abstract class cannot be instantiated, that is, an object of an abstract class cannot be defined. The only way is to generate a non-abstract derived class of the abstract class through the inheritance mechanism and then instantiate it.

The difference between abstract class and interface

Interface: Interface is a concept. It is implemented in C++ using abstract classes

the difference:

  1. An abstract class is an abstraction of a thing, that is, an abstraction of a class , while an interface is an abstraction of behavior . To give a simple example, airplanes and birds are different types of things, but they all have one thing in common, that is, they can fly. So when designing, you can design an airplane as an Airplane-like, and a bird as a Bird-like, but you cannot design the flying feature as a class, so it is only a behavioral feature, not an abstract description of a class of things. . At this time, flight can be designed as an interface Fly , including the method fly(), and then Airplane and Bird respectively implement the Fly interface according to their own needs . Then as for different types of aircraft, such as fighter jets, civil aircraft, etc., they can directly inherit Airplane. The same is true for birds. Different types of birds can directly inherit the Bird class. It can be seen from here that inheritance is a "is it" relationship, while interface implementation is a "is it is" relationship. If a class inherits an abstract class, the subclass must be of the type of abstract class, and the interface implementation is related to whether it exists or not, such as whether a bird can fly (or whether it has the characteristic of flying), whether it can fly Then you can implement this interface. If you can't fly, you can't implement this interface.

  2. The design level is different. Abstract class serves as the parent class of many subclasses. It is a template design . The interface is a behavioral specification . For example, a certain elevator is equipped with some kind of alarm. Once the alarm needs to be updated, all alarms must be updated. That is to say, for abstract classes, if you need to add new methods, you can add specific implementations directly to the abstract class, and the subclasses do not need to be changed; but for interfaces, this is not possible. If the interface is changed, all implementations of this interface will be affected. Classes must be modified accordingly.

The difference between the two usage timings

1. Abstract class: Design pattern and template method allow subclasses to redefine the specific implementation of certain steps in the algorithm without changing the algorithm structure.

2. Interface: The implementation class needs to have many different functions, but there may be no connection between the various functions.

Let’s talk about whether pure virtual functions can be instantiated and why? Should derived classes implement it? Why?

Reference answer

  1. Pure virtual functions cannot be instantiated, but they can be instantiated using their derived classes.

  2. The principle of virtual function adopts vtable. When a class contains pure virtual functions, its vtable is incomplete and has a gap.

    That is, "the corresponding entry of the pure virtual function in the vftable table of the class is assigned a value of 0. That is, it points to a non-existent function. Since the compiler absolutely does not allow the possibility of calling a non-existent function, the class cannot be generated Object. In its derived classes, objects cannot be generated unless this function is overridden."

    So pure virtual functions cannot be instantiated.

  3. A pure virtual function is a virtual function declared in the base class, which requires any derived class to define its own implementation method to achieve polymorphism.

  4. Pure virtual functions are defined to implement an interface and to standardize the behavior of derived classes, that is, to standardize that programmers who inherit this class must implement this function. Derived classes simply inherit functions from the interface. The significance of pure virtual functions is that all class objects (mainly derived class objects) can perform the actions of pure virtual functions, but the base class cannot provide a reasonable default implementation for pure virtual functions. So the declaration of a pure virtual function in a class tells the designer of the subclass, "You must provide an implementation of a pure virtual function, but I don't know how you will implement it."

Talk about the difference between virtual functions and pure virtual functions in C++

Reference answer

  1. Virtual functions and pure virtual functions can be defined in the same class. A class containing pure virtual functions is called an abstract class, while a class containing only virtual functions cannot be called an abstract class.

  2. Virtual functions can be used directly or overloaded by subclasses and called in polymorphic form. Pure virtual functions must be implemented in subclasses before they can be used, because pure virtual functions are declared but not defined in the base class. .

  3. Both virtual functions and pure virtual functions can be overloaded in subclasses and called polymorphically.

  4. Virtual functions and pure virtual functions usually exist in abstract base classes and are overloaded by inherited subclasses in order to provide a unified interface.

  5. The definition form of virtual function: virtual{}; the definition form of pure virtual function: virtual {} = 0; There cannot be static identifier in the definition of virtual function and pure virtual function. The reason is very simple. Functions modified by static are not compiled during compilation. However, virtual functions are dynamically bound, and the life cycles of functions modified by the two are also different.

Answer analysis

  1. Let’s take an example of a virtual function:

    class A { public:     
    virtual void foo()     
    {         cout<<"A::foo() is called"<<endl;     } }; 
    class B:public A { 
    public:     
    void foo()     {         cout<<"B::foo() is called"<<endl;     } }; 
    int main(void) {     
    A *a = new B();     
    a->foo(); // Here, although a is a pointer to A, the called function (foo) belongs to B!     
    return 0; }
    
    
    

    This example is a typical application of virtual functions. Through this example, you may have some concepts about virtual functions. It is based on the so-called "deferred binding" or "dynamic binding". The call of a class function is not determined at compile time, but at run time. Since it is not possible to determine whether the function being called is a function of the base class or a derived class when writing the code, it is called a "virtual" function. Virtual functions can only achieve polymorphic effects with the help of pointers or references.

  2. A pure virtual function is a virtual function declared in the base class. It is not defined in the base class, but requires any derived class to define its own implementation method. The way to implement a pure virtual function in a base class is to add "=0" after the function prototype

    virtual void funtion1()=0

    In order to facilitate the use of polymorphic features, we often need to define virtual functions in the base class.

    In many cases, it is unreasonable for the base class itself to generate objects. For example, as a base class, animals can derive subclasses such as tigers and peacocks, but the object generated by the animal itself is obviously unreasonable.

    In order to solve the above problem, the concept of pure virtual function is introduced, and the function is defined as a pure virtual function (method: virtual ReturnType Function() = 0;), then the compiler requires that it must be rewritten in the derived class to achieve polymorphism . A class that also contains pure virtual functions is called an abstract class and cannot generate objects. This solves the above two problems very well. A class that declares pure virtual functions is an abstract class. Therefore, users cannot create instances of a class, only instances of its derived classes.

    The most significant feature of pure virtual functions is that they must be re-declared in the inherited class (do not follow =0, otherwise the derived class cannot be instantiated), and they are often not defined in abstract classes.

    The purpose of defining pure virtual functions is to make derived classes just inherit the interface of the function.

    The meaning of pure virtual functions is that all class objects (mainly derived class objects) can perform the actions of pure virtual functions, but the class cannot provide a reasonable default implementation for pure virtual functions. So the declaration of a pure virtual function in a class tells the designer of the subclass, "You must provide an implementation of a pure virtual function, but I don't know how you will implement it."

Which functions in C++ cannot be declared virtual?

Reference answer

Common functions that cannot be declared as virtual include: ordinary functions (non-member functions), static member functions, inline member functions, constructors, and friend functions.

  1. Why doesn't C++ support ordinary functions as virtual functions?

Ordinary functions (non-member functions) can only be overloaded, not overridden, and there is no meaning in declaring them as virtual functions, so the compiler will bind the function at compile time.

  1. Why doesn't C++ support constructors as virtual functions?

    The constructor is used to create a new object , and the operation of the virtual function is based on the object ( the object can be processed correctly without fully understanding the details ) . When the constructor is executed, the object has not yet been formed, so it cannot be The constructor is defined as a virtual function

  2. Why doesn't C++ support inline member functions as virtual functions?

    In fact, it is very simple. The inline function is to expand directly in the code and reduce the cost of function calls. The virtual function is to allow the object to accurately perform its own actions after inheritance. This is impossible to unify.

    Inline functions are expanded at compile time, while virtual functions are dynamically compiled at runtime, so the two are contradictory and inline functions cannot be defined as virtual functions.

  3. Why doesn't C++ support static member functions as virtual functions?

    This is also very simple. The static member function has only one code for each class , and all objects share this code . There is no need for dynamic binding.

    Static member functions belong to a class rather than an object. Without this pointer, it cannot identify objects.

  4. Why doesn't C++ support friend functions as virtual functions?

    If it is not a member function, it cannot be a virtual function.

friend function

Friend functions of a class are defined outside the class but have access to all private and protected members of the class. Although the prototype of a friend function appears in the class definition, a friend function is not a member function.

Friendly relationship

  1. The friend relationship is not transitive; C is a friend of B, and B is a friend of A. If there is no declaration, C and A do not have any friend relationship.

  2. Friend relationships are one-way; B is a friend of A, but A is not a friend of B.

  3. Friend relationships are not inherited;

4. Classes and Objects

C++ class objects, what is the memory size related to, and how much space does the empty class occupy?

The usually declared class is just a type definition and has no size itself. Using the sizeof operator, you get the size of the object of this type.

  1. Non-static data members (such as char-1 byte, int-4 bytes);

  2. Memory occupied by virtual function pointer ( virtual pointer , 64-bit-8 bytes, 32-bit 4 bytes)

  3. The space occupied by data alignment processing (calculated according to the largest type size among member variables) - trade space efficiency for time efficiency.

In order to support virtual functions, the compiler will incur additional burdens. The size of the virtual pointer (vptr) pointing to the virtual table is 8 Bytes on a 64-bit machine and 4 bytes on a 32-bit machine. No matter how many virtual functions a class has, a class object only has this one pointer.

Note: If the base class is not an empty class, the size of the derived class must be added to the space occupied by the base class. The size of the empty class space of single inheritance and multiple inheritance is 1 byte, but virtual inheritance involves a virtual table (virtual pointer) with a size of 8 bytes.

Member functions, static data members, constructors, and destructors do not occupy the memory space of the object.

Answer: An empty type object does not contain any information, but its size is not 0. This is because when an object of this type is declared, it must occupy a certain amount of space in memory, otherwise it cannot be used. Each instance of an empty type in C++ occupies 1 Byte of space.

Supplement: The function code is stored outside the object space , and the function code segment is public, that is, if 10 objects are defined for the same class, the member functions of these objects correspond to the same function code segment, not 10 different function code snippets.

class Base{
private:
     char c;
     int a;
public:
     virtual void func1();
};
class Child : public Base{
public:
     virtual void func2();
private:
     int b;
     //double b;
};
Parent class size: char c (4, aligned with int data), int a (4), virtual pointer (8) -- size 16
Subclass size: int b (4 + 4, aligned with virtual pointer data), parent class size (16)--size 24 //data alignment
//Subclass size: double b(8), parent class size(16)--size 24
//Although the subclass also has its own virtual function, it inherits the virtual function table pointer of the parent class, which is equivalent to inheriting the virtual table and virtual pointer of the parent class.
//Its own virtual function address will be added to the virtual function table inherited from the parent class.

Let’s talk about several types of constructors and what their functions are.

Reference answer

Constructors in C++ can be divided into 4 categories: default constructor, initializing constructor, copy constructor, and move constructor.

  1. Default constructor and initializing constructor. When defining an object of a class, complete the initialization of the object.

    class Student { public: //Default constructor Student() { num=1001; age=18; } //Initialization constructor Student(int n,int a):num(n),age(a){} private: int num; int age; }; int main() { //Initialize object S1 with the default constructor Student s1; //Initialize object S2 with the initialization constructor Student s2(1002,18); return 0; }

    With parameterized constructors, the compiler does not provide a default constructor.

  2. copy constructor

    #include "stdafx.h" 
    #include "iostream.h"  
    class Test {     
    int i;     
    int *p; public:     
    Test(int ai,int value)     {         i = ai;         p = new int(value);     }     ~Test()     {         delete p;     }     
    Test(const Test& t) { this->i = ti; this->p = new int(*tp); } }; //Copy constructor is used to copy objects of this class
    int main(int argc, char* argv[]) { Test t1(1,2); Test t2(t1);//Copy object t1 to t2. Note that the concepts of copying and assignment are different return 0; }

    Copying objects of this class The assignment constructor implements value copy (shallow copy) by default.

  3. Move constructor. Used to implicitly convert variables of other types into objects of this class. The following conversion constructor converts the int type r into a Student type object. The age of the object is r and the num is 1004.

    Student(int r) {  int num=1004;  int age= r; }

Copy initialization and direct initialization

  1. ClassTest ct1("ab"); This statement belongs to direct initialization and directly calls the constructor;

  2. ClassTest ct2 = "ab"; This statement is copy initialization . It first calls the constructor to create a temporary object, and then calls the copy constructor, taking this temporary object as a parameter to construct the object ct2;

  3. ClassTest ct3 = ct1; This statement is copy initialization . Because ct1 already exists, there is no need to call the relevant constructor, but directly call the copy constructor to copy its value to the object ct3;

  4. ClassTest ct4 (ct1); This statement is direct initialization , because ct1 already exists, and the copy constructor is directly called to generate a copy object ct4 of object ct3.

Direct initialization: directly call the corresponding constructor according to the parameters;

Copy initialization: The constructor creates a temporary object, then calls the copy constructor, taking this temporary object as a parameter to construct the object

Only define the destructor, which constructors will be automatically generated

Reference answer

Only the destructor is defined, and the compiler will automatically generate a copy constructor and a default constructor for us .

Default constructor and initializing constructor. When defining an object of a class, complete the initialization of the object.

class Student { public: //Default constructor Student() { num=1001; age=18; } //Initialization constructor Student(int n,int a):num(n),age(a){} private: int num; int age; }; int main() { //Initialize object S1 with the default constructor Student s1; //Initialize object S2 with the initialization constructor Student s2(1002,18); return 0; }

With parameterized constructors, the compiler does not provide a default constructor.

copy constructor

#include "stdafx.h"
#include "iostream.h"
class Test
{
    int i;
    int *p;
public:
    Test(int ai,int value)
    {
        i = ai;
        p = new int(value);
    }
    ~Test()
    {
        delete p;
    }
    Test(const Test& t)
    {
        this->i = t.i;
        this->p = new int(*t.p);
    }
};
int main(int argc, char* argv[])
{
    Test t1(1,2);
    Test t2(t1);//Copy object t1 to t2. Note that the concepts of copying and assignment are different.
    
    return 0;
}

The assignment constructor implements value copy (shallow copy) by default.

Answer analysis

Examples are as follows:

class HasPtr
{
public:
    HasPtr(const string& s = string()) :ps(new string(s)), i(0) {}
    ~HasPtr() { delete ps; }
private:
    string * ps;
    int i;
};



If there is such a function outside the class:

HasPtr f(HasPtr hp)
{
    HasPtr ret = hp;
    ///... Other operations
    return ret;
 
}



After the function is executed, the destructors of hp and ret will be called to delete the ps members of hp and ret. However, since ret and hp point to the same object, the ps member of the object is deleted twice. , this generates an undefined error, so if a class defines a destructor, then it must define its own copy constructor and default constructor.

Let’s talk about a class, which functions will be generated by default

Reference answer

Define an empty class

class Empty
{
};



The following functions are generated by default

  1. No-argument constructor

    When defining an object of a class, complete the initialization of the object.

Empty()
{
}



  1. copy constructor

    The copy constructor is used to copy objects of this class

Empty(const Empty& copy)
{
}



  1. assignment operator

Empty& operator = (const Empty& copy)
{
}



  1. Destructor (non-virtual)

~Empty()
{
}


 The difference between constructor initialization, function body initialization and member list initialization

In C++, the member variables of an object can be initialized in different ways, including constructor initialization, function body initialization and member initialization list . There are some differences between these initialization methods. Here is a comparison between them:

  • Constructor initialization : Use assignment statements inside the constructor for initialization. This is the most common initialization method. It calls the constructor when the object is created and performs initialization operations within the function body. Initialization in this manner occurs within the body of the object's constructor.

  • class MyClass {
    public:
        MyClass() {
            x = 0; // 构造函数初始化
        }
    private:
        int x;
    };
    
  • Initialization in the function body : Inside the constructor, you can use the assignment statement or initialization list in the function body for initialization. Initialization in this manner occurs within the constructor body, but after other statements in the constructor body.

  • class MyClass {
    public:
        MyClass() {
            // 构造函数体内初始化
            x = 0;
        }
    private:
        int x;
    };
    
  • Member initialization list : Use an initialization list to initialize member variables at the beginning of the constructor. Initialization in this way occurs before entering the constructor body, which helps avoid unnecessary variable initialization and copy construction.

  • class MyClass {
    public:
        // 成员初始化列表初始化
        MyClass() : x(0) {
        }
    private:
        int x;
    };
    

    Member initializer lists are generally considered a better choice because they reduce extra construction and copy operations and improve performance. In some cases, such as for const member variables and reference member variables, using a member initialization list is necessary.

What is the difference between ordinary functions and member functions?

Ordinary functions are defined outside the class, while member functions are defined inside the class.

Ordinary functions cannot directly access private and protected members of the class, while member functions can access all members of the class, including private and protected members.

Ordinary functions can be called directly, while member functions need to be called through objects of the class.

Member functions have a special pointer this, which points to the object that calls the member function. Ordinary functions do not have this pointer.

What is the this pointer for? (Tencent)

The this pointer is the address pointing to the current object . It is mainly used to access the member variables and member functions of the current object in the member functions of the class.

When an object calls its own member function, the compiler will implicitly pass the address of the object to the member function through this . Through this pointer, member functions can access and operate member variables and member functions of the current object .

This pointer can only be used in non-static member functions, because static member functions do not have this pointers and they do not belong to any specific object.

Let’s talk about the initialization sequence of C++ class objects and the sequence in the case of multiple inheritance.

Reference answer

  1. When creating an object of a derived class, the constructor of the base class is called first (also prior to the member class in the derived class);

  2. If there is a member class in the class, the constructor of the member class will be called first; (also prior to the constructor of the class itself)

  3. Base class constructor If there are multiple base classes, the order in which the constructors are called is the order in which a certain class appears in the class derivation table rather than the order in which they appear in the member initialization table;

  4. Member class object constructor If there are multiple member class objects, the order in which the constructors are called is the order in which the objects are declared in the class rather than the order in which they appear in the member initialization table;

  5. Derived class constructors, as a general rule, derived class constructors should not assign values ​​directly to a base class data member but pass the value to the appropriate base class constructor, otherwise the implementation of the two classes becomes tightly coupled. It will be more difficult to correctly modify or extend the implementation of the base class. (It is the responsibility of the base class designer to provide an appropriate set of base class constructors)

  6. In summary, it can be concluded that the initialization sequence is:

    Parent class constructor–>member class object constructor–>self constructor

    The initialization of member variables is related to the declaration order, and the calling order of constructors is the order in the class derivation list.

    The order of destruction is opposite to the order of construction.

Briefly describe upward transformation and downward transformation

  1. Convert a subclass to a parent class: Upcast, use dynamic_cast<type_id>(expression), this conversion is relatively safe and will not cause data loss;

  2. Converting parent class to subclass: For downward transformation, you can use forced conversion . This conversion is unsafe and will cause data loss because the pointer of the parent class or the referenced memory may not contain the memory of the members of the subclass. .

Briefly describe deep copy and shallow copy, and how to implement deep copy

  1. Shallow copy: Also known as value copy, the value of the source object is copied to the target object. Essentially, the source object and the target object share an entity , but the referenced variable names are different, and the addresses are actually the same. To give a simple example, your nickname is Xixi and your first name is Dongdong. When someone calls you Xixi or Dongdong, you will agree. Although these two names are different, they both refer to you.

  2. Deep copy, when copying, first opens up a space that is the same size as the source object , and then copies the contents of the source object to the target object , so that the two pointers point to different memory locations. And the content inside is the same, which not only achieves the purpose we want, but also does not cause problems. The two pointers call the destructor one after another and release the locations they point to. That is, every time a pointer is added, a new memory is allocated and the pointer points to the new memory. In the case of deep copy, the error of repeatedly releasing the same memory will not occur.

  3. Implementation of deep copy: Traditional implementation of overloaded copy constructor and assignment operator of deep copy:

    STRING( const STRING& s )
    {
        //_str = s._str;
        _str = new char[strlen(s._str) + 1];
        strcpy_s( _str, strlen(s._str) + 1, s._str );
    }
    STRING& operator=(const STRING& s)
    {
        if (this != &s)
        {
            //this->_str = s._str;
            delete[] _str;
            this->_str = new char[strlen(s._str) + 1];
            strcpy_s(this->_str, strlen(s._str) + 1, s._str);
        }
        return *this;
    }
    
    
    
    

    The copy constructor here is easy to understand. It first opens up a memory area as large as the source object, and then copies the data to be copied to the target copy object. So how is the overloading of the assignment operator here done?

    This method solves our pointer hanging problem by constantly opening up space so that different pointers point to different memories to prevent the same memory from being released twice.

What is the difference between copy constructor and assignment operator overloading?

The copy constructor is used to construct new objects;

Assignment operator overloading is used to copy the contents of the original object to the target object, and if the target object contains unreleased memory, it needs to be released first;

Can constructors and destructors throw exceptions?

From a syntax perspective, the constructor can throw exceptions, but from a logic and risk control perspective, try not to throw exceptions, otherwise it may cause memory leaks.

The destructor cannot throw an exception . If the destructor throws an exception, the program after the exception point, such as releasing memory and other operations, will not be executed, causing the problem of memory leakage ; and when an exception occurs, C++ The destructor of an object is usually called to release resources. If the destructor also throws an exception at this time, that is, the previous exception is not handled and a new exception occurs, causing the program to crash.

Briefly describe the move constructor. Which library uses this function?

There is a new move constructor in C++11. Like copying, moving uses the value of one object to set the value of another object. However, unlike copying, movement implements the real transfer of object values ​​(source object to destination object ): the source object will lose its content, and its content will be occupied by the destination object. The move operation occurs when the object of the moved value is an unnamed object. The unnamed objects here are those temporary variables, which don't even have names. A typical unnamed object is the return value of a function or the object of type conversion. Using the value of a temporary object to initialize the value of another object does not require a copy of the object: because the temporary object will not be used elsewhere, its value can be moved to the destination object. To do this, you need to use move constructors and move assignments: When using a temporary variable to construct and initialize an object, call the move constructor. Similarly, when assigning the value of an unnamed variable to an object, the move assignment operation is called.

The concept of move operations is useful for objects to manage the storage space they use, such as when objects are allocated memory using new and delete. In this type of object, copying and moving are different operations: copying from A to B means that new memory is allocated for B, and the entire contents of A are copied to the new memory allocated for B. Moving from A to B means that the memory assigned to A is transferred to B, no new memory is allocated, it just involves simply copying the pointer. Look at the following example:

//Move constructor and assignment
#include <iostream>
#include <string>
using namespace std;

class Example6 {
    string* ptr;
public:
    Example6 (const string& str) : ptr(new string(str)) {}
    ~Example6 () {delete ptr;}
    //Move constructor, parameter x cannot be const Pointer&& x,
    // Because the value of the member data of x needs to be changed;
    // Not supported by C++98, supported by C++0x (C++11)
    Example6 (Example6&& x) : ptr(x.ptr) 
    {
        x.ptr = nullptr;
    }
    // move assignment
    Example6& operator= (Example6&& x) 
    {
        delete ptr; 
        ptr = x.ptr;
        x.ptr=nullptr;
        return *this;
    }
    // access content:
    const string& content() const {return *ptr;}
    // addition:
    Example6 operator+(const Example6& rhs) 
    {
        return Example6(content()+rhs.content());
    }
};
int main () {
    Example6 foo("Exam"); ​​// Constructor
    // Example6 bar = Example6("ple"); // Copy constructor
    Example6 bar(move(foo)); // move constructor
                                // After calling move, foo becomes an rvalue reference variable,
                                // At this point, the string pointed to by foo has been "emptied out".
                                // So foo can no longer be called at this time
    bar = bar+ bar; // Moving assignment, here is the addition operation on the right side of the "=" sign,
                                // Generate a temporary value, that is, an rvalue
                                 // So the move assignment statement is called at this time
    cout << "foo's content: " << foo.content() << '\n';
    return 0;
}




Results of the:

foo's content: Example



Can you please answer the question: Can reference data members be defined within a C++ class?

Reference member variables can be defined within a C++ class, but the following three rules must be followed:

  1. It cannot be initialized with the default constructor . A constructor must be provided to initialize reference member variables. Otherwise, an uninitialized reference error will result.

  2. The formal parameters of the constructor must also be reference types.

  3. It cannot be initialized in the constructor and must be initialized in the initialization list .

Briefly describe what a constant function is and what it does

Adding const after a class member function indicates that this function will not make any changes to the data members of this class object (non-static data members to be precise). When designing a class, one principle is to add const at the end of member functions that do not change data members , and const cannot be added to member functions that change data members. Therefore, the const keyword defines the behavior of member functions more clearly: member functions with const modification (meaning that const is placed after the function parameter list, rather than in front of the function or in the parameter list), can only read data members , data members cannot be changed ; member functions without const modification can read and write data members. In addition, what are the benefits of adding const after the member function of the class? That is, constant (that is, const) objects can call const member functions, but cannot call non-const modified functions. Just as non-const type data can be assigned to const type variables, the reverse is not true.

#include<iostream>
using namespace std;
 
class CStu
{
public:
    int a;
    CStu()
    {
        a = 12;
    }
 
    void Show() const
    {
        //a = 13; //Constant functions cannot modify data members
        cout <<a << "I am show()" << endl;
    }
};
 
int main()
{
    CStu st;
    st.Show();
    system("pause");
    return 0;
}


What is the method of passing the parameters of the copy constructor and why?

Reference answer

  1. Copy constructor parameters must be passed by reference

  2. If the parameter in the copy constructor is not a reference, that is, in the form of CClass (const CClass c_class), then it is equivalent to using the pass -by-value method, and the pass-by-value method will call a copy of the class constructor , resulting in infinite recursive calls to the copy constructor. Therefore the argument to the copy constructor must be a reference.

    What needs to be clarified is that passing pointers is actually passing by value. If the above copy constructor is written as CClass (const CClass* c_class), it will not work. In fact, only passing by reference is not passing by value, all other transfer methods are by value.

  1. class Aclass B1:public virtual A;class B2:public virtual A;class D:public B1,public B2;

  1. Virtually inherited classes can be instantiated, for example:

    class Animal {/* ... */ };class Tiger : virtual public Animal { /* ... */ };class Lion : virtual public Animal { /* ... */ }

    int main( ){

    Liger lg;

    /*Since we have declared the "virtual" keyword in the definition of the Tiger and Lion classes, the following code compiles OK */int weight = lg.getWeight();

    }

Briefly describe copy assignment and move assignment?

Reference answer

  1. Copy assignment is to assign values ​​through the copy constructor. When creating an object, a previously created object in the same class is used to initialize the newly created object.

  2. Move assignment assigns values ​​through the move constructor. The main difference between the two is that

    1) The formal parameter of the copy constructor is an lvalue reference, while the formal parameter of the move constructor is an rvalue reference;

    2) The copy constructor completes the copy of the entire object or variable , while the move constructor generates a pointer pointing to the address of the source object or variable and takes over the memory of the source object, saving time and memory space compared to copying a large amount of data.

The difference between class objects and class pointers

  1. definition:

    Class object: Use the constructor of the class to allocate an area in memory (including some member variable assignments);

    Class pointer: It is a memory address value that points to a class object stored in memory (a class pointer can point to multiple different objects, polymorphism);

  2. use

    Reference members: use the "." operator for objects and the "->" operator for pointers;

  3. storage location

    Class object: It uses the memory stack and is a local temporary variable;

    Class pointer: It uses the memory heap and is a permanent variable unless you release it. new/delete

  4. Pointers can achieve polymorphism, but using objects directly cannot.

5. Template

Explain the difference between class templates and template classes in C++

Reference answer

  1. A class template is the definition of a template , not an actual class. Universal type parameters are used in the definition.

  2. A template class is a real class definition and an instantiation of a class template . Parameters in class definitions are replaced by actual types.

Answer analysis

  1. A class template can have one or more type parameters . Each type must be preceded by class , such as template <class T1, class T2>class someclass{...}; when defining the object , substitute the actual type name, such as someclass< int,double> obj;

  2. Just like using a class, you should pay attention to its scope when using a class template . You can only use it to define objects within its effective scope.

  3. Templates can have hierarchies, and a class template can serve as a base class from which derived template classes can be derived.

Let’s talk about when the template class was implemented

  1. Template instantiation: Template instantiation is divided into explicit instantiation and implicit instantiation . The former is when the developer explicitly tells the template what type should be used to generate a specific class or function, and the latter is done during the compilation process. The compiler decides what type to use to instantiate a template. Regardless of explicit instantiation or implicit instantiation, the final generated class or function is completely implemented according to the definition of the template.

  2. Template concretization: When the class or function generated after the template is instantiated using a certain type cannot meet the needs , you can consider concretizing the template. The definition of the original template can be modified during concretization . When using this type, it is implemented according to the concretized definition. Concretization is equivalent to special processing of a certain type.

  3. Code example:

    #include <iostream>
    using namespace std;
    
    // #1 Template definition
    template<class T>
    struct TemplateStruct
    {
        TemplateStruct()
        {
            cout << sizeof(T) << endl;
        }
    };
    
    // #2 Template display instantiation
    template struct TemplateStruct<int>;
    
    // #3 Template concretization
    template<> struct TemplateStruct<double>
    {
        TemplateStruct() {
            cout << "--8--" << endl;
        }
    };
    
    int main()
    {
        TemplateStruct<int> intStruct;
        TemplateStruct<double> doubleStruct;
    
        // #4 Template implicit instantiation
        TemplateStruct<char> llStruct;
    }
    
    
    
    

    operation result:

    4
    --8--
    1

Do you understand functors? what's the effect

Reference answer

  1. A functor, also known as a function object, is a class that can perform function functions . The syntax of functor is almost the same as our ordinary function call, but as a functor class, the operator() operator must be overloaded. For example:

class Func{public:

void operator() (const string& str) const {
    cout<<str<<endl;
}


};

Func myFunc;myFunc("helloworld!");

helloworld!

  1. Functors can pass in a given number of parameters like ordinary functions , and can also store or process more useful information we need . We can give an example:

    Suppose there is a vector<string>, and your task is to count the number of strings whose length is less than 5. If you use the count_if function, your code may look like this:

    bool LengthIsLessThanFive(const string& str) {

     return str.length()<5;    
    
    
    

    } int res=count_if(vec.begin(), vec.end(), LengthIsLessThanFive);

The third parameter of the count_if function is a function pointer and returns a bool type value. Generally, if a specific threshold length needs to be passed in, we might write the function like this:

bool LenthIsLessThan(const string& str, int len) {
    return str.length()<len;
}


This function looks more general than the previous version, but it cannot meet the parameter requirements of the count_if function: count_if requires an unary function (with only one parameter) as its last parameter. If we use functors, it will suddenly become clear:

class ShorterThan {
public:
    explicit ShorterThan(int maxLength) : length(maxLength) {}
    bool operator() (const string& str) const {
        return str.length() < length;
    }
private:
    const int length;
};

Guess you like

Origin blog.csdn.net/shisniend/article/details/131908947