[C++] Clases y objetos (abajo) -- lista de inicialización -- miembros estáticos -- amigos -- clases internas, este artículo le dará una comprensión profunda.

Tabla de contenido

1. Hablar de nuevo sobre el constructor.

1.1 Asignación del cuerpo del constructor

1.2 Lista de inicialización

1.2.1 El significado de la lista de inicialización

1.3 palabra clave explícita

2. Miembros estáticos

2.1 Introducción al problema

2.2 Características

3, tomomoto

3.1 Funciones de amigo

3.2 Clase amiga

4. Clase interna


1. Hablar de nuevo sobre el constructor.

1.1 Asignación del cuerpo del constructor

Al crear un objeto, el compilador llama al constructor para dar a cada variable miembro en el objeto un valor inicial apropiado.

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

Intentamos dar todos los valores predeterminados al constructor, de modo que incluso si queremos crear una instancia de un objeto sin parámetros, esta función se encargará de ello.

Aunque el objeto ya tiene un valor inicial después de llamar al constructor anterior, no se puede llamar la inicialización de las variables miembro en el objeto. La declaración en el cuerpo del constructor solo se puede llamar la asignación de valor inicial , no el valor inicial. inicialización . Porque la inicialización solo se puede inicializar una vez, y el cuerpo del constructor se puede asignar varias veces.

1.2 Lista de inicialización

Lista de inicialización: comienza con dos puntos , seguido de una lista de miembros de datos separados por comas , cada " variable de miembro " seguida de un valor inicial o una expresión entre paréntesis.

Sigamos mirando la clase Fecha:

class Date
{
public:

	// 初始化列表
	Date(int year, int month, int day)
		: _year(year)
		, _month(month)
		, _day(day)
	{}

private:
	// 成员变量声明
	int _year;
	int _month;
	int _day;
};

El código anterior es la implementación de la lista de inicialización.

1.2.1 El significado de la lista de inicialización

La lista de inicializadores es donde se definen las variables miembro de un objeto.

Algunas variables solo se pueden inicializar donde están definidas:

a. variable modificada const; b. variable de referencia; c. tipo personalizado (sin constructor predeterminado).

class A
{
public:
	A(int a)
		:_a(a)
	{}

private:
	int _a;
};
class Date
{
public:
	// 初始化列表
	Date(int year, int month, int day, int& i)
		: _year(year)
		, _month(month)
		, _day(day)
		, _x(1)
		, _refi(i)
		, _a(1)
	{}

private:
	// 成员变量的声明
	int _year;
	int _month;
	int _day;

	// 定义时必须初始化
	const int _x;
	int& _refi;
	A _a;
};

El valor predeterminado de la variable miembro se le da a la lista de inicialización; si la lista de inicialización no define explícitamente el tipo incorporado, el compilador definirá la definición predeterminada y le dará un valor aleatorio; le dará el valor predeterminado e inicializará el valor a usar el valor inicializado.

Para las variables miembro que deben inicializarse cuando se definen, veamos la situación de asignar valores iniciales en el cuerpo de la función:

Aquí es donde las cosas van mal, por lo que las listas de inicializadores son excelentes para esto.

Aviso:

1. Cada variable miembro solo puede aparecer una vez en la lista de inicialización (la inicialización solo se puede inicializar una vez)
2. La clase contiene los siguientes miembros, que deben colocarse en la lista de inicialización para la inicialización:
a. Variables miembro de referencia
b. miembro constante variables
C. Miembros de tipo personalizado (y la clase no tiene un constructor predeterminado)
3. Intente usar la inicialización de la lista de inicialización, porque ya sea que use la lista de inicialización o no, para las variables de miembro de tipo personalizado, primero debe usar la inicialización de la lista de inicialización.
4. El orden en que se declaran las variables miembro en la clase es el orden en que se inicializan en la lista de inicialización , independientemente de su orden en la lista de inicialización

Después de la inicialización, también se le puede asignar un valor en el cuerpo del constructor.

1.3 palabra clave explícita

