[C++ primer] 第六章习题答案

6.1

实参时形参的初始值。
形参是定义在函数参数列表中的局部变量, 等待被实参初始化。
实参是调用函数时给形参赋予的初始值。

6.2

(a)返回类型不匹配
(b)函数没有标明返回类型
(c)形参命名冲突
(d)函数体必须有{}

6.4

#include <iostream>
#include <stdexcept>
#include <vector>


using namespace std;

int func(int num)
{
	if (num == 1 || num == 0)
		return 1;
	else
		return num * func(num - 1);
}

int main()
{
	int a;
	cin >> a;
	cin.ignore();
	int res = func(a);
	cout << res << endl;
}

6.5

#include <iostream>
#include <stdexcept>
#include <vector>


using namespace std;

int func(int num)
{
	return abs(num);
}

int main()
{
	int a;
	cin >> a;
	cin.ignore();
	int res = func(a);
	cout << res << endl;
}

6.6

形参是定义在函数参数列表中的局部变量,等待被实参初始化
局部变量仅在函数的作用域内可见,还会隐藏在外层作用域中同名的其他所有声明中。形参和函数体内部定义的变量统称为局部变量。(仅在块内有效)
局部静态变量在函数的执行路径第一次经过对象定义语句时初始化,直到程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响。可以将局部变量定义成static类型获得。

6.8

建议变量和函数皆在头文件中声明,在源文件中定义。
函数声明时不需要函数体,用一个分号代替即可。形参也可以省略(建议保留,增加可读性)

6.10

每次调用函数时都会重新创建它的形参,并用传入的实参对形参进行初始化。
当实参拷贝给形参是,他们之间还是独立的对象,此过程被称为值传递。(改变形参不会影响到实参)
当形参是引用类型时,它将绑定到对应的实参上。被称为引用传递。
在c++中 建议使用引用类型的形参替代指针。

#include <iostream>
#include <stdexcept>
#include <vector>


using namespace std;

void func(int *p, int *q)
{
	int temp = *p;
	*p = *q;
	*q = temp;
}

int main()
{
	int a = 1, b = 2;
	func(&a, &b);
	cout << a << b << endl;
}

6.12

如果函数无须改变引用参数的值,最好将其声明为常量引用
可以使用引用避免拷贝

#include <iostream>
#include <stdexcept>
#include <vector>


using namespace std;

void func(int &p, int &q)
{
	int temp = p;
	p = q;
	q = temp;
}

int main()
{
	int a = 1, b = 2;
	func(a, b);
	cout << a << b << endl;
}

6.13

void f(T),在传参时,会把T类型的变量整个复制一份,此时对T的修改不会造成实参改变
void f(&T),在传参时,只会传递类型为T的实参的引用,可以对实参进行修改

6.15

find_char函数不应该在函数内部修改s的值,只读不写,因此该参数应该使用常量引用。
需要在函数内部修改occurs的值,所以是普通引用
c不是引用类型是根据函数的用途决定的,如果c是引用,则用户在调用find_char(s, ‘o’, ctr)时会出错,因为不能用一个字面值去初始化非常量引用。

当使用实参初始化形参时会忽略掉顶层const。当形参有顶层const时,传给他常量对象或者非常量对象都是可以的
指向常量的指针不能用于修改所指向对象的值(自以为是性)
要想指向常量对象的地址,只能使用指向常量的指针
尽量使用常量引用

6.16

如果实参是const string类型,则会报错——不能把普通引用绑定到const对象上

6.17

#include <iostream>
#include <stdexcept>
#include <vector>
#include <string>


using namespace std;

bool hasUpper(const string &s)
{
	for (string::size_type i = 0; i != s.size(); i++)
	{
		if (isupper(s[i]))
			return true;
	}
	return false;
}

void toLower(string &s)
{
	for (string::size_type i = 0; i != s.size(); i++)
	{
		if (isupper(s[i]))
			s[i] = tolower(s[i]);
	}
}

int main()
{
	string s = "KjdhuhjhksdjhkkkJHKJH";
	cout << hasUpper(s) << endl;
	toLower(s);
	cout << s << endl;
}

6.18

(a) bool compare(const matrix &A, const matrix &B);

(b) vector<int>::iterator change_val(int num, vector<int>::iterator res);

6.19

(a)不合法,calc函数只有一个参数
(b)(c)(d)合法

6.20

当函数不会改变参数值的时候,应该将形参设为常量引用。若其该为常量引用,而我们将其设为普通引用,当函数内部改变其值,将不会报错,引发不必要的错误

