CPP

1.static对象 

static对象:一旦被创建,就一直存在,直到程序退出。

根据静态对象的位置不同,可以分为两类:non-local static objectlocal static object

具体来说:

local staticobject:指函数中用static修饰符修饰的object

生命期起始时间:在函数第一次调用时构造初始化。

生命期终止时间:程序结束

non-local staticobject:包括全局objects,在名词空间范围的objects,类中用static修饰符修饰的objects

生命期起始时间:main函数调用之前被构造初始化。

生命期终止时间:程序结束

注意:在同一个文件或不同编译单元(不同文件)中,如果存在多个non-local static object,它们都是在主函数调用之前被构造的,但是它们之间的构造顺序时不定的。即对编译器来说,静态成员对象之间的初始化顺序和析构顺序是一个未定义的行为。

因此,不能用某个non-local static object去初始化non-local static object,无论这两个non-local static object在不在同一个编译单元中。

下面给出一个处在两个编译单元(CPP文件)中的non-local static object相互引用的情况:

[cpp]view plaincopyprint?

1.  class FileSystem   

2.  {  

3.  public: …  

4.      std::size_t numDisks() const;  

5.  };  

6.    

7.  extern FileSystem tfs;  

8.    

9.  //另一编译单元  

10.   

11. class Directory   

12. {  

13. public:  

14.     Directory(params);  

15. };  

16.   

17. Directory::Directory(params)   

18. {  

19.     std::size_t disks = tfs.numDisks();//使用另一个编译单元的静态变量  

20. }  

21.   

22. Directory tempDir (params);  

class FileSystem

{

public: …

std::size_t numDisks() const;

};

 

extern FileSystem tfs;

 

//另一编译单元

 

class Directory

{

public:

Directory(params);

};

 

Directory::Directory(params)

{

std::size_t disks =tfs.numDisks();//使用另一个编译单元的静态变量

}

 

Directory tempDir (params);

由于编译器没有定义non-local static object之间的构造顺序,所以有可能类tfs还没有被构造,所以程序可能会报错。

解决方法:用local static对象替换non-local static对象。

C++保证,函数内的local static 对象会在该函数被调用期间,首次遇上该对象定义式时被初始化。

[cpp]view plaincopyprint?

1.  class FileSystem {…};  

2.    

3.  FileSystem& tfs()   

4.  {  

5.      static FileSystem fs;  

6.      return fs;  

7.  }  

8.    

9.  //另一编译单元  

10. class Directory {…};  

11.   

12. Directory::Directory (params)   

13. {  

14.     std::size_t disks = tfs().numDisks();//执行函数tfs时,对象fs肯定会被构造。  

15. };  

16.   

17. Directory& tempDir()   

18. {  

19.     static Directory td;  

20.     return td;  

21. }  

 

 

 

 

 

2.C++面试题

分类:C++面试题2013-06-04 20:48247人阅读评论(0)收藏举报

1、指针的优点和缺点

优点:灵活高效

(1)提高程序的编译效率和执行速度(数组下标往下移时,需要使用乘法和加法,而指针直接使用++即可)

(2)通过指针可使用主调函数和被调函数之间共享变量或数据结构,便于实现双向数据通讯。

(3)可以实现动态的存储分配。

(4)便于表示各种数据结构,如结构体,编写高质量的程序。

缺点:容易出错

(1)可能变成野指针,导致程序崩溃

(2)内存泄露

(3)可读性差

2、指针和引用的定义和区别

(1)指针和引用的定义

1)指针:指针是一个变量,存储一个地址,指向内存的一个存储单元;

2)引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。

(2)指针和引用的区别

<1> 从内存分配上来说:

1)指针是一个实体,而引用仅是个别名,即为指针分配内存,而不为引用分配内存空间;

<2> 从指向的内容来说:

2)引用只能在定义时被初始化一次,之后不可变;指针可变;

3)引用不能为空,指针可以为空;

4)const与指针搭配可以表示指针指向指针指向内容是否可变。const与引用搭配只有一种,即来修饰其内容的可读性。由于引用从一而终,不用修饰其指向。

5)指针可以有多级,但是引用只能是一级(int **p;合法,int &&a是不合法的)

<3> 其他方面

6)"sizeof引用"得到的是所指向的变量(对象)的大小,而"sizeof指针"得到的是指针本身的大小;

7)指针和引用的自增(++)运算意义不一样;

指针和引用在符号表中的形式:程序在编译时分别将指针和引用添加到符号表上。在符号表上记录的是变量名及变量所对应地址。在符号表上,指针变量对应的地址值为指针变量的地址值,而引用对应的地址值是引用对象的地址值。符号表生成后就不会再改,因此指针可以改变指向的对象(指针变量中的值可以改),而引用对象不能改。

3malloc/free new/delete相关的面试题

1):C++有了malloc/free 为什么还要new/delete

1malloc/free只在申请空间时,它们只需要申请空间,无法对空间进行操作。

2而在创建C++的对象时,不仅仅是需要申请空间,还需要自动调用构造函数,以及在对象消亡之前要自动执行析构函数。

因此 C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete

根据上面两点,我们可以知道malloc/free 是不能满足C++的需要的,因此需要new/delete

即在创建对象时,能分配内存空间且初始化内存 + 销毁对象时,能回收空间且对内存进行清理的new/delete

2)为什么malloc/free在申请空间时,它们只能申请空间,无法对空间进行操作?

因为:malloc/free 是库函数,使用它需要头文件,在一定程度上是独立于语言的。编译器在处理库函数时,编译器不需要知道它是做啥的,而仅仅需要对该函数进行编译,并且保证调用函数时的参数和返回值是合法的,并生成相应 call 函数的代码就ok了。编译器不会控制库函数做一些操作,比如调用构造函数和析构函数。因此malloc/free无法满足动态生成对象的要求。

3)为什么new/delete 在申请空间时,它们不仅能申请空间,还能调用构造函数或析构函数对对空间进行操作?

因为:new/delete是运算符,它与+-*/的地位是一样的。编译器看到new/delete时,就知道只要它要做啥操作,并生成对应的代码。

4):malloc/free  new/delete 的相同点和不同点

相同点:它们都可以申请和释放空间。

不同点:

一、new/delete 在申请空间的时候能对空间进行操作,而malloc/free 不能。

1new :分配内存 + 调用类的构造函数 + 初始化  delete:释放内存 + 调用类的析构函数

2malloc:只分配内存,不会进行初始化类成员的工作   free只释放内存,不会调用析构函数

二、new/deleteC++运算符,能重载

1newdelete 是运算符,可以进行重载

2malloc,free是标准库函数,不可以进行重载,但可以覆盖。

三、new delete 更加安全,简单:

1)不用计算类型大小:自动计算要分配存储区的字节数

2)不用强制类型转换:自动返回正确的指针类型(二者返回值不同,一个为void*,一个是某种数据类型指针

四、new可以分配一个对象或对象数组的存储空间,malloc不可以

五、newdelete搭配使用,mallocfree搭配使用:混搭可能出现不可预料的错误

六、new后执行的三个操作:(某面试题目)

1new的类分配内存空间。

2)调用类的构造方法。

3)返回该实例(对象)的内存地址

4、构造函数、析构函数与虚函数的关系

1)为什么构造函数不能是虚函数?

简单点说,构造函数的调用是发生多态的前提(多态有对象指针引发)。

2)为什么在派生类中的析构函数常常为虚析构函数?

简单点说,发生多态时,防止漏掉调用派生类的析构函数。

3)把所有的类的析构函数都设置为虚函数好吗?

简单点说,不好,会造成时间和空间浪费。

具体答案在博客构造函数、析构函数与虚函数的关系中。

5C++,哪些函数不可以被声明为虚函数

总的来说,共有五种,普通函数(非成员函数)、构造函数、内联函数、静态函数、友元函数。

首先说明两点:

1虚函数是为了实现多态,而多态是属于动态联编,在运行时确定调用哪个函数。

2)虚函数调用时,类之间需要有公有继承 +继承关系基类指针引用调用

具体解释

1)普通函数为啥不能是虚函数?

原因:多态是依托于类的,要声明的多态的函数前提必须是虚函数。

2)构造函数为啥不能是虚函数?

原因:多态是依托于类的,多态的使用必须是在类创建以后,而构造函数是用来创建构造函数的,所以不行。

具体的原因:虚表指针的初始化时在构造函数进行的,而虚函数需要放到虚表中。在调用虚函数前,必须首先知道虚表指针,此时矛盾就出来了。

3)内联函数为啥不能是虚函数?

原因:内联函数属于静态联编,即内联函数是在编译期间直接展开,可以减少函数调用的花销,即是编译阶段就确定调用哪个函数了。但是虚函数是属于动态联编,即是在运行时才确定调用哪一个函数。显然这两个是冲突的。

4)静态函数为啥不能使虚函数?

原因:

<1>从技术层面上说,静态函数的调用不需要传递this指针。但是虚函数的调用需要this指针,来找到虚函数表。相互矛盾

<2>从存在的意义上说,静态函数的存在时为了让所有类共享。可以在对象产生之前执行一些操作。与虚函数的作用不是一路的。

5)友元函数为啥不能是虚函数?

原因:C++不支持友元函数的继承,不能继承的函数指定不是虚函数。

6、能做switch()的参数类型是:能自动转换为整形(int)且转换过程中不存在精度损失的类型

具体包括:byte,short,char,int. 但是不包括:long,string,double,float等。

7、堆栈溢出一般是由什么原因导致的?
1)没有回收垃圾资源

2)递归调用的层次太深

8C++中的空类,默认产生哪些类成员函数?

1)默认的构造函数

2)默认的拷贝构造函数

3)默认的赋值函数

4)默认的析构函数

5)默认的取地址运算符(不带const

6)默认的取地址运算符(带const

[cpp]view plaincopyprint?

1.  class Empty  

2.  {  

3.  public:  

4.      Empty();                          // 缺省构造函数  

5.      Empty( const Empty& );            // 拷贝构造函数  

6.      ~Empty();                         // 析构函数  

7.      Empty& operator=( const Empty& ); // 赋值运算符  

8.      Empty* operator&();               // 取址运算符  

9.      const Empty* operator&() const;   // 取址运算符 const   

10. };  

class Empty

{

public:

    Empty();                          // 缺省构造函数

    Empty( const Empty& );            // 拷贝构造函数

    ~Empty();                         // 析构函数

    Empty& operator=( constEmpty& ); // 赋值运算符

    Empty* operator&();               // 取址运算符

    const Empty* operator&()const;   // 取址运算符 const

};

9、面向对象的四个特性:

1)抽象性:是对事物的抽象概括描述,继而将客观事物抽象成类,从而实现了客观世界向计算机世界的转化。

2)封装性:把客观事物封装成抽象的类,并且只让可信的类活对象进行访问,而对其他成员进行信息隐藏。

3)继承性:新产生的类无需重新编写现有类的代码,而直接使用现有类的功能,继而对新类进行扩展。

4)多态性:允许基类指针指向各种各样的派生类对象,之后能够根据其指向的派生类对象,做出不同的反应(该反应为虚函数实现的)。即多态的出现,就是给出一个统一处理各种各样的派生类的方法。比如可以定义一个数组,数组类型为基类指针类型,放的可以是各种派生类,之后可以使用基类指针对数组元素统一处理。

注:有书上说是四个,有书上说是三个,不是很统一。

10、简述strcpymemcpy的相同点和区别点:

相同点:strcpymemcpy都可以实现拷贝的功能

不同点:

1)实现功能不同,strcpy主要实现字符串变量间的拷贝,memcpy主要是内存块间的拷贝。

2)操作对象不同,strcpy的操作对象是字符串,memcpy 的操作对象是内存地址,并不限于何种数据类型。

3)执行效率不同,memcpy最高,strcpy次之。

11、内存分配方式

一个CC++程序编译时内存分为5大存储区:全局区、栈区、堆区、文字常量区、程序代码区。

(1) 在静态存储区域分配

控制者:编译器

分配时间:在程序编译的时候分配内存

释放时间:在程序的整个运行期间都存在,程序结束后由OS释放

内容:全局变量,static变量

特点:

0、速度快,不易出错。

1、初始化的全局变量和静态变量在一块区域,未初始化的全局变量和静态变量在另一块区域

2、定义后,变量的值可以改变

(2) 在栈上创建

控制者:由编译器自动分配释放

分配时间:在程序运行(执行函数)的时候分配内存

释放时间:在函数执行结束时,释放存储单元自动被释放。

举例:局部变量,函数参数

特点:

1、栈内存分配运算内置于处理器的指令集中,分配效率很高,但是分配的内存容量有限。

2、定义后,变量的值可以改变

(3) 从堆上分配

控制者:程序员一般由程序员分配和释放,。

分配时间:在程序运行(遇见newmalloc)的时候分配内存。

释放时间:程序员自己决定,若程序员不释放,程序结束时可能由OS回收,但是程序运行期间不释放的内存属于内存泄露。

举例:使用new malloc申请的空间

特点:

0、频繁地分配和释放不同大小的堆空间将会产生堆内碎块

1、程序员使用malloc new 申请任意多少的内存,自己负责在何时用free delete 释放内存,否则会造成内存泄露。

2、定义后,变量的值可以改变

(4) 文字常量区

控制着:编译器

分配时间:在程序编译的时候分配内存

释放时间:程序结束后由系统释放

举例:常量字符串

特点:定义后,变量的值不可以改变,只读的

(5) 程序代码区

内容:存放函数体的二进制代码

12.堆内存与栈内存的比较

(1)申请方式

栈:由系统分配,

堆:有程序员显式分配,mallocnew

(2)申请大小的限制

栈:在Windows,栈是向低地址扩展的数据结构,是一块连续的内存的区域。栈内存的地址和大小是系统预先规定好的,而且能从栈申请的大小很小,在WINDOWS下,栈的大小是2M,如果申请的空间超过栈的剩余空间时,将提示overflow

堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

(3)申请后系统的响应

栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。

堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

(4)申请效率的比较

栈:由系统自动分配,速度较快。但程序员是无法控制的。
堆:由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。

(5)堆和栈中的存储内容

栈:在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。

堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。
(6)存取效率的比较

栈:存取速度快

堆:存取速度慢

13、如何判断程序中堆和栈增长方向?

栈的生长方式是向下的,向着内存地址减小的方向增长。

堆的生长方式是向上的,向着内存地址增加的方向增长。

怎么判断堆和栈的增长方向呢,其实就是依次申请两个变量,判断两个变量的地址大小。

判断栈的增长方式:

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  using namespace std;  

3.    

4.  void Func(int a)  

5.  {  

6.      int b = 1;  

7.      cout<<"&a = "<<&a<<endl; //0012FE8C   

8.      cout<<"&b = "<<&b<<endl; //0012FE7C   

9.  }  

10.   

11. int main()  

12. {  

13.     int a = 1;  

14.     Func(a);  

15.     system("pause");  

16.     return 1;  

17. }  

#include <iostream>

using namespace std;

 

void Func(int a)

{

int b = 1;

cout<<"&a ="<<&a<<endl; //0012FE8C

cout<<"&b ="<<&b<<endl; //0012FE7C

}

 

int main()

{

int a = 1;

Func(a);

system("pause");

return 1;

}

分析:在函数Func中,变量ab都是局部变量,都是在栈上申请的,而且肯定是先申请变量a的内存,后申请b的内存。

此时可以直接比较ab的地址即可。

根据程序的注释可以知道a的地址大于b的地址,且a先于b申请,即在栈中,先申请的内存地址大,后申请的内存地址小。

从而得出,栈内存的增长方式是由高地址到低地址的。

判断堆的增长方式:

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  using namespace std;  

3.    

4.  void Func(int* a)  

5.  {  

6.      int* b = new int;  

7.      cout<<"a = "<<a<<endl; //00395B80   

8.      cout<<"b = "<<b<<endl; //00395BB0   

9.  }  

10.   

11. int main()  

12. {  

13.     int* a = new int;  

14.     Func(a);  

15.     system("pause");  

16.     return 1;  

17. }  

#include <iostream>

using namespace std;

 

void Func(int* a)

{

int* b = new int;

cout<<"a ="<<a<<endl; //00395B80

cout<<"b ="<<b<<endl; //00395BB0

}

 

int main()

{

int* a = new int;

Func(a);

system("pause");

return 1;

}

分析:在函数Func中,变量ab中存放的都是堆内存,而且肯定是先申请变量a的内存,后申请b的内存。

此时可以直接比较ab的地址即可。

根据程序的注释可以知道a的地址小于b的地址,且a先于b申请,即在堆中,先申请的内存地址小,后申请的内存地址大。

从而得出,堆内存的增长方式是由低地址到高地址的。

13class struct的区别

C++struct进行了扩充,使得struct不仅仅包含变量,还可以包含成员函数、能继承、能重载,能实现多态。

c++中,structclass的区别主要在于: 

1)默认访问权限不同。

struct:变量和函数默认的访问权限是public,而class默认的访问权限为private

2)默认继承的访问权限不同。

struct:继承权限默认为public,而class默认的继承权限为private

3class这个关键字可用于定义模板参数,但关键字struct不可以用于定义模板参数。

14STLset用什么实现的?为什么不用hash

set是由红黑树实现的,红黑树是一个平衡二叉查找树,用来检测某个元素是否在集合中出现。

如果使用hash实现时,需要为不同类型的数据编写哈希函数,而利用红黑树只需要为不同类型重载operator<就可以了。

15、列举面向对象设计的三个基本要素和五种主要设计原则。
1)三个基本要素:继承、封装、多态
2)主要设计原则:单一职责原则、里氏代换原则、依赖倒置原则、接口隔离原则、迪米特原则、开放-封闭原则。

16、分析++itit++的优劣 - 巨人网络2014校招

[cpp]view plaincopyprint?

1.  1for(iterator it = V.begin(); it != V.end(); ++it)    

2.  2for(iterator it = V.begin(); it != V.end(); it++)   

1、for(iterator it = V.begin(); it != V.end(); ++it) 

2、for(iterator it = V.begin(); it != V.end(); it++)

分析:

1)这两个式子的结果肯定是一样的。

2)俩式子效率不同。++it返回的是对象引用,而it++返回的是临时的对象。由于it是用户自定义类型的,编译器无法对其进行优化,即无法直接不生成临时对象,进而等价于++it,所以每进行一次循环,编译器就会创建且销毁一个无用的临时对象。

3)对于int i,i++ ++i release下,二者效率是等价的,因为编译器对其进行优化了。

17、分析下面代码,回答问题

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  using namespace std;  

3.    

4.  class Test  

5.  {  

6.  public:  

7.      void print()  

8.      {  

9.          cout<<"a::print null"<<endl;  

10.     }  

11.     void set(int v)  

12.     {  

13.         m_val = v;  

14.         cout<<"a::set val = %d"<<endl;  

15.     }  

16. private:  

17.     int m_val;  

18. };  

19.   

20. int main()  

21. {  

22.     Test* a = NULL;  

23.     a->print();  //这个函数的调用结果是什么?  

24.     a->set(100); //这个函数的调用结果是什么?  

25.     system("pause");  

26.     return 1;  

27. }  

#include <iostream>

using namespace std;

 

class Test

{

public:

void print()

{

   cout<<"a::printnull"<<endl;

}

void set(int v)

{

   m_val = v;

   cout<<"a::set val =%d"<<endl;

}

private:

int m_val;

};

 

int main()

{

Test* a = NULL;

a->print();  //这个函数的调用结果是什么?

a->set(100); //这个函数的调用结果是什么?

system("pause");

return 1;

}

分析:

a->print():输出a::print null

a->set():没有输出,程序直接崩溃。

原因:

1a只是一个类Test的指针,程序并未为其生成Test类型的内存。但是a仅仅调用print函数,该函数没有对内存进行读写,而且该函数属于类共享的,所以从编译器的角度上,调用也是没问题的。

2a只是一个类Test的指针,程序并未为其生成Test类型的内存,所以m_val是没有内存空间的,因此为其赋值就相当于为null赋值。由于地址为null的位置其实是0x0000,这是一个特殊的内存,只能读不能写,所以当为该内存赋值时,会崩溃。

 

3.内存对齐(成员涉及基本变量和位域,不涉及虚函数、虚基类)

分类:C++2013-05-22 16:01162人阅读评论(0)收藏举报

目录(?)[+]

1.内存对齐的定义

2.内存对齐的原因

3.内存对齐的规则

4.注意事项

转载于百度百科:http://baike.baidu.com/view/4786260.htm

说明:在此文中,类中成员涉及基本变量和位域,不涉及虚函数、虚基类

内存对齐的定义:

内存对齐是编译器的管辖范围,编译器将程序中的每个数据单元安排在适当的位置上。对于大部分程序员来说,内存对齐对他们来说都应该是透明的。但是,C语言允许程序员干预内存对齐,如果你想了解更加底层的秘密,内存对齐对你就不应该再透明了。

内存对齐的原因:

(1) 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

(2) 性能原因数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

内存对齐的规则:

每个编译器都有自己的默认对齐系数。程序员可以通过预编译命令#pragma pack(n)n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的对齐系数。注意,n只能取这几个值,如果n取其它值,系统将无视此对齐系数,而使用系统默认的值。

具体规则:

1数据成员对齐规则:结构体(struct)或联合题(union))的数据成员,第一个数据成员放在offset0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。

具体来说:

(1) 第一个成员的存放地址相对于相对于首地址的偏移地址为0

(2) 其它成员的存放地址是按 min(#pragma pack指定的数值 , 该数据成员自身长度进行对齐,即找到能整除该最小值的最近偏移地址

总结<1>从单个成员上说,存放该变量的首地址 = 能除尽min(#pragma pack指定的数值 , 该数据成员自身长度的最近偏移地址的值。

2结构(或联合)整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。

根据原则2,可以得到结构体或联合体最终的占的内存大小

1)最终的内存大小按min(#pragma pack指定的数值 ,结构(或联合)中最大数据成员长度对齐

2)根据所有成员目前已经占有的空间大小Sum,找到比Sum值大而且能够整除较小值min值的值。

总结<2>从整体上说,该类的大小等于比Sum值大而且能够整除较小值min值的值。

3 结合12可推断:当#pragma packn值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果

即,当#pragma packn值比任何数据成员的长度都大内存对齐可以直接忽略n值。

举例:

[cpp]view plaincopyprint?

1.  #pragma pack(4)  

2.    

3.   class A  

4.   {  

5.   public:  

6.      int a; /* int型长度4 = 4 4对齐;起始offset=0 0%4=0;存放位置区间[0,3] */  

7.      char b; /* char型长度1 < 4 1对齐;起始offset=4 4%1=0;存放位置区间[4] */  

8.      short c;/*short型长度2 < 4 2对齐;起始offset=6 6%2=0;存放位置区间[6,7] */  

9.      char d[6];/* char型长度1 < 4 1对齐;起始offset=8 8%1=0;存放位置区间[8,13] */  

10.  };  

11.   sizeof(A) = 16  

12.   

13.  数据成员对齐:  

14.      1、任一数据成员对齐系数 = min(n,该数据成员长度)  

15.      2、按照对齐系数对齐  

16.    

17.  数据成员对齐举例:  

18.    对于short c;  

19.    对齐系数 = min(4,2) = 2  

20.    初始的偏移地址为 5%2 !=0,得出其偏移地址为6       

21.    

22.  整体对齐  

23.     整体对齐系数 = min(n,所有数据成员的最大长度)  

24.   

25.  数据成员对齐举例:  

26.    整体对齐系数 = min(n,max(int,char,short,char)) = 4;  

27.    对所有数据成员对齐后,结构体占的大小为14个字节,  

28.        14%4!=0,此时需要找到离14最近且能够整除4的数 = 16  

#pragma pack(4)

 

 class A

 {

 public:

   inta; /* int型长度4 = 4 按4对齐;起始offset=00%4=0;存放位置区间[0,3] */

   charb; /* char型长度1 < 4 按1对齐;起始offset=44%1=0;存放位置区间[4] */

   shortc;/*short型长度2 < 4 按2对齐;起始offset=66%2=0;存放位置区间[6,7] */

  char d[6];/* char型长度1 < 4 按1对齐;起始offset=8 8%1=0;存放位置区间[8,13] */

 };

  sizeof(A) = 16

 

 数据成员对齐:

   1、任一数据成员对齐系数 = min(n,该数据成员长度)

   2、按照对齐系数对齐

 

 数据成员对齐举例:

   对于short c;

   对齐系数 = min(4,2) = 2

   初始的偏移地址为5 且 5%2 !=0,得出其偏移地址为6 

 

 整体对齐

  整体对齐系数 = min(n,所有数据成员的最大长度)

 

 数据成员对齐举例:

   整体对齐系数 =min(n,max(int,char,short,char)) = 4;

   对所有数据成员对齐后,结构体占的大小为14个字节,

    而14%4!=0,此时需要找到离14最近且能够整除4的数 = 16

注意事项:

(1) 微软的编译器中,结构体第一个变量的首地址是其最长基本类型成员的整数倍

(2) 微软的编译器中,默认的对齐系数为8,即 #pragam pack(8)

(3) GCC的编译器中,对齐系数最大值只能为4,即使成员中有double变量。n只能取值1,2,4

(4) 成员的对齐顺序是按变量在结构体中声明顺序进行的

(5) 如果在结构体中包含结构体成员变量,则先对结构体变量进行内存对齐,之后在对本结构体进行内存对齐

(6) 结构体总大小是包括填充字节在内的总大小

举例:系统XP,编译器VS2008

[cpp]view plaincopyprint?

1.  sizeof(char) = 1;  

2.  sizeof(short) = 2;  

3.  sizeof(int) = 4;  

4.  sizeof(double) = 8;  

sizeof(char) = 1;

sizeof(short) = 2;

sizeof(int) = 4;

sizeof(double) = 8;

举例 (1)  1字节对齐

[cpp]view plaincopyprint?

1.  #pragma pack(1)  

2.  class A  

3.  {  

4.  public:  

5.      int a;   

6.      char b;   

7.      short c;  

8.      char d[6];  

9.  };  

10. 输出结果:sizeof(A) = 13   

11.   

12.   

13. 分析过程:  

14. 1) 成员数据对齐  

15. #pragma pack(1)  

16. class A   

17. {  

18.     int a; /* int型,长度4 > 1 1对齐;起始offset=0 0%1=0;存放位置区间[0,3] */  

19.     char b; /* char型,长度1 = 1 1对齐;起始offset=4 4%1=0;存放位置区间[4] */  

20.     short c; /* short型,长度2 > 1 1对齐;起始offset=5 5%1=0;存放位置区间[5,6] */  

21.     char d[6]; /* char型,长度1 = 1 1对齐;起始offset=7 7%1=0;存放位置区间[7,12] */  

22. };  

23. #pragma pack()  

24. 成员总大小=13  

25.   

26.   

27. 2) 整体对齐  

28. 整体对齐系数 = min((max(int,short,char), 1) = 1  

29. 整体大小(size)=(成员总大小 (整体对齐系数圆整 = 13 /*13%1=0*/  

#pragma pack(1)

class A

{

public:

int a;

char b;

short c;

char d[6];

};

输出结果:sizeof(A) = 13

 

 

分析过程:

1) 成员数据对齐

#pragma pack(1)

class A

{

int a; /* int型,长度4 > 1 按1对齐;起始offset=0 0%1=0;存放位置区间[0,3] */

char b; /* char型,长度1 = 1 按1对齐;起始offset=4 4%1=0;存放位置区间[4] */

short c; /* short型,长度2 > 1 按1对齐;起始offset=5 5%1=0;存放位置区间[5,6] */

char d[6]; /* char型,长度1 = 1 按1对齐;起始offset=7 7%1=0;存放位置区间[7,12] */

};

#pragma pack()

成员总大小=13

 

 

2) 整体对齐

整体对齐系数 = min((max(int,short,char), 1) = 1

整体大小(size)=(成员总大小) 按 (整体对齐系数) 圆整 = 13 /*13%1=0*/

举例(2) 2字节对齐

[cpp]view plaincopyprint?

1.  #pragma pack(2)  

2.  class A  

3.  {  

4.  public:  

5.      int a;   

6.      char b;   

7.      short c;  

8.      char d[6];  

9.  };  

10. 输出结果:sizeof(A) = 14   

11.   

12. 分析过程:  

13. 1) 成员数据对齐  

14. #pragma pack(2)  

15. class A  

16. {  

17.     int a; /* int型长度4 > 2 2对齐;起始offset=0 0%2=0;存放位置区间[0,3] */  

18.     char b; /* char型长度1 < 2 1对齐;起始offset=4 4%1=0;存放位置区间[4] */  

19.     short c; /* short型长度2 = 2 2对齐;起始offset=6 6%2=0;存放位置区间[6,7] */  

20.     char d[6]; /* char型长度1 < 2 1对齐;起始offset=8 8%1=0;存放位置区间[8,13] */  

21. };  

22. #pragma pack()  

23. 成员总大小=14  

24. 2) 整体对齐  

25. 整体对齐系数 = min((max(int,short,char), 2) = 2  

26. 整体大小(size)=(成员总大小(整体对齐系数圆整 = 14 /* 14%2=0 */  

#pragma pack(2)

class A

{

public:

int a;

char b;

short c;

char d[6];

};

输出结果:sizeof(A) = 14

 

分析过程:

1) 成员数据对齐

#pragma pack(2)

class A

{

int a; /* int型长度4 > 2 按2对齐;起始offset=0 0%2=0;存放位置区间[0,3] */

char b; /* char型长度1 < 2 按1对齐;起始offset=4 4%1=0;存放位置区间[4] */

short c; /* short型长度2 = 2 按2对齐;起始offset=6 6%2=0;存放位置区间[6,7] */

char d[6]; /* char型长度1 < 2 按1对齐;起始offset=8 8%1=0;存放位置区间[8,13] */

};

#pragma pack()

成员总大小=14

2) 整体对齐

整体对齐系数 = min((max(int,short,char), 2) = 2

整体大小(size)=(成员总大小) 按(整体对齐系数) 圆整 = 14 /* 14%2=0 */

举例(3) 4字节对齐

[cpp]view plaincopyprint?

1.  #pragma pack(4)  

2.  class A  

3.  {  

4.  public:  

5.      int a;   

6.      char b;   

7.      short c;  

8.      char d[6];  

9.  };  

10. 输出结果:sizeof(A) = 16   

11.   

12. 分析过程:  

13. 1) 成员数据对齐  

14. #pragma pack(4)  

15. class A  

16. {  

17.     int a; /* int型,长度4 = 4 4对齐;起始offset=0 0%4=0;存放位置区间[0,3] */  

18.     char b; /* char型,长度1 < 4 1对齐;起始offset=4 4%1=0;存放位置区间[4] */  

19.     short c; /*short型, 长度2 < 4 2对齐;起始offset=6 6%2=0;存放位置区间[6,7] */  

20.     char d[6]; /* char型,长度1 < 4 1对齐;起始offset=8 8%1=0;存放位置区间[8,13] */  

21. };  

22. #pragma pack()  

23. 成员总大小=14  

24. 2) 整体对齐  

25. 整体对齐系数 = min((max(int,short,char), 4) = 4  

26. 整体大小(size)=(成员总大小)(整体对齐系数圆整 = 16 /*16%4=0*/  

#pragma pack(4)

class A

{

public:

int a;

char b;

short c;

char d[6];

};

输出结果:sizeof(A) = 16

 

分析过程:

1) 成员数据对齐

#pragma pack(4)

class A

{

int a; /* int型,长度4 = 4 按4对齐;起始offset=0 0%4=0;存放位置区间[0,3] */

char b; /* char型,长度1 < 4 按1对齐;起始offset=4 4%1=0;存放位置区间[4] */

short c; /*short型, 长度2 < 4 按2对齐;起始offset=6 6%2=0;存放位置区间[6,7] */

char d[6]; /* char型,长度1 < 4 按1对齐;起始offset=8 8%1=0;存放位置区间[8,13] */

};

#pragma pack()

成员总大小=14

2) 整体对齐

整体对齐系数 = min((max(int,short,char), 4) = 4

整体大小(size)=(成员总大小)按(整体对齐系数) 圆整 = 16 /*16%4=0*/

举例(4) 8字节对齐

[cpp]view plaincopyprint?

1.  #pragma pack(8)  

2.  class A  

3.  {  

4.  public:  

5.      int a;   

6.      char b;   

7.      short c;  

8.      char d[6];  

9.  };  

10. 输出结果:sizeof(A) = 16   

11.   

12. 分析过程:  

13. 1) 成员数据对齐  

14. #pragma pack(8)  

15. class A  

16. {  

17.     int a; /* int型,长度4 < 8 4对齐;起始offset=0 0%4=0;存放位置区间[0,3] */  

18.     char b; /* char型,长度1 < 8 1对齐;起始offset=4 4%1=0;存放位置区间[4] */  

19.     short c; /* short型,长度2 < 8 2对齐;起始offset=6 6%2=0;存放位置区间[6,7] */  

20.     char d[6]; /* char型,长度1 < 8 1对齐;起始offset=8 8%1=0;存放位置区间[8,13] */  

21. };  

22. #pragma pack()  

23. 成员总大小=14  

24. 2) 整体对齐  

25. 整体对齐系数 = min((max(int,short,char), 8) = 4  

26. 整体大小(size)=(成员总大小 (整体对齐系数圆整 = 16 /*16%4=0*/  

#pragma pack(8)

class A

{

public:

int a;

char b;

short c;

char d[6];

};

输出结果:sizeof(A) = 16

 

分析过程:

1) 成员数据对齐

#pragma pack(8)

class A

{

int a; /* int型,长度4 < 8 按4对齐;起始offset=0 0%4=0;存放位置区间[0,3] */

char b; /* char型,长度1 < 8 按1对齐;起始offset=4 4%1=0;存放位置区间[4] */

short c; /* short型,长度2 < 8 按2对齐;起始offset=6 6%2=0;存放位置区间[6,7] */

char d[6]; /* char型,长度1 < 8 按1对齐;起始offset=8 8%1=0;存放位置区间[8,13] */

};

#pragma pack()

成员总大小=14

2) 整体对齐

整体对齐系数 = min((max(int,short,char), 8) = 4

整体大小(size)=(成员总大小) 按 (整体对齐系数) 圆整 = 16 /*16%4=0*/

举例(5) 16字节对齐

[cpp]view plaincopyprint?

1.  #pragma pack(16)  

2.  class A  

3.  {  

4.  public:  

5.      int a;   

6.      char b;   

7.      short c;  

8.      char d[6];  

9.  };  

10. 输出结果:sizeof(A) = 16  

11.   

12. 分析过程:  

13. 1) 成员数据对齐  

14. #pragma pack(16)  

15. class A  

16. {  

17.     int a; /* int型,长度4 < 16 4对齐;起始offset=0 0%4=0;存放位置区间[0,3] */  

18.     char b; /* char型,长度1 < 16 1对齐;起始offset=4 4%1=0;存放位置区间[4] */  

19.     short c; /* short型,长度2 < 16 2对齐;起始offset=6 6%2=0;存放位置区间[6,7] */  

20.     char d[6]; /* char型,长度1 < 16 1对齐;起始offset=8 8%1=0;存放位置区间[8,13] */  

21. };  

22. #pragma pack()  

23. 成员总大小=14  

24. 2) 整体对齐  

25. 整体对齐系数 = min((max(int,short,char), 16) = 4  

26. 整体大小(size)=(成员总大小 (整体对齐系数圆整 = 16 /*16%4=0*/  

#pragma pack(16)

class A

{

public:

int a;

char b;

short c;

char d[6];

};

输出结果:sizeof(A) = 16

 

分析过程:

1) 成员数据对齐

#pragma pack(16)

class A

{

int a; /* int型,长度4 < 16 按4对齐;起始offset=0 0%4=0;存放位置区间[0,3] */

char b; /* char型,长度1 < 16 按1对齐;起始offset=4 4%1=0;存放位置区间[4] */

short c; /* short型,长度2 < 16 按2对齐;起始offset=6 6%2=0;存放位置区间[6,7] */

char d[6]; /* char型,长度1 < 16 按1对齐;起始offset=8 8%1=0;存放位置区间[8,13] */

};

#pragma pack()

成员总大小=14

2) 整体对齐

整体对齐系数 = min((max(int,short,char), 16) = 4

整体大小(size)=(成员总大小) 按 (整体对齐系数) 圆整 = 16 /*16%4=0*/

说明:

什么是圆整
举例说明:如上面的8字节对齐中的整体对齐,整体大小=9 4 圆整 = 12

圆整的过程:从9开始每次加一,看是否能被4整除,这里91011均不能被4整除,到12时可以,则圆整结束。

位域

如果结构体中含有位域(bit-field),规则如下:

(1) 如果相邻位域字段类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止; 
(2)
如果相邻位域字段类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍; 
(3)
如果相邻的位域字段类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式不同位域字段存放在不同的位域类型字节中),Dev-C++GCC都采取压缩方式:不同位域字段仍存放在相同的位域类型字节

换句话说,当相邻位域字段的类型不同时,VC把不同的位域字段分别找自己的空间存入,即不进行压缩。GCC把不同的位域字段都放入同一个位域类型字节中,从下一个字节开始存储,(注意,可不是把不同类型的位域放入同一个字节中)。

(4) 如果位域字段之间穿插着非位域字段,则不对位域进行压缩 
(5)
结构体的总大小为结构体最宽基本类型成员大小的整数倍,且应尽量节省内存

举例:使用规则3

[cpp]view plaincopyprint?

1.  class A  

2.  {  

3.  public:  

4.      char c:2;  

5.      int i:4;  

6.  };  

7.  如果是规则3中的不压缩方式存储(VC)  

8.  sizeof(A) = 8  

9.     

10. 如果是规则3中的压缩方式存储(gcc)  

11. sizeof(A) = 4  

class A

{

public:

char c:2;

int i:4;

};

如果是规则3中的不压缩方式存储(VC)

sizeof(A) = 8

 

如果是规则3中的压缩方式存储(gcc)

sizeof(A) = 4

分析:

如果是不压缩方式(VC)

成员遍历c放入第0个字节,而成员遍历i的偏移地址应该是4的整数倍,所以c成员后要填充3个字节,然后再开辟4个字节的空间作为int型,其中4位用来存放i,所以上面结构体在VC中所占空间为8个字节;

即,一个字节放c,中间填充三个字节,之后四个字节放i

如果是压缩方式(gcc)

成员遍历c放入第0个字节,而成员遍历i的偏移地址应该是4的整数倍,所以c成员后要填充3个字节。不同的是,如果填充的3个字节能容纳后面成员的位,则压缩到填充字节中,不能容纳,则要单独开辟空间。这里填充的三个字节能够容纳变量i,则在GCC或者Dev- C++中所占空间应该是4个字节。

举例:使用规则四

[cpp]view plaincopyprint?

1.  class A  

2.  {  

3.  public:  

4.      char c:2;  

5.      double d;  

6.      int i:4;  

7.  };  

8.    

9.  VC,采用默认的对齐系数 = 8  

10. sizeof(A) = 24  

11.   

12. gcc,采用默认对齐系数 = 4  

13. sizeof(A) = 16  

class A

{

public:

  char c:2;

  double d;

  int i:4;

};

 

在VC中,采用默认的对齐系数 = 8

sizeof(A) = 24

 

在gcc中,采用默认对齐系数 = 4

sizeof(A) = 16



 

 

相关说明:

1 定义结构体或类的时候,成员最好能从大到小来定义,那样能相对的省空间。

举例:

[cpp]view plaincopyprint?

1.  class A   

2.   {   

3.       double d;   

4.       int i;   

5.       char c;   

6.   };   

class A

 {

   double d;

   int i;

   char c;

 };

vc下或者gcc下,都是大小都是16字节,这里都采用其编译器下默认的对齐系数

 

 

 

 

 

4.C++_转换运算符_static_cast

分类:C++2013-05-19 10:4792人阅读评论(0)收藏举报

目录(?)[+]

1.static_cast的作用

2.static_cast的语法

static_cast的作用:

前提:互换的两种类型必须是相关的类型,而且不针对多态

(1) 在非多态的类层次中,子孙对象指针或引用的互换

(2) 空指针与其他类型指针的呼唤

(3) 用于基本数据类型之间的转换

即,非多态的类层次之间以及基本数据类型之间的转换

注意

(0) static_cast static表示在类型转换时,是在编译时进行的,其与dynamic对应。

(1) static_cast 不能转换掉constvolatile、或者__unaligned属性

(2) static_cast 在编译期间转换,存在编译时的类型检查,检查原类型和目标类型是否能转换,不能则报错。

(3) static_cast  进行的是简单粗暴的转换,没有运行时类型检查来保证转换的安全性。

(4) 在有继承关系的对象之间转换时,尽量不要用static_cast ,而用dynamic_cast

(5)static_cast 作用于存在多态的对象之间时,由子类到父类的转换是安全的,而父类到子类的转换是不安全的。

static_cast的语法:

[cpp]view plaincopyprint?

1.  static_cast<type-id>(expression)  

static_cast<type-id>(expression)

说明:

(1) type_id 目标类型是一样的

(2) type_id  目标类型是引用或指针。

举例:

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  using namespace std;  

3.    

4.  class Base  

5.  {  

6.  public:  

7.      void BaseAAA()  

8.      {  

9.          cout<<"BaseAAA"<<endl;  

10.     }  

11.   

12.     void AAA()  

13.     {  

14.         cout<<"Base::AAA"<<endl;  

15.     }  

16. };  

17.   

18. class Derived : public Base  

19. {  

20. public:  

21.     void DerivedAAA()  

22.     {  

23.         cout<<"DerivedAAA"<<endl;  

24.     }  

25.   

26.     void AAA()  

27.     {  

28.         cout<<"Derived::AAA"<<endl;  

29.     }  

30. };  

31.   

32.   

33. int main()  

34. {  

35.       

36.     Derived* pDerived = new Derived;  

37.   

38.     Base* pBase = static_cast<Base*>(pDerived); //子类到父类的转换  

39.     pBase->AAA();     //Base::AAA   

40.     pBase->BaseAAA(); //BaseAAA   

41.   

42.     pDerived = static_cast<Derived*>(pBase);  //父类到子类的转换  

43.     pDerived->AAA();       //Derived::AAA   

44.     pDerived->BaseAAA();   //BaseAAA   

45.     pDerived->DerivedAAA();//DerivedAAA   

46.   

47.     void* pVoid = static_cast<void*>(pDerived);//空指针与其他类型指针互换  

48.     pDerived = static_cast<Derived*>(pVoid);  

49.     pDerived->AAA();       //Derived::AAA   

50.     pDerived->BaseAAA();   //BaseAAA   

51.     pDerived->DerivedAAA();//DerivedAAA   

52.       

53.     double dNum = 1.256; //用于基本数据类型之间的转换  

54.     int nNum = static_cast<int>(dNum);  

55.     cout<<nNum<<endl; //1   

56.   

57.     system("pause");  

58.     return 1;  

59. }  

#include <iostream>

using namespace std;

 

class Base

{

public:

  void BaseAAA()

  {

   cout<<"BaseAAA"<<endl;

  }

 

  void AAA()

  {

   cout<<"Base::AAA"<<endl;

  }

};

 

class Derived : public Base

{

public:

  void DerivedAAA()

  {

   cout<<"DerivedAAA"<<endl;

  }

 

  void AAA()

  {

   cout<<"Derived::AAA"<<endl;

  }

};

 

 

int main()

{

 

  Derived* pDerived = new Derived;

 

  Base* pBase =static_cast<Base*>(pDerived); //子类到父类的转换

  pBase->AAA();     //Base::AAA

  pBase->BaseAAA(); //BaseAAA

 

  pDerived =static_cast<Derived*>(pBase);  //父类到子类的转换

  pDerived->AAA();       //Derived::AAA

  pDerived->BaseAAA();   //BaseAAA

  pDerived->DerivedAAA();//DerivedAAA

 

  void* pVoid =static_cast<void*>(pDerived);//空指针与其他类型指针互换

  pDerived =static_cast<Derived*>(pVoid);

  pDerived->AAA();       //Derived::AAA

  pDerived->BaseAAA();   //BaseAAA

  pDerived->DerivedAAA();//DerivedAAA

 

  double dNum = 1.256; //用于基本数据类型之间的转换

  int nNum =static_cast<int>(dNum);

  cout<<nNum<<endl; //1

 

  system("pause");

  return 1;

}

 

5.C++_转换运算符_dynamic_cast

分类:C++2013-05-17 21:45163人阅读评论(1)收藏举报

目录(?)[+]

1.dynamic_cast在什么时候使用

2.引入dynamic_cast的原因

3.dynamic_cast的作用

4.注意事项

5.dynamic_cast的语法

6.使用dynamic_cast转换时会执行两个操作

7.dynamic_cast的分类

8.dynamic_cast不能转换的原因

dynamic_cast在什么时候使用:

使用时间:子类和父类之间的多态类型转换

引入dynamic_cast的原因:

举例说明:

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  using namespace std;  

3.    

4.  class Base  

5.  {  

6.  public:  

7.      virtual void BaseAAA()  

8.      {  

9.          cout<<"BaseAAA"<<endl;  

10.     }  

11.   

12.     virtual void AAA()  

13.     {  

14.         cout<<"Base::VirtualAAA"<<endl;  

15.     }  

16. };  

17.   

18. class Derived : public Base  

19. {  

20. public:  

21.     void DerivedAAA()  

22.     {  

23.         cout<<"DerivedAAA"<<endl;  

24.     }  

25.   

26.     void AAA()  

27.     {  

28.         cout<<"Derived::VirtualAAA"<<endl;  

29.     }  

30. };  

31.   

32.   

33. int main()  

34. {  

35.     //为了实现虚函数的调用,让基类指针指向了派生类对象。  

36.     Base* pBaseOne = new Derived;  

37.   

38.     //使用基类指针pBaseOne,可以访问虚函数,实现多态  

39.     pBaseOne->AAA();  //输出Derived::VirtualAAA   

40.       

41.     //但是问题来了,让基类指针指向派生类后,基类指针无法访问派生类的成员了。  

42.     //pBaseOne->DerivedAAA();//操作失败  

43.   

44.     //这时需要对这个基类指针进行强制类型转换,获得派生类对象,之后操作派生类自己的成员  

45.     Derived* pDerived = dynamic_cast<Derived*>(pBaseOne);  

46.     pDerived->DerivedAAA(); //输出DerivedAAA   

47.   

48.     system("pause");  

49.     return 1;  

50. }  

#include <iostream>

using namespace std;

 

class Base

{

public:

virtual void BaseAAA()

{

cout<<"BaseAAA"<<endl;

}

 

virtual void AAA()

{

   cout<<"Base::VirtualAAA"<<endl;

}

};

 

class Derived : public Base

{

public:

void DerivedAAA()

{

   cout<<"DerivedAAA"<<endl;

}

 

void AAA()

{

   cout<<"Derived::VirtualAAA"<<endl;

}

};

 

 

int main()

{

//为了实现虚函数的调用,让基类指针指向了派生类对象。

Base* pBaseOne = new Derived;

 

//使用基类指针pBaseOne,可以访问虚函数,实现多态

pBaseOne->AAA();  //输出Derived::VirtualAAA

//但是问题来了,让基类指针指向派生类后,基类指针无法访问派生类的成员了。

//pBaseOne->DerivedAAA();//操作失败

 

//这时需要对这个基类指针进行强制类型转换,获得派生类对象,之后操作派生类自己的成员

Derived* pDerived =dynamic_cast<Derived*>(pBaseOne);

pDerived->DerivedAAA(); //输出DerivedAAA

 

system("pause");

return 1;

}

具体来说:

为了实现多态,让基类指针指向了派生类对象,但是这个基类指针无法操作派生类自己的成员了,所以就想把这个指针由基类指针转换为派生类指针,此时就可以使用dynamic_cast即,已经有了派生类指针转换为基类指针。之后,使用dynamic_cast,把基类指针转换派生类指针。

dynamic_cast的作用:

作用:将基类类型的指针或引用安全地转换为派生类型的指针或引用。

注意事项:

0)在dynamic_cast中,dynamic表示在类型转换时,是在运行时进行的,其与Static对应。

1)使用dynamic_cast转换时,在expression中必有虚函数,否则编译会出错。而Static_cast没有这个限制。但是即使原来的参数和目标参数之间没有继承关系时,编译器不会报错。

原因:在类中存在虚函数时,才会有可能出现让基类指针或引用指向派生类对象的情况(虚函数使用的前提),此时转换才有意义。或者说,dynamic_cast操作时专门针对由虚函数的继承来的,它将基类指针转换为想要的子类指针,以做好操作子类中有而父类中没有的操作。由于,判断类是否有继承关系,是需要检测是否有虚函数,即在原来的参数和目标参数之间没有继承关系时,编译器不会报错,因为它由虚函数。

dynamic_cast的语法:

[cpp]view plaincopyprint?

1.  dynamic_cast<type-id>(expression)  

dynamic_cast<type-id>(expression)

说明:

(1) type_id 目标类型是一样的

(2) type_id 目标类型是引用或指针。

举例:

[cpp]view plaincopyprint?

1.  Derived* pDerived = dynamic_cast<Derived*>(pBaseOne);//成功,返回Derived的指针,失败返回NULL   

2.  Base& BaseClass = dynamic_cast<Base&>(DerivedClass); //成功,返回Base的引用,失败抛出bad_cast异常  

Derived* pDerived = dynamic_cast<Derived*>(pBaseOne);//成功,返回Derived的指针,失败返回NULL

Base& BaseClass = dynamic_cast<Base&>(DerivedClass); //成功,返回Base的引用,失败抛出bad_cast异常

使用dynamic_cast转换时,会执行两个操作:

(1)它首先验证被请求的转换是否有效,这是在编译时判断,检测expression代表的类中是否有虚函数。

(2)对操作数进行类型转换。这是在运行是判断。

dynamic_cast的分类:

dynamic_cast要求要转换的两个值是有关系的,如果转型成功,则返回转换过的指针,如果转换不成功,返回NULL

根据类之间的关系,可以分三种情况:

(1)上行转换:一个子类对象的指针/引用转换为基类的指针/引用(子类到基类的转换)

(2)下行转换:基类的指针/引用转换为一个子类的指针/引用(基类到子类的转换)

(3)交叉转换:多个基类,不同子类之间的相互转换

下面分情况说明这三种情况:

(1)上行转换:一个子类对象的指针/引用转换为基类的指针/引用(子类到基类的转换)

说明:这里dynamic_cast和static_cast以及直接赋值的效果是一样的。

举例:

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  using namespace std;  

3.    

4.  class Base  

5.  {  

6.  public:  

7.      virtual void BaseAAA()  

8.      {  

9.          cout<<"BaseAAA"<<endl;  

10.     }  

11.   

12.     virtual void AAA()  

13.     {  

14.         cout<<"BaseAAA::VirtualAAA"<<endl;  

15.     }  

16. };  

17.   

18. class Derived : public Base  

19. {  

20. public:  

21.     virtual void DerivedAAA()  

22.     {  

23.         cout<<"DerivedAAA"<<endl;  

24.     }  

25.   

26.     virtual void AAA()  

27.     {  

28.         cout<<"Derived::VirtualAAA"<<endl;  

29.     }  

30. };  

31.   

32.   

33.   

34. int main()  

35. {  

36.     Derived* pD = new Derived;  

37.       

38.     //正常情况-dynamic_cast   

39.     Base* pB = dynamic_cast<Derived*>(pD);  

40.     pB->BaseAAA(); //输出BaseAAA   

41.     pB->AAA(); //输出Derived::VirtualAAA   

42.   

43.     //正常情况-static_cast   

44.     Base* pBB = static_cast<Derived*>(pD);  

45.     pBB->BaseAAA(); //输出BaseAAA   

46.     pBB->AAA(); //输出Derived::VirtualAAA   

47.   

48.     //不要使用转换  

49.     Base* pBBB = pD;  

50.     pBBB->BaseAAA(); //输出BaseAAA   

51.     pBBB->AAA(); //输出Derived::VirtualAAA   

52.   

53.     system("pause");  

54.     return 1;  

55. }  

#include <iostream>

using namespace std;

 

class Base

{

public:

virtual void BaseAAA()

{

  cout<<"BaseAAA"<<endl;

}

 

virtual void AAA()

{

  cout<<"BaseAAA::VirtualAAA"<<endl;

}

};

 

class Derived : public Base

{

public:

virtual void DerivedAAA()

{

  cout<<"DerivedAAA"<<endl;

}

 

virtual void AAA()

{

  cout<<"Derived::VirtualAAA"<<endl;

}

};

 

 

 

int main()

{

Derived* pD = new Derived;

//正常情况-dynamic_cast

Base* pB =dynamic_cast<Derived*>(pD);

pB->BaseAAA(); //输出BaseAAA

pB->AAA(); //输出Derived::VirtualAAA

 

//正常情况-static_cast

Base* pBB =static_cast<Derived*>(pD);

pBB->BaseAAA(); //输出BaseAAA

pBB->AAA(); //输出Derived::VirtualAAA

 

//不要使用转换

Base* pBBB = pD;

pBBB->BaseAAA(); //输出BaseAAA

pBBB->AAA(); //输出Derived::VirtualAAA

 

system("pause");

return 1;

}

(2)下行转换:基类的指针/引用转换为一个子类的指针/引用(基类到子类的转换)

说明:这是dynamic_cast的特长。比static_cast安全。

原因:dynamic_cast能够在运行是对类型进行检查,如果绑定到引用或指针的对象不是目标类型的对象,则dynamic_cast 失败。

dynamic_cast在什么时候能成功?

如果基类指针或引用本来原来是指向或引用一个派生类的情况下,会成功。

失败后的行为:

使用dynamic_cast转换:

如果,基类指针或引用的值在转换前就是指向或引用派生类的情况下,则在转换后,派生类指针或引用就重新获得了一个派生类指针或引用。

如果,基类指针或引用的值在转换前就是指向或引用基类的情况下,在语句执行时,会立即出现错误,并返回不同的状态。

dynamic_cast失败后的状态:

如果,待转换的参数为指针类型,则返回NULL

如果,待转换的参数为引用类型,则抛出一个bad_cast 类型的异常。

使用static_cast转换:

如果,基类指针或引用的值在转换前就是指向或引用派生类的情况下,则在转换后,派生类指针或引用就重新获得了一个派生类指针或引用。

如果,基类指针或引用的值在转换前就是指向或引用基类的情况下,在语句执行时,不会立即出现错误,而是会在使用的时候才会出现错误。

static_cast失败的情况:

如果,使用转换后的指针或引用调用了派生类的成员,则会报错。

如果,仍然调用基类(自己)的成员,则不会报错。即会隐含错误。

举例:

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  using namespace std;  

3.    

4.  class Base  

5.  {  

6.  public:  

7.      virtual void BaseAAA()  

8.      {  

9.          cout<<"BaseAAA"<<endl;  

10.     }  

11.   

12.     virtual void AAA()  

13.     {  

14.         cout<<"BaseAAA::VirtualAAA"<<endl;  

15.     }  

16. };  

17.   

18. class Derived : public Base  

19. {  

20. public:  

21.     virtual void DerivedAAA()  

22.     {  

23.         cout<<"DerivedAAA"<<endl;  

24.     }  

25.   

26.     virtual void AAA()  

27.     {  

28.         cout<<"Derived::VirtualAAA"<<endl;  

29.     }  

30. };  

31. int main()  

32. {  

33.     Derived* pD = NULL;  

34.     Base* pB = new Derived;  

35.   

36.     //正常情况-dynamic_cast   

37.     pD = dynamic_cast<Derived*>(pB);  

38.     pD->DerivedAAA(); //输出DerivedAAA   

39.     pD->AAA(); //输出Derived::VirtualAAA   

40.   

41.     //正常情况-static_cast   

42.     Derived* pD1 = static_cast<Derived*>(pB);  

43.     pD1->DerivedAAA(); //输出DerivedAAA   

44.     pD1->AAA(); //输出Derived::VirtualAAA   

45.   

46.       

47.     Base* pBB = new Base;  

48.       

49.     //失败情况  

50.     Derived* pDD = dynamic_cast<Derived*>(pBB);//此时pDD返回NULL   

51.     //pDD->DerivedAAA(); //出错  

52.     //pDD->AAA(); //出错  

53.   

54.     //失败情况-static_cast   

55.     Derived* pDD1 = static_cast<Derived*>(pBB);  

56.     //pDD1->DerivedAAA(); //出错-用到不是自己的成员时,且该成员是虚函数时才报错。  

57.     pDD1->AAA(); //输出Base::VirtualAAA    

58.   

59.     system("pause");  

60.     return 1;  

61. }  

#include <iostream>

using namespace std;

 

class Base

{

public:

virtual void BaseAAA()

{

  cout<<"BaseAAA"<<endl;

}

 

virtual void AAA()

{

  cout<<"BaseAAA::VirtualAAA"<<endl;

}

};

 

class Derived : public Base

{

public:

virtual void DerivedAAA()

{

  cout<<"DerivedAAA"<<endl;

}

 

virtual void AAA()

{

  cout<<"Derived::VirtualAAA"<<endl;

}

};

int main()

{

Derived* pD = NULL;

Base* pB = new Derived;

 

//正常情况-dynamic_cast

pD =dynamic_cast<Derived*>(pB);

pD->DerivedAAA(); //输出DerivedAAA

pD->AAA(); //输出Derived::VirtualAAA

 

//正常情况-static_cast

Derived* pD1 =static_cast<Derived*>(pB);

pD1->DerivedAAA(); //输出DerivedAAA

pD1->AAA(); //输出Derived::VirtualAAA

 

Base* pBB = new Base;

//失败情况

Derived* pDD =dynamic_cast<Derived*>(pBB);//此时pDD返回NULL

//pDD->DerivedAAA(); //出错

//pDD->AAA(); //出错

 

//失败情况-static_cast

Derived* pDD1 =static_cast<Derived*>(pBB);

//pDD1->DerivedAAA(); //出错-用到不是自己的成员时,且该成员是虚函数时才报错。

pDD1->AAA(); //输出Base::VirtualAAA

 

system("pause");

return 1;

}

B要有虚函数,否则会编译出错;static_cast则没有这个限制。

B中需要检测有虚函数的原因:类中存在虚函数,就说明它有想要让基类指针或引用指向派生类对象的情况,此时转换才有意义。
这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表(关于虚函数表的概念,详细可见<Inside c++ object model>)中,只有定义了虚函数的类才有虚函数表,没有定义虚函数的类是没有虚函数表的。

3)交叉转换:多个基类,不同子类之间的相互转换

说明:dynamic_cast可以做,但是static_cast不可以做,编译都通不过。

举例:

dynamic_cast在什么时候能成功转换?

如果一个派生类DerivedTwo是由两个基类BaseOneBaseTwo派生。

而且把DerivedTwo对象的指针给了其中一个基类BaseOne,那么可以对BaseOne的指针进行dynamic_cast转换,转换为BaseTwo的指针。

[cpp]view plaincopyprint?

1.  BaseOne* pBaseOne = new DerivedTwo;  

2.  BaseTwo* pBaseTwo = dynamic_cast<BaseTwo*>(pBaseOne);  

3.  pBaseTwo->AAA();  //正确执行  

4.  pBaseTwo->BaseTwoAAA(); //正确执行  

BaseOne* pBaseOne = new DerivedTwo;

BaseTwo* pBaseTwo = dynamic_cast<BaseTwo*>(pBaseOne);

pBaseTwo->AAA();  //正确执行

pBaseTwo->BaseTwoAAA(); //正确执行

为什么dynamic_cast能成功转换?

这是由于在定义指针pBaseOne的指向时,其指向的内容本来就包含两个基类的内容,所以可以转换成功。

给出不成功的情况:

[cpp]view plaincopyprint?

1.  BaseOne* pBaseOne = new DerivedOne;  

2.  BaseTwo* pBaseTwo = dynamic_cast<BaseTwo*>(pBaseOne);  

3.  pBaseTwo->AAA();//报错  

4.  pBaseTwo->BaseTwoAAA();//报错  

BaseOne* pBaseOne = new DerivedOne;

BaseTwo* pBaseTwo = dynamic_cast<BaseTwo*>(pBaseOne);

pBaseTwo->AAA();//报错

pBaseTwo->BaseTwoAAA();//报错

dynamic_cast不能转换的原因?

这是由于在定义指针pBaseOne的指向时,其指向的内容只包含一个基类BaseOne的内容,不含有BaseTwo的内容,所以不能成功,此时pBaseTwo的值为NULL

举例:

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  using namespace std;  

3.    

4.  class BaseOne  

5.  {  

6.  public:  

7.      virtual void BaseOneAAA()  

8.      {  

9.          cout<<"BaseOneAAA"<<endl;  

10.     }  

11.   

12.     virtual void AAA()  

13.     {  

14.         cout<<"BaseOne::VirtualAAA"<<endl;  

15.     }  

16. };  

17.   

18. class BaseTwo  

19. {  

20. public:  

21.     virtual void BaseTwoAAA()  

22.     {  

23.         cout<<"BaseTwoAAA"<<endl;  

24.     }  

25.   

26.     virtual void AAA()  

27.     {  

28.         cout<<"BaseTwo::VirtualAAA"<<endl;  

29.     }  

30. };  

31.   

32. class DerivedOne : public BaseOne  

33. {  

34. public:  

35.     virtual void DerivedOneAAA()  

36.     {  

37.         cout<<"DerivedOneAAA"<<endl;  

38.     }  

39.   

40.     void AAA()  

41.     {  

42.         cout<<"DerivedOne::VirtualAAA"<<endl;  

43.     }  

44. };  

45.   

46. class DerivedTwo : public BaseOne,public BaseTwo  

47. {  

48. public:  

49.     virtual void DerivedTwoAAA()  

50.     {  

51.         cout<<"DerivedTwoAAA"<<endl;  

52.     }  

53.   

54.     void AAA()  

55.     {  

56.         cout<<"DerivedTwo::VirtualAAA"<<endl;  

57.     }  

58. };  

59. int main()  

60. {  

61.     BaseOne* pBaseOne = NULL;  

62.     BaseTwo* pBaseTwo = NULL;  

63.   

64.     //dynamic_cast转换成功的情况  

65.     pBaseOne = new DerivedTwo;  

66.     pBaseTwo = dynamic_cast<BaseTwo*>(pBaseOne);  

67.     pBaseTwo->AAA();  //输出DerivedTwo::VirtualAAA   

68.     pBaseTwo->BaseTwoAAA(); //输出BaseTwoAAA   

69.   

70.     //dynamic_cast转换失败的情况  

71.     pBaseOne = new DerivedOne;  

72.     pBaseTwo = dynamic_cast<BaseTwo*>(pBaseOne);  

73.     pBaseTwo->AAA();  

74.     pBaseTwo->BaseTwoAAA();  

75.   

76.     static_cast转换失败的情况  

77.     直接是编译不通过:static_cast无法实现BaseOne* BaseTwo* 的转换。  

78.   

79.     //pBaseOne = new DerivedOne;   

80.     //pBaseTwo = static_cast<BaseTwo*>(pBaseOne);   

81.     //pBaseTwo->AAA();   

82.     //pBaseTwo->BaseTwoAAA();   

83.   

84.     //pBaseOne = new DerivedTwo;   

85.     //pBaseTwo = static_cast<BaseTwo*>(pBaseOne);   

86.     //pBaseTwo->AAA();    

87.     //pBaseTwo->BaseTwoAAA();    

88.   

89.   

90.     system("pause");  

91.     return 1;  

 

 

 

 

6.C++_转换运算符_reinterpret_cast

分类:C++2013-05-17 17:32120人阅读评论(0)收藏举报

目录(?)[+]

1.reinterpret_cast作用

2.reinterpret_cast语法

3.说明

4.注意

5.参考

reinterpret_cast作用:

对数据类型重新解释,用来处理无关类型的相互转换,属于强制类型转换。

reinterpret_cast语法:

[cpp]view plaincopyprint?

1.  reinterpret_cast <typeid>(expression)  

reinterpret_cast <typeid>(expression)

说明:

(1)它返回一个新值,该值的类型与expressoin的类型不同,但两个值的二进制位完全相同

举例一:说明该值的类型与expressoin的类型不同,但两个值的二进制位完全相同

[cpp]view plaincopyprint?

1.  #include <iostream>   

2.  using namespace std;  

3.    

4.  void main()   

5.  {   

6.      int i = 875770417;   

7.      cout<<i<<" "<<endl;  

8.      char* p = reinterpret_cast<char*>(&i);  

9.      for(int j=0; j<4; j++)  

10.         cout<<p[j]<<" ";  

11.     cout<<endl;  

12. }  

#include <iostream>

using namespace std;

 

void main()

{

int i = 875770417;

cout<<i<<""<<endl;

char* p =reinterpret_cast<char*>(&i);

for(int j=0; j<4; j++)

   cout<<p[j]<<"";

cout<<endl;

}

输出

说明:875770417当做整形数据存储在内存中时,在内存中的数据表示从低位到高位依次是0x31 0x32 0x33 0x34。当其当做整形一起输出时,就是875770417。当把该值直接强制转换为char*时,只是将i的首地址(原来为int*类型)强制变成了char*类型,它们所指向的地址还是一样的,此时输出四个字节的值时,是按照字符识别输出的,既输出 1 2 3 4

(2) reinterpret_cast使用时有很大的风险,慎用之。

举例二:

[cpp]view plaincopyprint?

1.  int *ip;  

2.  char *pc = reinterpret_cast<char*>(ip);  

int *ip;

char *pc = reinterpret_cast<char*>(ip);

说明:程序员必须永远记得 pc 所指向的真实对象其实是 int 型,而并非字符数组。任何假设 pc 是普通字符指针的应用,都有可能带来有趣的运行时错误。问题源于类型已经改变时编译器没有提供任何警告或错误提示。当我们用int 型地址初始化pc 时,由于显式地声明了这样的转换是正确的,因此编译器不提供任何错误或警告信息。后面对 pc 的使用都假设它存放的是char* 型对象的地址,编译器确实无法知道pc 实际上是指向 int 型对象的指针。因此用 pc 初始化str 是完全正确的——虽然实际上是无意义的或是错误的。查找这类问题的原因相当困难,特别是如果ip  pc 的强制转换和使用pc 初始化 string 对象这两个应用发生在不同文件中的时候。

注意:

(1) reinterpret _cast不能转换掉expressionconstvolitale或者_unaligned属性

(2) reinterpret_cast是为了映射到一个完全不同类型的意思,这个关键词在我们需要把类型映射回原有类型时用到它。我们映射到的类型仅仅是为了故弄玄虚和其他目的,这是所有映射中最危险的,即不建议使用。

参考:

http://blog.csdn.net/coding_hello/article/details/2211466

http://www.cnblogs.com/ider/archive/2011/07/30/cpp_cast_operator_part3.html

http://baike.baidu.com/view/1263731.htm

7.C++_转换运算符_const_cast

分类:C++2013-05-17 11:3078人阅读评论(0)收藏举报

目录(?)[+]

1.const_cast作用

2.const_cast的用途

3.const_cast的语法

4.注意

const_cast作用:

作用:去除变量的constvolatile属性

说明,由于不了解volatile,这里不讲关于volatile的知识。

const_cast的用途:

用途:有时候需要把常量的值赋值给普通变量。

[cpp]view plaincopyprint?

1.  const char* max(const char* s1,const char* s2)  

2.  {  

3.      return strcmp(s1,s2) > 0 ? s1 : s2;  

4.  }  

5.    

6.  char* p = max("AAA","BBB");//错误  

const char* max(const char* s1,const char* s2)

{

return strcmp(s1,s2) > 0 ? s1 :s2;

}

 

char* p = max("AAA","BBB");//错误

说明:由于防止函数max修改字符串s1 s2的内容,就在形参中加入const。在函数返回值时,由于指针p为非const指针,是不允许接受const值的。这就要求取出返回值的const限制,为p赋值。

const_cast的语法:

[cpp]view plaincopyprint?

1.  const_cast<type_id> (expression)  

const_cast<type_id> (expression)

说明:

(1) type_id expression的类型是一样的

(2)  type_id expression应该是引用或指针。

举例:

[cpp]view plaincopyprint?

1.  const int constNum = 0;  

2.  int& Num = const_cast<int&>(constNum);  

3.  int* pNum = const_cast<int*>(&constNum);  

const int constNum = 0;

int& Num = const_cast<int&>(constNum);

int* pNum = const_cast<int*>(&constNum);

编译器处理方式:

先举个例子:

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  using namespace std;  

3.    

4.  int main(int argc, char* argv[])  

5.  {  

6.      const int constNum = 0;  

7.      int& Num = const_cast<int&>(constNum);  

8.    

9.      cout<<"两个变量的地址:"<<endl;  

10.     cout<<"&constNum = "<<&constNum<<endl;  

11.     cout<<"&Num = "<<&Num<<endl<<endl;  

12.   

13.     cout<<"原来的值:"<<endl;  

14.     cout<<"constNum = "<<constNum<<endl;  

15.     cout<<"Num = "<<Num<<endl<<endl;  

16.   

17.   

18.     Num = 10;  

19.     cout<<"现在的值:"<<endl;  

20.     cout<<"constNum = "<<constNum<<endl;  

21.     cout<<"Num = "<<Num<<endl;  

22.   

23.     system("pause");  

24. }  

#include <iostream>

using namespace std;

 

int main(int argc, char* argv[])

{

const int constNum = 0;

int& Num =const_cast<int&>(constNum);

 

cout<<"两个变量的地址:"<<endl;

cout<<"&constNum ="<<&constNum<<endl;

cout<<"&Num ="<<&Num<<endl<<endl;

 

cout<<"原来的值:"<<endl;

cout<<"constNum ="<<constNum<<endl;

cout<<"Num ="<<Num<<endl<<endl;

 

 

Num = 10;

cout<<"现在的值:"<<endl;

cout<<"constNum ="<<constNum<<endl;

cout<<"Num ="<<Num<<endl;

 

system("pause");

}

输出:

问题:在执行 const_cast 转换后,发现变量Num 的地址和constNum的地址是一样的,但对Num赋值后,也就是对这个地址赋值后,为什么输出的Num的值和constNum的值为什么不同,Num的值为新值,而ConstNum的值仍然是老值?

分析:这是属于常数折叠或者常量替换

1)这是由于编译器一般不会为 const常量或者表达式分配内存空间的,而是在编译阶段求出常量表达式的值或者直接把常量的值放入一个符号表中,当const常量或常量表达式在程序中使用时,编译器会在编译阶段把该const常量替换为具体值,和C中的宏define类似。所以在执行cout<<constNum时,constNum的值一直从符号表中取出,而且在预编译后变成了cout<<10

2)如果我们在程序中取了这个const常量的地址,那么编译器将为此常量分配一个内存空间,生成一个常量副本, 此时所有通过地址对常量的操作都是针对该副本的。 

所以,&constNum 的地址是一个编译器为其分配的临时空间,其中Num也指向这个临时空间,在执行 Num = 10 后,临时空间的值为10

注意:

给出一个不需要使用 const_cast的例子:

[cpp]view plaincopyprint?

1.  const int constNum = 0;  

2.  int Num = constNum; //num = 0  

const int constNum = 0;

int Num = constNum; //num = 0

这里,Num只是引用常量 constNum 中的值,除此之外,变量num和变量constNum 是两个没有半毛钱关系的变量。只有当使用引用或指针指向常量变量时才需要使用const_cast转换,因为在这两种情况才导致Num和常量constNum指向同一个空间,而可能修改constNum中的值。

总结一句,常量是不能改的,想改就找个副本给你改。

 

 

8.C++ 虚函数表解析(写的很好,转过来看看)

分类:C++2013-05-16 21:2294人阅读评论(0)收藏举报

目录(?)[+]

1.前言

2.虚函数表

3.一般继承无虚函数覆盖

4.一般继承有虚函数覆盖

5.多重继承无虚函数覆盖

6.多重继承有虚函数覆盖

7.安全性

8.结束语

9.附录一VC中查看虚函数表

10.附录二例程

原作者:陈皓

源地址:http://blog.csdn.net/haoel/article/details/1948051

前言

C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有多种形态,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议。

 

关于虚函数的使用方法,我在这里不做过多的阐述。大家可以看看相关的C++的书籍。在这篇文章中,我只想从虚函数的实现机制上面为大家 一个清晰的剖析。

 

当然,相同的文章在网上也出现过一些了,但我总感觉这些文章不是很容易阅读,大段大段的代码,没有图片,没有详细的说明,没有比较,没有举一反三。不利于学习和阅读,所以这是我想写下这篇文章的原因。也希望大家多给我提意见。

 

言归正传,让我们一起进入虚函数的世界。

 

 虚函数表

 

C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

 

这里我们着重看一下这张虚函数表。C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。 这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

 

听我扯了那么多,我可以感觉出来你现在可能比以前更加晕头转向了。 没关系,下面就是实际的例子,相信聪明的你一看就明白了。

 

假设我们有这样的一个类:

 

class Base {

     public:

            virtual void f() { cout << "Base::f" << endl; }

            virtual void g() { cout << "Base::g" << endl; }

            virtual void h() { cout << "Base::h" << endl; }

 

};

 

按照上面的说法,我们可以通过Base的实例来得到虚函数表。 下面是实际例程:

 

          typedef void(*Fun)(void);

 

            Baseb;

 

            FunpFun = NULL;

 

            cout<< "虚函数表地址:" << (int*)(&b) << endl;

            cout<< "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&b) << endl;

 

            // Invoke the first virtual function 

            pFun= (Fun)*((int*)*(int*)(&b));

            pFun();

 

实际运行经果如下:(Windows XP+VS2003,  Linux 2.6.22 + GCC 4.1.3)

 

虚函数表地址:0012FED4

虚函数表 — 第一个函数地址:0044F148

Base::f

 

 

通过这个示例,我们可以看到,我们可以通过强行把&b转成int *,取得虚函数表的地址,然后,再次取址就可以得到第一个虚函数的地址了,也就是Base::f(),这在上面的程序中得到了验证(把int*强制转成了函数指针)。通过这个示例,我们就可以知道如果要调用Base::g()Base::h(),其代码如下:

 

            (Fun)*((int*)*(int*)(&b)+0);  // Base::f()

            (Fun)*((int*)*(int*)(&b)+1);  // Base::g()

            (Fun)*((int*)*(int*)(&b)+2);  // Base::h()

 

这个时候你应该懂了吧。什么?还是有点晕。也是,这样的代码看着太乱了。没问题,让我画个图解释一下。如下所示:

注意:在上面这个图中,我在虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符/0一样,其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。在WinXP+VS2003下,这个值是NULL。而在Ubuntu 7.10 +Linux 2.6.22 + GCC 4.1.3下,这个值是如果1,表示还有下一个虚函数表,如果值是0,表示是最后一个虚函数表。

 

 

下面,我将分别说明无覆盖有覆盖时的虚函数表的样子。没有覆盖父类的虚函数是毫无意义的。我之所以要讲述没有覆盖的情况,主要目的是为了给一个对比。在比较之下,我们可以更加清楚地知道其内部的具体实现。

 

一般继承(无虚函数覆盖)

下面,再让我们来看看继承时的虚函数表是什么样的。假设有如下所示的一个继承关系:

 

 

请注意,在这个继承关系中,子类没有重载任何父类的函数。那么,在派生类的实例中,其虚函数表如下所示:

 

对于实例:Derive d; 的虚函数表如下:

 

我们可以看到下面几点:

1虚函数按照其声明顺序放于表中。

2父类的虚函数在子类的虚函数前面。

 

我相信聪明的你一定可以参考前面的那个程序,来编写一段程序来验证。

 

 

一般继承(有虚函数覆盖)

 

覆盖父类的虚函数是很显然的事情,不然,虚函数就变得毫无意义。下面,我们来看一下,如果子类中有虚函数重载了父类的虚函数,会是一个什么样子?假设,我们有下面这样的一个继承关系。

 

 

 

为了让大家看到被继承过后的效果,在这个类的设计中,我只覆盖了父类的一个函数:f()。那么,对于派生类的实例,其虚函数表会是下面的一个样子:

 

 

我们从表中可以看到下面几点,

1覆盖的f()函数被放到了虚表中原来父类虚函数的位置。

2没有被覆盖的函数依旧。

 

这样,我们就可以看到对于下面这样的程序,

 

            Base*b = new Derive();

 

            b->f();

 

b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。这就实现了多态。

 

多重继承(无虚函数覆盖)

下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类并没有覆盖父类的函数。

 

 

 

对于子类实例中的虚函数表,是下面这个样子:

 

我们可以看到:

1  每个父类都有自己的虚表。

2  子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)

 

这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

多重继承(有虚函数覆盖)

下面我们再来看看,如果发生虚函数覆盖的情况。

 

下图中,我们在子类中覆盖了父类的f()函数。

 

 

 

下面是对于子类实例中的虚函数表的图:

 

 

我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:

 

            Derived;

            Base1*b1 = &d;

            Base2*b2 = &d;

            Base3*b3 = &d;

            b1->f(); //Derive::f()

            b2->f(); //Derive::f()

            b3->f(); //Derive::f()

 

            b1->g(); //Base1::g()

            b2->g(); //Base2::g()

            b3->g(); //Base3::g()

 

安全性

每次写C++的文章,总免不了要批判一下C++。这篇文章也不例外。通过上面的讲述,相信我们对虚函数表有一个比较细致的了解了。水可载舟,亦可覆舟。下面,让我们来看看我们可以用虚函数表来干点什么坏事吧。

 

一、通过父类型的指针访问子类自己的虚函数

我们知道,子类没有重载父类的虚函数是一件毫无意义的事情。因为多态也是要基于函数重载的。虽然在上面的图中我们可以看到Base1的虚表中有Derive的虚函数,但我们根本不可能使用下面的语句来调用子类的自有虚函数:

 

          Base1*b1 = new Derive();

            b1->f1();  //编译出错

 

任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法,所以,这样的程序根本无法编译通过。但在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。(关于这方面的尝试,通过阅读后面附录的代码,相信你可以做到这一点)

 

 

二、访问non-public的虚函数

另外,如果父类的虚函数是private或是protected的,但这些非public的虚函数同样会存在于虚函数表中,所以,我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数,这是很容易做到的。

 

如:

 

class Base {

    private:

            virtual void f() { cout << "Base::f" << endl; }

 

};

 

class Derive : public Base{

 

};

 

typedef void(*Fun)(void);

 

void main() {

    Derived;

    Fun  pFun= (Fun)*((int*)*(int*)(&d)+0);

    pFun();

}

 

结束语

C++这门语言是一门Magic的语言,对于程序员来说,我们似乎永远摸不清楚这门语言背着我们在干了什么。需要熟悉这门语言,我们就必需要了解C++里面的那些东西,需要去了解C++中那些危险的东西。不然,这是一种搬起石头砸自己脚的编程语言。

 

在文章束之前还是介绍一下自己吧。我从事软件研发有十个年头了,目前是软件开发技术主管,技术方面,主攻Unix/C/C++,比较喜欢网络上的技术,比如分布式计算,网格计算,P2PAjax等一切和互联网相关的东西。管理方面比较擅长于团队建设,技术趋势分析,项目管理。欢迎大家和我交流,我的MSNEmail是:haoel@hotmail.com 

附录一:VC中查看虚函数表

我们可以在VCIDE环境中的Debug状态下展开类的实例就可以看到虚函数表了(并不是很完整的)

附录 二:例程

 

下面是一个关于多重继承的虚函数表访问的例程:

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  using namespace std;  

3.     

4.  class Base1 {  

5.  public:  

6.              virtual void f() { cout << "Base1::f" << endl; }  

7.              virtual void g() { cout << "Base1::g" << endl; }  

8.              virtual void h() { cout << "Base1::h" << endl; }  

9.     

10. };  

11.    

12. class Base2 {  

13. public:  

14.             virtual void f() { cout << "Base2::f" << endl; }  

15.             virtual void g() { cout << "Base2::g" << endl; }  

16.             virtual void h() { cout << "Base2::h" << endl; }  

17. };  

18.    

19. class Base3 {  

20. public:  

21.             virtual void f() { cout << "Base3::f" << endl; }  

22.             virtual void g() { cout << "Base3::g" << endl; }  

23.             virtual void h() { cout << "Base3::h" << endl; }  

24. };  

25.    

26.    

27. class Derive : public Base1, public Base2, public Base3 {  

28. public:  

29.             virtual void f() { cout << "Derive::f" << endl; }  

30.             virtual void g1() { cout << "Derive::g1" << endl; }  

31. };  

32.    

33.    

34. typedef void(*Fun)(void);  

35.    

36. int main()  

37. {  

38.             Fun pFun = NULL;  

39.    

40.             Derive d;  

41.             int** pVtab = (int**)&d;  

42.    

43.             //Base1's vtable   

44.             //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+0);   

45.             pFun = (Fun)pVtab[0][0];  

46.             pFun();  

47.    

48.             //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+1);   

49.             pFun = (Fun)pVtab[0][1];  

50.             pFun();  

51.    

52.             //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+2);   

53.             pFun = (Fun)pVtab[0][2];  

54.             pFun();  

55.    

56.             //Derive's vtable   

57.             //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+3);   

58.             pFun = (Fun)pVtab[0][3];  

59.             pFun();  

60.    

61.             //The tail of the vtable   

62.             pFun = (Fun)pVtab[0][4];  

63.             cout<<pFun<<endl;  

64.    

65.    

66.             //Base2's vtable   

67.             //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);   

68.             pFun = (Fun)pVtab[1][0];  

69.             pFun();  

70.    

71.             //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);   

72.             pFun = (Fun)pVtab[1][1];  

73.             pFun();  

74.    

75.             pFun = (Fun)pVtab[1][2];  

76.             pFun();  

77.    

78.             //The tail of the vtable   

79.             pFun = (Fun)pVtab[1][3];  

80.             cout<<pFun<<endl;  

81.    

82.    

83.    

84.             //Base3's vtable   

85.             //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);   

86.             pFun = (Fun)pVtab[2][0];  

87.             pFun();  

88.    

89.             //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);   

90.             pFun = (Fun)pVtab[2][1];  

91.             pFun();  

92.    

93.             pFun = (Fun)pVtab[2][2];  

94.             pFun();  

95.    

96.             //The tail of the vtable   

97.             pFun = (Fun)pVtab[2][3];  

98.             cout<<pFun<<endl;  

99.    

100.            return 0;  

101.}  

#include <iostream>

using namespace std;

 

class Base1 {

public:

            virtual void f() { cout<< "Base1::f" << endl; }

            virtual void g() { cout<< "Base1::g" << endl; }

            virtual void h() { cout<< "Base1::h" << endl; }

 

};

 

class Base2 {

public:

            virtual void f() { cout<< "Base2::f" << endl; }

            virtual void g() { cout<< "Base2::g" << endl; }

            virtual void h() { cout<< "Base2::h" << endl; }

};

 

class Base3 {

public:

            virtual void f() { cout<< "Base3::f" << endl; }

            virtual void g() { cout<< "Base3::g" << endl; }

            virtual void h() { cout<< "Base3::h" << endl; }

};

 

 

class Derive : public Base1, public Base2, public Base3 {

public:

            virtual void f() { cout<< "Derive::f" << endl; }

            virtual void g1() { cout<< "Derive::g1" << endl; }

};

 

 

typedef void(*Fun)(void);

 

int main()

{

            Fun pFun = NULL;

 

            Derive d;

            int** pVtab =(int**)&d;

 

            //Base1's vtable

            //pFun =(Fun)*((int*)*(int*)((int*)&d+0)+0);

            pFun = (Fun)pVtab[0][0];

            pFun();

 

            //pFun =(Fun)*((int*)*(int*)((int*)&d+0)+1);

            pFun = (Fun)pVtab[0][1];

            pFun();

 

            //pFun =(Fun)*((int*)*(int*)((int*)&d+0)+2);

            pFun = (Fun)pVtab[0][2];

            pFun();

 

            //Derive's vtable

            //pFun =(Fun)*((int*)*(int*)((int*)&d+0)+3);

            pFun = (Fun)pVtab[0][3];

            pFun();

 

            //The tail of the vtable

            pFun = (Fun)pVtab[0][4];

           cout<<pFun<<endl;

 

 

            //Base2's vtable

            //pFun =(Fun)*((int*)*(int*)((int*)&d+1)+0);

            pFun = (Fun)pVtab[1][0];

            pFun();

 

            //pFun =(Fun)*((int*)*(int*)((int*)&d+1)+1);

            pFun = (Fun)pVtab[1][1];

            pFun();

 

            pFun = (Fun)pVtab[1][2];

            pFun();

 

            //The tail of the vtable

            pFun = (Fun)pVtab[1][3];

           cout<<pFun<<endl;

 

 

 

            //Base3's vtable

            //pFun =(Fun)*((int*)*(int*)((int*)&d+1)+0);

            pFun = (Fun)pVtab[2][0];

            pFun();

 

            //pFun =(Fun)*((int*)*(int*)((int*)&d+1)+1);

            pFun = (Fun)pVtab[2][1];

            pFun();

 

            pFun = (Fun)pVtab[2][2];

            pFun();

 

            //The tail of the vtable

            pFun = (Fun)pVtab[2][3];

           cout<<pFun<<endl;

 

            return 0;

}


一些很好的回复:

[cpp]view plaincopyprint?

1.  class B  

2.  {  

3.      public:  

4.          virtual void f()   

5.          {   

6.              cout << "B::f()" << endl;  

7.          }  

8.  };  

9.  class B1 :   public B  

10. {  

11.     public:  

12.         virtual void f()   

13.         {   

14.             B::f();  //这个是如何取到地址的?  

15.             cout << "B1::f()" << endl;  

16.         }  

17. };  

class B

{

    public:

        virtual void f()

   {

    cout << "B::f()"<< endl;

   }

};

class B1 :   public B

{

    public:

        virtual void f()

   {

    B::f();  //这个是如何取到地址的?

    cout << "B1::f()"<< endl;

   }

};

如上代码中会输出 B::f()B1::f()

看完《虚函数解析》之后,认为子类的虚函数会直接覆盖父类的虚函数
在整个子类虚函数表中已经找不到被覆盖的父类函数了,那么上述 B;;f()代码是如何寻址的呢?

这是因为,它不通过虚函数表去找, B::f()就是函数的地址,编译器直接把 B::f 的函数地址写在代码这里.

虚函数表查找是在基类指针或引用调用时发生的,你这的B::f();已经明确函数地址了,跟查虚函数表没牵连的。

动态绑定时(也就是运行期间)才会去虚函数表中找,
你这样写B::f(),调用代码在#编译期间#就确定了,和虚函数没什么关系啊

 

 

 

 

 

 

 

 

9.构造函数、析构函数与虚函数的关系

分类:C++2013-05-16 20:35128人阅读评论(0)收藏举报

目录(?)[+]

1.为什么构造函数不能是虚函数

2.为什么在派生类中的析构函数常常为虚析构函数

3.在一个基类中析构函数设置为虚析构函数的原因是什么

4.把所有的类的析构函数都设置为虚函数好吗

1、为什么构造函数不能是虚函数?

因为:从使用上来说,虚函数是通过基类指针或引用来调用派生类的成员的,则在调用之前,对象必须存在,而构造函数是为了创建对象的。

2、为什么在派生类中的析构函数常常为虚析构函数

注意,默认不是析构函数

一句话,是为了避免内存泄露

如果不考虑虚函数的状况,给出一个基类和派生类,如果调用派生类的析构函数时,肯定会引发调用基类的析构函数,这和析构函数是不是虚函数没关系。

现在考虑虚函数的问题,由于使用虚函数使我们可以定义一个基类指针或引用可以直接对派生类进行操作,这就存在两种情况:

如果,不把基类的析构函数设置为虚函数,则在删除对象时,如果直接删除基类指针,系统就只能调用基类析构函数,而不会调用派生类构造函数。这就会导致内存泄露。

如果,把基类的析构函数设置为虚函数,则在删除对象时,直接删除基类指针,系统会调用派生类析构函数,之后此派生类析构函数会引发系统自动调用自己的基类,这就不会导致内存泄露。

所以,在写一个类时,尽量将其析构函数设置为虚函数,但析构函数默认不是虚函数

举例一:通过派生类指针删除派生类对象的情况

[cpp]view plaincopyprint?

1.  #include<iostream>  

2.  using namespace std;  

3.    

4.  class Base  

5.  {  

6.  public:  

7.      ~Base()  

8.      {  

9.          cout<<" Base 的析构函数"<<endl;  

10.     }  

11.   

12. };  

13.   

14. class Derive : public Base  

15. {  

16. public:  

17.     ~Derive()  

18.     {  

19.         cout<<" Derive 的析构函数"<<endl;  

20.     }  

21.   

22. };  

23.   

24. void main()  

25. {  

26.     Derive* p = new Derive();  

27.     delete p;  

28.     system("pause");  

29. }  

#include<iostream>

using namespace std;

 

class Base

{

public:

~Base()

{

   cout<<" Base 的析构函数"<<endl;

}

 

};

 

class Derive : public Base

{

public:

~Derive()

{

   cout<<" Derive 的析构函数"<<endl;

}

 

};

 

void main()

{

Derive* p = new Derive();

delete p;

system("pause");

}

结果:

分析:即调用了基类的析构函数,又调用了派生类的析构函数

说明:

1p是指向派生类Derive的指针,删除p时,会自动调用Derive的析构函数,同时在之后,有继续向上调用基类Base的析构函数。

2这个过程和虚函数没有关系,只要调用派生类的析构函数,就自动回调用其祖先的析构函数。

举例二:通过基类指针删除派生类对象时没有把基类的析构函数设置为虚函数的情况

[cpp]view plaincopyprint?

1.  #include<iostream>  

2.  using namespace std;  

3.    

4.  class Base  

5.  {  

6.  public:  

7.      ~Base()  

8.      {  

9.          cout<<" Base 的析构函数"<<endl;  

10.     }  

11.   

12. };  

13.   

14. class Derive : public Base  

15. {  

16. public:  

17.     ~Derive()  

18.     {  

19.         cout<<" Derive 的析构函数"<<endl;  

20.     }  

21.   

22. };  

23.   

24. void main()  

25. {  

26.     Base* p = new Derive();  

27.     delete p;  

28.     system("pause");  

29. }  

#include<iostream>

using namespace std;

 

class Base

{

public:

~Base()

{

   cout<<" Base 的析构函数"<<endl;

}

 

};

 

class Derive : public Base

{

public:

~Derive()

{

   cout<<" Derive 的析构函数"<<endl;

}

 

};

 

void main()

{

Base* p = new Derive();

delete p;

system("pause");

}

结果:

分析:只调用了基类的析构函数,没调用了派生类的析构函数

说明:

1)由于p是指向基类Base的指针,而且其Base的析构函数也不是虚函数,在删除p时,会直接调用Base的析构函数,而不会调用自己实际指向Derive的析构函数(这是用来和后面对比的),如果在Derive中的析构函数中有内存的释放,就会造成内存泄露。

举例三:通过基类指针删除派生类对象时把基类的析构函数设置为虚函数的情况

[cpp]view plaincopyprint?

1.  #include<iostream>  

2.  using namespace std;  

3.    

4.  class Base  

5.  {  

6.  public:  

7.      virtual ~Base()  

8.      {  

9.          cout<<" Base 的析构函数"<<endl;  

10.     }  

11.   

12. };  

13.   

14. class Derive : public Base  

15. {  

16. public:  

17.     ~Derive()  

18.     {  

19.         cout<<" Derive 的析构函数"<<endl;  

20.     }  

21.   

22. };  

23.   

24. void main()  

25. {  

26.     Base* p = new Derive();  

27.     delete p;  

28.     system("pause");  

29. }  

#include<iostream>

using namespace std;

 

class Base

{

public:

virtual ~Base()

{

   cout<<" Base 的析构函数"<<endl;

}

 

};

 

class Derive : public Base

{

public:

~Derive()

{

   cout<<" Derive 的析构函数"<<endl;

}

 

};

 

void main()

{

Base* p = new Derive();

delete p;

system("pause");

}

结果

分析

1)由于p是指向派生类Base的指针,而且其析构函数是虚函数,由于虚函数的性质,在删除p时,会直接调用Derive的析构函数

2)在调用Derive的析构函数后,会引发Derive的基类Base的析构函数的调用。这和其是否是虚函数没关系,只是析构函数自己的功能。

总结,在有派生存在的类集合中,基类的析构函数尽量设置为虚函数,而且常常为虚函数,但默认不是虚函数,而且不需要一定设置为虚函数。

注意:如果基类的析构函数设置为虚函数,那么所有的派生类也默认为虚析构函数,即使没有带关键字Virtual

在一个基类中,析构函数设置为虚析构函数的原因是什么?

主要是因为:

1)需要使用指向基类指针对派生类进行操作时,才有可能会导致内存泄露。如果不需要这样操作,完全可以不这么设置。

2)基类的析构函数设置为虚函数后,其派生出类的析构函数自动为虚函数。

3、把所有的类的析构函数都设置为虚函数好吗?

肯定不好,其实这就是想问虚函数的缺点。

虚函数属于在运行时进行处理的,为了在运行时根据不同的对象调用不同的虚函数,这就要求具有虚函数的类拥有一些额外信息,这些信息用于在运行时确定该对象应该调用哪一个虚函数。系统为每一个对象存储了一个虚函数表vtblvirtual table)。虚函数表是一个函数指针数组,数组中每一个成员都包含一个虚函数,并把这个表的首地址存储在 vptrvirtual table pointer)中。当一个对象调用了虚函数,实际的被调用函数通过下面的步骤确定:找到对象的 vptr 指向的 vtbl,然后在 vtbl 中寻找合适的函数指针。

如图:

因此,使用虚函数后的类对象要比不使用虚函数的类对象占的空间多,而且在查找具体使用哪一个虚函数时,还会有时间代价。即当一个类不打算作为基类时,不用将其中的函数设置为虚函数。

 

 

 

 

 

 

 

 

 

10.C++命名规则

分类:C++2012-08-18 19:46193人阅读评论(0)收藏举报

c++functionvectorcservicebuffer

目录(?)[+]

1. 

2.类结构

3.函数

4.变量

5.常量

6.枚举联合typedef

7.宏枚举值

 

C++命名规范的整体原则:

 

同一性

在编写一个子模块或派生类的时候,要遵循其基类或整体模块的命名风格,保持命名风格在整个模块中的同一性。

标识符组成

标识符采用英文单词或其组合,应当直观且可以拼读,可望文知意,用词应当准确。

最小化长度 && 最大化信息量原则

在保持一个标识符意思明确的同时,应当尽量缩短其长度。

避免过于相似

不要出现仅靠大小写区分的相似的标识符,例如“i”“I”“function”“Function”等等。

避免在不同级别的作用域中重名

程序中不要出现名字完全相同的局部变量和全局变量,尽管两者的作用域不同而不会发生语法错误,但容易使人误解。

正确命名具有互斥意义的标识符

用正确的反义词组命名具有互斥意义的标识符,如:"nMinValue""nMaxValue""GetName()" "SetName()" ....

避免名字中出现数字编号

尽量避免名字中出现数字编号,如Value1,Value2等,除非逻辑上的确需要编号。这是为了防止程序员偷懒,不肯为命名动脑筋而导致产生无意义的名字(因为用数字编号最省事)。

 

类/结构

除了异常类等个别情况(不希望用户把该类看作一个普通的、正常的类之情况)外,C++/结构
的命名应该遵循以下准则:

C++/结构的命名

类的名称都要以大写字母“C”开头,后跟一个或多个单词。为便于界定,每个单词的首字母要大写。

推荐的组成形式

类的命名推荐用"名词""形容词+名词"的形式,例如:"CAnalyzer", "CFastVector" ....

不同于C++类的概念,传统的C结构体只是一种将一组数据捆绑在一起的方式。传统C结构体的命名规则为:

传统C结构体的命名

传统C结构体的名称全部由大写字母组成,单词间使用下划线界定,例如:"SERVICE_STATUS", "DRIVER_INFO" ....

函数

函数的命名

函数的名称由一个或多个单词组成。为便于界定,每个单词的首字母要大写。

推荐的组成形式

函数名应当使用"动词"或者"动词+名词"(动宾词组)的形式。例如:"GetName()", "SetValue()", "Erase()", "Reserve()" ....

保护成员函数

保护成员函数的开头应当加上一个下划线“_”以示区别,例如:"_SetState()" ....

私有成员函数

类似地,私有成员函数的开头应当加上两个下划线“__”,例如:"__DestroyImp()" ....

虚函数

虚函数习惯以“Do”开头,如:"DoRefresh()", "_DoEncryption()" ....

回调和事件处理函数

回调和事件处理函数习惯以单词“On”开头。例如:"_OnTimer()", "OnExit()" ....

变量

变量应该是程序中使用最多的标识符了,变量的命名规范可能是一套C++命名准则中最重要的部分:

变量的命名

变量名由作用域前缀+类型前缀+一个或多个单词组成。为便于界定,每个单词的首字母要大写。

对于某些用途简单明了的局部变量,也可以使用简化的方式,如:i, j, k, x, y, z ....

作用域前缀

作用域前缀标明一个变量的可见范围。作用域可以有如下几种:

前缀

说明

局部变量

m_

类的成员变量(member

sm_

类的静态成员变量(static member

s_

静态变量(static

g_

外部全局变量(global

sg_

静态全局变量(static global

gg_

进程间共享的共享数据段全局变量(global global

除非不得已,否则应该尽可能少使用全局变量。

类型前缀

类型前缀标明一个变量的类型,可以有如下几种:

前缀

说明

n

整型和位域变量(number

e

枚举型变量(enumeration

c

字符型变量(char

b

布尔型变量(bool

f

浮点型变量(float

p

指针型变量和迭代子(pointer

pfn

特别针对指向函数的指针变量和函数对象指针(pointer of function

g

数组(grid

i

类的实例(instance

对于经常用到的类,也可以定义一些专门的前缀,如:std::stringstd::wstring类的前缀可以定义为"st"std::vector类的前缀可以定义为"v"等等。

类型前缀可以组合使用,例如"gc"表示字符数组,"ppn"表示指向整型的指针的指针等等。

推荐的组成形式

变量的名字应当使用"名词"或者"形容词+名词"。例如:"nCode", "m_nState""nMaxWidth" ....

常量

C++中引入了对常量的支持,常量的命名规则如下:

常量的命名

常量名由类型前缀+全大写字母组成,单词间通过下划线来界定,如:cDELIMITER, nMAX_BUFFER ....

类型前缀的定义与变量命名规则中的相同。

枚举、联合、typedef

枚举、联合及typedef语句都是定义新类型的简单手段,它们的命名规则为:

枚举、联合、typedef的命名

枚举、联合、typedef语句生成的类型名由全大写字母组成,单词间通过下划线来界定,如:FAR_PROC, ERROR_TYPE ....

宏、枚举值

宏、枚举值的命名

宏和枚举值由全大写字母组成,单词间通过下划线来界定,如:ERROR_UNKNOWN, OP_STOP ....

 

 

 

 

 

 

 

 

 

 

11.面向对象和面向过程的区别

分类:C++2012-07-28 14:44258人阅读评论(0)收藏举报

java语言编程cjvmjsf

   面向过程一种以事件为中心的编程思想,以功能(行为)为导向,按模块化的设计,就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。

   面向对象一种以事物为中心的编程思想,以数据(属性)为导向,将具有相同一个或者多个属性的物体抽象为,将他们包装起来;而有了这些数据(属性)之后,我们再考虑他们的行为(对这些属性进行什么样的操作),是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

例如五子棋,面向过程的设计思路就是首先分析问题的步骤:

   1、开始游戏,2、黑子先走,3、绘制画面,4、判断输赢,5、轮到白子,
   6
、绘制画面,7、判断输赢,8、返回步骤29、输出最后结果。

   把上面每个步骤用分别的函数来实现,问题就解决了。

而面向对象的设计则是从另外的思路来解决问题。整个五子棋可以分为:

   1、黑白双方,这两方的行为是一模一样的,
   2
、棋盘系统,负责绘制画面,

   3、规则系统,负责判定诸如犯规、输赢等。

   第一类对象(玩家对象)负责接受用户输入,并告知第二类对象(棋盘对象)棋子布局的变化,棋盘对象接收到了棋子的变化就要负责在屏幕上面显示出这种变化,同时利用第三类对象(规则系统)来对棋局进行判定。

   可以明显地看出,面向对象是以功能来划分问题,而不是步骤。同样是绘制棋局,这样的行为在面向过程的设计中分散在了总多步骤中,很可能出现不同的绘制版本,因为通常设计人员会考虑到实际情况进行各种各样的简化。而面向对象的设计中,绘图只可能在棋盘对象中出现,从而保证了绘图的统一。

   功能上的统一保证了面向对象设计的可扩展性。比如我要加入悔棋的功能,如果要改动面向过程的设计,那么从输入到判断到显示这一连串的步骤都要改动,甚至步骤之间的循序都要进行大规模调整。如果是面向对象的话,只用改动棋盘对象就行了,棋盘系统保存了黑白双方的棋谱,简单回溯就可以了,而显示和规则判断则不用顾及,同时整个对对象功能的调用顺序都没有变化,改动只是局部的。

   举例2:又比如我们有一台演出,为简单起见,假设有如下流程:主持人开场——演员一表演——演员二表演——主持人总结。

用面向过程的思想来分析,就是先完成主持人开场,再完成演员一的表演,再完成演员二的表演,最后完成主持人的总结。

而如果用面向对象的思想来分析,就应该是这样的。这个演出由两大部分组成:主持人、演员。与主持人相关的:开场、总结。

与演员相关的:演员编号、所演的节目。然后这台演出就可以这样策划:需要一个主持人a,需要两个演员bc

演出的事情可以表示为:a的开场——> bc的编号和节目——> a的总结。

什么是面向对象技术?

   面向对象技术是一种以对象为基础,以事件或消息来驱动对象执行处理的程序设计技术。它具有封装性、继承性及多态性。

传统的面向过程式编程语言是以过程为中心以算法为驱动的话,面向对象的编程语言则是以对象为中心以消息为驱动。

面向对象技术有哪些优点?

   面向对象技术具有程序结构清晰,自动生成程序框架,实现简单,维护简单,代码重用率高,软件开发效率高等优点

在中兴面试的时候曾被问到C++java有什么区别,当时答的一踏糊涂,现在也不知道,只是做个基本的总结吧

   C++Java均支持面向对象技术的基本概念和基本特征,如封装、类、构造函数、析构函数、继承、多态等。 C++Java语法描述有相同之处,也有不同之处。如基本结构语句的语法大致相同,而类定义的语法等方面则不相同。

1.指针 

  JAVA语言让编程者无法找到指针来直接访问内存无指针,并且增添了自动的内存管理功能,从而有效地防止了cc++语言中指针操作失误,如野指针所造成的系统崩溃。但也不是说JAVA没有指针,虚拟机内部还是使用了指针,只是外人不得使用而已。这有利于Java程序的安全。

   C++通过构造函数创建对象,可以直接使用指针来操作对象的成员,而Java通过new运算符创建对象,通过new运算符返回的对象引用来使用对象,而不是直接操作指针; 

2.多重继承 

   c++支持多重继承,这是c++的一个特征,它允许多父类派生一个类。尽管多重继承功能很强,但使用复杂,而且会引起许多麻烦,编译程序实现它也很不容易。Java不支持多重继承,但允许一个类实现多个接口(extends+implement),实现了c++多重继承的功能,又避免了c++中的多重继承实现方式带来的诸多不便。 

4.内存管理 

   C++程序要显式地释放所分配的内存,而Java具有内存垃圾收集机制
   Java
自动进行无用内存回收操作,不需要程序员进行删除。Java中当一个对象不被再用到时,无用内存回收器将给它加上标签以示删除。JAVA里无用内存回收程序是以线程方式在后台运行的,利用空闲时间工作。

   c++中必须由程序贝释放内存资源,增加了程序设计者的负扔。

5 操作符重载

   C++有运算符重载机制,而Java没有此特性;

6 可移植性

  java 是运行在jvm上的,之所以说它的可移植性强,是因为jvm可以安装到任何的系统

   c++不是不能在其他系统运行,而是c++在不同的系统上运行,需要不同的编码(这一点不如java,只编写一次代码,到处运行)

7.数据类型及类 

  Java是完全面向对象的语言,所有函数和变量部必须是类的一部分。除了基本数据类型之外,其余的都作为类对象,包括数组。对象将数据和方法结合起来,把它们封装在类中,这样每个对象都可实现自己的特点和行为。

   C++是混合型面向对象程序设计语言,c++允许将函数和变量定义为全局的。此外,Java中取消了cc++中的结构和联合,消除了不必要的麻烦。

出处:http://blog.sina.com.cn/s/blog_5a3744350100jsf7.html

 

 

 

12.C++_类型转换

分类:C++2011-08-05 01:26294人阅读评论(0)收藏举报

c++stringdeletesystemclassnull

类型转换:将一种类型的值转换为另一种类型的值

类类型:classstuctunion类型

标准类型:除类类型外的所有类型,如int

类型种类:

类型转换有4种:

1)标准类型->标准类型 

2)标准类型->类类型

3)类类型->标准类型

4)类类型->类类型

一、标准类型转变为标准类型

方法:

1)隐式转换

         使用场合:程序未显式类型转换,但是含有混合运算,赋值语句,函数返回值,形参和实参转换

2)显式转换

        使用场合:程序显式类型转换

        方法:C语言的强制法(int)aC++的函数法int(a)

         注意:当类型名有两个以上时(unsign int ),不能使用C++的函数法,但是可以使用C语言的强制法,所以以后可以直接使用C的强制法,不会错

二、标准类型转化成类类型

方法:构造函数和自定义的重载赋值号=的函数

必备参数:它们都需要有标准类型的参数(这个标准类型就是等号右边的值类型)

代码:

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  using namespace std;  

3.  class String  

4.  {  

5.  private:  

6.      int len;  

7.      char* strContent;  

8.  public:  

9.      String(int i);  

10.     String(char* p);  

11.     String& operator=(int i);  

12.     String& operator=(char* p);  

13.     void show();  

14. };  

15. String::String(int i)  

16. {  

17.     len=i;  

18.     strContent = new char[len];  

19.     memcpy(strContent,"",len);  

20. }  

21. String::String(char* p)  

22. {  

23.     len=strlen(p)+1;  

24.     strContent = new char[len];  

25.     memcpy(strContent,p,len);  

26. }  

27. String& String::operator=(int i)  

28. {  

29.     len=i;  

30.     if (strContent!=NULL)  

31.     {  

32.         delete []strContent;  

33.     }  

34.     strContent = new char[len];  

35.     memcpy(strContent,"",len);  

36.     return *this;  

37. }  

38.   

39. String& String::operator=(char* p)  

40. {  

41.     len=strlen(p)+1;  

42.     if (strContent!=NULL)  

43.     {  

44.         delete []strContent;  

45.     }  

46.     strContent = new char[len];  

47.     memcpy(strContent,p,len);  

48.     return *this;  

49. }  

50.   

51.   

52. void String::show()  

53. {  

54.     cout<<"字符串长度为:"<<len<<endl;  

55.     cout<<"字符串内容为:"<<strContent<<endl;  

56. }  

57.   

58. void main()  

59. {  

60.       

61.     String a=1;//调用构造函数  

62.     a.show();  

63.     String b="123";//调用构造函数  

64.     b.show();  

65.     b=6;     //调用重载运算符=   

66.     b.show();  

67.     b="456";//调用重载运算符=   

68.     b.show();  

69.     system("pause");  

70. }  

#include <iostream>

using namespace std;

class String

{

private:

int len;

char* strContent;

public:

String(int i);

String(char* p);

String& operator=(int i);

String& operator=(char* p);

void show();

};

String::String(int i)

{

len=i;

strContent = new char[len];

memcpy(strContent,"",len);

}

String::String(char* p)

{

len=strlen(p)+1;

strContent = new char[len];

memcpy(strContent,p,len);

}

String& String::operator=(int i)

{

len=i;

if (strContent!=NULL)

{

   delete []strContent;

}

strContent = new char[len];

memcpy(strContent,"",len);

return *this;

}

 

String& String::operator=(char* p)

{

len=strlen(p)+1;

if (strContent!=NULL)

{

   delete []strContent;

}

strContent = new char[len];

memcpy(strContent,p,len);

return *this;

}

 

 

void String::show()

{

cout<<"字符串长度为:"<<len<<endl;

cout<<"字符串内容为:"<<strContent<<endl;

}

 

void main()

{

String a=1;//调用构造函数

a.show();

String b="123";//调用构造函数

b.show();

b=6;     //调用重载运算符=

b.show();

b="456";//调用重载运算符=

b.show();

system("pause");

}

注意:为了避免标准类型隐式转换为类类型(OBJ obj=20),可以把构造函数放在私有段,在创建对象的时候就不能直接调用构造函数了,

           这时创建对象可以通过定义一个静态成员函数或一个友元函数来间接调用构造函数来
代码:

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  using namespace std;  

3.  class String  

4.  {  

5.  private:  

6.      int len;  

7.      char* strContent;  

8.      String(char* p);  

9.  public:  

10.     static String make(char* p);  

11.     void show();  

12. };  

13. String::String(char* p)  

14. {  

15.     len=strlen(p)+1;  

16.     strContent = new char[len];  

17.     memcpy(strContent,p,len);  

18. }  

19.   

20. String String::make(char* p)  

21. {  

22.     String a=p;//创建一个类,并调用构造函数  

23.     return a;  

24. }  

25.   

26. void String::show()  

27. {  

28.     cout<<"字符串长度为:"<<len<<endl;  

29.     cout<<"字符串内容为:"<<strContent<<endl;  

30. }  

31.   

32. void main()  

33. {  

34.          String a="123"//错误  

35.     String a = String::make("123456");  

36.     a.show();  

37.     system("pause");  

38. }  

#include <iostream>

using namespace std;

class String

{

private:

int len;

char* strContent;

String(char* p);

public:

static String make(char* p);

void show();

};

String::String(char* p)

{

len=strlen(p)+1;

strContent = new char[len];

memcpy(strContent,p,len);

}

 

String String::make(char* p)

{

String a=p;//创建一个类,并调用构造函数

return a;

}

 

void String::show()

{

cout<<"字符串长度为:"<<len<<endl;

cout<<"字符串内容为:"<<strContent<<endl;

}

 

void main()

{

         String a="123";//错误

String a =String::make("123456");

a.show();

system("pause");

}

三、类类型->标准类型类类型->类类型

方法:引入特殊的成员函数---类型转换函数

类型转换函数:在类对象之间提供一种类似显式类型转换的机制。

语法:

[cpp]view plaincopyprint?

1.  Class 源类类名 //在此类中定义  

2.  {  

3.      Operator 目的类型()  

4.      {  

5.          Return 目的类型的数据;  

6.      }  

7.  }  

Class 源类类名 //在此类中定义

{

Operator 目的类型()

{

   Return 目的类型的数据;

}

}

作用:将对象转换成目的类型的变量,目的类型如果是标准类型也可以是类类型

注意一:类型转换函数没有参数、没有返回类型、但是有返回值(一个type的实例)。

注意二、类型转换函数只能定义为类的成员函数,而不能是友元函数(不允许有参数)

注意三、类型转换函数不可以被超载,因为没有参数

代码:

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  using namespace std;  

3.  class point  

4.  {  

5.  private:  

6.      int x;  

7.  public:  

8.      point()  

9.      {  

10.         x=10;  

11.     }  

12.     operator int() //类型转换函数,转换为整型  

13.     {  

14.         return x;  

15.     }  

16. };  

17. void main()  

18. {  

19.     point a;  

20.     int t=a;//隐式调用 类型转换函数  

21.     cout<<t<<endl;  

22.     int z = a.operator int();//显示调用 类型转换函数  

23.     cout<<z<<endl;  

24.     system("pause");  

25. }  

#include <iostream>

using namespace std;

class point

{

private:

int x;

public:

point()

{

   x=10;

}

operator int() //类型转换函数,转换为整型

{

   return x;

}

};

void main()

{

point a;

int t=a;//隐式调用 类型转换函数

cout<<t<<endl;

int z = a.operator int();//显示调用 类型转换函数

cout<<z<<endl;

system("pause");

}

注意一:一般使用隐式方式,当需要明确指出用哪一个类型转换函数时,才使用显式方式

注意二:注意下面两种转换方式:

[cpp]view plaincopyprint?

1.  int a = obj;//使用类型转换函数进行转换  

2.  OBJ obj=a;  //使用构造函数进行转换  

int a = obj;//使用类型转换函数进行转换

OBJ obj=a;  //使用构造函数进行转换

注意三、当一个类既有用于转换的构造函数,又拥有类型转换函数

如:

构造函数:A(int a);

类型转换函数:operator int( )

则,obj=obj+n 就有两种解释:类和标准类型可以互相转化,隐式转换有二义性

解释一:可以先把对象转换为数,再两个数相加

解释二:可以先把数转换成类,再相加

解决方法:这是需要显示使用类型转换函数:

方法一:先把数变对象,两个对象再相加

              INT obj1=nObj=obj+obj1 两个对象相加

方法二:先把对象变数,两个整数再相加

             Obj=intobj+n

即:用户类型定义的类型转换函数,只有无二义性的时候,才能使用隐式转换

 

 

13.C++_静态成员

1.C语言中的Static不涉及到类

2.一句话描述C语言中static的作用

1.静态成员的分类

2.静态变量

1.静态全局变量

2.静态局部变量

3.静态函数

3.面向对象的static

1.一句话描述C语言中static的作用

2.静态数据成员

3.静态成员函数

4.注意

5.说明

C语言中的Static(不涉及到类)

一句话描述C语言中static的作用:

无论是针对变量还是函数:static作用都是修改变量的作用域

静态成员的分类:

静态成员包括静态变量静态函数两部分

静态变量

静态全局变量

引入的原因(作用):隐藏变量(主要功能)

说明:即加了static,就会对其它源文件隐藏。

具体来说,如果直接定义全局变量,则该全局变量相当于一个项目变量,即是在整个项目的cpp中共享的,可以使用extern在别的cpp文件中使用。而如果定义成静态全局变量,该全局变量就变成了一个文件变量,即是在所在cpp文件使用。

优点:利用其隐藏特性,可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。

程序:

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  using namespace std;  

3.    

4.  static int a;  

5.    

6.  void func1();  

7.    

8.    

9.  int  main()  

10. {  

11.     cout<<"a = "<<a<<endl;//输出静态变量的值:0   

12.     int a = 1;  

13.     cout<<"a = "<<a<<endl; //输出内部变量的值:1   

14.     a = 10;  

15.     cout<<"a = "<<a<<endl; //输出内部变量的值:10   

16.     func1();       //输出静态变量的值:2   

17.     cout<<"a = "<<a<<endl; //输出内部变量的值:10   

18.     system("pause");  

19. }  

20.   

21. void func1()  

22. {  

23.     a += 2;   

24.     cout<<"a = "<<a<<endl; //2   

25. }  

#include <iostream>

using namespace std;

 

static int a;

 

void func1();

 

 

int  main()

{

cout<<"a ="<<a<<endl;//输出静态变量的值:0

int a = 1;

cout<<"a ="<<a<<endl; //输出内部变量的值:1

a = 10;

cout<<"a ="<<a<<endl; //输出内部变量的值:10

func1();       //输出静态变量的值:2

cout<<"a ="<<a<<endl; //输出内部变量的值:10

system("pause");

}

 

void func1()

{

a += 2;

cout<<"a ="<<a<<endl; //2

}

定义:在全局变量前加上关键字static

特点:

1)内存所在位置:静态存储区(即全局数据区)

2)作用域:变量所在文件,(其他文件不可见)

3)生命期:整个程序

4)只在声明处初始化一次,不存在其他位置的初始化

5)默认的初始化值为0,(局部变量默认值为随机值,全局变量默认值为0

静态局部变量

定义:在局部变量(函数中的变量)前加上关键字static

特点:

1)内存所在位置:静态存储区(即全局数据区)

2)作用域:所在函数

3)生命期:整个程序,程序结束,生命结束

4在函数调用时,才对其初始化,而且只在声明处初始化一次,其他位置初始化忽略不见(函数可能被调用多次)

5)默认的初始化值为0,(局部变量默认值为随机值,全局变量默认值为0

作用:

1)判断函数是否被调用过

2)统计访问所在函数的次数

举例:

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  using namespace std;  

3.    

4.  void func1();  

5.  void func2();  

6.    

7.  int  main()  

8.  {  

9.      int a = 0;  

10.     cout<<"a = "<<a<<endl; //0   

11.     func1();//4   

12.     func1();//6   

13.     cout<<"a = "<<a<<endl; //0   

14.     func2();//4   

15.     system("pause");  

16. }  

17.   

18. void func1()  

19. {  

20.     static int a = 2;  

21.     a += 2;   

22.     cout<<"a = "<<a<<endl; //第一输出 4 第二次输出6   

23. }  

24.   

25. void func2()  

26. {  

27.     static int a = 2;  

28.     a += 2;   

29.     cout<<"a = "<<a<<endl; //4   

30. }  

#include <iostream>

using namespace std;

 

void func1();

void func2();

 

int  main()

{

int a = 0;

cout<<"a ="<<a<<endl; //0

func1();//4

func1();//6

cout<<"a ="<<a<<endl; //0

func2();//4

system("pause");

}

 

void func1()

{

static int a = 2;

a += 2;

cout<<"a ="<<a<<endl; //第一输出 4 第二次输出6

}

 

void func2()

{

static int a = 2;

a += 2;

cout<<"a ="<<a<<endl; //4

}

例子说明:

1main函数中的afunc1func2函数中的a空间位置不同,其值的改变会不影响

2func1被调用了两次,但是在第二次调用输出后结果为6,原因是第二次静态变量a的初始化被忽视,而在直接执行操作

3func1func2中都存在静态变量a,但是它们的空间位置也是不同的,所以值的改变也互不影响

注意

1)静态局部变量与局部变量的对比

函数中的局部变量作用域是所在函数,而且在多次函数调用时,会产生多个副本。

函数的静态局部变量作用域也是所在函数,但是在多次调用函数时,只产生一个副本。

说白了就是,静态局部变量可以使用多次,而且下次使用的时候依旧可以使用上次的值,而局部变量只能使用一次,每次使用的都是新值。

2)全局变量和全局静态变量的区别

相同点:都是在静态存储区分配空间

不同点:作用域不同

具体来说,全局变量可以在整个工程使用,即在一个文件内定义的全局变量,在另一个文件中,通过extern 全局变量名的声明,就可以使用全局变量。但是静态全局变量只能在一个cpp中使用。

3)从分配内存空间上对比(静态局部变量、局部变量、全局变量、静态全局变量)

全局变量、静态局部变量、静态全局变量都在静态存储区分配空间,而局部变量在栈分配空间

说明

1、若全局变量仅在单个文件中访问,则可以将这个变量修改为静态全局变量。

2、若全局变量仅在单个函数中使用而且下次函数的调用需要使用上一次的值,则可以将这个变量变成该函数的静态局部变量。

3、全局变量、静态局部变量、静态全局变量都存放在静态数据存储区。

静态函数

引入的原因:限定函数的作用域,让其只能在所在文件可见,不能被其它文件使用。

优点:静态函数不能被其它文件所用,在其它文件中可以定义相同名字的函数,不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名,即使同名不会发生冲突。

定义:static + 正常函数定义

说明:

1、静态函数与普通函数的对比

区别:

1)作用域不同。

静态函数只能在声明它的文件当中可见,不能被其它文件使用。普通函数可以再其他文件中使用

2)在内存中存放不同

static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝

相同点:除了上述的两个不同点外,静态函数可以看出普通的函数。

静态函数可以调用普通函数,普通函数也可以调用静态函数。

面向对象的static

一句话描述C++语言中static的作用:

静态变量:让本类的所有对象都共享。静态函数:在对象出现前做些操作

注意:

类体中的成员的声明前加上static关键字,该成员就成为了该类的静态成员。和其他成员一样,静态成员也遵守public/protected/private访问规则。

静态数据成员

为什么要引入静态数据成员:主要原因是为了取代全局变量,让类的所有对象共享数据。

什么时候使用静态数据成员:定义类的各个对象公有的数据,如统计总数,平均数

优点:可以节省内存

类中的静态变量在内存中只存储一次,供所有对象所共有的,一旦一个对象改变其值,其他对象可以直接使用改变的值,这样可以提高效率和节省内存空间。

缺点:

由于静态变量时是类的多个对象共享。则在多线程的情况下,访问静态变量我们需要加一些异步机制,防止多个线程同时修改静态变量。

语法:

定义:static + 普通数据成员定义

初始化位置:必须在类外初始化,主函数前,而不能在类中进行初始化

注意:不能在类的初始化(在.h文件中)中对静态变量进行初始化,这样会导致重复定义

初始化方式:类型类名::变量 = ;------注意这时,前面不能加static

使用:

类外使用:

      访问规则:public

       使用方式:类对象名.静态数据成员名类类型名::静态数据成员名

类中使用:

       访问规则:public/protected/private可以是任意方式定义)

      使用方式:直接使用静态成员名

举例:

[cpp]view plaincopyprint?

1.  静态成员的定义:static int x;  

2.  静态成员初始化:int A::x=1;  

静态成员的定义:static int x;

静态成员初始化:int A::x=1;

性质:

1、针对类而言,只有一个静态数据存储空间存储空间不由构造函数分配

2、类中的静态数据成员在编译时创建并初始化,在所有类的任何对象被建立前就存在。

3静态数据成员被类的所有对象所共享,包括该类派生类的对象。即派生类对象与基类对象共享基类的静态数据成员。

举例:

[cpp]view plaincopyprint?

1.  #include<iostream>  

2.  using namespace std;  

3.  class A  

4.  {  

5.  protected:  

6.      static int num;  

7.  public:  

8.      void show()  

9.      {  

10.         cout<<"num:"<<num<<endl;  

11.     }  

12. };  

13. class B:public A  

14. {  

15. public:  

16.     B()  

17.     {  

18.         num++;  

19.     }  

20.     void show()  

21.     {  

22.         cout<<"num:"<<num<<endl;  

23.     }  

24. };  

25. int A::num=0;//静态数据成员的真正定义---写成 int B::num=0; 也是可以的  

26. void main()  

27. {  

28.     A a;  

29.     a.show();  

30.     B b;  

31.     b.show();  

32.     B bb;  

33.     bb.show();  

34.     system("pause");  

35. }  

#include<iostream>

using namespace std;

class A

{

protected:

static int num;

public:

void show()

{

  cout<<"num:"<<num<<endl;

}

};

class B:public A

{

public:

B()

{

  num++;

}

void show()

{

  cout<<"num:"<<num<<endl;

}

};

int A::num=0;//静态数据成员的真正定义---写成 int B::num=0; 也是可以的

void main()

{

A a;

a.show();

B b;

b.show();

B bb;

bb.show();

system("pause");

}

4静态数据成员可以成为成员函数的确省参数,而普通数据成员则不可以

举例:

[cpp]view plaincopyprint?

1.  #include<iostream>  

2.  using namespace std;  

3.  class A  

4.  {  

5.  protected:  

6.      static int num;  

7.  public:  

8.      void count(int i=num)//通常确省参数给出的是数,这里可以使用静态成员  

9.      {  

10.   

11.     }  

12. };  

13. int A::num=0;  

14. void main()  

15. {  

16.     A a;  

17.     system("pause");  

18. }  

#include<iostream>

using namespace std;

class A

{

protected:

static int num;

public:

void count(int i=num)//通常确省参数给出的是数,这里可以使用静态成员

{

 

}

};

int A::num=0;

void main()

{

A a;

system("pause");

}

5静态数据成员的类型可以是自己类的类型,而普通数据成员则不可以。普通数据成员的只能声明为 所属类类型的指针或引用。

举例:

[cpp]view plaincopyprint?

1.  class A  

2.  {  

3.  protected:  

4.      A aaa;//错误,普通数据成员不可以使用自己类作为类型名  

5.      A *b; //正确,普通成员可以使用自己类的指针  

6.      static A aa;//正确,静态成员函数可以使用自己类作为类型名  

7.  };  

class A

{

protected:

A aaa;//错误,普通数据成员不可以使用自己类作为类型名

A *b; //正确,普通成员可以使用自己类的指针

static A aa;//正确,静态成员函数可以使用自己类作为类型名

};

注意:

1、类中静态成员变量类中非静态成员变量的对比

(1)  对象是否拥有

对于非静态数据成员,每个类对象都有自己的拷贝,都拥有一份自己的变量。

对于静态数据成员,静态数据成员是该类的所有对象所共有的。即无论这个类的对象被定义了多少个,静态数据成员在程序中也只有一份拷贝,只分配一次内存,由该类型的所有对象共享访问。

(2) 内存的分配

静态数据成员存储在全局数据区。不能在类声明中定义。

内部成员(非全局,非静态,非const变量)存储在栈中。

2、类中静态成员变量与全局变量相比

(1) 静态数据成员没有进入程序的全局名字空间,因此不存在与程序中其它全局名字冲突的可能性;

(2 )可以实现信息隐藏。静态数据成员可以是private成员,而全局变量不能;

静态成员函数

为什么要引入静态数据成员:主要原因是为了取代全局函数,能在调用构造函数前(未初始化类之前)调用,通常CALLBACK函数中会用得到。

什么时候使用静态数据成员:

(1) 为了访问全局变量或类的静态数据成员

(2) 要调用一个函数,该函数是在构造对象之前使用,但是不能使用全局函数(不符合OO实现),而且类中非静态函数的调用需要使用对象,这时候需要使用静态成员函数。

语法:

定义:static + 普通成员函数定义

定义位置:可以在类内,也可以自类外。类外定义是不加static

使用:

类外使用:

      访问规则:public

       使用方式:类名::静态公有成员函数名(实参表);

       注意:和普通的成员变量一样,如果是私有或受保护的,不能在类外直接使用

类中使用:

       访问规则:public/protected/private可以是任意方式定义)

      使用方式:直接使用静态成员函数

性质:

1、没有this指针,不可以直接访问类中非非静态成员函数,常常用于访问类的静态数据和静态成员函数

2、只属于一个类,可以再多个对象间共享。

3一般情况下,静态成员函数不访问非静态成员函数,如果确实需要,则需要传入参数通过对象名访问。

4、静态成员函数不可以同时声明为virtualconstvolatile函数。

5、静态成员函数不能是虚函数

举例:

[cpp]view plaincopyprint?

1.  class   A  

2.  {     

3.      virtual   static   void   a();//错误     

4.      static   void   b()   const;//错误     

5.      static   void   c()   volatile;//错误     

6.  };   

class   A

{  

virtual   static  void   a();//错误  

static   void  b()   const;//错误  

static   void  c()   volatile;//错误  

};

举例:

[cpp]view plaincopyprint?

1.  #include<iostream>  

2.  using namespace std;  

3.  class A  

4.  {  

5.  private:   

6.      int i;  

7.      static int j;  

8.  public:  

9.      A(int i=1)  

10.     {  

11.         this->i=i;  

12.     }  

13.     static void show();  

14.     void show(const A&a);  

15. };  

16. int A::j=0; //类外初始化静态变量时,不要加static   

17. void A::show()//类外定义函数体时,不要加static ----静态函数访问静态成员  

18. {  

19.     cout<<j<<endl;  

20. }  

21. void A::show(const A&a)//静态函数访问非静态成员,必须加参数  

22. {  

23.     cout<<a.i<<endl;  

24.     cout<<j<<endl;  

25. }  

26. void main()  

27. {  

28.     A a;  

29.     a.show();  

30.     a.show(a);  

31.     system("pause");  

32. }  

#include<iostream>

using namespace std;

class A

{

private:

int i;

static int j;

public:

A(int i=1)

{

this->i=i;

}

static void show();

void show(const A&a);

};

int A::j=0; //类外初始化静态变量时,不要加static

void A::show()//类外定义函数体时,不要加static ----静态函数访问静态成员

{

cout<<j<<endl;

}

void A::show(const A&a)//静态函数访问非静态成员,必须加参数

{

cout<<a.i<<endl;

cout<<j<<endl;

}

void main()

{

A a;

a.show();

a.show(a);

system("pause");

}

 注意:

1、类的静态成员函数不能访问非静态成员,但是非静态成员可以访问静态成员。

2、出现在类体外的函数定义不能指定关键字static

3、静态成员之间可以相互访问,即静态成员函数访问静态数据成员和访问静态成员函数;

4、非静态成员函数可以任意地访问静态成员函数和静态数据成员;

5、静态成员函数不能访问非静态成员函数和非静态数据成员;

6、调用静态成员函数,可以用对象调用,也可以通过类调用

说明:

1staticconst的对比

变量的对比:

static:是为限定变量的作用域的,值是可以改变的

const:是表示变量是常量,是不可变的,提高程序健壮性。

函数的对比:

static

修饰C中的函数:是为了限定作用域仅在本文件,其他文件不可用

修饰C++中的函数:是为了在对象创建之前做一些操作

const

表示函数中的变量不可修改

 

 

 

 

 

 

14.C++_Const的使用

分类:C++高质量C++编程2011-08-04 12:35280人阅读评论(0)收藏举报

c++fun编译器system存储

目录(?)[+]

1.一用const 修饰函数的参数

2.二用const 修饰函数的返回值

3.关于用const 修饰函数的返回值时的几个问题

4.三const 成员函数

5.define的优点和缺点

6.const的优点

7.inline的优点

8.指针和const连用

const的作用:表示被修饰变量受到强制保护,可以预防意外的变动,能提高程序的健壮性。

const的用处:修饰函数的参数、返回值、函数的定义体,变量等,其中前面三个是其魅力所在。

根据函数的组成,可以把const的作用分成三部分:const修饰函数的参数,const修饰函数体,const修饰函数返回值。

一、用const修饰函数的参数

什么时候使用const

如果在函数体中只是对参数读取数据,而不对参数进行修改,则该参数要使用const修饰。

怎么使用const

(1) 对于非内部数据类型的参数而言,传递参数常常使用引用传递+ const修饰

举例:

[cpp]view plaincopyprint?

1.  void Func(const A &a)  

void Func(const A &a)

原因:不加引用:传参时使用采用值传递而会产生A 类型的临时对象,而临时对象的构造、复制、析构过程都将消耗时间,效率比较底。而使用引用则就不会产生临时变量,而是原对象的一个别名,可直接使用。不加const:引用传递有可能改变参数a,这是我们不期望的。解决这个问题很容易,加const修饰即可。

(2) 对于内部数据类型的输入参数而言:不要将值传递的方式改为const 引用传递。

举例:

[cpp]view plaincopyprint?

1.  void Func(int x) 不应该改为 void Func(const int &x)  

void Func(int x) 不应该改为 void Func(const int &x)

原因:因为内部数据类型的参数不存在构造、析构的过程,而复制也非常快,值传递引用传递的效率几乎相当,没有必要使用引用传递。否则既达不到提高效率的目的,又降低了函数的可理解性

二、用const修饰函数的返回值

作用:使函数返回值不会被改变

什么时候返回值使用const:

1、如果函数返回值采用指针类型,则可以使用const,表示函数返回值(即指针)的内容不能被修改

2、如果函数返回值不是指针类型,则分情况讨论是否能使用const

1)如果返回值是内部数据类型,则没必要使用const修饰

2)如果返回值是非内部数据类型,而且使用值传递,则没必要使用const修饰

3)如果返回值是非内部数据类型,而且使用引用传递,而且在以后的程序中值会发生改变,则不可以使用const修饰

4)如果返回值是非内部数据类型,而且使用引用传递,而且在以后的程序中值不发生改变,则可以使用const修饰(用的很少)

具体说明:

1、如果函数返回值采用指针类型,则可以使用const,表示函数返回值(即指针)的内容不能被修改,并且该返回值只能被赋给加const修饰的同类型指针。

举例:

[cpp]view plaincopyprint?

1.  函数声明:const int* a()  

2.  函数调用:const int *p = a();  

3.  函数说明:我们可以把a()看作成一个变量,即指针指向内容不可变。  

函数声明:const int* a()

函数调用:const int *p = a();

函数说明:我们可以把a()看作成一个变量,即指针指向内容不可变。

2、如果函数返回值不是指针类型,则分情况讨论是否能使用const

(1) 如果返回值是内部数据类型,则没必要使用const修饰

(2) 如果返回值是非内部数据类型,而且使用值传递,则没必要使用const修饰

原因:函数返回值采用值传递方式,由于函数会把返回值复制到外部临时的存储单元中,这时const修饰的是临时变量,是不会得到改变,没有任何价值

举例:

[cpp]view plaincopyprint?

1.  不要把函数int GetInt() 写成const int GetInt()  

2.  不要把函数A GetA()     写成const A GetA(),A为用户自定义类型  

不要把函数int GetInt() 写成const int GetInt()

不要把函数A GetA()     写成const A GetA(),A为用户自定义类型

(3) 如果返回值是非内部数据类型,而且使用引用传递,而且在以后的程序中值会发生改变,则不可以使用const修饰

(4) 如果返回值是非内部数据类型,而且使用引用传递,而且在以后的程序中值不发生改变,则可以使用const修饰(用的很少)

举例:

[cpp]view plaincopyprint?

1.  const A& aa = fuc(a); //之后aa的只就不能改变了,而且aa也不能调用非const函数。  

2.  //aa.m_x = 10;//错误  

3.  //aa.show();//错误,show为非const函数。  

const A& aa = fuc(a); //之后aa的只就不能改变了,而且aa也不能调用非const函数。

//aa.m_x = 10;//错误

//aa.show();//错误,show为非const函数。

说明:

(1)一旦把返回值定为const变量,而且还使用const变量接收,那么此时变量完全被束缚住手脚了,各种操作无能啊。

(2)函数返回值采用引用传递的场合并不多,这种方式一般只出现在类的赋值函数中,目的是为了实现链式表达

举例:

[cpp]view plaincopyprint?

1.  class A  

2.  {  

3.      A & operate = (const A &other); // 赋值函数  

4.  };  

5.  A a, b, c; // a, b, c 的对象  

6.    

7.  a = b = c; // 正常的链式赋值  

class A

{

  A & operate = (const A&other); // 赋值函数

};

A a, b, c; // a, b, c 为A 的对象

a = b = c; // 正常的链式赋值

关于用const修饰函数的返回值时的几个问题:

举例说明

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  using namespace std;  

3.  class A  

4.  {  

5.  public:  

6.      A(int x)  

7.      {  

8.          m_x = x;  

9.          m_pY = NULL;  

10.     }  

11.   

12.     A(const A& other)  

13.     {  

14.         cout<<"A(const A& other)"<<endl;  

15.         m_x = other.m_x;  

16.         m_pY = NULL;  

17.     }  

18.     void show()  

19.     {  

20.         cout<<"m_x = "<<m_x<<endl;  

21.     }  

22.   

23.     const int& GetX()  

24.     {  

25.         return m_x;  

26.     }  

27.   

28.     const int* GetY()  

29.     {  

30.         return m_pY;  

31.     }  

32.   

33. public:  

34.     int m_x;  

35.     int* m_pY;  

36. };  

37.   

38. const A& func(A& a)  

39. {  

40.     return a;  

41. }  

42.   

43. int main()  

44. {  

45.     A a(1);  

46.     a.show();  

47.   

48.     //返回值是一个整形的引用  

49.     int x = a.GetX(); //正确  

50.     cout<<x<<endl;   

51.     x = 10;  

52.     cout<<x<<endl;  

53.   

54.     //返回值是一个整形的引用  

55.     //int* pY = a.GetY(); //为什么是错误的  

56.   

57.     //返回值是一个类的引用  

58.     A aa = func(a); //正确  

59.     aa.m_x = 10;   //正确  

60.     aa.show();       

61.   

62.     //int& xx = a.GetX(); //错误  

63.     //A& aa = fuc(a);//错误  

64.       

65.     //函数返回值使用const时,函数接受值的正确用法  

66.     const int& xx = a.GetX(); //正确  

67.     const A& aaa = func(a); //正确  

68.     const int* pYY = a.GetY();//正确  

69.   

70.     system("pause");  

71.     return 1;  

72. }  

#include <iostream>

using namespace std;

class A

{

public:

A(int x)

{

   m_x = x;

   m_pY = NULL;

}

 

A(const A& other)

{

   cout<<"A(const A&other)"<<endl;

   m_x = other.m_x;

   m_pY = NULL;

}

void show()

{

   cout<<"m_x ="<<m_x<<endl;

}

 

const int& GetX()

{

   return m_x;

}

 

const int* GetY()

{

   return m_pY;

}

 

public:

int m_x;

int* m_pY;

};

 

const A& func(A& a)

{

return a;

}

 

int main()

{

A a(1);

a.show();

 

//返回值是一个整形的引用

int x = a.GetX(); //正确

cout<<x<<endl;

x = 10;

cout<<x<<endl;

 

//返回值是一个整形的引用

//int* pY = a.GetY(); //为什么是错误的

 

//返回值是一个类的引用

A aa = func(a); //正确

aa.m_x = 10;   //正确

aa.show();    

 

//int& xx = a.GetX(); //错误

//A& aa = fuc(a);//错误

//函数返回值使用const时,函数接受值的正确用法

const int& xx = a.GetX(); //正确

const A& aaa = func(a); //正确

const int* pYY = a.GetY();//正确

 

system("pause");

return 1;

}

问题(1)为什么返回值是const类型,但是(1)(2)接受值不是const,结果仍然对,(3)却错了

[cpp]view plaincopyprint?

1.  (1) A aa = func(a); //正确  

2.  (2) int x = a.GetX(); //正确    

3.  (3) int* pY = a.GetY(); //错误的  

(1) A aa = func(a); //正确

(2) int x = a.GetX(); //正确

(3) int* pY = a.GetY(); //错误的

说明:

为什么返回值是const类型,但是接受值可以不是const类型?

虽然函数funcGetX都返回const引用,但是接受值使用的非引用变量,编译器为aax会申请新的空间,之后系统直接拿返回值的值初始化aax了。由于aax和函数返回值的空间不同,对改变aax的值不会影响const修饰的那个变量,即对函数返回值的空间的值没有威胁,所以是正确的。

为什么(3)是错误的?

虽然系统也为pY申请了空间,但是由于pY是指针变量,它指向的地址和函数返回值指向的内容是一样的,因此有可能改变函数返回值指向的内容,而由于const的存在,编译器是不允许这个操作发生的,因此(3)是错误的。

说明:

[cpp]view plaincopyprint?

1.  A aa = func(a); //正确  

2.  int x = a.GetX(); //正确   

A aa = func(a); //正确

int x = a.GetX(); //正确

上述写法是不能反应函数返回值使用const的作用的,它只是借用函数返回值的值而已。

[cpp]view plaincopyprint?

1.  const int& xx = a.GetX(); //正确写法  

2.  const A& aaa = func(a); //正确写法  

3.  const int* pYY = a.GetY();//正确写法  

const int& xx = a.GetX(); //正确写法

const A& aaa = func(a); //正确写法

const int* pYY = a.GetY();//正确写法

上述写法才是正确写法,即函数返回值使用const时,函数接受值的正确用法。

即我们使用const修饰函数返回值时,应该主动定义一个const对象引用或者变量引用来接收函数返回值,这样使用const修饰函数返回值才有意义。

其他:

const对象只能调用const函数

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  using namespace std;  

3.  class MyClass  

4.  {  

5.  public:  

6.      int m_nTemp;  

7.      void fun1() const  

8.      {  

9.      }  

10.     void fun2()  

11.     {  

12.     }  

13.     const MyClass& fun6(MyClass& aa)  

14.     {  

15.         return aa;  

16.     }  

17. };  

18.   

19. void main()  

20. {  

21.     MyClass aa;  

22.     const MyClass bb=aa.fun6(aa);//能使用返回const对象的函数为const对象赋值  

23.     bb.fun1();  

24. //  bb.fun2(); //报错,因为bbconst对象,只能调用const函数  

25. //  bb.m_nTemp=22; //报错  

26.     system("pause");  

27. }  

#include <iostream>

using namespace std;

class MyClass

{

public:

int m_nTemp;

void fun1() const

{

}

void fun2()

{

}

const MyClass& fun6(MyClass&aa)

{

return aa;

}

};

 

void main()

{

MyClass aa;

const MyClass bb=aa.fun6(aa);//能使用返回const对象的函数为const对象赋值

bb.fun1();

// bb.fun2(); //报错,因为bb为const对象,只能调用const函数

// bb.m_nTemp=22; //报错

system("pause");

}

 三、const 成员函数

作用:在函数体中不修改数据成员的值

什么时候把函数设置为const成员函数:任何不会修改数据成员的函数都应该声明为const 类型。

说明:

(1) 如果在编写const 成员函数时,不慎修改了数据成员

(2) 调用了其它非const 成员函数,编译器将指出错误,这无疑会提高程序的健壮性。

程序:

[cpp]view plaincopyprint?

1.  class Stack  

2.  {  

3.  public:  

4.      void Push(int elem);  

5.      int Pop(void);  

6.      int GetCount(voidconst// const 成员函数  

7.  private:  

8.      int m_num;  

9.      int m_data[100];  

10. };  

11. int Stack::GetCount(voidconst  

12. {  

13.     ++ m_num; // 编译错误,企图修改数据成员m_num   

14.     Pop(); // 编译错误,企图调用非const 函数  

15.     return m_num;  

16. }  

class Stack

{

public:

void Push(int elem);

int Pop(void);

int GetCount(void) const; // const 成员函数

private:

int m_num;

int m_data[100];

};

int Stack::GetCount(void) const

{

++ m_num; // 编译错误,企图修改数据成员m_num

Pop(); // 编译错误,企图调用非const函数

return m_num;

}

说明:

1、#define的优点和缺点

预处理语句#define的优点:

1)见名知意且方便程序的修改

2)提高程序的运行效率

具体来说:

1)使用一个有意义的名字代替数字,可以避免避免意义模糊的数字出现,而且数字改变时只需要改变变量的值即可。

2define符号的替换与编译器无关。程序把符号换成数字是在预处理阶段完成,程序不必把符号放入符号表中,这样就没有了存储与读内存的操作,使得它的效率也很高。

3)当使用带参数的宏定义完成函数调用的功能时,并没有进行函数调用,而减少系统开销,提高运行效率。

预处理语句#define的缺点:

1)缺乏类型的检测机制 

2)存在边际效应

具体来说:

1)预处理语句仅仅只是简单值替代,缺乏类型的检测机制,从而可能成为引发一系列错误的隐患。 

2)预处理语句在字符替换可能会产生意料不到的错误(边际效应)

注意:在C++中,使用const inline 可以替代define的作用。

2、const的优点

const 推出的初始目的,正是为了取代预编译指令,消除它的缺点,同时继承它的优点。

const 的优点:

1const定义常量,具有不可变性。

2)见名知意且方便程序的修改

3)提高程序的运行效率。

4)存在类型检测

具体来说:

1const 修饰的常量值,具有不可变性,这是它能取代预定义语句的基础。

2)使用一个有意义的名字代替数字,可以避免避免意义模糊的数字出现,而且数字改变时只需要改变变量的值即可。

3const常量的替换与编译器有关。C++的编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高,同时,这也是它取代预定义语句的重要基础。

4const定义也像一个普通的变量定义一样,它会由编译器对它进行类型的检测,消除了预定义语句的隐患。

inline的优点:

inline 推出的目的是为了取代C中表达式形式的宏定义,它消除了它的缺点,同时又很好地继承了它的优点。

inline的优点:

1)提高程序的运行效率

2)存在安全检查

3)具有与类的成员函数的相同的性质

举例来说:

1 inline 定义的类的内联函数,函数的代码被放入符号表中,在使用时直接进行替换,(像宏一样展开),没有了调用的开销,效率也很高。
2
类的内联函数是一个真正的函数,编译器在调用一个内联函数时,会首先检查它的参数的类型,保证调用正确。然后进行一系列的相关检查,就像对待任何一个真正的函数一样。这样就消除了它的隐患和局限性。
3
inline 可以作为某个类的成员函数,当然就可以在其中使用所在类的保护成员及私有成员。

2、指针和const连用:

作用:const修饰的是它右边的内容

1

[cpp]view plaincopyprint?

1.  charconst pContent;  

2.  表示:指针本身内容是常量,不可变  

3.  说明:const修饰pContent,表示变量的内容不变,即指针指向不变  

char* const pContent;

表示:指针本身内容是常量,不可变

说明:const修饰pContent,表示变量的内容不变,即指针指向不变

2

[cpp]view plaincopyprint?

1.  const char* pContent;   

2.  表示:指针所指向的内容是常量不可变  

3.  说明:const修饰char*,表示指针指向的内容不变,即指针指向内容不变  

const char* pContent;

表示:指针所指向的内容是常量不可变

说明:const修饰char*,表示指针指向的内容不变,即指针指向内容不变

3

[cpp]view plaincopyprint?

1.  const charconst pContent;   

2.  表示:两者都不可变  

3.  说明:pContent指向不变,指向的内容也不变   

 

 

 

 

 

 

15.C++_Const的使用(2)

分类:C++2011-08-04 15:16251人阅读评论(0)收藏举报

c++classsystem编译器function

3、与类有关的const

1const修饰成员变量:成员常量不能被修改初始化:

方法一:只能在构造函数的初始化列表中赋值----比较常用

[cpp]view plaincopyprint?

1.  class A      

2.  {        

3.  private:  

4.      const int n;   //成员常量不能被修改       

5.  public:   

6.      A(int x): n(x) //只能在初始化列表中赋值  

7.      { } ;     

8.  }  

class A   

{     

private:

const int n;   //成员常量不能被修改   

public:

A(int x): n(x) //只能在初始化列表中赋值

{ } ;  

}

方法二:把变量static const 并用,在外部初始化

[cpp]view plaincopyprint?

1.  class A  

2.  {  

3.  public:  

4.      A()   

5.      {}   

6.  private:   

7.      static const int i; //注意必须是静态的!  

8.  };   

9.  const int A::i=3;  

class A

{

public:

A()

{}

private:

static const int i; //注意必须是静态的!

};

const int A::i=3;

注意:为什么const成员不能在构造函数体中进行初始化?

原因:在构造函数中那叫赋值,因为定义在类中已经定义好了,之后只需要在构造函数赋值就可以了,这是定义和赋值时分开的。但是,const变量是必须在定义的时候初始

的,因此只能使用初始化列表中进行,表示是在定义时就能初始化。

2const修饰成员函数:常函数,函数体为常量,不能改变其他非const成员的值

使用时间:任何不会修改数据成员(即函数中的变量)的函数都应该声明为const 类型

const位置:一般写在函数的尾部来const修饰的成员函数

主要作用:只能被const类对象、指针、引用调用

[cpp]view plaincopyprint?

1.  class A  

2.  {   

3.      void function()const//常成员函数它不改变对象的成员变量也不能调用类中任何非const成员函数。  

4.  }  

class A

{

void function()const; //常成员函数, 它不改变对象的成员变量. 也不能调用类中任何非const成员函数。

}

说明:

注意一:在const成员函数内部不允许修改数据成员。反过来,将修改数据成员的成员函数声明为const将导致编译错误。

注意二:const成员函数不允许调用非const成员函数,而只能使用const成员

               const成员函数可以调用const成员函数。

注意三:可以对const成员函数进行非const版本的重载

注意四:构造函数和析构函数不允许声明为const类型。构造函数既可以初始化const对象,也可以初始化非const对象。析构函数同理。

注意五:建议将不修改数据成员的成员函数都声明为const当编写这种成员函数时,若不经意修改了数据成员,编译器将产生错误信息。

代码:

const重载

[cpp]view plaincopyprint?

1.  #include<iostream>  

2.  using namespace std;  

3.  class A  

4.  {  

5.  private:  

6.      int a;  

7.      const int b;  

8.  public:  

9.      A(int i,int j):b(j)  

10.     {  

11.         a=i;  

12.     }  

13.     void print()  

14.     {  

15.         cout<<"const函数"<<endl;  

16.         cout<<a<<b<<endl;  

17.     }  

18.     void print ()const  

19.     {  

20.         cout<<"const函数"<<endl;  

21.         cout<<a<<b<<endl;  

22.     }  

23. };  

24. void main()  

25. {  

26.     A a(1,2);  

27.     a.print(); //a为普通的对象,会调用普通的成员函数  

28.     const A aa(3,4);  

29.     aa.print();//aa为常对象,会调用常成员函数  

30.     system("pause");  

31. }  

#include<iostream>

using namespace std;

class A

{

private:

int a;

const int b;

public:

A(int i,int j):b(j)

{

   a=i;

}

void print()

{

   cout<<"非const函数"<<endl;

   cout<<a<<b<<endl;

}

void print ()const

{

   cout<<"const函数"<<endl;

   cout<<a<<b<<endl;

}

};

void main()

{

A a(1,2);

a.print(); //a为普通的对象,会调用普通的成员函数

const A aa(3,4);

aa.print();//aa为常对象,会调用常成员函数

system("pause");

}

注意四:同一成员函数名,若只有const型,则非const对象可以调用该成员函数,即调用const成员函数。 <见上面的例子const重载>

注意五:同一成员函数名,若既有非const型,又有const(重载),则非const对象只调用非const成员函数。<见上面的例子const重载>

常对象和常成员函数的调用

[cpp]view plaincopyprint?

1.  #include<iostream>  

2.  using namespace std;  

3.  class A  

4.  {  

5.  private:  

6.      int a;  

7.      const int b;  

8.  public:  

9.      A(int i,int j):b(j)  

10.     {  

11.         a=i;  

12.     }  

13.     void print ()const  

14.     {  

15. //      a=10;//错误,常函数中不能更新数据成员  

16.         cout<<"const函数"<<endl;  

17.         cout<<a<<b<<endl;  

18.     }  

19.     void set(int i,int j)  

20.     {  

21.         a=i;  

22. //      b=j;//错误,const成员不能改变  

23.     }  

24. };  

25. void main()  

26. {  

27.     const A a(3,4);  

28. //  a.set(5,6);//错误,常对象不能调用非常成员,常成员函数是常对象唯一对外出口  

29.     a.print(); //正确,常对象只能调用常函数。  

30.     system("pause");  

31. }  

#include<iostream>

using namespace std;

class A

{

private:

int a;

const int b;

public:

A(int i,int j):b(j)

{

a=i;

}

void print ()const

{

// a=10;//错误,常函数中不能更新数据成员

cout<<"const函数"<<endl;

cout<<a<<b<<endl;

}

void set(int i,int j)

{

a=i;

// b=j;//错误,const成员不能改变

}

};

void main()

{

const A a(3,4);

// a.set(5,6);//错误,常对象不能调用非常成员,常成员函数是常对象唯一对外出口

a.print(); //正确,常对象只能调用常函数。

system("pause");

}

 

3const修饰类对象、对象指针、对象引用:常量对象(对象指针和对象引用),任何成员都不能被修改,只能调用常成员函数。

注意一:const对象在定义后,其值不可被改变。

注意二:const对象不允许进行成员函数的调用,即使是不修改对象的成员函数也不行。除非成员函数本身也声明为const      

               即:const对象只可调用const成员函数,不能调用任何非const成员函数(构造与析构函数除外)      

              原因:任何非const成员函数会有修改成员变量的企图。

注意三:非const对象既可调用const成员函数,可调用非const成员函数。

注意四:同一成员函数名,若只有const型,则非const对象可以调用该成员函数,即调用const成员函数。 <见上面的例子const重载>

注意五:同一成员函数名,若既有非const型,又有const(重载),则非const对象只调用非const成员函数。<见上面的例子const重载>

[cpp]view plaincopyprint?

1.  class A  

2.  {  

3.      void func1();  

4.      void func2() const;  

5.  }   

6.  const A aObj;  

7.  aObj.func1(); //错误,常函数只能调用常成员函数  

8.  aObj.func2(); 正确  

9.  const A* aObj = new A();  

10. aObj-> func1();//错误,常指针只能调用常成员函数  

11. aObj-> func2(); 正确   

class A

{

void func1();

void func2() const;

}

const A aObj;

aObj.func1(); //错误,常函数只能调用常成员函数

aObj.func2(); 正确

const A* aObj = new A();

aObj-> func1();//错误,常指针只能调用常成员函数

aObj-> func2(); 正确

注意:
1const对象只能访问const成员函数, const对象可以访问任意的成员函数=const成员函数+const成员函数.

2const对象的成员是不可修改的,然而const对象通过指针维护的对象却是可以修改的.

3、常量指针被转化成非常量指针,并且仍然指向原来的对象;

     常量引用被转换成非常量引用,并且仍然指向原来的对象;       

     常量对象被转换成非常量对象

4、将Const类型转化为非Const类型的方法:略

const使用建议:

1、在参数中使用const应该使用引用或指针,而不是一般的对象实例;

2const在成员函数中的三种用法(参数、返回值、函数)要很好的使用

      1)参数:const A& a:只要函数体内是只读,不会修改参数,又想提高效率,则常常使用const &

      2)返回值:不要轻易的将函数的返回值类型定为const,除了重载操作符外一般不要将返回值类型定为对某个对象的const引用;

      3)常函数:任何不会修改数据成员的函数都应该声明为const 类型。

 

 

 

 

 

 

16.C++_引用

分类:C++2011-08-02 22:14218人阅读评论(0)收藏举报

c++stringc存储

引用的应用:

常引用:

语法:

[cpp]view plaincopyprint?

1.  const 类型 &引用名=目标变量名;  

const 类型 &引用名=目标变量名;

作用:防止通过引用对目标变量进行修改,达到了引用的安全性

注意:是防止通过引用进行修改,但可以直接对变量进行修改

使用范围:只要一个参数声明过来时只读的,都可以使用。常见有函数传参时,如果不想让参数被改变,则声明为常引用

举例1

[cpp]view plaincopyprint?

1.  #include<iostream>         

2.  using namespace std;  

3.  void main()  

4.  {  

5.      int a=0;  

6.      const int& b=a;  

7.      a=1;//正确,不能通过引用进行修改,但是可以直接对它自己进行修改  

8.      b=1;//错误,ba的引用,使用const的作用就是防止通过引用(b),对a进行修改  

9.      cout<<a<<b<<endl;  

10.     system("pause");  

11. }  

#include<iostream>      

using namespace std;

void main()

{

int a=0;

const int& b=a;

a=1;//正确,不能通过引用进行修改,但是可以直接对它自己进行修改

b=1;//错误,b为a的引用,使用const的作用就是防止通过引用(b),对a进行修改

cout<<a<<b<<endl;

system("pause");

}

举例2

[cpp]view plaincopyprint?

1.  void ss(const int& a)  

2.  {  

3.         a=1;//错误,不能通过a对它进行改变  

4.  }  

void ss(const int& a)

{

       a=1;//错误,不能通过a对它进行改变

}

说明:引用型参数应该在能被定义为const的情况下,尽量定义为const

举例3

[cpp]view plaincopyprint?

1.  前提:  

2.  string foo( );  

3.  void bar(string & s);   

4.  调用:  

5.  bar(foo( ));     

6.  bar("hello world");  

前提:

string foo( );

void bar(string & s);

调用:

bar(foo( ));  

bar("hello world");

错误原因:试图将一个const类型的对象转换为非const类型是非法的。

因为:foo( )"hello world"串都会产生一个临时对象,而在C++中,这些临时对象都是const类型的。

返回函数引用:函数返回一个变量,可以做左值和右值

语法:

[cpp]view plaincopyprint?

1.  类型函数名(形参列表及类型说明)  

2.  {  

3.      //函数体  

4.  }  

类型& 函数名(形参列表及类型说明)

{

//函数体

}

优点:在内存中不产生被返回值的副本,而会直接得到返回值的存储单元,既可以做左值,也可以做右值。

举例:

返回函数引用:

[cpp]view plaincopyprint?

1.  #include<iostream>         

2.  using namespace std;  

3.  int& B(int &n);  

4.  int main()  

5.  {  

6.      int a = 10;  

7.      int& b = B(a); //a传入函数B中的n,之后返回nb,这时b就是n也是a的引用,都指向一个空间  

8.      cout << b << endl;  

9.      cout << a << endl;  

10.     system("pause");  

11. }  

12.   

13. int& B(int &n)  

14. {  

15.     n++;  

16.     return n;  

17. }  

#include<iostream>      

using namespace std;

int& B(int &n);

int main()

{

int a = 10;

int& b = B(a); //把a传入函数B中的n,之后返回n给b,这时b就是n也是a的引用,都指向一个空间

cout << b << endl;

cout << a << endl;

system("pause");

}

 

int& B(int &n)

{

n++;

return n;

}

分析:return n; 就相当于是 return a;,返回到 b 的就是 a 的引用, b 就是 an b 的操作会直接影响 a, 因为它们是同一個东西.

想对的例子:

[cpp]view plaincopyprint?

1.  int A(int n) //函数返回的是一个值,而非变量  

2.  {  

3.      return n;  

4.  }  

int A(int n) //函数返回的是一个值,而非变量

{

return n;

}

说明:int c=A(a);这样调用,na不是同一个空间,而是a的一个复制体,最终返回的只是个值的副本(这个副本不是nn已经释放),没有引用任何东西,c得到的仅仅是

返回的值,而不是一个空间,所以以后对c的操作也没对a有任何影响。

返回函数引用常用的使用方法:把需要返回的参数,以引用或指针方式当作函数的参数,传入函数中。

举例:

函数返回值使用引用的方式传入:

[cpp]view plaincopyprint?

1.  int& abc(int a, int b, int c, int& result)  

2.  {  

3.      result = a + b + c;  

4.      return result;  

5.  }  

int& abc(int a, int b, int c, int& result)

{

result = a + b + c;

return result;

}

函数返回值使用指针的方式传入:

[cpp]view plaincopyprint?

1.  int& abc(int a, int b, int c, int *result)  

2.  {  

3.      *result = a + b + c;  

4.      return *result;  

5.  }  

int& abc(int a, int b, int c, int *result)

{

*result = a + b + c;

return *result;

}

这里的变量result不是一个临时变量,在函数外有效,因此,返回函数引用是可以的

错误的方式:

[cpp]view plaincopyprint?

1.  int& abc(int a, int b, int c)  

2.  {  

3.      return a + b + c;  

4.  }  

int& abc(int a, int b, int c)

{

return a + b + c;

}

这里a+b+c仅仅代表的是一个值,而不是一个可以引用的内存单元,而返回函数要求的是可以引用的变量,因此是错误的

注意:

1、不能返回局部变量的引用。

原因:局部变量会在函数返回后被销毁,被返回的引用就成为了"无所指"的引用,程序会进入未知状态。

2、不能返回函数内部new分配的内存的引用。

原因:虽然new申请的空间是被显式释放的,这就不存在局部空间被销毁的情况,但是被函数返回的引用只是作为一个临时变量,而没有赋予实际的变量,那么

这个引用所指向空间就无法释放,造成内存泄漏。

3、返回函数引用,可以和某些运算符重载结合,如超载输入输出运算符>><<

原因:要支持运算符的连用,这时返回函数引用可以使赋值操作符的返回值必须是一个左值,以便可以被继续赋值。

说明:

1、引用的目的主要用于在函数参数传递中,解决大块数据或对象的传递效率和空间不如意的问题,而单纯给某个变量取个别名是毫无意义的。

2、用引用传递函数的参数,能保证参数传递中不产生副本,提高传递的效率,且通过const的使用,保证了引用传递的安全性。

3、常见的函数引用的时候:

流操作符<<>>的返回值:

[cpp]view plaincopyprint?

1.  friend ostream& operator<<(ostream& cout,const Point& p);//使用友元函数重载<<输出运算符     

2.  friend istream& operator>>(istream& cin,Point& p);//使用友元函数重载>>输出运算符    

friend ostream& operator<<(ostream& cout,const Point&p);//使用友元函数重载<<输出运算符  

friend istream& operator>>(istream& cin,Point& p);//使用友元函数重载>>输出运算符 

赋值操作符=的返回值和参数:

[cpp]view plaincopyprint?

1.  Point& Point::operator=(const Point&p)//重载赋值运算符  

Point& Point::operator=(const Point&p)//重载赋值运算符

拷贝构造函数的参数:

[cpp]view plaincopyprint?

1.  Point::Point(const  Point& p)//拷贝构造函数    

 

 

 

 

17.C++_多继承与虚基类

分类:C++2011-08-02 13:25859人阅读评论(4)收藏举报

c++classsystemc

多继承的定义:派生类的基类大于一个

语法:

[cpp]view plaincopyprint?

1.  class  派生类名:继承方式基类名1,继承方式基类名2...  

2.  {  

3.      <派生类新定义成员>  

4.  };  

class  派生类名:继承方式1 基类名1,继承方式2 基类名2...

{

<派生类新定义成员>

};

多重继承与构造函数的关系:

多重继承时构造函数的作用:

1)初始化派生类(自己)

2)调用该派生类所有基类构造函数,并且为所有基类传参(参数个数必须包含所有基类所需参数)

构造函数语法:

[cpp]view plaincopyprint?

1.  派生类构造函数名(总参数表列): 基类1构造函数(参数表列), 基类2构造函数(参数表列), 基类3构造函数(参数表列)  

2.  {  

3.      //派生类中新增数成员据成员初始化语句  

4.  }  

派生类构造函数名(总参数表列): 基类1构造函数(参数表列), 基类2构造函数(参数表列), 基类3构造函数(参数表列)

{

//派生类中新增数成员据成员初始化语句

}

说明:派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用;如果未列出,则表示使用该虚基类的缺省构造函数。

具体点来说:初始化列表中要包括对直接基类 + 虚基类进行调用。

构造函数的执行次序(不含虚基类)

(1)基类:依派生的次序决定,与构造函数中书写顺序无关

(2)子对象的构造函数

(3)派生类的构造函数

析构函数的执行次序:和上述执行顺序相反

注意:

1)析构函数能继承;

2)派生类中要定义自己的析构函数释放在派生中新增的成员;

3)从基类中继承的成员释放,可以通过基类的析构函数实现;

4)激活析构函数的顺序与构造函数缴活顺序相反。

举例:

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  using namespace std;  

3.  class A  

4.  {  

5.  public:  

6.      A()  

7.      {  

8.          cout<<"调用A的构造函数"<<endl;  

9.      }  

10. };  

11. class B  

12. {  

13. public:  

14.     B()  

15.     {  

16.         cout<<"调用B的构造函数"<<endl;  

17.     }  

18. };  

19. class C:public A,public B //这里声明顺序决定了调用基类的顺序  

20. {  

21. private:  

22.     A a;  

23. public:  

24.     C()  

25.     {  

26.         cout<<"调用C的构造函数"<<endl;  

27.     }  

28. };  

29. void main()  

30. {  

31.     C c;  

32.     system("pause");  

33. }  

#include <iostream>

using namespace std;

class A

{

public:

A()

{

   cout<<"调用A的构造函数"<<endl;

}

};

class B

{

public:

B()

{

   cout<<"调用B的构造函数"<<endl;

}

};

class C:public A,public B //这里声明顺序决定了调用基类的顺序

{

private:

A a;

public:

C()

{

   cout<<"调用C的构造函数"<<endl;

}

};

void main()

{

C c;

system("pause");

}

运行结果:

调用A的构造函数--C的基类

调用B的构造函数--C的基类

调用A的构造函数--C的对象成员

调用C的构造函数--C自己的构造函数

说明:

1、继承/多重继承一般是公有继承,保护继承/私有继承只是在技术讨论较多,实际使用较少。

多继承中同名覆盖原则

当派生类与基类中有同名成员时:

调用派生类的成员:定义派生类对象,直接调用同名函数即可,而自动屏蔽基类的同名函数。

访问基类中成员:应使用基类名限定。

举例:

[cpp]view plaincopyprint?

1.  #include<iostream>  

2.  using namespace std;  

3.  class A  

4.  {  

5.  public:  

6.      void f()  

7.      {  

8.          cout<<"调用A的构造函数"<<endl;  

9.      }  

10. };  

11. class B  

12. {  

13. public:  

14.     void f()  

15.     {  

16.         cout<<"调用B的构造函数"<<endl;  

17.     }  

18. };  

19. class C:public A,public B  

20. {  

21. public:  

22.     void f()  

23.     {  

24.         cout<<"调用C的构造函数"<<endl;  

25.     }  

26. };  

27. void main()  

28. {  

29.     C c;  

30.     c.f();//覆盖基类中f函数  

31.     c.B::f();//通过基类名限制访问  

32.     c.A::f();  

33.     system("pause");  

34. }  

#include<iostream>

usingnamespace std;

classA

{

public:

void f()

{

   cout<<"调用A的构造函数"<<endl;

}

};

classB

{

public:

void f()

{

   cout<<"调用B的构造函数"<<endl;

}

};

classC:public A,public B

{

public:

void f()

{

   cout<<"调用C的构造函数"<<endl;

}

};

voidmain()

{

C c;

c.f();//覆盖基类中f函数

c.B::f();//通过基类名限制访问

c.A::f();

system("pause");

}

多继承带来的二义性:

当派生类从多个基类派生,而这些基类又从同一个基类派生,则在访问此共同基类中的成员时,将产生二义性

即:派生类从同一个基类,沿不同继承方向,得到多个相同的拷贝,不知道要访问哪一个,就产生了二义性。

二义性的常用解决方法:使用作用域运算符(类名::)来解决访问二义性问题使用访问,但是这里的成员都是来源于同一个基类,这时是不能解决问题的,这里就引入虚基类

虚基类:

虚基类的作用:使公共基类只产生一个拷贝,即只对第一个调用的有效,对其他的派生类都是虚假的,没有调用构造函数

使用场合:用于有共同基类的场合

原理:让虚基类的构造函数只执行一次,派生类只得到一套虚基类的成员

语法:

[cpp]view plaincopyprint?

1.  class 派生类名:virtual 继承方式 类名  //在派生类定义的时候写。  

2.  {  

3.    

4.  }  

class 派生类名:virtual 继承方式 类名  //在派生类定义的时候写。

{

 

}

注意:声明后,当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次

虚基类的初始化:与一般多继承的初始化在语法上是一样的,但构造函数的调用次序不同.

派生类构造函数的调用次序:先虚基类,后基类,再成员对象,最后自身

(1)对虚基类间的构造函数的顺序:根据虚基类间继承的顺序调用

(2)对基类间的构造函数的顺序:根据基类间继承的顺序调用

(3)对成员对象的构造函数的顺序:根据成员对象在类中声明顺序调用

(4)若同一层次中包含多个虚基类,这些虚基类的构造函数按它们说明的次序调用;

(5)若虚基类由非虚基类派生而来,则仍先调用基类构造函数,再调用派生类的构造函数.

举例:

[cpp]view plaincopyprint?

1.  class  A :  public B, public C,virtual public D  

2.  {}  

3.  X one;  

4.  将产生如下调用次序:  

5.  D()  

6.  B()  

7.  C()  

8.  A()  

class  A :  public B, public C,virtual public D

{}

X one;

将产生如下调用次序:

D()

B()

C()

A()

说明:

1)DA的虚基类,故先调用D的构造函数

2)在同层次的多个虚基类中,从左到右调用,先BC

3)基类构造函数调用完后,在调用A的构造函数

举例:使用虚基类和不使用虚基类的说明:

错误代码:

[cpp]view plaincopyprint?

1.  #include<iostream>        

2.  using namespace std;  

3.  class A  

4.  {  

5.  protected:  

6.      int a;  

7.  public:  

8.      A(int a)  

9.      {  

10.         this->a=a;  

11.     }  

12. };  

13. class B1: public A  

14. {  

15. public:  

16.     B1(int a):A(a)  

17.      {  

18.      }  

19. };  

20. class B2: public A  

21. {  

22. public:  

23.     B2(int a):A(a)  

24.     {  

25.         this->a=a;  

26.     }  

27. };  

28. class C:public B1,public B2  

29. {  

30. public:  

31.     C(int a):B1(a),B2(a) //没有使用虚基类,声明时,只写C的直接基类B1B2,不写虚基类构造函数A   

32.     {  

33.   

34.     }  

35.     void display()  

36.     {  

37.         cout<<"a="<<a<<endl;//使用A::a区分也不行,这里的a是从A得到的,B1B2都继承到了,到D中就有两份拷贝,都来自A产生歧义  

38.     }  

39. };  

40. void main()  

41. {  

42.     D d(1);  

43.     d.display();  

44.     system("pause");  

45. }  

#include<iostream>     

using namespace std;

class A

{

protected:

int a;

public:

A(int a)

{

   this->a=a;

}

};

class B1: public A

{

public:

B1(int a):A(a)

 {

 }

};

class B2: public A

{

public:

B2(int a):A(a)

{

   this->a=a;

}

};

class C:public B1,public B2

{

public:

C(int a):B1(a),B2(a) //没有使用虚基类,声明时,只写C的直接基类B1和B2,不写虚基类构造函数A

{

 

}

void display()

{

   cout<<"a="<<a<<endl;//使用A::a区分也不行,这里的a是从A得到的,B1和B2都继承到了,到D中就有两份拷贝,都来自A产生歧义

}

};

void main()

{

D d(1);

d.display();

system("pause");

}

基类的层次图:

正确代码:

[cpp]view plaincopyprint?

1.  #include<iostream>  

2.  using namespace std;  

3.  class A  

4.  {  

5.  protected:  

6.      int a;  

7.  public:  

8.      A(int a)  

9.      {  

10.         this->a=a;  

11.     }  

12. };  

13. class B:virtual public A  

14. {  

15. public:  

16.     B(int a):A(a)  

17.      {  

18.      }  

19. };  

20. class C:virtual public A  

21. {  

22. public:  

23.     C(int a):A(a)  

24.     {  

25.         this->a=a;  

26.     }  

27. };  

28. class D:public B,public C  

29. {  

30. public:  

31.     D(int a):B(a),C(a),A(a)//使用虚基类,声明时是 D的直接基类BC + 直接基类的共同基类A   

32.     {  

33.   

34.     }  

35.     void display()  

36.     {  

37.         cout<<"a="<<a<<endl;  

38.          //使用虚基类时,A只执行一次,调用B的时候调用一次虚基类,调用C时,就没有调用A了,D中的a也只有一个拷贝,因而不产生歧义  

39.     }  

40. };  

41. void main()  

42. {  

43.     D d(1);  

44.     d.display();  

45.     system("pause");  

46. }  

#include<iostream>

using namespace std;

class A

{

protected:

int a;

public:

A(int a)

{

   this->a=a;

}

};

class B:virtual public A

{

public:

B(int a):A(a)

 {

 }

};

class C:virtual public A

{

public:

C(int a):A(a)

{

   this->a=a;

}

};

class D:public B,public C

{

public:

D(int a):B(a),C(a),A(a)//使用虚基类,声明时是 D的直接基类B和C + 直接基类的共同基类A

{

 

}

void display()

{

   cout<<"a="<<a<<endl;

         //使用虚基类时,A只执行一次,调用B的时候调用一次虚基类,调用C时,就没有调用A了,D中的a也只有一个拷贝,因而不产生歧义

}

};

void main()

{

D d(1);

d.display();

system("pause");

}

使用虚基类后的层次图:

注意:

1、派生类构造函数初始化列表对虚基类的处理:

有虚基类和没费基类两种情况下,派生类构造函数的书写情况是不一样的,上面注释有代码。

没有虚基类的多继承,派生类构造函数的声明只包括其直接的基类

有虚基类的多继承,派生类构造函数的声明不仅包含其直接基类还要包含直接基类的虚基类。

2

1)运行时,C创建对象时,先找到直接基类B1,调用直接基类B1的构造函数时,又调用A的构造函数,无基类,直接调用

2)之后,在调用B1的构造函数,之后再调B2的构造函数时,发现有基类A,但是A为虚基类,已经调用过一次,不再调用

3)之后,直接调用B2的构造函数,完了,就直接调用C的构造函数

说明:

1、虚基类怎么保证初始化派生类对象时,只被调用一次?

因为:初始化列表中要包括对直接基类 + 虚基类进行调用,但仅仅用建立对象的最远派生类的构造函数调用虚基类的构造函数,而该派生类的所有基类中列出的对虚基类

的构造函数的调用在执行中被忽略,即对其他基类来说,这个基类是虚假的,而不再调用虚基类,从而保证对虚基类子对象只初始化一次。

2、一个类可以在一个类族中既被用作虚基类,也被用作非虚基类。

 

 

18.C++_多态与虚函数

分类:C++2011-07-30 00:50512人阅读评论(1)收藏举报

c++deleteclasssystem工作

什么是多态?

一个操作随着所传递或捆绑的对象类型的不同能够做出不同的反应,其行为模式称为多态。

即,对这个操作,能接受不同类型的参数,而且在处理参数时,会根绝其类型做出不同的反应。

为什么要引入多态?

针对由继承得到一类对象,在处理这类对象时,能够以同一操作处理完所有对象。

遇到的问题:

[cpp]view plaincopyprint?

1.  #include<iostream>  

2.  using namespace std;  

3.    

4.  class Base  

5.  {  

6.  public:  

7.      void Show()  

8.      {  

9.          cout<<"Base"<<endl;  

10.     }  

11. };  

12.   

13. class DeriveOne : public Base  

14. {  

15. public:  

16.     void Show()  

17.     {  

18.         cout<<"DeriveOne"<<endl;  

19.     }  

20. };  

21.   

22.   

23. void AAA(Base base)  

24. {  

25.     base.Show();  

26. }  

27.   

28.   

29. int main()  

30. {  

31.     Base base;  

32.     DeriveOne deriveOne;  

33.       

34.     AAA(base);  

35.     AAA(deriveOne);  

36.   

37.     system("pause");  

38.     return 1;  

39. }  

#include<iostream>

using namespace std;

 

class Base

{

public:

void Show()

{

   cout<<"Base"<<endl;

}

};

 

class DeriveOne : public Base

{

public:

void Show()

{

   cout<<"DeriveOne"<<endl;

}

};

 

 

void AAA(Base base)

{

base.Show();

}

 

 

int main()

{

Base base;

DeriveOne deriveOne;

AAA(base);

AAA(deriveOne);

 

system("pause");

return 1;

}

输出

说明:

在父类Base和派生类DeriveOne中都定义了Show操作,它们分别输出对应类的信息,Baseshow操作显示Base的信息,DeriveOneshow操作显示DeriveOne的信息。在函数AAA中,由于参数是base类型,在函数AAA中使用时,无论形参是父类Base还是子类DeriveOne,在函数中,其仅仅表示父类或者子类中的父类的那部分信息。若想显示完全,就必须写两个函数,它们分别接受两种参数,如:

[cpp]view plaincopyprint?

1.  void show(Base base);  

2.  void show(DeriveOne Derive);  

void show(Base base);

void show(DeriveOne Derive);

问题又来了,由于派生类可以转化为基类,因此在调用Show函数时,还需要判断来的类是基类还是派生类。此时,用的时候就很不方便了。

此时,我们想要的是,函数AAA的获得实参可以是继承类集合中的任意类对象,而且其函数体也能根据其实参的改变调用不同的函数,即自动分别实参。这就是我们要求的多态。

举例说明要实现的目标:

[cpp]view plaincopyprint?

1.  #include<iostream>  

2.  using namespace std;  

3.    

4.  class Base  

5.  {  

6.  public:  

7.      virtual void Show()  

8.      {  

9.          cout<<"Base"<<endl;  

10.     }  

11. };  

12.   

13. class DeriveOne : public Base  

14. {  

15. public:  

16.      void Show()  

17.     {  

18.         cout<<"DeriveOne"<<endl;  

19.     }  

20. };  

21.   

22. class DeriveTwo : public Base  

23. {  

24. public:  

25.     void Show()  

26.     {  

27.         cout<<"DeriveTwo"<<endl;  

28.     }  

29. };  

30.   

31. //定义一个操作,对多个不同的继承类对象做统一操作  

32. void AAA(Base& base)    

33. {  

34.     base.Show();  

35. }  

36.   

37. int main()  

38. {  

39.     Base base;  

40.     DeriveOne deriveOne;  

41.     DeriveTwo deriveTwo;  

42.   

43.     AAA(base);  

44.     AAA(deriveOne);  

45.     AAA(deriveTwo);  

46.     system("pause");  

47.     return 1;  

48. }  

#include<iostream>

using namespace std;

 

class Base

{

public:

              virtual void Show()

              {

                            cout<<"Base"<<endl;

              }

};

 

class DeriveOne : public Base

{

public:

               void Show()

              {

                            cout<<"DeriveOne"<<endl;

              }

};

 

class DeriveTwo : public Base

{

public:

              void Show()

              {

                            cout<<"DeriveTwo"<<endl;

              }

};

 

//定义一个操作,对多个不同的继承类对象做统一操作

void AAA(Base& base) 

{

              base.Show();

}

 

int main()

{

              Base base;

              DeriveOne deriveOne;

              DeriveTwo deriveTwo;

 

              AAA(base);

              AAA(deriveOne);

              AAA(deriveTwo);

              system("pause");

              return 1;

}

说明,这里还是给出一个函数AAA,其调用AAA的实参是有三个,basederiveOnederiveTwo,函数AAA能够根据传进来的实参来自定义判断调用哪个类的Show函数。这是我们想要的结果,即函数AAA能够接受整个派生类集合,并能区分不同的类对象。

怎么实现多态?

虚函数+ 基类指针引用调用。

什么是虚函数?

简单地说,那些被virtual关键字修饰的成员函数,就是虚函数。

虚函数的原理:

虚函数的作用是实现类的继承所体现的多态性,具体点是实现动态联编。

从程序的角度上来说,在定义了虚函数后,可以在派生类中对虚函数重新定义,以实现统一的接口,不同定义过程,在程序的运行阶段动态地选择合适的成员函数。

一旦标记基类的函数为虚函数,便有连锁反应,后面继承的类中一切同名成员函数都变成了虚函数,(即使后面的这些同名函数没有声明该函数为虚函数)。

C++实现运行时多态性的关键途径:在公有派生情况下,一个指向基类的指针可用来访问从基类继承的任何对象

虚函数语法:

对函数的设置:普通函数的前面加上virtual

[cpp]view plaincopyprint?

1.  virtual 函数返回值类型 虚函数名(形参表)  

2.  {   

3.      //函数体  

4.  }  

virtual 函数返回值类型 虚函数名(形参表)

{

//函数体

}

虚函数的调用方式:只能通过指向基类的指针或基类对象的引用来调用虚函数

调用语法:

[cpp]view plaincopyprint?

1.  指向基类的指针变量名->虚函数名(实参表)  

2.  基类对象的引用名虚函数名(实参表)  

指向基类的指针变量名->虚函数名(实参表)

基类对象的引用名. 虚函数名(实参表)

注意事项一:正常情况下,如果不把函数声明为虚函数,指向基类的指针的访问情况如下:

1基类指针指向基类对象:基类指针可以直接访问基类对象中的成员

2)基类指针指向派生类对象:基类指针只能访问派生类中的从基类中继承的成员,派生类有同名的函数或成员,也只能调用基类的成员。

如果定义成虚函数时:定义一个基类指针,把不同的派生类对象付给它,会调用对应派生类的函数,而非基类函数。

举例:

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  using namespace std;  

3.  class A  

4.  {  

5.  public:  

6.      virtual void show()  

7.      {  

8.          cout<<"A"<<endl;  

9.      }  

10. };  

11. class B:public A  

12. {  

13. public:  

14.     void show()  

15.     {  

16.         cout<<"B"<<endl;  

17.     }  

18. };  

19. class C:public A  

20. {  

21. public:  

22.     void show()  

23.     {  

24.         cout<<"C"<<endl;  

25.     }  

26. };  

27. void main()  

28. {  

29.     A*a;  

30.     B b;  

31.     C c;  

32.     a=&b;  

33.     a->show();  

34.     a=&c;  

35.     a->show();  

36.     system("pause");  

37. }  

#include <iostream>

using namespace std;

class A

{

public:

virtual void show()

{

   cout<<"A"<<endl;

}

};

class B:public A

{

public:

void show()

{

   cout<<"B"<<endl;

}

};

class C:public A

{

public:

void show()

{

   cout<<"C"<<endl;

}

};

void main()

{

A*a;

B b;

C c;

a=&b;

a->show();

a=&c;

a->show();

system("pause");

}

运行结果:B(换行)C(换行)--指向不同的派生类,调用不同的函数

如果不加基类A中的Virtual,则输出结果:A(换行)A(换行)--基类指针,调用派生类中继承的基类成分

注意事项二:如果不是使用基类指针或引用,而仅仅是类之间的复制,那么子类对象就完全转换为父类对象,就不会有多态了。

[cpp]view plaincopyprint?

1.  Student s; //基类  

2.  GraduateStudent gs; //派生类  

3.    

4.  void fn (Student a)//形参传值--而非(Student & a)   

5.  {  

6.      a.Show();  

7.  };  

8.    

9.  fn(gs); //无多态,调用基类的show函数,而不是派生类的show函数。  

Student s; //基类

GraduateStudent gs; //派生类

 

void fn (Student a)//形参传值--而非(Student & a)

{

a.Show();

};

 

fn(gs); //无多态,调用基类的show函数,而不是派生类的show函数。

原因,在参数传递的过程中,已经将实参的性质做了肯定的转变,而对于确定的对象,是没有选择操作可言的

因此,仅仅对于对象的指针和引用的间接访问,才会发生多态现象

定义虚函数,实现动态联编需要三个条件:

1)必须把动态联编的行为定义为类的虚函数---定义虚函数

2)类之间存在子类型关系,一般表现为一个类从另一个类公有派生而来---类之间是公有继承

3基类指针指向派生类的对象,然后使用基类指针调用虚函数

把那些函数设置为虚函数?

如果一个函数在子类和父类中都有,而且子类使用这个函数时,仅仅使用自己就ok,那么可以把这个函数设置为虚函数。

虚函数是怎么实现动态捆绑的?

编译器在编译的时候,看到一个基类函数前面有关键字”virtual“,编译器就知道需要对该函数进行晚捆绑,即不能立即断定虚函数的确切位置,此时编译器将此函数调用转化为一个指针。该指针在该虚函数被调用的时候,根据实际对象来给指针赋值,即指向实际对象的成员函数。这样一来,若实际对象是基类,则调用的是基类成员函数。若实际对象是派生类,则调用的是子类的成员函数。

为了给这个指针赋值,必须在每个实际的对象中额外占有一个指针空间,以指向自己类中的虚函数表。

如:

注意,具有虚函数的类,其对象空间比没有虚函数的类多了一个指针空间,而且在使用时需要间接访问虚函数,即采用虚函数,会影响一些程序运行效率。

关于虚函数的详细说明,请见另一篇文章:

虚函数的

注意:

1、使用时,虚函数可以在基类中声明,提供界面。可以在给派生类中定义具体的实现方法,而得到多种方法。

2多态的实现必须是公有派生。

原因:虚函数为了实现多态,虚函数建立在赋值兼容原则上,而赋值兼容原则成立的前提条件是派生类从基类那里公有派生。

3、派生类对基类中虚函数重新定义时,关键字可以写也可以不写,系统可以自动判断。

因为,虚函数会自动地从基类往下传播,可以虚到底。在函数调用时,总是先碰见基类的函数,只要把该函数设置为虚函数,系统就认为其他派生类函数就为虚的啦。

4虚函数必须是类的成员函数,不能是友元函数,也不能是静态成员函数。

因为虚函数仅仅适用于有继承关系的类对象,所有普通函数不行。由于静态函数不收对象的捆绑,即使在形象上的捆绑,实际上也没有任何对象的信息,只有类的信息。但是虚函数可以是另一个类的友元函数

5、内敛函数不能是虚函数。

因为内敛函数不能在运行时动态确定位置。所以虚函数都是非内敛的。即使虚函数在类内部定义,在编译时,仍被看做为非内敛的。

6、构造函数不是虚函数。因为构造时,对象还不存在。

7、析构函数是可以虚函数,那么派生而来的所有派生类的析构函数也是虚函数,不管是否使用了关键字virtual

8、一旦一个函数被说明为虚函数,不管经历多少层派生,都将保持其虚特性,即虚特性具有继承性

9、如果在派生类中没有对虚函数重新定义,则它继承其基类的虚函数。

10、虚函数在派生类中重新定义时,可以函数原型必须完全相同,如具有相同的形参个数和形参类型,但返回值可以不同。

 

纯虚函数:如果基类的函数没有必要或者无法实现,完全要依赖子类去实现的话,可以使用纯虚函数

简单点说,基类提供接口,而不提供定义,派生类提供实现

语法:

[cpp]view plaincopyprint?

1.  virtual 函数返回值类型 虚函数名(形参表)=0  

virtual 函数返回值类型 虚函数名(形参表)=0;

注意:从基类继承来的纯虚函数,在派生类中仍是纯虚函数,除非派生类按原型一致地超载该纯虚函数,变成类的纯虚函数

 

抽象类:包含了纯虚函数的类

注意:抽象类只能用作其他类的基类,不能建立对象,不能作为参数类型、返回类型或显式转换类型,可声明指针和引用。可以定义返回引用。

 

19.C++_继承与派生

分类:C++2011-07-29 23:32377人阅读评论(0)收藏举报

c++classsystem程序开发

目录(?)[+]

1.继承的意义

2.什么叫继承

3.为什么要使用继承

4.继承实现的功能

5.继承的分类

6.派生类的构造函数和析构函数

7.同名覆盖与重写

8.派生类为基类赋值

9.注意

继承的意义?

使程序的设计更符合发展规律,即事物的发展是一个从低级到高级的发展过程,类的继承也是反映由原始的简单代码到丰富的高级代码的过程。它能帮助我们描述事物的层次关系,有效而精确的理解事物,理解事物直到本质。

什么叫继承?

1)继承使类与类之间建立起一种上下级的层次关系

2)子类可以重复使用父类的操作和数据成员,子类可以声明新的属性和操作,还可以剔除不适合自己用途的父类操作。

为什么要使用继承?

原因:为了提高代码复用率,缩短程序开发成本

继承实现的功能:

1、继承基类的数据成员:将基类成员全盘吸收

2、增加新的数据成员、改变现有成员的属性:不同方式继承声明一个同名成员,使用重写覆盖技术

3、重新定义已有成员函数

继承的分类:

针对派生类而言,根据基类的个数分:单继承多继承

单继承:派生类的基类只有一个

语法格式:

[cpp]view plaincopyprint?

1.  class 派生类名:继承方式 基类名  

2.  {  

3.      //成员声明:类似于普通类  

4.  };  

class 派生类名:继承方式 基类名

{

//成员声明:类似于普通类

};

三种继承方式:公有继承(public),私有继承(private),保护继承(protected)

继承后访问属性的种类:针对派生类而言,成员可分为:不可访问成员、私有成员、受保护成员、公有成员

说明:无论是那种派生方式,派生类无法直接使用不可访问变量的,换句话说,派生类不能访问基类的私有成员。

具体如图:

 

 

 

 

 

 

 

 

 

 

 

继承后的法则:

派生类的成员函数访问基类成员时,

         无论是公有、私有、受保护继承,基类的私有变量均不可访问,但继承后属性为公有、受保护成员均可以直接访问。

派生类的对象类外访问基类成员时,

        若受保护、私有继承时,派生类对象均不可访问基类的成员(无论是哪种访问属性)。

        若公有继承,派生类的对象可以访问基类的公有成员。

 一句话:就派生类而言,基类私有变量不可访问,其他成员的访问情况,按具体继承后的属性而定

根据父亲物品的使用权以及继承,换一种方式理解成员权限的定义:

父亲的东西可以分为几类,只能是父母知道,子女都不可以知道。子女可以知道,但是外人不可以知道。外人可以知道。

根据分类,给出哪种物品使用哪种权限限制:

--只能是父母知道,子女都不可以知道:把这部分东西定义为Private

--子女可以知道,但是外人不可以知道:把这部分定义为protected

--外人可以知道:把这部分内容定义为Public

最后根据那些继承方式,对父类物品的访问类型进一步约束,形成自己的访问法则。

注意,无论是哪一种继承方式,继承方式对父亲的孩子能访问父亲哪些的物品不能访问哪些物品是没有限制的,继承方式是为了限制后来的继承。

即无论是哪一种继承方式,子类成员都不可以访问私有成员,但都可以访问父类的受保护和公有成员。当自己作为父类时,根据自己从父亲那里的继承权限,在就会限制自己的子类访问自己的父亲的权限。

调整访问控制

前提:对于在派生类中可以看见的成员(父类除私有成员外的成员),

目标:在派生类中,将基类中的私有成员调整为公有成员,将公有成员变成私有成员

[cpp]view plaincopyprint?

1.  class BaseClass  

2.  {  

3.  public:  

4.      int nPublicA;  

5.  protected:  

6.      int nProtectedA;  

7.  private:  

8.      int nPrivateA;  

9.  };  

10.   

11. class DerivedClass : public BaseClass  

12. {  

13. public:  

14.     using BaseClass::nPrivateA; /*错误,父类私有成员在子类不可见*/  

15.     using BaseClass::nProtectedA;/*正确,父类受保护成员变公有成员*/  

16. private:  

17.     using BaseClass::nPublicA;/*正确,父类公有成员变私有成员*/  

18. };  

class BaseClass

{

public:

  int nPublicA;

protected:

  int nProtectedA;

private:

  int nPrivateA;

};

 

class DerivedClass : public BaseClass

{

public:

  using BaseClass::nPrivateA; /*错误,父类私有成员在子类不可见*/

  using BaseClass::nProtectedA;/*正确,父类受保护成员变公有成员*/

private:

  using BaseClass::nPublicA;/*正确,父类公有成员变私有成员*/

};

派生类的构造函数和析构函数

类一旦被创建,C++编译器会为其产生四个缺省函数,默认的无参构造函数,默认的拷贝构造函数,默认的析构函数,默认的赋值函数。

对于构造函数:

若父类的构造函数无参,则子类的构造函数可以无参,若父类的构造函数有参,则子类的构造函数必有参,可以使用参数列表为基类构造函数传参。

对于拷贝构造函数和赋值函数:

由于这两类函数的函数名和参数都是确定的,所以不涉及由子类为父类传参的情况。

一句话:无论是父类还是基类,只要有自定义的拷贝构造函数,则在调用拷贝构造函数时,就调用自定义的,否则调用默认的。

若父类没有自定义拷贝构造函数,则子类的拷贝构造函数将调用父类的默认拷贝构造函数。

若父类有自定义的拷贝构造函数,则子类的拷贝构造函数将调用父类的自定义的拷贝构造函数。

注意,对于父类和子类之间的拷贝和赋值时,由于子类中包含父类中的成员,可以按照参数对应赋值

 

对象构造顺序:

        创建对象时,先基类,再对象类成员的构造函数,后派生类 

       撤销对象时,先派生类,再对象类成员的析构函数,后基类

如果,在类中有多个对象成员,则其调用构造函数的顺序和类中对象声明的顺序有关,而和在参数列表中的顺序无关。析构函数的调用顺序和构造函数的顺序相反。

语法:

[cpp]view plaincopyprint?

1.  派生类名(派生类构造函数参数表):基类名(参数),子对象名(参数)  

2.  {  

3.      //派生类的数据成员初始化  

4.  }  

派生类名(派生类构造函数参数表):基类名(参数),子对象名(参数)

{

//派生类的数据成员初始化

}

为基类传参:使用参数列表的形式初始化对象

举例:

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  using namespace std;  

3.  class Base  

4.  {  

5.  private:  

6.      int i;  

7.  public:  

8.      Base(int )  

9.      {  

10.         cout<<"调用基类构造函数"<<endl;  

11.     }  

12. };  

13. class Derived:public Base  

14. {  

15. private:  

16.     int j;  

17.     Base B;//基类对象  

18. public:  

19.     Derived(int i,int j):Base(i),B(i)  

20.     {  

21.         this->j=j;  

22.         cout<<"调用派生类的构造函数"<<endl;  

23.     }  

24. };  

25. void main()  

26. {  

27.     Derived d(1,2);  

28.     system("pause");  

29. }  

#include <iostream>

using namespace std;

class Base

{

private:

int i;

public:

Base(int )

{

   cout<<"调用基类构造函数"<<endl;

}

};

class Derived:public Base

{

private:

int j;

Base B;//基类对象

public:

Derived(int i,int j):Base(i),B(i)

{

   this->j=j;

   cout<<"调用派生类的构造函数"<<endl;

}

};

void main()

{

Derived d(1,2);

system("pause");

}

运行结果:

调用基类构造函数

调用基类构造函数

调用派生类的构造函数

同名覆盖与重写:

产生原因:派生类定义的成员与基类中的成员同名

                   如果是函数的话,函数名和参数完全相同

结果:派生类的成员覆盖了基类的同名成员

具体来数,直接使用派生类对象调用成员时,只能调到派生类自己的,而无法访问基类成员

如果要访问基类被覆盖的成员,则需要加上类名::成员访问

例子:

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  using namespace std;  

3.  class Base  

4.  {  

5.  public:  

6.      void a()  

7.      {  

8.          cout<<"调用基类的成员函数"<<endl;  

9.      }  

10. };  

11. class Derived:public Base  

12. {  

13. public:  

14.     void a()  

15.     {  

16.         cout<<"调用派生类的成员函数"<<endl;  

17.     }  

18. };  

19. void main()  

20. {  

21.      Derived d;  

22.      d.a(); //调用的是派生类的成员函数,这时会覆盖基类的成员函数  

23.      d.Base::a();//调用基类的成员函数  

24.      d.Derived::a();//调用派生类的成员函数  

25.      system("pause");  

26. }  

#include <iostream>

using namespace std;

class Base

{

public:

void a()

{

   cout<<"调用基类的成员函数"<<endl;

}

};

class Derived:public Base

{

public:

void a()

{

   cout<<"调用派生类的成员函数"<<endl;

}

};

void main()

{

Derived d;

d.a(); //调用的是派生类的成员函数,这时会覆盖基类的成员函数

d.Base::a();//调用基类的成员函数

d.Derived::a();//调用派生类的成员函数

system("pause");

}

调用结果:

调用派生类的成员函数

调用基类的成员函数

调用派生类的成员函数

派生类为基类赋值:

定义:在公有派生情况下,一个派生类的对象可用于基类对象可使用的任何地方

换句话说:公有派生情况下,一个派生类对象可以直接作为基类的对象使用

具体分3仲情况:

1  派生类的对象基类对象赋值

        结果:基类对象能够访问派生类中从基类继承的成员

2  派生类的对象基类对象的引用赋值

       结果:基类对象只可以访问派生类中基类的成员,看不到派生类之外的成员,也就不可访问派生类自己定义的成员

3  派生类的对象基类对象的指针赋值

       结果:基类指针只可以访问派生类中基类的成员,派生类之外成员不可访问

若使基类指针访问派生类自己的成员,则必须使用显式类型转换,把基类指针显示转换为派生类指针,这时可以访问所有派生类成员。

举例:

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  using namespace std;  

3.  class Base  

4.  {  

5.  public:  

6.      void a()  

7.      {  

8.          cout<<"调用基类的成员函数"<<endl;  

9.      }  

10. };  

11. class Derived:public Base  

12. {  

13. public:  

14.     void a()  

15.     {  

16.         cout<<"调用派生类的成员函数"<<endl;  

17.     }  

18. };  

19. void main()  

20. {  

21.     Base b;  

22.     Base *B;  

23.     Derived d;  

24.     B=&b;  

25.     B->a();        //基类指针指向基类,调用的都是基类函数  

26.     B=&d;   

27.     B->a();        //基类指针指向派生类,调用的是派生类中从基类继承的函数  

28.     b=d;    

29.     b.a();         //使用派生类为基类赋值,调用的是派生类中从基类继承的函数  

30. //  Derived *D=&b; //错误,不能用基类为派生类指针赋值  

31. //  d=b;           //错误,不能用基类为派生类赋值  

32.     Base *E;  

33.     E=&d;          //必须要先赋值啊,这时派生类指针E才不会乱指。  

34.     ((Derived*)E)->a();//对基类指针强制转换为指向派生类,这时可以指向派生类自己的成员  

35.     system("pause");  

36. }  

#include <iostream>

using namespace std;

class Base

{

public:

void a()

{

   cout<<"调用基类的成员函数"<<endl;

}

};

class Derived:public Base

{

public:

void a()

{

   cout<<"调用派生类的成员函数"<<endl;

}

};

void main()

{

Base b;

Base *B;

Derived d;

B=&b;

B->a();        //基类指针指向基类,调用的都是基类函数

B=&d;

B->a();        //基类指针指向派生类,调用的是派生类中从基类继承的函数

b=d; 

b.a();         //使用派生类为基类赋值,调用的是派生类中从基类继承的函数

// Derived *D=&b; //错误,不能用基类为派生类指针赋值

// d=b;           //错误,不能用基类为派生类赋值

Base *E;

E=&d;          //必须要先赋值啊,这时派生类指针E才不会乱指。

((Derived*)E)->a();//对基类指针强制转换为指向派生类,这时可以指向派生类自己的成员

system("pause");

}

运行结果:
调用基类的成员函数
调用基类的成员函数
调用基类的成员函数

注意:

1、在公有继承下,为什么一个指向基类的指针可以指向其公有派生类的对象,但是指向派生类的指针不能指向一个基类的对象?

因为:派生类指向基类时,派生类能力减弱,不行的。

           基类指向派生类时,不是能力扩大,实际上还是指向派生类中包含的基类。

2、为什么前提必须是公有派生?

因为:公有派生:派生后类外还是类可以直接访问,私有或受保护派生:类不可以直接访问基类成员,就没什么意思了

3、引入protected成员的原因:

若一个类,将外界能够访问的操作都公有化了,所有不能被外界访问的成员都私有化,这样一来,在后继的类中,就没有对基类任何可以悄悄改进的余地了,即无法对类中某些功能可扩展的变量扩展功能。所以,继承也需要这样的成员,它们对外界是私有的,对派生的子女是允许访问的。这种设计需求就是访问控制符protected

此时,类中private成员是一种隐私,子女以及外来人员谁都不能动的

    类中protected成员是基类想后代开放的成员,便于继承者基于此而改进,基类为了长远考虑,可以留下保护成员,但派生类简单而清晰的设计应该是不使用任何保护成员,即只使用公有成员。

 

4、使用派生类对象对基类对象、基类对象引用、基类对象指针赋值时,是否调用拷贝构造函数?

由于派生类对象包含的对象实体拥有基类对象包含的实体对象,因此可以使用派生类对象对基类对象、基类对象引用、基类对象指针赋值。

1)在创建对象时,使用派生类对象为基类对象赋值,需要调用拷贝构造函数或赋值函数

2)由于基类对象引用、基类对象指针只是获得了派生类中基类部分的地址,即取了个别名,此时没有调用拷贝构造函数。

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  #include <string>  

3.  using namespace std;  

4.    

5.  class BaseClass  

6.  {  

7.  public:  

8.      BaseClass(string baseA)  

9.      {  

10.         m_strBaseA = baseA;  

11.     }  

12.       

13.     BaseClass(const BaseClass& other)  

14.     {  

15.         m_strBaseA = other.m_strBaseA;  

16.     }  

17.   

18.     void show()  

19.     {cout<<"Base: "<<m_strBaseA<<endl;}  

20. private:  

21.     string m_strBaseA;  

22. };  

23.   

24. class DerivedClass : public BaseClass  

25. {  

26. public:  

27.     DerivedClass(string strBaseA,string strDerivedA):BaseClass(strBaseA)  

28.     {  

29.         m_strDerivedA = strDerivedA;  

30.     }  

31.     void show()  

32.     {cout<<"Derived: "<<m_strDerivedA<<endl;}  

33. private:  

34.     string m_strDerivedA;  

35. };  

36.   

37. /*可以使用派生类对基类进行直接复制*/  

38. int main()  

39. {  

40.     /* 

41.     使用拷贝构造函数,用派生类对基类赋值 

42.     由于derive的对象实体包含baseOne的对象实体, 

43.     使用derivebaseOne赋值,就是将derive中的Base对象实体复制给baseOne 

44.         **注意:这个过程调用了拷贝构造函数 

45.     */  

46.     DerivedClass derive("Base","Derived");  

47.     BaseClass baseOne(derive);   

48.     derive.show();  

49.     baseOne.show();  

50.   

51.     /* 

52.     使用派生类对象初始化基类对象的引用 

53.     等价于 baseTwo  derive中的Base对象实体的别名 

54.         **注意:这个过程没有调用了拷贝构造函数 

55.     */  

56.     BaseClass& baseTwo = derive;  

57.     derive.show();  

58.     baseTwo.show();  

59.   

60.     /* 

61.     使用派生类对象初始化基类对象的指针 

62.     等价于 baseThree  derive中的Base对象实体的别名 

63.         **注意:这个过程没有调用了拷贝构造函数 

64.     */  

65.     BaseClass* baseThree = &derive;  

66.     derive.show();  

67.     baseThree->show();  

68.   

69.     system("pause");  

70.     return 1;  

71. }  

#include <iostream>

#include <string>

using namespace std;

 

class BaseClass

{

public:

    BaseClass(string baseA)

    {

        m_strBaseA = baseA;

    }

   

    BaseClass(const BaseClass&other)

    {

        m_strBaseA =other.m_strBaseA;

    }

 

    void show()

    {cout<<"Base:"<<m_strBaseA<<endl;}

private:

    string m_strBaseA;

};

 

class DerivedClass : public BaseClass

{

public:

    DerivedClass(stringstrBaseA,string strDerivedA):BaseClass(strBaseA)

    {

        m_strDerivedA = strDerivedA;

    }

    void show()

    {cout<<"Derived:"<<m_strDerivedA<<endl;}

private:

    string m_strDerivedA;

};

 

/*可以使用派生类对基类进行直接复制*/

int main()

{

    /*

    使用拷贝构造函数,用派生类对基类赋值

    由于derive的对象实体包含baseOne的对象实体,

    使用derive对baseOne赋值,就是将derive中的Base对象实体复制给baseOne

        **注意:这个过程调用了拷贝构造函数

    */

    DerivedClassderive("Base","Derived");

    BaseClass baseOne(derive);

    derive.show();

    baseOne.show();

 

    /*

    使用派生类对象初始化基类对象的引用

    等价于 baseTwo 是 derive中的Base对象实体的别名

        **注意:这个过程没有调用了拷贝构造函数

    */

    BaseClass& baseTwo = derive;

    derive.show();

    baseTwo.show();

 

    /*

    使用派生类对象初始化基类对象的指针

    等价于 baseThree 是 derive中的Base对象实体的别名

        **注意:这个过程没有调用了拷贝构造函数

    */

    BaseClass* baseThree =&derive;

    derive.show();

    baseThree->show();

 

    system("pause");

    return 1;

}


5 父类和子类相互转换时的内存状况

[cpp]view plaincopyprint?

1.  Student:父类  

2.  GraduateStudent:子类  

3.    

4.    

5.  GraduateStudent gs;  

6.  (1)Student s = gs;    // 正确  

7.  (2)Student& t = gs;   //正确  

8.  (3)Student* p = &gs;  //正确  

9.    

10.   

11. (4)GraduateStudent gs = s;    // 错误  

12. (5)Graduatestudent* pGS = &s;   //错误     

13.   

14.   

15. (6)Student* pS = reinterpret_cast<Student*>(&gs);  // 正确  

16. (7)GraduateStudent* pGS = reinterpret_cast<GraduateStudent*>(&s);  // 错误   

Student:父类

GraduateStudent:子类

 

 

GraduateStudent gs;

(1)Student s = gs;    // 正确

(2)Student& t = gs;   //正确

(3)Student* p = &gs;  //正确

 

 

(4)GraduateStudent gs = s;    // 错误

(5)Graduatestudent* pGS = &s;   //错误  

 

 

(6)Student* pS = reinterpret_cast<Student*>(&gs);  // 正确

(7)GraduateStudent* pGS =reinterpret_cast<GraduateStudent*>(&s);  // 错误

分析一:(1)正确,但(4)不正确。这是因为在子类对象中包含有父类对象的实体,可以拿出来父类部分为父类对象赋值。但是基类中数据不充分,不含有子类中的全部信息,所以拒绝执行父类对子类的赋值。对于指针,也是同样原因。(3)正确,(5)错误。原因同上。

分析二:(1)(2)(3)正确,表示子类对象可以为父类对象、父类对象引用、父类对象指针赋值。其中(1)需要调用拷贝函数,而(2)(3)不需要调用拷贝构造函数。

分析三:(6)和(3)一样,而且都正确。原因同分析一。即可以使用子类指针为父类指针赋值。

分析四:(7)和(5)一样,都错误。原因同分析一。即不可以使用父类指针为子类指针赋值。

内存图:

从内存图可以看出,

1、父类对象s复制了子类对象gs中的Student部分,构成了名副其实的Student对象实体。

2、父类对象的引用 t 只是引用了子类对象中的Student部分。

3、父类对象的指针 p 指向子类对象gs的首地址,它恰好是子类对象中的Student部分的首地址。

4、子类指针可以强制类型转化给父类指针。这是由于在父类GraduateStudent中,学生对象的地址和研究生对象的地址是重合的,研究生对象指针(子类指针)转化为学生指针(父类指针)时,只是把指针类型换了换而已,而指针的地址即指针指向没有改变。所以,上例中(7)也是错的,因为学生对象和研究生对象的地址不重合。

 

 

 

 

 

20.C++_输入常用函数

分类:C++2011-07-29 01:08301人阅读评论(1)收藏举报

c++systemstringc

C++ 常用输入有:cincin.get( )cin.getline( )

C常用输入:getline( )gets( )

1cin-- 相当于scanf

功能:输入字符串到字符输出,遇空格、Tab、回车结束

代码:

[cpp]view plaincopyprint?

1.  //输入字符串到字符数组  

2.  #include <iostream>  

3.  using namespace std;  

4.  void main()  

5.  {  

6.      const int len =100;  

7.      char ch[len];  

8.      cin>>ch;  

9.      system("pause");  

10. }  

//输入字符串到字符数组

#include <iostream>

using namespace std;

void main()

{

const int len =100;

char ch[len];

cin>>ch;

system("pause");

}

输入:ab cd

数组接受的值:ab

功能:输入字符串到字符串输出,遇空格、Tab、回车结束

代码:

[cpp]view plaincopyprint?

1.  //输入字符串到字符串中  

2.  #include <iostream>  

3.  #include <string>  

4.  using namespace std;  

5.  void main()  

6.  {  

7.      string a;  

8.      cin>>a;  

9.      system("pause");  

10. }  

//输入字符串到字符串中

#include <iostream>

#include <string>

using namespace std;

void main()

{

string a;

cin>>a;

system("pause");

}

输入:ab cd

字符串接受的值:ab

2ch=cin.get();

功能:输入字符,遇空格、Tab、回车结束

代码:

[cpp]view plaincopyprint?

1.  //输入字符  

2.  #include <iostream>  

3.  #include <string>  

4.  using namespace std;  

5.  void main()  

6.  {  

7.      char ch;  

8.      ch=cin.get();  

9.      cout<<ch;  

10.     system("pause");  

11. }  

//输入字符

#include <iostream>

#include <string>

using namespace std;

void main()

{

char ch;

ch=cin.get();

cout<<ch;

system("pause");

}

输入:abcd

字符接受的值:a

3cin.getline(ch,len); --  相当于gets( )

功能:输入字符串,遇回车结束

代码:

[cpp]view plaincopyprint?

1.  //输入一行,可以包含空格,遇回车结束  

2.  #include <iostream>  

3.  using namespace std;  

4.  void main()  

5.  {  

6.      const int len=5;  

7.      char ch[len];  

8.      cin.getline(ch,len);//len表示接受的最大字符数  

9.      cout<<ch;  

10.     system("pause");  

11. }  

//输入一行,可以包含空格,遇回车结束

#include <iostream>

using namespace std;

void main()

{

const int len=5;

char ch[len];

cin.getline(ch,len);//len表示接受的最大字符数

cout<<ch;

system("pause");

}

输入:12345678

输出:1234(最大长度为5,实际存4个,还有接个结束符\0)

4cin.getline()与二维数组连用

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  #include <string>  

3.  using namespace std;  

4.  void main()  

5.  {  

6.      char ch[3][81];  

7.      for (int i=0;i<3;i++)  

8.      {  

9.          cin.getline(ch[i],81);  

10.     }  

11.     for (int j=0;j<3;j++)  

12.     {  

13.         cout<<ch[j]<<endl;  

14.     }  

15.     system("pause");  

16. }  

#include <iostream>

#include <string>

using namespace std;

void main()

{

char ch[3][81];

for (int i=0;i<3;i++)

{

cin.getline(ch[i],81);

}

for (int j=0;j<3;j++)

{

cout<<ch[j]<<endl;

}

system("pause");

}

 

注意:

实际应用:接受单个字符,使用cin,接收字符串cin.getline( )

原因:虽然cin.get( )不仅可以接受字符,还可以接受字符串。但是接受完数据后,还会有回车在缓冲区中,下次在接受数据时,会自动把这个回车给下个变量,这就会产生问题,要想使用它,还要在cin.get( )语句后面接受回车。使用cin.get( ).

错误代码:

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  #include <string>  

3.  using namespace std;  

4.  void main()  

5.  {  

6.      char ch;  

7.      ch=cin.get();  

8.      cout<<ch;  

9.      ch=cin.get();  

10.     cout<<ch;  

11.     system("pause");  

12. }  

#include <iostream>

#include <string>

using namespace std;

void main()

{

char ch;

ch=cin.get();

cout<<ch;

ch=cin.get();

cout<<ch;

system("pause");

}

正确代码:

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  #include <string>  

3.  using namespace std;  

4.  void main()  

5.  {  

6.      char ch;  

7.      ch=cin.get();  

8.      cin.get();// 接收回车  

9.      cout<<ch;  

10.     ch=cin.get();  

11.     cin.get(); //接收回车  

12.     cout<<ch;  

13.     system("pause");  

14. }  

#include <iostream>

#include <string>

using namespace std;

void main()

{

char ch;

ch=cin.get();

cin.get();// 接收回车

cout<<ch;

ch=cin.get();

cin.get(); //接收回车

cout<<ch;

system("pause");

}

 

 

 

 

21.C++_文本文件读写常用代码

分类:C++2011-07-29 00:00539人阅读评论(0)收藏举报

c++stringsystemfile测试

数据测试:有一个文件file.txt,内容入下:

[html]view plaincopyprint?

1.  This life will always love you.  

2.  Let me always love you!   

3.  If  you have locked in my memory, and that the key to keeping your life on it for me.   

This life will always love you.

Let me always love you!

If  you have locked in my memory,and that the key to keeping your life on it for me.

说明:省略了一些判断,如文件打开失败

1、逐词读入,放到字符串中,逐词处理

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  #include <fstream>  

3.  #include <string>  

4.  using namespace std;  

5.  void main()  

6.  {  

7.      ifstream file("file.txt");  

8.      string word;  

9.      while (file>>word) // >>:读取成功,返回true,读取失败返回false   

10.     {  

11.         cout<<word<<endl;  

12.     }  

13.     system("pause");  

14. }  

#include <iostream>

#include <fstream>

#include <string>

using namespace std;

void main()

{

ifstream file("file.txt");

string word;

while (file>>word) //>>:读取成功,返回true,读取失败返回false

{

   cout<<word<<endl;

}

system("pause");

}

运行结果:见一个单词换一行。

This

life

......

for

 me.

2、逐行输入,并放到字符数组中,逐行处理

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  #include <fstream>  

3.  using namespace std;  

4.  void main()  

5.  {  

6.      const int len=100;  

7.      char str[len];  

8.      ifstream file("file.txt");  

9.      while (file.getline(str,len))//len表示这次输入字符串的最大值,getline读取成功,返回true,读取失败返回false   

10.     {  

11.         cout<<str<<endl;  

12.     }  

13.     system("pause");  

14. }  

#include <iostream>

#include <fstream>

using namespace std;

void main()

{

const int len=100;

char str[len];

ifstream file("file.txt");

while (file.getline(str,len))//len表示这次输入字符串的最大值,getline读取成功,返回true,读取失败返回false

{

   cout<<str<<endl;

}

system("pause");

}

运行结果:见一句换一行

This life willalways love you.---第一次循环
Let me always love you!  ---
第二次循环
If  you have locked in my memory, and that the key to keeping your life onit for me. ---
第三次循环

注意:

   1)这个getline函数是 ifstream的成员函数

    2)如果一行中的数据长度超过len,就会读取失败,返回false

   3)第一个参数是 指针类型*,不能使用string类型

3 逐行输入,存放字符串中,逐行处理

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  #include <fstream>  

3.  #include <string>  

4.  using namespace std;  

5.  void main()  

6.  {  

7.      string str;  

8.      ifstream file("file.txt");  

9.      while(getline(file,str)) //getline:读取成功,返回true,读取失败返回false   

10.     {  

11.         cout<<str<<endl;  

12.     }  

13.     system("pause");  

14. }  

#include <iostream>

#include <fstream>

#include <string>

using namespace std;

void main()

{

string str;

ifstream file("file.txt");

while(getline(file,str)) //getline:读取成功,返回true,读取失败返回false

{

   cout<<str<<endl;

}

system("pause");

}

注意:这个getline函数是 string 头文件中提供的一个函数,与上一个例子不同,使用要加头文件#include <string>

运行结果:见一句换一行

This life willalways love you.---第一次循环
Let me always love you!  ---
第二次循环
If  you have locked in my memory, and that the key to keeping your life onit for me. ---
第三次循环

注意:>>从文件读入内存时,见到空格、Tab、回车会停止接受

          file.getline():一行为单位,见到回车停止,但是见空格、Tab不停止

 

 

 

 

 

 

 

22.C++_文件读写

分类:C++2011-07-28 22:051070人阅读评论(0)收藏举报

c++systembufferiostreamios存储

相关的头文件:#include <fstream>

需要相关的类

fstream提供三种类,实现C++对文件的操作

ofstream:写操作由ostream引申而来

ifstream:读操作,由istream引申而来 

fstream :同时读写操作,由iostream引申而来 

文件的类型:文本文件二进制文件

文件读写的步骤:

1、包含的头文件:#include <fstream>

2、创建流

3、打开文件(文件和流关联)

4、读写 (写操作:<<,put( ), write( )读操作:>> , get( ),getline( ), read( ))

5、关闭文件:把缓冲区数据完整地写入文件,添加文件结束标志,切断流对象和外部文件的连接

文件的读写:

1、文本文件的读写:

方法:

一次性读写若干字符

      1)使用运算符<< >>进行读写

      功能:

      << 能实现以行为单位写入文件

      >> 不能一行为单位读入内存,总是以空格、Tab、回车结束,而是以单词为单位

代码:

函数功能:使用<< ,写入文件一行字符

[cpp]view plaincopyprint?

1.  #include <fstream>  

2.  #include <iostream>  

3.  using namespace std;  

4.  void main()  

5.  {  

6.      ofstream OpenFile("file.txt");  

7.      if (OpenFile.fail())  

8.      {  

9.          cout<<"打开文件错误!"<<endl;  

10.         exit(0);  

11.     }  

12.     OpenFile<<"abc def ghi";  

13.     OpenFile.close();  

14.     system("pause");  

15. }  

#include <fstream>

#include <iostream>

using namespace std;

void main()

{

ofstreamOpenFile("file.txt");

if (OpenFile.fail())

{

   cout<<"打开文件错误!"<<endl;

   exit(0);

}

OpenFile<<"abc defghi";

OpenFile.close();

system("pause");

}

运行结果:文件中写入内容:abc def ghi

函数功能:使用>>,从文件读入一个单词

[cpp]view plaincopyprint?

1.  #include <fstream>  

2.  #include <iostream>  

3.  using namespace std;  

4.  void main()  

5.  {  

6.      const int len=20;  

7.      char str[len];  

8.      ifstream OpenFile("file.txt");  

9.      if (OpenFile.fail())  

10.     {  

11.         cout<<"打开文件错误!"<<endl;  

12.         exit(0);  

13.     }  

14.     OpenFile>>str;  

15.     cout<<str<<endl;  

16.     OpenFile.close();  

17.     system("pause");  

18. }  

#include <fstream>

#include <iostream>

using namespace std;

void main()

{

const int len=20;

char str[len];

ifstreamOpenFile("file.txt");

if (OpenFile.fail())

{

   cout<<"打开文件错误!"<<endl;

   exit(0);

}

OpenFile>>str;

cout<<str<<endl;

OpenFile.close();

system("pause");

}

运行结果:str的内容为abc,而不是abc def ghi(见空格停止)

      2)使用运算符<<()getline()进行读写

       功能:

      <<:以行为单位输入文件

      getline():以行为单位 读入内存,能一次读入一行

       函数原型:istream &getline( char *buffer,streamsize num );

      功能:getline( )函数用于从文件读取num-1个字符到buffer(内存)中,直到下列情况发生时,读取结束:

      1)num - 1个字符已经读入

       2):碰到一个换行标志

      3):碰到一个EOF

代码:

[cpp]view plaincopyprint?

1.  #include <fstream>  

2.  #include <iostream>  

3.  using namespace std;  

4.  void main()  

5.  {  

6.      const int len=20;  

7.      char str[len];  

8.      ifstream OpenFile("file.txt");  

9.      if (OpenFile.fail())  

10.     {  

11.         cout<<"打开文件错误!"<<endl;  

12.         exit(0);  

13.     }  

14.     OpenFile.getline(str,20);  

15.     cout<<str<<endl;  

16.     OpenFile.close();  

17.     system("pause");  

18. }  

#include <fstream>

#include <iostream>

using namespace std;

void main()

{

const int len=20;

char str[len];

ifstreamOpenFile("file.txt");

if (OpenFile.fail())

{

   cout<<"打开文件错误!"<<endl;

   exit(0);

}

OpenFile.getline(str,20);

cout<<str<<endl;

OpenFile.close();

system("pause");

}

运行结果:str的内容为abc def ghi (一直把一行读完)

一次读写一个字符:

使用get( )put( )函数

函数声明:istream& get(char &c);

函数功能:使用 get( )函数把字符1输入到文件

[cpp]view plaincopyprint?

1.  #include <fstream>  

2.  #include <iostream>  

3.  using namespace std;  

4.  void main()  

5.  {  

6.      char ch='1';  

7.      ofstream OpenFile("file.txt");  

8.      if (OpenFile.fail())  

9.      {  

10.         cout<<"打开文件错误!"<<endl;  

11.         exit(0);  

12.     }  

13.     OpenFile.put(ch);  

14.     OpenFile.close();  

15.     system("pause");  

16. }  

#include <fstream>

#include <iostream>

using namespace std;

void main()

{

char ch='1';

ofstream OpenFile("file.txt");

if (OpenFile.fail())

{

   cout<<"打开文件错误!"<<endl;

   exit(0);

}

OpenFile.put(ch);

OpenFile.close();

system("pause");

}

运行结果:把字符1写入文件

函数功能:使用 put( )函数把文件中第一个字符输入内存

[cpp]view plaincopyprint?

1.  #include <fstream>  

2.  #include <iostream>  

3.  using namespace std;  

4.  void main()  

5.  {  

6.      char ch;  

7.      ifstream OpenFile("file.txt");  

8.      if (OpenFile.fail())  

9.      {  

10.         cout<<"打开文件错误!"<<endl;  

11.         exit(0);  

12.     }  

13.     OpenFile.get(ch);  

14.     cout<<ch;  

15.     OpenFile.close();  

16.     system("pause");  

17. }  

#include <fstream>

#include <iostream>

using namespace std;

void main()

{

char ch;

ifstreamOpenFile("file.txt");

if (OpenFile.fail())

{

   cout<<"打开文件错误!"<<endl;

   exit(0);

}

OpenFile.get(ch);

cout<<ch;

OpenFile.close();

system("pause");

}

运行结果:把字符1从文件中读到ch(内存)中

2、二进制文件的读写:

  1)使用运算符get( )  put( )读写一个字节

功能:

      get( ) :在文件中读取一个字节到内存

函数原型:ifstream &get(char ch)

      put( ) :在内存中写入一个字节到文件

函数原型:ofstream &put(char ch)

代码:

功能:把26个字符写入文件中

[cpp]view plaincopyprint?

1.  #include <fstream>  

2.  #include <iostream>  

3.  using namespace std;  

4.  void main()  

5.  {  

6.      char ch='a';  

7.      ofstream OpenFile("file.txt",ios::binary);  

8.      if (OpenFile.fail())  

9.      {  

10.         cout<<"打开文件错误!"<<endl;  

11.         exit(0);  

12.     }  

13.     for (int i=0;i<26;i++)  

14.     {  

15.         OpenFile.put(ch);  

16.         ch++;  

17.     }  

18.     OpenFile.close();  

19.     system("pause");  

20. }  

#include <fstream>

#include <iostream>

using namespace std;

void main()

{

char ch='a';

ofstreamOpenFile("file.txt",ios::binary);

if (OpenFile.fail())

{

   cout<<"打开文件错误!"<<endl;

   exit(0);

}

for (int i=0;i<26;i++)

{

   OpenFile.put(ch);

   ch++;

}

OpenFile.close();

system("pause");

}

运行结果:文件内容为abcdefghijklmnopqlst...z

功能:把文件中的26个字母读入内存

[cpp]view plaincopyprint?

1.  #include <fstream>  

2.  #include <iostream>  

3.  using namespace std;  

4.  void main()  

5.  {  

6.      char ch;  

7.      ifstream OpenFile("file.txt",ios::binary);  

8.      if (OpenFile.fail())  

9.      {  

10.         cout<<"打开文件错误!"<<endl;  

11.         exit(0);  

12.     }  

13.     while (OpenFile.get(ch))  

14.         cout<<ch;  

15.     OpenFile.close();  

16.     system("pause");  

17. }  

#include <fstream>

#include <iostream>

using namespace std;

void main()

{

char ch;

ifstreamOpenFile("file.txt",ios::binary);

if (OpenFile.fail())

{

   cout<<"打开文件错误!"<<endl;

   exit(0);

}

while (OpenFile.get(ch))

   cout<<ch;

OpenFile.close();

system("pause");

}

运行结果:ch依次为abc...z

   2)使用read()write()进行读写

read( ):

      功能:从文件中提取 n 个字节数据,写入buf指向的地方中

      函数声明:istream &  read ( char * buf ,  int  n) ;

代码:

 函数功能:使用write( )函数,一次从内存向文件写入一行数据

[cpp]view plaincopyprint?

1.  #include <fstream>  

2.  #include <iostream>  

3.  using namespace std;  

4.  void main()  

5.  {  

6.      char ch[12]="12 3 456 78";  

7.      ofstream OpenFile("file.txt");  

8.      if (OpenFile.fail())  

9.      {  

10.         cout<<"打开文件错误!"<<endl;  

11.         exit(0);  

12.     }  

13.     OpenFile.write(ch,12);  

14.     OpenFile.close();  

15.     system("pause");  

16. }  

#include <fstream>

#include <iostream>

using namespace std;

void main()

{

char ch[12]="12 3 456 78";

ofstreamOpenFile("file.txt");

if (OpenFile.fail())

{

   cout<<"打开文件错误!"<<endl;

   exit(0);

}

OpenFile.write(ch,12);

OpenFile.close();

system("pause");

}

运行结果:文件内容12 3 456 78

write( ):

     功能:把buf指向的内容取n个字节写入文件

      函数声明:ostream & ostream :: write ( char *buf ,  int  n ) ;

     参数说明:buf表示要写入内存的地址,传参时要取地址。n表示要读入字节的长度

     注意:1):该函数遇到空字符时并不停止,因而能够写入完整的类结构

                 2):第一个参数一个char型指针(指向内存数据的起始地址),与对象结合使用的时候,要在对象地址之前要char做强制类型转换。

函数功能:使用write( )函数,一次从文件向内存写入一行数据

[cpp]view plaincopyprint?

1.  #include <fstream>  

2.  #include <iostream>  

3.  using namespace std;  

4.  void main()  

5.  {  

6.      char ch[12];  

7.      ifstream OpenFile("file.txt");  

8.      if (OpenFile.fail())  

9.      {  

10.         cout<<"打开文件错误!"<<endl;  

11.         exit(0);  

12.     }  

13.     OpenFile.read(ch,12);  

14.     cout<<ch;  

15.     OpenFile.close();  

16.     system("pause");  

17. }  

#include <fstream>

#include <iostream>

using namespace std;

void main()

{

char ch[12];

ifstreamOpenFile("file.txt");

if (OpenFile.fail())

{

   cout<<"打开文件错误!"<<endl;

   exit(0);

}

OpenFile.read(ch,12);

cout<<ch;

OpenFile.close();

system("pause");

}

运行结果:数组ch的内容为12 3 456 78

说明:

1、程序不再使用文件时,为什么要关闭文件?

因为:1)文件缓冲区是一块小的内存空间.

           2)操作系统限制同时打开的文件数量 

注意:close ( ) 函数关闭文件,但流对象仍然存在。

2、文件的默认打开方式为文本文件,要是想以二进制的方式处理,在打开时要用 ios::binary 显式声明。

3、针对文本文件操作时,get函数和>>的区别:

区别:在读取数据时,get函数包括空白字符(遇空白字符不停止读取)

           >>在默认情况下拒绝接受空白字符(遇到空白符停止读取)

4、判断文件是否打开的方法:

[cpp]view plaincopyprint?

1.  if (OpenFile)  

2.  {  

3.      cout<<"打开文件失败!";  

4.      exit(0);  

5.  }  

6.  if (OpenFile.fail())  

7.  {  

8.      cout<<"打开文件错误!"<<endl;  

9.      exit(0);  

10. }  

if (OpenFile)

{

cout<<"打开文件失败!";

exit(0);

}

if (OpenFile.fail())

{

cout<<"打开文件错误!"<<endl;

exit(0);

}

5、判断文件是否结束的方法:

        1)使用成员函数eof()可以检测到这个结束符,如果非0表示文件结束。

[cpp]view plaincopyprint?

1.  while (!OpenFile.eof())  

2.      {  

3.          //文件结束时的代码  

4.      }  

while (!OpenFile.eof())

{

//文件结束时的代码

}

         2)使用流直接检测,如果为0表示文件结束

[cpp]view plaincopyprint?

1.  while (!OpenFile)  

2.      {  

3.                    //文件结束时的代码  

4.      }  

while (!OpenFile)

{

                  //文件结束时的代码

}

        3)使用get函数,读取最后一个结束符时,返回0.读取正常情况下,返回1,并把读取的字符放到ch

[cpp]view plaincopyprint?

1.  while ( (OpenFile.get(ch) )!=EOF)  

2.      {  

3.          //成功时候的代码  

4.      }  

while ( (OpenFile.get(ch) )!=EOF)

{

   //成功时候的代码

}

文本文件的读写常使用的方法:使用<<写入文件,使用getline >> 读到内存

二进制文件的读写常使用的方法:使用istream 类的成员函数read write 来实现,

这两个成员函数的原型为:

[cpp]view plaincopyprint?

1.  istream& read(char *buffer,int len);  

2.  ostream& write(const char * buffer,int len);   

istream& read(char *buffer,int len);

ostream& write(const char * buffer,int len);

参数说明:字符指针 buffer 指向内存中一段存储空间。len 是读/写的字节数。

与对象结合写入二进制文件时:

       write函数调用语句:

[cpp]view plaincopyprint?

1.  输出文件流对象名.write((char*)& 对象名,sizeof(<对象所属类名>));  

2.  输出文件流对象名.write((char*)& 对象数组名[下标],sizeof(<对象所属类名>));  

输出文件流对象名.write((char*)& 对象名,sizeof(<对象所属类名>));

输出文件流对象名.write((char*)& 对象数组名[下标],sizeof(<对象所属类名>));

       read函数调用语句:

[cpp]view plaincopyprint?

1.  输入文件流对象名.read((char*)& 对象名,sizeof(<对象所属类名>));          

2.  输入文件流对象名.read((char*)& 对象数组名[下标],sizeof(<对象所属类名>));      

输入文件流对象名.read((char*)& 对象名,sizeof(<对象所属类名>));       

输入文件流对象名.read((char*)& 对象数组名[下标],sizeof(<对象所属类名>));    

注意:gcount()函数经常和read函数配合使用,用来获得实际读取的字节数。

 

 

 

23.C++_模板举例_使用模板实现集合类(堆栈)

分类:C++2011-07-27 19:32572人阅读评论(0)收藏举报

c++classsystem

[cpp]view plaincopyprint?

1.  //stack集合类是一个简单的堆栈的实现。  

2.  //这里有两个模板参数,Tsize,指定堆栈中的元素类型和堆栈中项数的最大值。  

3.  //push  pop成员函数添加和删除堆栈中的项,并在堆栈底部增加。  

4.  #include <iostream>  

5.  #include <algorithm>  

6.  #include <string>  

7.  using namespace std;  

8.  template <class T,int size> //出错点  

9.  class stack  

10. {  

11. private:  

12.     T items[size];  

13.     int top;  

14.     const int MaxSize;  

15. public:  

16.     stack():MaxSize(size)  

17.     {  

18.         top=-1;//指向栈底  

19.     }  

20.     bool IsFull();  

21.     bool IsEmpty();  

22.     void push(const T item);  

23.     T pop();  

24. };  

25.   

26. template <class T,int size>  

27. bool stack<T,size>::IsFull()  

28. {  

29.     if(top==MaxSize-1)  

30.     {  

31.         return true;  

32.     }  

33.     else  

34.     {  

35.         return false;  

36.     }  

37. }  

38.   

39. template <class T,int size>  

40. bool stack<T,size>::IsEmpty()  

41. {  

42.     if (top==-1)  

43.     {  

44.           

45.         return true;  

46.     }  

47.     else  

48.     {  

49.         return false;  

50.     }  

51. }  

52.   

53. template <class T,int size>//出错点  

54. void stack<T,size>::push(const T item)  

55. {  

56.     if (IsFull())  

57.     {  

58.         cout<<"stack is full"<<endl;  

59.         return;  

60.     }  

61.     else  

62.     {  

63.         items[++top]=item;  

64.     }  

65. }  

66.   

67. template <class T,int size>  

68. T stack<T,size>::pop()  

69. {  

70.     if (!IsEmpty())  

71.     {  

72.         return items[top--];  

73.     }  

74.     else  

75.     {  

76.         cout<<"栈空"<<endl;  

77.         return 0;  

78.     }  

79. }  

80.   

81. void main()  

82. {  

83.     //stack<string,5> s;//栈中存放字符串  

84.     stack<int,5> s;   //栈中存放整型数据  

85.     while(!s.IsFull())  

86.     {  

87.         //s.push("123");//字符串入栈  

88.         s.push(1);//整型数据入栈  

89.     }  

90.     while(!s.IsEmpty())  

91.     {  

92.         cout<<s.pop()<<endl;  

93.     }  

94.     system("pause");  

95. }  

 

 

 

 

 

 

24.C++_类模板基础知识

分类:C++2011-07-26 18:391130人阅读评论(3)收藏举报

c++classvectorstringc编程

类模板与模板类

为什么要引入类模板:类模板是对一批仅仅成员数据类型不同的类的抽象,程序员只要为这一批类所组成的整个类家族创建一个类模板,给出一套程序代码,就可以用来生成多种具体的类,(这类可以看作是类模板的实例),从而大大提高编程的效率。

注意:

1、类模板是参数化的类,即用于实现数据类型参数化的类。

2、应用类模板可以使类中的数据成员、成员函数的参数及成员函数的返回值,能根据模板参数匹配情况取任意数据类型

语法:

//类模板的定义

[cpp]view plaincopyprint?

1.  template <类型形参表>   

2.  class 类名   

3.  {    

4.   //类说明体  

5.  };  

template <类型形参表>

class 类名

 //类说明体

};

//类外成员函数的定义

[cpp]view plaincopyprint?

1.  template <类型形参表>  //不要漏了啊。  

2.  返回值类型  类模板名<类型名表>::成员函数名(参数表)   

3.  {   

4.      //成员函数体    

5.  }  

template <类型形参表>  //不要漏了啊。

返回值类型  类模板名<类型名表>::成员函数名(参数表)

{

//成员函数体

}

类模板的实例化:

[cpp]view plaincopyprint?

1.  类模板名 <实际类型>  对象名(实参表)   

类模板名 <实际类型>  对象名(实参表);

具体的使用过程:

当类模板创建对象时,根据用户给出的实际类型,系统将类模板中的模板参数置换为确定的参数类型,生成一个具体的类(模板类)

有对象之后,和普通类的对象使用方法相同。  

注意:

1、模板类的成员函数必须是函数模板

2、类模板中的成员函数的定义,若放在类模板中,则与普通类的成员函数的定义方法相同;若在类模板之外定义,则成员函数的定义格式如上

书写代码时要注意类模板和普通类的代码区别:

1、针对类整体而言,在类定义前必须有模板声明

2、针对类外定义的成员函数(每个函数都有)而言,定义时必须要冒(模板声明) + 在函数名前、类后必须加<Type>

3、针对类的对象而言,在建立对象时,必须在类名和对象之间要写实际类型<实际类型>

模板类的形参表:template <类型形参表>

模板形参表类似函数形参表,由类型、非类型和模板三部分组成。

类型形参:代表所有的数据类型-所有的均可代表(类类型+预定义类型)

格式:由关键字classtypename + 名字组成

例如:template <class T,class D>,其中TD都是一个类型形参,名字可以有自己定义

用途:这三种形参作用一样,均可以模板类型形参可作为类型说明符用在模板中的任何地方,即可以用于指定反回类型,变量声明等。

替代时间:类模板的类型实参必须在实例化类对象的时候指定

说明:这里的class表示一类类型,T是在实例化时确定,它的替代可以是任何类型,即类类型,预定义数据类型,比较通用

 

非类型形参=内置类型形参:代表具体的一类(C++的标准数据类型的一种,如整型)

格式:由标准数据类型 + 名字组成

例如:template <int a, int b>,其中ab都是非类型形参template<class T, int a>,T为类型参数,a为非类型参数

参数有三种情况:

1、整型或枚举类型

2、指针类型(包含普通对象的指针类型、函数指针类型、指向成员的指针类型)

3、引用类型(指向对象或指向函数的引用都是允许的)

有些常值不能作为有效的非类型实参,包括:

1、空指针常量

2、浮点型值

3、字符串

说明:

1、非类型形参在模板定义的内部是常量值,也就是说非类型形参在模板的内部是常量,或者说只能是右值,它们不能被取址,也不能被赋值

2、调用非类型模板形参的实参必须是一个常量表达式,或者是一个数,即它必须能在编译时计算出结果。

举例:

[cpp]view plaincopyprint?

1.  template <class T, int size>   

2.  class A  

3.  {  

4.    

5.  };  

6.  A<int 10> a; //正确  

7.  int b;  

8.  A<int, b> a; //错误,b不是常量  

9.  const int c;  

10. A<int, c> a; //正确,c是常量  

template <class T, int size>

class A

{

 

};

A<int 10> a; //正确

int b;

A<int, b> a; //错误,b不是常量

const int c;

A<int, c> a; //正确,c是常量

非类型模板形参还可以使用缺省值

[cpp]view plaincopyprint?

1.  template <class T, int Size=100>  

2.  class A  

3.  {  

4.    

5.  };  

6.  A<int>a;//使用缺省值,设置长度100   

7.  A<int,200>a;//不使用缺省值长度设置200  

template <class T, int Size=100>

class A

{

 

};

A<int>a;//使用缺省值,设置长度100

A<int,200>a;//不使用缺省值长度设置200

说明:

百度说,非类型形参一般不应用于函数模板

非类型模板形参的形参和实参间所允许的转换 <引用黄邦勇帅的总结。>

1、允许从数组到指针,从函数到指针的转换。如:template <int *a> class A{}; intb[1]; A<b> m;即数组到指针的转换

2const修饰符的转换。如:template<const int *a> class A{};int b; A<&b> m; 即从int *const int *的转换。

3、提升转换。如:template<int a> class A{}; constshort b=2; A<b> m; 即从shortint的提升转换

4、整值转换。如:template<unsigned int a> classA{}; A<3> m; 即从intunsigned int的转换。

5、常规转换。

模板形参:一个模板作为另一个模板的参数

格式:

[html]view plaincopyprint?

1.  template <class A,class B,template<class C> class D>   

2.  class E  

3.  {//类模板E的定义}  

template <class A,class B,template<class C> class D>

class E

{//类模板E的定义}

说明:

1template<class C> class D 是一个模板。

2、定义一个模板E,其中有两个类型参数AB,一个模板参数D。注意C有时候可以省略不写

模板参数使用说明:

1、首先要定义一个模板:用来实例化模板E的对象。

2AB可以是任意类型;但是D可以是程序员自己定义的,也可以是STL中的标准模板库

代码:

[cpp]view plaincopyprint?

1.  //首先定义一个普通的模板  

2.  template<class T>  

3.  class Array   

4.  {  

5.     //模板Array的成员  

6.  };  

7.  //定义一个模板Container,参数列表:一个类型为T,另一个模板类型Seq   

8.  template<class T, template<classclass Seq>//这里Seq前面的<>没有写参数名字  

9.  class Container   

10. {  

11.     Seq<T> seq;  

12.     ... ...  

13. };  

14. 注意:注意Seq模板的声明并没有带模板参数的名字,因为这里我们不会用到这个参数,所以可以省略不写.  

//首先定义一个普通的模板

template<class T>

class Array

{

   //模板Array的成员

};

//定义一个模板Container,参数列表:一个类型为T,另一个模板类型Seq

template<class T, template<class> class Seq>//这里Seq前面的<>没有写参数名字

class Container

{

Seq<T> seq;

... ...

};

注意:注意Seq模板的声明并没有带模板参数的名字,因为这里我们不会用到这个参数,所以可以省略不写.

实例化:

[cpp]view plaincopyprint?

1.  Container<int, Array> container;  

Container<int, Array> container;

 说明:模板第一个参数为int,第二个参数为自己定义的模板(注意这里Array也代表一个类型,用户自定义的数据类型)

[cpp]view plaincopyprint?

1.  Container<int, vector<int>> container;  

Container<int, vector<int>> container;

说明:模板第一个参数为int,第二个参数为STL中提供的模板(注意要使用实例化后的类型,因为这里参数必须是类型)
不知道直接写vector能不能用,是不是直接写vector时,下面使用seq时,要加<int>seq<int>,写了就不加<int>:seq.
等以后用到在确定下???

用途:这样定义一个泛化的容器,容器里所存储的对象也是泛化

分析:需要两个类型:

一个是代表容器类型,vectorlist

另一个是存放数据的类型,如intstring

这时就要使用模板的模板参数:模板参数本身含是一个模板

举例:

[cpp]view plaincopyprint?

1.  template<class TElem,template<class Type  

2.                      ,class Allocator=allocator<Type>> class TContainer>  

3.  class MyContainer : public TContainer<TElem>  

4.  {  

5.    

6.  };  

template<class TElem,template<class Type

                    ,classAllocator=allocator<Type>> class TContainer>

class MyContainer : public TContainer<TElem>

{

 

};

实例化时就可以:

[cpp]view plaincopyprint?

1.  MyContainer<int,vector> myContainer;  

MyContainer<int,vector> myContainer;

说明:这里参数也可以使用默认的参数,就像函数中的确省参数是一样的,如class Allocator=allocator<Type>

 

 

 

 

 

 

 

 

 

 

 

 

 

25.C++_函数模板基础知识

分类:C++2011-07-26 17:41954人阅读评论(0)收藏举报

c++class编译器systemfloat

为什么要引入模板:为了避免代码重复,程序员可以编写脱离数据类型通用模板。

模板的分类:函数模板 + 类模板

注意:模板的声明或定义只能在全局,命名空间或类范围内进行。不能在函数内进行,比如不能在main函数中声明或定义一个模板。

函数模板:

定义:

函数模板:关键词在后两个字,模板:提供一类函数的抽象,以任意类型T为参数把具有相同程序正文的一类函数抽象出来,可以适合任意类型T的参数。

模板函数:重点在函数,是对函数模板进行参数实例化后的结果,是一个具体的函数。

什么时候使用函数模板:会有一类函数,它们几乎相同,唯一的区别就是形参类型不同。这时我们可以撇开不同的数据类型,创建一个模板,并把数据类型也当作一个参数而设计一个模板

举例说明:编写比较两个整型数据大小的函数,它只适合于整型数据。如果是比较两个浮点型数据,就需要重新编写另外一个函数。如果我们使用函数模板的话,可以把数据类型忽略,编写一个max函数,既可以实现int,也可以实现浮点型。

语法:

[cpp]view plaincopyprint?

1.  template <class T>   //模板声明格式  

2.  返回值类型 函数名(模板形参表)   //模板函数形参表  

3.  {  

4.      //函数定义体  

5.  }  

6.  函数调用:函数名(模板实参表//和普通的函数调用没有区别,只不过运行时,系统会首先根据参数类型确定数据类型,生成一个模板函数。  

template <class T>   //模板声明格式

返回值类型 函数名(模板形参表)  //模板函数形参表

{

//函数定义体

}

函数调用:函数名(模板实参表) //和普通的函数调用没有区别,只不过运行时,系统会首先根据参数类型确定数据类型,生成一个模板函数。

说明:

       1、这里的class指一类的意思

        2、参数有两种:类型参数 + 函数参数

       3T 为类型参数,可以是预定义的数据类型,可以是用户自定义的类型(类,结构体等)。在运行中必须用实际的数据类型替代它

       4template 语句函数模板定义语句不允许有别的语句

如:

[cpp]view plaincopyprint?

1.  template <class T>  

2.  int i;            //这是不允许的.   

3.  T max(T a , T b)  

4.  {  

5.      return (a>b)?a:b;  

6.  }  

template <class T>

int i;            //这是不允许的.

T max(T a , T b)

{

return (a>b)?a:b;

}

举例:求两个数最大值,使用模板

[cpp]view plaincopyprint?

1.  template <class T>   

2.  T max(T a , T b)  

3.  {  

4.      return (a>b)?a:b;  

5.  }  

6.  调用:  

7.  int i1,i2;  

8.  flaot f1,f2;  

9.  max(i1,i2);  

10. max(f1,f2);  

template <class T>

T max(T a , T b)

{

return (a>b)?a:b;

}

调用:

int i1,i2;

flaot f1,f2;

max(i1,i2);

max(f1,f2);

运行过程可分两部分:

     第一、传递参数类型:用模板实参int类型参数T进行实例化。即用 int  代替T

     第二、传递参数:把参数传递给函数

函数模板的重载:

引入函数模板重载的原因:函数模板要求要传入参数类型必须全都相同,不同则报错。使用函数模板重载就是要解决这个问题

引入两种方法解决这个问题:

       1、函数模板可以使用多个模板参数:有几个不同的类型,就是用不同的模板参数来代表它。

       2、超载一个函数模板:定义一个模板后,在定义一个超载函数

具体来说:

      1、定义一个多参的函数模板:

[cpp]view plaincopyprint?

1.  template <class T,class D>  

2.  T max(T a,D b)  

3.  {  

4.      return (a>b)?a:b;  

5.  }  

template <class T,class D>

T max(T a,D b)

{

return (a>b)?a:b;

}

调用:

[cpp]view plaincopyprint?

1.  int afloat b  

2.  max(a,b)  

int a;float b

max(a,b)

       2、超载一个函数模板

[cpp]view plaincopyprint?

1.  template <class T>  

2.  T max(T a,T b)  

3.  {  

4.      return (a>b)?a:b;  

5.  }  

6.  int max(int,int);//用户自己定义一个同名函数  

template <class T>

T max(T a,T b)

{

return (a>b)?a:b;

}

int max(int,int);//用户自己定义一个同名函数

[cpp]view plaincopyprint?

1.  参数调用:int m,n;  

2.  char a,b;  

3.  char s[10];  

4.  max(m,n);//调用函数 实参和函数的参数类型完全一致,直接调用函数  

5.  max(a,b);//调用模板 实参和函数参数类型不同,直接掉模板  

6.  max(m,a);//调用函数 实参的类型不同,模板不适合,则再次调用函数  

7.  max(m,s);//报错-函数也不适合,直接报错  

参数调用:int m,n;

char a,b;

char s[10];

max(m,n);//调用函数 实参和函数的参数类型完全一致,直接调用函数

max(a,b);//调用模板 实参和函数参数类型不同,直接掉模板

max(m,a);//调用函数 实参的类型不同,模板不适合,则再次调用函数

max(m,s);//报错-函数也不适合,直接报错

分析:调用函数时的顺序:先调用函数 ---  其次套用模板  --- 再调用函数 --- 报错

运行步骤:

如果调用语句的实参类型和函数类型完全一致,这时不找模板,而优先使用函数。

如果调用语句的实参类型和函数类型不一致,应该找模板

如果调用语句的实参各自类型不同,应重新调用函数,并试着把实参类型转换为形参类型,成功则调用,失败,则报错

函数模板和类结合

举例:

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  using namespace std;  

3.  class Point  

4.  {  

5.  private:  

6.      int x;  

7.  public:  

8.      Point(int x1);  

9.      bool operator>(const Point& p);  

10.     friend ostream& operator<<(ostream& cout,const Point& p);  

11. };  

12. Point::Point(int x1)  

13. {  

14.     x=x1;  

15. }  

16. bool Point::operator>(const Point& p)  

17. {  

18.     return (x>p.x)?true:false;  

19. }  

20. ostream& operator<<(ostream& cout,const Point& p)  

21. {  

22.     cout<<p.x;  

23.     return cout;  

24. }  

25. template <class T>  

26. T& max(T& a,T& b)  

27. {  

28.     cout<<"调用函数模板"<<endl;  

29.     return (a>b)?a:b;  

30. }  

31. void main()  

32. {  

33.     Point a(1);  

34.     Point b(2);  

35.     cout<<max(a,b);  

36.     system("pause");  

37. }  

#include <iostream>

using namespace std;

class Point

{

private:

int x;

public:

Point(int x1);

bool operator>(const Point&p);

friend ostream&operator<<(ostream& cout,const Point& p);

};

Point::Point(int x1)

{

x=x1;

}

bool Point::operator>(const Point& p)

{

return (x>p.x)?true:false;

}

ostream& operator<<(ostream& cout,const Point& p)

{

cout<<p.x;

return cout;

}

template <class T>

T& max(T& a,T& b)

{

cout<<"调用函数模板"<<endl;

return (a>b)?a:b;

}

void main()

{

Point a(1);

Point b(2);

cout<<max(a,b);

system("pause");

}

 什么时候使用模板:

1、创建一个类型安全的集合类(如,堆栈)用来处理各种类型的数据

2、为函数添加额外的类型检查以避免获得空指针

模板的优势:为函数大多数以上应用可以不用模板实现

模板具有以下几个优势:
1
、开发容易。你可以只为你的类或函数创建一个普通的版本代替手工创建特殊情况处理。

2、理解容易。模板为抽象类型信息提供了一个直截了当的方法。

3、类型安全。模板使用的类型在编译时是明确的,编译器可以在发生错误之前进行类型检查。

 

 

 

 

 

26.C++_运算符重载

分类:C++2011-07-23 11:5420955人阅读评论(20)收藏举报

c++iostreamclassc

什么是运算符的重载?

         运算符与类结合,产生新的含义。 

为什么要引入运算符重载?

        作用:为了实现类的多态性(多态是指一个函数名有多种含义)

怎么实现运算符的重载?

方式:类的成员函数友元函数(类外的普通函数)

规则:不能重载的运算符有 .   .* ?: ::  sizeof

友元函数和成员函数的使用场合:一般情况下,建议一元运算符使用成员函数,二元运算符使用友元函数

       1、运算符的操作需要修改类对象的状态,则使用成员函数。如需要做左值操作数的运算符(如=+=++

        2、运算时,有数和对象的混合运算时,必须使用友元

       3、二元运算符中,第一个操作数为非对象时,必须使用友元函数。如输入输出运算符<<>>

具体规则如下:

运算符

建议使用

所有一元运算符

成员函数

( ) [ ]  ->

必须是成员函数

+= -= /= *= ^= &= != %= >>= <<= , 似乎带等号的都在这里了.

成员函数

所有其它二元运算符, 例如: –,+,*,/

友元函数

<< >>

必须是友元函数

2. 参数和返回值

    当参数不会被改变,一般按const引用来传递(若是使用成员函数重载,函数也为const).

    对于返回数值的决定:

    1) 如果返回值可能出现在=号左边, 则只能作为左值, 返回非const引用。

    2) 如果返回值只能出现在=号右边, 则只需作为右值, 返回const型引用或者const型值。

    3) 如果返回值既可能出现在=号左边或者右边, 则其返回值须作为左值, 返回非const引用。

运算符重载举例:

+ -运算符的重载:

[cpp]view plaincopyprint?

1.  class Point    

2.  {    

3.  private:    

4.      int x;   

5.  public:    

6.      Point(int x1)  

7.      {   x=x1;}    

8.      Point(Point& p)     

9.      {   x=p.x;}  

10.     const Point operator+(const Point& p);//使用成员函数重载加号运算符  

11.     friend const Point operator-(const Point& p1,const Point& p2);//使用友元函数重载减号运算符  

12. };    

13.   

14. const Point Point::operator+(const Point& p)  

15. {  

16.     return Point(x+p.x);  

17. }  

18.   

19. Point const operator-(const Point& p1,const Point& p2)  

20. {  

21.     return Point(p1.x-p2.x);  

22. }  

class Point 

private: 

int x;

public: 

Point(int x1)

{  x=x1;} 

Point(Point& p)  

{  x=p.x;}

const Point operator+(constPoint& p);//使用成员函数重载加号运算符

friend const Point operator-(constPoint& p1,const Point& p2);//使用友元函数重载减号运算符

}; 

 

const Point Point::operator+(const Point& p)

{

return Point(x+p.x);

}

 

Point const operator-(const Point& p1,const Point& p2)

{

return Point(p1.x-p2.x);

}

调用:

[cpp]view plaincopyprint?

1.  Point a(1);    

2.  Point b(2);  

3.  a+b;  //正确,调用成员函数  

4.  a-b;  //正确,调用友元函数  

5.  a+1;  //正确,先调用类型转换函数,把1变成对象,之后调用成员函数  

6.  a-1;  //正确,先调用类型转换函数,把1变成对象,之后调用友元函数  

7.  1+a;  //错误,调用成员函数时,第一个操作数必须是对象,因为第一个操作数还有调用成员函数的功能  

8.  1-a;  //正确,先类型转换 后调用友元函数  

Point a(1); 

Point b(2);

a+b;  //正确,调用成员函数

a-b;  //正确,调用友元函数

a+1;  //正确,先调用类型转换函数,把1变成对象,之后调用成员函数

a-1;  //正确,先调用类型转换函数,把1变成对象,之后调用友元函数

1+a;  //错误,调用成员函数时,第一个操作数必须是对象,因为第一个操作数还有调用成员函数的功能

1-a;  //正确,先类型转换 后调用友元函数

总结:

1、由于+ -都是出现在=号的右边,如c=a+b,即会返回一个右值,可以返回const型值
2
、后几个表达式讨论的就是,数和对象混合运算符的情况,一般出现这种情况,常使用友元函数

3双目运算符的重载:

     重载运算符函数名:operator@(参数表)

      隐式调用形式:obj1+obj2

     显式调用形式:obj1.operator+(OBJ obj2)---成员函数

                                 operator+(OBJ obj1OBJ obj2)---友元函数

     执行时,隐式调用形式和显式调用形式都会调用函数operator+()

++--运算符的重载:

[cpp]view plaincopyprint?

1.  class Point    

2.  {    

3.  private:    

4.      int x;   

5.  public:    

6.      Point(int x1)  

7.      {   x=x1;}    

8.      Point operator++();//成员函数定义自增  

9.      const Point operator++(int x); //后缀可以返回一个const类型的值  

10.     friend Point operator--(Point& p);//友元函数定义--   

11.     friend const Point operator--(Point& p,int x);//后缀可以返回一个const类型的值  

12. };    

13.   

14. Point Point::operator++()//++obj   

15. {  

16.     x++;  

17.     return *this;  

18. }  

19. const Point Point::operator++(int x)//obj++   

20. {  

21.     Point temp = *this;  

22.     this->x++;  

23.     return temp;  

24. }  

25. Point operator--(Point& p)//--obj   

26. {  

27.     p.x--;  

28.     return p;  

29.          //前缀形式(--obj)重载的时候没有虚参,通过引用返回*this  自身引用,也就是返回变化之后的数值  

30. }  

31. const Point operator--(Point& p,int x)//obj--   

32. {  

33.     Point temp = p;  

34.     p.x--;  

35.     return temp;  

36.          // 后缀形式obj--重载的时候有一个int类型的虚参返回原状态的拷贝  

37. }  

class Point 

private: 

int x;

public: 

Point(int x1)

{  x=x1;} 

Point operator++();//成员函数定义自增

const Point operator++(int x); //后缀可以返回一个const类型的值

friend Point operator--(Point&p);//友元函数定义--

friend const Pointoperator--(Point& p,int x);//后缀可以返回一个const类型的值

}; 

 

Point Point::operator++()//++obj

{

x++;

return *this;

}

const Point Point::operator++(int x)//obj++

{

Point temp = *this;

this->x++;

return temp;

}

Point operator--(Point& p)//--obj

{

p.x--;

return p;

         //前缀形式(--obj)重载的时候没有虚参,通过引用返回*this 或 自身引用,也就是返回变化之后的数值

}

const Point operator--(Point& p,int x)//obj--

{

Point temp = p;

p.x--;

return temp;

         // 后缀形式obj--重载的时候有一个int类型的虚参, 返回原状态的拷贝

}

函数调用:

[cpp]view plaincopyprint?

1.  <PRE class=cpp name="code">Point a(1);  

2.  Point b(2);  

3.  a++;//隐式调用成员函数operator++(0),后缀表达式  

4.  ++a;//隐式调用成员函数operator++(),前缀表达式  

5.  b--;//隐式调用友元函数operator--(0),后缀表达式  

6.  --b;//隐式调用友元函数operator--(),前缀表达式  

7.  cout<<a.operator ++(2);//显式调用成员函数operator ++(2),后缀表达式  

8.  cout<<a.operator ++();//显式调用成员函数operator ++(),前缀表达式  

9.  cout<<operator --(b,2);//显式调用友元函数operator --(2),后缀表达式  

10. cout<<operator --(b);//显式调用友元函数operator --(),前缀表达式 </PRE>  

[cpp]view plaincopyprint?

1.Point a(1);  

2.Point b(2);  

3.a++;//隐式调用成员函数operator++(0),后缀表达式  

4.++a;//隐式调用成员函数operator++(),前缀表达式  

5.b--;//隐式调用友元函数operator--(0),后缀表达式  

6.--b;//隐式调用友元函数operator--(),前缀表达式  

7.cout<<a.operator ++(2);//显式调用成员函数operator ++(2),后缀表达式  

8.cout<<a.operator ++();//显式调用成员函数operator ++(),前缀表达式  

9.cout<<operator --(b,2);//显式调用友元函数operator --(2),后缀表达式  

10.cout<<operator --(b);//显式调用友元函数operator --(),前缀表达式   

Point a(1);

Point b(2);

a++;//隐式调用成员函数operator++(0),后缀表达式

++a;//隐式调用成员函数operator++(),前缀表达式

b--;//隐式调用友元函数operator--(0),后缀表达式

--b;//隐式调用友元函数operator--(),前缀表达式

cout<<a.operator ++(2);//显式调用成员函数operator ++(2),后缀表达式

cout<<a.operator ++();//显式调用成员函数operator ++(),前缀表达式

cout<<operator --(b,2);//显式调用友元函数operator --(2),后缀表达式

cout<<operator --(b);//显式调用友元函数operator --(),前缀表达式 

 总结:

1a++

      函数返回:temp(临时变量)

      函数返回是否是const类型:返回是一个拷贝后的临时变量),不能出现在等号的左边(临时变量不能做左值),函数的结果只能做右值,则要返回一个const类型的值

     ++a

      函数返回:*this;

     函数返回是否是const类型:返回原状态的本身,返回值可以做左值,即函数的结果可以做左值,则要返回一个非const类型的值

2、前后缀仅从函数名(operator++)无法区分,只能有参数区分,这里引入一个虚参数int xx可以是任意整数。

3单目运算符的重载:

     重载运算符函数名:operator@(参数表)

      隐式调用形式:obj1@   @obj1

     显式调用形式:

            成员函数:

                   obj1.operator@( )//前缀

                   obj1.operator@(0)//后缀

             友元函数:

                   operator@(OBJ obj)//前缀

                   operator@(OBJ obj,int x)//后缀

      执行时,隐式调用形式和显式调用形式都会调用函数operator@()

 重载下标运算符[ ]

[cpp]view plaincopyprint?

1.  class Point    

2.  {    

3.  private:    

4.      int x[5];   

5.  public:    

6.      Point()  

7.      {  

8.          for (int i=0;i<5;i++)  

9.          {  

10.             x[i]=i;  

11.         }  

12.     }   

13.     int& operator[](int y);  

14. };    

15. int& Point::operator[](int y)  

16. {  

17.     static int t=0;  

18.     if (y<5)  

19.     {  

20.         return x[y];  

21.     }  

22.     else  

23.     {  

24.         cout<<"下标出界";  

25.         return t;  

26.     }     

27. }  

class Point 

private: 

int x[5];

public: 

Point()

{

   for (int i=0;i<5;i++)

   {

    x[i]=i;

   }

}

int& operator[](int y);

}; 

int& Point::operator[](int y)

{

static int t=0;

if (y<5)

{

   return x[y];

}

else

{

   cout<<"下标出界";

   return t;

}

}

调用:

[cpp]view plaincopyprint?

1.  Point a;  

2.  for (int i=0;i<10;i++)  

3.  {  

4.           cout<<a[i]<<endl;//无论i下标是否越界,每当使用a[i]时,都会调用[]的重载  

5.  }  

6.  a[0]=10;  

Point a;

for (int i=0;i<10;i++)

{

        cout<<a[i]<<endl;//无论i下标是否越界,每当使用a[i]时,都会调用[]的重载

}

a[0]=10;

重载下标运算符[ ]的目的:

         1、对象[x]  类似于数组名[x],更加符合习惯

         2、可以对下标越界作出判断

语法:

       重载方式:只能使用成员函数重载

        函数名:operator[ ](参数表)

       参数表:一个参数,且仅有一个参数,该参数设定了下标值,通常为整型,但是也可以为字符串( 看成下标)

        函数调用:显式调用:Obj[arg]-对象[下标]

                             隐式调用:obj.operator[ ](arg)  

       返回类型:

              1、返回函数引用 + 返回成员的实际类型(由程序员根据函数体定义)

              2、因为返回值可以做左值和右值,应该不使用返回值为const类型

                    但是,为了能访问const对象,下标运算符重载有非constconst两个版本。(待定写)

 如:int&  Point::operator[](int y)//为什么使用返回引用:返回的值可以做左值,也可以做右值,则必须使用返回引用

 重载运算符( )

[cpp]view plaincopyprint?

1.  class Point    

2.  {    

3.  private:    

4.      int x;   

5.  public:    

6.      Point(int x1)  

7.      {   x=x1;}    

8.      const int operator()(const Point& p);  

9.  };    

10.   

11. const int Point::operator()(const Point& p)  

12. {  

13.     return (x+p.x);  

14. }  

class Point 

private: 

int x;

public: 

Point(int x1)

{  x=x1;} 

const int operator()(constPoint& p);

}; 

 

const int Point::operator()(const Point& p)

{

return (x+p.x);

}

[cpp]view plaincopyprint?

1.  调用:  

2.  Point a(1);  

3.  Point b(2);  

4.  cout<<a(b);  

调用:

Point a(1);

Point b(2);

cout<<a(b);

重载运算符( )的目的:

         1、对象( )  类似于函数名(x),更加符合习惯

语法:

       重载方式:只能使用成员函数重载

       重载后还可以继续重载

        函数名:operator( )(参数表)

       参数表:参数随意,具体根据实际情况而定。

        函数调用:显式调用:Obj(x)

                            隐式调用:obj.operator( )(x)  

       返回类型:

              1、返回成员的实际类型随意,具体由程序员根据函数体定义

              2、因为返回值只能做右值,只读,应该使用返回值为const类型


重载输入输出操作符<< >>

[cpp]view plaincopyprint?

1.  class Point    

2.  {    

3.  private:    

4.      int x;   

5.  public:    

6.      Point(int x1)  

7.      {   x=x1;}   

8.      friend ostream& operator<<(ostream& cout,const Point& p);//使用友元函数重载<<输出运算符  

9.      friend istream& operator>>(istream& cin,Point& p);//使用友元函数重载>>输出运算符  

10. };    

11. ostream& operator<<(ostream& cout,const Point& p)  

12. {  

13.     cout<<p.x<<endl;  

14.     return cout;  

15. }  

16. istream& operator>>(istream& cin,Point& p)  

17. {  

18.     cin>>p.x;  

19.     return cin;  

20. }  

class Point 

private: 

int x;

public: 

Point(int x1)

{  x=x1;}

friend ostream&operator<<(ostream& cout,const Point& p);//使用友元函数重载<<输出运算符

friend istream&operator>>(istream& cin,Point& p);//使用友元函数重载>>输出运算符

}; 

ostream& operator<<(ostream& cout,const Point& p)

{

cout<<p.x<<endl;

return cout;

}

istream& operator>>(istream& cin,Point& p)

{

cin>>p.x;

return cin;

}

[cpp]view plaincopyprint?

1.  调用:  

2.  Point a(1);  

3.  Point b(2);  

4.  cin>>a>>b;  

5.  cout<<a<<b<<endl;   

调用:

Point a(1);

Point b(2);

cin>>a>>b;

cout<<a<<b<<endl;

语法:

重载方式:只能使用友元函数重载使用三个引用&

函数名:

      输出流: operator<<(参数表)

      输入流:operator>>(参数表)

参数表:固定(容易出错啊),两个参数均用引用&

      输出流: 必须是两个参数:对输出流ostream&对象

                       第一个操作数cout,定义在文件iostream中,是标准类类型ostream的对象的引用。

                       如:ostream& cout,const Point& p

      输入流:必须是两个参数:对输入流ostream&对象

                      第一个操作数是cin,定义在文件iostream,实际上是标准类类型istream的对象的引用

                       如:instream& cin,const Point& p

函数调用:

       输出流: 显式调用:cout<<对象

                       隐式调用: operator<<(cout,对象)

      输入流:显式调用:cin>>对象

                       隐式调用: operator>>(cin,对象)

返回类型:返回类型固定 + 使用返回函数引用(满足连续输出)

       输出流: 返回ostream&

                       如:ostream& operator<<(ostream& cout,constPoint& p)

      输入流:返回:istream&

                        如:istream&operator>>(istream& cin,Point& p)

注意:为什么输入输出操作符的重载必须使用友元函数?

因为:成员函数要求是有对象调用,则第一个参数必须是类的对象,但是<<>>第一个参数是流的对象引用。

故,不能使用成员函数

 

 

 

 

 

27.C++_指针悬挂和赋值操作符的重载

分类:C++2011-07-22 16:50990人阅读评论(3)收藏举报

c++stringdeleteclass存储c

指针悬挂:

问题:使用new申请的内存内存空间无法访问,也无法释放。

原因:直接对指向new申请的存储空间的指针变量进行赋值修改

后果:失去了原来的地址,原来的空间无法访问也无法释放,造成内存泄漏

           还可能造成同一个内存释放两次

容易引起指针悬挂的方式:对象的初始化和对象间赋值

容易引起指针悬挂的条件:类中含有指针类型的成员时,使用默认的拷贝构造函数和赋值函数都会出现两个指针变量互相赋值,产生指针悬挂的问题。

解决方法:需要重新定义拷贝构造函数和超载赋值运算符

赋值操作符:    

 作用:两个已经存在的对象间相互赋值,产生两个完全相同的内存拷贝

 举例:string a("hello");//调用构造函数

             stringb("would");//调用构造函数

            string c=a;//调用拷贝构造函数--风格差,应使用string c(a)

             c=b; //调用拷贝赋值函数

重载赋值运算符:

语法:

[html]view plaincopyprint?

1.  X& X::operator=(const X & fm)  

2.  {  

3.      函数体  

4.  }  

X& X::operator=(const X & fm)

{

函数体

}

注意:

         1、第一个引用的作用(为什么使用返回函数引用):

               原因:为了实现对象间的连续赋值。

              使用返回函数引用的好处:结果得到的是一个变量,它既可以当左值,也可当右值,且采用赋值时没有引入临时变量,直接从原结果拷贝。

         2、第二个引用的作用:防止调用拷贝构造函数,因为拷贝构造函数也可能引起指针悬挂

         3const的作用:当参数使用引用时,可能会改变传入的参数,为了避免这样,就使用const

具体代码:

[cpp]view plaincopyprint?

1.  class Point  

2.  {  

3.  private:  

4.      char * name;  

5.  public:  

6.      Point(char * className)  

7.      {  

8.          name = new char[strlen(className)+1];  

9.          strcpy(name, className);  

10.     }  

11.     Point(const Point& p)//深拷贝  

12.     {  

13.         name = new char[strlen(p.name)+1];  

14.         strcpy(name,p.name);  

15.     }  

16.     ~Point()  

17.     {  

18.         cout<<name<<endl;  

19.         delete []name;  

20.     }  

21.     Point& operator=(const Point&p);  

22. };  

23. 系统自带的等号运算符:  

24. Point& Point::operator=(const Point&p)  

25. {  

26.     name=p.name;//造成指针悬挂  

27. }  

28. 重载后的等号运算符:  

29. Point& Point::operator=(const Point&p)  

30. {        

31.     delete[]name;//释放原来的        

32.     name=new char[strlen(p.name)+1];        

33.     strcpy(name,p.name);          

34.     return *this;      

35. }  

class Point

{

private:

char * name;

public:

Point(char * className)

{

   name = newchar[strlen(className)+1];

   strcpy(name, className);

}

Point(const Point& p)//深拷贝

{

   name = new char[strlen(p.name)+1];

   strcpy(name,p.name);

}

~Point()

{

   cout<<name<<endl;

   delete []name;

}

Point& operator=(constPoint&p);

};

系统自带的等号运算符:

Point& Point::operator=(const Point&p)

{

name=p.name;//造成指针悬挂

}

重载后的等号运算符:

Point& Point::operator=(const Point&p)

{     

delete[]name;//释放原来的     

name=newchar[strlen(p.name)+1];     

strcpy(name,p.name);       

return *this;   

}

拷贝构造函数和重载赋值运算符的代码对比:

[cpp]view plaincopyprint?

1.  Point::Point(const  Point& p)//拷贝构造函数  

2.  Point& Point::operator=(const Point&p)//重载赋值运算符  

Point::Point(const  Point& p)//拷贝构造函数

Point& Point::operator=(const Point&p)//重载赋值运算符

注意:

        1、参数都是一样的,都有const和引用,但是带他们的原因不同,具体见上面。

        2、拷贝构造函数无返回值,而重载赋值运算符使用返回函数引用。

 

 

28.C++_拷贝构造函数

分类:C++2011-07-15 23:54378人阅读评论(1)收藏举报

c++funstringdelete测试class

为什么要引入拷贝构造函数?

       作用:创建一个对象的同时,使用一个已经存在的对象给另一个对象赋值

      具体来说:它将一个已经定义过对象的数据成员 逐一拷贝给 新对象,而产生两个完全相同的内存拷贝

      做比较:拷贝构造函数:对象被创建+  用一个已经存在的对象进行初始化

                      拷贝赋值函数:对象已经存在不用创建 + 用一个已经存在的对象进行初始化

       举例:string a("hello");//调用构造函数

                   stringb("would");//调用构造函数

                  string c=a;//调用拷贝构造函数--风格差,应使用string c(a)

                   c=b;//调用拷贝赋值函数

什么时候使用拷贝构造函数?(系统自己调用)

      在创建新对象的时候,希望将一个已经存在的对象拷贝给这个新对象,这时系统会自动调用拷贝构造函数

      总结:1、拷贝构造函数的参数必须是引用,否则出错。

                  2、执行的语句类似Coord p=p1; 则会调用拷贝构造函数                

      有三种情况:

       1)创建一个新类并使用类的一个对象初始化该类的另一个对象

              Coord p2(p1);//用对象p1初始化对象p2

              Coord p3=p1;//用对象p1初始化对象p1

       2)函数的形参是类的对象参数使用值传递(参数为引用的时候不调用拷贝构造函数),传参时,会调用拷贝构造函数

[cpp]view plaincopyprint?

1.  fun1(Coord p)  

2.  {  

3.      函数体  

4.  }  

5.  调用语句:  

6.  Coord p1  

7.  fun1(p1);  

8.  //分析:调用拷贝构造函数 Coord p=p1;  

9.    

10. fun1(Coord& p)  

11. {  

12.     函数体  

13. }  

14. 调用语句:  

15. Coord p1  

16. fun1(p1);   

fun1(Coord p)

{

函数体

}

调用语句:

Coord p1

fun1(p1);

//分析:调用拷贝构造函数 Coord p=p1;

 

fun1(Coord& p)

{

函数体

}

调用语句:

Coord p1

fun1(p1);

//分析:参数表中使用了引用,没有调用拷贝构造函数啊,执行 Coord& p=p1;

        3)函数的返回值是对象,函数调用完毕,返回调用者时,会调用拷贝构造函数

[cpp]view plaincopyprint?

1.  Coord fun1(Coord& fun)  

2.  {  

3.      return fun;  

4.  }  

5.    

6.  调用语句:  

7.  Coord  c;  

8.  Coord p=fun(c);   

Coord fun1(Coord& fun)

{

return fun;

}

 

调用语句:

Coord  c;

Coord p=fun(c);

//分析:return fun调用两次拷贝构造函数; VC测试,但是VS2005只调用一次,应该是进行了优化

[cpp]view plaincopyprint?

1.  Coord& fun1(Coord& fun1)  

2.  {    

3.      return fun1;  

4.  }  

5.    

6.  调用语句:  

7.  Coord  c;  

8.  Coord p=fun1(c);   

Coord& fun1(Coord& fun1)

return fun1;

}

 

调用语句:

Coord  c;

Coord p=fun1(c);

// 分析:Coord p=fun1(c)调用一次拷贝构造函数,因为最后使用返回函数引用,return时没有借助临时变量,直接是 Coord p=fun1;
 
出现这种现象的原因:

1、在使用return返回一个对象时,系统是先申请一个临时对象temp,执行Coord temp=fun1;(调用一次拷贝构造函数)

      之后在执行Coord p=temp;(第二次调用)

2、在使用返回函数引用时,系统不会申请临时对象,只是直接把fun1拷贝给p

      即直接执行Coord p=fun1,而没有引入temp,故少用一次拷贝构造函数

     注意:这时要注意一个常出现的错误,返回的值不能是一个临时变量,常常的解决办法是函数参数使用引用,之后在使用return返回即可

怎么使用拷贝构造函数?

        语法:函数名与类名相同,参数为本对象的引用,无返回类型,只有一个

                   类名::类名(类名& 对象名)

                   {拷贝成员}

        代码:

[cpp]view plaincopyprint?

1.  class Point  

2.  {  

3.  public:  

4.      Point(int xx=0,int yy=0){X=xx; Y=yy;}  

5.      Point(Point&  p);  

6.  private:  

7.      int  X,Y;  

8.  };  

9.  Point::Point (Point& p)  

10. {  

11.     X=p.X; //参数p可以直接引用私有变量  

12.     Y=p.Y;  

13. }  

class Point

{

public:

Point(int xx=0,int yy=0){X=xx;Y=yy;}

Point(Point&  p);

private:

int X,Y;

};

Point::Point (Point& p)

{

X=p.X; //参数p可以直接引用私有变量

Y=p.Y;

}

        注意:参数必须为本对象的引用 + 函数体内参数可以直接引用私有变量(老忘)

常见问题:

       1、为什么拷贝函数的参数必须是引用?

             简单点说,为了避免递归。

             具体来说,引用传递的时候不需要调用拷贝构造函数值传递需要调用拷贝构造函数

              所以,在进入拷贝构造函数时,需要把对象传进来,这时使用值传递还要再调一次拷贝构造函数.....这时要无限传递下去

      2、浅拷贝与深拷贝

            出现这个问题的原因:构造函数中需要为指针申请空间(简单点说,成员变量含有指针)

            深拷贝:在拷贝构造函数中,为指针显式申请空间(正确的方式)

            浅拷贝:在拷贝构造函数中,仅仅使用成员间的对应复制,两个对象的指针的都指向同一个空间,一个指针指向的空间释放,另一个指针为野指针,危害江湖。

            解决方法:如果构造函数需要为类显示申请空间(含指针,使用new),则要使用显式的拷贝构造函数

                                如果类中成员都是非指针,则可以使用系统默认的拷贝构造函数。

浅拷贝代码

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  #include <string>  

3.  using namespace std;  

4.  class Point  

5.  {  

6.  private:  

7.      char * name;  

8.  public:  

9.      Point(char * className)  

10.     {  

11.         name = new char[strlen(className)+1];  

12.         strcpy(name, className);  

13.     }  

14.     Point(Point& p)//浅拷贝,或者不写  

15.     {  

16.         name = p.name;//(系统默认拷贝函数执行的代码)   

17.     }  

18.     ~Point()  

19.     {  

20.         cout<<name<<endl;//测试关键语句  

21.         delete []name;  

22.     }  

23. };  

24.   

25. int main()  

26. {  

27.     Point a("123");  

28.     Point b=a;  

29.     return 0;  

30. }  

#include <iostream>

#include <string>

using namespace std;

class Point

{

private:

char * name;

public:

Point(char * className)

{

   name = newchar[strlen(className)+1];

   strcpy(name, className);

}

  Point(Point&p)//浅拷贝,或者不写

  {

    name= p.name;//(系统默认拷贝函数执行的代码)

  }

~Point()

{

   cout<<name<<endl;//测试关键语句

   delete []name;

}

};

 

int main()

{

Point a("123");

Point b=a;

return 0;

}

结果:

 

 

 

深拷贝:

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  #include <string>  

3.  using namespace std;  

4.  class Point  

5.  {  

6.  private:  

7.      char * name;  

8.  public:  

9.      Point(char * className)  

10.     {  

11.         name = new char[strlen(className)+1];  

12.         strcpy(name, className);  

13.     }  

14.     Point(Point& p)//深拷贝  

15.     {  

16.         name = new char[strlen(p.name)+1];  

17.         strcpy(name,p.name);  

18.     }  

19.     ~Point()  

20.     {  

21.         cout<<name<<endl;  

22.         delete []name;  

23.     }  

24. };  

25.   

26. int main()  

27. {  

28.     Point a("123");  

29.     Point b=a;  

30.     return 0;  

31. }  

#include <iostream>

#include <string>

using namespace std;

class Point

{

private:

char * name;

public:

Point(char * className)

{

   name = newchar[strlen(className)+1];

   strcpy(name, className);

}

Point(Point& p)//深拷贝

{

   name = new char[strlen(p.name)+1];

   strcpy(name,p.name);

}

~Point()

{

   cout<<name<<endl;

   delete []name;

}

};

 

int main()

{

Point a("123");

Point b=a;

return 0;

}

结果: 

分享到:

 

 

 

 

29.C++_构造函数和析构函数

分类:C++2011-07-15 17:40396人阅读评论(0)收藏举报

c++classdeletefloat存储工作

构造函数:

作用:

   1)分配空间:分配非静态数据成员的存储空间

   2)初始化成员:初始化非静态数据成员

分配空间:

   1)含有指针变量,需要程序员显式申请空间(使用new申请)

   2)非指针变量:由系统自动分配空间

初始化成员:

   1)使用赋值语句初始化:一般的变量

   2)使用表达式表初始化:一般的变量 +  Const成员,引用成员,对象成员

调用时机:在定义对象的时候,系统自动调用构造函数

   1)对象被创建时,自动调用构造函数

          Coordp1(1);  

          Coordp2=1;  //此时也会调用构造函数,但是有前提:构造参数只能有一个参数

   2)如果对象是用new 创建时,自动调用构造函数。

语法:名字和类名相同、无返回类型、允许为内联函数、重载函数、带默认形参值的函数

代码:

[cpp]view plaincopyprint?

1.  class A  

2.  {  

3.  private:  

4.      int x;  

5.      int& rx;   

6.      const float pi;  

7.  public:  

8.      A(int x1):rx(x),pi(3.14)//rx(x)等价于rx=x,pi(3.14)相当于pi=3.14   

9.      {  

10.         x=x1;//一般的变量  

11.     }                                       

12. };  

13. 调用:A a(10);  

class A

{

private:

int x;

int& rx;

const float pi;

public:

A(int x1):rx(x),pi(3.14)//rx(x)等价于rx=x,pi(3.14)相当于pi=3.14

{

   x=x1;//一般的变量

}                                    

};

调用:A a(10);

成员初始化表的使用

语法:

类名::构造函数名(参数表):(数据成员名1(初始值1),数据成员名2(初始值2),…… )

{

        函数体

}

说明:

1、类成员的初始化:照他们在类中声明的顺序进行初始化他们在成员初始化表中的顺序无关

2、数据成员含数组时,应在构造函数中通过赋值语句实现,不能使用成员初始化表

 

析构函数:处理善后工作

作用:释放空间:

  1)含有指针变量,需要程序员显式释放空间

  2)非指针变量:由系统自动释放空间

调用时机:

  1)对象被撤销时,如对象定义在函数内,当函数调用结束时,该对象被撤销。

  2如果对象是用new 创建的,当delete它时,自动调用析构函数。

语法:没有返回类型,没有参数,函数名是类名前加 "~"

代码:

[cpp]view plaincopyprint?

1.  class X  

2.  {  

3.  public:  

4.      X()  { }  

5.      ~X() { }  

6.  };   

class X

{

public:

X() { }

~X() { }

};

说明:

1、为什么要把析构函数定义为虚函数?
因为:虚函数是为了支持多态,。。。之后补充

遇到的问题

[cpp]view plaincopyprint?

1.  #include <iostream>  

2.  using namespace std;  

3.  class AAA  

4.  {  

5.  public:  

6.      AAA(void);  

7.      ~AAA(void);  

8.  };  

9.    

10. AAA::AAA()  

11. {  

12.   

13. };  

14.   

15. int main()  

16. {  

17.     AAA t;  //报错  

18.     AAA *p = new AAA(); //不报错  

19.     system("pause");  

20.     return 1;  

21. }  

#include <iostream>

using namespace std;

class AAA

{

public:

AAA(void);

~AAA(void);

};

 

AAA::AAA()

{

 

};

 

int main()

{

AAA t;  //报错

AAA *p = new AAA(); //不报错

system("pause");

return 1;

}

问题分析:因为编译器为每一个类都会声明一个默认的构造函数和析构函数,拷贝构造函数、赋值运算符。当用户自己定义时,系统就不在为类自动生成。这里类中已经自己定义了构造函数和析构函数,系统就没有自己生成。

AAA t;//报错

原因:在堆上定义对象是,系统会自动调用构造函数和析构函数。在链接时,系统会发现找不到析构函数的实现,这时就会报错,说找不到析构函数。要用你但是找不着你就会报错

AAA *p = newAAA(); //不报错

原因new的时候会调用构造函数,但是由于释放的时候需要用户自己手动释放,这里由于没有delete,所以,不会用到析构函数。在程序链接时,由于没有用到析构函数,就没有去找其实现,就不会报错

[cpp]view plaincopyprint?

1.  int main()  

2.  {  

3.      //AAA t;  //报错  

4.      AAA *p = new AAA();   

5.      delete p; //报错  

6.      system("pause");  

7.      return 1;  

8.  }  

int main()

{

//AAA t;  //报错

AAA *p = new AAA();

delete p; //报错

system("pause");

return 1;

}

如上的例子,如果写delete

AAA *p = newAAA(); //不报错

delete p;

这时程序执行delete时,需要用到析构函数。链接时,系统就会找其实现。当发现没实现时,而报错。

所以,我们在声明了函数就要记得实现,尤其是构造函数和析构函数,拷贝构造函数、赋值运算符。

 

 

 

30.C++_友元函数

分类:C++2011-07-15 15:176451人阅读评论(10)收藏举报

c++integerclassc

1、为什么要引入友元函数:在实现类之间数据共享时,减少系统开销,提高效率

     具体来说:为了使其他类的成员函数直接访问类的私有变量

     即:允许外面的类或函数去访问类的私有变量和保护变量,从而使两个类共享同一函数

     优点:能够提高效率,表达简单、清晰

      缺点:友元函数破环了封装机制,尽量不使用成员函数,除非不得已的情况下才使用友元函数。

2、什么时候使用友元函数:

      1)运算符重载的某些场合需要使用友元。

      2)两个类要共享数据的时候

3、怎么使用友元函数:

友元函数的参数:

      因为友元函数没有this指针,则参数要有三种情况:

      1  要访问非static成员时,需要对象做参数;--常用(友元函数常含有参数)

       2  要访问static成员或全局变量时,则不需要对象做参数

       3  如果做参数的对象是全局对象,则不需要对象做参数

友元函数的位置:

      因为友元函数是类外的函数,所以它的声明可以放在类的私有段或公有段且没有区别。

友元函数的调用:

      可以直接调用友元函数,不需要通过对象或指针

友元函数的分类:

根据这个函数的来源不同,可以分为三种方法:

1、普通函数友元函数:

       a) 目的:使普通函数能够访问类的友元

      b) 语法:声明位置:公有私有均可,常写为公有

                       声明: friend + 普通函数声明

                       实现位置:可以在类外或类中

                       实现代码:与普通函数相同(不加不用friend和类::

                       调用:类似普通函数,直接调用

       c) 代码:        

[cpp]view plaincopyprint?

1.  class INTEGER  

2.  {    

3.  private:  

4.      int num;  

5.  public:  

6.      friend void Print(const INTEGER& obj);//声明友元函数  

7.  };  

8.  void Print(const INTEGER& obj)//不使用friend和类::  

9.  {  

10.     //函数体  

11. }  

12. void main()  

13. {  

14.     INTEGER obj;  

15.     Print(obj);//直接调用  

16. }  

class INTEGER

private:

int num;

public:

friend void Print(const INTEGER&obj);//声明友元函数

};

void Print(const INTEGER& obj)//不使用friend和类::

{

//函数体

}

void main()

{

INTEGER obj;

    Print(obj);//直接调用

}

2、类Y的所有成员函数都为类X友元函数友元类

      a)目的:使用单个声明使Y类的所有函数成为类X的友元

                       它提供一种类之间合作的一种方式,使类Y的对象可以具有类X和类Y的功能

                       具体来说:

                                前提:AB的友元(=A中成员函数可以访问B中有所有成员,包括私有成员和公有成员--老忘)

                                    则:在A中,借助类B,可以直接使用~B . 私有变量~的形式访问私有变量

     b)语法:声明位置:公有私有均可,常写为私有(把类看成一个变量)

                       声明:friend + 类名---不是对象啊

                       调用:

     c)代码:

[cpp]view plaincopyprint?

1.  class girl;  

2.    

3.  class boy  

4.  {    

5.  private:  

6.      char *name;    

7.      int age;    

8.  public:    

9.      boy();  

10.     void disp(girl &);     

11. };    

12.   

13. void boy::disp(girl &x) //函数disp()为类boy的成员函数,也是类girl的友元函数    

14. {   

15.     cout<<"boy's name is:"<<name<<",age:"<<age<<endl;//正常情况,boy的成员函数disp中直接访问boy的私有变量  

16.     cout<<"girl's name is:"<<x.name<<",age:"<<x.age<<endl;   

17.     //借助友元,在boy的成员函数disp中,借助girl的对象,直接访问girl的私有变量  

18.     //正常情况下,只允许在girl的成员函数中访问girl的私有变量  

19. }  

20.   

21. class girl  

22. {    

23. private  

24.     char *name;    

25.     int age;    

26.     friend boy;   //声明类boy是类girl的友元     

27. public:    

28.     girl();     

29. };    

30. void main()    

31. {     

32.     boy b;    

33.     girl g;    

34.     b.disp(g);  //b调用自己的成员函数,但是以g为参数,友元机制体现在函数disp  

35. }  

class girl;

 

class boy

private:

    char *name; 

    int age; 

public: 

    boy();

    void disp(girl &);  

}; 

 

void boy::disp(girl &x) //函数disp()为类boy的成员函数,也是类girl的友元函数

{

    cout<<"boy's nameis:"<<name<<",age:"<<age<<endl;//正常情况,boy的成员函数disp中直接访问boy的私有变量

cout<<"girl's nameis:"<<x.name<<",age:"<<x.age<<endl;

//借助友元,在boy的成员函数disp中,借助girl的对象,直接访问girl的私有变量

//正常情况下,只允许在girl的成员函数中访问girl的私有变量

}

 

class girl

private

    char *name; 

    int age; 

    friend boy;  //声明类boy是类girl的友元 

public: 

    girl();  

}; 

void main() 

{  

    boy b; 

    girl g; 

    b.disp(g);  //b调用自己的成员函数,但是以g为参数,友元机制体现在函数disp中

}

3、类Y的一个成员函数为类X的友元函数

     a)目的:使类Y的一个成员函数成为类X的友元

            具体而言:而在类Y的这个成员函数中,借助参数X,可以直接以X。私有变量的形式访问私有变量

     b)语法:声明位置:声明在公有中(本身为函数)

                       声明:friend + 成员函数的声明

                       调用:先定义Y的对象y---使用y调用自己的成员函数---自己的成员函数中使用了友元机制

      c)代码: 

[cpp]view plaincopyprint?

1.  class girl;   

2.  class boy  

3.  {    

4.  private:  

5.      char *name;    

6.      int age;    

7.  public:    

8.      boy();  

9.      void disp(girl &);       

10. };     

11.    

12. class girl  

13. {  

14. private:  

15.     char *name;    

16.     int age;    

17. public:    

18.     girl(char *N,int A);    

19.     friend void boy::disp(girl &); //声明类boy的成员函数disp()为类girl的友元函数     

20. };    

21.    

22. void boy::disp(girl &x)    

23. {     

24.     cout<<"boy's name is:"<<name<<",age:"<<age<<endl;  //访问自己(boy)的对象成员,直接访问自己的私有变量     

25.     cout<<"girl's name is:"<<x.name<<",age:"<<x.age<<endl;    

26.     //借助友元,在boy的成员函数disp中,借助girl的对象,直接访问girl的私有变量  

27.     //正常情况下,只允许在girl的成员函数中访问girl的私有变量     

28. }    

29. void main()    

30. {     

31.     boy b();    

32.     girl g();    

33.     b.disp(g);  }  

class girl;

class boy

private:

    char *name; 

    int age; 

public: 

    boy();

    void disp(girl &);    

};  

 

class girl

{

private:

    char *name; 

    int age; 

public: 

    girl(char *N,int A); 

    friend void boy::disp(girl&); //声明类boy的成员函数disp()为类girl的友元函数 

}; 

 

void boy::disp(girl &x) 

{  

    cout<<"boy's nameis:"<<name<<",age:"<<age<<endl;  //访问自己(boy)的对象成员,直接访问自己的私有变量 

    cout<<"girl's nameis:"<<x.name<<",age:"<<x.age<<endl; 

//借助友元,在boy的成员函数disp中,借助girl的对象,直接访问girl的私有变量

//正常情况下,只允许在girl的成员函数中访问girl的私有变量 

void main() 

{  

    boy b(); 

    girl g(); 

    b.disp(g);  }

4在模板类中使用友元operator<<(<<运算符的重载)

   a)使用方法:

在模板类中声明:

[cpp]view plaincopyprint?

1.  friend ostream& operator<< <>(ostream& cout,const MGraph<VexType,ArcType>& G);  

friend ostream& operator<< <>(ostream& cout,constMGraph<VexType,ArcType>& G);

在模板类中定义:

[cpp]view plaincopyprint?

1.  template<class VexType,class ArcType>  

2.  ostream& operator<<(ostream& cout,const MGraph<VexType,ArcType>& G)  

3.  {  

4.      //函数定义  

5.  }  

template<class VexType,class ArcType>

ostream& operator<<(ostream& cout,constMGraph<VexType,ArcType>& G)

{

//函数定义

}

    b)注意:

把函数声明非模板函数:

[cpp]view plaincopyprint?

1.  friend ostream& operator<< (ostream& cout,const MGraph& G);  

friend ostream& operator<< (ostream& cout,const MGraph&G);

把函数声明为模板函数:

[cpp]view plaincopyprint?

1.  friend ostream& operator<< <>(ostream& cout,const MGraph<VexType,ArcType>& G);  

friend ostream& operator<< <>(ostream& cout,constMGraph<VexType,ArcType>& G);

或:

[cpp]view plaincopyprint?

1.  friend ostream& operator<< <VexType,ArcType>(ostream& cout,const MGraph<VexType,ArcType>& G);  

friend ostream& operator<< <VexType,ArcType>(ostream&cout,const MGraph<VexType,ArcType>& G);

说明:
 
在函数声明中加入operator<< <>:是将operator<<函数定义为函数模板,将函数模板申明为类模板的友员时,是一对一绑定的
 
实际的声明函数:这里模板参数可以省略,但是尖括号不可以省略

[cpp]view plaincopyprint?

1.  friend ostream& operator<< <VexType,ArcType>(ostream& cout,const MGraph<VexType,ArcType>& G);  

friend ostream& operator<< <VexType,ArcType>(ostream&cout,const MGraph<VexType,ArcType>& G);

5、友元函数和类的成员函数的区别:成员函数有this指针,而友元函数没有this指针。

6、记忆:AB的友元《=AB的朋友《=》借助B的对象,在A中可以直接通过B。成员变量(可以是公有,也可以为私有变量)的方式访问B 

 

 

 

31.C++_动态存储空间的分配和释放

分类:C++2011-07-15 14:171573人阅读评论(1)收藏举报

存储c++delete面试工作c

使用new申请空间:

特点:

       1、没有名字,只能通过指针间接访问它们。

       2、从堆中申请空间

分类:

变量:

          语法:指针变量 = new 类型名;

                      Delete指针变量

          举例:int *p;

                      p=newint;

                      deletep;

          初始化:

                       *p=90;

                      p=new int99;

一维数组:

          语法:指针变量 = new 类型名[下标表达式]; 下标表达式是整型常量或整型表达式

                      Delete [ ]指针变量;---[ ]表明是个数组,但不需要指出其大小

           举例:Int *p;

                      P= new type[s];

                      Delete [ ]p;

多维数组(二维数组):  必须指出所有维的长度

          语法:指针变量 = new 类型名[下标表达式] [下标表达式];

                      Delete [ ]变量名

          举例:定义一个3×4的数组

                      Int *p[4]; p是个指针,它指向一个n维数组(4定义的时候必须已知,即后几维必须已知

                      p=new int[3][4];// 第一维可以是常量或表达式,其他维必须全部为常量

                      Delete [ ]p;

对象:可以在创建对象的时候同时进行初始化

          语法: 类名 *指针变量 = new 类名(实参表);

对象数组:

          语法:类名数组名[元素个数]

                       类名* 指针名;指针名=new 类名[数组长度];

          举例:Point A[2];

                      Point* p;

                     *p=new Point[5];

                       delete[ ]p;

          初始化:每个对象都要调用一次构造函数和一次析构函数。

                1)对象数组确定且长度有限:可以使用参数列表初始化

                   Point A[2]={Point(1,2),Point(3,4)};

                2)对象数组长度比较大或不确定:只能调用无参或者使用缺省参数的函数

                  PointA[2];

            原因:在建立对象数组的时候,C++不允许整体对对象进行初始化([ ]( )不能一起使用),这时不能给对象使用有参数的构造函数

           所以:若创建一个类的对象数组,就对给类的构造函数有要求:对象数组只能调用一个不需要参数的构造函数

常见问题:

1):有了malloc/free 为什么还要new/delete?

           malloc/free 只能满足内部数据类型的变量,它们只需要申请空间,无法对空间进行操作

          而对于非内部数据类型的对象来说,对象在创建的同时要自动执行构造函数,在消亡之前要自动执行析构函数。

          由于malloc/free 是库函数而不是运算符,不在编译器控制权限之内,不能够自动地调用构造函数和析构函数。即用无法满足动态对象的要求。

          因此 C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete

          注意new/delete 不是库函数,而是运算符。malloc/free 是标准库函数。

2):malloc/free  new/delete 的相同点和不同点

             相同点:它们都可以申请和释放空间。

             不同点:

                     一、new :分配内存 + 调用类的构造函数 + 初始化  delete:释放内存 + 调用类的析构函数

                              malloc:只分配内存,不会进行初始化类成员的工作   free只释放内存,不会调用析构函数

                      二、new delete 是运算符,可以进行重载

                             malloc,free是函数,不可以进行重载

                      三、new delete 更加安全,简单:

                             不用计算类型大小:自动计算要分配存储区的字节数

                             不用强制类型转换:自动返回正确的指针类型

                    四、new可以分配一个对象或对象数组的存储空间,malloc不可以

                    五、可以超载与类相关的newdelete

                    六、malloc/free 是标准库函数,new/deleteC++运算符

2):new和delete搭配使用,malloc和free搭配使用:混搭可能出现不可预料的错误

3):new后执行的三个操作:(某面试题目)

                       1new的类分配内存空间。
                      2调用类的构造方法。
                      3 、返回该实例(对象)的内存地址

 

 

 

32.C++和C的语法区别_输入输出

分类:C++2011-07-06 13:22856人阅读评论(0)收藏举报

c++ciostream百度终端语言

  

语法区别:

C:使用printfscanf getsputs

C++:使用cincout

优点:

      C++使用cincout有三个突出优点:

      1、简单安全:根据操作对象自适应的,只需要使用>>或者<<就可以搞定一切

      2、支持用户自定义类型的流操作:需要重载<<运算符

注意事项:

      1、要使用头文件iostreamcoutcin并不是C++语言中提供的语句,它们是iostream类的对象,故使用的时候必须加上头文件iostream,又因为coutstd名字空间中定义的全局对象,在main函数执行前就已经构造好了,只管用就是,而不用在定义对象等操作。

      2、二者效率不同。至于printfcout那个快,根据数据不同,会有不同的结果。但是百度上说做ZOJ时,使用cout会有超时的情况,这个要注意。

      3、不同类的输入输出语句不能混合使用。cincout搭配使用,printfscanf搭配使用。因为运行机制不同,混合使用可能会出问题。

 常用语法:
      cin>>变量/指针;

      1、数据流向谁就指向谁。(记忆)(键盘->内存)

      2、变量可以是一个也可以是多个,数据之间可以使用空格、Tab或回车分开

             cin>>a>>b

      cout<<变量/常量(各种类型常量);

      1  数据流向谁就指向谁。(内存->显示器)

      2  变量可以是一个、多个、也可以是表达式

            cout<<a<<b<<a+b;

            cout<<endl;

典型问题: 

[plain]view plaincopyprint?

1.  #include <iostream.h>  

2.  #include <stdio.h>  

3.  void main()  

4.  {  

5.           printf("A\n");     

6.           cout<< "B\n";       

7.           printf("C\n");   

8.  }  

9.  输出结果:  

10. A  

11. C  

12. B  

#include <iostream.h>

#include <stdio.h>

void main()

{

        printf("A\n");  

         cout<<"B\n";    

         printf("C\n");

}

输出结果:

A

C

B

[html]view plaincopyprint?

1.  #include <iostream.h>  

2.  #include <stdio.h>  

3.  void main()  

4.  {  

5.      printf("A\n");     

6.           cout<< "B"<<endl;       

7.           printf("C\n");   

8.  }  

9.  输出结果:  

10. A  

11. B  

12. C  

#include <iostream.h>

#include <stdio.h>

void main()

{

printf("A\n");  

         cout<<"B"<<endl;    

         printf("C\n");

}

输出结果:

A

B

C

原因:printf不使用缓冲,输入时直接输出到终端,但是cout使用流,有缓冲输出,总是在强制刷新或程序结束后才能显示结果。

cout < <"abc " < <endl; 等价于 cout < < "abc\n ";cout< <flush; (flush使立即强迫缓冲输出)

endl相当于输出回车后,再强迫缓冲输出,包含flush的作用。

通过cout分步执行可以看到,执行cout<< "B\n "时(这里没使用endl或刷新)无内容立即输出,一直到程序而执行cout<< "B"<<endl;时有结果输出。

cout分步执行时,到程序结束时或使用endl才输出结果到屏幕 printf每步运行都有结果输出,有利于调试。

因此,调试的时候,语句中含有cout < < "abc "这种的时候,要加上endl或改成 printf 语句。

 

 

 

33.述list,vector,map,set四中STL的数据结构的区别和各自特点

2013-11-08 20:318人阅读评论(0)收藏举报

 1 vector

   向量相当于一个数组
   
在内存中分配一块连续的内存空间进行存储。支持不指定vector大小的存储。STL内部实现时,首先分配一个非常大的内存空间预备进行存储,即capacituy()函数返回的大小,当超过此分配的空间时再整体重新放分配一块内存存储,这给人以vector可以不指定vector即一个连续内存的大小的感觉。通常此默认的内存分配能完成大部分情况下的存储。
  
优点:(1)不指定一块内存大小的数组的连续存储,即可以像数组一样操作,但可以对此数组
              
进行动态操作。通常体现在push_back()pop_back()
              (2)
随机访问方便,即支持[]操作符和vector.at()
              (3)
节省空间。
  
缺点:(1)在内部进行插入删除操作效率低。
              (2)
只能在vector的最后进行pushpop,不能在vector的头进行pushpop
               (3) 当动态添加的数据超过vector默认分配的大小时要进行整体的重新分配、拷贝与释
                    
 

list
   
双向链表
   
每一个结点都包括一个信息快Info、一个前驱指针Pre、一个后驱指针Post。可以不分配必须的内存大小方便的进行添加和删除操作。使用的是非连续的内存空间进行存储。
  
优点:(1)不使用连续内存完成动态操作。
               (2)
在内部方便的进行插入和删除操作
              (3)
可在两端进行pushpop
  
缺点:(1) 不能进行内部的随机访问,即不支持[ ]操作符和vector.at()
              (2) 相对于verctor占用内存多

deque
   
双端队列 double-end queue
   
deque是在功能上合并了vectorlist
  
优点:(1)随机访问方便,即支持[]操作符和vector.at()
               (2)
在内部方便的进行插入和删除操作
              (3)
可在两端进行pushpop
  
缺点:(1)占用内存多
4 map

MapSTL的一个关联容器,它提供一对一(其中第一个可以称为关键字,每个关键字只能在map中出现一次,第二个可能称为该关键字的值)的数据处理能力,由于这个特性map内部的实现自建一颗红黑树(一种非严格意义上的平衡二叉树),这颗树具有对数据自动排序的功能。

5set

set是集合,set中不会包含重复的元素,这是和vector的第一个区别,第二个区别是set内部用平衡二叉树实现,便于元素查找,而vector是使用连续内存存储,便于随机存取。
因此在实际使用时,如何选择这几个容器中哪一个,应根据你的需要而定,一般应遵循下面
的原则:
1
、如果你需要高效的随即存取,而不在乎插入和删除的效率,使用vector
2
、如果你需要大量的插入和删除,而不关心随即存取,则应使用list
3
、如果你需要随即存取,而且关心两端数据的插入和删除,则应使用deque

4、如果你要存储一个数据字典,并要求方便地根据keyvalue,那么map是较好的选择

5、如果你要查找一个元素是否在某集合内存中,则使用set存储这个集合比较好

 

34.金山的一道面试题(考察面向对象和内存模型)

2013-11-08 19:468人阅读评论(0)收藏举报

 请问程序会输出什么结果:

1.  #include <stdio.h>

2.   

3.  class A

4.  {

5.  public:

6.      A() {m_a = 1; m_b = 2;}

7.      ~A(){};

8.      void fun(){printf("%d%d", m_a,m_b);}

9.  private:

10.      int m_a;

11.      int m_b;

12.   

13.  };

14.   

15.  class B

16.  {

17.  public:

18.      B(){m_c = 3;}

19.      ~B();

20.      void fun() {printf("%d", m_c);}

21.  private:

22.      int m_c;

23.  };

24.   

25.  void main()

26.  {

27.  A a;

28.  B *pb = (B*)(&a);

29.  pb->fun();

30.  }

 

VC 6.0的编译环境下,输出的是1. 

 

个人认为:

    类的内存分配方式和模型都是相同的。通过(B*)(&a);的强制类型转换,把指针B类的指针pb指向类A a这块临时栈内存中。 

     因为类成员函数的地址是写在静态存储区的。所以这个时候pb->fun();调用的还是B中的fun(); 接着,fun()访问m_c变量的值。

     因为前面发生了强制类型转换。所以fun()读取m_c值的时候,实际上是读取A a临时变量在栈中分配的内存。而栈的分配是按照类成员变量顺序来分配的。第一个是int类型的变量就是最前面四个字节,第二个是char的,那么就是第5个字节。类似这样。。那么这时候读取 m_c 实际上就是读的类A临时变量a的堆栈的前四个字节(因为m_cm_a都是第一个成员变量。它们的内存是匹配的)。所以m_c的值看起来是等于m_a。实际我们访问的是m_a的值。

 

35.成员函数指针与高性能的C++委托

Member Function Pointers and theFastest Possible C++ Delegates

 

撰文:Don Clugston

翻译:周翔

 

引子

标准C++中没有真正的面向对象的函数指针。这一点对C++来说是不幸的,因为面向对象的指针(也叫做闭包(closure委托(delegate)在一些语言中已经证明了它宝贵的价值。在Delphi (Object Pascal)中,面向对象的函数指针是Borland可视化组建库(VCLVisual Component Library)的基础。而在目前,C#使委托的概念日趋流行,这也正显示出C#这种语言的成功。在很多应用程序中,委托简化了松耦合对象的设计模式[GoF]。这种特性无疑在标准C++中也会产生很大的作用。

很遗憾,C++中没有委托,它只提供了成员函数指针(member function pointers。很多程序员从没有用过函数指针,这是有特定的原因的。因为函数指针自身有很多奇怪的语法规则(比如“->*”“.*”操作符),而且很难找到它们的准确含义,并且你会找到更好的办法以避免使用函数指针。更具有讽刺意味的是:事实上,编译器的编写者如果实现委托的话会比他费劲地实现成员函数指针要容易地多!

在这篇文章中,我要揭开成员函数指针那神秘的盖子。在扼要地重述成员函数指针的语法和特性之后,我会向读者解释成员函数指针在一些常用的编译器中是怎样实现的,然后我会向大家展示编译器怎样有效地实现委托。最后我会利用这些精深的知识向你展示在C++编译器上实现优化而可靠的委托的技术。比如,在Visual C++(6.0, .NET, and .NET 2003)中对单一目标委托(single-target delegate)的调用,编译器仅仅生成两行汇编代码!

 

函数指针

下面我们复习一下函数指针。在CC++语言中,一个命名为my_func_ptr的函数指针指向一个以一个int和一个char*为参数的函数,这个函数返回一个浮点值,声明如下:

float (*my_func_ptr)(int,char *);

为了便于理解,我强烈推荐你使用typedef关键字。如果不这样的话,当函数指针作为一个函数的参数传递的时候,程序会变得晦涩难懂。这样的话,声明应如下所示:

typedef float(*MyFuncPtrType)(int, char *);

MyFuncPtrType my_func_ptr;

应注意,对每一个函数的参数组合,函数指针的类型应该是不同的。在Microsoft Visual C++(以下称MSVC)中,对三种不同的调用方式有不同的类型:__cdecl,__stdcall, __fastcall。如果你的函数指针指向一个型如floatsome_func(int, char *)的函数,这样做就可以了:

my_func_ptr = some_func;

当你想调用它所指向的函数时,你可以这样写:

(*my_func_ptr)(7,"Arbitrary String");

你可以将一种类型的函数指针转换成另一种函数指针类型,但你不可以将一个函数指针指向一个void *型的数据指针。其他的转换操作就不用详叙了。一个函数指针可以被设置为0来表明它是一个空指针。所有的比较运算符(==, !=, <, >, <=, >=)都可以使用,可以使用“==

C语言中,函数指针通常用来像qsort一样将函数作为参数,或者作为Windows系统函数的回调函数等等。函数指针还有很多其他的应用。函数指针的实现很简单:它们只是代码指针(code pointer,它们体现在汇编语言中是用来保存子程序代码的首地址。而这种函数指针的存在只是为了保证使用了正确的调用规范。

 

成员函数指针

C++程序中,很多函数是成员函数,即这些函数是某个类中的一部分。你不可以像一个普通的函数指针那样指向一个成员函数,正确的做法应该是,你必须使用一个成员函数指针。一个成员函数的指针指向类中的一个成员函数,并和以前有相同的参数,声明如下:

float(SomeClass::*my_memfunc_ptr)(int, char *);

对于使用const关键字修饰的成员函数,声明如下:

float(SomeClass::*my_const_memfunc_ptr)(int, char *) const;

注意使用了特殊的运算符(::*),而“SomeClass”是声明中的一部分。成员函数指针有一个可怕的限制:它们只能指向一个特定的类中的成员函数。对每一种参数的组合,需要有不同的成员函数指针类型,而且对每种使用const修饰的函数和不同类中的函数,也要有不同的函数指针类型。在MSVC中,对下面这四种调用方式都有一种不同的调用类型:

__cdecl, __stdcall,__fastcall, __thiscall

__thiscall是缺省的方式,有趣的是,在任何官方文档中从没有对__thiscall关键字的详细描述,但是它经常在错误信息中出现。如果你显式地使用它,你会看到它被保留作为以后使用(it is reserved for future use的错误提示。)

如果你使用了成员函数指针,你最好使用typedef以防止混淆。将函数指针指向型如floatSomeClass::some_member_func(int, char *)的函数,你可以这样写:

my_memfunc_ptr =&SomeClass::some_member_func;

很多编译器(比如MSVC)会让你去掉“&”,而其他一些编译器(比如GNU G++)则需要添加“&”,所以在手写程序的时候我建议把它添上。若要调用成员函数指针,你需要先建立SomeClass的一个实例,并使用特殊操作符“->*”,这个操作符的优先级较低,你需要将其适当地放入圆括号内。

SomeClass *x = newSomeClass;

(x->*my_memfunc_ptr)(6,"Another Arbitrary Parameter");

//如果类在栈上,你也可以使用“.*”运算符。

SomeClass y;

(y.*my_memfunc_ptr)(15,"Different parameters this time");

不要怪我使用如此奇怪的语法——看起来C++的设计者对标点符号有着由衷的感情!C++相对于C增加了三种特殊运算符来支持成员指针。“::*”用于指针的声明,而“->*”“.*”用来调用指针指向的函数。这样看起来对一个语言模糊而又很少使用的部分的过分关注是多余的。(你当然可以重载“->*”这些运算符,但这不是本文所要涉及的范围。)

一个成员函数指针可以被设置成0,并可以使用“==”“!=”比较运算符,但只能限定在同一个类中的成员函数的指针之间进行这样的比较。任何成员函数指针都可以和0做比较以判断它是否为空。与函数指针不同,不等运算符(<, >, <=, >=)对成员函数指针是不可用的。

 

成员函数指针的怪异之处

成员函数指针有时表现得很奇怪。

首先,你不可以用一个成员函数指针指向一个静态成员函数,你必须使用普通的函数指针才行(在这里成员函数指针会产生误解,它实际上应该是非静态成员函数指针才对)。

其次,当使用类的继承时,会出现一些比较奇怪的情况。比如,下面的代码在MSVC下会编译成功(注意代码注释):

#include “stdio.h”

class SomeClass {

public:

virtual voidsome_member_func(int x, char *p) {

printf("InSomeClass"); };

};

class DerivedClass : publicSomeClass {

public:

// 如果你把下一行的注释销掉,带有 line (*)的那一行会出现错误

// virtual void some_member_func(intx, char *p) { printf("In DerivedClass"); };

};

int main() {

//声明SomeClass的成员函数指针

typedef void(SomeClass::*SomeClassMFP)(int, char *);

SomeClassMFPmy_memfunc_ptr;

my_memfunc_ptr =&DerivedClass::some_member_func; // ---- line (*)

return 0;

}

奇怪的是,&DerivedClass::some_member_func是一个SomeClass类的成员函数指针,而不是DerivedClass类的成员函数指针!(一些编译器稍微有些不同:比如,对于Digital Mars C++,在上面的例子中,&DerivedClass::some_member_func会被认为没有定义。)但是,如果在DerivedClass类中重写(override)了some_member_func函数,代码就无法通过编译,因为现在的&DerivedClass::some_member_func已成为DerivedClass类中的成员函数指针!

成员函数指针之间的类型转换是一个讨论起来非常模糊的话题。在C++的标准化的过程中,在涉及继承的类的成员函数指针时,对于将成员函数指针转化为基类的成员函数指针还是转化为子类成员函数指针的问题是否可以将一个类的成员函数指针转化为另一个不相关的类的成员函数指针的问题,人们曾有过很激烈的争论。然而不幸的是,在标准委员会做出决定之前,不同的编译器生产商已经根据自己对这些问题的不同的回答实现了自己的编译器。根据标准(第

在一些编译器中,在基类和子类的成员函数指针之间的转换时常有怪事发生。当涉及到多重继承时,使用reinterpret_cast将子类转换成基类时,对某一特定编译器来说有可能通过编译,而也有可能通不过编译,这取决于在子类的基类列表中的基类的顺序!下面就是一个例子:

class Derived: publicBase1, public Base2 // 情况 (a)

class Derived2: publicBase2, public Base1 // 情况 (b)

typedef void (Derived::* Derived_mfp)();

typedef void (Derived2::*Derived2_mfp)();

typedef void (Base1::*Base1mfp) ();

typedef void (Base2::*Base2mfp) ();

Derived_mfp x;

对于情况(a)static_cast<Base1mfp>(x)是合法的,而static_cast<Base2mfp>(x)则是错误的。然而情况(b)却与之相反。你只可以安全地将子类的成员函数指针转化为第一个基类的成员函数指针!如果你要实验一下,MSVC会发出C4407号警告,而Digital Mars C++会出现编译错误。如果用reinterpret_cast代替static_cast这两个编译器都会发生错误,但是两种编译器对此有着不同的原因。但是一些编译器对此细节置之不理,大家可要小心了!

标准C++中另一条有趣的规则是:你可以在类定义之前声明它的成员函数指针。这对一些编译器会有一些无法预料的副作用。我待会讨论这个问题,现在你只要知道要尽可能得避免这种情况就是了。

值得注意的是,就像成员函数指针,标准C++中同样提供了成员数据指针(member datapointer)。它们具有相同的操作符,而且有一些实现原则也是相同的。它们用在stl::stable_sort的一些实现方案中,而对此很多其他的应用我就不再提及了。

 

成员函数指针的使用

现在你可能会觉得成员函数指针是有些奇异。但它可以用来做什么呢?对此我在网上做了非常广泛的调查。最后我总结出使用成员函数指针的两点原因:

·        用来做例子给C++初学者看,帮助它们学习语法;或者

·        为了实现委托(delegate

成员函数指针在STLBoost库的单行函数适配器(one-line function adaptor)中的使用是微不足道的,而且允许你将成员函数和标准算法混合使用。但是它们最重要的应用是在不同类型的应用程序框架中,比如它们形成了MFC消息系统的核心。

当你使用MFC的消息映射宏(比如ON_COMMAND)时,你会组装一个包含消息ID和成员函数指针(型如:CCmdTarget::*成员函数指针)的序列。这是MFC类必须继承CCmdTarget才可以处理消息的原因之一。但是,各种不同的消息处理函数具有不同的参数列表(比如OnDraw处理函数的第一个参数的类型为CDC *),所以序列中必须包含各种不同类型的成员函数指针。

MFC是怎样做到这一点的呢?MFC利用了一个可怕的编译器漏洞(hack),它将所有可能出现的成员函数指针放到一个庞大的联合(union)中,从而避免了通常需要进行的C++类型匹配检查。(看一下afximpl.hcmdtarg.cpp中名为MessageMapFunctionsunion,你就会发现这一恐怖的事实。)

因为MFC有如此重要的一部分代码,所以事实是,所有的编译器都为这个漏洞开了绿灯。(但是,在后面我们会看到,如果一些类用到了多重继承,这个漏洞在MSVC中就不会起作用,这正是在使用MFC时只能必须使用单一继承的原因。

boost::function中有类似的漏洞(但不是太严重)。看起来如果你想做任何有关成员函数指针的比较有趣的事,你就必须做好与这个语言的漏洞进行挑战的准备。要是你想否定C++的成员函数指针设计有缺陷的观点,看来是很难的。

在写这篇文章中,我有一点需要指明:允许成员函数指针之间进行转换(cast),而不允许在转换完成后调用其中的函数,把这个规则纳入C++的标准中是可笑的。

首先,很多流行的编译器对这种转换不支持(所以,转换是标准要求的,但不是可移植的)。

其次,所有的编译器,如果转换成功,调用转换后的成员函数指针时仍然可以实现你预期的功能:那编译器就没有所谓的“undefinedbehavior(未定义的行为)这类错误出现的必要了(调用(Invocation)是可行的,但这不是标准!)。

第三,允许转换而不允许调用是完全没有用处的,只有转换和调用都可行,才能方便而有效地实现委托,从而使这种语言受益。

为了让你确信这一具有争议的论断,考虑一下在一个文件中只有下面的一段代码,这段代码是合法的:

class SomeClass;

typedef void (SomeClass::*SomeClassFunction)(void);

void Invoke(SomeClass*pClass, SomeClassFunction funcptr) {(pClass->*funcptr)(); };

注意到编译器必须生成汇编代码来调用成员函数指针,其实编译器对SomeClass类一无所知。显然,除非链接器进行了一些极端精细的优化措施,否则代码会忽视类的实际定义而能够正确地运行。而这造成的直接后果是,你可以安全地调用从完全不同的其他类中转换过来的成员函数指针。

为解释我的断言的另一半——转换并不能按照标准所说的方式进行,我需要在细节上讨论编译器是怎样实现成员函数指针的。我同时会解释为什么使用成员函数指针的规则具有如此严格的限制。获得详细论述成员函数指针的文档不是太容易,并且大家对错误的言论已经习以为常了,所以,我仔细检查了一系列编译器生成的汇编代码……

 

成员函数指针——为什么那么复杂?

类的成员函数和标准的C函数有一些不同。与被显式声明的参数相似,类的成员函数有一个隐藏的参数this,它指向一个类的实例。根据不同的编译器,this或者被看作内部的一个正常的参数,或者会被特别对待(比如,在VC++中,this一般通过ECX寄存器来传递,而普通的成员函数的参数被直接压在堆栈中)。this作为参数和其他普通的参数有着本质的不同,即使一个成员函数受一个普通函数的支配,在标准C++中也没有理由使这个成员函数和其他的普通函数(ordinary function)的行为相同,因为没有thiscall关键字来保证它使用像普通参数一样正常的调用规则。成员函数是一回事,普通函数是另外一回事(Member functionsare from Mars, ordinary functions are from Venus)。

你可能会猜测,一个成员函数指针和一个普通函数指针一样,只是一个代码指针。然而这种猜测也许是错误的。在大多数编译器中,一个成员函数指针要比一个普通的函数指针要大许多。更奇怪的是,在Visual C++中,一个成员函数指针可以是4812甚至16个字节长,这取决于它所相关的类的性质,同时也取决于编译器使用了怎样的编译设置!成员函数指针比你想象中的要复杂得多,但也不总是这样。

让我们回到二十世纪80年代初期,那时,最古老的C++编译器CFront刚刚开发完成,那时C++语言只能实现单一继承,而且成员函数指针刚被引入,它们很简单:它们就像普通的函数指针,只是附加了额外的this作为它们的第一个参数,你可以将一个成员函数指针转化成一个普通的函数指针,并使你能够对这个额外添加的参数产生足够的重视。

这个田园般的世界随着CFront 2.0的问世被击得粉碎。它引入了模版和多重继承,多重继承所带来的破坏造成了成员函数指针的改变。问题在于,随着多重继承,调用之前你不知道使用哪一个父类的this指针,比如,你有4个类定义如下:

class A {

public:

virtual int Afunc() {return 2; };

};

class B {

public:

int Bfunc() { return 3; };

};

// C是个单一继承类,它只继承于A

class C: public A {

public:

int Cfunc() { return 4; };

};

// D 类使用了多重继承

class D: public A, public B{

public:

int Dfunc() { return 5; };

};

假如我们建立了C类的一个成员函数指针。在这个例子中,AfuncCfunc都是C的成员函数,所以我们的成员函数指针可以指向Afunc或者Cfunc。但是Afunc需要一个this指针指向C::A(后面我叫它Athis),而Cfunc需要一个this指针指向C(后面我叫它Cthis)。编译器的设计者们为了处理这种情况使用了一个把戏(trick):他们保证了A类在物理上保存在C类的头部(即C类的起始地址也就是一个A类的一个实例的起始地址),这意味着Athis == Cthis。我们只需担心一个this指针就够了,并且对于目前这种情况,所有的问题处理得还可以。

现在,假如我们建立一个D类的成员函数指针。在这种情况下,我们的成员函数指针可以指向AfuncBfuncDfunc。但是Afunc需要一个this指针指向D::A,而Bfunc需要一个this指针指向D::B。这时,这个把戏就不管用了,我们不可以把A类和B类都放在D类的头部。所以,D类的一个成员函数指针不仅要说明要指明调用的是哪一个函数,还要指明使用哪一个this指针。编译器知道A类占用的空间有多大,所以它可以对Athis增加一个delta = sizeof(A)偏移量就可以将Athis指针转换为Bthis指针。

如果你使用虚拟继承(virtual inheritance),比如虚基类,情况会变得更糟,你可以不必为搞懂这是为什么太伤脑筋。就举个例子来说吧,编译器使用虚拟函数表(virtual functiontable——“vtable”)来保存每一个虚函数、函数的地址和virtual_delta:将当前的this指针转换为实际函数需要的this指针时所要增加的位移量。

综上所述,为了支持一般形式的成员函数指针,你需要至少三条信息:函数的地址,需要增加到this指针上的delta位移量,和一个虚拟函数表中的索引。对于MSVC来说,你需要第四条信息:虚拟函数表(vtable)的地址。

 

成员函数指针的实现

那么,编译器是怎样实现成员函数指针的呢?这里是对不同的326416位的编译器,对各种不同的数据类型(有intvoid*数据指针、代码指针(比如指向静态函数的指针)、在单一(single-)继承、多重(multiple-)继承、虚拟(virtual-)继承和未知类型(unknown)的继承下的类的成员函数指针)使用sizeof运算符计算所获得的数据:

编译器

选项

int

DataPtr

CodePtr

Single

Multi

Virtual

Unknown

MSVC

 

4

4

4

4

8

12

16

MSVC

/vmg

4

4

4

16#

16#

16#

16

MSVC

/vmg /vmm

4

4

4

8#

8#

--

8#

Intel_IA32

 

4

4

4

4

8

12

12

Intel_IA32

/vmg /vmm

4

4

4

4

8

--

8

Intel_Itanium

 

4

8

8

8

12

20

20

G++

 

4

4

4

8

8

8

8

Comeau

 

4

4

4

8

8

8

8

DMC

 

4

4

4

4

4

4

4

BCC32

 

4

4

4

12

12

12

12

BCC32

/Vmd

4

4

4

4

8

12

12

WCL386

 

4

4

4

12

12

12

12

CodeWarrior

 

4

4

4

12

12

12

12

XLC

 

4

8

8

20

20

20

20

DMC

small

2

2

2

2

2

2

2

DMC

medium

2

2

4

4

4

4

4

WCL

small

2

2

2

6

6

6

6

WCL

compact

2

4

2

6

6

6

6

WCL

medium

2

2

4

8

8

8

8

WCL

large

2

4

4

8

8

8

8

注:

# 表示使用__single/__multi/__virtual_inheritance关键字的时候代表4、8或12。

这些编译器是Microsoft Visual C++ 4.0 to 7.1 (.NET 2003), GNU G++ 3.2 (MingW binaries, http://www.mingw.org/), Borland BCB 5.1 (http://www.borland.com/), Open Watcom (WCL) 1.2 (http://www.openwatcom.org/), Digital Mars (DMC) 8.38n (http://www.digitalmars.com/), Intel C++ 8.0 for Windows IA-32, Intel C++ 8.0 for Itanium, (http://www.intel.com/), IBM XLC for AIX (Power, PowerPC), Metrowerks Code Warrior 9.1 for Windows (http://www.metrowerks.com/), 和 Comeau C++ 4.3 (http://www.comeaucomputing.com/). Comeau的数据是在它支持的32位平台(x86, Alpha, SPARC等)上得出的。16位的编译器的数据在四种DOS配置(tiny, compact, medium, 和 large)下测试得出,用来显示各种不同代码和数据指针的大小。MSVC在/vmg的选项下进行了测试,用来显示“成员指针的全部特性”。(如果你拥有在列表中没有出现的编译器,请告知我。非x86处理机下的编译器测试结果有独特的价值。)

看着表中的数据,你是不是觉得很惊奇?你可以清楚地看到编写一段在一些环境中可以运行而在另一些编译器中不能运行的代码是很容易的。不同的编译器之间,它们的内部实现显然是有很大差别的;事实上,我认为编译器在实现语言的其他特性上并没有这样明显的差别。对实现的细节进行研究你会发现一些奇怪的问题。

一般,编译器采取最差的,而且一直使用最普通的形式。比如对于下面这个结构:

// Borland (缺省设置) Watcom C++.

struct {

FunctionPointerm_func_address;

int m_delta;

int m_vtable_index; //如果不是虚拟继承,这个值为0

};

// Metrowerks CodeWarrior使用了稍微有些不同的方式。

//即使在不允许多重继承的Embedded C++的模式下,它也使用这样的结构!

struct {

int m_delta;

int m_vtable_index; // 如果不是虚拟继承,这个值为-1

FunctionPointerm_func_address;

};

// 一个早期的SunCC版本显然使用了另一种规则:

struct {

int m_vtable_index; //如果是一个非虚拟函数(non-virtual function),这个值为0

FunctionPointerm_func_address; //如果是一个虚拟函数(virtual function),这个值为0

int m_delta;

};

//下面是微软的编译器在未知继承类型的情况下或者使用/vmg选项时使用的方法:

struct {

FunctionPointerm_func_address;

int m_delta;

int m_vtordisp;

int m_vtable_index; // 如果不是虚拟继承,这个值为0

};

// AIX (PowerPC)IBMXLC编译器:

struct {

FunctionPointerm_func_address; // PowerPC来说是64

int m_vtable_index;

int m_delta;

int m_vtordisp;

};

// GNU g++使用了一个机灵的方法来进行空间优化

struct {

union {

FunctionPointerm_func_address; // 其值总是4的倍数

int m_vtable_index_2; // 其值被2除的结果总是奇数

};

int m_delta;

};

对于几乎所有的编译器,deltavindex用来调整传递给函数的this指针,比如Borland的计算方法是:

adjustedthis = *(this +vindex -1) + delta // 如果vindex!=0

adjustedthis = this + delta// 如果vindex=0

(其中,“*”是提取该地址中的数值,adjustedthis是调整后的this指针——译者注)

Borland使用了一个优化方法:如果这个类是单一继承的,编译器就会知道deltavindex的值是0,所以它就可以跳过上面的计算方法。

GNU编译器使用了一个奇怪的优化方法。可以清楚地看到,对于多重继承来说,你必须查看vtable(虚拟函数表)以获得voffset(虚拟函数偏移地址)来计算this指针。当你做这些事情的时候,你可能也把函数指针保存在vtable中。通过这些工作,编译器将m_func_addressm_vtable_index合二为一(即放在一个union中),编译器区别这两个变量的方法是使函数指针(m_func_address)的值除以2后结果为偶数,而虚拟函数表索引(m_vtable_index_2)除以2后结果为奇数。它们的计算方法是:

adjustedthis = this + delta

if (funcadr & 1) //如果是奇数

call (* ( *delta +(vindex+1)/2) + 4)

else //如果是偶数

call funcadr

(其中, funcadr是函数地址除以2得出的结果。——译者注)

InterItanium编译器(但不是它们的x86编译器)对虚拟继承(virtual inheritance)的情况也使用了unknown_inheritance结构,所以,一个虚拟继承的指针有20字节大小,而不是想象中的16字节。

// Itaniumunknown virtual inheritance下的情况.

struct {

FunctionPointerm_func_address; //Itanium来说是64

int m_delta;

int m_vtable_index;

int m_vtordisp;

};

我不能保证Comeau C++使用的是和GNU相同的技术,也不能保证它们是否使用short代替int使这种虚拟函数指针的结构的大小缩小至8个字节。最近发布的Comeau C++版本为了兼容微软的编译器也使用了微软的编译器关键字(我想它也只是忽略这些关键字而不对它们进行实质的相关处理罢了)。

Digital Mars编译器(即最初的Zortech C++到后来的Symantec C++)使用了一种不同的优化方法。对单一继承类来说,一个成员函数指针仅仅是这个函数的地址。但涉及到更复杂的继承时,这个成员函数指针指向一个形式转换函数(thunk function),这个函数可以实现对this指针的必要调整并可用来调用实际的成员函数。每当涉及到多重继承的时候,每一个成员函数的指针都会有这样一个形式转换函数,这对函数调用来说是非常有效的。但是这意味着,当使用多重继承的时候,子类的成员函数指针向基类成员函数指针的转换就会不起作用了。可见,这种编译器对编译代码的要求比其他的编译器要严格得多。

很多嵌入式系统的编译器不允许多重继承。这样,这些编译器就避免了可能出现的问题:一个成员函数指针就是一个带有隐藏this指针参数的普通函数指针。

 

微软"smallest for class"方法的问题

微软的编译器使用了和Borland相似的优化方法。它们都使单一继承的情况具有最优的效率。但不像Borland,微软在缺省条件下成员函数指针省略了值为0 的指针入口(entry),我称这种技术为“smallest for class”方法:对单一继承类来说,一个成员函数指针仅保存了函数的地址(m_func_address),所以它有4字节长。而对于多重继承类来说,由于用到了偏移地址(m_delta),所以它有8字节长。对虚拟继承,会用到12个字节。这种方法确实节省空间,但也有其它的问题。

首先,将一个成员函数指针在子类和基类之间进行转化会改变指针的大小!因此,信息是会丢失的。其次,当一个成员函数指针在它的类定义之前声明的时候,编译器必须算出要分配给这个指针多少空间,但是这样做是不安全的,因为在定义之前编译器不可能知道这个类的继承方式。对Intel C++和早期的微软编译器来说,编译器仅仅对指针的大小进行猜测,一旦在源文件中猜测错误,你的程序会在运行时莫名其妙地崩溃。所以,微软的编译器中增加了一些保留字:__single_inheritance,__multiple_inheritance, __virtual_inheritance,并增设了一些编译器开关(compiler switch),如/vmg,让所有的成员函数指针有相同的大小,而对原本个头小的成员函数指针的空余部分用0填充。Borland编译器也增加了一些编译器开关,但没有增加新的关键字。Intel的编译器可以识别Microsoft增加的那些关键字,但它在能够找到类的定义的情况下会对这些关键字不做处理。

对于MSVC来说,编译器需要知道类的vtable在哪儿;通常就会有一个this指针的偏移量(vtordisp),这个值对所有这个类中的成员函数来说是不变的,但对每个类来说会是不同的。对于MSVC,经调整过的this指针是在原this指针的基础上经过下面的计算得出的:

if (vindex=0) //如果不是虚拟继承(_virtual_inheritance

adjustedthis = this + delta

else //如果是

adjustedthis = this + delta+ vtordisp + *(*(this + vtordisp) + vindex)

在虚拟继承的情况下,vtordisp的值并不保存在__virtual_inheritance指针中,而是在发现函数调用的代码时,编译器才将其相应的汇编代码进去。但是对于未知类型的继承,编译器需要尽可能地通过读代码确定它的继承类型,所以,编译器将虚拟继承指针(virtualinheritance pointer)分为两类(__virtual_inheritance__unknown_inheritance)。

理论上,所有的编译器设计者应该在MFP(成员函数指针)的实现上有所变革和突破。但在实际上,这是行不通的,因为这使现在编写的大量代码都需要改变。微软曾发表了一篇非常古老的文章(http://msdn.microsoft.com/archive/en-us/dnarvc/html/jangrayhood.asp)来解释Visual C++运作的实现细节。这篇文章是Jan Gray写的,他曾在1990年设计了Microsoft C++的对象模型。尽管这篇文章发表于1994年,但这篇文章仍然很重要——这意味着C++的对象模型在长达15年的时间里(1990年到2004年)没有丝毫改变。

现在,我想你对成员函数指针的事情已经知道得太多了。要点是什么?我已为你建立了一个规则。虽然各种编译器的在这方面的实现方法有很大的不同,但是也有一些有用的共同点:不管对哪种形式的类,调用一个成员函数指针生成的汇编语言代码是完全相同的。有一种特例是使用了“smallest forclass”技术的非标准的编译器,即使是这种情况,差别也是很微小的。这个事实可以让我们继续探索怎样去建立高性能的委托(delegate)。

 

委托(delegate

和成员函数指针不同,你不难发现委托的用处。最重要的,使用委托可以很容易地实现一个Subject/Observer设计模式的改进版[GoF, p. 293]Observer(观察者)模式显然在GUI中有很多的应用,但我发现它对应用程序核心的设计也有很大的作用。委托也可用来实现策略(Strategy[GoF, p. 315]和状态(State[GoF, p. 305]模式。

现在,我来说明一个事实,委托和成员函数指针相比并不仅仅是好用,而且比成员函数指针简单得多!既然所有的.NET语言都实现了委托,你可能会猜想如此高层的概念在汇编代码中并不好实现。但事实并不是这样:委托的实现确实是一个底层的概念,而且就像普通的函数调用一样简单(并且很高效)。一个C++委托只需要包含一个this指针和一个简单的函数指针就够了。当你建立一个委托时,你提供这个委托一个this指针,并向它指明需要调用哪一个函数。编译器可以在建立委托时计算出调整this指针需要的偏移量。这样在使用委托的时候,编译器就什么事情都不用做了。这一点更好的是,编译器可以在编译时就可以完成全部这些工作,这样的话,委托的处理对编译器来说可以说是微不足道的工作了。在x86系统下将委托处理成的汇编代码就应该是这么简单:

mov ecx, [this]

call [pfunc]

但是,在标准C++中却不能生成如此高效的代码。 Borland为了解决委托的问题在它的C++编译器中加入了一个新的关键字(__closure,用来通过简洁的语法生成优化的代码。GNU编译器也对语言进行了扩展,但和Borland的编译器不兼容。如果你使用了这两种语言扩展中的一种,你就会限制自己只使用一个厂家的编译器。而如果你仍然遵循标准C++的规则,你仍然可以实现委托,但实现的委托就不会是那么高效了。

有趣的是,C#和其他.NET语言中,执行一个委托的时间要比一个函数调用慢8(参见http://msdn.microsoft.com/library/en-us/dndotnet/html/fastmanagedcode.asp)。我猜测这可能是垃圾收集和.NET安全检查的需要。最近,微软将统一事件模型(unified event model加入到Visual C++中,随着这个模型的加入,增加了__event __raise__hook__unhookevent_sourceevent_receiver等一些关键字。坦白地说,我对加入的这些特性很反感,因为这是完全不符合标准的,这些语法是丑陋的,因为它们使这种C++不像C++,并且会生成一堆执行效率极低的代码。

 

解决这个问题的推动力:对高效委托(fast delegate)的迫切需求

使用标准C++实现委托有一个过度臃肿的症状。大多数的实现方法使用的是同一种思路。这些方法的基本观点是将成员函数指针看成委托&#0;&#0;但这样的指针只能被一个单独的类使用。为了避免这种局限,你需要间接地使用另一种思路:你可以使用模版为每一个类建立一个成员函数调用器(member function invoker。委托包含了this指针和一个指向调用器(invoker)的指针,并且需要在堆上为成员函数调用器分配空间。

对于这种方案已经有很多种实现,包括在CodeProject上的实现方案。各种实现在复杂性上、语法(比如,有的和C#的语法很接近)上、一般性上有所不同。最具权威的一个实现是boost::function。最近,它已经被采用作为下一个发布的C++标准版本中的一部分[Sutter1]。希望它能够被广泛地使用。

就像传统的委托实现方法一样,我同样发觉这种方法并不十分另人满意。虽然它提供了大家所期望的功能,但是会混淆一个潜在的问题:人们缺乏对一个语言的底层的构造。成员函数调用器的代码对几乎所有的类都是一样的,在所有平台上都出现这种情况是令人沮丧的。毕竟,堆被用上了。但在一些应用场合下,这种新的方法仍然无法被接受。

我做的一个项目是离散事件模拟器,它的核心是一个事件调度程序,用来调用被模拟的对象的成员函数。大多数成员函数非常简单:它们只改变对象的内部状态,有时在事件队列(event queue)中添加将来要发生的事件,在这种情况下最适合使用委托。但是,每一个委托只被调用(invoked)一次。一开始,我使用了boost::function,但我发现程序运行时,给委托所分配的内存空间占用了整个程序空间的三分之一还要多!我要真正的委托!我在内心呼喊着,真正的委托只需要仅仅两行汇编指令啊!

我并不能总是能够得到我想要的,但后来我很幸运。我在这儿展示的代码(代码下载链接见译者注)几乎在所有编译环境中都产生了优化的汇编代码。最重要的是,调用一个含有单个目标的委托(single-target delegate)的速度几乎同调用一个普通函数一样快。实现这样的代码并没有用到什么高深的东西,唯一的遗憾就是,为了实现目标,我的代码和标准C++的规则有些偏离。我使用了一些有关成员函数指针的未公开知识才使它能够这样工作。如果你很细心,而且不在意在少数情况下的一些编译器相关(compiler-specific)的代码,那么高性能的委托机制在任何C++编译器下都是可行的。

诀窍:将任何类型的成员函数指针转化为一个标准的形式

我的代码的核心是一个能够将任何类的指针和任何成员函数指针分别转换为一个通用类的指针和一个通用成员函数的指针的类。由于C++没有通用成员函数(generic member function的类型,所以我把所有类型的成员函数都转化为一个在代码中未定义的CGenericClass类的成员函数。

大多数编译器对所有的成员函数指针平等地对待,不管他们属于哪个类。所以对这些编译器来说,可以使用reinterpret_cast将一个特定的成员函数指针转化为一个通用成员函数指针。事实上,假如编译器不可以,那么这个编译器是不符合标准的。对于一些接近标准(almost-compliant)的编译器,比如Digital Mars,成员函数指针的reinterpret_cast转换一般会涉及到一些额外的特殊代码,当进行转化的成员函数的类之间没有任何关联时,编译器会出错。对这些编译器,我们使用一个名为horrible_cast的内联函数(在函数中使用了一个union来避免C++的类型检查)。使用这种方法看来是不可避免的&#0;&#0;boost::function也用到了这种方法。

对于其他的一些编译器(如Visual C++, Intel C++Borland C++),我们必须将多重(multiple-)继承和虚拟(virtual-)继承类的成员函数指针转化为单一(single-)继承类的函数指针。为了实现这个目的,我巧妙地使用了模板并利用了一个奇妙的戏法。注意,这个戏法的使用是因为这些编译器并不是完全符合标准的,但是使用这个戏法得到了回报:它使这些编译器产生了优化的代码。

既然我们知道编译器是怎样在内部存储成员函数指针的,并且我们知道在问题中应该怎样为成员函数指针调整this指针,我们的代码在设置委托时可以自己调整this指针。对单一继承类的函数指针,则不需要进行调整;对多重继承,则只需要一次加法就可完成调整;对虚拟继承...就有些麻烦了。但是这样做是管用的,并且在大多数情况下,所有的工作都在编译时完成!

这是最后一个诀窍。我们怎样区分不同的继承类型?并没有官方的方法来让我们区分一个类是多重继承的还是其他类型的继承。但是有一种巧妙的方法,你可以查看我在前面给出了一个列表(见中篇)——MSVC,每种继承方式产生的成员函数指针的大小是不同的。所以,我们可以基于成员函数指针的大小使用模版!比如对多重继承类型来说,这只是个简单的计算。而在确定unknown_inheritance16字节)类型的时候,也会采用类似的计算方法。

对于微软和英特尔的编译器中采用不标准12字节的虚拟继承类型的指针的情况,我引发了一个编译时错误(compile-timeerror),因为需要一个特定的运行环境(workaround)。如果你在MSVC中使用虚拟继承,要在声明类之前使用FASTDELEGATEDECLARE宏。而这个类必须使用unknown_inheritance(未知继承类型)指针(这相当于一个假定的__unknown_inheritance关键字)。例如:

FASTDELEGATEDECLARE(CDerivedClass)

class CDerivedClass :virtual public CBaseClass1, virtual public CBaseClass2 {

// : (etc)

};

这个宏和一些常数的声明是在一个隐藏的命名空间中实现的,这样在其他编译器中使用时也是安全的。MSVC7.0或更新版本)的另一种方法是在工程中使用/vmg编译器选项。而Inter的编译器对/vmg编译器选项不起作用,所以你必须在虚拟继承类中使用宏。我的这个代码是因为编译器的bug才可以正确运行,你可以查看代码来了解更多细节。而在遵从标准的编译器中不需要注意这么多,况且在任何情况下都不会妨碍FASTDELEGATEDECLARE宏的使用。

一旦你将类的对象指针和成员函数指针转化为标准形式,实现单一目标的委托(single-target delegate)就比较容易了(虽然做起来感觉冗长乏味)。你只要为每一种具有不同参数的函数制作相应的模板类就行了。实现其他类型的委托的代码也大都与此相似,只是对参数稍做修改罢了。

这种用非标准方式转换实现的委托还有一个好处,就是委托对象之间可以用等式比较。目前实现的大多数委托无法做到这一点,这使这些委托不能胜任一些特定的任务,比如实现多播委托(multi-cast delegates [Sutter3]

 

静态函数作为委托目标(delegate target

理论上,一个简单的非成员函数(non-member function),或者一个静态成员函数(static memberfunction)可以被作为委托目标(delegate target)。这可以通过将静态函数转换为一个成员函数来实现。我有两种方法实现这一点,两种方法都是通过使委托指向调用这个静态函数的调用器(invoker的成员函数的方法来实现的。

第一种方法使用了一个邪恶的方法(evil method)。你可以存储函数指针而不是this指针,这样当调用调用器的函数时,它将this指针转化为一个静态函数指针,并调用这个静态函数。问题是这只是一个戏法,它需要在代码指针和数据指针之间进行转换。在一个系统中代码指针的大小比数据指针大时(比如DOS下的编译器使用medium内存模式时),这个方法就不管用了。它在目前我知道的所有32位和64位处理器上是管用的。但是因为这种方法还是不太好,所以仍需要改进。

另一种是一个比较安全的方法(safe method),它是将函数指针作为委托的一个附加成员。委托指向自己的成员函数。当委托被复制的时候,这些自引用(self-reference)必须被转换,而且使“=”“==”运算符的操作变得复杂。这使委托的大小增至4个字节,并增加了代码的复杂性,但这并不影响委托的调用速度。

我已经实现了上述两种方法,两者都有各自的优点:安全的方法保证了运行的可靠性,而邪恶的方法在支持委托的编译器下也可能会产生与此相同的汇编代码。此外,安全的方法可避免我以前讨论的在MSVC中使用多重继承和虚拟继承时所出现的问题。我在代码中给出的是安全的方法的代码,但是在我给出的代码中邪恶的方法会通过下面的代码生效:

#define(FASTDELEGATE_USESTATICFUNCTIONHACK)

 

多目标委托(multiple-target delegate)及其扩展

使用委托的人可能会想使委托调用多个目标函数,这就是多目标委托(multiple-target delegate,也称作多播委托(multi-cast delegate。实现这种委托不会降低单一目标委托(single-targetdelegate)的调用效率,这在现实中是可行的。你只需要为一个委托的第二个目标和后来的更多目标在堆上分配空间就可以了,这意味着需要在委托类中添加一个数据指针,用来指向由该委托的目标函数组成的单链表的头部节点。如果委托只有一个目标函数,将这个目标像以前介绍的方法一样保存在委托中就行了。如果一个委托有多个目标函数,那么这些目标都保存在空间动态分配的链表中,如果要调用函数,委托使用一个指针指向一个链表中的目标(成员函数指针)。这样的话,如果委托中只有一个目标,函数调用存储单元的个数为1;如果有nn>0)个目标,则函数调用存储单元的个数为n+1(因为这时函数指针保存在链表中,会多出一个链表头,所以要再加一——译者注),我认为这样做最合理。

由多播委托引出了一些问题。怎样处理返回值?(是将所有返回值类型捆绑在一起,还是忽略一部分?)如果把同一个目标在一个委托中添加了两次那会发生什么?(是调用同一个目标两次,还是只调用一次,还是作为一个错误处理?)如果你想在委托中删除一个不在其中的目标应该怎么办?(是不管它,还是抛出一个异常?)

最重要的问题是在使用委托时会出现无限循环的情况,比如,A委托调用一段代码,而在这段代码中调用B委托,而在B委托调用的一段代码中又会调用A委托。很多事件(event)和信号跟踪(signal-slot)系统会有一定的方案来处理这种问题。

为了结束我的这篇文章,我的多播委托的实现方案就需要大家等待了。这可以借鉴其他实现中的方法——允许非空返回类型,允许类型的隐式转换,并使用更简捷的语法结构。如果我有足够的兴趣我会把代码写出来。如果能把我实现的委托和目前流行的某一个事件处理系统结合起来那会是最好不过的事情了(有自愿者吗?)。

 

本文代码的使用

原代码包括了FastDelegate的实现(FastDelegate.h)和一个demo .cpp的文件用来展示使用FastDelegate的语法。对于使用MSVC的读者,你可以建立一个空的控制台应用程序(ConsoleApplication)的工程,再把这两个文件添加进去就好了,对于GNU的使用者,在命令行输入“gcc demo.cpp”就可以了。

FastDelegate可以在任何参数组合下运行,我建议你在尽可能多的编译器下尝试,你在声明委托的时候必须指明参数的个数。在这个程序中最多可以使用8个参数,若想进行扩充也是很容易的。代码使用了fastdelegate命名空间,在fastdelegate命名空间中有一个名为detail的内部命名空间。

Fastdelegate使用构造函数或bind()可以绑定一个成员函数或一个静态(全局)函数,在默认情况下,绑定的值为0(空函数)。可以使用操作符判定它是一个空值。

不像用其他方法实现的委托,这个委托支持等式运算符(==, !=)。

下面是FastDelegateDemo.cpp的节选,它展示了大多数允许的操作。CBaseClassCDerivedClass的虚基类。你可以根据这个代码写出更精彩的代码,下面的代码只是说明使用FastDelegate的语法:

using namespacefastdelegate;

int main(void)

{

printf("--FastDelegate demo -- A no-parameter

delegate is declared usingFastDelegate0 ");

FastDelegate0noparameterdelegate(&SimpleVoidFunction);

noparameterdelegate();

//调用委托,这一句调用SimpleVoidFunction()

printf(" -- Examplesusing two-parameter delegates (int, char *) -- ");

typedef FastDelegate2MyDelegate;

MyDelegate funclist[12]; //委托初始化,其目标为空

CBaseClass a("BaseA");

CBaseClass b("BaseB");

CDerivedClass d;

CDerivedClass c;

// 绑定一个成员函数

funclist[0].bind(&a,&CBaseClass::SimpleMemberFunction);

//你也可以绑定一个静态(全局)函数

funclist[1].bind(&SimpleStaticFunction);

//绑定静态成员函数

funclist[2].bind(&CBaseClass::StaticMemberFunction);

// 绑定const型的成员函数

funclist[3].bind(&a,&CBaseClass::ConstMemberFunction);

// 绑定虚拟成员函数

funclist[4].bind(&b,&CBaseClass::SimpleVirtualFunction);

// 你可以使用”=”来赋值

funclist[5] =MyDelegate(&CBaseClass::StaticMemberFunction);

funclist[6].bind(&d,&CBaseClass::SimpleVirtualFunction);

//最麻烦的情况是绑定一个抽象虚拟函数(abstract virtual function

funclist[7].bind(&c,&CDerivedClass::SimpleDerivedFunction);

funclist[8].bind(&c,&COtherClass::TrickyVirtualFunction);

funclist[9] =MakeDelegate(&c, &CDerivedClass::SimpleDerivedFunction);

// 你也可以使用构造函数来绑定

MyDelegate dg(&b,&CBaseClass::SimpleVirtualFunction);

char *msg = "Lookingfor equal delegate";

for (int i=0; i<12; i++){

printf("%d :",i);

// 可以使用”==”

if (funclist[i]==dg) { msg= "Found equal delegate"; };

//可以使用”!”来判应一个空委托

if (!funclist[i]) {

printf("Delegate isempty ");

} else {

// 调用生成的经过优化的汇编代码

funclist[i](i, msg);

};

}

};

因为我的代码利用了C++标准中没有定义的行为,所以我很小心地在很多编译器中做了测试。具有讽刺意味的是,它比许多所谓标准的代码更具有可移植性,因为几乎所有的编译器都不是完全符合标准的。目前,核心代码已成功通过了下列编译器的测试:

·        Microsoft Visual C++ 6.0, 7.0 (.NET) and 7.1 (.NET 2003)(including /clr 'managed C++'),

·        GNU G++ 3.2 (MingW binaries),

·        Borland C++ Builder

对于Comeau C++ 4.3(x86, SPARC, Alpha, Macintosh),能够成功通过编译,但不能链接和运行。对于Intel C++ 8.0for Itanium能够成功通过编译和链接,但不能运行。

此外,我已对代码在MSVC 1.5 4.0Open Watcom WCL 1.2上的运行情况进行了测试,由于这些编译器不支持成员函数模版,所以对这些编译器,代码不能编译成功。对于嵌入式系统不支持模版的限制,需要对代码进行大范围的修改。(这一段是在刚刚更新的原文中添加的——译者注)

而最终的FastDelegate并没有进行全面地测试,一个原因是,我有一些使用的编译器的评估版过期了,另一个原因是——我的女儿出生了!如果有足够的兴趣,我会让代码在更多编译器中通过测试。(这一段在刚刚更新的原文中被删去了,因为作者目前几乎完成了全部测试。——译者注)

 

总结

为了解释一小段代码,我就得为这个语言中具有争议的一部分写这么一篇长长的指南。为了两行汇编代码,就要做如此麻烦的工作。唉~

我希望我已经澄清了有关成员函数指针和委托的误解。我们可以看到为了实现成员函数指针,各种编译器有着千差万别的方法。我们还可以看到,与流行的观点不同,委托并不复杂,并不是高层结构,事实上它很简单。我希望它能够成为这个语言(标准C++)中的一部分,而且我们有理由相信目前已被一些编译器支持的委托,在不久的将来会加入到标准C++的新的版本中(去游说标准委员会!)。

据我所知,以前实现的委托都没有像我在这里为大家展示的FastDelegate一样有如此高的性能。我希望我的代码能对你有帮助。如果我有足够的兴趣,我会对代码进行扩展,从而支持多播委托(multi-cast delegate)以及更多类型的委托。我在CodeProject上学到了很多,并且这是我第一次为之做出的贡献。

 

参考文献

[GoF] "DesignPatterns: Elements of Reusable Object-Oriented Software", E. Gamma, R.Helm, R. Johnson, and J. Vlissides.

I've looked at dozens ofwebsites while researching this article. Here are a few of the most interestingones:

我在写这篇文章时查看了很多站点,下面只是最有趣的一些站点:

[Boost] Delegates can beimplemented with a combination of boost::function and boost::bind.Boost::signals is one of the most sophisticated event/messaging systemavailable. Most of the boost libraries require a highly standards-conformingcompiler. http://www.boost.org/

[Loki] Loki provides'functors' which are delegates with bindable parameters. They are very similarto boost::function. It's likely that Loki will eventually merge with boost. http://sourceforge.net/projects/loki-lib

[Qt] The Qt libraryincludes a Signal/Slot mechanism (i.e., delegates). For this to work, you haveto run a special preprocessor on your code before compiling. Performance isvery poor, but it works on compilers with very poor template support. http://doc.trolltech.com/3.0/signalsandslots.html

[Libsigc++] An event systembased on Qt's. It avoids the Qt's special preprocessor, but requires that everytarget be derived from a base object class (using virtual inheritance - yuck!).http://libsigc.sourceforge.net/

[Hickey]. An old (1994)delegate implementation that avoids memory allocations. Assumes that allpointer-to-member functions are the same size, so it doesn't work on MSVC.There's a helpful discussion of the code here. http://www.tutok.sk/fastgl/callback.html

[Haendal]. A websitededicated to function pointers?! Not much detail about member function pointersthough. http://www.function-pointer.org/

[Sutter1] Generalizedfunction pointers: a discussion of how boost::function has been accepted intothe new C++ standard. http://www.cuj.com/documents/s=8464/cujcexp0308sutter/

[Sutter2] Generalizing theObserver pattern (essentially, multicast delegates) using std::tr1::function.Discusses the limitations of the failure of boost::function to provide operator==.

http://www.cuj.com/documents/s=8840/cujexp0309sutter

[Sutter3] Herb Sutter'sGuru of the Week article on generic callbacks. http://www.gotw.ca/gotw/083.htm

 

关于作者Don Clugston

我在澳大利亚的high-techstartup工作,是一个物理学家兼软件工程师。目前从事将太阳航空舱的硅质晶体玻璃(CSG)薄膜向市场推广的工作。我从事有关太阳的(solar)研究,平时喜欢做一些软件(用作数学模型、设备控制、离散事件触发器和图象处理等),我最近喜欢使用STLWTL写代码。我非常怀念过去的光荣岁月:)而最重要的,我有一个非常可爱的儿子(20025月出生)和一个非常年轻的小姐(20045月出生)。

黑暗不会战胜阳光,阳光终究会照亮黑暗。

译者注

由于本文刚发表不久,作者随时都有可能对文章或代码进行更新,若要浏览作者对本文的最新内容,请访问:

http://www.codeproject.com/cpp/FastDelegate.asp

点击以下链接下载FastDelegate的源代码:

http://www.codeproject.com/cpp/FastDelegate/FastDelegate_src.zip

 

 

36.h头文件 .lib库文件 .dll动态链接库文件关系

.h头文件是编译时必须的,lib是链接时需要的,dll是运行时需要的。

附加依赖项的是.lib不是.dll,若生成了DLL,则肯定也生成 LIB文件。如果要完成源代码的编译和链接,有头文件和lib就够了。如果也使动态连接的程序运行起来,有dll就够了。在开发和调试阶段,当然最好都有。

.h .lib .dll三者的关系是:

H文件作用是:声明函数接口

DLL文件作用是: 函数可执行代码

当我们在自己的程序中引用了一个H文件里的函数,编链器怎么知道该调用哪个DLL文件呢?这就是LIB文件的作用: 告诉链接器调用的函数在哪个DLL中,函数执行代码在DLL中的什么位置,这也就是为什么需要附加依赖项 .LIB文件,它起到桥梁的作用。如果生成静态库文件,则没有DLL ,只有lib,这时函数可执行代码部分也在lib文件中

目前以lib后缀的库有两种,一种为静态链接库(Static Libary,以下简称静态库”),另一种为动态连接库(DLL,以下简称动态库”)的导入库(Import Libary,以下简称导入库)。静态库是一个或者多个obj文件的打包,所以有人干脆把从obj文件生成lib的过程称为Archive,即合并到一起。比如你链接一个静态库,如果其中有错,它会准确的找到是哪个obj有错,即静态lib只是壳子。动态库一般会有对应的导入库,方便程序静态载入动态链接库,否则你可能就需要自己LoadLibary调入DLL文件,然后再手工GetProcAddress获得对应函数了。有了导入库,你只需要链接导入库后按照头文件函数接口的声明调用函数就可以了。导入库和静态库的区别很大,他们实质是不一样的东西。静态库本身就包含了实际执行代码、符号表等等,而对于导入库而言,其实际的执行代码位于动态库中,导入库只包含了地址符号表等,确保程序找到对应函数的一些基本地址信息。

一般的动态库程序有lib文件和dll文件。lib文件是必须在编译期就连接到应用程序中的,而dll文件是运行期才会被调用的。如果有dll文件,那么对应的lib文件一般是一些索引信息,具体的实现在dll文件中。如果只有lib文件,那么这个lib文件是静态编译出来的,索引和实现都在其中。静态编译的lib文件有好处:给用户安装时就不需要再挂动态库了。但也有缺点,就是导致应用程序比较大,而且失去了动态库的灵活性,在版本升级时,同时要发布新的应用程序才行。在动态库的情况下,有两个文件,而一个是引入库(.LIB)文件,一个是DLL文件,引入库文件包含被DLL导出的函数的名称和位置,DLL包含实际的函数和数据,应用程序使用LIB文件链接到所需要使用的DLL文件,库中的函数和数据并不复制到可执行文件中,因此在应用程序的可执行文件中,存放的不是被调用的函数代码,而是DLL中所要调用的函数的内存地址,这样当一个或多个应用程序运行是再把程序代码和被调用的函数代码链接起来,从而节省了内存资源。从上面的说明可以看出,DLL.LIB文件必须随应用程序一起发行,否则应用程序将会产生错误。

 

 

#pragma once

编辑

目 录

1概述

2具体写法

3比较

1概述

这是一个比较常用的C/C++杂注,只要在头文件的最开始加入这条杂注,就能够保证头文件只被编译一次。

#pragma once是编译器相关的,就是说即使这个编译系统上有效,但在其他编译系统也不一定可以,不过现在基本上已经是每个编译器都有这个杂注了。

#ifndef,#define,#endif是C/C++语言中的宏定义,通过宏定义避免文件多次编译。所以在所有支持C++语言的编译器上都是有效的,如果写的程序要跨平台,最好使用这种方式。

2具体写法

方式一:

#ifndef _SOMEFILE_H_

#define _SOMEFILE_H_

.......... // 一些声明语句

#endif

方式二:

#pragma once

... ... // 一些声明语句

3比较

#ifndef的方式依赖于宏名字不能冲突,这不光可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件不会被不小心同时包含。当然,缺点就是如果不同头文件的宏名不小心“撞车”,可能就会导致头文件明明存在,编译器却硬说找不到声明的状况。

#pragma once则由编译器提供保证:同一个文件不会被编译多次。注意这里所说的“同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。带来的好处是,你不必再费劲想个宏名了,当然也就不会出现宏名碰撞引发的奇怪问题。对应的缺点就是如果某个头文件有多份拷贝,本方法不能保证他们不被重复包含。当然,相比宏名碰撞引发的“找不到声明”的问题,重复包含更容易被发现并修正。

方式一由语言支持所以移植性好,方式二 可以避免名字冲突

#pragma once方式产生于#ifndef之后,因此很多人可能甚至没有听说过。目前看来#ifndef更受到推崇。因为#ifndef受语言天生的支持,不受编译器的任何限制;而#pragma once方式却不受一些较老版本的编译器支持,换言之,它的兼容性不够好。也许,再过几年等旧的编译器死绝了,这就不是什么问题了。

我还看到一种用法是把两者放在一起的:

#pragma once

#ifndef __SOMEFILE_H__

#define __SOMEFILE_H__

... ... // 一些声明语句

#endif

看起来似乎是想兼有两者的优点。不过只要使用了#ifndef就会有宏名冲突的危险,所以混用两种方法似乎不能带来更多的好处,倒是会让一些不熟悉的人感到困惑。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值