Los constructores no solo pueden construir e inicializar objetos, sino que también tienen la función de conversión de tipos para un solo parámetro o un constructor con valores predeterminados, excepto el primer parámetro, que no tiene un valor predeterminado.

class Date
{
public:
	Date(int year)
		: _year(year)
	{}

private:
	int _year;
};
int main()
{
	Date d1(2023);

	// 隐式类型转换
	Date d2 = 2023;// 2023调用构造函数生成临时对象,再用临时对象去拷贝构造d2	

	return 0;
}

2023 es de tipo int, pero no se reporta error al asignarlo a d2 de tipo Date, porque 2023 tiene una conversión de tipo implícita. Si no queremos que ocurra la conversión de tipo ermitaño, agregue un modificador explícito antes del constructor.

Decorar el constructor con explícito prohibirá la conversión implícita del constructor.

Extensión:

En C++11, se permite la conversión implícita de tipos de varios parámetros.

class Date
{
public:
	Date(int year, int month, int day)
		: _year(year)
		, _month(month)
		, _day(day)
	{}

private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	// 隐式类型转换
	Date d = { 2023, 8, 7 };// 多参数隐式类型转换要用{}将多个参数括起来	
	const Date& d1 = { 2023, 1, 1 };// 转换时生成的临时变量具有常性,因此需要const修饰
    
	return 0;
}

La conversión de tipo implícito de varios parámetros necesita usar {} para incluir varios parámetros.

2. Miembros estáticos

2.1 Introducción al problema

¿Contemos cuántos de Clase A se han creado y cuántos se están utilizando?

// 创建了多少个对象
int n = 0;
// 正在使用的有多少个对象
int m = 0;
class A
{
public:
	A() 
	{ 
		++n;
		++m;
	}
	A(const A & t) 
	{
		++n;
		++m;
	}
	~A() 
	{
		--m;
	}
};
A func(A aa)
{
	return aa;
}
int main()
{
	A a1;
	A a2;
	cout << n << " " << m << endl;

	A();// 匿名对象,在本行创建并在本行销毁
	cout << n << " " << m << endl;

	func(a1);
	cout << n << " " << m << endl;

	return 0;
}

resultado de la operación:

Podemos ver que las variables globales se pueden lograr, pero las variables globales no son seguras (las variables globales se pueden modificar globalmente) y no conducen a la encapsulación , por lo que no usamos este método.

Cambiemos el método para lograr esto:

Ponemos el contador en la clase y lo definimos como privado, pero esto aún no es suficiente. Cada vez que creamos un objeto, habrá una n y una m. Nuestro propósito es que el objeto sea público, por lo que usamos la modificación estática, que resuelve el problema Estas dos preguntas.

class A
{
public:
	A() 
	{ 
		++n;
		++m;
	}
	A(const A & t) 
	{
		++n;
		++m;
	}
	~A() 
	{
		--m;
	}

//private:
	// 创建了多少个对象
	static int n;
	// 正在使用的有多少个对象
	static int m;
};
int A::n = 0;
int A::m = 0;

A func(A aa)
{
	return aa;
}
int main()
{
	cout << "sizeof(A):" << sizeof(A) << endl;

	A a1;
	A a2;
	cout << A::n << " " << A::m << endl;

	A();// 匿名对象,在本行创建并在本行销毁
	cout << a1.n << " " << a2.m << endl;

	func(a1);
	cout << a1.n << " " << a2.m << endl;

	return 0;
}

Después de la modificación estática, n y m son variables miembro estáticas , no de un objeto determinado, sino de todos los objetos, por lo que no existen en el objeto, sino en el área estática (sizeof se calcula como 1, que es un tipo vacío ).

En segundo lugar, la clase es solo la declaración de las variables miembro. Las variables miembro estáticas deben definirse fuera de la clase y no es necesario agregar estáticas al definirlas.
Si definimos miembros estáticos como privados, debemos proporcionar la interfaz Get para acceder a n, m

