C++ Primer第六章③
C++ Primer
第六章 函数
返回类型和return语句
return语句终止当前正在执行的函数,并把控制权返回到调用该函数的地方。
无返回值的函数
就是我们之前写的返回类型为void的函数:
void f()
{
return; //也可以不写
}
有返回值的函数
只要函数的返回类型不是void,那必须要有return内容。我的经验是,要保证任何情况都有返回。因为实际项目中的代码逻辑都比较复杂,一定注意不要有没有返回值得情况,这个得程序员自己注意,因为编译器基本检查不出这种类型的错误:
int f() //这种函数编译器不会报错的
{
}
值是如何被返回的
- 返回类型为值:
其实和初始化一个变量的方式完全一样,返回的值会初始化一个临时的对象,然后赋值给调用点。
该函数的返回类型是string,值类型,也就是说,返回值会被拷贝到调用点,这个函数在返回的时候会创建一个临时的string对象(内容是word+p),然后给调用点。 这样是不是很麻烦且浪费呢?我们当然可以不拷贝,怎么办呢,用引用啊string plus(const string &word, const string &p) { return word + p; }
- 返回类型为引用:
//返回两个string中较短的那个 const string &shortStr(const string &s1, const string &s2) { return s1.size() <= s2.size() ? s1 : s2; }
graph LR
形参为引用-->调用函数时不用拷贝string对象
graph LR
返回类型为引用-->返回结果不用拷贝string对象
那么,是不是这么好用呢?你如果很注意细节的话当然是,但这个稍不注意就会出错,不信你看看下面的这个函数对不对:
//判断string是不是空的
const string &f()
{
string ret;
if(!ret.empty())
{
return ret;
}
else
{
return "empty";
}
}
两处返回值都错了,返回ret肯定错,因为ret是在被调用函数中定义的,当return之后ret都被销毁了,怎么还能返回引用呢?那之前的怎么合法呢?因为人家不是自己定义啊,是主调函数传进来的。第二个也是同样的错误,"empty"只是个在被调函数中临时创建的对象,return之后也是要销毁的。 调用一个返回引用的函数得到左值(身份),其他返回类型得到右值(内容),所以啊,我们可以像使用其他左值那样来使用返回引用的函数的调用,比如,可以给返回类型是非常量引用的函数的结果赋值:
int &getVal(){}
int main()
{
getVal() = 5; //这样是合法的
return 0;
}
列表初始化返回值:只不过是返回的数据类型复杂些:
vector<string> process()
{
if(1)
{
return {}; //返回空vector对象
}
else
{
return {"你看不到我"};
}
}
看看老伙计main函数
int main()
{
}
这样写会报错吗?它没有return语句啊,没事,编译器会隐式插入一条return 0; 返回0表示执行成功,返回其他值表示执行失败,具体含义依机器而定。
递归
我认为递归是最考验你对函数调用理解的,我们来写个递归求阶乘的:
int fact(int val)
{
if(val > 1)
{
return fact(val-1) * val;
}
return 1; //一定要有这个触底反弹
}
你可以试试按照上面的函数求个5的阶乘,有助于理解哦。
返回数组指针
因为数组不能被拷贝,所以函数不能返回数组,只能返回数组的指针或者引用。
int (*f())[10]; //函数返回的是指针,指向一个大小为10的int数组
因为这样看起来比较麻烦,我们可以用之前学过的类型别名:
using arr = int[10];
arr* f(); //这样的声明就比较简洁了
除了使用类型别名外,C++还有一种方法:尾置返回类型,顾名思义,就是把返回类型放后面:
auto f() -> int(*)[10]
别忘了我们还有一个神器,decltype:
int a[] = {1,2,3};
decltype(a) *arrp(){} //返回类型是指针,且指向的类型与a一致
//使用它的前提是我们知道返回的指针指向什么类型的数组。
#C++工程师#