一、概述
作为C++的三大特性之一封装,继承,多态 中的继承,我们在进阶部分一定要详细说明。请跟着如下的小标题进入深度学习。
二、正文
1.继承的概念及定义
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "peter"; // 姓名
int _age = 18; // 年龄
};
// 继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分
// 这里体现出了Student和Teacher复用了Person的成员。
class Student : public Person
{
public:
void test_protected_private()
{
cout << _age;
}
protected:
int _stuid; // 学号
};
class Teacher : public Person
{
protected:
int _jobid; // 工号
};
int main()
{
Student s;
Teacher t;
// 由于这个S是自定义类型,没有做操作符重载,没法用cout
s.Print();
cout << "====================" << endl;
t.Print();
return 0;
}
我们先不要管他继承的格式,后面会讲继承的格式,这段代码就是告诉大家,从运行结果可以看出显然子类继承了父类的特性,注意这里的的protected和private protected其实是被保护的数据,在子类或者说继承类里面可以访问,private是私有的,在子类访问不了,如果感兴趣可以把上面的数据改成private,这时候你就发现void test_protected_private()报错了。
运行结果
#include <iostream>
#include <string>
using namespace std;
// 实例演示三种继承关系下基类成员的各类型成员访问关系的变化
class Person
{
public:
void Print()
{
cout << _name << endl;
}
protected:
string _name; // 姓名
private:
int _age; // 年龄
};
//class Student : protected Person
//class Student : private Person
class Student : public Person
{
protected:
int _stunum; // 学号
};
int main()
{
Student stu1;
stu1.Print();
return 0;
}
2.基类和派生类对象赋值转换
#include <iostream>
#include <string>
using namespace std;
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;
}
解析都在代码中。
3.继承中的作用域
// Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆
class Person
{
protected :
string _name = "小李子"; // 姓名
int _num = 111; // 身份证号
};
class Student : public Person
{
public:
void Print()
{
cout<<" 姓名:"<<_name<< endl;
// 在子类成员函数中,可以使用 基类::基类成员 显示访问
cout<<" 身份证号:"<<Person::_num<< endl;
cout<<" 学号:"<<_num<<endl;
}
protected:
int _num = 999; // 学号
};
void Test()
{
Student s1;
s1.Print();
};
#include <iostream>
#include <string>
using namespace std;
// 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);
};
int main()
{
Test();
return 0;
}
解析在代码里,结果如下:
4.派生类的默认成员函数
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
Person(const char* name = "peter") // 构造函数
: _name(name)
{
cout << this << "Person()" << endl;
}
Person(const Person& p) // 拷贝构造函数
: _name(p._name)
{
cout << this << "Person(const Person& p)" << endl;
}
Person& operator=(const Person& p) // 这一步是重载了操作符
{
cout << this << "Person operator=(const Person& p)" << endl;
if (this != &p)
_name = p._name;
return *this;
}
~Person() //析构函数
{
cout << this << "~Person()" << endl;
}
protected:
string _name; // 姓名
};
class Student : public Person
{
public:
Student(const char* name, int num)
: Person(name)
, _num(num)
{
cout << this << "Student()" << endl;
}
Student(const Student& s)
: Person(s)
, _num(s._num)
{
cout << this << "Student(const Student& s)" << endl;
}
Student& operator = (const Student& s)
{
cout << this << "Student& operator= (const Student& s)" << endl;
if (this != &s)
{
Person::operator =(s);
_num = s._num;
}
return *this;
}
~Student()
{
cout <<this <<"~Student()" << endl;
}
protected:
int _num; //学号
};
void Test()
{
Student s1("jack", 18);
Student s2(s1);
Student s3("rose", 17);
s1 = s3;
}
int main()
{
Test();
return 0;
}
这个大致分析写在了代码注释里,其实就是想说个子类和父类默认函数执行顺序的事情,大家可以粘过去试一试运行。运行结果如下:
5.继承与友元
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string>
using namespace std;
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);
}
6.继承与静态成员
#include <iostream>
#include <string>
using namespace std;
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;// 打印出来是4
Student::_count = 0;
cout << " 人数 :" << Person::_count << endl;// 打印出来是0
}
int main()
{
TestPerson();
return 0;
}
7.复杂的菱形继承及菱形虚拟继承
菱形继承:菱形继承是多继承的一种特殊情况。如下图所示:
那么到底为什么我们要把这个拿出来说呢,实际上,是因为这个出现问题了,菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在Assistant的对象中Person成员会有两份。
造型是这样的:
这里是只画出来了一点数据,但是如果数据多的话,这种方式明显不合理。
我把这些问题再代码复现一下:
#include <iostream>
#include <string>
using namespace std;
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";
}
至于如何解决这个菱形继承的问题,是有一一定难度的,也是面试常问的,如:
1. 什么是菱形继承?菱形继承的问题是什么?2. 什么是菱形虚拟继承?如何解决数据冗余和二义性的?
在这里我们也做一下讲解如何解决这种问题。
#include <iostream>
#include <string>
using namespace std;
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";
}
int main()
{
Test();
return 0;
}
那么虚拟继承的原理是什么呢?
其实有很多底层的东西,但是我们简单一点有助于大家理解来说的话就是,他会有一个偏移值,自动映射到最初的成员变量,就不会导致冗余数据了,但是这仅是我们初学者这样理解,底层远比者复杂的多,大家先了解,后续有机会了再深入了解。
8.继承和组合
#include <iostream>
#include <string>
using namespace std;
// Car和BMW Car和Benz构成is-a的关系
class Car {
protected:
string _colour = "白色"; // 颜色
string _num = "陕ABIT00"; // 车牌号
};
class BMW : public Car {
public:
void Drive() { cout << "好开-操控" << endl; }
};
class Benz : public Car {
public:
void Drive() { cout << "好坐-舒适" << endl; }
};
// Tire和Car构成has-a的关系
class Tire {
protected:
string _brand = "Michelin"; // 品牌
size_t _size = 17; // 尺寸
};
class Car {
protected:
string _colour = "白色"; // 颜色
string _num = "陕ABIT00"; // 车牌号
Tire _t; // 轮胎
};