Función miembro predeterminada de clase (C++)

1. Constructor

El constructor es una función miembro especial con el mismo nombre que el nombre de la clase. El compilador lo llama automáticamente al crear un objeto de tipo de clase para garantizar que cada función miembro tenga un valor inicial y se llame solo una vez en todo el ciclo de vida de el objeto.

característica

Un constructor es una función miembro especial. Cabe señalar que aunque el nombre de un constructor se llama constructor, la tarea principal de un constructor no es abrir espacio para crear objetos, sino inicializar objetos.
clave: abrir espacio es una cuestión del marco de la pila de funciones y llamar a la función para abrir espacio.

  • El nombre de la función debe ser el mismo que el nombre de la clase.
  • Sin valor de retorno (no es necesario escribir void)
  • Llame automáticamente al constructor correspondiente cuando se crea una instancia del objeto
  • Los constructores se pueden sobrecargar
class Time
{
    
    
public:
	//无参的构造函数
	Time()
	{
    
    
		cout << "None Pram" << endl;
	}


	//带参数的构造函数
	Time (int hour, int minute, int second)
	{
    
    
		_hour = hour;
		_minute = minute;
		_second = second;
		cout << _hour << ":" << _minute << ":" << _second << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};


int main()
{
    
    
	Time t1;  //调用无参的构造函数
	Time t2(9, 19, 30);  //调用带参的构造函数
	Time t3();   //该方法调用无参构造函数会报错,该种写法是写一个返回值为Time类型函数的写法。

	
}

inserte la descripción de la imagen aquí

  • Si no hay un constructor de definición explícito en la clase, el compilador de C++ generará un constructor predeterminado sin parámetros por defecto. Una vez que el usuario define explícitamente el compilador, ya no lo generará automáticamente.
class Time
{
    
    
public:
	带参数的构造函数
	//Time(int hour, int minute, int second)
	//{
    
    
	//	_hour = hour;
	//	_minute = minute;
	//	_second = second;
	//	cout << _hour << ":" << _minute << ":" << _second << endl;
	//}
private:
	int _hour;
	int _minute;
	int _second;
};


int main()
{
    
    
	//1.将显示定义的构造函数屏蔽以后,编译器会默认自动生成一个
	//无参的默认构造函数,因此不会报错。

	//2.如果将显示定义的构造函数展开,编译器不在生成,因为用户定义了一个。
	//此时如果还这样声明变量,就会报错,要以自己定义的方式传参。
	Time t1;  
}
  • Para el constructor predeterminado generado por el compilador, en realidad no inicializa las variables miembro de la clase Time.¿Cuál es su uso?
    El constructor predeterminado generado por el compilador no trata con tipos integrados (int/char/double/pointer) y llama a sus propios constructores predeterminados para tipos personalizados (struct, class, union), etc. (No manejar tipos integrados es un defecto de C++). C++ 11 agrega otra característica para compensar el defecto, es decir, las variables miembro de tipos incorporados pueden recibir valores predeterminados cuando se declaran en la clase.

  • Los constructores sin argumentos, los constructores predeterminados completos y los constructores generados sin compiladores pueden considerarse constructores predeterminados, y solo puede haber un constructor predeterminado .
    inserte la descripción de la imagen aquí

2. Destructor

Al contrario de la función del constructor, el destructor no completa la destrucción del objeto en sí, y el compilador realiza la destrucción del objeto local. Cuando se destruye el objeto, se llama automáticamente al destructor para completar el trabajo de limpieza de recursos en el objeto . El objeto se destruirá automáticamente cuando se alcance su ciclo de vida.

característica

  • El nombre del destructor es el carácter ~ antes del nombre de la clase.
  • sin parámetros sin tipo de retorno
  • Una clase solo 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
  • Al final del ciclo de vida del objeto, el sistema compilador de C++ llama automáticamente al destructor
class Stack
{
    
    
public:
	Stack(int capacity=3)
	{
    
    
		_arr = (int*)malloc(sizeof(int) * capacity);
		if (_arr == nullptr)
		{
    
    
			printf("malloc failed");
		}
		_capacity = capacity;
		_size = 0;
		printf("malloc success\n");
	}

	~Stack()
	{
    
    
		if (_arr)
		{
    
    
			free(_arr);
			_capacity = 0;
			_size = 0;
		}
		cout << "~stcak" << endl;
	}

private:
	int* _arr;
	int _capacity;
	int _size;
};


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

inserte la descripción de la imagen aquí

  • Cuando el destructor no está definido explícitamente, el compilador generará automáticamente un destructor de forma predeterminada. No procesa el tipo incorporado y llama a su propio destructor para el tipo personalizado.
  • Si no hay ningún recurso de aplicación en la clase, puede usar directamente el predeterminado generado por el compilador sin escribir el destructor. De lo contrario, provocará una fuga de recursos.

3. Copiar constructor

Al crear un objeto, puede crear un objeto nuevo que sea exactamente igual a un objeto existente. Esta función tiene un único parámetro formal, que es una referencia al objeto de este tipo de clase (comúnmente modificado por const).

característica

  • El constructor de copia es una forma sobrecargada del constructor.
  • El parámetro del constructor de copia es solo uno y debe ser una referencia al objeto de tipo.Usar el método de paso de valor causará llamadas infinitas, porque cada llamada de paso de valor, el valor pasado es una construcción de copia (el valor será temporalmente copiado a los Parámetros de forma pasados ​​en el pasado), lo que provocará una repetición infinita.
class Time
{
    
    
public:
	//全缺省的构造函数
	Time(int hour=0, int minute=0, int second=0)
	{
    
    
		_hour = hour;
		_minute = minute;
		_second = second;
		cout << _hour << ":" << _minute << ":" << _second << endl;
	}

	//复制本应该有两个形参,为什么只写了一个?
	//因为还隐含了一个this指针。
	//使用const是防止传进来的d被修改
	Time(const Time& d)
	{
    
    
		this->_hour = d._hour;
		_minute = d._minute;
		_second = d._second;
	}
private:
	int _hour;
	int _minute;
	int _second;
};


int main()
{
    
    
	Time t1;
	Time t2(t1);  //拷贝构造的语法,获得一个以t1完全一样的t2
	
	return 0;
}

inserte la descripción de la imagen aquí


inserte la descripción de la imagen aquí
Pregunta 1: ¿Por qué no usar el símbolo = para completar la construcción de la copia?
Aunque tanto d2 como d3 pueden completar la tarea de construcción de copias, d1 no es intuitivo ni claro, y es fácil confundirlo con una asignación.
Pregunta 2: Los punteros también pueden completar la tarea de construcción de copias, ¿por qué usar referencias?
Es un poco redundante y problemático pasar la dirección de d4 ¿Qué significa asignar la dirección de d1 a d5? Obviamente, comparando d2, d4 y d5, se encuentra que el uso de referencias está más en línea con la lógica de la gente normal.

  • Si no se define explícitamente, el compilador generará un constructor de copia predeterminado. El objeto constructor de copia predeterminado se copia en orden de bytes de acuerdo con el almacenamiento de la memoria. Este tipo de copia se denomina copia superficial o copia de valor.
class TestCls {
    
    
public:
    TestCls()
    {
    
    
        cout << "TestCls()" <<endl;
        p = new int;
    }
    
    TestCls(const TestCls& d)
    {
    
    
        cout << "TestCls(const TestCls& testCls)" << endl;
        a = d.a;
        //p = testCls.p;   //如果是编译器默认生成的,会直接把指针的地址赋给p
        p = new int;

        *p = *(d.p);      //为拷贝类的p指针分配空间,实现深度拷贝
    }

    ~TestCls()
    {
    
    
        delete p;
        std::cout << "~TestCls()" << std::endl;
    }
private:
    int a;
    int* p;
};

int main()
{
    
    
    TestCls t1;
    TestCls t2(t1);

    return 0;
}

inserte la descripción de la imagen aquí

También se puede ver en el ejemplo anterior que el tipo de función incorporada (operación de asignación simple) generalmente tiene como valor predeterminado una copia profunda, pero cuando se trata de punteros y clases personalizadas, debemos prestar atención al escribir constructores. proceso de copiar.


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

Tenga en cuenta que en este código, las variables miembro de d1 y las variables miembro de _t están una al lado de la otra en la memoria.
inserte la descripción de la imagen aquí

  • Si la aplicación de recursos no está involucrada en la clase, el constructor de copias puede escribirse o no; una vez que la aplicación de recursos está involucrada, el constructor de copias debe escribirse, de lo contrario, es una copia superficial (solo asignada al puntero)

  • Escenarios típicos de construcción de copias
    Use objetos existentes para crear nuevos objetos; el tipo de parámetro de función es un objeto de tipo de clase; el valor de retorno de la función es un objeto de tipo de clase.

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

inserte la descripción de la imagen aquí

Por lo tanto, para mejorar la eficiencia, trate de usar tipos de referencia al pasar parámetros a objetos generales; al regresar, use referencias tanto como sea posible de acuerdo con los escenarios reales.

4. Función de sobrecarga de asignación

sobrecarga del operador

Para mejorar la legibilidad del código, C++ introduce la sobrecarga de operadores, que es una función con un nombre de función especial.
Los tipos incorporados pueden usar operadores directamente para operar y el compilador sabe cómo hacerlo; los tipos personalizados no pueden usar operadores directamente y el compilador no sabe cómo operar, por lo que debe implementar la sobrecarga de operadores usted mismo.

Sintaxis de definición de función:

operador de tipo de retorno operador (lista de parámetros)

  • No se pueden crear nuevos operadores, como: operator@
  • Un operador sobrecargado debe tener un parámetro de tipo de clase
  • Como operador de un tipo incorporado, su significado no se puede cambiar, por ejemplo: el tipo incorporado + no puede cambiar su significado.
  • 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 es el oculto this.
  • .* (raramente visto) :: (calificador de alcance) ​​sizeof ?: (operador ternario) .(símbolo de llamada de función) Tenga en cuenta que los 5 símbolos anteriores no se pueden sobrecargar
class Date
{
    
    
public:

	//构造函数
	Date(int year = 1970, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	// 运算符重载
	//相当于bool operator==(Date* const this, const Date& d);
	//注意:这里左操作数是this,右操作数是d。
	bool operator==(const Date& d)
	{
    
    
		return _year == d._year
			&& _month == d._month
			&& _day == d._day;
	}

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

int main()
{
    
    
	Date d1(2023, 8, 3);
	Date d2(d1);
	Date d3(1, 1, 1);

	//相当于
	//cout<<d1.operator==(&d1, d2)<<endl;
	cout << (d1 == d2) << endl;  //结果为1
	cout << (d1 == d3) << endl;		//结果为0
	return 0;
}

sobrecarga del operador de asignación

Es decir, asignar un valor a un objeto existente (que ya existe) y copiar la construcción (asignar un valor a un nuevo objeto).

  • Tipo de parámetro: const T&, pasar la referencia puede mejorar la eficiencia
  • Tipo de valor de retorno: T&, la referencia de retorno puede mejorar la eficiencia del retorno, y el propósito del valor de retorno es apoyar la asignación continua
  • Comprueba si te asignas un valor
  • Devuelve *esto: Garantiza que no habrá errores en la asignación continua.
// 赋值运算符重载
//参数加引用和const,传参更快,防止参数修改
//返回引用,防止拷贝构造。
	Date& operator=(const Date& d)
	{
    
    
		//防止  d1 = d1;
		if(this != &d)
		{
    
    
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}

		return *this;
	}

	//可以这样调用
	d2 = d1;  // d2.operator=(&d2, d1)   第一个参数是地址,第二个参数是传引用
	d3 = d2 = d1;
  • El operador de asignación solo puede sobrecargarse como una función miembro de la clase y no puede sobrecargarse como una función global
    Debido a que el operador de asignación está sobrecargado, si la definición que el usuario no muestra está en la clase, el compilador generará automáticamente uno en la clase. En este momento, si declara uno más globalmente, entrará en conflicto con el de la clase.

  • Cuando el usuario no muestra la implementación, el compilador generará una sobrecarga de operador de asignación predeterminada por defecto, que se copia byte a byte en forma de valor. La variable miembro de tipo incorporada se asigna directamente y la clase personalizada debe llamar a la autoimplementada.

función miembro constante

Use la función de miembro modificada por const
para referirse a la dirección del artículo

sobrecarga del operador de toma de direcciones

//写在类里面

	//取地址运算符重载
	Date* operator&()
	{
    
    
		return this;
	}
	//通过这样设置可以保证,类外面的成员只能访问地址,不能修改地址。
	const Date* operator&()const
	{
    
    
		return this;
	}

En circunstancias normales, si no escribimos esta función miembro, el compilador la generará automáticamente. Por lo general, no necesitamos escribirlo nosotros mismos.
Entonces, ¿cuándo deberíamos escribir esta función miembro?
Por ejemplo, cuando no desea que otros obtengan la dirección de este tipo de objeto, puede devolver nullptr, de modo que nunca podrá obtener la dirección de este objeto.

Supongo que te gusta

Origin blog.csdn.net/weixin_45153969/article/details/132032354
Recomendado
Clasificación