Welcome to WX public address: [programmer Guan Xiaoliang]
Column C + + learning notes
C++ Primer learning notes / exercise answers
---------------------------
- Learning notes of C++ Primer (7): Class
đđģ Cpp-Prime5 + Cpp-Primer-Plus6 source code and after class questions
Chapter 1 - getting started
Exercise 1.20
On website http://www.informit.com/title/032174113 On, the code directory in Chapter 1 contains the header file sales item. H. Copy it to your own working directory. Use it to write a program, read a set of book sales records, and print each record to the standard output.
Solution:
Sales_item.h :
/* * This file contains code from "C++ Primer, Fifth Edition", by Stanley B. * Lippman, Josee Lajoie, and Barbara E. Moo, and is covered under the * copyright and warranty notices given in that book: * * "Copyright (c) 2013 by Objectwrite, Inc., Josee Lajoie, and Barbara E. Moo." * * * "The authors and publisher have taken care in the preparation of this book, * but make no expressed or implied warranty of any kind and assume no * responsibility for errors or omissions. No liability is assumed for * incidental or consequential damages in connection with or arising out of the * use of the information or programs contained herein." * * Permission is granted for this code to be used for educational purposes in * association with the book, given proper citation if and when posted or * reproduced.Any commercial use of this code requires the explicit written * permission of the publisher, Addison-Wesley Professional, a division of * Pearson Education, Inc. Send your request for permission, stating clearly * what code you would like to use, and in what specific way, to the following * address: * * Pearson Education, Inc. * Rights and Permissions Department * One Lake Street * Upper Saddle River, NJ 07458 * Fax: (201) 236-3290 */ /* This file defines the Sales_item class used in chapter 1. * The code used in this file will be explained in * Chapter 7 (Classes) and Chapter 14 (Overloaded Operators) * Readers shouldn't try to understand the code in this file * until they have read those chapters. */ #ifndef SALESITEM_H // we're here only if SALESITEM_H has not yet been defined #define SALESITEM_H #include "Version_test.h" // Definition of Sales_item class and related functions goes here #include <iostream> #include <string> class Sales_item { // these declarations are explained section 7.2.1, p. 270 // and in chapter 14, pages 557, 558, 561 friend std::istream& operator>>(std::istream&, Sales_item&); friend std::ostream& operator<<(std::ostream&, const Sales_item&); friend bool operator<(const Sales_item&, const Sales_item&); friend bool operator==(const Sales_item&, const Sales_item&); public: // constructors are explained in section 7.1.4, pages 262 - 265 // default constructor needed to initialize members of built-in type #if defined(IN_CLASS_INITS) && defined(DEFAULT_FCNS) Sales_item() = default; #else Sales_item() : units_sold(0), revenue(0.0) { } #endif Sales_item(const std::string &book) : bookNo(book), units_sold(0), revenue(0.0) { } Sales_item(std::istream &is) { is >> *this; } public: // operations on Sales_item objects // member binary operator: left-hand operand bound to implicit this pointer Sales_item& operator+=(const Sales_item&); // operations on Sales_item objects std::string isbn() const { return bookNo; } double avg_price() const; // private members as before private: std::string bookNo; // implicitly initialized to the empty string #ifdef IN_CLASS_INITS unsigned units_sold = 0; // explicitly initialized double revenue = 0.0; #else unsigned units_sold; double revenue; #endif }; // used in chapter 10 inline bool compareIsbn(const Sales_item &lhs, const Sales_item &rhs) { return lhs.isbn() == rhs.isbn(); } // nonmember binary operator: must declare a parameter for each operand Sales_item operator+(const Sales_item&, const Sales_item&); inline bool operator==(const Sales_item &lhs, const Sales_item &rhs) { // must be made a friend of Sales_item return lhs.units_sold == rhs.units_sold && lhs.revenue == rhs.revenue && lhs.isbn() == rhs.isbn(); } inline bool operator!=(const Sales_item &lhs, const Sales_item &rhs) { return !(lhs == rhs); // != defined in terms of operator== } // assumes that both objects refer to the same ISBN Sales_item& Sales_item::operator+=(const Sales_item& rhs) { units_sold += rhs.units_sold; revenue += rhs.revenue; return *this; } // assumes that both objects refer to the same ISBN Sales_item operator+(const Sales_item& lhs, const Sales_item& rhs) { Sales_item ret(lhs); // copy (|lhs|) into a local object that we'll return ret += rhs; // add in the contents of (|rhs|) return ret; // return (|ret|) by value } std::istream& operator>>(std::istream& in, Sales_item& s) { double price; in >> s.bookNo >> s.units_sold >> price; // check that the inputs succeeded if (in) s.revenue = s.units_sold * price; else s = Sales_item(); // input failed: reset object to default state return in; } std::ostream& operator<<(std::ostream& out, const Sales_item& s) { out << s.isbn() << " " << s.units_sold << " " << s.revenue << " " << s.avg_price(); return out; } double Sales_item::avg_price() const { if (units_sold) return revenue / units_sold; else return 0; } #endif
Version_test.h :
/* * This file contains code from "C++ Primer, Fifth Edition", by Stanley B. * Lippman, Josee Lajoie, and Barbara E. Moo, and is covered under the * copyright and warranty notices given in that book: * * "Copyright (c) 2013 by Objectwrite, Inc., Josee Lajoie, and Barbara E. Moo." * * * "The authors and publisher have taken care in the preparation of this book, * but make no expressed or implied warranty of any kind and assume no * responsibility for errors or omissions. No liability is assumed for * incidental or consequential damages in connection with or arising out of the * use of the information or programs contained herein." * * Permission is granted for this code to be used for educational purposes in * association with the book, given proper citation if and when posted or * reproduced. Any commercial use of this code requires the explicit written * permission of the publisher, Addison-Wesley Professional, a division of * Pearson Education, Inc. Send your request for permission, stating clearly * what code you would like to use, and in what specific way, to the following * address: * * Pearson Education, Inc. * Rights and Permissions Department * One Lake Street * Upper Saddle River, NJ 07458 * Fax: (201) 236-3290 */ #ifndef VERSION_TEST_H #define VERSION_TEST_H /* As of the first printing of C++ Primer, 5th Edition (July 2012), * the Microsoft Complier did not yet support a number of C++ 11 features. * * The code we distribute contains both normal C++ code and * workarounds for missing features. We use a series of CPP variables to * determine whether a given features is implemented in a given release * of the MS compiler. The base version we used to test the code in the book * is Compiler Version 17.00.50522.1 for x86. * * When new releases are available we will update this file which will * #define the features implmented in that release. */ #if _MSC_FULL_VER == 170050522 || _MSC_FULL_VER == 170050727 // base version, future releases will #define those features as they are // implemented by Microsoft /* Code in this delivery use the following variables to control compilation Variable tests C++ 11 Feature CONSTEXPR_VARS constexpr variables CONSTEXPR_FCNS constexpr functions CONSTEXPR_CTORS constexpr constructors and other member functions DEFAULT_FCNS = default DELETED_FCNS = delete FUNC_CPP __func__ local static FUNCTION_PTRMEM function template with pointer to member function IN_CLASS_INITS in class initializers INITIALIZER_LIST library initializer_list<T> template LIST_INIT list initialization of ordinary variables LROUND lround function in cmath NOEXCEPT noexcept specifier and noexcept operator SIZEOF_MEMBER sizeof class_name::member_name TEMPLATE_FCN_DEFAULT_ARGS default template arguments for function templates TYPE_ALIAS_DECLS type alias declarations UNION_CLASS_MEMS unions members that have constructors or copy control VARIADICS variadic templates */ #endif // ends compiler version check #ifndef LROUND inline long lround(double d) { return (d >= 0) ? long(d + 0.5) : long(d - 0.5); } #endif #endif // ends header guard
#include<iostream> #include"Sales_item.h" int main() { Sales_item book; std::cout << "Please enter the sales record:" << std::endl; while (std::cin >> book){ std::cout << "ISBN,The number of copies sold, sales volume and average selling price are" << book << std::endl; } system("pause"); return 0; }
Exercise 1.21
Write a program to read the same sales item object of two ISBN s and output their sum.
Solution:
#include<iostream> #include"Sales_item.h" int main() { Sales_item trans1, trans2; std::cout << "Please enter two ISBN Same sales record:" << std::endl; std::cin >> trans1 >> trans2; if (compareIsbn(trans1, trans2)) std::cout << "Summary information: ISBN,The number of copies sold, sales volume and average selling price are" << trans1 + trans2 << std::endl; else std::cout << "Of two sales records ISBN Different" << std::endl; system("pause"); return 0; }
Exercise 1.22
Write a program to read multiple sales records with the same ISBN, and output the sum of all records.
Solution:
#include<iostream> #include"Sales_item.h" int main(){ Sales_item total, trans; std::cout << "Please input several ISBN Same sales record:" << std::endl; if (std::cin >> total){ while (std::cin >> trans) if (compareIsbn(total, trans)){ total = total + trans; } else{ std::cout << "ISBN Different" << std::endl; return -1; } std::cout << "Summary information: ISBN,The number of copies sold, sales volume and average selling price are" << total << std::endl; } else{ std::cout << "no data" << std::endl; return -1; } system("pause"); return 0; }
Exercise 1.23
Write program, read multiple sales records, and count several sales records of each ISBN (each book).
Solution:
#include <iostream> #include"Sales_item.h" #include <fstream> // file I/O support #include <cstdlib> // support for exit() const int SIZE = 60; int main() { using namespace std; Sales_item trans1, trans2; int num = 1; char filename[SIZE]; ifstream inFile; // object for handling file input cout << "Enter name of data file: "; cin.getline(filename, SIZE); inFile.open(filename); // associate inFile with a file if (!inFile.is_open()) // failed to open file { cout << "Could not open the file " << filename << endl; cout << "Program terminating.\n"; // cin.get(); // keep window open exit(EXIT_FAILURE); } cout << "Please input several ISBN Same sales record:" << endl; if (inFile >> trans1){ while (inFile >> trans2) if (compareIsbn(trans1, trans2)) num++; else{ cout << trans1.isbn() << "Share" << num << "Sales records" << endl; trans1 = trans2; num = 1; } cout << trans1.isbn() << "Share" << num << "Bar record" << endl; } else{ cout << "no data" << endl; return -1; } inFile.close(); // finished with the file system("pause"); return 0; }
Exercise 1.24
Enter multiple sales records representing multiple ISBNs to test the previous program, and the records of each ISBN should be grouped together.
Solution:
#include<iostream> #include"Sales_item.h" int main(){ Sales_item trans1, trans2; int num = 1; std::cout << "Please input several ISBN Same sales record:" << std::endl; if (std::cin >> trans1){ while (std::cin >> trans2) if (compareIsbn(trans1, trans2)) num++; else{ std::cout << trans1.isbn() << "Share" << num << "Sales records" << std::endl; trans1 = trans2; num = 1; } std::cout << trans1.isbn() << "Share" << num << "Bar record" << std::endl; } else{ std::cout << "no data" << std::endl; return -1; } system("pause"); return 0; }
Exercise 1.25
With the help of the sales item. H header file on the website, compile and run the bookstore program given in this section.
Solution:
#include<iostream> #include"Sales_item.h" int main(){ Sales_item total, trans; std::cout << "Please input several ISBN Same sales record:" << std::endl; if (std::cin >> total){ while (std::cin >> trans) if (compareIsbn(total, trans)){ total = total + trans; } else{ std::cout << total << std::endl; total = trans; } std::cout << total << std::endl; } else{ std::cerr << "no data" << std::endl; return -1; } system("pause"); return 0; }
Chapter II variables and basic types
Exercise 2.39
Compile the following program and observe its running results. Note what happens if you forget to write the semicolon behind the class definition body? Record relevant information, which may be useful in the future.
struct Foo { /* Empty here */ } // Note: no semicolon int main() { return 0; }
Solution:
Operation result:
The program failed to compile because a semicolon is missing. Because a variable name can be immediately followed by a class body to define an object of this type, a semicolon must be written after the curly bracket on the right side of the class body to indicate the end.
With a little modification, the program can be compiled.
struct Foo { /* Empty here */ }; int main(){ return 0; }
Exercise 2.40
Write the sales & data class according to your own understanding, which is better different from the example in the book.
Solution:
The program in the original book contains three data members: bookNo and units sold
(sales volume), revenue;
The newly designed Sales data class refines the calculation method of sales revenue. On the basis of keeping bookNo and units sold, sellingprice (retail price, original price), saleprice (real price, discount price) and discount (discount) are added, in which discount=saleprice/sellingprice.
struct Sales_data{ std::string bookNo; //Book number unsigned units_sold = 0; //Sales volume double sellingprice = 0.0; //retail price double saleprice = 0.0; //Real selling price double discount = 0.0; //Discount };
Exercise 2.41
Rewrite the exercises in sections 1.5.1 (on page 20), 1.5.2 (on page 21), and 1.6 (on page 22) using your own sales & U data class. For now, put the definition of the sales [u data class and the main function in a file.
Solution:
#include<iostream> #include<string> using namespace std; class Sales_data{ //friend function friend std::istream& operator >> (std::istream&, Sales_data&); //friend function friend std::ostream& operator << (std::ostream&, const Sales_data&); //friend function friend bool operator < (const Sales_data&, const Sales_data&); //friend function friend bool operator == (const Sales_data&, const Sales_data&); public://Constructor's3Species form Sales_data() = default; Sales_data(const std::string &book) : bookNo(book){} Sales_data(std::istream &is){ is >> *this; } public: Sales_data& operator += (const Sales_data&); std::string isbn() const { return bookNo; } private: std::string bookNo; //Book number, implicitly initialized to empty string unsigned units_sold = 0; //Sales volume, explicitly initialized to 0 double sellingprice = 0.0; //Original price, explicitly initialized to 0.0 double saleprice = 0.0; //Actual price, explicitly initialized to 0.0 double discount = 0.0; //Discount, explicitly initialized to 0.0 }; inline bool compareIsbn(const Sales_data &lhs, const Sales_data &rhs) { return lhs.isbn() == rhs.isbn(); } Sales_data operator+(const Sales_data&, const Sales_data&); inline bool operator == (const Sales_data &lhs, const Sales_data &rhs) { return lhs.units_sold == rhs.units_sold && lhs.sellingprice == rhs.sellingprice && lhs.saleprice == rhs.saleprice && lhs.isbn() == rhs.isbn(); } inline bool operator != (const Sales_data &lhs, const Sales_data &rhs) { return !(lhs == rhs); //The definition of! = based on the operator = = is given } Sales_data& Sales_data::operator += (const Sales_data& rhs) { units_sold += rhs.units_sold; saleprice = (rhs.saleprice*rhs.units_sold + saleprice*units_sold) / (rhs.units_sold + units_sold); if (sellingprice != 0) discount = saleprice / sellingprice; return *this; } Sales_data operator + (const Sales_data& lhs, const Sales_data& rhs) { Sales_data ret(lhs); //Copy the contents of lhs to the temporary variable ret, which is convenient for calculation ret += rhs; //Add the content of rhs return ret; //Return to ret } std::istream& operator >> (std::istream& in, Sales_data& s) { in >> s.bookNo >> s.units_sold >> s.sellingprice >> s.saleprice; if (in && s.sellingprice != 0) s.discount = s.saleprice / s.sellingprice; else s = Sales_data(); //Input error, reset input data return in; } std::ostream& operator << (std::ostream& out, const Sales_data& s) { out << s.isbn() << " " << s.units_sold << " " << s.sellingprice << " " << s.saleprice << " " << s.discount; return out; } int main(){ Sales_data book; std::cout << "Please enter the sales record:" << std::endl; while (std::cin >> book){ std::cout << "ISBN,The number of copies sold, the original price, the actual price and the discount are" << book << std::endl; } Sales_data trans1, trans2; std::cout << "Please enter two ISBN Same sales record:" << std::endl; std::cin >> trans1 >> trans2; if (compareIsbn(trans1, trans2)) std::cout << "Summary information: ISBN,The number of copies sold, the original price, the actual price and the discount are" << trans1 + trans2 << std::endl; else std::cout << "Of two sales records ISBN Different" << std::endl; Sales_data total, trans; std::cout << "Please input several ISBN Same sales record:" << std::endl; if (std::cin >> total){ while (std::cin >> trans) if (compareIsbn(total, trans)) //ISBN is the same. total = total + trans; else{ //ISBN different std::cout << "Current books ISBN Different" << std::endl; break; } std::cout << "Effective summary information: ISBN,The number of copies sold, the original price, the actual price and the discount are" << total << std::endl; } else{ std::cout << "no data" << std::endl; return -1; } int num = 1; //Record the total number of sales records for the current book std::cout << "Please enter several sales records:" << std::endl; if (std::cin >> trans1){ while (std::cin >> trans2) if (compareIsbn(trans1, trans2)) //ISBN is the same. num++; else{ //ISBN different std::cout << trans1.isbn() << "Share" << num << "Sales records" << std::endl; trans1 = trans2; num = 1; } std::cout << trans1.isbn() << "Share" << num << "Sales records" << std::endl; } else{ std::cout << "no data" << std::endl; return -1; } system("pause"); return 0; }
Exercise 2.42
Rewrite a Sales_data.h header file based on your own understanding and redo the exercise in section 2.6.2 (page 67).
Solution:
Sales_data.h:
#ifndef SALES_DATA_H_INCLUDED #define SALES_DATA_H_INCLUDED #include<iostream> #include<string> class Sales_data{ //friend function friend std::istream& operator >> (std::istream&, Sales_data&); //friend function friend std::ostream& operator << (std::ostream&, const Sales_data&); //friend function friend bool operator < (const Sales_data&, const Sales_data&); //friend function friend bool operator == (const Sales_data&, const Sales_data&); public://Constructor's3Species form Sales_data() = default; Sales_data(const std::string &book) : bookNo(book){} Sales_data(std::istream &is){ is >> *this; } public: Sales_data& operator += (const Sales_data&); std::string isbn() const { return bookNo; } private: std::string bookNo; //Book number, implicitly initialized to empty string unsigned units_sold = 0; //Sales volume, explicitly initialized to 0 double sellingprice = 0.0; //Original price, explicitly initialized to 0.0 double saleprice = 0.0; //Actual price, explicitly initialized to 0.0 double discount = 0.0; //Discount, explicitly initialized to 0.0 }; inline bool compareIsbn(const Sales_data &lhs, const Sales_data &rhs) { return lhs.isbn() == rhs.isbn(); } Sales_data operator+(const Sales_data&, const Sales_data&); inline bool operator == (const Sales_data &lhs, const Sales_data &rhs) { return lhs.units_sold == rhs.units_sold && lhs.sellingprice == rhs.sellingprice && lhs.saleprice == rhs.saleprice && lhs.isbn() == rhs.isbn(); } inline bool operator != (const Sales_data &lhs, const Sales_data &rhs) { return !(lhs == rhs); //The definition of! = based on the operator = = is given } Sales_data& Sales_data::operator += (const Sales_data& rhs) { units_sold += rhs.units_sold; saleprice = (rhs.saleprice*rhs.units_sold + saleprice*units_sold) / (rhs.units_sold + units_sold); if (sellingprice != 0) discount = saleprice / sellingprice; return *this; } Sales_data operator + (const Sales_data& lhs, const Sales_data& rhs) { Sales_data ret(lhs); //Copy the contents of lhs to the temporary variable ret, which is convenient for calculation ret += rhs; //Add the content of rhs return ret; //Return to ret } std::istream& operator >> (std::istream& in, Sales_data& s) { in >> s.bookNo >> s.units_sold >> s.sellingprice >> s.saleprice; if (in && s.sellingprice != 0) s.discount = s.saleprice / s.sellingprice; else s = Sales_data(); //Input error, reset input data return in; } std::ostream& operator << (std::ostream& out, const Sales_data& s) { out << s.isbn() << " " << s.units_sold << " " << s.sellingprice << " " << s.saleprice << " " << s.discount; return out; } #endif // SALES_DATA_H_INCLUDED
main.cpp:
#include<iostream> #include"Sales_data.h" int main(){ Sales_data book; std::cout << "Please enter the sales record:" << std::endl; while (std::cin >> book){ std::cout << "ISBN,The number of copies sold, the original price, the actual price and the discount are" << book << std::endl; } Sales_data trans1, trans2; std::cout << "Please enter two ISBN Same sales record:" << std::endl; std::cin >> trans1 >> trans2; if (compareIsbn(trans1, trans2)) std::cout << "Summary information: ISBN,The number of copies sold, the original price, the actual price and the discount are" << trans1 + trans2 << std::endl; else std::cout << "Of two sales records ISBN Different" << std::endl; Sales_data total, trans; std::cout << "Please input several ISBN Same sales record:" << std::endl; if (std::cin >> total){ while (std::cin >> trans) if (compareIsbn(total, trans)) //ISBN is the same. total = total + trans; else{ //ISBN different std::cout << "Current books ISBN Different" << std::endl; break; } std::cout << "Effective summary information: ISBN,The number of copies sold, the original price, the actual price and the discount are" << total << std::endl; } else{ std::cout << "no data" << std::endl; return -1; } int num = 1; //Record the total number of sales records for the current book std::cout << "Please enter several sales records:" << std::endl; if (std::cin >> trans1){ while (std::cin >> trans2) if (compareIsbn(trans1, trans2)) //ISBN is the same. num++; else{ //ISBN different std::cout << trans1.isbn() << "Share" << num << "Sales records" << std::endl; trans1 = trans2; num = 1; } std::cout << trans1.isbn() << "Share" << num << "Sales records" << std::endl; } else{ std::cout << "no data" << std::endl; return -1; } system("pause"); return 0; }
Chapter VII category
Exercise 7.1
Write a new version of the 1.6 transaction handler using the sales & data class defined in section 2.6.1.
Solution:
#include<iostream> #include"Sales_data.h" using namespace std; int main(){ cout << "Please enter transaction( ISBN,Sales volume, original price and actual sales price):" << endl; Sales_data total; //Variables to save next transaction //Read in the first transaction and make sure there is data to process if (cin >> total){ Sales_data trans; //Variables saved and //Read in and process remaining transactions while(cin >> trans){ //If we're still working on the same book if (total.isbn() == trans.isbn()) total += trans; //Update total sales else{ //Print the results of the previous book cout << total << endl; total = trans; //total now represents sales for the next book } } cout << total << endl; //Print the results of the last book } else{ //No input! Warning readers cerr << "No dataīŧ!" << endl; return -1; // Express failure } system("pause"); return 0; }
Exercise 7.2
In the exercise in section 2.6.2, I wrote a sales & U data class. Please add the combine function and isbn member to this class.
Solution:
The sales data class after adding the combine and isbn members is:
class Sales_data{ private: std::string bookNo; //Book number, implicitly initialized to empty string unsigned units_sold = 0; //Sales volume, explicitly initialized to 0 double sellingprice = 0.0; //Original price, explicitly initialized to 0.0 double saleprice = 0.0; //Actual price, explicitly initialized to 0.0 double discount = 0.0; //Discount, explicitly initialized to 0.0 public: //Define public function members //isbn function has only one statement, and returns bookNo string isbn() const { return bookNo; } //The combine function is used to merge the same sales records of two ISBN s Sales_data& combine(const Sales_data &rhs) { units_sold += rhs.units_sold; //Cumulative book sales saleprice = (rhs.saleprice*rhs.units_sold + saleprice*units_sold) / (rhs.units_sold + units_sold); //Recalculate actual sales price if (sellingprice != 0) discount = saleprice / sellingprice;//Recalculate actual discount return *this; //Return the merged result } }
Exercise 7.3
Modify the transaction handler in section 7.1.1 to use these members.
Solution:
#include<iostream> #include"Sales_data.h" using namespace std; int main(){ Sales_data total, trans; cout << "Please enter transaction( ISBN,Sales volume, original price and actual sales price):" << endl; if (cin >> total){ while (cin >> trans) if (total.isbn() == trans.isbn()){ total.combine(trans); } else{ cout << total << endl; total = trans; } cout << total << endl; } else{ cerr << "no data" << endl; return -1; } system("pause"); return 0; }
Exercise 7.4
Write a class called Person that represents the name and address of the Person. Use string objects to store these elements, and the next exercise will continue to enrich other features of this class.
Solution:
The Person class satisfying the question is:
#include <string> class Person { private: string strName; //Full name string strAddress; //address };
Exercise 7.5
There are actions in your Person class that enable it to return names and addresses.
Should these functions be const? Explain why.
Solution:
The modified Person class is:
#include <string> class Person { private: string strName; //Full name string strAddress; //address public: string getName() const { return strName; } //Return name string getAddress() const { return strAddress; } //Return address };
The above two functions should be defined as constant member functions, because no matter the return name or return address, only the value of data member is read in the function body without any change.
Exercise 7.6
For functions add, read, and print, define your own version.
Solution:
The add, read and print functions that satisfy the question are as follows:
#include <string> #include <iostream> Sales_data add(const Sales_data &lhs, const Sales_data &rhs) { Sales_data sum = lhs; sum.combine(rhs); return sum; } std::istream &read(std::istream &is, Sales_data &item) { is >> item.bookNo >> item.units_sold >> item.sellingprice >> item.saleprice; if (is && item.sellingprice != 0) item.discount = item.saleprice / item.sellingprice; else item = Sales_data(); //Input error, reset input data return is; } std::ostream &print(std::ostream &os, const Sales_data &item) { os << item.isbn() << " " << item.units_sold << " " << item.sellingprice << " " << item.saleprice << "" << item.discount; return os; }
Exercise 7.7
Use these new functions to rewrite the program in the exercise in section 7.1.2.
Solution:
Replace > > with read function, replace < < with print function, and replace combine function with add function.
#include<iostream> #include"Sales_data.h" using namespace std; int main(){ Sales_data total, trans; cout << "Please enter transaction( ISBN,Sales volume, original price and actual sales price):" << endl; if (read(cin, total)){ 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; } system("pause"); return 0; }
Exercise 7.8
Why does the read function define its sales data parameter as a normal reference, while the print function defines its parameter as a constant reference?
Solution:
- The read function defines its sales data parameter as a normal reference, because we need to read data from the standard input stream and write it to the given sales data object, so we need to have permission to modify the object.
- print defines its parameters as constant references because it is only responsible for the output of data and does not make any changes.
Exercise 7.9
For the code in the 7.1.2 exercise, add operations to read and print the Person object.
Solution:
The read and print functions satisfying the question are as follows:
std::istream &read(std::istream &is, Person &per) { is >> per.strName >> per.strAddress; return is; } std::ostream &print(std::ostream &os, const Person &per) { os << per.getName() << per.getAddress(); return os; }
Exercise 7.10
In the following if statement, what is the function of the condition part?
if (read(read(cin, data1), data2))
Solution:
The return type of read function is STD:: istream &, which is a reference, so the return value of read(cin, data1) can continue to be used as an argument of the outer read function. This condition checks whether the process of reading data1 and data2 is correct. If it is correct, the condition is met; otherwise, the condition is not met.
Exercise 7.11:
Add a constructor to your sales & data class,
Then write a program to use each constructor.
Solution:
Head file:
#ifndef SALES_DATA_H_INCLUDED #define SALES_DATA_H_INCLUDED #include<iostream> #include<string> class Sales_data{ //friend function friend std::istream& operator >> (std::istream&, Sales_data&); //friend function friend std::ostream& operator << (std::ostream&, const Sales_data&); //friend function friend bool operator < (const Sales_data&, const Sales_data&); //friend function friend bool operator == (const Sales_data&, const Sales_data&); public://Constructor's3Species form Sales_data() = default; Sales_data(const std::string &book) : bookNo(book){} Sales_data(const std::string &book, const unsigned num, const double sellp, const double salep); Sales_data(std::istream &is){ is >> *this; } public: Sales_data& operator += (const Sales_data&); std::string isbn() const { return bookNo; } //The combine function is used to merge the same sales records of two ISBN s Sales_data& combine(const Sales_data &rhs) { units_sold += rhs.units_sold; //Cumulative book sales saleprice = (rhs.saleprice*rhs.units_sold + saleprice*units_sold) / (rhs.units_sold + units_sold); //Recalculate actual sales price if (sellingprice != 0) discount = saleprice / sellingprice;//Recalculate actual discount return *this; //Return the merged result } //private: public: std::string bookNo; //Book number, implicitly initialized to empty string unsigned units_sold = 0; //Sales volume, explicitly initialized to 0 double sellingprice = 0.0; //Original price, explicitly initialized to 0.0 double saleprice = 0.0; //Actual price, explicitly initialized to 0.0 double discount = 0.0; //Discount, explicitly initialized to 0.0 }; Sales_data::Sales_data(const std::string &book, const unsigned num, const double sellp, const double salep) { bookNo = book; units_sold = num; sellingprice = sellp; saleprice = salep; if (sellingprice != 0) discount = saleprice / sellingprice; } inline bool compareIsbn(const Sales_data &lhs, const Sales_data &rhs) { return lhs.isbn() == rhs.isbn(); } Sales_data operator+(const Sales_data&, const Sales_data&); inline bool operator == (const Sales_data &lhs, const Sales_data &rhs) { return lhs.units_sold == rhs.units_sold && lhs.sellingprice == rhs.sellingprice && lhs.saleprice == rhs.saleprice && lhs.isbn() == rhs.isbn(); } inline bool operator != (const Sales_data &lhs, const Sales_data &rhs) { return !(lhs == rhs); //The definition of! = based on the operator = = is given } Sales_data& Sales_data::operator += (const Sales_data& rhs) { units_sold += rhs.units_sold; saleprice = (rhs.saleprice*rhs.units_sold + saleprice*units_sold) / (rhs.units_sold + units_sold); if (sellingprice != 0) discount = saleprice / sellingprice; return *this; } Sales_data operator + (const Sales_data& lhs, const Sales_data& rhs) { Sales_data ret(lhs); //Copy the contents of lhs to the temporary variable ret, which is convenient for calculation ret += rhs; //Add the content of rhs return ret; //Return to ret } std::istream& operator >> (std::istream& in, Sales_data& s) { in >> s.bookNo >> s.units_sold >> s.sellingprice >> s.saleprice; if (in && s.sellingprice != 0) s.discount = s.saleprice / s.sellingprice; else s = Sales_data(); //Input error, reset input data return in; } std::ostream& operator << (std::ostream& out, const Sales_data& s) { out << s.isbn() << " " << s.units_sold << " " << s.sellingprice << " " << s.saleprice << " " << s.discount; return out; } #endif // SALES_DATA_H_INCLUDED
In the definition of class, we design four constructors:
- The first constructor is the default constructor, which uses the = Default provided by the new C++11 standard. Its parameter list is empty, so we can construct an object without any data.
- The second constructor accepts only one const strings, representing the ISBN number of the book, and the compiler gives the initial value in other data member classes.
- The third constructor accepts the complete sales record information, const strings represents the ISBN number of the book, const unsigned represents the sales volume, and the latter two const double represent the original price and actual sales price of the book respectively.
- The last constructor takes istreams and reads the book's sales information from it.
We create four sales [u data objects in the main function and output their contents in turn. The constructors defined above are used once respectively.
Main function:
#include <iostream> #include "Sales_data.h" int main() { using namespace std; Sales_data data1; Sales_data data2("978-7-121-15535-2"); Sales_data data3("978-7-121-15535-2", 100, 128, 109); Sales_data data4(cin); cout << "The sales of books are as follows:" << endl; cout << data1 << "\n" << data2 << "\n" << data3 << "\n" << data4 << endl; system("pause"); return 0; }
Exercise 7.12
Move a constructor that accepts only one istream as an argument inside the class.
Solution:
According to the requirements of the topic, after the constructor that only accepts an istream as a parameter is defined inside the class, the form of the class is as follows:
class Sales_data { public: //Four forms of constructors Sales_data() = default; Sales_data(const std::string &book) : bookNo(book){} Sales_data(const std::string &book, const unsigned num, const double sellp, const double salep); Sales_data(std::istream &is){ is >> *this; } public: std::string bookNo; //Book number, implicitly initialized to empty string unsigned units_sold = 0; //Sales volume, explicitly initialized to 0 double sellingprice = 0.0; //Original price, explicitly initialized to 0.0 double saleprice = 0.0; //Actual price, explicitly initialized to 0.0 double discount = 0.0; //Discount, explicitly initialized to 0.0 };
Exercise 7.13
Rewrite the program on page 229 using the istream constructor.
Solution:
#include<iostream> #include"Sales_data.h" using namespace std; int main(){ Sales_data total(cin); if (cin){ Sales_data trans(cin); 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; } system("pause"); return 0; }
Exercise 7.14
Write a constructor that explicitly initializes the members with the class initializers we provide.
Solution:
The constructor that uses the initial value list is:
Sales_data(const std::string &book) :bookNo(book), units_sold(0), sellingprice(0), saleprice(0), discount(0) { }
Exercise 7.15
Add the correct constructor for your Person class.
Solution:
class Person { private: string strName; //Full name string strAddress; //address public: Person() = default; Person(const string &name, const string &add) { strName = name; strAddress = add; } Person(std::istream &is) { is >> *this; } public: string getName() const { return strName; } //Return name string getAddress() const { return strAddress; } //Return address };
Exercise 7.16
Is there a limit in the definition of a class to where and how many access specifiers appear?
If so, what is it? What members should be defined after the public specifier?
What members should be defined after the private specifier?
Solution:
In the definition of a class, it can contain 0 or more access descriptors, and there is no strict regulation on how many times an access descriptor can appear and where it can appear. Each access specifier specifies the access level of the next member, valid until the next access specifier appears or the end of the class is reached.
In general, as part of the interface,
- Constructors and some member functions should be defined after the public specifier,
- Data members and functions that are part of the implementation should follow the private specifier.
Exercise 7.17
Is there any difference between using class and struct? If so, what is it?
Solution:
class and struct can be used to declare classes. Most of their functions are similar. The only difference is that the default access permissions are different.
A class can define members before its first access specifier, and access to such members depends on how the class is defined.
- If you use the struct keyword, the members defined before the first access specifier are public;
- Instead, if you use the class keyword, these members are private.
Exercise 7.18
What does encapsulation mean? What's the use of it?
Solution:
Encapsulation refers to the ability to protect members of a class from arbitrary access. By setting the implementation details of the class to private, we can complete the class encapsulation. Encapsulation realizes the separation of class interface and implementation.
As mentioned in the book, encapsulation has two important advantages:
- One is to ensure that the user code does not inadvertently destroy the state of the encapsulated object;
- Second, the specific implementation details of the encapsulated class can be changed at any time without adjusting the user level code.
Once the data member is defined as private, the author of the class can modify the data more freely. When the implementation part changes, only the code of the class itself needs to be checked to confirm the impact of the change; in other words, as long as the interface of the class remains unchanged, the user code does not need to change.
If the data is public, all codes that use the original data members may fail. At this time, we must locate and rewrite all codes that depend on the implementation of the old version before we can reuse the program.
There is another advantage of setting the access rights of data members to private, which can prevent the data from being damaged due to the user's reasons. If we find that a program defect destroys the state of an object, we can locate the defect within a limited scope: because only the code of the implementation part may produce such an error. Therefore, limiting the error search to a limited range will greatly simplify the work of changing problems and correcting procedures.
Exercise 7.19
In your Person class, which members will you declare as public?
Which are declared private?
Explain why you did it.
Solution:
According to the meaning of encapsulation, as part of the interface,
- Constructors and some member functions should be defined after the public specifier,
- Data members and functions that are part of the implementation should follow the private specifier.
Based on the above analysis,
- We set the data members strName and strAddress to private, which can avoid the user program inadvertently modifying and destroying them;
- At the same time, the constructor and two interface functions that get data members are set to public, which is convenient for us to access outside the class.
Exercise 7.20
When will friends be useful? Please list the advantages and disadvantages of using friends.
Solution:
Friends provide the ability to access the private members of a class's nonmember interface functions. The advantages and disadvantages of this ability coexist.
When non member functions do need to access private members of a class, we can declare them as friends of the class.
- At this point, friends can "work inside the class" and access all the data and functions of the class like members of the class.
- However, if you use it carelessly (such as setting friends at will), you may break the encapsulation of the class.
Exercise 7.21
Modify your sales & data class to hide the implementation details.
The program you wrote about the sales data operation should continue to be used. Recompile the program with the help of the new definition of the class to ensure its normal operation.
Solution:
class Sales_data { friend Sales_data add(const Sales_data &lhs, const Sales_data &rhs); friend std::istream &read(std::istream &is, Sales_data &item); friend std::ostream &print(std::ostream &os, const Sales_data &item); private: std::string bookNo; //Book number, implicitly initialized to empty string unsigned units_sold = 0; //Sales volume, explicitly initialized to 0 double sellingprice = 0.0; //Original price, explicitly initialized to 0.0 double saleprice = 0.0; //Actual price, explicitly initialized to 0.0 double discount = 0.0; //Discount, explicitly initialized to 0.0 };
Exercise 7.22
Modify your Person class to hide the implementation details.
Solution:
So far, the Person class we have designed contains two data members, three constructors and two interface functions to get data. Obviously, several functions except data members are accessible by external programs, so we set the data members to private to ensure the encapsulation of the class.
class Person { private: string strName; //Full name string strAddress; //address public: Person() = default; Person(const string &name, const string &add) { strName = name; strAddress = add; } Person(std::istream &is) { is >> *this; } public: string getName() const { return strName; } //Return name string getAddress() const { return strAddress; } //Return address };
Exercise 7.23
Write your own Screen type.
Solution:
For the Screen class, the necessary data members are: the width and height of the Screen, the content of the Screen, and the current position of the cursor, which is consistent with the example in the book. Therefore, the Screen class that contains only data members is:
class Screen { private: unsigned height=0, width=0; unsigned cursor=0; string contents; }
Exercise 7.24
Add three constructors to your Screen class: a default constructor; another constructor that takes values of width and height and initializes contents to a given number of blanks; and the third constructor that takes values of width and height and a character as the content of the initialized Screen.
Solution:
Use the list initial value of the constructor to perform initialization. After adding the constructor, the Screen class is:
class Screen { private: unsigned height = 0, width = 0; unsigned cursor = 0; string contents; public: Screen() = default; // 1 Screen(unsigned ht, unsigned wd) : height(ht), width(wd), contents(ht*wd, ' '){ } // 2 Screen(unsigned ht, unsigned wd, char c) : height(ht), width(wd), contents(ht*wd, c){ } // 3 };
Exercise 7.25
Can Screen safely rely on the default version of copy and assign operations?
If so, why? If not? Why?
Solution:
In general, classes with pointer data members should not use the default copy and assignment operations. If the data members of a class are of built-in type, they will not be disturbed.
The four data members of Screen are built-in types (the string class defines the copy and assignment operators), so it is possible to directly use class objects to perform the copy and assignment operations.
Exercise 7.26
Define Sales_data::avg_price as an inline function.
Solution:
- Implicitly inline, put the definition of AVG īš price function inside the class:
class Sales_data { public: double avg_price() const { if(units_sold) return revenue/units_sold; else return 0; } }
- Explicitly inline, put the definition of the AVG īšŖ price function outside the class, and specify inline:
class Sales_data { double avg_price() const; } inline double Sales_data::avg_price() const { if(units_sold) return revenue/units_sold; else return 0; }
Exercise 7.27
Add move, set, and display functions to your own Screen class, and verify that your class is correct by executing the following code.
Screen myScreen(5, 5, 'X'); myScreen.move(4, 0).set('#').display(cout); cout << "\n"; myScreen.display(cout); cout << "\n";
Solution:
After adding the move, set, and display functions, the new Screen class is:
#include "Version_test.h" #include <string> #include <iostream> class Screen { public: typedef std::string::size_type pos; #if defined(IN_CLASS_INITS) && defined(DEFAULT_FCNS) Screen() = default; // needed because Screen has another constructor #else Screen() : cursor(0), height(0), width(0) { } #endif // cursor initialized to 0 by its in-class initializer Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c) { } friend class Window_mgr; Screen(pos ht = 0, pos wd = 0) : cursor(0), height(ht), width(wd), contents(ht * wd, ' ') { } char get() const // get the character at the cursor { return contents[cursor]; } // implicitly inline inline char get(pos ht, pos wd) const; // explicitly inline Screen &clear(char = bkground); private: static const char bkground = ' '; public: Screen &move(pos r, pos c); // can be made inline later Screen &set(char); Screen &set(pos, pos, char); // other members as before // display overloaded on whether the object is const or not Screen &display(std::ostream &os) { do_display(os); return *this; } const Screen &display(std::ostream &os) const { do_display(os); return *this; } private: // function to do the work of displaying a Screen void do_display(std::ostream &os) const { os << contents; } // other members as before private: #ifdef IN_CLASS_INITS pos cursor = 0; pos height = 0, width = 0; #else pos cursor; pos height, width; #endif std::string contents; }; Screen &Screen::clear(char c) { contents = std::string(height*width, c); return *this; } inline // we can specify inline on the definition Screen &Screen::move(pos r, pos c) { pos row = r * width; // compute the row location cursor = row + c; // move cursor to the column within that row return *this; // return this object as an lvalue } char Screen::get(pos r, pos c) const // declared as inline in the class { pos row = r * width; // compute row location return contents[row + c]; // return character at the given column } inline Screen &Screen::set(char c) { contents[cursor] = c; // set the new value at the current cursor location return *this; // return this object as an lvalue } inline Screen &Screen::set(pos r, pos col, char ch) { contents[r*width + col] = ch; // set specified location to given value return *this; // return this object as an lvalue }
Test code:
#include<iostream> #include"Screen.h" using namespace std; 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"; system("pause"); return 0; }
Exercise 7.28
If the return type of the move, set, and display functions is not Screen & but Screen, what happened in the previous exercise?
Solution:
- If the return value of a function is a reference, it indicates that the function returns the object itself;
- If the return value of the function is not a reference, it indicates that the function returns a copy of the object.
Functions that return references are left-handed, meaning they return the object itself rather than a copy of the object. If we connect a series of such operations, all of them will be performed on the same object.
In the previous exercise, the return types of the move, set, and display functions are screen &, which means that we first move the cursor to the (4,0) position, then change the character of the position to āˇ, and finally output the contents of myscreen.
On the contrary, if we change the return type of move, set and display functions to Screen, then the above functions only return a temporary copy, and will not change the value of myScreen.
Exercise 7.29
Modify your Screen class so that the move, set, and display functions return Screen and check the results of the program. Was your guess correct in the previous exercise?
Solution:
The conjecture is correct.
# with '&' XXXXXXXXXXXXXXXXXXXX#XXXX XXXXXXXXXXXXXXXXXXXX#XXXX ^ # without '&' XXXXXXXXXXXXXXXXXXXX#XXXX XXXXXXXXXXXXXXXXXXXXXXXXX ^
Exercise 7.30
Although it is legal to use members through this pointer, it is a bit redundant. Discusses the advantages and disadvantages of using pointers to access members.
Solution:
Access the member's
- The advantage is that it can be very clear that the access is to the members of the object, and the parameter with the same name as the data member can be used in the member function;
- The disadvantage is that it is redundant and the code is not concise enough.
Exercise 7.31
Defines a pair of classes X and y, where x contains a pointer to y and Y contains an object of type X.
Solution:
class X; class Y{ X* x; }; class X{ Y y; };
The declaration of class X is called a forward declaration, which introduces the name x into the program and indicates that x is a kind of type. For type X, we know it's a class type at this time, but we don't know which members it contains, so it's an incomplete type. We can define a pointer to an incomplete type, but we cannot create an object of an incomplete type.
If you try to write in the following form, a compiler error will be raised.
class Y; class X{ Y y; }; class Y{ X* x; };
At this time, we try to create an object of incomplete type Y in class X, and the compiler gives the error message:
error:field 'y' has incomplete type
Exercise 7.32
Define your own Screen and window? Mgr, where clear is a member of window? Mgr and a friend of Screen.
Solution:
Class can define other classes as friends, or the member functions of other classes as friends. When defining member functions as friends, pay special attention to the organizational structure of the program.
To make the clear function a friend of screen, you only need to make a friend declaration in the screen class. The real key of this question is the organizational structure of the program. We must
- First, define the Window mgr class, in which the clear function is declared, but it cannot be defined;
- Next, the Screen class is defined, in which the clear function is its friend;
- Finally, the clear function is defined.
The procedure to satisfy the question is as follows:
#include <iostream> #include <string> using namespace std; class Window_mgr { public: void clear(); }; class Screen { friend void Window_mgr::clear(); private: unsigned width = 0, height = 0; unsigned cursor = 0; string contents; public: Screen() = default; Screen(unsigned ht, unsigned wd, char c) :height(ht), width(wd), contents(ht*wd, c) { } }; void Window_mgr::clear() { Screen myScreen(10, 20, 'X'); cout << "Before cleaning up myScrean The content of is:" << endl; cout << myScreen.contents << endl; myScreen.contents = ""; cout << "After cleaning up myScrean The content of is:" << endl; cout << myScreen.contents << endl; } int main() { Window_mgr w; w.clear(); system("pause"); return 0; }
Exercise 7.33
What happens if we add a size member to Screen as follows? If something goes wrong, try modifying it.
pos Screen::size() const { return height * width; }
Solution:
If you add the size function as shown in the title, you will get compilation errors. Because the function's return type pos itself is defined inside the Screen class, pos cannot be used directly outside the class. To use pos, you need to precede it with the scope Screen::.
The revised procedure is:
Screen::pos Screen::size() const { return height * width; }
Exercise 7.34
What happens if we put the typedef of the pos of the Screen class on page 256 on the last line of the class?
Solution:
This causes compilation errors because the use of pos comes before its declaration, at which point the compiler does not know what pos means.
Exercise 7.35
Explain the meaning of the following code, which definition is used for Type and initVal respectively. If there is an error in the code, try modifying 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; }
Solution:
typedef string Type; //Declaration Type alias Type represents string Type initVal(); //Declare function initVal, return Type is Type class Exercise { //Define a new class Exercise public: typedef double Type; //Redeclare Type alias in inner scope Type represents double Type setVal(Type); //Declaration function setVal, parameter and return value are of Type Type initVal(); //Redeclare function initVal in inner scope, return Type is Type private: int val; //Declare private data member val }; //Define the setVal function. At this time, the Type is obviously in the outer scope Type Exercise::setVal(Type parm) { val = parm + initVal(); //The initVal function in the class is used here return val; }
Among them,
- Within the Exercise class, the types used by setval and initval are Type aliases declared within Exercise, and the corresponding actual Type is double.
- Outside the Exercise class, when defining the Exercise::setVal function, the parameter Type uses the alias defined inside Exercise, corresponding to double; the return Type uses the alias of global scope, corresponding to string. The initVal function used is the version defined within the Exercise class.
An error occurred at the definition of setVal when compiling the above program. The function parameter type defined here is double and the return value type is string, while the function parameter type declared in the class with the same name is double and the return value type is double, which cannot be matched. The modification is to use the scope operator to force the return value type of the function when defining the setVal function.
Exercise::Type Exercise::setVal(Type parm){ val=parm+initVal(); //The initVal function in the class is used here return val; }
Exercise 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; };
Solution:
The purpose of this question is to examine the initialization order of the members when using the constructor initializer list. The initialization order is only related to the order in which the data members appear in the class, but not to the order of the initializer list.
In class X, the order in which the two data members appear is rem in the first place and base in the second place. Therefore, REM is initialized first when the X object is initialized. As shown in the above code, the base value is used to initialize rem. at this time, the base has not been initialized, so an error will occur. This procedure has nothing to do with who appears first and who appears next in the constructor initializer list.
The modification method is very simple. You only need to change the order of the variables rem and base. The form is:
struct X { X (int i, int j): base(i), rem(base % j) {} int base, rem; };
Exercise 7.37
Use the sales & U data class provided in this section to determine which constructor is used when initializing the following variables, and then list the values of all data members of each object.
Sales_data first_item(cin); int main() { Sales_data next; Sales_data last("9-999-99999-9"); }
Solution:
According to the different calls of the arguments, the best matching constructor is implemented, and the members that do not provide the arguments are initialized with their initial values within the class.
-
Sales_data first_item(cin); uses a constructor that takes the std::istreams parameter, whose member values depend on the user's input.
-
Sales_data next; uses the default constructor of Sales data, where the member bookNo of string type is initialized to an empty string by default, and several other members are initialized to 0 using the initial value in the class.
-
Sales_data last("9-999-99999-9"); a constructor that accepts the const strings parameter is used, where bookNo is initialized to "9-999-99999-9" with an argument, and several other members are initialized to 0 with an in class initial value.
Exercise 7.38
In some cases we want to provide cin as the default argument to the constructor that accepts the istream & parameter, declare such a constructor.
Solution:
The constructors satisfying the question are as follows:
Sales_data(std::istream &is = std::cin) { is >> *this; }
At this time, the function has the function of default constructor, so the previously declared default constructor, Sales data()=default; should be removed, otherwise the call ambiguity will be caused.
Exercise 7.39
If the constructor that accepts string and the constructor that accepts istream & use default arguments, is this behavior legal? If not, why?
Solution:
If we provide a default argument for all the parameters of a constructor (including a constructor that accepts only one parameter), the constructor has the function of a default constructor at the same time. At this point, even if we do not provide any arguments to create class objects, we can find the available constructors.
However, if we give default arguments to both constructors according to the description of this topic, then both constructors have the function of default constructors. Once we create an object of a class without any arguments, the compiler cannot determine which of these two (overloaded) constructors is better, resulting in a ambiguity error.
Exercise 7.40
Choose one of the following abstractions (or specify one yourself), think about which data members such a class needs, provide a reasonable set of constructors, and explain why.
(a) Book (b) Data (c) Employee (d) Vehicle (e) Object (f) Tree
Solution:
First, select (a) Book,
- A Book usually contains the title, ISBN number, pricing, Author, publishing house and other information, so its data members are: Name, ISBN, Price, Author, Publisher, where Price is a double type, others are string type.
- Book has three constructors: a default constructor, a constructor with complete book information, and a constructor that accepts user input.
It is defined as follows:
class Book { private: string Name, ISBN, Author, Publisher; double Price = 0; public: Book() = default; Book(const string &n, const string &I, double pr, const string &a, const string &p) { Name = n; ISBN = I; Price = pr; Author = a; Publisher = p; } Book(std::istream &is) { is >> *this; } };
You can also choose (f) Tree,
- A tree usually contains the Name, year of survival, and Height of the tree, so its data members are: Name, Age, Height, where Name is of string type, Age is of unsigned type, and Height is of double type.
- If we don't want the user to input the Tree information, we can remove the constructor that accepts the STD:: istream & parameter, and only keep the default constructor and the constructor that accepts all the information.
It is defined as follows:
class Tree { private: string Name; unsigned Age = 0; double Height = 0; public: Tree() = default; Tree(const string &n, unsigned a, double h):Name(h), Age(a), Height(h); };
Open questions.
Exercise 7.41
Rewrite your sales & data class with a delegate constructor, adding a statement to each constructor body that prints a message as soon as it executes. Create the sales & data object in various possible ways, and carefully study the information output each time until you really understand the execution order of the delegate constructor.
Solution:
#include <iostream> #include <string> using namespace std; class Sales_data{ friend std::istream &read(std::istream &is, Sales_data &item); friend std::ostream &print(std::ostream &os, const Sales_data &item); public: //Delegating constructors Sales_data(const string &book, unsigned num, double sellp, double salep) :bookNo(book), units_sold(num), sellingprice(sellp), saleprice(salep) { if (sellingprice) discount = saleprice / sellingprice; cout << "The constructor accepts four information: book number, sales volume, original price and actual sales price" << endl; } Sales_data() :Sales_data("", 0, 0, 0) { cout << "This constructor does not accept any information" << endl; } Sales_data(const string &book) :Sales_data(book, 0, 0, 0) { cout << "This constructor accepts the book number information" << endl; } Sales_data(std::istream &is) :Sales_data() { read(is, *this); cout << "This constructor accepts the information entered by the user" << endl; } private: std::string bookNo; //Book number, implicitly initialized to empty string unsigned units_sold = 0; //Sales volume, explicitly initialized to 0 double sellingprice = 0.0; //Original price, explicitly initialized to 0.0 double saleprice = 0.0; //Actual price, explicitly initialized to 0.0 double discount = 0.0; //Discount, explicitly initialized to 0.0 }; std::istream &read(std::istream &is, Sales_data &item) { is >> item.bookNo >> item.units_sold >> item.sellingprice >> item.saleprice; return is; } std::ostream &print(std::ostream &os, const Sales_data &item) { os << item.bookNo << "" << item.units_sold << "" << item.sellingprice << "" << item.saleprice << "" << item.discount; return os; } int main(){ Sales_data fist("978-7-121-15535-2", 85, 128, 109); cout << "*************" << endl; Sales_data second; cout << "*************" << endl; Sales_data third("978-7-121-15535-2"); cout << "*************" << endl; Sales_data last(cin); system("pause"); return 0; }
Exercise 7.42
For the class you wrote in exercise 7.40, determine which constructors can use delegates. If you can, write a delegate constructor. If not, choose another one from the list of abstract concepts that you think you can use a delegate constructor to write a class definition for the selected concept.
Solution:
Take the Book class built in exercise 7.40 as a n example. Let's make the constructor Book (const string & n, const string & I, double PR, const string & A, const string & P) a normal constructor and the other two as delegate constructors.
Its specific form is as follows:
class Book { private: string Name, ISBN, Author, Publisher; double Price = 0; public: Book(const string &n, const string &I, double pr, const string &a, const string &p) :Name(n), ISBN(I), Price(pr), Author(a), Publisher(p) { } Book(std::istream &is):Book() { is >> *this; } };
Exercise 7.43
Suppose you have a class named NoDefault that has a constructor that accepts int, but no default constructor. Define class C. C has a member of type NoDefault. Define the default constructor of C.
Solution:
We need to provide a default int value as a parameter for the constructor of class C, and the class definition and validation procedures that satisfy the topic are as follows:
#include<iostream> #include<string> using namespace std; // The type does not explicitly define a default constructor, and the compiler will not synthesize one for it class NoDefault { public: NoDefault(int i) { val = i; } int val; }; class C { public: NoDefault nd; // nd must be initialized explicitly by calling NoDefault's parameterized constructor C(int i = 0) :nd(i) { } }; int main() { C c; // Default constructor of type C used cout << c.nd.val << endl; system("pause"); return 0; }
Exercise 7.44
Is this statement legal? If not, why?
vector<NoDefault> vec(10);
Solution:
The meaning of the above statement is to create a vector object vec, which contains 10 elements. Each element is of type NoDefault and performs default initialization.
However, because we did not design a default constructor in the definition of the class NoDefault, the required default initialization process cannot be performed. The compiler will report this error.
Exercise 7.45
If the element type of the vector defined in the previous exercise is C, is the declaration legal? Why?
Solution:
Compared with the previous exercise, if you change the element type of vector to C, the declaration is legal. This is because we have defined a default constructor with parameters for type C, which can complete the default initialization required by the declaration statement.
Exercise 7.46
Which of the following statements is not true? Why?
- (a) A class must provide at least one constructor.
- (b) The default constructor is one with an empty argument list.
- © if there is no meaningful default value for a class, the class should not provide a default constructor.
- (d) If the class does not have a default constructor defined, the compiler generates one for it and initializes each data member to a default value of the corresponding type.
Solution:
(a) Is wrong, the class can not provide any constructors, at this time the compiler automatically implements a synthetic default constructor.
(b) It's wrong. If a constructor contains several parameters, but at the same time provides default parameters for them, the constructor also has the function of default constructor.
(c) Is wrong, because if a class does not have a default constructor, that is, we define some constructors of the class but do not design a default constructor for it, the class cannot be used when the compiler does need to implicitly use the default constructor. So in general, you should build a default constructor for your class.
(d) Is wrong. For the compiler synthesized default constructor, the members of class type execute the default constructor of their own class, and the members of built-in type and composite type only perform initialization on the objects defined in the global scope.
Exercise 7.47
Explains whether the sales & U data constructor that accepts a string parameter should be explicit, and explains the advantages and disadvantages of doing so.
Solution:
The sales data constructor that accepts a string parameter should be explicit, otherwise, the compiler may automatically convert a string object to a sales data object, which seems a bit arbitrary and sometimes goes against the original intention of the programmer.
The advantage of using explicit is to avoid unexpected errors caused by implicit class type conversion. The disadvantage is that when users really need such class type conversion, they have to use a slightly cumbersome way to achieve it.
Exercise 7.48
Assuming that the constructor of Sales_data is not explicit, what operations will be performed by the following definitions?
string null_isbn("9-999-9999-9"); Sales_data item1(null_isbn); Sales_data item2("9-999-99999-9");
Solution:
- If the constructor is not explicit, the string object is implicitly converted to the Sales_data object;
- On the contrary, if the constructor is explicit, the implicit class type conversion will not occur.
In the code given in this topic, the first line creates a string object, and the second and third lines call the Sales_data constructor (which accepts a string) to create its object. No class type conversion is required here, so item1 and item2 can be created correctly, regardless of whether the constructors of sales data are explicit or not. Their bookNo members are 9-999-99999-9, and other members are 0.
Exercise 7.49
What happens to the three different declarations of the combine function when we call i.combine(s)? Where i i s a Sales_data and S is a string object.
(a) Sales_data &combine(Sales_data); (b) Sales_data &combine(Sales_data&); (c) Sales_data &combine(const Sales_data&) const;
Solution:
(a) Yes, the compiler first creates a sales data object with the given string object s, and then the newly generated temporary object is passed to the formal parameter of combine (the type is sales data). The function executes correctly and returns the result.
(b) Unable to compile because the parameter of the combine function is a non constant reference, and S is a string object. The compiler uses s to automatically create a temporary object of sales & data, but the newly generated temporary object cannot be passed to the non constant reference required by the combine. If we change the function declaration to Sales_data & combine (const Sales_data &); then we can.
(c) Unable to compile because we declared the combine as a constant member function, so the function cannot modify the value of the data member.
Exercise 7.50
Determine if there are any constructors in your Person class that should be explicit.
Solution:
The Person class we defined earlier contains three constructors. Because the number of parameters accepted by the first two constructors is not 1, they do not have the problem of implicit conversion, and of course, they do not need to specify explicit.
The last constructor of the Person class, Person (STD:: istream & is), takes only one parameter, and by default, it will automatically convert the read data into a Person object. We prefer to strictly control the generation process of Person objects,
- If you really need to use the Person object, you can specify it explicitly;
- In other cases, you do not want automatic type conversion to occur. So this constructor should be specified as explicit.
Exercise 7.51
vector defines its one parameter constructor as explicit, while string is not. What do you think is the reason?
Solution:
Whether automatic conversion from parameter type to class type makes sense depends on the programmer's opinion,
- If the transformation is natural, it should not be defined as explicit;
- If the semantic distance between them is far, in order to avoid unnecessary transformation, the corresponding constructor should be specified as explicit.
The single parameter accepted by string is const char * type. If we get a constant character pointer (character array), it's a natural process to treat it as a string object. It's also logical for compiler to automatically convert parameter type to class type, so we don't need to specify it as explicit.
In contrast to string, vector accepts a single parameter of type int, which is intended to specify the capacity of vector. If we provide an int value where we need vector and want the int value to be automatically converted to vector, this process is far fetched, so it is more reasonable to define the vector's single parameter constructor as explicit.
Exercise 7.52
Use the sales data class in section 2.6.1 to explain the following initialization process. If there is a problem, try modifying it.
Sales_data item = {"987-0590353403", 25, 15.99};
Solution:
The intention of the program is to perform the aggregate class initialization operation on the item and initialize the data members of the item with the values in curly braces. However, if the actual process does not conform to the original intention of the program, the compiler will report an error.
This is because the aggregation class must meet some very strict conditions, one of which is that there is no in class initial value, and in the definition given in section 2.6.1, the data members units "sold" and "revenue" both contain in class initial value. As long as the initial values in these two classes are removed, the program can run normally.
struct Sales_data { string bookNo; unsigned units_sold; double revenue; };
Exercise 7.53
Define your own Debug.
Solution:
Literal constant class is a very special class type. Aggregate class is literal constant class. Although some classes are not aggregate classes, they are also literal constant classes when they meet the requirements in the book. The literal constant class must provide at least one constexpr constructor.
Just refer to the examples in the book to define the Debug class.
class Debug { public: constexpr Debug(bool b = true) : hw(b), io(b), other(b) { } constexpr Debug(bool h, bool i, bool o) : hw(r), io(i), other(0) { } constexpr bool any() { return hw || io || other; } void set_hw(bool b) { hw = b; } void set_io(bool b) { io = b; } void set_other(bool b) { other = b; } private: bool hw; // Hardware error, not IO error bool io; // IO error bool other; // Other mistakes };
Exercise 7.54
Should a member in Debug that starts with set be declared constexpr? If not, why?
Solution:
These members starting with set īšŖ cannot be declared as constexpr. These functions are used to set the value of data members. Constexpr functions can only contain return statements and are not allowed to perform other tasks.
Exercise 7.55
Is the Data class in section 7.5.5 a literal constant class? Please explain why.
Solution:
Because the Data class is an aggregate class, it is also a literal constant class.
Exercise 7.56
What are static members of a class? What are its advantages? What is the difference between a static member and an ordinary member?
Solution:
Static member refers to the class member with the keyword static before the declaration statement. The static member is not a part of any individual object, but is shared by all objects of the class.
Advantages of static members include:
- Scope is within the scope of a class, avoiding name conflicts with members of other classes or global scopes;
- It can be a private member, but not a global object;
- By reading the program, you can easily see that static members are associated with specific classes, which makes the meaning of the program clear.
The difference between static members and ordinary members is mainly reflected in the association between ordinary members and objects of a class, which is a part of a specific object; while static members do not belong to any specific object, which is shared by all objects of the class. In addition, there is a subtle difference. Static members can be used as default arguments, while ordinary data members cannot be used as default arguments.
Exercise 7.57
Write your own Account class.
Solution:
If some (some) members of a class should logically be associated with the class itself rather than with the concrete objects of the class, we should declare such members as static. In the Account class, it is obvious that the interest rate is relatively stable and unified, and it should be a static member; while the Account holder and its savings amount are closely related to the object, not static.
For simplicity, we only declare the Account class:
class Account { private: string strName; double dAmount=0.0; static double dRate; };
Exercise 7.58
Are there any errors in the declaration and definition of the following static data members? Please explain why.
//example.h class Example { public: static double rate = 6.5; static const int vecSize = 20; static vector<double> vec(vecSize); }; //example.c #include "example.h" double Example::rate; vector<double> Example::vec;
Solution:
There are several errors in the program:
The initialization of rate and vec is wrong inside the class, because other static members cannot be initialized inside the class except static constant members.
In addition, the two statements in the example.c file are also wrong, because here we have to give the initial value of the static member.