C++继承是一种面向对象编程的特性,允许一个类(称为子类或派生类)继承另一个类(称为基类或父类)的属性和方法。通过继承,子类可以重用和扩展基类的代码,实现代码复用和层次化设计。
例如:
对于两个类 Studen 和 Teacher 它们都有相同的成员变量,但是又各自又不同的成员变量,把它们设计到两个类里面就是冗余的,但是它们各自的不同的成员变量是“学号”,“职称”,它们各自的功能也不同,是“学习”与“授课”
class Student
{
public:
void study(){
//..
}
protected:
string _name = "A";//重复的
string _student_id = "1231312312";//不重复的
};
class Teacher
{
public:
void teach() {
//..
}
protected:
string _name = "A";//重复的
string _teacher_id = "1231312312";//不重复的
};
为了解决这问题,我们使用它俩继承People的行为
class People
{
public:
void A()
{
}
protected:
string _name;
};
class Student :public People
{
public:
void study()
{
}
private:
string _student_id;
};
class Teacher :public People
{
public:
void teach()
{
}
private:
string _teacher_id;
};
C++继承是指一个类(子类)能够获取另一个类(基类)的属性和方法,从而实现代码复用和扩展的一种机制。
// 派生类名 继承方式 基类名
class Student : public People
{
//扩展内容
public:
void study()
{
}
private:
string _student_id;
};
public继承 protected继承 private继承
虽然关键字和访问限定符 相同 但是 不同 于访问限定符
它们遵循这样的规则进行继承
基类类成员//继承方式 | public | protected | private |
---|---|---|---|
public | public | protected | private |
protected | protected | protected | private |
private | 不可见 | 不可见 | 不可见 |
总结:基类类成员是private继承后全不可见 剩下的会按照继承方式 public不变 其它的相同与继承方式
注意
基类成员的private实际上是被继承了,只是无法在派生类中操作 基类中的“private”建议以protected的形式写 class的默认继承方式是priavre,struct的默认继承方式是public public的使用更多
class People
{
public:
void C()
{
_C = 0;
}
int _A;
protected:
int _B;
private:
int _C;
};
class Man :public People
{
//这里可以调用 还是public的 A
//还是protected的B
void TestA()
{
_A = 0;
_B = 0;
People::C();
}
//但是没有C 除非找他爹不然无法调用C
};
例如继承已有的vector的类模板时 要实例化一下
template<class T>
class A :public vector<T>//继承vector的模板
{
public:
void push(const T& x)
{
// 这里要实例化不然找不到pushback
vector<T>::push_back(x);
}
};
解释一下
可以认为基类的扩展为派生类,派生类的空间更大,基类指向自己派生类的时候,总会指向源自于基类的位置,不会指向派生类产出的额外的位置,对派生类产生了“切割”,不会越界访问所以可以,反之会越界所以不可以。
class People
{
public:
void A()
{
}
protected:
string _name;
};
class Student : public People
{
//扩展内容
public:
void study()
{
}
private:
string _student_id;
};
int main()
{
Student s;
People* p1 = &s;
People& p2 = s;
//而且派生类可以赋值给基类 但是基类不能赋值给派生类
People b = s;
return 0;
}
class A
{
public:
int a(){}
int b;
};
class B :public A
{
public:
int a(){/*我和上面不是同一个a*/}
int b;//我和上面不是同一个B
void test()
{
a();
A::a();
b = 0;
A::b = 0;
}
};
先说结论:建议你都要写 而且把基类的部分要单独看成一个内置类型 都要调用基类的玩意 析构时派生先析构 构造是基类先构造
派生类的构造函数必须调用基类构造函数的那一部分成员,如果基类没有默认构造函数,就必须在派生类构造函数的初始化列表显式调用。
class A
{
public:
A(int a):_a(a){}
int _a;
};
class B :public A
{
public:
B(int b, int a = 0) :_b(b), A(a) {};
int _b;
};
派生类的拷贝构造函数必须调用基类的拷贝构造函数完成拷贝初始化 派生类的 = 也要调用基类的 = 而且要显示调用和指定类域
class B :public A
{
public:
B& operator=(const B& x)
{
if (this != &x)
{
_b = x._b;
A::operator=(x);
}
}
int _b;
};
派生类型的析构函数会被调用玩后自动带哦用基类的析构函数清理基类成员,要保证先清理派生类型后清理基类,所以不用再派生的析构里写基类的析构函数 派生类初始化会先调用基类构造再调用派生构造 派生对象析构清理会先调用派生后调用基类 因为多态中⼀些场景析构函数需要构成重写,重写的条件之⼀是函数名相同那么编译器会对析构函数名进⾏特殊处理,处理成destructor(),所以基类析构函数不加virtual的情况下,派⽣类析构函数和基类析构函数构成隐藏关系。
给基类的构造一个私有就不能继承 给基类一个final关键字也不能继承
class base finall{};
友元关系不继承
静态成员继承,但是只指向同一个static成员实例
组合:
class B
{
class A
{
//
};
//
};
继承的关系类似于,A和B是绑定关系,白菜是菜,白菜就是菜 组合的关系类似于,A有B没B都不会破坏A或者B的存在,车有轮胎 车没轮胎
单继承:一个派生继承一个类 A指向B
多继承:一个派生继承多个类 A指向B和C
菱形继承:一个派生类继承多个类,这多个类的继承源自于同一个类 例:A指向B和C,B和C指向D
源自于多继承的菱形继承,会导致数据很多,而且对某些数据还有歧义,因为“隐藏”的原因,这个源于“原始基类”的数据被多次继承,每一个继承都有一个‘隐藏’,数据多而且乱七八糟,并不建议菱形继承,有些语言甚至没有多继承
class A{};
class B:public A{};
class C:public A{};
class D:public A,public B{};
c++提供了一个虚继承,底层复杂而且性能损失 虚继承采用virtual在腰部的继承的类上使用
class A{};
class B:virtual public A{};
class C:virtual public A{};
class D:public A,public B{};
在多重继承中,如果不使用虚继承,一个基类可能会因为被不同的派生类从不同路径继承而导致其数据成员在派生类中存在多份拷贝。这种情况会造成内存空间的浪费。通过虚继承,可以确保无论基类被继承多少次,其数据成员在内存中只占用一份空间。这是因为虚继承机制使得所有从同一基类派生的类共享该基类的一个实例。
当派生类从多个路径继承同一个基类时,如果不使用虚继承,在派生类中访问基类成员时可能会产生二义性。编译器在面对这种情况时,无法确定应该访问哪一份基类的拷贝,从而导致编译错误或不确定的行为。虚继承通过共享基类数据成员的方式消除了这种二义性,使得在派生类中访问基类成员变得明确且唯一。
虚继承的底层实现原理通常与编译器相关,但一般通过虚基类指针(vbptr)和虚基类表(vtable)来实现。每个虚继承的子类都有一个虚基类指针,该指针指向一个虚基类表,虚表中记录了虚基类与本类的偏移地址。通过偏移地址,可以找到虚基类成员,从而实现了在多重派生子类中只保存一份共有基类的拷贝的功能。