C++:缺省参数,内联函数(inline),宏的优缺点

830 阅读4分钟

文章目录


缺省参数

缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。

void TestFunc(int a = 0)
{
	cout<<a<<endl;
}
int main() {
	TestFunc();		// 没有传参时,使用参数的默认值 
	TestFunc(10);	// 传参时,使用指定的实参
}

缺省参数分类

  • 全缺省参数
void TestFunc(int a = 10, int b = 20, int c = 30)
{
 cout<<"a = "<<a<<endl;
 cout<<"b = "<<b<<endl;
 cout<<"c = "<<c<<endl;
}
  • 半缺省参数
void TestFunc(int a, int b = 10, int c = 20)
{
 cout<<"a = "<<a<<endl;
 cout<<"b = "<<b<<endl;
 cout<<"c = "<<c<<endl;
}

总结:

  1. 半缺省参数必须从右往左依次来给出,不能间隔着给
  2. 缺省参数不能在函数声明和定义中同时出现,只能出现一次
// a.h
void TestFunc(int a = 10);
// a.c
void TestFunc(int a = 20){}

// 如果声明与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值。
  1. 缺省值必须是常量或者全局变量
  2. C语言不支持(编译器不支持)

内联函数

C++ 语言支持函数内联,其目的是为了提高函数的执行效率(速度)。

inline 修饰的函数叫做内联函数,编译时 C++编译器会 在调用内联函数的地方展开,没有函数压栈的开销, 内联函数提升程序运行的效率。

  • 在调用一个内联函数时,编译器首先检查调用是否正确(进行类型安全检查,或者进行自动类型转换,当然对所有的函数都一样)。如果正确,内联函数的代码就会直接替换函数调用,于是省去了函数调用的开销。这个过程与预处理有显著的不同,因为预处理器不能进行类型安全检查,或者进行自动类型转换。假如内联函数是成员函数,对象的地址(this)会被放在合适的地方,这也是预处理器办不到的。
  • C++ 语言的函数内联机制既具备宏代码的效率,又增加了安全性,而且可以自由操作类的数据成员。所以在C++ 程序中,应该用内联函数取代所有宏代码。“断言assert”恐怕是唯一的例外。assert是仅在Debug版本起作用的宏,它用于检查“不应该”发生的情况。为了不在程序的Debug版本和Release版本引起差别,assert不应该产生任何副作用。如果assert是函数,由于函数调用会引起内存、代码的变动,那么将导致Debug版本与Release版本存在差异。所以assert不是函数,而是宏。

内联函数的编程风格:

关键字 inline 必须与函数定义体放在一起才能使函数成为内联,仅将 inline 放在函数声明前面不起任何作用。如下风格的函数Foo不能成为内联函数:

inline void Foo(int x, int y); 	// inline仅与函数声明放在一起
void Foo(int x, int y)
{
	…
}

而如下风格的函数Foo则成为内联函数:

void Foo(int x, int y); 	
inline void Foo(int x, int y)	// inline与函数定义体放在一起
{
	…
}

所以说,inline是一种“用于实现的关键字”,而不是一种“用于声明的关键字”。一般地,用户可以阅读函数的声明,但是看不到函数的定义。尽管在大多数教科书中内联函数的声明、定义体前面都加了inline关键字,但我认为inline不应该出现在函数的声明中。这个细节虽然不会影响函数的功能,但是体现了高质量C++/C程序设计风格的一个基本原则:声明与定义不可混为一谈,用户没有必要、也不应该知道函数是否需要内联。

定义在类声明之中的成员函数将自动地成为内联函数,例如

	class A
	{
	public:
		void Foo(int x, int y) { … } 	// 自动地成为内联函数
	}

将成员函数的定义体放在类声明之中虽然能带来书写上的方便,但不是一种良好的编程风格,上例应该改成:

	// 头文件
	class A
	{
	public:
		void Foo(int x, int y); 
	}
	// 定义文件
	inline void A::Foo(int x, int y)
	{
		…
	}

如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。以下情况不宜使用内联:

  1. 如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。
  2. 如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。

类的构造函数和析构函数容易让人误解成使用内联更有效。要当心构造函数和析构函数可能会隐藏一些行为,如“偷偷地”执行了基类或成员对象的构造函数和析构函数。所以不要随便地将构造函数和析构函数的定义体放在类声明中。

总结:

  1. inline是一种以空间换时间的做法,省去调用函数的开销。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数。(当内联函数被调用的时候不会创建新的栈,而是在调用处展开)
  2. inline对于编译器而言只是一个建议,编译器会自动优化。如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联。
  3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。(内敛函数具有文件作用域)
// F.h
#include <iostream>
using namespace std;

