C++ 函数

238 阅读7分钟

函数分类

  • 内置函数(STL和Boost C++)
  • 自定义函数

函数“三要素”

  1. 返回值类型
  2. 函数名
  3. 参数列表
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

数组的传递

注意:

  1. 数组作为函数实参时,只传递数组的地址,并不传递整个数组的空间
  2. 当用数组名作为实参调用函数时,数组首地址指针就被传递到函数中

如果不想传入的数组被函数修改的话可以使用 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);

注意:

  1. 上面的语句声明了一个指针 ptr_sum,指向 sum 函数。
  2. 如果使用 double *prt_sum(double, double); 进行声明的话,这意味着一个返回值为 double* 类型的函数 ptr_sum
  3. C++11 中可以使用 auto ptr_func = func; 来定义一个函数指针,他可以自动推断类型,但是定义时必须初始化。
  4. 也可以使用 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

注意: 参数的引用传递还常用于传递非内部数据类型,如自定义类型,这样传递的只是一个别名,而不是副本,可以大大的提高效率。

引用参数优势

  1. 引用可以在函数中修改参数的值
  2. 数据对象较大时,引用不需要生成副本,提高运行效率
    • 若数据对象很小,建议按值传递
    • 传递数组只能使用指针,若不想修改数组内容使用 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)

函数模板就是建立一个通用函数,有如下特点:

  • 函数定义不指定具体的数据类型(使用虚拟类型替代)
  • 函数被调用时编译器根据实参反推数据类型-类型的参数化

函数模板的定义

模板的类型参数关键字可以使用 typenameclass 来声明,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;
			}
		}
	}
}