C++笔记.docx
- 文档编号:8936934
- 上传时间:2023-05-16
- 格式:DOCX
- 页数:38
- 大小:177.21KB
C++笔记.docx
《C++笔记.docx》由会员分享,可在线阅读,更多相关《C++笔记.docx(38页珍藏版)》请在冰点文库上搜索。
C++笔记
C++的函数中,什么时候要写inline
这涉及一个效率问题。
记住,调用函数的开销是很大的,所谓的空间开销是指调用函数前,先要将原来的函数保存在寄存器(占用寄存器空间)里面,并在调用结束后恢复。
调用函数时,还要复制实参(占用内存空间)。
如果被调用函数一旦调用频繁,就会花费很多空间。
如果你有一段“短小而频繁调用的函数”,内联是个不错的选择。
“如果含有复杂的分支或循环结构,我使用inline会有什么后果?
”
事实上,所谓内联,是编译器将内联函数在函数调用点上展开函数代码。
例如
inlineintsmall(inta,intb){returna
a:
b;}
cout<
cout<<(a
a:
b)< inline只是你给编译器提一个建议,希望它将函数内联,至于它会不会执行就不一定了。 基本上复杂的函数它是不会同意的,就算同意了,也只会使你的编译变得更将浪费时间,而执行速度得不到什么提升。 还有,一些编译器是不会通过含有递归的内联函数的。 记住,短小而且反复调用的函数进行内联就可以了。 inline 关键字用来定义一个类的内联函数,引入它的主要原因是用它替代C中表达式形式的宏定义。 表达式形式的宏定义一例: #defineExpressionName(Var1,Var2)((Var1)+(Var2))*((Var1)-(Var2))为什么要取代这种形式呢,且听我道来: 1.首先谈一下在C中使用这种形式宏定义的原因,C语言是一个效率很高的语言,这种宏定义在形式及使用上像一个函数,但它使用预处理器实现,没有了参数压栈,代码生成 等一系列的操作,因此,效率很高,这是它在C中被使用的一个主要原因。 2.宏定义在形式上类似于一个函数,但在使用它时,仅仅只是做预处理器符号表中的简单替换,因此它不能进行参数有效性的检测,也就不能享受C++编译器严格类型检查的好处,另外它的返回值也不能被强制转换为可转换的合适的类型,这样,它的使用就存在着一系列的隐患和局限性。 3.在C++中引入了类及类的访问控制,这样,如果一个操作或者说一个表达式涉及到类的保护成员或私有成员,你就不可能使用这种宏定义来实现(因为无法将this指针放在合适的位置)。 4.inline推出的目的,也正是为了取代这种表达式形式的宏定义,它消除了它的缺点,同时又很好地继承了它的优点。 宏定义和内联函数的区别,自己总结 1.宏定义和内联函数都不生成代码段,程序运行也没有参数压栈开销 2.内联函数检查调用参数的合法性,也可以进行参数类型转换 解释类声明与类定义之间的差异。 何时使用类声明? 何时使用类定义? 类声明是不完全类型,只能以有限方式使用,不能定义该类型的对象,只能用于定义指向该类型的指针及引用,或者声明使用该类型作为形参类型或返回类型的函数。 类定义,一旦类被定义,我们就可以知吗道所有类的成员,以及存储该类的对象所需的存储空间。 在创建类的对象之前或者使用引用或指针访问类的成员之前必须定义类。 This指针 1.this指针的用处: 一个对象的this指针并不是对象本身的一部分,不会影响sizeof(对象)的结果。 this作用域是在类内部,当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。 也就是说,即使你没有写上this指针,编译器在编译的时候也是加上this的,它作为非静态成员函数的隐含形参,对各成员的访问均通过this进行。 例如,调用date.SetMonth(9)<===>SetMonth(&date,9),this帮助完成了这一转换. 2.this指针的使用: 一种情况就是,在类的非静态成员函数中返回类对象本身的时候,直接使用return*this;另外一种情况是当参数与成员变量名相同时,如this->n=n(不能写成n=n)。 3.this指针程序示例: this指针存在于类的成员函数中,指向被调用函数所在的类实例的地址。 根据以下程序来说明this指针 #include classPoint { private: intx,y; public: Point(inta,intb){x=a;y=b;} voidMovePoint(inta,intb){x+=a;y+=b;} voidprint(){cout<<"x="< }; voidmain() { Pointpoint1(10,10); point1.MovePoint(2,2); point1.print(); } 当对象point1调用MovePoint(2,2)函数时,即将point1对象的地址传递给了this指针。 MovePoint函数的原型应该是voidMovePoint(Point*this,inta,intb);第一个参数是指向该类对象的一个指针,我们在定义成员函数时没看见是因为这个参数在类中是隐含的。 这样point1的地址传递给了this,所以在MovePoint函数中便显式的写成: voidMovePoint(inta,intb){this->x+=a;this->y+=b;} 即可以知道,point1调用该函数后,也就是point1的数据成员被调用并更新了值。 4.关于this指针的一个经典回答: 当你进入一个房子后, 你可以看见桌子、椅子、地板等, 但是房子你是看不到全貌了。 对于一个类的实例来说, 你可以看到它的成员函数、成员变量, 但是实例本身呢? this是一个指针,它时时刻刻指向你这个实例本身 下面的初始化式有错误。 找出并改正错误。 structX{ X(inti,intj): base(i),rem(base%j){} intrem,base; }; structX的定义中,rem先被定义,先被初始化,在构造函数初始化列表中的初始化顺序是先初始化rem,而此时rem用还没有被初始化的base来初始化rem,会产生runtimeerror. 改正为: structX{ X(inti,intj): rem(i%j),base(i){} 假定有个命名为NoDefault的类,该类有一个接受一个int的构造函数,但没有默认构造函数。 定义有一个NoDefault类型成员的类C。 为类C定义默认构造函数 classc{ public: c(): i(0),Ndf(i){} private: inti; NoDefaultNdf; }; Istream形参 逻辑上讲,我们可能希望将cin作为默认实参提供给接受一个istream&形参的构造函数。 编写使用cin作为默认实参的构造函数声明。 Sales_item(std: : istream&is=std: : cin); 引用类型参数 定义函数时,什么时候需要用引用类型的参数,什么时候不需要用? 函数的参数调用实际是对传入的实参生成一个副本,相当于复制了实参,这样在函数中的对该参数的修改只是作用在这个副本上,并没有对实参进行修改,只是对这个副本进行了修改,如果你想修改实参的值,就需要用引用或指针,还有另一种情况,如果你的实参类型比较复杂,比如类类型,那么在函数调用中他将复制整个类类型,消耗资源,使用引用就可以直接对这个值进行操作,避免了生成副本消耗的资源。 用实际参数初始化用引用型参数声明,值传递的方式是双向的: 实参带值进入函数,函数体通过实参的别名对实参进行操作也就是通过引用型参数可以返回计算结果 但是要记住实参必须是一个能够被引用的量,是一个变量实体 对比: voidswap(int&a,int&b) { inttemp; temp=a;a=b;b=temp; } voidswap(inta,intb) { inttemp; temp=a;a=b;b=temp; } #include usingnamespacestd; intmain() { intx=1,y=2; cout<<"x="< swap1(x,y); cout<<"x="< swap(x,y); cout<<"x="< return0; } do...while(0)的妙用 在C++中,有三种类型的循环语句: for,while,和do...while,但是在一般应用中作循环时,我们可能用for和while要多一些,do...while相对不受重视。 但是,最近在读我们项目的代码时,却发现了do...while的一些十分聪明的用法,不是用来做循环,而是用作其他来提高代码的健壮性。 1.do...while(0)消除goto语句。 通常,如果在一个函数中开始要分配一些资源,然后在中途执行过程中如果遇到错误则退出函数,当然,退出前先释放资源,我们的代码可能是这样: version1 bool Execute() { // 分配资源 int *p = new int; bool bOk(true); // 执行并进行错误处理 bOk = func1(); if(! bOk) { delete p; p = NULL; return false; } bOk = func2(); if(! bOk) { delete p; p = NULL; return false; } bOk = func3(); if(! bOk) { delete p; p = NULL; return false; } // .......... // 执行成功,释放资源并返回 delete p; p = NULL; return true; } 这里一个最大的问题就是代码的冗余,而且我每增加一个操作,就需要做相应的错误处理,非常不灵活。 于是我们想到了goto: version2 bool Execute() { // 分配资源 int *p = new int; bool bOk(true); // 执行并进行错误处理 bOk = func1(); if(! bOk) goto errorhandle; bOk = func2(); if(! bOk) goto errorhandle; bOk = func3(); if(! bOk) goto errorhandle; // .......... // 执行成功,释放资源并返回 delete p; p = NULL; return true; errorhandle: delete p; p = NULL; return false; } 代码冗余是消除了,但是我们引入了C++中身份比较微妙的goto语句,虽然正确的使用goto可以大大提高程序的灵活性与简洁性,但太灵活的东西往往是很危险的,它会让我们的程序捉摸不定,那么怎么才能避免使用goto语句,又能消除代码冗余呢,请看do...while(0)循环: version3 bool Execute() { // 分配资源 int *p = new int; bool bOk(true); do { // 执行并进行错误处理 bOk = func1(); if(! bOk) break; bOk = func2(); if(! bOk) break; bOk = func3(); if(! bOk) break; // .......... }while(0); // 释放资源 delete p; p = NULL; return bOk; } “漂亮! ”,看代码就行了,啥都不用说了... 2宏定义中的do...while(0) 如果你是C++程序员,我有理由相信你用过,或者接触过,至少听说过MFC,在MFC的afx.h文件里面,你会发现很多宏定义都是用了do...while(0)或do...while(false),比如说: #defineAFXASSUME(cond) do{bool__afx_condVal=! ! (cond);ASSERT(__afx_condVal);__analysis_assume(__afx_condVal);}while(0) 粗看我们就会觉得很奇怪,既然循环里面只执行了一次,我要这个看似多余的do...while(0)有什么意义呢? 当然有! 为了看起来更清晰,这里用一个简单点的宏来演示: #defineSAFE_DELETE(p)do{deletep;p=NULL}while(0) 假设这里去掉do...while(0), #defineSAFE_DELETE(p)deletep;p=NULL; 那么以下代码: if(NULL! =p)SAFE_DELETE(p) else ...dosth... 就有两个问题, 1)因为if分支后有两个语句,else分支没有对应的if,编译失败 2)假设没有else,SAFE_DELETE中的第二个语句无论if测试是否通过,会永远执行。 你可能发现,为了避免这两个问题,我不一定要用这个令人费解的do...while, 我直接用{}括起来就可以了 #defineSAFE_DELETE(p){deletep;p=NULL;} 的确,这样的话上面的问题是不存在了,但是我想对于C++程序员来讲,在每个语句后面加分号是一种约定俗成的习惯,这样的话,以下代码: if(NULL! =p)SAFE_DELETE(p); else ...dosth... 其else分支就无法通过编译了(原因同上),所以采用do...while(0)是做好的选择了。 也许你会说,我们代码的习惯是在每个判断后面加上{},就不会有这种问题了,也就不需要do...while了,如: if(...) { } else { } 诚然,这是一个好的,应该提倡的编程习惯,但一般这样的宏都是作为library的一部分出现的,而对于一个library的作者,他所要做的就是让其库具有通用性,强壮性,因此他不能有任何对库的使用者的假设,如其编码规范,技术水平等。 typedef还可以掩饰复合类型,如指针和数组。 例如,你不用象下面这样重复定义有81个字符元素的数组: charline[81]; chartext[81]; 定义一个typedef,每当要用到相同类型和大小的数组时,可以这样: typedefcharLine[81]; Linetext,secondline; 上面讨论的typedef行为有点像#define宏,用其实际类型替代同义字。 不同点是typedef在编译时被解释,因此让编译器来应付超越预处理器能力的文本替换。 例如: typedefint(*PF)(constchar*,constchar*); 这个声明引入了PF类型作为函数指针的同义字,该函数有两个constchar*类型的参数以及一个int类型的返回值。 如果要使用下列形式的函数声明,那么上述这个typedef是不可或缺的: PFRegister(PFpf); Register()的参数是一个PF类型的回调函数,返回某个函数的地址,其署名与先前注册的名字相同。 做一次深呼吸。 下面我展示一下如果不用typedef,我们是如何实现这个声明的: int(*Register(int(*pf)(constchar*,constchar*))) (constchar*,constchar*); 1.指针函数的定义 顾名思义,指针函数即返回指针的函数。 其一般定义形式如下: 类型名 *函数名(函数参数表列); 其中,后缀运算符括号“()”表示这是一个函数,其前缀运算符星号“*”表示此函数为指针型函数,其函数值为指针,即它带回来的值的类型为指针,当调用这个函数后,将得到一个“指向返回值为…的指针(地址),“类型名”表示函数返回的指针指向的类型”。 “(函数参数表列)”中的括号为函数调用运算符,在调用语句中,即使函数不带参数,其参数表的一对括号也不能省略。 其示例如下: int*pfun(int,int); 由于“*”的优先级低于“()”的优先级,因而pfun首先和后面的“()”结合,也就意味着,pfun是一个函数。 即: int*(pfun(int,int)); 接着再和前面的“*”结合,说明这个函数的返回值是一个指针。 由于前面还有一个int,也就是说,pfun是一个返回值为整型指针的函数。 我们不妨来再看一看,指针函数与函数指针有什么区别? int(*pfun)(int,int); 通过括号强行将pfun首先与“*”结合,也就意味着,pfun是一个指针,接着与后面的“()”结合,说明该指针指向的是一个函数,然后再与前面的int结合,也就是说,该函数的返回值是int。 由此可见,pfun是一个指向返回值为int的函数的指针。 虽然它们只有一个括号的差别,但是表示的意义却截然不同。 函数指针的本身是一个指针,指针指向的是一个函数。 指针函数的本身是一个函数,其函数的返回值是一个指针。 2. 用函数指针作为函数的返回值 在上面提到的指针函数里面,有这样一类函数,它们也返回指针型数据(地址),但是这个指针不是指向int、char之类的基本类型,而是指向函数。 对于初学者,别说写出这样的函数声明,就是看到这样的写法也是一头雾水。 比如,下面的语句: int(*ff(int))(int*,int); 我们用上面介绍的方法分析一下,ff首先与后面的“()”结合,即: int(*(ff(int)))(int*,int); // 用括号将ff(int)再括起来 也就意味着,ff是一个函数。 接着与前面的“*”结合,说明ff函数的返回值是一个指针。 然后再与后面的“()”结合,也就是说,该指针指向的是一个函数。 这种写法确实让人非常难懂,以至于一些初学者产生误解,认为写出别人看不懂的代码才能显示自己水平高。 而事实上恰好相反,能否写出通俗易懂的代码是衡量程序员是否优秀的标准。 一般来说,用typedef关键字会使该声明更简单易懂。 在前面我们已经见过: int(*PF)(int*,int); 也就是说,PF是一个函数指针“变量”。 当使用typedef声明后,则PF就成为了一个函数指针“类型”,即: typedefint(*PF)(int*,int); 这样就定义了返回值的类型。 然后,再用PF作为返回值来声明函数: PFff(int); 下面将以程序清单1为例,说明用函数指针作为函数的返回值的用法。 当程序接收用户输入时,如果用户输入d,则求数组的最大值,如果输入x,则求数组的最小值,如果输入p,则求数组的平均值。 程序清单 1 求最值与平均值示例 1 #include 2 #include 3 doubleGetMin(double*dbData,intiSize) // 求最小值 4 { 5 doubledbMin; 6 inti; 7 8 assert(iSize>0); 9 dbMin=dbData[0]; 10 for(i=1;i 11 if(dbMin>dbData[i]){ 12 dbMin=dbData[i]; 13 } 14 } 15 returndbMin; 16 } 17 18 doubleGetMax(double*dbData,intiSize) // 求最大值 19 { 20 doubledbMax; 21 inti; 22 23 assert(iSize>0); 24 dbMax=dbData[0]; 25 for(i=1;i 26 if(dbMax 27 dbMax=dbData[i]; 28 } 29 } 30 returndbMax; 31 } 32 33 doubleGetAverage(double*dbData,intiSize) // 求平均值 34 { 35 doubledbSum=0; 36 inti; 37 38 assert(iSize>0); 39 for(i=0;i 40 { 41
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- C+ 笔记