内联inline函数
在C中保持效率的一种方法是使用宏,他的行为类似于函数调用但却没有调用的开销(like a function call without the normal function call overhead.)。
宏是由由预处理器preprocessor而非编译器compiler处理的,其直接替换宏代码,没有参数入栈、函数调用及返回等开销。
但是在C++中使用宏有两个问题:
宏类似于函数调用但并非总是如此,其有副作用;
预处理器不能访问类的成员变量(preprocessor has no permission to access class member data),因此不能作为成员函数。
为了保持预处理器宏的效率同时增添真正的函数的安全及类范围的特性,C++中采用了内联函数。
预处理器的陷阱
如果你认为编译器和预处理器的行为相似,那么你则进入了陷阱,如:
#define FLOOR(x,b) x>=b?0:1
当调用形式如下时,
if(FLOOR(a&0x0f,0x07)) // ...
宏将扩展为
if(a&0x0f>=0x07?0:1)
由于&的优先级最低,因此其行为和预想的不一样了
因此在使用宏时,要将参数加上(),以防止改变了表达式的优先顺序
#define FLOOR(x,b) ((x)>=(b)?0:1)
但即使如此,宏仍然有其副作用,如
#define BAND(x) (((x)>5 && (x)<10) ? (x) : 0)
//: C09:MacroSideEffects.cpp
#include "../require.h"
#include <fstream>
using namespace std;
#define BAND(x) (((x)>5 && (x)<10) ? (x) : 0)
int main() {
ofstream out("macro.out");
assure(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;
}
} ///:~
a = 4
BAND(++a)=0
a = 5
a = 5
BAND(++a)=8
a = 8
a = 6
BAND(++a)=9
a = 9
a = 7
BAND(++a)=10
a = 10
a = 8
BAND(++a)=0
a = 10
a = 9
BAND(++a)=0
a = 11
a = 10
BAND(++a)=0
a = 12
根据a的大小,++a的执行次数不一样,因此系统的副作用始终在变,即函数的功能特性不稳定,这是程序员所不希望的,其本质原因在于宏是简单的文本替换。
宏和访问权限
谨慎的使用宏可以避免上述问题,但是仍然有一个不可逾越的障碍是,宏没有对于成员访问范围的概念。
class X {
int i;
public:
。。。
}
#define VAL(X::i) // Error
宏不能访问类的私有成员,另外不能确定你访问的是哪个对象。因此很多程序员为了性能将某些成员变量属性更改为public的,但这样就失去了private的安全性能。
内联函数
为了解决宏对于类的私有成员变量的访问权限问题,将宏的概念纳入编译器的控制范围即可,这就是内联函数,他具备函数的一切特性,但是没有函数调用的开销,因为内联函数象预处理宏一样在调用处被扩展。
类内部定义的函数自动扩展为内联函数,但是你也可以通过inline关键字声明某函数为内联函数。但是声明时必须和函数定义放在一起,否则编译器将其视为普通函数。对于内联函数,编译器会进行参数和返回值的类型检查。
inline int plusOne(int x);这样是不起任何作用的,必须如下方式声明
inline int plusOne(int x)
{ return ++x; }
必须将内联函数的定义放在头文件中,编译器将函数类型和函数体放在其符号表中,当遇到相应的调用时,就将其替换;头文件中的内联函数有个特殊状态,每个文件中都有内联函数的实现,但并不会出现重复定义的错误,因为内联函数是在编译阶段替换的,并没有链接过程,不涉及导函数分配的内存地址等问题。
内联函数和编译器
为了理解内联函数何时有效,需要了解编译器如何处理内联。
编译器将函数类型包括函数名、参数个数及其类型还有返回值类型保存在符号表中,当函数体的语法无误时将其实现也保存在符号表中,代码的形式取决于编译器。当遇到调用内联函数时,编译器会分析参数和返回值类型并可能做适当的强制转换,都没有问题时就会进行代码替换,并可能还有进一步的优化。
什么时候不能使用内联
内联函数有两种限制,当不能使用内联时,编译器将之视为普通函数,为其分配内存空间,通常会出现多重定义的错误,但是链接器被告知忽略这种问题。
当函数的功能过于复杂时,编译器不会实施内联,这取决于编译器,但通常情况下,循环或者过多的代码不会被内联,因为此时代码执行的时间可能比函数调用的时间多很多,内联失去了意义。
另外一种情况是需要显式或隐式的得到某函数的地址时,编译器要产生地址则必须为其分配内存空间;而进行内联替换时只是将其保存在符号表中,并不为其分配空间。
总之,inline关键词只是对编译器的一种建议,并非强制,是否内联取决于编译器的分析。
内联中的前向引用
当在内联函数中调用了类中还未声明的函数怎么办呢?
这种情况,编译器仍然可以将其内联,因为语法规则表明只有到类声明的“}”处才进行内联函数的替换。
//: C09:EvaluationOrder.cpp
// Inline evaluation order
class Forward {
int i;
public:
Forward() : i(0) {}
// Call to undeclared function:
int f() const { return g() + 1; }
int g() const { return i; }
};
int main() {
Forward frwd;
frwd.f();
} ///:~
构造和析构函数中的隐藏活动
在构造和析构函数中你可能误认为内联比实际的效率高,因为构造和析构过程中可能含有隐含活动,如当类中含有子对象时必须调用子对象的构造和析构函数。这种子对象可能是成员函数也可能是继承而来的。成员对象的例子如下:
// Hidden activities in inlines
#include <iostream>
using namespace std;
class Member {
int i, j, k;
public:
Member(int x = 0) : i(x), j(x), k(x) {}
~Member() { cout << "~Member" << endl; }
};
class WithMembers {
Member q, r, s; // Have constructors
int i;
public:
WithMembers(int ii) : i(ii) {} // Trivial?
~WithMembers() {
cout << "~WithMembers" << endl;
}
};
int main() {
WithMembers wm(1);
} ///:~
减少clutter
在实际的工程项目中,若在类中定义函数,则会弄乱类的接口并使得类很难使用,因此有些人认为任何成员函数的定义应该放在类的外部实现,以保持类的整洁。如果需要优化的话,则用inline关键字。
//: C09:Noinsitu.cpp
// Removing in situ functions
class Rectangle {
int width, height;
public:
Rectangle(int w = 0, int h = 0);
int getWidth() const;
void setWidth(int w);
int getHeight() const;
void setHeight(int h);
};
inline Rectangle::Rectangle(int w, int h)
: width(w), height(h) {}
inline int Rectangle::getWidth() const {
return width;
}
inline void Rectangle::setWidth(int w) {
width = w;
}
inline int Rectangle::getHeight() const {
return height;
}
inline void Rectangle::setHeight(int h) {
height = h;
}
int main() {
Rectangle r(19, 47);
// Transpose width & height:
int iHeight = r.getHeight();
r.setHeight(r.getWidth());
r.setWidth(iHeight);
} ///:~
Inline成员函数应该放在头文件中,而非inline函数应该放在定义文件中。
使用inline关键字的形式声明还有一个好处是使得各个成员函数的定义具备统一的风格。
更多的预处理器特性
当需要用到预处理器中的三种特性时,采用宏而非内联函数。
分别为字符串化(stringizing),字符串连接(string concatenation),及符合粘贴(token pasting)。字符串化即强制将x转化为字符数组,通过#实现。当两个字符串之间没有任何符号时,字符串连接将使其合并为一个字符串。这两个特性在编写调试代码时特别有用,如:
#define DEBUG(x) cout << #x " = " << x << endl
可以用此技术来跟踪代码的执行,在执行代码的同时打印相应信息。
#define TRACE(s) cerr << #s << endl; s
这种方法在只有单一语句的for循环中可能会出现问题,如
for(int i = 0; i < 100; i++)
TRACE(f(i));
此时可以将“;”更改为“,”成为逗号表达式。
#define TRACE(s) cerr << #s << endl, s
或者更改为do while结构,如:
#define TRACE(s) do{ cerr << #s << endl; s ;} while(0)
符号粘贴
符号粘贴即将两个符号粘贴在一起生成一个新的符号,通过“##”实现。
#define FIELD(a) char* a##_string; int a##_size
class Record {
FIELD(one);
FIELD(two);
FIELD(three);
// ...
};
上述方式生成一个标识符作为字符串,另一个作为串的长度,不仅便于阅读,同时消除了编码出错的可能性,并且便于维护。
在Linux的内核代码中存在大量这样数据的定义,尤其是些init初始化阶段的数据,或者是某些保存在特殊段的数据结构
在C中保持效率的一种方法是使用宏,他的行为类似于函数调用但却没有调用的开销(like a function call without the normal function call overhead.)。
宏是由由预处理器preprocessor而非编译器compiler处理的,其直接替换宏代码,没有参数入栈、函数调用及返回等开销。
但是在C++中使用宏有两个问题:
宏类似于函数调用但并非总是如此,其有副作用;
预处理器不能访问类的成员变量(preprocessor has no permission to access class member data),因此不能作为成员函数。
为了保持预处理器宏的效率同时增添真正的函数的安全及类范围的特性,C++中采用了内联函数。
预处理器的陷阱
如果你认为编译器和预处理器的行为相似,那么你则进入了陷阱,如:
#define FLOOR(x,b) x>=b?0:1
当调用形式如下时,
if(FLOOR(a&0x0f,0x07)) // ...
宏将扩展为
if(a&0x0f>=0x07?0:1)
由于&的优先级最低,因此其行为和预想的不一样了
因此在使用宏时,要将参数加上(),以防止改变了表达式的优先顺序
#define FLOOR(x,b) ((x)>=(b)?0:1)
但即使如此,宏仍然有其副作用,如
#define BAND(x) (((x)>5 && (x)<10) ? (x) : 0)
//: C09:MacroSideEffects.cpp
#include "../require.h"
#include <fstream>
using namespace std;
#define BAND(x) (((x)>5 && (x)<10) ? (x) : 0)
int main() {
ofstream out("macro.out");
assure(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;
}
} ///:~
a = 4
BAND(++a)=0
a = 5
a = 5
BAND(++a)=8
a = 8
a = 6
BAND(++a)=9
a = 9
a = 7
BAND(++a)=10
a = 10
a = 8
BAND(++a)=0
a = 10
a = 9
BAND(++a)=0
a = 11
a = 10
BAND(++a)=0
a = 12
根据a的大小,++a的执行次数不一样,因此系统的副作用始终在变,即函数的功能特性不稳定,这是程序员所不希望的,其本质原因在于宏是简单的文本替换。
宏和访问权限
谨慎的使用宏可以避免上述问题,但是仍然有一个不可逾越的障碍是,宏没有对于成员访问范围的概念。
class X {
int i;
public:
。。。
}
#define VAL(X::i) // Error
宏不能访问类的私有成员,另外不能确定你访问的是哪个对象。因此很多程序员为了性能将某些成员变量属性更改为public的,但这样就失去了private的安全性能。
内联函数
为了解决宏对于类的私有成员变量的访问权限问题,将宏的概念纳入编译器的控制范围即可,这就是内联函数,他具备函数的一切特性,但是没有函数调用的开销,因为内联函数象预处理宏一样在调用处被扩展。
类内部定义的函数自动扩展为内联函数,但是你也可以通过inline关键字声明某函数为内联函数。但是声明时必须和函数定义放在一起,否则编译器将其视为普通函数。对于内联函数,编译器会进行参数和返回值的类型检查。
inline int plusOne(int x);这样是不起任何作用的,必须如下方式声明
inline int plusOne(int x)
{ return ++x; }
必须将内联函数的定义放在头文件中,编译器将函数类型和函数体放在其符号表中,当遇到相应的调用时,就将其替换;头文件中的内联函数有个特殊状态,每个文件中都有内联函数的实现,但并不会出现重复定义的错误,因为内联函数是在编译阶段替换的,并没有链接过程,不涉及导函数分配的内存地址等问题。
内联函数和编译器
为了理解内联函数何时有效,需要了解编译器如何处理内联。
编译器将函数类型包括函数名、参数个数及其类型还有返回值类型保存在符号表中,当函数体的语法无误时将其实现也保存在符号表中,代码的形式取决于编译器。当遇到调用内联函数时,编译器会分析参数和返回值类型并可能做适当的强制转换,都没有问题时就会进行代码替换,并可能还有进一步的优化。
什么时候不能使用内联
内联函数有两种限制,当不能使用内联时,编译器将之视为普通函数,为其分配内存空间,通常会出现多重定义的错误,但是链接器被告知忽略这种问题。
当函数的功能过于复杂时,编译器不会实施内联,这取决于编译器,但通常情况下,循环或者过多的代码不会被内联,因为此时代码执行的时间可能比函数调用的时间多很多,内联失去了意义。
另外一种情况是需要显式或隐式的得到某函数的地址时,编译器要产生地址则必须为其分配内存空间;而进行内联替换时只是将其保存在符号表中,并不为其分配空间。
总之,inline关键词只是对编译器的一种建议,并非强制,是否内联取决于编译器的分析。
内联中的前向引用
当在内联函数中调用了类中还未声明的函数怎么办呢?
这种情况,编译器仍然可以将其内联,因为语法规则表明只有到类声明的“}”处才进行内联函数的替换。
//: C09:EvaluationOrder.cpp
// Inline evaluation order
class Forward {
int i;
public:
Forward() : i(0) {}
// Call to undeclared function:
int f() const { return g() + 1; }
int g() const { return i; }
};
int main() {
Forward frwd;
frwd.f();
} ///:~
构造和析构函数中的隐藏活动
在构造和析构函数中你可能误认为内联比实际的效率高,因为构造和析构过程中可能含有隐含活动,如当类中含有子对象时必须调用子对象的构造和析构函数。这种子对象可能是成员函数也可能是继承而来的。成员对象的例子如下:
// Hidden activities in inlines
#include <iostream>
using namespace std;
class Member {
int i, j, k;
public:
Member(int x = 0) : i(x), j(x), k(x) {}
~Member() { cout << "~Member" << endl; }
};
class WithMembers {
Member q, r, s; // Have constructors
int i;
public:
WithMembers(int ii) : i(ii) {} // Trivial?
~WithMembers() {
cout << "~WithMembers" << endl;
}
};
int main() {
WithMembers wm(1);
} ///:~
减少clutter
在实际的工程项目中,若在类中定义函数,则会弄乱类的接口并使得类很难使用,因此有些人认为任何成员函数的定义应该放在类的外部实现,以保持类的整洁。如果需要优化的话,则用inline关键字。
//: C09:Noinsitu.cpp
// Removing in situ functions
class Rectangle {
int width, height;
public:
Rectangle(int w = 0, int h = 0);
int getWidth() const;
void setWidth(int w);
int getHeight() const;
void setHeight(int h);
};
inline Rectangle::Rectangle(int w, int h)
: width(w), height(h) {}
inline int Rectangle::getWidth() const {
return width;
}
inline void Rectangle::setWidth(int w) {
width = w;
}
inline int Rectangle::getHeight() const {
return height;
}
inline void Rectangle::setHeight(int h) {
height = h;
}
int main() {
Rectangle r(19, 47);
// Transpose width & height:
int iHeight = r.getHeight();
r.setHeight(r.getWidth());
r.setWidth(iHeight);
} ///:~
Inline成员函数应该放在头文件中,而非inline函数应该放在定义文件中。
使用inline关键字的形式声明还有一个好处是使得各个成员函数的定义具备统一的风格。
更多的预处理器特性
当需要用到预处理器中的三种特性时,采用宏而非内联函数。
分别为字符串化(stringizing),字符串连接(string concatenation),及符合粘贴(token pasting)。字符串化即强制将x转化为字符数组,通过#实现。当两个字符串之间没有任何符号时,字符串连接将使其合并为一个字符串。这两个特性在编写调试代码时特别有用,如:
#define DEBUG(x) cout << #x " = " << x << endl
可以用此技术来跟踪代码的执行,在执行代码的同时打印相应信息。
#define TRACE(s) cerr << #s << endl; s
这种方法在只有单一语句的for循环中可能会出现问题,如
for(int i = 0; i < 100; i++)
TRACE(f(i));
此时可以将“;”更改为“,”成为逗号表达式。
#define TRACE(s) cerr << #s << endl, s
或者更改为do while结构,如:
#define TRACE(s) do{ cerr << #s << endl; s ;} while(0)
符号粘贴
符号粘贴即将两个符号粘贴在一起生成一个新的符号,通过“##”实现。
#define FIELD(a) char* a##_string; int a##_size
class Record {
FIELD(one);
FIELD(two);
FIELD(three);
// ...
};
上述方式生成一个标识符作为字符串,另一个作为串的长度,不仅便于阅读,同时消除了编码出错的可能性,并且便于维护。
在Linux的内核代码中存在大量这样数据的定义,尤其是些init初始化阶段的数据,或者是某些保存在特殊段的数据结构
发表评论
-
C++学习重点分析
2009-11-17 18:22 788一、#include “filename.h”和#includ ... -
C++/C编程指南-- 第5章 常量
2009-10-29 08:53 890第5章 常量 常量是一种标识符,它的值在运行 ... -
C++ MAP 基本用法
2009-10-20 16:13 4112/* map内部数据的组织,map内部自建一颗红黑树( ... -
C++ LIst 基本用法
2009-10-20 10:21 4691#include <iostream> #incl ... -
stringstream的用法
2009-10-19 08:49 3972stringstream通常是用来做数据转换的。相比c库的转换 ... -
CString,int,string,char*之间的转换
2009-09-17 09:02 1720《C++标准函数库》中说的 有三个函数可以将字符串的内容转 ... -
string类的部分操作
2009-09-10 13:48 788#include <string> #includ ... -
c++ string 类基本用法样例
2009-09-07 14:20 2816#include <string> // ... -
C++ 类中的静态变量和静态成员函数
2009-08-13 10:49 2428静态数据成员: 下面看 ... -
C++友元类------自己的不一定正确
2009-08-13 10:03 1772/*预引用.告诉编译器类B将会在后面定义.使用了预引用后,就可 ... -
友元函数初步理解
2009-08-12 16:18 823友元函数: 友元函数-----不是类的成员函数,但却能访问类的 ... -
关于this指针
2009-08-12 11:02 654this指针 1.只能用在类的成员函数中,它指向掉用这个函数 ...
相关推荐
内联函数(inline)在C++编程语言中是一种优化手段,用于提高程序的运行效率。它的主要作用是在编译期间将函数体插入到每个调用该函数的地方,从而避免了函数调用时的开销,如函数调用的压栈、跳转以及返回等过程。...
C++内联汇编是一种将汇编语言代码嵌入到C++程序中的技术,它允许开发者在高级语言中直接插入低级操作,以优化特定性能关键的代码段或解决特定平台的问题。本工程通过一系列示例,展示了如何在C++程序中使用内联汇编...
"C++中的内联函数inline用法实例" C++中的内联函数是一种特殊的函数类型,它可以在编译期间将函数体替换到调用处,以减少函数调用时的开销。下面我们将详细介绍C++中的内联函数inline用法实例,以及相关知识点。 ...
1. **`inline`**: 专门用于内联C++函数。 2. **`__inline`**: 可用于内联C和C++函数。 这意味着在C/C++混合编程环境下,使用`__inline`可以提供更多的灵活性。 #### 三、内联功能的启用 在Visual C++编译器中,...
内联函数是C++编程语言中一种优化技术,它的主要目的是减少函数调用时的开销,提升程序的运行效率。内联函数的工作原理与预处理宏有些相似,但克服了宏的一些缺点。 预处理宏在编译之前进行文本替换,它们能够提高...
2. **使用inline函数的优势**: - 提升性能:由于消除了函数调用的开销,内联函数通常比常规函数更快。 - 避免栈帧分配和恢复:函数调用通常会涉及栈帧的创建和销毁,内联避免了这些操作。 - 更少的间接跳转:...
内联函数(inline function)是C++中的一种特殊函数,它允许编译器在编译时将函数调用替换为函数体本身的代码,从而避免了通常的函数调用机制(如参数压栈、返回地址保存等)所带来的额外开销。这种替换过程称为内联...
C++中的inline函数是一种特殊的函数,其主要目的是提高程序的执行效率。内联函数的定义是在函数声明或定义时在其返回类型前加上`inline`关键字。这样做的目的是告诉编译器,希望它在可能的情况下在调用点直接展开...
内联函数(inline function)是 C++ 语言中的一种特殊函数,具有优缺点,今天我们将深入探究内联函数的定义格式、编程风格、优缺点、使用注意事项,以及与宏的区别。 首先,内联函数的定义格式是将关键字 `inline` ...
内联函数是C++编程语言中的一种特性,它主要用于提高程序执行效率,特别是在频繁调用的小型函数中。内联函数的设计目标是替代C语言中的宏定义,克服宏定义的一些弊端,同时保留其效率优势。 在C语言中,宏定义(如`...
### inline函数:优化与陷阱 在C++编程中,`inline`关键字的使用是一个重要的优化技术,它旨在减少函数调用的开销,提高代码执行效率。本文将深入探讨`inline`函数的工作原理、使用场景及其潜在的陷阱,帮助开发者...
内联函数是C++中一个重要的优化手段,用于提高程序执行效率。在C++中,`inline`关键字用来修饰函数,提示编译器尽可能地在调用点将函数体展开,而不是通过传统的函数调用机制。这样可以避免函数调用时的开销,如栈...
### C++中的宏、内联函数和宏的比较 在C++编程中,宏和内联函数是提高代码效率和可读性的两种常见方法。它们各自有着不同的应用场景和特点,了解这些差异对于编写高质量的C++代码至关重要。 #### 宏(Macro) 宏...
在C++中,通过在函数声明前加上关键字`inline`来声明一个内联函数。例如: ```cpp inline int square(int x) { return x * x; } ``` #### 6. 函数重载 函数重载允许在同一作用域内声明多个同名函数,但这些函数...
在C++中,我们通过在函数定义前加上`inline`关键字来请求编译器将其内联处理。然而,编译器并不总是会遵循这个请求,特别是在函数体过大或者函数被定义在另一个编译单元时,它可能会选择不进行内联。此外,内联函数...
内联函数使用`inline`关键字声明,但是否真的进行内联替换是由编译器决定的。编译器会考虑函数的大小、复杂性和代码体积等因素。内联函数保留了函数的特性,比如类型检查、作用域规则以及异常处理,这使得内联函数...
内联函数是C++引入的概念,尽管也可以在C语言中使用。它在编译时将函数体插入到每个调用点,消除了函数调用的开销,从而提高了执行效率。内联函数的定义类似于普通函数,但前面会加上`inline`关键字。例如: ```c ...
这种在函数调用处直接嵌入函数体的函数称为内联函数(inline function),又称内嵌函数或内嵌函数。 指定内联函数的方法很简单,只需要在定义函数时增加 inline 关键字。 注意:是在函数定义时增加 inline 关键字,...