[C++] Classes and Objects

Table of contents

Classes and Objects:

                             1. The 6 default member functions of the class

                             2. Constructor

                             3. Destructor

                             4. Copy constructor

                             5. Assignment operator overloading

                             6. const member function

                             7. Address and const address operator overloading

Classes and Objects:

1. The 6 default member functions of the class

If there are no members in a class, it is simply called an empty class. Does the empty class really have nothing? No, when any class does not write anything, the compiler will automatically generate 6 default member functions.

Default member function:

If the user does not display the implementation, the member function automatically generated by the compiler is called the default member function.

class Date
{};

2. Constructor

The concept of a constructor:

For 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
}

For the Date class, you can set the date for the object through the Init public method, but if you call this method to set the date every time you create an object, it is a bit too troublesome, can you set the date when the object is created?

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

Features of the constructor:

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.

Its characteristics are as follows:

1. The function name is the same as the class name.

2. No return value

3. The compiler automatically calls the corresponding constructor when the object is instantiated.

4. The constructor can be overloaded, and multiple constructors and multiple initialization methods can be provided, but generally it will be written with default parameters.

class Date
{
 public:
        //1.无参构造函数
        Date()
        {}
        //2.带参构造函数
        Date(int year,int month,int day)
        {
            _year = year;
            _month = month;
            _day = day;
        }
private:
     int _year;
     int _month;
     int _day;
}
 void TestDate()
 {
       Date d1; // 调用无参构造函数
       Date d2(2015, 1, 1); // 调用带参的构造函数
       // 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
       // 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
       Date d3();
       // warning C4930: “Date d3(void)”: 未调用原型函数
}

5. If there is no explicit definition of the constructor in the class, the C++ compiler will automatically generate a default constructor without parameters . Once the user explicitly defines the compiler, it will no longer generate it.

class Date
{
 public:
   /*如果用户显式定义了构造函数,编译器将不再生成
   Date(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类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数
    //将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成
    //放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
    Date d1;
    return 0;
 }

6. Regarding the default member function generated by the compiler, many students will have doubts: if the constructor is not implemented, the compiler will generate a default constructor, but it seems that the default constructor is useless? The d object calls the compiler The generated default constructor , but the d objects _year, _month, and _day are still random values, which means that the default constructor generated by the compiler is of no use here ?

Default constructor : No parameter, all default and compiler-generated constructors are called default constructors.

Answer: C++ divides types into built-in types and custom types. Built-in types are the data types provided by the language, such as int, char..., and custom types are the types we define ourselves using class, struct, union, etc. Take a look In the following program, you will find that the default constructor generated by the compiler will call its default member function for the custom type member.

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

Note: In C++11, a patch has been applied for the defect that the built-in type members are not initialized, that is, the built-in type member variables can be given a default value when declared in the class.

7. The no-argument constructor and all-default constructor are called default constructors , and there can only be one default constructor. Note: No-argument constructors and all-default constructors will be generated by the compiler without writing constructors can be considered as default constructors.

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;
};
// 以下测试函数不能通过编译
void Test()
{
	Date d1;
}

3. Destructor

The concept of a destructor:

Contrary to 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.

Characteristics of the destructor:

A destructor is a special member function with the following characteristics:

1. The name of the destructor is to add the character ~ before the class name.

2. No parameters and no return value type.

3. A class can only have one destructor. If no definition is displayed, the system will automatically generate a default destructor. Note: Destructors cannot be overloaded.

4. When the object life cycle ends, the C++ compilation system will automatically call the destructor.

Demand-oriented: If the default generated by the compiler is satisfactory, you don’t need to write it yourself. If you don’t meet it, you need to write it yourself. For example: the constructors of Date and Stack need to be written by themselves, but MyQueue does not need to be written by itself, and the ones generated by default can be used. The destructor of Stack needs to be written by ourselves, and the destructor of MyQueue does not need to be written by ourselves, it can be generated by default.

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 3)
	{
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}
	void Push(DataType data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	int _capacity;
	int _size;
};
void TestStack()
{
	Stack s;
	s.Push(1);
	s.Push(2);
}

5. Regarding the destructor automatically generated by the compiler, call its destructor for the custom member.

6. If there is no resource application in the class, the destructor can not be written, and the default destructor generated by the compiler can be used directly, such as the Date class. When there is a resource application, it must be written, otherwise it will cause resource leakage, such as the Stack class .

class Time
{
public:
	~Time()
	{
		cout << "~Time()" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d;
	return 0;
}
// 程序运行结束后输出:~Time()
// 在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?
// 因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month, _day三个是
// 内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对象,所以在
// d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。但是:main函数
// 中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函
// 数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time
// 类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁
// main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数
// 注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数

4. Copy constructor

The concept of copy constructor:

There is only a single formal parameter, which is a reference to the object of this class type (generally modified by const), and the copy construction is to use an object of the same type to initialize another object.

Features of the copy constructor:

1. The copy constructor is an overloaded form of the constructor.

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

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
    //形参加const 防止写反了 问题就可以检查出来了
	Date(const Date& d)   
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	Date d2(d1);
	//Date d2 = d1;//拷贝构造 它满足一个拷贝初始化另一个马上要创建的对象
	return 0;
}

3. If the definition is not displayed, the compiler will generate a default copy constructor. The memory storage of the default copy constructor object is copied in byte order. This copy is called shallow copy, or value copy.

Note: In the default copy constructor generated by the compiler, the built-in type is copied directly by byte, while the custom type is copied by calling its copy constructor.

class Time
{
public:
	Time()
	{
		_hour = 1;
		_minute = 1;
		_second = 1;
	}
	Time(const Time& t)
	{
		_hour = t._hour;
		_minute = t._minute;
		_second = t._second;
		cout << "Time::Time(const Time&)" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d1;

	// 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数
	// 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数
	Date d2(d1);
	return 0;
}

4. The default copy constructor generated by the compiler can already complete the value copy of the byte order. Do you still need to display the implementation yourself? Of course, classes like the date class are not necessary, so what about the following classes?

//这里会发现下面的程序会崩溃掉? 崩溃原因:同一块空间析构两次 需要用深拷贝去解决。
typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		_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;
}

 Note: If the resource application is not 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. That is: if you need to write a destructor, you need to write a deep copy of the copy construction Stack, and you don’t need to write a destructor, you can use Date/MyQueue for the shallow copy of the copy construction that is generated by default

5. Typical calling scenarios of copy constructor:

a. Create a new object from an existing object

b. When passing parameters, the function parameter type is a class type object

c. When the function returns, the return value type of the function is a class type object

class Date
{
public:
	Date(int year, int minute, int day)
	{
		cout << "Date(int,int,int):" << this << endl;
	}
	Date(const Date& d)
	{
		cout << "Date(const Date& d):" << this << endl;
	}
	~Date()
	{
		cout << "~Date():" << this << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
Date Test(Date d)
{
	Date temp(d);
	return temp;
}
int main()
{
	Date d1(2022, 1, 13);
	Test(d1);
	return 0;
}

In order to improve the efficiency of the program, when passing parameters to general objects, try to use the reference type as much as possible, and use references as much as possible according to the actual scene when returning.

5. Assignment operator overloading

Operator overloading:

C++ introduces operator overloading in order to enable custom types to use operators and 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, and its return value type and parameter lists are similar to normal functions.

Function name: the keyword operator followed by the operator symbol that needs to be overloaded

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

Notice:

1. You cannot create new operators by concatenating other symbols

2. An overloaded operator must have a class type parameter

3. The meaning of operators used for built-in types cannot be changed, such as the built-in integer +, whose meaning cannot be changed

4. When overloaded as a class member function, its formal parameters seem to be 1 less than the number of operands, because the first parameter of the member function is the hidden this pointer

5. .* :: sizeof ?: .    Note that the above 5 operators cannot be overloaded.

// 全局的operator==
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//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;
}
void Test()
{
	Date d1(2018, 9, 26);
	Date d2(2018, 9, 27);
	cout << (d1 == d2) << endl;
}
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		 _year = year;
		 _month = month;
		 _day = day;
	}

		// bool operator==(Date* this, const Date& d2)
		// 这里需要注意的是,左操作数是this,指向调用函数的对象
	bool operator==(const Date& d2)
	{
		return _year == d2._year;
		       && _month == d2._month
			   && _day == d2._day;
	}
private:
	int _year;
	int _month;
	int _day;
}
int main()
{
	Date d1(2022, 9, 22);
	Date d2(2023, 1, 1);
	d1 == d2; //函数放在全局会转换成operator==(d1,d2);函数放在类中d1.operator==(d2)
	//也可以显示调用 但一般不这样
	//operator==(d1, d2);//d1.operator==(d2)
    return 0;
}

Assignment operator overloading:

1. Assignment operator overload format:

   Parameter type: const T&, pass by reference can improve transfer efficiency

   Return value type: T&, return reference can improve the efficiency of return, the purpose of return value is to support continuous assignment

   Check if you assign a value to yourself

   Return *this: to conform to the meaning of continuous assignment

2. Assignment operators can only overload member functions of classes and cannot be overloaded as global functions

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;
}
// 编译失败:
// error C2801: “operator =”必须是非静态成员

Reason: If the assignment operator does not display the implementation, the compiler will generate a default one. At this time, the user implements a global assignment operator overload outside the class, which will conflict with the default assignment operator overload generated by the compiler in the class. Therefore, assignment operator overloading can only be a member function of a class.

3. When the user does not display the implementation, the compiler will generate a default assignment operator overload, which is copied byte by byte in the form of value. Note: Built-in type members are directly assigned, while custom type member variables need to call the assignment operator overload of the corresponding class to complete the assignment.

Note: The difference between assignment operator overloading and copy construction:

Copy construction is to use an object of the same type to initialize another object. Assignment operator overloading means that two objects already exist. At this time, the value of one object must be copied to another object.

class Time
{
public:
	Time()
	{
		_hour = 1;
		_minute = 1;
		_second = 1;
	}
	Time& operator=(const Time& t)
	{
		if (this != &t)
		{
			_hour = t._hour;
			_minute = t._minute;
			_second = t._second;
		}
		return *this;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d1;
	Date d2;
	d1 = d2;
	return 0;
}

Now that the default assignment operator overload function generated by the compiler can already complete byte-ordered value copying, do you still need to implement it yourself?

//这里会发现下面的程序会崩溃掉 
//崩溃原因:如果这里不写赋值运算符重载而使用编译器提供的默认赋值重载函数 不仅会导致同一块空间析构两次 还会导致内存泄露  这里就需要深拷贝去解决。
typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		_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;
	s2 = s1;
	return 0;
}

Note: If the resource cleanup is involved in the class, assignment operator overloading can be implemented or not. Once it involves resource management, it must be implemented. 

pre++ and post++ overloads:

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

6. const member function

The const-modified "member function" is called a const member function. A const-modified class member function actually modifies the implicit this pointer of the member function, indicating that any member of the class cannot be modified in the member function. 

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << "Print()" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
	void Print() const
	{
		cout << "Print()const" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};
void Test()
{
	Date d1(2022, 1, 13);
	d1.Print();
	const Date d2(2022, 1, 13);
	d2.Print();//报错
}

Consider the following questions:

1. Can a const object call a non-const member function?

No, const Date* d1 —> Date* const this permission enlargement

2. Can a non-const object call a const member function?

Yes, Date* d1 —> const Date* const this narrows permissions

3. Can other non-const member functions be called in a const member function?

No, const member functions cannot call other non-const member functions. From a functional point of view, I don’t want to have the risk of modifying member variables. From a grammatical point of view, this -> test2() const Date* const this -> Date* const this authority enlargement

4 Can non-const member functions call other const member functions?

Yes, other const member functions can be called in non-const member functions, this -> test1() Date* const this -> const Date* const this The permissions are reduced

7. Address and const address operator overloading

These two constructors generally do not need to be overloaded, 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; // 日
};

Guess you like

Origin blog.csdn.net/qq_66767938/article/details/130031721