答案目录

第6章 函数

练习6.1

形参:函数定义的时的参数—也就是无赋值的变量(作用是说明参数的类型)

实参:调用函数时使用的参数—也就是有赋值的变量(函数实际操作的对象)

练习6.2

1
2
3
4
5
6
int f()
{
string s; // 定义函数f的返回值是int,但实际却返回的string类型,错误。应改为:string f()
// ...
return s;
}
1
f2 (int i) {/* ... */}            // 未定义函数的返回值。改为void f2(int i)
1
int calc (int v1, int v1)  { /* ... */ }                // 形参命名冲突。
1
double square(double x)  return x * x;        //函数体应该用{}

练习6.4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int jiecheng(int num)
{
int result = 1;
if (num == 1 || num == 0) {
return 1;
}
else {
return num * jiecheng(num - 1);//递归调用
}

}
int main()
{
int k = 5, result;
result = jiecheng(k);
cout << result << endl;
}

练习6.5

1
2
3
4
int abs(int num)
{
return num > 0 ? num : -num;
}

练习6.6

形参,指的是函数声明时的函数列表中定义的局部变量,在整个函数中起作用。
局部变量,指的是定义在块中的变量,只在块中起作用。
局部静态变量:在函数体内定义的静态变量,但是当函数结束时不会被销毁,直到整个程序结束之后才会销毁。

练习6.7

1
2
3
4
5
6
7
8
9
10
11
12
int leijia(int val)
{
static int cnt = 0;
int result = 1;
cout << "The function is call " << ++cnt << "times.";
if (val == 1 || val == 0) {
return 1;
}
else {
return val *leijia(val - 1);
}
}

练习6.10

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void swap_pointer(int* a, int* b)
{
int tmp;

tmp = *a;
*a = *b;
*b = tmp;
}

int main()
{
int x = 5, y = 10;

swap_pointer(&x, &y);

cout << "x = " << x << ", y = " << y << endl;
}

练习6.13

前者以传值方式传入参数,不能修改实参。后者以传址的方式传入参数,可以修改实参。

练习6.14

形参是否是引用类型应该根据是否需要修改对应实参的值来看,如果需要修改,则应是引用类型

练习6.15

原因:因为s字符串是不能被函数所修改的,所以是常量引用。

c可能是一个临时变量,所以不需要使用引用类型,也无需函数加以修改其本身。

如果令occurs为常量引用,则输出occurs为0(不能加以修改),而s可能会在程序中被修改。

练习6.16

1
2
3
bool is_empty (string& s) { return s.empty(); }
// 因为函数不会改变s的值,因此传递的参数应为cons string& s,否则实参为字符常量或const字符串都无法正常使用该函数,应改为:
bool is_empty (const string& s) { return s.empty(); }

练习6.17

判断是否有大写字母应该使用const 引用类型,因为不会改变该参数的值。而转换为大写字母不能使用const类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bool is_have_upper (const string& s)
{
for (int i = 0; i < s.size(); i++) {
if (isupper(s[i])) {
return true;
}
}
return false;
}

void to_upper (string& s)
{
for (int i = 0; i < s.size(); i++) {
s[i] = toupper(s[i]);
}
}

练习6.18

1
bool compare (const matrix& a, const matrix& b);
1
vector<int>::iterator change_val (int val, vector<int>::iterator iter);

练习6.19

1
2
3
4
5
6
7
8
9
double calc(double);
int count (const string&, char);
int sum (vector<int>::iterator, vector<int>::iterator, int);
vector<int> vec(10);

(a) calc (23.4, 55.1); // 不合法,calc函数只有一个参数。
(b) count ("abcda", 'a'); // 合法
(c) calc (66); // 合法,但会有警告
(d) sum (vec.begin(), vec.end(), 3.8); // 合法,最后一个参数是int类型,传double会截断

练习6.20

无需在函数中改变的参数应该为常量引用,普通情况下,可能不会出错,但是有些不小心改变了不应该改变的参数就会引发不必要的错误

练习6.21

1
2
3
4
int compare(int x, const int *y)
{
return (x > *y) ? x : *y;
}

练习6.22

1
2
3
4
5
6
void swap_pointer(int* &x, int* &y)
{
int *temp = x;
x = y;
y = temp;
}

练习6.23

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void print (const int *p)
{
if (p != nullptr) {
cout << *p << endl;
}
}

void print (const char *p)
{
if (*p) {
while (*p)
cout << *p ++ << " ";
}
cout << endl;
}

void print (const int *beg, const int *end)
{
while (beg != end) {
cout << *beg ++ << " ";
}
cout << endl;
}

void print (const int a[], size_t size)
{
for (size_t i = 0; i < size; i++) {
cout << a[i] << " ";
}
cout << endl;
}
// i = 0, 如果对指针进行判空,i将不会输出。因为0与空指针对编译器来说一样。

练习6.24

此题明显以传值的方式传递数组,是错误的

数组做参数时,会退化为指针,因此函数中的const int a[10]等同于const int *a,并且长度是不确定的,传a[3]或a[255]是没有区别的

因此如果我们要确定传递一个长度为10的数组,应该这样定义:

1
void print (const int (&a)[10]);

练习6.25,6.26

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main(int argc, char **argv)
{
if (argc != 3) {
cout << "Usage: should have two arguments." << endl;
}

string str;

for (int i = 1; i < argc; i++) {
str += string(argv[i]) + " ";
}
cout << str << endl;
return 0;
}

练习6.27

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int sum(initializer_list<int> const il)
{
int sum = 0;
for (auto i : il) {
sum += i;
}
return sum;
}

int main()
{
auto il = { 1, 2, 3, 4, 5, 6, 7, 8 };
cout << sum(il) << endl;
}

练习6.28

该函数中的elem应该是const string& 类型

练习6.29

当循环控制变量是基本类型是,可以不声明为引用,否则还应该声明成引用,因为initializer_list对象可能是各种类型,有可能是自定义类型或者string类型。此时使用引用可以避免拷贝。

练习6.30

运行该函数,现在使用的MinGW编译器报错误1,不报错误2,

错误 C2561 “str_subrange”: 函数必须返回值 C++ Prime D:\Visio Studio project\C++ Prime\源.cpp 21

练习6.31

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

练习6.32

1
2
3
4
5
6
7
8
int& get (int *array, int index) { return array[index]; }
int main()
{
int ia[10];
for (int i = 0; i != 10; ++i)
get(ia, i) = i;
}
// 该函数合法,其功能是从0递增,初始化一个数组。本题中是将一个长度未10的数组初始化未0--9。

练习6.33

1
2
3
4
5
6
7
void print_vector(vector<int>::const_iterator beg, vector<int>::const_iterator end)
{
if (beg != end) {
cout << *beg << " ";
print_vector(++ beg, end);
}
}

练习6.34

如果传入的参数为负数,那么将永远不会停止

练习6.35

–在后是先用后减,达不到我们减一的目的。结果就是一直使用val循环

练习6.36

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

练习6.37

1
2
3
4
5
6
7
8
9
10
// 类型别名
using arrStr = string[10];
arrStr& func( arrStr& arr_str );

// 尾置返回类型
auto func (string (&str)[10] ) -> string(&)[10];

// decltype 关键字
string str[10];
decltype(str) &func ( decltype(str)& );

练习6.38

1
2
3
4
decltype(odd) &arrPtr (int i)
{
return (i % 2) ? odd : even;
}

练习6.39

1
2
3
4
5
6
7
8
9
10
11
12
-- a
int calc (int, int);
int calc (const int, const int);
// 属于顶层const形参,第二行是重复声明,与第一行含义相同。但是c++中允许函数重复声明。因此这两行代码合法,编译器不会报错。
-- b
int get ();
double get();
// 非法,只有返回值不同不能算函数重载。
-- c
int *reset (int *);
double *reset (double *);
// 合法,参数类型不同,属于函数重载。

练习6.40

1
2
3
4
-- a
int ff ( int a, int b = 0, int c = 0 ); // 无错误
-- b
char *init (int ht = 24, int wd, char bckgrnd); // 错误,如果要为参数加默认值,第一个参数加了,后面的都要加。可以把需要加默认值的参数放在最后。

练习6.41

1
2
3
4
char *init (int ht, int wd = 80, char bckgrnd = ' ');
(a) init (); // 非法,第一个参数无默认值,应该初始化赋值。
(b) init (24, 10); // 合法
(c) init (14, '*'); // 合法,但与初衷不符,初衷应是让ht = 14, bakgrnd = *。但实际是ht = 14, wd = ‘*’。

练习6.43

1
2
(a) inline bool eq (const BigInt&, const BigInt& ) { ... }        // 内联函数一般放在头文件中
(b) void putValues (int *arr, int size); // 普通函数的声明,一般也放在头文件中

练习6.44

1
2
3
4
inline bool isShorter (const string& str1, const string& str2)
{
return str1.size() < str2.size();
}

练习6.45

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

练习6.46

不能,因为isShorter函数中传入的参数不是字面值类型,str1.size() < str2.size()返回的也不是字面值类型

练习6.47

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void print_vec(vector<int>& ivec)
{
#ifndef NDEBUG
cout << "vector's size is " << ivec.size() << endl;
#endif // NDEBUG

if (!ivec.empty()) {
auto tmp = ivec.back();
ivec.pop_back();
print_vec(ivec);
cout << tmp << " ";
}
cout << endl;
}

练习6.48

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

练习6.49

重载函数集合中的函数称为候选函数,候选函数具备两个特征:(1)与被调用的函数同名;(2)其声明在调用点可见。
从候选函数中选出能被这组实参调用的函数成为可行函数,可行函数也有两个特征:(1)其形参数量与本次调用提供的实参数量相等;(2)每个实参的类型与对应的形参类型相同,或是能转换成形参的类型

练习6.50

1
2
3
4
(a) f (2.56, 42)        // 非法,因为实参类型是double, int,没有可匹配的函数。如果不是重载函数,只有一个声明f(double, double),则该语句合法。只有在重载时时非法的,要严格执行参数类型匹配。
(b) f (42) // 调用 f (int)
(c) f (42, 0) // 调用 f (int, int)
(d) f (2.56, 3.14) // 调用 f (double, double = 3.14)

练习6.52

1
2
(a) manip ('a', 'z');                 // 类型提升
(b) manip (55.4, dobj); // 算术类型转换

练习6.53

1
2
3
4
5
6
(a) int calc (int &, int &);
int calc (const int&, const int&); // 合法,会根据传入的实参是否是const类型决定使用哪个函数。
(b) int calc (char*, char*);
int calc (const char*, const char*); // 合法,会根据传入的实参是否是const类型决定使用哪个函数。
(c) int calc (char*, char*);
int calc (char* const, char* const); // 非法,与第一行含义相同,属于重复定义。

练习6.54

1
2
3
4
5
6
7
8
9
using func1 = int (int, int);
vector<func1*> fvec;

int func (int a, int b);
typedef decltype(func) * func2;
vector<func2> fvec;

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

练习6.55,6.56

1
2
3
4
5
6
7
8
9
10
11
12
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(2, 4) << endl;
}
}