What & How & Why

Chapter.7

第七章的习题答案


Ex.7.1-7.10

ex.7.1
Exercise 7.1: Write a version of the transaction-processing program from § 1.6 (p. 24) using the Sales_data class you defined for the exercises in §

CODE: source CODE: header

ex.7.2
Exercise 7.2: Add the combine and isbn members to the Sales_dataclass you wrote for the exercises in § 2.6.2 (p. 76).

CODE: header

ex.7.3
Exercise 7.3: Revise your transaction-processing program from § 7.1.1 (p.256) to use these members.

CODE: Q.1

ex.7.4
Exercise 7.4: Write a class named Person that represents the name andaddress of a person. Use a string to hold each of these elements.Subsequent exercises will incrementally add features to this class.

CODE: header

ex.7.5
Exercise 7.5: Provide operations in your Person class to return the nameand address. Should these functions be const? Explain your choice.

CODE: header

These functions should be declared as const, as they are not meant to change the content of the object they are called from.

ex.7.6
Exercise 7.6: Define your own versions of the add, read, and print functions.

CODE: header

ex.7.7
Exercise 7.7: Rewrite the transaction-processing program you wrote for the exercises in § 7.1.2 (p. 260) to use these new functions.

CODE: Q.1

ex.7.8
Exercise 7.8: Why does read define its Sales_data parameter as a plain reference and print define its parameter as a reference to const?

Because the 'read' function modified the value of revenue, a member of the class “Sales_data”, whereas the 'print' function did not alter the members of the class, but only printed and called them.

ex.7.9
Exercise 7.9: Add operations to read and print Person objects to the code you wrote for the exercises in § 7.1.2 (p. 260).
ex.7.10
Exercise 7.10: What does the condition in the following if statement do?

if (read(read(cin, data1), data2))
Since the inner “read” function returns an “istream” object, the if statement is an operation that continues to read input obejcts from data1 to data2. If any of these readings fail, the if statement will return a false value, but otherwise it will return true.

Ex.7.11-7.20

ex.7.11
Exercise 7.11: Add constructors to your Sales_data class and write a program to use each of the constructors.

CODE: source header

ex.7.12
Exercise 7.12: Move the definition of the Sales_data constructor that takes an istream into the body of the Sales_data class

CODE: header

ex.7.13
Exercise 7.13: Rewrite the program from page 255 to use the istream constructor.

CODE: Q.1

ex.7.14
Exercise 7.14: Write a version of the default constructor that explicitly initializes the members to the values we have provided as in-class initializers.

Sales_data() : book_no(""), unit_sold(0), revenue(0.0) {}

ex.7.15
Exercise 7.15: Add appropriate constructors to your Person class.

CODE: source header

ex.7.16
Exercise 7.16: What, if any, are the constraints on where and how often an access specifier may appear inside a class definition? What kinds of members should be defined after a public specifier? What kinds should be private?

There is no constraint on where and how often an access specifier may appear inside a class definition. Members belonging to a part of an interface, for example, the constructor or member function, should be defined after a public specifier. Class implementation members, such as built-in type members and class implementation functions, should be defined after private specifiers.

ex.7.17
Exercise 7.17: What, if any, are the differences between using class or struct?

The only difference between class and struct specifier is how they define a default access level. By default, members in a struct are defined as public untill they meet the first specifyer, and vice versa for a class .

ex.7.18
Exercise 7.18: What is encapsulation? Why is it useful?

Encapsulation is one of the most important aspects of class structure. It means that a class should be divided into two parts: interfaces and implementations. It allows implementers to focus on implementing the class, while the user may concentrate on using the interface's functions without knowing what is happening inside the class. Thus, the user's actions and the developer's actions will be isolated and won't interfere with each other.

ex.7.19
Exercise 7.19: Indicate which members of your Person class you would declare as public and which you would declare as private. Explain your choice.
  • public members: constructors, get_name(), get_addr(), they are parts of the interface.
  • private members: name, address, they are parts of the implemtaion.
ex.7.20
When are friends useful? Discuss the pros and cons of using friends.

The functions that are part of the class in aspect are not a part of the implementation of the class, but they need access to the encapsulated resource of the class. Declaring those functions as friends allows access to those resources.

  • Pros: In some cases, the implementation requires data exchange between different classes or functions, but those data are not intended for public consumption. In that case, Friend works well.
  • Cons: The encapsulation of a class will be lessened if it is friended.

Ex.7.21-7.30

ex.7.21
Exercise 7.21: Update your Sales_data class to hide its implementation. The programs you’ve written to use Sales_data operations should still continue to work. Recompile those programs with your new class definition to verify that they still work.

CODE: source header

ex.7.22
Exercise 7.22: Update your Person class to hide its implementation.

