C++ Primer (第五版) 课后习题 Unit7

7.1.1节练习

练习7.1

问题:使用2.6.1节定义的Sales_data类为1.6节的交易处理程序编写一个新版本。


#include <iostream>
using std::string;
using std::cin;
using std::cout;
using std::endl;
struct Sales_data{
    string bookNo;
    unsigned units_sold=0;
    double revenue=0.0;
};
int main() {
    Sales_data total;
    if(cin>>total.bookNo>>total.units_sold>>total.revenue){
        Sales_data trans;
        while(cin>>trans.bookNo>>trans.units_sold>>trans.revenue){
            if(total.bookNo==trans.bookNo)
                total.units_sold+=trans.units_sold;
            else{
                cout<<"bookNO:"<<total.bookNo<<"  soldout:"<<total.units_sold<<endl;
                total.bookNo=trans.bookNo;
                total.units_sold=trans.units_sold;
                total.revenue=trans.revenue;
            }
        }
        cout<<"bookNO:"<<total.bookNo<<"  soldout:"<<total.units_sold<<endl;
    }else{
        std::cerr<<"No data?"<<endl;
        return -1;
    }
    return 0;
}

7.1.2节练习

练习7.2

问题:曾在2.6.2节的练习中编写了一个Sales_data类,请向这个类添加combine函数和isbn成员。


struct Sales_data{
    string bookNo;
    unsigned units_sold=0;
    double revenue=0.0;
    Sales_data& combine(const Sales_data&);
    string isbn() const{return bookNo;}
};

Sales_data& Sales_data::combine(const Sales_data &rhs) {
    units_sold+=rhs.units_sold;
    revenue+=rhs.revenue;
    return *this;
}

练习7.3

问题:修改7.1.1节的交易处理程序,令其使用这些成员。


#include <iostream>
using std::string;
using std::cin;
using std::cout;
using std::endl;
struct Sales_data{
    string bookNo;
    unsigned units_sold=0;
    double revenue=0.0;
    Sales_data& combine(const Sales_data&);
    string isbn() const{return bookNo;}
};

Sales_data& Sales_data::combine(const Sales_data &rhs) {
    units_sold+=rhs.units_sold;
    revenue+=rhs.revenue;
    return *this;
}
int main() {
    Sales_data total;
    if(cin>>total.bookNo>>total.units_sold>>total.revenue){
        Sales_data trans;
        while(cin>>trans.bookNo>>trans.units_sold>>trans.revenue){
            if(total.isbn()==trans.isbn())
                total.combine(trans);
            else{
                cout<<"bookNO:"<<total.bookNo<<"  soldout:"<<total.units_sold<<endl;
                total.bookNo=trans.bookNo;
                total.units_sold=trans.units_sold;
                total.revenue=trans.revenue;
            }
        }
        cout<<"bookNO:"<<total.bookNo<<"  soldout:"<<total.units_sold<<endl;
    }else{
        std::cerr<<"No data?"<<endl;
        return -1;
    }
    return 0;
}

练习7.4&练习7.5

问题:编写一个名为Person的类,使其表示人员的姓名和地址。使用string对象存放这些元素,接下来的练习将不断充实这个类的其他特征。

问题(7.5):在你的Person类中提供一些操作使其能够返回姓名和地址。 这些函数是否应该是const的呢?解释原因。


struct Person{
    std::string Name;
    std::string Address;
    std::string getName() const{return Name;}
    std::string getAddress()  const {return Address;}
};

答7.5:应该是const的,因为这个过程不希望this对成员变量进行修改。只进行只读操作。

7.1.3节练习

练习7.6&7.7

问题:对于函数addreadprint,定义你自己的版本。


#include <iostream>
using std::string;
using std::cin;
using std::cout;
using std::endl;

struct Sales_data{
    string bookNo;
    unsigned units_sold=0;
    double revenue=0.0;
    Sales_data& combine(const Sales_data&);
    string isbn() const{return bookNo;}
    double avg_price() const;
};

Sales_data& Sales_data::combine(const Sales_data &rhs) {
    units_sold+=rhs.units_sold;
    revenue+=rhs.revenue;
    return *this;
}

double Sales_data::avg_price() const{
    if(units_sold)
        return revenue/units_sold;
    return 0;
}
std::istream &read(std::istream &is,Sales_data &item){
    double price=0;
    is>>item.bookNo>>item.units_sold>>price;
    item.revenue=price*item.units_sold;
    return is;
}

std::ostream &print(std::ostream &os,Sales_data const &item){
    os<<item.isbn()<<"  "<<item.units_sold<<"  "<<item.revenue<<"  "<<item.avg_price();
    return os;
}

Sales_data add(const Sales_data &lhs,const Sales_data &rhs){
    Sales_data sum=lhs;
    sum.combine(rhs);
    return sum;
}
int main() {
    Sales_data total;
    if(read(cin,total)){
        Sales_data trans;
        while(read(cin,trans)){
            if(total.isbn()==trans.isbn())
                total.combine(trans);
            else{
                print(cout,total);
                total=trans;
            }
        }
        print(cout,total);
    }else{
        std::cerr<<"No data?"<<endl;
        return -1;
    }
    return 0;
}

练习7.8

问题:为什么read函数将其Sales_data参数定义成普通的引用,而print函数将其参数定义成常量引用?


答:因为read需要对类对象的成员变量尽心修改,而print对成员变量执行只读操作。

练习7.9

问题:对于7.1.2节练习中代码,添加读取和打印Person对象的操作。


class Person{
public:
    std::string Name;
    std::string Address;
    std::string getName() const{return Name;}
    std::string getAddress()  const {return Address;}
};
std::istream &read(std::istream& is,Person& p){
    is>>p.Name>>p.Address;
    return is;
}
std::ostream &print(std::ostream os,Person const& p){
    os<<p.getName()<<"  "<<p.getAddress();
    return os;
}

练习7.10

问题:在下面这条if语句中,条件部分的作用是什么?


if(read(read(cin,data1),data2))

答:从输入流中读取数据分别给data1,data2

7.1.4节练习

练习7.11

问题:在你的Sales_data类中添加构造函数, 然后编写一段程序令其用到每个构造函数。


#include <iostream>
using std::string;
using std::cin;
using std::cout;
using std::endl;

struct Sales_data{
    Sales_data()=default; //默认构造
    Sales_data(const string& s):bookNo(s){} //构造函数
    Sales_data(const string& s,unsigned n,double p):bookNo(s),units_sold(n),revenue(p){}
    string bookNo;
    unsigned units_sold=0;
    double revenue=0.0;
    Sales_data& combine(const Sales_data&);
    string isbn() const{return bookNo;}
    double avg_price() const;
};

Sales_data& Sales_data::combine(const Sales_data &rhs) {
    units_sold+=rhs.units_sold;
    revenue+=rhs.revenue;
    return *this;
}

double Sales_data::avg_price() const{
    if(units_sold)
        return revenue/units_sold;
    return 0;
}
std::istream &read(std::istream &is,Sales_data &item){
    double price=0;
    is>>item.bookNo>>item.units_sold>>price;
    item.revenue=price*item.units_sold;
    return is;
}

std::ostream &print(std::ostream &os,Sales_data const &item){
    os<<item.isbn()<<"  "<<item.units_sold<<"  "<<item.revenue<<"  "<<item.avg_price();
    return os;
}

Sales_data add(const Sales_data &lhs,const Sales_data &rhs){
    Sales_data sum=lhs;
    sum.combine(rhs);
    return sum;
}
int main() {
    Sales_data total;
    Sales_data test1("ax-123");
    Sales_data test2("ax-123",10,2.5);
    print(cout,test1);
    print(cout,test2);
    if(read(cin,total)){
        Sales_data trans;
        while(read(cin,trans)){
            if(total.isbn()==trans.isbn())
                total.combine(trans);
            else{
                print(cout,total);
                total=trans;
            }
        }
        print(cout,total);
    }else{
        std::cerr<<"No data?"<<endl;
        return -1;
    }
    return 0;
}

