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;

 



结束!

 

posted @ 2023-09-04 10:49  para_dise  阅读(63)  评论(0)    收藏  举报