6.21

数组形参:
不允许拷贝数组,使用数组时通常会将其转换成指针,——无法以值传递的方式使用数组参数,当我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针

由于数组是以指针的形式传递给函数的,所以函数不知道数组的大小,指针就可能会发生越界问题。有三种方法可以解决这一点问题。

1. 使用标记指定数组长度:要求数组本身包含一个结束标记(比如C风格字符串,函数在遇到空字符停止)。适用于有明显结束标记且标记不会与普通数据混淆的情况
2. 使用标准库规范:传递指向数组首元素和尾后元素的指针。
3. 显式传递一个表示数组大小的形参:专门定义一个表示数组大小的形参。

#include <iostream>
#include <stdexcept>
#include <vector>
#include <string>


using namespace std;



int func(int *p, int val)
{
	return (*p > val) ? *p : val;
}

int main()
{
	int a = 4, b = 7;
	cout << func(&a, b) << endl;
}

6.22

#include <iostream>
#include <stdexcept>
#include <vector>
#include <string>


using namespace std;



void func(int *&p, int *&q)
{
	int *temp = p;
	p = q;
	q = temp;
}

int main()
{
	int a = 4, b = 7;
	int *p = &a, *q = &b;
	func(p, q);
	cout << *p << *q << endl;
}

6.24

不能用传值的方式传递数组。
应该定义为:

void print(const int (&ia)[10])

6.25

使用argv中的实参时,一定要记得可选参数从argv[1]开始,argv[0]保存的是程序的名字。

#include <iostream>
#include <stdexcept>
#include <vector>
#include <string>


using namespace std;



int main(int argc, char **argv)
{
	string res = "";

	for (int i = 1; i < argc; i++)
	{
		res += argv[i];
		res += " ";
	}

	cout << res << endl;
	return 0;
}

6.27

如果函数的实参数量未知但所有实参的类型都相同:使用initializer_list类型的形参
initializer_list中的元素永远是const类型
含有initializer_list形参的函数也可以同时拥有其他形参

省略符形参是为了便于C++程序访问某些特殊的C代码设置的。应该仅仅用于C和C++通用的类型。(注意:大多数类类型的对象在传递给省略符形参时都无法正确拷贝)
省略符形参只能出现在形参列表的最后一个位置
省略符形参所对应的实参无需类型检查

#include <iostream>
#include <stdexcept>
#include <vector>
#include <string>


using namespace std;

int func(initializer_list<int> num)
{
	int sum = 0;
	for (auto i = num.begin(); i != num.end(); i++)
	{
		sum += *i;
	}
	return sum;
}



int main(int argc, char **argv)
{
	cout << func({ 1, 2, 3, 1 }) << endl;

}

6.28

const string &

6.29

在范围for循环中使用initializer_list对象时,应该将循环变量声明成引用类型。这是因为考虑到复制实参的问题,声明成引用,可以避免复制实参,提高了效率。

6.30


return语句终止当前正在执行的函数并将控制权返回到调用该函数的地方
return语句的返回值类型必须与函数的返回类型相同,或者能隐式得转换成函数的返回类型

函数返回的值用于初始化调用点的一个临时量,该临时量是函数调用的结果
如果函数返回引用,则该引用仅是它所引对象的一个别名

不要返回局部对象的引用或指针(函数完成后,它所占用的存储空间也随之被释放掉)
调用运算符的优先级和点运算符、箭头运算符的优先级一样

函数的返回类型决定函数调用是否是左值:引用返回左值,其它类型返回右值
如果返回类型是常量引用,则不能对返回类型进行赋值

函数可以返回花括号包围的值的列表,如果列表为空,临时量执行值初始化,否则返回的值由函数的返回类型决定

允许main函数没有return 语句直接结束。(编译器将隐式地插入一个return 0)

递归时,main函数不能调用自己


严重性	代码	说明	
错误	C2561	“func”: 函数必须返回值	

6.31

返回局部引用时无效,返回局部定义的常量引用无效。要确保返回的引用有效,就要确定引用所返回的是在函数之前已经存在的某个对象。

6.32

合法,其功能是从0递增,初始化一个数组。

6.33

#include <iostream>
#include <stdexcept>
#include <vector>
#include <string>


using namespace std;

void func(vector<int>::const_iterator beg, vector<int>::const_iterator end)
{
	if (beg != end)
	{
		cout << *beg++ << endl;
		func(beg, end);
	}

}