class A
{
public:
	A() 
	{ 
		++n;
		++m;
	}
	A(const A & t) 
	{
		++n;
		++m;
	}
	~A() 
	{
		--m;
	}

	int GetN()
	{
		return n;
	}
	int GetM()
	{
		return m;
	}

private:
	// 创建了多少个对象
	static int n;
	// 正在使用的有多少个对象
	static int m;
};
int A::n = 0;
int A::m = 0;

A func(A aa)
{
	return aa;
}
int main()
{
	A a1;
	A a2;
	cout << a1.GetN() << " " << a1.GetM() << endl;

	A();// 匿名对象,在本行创建并在本行销毁
	cout << a1.GetN() << " " << a1.GetM() << endl;

	func(a1);
	cout << a1.GetN() << " " << a1.GetM() << endl;


	return 0;
}

Si solo creamos dos objetos anónimos y queremos ver n y m, no se puede llamar a ningún objeto. Si creamos un objeto para llamar, esto afectará a n y m, por lo que también cambiamos la función a estática, que solo necesita agregue el campo de clase para acceder a él.

class A
{
public:
	A() 
	{ 
		++n;
		++m;
	}
	A(const A & t) 
	{
		++n;
		++m;
	}
	~A() 
	{
		--m;
	}

	// 静态成员函数的特点:没有this指针
	static int GetN()
	{
		return n;
	}
	static int GetM()
	{
		return m;
	}

private:
	// 静态成员变量属于所有A对象,属于整个类
	// 创建了多少个对象
	static int n;
	// 正在使用的有多少个对象
	static int m;
};
int A::n = 0;
int A::m = 0;

A func(A aa)
{
	return aa;
}
int main()
{
	A a1;
	A a2;
	cout << A::GetN() << " " << A::GetM() << endl;

	A();// 匿名对象,在本行创建并在本行销毁
	cout << A::GetN() << " " << A::GetM() << endl;

	func(a1);
	cout << A::GetN() << " " << A::GetM() << endl;

	return 0;
}

Nota: No se puede acceder a las funciones miembro no estáticas en las funciones miembro estáticas porque no existe este puntero . Generalmente, las variables miembro modificadas estáticamente se usan junto con funciones miembro modificadas estáticamente.

2.2 Características

1. Los miembros estáticos son compartidos por todos los objetos de clase , no pertenecen a un objeto específico y se almacenan en el área estática 2.
Las variables de miembros estáticos deben definirse fuera de la clase , y la palabra clave estática no se agrega al definir, solo se declara en la
clase 3. Se puede acceder nombre de clase::miembro estático u objeto.miembro estático
4. Las funciones de miembro estático no tienen este puntero oculto y no pueden acceder a ningún miembro no estático
5. Los miembros estáticos también son miembros de clases , sujeto a restricciones de acceso público, protegido y privado límite de caracteres

3, tomomoto

Amigo se divide en función de amigo y clase de amigo.

Los amigos brindan una forma de salir del encapsulamiento y, a veces, conveniencia. Pero los amigos destruyen la encapsulación, por lo que los amigos no deben usarse con más frecuencia.

3.1 Funciones de amigo

Pregunta: En el artículo anterior, implementamos todos los operadores de la clase Date que deben sobrecargarse.Si necesitamos usar cout para imprimir objetos de tipo personalizado, debemos sobrecargar el signo de intercalación de flujo.

El formato impreso es cout << " " << endl;

Entonces el cursor de flujo de sobrecarga no puede estar dentro de la clase.El primer parámetro en la clase es el puntero implícito this, que competirá con cout por la posición del primer parámetro , por lo que solo podemos definirlo fuera de la clase.
Si lo ponemos afuera, no podemos acceder a las variables de los miembros de la clase, por lo que es el turno de nuestro amigo para jugar aquí.

Una función amigo puede acceder directamente a los miembros privados de la clase . Es una función ordinaria definida fuera de la clase y no pertenece a ninguna clase, pero debe declararse dentro de la clase, y la palabra clave amigo debe agregarse al declarar .

class Date
{
	// 友元
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);

