Notas de estudio de C++ Plantilla de clase 22 y caso de encapsulación de clase de matriz

22.0 Prefacio

He aprendido el conocimiento relevante de las plantillas de funciones antes y luego aprenderé el conocimiento relevante de las plantillas de clase.


22.1 Sintaxis de plantilla de clase

Rol de plantilla de clase:

  • Sugiera una clase general, el tipo de datos de los miembros de la clase no se puede especificar, pero se puede representar mediante un tipo virtual .

gramática:

template<typename T,typename U,typename V,...> //类中需要几个数据类型就写几个
类的定义

//类的实例化
类名<数据类型1,数据类型2,数据类型3,...> 变量名(参数)  //在实例化的时候需要将模板的数据类型参数列表依次指定。

explicar:

  • plantilla: declara la plantilla de creación.
  • typename: indica que el símbolo detrás de él es un tipo de datos, que puede ser reemplazado por clase.
  • T,U,V: tipos de datos universales, los nombres se pueden reemplazar, generalmente en letras mayúsculas.
  • Nota: Cuando se llama a la plantilla de clase, no se puede omitir la lista de parámetros.

Ejemplo:

#include<iostream>
using namespace std;
template<class NameType,class AgeType>
class Person1
{
    
    
public:
	Person1(NameType name, AgeType age)
	{
    
    
		this->name = name;
		this->age = age;
	}
	NameType name;
	AgeType age;
	void showPerson()
	{
    
    
		cout << "姓名 : " << this->name << endl;
		cout << "年龄 : " << this->age << endl;
	}
};
void test1()
{
    
    
	Person1<string, int> p("张三", 60);
	p.showPerson();
}

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

22.2 La diferencia entre plantillas de clase y plantillas de función

Hay dos diferencias principales entre las plantillas de clase y las plantillas de función:

  1. Las plantillas de clase no tienen forma de usar la deducción automática de tipo.
  2. Las plantillas de clase pueden tener parámetros predeterminados en la lista de parámetros de función.

Ejemplo:

#include<iostream>
#include<string>
using namespace std;

template<class NameType,class AgeType>
class Person2
{
    
    
public:
	Person2(NameType name,AgeType age)
	{
    
    
		this->name = name;
		this->age = age;
	}
	void showPerson()
	{
    
    
		cout << "name : " << this->name << endl;
		cout << "age : " << this->age << endl;
	}
	NameType name;
	AgeType age;
};

template<class NameType, class AgeType = int >
class Person2_02
{
    
    
public:
	Person2_02(NameType name, AgeType age)
	{
    
    
		this->name = name;
		this->age = age;
	}
	void showPerson()
	{
    
    
		cout << "name : " << this->name << endl;
		cout << "age : " << this->age << endl;
	}
	NameType name;
	AgeType age;
};

void test2()
{
    
    
	//Person2 p("孙悟空", 1000);  无法用自动类型推导
	Person2<string, int> p("孙悟空", 1000);  //显示指定是可以的
	p.showPerson();

	Person2_02<string> p2("猪八戒", 900.3);  //有默认参数,是默认的 
	p2.showPerson();

	//用法和函数的默认参数是相同的,也要遵循从右往左的规则
	Person2_02<string,double> p3("沙僧", 800.5);  
	p3.showPerson();
}

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

22.3 Cuándo crear funciones miembro en plantillas de clase

Hay una diferencia en el tiempo de creación de funciones miembro en plantillas de clase y funciones miembro en clases ordinarias:

  • Las funciones miembro de las clases ordinarias se pueden crear desde el principio.
  • Las funciones miembro en las plantillas de clase se crean cuando se las llama
#include<iostream>
using namespace std;

class Person3_1
{
    
    
public:
	void showPerson1()
	{
    
    
		cout << "Person1 show" << endl;
	}
};

class Person3_2
{
    
    
public:
	void showPerson2()
	{
    
    
		cout << "Person2 show" << endl;
	}
};

template<class T>
class MyClass3
{
    
    
public:
	T obj;
	//成员函数
	void func1()
	{
    
    
		obj.showPerson1();
	}
	void func2()
	{
    
    
		obj.showPerson2();
	}
	//这里showPerson1()和showPerson2()是两个不同类下的成员函数,但是这里用一个变量调用,从逻辑上是说不过去的
	//但是函数模板是没问题的,因为函数模板不会在一开始就创建这两个函数,只有在调用的时候才创建
};

