Notas de estudio de C++ (4) - clases y objetos (medio)

contenido

1. Las 6 funciones miembro predeterminadas de la clase

2. Constructor

2.1 Concepto

2.2 Características

3. Destructor

3.1 Concepto

3.2 Características

4. Copiar constructor

4.1 Concepto

4.2 Características

5. Sobrecarga del operador de asignación

5.1 Sobrecarga del operador

5.1 Sobrecarga del operador de asignación

6. miembros constantes

6.1 Funciones miembro de clases modificadas por const

6.2 Sobrecarga de operadores de direcciones y direcciones constantes

 7. La realización de la clase de fecha (se puede practicar la aplicación de los conocimientos anteriores)


1. Las 6 funciones miembro predeterminadas de la clase

Si una clase no tiene ningún miembro, simplemente se llama clase vacía, ¿y no hay nada en una clase vacía? No, cualquier clase generará automáticamente las siguientes 6 funciones miembro predeterminadas si no las escribimos.

class Date{};

2. Constructor

2.1 Concepto

Concepto: el constructor es una función miembro especial con el mismo nombre que el nombre de la clase , que el compilador llama automáticamente al crear un objeto de tipo de clase.

El uso garantiza que cada miembro de datos tenga un valor inicial adecuado y se llame solo una vez durante la vida útil del objeto .

2.2 Características

Constructor es una función miembro especial. Cabe señalar que aunque el nombre del constructor se llama construcción, cabe señalar que la tarea principal del constructor no abrir espacio para crear objetos , sino inicializar objetos .

Sus características son las siguientes:

  • El nombre de la función es el mismo que el nombre de la clase.
  • sin valor de retorno
  • El compilador de creación de instancias de objetos llama automáticamente al constructor correspondiente
  • El constructor puede estar sobrecargado
class Date
{
public:
	// 构造函数
	// 1.无参构造函数
	Date()
	{

	}
	// 2. 有参构造
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	// 3.全缺省
	//Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
    //
private:
	int _year;
	int _month;
	int _day;
};
void test(){
  Date d1;//调用无参构造函数
  Date d2(2022,1,18);//调用带参的构造函数
}

Nota : si la clase no define explícitamente un constructor, el compilador de C++ generará automáticamente un constructor predeterminado sin argumentos y, una vez que el usuario lo defina explícitamente, el compilador ya no lo generará automáticamente.

Hay tres constructores predeterminados:

  • El constructor generado por el compilador de forma predeterminada (no manejará tipos incorporados ni valores aleatorios; para tipos personalizados, llamarán a su propia inicialización del constructor)
  • constructor predeterminado completo
  • constructor sin argumentos

Cuando no implementamos el constructor, el compilador llamará al constructor generado por el compilador de forma predeterminada.Mirando el siguiente código, ¿puede el constructor predeterminado del compilador implementar la inicialización de las variables miembro? 

class Date
{
public:
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};


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

	return 0;
}

 
 

Los tres valores son todos valores aleatorios. El constructor predeterminado del compilador no inicializa las variables miembro. ¿Significa que esta función es inútil?

Respuesta: C++ divide los tipos en tipos integrados (tipos primitivos) y tipos personalizados. El tipo incorporado es el tipo que ha sido definido por la gramática: como int/char..., el tipo personalizado es el tipo que definimos usando class/struct/union.Si observa el siguiente programa, encontrará que el compilador genera un constructor predeterminado. Su función de miembro predeterminada invoca el miembro de tipo personalizado _t.

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

 Resumen: 1. Los tipos integrados del constructor predeterminado (int/char/double) no manejarán valores aleatorios, para los tipos personalizados (struct/class), llamarán a su propia inicialización del constructor.

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

3. En general, se recomienda escribir un constructor predeterminado completo, que pueda adaptarse a la mayoría de los escenarios.

3. Destructor

3.1 Concepto

Concepto: Al contrario de la función constructora, el destructor no completa la destrucción del objeto, el compilador realiza el trabajo de destrucción del objeto local. Cuando se destruye el objeto, se llamará automáticamente al destructor para completar algún trabajo de limpieza de recursos de la clase.

3.2 Características

Los destructores son funciones miembro especiales

