C++ Orientado a objetos/Programación básica/Introducción a C++

1 modelo de partición de memoria

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

  • Área de código: almacena el código binario del cuerpo de la función, gestionado por el sistema operativo
  • Área global: almacena variables globales y variables y constantes estáticas (constantes de cadena, constantes modificadas por const)
  • Área de pila: asignada y liberada automáticamente por el compilador, almacenando valores de parámetros de funciones, 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.

1.1 Antes de ejecutar el programa

Después de compilar el programa, se genera un programa ejecutable exe, que se divide en dos áreas antes de ejecutar el programa:

área de código:

Almacena las instrucciones de 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 por la que es de solo lectura es para evitar que el programa modifique accidentalmente sus instrucciones.

Zona mundial:

El sistema operativo libera los datos en esta área después de que finaliza el programa.


1.2 Después de ejecutar 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

int * func()
{
    
    
	int a = 10;
	return &a;
}

int main()
{
    
    
	int* p = func();

	cout << *p << endl;
	cout << *p << endl;

	return 0;
}

Área de montón:

Es asignado y liberado por el programador. Si el programador no lo libera, será reclamado por el sistema operativo al final del programa.

Utilice principalmente new para abrir memoria en el área del montón

int* func()
{
    
    
	int* a = new int(10);
	return a;
}

int main()
{
    
    
	int* p = func();

	cout << *p << endl;
	cout << *p << endl;

	return 0;
}

Los datos desarrollados en el área del montón son desarrollados manualmente por el programador, liberados manualmente y liberados usando el operador de eliminación.

Los datos creados por new devolverán un puntero del tipo correspondiente a los datos


1.3 nuevos implementos asignación de memoria dinámica

  • Primer uso: asignar una P = new T;variable

  • Explicación de parámetros: T es cualquier nombre de tipo, P es un puntero de tipo T*

  • Función: asigna dinámicamente un espacio de memoria de tamaño de (T) bytes y asigna la dirección inicial del espacio de memoria a P

int* pn;
pn = new int;
*pn = 5;

  • Segundo uso: asignar una P = new T[N];matriz
  • Explicación del parámetro: T es cualquier nombre de tipo, P es un puntero de tipo T*, N es el número de elementos de matriz que se asignarán y puede ser una expresión entera
  • Función: asigna dinámicamente un espacio de memoria con un tamaño de N*sizeof(T) bytes, y asigna la dirección inicial del espacio de memoria a P
int* pn;
int i = 5;
pn = new int[i * 20];
pn[0] = 20;

  • El espacio de memoria asignado dinámicamente por new debe liberarse mediante eliminación; de lo contrario, ocupará el espacio todo el tiempo, lo que puede provocar que el espacio de memoria sea insuficiente para que se ejecute el sistema operativo u otras aplicaciones.
delete 指针;  // 该指针必须指向 new 出来的空间

int* p = new int;
*p = 4;
delete p;
delete p;  // 不能多次 delete
delete [] 指针;  // 该指针必须指向 new 出来的空间

int* p = new int[20];
p[0] = 9;
delete [] p;


2 citas

Función: alias de la variable

Sintaxis 数据类型& 别名 = 原名:

  • Suplementos o notas para punteros y referencias:
int a = 10;
// 等价 int* const b = &a;这里也说明了为啥引用一旦初始化之后,就不能再改变了
int& b = a; 

a = 100;  // 此时,a 和 b 的值都是 100
b = 200;  // 此时,a 和 b 的值都是 200


int x = 4;
const int& y = x;  // 注意这里的 const 修饰的 y,而不是 x !!!

x = 9;  // 这里仍然可以修改 x 的值,x 和 y 的值都是 9
// y = 99;  // 错误!!因为是 const 修饰的 y

----------------------------------------

// 指针复习
int n = 4;

int* ptr = &n;
*ptr = 10000;

// 限定 p,既不能修改 p 的指向,也不能修改 p 指向的值,这里说指向的值,是不能用 *p 修改 n 的值
const int* const p = &n;
n = 10;
// *ptr = 9;  // 错误!!表达式必须是可以修改的左值

Características:

  • la referencia debe ser inicializada

  • Después de inicializar la referencia, no se puede cambiar (porque la esencia de la referencia es en realidad un pseudo puntero)

    int rat;
    int& rodent = rat;  // rodent 和 *ptr 是等价的
    int* const ptr = &rat;  // 指针常量,不能改变指针的指向,但是可以改变指向的值
    
  • Cuando el parámetro formal es una referencia constante y el parámetro real no coincide, c++ generará una variable temporal, y su comportamiento es similar a pasar por valor.Para garantizar que los datos originales no se modifiquen, se creará una variable temporal utilizado para almacenar el valor. Las dos situaciones siguientes generarán una variable temporal Variable:
    1. El tipo del parámetro real es correcto, pero no es un valor l (un objeto de datos al que se puede hacer referencia, es decir, un objeto que puede se puede acceder por dirección)
    2. El tipo del parámetro real es incorrecto, pero se puede convertir al tipo correcto

  • Si la declaración especifica la referencia como const, C++ generará una variable temporal cuando sea necesario, pero la variable de referencia modificada por const no se puede cambiar más adelante.

double refcube(const double& a)
{
    
    
 	return a*a*a;
}

double side = 3.0;
long edge = 5L;
double lens[4] = {
    
    2.0, 5.0, 10.0, 12.0};

double c1 = refcube(side);  // a 是 side
double c2 = refcube(lens[2]);  // a 是 lens[2]
double c3 = refcube(edge);  // 虽然 c3 的结果是 125,但是 a 是临时变量
  • Ahora, si se trata de un parámetro modificado de referencia ordinaria, el tipo de parámetro real no coincide y se informará un error. A diferencia de antes, habrá un proceso de conversión de variable temporal.
  • Aquí hay algunos puntos de conocimiento adicionales: los valores que no son l incluyen constantes literales (excepto las constantes de cadena, que tienen sus propias direcciones) y expresiones que contienen varios elementos
  • Cuando una función pasa parámetros, las referencias permiten que los parámetros formales modifiquen los parámetros reales, lo que puede simplificar la modificación del puntero de los parámetros reales.
  • La referencia debe ser una variable en la pila o montón, y no puede referirse directamente a una constante. Como int& a = 10;se mencionó aquí, no se puede hacer referencia directa a la constante porque está en la memoria y es una dirección temporal, que se puede liberar en cualquier momento.

