【精品】C++总结.doc_第1页
【精品】C++总结.doc_第2页
【精品】C++总结.doc_第3页
【精品】C++总结.doc_第4页
【精品】C++总结.doc_第5页
已阅读5页,还剩14页未读 继续免费阅读

下载本文档

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

文档简介

【精品】C+总结 一、struct字节对齐问题原则对齐的标准编译器的对齐模式和struct中的数据的最大字节取的最小值例一#pragma pack (4)#include#include#includeusing namespacestd;void main()struct TEST_1int a;char*b;float c;double d;cout (2)#include#include#includeusing namespacestd;void main()struct TEST_1int a;char b;char list9;double c;cout Int a占4个字节,double c占8个字节,所以整个struct为22例三#pragma pack (4)#include#include#includeusing namespacestd;void main()struct TEST_1int a;char b;struct TEST_2TEST_1tStr1;char a;int b;cout 二、判断大小端大端(Big-Endian)就是把数值的高位字节放在内存的低位地址上,把数值的地位字节放在内存的高位地址上。 小端(Little-Endian)就是把数字的高位字节放在高位的地址上,低位字节放在低位地址上。 #pragma pack (4)#include#include#includeusing namespacestd;void main()unionint a;char b;u;u.a=0x01020304;cout 该问题用另外一种方法实现为#pragma pack (4)#include#include#includeusing namespacestd;void main()int a;char*b;a=0x01020304;b=(char*)&a;coutfunc1();aObj-func2();正确(二)、extern关键字1.全局变量extern inta;表示a是在其他文件中定义的一个变量,需要在此引用。 a已在别的文件中,所以不需要再分配空间了。 定义只能有一处,但声明可有很多处,这些声明所指都是定义时分配的内存空间。 2.使用c的链接方式externc表示函数使用c的链接方式,也就是说能被c语言写的程序调用。 在C+程序中调用被C编译器编译过的函数。 为什么要加上externc声明呢?因为c语言和C+语言的编译规则不一样,所以要告诉系统哪些函数是用c方式编译的,哪些函数是需要用C+方式编译。 如果你不加externc,在编译时,系统会提示找不到此函数。 五、在C+中引用和指针的区别引用不允许引用空。 必须初始化,一旦初始化不可改变。 引用不在内存空间。 引用速度快。 指针可以指向NULL。 指针初始化之后,可以改变。 指针占4个字节的内存空间。 六、C+中的inline内联函数首先先说一下C语言中的宏定义,宏定义是在预处理期间起作用的。 定义过的宏直接拉到程序中,而且不做类型检查,很不安全。 而C+中的内联函数,也是把内联函数直接拉到使用内联函数的地方!玉宏定义不同的是,它做类型检查。 但是有些函数是不能当做内联函数用的!循环函数和递归函数就不能用内联函数,使用内联函数就是因为要编译更快!而这样展开的话,代码区会更长。 反而更浪费时间!同时虚函数也不可能有inline函数!因为虚函数是在执行期编译的,而inline函数是在预处理期就编译好的!有的时候很简单的函数即使你不定义为内联函数,编译器也会自己默认变成内联函数。 还有一点就是当有个函数指针指向内联函数之后,内联函数是不会扩展的!Void fun()Static obj;在这个函数fun()第一次调用的时间,static obj,会被声明出来。 如果是内联函数,例如inline void fun()Static obj;。 在另一个函数中对此此奥用fun(),每次都会横展开,每次都会产生一个static obj,但是在内存中的名字会变掉! 七、对象的内存布局 (1)不带虚函数的场合在对象的内存模型中,只存储非静态的成员变量。 成员函数无论是不是静态的都不存储在对象的内存模型中。 子类继承父类时,在子类的内存模型中,会进行逐位拷贝,把父类的内存空间的内容原封不动的拷贝下来,然后在接下来的空间中,加入自己的非静态成员变量。 接下来看个例子class TestObjBaseprivate:int database1;char database2;class TestObj:public TestObjBasepublic:void fun1();/不在类范围内static voidfun2();/不在类范围内private:int data1;static int data3;/不在类范围内float data2;内存布局 (2)包含虚函数的场合在父类中,会建立一个virtual table表里面弄存储着很多指向虚函数首地址的指针,在父类的内存空间中会有一个虚指针,指向这个虚表的首地址,另外一般情况下,该虚表格开始处会放置type_info,用于支持runtime typeindentification(RTTI)。 同时,子类的虚表中如果没有重写父类的你某个函数,会存储父类的这个函数。 接下来看下例子和内存模型class Basepublic:virtual voidfun1().;virtual voidfun2().;private:int database1;char database2;class derived:public Basepublic:voidfun1().;/没有重写基类的fun2virtual voidfun3().;private:int data1;intdata2;;database14字节database21字节3字节对齐用database14字节database21字节3字节对齐用data14字节data24字节TestObjBase TestObj内存模型 八、编译器行为如果你写一个空类,编译器会帮你创建一个析构函数,一个拷贝构造函数一个该类引用类型的赋值运算符,一对取址运算符。 如果你没有定义构造函数编译器会有可能帮你定义一个默认构造函数的。 例如class Emptypublic:Empty();/缺省构造函数Empty(const Empty&rhs);/拷贝构造函数Empty();/析构函数-一般非虚的(除非继承体系有虚函数)Empty&operator=(const Empty&rhs);/赋值运算符Empty*operator&();/取址运算符const Empty*operator&()const; (1)如果一个类中有指针类型的成员变量,并且没有自己定义赋值元运算符,那么当这个指针类型的变量通过2个对象指向不同的内存,然后这两个对象做赋值运算,这时因为没有自定义的operator=可以调用,c+会生成并调用一个缺省的operator=操作符。 这个缺省的赋值操作符会执行从一个对象的成员到另外一个对象的成员的逐个成员的赋值操作,对指针来说就是逐位拷贝。 那么这两个指针就指向了同一块内存地址,而必然会有一块内存存储着原来的信息,但是永远不能删除,并且这两个指针指向的同一块内存,当一个对象调用了析构函database14字节database21字节3字节对齐用database14字节database21字节3字节对齐用data14字节data24字节Base derivedType infoBase:fun1Base:fun2vptr baseType infoderived:fun1Base:fun2derived:fun3vptr derived数,就会回收内存,那么就有一个指针指向了,内容的内存。 这样就造成了内存泄露。 例如class stringpublic:string(const char*value);string();./没有拷贝构造函数和operator=private:char*data;string:string(const char*value)if(value)data=new charstrlen(value)+1;strncpy(data,value,sizeof(data);elsedata=new char1;*data=0;inline string:string()deletedata;如果这样定义两个对象string a(hello);string b(world);其结果就会如下所示a:datahello0b:dataworld0对象a的内部是一个指向包含字符串hello的内存的指针,对象b的内部是一个指向包含字符串world的内存的指针。 如果进行下面的赋值b=a;因为没有自定义的operator=可以调用,c+会生成并调用一个缺省的operator=操作符。 这个缺省的赋值操作符会执行从a的成员到b的成员的逐个成员的赋值操作,对指针(a.data和b.data)来说就是逐位拷贝。 赋值的结果如下所示a:data-hello0b:data-/world0这样world0这个就成了永远不能删除的内存了。 并且如果a对象调用析构函数就把hello0内存释放掉了,b对象中的指针就指向了的内存区域。 (2)如果没有定义拷贝构造函数的话,当在一个成员函数传值的时候,会调用默认拷贝构造函数生成一个原来变量的指针的拷贝,那么当这个成员函数执行完,这个指针拷贝也会释放空间,这样会把原来的变量的内存空间释放。 例如void donothing(string localstring)string s=the truthis outthere;donothing(s);因为被传递的localstring是一个值,它必须从s通过(缺省)拷贝构造函数进行初始化。 于是localstring拥有了一个s内的指针的拷贝。 当donothing结束运行时,localstring离开了其生存空间,调用析构函数。 其结果也将是s包含一个指向localstring早已删除的内存的指针。 解决此类问题方案1只要类里有指针时,就要写自己版本的拷贝构造函数和赋值操作符函数。 2可以只声明这些函数(声明为private成员)而不去定义(实现)它们。 这就防止了会有人去调用它们,也防止了编译器去生成它们。 如class stringprivate:string&operator=(const string&rhs); 九、成员函数、非成员函数、友元函数成员函数和非成员函数最大的区别在于成员函数可以是虚拟的而非成员函数不行。 所以,如果有个函数必须进行动态绑定,就要采用虚拟函数,而虚拟函数必定是某个类的成员函数。 关于这一点就这么简单。 如果函数不必是虚拟的,情况就稍微复杂一点。 看下面表示有理数的一个类class rationalpublic:rational(int numerator=0,int denominator=1);int numerator()const;int denominator()const;private:.;这是一个没有一点用处的类。 所以,要对它增加加,减,乘等算术操作支持,但是,该用成员函数还是非成员函数,或者,非成员的友元函数来实现呢?当拿不定主意的时候,用面向对象的方法来考虑!有理数的乘法是和rational类相联系的,所以,写一个成员函数把这个操作包到类中。 class rationalpublic:.const rationaloperator*(const rational&rhs)const;现在可以很容易地对有理数进行乘法操作rational oneeighth(1,8);rational onehalf(1,2);rational result=onehalf*oneeighth;/运行良好result=result*oneeighth;/运行良好但不要满足,还要支持混合类型操作,比如,rational要能和int相乘。 但当写下下面的代码时,只有一半工作result=onehalf*2;/运行良好result=2*onehalf;/出错!这是一个不好的苗头。 记得吗?乘法要满足交换律。 如果用下面的等价函数形式重写上面的两个例子,问题的原因就很明显了result=onehalf.operator* (2);/运行良好result=2.operator*(onehalf);/出错!对象onehalf是一个包含operator*函数的类的实例,所以编译器调用了那个函数。 而整数2没有相应的类,所以没有operator*成员函数。 编译器还会去搜索一个可以象下面这样调用的非成员的operator*函数(即,在某个可见的名字空间里的operator*函数或全局的operator*函数)result=operator*(2,onehalf);/错误!但没有这样一个参数为int和rational的非成员operator*函数,所以搜索失败。 再看看那个成功的调用。 它的第二参数是整数2,然而rational:operator*期望的参数却是rational对象。 怎么回事?为什么2在一个地方可以工作而另一个地方不行?关键在于隐式类型转换。 编译器知道传的值是int而函数需要的是rational,但它也同时知道调用rational的构造函数将int转换成一个合适的rational,所以才有上面成功的调用。 换句话说,编译器处理这个调用时的情形类似下面这样const rationaltemp (2);/从2产生一个临时/rational对象result=onehalf*temp;/同onehalf.operator*(temp);当然,只有所涉及的构造函数没有声明为explicit的情况下才会这样,因为explicit构造函数不能用于隐式转换,这正是explicit的含义。 如果rational象下面这样定义class rationalpublic:explicit rational(int numerator=0,/此构造函数为int denominator=1);/explicit.const rationaloperator*(const rational&rhs)const;.;那么,下面的语句都不能通过编译result=onehalf*2;/错误!result=2*onehalf;/错误!这不会为混合运算提供支持,但至少两条语句的行为一致了。 然而,我们刚才研究的这个类是要设计成可以允许固定类型到rational的隐式转换的这就是为什么rational的构造函数没有声明为explicit的原因。 这样,编译器将执行必要的隐式转换使上面result的第一个赋值语句通过编译。 实际上,如果需要的话,编译器会对每个函数的每个参数执行这种隐式类型转换。 但它只对函数参数表中列出的参数进行转换,决不会对成员函数所在的对象(即,成员函数中的*this指针所对应的对象)进行转换。 这就是为什么这个语句可以工作result=onehalf.operator* (2);/converts int-rational而这个语句不行result=2.operator*(onehalf);/不会转换/int-rational第一种情形操作的是列在函数声明中的一个参数,而第二种情形不是。 尽管如此,你可能还是想支持混合型的算术操作,而实现的方法现在应该清楚了使operator*成为一个非成员函数,从而允许编译器对所有的参数执行隐式类型转换class rational./contains nooperator*;const rationaloperator*(const rational&lhs,const rational&rhs)return rational(lhs.numerator()*rhs.numerator(),lhs.denominator()*rhs.denominator();rational onefourth(1,4);rational result;result=onefourth*2;/工作良好result=2*onefourth;/开始工作!这当然是一个完美的结局,但还有一个担心operator*应该成为rational类的友元吗?这种情况下,答案是不必要。 因为operator*可以完全通过类的公有(public)接口来实现。 上面的代码就是这么做的。 只要能避免使用友元函数就要避免,因为,和现实生活中差不多,友元带来的麻烦往往比它对你的帮助多。 然而,很多情况下,不是成员的函数从概念上说也可能是类接口的一部分,它们需要访问类的非公有成员的情况也不少。 例如string类。 如果想重载operator和operator和/operator(istream&input);ostream&operatorcin;/合法,但/有违常规s(istream&input,string&string)deletestring.data;read frominput intosome memory,and makestring.data pointto itreturn input;ostream&operator(ostream&output,const string&string)return output和operator或operator,让f成为非成员函数。 如果f还需要访问c的非公有成员,让f成为c的友元函数。 只有非成员函数对最左边的参数进行类型转换。 如果f需要对最左边的参数进行类型转换,让f成为非成员函数。 如果f还需要访问c的非公有成员,让f成为c的友元函数。 十、explicit关键字

温馨提示

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

最新文档

评论

0/150

提交评论