C + + classes and objects

Process oriented and object-oriented

Process oriented: focus on the process of problem solving, and gradually solve the problem through function call.
Object oriented: focus on the objects involved in the problem, split one thing into different objects, and solve the problem by the interaction between objects.

Class introduction

Comparison between C + + structure and C + + structure

Define classes in C + +. You can use struct or class
C + + is compatible with the usage of structure in C, and struct is upgraded to class in C + +

For example, to define a structure in C, you need to write not only the class name, but also struct. Classes in C + + can directly define variables with the class name.

struct Student
{
	char name[10];
	int age;
	int id;
};

int main()
{
	struct Student s1;//Compatible with C usage
	Student s2;//When upgrading to C + +, you can directly define classes with class names

	strcpy(s1.name, "Zhang San");
	s1.id = 1;
	s1.age = 18;

	strcpy(s2.name, "Li Si");
	s2.id = 2;
	s2.age = 19;

	return 0;
}

In addition, unlike structs, classes can define not only variables (called member variables), but also functions (called member methods / functions)
At the same time, the defined class variables are generally called objects instead of variables.

struct Student
{
	//Member variable
	char _name[10];
	int _age;
	int _id;

	//Member method / function
	void Init(const char* name, int age, int id)
	{
		strcpy(_name, name);//Copy the second parameter string into the first parameter
		_age = age;
		_id = id;
	}

	void Print()
	{
		cout << _name << endl;
		cout << _age << endl;
		cout << _id << endl;
	}
};

int main()
{
	struct Student s1;
	Student s2;
	//In C + +, s1 and s2 here are no longer called variables, but used to be called objects

	s1.Init("Zhang San", 18, 1);
	s2.Init("Li Si", 19, 2);

	s1.Print();
	s2.Print();

	return 0;
}

Member variable preceded by_ It is a habit to distinguish it from the formal parameters of member functions.

Class definition method

Defined in the following format

class className
{
	//Class body: it is composed of member functions and member variables
};//As with structs, curly braces must be followed by semicolons

Where class is the keyword defining the class, ClassName is the name of the class, and {} is the body of the class. Note the semicolon after the end of class definition.
The elements in a class are called the members of the class: the data in the class is called the attributes or member variables of the class; Functions in a class are called methods or member functions of the class.

When defining a class, if the declaration and definition are all placed in the class body, the member function will be treated as an inline function (it is equivalent to adding inline in front of the function, but whether it is really expanded depends on the specific situation);
The separation of declaration and definition is handled as follows:
Take the stack class as an example
Put declaration in header file

The implementation of the member function of the class is placed in one cpp file
It should be noted that when implementing member functions, the class field should be specified between the return type and the function name

(the class defines a new scope, and all members of the class are in the scope of the class. To define members outside the class, you need to use the:: scope resolver to indicate which class domain the members belong to.)

Access qualifiers and encapsulation of classes

Encapsulation: organically combine the data with the methods of operating the data, hide the attributes and implementation details of the object, and only expose the interface to interact with the object.

Access qualifier
Encapsulation can be realized through access qualifiers, including public, private and protected
Usage:

  1. Members decorated with public can be accessed directly outside the class
  2. Protected and private modified members are not directly accessible outside the class (protected and private are similar here)
  3. The scope of access right is from the position where the access qualifier appears to the next access qualifier or the closing curly bracket (i.e. the end)
  4. The default access permission of class is private, and the default access permission of struct is public, but it is usually written in full instead of the default

Note: access qualifiers are only useful at compile time. When data is mapped to memory, there is no difference in access qualifiers

Difference between class and object (instantiation)

Class defines a class, which is equivalent to a template. The process of defining a variable (object) with this class is the instantiation of the class.

Calculation of class size

The member functions in the class are not stored in the class, but in the code segment. Therefore, calculating the size of the class does not need to consider the member functions, but only the member variables.
Class needs memory alignment as the structure in C, and the rules are the same. See C language structure

It should be noted that an empty class (that is, a class with nothing in the class body) also needs to occupy 1 byte of space

this pointer

The C + + compiler adds a hidden pointer parameter to each "non static member function" so that the pointer points to the current object (the object that calls the function when the function runs). All member variable operations in the function body are accessed through the pointer. However, all operations are transparent to the user, that is, the user does not need to pass them, and the compiler completes them automatically.

Member functions are not saved in the class. When the class is instantiated into an object, the object only saves member variables, and the member function is public. Different objects use the same member function. This is achieved through the this pointer.

In fact, the first parameter of each member function is a pointer to this class (the name of this pointer is this).

The member variables in the member function will be modified to this - > member variables by the compiler.

