C++笔记八(模板)

1.模板

1.1·C++中除了有面向对象的思想外还有一种编程思想为泛型编程,主要利用技术便是模板。

1.2基本概念:template称为被参数化的类型,称其参数化是因为把类型相关的信息剥离出模板;称其类型是因为模板会根据它所作用的类型而有性质上的变化。

1.3特点:提高复用性,模板不可直接使用,且模板的通用性不是万能的。

1.4 模板分为两种:函数模板、类模板

1.5 模板参数并不非得是某个类型,也可以是常量表达式等等,暂时先知道即可,等后面遇到了再补充,本文全是当作某种类型来看。

1.6建议:将模板的类型参数视作类类型,根据实际使用情况能用常引用就不用值传递,对于类模板,能用初始化列表就用初始化列表

2.函数模板

2.1函数模板:建立一个通用模板,其函数的返回类型与参数类型不具体制定,用一个虚拟的类型来表示。

2.1语法:

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

template:声明创建模板,后面跟着模板参数列表
typename:表示后面的符号是一种数据类型,可以用class代替,效果一样
T:通用的数据类型,名称可以替换,通常为大写字母
示例:

template<typename T>
void func(T &a, T &b)	//交换函数
{
    
    
	T temp;
	temp = a;
	a = b;
	b = temp;
}
int main()
{
    
    
	int a = 10;
	int b = 20;
	char c = 'x';
	char d = 'y';
	cout << "a:" << a << "\tb:" << b << endl;
	func(a, b);	//模板第一种使用方法:自动数据推导,实参推断
	cout << "a:" << a << "\tb:" << b << endl;
	cout << "c:" << c << "\td:" << d << endl;
	func<char>(c, d);	//模板第二种使用方法:显示指定类型,显示实参
	cout << "c:" << c << "\td:" << d << endl;
	return 0;
}

注意:
1>其实模板有类型参数化的味道,上面实例给出了使用模板的两种方法。
2>使用自动数据推导时,必须推导出一致的数据类型才可以使用,简单说就是数据类型要合法,而且并不是说只能有一个类型参数(如:template<typename T, class T1>)
3>模板必须要确定出T的数据类型才可以使用(即使没有使用到T)

template<class T>
void func2()
{
    
    
	cout << "hello!" << endl;
}

int main()
{
    
    
	//func2();	//错误
	func2<int>();	//正确
	return 0;
}

2.3函数模版与一般函数区别:
1>普通函数可以发生隐式类型转换
2>使用函数模板显示指定类型也可以发生隐式类型转换
3>使用函数模板自动类型推导则不可发生隐式类型转换

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

template<class T>
T Add2(T a,T b)
{
    
    
    return a+b;
}

void test()
{
    
    
    int a=10;
    int b=20;
    char c='c';
    cout<<Add1(a, b)<<endl;
    cout<<Add1(a, c)<<endl; //发生隐式类型转换,c转换成ascii码99
    //cout<<Add2(a, c)<<endl; //发生错误,可见自动数据推导不可隐式转换
    cout<<Add2<int>(a, c)<<endl;    //显示指定类型可以发生隐式转换
}

int main()
{
    
    
    test();
    return 0;
}

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

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

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

template<typename T>    //函数模板可发生重载
void myPrintf(T a, T b, T c)
{
    
    
    cout<<"调用重载模板!"<<endl;
}

void test()
{
    
    
    int a = 10;
    int b = 20;
    char c = 'c';
    char d = 'd';
    //当普通函数与函数模板都可以实现时调用普通函数
    myPrintf(a, b); //输出为调用普通函数!
    //虽然c、d可以通过隐式转换调用普通函数但函数模板更加适合,所以调用函数模板
    myPrintf(c, d); //输出为调用模板!
    //可以通过空模板参数列表来调用函数模板
    myPrintf<>(a, b);   //输出为调用模板!
    //函数模板可以发生重载
    myPrintf(a, b, 100);    //输出为调用重载模板!
}

int main()
{
    
    
    test();
    return 0;
}

