6.1节练习
练习6.1
问题:实参和形参区别是什么?
实参是相对于形参所讲的。在函数之内定义的变量,在函数中使用的就是形参,形参会随着被调函数的执行而被声明,又会随着函数执行结束而销毁。实参就是传递给函数的值,是函数调用的实际值。
练习6.2
问题:请指出下列函数哪个有错误,为什么?应该如何修改这些错误呢?
(a) int f() {
string s;
// ...
return s;
}
(b) f2(int i) { /* ... */ }
(c) int calc(int v1, int v1) { /* ... */ }
(d) double square (double x) return x * x;
修改:
a)返回的是一个string类型,所以 函数返回类型不对
string f(){...}
b)没有注明函数的返回值
void f2(int i)
c)两个形参的不能同名
int calc(int v1,int v2)
d)函数体应该写在{}中
double square(double x) {return x*x;}
练习6.3
编写自己的fact函数,检查是否正确
这里使用到了递归
#include <iostream>
using std::cout;
using std::cin;
double fact(double v){
if(v!=1){
return v*fact(v-1);
}
return 1;
}
int main() {
double a;
cin>>a;
cout<<fact(a);
return 0;
}
练习6.4 练习6.5
略
6.1.1节练习
练习6.6
问题: 说明形参、局部变量以及局部静态变量的区别。编写一个函数,同时达到这三种形式
#include <iostream>
using namespace std;
int out(int i){ //i属于形参
static int j=0; //j属于局部静态变量,i属于局部变量,除了函数内以外的其他地方无法访问这个变量
cout<<"i:"<<++i<<"j:"<<++j<<endl;
}
int main() {
for(int i=0;i<5;i++){ //i也属于局部变量
out(i);
}
return 0;
}
形参:就是函数形参列表中定义的变量,用来在函数中执行,在函数开始时被创建,函数结束时被销毁的变量。
局部变量:形参就属于一个局部变量,在for语句中定义的int i也属于局部变量,虽然这两种变量都是i,但是它们只能在各自的作用域内被访问。
局部静态变量:虽然它是一个局部变量,但是它在程序从该代码块中运行出来的时候并不对其进行销毁,如果之后程序再次调用该代码块,会在原先的基础上继续调用
练习6.7
问题:编写一个函数,当它第一次被调用时返回0,以后每次被调用返回值加1。
通过局部静态变量解决
int out(){
static int i;
return i++;
}
6.1.2节练习
练习6.8
问题:编写一个名为Chapter6.h 的头文件,令其包含6.1节练习中的函数声明。
int f();
int f2(int i);
int calc(int v1,int v2);
double square (double x);
6.2.1节练习
练习6.10
问题:编写一个函数,使用指针形参交换两个整数的值。 在代码中调用该函数并输出交换后的结果,以此验证函数的正确性。
#include <iostream>
using std::cout;
void swap(int *a,int *b){
int temp;
temp=*a;
*a=*b;
*b=temp;
}
int main() {
int i=3,j=4;
swap(&i,&j);
cout<<"i :"<<i<<"j:"<<j;
return 0;
}
6.2.2节练习
练习6.11
问题:编写并验证你自己的reset函数,使其作用于引用类型的参数。注:reset即置0。
#include <iostream>
using std::cout;
void reset(int &m){
m=0;
}
int main() {
int ss=256;
reset(ss);
cout<<ss;
return 0;
}
练习6.12
问题:改写6.2.1节练习中的程序,使其引用而非指针交换两个整数的值。你觉得哪种方法更易于使用呢?为什么?
#include <iostream>
using std::cout;
void swap(int &a,int &b){
int emp;
emp=a;
a=b;
b=emp;
}
int main() {
int a{3},b{4};
swap(a,b);
cout<<"a:"<<a<<"b:"<<b;
return 0;
}
我认为引用的方法更容易使用。它是变量的别名,可以直接对变量进行操作。而省去了像指针那样繁琐的解引用操作。
练习6.13
问题:假设T
是某种类型的名字,说明以下两个函数声明的区别: 一个是void f(T)
, 另一个是void f(&T)
。
答:第一个函数 void f(T) 是传入了一个拷贝值。它通过实参拷贝给形参。函数对形参进行操作,并不会影响到实参。
第二个函数 void f(&T) 传入了一个引用值。它通过对实参的别名进行操作,会直接影响到实参。
练习6.14
问题:举一个形参应该是引用的例子,再举一个形参不应该是引用的例子。
答:
形参是引用的例子:例如交换两个整数的函数,形参应该是引用
形参不应该是引用的例子:当实参的值是右值时,形参不能为引用类型
练习6.15
问题:说明find_char
函数中的三个形参为什么是现在的类型,特别说明为什么s
是常量引用而occurs
是普通引用? 为什么s
和occurs
是引用类型而c
不是? 如果令s
是普通引用会发生什么情况? 如果令occurs
是常量引用会发生什么情况
答:find_char函数的目的是找到字符串中某个字母在哪里出现。当然 s就是被检测的字符串,所以是string;而需要被检测的c是字符类型,就是char类型;最后输出这个c在字符串中出现了几次,因为该值是个unsigned的值,最后就选了string::size_type
s之所以是常量引用,是因为不希望对s的值进行改变。而occurs是普通引用,是因为它要用来记录字符出现的次数,所以可能会对其修改。
s和occurs是引用类型,s是引用类型为了提升效率,避免了大量的拷贝,occurs是引用类型是它隐式的返回出现次数,这解决了函数一次不能返回多个值的弊端,而c只是用来记录是哪个字符。
如果令s是普通引用,可能会导致s的值被修改。
如果occurs是常量,则会在函数中创建一个名为occurs的形参,这个值不会被返回。
6.2.3节练习
练习6.16
下面的这个函数虽然合法,但是不算特别有用。指出它的局限性并设法改善。
bool is_empty(string& s) { return s.empty(); }
答:传入了一个string引用。为了不对string对象的值产生影响,我们或许可以使用常量引用
bool is_empty(const string& s) { return s.empty(); }
❌:局限没找对,这个只能传入一个string对象却不能传入一个字符串常量或者字符串字面值
练习6.17
问题:
编写一个函数,判断string
对象中是否含有大写字母。 编写另一个函数,把string
对象全部改写成小写形式。 在这两个函数中你使用的形参类型相同吗?为什么?
答:不同;
第一个函数,并不需要对string对象进行改变。所以可以使用常量引用,这样更多类型都可以在函数中使用,比如字符串字面值、还有字符串常量。
第二个函数,因为需要将对象改为小写。所以我选择普通的引用。
练习6.18
问题:
为下面的函数编写函数声明,从给定的名字中推测函数具备的功能。
- (a) 名为
compare
的函数,返回布尔值,两个参数都是matrix
类的引用。 - (b) 名为
change_val
的函数,返回vector
的迭代器,有两个参数:一个是int
,另一个是vector
的迭代器
bool compare(matrix &,matrix &);
vector<int>::iterator change_val(int ,vector<int>::iterator);
练习6.19
假定有如下声明,判断哪个调用合法、哪个调用不合法。对于不合法的函数调用,说明原因
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);
(b) count("abcda",'a');
(c) calc(66);
(d) sum(vec.begin(), vec.end(), 3.8);
答:
a) 不合法,函数形参列表只有一个值,但是这里给了两个
b)合法
c)合法
d)不合法,最后一项需要一个int值,但是给进去了一个浮点数 ❌
d是合法的,虽然函数声明了int ,但是这里可以通过强制转换。所以其实最终输入的是截取掉浮点的数字
练习6.20
引用形参什么时候应该是常量引用?如果形参应该是常量引用,而我们将其设为了普通引用,会发生什么情况?
当被引用的值不被改变时,是常量引用。被引用的值本身就是常量或者是字面值
如果被引用值本身是常量或是字面值(一个常量实参),而此时用了普通引用,则会报错。
6.2.4节练习
练习6.21
问题:编写一个函数,令其接受两个参数:一个是int
型的数,另一个是int
指针。 函数比较int
的值和指针所指的值,返回较大的那个。 在该函数中指针的类型应该是什么?
答:指针类型应该是 指向常量的指针
#include <iostream>
using std::cout;
int compare(int a,const int *b){
return (a>*b)?a:*b;
}
int main() {
int a=1;
const int b=2;
cout<<compare(a,&b);
return 0;
}
练习6.22
问题:编写一个函数,令其交换两个int
指针。
#include <iostream>
using std::cout;
void swap(int *&m,int *&n){ //对指针的引用
int *temp;
temp=m;
m=n;
n=temp;
}
int main() {
int a=3;
int b=5;
int *c=&a;
int *d=&b;
cout<<"*c:"<<*c<<"*d:"<<*d<<"\n";
swap(c,d);
cout<<"*c:"<<*c<<"*d:"<<*d<<"\n";
return 0;
}
练习6.23
问题:参考本节介绍的几个print
函数,根据理解编写你自己的版本。 依次调用每个函数使其输入下面定义的i
和j
:
int i = 0, j[2] = { 0, 1 };
#include <iostream>
using std::begin;
using std::end;
using std::cout;
using std::endl;
void print(const int * begin,const int * end){
for(auto m=begin;m!=end;m++)
cout<<*m;
cout<<endl;
}
int main() {
int a=0,j[2]={1,2};
print(&a,&a+1);
//cout<<"&a:"<<&a<<"&a+1:"<<&a+1;
print(begin(j),end(j));
return 0;
}
练习6.24
问题:
描述下面这个函数的行为。如果代码中存在问题,请指出并改正。
void print(const int ia[10])
{
for (size_t i = 0; i != 10; ++i)
cout << ia[i] << endl;
}
答:该函数打印出含有10个元素的int数组每个值。从实参到形参,其实传入的是数组第一个元素的指针。
6.2.5节练习
练习6.25&练习6.26
问题:编写一个main
函数,令其接受两个实参。把实参的内容连接成一个string
对象并输出出来。
问题:编写一个程序,使其接受本节所示的选项;输出传递给main
函数的实参内容。
#include <iostream>
using std::string;
using std::cout;
int main(int argc,char **argv) {
string str;
for(int i=1;i<argc;++i){
str+=string(argv[i])+" ";
}
cout<<argc;
cout<<str;
return 0;
}
6.2.6节练习
练习6.27
问题:编写一个函数,它的参数是initializer_list
类型的对象,函数的功能是计算列表中所有元素的和。
#include <iostream>
#include <initializer_list>
using std::initializer_list;
using std::cout;
int sum(initializer_list<int> list){
int su=0;
for(auto iter=list.begin();iter!=list.end();iter++){
su+=*iter;
}
return su;
}
int main() {
int a=1,b=2,c=3;
cout<<"a+b:"<<sum({a,b})<<"\n";
cout<<"a+b+c:"<<sum({a,b,c})<<"\n";
return 0;
}
练习6.28
问题:在error_msg
函数的第二个版本中包含ErrCode
类型的参数,其中循环内的elem
是什么类型?
const string&类型
练习6.29
问题:在范围for
循环中使用initializer_list
对象时,应该将循环控制变量声明成引用类型吗?为什么?
声明成引用或者不声明成引用都可以。
但是要声明成常量类型
6.3.2节练习
练习6.30
问题:编译第200页的str_subrange
函数,看看你的编译器是如何处理函数中的错误的。
略
练习6.31
问题:什么情况下返回的引用无效?什么情况下返回常量的引用无效?
当使用的是局部变量的引用时,返回引用无效。当该常量为局部变量时,返回常量的引用无效。(可以参考202页,const string &mainp())
练习6.32
问题:下面的函数合法吗?如果合法,说明其功能;如果不合法,修改其中的错误并解释原因。
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;
}
返回值时int &,而返回类型时 int;不合法
int &get(int *&array,int index){return array[index];}
❌:理解错误,这里用返回最初的介绍:返回一个值的方式和初始化一个变量或形参的方式完全一样。
所以我们把这里理解成初始化一个变量:int & n(随便一个名称) = array[index] ,完全可以,最终绑定到的还是array,所以这里array前不需要&
改正:合法,
练习6.33
问题:编写一个递归函数,输出vector
对象的内容
#include <iostream>
#include <vector>
using std::vector;
using std::cout;
void print(const vector<int> &vec, int index){
if(index<vec.size()){
cout<<vec[index++]<<"\n";
print(vec,index);
}
return;
}
int main() {
vector<int> ll{1,2,3,4,5,6,33,44,55};
print(ll,0);
return 0;
}
练习6.34
问题:如果factorial
函数的停止条件如下所示,将发生什么?
int factorial(int val)
{
if(val>1)
return factorial(val-1)*val;
return 1;
}
答:如果输入一个负数,则迭代永不会停止
练习6.35
问题:在调用factorial
函数时,为什么我们传入的值是val-1
而非val--
?
如果调用val--,会将factorial(val)*factorial(val)*factorial(val-1)...,会算错。❌
如果调用val--,迭代会变味factorial(val)*factorial(val)*.... 会永远没有出口。无限计算下去。
6.3.3节练习
练习6.36
问题:编写一个函数声明,使其返回数组的引用并且该数组包含10个string
对象。 不用使用尾置返回类型、decltype
或者类型别名。
string (&m)[10] = a;
string (&func(void))[10];//参考上面的包含10个string对象的数组的引用
练习6.37
问题:为上一题的函数再写三个声明,一个使用类型别名,另一个使用尾置返回类型,最后一个使用decltype
关键字。 你觉得哪种形式最好?为什么?
//类型别名
using arrStr = string[10];
arrStr (&func)();
//尾置返回类型
auto func()->string (&)[10]
//decltype
string str[10]={"",10};
decltype(str) &func();
练习6.38
问题:修改arrPtr函数,使其返回数组的引用
decltype(odd) &arrPtr(int i)
{
return (i%2)?odd:even;
}
6.4节练习
练习6.39
问题:说明在下面的每组声明中第二条语句是何含义。 如果有非法的声明,请指出来。
(a) int calc(int, int);
int calc(const int, const int);
(b) int get();
double get();
(c) int *reset(int *);
double *reset(double *);
a)将两个整型常量相加 ❌顶层const不影响传入对象,所以第二个声明和第一个无法区分开
b)获取一个double类型的浮点数,非法。编译器不知道调用哪个
c)重置一个指向double的指针,返回一个指向double 的指针
6.5.1节练习
练习6.40
问题:下面的哪个声明是错误的?为什么?
(a) int ff(int a, int b = 0, int c = 0);
(b) char *init(int ht = 24, int wd, char bckgrnd);
答:因为只能省略尾部实参,所以一眼可以看出,b项给出的实参是没有用的,ht也还是要定义,如果想省略首部实参,必然会引发错误
a)正确
b)错误
练习6.41
问题:下面的哪个调用是非法的?为什么?哪个调用虽然合法但显然与程序员的初衷不符?为什么?
char *init(int ht, int wd = 80, char bckgrnd = ' ');
(a) init();
(b) init(24,10);
(c) init(14,'*');
答:
a)非法,因为第一项不为默认实参,所以不能什么都不写
b)合法,ht=24,第二项不实用默认实参,wd=10
c)合法,第一项ht=14,第二项会强制转换:‘*’= 42,所以wd=42
练习6.42
问题:给make_plural
函数的第二个形参赋予默认实参's', 利用新版本的函数输出单词success和failure的单数和复数形式。
#include <iostream>
using std::string;
using std::cout;
using std::endl;
string make_plural(size_t ctr,const string &word,const string &ending="s"){
return ctr>1? word+ending:word;
}
int main() {
cout<<make_plural(2,"success","es")<<endl;
cout<<make_plural(1,"faliure")<<endl;
return 0;
}
6.5.2节练习
练习6.43
问题:你会把下面的哪个声明和定义放在头文件中?哪个放在源文件中?为什么?
(a) inline bool eq(const BigInt&, const BigInt&) {...}
(b) void putValues(int *arr, int size);
答:
a)头文件中,因为内联函数的多个定义必须完全一致。
b)头文件中,因为它是声明
练习6.44
问题:将6.2.2节的isShorter
函数改写成内联函数。
incline bool isShorter(const string &s1,const string &s2)
{
return s1.size()<s2.size()
}
练习6.45
问题:回顾在前面的练习中你编写的那些函数,它们应该是内联函数吗? 如果是,将它们改写成内联函数;如果不是,说明原因。
练习6.42应该是内联函数,前面直接加incline就好了。这里掌握好内联函数的使用条件:规模较小,流程直接,频繁调用
练习6.46
问题:能把isShorter
函数定义成constexpr
函数吗? 如果能,将它改写成constxpre
函数;如果不能,说明原因。
答:不能,因为constexpr函数的条件是所有的返回值还有形参都是字面值类型。很显然,字符串并不属于字面值类型。所以不能使用。
6.5.3节练习
练习6.47
问题:改写6.3.2节练习中使用递归输出vector
内容的程序,使其有条件地输出与执行过程有关的信息。 例如,每次调用时输出vector
对象的大小。 分别在打开和关闭调试器的情况下编译并执行这个程序。
#include <iostream>
#include<vector>
#define NDEBUG //如果定义了它,则下面的代码不会实行,如果没有定义,才会实行
using std::vector;
using std::cout;
using std::endl;
void print(const vector<int>&v,vector<int>::iterator iter){
if(iter!=v.end()){
cout<<*iter<<endl;
#ifndef NDEBUG
cout<<v.size()<<endl;
#endif
print(v,++iter);
}
return;
}
int main() {
vector<int> v{1,2,3,4,5};
print(v,v.begin());
return 0;
}
练习6.48
问题:说明下面这个循环的含义,它对assert的使用合理吗?
string s;
while (cin >> s && s != sought) { } //空函数体
assert(cin);
答:当程序输入错误时,cin流为0,终止程序执行,assert输出信息。合理?❌
值得探讨,
不合理。从这个程序的意图来看,应该用 assert(s==sought)。
6.6节练习
练习6.49
问题:什么是候选函数?什么是可行函数?
答:候选函数:在一次函数调用对应的重载函数集中的函数。和被调函数同名且在声明在调用点可见。
可行函数:候选函数中能被这组实参调用的函数,称为可行函数。形参与本次提供的实参数量相等且每个实参类型与对应形参类型相同。
练习6.50
问题:已知有第217页对函数f
的声明,对于下面的每一个调用列出可行函数。 其中哪个函数是最佳匹配? 如果调用不合法,是因为没有可匹配的函数还是因为调用具有二义性?
(a) f(2.56, 42)
(b) f(42)
(c) f(42, 0)
(d) f(2.56, 3.14)
答:
a)不合法,调用具有二义性
b)f(int)
c)f(int ,int )
d)f(double ,double)
练习6.51
问题:编写函数f
的4版本,令其各输出一条可以区分的消息。 验证上一个练习的答案,如果你的回答错了,反复研究本节内容直到你弄清自己错在何处
#include <iostream>
using std::cout;
using std::endl;
void f(){
cout<<"void f()"<<endl;
}
void f(int){
cout<<" void f(int)"<<endl;
}
void f(int,int ){
cout<<"void f(int int)"<<endl;
}
void f(double,double =3.4){
cout<<"void f(double,double 3.4)"<<endl;
}
int main() {
//f(2.56,42); ambigous
f(42);
f(42,0);
f(2.56,3.14);
return 0;
}
6.6.1节练习
练习6.52
问题:已知有如下声明:
void manip(int,int);
double dobj;
请指出下列调用中,每个类型转换的等级。
a)manip('a','z')
b)manip(55.4,dobj)
答:
a)属于第三级,通过类型提升实现的匹配。这里都由char 提升至 int
b)属于第四级,通过算数类型转换实现的匹配。 这里都是由 double下降至int
练习6.53
问题:说明下列每组声明中的第二条语句会产生什么影响,并指出哪些不合法(如果有的话)。
(a) int calc(int&, int&);
int calc(const int&, const int&);
(b) int calc(char*, char*);
int calc(const char*, const char*);
(c) int calc(char*, char*);
int calc(char* const, char* const);
a)当有const int 值传入时,会精确匹配到第二条。
b)当有指向const char 的指针时,匹配到第二个。
c)第二条没有什么用。会产生二义性。它的本意是传入一个常量指针。但是实参是常量指针,传入到形参,也不会改变实参的值。所以会产生二义性。
6.7节练习
练习6.54
问题:编写函数的声明,令其接受两个int
形参并返回类型也是int
;然后声明一个vector
对象,令其元素是指向该函数的指针。
int a(int, int);
using pa=int(*)(int,int)
vector<pa> va;
练习6.55
问题:编写4个函数,分别对两个int
值执行加、减、乘、除运算;在上一题创建的vector
对象中保存指向这些函数的指针。
int func_add(int,int);
int func_substract(int,int);
int func_multiply(int,int);
int func_divide(int,int);
vector<decltype(func_add)*> va={func_add,func_substract,func_multiply,func_divide};
练习6.56
问题:调用上述vector
对象中的每个元素并输出结果。
#include <iostream>
#include<vector>
using std::cout;
using std::endl;
using std::vector;
int func_add(int a,int b){
return a+b;
}
int func_substract(int a,int b){
return a-b;
}
int func_multiply(int a,int b){
return a*b;
}
int func_divide(int a,int b){
return a/b;
}
int main() {
vector<decltype(func_add)*> va={func_add,func_divide,func_multiply,func_substract};
cout<<va[1](6,3)<<endl;
cout<<va[3](6,3)<<endl;
return 0;
}
输出: