c++学习笔记-提高编程-模板(哔站-黑马程序员c++教学视频)

目录

1、模板概念

2、模板特点

3、模板语法

3.1编程思想:泛型编程

3.2两种模板方法:

3.2.1 函数模板

3.2.2 类模板


1、模板概念

通用的模具,提高代码复用性

2、模板特点

不可以直接使用,只是一个框架;模板的通用性并不是万能的。

3、模板语法

3.1编程思想:泛型编程

3.2两种模板方法:

3.2.1 函数模板

函数模板的作用:建立一个通用函数,其函数返回值类型和参数类型可以不具体确定,用一个虚拟的类型来代表。

1)语法:


template<typename T>//函数声明或定义

函数


template——声明创建函数模板

typename——表明其后面的符号是一种类型,可以用class替代

T——通用的数据类型,名称可以替换,通常为大写字母

2)代码示例:

#include<iostream>
using namespace std;

//传统方法
//整型两个整型函数

void swapInt(int& a, int& b)
{
	int temp = a;
	a = b;
	b = temp;
}

//交换两个浮点型
void swapDouble(double& a, double& b)
{
	double temp = a;
	a = b;
	b = temp;
}

//函数模板
template<typename T>//声明一个模板,告诉编译器后面的代码中紧跟着的T不要报错,T是一个通用数据类型
void mySwap(T& a, T& b)
{
	T temp = a;
	a = b;
	b = temp;
}

void test01()
{
	int a = 10;
	int b = 20;

	swapInt(a, b);
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;

	double c = 30.33;
	double d = 40.1;

	swapDouble(c, d);
	cout << "c = " << c << endl;
	cout << "d = " << d << endl;
}

void test02()
{
	int a = 10;
	int b = 20;
	//利用函数模板交换
	//两种方式使用
	//1、自动类型推到
	mySwap(a, b);


	//2、显示指定类型
	mySwap<int>(a, b);//<>中指定T的类型

	cout << "a = " << a << endl;
	cout << "b = " << b << endl;

}

int main()
{
	test02();

	system("pause");
	return 0;
}

3)总结:

  • 函数模板利用关键字Template
  • 使用函数模板有两种方法:自动推导类型、显示指定类型
  • 模板的目的是为了提高代码复用性,将类型参数化

4)注意事项:

  • 自动类型推导,T必须推导出一致的数据类型
  • 模板必须要确定出T的类型,才可以使用

5)案例-选择排序

#include<iostream>
using namespace std;

//实现通用的对数组进行排序的函数
//规则 从大到小
//算法 选择排序
//测试 char 数组、int数组

//交换模板
template<typename T>
void mySwap(T& a, T& b)
{
	T temp;
	temp = a;
	a = b;
	b = temp;
}

//排序模板
template<typename T>
void mySort(T arr[],int len)
{
	for (int i = 0; i < len; i++)
	{
		int max = i;//认定最大值的下标
		for (int j = i + 1; j < len; j++)
		{
			//认定的最大值 比 遍历的j下标要小,说明j下标的元素才是真的最大值
			if (arr[max] < arr[j])
			{
				max = j;
			}
		}
		if (max != i)
		{
			//交换这两个元素
			mySwap(arr[i], arr[max]);
		}

	}

}

//打印数组的模板
template<typename T>//typename和class可相互替换
void printArray(T arr[],int len)
{
	for (int i = 0; i < len; i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
}

void test01()
{
	char charArr[] = "badcfe";
	int len = sizeof(charArr) / sizeof(char)-1;
	mySort(charArr, len);
	printArray(charArr, len);
	
}


void test02()
{
	int intArr[] = { 1,4,3,6,3,8,2,10 };
	int len = sizeof(intArr) / sizeof(int);
	mySort(intArr, len);
	printArray(intArr, len);
}

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

6)普通函数和函数模板的区别:普通函数可以发生隐式类型转换,但函数模板只有显示类型推导时,可以发生隐式类型转换

#include<iostream>
using namespace std;

//1、普通函数调用可以发生隐式类型转换
//2、函数模板 用自动类型推导,不可以发生隐式类型转换
//3、函数模板 用显示类型推导,可以发生隐式类型转换

//普通函数
int myAdd01(int a, int b)
{
	return a + b;
}

template<typename T>
int myAdd02(T a, T b)
{
	return a + b;
}

void test01()
{
	int a = 10;
	int b = 20;
	char c = 'c';//把字符型变量转换为整型。ASCII,a-97,c-99,
	cout << myAdd01(a, c) << endl;

	//cout << myAdd02(a, c) << endl;//自动推导类型!错误示例
	cout << myAdd02<int>(a, c) << endl;
}
int main()
{
	test01();
	system("pause");
	return 0;
}

 7)普通函数和函数模板的调用规则:

结论:如果创建了函数模板,就别多此一举创建普通函数了,OK?

  • 如果同时存在普通函数和模板函数,优先调用普通函数
  • 函数模板也存在函数重载
  • 可以使用空模板参数列表  强制调用 模板函数
  • 如果调用模板函数可以产生更好的效果,优先调用模板函数
#include<iostream>
using namespace std;

//普通函数与函数模板调用规则
//1、如果普通函数和模板函数都可以调用,优先调用普通函数
//2、可以通过空模板参数列表  强制调用  函数模板
//3、函数模板可以发生函数重载
//4、如果函数模板可以产生更好的匹配,优先调用函数模板

void myPrint(int a, int b)
{
	cout << "调用普通函数" << endl;
}

template<class T>
void myPrint(T &a, T &b)
{
	cout << "调用模板" << endl;
}

template<class T>
void myPrint(T a, T b,T c)
{
	cout << "调用 重载模板" << endl;
}

void test01()
{
	int a = 10;
	int b = 10;
	//myPrint(a, b);

	通过空模板的模板参数列表,强制调用函数模板
	//myPrint<>(a, b);

	函数模板也可以发生重载
	//myPrint<>(a, b,100);

	//如果函数模板产生更好的匹配,优先调用函数模板
	char c1 = 'a';
	char c2 = 'c';
	myPrint(c1, c2);
}
int main()
{
	test01();
	system("pause");
	return 0;
}

 8)模板的局限性

 模板并不是万能的,有些特定的数据类型,需要具体化方式做特殊实现

#include<iostream>
using namespace std;

//模板的局限性
//模板并不是万能的,有些特殊数据类型,需要具体化方式做特殊实现

class Person
{
public:
	Person(string name, int age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}
	string m_Name;
	int m_Age;
};

//对比两个数据是否相等
template<class T>
bool myCompare(T& a, T& b)
{
	if (a == b)
	{
		return true;
	}
	else
	{
		return false;
	}
}

//利用具体化PersomyComparen的版本,具体化优先调用
template<>bool myCompare(Person& p1, Person& p2)
{
	if (p1.m_Name==p2.m_Name && p1.m_Age == p2.m_Age)
	{
		return true;
	}
	else
	{
		return false;
	}
}

void test01()
{
	int a = 10;
	int b = 20;

	bool ret = myCompare(a, b);

	if (ret)
	{
		cout << "a == b" << endl;
	}
	else
	{
		cout << "a != b" << endl;
	}

}
void test02()
{
	Person p1("Tom", 10);
	Person p2("Tom",20);

	bool ret = myCompare(p1, p2);

	if (ret)
	{
		cout << "p1 == p2" << endl;
	}
	else
	{
		cout << "p1 != p2" << endl;
	}

}

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

3.2.2 类模板

建立一个通用类,类中成员  数据类型可以不具体制定,用一个虚拟的类型来代替

1)类模板语法


template <typename T>


template——声明创建模板

typename——表明其后面的符号是一种类型,可以用class替代

T——通用的数据类型,名称可以替换,通常为大写字母

#include<iostream>
using namespace std;

//模板类


template<class NameType,class AgeType>
class Person
{
public:
	Person(NameType name, AgeType age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}
	void ShowPerson()
	{
		cout << "name:  " << this->m_Name << " age:  "<<this->m_Age << endl;
	}
	NameType m_Name;
	AgeType m_Age;
};


void test01()
{
	Person<string, int>p1("Susan", 99);//<string, int>模板参数列表
	p1.ShowPerson();

}

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

2)类模板和函数模板的区别

  • 类模板没有自动类型推导的使用方式
  • 类模板在模板的参数列表中,可以有默认参数
#include<iostream>
using namespace std;

//模板类和函数模板的区别
//1、类模板没有自动类型推导的使用方式
//2、类模板在模板的参数列表中,可以有默认参数


//template<class NameType, class AgeType >//没默认参数
template<class NameType, class AgeType = int>//类模板可以有默认参数
class Person
{
public:
	Person(NameType name, AgeType age)
	{
		this->m_name = name;
		this->m_age = age;
	}

	void ShowPerson()
	{
		cout << "姓名:" << this->m_name << "  年龄:" << this->m_age << endl;
	}

	NameType m_name;
	AgeType m_age;
};



void test01()
{
	//Person p1("Susan", 18);//无法自动推导!错误示例

	//无默认参数的调用
	Person<string, int> p1("Susan", 18);//只能显示指定类型,正确示例
	p1.ShowPerson();
}

void test02()
{
	//有默认参数的调用
	Person<string>  p("Tom", 999);
	p.ShowPerson();
}

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

3)类模板中成员函数的调用时机

  • 普通类中的成员函数在一开始就可以创建
  • 类模板中的成员函数在调用时才可以创建
#include<iostream>
using namespace std;

//类模板中的成员函数的创建时机
//普通类中的成员函数一开始就可以创建
//类模板中的成员函数在调用时才可以创建

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

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

template<class T>
class MyClass
{
public:
	T obj;

	//类模板中的成员函数,并不是一开始就创建,而是在模板调用时再生成
	void func1()
	{
		obj.showPerson1();
	}
	void func2()
	{
		obj.showPerson2();
	}
};

void test01()
{
	MyClass<Person1>m;
	m.func1();
	//m.func2();//编译会出错,说明函数调用才会去创建成员函数
}

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

4)类模板对象做函数参数

类模板实例化出的对象,向函数传参的方式共三种:

  • 指定传入的类型 ---  直接显示对象的数据类型
  • 参数模板化        ---将对象的参数变为模板后进行传递
  • 整个类模板化   ---将这个对象类型 模板化进行传递
#include<iostream>
#include<string>
using namespace std;

//类模板对象做函数参数
//1、指定传入的类型
//2、参数模板后
//3、整个类模板化

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

	T1 m_Name;
	T2 m_Age;
};

//1、指定传入的类型
void printPerson(Person<string, int>&p)
{
	p.showPerson();
}
void test01()
{
	Person<string, int>p("Susan", 18);
	printPerson(p);
	
}

//2、将参数模板化
template<class T1,class T2>
void printPerson2(Person<T1,T2>&p)
{
	p.showPerson();
	cout << "T1 的类型:" << typeid(T1).name() << endl;
	cout << "T2的类型:" << typeid(T2).name() << endl;

}
void test02()
{
	Person<string, int>p("Tom", 98);
	printPerson2(p);

}

//3、整个类模板化
template<class T>
void printPerson3(T &p)
{
	p.showPerson();
	cout << "T的数据类型:" << typeid(T).name() << endl;
}

void test03()
{
	Person<string, int>p("Lily", 20);
	printPerson3(p);
}

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

 总结:指定传入类型比较常用!

5)类模板与继承

  • 当子类继承的是父类木板时,子类声明的时候,需要指定父类模板中T的类型
  • 如果不指定,编译器无法给子类分配内存
  • 如果想灵活指定父类中T的类型,子类也需要变为模板
#include<iostream>
#include<string>
using namespace std;

//类模板的成员函数的类外实现

template<class T1,class T2>
class Person
{
public:
	Person(T1 name, T2 age);

	void showPerson();
	
	T1 m_Name;
	T2 m_Age;
};

//构造函数的类外实现
template<class T1,class T2>
Person<T1,T2>::Person(T1 name, T2 age)
{
	this->m_Name = name;
	this->m_Age = age;
}

//成员函数的类外实现
template<class T1,class T2>
void Person<T1,T2>::showPerson()//!!!必须写模板参数列表!!!
{
	cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl;
}

void test01()
{
	Person<string, int>p("Tom", 100);
	p.showPerson();
}

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

注意:成员函数的类外实现
template<class T1,class T2>
void Person<T1,T2>::showPerson()//!!!必须写模板参数列表!!! 

6)类模板分文件编写

类模板中的成员函数创建时机时在调用阶段,导致分文件编写时链接不上

  • 解决方法1:直接包含.cpp文件
  • 解决方法2:将声明和实现写到同一个文件中,并改后缀名为.hpp,hpp是约定的名称,并不强制

person1.hpp文件

#pragma once
#include<iostream>
using namespace std;

template<class T1, class T2>
class Person
{
public:
	Person(T1 name, T2 age);

	void showPerson();

	T1 m_Name;
	T2 m_Age;
};

template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
	this->m_Name = name;
	this->m_Age = age;
}

template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
	cout << "姓名:" << this->m_Name << "  年龄:" << this->m_Age << endl;
}

13模板-类模板分文件编写.cpp

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

//第一种解决方式
#include"person.cpp"//#include"person.h"改为#include"person.cpp"才不会报错

//第二种解决方式  将.h和.cpp文件中的内容写到一起,将后缀名改为.hpp文件
#include"person1.hpp"

//类模板的份文件编写
//类模板中的成员函数创建时机在调用阶段,导致分文件时链接不是
//
//template<class T1, class T2>
//class Person
//{
//public:
//	Person(T1 name, T2 age);
//
//	void showPerson();
//
//	T1 m_Name;
//	T2 m_Age;
//};

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

void test01()
{
	Person<string, int>p("Susan", 19);
	p.showPerson();
}

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

7)类模板和友元:建议使用全局函数的类内实现

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

//类模板和友元
// 通过全局函数打印Person的信息

//提前让编译器知道Person类的存在
template<class T1,class T2>
class Person;

//全局函数  类外实现
template<class T1, class T2>
void printPerson2(Person<T1, T2> p)
{
	cout << "类外实现——Name: " << p.m_Name << "  Age:" << p.m_Age << endl;
}

template<class T1,class T2>
class Person
{
	//全局函数  类内实现
	friend void printPerson(Person<T1, T2> p)
	{
		cout << "Name: " << p.m_Name << "  Age:" << p.m_Age << endl;
	}

	//全局函数  类外实现
	//加空模板的参数列表
	//如果成员函数类外实现,需要让编译器提前知道这个函数的存在
	friend void printPerson2<>(Person<T1, T2> p);
public:
	Person(T1 name,T2 age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}
private:
	T1 m_Name;
	T2 m_Age;
};


//类内实现测试
void test01()
{
	Person<string, int>p("Tom",10);
	printPerson(p);
}

//类外实现测试
void test02()
{
	Person<string, int>p("Susan", 2);
	printPerson2(p);
}

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

8)案例:通用的数组类

  • 运用知识:类模板、深浅拷贝、函数重载
  • 几个重点:

T* = new T[5];//new出来地址所以用地址变量承接,new的类型是T,所以指针数据类型也是T

”拷贝构造和operator=“防止在堆区数据浅拷贝带来的问题

T& operator[](int index)//函数调用作为左值,要反回引用&

  • 代码实现:

#pragma once
//自己通用的数组类
#include<iostream>
using namespace std;

template<class T>
class MyArray
{
public:
	//有参构造  参数 容量
	MyArray(int capacity)
	{
		//cout << "MyArray 的有参构造调用" << endl;
		this->m_Capacity = capacity;
		this->m_Size = 0;
		this->pAddress = new T[capacity];
	}

	MyArray(const MyArray& arr)//拷贝构造 防止浅拷贝问题
	{
		//cout << "MyArray 的拷贝构造调用" << endl;
		this->m_Capacity = arr.m_Capacity;
		this->m_Size = arr.m_Size;
		//this->pAddress = arr.pAddress;//浅拷贝导致堆区开辟空间重复释放

		//深拷贝
		this->pAddress = new T[arr.m_Capacity];
		//将arr中的数据拷贝过来了
		for (int i = 0; i < this->m_Size; i++)
		{
			this->pAddress[i] = arr.pAddress[i];
		}
	}

	//operater= 防止浅拷贝问题
	MyArray& operator=(const MyArray& arr)
	{
	    //cout << "MyArray 的operater=调用" << endl;
		//先判断原来堆区是否有数据,有就先释放
		if (this->pAddress != NULL)
		{
			delete[] this->pAddress;
			this->pAddress = NULL;
			this->m_Size = 0;
			this->m_Capacity = 0;
		}
		//深拷贝
		this->m_Capacity = arr.m_Capacity;
		this->m_Size = arr.m_Size;
		this->pAddress = new T[arr.m_Capacity];
		for (int i = 0; i < arr.m_Capacity; i++)
		{
			this->pAddress[i] = arr.pAddress[i];
		}
		return* this;
	}

	//尾增
	void Push_Back(const T& val)
	{
		//判断容量是否等于大小
		if (this->m_Capacity == this->m_Size)
		{
			return;
		}
		this->pAddress[this->m_Size] = val;//数组末尾插入数据
		this->m_Size++;//更新数组大小
	}

	//尾删
	void Pop_Back()
	{
		//让用户访问不到最后一个元素,即为尾删,逻辑删除
		if (this->m_Size == 0)
		{
			return;
		}
		this->m_Size--;
	}

	//用户通过下标访问数组中元素 arr[0]=100;
	T& operator[](int index)//函数调用作为左值,要反回引用&
	{
		return this->pAddress[index];
	}

	//返回数组容量
	int getCapacity()
	{
		return this->m_Capacity;
	}
	//返回数组大小
	int getSize()
	{
		return this->m_Size;
	}


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

	int m_Capacity;//数组容量

	int m_Size;
};

猜你喜欢

转载自blog.csdn.net/qq_26572229/article/details/129043774