1. 程式人生 > 其它 >Chapter 7 Classes

Chapter 7 Classes

 

7.1 Defining Abstract Data Type


  • The fundamental ideas behind classes are data struaction and encapsulation.
Key Concept: Different Kind of Programming Roles

C++ programmers tend to speak of users interchangeably as users of the application or users of a class.

 

7.1.2 Defining the Revised Sales_data Class


  • Functions defined in the class are implicitly inline.
  struct Sales_data{
      std::string isbn() const { return bookNo; }
      Sales_data& combine (const Sales_data&);
      double avg_price() const;
      std::string book No;
      unsigned units_sold = 0;
      double revenue = 0.0;
  };
  // nonmember Sales_data interface functions
  Sales_data add(const Sales_data&, const Sales_data&);
  std::ostream &print(std::ostream&, const Sales_data&);
  std::istream &read(std::istream&, Sales_data&);

1. Defining Member Functions

  • Although every member must be declared inside its class, we can define a member function's body either inside or outside of the class body.

2. Introducing this

  • Member functions access the object on which they were called through an extra, implicit parameter named this
    . When we can call a member function, this is initialized with the address of the object on which the function was invoked.
  • Any direct use of a member of the class is assumed to be an implicit reference through this.
  // when isbn use bookNo, it is implicitly using the member to which this points.
  std::string isbn() const { return this->booNo; }
  • Because this is intended to always refer to "this" object, this is a const pointer. We cannot change the address that this holds.

3. Introducing const Member Functions

  • By default, the type of this is a const pointer to the nonconst version of the class type.
  • A const following the parameter list indicates that this is a pointer to const. Member functions that use const in this way are const member functions. (may read but not write to the data members of the objects on which it is called)

4. Class Scope and Member Functions

5. Defining a Member Function outside the Class

  • The name of a member defined outside the class must include the name of the class of which it is a member. (use the scope operator)
  double Sales_data::avg_price() const{}

6. Defining a Function to Return "This" Object

  Sales_data& Sales_data::combine(const Sales_data &rhs){
      units_sold += rhs.units_sold;  // add the member of rhs into
      revenue += rhs.revenue;  // the members of "this" object
      return *this;  // return the object on which the function was called
  }
  total.combine(trans);  // update the running total
  • The address of total is bound to the implicit this parameter and rhs is bound to trans.
  • Here the return statement dereferences this to obtain the object on which the function is executing. (a reference to total)

 


  • Ordinarily, nonmember functions that are part of the interface of a class should be declared in the same header as the class itself.

1. Defining the read and print Functions

  • The IO classes are types that cannot be copied, so we may only pass them by reference. Reading or writing to a stream changes that stream, so both functions take ordinary references, not references to const.

 

7.1.4 Constructors


  • Constructors have the same name as the class. Unlike other functions, constructors have no return type.
  • A class can have multiple constructors. (differ in the number or type of their parameter)
  • Unlike other member functions, constructors may not be declared as const. Constructors can write to const obeject during their construction.

1. The Sythesized Default Constructor

  Sales_data total;  // variable to hold the running sum.
  Sales_data trans;  // variable to hold data for the next transaction.
  • If our class does not explicitly define any constructors, the compiler will implicitly define the default constructor for us. (synthesized default constructor which takes no arguments)

2. Some Class Cannot Rely on the Synthesized Default Constructor

  • The compiler generates a default constructor automatically only if a class declares no constructors.
  • Classes that have members of built-in or compound type usually should rely on the synthesized default constructor only if all such members have in-class initializers.

3. What = default Means

  • Under C++11, if we want the default behavior, we can ask the compiler to generate the constructor for us by writing = default after the parameter list;
  • Sometimes we have provided a constructor, the compiler will not automatically generate a default constructor for us. So we use = default to ask the compiler to synthesize the default constructor's definition for us.
  Sales_data() = default;

4. Constructor Initializer List

  // the new parts in the definitions are the colon and the code between it.
  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) {}
  // has the same behavior as the original constructor defined above.
  Sales_data(const std::string &s) :
      bookNo(s), units_sold(0), revenue(0) {}