inline void f(int i);

// F.cpp
#include “F.h"
void f(int i)
{
    cout << i << endl;
}

// main.cpp
#include “F.h"
int main() 
{
    f(10);
    return 0; 
}
// 链接错误:main.obj : error LNK2019: 无法解析的外部符号 "void __cdecl f(int)" (? f@@YAXH@Z),该符号在函数 _main 中被引用

宏的优缺点?

宏常量 ------ #define MAX_SIZE 100

  • 优点:

    1. 一改全改(不需要针对代码中的所有相同常量重新修改)
    2. 常量名字具有一定的含义
  • 缺点:

    1. 宏常量没有类型(不会参与到类型检测中,导致代码安全性降低),编译器一旦报错,报错的位置不明确

宏函数 ------ #define MAX(a, b) (a > b ? a : b)

  • 优点:

    1. 宏函数不是一个真正的函数,在预处理阶段,预处理器已经将宏函数采用宏体进行替换了,少了函数调用参数压栈、开辟栈帧、返回等的开销了,代码的运行效率提高了
  • 缺点:

    1. 在实现时,可能会比较麻烦(需要到处加括号)
    2. 宏函数也没有参数类型(不会有参数类型检测,安全性不高)
    3. 宏函数在预处理阶段会展开(不能调试、代码膨胀)
    4. 宏函数具有副作用 —MAX(++a, b)(调用了两次++,本该返回一次++的值)

总结优点:

  1. 增强代码的复用性。(宏常量)
  2. 提高性能。(宏函数)

总结缺点:

  1. 不方便调试宏。(因为预编译阶段进行了替换)
  2. 导致代码可读性差,可维护性差,容易误用。
  3. 没有类型安全的检查 。

C++有哪些技术替代宏?

  1. 常量定义 换用 const常量可以达到宏替换的效果(而且有类型检测,更加安全)

在C语言中,被const修饰的变量仍然是变量,只是具有常属性(该变量的值不能修改),const 常量的替换发生在编译时。

在C++中,被const修饰的变量已经是常量,而且还具有宏替换的属性(const常量的替换发生在编译时)

#include <iostream>
using namespace std;

int main(){
    const int a;	// 在C++中错误,在C语言中可以
    return 0;
}
  1. 函数定义 换用 内联函数:除了宏函数所带来代码膨胀的问题,其余问题都能解决~~

知识点习题:

1.关于c++的inline关键字,以下说法正确的是()

A. 使用inline关键字的函数会被编译器在调用处展开
B. 头文件中可以包含inline函数的声明
C.可以在同一个项目的不同源文件内定义函数名相同但实现不同的inline函数
D. 定义在Class声明内的成员函数默认是inline函数
E. 优先使用Class声明内定义的inline函数
F. 优先使用Class实现的内inline函数的实现

正确答案:

D

答案解析:

A 项错误,因为使用 inline 关键字的函数只是用户希望它成为内联函数,但编译器有权忽略这个请求,比如:若此函数体太大,则不会把它作为内联函数展开的。

B 项错误,头文件中不仅要包含 inline 函数的声明,而且必须包含定义,且在定义时必须加上 inline 。【关键字 inline 必须与函数定义体放在一起才能使函数成为内联,仅将 inline 放在函数声明前面不起任何作用】

C 项错误, inline 函数可以定义在源文件中,但多个源文件中的同名 inline 函数的实现必须相同。一般把 inline 函数的定义放在头文件中更加合适。

D 项正确,类内的成员函数,默认都是 inline 的。【定义在类声明之中的成员函数将自动地成为内联函数】

EF 项无意思,不管是 class 声明中定义的 inline 函数,还是 class 实现中定义的 inline 函数,不存在优先不优先的问题,因为 class 的成员函数都是 inline 的,加了关键字 inline 也没什么特殊的。

  1. 以下程序输出结果是__
class A
{
public:
    virtual void func(int val = 1)
    { std::cout<<"A->"<<val <<std::endl;}
    virtual void test()
    { func();}
};
class B : public A
{
public:
    void func(int val=0)
	{std::cout<<"B->"<<val <<std::endl;}
};
int main(int argc ,char* argv[])
{
    B* p = new B;
    p->test();
	return 0;
}

A. A->0
B. B->1
C. A->1
D. B->0
E. 编译出错
F. 以上都不对

正确答案:

B

答案解析

virtual 函数是动态绑定,而缺省参数值却是静态绑定。 意思是你可能会在“调用一个定义于派生类内的virtual函数”的同时,却使用基类为它所指定的缺省参数值。

结论:绝不重新定义继承而来的缺省参数值! (可参考《Effective C++》条款37)


如有不同见解,欢迎留言讨论