Sus características son las siguientes:

  • El nombre del destructor está precedido por el nombre de la clase con el carácter ~
  • Sin parámetros, sin valor de retorno
  • Una clase tiene un único destructor, si no hay una definición explícita, el sistema genera automáticamente un destructor.
  • Al final del ciclo de declaración del objeto, el sistema de compilación de C++ llamará automáticamente al destructor.
typedef int DataType;
class SeqList
{
public :
SeqList (int capacity = 10)
{
_pData = (DataType*)malloc(capacity * sizeof(DataType));
assert(_pData);
_size = 0;
_capacity = capacity;
}
~SeqList()//析构函数
{
if (_pData)
{
free(_pData ); // 释放堆上的空间
_pData = NULL; // 将指针置为空
_capacity = 0;
_size = 0;
}
}
private :
int* _pData ;
size_t _size;
size_t _capacity;
};
class String
{
public:
String(const char* str = "jack")
{
_str = (char*)malloc(strlen(str) + 1);
strcpy(_str, str);
}
~String()
{
cout << "~String()" << endl;
free(_str);
}
private:
char* _str;
};
class Person
{
private:
String _name;
int _age;
};
int main()
{
Person p;
return 0;
}
//同样对于默认析构函数:对于内置类型成员不做处理,对于自定义类型会去调用他的析构函数

Si no implementamos un destructor, el compilador generará automáticamente un destructor, y el tipo integrado del montón del destructor no se procesará, y se llamará a su destructor para un tipo personalizado, que es el mismo que el constructor predeterminado. generado por el compilador Las funciones son algo similares.

class A
{
public:
	A()
	{
		cout << "A()" << endl;
	}

	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};

class B
{
public:
	B()
	{
		cout << "B()" << endl;
	}

	~B()
	{
		cout << "~B()" << endl;
	}
private:
	int _b;
};

int main()
{
	A a;
	B b;

	return 0;
}
// 因为对象是定义在函数中,函数调用会建立栈帧,
	// 栈帧中的对象构造和析构也要很符合后进先出。
	// 所以这里的顺序是: A()->B()->~B()->~A()

 

4. Copiar constructor

4.1 Concepto

Al crear un objeto, ¿es posible crear un nuevo objeto que sea igual a un objeto?
Constructor: solo hay un único parámetro, que es una referencia a un 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

4.2 Características

  •  El constructor de copia es una forma sobrecargada del constructor;

  • El constructor de copias tiene solo un parámetro y debe pasarse por referencia.Usar el método de pasar por valor causará infinitas llamadas recursivas;