void test3_01()
{
    
    
	MyClass3<Person3_1> p;
	p.func1();      //因为指明了T是Person3_1,所以可以调用
	//p.func2();	//因为类型不对,所以不能调用
}
int main()
{
    
    
	test3_01();
	system("pause");
	return 0;
}

22.4 Objeto de plantilla de clase como parámetro de función (con un poco de conocimiento de la sintaxis del tipo de datos)

objetivo de aprendizaje:

  • Objetos instanciados desde plantillas de clase, cómo pasar parámetros a funciones.

Hay tres métodos de entrada:

  1. Especifique el tipo entrante: muestre directamente el tipo de datos del objeto
  2. Plantillas de parámetros: convertir los parámetros del objeto en plantillas para pasar
  3. Toda la clase con plantilla: pase este tipo de objeto con plantilla

Ejemplo:

#include<iostream>
#include<string>
using namespace std;

template<class T1,class T2>
class Person4
{
    
    
public:
	Person4(T1 name, T2 age)
	{
    
    
		this->name = name;
		this->age = age;
	}
	void showPerson()
	{
    
    
		cout << "名字是: " << name << endl;
		cout << "年龄是: " << age << endl;
	}
	T1 name;
	T2 age;
};
//1.
void printPerson1(Person4<string,int>& p)  //一般用这种,因为这样代码安全,不容易出错。
{
    
    
	p.showPerson();
}

//2.
template<class T1,class T2>
void printPerson2(Person4<T1, T2>& p)
{
    
    
	cout << "T1 : " << typeid(T1).name() << endl;  //这是看数据类型的方法,可以查看类型或者变量的数据类型.
	cout << "T2 : " << typeid(T2).name() << endl;
	p.showPerson();
}

//3.
template<class T>
void printPerson3(T& p)
{
    
    
	cout << typeid(T).name() << endl;
	p.showPerson();
}

void test4_01()
{
    
    
	//1.指定传入类型
	Person4<string, int> p1("孙悟空", 100);
	printPerson1(p1);

	//2.参数模板化
	Person4<string, int> p2("猪八戒", 90);
	printPerson2(p2);	

	//3.整个类模板化
	Person4<string, int> p3("沙僧", 30);
	printPerson3(p3);
}
int main()
{
    
    
	test4_01();
	system("pause");
	return 0;
}

Consulte la sintaxis del tipo de datos:

typeid(变量/数据类型).name()

Resumir:

  • Los objetos creados a través de plantillas de clase pueden pasar parámetros a funciones de tres maneras.
  • El más utilizado es el primero, que especifica directamente el tipo entrante.

22.5 Plantillas de clase y herencia

Cuando las plantillas de clase encuentran herencia, debe prestar atención a los siguientes puntos:

  • Cuando la clase padre heredada por la subclase es una plantilla de clase, al definir la definición, es necesario especificar el tipo de T en la clase padre, la sintaxis es:class Son:继承方式 Base<数据类型>
  • Si no se especifica, el compilador no puede asignar memoria para la subclase.
  • Si desea especificar de manera flexible el tipo de T en la clase principal, la subclase también debe convertirse en una plantilla de clase.

Ejemplo:

#include<iostream>
using namespace std;
template<class T>
class Base
{
    
    
public:
	Base()
	{
    
    
		cout << "T类型:" << typeid(T).name() << endl;
	}
	T m;
};

class Son :public Base<int>       //子类是普通函数,不指定数据类型,编译器无法分配内存.
{
    
    
public:
};

//如果想灵活指定父类T的类型,子类也需要变类模板
template<class T1, class T2>
class Son2 :public Base<T1>   //根据传递规则可知,此时父类中的数据类型T就是现在的数据类型T1
{
    
    
public:
	Son2()
	{
    
    
		cout << "T1类型:" << typeid(T1).name() << endl;
		cout << "T2类型:" << typeid(T2).name() << endl;
	}
	T2 obj;
};
void test5_01()
{
    
    
	Son s1;
	Son2<double,char> s2; //T1为int ,T2为char
}
int main()
{
    
    
	test5_01();
	system("pause");
	return 0;
}

22.6 Implementación fuera de clase de funciones miembro de plantilla de clase

gramática:

类内定义
template<class T1,class T2,...>
//类内
函数声明;

//类外
template<class T1,class T2,...>
作用域<T1, T2,...>::函数名(){
    
    }

Resumen:
cuando una función miembro en una plantilla de clase se implementa fuera de la clase, se debe agregar una lista de parámetros de plantilla (ya sea que la función miembro use parámetros de plantilla o no, se debe agregar, lo que se puede registrar como una forma fija de escribiendo).

Ejemplo:

#include<iostream>
using namespace std;

template<class T1,class T2>
class Person6 
{
    
    
public:
	Person6(T1 name, T2 age);  //类内声明
	//{
    
    
	//	this->name = name;
	//	this->age = age;
	//}
	void showPerson();
	//{
    
    
	//	cout << "姓名:" << name << endl;
	//	cout << "年龄:" << age << endl;
	//}
	T1 name;
	T2 age;
};

template<class T1,class T2>
Person6<T1, T2>::Person6(T1 name, T2 age) 
{
    
    
	this->name = name;
	this->age = age;
}
template<class T1, class T2>
void Person6<T1, T2>::showPerson()
{
    
    
	cout << "姓名:" << name << endl;
	cout << "年龄:" << age << endl;
}

void test6()
{
    
    
	Person6<string,int> p1("孙悟空",20);
	p1.showPerson();
}
int main()
{
    
    
	test6();
	system("pause");
	return 0;
}

22.7 Compilación de archivos de plantilla de clase

pregunta:

La escritura de subarchivo convencional consiste en escribir la declaración de la función miembro de la clase en el archivo de encabezado .h y escribir la definición de la función miembro de la clase en el archivo fuente .cpp.
El problema con esta escritura incorrecta es que las funciones miembro de la clase no serán creadas por el compilador desde el principio . Cuando su archivo de ejecución contiene el archivo de encabezado, el compilador ve la declaración de la función miembro en el archivo de encabezado, y la compilación no saldrá mal, pero el compilador no irá al archivo .cpp correspondiente para encontrar la definición de estos miembros. functions , lo que lleva a que las definiciones de estas funciones miembro no se incluyan en el archivo de ejecución, por lo que al llamar, el compilador no puede encontrar la definición correspondiente a la declaración y no puede analizar el comando.

Resuelto :

  • Método 1: incluir directamente archivos fuente .cpp (generalmente no se usan comúnmente)
  • Método 2: Escriba la declaración y la implementación en el mismo archivo y cambie el sufijo a .hpp.hpp es el nombre acordado, no obligatorio (generalmente se crea en el archivo de encabezado).

Ejemplo (el método 1 es muy simple, aquí solo se muestra el método 2):

Cree un archivo de encabezado person.hpp, escriba

#pragma once
#include<iostream>
using namespace std;
template<class T1, class T2>
class Person7
{
    
    
public:
	Person7(T1 name, T2 age);
	void showPerson();
	T1 name;
	T2 age;
};

template<class T1, class T2>
Person7<T1, T2>::Person7(T1 name, T2 age)
{
    
    
	this->name = name;
	this->age = age;
}
template<class T1, class T2>
void Person7<T1, T2>::showPerson()
{
    
    
	cout << "姓名: " << name << endl;
	cout << "年龄: " << age << endl;
}

Para crear un archivo ejecutable, escriba

#include"person.hpp"
#include<string>
//类模板分文件编写问题及解决

void test7()
{
    
    
	Person7<string, int> p1("孙悟空", 1000);
	p1.showPerson();
}
int main()
{
    
    
	test7();
	system("pause");
	return 0;
}

22.8 Plantillas de clase y amigos


Las plantillas de clase cooperan con implementaciones dentro y fuera de la clase de funciones amigas:
  • Implementación de funciones globales dentro de la clase: simplemente declare amigos directamente dentro de la clase.
  • Implementación fuera de clase de funciones globales: el compilador debe ser informado de la existencia de funciones globales con anticipación.
#include<iostream>
using namespace std;

//通过全局函数来打印Person8的信息

template<class T1,class T2>
class Person8;
//类外实现
// 
//对于普通的函数和类都是一开始就由编译器创建,因为这里只声明了类而没有定义,所以会报错.
//因为函数模板同样不是一开始就被创建,而是在调用的时候才创建,所以这里不用担心类的声明和定义顺序
//因为在调用此函数模板的时候,类模板的相关定义已经被创建完毕.
template<class T1, class T2>    //函数模板的实现
void printPerson2(Person8<T1, T2> p)
{
    
    
	cout << "姓名 : " << p.name << endl;
	cout << "年龄 : " << p.age << endl;
}

template<class T1,class T2>
class Person8
{
    
    
	//全局函数 类内实现    据说只能在vs上通过
	friend void printPerson(Person8<T1, T2> p)
	{
    
    
		cout << "姓名 : " << p.name << endl;
		cout << "年龄 : " << p.age << endl;
	}
	//加空模板参数列表
	//如果全局函数是类外实现,需要让编译器知道这个函数的存在
	friend void printPerson2<>(Person8<T1, T2> p);  //声明为函数模板

public:
	Person8(T1 name, T2 age) 
	{
    
    
		this->name = name;
		this->age = age;
	}
private:
	T1 name;
	T2 age;
};

void test01()
{
    
    
	Person8<string, int> p1("汤姆", 20);
	printPerson(p1);
	printPerson2(p1);
}
int main()
{
    
    
	test01();
	system("pause");
	return 0;
}

La implementación de funciones globales fuera de la clase es todavía muy complicada.
Nota:
printPerson2los miembros de la clase se llaman en la función, y esta clase solo tiene declaraciones y ninguna definición antes de esta función. Esto no se puede compilar para clases ordinarias. Puede consultar los amigos de la clase y el orden de definición y declaración de la clase .
Pero está permitido para plantillas de clase y plantillas de funciones, porque las plantillas de funciones no se crean al principio, sino que se crean cuando se las llama. La plantilla de la clase se ha creado antes de llamar a la plantilla de funciones, por lo que no hay necesidad de preocuparse. sobre la clase El orden de declaración y definición.


22.9 Caso de plantilla de clase: análisis de requisitos de encapsulación de clase de matriz

Descripción del caso: Para implementar una clase de matriz general, los requisitos son los siguientes:

  • Se pueden almacenar datos de tipos de datos integrados y tipos de datos personalizados
  • Almacene los datos en la matriz en el montón
  • La capacidad de la matriz que se puede pasar al constructor.
  • Proporcione el constructor de copia y el operador = correspondientes para evitar problemas de copia superficial
  • Proporcione métodos de inserción y eliminación de cola para agregar y eliminar datos en la matriz
  • Se puede acceder a los elementos de la matriz subíndices
  • Puede obtener la cantidad actual de elementos en la matriz y la capacidad de la matriz

Ejemplo:
Crear un archivo de encabezado:MyArray.hpp

#pragma once //防止重复包含
#include<iostream>
using namespace std;

template<class T>
class MyArray
{
    
    
public:
	//有参构造,参数是容量
	MyArray(int capacity)
	{
    
    
		cout << "MyArray有参构造调用" << endl;
		this->capacity = capacity;
		this->size = 0;
		this->pAddress = new T[this->capacity]; //capacity表示开辟的数组中的元素个数
	}

	//拷贝构造
	MyArray(const MyArray& arr)
	{
    
    
		cout << "MyArray拷贝构造调用" << endl;
		//浅拷贝
		this->capacity = arr.capacity;
		this->size = arr.size;
		//深拷贝
		this->pAddress = new T[arr.capacity];
		for (int i = 0; i < this->size; i++)
		{
    
    
			this->pAddress[i] = arr.pAddress[i];
		}
	}

	MyArray& operator=(const MyArray& arr)
	{
    
    
		cout << "MyArray的operator=构造调用" << endl;
		//判断原来堆区是否有数据
		if (this->pAddress != NULL)
		{
    
    
			delete[] this->pAddress;
			this->capacity = 0;
			this->size = 0;
		}
		//深拷贝
		this->capacity = arr.capacity;
		this->size = arr.size;
		this->pAddress = new T[arr.capacity];
		for (int i = 0; i < this->size; i++)
		{
    
    
			this->pAddress[i] = arr.pAddress[i];
		}
		return *this;
	}

	//尾插法
	void Push_Back(const T& value)
	{
    
    
		if (this->size == this->capacity) 
		{
    
    
			return;
		}
		this->pAddress[this->size] = value;
		this->size++;
	}
	//尾删法
	void Pop_Back()
	{
    
    
		//让用户访问不到删除最后一个元素,即为尾删,逻辑删除
		if (this->size == 0)
		{
    
    
			return;
		}
		this->size--;
	}
	
	//通过下标方式访问数组的元素 ,如果想让这个值作为左值,需要用引用类型,否则不会改变原数组元素. arr[0]=100
	T& operator[](int index)
	{
    
    
		return this->pAddress[index];
	}

	//返回数组的容量
	int getCapacity()
	{
    
    
		return this->capacity;
	}
	int getSize()
	{
    
    
		return this->size;
	}

	//析构函数
	~MyArray()
	{
    
    
		if (this->pAddress != 0)
		{
    
    
			cout << "MyArray析构函数调用" << endl;
			delete[] this->pAddress; //释放堆区数组
			this->pAddress = 0;
		}
	}
private:
	T* pAddress;    //指向堆区开辟的真实数组

	int capacity; //数组容量

	int size; //数组大小
};

Crear archivo ejecutable:

#include<iostream>
using namespace std;
#include"MyArray.hpp"

void printArray(MyArray<int>& arr)
{
    
    
	for (int i = 0; i < arr.getSize(); i++)
	{
    
    
		cout << arr[i] << endl;   //索引
	}
}
void test9_01()
{
    
    
	MyArray<int> arr1(10);
	MyArray<int> arr2(arr1);
	MyArray<int> arr3(100);
	arr3 = arr1;
}

void test9_02() 
{
    
    
	MyArray<int> arr1(10);
	for (int i = 0; i < 6; i++)
	{
    
    
		//尾插法插入数据
		arr1.Push_Back(i);
	}
	cout << "arr1的打印输出为: " << endl;
	printArray(arr1);

	cout << "arr1的容量为: " << arr1.getCapacity() << endl;
	cout << "arr1的大小为: " << arr1.getSize() << endl;

	MyArray<int> arr2(arr1);
	cout << "arr2的打印输出为: " << endl;
	printArray(arr2);
	arr2.Pop_Back();
	cout << "尾删后\narr2的打印输出为: " << endl;
	printArray(arr2);
}

//测试自定义数据类型
class Person9
{
    
    
public:
	Person9() {
    
    };   //必须写无参构造,向堆区开辟内存的时候是没有参数的,如果不写无参构造函数,会报错.
	Person9(string name, int age)
	{
    
    
		this->name = name;
		this->age = age;
	}
	string name;
	int age;
};
void printArray(MyArray<Person9>& arr)   //函数重载
{
    
    
	for (int i = 0; i < arr.getSize(); i++)
	{
    
    
		cout << "姓名: " << arr[i].name << "  年龄: " << arr[i].age << endl;
	}
}
void test9_03()
{
    
    
	//在向堆区开辟内存的时候如果有有参构造函数,就不会调用无参构造函数,而这里又没有传入参数,那么就无法开辟内存而报错
	MyArray<Person9> arr(10); 
	Person9 p1("孙悟空", 999);
	Person9 p2("猪八戒", 900);
	Person9 p3("沙僧", 850);
	Person9 p4("赵云", 550);
	Person9 p5("关羽", 650);

	//数据插入
	arr.Push_Back(p1);
	arr.Push_Back(p2);
	arr.Push_Back(p3);
	arr.Push_Back(p4);
	arr.Push_Back(p5);

	printArray(arr);

	//输出容量
	cout << "arr的容量为: " << arr.getCapacity() << endl;
	//输出大小
	cout << "arr的大小为: " << arr.getSize() << endl;
}
int main()
{
    
    
	test9_01();
	test9_02();
	test9_03();
	system("pause");
	return 0;
}

Supongo que te gusta

Origin blog.csdn.net/qq_49030008/article/details/123569976
Recomendado
Clasificación