C++继承

Posted by 川川的博客 on May 7, 2025

继承

class Person
{
protected:
		string _name;
		int _age;

};

class Student : public Person
{
protected:
		int _stuid;//学号
};
class Teacher : public Person
{
protected:
		int _jobid;//工号
};

继承访问方式取基类和派生类最小的访问方式

基类的其他成员在子类 的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private

在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承扩展性不强

子类和父类之间的赋值兼容规则

  • 子类对象可以赋值给父类对象/指针/引用
Student st;
Person p;
p = st; //切片
Person* ptr = &st;  
Person& ref = st;
  • 基类对象不能赋值给派生类对象
  • 基类的指针可以通过强制类型转换赋值给派生类的指针

class Person
 {
 protected :
    string _name; // 姓名
    string _sex;  // 性别
    int _age;    // 年龄
};
 class Student : public Person
 {
 public :
    int _No ; // 学号
};
 void Test ()
 {
    Student sobj ;
    // 1.子类对象可以赋值给父类对象/指针/引用
    Person pobj = sobj ;
    Person* pp = &sobj;
    Person& rp = sobj;
    
    //2.基类对象不能赋值给派生类对象
    sobj = pobj;
    
    // 3.基类的指针可以通过强制类型转换赋值给派生类的指针
    pp = &sobj
    Student* ps1 = (Student*)pp; // 这种情况转换时可以的。
    ps1->_No = 10;
    
    pp = &pobj;
    Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问题
    ps2->_No = 10;

继承作用域

 // B中的fun和A中的fun不是构成重载,因为不是在同一作用域
// B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
class A
 {
 public:
    void fun()
    {
        cout << "func()" << endl;
    }
 };
 class B : public A
 {
 public:
    void fun(int i)
    {
   	 A::fun();
     cout << "func(int i)->" <<i<<endl;
    }
 };
     
     void Test()
     {
     B b;
     b.fun(10);
     };

派生类的默认成员函数

  • 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
  • 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类 对象先清理派生类成员再清理基类成员的顺序。
  • 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化
  • 派生类的operator=必须要调用基类的operator=完成基类的复制。
  • 派生类对象初始化先调用基类构造再调派生类构造。
  • 派生类对象析构清理先调用派生类析构再调基类的析构。
class Person
 {
 public :
 Person(const char* name = "peter")
 		: _name(name )
 {
 		cout<<"Person()" <<endl;
 }
 
 Person(const Person& p)
 		: _name(p._name)
 {
		 cout<<"Person(const Person& p)" <<endl;
 }
 
 Person& operator=(const Person& p )
 {
 		cout<<"Person operator=(const Person& p)"<< endl;
        if (this != &p)
           _name = p ._name;
        
        return *this ;
 }
 
 ~Person()
 {
	 cout<<"~Person()" <<endl;
 }
 
 protected :
 	string _name ; // 姓名
};


class Student : public Person
 {
 public :
 	Student(const char* name, int num)
 		: Person(name )
 		, _num(num )
 {
		 cout<<"Student()" <<endl;
 }
 
 Student(const Student& s)
 		: Person(s)
 		, _num(s ._num)
 {
 		cout<<"Student(const Student& s)" <<endl ;
 }
 
 Student& operator = (const Student& s )
 {
 	cout<<"Student& operator= (const Student& s)"<< endl;
     if (this != &s)
     {
         Person::operator =(s);
        _num = s ._num;
     }
        return *this ;
} 
    //子类的析构函数和父类的析构函数构成隐藏,因为他们名字会被编译器统一处理成destructor
    ~Student()
    {
        //Person::~Person();
        cout<< "~Student()"<<endl;
    }

protected :
    int _num ; //学号
};




子类结束时会自动调用父类的析构函数,不需要显示调用

友元关系不能继承

 class Student;
 class Person
 {
 public:
    friend void Display(const Person& p, const Student& s);
 protected:
    string _name; // 姓名
};
 class Student : public Person
 {
 protected:
    int _stuNum; // 学号
};
//基类友元不能访问子类私有和保护成员
void Display(const Person& p, const Student& s)
 {
    cout << p._name << endl;
    cout << s._stuNum << endl;
 }
 
void main()
 {
    Person p;
    Student s;
    Display(p, s);
 }

继承静态实例只有一个

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一 个static成员实例 。

class Person
 {
 public :
    Person () {++ _count ;}
 protected :
    string _name ; // 姓名
public :
    static int _count; // 静态成员统计人的个数。
};


 int Person :: _count = 0;
 
 class Student : public Person
 {
 protected :
    int _stuNum ; // 学号
};


class Graduate : public Student
 {
 protected :
    string _seminarCourse ; // 研究科目
};
 void TestPerson()
 {
    Student s1 ;
    Student s2 ;
    Student s3 ;
    Graduate s4 ;
    cout <<" 人数 :"<< Person ::_count << endl;
    Student ::_count = 0;
    cout <<" 人数 :"<< Person ::_count << endl;
 }

复杂菱形继承及菱形虚拟继承

单继承 class S : public A

多继承 class S : public A,public B

菱形继承 问题:数据冗余和二义性

虚拟继承可以解决上述问题 :virtual

								   class Person
									    ⬇	
class Student:virtual public Person              class Teacher:virtual public Person
                    ⬇										⬇
                     class A : public Student,public Teacher
                      

菱形继承内存对象模型

数据冗余和二义性

class A
 {
 public:
 	int _a;
 };
 // class B : public A
 class B : virtual public A
 {
 public:
	 int _b;
 };
 // class C : public A
 class C : virtual public A
 {
 public:
 	int _c;
 };
 class D : public B, public C
 {
 public:
 	int _d;
 };
 int main()
 {
     D d;
     d.B::_a = 1;
     d.C::_a = 2;
     d._b = 3;
     d._c = 4;
     d._d = 5;
     
     return 0;
 }

image-20250618220407557

class A
 {
 public:
 	int _a;
 };
 // class B : public A
 class B : virtual public A
 {
 public:
 	int _b;
 };
 // class C : public A
 class C : virtual public A
 {
 public:
 	int _c;
 };
 class D : public B, public C
 {
 public:
     	int _d;
 };
//普通继承对象D d,sizeof(d)输出大小20字节

//虚继承输出大小24字节  int _a , _b,_c,_d,两个虚基表指针 

虚继承解决数据的冗余性和二义性,在公共区域存放有二义性的数据

原理:将虚基类对象放到公共位置(visual 下是放到整个对象的尾部),虚基表中存放偏移量,来计算虚基类对象的位置。

这里是通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A。虚基表也是存在代码段(常量区)

(菱形虚拟继承)

image-20250613233026054

实际中不到万不得已,不要把类的关系设计成菱形继承

总结:

  • is-a 继承是一种白箱复用,父类对子类基本透明,但是它一定程度破坏了父类的封装

  • has-a 组合是一种黑箱复用,C对D不透明,C保持它的封装