您的位置:首页 > 编程语言 > C语言/C++

C++Primer习题第七章

2017-11-22 18:09 190 查看
练习7.1:使用2.6.1节练习定义的Sales_data类为1.6节的交易处理程序编写一个新的版本。

#include<iostream>
#include"Sales_data.h"

using namespace std;

int main()
{
Sales_data total;
cout << "请输入交易记录(ISBN、销售量、原价、实际售价):" << endl;

if( cin >> total )
{
Sales_data trans;
while( cin >> trans )
{
if( compareIsbn( total, trans ) )
total += trans;
else
{
cout << total << endl;
total = trans;
}
}
cout << total << endl;
}
else
{
cerr << "没有数据!" << endl;
return -1;
}

return 0;
}


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

class Sales_data
{
public:
string isbn() const { return bookNo; }
Sale_data& combine( const Sales_data &rhs )
{
units_sold += rhs.units_sold;
sale_price = ( units_sold * sale_price + rhs.units_sold * rhs.sale_price )
/ ( units_sold + rhs.units_sold );
if( selling_price != 0 )
discount = sale_price / selling_price;

return *this;
}

private:
string bookNo;
unsigned units_sold = 0;
double selling_price = 0.0;
double sale_price = 0.0;
double dicount = 0.0;
}


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

class Sales_data
{
public:
string isbn() const { return bookNo; }
Sale_data& combine( const Sales_data &rhs )
{
units_sold += rhs.units_sold;
sale_price = ( units_sold * sale_price + rhs.units_sold * rhs.sale_price )
/ ( units_sold + rhs.units_sold );
if( selling_price != 0 )
discount = sale_price / selling_price;

return *this;
}

private:
string bookNo;
unsigned units_sold = 0;
double selling_price = 0.0;
double sale_price = 0.0;
double dicount = 0.0;
}


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

class Person
{
private:
string Name;
string Address;
public:
string getName() const { return Name; }
string getAddress() const { return Address; }
};


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

类的定义如上,已经包含getName()和getAddress()成员。

这些函数应该是const的,因为,这两个成员函数的操作只是读取数据成员的值,并不需要改动它们的值。

练习7.6:对于函数add、read和print,定义你自己的版本。

类的作者常常需要定义一些辅助函数。虽然这些属于类的接口的组成部分,但它们实际上并不属于类本身。

Sales_data& add( Sales_data &rhs, Sales_data &lhs );
std::istream& read( std::istream& in, Sales_data item );
ostream& print( ostream& out, const Sales_data );

Sales_data add( Sales_data &rhs, Sales_data &lhs )
{
Sales_data sum = rhs;
sum.combine( lhs );
return sum;
}

std::istream &read( std::istream& in, Sales_data item )
{
in >> item.bookNo >> item.units_sold >> item.selling_price >> item.sale_price;
return in;
}

std::ostream &print( std::ostream &out, const Sales_data &rhs )
{
out << rhs.bookNo << " " << rhs.units_sold << " " << rhs.selling_price << " "
<< rhs.sale_price << " " << discount;
return out;
}


练习7.7:使用这些新函数重写7.1.2节练习中的交易处理程序。

这里需要注意,add、read、print函数被需要声明为该类的友元函数,否则无法访问该类的private数据成员。

#include<iostream>
#include"Sales_data.h"

using namespace std;

int main()
{
cout << "请输入交易记录(ISBN、销售量、原价、实际售价):" << endl;
Sales_data total;
if( read( cin, total ) ){
Sales_data trans;
while( read( cin, trans ) ){
if( total.isbn() == trans.isbn() )
total = add( total, trans );
else{
print( cout, total );
cout << endl;
total = trans;
}

}
print( cout, total );
cout << endl;
}
else{
cerr << "No data?" << endl;
return -1;
}
return 0;
}


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

因为read函数从输入流中读取数据并写入到Sales_data类对象,因此需要有修改对象的权限。

print函数只负责对类对象的数据的输出,不需要对其作出任何更改。

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

class Person
{
friend std::istream &read( istream&, Person& );
friend std::ostream &print( ostream&, const Person&);
private:
string Name;
string Address;
public:
string getName() const { return Name; }
string getAddress() const { return Address; }
};

inline std::istream &read( istream &is, Person &aPerson)
{
is >> aPerson.Name >> aPerson.Address;
return is;
}

inline std::ostream &print( ostream &os, const Person &aPerson )
{
os << aPerson.getName() << aPerson.getAddress();
return os;
}


练习7.10:在下面这条if语句中,条件部分的作用是什么?
    if( read( read( cin, data1 ), data2 ) )

