c++primer Chapter VII Class

The basic idea of classes is data abstraction and encapsulation.
Data abstraction is a programming technique that relies on interfaces and implements separation.

7.1 Define abstract data types

7.1.1 Design the Sales_data class

  • The declaration of a member function must be inside the class, and the definition can be inside or outside
  • Non-member functions that act as interfaces, such as print, read, declare that the definitions are outside the class.
  • Functions defined within a class are implicit inline functions
  • Implicitly initialize this pointer when calling a member function
  • Any parameter or variable with custom name this is illegal
  • const member function
    • Const member function: function with const keyword after parameter list
    • const modifies the type of implicit this pointer
    • By default, this type is a constant pointer to a non-constant type. Therefore, this cannot be bound to a non-constant object (this cannot be bound to other objects), so it cannot call a normal member function on a constant object (const objects cannot be used to access normal member functions).
    • const member function improves function flexibility
  • Constant objects, as well as references or pointers to constant objects, can only call constant member functions.
  • The compiler handles classes in two steps.
    1. Compile member declarations.
    2. The member body is compiled after all member declarations have been compiled. Therefore, when member declarations appear in the member body, the compiler can compile normally as well.
  • Define function body outside class
    • You need to precede the function name with the class name::, the remaining code after the class name is in scope
    • If the return type is also declared within a class, you need to precede both the function name and the return type with the class name:.
    • When declared as a const member function within a class, the const keyword cannot be omitted when defined externally.
  • If you need to return the class itself, use return *this

7.1.3 Define class-related nonmember functions

  • Class-related nonmember function: An interface that belongs to a class, but not to the class itself.
  • Function declarations and definitions are usually separated. Class declarations are in the same header file.
  • Typically, copying a class is actually copying its members. (See the copy assignment function if you want the copy to do something else.)
Sale_data s1;
Sale_data s2=s1;//s2 copies the members of s1

7.1.4 Constructor

  • The task of the constructor is to initialize the data members of the class object
  • Constructors will be executed as long as class objects are created
  • The constructor name is the same as the class name, with no return type, and the others are the same as normal functions.
  • Constructor cannot be declared const
  • Default constructor
    • Default constructor does not require any arguments
    • If no constructor is explicitly defined for the class, the compiler implicitly constructs a composite default constructor.
    • The synthetic default constructor initializes the class members according to the following rules
      • Initialize members with an in-class initializer if it exists
      • Otherwise, initialize members by default
    • Some classes cannot depend on the synthetic default constructor
      • If a class contains members of a built-in or composite type, it is only appropriate to use the composite default constructor if all of these values are assigned to an in-class initializer.
      • If class A contains a member class b, if B does not have a default constructor, the compiler cannot construct the correct default constructor for a
      • If other constructors are defined, the compiler will not construct a default initializer
class A{
//A constructor with string arguments is defined
//At this point, the compiler will not synthesize the default constructor
	A(std::string a){}
}
A a;//Error, no default constructor
A a1(std::string("Little Black"));//string parameter only
  • Adding = defualt to the parameter list requires the compiler to generate a default constructor
  • =defualt can appear within a class along with a declaration or outside as a definition.
    Inside the class, the default constructor is inline, and outside the class, it is not.
class A{
	A()=defualt;
}
A a;//Correct, compiler generates default constructor
  • List of constructor initializers
    • There is a compiler that does not support in-class initializations, so the default constructor is not applicable (because the default constructor initializes class members using in-class initializations), and the list of constructor initializers should be used.
    • A list of function initializers is a list of parameters as follows (colons and codes between colons and curly braces:: bookNo(s))
    • Constructors should not easily override the initial values within a class unless the newly assigned values are different from the original values
    • During the constructor process, members that do not appear in the function initialization list will be initialized by default
class Sales_data{
	Sales_data(const std::string &s,unsigned n,double p):
	bookNo(s),units_sold(n),revenue(p*n){}
	//When the compiler does not support in-class initializations, you can define them as follows
	Sales_data(const std::string &s):
	bookNo(s),units_sold(0),revenue(0){}
}
  • Define a constructor outside a class to declare which class is the constructor, prefix the function name with the class name:
Sales_data::Sales_data(std::istream cin){
	read(cin,*this);
}

7.1.5 Copies, Assignments, and Destructions

  • The compiler synthesizes copies, assignments, and destroy operations for classes.
  • The version generated by the compiler copies, assigns, and destroys each member of the object