Best Practice

Constructors should not override in-class initializers except to use a different initial value. If you can't use in-class initializers, each constructor should explicitly initialize every member of built-in type.

5. Defining a Constuctor outside the Class Body

  Sales_data::Sales_data(std::istream &is){
  read(is, *this);  // read will read a transaction from is into this object.
  }
  • Sales_data::Sales_data says what we're defining the Sales_data member named Sales_data. This member is a constructor because it has the same name as its class.
  • We use *this to pass "this" object as an argument to the read function.

7.1.5 Copy, Assignment, and Destruction

  • Objects are copied: 1. when we initialize a variable; 2. when we pass an obj by value; 3. when we return an obj by value.
  • Objects are assgined: 1. when we use the assignment operator.
  • Objects are destroyed: 1. when they cease to exist.(such as when a local object is destroyed on exit from the block in which it was created.)

1. Some Classes Cannot Rely on the Synthesized Versions

 

7.2 Access Control and Encapsulation

  • The public members define the interface to the class.
  • The private sections encapsulate(i.e., hide) the implementation. 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.

1. Using the class or struct Keyword

  • The only difference between using class and using struct to define a class is the defult access level. (struct is public, and class is private)

 

7.2.1 Friends


  • A class can allow another class or function to access its nonpublic members by making that class or function a friend.(friend declaration)
  • Ordinarily it is a good idea to group firend declarations together at the beginning or end of the class definition.
Key Concept: Benefits of Encapsulation

  • User code cannot inadvertently corrupt the state of an encapsulated object.
  • The implementation of an encapsulated class can change over time without requiring changes in uer-level code.
  • Although user code need not change when a class definition changes, the source files that use a class must be recompiled any time the class changes.

1. Declarations for Friends

  • A friend declaration only specifies access. We must also declare the function separately from the friend declaration.

 

7.3 Additional Class Features


 

7.3.1 Class Members Revisited


1. Defining a Type Member

  class Screen{
  public:
      typedef std::string::size_type pos;
  }
  class Screen{
  public:
      // alternative way to declare a type member using a type alias.
      using pos = std::string::size_type;
  }
  • Type members usually appear at the beginning of the class.

2. Member Functions of class Screen

3. Making Members inline

  • Member functions defined inside the class are automatically inline.

4. Overloading Member Functions

5. mutable Data Member

  • A mutable data member is never const, even when it is a member of a const object.
  class Screen{
  public:
      void some_member() const;
  private:
      mutable size_t access_ctr;  // may change even in a const object.
  };
  void Screen::some_member() const{
      ++access_ctr;  // keep a count of the calls to any member function.
      // whatever other work this member needs to do.
  }

6. Initializers for Data Members of Class Type

  • In-class initializers must use either the = form of initialization or the direct form of initialization using curly braces.

 

7.3.2 Functions That Return *this


  • Functions that return a reference are lvalues, which means that they return the object itself, not a copy of the object.
  // move the cursor to a given position, and set that character.
  myScreen.move(4,0).set('#');
  // these operations will execute on the same object.

  // if move returns Screen not Screen&.
  Screen tmp = myScreen.move(4,0);  // the return value would be copied.
  temp.set('#');  // the contents inside myScreen would be unchanged.
  • If move had a nonreference return type, then the return value of move would be a copy of *this.(The call to set would change the temporary copy, not myScreen)

1. Returning *this from a const Member Function

  • A const member function that return *this as a reference should have a return type that is a reference to const.

2. Overloading Based on const

  • We can overload a member function based on whether it is const for the same reasons that we can overload a function based on whether a pointer parameter points to const.
  class Screen{
  public:
      // 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){
          do_display(os);
          return *this;}
      }
  private:
      // function to do the work of displaying a Screen.
      void do_display(std::ostream &os) const {
          os << contents;
      }
  };
Advid: Use Private Utility Functions for Common Code

In practice, well-designed C++ programs tend to have lots of small functions such as do_dspaly that are called to do the "real" work of some other set of functions.

3. Exercise 7.27: Screen with move, set and display operations.

