C++知识点总结_第1页
C++知识点总结_第2页
C++知识点总结_第3页
C++知识点总结_第4页
C++知识点总结_第5页
已阅读5页,还剩9页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、类和对象初步1. 类的定义 在定义外成员函数的实现2. 类的成员函数之间可以相互调用,类的成员函数也可以重载,也可设默认参数值3. 一般来讲,一个对象占用的内存空间的大小等于其成员变量的体积之和。每个对象都有自己的存储空间(成员变量),但成员函数只有一份 对象名。成员名 指针成员名 引用名.成员名4. private:一个类的私有成员,只能在该类的成员函数中才能访问 public: proteced: 5. class 默认 private struct默认public6. 内联成员函数:成员函数名前加inline 或函数体写在类定义内部的成员函数。执行更快,但会带来额外的内存开销构造函数1.

2、 构造函数全局变量在堆上,系统自动初始化为零。局部变量在栈上,初始值是随机的,需要初始化。2. 构造函数:对对象进行初始化。构造函数执行时对象的内存空间已经分配,构造函数的作用是初始化这片空间。可重载,不写的话有默认构造函数,但是如果编写了构造函数,那默认构造函数不会再执行。是一类特殊的成员函数.不写返回值类型,函数名为类名.3. 对象在生成时一定会调用某个构造函数,一旦生成,不再执行构造函数。4. P183 Ctest pArray3=new Ctest(4),new Ctest(1,2)5. 复制构造函数:其是构造函数的一种,只有一个参数,为本类的引用,防止混淆,构造函数不能以本类的对象作

3、为唯一的参数。 默认复制构造函数。6. 复制构造函数被调用的三种情形:1用一个对象去初始化另一个对象时Complex C1(C2) Complex C2=C1; 2 函数的参数是类A的对象。形参未必等于实参 函数中用对象的引用不会调用复制构造函数 void Function(const Complex &c)3 函数的返回值是类A的对象7. 类型转换构造函数:除复制构造函数外,只有一个参数的构造函数 C=68. 析构函数:在对象消亡时调用,可以定义其做善后工作。是一类特殊的成员函数,一个类有且只有一个构造函数。默认析构函数9. 注意:函数的参数对象以及作为函数返回值的对象,在消亡时也会

4、调用析构函数10. 构造函数 析构函数 变量的生存期: 全局变量在main函数开始执行前初始化.函数调用结束后静态局部对象不消亡.main函数结束时,静态局部对象先消亡,全局对象再消亡11. 静态成员变量和静态成员函数:实质是全局变量和全局函数,被所有的同类共享。生成在对象生成之前.计算体积时不会将静态成员变量算入其中 老师的coeblocks C0 13 静态成员变量必须在类定义外进行声明,声明的同时也可初始化。因为静态成员函数内部不作用于某个对象,所以不能访问非静态成员12. 常量对象和常量成员函数:常量对象一旦初始化后,值再也不能改变。常量对象和普通对象都可调用常量成员函数。通过常量对象

5、调用常量成员函数.常量成员函数内不能调用同类的非常量成员函数,静态成员函数除外。13. 封闭类:包含成员对象的类。在定义封闭类的构造函数时,用初始化列表的方式初始化。封闭类对象生成时,先执行所有成员对象友元和this1. 友元函数:把全局函数和其它类的成员函数声明为友元函数后,可直接在友元函数内部访问该类的私有成员;但不能把其它类的私有成员函数声明为友元。2. 全局函数声明为友元:friend 返回值类型 函数名(参数表); 3. 其它类的成员函数声明为友元:friend 返回值类型 其它类的类名:成员函数名(参数表);4. 友元类:类A将类B声明为自己的友元,则类B中所有的成员函数都可访问类

6、A对象中的私有成员。私有成员函数也可访问.5. 注意:友元关系不能传递。6. this指针:非静态成员函数的实际形参比编写的多一个,就是this指针,指向成员函数作用的对象。通过this指针找到对象所在的地址,继而找到对象非静态成员变量的地址。7. 注意:因为静态成员函数不作用于某个对象,所以在其内部无this指针,不能使用.运算符重载1. 运算符重载:是对已有的运算符赋予多重含义,使不同的运算符作用于不同的类型的数据时导致的不同的行为。 实质是编写运算符作为名称的函数。2. 返回值类型 operator 运算符(形参表)。.。.3. 使用了被重载的运算的表达式,会被编译成对运算符函数的调用,

7、实参是运算符的·操作数,运算的结果是函数的返回值。运算符可多次被重载,可重载为全局函数和成员函数。重载为全局函数时,参数个数等于操作符的个数;重载为成员函数时,参数的个数等于操作数的个数减1。 a-b被重载为a.operator-(b);4. 类名(构造函数参数表)这个写法表示生成一个临时对象.该临时对象没有名字,生存期到包含它的语句执行完为止。5. C+规定 = 只能重载为成员函数。赋值运算符可以连用。6. const char c_str() const return str); 两种错误的情形: char *p=s。c_str(); strcpy(s。c_str(),”tian

8、ggong 1");7. a=b="hello";等价于a.operator=(b。operator=("hello");8. (a=b)=c;等价于(a。operator=(b))。operator=(c);9. 深复制和浅复制:两个对象的指针成员变量指向同一个地方,指向两个不同的地方.10. 将运算符重载为友元函数11. 重载插入运算符和流提取运算符:cout是ostream类的对象。ostream类和cout都是在iostream头文件中声明的。ostream类将"<<"多次重载为成员函数。比如:ostre

9、am ostream::operator<( const char*s) .。 return this;) ostream ostream::operator<(int n) .。. return this;) 12. 返回值*this是cout的引用 cout<<”star war"5;等价于(cout.operator<("star war"))。operator<(5);13. 重载强制类型转换运算符:(类型名)对象 等价于 对象。operator 类型名()14. 重载自增,自减运算符 obj+ CDemo operat

10、or+(int)15. +obj CDemo operator+(int)继承与派生1. 继承与派生的关系:A类派生出B类,B类继承了A。派生类是通过对基类进行扩充和修改后得到的。基类所有成员自动成为派生类成员。在派生类的成员函数中,不能访问基类的私有成员。派生类对象的体积等于基类对象的体积加上自己成员变量的体积. 在派生类对象中,包含着基类对象,且基类对象的存储位置位于新增的成员变量之前.2. 基类的构造函数和析构函数不能被派生类继承,需要自己重新定义。如果基类构造函数没有定义,派生类也可不定义,用缺省。如果基类的构造函数定义,派生类构造函数一定要定义.3. 注意:在基类和派生类中有同名成员

11、,在派生类成员函数或派生类对象中访问同名成员,除非特别之指出,访问的是派生类的成员。以下两种情况访问的是基类的成员:s2.CStudent::PrintInfo(); pCStudent::PrintInfo();4. 复合关系和继承关系:复合关系,有,表现为封闭类,但不一定只是封闭类。即一个类以另一个类的对象作为成员变量。 继承关系 ,是,类B继承类A,满足B所代表的事物也是A所代表的事物5. 类A知道类B:类A的成员变量是类B的指针6. /导出类构造函数和析构函数的构建基类的构造函数和析构函数不能被派生类继承。如果基类没有定义构造函数,派生类也可以不定义构造函数,全都采用缺省的构造函数,此

12、时,派生类新增成员的初始化工作可用其他公有函数来完成。如果基类定义了带有形参表的构造函数,派生类就必须定义构造函数,提供一个将参数传递给基类构造函数的途径,以便保证在基类进行初始化时能获得必需的数据。如果派生类的基类也是派生类,则每个派生类只需负责其直接基类的构造,不负责自己的间接基类的构造。派生类是否要定义析构函数与所属的基类无关,如果派生类对象在撤销时需要做清理善后工作,就需要定义新的析构函数.多态与虚函数1. 多态:派生类对象的地址可以赋值给基类指针。对于通过基类指针调用基类和派生类中都有的同名的,同参数的虚函数的语句,编译时不需要确定执行的是基类的还是派生类的虚函数。当程序运行到该语句

