函数分类
- 内置函数(STL和Boost C++)
- 自定义函数
函数“三要素”
- 返回值类型
- 函数名
- 参数列表
return_type functionName(parameterList) {
//函数体
}
自定义函数
自定义函数的定义
int sum(int, int); //函数原型
int main() {
//函数调用
int result = sum(5, 3):
}
//函数定义
int sum(int num1, int num2) {
return (num1 + num2);
}
注意: C++ 中返回值类型不能是数组,但可以是其他任何类型(可以将数组作为结构或对象组成部分返回)。
参数的传递
按值传递
给函数传递变元(参数)时,变元(参数)值不会直接传递给函数,而是先制作变元(参数)值的副本,存储在栈上,再使这个副本可用于函数,而不是使用初始值。如果想要在函数内修改外部的值,可以传入引用或指针。
void change(int &num) {
num++;
}
int main() {
int num = 10;
change(num);
cout << num << endl;
return 0;
}
输出
11
数组的传递
注意:
- 数组作为函数实参时,只传递数组的地址,并不传递整个数组的空间
- 当用数组名作为实参调用函数时,数组首地址指针就被传递到函数中
如果不想传入的数组被函数修改的话可以使用 const 关键字:
void show(const int value_array[], int len)
想要传入二维数组作为参数的话,可以:
void show(double (*)[5], int);
// 或者
void show(double [][5], int);
函数指针
函数也是有地址的,函数的地址是存储其机器语言代码的内存开始地址。函数指针的好处在于,可以在不同的时间使用不同的函数。并且可以将函数作为参数进行传递。
函数指针的声明调用
//函数原型
double sum(double, double);
//函数指针声明
double (*ptr_sum1)(double, double);
//还可以使用auto进行自动判断来定义函数指针
auto ptr_sum2 = sum;
//给函数指针赋值
ptr_sum1 = sum;
//函数指针的调用
ptr_sum1(5, 6);
//或
(*ptr_sum2)(5, 6);
注意:
- 上面的语句声明了一个指针
ptr_sum,指向sum函数。 - 如果使用
double *prt_sum(double, double);进行声明的话,这意味着一个返回值为double*类型的函数ptr_sum。 - C++11 中可以使用
auto ptr_func = func;来定义一个函数指针,他可以自动推断类型,但是定义时必须初始化。 - 也可以使用
typedef来声明一个函数指针类型:
//定义函数指针类型名为 ptr_func
typedef double (*ptr_func)(double, double);
//定义一个函数指针 ptr_func1
ptr_func ptr_func1;
内联(inline)函数
内联函数是 C++ 为提高程序运行速度所做的一项改进。与常规函数的区别不在于编写方式,而在于被调用时的运行机制的不同。
具体的运行方式就是,编译器会将函数代码替换函数调用,即将函数的代码直接复制到调用的地方执行。
建议当函数代码执行时间很短,内容短时,使用内联函数,可以节省大部分时间。
定义内联函数
// 在函数声明前加关键字 inline
inline int func(int, int);
int func(int, int) {
...
}
// 在函数定义前加关键字 inline
int func(int, int);
inline int func(int, int) {
...
}
引用作为函数参数
引用其实就是指针的封装,指针的一种高级用法。例如当函数想要修改参数的值时,我们需要用指针参数来进行修改,而引用可以更加优雅的实现这个操作。对比下面两段代码:
#include <iostream>
using namespace std;
// 使用指针
void Swap1(int*, int*);
// 使用引用
void Swap2(int&, int&);
int main() {
int num1 = 5, num2 = 10;
Swap1(&num1, &num2);
cout << "执行Swap1后:" << num1 << '\t' << num2 << endl;
Swap2(num1, num2);
cout << "执行Swap2后:" << num1 << '\t' << num2 << endl;
return 0;
}
void Swap1(int* ptr1, int* ptr2) {
int temp;
temp = *ptr1;
*ptr1 = *ptr2;
*ptr2 = temp;
}
void Swap2(int& ref1, int& ref2) {
int temp;
temp = ref1;
ref1 = ref2;
ref2 = temp;
}
输出结果
执行Swap1后:10 5
执行Swap2后:5 10
可以发现,两个的执行效果是一样的,都能够改参数的数值,而引用更加的简洁。
使用引用时如果我们不希望在使用引用参数时,参数被修改,那么我们可以在声明引用参数时加入关键字 const。
注意: 参数的引用传递还常用于传递非内部数据类型,如自定义类型,这样传递的只是一个别名,而不是副本,可以大大的提高效率。
引用参数优势
- 引用可以在函数中修改参数的值
- 数据对象较大时,引用不需要生成副本,提高运行效率
- 若数据对象很小,建议按值传递
- 传递数组只能使用指针,若不想修改数组内容使用
const关键字 - 数据对象是类对象时,要求使用引用
返回引用类型
- 如果想要返回引用类型,那么千万不要返回局部变量的引用,这样得到的返回值是未知的。
- 返回引用时,要求函数参数中包含被返回的引用对象。
int& func() {
int num = 10;
int& ref = num;
return ref; // 返回局部变量num的引用
}
void test() {
int x = 1;
int y = 2;
}
int main() {
int& result = func(); // result指向局部变量num的地址
test(); // 定义其他参数,使用了局部变量num的地址
cout << result << endl;
return 0;
}
输出
1
这里可以发现,局部变量在函数指向完成之后释放了其内存,如果没有 test() 函数,可能输出还是 10,但是这只是假象,当定义其他操作需要分配内存空间时,就有可能使用原先局部变量的地址,因为局部变量已经被释放了。
函数重载
- 指可以有多个同名的函数
- 函数名相同,参数列表不同(特征标不同)
函数重载的实现原理: 其实函数重载就是编译器在编译时,根据参数列表对函数进行重新命名,例如:
void Swap(int, int);
//这种函数编译时会命名为 Swap_int_int
void Swap(float, float);
//这个则会命名为 Swap_float_float
void Swap(int&, int&);
//而若是引用则会命名为 Swap_int_int,所以引用其实和不引用用的是同一函数。
这种操作叫做重载决议。在重载时引用和关键字 const 是不起作用的,会和他们原来的类型一样,收益使用时需要注意。
重载的实现
例如定义一个能接受多个类型的 Sort 函数:
// 使用重载实现Sort 函数对浮点型,双浮点,整型数组进行排序
void Sort(int[], int);
void Sort(float[], int);
void Sort(double[], int);
void Sort(int nums[], int len) {
int temp;
for (int i = 0; i < len - 1; i++) {
for (int j = 0; j < len - i - 1; j++) {
if (nums[j] > nums[j + 1]) {
temp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = temp;
}
}
}
}
void Sort(float nums[], int len) {
float temp;
for (int i = 0; i < len - 1; i++) {
int max_index = 0;
for (int j = 1; j < len - i; j++) {
max_index = nums[j] > nums[max_index] ? j : max_index;
}
//cout << max_index << '\t' << nums[max_index] << endl;
if (max_index != len - i - 1) {
temp = nums[max_index];
nums[max_index] = nums[len - i - 1];
nums[len - i - 1] = temp;
}
}
}
void Sort(double nums[], int len) {
float temp;
for (int i = 1; i < len; i++) {
for (int j = i - 1; j >= 0; j--) {
if (nums[j+1] < nums[j]) {
temp = nums[j+1];
nums[j+1] = nums[j];
nums[j] = temp;
}
else
break;
}
}
}
函数模板(Function Template)
函数模板就是建立一个通用函数,有如下特点:
- 函数定义不指定具体的数据类型(使用虚拟类型替代)
- 函数被调用时编译器根据实参反推数据类型-类型的参数化
函数模板的定义
模板的类型参数关键字可以使用 typename 或 class 来声明,class 是早期 C++ 的写法,这和类有点歧义,所以在新版 C++ 已经改为使用 typename 了。
// 函数声明
template <typename 类型参数1, typename 类型参数2, ...>
返回值类型 函数名(形参列表);
...
// 函数定义
template <typename 类型参数1, typename 类型参数2, ...>
返回值类型 函数名(形参列表) {
...
}
例如将上面排序函数改为模板函数:
template <typename T> void Sort(T[], int);
...
template <typename T>
void Sort(T nums[], int len) {
T temp;
for (int i = 0; i < len - 1; i++) {
for (int j = 0; j < len - i - 1; j++) {
if (nums[j] > nums[j + 1]) {
temp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = temp;
}
}
}
}