本文为C++ primer第5版第七章的练习题记录,因为是初学者,觉得这一章比较难,代码质量应该不高..........
7.1.1节练习
练习7.1
#include <iostream>
#include <string>
using namespace std;
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;
total.revenue += trans.revenue;
}
else
{
cout << total.bookNo << " " << total.units_sold << " " << total.revenue << endl;
total = trans;
}
}
cout << total.bookNo << " " << total.units_sold << " " << total.revenue << endl;
}
else
{
std::cerr << "No data?!" << std::endl;
return -1;
}
return 0;
}
7.1.2节练习
练习7.2
struct Sales_data
{
string isbn() const { return bookNo; };
Sales_data& combine(const Sales_data&);
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;
}
练习7.3
struct Sales_data
{
string isbn() const { return bookNo; };
Sales_data& combine(const Sales_data&);
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;
}
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.combine(trans);
}
else
{
cout << total.bookNo << " " << total.units_sold << " " << total.revenue << endl;
total = trans;
}
}
cout << total.bookNo << " " << total.units_sold << " " << total.revenue << endl;
}
else
{
cerr << "No data?!" << std::endl;
return -1;
}
return 0;
}
练习7.4
struct Person
{
string Name;
string Address;
};
练习7.5
struct Person
{
string isname() const { return Name; }
string isAddress() const { return Address; }
string Name;
string Address;
};
应该是const,因为该函数只调用而并不改变调用对象的内容。
7.1.3节练习
练习7.6
略
练习7.7
struct Sales_data
{
string isbn() const { return bookNo; };
Sales_data& combine(const Sales_data&);
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;
}
istream& read(istream& is, Sales_data& item)
{
double price = 0;
is >> item.bookNo >> item.units_sold >> item.revenue;
return is;
}
ostream& print(ostream& os, const Sales_data& item)
{
os << item.isbn() << " " << item.units_sold << " " << item.revenue ;
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.bookNo == trans.bookNo)
{
total = add(total,trans);
}
else
{
print(cout,total);
cout << endl;
total = trans;
}
}
print(cout, total);
cout << endl;
}
else
{
cerr << "No data?!" << std::endl;
return -1;
}
return 0;
}
练习7.8
因为读取和写入的操作会改变流的内容,所以 read 函数接受的是普通引用。
打印不会改变流的内容,所以 print 函数将其参数定义成常量引用。
练习7.9
struct Person
{
string isname() const { return Name; }
string isAddress() const { return Address; }
string Name;
string Address;
};
istream& read(istream& is, Person& human)
{
is >> human.Name >> human.Address;
return is;
}
ostream& print(ostream& os, const Person& human)
{
os << human.Name << " " << human.Address;
return os;
}
练习7.10
if 检验是否 data1 和 data2 的数据都读入了,都读入了则条件满足,反之不满足。
7.1.4节练习
练习7.11
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*n) { }
Sales_data(istream &is);
string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
int main()
{
Sales_data data1;
Sales_data data2("123456789");
Sales_data data3("123456789, 5 , 200");
Sales_data data4(cin);
cout << data1; // 最后一句报错,没有与这些操作数匹配的>>运算符,还不知道为什么,我好菜,等我学会了再回来改好了
}
练习7.12
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*n) { }
Sales_data(istream &is);
string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
int main()
{
Sales_data data1;
Sales_data data2("123456789");
Sales_data data3("123456789, 5 , 200");
Sales_data data4(cin);
cout << data1;
}
练习7.13
int main()
{
Sales_data total(cin);
if (cin)
{
Sales_data trans;
while (read(cin,trans))
{
if (total.bookNo == trans.bookNo)
{
total.combine(trans);
}
else
{
cout << total.bookNo << " " << total.units_sold << " " << total.revenue << endl;
total = trans;
}
}
cout << total.bookNo << " " << total.units_sold << " " << total.revenue << endl;
}
else
{
cerr << "No data?!" << std::endl;
return -1;
}
return 0;
}
练习7.14
Sales_data(const string &s):
bookNo(s), units_sold(0), revenue(0) {}
练习7.15
struct Person
{
/* 构造函数 */
Person(const string& s) :
Name(s), Address(s) { }
string isname() const { return Name; }
string isAddress() const { return Address; }
string Name;
string Address;
};
istream& read(istream& is, Person& human)
{
is >> human.Name >> human.Address;
return is;
}
ostream& print(ostream& os, const Person& human)
{
os << human.Name << " " << human.Address;
return os;
}
7.2节练习
练习7.16
在类的定义中对于访问说明符出现的位置没有严格要求。作为接口的一部分,构造函数和部分成员函数紧跟在 public 说明符之后,而数据成员和作为实现部分的函数则跟在 private 说明符后面。
一个类可以包含0个或多个访问说明符,而且对于某个访问说明符能出现多少次也没有严格规定。
练习7.17
使用class 和 struct 定义类的唯一区别就是默认的访问权限不一样。
练习7.18
封装就是分离类的实现与接口,从而隐藏类的实现细节。在C++语言中,通过把实现部分设为private 完成封装的任务。
练习7.19
在我的Person类中,将构造函数声明为public,Name 和 Address 两个数据成员声明为 private。
7.2.1节练习
练习7.20
需要类允许其他类或者函数访问它的非公有成员,可以使用友元。
利:使非成员函数访问类的私有成员;
弊:若使用不慎,可能破坏类的封装性。
练习7.21
struct Sales_data
{
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* n) { }
Sales_data(istream& is);
private:
string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
练习7.22
struct Person
{
public:
/* 构造函数 */
Person(const string& s) :
Name(s), Address(s) { }
string isname() const { return Name; }
string isAddress() const { return Address; }
private:
string Name;
string Address;
};
7.3.1节练习
练习7.23
class Screen
{
private:
int cursor = 0;
double height = 0, width = 0;
string contents;
};
练习7.24
class Screen
{
public:
Screen() = default;
Screen(double ht, double wd) : height(ht),width(wd),contents(ht * wd,' ') { }
Screen(double ht, double wd, char c) : height(ht), width(wd), contents(ht* wd,c) { }
private:
int cursor = 0;
double height = 0, width = 0;
string contents;
};
练习7.25
可以,因为Screen的4个数据成员都是内置类型。
练习7.26
class Screen
{
public:
double avg_price() const;
};
inline double Sales_data::avg_price() const
{
if (units_sold)
return revenue / units_sold;
else
return 0;
}
7.3.2节练习
练习7.27
class Screen
{
public:
Screen() = default;
Screen(double ht, double wd) : height(ht), width(wd), contents(ht* wd, ' ') { }
Screen(double ht, double wd, char c): height(ht), width(wd), contents(ht* wd, c) { }
private:
int cursor = 0;
double height = 0, width = 0;
string contents;
public:
Screen& move(unsigned r, unsigned c)
{
cursor = r * width + c;
return *this;
}
Screen& set(char ch)
{
contents[cursor] = ch;
return *this;
}
Screen& set(unsigned r, unsigned c,char ch)
{
contents[r * width + c] = ch;
return *this;
}
Screen& display()
{
cout << contents;
return *this;
}
};
int main()
{
Screen myScreen(5, 5, 'X');
myScreen.move(4, 0).set('#').display();
cout << "\n";
myScreen.display();
cout << "\n";
}
练习7.28
myScreen 的值不会改变。
练习7.29
正确。
练习7.30
通过this指针访问成员的优点是,可以非常明确地指出访问的是对象的成员,并且可以在成员函数中使用与数据成员同名的形参;缺点是显得多余,代码不够简洁。
7.3.3节练习
练习7.31
class X;
class Y
{
X* x;
};
class X
{
Y y;
};
练习7.32
class Window_mgr
{
public:
void clear();
};
class Screen
{
friend void Window_mgr::clear();
public:
Screen() = default;
Screen(double ht, double wd, char c) : height(ht), width(wd), contents(ht * wd, c) { }
private:
int cursor = 0;
double height = 0, width = 0;
string contents;
};
void Window_mgr::clear()
{
}
7.4节练习
练习7.33
会报错,修改后的程序为
Screen::pos Screen::size() const
{
return height * width;
}
7.4.1节练习
练习7.34
编译会报错,因为使用了未定义的pos。
练习7.35
类内Type为double,Exercise::setVal的形参类型Type为double,返回类型Type为String。
错误:Exercise::setVal的类内同名函数形参类型为double,返回类型也为double,与类外部Exercise::setVal函数的定义不一致,会报错。
改正:类外Exercise::setVal函数的返回类型改为Exercise::Type。
7.5.1节练习
练习7.36
将 int rem,base;改为 int base,rem;令其先初始化base。
练习7.37
Sales_data first_item (cin); 使用了接受std::istream & 参数的构造函数,该对象的数据成员值依赖于用户的输入。
Sales_data next;使用了默认构造函数,string类型成员初始化为空字符串,其他成员初始化为0;
Sales_data last;使用了接受const string& 参数的构造函数,bookNo 初始化为“9-999-99999-9”,其他成员初始化为0。
练习7.38
Sales_data(sta::istream &is = std::cin) {is >> *this;}
练习7.39
不合法,可能会出现二义性错误。
练习7.40
(b) Date,定义其数据成员为 date,Month,Year,三者都为int类型。
class Date
{
private:
int date, Month, Year;
public:
Date() = default;
Date(int i, int j, int m):
date(i), Month(j), Year(m) {}
};
7.5.2节练习
练习7.41
class Sales_data
{
private:
string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
/* 委托构造函数*/
public:
Sales_data(const string& s, unsigned n, double p) : bookNo(s), units_sold(n), revenue(p* n) { }
Sales_data() : Sales_data(" ", 0, 0) { cout << "执行成功"; }
Sales_data(const string s) : Sales_data(s, 0, 0) { cout << "执行成功"; }
Sales_data(istream& is) : Sales_data() { cout << "执行成功"; }
};
int main()
{
Sales_data data1;
Sales_data data2("123456789");
Sales_data data3("123456789, 5 , 200");
Sales_data data4(cin);
}
练习7.42
class Date
{
private:
int date, Month, Year;
public:
Date(int i, int j, int m) :
date(i), Month(j), Year(m) {}
Date():Date(0, 0, 0) { }
};
7.5.3节练习
练习7.43
class NoDefault
{
public:
int val;
NoDefault(int i)
{
val = i;
}
};
class C
{
public:
NoDefault nodefault;
C(int i = 5) : nodefault(i) { }
};
int main()
{
C c;
cout << c.nodefault.val << endl;
}
练习7.44
不合法,因为NoDefault中无默认构造函数。
练习7.45
合法,因为C定义了默认构造函数。
练习7.46
都是不正确的。
(a)是错误的,类可以不提供任何构造函数,这时编译器自动实现一个合成的默认构造函数。
(b)是错误的,如果某个构造函数包含若干形参,但是同时为这些形参都提供了默认实参,则该构造函数也具备默认构造函数的功能。
(c)是错误的,因为如果一个类没有默认构造函数,也就是说我们定义了该类的某些构造函数但是没有为其设计默认构造函数,则当编译器确实需要隐式地使用默认构造函数时,该类无法使用。所以一般情况下,都应该为类构建一个默认构造函数。
(d)是错误的,对于编译器合成的默认构造函数来说,类类型的成员执行各自所属类的默认构造函数,内置类型和复合类型的成员只对定义在全局作用域中的对象执行初始化。
7.5.4节练习
练习7.47
应该是 explicit 的,否则 string 对象可能会被隐式转换成 Sales_data 对象。
练习7.48
第一行创建一个 string 对象。
第二行调用 Sales_data 的构造函数创建它的对象。
第三行调用 Sales_data 的构造函数创建它的对象。
若 Sales_data 的构造函数是 explicit 的,也会发生上述的操作。
练习7.49
(a) 将给定的 string 对象隐式转换成 Sales_data 对象,然后新生成的临时对象传给combine形参,函数正确执行并返回结果。
(b) 编译报错,因为隐式转换后的临时对象无法传递给 combine 所需的非常累引用。
(c) 编译报错,因为把 combine 声明成了常量成员函数,所以该函数无法修改数据成员的值。
练习7.50
无
练习7.51
string 接受的单参数是 const char* 类型,如果我们得到了一个常量字符指针,则把它看作string对象是自然而然的过程,编译器自动把参数类型转换成类类型也非常符合逻辑。因此我们无需指定为 explicit 的。
与 string 相反,vector接受的单参数是 int 类型,这个参数的原意是指定 vector 的容量。如果我们在本来需要 vector 的地方提供一个 int 值并希望这个 int 值自动转换成 vector,则这个过程显得比较牵强,因此把 vector 的单参数构造函数定义成 explicit 的更加合理。
7.5.5节练习
练习7.52
2.6.1节中的Sales_data 类不符合聚合类的要求,将类内初始值删掉即可。
7.5.6节练习
练习7.53
class Debug
{
public:
constexpr Debug(bool b = true): hw(b), io(b), other(b) { }
constexpr Debug(bool h, bool i, bool o) : hw(b), 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.54
本应该,因为 constexpr 函数能拥有的唯一执行语句就是返回语句,而set_ 开头的成员执行的操作是赋给成员数据。
练习7.55
是,数据成员都是字面值类型的聚合类是字面值常量类。
7.6节练习
练习7.56
静态成员是指声明语句之前带有关键字 static 的类成员,静态成员不是任意单独对象的组成部分,而是由该类的全体对象所共享。
优点:作用域位于类的范围之内,避免与其他类的成员或者全局作用域的名字冲突;可以是私有成员,而全局对象不可以;通过阅读程序可以非常容易地看出静态成员与特定类关联,使得程序的含义清晰明了。
静态成员与普通成员的区别主要体现在普通成员与类的对象关联,是某个具体对象的组成部分;而静态成员不从属于任何具体的对象,它由该类的所有对象共享。另外,还有一个细微的区别,静态成员可以作为默认实参,而普通数据成员不能作为默认实参。
练习7.57
略。
练习7.58
除了静态常量成员之外,其他静态成员不能在类的内部初始化。
example.C 文件的语句也是错误的,因为在这里我们必须给出静态成员的初始值。