前言
在上述教程中,我们已经完成了 C++相对于 C语言来说独特的语法部分,在接下来的教程中,我们将叙述 C++中面向对象的语法特性。我们在学习面向对象的这种编程方法的时候,常常会听到这三个词,封装、继承、派生,这也是面向对象编程的三大特性,在本节我们将依次阐述封装、继承、派生的具体用法,在这里,我们先叙述的是封装这个属性的的相关内容。下图是关于 封装 这个特性所包含的一些内容。
封装
下图就是封装所具备的相关特性:
image-20210209204824118
那么上图所示的抽象出数据成员以及成员函数具体的含义是什么呢,正如前面教程所述,在前面的教程里,我们选用一个 Person类来作为例子进行讲解,其中这个类里我们有 name以及age,这个也就是我们抽象出来的数据,那抽象出来的成员函数也就是前面教程讲到的setName()和setAge()函数,在设计这个类的时候,会把这个类的一些成员设置为私有的或者公有的,这也就是访问控制。具体的代码如下所示:
- /* 为了代码简便,省略相关构造函数以及析构函数,为的是展示封装的特性*/
- class Person {
- private:
- char *name;
- int age;
- public:
- Person()
- {
- cout << "Person" << endl;
- name = NULL;
- }
- ~Person()
- {
- cout << "~Person()" << endl;
- if (this->name)
- {
- delete this->name;
- }
- }
- void setName(char *name)
- {
- if (this->name) {
- delete this->name;
- }
- this->name = new char[strlen(name) + 1];
- strcpy(this->name, name);
- }
- int setAge(int a)
- {
- if (a < 0 || a > 150)
- {
- age = 0;
- return -1;
- }
- age = a;
- return 0;
- }
- };
继承
继承的含义就如其字面意思一样,用更加专业的话来说,就是从基类继承相关属性,而这个新的类就叫做派生类。下面这个示意图也表明了继承所带来的代码的简洁与方便。
image-20210209211013964
就如上述这张图所示,一个人肯定具有名字和年龄这两个属性,那作为一个学生来讲,他也必定具备名字和年龄这两个属性,那这个时候是要在 Student类里重新定义这些属性么?显然,因为引入了继承这个特性,只需要继承Person类,那么Student就具备 Person类的相关属性。在上述代码的基础上,我们增加如下所示的代码:
- /* 注意是在上述代码的基础上 */
- class Student : public Person
- {
- };
- int main(int argc, char **argv)
- {
- Student s;
- s.setName("zhangsan");
- s.setAge(16);
- s.printInfo();
- return 0;
- }
上述代码中,Student类是继承自 Person类的,我们可以看到在上述所示的Student类中,并没有setName和 setAge的成员函数,但是在定义的 Student实例中,却能够适用 setName和 setAge的成员函数,这也就说明了 Student类已经继承了 Person类。
继承后的访问控制
private
一个派生类从一个基类继承而来,而继承的方式有多种,可以是私有继承,也可以是公有继承,同时也可以是保护继承。那么这个时候基类的各个数据成员的访问属性又是怎么样的呢,我们来看一下下面这张图,其展现了以各种方式继承自基类的派生类的数据成员的属性。
image-20210209223145289
从这个表可以清楚地知道基类的访问属性与派生类的访问属性的对应情况。同样的,我们用一个简单的例子来说明这个知识点:
- class Father
- {
- private:
- int money;
- public:
- void it_skill(void)
- {
- cout << "The father's it skill" <<endl;
- }
- int getMoney(void)
- {
- return money;
- }
- void setMoney(int money)
- {
- this->money = money;
- }
- };
这个是基类的数据成员以及成员函数,为了更好的说明继承后的数据的属性,我们定义一个 son类,代码如下所示:
- class Son : public Father
- {
- private:
- int toy;
- public:
- void play_game(void)
- {
- cout << "play_game()" << endl;
- int m;
- //money -= 1; /* 错误的代码 */
- m = getMoney();
- m--;
- setMoney(m);
- }
- };
上述定义了两个类,一个是 Father类,一个是 Son类,Son类继承于 Father类,这两个类用通俗的语言进行解释便是,父亲有自己的私房钱,儿子有自己的玩具,父亲有一项技能是 it,儿子呢比较喜欢玩游戏。因为是继承,所以儿子类具有父亲类的相关属性,但是,作为儿子是不能够直接去父亲兜里拿钱的,那会被揍,但是如果儿子需要钱,可以向父亲要。这对应的代码也就是上述中 money -= 1,但是这是错误的,不能直接从父亲的兜里拿钱,而剩余的三句代码的意思也就相当于是向父亲要钱。用专业的话来讲也就是:派生类不能够访问基类的私有成员,紧接着是主函数的代码:
- int main(int argc, char **argv)
- {
- Son s;
- s.it_skill();
- s.setMoney(10);
- cout << "The money is:" << s.getMoney() << endl;
- s.play_game();
- return 0;
- }
代码输出的结果如下所示:
image-20210209232507917
protected
还是采用比较通俗的话来叙述这一知识点,儿子相对于父亲的关系自然是与其他人有所不同的,比如有一把父亲房间门的钥匙,对于儿子来说是可以拿到的,但是对于外人来说,这是不可访问的。那在程序中要如何实现这么一个功能呢?这里就要引入 protected了。代码如下所示:
- class Father {
- private:
- int money;
- protected:
- int room_key; /* 增添的 room_key */
- public:
- void it_skill(void)
- {
- cout<<"father's it skill"<<endl;
- }
- int getMoney(void)
- {
- return money;
- }
- void setMoney(int money)
- {
- this->money = money;
- }
- };
我们可以看到在 Father类中,增添了一项就是 protected修饰的 room_key,紧接着我们来看Son类的代码:
- class Son : public Father {
- private:
- int toy;
- public:
- void play_game(void)
- {
- int m;
- cout<<"son paly game"<<endl;
- m = getMoney();
- m--;
- setMoney(m);
- /* 外人不能拿父亲的房间钥匙
- * 儿子可以
- */
- room_key = 1;
- }
- };
我们看到,这个时候,是可以在 Son类里面直接操作使用 protected修饰的 room_key的。在这里总结一下就是:派生类可以直接访问到基类用 protected 修饰的数据成员。接下来,我们继续看主函数的代码:
- int main(int argc, char **argv)
- {
- Son s;
- s.setMoney(10);
- cout << s.getMoney()<<endl;
- s.it_skill();
- s.play_game();
- //s.room_key = 1;
- return 0;
- }
通过上述代码可以看到 s.room_key = 1这条语句被注释了,这条语句是错误的,虽然基类使用了 protected修饰了 room_key,但是在主函数中,仍然是不能够直接访问 room_key的。
调整访问控制
依旧采用比较通俗的话来阐述,如果儿子从父亲那里继承了一些东西,那这个时候,继承得到的这些东西的处理权就全在儿子了。在程序里面也是同样的道理,我们在上述代码的基础上进行更改,Father类不变,改变 Son类。代码如下所示:
- class Son : public Father {
- private:
- int toy;
- public:
- using Father::room_key;
- void play_game(void)
- {
- int m;
- cout<<"son paly game"<<endl;
- m = getMoney();
- m--;
- setMoney(m);
- room_key = 1;
- }
- };
上述代码中,我们可以看到在 public的作用域内,我们使用 using Father::room_key将 room_key的属性更改为 public,做了这样的更改之后,我们就可以在主函数里直接访问 room_key了。代码如下所示:
- int main(int argc, char **argv)
- {
- Son s;
- s.setMoney(10);
- cout << s.getMoney()<<endl;
- s.it_skill();
- s.play_game();
- s.room_key = 1;
- return 0;
- }
上述代码是可以运行的,也说明这种方式是可行的。但是如果想要将 money的属性更改为 public,也就是增加如下所示的代码:
- class Son : public Father {
- private:
- int toy;
- public:
- using Father::room_key;
- using Father::money;
- void play_game(void)
- {
- int m;
- cout<<"son paly game"<<endl;
- m = getMoney();
- m--;
- setMoney(m);
- room_key = 1;
- }
- };
那么编译将不会通过,错误信息如下所示:
image-20210210001456319
说明这种方法是不可行的,这是为什么呢?是因为对于 Son来说,money本身就是它不能访问到的数据,那么自然也就不能够对其属性进行更改了。换句更加专业的话来叙述也就是:在调整访问控制的时候,只有类本身能够访问到的数据才能调整它的访问控制,如果其本身对于这个类就是不能够访问的,那么也就无法对其进行更改。
那上述可以说是提升访问控制,同样的,也可以降低访问控制,比如说上述的 it_skill,如果不想把这个属性继续继承下去或者说不让外部能够访问到它,那么也可以降低它的访问控制,降低的方法跟提升的方法是一样的,只需要在 private中加上一句代码就可以,加了的代码如下所示:
- class Son : public Father
- {
- private:
- int toy;
- using Father::it_skill;
- public:
- /* 省略 */
- };
因此,只要对于派生类能够看到的数据成员或者成员函数,它都能够提高或者降低它的访问控制。
三种不同继承方式的差异
在上述的内容中,我们提到了派生类在继承基类的时候,存在不同的继承方式,不同的继承方式对数据成员的使用以及其成员函数的调用存在不同的影响,下面分别是三种不同的继承方式:public和 private以及protected,代码如下所示:
- /* 以 public 方式继承 */
- class Son_pub : public Father {
- private:
- int toy;
- public:
- void play_game(void)
- {
- int m;
- cout<<"son play game"<<endl;
- m = getMoney();
- m--;
- setMoney(m);
- room_key = 1;
- }
- };
- /* 以 private 方式继承 */
- class Son_pri : private Father {
- private:
- int toy;
- public:
- void play_game(void)
- {
- int m;
- cout<<"son play game"<<endl;
- m = getMoney();
- m--;
- setMoney(m);
- room_key = 1;
- }
- };
- /* 以 protected 方式继承 */
- class Son_pro : protected Father {
- private:
- int toy;
- public:
- void play_game(void)
- {
- int m;
- cout<<"son play game"<<endl;
- m = getMoney();
- m--;
- setMoney(m);
- room_key = 1;
- }
- };
上述代码就是以三种不同方式从 Father类得到的 Son类,每一种继承方式存在什么不同呢,我们通过主函数来说明这个问题:
- int main(int argc, char **argv)
- {
- Son_pub s_pub;
- Son_pro s_pro;
- Son_pri s_pri;
- s_pub.play_game();
- s_pro.play_game();
- s_pri.play_game();
- s_pub.it_skill();
- //s_pro.it_skill(); // error
- //s_pri.it_skill(); // error
- return 0;
- }
通过上述代码,并对照上述那种表,我们可以知道,无论是何种继承方式,派生类内部public的成员函数都是可以使用的,而对于从基类继承得到的成员函数,如果是以 protected和private方式来继承的话,那么是不能够在主函数进行调用的,因此上述代码中注释掉的两句后面表明了错误。
上述的代码所展示的是一层的继承,我们在继承得到的派生类 Son的基础上继续继承得到 Grandson,首先我们先在 Father类里新增加一个public的数据成员,增加的代码如下所示:
- class Father
- {
- private:
- int money;
- protected:
- int room_key;
- public:
- int address;
- /*其余不改动,省略*/
- };
增加了上述Father类的代码之后,我们来看 Grandson_pub类的代码:
- class Grandson_pub : public Son_pub
- {
- public:
- void test(void)
- {
- room_key = 1; /* room_key is protected */
- address = 2; /* address is public */
- }
- };
上述代码中,Grandson_pub是以 public的方式从 Son_pub继承而来,room_key在 Father类是 protected,在 Son_pub类也是 protected,那么在这里也是 protected,而对于 address来说,它在 Father类里是 public,在 Son_pub里也是 public,在这里也是 public,所以在这里都能够访问到。
紧接着来看,Grandson_pro类的代码:
- class Grandson_pro : public Son_pro
- {
- public:
- void test(void)
- {
- room_key = 1; /* room_key is protected */
- address = 2; /* address is protected */
- }
- };
上述中,Grandson_pro是以 public的方式从 Son_pro中继承得到的,以刚刚那种分析的思路我们能够分析得出 room_key当前是 protected以及 address是 protected,那么当前的数据成员在这也就是都能够访问的了。
继续来看Grandson_pri类的代码,代码如下所示:
- class Grandson_pri : public Son_pri
- {
- public:
- void test(void)
- {
- //room_key = 1; /* room_key is private */
- //address = 2; /* address is private */
- }
- };
上述中,Grandson_pri是以 public的方式从 Son_pri中继承得来,同样按照上述的分析方法,我们能够分析出 room_key和 address都是 private的,既然是 private的,那么也就不能够进行访问,因此上述代码中,我们将两句代码进行了注释。
小结
上述就是本次分享的关于封装以及继承的相关内容,主要是关于继承之后数据成员的访问控制,以及通过不同的方式进行继承时的数据成员的访问控制。
上述教程所涉及的代码可以通过百度云链接的方式获取到,下面是百度云链接:
链接:https://pan.baidu.com/s/18AGYqxkxsEcR4ZW6_Nhevg
提取码:dlst
本文转载自微信公众号「 wenzi嵌入式软件」,可以通过以下二维码关注。转载本文请联系 wenzi嵌入式软件公众号。