面向对象编程三大特性之一,是类设计层次的复用,允许程序员在保持原有类特性的基础上进行扩展,增加功能,从而产生新的类
示例:
class Person// 基类/父类
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "peter"; // 姓名
int _age = 18; // 年龄
};
//派生类/子类
class Student : public Person
{
protected:
int _stuid; // 学号
};
//派生类/子类
class Teacher : public Person
{
protected:
int _jobid; // 工号
};
int main()
{
Student s;
Teacher t;
s.Print();//可以直接用从基类继承下来的函数
t.Print();
return 0;
}
继承格式:
class Student : public Person
派生类 继承方式 基类
继承基类成员访问方式的变化:
注:
我们讨论的是 public 继承的情况下
double d = 1.1;
int a = d;//这期间 d 先隐式类型转换生成一个临时变量,再赋值给 a
class Person
{
protected:
string _name; // 姓名
string _sex; // 性别
int _age; // 年龄
};
class Student : public Person
{
public:
int _No; // 学号
};
//…………………………………………
Student s ;
Person p = s;
//Student对象向 Public对象赋值,是天然支持的,不用隐式类型转换,不生成临时变量
//就像是把派生类中父类那部分切来赋值过去一样
Person& rp = s;
Person* pp = &s
基类一般不能给派生类赋值
● 在继承体系中基类和派生类都有独立的作用域
● 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义(但可以使用 基类::基类成员 显示访问)
● 关于成员函数,只需要函数名相同就构成隐藏(不构成重载:不在同一个作用域里)
● 建议别定义同名成员
示例:
class Person
{
protected:
int _id = 1;
};
class Student : public Person
{
public:
void Print()
{
cout << _id << endl;
}
protected:
int _id = 9;
};
int main() {
Student().Print();//输出9
}
● 构造函数:
派生类没有主动实现则调用父类的构造函数,主动实现时 父类继承而来的成员也要调用父类的构造函数
class Person
{
public:
Person(const char* name = "peter")
: _name(name)
{
cout << "Person()" << endl;
}
protected:
string _name; // 姓名
};
class Student : public Person
{
public:
Student(const char* name, int num)
: Person(name)//父类继承而来的成员只能调用父类的构造函数
, _num(num)
{
cout << "Student()" << endl;
}
protected:
int _num; //学号
};
int main() {
Student s("hazb",10);
return 0;
}
● 拷贝构造:
同理
//父类:
Person(const Person& p)
:_name(p._name)
{
cout << "Person(const Person& p)" << endl;
}
//子类:
Student(const Student& s)
:Person(s)//派生类天然支持直接给基类赋值
, _num(s._num)
{
cout << "Student(const Student& s)" << endl;
}
● 赋值重载:
同理,只是要注意函数构成隐藏的处理
//父类
Person& operator=(const Person& p)
{
cout << "Person operator=(const Person& p)" << endl;
if (this != &p)
_name = p._name;
return *this;
}
//子类
Student& operator=(const Student& s)
{
cout << "Student& operator= (const Student& s)" << endl;
if (this != &s)
{
Person::operator=(s);//函数构成隐藏
_num = s._num;
}
return *this;
}
● 析构函数:
同理,但要注意子类不用我们去显示调用父类的析构
//父类
~Person()
{
cout << "~Person()" << endl;
}
//子类
~Student()
{
但因为多态的一些原因,析构函数名会被处理成 destructor,构成隐藏,于是我们这么写:
//Person::Person();
然后却发现多调用了一次父类的析构函数,也就是说上面这句是多余的(悲)
//●析构函数不需要我们显示的去调用父类的析构函数,子类析构函数完成时会自动调用父类的析构函数(这么做就可以保证先构造的父后析构 —— 虽然好像没什么用……因为这又不是两个对象,而是一个对象中的两部分,但主打一个和通常情况一致)
cout<<"~Student()" <<endl;
}
友元不能继承
静态成员属于整个类(包括派生类),即派生类不会继承,但可以访问
//来自老师的解决方法(我稍微加了点东西)
class A
{
public:
static A CreateObj()
{
return A();
}
A(const A& a) {
_a = a._a;
}
void Print() {
cout << _a << endl;
}
private:
int _a = 1;
A()
{}
};
class B : public A
{};
int main()
{
A a = A::CreateObj();
a.Print();
return 0;
}
起初,C++的创始人只考虑到了继承的两种情况:
却没考虑到两者组合到一起的菱形继承:
菱形继承会导致数据冗余和二义性的问题,对此,后来补充的虚拟继承解决了这一问题:
class Person
{
public:
string _name; // 姓名
};
class Student : virtual public Person//注意 virtual 添加的位置
{
protected:
int _num; //学号
};
class Teacher : virtual public Person
{
protected:
int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
string _majorCourse; // 主修课程
};
int main()
{
Assistant a;
a._name = "peter";
cout << a._name;
return 0;
}
如果不加虚继承:
二义性:Assistant 类继承了两个类的 _name,直接访问不知道你访问的是哪个(想访问必须指定::)
冗余性:你这个人作为助手(Assistant)真的需要从学生类和老师类里继承两份_name下来吗?(一个名字显然不需要存两份)
实例分析:
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;
B b = d;
return 0;
}
虚继承借助记录的偏移量找到相应位置从而使得访问的是同一份数据
● 在这个示例中,使用虚继承后用到的空间反而变大了,对此的解释:
● 可以看到虚继承使得父类和子类的结构都发生了变化,父类的结构也跟着变化有什么好处?
B*ptrd = &d;
B*ptrb = &b;
//这么做使得这两个指针在访问时的操作是一样的
一道和上面的知识点联系感觉不太紧密的问题:
问:输出结果是什么?
class A class B class C class D
原因:
● 从我们的角度看,应当输出三次class A(经过三次A的初始化),但经编译器处理后,其实只会经过一次A的初始化
● 初始化的顺序和初始化列表中出现的顺序无关,和声明的顺序相关,而谁先被继承谁就先被声明
总结:建议不要使用多继承,一定不要用菱形继承,快跑!