系列文章目录
文章目录
前言
- C++ 的模板是一种特性,允许程序员创建泛型代码,可以处理多种数据类型。模板可以应用于函数(称为函数模板)和类(称为类模板)
- C++ 提供两种模板机制:函数模板和类模板
一、函数模板
1.基本语法
函数模板的定义以关键字 template
开始,后面跟着模板参数列表,这个列表是在尖括号 < >
中的一个或多个模板参数。
template <typename T>
T add(T a, T b)
{
return a + b;
}
2.数组排序案例
template <class T>
void mysort(T buf[], int len)
{
for (int i = 0; i < len; i++)
{
for (int j = 0; j < len - i - 1; j++)
{
if (buf[j] > buf[j + 1])
{
T temp = buf[j];
buf[j] = buf[j + 1];
buf[j + 1] = temp;
}
}
}
}
template <class T>
void print(T buf, int len)
{
for (int i = 0; i < len; i++)
{
cout << buf[i] << " ";
}
cout << endl;
}
void test01()
{
char buf[] = "adsfe";
int len = sizeof(buf) / sizeof(buf[0]);
mysort(buf, len);
print(buf, len);
}
void test02()
{
int buf[] = {1, 3, 5, 7, 9, 2, 4, 6, 8, 0};
int len = sizeof(buf) / sizeof(buf[0]);
mysort(buf, len);
print(buf, len);
}
3.普通函数与函数模板的区别
- 泛型性:普通函数通常对特定类型的参数进行操作,而函数模板可以对多种类型的参数进行操作。函数模板是泛型的,可以用于创建处理不同数据类型的函数。
- 定义方式:普通函数的定义与我们通常看到的函数定义相同。函数模板的定义以
template
关键字开始,后面跟着模板参数列表。- 编译时机:普通函数在编译时会生成机器代码。函数模板在编译时不会生成任何代码,只有当模板被实例化(即使用特定类型调用模板)时,才会生成代码。
- 调用方式:普通函数可以直接通过函数名调用。函数模板可以通过函数名直接调用,也可以在函数名后面加上尖括号和具体的类型来调用。
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 = 20;
//myPrint(a, b); //调用普通函数
myPrint<>(a, b); //强制调用函数模板
myPrint(a, b, 100); //调用重载的函数模板
}
5.总结
- 函数模板利用关键字template
- 使用函数模板有两种方式:自动类型推导、显示指定类型
- 模板的目的是为了提高复用性,将类型参数化
二、类模板
1.基本语法
//类模板
template<class T1, class T2>
class Person
{
public:
Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
void showPerson()
{
cout << "name:" << this->m_Name << " age:" << this->m_Age << endl;
}
T1 m_Name;
T2 m_Age;
};
void test01()
{
Person<string, int>p("Tom", 20);
p.showPerson();
}
2.类模板中成员函数创建时机
在 C++ 中,类模板的成员函数并不是在类模板定义时就立即创建的,而是在我们实例化模板类并使用这些成员函数时才创建的。这种方式的好处是,编译器只会为我们实际使用的类型生成函数,不会为所有可能的类型生成函数,这可以节省编译时间和生成的代码的大小。但是,这也意味着如果我们的代码中有一些错误,只有在实际使用模板类时才会被发现。
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 << "name:" << this->m_Name << " age:" << this->m_Age << endl;
}
T1 m_Name;
T2 m_Age;
};
//1、指定传入类型
void printPerson1(Person<string, int>& p)
{
p.showPerson();
}
void test01()
{
Person<string, int>p("Tom", 20);
printPerson1(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("Jerry", 30);
printPerson2(p);
}
//3、整个类模板化
template<class T>
void printPerson3(T& p)
{
p.showPerson();
cout << "T的类型为:" << typeid(T).name() << endl;
}
void test03()
{
Person<string, int>p("Jerry", 30);
printPerson3(p);
}
4.类模板与继承
在 C++ 中,类模板可以被继承,就像普通的类一样。但是,当子类继承的父类是一个类模板时,子类在声明的时候,需要指定父类模板参数的类型。如果不指定,编译器无法给子类分配内存。
//类模板的继承
//当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型
//如果不指定,编译器无法给子类分配内存
template<class T>
class Base
{
T m;
};
//class Son :public Base //错误的
class Son :public Base<int>
{
};
void test01()
{
Son s1;
}
//如果想灵活指定出父类中T的类型,子类也需变为类模板
template<class T1, class T2>
class Son2 :public Base<T2>
{
T1 obj;
};
void test02()
{
Son2<int, char> s2;
}
5.类模板成员函数的类外实现
//类模板成员函数的类外实现
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", 20);
p.showPerson();
}
6.类模板与友元
- 全局函数类内实现-直接在类内声明友元即可
- 全局函数类外实现-需要提前让编译器知道全局函数的存在
template<class T1, class T2>
class Person;
//类外实现
template<class T1, class T2>
void printPerson2(Person<T1, T2> &p)
{
cout << "类外实现 -- 姓名: " << p.m_Name << " 年龄: " << p.m_Age << endl;
}
template<class T1, class T2>
class Person
{
//全局函数 类内实现
friend void printPerson(Person<T1, T2> &p)
{
cout << "类内实现 -- 姓名: " << p.m_Name << " 年龄: " << p.m_Age << endl;
}
//全局函数 类外实现
friend void printPerson2<>(Person<T1, T2> &p);
public:
string m_Name;
int m_Age;
};
void test01()
{
Person<string, int> p;
p.m_Name = "Tom";
p.m_Age = 18;
printPerson(p);
printPerson2(p);
}
总结
函数模板:函数模板是一种特殊的函数,可以处理不同类型的数据。你可以将数据类型作为参数传递给模板,编译器会根据传递的数据类型生成相应的函数。
类模板:类模板是一种特殊的类,可以处理不同类型的数据。你可以将数据类型作为参数传递给模板,编译器会根据传递的数据类型生成相应的类。
模板参数:模板参数可以是类型参数,也可以是非类型参数。类型参数表示的是类型,非类型参数表示的是值。
模板特化:模板特化是一种特殊的模板,它为特定的类型或值提供特殊的实现。
模板的实例化:模板的实例化是在编译时根据模板生成具体代码的过程。编译器会根据模板参数的类型或值生成相应的函数或类。
模板的调用规则:当普通函数和模板函数都可以处理某个函数调用时,编译器会优先选择普通函数。
模板与友元:类模板可以有友元函数。在定义友元函数时,需要使用
template
关键字和模板参数列表,并在函数名前使用类模板名和模板参数列表。类模板的继承:类模板可以被继承,子类在声明时需要指定父类模板参数的类型。
类模板成员函数的类外实现:类模板的成员函数可以在类外部定义,需要在函数定义前使用
template
关键字和模板参数列表,并在函数名前使用类模板名和模板参数列表。