Introduction to C++: Classes and Objects (Part 2)

Table of contents

Foreword:

One: 6 default member functions of the class

Two: Constructor (first member)

(1) concept

(2) Features

(3) Analysis of characteristics

⭐Feature 4

⭐Feature 5

⭐Feature 6

⭐Feature 7

Three: initialization list

(1) Import

(2) Concept

(3) Note

Four: Destructor (second member)       

(1) concept       

(2) Features

(3) example

Five: copy constructor (third member)

(1) concept

(2) Features

(3) Analysis of characteristics

⭐Feature 2

⭐Feature 3

⭐Feature 5

 Six: Operator overloading

(1) concept

(2) Example

(3) Note

Seven: Assignment operator overloading (the fourth member)

(1) Assignment operator overload format

(2) Features

⭐Feature 1

⭐Feature 2

Eight: const modified members

Nine: Address and const address operator overloading (fifth and sixth members)

Ten: Friends

(1) Friend function

(2) Friend class

Eleven: Implement a relatively complete date class

(1) Let me talk about a few more important points first

⭐ Get the number of days in the month

⭐ Implement the principle of comparison operator overloading

⭐ Input and output overloading

⭐The difference between front ++(--) and post ++(--)

(2) Date class implementation (sub-file)

⭐Date.h (function declaration)

⭐Date.cpp (function implementation)

⭐test.cpp (test)


Foreword:

The content of the C++ column is coherent, and the important content that has not been expanded is in the previous issue of the column.

For the convenience of grammar learning, directly expand the std namespace.

Link to personal homepage: Pai Xiaoxing 233's Blog_CSDN Blog - Elementary data structure, C language, C++ elementary field blogger


One: 6 default member functions of the class

If there are no members in a class, we simply call it an empty class.

But the empty class doesn't really have nothing, even if we don't write anything, the compiler will generate six member functions by default.

class Date{};

 You only need to have a basic understanding of this part, and I will explain it one by one later. (These member functions are special, don't look at them as normal functions)


Two: Constructor (first member)

(1) concept

Let's look at the following Date class:

class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	d1.Init(2022, 7, 5);
	d1.Print();
	Date d2;
	d2.Init(2022, 7, 6);
	d2.Print();
	return 0;
}

When writing code in C language, we often write initialization functions . Every time we create a structure variable, we need to manually call the initialization function. Can we automatically call the initialization function every time we create a variable?

The answer is yes, the constructor can solve this problem .

The constructor is a special member function that has no return value (that is, nothing in the true sense, not even empty), the function name is the same as the class name, the compiler will automatically call this function when creating an object, and only will be called once .

(2) Features

The constructor is a special member function. It should be noted that although the name of the constructor is called construction, the main task of the constructor
is not to open space to create objects, but to initialize objects
. (Constructor is responsible for initialization! Initialization!! Initialization!!! )

Its characteristics are as follows:

  1. The function name is the same as the class name
  2. No return value (that is, there is really nothing, not even empty)
  3. When the object is instantiated, the compiler calls the constructor by itself
  4. Constructors can be overloaded
  5. If no constructor is explicitly defined in the class , the C++ compiler will automatically generate a default constructor with no parameters . Once
    the user explicitly defines the compiler, it will no longer generate it.
  6. Each member variable of a class has a corresponding constructor. The default constructor of a class actually calls the constructor of the corresponding type of member variable . (This is hard to understand, I will expand on it later)
  7. Both the parameterless constructor and the default constructor are called default constructors, and there can only be one default constructor .
    Note: No-argument constructors, all default constructors, and constructors generated by the compiler without writing, can all be considered
    as default constructors.

(3) Analysis of characteristics

⭐Feature 4

Constructors can be overloaded.

class Date
{
public:
	// 1.无参构造函数
	Date()
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}

	// 2.带参构造函数
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	// 调用无参构造函数
	Date d1;
	// 调用带参的构造函数
	Date d2(2015, 1, 1); 
	// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
}


Although overloading can be formed, we generally do not use the above writing method, and we can use the full default parameters we learned earlier to make the code more concise .

The following writing method is actually applied more, and it is also very easy to use! ! !

class Date
{
public:
	//利用全缺省参数
	//实际中这个写法非常好用
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	// 都是调用一个函数,但不传参数,缺省值生效
	Date d1;
	// 都是调用一个函数,传了参数,缺省值无效
	Date d2(2015, 1, 1); 
}