Rules on the use of this
1. When calling a member function, you cannot explicitly pass an argument to this
2. When defining a member function, the formal parameter this cannot be explicitly declared
3. However, the formal parameter this can be explicitly used inside the member function, but it is generally not written unless necessary

This pointer does not exist in the class (this pointer is not considered when calculating the size of the class). It usually exists in the stack, but it may also be in the register.

class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;//If the member variable and the formal parameter have the same name year = year, then both years are formal parameters because local variables are accessed first
		_month = month;//Of course, it can also be written as Date::year = year, specifying the class domain clearly, but it is not recommended
		_day = day;
	}

	//void Init(Date* const this, int year, int month, int day)
	//{
	//	this->_year = year;
	//	this->_month = month;
	//	this->_day = day;
	//}
	//Note that only member variables are added with this - >

	//The compiler will actually process this * as date and void
	//The address pointed to by this cannot be changed, and the content in the pointing address can be changed
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
		//cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	d1.Init(2022, 1, 15);//d1.Init(&d1, 2022, 1, 15);
	d1.Print();//This code will be processed by the compiler as D1 Print(&d1);
	//All non static member functions will be processed (this pointer is hidden)

	Date d2;
	d2.Init(2022, 1, 16);
	d2.Print();//d2.Print;
	//It should be noted that,
	//1. When calling a member function, you cannot explicitly pass an argument to this
	//2. When defining a member function, the formal parameter this cannot be explicitly declared
	//3. However, the formal parameter this can be explicitly used inside the member function, but it is generally not written unless necessary

	//this is usually stored in the stack, but VS is stored in register ecx

	return 0;
}

Further understand how member functions are called

class A
{
public:
	void Show()
	{
		cout << "Show()" << endl;
	}
private:
	int _a;
};

int main()
{
	A* p = nullptr;
	p->Show();//The program will run normally

	return 0;
}
class A
{
public:
	void PrintA()
	{
		cout << _a << endl;
	}
private:
	int _a;
};

int main()
{
	A* p = nullptr;
	p->PrintA();//Run crash here

	return 0;
}

This is because when calling a member function with a class pointer, the compiler finds the member function to call according to the pointer type, and does not dereference the pointer.

However, if a member variable appears in the class, it will be dereferenced with this pointer to the object to get the specific member variable. At this time, the null pointer will be dereferenced and the program will report an error.

Class

For any class, if we don't implement the following six member functions ourselves, the compiler will also generate one (if we write it ourselves, the compiler will no longer generate it)
namely

  1. Constructor
  2. Destructor
  3. copy construction
  4. Assignment overload
  5. Address overload
  6. const address overload

Constructor

The member of the constructor is used to initialize the function.

The constructor is a special member function with the same name as the class name. When creating a class type object, it is automatically called by the compiler to ensure that each data member has an appropriate initial value and is called only once in the life cycle of the object.

features

  1. The function name is the same as the class name.
  2. No return value.
  3. When an object is instantiated, the compiler automatically calls the corresponding constructor.
  4. Constructors can be overloaded.

For example, the following member function Date() is the constructor. The constructor does not need to be called by ourselves. When we instantiate an object, the constructor will be called automatically.

The constructor of the example here is a self written constructor that can receive parameters. The parameter passing method is to add parentheses after the object name when instantiating the object, and the parameters are written in parentheses.
But you can't write that

Date d();
class Date
{
public:
	Date()
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}

	Date(int year, int month, int day)//Here and the previous constructor constitute a function overload
	{
		_year = year;
		_month = month;
		_day = day;
	}

	//The above two functions can actually be written as a function, using the syntax of default parameters
	//Full default or semi default is recommended
	//Date(int year = 1, int month = 1, int day = 1)
	//{
	//	_year = year;
	//	_month = month;
	//	_day = day;
	//}

	//Note that grammatically, two functions, all default and no parameter, can constitute function overloading
	//There's no problem as long as you don't use parameterless calls, but if there are parameterless calls, there's ambiguity

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;//Without parameters, call the first constructor
	Date d2(2022, 1, 15);//A method that calls a constructor with parameters
	Date d3(2020);

	return 0;
}

If we do not implement the constructor ourselves, the compiler will automatically generate a parameterless constructor (if we implement the constructor ourselves, the compiler will not generate it), but for built-in types (int, char, etc.), it will not initialize, and for custom types (classes), it will call the constructor of this class.

Default constructor

The constructor that can be called without parameters is the default constructor (without default parameters, you can't call without parameters. See the knowledge point of default parameters for getting started with C + +).
Including parameterless constructors and all default constructors.
There can only be one default constructor (otherwise there will be ambiguity when calling)

Destructor

The destructor completes the resource cleanup, which is equivalent to the space created by free in C.
However, even if no variables in this class exist on the heap, a destructor is required.

When the object is destroyed, it will automatically call the destructor to clean up the resources of the class.

features

  1. The destructor name is preceded by the character ~.
  2. No parameter, no return value.
  3. A class has only one destructor. If not explicitly defined, the system will automatically generate a default destructor.
  4. At the end of the object life cycle, the C + + compilation system automatically calls the destructor.

The destructor generated by default does not handle built-in types, and its destructor will be called for custom types.

For classes without dynamic space, use the destructor generated by the compiler itself; If there is a dynamically opened space, you need to write your own destructor to free up space, otherwise it will cause memory leakage.

Take stack objects as an example

class Stack
{
public:
	Stack(int capacity = 4)//Just give the required parameters
	{
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == nullptr)
		{
			cout << "malloc fail\n" << endl;
			exit(-1);
		}
		_top = 0;
		_capacity = capacity;
	}

	void Push(int x)
	{}
	//If you don't write your own default constructor, the default generated destructor is similar to the default generated constructor
	// 1. Do not handle the built-in type member variables (there are reasons here. If you handle them all, you may be killed by mistake)
	// 2. For a custom type, the member variable will call its destructor
	//
	~Stack()
	{
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}

private:
	int* _a;
	size_t _top;
	size_t _capacity;
};

Note one small detail: the order of deconstruction is opposite to that of construction, because the stack is last in first out.

copy construction

Constructor: there is only a single parameter, which is a reference to the object of this class type (commonly used const decoration). It is automatically called by the compiler when creating a new object with an existing class type object

features:

  1. The copy constructor is an overloaded form of the constructor.
  2. There is only one parameter of the copy constructor, and the parameter must be passed by reference. Using the value passing method will cause infinite recursive calls. (if there is no transfer structure in C language, infinite recursion will occur. When C transfers values, it will be copied directly in byte order)
class Date
{
public:
	 Date(int year = 1900, int month = 1, int day = 1)
	 {
	 _year = year;
	 _month = month;
	 _day = day;
	 }
	
	 Date(const Date& d)//Class can directly access private
	 {
	 _year = d._year;
	 _month = d._month;
	 _day = d._day;
	 }
private:
	 int _year;
	 int _month;
	 int _day;
};

The second member function in the above code is the copy constructor. With the copy constructor, you can initialize variables in this way

int main()
{
	Date d1;
	Date d2(d1);//d2 is an object with the same content as d1

	return 0;
}

If we don't write the copy structure ourselves, the compiler will also generate it by itself. The copy structure generated by ourselves will copy the byte order (like strcpy, copy byte by byte, which is also called shallow copy)

For date classes, this kind of shallow copy is enough, but for stack classes, for example, shallow copy will come to the problem.
for example

class Stack
{
public:
	Stack(int capacity = 4)//Just give the required parameters
	{
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == nullptr)
		{
			cout << "malloc fail\n" << endl;
			exit(-1);
		}
		_top = 0;
		_capacity = capacity;
	}

	~Stack()
	{
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}

private:
	int* _a;
	size_t _top;
	size_t _capacity;
};

int main()
{
	Stack s1;
	Stack s2(s1);
	return 0;
}

The above program will report an error because of the error in s2 and s1_ a refers to the same space. The destructor is called once in s1 and s2 respectively. The same space is free twice, resulting in an error.
And there is a problem in using the same space for two different stacks.
Therefore, in this case, you need to implement the copy constructor yourself.

Details: the copy construct also belongs to the constructor. When you write the copy construct yourself, the compiler will no longer generate the constructor

Assignment operator overload

Before we talk about assignment operator overloading, we need to talk about what operator overloading is

Operator overloading

In order to enhance the readability of the code, C + + introduces operator overloading. Operator overloading is a function with special function name, and also has its return value type, function name and parameter list. Its return value type and parameter list are similar to ordinary functions.
The function name is: the keyword operator is followed by the operator symbol that needs to be overloaded.
Function prototype: return value type operator operator (parameter list)

matters needing attention

  • You cannot create a new operator by concatenating other symbols: for example, operator@
  • Overloaded operators must have an operand of class type or enumeration type
  • The meaning of operators used for built-in types cannot be changed. For example, the meaning of built-in integer +
  • When an overloaded function is a class member, like other member functions, the first parameter is also an implicit this pointer
  • .* ,:: ,sizeof ,?: ,. Note that the above five operators cannot be overloaded.

Because overloaded functions generally need to access member variables, and member variables are generally limited to private, in order not to destroy encapsulation, operators are generally overloaded as member functions.

// Global operator==
class Date
{
public:
	Date(int year = 2000, int month = 1, int day =1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
//If it is a global operator overload, you should be able to access member variables. Here, set the member variable to public
//private:
	int _year;
	int _month;
	int _day;
};

bool operator==(const Date& d1, const Date& d2)
{
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}

int main()
{
	Date d1;
	Date d2(2022, 1, 16);
	
	cout << (d1 == d2) << endl;//Because stream insertion < < priority is higher than = =, parentheses should be added

	return 0;
}

If you write it as a member function

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

	bool operator==(const Date& d2)
	{
		return _year == d2._year//Actually this - >_ year == d2._ year
			&& _month == d2._month
			&& _day == d2._day;
	}
};

int main()
{
	Date d1;
	Date d2(2022, 1, 16);
	
	cout << (d1 == d2) << endl;//Because stream insertion < < priority is higher than = =, parentheses should be added

	return 0;
}

Note that there is only one parameter after the member function is written above, because the first parameter is an implicit this pointer.

Return to assignment operator overload

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


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

	//	return *this;// Here is the value transfer return. You can write the copy structure yourself to see how many times the copy structure has been called
	//}

	Date& operator=(const Date& d)
	{
		if (this != &d)//Small optimization, be careful not to write * this= d,
			//Because it needs to be overloaded= And to call the function, it is not optimization
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}


		return *this;//Since * this has not been destroyed at the end of the function, it can be returned by reference
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2022, 1, 16);
	Date d2;
	d2 = d1;

	return 0;
}

Pay attention to some small details. Since there is no need to modify the parameters, the const reference is used to pass the parameters. Since the content referred to by * this is not destroyed at the end of the function, you can use the reference return (see Getting started with C + + ). if judgment means that the value will not be assigned when it is equal (the cost of judgment is greater than that of assignment).

Address fetching operator overload, const address fetching operator overload

You need to talk about const member functions before using these two default member functions

const member function

The class member function modified by const is called const member function, which is used to make the type of this pointer

const class* const

This allows you to use class objects of type const and member functions. (it is recommended to set the member function that does not need to modify the content of the class object as const member function)

Const member function definition method: add const after the bracket of member function

class Date
{
public:
	void Print() const
	{
		cout << _year << endl;
		cout << _month << endl;
		cout << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

Go back to address fetching operator overload and const address fetching operator overload

class Date
{
public:
	//Overload of address fetching operator (for non const)
	Date* operator&()
	{
		return this;
	}
	
	//The const address fetching operator is overloaded (you can fetch the address of const class objects, and the return address type is also a const type pointer)
	const Date* operator&() const
	{
		return this;
	}
private:
	int _year;
	int _month;
	int _day;
};

However, these two member functions generally do not need to be written by themselves, and the default generation is enough. (that is, the overload of the address taking operator without writing the class can also take the address of the class object)

Implement a date class

Constructor

(therefore, the domain of declaration is specified here)

Date::Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;

	if (!(year > 0
		&& (month > 0 && month < 13)
		&& (day > 0 && day <= GetMonthDay(year, month))))
	{
		cout << "Illegal date->";
		Print();//You can also call member functions in the class, which will be converted into
		//this->Print();
	}
}

Print date

void Date::Print() const
{
	cout << _year << "-" << _month << "-" << _day << endl;
}

Compare size of two dates >

bool Date::operator>(const Date& d) const//Achieve greater than (or less than) and equal to, and reuse all overloads of other comparison symbols
{
	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;
	}
}

The two dates are equal==

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);
}

<=

bool Date::operator<=(const Date& d) const
{
	return !(*this > d);
}

!=

bool Date::operator!=(const Date& d) const
{
	return !(*this == d);
}

Date plus / minus days returns the date

To implement this function, you need to know the number of days of each month in a given year, so first implement a function GetMonthDay()

(this is the way to write the separation of declaration and definition, so the class domain is specified)

int Date::GetMonthDay(int year, int month)
{
	//This array is unchanged every time it comes in. It can be defined as static and small optimization
	static int monthDayArray[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
	int day = monthDayArray[month];
	if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
	{//Judge whether it is February first, and then leap year
		//Pay attention to adding parentheses to the left of conditional judgment and judgment, and complete each step in strict accordance with logic
		//If it's written like this
		//(month == 2 && (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
		//Judging from left to right, for those years that are not February but are integral multiples of 400 years, one day is added to each month (2000)
		day += 1;
	}
	return day;
}

Date plus days returns the date

Date& Date::operator+=(int day)//End of function * this still exists and can be returned by reference
{
	if (day < 0)//Handling special situations
	{
		return *this -= -day;
	}

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

	return *this;
}

Similarly
Date minus days return date

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

	_day -= day;
	while (_day <= 0)
	{
		--_month;//Pay attention to deal with the month first
		if (_month == 0)
		{
			--_year;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);//Reprocessing day
	}

	return *this;
}