13、时,如果基类指针指向的是一个基类对象,则调用基类的虚函数;如果基类指向的是一个派生类对象,则调用派生类的虚函数.这种机制成为多态;2. 面向对象程序设计语言有封装,继承,多态三种机制,这三种机制能够有效提高程序的可读性,可扩充性和可重用性。3. 多态指同一事物可以完成不同的功能.分为编译时的多态和运行时的多态。前者主要指函数的重载(包括运算符重载),对重载函数的调用。后者主要与继承,虚函数等概念有关。4. 虚函数:声明前面加了virtual关键字的成员函数。virtual 关键字只能在类定义中的成员函数声明处使用,不能在类外编写成员函数体时使用。静态成员函数不能是虚函数。包含虚函数的类成为多态

14、类。5. 多态可以简单地理解成同一条函数调用语句能调用不同的函数。或者对不同的对象发同一消息,使得不同对象有各自不同的行为.6. 注意:多态的语句调用的是哪个类的成员函数,是在运行时才能确定的,编译时不能确定。因此多态的函数调用语句是动态联编的,普通的函数调用语句时静态联编的。7. 通过基类引用实现多态。通过基类的引用调用虚函数的语句是多态的,即通过基类的引用调用基类和派生类中的同名同参表的虚函数时引用的是哪个类的对象,调用该对象的虚函数.8. 多态的实现原理:每一个有虚函数的类,都有一个虚函数表,列出了该类所有虚函数的地址。该类的任何对象都存放着该虚函数表的指针。位于对象存储空间的最前端,在

15、数据成员之前,里面存放着虚函数表的地址。9. 多态的实现过程:根据基类指针所指向的或基类引用所引用的对象中存放的虚函数表的地址,查找要调用的虚函数的地址,调用虚函数。10. 类的成员函数可以相互调用,在成员函数(静态,构造,析构除外)中调用同类的虚函数的语句是多态的。11. 在构造函数和析构函数中调用虚函数不是多态的,即编译时即可确定。如果本类有该函数,则调用本类的,本类没有调用直接基类的,基类没有,调用间接基类的。.。12. 注意:只要在基类中某个函数被声明为虚函数,则在直接和间接派生类中,同名同参数的成员函数即使不写virtual也自动成为虚函数。13. 虚析构函数:如果一个基类指针指向n

16、ew出来派生类的对象,释放该对象时是通过该基类指针来完成的。14. 如果基类的析构函数是虚函数,派生类的析构函数即使不用virtual关键字也自动成为虚函数。15. 注意:!析构函数可以是虚函数,但构造函数不可以!16. 纯虚函数:没有函数体的虚函数;在函数声明后加=0;17. 抽象类:包含纯虚函数的类;不能生成独立的对象,但抽象类可以作为基类,派生出新类.因此,独立的抽象类的对象不存在,但是被包含在派生类对象中的抽象类的对象,是可以存在的(但要实例化,有函数体).18. 可以定义抽象类的指针或引用,让他们指向或引用抽象类的派生类的对象,实现多态。19. 如果一个类从抽象类派生而来,那么当且仅

17、当它对基类中所有纯虚函数都进行覆盖并都写出函数体(也是),它才能成为非抽象类。函数调用也会带来降低效率的问题,因为调用函数实际上将程序执行顺序转移到函数所存放在内存中某个地址,将函数的程序内容执行完后,再返回到转去执行该函数前的地方.这种转移操作要求在转去前要保护现场并记忆执行的地址,转回后先要恢复现场,并按原来保存地址继续执行. 因此,函数调用要有一定的时间和空间方面的开销,于是将影响其效率.特别是对于一些函数体代码不是很大,但又频繁地被调用的函数来讲,解决其效率问题更为重要。引入内联函数实际上就是为了解决这一问题。 优点:可以加快代码的执行速度,当程序中调用内联函数时,该函数直接嵌入到每个

18、调用语句处,每次函数调用时都用相对应的一段代码代替。可见它是以目标代码的增加为代价来换取时间的节省 主要解决功能相对简单、规模不大但使用相当频繁的程序运行效率问题。 使用内联函数时,遵守以下规则: 1. 内联函数体内不能包含任何静态变量,不能使用循环语句、switch;不能递归。 2.内联函数的定义必须出现在第一次被调用之前。 3。如果函数返回类型为void,则不能有return 语句。二:指针 通过指针引用数组元素 int a10; int p; p+是合法的,而a+是错误的。a是数组名,它是数组的首地址,是常量;指向函数的指针变量:存放函数入口地址,指向的是程序代码存储区。 1、函数调用可