The complete code:
  #include<iostream>
  #include<string>
  using namespace std;

  class Screen {
  private:
      unsigned height = 0, width = 0;
      unsigned cursor = 0;
      string contents;
  public:
      Screen() = default;  // default constructor.
      Screen(unsigned ht, unsigned wd) : height(ht), width(wd), contents(ht* wd, ' ') {}
      Screen(unsigned ht, unsigned wd, char c) : height(ht), width(wd), contents(ht* wd, c) {}
  public:
      Screen& move(unsigned r, unsigned c) {
          cursor = r * width + c;
          return *this;
      }
      Screen& set(char ch) {
          contents[cursor] = ch;
          return *this;
      }
      Screen& set(unsigned r, unsigned c, char ch) {
          contents[r * width + c] = ch;
          return *this;
      }
      Screen& display(ostream &os) {
          os << contents;
          return *this;
      }
  };

  int main() {
      Screen myScreen(5, 5, 'x');
      myScreen.move(4, 0).set('#').display(cout);
      cout << "\n";
      myScreen.display(cout);
      cout << "\n";
  }

  // output:
  // xxxxxxxxxxxxxxxxxxxx#xxxx
  // xxxxxxxxxxxxxxxxxxxx#xxxx

 

7.3.3 Class Types

  • Every class defines a unique type.
  • Even if two classes have exactly the same member list, they are different types. The members of each class are distinct from the members of any other class(or any other scope).

1. Class Declarations

 

Friendship Revisited


1. Friendship between Classes

  • Each class controls which classes or functions are its friends. Friendship is not transitive.
  class Screen{
      // Window_mgr members can access the private parts of class Screen.
      friend class Window_mgr;
  }
  class Window_mgr{
  public:
      // location ID for each screen on the window
      using ScreenIndex = std::vector<Screen>::size_type;
      // reset the Screen at the given position to all blanks.
      void clear(ScreenIndex);
  private:
      std::vector<Screen> screens{Screen(24, 80, ' ')};
  };
  void Window_mgr::clear(ScreenIndex i){
      // s is a reference to the Screen we want to clear.
      Screen &s = screen[i];
      // reset the contents of that Screen to all blanks.
      s. contens = string(s.heitght * s.width, ' ');
  }

2. Making a Member Function a Friend

3. Overloading Functions and Friendship

  • A class must declare as a friend each function in a set of overloaded functions that it wishes to make a friend.

4. Friend Declarations and Scope

5. Exericise 7.32: Screen and window_mgr in which clear is member of Window_mgr and a friend of Screen

The complete code:
#include<iostream>
#include<string>
using namespace std;

class Window_mgr {
public:
	void clear();
};
class Screen {
	friend void Window_mgr::clear();
private:
	unsigned height = 0, width = 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 clear myScreen contents: " << endl;
	cout << myScreen.contents << endl;
	myScreen.contents = "";
	cout << "after claer myScreen contents: " << endl;
	cout << myScreen.contents << endl;
}

int main() {
	Window_mgr w;
	w.clear();
	return 0;
}

 

7.4 Class Scope


1. Scope and Members Defined outside the Class

  • Outside of the class, the name of the members are hidden, that's why we must using the scope operator.
  • Once the class name is seen, the remainder of the definition——including the parameter list and the function body——is in the scope of the class.
  void Window_mgr::clear(ScreenIndex i){  // no need to specify that we want the ScreenIndex that is defined by WindowMgr.
      Screen &s= screens[i];
      s.contents = string(s.height * s.width, ' ');
  } 
  • When a member function is defined outside the class body, any name used in the return type is outside the class scope, the return type must specify the class of which it is a member.
  Window_mgr::ScreenIndex Window_mgr::addScreen(const Screen &s){
      screens.push_back(s);
      return screens.size() - 1;
  }

 

7.4.1 Name Lookup and Class Scope


  • Member function definitions are processed after the compiler processes all of the declarations in the class.

1. Name Lookup for Class Member Declarations

  typedef double Monny;
  string bal;
  class Account{
  public: 
      Money balance() {return bal;}  // the fucntion body is processed only after the entire class is seen.
                                     // return the member named bal, not the string from the outer scope. 
  private:
      Money bal;
  }

