1.1. 继承的概念
在C++中,继承是一种面向对象编程的重要概念,它允许一个类(称为派生类)从另一个类(称为基类)继承属性和方法。通过继承,派生类可以重用基类的代码,并且可以在基础上添加或修改功能。
class Person
{
public:
void Print()
{
cout << _name << _age << endl;
}
string _name = "";
int _age = 1;
};
class Student : public Person
{
private:
int _id = 0; //学号
};
1.2. 继承的定义
1.2.1. 定义的格式
1.2.2. 继承关系和访问限定符
1.2.3. 继承基类成员访问方式的变化
总结:
- 私有成员不可见
- 成员权限和继承权限取最小的那个 (public > protected> private)
- 不写是什么继承方式的,使用class关键字默认继承方式为私有继承
- protected关键字在类外不可以被直接访问,但是在派生类中可以直接访问
class Person
{
public:
void Print()
{
cout << _name << " " << _age << endl;
}
protected:
string _name = "张三";
private:
int _age = 1;
};
//class Student : public Person // 输出张三 1
//class Student : protected Person //编译不通过
class Student : private Person //编译不通过
{
private:
int _id = 0; //学号
};
int main()
{
Student s1;
s1.Print();
return 0;
}
1.3. 基类和派生类对象赋值转换
派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。形象的说法叫做切片或者切割
class Person
{
public:
void Print()
{
cout << _name << " " << _age << endl;
}
protected:
string _name = "张三";
int _age = 1;
};
class Student : public Person // 输出张三 1
{
private:
int _id = 0; //学号
};
int main()
{
Student s1;
Person p1;
p1 = s1;
return 0;
}
记住一定是派生类给基类赋值才能这样!另外保护继承也不能这样
1.4. 继承中的作用域
- 在继承体系中基类和派生类都有独立的作用域。
- 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)
- 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
- 注意在实际中在继承体系里面最好不要定义同名的成员。
class Person
{
public:
void Print()
{
cout << _name << " " << _age << endl;
}
protected:
string _name = "张三";
int _age = 1;
};
class Student : public Person // 输出张三 1
{
public:
void Print()
{
cout << _id << " " << _name << " " << _age << endl;
}
private:
int _id = 0; //学号
};
int main()
{
Student s1;
s1.Print();
s1.Person::Print();
return 0;
}
输出结果:
1.5. 派生类的默认成员函数
class Person
{
public:
Person(string name, int age)
:_name(name)
, _age(age)
{
cout << "Person(string name, 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;
return *this;
}
~Person()
{
cout << "~Person()" << endl;
}
void Print()
{
cout << _name << " " << _age << endl;
}
protected:
string _name = "张三";
int _age = 1;
};
class Student : public Person // 输出张三 1
{
public:
Student(string name, int age, int id)
:Person(name,age)
,_id(id)
{
cout << "Student(string name, int age, int id);" << endl;
}
Student(const Student& s)
:Person(s) //这里能用Student赋值,是因为上面讲过的赋值转换
, _id(s._id)
{
cout << "Student(const Student& s);" << endl;
}
Student& operator = (const Student& s)
{
cout << "Student& operator= (const Student& s)" << endl;
if (this != &s)
{
Person::operator =(s);//这里必须使用基类的运算符重载
//因为派生类函数名和基类函数名相同构成了隐藏
_id = s._id;
}
return *this;
}
void Print()
{
cout << _id << " " << _name << " " << _age << endl;
}
~Student()
{
cout << "~Student()" << endl;
}
private:
int _id = 0; //学号
};
int main()
{
Student s1("jkl",18,006);
Student s2(s1);
Student s3("xxx",19,121);
s3 = s1;
return 0;
}
总结:
- 先是调用基类的默认成员函数(析构函数除外),再去调用派生类自己的默认成员函数
- 根据内置类型和自定义类型来处理
- 派生类析构函数和基类析构函数构成隐藏关系
- 编译器先调用派生类析构函数,再调用基类析构函数
1.6. 友元关系与继承
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;
}
int main()
{
Person p;
Student s;
Display(p, s);
return 0;
}
其中原因是友元关系是不能继承的,也就是说Student是没有继承到友元函数的,也就导致了Student的成员不能被访问。
1.7. 继承与静态成员
class Person
{
public:
Person()
{
cout << "Person()" << endl;
++_count;
}
static int _count;
protected:
string _name; // 姓名
};
int Person::_count = 0;
class Student : public Person
{
public:
Student()
{
cout << "Student()" << endl;
}
protected:
int _stuNum; // 学号
};
int main()
{
Person p;
Student s;
cout << Person::_count << endl;
return 0;
}
输出结果:
主函数只有一个Person和一个Student,但是_count却是2.
原因是:**基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。**无论派生出多少个子
类,都只有一个static成员实例
1.8. 菱形继承和菱形虚拟继承
1.8.1. 单继承
以上这种继承叫做单继承,所有的子类只有一个直接的父类。
1.8.2. 多继承
一个子类有两个或以上直接父类时称这个继承关系为多继承。
1.8.3. 菱形继承
菱形继承是多继承的一种特殊情况
菱形继承的问题:菱形继承有数据冗余和二义性的问题。在D的对象中A成员会有两份。
这里主要的问题是二义性和数据的冗余问题。
class A
{
public:
int _a = 1;
};
class B : public A
{
public:
int _b = 2;
};
class C : public A
{
public:
int _c = 3;
};
class D : public B, public C
{
public:
int _d = 4;
};
int main()
{
D d;
cout << d._d << endl;
cout << d._a << endl;
return 0;
}
编译结果:
可以通过显示访问父类成员的方式解决。
int main()
{
D d;
cout << d._d << endl;
cout << d.B::_a << endl;
cout << d.C::_a << endl;
return 0;
}
输出结果:
但是这样做并没有解决数据冗余的问题。所以就有了虚拟继承。
1.8.4. 虚拟继承
在B和C对A的继承使用虚拟继承就可以解决问题。
下面从内存的角度看虚拟继承
其中B和C存的另一个地址是A的偏移量的地址,方便快速的找到A。如下图