Resumen: el efecto de pasar parámetros por referencia es el mismo que pasar por dirección, y la sintaxis de referencia es más concisa


  • Una referencia es un valor de retorno que se puede utilizar como una función

NOTA: No devuelva referencias de variables locales

Uso: llamada de función como lvalue

#include<iostream>
#include<string>

using namespace std;

// 返回局部变量引用
int& test01() {
    
    
	int a = 10;  // 局部变量
	return a;
}

// 返回静态变量引用
int& test02() {
    
    
	static int a = 20;
	return a;
}

int main() {
    
    
	// 不能返回局部变量的引用
	int& ref = test01();
	cout << "ref = " << ref << endl;  // ref = 10
	//cout << "ref = " << ref << endl;  // test01 返回的是局部变量,函数调用完之后,a 的内存被释放了,因此 ref 也被释放

	int& ref2 = test02();
	cout << "ref2 = " << ref2 << endl;  // ref2 = 20
	cout << "ref2 = " << ref2 << endl;  // ref2 = 20
  
	// 如果函数做左值,那么必须返回引用
	test02() = 1000;
	cout << "ref2 = " << ref2 << endl;  // ref2 = 1000
	cout << "ref2 = " << ref2 << endl;  // ref2 = 1000
   
	return 0;
}

La esencia de la referencia se implementa dentro de C++ como 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;
    
	func(a);

	cout << "a:" << a << endl;
	cout << "ref:" << ref << endl;

	return 0;
}
  • Las referencias constantes se utilizan principalmente para modificar parámetros formales para evitar el mal uso.
// 引用使用的场景,通常用来修饰形参
void showValue(const int& v)
{
    
    
	// v += 10;  // 因为是 const 修饰,所以不能修改 v 的值
	cout << v << endl;
}

int main()
{
    
    
	// int& ref = 10;  引用本身需要一个合法的内存空间,因此这行错误
	// 但是如果是 const 修饰的引用,编译器优化代码 int temp = 10; const int& ref = temp;
	const int& ref = 10;

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

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

	return 0;
}

Reponer:

  • No puede asignar un puntero constante a un puntero no constante, pero viceversa; no puede asignar una referencia constante a una referencia no constante, y viceversa
int a = 10;
const int& x = a;
// int& y = x;  // 这里会报错,因为将 int& 类型的引用绑定到 const int& 类型的初始值设定时,限定符会被丢弃

-----------------------------------------------

int b = 10;
int& m = b;
const int& n = m;  // 这里是可以正常编译的

b = 20;  // b m n 的值都是 20
m = 49;  // b m n 的值都是 49
// n = 99;  // 错误!!表达式必须是可以修改的左值

-----------------------------------------------

// 指针同上,但是也可以通过强制类型转换的方式,把常量指针赋值给非常量指针
const int* p1;
int* p2;

p1 = p2;  // ok
p2 = p1;  // error

p2 = (int*) p1;  // ok,强制转换

Se agregaron funciones de miembros constantes:

  • Agregue la palabra clave const después de la declaración de función miembro de la clase, entonces la función miembro es una función miembro constante

  • Durante la ejecución de una función miembro constante, no se debe modificar el objeto sobre el que actúa (no se puede modificar el valor de la variable miembro, excepto la variable miembro estática; ni se puede llamar a la misma función miembro no constante [porque puede cambiar], a excepción de la función de miembro estático)

  • Dos funciones miembro con el mismo nombre y parámetros, pero una es constante y la otra no, cuentan como sobrecarga


3 funciones

3.1 Funciones ordinarias

  • Puede haber parámetros de marcador de posición en la lista de parámetros formales de la función, que se utilizan como marcadores de posición, y el lugar debe llenarse al llamar a la función

  • El propósito de que los parámetros de función sean predeterminados es mejorar la escalabilidad del programa.

gramática 返回值类型 函数名 (数据类型){}:

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

int main()
{
    
    
	func(10, 10);  // 占位参数必须填补

	return 0;
}

Sobrecarga de funciones: los nombres de las funciones pueden ser iguales, lo que mejora la reutilización

La sobrecarga de funciones cumple las condiciones:

  • bajo el mismo alcance
  • mismo nombre de función
  • Los parámetros de la función son de diferentes tipos o números u órdenes.

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

Nota sobre la sobrecarga de funciones:

  • referencias como condiciones de sobrecarga
  • La sobrecarga de funciones encuentra parámetros predeterminados de funciones
// 1、引用作为重载条件
void func(int& a)  // int& a = 10; 错误!
{
    
    
	cout << "func (int& a) 调用 " << endl;
}

// const int& a = 10; 注意这个是合法的!
// 相当于 int tmp = 10; const int& a = tmp;
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

	// func2(10);  // 碰到默认参数产生歧义,需要避免

	return 0;
}

3.2 Funciones en línea

  • Las llamadas a funciones tienen una sobrecarga de tiempo Si la función en sí tiene solo unas pocas declaraciones, la ejecución es muy rápida. Por el contrario, la sobrecarga de las funciones de llamada será mucho mayor

  • Para reducir la sobrecarga de las llamadas a funciones, se introduce un mecanismo de función en línea. Cuando el compilador procesa una declaración de llamada para una función en línea, inserta el código de la función completa en la declaración de llamada en lugar de generar una declaración que llame a la función.

  • Agregue la palabra clave en línea antes de la definición de función para definir una función en línea

inline int Max(int a, int b)
{
    
    
	if (a > b) return a;
	return b;
}

4 Clases y Objetos

Programación estructurada: programa = estructura de datos + algoritmo

Programa Orientado a Objetos = Clase + Clase + ... + Clase

¡El proceso de diseñar un programa es el proceso de diseñar una clase!

Método de programación orientado a objetos:

  • Resumir las características comunes (atributos) de ciertos tipos de cosas objetivas para formar una estructura de datos
  • Resuma los comportamientos que tales cosas pueden realizar para formar una función que pueda usarse para manipular la estructura de datos (este paso se llama "abstracción")

  • Las funciones de miembro de clase y las definiciones de clase se escriben por separado (las definiciones fuera de la clase necesitan usar el operador :: para limitar el alcance)
class CRectangle
{
    
    
public:
	int w, h;
	int Area();  // 声明成员函数
	int Perimeter();
	void Init(int w_, int h_);
};

int CRectangle::Area()
{
    
    
	return w * h;
}

int CRectangle::Perimeter()
{
    
    
	return 2 * (w + h);
}

void CRectangle::Init(int w_, int h_)
{
    
    
	w = w_; h = h_;
}

Las tres características principales de C++ orientado a objetos son: encapsulación, herencia y polimorfismo.

C++ cree que todo es un objeto, y los objetos tienen atributos y comportamientos


4.1 Embalaje

  • Las variables en una clase se llaman variables miembro, las funciones en una clase se llaman funciones miembro, las variables definidas por una clase, también llamadas instancias de una clase, también son "objetos".

  • Cada objeto tiene su propio espacio de almacenamiento.Si se cambia una variable miembro de un objeto, no afectará a otro objeto.

  • A través de cierta forma gramatical, la estructura de datos y la función que opera la estructura de datos se "agrupan" para formar una clase, de modo que la estructura de datos y el algoritmo que opera la estructura de datos presentan una relación cercana, que es "encapsulación".

El significado del embalaje:

  • propiedades y el comportamiento como un todo, representados como cosas
  • Controle atributos y comportamientos con permisos

Hay tres tipos de derechos de acceso:

  1. público se puede acceder a los permisos públicos dentro de la clase y se puede acceder fuera de la clase
  2. protected Se puede acceder a la clase de permiso de protección fuera de la clase y no se puede acceder
  3. se puede acceder a la clase de permiso privado privado fuera de la clase no se puede acceder

El mecanismo de establecer miembros privados se llama "ocultar". El propósito de ocultar es obligar a que el acceso a las variables miembro se lleve a cabo a través de funciones miembro. Después de modificar el tipo y otros atributos de las variables miembro en el futuro, solo modifique el miembro funciones De lo contrario, todas las declaraciones que acceden directamente a las variables miembro deben modificarse

Dentro de una función miembro de una clase, puede acceder a:

  • Todas las propiedades y funciones del objeto actual
  • Todas las propiedades y funciones de otros objetos del mismo tipo.

Fuera de las funciones miembro de una clase, solo se puede acceder a los miembros públicos de los objetos de esa clase.

La única diferencia entre estructura y clase en C++ son los derechos de acceso predeterminados

  • los permisos predeterminados de la estructura son públicos
  • el permiso predeterminado de la clase es privado

4.2 Inicialización y limpieza de objetos

Orientado a objetos en C ++ viene de la vida, cada objeto tiene configuraciones iniciales y datos de limpieza antes de la destrucción del objeto


4.2.1 Constructores y destructores

La inicialización y la limpieza de objetos son dos cuestiones de seguridad muy importantes.

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á

El constructor y el destructor proporcionados por el compilador son implementaciones vacías.

  • Constructor: asigne valores a las propiedades miembro del objeto al crear el objeto. El compilador llama automáticamente al constructor sin una llamada manual.
  • Destructor: el sistema llama automáticamente antes de que se destruya el objeto para realizar algún trabajo de limpieza.

Sintaxis del constructor 类名(){}:

  1. Constructor, sin valor de retorno y sin vacío
  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. El programa llamará automáticamente al constructor cuando llame al objeto, no es necesario llamarlo manualmente, y solo se llamará una vez

Sintaxis del destructor ~类名(){}:

  1. Destructor, sin valor de retorno y sin vacío
  2. El nombre de la función es el mismo que el nombre de la clase, preceda el nombre con el 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

Hay dos clasificaciones de destructores:

  • Dividido por parámetros: construcción parametrizada y construcción no paramétrica (por defecto)
  • Dividido por tipo: construcción ordinaria y construcción de copia

Tres formas de llamar al destructor:

  • entre paréntesis
  • método de visualización
  • 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(10);

	// Person p2();  // 注意1:调用无参构造函数不能加括号,否则编译器认为这是函数声明

	// 2.2 显式法
	Person p2 = Person(10); 
	Person p3 = Person(p2);
	// Person(10);  // 单独写就是匿名对象,当前行结束之后,马上析构

	// 注意2:这里不要利用拷贝构造函数初始化匿名对象,编译器会认为是对象声明
	// Person(p9);  // Person(p9) == Person p9;

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

	// Person p5(p4);
}

Por lo general, hay tres situaciones en las que se llama al constructor de copias en C++:

  • Usar un objeto para inicializar otro objeto del mismo tipo
  • Si una función tiene un parámetro que es un objeto de la clase A, entonces cuando se llama a la función, se llamará al constructor de copias de la clase A.
  • Si el valor de retorno de la función es un objeto de clase A, cuando la función regresa, la función de transformación de copia de A se llama
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 = man;  // 不是调用拷贝构造函数
}

// 2. 值传递的方式给函数参数传值
// 相当于Person p1 = p;
void doWork(Person p1) {
    
    }
void test02()
{
    
    
	Person p;  // 无参构造函数
	doWork(p);
}

// 3. 以值方式返回局部对象
Person doWork2()
{
    
    
	Person p1;  // 局部对象在 doWork2() 作用完后,随即被释放
	cout << (int*)&p1 << endl;

	// 注意这里返回 p1 是可以的,因为他在这里创建了一个副本,这个副本指的是拷贝构造函数产生的对象,生命周期要看它被使用到什么时候!只有返回引用和指针类型的数据才会报错
	return p1;  // 这里返回的不是 p1 本身,而是创建一个 p1 副本返回
}

void test03()
{
    
    
	Person p = doWork2();
	cout << (int*)&p << endl;
}

De forma predeterminada, el compilador agrega al menos 3 funciones a 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.

Reglas de llamada del constructor:

  • Si el usuario define un constructor con parámetros, C++ ya no proporciona una construcción predeterminada sin argumentos, pero proporcionará una construcción de copia predeterminada
  • Si un usuario define un constructor de copia, c++ ya no proporciona otro constructor

Nota: Suplemento de puntos de conocimiento del constructor

  1. Si define un constructor parametrizado, ya no puede usar un constructor sin parámetros para definir variables
class Complex
{
    
    
private:
	double real, imag;
public:
	Complex(double r, double i = 0);
};

Complex::Complex(double r, double i)
{
    
    
	real = r, imag = i;
}

int main()
{
    
    
	Complex c1;  // error,缺少构造函数的参数
	Complex* pc = new Complex;  // error,缺少构造函数的参数
	Complex c1(2);  // OK
	Complex* pc = new Complex;  // OK

	return 0;
}

  1. Uso de constructores en arreglos
class CSample
{
    
    
	int x;
public:
	CSample()
	{
    
    
		cout << "Constructor 1 Called" << endl;
	}
	CSample()
	{
    
    
		x = n;
		cout << "Constructor 2 Called" << endl;
	}
};

