一.相关知识点
任何在类中定义的函数自动地成为内联函数,但也可以使用inline关键字放在类外定义的函数前面使之成为内联函数。但为了使之有效,必须使函数体和声明结合在一起,否则,编译器将它作为普通函数对待。因此
inline int PlusOne(int x);
没有任何效果,仅仅只是声明函数(这不一定能够在稍后某个时候得到一个内联定义)。成功的方法如下:
inline int PlusOne(int x) { return ++x ;}
在头文件里,内联函数默认为内部连接——即它是 static, 并且只能在它被包含的编译单元看到。因而,只要它们不在相同的编译单元中声明,在内联函数和全局函数之间用同样的名字也不会在连接时产生冲突。
局限性
这儿有两种编译器不能处理内联的情况。在这些情况下,它就像对非内联函数一样,通过定义内联函数和为函数建立存贮空间,简单地将其转换为函数的普通形式。假如它必须在多编译单元里做这些(通常将产生一个多定义错误),连接器就会被告知忽略多重定义。
假如函数太复杂,编译器将不能执行内联。这取决于特定编译器,但大多数编译器这时都会放弃内联方式,因为这时内联将可能不为我们提供任何效率。一般地,任何种类的循环都被认为太复杂而不扩展为内联函数。循环在函数里可能比调用要花费更多的时间。假如函数仅有一条简单语句,编译器可能没有任何内联的麻烦,但假如有许多语句,调用函数的开销将比执行函数体的开销少多了。记住,每次调用一个大的内联函数,整个函数体就被插入在函数调用的地方,所以没有任何引人注目的执行上的改进就使代码膨胀。本书的一些例子可能超过了一定的合理内联尺寸。
假如我们要显式或隐含地取函数地址,编译器也不能执行内联。因为这时编译器必须为函数代码分配内存从而为我们产生一个函数的地址。但当地址不需要时,编译器仍可能内联代码。我们必须理解内联仅是编译器的一个建议,编译器不强迫内联任何代码。一个好的编译器将会内联小的、简单的函数,同时明智地忽略那些太复杂的内联。这将给我们想要的结果—具有宏效率的函数调用。
小结
能够隐藏类下面的实现是关键的,因为在以后我们有可能想修改那个实现。我们可能为了效率这样做,或因为对问题有了更好的理解,或因为有些新类变得可用而想在实现里使用这些新类。任何危害实现隐蔽性的东西都会减少语言的灵活性。这样,内联函数就显得非常重要,因为它实际上消除了预处理器宏和伴随它们的问题。通过用内联函数方式,成员函数可以和预处理器宏一样有效。
当然,内联函数也许会在类定义里被多次使用。因为它更简单,所以程序设计者都会这样做。但这不是大问题,因为以后期待程序规模减少时,可以将函数移出内联而不影响它们的功能。开发指南应该是“首先是使它起作用,然后优化它。”
二.相关代码
1.
<span style="font-size:18px;"><strong>/*MACPO.cpp*/ #include <fstream.h> #define band(x) (((x)>5 && (x)<10)?(x):0) //但当数字在范围之内时,两个表达式都测试,产生两次自增操作。产生这个结果是 //由于再次对参数操作。一旦数字出了范围,两个条件仍然测试,所以也产生两次自 //增操作。根据参数不同产生的副作用也不同。 int main() { ofstream out("macro.out"); for(int i = 4; i < 11; ++i) { int a = i; out << "a = " << a << endl << '\t'; out << "band(++a)=" << band(++a) << endl; out << "\t a = " << a << endl; } return 0; }</strong></span>
2.
<span style="font-size:18px;"><strong>/*存取函数 在类中内联函数的最重要的用处之一是用于一种叫存取函数的函数。这是一个小函数,它 容许读或修改对象状态—即一个或几个内部变量。*/ /*ACCESS.cpp*/ #include <iostream> using namespace std; class access { int i; public: int read() const { return i; } void set(int I) { i = I; } }; int main() { access A; A.set(100); int x = A.read(); return 0; }</strong></span>
3.
<span style="font-size:18px;"><strong>/*存取器( accessors )和修改器( mutators ) 一些人进一步把存取函数的概念分成存取器(从一个对象读状态信息)和修改器(修改状态信息)。而 且,可以用重载函数对存取器和修改器提供相同名字的函数,如何调用函数决定了我们是读还是修改状 态信息。*/ /*RECTANGL.cpp*/ #include <iostream> using namespace std; class rectangle { int Width, Height; public: rectangle(int W = 0, int H = 0):Width(W), Height(H) {} int width() const//read { return Width; } void width(int W)//set { Width = W; } int height() const//read { return Height; } void height(int H)//set { Height = H; } }; int main() { rectangle R(19, 47); R.height(2*R.width()); R.width(2*R.height()); return 0; }</strong></span>
4.
<span style="font-size:18px;"><strong>/*一个内联函数对于一个还没有在类里声明的函数进行向前引用,编 译器就可能不能处理它*/ /*ECORDER.cpp*/ #include <iostream> using namespace std; class forward { int i; public: forward():i(0) {} int f() const { return g()+1; } int g() const { return i; } }; int main() { forward F; F.f(); return 0; } /*虽然函数g()还没有定义,但在函数 f()里对函数g()进行了调用。这是可行的,因 为语言定义规定非内联函数直到类声明结束才赋值。当然,函数g()也调用函数f(), 我们将得到一组递归调用,这些递归对于编译器进行内联是过于复杂了。(应该在函 数f()或g()里也执行一些测试来强迫它们之一“停止”,否则递归将是无穷的)。*/</strong></span>
5.
<span style="font-size:18px;"><strong>/*在构造函数和析构函数里隐藏行为 构造函数和析构函数是两个使我们易于认为内联比它实际上更有效的函数。构造函数和析 构函数都可能隐藏行为,因为类可以包含子对象,子对象的构造函数和析构函数必须被调用。 这些子对象可能是成员对象,或可能由于继承(继承还没有介绍)而存在。下面是一个有成员 对象的例子*/ /*HIDDEN.cpp*/ #include <iostream> using namespace std; class member { int i, j, k; public: member(int x = 0) { i = j = k = x; } ~member() { cout << "~member" << endl; } }; class withMembers { member Q, R, S; int i; public: withMembers(int I):i(I) {} ~withMembers() { cout << "~withMembers" <<endl; } }; int main() { withMembers WM(1); return 0; } /*在类withMembers里,内联的构造函数和析构函数看起来似乎很直接和简单,但其实很复 杂。成员对象Q、 P和S的构造函数和析构函数被自动调用,这些构造函数和析构函数也是内联 的,所以它们和普通的成员函数的差别是显著的。这并不是意味着应该使构造函数和析构函数 定义为非内联的。一般说来,快速地写代码来建立一个程序的初始“轮廓”时,使用内联函数 经常是便利的。但假如要考虑效率,内联是值得注意的一个问题。*/</strong></span>
6.
<span style="font-size:18px;"><strong>/*假如想优化,那么使用关键字inline。使用这个方法,前面 RECTANGL.CPP例子修改 如下:*/ /*NOINSITU.cpp*/ #include <iostream> using namespace std; class rectangle { int Width, Height; public: rectangle(int W = 0, int H = 0); int width() const; void width(int W); int height() const; void height(int H); }; inline rectangle::rectangle(int W, int H):Width(W), Height(H) {} inline int rectangle::width() const//read { return Width; } inline void rectangle::width(int W)//set { Width = W; } inline int rectangle::height() const//read { return Height; } inline void rectangle::height(int H)//set { Height = H; } int main() { rectangle R(19, 47); R.height(2*R.width()); R.width(2*R.height()); return 0; }</strong></span>
7.
<span style="font-size:18px;"><strong>/*使用标准的C库函数中的时间函数来生成简单的 Time类*/ /*CPPTIME.h*/ #ifndef TEXT_H_ #define TEXT_H_ #include <time.h> #include <string.h> class Time { time_t T; tm local; char Ascii[26]; unsigned char lflag, aflag; void updateLocal() { if(!lflag) { local = *localtime(&T); lflag++; } } void updateAscii() { if(!aflag) { updateLocal(); strcpy(Ascii, asctime(&local)); aflag++; } } public: Time() { mark(); } void mark() { lflag = aflag = 0; time(&T); } const char* ascii() { updateAscii(); return Ascii; } int delta(Time* dt) const { return difftime(T, dt->T); } int DaylightSavings() { updateLocal(); return local.tm_isdst; } int DayOfYear() { updateLocal(); return local.tm_yday; } int DayOfWeek() { updateLocal(); return local.tm_wday; } int Since1900() { updateLocal(); return local.tm_year; } int Month() { updateLocal(); return local.tm_mon; } int DayOfMonth() { updateLocal(); return local.tm_mday; } int Hour() { updateLocal(); return local.tm_hour; } int Minute() { updateLocal(); return local.tm_min; } int Second() { updateLocal(); return local.tm_sec; } }; #endif /*两个私有函数updateLocal( )和 updateAscii( )检查标 记,并有条件地执行更新。*/</strong></span>
<span style="font-size:18px;"><strong>#include <iostream.h> #include "CPPTIME.h" /*在这个例子里,一个 Time对象被创建,然后执行一些时延动作,接着创建第 2个 Time对象来标记结束时间。这些用于显示开始时间、结束时间和消耗的时间。*/ int main() { Time start; for(int i = 1; i < 1000; ++i) { cout << i << ' '; if(i%10 == 0) { cout << endl; } } Time end; cout << endl; cout << "start = " << start.ascii(); cout << "end = " << end.ascii(); cout << "delta = " << end.delta(&start); return 0; }</strong></span>
8.
<span style="font-size:18px;"><strong>/*这是预处理器仍然有用的另一个例子,因为 _ FILE_和_LINE_指示仅和预处理器一 起起作用并用在assert( )宏里。假如assert( )宏在一个错误函数里被调用,它仅打 印出错函数的行号和文件名字而不是调用错误函数。这儿显示了使用宏联接(许多是 assert()方法)函数的方法,紧接着调用assert()(程序调试成功后这由一个#define NDEBUG消除)。*/ /*ALLEGE.h*/ #ifndef ALLEGE_H_ #define ALLEGE_H_ #include <stdio.h> #include <stdlib.h> #include <assert.h> inline void allege_error(int val, const char* msg) //函数allege_error()有两个参数:一个是整型表达式的值,另一个是这个值为 //false时需打印的消息 { if(!val) { fprintf(stderr, "error: %s\n", msg); //函数fprintf()代替iostreams是因为在只有少量错误的情况下,它工作得 //更好。假如这不是为调试建立的,exit(1)被调用以终止程序。 #ifdef NDEBUG exit(1); #endif } } //allege()宏使用三重if-then-else强迫计算表达式expr求值。在宏里调用了 //allege_error(),接着是assert(),所以我们能在调试时获得 assert()的好处 //——因为有些环境紧密地把调试器和assert()结合在一起。 #define allege(expr, msg){ allege_error((expr)?1:0, msg); assert(expr);} #define allegemem(expr) allege(expr, "out of memory") //allegefile( )宏是allege( )宏用于检查文件的专用版本 #define allegefile(expr) allege(expr, "could not open file") //allegemen()宏是allege( )宏用于检查内存的专用版本 #endif </strong></span>
<span style="font-size:18px;"><strong>/*ERRTEST.cpp*/ //#define NDEBUG//Turn off asserts #include "ALLEGE.h" #include <fstream.h> int main() { int i = 1; allege(i, "value must be nonzero"); void* m = malloc(100); allegemem(m); ifstream nofile("nofile.xxx"); allegefile(nofile); return 0; }</strong></span>
三.习题+解答
1. 将第7章练习2例子增加一个内联构造函数和一个称为 Print( )的内联成员函数,这个函数用于打印所有数组的值。
<span style="font-size:18px;"><strong>#include <iostream> using namespace std; class A { const int i,j; enum { size = 100 }; unsigned char arr[size]; int number; public: A(); A(int Number):i(0),j(0) { number = Number; } void Print() { for(int k = 0; k < size; ++k) { cout << arr[k] << ","; } cout << endl; } }; A::A():i(0),j(0) { for(int k = 0,m = '0'; k < size; ++k) { arr[k] = m + k; } }; int main() { A a; a.Print(); return 0; }</strong></span>
2. 对于第 3章的NESTFRND.CPP例子,用内联函数代替所有的成员函数使它们成为非in situ内联函数。同时再把initialize()函数改成构造函数。
<span style="font-size:18px;"><strong>#include <iostream> using namespace std; /*nestfrnd.cpp*/ #include <string.h> #define SZ 20 /*struct holder包含一个整型数组和一个 p ointer,我们可以通过 pointer来存取这些整数。因为 pointer与holder紧密相连,所以有必要将它作为 struct中的一个成员。一旦 pointer被定义,它就 可以通过下面的声明来获得存取 holder的私有成员的权限: friend holder : :pointer ; 注意,这里struct关键字并不是必须的,因为编译器已经知道 pointer是什么了*/ class holder { private: int a[SZ]; public: holder(); class pointer { private: holder* h; int* p; public: pointer(holder* H); void next(); void previous(); void top(); void end(); int read(); void set(int i); }; friend pointer; }; inline holder::holder() { memset(a,0,SZ*sizeof(int)); } inline holder::pointer::pointer(holder* H) { h = H; p = h->a; } inline void holder::pointer::next() { if(p < &(h->a[SZ-1])) { p++; } } inline void holder::pointer::previous() { if(p > &(h->a[0])) { p--; } } inline void holder::pointer::top() { p = &(h->a[0]); } inline void holder::pointer::end() { p = &(h->a[SZ-1]); } inline int holder::pointer::read() { return *p; } inline void holder::pointer::set(int i) { *p = i; } int main() { holder h; holder::pointer hp1(&h); holder::pointer hp2(&h); int i; for(i = 0;i < SZ;++i) { hp1.set(i); hp1.next(); } hp1.top(); hp2.end(); for(i = 0;i < SZ;++i) { cout<<"hp1 = "<<hp1.read()<<"," <<"hp2 = "<<hp2.read()<<endl; hp1.next(); hp2.previous(); } return 0; }</strong></span>
3. 使用第6章NL.CPP,在它自己的头文件里,将nl转变为内联函数。
STL源码
<span style="font-size:18px;"><strong>#if _MSC_VER > 1000 #pragma once #endif #ifdef __cplusplus #ifndef _INC_OSTREAM #define _INC_OSTREAM #if !defined(_WIN32) && !defined(_MAC) #error ERROR: Only Mac or Win32 targets supported! #endif #ifdef _MSC_VER // Currently, all MS C compilers for Win32 platforms default to 8 byte // alignment. #pragma pack(push,8) #include <useoldio.h> #endif // _MSC_VER /* Define _CRTIMP */ #ifndef _CRTIMP #ifdef _DLL #define _CRTIMP __declspec(dllimport) #else /* ndef _DLL */ #define _CRTIMP #endif /* _DLL */ #endif /* _CRTIMP */ #include <ios.h> #ifdef _MSC_VER // C4514: "unreferenced inline function has been removed" #pragma warning(disable:4514) // disable C4514 warning // #pragma warning(default:4514) // use this to reenable, if desired #endif // _MSC_VER typedef long streamoff, streampos; class _CRTIMP ostream : virtual public ios { public: ostream(streambuf*); virtual ~ostream(); ostream& flush(); int opfx(); void osfx(); inline ostream& operator<<(ostream& (__cdecl * _f)(ostream&)); inline ostream& operator<<(ios& (__cdecl * _f)(ios&)); ostream& operator<<(const char *); inline ostream& operator<<(const unsigned char *); inline ostream& operator<<(const signed char *); inline ostream& operator<<(char); ostream& operator<<(unsigned char); inline ostream& operator<<(signed char); ostream& operator<<(short); ostream& operator<<(unsigned short); ostream& operator<<(int); ostream& operator<<(unsigned int); ostream& operator<<(long); ostream& operator<<(unsigned long); inline ostream& operator<<(float); ostream& operator<<(double); ostream& operator<<(long double); ostream& operator<<(const void *); ostream& operator<<(streambuf*); inline ostream& put(char); ostream& put(unsigned char); inline ostream& put(signed char); ostream& write(const char *,int); inline ostream& write(const unsigned char *,int); inline ostream& write(const signed char *,int); ostream& seekp(streampos); ostream& seekp(streamoff,ios::seek_dir); streampos tellp(); protected: ostream(); ostream(const ostream&); // treat as private ostream& operator=(streambuf*); // treat as private ostream& operator=(const ostream& _os) {return operator=(_os.rdbuf()); } int do_opfx(int); // not used void do_osfx(); // not used private: ostream(ios&); ostream& writepad(const char *, const char *); int x_floatused; }; inline ostream& ostream::operator<<(ostream& (__cdecl * _f)(ostream&)) { (*_f)(*this); return *this; } inline ostream& ostream::operator<<(ios& (__cdecl * _f)(ios& )) { (*_f)(*this); return *this; } inline ostream& ostream::operator<<(char _c) { return operator<<((unsigned char) _c); } inline ostream& ostream::operator<<(signed char _c) { return operator<<((unsigned char) _c); } inline ostream& ostream::operator<<(const unsigned char * _s) { return operator<<((const char *) _s); } inline ostream& ostream::operator<<(const signed char * _s) { return operator<<((const char *) _s); } inline ostream& ostream::operator<<(float _f) { x_floatused = 1; return operator<<((double) _f); } inline ostream& ostream::put(char _c) { return put((unsigned char) _c); } inline ostream& ostream::put(signed char _c) { return put((unsigned char) _c); } inline ostream& ostream::write(const unsigned char * _s, int _n) { return write((char *) _s, _n); } inline ostream& ostream::write(const signed char * _s, int _n) { return write((char *) _s, _n); } class _CRTIMP ostream_withassign : public ostream { public: ostream_withassign(); ostream_withassign(streambuf* _is); ~ostream_withassign(); ostream& operator=(const ostream& _os) { return ostream::operator=(_os.rdbuf()); } ostream& operator=(streambuf* _sb) { return ostream::operator=(_sb); } }; extern ostream_withassign _CRTIMP cout; extern ostream_withassign _CRTIMP cerr; extern ostream_withassign _CRTIMP clog; inline _CRTIMP ostream& __cdecl flush(ostream& _outs) { return _outs.flush(); } inline _CRTIMP ostream& __cdecl endl(ostream& _outs) { return _outs << '\n' << flush; } inline _CRTIMP ostream& __cdecl nl(ostream& _outs) { return _outs << '\n'; } inline _CRTIMP ostream& __cdecl ends(ostream& _outs) { return _outs << char('\0'); } _CRTIMP ios& __cdecl dec(ios&); _CRTIMP ios& __cdecl hex(ios&); _CRTIMP ios& __cdecl oct(ios&); #ifdef _MSC_VER // Restore default packing #pragma pack(pop) #endif // _MSC_VER #endif // _INC_OSTREAM #endif /* __cplusplus */ </strong></span>
4. 创建一个类A,具有能自我宣布的缺省构造函数。再写一个新类 B, 将A的一个对象作为B的成员,并为类B写一个内联构造函数。创建一个 B对象的数组并看看发生了什么事。
#include <iostream> using namespace std; class A { int i; public: A(int I = 0); ~A() { cout << "Delete A!" << endl; } }; A::A(int I):i(I) { cout << "Create A!" <<endl; } class B { A a; enum{ size = 10 }; int arr[size]; public: friend A; B() { a = 0; for(int k = 0,m = 0;k < size; ++k,++m) { arr[k] = m; } cout << "Create B!" << endl; } ~B() { cout << "Delete B!" << endl; } }; int main() { B b; return 0; }
输出结果如下:
5. 从练习4里创建大量的对象并使用 Time类来计算非内联构造函数和内联构造函数之间的时间差别(假如我们有剖析器,也试着使用它。)
对于大量对象无法很快得出,故加入了非内联函数和内联函数:
/*使用标准的C库函数中的时间函数来生成简单的 Time类*/ /*CPPTIME.h*/ #ifndef TEXT_H_ #define TEXT_H_ #include <time.h> #include <string.h> #include <iostream> using namespace std; class A { int i; char c; float f; double d; int j; char n; float m; double g; public: A() { i = j = 0; c = n = '0'; f = m = 1.0; d = g = 0.0; cout << "Create A()!" << endl; } A(int I, char C, float F, double D, int J, char N, float M, double G); }; A::A(int I, char C, float F, double D, int J, char N, float M, double G) :i(I),c(C),f(F),d(D),j(J),n(N),m(M),g(G) { cout << "Create A(int I, char C, float F, double D, int J, char N, float M, double G)!" << endl; } class B { A a; int flag; char name; double number; enum{ size = 10 }; int arr[size]; public: friend A; B() { flag = 0; name = '0'; number = 0.0; for(int k = 0,m = 0;k < size; ++k,++m) { arr[k] = m; } cout << "Create B()!" << endl; } B(int Flag, char Name, double Number); void g(); void f() { cout << "f()" << " "; } }; B::B(int Flag, char Name, double Number):flag(Flag),name(Name),number(Number) { cout << "Create B(int Flag, char Name, double Number)!" << endl; } void B::g() { cout << "g()" << " "; } class Time { time_t T; tm local; char Ascii[26]; unsigned char lflag, aflag; void updateLocal() { if(!lflag) { local = *localtime(&T); lflag++; } } void updateAscii() { if(!aflag) { updateLocal(); strcpy(Ascii, asctime(&local)); aflag++; } } public: Time() { mark(); } void mark() { lflag = aflag = 0; time(&T); } const char* ascii() { updateAscii(); return Ascii; } int delta(Time* dt) const { return difftime(T, dt->T); } int DaylightSavings() { updateLocal(); return local.tm_isdst; } int DayOfYear() { updateLocal(); return local.tm_yday; } int DayOfWeek() { updateLocal(); return local.tm_wday; } int Since1900() { updateLocal(); return local.tm_year; } int Month() { updateLocal(); return local.tm_mon; } int DayOfMonth() { updateLocal(); return local.tm_mday; } int Hour() { updateLocal(); return local.tm_hour; } int Minute() { updateLocal(); return local.tm_min; } int Second() { updateLocal(); return local.tm_sec; } }; #endif /*两个私有函数updateLocal( )和 updateAscii( )检查标 记,并有条件地执行更新。*/
#include "CPPTIME.h" /*在这个例子里,一个 Time对象被创建,然后执行一些时延动作,接着创建第 2个 Time对象来标记结束时间。这些用于显示开始时间、结束时间和消耗的时间。*/ int main() { Time start1; A a; B b; for(int i = 0;i < 1000; ++i) { b.f(); } Time end1; cout << endl; cout << "start1 = " << start1.ascii(); cout << "end1 = " << end1.ascii(); cout << "delta1 = " << end1.delta(&start1) << endl; Time start2; A d(1,'1',1.0,2.0,2,'2',3.0,4.0); B c(1,'0',1.0); for(i = 0; i < 1000; ++i) { c.g(); } Time end2; cout << endl; cout << "start2 = " << start2.ascii(); cout << "end2 = " << end2.ascii(); cout << "delta2 = " << end2.delta(&start2) << endl; return 0; }
测试结果:
前者为内联函数+内联构造函数;后者为非内联函数+非内联构造函数。
可知当函数内部较为复杂时,内联函数的效率低,应尽量避免使用。
以上代码仅供参考,如有错误请大家指出,谢谢大家~
版权声明:本文为博主原创文章,未经博主允许不得转载。