Notas de C++: la primera clase y objeto

Tabla de contenido

introducir

La diferencia entre C y C++

 La conexión y diferencia entre C++ y Java

La diferencia entre C++ y Python

1. Primer contacto con la clase.

1. En C++, no solo se pueden definir variables en la estructura, sino que también se pueden definir funciones

 2. Definición de clase

Hay dos formas de definir una clase:

3. Calificadores de acceso a clases y encapsulamiento

3.1 Calificadores de acceso

¿Cuál es la diferencia entre estructura y clase en C++?

3.2 Embalaje

4. Ámbito de clase

5. Instanciación de la clase

6. Modelo de objeto de clase 

6.1 Cómo calcular el tamaño de un objeto de clase

6.2 Método de almacenamiento de objetos de clase 

6.3 Reglas de alineación de memoria de estructura

7. este puntero 

7.1 ¿Qué es este puntero?

7.2 Características de este puntero

¿Puede este puntero ser nulo?

En segundo lugar, las funciones miembro de la clase

Las 6 funciones miembro predeterminadas de la clase:

1. Constructor

 2. Destructor

2.1 Características

3. Copiar constructor

3.1 Concepto

3.2 Características

4. Sobrecarga del operador de asignación 

4.1 Sobrecarga de operadores

 4.2 Sobrecarga de operadores de asignación

5. miembro constante

Funciones miembro de clases modificadas const

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

3. Contacto profundo con los constructores

1. Asignación del cuerpo del constructor

2. Lista de inicialización

3. La palabra clave explícita

Cuatro miembros estáticos

1. Concepto

2. Características

Cinco, inicialización de miembro C++11 

Seis, destino? Youyuan!

1. Función de amigo

 2 clase amiga

Siete, clase interna

Concepto y Características


introducir

C++ está orientado a objetos.


La diferencia entre C y C++

C es un lenguaje orientado a procesos, y C++ es un superconjunto del lenguaje C, que agrega características orientadas a objetos, es decir, encapsulación, herencia y polimorfismo. La encapsulación puede ocultar los detalles de implementación del código, lo que hace que el código sea modular y más fácil de administrar y expandir. La herencia permite que las subclases reutilicen el código y las propiedades de la clase principal. El polimorfismo consiste en implementar la misma interfaz reescribiendo la función virtual de la clase principal por subclases, lo que da como resultado diferentes comportamientos.

C y C++ administran la memoria de diferentes maneras. C++ usa principalmente new y delete. Además, C++ también agrega conceptos como la sobrecarga de funciones y la referencia.

 La conexión y diferencia entre C++ y Java

Tanto C++ como Java son lenguajes orientados a objetos. C++ debe compilarse en un archivo ejecutable para ejecutarse. Java se compila y se ejecuta en la máquina virtual jvm. Por lo tanto, Java tiene mejores funciones multiplataforma, pero su eficiencia de ejecución no es tan bueno como C++.

La memoria de C++ debe ser administrada manualmente por el programador, mientras que Java puede ser completada por una máquina virtual, utilizando el algoritmo de reciclaje de marcas.

C++ tiene punteros, Java no y Java solo tiene referencias.

Tanto Java como C++ tienen constructores, C++ tiene destructores y Java no.

La diferencia entre C++ y Python

Python es un lenguaje de secuencias de comandos para interpretación y ejecución, y C++ es un lenguaje compilado, por lo que la multiplataforma de Python es mejor que C++.

Python usa sangría para distinguir diferentes bloques de código y C++ usa llaves.

C++ necesita definir primero el tipo de variable, pero Python no.

Hay más bibliotecas de Python que de C++, y es más cómodo de usar.



1. Primer contacto con la clase.



1. En C++, no solo se pueden definir variables en la estructura, sino que también se pueden definir funciones

struct Student
{
    void SetStudentInfo(const char* name, const char* gender, int age)
    {
        strcpy(_name, name);
        strcpy(_gender, gender);
        _age = age;
    }

    void PrintStudentInfo()
    {
        cout<<_name<<" "<<_gender<<" "<<_age<<endl;
    }

    char _name[20];
    char _gender[3];
    int _age;
};

 transferir:

int main()
{
    Student s;
    s.SetStudentInfo("哈哈", "女", 18);
    return 0;
}

 2. Definición de clase

class className
{
    // 类体:由成员函数和成员变量组成
}; //分号

Los elementos de la clase se denominan miembros de la clase: los datos de la clase se denominan atributos o variables miembro de la clase; las funciones de la clase se denominan métodos o funciones miembro de la clase.

Hay dos formas de definir una clase:

1. La declaración y la definición se colocan en el cuerpo de la clase. Debe tenerse en cuenta que si la función miembro está definida en la clase, el compilador puede tratarla como una función en línea.

 2. La declaración se coloca en el archivo .h y la definición de clase se coloca en el archivo .cpp (el segundo método es mejor)

//person.h
class Person
{
public:
	void show()
	{
		cout << _name << " " << _age << endl;
	}
public:
	char* _name;
	int _age;

};
//person.cpp
#include "person.h"
void Person::show()
{
	cout << _name << " " << _age << endl;
}

3. Calificadores de acceso a clases y encapsulamiento

3.1 Calificadores de acceso

La forma de C++ para lograr la encapsulación: use la clase para combinar las propiedades y los métodos del objeto para que el objeto sea más perfecto y proporcione selectivamente su interfaz a usuarios externos a través de derechos de acceso

Calificadores de acceso:

protegido, privado, público

[Descripción de los calificadores de acceso]
1. Se puede acceder directamente a los miembros modificados por el público fuera de la clase


2. No se puede acceder directamente a los miembros modificados protegidos y privados fuera de la clase (aquí protegido y privado son similares)


3. El alcance de los derechos de acceso comienza desde la ocurrencia del calificador de acceso hasta la próxima ocurrencia del calificador de acceso.


4. El permiso de acceso predeterminado de class es privado y struct es público (porque struct es compatible con C)

Los calificadores de acceso solo son útiles en el momento de la compilación, cuando los datos se asignan a la memoria, no hay diferencia en los calificadores de acceso

¿Cuál es la diferencia entre estructura y clase en C++?

Respuesta: C++ debe ser compatible con el lenguaje C, por lo que struct en C++ se puede usar como estructura. Además, struct en C++ también se puede usar para definir clases.
Es lo mismo que class es definir una clase, la diferencia es que el modo de acceso predeterminado de los miembros de la estructura es público y el modo de acceso predeterminado de los miembros de la clase es privado.

3.2 Embalaje

Las tres características principales de la orientación a objetos: encapsulación, herencia y polimorfismo.

Encapsulación: oculta las propiedades y los detalles de implementación del objeto, y solo expone la interfaz para interactuar con el objeto


4. Ámbito de clase

Una clase define un nuevo ámbito y todos los miembros de la clase están en el ámbito de la clase. Para definir miembros fuera de la clase, debe usar el :: analizador de alcance para indicar a qué dominio de clase pertenece el miembro

class Person
{
public:
	void show()
	{
		cout << _name << " " << _age << endl;
	}
public:
	char* _name;
	int _age;

};

void Person::show()
{
	cout << _name << " " << _age << endl;
}

5. Instanciación de la clase

El proceso de creación de un objeto con un tipo de clase se denomina instanciación de la clase.

1. Defina una clase sin asignar espacio de memoria real para almacenarla


2. Una clase puede crear instancias de varios objetos, y los objetos instanciados ocupan el espacio físico real y almacenan las variables de los miembros de la clase.

Por ejemplo, se pueden hacer muchas figuras basadas en un hombre de papel, y cada figura puede tener diferencias debido a problemas del fabricante.  


6. Modelo de objeto de clase 

6.1 Cómo calcular el tamaño de un objeto de clase

Hay variables miembro almacenadas en el objeto, pero no funciones miembro. Cada objeto tiene variables miembro independientes


Calcule el tamaño de una clase o un objeto de clase: solo mire las variables miembro, y las reglas de alineación de la memoria son consistentes con la estructura

Clase vacía: 1 byte. Para distinguir diferentes clases vacías, la clase vacía también puede crear instancias de objetos, y la dirección de cada objeto es diferente, por lo que el compilador agregará implícitamente un byte a la clase vacía.

6.2 Método de almacenamiento de objetos de clase 

Solo se guardan las variables miembro y las funciones miembro se almacenan en segmentos de código público

class A1 {
public:
	void f1() {}
private:
	int _a;
};

// 类中仅有成员函数
class A2 {
public:
	void f2() {}
};

// 类中什么都没有---空类
class A3
{};

//sizeof(A1) : __4____ sizeof(A2) : __1____ sizeof(A3) : ___1___

 El tamaño de una clase es en realidad la suma de las "variables miembro" en la clase. Por supuesto, también se requiere alineación de memoria. Preste atención al tamaño de la clase vacía. La clase vacía es especial. El compilador proporciona la clase vacía un byte para identificar de forma única la clase

6.3 Reglas de alineación de memoria de estructura