练习7.12

问题:把只接受一个istream作为参数的构造函数移到类的内部


//构造函数部分:
    Sales_data(std::istream &is){
        double price;
        is>>this->bookNo>>(*this).units_sold>>price;
        (*this).revenue=price*(*this).units_sold;
    }

//执行部分:
    Sales_data test3(std::cin);
    print(cout,test3);

练习7.13

问题:使用istream构造函数重写229页的程序


练习7.14

问题:编写一个构造函数,令其用我们提供的类内初始值显式地初始化成员。


Sales_data() : units_sold(0) , revenue(0) { }

练习7.15

问题:为你的Person类添加正确的构造函数。


    Person()=default;
    Person(const string &n,const string &a):Name(n),Address(a){}

7.2节练习

练习7.16

问题:在类的定义中对于访问说明符出现的位置和次数有限定吗? 如果有,是什么?什么样的成员应该定义在public说明符之后? 什么样的成员应该定义在private说明符之后?


答:访问说明符位置和次数没有限定

如果是需要该成员在整个程序内都可被访问的,应该定义在public后

如果需要该成员只可以被类成员访问,但是不能被使用该类的代码访问,则使用private

练习7.17

问题:使用classstruct时有区别吗?如果有,是什么?


答:有区别,它们的默认访问权限不同。使用class时,第一次使用访问说明符之前定义的成员都是private的;使用struct时,第一次使用访问说明符之前的定义的成员都是public的

练习7.18

问题:封装是何含义?它有什么用处?


封装:将类内部分成员设置为外部不可见,而提供部分接口给外面,这样的行为叫做封装。

用处:

  • 1.确保用户的代码不会无意间破坏封装对象的状态。
  • 2.被封装的类的具体实现细节可以随时改变,而无需调整用户级别的代码。

练习7.19

问题:在你的Person类中,你将把哪些成员声明成public的? 哪些声明成private的? 解释你这样做的原因。


答:我会把Name 和Address这些成员声明成private 防止对他们随意改变。而把 getName()getAddress()changeName()changeAddress()这些方法设置为public,来对成员进行访问和修改。函数是暴露给对外的接口,而数据应该设为对外不可见。

7.2.1节练习

练习7.20

问题:友元在什么时候有用?分别列举出友元的利弊


其他类或者函数想要访问当前私有变量时,使用友元函数

友元弊端:破坏了程序的封装特性。

友元利:允许其他类或者函数访问某个类的非公有成员。

练习7.21

问题:修改你的Sales_data类使其隐藏实现的细节。 你之前编写的关于Sales_data操作的程序应该继续使用,借助类的新定义重新编译该程序,确保其正常工作。


#include <iostream>
using std::string;
using std::cin;
using std::cout;
using std::endl;

struct Sales_data{
    friend std::istream &read(std::istream &is,Sales_data &item);
    friend std::ostream &print(std::ostream &os,Sales_data const &item);
public:
    Sales_data()=default; //默认构造
    Sales_data(const string& s):bookNo(s){} //构造函数
    Sales_data(const string& s,unsigned n,double p):bookNo(s),units_sold(n),revenue(p){}
    Sales_data(std::istream &is){
        double price;
        is>>this->bookNo>>(*this).units_sold>>price;
        (*this).revenue=price*(*this).units_sold;
    }
    Sales_data& combine(const Sales_data&);
    string isbn() const{return bookNo;}
    unsigned unis() const{return units_sold;}
    double reve() const {return revenue;}
    double avg_price() const;
private:
    string bookNo;
    unsigned units_sold=0;
    double revenue=0.0;
};

Sales_data& Sales_data::combine(const Sales_data &rhs) {
    units_sold+=rhs.units_sold;
    revenue+=rhs.revenue;
    return *this;
}

double Sales_data::avg_price() const{
    if(units_sold)
        return revenue/units_sold;
    return 0;
}
std::istream &read(std::istream &is,Sales_data &item){
    double price=0;
    is>>item.bookNo>>item.units_sold>>price;
    item.revenue=price*item.units_sold;
    return is;
}

std::ostream &print(std::ostream &os,Sales_data const &item){
    os<<item.isbn()<<"  "<<item.units_sold<<"  "<<item.revenue<<"  "<<item.avg_price();
    return os;
}

Sales_data add(const Sales_data &lhs,const Sales_data &rhs){
    Sales_data sum=lhs;
    sum.combine(rhs);
    return sum;
}
int main() {
    std::istream &is =cin;
    Sales_data total(is);
    if(is){
        Sales_data trans(is);
        while(is){
            if(total.isbn()==trans.isbn())
                total.combine(trans);
            else{
                print(cout,total)<<endl;
                total=trans;
            }
            read(is,trans);
        }
        print(cout,total)<<endl;
    }
    else
        std::cerr<<"No data?"<<endl;
    return 0;
}

练习7.22

问题:修改你的Person类,使其隐藏实现的细节。


#include <iostream>
using std::string;

class Person{
    friend std::istream &read(std::istream&,Person&);
private:
    std::string Name;
    std::string Address;
public:
    Person()=default;
    Person(const string &n,const string &a):Name(n),Address(a){}
    std::string getName() const{return Name;}
    std::string getAddress()  const {return Address;}
};
std::istream &read(std::istream &is,Person &p){
    is>>p.Name>>p.Address;
    return is;
}
std::ostream &print(std::ostream os,const Person& p){
    os<<p.getName()<<"  "<<p.getAddress();
    return os;
}


int main() {

    return 0;
}

7.3.1节练习

练习7.23

问题:编写你自己的Screen类


#include <iostream>
#include<string>

class Screen{
public:
    typedef std::string::size_type pos;
    Screen()=default;
    Screen(pos ht,pos wd,char c):height(ht),width(wd),contents(ht*wd,c){}
    char get() const {return contents[cursor];}
    inline char get(pos r,pos c) const{return contents[r*width+c];}

private:
    pos cursor=0;
    pos height=0,width=0;
    std::string contents;
};

练习7.24

问题:给你的Screen类添加三个构造函数:一个默认构造函数;另一个构造函数接受宽和高的值,然后将contents初始化成给定数量的空白;第三个构造函数接受宽和高的值以及一个字符,该字符作为初始化后屏幕的内容。


#include <iostream>
#include<string>

class Screen{
public:
    typedef std::string::size_type pos;
    Screen()=default;
    Screen(pos ht,pos wd):height(ht),width(wd),contents(ht*wd,' '){}
    Screen(pos ht,pos wd,char c):height(ht),width(wd),contents(ht*wd,c){}
    char get() const {return contents[cursor];}
    inline char get(pos r,pos c) const{return contents[r*width+c];}

private:
    pos cursor=0;
    pos height=0,width=0;
    std::string contents;
};
int main() {
    std::cout << "Hello, World!" << std::endl;
    return 0;
}

练习7.25

问题:Screen能安全地依赖于拷贝和赋值操作的默认版本吗? 如果能,为什么?如果不能?为什么?


答:不能。因为在我们定义的Screen类中,成员变量是私有的,也就是对外封装。那么当使用赋值或者拷贝时,必然会要隐式的去访问类的成员,所以不能。❌

能。 Screen的成员只有内置类型和string,因此能安全地依赖于拷贝和赋值操作的默认版本。

管理动态内存的类则不能依赖于拷贝和赋值操作的默认版本,而且也应该尽量使用stringvector来避免动态管理内存的复杂性。

练习7.26

问题:将Sales_data::avg_price定义成内联函数。