int main()
{
    
    
	CSample array[2];  // 输出两次 Constructor 1 Called
	cout << "step1" << endl;

	CSample array2[2] = {
    
     4, 5 };
	cout << "step2" << endl;  // 输出两次 Constructor 2 Called

	CSample array3[2] = {
    
    3};
	cout << "step3" << endl;  // Constructor 2 Called,Constructor 1 Called

	CSample* array4 = new CSample[2];  // 输出两次 Constructor 1 Called

	delete[] array4;

	return 0;
}


Reponer:

01 copia constructor:

  • Solo un parámetro, una referencia a un objeto del mismo tipo.
  • La forma es como X::X( X& ) o X::X( const X& ), elija una de las dos y tenga en cuenta que el parámetro debe ser una referencia. Este último puede tomar objetos constantes como argumentos.
  • Si no se define un constructor de copia, el compilador genera un constructor de copia predeterminado y el constructor de copia predeterminado completa la función de copia

Aviso:

  • La asignación entre objetos no hace que se llame al constructor de copia
class CMyclass
{
    
    
public:
	int n;
	CMyclass() {
    
    };
	CMyclass(CMyclass& c) {
    
    
		n = 2 * c.n;  // 拷贝构造函数一般不这么做,这里只是为了举例子
	};
};

int main()
{
    
    
	CMyclass c1, c2;
	c1.n = 5;
	c2 = c1;  // 赋值语句,不是初始化语句,因此这里不会调用拷贝构造函数
	CMyclass c3(c1);  // 会调用拷贝构造函数

	cout << "c2.n=" << c2.n << ",";  // 5
	cout << "c3.n=" << c3.n << endl;  // 10

	return 0;
}

02 Uso de parámetros de referencia constantes

void fun(CMyclass obj_)
{
    
    
	cout << "fun" << endl;
}
  • Para la función anterior, la generación de parámetros formales al llamar provocará la llamada del constructor de copias, lo cual es costoso. Por lo tanto, considere usar un tipo de referencia CMyclass& como parámetro
  • Si desea que el valor del parámetro real no se cambie en la función, puede agregar la palabra clave const

03 constructor de conversión de tipo

  • El propósito de definir un constructor de conversión es lograr la conversión automática de tipos
  • Solo hay un parámetro, y el constructor que no es un constructor de copia generalmente se puede considerar como un constructor de conversión.
  • Cuando sea necesario, el sistema de compilación llamará automáticamente al constructor de conversión para crear un objeto temporal sin nombre (o variable temporal)
class Complex
{
    
    
public:
	double real, imag;
	Complex(int i)  // 类型转换构造函数,如果碰到类型不匹配的,那么会生成临时变量来接收
	{
    
    
		cout << "IntConstructor called" << endl;
		real = i;
		imag = 0;
	}

	Complex(double r, double i) {
    
     real = r; imag = i; }
};

int main()
{
    
    
	Complex c1(7, 8);
	Complex c2 = 12;
	// 因为这里直接赋值的话,类型是不匹配的。9 被自动转为临时 Complex 对象
	c1 = 9;

	cout << c1.real << "," << c1.imag << endl;

	return 0;
}

04 Destructores y arreglos

  • Al final del ciclo de declaración de la matriz de objetos, se llamará al destructor de cada elemento de la matriz de objetos
class Ctest
{
    
    
public:
	~Ctest() {
    
     cout << "destructor called" << endl; }
};

int main()
{
    
    
	Ctest array[2];  // 输出两次 destructor called
	cout << "End main" << endl;

	return 0;
}

05 Eliminación de destructor y operador

  • La operación de eliminación da como resultado una llamada al destructor.
  • Si new es una matriz de objetos, [] debe escribirse cuando se usa delete para liberar, de lo contrario, solo elimine un objeto (llame al destructor una vez)
Ctest* pTest;
pTest = new Ctest;  // 构造函数调用
delete pTest;  // 析构函数调用
------------------------------------------
pTest = new Ctest[3];  // 调用 3 次构造函数
delete[] pTest;  // 调用 3 次析构函数

06 Se llama al destructor después de que el objeto se devuelve como una función

class CMyclass
{
    
    
public:
	~CMyclass() {
    
     cout << "destructor" << endl; }
};

CMyclass obj;  // 全局函数变量,整个程序结束时,也会调用析构函数

// 这里的形参被传入参数时,调用 CMyclass 的构造函数,当参数对象消亡,也会调用析构函数
CMyclass fun(CMyclass sobj)
{
    
    
	// 这里注意 2 个点
	// 1.这里生成返回的临时对象
	return sobj;  // 函数调用返回时,返回的是生成的临时对象
}

int main()
{
    
    
	// 函数调用的返回值(临时对象)被用过后,该临时对象析构函数被调用
	// 这里是用临时对象给 obj 赋值
	// 2.临时对象的生命周期在执行完下行语句后消亡
	obj = fun(obj);  // 输出 3 次 destructor

	return 0;
}

07 Cuándo llamar a constructores y destructores

class CMyclass
{
    
    
	int id;
public:
	CMyclass(int i)  // 类型转换构造函数
	{
    
    
		id = i;
		cout << "id = " << id << " constructed" << endl;
	}
	~CMyclass() {
    
     cout << "id = " << id << " destructed" << endl; }
};

CMyclass d1(1);  // 因为这里有全局变量,所以 d1 调用构造函数比 main 中的还早

void Func()
{
    
    
	static CMyclass d2(2);
	CMyclass d3(3); 

	cout << "func" << endl;
}

int main()
{
    
    

	/* 程序执行输出结果
	id = 1 constructed
	id = 4 constructed
	id = 6 constructed
	id = 6 destructed
	main
	id = 5 constructed
	id = 5 destructed
	id = 2 constructed
	id = 3 constructed
	func
	id = 3 destructed
	main ends!
	id = 6 destructed
	id = 2 destructed
	id = 1 destructed
	*/

	CMyclass d4(4);
	d4 = 6;
	cout << "main" << endl;

	{
    
    CMyclass d5(5); }  // 局部变量,遇到有大括号后,变量使用结束

	Func();

	cout << "main ends!" << endl;

	return 0;
}

4.2.2 Copia profunda y copia superficial