int main(int argc, char **argv)
{
	vector<int> temp = { 0, 1, 2, 3 };
	func(temp.begin(), temp.end());

}

6.34

如果传入的val < 0,则递归永远不会停止

6.35

val – 返回的是val的值,相当于又把val当作参数传递,递归将永远不会停止,并且第一次递归不断重复执行。

6.36


因为数组不能被拷贝,所以函数不能返回数组,不过函数可以返回数组的指针或引用。可以使用类型别名简化过程

  1. 声明一个返回数组指针的函数:数组的维度跟在形参列表之后
Type (*function(parameter_list))[dimension]
  1. 使用尾置返回类型:任何函数的定义都可以使用尾置返回,但是这种形式对于返回类型比较复杂的函数最有效。
auto func(int i) -> int(*)[10]//返回一个指针,该指针指向含有10个整数的数组
  1. 使用decltype:已知函数的返回值时,可以使用关键字decltype表示返回类型为指针

string (&func(string (&str)[10]))[10]

6.37

(1) typedef string arr[10];
	using arr = string[10];//c++11
	arr& func(arr& str);
(2) auto func(string (&str)[10]) -> string(&)[10];
(3) string temp[10];
decltype(temp)& func(decltype(temp)& str);

6.39


如果同一作用域内几个函数名字相同但形参列表不同,我们称之为重载函数。
main函数不能重载, 不允许两个函数除了返回类型外其他所有要素都相同
顶层const不影响传入函数的对象,一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开
如果形参是某种类型的指针或引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载

调用重载函数时有三种可能的结果:

  1. 编译器找到一个与实参最佳匹配的函数,生成调用该函数的代码
  2. 找不到任何一个函数与调用的实参匹配,此时编译器发出无匹配的错误信息
  3. 有多余一个函数可以匹配,但每一个都不是明显的最佳选择,此时也将发生错误 (二义性调用)

不要把函数声明放在局部作用域内


(a)不合法。属于顶层const形参,第二行是重复声明,与第一行含义相同。
(b)不合法。 不允许两个函数除了返回类型外其他所有要素都相同
(c)合法。参数类型不同,属于函数重载。

6.40


默认实参
调用含有默认实参的函数时,可以包含该实参,也可以省略该实参。
一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值
使用默认实参时,只要在调用函数时省略该形参就可以了。

在给定的作用域中一个形参只能被赋予一次默认实参,函数的后续声明只能为之前没有默认值的形参添加默认实参,而且该形参右侧的所有形参必须有默认值

局部变量不能作为默认实参


(a)正确
(b)错误,一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值

6.41

(a)非法,第一个参数无默认值,应该初始化赋值。
(b)(c)合法

6.42

#include <iostream>
#include <stdexcept>
#include <vector>
#include <string>


using namespace std;

string make_plural(size_t ctr, const string &word, const string &ending = "s")
{
	return (ctr > 1) ? word + ending : word;
}



int main(int argc, char **argv)
{
	string str1 = "hello";
	string str2 = "world";

	cout << make_plural(2, str1) << endl;
	cout << make_plural(1, str2) << endl;

}

6.43


内联函数
内联函数可以避免函数调用时的开销,在函数返回类型前面加上关键字inline就可以将其声明为内联函数
内联机制用于优化规模较小,流程直接,频繁调用的函数,很多编译器都不支持内联递归函数



constexpr函数
constexpr函数是指能用于常量表达式的函数。其中函数的返回类型及所有形参的类型都得是字面值类型,而且函数体中必须有且只有一条return 语句。
constexpr函数的返回值不一定是一个常量,跟调用类型有关
nline函数和constexpr函数可以在函数中多次定义,但是通常将其定义在头文件中。(多个定义必须完全一致)


(a)内联函数通常定义在头文件中
(b)普通函数的声明,一般也放在头文件中

6.44

inline bool isShorter(const string &s1, const string &s2)
{
	return s1.size() < s2.size();
}

6.45

练习题中的函数短小的,应该被定义成内联函数。改写为内联函数只需要在函数声明前加inline关键字就可以。

6.46

不可以,参数是string类型,不是字面值。

6.47


程序有时会包含一些用于调试的代码,但是这些代码只在开发程序时使用,当应用程序编写完成准备发布时,要先屏蔽掉调试代码,此时用到两种预处理功能:assert和NDEBUG

assert预处理宏:assert(expr):包含一个表达式,expr为真时,assert什么也不做,为假时输出信息并终止程序。包含在cassert头文件中。通常用于检查不能发生的条件