inline double Sales_data::avg_price()
{
    return units_sold ? revenue/units_sold : 0;
}

7.3.2节练习

练习7.27

问题:给你自己的Screen类添加moveset 和display函数,通过执行下面的代码检验你的类是否正确。


#include <iostream>
#include<string>

class Screen{
public:
    typedef std::string::size_type pos;
    Screen()=default;
    Screen(pos ht,pos wd):height(ht),width(wd),contents(ht*wd,' '){}
    Screen(pos ht,pos wd,char c):height(ht),width(wd),contents(ht*wd,c){}
    char get() const {return contents[cursor];}
    inline char get(pos r,pos c) const{return contents[r*width+c];}
    Screen& move(pos r,pos c);
    Screen& set(char);
    Screen& set(pos,pos,char);
    Screen &display(std::ostream &os)
            {do_display(os);return *this;}
    const Screen &display(std::ostream &os) const
            {do_display(os);return *this;}
private:
    pos cursor=0;
    pos height=0,width=0;
    std::string contents;
    void do_display(std::ostream &os) const{os<<contents;}
};
inline Screen &Screen::move(pos r, pos c) {
    pos row = r*width;
    cursor = row+c;
    return *this;
}
inline Screen &Screen::set(char c) {
    contents[cursor]=c;
    return *this;
}
inline Screen &Screen::set(pos r, pos c, char v) {
    cursor=r*width+c;
    contents[cursor]=v;
    return *this;
}

int main() {
    Screen myScreen(5,5,'x');
    myScreen.move(4,0).set('#').display(std::cout);
    std::cout<<"\n";
    myScreen.display(std::cout);
    std::cout<<"\n";
    return 0;
}

运行结果:

练习7.28

问题:如果movesetdisplay函数的返回类型不是Screen& 而是Screen,则在上一个练习中将会发生什么?


则会对一个新生成的临时Screen类型的变量进行修改,并不会改变myScreen的值。

这是第三次输出和第四次输出的对比。

练习7.29

问题:修改你的Screen类,令movesetdisplay函数返回Screen并检查程序的运行结果,在上一个练习中你的推测正确吗?


class Screen2{
public:
    typedef std::string::size_type pos;
    Screen2()=default;
    Screen2(pos ht,pos wd):height(ht),width(wd),contents(ht*wd,' '){}
    Screen2(pos ht,pos wd,char c):height(ht),width(wd),contents(ht*wd,c){}
    char get() const {return contents[cursor];}
    inline char get(pos r,pos c) const{return contents[r*width+c];}
    Screen2 move(pos r,pos c);
    Screen2 set(char);
    Screen2 set(pos,pos,char);
    Screen2 display(std::ostream &os)
    {do_display(os);return *this;}
    const Screen2 display(std::ostream &os) const
    {do_display(os);return *this;}
private:
    pos cursor=0;
    pos height=0,width=0;
    std::string contents;
    void do_display(std::ostream &os) const{os<<contents;}
};
inline Screen2 Screen2::move(pos r, pos c) {
    pos row = r*width;
    cursor = row+c;
    return *this;
}
inline Screen2 Screen2::set(char c) {
    contents[cursor]=c;
    return *this;
}
inline Screen2 Screen2::set(pos r, pos c, char v) {
    cursor=r*width+c;
    contents[cursor]=v;
    return *this;
}

推测正确

练习7.30

问题:通过this指针使用成员的做法虽然合法,但是有点多余。讨论显式使用指针访问成员的优缺点。


优点:程序更加明确。

缺点:显得程序比较冗余。

7.3.3节练习

练习7.31 

问题:定义一对类XY,其中X包含一个指向Y的指针,而Y包含一个类型为X的对象。


class X;
class Y;
class X{
    Y *ptry=nullptr;
};
class Y{
    X x;
};

7.4节练习

练习7.33

问题:如果我们给Screen添加一个如下所示的size成员将发生什么情况?如果出现了问题,请尝试修改它。

