在C++开发中,我们经常遇到这样的场景:需要为不同类型实现功能完全相同的代码。比如实现一个交换函数,对于int类型需要写一个版本,对于double类型又得重写一个几乎相同的版本。这不仅造成代码冗余,更增加了维护成本。
模板的出现完美解决了这个问题。它允许我们编写与类型无关的通用代码,编译器会在使用时根据具体类型自动生成对应的代码。这种技术称为泛型编程(Generic Programming)。
函数模板的声明格式如下:
cpp复制template<typename T> // 或者 template<class T>
返回值类型 函数名(参数列表) {
// 函数体
}
以交换函数为例:
cpp复制template<typename T>
void Swap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
当调用Swap(x, y)时,编译器会根据x和y的类型自动推导出T的具体类型,这个过程称为模板实例化。
注意:typename和class在模板参数声明中可以互换,但typename更明确表达了"类型名"的含义,特别是在嵌套依赖类型名时。
C++模板的类型推导有一套复杂的规则:
当需要精确控制类型推导时,可以使用显式指定模板参数:
cpp复制Swap<double>(x, y); // 强制使用double类型实例化
模板可以接受多个类型参数:
cpp复制template<typename T1, typename T2>
auto Add(const T1& a, const T2& b) -> decltype(a + b) {
return a + b;
}
这里使用了C++11的返回类型后置语法和decltype,可以自动推导出最适合的返回类型。
类模板允许我们定义通用的数据结构。以栈为例:
cpp复制template<typename T>
class Stack {
private:
T* elements;
size_t capacity;
size_t top;
public:
Stack(size_t initSize = 10);
~Stack();
void Push(const T& element);
T Pop();
bool IsEmpty() const;
};
类模板的成员函数实现有特殊语法:
cpp复制template<typename T>
Stack<T>::Stack(size_t initSize)
: elements(new T[initSize]), capacity(initSize), top(0) {}
template<typename T>
void Stack<T>::Push(const T& element) {
if (top == capacity) {
// 扩容逻辑
}
elements[top++] = element;
}
重要提示:类模板的成员函数只有在被调用时才会实例化,这称为"惰性实例化"。
对于某些特定类型,我们可以提供特殊实现:
cpp复制template<>
class Stack<bool> {
// 针对bool类型的特化实现,可以用位压缩存储
};
C++的string类是对C风格字符串(char*)的封装和增强,主要解决了以下问题:
cpp复制string s1; // 空字符串
string s2("Hello"); // 从C字符串构造
string s3(s2); // 拷贝构造
string s4(5, 'A'); // 重复字符构造
s1 = "World"; // 赋值操作
cpp复制s.size(); // 当前字符数
s.capacity(); // 当前分配的内存大小
s.reserve(100); // 预分配内存
s.shrink_to_fit(); // 释放多余内存(C++11)
cpp复制s[0]; // 不检查边界
s.at(0); // 检查边界,越界抛出异常
s.front(); // 首字符
s.back(); // 末字符
cpp复制// 低效写法
string result = s1 + " " + s2 + " " + s3;
// 高效写法
string result;
result.reserve(s1.size() + s2.size() + s3.size() + 2);
result = s1;
result += " ";
result += s2;
result += " ";
result += s3;
cpp复制std::string_view sv(s.c_str(), 5); // 不拷贝数据
cpp复制int i = std::stoi("42");
double d = std::stod("3.14");
string s = std::to_string(123);
利用模板在编译期进行计算:
cpp复制template<unsigned n>
struct Factorial {
static const unsigned value = n * Factorial<n-1>::value;
};
template<>
struct Factorial<0> {
static const unsigned value = 1;
};
// 使用
const unsigned fact10 = Factorial<10>::value;
cpp复制template<typename StringType>
void ProcessString(const StringType& str) {
// 统一处理string和string_view
}
// 使用
ProcessString(std::string("Hello"));
ProcessString(std::string_view("World"));
在实际项目中,我发现模板和string的高效使用可以显著提升代码质量和性能。特别是在处理复杂数据结构时,模板提供的类型安全性和代码复用能力是无价的。对于string类,合理使用其接口可以避免很多常见的内存和性能问题。