La base de las notas de estudio de C++

prefacio

  Esta nota de estudio es un resumen de estudio personal. No se incluyen algunos puntos relativamente simples y de conocimiento común. Es principalmente para complementar y explicar las dudas en el proceso de aprendizaje. Esta nota es solo para referencia y estudio, no apta para tutoriales.

1. Puntos de conocimiento fragmentarios

Constantes: #definey const, las constantes no se pueden modificar cuando el programa se está ejecutando
1. ¿Las constantes de macro son cadenas?
Respuesta: Su función es definir un identificador y una cadena con la directiva de preprocesamiento del compilador #define.Cuando el identificador se encuentre en el programa fuente, se reemplazará con la cadena especificada.
2. Los identificadores distinguen entre mayúsculas y minúsculas y es mejor saber el significado por nombre.

inserte la descripción de la imagen aquí
De manera predeterminada, los decimales se tratan como precisión doble y se agrega f a la precisión simple. Al generar decimales de manera predeterminada, hay 6 dígitos significativos (tanto de precisión simple como de precisión doble).
Notación científica: 1e-3, 1e-6
Cadena:
Estilo C:
inserte la descripción de la imagen aquí

Estilo C ++:
inserte la descripción de la imagen aquí
ifhay tres tipos de estructuras
switcha las que se debe prestar atención al usarlas, breaklos efectos de agregar y no agregar, y defaultlos efectos de agregar o no.
Generación de números aleatorios:

int num = rand()% 100 + 1: // rand()%100 +1生成 0+1 99 +1 随机数

Semilla aleatoria

//添加随机数种子 作用利用当前系统时间生成随机数,防止每次随机数都一样
srand((unsigned int)time (NULL)):

inserte la descripción de la imagen aquí
switchSolo puede ser de tipo entero o carácter, porque el tipo entero y el carácter pueden entenderse como un tipo de datos, pero el número de bytes ocupados es diferente.
Declaración de declaración de salto break:

Función: Se utiliza para saltar fuera de la estructura de selección o breakla temporización de la estructura de bucle:

  • Aparece en la declaración condicional del interruptor, la función es terminar el caso y saltar del interruptor.
  • Aparece en una declaración de bucle, la función es saltar fuera de la declaración de bucle actual
  • Aparece en un bucle anidado, saltando fuera 最近de la instrucción del bucle interno

Tenga en cuenta el uso mixto de ruptura en bucles y selecciones, la ruptura solo funciona en declaraciones relacionadas con sí mismo.

goto //跳转到某一句

No se recomienda su uso, rompiendo la estructura de operación de arriba hacia abajo de C++

goto FLAG;//直接跳转到下面的FLAG:后面的语句
cout << "3"<< endl;
cout << "4" << endl;
FLAG:
cout << "4" << endl;
system("pause") ;

Matriz:
tres formas de definir una matriz dimensional:

1. Nombre de matriz de tipo de datos [longitud de matriz]
2. Nombre de matriz de tipo de datos [longitud de matriz] = {valor1, valor2...}
3. Nombre de matriz de tipo de datos[] = {valor1, valor2...};

Características de la matriz: cada elemento de la matriz se coloca en un espacio de memoria continuo del mismo tipo de datos.
La longitud de la matriz no puede determinarse mediante variables.
El propósito del nombre de matriz unidimensional

1. Puede contar la longitud de toda la matriz en la memoria
2. Puede obtener la primera dirección de la matriz en la memoria

El nombre de la matriz es un
tipo de burbuja constante:

1. Compara elementos adyacentes. Si el primero es más grande que el segundo, cámbialos a ambos.
2. Realice el mismo trabajo para cada par de elementos adyacentes y, después de la ejecución, encuentre el primer valor máximo.
3. Repita los pasos anteriores, cada vez que el número de comparaciones -1, hasta que no se requiera ninguna comparación.

inserte la descripción de la imagen aquí
Matriz bidimensional: no se puede omitir el número de columnas

