黑马程序员C++笔记---模板

1.1模板的概念

1.2 函数模板

  • c++另一种编程思想是 泛型编程,利用的技术就是 模板
  • c++提供两种模板机制:函数模板类模板

1.2.1 函数模板语法

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

语法:

template
函数声明或定义

 解释:

        template---声明创建模板

        typename---表示后面的符号是一种数据类型,也可以用class

                T---通用的数据类型

示例:

#include
using namespace std;

//函数模板

//整型交换函数
void swapInt(int &a, int &b) {
	int temp = a;
	a = b;
	b = temp;
}

//浮点型交换函数
void swapFloat(float a, float b) {
	float 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;

	float c = 1.5;
	float d = 2.3;
	swapFloat(c, d);
	cout << "c = " << c << endl;
	cout << "d = " << d << endl;
}

问题:只有形参的类型不同,函数体内部过程完全相同

解决:

//函数模板:将类型参数化
template//声明模板,告诉编译器T不要报错,它是一个通用数据类型
void swapT(T &a, T &b) {
	T temp = a;
	a = b;
	b = temp;
}

void test01() {
	int a = 10;
	int b = 20;
	//两种方式使用模板
	//1、自动类型推导
	swapT(a, b);//自动推导a,b是int型
	//2、显式
	swapT(a, b);

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

1.2.2 函数模板注意事项

  • 自动类型推导,必须推导出一致的数据类型T,才可以使用
  • 模板必须要确定T的数据类型,才可以使用
template
void swapT(T &a, T &b) 
{
	T temp = a;
	a = b;
	b = temp;
}
//1、自动类型推导,必须推导出一致的数据类型T,才可以使用
void test01() {
	int a = 1;
	int b = 2;
	swapT(a, b); //正确

	char c = 'c';
	// swapT(a, c);   错误,推导出T是int 和 char
}

//2、模板必须要确定T的数据类型,才可以使用
template
void func() 
{
	cout << "func调用" << endl;
}
void test02() {
	func();//此时必须显示,告诉编译器T是什么
}

1.2.3 函数模板案例

案例描述:

  • 利用函数模板封装一个排序函数,可以对不同的数据类型数组进行排序
  • 规则是 从大到小,排序算法是 选择排序
  • 分别用 int数组 和 char数组 进行测试
//交换模板
template
void swapT(T &a, T &b)
{
	T temp = a;
	a = b;
	b = temp;
}

//实现通用的排序函数,规则是从大到小
template
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[j] > arr[max])
			{
				max = j;
			}
		}
		if (max != i)
		{
			swap(arr[i], arr[max]);
		}
	}
}

//打印数组的模板
template
void print_Arr(T arr, int len) 
{
	for (int i = 0; i < len; i++)
	{
		cout << arr[i]<<" ";
	}
	cout << endl;
}

void test01() {
	char charArr[] = "bcdeja";
	int num = sizeof(charArr) / sizeof(char);
	mySort(charArr,num);
	print_Arr(charArr, num);
}
void test02()
{
	int intArr[] = { 7,1,3,4,2,5 };
	int num = sizeof(intArr) / sizeof(int);
	mySort(intArr, num);
	print_Arr(intArr, num);
}

1.2.4 普通函数和函数模板的区别

  • 普通函数调用时可以发生 自动类型转换(隐式类型转换)
  • 函数模板调用时:
    • 如果利用自动类型推导,不会发生隐式类型转换
    • 如果利用显示 指定类型 的方式,可以发生隐式类型转换

示例:

//普通函数
int myAdd1(int a, int b)
{
	return a + b;
}
//函数模板
template
T myAdd2(T a, T b)
{
	return a + b;
}

void test1() {
	int a = 10;
	char c = 'c';
	cout << myAdd1(a, c) << endl; //普通函数调用,char自动转换为int型

	//cout << myAdd2(a, c) << endl; //错误,自动类型推导时,T不可以进行隐式转换

	cout << myAdd2(a, c) << endl; //显式指定参数类型,可以隐式转换
}

1.2.5 普通函数和函数模板的调用规则

  • 如果函数模板和普通函数都可以实现,优先调用普通函数
  • 可以通过空模板参数列表来强制调用函数模板
  • 函数模板可以重载
  • 如果函数模板可以产生更好的匹配,优先调用函数模板
void myPrint(int a, int b) {
	cout << "调用的普通函数" << endl;
}

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

//3、函数模板也可以重载
template
void myPrint(T a, T b,T c)
{
	cout << "重载的模板" << endl;
}

