版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第7章const和inline7.1const变量 137.2const成员变量与成员函数7.3内联函数本章小结习题
const问题一直是C++语言设计中的一个难题。很多从C转成C++ 的程序员可能还是会经常使用#define这种宏定义来声明常量,但是对于程序来说,用const来声明常量会更加灵活。比如用const声明常量时可以有数据类型,而#define这种常量的声明就没有数据类型,这样便于编译器对其进行数据类型的安全性检查等。7.1const变量通俗地讲,常量就是常数,或代表固定不变值的名字。在程序中,如果想让某个变量的内容从初始化后就一直保持不变,就可以定义一个常量。被const修饰的对象都受到保护,可以防止在程序中其他的地方被意外地改动,从而提高程序的健壮性。7.1.1const与值替代
1.内部常量在C++中,平常使用的内部数据类型的值都可以称作是值常量,下面介绍几种内部数据类型的常量。(1)实数型常量:在C++中,有两种实数(即浮点数):一种是带有小数点的,还有一种就是指数型的:小数点型:1.23 指数型:1.23e2或1.23E2(2)整型常量:十进制:10 八进制:012 十六进制:0X12(3)字符常量:普通字符:'a' 特殊字符:'\n'(4)字符串常量:
"Helloworld“(5)枚举常量:
enumday{Mon,Tue,Wends,Thu,Fri,Sat,Sun};在这里,Mon的值默认为0,后面依次加1。2.const声明常量下面来看看在程序中定义的常量。如果在整个程序的编写中,在很多地方都需要用到一个常数时,那么,在这些地方的一个或多个地方写错了这个值的话,就会导致计算错误。这时,可以给这个常数取一个名字,在每处常数被使用的地方都用这个名字代替,那么编译器只需要检查这个名字的拼写错误,这样能更容易地避免数值的不一致性导致的计算错误。举个常见的例子,在编写很多关于圆的周长或面积的计算公式的程序时,经常需要使用到圆周率p,此时,可以通过命名一个容易理解并且便于记忆的名字来改进程序的可读性,并且在定义的时候为其加上关键字const来进行修饰,为其加上常量的性质,来帮助预防程序出现数值一致性的错误。由于圆周率p不属于C++语言的字符描述集的范围,因此不能直接当作C++程序中的变量名,所以可以用另外的名字来表示:
constfloatpi=3.141592653;
在这里,细心的读者可能会提出疑问:float型的变量能存储上面所有小数点后的数值吗?不错,的确不行!因为在C++中利用const关键字来声明常量的时候是需要指定数据类型的,而且C++的编译器会进行类型的检查。由于float型只能存储7位有效精度的实数,因此在上面的声明中,pi的实际值是3.141593,最后的3位是不起作用的,而且最后一位会四舍五入。如果将上面定义中的float型改为double型,则能够完全存储上面小数点后的数值。
在将某变量利用const关键字修饰之后,程序中的任何语句对它都只能读取值而不能修改,这样可以防止该变量的值在程序中被无意中修改。也正是由于常量不能被修改,因此,常量必须在定义时就初始化,而不能像下面的代码:
constfloatpi;pi=3.1415926;
这样做是错误的,常量的名称永远都不能放在赋值语句的左边。由于常量在程序运行之前就已经知道了它的值,即在编译时就能求值了,因此在定义常量时,对其进行初始化的值可以是一个常量的表达式。但是这个常量表达式中不能含有某个函数,因为一般的函数都是在程序运行时才能返回具体的值,在编译时是无法获取其返回值的。不过有一点比较容易混淆,即类似于sizeof()这种看起来像函数的表达式,它其实是C++中的基本操作符,它在编译时就可以确定返回值了,所以这种表达式可以出现在常量的赋值语句中。其实在C语言中就已出现了const,用来声明常量,但是C语言中的const对数据的修饰仅仅是表明这是一个不能再被更改的普通变量,而且对该变量的数据类型没有说明。
3.const声明变量在C语言中,const关键字的主要作用是定义常量、修饰函数参数和修饰函数返回值这三个;在C++中,const关键字不仅有这三个作用,而且还能修饰函数的定义体和定义类中某个函数为恒态函数,即无法改变类中的数据成员。
此外,在C语言中,const修饰变量时是需要占用系统内存的,并且它的名字是全局符。这也使得C语言的编译器不能把const修饰的对象当作是一个编译期间的常量。例如:
constsize=100;
chara[size];
看起来这两个语句是正确的,是合理的声明,但是这段C程序却会得到一个编译的错误。其原因是,在C语言中,用const修饰的变量size占用了内存的某个地址,故C语言的编译器得不到它在编译时的值,因此会出现编译的错误。在C语言当中,直接用const加上变量名即可声明一个常量,和#define一样,无需指定该变量的数据类型。但是在C++中不行,在C++中使用const修饰变量时必须同时声明该变量的数据类型,这也是面向对象程序设计的一个特性的体现。
在C语言中还可以这样使用const来修饰变量:
constsize;
C语言的编译器把这看做是一个合理的声明,这个声明表示在别的地方有内存分配,在这里进行引用。但是这样在C++中是不允许的,因为在C语言中默认const是外部连接的,而在C++中默认const是内部连接的,所以在C++中仅仅这样声明是不正确的。如果想用C++达到上面同样的目的,必须显式地用extern将内部连接改为外部连接:
externconstsize;这样,C++的编译器也能知道这是一个声明,表示在别的地方有内存分配。当然,这样显式地在C语言中声明也是可以的,但是由于在C语言中默认的就是外部连接,所以这样使用没有太大的意义。4.const与 #define的区别其实,从某种意义上来说,在C语言当中使用const的意义也不大。在前面曾提到,用const修饰的变量在编译期间必须知道它的值,所以即使在常数表达式里想使用一个已命名的值,用const的效率并不如在预处理器中使用#define的高。因此在C语言里,建议还是使用#define来声明常量。
const常量和 #define声明的宏常量的区别如下:
(1)在C++中声明的const常量是有数据类型的,而用#define声明的常量是没有数据类型的。因此,C++的编译器可以对常量进行数据类型的安全性检查,而C语言的编译器只是对 #define声明的常量进行字符的替代,这样对数据类型不仅没有安全性检查,而且在字符替代的过程中,可能会产生意料不到的错误,比如边际效应等。
(2)很多集成化的编译工具在对C++程序进行编译时,可以对const常量进行调试,但是对于C语言中的宏常量是无法进行调试的,所以在C++中使用const常量对程序的调试也是有很大帮助的。
(3)使用const常量比 #define形式更节省空间,避免不必要的内存分配。例如:
#defineA10 //宏定义
constinta=10; //const常量定义,此时并没有将a放入ROM中
intb=A; //编译期间会进行宏替换,需要分配内存intc=a; //此时会为a分配内存,以后不再分配
intd=A; //编译期间会进行宏替换,需要再次分配内存
inte=a; //不需要分配内存7.1.2常量指针与指针常量
1.常量指针当一个指针所指向的内容是不能被修改的内存空间时,称这个指针为常量指针。例如:
constinta=100;constint*p=&a;
此时,就称p为常量指针,因为指针p指向的是一个常量,它占据的内存空间是不能被外界所修改的。因为常量是不能修改的,所以下面通过指针的赋值操作是错误的:*p=200;常量指针的含义是指针指向了一个不能再被修改的变量,而这个指针本身是可以修改的,例如可以将上面的指针p再指向另一个常量:
constintb=200;p=&b;
这时指针p还是一个常量指针,只是它指向的是另一个常量而已,当然在这里还是不能为*p去赋值。
常量指针有一个很特殊的用法,还是以上面已经定义的常量指针p为例:
intc=300;p=&c;*p=400; //错误
c=400; //正确变量c并不是常量,所以它可以被修改,但是为什么还是不能对*p进行赋值呢?这是因为定义了一个指向某个常量的指针,只是限制了这个指针的间接访问操作,而不能规定这个指针所指向的值其本身的操作的规定性。就像上面将常量指针指向一个普通的变量,这样就可以保护被指向的变量在指针的操作中无法被修改,但是可以直接操作这个非常量的值。这种用法在函数传递中会被经常使用到。
下面来看看函数传递时const的用法:
#include<iostream>usingnamespacestd;voidmyStrcpy(char*destStr,constchar*sourceStr){ while(*destStr++=*sourceStr++) ;}intmain(){ chara[15]="Helloworld!"; charb[15]; myStrcpy(b,a); cout<<b<<endl; return0;}
运行结果如下:char型的数组a本来是一个变量,但是在传递给函数strcpy作为参数时,被常量指针修饰之后就成为不能被修改的常量了,这样就不允许对源字符串进行任何修改。因为在将char型数组a传递给strcpy函数时,相当于执行了以下操作:constint*sourceStr=&a;所以在函数体里,*sourceStr是个常量,不能将*sourceStr作为左值进行任何操作,从而达到保护源字符串的目的。当然,在主函数中,数组a还是一个普通的变量,不受任何约束,可以随时被修改。2.指针常量从字面意思就可以粗略地看出,指针常量是指这个指针本身就是一个常量。和常量指针相反,指针常量所指向的内存空间的值是可以修改的,但是这个指针本身是不能被修改的。例如:
#include<iostream>usingnamespacestd;intmain(){ chara='B'; char*constp=&a; *p='A'; cout<<*p<<endl; return0;}
运行结果如下:
因为p是指针常量,它本身是不能被修改的,所以它不能出现在赋值语句的左侧,否则将会引起编译错误。指针常量在初始化时和普通常量一样,既然不能被修改,那么在定义指针常量时就必须初始化。
由于*p并不是常量,因此可以对其进行修改,例如还可以这样做:*(p+1)
此时,可以对指针p所指向的内存区域的下一个字节进行操作。既然有指向常量的指针,也有指针常量,那么当然可以定义一个指向常量的指针常量:
constinta=10;constint*constp=&a;
这时,指针p指向的是一个常量,所以*p值是不能被修改的,而指针p本身也是一个指针常量,因此指针p也是不能被修改的,即p所指向的地址是不能被修改的。所以此时p和*p都不能作为赋值语句的左值而被修改。7.1.3常量引用在很多时候,函数需要将非内部数据类型(如程序员自己定义的类)的对象作为参数,而这样的函数的运行效率是非常低的。因为在运行期间,在函数体的内部将会产生非内部数据类型的临时对象用于复制其作为参数的对象,而临时对象的构造、析构以及赋值的操作都将消耗系统资源。例如:
classA{ …};voidfunction(Aa){ …}
这时,为了提高效率,可以将函数参数中的类对象改为类对象的引用,这仅仅是借用了一下别名而已,不会产生非内部数据类型的对象,这样可以大大提高程序的效率。但是,这样做也有一个不足的地方,就是引用在传递的过程中有可能改变原对象的值,这不是希望看到的。为了解决这个问题,只需要为这个引用加上const关键字来修饰即可:
voidfunction(constA&a)
这样,不但不会对原对象的值有任何影响,而且比用指针更简单些,体现了使用const所带来的安全性。
在C++中的引用不像指针那样,分常量指针和指针常量,C++是不会区分某个变量的const引用和const变量的引用的。因为你永远都不可能给引用本身去重新赋值,使它去指向另外一个变量,而且引用总是const的,所以无所谓什么常量引用和引用常量了。因此,像前面那样对引用应用const关键字来修饰,其作用只是使引用的目标成为const变量,所以你不可能看到这样的情况:
constintconst&a=1;7.1.4传递const值在很多时候,在调用某个需要参数的函数时,并不希望在它的函数体内将这个参数进行任何修改,而仅仅是将值“借给”这个函数当作参数来使用,通常是在将值传递给该函数时加上关键字const进行修饰以达到这个目的。下面简单看一下这种传递const值的方法:
voidfunction(constinta){ …}
这样,在主函数中调用函数function时,用相应的变量初始化const修饰的常量,在函数体中就可以按照被const修饰的内容进行常量化,而为该函数传递的参数值在该函数体内是无法被修改的。前面两个小节介绍的用const修饰指针或引用作为函数的参数来传递值,就属于传递const值的特殊情况。因为当参数采用指针或引用来传递时,可以防止指针或引用所指向的内容被意外修改。在一般的程序中,使用const指针或const引用来传递函数的参数的情况较为普遍,而一般在仅仅是内部数据类型的值传递时,建议尽量不要用const关键字来进行修饰。因为对于内部数数据类型的参数,函数本身会自动产生一个临时变量来复制该参数的内容,而且作为内部数据类型,不存在构造和析构的问题,复制的过程也很快,不会消耗多少系统资源。这样,这个内部数据类型的参数本来就无需保护,所以这个时候更不需要利用const关键字来进行修饰,以此达到对该参数的保护目的。当然,显式地使用const来修饰也不会产生错误,对于一些C++的初学者,可能还会有加深理解的作用。其实,还有一种方法可以确保在函数内部的执行不会影响到函数外部传递给函数的参数,就是在函数体的内部定义另外一个变量来复制这个参数的值。例如:voidfunction(inta){ constint&b=a; …}
这样,在函数体内部只需要对变量b进行操作即可。关于传递const值,有一个需要特别注意的地方,就是const只能修饰输入函数。如果函数的某个参数是作为输出使用的,那么这个时候,不论采用的是const指针传递还是const引用传递,都不能用const关键字来进行修饰,否则这个函数将失去输出的功能。总之,通过使用常量参数,函数的调用者可以保证他们所传递给函数的对象不会在函数执行过程中被改变,也不会对函数的执行带来任何其他影响。7.1.5返回const值当希望函数的返回值在使用时不被外界改变时,可以为其加上const关键字来进行修饰。当然,如果一个函数的返回值是void,那么就完全不用const来对返回值进行修饰了。其实,不仅仅是对于void型的函数返回值,如果函数返回的是内部数据类型,也不必用const对返回值进行修饰。例如:
constintfunction(inta){ …}
该函数返回的是int型的值,即返回的本来就是一个数值,当然不可能再通过赋值语句对其进行重新赋值。所以,一般来说,当函数的返回类型为内部数据类型时,建议最好将const关键字去掉。当函数返回非内部数据类型(即程序员自己定义的类)的对象或这个对象的引用时,也最好不使用const关键字进行修饰,因为此时返回值如果具有const常量属性,则返回的实例只能访问这个自定义类的public成员、protected成员和const成员函数(在下一小节将介绍),而且不允许对这些成员进行赋值操作,这在一般的软件设计中出现的几率比较小。所以,不建议为返回值为某个自定义类的对象或对象的引用的函数加上const来修饰返回值。
当然,并不是当所有的函数返回类对象时都不适用const来修饰返回值,只是一般情况下,此时,用const修饰返回值的情况多用于二元操作符的重载并会产生新对象的情况。当函数的返回值为指针时,应为函数的返回值加上const关键字,那么该函数返回的指针不能被用在赋值语句的左侧而被修改,它只能赋值给被const关键字修饰的同类型的指针。例如:
constchar*function();constchar*p=function();
而下面的语句将会出现编译错误:
char*p=function();7.2const成员变量与成员函数1.const成员变量和普通变量一样,在构造一个类的时候,也可以为这个类的某些成员加上const关键字来修饰,使其成为const数据成员。而对于const成员变量,需要注意的是,由于它必须被初始化而且又不能被更新,那么在构造函数中,只能使用初始化成员列表来对其进行初始化,如果试图在构造函数的函数体内或者该类的其他地方对const成员变量进行初始化或修改,都会引起编译错误。例如:
#include<iostream>usingnamespacestd;classA{ constinta;public: A(intb):a(b){}
intgetA() { returna; }};intmain(){ A*a=newA(10); cout<<a->getA()<<endl; return0;}
运行结果如下:
因为C++是一种面向对象的程序设计语言,所以,const成员变量也只是在该类的某个对象的生存期间内是常量,对于整个类来说则是可以改变的。因为这个类可以创建很多个它自己的对象,而在这些不同的类对象中,其const成员变量的值都可以互不相同。比如在上面的例子中,在创建类A的对象时,可以为其构造函数传入不同的参数提供给成员初始化列表,这样,在不同的类对象中,常量a就具有不同的值。所以,在类的声明中,不能对const成员变量进行初始化。因为在类的对象没有被具体创建时,编译器无法知道某个具体对象的const成员变量的值是多少。
因此,类中的const成员变量并不能使其在每一个类对象中都具有固定的值,这样在大型的项目开发中可能会带来一些不便。如果想要使用在整个类中都是常量的成员,那么可以使用枚举来实现。例如:
classA{ enum{a=10,b=20,…}; …};
此外,这种枚举型的常量不会占用类对象的存储空间,编译器在编译期间就知道这些成员变量的值。不过这种使用方法的不足之处是,枚举的默认类型为整型数据,所以最大值是有限制的,而且不能表示浮点型的数据。2.const成员函数使用了const关键字进行修饰的成员函数,称之为const成员函数。例如:
voidfunction()const{ …}
当某个类中有const成员变量时,这些const成员变量不像普通变量一样能被该类中的所有成员函数所访问或进行操作,只有const成员函数才能访问或者操作类中的常量成员或对象,没有加const修饰的成员函数试图访问或操作const成员时,编译器为了保证这些成员的const特性,会引发编译错误。当然,在const成员函数的内部,也不能试图去改变const成员变量的值。
从上面的const成员函数的声明格式可以看出,const关键字是加在函数说明的后面的,它是整个函数类型说明的一个组成部分,所以,是否为成员函数加上const关键字进行修饰,是可以进行函数重载的。而且,既然const成员函数要操作类的const成员,那么在函数体的内部,肯定还需要出现const关键字。前面讲过,const成员变量的初始化必须放在初始化成员列表中来进行,构造函数是不能对const成员变量进行操作的,所以,构造函数是不能声明为const的。同理,析构函数也不可以声明为const的。
const成员函数能访问或操作const成员变量,当然也能访问、操作不是const的成员变量,那么这样做又有什么意义呢?
有些时候,虽然类的某些数据成员没有被const关键字加以修饰,但是在某个函数的函数体内,还是不希望这些数据成员被修改,那么,这时为函数加上const修饰就能达到不会修改任何数据成员的目的。如果在函数体内,不小心执行了修改这些数据成员的操作,或者调用了其他的非const成员函数而引发某些操作时,编译器都将提示错误,这样能大大提高程序的健壮性。下面,通过具体的程序来理解为什么要给访问普通数据成员的函数加上const修饰。
classA{public: intadd(){number++} intgetValue()const;private: intnumber;};intA::getValue()const{ //++number; //编译错误,因为试图修改数据成员
//add();//编译错误,因为试图调用非const成员函数
returnnumber;}
这样,在getValue()函数的执行过程中,就能确保number的值不会被任何其他操作所修改。3.使用const的注意事项
(1)在能够理解并且知道为什么要使用的情况下,要尽可能放心地去使用const关键字,这样经常可以提高程序的可读性、可维护性以及健壮性。但是并不是说const常量可以过多地使用。如果在某个头文件中定义了一个常量,那么在程序链接时,所有使用这个头文件的代码都将复制这个常量,如果用到该头文件的代码比较多,则会增加程序的可执行文件的大小,在一个大的工程项目中,这是很影响效率的。所以,不要随便将一个对象修饰为常量,要真正需要时才使用const关键字。
(2)永远不要将const修饰的成员放在赋值语句的左侧,试图对其进行修改,这是使用const关键字最基本的原则。(3)在传递参数时,const关键字一般修饰指针或者引用,而不是内部数据类型或者是自定义类的实例,因此这时的const关键字显得没有太大的意义。
(4)区分清楚并理解const和成员函数之间的关系:传递const值、返回const值和const成员函数,const关键字要放在需要它的地方,并了解const放在不同地方的不同意义,不要轻易地将函数的返回值修饰为const。而且,除非是操作符重载时,否则一般不要将函数的返回值的类型声明为对某个类对象的const引用。
(5)应该尽量将常量局部化,只在需要常量的模块中定义就可以了,因为并不是所有的常量都需要全局来访问,除非是整个工程项目都需要知道的关键常量。7.3内联函数在C++ 程序中,一些局部的变量都是存放在栈里面,而系统的栈空间是有限的,在程序中,如果频繁地去使用一些相同的函数,就会大量地消耗栈空间,如果栈空间用尽了,那么程序的执行就会出现错误。不仅仅是函数的局部变量会带来问题,在某个函数被调用时,其实是将程序执行的顺序转移到该函数在内存中存放的地址,在调用完该函数之后,程序的执行将跳转回原来的地址,继续执行原来的内容。这就要求在调用该函数之前,需要记录调用者原来的执行地址,调用完毕后需要返回所记录的地址并继续往下执行,这在时间和空间上都会有一定的消耗,影响整个程序的执行效率。
为了解决这个问题,在C++中特别地引入了内联函数,用inline关键字来修饰。
7.3.1inline和编译器通过以下程序来看看内联函数是如何解决上述问题的:
#include<iostream>#include<string>usingnamespacestd;inlinestringfunction(inta){ return(a%2==0)?"偶数":"奇数";}voidmain(){ for(inti=0;i<10;i++) { cout<<i<<":"<<function(i)<<endl;}}
如果没有用inline来修饰函数,那么在主函数中的循环内部将调用10次function()函数,这样,就会反复在栈中为function()的局部变量开辟内存,这将消耗大量的栈空间。
为函数function()加上inline关键字进行修饰后,它就成了一个内联函数,在主函数的执行过程之前,编译器已经将每次的function()函数调用都替换成:
return(a%2==0)?"偶数":"奇数";
这样,就不会因为反复调用函数并为其局部变量反复开辟栈空间了。因为在程序编译时,编译器就已经将程序中出现的调用内联函数的表达式都用内联函数的函数体中的语句进行了替换。这样,既不用反复地为函数的局部变量在栈中开辟内存空间,也不存在执行程序在函数的调用者和函数之间来回跳转而引起的时间和空间的消耗。
通过上面的程序片段可以很清楚地看到,内联函数的定义只需要在函数声明或定义的时候,在前面为其加上inline关键字即可,即
inline返回类型函数名(参数列表){ …}
在定义内联函数时要注意,在声明函数为内联函数时,必须同时定义该函数的函数体,如果将声明和函数体的定义分开,那么即使在声明时加上了inline关键字,编译器也只是将该函数当作普通函数一样对待。例如,如下的函数声明中,inline关键字是不会起任何作用的:
inlinestringfunction(inta);
当某个函数加上inline关键字声明为内联函数以后,在程序的其他地方对该函数的调用就不像一般的函数是在函数运行时调用,而是通过编译器在编译期间就已经对该函数的调用处运用了代码的替换。不过要注意的是,内联函数必须在其第一次被调用前就定义或声明。另外,inline关键字的使用并不是没有限制的,由于编译器对内联函数的处理是对其在调用时实行代码的替换,那么就要求内联函数的函数体内的代码是简单结构的,不能有复杂的结构控制语句或循环语句,如switch、for等。比如在前面所举的例子,主函数在for循环中调用内联函数,如果内联函数中有更加复杂的循环语句,那么在执行代码的替换后,很可能就会造成循环结构的混乱,从而导致程序的错误。而且内联函数本身不能是直接递归函数,这样,也会由于函数体内的递归关系导致程序结构的混乱。既然内联函数是由编译器在编译期间处理,那么在下一章将要介绍的虚函数就没有办法被编译器当作内联函数了,由于多态中虚函数的特性,要等到程序运行时才知道到底执行哪一个函数。实际上,内联函数所带来的并不仅仅是节省函数调用对栈空间等的开销,而且会对编译器有一定的优化作用。因为当编译器在编译一段连续的且没有对其他函数调用的代码时,会大大地提高效率,所以当使用inline关键字修饰一个函数使其成为内联函数时,就可以使得编译器在对这些连续的无函数调用的代码进行编译时带来特殊的优化。
如果某个被inline修饰的函数的函数体很小,那么为这个内联函数的函数体生成的目标代码可能比在调用一个函数时产生的目标代码要小很多。此时,使用内联函数多所带来的结果就是使得目标代码更小,而且对指令的缓存有更高的使用率。
inline关键字对于编译器来说,其实只能算是一种建议,而不是对编译器的命令。这个建议可以用inline关键字显式或隐式地向编译器提出。这里所说的“隐式”,其实是对于类的成员函数而言的,因为类的成员函数都被编译器默认为内联函数。当然,和前面所提到的关于inline关键字的使用限制一样,只有当成员函数的函数体没有复杂的结构时,成员函数才能被编译器当作内联函数来对待,否则,编译器将放弃对成员函数的内联方式的使用。
对于普通的函数也一样,当函数的结构体很复杂时,即使使用inline关键字进行了修饰,在编译时编译器还是会放弃内联的方式。在这里,对前面的一个知识点进行一些补充。编译器是否接受inline关键字的请求,并不仅仅取决于函数体内的结构是否复杂。因为内联函数并不是完美无瑕的,它的使用也是有一定的代价的。由于内联函数实现的本质就是在调用时实行代码的替换,那么这肯定会增加整个工程的目标代码的数量,因此在程序中过多地使用内联函数就会导致程序的目标代码的庞大,从而导致资源有限的计算机的执行效率的下降,这样内联函数本来想体现的优势将不复存在,甚至还不如普通函数的调用。所以,当函数用inline关键字向编译器提出请求时,编译器会根据函数的具体情况来决定会不会对该函数真正使用内联的方式在编译时对其进行处理。因此,从这个角度来说,inline关键字对于编译器仅仅是个请求。既然编译器对内联函数的操作仅仅是代码的替换,而#define这种宏定义的方式也是替换,那么它们之间又有什么区别呢?其实,在很多时候使用宏来进行代码的替换,也是为了节省一些系统资源的开销。例如,可以通过下面的宏定义来获得a和b中较大的一个,而不需要再去定义一个函数:
#defineMAX(a,b)((a)>(b))?(a):(b)
但是宏的使用的局限性太大,比如在前面也提到了,对于常量建议用const关键字而不使用#define的形式,其原因就是宏的使用没有对数据类型的检查。你很难想象,对于一个函数来说,内部的数据如果没有数据类型会是什么样子。内联函数和宏定义最根本的区别是:内联函数的调用是由编译器在编译期间进行函数体代码的替换,而宏则是由预处理器在编译之前进行宏体对宏名的替换。下面总结一下宏定义和内联函数在代码替换方面的区别:
(1)宏的替换方式是在编译之前由预处理器进行的,即先用宏体替换宏名,然后再进行编译,而内联函数的替换方式是在编译时由编译器将函数体替换为调用表达式。(2)由于在宏的定义中是没有数据类型的,因此宏所做的替换其实只是简单的字符串替换,而在对内联函数的调用中,对于在代码替换过程中的参数是有数据类型限制的。
(3)在宏的定义中所使用的参数不占系统的内存空间,只是做简单的字符串替换,而在对内联函数的调用中,参数的传递则是具体变量之间的内容传递,而且形参会作为代码替代后函数的局部变量,显然是要占系统的内存空间的。因此,内联函数不仅能起到宏定义一样的替换作用,节省函数在调用期间对系统资源的开销,而且解决了在宏的定义中没有数据类型的不足,比宏的使用更加灵活。7.3.2inline函数与程序效率在前一节已经介绍过,C++引入内联函数的目的就是希望解决程序中由于函数调用而引起的效率问题,所以内联函数的使用能够提高函数的运行效率。内联函数的调用方法和普通函数一样,但是由于在编译期间,编译器已将内联函数的函数体完全替代了程序中对内联函数的调用,因此内联函数的执行效率比一般的函数要高。在前面已经介绍,被inline关键字修饰的内联函数不需要再像普通的函数被调用一样,使程序的执行权在函数的内部和函数被调用的地方来回跳转,在时间和空间上都大大提高了效率,而且不需要再为函数的局部变量在栈中反复开辟内存空间了。
一般来说,类中的成员函数都会被编译器当作内联函数来对待,而最常用的情况莫过于在类中将关于类成员存取的函数作为内联函数来使用。因为在程序员自定义的类中,经常会有很多私有的或者是受保护的数据成员,以防止外界访
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年大学计算机科学与技术(计算机网络)试题及答案
- 2025年中职药剂(药品检验)试题及答案
- 2025年中职森林培育(森林培育技术)试题及答案
- 2025年中职(汽车运用与维修)汽车电器设备检修试题及答案
- 2025年中职耳鼻喉护理(耳鼻喉基础护理)试题及答案
- 2025年大学软件工程(人工智能应用基础)试题及答案
- 2025年高职无人机植保技术(植保方案设计)试题及答案
- 2025年高职工业机器人技术(机器人调试与运维)试题及答案
- 2025年中职统计学(统计调查)试题及答案
- 2026年管道安装(水管铺设)试题及答案
- 2026年孝昌县供水有限公司公开招聘正式员工备考题库及答案详解参考
- 《创新创业基础》课件-项目1:创新创业基础认知
- 2026年初一寒假体育作业安排
- 物流行业运输司机安全驾驶与效率绩效评定表
- 2026北京市通州区事业单位公开招聘工作人员189人笔试重点基础提升(共500题)附带答案详解
- 2025~2026学年山东省菏泽市牡丹区第二十一初级中学八年级上学期期中历史试卷
- 2026国家统计局仪征调查队招聘辅助调查员1人(江苏)考试参考试题及答案解析
- 水利工程施工质量检测方案
- 2025年北京高中合格考政治(第一次)试题和答案
- 卵巢类癌诊治中国专家共识(2025年版)
- 临床护理教学中的人文关怀
评论
0/150
提交评论