《C++ Primer》第6章 函数
6.3节 返回类型和return语句
练习6.30:编译第200页的str_subrange函数,看看你的编译器是如何处理函数中的错误的。
【出题思路】
函数对于返回结果的要求是:每一条return语句的结果类型必须与函数的返回值类型相同,并且在函数执行逻辑中每一个可能的结束点都应该有一条return语句。
【解答】
该函数在作者所用的编译环境中无法编译通过,编译器发现了一个严重错误,即for循环中的return语句是非法的。函数的返回值类型是布尔值,而该条return语句没有返回任何值。事实上程序还存在另一个严重错误,按照程序的逻辑,for循环有可能不会中途退出而是一直执行完毕,此时显然缺少一条return语句处理这种情况。遗憾的是,编译器无法发现这一错误。
练习6.31:什么情况下返回的引用有效?什么情况下返回常量的引用有效?
【出题思路】
函数返回其结果的过程与它接受参数的过程类似。如果返回的是值,则创建一个未命名的临时对象,并把要返回的值拷贝给这个临时对象;如果返回的是引用,则该引用是它所引对象的别名,不会真正拷贝对象。
【解答】
如果引用所引的是函数开始之前就已经存在的对象,则返回该引用是有效的;如果引用所引的是函数的局部变量,则随着函数结束局部变量也失效了,此时返回的引用无效。当不希望返回的对象被修改时,返回对常量的引用。
练习6.32:下面的函数合法吗?如果合法,说明其功能;如果不合法,修改其中的错误并解释原因。
int &get(int *arry, int index) { return array[index]; }
int main() {
int ia[10];
for(int i = 0; i != 10; ++i)
get(ia, i) = i;
}
【出题思路】
考查当函数的参数和返回值是复合类型时,如何向函数传入数据及如何接受返回结果。读者尤其需要理解函数是怎样返回引用类型的。
【解答】
该函数是合法的。get函数接受一个整型指针,该指针实际指向一个整型数组的首元素,另外还接受一个整数表示数组中某个元素的索引值。它的返回值类型是整型引用,引用的对象是arry数组的某个元素。当get函数执行完毕后,调用者得到实参数组arry中索引为index的元素的引用。
在main函数中,首先创建一个包含10个整数的数组,名字是ia。请注意,由于ia定义在main函数的内部,所以ia不会执行默认初始化操作,如果此时我们直接输出ia每个元素的值,则这些值都是未定义的。接下来进入循环,每次循环使用get函数得到数组ia中第i个元素的引用,为该引用赋值i,也就是说,为第i个元素赋值i。循环结束时,ia的元素依次被赋值为0~9。
#include <iostream>
using namespace std;
int &get(int *array, int index)
{
cout << "index[" << index << "] = " << array[index] << " ";
if(0 == index % 3)
cout << endl;
return array[index];
}
int main()
{
int ia[10];
for(int i = 0; i != 10; ++i)
{
//get(ia, i) 返回是一个数组的引用ia[i] 相当于 ia[i] = i;
get(ia, i) = i;
}
cout << endl;
for(int j = 0; j != 10; ++j)
{
cout << "ia[" << j << "] = " << ia[j] << " ";
if(0 == j % 3)
cout << endl;
}
return 0;
}
运行结果:
练习6.33:编写一个递归函数,输出vector对象的内容。
【出题思路】
函数的递归分为直接递归和间接递归。编写递归函数的关键是确定递归规律和递归终止条件。
【解答】
满足题意的程序如下所示:
#include <iostream>
#include <vector>
#include <time.h>
using namespace std;
//递归函数输出vector<int>的内容
void print(vector<int> vInt, unsigned index)
{
unsigned long sz = vInt.size();
if(!vInt.empty() && index < sz)
{
cout << "vInt[" << index << "] = " << vInt[index] << endl;
print(vInt, index + 1);
}
}
void print(vector<int> vInt)
{
for(auto val: vInt)
{
cout << "val = " << val << endl;
}
}
int main()
{
vector<int> v = {1, 3, 5, 7, 9, 11, 13, 15};
print(v, 0);
print(v);
return 0;
}
运行结果:
练习6.34:如果factorial函数的停止条件如下所示,将发生什么情况?
if(val != 0)
【出题思路】
理解递归函数的执行逻辑。
【解答】
因为原文中递归函数的参数类型是int,所以理论上用户传入factorial函数的参数可以是负数。按照原程序的逻辑,参数为负数时函数的返回值是1。如果修改递归函数的停止条件,则当参数的值为负时,会依次递归下去,执行连续乘法操作直至溢出。因此,不能把if语句的条件改成上述形式。
练习6.35:在调用factorial函数时,为什么我们传入的值是val-1而非val--?
【出题思路】
回顾后置递减运算符参与表达式运算时的求值规律。
【解答】
如果把val-1改成val--,则出现一种我们不期望看到的情况,即变量的递减操作与读取变量值的操作共存于同一条表达式中,这时有可能产生未定义的值。
练习6.36:编写一个函数的声明,使其返回数组的引用并且该数组包含10个string对象。不要使用尾置返回类型、decltype或者类型别名。
【出题思路】
因为数组不能被拷贝,所以函数不能直接返回数组,但是可以返回数组的指针或引用。
【解答】
要想使函数返回数组的引用并且该数组包含10个string对象,可以按照如下所示的形式声明函数:
string (&func())[10];
上述声明的含义是:func()表示调用func函数无须任何实参,(&func( ))表示函数的返回结果是一个引用,(&func( ))[10]表示引用的对象是一个维度为10的数组,string (&func( ))[10]表示数组的元素是string对象。
练习6.37:为上一题的函数再写三个声明,一个使用类型别名,另一个使用尾置返回类型,最后一个使用decltype关键字。你觉得哪种形式最好?为什么?
【出题思路】
直接编写返回数组引用的函数比较烦琐且不易理解,使用类型别名、尾置返回类型和decltype关键字都可以简化这一过程。
【解答】
使用类型别名:
typedef string arr[10];
arr& func();
使用尾置返回类型:
auto func()->string(&)[10];
使用decltype关键字:
string str[10];
decltype(str) &func();
练习6.38:修改arrPtr函数,使其返回数组的引用。
【出题思路】
数组也是一个对象,所以可以定义数组的引用。要想为数组的引用赋值,只需要把数组名赋给该引用即可。
【解答】
满足题意的arrPtr函数是:
#include <iostream>
#include <string.h>
using namespace std;
int odd[] = {1, 3, 5, 7, 9};
int even[] = {0, 2, 4, 6, 8};
//返回一个引用,该引用所引的对象是一个含有5个整数的数组
decltype(odd) &arrPtr(int i)
{
return (i % 2) ? odd : even;
}
int main()
{
for(auto od: odd)
cout << "odd=====" << od << endl;
int *a = arrPtr(1);
for(int i = 0; i < 5; ++i)
a[i] = i * 3;
for(int j = 0; j < 5; ++j)
cout << "a[" << j << "] = " << a[j] << endl;
return 0;
}
运行结果: