string和模板(初阶)

文章目录

  • 泛型编程
  • 函数模板
    • 函数模板的概念的及其形式
    • 函数模板的原理
    • 函数模板的实例化
    • 模板实例化时给两个参数
    • 函数模板的匹配原则
  • 类模板
    • 类模板的定义格式
    • 类模板的实例化
  • 结合类模板实现栈

泛型编程

#include 
#include 
using namespace std;
void Swap(int& x, int& y)
{
	int tmp = x;
	x = y;
	y = tmp;
}
void Swap(double& x, double& y)
{
	double tmp = x;
	x = y;
	y = tmp;
}
void Swap(char& left, double& right)
{
	char temp = left;
	left = right;
	right = temp;

}


如果我们使用交换函数实现两个数的交换时,使用函数重载的缺点。
1:重载的函数仅仅只是类型不同,代码复用率比较低,只要有新类型出现时,就需要我们自己增加对应的函数。
2:当有一个函数出现错误时,可能造成所有的重载函数出错。
所以便有了泛型编程产生。
**泛型编程:**编写与类型无关的通用代码,是代码复用的一种手段,模板时泛型编程的基础。
string和模板(初阶)_第1张图片

函数模板

函数模板的概念的及其形式

一:函数模板的形式

template
返回类型 函数名 ( 参数列表 )

例如:

using namespace std;
template<typename T1>
void Swap(T& x, T& y)
{
	T tmp = x;
	x = y;
	y = tmp;
}

注意:
1:其中typename后面类型名字T是随便取的,一般是大写字母,或者单词首字母大写。
2:T代表的是一种模板类型。(虚拟类型)
3:typename是定义模板类型的关键字,可以用class代替,但是不能使用struct代替。

函数模板的原理

函数模板实际是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以模板就是将应该我们重复做的事情交给了编译器。
string和模板(初阶)_第2张图片
在编译器阶段,对于模板函数的使用,编译器会根据传入的实参类型来推演生成对应类型的函数以供调用
比如:当时double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类
型,然后产生一份专门处理double类型的代码。

函数模板的实例化

用不同类型的参数使用函数模板时,称为函数模板的实例化。
:隐式类型实例化:让编译器根据实参推演模板参数的实际类型。

using namespace std;
template<typename T1>
void Swap(T& x, T& y)
{
	T tmp = x;
	x = y;
	y = tmp;
}

int main()
{
	int a1 = 10, a2 = 20; 
	double d1 = 10.0, d2 = 20.0;
	Add(a1, a2);
	Add(d1,d2);
	return 0;
}

特别注意:
对于以下代码:

int a1 = 10;
double d2 = 10.1
Add(a1,d2);

该语不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其参数类型,通过实参a1将T推演为int,

通过实参d1将T推演为double类型,但模板参数列表中只有一个T,编译器无法确定此处到底该将T确定为int类型或者是double类型而报错。

对于这种编译器无法确定模板参数T的类型时,我们有两种方式。
一:我们在传参时可以先将d2强制转换为int类型,然后再通过隐式类型实例化将T演化为对应类型。

int a1 = 10;
double d2 = 10.1
Add(a1,(int)d2);

二: 显示实例化: 在函数名后<>中指定模板的实际类型。

int Add(const T& left, const T& right)
{
	return left + right;
}
int main()
{
	int a1 = 10, a2 = 20; 
	double d1 = 10.0, d2 = 20.0;
    Add<int>(a1,d1);   //显示类型实例化
	return 0;
}

1:有一种函数必须显示实例化模板
例如:我们创建调用一个Add函数时,并没有对该函数进行显示实例化模板,此时编译器,是不会通过的。
string和模板(初阶)_第3张图片
对于函数Func(),因为参数和T没有关系,而返回值才和T有关,且形参类型和返回值的类型并不相同,所以编译器无法根据实参的类型进行隐式实例化将T推演至合适的类型,必须显示实例化模板才能调用。

例如:
我们对函数Func()进行显示实例化,从而将T推演至我们指定的类型:类A

using namespace std;

class A
{
public:
	A(int a1 = 0)
		:_a(a1)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
template<typename T1>
T1* Func(int n)
{
	T1* a = new T1[n];
	return a;
}
int main()
{
	Func<A>(1);
	return 0;
}

模板实例化时给两个参数

1:代码Add(1,2.2)编译器通过隐式实例化,将T1推演为int类型,将T2推演为double类型,然后 代码left + tight会通过隐式类型转化形成double类型,但是返回值T1又为int类型的所以会发生截断,造成数据丢失。
2:同理,对于代码(2.2,1)编译器通过隐式实例化,将T1推演为double类型,将T2推演为int类型,代码left + tight会通过隐式类型转化形成double类型返回。
string和模板(初阶)_第4张图片

函数模板的匹配原则

一:当一个非函数模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。

#include 
#include 
using namespace std;
int Add(int left, int right) //Add类型的非模板函数
{
	return left + right;
}
template < typename  T>  
T Add( T left, T right) //通用类型Add的函数模板。
{
	return left + right;
}

int main()
{
	cout << Add(1, 2) << endl;         //调用非模板函数,编译器不需要实例化。
	cout << Add<int>(1.1, 2.1) << endl;//调用编译器实例化的Add函数。
	return 0;
}

二:对于非模板函数和同名函数模板,如果其他条件都相同,在调用时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板。

int Add(int left, int right) //专门处理int的加法函数。
{
 return left + right;
}
template<class T1, class T2> //通用加法函数。
T1 Add(T1 left, T2 right)  
{
 return left + right;
}
void Test()
{
    Add(1,2);        //与非函数模板类型完全匹配,不需要函数模板实例化。
    Add(1,2.0);      //模板函数可以生成更加匹配的版本,编译器编译器根据实参推                              演成更加匹配的Add函数。
}

类模板

类模板的定义格式

template < class T1 , class T2… , class Tn >
class 类模板名
{
// 类内成员声明
};

例如:

using namespace std;
template <class T>
class Name
{
public:
	void Print()
	{
		cout << "张三" << endl;
		cout << "李四" << endl;
		cout << "小明" << endl;
	}
private:
	T _name1;
	T_ name2;
	T_ name3;
};

注意:当类模板中的成员函数若是打放在类外定义时,需要加模板参数列表。

using namespace std;
template <class T>
class Name       //Nmae类模板名
{
public:
	void Print()private:
	T _name1;
	T_ name2;
	T_ name3;
};
template <class T>
//类模板中的成员函数在类外定义,需要加模板参数列表。
void Name < T>::Print() //指名类域
{
	cout << "张三" << endl;
	cout << "李四" << endl;
	cout << "小明" << endl;
}

类模板的实例化

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

//Vector 是类名,Vector才是类型。
Vector <int> s1;
Vector <int > s2;

结合类模板实现栈

#include 
using namespace std;
#include 
template<typename T>
class Stack
{
public:
	Stack(size_t capacity = 4) //当我传参为0时,就意味着我可以不开空间,否则就默认要开空间。
		:_a(nullptr)
		, _top(0)
		, _capacity(0)
	{
		if (capacity > 0)  
		{
			_a = new T[capacity];
			_capacity = capacity;
			_top = 0;
		}
	}


	~Stack()
	{
		delete[] _a;
		_a = nullptr;
		_capacity = _top = 0;
	}

	void Push(const T& x);

	void Pop()
	{
		assert(_top > 0);
		--_top;
	}

	bool Empty()
	{
		return _top == 0;
	}

	const T& Top()
	{
		assert(_top > 0);

		return _a[_top - 1];
	}
private:
	T* _a = nullptr;
	size_t _top = 0;
	size_t _capacity = 0;
};

template<class T>
void Stack<T>::Push(const T& x) //类模板在一个文件里声明与定义分离;
{
	if (_top == _capacity)
	{
		size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2; //在入栈之前要考虑为开辟过空间和栈满的情况。
		
		T* tmp = new T[newCapacity];
		if (_a)  //如果push之前开辟过空间,则要将以前空间的数据拷贝给新空间。
			      // 然后将旧的空间释放,节省空间资源。
		{
			memcpy(tmp, _a, sizeof(T) * _top);
			delete[] _a;
		}

		_a = tmp;
		_capacity = newCapacity;
	}

	_a[_top] = x;  //top决定入栈的位置。
	++_top;
}
int main()
{
	try
	{
	//类模板都是显示实例化
// 虽然他们用了一个类模板,但是Stack,Stack 两个类型

		Stack<int> st1;
		st1.Push(1);
		st1.Push(2);
		st1.Push(3);
		st1.Push(4);
		st1.Push(5);

		while (!st1.Empty())
		{
			cout << st1.Top() << " ";
			st1.Pop();
		}
		cout << endl;
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

运行结果如下:
string和模板(初阶)_第5张图片

你可能感兴趣的:(C++,c++,算法,c语言)