⭐Feature 5

  If no constructor is explicitly defined in the class, the C++ compiler will automatically generate a default constructor with no parameters. Once
Compilers explicitly defined by the user will no longer be generated. 

class Date
{
public:
	/*
	// 如果用户显式定义了构造函数,编译器将不再生成
    //这也是为什么建议写成全缺省
    //写成全缺省这里放开也不会报错
	Date(int year, int month, int day)
	{
	    _year = year;
	    _month = month;
	    _day = day;
	}
	*/
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	// 将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数
	// 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成
	// 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
	Date d1;
	return 0;
}

We release the comment section

⭐Feature 6

Each member variable of a class has a corresponding constructor. The default constructor of a class is actually to call the constructor of the corresponding type of each member variable.

You just need to keep these two things in mind:

  • The default generated constructor does not handle built-in types .
  • The default generated constructor will call the constructor of the member variable. If the member variable does not explicitly define the constructor, call the default generated constructor, otherwise call the explicitly defined constructor .

Let's look at the following code:

class Time
{
public:
	Time()
	{
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;
		_second = 0;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
private:
	// 基本类型(内置类型)
	int _year;
	int _month;
	int _day;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d;
	return 0;
}

explain this phenomenon

  1. In fact, when the object d is instantiated, the default constructor of the class Date is called.
  2. This default generated constructor calls the default generated constructor of the int type three times (_year, _month, _day are all built-in types and will not be processed) and the constructor of the Time type (the class is a custom type)
  3. Among them, the constructor of Time type is explicitly defined, so call this function to complete the initialization

In addition: In C++11 , a patch has been applied for the defect of non-initialization of built-in type members, namely: built-in type member variables are in
Default values ​​can be given when declaring in a class .

​
​
class Time
{
public:
	Time()
	{
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;
		_second = 0;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
private:
	// 基本类型(内置类型)
    //这个操作可以理解为给了int类型默认生成的构造函数一个缺省值
    //原本这三个变量默认生成的构造函数时一致的
    //给了默认值后编译器会为这三个变量生成不同的构造函数
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d;
	return 0;
}

⭐Feature 7

Both the parameterless constructor and the default constructor are called default constructors, and there can only be one default constructor.
Note: No-argument constructors, all default constructors, and constructors generated by the compiler without writing, can all be considered
as default constructors. ( What they have in common is that they can be called without parameters )

 Let's look at the following code:

class Date
{
public:
	Date()
	{
		_year = 1900;
		_month = 1;
		_day = 1;
	}
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	//调用的时候存在歧义,调用哪一个好像都可以,所以编译器会报错
	Date d1;
}

analyze:

  • There is ambiguity when the above code is called. It seems that it can be called, so the compiler will report an error.
  • Although there can only be one default constructor, you must let them exist at the same time, as long as you manually initialize each variable (so another function is meaningless. In fact, the default also loses its effect)

Three: initialization list

(1) Import

Let's look at the following code:

class Time
{
public:
	Time()
	{
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;
		_second = 0;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
    //我自己写了一个构造函数,不会自动生成构造函数了
    //_t还会被初始化吗?
	Date(int year = 1,int month = 1,int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	// 基本类型(内置类型)
	int _year;
	int _month;
	int _day;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d;
	return 0;
}

We know that the constructor generated by default will call the constructor of the member variable, so the current constructor is explicitly defined, will it still call the constructor of the member variable?

The answer is yes, all of this is done by the initialization list.

(2) Concept

An initializer list is a syntactic form in C++ for initializing member variables when an object is created. It can be achieved by listing the initialization values ​​​​of the member variables after the colon before the function body of the object constructor.

The role of the initialization list is to directly assign values ​​​​to member variables in the object's constructor, thereby avoiding the operation of first default construction and then assignment, and reducing the construction and initialization time of the object .

NOTE: Initialization lists only exist for explicitly defined constructors.

Simply put, what the initialization list does is to call the constructor of the member variable before entering the function body of the constructor, and this process can be controlled manually.

Looking at the code, this is not difficult to understand:

class Time
{
public:
	Time()
	{
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;
		_second = 0;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
	Date(int year = 1,int month = 1,int day = 1)
		//初始化列表在这个位置发挥作用
		//大家也可以把这个地方理解为对象的成员开辟了空间
		//展示一下如何人为控制
		:_year(year) 
		,_month(10)  //括号后面也可以自己给值
		,_day(day)  //前面所讲的声明时给默认值其实就是这里给值
	{}
private:
	// 基本类型(内置类型)
	int _year;
	int _month;
	int _day;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d;
	return 0;
}

After learning the initialization list, it is recommended to use the initialization list for initialization uniformly , because it can initialize its member variables when the object is constructed. Doing so can improve the execution efficiency of the program, avoid some initialization problems, and be more standardized and clear.

But for some complex situations, it still needs to be done in the function body. (such as array initialization and malloc application space check)

(3) Note

The order in which member variables are initialized is determined by the order of declaration and has nothing to do with the order of the initialization list.

class Date
{
public:
	Date(int year = 1,int month = 1,int day = 1)
		:_month(12) 
		,_year(_month)
		,_day(day) 
	{}
	void print()
	{
		cout << "年:>" << _year << endl; 
		cout << "月:>" << _month << endl;
		cout << "日:>" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d;
	d.print();
	return 0;
}

Cause:

  • The order of declaration determines the order of initialization, _year is initialized first
  • But the initialization list uses _month to initialize _year. At this time, _month has not been initialized and is an unknown number, which leads to the above result.

Four: Destructor (second member)       

(1) concept       

Destructor: Contrary to the function of the constructor, the destructor does not complete the destruction of the object itself, and the local object destruction is done by the compiler. When the object is destroyed, it will automatically call the destructor to complete the cleanup of resources in the object .    

Illustration:

(2) Features

A destructor is a special member function whose characteristics are as follows:

  1.  The destructor name is prefixed with the character ~ before the class name .
  2. No parameters and no return type.
  3. A class can have only one destructor. If not explicitly defined, the system will automatically generate a default destructor. (Note: Because the destructor stipulates that there can be no parameters, there cannot be overloading)    
  4. When the life cycle of the object ends, the C++ compilation system automatically calls the destructor.     
  5. If there is no destructor explicitly defined in the class , the C++ compiler will automatically generate a default destructor , once
    the user explicitly defines the compiler will no longer generate it.
  6. Each member variable of a class has a corresponding destructor. The default generated destructor of a class is actually to call the destructor of the corresponding type of member variable . (The destructor corresponding to the built-in type will not be processed, and the characteristics of this part are consistent with the previous constructor )
  7. If there is no application resource in the class, the destructor can be omitted, and the default destructor generated by the compiler can be used directly.

(3) example

class SeqList
{
public:
	//析构函数
	~SeqList()
	{
		//清理向堆申请的空间
		free(_a);
		_a = nullptr;
	}
	//构造函数
	SeqList(int capacity)
		:_capacity(capacity)
		,_a(nullptr)
		,_size(0)
	{
		_a = (int*)malloc(sizeof(int) * _capacity);
		//检查是否申请成功
		if (_a == nullptr)
		{
			cout << "malloc error" << endl;
			assert(false);
		}
	}
	//一系列成员函数
private:
	int* _a;
	int _capacity;
	int _size;
};

int main()
{
	SeqList s(10);
	return 0;
}

Five: copy constructor (third member)

(1) concept

There is only a single formal parameter, which is a reference to the object of this class type (commonly modified by const), which is automatically called by the compiler when creating a new object with an existing class type object.

example:

class A
{
public:
	A(int a = 0)
		:_a(a)
	{}
	//和构造函数构成函数重载
	A(const A& a)
	{
		_a = a._a;
	}
private:
	int _a;
};

int main()
{
	A a1(10);
	//调用拷贝构造
	A a2(a1);
}

(2) Features

The copy constructor is also a special member function with the following characteristics:

  1. The copy constructor is an overloaded form of the constructor (the default constructor will not be generated if the copy constructor is written) .
  2. The parameter of the copy constructor is only one and must be a reference to a class type object, and the compiler will directly report an error if the value-passing method is used,
    because it will cause infinite recursive calls. (I will explain why it causes infinite recursion later)
  3. If not explicitly defined, the compiler will generate a default copy constructor. The default copy constructor object
    is copied in byte order according to memory storage. This kind of copy is called shallow copy, or value copy
    (that is, copy member variables byte by byte).
  4. Each member variable of a class has a corresponding copy constructor. The default copy constructor generated by a class actually calls the copy constructor of the corresponding type of member variable . ( The copy constructor corresponding to the built-in type will handle it, and the characteristics of this part are slightly different from the previous constructor )
  5. If there is no resource application involved in the class, the copy constructor can be written or not; once the resource application is involved 
    , the copy constructor must be written, otherwise it is a shallow copy. (I will give an example later)
  6. There are two ways of writing the call of copy construction, the first is A a1(a2) , the second is A a3 = a2 , these two ways of writing are completely equivalent , you must keep this in mind.

(3) Analysis of characteristics

⭐Feature 2

The parameter of the copy constructor is only one and must be a reference to a class type object, and the compiler will directly report an error if the value-passing method is used,
because it will cause infinite recursive calls.

The reason is very simple. When passing function parameters and function return values, a temporary variable will be created. If the passed parameter is a class object, the creation of this temporary variable is actually to call the copy constructor .

Then the following situation is formed:

Expanding this feature is mainly to know that the object needs to create a temporary variable when passing parameters and returning values, and this process calls copy construction .

⭐Feature 3

If not explicitly defined, the compiler will generate a default copy constructor. The default copy constructor object
is copied in byte order according to memory storage. This kind of copy is called shallow copy, or value copy (that is, copy member variables byte by byte).

code:

class Time
{
public:
	Time()
		:_hour(1)
	{}
private:
	int _hour;
};
class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d1;
	// 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数
	// 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数
	Date d2(d1);
	return 0;
}

⭐Feature 5

If there is no resource application involved in the class, the copy constructor can be written or not; once the resource application is involved 
, the copy constructor must be written, otherwise it is a shallow copy.

Code (this code will crash when run directly):

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10)
		:_size(0)
		,_capacity(capacity)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	void Push(const DataType& data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	size_t _size;
	size_t _capacity;
};
int main()
{
	Stack s1;
	//不仅仅是不符合要求,运行的时候会导致崩溃
	//原因是对堆上申请的空间多次释放
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	Stack s2(s1);
	return 0;
}


 Six: Operator overloading

The remaining three member functions all involve operator overloading, and operator overloading is of great significance, so I will talk about it separately here.

(1) concept

C++ introduces operator overloading to enhance the readability of the code. Operator overloading is a function with a special function name, and also has its return value type, function name, and parameter list. The return value type and parameter list are similar to ordinary functions.
(Classes can also use operators. By converting operator expressions into related functions, it is concise and readable)

The function name is: the keyword operator followed by the operator symbol that needs to be overloaded.

Function prototype: return value type operator operator (parameter list)

(2) Example

code:

//以 == 重载为例
class A
{
public:
	A(int a)
		:_a(a)
	{}
	bool operator==(const A& x)
	{
		return (x._a == _a);
	}
private:
	int _a;
};

int main()
{
	A a1(5);
	A a2(10);
	A a3(10);
	cout << (a1 == a2) << endl;
	cout << (a1 == a2) << endl;
	//(a1 == a2)实际上就是a1.operator==(&a1,a2)
	//这里加()是因为cout本质也是函数重载,不加括号就会先和a1结合,cout和a1结合去调用函数
	//这个调用的返回值是另一个类的对象,这个对象再和>a2结合,我们没有实现这个函数重载,会报错
	//后面专门讲一下输入输出实现
}

(3) Note

  • New operators cannot be created by concatenating other symbols : e.g. operator@
  • An overloaded operator must have a class type parameter
  • When overloaded as a class member function, its formal parameters appear to be 1 less than the number of operands, because the first parameter of the member function is the hidden
    this
  • * (dereference), :: (scope qualifier), sizeof , ?: (ternary operator), . (class member access operator), Note that the above 5 operators cannot be overloaded. This often appears in written multiple choice questions.
  • The associative order of operator overloading is related to operator precedence and associativity.
  • In most cases, operator overloading is used as a member function of a class. If there is no other way, it can be made into a normal function, which is essentially replaced by a function call. (For example, to realize the input and output of the class, we will talk about it separately later)

Seven: Assignment operator overloading (the fourth member)

(1) Assignment operator overload format

  • Parameter type: const T&, passing by reference can improve the efficiency of parameter passing
  • Return value type: T&, return reference can improve the efficiency of return, and the purpose of return value is to support continuous assignment
  • Check if you assign a value to yourself
  • Return *this: to compound the meaning of continuous assignment
code:
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
		:_year(year)
		,_month(month)
		,_day(day)
	{}

	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	Date& operator=(const Date& d)
	{
		//this指针和传递过来的对象地址一样说明是自己赋值自己,不进行处理
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}

		return *this;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
    Date d1;
    Date d2;
    Date d3(2022,11,11);
    d1 = d2 = d3;
    //连续赋值,相当于先d2 = d3(d2.operator=(&d2,d3)),返回值为d2的引用
	//然后d1 = d2(相当于d1.operator=(&d1,d2))
    return 0;
}

(2) Features

