C++理论梳理6——函数
一个典型的函数定义包括:返回值类型、函数名、形参列表、函数体语句、return。
返回值类型 函数名( 形参列表 )
{
函数体语句;
return XXX;
}
函数声明&定义
- 声明:一般位于头文件,告知编译器这里有一个叫xxx(函数名)的函数,作用是让编译器知道这个函数的存在。一个典型的函数声明包括:返回值类型、函数名、形参列表(可以只有形参类型而没有形参名)。主要记得加分号。
- 定义:用于实现这个函数,即这个函数到底是什么样的,真正在内存(堆或栈)中为此函数分配空间。定义一般在源文件里,以函数体的形式展现函数的实现过程。一个典型的函数定义包括:返回值类型、函数名、形参列表、函数体(函数体语句、return)。
- 一般函数声明放在头文件里,函数定义放在源文件里。可以多次声明,但只能有一个定义。
int i; //声明并定义
extern int i; //声明
extern int i=10; //定义
void f(); //声明
extern void f(); //声明, extern关键字对函数来说是多余的,可选的
void f() {}; //定义
函数的分文件编写
作用:让代码结构更加清晰
5个步骤:
- 创建后缀名为.h的头文件
- 创建后缀名为.c的源文
- 在头文件中写函数的声明、STL标准库、命名空间等
- 在源文件中写函数的定义、include头文件
- 在main中include头文件
函数的形参和实参
-
按值传递
将实参复制给形参,此时形参和实参是两个相互独立的对象。被调函数在形参上做处理,而不在实参上做处理。 -
按址传递(引用&)
可理解为变量的别名。传递的是实参地址,因此可在实参上做处理。 -
按址传递(指针*)
传递的是实参地址,因此可在实参上做处理;
注意:要做空指针判断(NULL),保证代码稳健性。 -
总结
降低内存消耗,和被调函数修改主函数时,建议使用按址传递。
函数调用
调用流程图如下:
函数的返回值
- 按值传递
绝大多数情况下,被调函数使用按值传递。 - 按址传递(static)
如果使用static(局部静态变量),该变量不会在函数结束后释放内存,此时可以按址传递。
注意:函数的返回值不能是数组或函数,但可以是指向数组和函数的指针
函数中的指针和引用
- 形参中的指针和引用
- 不存在空引用,引用必须连接到一块合法的内存。
- 一旦引用被初始化为一个对象,就不能指向另一个对象。指针可以在任何时候指向另一个对象。
- 引用必须在创建的时候初始化。指针可以在任何时候被初始化。
- 不指向任何地址的指针为空指针,值为0(null),因此使用指针建议都进行空指针判断。
- 另详见上文“函数的形参”
- 函数指针
// *func_ptr[cnt]是一个指针数据,指针为函数地址。
// 可以指向返回值为const vector<int>*,形参为int的函数。
const vector<int>* (*func_ptr[cnt])(int);
函数指针最大的作用时提高代码的复用性和泛化能力,但注意要进行空指针判断
另外,我们还有其他两种方法增加代码的复用性:重载函数&模板函数。
重载函数
参数列表一定要不同,函数主题代码可以不同,但函数名相同,的多个函数(如*,重载后可实现乘法和指针)
// 以下4个函数可重载声明:
void func(char ch);
void func(const string&);
void func(const string&, int);
void func(const string&, int, int);
模板函数
参数列表数据类型不同,其他全部相同,的多个函数(使函数脱离数据类型的限制)
// 以下4个函数可使用模板函数:
void func(char ch, const vector<int>&);
void func(char ch, const vector<double>&);
void func(char ch, const vector<string>&);