+ El desplazamiento del primer miembro en la estructura es 0
+ Otras variables miembro deben almacenarse de acuerdo con la dirección de un múltiplo entero del número de alineación
+ Número de alineación = min (el número de alineación predeterminado del compilador, el número de alineación de este miembro), en Linux es 4. En vs, es 8
+ el tamaño total de la estructura es un múltiplo entero del número máximo de alineación

+ Si una estructura está anidada, la estructura anidada se alinea con un múltiplo entero de su propia alineación máxima y el tamaño total de la estructura es un múltiplo entero de todas las alineaciones máximas (incluida la alineación de la estructura anidada).


7. este puntero 

7.1 ¿Qué es este puntero?

Solo los objetos de función miembro de clase no estática tienen este puntero, y este puntero solo se puede usar en funciones miembro de la clase, y apunta al objeto donde se llama a la función miembro.

Este puntero oculto: al llamar a una función miembro, el parámetro real no se puede pasar a esta. Al definir una función miembro, no puede declarar explícitamente el parámetro formal para esto. Dentro de una función miembro, puede escribir.

class Date
{
public:
	void Display()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
		//cout<< this->_year << this->_month << this->_day <<endl;  
	}
	void SetDate(int year, int month, int day)
	{
		_year = year;
		//this->_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};
int main()
{
	Date d1, d2;
	d1.SetDate(2018, 5, 1);
	d2.SetDate(2018, 7, 1);


	d1.Display();
	d2.Display();
	//void Display(Date * this) 形参
	//{}

	return 0;
}

El compilador de C++ agrega un parámetro de puntero oculto a cada "función miembro no estática", de modo que el puntero apunte al objeto actual (el objeto que llama a la función cuando la función se está ejecutando) y la operación de todas las variables miembro en el el cuerpo de la función es Es solo que todas las operaciones son transparentes para el usuario, es decir, el usuario no necesita pasarlo y el compilador lo completa automáticamente.

7.2 Características de este puntero


1. El tipo de este puntero: tipo de clase * const 2. Solo se puede usar dentro de la
" función miembro " 3. Este puntero es en realidad un parámetro formal de
una función miembro en esencia , cuando el objeto llama a la función miembro , la dirección del objeto se usa como el parámetro real pasado a este parámetro. Por lo tanto, el puntero this no se almacena en el objeto . 4. El puntero this es el primer parámetro de puntero implícito de la función miembro Generalmente, el compilador lo pasa automáticamente a través del registro ecx y no es necesario que lo pase el usuario. Dado que es un parámetro formal, generalmente se almacena en la pila.

¿Puede este puntero ser nulo?

class A
{
public:
	void PrintA()
	{
		cout << _a << endl;
	}
	void Show()
	{
		cout << "Show()" << endl;
	}
private:
	int _a;
};
int main()
{
	A* p = nullptr;//空指针不是语法错误,编译不会报错

	p->PrintA();//运行到这里时,崩溃,因为此时 调用 _a,this->_a
	//把p这个空指针作为实参赋给this,对空指针解引用。

	p->Show();
}

La función miembro no existe en el objeto y p llama a la página de función miembro sin un puntero nulo.



En segundo lugar, las funciones miembro de la clase



Las 6 funciones miembro predeterminadas de la clase:

Inicialización y limpieza: constructor, destructor

Asignación de copia: construcción de copia, sobrecarga del operador de asignación

Tome la sobrecarga de direcciones: tome la dirección para objetos ordinarios y objetos constantes


1. Constructor

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

La tarea principal es inicializar objetos.
Sus características 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 instancia el objeto.


4. El constructor puede estar sobrecargado.

5. Si no se define explícitamente ningún constructor en la clase, el compilador de C++ generará automáticamente un constructor predeterminado sin parámetros. Una vez definido explícitamente, el compilador ya no lo generará.

6. Tanto el constructor sin parámetros como el constructor predeterminado se denominan constructores predeterminados y solo puede haber un constructor predeterminado.

Nota: Los constructores sin argumentos, los constructores predeterminados completos y los constructores generados por el compilador de forma predeterminada pueden considerarse funciones miembro predeterminadas.

7. El constructor predeterminado generado por el compilador llamará a su función miembro predeterminada para el miembro de tipo personalizado

class Date
{

public:
	// 1.无参构造函数
	Date()
	{}
	// 2.带参构造函数
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};
void TestDate()
{
	Date d1; // 调用无参构造函数
	Date d2(2015, 1, 1); // 调用带参的构造函数
	// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
	// 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
	Date d3();
}

 2. Destructor

El destructor se llama automáticamente cuando se destruye el objeto.

2.1 Características

Los destructores son funciones miembro especiales.
Sus características son las siguientes:
1. El nombre del destructor es agregar caracteres   
antes del nombre de la clase 2. Sin parámetros y sin valor de retorno.
3. Una clase tiene un único destructor. Si no se define explícitamente, el sistema generará automáticamente un destructor predeterminado.
4. Cuando finaliza el ciclo de vida del objeto, el sistema de compilación de C++ llama automáticamente al destructor

5. El destructor predeterminado generado por el compilador llama a su destructor para miembros de tipo personalizado

typedef int DataType;
class SeqList
{
public:
	~SeqList()
	{
		if (_pData)
		{
			free(_pData); // 释放堆上的空间
			_pData = NULL; // 将指针置为空
			_capacity = 0;
			_size = 0;
		}
	}
private:
	int* _pData;
	size_t _size;
	size_t _capacity;
};


3. Copiar constructor


3.1 Concepto

Crea un nuevo objeto idéntico a un objeto. Un objeto que ya existe, para copiar e inicializar un objeto que será instanciado inmediatamente

3.2 Características

El constructor de copias también es una función miembro especial y sus características son las siguientes:
1. El constructor de copias es una forma sobrecargada del constructor.
2. El constructor de copias tiene solo un parámetro y debe pasarse por referencia , y el método de pasar por valor provocará infinitas llamadas recursivas.

class Date
{

public:
	// 1.无参构造函数
	Date()
	{}
	// 2.带参构造函数
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

    //3.拷贝构造函数
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}


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

int main()
{
    Date d1;
    Date d2(d1);//调用拷贝构造函数
    return 0;
}

 3. Si no se muestra ninguna definición, el sistema genera un constructor de copia predeterminado. El objeto constructor de copia predeterminado se almacena en la memoria y se copia en orden de bytes. Este tipo de copia se denomina copia superficial o copia de valor.

 4. La diferencia entre copia superficial y copia profunda se discutirá más adelante, precisamente por esta diferencia. Necesitamos implementar nuestra propia construcción de copias.


4. Sobrecarga del operador de asignación 

4.1 Sobrecarga de operadores

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)