  1. When the user does not explicitly implement, the compiler will generate a default assignment operator overload (this is very special) , and copy byte by value in the form of value (this is the so-called shallow copy, which is not applicable to the situation where resources need to be cleaned up) , the display definition is not generated. Note: Built-in type member variables are directly assigned, while custom type member variables need to call the assignment operator overload of the corresponding class to complete the assignment.
  2. Based on the first point, another feature is introduced. The assignment operator can only be overloaded as a member function of a class and cannot be overloaded as a global function .

⭐Feature 1

For when you should implement assignment overloading yourself, take the stack we mentioned earlier as an example.

Summary: If resource management is not involved in the class, the assignment operator can be implemented or not; once resource management is involved, it must be
implemented.
 

⭐Feature 2

code:

//这一段代码无法通过编译
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	int _year;
	int _month;
	int _day;
};
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date& operator=(Date& left, const Date& right)
{
	if (&left != &right)
	{
		left._year = right._year;
		left._month = right._month;
		left._day = right._day;
	}
	return left;
}

 Reason: If the assignment operator is not explicitly defined, the compiler will generate a default one.
At this time, if the user implements a global assignment operator overload outside the class , it will conflict with the default assignment operator overload generated by the compiler in the class , so the assignment
operator overload can only be a member function of the class
.


Eight: const modified members

Let's look at the following code first:

class A
{
public:
	A(int a = 0)
		:_a(a)
	{}
	void print()
	{
		cout << _a << endl;
	}
private:
	int _a;
};

int main()
{
	const A a1;
	a1.print();
	return 0;
}

Compilation result:

We know that the variable modified with const in C++ is already a constant, but even if it is a constant, we hope it can be printed, compared, and used as a copy master, but the above code cannot even be compiled.

Reason: This involves the problem of enlarging and shrinking permissions, because the default type of the hidden this pointer is -> class name * const this, * is not decorated with const in front, indicating that the object pointed to by this pointer is modifiable. However, the above object is modified with const, and the passed pointer type is -> const class name * const this, which belongs to the amplification of authority and is not allowed.

Solution: To solve this problem, we need to add const in front of the hidden this type, but this is hidden, how should we tell the compiler our needs?

Summary: For some member functions that do not need to modify the object, we try to add const after the function to make the code more widely used. (Most of the previous examples should actually be modified with const) 


Nine: Address and const address operator overloading (fifth and sixth members)

These two default member functions generally do not need to be redefined, and the compiler will generate them by default.
class Date
{
public:
	Date* operator&()
	{
		return this;
	}
	const Date* operator&()const
	{
		return this;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};

Ten: Friends

By default, the member variables of an object can only be accessed inside the class, but what if we want to access the member variables of the class elsewhere? This requires friends .

Note: Friends can be more flexible, but they break encapsulation and should be used sparingly.

(1) Friend function

class A
{
public:
	//关键字friend,在类中进行函数声明即可。
	friend void print(A& a);
	A(int a = 0)
		:_a(a)
	{}
private:
	int _a;
};

//想让非成员函数访问类成员变量
void print(A& a)
{
	a._a++;
	cout << a._a << endl;
}
int main()
{
	A a1(20);
	print(a1);
}

(2) Friend class

class A
{
public:
	//利用关键字friend,A是B的友元,B可以访问A,A不可以访问B
	//A把B当朋友,B不一定把A当朋友
	friend class B;
	A(int a = 0)
		:_a(a)
	{}
private:
	int _a;
};

class B
{
public:
	
