八、 c++ 的函数
1. 函数重载
eg: 图形库中画图函数
c:
void drawCircle(int x, int y, int r){}
void drawRect(int x, int y, int w, int h){}
...
c++:
void draw(int x, int y, int r){}
void draw(int x, int y, int w, int h){}
...
1) 函数重载的定义
在相同作用域中,定义同名的函数, 但是它们的参数有所区别,这样的函数构成重载关系.
注: 重载与函数返回类型无关,与参数名也无关.
2) 函数重载的匹配
调用重载关系的函数时,编译器将根据实参与行参匹配度,自动选择最优的重载版本.
当前编译器(g++ 4.4.7)的一般匹配规则:
完全匹配 > 常量转换 > 升级转换 > 降级转换 > 省略号匹配
3) 重载实现原理
c++ 编译器是通过对函数进行换名, 将参数表的类型信息整合到新的名字中,实现解决函数重载与名字冲突的矛盾.
思考题: extern "C" 声明的作用?
2. c++ 函数的缺省参数
1) 可以为函数的部分或全部行参指定缺省值,调用该函数时,如果不给实参,就取缺省值作为相应的行参的值.
eg:
void func(int a, int flags = 缺省值){}
int main(void){
func(10);
func(10, 20);
}
2) 缺省值必须靠右,如果一个参数带有缺省值,那么这个参数的右侧的所有行参都必须带有缺省值:
eg:
void func(int a = 100, int b = 200, int c){} // Error
void func(int a, int b = 200, int c){} // Error
void func(int a, int b, int c = 300){} // Ok
void func(int a, int b = 200, int c = 300){} // Ok
3) 如果函数的定义和声明分开,缺省参数应该写到函数的声明部分,而定义部分不写,但是最好的注释,提高代码的可读性。
void func(int a = 100) // 函数的声明
void func(int a /*= 100*/){} // 函数的定义
3. c++ 函数的哑元参数
1) 定义: 只有类型没有变量名的行参称为哑元
void func(int /* 哑元参数 */){
// code
}
int main(void){
func(100);
}
2) 为了兼容旧代码
eg:
算法库:
void math_func(int a, int b){
// code
}
使用者:
int main(void){
math_func(10,20);
math_func(20,50);
math_func(30,50);
// ...
}
------------------------
升级算法库:
void math_func(int a, 哑元){
// ...
}
使用者:
int main(void){
math_func(10,20);
math_func(20, 50);
math_func(30, 50);
// ...
}
3) 运算符重载, 区分前后 ++/--
4. 内联函数 inline
// 思考题: inline 关键字的作用
八、 4 思考题答案
使用 inline 关键字修饰的函数, 表示这个函数是内联函数, 编译器会尝试做内联优化,避免函数调用的开销.
1) 多次调用的小而简单的函数适合内联
2) 调用次数极少或者大而复杂的函数不适合内联
3) 递归函数不能内联
4) 内联只是一种建议而不是一种强制要求, 能否内联主要取决于编译器,有些函数不加 inline 关键字也会以内联的方式展开,有些函数即便加了 inline 关键字也不会处理为内联。
九、 c++ 的引用 (reference)
1. 定义引用就是给某个变量起别名,对引用的操作与对变量的操作完全相同。
int a = 10;
int &b = a; // 定义引用 b, b 就是 a 的别名
b++;
cout << a << endl; // 11
2. 常引用
1) 定义引用的时候加 const 修饰, 即为常引用, 不能通过常引用修改引用的目标
eg:
int a = 100;
const int& b = a; // b 就是 a 的常引用
b = 20; // Error
2) 普通的引用只能引用左值(变量), 常引用也叫万能引用,既可以引用左值也可以引用右值。
eg:
int &r = 100; // Error
const int& r = 100 // Ok
3. 关于左值和右值的概念
1) 左值: 可以放在赋值运算符的左边,可以被修改, 可以被取地址.
int i;
i = 100;
++i;
&i;
常见左值:
- 变量
- 前++/-- 表达式的值、赋值表达式的值
eg:
int number = 1;
cout << (++number) << endl; // 2
cout << (number) << endl; // 2
++number = 10; // Ok
++++++number;
eg:
int a = 10;
int b = 20;
(a = b) = 30;
cout << a << endl; // 30
cout << b << endl; // 20
2) 右值: 只能放在赋值运算符右边,不可以被修改, 不可以被取地址.
常见右值:
- 字面值
10 = i; // Error
++10; // Error
&10; // Error
- 大部分表达式的值
eg:
int number = 1;
cout << (number++) << endl; // 1
cout << (number) << endl; // 2
number++ = 20; // Error
number++++; // Error
eg:
int a = 3;
int b = 5;
(a + b) = 10; // Error
- 函数返回值
int func(void){
int a = 100;
return a; // 将 a 保存到一个临时变量中: temp = a
}
int main(void){
int res = func(); // int res = temp
foo() = 200; // Error
++foo(); // Error
}
4. 引用型的参数
1) 可以将引用用于函数的参数, 可以直接修改实参变量的值, 同时可以减少函数调用的开销:
eg:
void func(int& a = x){
a++;
cout << a << endl; // 2
}
int main(void){
int x = 1;
func(x);
cout << x << endl; // 2
return 0;
}
2) 引用参数有可能意外修改实参变量的值,如果不希望修改实参本身,可以将行参定义为常引用, 在提高传参效率的同时还可以接收常量型的实参。
思考题:
1. 使用引用参数完成两个字符串(char*)的交换
int main(void){
const char* s1 = "hello";
const char* s2 = "world";
swap4(s1, s2);
cout << s1 << endl; // world
cout << s2 << endl; // hello
}
2. 总结引用和指针的区别和联系
八、 1 (3)思考题答案
思考题答案: 函数声明前加入 extern "C"要求 c++ 编译器不对函数做换名,编译 c 程序调用该函数.
注: 这样的函数无法重载
eg:
extern "C" void func(void){// code}
extern "C"{
void func1(){// code}
void func2(){// code}
}