19、以通过函数名调用,也可以通过指向函数的指针变量调用。 2、(*p) ( )表示定义一个指向函数的指针变量,在程序中把哪个函数的地址赋给它,它就指向哪一个函数。 3、给函数指针变量赋值时,只需给出函数名而不必给出参数. p = max 4、用函数指针变量调用函数时,只需将(*p)代替函数名,在(*p)之后的括弧中根据需要写实参。 c=(p)(a,b) 5、对指向函数的指针变量不能运算const pointer一个指针涉及到两个变量,一个是指针本身,另一个是指向的变量1.指向常量的指针const放在指针类型前,在程序中不能通过指针来间接修改指针所指向的内存空间的值,但可以改变指向的空间int a

20、= 10;const int b = 20;const int* pa = &a;const int pb = &b;a = 100; / okb = 200; / errorpa = 100; / errorpb = 200; / errorpb = &a; / okpa = &b; / ok2.指针常量const放在”*”和指针名之间,不能改地址能改所指向的值.int b =28;int const pb = b;pb = 100; / okpb+; / error在定义指针常量时,必须将其初始化。3。指向常量的指针常量const在上述两个地方都加。既不允许

21、修改指针值,也不允许修改指针变量所指向的值const int a = 10;const int * const pa = &a;a = 100; / errorpa = 100; / errorpa +; / error三:引用标题引用与指针的区别引用是 C+ 语言引进的概念,在 C 语言中没有.1)、指针是变量,引用是别名 引用是别名,引用本身没有地址. 2)、指针可作数组元素、引用不可 例: int pa5;/指针数组 int a5; int &rea5=a;/不可 3)、可以有空指针,不可有空引用 例: int p=null;/合法 int &re=null;/无

22、意义四:类(class)私有(private)的数据和函数,只允许本类的成员函数访问或调用;保护(protected:)的数据和函数,允许本类和本类派生类的成员函数访问或调用;公有(public:)的数据和函数,允许本类和其它类的函数访问或调用。静态数据成员: 1。类的静态数据成员为该类的所有对象所共享。 2。 必须在类外文件作用域中的某个地方对静态数据成员赋初值(因为构造函数多次被调用,而静态数据成员只初始化1次): <类型> 类名:<静态数据成员 = <初值>Const 成员函数中确保不会修改任何本类对象的数据成员.class constfunprivate:

23、int a;public:void nonconstFunc( )a=18; /okvoid Func( ) consta=18 /errorthis指针:this指针是指向对象的指针,隐含在类的成员函数中,用来指向成员函数所属类的正在被操作对象。this指针可以看作是类自身的一个引用。构造函数的调用顺序 对于构造函数,先执行基类的,再执行对象成员的,最后执行派生类的。 对于析构函数,先执行派生类的,再执行对象成员的,最后执行基类的。导出类构造函数和析构函数的构建基类的构造函数和析构函数不能被派生类继承.如果基类没有定义构造函数,派生类也可以不定义构造函数,全都采用缺省的构造函数,此时,派生类

24、新增成员的初始化工作可用其他公有函数来完成。如果基类定义了带有形参表的构造函数,派生类就必须定义构造函数,提供一个将参数传递给基类构造函数的途径,以便保证在基类进行初始化时能获得必需的数据。如果派生类的基类也是派生类,则每个派生类只需负责其直接基类的构造,不负责自己的间接基类的构造.派生类是否要定义析构函数与所属的基类无关,如果派生类对象在撤销时需要做清理善后工作,就需要定义新的析构函数。五:多重继承解决二义性问题解决方法一:用类名来限定(主要解决方法) 为避免二义性,可在调用时加上基类的名称,如 A::print() 或 B:print() 。解决方法二:同名覆盖在C 中声明一个同名成员函数