	B(int b = 0)
		:_b(b)
	{}
	void print()
	{
		//在B类中访问A类的对象
		cout << "A:>" << a._a << "B:>"<< _b << endl;
	}
private:
	int _b;
	A a;
};

Eleven: Implement a relatively complete date class

(1) Let me talk about a few more important points first

Get the number of days in the month

// 获取某年某月的天数
//这个很容易理解,后面经常会用到这个函数
int	Date::GetMonthDay(int year, int month) const
{
	//会多次调用这个函数,所以直接设计成静态的数组
	static int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
	int day = days[month];
	if (month == 2
		&& ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
	{
		day += 1;
	}
	return day;
}

Implement the principle of comparison operator overloading

Now I have written a > comparison overload and == comparison overload. To implement the < overload, someone may choose to directly copy the > comparison code and change the comparison logic. This approach will make the code very redundant. Remain.


For a comparison relationship like this, we only need to write the > (or <) comparison and == comparison, and other comparisons directly reuse these two overloads, and then logically negate them.

For example: the judgment of < can be written as !((A>B) || (A == B)), the conditional judgment in the brackets >=, isn’t the logical inversion of >= just <? Other situations can be deduced by analogy.

⭐ Input and output overloading

cout is the instantiated global object of the ostream class, cout << a(int) actually calls the member function of this object, this member function has a parameter of type int, cout << d(double), this is also a member function, but one parameter is double type, which can be overloaded with the function corresponding to the previous int, but ostream only overloads the built-in type.

As for the realization of continuous printing, cout << a(int) << d(double), in fact, first combine with a to call the member function corresponding to int, then return the reference of cout, and then combine with d to call the member function corresponding to double .


cin is an instantiated global object of the istream class. It can automatically identify the type, and the principle of continuous input is similar to cout. The difference is that it needs to change the value of the variable, so it is passed by reference instead of value.


So what if we directly overload the input and output as member functions?

code:

class Date
{
public:
	const Date(int year = 1900, int month = 1, int day = 1)
		:_year(year)
		,_month(month)
		,_day(day)
	{}
	ostream& operator<<(ostream& out)const
	{
		//<<实现内置类型重载的时候类型是没有用const修饰的
		//这里不能加const修饰,不然就找不到这个成员函数了
		out << _year << "." << _month << "." << _day << endl;
		return out;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	const Date d1;
	const Date d2(2022,11,11);

	cout << d1 << d2 << endl;
	//这个先看ostream这个类里面有没有实现这个重载
	//没有看全局有没有第一个参数为ostraem&,第二个参数为Date&的全局函数
	//都没有找到说明没有这个函数,编译报错
}


To solve the above problem, we can design this overload as a global function. This global function needs to access the member variables of the object. We need to declare that this global function is a friend function of the class.

class Date
{
public:
	const Date(int year = 1900, int month = 1, int day = 1)
		:_year(year)
		,_month(month)
		,_day(day)
	{}
	//声明,表明这个函数是友元函数
	friend ostream& operator<<(ostream& out,const Date& d);
private:
	int _year;
	int _month;
	int _day;
};

ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "." << d._month << "." << d._day << endl;
	return out;
}

int main()
{
	const Date d1;
	const Date d2(2022,11,11);

	cout << d1 << d2 << endl;
}

Input and print are the same.

⭐The difference between front ++(--) and post ++(--)

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	// 前置++:返回+1之后的结果
	// 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
	Date& operator++()
	{
		_day += 1;
		return *this;
	}
	// 后置++:
	// 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
	// C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
	// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this + 1
	//而temp是临时对象,因此只能以值的方式返回,不能返回引用(之前讲过)
	Date operator++(int)
	{
		Date temp(*this);
		_day += 1;
		return temp;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d;
	Date d1(2022, 1, 13);
	d = d1++; // d: 2022,1,13   d1:2022,1,14
	d = ++d1; // d: 2022,1,15   d1:2022,1,15
	return 0;
}

(2) Date class implementation (sub-file)

You can try to write it yourself against the statement first, most of the logic is relatively simple.

Date.h (function declaration)

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <assert.h>
using namespace std;

class Date
{
public:
	//两个友元函数
	friend ostream& operator<<(ostream& out,const Date& d);
	friend istream& operator>>(istream& in,Date& d);
	// 获取某年某月的天数
	int GetMonthDay(int year, int month)const;

	// 全缺省的构造函数
	Date(int year = 1900, int month = 1, int day = 1);

	// 拷贝构造函数
	// d2(d1)
	Date(const Date& d)
		:_year(d._year)
		,_month(d._month)
		,_day(d._day)
	{
	}

	// 赋值运算符重载
	// d2 = d3 -> d2.operator=(&d2, d3)
	//不传引用返回会多构造一个
	Date& operator=(const Date& d);
	// 析构函数(日期类可以不写,这里是为了方便观察)
	~Date()
	{
		//cout << "析构" << endl;
	}
	// 日期+=天数
	Date& operator+=(int day);

	// 日期+天数
	Date operator+(int day) const;

	// 日期-天数
	Date operator-(int day) const;

	// 日期-=天数
	Date& operator-=(int day);

	// 前置++
	Date& operator++();

	// 后置++,这个int参数只是为了区分,传什么值又编译器自行处理
	Date operator++(int);

	// 后置--
	Date operator--(int);

	// 前置--
	Date& operator--();

	// >运算符重载
	bool operator>(const Date& d)const;

	// ==运算符重载
	bool operator==(const Date& d) const;

	// >=运算符重载
	bool operator >= (const Date& d) const;

	// <运算符重载
	bool operator < (const Date& d) const;

	// <=运算符重载
	bool operator <= (const Date& d) const;

	// !=运算符重载
	bool operator != (const Date& d) const;

	// 日期-日期 返回天数
	int operator-(const Date& d) const;

	//进行日期检查,合法返回1,否则返回0
	int CheckDate() const;

	//打印
	void print()
	{
		cout << _year << "|" << _month << "|" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

//某一天是星期几
int GetWeek(Date& d);

⭐Date.cpp (function implementation)

​
#define _CRT_SECURE_NO_WARNINGS 1
#include "Date.h"

//打印重载
ostream& operator<<(ostream& out,const Date& d)
{
	cout << d._year << "." << d._month << "." << d._day << endl;
	return out;
}

//输入重载
istream& operator>>(istream& in,Date& d)
{
	cin >> d._year >> d._month >> d._day;
	//输入完检查一下是否合法
	assert(d.CheckDate());
	return in;
}

// 全缺省的构造函数
Date::Date(int year, int month, int day)
	:_year(year)
	, _month(month)
	, _day(day)
{
	//检查一下是否合法
	assert(CheckDate());
}

// 获取某年某月的天数
int	Date::GetMonthDay(int year, int month) const
{
	//会多次调用这个函数,所以直接设计成静态的数组
	static int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
	int day = days[month];
	if (month == 2
		&& ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
	{
		day += 1;
	}
	return day;
}

// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
//不传引用返回会多构造一个
Date& Date::operator=(const Date& d)
{
	if (this != &d)
	{
		_day = d._day;
		_month = d._month;
		_year = d._year;
	}
	return *this;
}

// 日期+=天数
Date& Date::operator+=(int day)
{
	if (day < 0)
	{
		return *this -= -day;
	}

	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		++_month;
		if (_month == 13)
		{
			++_year;
			_month = 1;
		}
	}

	return *this;
}

// 日期+天数
Date Date::operator+(int day) const
{
	Date tmp(*this);
	tmp += day;
	return tmp;
}

// 日期-=天数
Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		return *this += -day;
	}

	_day -= day;
	//减到0要变成下个月的最后一天
	while (_day <= 0)
	{
		_month--;
		//如果减到0,要变成下一年的12月
		if (_month <= 0)
		{
			_month = 12;
			_year--;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}

// 日期-天数
Date Date::operator-(int day) const
{
	Date tmp(*this);
	tmp -= day;
	return tmp;
}

// 前置++
Date& Date::operator++()
{
	*this += 1;
	return *this;
}

// 后置++,这个int参数只是为了构成函数重载,传什么值编译器自行处理
Date Date::operator++(int)
{
	Date tmp(*this);
	*this += 1;
	return tmp;
}

// 后置--
Date Date::operator--(int)
{
	Date tmp(*this);
	*this -= 1;
	return tmp;
}

// 前置--
Date& Date::operator--()
{
	*this -= 1;
	return *this;
}

// >运算符重载
bool Date::operator>(const Date& d) const
{
	if (_year > d._year)
	{
		return true;
	}
	else if ((_year == d._year) && (_month > d._month))
	{
		return true;
	}
	else if ((_year == d._year) && (_month == d._month) && (_day > d._day))
	{
		return true;
	}
	//前面三种情况都是大于,最后一定是小于
	else
	{
		return false;
	}
}

// ==运算符重载
bool Date::operator==(const Date& d) const
{
	return (_year == d._year)
		&& (_month == d._month)
		&& (_day == d._day);
}

// >=运算符重载
bool Date::operator >= (const Date& d) const
{
	return (*this > d) || (*this == d);
}

// <运算符重载
bool Date::operator < (const Date& d) const
{
	return !((*this > d) || (*this == d));
}

// <=运算符重载
bool Date::operator <= (const Date& d) const
{
	return !(*this > d);
}

// !=运算符重载
bool Date::operator != (const Date& d) const
{
	return !(*this == d);
}

// 日期-日期 返回天数
int Date::operator-(const Date& d) const
{
	Date max = *this;
	Date min = d;
	int n = 0;
	//一开始默认前面大于后面
	int flag = 1;
	if (*this < d)
	{
		max = d;
		min = *this;
		//如果后面大于前面,最后结果为负数
		flag = -1;
	}

	while (max > min)
	{
		min++;
		n++;
	}
	return n * flag;
}

int GetWeek(Date& d)
{
	Date tmp(1, 1, 1);
	int week = 0; //0 - 周一
	int n = d - tmp;
	week += n;
	return week % 7;
}

//合法返回1,否则返回0
int Date::CheckDate() const
{
	return _month > 0 && _month < 13 && (_day > 0 && _day <= GetMonthDay(_year, _month));
}

​

⭐test.cpp (test)

#include "Date.h"

void text1()
{
	//  =
	/*Date d1;
	Date d2(3000, 11, 11);
	Date d3;
	d3 = d1 = d2;
	d1.print();
	d2.print();
	d3.print();*/


	//  +=
	/*Date d1(2022,12,1);
	d1 += 50;
	d1.print();
	d1 += 500;
	d1.print();
	d1 += 5000;
	d1.print();*/

	// + 
	//Date d1(2022, 11, 1);
	//Date d2 = d1 + 50;
	//d1.print();
	//d2.print();


	// -
	/*Date d1(2022, 12, 20);
	Date d2 = d1 - 20;
	d1.print();
	d2.print();*/

	// -=
	//Date d1(2022, 12, 20);
	//d1 -= d1 -= 20;

	// 前置++
	/*Date d1(2022, 11, 30);
	Date d2 = ++d1;*/

	// 后置++
	/*Date d1(2022, 11, 1);
	Date d2 = d1++;*/

	//前置--
	/*Date d1(2022, 11, 1);
	Date d2 = --d1;*/

	// 后置--
	/*Date d1(2022, 11, 1);
	Date d2 = d1--;*/


}
void text2()
{
	Date d1(2022, 12, 11);
	Date d2(2023, 12, 12);
	//cin >> d1 >> d2;
	cout << d1 << d2 << endl;
	// >
	/*Date d1(2022, 12, 20);
	Date d2(2023, 12, 20);
	cout << (d2 > d1) << endl;*/
	
	// ==
	/*Date d1(2023, 11, 20);
	Date d2(2023, 12, 20);
	cout << (d2 == d1) << endl; */
	
	// >=
	/*Date d1(2022, 12, 20);
	Date d2(2023, 12, 20);
	cout << (d2 >= d1) << endl;*/ 


	// <
	/*Date d1(2022, 12, 20);
	Date d2(2022, 12, 20);
	cout << (d2 < d1) << endl; */


	// <=
	/*Date d1(2022, 12, 20);
	Date d2(2023, 12, 20);
	cout << (d2 <= d1) << endl; */


	// !=
	/*Date d1(2022, 12, 20);
	Date d2(2022, 12, 21);
	cout << (d2 != d1) << endl; */


	// -(日期-日期 == 天数)
	/*Date d1(2022, 12, 20);
	Date d2(2023, 12, 30);
	cout << (d2 - d1) << endl; */

}

void menu()
{
	cout << "*****************************" << endl;
	cout << "1.日期加/减天数   2.日期-日期" << endl;
	cout << "3.算星期几        -1.退出    " << endl;
	cout << "*****************************" << endl;

}

//用来计算某一天是星期几,日期间的加减
void text3()
{
	int input = 0;
	int day = 0;
	Date d1;
	Date d2 = d1;
	//用下标来一一对应,0下标代表星期一
	const char* WeekArr[] = { "周一","周二","周三","周四","周五","周六","周日" };
	do
	{
		menu();
		cout << "请输入:>";
		cin >> input;
		if (input == 1)
		{
			cout << "请输入一个日期(空格隔开):>";
			cin >> d1;
			cout << "请输入天数:>";
			cin >> day;
			cout << d1 + day << endl;
		}
		else if (input == 2)
		{
			cout << "请输入两个日期(空格隔开):>";
			cin >> d1;
			cin >> d2;
			cout << d1 - d2 << endl;
		}
		else if (input == 3)
		{
			cout << "请输入一个日期(空格隔开):>";
			cin >> d1;
			cout << WeekArr[GetWeek(d1)] << endl;
		}
		else if (input != -1)
		{
			cout << "非法输入" << endl;
		}
	} while (input != -1);
}


int main()
{
	//text1();
	//text2();
	text3();
	return 0;
}

Guess you like

Origin blog.csdn.net/2301_76269963/article/details/131189359