void test1()
{
	int a = 10;
	int b = 20;
	//1、如何函数模板和普通函数都可以实现,优先调用普通函数
	myPrint(a, b); //输出 普通函数调用

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

	//函数模板可以重载
	myPrint(a, b, 100);//调用重载的模板

	//如果函数模板可以产生更好的匹配,优先调用函数模板
	char c1 = 'a';
	char c2 = 'b';
	myPrint(c1, c2);
	//调用普通函数时,int可以隐式转换为char
	//调用函数模板时,自动推导T为char ,更直接 
}

说明:既然提供了函数模板,最好不要提供普通函数,否则容易出现二义性

1.2.6 模板的局限性

问题:

template
void f(T a, T b)
{
	a = b; //若赋值操作 传入的a,b是数组,则无法实现
}
template
void f(T a, T b)
{
	if (a > b) { //若是自定义的对象,则无法比较大小
		...
	}
}

解决:

C++为了解决这种问题,提供模板的重载,可以为这些特定的类型提供具体化的模板

class Person {
public:
	Person(string name, int age)
	{
		m_name = name;
		m_age = age;
	}
	string m_name;
	int m_age;
};

template
bool myCompare(T &a, T &b)
{
	if (a == b){
		return true;
	}
	else{
		return false;
	}
}
//利用具体化的Person版本,具体化优先调用
template<> bool myCompare(Person &a, Person &b)
{
	if (a.m_name==b.m_name&&a.m_age==b.m_age) {
		return true;
	}
	else {
		return false;
	}
}

void test1() {
	Person p1("Tom", 10);
	Person p2("Jack", 20);
	cout << myCompare(p1, p2);

}

总结:

  • 利用具体化的模板,可以解决自定义类型的通用化
  • 要学会在STL中能运用系统提供的模板

1.3 类模板

1.3.1 类模板语法

作用:建立一个通用类,类中的成员、数据类型可以不具体指定,用一个虚拟的类型来代表

语法

template

例:

template
class Person {
public:
	Person(NameType name, AgeType age) {
		m_name = name;
		m_age = age;
	}
	void show_per() {
		cout << m_name << endl;
		cout << m_age << endl;
	}
	NameType m_name;
	AgeType m_age;
};

void test1() {
	Person p1("张三", 18);
	p1.show_per();
}

1.3.2 类模板和函数模板区别

  • 类模板没有自动类型推导
  • template
    class Person {
    public:
    	Person(NameType name, AgeType age) {
    		m_name = name;
    		m_age = age;
    	}
    	void show_p() {
    		cout << m_name << endl;
    		cout << m_age << endl;
    	}
    	NameType m_name;
    	AgeType m_age;
    };
    void test1() {
    	//1、,类模板无法用自动类型推导,必须显式指定类型
    	//Person p1("张三", 18);  错误
    	Person p2("孙悟空", 999);
    	p2.show_p();
    }
  • 类模板在模板参数列表中可以有默认参数
  • template //默认参数类型
    class Person {
    public:
    	Person(NameType name, AgeType age) {
    		m_name = name;
    		m_age = age;
    	}
    	void show_p() {
    		cout << m_name << endl;
    		cout << m_age << endl;
    	}
    	NameType m_name;
    	AgeType m_age;
    };
    
    //2、类模板在模板参数列表中可以有默认参数
    void test2() {
    	Person p1("孙悟空", 999);
    	p1.show_p();
    }

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

  • 普通类中的成员函数 从一开始就可以创建
  • 类模板中的成员函数 在调用是才创建
class Person1 {
public:
	//普通类的成员函数
	void show_p1() {
		cout << "Person1 的调用"<< endl;
	}
};
class Person2 {
public:
	void show_p2() {
		cout << "Person2 的调用" << endl;
	}
};
template
class MyClass {
public:
	T obj;
	//类模板中的成员函数编译时不会创建,因为无法确定obj的数据类型
	//直到被调用的时候,确定了T的数据类型,才会调用
	void func1() {
		obj.show_p1();
	}
	void func2() {
		obj.show_p2();
	}
};

void test1()
{
	MyClass m; //此时确定了T的类型,是Person1
	m.func1();
	//m.func2(); 错误,func2不是Person1的成员函数
}

1.3.4 类模板对象做函数参数

含义:类模板实例化出的对象,像函数传参的方式

有三种传入方式:

  • 指定传入的类型 ---直接显示对象的数据类型
  • 参数模板化---将对象中的参数变为模板进行传递
  • 整个类模板化---将这个对象类型 模板化进行传递
//类模板对象做函数参数
template
class Person {
public:
	Person(T1 name, T2 age)
	{
		m_name = name;
		m_age = age;
	}
	void show_p() {
		cout << m_name << endl;
		cout << m_age << endl;
	}
	
