摘要:继承继承,就是子类继承父亲的特征和行为,使得子类具有父类的成员变量和方法。此时,被继承的类称为父类或基类,而继承的类称为子类或派生类。,如果存在继承关系的时候,和就不一样了基类中的成员可以在派生类中使用,但是基类中的成员不能再派生类中使用。
c++在线编译工具,可快速进行实验: https://www.dooccn.com/cpp/
这段时间打算重新把c++捡起来, 实习给我的一个体会就是算法工程师是去解决实际问题的,所以呢,不能被算法或者工程局限住,应时刻提高解决问题的能力,在这个过程中,我发现cpp很重要, 正好这段时间也在接触些c++开发相关的任务,所有想借这个机会把c++重新学习一遍。 在推荐领域, 目前我接触到的算法模型方面主要是基于Python, 而线上的服务全是c++(算法侧, 业务那边基本上用go),我们所谓的模型,也一般是训练好部署上线然后提供接口而已。所以现在也终于知道,为啥只单纯熟悉Python不太行了, cpp,才是yyds。
和python一样, 这个系列是重温,依然不会整理太基础性的东西,更像是查缺补漏, 不过,c++对我来说, 已经5年没有用过了, 这个缺很大, 也差不多相当重学了, 所以接下来的时间, 重温一遍啦 ?
资料参考主要是C语言中文网和光城哥写的C++教程,然后再加自己的理解和编程实验作为辅助,加深印象。
今天这篇文章是C++非常重要的一块,关于类的继承和派生,我们知道C++面向对象开发有四大特性: 抽象,封装,继承和多态。 前面发现,通过定义类,把事物的数据和功能进行抽象,而通过隐藏对象的属性和实现细节,对外只提供接口的方式对类的内部成员形成了封装。 这两个前面都已经了解过, 而这篇文章主要是整理继承,即子类继承父类的特征和行为,使得子类具有父类的成员变量和方法, 继承最大的一个好处就是代码复用,两个类有一些相同的属性和方法。
这篇内容会有些偏多,还是各取所需即可 ?
主要内容如下:
Ok, let’s go!
在聊C++继承和派生之前,先来看看C++面向对象开发的四大特性,这样能先宏观把握一下继承到底位于什么样的位置。
C++面向对象开发有四大特性: 抽象,封装,继承和多态, 正所谓编程语言的背后都非常相似,Java既然也是面向对象的语言,同样也会有这四大特性。
抽象和封装前面其实已经整理过了, 封装主要讲的是信息隐藏,保护数据,而抽象又可以从两个层面来理解。
有了上面的宏观把握,再看继承就比较容易理解, 简单的讲,继承就是一个类从另一个类获取成员变量和成员函数的过程。 此时,被继承的类称为父类或基类,而继承的类称为子类或派生类。
C++中派生和继承是站在不同角度看的同种概念。继承时从儿子的角度看,派生是父亲的角度看,实际说的是一回事。
派生类除了拥有他爹的成员,还可以定义自己的新成员,增强功能,此时的好处就是只需要定义新成员即可,老的成员和功能,直接继承,实现了代码复用。
下面是两种典型使用继承的场景:
继承的语法:
class 派生类名:[继承方式] 基类名{ 派生类新增加的成员};
直接看个栗子:
class People{public: void setname(string name); string getname();private: string m_name; int m_age;};void People::setname(string name){m_name = name;} string People::getname(){return m_name;}class Student: public People{public: void setage(int age); int getage();private: int m_age;};void Student::setage(int age){m_age = age;}int Student::getage(){return m_age;}int main(){ Student stu; stu.setname("zhongqiang"); stu.setage(25); cout << stu.getname() << "的年龄是" << stu.getage() << endl; return 0;}
这个例子比较简单,不解释, 这里就会发现, Student继承了People之后,就有他的setname()
和getname()
方法,在子类里面可以直接调用。
上面演示了public的继承方式,但继承方式其实有3种, public, private, protected, 这哥仨不仅可以修饰类的成员,还可以指定继承方式。如果不写,默认是private(成员变量和成员函数默认也是private), 那么这三种继承方式到底有啥区别呢?
public, private, protected这哥仨,可以修饰类成员,之前见识过public和private了, 这里加上protected之后统一整理下访问权限的问题。
类成员的访问权限从高到低依次是public --> protected --> private
。 public成员可以通过对象来访问, private成员不能通过对象访问, protected成员和private成员蕾西, 也不能通过对象访问。
But, 如果存在继承关系的时候, protected和private就不一样了: 基类中的protected成员可以在派生类中使用,但是基类中的private成员不能再派生类中使用。
不同的继承方式使得基类成员在派生类中的访问权限也不一样, 下面这个很重要:
使用方法:
下面通过上面的代码例子来演示下, 由于private和protect继承方式会改变基类成员在派生类中的访问权限,导致继承关系复杂, 所以实际开发中一般使用public。
把上面的栗子修改下, 测试下上面的这几种情况,方便理解,这里只看public继承下面的。
class People{public: void setname(string name); string getname(); void setage(int age); int getage(); void setsex(string sex); string getsex(); void setwork(string work); string getwork(); // 属性 string m_sex; protected: string m_work; private: string m_name; int m_age;};void People::setname(string name){m_name = name;} string People::getname(){return m_name;}void People::setage(int age){m_age = age;}int People::getage(){return m_age;}void People::setsex(string sex){m_sex=sex;}string People::getsex(){return m_sex;}void People::setwork(string work){m_work=work;}string People::getwork(){return m_work;}class Student: public People{public: void setscore(float score); float getscore(); // 定义问候方法,这里面会访问基类的私有属性 string helloname(); string hellowork(); string hellosex();private: float m_score;};void Student::setscore(float score){m_score = score;}float Student::getscore(){return m_score;}// 访问基类中的公有属性string Student::hellosex(){return "hello, " + m_sex;}// 访问基类中的protect属性string Student::hellowork(){return "hello, " + m_work;}// 访问基类中的私有属性// string Student::helloname(){return "hello, " + m_name;} error: "std::string People::m_name" is private within this contextint main(){ Student stu; stu.setname("zhongqiang"); stu.setsex("man"); stu.setwork("student"); stu.setage(25); stu.setscore(66.6); cout << stu.getname() << "今年" << stu.getage() << ",性别: " << stu.getsex() << ", 职业: " << stu.getwork() << ", 分数: " << stu.getscore() << endl; //cout << stu.helloname() << endl; cout << stu.hellowork() << endl; cout << stu.hellosex() << endl; // 直接通过对象访问属性 cout << stu.m_sex << endl; // 公有属性到子类中依然是公有, 可以被访问 //cout << stu.m_name << endl; // error "std::string People::m_name" is private within this context //cout << stu.m_work << endl; // error "std::string People::m_work" is protected within this context //cout << stu.m_score << endl; // error "float Student::m_score" is private within this context return 0;}
在这里面就可以看出来, 在Student里面的成员函数中,只能访问到他爹的public属性和protect属性,不能访问他爹的private属性。而如果是通过Student的对象, 那么只能访问public属性,protect和private的都访问不到。
在派生类中访问基类的private成员的唯一方法就是借助基类的非private成员函数,如果基类没有非private成员函数,那么该成员在派生类中将无法访问。
这里注意一个问题,这里说的是基类的 private 成员不能在派生类中使用,并不是说基类的 private 成员不能被继承。实际上,基类的 private 成员是能够被继承的,并且(成员变量)会占用派生类对象的内存,它只是在派生类中不可见,导致无法使用罢了。private 成员的这种特性,能够很好的对派生类隐藏基类的实现,以体现面向对象的封装性。
using关键字可以改变基类成员在派生类中的访问权限, 比如将public改成private, protected改成public。
注意:using 只能改变基类中 public 和 protected 成员的访问权限,不能改变 private 成员的访问权限,因为基类中 private 成员在派生类中是不可见的,根本不能使用,所以基类中的 private 成员在派生类中无论如何都不能访问
class People{public: void setname(string name); string getname(); void setage(int age); int getage(); void setsex(string sex); string getsex(); void setwork(string work); string getwork(); // 属性 string m_sex; protected: string m_work; private: string m_name; int m_age;};void People::setname(string name){m_name = name;} string People::getname(){return m_name;}void People::setage(int age){m_age = age;}int People::getage(){return m_age;}void People::setsex(string sex){m_sex=sex;}string People::getsex(){return m_sex;}void People::setwork(string work){m_work=work;}string People::getwork(){return m_work;}class Student: public People{public: void setscore(float score); float getscore(); // 定义问候方法,这里面会访问基类的私有属性 string helloname(); string hellowork(); string hellosex(); using People::m_work; // 将m_work提升成public权限 private: float m_score; using People::m_sex; // 将m_sex降低为private权限};void Student::setscore(float score){m_score = score;}float Student::getscore(){return m_score;}// 访问基类中的公有属性string Student::hellosex(){return "hello, " + m_sex;}// 访问基类中的protect属性string Student::hellowork(){return "hello, " + m_work;}// 访问基类中的私有属性// string Student::helloname(){return "hello, " + m_name;} error: "std::string People::m_name" is private within this contextint main(){ Student stu; stu.setname("zhongqiang"); stu.setsex("man"); stu.setwork("student"); stu.setage(25); stu.setscore(66.6); cout << stu.getname() << "今年" << stu.getage() << ",性别: " << stu.getsex() << ", 职业: " << stu.getwork() << ", 分数: " << stu.getscore() << endl; // 直接通过对象访问属性 //cout << stu.m_sex << endl; // 这个这时候就会报错了 cout << stu.m_work << endl; // 这个就可以访问了 return 0;}
注意,using修改的是派生类里面的成员访问权限。并且是只能修改public和protected的访问权限。
这个说的情况是派生类中的成员(变量和函数),如果和基类中的成员重名,那么在派生类中使用该成员,实际上用的是派生类新增的成员,而不是从基类继承过来的。 即派生类遮蔽掉从基类继承过来的成员。
下面的这个例子,是Student继承了People, 又重写了People的show函数,那么通过Student对象调用show的时候,实际上是用的Student自身的show函数,但People的show函数也被Student继承了过来,如果想用,需要加上类名和域解析符。
class People{public: void show();protected: string m_name; int m_age;};void People::show(){ cout << m_name << " " << m_age << endl; }class Student: public People{public: Student(string name, int age, string sex); void show(); // 遮蔽基类的show() private: string m_sex;};Student::Student(string name, int age, string sex): m_sex(sex){ m_name = name; m_age = age; //m_sex = sex;}void Student::show(){ cout << m_name << " " << m_age << " " << m_sex << endl;}int main(){ Student stu("zhongqiang", 25, "man"); // 派生类新增的成员函数 stu.show(); // zhongqiang 25 man // 使用从基类继承过来的成员函数 stu.People::show(); // zhongqiang 25 return 0;}
这里我在实验的时候,发现个问题,就是Student的构造函数定义的时候, 本来是想用构造函数初始化列表的方式,一开始写的代码是这样:
Student::Student(string name, int age, string sex): m_name(name), m_age(age){ m_sex = sex;}
此时编译错误, 报错原因class "Student" does not have any field named "m_name"
, 而如果写成上面那种形式,或者不用参数化列表的方式,就没问题, 所以这里我感觉,参数化列表那个地方的参数,应该是当前类具有的成员变量才行, 继承过来的应该是不能往这里写。
上面的例子,其实就是派生类对基类的函数重写,内部在执行的时候, 先找派生类里面有没有对应的函数,如果有,就先执行派生类里面的重名函数,如果没有,那么再执行基类里面定义的。
那么,如果派生类里面的函数和基类的函数重名,但形参列表不一样的时候,此时会发生重载现象吗? 答: 不会。 一旦派生类中有同名函数,不管他们的参数是否一样,都会把基类中所有的同名函数遮蔽掉。
这个就不用例子演示了,而是整理下背后的所以然吧。
之前整理过,每个类都会有自己的作用域, 在这个作用域内会定义类的成员,那么,当存在继承关系的时候, 派生类的作用域嵌套在基类的作用域之内,如果一个名字在派生类的作用域没有找到,编译器会继续到外层的基类作用域查找该名字的定义。
两条:
看个嵌套作用域的例子:
class A{public: void func();public: int n = 500;};void A::func(){ cout<<"hello, changjinhu!!!"<<endl; }class B: public A{public: int n = 5000; int m;};class C: public B{public: int n = 50000; int x;};int main(){ C obj; cout << obj.n << endl; obj.func(); cout<<sizeof(C)<<endl; return 0;}
这个例子中的继承关系, B继承A, C继承B,那么作用域的嵌套关系如下:
func()
的时候,编译器没有在C类里面找到func这个名字,会继续到B作用域找,也没有找到,再往外,从A里面找到了,于是,调用A类作用域的func()
函数。有了上面这些,就能回答上面的两点疑问:
没有继承时对象内存的分布情况,成员变量和成员函数分开存储:
有继承关系的时候, 派生类的内存模型可以看成是基类成员变量和新增成员变量的总和,所有成员函数仍然存储在另外一个区域–代码区,由所有对象共享。
看个例子:
class A{public: A(int a, int b);protected: int m_a; int m_b;};A::A(int a, int b): m_a(a), m_b(b){}class B
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/122560.html
摘要:注意在完成配置环境变量后测试是否安装成功时键入命令安装出现了这样的问题,需要升级具体安装方法,可以参考该文档教程下载最新的之后,上边的问题就解决了。 由于其他项目中要使用Java的项目,所以,简单的学下,好对项目有个大概的了解。 一、Eclipse 安装 1.下载地址为: https://www.eclipse.org/downl... 2.配置环境 在配置环境变量中:设置JAVA_H...
摘要:类的介绍类用来描述具有相同的属性和方法的对象的集合。类变量类变量在整个实例化的对象中是公用的。类的定义语法格式如下类有一个名为的特殊方法,也即是构造函数,该方法会在定义对象的时候自动调用,可以通过参数传递来对类的实例进行设定。 1. 类的介绍 类(Class) 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例,类是对象的抽象。 ...
阅读 1574·2021-10-14 09:43
阅读 5400·2021-09-07 10:21
阅读 1253·2019-08-30 15:56
阅读 2099·2019-08-30 15:53
阅读 1211·2019-08-30 15:44
阅读 1984·2019-08-30 15:44
阅读 1296·2019-08-29 17:24
阅读 709·2019-08-29 15:19