Add / subtract two dates (pay attention to the difference between addition / subtraction and so on)

Date Date::operator+(int day) const//At the end of the function, ret is destroyed and cannot be returned by reference
{
	Date ret(*this);
	ret += day;//Multiplex addition

	return ret;
}

Date Date::operator-(int day) const//Date minus days
{
	Date ret(*this);
	ret -= day;

	return ret;
}

Given a date, print the day of the week

void Date::PrintWeekDay() const
{
	const char* arr[] = { "Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday" };
	Date start(1900, 1, 1);//The date is Monday
	int count = *this - start;//If the overload of two dates - is not set as const member function, then * this cannot be called as a parameter
	//int count = *this - Date(1900, 1, 1);// Anonymous functions are used here

	//cout << count << endl;
	cout << arr[count % 7] << endl;
}

Date + + / –

In order to distinguish between pre + + and post + +, C + + stipulates
The overload of the preceding + + has no parameters, and the parameter list of the latter + + is an int (the implicit this pointer is not considered here)
Front++

Date& Date::operator++()//Here you can return by reference
{
	*this += 1;
	return *this;
}

Postposition++

Date Date::operator++(int)//Here we need to return by value transfer. There are two copy structures
{
	Date ret(*this);//copy construction 
	*this += 1;

	return ret;//Copy construction again
}

Similarly
Front –

Date& Date::operator--()
{
	*this -= 1;

	return *this;
}

Post –

Date Date::operator--(int)
{
	Date ret(*this);
	*this -= 1;

	return ret;
}

Date minus date returns the number of days (and the previous date minus the number of days returns the date to form a function overload)

int Date::operator-(const Date& d) const//Date minus date
{
	Date max = *this;
	Date min = d;

	int flag = 1;//Suppose * this is greater than d
	if (*this < d)
	{
		flag = -1;
		max = d;
		min = *this;
	}
	
	int count = 0;
	while (min != max)
	{
		++min;
		++count;
	}

	return count * flag;
}

Stream insertion / stream extraction function overload

First of all, you should know that cout is essentially a class object of ostream class

< < automatic identification of different types is essentially a function overload

If < < is overloaded as a member function according to the previous operator overload
(the return value is ostream & to realize continuous output. The object cout still exists after the function is destroyed, so it can be returned by reference.

ostream& Date::operator<<(ostream& out)
{
	out << _year << "/" << _month << "/" << _day << endl;
}

But there are only two ways to use < < at this time

int main()
{
	Date d1(2022, 1, 17);
	d1.operator<<(cout);
	d1 << cout;
	
	return 0;
}

The first usage is too troublesome, and the second usage does not conform to the conventional usage, so that this operator overload does not realize the original intention of operator overload to improve the readability of code.

The reason for this problem is that the first parameter of the member function has been fixed to the this pointer to the object.

The solution is to overload < < into a global function, so that you can set the order of parameters by yourself. However, if you want to access member variables in the function without damaging the encapsulation as much as possible, you need to use friends.

Friends include friend functions and friend classes. Let's talk about friend functions first

friend function

Friends provide a way to break through encapsulation, sometimes providing convenience. However, friends will increase the degree of coupling and destroy the package, so friends should not be used more.

A friend function can directly access the private members of a class. It is an ordinary function defined outside the class and does not belong to any class, but needs to be declared inside the class. When declaring, you need to add the friend keyword.

Back to stream insert / stream extract function overload

. h medium

class Date
{
	//Friend function (declaration to be put in class)
	//friend void operator<<(ostream& out, const Date& d);
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
public:
	//slightly
private:
	int _year;
	int _month;
	int _day;
};

. cpp

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

istream& operator>>(istream& in, Date& d)
{
	in >> d._year;
	in >> d._month;
	in >> d._day;

	return in;
}

Precautions for friend functions

  1. Friend functions can access private and protected members of a class, but not member functions of a class
  2. Friend functions cannot be modified with const
  3. Friend functions can be declared anywhere in the class definition and are not limited by the class access qualifier
  4. A function can be a friend function of multiple classes
  5. The calling principle of friend function is the same as that of ordinary function

Homework on destructors

class A
{
public:
	A()
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
};

class B
{
public:
	B()
	{
		cout << "B()" << endl;
	}
	~B()
	{
		cout << "~B()" << endl;
	}
};

class C
{
public:
	C()
	{
		cout << "C()" << endl;
	}
	~C()
	{
		cout << "~C()" << endl;
	}
};

class D
{
public:
	D()
	{
		cout << "D()" << endl;
	}
	~D()
	{
		cout << "~D()" << endl;
	}
};

C c;

int main()
{
	A a;
	B b;
	static D d;

	//Construction sequence: C A B D
	//Destruct sequence: B A D C
	//Local static variables are destroyed before global static variables
	//Local variables are destructed before global variables
	//If they are both local or global (with the same life cycle), they are defined first and then destructed

	return 0;
}

When construction and copy construction occur continuously, they are merged into one copy construction only.
Copy construction can be called in the following two ways:

Widget v(u);
Widget w = v;//Note that this is not a call to an assignment overload, but a copy constructor is used to create an object w here

When the return value of the function is not received, there will be a copy structure to copy the return value to a temporary variable first. (the results of expressions including the addition of two numbers like the following will also be copied and saved in a temporary variable)

a+b;

Anonymous function

If you need to use a class object (such as passing parameters) but do not instantiate a class, you can use anonymous functions.
The life cycle of an anonymous function is only on its line.

Widget();

The following two methods can be used to call functions

Widget x;
f(x);
f(Widget());







Let's take an example

class Widget
{
public:
	Widget()
	{
		cout << "Widget()" << endl;
	}
	Widget(const Widget&)
	{
		cout << "Widget(const Widget&)" << endl;
	}
	Widget& operator=(const Widget&)
	{
		cout << "Widget& operator=(const Widget&)" << endl;
		return *this;
	}
	
	~Widget()//Validate the lifecycle of anonymous functions
	{
		cout << "~Widget()" << endl;
	}
};

Widget f(Widget u)
{
	Widget v(u);
	Widget w = v;
	return w;
}

int main()
{
	Widget x;
	Widget y = f(f(x));//If there is no optimization, there are 9 copies in total; After optimization, it becomes 7 times

	return 0;
}

Initialization list

In fact, the constructor does not initialize, but assign an initial value.

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

Although there is an initial value in the object after the above constructor call, it cannot be called initialization of class object members. The statements in the constructor body can only be called initial value, not initialization. Because initialization can only be initialized once, and the constructor body can be assigned multiple times.

The initialization is actually realized through the initialization list.

Initialization list: start with a colon, followed by a comma separated list of data members. Each "member variable" is followed by an initial value or expression in parentheses.

class Date
{
public:
 Date(int year, int month, int day)
 : _year(year)//Initialization list
 , _month(month)
 , _day(day)
 {}
 
private:
 int _year;
 int _month;
 int _day;
};

For some variables, initialization is necessary, but initialization is not allowed.
When there are three kinds of member variables, they must be initialized with an initialization list

  1. Reference member variable
  2. const member variable
  3. Custom type member without default constructor

In addition, each member variable can only appear once in the initialization list (initialization can only be initialized once)

The initialization list can initialize only some members

class A
{
public:
	A(int a)
	{
		_a = a;
	}
private:
	int _a;
};

class Date
{
public:
	//Date(int year, int month, int day)
	//{
	//	//This error indicates that the member variable has been defined when it is in the constructor body
	//	_N = 10;
	//	_year = year;
	//	_month = month;
	//	_day = day;
	//}

	//Initialization list - where member variables are defined
	//Initialization cannot be repeated. Only some member variables can be initialized
	//Only constructors can use initialization lists (including constructors and copy constructs)
	//Date(int year, int month, int day)
	//	:_year(year)
	//	,_month(month)
	//	,_day(day)
	//	,_ N(10) / / at this time, it is equivalent to initializing all at the time of definition
	//{}

	//You can also initialize only some variables
	Date(int year, int month, int day,int i)
		:_N(10)
		,_ref(i)
		,_aa(-1)
	{}

	//In short, variables that must be initialized at the time of definition must be initialized in the initialization list
	//There are three types of variables that must be initialized in the initialization list
	//1.const type
	//2. Reference
	//3. Custom type without default constructor

private://This is a declaration, not a definition. The declaration cannot be initialized!
	int _year;
	int _month;
	int _day;

	//How to initialize a constant variable?
	//Constants must be initialized at definition time
	const int _N;//const
	int& _ref;//quote
	A _aa;//Custom member variable without default constructor
};
//The definition of function is to implement function, and the definition of variable is open space

int main()
{
	int i = 0;
	Date d1(2022, 1, 19, i);
	//Date d2(2022, 1, 19);
	return 0;
}

Try to use the initialization list for initialization, because no matter whether you use the initialization list or not, for custom type member variables, you will use the initialization list first.

class Date
{
public:
	Time(int hour = 0)
	:_hour(hour)
	{
		cout << "Time()" << endl;
	}
private:
	int _hour;
};
class Date
{
public:
	Date(int day)
	{}
private:
	int _day;
	Time _t;
};

int main()
{
	Date d(1);
}


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

	A(const A& aa)
	{
		cout << "A(const A& aa)" << endl;
		_a = aa._a;
	}

	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		_a = aa._a;

		return *this;
	}

private:
	int _a;
};

class Date
{
public:
	//Date(int year, int month, int day, const A& aa)
	//{
	//	_aa = aa;
	//	_year = year;
	//	_month = month;
	//	_day = day;
	//}

	Date(int year, int month, int day, const A& aa)
		:_aa(aa)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;

	A _aa;
};

int main()
{
	//A aa(10);
	Date d1(2022, 1, 19, aa);

	return 0;
}

The declaration order of a member variable in a class is its initialization order in the initialization list, regardless of its order in the initialization list

class A
{
public:
	A(int a)
		:_a1(a)
		,_a2(_a1)
	{}

	void Print()
	{
		cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a2;
	int _a1;
};

int main()
{
	A aa(1);
	aa.Print();

	return 0;
}

explicit keyword

Constructors can not only construct and initialize objects, but also have the function of type conversion for constructors with single parameters. However, this may reduce the readability of the code.
Modifying the constructor with explicit will prohibit the implicit conversion of single parameter constructor.

class Date
{
public:
	//explicit Date(int year)
	//	:_year(year)
	//{
	//	cout << "Date(int year)" << endl;
	//}

	Date(int year)
		:_year(year)
	{
		cout << "Date(int year)" << endl;
	}
private:
	int _year;
};

int main()
{
	//Although the following two are constructed directly, the process is different
	Date d1(2022);
	Date d2 = 2022;//Implicit type conversion
	//The original intention is to construct a temporary variable Date(2022) with 2022, and then copy this object to construct d2
	//However, in a continuous process of C + + compiler, multiple constructs will be optimized and combined into one
	//So this is optimized to be a construct directly (New compilers usually do it)


	//C implicit type conversion - similar types, indicating types with similar meanings
	//Temporary variables will be generated in the middle
	double d = 1.1;
	int i = d;
	//const int& i = d;// This sentence will report an error because it is not a similar type

	//Cast - independent type
	int* p = &i;
	int j = (int)p;

	return 0;
}


If explicit keyword is not used

static member

Static member characteristics

  1. Static members are shared by all class objects and do not belong to a specific instance
  2. Static member variables must be defined outside the class without adding the static keyword
  3. Class static members are available class names: static members or objects Static member (note that only static member variables can be called in this way)
  4. Static member functions have no hidden this pointer and cannot access any non static members
  5. Like ordinary members of a class, static members have three access levels: public, protected and private, and can also have return values

Take the scene of calculating how many class objects a program creates as an example

int count = 0;
class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		++count;
	}

	A(const A& aa)
		:_a(aa._a)
	{
		++count;
	}
private:
	int _a;
};

void f(A a)
{

}

int main()
{
	A a1;
	A a2;
	f(a1);
	cout << count << endl;

	return 0;
}


If you do not use global variables, you can use static member variables

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		++_sCount;
	}

	A(const A& aa)
		:_a(aa._a)
	{
		++_sCount;
	}

	//int GetCount()
	//{
	//	return _sCount;
	//}

	//Static member function, without this pointer, and can only access static member variables and member functions
	static int GetCount()
	{
		return _sCount;
	}

private:
	int _a;

//public:
	//Static member variables belong to the whole class. The life cycle is in the whole program running period, and the scope is in the class domain
	static int _sCount;//statement
};

int A::_sCount = 0;//Definition + initialization
//Note the definition method of static members. They can only be defined in this way
//Static members do not belong to any object, so they cannot be defined with constructors

void f(A a)
{

}

int main()
{
	A a1;
	A a2;
	f(a1);
	//cout << count << endl;
	//When count is public, it can be accessed in this way
	//Access outside the class
	//cout << A::_sCount << endl;
	//cout << a1._sCount << endl;
	//cout << a2._sCount << endl;

	//When_ sCount is private
	//cout << a1.GetCount() << endl;

	//After changing to static member function, you can use object or class to call
	cout << a1.GetCount() << endl;
	cout << A::GetCount() << endl;

	return 0;
}

Static member functions cannot call non static member variables and non static member functions
However, non static member functions can call static member functions

An example of the application of static member variables and constructors

Summation of arithmetic sequence

class Sum
{
public:
    Sum()//Increment every time the constructor is called
    {
        _ret += _i;
        ++_i;
    }
    
    static int GetRet()
    {
        return _ret;
    }
private:
    static int _i;
    static int _ret;
};

int Sum::_i = 1;
int Sum::_ret = 0;

class Solution {
public:
    int Sum_Solution(int n) {
        Sum a[n];//Variable length arrays are used here
        
        //return Sum().GetRet()-(n+1);// The anonymous function is used to call getret. It is called more than once, so n + 1 should be subtracted
        return Sum::GetRet();
    }
};

A new method of initializing C + + members

C++11 supports initialization and assignment of non static member variables during declaration, but it should be noted that this is not initialization, but the default value of declared member variables.

class B
{
public:
	B(int b = 0)
		:_b(b)
	{}
private:
	int _b;
};

class A
{
public:
	//If the member variable is not initialized during the initialization list phase, it will be initialized with the default value
	A()
	{}
private://This is not initialization, because this is a declaration; This is actually the default value. The syntax here is added by C++11 in the way of patching
	int _a1 = 0;
	B _bb1 = 10;//Implicit type conversion
	B _bb2 = B(20);//Anonymous function
	int* p = (int*)malloc(4 * 10);
	int arr[10] = { 1,2,3,4,5,6 };

	//Static cannot give default values like this. Initialization must be defined in the global location outside the class
	//static int _sCount = 0;
};

int main()
{
	A aa;

	return 0;
}

Friends

Friends include friend functions and friend classes

Friend class

All member functions of a friend class are friend functions of another class and can access non-public members of another class.

For example, if the Date class Date is a friend of the Time class Time, the member function of Date can access the private member variable of Time.

class Time
{
	friend class Date;
public:
	Time(int hour = 0, int minute = 0, int second = 0)
		:_hour(hour)
		,_minute(minute)
		,_second(second)
	{}
private:
	int _hour;
	int _minute;
	int _second;
};

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

	void SetTimeOfDate(int hour, int minute, int second)
	{
		_t._hour = hour;
		_t._minute = minute;
		_t._second = second;
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t;
};

int main()
{
	return 0;
}

Precautions for friend classes

  1. Friend relationship is unidirectional and not exchangeable.
    For example, if the above Time class and Date class declare the Date class as its friend class in the Time class, you can directly access Time in the Date class
    Class, but not if you want to access the private member variables in the Date class in the Time class.
  2. Friend relationship cannot be passed
    If B is A friend of A and C is A friend of B, the friend of A at C cannot be explained.

Inner class

Concept: if a class is defined inside another class, this inner class is called an inner class. Note that the internal class is an independent class at this time. It does not belong to the external class, and the internal class cannot be called through the object of the external class. External classes do not have any superior access rights to internal classes.

main points:

  1. The inner class is a friend of the outer class, but the outer class is not a friend of the inner class.
  2. Internal classes can be defined in public, protected and private of external classes.
  3. The internal class can directly access the static and enumeration members in the external class without the object / class name of the external class.
  4. When calculating the size, the inner class and the outer class are independent.
class A 
{
private:
	static int k;
	int h;
public:
	//1. The definition of internal class B is basically the same as that of the global class, except that B is limited by the category A domain of the external class (reflected in that the calculated sizes A and B are independent)
	//2. Internal class B is naturally a friend of external class A
	class B
	{
	public:
		void foo(const A& a)
		{
			cout << k << endl;//OK
			cout << a.h << endl;//OK
		}
	private:
		int _b;
	};

	//void f(B bb)
	//{
	//	//A is not a friend of B and cannot access B
	//	bb._b;
	//}
};
int A::k = 1;
int main()
{
	cout << sizeof(A) << endl;
	//To access class B, you need to specify the class domain (provided that class B is public)
	A aa;
	A::B bb;//Defines an object belonging to class B

	return 0;
}

As an example of an inner class
In the previous 1 + 2 + 3 +... Topic, you can also write the Sum class as the internal class of the Solution class
Title Link

class Solution {
private:
    class Sum
    {
    public:
        Sum()
        {
            _ret += _i;
            ++_i;
        }
        
    };
    
private:
    static int _i;
    static int _ret;
public:
    int Sum_Solution(int n)
    {
        Sum a[n];//Variable length arrays are used here
        
        return _ret;
    }
};

int Solution::_i = 1;
int Solution::_ret = 0;

Keywords: C++ Back-end

Added by McChicken on Tue, 15 Feb 2022 08:38:48 +0200