C++笔记--模板

目录

模板

1.1 模板的概念

1.2 函数模板

        1.2.1 语法

        1.2.1.1 基本语法

        1.2.1.2 在main中的使用方法

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

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

        1.2.4 函数模板的局限

1.3 类模板

        1.3.1 语法

        1.3.2 类模板与函数模板的区别

        1.3.3 类模板对象做函数参数


模板

1.1 模板的概念

        可以用来通过稍作修改(不可直接使用,只是框架)就可以进行使用的通用性事物,称之为模板。

        生活中,就有很多模板,比如,常见的PPT模板、论文模板等等。

1.2 函数模板

        1.2.1 语法

        1.2.1.1 基本语法

        建立一个通用的函数,其返回值类型和形参类型均可不做具体设置,只用虚拟的类型来代替。

        语法:

template<typename T>    //模板创建声明
一个函数声明或定义        //此函数(紧跟在模板声明之后的函数)就是函数模板

//其中,template为声明创建模板
//typename可用class代替,表示之后的T为数据类型
//typename和class有一个巨大的差异————他们拼写不一样
//以上代码含义为,设置一个虚拟的类型T,T可由其他字母代换,通常为大写
//使用T做虚拟数据类型,为大众化的习惯

        接下来,通过一个简单的数值交换的函数来进行演示

template<typename T>     //虚拟数据类型声明
void swap(T &a, T &b)    //数值交换函数的模板(紧跟在虚拟数据类型声明之后)
{
    T temp = a;
    a = b;
    b = temp;
}
        1.2.1.2 在main中的使用方法

        1. 自动类型推导

        即,直接使用,编译器会自己推导T具体为什么类型。

        如

int main()
{
    int a = 10;
    int b = 20;
    swap(a, b);
    //此时的T为int类型
}

        注:其中,对T的推导必须是一致的才行,比如,当a为整型,b为浮点型时,编译器会报错。 

        2. 显示指定类型(一般都使用该方法)

        以上文例子为例,显示int类型的方法如下,

swap<int>(a, b);

        至于显示double等其他类型的方法,不能说和上文一摸一样吧,只能说是完全相同。

        注:调用函数模板,则虚拟数据类型(T)必须有明确的数据类型, 否则会报错,比如

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

template<class T>
void func()
{
	cout << "aaa" << endl;
}


void test_()
{
	func<int>();    //若无<int>则会报错
}

int main()
{
	test_();

	system("pause");
	return 0;
}

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

        • 普通函数 调用时可以使用隐式转换,如下

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

int main()
{
    int a = 10;
    char b = 'a';
    
    cout << add01(a, b) << endl;
}

        该代码是不会报错的,在调用 add01() 时传入的 char b 会自动根据ASCLL码值转换为整型(隐式转换),进行运算。

        • 函数模板 使用"自动类型推导"调用时,不会发生隐式类型转换,如下

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

void main()
{
	int a = 10;
	char b = 'a';

	cout << add02(a, b) << endl;    //此代码会报错
}

         • 函数模板 使用"显示指定类型"调用时,可以发生隐式类型转换,如下

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

void main()
{
	int a = 10;
	char b = 'a';

	cout << add03<int>(a, b) << endl;
}

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

        1. 当函数模板与普通函数均可调用时,优先普通函数。

        2. 函数模板可以发生重载。

        3. 可以通过空模板参数列表来强制调用函数模板。

        4. 如果函数模板可以发生更好的匹配,则会优先调用函数模板。

        注:最好写了同名的函数模板和普通函数不要同时出现

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

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

void test01()
{
    int a = 10;
    int b = 2;
    show(a, b);    //调用普通函数
}

void test02()
{
    char a = 10;
    char b = 2;
    show(a, b);    //调用函数模板,因为如果调用普通函数,需要进行隐式转换,而函数模板可以进行直接
                   //推导
}

void test01()
{
    int a = 10;
    int b = 2;
    show<>(a, b);    //调用函数模板(强制调用)
}

        1.2.4 函数模板的局限

        对于一些自定义数据类型(class类等),函数模板自身并不清楚该如何处理(例如两类相等的比较依据是什么)

        此时,就需要进行模板的具体化实现(重载),如下代码

//创建一个类,做例子
class Person
{
public:
	//构造
	Person(string name, int age)
	{
		m_Name = name;
		m_Age = age;
	}

	string m_Name;
	int m_Age;
};

//函数模板
template<class T>
bool compare(T& a, T& b)
{
	if (a == b) 
	{
		return true;
	}
	return false;
}