CODE: source header

ex.7.23
Exercise 7.23: Write your own version of the Screen class.

CODE: source header

ex.7.24
Exercise 7.24: Give your Screen class three constructors: a default constructor; a constructor that takes values for height and width and initializes the contents to hold the given number of blanks; and a constructorthat takes values for height, width, and a character to use as the contents ofthe screen.

CODE: source header

ex.7.25
Exercise 7.25: Can Screen safely rely on the default versions of copy and assignment? If so, why? If not, why not?

As all of the members are related to std::string, the Screen class can rely on its default versions of copy and assignment, which have well-defined copy and assignment behavior.

ex.7.26
exercise 7.26: Define Sales_data::avg_price as an inline function.

CODE: header

ex.7.27
Exercise 7.27: Add the move, set, and display operations to your version of Screen. Test your class by executing the following code:

Screen myScreen(5,5, 'X');
myScreen.move(4,0).set('#').display(cout);
cout << "\n";
myScreen.display(cout);
cout << "\n";
CODE: header source

ex.7.28
Exercise 7.28: What would happen in the previous exercise if the return type of move, set, and display was Screen rather than Screen&?

In the second call, As the return-by-value method returns a copy of the class, the move and set functions will not change anything in the myScreen object. The display function will still print out the default value of myScreen.

ex.7.29
Exercise 7.29: Revise your Screen class so that move, set, and display functions return Screen and check your prediction from the previous exercise.

//return by reference
XXXXXXXXXXXXXXXXXXXX#XXXX
XXXXXXXXXXXXXXXXXXXX#XXXX
//return by value
XXXXXXXXXXXXXXXXXXXX#XXXX
XXXXXXXXXXXXXXXXXXXXXXXXX

ex.7.30
Exercise 7.30: It is legal but redundant to refer to members through the this pointer. Discuss the pros and cons of explicitly using the this pointer to access members.

If a member function has a local variable that hides a member, the this pointer must be explicitly used:

class example {
	int i = 0;
	void func {
		int i = 1;
		cout << i; //print the local variable i
		cout << this->a; //print the member i
	}
};
Ref:When should I make explicit use of the `this` pointer?

Ex.7.31-7.40

ex.7.31
Exercise 7.31: Define a pair of classes X and Y, in which X has a pointer to Y, and Y has an object of type X.

CODE: header

ex.7.32
Exercise 7.32: Define your own versions of Screen and Window_mgr in which clear is a member of Window_mgr and a friend of Screen.

CODE: header

ex.7.33
Exercise 7.33: What would happen if we gave Screen a size member defined as follows? Fix any problems you identify.

pos Screen::size() const {
	return height * width;
}
If 'pos' is not defined outside of the class, then there will be an undeclared type error. If it is defined outside, the function size might have a declaration conflict with its inside declaration as they returned different type of pos. In order to return the class member pos, we need to specify the class scope:
Screen::pos 
Screen::size() const {
	return height * width;
}

ex.7.34
Exercise 7.34: What would happen if we put the typedef of pos in the Screen class on page 285 as the last line in the class?

There will be an undeclare error since type names must appear before using.

ex.7.35
Exercise 7.35: Explain the following code, indicating which definition of Type or initVal is used for each use of those names. Say how you would fix any errors.

typedef string Type;
Type initVal(); //string
class Exercise {
public:
	typedef double Type;
	Type setVal(Type); //double & double
	Type initVal(); //double
private:
	int val;
};

Type Exercise::setVal(Type parm) { //return a string, parameter is double
	val = parm + initVal();  //double
	return val;
}
Fix:
Type Exercise::setVal(Type parm)  //to
Exercise::type Exercise::setVal(Type parm)

ex.7.36
Exercise 7.36: The following initializer is in error. Identify and fix the problem.

struct X {
	X (int i, int j): base(i), rem(base % j) { }
	int rem, base;
};
In thi program, the constructor attempts to initialize base using i, and then initialize rem by using the initialized member base. However, due to the order of the members, initializing rem actually occurs first; therefore, the constructor supplied an uninitialized member base to initialize the member rem, which is an undefined behavior. In this case, a better option would be to use i and j directly as initialize values:
struct X {
	X (int i, int j): base(i), rem(i % j) { }
	int rem, base;
};

ex.7.37
Exercise 7.37: Using the version of Sales_data from this section, determine which constructor is used to initialize each of the following variables and list the values of the data members in each object:

Sales_data first_item(cin); //Sales_data(std::istream &is), the values depend on the input
int main() {
	Sales_data next; //Sales_data(std::string s = ""): bookNo(s) , ("", 0, 0)
	Sales_data last("9-999-99999-9");//Sales_data(std::string s = ""): bookNo(s), ("9-999-99999-9", 0, 0)
}