NDEBUG预处理变量:assert依赖于一个NDEBUG的预处理变量的状态,如果定义了NDEBUG,assert什么也不做,默认状态下NDEBUG是未定义的。编译器也可以预先定义该变量。
可以使用#define NDEBUG来关闭调试状态

也可以使用NDEBUG编写自己的条件调试代码。如果NDEBUG未定义,将执行#ifndef到#endif之间的代码,如果定义了NDEBUG,这些代码将被忽略。

用于调试:
_ func _ :一个静态数组,存放函数的名字
_ FILE _ :存放文件名的字符串字面值
_ LINE _ :存放当前行号的整形字面值
_ TIME _ :存放文件编译时间的字符串字面值
_ DATE _ :存放文件编译日期的字符串字面值


#include <iostream>
#include <stdexcept>
#include <vector>
#include <string>

using namespace std;
#define NDEBUG

void func(vector<int>::const_iterator beg, vector<int>::const_iterator end)
{
	#ifndef NDEBUG
	cerr << __func__ << endl;
#endif // !

	if (beg != end)
	{
		cout << *beg++ << endl;
		func(beg, end);
	}

}

int main(int argc, char **argv)
{
	vector<int> temp = { 0, 1, 2, 3 };
	func(temp.begin(), temp.end());

}

6.48

不合理,函数的意义是让用户进行输入,直到输入的字符串是sought是停止。因此assert (cin)一直为真,这条语句也就没有意义。可以改为:assert ( s != sought)

6.49


函数匹配的第一步是选定本次调用对应的重载函数集。集合中的函数被称为候选函数,候选函数有两个特征:一是与被调用的函数同名,二是其声明在调用点可见。
第二步是考察本次调用提供的实参,然后从候选函数中选出能被这组实参调用的函数,称为可行函数。可行函数有两个特征:一是形参数量与本次提供的实参数量相同,二是每个实参的类型与对应形参类型相同,或能转换成形参的类型。
第三步是考察实参类型是否与形参匹配。
(如果没找到可行函数,编译器将报告无匹配函数的错误)
实参类型与形参类型越接近,匹配的越好
注意避免二义性调用


6.50

(a)不合法,有二义性调用
(b)合法,void f(int)
(c)合法, void f(int, int)
(d)合法,void f(double, double = 3.14)

6.52


为了确定最佳匹配,编译器将实参类型到形参类型的转换划分为几个等级,具体排序:

  1. 精确匹配
  2. 通过const转换实现的匹配
  3. 通过类型提升实现的匹配
  4. 通过算术类型转换或者指针转换实现的匹配
  5. 通过类类型转换实现的匹配

函数匹配和const形参:

实参是const -> 形参是const的函数

实参非const -> 形参非const的函数


(a)类型提升
(b)算术类型转换

6.53

(a)合法,会根据传入的实参是否时const类型决定使用哪个函数。
(b)合法,会根据传入的实参是否时const类型决定使用哪个函数。
(c)非法,与第一行含义相同,属于重复定义。

6.54


要想声明一个可以指向函数的指针,只需要用指针替换函数名即可。
当我们把函数名作为一个值使用时,该函数自动地转换成指针。
此外,还可以直接使用只想函数地指针调用该函数,无须提前解引用指针。
在指向不同函数类型地指针间不存在转换规则。但可以为指针赋一个nullptr或0,表示指针没有指向任何一个函数

当我们使用重载函数时,上下文必须清晰地界定到底应该选用哪个函数。编译器通过指针类型决定选用哪个函数,指针类型必须与重载函数中地某一个精确匹配。

形参不可以是函数类型,但形参可以是指向函数的指针。(会将函数形参转换成指向函数的指针)

虽然不能返回一个函数,但是可以返回一个指向函数的指针。


using func1 = int(int, int);
vector<func1*> fvec1;

int func(int, int);
typedef decltype(func) *func2;
vector<func2> fvec2;

using func3 = int(*)(int, int);
vector<func3> fvec3;

6.56

#include <iostream>
#include <stdexcept>
#include <vector>
#include <string>


using namespace std;
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return (a - b); }
int multiply(int a, int b) { return a * b; }
int divide(int a, int b) { return b != 0 ? a / b : 0; }
int main()
{
	typedef int(*p) (int, int);
	vector<p> vec{ add, sub,multiply, divide };
	for (auto f : vec) {
		cout << f(1, 1) << endl;
	}
	getchar();
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值