C++ 面向对象编程概述 20131001
一些基本概念:封装、继承、组合、虚函数、抽象基类、动态绑定、多态性等等
1.一个笑话:如果坐在后排聊天的同学能够像中间打牌的同学那样安静的话,那么就不会影响到前排睡觉的同学了。
C++的封装机制控制信心的访问,C++中最根本的改变就是把函数放进结构体中,进而产生了C++的类。类可以把数据和函数绑定在一起,其中数据表示类的属性,函数表示类的行为,也就是成员函数。C++中提供public, protected, private 用于控制属性和函数的访问权限。
2.类的继承特性
子类会继承父类中的所有成员变量和成员函数,只是根据权限,有些是无法访问的。同时继承可以是多重继承的,一个比较重要的用途就是在已有接口和实现类的基础上创建自己的接口和实现类。
3.类的组合特性
组合(Composition)是一种类的符合技术,用于表示累的整合和部分的关系,这种关系不是派生。并不是所有的C++class 都要使用继承,有些类之间的关系是聚合或者引用。
4.动态特性
在绝大多数的情况下,程序的功能是在编译的时候就已经确定下来了,我们称为静态特性。反之,如果程序的功能是在运行的时候才确定下来的话,称之为动态特性。动态特性是面向对象语言最强大的功能之一,因为他是在语言层面上支持程序的可扩展性。C++虚函数、抽象基类、动态绑定和多态 实现C++语言的动态特性。
虚函数:
在基类中使用virtual 关键字修饰函数,然后再派生类中重写该函数,叫做Override,但是虚函数不是实现多态的唯一手段,其他的语言或许会采用其他的方式。
一旦类中的一个函数被声明为虚函数,那么器派生类的对应函数也自动变成虚函数,但是为了提高程序的清晰性,最好在派生类中的函数前面使用virtual关键字修饰。
抽象基类:
有时候有些类是不可以被实例化的,就叫做抽象基类(Abstract Class)能被实例化的类叫做具体类或者实体类(Implementation class)。抽象基类的唯一目的就是让其派生类继承并且实现抽象基类的接口。
如果我们将基类的虚函数声明为纯虚函数,那么该类就被定义为了抽象基类,纯虚函数就是声明的时候初始化为0。因为纯虚函数不知打如何实现,所以必须依靠派生类实现纯虚函数。C++中之纯虚函数才可以被指向0,这样告诉编译器不要为该函数编址,从而阻止该类的实例化行为。抽象基类的主要作用是显现接口和实现的分离:不仅要把数据成员信息隐藏,还要实现完全隐藏,只保留一些接口供外部调用。
动态绑定:
又叫做运行时绑定,动态绑定技术是如何实现的?程序之所以能够在运行的时候选择正确的虚函数,必定隐藏了一段运行时进行对象类型判断或是函数寻址的代码。
每一个具有虚函数的类都叫做多态类,这个虚函数是从基类继承来的或者是自己新增加的。C++编译器必须为每一个多态类至少创建一个虚表(Virtual -Table),其实就是一个函数指针数组,其中存放着该多态类的所有虚函数地址和该类的类型的信息。每一个多态对象都会隐含的包含了一个隐含的指针成员,指向所属类型的虚表,就是vptr。
实际上虚函数的动态绑定并没有使用到这个类型的信息,而是采用的运行时函数寻址技术,该类型的信息主要用在RTTI技术上。
多态数组:不要在数组中直接存放多态对象,而是换之以基类的指针或者是基类的智能指针。
5.C++对象模型
构造函数、拷贝和赋值函数、析构函数、静态成员、虚函数、继承、组合、动态对象的创建、RTTI等等,还有一些结合底层的知识内存映像、vtable的构造、vptr的插入和初始化实际、构造函数和析构函数的自动调用实际、对象的构造和析构次序、临时对象的好粗啊关键和销毁、RTTI底层实现技术。
对象的内存映像:
首先了解对象是如何在内存中布局:
非静态数据成员被存储在每一个对象中作为对象专有的数据成员(用户数据区);静态成员变量被提取出来放在程序中的静态数据区内,为该类的所有对象共享,只有一份(静态数据区);静态和非静态的成员函数都会被提取出来放在程序中的代码段中,并且为该类的所有对象共享,因此每一个成员函数只有一个代码实体(代码段)。
因此构成对象本身的只有数据,任何成员函数都不属于任何一个对象,而非静态成员函数于对象之间的关系是绑定的,绑定的中介是this指针。
增加继承和虚函数的类的对象模型变得复杂:
派生类继承基类的非静态数据成员,并且作为自己对象的专有数据成员;
派生类继承基类中的非静态成员函数,并且可以想自己的成员函数一样访问;
为每一个多态类简历一个虚函数的指针数组vtable,该类中所有的虚函数的地址保存在这个虚函数表中;
多态类的每一个对象中安插了一个指针成员vptr,类型是指向函数指针的指针,他总是指向所属类的vtable,也就是说vptr当前的对象是什么类型,就会指向这个类型的vtable。Vptr是C++中隐含的数据成员;
如果基类中插入vptr,则派生类会继承和重用该vptr;
如果派生类是继承多个基类,那么在每一个继承的分支上都会有一个vptr,编译器也为之生成了多个vtable;
Vptr在派生类对象中的相对位置不会随着继承层次的加深而改变,一般都会放在对象的最前面;
为了支持RTTI技术,为每一多态类创建一个type_info对象,并且把其地址保存在vtable中的一个固定的位置。
虚表是存储在静态存储区域的。
隐含成员:
一个对象的内存映像并不是和代码中的一样,虽然在编程的角度不需要了解这些底层的知识,但这些对于我们编写代码十分有帮助。
一个C++符合类型对象,其可能包含的隐含的成员:若干个vptr,默认的构造函数、默认的拷贝构造函数、析构函数、默认的拷贝赋值函数。
C++中对象模型要充分考虑到对象数据成员的空间效率和访问速度,以优化性能。另外内存空间占用不是简单的把成员加在一起:因为编译器可能安插了额外隐含的数据成员;对于存取效率的考虑,需要考虑增加填补字节使对象的边界能够对其到字长(Word)
C++如何处理成员函数:
程序中的函数保存在程序的代码段,并且是只有一份。对于两个编译单元中的完全相同的static全局函数,编译器会将他们当做不同的函数处理,分别生成可执行的代码。
C++通过Name-Mapping技术实现的吧每一个成员函数都转换成名字唯一的全局函数,并且通过对象、指针或者引用对么一个成员函数的调用语句改写成响应的全局函数的调用函数。
C++中的静态成员:
static生命的声明期限是永久,因此类的静态成员数据和静态成员函数在程序开始的时候创建在程序结束的时候销毁。因此他们不依赖对象的存在而存在,不需要通过对象来访问,本质上就是一个全局变量或者函数。
Class中静态数据成员可以直接在class定义中初始化,但是要清楚,这只是声明并且分给他们一个初始值而已,而且在一个编译单元中把它定义一次。
静态函数设计思想是:如果一个成员函数访问了对象的数据成员,那么给他传入一个this指针是必须的,但是有时候一个成员函数不会访问对象的任何数据成员,因此没有必要传递this指针。这就是后来的C++静态成员函数。
静态成员函数也是需要Name-Mapping处理的并且提取到class之外,不同的是他不需要this指针参数。可以直接使用class name的作用域符直接应用.
追梦的飞飞
20131001 于广州中山大学