//函数模板Person版(具体化实现)
template<> bool compare(Person& p1, Person& p2)
{
	if (p1.m_Age == p2.m_Age && p1.m_Name == p2.m_Name)
	{
		return true;
	}
	return false;
}

//用例子测试一下
void test_()
{
	Person p1("张三", 10);
	Person p2("李四", 20);
	bool re = compare(p1, p2);
	if (re)
	{
		cout << "相等" << endl;
	}
	else
	{
		cout << "不相等" << endl;
	}
}

1.3 类模板

        实际是,将类中的属性改变为虚拟数据类型。

        1.3.1 语法

template<class NameType, class AgeType>
class Person
{
public:
    //构造
    Person(NameType name, AgeType age)
    {
        m_Name = name;
        m_Age = age;
    }
    //简单的输出函数
    void showPerson()
    {
        cout << "Name:" << m_Name << "Age:" << m_Age << endl;
    }
    //属性
    NameType m_Name;
    AgeType m_Age;
};

//测试代码
void test()
{
    Person<string, int> p1("张三", 10);
    p1.showPerson();
}

        其中的NameType和AgeType就相当于函数模板中的T。大体语法与函数模板相同。

        1.3.2 类模板与函数模板的区别

        1. 类模板只能使用“显示指定类型”,不可使用“自动类型推导”。(即,使用类模板必须指定数据类型)

        2. 类模板的参数列表中,可以有参数。

template<class NameType, class AgeType = int>    //此为模板参数列表,其中的int就是赋予的参数
class Person
{
public:
    //有参构造
    Person(NameType name, AgeType age)
    {
        m_Name = name;
        m_Age = age;
    }
    //用于测试的函数
    void showPerson()
    {
        cout << "Name:" << m_Name << "\n" << "Age:" << m_Age << endl;
    }
    //属性
    NameType m_Name;
    AgeType m_Age;
};

void test_()
{
    Person<string, int> p1("张三", 10);        //类模板需要指定参数类型才可以使用
    p1.showPerson();
}

void test_1()
{
    Person<string> p2("李四", 100);            //当模板参数列表中有赋值时,相应的那部分,
                                               //就可以省略(这相当于AgeType有默认值)
}

        拓:自定义的数据类型(类...)也可做模板参数,如下

//定义两个数据类型(名称不重要)
class Person1
{
public:
    void showPerson1()
    {
        cout << "Person1" << endl;
    }
};

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

template<class T>
class Test
{
public:
    void func1()
    {
        a.showPerson1();
    }
    void func2()
    {
        a.showPerson2();
    }
    //虚拟数据类型
    T a;
};

void test_()
{
    Test<Person1> t1;
    t1.func1();    //调用Person1的showPerson1();
    t1.func2();    //此代码会报错
}

        1.3.3 类模板对象做函数参数

        先来一个类示例

template<class T1, class T2>
class Person
{
public:
	Person(string name, int age)
	{
		m_Name = name;
		m_Age = age;
	}

	void showPerson()
	{
		cout << "Name:" << m_Name << "\n" << "Age:" << m_Age << endl;
	}

	T1 m_Name;
	T2 m_Age;
};

        1. 指定参数传入类型

//1.指定传入类型
void printPerson1(Person<string, int>& p)
{
	p.showPerson();
}

        2. 参数模板化

//2.参数模板化
template<class T1, class T2>
void printPerson2(Person<T1, T2>& p)
{
	p.showPerson();
	cout << "T1 : " << typeid(T1).name() << "\n" << "T2 : " << typeid(T2).name() << endl;
}

        拓:typeid(T).name() 可查看编译器推导出的数据类型,另外,string的原名是很长的。

        3. 整个类模板化

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

        在这三种方法中,最常用的是第一种——指定传入类型。

        1.3.4 类模板与继承

        · 父类为类模板时,子类要想继承父类,需要在声明时,指定父类中T的类型

        · 若想在子类中灵活指定父类的T的类型,可将子类也做成类模板

        接下来,来看看具体的代码实现

//父类
template<class T>
class Base
{
public:
    T m;
};

//子类,此子类的父类T只能是int类型,若想灵活改变T的类型,则需写成下文中 Son2 的形式
class Son1 :public Base<int>    //指定父类类型的方式
{

};

//子类2.0版本,可灵活指定父类T的类型
template<class T1, class T2>
class Son2 :public Base<T1>
{
public:
    
    //打印T1、T2的类型
    void printType()
    {
        cout << "T1:" << typeid(T1).name() << endl << "T2:" << typeid(T2).name() << endl;
    }
    
    T2 n;
};


void test_()
{
    Son2<int, double> s1;
    s1.printType();

}

int main()
{
    test_();

    system("pause");
    return 0;
}
        1.3.4.1 关于类模板的成员函数类外实现

        接下来将在上文的代码的基础上进行修改,对于类模板的成员函数类外实现, 只需记住,

        (1).一旦变成了类模板,那么其声明时的名称就变成了 Base<...> 而不再是 Base

        (2).在出现T1、T2 之类的虚拟函数类型时,一定伴随有 template 

        接下来,看看代码的实现

//本此代码只写出与上文代码的不同之处,未写之处,均与上文代码相同
//子类2.0版本,可灵活指定父类T的类型
template<class T1, class T2, class T3>
class Son2 :public Base<T1>
{
public:
    
    Son2(T2 name, T3 age);
    

    //打印T1、T2的类型
    void printType()
    {
        cout << "T1:" << typeid(T1).name() << endl
            << "T2:" << typeid(T2).name() << endl
            << "T3:" << typeid(T3).name() << endl;
    }
    

    T2 m_Name;
    T3 m_Age;
};

//成员函数的类外实现
template<class T1, class T2, class T3>
Son2<T1, T2, T3>::Son2(T2 name, T3 age)
{
    m_Name = name;
    m_Age = age;

    cout << "姓名:" << m_Name << endl << "年龄:" << m_Age << endl;
}

void test_()
{
    Son2<int, string, double> s1("张三", 18);
    s1.printType();

}

        细看代码,不难看出,与普通类外实现相比 ,多了template,并且,其作用域变为了类模板特有的形式,其他地方均相同。

        1.3.4.2 类模板的分文件编写(类模板分文件,认准 .hpp )

        分文件编写会出现的问题:成员函数在调用阶段才会创建,所以,在分文件编写的时候难以链接到。

        先写个示例代码

在Person.h中
#pragma once
#include<iostream>
using namespace std;
#include<string>

template<class T1, class T2>
class Person
{
public:
    //只进行函数声明
	Person(T1 name, T2 age);

	void showPerson();

	T1 m_name;
	T2 m_age;
};

在Person.cpp中
#include"Person.h"

template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
	m_name = name;
	m_age = age;
}

template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
	cout << "Name: " << m_name << "Age: " << m_ age << endl;
}

在main中
#include"Person.h"

int main()
{
    Person<string, int> p("张三", 18);
    p.showPerson();   
}

        此时,如果运行代码,调用showPerson函数,编译器就会报错

        解决方法一

        对main中的 #include"Person.h" 进行修改

        #include"Person.cpp"

        直接包含 .cpp (源文件),此法不推荐,因为不便于阅读,一般源文件都会比较繁杂。

        解决方法二(一般都用此法):

        将 .h 和 .cpp 的内容写到一起,后缀名改为 .hpp (约定俗成的名字)

        上代码

在Person.hpp中

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

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)
{
	m_name = name;
	m_age = age;
}

template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
	cout << "Name: " << m_name << endl << "Age: " << m_age << endl;
}


在main中


#include"Person.hpp"

void test01()
{
	Person<string, int> p("张三", 18);
	p.showPerson();
}

int main()
{

	test01();
	
	system("pause");
	return 0;
}

        注:.hpp 是头文件 

        一般都是用法二。

        1.3.5 类模板与友元

        1.3.5.1 全局函数类内实现

        直接类内声明友元

        1.3.5.2 全局函数类外实现

        需要让编译器提前知道它的存在,且在声明时,与普通函数做好区分

//类外实现的特殊操作,并且类外实现需要先让编译器看到,即放在上面
template<class T1, class T2>
class Person;

template<class T1, class T2>
void showPerson02(Person<T1, T2>& p)
{
	cout << "友元类外实现——Age : " << p.m_age << endl;
}


template<class T1, class T2>
class Person
{
    //虽然在类内声明,但都是全局函数
	//类内实现的类内声明
	friend void showPerson01(Person<T1, T2>& p)
	{
		cout << "友元类内实现——Name : " << p.m_name << endl;
	}
	//类外实现的类内声明,加一个<>告诉编译器,这是一个模板函数的声明
	friend void showPerson02<>(Person<T1, T2>& p);

public:
	Person(T1 name, T2 age)
	{
		m_name = name;
		m_age = age;
	}

	void showPerson()
	{
		cout << "Name: " << m_name << endl << "Age: " << m_age << endl;
	}
private:
	T1 m_name;
	T2 m_age;
};

void test01()
{
	Person<string, int> p("张三", 18);
	showPerson01(p);
	showPerson02(p);
}

int main()
{

	test01();

	system("pause");
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值