函数与数组在某些方面很类似:同样占据一段内存空间,其名字同样代表其占用内存空间的首地址。但运行时调用函数可以执行语句,而数组则不行。
因此将函数当作一个整体看(通常是作为函数名存在)时,可以将其看作为一个类似数组的变量,类比理解;而调用函数时,则将函数拆散来看,关注函数对函数体内变量的操作。
以下的分析全都基于这一理解。
函数名的四种含义
作用域和生命周期
二者的区别
- 作用域是指某变量在代码块中的可视范围
- 生命周期是指某变量在运行时存在的一段时间
#include <iostream>
using std::cout;
using std::endl;
int i;
void func()
{
static int m;
cout << m << endl;
m = 5;
}
int main()
{
int j;
{
int k;
}
func();
func();
}
在这段代码,i、func在整个代码段中可视,m在func函数中可视,j在main函数中可视,k在main的内层大括号中可视;变量m在运行过程中一直存在,且只在第一次定义时自动初始化为0,而第二次输出的m是在第一次调用func函数后赋值为5的结果,故输出结果如下:
传递参数
传参的工作原理
- 无论参数的数据类型是什么,传参都是传入参数的值
#include <iostream>
using std::cout;
using std::endl;
int f(int i)
{
return 0;
}
int main()
{
int j;
j = f(7);
}
如上代码,调用f函数的过程即
int j;
int i = 7;
j = 0;
解释:
- 定义int型变量j
- 定义int型变量i,并将7赋值给i
- 将返回的0值赋值给j
当形参是指针或引用时,调用函数的原理也同上
#include <iostream>
using std::cout;
using std::endl;
void f(int &i)
{
i = 5;
return;
}
int main()
{
int j = 0;
f(j);
cout << j << endl;
}
如上代码,调用f函数的过程即:
int j = 0;
int &i = j;
i = 5;
cout << j << endl;
解释:
- 定义int型变量j
- 定义int型引用变量i,并将i与j绑定
- 对i赋值为5(即对j赋值为5)
- 输出j的值
输出结果如下
默认实参
合法的默认实参
声明时:有默认实参的形参右边的参数都应有默认实参。
调用时:有具体值的实参左边的实参都应有具体的值。
/*函数的声明*/
void f(int, int, int i = 5);//正确
void f(int, int i = 5, int);//错误
void f(int, int i = 5, int j = 5);//正确
/*函数的调用*/
int main()
{
···
f(5, 5, 5);//正确
f(5);//当后两个位置有默认实参时正确
f(5, , 5);//无论中间位置是否有默认实参,此调用方法均不正确
···
}
注意:逗号运算符的左侧须有primary-expression,因此后方位置想使用默认实参时需省略","
有默认实参情况下对函数传参过程的理解
定义的形参被默认实参初始化后,再用传入的值对各个形参进行赋值
先上代码:
#include <iostream>
using std::cout;
using std::endl;
void f(int i, int j = 4, int k = 5)
{
cout << i << " " << j << " " << k << endl;
}
int main()
{
f(1, 2);
f(1);
}
输出结果如下:
第一次进入f函数时,发生了以下过程:
int i, j = 4, k = 5;//被默认实参初始化
i = 1, j = 2;//用实参进行赋值
cout << i << " " << j << " " << k << endl;
而第二次进入f函数时:
int i, j = 4, k = 5;//被默认实参初始化
i = 1;//用实参进行赋值
cout << i << " " << j << " " << k << endl;
return的工作原理
void类
返回void函数不要求非得有return语句,因为在此类函数的最后一句后面会隐式地执行return。 ——《c++ primer》
非void类
从执行return语句开始,被调用函数中定义的局部自动变量释放内存空间,return后的表达式的值(可能是具体值,也可能是地址)被拷贝到调用点。
注意:当返回类型是指针或引用时必须返回调用点可视范围内的变量
返回值是指针
int j;
int *f(int i)
{
return &i;//错误,返回时i被释放了,在main函数中无法检测到此变量
return &j;//正确
}
int main()
{
int m;
int *ptr = f(m);
}
返回值是引用
调用一个返回引用的函数得到左值。——《c++ primer》
先上代码:
#include <iostream>
using std::cout;
using std::endl;
int &f(int &i)
{
return i;
}
int main()
{
int m = 0;
int n = 1;
f(m) = n;
if(m)
cout << "n对m的引用赋值" << endl;
else
cout << "n未对m的引用赋值" << endl;
}
结果如下:
调用时,i是int类型的引用,与m绑定在一起,因此返回i的引用即相当于返回m的引用。返回调用点后,用n的值对m的引用进行了赋值。
此过程相当于:
/*在main函数代码段中*/
int m = 0;
int n = 1;
/*在f函数代码段中*/
int &i = m;
return i;//即return了m
/*在main函数代码段中*/
m = n;//用n的值对返回的m的别名(也就是m)赋值
if(m)
cout << "n对m的引用赋值" << endl;
else
cout << "n未对m的引用赋值" << endl;
函数指针
函数会占用内存空间的一段地址。
数组名代表数组首元素所在的首地址,类似的,函数名也代表函数所占用内存空间的首地址。
函数名作为参数
函数表现形式 | 代表意义 |
---|---|
单独的函数名 | 函数所占内存空间的首地址 |
函数名+形参列表 | 函数的定义/声明(看后头跟什么) |
函数名+实参列表 | 调用函数后返回的值 |
当函数名单独存在作为参数(包括形参和实参)时,会自动转换成指向函数首地址的指针(这一点与数组类似)。
函数重载
定义
同一作用域内几个函数名字相同但形参列表不同,我们称之为函数重载。——《c++ primer》
合法的重载
- 重载函数在形参数量或形参类型上有所不同
- 同一类型的不同别名不算形参类型不同
- 是否加顶层const不影响形参的类型
调用的顺序
- 实参类型与形参相同或数组/函数名转换为对应的指针类型
其中,顶层const不改变数据类型,因此顶层const实参对应无const形参也属于同一类型 - 底层const转换
- 通过类型提升的转换:int->double, short->int之类
- ①算术类型转换:5.2 + 3->double
②指针转换:
指针原样 | 转换目标 |
---|---|
nullptr/0 | 任意指针类型 |
任意非常量的指针 | void* |
任意对象的指针 | const void* |
- 类类型的转换