Copia superficial: la referencia se usa como el valor de retorno de la función, lo que provocará la liberación repetida de memoria en el área del montón (generalmente un problema que a veces ocurre cuando se llama a un constructor)

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 << "Person 拷贝构造函数!" << endl;

		m_age = p.m_age;
		// m_Height = p.m_height;  // 编译器默认写的浅拷贝

		// 如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
		m_height = new int(*p.m_height);  // 这里要加括号,是固定用法,记住就行
	}

	// 析构函数
	~Person() {
    
    
		cout << "析构函数!" << endl;
		if (m_height != NULL)
		{
    
    
			delete m_height;
			m_height = NULL;
		}
	}
public:
	int m_age;
	int* m_height;  // 指针变量存放在栈,指针指向的值存放在堆中
};

void test01()
{
    
    
	Person p1(18, 180);  // 因为 m_height 是指针变量,所以,*m_height 的值被存放在堆中

	// 因为 p1 和 p2 是在栈中存储的,所以存储的时候,是先存储 p1 再存储 p2
	// 因此在释放(调用析构函数)的时候,先释放 p2 再释放 p1
	Person p2(p1);  // 拷贝构造函数,是浅拷贝。所以,p1 和 p2 的 m_height 指向的是同一块内存空间

	// 注意:这里用指针,是为了更好地突出深拷贝和浅拷贝面临的问题
	cout << "p1的年龄: " << p1.m_age << " 身高: " << *p1.m_height << endl;
	cout << "p2的年龄: " << p2.m_age << " 身高: " << *p2.m_height << endl;
}

Resumen: 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.


4.2.3 Lista de inicialización

Rol: utilizado para inicializar propiedades

Sintaxis 构造函数():属性1(值1),属性2(值2)... {}:

Objetos miembros y clases cerradas: una clase con objetos miembros se denomina clase envolvente.

class Person
{
    
    
public:
	// 初始化列表方式初始化
	Person(int a, int b, int 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();

	return 0;
}

4.2.4 Objetos de clase como miembros de clase

Miembro de objeto: Un miembro de una clase es un objeto de otra clase. La clase B tiene el objeto A como miembro y A es un miembro de objeto

class A {
    
    }
class B
{
    
    
	A a;
}

Al crear un objeto B, el orden de construcción es: primero llame al constructor del miembro del objeto (A) y luego llame a la construcción de esta clase; el orden de destrucción es inverso

Copiar constructor de clase envolvente

class A
{
    
    
public:
	A() {
    
     cout << "default" << endl; }
	A(A &a) {
    
     cout << "copy" << endl; }
};

class B {
    
     A a; };  // 既有无参构造函数,也有拷贝构造函数

int main()
{
    
    
	/*输出
	default
	copy*/

	// 说明 b2.a 是用类 A 的拷贝构造函数初始化的。而且调用拷贝构造函数时的实参就是 b1.a
	B b1;  // 这里输出的是 default
	B b2(b1);  // 此时,拷贝构造函数不是简单地把 b1 拷贝给 b2,是用类 A 的拷贝构造函数初始化的

	return 0;
}

4.2.5 Miembros estáticos

  • El miembro estático es agregar la palabra clave estática antes de la variable miembro y la función miembro, llamada miembro estático

  • Las variables miembro ordinarias tienen su propia copia para cada objeto, mientras que las variables miembro estáticas solo tienen una copia, que es compartida por todos los objetos.
  • sizeof no evalúa variables miembro estáticas
  • Las funciones de miembro ordinarias deben ser específicas de un objeto, mientras que las funciones de miembro estático no son específicas de un objeto; por lo tanto, se puede acceder a los miembros estáticos sin un objeto.

// 1.类名::成员名
CRectangle::PrintTotal();

// 2.对象名.成员名
CRectangle r;
r.PrintTotal();  // 这里要注意,PrintTotal 并不是作用在 r 上的

// 3.指针->成员名
CRectangle* p = &r;
p->PrintTotal();  // 这里要注意,PrintTotal 并不是作用在 p 上的

// 4.引用.成员名
CRectangle& ref = r;
int n = ref.nTotalNumber;  // 这里要注意,nTotalNumber并不是作用在 ref 上的

  • Las variables miembro estáticas son esencialmente variables globales, incluso si un objeto no existe, las variables miembro estáticas de la clase también existen.
  • Las funciones miembro estáticas son esencialmente funciones globales

Los miembros estáticos se dividen en:

  • variable miembro estática

    • Todos los objetos comparten los mismos datos.
    • Asignar memoria durante la compilación
    • Declaración en clase, inicialización fuera de clase
  • funciones de miembros estáticos

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

El propósito del mecanismo de establecer miembros estáticos es escribir algunas funciones y variables globales estrechamente relacionadas en la clase, que parece un todo y es fácil de mantener y comprender.

Las variables miembro estáticas deben declararse o inicializarse una vez en el archivo que define la clase; de ​​lo contrario, la compilación pasará y el enlace no pasará.


4.3 Modelo de objetos C++ y este puntero

En C++, las variables miembro y las funciones miembro de una clase se almacenan por separado

Solo las variables miembro no estáticas pertenecen a los objetos de la clase.

class Person
{
    
    
public:
	Person()
	{
    
    
		mA = 0;
	}

	int mA;  // 非静态成员变量占对象空间
	static int mB;  // 静态成员变量不占对象空间

	// 普通方法也不占对象空间,所有方法共享一个函数实例
	void func() {
    
    
		cout << "mA:" << this->mA << endl;
	}

	// 静态成员函数也不占对象空间
	static void sfunc() {
    
    
	}
};

int main()
{
    
    
	cout << sizeof(Person) << endl;  // 4

	return 0;
}
  • El puntero this apunta al objeto al que pertenece la función miembro llamada

  • El puntero this es un puntero implícito en cada función miembro no estática

  • El puntero this no necesita ser definido, se puede usar directamente

El propósito de este puntero:

  • Cuando el parámetro formal y la variable miembro (variable en la clase) tienen el mismo nombre, se puede usar el puntero this para distinguirlos
  • Para devolver el objeto en sí **** (objeto fuera de la clase) en la función miembro no estática de la clase , puede usar return *this
class Person
{
    
    
public:
	Person(int age)
	{
    
    
		// 1、当形参和成员变量同名时,可用 this 指针来区分
		this->age = age;  // this 指向的是类内非静态成员变量
	}

	Person& PersonAddPerson(Person p)
	{
    
    
		this->age += p.age;

		return *this;  // 返回对象本身,后续可以进行链式调用
	}

	int age;
};

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

