继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。
在C++中,继承是一种面向对象编程的概念,它允许一个类(称为派生类或子类)从另一个类(称为基类或父类)继承属性和方法。继承使得派生类可以重用基类的代码,并且可以在此基础上添加新的功能或修改现有的功能。
(1)公有继承(public inheritance):基类中的公有成员在派生类中仍然是公有的,保持了访问权限不变。派生类可以直接访问基类的公有成员,但不能直接访问基类的私有成员。
class 基类 {
public:
// 公有成员
};
class 派生类 : public 基类 {
public:
// 派生类的成员
};
(2)保护继承(protected inheritance):基类中的公有成员在派生类中变为保护的,保护成员只能在派生类内部或其派生类中访问,外部无法访问。
class 基类 {
protected:
// 保护成员
};
class 派生类 : protected 基类 {
public:
// 派生类的成员
};
(3)私有继承(private inheritance):基类中的公有成员在派生类中变为私有的,私有成员只能在派生类内部访问,外部无法访问。
class 基类 {
private:
// 私有成员
};
class 派生类 : private 基类 {
public:
// 派生类的成员
};
继承基类成员访问方式的变化
类成员/继承方式 | public继承 | protected继承 | private继承 |
---|---|---|---|
基类的public成员 | 派生类的public成员 | 派生类的protected成员 | 派生类的private成员 |
基类的protected成员 | 派生类的protected成员 | 派生类的protected成员 | 派生类的private成员 |
基类的private成员 | 在派生类中不可见 | 在派生类中不可见 | 在派生类中不可见 |
总结:
(1)基类private成员在派生类中无论以什么方式继承都是不可见的。 不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
(2)基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。 可以看出保护成员限定符是因继承才出现的。
(3)使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public, 不过最好显示的写出继承方式。
(4)在实际运用中一般使用都是public继承, 几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。
在C++中,基类和派生类之间的对象复制转换是指将一个基类对象赋值给一个派生类对象或将一个派生类对象赋值给一个基类对象的操作。 这种操作可以通过以下两种方式进行:
(1)赋值兼容(切片)(slicing):将派生类对象赋值给基类对象时,会发生对象切片。只会复制基类部分的成员,派生类特有的成员将被丢弃。
(2)引用/指针转换: 可以使用基类的引用或指针来引用或指向派生类对象。这样做可以实现基类和派生类对象之间的相互转换,但是只能访问到基类部分的成员,无法访问到派生类特有的成员。
class Person
{
protected :
string _name; // 姓名
string _sex; // 性别
int _age; // 年龄
};
class Student : public Person
{
public :
int _stuid ; // 学号
};
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;
}
总结:
(1)派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。
(2)基类对象不能赋值给派生类对象。
(3)基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。
(1)在继承体系中基类和派生类都有独立的作用域。
(2)子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏也叫重定义。
(3)需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
(4)注意在实际中在继承体系里面最好不要定义同名的成员。
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);
};
//此时A和B中的fun构成隐藏,不是重载,因为fun不在同一个作用域
//B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏
//重载需要函数在同一个作用域中,函数名相同,参数列表不同才可以实现
重载和隐藏的区别
(1)重载(overloading):重载是指在同一个作用域内,可以定义多个具有相同名称但参数列表不同的函数。 重载函数可以根据参数的不同类型或数量来决定调用哪个函数。重载函数的特征是具有相同的函数名,但参数列表不同。
void 函数名(int 参数1);
void 函数名(double 参数1);
void 函数名(int 参数1, int 参数2);
//此时函数实现重载
(2)隐藏(hiding):隐藏是指在派生类中定义了与基类中同名的成员(变量或函数),从而隐藏了基类的同名成员。 当在派生类中使用同名成员时,会优先访问派生类中的成员,而不是基类中的成员。如果需要访问基类的同名成员,可以使用作用域解析运算符(::)来指定基类的作用域。
class 基类 {
public:
int 成员变量;
void 成员函数();
};
class 派生类 : public 基类 {
public:
int 成员变量; // 隐藏了基类的成员变量
void 成员函数(); // 隐藏了基类的成员函数
};
派生类 obj;
obj.成员变量; // 访问派生类的成员变量
obj.基类::成员变量; // 访问基类的成员变量
obj.成员函数(); // 调用派生类的成员函数
obj.基类::成员函数(); // 调用基类的成员函数
//此时的成员函数构成隐藏
派生类的默认成员函数是指在派生类中没有显式定义的成员函数,而是通过继承基类的成员函数来自动生成的函数。
和普通的类一样派生类的默认成员函数包括以下几种:
(1)默认构造函数(Default Constructor): 如果派生类没有显式定义构造函数,那么编译器会自动生成一个默认构造函数。默认构造函数会调用基类的默认构造函数来初始化基类的成员,然后再初始化派生类的成员。
(2)拷贝构造函数(Copy Constructor): 如果派生类没有显式定义拷贝构造函数,那么编译器会自动生成一个拷贝构造函数。拷贝构造函数会调用基类的拷贝构造函数来初始化基类的成员,然后再初始化派生类的成员。
(3)赋值运算符(Assignment Operator): 如果派生类没有显式定义赋值运算符,那么编译器会自动生成一个赋值运算符。赋值运算符会调用基类的赋值运算符来赋值基类的成员,然后再赋值派生类的成员。
派生类的默认成员函数如何生成
1.构造函数
(1)派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。 如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
(2)派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
(3)派生类的operator=必须要调用基类的operator=完成基类的复制。
(4)派生类对象初始化先调用基类构造再调派生类构造。
2.析构函数
(1)派生类的析构函数会在被调用完成后,自动调用基类的析构函数清理基类成员。 因为这样才能保证派生类对象,先清理派生类成员,再清理基类成员的顺序。
(2)派生类对象析构清理,先调用派生类析构,再调基类的析构。
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。
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;
}
//人数: 4
//人数: 0
当然继承还有很多的知识点,这里只是对C++继承的部分介绍了
如有错误❌望指正,最后祝大家学习进步✊天天开心✨