[C++] Classes and objects--6 default member functions of classes

1. The 6 default member functions of the class

Default member function: the user does not display the implementation, and the member function generated by the compiler is called the default member function.

If there are no members in a class, it is simply called an empty class.

But is there really nothing in the empty class? No, any class will automatically generate the following 6 default member functions if we don't write it.

class Date{
    
    };
  1. Constructor: complete the initialization work
  2. Destructor: complete cleanup
  3. Copy constructor: Create an object with the same object initialization
  4. Assignment overloading: assigning one object to another object
  5. Address-taking operator overloading: taking the address of a common object
  6. const address operator overloading: take the address of the object modified by const

insert image description here

2. Constructor

2.1 Concept

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 today1;
	today1.Init(2023,1,16);
	today1.Print();

	Date today2;
	today2.Init(2023, 1, 17);
	today2.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 information every time the object is created, it is a bit troublesome. Can you set the information when the object is created?

We need a function: to ensure that the object is initialized when it is created.

The C++ constructor provides this functionality:

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 data member has a suitable initial value, and is called only once in the entire life cycle of the object .

Notice:

If we use 类名 对象名()the method of creating objects, then this line of code will be meaningless:

int main()
{
    
    
	Date d();

	return 0;
}

Write the above code, we go to the assembly to see Date d();what it does, and we will find that it does nothing
insert image description here

2.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.

Its characteristics are as follows:

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

  2. No return value (no need to write void)

  3. The compiler automatically calls the corresponding constructor when the object is instantiated .
    insert image description here

  4. Constructors can be overloaded. (a class can have multiple constructors)

    class Date
    {
          
          
    public:
    	Date()
    	{
          
          
    		cout << "自定义默认构造函数" << endl;
    	}
    	//Date(int year = 1, int month= 2, int day = 3)
    	//{
          
          
    	//	cout << "自定义全缺省默认构造函数" << endl;
    	//}
    	//Date(int year, int month, int day = 1)
    	//{
          
          
    	//	cout << "自定义半缺省构造函数" << endl;
    	//}
    	Date(int year, int month, int day)
    	{
          
          
    		cout << "自定义构造函数" << endl;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    
    int main()
    {
          
          
    	Date today(2023, 2, 6);
    
    	return 0;
    }
    
  5. Both the parameterless constructor and the default constructor are called default constructors, and there can only be one default constructor in a class.

    Reason: Although these two constructors satisfy overloading, the compiler cannot call them, and there is ambiguity. For example, the first no-argument constructor in the above code and the second all-default constructor with annotations, so there can only be one default constructor for a class. (There can only be one non-default constructor, such as the third semi-default constructor and the fourth constructor, which need to pass parameters, and there will be ambiguity if they exist at the same time)

    Note: Whether the constructor needs to pass parameters or not, we divide them into two types

    1. Default constructor: no parameter constructor, full default constructor, we did not write the default constructor generated by the compiler (next item) (these constructors that do not need to pass parameters are considered default constructors)
    2. Parameter passing constructor: no default constructor, full default constructor
    • When creating an object, call the default constructor without adding parentheses after the object (after adding parentheses, the compiler will treat it as a declaration of a function, not a created object). Call the parameter-passed constructor with parentheses and parameters after the object.
  6. If the class does not explicitly define a constructor, the C++ compiler will automatically generate a default constructor without parameters. Once the user explicitly defines the compiler, it will no longer generate it. (The constructor can be overloaded, there are many kinds, as long as we write one, the compiler will not generate the constructor by default)

    Note: As follows, when creating an object without parentheses, the default constructor is called, and with parentheses followed by parameters, the parameter-passing constructor is called.

    The constructor is already defined in the class shown in the figure below, and the compiler will not automatically generate a default constructor.
    insert image description here
    After adding the default constructor, it works normally
    insert image description here

Regarding the default constructor generated by the compiler, many people will be confused: if the constructor is not implemented, the compiler will generate a default constructor. But it seems that the default constructor is useless?

As shown in the following code, the today object calls the default constructor generated by the compiler, but the three member variables _day/_month/_year of the today object are still random numbers, which means that the default constructor generated by the compiler does not no use?

insert image description here
First of all, C++ divides types into the following two types:

  1. Built-in types: data types provided by the language, such as: int, char...
  2. Custom types: We use struct, class, union and other self-defined types

If there is a member variable of a custom type in a class, it needs to be initialized with the default constructor of the class corresponding to the member variable, otherwise it cannot pass. This is the meaning of the existence of the default constructor.

  1. There is a default constructor for the corresponding class of the member variable of the custom type

    class A
    {
          
          
    public:
    	A()
    	{
          
          
    		cout << "A" << endl;
    	}
    private:
    	int a;
    	int b;
    };
    
    class Date
    {
          
          
    public:
    	Date()
    	{
          
          
    		cout << "默认构造函数" << endl;
    	}
    	Date(int year, int month, int day)
    	{
          
          
    		cout << "传参构造函数" << endl;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    	A a1;
    };
    
    int main()
    {
          
          
    	Date today;
    
    	return 0;
    }
    

    insert image description here

  2. The class corresponding to the member variable of the custom type does not have a default constructor

    class A
    {
          
          
    public:
    	A(int c)
    	{
          
          
    		cout << "A" << endl;
    	}
    private:
    	int a;
    	int b;
    };
    
    class Date
    {
          
          
    public:
    	Date()
    	{
          
          
    		cout << "默认构造函数" << endl;
    	}
    	Date(int year, int month, int day)
    	{
          
          
    		cout << "传参构造函数" << endl;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    	A a1;
    };
    
    int main()
    {
          
          
    	Date today;
    
    	return 0;
    }
    

    insert image description here

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

class A
{
    
    
public:
	void Print()
	{
    
    
		cout << _a << " " << _b << endl;
	}
private:
	int _a = 10;
	int _b = 20;
};

class Date
{
    
    
public:
	void Print()
	{
    
    
		a1.Print();
	}

private:
	int _year = 2000;
	int _month = 1;
	int _day = 1;
	A a1;
};
int main()
{
    
    
	Date today;
	today.Print();

	return 0;
}

insert image description here

  • This is the default value. When the constructor does not initialize the member variable, the value of the member variable is the default value. If it is initialized, the constructor is the main one (such as the following code, a variable is initialized, and the variable is initialized with the constructor Initialization is main, and other member variables are default values)
class A
{
    
    
public:
	A()
	{
    
    
		_a = 40;
	}
	void Print()
	{
    
    
		cout << _a << " " << _b << endl;
	}
private:
	int _a = 10;
	int _b = 20;
};

class Date
{
    
    
public:
	void Print()
	{
    
    
		a1.Print();
	}

private:
	int _year = 2000;
	int _month = 1;
	int _day = 1;
	A a1;
};
int main()
{
    
    
	Date today;
	today.Print();

	return 0;
}

insert image description here

3. Destructor

3.1 Concept

We know that a constructor is needed to initialize an object when it is created, so what is needed when the object is destroyed?

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, the destructor is automatically called to complete the cleanup of resources in the object.

We create an object, which is destroyed when the stack frame of the corresponding function is destroyed after the life cycle of the object ends, and the destructor is automatically called by the function before the destruction, to clean up the resources of the object to clear the space of the object or to The requested space is given back to the compiler.

For the cleaning work, we must do it, otherwise it may cause memory leaks, and we often forget this operation, so C++ adds such a function.

3.2 Features

  1. The name of the destructor is to add the characters **~** (negation symbol) before the class name

  2. No parameters and no return value

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

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

    We write the following code to apply for space in the memory, and use the destructor to release the corresponding space.

    class Stack
    {
          
          
    public:
    	Stack()
    	{
          
          
    		ArrStack = (int*)malloc(sizeof(int) * 4);
            if(!ArrStack)//下图中未写
            {
          
          
                preeor("malloc fail!");
                exit(-1);
            }
    		_size = 4;
    		_top = 0;
    	}
    	~Stack()
    	{
          
          
    		if (ArrStack)
    		{
          
          
    			free(ArrStack);
    			ArrStack = nullptr;
    			_size = 0;
    			_top = 0;
    		}
    	}
    private:
    	int* ArrStack;
    	int _size;
    	int _top;
    };
    
    int main()
    {
          
          
    	Stack st;
    
    	return 0;
    }
    

    insert image description here

  5. If there is no resource application in the class, the destructor can not be written, and the compiler can be used to directly generate the default destructor, such as the Date class; when there is a resource application, it must be written, otherwise it will cause resource leakage.

As in the following code, when we create two variables for the same class, the execution order of the constructor is: s1, s2, and the function is in the form of a stack. Creating variables is to push the stack, s1 is pushed into the stack first, and s2 is pushed into the last When the stack is destroyed, s2 is popped out of the stack first, and s1 is popped out later. The order of calling the destructor is: s2, s1

class Stack
{
    
    
public:
	Stack(int num)
	{
    
    
		ArrStack = (int*)malloc(sizeof(int) * num);
        if(!ArrStack)//下图中未写
        {
    
    
            preeor("malloc fail!");
            exit(-1);
        }
		_size = 4;
		_top = 0;
	}
	~Stack()
	{
    
    
		if (ArrStack)
		{
    
    
			free(ArrStack);
			ArrStack = nullptr;
			_size = 0;
			_top = 0;
		}
	}
private:
	int* ArrStack;
	int _size;
	int _top;
};

int main()
{
    
    
	Stack s1(10);
	Stack s1(40);

	return 0;
}

Observe this->_sizethe changes in the figure below

insert image description here

When there is a member variable of a custom type in a class, then when the object created by this class is destroyed, the destructor of the member variable of the custom type in the class will be called

  1. write destructor

    class A
    {
          
          
    public:
    	~A()
    	{
          
          
    		cout << "A" << endl;
    	}
    private:
    	int a;
    	int b;
    };
    
    class Date
    {
          
          
    public:
    	~Date()
    	{
          
          
    		cout << "Date" << endl;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    	A a1;
    };
    int main()
    {
          
          
    	Date today;
    
    	return 0;
    }
    

    insert image description here

  2. no destructor

    class A
    {
          
          
    public:
    	~A()
    	{
          
          
    		cout << "A" << endl;
    	}
    private:
    	int a;
    	int b;
    };
    
    class Date
    {
          
          
    public:
    	
    private:
    	int _year;
    	int _month;
    	int _day;
    	A a1;
    };
    int main()
    {
          
          
    	Date today;
    
    	return 0;
    }
    

insert image description here

Notice:

  • Generate constructors and destructors by default, do not handle built-in types, and handle custom types. (Some compilers will, but the behavior of the compiler at that time has nothing to do with the syntax of C++)

4. Copy constructor

4.1 Concept

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

The function of this function is to assign the data of one object to another object, and the compiler will call this function when a copy occurs, as follows:

class Date
{
    
    
public:
	Date(int year,int month,int day)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d)//拷贝构造函数
	{
    
    
		_year = d._year;
		_month = d._month;
		_day = d._day;
		cout << "拷贝构造函数" << endl;
	}

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

void test(Date d)//调用拷贝构造函数
{
    
    }

int main()
{
    
    
	Date today1(2023,2,7);
	Date today2(today1);//调用拷贝构造函数

	test(today1);

	return 0;
}

insert image description here

4.2 Features

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

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

    If you don't use references, the code is as follows:

    class Date
    {
          
          
    public:
    	Date(const Date d)//拷贝构造函数
    	{
          
          
    		_year = d._year;
    		_month = d._month;
    		_day = d._day;
    	}
    
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    

    Such a copy constructor, we will copy when we call it, and if we need to copy, we need to call the copy constructor, which will cause an endless loop of formal parameters, because if you want to call you, you need to use You, the compiler won't allow this to happen.

    insert image description here

    As shown in the figure above, to copy the data of object d1 to d2, the copy constructor needs to be called, and the copy constructor needs to be called when the formal parameters of the called procedure are copied.

    So here we have to use references, as follows:

    	Date(const Date& d)//拷贝构造函数
    	{
          
          
    		_year = d._year;
    		_month = d._month;
    		_day = d._day;
    	}
    

    When calling for the first time, use d to alias the object, so there is no need to call other copy constructors.

    It is recommended to use decoration for this function constto prevent us from accidentally writing this function and changing the member variables of the object.

  3. If no definition is shown, the compiler generates 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, for the Date class object above, if a shallow copy occurs, it just copies the values ​​of all member variables in the space occupied by one object to the member variables of another object. It seems reasonable to do so, but it is not. For built-in types, of course There is no problem, but a data structure such as a stack is absolutely impossible. As the following stack code

    class Stack
    {
          
          
    public:
        Stack(size_t capacity = 10)
        {
          
          
            _array = (int*)malloc(int* sizeof(int));
            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:
        int *_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;
    }
    

    insert image description here

    This program is bound to produce errors.

    If we want the program to run correctly, we need to write our own copy constructor, that is, deep copy, so that the member variables of each of their objects have their own independent space when facing this situation, instead of sharing a space .

    This is also the meaning of the existence of the copy constructor. The compiler can only do shallow copy work. If the copy of an object needs to use deep copy, the programmer needs to complete this task manually. This is also a defect in the C language. It more than makes up for that.

    The modified stack code is as follows:

    class Stack
    {
          
          
    public:
        Stack(size_t capacity = 10)
        {
          
          
            _array = (int*)malloc(capacity * sizeof(int));
            if (nullptr == _array)
            {
          
          
                perror("malloc申请空间失败");
                return;
            }
            _size = 0;
            _capacity = capacity;
        }
        Stack(const Stack& st)
        {
          
          
            _array = (int*)malloc(sizeof(int) * st._capacity);
            if (_array == nullptr)
            {
          
          
                perror("malloc申请空间失败");
                return;
            }
            for (int i = 0; i < st._size; i++)
            {
          
          
                _array[i] = st._array[i];
            }
            _size = st._size;
        }
        void Push(const int& data)
        {
          
          
            // CheckCapacity();
            _array[_size] = data;
            _size++;
        }
        ~Stack()
        {
          
          
            if (_array)
            {
          
          
                free(_array);
                _array = nullptr;
                _capacity = 0;
                _size = 0;
            }
        }
    private:
        int* _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;
    }
    

    Therefore, if the class does not involve resource application, the copy constructor can be written or not. If it involves resource application, the copy constructor must be written, otherwise it is a shallow copy.

  4. The three scenarios where the copy constructor is called most frequently are as follows

    • Create a new object using an existing object
    • The function parameter type is a class type object
    • The return value type of the function is a class type object

    insert image description here

Through these we can also see that copying is a common thing in writing code, but it consumes a lot of resources, so in actual use, if you can use references, use references as much as possible to reduce computer consumption and create more resources. Excellent program.

Common way of writing:

class A
{
    
    };

A a;      //创建对象a
A b(a);   //使用对象a进行拷贝创建对象b
A c = a;  //使用对象a进行拷贝创建对象c
  • Note: The copy constructor is a constructor that uses another object to create itself when the object is defined, instead of performing copy construction when the object is created, so that the call is not copy construction but assignment overloading.
class A
{
    
    };

int main()
{
    
    
	A a;
	A b;
	b = a; //此处发生赋值重载

	return 0;
}

5. Assignment operator overloading

5.1 Operator overloading

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.

The encapsulation of classes in C++ is very good. If you want to compare between classes, copy and other operations need to call functions in the class, and for ordinary built-in types, you only need to use simple operators. Can be completed, C++ stipulates that some operators can be overloaded to complete this function, which enhances the readability of the code.

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)

Notice:

  • New operators cannot be created by concatenating other symbols: e.g. operator@
  • An overloaded operator must have a class type parameter
  • Operators used for built-in types, whose meaning cannot be changed, for example: built-in integer +, whose meaning cannot be changed
  • 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
  • .* :: sizeof ?: .Note that the above 5 operators cannot be overloaded. This often appears in multiple choice questions in written examinations.

In the following code, if the scope of the operator overloaded function is global, the member variables of that type must be public, so the encapsulation cannot be guaranteed

class Date
{
    
    
public:

	Date(int year = 2000, 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;
}

bool test()
{
    
    
	Date today1(2023, 2, 7);
	Date today2;
	return today1 == today2;
}

Here we can use friends to solve it, or put the operator overloading function into the class, and we generally put it into the class.

class Date
{
    
    
public:

	Date(int year = 2000, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	bool operator==(const Date& d1)
	{
    
    
		return _year == d1._year && _month == d1._month
			&& _day == d1._day;
	}

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

bool test()
{
    
    
	Date today1(2023, 2, 7);
	Date today2;
    //today1.operator==(today2)
	return today1 == today2;
}

When calling a member function, the compiler will automatically pass the called object as this pointer, and we only need to write a parameter.

Notice:

  1. You need to pay attention to the operator priority when using it. For example, you need to use parentheses when using operator overloading below.

    	cout << (today1 == today2) << endl;
    
  2. In operator overloading, if there are multiple parameters, the first parameter is the left operand, the second parameter is the right operand, and so on. As in the above code, the first parameter is today1, which is the left operand, and the operator overload function is called by this object, and the second parameter today2 is the parameter.

5.2 Assignment operator overloading

If you don’t implement the assignment operator yourself, the compiler will generate it by default. Only assignment and address taking are like this. If other custom types need to be used, we have to write them ourselves. (Take the address below)

Assignment operator overload format:

  • Parameter type: const Typedef&, passing by reference can improve the efficiency of parameter passing
  • Return value type: Typedef&, return reference can improve return efficiency, and 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
class Date
{
    
    
public:

	Date(int year = 2000, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	Date& operator=(const Date& d)
	{
    
    
		if (this != &d)//检测是否自己给自己赋值
		{
    
    
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;//返回*this
	}

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

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

class Date
{
    
    
public:

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

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

//全局函数不能用`this`指针,需要给两个参数
Date& operator=(const Date& d1, const Date& d2)
{
    
    
    if (d1 != &d2)//检测是否自己给自己赋值
    {
    
    
        d1._year = d2._year;
        d1._month = d2._month;
        d1._day = d2._day;
    }
	return d1;//返回*this
}
  1. Among them, in order to access the member variables in the class, they are publicized and the encapsulation is lost.
  2. Such a function is destined to fail to compile, if the assignment operator in the class is not implemented, the compiler will implement a default assignment operator in the class itself, and when calling, we implement one ourselves, and the compiler implements another This creates a conflict.

Therefore, assignment operator overloading can only be a member function of a class.

As mentioned above, if we don’t write it ourselves, the compiler will implement a default assignment operator overload, which is copied byte by byte in a worthwhile way during operation. In the above copy constructor, the copy constructor created by the compiler itself by default is also the same. It can only perform shallow copying, and can only copy values ​​and cannot allocate memory for it. However, assignment operator overloading is still a little different. It needs to be allocated for initialization. When creating space, it will first allocate space for the created object, and then use the assignment operator to discard the allocated space and store it in the space address of other objects.

The following code:

// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
class Stack
{
    
    
public:
	Stack(size_t capacity = 10)
	{
    
    
		_array = (int*)malloc(capacity * sizeof(int));
		if (nullptr == _array)
		{
    
    
			perror("malloc申请空间失败");
			return;
		}
			_size = 0;
		_capacity = capacity;
	}
	void Push(const int& data)
	{
    
    
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	~Stack()
	{
    
    
		if (_array)
		{
    
    
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	int* _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;
}

insert image description here

We should note that if resource management is not involved in the class, whether the assignment operator can be implemented or not; once resource management is involved, it must be implemented.

5.3 Pre-++ and post-++ overloading

For the front ++, we can write according to the normal operator overloading mode, but remember that the return type needs to be used to 类类型&return the modified object.

class Date
{
    
    
public:

	Date(int year = 2000, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	Date& operator++()
	{
    
    
		_year += 1;
		return *this;
	}

private:
	int _year;
	int _month;
	int _day;
};
int main()
{
    
    
	Date today(2023, 2, 7);
	Date d;
	d = ++today; //d:2024.2,7  today:2024,2,7

	return 0;
}

As for the post ++, in order to allow the two functions to be overloaded, it is stipulated to add an int type parameter as a distinction.

Note: The pre-++ is used before ++, so the modified object can be returned directly. For the post-++, the ++ is used first, so the returned object should be the unmodified object. We can modify the original Copy it before the object, then modify the original object, and return the previously copied object directly when returning, so that the original object is changed, and the unchanged object is used, which conforms to postposition ++

class Date
{
    
    
public:

	Date(int year = 2000, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	Date operator++(int)
	{
    
    
		Date temp(*this);
		_year += 1;
		return temp;
	}

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

int main()
{
    
    
	Date today(2023, 2, 7);
	Date d;
	d = today++; //d:2023,2,7  today:2024,2,7

	return 0;
}

5.4 Stream insertion and stream extraction operator overloading

In C++, we usually output and input a data by passing , they are actually a class object, and two operators cout、cinare overloaded , so input and output are actually calling two operator overloaded functions.<<、>>

insert image description here
As shown above, their types are ostream、istreamstored in iostreamthis header file, and the things defined in the C++ library are stored in stdthis namespace, so we need to write these two lines of code at the beginning of each time.

For built-in types, as follows:

int a = 10;
double b = 10.0;
cout << a;
cout << b;

Call different operator functions through function overloading, print them.

Let's take a look at how these two operators are overloaded.

stream extraction

Defined in the class:

class Date
{
    
    
public:
	Date(int year = 2000, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	void operator<<(ostream& out)
	{
    
    
        //下面就是输出内置类型的值,流提取调用头文件<iostream>内的
		out << _year << "年" << _month << "月" << _day << "日" << endl;
	}

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

int main()
{
    
    
	Date today;
    //第一个参数为左操作数,第二个参数为右操作数,由创建的对象调用类内的重载函数
    //today.operator<<(cout)
	today << cout;

	return 0;
}

insert image description here

We can see that the usage form of the function is today << cout;that the class object preempts the first parameter, which must be on the left, and cout is on the right. This writing is definitely not in line with our usual habits. If we want to put cout in the first position, we need to put Functions are defined globally.

class Date
{
    
    
public:
	friend ostream& operator<<(ostream& out, const Date& d);
	Date(int year = 2000, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}

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

//不对对象的成员变量做修改,最好使用const修饰,防止写错,发生错误
ostream& operator<<(ostream& out,const Date& d)
{
    
    
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
    return out;
}

int main()
{
    
    
	Date today;
	cout << today;

	return 0;
}

insert image description here

As in the above code, after our function becomes global, the location problem is solved very well, but we cannot access the member variables in the class. There are three methods here, we use the first one

  1. Make this function a friend function of the class. In the public scope of the class, use friend to modify the declaration of the function, and you can use the object of the corresponding class to call the member variable in the function.
  2. Add an interface, create an output function in the class, and call the corresponding function to get the corresponding member variable value. Objects outside the class cannot access member variables, but can access externally developed functions. (java likes to do this)
  3. Remove the private scope so member variables can be accessed. (It is not recommended to do this, it will destroy the encapsulation)

In order to prevent the following situation, in order to output the values ​​of multiple objects, we need to return the overloaded function coutso that the function can run normally.

cout << d1 << d2 << d3 << endl;
//cout << d1  //调用重载函数,调用后返回cout继续执行
//cout << d2  //同时,运行后返回cout
//..
//cout << endl; //与重载的类型不匹配,调用头文件内的函数

stream insertion

class Date
{
    
    
public:
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);

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

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

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

//需要改变对象的成员变量,不能使用const修饰
istream& operator>>(istream& in, Date& d)
{
    
    
	in >> d._year >> d._month >> d._day;
	return in;
}

int main()
{
    
    
	Date today;

	cin >> today;
	cout << today;

	return 0;
}

insert image description here

As above code is similar to stream extraction.

6. const members

Does the following code work normally?

class Date
{
    
    
public:
	Date(int year=2000,int month = 1,int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
    
    
		cout << "Print" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
    
    
	const Date d;
	d.Print();

	return 0;
}

It does not work properly because the object d is modified with const, and its value cannot be changed (the member variables of the object cannot be modified). When calling a member function, the default value passed by the compiler is that Date* const thisthe this pointer represents the object itself, which means that the member variable can be changed within this function, which creates a conflict. To put it more simply, this is to make an object that can only be read into readable and writable, regardless of its permissions.

To solve this problem, just use const to modify *this so that it cannot be changed, and this is the default of the compiler. It is hidden and difficult to modify. C++ provides the following method, in the parentheses of member functions Adding const directly after that means modification *this, as follows

	void Print() const
	{
    
    
		cout << "Print" << endl;
	}

If we call the const-decorated member function for the const-decorated object, it is ok at this time. The original object can be modified and read by calling the member function, but now it is just passed to the member function to read it.

class Date
{
    
    
public:
	Date(int year=2000,int month = 1,int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	void Print() const
	{
    
    
		cout << "Print" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
    
    
	Date d;
	d.Print();

	return 0;
}

In the same way: member functions in a class can call each other, but member functions modified by const cannot call non-modified member functions, because the function *this pointer of the modified member functions cannot be changed, while the undecorated ones are Can be changed, const loses its effect, this way of writing is wrong. The member function that has not been modified can be called and modified. This is the development from the situation of being both readable and writable to the situation of being only readable, without changing the syntax.

Notice:

  1. For member functions that do not change member variables inside the class, it is best to add const to prevent data from being modified

  2. Generally, const members are used in the following scenarios

    class Date
    {
          
          
    public:
    	Date(int year=2000,int month = 1,int day = 1)
    	{
          
          
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    	void Print() const
    	{
          
          
    		cout << _year << endl;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    
    void test(const Date& d)
    {
          
          
    	d.Print();
    }
    
    int main()
    {
          
          
    	Date td;
    	test(td);
    
    	return 0;
    }
    

    We generally do not modify the cosnt for the object at the beginning of creation, but we often pass the object as an actual parameter to other functions. If the formal parameter is modified by const, it can only be read in this function, and cannot be modified, which means calling The member functions of must also be modified by const.

  3. const is only for member functions

  4. If the definition and declaration are separated, when const needs to be modified, both the definition and the declaration must be modified

  5. Member functions are const-modified and not const-decorated to constitute const overloading

    	void Print() const
    	{
          
          
    		cout << _year << endl;
    	}
    	void Print()
    	{
          
          
    		cout << _year << endl;
    	}
    

    One formal parameter is Date* const this, one is const Date* const this, different formal parameters satisfy overloading

  6. If the member function is modified by const, pay attention to its return value type. If it returns a member variable, you also need to modify const, otherwise the permissions will change and the compilation will go wrong.

7. Address overloading and const address operator overloading

Address take overload and const address take operator overload are the last two member functions generated by default by the compiler. We generally don't write them, but directly use the ones generated by the compiler by default.

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

int main()
{
    
    
	Date d1;
	const Date d2;
	cout << &d1 << endl;
	cout << &d2 << endl;

	return 0;
}

insert image description here

We can also write it out if we want, as follows:

class Date
{
    
    
public:
	Date(int year=2000,int month = 1,int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	Date* operator&()//取地址重载
	{
    
    
		return this;
	}
	const Date* operator&() const //const取地址操作符重载
	{
    
    
		return this;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
    
    
	Date d1;
	const Date d2;
	cout << &d1 << endl;
	cout << &d2 << endl;

	return 0;
}

insert image description here

The two usage scenarios are different. The address-taking overload is used to get the address of a general object, and the const address-taking operator overload is used to get the address of an object modified by const.

Guess you like

Origin blog.csdn.net/m0_52094687/article/details/128958076