2. Type Names Are Special

  • In a class, if a member uses a name from an outer scope and that name is a type, then the class may not subsequently redefine that name.
  typedef double Money;
  class Account{
  public:
      Money balance() {return bal;}  // uses Money from the outer scope.
  private: 
      typedef double Money;  // error: cannot redefine Money.
      Money bal;
  };

3. Normal Block-Scope Name Lookup inside Member Definitions

  • Good prictice: don't use a member name for a parameter or other local variable.
  • Even though the class member is hidden, it is still possible to use that member by qualifying the member's name with the name of its class or by using the this pointer explicitly.
  int height;  // define a name subsequently used inside Screen.
  class Screen{
  public:
      typedef std::string::size_type pos;
  private:
      pos height = 0;
  };
  // bad practice: names local to member functions shouldn't hide member names.
  // in this case, the height parameter hides the member named height.
  void Screen::dummy_fcn(pos height){
      cursor = width * this->height;  // member height.
      // alternative way to indicate the member.
      cursor = width * Screen::height;  // member height.
  } 

4. After Class Scope, Look in the Surrounding Scope

  // bad practice: don't hide names that are needed from surrouding scopes.
  void Screen::dummy_fcn(pos height){
      cursor = width * ::height;  // which height? the global one.
  } 
  • Even though the outer object is hidden, it is still posiible to access that object by using the scope operator.

5. Names Are Resolved Where They Appear within a File

 

7.5 Constructors Revisited


 

7.5.1 Constructor Initializer List


1. Constructor Initializers Are sometimes Required

  • We hava no chance to assign the member which is const object or reference.
  // error: ci and ri must be initialized.
  ConstRef::ConstRef(int ii){
      // assignments:
      i = ii;  //ok.
      ci = ii;  // error: cannot assign to a const.
      ri = i;  // error: ri was never initialized.
  }
  • We must use the constructor initializer list to provide values for members that are const, reference, or of a class type that does not have a default constructor.

2. Order of Member Initialization

  • Members are initialized in the order in which they appear in the class definition. The order in which initializers appear in the constructor initializer list does not change the order of initialization.
Best Practice

It is a good idea to write constructor initializers in the same order as the members are declared. Moreover, when possible, avoid using members to initialize other members.

3. Default Arguments and Constructors

  • A constructor that supplies default argument for all its parameters aslso defines the default constructor.
  clss Sales_data{
      // defines the default constructor as well as one that takes a string argument.
      Sales_data(std::string s = ""):bookNo(s) { }
  };
  • The difference is that the constructor that takes a string argument uses that argument to initialize bookNo. The default constructor (implicitly) uses the string default constructor to initialize bookNo.

 

7.5.2 Delegating Constructors


  class Sales_data {
  public:
      // nondelegating constructor initializes members from corresponding arguments.
      Sales_data(std::string s, unsigned cnt, double price)
          : bookNo(s), units_sold(cnt), revenue(cnt * price) { }
      // remaining constructors all delegate to another constructor.
      Sales_data() : Sales_data("", 0, 0) {}
      Sales_data(std::string s) :Sales_data(s, 0, 0) {}
      Sales_data(std::istream &is) : Sales_data() { read(is, *this) } 
  };
  • When a constructor delegates to another constructor, the constructor initializer list and function body of the delegated-to constructor are both executed.

1. Exercise 7.41 Version of Sales_data class with delegating constructors.

The complete code:
#include<iostream>
#include<string>
using namespace std;

class Sales_data {
	friend istream& read(istream& is, Sales_data& item);
	friend ostream& print(ostream& os, const Sales_data& item);
public:
	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 delegating constructor get bookNo, units_sold, sellingpric and saleprice" << endl;
	}
	Sales_data() : Sales_data("", 0, 0, 0) {
		cout << "The delegating default initialize" << endl;
	}
	Sales_data(const string& book) :Sales_data(book, 0, 0, 0) {
		cout << "The delegating constructor get bookNo" << endl;
	}
	Sales_data(istream& is) :Sales_data() {
		read(is, *this);
		cout << "The delegating constructor get the user input" << endl;
	}