25、print(),f()再根据需要调用 A:: print() 或 B: print()解决方法三:使用虚函数面向对象设计的三大机制: 数据封装、继承、多态.继承:研究的是类与类之间的层次关系.多态性:指不同的对象接收到相同的消息时产生不同的响应动作,即对相同的函数名,却执行不同的函数体.函数重载和运算符重载实现类的一种多态性。静态联编和动态联编 联编(binding):是将函数调用与相应的函数体代码彼此关联的过程。 静态联编(static binding):如果联编过程在程序开始运行前的编译阶段完成。 例如:重载函数: void fun(int a,int b) void fun(float

26、x,float y) void fun(char c) 函数名字相同,但各自参数不同,编译器能根据函数参数的类型和个数自动选择相应的函数体编译。动态联编(dynamic binding) 在程序运行时进行的联编方式。 例如:虚函数 C+中的虚函数,由于其函数名、返回值、函数参数完全相同,但函数体不同,因此编译阶段无法确定函数调用与哪个函数体关联,只能由系统在程序运行时确定。六:虚函数虚函数(virtual function)-运行时多态在定义某一基类(或其派生类)时,若将其中的某一函数成员的属性说明为virtual,则称该函数为虚函数。若基类中某函数被说明为虚函数,则意味着其派生类中也要用到与

27、该函数同名、同参数表、同返回类型、但函数体不同。虚函数存在继承环境中。虚函数成员的定义语法:virtual 函数类型函数名(形参表)函数体程序举例 class BaseClasspublic:virtual void show() cout”Base class"endl;/如果不加关键字virtual,运行的结果都是"Base class";class Derived1:public BaseClasspublic:void show() cout<<”Derived class1”<endl;class Derived2:public Bas

28、eClasspublic:void show() cout<<"Derived class2”<endl;int main()BaseClass obj;BaseClass *p;Derived1 obj1;Derived2 obj2;p=obj;p-show();p=obj1;p>show();p=obj2;p-show();return 0;通过虚函数,达到了用基类指针访问派生类对象成员函数的目的,这样,只要声明了基类指针,就可以使不同的派生类对象产生不同的函数调用,实现了程序的运行时多态。运行多态应该使用虚函数,并通过指针、引用或者成员函数调用虚函数纯虚

29、函数和抽象类纯虚函数(pure virtual function): 在基类中声明虚拟函数而不给出具体的定义,把它的定义放在各个导出类中,此种函数为纯虚函数 通过基类指针或引用可以调用所有派生类的虚函数。抽象类:(abstract class) 声明了虚函数的类,基类只用于继承,仅作为一个接口,具体的功能则在派生类中实现。 注意:从抽象类可以派生出具体的或抽象类,但不能从具体类派生出抽象类。虚基类 虚基类的引入 用于有共同基类的场合 声明 以virtual修饰说明基类例:class B1:virtual public B 作用 主要用来解决多继承时可能发生的对同一基类继承多次而产生的二义性问题

30、. 为最远的派生类提供唯一的基类成员,而不重复产生多次拷贝类模板 C+中实现多态的另一种方法是类模板 类模板可以使用户为类定义一种模式,使得类中的一些数据成员和成员函数的参数,以及成员函数的返回值能够娶任意类型。#include <iostream.h>template class T> class TestClass public:T buffer10; /T类型的数据成员buffer数组大小固定为10 (灵活性差!) T getData(int j); /获取T类型buffer(数组)的第j个分量 ;template <class T T TestClassT::g

31、etData(int j) return (buffer+j); ; void main() TestClass<char ClassInstA; /char取代T,从而实例化为一个具体的类 char cArr6="abcde"for(int i=0; i5; i+)ClassInstA.bufferi=cArri;for(i=0; i5; i+) char res=ClassInstA.getData(i);coutres<” ”;coutendl; 程序执行后的显示结果如下:a b c d e2。1 13.2 24。3 35.4 46。5 57。6 既使用类

32、型参数又使用 普通参数的类模板示例include iostream.h#include "string。h”template <class T, int i class TestClass public:T bufferi; /T类型的buffer,其大小随普通形参i的值变化(灵活性大!)T getData(int j); ;template <class T, int i T TestClassT,i::getData(int j) return *(buffer+j); ;TestClass<double, 6 ClassInstF;double fArr6=1

33、2。1, 23.2, 34.3, 45。4, 56。5, 67。6;for(i=0; i6; i+)ClassInstF.bufferi=fArri10;for(i=0; i<6; i+) double res=ClassInstF.getData(i);coutres<" ”;cout<endl;常量指针与指针常量1.指向常量的指针const放在指针类型前,在程序中不能通过指针来间接修改指针所指向的内存空间的值,但可以改变指向的空间int a = 10;const int b = 20;const int pa = a;const int* pb = b;a =

