📔 C++ Primer 0x07 exercise solution
7.1 defining abstract data types
7.1.1 defining abstract data types
7.1 use the sales defined in section 2.6.1_ The data class writes a new version of the transaction handler in section 1.6.
#include <iostream> #include <string> struct Sales_data { std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; }; int main(){ Sales_data total,item; double price = 0; if (std::cin >> total.bookNo >> total.units_sold >> price){ total.revenue = total.units_sold * price; while (std::cin >> item.bookNo >> item.units_sold >> price){ item.revenue = item.units_sold * price; if (total.bookNo == item.bookNo) { total.units_sold += item.units_sold; total.revenue += item.revenue; }else { std::cout << total.bookNo << " " << total.units_sold << " " << total.revenue << std::endl; total = item; } } std::cout << total.bookNo << " " << total.units_sold << " " << total.revenue << std::endl; }else { std::cerr << "No data?!" << std::endl; return -1; } return 0; }
7.1.2 define improved Salses_data class
7.2 I wrote a sales in the exercise in section 2.6.2_ Data class, please add combine function and isbn member to this class.
7.3 modify the transaction processing procedures in section 7.1.1 to use these members.
#include <iostream> #include <string> struct Sales_data { std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; Sales_data& combine(const Sales_data &rhs); std::string isbn(){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,item; double price = 0; if (std::cin >> total.bookNo >> total.units_sold >> price){ total.revenue = total.units_sold * price; while (std::cin >> item.bookNo >> item.units_sold >> price){ item.revenue = item.units_sold * price; if (total.isbn() == item.isbn()) { total.combine(item); }else { std::cout << total.bookNo << " " << total.units_sold << " " << total.revenue << std::endl; total = item; } } std::cout << total.bookNo << " " << total.units_sold << " " << total.revenue << std::endl; }else { std::cerr << "No data?!" << std::endl; return -1; } return 0; }
7.4 write a class named Person to represent the name and address of the Person. Using string objects to store these elements, the next exercise will enrich other features of this class.
struct Person{ std::string name; std::string address; };
7.5 provide some operations in your Person class to enable it to return names and addresses. Should these functions be const? Explain why.
struct Person{ std::string name; std::string address; std::string get_name()const{return name;} std::string get_address()const{return address;} };
You should consider that constant objects may also use these operations
7.1.3 define class related non member functions
7.6 for the functions add, read and print, define your own version.
7.7 rewrite the program in the exercise in section 7.1.2 using these new functions.
#include <iostream> #include <string> struct Sales_data { std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; Sales_data& combine(const Sales_data &rhs); std::string isbn()const{return bookNo;} }; 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 = item.units_sold * price; 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; } int main(){ Sales_data total,item; if (read(std::cin,total)){ while (read(std::cin,item)){ if (total.isbn() == item.isbn()) { total.combine(item); }else { print(std::cout,total); std::cout << std::endl; total = item; } } print(std::cout,total); std::cout << std::endl; }else { std::cerr << "No data?!" << std::endl; return -1; } return 0; }
7.8 why does the read function its sales_ The data parameter is defined as an ordinary reference, while the print function defines its parameter as a constant reference?
read will change sales_ The content of data item, print will not
7.9 for the code in the exercise in section 7.1.2, add the operation of reading and printing the Person object.
#include <iostream> #include <string> struct Person{ std::string name; std::string address; std::string get_name()const{return name;} std::string get_address()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.get_name() << " " << p.get_address(); return os; } int main(){ Person p; read(std::cin,p); print(std::cout,p); return 0; }
7.10 what is the function of the condition part in the following if statement?
if (read(read(cin, data1), data2)) //Continuously read data of data1 and data2
7.1.4 constructor
7.11 in your sales_ Add constructors to the data class, and then write a program to use each constructor.
#include <iostream> #include <string> struct Sales_data { Sales_data() = default; Sales_data(const std::string &s):bookNo(s){} Sales_data(const std::string &s,unsigned u,double p) :bookNo(s),units_sold(u),revenue(u*p){} Sales_data(std::istream &); std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; Sales_data& combine(const Sales_data &rhs); std::string isbn()const{return bookNo;} double avg_price()const; }; double Sales_data::avg_price()const{ return units_sold?(revenue/units_sold):0; } 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 = item.units_sold * price; return is; } std::ostream & print(std::ostream &os,const Sales_data &item){ os << item.isbn() << " "<< item.units_sold << " " << item.revenue << " " << item.avg_price(); return os; } Sales_data::Sales_data(std::istream &is){ read(is,*this); } Sales_data add(const Sales_data &lhs,const Sales_data &rhs){ Sales_data sum = lhs; sum.combine(rhs); return sum; } int main(){ Sales_data A; print(std::cout,A) << std::endl; Sales_data B("bookB"); print(std::cout,B) << std::endl; Sales_data C("bookC",12,3.0); print(std::cout,C) << std::endl; Sales_data D(std::cin); print(std::cout,D) << std::endl; return 0; }
7.12 move the constructor that accepts only one istream as a parameter to the inside of the class.
#include <iostream> #include <string> struct Sales_data; std::istream &read(std::istream&,Sales_data&); struct Sales_data { Sales_data() = default; Sales_data(const std::string &s):bookNo(s){} Sales_data(const std::string &s,unsigned u,double p) :bookNo(s),units_sold(u),revenue(u*p){} Sales_data(std::istream &is){read(is,*this);} std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; Sales_data& combine(const Sales_data &rhs); std::string isbn()const{return bookNo;} double avg_price()const; }; double Sales_data::avg_price()const{ return units_sold?(revenue/units_sold):0; } 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 = item.units_sold * price; return is; } std::ostream & print(std::ostream &os,const Sales_data &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 A; print(std::cout,A) << std::endl; Sales_data B("bookB"); print(std::cout,B) << std::endl; Sales_data C("bookC",12,3.0); print(std::cout,C) << std::endl; Sales_data D(std::cin); print(std::cout,D) << std::endl; return 0; }
7.13 rewrite the program on page 229 using the istream constructor.
#include <iostream> #include <string> struct Sales_data; std::istream &read(std::istream&,Sales_data&); struct Sales_data { Sales_data() = default; Sales_data(const std::string &s):bookNo(s){} Sales_data(const std::string &s,unsigned u,double p) :bookNo(s),units_sold(u),revenue(u*p){} Sales_data(std::istream &is){read(is,*this);} std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; Sales_data& combine(const Sales_data &rhs); std::string isbn()const{return bookNo;} double avg_price()const; }; double Sales_data::avg_price()const{ return units_sold?(revenue/units_sold):0; } 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 = item.units_sold * price; return is; } std::ostream & print(std::ostream &os,const Sales_data &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(std::cin); if (!total.isbn().empty()){ std::istream &is = std::cin; while (is){ Sales_data item(is); if (total.isbn() == item.isbn()) { total.combine(item); }else { print(std::cout,total) << std::endl; total = item; } } print(std::cout,total) << std::endl; }else { std::cerr << "No data?!" << std::endl; return -1; } return 0; }
7.14 write a constructor that explicitly initializes members with the class initializers we provide.
Sales_data():bookNo(0),revenue(0){}
7.15 add the correct constructor for your Person class.
#include <iostream> #include <string> struct Person; std::istream& read(std::istream &,Person &); struct Person{ Person() = default; Person(const std::string n,const std::string a) :name(n),address(a){} Person(std::istream& is){read(is,*this);} std::string name; std::string address; std::string get_name()const{return name;} std::string get_address()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.get_name() << " " << p.get_address(); return os; } int main(){ Person p(std::cin); print(std::cout,p) << std::endl; Person p2("fishingrod","zj"); print(std::cout,p2) << std::endl; return 0; }
7.2 access control and encapsulation
7.16 is there a limit on the location and number of access specifiers in the class definition? If so, what is it? What kind of members should be defined after the public specifier? What members should be defined after the private specifier?
No restrictions
The member after the public specifier can be accessed in the whole program, and the public member defines the interface of the class
The member after the private specifier can be accessed by the member function of the class, but cannot be accessed by the code of the class. Private encapsulates the implementation details of the class
7.17 is there any difference between using class and struct? If so, what is it?
The only difference between using class and struct to define a class is the default access permission. All members of struct are public by default and all members of class are private by default
7.18 what does encapsulation mean? What's the use of it?
Encapsulation realizes the separation of class interface and implementation, and hides the implementation details. Class users can only use the interface and cannot access the implementation part
Two important advantages of encapsulation
- Ensure that user code does not inadvertently break the state of the encapsulated object
- The specific implementation of the encapsulated class can be changed at any time without adjusting the user level code
7.19 in your Person class, which members will you declare as public? Which are declared private? Explain why you did it.
Modified it
struct Person{ private://Data is private and invisible to users std::string name; std::string address; public://get_name,get_address, constructor public std::string get_name()const{return name;} std::string get_address()const{return address;} Person() = default; Person(const std::string n,const std::string a) :name(n),address(a){} Person(std::istream& is){read(is,*this);} };
7.2.1 friends
7.20 when are friends useful? Please list the advantages and disadvantages of using friends.
A friend declaration allows a class to allow other classes or functions to access its non-public members
It actually destroys encapsulation and maintainability
7.21 modify your sales_ The data class makes it hide the details of the implementation. What you wrote before about sales_ The program of data operation should continue to be used. Recompile the program with the help of the new definition of class to ensure its normal operation.
#include <iostream> #include <string> class Sales_data; std::istream &read(std::istream&,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 &); private: std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; public: Sales_data& combine(const Sales_data &rhs); std::string isbn()const{return bookNo;} double avg_price()const; Sales_data() = default; Sales_data(const std::string &s):bookNo(s){} Sales_data(const std::string &s,unsigned u,double p) :bookNo(s),units_sold(u),revenue(u*p){} Sales_data(std::istream &is){read(is,*this);} }; double Sales_data::avg_price()const{ return units_sold?(revenue/units_sold):0; } 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 = item.units_sold * price; return is; } std::ostream & print(std::ostream &os,const Sales_data &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(std::cin); if (!total.isbn().empty()){ std::istream &is = std::cin; while (is){ Sales_data item(is); if (total.isbn() == item.isbn()) { total.combine(item); }else { print(std::cout,total) << std::endl; total = item; } } print(std::cout,total) << std::endl; }else { std::cerr << "No data?!" << std::endl; return -1; } return 0; }
7.22 modify your Person class to hide the implementation details.
#include <iostream> #include <string> class Person; std::istream& read(std::istream &,Person &); class Person{ friend std::istream& read(std::istream &,Person &); friend std::ostream& print(std::ostream &,const Person &); private: std::string name; std::string address; public: std::string get_name()const{return name;} std::string get_address()const{return address;} Person() = default; Person(const std::string n,const std::string a) :name(n),address(a){} 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){ os << p.get_name() << " " << p.get_address(); return os; } int main(){ Person p(std::cin); print(std::cout,p) << std::endl; Person p2("fishingrod","zj"); print(std::cout,p2) << std::endl; return 0; }
Other characteristics of class 7.3
7.3.1 re exploration of class members
7.23 write your own Screen type.
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];} char get(pos r,pos c)const{return contents[r*width+c];} Screen &move (pos r,pos c); private: pos cursor = 0; pos height = 0, width = 0; std::string contents; }; inline Screen& Screen::move(pos r,pos c){ pos row = r * width; cursor = row + c; return *this; }
7.24 add three constructors to your Screen class: a default constructor; The other constructor accepts the values of width and height, and then initializes contents to a given number of blanks; The third constructor accepts width and height values and one character as the content of the initialized Screen.
class Screen{ public: typedef std::string::size_type pos; public: 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){} public: char get()const{return contents[cursor];} char get(pos r,pos c)const{return contents[r*width+c];} Screen &move (pos r,pos c); private: pos cursor = 0; pos height = 0, width = 0; std::string contents; }; inline Screen& Screen::move(pos r,pos c){ pos row = r * width; cursor = row + c; return *this; }
7.25 can screen safely rely on the default version of copy and assignment operations? If so, why? If not? Why?
Yes, using string can manage the necessary storage space
- Some classes cannot rely on the composite version. When a class needs to allocate resources other than class objects, the composite version often fails
- Many dynamic memory classes can (and should) use vector objects or string objects to manage the necessary storage space, which can avoid the complexity of allocating and freeing memory
- When an object containing a vector member performs a copy or assignment operation, the vector tries to copy or assign the elements in the member. Each element in the vector is also destroyed at one time. This is very similar to string
7.26 Sales_data::avg_price is defined as an inline function.
inline double Sales_data::avg_price()const{ return units_sold?(revenue/units_sold):0; }
7.3.2 member function returning * this
7.27 add move, set and display functions to your own Screen class. Check whether your class is correct by executing the following code.
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; }
#include <iostream> #include <string> class Screen{ public: typedef std::string::size_type pos; public: 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){} public: char get()const{return contents[cursor];} char get(pos r,pos c)const{return contents[r*width+c];} Screen& move(pos r,pos c); Screen& set(char c); Screen& set(pos r,pos col,char ch); 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; private: void do_display(std::ostream& os)const{os << contents;} }; inline Screen& Screen::set(char c){ contents[cursor] = c; return *this; } inline Screen& Screen::set(pos r,pos col,char ch){ contents[r * width + col] = ch; return *this; } inline Screen& Screen::move(pos r,pos c){ pos row = r * width; cursor = row + c; 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 if the return type of the move, set and display functions is not Screen & but Screen, what will happen in the previous exercise?
7.29 modify your Screen class so that the move, set and display functions return Screen and check the running results of the program. Was your guess correct in the previous exercise?
myScreen.move(4, 0).set('#'). display(std::cout);//move returns the copy. Set modifies the copy. Display shows another copy of set after modification //There are no operations on the same object
#include <iostream> #include <string> class Screen{ public: typedef std::string::size_type pos; public: 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){} public: char get()const{return contents[cursor];} char get(pos r,pos c)const{return contents[r*width+c];} Screen move(pos r,pos c); Screen set(char c); Screen set(pos r,pos col,char ch); 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; private: void do_display(std::ostream& os)const{os << contents;} }; inline Screen Screen::set(char c){ contents[cursor] = c; return *this; } inline Screen Screen::set(pos r,pos col,char ch){ contents[r * width + col] = ch; return *this; } inline Screen Screen::move(pos r,pos c){ pos row = r * width; cursor = row + c; 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; }
output
XXXXXXXXXXXXXXXXXXXX#XXXX XXXXXXXXXXXXXXXXXXXXXXXXX
7.30 although the method of using members through this pointer is legal, it is a little redundant. The discussion shows the advantages and disadvantages of using pointers to access members.
advantage:
1. Make the program intention clear and easier to read;
2. You can make the formal parameter name the same as the member name to be assigned.
std::string& setName(const string& name) { this->name = name; }
Disadvantages: some scenes are redundant
std::string const& getName() const { return this->name; }
7.3.3 type
7.31 define a pair of classes X and y, where x contains a pointer to y and Y contains an object of type X.
class Y; class X{ Y* point_y; }; class Y{ X x_in_y; };
7.3.4 re exploration of friends
7.32 define your own Screen and Window_mgr, where clear is window_ A member of Mgr and a friend of Screen.
#include <iostream> #include <string> #include <vector> class Screen; class Window_mgr{ public: using ScreenIndex = std::vector<Screen>::size_type; public: void clear(ScreenIndex); private: std::vector<Screen> screens; }; class Screen{ public: typedef std::string::size_type pos; friend void Window_mgr::clear(ScreenIndex); public: 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){} public: char get()const{return contents[cursor];} char get(pos r,pos c)const{return contents[r*width+c];} Screen move(pos r,pos c); Screen set(char c); Screen set(pos r,pos col,char ch); 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; private: void do_display(std::ostream& os)const{os << contents;} }; inline Screen Screen::set(char c){ contents[cursor] = c; return *this; } inline Screen Screen::set(pos r,pos col,char ch){ contents[r * width + col] = ch; return *this; } inline Screen Screen::move(pos r,pos c){ pos row = r * width; cursor = row + c; return *this; } void Window_mgr::clear(ScreenIndex i){ Screen& s = screens[i]; s.contents = std::string(s.height*s.width,' '); }
- First define Window_mgr class, which declares the clear function but does not define it. Screen must be declared before clear uses the member of screen
- Next, define the Screen, including the friend declaration for clear
- Finally, clear is defined, and then it can use the members of Screen
7.4 scope of class
7.33 what happens if we add a size member to Screen as shown below? If something goes wrong, try modifying it.
pos Screen::size() const { return height * width; }
If an error is reported in the class definition
extra qualification on member 'size' pos Screen::size()const{return height*width;}
Change to
pos size() const { return height * width; }
If an error is reported for an out of class definition
error: unknown type name 'pos'pos Screen::size()const
Change to
Screen::pos Screen::size()const{ return height*width; }
7.4.1 name lookup and class scope
7.34 what happens if we put the typedef of pos in the Screen class on the last line of the class?
pos not found, prompt unknown type
7.35 explain the meaning of the following code, and explain which definitions are used for Type and initVal. If there are errors in the code, try to modify it.
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; }
- In a class, if a member function uses a name in the outer scope and the name represents a type, the class cannot redefine the name later
7.5 further exploration
7.5.1 list of constructor initializers
7.36 the initial value below is wrong. Please find out the problem and try to modify it
struct X { X (int i, int j): base(i), rem(base % j) {} int rem, base; };
- The constructor list only describes the values used to initialize members, and does not limit the specific order of initialization
- Members are initialized in the same order as they appear in the class definition
struct X { X (int i, int j): base(i), rem(base % j) {} int base,rem; };
7.37 use the sales provided in this section_ The data class determines which constructor is used to initialize the following variables, and then lists the values of all data members of each object.
Sales_data first_item(cin); //Sales_data(std::istream &is){read(is,*this);} int main() { Sales_data next; //Sales_data() = default; //bookNo = "", cnt = 0, revenue = 0.0 Sales_data last("9-999-99999-9"); // Sales_data(const std::string &s):bookNo(s){} // bookNo = "9-999-99999-9", cnt = 0, revenue = 0.0 }
7.38 in some cases, we want to provide cin as the default argument of the constructor that accepts istream & parameters. Please declare such a constructor.
Sales_data(std::istream &is = std::cin){read(is,*this);}
7.39 if the constructor that accepts string and the constructor that accepts istream & use default arguments, is this behavior legal? If not, why?
No, a function cannot be both overloaded and a function with default parameters. I can't tell
7.40 choose one of the following abstract concepts (or specify one yourself), think about which data members such a class needs, provide a reasonable set of constructors, and explain the reasons for doing so.
(a) Book (b) Data (c) Employee (d) Vehicle (e) Object (f) Tree
(a) Book
class Book{ private: std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; public: Sales_data() = default; Sales_data(const std::string &s):bookNo(s){} Sales_data(const std::string &s,unsigned u,double p) :bookNo(s),units_sold(u),revenue(u*p){} };
7.5.2 delegate constructor
7.41 rewrite your sales using the delegate constructor_ Data class, add a statement to each constructor body to print a message once it is executed. Create sales in various possible ways_ Data object, carefully study the information output each time until you really understand the execution order of the delegate constructor.
#include <iostream> #include <string> class Sales_data; std::istream &read(std::istream&,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 &); private: std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; public: Sales_data& combine(const Sales_data &rhs); std::string isbn()const{return bookNo;} double avg_price()const; Sales_data(const std::string &s,unsigned u,double p) :bookNo(s),units_sold(u),revenue(u*p){ std::cout << "Sales_data(const std::string &s,unsigned u,double p)" << std::endl; } Sales_data() : Sales_data("",0,0){ std::cout << "Sales_data()" << std::endl; } Sales_data(const std::string &s):Sales_data(s,0,0){ std::cout << "Sales_data(const std::string &s)" << std::endl; } Sales_data(std::istream &is):Sales_data(){ read(is,*this); } }; double Sales_data::avg_price()const{ return units_sold?(revenue/units_sold):0; } 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 = item.units_sold * price; return is; } std::ostream & print(std::ostream &os,const Sales_data &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::cout << "create A" << std::endl; Sales_data A; std::cout << "create B" << std::endl; Sales_data B(std::cin); std::cout << "create C" << std::endl; Sales_data C("123-456-789"); std::cout << "create D" << std::endl; Sales_data D("789-456-123",10,2.5); return 0; }
output
create A Sales_data(const std::string &s,unsigned u,double p) Sales_data() create B Sales_data(const std::string &s,unsigned u,double p) Sales_data() create C Sales_data(const std::string &s,unsigned u,double p) Sales_data(const std::string &s) create D Sales_data(const std::string &s,unsigned u,double p)
7.42 for the classes you wrote in exercise 7.40, determine which constructors can use delegates. If you can, write a delegate constructor. If not, re select one from the list of abstract concepts that you think can use the delegate constructor, and write the class definition for the selected concept.
See exercise 7.41
7.5.3 function of default constructor
7.43 suppose you have a class named NoDefault, which has a constructor that accepts int, but there is no default constructor. Define class C, C has a member of NoDefault type, and define the default constructor of C.
class NoDefault{ public: NoDefault(int i){} }; class C{ public: C():nd(0){} private: NoDefault nd; };
7.44 is the following statement legal? If not, why?
vector<NoDefault> vec(10);
Illegal, NoDefault has no default constructor
7.45 if the element type of the vector defined in the previous exercise is C, is the declaration legal? Why?
Legal, with default constructor
7.46 which of the following statements are incorrect? Why?
- (a) A class must provide at least one constructor.
- (b) The default constructor is a constructor with an empty argument list.
- © If there is no meaningful default value for the class, the class should not provide a default constructor.
- (d) If the class does not define a default constructor, the compiler generates one for it and initializes each data member to the default value of the corresponding type.
(a) Incorrect, the compiler implicitly defines a composite default constructor.
(b) Incorrect, constructors that provide default values for all parameters are also default constructors.
© Incorrect, meaningless, initial value should also be provided
(d) Incorrect. It will not be generated automatically until no constructor is defined
7.5.4 implicit class type conversion
7.47 description sales that accept a string parameter_ Whether the data constructor should be explicit, and explain the advantages and disadvantages of doing so.
Depending on the situation, there is no big problem in this section. It should be noted that the string parameter may give a nonexistent bookNo
Advantage: explicit suppresses implicit conversions defined by constructors
Disadvantages: if you want to convert, you need to use constructors or cast types
7.48 hypothetical sales_ If the constructor of data is not explicit, what operations will the following definitions perform?
string null_isbn("9-999-9999-9"); Sales_data item1(null_isbn);//Direct initialization Sales_data item2("9-999-99999-9");//Illegal, only one-step implicit class type conversion is allowed
7.49 for the three different declarations of the combine function, what happens when we call i.combine(s)? Where I is a Sales_data, and S is a string object.
(a) Sales_data &combine(Sales_data); //legitimate (b) Sales_data &combine(Sales_data&); //Illegal, std::string to Sales_data implicit class type conversion exceeded 1 time (c) Sales_data &combine(const Sales_data&) const; //Constant member function, which does not meet the purpose of combine, needs to be modified
7.50 determine whether some constructors in your Person class should be explicit.
explicit Person(std::istream& is){read(is,*this);}
7.51 vector defines its single parameter constructor as explicit, while string is not. What do you think is the reason?
-
If vecotr but the parameter constructor is not defined as explicit, it will be strange to turn that number to vecotr < T >
-
string can basically be replaced by const char *, so it is not set to explicit
Suppose we have a function like this:
int getSize(const std::vector<int>&);
If the vector does not define the single parameter constructor as explicit, we can call it as follows:
getSize(34);
Obviously, this call is confusing. The function actually initializes a temporary vector with 34 elements and returns 34. But it doesn't make any sense. String is different. The parameter of the single parameter constructor of string is const char *, so const char * can be used wherever string is needed (the literal value is const char *). For example:
void print(std::string); print("hello world");
7.5.5 polymerization
7.52 use sales in section 2.6.1_ Data class to explain the following initialization process. If there is a problem, try modifying it.
Sales_data item = {"987-0590353403", 25, 15.99};
The aggregation class can only be initialized with the method of curly bracket list initial value. We can use sales_ Change data to aggregate class
struct Sales_data {//struct default public std::string bookNo; unsigned units_sold; double revenue; };
7.5.6 literal constant class
7.53 define your own 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){other = b;} private: bool hw; bool io; bool other; };
7.54 set in debug_ Should the first member be declared constexpr? If not, why?
No, the constexpr function must have only one return
7.55 is the Data class in section 7.5.5 a literal constant class? Please explain why.
No, std::string is not a literal type
literal type: the value of a constant expression needs to be evaluated at compile time, so there must be restrictions on the types used when declaring constexpr. These types are collectively referred to as literal types.
Arithmetic types, references, and pointers are literal types
constexpr int a = 0ï¼›//The arithmetic type int is a literal type;
Some classes are also literal types. These classes are called literal constant classes. Suppose that Debug is a literal constant class. Then:
constexpr Debug debug(args)ï¼›//Generate a constexpr object - debug;
- Aggregate classes whose data members are literal types are literal constant classes
- If a class is not an aggregate class, but it meets the following requirements, it is also a literal constant class
- Data members must all be literal types
- Class must contain at least one constexpr constructor
- If a data member contains an in class initial value, the initial value of the built-in type member must be a constant expression; Or if the member belongs to a certain kind of type, the initial value must use the member's own constexpr constructor
- The class must use the default definition of the destructor, and the member is responsible for destroying the object of the class
7.6 static members of classes
7.56 what are static members of a class? What are its advantages? What is the difference between static members and ordinary members?
Declare the static members of the class by adding the keyword static, which is not related to the specific object, but related to the class
Static members can be used in some scenarios that ordinary members cannot do
- Static data members can be incomplete types
- The type of a static data member can be the class type to which it belongs. A non static data member can only be declared as a pointer or reference to the class to which it belongs
- Static members can be used as default arguments, but non static data members cannot
7.57 write your own Account class.
class Account{ public: void calculate(){amount += amount*interestRate;} static double rate(){return interestRate;} static void rate(double){interestRate = newRate;} private: std::string owner; double amount; static double interestRate; static constexpr double Rate=13.5; static double initRate(){return Rate;} }; double Account::interestRate = initRate();
7.58 is there any error in the declaration and definition of the following static data members? Please explain why.
// example.h class Example { public: static double rate = 6.5;//Initialization within a static member class. The initial value must be a constant expression //static constexpr double rate = 6.5; static const int vecSize = 20; static vector<double> vec(vecSize);///A vector does not need to define a size within a class //static vector<double> vec; }; // example.C #include "example.h" constexpr double Example::rate; vector<double> Example::vec(Example::vecSize);