	Person p2(10);
	p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);  // 链式调用必须得是对象
	cout << "p2.age = " << p2.age << endl;  // p2.age = 40
}

En C++, el puntero nulo también puede llamar a funciones miembro, pero también prestar atención a si se usa este puntero

Si se usa este puntero, debe evaluarse para garantizar la solidez del código.

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

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

	void ShowPerson()
	{
    
    
		if (this == NULL) {
    
    
			return;
		}

	        // 因为 p 是空指针,没有一个确切的对象,更无法访问它的值了
		cout << mAge << endl;  // 这里其实有隐藏 buff,mAge 其实是 this->mAge
	}

public:
	int mAge;
};

void test01()
{
    
    
	Person* p = NULL;
	p->ShowClassName();  // 空指针,可以调用成员函数
	// p->ShowPerson();  // 但是如果成员函数中用到了 this 指针,就不可以了
}

4.4 Amigos

Hay dos tipos de clasificación de amigos: función de amigo y clase de amigo

Amigo: En el programa, algunos atributos privados también quieren ser accedidos por algunas funciones especiales o clases fuera de la clase.

Propósito: permitir que una función o clase acceda a miembros privados en otra clase

La relación entre amigos no se puede pasar, ni se puede heredar

Tres realizaciones de amigos

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

Solo presente las funciones de miembro como amigos, otras son similares

class Building;
class goodGay
{
    
    
public:
   	// 类中声明,类外定义,定义需要使用 :: 运算符
	goodGay();
	void visit();  // 只让 visit 函数作为 Building 的好朋友,可以发访问 Building 中私有内容
	void visit2();   // 不让 vist2 访问私有成员
private:
	Building* building;
};

class Building
{
    
    
	// 告诉编译器 goodGay 类中的 visit 成员函数是 Building 好朋友,可以访问私有内容
	friend void goodGay::visit();  // 注意哦:这里要声明作用域,因为 visit 是全局函数,否则会出错

public:
	Building();

public:
	string m_SittingRoom;  // 客厅

private:
	string m_BedRoom;  // 卧室
};

// 构造函数的定义(这里是为了锻炼,写在类内也可以的)
// a::b -- a 是限定作用域,意思就是 a 的 b
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();

}

4.5 Sobrecarga del operador

Concepto de sobrecarga de operadores: redefine los operadores existentes y dales otra función para adaptarse a diferentes tipos de datos

La esencia de la sobrecarga de operadores es la sobrecarga de funciones, que puede sobrecargarse como una función ordinaria o como una función miembro.


4.5.1 Sobrecarga del operador Plus

Función: realizar la operación de agregar dos tipos de datos personalizados

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

	// 成员函数实现 + 号运算符重载
	Person operator+(const Person& p) {
    
    
		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 = p2 + p1;  // 相当于 p2.operaor+(p1)
	cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl;

	Person p4 = p3 + 10;  // 相当于 operator+(p3,10)
	cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl;
}

Resumir:

  • No es posible cambiar los operadores para expresiones de tipos de datos incorporados

  • No abuse de la sobrecarga de operadores


4.5.2 Sobrecarga del operador de turno a la izquierda

Rol: puede generar tipos de datos personalizados

class Person
{
    
    
	friend ostream& operator<<(ostream& out, Person& p);
public:
	Person(int a, int b)
	{
    
    
		this->m_A = a;
		this->m_B = b;
	}
	// 成员函数 实现不了  p << cout 不是我们想要的效果
	//void operator<<(Person& p){
    
    
	//}
private:
	int m_A;
	int m_B;
};

// 全局函数实现左移重载
// ostream 对象只能有一个
ostream& operator<<(ostream& out, Person& p)
{
    
    
	out << "a:" << p.m_A << " b:" << p.m_B;
	return out;
}

void test() 
{
    
    
	Person p1(10, 20);
	cout << p1 << "hello world" << endl;  // 链式编程
}

Resumen: sobrecargar el operador de desplazamiento a la izquierda con amigos puede generar la salida de tipos de datos personalizados


4.5.3 Sobrecarga del operador de incremento

Función: realice su propio incremento de datos enteros sobrecargando el operador de incremento

class MyInteger
{
    
    
	friend ostream& operator<<(ostream& out, MyInteger myint);
public:
	MyInteger() {
    
    
		m_Num = 0;
	}
	// 前置++;注意这里的返回值类型必须要有 &,否则多次使 用 ++,不起效果(只有第一次会实现 ++),因为在第一次使用完这个值之后就会释放掉了
    	// 返回引用是为了一直对一个数进行递增
	MyInteger& operator++() {
    
    
		m_Num++;  // 先++

		return *this;  // 再返回
	}

	// 后置++;这里其实是前置++函数的重载
	MyInteger operator++(int) {
    
      // 注意这里需要有一个占位符,用来区分前置、后置,因为函数返回类型不作为重载的条件
		// 先返回
		MyInteger temp = *this;  // 记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后 ++
		m_Num++;
		return temp;  // 后置 ++,一定要返回的是值,因为这里用到了局部变量 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;
}

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


4.5.4 Sobrecarga de operadores de asignación

  • operador de asignación, que solo se puede sobrecargar como una función miembro

El compilador de c++ agrega al menos 4 funciones a 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 del atributo

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.

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

	p3 = p2 = p1;  // 赋值操作

	cout << "p1的年龄为:" << *p1.m_Age << endl;  // 18
	cout << "p2的年龄为:" << *p2.m_Age << endl;  // 18
	cout << "p3的年龄为:" << *p3.m_Age << endl;  // 18
}

int main()
{
    
    
	test01();

	int a = 10;
	int b = 20;
	int c = 30;

	c = b = a;
	cout << "a = " << a << endl;  // 10
	cout << "b = " << b << endl;  // 10
	cout << "c = " << c << endl;  // 10

	return 0;
}

4.5.5 Sobrecarga de operadores relacionales

Rol: sobrecargar los 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;
	}
}

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

	// 匿名对象调用  
	cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;
}

4.6 Herencia

Además de las características comunes del nivel superior, los miembros del nivel inferior también tienen sus propias características. La herencia se puede utilizar para reducir la duplicación de código.

Sintaxis heredada class 子类 : 继承方式 父类:

Hay tres tipos de herencia:

  • herencia pública
  • herencia protegida
  • herencia privada
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;  // 其他类只能访问到公共权限
}

// 保护继承
class Base2
{
    
    
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};

class Son2: protected Base2
{
    
    
public:
	void func()
	{
    
    
		m_A;  // 可访问 protected 权限
		m_B;  // 可访问 protected 权限
		// m_C; // 不可访问
	}
};

void myClass2()
{
    
    
	Son2 s;
	// 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;  // 父类公共权限可访问,但是变量权限改为 private 权限
		m_B;  // 父类保护权限可访问,但是变量权限改为 private 权限
		//m_C;  // 父类私有权限不可访问
	}
};

class GrandSon3: public Son3
{
    
    
public:
	void func()
	{
    
    
		// Son3 是私有继承,所以继承 Son3 的属性在 GrandSon3 中都无法访问到
		//m_A;
		//m_B;
		//m_C;
	}
};

Conclusión: la subclase también hereda los miembros privados de la clase principal, pero el compilador los oculta y no se puede acceder a ellos.


  • Después de que la subclase herede la clase principal, cuando se crea el objeto de la subclase, también se llamará al constructor de la clase principal.
  • Primero llame al constructor de la clase principal y luego llame al constructor de la subclase, el orden de destrucción es opuesto al de la construcción

En el objeto de la clase derivada, contiene el objeto de la clase base y la ubicación de almacenamiento del objeto de la clase base se encuentra antes de la nueva variable miembro del objeto de la clase derivada.

  • Herencia: relación "es"

    • Clase base A, B es una clase derivada
    • Requisito lógico: un objeto B también es un objeto A

  • Conforme a: la relación de "has"

    • La clase C "tiene" la variable miembro k, y k es un objeto de la clase D, entonces C y D son relaciones compuestas
    • Requisito lógico general: los objetos D son propiedades inherentes o componentes de los objetos C

Cuando aparece un miembro con el mismo nombre en la subclase y en la clase principal:

  • Para acceder a los miembros del mismo nombre de la subclase, puede acceder directamente a ella
  • Para acceder a los miembros con el mismo nombre de la clase principal, se debe agregar el alcance, nombre de la clase principal::nombre del miembro

Al crear un objeto de una clase derivada,
1) ejecute primero el constructor de la clase base para inicializar los miembros heredados de la clase base en el objeto de la clase derivada;

2) Ejecute el constructor de la clase de objeto miembro para inicializar el objeto miembro en el objeto de clase derivado

3) Finalmente, ejecute el propio constructor de la clase derivada

Hay dos formas de llamar al constructor de la clase base:

- 显式方式:在派生类的构造函数中,为基类的构造函数提供参数  
- 隐式方式:在派生类的构造函数中,省略基类构造函数时,派生类的构造函数自动调用基类的默认构造函数

Reglas de compatibilidad de asignación para herencia pública :

class base{
    
    };
class derived: public base{
    
    };
base b;
derived d;

1) Un objeto de una clase derivada se puede asignar a un objeto de clase base (un objeto de clase derivada es un objeto de clase base
b = d; // 把 d 内容拷贝给 b)

2) Los objetos de clase derivados pueden inicializar referencias de clase base

base& br = d;

3) La dirección del objeto de clase derivado se puede asignar al puntero de clase base

base* pb = &d;

C++ permite que una clase herede de varias clases

Sintaxis **: class 子类 :继承方式 父类1 , 继承方式 父类2...**

La herencia múltiple puede provocar la aparición de miembros con el mismo nombre en la clase principal, que debe distinguirse por alcance

No se recomienda usar la herencia múltiple en el desarrollo real de C++

[Falló la transferencia de la imagen del enlace externo, el sitio de origen puede tener un mecanismo anti-leeching, se recomienda guardar la imagen y subirla directamente (img-Og55A1P9-1685438574381)(assets/image-20230527124733-qjm1cxw.png)]

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

  • 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
class Animal
{
    
    
public:
	int m_Age;
};

// 继承前加 virtual 关键字后,变为虚继承
// 此时公共的父类 Animal 称为虚基类
class Sheep : virtual public Animal {
    
    };
// 直接基类 Tuo
class Tuo   : virtual public Animal {
    
    };
// 派生类 SheepTuo,可以不用加 virtual
class SheepTuo : public Sheep, public Tuo {
    
    };

void test01()
{
    
    
	SheepTuo st;
	st.Sheep::m_Age = 100;  // 表示将 st.m_Age 赋值为 100,但是呢,m_Age 是在 Sheep 作用域下的
	st.Tuo::m_Age = 200;

	cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;  // 200
	cout << "st.Tuo::m_Age = " <<  st.Tuo::m_Age << endl;  // 200
	cout << "st.m_Age = " << st.m_Age << endl;  // 200
}

4.7 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: la sobrecarga de funciones y la sobrecarga de operadores pertenecen al polimorfismo estático, reutilizar nombres de funciones
  • Polimorfismo dinámico: las clases derivadas y las funciones virtuales implementan polimorfismo en tiempo de ejecución

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

  • Enlace temprano de dirección de función polimórfica estática: determine la dirección de función durante la compilación
  • Enlace tardío de la dirección de la función polimórfica dinámica: el tiempo de ejecución determina la dirección de la función

El polimorfismo satisface las condiciones:

  • Herencia
  • La subclase anula la función virtual en la clase principal

Condiciones de uso polimórfico:

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

La clave del "polimorfismo" es que cuando se llama a una función virtual a través de un puntero o referencia de clase base, no se determina en tiempo de compilación si se llama a la función de la clase base o a una clase derivada, y se determina en tiempo de ejecución: "enlace dinámico"

Reescritura: el tipo de valor de retorno de la función, el nombre de la función y la lista de parámetros son exactamente iguales, lo que se denomina reescritura

En el polimorfismo, la implementación de funciones virtuales en la clase principal generalmente no tiene sentido, principalmente llamando al contenido reescrito por la subclase

Entonces puedes cambiar la función virtual a una función virtual pura

Sintaxis de función virtual pura virtual 返回值类型 函数名 (参数列表)= 0 ;:

Cuando una clase tiene una función virtual pura, esta clase también se denomina clase abstracta.

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

class Son: public Base
{
    
    
public:
	virtual void func() 
	{
    
    
		cout << "func调用" << endl;
	};
};

void test01()
{
    
    
	Base* base = NULL;
	// base = new Base;  // 错误,抽象类无法实例化对象
	base = new Son;
	base->func();
	delete base;  // 记得销毁
}

