从二叉搜索树(Binary Search Tree)入手,学习C++中类的构建 --(三)public\private\protected、封装\继承\多态、提供更丰富的函数接口
2015-03-13 14:22
399 查看
面向对象(objected-oriented,或者说理解为面向物体\实体 更合适一些),重要特点就是:封装、抽象、多态、继承
在本篇中,主要提及的是封装(Encapsulation)。而封装提供给我们的不只是更加结构化、模块化的程序设计,还有一个最重要的特性:数据隐藏。
类成员的访问者有三个:自身成员访问、类的对象访问、派生类访问。很好理解——
自身成员访问:
因为是自己的,所以相当于root权限。不会对自己隐藏自己的东西,哪怕是private。类比: 不管你怎么隐藏硬盘中的文件,硬盘自己都知道它在哪
类的对象访问:
——什么是对象?你不会做cpu,不会写操作系统,但这并不妨碍你能使用电脑。电脑就是一个对象,你能够使用该对象提供的接口-interface。
对象可以看做是用class封装后生产出的产品。生产的成品会使用自己内部的构造,而你只是负责使用的,所以你不必知道它是怎么使用的。
另一方面生产者往往也并不想给你公开这个东西的内部结构,一是怕你随便拆拆坏了--[数据保护],二是为了保护自己的专利--[数据隐藏]。
所以,类把源码和成品分开,并添加权限private\pubic做为区分。
派生类访问:
虽然同样是LINUX操作系统,但是我不仅可以在PC上使用它,我加一个外壳,把它做成Android,还可以在手机上使用它。
这里,Android就是Linux的一个派生类,而作为操作系统,Android显然不能不关心Linux是怎么工作的,所以对于Android用到的基于Linux的功能,Android必须能够有权限看到这部分的工作方式——protected。(当然作为用产品的人,我们还是不必知道这个东西里面究竟是怎么回事,对于对象来说protected也是加密的)。
而之所以开辟出这么一个级别,是因为面向对象的特性:多态、继承。Linux能够做桌面操作系统Ubuntu,还能做嵌入式操作系统Raspbian,更能来做手机操作系统Android。(其实WIndows也很厉害的,还能做ATM。。。)他们属于linux的分支(继承),但又各自拥有不同的新特性(多态)
说了这么过,访问标号的问题反而好理解了:
public——公开展示,任何人都可以访问
private——隐私内容,只有类成员函数可以访问。(不过friend友元函数也是可以的,可以理解为至交好友之类的。这里先不讨论)
protected——类成员函数可以访问、派生类可以访问基类的protected、[b]对象不可访问。[/b]
Child是Father的派生类。
这里主要讨论Child后面的 ”public“ 处,继承中访问标号的使用。
1 - 继承什么? 就是基类把自己分化,以适应各种不同的环境。
因为派生类没有基类的private成员访问权限。首先基类的private是自己的秘密,不会给任何派生类!
————因此也就是说:我们所继承的东西仅仅是pubic和protected!
————我们所关心的是,把基类成员,加上一个权限符号,他会变成什么权限?
————简单理解,就是只能把基类的成员变得更隐秘更难查看,更少的人能看到,而不能把它公开给更多人。
现在假设基类是一个公司的总裁。总裁手里有三种信息:
private——总裁经常挪用一些公款,这是秘密,谁也不能知道、
protected——公司的目前研发的一个项目,公司的职员可以知道、
public——公司的基本信息,我们公开给任何使用我们产品的用户
2 - private继承。
总裁现在需要向竞争对手处卧底一个职员,从事秘密工作。希望他保密公司的任何信息,可以提供一些维护服务,但是不能透露有关公司的任何信息。
————基类的protected和public,经过私有继承后,全部作为派生类的private成员。
3 - protected继承。
总裁现在要找一个下属,去开一个子公司拓展业务。子公司的员工知道他们是个分公司,但是子公司对外不公开自己的母公司。
————基类的public成员被继承为protected,显然protected继承后还是protected
4 - public继承。
总裁的业务做大了,在杭州建立了一个研究院,和总公司属于平行关系,相关研发都可以在新的研究院开展。
————protected仍然是protected,public依旧是public。
1 -- [b]private继承的卧底也需要有个合理的身份去伪装,因此可以发展出自己的public属性;[/b]
当然卧底有时候也需要发展自己的下线,所以卧底发展出了protected成员;
有时候卧底也会想要反水,这属于隐秘信息private。
————当然,毕竟是可控的派生类,再怎么发展卧底也不会把继承来的private说出去。
2 -- protected继承的子公司在发展中,[b]建立了自己的公众形象——产生public;[/b]
子公司随着业务的拓展,开了许多分部——产生protected;
子公司管理层腐败,[b]做了一些假账——发展出了自己的private;[/b]
[b] ————当然,继承的protected成员只能被子公司用来自己使用或者来开分部[/b]
3 -- public继承的杭州研究院,自然不必多说,和子公司有同样的权限。
不过因为吸纳了更多的员工,派生的类虽然和基类很像,但是也有自己的特色——public、private、protected
2——虽然开个子公司是个大事,但是总裁记性实在太差,子公司开了一堆分部,但是总裁表示不记得这个子公司不是我开的;
3——虽然盖研究院的大楼花了10个亿,但是总裁钱多任性,某一天研究院终于解决了某个核心技术的研发,总裁却一点也没有高兴,因为他忘了……
总裁很健忘——基类并不能知道派生类里面有什么,假如基类不去看源代码,根本不知道自己竟然有这么个派生类、更不用说取得对派生类内部成员的权限了!
因此:
1 - 派生类完全可看做是一个独立的类!
2 - 基类是静态的,不会访问派生类内部,更不会动态获取派生类的变化!(不过提供一块共享区域倒是可以让基类获得派生类的变化,比如传递引用之类的)
3 - 因为基类是静态的,所以经过继承的派生类是多态的!这也是面向对象的重要特性!
4 - 相反,派生类的记性倒是很好,它能够记得自己的上一代基类是谁(调用构造函数时,对于未定义构造函数的成员会去基类中寻找)
5 - 尽管派生类的记性比总裁好那么一点,但是让它记住自己的爷爷是谁也还是太难了!(不关心基类的基类)
(注意:这里并没有考虑virtual继承的情况,如果是virtual继承,则是共享模式,即base能够获得派生类带来的变化)
然后,定义我们的inorderBST()函数,使之能够中序遍历。
现在,我们的中序遍历已经完成了,把它放在拷贝构造函数中调用,即可保证生成类对象的时候,不仅产生一个二叉搜索树,而且维护一个有序的vector
copy constructor——
因此,我希望用下面的方式,提供一个inorderBST的函数借口:
(想想qsort,一样的使用方式。)
上面就是我们对于inorderBST函数的实现。
因为中序遍历中,需要不断移动操作的位置,因此我们在这里重载了inorderBST()函数。
第一个作为public的接口,提供给对象使用。
第二个函数作为内置调用的函数,标记为private。只希望内部调用,而希望对object隐藏。
至此,我们的class变成了这样——
其中,printNode用来打印节点,输出排序后的队列。
getNodeVal则演示了传入一个结构体用来获得参数/传入参数的使用方法。
在本篇中,主要提及的是封装(Encapsulation)。而封装提供给我们的不只是更加结构化、模块化的程序设计,还有一个最重要的特性:数据隐藏。
一、访问标号:public-private-protected
简单的来说,访问标号的用处就相当于为各个类成员添加一个权限。好比linux系统下的文件权限一样,可以对不同的访问者、用户组给予不同权限。类成员的访问者有三个:自身成员访问、类的对象访问、派生类访问。很好理解——
自身成员访问:
因为是自己的,所以相当于root权限。不会对自己隐藏自己的东西,哪怕是private。类比: 不管你怎么隐藏硬盘中的文件,硬盘自己都知道它在哪
类的对象访问:
——什么是对象?你不会做cpu,不会写操作系统,但这并不妨碍你能使用电脑。电脑就是一个对象,你能够使用该对象提供的接口-interface。
对象可以看做是用class封装后生产出的产品。生产的成品会使用自己内部的构造,而你只是负责使用的,所以你不必知道它是怎么使用的。
另一方面生产者往往也并不想给你公开这个东西的内部结构,一是怕你随便拆拆坏了--[数据保护],二是为了保护自己的专利--[数据隐藏]。
所以,类把源码和成品分开,并添加权限private\pubic做为区分。
派生类访问:
虽然同样是LINUX操作系统,但是我不仅可以在PC上使用它,我加一个外壳,把它做成Android,还可以在手机上使用它。
这里,Android就是Linux的一个派生类,而作为操作系统,Android显然不能不关心Linux是怎么工作的,所以对于Android用到的基于Linux的功能,Android必须能够有权限看到这部分的工作方式——protected。(当然作为用产品的人,我们还是不必知道这个东西里面究竟是怎么回事,对于对象来说protected也是加密的)。
而之所以开辟出这么一个级别,是因为面向对象的特性:多态、继承。Linux能够做桌面操作系统Ubuntu,还能做嵌入式操作系统Raspbian,更能来做手机操作系统Android。(其实WIndows也很厉害的,还能做ATM。。。)他们属于linux的分支(继承),但又各自拥有不同的新特性(多态)
说了这么过,访问标号的问题反而好理解了:
public——公开展示,任何人都可以访问
private——隐私内容,只有类成员函数可以访问。(不过friend友元函数也是可以的,可以理解为至交好友之类的。这里先不讨论)
protected——类成员函数可以访问、派生类可以访问基类的protected、[b]对象不可访问。[/b]
二、public-protected-private的继承
[1] 继承的使用:公用继承、受保护继承、私有继承
class Child : public/protected/private Father { };
Child是Father的派生类。
这里主要讨论Child后面的 ”public“ 处,继承中访问标号的使用。
1 - 继承什么? 就是基类把自己分化,以适应各种不同的环境。
因为派生类没有基类的private成员访问权限。首先基类的private是自己的秘密,不会给任何派生类!
————因此也就是说:我们所继承的东西仅仅是pubic和protected!
————我们所关心的是,把基类成员,加上一个权限符号,他会变成什么权限?
————简单理解,就是只能把基类的成员变得更隐秘更难查看,更少的人能看到,而不能把它公开给更多人。
现在假设基类是一个公司的总裁。总裁手里有三种信息:
private——总裁经常挪用一些公款,这是秘密,谁也不能知道、
protected——公司的目前研发的一个项目,公司的职员可以知道、
public——公司的基本信息,我们公开给任何使用我们产品的用户
2 - private继承。
总裁现在需要向竞争对手处卧底一个职员,从事秘密工作。希望他保密公司的任何信息,可以提供一些维护服务,但是不能透露有关公司的任何信息。
————基类的protected和public,经过私有继承后,全部作为派生类的private成员。
3 - protected继承。
总裁现在要找一个下属,去开一个子公司拓展业务。子公司的员工知道他们是个分公司,但是子公司对外不公开自己的母公司。
————基类的public成员被继承为protected,显然protected继承后还是protected
4 - public继承。
总裁的业务做大了,在杭州建立了一个研究院,和总公司属于平行关系,相关研发都可以在新的研究院开展。
————protected仍然是protected,public依旧是public。
[2]多态,继承后的变化
尽管派生类继承自基类,但是他们都是一个独立的个体——1 -- [b]private继承的卧底也需要有个合理的身份去伪装,因此可以发展出自己的public属性;[/b]
当然卧底有时候也需要发展自己的下线,所以卧底发展出了protected成员;
有时候卧底也会想要反水,这属于隐秘信息private。
————当然,毕竟是可控的派生类,再怎么发展卧底也不会把继承来的private说出去。
2 -- protected继承的子公司在发展中,[b]建立了自己的公众形象——产生public;[/b]
子公司随着业务的拓展,开了许多分部——产生protected;
子公司管理层腐败,[b]做了一些假账——发展出了自己的private;[/b]
[b] ————当然,继承的protected成员只能被子公司用来自己使用或者来开分部[/b]
3 -- public继承的杭州研究院,自然不必多说,和子公司有同样的权限。
不过因为吸纳了更多的员工,派生的类虽然和基类很像,但是也有自己的特色——public、private、protected
[3] 基类行为:总裁并不那么负责任
1——虽然private是派去卧底的,但是总裁把卧底派去后就忘记曾经派过这个人了;2——虽然开个子公司是个大事,但是总裁记性实在太差,子公司开了一堆分部,但是总裁表示不记得这个子公司不是我开的;
3——虽然盖研究院的大楼花了10个亿,但是总裁钱多任性,某一天研究院终于解决了某个核心技术的研发,总裁却一点也没有高兴,因为他忘了……
总裁很健忘——基类并不能知道派生类里面有什么,假如基类不去看源代码,根本不知道自己竟然有这么个派生类、更不用说取得对派生类内部成员的权限了!
因此:
1 - 派生类完全可看做是一个独立的类!
2 - 基类是静态的,不会访问派生类内部,更不会动态获取派生类的变化!(不过提供一块共享区域倒是可以让基类获得派生类的变化,比如传递引用之类的)
3 - 因为基类是静态的,所以经过继承的派生类是多态的!这也是面向对象的重要特性!
4 - 相反,派生类的记性倒是很好,它能够记得自己的上一代基类是谁(调用构造函数时,对于未定义构造函数的成员会去基类中寻找)
5 - 尽管派生类的记性比总裁好那么一点,但是让它记住自己的爷爷是谁也还是太难了!(不关心基类的基类)
(注意:这里并没有考虑virtual继承的情况,如果是virtual继承,则是共享模式,即base能够获得派生类带来的变化)
三、提供更丰富的函数接口
回到我们的二叉搜索树。1、实现调用构造函数的同时,维护vector<Node*> sortedNode,使之存放排序后的节点
首先内置一个操作vector的函数:void* getAllNode(Node *a) {sortedTree.push_back(a);return NULL;}作为维护vector的函数。
然后,定义我们的inorderBST()函数,使之能够中序遍历。
/* inorder visit BST */ void BST::inorderBST() { if(head==NULL) return; inorderBST(head->left); this->getAllNode(head); inorderBST(head->right); std::cout<<std::endl; } void BST::inorderBST(Node* head) { if(head==NULL) return; inorderBST(head->left); this->getAllNode(head); inorderBST(head->right); }这里重载了inroderBST,原因在2中有分析。
现在,我们的中序遍历已经完成了,把它放在拷贝构造函数中调用,即可保证生成类对象的时候,不仅产生一个二叉搜索树,而且维护一个有序的vector
copy constructor——
BST::BST(const vector<int>&num) { if(num.size()==0){ std::cout<<"Create Tree Failed, NULL Input!\n"; return; } Node *put = new Node(num[0]); //fun = &getAllNode; sortedTree.push_back(put); head = put; for(unsigned int i=1;i<num.size();i++) { put = new Node(num[i]); insertNode(*head,*put); //printNode(put); } inorderBST(); } /* ----- */
2、提供拥有函数接口的inorderBST函数
在框架中我们提到:希望提供中序遍历函数。在应用中,往往需要中序遍历二叉树做各种操作,譬如输出、拷贝、修改……等等。如果为这些行为每一个都写一个中序遍历函数,那么就太麻烦了!因此,我希望用下面的方式,提供一个inorderBST的函数借口:
void inorderBST(void*(*)(Node*,void*),void*);
(想想qsort,一样的使用方式。)
void BST::inorderBST(void*(*)(Node*,void*) fun,void* argument) { if(head==NULL) return; inorderBST(head->left,fun,argument); fun(head,argument); inorderBST(head->right,fun,argument); } void BST::inorderBST(Node* head,<span style="font-family: Arial, Helvetica, sans-serif;">void*(*)(Node*,void*)</span> fun,void* argument) { if(head==NULL) return; inorderBST(head->left,fun,argument); fun(head,argument); inorderBST(head->right,fun,argument); }
上面就是我们对于inorderBST函数的实现。
因为中序遍历中,需要不断移动操作的位置,因此我们在这里重载了inorderBST()函数。
第一个作为public的接口,提供给对象使用。
第二个函数作为内置调用的函数,标记为private。只希望内部调用,而希望对object隐藏。
至此,我们的class变成了这样——
class BST { private: inline void insertNode(Node&,Node&); void inorderBST(Node* head); void _deleteBST(Node* head); //BSTFUN fun; void inorderBST(); void inorderBST(Node*,<span style="font-family: Arial, Helvetica, sans-serif;">void*(*)(Node*,void*)</span>,void*); void* getAllNode(Node *a) {sortedTree.push_back(a);return NULL;} public: Node* head; vector<Node*> sortedTree; void inorderBST(<span style="font-family: Arial, Helvetica, sans-serif;">void*(*)(Node*,void*)</span>,void*); BST():head(NULL){}; BST(const vector<int>&); };
3、演示带有函数参数的inorderBST接口的使用:
void* printfnode(Node* a,void* arg) { std::cout<<a->val<<"=="; return NULL; }
struct get { vector<int> left; }; void* getNodeVal(Node*a, void* arg) { ((get*)arg)->left.push_back(a->val); return NULL; } /* == make sure destructor work == */ int main() { int a[7] = {3,1,8,2,6,7,5}; vector<int> num(a,a+7); BST tree1(num); tree1.inorderBST(printfnode,NULL); get tmp; tree1.inorderBST(getNodeVal,(void*)&tmp); return 0; }
其中,printNode用来打印节点,输出排序后的队列。
getNodeVal则演示了传入一个结构体用来获得参数/传入参数的使用方法。
相关文章推荐
- 从二叉搜索树(Binary Search Tree)入手,学习C++中类的构建 --(四)未完成。占坑。C++下的强制类型转换带来的问题
- 从二叉搜索树(Binary Search Tree)入手,学习C++中类的构建 --(一)BST与基本框架
- 从二叉搜索树(Binary Search Tree)入手,学习C++中类的构建 --(二)class与struct, 构造函数,重载函数
- C++中类的继承方式-public,protected,private
- OC学习中关于@private@protected@pakege@public的访问权限和继承问题
- C++中类的继承方式的区别以及private public protected 范围
- public protected private 成员函数和成员变量在public protected private 继承后访问权限问题
- 封装 关 键字 :public,protected,private 封装相关函数:__set() , __get()
- Java学习笔记(一)----封装 继承 多态 抽象 接口
- OC封装、继承、多态,@ public,@ protected,@private
- C++学习笔记14,private/protected/public继承,私有继承,保护继承,公有继承(五)(总结)
- c++ public, protected, private成员变量,成员函数继承和访问规则实例代码
- C++多态之继承5-继承和访问说明符(public ,protected, private)
- C++学习笔记14,private/protected/public继承,私有继承,保护继承,公有继承(五)(总结)
- public protected private 成员函数和成员变量在public protected private 继承后访问权限问题
- C++学习笔记14,private/protected/public继承,私有继承,保护继承,公有继承(五)(总结)
- 类的private继承,protected继承和public继承的区别
- C++ (public, protected, private继承)
- 封装,继承,多态,接口
- public、private、protected属性与public、private、protected继承