7.2 Access control and packaging

  • access specifier
    Members after the public specifier can be accessed throughout the program
    Members after the private specifier can be accessed by member functions of the class
  • A class can contain 0 or more access specifiers, valid until the next specifier appears.
  • The only difference between a class and a struct keyword defining a class is
    • class defaults to private before the first access specifier appears
    • The default zone for struct before the first access specifier appears is public

7.2.1 Friends

  • Classes can allow other classes or functions to access their non-public members by declaring friends with the keyword friend.
  • A declaration of a friend can only be inside a class
  • Friend declarations can be in any location, and it is best to centrally declare a friend before the class definition begins or ends.
  • Benefits of packaging
    • Ensure that user code does not unintentionally destroy the state of encapsulated objects
    • The implementation details of encapsulated classes can change at any time
  • A friend's declaration within a class only specifies access, not a function declaration in general sense
    • If you want users of a class to be able to call a friend function, you need to make a special declaration of the function in addition to the friend declaration.
    • To make a friend visible to class users, a friend declaration is prevented from being in the same header file as the class itself
    • Some compilers force friend functions to be declared outside a class before they can be used

Other features of class 7.3

Following are descriptions of type members, class members'in-class initial values, variable data members, inline member functions, returning *this from member functions, how to define the use of class types, friend classes

Re-exploration of Class 7.3.1 Members

  • Category name (type member):
    • Type names defined in classes have access restrictions like other members, either public or private
    • Category name must be defined before use
      (Recall: Class member variables can be defined after class member functions, but are used in class functions because the compiler compiles class member variables before a class member function)
    • Type members usually appear at the beginning of a class
class Screen{	
	public:
	//Equivalent to using pos = std::string::size_type
	typedef std::string::size_type pos;
}
  • Make members inline
    Functions defined inside a class are automatically inlined, functions defined outside a class are added if inline functions need to be declared.Inline member functions should also be defined in the same header folder as the corresponding class
inline 
Screen& Screen::move(pos r,pos c){
	pos row = r*width;
	cursor = row + c;
	return *this; 
}
  • A variable data member, never a const, even if he is a member of a const object
class Screen{
public void some_member() const;
private:
	mutable size_t access_ctr;//Declare variable data members using mutable
}
void Screen::some_member() const {
	++access_ctr;//Variable data members can be modified even in const member functions
}
  • Initialization within a class using = or direct initialization in curly brackets

7.3.2 Returns the member function of *this

  • Note whether the return type is a reference or not. References have a significant impact on how functions are used.
inline Screen &Screen::set(char ch){
	content[cursor] =ch;
	return *this;
}
inline Screen &Screen ::move(pos r,pos col){
	cursor= r * width + col ;
	return *this;
}
Screen s(3,2,'');
//The move function returns s s s itself, so you can call the set function
//And the move function returns a reference to Screen, or a new Screen object if it does not return a reference
s.move(3,2).set('!');
  • A constant reference is returned from the const function and class member variables cannot be modified in the const function
  • Use const function for overloading
    Write the function displayPrint contents in Screen, because it is just a display and does not need to modify the values, so it should be a const function.
    However, you want to be able to move the cursor after the display: s.display().move(2,3). This requires that the value returned by displayis modifiable, so this should not be a const function.
    Based on const overloading, you can overload based on whether the Screen object is const or not.
    • It's recommended that you use functions like do_display to get your work done, so that public code uses private functions
      • Can be modified centrally
      • No extra overhead
class Screen{
public:
	Screen* display(std::ostream &os){
		do_display(os);
		return *this;
	}  
	const Screen* display(std::ostream &os) const{
		do_display(os);
		return *this;
	} 
private:
	void do_display(std::ostream &os) const{
		os<<content;
	}
}
int main(){
	const Screen cs(3,3,'!');
	Screen s(3,3,'.')
	cs.display();//Since cs is const, call the second const function
	s.display();//Call the first non-const function
}

7.3.3 Types

  • Each class defines a unique type and is a different class even if its members are identical.
class A{
int member;
}
class B{
int member;
}
A a;
B b = a;//Error!!
  • Incomplete type
    • Like a function, a class can also be declared without a definition, which is called an incomplete type
    • The incomplete type is to indicate to the program that this is a class name
    • Incomplete types have limited usage environments, but you can define pointers or references to them, declare (but not define) functions that take incomplete types as parameters or return types.
  • Class must be defined before it is created
  • A member of a class cannot have the class itself (except the static class described later), but it can be a reference or pointer to itself

