目录
一、继承的概念
继承是复用代码的重要方法,允许我们在原有类特性的基础上进行拓展,增加功能。
原有类叫做基类(父类),拓展产生的新类叫做派生类(子类)
//父类
class Person
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "peter"; // 姓名
int _age = 18; // 年龄
};
//子类Student
class Student : public Person
{
protected:
int _stuid; // 学号
};
//子类Teacher
class Teacher : public Person
{
protected:
int _jobid; // 工号
};
二、继承的方式
继承有三种方式:public继承、protected继承、private继承
(实际使用中,一般用public继承,其他继承方式成员都只能在派生类内访问,拓展维护性不强)
1.基类的private成员无论是那种继承方式,其在派生类中都是不可见的。但是其还是被继承到派生类中了,只是语法上限制了派生类对象不管是类内还是类外都不能直接访问。
2.protected成员限定符是因继承而诞生的,由于父类的private成员在派生类中不可见,所以如果仅仅想要在类外不可见,而派生类内可见,就需要采用protected继承方式
3.关键字class默认的继承方式是private;struct默认的继承方式是public(最好写出继承方式)
三、基类和派生类对象的赋值转换
派生类对象可以赋值给基类对象、基类的指针、基类的引用
基类的指针、基类的引用可以通过强制类型转换赋值给派生类对象
派生类对象赋值给基类不同于隐式类型转换,过程中不会产生临时变量,而是通过切割(切片)的方式将派生类对象中基类成员的部分切出来,再赋值给基类对象
Student objs;
Person objp = objs;//派生类对象赋值给基类对象
Person* pp = &objs;//派生类对象指针赋值给基类指针
Person& rp = objs;//派生类对象引用可以赋值给基类引用
Student* ps = (Student*)pp;//基类对象指针通过强制类型转换赋值给派生类指针
Student& rs = (Student&)rp;//基类对象引用通过强制类型转换赋值给派生类对象引用
四、同名成员的隐藏(重定义)
当基类和派生类中出现同名成员,派生类对象会屏蔽基类中的同名成员,访问派生类中的同名成员
对于成员函数,只要函数名相同即构成隐藏
在派生类成员函数中,如果想要访问基类中的同名成员,可以通过 基类名:: 访问
//父类
class Person
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "peter"; // 姓名
int _age = 18; // 年龄
int num = 1;
};
//子类Student
class Student : public Person
{
public:
void Print()
{
cout << "派生类num:" << num << endl; //派生类num:2
cout << "基类num:" << Person::num << endl;//基类num:1
}
protected:
int _stuid; // 学号
int num = 2;
};
int main()
{
Student s;
s.Print();
//派生类num:2
//基类num:1
return 0;
}
五、派生类的默认成员函数
6个默认成员函数,是我们不写但编译器会自动生成。那么在派生类中,这些默认成员函数是如何生成的呢?
可以将基类的成员部分当成一个自定义类型来看
1.构造函数
派生类的构造函数必须调用基类的默认构造函数来初始化基类部分的成员,如果基类没有默认构造函数,则会报错,需要通过派生类对象的构造函数初始化列表显示调用
当基类中有默认构造函数时,派生类构造函数会自动调用基类的默认构造函数
当基类中没有默认构造函数时,派生类构造函数中必须通过初始化列表显式调用
- 正常情况,基类中有默认构造函数,创建派生类对象会自动调用基类中的默认构造函数
class Person {
public:
Person(const string name = "zz", const int age = 20)//默认构造函数
:_name(_name),
_age(age)
{
cout << "Person(const string name = zz, const int age = 20)默认构造函数" << endl;
}
private:
string _name;//姓名
int _age;//年龄
};
class Student :public Person {
public:
Student(int num)//有参构造函数
:_num(num)
{
cout << "Student(int num)有参构造函数" << endl;
}
private:
int _num;//学号
};
void test()
{
Student s(2250);
}
int main()
{
test();
return 0;
}
//Person(const string name = zz, const int age = 20)默认构造函数
//Student(int num)有参构造函数
- 当基类中没有默认构造函数时,必须通过派生类构造函数初始化列表显示调用,否则报错
class Person {
public:
//Person(const string name = "zz", const int age = 20)//默认构造函数
// :_name(_name),
// _age(age)
//{
// cout << "Person(const string name = zz, const int age = 20)默认构造函数" << endl;
//}
Person(const string name, const int age)//有参构造函数
:_name(_name),
_age(age)
{
cout << "Person(const string name, const int age)有参构造函数" << endl;
}
private:
string _name;//姓名
int _age;//年龄
};
class Student :public Person {
public:
//Student(int num)//有参构造函数
// :_num(num)
//{
// cout << "Student(int num)有参构造函数" << endl;
//}
Student(const string name, int age, int num)//有参构造函数
:Person(name, age),
_num(num)
{
cout << "Student(string name, int age, int num)有参构造函数" << endl;
}
private:
int _num;//学号
};
void test()
{
Student s("zz", 20, 2250);
}
int main()
{
test();
return 0;
}
//Person(const string name, const int age)有参构造函数
//Student(string name, int age, int num)有参构造函数
2.拷贝构造函数
派生类拷贝构造函数必须调用基类拷贝构造函数
当派生类没有显式写拷贝构造函数,编译器会自动生成拷贝构造函数,该自动生成的拷贝函数会调用基类的拷贝构造函数
当派生类显式写拷贝构造函数,则需要在派生类拷贝构造函数中显式调用基类的拷贝构造函数
- 派生类没有显式写拷贝构造函数
class Person {
public:
Person(const string name = "zz", const int age = 20)//默认构造函数
:_name(name),
_age(age)
{
cout << "Person(const string name = zz, const int age = 20)默认构造函数" << endl;
}
//Person(const string name, const int age)//有参构造函数
// :_name(_name),
// _age(age)
//{
// cout << "Person(const string name, const int age)有参构造函数" << endl;
//}
Person(const Person& p)//拷贝构造函数
:_name(p._name),
_age(p._age)
{
cout << "Person(const Person& p)拷贝构造函数" << endl;
}
private:
string _name;//姓名
int _age;//年龄
};
class Student :public Person {
public:
Student(int num)//有参构造函数
:_num(num)
{
cout << "Student(int num)有参构造函数" << endl;
}
Student(const string name, int age, int num)//有参构造函数
:Person(name, age),
_num(num)
{
cout << "Student(string name, int age, int num)有参构造函数" << endl;
}
//Student(const Student& s)//拷贝构造函数
//{
// cout << "Student(const Student& s)拷贝构造函数" << endl;
//}
private:
int _num;//学号
};
void test()
{
Student s1("zz", 20, 2250);
Student s2(s1);
}
int main()
{
test();
return 0;
}
//Person(const string name = zz, const int age = 20)默认构造函数
//Student(string name, int age, int num)有参构造函数
//Person(const Person& p)拷贝构造函数
- 派生类显式写拷贝构造函数
class Person {
public:
Person(const string name = "zz", const int age = 20)//默认构造函数
:_name(name),
_age(age)
{
cout << "Person(const string name = zz, const int age = 20)默认构造函数" << endl;
}
//Person(const string name, const int age)//有参构造函数
// :_name(_name),
// _age(age)
//{
// cout << "Person(const string name, const int age)有参构造函数" << endl;
//}
Person(const Person& p)//拷贝构造函数
:_name(p._name),
_age(p._age)
{
cout << "Person(const Person& p)拷贝构造函数" << endl;
}
private:
string _name;//姓名
int _age;//年龄
};
class Student :public Person {
public:
Student(int num)//有参构造函数
:_num(num)
{
cout << "Student(int num)有参构造函数" << endl;
}
Student(const string name, int age, int num)//有参构造函数
:Person(name, age),
_num(num)
{
cout << "Student(string name, int age, int num)有参构造函数" << endl;
}
Student(const Student& s)//拷贝构造函数
:Person(s),//显式调用基类拷贝构造函数(切片)
_num(s._num)
{
cout << "Student(const Student& s)拷贝构造函数" << endl;
}
private:
int _num;//学号
};
void test()
{
Student s1("zz", 20, 2250);
Student s2(s1);
}
int main()
{
test();
return 0;
}
//Person(const string name = zz, const int age = 20)默认构造函数
//Student(string name, int age, int num)有参构造函数
//Person(const Person& p)拷贝构造函数
//Student(const Student& s)拷贝构造函数
3.赋值重载函数
派生类赋值重载函数必须调用基类赋值重载函数
当派生类没有显式写赋值重载函数时,编译器会默认生成,并且该默认生成的赋值重载函数会自动调用基类的赋值重载函数
当派生类显式写赋值重载函数时,必须在其中显式调用基类的赋值重载函数
- 派生类对象没有显式写赋值重载函数
class Person {
public:
Person(const string name = "zz", const int age = 20)//默认构造函数
:_name(name),
_age(age)
{
cout << "Person(const string name = zz, const int age = 20)默认构造函数" << endl;
}
//Person(const string name, const int age)//有参构造函数
// :_name(_name),
// _age(age)
//{
// cout << "Person(const string name, const int age)有参构造函数" << endl;
//}
Person(const Person& p)//拷贝构造函数
:_name(p._name),
_age(p._age)
{
cout << "Person(const Person& p)拷贝构造函数" << endl;
}
Person& operator=(const Person& p)//赋值重载函数
{
cout << "Person& operator=(const Person& p)赋值重载函数" << endl;
if (this != &p)
{
_name = p._name;
_age = p._age;
}
return *this;
}
private:
string _name;//姓名
int _age;//年龄
};
class Student :public Person {
public:
Student(int num = 2250)//有参构造函数
:_num(num)
{
cout << "Student(int num = 2250)默认构造函数" << endl;
}
Student(const string name, int age, int num)//有参构造函数
:Person(name, age),
_num(num)
{
cout << "Student(string name, int age, int num)有参构造函数" << 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;
//}
private:
int _num;//学号
};
void test()
{
Student s1("zz", 20, 2250);
Student s2(s1);
Student s3;
s3 = s2;
}
//Person(const string name = zz, const int age = 20)默认构造函数
//Student(string name, int age, int num)有参构造函数
//Person(const Person& p)拷贝构造函数
//Student(const Student& s)拷贝构造函数
//Person(const string name = zz, const int age = 20)默认构造函数
//Student(int num = 2250)默认构造函数
//Person& operator=(const Person& p)赋值重载函数
- 派生类显式写赋值重载函数
class Person {
public:
Person(const string name = "zz", const int age = 20)//默认构造函数
:_name(name),
_age(age)
{
cout << "Person(const string name = zz, const int age = 20)默认构造函数" << endl;
}
//Person(const string name, const int age)//有参构造函数
// :_name(_name),
// _age(age)
//{
// cout << "Person(const string name, const int age)有参构造函数" << endl;
//}
Person(const Person& p)//拷贝构造函数
:_name(p._name),
_age(p._age)
{
cout << "Person(const Person& p)拷贝构造函数" << endl;
}
Person& operator=(const Person& p)//赋值重载函数
{
cout << "Person& operator=(const Person& p)赋值重载函数" << endl;
if (this != &p)
{
_name = p._name;
_age = p._age;
}
return *this;
}
private:
string _name;//姓名
int _age;//年龄
};
class Student :public Person {
public:
Student(int num = 2250)//有参构造函数
:_num(num)
{
cout << "Student(int num = 2250)默认构造函数" << endl;
}
Student(const string name, int age, int num)//有参构造函数
:Person(name, age),
_num(num)
{
cout << "Student(string name, int age, int num)有参构造函数" << 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;
}
private:
int _num;//学号
};
void test()
{
Student s1("zz", 20, 2250);
Student s2(s1);
Student s3;
s3 = s2;
}
int main()
{
test();
return 0;
}
//Person(const string name = zz, const int age = 20)默认构造函数
//Student(string name, int age, int num)有参构造函数
//Person(const Person& p)拷贝构造函数
//Student(const Student& s)拷贝构造函数
//Person(const string name = zz, const int age = 20)默认构造函数
//Student(int num = 2250)默认构造函数
//Student& operator=(const Student& s)赋值重载函数
//Person& operator=(const Person& p)赋值重载函数
4.析构函数
析构函数不同于其他默认成员函数,无论派生类是否显式写析构函数,派生类析构函数都会自动调用基类析构函数,因此保证先子后父的析构
因此当派生类显式写析构函数时,我们无需显式调用基类的析构函数
注意:派生类析构函数和基类析构函数会构成隐藏,尽管其函数名不相同,但是编译器会对析构函数进行特殊处理,处理后的析构函数名统一为destrutor()
六、继承与友元
友元不能继承,即基类中的友元函数,不可以访问派生类中的私有和保护成员
七、继承与静态成员
基类中的静态成员被继承到派生类中,但是不同于普通成员,基类与派生类共用一个静态成员
八、菱形继承
1.单继承
一个子类只用一个直接父类的继承叫做单继承
2.多继承
一个子类有多个直接父类的继承叫做多继承
3.菱形继承
菱形继承是多继承的一种特殊情况,即多继承中派生类的父类们有一个共同的父类
菱形继承会产生两个问题:
1.数据冗余(Assistant类中有两份Person类中的数据)
2.数据二义性(Assistant类中有两份Person类中的数据,无法确定用哪一个数据)
解决方法:
解决数据二义性,需要通过显示指定访问哪一个父类的成员,但是不能解决数据冗余的问题
class Person
{
public:
string _name; // 姓名
};
class Student : public Person
{
protected:
int _num; //学号
};
class Teacher : public Person
{
protected:
int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
string _majorCourse; // 主修课程
};
void Test()
{
// 这样会有二义性无法明确知道访问的是哪一个
Assistant a;
a._name = "peter";
// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
a.Student::_name = "xxx";
a.Teacher::_name = "yyy";
}
4.虚拟继承
为了同时解决数据冗余和二义性的问题,C++加入了虚拟继承(虚继承)
虚继承中基类公共的数据将不会各自被继承到其派生类中,而是其派生类通过各自的虚基表指针找到虚基表,再通过虚基表中存在的偏移量来找到它们公共基类中的数据,这样就解决了数据冗余的问题,数据冗余得到解决,那么二义性的问题自然也就不存在了。
注意:虚继承中的关键字virtual要放在有公共基类的派生类中
class Person
{
public :
string _name ; // 姓名
};
class Student : virtual public Person
{
protected :
int _num ; //学号
};
class Teacher : virtual public Person
{
protected :
int _id ; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected :
string _majorCourse ; // 主修课程
};
void Test ()
{
Assistant a ;
a._name = "peter";
}