1. Nombre de matriz de tipo de datos [número de fila] [número de columna]
2. Nombre de matriz de tipo de datos [número de fila] [número de columna]={ { {datos 1, datos 2, datos 3}, {datos 4, datos 5, datos 6}}
3. Tipo de datos nombre de matriz[filas][columnas]={datos 1, datos 2, datos 3, datos 4, datos 5, datos 6} 4. tipo de datos nombre de matriz[][columnas]= {
Datos 1, Datos 2, Datos 3, Datos 4, Datos 5, Datos 6} El compilador puede capturar automáticamente el número de líneas

Nombre de matriz bidimensional:

1. Puede ver el tamaño del espacio de memoria ocupado
2. Puede ver la primera dirección de la matriz bidimensional

inserte la descripción de la imagen aquí
Función:
La definición de una función generalmente tiene 5 pasos

1. Tipo de valor de retorno
2. Nombre de la función
3. Lista de parámetros
4. Declaración del cuerpo de la función
5. Expresión de retorno

Paso por valor:
los cambios de parámetros formales no afectarán a los parámetros reales y
voidno se pueden agregar valores de retorno

Cuatro formas funcionales:

1. Sin participación, sin devolución
2. Participación, sin devolución
3. Sin participación, devolución
4. Participación, devolución

Declaración de función:
Dígale al compilador que la función existe de antemano, el compilador debe verla de antemano, la declaración puede ser varias veces, pero la definición solo puede ser una vez.

Escritura de archivos de funciones:
la escritura de archivos de funciones generalmente tiene 4 pasos:

1. Cree .hun archivo de encabezado con el nombre de sufijo 2. Cree un archivo de origen con el
nombre de sufijo 3. Escriba la declaración de la función en el archivo de encabezado Generalmente, la biblioteca que se utilizará en el archivo de origen también debe escribirse aquí, por ejemplo , 4. En Escriba la definición de la función en el archivo fuente.cpp
iostreamusing namespace std;

  No basta con seguir los pasos anteriores. También debe incluir un archivo de encabezado personalizado en el archivo de origen, que debe incluir el nombre del precio del archivo de encabezado que creó. El propósito de este paso es incluir el encabezado personalizado. asociado con el archivo de origen, algunas declaraciones requeridas en el archivo de origen también se escriben en el archivo de encabezado en lugar de en el archivo de origen. Al usarlo, solo necesita agregar el archivo de encabezado del archivo que necesita usar al comienzo del lugar que usa.

inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí
Si el programa principal quiere llamar a la función definida en este momento, se puede usar directamente importando directamente el archivo de encabezado.

inserte la descripción de la imagen aquí
Puntero:
generalmente ocupa cuatro bytes, ocupa el mismo espacio en cada compilador y no tiene nada que ver con el tipo de datos.
Punteros nulos y punteros salvajes

Puntero nulo: la variable de puntero apunta al espacio con el número de memoria 0
Propósito: inicializar la variable de puntero
Nota: no se puede acceder a la memoria a la que apunta el puntero nulo, y el número de memoria entre 0 y 255 está ocupado por el sistema, por lo que no se puede acceder

//空指针
//1、空指针用于给指针变量进行初始化
int *p = NULL;
//2、空指针是不可以进行访问的,企图让p指向的内存填上数字100,是不可以的
*p = 100;

No se puede acceder al puntero nulo porque el número 0-255 en la memoria está ocupado por el sistema.
Puntero salvaje:
la variable del puntero apunta a un espacio de memoria ilegal

int main(
//野指针,随便让他们指向一个内存编号
//在程序中,尽量避免出现野指针
int *p = (int *)0x1100;
//访问野指针报错
cout << *p << end1;
system("pause");
return 0;

Para esta parte, puede consultar lo siguiente: resumen de puntero salvaje de C++

constDecorar el puntero
constpuede garantizar que el valor no cambie cuando se pase el puntero.
constHay tres casos para modificar punteros:

1. puntero modificado const - puntero constante
2. constante modificada const - constante de puntero
3. const no solo puntero modificado, sino también constante modificada

¿Ves quién está detrás de la const?
Puntero constante:

const int * p = &a;

El puntero del puntero se puede cambiar, pero el valor al que apunta el puntero no se puede cambiar
como una constante de puntero:

int * const p = &a;

El puntero del puntero no se puede cambiar, pero el valor apuntado se puede cambiar
constante significa modificar el puntero y modificar la constante:

const int * const p = &a;

No se puede cambiar la orientación del puntero ni el valor al que apunta el puntero.Método
de memoria:
  traducir constdirectamente a una constante y "*"luego traducirlo a un puntero para distinguir claramente entre una constante de puntero y un puntero constante.Si constse trata de un puntero o un dedo "*", quien lo sigue no puede ser cambiado o manipulado.
inserte la descripción de la imagen aquí

Consejos: mira constsi el de la derecha es un puntero o una constante, si es un puntero, es un puntero constante, si es una constante, es un puntero constante

Estructura:
Sintaxis:

struct 结构体名 {
    
    结构体成员列表 };

Hay tres formas de crear variables a través de estructuras:

  1. struct estructura nombre variable estrella nombre.
  2. struct nombre de la estructura nombre de la variable = {valor del miembro 1, valor del miembro 2...}.
  3. Las variables se crean por cierto al definir estructuras.

Nota: en C++, se puede omitir al crear una variable de estructura struct, pero no se puede omitir en C.
Resumir:

1: La palabra clave al definir una estructura es struct, que no se puede omitir
2: Al crear una estructura, se puede omitir la palabra clave struct
3: La variable de estructura utiliza el operador " ." para acceder a los miembros

Puntero de estructura:

Use el operador -> para acceder a las propiedades de la estructura a través del puntero de estructura

Hay dos oraciones en el código anterior que olvidé explicar:

system("pause")//按任意键退出系统
system("cls")//清屏

La operación ternaria se puede colocar en la declaración de salida:
inserte la descripción de la imagen aquí

switch case:

Si el caso va seguido de una declaración compuesta, debe encerrarse entre llaves.

Ideas de eliminación de matriz:

Busque la etiqueta que desea eliminar y los siguientes datos avanzarán como un todo.

2. Núcleo de C++

2.1 Partición de memoria

Modelo de partición de memoria: ¡
aquí es muy importante, muy importante, muy importante!

Cuando se ejecuta el programa C++, la dirección general de la memoria se divide en 4regiones

  • Área de código: almacena el código binario del cuerpo de la función, gestionado por el sistema operativo.
  • Área global: almacenar variables globales y variables y constantes estáticas
  • Área de pila: asignada y liberada automáticamente por el compilador, almacenando valores de parámetros de función , variables locales , etc.
  • Área de montón: asignada y liberada por el programador, si el programador no la libera, el sistema operativo la recuperará al final del programa.

El significado de las cuatro áreas de memoria:

Los datos almacenados en diferentes áreas están dotados de diferentes ciclos de vida, brindándonos una programación más flexible

Antes de que se ejecute el programa:
Después de compilar el programa, se genera exeun programa ejecutable Antes de que se ejecute el programa, se divide en dos áreas: el área de código y el
área de código de área global:

Almacene las instrucciones de la máquina ejecutadas por la CPU.
El área de código es compartida . El propósito de compartir es que para los programas que se ejecutan con frecuencia, solo se requiere una copia del código en la memoria. El
área de código es de solo lectura . La razón para hacer es de solo lectura Evita que un programa modifique accidentalmente sus instrucciones

Zona mundial:

Las variables globales y las variables estáticas se almacenan aquí.
El área global también contiene el área constante, las constantes de cadena y otras constantes también se almacenan aquí
.El sistema operativo libera los datos en esta área después de que finaliza el programa..

Constante:
cadena de decoración constante
const, dividida en global y local.

inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí
Resumen: C++ se divide en área global y área de código
antes de que se ejecute el programa

El área de código se caracteriza por compartir y solo lectura.
Las variables globales, las variables estáticas y las constantes se almacenan
en el área global. constLas constantes globales modificadas y las constantes de cadena se almacenan en el área constante. Las variables locales modificadas por const no se colocan en el área área mundial. Las constantes de macro no se asignan al montón o al área global, solo se reemplazan con sus valores constantes definidos durante la fase de preprocesamiento. Por lo tanto, las constantes de macro solo existen en el código y no se les asignará espacio de memoria cuando el programa se esté ejecutando.Se deben tener en cuenta los siguientes puntos.

Después de que se ejecute el programa:
área de pila:

Asignado y liberado automáticamente por el compilador, almacenando valores de parámetros de función, variables locales, etc.

Nota: No devuelva la dirección de la variable local, los datos creados en el área de la pila serán liberados automáticamente por el compilador , como se muestra en el siguiente ejemplo:

int* func()
{
    
    
	int a = 10;//局部变量 存放在栈区,栈区的数据在函数执行完后自动释放
	return &a; //返回局部变量的地址
}
int main() {
    
    
	//接受func函数的返回值
	int *p = func();
	cout << *p << endl; //第一次可以打印正确的数字,是因为编译器做了一次保留,只做一次保留; 
	cout << *p << endl; //第二次这个数据就不再保留了,此时相当于野指针
	system(pause");
	return 0;
}

Área de montón:

Es asignado y liberado por el programador. Si el programador no lo libera, será recuperado por el sistema operativo al final del programa.
En C++, se usa principalmente para newabrir memoria en el área del montón

int *func()
{
    
    
	//利用new关键字 可以将数据开辟到堆区
	//指针本质上也是局部变量,放在栈上,指针保存的数据是放在堆区
	int *p = new int(10); //返回的也是地址
	return p;
}
int main() {
    
    
	//在堆区开辟数据
	int *p = func();
	cout << *p << endl; 
	//只要不手动释放,在程序退出前可以一直输出
	cout << *p << endl; 
	cout << *p << endl; 
	cout << *p << endl; 
	system(pause");
	return 0;
}

nuevo operador:

En C++, el operador new se usa para abrir datos en el área del montón. Los datos desarrollados en el área del montón son desarrollados manualmente por el programador y liberados manualmente. La liberación usa el operador Sintaxis: , el retorno es un puntero Los datos creados por new devolverán un puntero del tipo correspondiente a delete
los datosnew 数据类型

void test010()
{
    
    
	int * p = func();
	cout << *p <<endl;
	cout << *p <<endl;
	cout << *p <<endl;
	//堆区的数据 由程序员管理开辟,程序员管理释放
	//如果想释放堆区的数据,利用关键字 delete
	delete p; //释放p指向的内容
	//报错,读取访问权限冲突,此时再访问就是野指针
	cout << *p <<endl;
}

Crea una matriz en el montón:

void test02(){
    
    
	//创建10整型数据的数组,在堆区
	int * arr = new int[10]: //10代表数组有10个元素,返回数组的首地址
	}

//释放堆区数组
//释放数组的时候 要加[]才可以
delete[] arr;

2.2 Referencias


Se dan Alias ​​de Referencia a las variables, en el mismo espacio de almacenamiento, si una cambia, la otra también cambia.
Para citar la sintaxis básica:

tipo de datos y alias = nombre original

Aviso:

  • la referencia debeinicialización, tienes que decirle al compilador de quién es el alias cuando apareces
  • Después de inicializar la referencia, no se puede cambiar.Solo puede ser un alias para una variable, de principio a fin
int a = 10;
//1、引用必须初始化//int &b;  错误,必须要初始化
int &b = a;
//2、引用在初始化后,不可以改变
int c = 20;
//赋值操作,而不是更改引用,相当于把c的值赋给了a和b,a和b完全等价,最终a,b,c三个值都是20.
b = c;

Citado como argumentos a las funciones:

Función: al pasar parámetros en una función, la técnica de referencia se puede utilizar para permitir que el parámetro formal modifique el parámetro real.Ventajas
: puede simplificar el puntero para modificar el parámetro real.

Aviso:

Cuando se utiliza una referencia como parámetro de función, el parámetro formal y el parámetro real comparten el mismo espacio de memoria. Es decir, el valor del parámetro actual se pasa directamente al parámetro formal, y la modificación del parámetro formal en la función se reflejará directamente en el parámetro actual.

Resumir:

Pasar parámetros por referencia tiene el mismo efecto que pasar por dirección. La sintaxis de las citas es más clara y sencilla.

Referencia como valor de retorno de una función:
una referencia puede existir como valor de retorno de una función.
Aviso:

1. No devolver referencias de variables locales, como se ha mencionado más arribaNo devolver la dirección de una variable localHay una razón
2. Llamadas a funciones como valores l

//返回局部变量引用,会引起意想不到的错误,相当于野指针 
int& test01() {
    
    
	int a = 10; //局部变量
	return a;
}

//返回静态变量引用
int& test02() {
    
    
	static int a = 20; //静态变量,存放在全局区,程序运行完才释放
	return a;
}

int main() {
    
    

	//不能返回局部变量的引用。这里说明一下,函数返回的是引用,接受的时候可以用引用也可以不用引用,
	//取决于是否需要对返回值进行修改。
	int& ref = test01();
	//第一次正确,编译器做了一次保留
	cout << "ref = " << ref << endl;
	//第二次错误,因为a的内存已释放
	cout << "ref = " << ref << endl;

	//如果函数做左值,那么必须返回引用
	//上面声明的是静态的,这里可以无限次使用
	int& ref2 = test02();
	//下面两句都能正常输出
	cout << "ref2 = " << ref2 << endl;
	cout << "ref2 = " << ref2 << endl;
	
	//如果函数的返回值是引用,这个函数的调用可以作为左值
	test02() = 1000;

	cout << "ref2 = " << ref2 << endl;
	cout << "ref2 = " << ref2 << endl;

	system("pause");

	return 0;
}

Explique el código anterior de nuevo:
inserte la descripción de la imagen aquí

La naturaleza de la cita:

La esencia de la referencia se implementa dentro de C++ como unpuntero constante

C++Se recomienda usar tecnología de referencia , porque la sintaxis es conveniente y la esencia de la referencia es una constante de puntero, pero el compilador realiza todas las operaciones de puntero por nosotros.

//发现是引用,转换为 int* const ref = &a;
void func(int& ref){
    
    
	ref = 100; // ref是引用,转换为*ref = 100
}
int main(){
    
    
	int a = 10;
    
    //自动转换为 int* const ref = &a; 指针常量是指针指向不可改,也说明为什么引用不可更改
	int& ref = a; 
	ref = 20; //内部发现ref是引用,自动帮我们转换为: *ref = 20;
    
	cout << "a:" << a << endl;
	cout << "ref:" << ref << endl;
    
	func(a);
	return 0;
}

En el ejemplo anterior, se encuentra internamente que ref es una referencia y se convierte automáticamente al formato de: *ref = 20

Referencia constante:
modifique los parámetros formales para evitar el mal uso y use la modificación tanto como sea C++posible en la escritura estándar . La referencia debe referirse a un espacio de memoria legal:constconst

int a = 10:
int & ref = 10;//引用必须引一块合法的内存空间(栈区或者堆区的数据),这里会报错,因为10是放在常量区的
//引用使用的场景,通常用来修饰形参
void showValue(const int& v) {
    
    
	//v += 10;
	cout << v << endl;
}

int main() {
    
    

	//int& ref = 10;  引用本身需要一个合法的内存空间,因此这行错误
	/*加入const就可以了,编译器将代码修改为:int temp = 10; const int& ref = temp;
	创建了一个临时变量就可以引用了,
	其实这里引用的是个中间变量,我们找不到他的原名,原名是编译器起的,我们只能通过别名操作。*/
	const int& ref = 10; //加入const之后变为只读,不可修改

	//ref = 100;  //加入const后不可以修改变量
	cout << ref << endl;

	//函数中利用常量引用防止误操作修改实参
	int a = 10;
	showValue(a);

	system("pause");

	return 0;
}

Las referencias constantes generalmente se escriben en parámetros formales.

2.3 Funciones

Los parámetros predeterminados de la función:

Si se pasan los datos, use los suyos propios, si no, use los predeterminados.
Si ya existe un parámetro predeterminado en una determinada posición, entonces debe haber parámetros predeterminados a partir de esta posición, es decir, el parámetro predeterminado debe colocarse detrás.
Si la declaración de la función tiene parámetros predeterminados, entonces la implementación de la función no puede tener parámetros predeterminados, o solo una de la declaración y la implementación pueden tener parámetros predeterminados.

Parámetros de marcador de posición de funciones:
Sintaxis: 返回值类型 函数名 (数据类型){}
  Al pasar parámetros, los marcadores de posición también deben completar los parámetros. Los parámetros de marcador de posición también pueden tener parámetros predeterminados, que se usarán más adelante. Cuando hay parámetros predeterminados, no es necesario pasar parámetros.

//函数占位参数 ,占位参数也可以有默认参数
void func(int a, int) {
    
    
	cout << "this is func" << endl;
}

int main() {
    
    

	func(10,10); //占位参数必须填补,否者报错

	system("pause");

	return 0;
}

Sobrecarga de funciones:
los nombres de las funciones pueden ser los mismos.
Condición de sobrecarga : se cumplen tres condiciones al mismo tiempo, el núcleo es que los parámetros deben ser diferentes

  • Bajo el mismo alcance, es decir, la función que desea llamar proviene del mismo lugar
  • mismo nombre de función
  • parámetros de funcióndiferentes tiposoel numero es diferenteoorden diferente

Nota: El valor de retorno
de una función no se puede utilizar como condición para la sobrecarga.

//函数重载需要函数都在同一个作用域下
void func()
{
    
    
	cout << "func 的调用!" << endl;
}
void func(int a)
{
    
    
	cout << "func (int a) 的调用!" << endl;
}
void func(double a)
{
    
    
	cout << "func (double a)的调用!" << endl;
}
void func(int a ,double b)
{
    
    
	cout << "func (int a ,double b) 的调用!" << endl;
}
void func(double a ,int b)
{
    
    
	cout << "func (double a ,int b)的调用!" << endl;
}

//函数返回值不可以作为函数重载条件
//int func(double a, int b)
//{
    
    
//	cout << "func (double a ,int b)的调用!" << endl;
//}


int main() {
    
    

	func();
	func(10);
	func(3.14);
	func(10,3.14);
	func(3.14 , 10);
	
	system("pause");

	return 0;
}

Nota sobre la sobrecarga de funciones:

  • Las referencias se pueden utilizar como condiciones de sobrecarga.
  • La sobrecarga de funciones encuentra parámetros predeterminados de funciones

Ejemplo:

//函数重载注意事项
//1、引用作为重载条件
void func(int &a)
{
    
    
	cout << "func (int &a) 调用 " << endl;
}

void func(const int &a)
{
    
    
	cout << "func (const int &a) 调用 " << endl;
}


//2、函数重载碰到函数默认参数

void func2(int a, int b = 10)
{
    
    
	cout << "func2(int a, int b = 10) 调用" << endl;
}

void func2(int a)
{
    
    
	cout << "func2(int a) 调用" << endl;
}

int main() {
    
    
	
	int a = 10;
	func(a); //调用无const
	func(10);//调用有const,并且int& a = 10也不合法,但是const int& a = 10,相当于创建了一个临时的变量


	//func2(10); //碰到默认参数产生歧义,不知道调用哪个了,需要避免这种情况。有函数重载的时候尽量不要使用默认参数。这里传两个参数可以。

	system("pause");

	return 0;
}

El núcleo de la sobrecarga de funciones: no puede haber ambigüedad al llamar a una función.

2.4 Clases y Objetos

C++El contenido más importante en la orientación a objetos:encapsulación,heredar,polimorfismo. Comencemos con la encapsulación.
Paquete:

La encapsulación es C++una de las tres características principales de la orientación a objetos.El
significado de la encapsulación:

  • Atributos y comportamientos como un todo, que representan cosas en la vida.
  • Controle atributos y comportamientos con permisos

Significado del paquete:

Al diseñar una clase, los atributos y los comportamientos se escriben juntos para representar cosas

gramática: class 类名{ 访问权限: 属性 / 行为 };

Mire un ejemplo de una clase simple: diseñe una clase de círculo, encuentre la circunferencia del círculo

//圆周率
const double PI = 3.14;

//1、封装的意义
//将属性和行为作为一个整体,用来表现生活中的事物

//封装一个圆类,求圆的周长
//class代表设计一个类,后面跟着的是类名
class Circle
{
    
    
public:  //访问权限  公共的权限

	//属性
	int m_r;//半径

	//行为
	//获取到圆的周长
	double calculateZC()
	{
    
    
		//2 * pi  * r
		//获取圆的周长
		return  2 * PI * m_r;
	}
};

int main() {
    
    
	//通过圆类,创建圆的对象
	// c1就是一个具体的圆
	Circle c1;
	c1.m_r = 10; //给圆对象的半径 进行赋值操作

	//2 * pi * 10 = = 62.8
	cout << "圆的周长为: " << c1.calculateZC() << endl;

	system("pause");

	return 0;
}

Diseñe una clase de estudiante, los atributos incluyen nombre y número de estudiante, puede asignar valores al nombre y número de estudiante, y puede mostrar el nombre y número de estudiante.

//学生类
class Student {
    
    
public:
	void setName(string name) {
    
    
		m_name = name;
	}
	void setID(int id) {
    
    
		m_id = id;
	}

	void showStudent() {
    
    
		cout << "name:" << m_name << " ID:" << m_id << endl;
	}
public:
	string m_name;
	int m_id;
};

int main() {
    
    

	Student stu;
	stu.setName("德玛西亚");
	stu.setID(250);
	stu.showStudent();

	system("pause");

	return 0;
}

Acceso a clases y objetos: incluyendo funciones y variables

  1. se puede acceder a los miembros de permisos públicos públicos dentro de la clase y se puede acceder fuera de la clase, generalmente se usa para interfaces externas
  2. la clase de miembro de permiso protegido puede acceder al contenido protegido en la clase principal cuando el hijo no puede acceder a la herencia
  3. Se puede acceder a la clase de miembro de permiso privado privado fuera de la clase y no se puede acceder. Al heredar, el hijo no puede acceder al contenido privado en la clase principal

Veamos un ejemplo:

//三种权限
//公共权限  public     类内可以访问  类外可以访问
//保护权限  protected  类内可以访问  类外不可以访问
//私有权限  private    类内可以访问  类外不可以访问

class Person
{
    
    
	//姓名  公共权限
public:
	string m_Name;

	//汽车  保护权限
protected:
	string m_Car;

	//银行卡密码  私有权限
private:
	int m_Password;

public:
	void func()
	{
    
    
		m_Name = "张三";
		m_Car = "拖拉机";
		m_Password = 123456;
	}
};

int main() {
    
    

	Person p;
	p.m_Name = "李四";
	//p.m_Car = "奔驰";  //保护权限类外访问不到
	//p.m_Password = 123; //私有权限类外访问不到

	system("pause");

	return 0;
}


structLa diferencia entre los permisos protegidos y los permisos privados a los que no se puede acceder fuera de la clase class:
A partir de la definición de la clase, se puede ver que la definición de estructura y clase es muy similar.

En C++ structy classla única diferencia es queLos derechos de acceso predeterminados son diferentes

la diferencia:

  • structEl permiso predeterminado es public, si el miembro no agrega un modificador de permiso, el predeterminado espublic
  • classEl permiso predeterminado es privado, si el miembro no agrega un modificador de permiso, el predeterminado esprivate
class C1
{
    
    
	int  m_A; //默认是私有权限
};

struct C2
{
    
    
	int m_A;  //默认是公共权限
};

int main() {
    
    

	C1 c1;
	c1.m_A = 10; //错误,访问权限是私有

	C2 c2;
	c2.m_A = 10; //正确,访问权限是公共

	system("pause");

	return 0;
}

También se puede ver en el ejemplo anterior que
establecer las propiedades de los miembros como privadas: esto generalmente se hace en proyectos, y la ventaja de las clases es la privacidad.

Ventaja 1: establezca todos los atributos de los miembros como privados y usted mismo puede controlar los permisos de lectura y escritura.
Ventaja 2: para los permisos de escritura, podemos verificar la validez de los datos.

class Person {
    
    
public:

	//姓名设置可读可写
	void setName(string name) {
    
    
		m_Name = name;
	}
	string getName()
	{
    
    
		return m_Name;
	}

	//设置年龄
	void setAge(int age) {
    
    
		if (age < 0 || age > 150) {
    
    
			cout << "请输入正确的年龄!" << endl;
			return;
		}
		m_Age = age;
	}

	//获取年龄 
	int getAge() {
    
    
		return m_Age;
	}
	
	//爱人设置为只写
	void setLover(string lover) {
    
    
		m_Lover = lover;
	}

private:
	string m_Name; //可读可写  姓名
	int m_Age; //只读  年龄
	string m_Lover; //只写  爱人
};


int main() {
    
    

	Person p;
	//姓名设置
	p.setName("张三");
	cout << "姓名: " << p.getName() << endl;

	//年龄设置
	p.setAge(50);
	cout << "年龄: " << p.getAge() << endl;

	//爱人设置
	p.setLover("xh");
	//cout << "爱人: " << p.m_Lover << endl;  //只写属性,不可以读取

	system("pause");

	return 0;
}

Una clase puede tener otra clase como miembro, vea el ejemplo a continuación.

#include<iostream>
using namespace std;

//点和圆的关系
// 点类
class Point
{
    
    
public:
	//设置x坐标
	void setX(int x)
	{
    
    
		m_X = x;
	}
	//获取x坐标
	int getX()
	{
    
    
		return m_X;
	}
	//设置Y坐标
	void setY(int y)
	{
    
    
		m_Y = y;
	}
	//获取Y坐标
	int getY()
	{
    
    
		return m_Y;
	}

private:
	int m_X;
	int m_Y;

};

//圆类
class Circle
{
    
    
private:
	int m_R;
	Point m_Center;

public:
	//设置半径
	void setR(int r)
	{
    
    
		m_R = r;
	}
	//获取半径
	int getR()
	{
    
    
		return m_R;
	}
	//设置圆心
	void setCenter(Point center)
	{
    
    
		m_Center = center;
	}
	//获取圆心
	Point getCenter()
	{
    
    
		return m_Center;
	}
};

//判断点和圆的关系
void isInCircle(Circle& c, Point& p)
{
    
    
	//计算两点之间距离平方
	int distance =
		(c.getCenter().getX() - p.getX()) * (c.getCenter().getX() - p.getX()) +
		(c.getCenter().getY() - p.getY()) * (c.getCenter().getY() - p.getY());
	//计算半径平方
	int rDistance = c.getR() * c.getR();
	//判断关系
	if (distance == rDistance)
	{
    
    
		cout << "点在圆上" << endl;
	}
	else if (distance > rDistance)
	{
    
    
		cout << "点在圆外" << endl;
	}
	else
	{
    
    
		cout << "点在圆内" << endl;
	}
}

int main() {
    
    
	//创建圆
	Circle c;
	c.setR(10);
	Point center;
	center.setX(10);
	center.setY(10);
	c.setCenter(center);
	//创建点
	Point p;
	p.setX(10);
	p.setY(10);
	//判断关系
	isInCircle(c, p);

	system("pause");
	return 0;
}

  ¿El código anterior se ve desordenado, sin ningún nivel, lo cual es muy desfavorable para el desarrollo de ingeniería? Ordenémoslo por tipo de archivo.
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí
Después de terminar lo anterior, ¿el código se ve mucho más conciso?

2.4.1 Inicialización y limpieza de objetos

2.4.2 Constructor y destructor

La inicialización y limpieza de objetos también son dos problemas de seguridad muy importantes.

​ Un objeto o variable no tiene un estado inicial, y se desconocen las consecuencias de su uso,
del mismo modo, después de usar un objeto o variable, si no se limpia a tiempo, también causará ciertos problemas de seguridad.

C++ utiliza constructores y destructores para resolver los problemas anteriores. El compilador llamará automáticamente a estas dos funciones para completar la inicialización y limpieza del objeto.
La inicialización y limpieza de objetos es lo que el compilador nos obliga a hacer, por lo que si no proporcionamos construcción y destrucción, el compilador proporcionará

Los constructores y destructores proporcionados por el compilador son implementaciones vacías.

  • Constructor: La función principal es asignar valores a las propiedades miembro del objeto al crear el objeto.El constructor es determinado por el compiladorllamada automática, no es necesario llamar manualmente.
  • Destructor: el papel principal está en el objeto.antes de la destrucciónEl sistema llama automáticamente para realizar algún trabajo de limpieza.

Sintaxis del constructor:类名(){}

  1. Constructor, sin valor de retorno y sin escritura.void
  2. El nombre de la función es el mismo que el nombre de la clase.
  3. Los constructores pueden tener parámetros, por lo que puede ocurrir una sobrecarga
  4. Cuando el programa llama al objeto, lo harállamada automáticaConstrucción, no es necesario llamar manualmente, y solo se llamará una vez

Sintaxis del destructor: ~类名(){}

  1. Destructor, sin valor de retorno y sin escritura.void
  2. El nombre de la función es el mismo que el nombre de la clase, prefije el nombre con un símbolo~
  3. Los destructores no pueden tener parámetros, por lo que no se puede producir una sobrecarga.
  4. El programa llamará automáticamente al destructor antes de que se destruya el objeto, no es necesario llamarlo manualmente, y solo se llamará una vez

Núcleo: uno es responsable de la inicialización y el otro es responsable de la destrucción.

class Person
{
    
    
public:
	//构造函数
	Person()
	{
    
    
		//如果自己不写,系统默认为空,这里什么都不写
		cout << "Person的构造函数调用" << endl;
	}
	//析构函数
	~Person()
	{
    
    
	//如果自己不写,系统默认为空,这里什么都不写
		cout << "Person的析构函数调用" << endl;
	}

};
//构造和析构都是必须有的实现,如果我们自己不提供,编译器会提供一个空实现的构造和析构
void test01()
{
    
    
	Person p; //在栈上的数据,test01执行完毕后释放对象。如果把创建对象放到main函数中,要等程序运行完才释放,即对象销毁后才释放
}

int main() {
    
    
	test01();
	system("pause");
	return 0;
}

2.4.3 Clasificación y convocatoria de constructor

Dos métodos de clasificación:

  1. Según los parámetros se divide en: construcción con parámetros y construcción sin parámetros (por defecto) construcción
  2. Dividido por tipo: construcción ordinaria y construcción de copia

Tres métodos de llamada:

  1. Soportes
  2. método de visualización
  3. método de conversión implícito
//1、构造函数分类
// 按照参数分类分为 有参和无参构造   无参又称为默认构造函数
// 按照类型分类分为 普通构造和拷贝构造

class Person {
    
    
public:
	//无参(默认)构造函数
	Person() {
    
    
		cout << "无参构造函数!" << endl;
	}
	//有参构造函数
	Person(int a) {
    
    
		age = a;
		cout << "有参构造函数!" << endl;
	}
	//拷贝构造函数,拷贝构造顾名思义拷贝一份类对象,但是又不能改变本体,拷贝的同时按照引用的方式传值
	Person(const Person& p) {
    
    
		//将传入的人身上的所有属性,拷贝到我身上
		age = p.age;
		cout << "拷贝构造函数!" << endl;
	}
	//析构函数
	~Person() {
    
    
		cout << "析构函数!" << endl;
	}
public:
	int age;
};

//2、构造函数的调用
//调用无参构造函数
void test01() {
    
    
	Person p; //调用无参构造函数
}

//调用有参的构造函数
void test02() {
    
    

	//2.1  括号法,常用,推荐使用
	Person p1; //默认构造函数
	Person p11(10);//有参构造函数
	Person p12(p1);//拷贝构造函数,传对象
	//注意1:调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明,不认为是创建对象
	//Person p2();

	//2.2 显式法
	Person p2; //默认构造函数
	Person p21 = Person(10); //有参构造函数,可以理解成Person p21(10)
	Person p22 = Person(p2); //拷贝构造函数,可以理解成Person p22(p2)
	//Person(10)单独写就是匿名对象,当前行结束之后,马上析构,即这个对象调用完,下面的函数还没执行就已经销毁了
	//不要用拷贝构造函数初始化匿名对象:Person(p2),会提示Person p2重定义,因为此时编译器会认为 Person p2;上面已经有一个了

	//2.3 隐式转换法
	Person p4 = 10; // Person p4 = Person(10); 有参构造函数
	Person p5 = p4; // Person p5 = Person(p4); 拷贝构造函数

	//注意2:不能利用 拷贝构造函数 初始化匿名对象 编译器认为是对象声明
	//Person (p4);
}

int main() {
    
    
	test01();
	//test02();
	system("pause");
	return 0;
}

2.4.4 Cuándo llamar al constructor de copias

Tres veces:

  • Inicializar un nuevo objeto usando un objeto ya creado
  • El método de pasar valores es pasar valores a parámetros de funciones, tenga en cuenta que no es una referencia
  • Devuelve el objeto local por valor, tenga en cuenta que no es una referencia
class Person {
    
    
public:
	//默认(无参)构造
	Person() {
    
    
		cout << "无参构造函数!" << endl;
		mAge = 0;
	}
	//有参构造
	Person(int age) {
    
    
		cout << "有参构造函数!" << endl;
		mAge = age;
	}
	//拷贝构造,将传过来的对象的数据全部拷贝一份
	Person(const Person& p) {
    
    
		cout << "拷贝构造函数!" << endl;
		mAge = p.mAge;
	}
	//析构函数在释放内存之前调用
	~Person() {
    
    
		cout << "析构函数!" << endl;
	}
public:
	int mAge;
};
//拷贝函数调用时机
//1. 使用一个已经创建完毕的对象来初始化一个新对象
void test01() {
    
    

	Person man(100); //p对象已经创建完毕
	Person newman(man); //调用拷贝构造函数
	Person newman2 = man; //拷贝构造

	//Person newman3;
	//newman3 = man; //不是调用拷贝构造函数,赋值操作
}

//2. 值传递的方式给函数参数传值
//相当于Person p1 = p; 
void doWork(Person p1) {
    
    }
void test02() {
    
    
	Person p; //无参构造函数
	//这里会调用拷贝构造函数,实参传给形参的时候会调用拷贝构造,但是doWork里面的p不会改变 这里定义的p,因为拷贝的是临时的副本,不一个内存空间
	doWork(p);
}

//3. 以值方式返回局部对象,局部对象有个特点,函数执行完立即释放
Person doWork2()
{
    
    
	Person p1;
	cout << (int *)&p1 << endl;
	//以值的方式返回,这里返回的并不是p1本身,而是根据p1来创建一个新的对象再返回
	return p1;
}

void test03()
{
    
    
	//函数执行完会调用析构函数
	Person p = doWork2();
	cout << (int *)&p << endl;
}


int main() {
    
    

	//test01();
	//test02();
	test03(); 	//执行完之后会有两个拷贝构造和两个析构函数打印出来。这里的析构函数要等程序运行完才会执行
	system("pause");
	return 0;
}

Reglas de llamada de constructores:
de forma predeterminada, siempre que se escriba una clase, c++el compilador agregará al menos una clase3 funciones

1. Constructor predeterminado (sin parámetros, el cuerpo de la función está vacío)
2. Destructor predeterminado (sin parámetros, el cuerpo de la función está vacío)
3. El constructor de copia predeterminado, que copia el valor del atributo, incluso si no está escrito por sí mismo, el compilador lo escribirá por sí mismo.

Por ejemplo, en el siguiente código, incluso si el constructor de copia no está escrito, el Person p2(p1);constructor de copia se creará automáticamente en la clase cuando se ejecute esta oración, y p1el atributo se asignará a p2, y el compilador hará una copia del atributo

void test01()
{
    
    
	Person p1(18);
	//如果不写拷贝构造,编译器会自动添加拷贝构造,并且做浅拷贝操作
	Person p2(p1);

	cout << "p2的年龄为: " << p2.age << endl;
}
  • Si se escribe un constructor con parámetros en la clase, pero no se escribe un constructor predeterminado (sin parámetros), el compilador no proporcionará un constructor predeterminado sin parámetros. Si se llama al constructor predeterminado, se informará un error, pero si no se escribe la construcción de copia, todavía se proporcionará un constructor de copia, se ha dado un ejemplo arriba. Es decir, después de escribir una estructura parametrizada, el compilador ya no proporciona una estructura predeterminada.
  • Si solo el constructor de copia está escrito en la clase, pero el constructor predeterminado y el constructor parametrizado no están escritos, entonces el compilador ya no lo proporcionará Si el constructor predeterminado y el constructor parametrizado se vuelven a llamar, se informará un error. Es decir, después de escribir la construcción de la copia, ya no se proporciona la otra.

Tome un ejemplo de la pregunta anterior:

class Person {
    
    
public:
	//无参(默认)构造函数
	Person() {
    
    
		cout << "无参构造函数!" << endl;
	}
	//有参构造函数
	Person(int a) {
    
    
		age = a;
		cout << "有参构造函数!" << endl;
	}
	//拷贝构造函数
	Person(const Person& p) {
    
    
		age = p.age;
		cout << "拷贝构造函数!" << endl;
	}
	//析构函数
	~Person() {
    
    
		cout << "析构函数!" << endl;
	}
public:
	int age;
};

void test01()
{
    
    
	Person p1(18);
	//如果不写拷贝构造,编译器会自动添加拷贝构造,并且做浅拷贝操作
	Person p2(p1);

	cout << "p2的年龄为: " << p2.age << endl;
}

void test02()
{
    
    
	//如果用户提供有参构造,编译器不会提供默认构造,会提供拷贝构造
	Person p1; //此时如果用户自己没有提供默认构造,会出错
	Person p2(10); //用户提供的有参
	Person p3(p2); //此时如果用户没有提供拷贝构造,编译器会提供

	//如果用户提供拷贝构造,编译器不会提供其他构造函数
	Person p4; //此时如果用户自己没有提供默认构造,会出错
	Person p5(10); //此时如果用户自己没有提供有参,会出错
	Person p6(p5); //用户自己提供拷贝构造
}

int main() {
    
    
	test01();
	system("pause");
	return 0;
}

Resumen:
la construcción predeterminada (sin parámetros), la construcción con parámetros y la construcción de copia son tres constructores. Solo se proporcionan los últimos. Mientras los constructores anteriores no estén escritos por sí mismos, el compilador no los proporcionará.

2.4.5 Copia profunda y copia superficial

Este punto de conocimiento es muy importante, ya menudo se pregunta durante las entrevistas.

  • Copia superficial : operación de copia de asignación simple en el compilador
  • Copia profunda : vuelva a solicitar espacio en el área del montón y realice operaciones de copia
class Person {
    
    
public:
	//无参(默认)构造函数
	Person() {
    
    
		cout << "无参构造函数!" << endl;
	}
	//有参构造函数
	Person(int age ,int height) {
    
    
		
		cout << "有参构造函数!" << endl;
		m_age = age;
		m_height = new int(height);
		
	}
	//拷贝构造函数  
	/*
	Person(const Person& p) {
		cout << "拷贝构造函数!" << endl;
		//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
		m_age = p.m_age;
		m_height = new int(*p.m_height);
		
	}
	*/

	//析构函数
	~Person() {
    
    
	//析构函数通常用来释放堆区中的数据
		cout << "析构函数!" << endl;
		if (m_height != NULL)
		{
    
    
			delete m_height;
		}
	}
public:
	int m_age;
	//创建一个指针为了上面把身高放在堆区。
	int* m_height;
};

void test01()
{
    
    
	Person p1(18, 180);
	Person p2(p1);

	cout << "p1的年龄: " << p1.m_age << " 身高: " << *p1.m_height << endl;
	cout << "p2的年龄: " << p2.m_age << " 身高: " << *p2.m_height << endl;
}

int main() {
    
    
	test01();
	system("pause");
	return 0;
}

Si ejecuta el código anterior,
inserte la descripción de la imagen aquí
encontrará los siguientes resultados: Veamos por qué se informa este error: La razón
inserte la descripción de la imagen aquí
  por la cual el código anterior tiene un problema es porque hay un problema con la altura, que se crea en el área del montón. la parte inferior, y los datos en el área del montón se copian de forma predeterminada cuando se crea el objeto , es decir, los datos en el área del montón se copian en . Una vez que el programa termina de ejecutarse, debe ejecutar el destructor para liberar la suma , pero la suma se coloca en la pila, los datos de la pila se liberan primero y luego se liberan los datos de la pila. los datos en el área del montón se liberan repetidamente y se genera un conflicto. ¿El motivo del problema es causado por la copia superficial del constructor de copias? ¿Cómo resolverlo? Este problema se puede resolver utilizando la prueba profunda. Se puede resolver cancelando los comentarios anteriores, implementando el constructor de copia usted mismo, resolviendo los problemas causados ​​por la copia superficial y no utilizando la copia de construcción predeterminada del sistema. Como se muestra en el código de la figura anterior, vuelva a abrir un área de montón para almacenar los datos copiados. Resumir:p1p1p2p1p2p2p1p2p1p2p2p1/* */
inserte la descripción de la imagen aquí

Si el atributo se abre en el área del montón , debe proporcionar un constructor de copias usted mismo para evitar problemas causados ​​por copias superficiales .

2.4.6 Lista de inicialización

C++ proporciona una sintaxis de lista de inicializadores para inicializar propiedades. Recomendamos esta forma de escribir en ingeniería. Tenga en cuenta la ubicación de los dos puntos.
gramática:构造函数():属性1(值1),属性2(值2)... {}

class Person {
    
    
public:

	传统方式初始化,通常我们都是使用这种有参构造初始化,但是不推荐
	//Person(int a, int b, int c) {
    
    
	//	m_A = a;
	//	m_B = b;
	//	m_C = c;
	//}

	//初始化列表方式初始化,工程中推荐用法,这样也可以在构造函数里面写一下其他的语法,这里一定注意冒号的位置
	Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) //相当于m_A = a, m_B = b, m_C = c
	{
    
    
	
	}
	void PrintPerson() {
    
    
		cout << "mA:" << m_A << endl;
		cout << "mB:" << m_B << endl;
		cout << "mC:" << m_C << endl;
	}
private:
	int m_A;
	int m_B;
	int m_C;
};

int main() {
    
    

	Person p(1, 2, 3);
	p.PrintPerson();


	system("pause");

	return 0;
}

2.4.7 Objetos de clase como miembros de clases

C++Un miembro de una clase puede ser un objeto de otra clase, llamamos a este miembro miembro de un objeto . En la relación entre el punto y el círculo de arriba, en realidad se ha mostrado una vez.

class Phone
{
    
    
public:
	Phone(string name)
	{
    
    
		m_PhoneName = name;
		cout << "Phone构造" << endl;
	}

	~Phone()
	{
    
    
		cout << "Phone析构" << endl;
	}

	string m_PhoneName;

};

class Person
{
    
    
public:

	//初始化列表可以告诉编译器调用哪一个构造函数
	/*Phone是一个类,这里给pName传过来的是个字符串,但是没有报错是因为什么呢?因为这里相当于隐式转换法
	Phone m_Pname = pName。这里就展示了类对象也可以用初始化列表的方式赋初值。*/
	Person(string name, string pName) :m_Name(name), m_Phone(pName)
	{
    
    
		cout << "Person构造" << endl;
	}

	~Person()
	{
    
    
		cout << "Person析构" << endl;
	}

	void playGame()
	{
    
    
		cout << m_Name << " 使用" << m_Phone.m_PhoneName << " 牌手机! " << endl;
	}

	string m_Name;
	Phone m_Phone;

};
void test01()
{
    
    
	//当类中成员是其他类对象时,我们称该成员为对象成员
	//构造的顺序是 :先调用对象成员的构造,再调用本类构造
	//析构顺序与构造相反
	Person p("张三" , "苹果X");
	p.playGame();

}

int main() {
    
    
	test01();
	system("pause");
	return 0;
}

Resumen:
la conversión de tipo implícita se produce al pasar

2.4.8 Miembros estáticos

Los miembros estáticos se dividen en:
Variables de miembros estáticos:

  • Todos los objetos comparten los mismos datos.
  • La memoria se asigna durante la fase de compilación y la memoria se asigna antes de que se ejecute el programa .
  • Declaración en clase, inicialización fuera de clase
  • se puede acceder por nombre de clase

Función miembro estática:

  • Todos los objetos comparten la misma función.
  • Las funciones de miembros estáticos solo pueden acceder a variables de miembros estáticos
  • se puede acceder por nombre de clase

Primero mire las variables miembro estáticas:

class Person
{
    
    
	
public:
	//静态成员变量特点:
	//1 在编译阶段分配内存
	//2 所有对象共享同一份数据
	//3 类内声明,类外初始化,必须要做的,否则没法访问
	static int m_A; //静态成员变量

};
//类外初始化,注意初始化的时候作用域
int Person::m_A = 10;

void test01()
{
    
    
	//静态成员变量两种访问方式

	//1、通过对象
	Person p1;
	p1.m_A = 100;
	cout << "p1.m_A = " << p1.m_A << endl;

	Person p2;
	p2.m_A = 200;
	cout << "p1.m_A = " << p1.m_A << endl; //共享同一份数据,输出200
	cout << "p2.m_A = " << p2.m_A << endl;//输出200
}

int main() {
    
    
	test01();
	system("pause");
	return 0;
}

La declaración en clase anterior y la inicialización fuera de clase significan que debe definirse en la clase y asignarse fuera de la clase, pero preste atención a la clase en la que se encuentra el espacio de declaración.
inserte la descripción de la imagen aquí
Mira el código completo, presta atención a la sección de comentarios:

class Person
{
    
    
	
public:

	static int m_A; //静态成员变量

	//静态成员变量特点:
	//1 在编译阶段分配内存
	//2 类内声明,类外初始化
	//3 所有对象共享同一份数据

private:
	static int m_B; //静态成员变量也是有访问权限的,外部无法访问
};
int Person::m_A = 10;
int Person::m_B = 10;

void test01()
{
    
    
	//静态成员变量不属于某个对象,所有对象都共享同一份数据
	//静态成员变量两种访问方式
	//1、通过对象,其实这里创不创建对象没任何意义,因为静态成员本身不属于任何对象
	Person p1;
	p1.m_A = 100;
	cout << "p1.m_A = " << p1.m_A << endl;

	Person p2;
	p2.m_A = 200;
	cout << "p1.m_A = " << p1.m_A << endl; //共享同一份数据
	cout << "p2.m_A = " << p2.m_A << endl;

	//2、通过类名,因为静态成员本身不属于任何对象
	cout << "m_A = " << Person::m_A << endl;


	//cout << "m_B = " << Person::m_B << endl; //私有权限访问不到
}

int main() {
    
    

	test01();
	system("pause");
	return 0;
}

Veamos de nuevo la función miembro estática:

  • Los programas comparten una función.
  • Las funciones de miembro estático solo pueden acceder a variables de miembro estático
    y se puede acceder a través del nombre de clase

inserte la descripción de la imagen aquí
  ¿Por qué las funciones de miembros estáticos no dan acceso a propiedades de miembros no estáticos? Debido a que los datos de la función miembro estática solo tienen una copia en la memoria, lo anterior m_Bno es una variable miembro no estática, se debe acceder a ella a través del objeto, cuando llamamos a la función estática anterior, el interior del cuerpo de la función no saber qué objeto se cambia por debajo de m_B. Por ejemplo, se crean dos objetos p1, para llamar a la función de miembro estático anterior, hay uno en la función , en el cuerpo de la función estática, no sé cómo cambiarlo p2, aunque se está llamando a la función de miembro estático , pero en el cuerpo de la función no se refleja que este sea un miembro, y es lo mismo por la misma razón. Puede ser porque no pertenece a ningún objeto.p1m_B=200m_Bp1200p1p1p2m_A

Las funciones miembro estáticas también tienen derechos de acceso:

class Person
{
    
    

public:

	//静态成员函数特点:
	//1 程序共享一个函数
	//2 静态成员函数只能访问静态成员变量
	
	static void func()
	{
    
    
		cout << "func调用" << endl;
		m_A = 100;
		//m_B = 100; //错误,不可以访问非静态成员变量
	}

	static int m_A; //静态成员变量
	int m_B; // 
private:

	//静态成员函数也是有访问权限的,类外无法访问
	static void func2()
	{
    
    
		cout << "func2调用" << endl;
	}
};
int Person::m_A = 10;


void test01()
{
    
    
	//静态成员变量两种访问方式

	//1、通过对象,没特殊意义,因为函数不属于任何对象
	Person p1;
	p1.func();

	//2、通过类名,因为函数不属于任何对象,可以直接通过类名访问
	Person::func();


	//Person::func2(); //类外访问不到私有权限静态成员函数
}

int main() {
    
    

	test01();

	system("pause");

	return 0;
}

2.4.9 Modelo de objetos de C++ y este puntero

2.4.9.1 Las variables miembro y las funciones miembro se almacenan por separado

  1. En C++, las variables miembro y las funciones miembro de una clase se almacenan por separado
  2. solovariable miembro no estáticaEn los objetos que pertenecen a una clase, como las variables miembro estáticas, las funciones miembro estáticas no pertenecen a un objeto determinado y las funciones miembro no estáticas no pertenecen a un objeto de clase. Solo hay una copia de las funciones miembro no estáticas, que se almacenan por separado de las variables miembro estáticas, es decir, las funciones no pertenecen a los objetos de clase.
class Person {
    
    
public:
	Person() {
    
    
		mA = 0;
	}
	//非静态成员变量占对象空间
	int mA;
	//静态成员变量不占对象空间
	static int mB; 
	//函数也不占对象空间,所有函数共享一个函数实例
	void func() {
    
    
		cout << "mA:" << this->mA << endl;
	}
	//静态成员函数也不占对象空间
	static void sfunc() {
    
    
	}
};
int main() {
    
    
	//一个空对象的占员工内存空间是1个字节,即C++编译器会为每个空对象分配一个字节空间,是为了区分空对象的占内存的位置
	//每个空对象也应该有一个独一无二的内存地址
	cout << sizeof(Person) << endl;
	system("pause");
	return 0;
}

  Cuando no hay ningún miembro en un solo objeto, el compilador lo abriráun byteEspacio. Cuando hay una variable miembro no estática en ella, el objeto ocupa el número de bytes de la variable miembro, es decir, la memoria se asigna de acuerdo con la variable miembro.Esta variable miembro no estática pertenece al objeto de la clase. Si hay variables miembro estáticas en la clase, entonces las variables miembro estáticas no pertenecen al objeto de clase y el objeto de clase no abrirá memoria para él. Si hay una función miembro no estática en la clase, no pertenece al objeto de la clase y no le asigna memoria. Si hay una función miembro estática en la clase, no pertenece al objeto de la clase, ni se le asignará memoria. Lo que quiero decir arriba es que las variables miembro y las funciones miembro se almacenan por separado.
inserte la descripción de la imagen aquí
En última instancia, sólovariable miembro no estáticapertenece a la clase objeto. No se asigna memoria a objetos que no pertenecen a la clase.

2.4.9.2 este puntero

  Justo ahora dijimos que algunos miembros definidos en el objeto de clase no pertenecen al objeto de clase, entonces, ¿cómo distinguimos qué objeto está en uso al crear diferentes objetos?
  Sabemos que C++las variables miembro y las funciones miembro se almacenan por separado, y cada función miembro no estática solo generará una instancia de función, lo que significa que varios objetos del mismo tipo compartirán una pieza de código.

Entonces, la pregunta es: ¿cómo distingue este código qué objeto se llama a sí mismo?

c++Al proporcionar un puntero de objeto especial, thispuntero ( un significado Pythonen la raíz self), resuelve los problemas anteriores. El puntero this apunta al objeto al que pertenece la función miembro llamada , y quienquiera que lo llame apunta a quién .
thisUn puntero es un puntero que está implícito en cada función miembro no estática. La función miembro no estática tiene un puntero this por defecto. Se puede usar directamente sin definirlo usted mismo.
thisNo es necesario definir el puntero, se puede usar directamente para usar
este puntero:

  • Cuando el parámetro formal y la variable miembro tienen el mismo nombre, se pueden thisdistinguir mediante punteros. resolver conflictos de nombres
  • Para devolver el objeto en sí mismo en una función miembro no estática de una clase, usedevolver*this. devolver el objeto en sí

vamos a verconflicto de nombresUn ejemplo de:

class Person
{
    
    
public:
	/*
	这里在编译的时候不会出现错误,但是最终的程序输出结果不是10,这里想做的是把传过来的值赋值给成员变量age,
	但是呢成员变量和age同名,出现了名称冲突。规范写法应该是区分开来,成员属性前面加个m,即m_age,表示成员属性,
	即下面的/**/注释部分写法。或者通过this指针去解决。
	 */
	Person(int age)
	{
    
    
		age = age;
	}
	int age;
	/*
		Person(int age)
	{
		m_age = age;
	}
	int m_age;*/
};

void test01()
{
    
    
	Person p1(10);
	cout << "p1.age = " << p1.age << endl;
}

int main() {
    
    
	test01();
	system("pause");
	return 0;
}

inserte la descripción de la imagen aquí
La segunda solución: thisresolviendo.

class Person
{
    
    
public:
	Person(int age)
	{
    
    
		//1、当形参和成员变量同名时,可用this指针来区分
		//this指针指向被调用成员函数所属的对象,此时的this就相当于p1.
		this->age = age;
	}
	int age;
};

void test01()
{
    
    
	Person p1(10);
	cout << "p1.age = " << p1.age << endl;
}

int main() {
    
    
	test01();
	system("pause");
	return 0;
}

vea abajoobjeto de retornoUso por sí mismo *this:

class Person
{
    
    
public:

	Person(int age)
	{
    
    
		//1、当形参和成员变量同名时,可用this指针来区分
		this->age = age;
	}
	/*注意,这里返回的是引用,返回的还是对象本身,如果这里返回的不是引用直接Person而不是Person&,即直接返回值,那么下面的代码
	p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);输出结果还是20。为什么呢?因为如果这里返回的是值的
	话,在调用完p2.PersonAddPerson(p1)第一次之后p2的年龄加了10岁,这里没问题,但是呢这里返回的已经不是p2的本体了,而是按照本
	体创建了一个新的数据调用了拷贝构造函数,我们知道在拷贝构造函数调用构造函数时,用值的方式返回会复制一份新的数据出来,相当于这
	里的person已经和自身的不一样了。即每次返回的都是一个新的东西,跟原来已经不一样了。*/
	Person& PersonAddPerson(Person p)
	{
    
    
		this->age += p.age;
		//返回对象本身
		//this指向对象本身,而*this指向的是p2这个对象本体
		return *this;
	}

	int age;
};

void test01()
{
    
    
	Person p1(10);
	cout << "p1.age = " << p1.age << endl;

	Person p2(10);
	//上面如果改成返回值的话这里输出就是20,因为值返回是拷贝构造创建新的对象,值一直都是10,加10返回就是20。
	p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);
	cout << "p2.age = " << p2.age << endl;
}

int main() {
    
    

	test01();
	system("pause");
	return 0;
}

inserte la descripción de la imagen aquí
La diferencia entre el valor de retorno de la función y la referencia de retorno: devolver por valor copiará un nuevo dato, mientras que la referencia se devuelve a sí misma. Ideas de programación en cadena.

2.4.9.3 Función miembro de acceso de puntero nulo

En C ++, el puntero nulo también puede llamar a la función miembro, pero también preste atención a si se usa el puntero this.
Si thisse usa el puntero, debe evaluarse para garantizar la solidez del código.

//空指针访问成员函数
class Person {
    
    
public:

	void ShowClassName() {
    
    
		cout << "我是Person类!" << endl;
	}

	void ShowPerson() {
    
    
		//加上这句话,防止下面空指针代码出错
		if (this == NULL) {
    
    
			return;
		}
		//其实这里的mAge默认的是this->mAge,而传入的指针是NULL,不指向任何东西,这里会报错。
		cout << mAge << endl;
	}

public:
	int mAge;
};

void test01()
{
    
    
	//空指针,没有指向任何确切的对象
	Person * p = NULL;
	p->ShowClassName(); //空指针,可以调用成员函数,单独调用这行代码是不会出错的
	//调用这行代码会出错
	p->ShowPerson();  //但是如果成员函数中用到了this指针,就不可以了
}

int main() {
    
    
	test01();
	system("pause");
	return 0;
}

Del código anterior, se puede ver que el puntero nulo puede acceder a los miembros de la clase, pero tenga cuidado al usarlo. Cuando no se usa ningún atributo de miembro en la clase, se puede usar un puntero nulo para acceder al miembro de la clase.

2.4.9.4 función miembro modificada const

Función constante:

  • Después de agregar la función miembro, constllamamos a esta función una función constante
  • Los atributos de los miembros no se pueden modificar en funciones constantes
  • Después de agregar palabras clave al declarar las propiedades de los miembros mutable, aún se pueden modificar en funciones constantes

Objeto constante:

  • constEl objeto se llama un objeto constante antes de declarar el objeto
  • Los objetos constantes solo pueden llamar a funciones constantes

Veamos primero la función constante, este lugar es un poco confuso, vea el siguiente código y comentarios:

class Person {
    
    
public:

	//this指针的本质是一个指针常量,指针的指向不可修改
	//如果想让指针指向的值也不可以修改,需要声明常函数
	//在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改
	void ShowPerson() const 
	{
    
    

		//this = NULL; //this指针是不能修改指针的指向的,但是指向的值是可以修改的,this的本意相当于:Person* const this;
		/*
		但是this指针指向的对象的数据是可以修改的,这里的前提是void ShowPerson() const中的const没加,如果加了也不能修改值了。
		总结:
		如果不加const呢,在这里的this指针就相当于Person * const this,指向不可修改,但是指向的值可以修改,
		如果加了const呢,就相当于const Person * const this,即指向不能改,指向的内容也不能改,
		那么这个const加在哪合适呢,编译器想来想去还是加载函数后面吧,于是就有了void ShowPerson() const*/
		this->mA = 100; 
		//const修饰成员函数,表示指针指向的内存空间的数据不能修改,除了mutable修饰的变量
		//变量前面加了mutable,表示可以修改
		//this->m_B = 100;
	}
	
public:
	int m_A;
	mutable int m_B; //可修改 可变的,即使在常函数中也可以修改
};


//const修饰函数
void test01() {
    
    

	Person p;   
	//当利用p去调用成员函数ShowPerson()的时候,类中的this就指向这个p。
	p.ShowPerson();
	cout << person.m_A << endl;
	p.showPerson();

}

int main() {
    
    
	test01();
	system("pause");
	return 0;
}

El código anterior muestra que thisla esencia del puntero es unpuntero constante, la dirección del puntero no se puede modificar.
Veamos de nuevo el objeto constanteconst modificado :

class Person {
    
    
public:
	Person() {
    
    
		m_A = 0;
		m_B = 0;
	}

	//this指针的本质是一个指针常量,指针的指向不可修改
	//如果想让指针指向的值也不可以修改,需要声明常函数
	void ShowPerson() const {
    
    
		//const Type* const pointer;
		//this = NULL; //不能修改指针的指向 Person* const this;
		//this->mA = 100; //但是this指针指向的对象的数据是可以修改的,加上了const就不能改了

		//const修饰成员函数,表示指针指向的内存空间的数据不能修改,除了mutable修饰的变量
		this->m_B = 100;
	}

	void MyFunc() {
    
    
		//mA = 10000;
	}

public:
	int m_A;
	mutable int m_B; //可修改 可变的
};


//const修饰对象  常对象
void test01() {
    
    

	const Person person; //在对象前加上const变为常量对象,不允许修改指针指向的值,对象的属性不允许修改
	cout << person.m_A << endl;
	//person.mA = 100; //常对象不能修改成员变量的值,但是可以访问
	person.m_B = 100; //但是常对象可以修改mutable修饰成员变量

	//常对象访问成员函数
	person.MyFunc(); //常对象能调用const的函数,不能调用普通成员函数,因为普通成员函数可以修改成员变量

}

int main() {
    
    

	test01();
	system("pause");
	return 0;
}

  El atributo del miembro no se puede modificar en la función constante. La razón es que la función constante modifica el puntero const, thisy thisel puntero en sí mismo es una constante de puntero, por lo que después de agregarlo, cosntincluso el valor al que apunta el puntero no se puede modificar. Si insistes en modificarlo, puedes añadirlo mutable. Los objetos constantes solo pueden llamar a funciones constantes.

2.4.10.Tomomoto

  En el programa, algunas funciones especiales o clases fuera de la clase también quieren acceder a algunos atributos privados, por lo que necesita usar la tecnología de amigos. Un amigo debe declarar algunas funciones especiales o algunas clases especiales para acceder a los miembros privados de esta clase como un buen amigo de otra clase.
La palabra clave para un amigo esamigo
Hay tres métodos de implementación de amigos:

  • Funciones globales como amigos
  • clase como amigo
  • función de miembro como amigo

Funciones globales como amigos:

class Building
{
    
    
	//告诉编译器 goodGay全局函数 是 Building类的好朋友,就可以访问类中的私有内容了
	friend void goodGay(Building * building);

public:

	Building()
	{
    
    
		this->m_SittingRoom = "客厅";
		this->m_BedRoom = "卧室";
	}

public:
	string m_SittingRoom; //客厅

private:
	string m_BedRoom; //卧室
};

//全局函数
void goodGay(Building * building)
{
    
    
	cout << "好基友正在访问: " << building->m_SittingRoom << endl;
	//上面类里面如果没有friend void goodGay(Building * building)这个声明就会报错,无法访问私有属性。
	cout << "好基友正在访问: " << building->m_BedRoom << endl;
}


void test01()
{
    
    
	Building b;
	goodGay(&b);
}

int main(){
    
    

	test01();

	system("pause");
	return 0;
}

Como puede ver arriba, siempre que coloque la función global delante de la clase friend, puede acceder a las propiedades privadas de la
clase.La clase es un amigo:
el propósito de un amigo es permitir que una clase acceda a miembros privados. en otra clase.

class Building; //为了防止下面写Building类的时候报错
class goodGay
{
    
    
public:
	//函数具体内容写在外面,这样代码看着更简洁
	goodGay();
	void visit();
private:
	Building *building;
};

class Building
{
    
    
	//告诉编译器 goodGay类是Building类的好朋友,可以访问到Building类中私有内容
	friend class goodGay;
public:
	Building();

public:
	string m_SittingRoom; //客厅
private:
	string m_BedRoom;//卧室
};
//在类外声明构造函数,注意加上作用于,哪个类下面的。下面使用了this指针,如果使用初始化列表的方式就不能使用this指针了
Building::Building()
{
    
    
	this->m_SittingRoom = "客厅";
	this->m_BedRoom = "卧室";
}
//类外声明构造函数,注意哪个类下面的
goodGay::goodGay()
{
    
    
	building = new Building;
}
//类外声明成员函数,注意哪个类下面的
void goodGay::visit()
{
    
    
	cout << "好基友正在访问" << building->m_SittingRoom << endl;
	cout << "好基友正在访问" << building->m_BedRoom << endl;
}

void test01()
{
    
    
	goodGay gg;
	gg.visit();
}

int main(){
    
    
	test01();
	system("pause");
	return 0;
}

Hay una nota implícita arriba, sobre si se puede usar cuando se inicializa el constructor, thispuede consultar las siguientes dos publicaciones de blog:

Los miembros funcionan como amigos:

class Building;
class goodGay
{
    
    
public:
	goodGay();
	void visit(); //只让visit函数作为Building的好朋友,可以发访问Building中私有内容
	void visit2(); 
private:
	Building *building;
};

class Building
{
    
    
	//告诉编译器  goodGay类中的visit成员函数 是Building好朋友,可以访问私有内容
	friend void goodGay::visit();
public:
	Building();

public:
	string m_SittingRoom; //客厅
private:
	string m_BedRoom;//卧室
};

Building::Building()
{
    
    
	this->m_SittingRoom = "客厅";
	this->m_BedRoom = "卧室";
}

goodGay::goodGay()
{
    
    
	building = new Building;
}

void goodGay::visit()
{
    
    
	cout << "好基友正在访问" << building->m_SittingRoom << endl;
	cout << "好基友正在访问" << building->m_BedRoom << endl;
}

void goodGay::visit2()
{
    
    
	cout << "好基友正在访问" << building->m_SittingRoom << endl;
	//cout << "好基友正在访问" << building->m_BedRoom << endl;
}

void test01()
{
    
    
	goodGay  gg;
	gg.visit();
}

int main(){
    
    
	test01();
	system("pause");
	return 0;
}

2.4.11 Sobrecarga del operador

Más sobrecarga de operadores:
  concepto de sobrecarga de operadores: redefine el operador existente y dale otra función para adaptarse a diferentes tipos de datos. Por ejemplo, si quiero agregar dos personas, ¿qué significa agregar dos personas? Mire la leyenda a continuación: Para agregar dos objetos, creamos una Person AddPersonfunción miembro/global, pero todos tienen un nombre diferente, por lo que el compilador dio un nombre común operator+, usando la función del compilador. El nombre puede simplificar el método de llamada y usarse directamente +_ Cabe señalar que si la sobrecarga se implementa a través de una función global, se deben pasar dos variables.
inserte la descripción de la imagen aquí
Veamos un ejemplo: ¿
inserte la descripción de la imagen aquí
Qué debo hacer si se informa el error anterior? Hay que solucionarlo sobrecargando.

class Person {
    
    
public:
	Person(int a, int b)
	{
    
    
		this->m_A = a;
		this->m_B = b;
	}
	//成员函数实现 + 号运算符重载
	Person operator+(const Person& p)
	{
    
    
		//创建一个临时的Person变量
		Person temp;
		temp.m_A = this->m_A + p.m_A;
		temp.m_B = this->m_B + p.m_B;
		return temp;
	}

public:
	int m_A;
	int m_B;
};

//全局函数实现 + 号运算符重载
//Person operator+(const Person& p1, const Person& p2) {
    
    
//	Person temp(0, 0);
//	temp.m_A = p1.m_A + p2.m_A;
//	temp.m_B = p1.m_B + p2.m_B;
//	return temp;
//}

//运算符重载 可以发生函数重载 ,实现一个对象加一个整数
Person operator+(const Person& p2, int val)  
{
    
    
	Person temp;
	temp.m_A = p2.m_A + val;
	temp.m_B = p2.m_B + val;
	return temp;
}

void test() {
    
    

	Person p1(10, 10);
	Person p2(20, 20);

	//成员函数方式
	//成员函数的本质调用方法:Person p3 = p1.operator+(p2);
	//简化方法
	Person p3 = p2 + p1;  //相当于 p2.operaor+(p1)
	cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl;
	
	//全局函数重载的本质调用:Person p3 = operator+(p1,p2)
	Person p4 = p3 + 10; //相当于 Person p4 = operator+(p3,10)
	cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl;
	
	//运算符重载也可以发生函数重载
	Person p4 = p3+10;
}

int main() {
    
    
	test();
	system("pause");
	return 0;
}

Resumen 1: es imposible cambiar los operadores de expresiones de tipos de datos incorporados. Por ejemplo, dos números enteros 1+1=2 no se pueden convertir en 1-1=0.
Resumen 2: No abuse de la sobrecarga de operadores Por ejemplo, operator+ es obviamente una operación de suma, pero la función del código es la resta.

Operador de desplazamiento a la izquierda:
  << la sobrecarga puede generar contenido personalizado. Por ejemplo, si desea generar directamente las variables miembro en la salida del nombre de la clase, normalmente es imposible, pero puede hacerlo mediante la sobrecarga.
inserte la descripción de la imagen aquí

class Person {
    
    
	friend ostream& operator<<(ostream& out, Person& p);

public:

	Person(int a, int b)
	{
    
    
		this->m_A = a;
		this->m_B = b;
	}

	/*如果这里参数写的是Person& p,最后调用的时候变成p.operator<<(p),简化版本变成了p<<p,
	不是我们要的。那如果传cout呢(cout也是对象,可以传),最后调用的时候变成p.operator<<(cout),
	简化版本为p<<cout,也不是我们想要的结果。成员函数无法实现移位运算符的重载*/
	//void operator<<(Person& p){
    
    
	//}

private:
	int m_A;
	int m_B;
};

//只能全局函数实现左移重载
/*
ostream(输出流)对象,全局只能有一个,必须用引用方式传过来。注意这里的返回类型ostream。重载的核心operator<<。
这里还要注意到友元,私有类型。这里返回ostream才可以无限使用<<输出,不然只能使用一次。
这里的使用的是引用,cout是别名,可以换成其他的名字。这里关于cout可以是别名仍有疑问,待更近一步研究(第二遍的理解:cout在可以理解成内部定义好的关键词,只有一个,全局成员函数只是通过引用的方式接收他而已,所以想起什么名字起什么名字)。
*/
//注意,这里cout还必须是引用,因为全局只有一个cout对象
ostream& operator<<(ostream& cout, Person& p) {
    
    
	cout << "a:" << p.m_A << " b:" << p.m_B;
	return cout;
}

void test() {
    
    

	Person p1(10, 20);
	cout << p1 << "hello world" << endl; //链式编程,因为自己实现了重载,因此可以实现cout << p1也可以实现cout << "hello world",相当于调用的函数不一样。
}

int main() {
    
    

	test();
	system("pause");
	return 0;
}

Resumen: sobrecargar el operador de desplazamiento a la izquierda con amigos puede generar la salida de tipos de datos personalizados. La sobrecarga no se puede lograr a través de funciones miembro.

Sobrecarga del operador de incremento:
Función: Realice sus propios datos enteros sobrecargando el operador de incremento
inserte la descripción de la imagen aquí

class MyInteger {
    
    

	friend ostream& operator<<(ostream& out, MyInteger myint);
public:
	MyInteger() {
    
    
		m_Num = 0;
	}
	//前置++,注意这里返回的是引用而不是值,如果返回值的话只能执行一次++,不可以连续多个++,如++(++a)仍然只做一次++。
	MyInteger& operator++() {
    
    
		//先++
		m_Num++;
		//再返回
		return *this;
	}

	//后置++,这里不能返回引用,如果返回的是引用相当于返回的是局部对象的引用,局部对象这里运行完立即释放,后面在操作就是非法的,这里有个占位参数int,只能写int,可以区分前置和后置,不然重载错误
	MyInteger operator++(int) {
    
    
		//先记录
		MyInteger temp = *this; //记录当前本身的值,然后让本身的值加1				     	  
		//后递增
		m_Num++;
		return temp;
	}
 
private:
	int m_Num;
};

ostream& operator<<(ostream& out, MyInteger myint) {
    
    
	out << myint.m_Num;
	return out;
}

//前置++ 先++ 再返回
void test01() {
    
    
	MyInteger myInt;
	cout << ++myInt << endl;
	cout << myInt << endl;
}

//后置++ 先返回 再++
void test02() {
    
    

	MyInteger myInt;
	cout << myInt++ << endl;
	cout << myInt << endl;
}

int main() {
    
    
	test01();
	//test02();
	system("pause");
	return 0;
}

Resumen: referencia de retorno de incremento previo, valor de retorno de incremento posterior

Sobrecarga del operador de asignación:
el compilador de C ++ en realidad agrega un total de 4 funciones a al menos una clase

  1. Constructor predeterminado (sin parámetros, el cuerpo de la función está vacío)
  2. Destructor predeterminado (sin parámetros, el cuerpo de la función está vacío)
  3. El constructor de copia predeterminado, que copia el valor de la propiedad.
  4. Operador de asignación operator=, copia el valor de la propiedad
class Person
{
    
    
public:
	Person(int age)
	{
    
    
		//将年龄数据开辟到堆区
		m_Age = new int(age);
	}

	//重载赋值运算符 
	Person& operator=(Person &p)
	{
    
    
	//先判断是否释放干净
		if (m_Age != NULL)
		{
    
    
			delete m_Age;
			m_Age = NULL;
		}
		//编译器提供的代码是浅拷贝,析构函数释放的时候会报错。在上面的拷贝构造函数中的浅拷贝已经解释过了。
		//m_Age = p.m_Age;

		//提供深拷贝 解决浅拷贝的问题
		m_Age = new int(*p.m_Age);

		//返回自身,自由返回自身才能多次赋值
		return *this;
	}

	~Person()
	{
    
    
		if (m_Age != NULL)
		{
    
    
			delete m_Age;
			m_Age = NULL;
		}
	}
	//年龄的指针
	int *m_Age;
};

void test01()
{
    
    
	Person p1(18);
	Person p2(20);
	Person p3(30);
	//p2 = p1; //赋值操作,将p1的所有数据赋值给p2,此时p1和p2中的堆中的数据指向同一个位置,如果析构函数中使用的是浅拷贝将会报错,内存释放两次。
	p3 = p2 = p1; 
	cout << "p1的年龄为:" << *p1.m_Age << endl;
	cout << "p2的年龄为:" << *p2.m_Age << endl;
	cout << "p3的年龄为:" << *p3.m_Age << endl;
}

int main() {
    
    
	test01();
	//int a = 10;
	//int b = 20;
	//int c = 30;
	//链式操作,我们的重载也应该要满足这个条件
	//c = b = a;
	//cout << "a = " << a << endl;
	//cout << "b = " << b << endl;
	//cout << "c = " << c << endl;
	system("pause");
	return 0;
}

Si hay atributos en la clase que apuntan al área del montón, también habrá problemas de copia profunda y superficial al realizar operaciones de asignación.

Sobrecarga de operadores relacionales:
función : sobrecarga de operadores relacionales, lo que permite que dos objetos de tipo personalizado realicen operaciones de comparación

class Person
{
    
    
public:
	Person(string name, int age)
	{
    
    
		this->m_Name = name;
		this->m_Age = age;
	};
	bool operator==(Person & p)
	{
    
    
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
		{
    
    
			return true;
		}
		else
		{
    
    
			return false;
		}
	}

	bool operator!=(Person & p)
	{
    
    
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
		{
    
    
			return false;
		}
		else
		{
    
    
			return true;
		}
	}
	string m_Name;
	int m_Age;
};

void test01()
{
    
    
	//int a = 0;
	//int b = 0;
	Person a("孙悟空", 18);
	Person b("孙悟空", 18);

	if (a == b)
	{
    
    
		cout << "a和b相等" << endl;
	}
	else
	{
    
    
		cout << "a和b不相等" << endl;
	}
	
	if (a != b)
	{
    
    
		cout << "a和b不相等" << endl;
	}
	else
	{
    
    
		cout << "a和b相等" << endl;
	}
}

int main() {
    
    
	test01();
	system("pause");
	return 0;
}

Sobrecarga del operador de llamada de función:

  • El operador de llamada de función () también se puede sobrecargar
  • Debido a que el método utilizado después de la sobrecarga es muy similar a la llamada de una función, se llama funtor.
  • Functor no tiene una forma fija de escritura y es muy flexible.
class MyPrint
{
    
    
public:
	void operator()(string text)
	{
    
    
		cout << text << endl;
	}
};
void test01()
{
    
    
	//重载的()操作符 也称为仿函数
	MyPrint myFunc;
	myFunc("hello world");
}

class MyAdd
{
    
    
public:
	int operator()(int v1, int v2)
	{
    
    
		return v1 + v2;
	}
};

void test02()
{
    
    
	MyAdd add;
	int ret = add(10, 10);
	cout << "ret = " << ret << endl;

	/*匿名对象调用MyAdd()代替add,执行完立即释放。匿名函数对象MyAdd(),首先他是个匿名对象,
	然后他又重载了(),所以我们叫它仿函数,所以又叫匿名函数对象。*/
	cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;
}

int main() {
    
    
	test01();
	test02();
	system("pause");
	return 0;
}

2.5 Herencia

  Hay una relación especial entre algunas clases. Por ejemplo, vemos que muchos sitios web tienen un encabezado común, un fondo común e incluso una lista izquierda común. Solo el contenido central es diferente. A continuación, usamos escritura ordinaria y se usa Herencia. para implementar los contenidos de las páginas web tutoriales de Java, C++ y Python, y echar un vistazo al significado y los beneficios de la herencia.
Implementación común:

//Java页面
class Java 
{
    
    
public:
	void header()
	{
    
    
		cout << "首页、公开课、登录、注册...(公共头部)" << endl;
	}
	void footer()
	{
    
    
		cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
	}
	void left()
	{
    
    
		cout << "Java,Python,C++...(公共分类列表)" << endl;
	}
	void content()
	{
    
    
		cout << "JAVA学科视频" << endl;
	}
};
//Python页面
class Python
{
    
    
public:
	void header()
	{
    
    
		cout << "首页、公开课、登录、注册...(公共头部)" << endl;
	}
	void footer()
	{
    
    
		cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
	}
	void left()
	{
    
    
		cout << "Java,Python,C++...(公共分类列表)" << endl;
	}
	void content()
	{
    
    
		cout << "Python学科视频" << endl;
	}
};
//C++页面
class CPP 
{
    
    
public:
	void header()
	{
    
    
		cout << "首页、公开课、登录、注册...(公共头部)" << endl;
	}
	void footer()
	{
    
    
		cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
	}
	void left()
	{
    
    
		cout << "Java,Python,C++...(公共分类列表)" << endl;
	}
	void content()
	{
    
    
		cout << "C++学科视频" << endl;
	}
};

void test01()
{
    
    
	//Java页面
	cout << "Java下载视频页面如下: " << endl;
	Java ja;
	ja.header();
	ja.footer();
	ja.left();
	ja.content();
	cout << "--------------------" << endl;

	//Python页面
	cout << "Python下载视频页面如下: " << endl;
	Python py;
	py.header();
	py.footer();
	py.left();
	py.content();
	cout << "--------------------" << endl;

	//C++页面
	cout << "C++下载视频页面如下: " << endl;
	CPP cp;
	cp.header();
	cp.footer();
	cp.left();
	cp.content();
}

int main() {
    
    
	test01();
	system("pause");
	return 0;
}

Como puede ver, muchos de los códigos anteriores se repiten.
Veamos el uso de la herencia:

//公共页面
class BasePage
{
    
    
public:
	void header()
	{
    
    
		cout << "首页、公开课、登录、注册...(公共头部)" << endl;
	}

	void footer()
	{
    
    
		cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
	}
	void left()
	{
    
    
		cout << "Java,Python,C++...(公共分类列表)" << endl;
	}

};

//Java页面
class Java : public BasePage
{
    
    
public:
	void content()
	{
    
    
		cout << "JAVA学科视频" << endl;
	}
};
//Python页面
class Python : public BasePage
{
    
    
public:
	void content()
	{
    
    
		cout << "Python学科视频" << endl;
	}
};
//C++页面
class CPP : public BasePage
{
    
    
public:
	void content()
	{
    
    
		cout << "C++学科视频" << endl;
	}
};

void test01()
{
    
    
	//Java页面
	cout << "Java下载视频页面如下: " << endl;
	Java ja;
	ja.header();
	ja.footer();
	ja.left();
	ja.content();
	cout << "--------------------" << endl;

	//Python页面
	cout << "Python下载视频页面如下: " << endl;
	Python py;
	py.header();
	py.footer();
	py.left();
	py.content();
	cout << "--------------------" << endl;

	//C++页面
	cout << "C++下载视频页面如下: " << endl;
	CPP cp;
	cp.header();
	cp.footer();
	cp.left();
	cp.content();
}

int main() {
    
    
	test01();
	system("pause");
	return 0;
}

Como puede ver, los métodos heredados pueden reducir en gran medida la duplicación de código.
Resumir:

Beneficios de la herencia:El código duplicado se puede reducir
Método de herencia:

class A : public B;

La clase A se denomina clase secundaria o clase derivada. La clase
B se denomina clase principal o clase base.

Miembros de clases derivadas, que incluyen dos partes :

Una clase se hereda de la clase base y la otra clase es un miembro agregado por sí mismo.

Los heredados de la clase base muestran su similitud, mientras que los miembros recién agregados reflejan su individualidad.

Método de herencia:
Sintaxis heredada:class 子类 : 继承方式 父类

Hay tres tipos de herencia:

  • herencia pública
  • herencia protegida
  • herencia privada

inserte la descripción de la imagen aquí
El extremo izquierdo de la imagen de arriba es la herencia pública, que está mal escrito en la imagen.
Veamos los siguientes tres ejemplos:

class Base1
{
    
    
public: 
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};

//公共继承
class Son1 :public Base1
{
    
    
public:
	void func()
	{
    
    
		m_A; //可访问父类中的 public权限成员
		m_B; //可访问父类中的 protected权限,到子类中仍然是保护权限,只有类内能访问,类外不能访问
		//m_C; //不可访问,父类中私有权限成员不可访问
	}
};

//创建一个测试函数
void myClass()
{
    
    
	Son1 s1;
	s1.m_A; //其他类只能访问到公共权限
	//s1.m_B;  报错,m_B到了子类Son1中变成了保护权限,类外不能访问保护权限
}

//保护继承
class Base2
{
    
    
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};
class Son2:protected Base2
{
    
    
public:
	void func()
	{
    
    
		m_A; //父类中的公共成员,到子类中变成了保护权限
		m_B; //父类中的保护成员,到子类中变成了保护权限
		//父类中的私有成员,不可访问
		//m_C; //不可访问
	}
};
void myClass2()
{
    
    
	Son2 s;
	//报错,m_A到了子类Son2中变成了保护权限,类外不能访问保护权限
	//s.m_A; //不可访问
}

//私有继承
class Base3
{
    
    
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};
class Son3:private Base3
{
    
    
public:
	void func()
	{
    
    
		m_A; //父类中的公共成员到子类中变成了私有成员
		m_B; //父类中的保护成员到子类中变成了私有成员
		//m_C; //不可访问
	}
};
class GrandSon3 :public Son3
{
    
    
public:
	void func()
	{
    
    
		//Son3是私有继承,所以即使孙子以公共的方式继承父类Son3的属性在GrandSon3中都无法访问到,会直接报错
		//m_A;
		//m_B;
		//m_C;
	}
};

2.5.1 Modelo de objetos en herencia

Todas las propiedades de los miembros no estáticos en la clase principal se heredarán, pero el compilador oculta las propiedades de los miembros privados en la clase principal, por lo que no se puede acceder a ellas, pero de hecho se heredan.

class Base
{
    
    
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C; //私有成员只是被隐藏了,但是还是会继承下去
};

//公共继承
class Son :public Base
{
    
    
public:
	int m_D;
};

void test01()
{
    
    
	//输出结果是12,父类中的所有非静态成员都别继承下去。父类中的私有属性是被编译器给隐藏了,因此访问不到,但是确实是被继承下去了。
	cout << "sizeof Son = " << sizeof(Son) << endl;
}

int main() {
    
    

	test01();

	system("pause");

	return 0;
}

También puede usar la herramienta del símbolo del sistema para desarrolladores para ver qué propiedades se heredan. Los pasos específicos son los siguientes:
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí

2.5.2 Orden de construcción y destrucción en la herencia

class Base 
{
    
    
public:
	Base()
	{
    
    
		cout << "Base构造函数!" << endl;
	}
	~Base()
	{
    
    
		cout << "Base析构函数!" << endl;
	}
};

class Son : public Base
{
    
    
public:
	Son()
	{
    
    
		cout << "Son构造函数!" << endl;
	}
	~Son()
	{
    
    
		cout << "Son析构函数!" << endl;
	}

};


void test01()
{
    
    
	//继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
	Son s;
}

int main() {
    
    

	test01();

	system("pause");

	return 0;
}

inserte la descripción de la imagen aquí
Si hay herencia en la subclase, al crear el objeto de la subclase, debido a la operación de herencia, la subclase definitivamente obtendrá las propiedades y los miembros de la clase principal, y también creará el objeto de la clase principal, por lo que llamará al objeto de la clase principal mientras crea el objeto de la clase principal.

Resumen: en la herencia, primero se llama al constructor de la clase principal y luego se llama al constructor de la subclase. El orden de destrucción es opuesto al de construcción.

2.5.3 Método de procesamiento de la herencia del mismo nombre

class Base {
    
    
public:
	Base()
	{
    
    
		m_A = 100;
	}

	void func()
	{
    
    
		cout << "Base - func()调用" << endl;
	}

	void func(int a)
	{
    
    
		cout << "Base - func(int a)调用" << endl;
	}

public:
	int m_A;
};


class Son : public Base {
    
    
public:
	Son()
	{
    
    
		m_A = 200;
	}

	//当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数
	//如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域
	void func()
	{
    
    
		cout << "Son - func()调用" << endl;
	}
public:
	int m_A;
};

void test01()
{
    
    
	Son s;
	//直接访问就是自己类下面的
	cout << "Son下的m_A = " << s.m_A << endl;
	//如果想要访问父类中的同名成员,需要加上作用域
	cout << "Base下的m_A = " << s.Base::m_A << endl;

	s.func();
	s.Base::func();
	/* 如果子类中出现和父类中的同名成员函数,子类中的同名成员函数会隐藏掉父类中所有的同名成员函数即所有的重载函数都被隐藏了。
	只要同名不管是否重载都被隐藏
	如果想访问到父类中的隐藏的同名成员函数,需要加作用域。
	*/
	s.Base::func(10);

}
int main() {
    
    
	test01();
	system("pause");
	return EXIT_SUCCESS;
}
  1. Los objetos de la subclase pueden acceder directamente a los miembros del mismo nombre en la subclase
  2. El objeto de la subclase más el alcance puede acceder a los miembros del mismo nombre de la clase principal
  3. Cuando la subclase tiene una función miembro con el mismo nombre que la clase principal, la subclase ocultará la función miembro con el mismo nombre en la clase principal (incluida la sobrecarga) y agregará un alcance para acceder a la función con el mismo nombre en la clase principal.

2.5.4 Herencia de miembros estáticos con el mismo método de procesamiento de nombre

  Primero revise las características de las variables miembro estáticas mencionadas anteriormente: todos los objetos comparten los mismos datos, la memoria se asigna durante la compilación y las características de las funciones miembro estáticas declaradas en la clase e inicializadas fuera de la clase: solo se puede acceder a las variables miembro estáticas, y no se puede acceder a las variables miembro no estáticas
y todos los objetos comparten un dato.
Pregunta: ¿Cómo se puede acceder a miembros estáticos con el mismo nombre en herencia en objetos de subclase?
Los miembros estáticos y no estáticos con el mismo nombre se manejan de la misma manera

  • Se puede acceder directamente a los miembros de la subclase con el mismo nombre
  • El acceso a miembros con el mismo nombre de la clase principal requiere alcance
class Base {
    
    
public:
	static void func()
	{
    
    
		cout << "Base - static void func()" << endl;
	}
	static void func(int a)
	{
    
    
		cout << "Base - static void func(int a)" << endl;
	}

	static int m_A;
};

int Base::m_A = 100;

class Son : public Base {
    
    
public:
	static void func()
	{
    
    
		cout << "Son - static void func()" << endl;
	}
	static int m_A;
};

int Son::m_A = 200;

//同名成员属性
void test01()
{
    
    
	//通过对象访问
	cout << "通过对象访问: " << endl;
	Son s;
	cout << "Son  下 m_A = " << s.m_A << endl;
	cout << "Base 下 m_A = " << s.Base::m_A << endl;

	//通过类名访问
	cout << "通过类名访问: " << endl;
	cout << "Son  下 m_A = " << Son::m_A << endl;
	//通过子类对象访问父类的静态成员数据,第一个::表示通过类名的方式访问,第二个::表示表示访问父类作用域下的
	cout << "Base 下 m_A = " << Son::Base::m_A << endl;
}

//同名成员函数
void test02()
{
    
    
	//通过对象访问
	cout << "通过对象访问: " << endl;
	Son s;
	s.func();
	s.Base::func();

	cout << "通过类名访问: " << endl;
	Son::func();
	//通过类名访问父类中的func()。
	Son::Base::func();
	//出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作作用域访问,如果下面的Base不加还是报错。
	Son::Base::func(100);
}
int main() {
    
    

	//test01();
	test02();

	system("pause");

	return 0;
}

Resumen: el método de procesamiento de los miembros estáticos con el mismo nombre es el mismo que el de los miembros no estáticos, excepto que hay dos métodos de acceso (a través de objetos y a través de nombres de clases), y solo se puede acceder a los miembros no estáticos a través de objetos

2.5.5 Herencia múltiple

C++ permite que una clase herede varias clases
Gramática: class 子类 :继承方式 父类1 , 继承方式 父类2...
la herencia múltiple puede hacer que aparezcan miembros con el mismo nombre en la clase principal, y es necesario agregar un alcance para distinguirlo.
En el desarrollo real de C++, no se recomienda usar múltiples herencia

class Base1 {
    
    
public:
	Base1()
	{
    
    
		m_A = 100;
	}
public:
	int m_A;
};

class Base2 {
    
    
public:
	Base2()
	{
    
    
		m_A = 200;  //开始是m_B 不会出问题,但是改为mA就会出现不明确
	}
public:
	int m_A;
};

//语法:class 子类:继承方式 父类1 ,继承方式 父类2 
class Son : public Base2, public Base1 
{
    
    
public:
	Son()
	{
    
    
		m_C = 300;
		m_D = 400;
	}
public:
	int m_C;
	int m_D;
};


//多继承容易产生成员同名的情况
//通过使用类名作用域可以区分调用哪一个基类的成员
void test01()
{
    
    
	Son s;
	//输出16,两个父类里面都继承了
	cout << "sizeof Son = " << sizeof(s) << endl;
	//当父类中出现同名的时候,使用的时候需要加作用域,所以一般不建议使用多继承
	cout << s.Base1::m_A << endl;
	cout << s.Base2::m_A << endl;
}

int main() {
    
    
	test01();
	system("pause");
	return 0;
}

2.5.6 Herencia de diamantes

Concepto de herencia de diamantes:

​ Dos clases derivadas heredan la misma clase base​ y una determinada clase hereda dos clases derivadas al mismo tiempo. Este tipo de herencia se denomina herencia de diamantes o herencia de diamantes.

Mira un ejemplo:

  1. Las ovejas heredan los datos de los animales, y los camellos también heredan los datos de los animales. Cuando los caballos de hierba y lodo usan datos, debido a que las ovejas y los camellos heredan los mismos datos, surgirá la ambigüedad en este momento.
  2. El caballo de barro de hierba heredó dos copias de los datos del animal, de hecho, debemos tener claro que solo necesitamos una copia de estos datos.
class Animal
{
    
    
public:
	int m_Age;
};

//继承前加virtual关键字后,变为虚继承
//此时公共的父类Animal称为虚基类
class Sheep : virtual public Animal 
{
    
    

};
class Tuo   : virtual public Animal
{
    
    

};
class SheepTuo : public Sheep, public Tuo
{
    
    

};

void test01()
{
    
    
	SheepTuo st;
	st.Sheep::m_Age = 100;//这里有个注意的地方,::的优先级高于.的优先级,这里注意一下,不然有时候这样写代码看着很迷惑
	st.Tuo::m_Age = 200;
	//当菱形继承时,两个父类具有相同的数据,需要加作用域区分
	cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
	cout << "st.Tuo::m_Age = " <<  st.Tuo::m_Age << endl;
	//这份数据我们知道 只要有一份就可以,菱形继承导致数据有两份,资源浪费。那么这里输出的应该是100还是200呢?怎么解决这个问题?利用虚继承可以解决菱形继承的问题,继承之前 加上关键字 virtual 变为虚继承。这个时候的输出为200,包括上面的输出也变为了200。当我们发生虚继承之后只保留最后一次的,也就是只能有一份数据。
	cout << "st.m_Age = " << st.m_Age << endl;
}

int main() {
    
    

	test01();
	system("pause");
	return 0;
}

Eche un vistazo con la herramienta de símbolo del sistema para desarrolladores.
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí
La figura anterior vbptres el puntero de clase base virtual, que apuntará a vbtable(tabla de clase base virtual)

Resumir:

  • El principal problema causado por la herencia de diamantes es que las subclases heredan dos copias de los mismos datos, lo que genera un desperdicio de recursos y falta de sentido.
  • El uso de la herencia virtual puede resolver el problema de la herencia de diamantes (puntero de clase base virtual, habrá un puntero de función virtual más adelante)
  • No recomendado para su uso

2.6 Polimorfismo

El polimorfismo es una de las tres características orientadas a objetos de C++

El polimorfismo se divide en dos categorías:

  • Polimorfismo estático:sobrecarga de funcionesysobrecarga del operadorPertenece al polimorfismo estático, reutiliza el nombre de la función
  • Polimorfismo dinámico: las clases derivadas y las funciones virtuales implementan polimorfismo en tiempo de ejecución,Usualmente nos referimos al polimorfismo como polimorfismo dinámico.

La diferencia entre polimorfismo estático y polimorfismo dinámico:

  • Dirección de función polimórfica estáticaencuadernación temprana- La fase de compilación determina la dirección de la función.
  • Dirección de función polimórfica dinámicaencuadernación tardía- El tiempo de ejecución determina la dirección de la función

inserte la descripción de la imagen aquí
En C++, se permite la conversión de tipo entre padre e hijo sin conversión de tipo obligatoria, y el puntero o referencia de la clase padre puede apuntar directamente al objeto de la subclase.
El resultado del código anterior es el animal que habla , porque ahora la dirección de nuestra función pertenece al enlace de dirección, el objeto entrante se ha vinculado a la Animaldirección y la dirección de la función se ha determinado durante la etapa de compilación. Si desea ejecutar Let the cat talk, entonces la dirección de esta función no se puede vincular por adelantado, debe vincularse durante la fase de ejecución y la dirección se vincula más tarde. ¿Cómo hacerlo? Es para agregarlo delante de la función de la clase principal.Después virtualde agregar esto, virtuallas siguientes subclases pueden realizar el enlace tardío de la dirección después de implementar la función con el mismo nombre que la clase principal.

El polimorfismo satisface las condiciones:

1. Hay una relación de herencia
2. La subclase anula la función virtual en la clase principal y la palabra clave virtual se puede escribir o no

Uso polimórfico:

Un puntero de clase principal o puntos de referencia a un objeto de subclase

Vea el ejemplo a continuación:

class Animal
{
    
    
public:
	//Speak函数就是虚函数
	//函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
	virtual void speak()
	{
    
    
		cout << "动物在说话" << endl;
	}
};

class Cat :public Animal
{
    
    
public:
	void speak()
	{
    
    
		cout << "小猫在说话" << endl;
	}
};

class Dog :public Animal
{
    
    
public:

	void speak()
	{
    
    
		cout << "小狗在说话" << endl;
	}

};
//我们希望传入什么对象,那么就调用什么对象的函数
//如果函数地址在编译阶段就能确定,那么静态联编
//如果函数地址在运行阶段才能确定,就是动态联编

void DoSpeak(Animal & animal)
{
    
    
	animal.speak();
}
//
//动态多态满足条件: 
//1、有继承关系
//2、子类重写父类中的虚函数,virtual关键字可写可不写
//多态使用:
//父类指针或引用指向子类对象

void test01()
{
    
    
	Cat cat;
	DoSpeak(cat);
	
	Dog dog;
	DoSpeak(dog);
}


int main() {
    
    
	test01();
	system("pause");
	return 0;
}

El polimorfismo dinámico satisface las condiciones:

1. Hay una relación de herencia
2. SubclasesAnular la función virtual en la clase principal, virtualla palabra clave puede escribirse o no

Uso polimórfico:

Un puntero de clase principal o puntos de referencia a un objeto de subclase

2.6.1 Principios de polimorfismo de bajo nivel

inserte la descripción de la imagen aquí
  El núcleo de la figura anterior es la tabla de funciones virtuales, y la tabla de funciones virtuales en la subclase se reemplazará con la dirección de función de la subclase. Hay un puntero de tabla de función virtual en la clase principal , que apunta a la función virtual en la clase principal. Cuando la subclase reescribe la función en la clase principal, la referencia o el puntero del objeto de la clase principal apunta a la subclase y el polimorfismo En este momento, el puntero apunta a la subclase. Preste atención a la diferencia entre la distinción y el puntero de clase base virtual anterior.
Ventajas del polimorfismo:

  • La organización del código es clara
  • Legible
  • Propicio para la expansión y el mantenimiento temprano y tardío

Veamos un caso para implementar una calculadora:

//普通实现
class Calculator {
    
    
public:
	int getResult(string oper)
	{
    
    
		if (oper == "+") {
    
    
			return m_Num1 + m_Num2;
		}
		else if (oper == "-") {
    
    
			return m_Num1 - m_Num2;
		}
		else if (oper == "*") {
    
    
			return m_Num1 * m_Num2;
		}
		//如果要提供新的运算,需要修改源码
	}
public:
	int m_Num1;
	int m_Num2;
};

void test01()
{
    
    
	//普通实现测试
	Calculator c;
	c.m_Num1 = 10;
	c.m_Num2 = 10;
	cout << c.m_Num1 << " + " << c.m_Num2 << " = " << c.getResult("+") << endl;

	cout << c.m_Num1 << " - " << c.m_Num2 << " = " << c.getResult("-") << endl;

	cout << c.m_Num1 << " * " << c.m_Num2 << " = " << c.getResult("*") << endl;
}

//多态实现
//抽象计算器类
//多态优点:代码组织结构清晰,可读性强,利于前期和后期的扩展以及维护
class AbstractCalculator
{
    
    
public :

	virtual int getResult()
	{
    
    
		return 0;
	}

	int m_Num1;
	int m_Num2;
};

//加法计算器
class AddCalculator :public AbstractCalculator
{
    
    
public:
	int getResult()
	{
    
    
		return m_Num1 + m_Num2;
	}
};

//减法计算器
class SubCalculator :public AbstractCalculator
{
    
    
public:
	int getResult()
	{
    
    
		return m_Num1 - m_Num2;
	}
};

//乘法计算器
class MulCalculator :public AbstractCalculator
{
    
    
public:
	int getResult()
	{
    
    
		return m_Num1 * m_Num2;
	}
};


void test02()
{
    
    
	//创建加法计算器
	AbstractCalculator *abc = new AddCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 10;
	cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;
	delete abc;  //用完了记得销毁

	//创建减法计算器
	abc = new SubCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 10;
	cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;
	delete abc;  

	//创建乘法计算器
	abc = new MulCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 10;
	cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;
	delete abc;
}

int main() {
    
    
	//test01();
	test02();
	system("pause");
	return 0;
}

Resumen: el desarrollo de C++ aboga por el uso de la arquitectura del programa de diseño de polimorfismos, porque el polimorfismo tiene muchas ventajas

2.6.2 Funciones virtuales puras y clases abstractas

  En el polimorfismo, por lo general, la implementación de la función virtual en la clase principal no tiene sentido, principalmente llamando al contenido reescrito por la subclase, por lo que la función virtual se puede cambiar a una función virtual pura . No hay necesidad de escribir funciones virtuales puras cuando la clase principal tiene sentido.
Sintaxis de función virtual pura: virtual 返回值类型 函数名 (参数列表)= 0 ;
cuando hay una función virtual pura en una clase, esta clase también se llamaclase abstracta
Las clases abstractas se utilizan principalmente para implementar interfaces y polimorfismos, y solo se pueden utilizar en forma de punteros o referencias , es decir, se pueden crear punteros o referencias a clases abstractas.
Características de la clase abstracta :

  • No se pudo instanciar el objeto
  • La subclase debe anular la función virtual pura en la clase abstracta, de lo contrario, también pertenece a la clase abstracta.
class Base
{
    
    
public:
	//纯虚函数
	//类中只要有一个纯虚函数就称为抽象类
	//抽象类无法实例化对象
	//子类必须重写父类中的纯虚函数,否则也属于抽象类,无法实例化对象
	virtual void func() = 0; //纯虚函数,前面必须加virtual才可以等于0
};

class Son :public Base
{
    
    
public:
	//子类必须重写父类中的纯虚函数,否则也属于抽象类,无法实例化对象
	virtual void func() 
	{
    
    
		cout << "func调用" << endl;
	};
};

void test01()
{
    
    
	Base * base = NULL;
	// Base b;// 错误,抽象类无法实例化对象
	//base = new Base; // 错误,抽象类无法实例化对象。无论栈还是堆方式都不可以
	
	base = new Son;
	base->func();
	delete base;//记得销毁
}

int main() {
    
    

	test01();
	system("pause");
	return 0;
}

Tecnología polimórfica: el puntero de la clase principal se usa para referirse o apuntar al objeto de la subclase

Veamos otro ejemplo y hagamos diferentes bebidas.

//抽象制作饮品
class AbstractDrinking {
    
    
public:
	//烧水
	virtual void Boil() = 0;
	//冲泡
	virtual void Brew() = 0;
	//倒入杯中
	virtual void PourInCup() = 0;
	//加入辅料
	virtual void PutSomething() = 0;
	//规定流程
	void MakeDrink() {
    
    
		Boil();
		Brew();
		PourInCup();
		PutSomething();
	}
};

//制作咖啡
class Coffee : public AbstractDrinking {
    
    
public:
	//烧水
	virtual void Boil() {
    
    
		cout << "煮农夫山泉!" << endl;
	}
	//冲泡
	virtual void Brew() {
    
    
		cout << "冲泡咖啡!" << endl;
	}
	//倒入杯中
	virtual void PourInCup() {
    
    
		cout << "将咖啡倒入杯中!" << endl;
	}
	//加入辅料
	virtual void PutSomething() {
    
    
		cout << "加入牛奶!" << endl;
	}
};

//制作茶水
class Tea : public AbstractDrinking {
    
    
public:
	//烧水
	virtual void Boil() {
    
    
		cout << "煮自来水!" << endl;
	}
	//冲泡
	virtual void Brew() {
    
    
		cout << "冲泡茶叶!" << endl;
	}
	//倒入杯中
	virtual void PourInCup() {
    
    
		cout << "将茶水倒入杯中!" << endl;
	}
	//加入辅料
	virtual void PutSomething() {
    
    
		cout << "加入枸杞!" << endl;
	}
};

//业务函数
void DoWork(AbstractDrinking* drink) {
    
    
	drink->MakeDrink();
	delete drink;
}

void test01() {
    
    
	DoWork(new Coffee);
	cout << "--------------" << endl;
	DoWork(new Tea);
}


int main() {
    
    
	test01();
	system("pause");
	return 0;
}

El núcleo del polimorfismo es buscar funciones reescritas de subclases a través de punteros de clase principal o referencias a objetos similares.

2.6.3 Destructor virtual y destructor virtual puro

  Vamos a introducir virtual destructor y pure virtual destructor ¿Por qué necesitamos saber esto? Sabemos que cuando se usa el polimorfismo, el puntero o la referencia de la clase principal se usa para apuntar al objeto de la subclase, pero el puntero de la clase principal no puede liberar el código destructor en la subclase , por lo que se abren los datos de la subclase. al área del montón, luego, cuando el puntero de la clase principal está allí, deletelos datos de la subclase no se pueden liberar, lo que provocará una pérdida de memoria

Cuando se usa el polimorfismo, si hay atributos en la subclase que se asignan al área del montón, entonces el puntero de la clase principal no puede llamar al código destructor de la subclase cuando se libera. Solución: cambie la función destructora en la clase principal a
virtual destructor o destructor virtual puro

Características comunes de virtual destructor y pure virtual destructor:

  • Puede resolver el problema de que el puntero de la clase principal no puede liberar el objeto de la subclase
  • necesita tener una implementación de función específica

La diferencia entre el destructor virtual y el destructor virtual puro:

  • Si hay uno en el código 纯虚析构, la clase es una clase abstracta y los objetos no se pueden instanciar. No entra en conflicto con la función virtual pura anterior, y el destructor también es una función.

Sintaxis del destructor virtual:

virtual ~类名(){}

Sintaxis pura del destructor virtual:

virtual ~类名() = 0;

类名::~类名(){}

class Animal {
    
    
public:

	Animal()
	{
    
    
		cout << "Animal 构造函数调用!" << endl;
	}
	virtual void Speak() = 0;

	//析构函数加上virtual关键字,变成虚析构函数
	//virtual ~Animal()
	//{
    
    
	//	cout << "Animal虚析构函数调用!" << endl;
	//}

	/*纯虚析构,利用纯虚析构和虚析构都可以解决,只能用一个。纯虚析构里面也要写上代码实现,不然直接写这句话报错,
	类外写上代码实现。区分跟纯虚函数的区别,纯虚函数可以直接写等于0*/
	//有了纯虚析构 之后 ,这个类也属于抽象类,无法实例化对象
	virtual ~Animal() = 0;
};
//如果这里不使用虚析构,将无法释放子类中的堆区数据
Animal::~Animal()
{
    
    
	cout << "Animal 纯虚析构函数调用!" << endl;
}

//和包含普通纯虚函数的类一样,包含了纯虚析构函数的类也是一个抽象类。不能够被实例化。

class Cat : public Animal {
    
    
public:
	Cat(string name)
	{
    
    
		cout << "Cat构造函数调用!" << endl;
		m_Name = new string(name);
	}
	virtual void Speak()
	{
    
    
		cout << *m_Name <<  "小猫在说话!" << endl;
	}
	~Cat()
	{
    
    
		cout << "Cat析构函数调用!" << endl;
		if (this->m_Name != NULL) {
    
    
			delete m_Name;
			m_Name = NULL;
		}
	}

public:
	string *m_Name;
};

void test01()
{
    
    
	Animal *animal = new Cat("Tom");
	animal->Speak();
	//父类指针在析构时候 不会调用子类中析构函数,导致子类如果有堆区属性,出现内存泄漏
	//怎么解决?把基类改成一个虚析构函数
	//虚析构函数就是用来解决通过父类指针释放子类对象,是因为在运行时,会根据对象的实际类型调用相应的析构函数。这样就可以确保子类对象被正确地销毁,避免了内存泄漏和其他异常问题。
	delete animal;
}

int main() {
    
    

	test01();
	system("pause");
	return 0;
}

Resumir:

  1. Virtual destructor o pure virtual destructor se usa para resolver el problema de liberar objetos de subclase a través de punteros de clase padre. Ambos están bien. La diferencia es que después de escribir pure virtual destructor, esta clase es una clase abstracta.
  2. Si no hay datos de área de montón en la subclase, no se puede escribir como destructor virtual o destructor virtual puro.
  3. Una clase con un destructor virtual puro también es una clase abstracta
  4. Tanto los destructores virtuales como los virtuales puros deben tenercarta especificanúmero realizado.
  5. El destructor virtual puro en la clase principal también es una clase abstracta y no se puede crear una instancia del objeto.

2.7 Documentación

2.7.1 Operación de archivos

Los datos generados cuando el programa se está ejecutando son todos datos temporales y se liberarán una vez que el programa termine de ejecutarse.
Las operaciones de C++ en archivos también se basan en operaciones orientadas a objetos

Los datos se pueden conservar a través de archivos

Las operaciones de archivo en C++ deben incluir archivos de encabezado<fstream>

Hay dos tipos de archivos:

  1. Archivos de texto : los archivos se almacenan en su computadora como texto en ASCII
  2. Archivos binarios : los archivos se almacenan en la computadora en forma de texto binario y, por lo general, los usuarios no pueden leerlos directamente.

Tres categorías de archivos operativos:

  1. ofstream: operación de escritura
  2. ifstream: operación de lectura
  3. fstream: operaciones de lectura y escritura

2.7.2 Archivos de texto

Los pasos para escribir un archivo son los siguientes:

  1. Incluir archivos de encabezado
    #include <fstream>
  2. Cree un objeto de flujo
    de flujo de;
  3. Abra el archivo
    ofs.open("ruta del archivo", método abierto);
  4. writedata
    ofs << "datos escritos";
  5. Cierra el archivo
    ofs.close();

Método de apertura de archivos:

método abierto explicar
ios::en archivo abierto para leer
ios::fuera archivo abierto para escribir
ios::ate Posición inicial: fin de archivo
ios::aplicación Agregar para escribir archivos
ios::tronco Si el archivo existe, elimínelo primero y luego créelo.
ios::binario modo binario

Nota: El método de apertura de archivos se puede utilizar junto con el operador |

Por ejemplo : escribir un archivo en modo binarioios::binary | ios:: out

#include <fstream>

void test01()
{
    
    
	ofstream ofs;
	ofs.open("test.txt", ios::out);

	ofs << "姓名:张三" << endl;
	ofs << "性别:男" << endl;
	ofs << "年龄:18" << endl;

	ofs.close();
}

int main() {
    
    

	test01();

	system("pause");

	return 0;
}

Después de que se ejecute el programa anterior, se generará un archivo txt en la ubicación del programa
.

  • Las operaciones de archivo deben incluir el archivo de encabezado fstream
  • Los archivos de lectura pueden usar la clase ofstream o fstream
  • Al abrir un archivo, debe especificar la ruta del archivo de operación y el método de apertura
  • Utilice << para escribir datos en el archivo
  • Una vez completada la operación, cierre el archivo.

2.7.3 Lectura de archivos

Los pasos para leer un archivo son similares a los de escribir un archivo, pero hay más métodos de lectura.

Los pasos para leer un archivo son los siguientes:

  1. Incluir archivos de encabezado
    #include <fstream>
  2. Cree un objeto de flujo
    ifstream ifs;
  3. Abra el archivo y juzgue si el archivo se abrió con éxito
    ifs.open("ruta del archivo", método abierto);
  4. Leer datos
    Cuatro formas de leer
  5. Cierra el archivo
    ifs.close();
#include <fstream>
#include <string>
void test01()
{
    
    
	ifstream ifs;
	ifs.open("test.txt", ios::in);

	if (!ifs.is_open())
	{
    
    
		cout << "文件打开失败" << endl;
		return;
	}

	//第一种方式
	//char buf[1024] = { 0 };
	//while (ifs >> buf)
	//{
    
    
	//	cout << buf << endl;
	//}

	//第二种
	//char buf[1024] = { 0 };
	//while (ifs.getline(buf,sizeof(buf)))
	//{
    
    
	//	cout << buf << endl;
	//}

	//第三种
	//string buf;
	//while (getline(ifs, buf))
	//{
    
    
	//	cout << buf << endl;
	//}

	char c;
	while ((c = ifs.get()) != EOF)
	{
    
    
		cout << c;
	}

	ifs.close();


}

int main() {
    
    

	test01();

	system("pause");

	return 0;
}

2.7.4 Escritura de archivos binarios

Leer y escribir archivos en modo binario

Abrir con para ser especificado comoios::binario

2.7.5 Escritura de archivos

Escribir archivos en modo binario utiliza principalmente el objeto de flujo para llamar a la función miembro escribir

Prototipo de función:ostream& write(const char * buffer,int len);

Explicación del parámetro: el búfer del puntero de carácter apunta a un espacio de almacenamiento en la memoria. len es el número de bytes leídos y escritos

#include <fstream>
#include <string>

class Person
{
    
    
public:
	char m_Name[64];
	int m_Age;
};

//二进制文件  写文件
void test01()
{
    
    
	//1、包含头文件

	//2、创建输出流对象
	ofstream ofs("person.txt", ios::out | ios::binary);
	
	//3、打开文件
	//ofs.open("person.txt", ios::out | ios::binary);

	Person p = {
    
    "张三"  , 18};

	//4、写文件
	ofs.write((const char *)&p, sizeof(p));

	//5、关闭文件
	ofs.close();
}

int main() {
    
    

	test01();

	system("pause");

	return 0;
}
  • El objeto de flujo de entrada de archivo puede leer datos en modo binario a través de la función de lectura

2.7.6 Lectura de archivos

La lectura de archivos en modo binario utiliza principalmente el objeto de flujo para llamar a la función miembro read

Prototipo de función:istream& read(char *buffer,int len);

Explicación del parámetro: el búfer del puntero de carácter apunta a un espacio de almacenamiento en la memoria. len es el número de bytes leídos y escritos

#include <fstream>
#include <string>

class Person
{
    
    
public:
	char m_Name[64];
	int m_Age;
};

void test01()
{
    
    
	ifstream ifs("person.txt", ios::in | ios::binary);
	if (!ifs.is_open())
	{
    
    
		cout << "文件打开失败" << endl;
	}

	Person p;
	ifs.read((char *)&p, sizeof(p));

	cout << "姓名: " << p.m_Name << " 年龄: " << p.m_Age << endl;
}

int main() {
    
    

	test01();

	system("pause");

	return 0;
}

Supongo que te gusta

Origin blog.csdn.net/qq_38683460/article/details/128235905
Recomendado
Clasificación