《C++ Primer》第7章 7.5节习题答案2

《C++ Primer》第7章 类

7.5节 构造函数再探

练习7.45:如果在上一个练习中定义的vector的元素类型是C,则声明合法吗?为什么?
【出题思路】
理解默认构造函数的用法,理解vector对象是如何定义和初始化的。
【解答】
与上一个练习相比,如果把vector的元素类型更改为C,则该声明是合法的,这是因为我们给类型C定义了带参数的默认构造函数,它可以完成声明语句所需要的默认初始化操作。
#include <iostream>
#include <string>
#include <vector>

using namespace std;

//该类型没有显式定义默认构造函数,编译器也不会为它合成一个
class NoDefault
{
public:
    NoDefault(int i)
    {
        val = i;
        cout << "NoDefault构造函数===========" << endl;
    }

    int val;
};

class C
{
public:
    NoDefault nd;
    //必须显式调用Nodefault的带参构造函数初始化nd
    C(int i = 0): nd(i)
    {
        cout << "C构造函数===========" << endl;
    }
};

int main()
{
    C c;  //使用了类型C的默认构造函数
    cout << c.nd.val << endl;
    vector<C> vec(10);
    return 0;
}

运行结果:

练习7.46:下面哪些论断是不正确的?为什么?
(a) 一个类必须至少提供一个构造函数?
(b) 默认构造函数是参数列表为空的构造函数?
(c) 如果对于类来说不存在有意义的默认值,则类不应该提供默认构造函数。
(d) 如果类没有定义默认构造函数,则编译器将为其生成一个并把每个数据成员初始化成相应类型的默认值。
【出题思路】
本题旨在考查读者对默认构造函数原理的熟悉程度。
【解答】
(a) 是错误的,类可以不提供任何构造函数,这时编译器自动实现一个合成的默认构造函数。
(b) 是错误的,如果某个构造函数包含若干形参,但是同时为这些形参都提供了默认实参,则该构造函数也具备默认构造函数的功能。
(c) 是错误的,因为如果一个类没有默认构造函数,也就是说我们定义了该类型的某些构造函数时但是没有为其设计默认构造函数,则当编译器确实需要隐式地使用默认构造函数时,该类无法使用。所以一般情况下,都应该为类构建一个默认构造函数。
(d) 是错误的,对于编译器合成的默认构造函数来说,类类型的成员执行各自所属类的默认构造函数,内置类型和复合类型的成员只对定义在全局作用域中的对象执行初始化。

练习7.47:说明接受一个string参数的Sales_data构造函数是否应该是explicit的,并解释这样做的优缺点。

【出题思路】

explicit用于抑制类类型的隐式转换,读者需要知道explicit的长处和不足。

 【解答】

接受一个string参数的Sales_data构造函数应该是explicit的,否则,编译器就有可能自动把一个string对象转换成Sales_data对象,这种做法显得有些随意,某些时候会与程序员的初衷相违背。使用explicit的优点是避免因隐式类类型转换而带来意想不到的错误,缺点是当用户的确需要这样的类类型转换时,不得不使用略显烦琐的方式来实现。

练习7.48:假定Sales_data的构造函数不是explicit的,则下述定义将执行什么样的操作?

string null_isbn("9-999-99999-9");
Sales_data item1(null_isbn);
Sales_data item2("9-999-99999-9");


如果Sales_data的构造函数是explicit的,又会发生什么呢?
【出题思路】
构造函数如果不是explicit的,则string对象隐式地转换成Sales_data对象;相反,构造函数如果是explicit的,则隐式类类型转换不会发生。

【解答】

在本题给出的代码中,第一行创建了一个string对象,第二行和第三行都是调用Sales_data的构造函数(该构造函数接受一个string)创建它的对象。此处无须任何类类型转换,所以不论Sales_data的构造函数是不是explicit的,item1和item2都能被正确地创建,它们的bookNo成员都是9-999-99999-9,其他成员都是0。

练习7.49:对于combine函数的三种不同声明,当我们调用i.combine(s)时分别发生什么情况?其中i是一个Sales_data,而s是一个stirng对象。
(a) Sales_data &combine(Sales_data);
(b) Sales_data &combine(Sales_data&);
(c) Sales_data &combine(const Sales_data&) const;
【出题思路】
要想使用隐式的类类型转换,必须遵循一系列规定。如果我们试图在一行代码中使用两种转换规则,编译器将报错。
【解答】
(a)是正确的,编译器首先用给定的string对象s自动创建一个Sales_data对象,然后这个新生成的临时对象传给combine的形参(类型是Sales_data),函数正确执行并返回结果。
(b)无法编译通过,因为combine 函数的参数是一个非常量引用,而s是一个string对象,编译器用s自动创建一个Sales_data临时对象,但是这个新生成的临时对象无法传递给combine所需的非常量引用。如果我们把函数声明修改为Sales_data &combine(const Sales_data&);就可以了。
(c)无法编译通过,因为我们把combine声明成了常量成员函数,所以该函数无法修改数据成员的值。

