文章目录
前言
C++为函数增加了一些新的特性,优化了C语言函数在某些方面设计的不够好的地方
以下是C++函数的新特性:
缺省参数
、内联函数
、函数重载
、运算符重载
一、缺省参数
1. 缺省参数的概念
缺省参数是声明或定义函数时,给函数参数指定的一个值。在调用该函数时,如果没有传实参,形参则为给定的缺省值
void Func(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << endl; // a = 10
cout << "b = " << b << endl; // b = 20
cout << "c = " << c << endl; // c = 30
}
int main()
{
Func();
return 0;
}
2. 缺省参数的作用
某些函数有这样一种形参,在函数的很多次调用中它们都被赋予一个相同的值。这时就可以给这些形参一个指定的缺省参数,便于以后调用该函数时不需要显式的给该形参传参
以 string 中的 erase 函数为例:
该函数的作用为从字符串下标为 pos 的位置开始向后删除 len 个字符。当调用该函数时,如果不传参数,即为清空整个字符串
3. 缺省参数的使用
-
某个形参被赋予缺省值,它后面的所有形参都必须有缺省值
void Func(int a = 10, int b, int c = 30) { cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl; } // 报错:形参 b 没有缺省值
原因: 传参时会有歧义,调用 Func(10,20) 时,实参如何分配无法确定
-
缺省参数不能在函数声明和定义中同时出现
void Func(int a = 10, int b = 20, int c = 30); int main() { Func(); return 0; } void Func(int a = 10, int b, int c = 30) { cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl; } // 报错:缺省值在声明时已经给出
原因: 声明与定义位置提供的值不同,那编译器就无法确定到底该用哪个缺省值
补充:
一般一个函数只声明一次,但声明多次也是合法的。但每个形参只能被赋予一次缺省值,函数的后续声明只能为没有缺省值的形参添加缺省值,而且该形参右侧的所有形参必须都有缺省值
void Func(int a, int b, int c =30); void Func(int a, int b, int c =20); // 报错:重复声明 void Func(int a = 10, int b = 20, int c =30); // 正确:添加缺省参数
二、内联函数
1. 概念
以 inline 修饰的函数叫做内联函数。编译阶段 C++ 编译器会在调用内联函数的地方展开,即用函数体替换函数调用,没有建立栈帧的开销
以取 a 和 b 的较大值为例:
template<typename T>
inline const T& GetMax(const T& a, const T& b)
{
return a > b ? a : b;
}
std::cout << GetMax(10,20) << std::endl;
// 编译阶段内联展开后相当于如下代码:
// std::cout << (10 > 20 ? 10 : 20) << std::endl;
2. 作用
内联函数在调用处展开,既是内联函数的优点,也是内联函数的缺点。优点在于没有函数建立栈桢的开销,缺点在于展开函数体会使目标文件变大
内联函数一般适用于规模较小、流程直接、调用频繁且无递归的函数
实际上,内联函数的缺点可以忽略,因为 inline 对于编译器只是个请求,编译器可以忽略这个请求,只有当该函数符合内联的适用场景,编译器才会采纳 inline 请求
然而,可能会有人有这样的疑问,C语言的宏函数貌似也可以做到和内联函数一样的事情啊,C++为什么还要引入内联函数呢?
在 Effective C++(改善程序与设计的55个具体做法)这本书中有这样一个条款:条款 02:尽量以 const,enum,inline 替换 #define
还是以取出 a 和 b 的较大值为例
- 宏函数
要写出一个宏函数可不容易,因为宏的本质是替换,所有你必须记住为宏中所有实参加上小括号,以避免有人调用宏时传递表达式。但纵使如此,还有一些事情无法避免,如下代码为例:#define GETMAX(a, b) ((a) > (b) ? (a) : (b))
调用该宏函数,a 的累加次数竟然取决于“它被拿来和谁比较”void func1() { int a = 5, b = 0; cout << GETMAX(++a, b) << endl; // 7 cout << a << endl; // 7 :a 被累加 2 次 } void func2() { int a = 5, b = 0; cout << GETMAX(++a, b + 10) << endl; // 10 cout << a << endl; // 6 :a 被累加 1 次 }
- 内联函数
还是用以上两个例子调用内联函数,没有发生像宏函数那样不可思议的情况!template<typename T> inline const T& GetMax(const T& a, const T& b) { return a > b ? a : b; }
综上来看,内联函数既继承了宏函数的优点(运行效率高,没有建立栈桢的开销),又避免了宏函数的缺点(代码安全性高)
三、函数重载
1. 概念
函数重载是函数的一种特殊情况,C++ 允许在同一作用域中声明几个功能类似的同名函数,但这些同名函数的形参列表必须不同(参数个数、参数类型、参数顺序)
注意,返回值不同不在构成函数重载的要求范围内
编译器根据传递的实参列表,来决定调用哪一个函数,以加法函数为例:
int Add(int left, int right)
{
cout << "int Add(int left, int right)" << endl;
return left + right;
}
double Add(double left, double right)
{
cout << "double Add(double left, double right)" << endl;
return left + right;
}
int main()
{
Add(1,2); // int Add(int left, int right)
Add(1.0,2.0); // double Add(double left, double right)
return 0;
}
2. 作用
函数重载一般用于实现操作非常相似的函数,以 C++ 输入输出函数为例:
C++ 的标准输出之所以能够自动识别类型,就是因为重载了不同类型版本的函数!
尽管函数重载在一定程度上减轻我们为函数起名字、记名字的负担,但是最好只重载那些非常相似的操作。因为有些情况下,给函数起不同的名字能使代码更易理解!!!
3. 补充
函数名修饰规则
- C 语言在编译后,函数名没有发生改变
- 而 C++ 在编译后,函数名被其参数的相关信息修饰,尽管函数名相同,但只要参数列表不同,经过修饰后的函数名也就不同
- 通过这里也就理解了 C 语言没办法支持重载而 C++ 支持重载的原因:C 语言无法区分同名函数,而 C++ 可以通过函数名修饰规则来区分同名函数
函数匹配原则
- 编译器会找一个与实参最佳匹配的函数,并调用该函数
- 如果找不到任何一个函数与实参匹配,编译器会发出无匹配的报错
- 没有最佳匹配,但仍有多个函数与实参匹配,编译器不知道到底该调用哪一个函数,会发出调用二义性的报错
重载与作用域的关系
- 只有在同一作用域的同名函数才构成重载,如果不同作用域有同名函数(例如在局部域中声明函数),内部作用域就会隐藏外部作用域的同名实体,以下代码为例:
string read(); void print(const string &); void print(double); int main() { bool read = false; read(); // 报错,read 为 bool 类型,无法调用 void print(int); print(5); // 报错,无法找到函数的实现 return 0; } string read() { cout << "string read()" << endl; return ""; } void print(const string &) { cout << "void print(const string &)" << endl; } void print(double) { cout << "void print(double)" << endl; }
- 局部域中的 read 隐藏全局域中的 read() 函数,编译器在找到局部域中的 read 后就不会到全局域中找了,故发出 read 为 bool 类型,无法调用的报错
- print(int) 在局部进行声明隐藏了全局域中同名函数的声明,在调用 print 函数时,编译器在局部域中找到了 print(int) 的声明,就不会去全局域中找了,然而 print(int) 没有被实现,故发出无法找到函数实现的报错
四、运算符重载
1. 概念
大家都知道,C++ 是一个经典的面向对象的编程语言。为了能让自定义类型也能像内置类型一样进行基本的运算,C++ 引入了运算符重载
当运算符作用于自定义类型的对象时,可以通过运算符重载来重新定义该运算符的含义,代码如下:
class item
{
public:
item(const char *name = "", int price = 0)
: _name(name), _price(price)
{
}
int operator+(const item &it)
{
return _price + it._price;
}
private:
std::string _name; // 商品名称
int _price; // 商品价格
};
int main()
{
item it1("apple", 3);
item it2("pine", 4);
cout << it1 + it2 << endl;
return 0;
}
该代码重载了 + 运算符,重新定义了 + 运算符作用于 item 对象的含义,为两个商品的价格总和
明智地使用运算符重载能令我们的代码更易于编写和阅读
2. 语法
- 重载运算符是具有特殊名字的函数,它的名字由关键字 operator 和其后要定义的运算符号共同组成。和其它函数一样,该函数也具有返回类型、参数列表以及函数体
- 不能对内置类型重载运算符,例如:
int operator+(int a, int b);
- 我们只能重载已有的运算符,不能创造新的运算符
- 因为运算符重载针对自定义类型,故在类和对象部分再深入讲解
总结
C++ 关于函数引入了许多特性,来弥补 C 语言的不足之处
缺省参数:
某些函数有这样一种形参,在函数的很多次调用中它们都被赋予一个相同的值,这时就可以给这些形参一个指定的缺省参数
内联函数:
尽量用 inline 替换 #define 宏函数
函数重载:
对于操作非常相似的函数,重载成不同的版本
运算符重载:
当运算符作用于自定义类型的对象时,可以通过运算符重载来重新定义该运算符的含义