参考了网上博主们的文章,特意写下这篇笔记,方便以后复习。
摘录了很多解释得比较透彻的原文,也加了些个人的理解,如果看见大量原文句子也请不要喷我哦。本文相当于是对网上文章的一个整合
如有错误还请指出,感激不尽。
\(\huge{\mathcal{Inline}}\)
“ 使用函数能够避免将相同代码重写多次的麻烦,还能减少可执行程序的体积,但也会带来程序运行时间上的开销。
函数调用在执行时,首先要在栈中为形参和局部变量分配存储空间,然后还要将实参的值复制给形参,接下来还要将函数的返回地址(该地址指明了函数执行结束后,程序应该回到哪里继续执行)放入栈中,最后才跳转到函数内部执行。这个过程是要耗费时间的。
另外,函数执行 return 语句返回时,需要从栈中回收形参和局部变量占用的存储空间,然后从栈中取出返回地址,再跳转到该地址继续执行,这个过程也要耗费时间。
总之,使用函数调用语句和直接把函数中的代码重新抄写一遍相比,节省了人力,但是带来了程序运行时间上的额外开销。
一般情况下,这个开销可以忽略不计。但是,如果一个函数内部没有几条语句,执行时间本来就非常短,那么这个函数调用产生的额外开销和函数本身执行的时间相比,就显得不能忽略了。假如这样的函数在一个循环中被上千万次地执行,函数调用导致的时间开销可能就会使得程序运行明显变慢。
作为特别注重程序执行效率,适合编写底层系统软件的高级程序设计语言,C++ 用 inline 关键字较好地解决了函数调用开销的问题。 ”
在C++中定义函数时,在类型前加上inline关键字,增加了inline关键字的函数称为内联函数,其与普通函数的区别在于:
"当编译器处理调用内联函数的语句时,不会将该语句编译成函数调用的指令,而是直接将整个函数体的代码插人调用语句处,就像整个函数体在调用处被重写了一遍一样。"
由此可见,有了内联函数,就能像调用一个函数那样方便地重复使用一段代码,而不需要付出执行函数调用的额外开销。
但很显然的是,使用内联函数会使最终可执行程序的体积增加,所以内联函数中应该只是简单方便的几句语句。
"如果一个函数较为复杂,它执行的时间可能上万倍于函数调用的额外开销,那么将其作为内联函数处理的结果是付出让代码体积增加不少的代价,却只使速度提高了万分之一,这显然是不划算的。"
值得注意的是,如果一个函数只有一两句的循环语句,但可能要执行很多次,消耗大量的时间,那么这类函数也不能定义为内联函数实现。
另外,"调用内联函数的语句前必须已经出现内联函数的定义(即整个数体),而不能只出现内联函数的声明"
这里给出内联函数的一个例子:
inline int Max(int a,int b){
return a>b?a:b;
}
下面是内联函数的一些补充说明:
C++编译器可以将一个函数进行内联编译
被C++编译器内联编译的函数叫做内联函数
内联函数在最终生成的代码中是没有定义的
C++编译器直接将函数体插入在函数调用的地方
内联函数没有普通函数调用时的额外开销(压栈,跳转,返回)
内联函数是对编译器的一种请求,因此编译器可能拒绝这种请求
C++中内联编译的限制(原则):
不能存在任何形式的循环语句、不能存在过多的条件判断语句、函数体不能过于庞大、不能对函数进行取址操作、函数内联声明必须在调用语句之前。
\(\huge{\mathcal{Const}}\)
"C++ \(const\) 允许指定一个语义约束,编译器会强制实施这个约束,允许程序员告诉编译器某值是保持不变的。如果在编程中确实有某个值保持不变,就应该明确使用 \(const\) ,这样可以获得编译器的帮助。"
但上述这句话也有人不赞同,有网友提到
一般来说如果全局中有值不变的话,应该更倾向于使用 \(#define\) 来完成。对于浮点数来说区别不大,但是对于整数来说,使用 \(const\) 会占用内存空间,访问的时候也会比立即数来得慢。
这还是看你个人的选择,这里就不再赘述了。
看到 \(const\) ,我们的第一反应就是定义常量,但其实 \(const\) 的用法还有很多
\(\LARGE{\mathcal{Const修饰变量或指针}}\)
\(const\) 修饰指针变量时:
(1)只有一个 \(const\) ,如果 \(const\) 位于*左侧,表示指针所指数据是常量,不能通过解引用修改该数据;指针本身是变量,可以指向其他的内存单元。
(2)只有一个 \(const\) ,如果 \(const\) 位于*右侧,表示指针本身是常量,不能指向其他内存地址;指针所指的数据可以通过解引用修改。
(3)两个 \(const\) ,*左右各一个,表示指针和指针所指数据都不能修改。
const int a=0 这里将a定义为常量,其值将不会被修改
const int *p1=&a 指针p1所指内容为常量,指针p1本身是变量 等价于 int const *p1=&a
int *const p2=&a 指针p2本身是常量,指针p2所指内容可以被修改
const int *const p3=&a 指针p3及其所指数据均为常量,不能修改 等价于 int const *const p3=&a
\(\LARGE{\mathcal{Const修饰函数参数}}\)
如果该参数用于输出,那么无论是采用指针传递还是引用传递,都不能加 \(const\) 修饰。所以 \(const\) 只能用于修饰输入参数。
1.值传递
如果采用值传递,由于函数将自动产生临时变量用于复制该参数,该输入参数本来就无需保护,所以不需要加 \(const\) 修饰。
比如 void F(T x) 写成 void F(const T x) 是无意义的(其中T为定义对象类型)
2.指针传递
如果采用指针传递,那么加 \(const\) 可以防止函数体内部对该参数进行改变,起到保护作用。
例如,void testF(const char *str) 函数中若试图改变str的内容,编译器将报错。
3.引用传递
首先我们来说一下,为什么要引入引用传递这种方法。
对于非内部数据类型的参数而言,像 void F(T a) 这样声明的函数注定效率比较低。因为函数体内将产生T类型的临时对象用于复制参数a,而临时对象的构造、复制、析构过程都将消耗时间。
为了提高效率,可以将函数声明改为 void Func(T &a) 。这样一来,根据引用传递的定义,只是借用了参数的别名,不需要产生临时对象。
但是,当函数体中改变了参数a的值后,相应的传递的原始值也会相应改变。所以如果不希望改变原始参数,只需要在前面加上 \(const\) 修饰,函数最终定义为 void Func(const T &a)。
同理,是否应将 void F(int x) 改写为 void F(const int &x) ,以便提高效率?完全没有必要,因为内部数据类型的参数不存在构造、析构的过程,而复制也非常快,“值传递”和“引用传递”的效率几乎相当。
总结一下 \(const\) 作为函数输入参数的用法:
(1)对于非内部数据类型的输入参数,应该将“值传递”的方式改为 \(const\) 引用传递”,目的是提高效率。例如将 void F(T a) 改为 void F(const T &a) 。
(2)对于内部数据类型的输入参数,不要将“值传递”的方式改为“ \(const\) 引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。例如 void F(int x) 不应该改为 void F(const int &x) 。
\(\LARGE{\mathcal{Const修饰函数的返回值}}\)
1.值传递
如果函数返回值采用“值传递”方式,由于函数会把返回值复制到外部临时的存储单元中,加 \(const\) 修饰没有任何价值。
例如,不要把函数 int GetInt(void) 写成 const int GetInt(void) 。同理不要把函数 T GetA(void) 写成 const T GetA(void),其中T为用户自定义的数据类型。
2.指针传递
如果函数返回值采用“指针传递”方式,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加 \(const\) 修饰的同类型指针。
例如,定义函数为 const char GetString(void),那么 char str=GetString() 将会出现编译错误。应该写成 const char *str=GetString() 。
3.引用传递
如果函数返回值是采用“引用传递”方式,它的意义在于能提供效率,而这种方式使用场合并不多。这个时候,一定要搞清楚函数究竟是想返回一个对象的“拷贝”还是仅返回“别名”就可以了,否则程序会出错。
\(\LARGE{\mathcal{Const修饰函数的定义体}}\)
定义 \(const\) 函数,只需要将 \(const\) 关键字放在函数声明的尾部。任何不会修改类的数据成员的函数都应该声明为 \(const\) 类型。如果在编写 \(const\) 成员函数时,不慎修改了数据成员,或者调用了其它非 \(const\) 成员函数,编译器将报错,这无疑会提高程序的健壮性。
例如,以下程序中,类 \(stack\) 的成员函数 \(GetCount\) 仅用于计数,从逻辑上讲 \(GetCount\) 应当为 \(const\) 函数。编译器将指出 \(GetCount\) 函数中的错误。
class Stack{
public:
void Push(int elem);
int Pop(void);
int GetCount(void)const; // const 成员函数
private:
int m_num;
int m_data[100];
};
int Stack::GetCount(void)const{
++ m_num; // 编译错误,企图修改数据成员m_num
Pop(); // 编译错误,企图调用非const 函数
return m_num;
}
以下是几点使用 \(const\) 的几点规则:
1) \(const\) 对象只能访问 \(const\) 成员函数,而非 \(const\) 对象可以访问任意的成员函数,包括 \(const\) 成员函数。
2) \(const\) 对象的成员是不可修改的,然而 \(const\) 对象通过指针维护的对象却是可以修改的。
3) \(const\) 成员函数不可以修改对象的数据,不管对象是否具有 \(const\) 性质.它在编译时,以是否修改成员数据为依据,进行检查。
\(\huge{\mathcal{Register}}\)
(不好意思先咕咕了,占坑待填...)