最近更新于 2024-05-05 14:18
环境
Debian 11 (arm64)
编译器 g++ 10.2.1;编译标准 C++20;参数:-std=c++20 -no-pie -Wall -Werror=return-type -Werror=non-virtual-dtor -Werror=address -Werror=sequence-point -Werror=format-security -Wextra -pedantic -Wimplicit-fallthrough -Wsequence-point -Wswitch-unreachable -Wswitch-enum -Wstringop-truncation -Wbool-compare -Wtautological-compare -Wfloat-equal -Wshadow=global -Wpointer-arith -Wpointer-compare -Wcast-align -Wcast-qual -Wwrite-strings -Wdangling-else -Wlogical-op -Wconversion -g -O0
函数模板
模板的作用,这里举一个例子来说明。假如这里我要写数据交换的函数,那么针对不同数据类型都要去写一个实现,而使用模板,则只用一个函数可以做到所有数据类型的交换。
#include <iostream>
template<typename T>
void swap(T &data1, T &data2)
{
T tmp = data1;
data1 = data2;
data2 = tmp;
}
int main()
{
int num1 = 8;
int num2 = 9;
swap(num1, num2);
std::cout << num1 << " " << num2 << std::endl;
std::string s1 = "AB";
std::string s2 = "ba";
swap(s1, s2);
std::cout << s1 << " " << s2 << std::endl;
double f1 = 2.1;
double f2 = 3.4;
swap(f1, f2);
std::cout << f1 << " " << f2 << std::endl;
char c1 = 'A';
char c2 = 'B';
swap(c1, c2);
std::cout << c1 << " " << c2 << std::endl;
}
代码示例中写的 template <typename T>
,T 就用于代表任意数据类型,可以自命名,如下图,只是一般习惯写 T
在使用模板时,T 必须是确定的类型,比如下面的例子,如果参数没有使用 T,就不能通过传入参数来推导出 T 的类型,此时就必须在调用时指定 T 的类型。
#include <iostream>
template<typename T>
void func()
{
std::cout << sizeof(T) << std::endl;
}
int main()
{
func<char>();
func<int>();
func<double>();
func<std::string>();
}
隐式转换
普通函数可以发生隐式转换,比如
函数的参数都是 double,我传入的一个是 int,另一个是 float,实际都会发生隐式转换,变为 double 再进行计算
#include <iostream>
double add(double num1, double num2)
{
return num1 + num2;
}
int main()
{
int a = 8;
float b = 2.4f;
std::cout << add(a, b) << std::endl;
}
而函数模板则不行
#include <iostream>
template<typename T>
double add(T num1, T num2)
{
return num1 + num2;
}
int main()
{
int a = 8;
float b = 2.4f;
std::cout << add(a, b) << std::endl;
}
除非调用时指定 T 的类型
优先顺序
当同时存在普通函数和函数模板匹配时,会优先调用普通函数
#include <iostream>
template<typename T>
T add(T num1, T num2)
{
std::cout << "函数模板" << std::endl;
return num1 + num2;
}
int add(int num1, int num2)
{
std::cout << "普通函数" << std::endl;
return num1 + num2;
}
int main()
{
add(8, 9);
}
如果要强制调用函数模板,则需要使用空模板参数列表
类模板
下面的例子定义了一个 Person 类型,属性有名字和年龄,变量不指定类型,由模板推导
#include <iostream>
// template <typename nameType, typename ageType> // 用 typename 或 class 都可以
template<class nameType, class ageType>
class Person
{
private:
nameType m_name;
ageType m_age;
public:
Person(nameType name, ageType age)
{
m_name = name;
m_age = age;
}
nameType getName()
{
return m_name;
}
ageType getAge()
{
return m_age;
}
};
int main()
{
Person p1("小明", 20);
std::cout << p1.getName() << " " << p1.getAge() << std::endl;
std::cout << typeid(p1.getName()).name() << std::endl;
std::cout << typeid(const char *).name() << std::endl;
std::cout << typeid(p1.getAge()).name() << std::endl;
std::cout << typeid(int).name() << std::endl;
}
PKc 就是 “const char *”
类模板对象做函数参数
#include <iostream>
template<typename nameType, typename ageType>
class Person
{
private:
nameType m_name;
ageType m_age;
public:
Person(nameType name, ageType age)
{
m_name = name;
m_age = age;
}
void setName(nameType name)
{
m_name = name;
}
void setAge(ageType age)
{
m_age = age;
}
nameType getName()
{
return m_name;
}
ageType getAge()
{
return m_age;
}
};
template<typename T1, typename T2>
void func(Person<T1, T2> &p)
{
p.setName("小明");
p.setAge(20);
}
int main()
{
Person<std::string, int> p("", -1);
func(p);
std::cout << p.getName() << " " << p.getAge() << std::endl;
std::cout << typeid(p.getName()).name() << std::endl;
std::cout << typeid(p.getAge()).name() << std::endl;
}
或者将上面的 func 函数改为如下图,让它自己推导类类型
类模板继承
这里写了一个例子,Son 类从 Base 类继承,需要指定 Base 类模板的 myType 类型,在派生类 Son 中也可以写成类模板,让它自动推导类型。在实例化 Son 类时,推导出 Son 中 myType1 的类型,再可以推出基类 Base 中 myType 的类型。
#include <iostream>
template<typename myType>
class Base
{
public:
myType m_data;
~Base()
{
std::cout << typeid(m_data).name() << std::endl;
}
};
template<typename myType1>
class Son : public Base<myType1>
{
public:
myType1 m_data1;
Son(myType1 data)
{
m_data1 = data;
}
};
int main()
{
Son s("hello");
std::cout << s.m_data1 << " " << typeid(s.m_data1).name() << std::endl;
}
类模板成员函数类外实现
在一般开发的时候,会把声明放到头文件,定义写到源文件,即声明和定义分离。不过类模板成员函数比较特殊,这是一个要注意的点,不能像平时那样,声明放在 .hpp,定义放在 .cpp,否则编译的时候会报错说找不到定义。因为类模板定义的东西在编译时不会为它分配内存,只有实例化的时候才会分配。
所以类模板的声明和定义必须写在同一个文件里,但是可以将声明和定义分离,这里就写了一个例子,在类外定义。
#include <iostream>
template<typename T1, typename T2>
class Person
{
private:
T1 m_name;
T2 m_age;
public:
Person(T1 name, T2 age);
T1 getName();
T2 getAge();
};
template<typename T1, typename T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
m_name = name;
m_age = age;
}
template<typename T1, typename T2>
T1 Person<T1, T2>::getName()
{
return m_name;
}
template<typename T1, typename T2>
T2 Person<T1, T2>::getAge()
{
return m_age;
}
int main()
{
Person p("小强", 19);
std::cout << p.getName() << " " << p.getAge() << std::endl;
}
类模板友元
第一个例子友元函数的实现是在类内的,只要加上了 friend,就代表它是一个全局函数,但是作为友元可以访问私有成员
#include <iostream>
template<typename T>
class Person
{
private:
T m_name;
friend T get(Person &p)
{
return p.m_name;
}
public:
Person(T name)
{
m_name = name;
}
};
int main()
{
Person p("小强");
std::cout << get(p) << std::endl;
}
而友元函数的实现写在类外的就要麻烦一点了
在类中声明友元函数的地方,函数名后要加上尖括号
在友元函数实现处,函数参数中的类模板要写上参数列表
#include <iostream>
template<typename T>
class Person
{
private:
T m_name;
friend T get<>(Person &p);
public:
Person(T name)
{
m_name = name;
}
};
template<typename T>
T get(Person<T> &p)
{
return p.m_name;
}
int main()
{
Person p("小强");
std::cout << get(p) << std::endl;
}
仿 vector
vector 是 C++ 中的一种容器,简单点说就是一个动态数组。这里就应用模板技术模拟几个 vector 的基础接口,以熟悉模板的使用。
MyVector.hpp
#ifndef MYVECTOR_HPP
#define MYVECTOR_HPP
#define INIT_SIZE 16 // 初始数组大小
#define EXTEND_SIZE 16 // 每次扩容大小
template<typename T>
class MyVector
{
private:
T *m_pData = nullptr; // 指向存储数据的指针
int m_capacity; // 数组容量
int m_size; // 数组实际大小
public:
/**
* @brief 默认构造函数
*/
MyVector();
/**
* @brief 拷贝构造函数 - 深拷贝实现
* @param mv 拷贝对象
*/
MyVector(const MyVector &mv);
/**
* @brief 赋值重载 - 深拷贝实现
* @param mv 拷贝对象
*/
MyVector &operator=(const MyVector &mv);
/**
* @brief 析构函数 - 释放资源
*/
~MyVector();
/**
* @brief 尾插实现
* @param data 要插入的数据
*/
void push_back(const T &data);
/**
* @brief 尾删实现
*/
void pop_back();
/**
* @brief 重载 [] 实现下标索引
* @param idx 下标
* @return 返回数据
*/
T &operator[](int idx);
/**
* @brief 获取数组的大小
* @return 数组的大小
*/
int size();
};
template<typename T>
MyVector<T>::MyVector()
{
m_pData = new T[INIT_SIZE];
m_capacity = INIT_SIZE;
m_size = 0;
}
template<typename T>
MyVector<T>::MyVector(const MyVector &mv)
{
if (m_pData != nullptr)
{
delete[] m_pData;
}
m_capacity = mv.m_capacity;
m_size = mv.m_size;
m_pData = new T[m_capacity];
for (int i = 0; i < m_size; ++i)
{
m_pData[i] = mv.m_pData[i];
}
}
template<typename T>
MyVector<T> &MyVector<T>::operator=(const MyVector &mv)
{
if (m_pData != nullptr)
{
delete[] m_pData;
}
m_capacity = mv.m_capacity;
m_size = mv.m_size;
m_pData = new T[m_capacity];
for (int i = 0; i < m_size; ++i)
{
m_pData[i] = mv.m_pData[i];
}
return *this;
}
template<typename T>
MyVector<T>::~MyVector()
{
if (m_pData != nullptr)
{
delete[] m_pData;
m_pData = nullptr;
m_capacity = 0;
m_size = 0;
}
}
template<typename T>
void MyVector<T>::push_back(const T &data)
{
if (m_size + 1 > m_capacity) // 扩容
{
T *tmp = new T[m_capacity + EXTEND_SIZE];
for (int i = 0; i < m_size; ++i)
{
tmp[i] = m_pData[i];
}
delete[] m_pData;
m_pData = tmp;
m_capacity += EXTEND_SIZE;
}
m_pData[m_size] = data;
++m_size;
}
template<typename T>
void MyVector<T>::pop_back()
{
if (m_size == 0)
{
return;
}
--m_size;
}
template<typename T>
T &MyVector<T>::operator[](int idx)
{
return m_pData[idx];
}
template<typename T>
int MyVector<T>::size()
{
return m_size;
}
#endif
testMain.cpp
#include "MyVector.hpp"
#include <iostream>
template<typename T>
void print(MyVector<T> &mv)
{
for (int i = 0; i < mv.size(); ++i)
{
std::cout << mv[i] << " ";
}
std::cout << std::endl;
}
int main()
{
MyVector<int> mv1; // 默认构造
for (int i = 0; i < 100; ++i)
{
mv1.push_back(i * 3); // 尾插
}
print(mv1);
int mv1Size = mv1.size();
for (int i = 0; i < mv1Size / 2; ++i)
{
mv1.pop_back(); // 尾删
}
print(mv1);
std::string s1 = "hello";
std::string s2 = "12345";
std::string s3 = "abcdef";
MyVector<std::string> mv2;
mv2.push_back(s1);
mv2.push_back(s2);
mv2.push_back(s3);
print(mv2);
MyVector mv3(mv2); // 拷贝构造
print(mv3);
MyVector mv4 = mv3; // 赋值重载
print(mv4);
}
右键在新标签页中打开图像即为原图