	T1 m_name;
	T2 m_age;
};
//1、指定传入类型
void print_p1(Person &p1) {
	p1.show_p();
}

void test1() {
	Person p1("张三", 18);
	print_p1(p1);
	
}

//2、参数模板化
template
void print_p2(Person &p2) {
	p2.show_p();
	//查看模板参数 推导的类型
	cout << typeid(T1).name() << endl;
	cout << typeid(T2).name() << endl;
}
void test2()
{
	Person p2("猪八戒", 90);
	print_p2(p2);
}

//3、整个类模板化
template
void print_p3(T &p3) {
	p3.show_p();
	cout << typeid(T).name();
}
void test3()
{
	Person p3("唐僧", 999);
	print_p3(p3);
}

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

1.3.5 类模板与继承

当类模板碰到继承时,需要注意:

  • 当子类继承的父类是一个类模板时,子类在声明时,要指定出父类中T的类型
  • 如果不指定,编译器无法给子类分配内存
  • 如果想灵活制定出父类的T,子类也需要变成类模板
//类模板与继承
template
class Base {
public:
	T m;
};
// class Son1:public Base {};  //错误,必须知道T的数据类型,才能继承给子类
class Son1 :public Base {

};

//如果想灵活指定父类中的T 的类型,子类也要变成类模板
template
class Son2 :public Base {
	T1 obj;
};

void test1() {
	Son1 s1;
	Son2 s2;  //明确指出数据类型
}

总结:如果父类是类模板,子类需要指定父类T中的数据类型

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

template
class Person {
public:
	Person(T1 name, T2  age);
	/*{
		m_name = name;
		m_age = age;
	}*/
	void show_p();
	/*{
		cout << "m_name = " << m_name << endl;
		cout << "m_age = " << m_age << endl;
	}*/
	T1 m_name;
	T2 m_age;
};
//构造函数的类外实现
template
Person::Person(T1 name, T2  age) //是类模板的类外实现
{
	m_name = name;
	m_age = age;
}
//成员函数的类外实现
template
void Person::show_p()
{
	cout << "m_name = " << m_name << endl;
	cout << "m_age = " << m_age << endl;
}

void test1() {
	Person p1("张三", 18);
	p1.show_p();
}

1.3.7 类模板分文件编写

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

解决1:直接包含.cpp源文件

//person.h
#pragma once
#include
#include
using namespace std;

//类模板分文件编写问题以及解决
template
class Person {
public:
	Person(T1 name, T2  age);
	void show_p();
	T1 m_name;
	T2 m_age;
};

//由于类模板中的成员函数不创建,当编译器看到这些代码时,并不会生成函数

//person.cpp
#include"person.h"
//构造函数的类外实现
template
Person::Person(T1 name, T2  age) //是类模板的类外实现
{
	m_name = name;
	m_age = age;
}
//成员函数的类外实现
template
void Person::show_p()
{
	cout << "m_name = " << m_name << endl;
	cout << "m_age = " << m_age << endl;
}
//主函数
#include
#include
using namespace std;
//#include "perosn.h" 报错
//第一种解决方式:直接包含源文件
#include "person.cpp" //正确运行
//因为里面只有类模板的成员函数声明,没有创建
//而.cpp文件中有成员函数的具体实现,且.cpp中也包含.h文件

//第二种解决方式:.h和.cpp写在一起,后缀名为.hpp文件,约定象征类模板

void test1() {
	Person p1("张三", 18);
	p1.show_p();
}

解决2(主流):将声明和实现写到同一个文件中,并更改后缀名为.hpp

//person.hpp
#pragma once
#include
#include
using namespace std;

//类模板分文件编写问题以及解决
template
class Person {
public:
	Person(T1 name, T2  age);
	void show_p();
	T1 m_name;
	T2 m_age;
};


//构造函数的类外实现
template
Person::Person(T1 name, T2  age) //是类模板的类外实现
{
	m_name = name;
	m_age = age;
}
//成员函数的类外实现
template
void Person::show_p()
{
	cout << "m_name = " << m_name << endl;
	cout << "m_age = " << m_age << endl;
}
//主函数
#include "person.hpp"
//第二种解决方式:.h和.cpp写在一起,后缀名为.hpp文件,约定象征类模板

void test1() {
	Person p1("张三", 18);
	p1.show_p();
}

1.3.8 类模板与友元

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

template
class Person {
	//全局函数,类内实现
	friend void print_p(Person p) {
		cout << p.m_name << endl;
		cout << p.m_age << endl;
	}
public:
	Person(T1 name,T2 age) {
		m_name = name;
		m_age = age;
	}
private:
	T1 m_name;
	T2 m_age;
};