7.3.4 Friends Explore Again

  • A class has its friend class, so a friend function can access all its members
  • There is no transitivity in a friend relationship
  • Each class is responsible for controlling its own friend class or function
    • Define the order of friend functions:
      There is a screen class, private member content;
      The clear function clears the contents of the content.
      1. Declare the clear function first
      2. Define the clear function as a friend function in the screen class
      3. Define the clear function, using the screen class
    • Define Friend Class
      There is a class window, which has private member content for windows; the friend class window_mgr requires direct operation on content.
      • Write a window class normally, declare in the window class: friend class window_mgr;
      • Write the window_mgr class normally, you can use the content of windows directly
      • Note to write the class in the header file in the following format; otherwise the compilation will error duplicate class definitions
#ifndef xxx_H
#define xxx_H
/class Definition///
#endif
  • A class that wants to define a set of overloaded functions as its friends requires a friend declaration for each of these functions.
  • A friend declaration simply means a declaration of a friend relationship, but not a declaration of the function itself.
struct X{
	friend viod f(){/*Friend functions can be defined inside classes, but I don't think that makes sense*/
	X(){f();}//Error, f not yet defined
	void g();
	void h();
	}
	void X::g(){ return f();}//Error, f not yet defined
	void f();
	void X::h(){return f();}//Correct, the declaration of f is already defined
};

Scope of class 7.4

  • A method defined outside of a class needs to be used before the method name:: Describes which class the method belongs to, and after describing which class it belongs to, the scope of the function lies within that class.
    • That is, the name used for the return type is outside the scope of the class. If the return type is also a member of the class, it needs to be used before the return type:: Specify the class to which the return type belongs.
//Pos's type is declared in the window class, and the return type is outside the scope of the class, so use window::pos
window::pos window::get_pos(){
//All code scopes after window::get_pos are within the class, so returning cursor is equivalent to this->cursor
return cursor;
}

7.4.1 Scope for name lookups and classes

  • Class definitions are handled in two steps
    1. Compile member declarations
    2. Compile the function body until all class members are visible
  • Generally speaking, an inner scope can redefine an outer scope name; however, if a name from an outer scope is used in a class and the name represents a type, the class cannot redefine the name later
typedef double Money;
class Acount{
public:
	Money balace(){return bal;}//Money using outer definition
private: 
	typedef double Money;//Error, Money cannot be redefined
	Money bal;
}
  • The definition of the type name usually appears at the beginning of the class to ensure that all members using the type appear after the definition;
  • Variables with the same name in the class are hidden, but member variables can be accessed with the this pointer
double height;
class Window{
	double height;
}
void Window::dummy_fcn(double height){
	double class_height = this->height;
	double para_height = height; 
	double  global_height = ::height;
}

Re-exploration of 7.5 Constructor

List of 7.5.1 constructor initializers

  • When a class has a reference member and a const member, it must be initialized in the constructor using an initializer list
  • It is recommended that you get into the habit of using constructor initializers
  • The list of initializers only describes the values of the initializer members and does not limit the order in which they are executed; the order of initializers is consistent with the order in which they appear in the class definition (some compilers generate a warning when the order in the list of initializers and in the class is inconsistent)
//Sample Dangerous Operations
strcut X{
//Actually initialize in declarative order, when rem is initialized first, the value of base is unknown
X(int i,int j):base(i),rem(base%j){}
int rem,base;
}
  • It is recommended that you use an initial list of values in the same order as the variables in the class and, if possible, try to avoid using some members to initialize other members.

7.5.2 Delegate Constructor

  • The only entry to the member initializer list is the class name, which can be used to call other constructors. The call procedure should be written at the initializer list location
class Sale_data{
public:
	Sales_data(const std::string &s,unsigned s_num,double price):units_sold(s_num),revenue(s_num*price),BookNo(s){}
	Sales_data():Sales_data("",0,0){}//Delegate to previous constructor
}

7.5.3 Role of default constructors

  • Perform default construction parameters when the object is initialized by default or by value
  • Default initialization occurs at:
    1. When a non-static variable or array defined without any initial value is not used within a block scope
    2. The class itself contains members of the class type and uses a synthetic default constructor
    3. When a class type member is not explicitly initialized in the constructor initializer list
  • Value initialization occurs at:
    1. When initializing an array, fewer initial values are provided than the array size
    2. When defining a local variable without an initial value
    3. Write an expression like T() that explicitly requests value initialization, where T is the type name. For example, a vectoraccepts an argument indicating the size of the vectors
  • If other constructors are defined, the compiler is not generating default constructors, so it is better to have our programmer provide one

