C++ Primer 练习题 1
Posted Arthur的仓库
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ Primer 练习题 1相关的知识,希望对你有一定的参考价值。
练习7.1
使用2.6.1节定义的Sales_data
类为1.6节的交易处理程序编写一个新版本。
using namespace std;
struct Sales_data
{
string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
int main() {
Sales_data current;
if (cin >> current.bookNo >> current.units_sold >> current.revenue) {
Sales_data next;
while (cin >> next.bookNo >> next.units_sold >> next.revenue) {
if(current.bookNo == next.bookNo) {
current.units_sold += next.units_sold;
current.revenue += next.revenue;
}
else {
cout << current.bookNo << " " << current.units_sold << " " << current.revenue << endl;
current = next;
}
}
cout << current.bookNo << " " << current.units_sold << " " << current.revenue << endl;
}
else {
cout << "NO data?" << endl;
}
return 0;
}
练习7.2
曾在2.6.2节的练习中编写了一个Sales_data
类,请向这个类添加combine
函数和isbn
成员。
struct Sale_data
{
std::string bookNo;
std::string bookName;
unsigned units_sold = 0;
double revenue = 0.0;
std::string isbn() const { return bookNo; }
Sale_data& combine(const Sale_data&);
};
Sale_data& Sale_data::combine(const Sale_data &rhs) {
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
练习7.3
修改7.1.1节的交易处理程序,令其使用这些成员。
using namespace std;
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 << 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.4
struct Person {
std::string name;
std::string address;
};
练习7.5
struct Person {
std::string name;
std::string address;
std::string getName() const { return name; }
std::string getAddress() const { return address; }
};
应该是const
的。因为常量的Person
对象也需要使用这些函数操作。
练习7.6
对于函数add
、read
和print
,定义你自己的版本。
struct Sales_data {
std::string const& isbn() const { return bookNo; };
Sales_data& combine(const Sales_data&);
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
// member functions.
Sales_data& Sales_data::combine(const Sales_data &rhs)
{
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
// nonmember functions
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, 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;
}
练习7.7
使用这些新函数重写7.1.2节练习中的程序。
int main()
{
Sales_data total;
if (read(std::cin, total))
{
Sales_data trans;
while (read(std::cin, trans)) {
if (total.isbn() == trans.isbn())
total.combine(trans);
else {
print(std::cout, total) << std::endl;
total = trans;
}
}
print(std::cout, total) << std::endl;
}
else
{
std::cerr << "No data?!" << std::endl;
return -1;
}
return 0;
}
练习7.8
为什么read
函数将其Sales_data
参数定义成普通的引用,而print
函数将其参数定义成常量引用?
因为read
函数会改变对象的内容,而print
函数不会。
练习7.9
对于7.1.2节练习中代码,添加读取和打印Person
对象的操作。
struct Person {
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, const Person &p) {
std::cout << p.name << " " << p.address;
return os;
}
练习7.10
在下面这条if
语句中,条件部分的作用是什么?
if (read(read(cin, data1), data2))
read
函数的返回值是istream
对象,先从标准输入流中向data1写入数据,如果这个动作成功,则继续向data2中写入数据。
练习7.11 :
在你的Sales_data
类中添加构造函数, 然后编写一段程序令其用到每个构造函数。
Sales_data.h
struct Sales_data;
std::istream &read(std::istream&, Sales_data&);
std::ostream &print(std::ostream&, const Sales_data&);
Sales_data add(const Sales_data&, const Sales_data&);
struct Sales_data {
Sales_data() = default;
Sales_data(std::string bN, unsigned n, double p) : bookNo(bN), units_sold(n), revenue(n * p) { }
Sales_data(std::string bN) : bookNo(bN) { }
Sales_data(const Sales_data&);
Sales_data(std::istream&);
std::string const& isbn() const { return bookNo; };
Sales_data& combine(const Sales_data&);
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
Sales_data::Sales_data(const Sales_data &s) {
bookNo = s.bookNo;
units_sold = s.units_sold;
revenue = s.revenue;
}
Sales_data::Sales_data(std::istream &is) {
read(is, *this);
}
Sales_data& Sales_data::combine(const Sales_data& rhs)
{
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
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, 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;
}
main.cpp
int main() {
Sales_data s1;
print(std::cout, s1);
std::cout << std::endl;
Sales_data s2("0-201-78345-X", 3, 20.00);
print(std::cout, s2);
std::cout << std::endl;
Sales_data s3("0-201-78345-X");
print(std::cout, s3);
std::cout << std::endl;
Sales_data s4(s2);
print(std::cout, s4);
std::cout << std::endl;
Sales_data s5(std::cin);
print(std::cout, s5);
std::cout << std::endl;
}
练习7.12
把只接受一个istream
作为参数的构造函数移到类的内部。
Sales_data(std::istream &is) { read(is, *this); }
练习7.13
使用istream
构造函数重写第229页的程序。
using namespace std;
int main()
{
Sales_data total(cin);
if (!total.isbn().empty())
{
while (cin) {
Sales_data trans(cin);
if (!cin) break;
if (total.isbn() == trans.isbn()) {
total.combine(trans);
} else {
print(cout, total);
cout << endl;
total = trans;
}
}
print(cout, total);
cout << endl;
}
else
{
std::cerr << "No data?!" << std::endl;
return -1;
}
return 0;
}
练习7.14
编写一个构造函数,令其用我们提供的类内初始值显式地初始化成员。
Sales_data() :
bookNo(std::string()), units_sold(0), revenue(0.0) { }
练习7.15
为你的Person
类添加正确的构造函数。
Person.h
struct Person;
std::istream &read(std::istream &, Person &);
std::ostream &print(std::ostream &, const Person &);
struct Person {
Person() = default;
Person(const std::string &sname, const std::string &saddress) : name(sname), address(saddress) { }
Person(const std::string &sname) : name(sname) { }
Person(const Person &p) : name(p.name), address(p.address) { }
Person(std::istream &);
std::string getName() const { return name; }
std::string getAddress() const { return address; }
std::string name;
std::string address;
};
Person::Person(std::istream &is) {
read(is, *this);
}
std::istream &read(std::istream &is, Person &p) {
is >> p.name >> p.address;
return is;
}
std::ostream &print(std::ostream &os, const Person &p) {
std::cout << p.name << " " << p.address;
return os;
}
main.cpp
int main()
{
Person p;
print(std::cout, p);
std::cout << std::endl;
Person p1("Charles", "Fengcheng 1st Road, Xi'an City, Shaanxi Province");
print(std::cout, p1);
std::cout << std::endl;
Person p2("Charles");
print(std::cout, p2);
std::cout << std::endl;
Person p3(p1);
print(std::cout, p3);
std::cout << std::endl;
Person p4(std::cin);
print(std::cout, p4);
std::cout << std::endl;
return 0;
}
练习7.16
在类的定义中对于访问说明符出现的位置和次数有限定吗?如果有,是什么?什么样的成员应该定义在public
说明符之后?什么样的成员应该定义在private
说明符之后?
在类的定义中对于访问说明符出现的位置和次数没有限定。
每个访问说明符指定了接下来的成员的访问级别,其有效范围直到出现下一个访问说明符或者达到类的结尾处为止。
如果某个成员能够在整个程序内都被访问,那么它应该定义为public
; 如果某个成员只能在类内部访问,那么它应该定义为private
。
练习7.17
使用class
和struct
时有区别吗?如果有,是什么?
class
和struct
的唯一区别是默认的访问级别不同。
练习7.18
封装是何含义?它有什么用处?
将类内部分成员设置为外部不可见,而提供部分接口给外面,这样的行为叫做封装。
用处:
1.确保用户的代码不会无意间破坏封装对象的状态。
2.被封装的类的具体实现细节可以随时改变,而无需调整用户级别的代码。
练习7.19
在你的Person
类中,你将把哪些成员声明成public
的?哪些声明成private
的?解释你这样做的原因。
构造函数、getName()
、getAddress()
函数将设为public
。 name
和 address
将设为private
。函数是暴露给外部的接口,因此要设为public
;而数据则应该隐藏让外部不可见。
练习7.20
友元在什么时候有用?请分别举出使用友元的利弊。
当其他类或者函数想要访问当前类的私有变量时,这个时候应该用友元。
利:
与当前类有关的接口函数能直接访问类的私有变量。
弊:
牺牲了封装性与可维护性。
练习7.21
修改你的Sales_data
类使其隐藏实现的细节。你之前编写的关于Sales_data
操作的程序应该继续使用,借助类的新定义重新编译该程序,确保其正常工作。
// 统一在类的外部进行独立声明:
class Sales_data;
Sales_data add (const Sales_data, const Sales_data&);
std::istream& read(std::istream&, Sales_data&);
std::ostream& print(std::ostream&, const Sales_data&);
class Sales_data {
friend std::istream &read(std::istream&, Sales_data&);
friend std::ostream &print(std::ostream&, const Sales_data&);
friend Sales_data add(const Sales_data&, const Sales_data&);
public:
Sales_data() = default;
Sales_data(const std::string &s):bookNo(s) { }
Sales_data(const std::string &s, unsigned n, double p):bookNo(s), units_sold(n), revenue(n*p){ }
Sales_data(std::istream &is) { read(is, *this); }
std::string isbn() const { return bookNo; };
Sales_data& combine(const Sales_data&);
private:
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
// member functions.
Sales_data& Sales_data::combine(const Sales_data& rhs)
{
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
// friend functions
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, 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;
}
练习7.22
修改你的Person
类使其隐藏实现的细节。
class Person;
std::istream& read(std::istream, Person&);
std::ostream& print(std::ostream&, const Person&);
class Person {
friend std::istream &read(std::istream&, Person&);
friend std::ostream &print(std::ostream&, const Person&);
public:
Person() = default;
Person(const std::string &sname, const std::string &saddress) : name(sname), address(saddress) { }
Person(const std::string &sname) : name(sname) { }
Person(const Person &p) : name(p.name), address(p.address) { }
Person(std::istream &);
std::string getName() const { return name; }
std::string getAddress() const { return address; }
private:
std::string name;
std::string address;
};
Person::Person(std::istream &is) {
read(is, *this);
}
std::istream &read(std::istream &is, Person &p) {
is >> p.name >> p.address;
return is;
}
std::ostream &print(std::ostream &os, const Person &p) {
std::cout << p.name << " " << p.address;
return os;
}
练习7.23
编写你自己的Screen
类型。
class Screen {
public:
using pos = std::string::size_type;
Screen() = default;
Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht*wd, c) { }
char get() const { return contents[cursor]; }
char get(pos r, pos c) const { return contents[r*width+c]; }
Screen& move(pos r, pos c) { cursor = r*width+c; return *this; }
private:
mutable pos cursor = 0;
pos height = 0, width = 0;
std::string contents;
};
练习7.24
给你的Screen
类添加三个构造函数:一个默认构造函数;另一个构造函数接受宽和高的值,然后将contents
初始化成给定数量的空白;第三个构造函数接受宽和高的值以及一个字符,该字符作为初始化后屏幕的内容。
class Screen {
public:
using pos = std::string::size_type;
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]; }
char get(pos r, pos c) const { return contents[r*width+c]; }
Screen& move(pos r, pos c) { cursor = r*width+c; return *this; }
private:
mutable pos cursor = 0;
pos height = 0, width = 0;
std::string contents;
};
练习7.25
Screen
能安全地依赖于拷贝和赋值操作的默认版本吗?如果能,为什么?如果不能?为什么?
能。 Screen
的成员只有内置类型和string
,因此能安全地依赖于拷贝和赋值操作的默认版本。
管理动态内存的类则不能依赖于拷贝和赋值操作的默认版本,而且也应该尽量使用string
和vector
来避免动态管理内存的复杂性。
练习7.26
将Sales_data::avg_price
定义成内联函数。
在头文件中加入:
inline double Sales_data::avg_price() const
{
return units_sold ? revenue/units_sold : 0;
}
练习7.27
给你自己的Screen
类添加move
、set
和display
函数,通过执行下面的代码检验你的类是否正确。
Screen myScreen(5, 5, 'X');
myScreen.move(4, 0).set('#').display(cout);
cout << "\n";
myScreen.display(cout);
cout << "\n";
class Screen {
public:
using pos = std::string::size_type;
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]; }
char get(pos r, pos c) const { return contents[r*width+c]; }
Screen& move(pos r, pos c) { cursor = r*width+c; return *this; }
Screen& set(pos r, pos c, char ch) { contents[r*width+c] = ch; return *this; }
Screen& set(char ch) { contents[cursor] = ch; return *this; }
Screen& display(std::ostream& os) { os << contents; return *this; }
const Screen& display(std::ostream& os) const { os << contents; return *this; }
private:
pos cursor = 0;
pos height = 0, width = 0;
std::string contents;
};
练习7.28
如果move
、set
和display
函数的返回类型不是Screen&
而是Screen
,则在上一个练习中将会发生什么?
如果返回类型是Screen
,那么move
返回的是*this
的一个副本,因此set
函数只能改变临时副本而不能改变myScreen
的值。
练习7.29
修改你的Screen
类,令move
、set
和display
函数返回Screen
并检查程序的运行结果,在上一个练习中你的推测正确吗?
推测正确。
'&' with
XXXX
XXXX
^
'&' without
XXXX
XXXXXXXXXXXXXXXXXXXXXXXXX
^
练习7.30
通过this
指针使用成员的做法虽然合法,但是有点多余。讨论显示使用指针访问成员的优缺点。
优点:
程序的意图更明确
函数的参数可以与成员同名,如
void setAddr(const std::string &addr) { this->addr = addr; }
缺点:
有时候显得有点多余,如
std::string getAddr() const { return this->addr; }
练习7.31
定义一对类X
和Y
,其中X
包含一个指向Y
的指针,而Y
包含一个类型为X
的对象。
class Y;
class X{
Y* y = nullptr;
};
class Y{
X x;
};
练习7.32
定义你自己的Screen
和Window_mgr
,其中clear
是Window_mgr
的成员,是Screen
的友元。
class Screen;
class Window_mgr
{
public:
using ScreenIndex = std::vector<Screen>::size_type;
inline void clear(ScreenIndex);
private:
std::vector<Screen> screens;
};
class Screen {
friend void Window_mgr::clear(ScreenIndex);
public:
using pos = std::string::size_type;
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]; }
char get(pos r, pos c) const { return contents[r*width+c]; }
Screen& move(pos r, pos c) { cursor = r*width+c; return *this; }
Screen& set(pos r, pos c, char ch) { contents[r*width+c] = ch; return *this; }
Screen& set(char ch) { contents[cursor] = ch; return *this; }
Screen& display(std::ostream& os) { os << contents; return *this; }
const Screen& display(std::ostream& os) const { os << contents; return *this; }
private:
pos cursor = 0;
pos height = 0, width = 0;
std::string contents;
};
inline void Window_mgr::clear(ScreenIndex i) {
Screen& s = screens[i];
s.contents = std::string(s.height*s.width,' ');
};
以上是关于C++ Primer 练习题 1的主要内容,如果未能解决你的问题,请参考以下文章