1. Un operador sobrecargado debe tener un operando de tipo clase o tipo enumeración.

2. El significado del operador utilizado para los tipos integrados no se puede cambiar, por ejemplo: el entero integrado + no puede cambiar su significado
3. Cuando se utiliza una función sobrecargada como miembro de una clase, sus parámetros formales parecen ser 1 función miembro menor que el número de operandos 4.
El operador tiene un parámetro formal predeterminado this, que está limitado al primer parámetro formal
5.       .*, ::, sizeof, ?:, .Tenga    en cuenta que los cinco operadores anteriores no se pueden sobrecargar

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

//bool operator==(Date* this, const Date& d2)
// 这里需要注意的是,左操作数是this指向的调用函数的对象
bool operator==(const Date& d2)
{
	
	bool operator==(Date* const this, const Date & d)
	{
		return _year == d2._year;
		    && _month == d2._month
			&& _day == d2._day;
	}
	
}

 4.2 Sobrecarga de operadores de asignación

Date& operator=(const Date& d)
{
    if(this != &d)
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
}

1. Tipo de parámetro
2. Devolver valor
3. Verificar si se asigna un valor a sí mismo
4. Devolver *this
5. Si una clase no define explícitamente una sobrecarga del operador de asignación, el compilador también generará uno para completar la copia de valor del objeto en orden de bytes.

6. A veces todavía tienes que escribir una copia de la tarea tú mismo

int main()
{
    Date d1;
    Date d2(2018,10, 1);

// 这里d1调用的编译器生成operator=完成拷贝,d2和d1的值也是一样的。
    d1 = d2;

return 0;
}

5. miembro constante

Funciones miembro de clases modificadas const

La función miembro de clase decorada con const se denomina función miembro const. La función miembro de clase modificada 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.


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

Simplemente use el predeterminado, y puede volver a cargarlo usted mismo cuando desee 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; // 日
};


3. Contacto profundo con los constructores



1. Asignación del cuerpo del constructor

La declaración en el cuerpo del constructor solo puede llamarse asignación de valor inicial, no inicialización. Porque la inicialización solo se puede inicializar una vez, y el cuerpo del constructor se puede asignar varias veces.


2. Lista de inicialización

Dos puntos, seguidos de una lista de miembros de datos separados por comas, cada "variable de miembro" seguida de un
valor inicial o expresión entre paréntesis.

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

Cada variable miembro solo puede aparecer una vez en la lista de inicialización (la inicialización solo se puede inicializar una vez)