Cuando se usa el polimorfismo, si hay propiedades en la subclase que están asignadas 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 el destructor en la clase principal a destructor virtual o destructor virtual puro

  • En la definición de una clase, una función miembro con la palabra clave virtual es una función virtual
  • La palabra clave virtual solo se usa en la declaración de la función en la definición de la clase, no al escribir el cuerpo de la función.
  • Los constructores y las funciones miembro estáticas no pueden ser virtuales

Mira el ejemplo para entender el principio de implementación del polimorfismo:

class Base
{
    
    
public:
	int i;
	virtual void Print() {
    
     cout << "Base:Print"; }
};

class Derived : public Base
{
    
    
public:
	int n;
	virtual void Print() {
    
     cout << "Derived:Print"; }
};

int main()
{
    
    
	Derived d;
	cout << sizeof(Base) << ", " << sizeof(Derived);  // 8, 12

	return 0;
}

De acuerdo con el código anterior, se puede ver que cada salida tiene 4 bytes más, lo que lleva a la clave para la implementación polimórfica: la tabla de funciones virtuales

Cada clase con funciones virtuales tiene una tabla de funciones virtuales, y cualquier objeto de esta clase tiene un puntero a la tabla de funciones virtuales. La dirección de la función virtual de esta clase aparece en la tabla de funciones virtuales, y los 4 bytes adicionales se utilizan para colocar la dirección de la tabla de funciones virtuales.

[Falló la transferencia de la imagen del enlace externo, el sitio de origen puede tener un mecanismo anti-leeching, se recomienda guardar la imagen y subirla directamente (img-BldKicUh-1685438574382)(assets/image-20230530155046-huvsjqw.png)]


Al eliminar un objeto de clase derivada a través de un puntero de clase base, generalmente solo se puede llamar al destructor de la clase base. Sin embargo, para eliminar un objeto de clase derivada, se debe llamar primero al destructor de la clase derivada y luego al destructor de la clase. la clase base debe llamarse

class son
{
    
    
public:
	~son() {
    
     cout << "bye from son" << endl; }
};

class grandson :public son
{
    
    
	~grandson() {
    
     cout << "bye from grandson" << endl; }

};

int main()
{
    
    
	son* pson;
	pson = new grandson();
	delete pson;  // 输出 bye from son

	return 0;
}

Solución: Declare el destructor de la clase base como virtual.
El destructor de la clase derivada puede ser virtual.
Al eliminar un objeto de clase derivada a través del puntero de la clase base, primero llame al destructor de la clase derivada y luego llame al destructor de la clase base Constructor

class son
{
    
    
public:
	virtual ~son() {
    
     cout << "bye from son" << endl; }
};

class grandson :public son
{
    
    
	~grandson() {
    
     cout << "bye from grandson" << endl; }
};

int main()
{
    
    
	son* pson;
	pson = new grandson();
	// 先输出 bye from grandson,在输出 bye from son
	delete pson;

	return 0;
}

En términos generales, si una clase define una función virtual, el destructor también debe definirse como una función virtual. Alternativamente, una clase destinada a ser utilizada como clase base también debe definir el destructor como una función virtual

Nota: las funciones virtuales no están permitidas como constructores


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

  • Puede resolver el puntero de la clase principal para 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 es un destructor virtual puro, la clase es una clase abstracta y no puede instanciar un objeto

Sintaxis del destructor virtual:

virtual ~类名(){}​​

Sintaxis pura del destructor virtual:

virtual ~类名() = 0;

类名::~类名(){}​​

  • Las funciones virtuales puras se pueden llamar en funciones miembro de clases abstractas , pero no se pueden llamar en constructores o destructores

Resumir:

1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
3. 拥有纯虚析构函数的类也属于抽象类

5 operaciones de archivo

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.

Los datos se pueden conservar a través de archivos

Las operaciones de archivo en C++ deben incluir archivos de encabezado

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 (salida a archivo)
  2. ifstream: operación de lectura (entrada del archivo)
  3. fstream: operaciones de lectura y escritura

5.1 Archivos de texto

01 Los pasos para escribir un archivo son los siguientes:

  1. Incluir el
    #include <fstream>archivo
  2. Crear un
    ofstream ofs;objeto
  3. abre el archivo
    ofs.open("文件路径", 打开方式);
  4. escribir datos
    ofs << "写入的数据";
  5. cerrar 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 ios::binary | ios:: outarchivos

#include <fstream>

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

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

	ofs.close();
}

02 Los pasos para leer el archivo son los siguientes:

  1. Incluir el archivo
    #include <fstream>
  2. Crear un objeto
    ifstream ifs;
  3. Abra el archivo y juzgue si el archivo se abrió con éxito.
    ifs.open("文件路径", 打开方式);
  4. leer datos
    四种方式读取
  5. cerrar 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))  // ifs -- 文件输入流对象,buf -- 文件读取之后存储的地方
	//{
    
    
	//	cout << buf << endl;
	//}

    	// 第四种,不推荐,使用前三种最好
	char c;
	while ((c = ifs.get()) != EOF)  // get() -- 按字符读取,EOF -- 文件尾部
	{
    
    
		cout << c;
	}

	ifs.close();
}

03 Punteros de lectura y escritura de archivos

ofstream fout("a1.out", ios::app);  // 以添加的方式打开
long location = fout.tellp();  // 获取写指针的位置
location = 10;
fout.seekp(location);  // 将写指针移动到第 10 个字节处
fout.seekp(location, ios::beg);  // 从头数 location
fout.seekp(location, ios::cur);  // 从当前数 location
fout.seekp(location, ios::end);  // 从尾部数 location

5.2 Binarios

Leer y escribir archivos en modo binario

El método de apertura debe especificarse como ios::binary

01 Escribir un archivo utiliza principalmente el objeto de flujo para llamar a la función miembro escribir

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

Explicación de parámetros: el búfer de puntero de carácter apunta a un espacio de almacenamiento en la memoria, y 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};  // p 存储在栈中,而{"张三", 18}存储在堆中
	
	// 4、写文件
	// write(const char* _Str, streamsize_Count),所以这里要进行强转,这里的_Str是要传地址的!
	ofs.write((const char*)& p, sizeof(p));
	
	// 5、关闭文件
	ofs.close();
}

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

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

Explicación de parámetros: el búfer de puntero de carácter apunta a un espacio de almacenamiento en la memoria, y 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));  // 这里 read 的第一个参数也是要传地址的!

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

Supongo que te gusta

Origin blog.csdn.net/Coder_J7/article/details/130798291
Recomendado
Clasificación