Object-Oriented Programming -- define basic classes and derived classes [continued]
IV,VirtualAnd other member functions
Function calls in C ++ do not use dynamic binding by default. To trigger dynamic binding, two conditions must be met:
1) only member functions specified as virtual functions can be dynamically bound. member functions are non-virtual functions by default, and non-virtual functions are not dynamically bound.
2) function calls must be performed through a reference or pointer of the base class type.
1. Conversion from a derived class to a base class
Because each derived class object contains the base class part, you can bind the reference of the base class type to the base class part of the derived class object. You can direct the base class pointer to the derived class object:
void print_total(const Item_base &item,size_t n); Item_base item; print_total(item,10); Item_base *p = &item; Bulk_item bulk; print_total(bulk,10); p = &bulk;
Regardless of the type of the actual object,The compiler treats itBase class type object. It is safe to treat a derived class object as a base class object because each derived class object has a base class sub-object. And,A derived class inherits operations of a base class.That is, any operation performed on the base class object can also be used by the derived class object.
[Explain]
The key points of base class type reference and pointer areStatic type(Reference type or pointer type known during compilation) andDynamic type(Pointer or reference the type of the bound object. This isOnly known at runtime) May be different.
2. You can call the virtual function at runtime.
When a virtual function is called through a pointer or reference, the compiler will generate code to determine which function to call at runtime. The called function corresponds to a dynamic type:
void print_total(const Item_base &item,size_t n){ cout << "ISBN: " << item.book() << "\t number sold: " << n << "\ttotal price: " << item.net_price(n) << endl;}
Because the item parameter is a reference and net_price is a virtual function, the net_price version called by item.net _ price (n) depends on the real parameter type bound to the item parameter at runtime:
Item_base base; Bulk_item derived; print_total(base,10); //Item_base::net_price print_total(derived,10); //Bulk_item::net_price
[Key Concept: Polymorphism in C ++]
The static and dynamic types of references and pointers can be different. This is the cornerstone of C ++ to support polymorphism!
When a function defined in the base class is called through a base class reference or pointer,We do not know the exact type of the object that executes the function., The object that executes the function may be of the base class type or a derived type.
If you call a non-virtual functionNo matter what type the actual object is, All the functions defined by the base class type are executed. If you call a virtual function, you cannot determine which function to call until the runtime. The running virtual function references the version of the Type Definition of the object to which the bound or Pointer Points.
[Understanding :]
The virtual function is determined at runtime only when it is called by reference or pointer.
3. Determine non-virtual calls during compilation
Non-virtual functions are always determined by the objects, references, or pointer types that call the function during compilation. Although the item type is constItem_base reference, no matter what type of the actual object referenced by the item at runtime, all non-virtual functions that call this object call the version defined in Item_base.
4. overwrite the virtual function mechanism
If you want to override the virtual function mechanism and force function calls to use a specific version of the virtual function, you can use the scope OPERATOR:
Item_base * baseP = & derived; // explicitly call the version in Item_base. When reloading, determine double d = baseP-> Item_base: net_price (42 );
[Best practices]
Only the code in the member functions should use the scope operator to overwrite the virtual function mechanism.
The overwrite virtual function mechanism is commonly used in: the virtual function of a derived class calls the version of the base class. In this case, the base class version can complete all types of public tasks in the inheritance level, each derived type only adds its own special work:
[Careful mine]
When a virtual function of a derived class calls a base class version, the scope operator must be explicitly used. If the function of the derived class ignores the scope operator, the function call is determined at runtime and will be a self-called function, resulting in infinite recursion!
5. virtual functions and default real parameters
Like any other function, virtual functions can also have default real parameters.When a virtual function is called through a base class reference or pointer,The default real parameter isValue specified in the basic virtual function declaration,If you call a virtual function through a pointer or reference of a derived class,The default real parameter isThe value declared in the version of the derived class.
Using different default real parameters in the base and derived classes of the same virtual function is almost troublesome. If you call a virtual function by referencing a base class or pointer,But the actual execution isDerived classVersion defined in,In this case, problems may occur.. In this case, the default parameter defined for the base class version of the virtual function is passed to the version defined by the derived class, and the derived class version is defined by different default real parameters.
// P482 exercise 15.8 struct base {base (string name = ""): baseName (name) {}string name () {return baseName;} virtual void print (ostream & OS) {OS <baseName;} private: string baseName;}; struct derived: public base {derived (string name = "", int intMem = 0): base (name ), mem (intMem) {} void print (ostream & OS) {base: print (OS); // infinite recursion is formed here! OS <"" <mem;} private: int mem ;};
// Exercise 15.9 understand the following procedure: int main () {base ba ("xiaofang"); base * p = & ba; p-> print (cout ); cout <endl; derived de ("xiaofang"); p = & de; p-> print (cout); // call the print function of the derived class}
V. Public, private, and protected inheritance
Access to the members inherited by the class is jointly controlled by the Access Level of the members in the base class and the access label used in the list of derived classes. Each class controls the access of members it defines.The derived class can further restrict but cannot relax access to inherited members.!
Specify the base classMinimum Access Control for Members. Private in the base class. Only the friends of the base class and the base class can access this member. The derived class cannot access the private members of its base class, and of course cannot access its own users!
If the base class member is public or protected, the access label used in the derived list determines the access level of the member in the derived class:
1) if it is a public inheritance public:The base class members maintain their own access level.: The public Member of the base class is the public Member of the derived class, and the protected member of the base class is the protected member of the derived class.
2) If it is protected and inherits protected:Base ClassPublicAndProtectedThe Member isProtectedMember.
3) for private inheritance: all the members of the base class are private members in the derived class.
class Base{public: void baseMem();protected: int i;};class Public_derived : public Base{ int use_base() { return i; //OK }};class Private_derived : private Base{ int use_base() { return i; //OK }};
Examples:No matter what access label is in the derived list, allInheritanceBaseClassPairBaseMemberHave the same Access PermissionsThe access label of the derived class willControls the User-DefinedBaseAccess of inherited members:
Base b; Public_derived d1; Private_derived d2; b.baseMem(); d1.baseMem(); //OK d2.baseMem(); //Error
The access label of a derived class also controls access from a non-direct derived class:
class Derived_from_Private : public Private_derived{ int use_base() { return i; //Error }};class Derived_from_Public : public Public_derived{ int use_base() { return i; //OK }};
In fact, this can also be understood: In the Private_derived class, all the things it inherits have become private, which is equivalent to the fact that the derived class cannot access the private Members of the base class! The class derived from Public_derived can access the I from the Base class because the Member is still a protected member in Public_derived.
1. interface inheritance and implementation inheritance
The public derived class inherits the interface of the base class: it has the same interface as the base class. In a well-designed class hierarchy, the object of the public derived class can be used in any place where the base class object is required. [Interface inheritance]
Private and protected Derived classes inherit the implementation of the base class: they do not inherit the interface of the base class [because of inheritance, it is equivalent to being a derived built-in utility function...], the derived class is inherited in the implementation, but the part that inherits the base class is not part of its interface! [Implement inheritance]
[So far: The most common inheritance form is public!]
[Key concepts: inheritance and combination]
When defining a class as a public derived class of another class, the derived class should reflect the "A (IsA)" relationship with the base class. In the bookstore example, the base class represents the concept of a book sold at a specified price. Bulk_item is a book, but it has different pricing policies.
Another common relationship between types is known as "there is a (HasA)" relationship. The class in the bookstore example has the price and ISBN. Type Related to the relationship through "one"Hidden member relationshipsTherefore, the class in the bookstore example consists of the price and ISBN members.
2. Remove individual members
If private/protected is inherited, the access level of the base class members is more limited than that of the base class members in the derived class:
Class Base {public: std: size_t size () const {return n;} protected: std: size_t n ;}; class Derived: private Base {//...}; // test int main () {Derived de; std: size_t n = de. size (); // Error}
[Note]
Derived classThe access level of inherited members can be restored.But it cannot make the access level more strict than the one previously specified in the base class (?) Or loose!
In the preceding example, the size is private in Derived. To restore the size to its previous position [public] In Derived, you can add a using declaration in the public part of Derived. The definition of Derived is changed as follows, so that the size member can be accessed by the user and the n can be accessed by classes Derived from Derived:
class Derived : private Base{public: using Base::size;protected: using Base::n;};
Just as you can use using to declare a name from a namespace, you can alsoUseUsingDeclare the name of the Access Base Class.
At this point:
Derived de; std::size_t n = de.size(); //OK
3. Default inheritance Protection Level
The default inheritance access level defines different Derived classes based on the reserved keywords used: the derived classes defined by class have private inheritance by default, while the classes defined by struct have public inheritance by default:
class Base{ /* ... */};struct D1 : Base //struct D1 : public Base{ /* ... */};class D2 : Base //class D2 : private Base{ /* ... */};
Note: The only difference between class and struct default inheritance is the default Member Protection Level and the default derived protection level!
class D3 : public Base{public: /* ... */};// equivalent definition of D3struct D3 : Base{ /* ... */};struct D4 : private Base{private: /* ... */};// equivalent definition of D4class D4 : Base{ /* ... */};
[Best practices]
Although private inheritance is the default situation when using class reserved words, it is relatively rare in practice [it is recommended that you do not use it because not only computers are the final part of your source code, there are programmers!]. Because private inheritance is so rareSpecify explicitlyPrivateIt is a better way than Yiyun by default.Explicitly specifying clearly indicates that you want private inheritance rather than temporary negligence.
Vi. Friendship and inheritance
The relationship between friends and friends cannot be inherited.Friends of the base classPairMember of the derived classNo special access permission. If the base class is granted a friend relationship, only the base class has special access permissions. The derived class of the base class cannot access the class that grants the friend relationship.
Each class controls the membership relationship of its own members:
Class Base {friend class Frnd; protected: int I ;}; // Frnd has no special access permission to D1 class D1: public Base {protected: int j ;}; class Frnd {public: int mem (const Base & obj) {return obj. i; // OK} int mem (const D1 & obj) {return obj. j; // Error }}; // D2 does not have special access permissions to the Base class D2: public Frnd {public: int mem (const Base & obj) {return obj. i; // Error }};
The principal of the base class does not have special access permissions on the types derived from the base class. Similarly, if both the base class and the derived class need to access a class, the class must specifically grant access permissions to the base class and each derived class.
VII. Inheritance and static members
If a base class defines a static member, there is only one such member in the entire inheritance hierarchy: no matter how many derived classes are derived from the base class,EachStaticThe Member has only one instance..
Static members follow regular access control:If the member is in the base classPrivate,The derived class cannot access it.. If you can access this static member [such as public ],It can be accessed through the base classStaticMember,You can also accessStaticMember. Generally, you can use either the scope operator or the DOT or arrow member access operator.
struct Base{ static void statMem();};struct Drived : public Base{ void f (const Drived &);};void Drived::f(const Drived &derived_obj){ Base::statMem(); Drived::statMem(); derived_obj.statMem(); statMem();}
// P487 exercise 15.13 struct ConcreteBase {static std: size_t object_count (); protected: static std: size_t obj_count ;}; struct C1: public ConcreteBase {void f (const ConcreteBase & obj) {ConcreteBase: object_count (); ConcreteBase: obj_count; C1: object_count (); C1: obj_count; obj. object_count (); obj. obj_count; object_count (); obj_count ;}}; struct C2: public ConcreteBase {}; int main () {C2 obj; obj. object_count (); // obj cannot directly access the obj_count member because the Member is a protected member and cannot access obj through an object. obj_count; // Error obj. concreteBase: object_count (); obj. c2: object_count ();}