苏州网站建设苏州,wordpress 时间选择器,wordpress投稿上传图片,泾川网站建设文章目录 #x1f99c;1. 什么是继承#x1f40a;1.1 概念#x1f40a;1.2 格式#x1f40a;1.3 继承方式 访问限定符 #x1f426;2. 派生类和基类的赋值问题#x1f9a9;3. 派生类和基类同名成员问题#x1f413;4.派生类默认成员函数#x1f409;4.1 构造函数… 文章目录 1. 什么是继承1.1 概念1.2 格式1.3 继承方式 访问限定符 2. 派生类和基类的赋值问题3. 派生类和基类同名成员问题4.派生类默认成员函数4.1 构造函数4.2 拷贝构造4.3 赋值运算符重载4.4 析构函数 5. 友元和静态成员6. 多继承 1. 什么是继承
1.1 概念
在现实生活中谈起继承就会联想到继承家业、家产。
而在编程世界中继承也是如此一个类称子类或者派生类可以继承另一个类称父类或基类的属性和行为。
//定义一个人的属性 基类
class Person
{
public:Person(string name Kangkang, string gender male, int age 18):_name(name),_gender(gender),_age(age){cout Person() endl;}void Print(){cout name: _name endl;cout gender: _gender endl;cout age: _age endl;}
protected:string _name; // 姓名string _gender; // 姓别int _age; //年龄
};
//定义一个学生类继承人的属性 子类
class Student :public Person
{
public:Student(string name Lihua, string gender female, int age 20, int id111):Person(name,gender,age),_stuId(id){};
protected:int _stuId; //学号
};
int main()
{Person p;Student stu(Lisa,female,20,20230812);p.Print();stu.Print();return 0;
}1.2 格式
class 子类 : 继承方式 基类
{};1.3 继承方式 访问限定符 继承方式public继承protected继承private继承父类public成员子类的public成员子类的protected成员子类的private成员父类的protected成员子类的protected成员子类的protected成员子类的private成员父类的private成员子类不可见子类不可见子类不可见 这里其实很好分辨我们只需要取权限小的即可publicprotectedprivate 对于public成员我们可以直接在类的外面访问调用而对于protected成员可在类里面通过this指针访问而private成员虽然继承到了派生类对象中但无法访问到也可以理解为将父类的成员设为private就是不想让其他类继承 但是在实际应用过程中一般都是采用的public继承方式 Tips: 关键字class不指定继承方式时默认继承方式为private 而使用struct关键字时默认继承方式为public 但这里还是建议每次都显示继承方式 class A
{
public:void func1() { cout func1() endl; }
protected:void func2(){ cout func2() endl; }
private:void func3(){ cout func3() endl; }int _a 0;
};
class B :public A
{
public:void Print(){this-func1();this-func2();}int _b 1;};
int main()
{B b;b.Print();
}2. 派生类和基类的赋值问题
派生类和基类之间的赋值操作涉及到对象切片的问题。派生类的对象可以赋值给基类对象/基类指针/基类引用 但反过来将基类对象赋值给派生类对象是不合法的因为这可能导致对象切片即派生类对象的额外成员信息丢失 这就好比学习CC是在C语言的基础上衍生出来的可以理解问C继承了C语言的衣钵C的代码可以兼容C的代码反之C的代码却不能却不能兼容C。 3. 派生类和基类同名成员问题
class A
{
public:int _x1;int _y2;void Print(){cout A() endl;}
};
class B :public A
{
public:int _x 3;int _y 4;void Print(){cout B() endl;}
};
int main()
{B b;cout b._x endl; // 3cout b._y endl; // 4b.Print(); // B()
}这段代码基类A和派生类B成员名都是相同的但我们输出发现输出的是派生类的成员那这里是否继承了A的这些成员呢 通过监视窗口发现这里A是被B继承了但是由于成员名相同A被B给隐藏了这也叫重定义。
如果要访问基类的成员可使用基类基类成员显示访问这也可理解问他们都有着独立的作用域 4.派生类默认成员函数
4.1 构造函数
派生类的构造函数必须调用基类的构造函数来初始化继承下来的那部分成员如果基类没有默认构造那在派生类构造函数的初始化列表显示调用
class Person
{
public://全缺省默认构造Person(string name Kangkang):_name(name){}
protected:string _name;
};
class Student : public Person
{
public:Student(string name, int id):Person(name),_id(id){}void Print(){cout name: _name endl;cout id: _id endl;}
protected:int _id;
};
int main()
{Student stu(Lisa,2023);stu.Print();return 0;
}4.2 拷贝构造
派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化但我们可以直接传子类对象因为调用父类的拷贝构造时父类会自动切片拿到父类中的对象
父类拷贝构造
Person(const Person p):_name(p._name)
{}子类拷贝构造
Student(const Studentstu):Person(stu._name),_id(stu._id)
{}调用
Student stu(Lisa, 2023);
stu.Print();
Student stu2(stu);
stu2.Print();4.3 赋值运算符重载
子类的operator必须要调用父类的operator完成基类的复制但是因为赋值运算符重载了那么子类和父类的名字都是一样这样就造成了子类隐藏了父类的operator。所以需要显示调用父类的operator
//operator
Person operator(const Person p)
{if (this ! p){_name p._name;}return *this;
}
Student operator(Student stu)
{if (this ! stu){//指定调用父类Person::operator (stu);_id stu._id;}return *this;
}4.4 析构函数
子类的析构函数会在被调用完成后自动调用父类的析构函数清理基类成员因为这样才能保证子类对象先清理子类成员再清理父类成员的顺序。 Tips: 切记这里是自动调用父类的析构所以我们不需要在子类的析构函数中调用父类的析构函数 如果这里有指针同一块区域释放两次会造成未定义行为 5. 友元和静态成员
在继承中友元关系是不可以被继承的就好比咱们朋友的朋友不一定是咱们的朋友。
对于静态成员这里继承的是它的使用权就比如家里有三个孩子一个大哥哥两个小弟弟这个哥哥是他两“共用的”并不会说2个弟弟必须有2个哥哥。
class A
{
public:static int _sa;int _a;
};
int A::_sa 1;
class B :public A
{
public:int _b;
};int main()
{A a;B b;cout a._a: a._a endl;cout b._a: b._a endl;cout a._sa: A::_sa endl;cout b._sa: B::_sa endl;
}这里也可以验证对于静态成员父类和子类是共用的可用于计算父类有多少个派生类。
6. 多继承
对于一个子类只有一个直接父类这种关系称为单继承
//单继承
class A
{};
class B:public A
{};
class C :public B
{};而对于一个子类有多个直接父类这种关系称为多继承
//多继承
class A
{};
class B
{};
class C :public A, public B
{};多继承会引发一个很麻烦的问题——菱形继承 我们先来上代码
class A
{
public:int _a;
};
class B:public A
{
public:int _b;
};
class C :public A
{
public:int _c;
};
class D :public B, public C
{
public:int _d;
};
int main()
{D d;d._a 1; //errord._b 2;return 0;
}这段代码直接报错_a的指定不明确因为D类继承了B类和C类编译器不知道这个_a是属于继承的哪个类从而产生二义性的问题。 当然前面也提到过可以通过指定类域来明确告诉编译器这属于哪个类
D d;
d.B::_a 1;
d.C::_a 2;
d._b 3;
d._c 4;
d._d 5;这样虽然解决了二义性的问题但是这样看的数据十分冗余很容易分不清哪个是哪个 为了填补这个坑推出了一种名为虚拟继承的继承方式仅限菱形继承使用其他地方不要使用
class A
{
public:int _a;
};
class B:virtual public A
{
public:int _b;
};
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;d._a 6;return 0;
}使用虚拟继承之后我们发现这里的_a只有一份了而且我们查看内存发现数据并不是连在一起多了一些地址 这叫做虚基表用来寻找基类偏移量的表虚拟继承的派生类里面就包含了这个虚表这个虚表记录着距离基类的偏移量如果要用到基类的数据加上这个距离就能找到这样就解决了数据的二义性和数据冗余的问题。
但是在实际过程中这个模型十分鸡肋且复杂所以一般都不会采用这种继承方式。 多继承就属于C语法复杂的一个体现而继承虽然可以复用但是继承的耦合度十分高代码直接的依赖关系很强这样就造成了代码的不便于维护。但又涉及到多态必须使用继承所以在实际之中代码要复用的话我们得分场景。 那本期的方向就到这咯我们下期再见如果有下期的话。