7.5.4 Implicit Class Type Conversion

  • Conversion constructor: A constructor called by an argument defines a rule for converting from a constructor's parameter construction type to a class type.
vector<string> str_vec;
//A string is required for push, but a string is passed in. The string's transformation constructor is used here
str_vec.push_back("Little Black~");
  • Conversion constructor allows only one step to construct a transformation
  • Constructors that require multiple parameters cannot perform implicit conversions
//Sales_data has a constructor with a string parameter
//The combine for Sales_data is the method:
//Sales_data & Sales_data::combine(const Sales_data& );
Sales_data item;
item.combine("Infinite~")//Error, only one step construction allowed
item.combine(string("Infinite~"))//Correct, only string to Sales_data implicitly constructs the transformation
  • Use explicit to suppress implicit conversions of constructor definitions
class Sales_data{
explicit Sales_data(const string&):bookNo(s){};
...//Other Statements
}
item.combine(string("Infinite~"));//Error, explicit prevented implicit conversion
  • The explicit function can only be used for direct initialization
//Constructor for Sales_data: explicit Sales_data (const string &): bookNo(s) {};
string bookNo = "001";
Sales_data item1(bookNo);//Correct, direct initialization
Sales_data item2 = bookNo;//Error, copy initialization

  • Although the compiler will not use explicit's constructor for implicit conversion, explicit forced conversion can be used
string bookNo ="001";
item.combine(bookNo);//Error, explicit prevented implicit conversion
item.combine(static_cast<Sales_data>(bookNo));//Correct, cast

7.5.5 Aggregation Classes

  • The definition of an aggregate class. A class that meets the following conditions is called an aggregate class
    1. All members are public
    2. No constructor defined
    3. No in-class initialization
    4. No base class, no virtual function
  • Aggregate classes can be initialized using a list of member initializers enclosed in {}
class Data{
public:
	int ival;
	string s;
	}
//The order must be the same
Data val1={0,"Confucius"};		

7.5.6 Literal Value Constant

  • The parameters and return values of the constexpr function must be literal
  • Literal value types include: arithmetic type, pointer, reference, data member are aggregated classes of literal value type and classes satisfying the following conditions.
    1. Data members are literal value types
    2. Class must contain a constexpr constructor
    3. Use default defined destructors
    4. If a data member contains an in-class initial value, the initial value must be a constant expression; if the data member belongs to a class type, the initial value must use its own constexpr constructor
    • Constructors cannot be const, but can be constexpr.
    • Literal constant class with at least one constexpr constructor
    • constexpr constructor
      • Is a constructor, no return statement
      • Is the constexpr function, the only executable statement is the return statement
      • So the constexpr constructor body is empty and can only be initialized by initializing the list values
class Data{
public:
	constexpr Data(int para_i,string para_s):ival(para_i),s(para_s){}
	int ival;
	string s;
	}
constexpr Data data(1,"Eat roast meat");

Static members of class 7.6

  • Use static to declare a static member in a class that is associated with the class, not with the class object
  • Static member functions are also not bound to any class object, and static member functions do not contain this pointer. (Includes explicit calls to this and implicit calls to non-static members)
  • When defining a static function externally, you cannot repeat the static; the static keyword appears with a declaration statement inside the class
  • Static members cannot be initialized inside a class, and each static member must be defined and initialized outside the class, so once defined, it always exists throughout the life cycle of the program
  • The best way to ensure that an object is defined only once is to place the definition of static data members in the same file as the definition of other non-inline functions
  • Intra-class initialization of static members
    • In general, static members of a class should not be initialized inside the class
    • Static members must be literal constant type (constexpr)
    • Even if a constant static data member is initialized inside a class, it should usually be defined outside the class (so that the life cycle ends at the end of the program).
  • Special usage scenarios for static members
    • The type of a static data member can be the type to which it belongs
class Bar{
public:
//...
provite:
	static Bar mem1;//Correctly, a static member can be an incomplete type
	Bar* mem2;//Correct, pointer members can be incomplete types
	Bar mem3;//error
}
- Static members can be used as default arguments
class Screen{
	public:
	Screen& clear(char = bkground)
	private:
	static const char bkground;
}

Tags: C++

Posted on Sun, 26 Sep 2021 14:55:02 -0400 by Dj_Lord