class Date {
public:
	Date(int year = 2020, int month = 2, int day = 20)
	{
		_year = year;
		_month = month;
		_day = day;
	}
  // Date(Date d)
//若使用传值拷贝,则在传参时会先调用拷贝构造函数(因为传值拷贝时需先将原数据拷贝到临时空间再使用临时空间中的数据进行赋值),这样又会出现拷贝,这样就会陷入循环往复过程。   
   {
      _year=d._year;
      _month=d._month;
      _day=d._day;
     }//
	Date(const Date& d)//这里传参时也推荐加 const 因为被拷贝的对象是不应该被修改的
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	void print()
	{
		cout << _year << "-" << _month << "-" << _day  << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
// 总结:
// 1.Date这样的类,需要的就是浅拷贝,那么默认生成的拷贝构造就够用了,不需要自己写
// 2.但是像Stack这样的类,需要的是深拷贝,浅拷贝会导致析构两次,程序就会崩溃等问题

La razón por la que pasar por valor provoca una recurrencia infinita: 

  •  Si no se muestra ninguna definición, el sistema genera un constructor de copia predeterminado . El objeto constructor de copia predeterminado se copia en orden de bytes de acuerdo con el almacenamiento de memoria.Esta copia se denomina copia superficial o copia de valor.

Para un tipo personalizado (Pila, etc.), el compilador aún realizará una copia superficial en él, que copiará la dirección del espacio abierto del objeto original. Las consecuencias de esto son:
1. Dos objetos comparten un espacio, y cuando el análisis se llama El espacio se liberará dos veces durante el constructor
2. La inserción y eliminación de datos en uno de los objetos hará que otro objeto también inserte y elimine datos .
Por lo tanto, para una clase como Stack, la estructura de copia que genera el compilador por defecto es una copia superficial, que no cumple con los requisitos y necesita implementar una copia profunda .

class String
{
public:
	String(const char* str = "jack")
	{
		_str = (char*)malloc(strlen(str) + 1);
		strcpy(_str, str);
	}
	~String()
	{
		cout << "~String()" << endl;
		free(_str);
	}
private:
	char* _str;
};
int main()
{
	String s1("hello");
	String s2(s1);
}

 El código se bloquea directamente, esta es la consecuencia de la copia superficial: la misma memoria se destruye dos veces 

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 , así como 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.
El nombre de la función es: la palabra clave operator seguida del símbolo del operador que debe sobrecargarse .
Prototipo de función: operador de tipo de valor de retorno operador (lista de parámetros)

Aviso:

  • No puede crear nuevos operadores concatenando otros símbolos: como operator@
  • Un operador sobrecargado debe tener un operando de tipo de clase o tipo de enumeración
  • Operadores utilizados para tipos incorporados, cuyo significado no se puede cambiar, por ejemplo: entero incorporado + 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
  • * , :: , sizeof , ?: , . Tenga en cuenta que los cinco operadores anteriores no se pueden sobrecargar. Esto ocurre a menudo en preguntas de opción múltiple en exámenes escritos.
     

Ejemplo de código: '>'

class Date
{
public:
    Date(int year = 2022, int month = 2, int day = 20)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    
    // bool operator>(Date* this, const Date& d2)
 
    bool operator>(const Date& d2)
    {
        
    if (_year > d2._year)
	{
		return true;
	}
	else  if(_year == d2._year && _month > d2._month)
	{
		return true;
	}
	else if (_year == d2._year && _month == d2._month && _day > d2._day)
	{
		return true;
	}
	else {
		return false;
	}
    }
private:
    int _year;
    int _month;
    int _day;
};
void Test ()
{
    Date d1(2021, 1, 1);
    Date d2(2021, 1, 2);
    cout<<(d1 >d2)<<endl;// <<优先级高于==,这里需加括号
    //等于d1.operator>(d2)
}

5.1 Sobrecarga del operador de asignación

El operador de asignación tiene principalmente los siguientes cinco puntos:

  • Tipo de parámetro
  • valor de retorno
  •  Comprueba si te asignas un valor
  •  devolver *esto
  • Si una clase no define explícitamente una sobrecarga del operador de asignación, el compilador también generará uno para completar la copia del valor endian del objeto.
class Date
{
public:
	Date(int year = 2000, int month = 9, int day = 18)
	{
		_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;
	}
 
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
 
private:
	int _year;
	int _month;
	int _day;
};
 
 
int main()
{
	Date d1;
    Date d2(2022,2,20);
    d1=d2;//这里d1调用的编译器生成operator=完成拷贝,d2和d1的值也是一样的
 
	Date d3;
	d3 = d2 = d1;//连续赋值
	d2.Print();
	d3.Print();
 
	Date d4 = d1; // 拷贝构造
	return 0;     // 拷贝构造:拿一个已经存在的对象去拷贝初始化另一个要创建的对象
}                 // 赋值重载:两个已经存在的对象拷贝
            

Diferencia entre la sobrecarga del operador de asignación y el constructor de copias

Lo mismo: cuando no hay una definición explícita, el compilador generará una por defecto.Para los tipos incorporados, realizará una copia superficial en orden de bytes, y para los tipos personalizados, llamará a su propio constructor de copias u operator=.
Diferencia: copiar construcción: inicializar un objeto que está a punto de crearse con un objeto existente .
           Sobrecarga de asignaciones: Dos copias de objetos existentes .

6. miembros constantes

6.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 se puede modificar en la función miembro.

class Date
{
public :
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可以调用const;const不能调用非const(权限不能放大)
const Date d2;//建议成员函数中,不需要改变成员变量,都加上const。
d2.Display ();
}

6.2 Sobrecarga de operadores de direcciones y direcciones constantes

Por lo general, estos dos operadores no necesitan sobrecargarse. Puede usar la sobrecarga de la búsqueda de direcciones predeterminada generada por el compilador. Solo en casos especiales, se requiere sobrecarga, como permitir que otros obtengan el contenido especificado .

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

 7. La realización de la clase de fecha (se puede practicar la aplicación de los conocimientos anteriores)

Fecha.h

Date.h
#pragma once
#include<iostream>
using namespace std;
class Date {
public:
	Date(int year = 1, int month = 1, int day = 1);
	void print();
	int GetMonthDay(int year,int  month);//获取每个月的天数
	bool operator>(const Date& d);//比较第一个数是否比第二个数大
	bool operator<(const Date& d);//以下同理
	bool operator>=(const Date& d);
	bool operator<=(const Date& d);
	bool operator==(const Date& d);
	bool operator!=(const Date& d);
	//d1+=100
	Date& operator+=(int day);//在一个数本身加上天数,本身改变
	//d1+100
	Date operator+(int day);//一个数加上给定的天数是哪天?本身不改变
	//d1-=100
	Date& operator-=(int day);
    //d1-100
	Date operator-(int day);
	Date operator--(int);
	Date& operator--();
	Date operator++(int);//后置++,为了区分前置++与其构成函数重载,需要加int(规定,不允许是其他类型)
	Date& operator++();//前置++
	int operator-(const Date& d);//两个日期相减
	void PrintWeekDay() const;//打印今天是星期几

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

Fecha.c 

#include"Date.h"
Date::Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;
	if(!(_year >= 0 && (_month > 0 && _month < 13) && (_day>0 && _day <= GetMonthDay(year, month))))
	{
		cout << "非法日期" << endl;
	}
}
void Date::print()
{
	cout << _year << "-" << _month << "-" << _day << endl; 
}
int Date::GetMonthDay(int year, int month)
{
	int MonthDayArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
	int day = MonthDayArray[month];
	if (month == 2 && (year % 4 == 0 && year % 400 != 0) || year % 100 == 0)
	{
		day += 1;
	}
	return day;
}
bool Date::operator>(const Date& d)
{
	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;
	}
}
bool Date::operator==(const Date& d)
{
	if (_year == d._year && _month == d._month && _day == d._day)
	{
		return true;
	}
	else {
		return false;
	}
}
bool Date::operator>=(const Date& d)
{
	return (*this > d || *this == d);
}
bool Date::operator<(const Date& d)
{
	return !(*this >= d);
}
bool Date::operator<=(const Date& d)
{
	return !(*this > d);
}
bool Date::operator!=(const Date& d)
{
	return !(*this == d);
}
Date& Date::operator+=(int day)
{
	//2021-12-28 +=300
	_day += day;
	while ( _day> GetMonthDay(_year, _month))
	{
		_month += 1;
		_day -= GetMonthDay(_year, _month);
		while (_month == 13)
		{
			_year += 1;
			_month = 1;
		}
	}
	return *this;
}
Date Date::operator+(int day)
{
	Date ret(*this);
	ret += day;
	return ret;
}
Date& Date::operator-=(int day)
{
	//2021-1-20 -45
	_day -= day;
   while(_day <= 0)
	{
		_month--;
		if (_month == 0)
		{
			_year--;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);
	}
   return *this;
}
Date Date::operator - (int day)
{
	Date ret(*this);
	ret -= day;
	return ret;
}
//前置++
Date& Date::operator++()
{
	*this += 1;
	return *this;
}
Date Date::operator++(int)
{
	Date ret(*this);
	*this += 1;
	return ret;
}
Date& Date::operator--()
{
	*this -= 1;
	return *this;
}
Date Date::operator--(int)
{
	Date ret(*this);
	*this -= 1;
	return ret;
}
int Date::operator-(const Date& d)
{
	Date max = *this;
     Date min = d;
	 int flag = 1;
	 if (*this < d)
	 {
		 max = d;
		 min = *this;
		 flag = -1;
	 }
	 int count = 0;
	 while (min != max)
	 {
		 min++;
		 count++;
	 }
	 return flag * count;
}
void Date::PrintWeekDay() const
{
	const char* arr[] = { "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期天" };
	Date start(1900, 1, 1);
	int count = *this - start;
	cout << arr[count % 7] << endl;
}

 Lo anterior es parte del contenido de las clases y los objetos. Si hay deficiencias o una mejor comprensión del código, deje un mensaje en el área de comentarios para discutir y progresar juntos.

 

Supongo que te gusta

Origin blog.csdn.net/m0_58367586/article/details/123019812
Recomendado
Clasificación