public:
	Date(int year = 1, 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;
	return out;
}
istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}

int main()
{
	Date d1;
	cin >> d1;
	cout << d1;
}

resultado de la operación:

ilustrar:

Las funciones amigas pueden acceder a miembros privados y protegidos de la clase, pero no a las funciones miembro de la clase
. Las funciones amigas no se pueden modificar con const (las funciones amigas no tienen este puntero).
Las funciones amigas se pueden declarar en cualquier parte de la definición de la clase y no se limitado por el acceso de clase Restricciones de especificador
Una función puede ser una función amiga de múltiples clases
El principio de llamar a una función amiga es el mismo que el de una función ordinaria

3.2 Clase amiga

Todas las funciones miembro de una clase amiga pueden ser funciones amigas de otra clase y pueden acceder a miembros no públicos de otra clase.

class Time
{
	friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量

public:
    Time(int hour = 0, int minute = 0, int second = 0)
    : _hour(hour)
    , _minute(minute)
    , _second(second)
    {}

private:
    int _hour;
    int _minute;
    int _second;
};

class Date
{
	// 友元
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);

public:
    Date(int year = 1900, int month = 1, int day = 1)
    : _year(year)
    , _month(month)
    , _day(day)
    {}

    void SetTimeOfDate(int hour, int minute, int second)
    {
    // 直接访问时间类私有的成员变量
    _t._hour = hour;
    _t._minute = minute;
    _t._second = second;
    }

private:
    int _year;
    int _month;
    int _day;
	Time _t;

};
ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << " " << d._month << " " << d._day;
	return out;
}
istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}

1. La relación de amistad es unidireccional e inintercambiable.
Por ejemplo, la clase de hora y la clase de fecha mencionadas anteriormente declaran la clase de fecha como su clase amiga en la clase de hora, luego puede acceder directamente a las variables de miembros privados de la clase de tiempo en la clase de fecha, pero desea acceder a las variables de miembros privados de la clase Fecha en la clase Hora no.
2. No se puede transmitir la relación de amistad,
si C es amigo de B y B es amigo de A, no se puede explicar que C sea amigo de A.

4. Clase interna

Concepto: si una clase se define dentro de otra clase, la clase interna se denomina clase interna. Es definir otra clase dentro de una clase , la clase interna es una clase independiente, no pertenece a la clase externa, y no es posible acceder a los miembros de la clase interna a través del objeto de la clase externa. Las clases externas no tienen ningún acceso privilegiado a las clases internas.

class A
{
public:
	class B
	{

	private:
		int _b;
	};
	int _a;
};
int main()
{
	cout << sizeof(A) << endl;
}

La impresión aquí es 4, por lo que podemos saber que sizeof (clase externa) = el tamaño de la clase externa Aunque la clase interna está anidada dentro, la clase interna no se cuenta en el tamaño de la clase externa.

En segundo lugar, la clase interna es por defecto la clase amiga de la clase externa.

class A
{
public:
	class B
	{
	public:
		void func()
		{
			A a;
			a._a = 1;
			cout << a._a << endl;
		}

	private:
		int _b;
	};

private:
	int _a;
};
int main()
{
	A::B b;
	b.func();

	return 0;
}

Definimos una función en la clase B, que accede a la variable miembro _a de la clase A y le asigna un valor para ver si puede ejecutarse normalmente.

Podemos ver aquí que se puede acceder a las variables miembro de la clase A en la clase B y asignarles valores. Se puede ver que la clase B es amiga de la clase A.

Sin embargo, la clase A no es amiga de la clase B, y los amigos son unidireccionales.

característica:

1. La clase interna se puede definir como pública, protegida o privada en la clase externa.
2. Tenga en cuenta que la clase interna puede acceder directamente a los miembros estáticos de la clase externa sin el nombre de clase/objeto de la clase externa.
3. sizeof (clase externa) = clase externa, no tiene nada que ver con la clase interna.

Supongo que te gusta

Origin blog.csdn.net/Ljy_cx_21_4_3/article/details/132200058
Recomendado
Clasificación