ex.7.38
Exercise 7.38: We might want to supply cin as a default argument to the constructor that takes an istream&. Write the constructor declaration that uses cin as a default argument.

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

ex.7.39
Exercise 7.39: Would it be legal for both the constructor that takes a string and the one that takes an istream& to have default arguments? If not, why not?

No, it is illegal. If all of them take their default argument to initialize the members, they all become the default constructor, which will cause overload ambiguous.

ex.7.40
Exercise 7.40: Choose one of the following abstractions (or an abstraction of your own choosing). Determine what data are needed in the class. Providean appropriate set of constructors. Explain your decisions.

(a) Book (b) Date © Employee (d) Vehicle (e) Object (f) Tree

class Book should be the most realevant:

Book() = Default;
Book(std::string bn, unsigned bs{0}, double pr{0.0}, std::string sda{1900-01-01}):
	book_no(bn), book_sold(bs), book_price(pr), sold_start_date(sda){}
Book(std::istream& is) { is >> book_no >> book_sold
						    >> book_price >> sold_start_date; }
private:
	std::string book_no;
	unsigned book_sold{0};
	double book_price{0.0};
	std::string sold_start_date{1900-01-01};

Ex.7.41-7.50

ex.7.41
Exercise 7.41: Rewrite your own version of the Sales_data class to use delegating constructors. Add a statement to the body of each of the constructors that prints a message whenever it is executed. Write declarations to construct a Sales_data object in every way possible. Study the output until you are certain you understand the order of execution among delegating constructors.

CODE: header source

ex.7.42
Exercise 7.42: For the class you wrote for exercise 7.40 in § 7.5.1 (p. 291), decide whether any of the constructors might use delegation. If so, write the delegating constructor(s) for your class. If not, look at the list of abstractions and choose one that you think would use a delegating constructor. Write the class definition for that abstraction.

//base constructor
Book(std::string bn, unsigned bs{0}, double pr{0.0}, std::string sda{1900-01-01}):
	book_no(bn), book_sold(bs), book_price(pr), sold_start_date(sda){}
//delegating
Book():Book("", 0, 0.0, 1900-01-01){};
Book(std::istream& is):Book() { 
	is >> book_no >> book_sold >> book_price >> sold_start_date; }
private:
	std::string book_no;
	unsigned book_sold{0};
	double book_price{0.0};
	std::string sold_start_date{1900-01-01};

ex.7.43
Exercise 7.43: Assume we have a class named NoDefault that has a constructor that takes an int, but has no default constructor. Define a class C that has a member of type NoDefault. Define the default constructor for C.

class NoDefault {
public:	
	NoDefault(int i){};
};

class C{
public:
	C():non_d_obj(0){};
private:
	NoDefault non_d_obj;
};

ex.7.44
Exercise 7.44: Is the following declaration legal? If not, why not?

vector<NoDefault> vec(10);
It is illegal. As an initialization with the form of type() is a value initialization, a default constructor is mandatory. The Nodefault class, however, does not have a default constructor, so the initialization cannot be done.

ex.7.45
Exercise 7.45: What if we defined the vector in the previous execercise to hold objects of type C?

It is legal beucase the class C has a default consturctor.

ex.7.46
Exercise 7.46: Which, if any, of the following statements are untrue? Why?

a) A class must provide at least one constructor.
Literally, yes. It doesn't matter whether the constructor was generated by a developer or a compiler; it is part of a class.

b) A default constructor is a constructor with an empty parameter list.

No. Default constructors are defined by their behavior; they are used for default initialization and value initialization. The opposite is a constructor that has default arguments for all its parameters. It operates as a default constructor if it is not supplied with any arguments.

c) If there are no meaningful default values for a class, the class should not provide a default constructor.
No. As a default constructor is required for both default initialization and value initialization, it cannot be omitted in a class.

d) If a class does not define a default constructor, the compiler generates one that initializes each data member to the default value of its associated type.
No. The compiler must find no custom defined constructor in the class definition before creating a synthesized default constructor.

ex.7.47
Exercise 7.47: Explain whether the Sales_data constructor that takes a string should be explicit. What are the benefits of making the constructor explicit? What are the drawbacks?

In the Sales_data class, it should be OK if there is no explicit constructor. In the class, an implicit conversion will result in a book that has its ISBN but no copies sold, and an unfilled price. This makes sense.

Pros:

  • It restricts the workflow. In order to perform the operation, the user must supply a Sales_data type. By avoiding implicit conversion, the potential side effects can be avoided.

Cons:

  • Lost flexablity.
  • The keyword explict only works for constructors with one argument.