void test1() {
	Person p1("张三",18);
	print_p(p1);
}

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

template //告诉编译器,这有一个模板类
class Person;

//全局函数,类外实现,写在前面,提前告诉编译器
template
void print_p(Person p)
{
	cout << p.m_age << endl;
	cout << p.m_name << endl;
}

template
class Person {
	//全局函数,类外实现
	friend void print_p<>(Person p);
public:
	Person(T1 name,T2 age) {
		m_name = name;
		m_age = age;
	}
private:
	T1 m_name;
	T2 m_age;
};


void test1() {
	Person p1("张三",18);
	print_p(p1);
}

总结:全局函数在类内实现更方便,用法也更简单,且编译器可以直接识别

1.3.9 类模板案例

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

  • 可以对内置数据类型以及自定义数据类型的数据进行存储
  • 将数组中的数据存储到堆区
  • 构造函数中可以传入数组的容量
  • 提供对应的拷贝构造函数以及operator=防止浅拷贝的问题
  • 提供尾插法和尾删法对数组中的数据进行增加和删除
  • 通过下标的方式访问数组中的元素
  • 可以获取数组中当前元素个数和数组的容量
#pragma once
#include
#include

using namespace std;

template
class MyArray {
public:
	//构造函数
	MyArray(int capacity) {
		//cout << "有参构造的调用" << endl;
		m_Capacity = capacity;
		m_Size = 0;
		pAddress = new T[this->m_Capacity];
		
	}
	//拷贝构造
	MyArray(const MyArray& arr)
	{
		//cout << "拷贝构造的调用"<m_Capacity = arr.m_Capacity;
		this->m_Size = arr.m_Size;
		//解决浅拷贝的问题
		//this->pAddress = arr.pAddress;
		this->pAddress = new T[this->m_Capacity];
		//将arr数组数据复制到新区,否则会造成重复释放
		for (int i = 0; i < arr.m_Size; i++)
		{
			this->pAddress[i] = arr.pAddress[i];
		}
	}

	//operator=防止浅拷贝的问题 a=b=c
	MyArray& operator=(const MyArray& arr)
	{
		//先判断原来的堆区是否有数据,如果有,先释放
		if (this->pAddress)
		{
			delete[] this->pAddress;
			this->pAddress = NULL;
			this->m_Capacity = 0;
			this->m_Size = 0;
		}
		this->m_Capacity = arr.m_Capacity;
		this->m_Size = arr.m_Size;
		//解决浅拷贝的问题
		//this->pAddress = arr.pAddress;
		this->pAddress = new T[this->m_Capacity];
		//将arr数组数据复制到新区,否则会造成重复释放
		for (int i = 0; i < arr.m_Size; i++)
		{
			this->pAddress[i] = arr.pAddress[i];
		}
		return *this;  //为什么?

	}

	//尾插法
	void Push_Back(const T &val) {
		if (this->m_Capacity == this->m_Size) {
			return;
		}
		else {
			this->pAddress[this->m_Size] = val;
			this->m_Size++;
		}
	}


	//尾删法
	void Pop_Back() {
		if (this->m_Size == 0) {
			return;
		}
		else {
			this->m_Size--;
		}
	}

	//利用下标的方式访问数组的元素
	T& operator[](int index) {
		return this->pAddress[index];
	}

	//获取数组的容量
	int getCapacity() {
		return this->m_Capacity;
	}

	//获取数组的大小
	int getSize() {
		return this->m_Size;
	}

	//析构函数
	~MyArray() {
		if (this->pAddress)
		{
			delete[] pAddress;
			this->pAddress = NULL;

		}
	}


private:
	//数组
	T * pAddress; //指针指向堆区开辟的数组
	//数组容量
	int m_Capacity;
	//数组大小
	int m_Size;
};
#include
#include
using namespace std;
#include"MyArray.hpp"


class Person {
public:
	Person() {};
	Person(string name,int age)
	{
		this->m_name = name;
		this->m_age = age;
	}
	string m_name;
	int m_age;
};
void printArr(MyArray arr)
{
	for (int i = 0; i < arr.getSize(); i++) {
		cout << arr[i].m_name<<":"<arr1(10);
	Person p1("安其拉", 20);
	Person p2("韩信", 57);
	Person p3("牛魔王",13);
	Person p4("貂蝉", 45);
	Person p5("吕布", 75);
	arr1.Push_Back(p1);
	arr1.Push_Back(p2);
	arr1.Push_Back(p3);
	arr1.Push_Back(p4);
	arr1.Push_Back(p5);
	printArr(arr1);
}

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

你可能感兴趣的:(C++,c++)