read函数返回类型是std::istream&,输入流的引用,所以可以作为实参继续使用和作为条件判断。

如果cin的数据正确写入data1和data2,则条件为真。否则为假。

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

#include<iostream>
#include"Sales_data.h"
using namespace std;

int main()
{
//使用四种构造函数
Sales_data dat1;
Sales_data dat2( "剑指offer" );
Sales_data dat3( "编程之美", 20, 45, 37 );
Sales_data dat4( cin );
cout << "书籍的销售情况是:" << endl;
cout << dat1 << endl;
cout << dat2 << endl;
cout << dat3 << endl;
cout << dat4 << endl;

return 0;
}


练习7.12:把只接受一个istream作为参数的构造函数定义到类的内部。

class Sales_data
{
public:
string isbn() const { return bookNo; }
Sale_data& combine( const Sales_data &rhs );

public:
Sales_data() = default;
Sales_data( const std::string &book ): bookNo(book) {}
Sales_data( const std::string &book, const unsigned units, const double sellingprice, const
4000
double saleprice );
Sale_data( istream& ) { is >> *this; }
private:
string bookNo;
unsigned units_sold = 0;
double selling_price = 0.0;
double sale_price = 0.0;
double dicount = 0.0;
}


练习7.13:使用istream构造函数重写第229页的程序。

#include<iostream>
#include"Sales_data.h"

using namespace std;

int main()
{
cout << "请输入交易记录:(ISBN、销量、原价、实际售价):" << endl;
Sales_data total( cin );
if( cin )
{
Sales_data trans( cin );
do
{
if( total.isbn() == trans.isbn() )
total.combine( trans );
else
{
print( cout, total );
cout << endl;
total = trans;
}
}while( read( cin, trans ) );
print( cout, total);
cout << endl;
}
else
{
cerr << " No data?! " << endl;
return -1;
}

return 0;
}


练习7.14:编写一个构造函数,令其用我们提供的类内初始值显示地初始化成员。
Sales_data( const string &s ) : bookNo(s), units_sold(0), selling_price(0), sale_price(0), discount(0)  { }

练习7.15:为你的Person类添加正确的构造函数。

class Person
{
friend std::istream &read( istream&, Person& );
friend std::ostream &print( ostream&, const Person&);
private:
string Name;
string Address;
public:
Person() = default;
person( const string &Nam, const string &Adr ): Name( Nam ), Address( Adr ) { }
person( istream &is ) { read( is, *this ) }

public:
string getName() const { return Name; }
string getAddress() const { return Address; }
};

inline std::istream &read( istream &is, Person &aPerson)
{
is >> aPerson.Name >> aPerson.Address;
return is;
}

inline std::ostream &print( ostream &os, const Person &aPerson )
{
os << aPerson.getName() << aPerson.getAddress();
return os;
}


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

定义在public之后的成员在整个程序内可被访问,public成员定义类的接口

定义在private之后的成员可以被类的成员函数访问,但是不能被使用该类的代码访问。

对于某个访问说明符能出现多少次没有严格限定。

一般来说,作为接口的一部分,构造函数和一部分成员函数应该定义在public说明符之后,而数据成员和作为实现部分的函数则应该跟在private后面。

练习7.17:使用struct和class时有区别吗?如果有,是什么?

struct和class的默认访问权限不同。

struct默认成员是public的

class默认成员是private的。

练习7.18:封装是何含义?它有什么作用?

(标准)

答:

(1)封装是指保护类的成员不被随意访问的能力。通过把类的实现细节设置为private,我们就能完成类的封装。

封装实现了接口和实现的分离。

(2)封装有两个重要的优点:一是确保用户代码不会无意间破坏对象的状态;二是被封装的类的具体实现细节可以随时改变,而无须调整用户级别的代码。

只要类的接口不变,用户代码就无须改变。

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

构造函数和接口( getName() 、 getAddress() )设置为public,以便于外部访问。

数据成员Name和Address设置为private,防止用户不经意修改和破坏它们。

练习7.20:友元在什么时候有用?请分别列举出使用友元的利弊。

--------------------------------------------------------------------------------------------------------------------------------------------------------

插播一条PS:

友元声明只能出现在类定义的内部,但是在类内出现的具体位置不限。其不受访问控制级别的约束。(不是类内成员)

友元的声明仅仅指定了其访问的权限,并非通常意义上的函数声明。故友元声明之外还需专门对函数进行一次声明。

--------------------------------------------------------------------------------------------------------------------------------------------------------

答:(标准)

当非成员函数确实需要访问类内的私有成员时,我们可以把它声明成该类的友元。

