[Tienda de esencias de C++] 5. Seis funciones miembro predeterminadas de clases de C++ y clases de objetos (intermedias)

Tabla de contenido

1. Seis funciones miembro predeterminadas

2. Constructor

2.1 Concepto

2.2 Construcción predeterminada

2.2.1 Construcciones predeterminadas generadas por el sistema

2.2.2 Constructor predeterminado personalizado

 2.3 Sobrecarga de constructores

3. Destructor

3.1 Concepto

 3.2 Destructor generado por el sistema

 3.3 Destructor personalizado

4. Copiar la construcción

4.1 Concepto

 4.2 Construcción de copia generada por defecto (copia superficial)

 4.3 Construcción de copia personalizada (copia profunda)

 5. Sobrecarga del operador de asignación

5.1 Sobrecarga del operador

5.2 Sobrecarga del operador de asignación

6. Sobrecarga del operador de dirección y dirección constante

7. Adjunto: clase de fecha completa (el código en el artículo no se toma del código aquí, se escribe temporalmente con el fin de explicar los puntos de conocimiento, el código aquí es la clase de fecha completa (tomada de Bittech), puede aprender de ello)


1. Seis funciones miembro predeterminadas

        Cuando pensamos en una clase vacía, debemos pensar que una clase que no contiene nada se llama clase vacía, pero este no es el caso. Cuando no se escribe nada en una clase, el compilador generará seis funciones miembro predeterminadas de forma predeterminada para completar las funciones básicas de una clase.

  1. Constructor: La inicialización de objetos funciona.
  2. Destructor: trabajo de limpieza espacial
  3. Copiar construcción y sobrecarga del operador de asignación: Copiar objetos Copiar trabajos
  4. Sobrecarga de búsqueda de direcciones y búsqueda constante de direcciones: generalmente, rara vez se implementa por sí sola, a menos que sea necesario devolver una dirección especial específica al usuario.

2. Constructor

2.1 Concepto

        El constructor es una función miembro especial que el compilador llama automáticamente para completar la inicialización del objeto cuando se crea el objeto.El constructor no tiene valor de retorno y el nombre de la función es el mismo que el nombre de la clase.

        Las características del constructor son las siguientes:

  1. El nombre de la función es el mismo que el nombre de la clase.
  2. Sin valor de retorno.
  3. El compilador llama automáticamente al constructor correspondiente cuando se crea una instancia del objeto.
  4. Los constructores pueden estar sobrecargados. 
class Date
{
public:
	Date()
	{
		//构造函数
	}
private:

};

2.2 Construcción predeterminada

        ¿Qué es la construcción predeterminada? Tanto el constructor sin parámetros como el constructor predeterminado se denominan constructores predeterminados y solo puede haber un constructor predeterminado. En términos generales, los tipos personalizados que implementamos deben tener un constructor predeterminado; el motivo se describirá a continuación. Nota: Los constructores sin argumentos, los constructores predeterminados completos y los constructores que no escribimos para ser generados por el compilador de forma predeterminada pueden considerarse constructores predeterminados.

2.2.1 Construcciones predeterminadas generadas por el sistema

         Cuando no definimos manualmente el constructor, el sistema generará un constructor sin argumentos de forma predeterminada. Este constructor sin argumentos inicializará las variables miembro en la clase, incluida la llamada a su constructor predeterminado para el tipo personalizado (es por eso que cada clase debe tener una estructura predeterminada), no maneja tipos integrados , sí, lo escuchaste bien, la estructura predeterminada generada por el sistema no maneja tipos integrados, este es un punto con mucho error. Por ejemplo el siguiente caso:

class Time
{
public:
	Time()
	{
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;

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

producción:

Hora()
-858993460-858993460-858993460 

        Se puede ver que el constructor generado de forma predeterminada no procesará tipos integrados y su construcción predeterminada se llamará para tipos personalizados. Debido a esto, la estructura predeterminada generada por el sistema es inútil, por lo que se aplica un parche a esta vulnerabilidad en C++11: En C++11, se aplica un parche para el defecto de no inicialización del tipo incorporado miembros, a saber: a la variable miembro de tipo incorporada se le puede dar un valor predeterminado cuando se declara en la clase. (Asigne un valor predeterminado al tipo integrado de la misma manera que proporciona un valor predeterminado) de la siguiente manera:

class Time
{
public:
	Time()
	{
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;

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

producción:

Hora()
2023 8 9

2.2.2 Constructor predeterminado personalizado

        Sólo hay dos tipos de constructores predeterminados personalizados, constructores sin argumentos y constructores totalmente predeterminados , y sólo puede existir uno de estos dos tipos de constructores. Pero alguien puede preguntar, ¿estos dos constructores no constituyen una sobrecarga? ¿Por qué no pueden existir al mismo tiempo? En primer lugar, la sintaxis estipula que solo puede existir una estructura predeterminada. Además, aunque estas dos funciones se ajustan a la sintaxis de sobrecarga de funciones, habrá ambigüedad al llamarlas. Debes saber que existen razones para cualquier regulación gramatical. como sigue:

class Time
{
public:
	Time()
	{
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;

		_second = 0;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
	Date()
	{
		cout << "Date()" << endl;
	}

	Date(int a = 1, int b = 2)
	{
		cout << "Date(int a = 1, int b = 2)" << endl;
	}
private:
	int _year = 2023;
	int _month = 8;
	int _day = 9;
	Time _t;
};
int main()
{
	Date d;  //这里出现了歧义,
			   //不知道调用的是全缺省的构造还是无参的构造
			   //因为这俩种函数都可以用 Date() 的形式来调用就出现了歧义
	return 0;
}

 Error:
C2668 'Fecha::Fecha': llamada ambigua a función sobrecargada.

E0339 La clase "Fecha" contiene varios constructores predeterminados.

        Pero si pasamos los parámetros reales al constructor, podemos llamarlo, por lo que la gramática estipula que solo puede haber una construcción predeterminada. La esencia es que la situación anterior causará ambigüedad y podemos pasar un parámetro real cuando normalmente instanciar Para evitar este tipo de ambigüedad, incluso si se definen dos estructuras predeterminadas en este momento, no se informará ningún error, pero no recomendamos escribir de esta manera, porque el riesgo en el proyecto es enorme, como el siguiente:

class Time
{
public:
	Time()
	{
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;

		_second = 0;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
	Date()
	{
		cout << "Date()" << endl;
	}

	Date(int a = 1, int b = 2)
	{
		cout << "Date(int a = 1, int b = 2)" << endl;
	}
private:
	int _year = 2023;
	int _month = 8;
	int _day = 9;
	Time _t;
};
int main()
{
	Date d(1,2);  //不建议,不能因为避免报错就去特殊处理初始化方式。
                  //不能同时定义俩个默认构造,即使编译器没有报错
	return 0;
}

 2.3 Sobrecarga de constructores

        El constructor admite la sobrecarga, lo que nos permite manejar diferentes escenarios de inicialización (aquí enfatizo nuevamente: la construcción sin argumentos y la construcción predeterminada completa no se pueden definir al mismo tiempo, y habrá ambigüedad).

public:
	Date()
	{
		cout << "Date()" << endl;
	}

	Date(int a, int b = 2)
	{
		cout << "Date(int a = 1, int b = 2)" << endl;
	}
	Date(int year, int month, int day)
	{
		cout << "Date(int year, int month, int day)" << endl;
	}
private:
	int _year = 2023;
	int _month = 8;
	int _day = 9;
	Time _t;
};

3. Destructor

3.1 Concepto

        La naturaleza del destructor es muy similar a la del constructor, pero a diferencia de la función del constructor, el destructor se usa para limpiar el espacio después de que se destruye el objeto, pero no completa la destrucción del objeto en sí. y la destrucción del objeto local la realiza el compilador de. Cuando se destruye el objeto, llamará automáticamente al destructor para completar la limpieza de los recursos del objeto.

        El destructor no tiene parámetros ni valor de retorno como el constructor, y el nombre es diferente, y el compilador llama automáticamente al destructor cuando se destruye el objeto, que es similar al constructor. Las características son las siguientes:

  1. El nombre del destructor tiene como prefijo el carácter ~ antes del nombre de la clase.
  2. Sin parámetros ni tipo de retorno.
  3.  Una clase sólo puede tener un destructor. Si no se define explícitamente, el sistema generará automáticamente un destructor predeterminado. Nota: los destructores no se pueden sobrecargar.
  4. Cuando finaliza el ciclo de vida del objeto, el sistema de compilación C++ llama automáticamente al destructor.
class Date
{
public:
	~Date()
	{
		//析构函数
	}
private:
	int _year = 2023;
	int _month = 8;
	int _day = 9;
};

 3.2 Destructor generado por el sistema

        Si no hay una definición explícita de un destructor en una clase, el sistema generará automáticamente un destructor predeterminado. Este destructor se llamará automáticamente al final del ciclo de vida del objeto. El destructor generado por defecto no procesa tipos integrados. tipos personalizados, se llamará su destructor. como sigue:

class Time
{
public:
	~Time()
	{
		cout << "~Time()" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _year = 2023;
	int _month = 8;
	int _day = 9;
	Time _t;
};
int main()
{
	Date d;
	return 0;
}

producción:

~Fecha()
~Hora()

 3.3 Destructor personalizado

        Como se mencionó anteriormente, el destructor no maneja tipos integrados, pero en nuestro uso diario, a menudo involucramos aplicaciones de memoria (como malloc) y luego usamos tipos de puntero para almacenar direcciones. No podemos confiar en el sistema para tales espacios. El destructor predeterminado se genera para evitar pérdidas de memoria y debe definir su propio destructor para liberarlo manualmente. 

class Date
{
public:
	Date()
	{
		p = (int*)malloc(10 * sizeof(int));
	}
	~Date()
	{
		free(p);
		cout << "~Date()" << endl;
	}
private:
	int _year = 2023;
	int _month = 8;
	int _day = 9;
	int* p = nullptr;
};

4. Copiar la construcción

4.1 Concepto

        La construcción de copia es una forma sobrecargada de constructor. La construcción de copia tiene solo un parámetro formal, que generalmente es una referencia constante de este tipo (solo se pueden pasar referencias, no valores, y pasar valores provocará una recursividad infinita). Llamado automáticamente por el compilador cuando se crea un nuevo objeto de tipo objeto.

        Pasar por referencia (correcto):

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d)  //拷贝构造函数
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year = 2023;
	int _month = 8;
	int _day = 9;
	int* p = nullptr;
};

        Pasar por valor (recursión infinita):

 4.2 Construcción de copia generada por defecto (copia superficial)

        Cuando no definimos explícitamente la construcción de copia, se generará automáticamente una construcción de copia predeterminada, que copiará nuestras variables miembro byte a byte y también se convertirá en una copia de valor o una copia superficial.

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
		p = (int*)malloc(10 * sizeof(int));
	}
    ~Date()
	{
		free(p);
		cout << "~Date()" << endl;
	}

private:
	int _year;
	int _month;
	int _day;
	int* p = nullptr;
};

int main()
{
	Date d1(12, 12, 12);
	Date d2(d1);   //引发异常
}

 4.3 Construcción de copia personalizada (copia profunda)

        Cuando se trata de administración de memoria, la estructura de copia generada por el compilador por defecto no es suficiente, en este momento necesitamos hacer una copia profunda de la misma. Qué es una copia profunda: consiste en crear un nuevo objeto y copiar el "valor" (todos los elementos del array) de los atributos del objeto original. Si es la situación anterior, debemos volver a solicitar un espacio para guardar los datos señalados por dp.

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
		p = (int*)malloc(10 * sizeof(int));
		if (p == NULL)
		{
			perror("malloc fail");
			exit(-1);
		}
		for (int i = 0; i < 10; i++)
		{
			p[i] = 10;
		}
	}
	~Date()
	{
		free(p);
		cout << "~Date()" << endl;
	}
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		p = (int*)malloc(10 * sizeof(int));
		if (p == NULL)
		{
			perror("malloc fail");
			exit(-1);
		}
		for (int i = 0; i < 10; i++)
		{
			p[i] = d.p[i];
		}
	}
private:
	int _year;
	int _month;
	int _day;
	int* p = nullptr;
};
int main()
{
	Date d1(12, 12, 12);
	Date d2(d1);
}

 producción:

~Fecha()
~Fecha()

 5. Sobrecarga del operador de asignación

5.1 Sobrecarga del operador

        C++ introduce la sobrecarga de operadores para mejorar la legibilidad del código. La sobrecarga de operadores es una función con un nombre de función especial y también tiene su tipo de valor de retorno, nombre de función y lista de parámetros. El tipo de valor de retorno y la lista de parámetros son similares a las funciones ordinarias. . El nombre de la función es: el operador de palabra clave seguido del símbolo del operador que debe sobrecargarse. Prototipo de función: operador de tipo de valor de retorno (lista de parámetros).

Aviso:

  1. No se pueden crear nuevos operadores concatenando otros símbolos: por ejemplo, operador@ operador sobrecargado debe tener un parámetro de tipo de clase
  2. Operadores utilizados para tipos integrados, cuyo significado no se puede cambiar, por ejemplo: entero integrado +, cuyo significado no se puede cambiar
  3. Cuando se sobrecarga como una función miembro de clase, sus parámetros formales parecen ser 1 menos que el número de operandos, porque el primer parámetro de la función miembro está oculto.
  4. .* :: sizeof ?: Tenga en cuenta que los 5 operadores anteriores no se pueden sobrecargar. Esto suele aparecer en preguntas escritas de opción múltiple.

        Tenemos dos formas de definir la sobrecarga de operadores: se puede definir como global o directamente como un miembro de función, pero cuando se define como un miembro de función, los parámetros formales que veremos serán uno menos (porque este puntero implica parámetros), como sigue == como ejemplo:

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

//函数成员

class Date
{
public:
	bool operator==(const Date& d)
	{
		return (_year == d._year)
			&& (_month == d._month)
			&& (_day == d._day);
	}
private:
	int _year;
	int _month;
	int _day;
};

        A lo que se debe prestar atención en la sobrecarga de operadores es a "++" y "--", porque estos dos operadores tienen la diferencia entre preposición y posposición, por lo que la distinción entre preposición y posposición también se hace de la siguiente manera (con ++ para ejemplo):

	Date& operator++();//前置
	Date operator++(int);//后置 多了一个int形参,仅作标记,没有实际含义

5.2 Sobrecarga del operador de asignación

        Si la función de sobrecarga del operador de asignación no está definida explícitamente, el compilador generará automáticamente una sobrecarga del operador de asignación predeterminada y el método de copia es una copia superficial. La diferencia con otras funciones de sobrecarga de operadores es que la sobrecarga del operador de asignación debe definirse como una función miembro y no puede definirse como global. Si se define como global, se generará automáticamente si no hay una sobrecarga del operador de asignación en el cuerpo de clase, lo que entrará en conflicto con nuestra definición global.

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	  
	Date& operator=(const Date& d) //引用传参避免拷贝提高效率								
	{                              //引用返回因为赋值运算符支持连续赋值 d1 = d2 = d3;
		if (this != &d)
		{
			_year= d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}

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

        Si el tipo implica administración de memoria, se requiere una copia profunda. Aquí es exactamente la misma que la estructura de copia. Si no lo comprende, puede mirar hacia adelante.

6. Sobrecarga del operador de dirección y dirección constante

        Estos dos compiladores de sobrecarga de operadores también se generarán de forma predeterminada. En la mayoría de los casos, no necesitamos definirlos nosotros mismos, a menos que queramos que otros obtengan el contenido especificado.

class Date
{
public:
	Date* operator&()
	{
		return this;
	}
	const Date* operator&()const
	{
		return this;
	}
private:
	int _year;
	int _month;
	int _day;
};

7. Adjunto: clase de fecha completa (el código en el artículo no se toma del código aquí, se escribe temporalmente con el fin de explicar los puntos de conocimiento, el código aquí es la clase de fecha completa (tomada de Bittech), puede aprender de ello)

#pragma once
#include <iostream>
#include <assert.h>
using namespace std;
class Date
{
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
public:
	Date(int year = 2023, int month = 8, int day = 10);
	void Print() const;
	int GetMonthDay(int year, int month) const;
	bool operator==(const Date& d) const;
	bool operator!=(const Date& d) const;
	bool operator<(const Date& d) const;
	bool operator<=(const Date& d) const;
	bool operator>(const Date& d) const;
	bool operator>=(const Date& d) const;
	Date& operator+=(int day);
	Date operator+(int day) const;
	Date& operator-=(int day);
	Date operator-(int day) const;
	int operator-(const Date& d) const;
	Date& operator++();
	Date operator++(int);
	Date& operator--();
	Date operator--(int);
private:
	int _year;
	int _month;
	int _day;
};
inline ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}
inline istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}
#include"Date.h"
int Date::GetMonthDay(int year, int month) const
{
	assert(month > 0 && month < 13);

	int monthArray[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
	if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400) == 0))
	{
		return 29;
	}
	else
	{
		return monthArray[month];
	}
}
Date::Date(int year, int month, int day)
{
	if (month > 0 && month < 13
		&& (day > 0 && day <= GetMonthDay(year, month)))
	{
		_year = year;
		_month = month;
		_day = day;
	}
	else
	{
		cout << "日期非法,初始化失败" << endl;
	}
}
void Date::Print() const
{
	cout << _year << " " << _month << " " << _day << endl;
}
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 _year < d._year
		|| (_year == d._year && _month < d._month)
		|| (_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& Date::operator+=(int day)
{
	if (day < 0)
	{
		*this -= -day;
		return *this;
	}
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month == 13)
		{
			++_year;
			_month = 1;
		}
	}
	return *this;
}
Date Date::operator+(int day) const
{
	Date tmp(*this);

	tmp += day;

	return tmp;
}
Date& Date::operator-=(int day) 
{
	if (day < 0)
	{
		*this += -day;
		return *this;
	}
	_day -= day;
	while (_day <= 0)
	{
		--_month;
		if (_month == 0)
		{
			--_year;
			_month = 12;
		}

		_day += GetMonthDay(_year, _month);
	}

	return *this;
}
Date Date::operator-(int day) const
{
	Date tmp(*this);
	tmp -= day;
	return tmp;
}
Date& Date::operator++()
{
	*this += 1;
	return *this;
}
Date Date::operator++(int)
{
	Date tmp(*this);

	*this += 1;

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

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

	return tmp;
}
int Date::operator-(const Date& d) const
{
	Date max = *this;
	Date min = d;
	int flag = 1;

	if (*this < d)
	{
		max = d;
		min = *this;
		flag = -1;
	}
	int n = 0;
	while (min != max)
	{
		++min;
		++n;
	}
	return n*flag;
}

Supongo que te gusta

Origin blog.csdn.net/qq_64293926/article/details/132189479
Recomendado
Clasificación