La clase contiene los siguientes miembros, que deben colocarse en la lista de inicialización para la inicialización:
variable de miembro de referencia variable
de miembro constante
miembro de tipo personalizado (esta clase no tiene un constructor predeterminado

class B
{
public:
    B(int a, int ref)
    :_aobj(a)
    ,_ref(ref)
    ,_n(10)

private:
    A _aobj; // 没有默认构造函数
    int& _ref; // 引用
    const int _n; // const
};

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 miembro de tipo personalizado, primero debe inicializarse con la lista de inicialización.

El orden en que se declaran las variables miembro en una clase es el orden en que se inicializan en la lista de inicialización

 Aviso:

No importa si un tipo incorporado usa una lista de inicializadores o no.

Un tipo personalizado utiliza una lista de inicializadores: se llama a su propio constructor.

No utilizado: primero se llamará al constructor predeterminado, luego a su propio constructor y luego se le asignará.


3. La palabra clave explícita


Los constructores no solo pueden construir e inicializar objetos, sino que también tienen funciones de conversión de tipo para constructores de un solo parámetro.

Modificar el constructor con explícito prohibirá la conversión implícita del constructor de un solo argumento

class Date
{
public:
	Date(int year)
		:_year(year)
	{}
	explicit Date(int year)
		:_year(year)
	{}
private:
	int _year;
	int _month;
	int _day;
};
void TestDate()
{
	Date d1(2018);

	// 用一个整形变量给日期类型对象赋值
	
	d1 = 2019;
}


Cuatro miembros estáticos

1. Concepto

Los miembros de clase declarados como estáticos se denominan miembros estáticos de la clase, las variables miembro modificadas con estática se denominan variables miembro estáticas y las funciones miembro modificadas con estática se denominan funciones miembro estáticas. Las variables miembro estáticas deben inicializarse fuera de la clase

class A
{
public:
	A() { ++_scount; }
	A(const A& t) { ++_scount; }
	static int GetACount() { return _scount; }
private:
	static int _scount;
};

int A::_scount = 0;//初始化

void TestA()
{
	cout << A::GetACount() << endl;
	A a1, a2;
	A a3(a1);
	cout << A::GetACount() << endl;
}

2. Características

1. Los miembros estáticos son compartidos por todos los objetos de clase y no pertenecen a una instancia específica
2. Las variables de miembros estáticos deben definirse fuera de la clase sin agregar la palabra clave static
3. Los miembros estáticos de clase se pueden usar nombre de clase:: miembro estático u Objeto Miembros estáticos para acceder
4. Las funciones de miembros estáticos no tienen un puntero oculto, y no pueden acceder a ningún miembro no estático
5. Al igual que los miembros ordinarios de una clase, los miembros estáticos también tienen tres niveles de acceso: público, protegido y privado. y también puede tener un valor de retorno



Cinco, inicialización de miembro C++11 

private:
// A las variables miembro no estáticas se les puede dar un valor predeterminado cuando se declara el miembro.
int a = 10;



Seis, destino? Youyuan!

Los amigos se dividen en: función de amigo y clase de amigo
, pero los amigos aumentarán el grado de acoplamiento y destruirán la encapsulación, por lo que los amigos no deben usarse con más frecuencia.

1. Función de amigo

Las funciones amigas pueden acceder a miembros privados y protegidos de una clase, pero no a funciones miembro de la clase

Las funciones de amigo no se pueden modificar con const


Las funciones amigas se pueden declarar en cualquier parte de la definición de la clase, no restringidas por calificadores de acceso a la clase.


Una función puede ser una función amiga de múltiples clases


El llamado de las funciones de amigo es el mismo que el de las funciones ordinarias. 


 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.


La relación de amistad es unidireccional y no intercambiable.


La amistad no se puede pasar
 

class Date;
class Time
{

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

};


Siete, clase interna

Concepto y Características


concepto :

Si una clase se define dentro de otra clase, la clase interna se denomina clase interna. Tenga en cuenta que esta clase interna es una clase independiente en este momento, no pertenece a la clase externa, y mucho menos llama a la clase interna a través del objeto de la clase externa. La clase externa no tiene ningún acceso privilegiado a la clase interna
Nota: La clase interna es la clase amiga de la clase externa. Preste atención a la definición de la clase amiga. La clase interna puede acceder a todos los miembros de la clase externa a través de los parámetros de objeto de la clase externa. Pero la clase exterior no es amiga de la clase interior.

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 y de enumeración en 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/MuqiuWhite/article/details/129493553
Recomendado
Clasificación