C++Primer_第7章_类

来源:互联网 发布:ubuntu 16.04安装qq 编辑:程序博客网 时间:2024/05/20 09:49

7.1结构体

struct Sales_data {Sales_data() = default;Sales_data(const std::string &s):bookNo(s){}Sales_data(const std::string &s,unsigned n,double p):bookNo(s),units_sold(n),revenue(p*n){}Sales_data(std::istream&);std::string isbn()const { return bookNo; }Sales_data& combine(const Sales_data&);double avg_price()const;std::string bookNo;unsigned units_sold = 0;double revenue = 0.0;};


=default的含义

构造函数不能被声明成const的。

Under the new standard, if we want the default behavior, we can ask the compiler to generate the constructor for us by writing = default after the parameter list.

构造函数初始值列表

Sales_data(const std::string &s):bookNo(s){}Sales_data(const std::string &s,unsigned n,double p):bookNo(s),units_sold(n),revenue(p*n){}

花括号定义了(空的)函数体,冒号和花括号之间的部分称为构造函数初始值列表(constructor
initializer list),它负责为新创建的对象的一个或几个数据成员赋初值。括号括起来的是成员初值。

在类的外部定义构造函数

Sales_data::Sales_data(std::istream &is){read(is, *this);//read函数的作用是从is中读取一条交易信息然后存入this对象中}

练习7.11在你的Sales_data类中添加构造函数,然后编写一段程序令其用到每个构造函数。
#ifndef CP5_ex7_11_h#define CP5_ex7_11_h#include <iostream>#include <string>// Add constructors to your Sales_data classstruct Sales_data {Sales_data() = default;Sales_data(const std::string& s) : bookNo(s) {}Sales_data(const std::string& s, unsigned n, double p): bookNo(s), units_sold(n), revenue(n * p){}Sales_data(std::istream& is);std::string isbn() const { return bookNo; };Sales_data& combine(const Sales_data&);std::string bookNo;unsigned units_sold = 0;double revenue = 0.0;};// nonmember functionsstd::istream& read(std::istream& is, Sales_data& item){double price = 0;is >> item.bookNo >> item.units_sold >> price;item.revenue = price * item.units_sold;return is;}std::ostream& print(std::ostream& os, const Sales_data& item){os << item.isbn() << " " << item.units_sold << " " << item.revenue;return os;}Sales_data add(const Sales_data& lhs, const Sales_data& rhs){Sales_data sum = lhs;sum.combine(rhs);return sum;}// member functions.Sales_data::Sales_data(std::istream& is){read(is, *this);}Sales_data& Sales_data::combine(const Sales_data& rhs){units_sold += rhs.units_sold;revenue += rhs.revenue;return *this;}#endif
#include "ex7_11_sales_data.h"int main(){    Sales_data item1;    print(std::cout, item1) << std::endl;    Sales_data item2("0-201-78345-X");    print(std::cout, item2) << std::endl;    Sales_data item3("0-201-78345-X", 3, 20.00);    print(std::cout, item3) << std::endl;    Sales_data item4(std::cin);    print(std::cout, item4) << std::endl;}
练习7.12:把只接受一个istream作为参数的构造函数定义移到类的内部。
#ifndef CP5_ex7_12_h#define CP5_ex7_12_h#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 n, double p): bookNo(s), units_sold(n), revenue(n * p){}// Move the definition of the Sales_data constructor that takes an istream into// the body of the Sales_data class.Sales_data(std::istream& is) { read(is, *this); }std::string isbn() const { return bookNo; };Sales_data& combine(const Sales_data&);std::string bookNo;unsigned units_sold = 0;double revenue = 0.0;};// member functions.Sales_data& Sales_data::combine(const Sales_data& rhs){units_sold += rhs.units_sold;revenue += rhs.revenue;return *this;}// nonmember functionsstd::istream& read(std::istream& is, Sales_data& item){double price = 0;is >> item.bookNo >> item.units_sold >> price;item.revenue = price * item.units_sold;return is;}std::ostream& print(std::ostream& os, const Sales_data& item){os << item.isbn() << " " << item.units_sold << " " << item.revenue;return os;}Sales_data add(const Sales_data& lhs, const Sales_data& rhs){Sales_data sum = lhs;sum.combine(rhs);return sum;}#endif
练习7.13:使用istream构造函数重写第229页的程序。
#include "CP5_ex7_12.h"int main(){Sales_data total(std::cin);if (!total.isbn().empty()) {std::istream& is = std::cin;while (is) {Sales_data trans(is);if (total.isbn() == trans.isbn())total.combine(trans);else {print(std::cout, total) << std::endl;total = trans;}}}else {std::cerr << "No data?!" << std::endl;return -1;}return 0;}

拷贝、赋值和析构Copy, Assignment, and Destruction

    In addition to defining how objects of the class type are initialized, classes also control what happens when we copy, assign, or destroy objects of the class type.

    Although the compiler will synthesize the copy, assignment, and destruction operations for us, it is important to understand that for some classes the default versions do not behave appropriately. In particular, the synthesized versions are unlikely to work correctly for classes that allocate resources that reside outside the class objects themselves. As one example, in Chapter 12 we’ll see how C++ programs allocate and manage dynamic memory. As we’ll see in § 13.1.4 , classes that manage dynamic memory, generally cannot rely on the synthesized versions of these operations.
    However, it is worth noting that many classes that need dynamic memory can (and generally should) use a vector or a string to manage the necessary storage.Classes that use vectors and strings avoid the complexities involved in allocating and deallocating memory.

访问控制与封装Access Control and Encapsulation

    Members defined after a public specifier are accessible to all parts of the program. The public members define the interface to the class.
    Members defined after a private specifier are accessible to the member functions of the class but are not accessible to code that uses the class. The private sections encapsulate (i.e., hide) the implementation.
class Sales_data {public: // access specifier addedSales_data() = default;Sales_data(const std::string &s, unsigned n, double p):bookNo(s), units_sold(n), revenue(p*n) { }Sales_data(const std::string &s): bookNo(s) { }Sales_data(std::istream&);std::string isbn() const { return bookNo; }Sales_data &combine(const Sales_data&);private: // access specifier addeddouble avg_price() const{ return units_sold ? revenue/units_sold : 0; }std::string bookNo;unsigned units_sold = 0;double revenue = 0.0;};
敲打Warning!  The only difference between using class and usingstruct to define a class is the default access level. (class : private, struct : public)
出于统一编程风格的考虑,当我们希望定义的类的所有成员是public的时,使用struct;
反之,如果希望成员是private的,使用class。

友元 Friends

    A class can allow another class or function to access its nonpublic members by making that class or function a friend.//类可以允许其他类或函数访问它的非公有成员,方法是前面加friend的声明
//友元不存在传递性

class Sales_data {// friend declarations for nonmember Sales_data operations added//为Sales_data的非成员函数所做的友元声明friend Sales_data add(const Sales_data&, const Sales_data&);friend std::istream &read(std::istream&, Sales_data&);friend std::ostream &print(std::ostream&, const Sales_data&);// other members and access specifiers as before其他成员及访问说明符与之前一致public:Sales_data() = default;Sales_data(const std::string &s, unsigned n, double p) :bookNo(s), units_sold(n), revenue(p*n) { }Sales_data(const std::string &s) : bookNo(s) { }Sales_data(std::istream&);std::string isbn() const { return bookNo; }Sales_data &combine(const Sales_data&);private:std::string bookNo;unsigned units_sold = 0;double revenue = 0.0;};// declarations for nonmember parts of the Sales_data interface//Sales_data接口的非成员 组成部分的声明Sales_data add(const Sales_data&, const Sales_data&);std::istream &read(std::istream&, Sales_data&);std::ostream &print(std::ostream&, const Sales_data&);
友元声明只能出现在类定义的内部,但是在类内出现的具体位置不限。
Friend declarations may appear only inside a class definition; they may appear anywhere in the class.
安静Tip. Ordinarily it is a good idea to group friend declarations together at the beginning or end of the class definition.一般来说,最好在类定义开始或结束前的位置集中声明友元。

练习7.20:When are friends useful? Discuss the pros and cons of using friends.

friend is a mechanism by which a class grants access to its nonpublic members. They have the same rights as members.

Pros:

  • the useful functions can refer to class members in the class scope without needing to explicitly prefix them with the class name.
  • you can access all the nonpublic members conveniently.
  • sometimes, more readable to the users of class.

Cons:

  • lessens encapsulation and therefore maintainability.
  • code verbosity, declarations inside the class, outside the class.
练习7.21:Update your Sales_data class to hide its implementation.The programs you’ve written to use Sales_data operations should still continue to work.
#ifndef CP5_ex7_12_h#define CP5_ex7_12_h#include <iostream>#include <string>struct Sales_data;std::istream& read(std::istream&, Sales_data&);class Sales_data {friend std::istream& read(std::istream& is, Sales_data& item);friend std::ostream& print(std::ostream& os, const Sales_data& item);friend Sales_data add(const Sales_data& lhs, const Sales_data& rhs);public:Sales_data() = default;Sales_data(const std::string& s) : bookNo(s) {}Sales_data(const std::string& s, unsigned n, double p): bookNo(s), units_sold(n), revenue(n * p){}// Move the definition of the Sales_data constructor that takes an istream into// the body of the Sales_data class.Sales_data(std::istream& is) { read(is, *this); }std::string isbn() const { return bookNo; };Sales_data& combine(const Sales_data&);private:std::string bookNo;unsigned units_sold = 0;double revenue = 0.0;};// member functions.Sales_data& Sales_data::combine(const Sales_data& rhs){units_sold += rhs.units_sold;revenue += rhs.revenue;return *this;}// nonmember functionsstd::istream& read(std::istream& is, Sales_data& item){double price = 0;is >> item.bookNo >> item.units_sold >> price;item.revenue = price * item.units_sold;return is;}std::ostream& print(std::ostream& os, const Sales_data& item){os << item.isbn() << " " << item.units_sold << " " << item.revenue;return os;}Sales_data add(const Sales_data& lhs, const Sales_data& rhs){Sales_data sum = lhs;sum.combine(rhs);return sum;}#endif

类成员再探

typedef std::string::size_type pos;//alternative way to declare a type member using a type aliasusing pos = std::string::size_type;//使用类型别名等价地声明一个类型名字

7.5构造函数再探

如果成员是const、引用,或者属于某种未提供默认构造函数的类类型,我们必须通过构造函数初始值列表为这些成员提供初值。在很多类中,初始化和赋值的区别事关底层效率问题:前者直接初始化数据成员,后者则先初始化再赋值。建议读者养成使用构造函数初始值的习惯,这样能避免某些意想不到的编译错误。

委托构造函数:
class Book_{public:Book_() = default;Book_(unsigned no,std::string name,std::string author,std::string pubdate):no_(no),name_(name),author_(author),pub_date_(pubdate){}Book_(std::istream &in) { in >> no_ >> name_ >> author_ >> pub_date_; }private:unsigned no_;std::string name_;std::string author_;std::string pub_date_;};//使用委托构造函数class Book {public:Book(unsigned no, std::string name, std::string author, std::string pubdate) :no_(no), name_(name), author_(author), pubdate_(pubdate) { }Book() : Book(0, "", "", "") { }Book(std::istream &in) : Book() { in >> no_ >> name_ >> author_ >> pubdate_; }private:unsigned no_;std::string name_;std::string author_;std::string pubdate_;};