- 什么是局部对象? 它的的生命周期是怎样的?
形参和在函数体内部定义的变量统称为局部变量, 仅在函数作用域内可见. 其生命周期依赖于定义的方式. 局部对象有两种:
- 自动对象
当函数执行到对象定义语句(函数内部)时创建该对象, 当执行到对象所在块(其实是对象名的作用域)末尾时销毁该对象. - 局部静态对象
当函数执行到对象定义语句(函数内部)时创建该对象, 直到程序终止时销毁该对象.
形参是自动对象, 其在函数开始时定义, 函数结束时形参被销毁.
- 自动对象
- 函数参数传递的方式有哪些?
函数传递参数的方式有三种: 按值传递 按指针传递 按引用传递
- 按值传递
按值传递即直接将要传递给函数的对象的值传递给函数的形参. 这里的直接事实上包含了拷贝的过程.
- 按值传递
int i = 20; // 要传递给函数function的实参
void function(int num)
{
cout << num << endl;
}
function(i); // 调用函数
//----------------真实的传值过程-----------------------
int temp = i; // temp是临时对象, 获得i的拷贝值
num = temp;
-
- 按指针传递
按指针传递是将要传递给函数形参的对象的地址传给形参.
为什么要建立按指针传递的机制呢? 可以想象一下, 如果我们有一个非常大的类对象要传递给函数, 那么复制一个这样的类对象并传递给函数将会占用很多内存, 并且当我们想对原类对象做一些改变时, 由于我们在函数中处理的是原类对象的一个拷贝, 原类对象是不会有任何改变的. 如果将类对象的地址传给函数, 一方面不用占用很大的内存, 另一方面也可以直接改变原类对象的内容. 考虑到按指针传递时有时候我们并不想改变指针指向的内容, 所以又建立了const机制.
- 按指针传递
int i = 20; // 要传递给函数function的实参
void function(int *num)
{
cout << num << endl;
}
function(&i); // 调用
传递数组时实际上传递的是指针.
-
- 按引用传递
C++建立了类似于按指针传递的引用传递机制. 只不过传递的不是指向对象的指针而是绑定对象的引用.
- 按引用传递
C++中尽量使用按引用传递的机制.
- 如果我想返回多个参数怎么办?
当需函数要返回多个参数时, 可以创建额外的引用形参, 将希望从函数中获取的信息通过形参传递出来.
int i = 3, j = 2;
int sum = 0;
// 需要函数返回积与和
// 积通过返回值获取
// 和通过额外形参获取
int function(int num1, int num2, int &refSum)
{
refSum = num1 + num2;
return (i * j);
}
int mul = function(i, j, sum);
// 此时mul = 6 sum = 5
- main函数有没有参数? 怎么给main函数传递参数?
事实上main函数可以带有形参:
int main(int argc, char *argv[])
{
......
}
第二个形参argv是一个C风格的字符串指针, 第一个形参是数组中argv[]中字符串的个数. argv的第一个字符串必须指向程序名或者空字符.
通过下面命令行语句给main函数传递参数:
prog -d -o ofile data0
// 此时argv数组的内容
argv[0] = "prog";
aegv[1] = "-d";
argv[2] = "-o";
argv[3] = "ofile";
argv[4] = "data0";
argv[5] = 0;
- 怎样简单的返回复杂类型对象?
有时候函数返回的类型比较复杂时代码会很晦涩, 比如返回数组指针:
int (*func(int i))[10];
// 可以这样理解上述代码
func(int i); // func的参数是int型
(*func(int i)); // func返回一个指针
(*func(inti))[10]; // 指针指向大小为10的数组
int (*func(int i))[10]; // 数组的元素为int型
我们可以采用下面三种方式简化这种操作:
1.使用类型别名
typedef int arrT[10]; // arrT是类型别名,表示含有10个整数的数组
using arrT = int[10]; // 同上一句
arrT* func(int i); // func返回指向含有10个整数的数组的指针
2.使用尾置返回类型
// int(*)[10]是func的返回值
auto func(int i) -> int (*)[10]
3.使用decltype
当知道函数返回哪个数组时可以有decltype关键字声明返回类型:
int odd[] = {1, 3, 5, 7, 9};
int even[] = {0, 2, 4, 6, 8};
// 返回一个指向含有5个int元素的数组的指针
decltype(odd) *arrPtr(int i)
{
return (i % 2) ? &odd : &even;
}
- 编译器是怎样确定调用哪个重载函数的?
编译器确定调用哪个重载函数的过程称为函数匹配. 函数匹配分为三步:
- 确定本次调用的重载函数集(候选函数).
- 根据实参的数量在候选函数中选出可以执行(可能不止一个)本次调用的函数(可行函数).
- 根据实参的类型从可行函数中选择本次调用最终执行的函数(最匹配函数).
这里”匹配”的含义很复杂, 我们将实参类型与形参类型完全相同称为精确匹配, 其他还有实参需要类型转换的匹配. 所谓最佳匹配指的是实参与形参的类型最接近.
可以看出匹配的概念是很模糊的, 存在很多二义性, 如果匹配也像运算符一样有”优先级”就可以很好的解决这个问题.
- 匹配的优先级
所谓匹配的优先级指的就是优先级越靠前编译器越会判定其为最匹配.
- 精确匹配, 包括以下情况:
- 实参与形参类型相同.
- 实参从数组类型或函数类型转换成对应指针类型.
- 向实参添加顶层const或者从实参中删除底层const.(顶层/底层const仅存在与引用与指针,注意下面的例子)
- 通过const转换实现的匹配.
- 通过类型提升实现的匹配.
- 通过算术类型转换实现的匹配.
- 通过类类型转换实现的匹配.
- 精确匹配, 包括以下情况:
// 简要介绍几种情况
// 向实参添加顶层const
void func(const int &num);
int i = 8;
int &j = i;
func(j);
// 通过const转换
void func(const num);
int i = 8;
func(i);
// 通过类型提升
void func(int num);
char i = 8;
func(i);
// 通过算术类型转换
void func(int num);
float i = 8.2;
func(i);
// 一种重要的情况(注意与向实参添加顶层const的区别)
void lookip(int &);
void lookup(const int &);
const int i;
int j; // j不是引用, 不存在添加顶层const, 只有转换成const类型
lookup(i); // 只能调用void lookup(const int &)
looup(j); // 调用void lookup(int &)
- 函数重载中为什么需要const_cast?
看这个程序:
// const版本
const string &shorterString(const string &s1, const string &s2)
{
return s1.size() <= s2.size() ? s1 : s2;
}
这个函数的参数和返回值都是const string &类型, 当我们用两个const string &实参调用函数时得到一个const string &类型, 这很好. 但是但我们用两个普通的string &调用函数时函数仍然可以工作, 只不过得到的仍然是const string &, 这个就不好了, 因为我们想要得到的是普通的string &. 这时const_cast可以完成这个任务.
// non-const版本
string &shorterString(string &s1, string &s2)
{
auto &r = shorterString(const_cast<const string &> (s1), const_cast<const string &>(s2));
return const_cast<string &>(r);
}
当我们用普通string &调用函数时调用non-const版本, 在这个版本中实参被转换成const string &型, 然后调用const版本, 返回const string &类型, 赋给r, 再用const_cast将const string &转换为普通string &.
- 能不能理解这条语句: int ( * func(int))(int * , int) ?
可以这样理解:
- func(int): func是一个带有一个int形参的函数.
- ( * func(int)): func返回一个指针.
- ( * func(int))(int * , int): 指针指向的内容是一个函数, 且取名为M, M函数带有一个int *型和一个int型形参.
- int ( * func(int))(int * , int): M函数的返回一个int类型.
这里就出现了函数指针(指向函数的指针)这种类型的指针.
void (*func)(int *, int); // 这是一个函数指针
int array[10]; // 这是一个数组
现在再看看函数的声明大概可以猜到, 函数名和数组名一样, 代表了函数的入口地址.