private:
	string bookNo;
	unsigned units_sold = 0;
	double sellingprice = 0.0;
	double saleprice = 0.0;
	double discount = 0.0;
};

istream& read(istream& is, Sales_data& item) {
	is >> item.bookNo >> item.units_sold >> item.sellingprice >> item.saleprice;
	return is;
}

ostream& print(ostream& os, const Sales_data& item) {
	os << item.bookNo << " " << item.units_sold << " " << item.sellingprice << " " << item.saleprice << " " << item.discount;
	return os;
}

int main() {
	Sales_data first("987", 85, 128, 109);
	Sales_data second;
	Sales_data third("987");
	Sales_data last(cin);
	return 0;
}

/*output:
  The delegating constructor get bookNo, units_sold, sellingpricand saleprice
  The delegating constructor get bookNo, units_sold, sellingpricand saleprice
  The delegating default initialize
  The delegating constructor get bookNo, units_sold, sellingpricand saleprice
  The delegating constructor get bookNo
  The delegating constructor get bookNo, units_sold, sellingpricand saleprice
  The delegating default initialize
*/

 

7.5.4 The Role of the Default Constructor


Best Practice

In practice, it is almost always right to provide a default constructor if other constructors are being defined.

1. Using the default Constructor

 

7.5.4 Implicit Class-Type Conversions


  • A constructor that can be called with a single argument defines an implicit conversion from the constructor's parameter type to the class type.
  Sales_data &combine(const Sales_data&);
  string null_book = "999";
  // constructs a temporary Sales_data object with units_sold and revenue equal to 0 and bookNo equal to null_book.
  item.combine(null_book);

1. Only One Clas-Type Conversion Is Allowed

  // error:requires two user-defined conversions:
  // 1. convert "999" to string.
  // 2. convert that (temporary) string to Sales_data.
  item.combine("999");
  // ok: explicit conversion to string, implicit conversion to Sales_data.
  item.combine(string("999"));
  // ok: implicit conversion to string, explicit conversion to Sales_data.
  item.combine(Sales_data("999"));

2. Class-Type Conversions Are Not Always Useful

3. Suppressing Implicit Conversions Defined by Constructors

  explicit Sales_data(const std::string &s) : bookNo(s) {}
  explicit Sales_data(std::istream&);

  item.combine(null_book);  // error: string constructor is explicit.
  item.combine(cin);  // error: istream constructor is explicit.
  • The explicit keyword is used only on the constructor declaration inside the class. It is not repeated on a definition made outside the class body.

4. explicit Constructors Can be Used Only for Direct Initialization

  • When a constructor is declared explicit, it can be used only with the direct form of initialization. Moreover, the compiler will not use this constructor in an automatic conversion.

5. Explicitly Using Constructors for Conversions

  // ok: the argument is an explicitly constructed Sales_data object.
  item.combine(Sales_data(null_book));l
  // ok: static_cast can use an explicit constructor.
  item.combine(static_cast<Sales_data>(cin));

6. Library Classes with explicit Constructors

 

7.5.5 Aggregate Classes


 

7.5.6 Literal Classes


1. constexpr Constructors

 

7.6 static Class Members


  • Classes sometimes need members that are associated with the class, rather than with individual object of the class type.

1. Declaring static Members

  • The static members of a class exist outside any object.
  • static member functions are not bound to any object; they do not have a this pointer.
  • static member functions may not be declared as const, and we may not refer to this in the body of a static member.

2. Using a Class static Member

3. Defining static Members

  • The static keyword, is used only on the declaration inside the class body.
  // define and initialize a static class member
  double Account::interestRate = initRate();
  • In gernal, we may not initialize a static member inside the class, must define and initialize each static data member outside the class body.

4. In-Class Initialization of static Data Members

Best Practice

Even if a const static data memebr is initialized in the class body, that member ordinarily should be defined outside the class definition.

5. static Members Can be Used in ways Ordinary Members Can't