此时,友元可以“工作在类的内部”,像类成员一样访问类的所有数据和函数。但是一旦使用不慎,就有可能破坏类的封装性。

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

略了。(之前的程序已封装好了)

练习7.22:修改你的Person类使其隐藏实现的细节。

class Person
{
friend std::istream &read( istream&, Person& );
friend std::ostream &print( ostream&, const Person&);
private:
string Name;
string Address;
public:
Person() = default;
person( const string &Nam, const string &Adr ): Name( Nam ), Address( Adr ) { }
person( istream &is ) { read( is, *this ) }

public:
string getName() const { return Name; }
string getAddress() const { return Address; }
};

inline std::istream &read( istream &is, Person &aPerson)
{
is >> aPerson.Name >> aPerson.Address;
return is;
}

inline std::ostream &print( ostream &os, const Person &aPerson )
{
os << aPerson.getName() << aPerson.getAddress();
return os;
}


练习7.23:编写你的screen类。

class Screen
{
public:
typedef std::string::size_type pos;

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


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

class Screen
{
public:
typedef std::string::size_type pos;
//构造函数
Screen() = default;
Screen( const pos ht, const pos wt ) : height( ht ), width( wt ), contents( ht * wt, ' ' ) { }
Screen( const pos ht, const pos wt, const char c ) : height( ht ), width( wt ), contents( ht * wt, c ) { }

private:
//数据成员
std::string contents;
pos cursor = 0;
pos height = 0;
pos width = 0;
};


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

能。因为Screen的数据成员都是内置类型(string类定义了拷贝和赋值运算符),因此直接使用类对象执行拷贝和赋值操作是可以的。

练习7.26:将Sales_data::avg_price定义成内联函数。

如果定义在类内部,自动隐式内联。

如果定义在类外部,加上inline

练习7.27:给你自己的Screen类添加move、set和display函数,通过执行下面的代码检验你的类是否正确。

class Screen
{
public:
typedef std::string::size_type pos;
//构造函数
Screen() = default;
Screen( const pos ht, const pos wt ) : height( ht ), width( wt ), contents( ht * wt, ' ' ) { }
Screen( const pos ht, const pos wt, const char c ) : height( ht ), width( wt ), contents( ht * wt, c ) { }
public:
//成员函数
Screen& cursor_move( const pos r, const pos c )
{
cursor = r * width + c;
return *this;
}
Screen& set_ch( const pos r, const pos c, const char ch )
{
contents[ r * width + c ] = ch;
return *this;
}
Screen& set_ch( const char ch )
{
contents[ cursor ] = ch;
return *this;
}
Screen& display( void )
{
std::cout << contents;
return *this;
}

private:
//数据成员
std::string contents;
pos cursor = 0;
pos height = 0;
pos width = 0;
};

#include<iostream>
#include"Screen.h"

using namespace std;

int main()
{
Screen myScreen( 5, 5, 'X' );
myScreen.cursor_move( 4, 0 ).set_ch( '#' ).display();
cout << "\n";
myScreen.display();
cout << "\n";

return 0;
}


练习7.28:如果move 、set 、display函数的返回类型不是Screen&而是Screen,则在上一个练习中将会发生什么情况?

返回引用的函数是左值的,意味着这些函数的返回的是对象本身而不是对象的副本。如果不是Screen,则上述函数各自只返回一个临时副本,不会改变myScreen的值。

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

正确。

myScreen对象的值并没有被改变。

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

通过this指针访问成员的优点:非常明确地指出访问的是对象的成员,并且,可以在成员函数中使用与数据成员同名的形参;

缺点:显得多余,不简洁。

练习7.31:定义一对类X和Y,其中X包含一个指向Y的指针,而Y包含一个类型为X的对象。

要创建一个类对象必须要在该类的定义之后。

class Y; //前向声明

class X

{

Y*  y;//此时Y是一个不完全类型(未被定义)

};

class Y

{

X  x;  //在X的定义

};

练习7.32:定义你自己的Screen和Window_mgr,其中clear是Window_mgr的成员,是Screen的友元。

先空着...在vector<class_type>容器的类型使用自定义类类型出现了问题还没能够解决。

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

修改后:

Screen:: pos Screen::size() const

{

return height * width;

}

练习7.34:如果我们把第256页Screen类的pos的typedef放在类的最后一行会发生什么情况?

编译出现错误。因为这样会造成对pos的使用在pos的声明之前,此时的编译器不知道pos是什么含义。

练习7.35:解释下面代码的含义,说明其中的Type和initVal分别使用了哪个定义。如果代码存在错误,尝试修改它。

-------------------------------------------------------------------------------------------------------------------------------------------------------

插播一条PS:

在类中,如果成员使用了外层作用域某个名字,而该名字代表一种类型,则类不能在之后重新定义该名字。

所以,如果成员没有使用外层作用域的类型的话,类还是能够重新定义同名字的类型的别名的。

-------------------------------------------------------------------------------------------------------------------------------------------------------

f3d4

typedef string Type;//外层作用域,Type是string的类型别名

Type initVal(); //声明initVal函数,该函数的返回类型是外层的Type,也就是string

class Exercise{

public:

typedef double Type; //内层作用域。Type是double的类型别名

Type setVal( Type );  //声明函数setVal, 形参类型和返回类型都是内层的Type,也就是double

Type initVal(); //重新声明initVal函数,返回类型是内层的Type

private:

int val;

};

Type Exercise::setVal( Type parm ) {    //第一个Type是类外的,是string。 第二个Type是类内的,是double

val = parm + initVal();  //此处的initVal是类内的

return val;

}

以上会发生编译错误。因为setVal成员函数的return语句实际返回类型和返回类型不匹配。

所以setVal函数应该修改为:

Exercise::Type Exercise::setVal( Type parm ) { 

val = parm + initVal();  

return val;

}

-----------------------------------------------------------------------------------------------------------------------------------------------------

独立的ps:

如果没有在构造函数的初始值列表中显示地初始化成员,则该成员将在构造函数体之前执行默认初始化。

要养成使用构造函数初始值的习惯。

-----------------------------------------------------------------------------------------------------------------------------------------------------

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

struct X{

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

int rem, base;

};

因为X先定义rem再定义base,所以X构造函数先初始化的是rem,而rem( base % j ) 里的base此时是未定义的。

修改:

struct X{

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

int rem, base;

};

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

略了。

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

class Sales_data

{

public:

Sales_data( std::istream &is = std::cin ) { is >> *this; }

//其他的一致。

}

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

答:

不合法。

如果不提供实参的情况下,会产生二义性错误,编译器无法判断调用这两个构造函数的哪一个。

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

(a)book

class book{

private:

string bookName;

string Author;

string ISBN;

string Publisher;

double price = 0.0;

public:

book() = default;

book( std::istream &is ) { is >> *this; }

book( const string &n, const string &a, const string &I, const string &P, double pr );

};

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

略了。

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

class book{
private:
string bookName;
string Author;
string ISBN;
string Publisher;
double price = 0.0;
public:
book( const string &n, const string &a, const string &I, const string &P, double pr ) :
bookName( n ), Author( a ), ISBN( I ), Publisher( P ), price( pr ) { }
book() : book( "", "", "", "", 0 ) { }
book( std::istream &is ) : book() { is >> *this; }
};

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

#include<iostream>

using namespace std;

class NoDefault{ //NoDefault没有默认构造函数,编译器也不会为其合成一个。
public:
NoDefault( int i ) : val( i ) { }
private:
int val;
};

class C{
public:
C( int m = 0 ) : men( m ) { }

private:
NoDefault mem;
};

练习7.44:下面这条声明合法吗?如果不,为什么?

vector<NoDefault> vec( 10 );

不合法。NoDefault类类型没有默认构造函数,不能默认初始化。编译器报错。

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

合法。C定义了带参数的默认构造函数,可以执行默认初始化。

练习7.46:下面的哪些论断是不正确的?为什么?

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

不正确。因为没有提供构造函数,编译器会合成一个默认构造函数。

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

不正确。默认构造函数也可以带参数,但须提供默认实参。

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

不正确。因为如果一个类没有默认构造函数,则当编译器确实需要隐式地使用默认构造函数时,该类无法使用。所以一般情况下,都应该为类构建一个默认构造函数。

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

不正确。对于编译器合成的默认构造函数来说,类类型的成员执行了各自所属类的默认构造函数,内置类型和复合类型的成员只对定义在全局作用域的对象执行初始化。

--------------------------------------------------------------------------------------------------------------------------------------------------------

插播一条PS:

如果构造函数只接受一个实参,则它实际上定义了转换此类类型的隐式转换机制,这种构造函数称作转换构造函数。构造函数的参数类型向类类型隐式转换。

explicit抑制这种隐式转换。即使explicit声明了,但是如果希望能转换,可以显式地用类构造函数或者使用static_cast强制转换。explicit只能在类内的构造函数声明。

explicit构造函数只能直接初始化。

--------------------------------------------------------------------------------------------------------------------------------------------------------

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

答:应该是explicit的。否则,编译器有可能自动把一个string对象转换成Sales_data对象,这种做法有时候会与初衷相违背。

这样做的优点:避免了因隐式类类型转换而带来意想不到的错误。缺点是,当用户的确需要这样的类类型时,不得不使用略显繁琐的方式来实现。

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

string null_isbn( "9-999-9999-9" ); //将string对象null_isbn初始化成"9-999-9999-9"

Sales_data item1( null_isbn );  //调用接受一个string的Sales_data的构造函数通过null_isbn构造item1对象。

Sales_data item2( "9-999-9999-9");//同上。无须类型转换。

如果Sales_data的构造函数是explicit的,item1和item2依然都能被正确地创建。

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

(a)Sales_data &combine( Sales_data );

s隐式地调用Sales_data的构造函数,生成一个临时的Sales_data对象,然后传递给combine的形参。

(b)Sales_data &combine( Sales_data& );

编译无法通过。因为combine成员函数的形参是非常量引用,但是s自动创建的Sales_data临时对象无法传递给combine所需的非常量引用。(PS:隐式转换生成的无名的临时对象是const的)

修改为:Sales_data &combine( const Sales_data& ) 就可以了。

(c)Sales_data &combine( const Sale_data& ) const;

编译无法通过。因为我们把combine成员函数声明成了常量成员函数,所以该函数无法修改数据成员的值。

练习7.50:确定在你的Person类中是否有一些构造函数应该是explicit的。

Sales_data( std::istream &is ) { is >> *this; }

这个构造函数应该是explicit的。我们更倾向于严格控制Person对象的生成过程,如果确实需要使用Person对象,可以明确指定;其他情况下则不希望自动类型转换的发生。所以应该把这个构造函数声明成explicit的。

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

string接受的单参数类型是const char*类型,如果我们得到了一个常量字符串指针,则把它看做string对象是自然而然的过程。无须指定为explicit的。

但是vector接受的单参数类型是int类型,指定vector的容量。如果在本来需要vector的地方提供一个int值并且希望这个int值自动转换成vector,这个过程比较牵强。

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

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

原意是执行聚合类的初始化。

但是Sales_data的数据成员含有类内初始值,不满足条件。

修改为:

struct Sales_data{

string bookNo;

unsigned units_sold;

double revenue;

};

练习7.53:定义你自己的Debug。

------------------------------------------------------------------------------------------------------------------------------------------------------

字面值常量类:

数据成员都是字面值类型的聚合类。注意是字面值类型,我一开始看成了字面值常量。算术类型、引用和指针都属于字面值类型。某些类也是字面值类型,它们可能含有constexpr函数成员

若不是聚合类,但符合某些要求,则它也是一个字面值常量。

constexpr构造函数用于生成constexpr对象以及constexpr函数的参数或返回类型。

-------------------------------------------------------------------------------------------------------------------------------------------------------

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

不应该。因为constexpr,这些函数的作用是设置数据成员的值,而constexpr函数只能包含return语句,不允许执行其他任务。

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

是字面值常量类。因为Data类是聚合类,然后他的数据成员也全是字面值类型的。

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

------------------------------------------------------------------------------------------------------------------------------------------------------

插播一条PS:

即使一个常量静态数据成员在类内部被初始化了,通常情况下也应该在类的外部定义一下该成员。

静态数据成员可以是不完全类型。特别的:静态数据成员可以就是它所属的类类型,而非静态数据成员则受到限制,只能声明成它所属类的指针或引用。

-------------------------------------------------------------------------------------------------------------------------------------------------------

答:

静态成员是指声明语句之前带有关键字static的类成员,静态成员不是任意单独对象的组成部分,而是由该类的全体对 象所共享。

静态成员的优点包括:作用域位于类的范围之内,避免与其他类的成员或者全局作用域的名字冲突;可以是私有成员,而全局对象不可以;通过阅读程序可以非常容易地看出静态成员与特定类关联,使得程序的含义清晰明了。

静态成员与普通成员的区别主要体现在普通成员与类的对象关联,是某个具体对象的组成部分;而静态成员不从属于任何具体的对象,它由该类的所有对象共享。另外,还有一个细微的区别,静态成员可以作为默认参数,而普通成员不能作为默认参数。

练习7.57:编写你的Account类。

class Account{
public:
static double rate() { return interestRate; }
private:
std::string Name;
double Amount = 0.0;
static double interestRate;
};
练习7.58:下面的静态数据成员的声明和定义有错误吗?请解释原因。

除了静态常量成员之外,其他静态成员不能在类的内部初始化。  

所以rate和vec不能在类内初始化。

所以rate和vec的类外定义必须给出其初始值。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: