C++学习日记5——模板

目录

一、模板的概念

1.1 概念

1.2 特点

二、函数模板

2.1 函数模板

2.2 函数模板注意事项

2.3 函数模板案例

2.4 普通函数与函数模板的区别

2.5 普通函数与函数模板的调用规则

2.6 模板的局限性

三、类模板

3.1 类模板语法

3.2 类模板与函数模板区别

3.3 类模板中成员函数创建时机

3.4 类模板对象做函数参数

3.5 类模板与继承

3.6 类模板成员函数类外实现

3.7 类模板分文件编写

3.8 类模板与友元

四、综合案例


C++ 提高阶段:

本阶段主要针对 C+ + 泛型编程和 STL 技术做详细讲解,探讨 C++ 更深层的使用

一、模板的概念

1.1 概念

模板就是建立通用的模具,大大提高复用性

例如:

(1) 一寸照片模板

C++学习日记5——模板_第1张图片

(2) PPT 模板

1.2 特点

模板不可以直接使用,它只是个框架

模板的通用并不是万能的

二、函数模板

C++另一种编程思想称为泛型编程,主要利用的技术就是模板

C++提供两种模板机制函数模板和类模板

2.1 函数模板

1、函数模板作用:

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

 

2、语法:

template
函数声明或定义

3、解释:

template —— 声明创建模板
typename —— 表面其后面的符号是一种数据类型,可以用 class 代替
T —— 通用的数据类型,名称可以替换,通常为大写字母

4、示例:

原来的写法:

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

void swapDouble(double& a, double& b)
{
	double temp = a;
	a = b;
	b = temp;
}

修改后的写法:

#include
using namespace std;
#include


template

void mySwap(T& a, T& b)
{
	T temp = a;
	a = b;
	b = temp;
}

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

	// 1、自动类型推导
	mySwap(a, b);

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

	// 2、显示指定类型
	mySwap(a, b);

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

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

5、图示

 

2.2 函数模板注意事项

自动类型推导,必须推导出一致的数据类型 T 才可以使用

模板必须要确定出 T 的数据类型,才可以使用

#include
using namespace std;
#include

template // typename可以替换成class

void mySwap(T& a, T& b)
{
	T temp = a;
	a = b;
	b = temp;
}

void test()
{
	int a = 10;
	int b = 'b';

	// 错误:推导不出一致的T类型

	mySwap(a, b);
}

template
void func()
{
	cout << "func调用" << endl;
}

void test2()
{
	// 注意:必须要加上 
	func();
}

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

2.3 函数模板案例

案例描述:

利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序

排序规则从大到小,排序算法为选择排序

分别利用 char 数组和 int 数组进行测试

#include 
using namespace std;


//交换的函数模板
template
void mySwap(T& a, T& b)
{
	T temp = a;
	a = b;
	b = temp;
}


template // 也可以替换成typename
//利用选择排序,进行对数组从大到小的排序
void mySort(T arr[], int len)
{
	for (int i = 0; i < len; i++)
	{
		int max = i; //最大数的下标
		for (int j = i + 1; j < len; j++)
		{
			if (arr[max] < arr[j])
			{
				max = j;
			}
		}
		if (max != i) //如果最大数的下标不是i,交换两者
		{
			mySwap(arr[max], arr[i]);
		}
	}
}

template
void printArray(T arr[], int len) {

	for (int i = 0; i < len; i++) {
		cout << arr[i] << " ";
	}
	cout << endl;
}


void test()
{
	// 测试 char 数组
	char charArr[] = "badcfe";
    int intArr[] = { 7,5,4,1,9,2,3,6,8 };
	int num = sizeof(charArr) / sizeof(char);
	mySort(charArr, num);
	printArray(charArr, num);
}

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

2.4 普通函数与函数模板的区别

1、普通函数与函数模板区别:

(1) 普通函数调用时可以发生自动类型转换(隐式类型转换)

(2) 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换

(3) 如果利用显示指定类型的方式,可以发生隐式类型转换

#include 
using namespace std;


int myAdd01(int a, int b)
{
	return a + b;
}

template
T myAdd02(T a, T b) 
{
	return a + b;
}

void test()
{
	int a = 10;
	int b = 20;
	char c = 'c';
	cout << myAdd01(a, c) << endl;
	cout << myAdd02(a, c) << endl;
}

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

C++学习日记5——模板_第2张图片

2.5 普通函数与函数模板的调用规则

调用规则如下:

(1) 如果函数模板和普通函数都可以实现,优先调用普通函数

(2) 可以通过空模板参数列表来强制调用函数模板

(3) 函数模板也可以发生重载

(4) 如果函数模板可以产生更好的匹配,优先调用函数模板

1、如果函数模板和普通函数都可以实现,优先调用普通函数

#include 
using namespace std;


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

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

void test()
{
	int a = 10;
	int b = 20;
	myPrint(a, b);
}

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

C++学习日记5——模板_第3张图片

2、可以通过空模板参数列表来强制调用函数模板

void test()
{
	int a = 10;
	int b = 20;
	myPrint<>(a, b);
}

C++学习日记5——模板_第4张图片

3、函数模板也可以发生重载

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

void test()
{
	int a = 10;
	int b = 20;
	myPrint(a, b, 100);
}

C++学习日记5——模板_第5张图片

2.6 模板的局限性

1、局限性:

模板的通用性并不是万能的

template
void f(T a, T b)
{
	a = b;
}

上述代码中提供的赋值操作,如果传入的 a 和 b 是一个数组,就无法实现了

template
void f(T a, T b)
{
	if (a > b) { ... }
}

上述代码中,如果传入的数据类型是像 Person 这样的自定义数据类型,也无法正常运行

2、代码

#include 
using namespace std;
#include 

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

	string Name;
	int Age;
};


template
bool myCompare(T &a, T &b)
{
	if (a == b)
	{
		return true;
	}
	else
	{
		return false;
	}
}

// 1、运算符重载

// 2、具体化Person的版本实现代码 (具体化优先调用)
template<> bool myCompare(Person &p1, Person &p2)
{
	if (p1.Name == p2.Name && p1.Age == p2.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", 11);

	bool ret = myCompare(p1, p2);

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

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

三、类模板

3.1 类模板语法

1、类模板作用:

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

2、代码

#include 
using namespace std;
#include 

template
class Person
{
public:
	Person(NameType name, AgeType age)
	{
		this->Name = name;
		this->Age = age;
	}

	void showPerson()
	{
		cout << Name << " " << Age << endl;
	}

	NameType Name;
	AgeType Age;
};

void test()
{
	Person p1("孙悟空", 999);
	p1.showPerson();
}

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

C++学习日记5——模板_第6张图片

3.2 类模板与函数模板区别

1、类模板与函数模板区别主要有两点:

(1) 类模板没有自动类型推导的使用方式

(2) 类模板在模板参数列表中可以有默认参数

2、代码

// 1、类模板没有自动类型推导使用方式
// 即不能使用 Person p1("孙悟空", 999);
Person p1("孙悟空", 999);


// 2、类模板在模板参数列表中可以有默认参数
template

Person p2("猪八戒", 999);

3.3 类模板中成员函数创建时机

1、类模板中成员函数和普通类中成员函数创建时机是有区别的:

(1) 普通类中的成员函数一开始就可以创建

(2) 类模板中的成员函数在调用时才创建

2、代码:

#include 
using namespace std;
#include 

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

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

template
class MyClass
{
public:
	T obj;

	// 类模板中的成员函数
	void func1()
	{
		obj.showPerson1();
	}

	void func2()
	{
		obj.showPerson2();
	}
};

void test()
{
	MyClass m;
	m.func1();
	// 下面这个不能运行
	// m.func2();
}

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

3、总结:

类模板中的成员函数并不是一开始就创建的, 在调用时才去创建

3.4 类模板对象做函数参数

1、学习目标:

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

2、共有三种传入方式:

(1) 指定传入的类型 —— 直接显示对象的数据类型

(2) 参数模板化 —— 将对象中的参数变为模板进行传递

(3) 整个类模板化 —— 将这个对象类型模板化进行传递

3、代码

#include 
using namespace std;
#include 

template
class Person
{
public:
	Person(NameType name, AgeType age)
	{
		this->Name = name;
		this->Age = age;
	}

	void showPerson()
	{
		cout << Name << " " << Age << endl;
	}

	NameType Name;
	AgeType Age;
};

// 1、指定传入类型
void printPerson1(Person &p)
{
	p.showPerson();
}

void test1()
{
	Person p("孙悟空", 999);
	printPerson1(p);
}

// 2、参数模板化
template
void printPerson2(Person &p)
{
	p.showPerson();
	cout << "NameType 的类型为:" << typeid(NameType).name() << endl;
	cout << "AgeType 的类型为:" << typeid(AgeType).name() << endl;
}

void test2()
{
	Person p("猪八戒", 999);
	printPerson2(p);
}

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

void test3()
{
	Person p("唐僧", 999);
	printPerson3(p);
}

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

C++学习日记5——模板_第7张图片

4、总结:

通过类模板创建的对象,可以有三种方式向函数中进行传参

使用比较广泛是第一种:指定传入的类型

3.5 类模板与继承

1、当类模板碰到继承时,需要注意一下几点:

(1) 当子类继承的父类是一 个类模板时,子类在声明的时候,要指定出父类中T的类型

(2) 如果不指定,编译器无法给子类分配内存

(3) 如果想灵活指定出父类中T的类型,子类也需变为类模板

2、代码

#include 
using namespace std;

template
class Base
{
public:
	T m;
};

// 方式一
class Son : public Base
{
};

// 方式二:灵活指定父类中 T 类型,子类也需要变类模板
template
class Son2 : public Base
{
	T1 obj;
};

void test()
{
	Son s1;
	Son2 s2;
}

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

3.6 类模板成员函数类外实现

1、学习目标:

能够掌握类模板中的成员函数类外实现

2、代码:

#include 
using namespace std;
#include 

template
class Person
{
public:
	Person(NameType name, AgeType age);

	void showPerson();

	NameType Name;
	AgeType Age;
};

// 构造函数类外实现
template
Person::Person(NameType name, AgeType age)
{
	this->Name = name;
	this->Age = age;
}

// 成员函数类外实现
template
void Person::showPerson()
{
	cout << Name << " " << Age << endl;
}

void test()
{
	Person p("唐僧", 999);
	p.showPerson();
}

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

3.7 类模板分文件编写

方法一:

C++学习日记5——模板_第8张图片

C++学习日记5——模板_第9张图片

 C++学习日记5——模板_第10张图片

 方法二:

C++学习日记5——模板_第11张图片

 C++学习日记5——模板_第12张图片

3.8 类模板与友元

1、学习目标:

掌握类模板配合友元函数的类内和类外实现

全局函数类内实现——直接在类内声明友元即可

全局函数类外实现——需要提前让编译器知道全局函数的存在

2、代码

#include 
using namespace std;
#include 

// 提前让编译器知道 Person 类存在
template
class Person;

// 类外实现
template
void printPerson2(Person p)
{
	cout << "类外实现:" << p.Name << " " << p.Age << endl;
}

template
class Person
{
public:
	// 全局函数 类内实现
	friend void printPerson1(Person p)
	{
		cout << "姓名:" << p.Name << " " << " 年龄:" << p.Age << endl;
	}

	// 全局函数 类外实现
	// 加空模板的参数列表
	// 如果全局函数是类外实现,需要让编译器提前知道这个函数的存在
	friend void printPerson2<>(Person p);

	Person(NameType name, AgeType age)
	{
		this->Name = name;
		this->Age = age;
	}

private:
	NameType Name;
	AgeType Age;
};

// 类内实现测试
void test1()
{
	Person p("Tom", 20);
	printPerson1(p);
}

// 类外实现测试
void test2()
{
	Person p("Tom", 20);
	printPerson2(p);
}

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

3、总结:

建议全局函数做类内实现,用法简单,而且编译器可以直接识别

四、综合案例

4.1 案例描述

1、实现一个通用的数组类,要求如下:

可以对内置数据类型以及自定义数据类型的数据进行存储

将数组中的数据存储到堆区

构造函数中可以传入数组的容量

提供对应的拷贝构造函数以及 operator= 防止浅拷贝问题

提供尾插法和尾删法对数组中的数据进行增加和删除

可以通过下标的方式访问数组中的元素

可以获取数组中当前元素个数和数组的容量

4.2 代码

1、myArry.h 代码

2、

你可能感兴趣的:(C/C++,c++,学习,开发语言)