ex.7.48
Exercise 7.48: Assuming the Sales_data constructors are not explicit, what operations happen during the following definitions

string null_isbn("9-999-99999-9"); //const char* to std::string
Sales_data item1(null_isbn); // Sales_object item1 initialized to ("9-999-99999-9" , 0, 0);
Sales_data item2("9-999-99999-9");  //const char* to std::string, then item2 is initialized to ("9-999-99999-9" , 0, 0);
What happens if the Sales_data constructors are explicit?

The keyword explicit has nothing to do with the class initializations. These initializations both deliver a string argument that can be used with the Sales_data(std::string& s) to initialize a class object. A class conversion only happens when you supply a build-in type to a function that requires a class object type. In this case, we got the same results.

ex.7.49
Exercise 7.49: For each of the three following declarations of combine, explain what happens if we call i.combine(s), where i is a Sales_data and s is a string:

(a) Sales_data &combine(Sales_data); 
//Ok.
//The combine is asking a Sale_data type parameter. We feed a string which is converted to a temporary Sale_data obj.

(b) Sales_data &combine(Sales_data&); 
//Error. The combine is kasing a reference to non-const, we cannot bind a temporary object to it. 

(c) Sales_data &combine(const Sales_data&) const; 
//error.This is a const member function, which is not allowed to modify members.

p259: Because combine’s parameter is a reference to const, we can pass a temporary to that parameter.
ex.7.50
Exercise 7.50: Determine whether any of your Person class constructors should be explicit.

This constructor should be explicit.

Person(std::istream& is) { read(is, *this);}

Ex.7.51-7.58

ex.7.51
Exercise 7.51: Why do you think vector defines its single-argument constructor as explicit, but string does not?

Because converting a number to a vector doesn't make any sense, but converting a const char* to a std::string is something string was designed for, for example:

void print(vector<int>& );//the print function is suppose to print a vector. 
print(1); //There is no relationship between a number and a vector.
void print(std::string);//the print function is suppose to print a std::string. 
print("Hello, world"); //const char* to std::string makes sense.

ex.7.52
Exercise 7.52: Using our first version of Sales_data from § 2.6.1 (p. 72), explain the following initialization. Identify and fix any problems.

Sales_data item = {"978-0590353403", 25, 15.99};
The class Sale_data
struct Sales_data {
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
is not an aggregate class because an aggregate class doesn't allow in-class initializers. To use a list of member initializers, the existing in-class initializer must be removed:
struct Sales_data {
std::string bookNo;
unsigned units_sold;
double revenue;
};

ex.7.53
Exercise 7.53: Define your own version of Debug.

CODE: header source

PS: There might be a problem with the code in the book:

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; }
As the Debug constructor is a constexpr constructor, it creates a const object. Calling any() function behaves the same way as calling a non-const member function on a const member.

The keyword constexpr only specifiy the return type. To get a const member function, we need to add const after the parameter list:
constexpr bool any() const { return hw || io || other; }
PPS: The problem is caused by the new standard, using g++ -std=c++11 instead, or modify the function as above.

ex.7.54
Exercise 7.54: Should the members of Debug that begin with set_ be declared as constexpr? If not, why not?

No. A constexpr function can only have one executable statement, which is return statement.

ex.7.55
Exercise 7.55: Is the Data class from § 7.5.5 (p. 298) a literal class? If not, why not? If so, explain why it is literal.

No. The Data class has a member with std::string type, which is not literal type.

ex.7.56
Exercise 7.56: What is a static class member? What are the advantages of static members? How do they differ from ordinary members?

What is a static class member?

A static class member is a member of a class that does not belong to its object.

What are the advantages of static members?

A static class member is a member of a class that does not belong to its object. Since it is an independent property of the class object, it can be shared by all existing class objects. In this way, we can take advantage of static class members by using them in something that is very common across all objects and maintain them much easier.

How do they differ from ordinary members?

  • a static member can be an incomplete type
  • a static member can be a default argument
ex.7.57
Exercise 7.57: Write your own version of the Account class.

CODE: header source

ex.7.58
Exercise 7.58: Which, if any, of the following static data member declarations and definitions are errors? Explain why.

// example.h
class Example {
public:
	//The static members below should be initialized and defined outside of the class.
	static double rate = 6.5;
	static const int vecSize = 20; //This is legal, but it would be better to initialize it again outside of the class.
	static vector<double> vec(vecSize);
};
// example.C
#include "example.h"
double Example::rate;
vector<double> Example::vec;
Fix:
// example.h
class Example {
public:
	static double rate;
	static const int vecSize;
	static vector<double> vec;
};
// example.C
#include "example.h"
double Example::rate {6.5};
const int Example::vecSize{20};
std::vector<double> Example::vec(vecSize);