Clases y objetos de C++ (medio)

contenido

6 funciones miembro predeterminadas de una clase

1. Constructor

2. Función destructora

Tercero, el constructor de copias.

4. Sobrecarga de operadores de asignación

4.1 Sobrecarga de operadores

4.2 Sobrecarga de operadores de asignación

Cinco, miembros constantes

5.1 Funciones miembro de clases modificadas por const

Seis, sobrecarga del operador de dirección y dirección constante


6 funciones miembro predeterminadas de una clase

  Si una clase no tiene miembros, se denomina clase vacía. ¿Nada en la clase vacía? No, cualquier clase generará automáticamente las siguientes 6 funciones miembro predeterminadas si no las escribimos
class Date {}; //空类

Nota: Estas son 6 funciones de miembros especiales

1. Constructor

Concepto: Aunque el nombre del constructor se llama construcción, no abre espacio para crear objetos, sino que inicializa objetos.

Las características son las siguientes:

1. El nombre de la función y el nombre de la clase son los mismos

2, sin valor de retorno, no nulo

3. Cuando se crea una instancia del objeto, se llama automáticamente al constructor correspondiente

4. Los constructores pueden sobrecargarse

class Date
{
public:
	Date()//1、无参构造函数
	{
		_year = 2000;
		_mouth = 2;
		_day = 5;
	}
	Date(int year, int mouth,int day)//2、带参构造函数(可以是缺省的参数)
	{
		_year = year;
		_mouth = mouth;
		_day = day;
	}
//以上两个函数名字相同,但是参数不同,构成了函数重载

private:
	int _year;
	int _mouth;
	int _day;
};
int main()
{
	Date d1;//这里就是具体的对象实例化
	Date d2(2022,2,12);
	return 0;
}

5. Consejos: C++ divide nuestros tipos en dos categorías

El primer tipo de tipo incorporado: int/doble/tipo de puntero/matriz de tipos incorporados, etc.

El segundo tipo de tipo personalizado: el tipo definido por struct/class

Reglas especiales de manejo:

① No escribimos el constructor predeterminado generado por el compilador y no tratamos con tipos incorporados

②Para una variable miembro de un tipo personalizado, se llamará a su constructor predeterminado (es decir, una función miembro que se puede llamar sin parámetros) para la inicialización

③ Si no hay un constructor predeterminado, el compilador informará un error

Hay tres tipos de constructores predeterminados (que se pueden llamar sin parámetros): todos predeterminados, sin parámetros, no escribimos el predeterminado generado por el compilador

por ejemplo:

//A类
class A
{
public:
	A()
	{
		cout << "调用A()" << endl;
		_a = 0;
	}
	//如果是下面的带参的函数,就不是默认构造函数,编译器就会报错
	/*A(int a)
	{
		cout << "调用A()" << endl;
		_a = 0;
	}*/
private:
	int _a;
};

class Date
{
public:
	//这里不写
private:
	int _year;
	int _mouth;
	int _day;
	A _aa;//这里对于自定义类型,会去调用A类型的默认构造函数
};
int main()
{
	Date d1;
	return 0;
}

resultado de la operación: 

Descripción: no se realiza ningún procesamiento para los tipos integrados. Para la variable miembro _aa en el tipo A personalizado, regrese y llame al constructor predeterminado de la clase A. Si no hay un constructor predeterminado, se informará un error.

Abra la ventana del reloj y observe:

6. Si no hay un constructor definido explícitamente en la clase, el compilador de C++ generará automáticamente un constructor predeterminado sin parámetros. Una vez que el usuario lo defina explícitamente, el compilador ya no lo generará.

P.ej: 

class Date
{
public:
	//这里我们不手动写,编译器会默认生成构造函数帮我们完成初始化操作
private:
	int _year;
	int _mouth;
	int _day;
};
int main()
{
	Date d1;
	return 0;
}

Abrimos la ventana de monitoreo en este momento y podemos observar que el valor aleatorio inicializado en el objeto también confirma nuestra conclusión anterior:

requiere atención:

class Date
{
public:
	Date()//1、无参构造函数
	{
		_year = 2000;
		_mouth = 2;
		_day = 5;
	}
	Date(int year=2000, int mouth=2, int day=5)//2、带参构造函数
	{
		_year = year;
		_mouth = mouth;
		_day = day;
	}

private:
	int _year;
	int _mouth;
	int _day;
};
int main()
{
	Date d1;//这里就会报错
	//1、语法上无参和全缺省的可以同时存在
	//2、但是如果存在无参调用,就会存在歧义(编译器报错)
	Date d2(2022, 2, 12);
	return 0;
}

2. Función destructora

Concepto: Al contrario de la función del constructor, el destructor no completa la destrucción del objeto, y el compilador realiza la destrucción del objeto local. Cuando se destruye el objeto, se llamará automáticamente al destructor para completar la limpieza de algunos recursos en el objeto.

Por ejemplo: algunos punteros abiertos por nuestra memoria dinámica, debemos liberarlos, lo que se denomina limpieza de recursos

Características :

1. El nombre del destructor y el nombre de la clase son iguales, y se agrega un ~ delante

2, sin parámetros, sin valor de retorno

3. Una clase tiene un único destructor, si no está definido explícitamente, el sistema generará automáticamente un destructor por defecto

4. Al final del ciclo de vida del objeto, el sistema de compilación de C++ llamará automáticamente al destructor

class Date
{
public:
	~Date()
	{//Date类没有资源需要清理,所以Date不实现析构函数也是可以的
		cout << "调用 ~Date()" << endl;
	}
private:
	int _year;
	int _mouth;
	int _day;
};
int main()
{
	Date d1;
	return 0;
}

Pero necesitamos limpiar los recursos en algunos casos: (por ejemplo , los recursos malloc deben procesarse )

class stack
{
public:
	stack(int capacity = 4)
	{
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == nullptr)
		{
			cout << "malloc fail" << endl;
			exit(-1);
		}
	}
	~stack()
	{
		free(_a);
		_a = nullptr;
        _top = _capacity = 0;
	}
private:
	int* _a;
	size_t _top;
	size_t _capacity;
	
};
int main()
{
	stack s1;
	stack s2(10);
	return 0;
}

5 、 consejos:

Si no lo escribimos, el destructor generado por defecto es similar al constructor

①Para las variables miembro de tipos incorporados, no se realiza ningún procesamiento

②Para una variable miembro de un tipo personalizado, su destructor se llamará

Tomemos una pregunta aquí, usando dos pilas para implementar la cola, en la que el constructor y el destructor generados por defecto llamarán a su constructor y destructor:

class stack
{
public:
	stack(int capacity = 4)
	{
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == nullptr)
		{
			cout << "malloc fail" << endl;
			exit(-1);
		}
	}
	~stack()
	{
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
private:
	int* _a;
	size_t _top;
	size_t _capacity;
	
};

//两个栈实现一个队列
class MyQueue
{
public:
	// 默认生成构造函数和析构函数会对自定义类型成员调用他的构造和析构
	void push(int x)
	{}
private:
	stack pushST;
	stack popST;
};
int main()
{
	MyQueue q;
	return 0;
}

Tercero, el constructor de copias.

Concepto: Como su nombre indica, consiste en copiar y asignar un objeto que sea exactamente igual a sí mismo. El constructor de copia tiene un solo parámetro , y el parámetro modificado es una referencia al objeto de este tipo de clase (generalmente modificado const) , que el compilador llama automáticamente cuando se crea un nuevo objeto con un objeto de tipo de clase existente.

#include <iostream>
using namespace std;
class Date
{
public:
	Date(int year = 1, 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;
	int _month;
	int _day;
};
int main()
{
	Date d1(2000, 1, 1);
	Date d2(d1); // 用已存在的对象d1创建对象d2
//用一个同类型对象初始化就是拷贝构造,将d1的内容复制给d2

	return 0;
}

Características :

1. El constructor de copias es una forma sobrecargada del constructor.

2. Solo hay un parámetro de función en la construcción de copia , y el parámetro debe pasarse por referencia.El método de pasar por valor causará infinitas llamadas recursivas.

class Date
{
public:
	Date(int year = 1, 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;
	}
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	Date d2(d1);
	//将d1的内容复制给d2
	//用同一个类型的对象去初始化,就是拷贝构造
	return 0;
}

¿Por qué se pasa por referencia aquí?

date es un alias de d1 para evitar la repetición infinita causada por la copia al pasar valores

¿Por qué se recomienda agregar const aquí?

Aquí queremos copiar un objeto que es el mismo que el objeto original, queremos mantener el contenido del objeto original sin cambios, así que dejemos que el objeto original tenga atributos constantes

3. Si no se define explícitamente, el sistema genera un constructor de copia predeterminado

Constructor de copia generado por defecto:

1) Variables miembro de tipos incorporados: se completará la copia en orden de bytes (copia superficial)

2) Variables miembro de tipos personalizados: se llamará a su constructor de copias

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//我们这里不写拷贝构造函数,编译器会自动生成
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	Date d2(d1);
	//将d1的内容复制给d2
	return 0;
}

Abra la ventana del reloj para observar dos objetos:

Se puede encontrar que se produce una copia superficial (copia de valor) y se copia un d2 con exactamente el mismo contenido.

No definimos un constructor de copia aquí, pero el constructor de copia generado automáticamente por el compilador aún completa la tarea de copia de objetos.

4. El constructor de copia generado automáticamente por el compilador no puede completar una copia profunda

A veces, si no lo escribimos, el constructor de copia generado automáticamente por el compilador es suficiente, pero el constructor de copia generado automáticamente por el compilador no puede completar una copia profunda.Por ejemplo, cuando necesitamos copiar y construir una pila, por el valor predeterminado El constructor de copia generado no se puede utilizar

class Stack
{
public:
	Stack(int capacity = 4)
	{
		_ps = (int*)malloc(sizeof(int)* capacity);
		_size = 0;
		_capacity = capacity;
	}
	void Print()
	{
		cout << _ps << endl;// 打印栈空间地址
	}
private:
	int* _ps;
	int _size;
	int _capacity;
};
int main()
{
	Stack stack1;
	stack1.Print();// 打印stack1栈空间的地址
	Stack stack2(stack1);// 用已存在的对象stack1创建对象stack2
	stack2.Print();// 打印s2栈空间的地址
	return 0;
}

Análisis: si nuestra estructura de copia sigue siendo una copia superficial en orden de bytes, la copia copiará la dirección de la pila abierta 1 a la pila 2, de modo que los dos punteros apuntarán a la misma área del montón . la operación de las dos pilas se afectará entre sí. Cuando se destruye la pila 2, la pila 1 también destruye la misma área, lo que no está permitido y hará que el programa se bloquee . Es correcto que nosotros mismos escribamos la estructura de copia de la pila. Cuando se ejecuta el programa, la pila stack2 se destruirá primero. En este momento, se liberará el espacio de la pila, y luego la pila stack1 también se destruirá, y se liberará nuevamente el espacio. .

4. Sobrecarga de operadores de asignación

4.1 Sobrecarga de operadores

De forma predeterminada, C++ no admite el uso de operadores para objetos de tipos personalizados. Para permitirles operar (como el tamaño), se propone la sobrecarga de operadores. La sobrecarga de operadores es una función con un nombre de función especial . El propósito es Permitir que los tipos personalizados puedan Me gusta Los tipos incorporados se pueden manipular directamente usando operadores

Características :

1. Tiene su tipo de valor de retorno, nombre de función y lista de parámetros.Su tipo de valor de retorno y lista de parámetros son similares a las funciones ordinarias.

2. El nombre de la función es: la palabra clave operator seguida del símbolo del operador que debe sobrecargarse

3. Prototipo de función: operador de tipo de valor de retorno operador (lista de parámetros)

4. Parámetros: el operador tiene varios operandos y tiene varios parámetros

Nota :

1) No puede crear nuevos operadores conectando otros símbolos: como operator@

2) Un operador sobrecargado debe tener un operando de un tipo de clase o un tipo de enumeración

3) El significado de los operadores para tipos integrados no se puede cambiar. Por ejemplo, el entero integrado + no puede cambiar su significado.

Cuando una función sobrecargada es un miembro de clase, sus parámetros formales parecen ser uno menos que el número de operandos. El operador de la función miembro tiene un parámetro predeterminado this, que se limita al primer parámetro

4) .* , :: , sizeof , ?: , . Tenga en cuenta que los 5 operadores anteriores no se pueden sobrecargar. Esto ocurre a menudo en preguntas de opción múltiple en exámenes escritos.

Queremos implementar la sobrecarga de operadores, primero echemos un vistazo al operador global>

#include<iostream>
using namespace std;
class Date
{
public:
	Date(int year = 1, 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)
{
	if (d1._year > d2._year)
		return true;
	else if (d1._year == d2._year && d1._month > d2._month)
		return true;
	else if (d1._year == d2._year && d1._month == d2._month && d1._day > d2._day)
		return  true;
	else
		return false;
}

int main()
{
	Date d1(2022,2,1);
	Date d2(d1);
	Date d3(2000, 1, 1);
	
	cout <<(d1 > d3 )<< endl;
	cout << (operator>(d1,d3)) << endl;
	//将d1的内容复制给d2
	return 0;
}

Aquí, se encontrará que la sobrecarga del operador en un global requiere las variables miembro públicas, lo que destruirá la encapsulación del programa.

De hecho, podemos escribir sobrecarga en la clase:

#include<iostream>
using namespace std;
//重载成员函数
class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	bool operator>(const Date& d)//成员函数默认有一个隐藏的this指针
	{
		if (_year > d._year)
			return true;
		else if (_year == d._year && _month > d._month)
			return true;
		else if (_year == d._year && _month == d._month && _day > d._day)
			return  true;
		else
			return false;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2022, 2, 1);
	Date d2(d1);
	Date d3(2000, 1, 1);
	cout << (d1>d3) << endl;//这样写会被编译器转化为下面这行
	cout<< d1.operator>(d3)<<endl;
	return 0;
}

Las funciones miembro tendrán un puntero este oculto

4.2 Sobrecarga de operadores de asignación

sabemos:

Un objeto existente para inicializar un objeto que crea inmediatamente una ciudad - copiar construcción

Operación de asignación entre dos objetos existentes - copia de asignación

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date& operator=(const Date& d)//成员函数默认有一个隐藏的this指针,加const保持赋值的那个对象不变,具有常属性
	{
		if (this != &d)//预防自己给自己拷贝,没有这个必要
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;//引用返回,减少值拷贝
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2022, 2, 1);
	Date d2(d1);
	Date d3(2000, 1, 1);

	d1 = d3;
//将d3的内容拷贝给d1,将d1的内容给覆盖了
	return 0;
}

Las principales características del operador de asignación :

1. El tipo de parámetro es una referencia y se modifica con const

 El operando correcto es un objeto existente, generalmente no lo cambiamos, así que agregue una modificación const

2. Utilice la referencia para devolver, con el fin de reducir las copias múltiples

Si asignamos d2=d1, no es necesario devolver ningún valor, pero si asignamos d3=d2=d1 continuamente, el valor devuelto a d2 debe copiarse y luego asignarse a d3, de modo que una copia de finalmente se realiza la llamada de devolución. Esto forma copias múltiples. Para evitar copias innecesarias, utilizamos devolución de referencia

3. Comprueba si te asignas un valor

Si no hay una asignación innecesaria como d1=d1, podemos evitar operaciones de asignación innecesarias

4, devolver * esto

Fuera del alcance *esto todavía existe, *este es el objeto d (el objeto no se destruye), use la referencia para volver a reducir la copia

5. Si no hay una definición explícita de sobrecarga del operador de asignación en una clase, el compilador generará uno por defecto, que completará la copia del orden de bytes del objeto. 

El compilador genera sobrecargas de asignación de forma predeterminada y el constructor de copia raíz hace algo similar

1) Para los tipos incorporados, se realiza una copia superficial en endianness

2) Para tipos personalizados, llamará a su propio operador de sobrecarga

Cinco, miembros constantes

5.1 Funciones miembro de clases modificadas por const

La función miembro de clase modificada por const se llama función miembro const, y la función miembro de clase modificada por const en realidad modifica el puntero implícito this de la función miembro, lo que indica que ningún miembro de la clase puede modificarse en la función miembro.

class Date
{ 
public :
 void Display ()
 {
 cout<<"Display ()" <<endl;
 cout<<"year:" <<_year<< endl;
 cout<<"month:" <<_month<< endl;
 cout<<"day:" <<_day<< endl<<endl ;
 }
 void Display () const
 {
 cout<<"Display () 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 ;//普通对象
 d1.Display ();
 
 const Date d2;//const修饰对象
 d2.Display ();
}

Conclusión : es bueno agregar const a las funciones miembro, se recomienda agregar todo lo que se pueda agregar. De esta forma, se pueden llamar tanto objetos ordinarios como objetos modificados por const, pero si desea modificar funciones miembro de variables miembro, no puede agregar const

Seis, sobrecarga del operador de dirección y dirección constante

Estas dos funciones miembro predeterminadas generalmente no necesitan ser redefinidas, y el compilador generará

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

En general, estos dos operadores no necesitan sobrecargarse. Puede usar la sobrecarga de la dirección predeterminada generada por el compilador. Solo en casos especiales, se requiere sobrecarga, por ejemplo, si desea que otros obtengan el contenido especificado.

¡Gracias por ver!

Supongo que te gusta

Origin blog.csdn.net/weixin_57675461/article/details/122991851
Recomendado
Clasificación