34、100; / okb = 200; / errorpa = 100; / error*pb = 200; / errorpb = &a; / okpa = b; / ok2.指针常量const放在"”和指针名之间,不能改地址能改所指向的值.int b =28;int const pb = b;pb = 100; / okpb+; / error在定义指针常量时,必须将其初始化.3。指向常量的指针常量const在上述两个地方都加。既不允许修改指针值,也不允许修改指针变量所指向的值const int a = 10;const int const pa = a;内存模型描述的是程序

35、中各变量(实例域、静态域和数组元素)之间的关系,以及在实际计算机系统中将变量存储到内存和从内存取出变量这样的低层细节。不同平台间的处理器架构将直接影响内存模型的结构。首先介绍一下C+中有继承关系的类对象内存的布局:在C+中,如果类中有虚函数,那么它就会有一个虚函数表的指针_vfptr,在类对象最开始的内存数据中。之后是类中的成员变量的内存数据。对于子类,最开始的内存数据记录着父类对象的拷贝(包括父类虚函数表指针和成员变量)。之后是子类自己的成员变量数据。对于子类的子类,也是同样的原理。但是无论继承了多少个子类,对象中始终只有一个虚函数表指针.为了探讨C+类对象的内存布局,先来写几个类和函数首先

36、写一个基类:1. class Base 2. 3. public: 4. virtual void f() cout << ”Base:f” << endl; 5. virtual void g() cout << ”Base::g” < endl; 6. virtual void h() cout < "Base::h" << endl; 7. int base; 8. protected: 9. private: 10. ; 然后,我们多种不同的继承情况来研究子类的内存对象结构。1. 无虚函数集继承 1. /子

37、类1,无虚函数重载 2. class Child1 : public Base 3. 4. public: 5. virtual void f1() cout "Child1::f1" < endl; 6. virtual void g1() cout << "Child1::g1” endl; 7. virtual void h1() cout < "Child1:h1" < endl; 8. int child1; 9. protected: 10. private: 11. ; 这个子类Child1没有继承任

38、何一个基类的虚函数,因此它的虚函数表如下图:我们可以看出,子类的虚函数表中,先存放基类的虚函数,在存放子类自己的虚函数。2. 有一个虚函数继承 1. /子类2,有1个虚函数重载 2. class Child2 : public Base 3. 4. public: 5. virtual void f() cout < ”Child2::f" endl; 6. virtual void g2() cout "Child2:g2” < endl; 7. virtual void h2() cout ”Child2::h2” < endl; 8. int chi

39、ld2; 9. protected: 10. private: 11. ; 当子类重载了父类的虚函数,则编译器会将子类虚函数表中对应的父类的虚函数替换成子类的函数。3。 全部虚函数都继承 1. /子类3,全部虚函数重载 2. class Child3 : public Base 3. 4. public: 5. virtual void f() cout < "Child3::f" < endl; 6. virtual void g() cout < "Child3::g” << endl; 7. virtual void h() c

40、out << ”Child3:h” << endl; 8. protected: 9. int x; 10. private: 11. ; 4. 多重继承 多重继承,即类有多个父类,这种情况下的子类的内存结构和单一继承有所不同.我们可以看到,当子类继承了多个父类,那么子类的内存结构是这样的:子类的内存中,顺序5. 菱形继承 6。 单一虚拟继承 虚拟继承的子类的内存结构,和普通继承完全不同。虚拟继承的子类,有单独的虚函数表, 另外也单独保存一份父类的虚函数表,两部分之间用一个四个字节的0x00000000来作为分界.子类的内存中,首先是自己的虚函数表,然后是子类的数据成员

41、,然后是0x0,之后就是父类的虚函数表,之后是父类的数据成员。如果子类没有自己的虚函数,那么子类就不会有虚函数表,但是子类数据和父类数据之间,还是需要0x0来间隔。 因此,在虚拟继承中,子类和父类的数据,是完全间隔的,先存放子类自己的虚函数表和数据,中间以0x分界,最后保存父类的虚函数和数据。如果子类重载了父类的虚函数,那么则将子类内存中父类虚函数表的相应函数替换。7。 菱形虚拟继承结论:(1) 对于基类,如果有虚函数,那么先存放虚函数表指针,然后存放自己的数据成员;如果没有虚函数,那么直接存放数据成员.(2) 对于单一继承的类对象,先存放父类的数据拷贝(包括虚函数表指针),然后是本类的数据。