练习7.50:确定在你的Person类中是否有一些构造函数应该是explicit的。
【出题思路】
explicit的优点是可以避免程序员不期望的隐式类类型转换。
【解答】
我们之前定义的Person类含有3个构造函数,因为前两个构造函数接受的参数个数都不是1,所以它们不存在隐式转换的问题,当然也不必指定explicit . Person类的最后个构造函数Person(std::istream &input);只接受一个参数,默认情况下公把读入的数据自动转换成Person对象。我们更假向于严格控制Person对象的生成过程,如果确实需要使用Person对象,可以明确指定; 在其他情况下则不希望自动类型转换的发生。所以应该把这个构造函数指定为explicit的。
#include <iostream>
#include <string>

using namespace std;

class Person
{
    friend std::istream& operator >> (std::istream&, Person&);

private:
    string strName;
    string strAddress;

public:
    Person() = default;

    Person(const string &name, const string &add)
    {
        strName = name;
        strAddress = add;
    }

    explicit Person(std::istream &input) { input >> *this; }

    string getName() const
    {
        return strName;
    }

    string getAddress() const
    {
        return strAddress;
    }
};


std::istream& operator >> (std::istream& in, Person& per)
{
    in >> per.strName >> per.strAddress;
    return in;
}


int main()
{
    cout << "请输入姓名和地址:" << endl;
    Person person(cin);
    cout << "name: " << person.getName() << "       address: " << person.getAddress() << endl;

    return 0;
}

运行结果:

 

练习7.51:vector将其单参数的构造函数定义成explicit的,而string则不是,你觉得原因何在?
【出题思路】
从参数类型到类类型的自动转换是否有意义依赖于程序员的看法,如果这种转换是自然而然的,则不应该把它定义成explicit的;如果二者的语义距离较远,则为了避免不必要的转换,应该指定对应的构造函数是explicit的。
【解答】
string接受的单参数是const char*类型,如果我们得到了一个常量字符串指针(字符数组),则把它看作string对象是自然而然的过程,编译器自动把参数类型转换成类类型也非常符合逻辑,因此我们无须指定为explicit的。
与string相反,vector接受的单参数是int类型,这个参数的原意是指定vector的空量。如果我们在本来需要vector的地方提供一个int值并且希望这个int值自动转换成vector,则这个过程显得比较牵强,因此把vector的单参数构造函数定义成explicit的更合理。

练习7.52:使用2.6.1节(第64页)的Sales_data类,解释下面的初始化过程。如果存在问题,尝试修改它。
Sales_data item = {"978-0590353403", 25, 15.99}
【出题思路】
熟悉聚合类的概念,理解聚合类初始化的过程及对数据成员的要求。
【解答】
程序的意图是对item执行聚合类初始化操作,用花括号内的值初始化item的数据成员。然而实际过程与程序的原意不符合,编译器会报错。这是因为聚合类必须满足一些非常苛刻的条件,其中一项就是没有类内初始值,而在2.6.1节给出的定义中,数据成员units_sold和revenue都包含类内初始值。只要去掉这两个类内初始值,程序就可以正常运行了。
struct Sales_data
{
    string bookNo;
    unsigned units_sold;
    double revenue;
};

练习7.53:定义你自己的Debug。
【出题思路】
本题旨在考查字面值常量类的用法。
【解答】
字面值常量类是一种非常特殊的类类型,聚合类是字面值常量类,某些类虽然不是聚合类但在满足书中所提要求的情况下也是字面值常量类。字面值常量类必须至少提供一个constexpr构造函数。
#include <iostream>
#include <string>

using namespace std;

class Debug
{
public:
    constexpr Debug(bool b = true): hw(b), io(b), other(b) { }
    constexpr Debug(bool h, bool i, bool o): hw(h), io(i), other(o) { }
    constexpr bool any() { return (hw || io || other); }

    void set_io(bool b) { io = b; }
    void set_hw(bool b) { hw = b; }
    void set_other(bool b) { other = b; }

private:
    bool hw;
    bool io;
    bool other;
};

int main()
{
    constexpr Debug io_sub(false, true, false);             //调式IO
    if(io_sub.any())                                        //等价于if(true)
    {
        cerr << "print appropriate error messags" << endl;
    }
    constexpr Debug prod(false);                            //无调式
    if(prod.any())                                          //等价于if(false)
    {
        cerr << "print an error message" << endl;
    }

    return 0;
}

运行结果:

 练习7.54:Debug中以set_开头的成员应该被声明成constexpr吗?如果不,为什么?

【出题思路】

理解constexpr函数的用法。

【解答】

这些以set_开头的成员不能声明成constexpr,这些函数的作用是设置数据成员的值,而constexpr函数只能包含return语句,不允许执行其他任务。

练习7.55:7.5.5节(第266页)的Data类是字面值常量类吗?请解释原因。
【出题思路】
读者需要掌握字面值常量类的判断方法。
【解答】
因为Data类是聚合类,所以它也是一个字面值常量类。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值