注意:既然提供了函数模板,那就最好不要再提供普通函数来,否则容易产生二义性!

2.5函数模板的局限性
模板不是万能的,比如使用自定义数据类型就不太适合

class People
{
    
    
public:
	People(string name, int age)
	{
    
    
		this->m_name = name;
		this->m_age = age;
	}
	string m_name;
	int m_age;
};

template<class T>
void Compare(T a, T b)
{
    
    
	if (a == b)	//此处有解决方法使得p1\p2比较,用运算符重载
		cout << "相等!" << endl;
	else
		cout << "不相等!" << endl;
}

//特例化比较函数
template<>
void Compare(People a, People b)
{
    
    
	if (a.m_age == b.m_age&&a.m_name == b.m_name)
		cout << "相等!" << endl;
	else
		cout << "不相等!" << endl;
}

void test()
{
    
    
	People p1("张三", 19);
	People p2("张三", 9);
	int a = 10;
	int b = 10;
	Compare(a, b);
	Compare(p1, p2);	//如果不重载则报错,因为编译器知道T类型是People但在比较中无法比较
}
int main()
{
    
    
	test();
	return 0;
}

所以,可以使用模板特例化来解决模板使用自定义类型的问题。注意,模板特例化是一个实例而不是重载!!!

3.类模板

3.1 类模板是建立一个通用类,类中的成员数据类型不确定,用一个虚拟类型表示
3.2类模板语法

template<typename T>
//类

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

3.3类模板与函数模板的区别
1>类模板没有自动类型推导的使用方式(类型推断)只有显示实参
2>类模板在模板参数列表中可以有默认参数(规则还是与之前一样)

template<class TypeName, class TypeAge>
class People
{
    
    
public:
	People(string name, int age)
	{
    
    
		this->m_name = name;
		this->m_age = age;
	}
	TypeName m_name;
	TypeAge m_age;
};

template<class TypeName, class TypeAge=int>
class Animal
{
    
    
public:
	Animal(string name, int age)
	{
    
    
		this->m_name = name;
		this->m_age = age;
	}
	TypeName m_name;
	TypeAge m_age;
};
void test()
{
    
    
	//函数模板可以是因为可以根据实参的类型、顺序、个数来匹配,
	//但类模板就不行,因为这传入的是用于初始化成员变量的参数,简单说构造函数只负责初始化没有这样的功能
	//People p1("张三", 10);	//报错
	People<string, int> p1("张三", 10);

	Animal<string> a1("猫", 10);	//类模板可以有默认参数
}
int main()
{
    
    
	test();
	return 0;
}

3.4 类模板的成员函数创建时机:并不是一开始就创建,而是在调用时才创建。

class People1
{
    
    
public:
	void Show1()
	{
    
    
		cout << "调用People1中的Show1!" << endl;
	}
};

class People2
{
    
    
public:
	void Show2()
	{
    
    
		cout << "调用People2中的Show2!" << endl;
	}
};

template<class T>
class Animal
{
    
    
public:
	T t;
	void Show3()
	{
    
    
		t.Show1();
	}
	void Show4()
	{
    
    
		t.Show2();
	}
};
void test()
{
    
    
	Animal<People1> animal;
	animal.Show3();
	//animal.Show4();	//报错!
}
int main()
{
    
    
	test();
	return 0;
}