42、(3) 虚函数表中,先存放父类的虚函数,再存放子类的虚函数(4) 如果重载了父类的某些虚函数,那么新的虚函数将虚函数表中父类的这些虚函数覆盖。(5) 对于多重继承,先存放第一个父类的数据拷贝,在存放第二个父类的数据拷贝,一次类推,最后存放自己的数据成员。其中每一个父类拷贝都包含一个虚函数表指针。如果子类重载了某个父类的某个虚函数,那么该将该父类虚函数表的函数覆盖。另外,子类自己的虚函数,存储于第一个父类的虚函数表后边部分.(6) 当对象的虚函数被调用是,编译器去查询对象的虚函数表,找到该函数,然后调用。到这c+类对象的内存模型就介绍完了,希望对大家有帮助。C+作为一款C语言的升级版本,具有非常

43、强大的功能。它不但能够支持各种程序设计风格,而且还具有C语言的所有功能。我们在这里为大家介绍的是其中一个比较重要的内容,C+内存区域的基本介绍。一。 在c中分为这几个存储区1.栈 由编译器自动分配释放2。堆 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收3.全局区(静态区)全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 程序结束释放4。另外还有一个专门放常量的地方.程序结束释放在函数体中定义的变量通常是在栈上,用malloc, calloc, realloc等分配内存的函数分配得到的就是在堆

44、上。在所有函数体外定义的是全局量,加了static修饰符后不管在哪里都存放在全局区(静态区),在所有函数体外定义的static变量表示在该文件中有效,不能extern到别的文件用,在函数体内定义的static表示只在该函数体内有效。另外,函数中的"adgfdf”这样的字符串存放在常量区。比如:1. int a = 0; /全局初始化区 2. char *p1; /全局未初始化区 3. void main() 4. 5. int b; /栈 6. char s = ”abc”; /栈 7. char p2; /栈 8. char *p3 = "123456”; /123456

