[C++] Primeira introdução aos modelos

1. Programação genérica

Suponha que queiramos implementar uma função de troca e oferecer suporte a diferentes tipos de implementações de parâmetros. Podemos usar typedef para renomear o tipo, como o seguinte código:

		// 将 int 起别名为 DataType
		typedef int DataType;
		
		void Swap(DataType& x, DataType& y)
		{
			DataType tmp = x;
			x = y;
			y = tmp;
		}
		
		int main()
		{
			DataType x = 0, y = 6;
			Swap(x, y);
		
			return 0;
		}

Desta forma, toda vez que precisarmos mudar o tipo, só precisamos mudar o int para outro tipo;

O acima é um método e há outro método que pode ser implementado usando sobrecarga de função, por exemplo:

		void Swap(int& left, int& right)
		{
			int temp = left;
			left = right;
			right = temp;
		}
		
		void Swap(double& left, double& right)
		{
			double temp = left;
			left = right;
			right = temp;
		}
		
		void Swap(char& left, char& right)
		{
			char temp = left;
			left = right;
			right = temp;
		}

Embora os dois métodos acima possam implementar funções de troca universais, eles têm as seguintes desvantagens:

  1. As funções sobrecarregadas possuem apenas tipos diferentes e a taxa de reutilização de código é relativamente baixa.Enquanto um novo tipo aparecer, o usuário precisará adicionar a função correspondente ou modificar o tipo.
  2. A capacidade de manutenção do código é relativamente baixa e um erro pode causar erros em todas as sobrecargas.

Você pode informar um modelo ao compilador e permitir que o compilador use o modelo para gerar código de acordo com diferentes tipos? A resposta é sim, aqui você precisa introduzir a programação genérica.Programação genérica : escrever código universal independente do tipo é um meio de reutilização de código. Os modelos são a base da programação genérica.

Os modelos são divididos em modelos de função e modelos de classe .

2. Modelo de função

1. O conceito de modelo de função

Um modelo de função representa uma família de funções que são independentes de tipo e são parametrizadas quando usadas para produzir uma versão específica do tipo da função com base nos tipos de parâmetros reais.

2. Formato do modelo de função

Antes de definir o modelo de função, precisamos introduzir uma palavra-chave: template , que é a palavra-chave para definir o modelo;

Formato de uso: template<typename T1, typename T2,......,typename Tn>, use colchetes angulares para colocar os parâmetros do modelo após a palavra-chave do modelo. O número de parâmetros do modelo pode ser arbitrário, mas você precisa usar a palavra-chave typename para definir os parâmetros do modelo, ou você pode usar classe (lembre-se: você não pode use struct em vez de class ).

Por exemplo, o modelo de função da função de troca:

		template<typename T>
		void Swap(T& t1, T& t2)
		{
			T tmp = t1;
			t1 = t2;
			t2 = tmp;
		}

3. Princípio do modelo de função

Um modelo de função é um modelo, que não é uma função em si. É um molde que o compilador usa para gerar um tipo específico de função. Então, na verdade, o modelo é entregar as coisas repetitivas que devemos fazer ao compilador.

Por exemplo, a figura a seguir ilustra muito bem esse processo:

Insira a descrição da imagem aqui

Na fase de compilação do compilador, para usar funções de modelo, o compilador precisa deduzir e gerar tipos correspondentes de funções para chamar com base nos tipos de parâmetros reais passados.

Por exemplo: ao usar um modelo de função com um tipo duplo , o compilador determina T como um tipo duplo através da dedução do parâmetro real type e, em seguida, gera um código que trata especificamente do tipo duplo. O mesmo se aplica aos tipos de caracteres, que isto é, o compilador usa instâncias de modelo para gerar a função Swap correspondente.

4. Instanciação do modelo de função

Quando um modelo de função é usado com parâmetros de tipos diferentes, isso é chamado de instanciação do modelo de função. A instanciação de parâmetros de modelo é dividida em: instanciação implícita e instanciação explícita .

Instanciação implícita: deixe o compilador deduzir o tipo real do parâmetro do modelo com base nos parâmetros reais.Por exemplo, o seguinte modelo de função Add implementa a adição de dois números:

		template<class T>
		T Add(T a, T b)
		{
			return a + b;
		}
		
		int main()
		{
			int a = 10, b = 20;
			double c = 1.11, d = 2.22;
		
			cout << "sum = " << Add(a, b) << endl;
			cout << "sum = " << Add(c, d) << endl;
		
			return 0;
		}

Não há problema com a instanciação das duas chamadas acima. O compilador executa a instanciação implícita e os resultados da execução são os seguintes:

Insira a descrição da imagem aqui

Mas ele será compilado se for chamado assim: Add(a, d), a resposta é não, T é deduzido para int por meio do parâmetro real a e T é deduzido para double tipo por meio do parâmetro real d , mas há apenas um T na lista de parâmetros do modelo , e o compilador não pode determinar isso. No final, T determinado como sendo do tipo int ou double e um erro será relatado.

Portanto, existem duas soluções neste momento:

  1. Os próprios usuários forçam a conversão
  2. Use instanciação explícita

Se quiser forçar a conversão sozinho, você pode usar o seguinte método:

		int main()
		{
			int a = 10, b = 20;
			double c = 1.11, d = 2.22;
			
			cout << "sum = " << Add((double)a, d) << endl;
			cout << "sum = " << Add(a, (int)d) << endl;
		
			return 0;
		}

Podemos forçar a a double ou d a int ao chamar a função Add .

Instanciação explícita: Especifique o tipo real do parâmetro do modelo em <> após o nome da função.

Por exemplo, no problema acima, usamos instanciação explícita para resolver o problema. O código é o seguinte:

		int main()
		{
			int a = 10, b = 20;
			double c = 1.11, d = 2.22;
		
			cout << "sum = " << Add(a, b) << endl;
			cout << "sum = " << Add(c, d) << endl;
		
			cout << "sum = " << Add<double>(a, d) << endl;
			cout << "sum = " << Add<int>(a, d) << endl;
		
			return 0;
		}

Usamos colchetes angulares após o nome da função para especificar o tipo do parâmetro do modelo. Esta é uma instanciação explícita.

Nota: Se os tipos não corresponderem, o compilador tentará realizar a conversão implícita de tipo. Se a conversão não puder ser bem-sucedida, o compilador reportará um erro.

5. Princípios de correspondência de parâmetros de modelo

Para funções não-modelo e modelos de função com o mesmo nome , se outras condições forem iguais, a função não-modelo será chamada primeiro durante a chamada e uma instância não será gerada a partir do modelo. O modelo será escolhido se puder produzir uma função com melhor correspondência.

Por exemplo, os dois trechos de código a seguir:

		// 专门处理int的加法函数
		int Add(int a, int b)
		{
			cout << "int Add(int a, int b)" << endl;
			return a + b;
		}
		
		// 通用加法函数
		template<class T>
		T Add(T a, T b)
		{
			cout << "T Add(T a, T b)" << endl;
			return a + b;
		}
		
		int main()
		{
			// 与非函数模板类型完全匹配,不需要函数模板实例化
			Add(1, 2); 
		
			// 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的 Add 函数
			Add<int>(1, 2); 
		
			return 0;
		}

3. Modelo de aula

Suponha que precisemos implementar uma pilha universal. Podemos usar a palavra-chave typedef para alias do tipo. Cada vez que precisarmos alterar o tipo, precisamos apenas alterar o typedef . Por exemplo, a seguinte classe Stack :

		typedef int DataType;
		class Stack
		{
		public:
			Stack(size_t capacity = 4)
			{
				_array = new DataType[capacity];
		
				_capacity = capacity;
				_size = 0;
			}
		
			~Stack()
			{
				cout << "~Stack()" << endl;
		
				delete[] _array;
				_array = nullptr;
				_size = _capacity = 0;
			}
		
		private:
			// 内置类型
			DataType* _array;
			int _capacity;
			int _size;
		};

Embora os diferentes tipos da classe Stack acima precisem apenas alterar o tipo typedef , se eu precisar de duas pilhas ao mesmo tempo, o parâmetro de uma pilha é int e o parâmetro da outra pilha é double , o método acima não funcionará Bem. Satisfeito, então apresentamos modelos de classe.

O uso de modelos de classe é o seguinte, tomando a classe Stack como exemplo:

		template<class T>
		class Stack
		{
		public:
			Stack(size_t capacity = 4)
			{
				_array = new T[capacity];
		
				_capacity = capacity;
				_size = 0;
			}
		
			~Stack()
			{
				delete[] _array;
				_array = nullptr;
				_size = _capacity = 0;
			}
		private:
			T* _array;
			int _capacity;
			int _size;
		};

O objeto instanciado é o seguinte:

		int main()
		{
			Stack<int> st1;
			Stack<double> st2;
		
			return 0;
		}

Observe que Stack é o nome da classe Stack<int>e Stack<double>é o tipo; o escopo do modelo é a classe Stack .

Desta forma, implementamos duas pilhas ao mesmo tempo. Uma pilha armazena parâmetros de int e a outra armazena double .

Acho que você gosta

Origin blog.csdn.net/YoungMLet/article/details/132091415
Recomendado
Clasificación