C++primer习题集(第六章)
6.1 实参和形参的区别是什么?
形参是函数定义时声明的参数,用于接收实参的值;而实参是函数调用时传递给函数的具体值或变量。
或者说,形参就是告诉你有这样一个盒子,告诉了你它的形状,但是里面没有东西(形参),需要把实际存在的参数/实物(实参)放入到匹配的盒子(形参)里才能使用。
6.2 指出下面哪个函数有错误,为什么?并指出如何修改?
a:问题在于函数是int型,返回的应该是int而不是string数据类型,可以改为string f() int f() { string s; //··· return s; } b:问题在于没有定义函数的返回类型,可以改为void f2(int i) f2(int i) { //··· } c:问题在于两个形参的名字不能一样,否则会分不清哪个是哪个,可以改为v1和v2 int calc(int v1,int v1) { //··· } d:函数就算只有一句也要加花括号 double sqrt(double x) return x*x;
6.3 编写你自己的fact函数,检查是否正确。
#include<iostream> using namespace std; int fact(int n) { int s=1; if(n==1) return 1; if(n<1) return -1; while(n>1) { s*=n; n--; } return s; } int main() { int x; cin>>x; int sum=fact(x); cout<<sum; return 0; }
6.4 编写一个与用户交互的程序,用户输入一个数字,计算生成该数字的阶乘,在main函数中调用它。
如题6.3,已经实现了该功能。
6.5 编写一个函数,输出其实参的绝对值。
#include<iostream> using namespace std; int abs(int n) { if(n<0) return -n; else return n; } int main() { int x; cin>>x; int abs_x=abs(x); cout<<abs_x; return 0; }
6.6 说明形参、局部变量、静态局部变量的区别。编写一个函数,同时用到这三个类型。
形参用于接受传递给函数的参数,用于接收实参;
局部变量是在函数内部声明的变量,在函数返回或者退出时,会销毁,下次调用同一个函数又是重新声明
静态局部变量是通过static声明的变量,它在函数结束后也会保存,生命周期和全局变量一样(第二次调用的是上次改变之后的值,也就是,可以保留变化)。
#include<iostream> using namespace std; void view(int n) { static int s_x=10; int tmp=100; tmp+=10; s_x+=2; cout<<"形参:"<<n<<endl; cout<<"局部变量:"<<tmp<<endl; cout<<"静态局部变量:"<<s_x<<endl; } int main() { for(int i=0;i<10;i++) view(i); return 0; }
6.7 编写一个程序,在第一次被调用的时候返回0,后面每次调用,返回值加一。
#include<iostream> using namespace std; int view() { static int x=0; return x++; } int main() { for(int i=0;i<10;i++) cout<<view()<<endl; return 0; }
6.8 编写一个名为Chapter6.h 的头文件,令其包含6.1节练习(184页)中的函数声明。
int fact(int val); int func(); int abs(int My_number);
6.9 编写你的fact.cc和factMain.cc,这两个文件都应该包含上一小节的练习中编写的Chapter6.h头文件。通过这些文件,理解你的编译器是如何支持分离式编译的。
代码自己写,编译可以参照下面链接:
详解C/C++代码的预处理、编译、汇编、链接全过程 - 知乎 (zhihu.com)
6.10 编写一个函数,使用指针形参交换两个整数的值。在代码中调用该函数并输出交换后的结果,以此验证函数的正确性。
void exchange(int *a,int *b) { int temp=*a; *a=*b; *b=temp; } int main() { int a,b; a=10; b=9; exchange(&a,&b); cout<<a<<" "<<b<<endl; return 0; }
6.11 编写并验证你自己的reset函数,使其作用于引用类型的参数。
void reset(int &i) { i=0; cout<<"reset:"<<i<<endl; }
6.12 改写6.2.1节练习中的程序,使其引用而非指针交换两个整数的值。你觉得哪种方法更易于使用呢?为什么?
#include <iostream> using namespace std; void swap(int &a,int &b) { int tmp=a; a=b; b=tmp; } int main() { int x=100,y=88; swap(x,y); cout<<x<<" "<<y<<endl; return 0; } /* 在使用方面引用比较方便,不用额外的*来指向目的数据 但是,引用和拷贝容易混淆,没有指针那么清晰 */
6.13 假设 T 是某种类型的名字,说明以下两个函数声明的区别:一个是void f(T), 另一个是 void f(&T)。
#include <iostream> // 函数声明:按值传递 void f1(int x) { x = 10; } // 函数声明:按引用传递 void f2(int& x) { x = 10; } int main() { int num1 = 5; int num2 = 5; f1(num1); std::cout << "num1 after f1: " << num1 << std::endl; // 输出:num1 after f1: 5 f2(num2); std::cout << "num2 after f2: " << num2 << std::endl; // 输出:num2 after f2: 10 return 0; } /* void f(T) 表示按值传递参数,函数会对参数进行副本操作。 void f(&T) 表示按引用传递参数,函数会对参数进行引用操作,对参数的修改会影响到原始值。 */
6.14 举一个形参应该是引用类型的例子,再举一个形参不能是引用类型的例子。
/*引用类型*/ void swap(int &a,int &b) { int tmp=a; a=b; b=tmp; } /*不引用类型*/ void sum(int a,int b) { return a+b; }
6.15 说明find_char 函数中的三个形参为什么是现在的类型,特别说明为什么s是常量引用而occurs是普通引用?为什么s和occurs是引用类型而c不是?如果令s是普通引用会发生什么情况?如果令occurs是常量引用会发生什么情况?
/* 为什么s是常量引用而occurs是普通引用? 因为occors在函数内要发生变化,而s不用,只读 为什么s和occurs是引用类型而c不是? 因为s是字符串,拷贝成本较大所以引用;occurs在函数内是要变化的,所以引用;c直接拷贝比引用更方便,也节省空间 如果令s是普通引用会发生什么情况? 不会有什么问题;但是在比较复杂的函数中,可能会对其误操作修改它的值,要对这种情况进行避免 如果令occurs是常量引用会发生什么情况? 那将无法更改它的值,也就无法统计出现的次数了。 */
6.16 下面的这个函数虽然合法,但是不算特别有用。指出它的局限性并设法改善。
bool is_empty(string& s){ return s.empty(); } /* 一方面这个函数不需要修改参数的值,因此应该加上const让程序员清楚这一点 另一方面,常量引用会限制函数的适用范围,如果传入的参数是字面值、const的时候,会出错 */
6.17 编写一个程序,判断string对象中是否含义大写字母。编写另一个程序,把strubf对象全都改写成小写形式。在这两个函数里面你是用的形参类型相同吗?为什么?
#include <iostream> using namespace std; int jud_string(const string &s) { for(auto c:s) { if(c>='A'&&c<='Z') return 1; } return 0; } void upper_string(string &s) { for(auto &c:s) { if(c>='a'&&c<='z') c=c+'A'-'a'; } } int main() { string s="Hello, I have 100 dollors."; cout<<"首先s: "<<s<<endl; int ret=jud_string(s); if(ret==1) cout<<"s有大写字母"<<endl; else cout<<"s没有大写字母"<<endl; upper_string(s); cout<<s<<endl; return 0; } //形参不一样,因为一个需要修改字符串,而一个不需要
6.18 为下面的函数编写函数声明,从给定的名字中推测函数具备的功能。
(a)名为conpare的函数,返回布尔值,两个参数都是matrix类的引用。 bool compare(const matrix &a,const matrix &b) { //TODO(),用于比较两个matrix的大小,相同返回1,不同返回0 } (b)名为change_val的函数,返回vector<int>的迭代器,有两个参数:一个是int,一个是vector<int>的迭代器 vector<int>::vector change_val(int x,vector<int>::vector vx) { //TODO()用于把vx里面的内容全都替换成x的值并返回,因为返回了,所以不需要引用 }
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); //合法
6.20 引用形参什么时候应该是常量引用?如果形参应该是常量引用,而我们将其设为了普通引用,会发生什么情况?
/* 无需在函数中改变的参数应该设为常量引用,如果我们可以使用const,就使用它。 如果我们在一个参数可以作为const的引用时,将其作为普通引用,那么这个引用的值可能会改变。 */
6.21 编写一个函数,令其接受两个参数:一个是int型的数,另一个是int指针。函数比较int的值和指针所指的值,返回较大的那个。在该函数中指针的类型应该是什么?
#include <iostream> using namespace std; int comp(int i,const int *const ci) { return i>*ci?i:*ci; } int main() { int x=100; cout<<comp(101,&x); return 0; }
6.22 编写一个函数,令其交换两个int指针。
#include <iostream> using namespace std; void swap(const int * &i1,const int * &i2) { auto tmp=i1; i1=i2; i2=tmp; } int main() { int x=99,y=1; int *px=&x; int *py=&y; swap(px,py); cout<<*px<<endl; cout<<*py<<endl; return 0; }
6.23 参考本节介绍的几个print函数根据理解编写你的版本。依次调用每个函数使其输入下面定义的i和j。
int i = 0, j[2] = {0, 1};
#include <iostream> using namespace std; //指针类型的,适合有明显结束符号的情况 void print(const char *cp) { if(!cp){ cout<<"输入空指针!\n"; return ; } while(*cp) cout<<*cp++; cout<<endl; } //传入头尾指针的,适合清楚头尾状况的数组 void print(int *beg,const int *end) { while(beg!=end) cout<<*beg++<<endl; cout<<endl; } //传入头指针和长度 void print(const int *beg,int len) { for(auto i=0;i<len;i++) cout<<beg[i]<<endl; cout<<endl; } int main() { int num[7]={1,2,3,4,5,6,7}; char *str="hello"; print(str); print(begin(num),end(num)); print(num,7); return 0; }
6.24 描述下面这个函数的行为。如果代码中存在问题,请指出并改正。
void print(const int ia[10]) { for(size_t i=0;i!=10;i++) cout<<ia[i]<<endl; } //行为是针对大小为10的int数组,进行打印 //问题:这种传入是会被忽略的,而传递给函数的是一个指向整数的指针。 // 如果实际传入数组大小不符,可能会导致溢出等问题 //修改:改成print(const int *ia, int size)
6.25 编写一个main函数,令其接受两个实参。把实参的内容连接成一个string并输出出来。
//使用argv中的实参时,可选参数应该从argv[1]开始,argv[0]保存的是程序的名字。 #include <iostream> #include<string.h> using namespace std; int main(int argc,char **argv) { string str; for(auto i=1;i!=argc;i++) { str+=argv[i]; str+=" "; } cout<<str<<endl; return 0; }
6.26 编写一个程序,使其接受本节所示的选项;输出传递给main函数的实参的内容。
同6.25。
6.27 编写一个函数,他的参数是initializer_list<int>类型的对象,函数功能hi计算列表里面所有元素的和。
#include <iostream> #include<string.h> using namespace std; int sum(initializer_list<int> ix) { int sum=0; for(auto i:ix) sum+=i; return sum; } int main(int argc,char **argv) { int s=sum({1,2,3,4,5}); cout<<s<<endl; return 0; }
6.28 在error_msg函数的第二个版本中包含ErrCode类型的参数,其中循环的elem是什么类型?
elem是const string类型的。
6.29 在范围for循环中使用initializer_list对象时,应该将寻欢控制变量声明成引用类型吗?为什么?
不应该,因为是const的值,只读,所以不应该声明成引用,而应该声明成常量引用类型,避免意外地修改元素值。
6.30 编译第200页的str_subrange函数,看看你的编译器如何处理函数的错误的。
这个自己试试吧。
6.31 什么情况下返回的引用无效?什么情况下返回常量的引用无效?
当返回的引用指向一个局部变量或者临时对象时,该引用就会无效。
这是因为局部变量和临时对象在函数执行完毕后会被销毁,因此返回的引用将指向一个已经无效的对象。
当返回常量的引用指向一个局部常量时,因为该局部常量在函数执行完毕后会被销毁;
当返回常量的引用指向一个非常量对象的常量成员时,如果这个对象在函数执行完毕后被修改,那么返回的引用将无效。
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; } //合法,作用是给数组ia赋值,ia[i]=i
6.33 编写一个递归函数,输出vector对象的内容。
#include <iostream> #include<vector> using namespace std; void print2(vector<int>::iterator beg,vector<int>::iterator end) { if(beg!=end) { cout<<*beg<<" "; print2(++beg,end); } else cout<<endl; return ; } int main() { vector<int> iv{1,2,3,4,5,6,7,8}; print2(iv.begin(),iv.end()); return 0; }
6.34 如果factorial函数的停止条件如下所示,将发生生么情况?
if (val != 0)
//如果改为val != 0, 那么输入一个负数,这个递归会一直运行,直到溢出
6.35 在调用factorial函数的时候,为什么我们传入的值是val-1而不是,val--?
//因为val--传的是val,一方面和val-1的思想不符,导致无限递归 //另一方面,再这样会导致临时保存的val在递归中,留存很久,占用不必要的空间资源
6.36 编写一个函数的声明,使其返回数组的引用并且该数组包含10个string对象。不要使用尾置返回类型,decltype或者类型别名。
string (&func(string (&array)[10]))[10]
6.37 为上一题的函数再写三个声明,一个使用类型别名,另一个使用尾置返回类型,最后一个使用decltype关键字。你觉得哪种形式最好?为什么?
//感觉类型别名和尾置类型比较直观 //类型别名 using Arrt = string[10]; Arrt &func(Arrt &arr); //尾置类型 auto func2(ArrT& arr) -> string(&)[10]; //decltype string arrS[10]; decltype(arrS)& func3(ArrT& arr);
6.38 修改arrPtr函数,使其返回数组的引用。
decltype(odd) &arrPtr(int i){ return (i%2) ? odd : even; }
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 *); //合法,重载了reset函数
6.40 下面哪个声明是错误的?为什么?
//b是错的,因为某个形参赋予了默认值之后,后面的所有形参都必须有默认值 (a) int ff(int a, int b = 0, int c = 0); (b) char *init(int ht = 24, int wd, char bckgrnd);
6.41 下面哪个调用是非法的?为什么?哪个调用虽然合法但是与程序员的初衷不符?为什么?
char *init(int ht, int wd = 80, char bckgrnd = ' '); (a) init(); //错误,ht没有默认值,需要传参 (b) init(24,10); //正确 (c) init(14,'*'); //正确,但是不符合,它默认是从左往右赋值,'*'会赋给wd
6.42 给make_plural函数(6.3.2节,201页)的第二个形参赋予默认实参's',利用新版本的函数输出单词success和failure的单数和复数形式。
#include <iostream> #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() { cout << "singual: " << make_plural(1, "success", "es") << " " << make_plural(1, "failure") << endl; cout << "plural : " << make_plural(2, "success", "es") << " " << make_plural(2, "failure") << endl; }
6.43 你会把下面哪个声明和定义放在头文件里,哪个放进源文件里?为什么?
(a) inline bool eq(const BigInt&, cosnt BigInt&){...} //放在头文件,内联函数 (b) void putValues(int *arr, int size); //放在头文件,声明
6.44 将6.22节(189页)的isShorter函数改写成内联函数。
inline bool isShorter(const string &s1, const string &s2) { return s1.size()<s2.size(); }
6.45 回顾前面练习中写的那些函数,他们应该是内联函数吗?如果是,请改写;如果不是,说明原因。
/* 内联函数的意义其实是利用空间换取时间,避免函数调用的开销。 但是入股函数体太大,那会导致拷贝的成本,以及可执行文件过大。 因此一般情况下,会将那些比较小,但是关键(频繁调度)的函数转换为内联函数。 */
6.46 可以把isShorter函数定义为constexpr函数吗?如果可以请改写,如果不能请说明原因。
constexpr函数是指能用于常量表达式的函数:函数的返回值类型和所有形参的类型必须是“字面值类型”:算术、引用、指针。并且函数体内有且只有一条return语句。
/* isShort函数里面,s1.size() == s2.size()并不是常量表达式,所以,并不能改写 */
6.47 改写6.3.2节(205页)连洗澡使用递归输出vector内容的程序,使其有条件地输出与执行过程相关的信息。例如,每次调用时输出vector对象的大小。分别在打开和关闭调试器的情况下编译并执行这个程序。
#include <iostream> #include<vector> #define NDEBUG using namespace std; void print2(vector<int>::iterator beg,vector<int>::iterator end) { static int size = 0; if(beg!=end) { cout<<*beg<<" "; size++;
#ifdef NDEBUG cout<<"size: "<<size<<endl; #endif // NDEBUG
print2(++beg,end); } else cout<<endl; return ; } int main() { vector<int> iv{1,2,3,4,5,6,7,8,1,11,111,1111}; print2(iv.begin(),iv.end()); return 0; }
————————PS:看成6.3.3了,哈哈哈——————————
6.48 说明下面这个循环的含义,它对assert的使用合理吗?
string s; while(cin>>s && s != sought) { } assert(cin); /* 循环的含义:循环输入字符串s直到s和sought字符串相等终止循环 断言使用不对,因为在循环过程中,不会调用该断言,应该将它放到循环体内 */
6.49 什么是候选函数?什么是可行函数?
/* 候选函数:与被调用的函数同名,并且在被调用点可见 可行函数:在候选函数中,参数与调用提供的实参相匹配的函数,是候选函数的子集 */
6.50 已知有第217页对函数 f 的声明,对于下面的每一个调用列出可行函数。其中哪个函数是最佳匹配?如果调用不合法,是因为没有可匹配的函数还是因为调用具有二义性?
(1) void f(); (2) void f(int); (3) void f(int, int); (4) void f(double, double = 3.14); (a) f(2.56, 42) //候选:3, 4 最佳:无,二义性 (b) f(42) //候选:2, 4 最佳:2 (c) f(42, 0) //候选:3,4 最佳:3 (d) f(2.56, 3.14) //候选:3, 4 最佳:4
6.51 编写函数f的4个版本,令其各自输出一条可以区分的消息。验证上一个练习的答案,如果回答错了,研究到你明白。
#include <iostream> #include<vector> #define NDEBUG using namespace std; void f() { cout<<"this is f()"<<endl; } void f(int) { cout<<"this is f(int)"<<endl; } void f(int, int) { cout<<"this is f(int, int)"<<endl; } void f(double, double = 3.14) { cout<<"this is f(double, double = 3.14)"<<endl; } int main() { //f(2.56, 42); //候选:3, 4 最佳:无,二义性 f(42); //候选:2, 4 最佳:2 f(42, 0); //候选:3,4 最佳:3 f(2.56, 3.14); //候选:3, 4 最佳:4 return 0; } /* (a) error: call of overloaded 'f(double, int)' is ambiguous| (b) this is f(int) (c) this is f(int, int) (d) this is f(double, double = 3.14) */
6.52 已知有如下声明,请指出下列调用中每个类型转换的等级。
void manip(int ,int); double dobj;
(a) manip('a', 'z');
(b) manip(55.4, dobj);
(a) manip('a', 'z'); // 通过类型提升实现的匹配 (b) manip(55.4, dobj); // 通过算数类型转换
6.53 说明下列每组声明中的第二条语句会产生什么影响,并指出哪些不合法(如果有的话)。
(a) int calc(int&, int&); int calc(const int&, const int&); // 合法,实参可以为const int (b) int calc(char*, char*); int calc(const char*, const char*); // 合法,实参可以为const char* (c) int calc(char*, char*); int calc(char* const, char* const); // 合法,顶层const,声明重复(可以重复声明,不可重复定义)
6.54 编写函数的声明,令其接受两个int形参并返回类型也是int;然后声明一个vector对象,令其元素是指向该函数的指针。
int func(int a, int b); using pFunc1 = decltype(func) *; typedef decltype(func) *pFunc2; using pFunc3 = int (*)(int a, int b); using pFunc4 = int(int a, int b); typedef int(*pFunc5)(int a, int b); using pFunc6 = decltype(func); std::vector<pFunc1> vec1; std::vector<pFunc2> vec2; std::vector<pFunc3> vec3; std::vector<pFunc4*> vec4; std::vector<pFunc5> vec5; std::vector<pFunc6*> vec6;
6.55 编写4个函数,分别对两个int值执行加、减、乘、除运算;在上一题创建的vector对象中保存指向这些函数的指针。
int add(int a, int b) { return a + b; } int subtract(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; }
6.56 调用上述vector对象中的每个元素并输出结果。
std::vector<decltype(func) *> vec{add, subtract, multiply, divide}; for (auto f : vec) std::cout << f(2, 2) << std::endl;
结束!

浙公网安备 33010602011771号