45、post。content在常量区,p3在栈上 9. static int c = 0; /全局(静态)初始化区 10. p1 = (char *)malloc(10); /分配得来得10字节的区域在堆区 11. p2 = (char )malloc(20); /分配得来得20字节的区域在堆区 12. strcpy(p1, "123456”); 13. /123456post.content放在常量区,编译器可能会将它与p3所指向的”123456”优化成一块 14. 二。在C+中,内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区1.栈,就是那些由编译器在需要的

46、时候分配,在不需要的时候自动清楚的变量的存储区。里面的变量通常是局部变量、函数参数等.2。堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。3.自由存储区,就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。4。全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C+里面没有这个区分了,他们共同占用同一块内存区.5.常量存储区,这是一块比较特殊的存储区,他们里

47、面存放的是常量,不允许修改(当然,你要通过非正当手段也可以修改)三。 谈谈堆与栈的关系与区别具体地说,现代计算机(串行执行机制),都直接在代码底层支持栈的数据结构。这体现在,有专门的寄存器指向栈所在的地址,有专门的机器指令完成数据入栈出栈的操作。这种机制的特点是效率高,支持的数据有限,一般是整数,指针,浮点数等系统直接支持的数据类型,并不直接支持其他的数据结构。因为栈的这种特点,对栈的使用在程序中是非常频繁的。对子程序的调用就是直接利用栈完成的.机器的call指令里隐含了把返回地址推入栈,然后跳转至子程序地址的操作,而子程序中的ret指令则隐含从堆栈中弹出返回地址并跳转之的操作。C/C+中的自

48、动变量是直接利用栈的例子,这也就是为什么当函数返回时,该函数的自动变量自动失效的原因。和栈不同,堆的数据结构并不是由系统(无论是机器系统还是操作系统)支持的,而是由函数库提供的.基本的malloc/realloc/free 函数维护了一套内部的堆数据结构。当程序使用这些函数去获得新的内存空间时,这套函数首先试图从内部堆中寻找可用的内存空间,如果没有可以使用的内存空间,则试图利用系统调用来动态增加程序数据段的内存大小,新分配得到的空间首先被组织进内部堆中去,然后再以适当的形式返回给调用者.当程序释放分配的内存空间时,这片内存空间被返回内部堆结构中,可能会被适当的处理(比如和其他空闲空间合并成更大

49、的空闲空间),以更适合下一次内存分配申请.这套复杂的分配机制实际上相当于一个内存分配的缓冲池(Cache),使用这套机制有如下若干原因:1。 系统调用可能不支持任意大小的内存分配.有些系统的系统调用只支持固定大小及其倍数的内存请求(按页分配);这样的话对于大量的小内存分类来说会造成浪费.2。 系统调用申请内存可能是代价昂贵的。系统调用可能涉及用户态和核心态的转换。3。 没有管理的内存分配在大量复杂内存的分配释放操作下很容易造成内存碎片。堆和栈的对比从以上知识可知,栈是系统提供的功能,特点是快速高效,缺点是有限制,数据不灵活;而栈是函数库提供的功能,特点是灵活方便,数据适应面广泛,但是效率有一定

50、降低。栈是系统数据结构,对于进程/线程是唯一的;堆是函数库内部数据结构,不一定唯一。不同堆分配的内存无法互相操作.栈空间分静态分配和动态分配两种。静态分配是编译器完成的,比如自动变量(auto)的分配。动态分配由alloca函数完成。栈的动态分配无需释放(是自动的),也就没有释放函数。为可移植的程序起见,栈的动态分配操作是不被鼓励的!堆空间的分配总是动态的,虽然程序结束时所有的数据空间都会被释放回系统,但是精确的申请内存/ 释放内存匹配是良好程序的基本要素。1.碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在

51、这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出,详细的可以>参考数据结构,这里我们就不再一一讨论了。2.生长方向:对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。3.分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配.动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。4.分配

52、效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C+函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多.明确区分堆与栈:在bbs上,堆与栈的区分问题,似乎是一个永恒的话题,由此可见,初学者对此往往是混淆不清的,所以我决

53、定拿他第一个开刀。首先,我们举一个例子:1. void f() 2. 3. int p=new int5; 4. 这条短短的一句话就包含了堆与栈,看到new,我们首先就应该想到,我们分配了一块堆内存,那么指针p呢?他分配的是一块栈内存,所以这句话的意思就是:在栈内存中存放了一个指向一块堆内存的指针p。在程序会先确定在堆中分配内存的大小,然后调用operator new分配内存,然后返回这块内存的首地址,放入栈中,他在VC6下的汇编代码如下:1. 00401028 push 14h 2. 0040102A call operator new (00401060) 3. 0040102F add

54、esp,4 4. 00401032 mov dword ptr ebp-8,eax 5. 00401035 mov eax,dword ptr ebp-8 6. 00401038 mov dword ptr ebp4,eax 这里,我们为了简单并没有释放内存,那么该怎么去释放呢?是delete p么?澳,错了,应该是delete p,这是为了告诉编译器:我删除的是一个数组,VC6就会根据相应的Cookie信息去进行释放内存的工作。好了,我们回到我们的主题:堆和栈究竟有什么区别?主要的区别由以下几点:· 管理方式不同;· 空间大小不同;· 能否产生碎片不同;

55、3; 生长方向不同;· 分配方式不同;· 分配效率不同;管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。空间大小:一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,例如,在VC6下面,默认的栈空间大小是1M(好像是,记不清楚了)。当然,我们可以修改:打开工程,依次操作菜单如下:Project>Setting->Link,在Category 中选中Output,然后在Reserve中设定堆栈的最大值和commit。注意:reserve最小值为4Byte;commit是保留在虚拟内存的页文件里面,它设置的较大会使栈开辟较大的值,可能增加内存的开销和启动时间.堆和栈相比,由于大量new/delete的使用,容易造成大量的内存碎片;由于没有专门的系统支持,效率很低;由于可能引发用户态和核心态的切换,内存的申请,代价变得更加昂贵。所以栈在程序中是应用最广泛的,就算是函数的调用也利用栈去完成,函数调

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论