3.5类模板对象做函数参数
一共有三种传入方式:
1> 指定传入的类型——直接显示对象的数据类型(推荐
2>参数模板化——将对象中的参数变为模板进行传递
3>整个类模板化——将这个对象类型模板化进行传递

template<class T1, class T2>
class People
{
    
    
public:
	People(T1 name,T2 age)
	{
    
    
		this->m_Name = name;
		this->m_Age = age;
	}
	void show()
	{
    
    
		cout << "姓名为:" << m_Name << "     年龄为:" << m_Age << endl;
	}
	T1 m_Name;
	T2 m_Age;
};
//第一种:指定传入数据类型,直接把类模板的数据类型确定
void myPrintf1(People<string, int> p)
{
    
    
	p.show();
}

void test1()
{
    
    
	People<string, int> p1("张三", 10);
	myPrintf1(p1);
}
//第二种:参数模板化
template<class T1,class T2>
void myPrintf2(People<T1, T2> p)
{
    
    
	p.show();
}
void test2()
{
    
    
	People<string, int> p2("张三", 20);
	myPrintf2(p2);
}
//第三种:整个类模板化
template<class T>
void myPrintf3(T p)
{
    
    
	p.show();
}
void test3()
{
    
    
	People<string, int> p3("张三", 30);
	myPrintf3(p3);
}

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

推荐使用第一种!

3.6类模板与继承
注意事项:
1> 当子类继承的父类是一个类模板时,子类在声明时,要指定出父类的数据类型
2> 如果不指定,编译器将无法给子类分配内存。(因为数据类型不确定根本就不知道占多大内存空间)
3> 如果想灵活指定出父类中T的类型,子类也需要变为类模板

template<class T>
class Base
{
    
    
public:
	T m_B;
};

//第一种,指定父类数据类型
class Son :public Base<int>{
    
    };

void test1()
{
    
    
	Son s;
	cout << typeid(s.m_B).name() << endl;
}

//第二种:不指定父类具体数据类型,但子类也要是模板
template<class T1, class T2>
class Son1 :public Base<T1>
{
    
    
public:
	T2 m_S;
};

void test2()
{
    
    
	Son1<int, char> s1;
	cout << typeid(s1.m_B).name() << endl;
	cout << typeid(s1.m_S).name() << endl;
}

int main()
{
    
    
	test1();
	test2();
	return 0;
}

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

template<class T1,class T2>
class Person
{
    
    
public:
	Person(T1 name, T2 age);
	void Show();
	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>::Show()
{
    
    
	cout << m_name << "   " << m_age << endl;
}

void test()
{
    
    
	Person<string, int> p1("张三",15);
	p1.Show();
}

int main()
{
    
    
	test();
	return 0;
}

如何记忆:先按一般类外实现去写,然后,这是类模板所以要在类名与域作用符之间加上<T1……>,最后既然有模板参数列表,那么前面肯定要加template关键字加模板参数列表。

3.8类模板分文件编写
问题:类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到。(各个文件独立编译,如果在某.cpp文件中出现了函数调用,但是在此.cpp文件并没有对应函数的实现。此时就会在函数调用出生成特定的符号,在之后的链接过程完成函数调用)
解决方案:
1>直接包含源文件(之前包含的是头文件,把头文件换成.cpp文件)
2>将声明和实现都写在同一文件中,头文件.h后缀更改为.hpp,.hpp是约定名称,不强制

3.9类模板与友元
全局函数作友元类内实现:直接在类内声明友元即可(推荐
全局函数作友元类外实现:需要提前让编译器知道全局函数的存在

//因为友元提前了,里面Person肯定要先声明,而Person又是一个类模板,所以还要加上template……
template<class T1, class T2>
class Person;
//这里之所以放在最前面,是因为如果全局函数作为友元类外实现的话,必须要让编译器提前知道,函数本质发生变化,变成函数模板
template<class T1, class T2>
void Show2(Person<T1, T2> p)
{
    
    
	cout << "类外实现——姓名为:" << p.m_Name << "\t" << "年龄为:" << p.m_Age << endl;
}

template<class T1,class T2>
class Person
{
    
    
public:
	//第一种:直接在类内实现,参数模板化实现
	friend void Show1(Person<T1,T2> p)
	{
    
    
		cout << "姓名为:" << p.m_Name << "\t" << "年龄为:" << p.m_Age << endl;
	}

	//第二种:内外实现
	friend void Show2<>(Person<T1, T2> p);	//注意:这里要加<>是为了表明是一个函数模板
	Person(T1 name, T2 age);
private:
	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;
}

void test1()
{
    
    
	Person<string, int> p1("张三", 10);
	Show1(p1);
	Show2(p1);
}
int main()
{
    
    
	test1();
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_45884870/article/details/110820862