pos Screen::size() const
{
    return height * width;
}

修改后:

pos size() const{
           return height * width;
       }

7.4.1节练习

练习7.34

问题:如果我们把第256页Screen类的postypedef放在类的最后一行会发生什么情况?


答:如果将pos放在最后一行,则对于类中的定义不可见。

类型名的定义要出现在类的开始处,这样就能确保所有使用该类型的成员都出现在类型名之后。

练习7.35

问题:解释下面代码的含义,说明其中的TypeinitVal分别使用了哪个定义。如果代码存在错误,尝试修改它。

typedef string Type;
Type initVal(); 
class Exercise {
public:
    typedef double Type;
    Type setVal(Type);
    Type initVal(); 
private:
    int val;
};
Type Exercise::setVal(Type parm) { 
    val = parm + initVal();     
    return val;
}

因为Type在类外已经定义,所以不能出现在类内。以为Type代表一种类型。

修改方法为:删去类内的 typedef double Type;

这里可以看到,存在两个initVal 不过因为所处的作用域不同,如果在setVal中定义的initVal  应该属于类内的initVal。

7.5.1节练习

练习7.36

问题:下面的初始值是错误的,请找出问题所在并尝试修改它。

struct X {
	X (int i, int j): base(i), rem(base % j) {}
	int rem, base;
};

答:类的成员变量先定义了rem,后定义了base;而在初始化的事后,rem是需要base的值的,也就是说这个程序是试图使用未定义的base来初始化rem

修改:避免使用成员初始化其他成员变量。

struct X {
	X (int i, int j): base(i), rem(i % j) {}
	int rem, base;
};

练习7.37

问题:使用本节提供的Sales_data类,确定初始化下面的变量时分别使用了哪个构造函数,然后罗列出每个对象所有数据成员的值。


答:

Sales_data first_item(cin);

//使用了
Sales_data(std::istream &is){read(is,*this);}

//这个值取决于输入
Sales_data next;

//这个使用了
Sales_data(std::string s=""):bookNo(s){}

//每个数据成员的值
bookNo("")
units_sold(0)
revenue(0)
Sales_data last("9-999-99999-9")

//使用了
Sales_data(std::string s=""):bookNo(s){}

bookNo("9-999-99999-9")
units_sold(0)
revenue(0)

练习7.38

问题:有些情况下我们希望提供cin作为接受istream&参数的构造函数的默认实参,请声明这样的构造函数。


class X{
    X(std::istream &is){read(is,*this)}

private:
    int a;
    int b;
    string c;
}

练习7.39

问题:如果接受string的构造函数和接受istream&的构造函数都使用默认实参,这种行为合法吗?如果不,为什么?


不合法,如果同时有两种构造函数都接受默认实参,则程序会出现二义性。编译器无法判定执行哪一个构造函数。

 

练习7.40

问题:从下面的抽象概念中选择一个(或者你自己指定一个),思考这样的类需要哪些数据成员,提供一组合理的构造函数并阐明这样做的原因。


class Book{
public:
    Book()=default;
    Book(double p,std::string a;std::string bn):price(p),author(a),bookName(bn){}
    Book(std::istream &is){is>>price>>author>>bookName;}
private:
    double price=0.0;
    string author;
    string bookName;
}
    

7.5.2节练习

练习7.41

问题:使用委托构造函数重新编写你的Sales_data类,给每个构造函数体添加一条语句,令其一旦执行就打印一条信息。用各种可能的方式分别创建Sales_data对象,认真研究每次输出的信息直到你确实理解了委托构造函数的执行顺序。


#include <iostream>
class Sales_data{
public:
    Sales_data(std::string s,unsigned cnt,double price):
        bookNo(s),units_sold(cnt),revenue(cnt*price){std::cout<<"1";}
    Sales_data():Sales_data("",0,0){std::cout<<"2";}
    Sales_data(std::string s):Sales_data(s,0,0){std::cout<<"3";}
    Sales_data(std::istream &is):Sales_data(){is>>bookNo>>units_sold>>revenue;
                                                std::cout<<"4";}
private:
    std::string bookNo;
    unsigned units_sold;
    double revenue;
};
int main() {
    Sales_data a1("a",1,1);//1
    std::cout<<"\n";
    Sales_data a2;
    std::cout<<"\n";
    Sales_data a3("mv");
    std::cout<<"\n";
    Sales_data a4(std::cin);

    return 0;
}

运行结果:

这里我还发现了一个有趣的现象。就是当我想使用无参数的构造函数时,下意识的输入了a2(),但是程序直接跳过了对a2的生成。如果要调用无参数,直接声明Sales_data a;就好了

这个问题得到了解决:实际上这里 Sales_data a();定义了返回 Sales_data对象且没有任何输入的函数。如果单纯的想声明一个默认初始化的对象,正确的方法就是去掉后面的括号。

练习7.42

问题:对于你在练习7.40中编写的类,确定哪些构造函数可以使用委托。如果可以的话,编写委托构造函数。如果不可以,从抽象概念列表中重新选择一个你认为可以使用委托构造函数的,为挑选出的这个概念编写类定义。


经过分析,可以看出Book()=default这个函数可以委托

class Book{
public:
    Book(double p,std::string a;std::string bn):price(p),author(a),bookName(bn){}
    Book():Book(0,"",""){}
    Book(std::istream &is){is>>price>>author>>bookName;}
private:
    double price=0.0;
    string author;
    string bookName;
}
    

7.5.3节练习

练习7.43

问题:假定有一个名为NoDefault的类,它有一个接受int的构造函数,但是没有默认构造函数。定义类CC有一个 NoDefault类型的成员,定义C的默认构造函数。


class NoDefault{
public:
    NoDefault(int s){}
private:
    int n;
}

class C{
public:
    C():ND(0){}

private:
    NoDefault ND;
}

练习7.44

问题:下面这条声明合法吗?如果不,为什么?

vector<NoDefault> vec(10);

答:不合法,这条语句声明 vector 有10个元素。NoDefault没有默认构造函数。无法初始化。

练习7.45

如果在上一个练习中定义的vector的元素类型是C,则声明合法吗?为什么?


答:合法,因为C有默认构造函数

练习7.46

下面哪些论断是不正确的?为什么?

(a) 一个类必须至少提供一个构造函数。

(b) 默认构造函数是参数列表为空的构造函数。

(c) 如果对于类来说不存在有意义的默认值,则类不应该提供默认构造函数。

(d) 如果类没有定义默认构造函数,则编译器将为其生成一个并把每个数据成员初始化成相应类型的默认值。


a)类也可以不提供构造函数。编译器会自动合成默认构造函数

b)不完全正确,为每个值提供默认参数的构造函数也是默认构造函数。

c)默认构造函数是在对象被默认初始化或值初始化时自动调用的函数。这和默认值是否有意义无关。错

d)不能,只有当一个类没有任何构造函数时,编译器才会为其生成一个默认构造函数。

7.5.4节练习

练习7.47

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


答:因为只接受一个参数。说明Sales_data可以通过string隐式创建对象。

应该加上eplicit。❌(是否需要从stringSales_data的转换依赖于我们对用户使用该转换的看法)

优点:只能以直接初始化的形式来使用该构造函数。程序变得更加易读。

缺点:限制了隐式初始化,意味着 拷贝初始化、类类型转换无法使用。为了转换,需要显式的使用构造函数

练习7.48

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

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

答:

item1(null_isbn)  创建一个Sales_data类

item2(“9-999-99999-9”)创建一个Sales_data类

如果Sales_data的构造函数是explicit的,又会发生什么?

这些定义和构造函数是否是explicit无关

练习7.49

问题:对于combine函数的三种不同声明,当我们调用i.combine(s)时分别发生什么情况?其中i是一个Sales_data,而 s是一个string对象。

(a) Sales_data &combine(Sales_data);
(b) Sales_data &combine(Sales_data&); 
(c) Sales_data &combine(const Sales_data&) const;

答:s是一个string对象

a)相当于一步类类型转换

b)需要传入Sales_data的引用,而传入的是string对象。无法将参数从std::string  转换到  Sales_data &

c)该成员函数是const的,意味着不能改变成员变量,但是该函数本身的目的就是为了改变参数。

练习7.50

问题:确定你在Person类中是否有一些构造函数应该是explicit的


 

练习7.51

问题:vector将其单参数的构造函数定义成explicit的,而string则不是,你觉得原因何在?


答:假如我们有这样一个函数:

int getSize(const std::vector<int>&);

如果我们不使用explicit而隐式调用的话, getSize(34), 此时实际上传入的是含有34个int元素的vector容器,但是这看上去是为getSize()函数传入了一个值为34的int值。

而string 则不一样。string的单参数构造函数的参数是 const char *;因此凡是需要string的地方都可以用const char*来代替

int getChar(std::string);

这里string可以随时被替换成 const char *。

7.5.5节练习

练习7.52

问题:使用2.6.1节的 Sales_data 类,解释下面的初始化过程。如果存在问题,尝试修改它。

Sales_data item = {"987-0590353403", 25, 15.99};

这是一个隐式初始化。将构造函数用于拷贝形式的初始化过程❌

这里将隐式初始化  和  聚合类搞混了。

拷贝形式的初始化,只能使用在构造函数只有一个输入参数的时候。

比如:Sales_data item= null_book;  

平时我们使用的string a=“1111”;类似这样的,实际上也是使用了这里所说的拷贝初始化。

这里的参数列表的初始化,应该满足聚合类。应该把Sales_data改为:

struct Sales_data{
string bookNo;
int num;
double revenue;
};

7.5.6节练习

练习7.53

问题:定义你自己的Debug


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){hw =b ;}
private:
    bool hw;
    bool io;
    bool other;
};

练习7.53:

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


答:不能;因为constexpr 函数的要求是有且只能有一条return 语句。显然这与constexpr的要求不符合

练习7.55

问题:7.5.5节的Data类是字面值常量类吗?请解释原因。


答:根据字面值常量类的定义:数据成员都是字面值类型的聚合类。该Data 中有int 类型和string类型。string类型不属于字面值类型。所以不是字面值常量类。

7.6节练习

练习7.56

问题:什么是类的静态成员?它有何优点?静态成员与普通成员有何区别?


静态成员就是和类直接相关的成员。

存在于对象外,不会受到各个对象的影响。而且能统领各个对象。

类的静态成员存在于任何对象之外。对象里不包含任何和静态数据成员有关的数据。静态成员能用在一些场景下。

练习7.57

问题:编写你自己的Account 类


class Account{
public:
    void calculate(){amount+=amount*interestRate};
    static double rate(){return interestRate;}
    static void rate(double);

private:
    std::string owner;
    double amount;
    static double interestRate;
    static double initRate();
};
void Account::rate(double newRate) {interestRate=newRate;}
double Account::initRate() {interestRate=interestRate*1.4;}//汇率

练习7.58

问题:下面的静态数据成员的声明和定义有错误吗?请解释原因。

//example.h
class Example {
public:
	static double rate = 6.5;
	static const int vecSize = 20;
	static vector<double> vec(vecSize);
};

//example.c
#include "example.h"
double Example::rate;
vector<double> Example::vec;

在类外定义rate需要对其初始化,且不能在类内对其初始化。如果需要在类内初始化,则需要定义成静态常量。

对于vector的静态变量,需要在类外对其进行初始化。

class Example{
public:
    constexpr static double  rate=1;
    static const int vecSize =20;
    static vector<double> vec;
};
const double Example::rate;
vector<double> Example::vec(vecSize);

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值