一、模板
1. 基本概念
模板是C++的源代码复用机制,核心是“参数化类型”——将程序模块(函数、类)中的数据类型抽象为“类型参数”,使得同一套代码可以对不同类型的数据执行相同逻辑,是C++多态的重要实现形式之一。
模板分为两类:
- 函数模板(类属函数):针对函数的参数化,实现同一逻辑对不同类型的复用;
- 类模板(类属类):针对类的参数化,实现类结构对不同类型的复用。
模板的核心流程是“实例化”:编译期根据实际传入的类型/参数,生成具体的函数或类代码(未使用的模板实例不会生成代码,减少冗余)。
2. 知识点解析
2.1 函数模板:参数化的函数复用
2.1.1 核心定义与语法
函数模板通过template <typename T>(typename可替换为class)声明类型参数,后续函数中用T表示抽象类型,编译时根据调用场景自动推导T的具体类型并实例化。
// 原始int版本排序
void sort(int A[], unsigned int num) {
for (int i = 1; i < num; i++)
for (int j = 0; j < num - i; j++) {
if (A[j] > A[j+1]) { // 依赖>运算符
int t = A[j];
A[j] = A[j+1];
A[j+1] = t;
}
}
}
// 模板化改造后
template <typename T> // 声明类型参数T
void sort(T A[], unsigned int num) {
for (int i = 1; i < num; i++)
for (int j = 0; j < num - i; j++) {
if (A[j] > A[j+1]) { // 要求T类型重载了>运算符
T t = A[j]; // 用T替代具体类型
A[j] = A[j+1];
A[j+1] = t;
}
}
}
// 调用示例
int a[100];
double b[200];
class C {
public:
bool operator>(const C& other) { /* 自定义比较逻辑 */ }
};
C c[300];
sort(a, 100); // 实例化sort<int>(int[], unsigned int)
sort(b, 200); // 实例化sort<double>(double[], unsigned int)
sort(c, 300); // 实例化sort<C>(C[], unsigned int),依赖C重载的>
代码分析:
- 模板参数
T是“类型占位符”,编译时根据实参类型自动推导; - 函数模板的实例化是隐式的(无需手动指定类型,除非有歧义);
- 模板代码依赖的操作符(如
>、=)需由T类型提供(内置类型默认支持,自定义类型需重载); - 函数模板本质是“一类重载函数”,编译后会为不同类型生成独立的函数代码。
2.1.2 函数模板的参数规则
- 支持多个类型参数:用逗号分隔,如
template <class T1, class T2> void f(T1 a, T2 b); - 支持普通参数:必须放在类型参数之后,调用时需显式指定,如:
template <class T, int size> // size是普通模板参数(编译期常量)
void f(T a) {
T temp[size]; // 用普通参数定义数组大小
}
f<int, 10>(1); // 显式指定T=int,size=10
2.1.3 函数模板与函数重载的配合
当函数模板无法直接匹配调用(如参数类型不一致)时,可通过函数重载补充。
// 函数模板
template <class T>
T max(T a, T b) {
return a > b ? a : b;
}
// 重载函数:处理int和double混合调用
double max(int a, double b) {
return a > b ? a : b;
}
// 调用场景
int x = 3, y = 5;
double m = 4.2, n = 2.8;
max(x, y); // 匹配模板,实例化max<int>
max(m, n); // 匹配模板,实例化max<double>
max(x, m); // 匹配重载函数,返回double
代码分析:
- 函数模板要求参数类型一致(
T需推导为同一类型),max(x, m)中x是int、m是double,模板无法匹配; - 重载函数明确处理混合类型,优先级高于模板(编译器优先匹配非模板函数)。
2.2 模板特化(全特化):针对特定类型的定制化实现
通用模板可能无法满足某些特殊类型的需求(如C风格字符串的比较需用strcmp而非==),此时需对模板进行“特化”——为特定类型单独定义实现。
// 通用模板:适用于大多数类型
template <typename T>
bool isEqual(T a, T b) {
return a == b; // 内置类型/重载了==的自定义类型可用
}
// 全特化:针对const char*(C风格字符串)
template <> // 特化标识,无类型参数
bool isEqual<const char*>(const char* a, const char* b) {
return std::strcmp(a, b) == 0; // 字符串比较需用strcmp
}
// 调用场景
int i = 10, j = 10;
isEqual(i, j); // 调用通用模板,返回true
const char* p1 = "hello";
const char* p2 = "hello";
isEqual(p1, p2); // 调用特化版本,返回true(比较字符串内容)
关键要点:
- 特化必须基于已存在的通用模板(不能单独定义特化);
- 特化的函数名、参数列表需与通用模板一致,仅类型参数被具体类型替换;
- 特化的优先级高于通用模板:当调用类型与特化类型匹配时,优先执行特化实现。
2.3 类模板:参数化的类结构复用
类模板将类中的成员类型、成员函数参数类型抽象为类型参数,生成适用于不同类型的类实例。
// 类模板定义:T是类型参数
template <class T>
class Stack {
private:
T buffer[100]; // 存储不同类型的数组
int top = -1;
public:
void push(T x); // 成员函数参数类型为T
T pop(); // 成员函数返回类型为T
};
// 类模板成员函数实现:需加template声明,用Stack<T>::限定
template <class T>
void Stack<T>::push(T x) {
if (top < 99) buffer[++top] = x;
}
template <class T>
T Stack<T>::pop() {
if (top >= 0) return buffer[top--];
throw std::out_of_range("Stack underflow");
}
// 实例化与使用
Stack<int> st1; // 实例化int类型的Stack
st1.push(10); // 只能push int类型
int val1 = st1.pop();// 返回int类型
Stack<double> st2; // 实例化double类型的Stack
st2.push(3.14); // 只能push double类型
double val2 = st2.pop();// 返回double类型
2.3.1 类模板的扩展特性
- 带普通参数的类模板:普通参数需为编译期常量,如指定数组大小:
template <class T, int size> // size是普通参数
class Stack {
private:
T buffer[size]; // 数组大小由模板参数指定
};
Stack<int, 100> st1; // 大小为100的int栈
Stack<double, 200> st2;// 大小为200的double栈
- 静态成员归属:类模板的静态成员属于“实例化后的类”,而非模板本身。不同类型实例的静态成员相互独立:
template <class T>
class Test {
public:
static int count;
};
template <class T>
int Test<T>::count = 0; // 静态成员初始化
Test<int>::count = 1; // 仅影响Test<int>的count
Test<double>::count = 2;// 仅影响Test<double>的count
- C++17类模板参数推导:可省略显式类型参数,编译器根据初始化值推导:
std::vector v = {1, 2, 3}; // C++17后无需写std::vector<int>
2.4 万能引用与完美转发
2.4.1 万能引用(Universal Reference)
T&&并非单纯的右值引用,而是“万能引用”——当T是模板参数时,T&&可匹配左值或右值,并通过“引用折叠”规则推导最终类型:
- 左值传入:
T推导为T&,T&&折叠为T&(左值引用); - 右值传入:
T推导为T,T&&保持为T&&(右值引用)。
2.4.2 完美转发(Perfect Forwarding)
通过std::forward<T>(arg)可将万能引用的参数“原样转发”给其他函数,确保左值保持左值属性、右值保持右值属性(避免右值被转为左值导致拷贝)。
// 重载process函数,分别处理左值和右值
void process(const std::string& s) {
std::cout << "处理左值:" << s << std::endl;
}
void process(std::string&& s) {
std::cout << "处理右值:" << s << std::endl;
}
// 完美转发函数
template <typename T>
void perfectTransfer(T&& arg) { // T&&是万能引用
process(std::forward<T>(arg)); // 原样转发
}
// 调用场景
int main() {
std::string s = "hello";
perfectTransfer(s); // s是左值,转发为左值,调用process(const string&)
perfectTransfer(std::string("world")); // 右值,转发为右值,调用process(string&&)
}
代码分析:
- 若不用
std::forward,arg在函数内始终是左值(即使传入的是右值),会导致右值被误判为左值; std::forward依赖模板参数T的推导结果,精准转发参数属性。
2.5 if constexpr:编译期条件分支
if constexpr是C++17特性,用于在编译期判断条件并删除无效分支,避免运行时开销,常用于模板中处理不同类型的分支逻辑。
#include <string>
#include <type_traits>
// 用if constexpr实现类型分支
template <typename T>
std::string autoToString(T val) {
if constexpr (std::is_same_v<T, std::string>) {
// T是string时,编译期保留该分支,删除else
return val;
} else {
// T是数字类型时,编译期保留该分支,删除if
return std::to_string(val);
}
}
// 调用场景
int main() {
std::string s = "hello";
std::cout << autoToString(s) << std::endl; // 调用return val
int num = 123;
std::cout << autoToString(num) << std::endl; // 调用return to_string(num)
}
关键区别:普通if会在运行时判断,所有分支的代码都需合法(如对string调用to_string会编译错误);而if constexpr在编译期删除无效分支,仅保留匹配类型的代码,避免编译错误。
2.6 模板元编程(Template MetaProgramming, TMP)
模板元编程是利用模板的实例化机制,在编译期执行计算的编程方式(核心是“用模板参数作为数据,用实例化作为计算”)。
// 递归模板元编程:编译期计算Fib(N)
template<int N>
class Fib {
public:
enum { value = Fib<N-1>::value + Fib<N-2>::value }; // 编译期计算
};
// 特化:终止条件(Fib(0)=1, Fib(1)=1)
template<>
class Fib<0> {
public:
enum { value = 1 };
};
template<>
class Fib<1> {
public:
enum { value = 1 };
};
// C++11 constexpr简化版本
template<int N>
constexpr int FibFunc() {
if constexpr (N <= 1) {
return 1;
} else {
return FibFunc<N-1>() + FibFunc<N-2>(); // 编译期递归
}
}
int main() {
std::cout << Fib<8>::value << std::endl; // 编译期计算,输出34
std::cout << FibFunc<8>() << std::endl; // 编译期计算,输出34
}
特点:
- 计算在编译期完成,运行时无计算开销;
- 仅支持编译期常量(模板参数必须是编译期可确定的值);
- 递归深度受编译器模板递归限制。
2.7 编译期类型推导:auto关键字
C++11引入的auto是类型占位符,编译器根据初始化值自动推导变量类型,简化代码书写,尤其适用于复杂类型(如迭代器)。
#include <map>
#include <vector>
int main() {
// 简化迭代器定义
std::map<std::string, int> scores = {{"Alice", 95}, {"Bob", 88}};
for (auto it = scores.begin(); it != scores.end(); ++it) {
// it被推导为std::map<std::string, int>::iterator
std::cout << it->first << ": " << it->second << std::endl;
}
// 范围for循环中的auto
std::vector<int> vec = {1, 2, 3, 4, 5};
for (auto& v : vec) { // auto&推导为int&,可修改元素
v *= 2;
}
for (const auto& v : vec) { // const auto&推导为const int&,只读
std::cout << v << " "; // 输出2 4 6 8 10
}
}
auto推导规则:
- 默认推导为“值类型”(拷贝),丢弃引用和顶层
const; - 保留“底层
const”(如const char*中的const); - 需显式加
&才能推导为引用类型(auto&),加const&推导为常量引用。
二、操作符重载
1. 基本概念
操作符重载是C++赋予已有操作符(如+、->、<<)新语义的机制,使得自定义类型(如类)可以像内置类型一样使用操作符,提高代码可读性和简洁性。
核心规则:
- 不改变操作符的优先级和结合性;
- 不改变操作符的操作数个数;
- 不能发明新操作符(只能重载已存在的操作符);
- 部分操作符不能重载(如
.、.*、::、?:、sizeof); - 重载函数可以是类成员函数、全局函数(常为友元,以访问类私有成员)。
2. 知识点解析
2.1 类型转换运算符:自定义类到其他类型的转换
类型转换运算符重载允许将自定义类的对象隐式或显式转换为其他类型(如double、int),语法为operator 目标类型()。
class Rational {
private:
int n; // 分子
int d; // 分母
public:
Rational(int n1, int n2) : n(n1), d(n2) {} // 构造函数
operator double() { // 类型转换运算符:Rational -> double
return (double)n / d; // 返回转换后的值
}
};
// 调用场景
Rational r(1, 2); // 1/2
double x = r; // 隐式转换:r.operator double(),x=0.5
x = x + r; // r隐式转为double,0.5+0.5=1.0
2.1.1 歧义问题与explicit关键字
若一个类重载了多个可能冲突的类型转换运算符,会导致编译歧义:
class A {
public:
operator int() { return 1; } // A->int
operator double() { return 2.0; } // A->double
};
A a;
// cout << a + 0; // 编译错误:a既可转int也可转double,歧义
cout << static_cast<double>(a) + 0; // 显式转换,避免歧义,输出2.0
解决方案:用explicit关键字修饰类型转换运算符,禁止隐式转换,仅允许显式转换(static_cast):
class A {
public:
explicit operator int() { return 1; } // 仅允许显式转换
explicit operator double() { return 2.0; }
};
A a;
// double x = a; // 编译错误:禁止隐式转换
double x = static_cast<double>(a); // 合法
2.2 ->运算符重载:智能指针核心实现
->运算符重载是智能指针的基础,其核心特性是链式调用:重载后的->需返回指针(或重载了->的对象),编译器会自动将a->f()解析为a.operator->()->f()。
class CPen { // 画笔类
private:
int m_color;
int m_width;
public:
void setColor(int c) { m_color = c; } // 设置颜色
int getWidth() { return m_width; } // 获取宽度
};
class CPanel { // 画板类
private:
CPen m_pen; // 包含CPen对象
int m_bkColor;
public:
CPen* operator->() { // 重载->,返回CPen*
return &m_pen;
}
void setBkColor(int c) { m_bkColor = c; }
};
// 调用场景
CPanel c;
c->setColor(16); // 解析为c.operator->()->setColor(16)
// 等价于 c.m_pen.setColor(16)
int width = c->getWidth(); // 解析为c.operator->()->getWidth()
关键要点:
->重载函数无参数,返回类型必须是指针(如CPen*)或重载了->的对象;- 链式调用支持多层嵌套(如智能指针返回另一个智能指针);
- 常用于封装内部对象,提供简洁的访问接口(如智能指针管理动态对象)。
2.3 new/delete运算符重载:自定义内存管理
new和delete是C++的内存分配/释放运算符,重载后可自定义内存管理策略(如内存池、统计内存使用),提高频繁内存操作的效率。
2.3.1 重载规则
new重载函数:返回void*,第一个参数必须是size_t(系统自动传入对象大小),可带额外参数;delete重载函数:返回void,第一个参数是void*(待释放的内存地址),可选第二个size_t参数(对象大小);- 重载的
new/delete是类的静态成员函数(无需显式声明static),遵循类的访问控制,可继承。
2.3.2 标准版本与扩展版本
#include <new> // 包含std::bad_alloc
// 1. 标准版本:内存不足时抛出std::bad_alloc
void* operator new(std::size_t size) throw(std::bad_alloc) {
void* p = malloc(size);
if (!p) throw std::bad_alloc(); // 内存不足抛异常
return p;
}
// 2. Placement new(定位new):在已有内存上构造对象,不分配新内存
void* operator new(std::size_t size, void* ptr) throw() {
return ptr; // 直接返回传入的内存地址
}
// 3. 不抛异常版本:内存不足返回nullptr
void* operator new(std::size_t size, const std::nothrow_t&) throw() {
return malloc(size); // 失败返回nullptr,不抛异常
}
// delete重载(对应标准版本)
void operator delete(void* p, std::size_t size) {
free(p); // 释放内存
}
2.3.3 应用场景:内存池
重载new/delete的核心价值是实现内存池——预先申请一块大内存,后续对象分配/释放都在该内存中进行,减少系统调用开销:
class MemoryPool {
private:
char* pool; // 内存池缓冲区
size_t used; // 已使用大小
public:
MemoryPool(size_t size) : used(0) {
pool = new char[size]; // 预先申请大内存
}
// 重载new:从内存池分配
void* operator new(size_t size) {
if (used + size > sizeof(pool)) throw std::bad_alloc();
void* p = pool + used;
used += size;
return p;
}
// 重载delete:无需释放(内存池统一管理)
void operator delete(void* p) {}
};
2.4 I/O运算符重载(<<和>>):自定义类的输入输出
<<(输出)和>>(输入)运算符需重载为全局函数(常声明为类的友元),因为左操作数是ostream/istream对象(非自定义类对象),无法作为类成员函数的隐式this指针。
2.4.1 基础重载实现
#include <iostream>
using namespace std;
class CPoint2D {
private:
double x, y;
public:
CPoint2D(double x_, double y_) : x(x_), y(y_) {}
// 声明友元,允许operator<<访问私有成员
friend ostream& operator<<(ostream& out, const CPoint2D& a);
};
// 重载<<:输出CPoint2D对象
ostream& operator<<(ostream& out, const CPoint2D& a) {
out << a.x << "," << a.y; // 自定义输出格式
return out; // 返回out,支持链式输出(如cout << a << b)
}
// 调用场景
CPoint2D a(1.5, 2.5);
cout << a << endl; // 输出1.5,2.5
多态输出:非成员函数虚拟化
当子类继承自父类时,直接重载<<会导致子类输出无法复用父类逻辑,需通过“虚成员函数+全局<<”实现多态输出:
class CPoint2D {
private:
double x, y;
public:
CPoint2D(double x_, double y_) : x(x_), y(y_) {}
// 虚成员函数:定义输出逻辑
virtual void display(ostream& out) const {
out << x << "," << y;
}
// 全局<<调用虚函数
friend ostream& operator<<(ostream& out, const CPoint2D& a) {
a.display(out);
return out;
}
};
class CPoint3D : public CPoint2D {
private:
double z;
public:
CPoint3D(double x_, double y_, double z_) : CPoint2D(x_, y_), z(z_) {}
// 重写虚函数:扩展输出逻辑
void display(ostream& out) const override {
CPoint2D::display(out); // 复用父类逻辑
out << "," << z; // 新增z坐标输出
}
};
// 调用场景
CPoint2D b(1, 2);
CPoint3D c(3, 4, 5);
cout << b << endl; // 输出1,2
cout << c << endl; // 输出3,4,5(多态生效)
代码分析:
- 父类
CPoint2D的display是虚函数,子类CPoint3D重写该函数; - 全局
<<运算符调用display,通过多态机制自动匹配子类实现,实现统一接口下的不同输出。
三、例题解析
题目1:函数模板实现通用数组排序
题目背景
实现一个通用排序函数,支持对int、double、自定义类等类型的数组进行升序排序,自定义类需重载>运算符。
任务要求
- 定义函数模板
sortArray,参数为数组指针和数组长度; - 测试
int数组、double数组、自定义Student类数组(按成绩排序); - 要求排序算法为冒泡排序。
解析与代码
#include <iostream>
using namespace std;
// 函数模板:通用冒泡排序(升序)
template <typename T>
void sortArray(T arr[], size_t length) {
for (size_t i = 1; i < length; ++i) { // 外层循环:控制排序轮次
for (size_t j = 0; j < length - i; ++j) { // 内层循环:相邻元素比较
if (arr[j] > arr[j+1]) { // 依赖T重载>运算符
// 交换元素(依赖T重载=运算符)
T temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
// 自定义类:Student(按成绩排序)
class Student {
private:
string name;
int score;
public:
Student(string n, int s) : name(n), score(s) {}
// 重载>:成绩高的学生"大于"成绩低的
bool operator>(const Student& other) const {
return this->score > other.score;
}
// 友元:输出Student信息
friend ostream& operator<<(ostream& out, const Student& stu) {
out << "姓名:" << stu.name << ",成绩:" << stu.score;
return out;
}
};
int main() {
// 测试1:int数组
int intArr[] = {3, 1, 4, 1, 5, 9};
size_t intLen = sizeof(intArr) / sizeof(int);
sortArray(intArr, intLen);
cout << "int数组排序结果:";
for (size_t i = 0; i < intLen; ++i) cout << intArr[i] << " ";
cout << endl;
// 测试2:double数组
double doubleArr[] = {3.14, 1.59, 2.65, 0.78};
size_t doubleLen = sizeof(doubleArr) / sizeof(double);
sortArray(doubleArr, doubleLen);
cout << "double数组排序结果:";
for (size_t i = 0; i < doubleLen; ++i) cout << doubleArr[i] << " ";
cout << endl;
// 测试3:Student数组
Student stuArr[] = {{"Alice", 95}, {"Bob", 88}, {"Charlie", 92}};
size_t stuLen = sizeof(stuArr) / sizeof(Student);
sortArray(stuArr, stuLen);
cout << "Student数组排序结果:" << endl;
for (size_t i = 0; i < stuLen; ++i) cout << stuArr[i] << endl;
return 0;
}
代码注解
- 函数模板
sortArray的T为数组元素类型,依赖T重载>和=运算符; - 自定义
Student类重载>,指定排序规则(按成绩降序对应升序排序); - 测试用例覆盖内置类型和自定义类型,验证模板的通用性。
题目2:类模板实现带大小参数的栈
题目背景
实现一个类模板FixedStack,栈的大小由模板普通参数指定(编译期确定),支持push(入栈)、pop(出栈)、isEmpty(判空)、isFull(判满)操作。
任务要求
- 类模板参数:类型参数
T(栈元素类型)、普通参数size(栈最大容量); - 成员函数:
push(入栈,满则抛出异常)、pop(出栈,空则抛出异常)、isEmpty、isFull; - 测试
int类型栈(容量5)和string类型栈(容量3)。
解析与代码
#include <iostream>
#include <string>
#include <stdexcept> // 包含异常类
using namespace std;
// 类模板:固定大小的栈
template <typename T, size_t size>
class FixedStack {
private:
T buffer[size]; // 栈缓冲区(大小由模板参数指定)
size_t top; // 栈顶指针(-1表示空栈)
public:
FixedStack() : top(0) {} // 构造函数:初始化空栈
bool isEmpty() const { return top == 0; } // 栈空判断
bool isFull() const { return top == size; } // 栈满判断
// 入栈:满则抛出overflow异常
void push(const T& val) {
if (isFull()) {
throw overflow_error("Stack overflow");
}
buffer[top++] = val;
}
// 出栈:空则抛出underflow异常
T pop() {
if (isEmpty()) {
throw underflow_error("Stack underflow");
}
return buffer[--top];
}
};
int main() {
try {
// 测试1:int栈(容量5)
FixedStack<int, 5> intStack;
intStack.push(10);
intStack.push(20);
intStack.push(30);
cout << "int栈出栈:" << intStack.pop() << endl; // 30
cout << "int栈出栈:" << intStack.pop() << endl; // 20
// 测试2:string栈(容量3)
FixedStack<string, 3> strStack;
strStack.push("hello");
strStack.push("world");
strStack.push("cpp");
// strStack.push("test"); // 会抛出overflow_error
cout << "string栈出栈:" << strStack.pop() << endl; // cpp
cout << "string栈出栈:" << strStack.pop() << endl; // world
} catch (const exception& e) {
cout << "异常:" << e.what() << endl;
}
return 0;
}
代码注解
- 普通模板参数
size是编译期常量,用于定义栈缓冲区大小; - 入栈/出栈时的异常处理确保程序健壮性;
- 模板实例化时需显式指定类型参数和普通参数(如
FixedStack<int, 5>)。
题目3:模板特化实现字符串长度比较
题目背景
实现一个通用模板compareLength,比较两个对象的“长度”:
- 对字符串(
const char*、std::string)返回字符个数; - 对数组返回元素个数;
- 对数值类型(
int、double)返回其绝对值的位数。
任务要求
- 定义通用模板
compareLength,返回两个对象长度的较大值; - 对
const char*、std::string、数组、数值类型分别实现特化或重载; - 测试不同类型的比较场景。
解析与代码
#include <iostream>
#include <string>
#include <cstring>
#include <cmath>
using namespace std;
// 辅助函数:计算数值类型的位数
template <typename T>
size_t getNumberLength(T num) {
if (num == 0) return 1;
num = abs(num);
size_t len = 0;
while (num > 0) {
num /= 10;
len++;
}
return len;
}
// 通用模板:默认比较数值类型的位数
template <typename T>
size_t compareLength(const T& a, const T& b) {
size_t lenA = getNumberLength(a);
size_t lenB = getNumberLength(b);
return lenA > lenB ? lenA : lenB;
}
// 特化1:const char*(C风格字符串)
template <>
size_t compareLength<const char*>(const char* const& a, const char* const& b) {
size_t lenA = strlen(a);
size_t lenB = strlen(b);
return lenA > lenB ? lenA : lenB;
}
// 特化2:std::string
template <>
size_t compareLength<string>(const string& a, const string& b) {
return a.size() > b.size() ? a.size() : b.size();
}
// 重载:数组(推导数组大小)
template <typename T, size_t N>
size_t compareLength(const T(&a)[N], const T(&b)[N]) {
// 数组长度固定为N,返回N
return N;
}
int main() {
// 测试1:C风格字符串
const char* s1 = "hello";
const char* s2 = "worldcpp";
cout << "C字符串长度较大值:" << compareLength(s1, s2) << endl; // 7
// 测试2:std::string
string str1 = "cpp";
string str2 = "template";
cout << "string长度较大值:" << compareLength(str1, str2) << endl; // 8
// 测试3:int数值
int num1 = 123;
int num2 = 4567;
cout << "int位数较大值:" << compareLength(num1, num2) << endl; // 4
// 测试4:数组
int arr1[5] = {1,2,3,4,5};
int arr2[5] = {6,7,8,9,0};
cout << "数组长度:" << compareLength(arr1, arr2) << endl; // 5
return 0;
}
代码注解
- 通用模板处理数值类型,通过
getNumberLength计算位数; - 对
const char*和std::string进行全特化,用strlen和size()计算长度; - 数组通过函数重载处理,利用模板参数推导数组大小
N; - 特化和重载的优先级高于通用模板,确保不同类型匹配正确实现。
题目4:完美转发实现通用函数调用器
题目背景
实现一个通用函数调用器callFunc,可以接收任意函数和参数,通过完美转发将参数传递给目标函数,支持左值和右值参数。
任务要求
- 定义模板函数
callFunc,第一个参数为函数指针,后续参数为万能引用; - 用
std::forward实现参数完美转发; - 测试普通函数、带左值/右值参数的函数。
解析与代码
#include <iostream>
#include <string>
using namespace std;
// 测试函数1:无参数
void func1() {
cout << "调用无参函数func1" << endl;
}
// 测试函数2:左值参数
void func2(const string& s) {
cout << "调用左值参数函数func2:" << s << endl;
}
// 测试函数3:右值参数
void func3(string&& s) {
cout << "调用右值参数函数func3:" << s << endl;
}
// 测试函数4:多参数
int func4(int a, double b) {
cout << "调用多参数函数func4:a=" << a << ", b=" << b << endl;
return a + static_cast<int>(b);
}
// 通用函数调用器:完美转发参数
template <typename Func, typename... Args> // Args是参数包
auto callFunc(Func&& func, Args&&... args) -> decltype(func(std::forward<Args>(args)...)) {
// 完美转发所有参数给func
return func(std::forward<Args>(args)...);
}
int main() {
// 测试1:无参数函数
callFunc(func1);
// 测试2:左值参数
string s = "左值字符串";
callFunc(func2, s);
// 测试3:右值参数
callFunc(func3, string("右值字符串")); // 右值转发
callFunc(func3, std::move(s)); // 左值转为右值转发
// 测试4:多参数函数
int result = callFunc(func4, 10, 3.14);
cout << "func4返回值:" << result << endl; // 13
return 0;
}
代码注解
typename... Args是参数包,用于接收任意个数的参数;std::forward<Args>(args)...是参数包展开,将所有参数完美转发;- 返回类型
decltype(func(...))通过尾置返回类型推导目标函数的返回值; - 支持无参、单参、多参函数,左值和右值参数均能正确转发。
题目5:if constexpr实现类型安全的类型转换
题目背景
实现一个模板函数safeCast,将一种类型安全转换为另一种类型:
- 数值类型之间可直接转换(如
int→double); std::string可转换为const char*;- 其他类型转换(如
string→int)抛出异常。
任务要求
- 用
std::is_arithmetic判断数值类型,std::is_same判断字符串类型; - 用
if constexpr在编译期分支处理不同转换逻辑; - 转换失败时抛出
invalid_argument异常。
解析与代码
#include <iostream>
#include <string>
#include <type_traits>
#include <stdexcept>
using namespace std;
// 模板函数:安全类型转换
template <typename Target, typename Source>
Target safeCast(Source&& src) {
// 编译期分支1:数值类型之间转换
if constexpr (is_arithmetic_v<Target> && is_arithmetic_v<Source>) {
return static_cast<Target>(src);
}
// 编译期分支2:Source是string,Target是const char*
else if constexpr (is_same_v<Target, const char*> && is_same_v<remove_cvref_t<Source>, string>) {
return src.c_str();
}
// 编译期分支3:不支持的转换
else {
throw invalid_argument("不支持的类型转换");
}
}
int main() {
try {
// 测试1:int→double(数值转换)
int a = 10;
double b = safeCast<double>(a);
cout << "int→double:" << b << endl; // 10.0
// 测试2:string→const char*(字符串转换)
string s = "hello";
const char* p = safeCast<const char*>(s);
cout << "string→const char*:" << p << endl; // hello
// 测试3:不支持的转换(string→int)
// int c = safeCast<int>(s); // 抛出异常
} catch (const exception& e) {
cout << "转换失败:" << e.what() << endl;
}
return 0;
}
代码注解
is_arithmetic_v<T>判断T是否为算术类型(int、double等);remove_cvref_t<Source>移除Source的const、volatile和引用,确保匹配string;if constexpr在编译期删除无效分支,如数值转换时不会编译字符串转换的代码;- 不支持的转换在编译期确定后,运行时抛出异常。
题目6:类型转换运算符重载实现分数类
题目背景
实现一个分数类Fraction,支持分子和分母的存储,并重载double类型转换运算符,将分数转换为小数形式,同时用explicit避免隐式转换歧义。
任务要求
- 类成员:分子
num、分母den(确保分母不为0); - 重载
explicit operator double(),支持显式转换; - 实现构造函数(处理分母为0的情况)和
print成员函数。
解析与代码
#include <iostream>
#include <stdexcept>
using namespace std;
class Fraction {
private:
int num; // 分子
int den; // 分母
public:
// 构造函数:初始化分数,确保分母不为0
Fraction(int numerator, int denominator) {
if (denominator == 0) {
throw invalid_argument("分母不能为0");
}
// 简化分数(这里仅处理符号,可扩展为最大公约数约分)
if (denominator < 0) {
num = -numerator;
den = -denominator;
} else {
num = numerator;
den = denominator;
}
}
// 显式类型转换运算符:Fraction→double
explicit operator double() const {
return static_cast<double>(num) / den;
}
// 打印分数
void print() const {
cout << num << "/" << den << endl;
}
};
int main() {
try {
Fraction f1(1, 2); // 1/2
Fraction f2(3, 4); // 3/4
cout << "f1:";
f1.print();
cout << "f2:";
f2.print();
// 显式转换为double
double d1 = static_cast<double>(f1);
double d2 = static_cast<double>(f2);
cout << "f1→double:" << d1 << endl; // 0.5
cout << "f2→double:" << d2 << endl; // 0.75
cout << "f1 + f2 = " << d1 + d2 << endl; // 1.25
// 隐式转换(编译错误,因为operator double()是explicit)
// double d3 = f1;
} catch (const exception& e) {
cout << "错误:" << e.what() << endl;
}
return 0;
}
代码注解
- 构造函数处理分母为0的异常,确保分数合法性;
explicit operator double()禁止隐式转换,避免歧义(如分数与数值混合运算时);- 显式转换需用
static_cast<double>,转换逻辑为分子/分母的小数形式。
题目7:->运算符重载实现简易智能指针
题目背景
实现一个简易智能指针MySmartPtr,通过重载->和*运算符,管理动态分配的对象,自动释放内存(避免内存泄漏)。
任务要求
- 类模板:支持任意类型的对象管理;
- 重载
->(返回对象指针)和*(返回对象引用); - 析构函数自动
delete管理的对象; - 禁止拷贝构造和赋值(避免双重释放)。
解析与代码
#include <iostream>
using namespace std;
// 简易智能指针类模板
template <typename T>
class MySmartPtr {
private:
T* ptr; // 管理的对象指针
public:
// 构造函数:接收动态分配的对象指针
explicit MySmartPtr(T* p = nullptr) : ptr(p) {}
// 析构函数:自动释放内存
~MySmartPtr() {
delete ptr;
ptr = nullptr;
cout << "智能指针释放内存" << endl;
}
// 重载->:返回对象指针,支持ptr->func()
T* operator->() const {
if (!ptr) throw runtime_error("空指针访问");
return ptr;
}
// 重载*:返回对象引用,支持(*ptr).func()
T& operator*() const {
if (!ptr) throw runtime_error("空指针访问");
return *ptr;
}
// 禁止拷贝构造和赋值(避免双重释放)
MySmartPtr(const MySmartPtr&) = delete;
MySmartPtr& operator=(const MySmartPtr&) = delete;
};
// 测试类
class Test {
public:
void show() const {
cout << "Test类的show函数" << endl;
}
int add(int a, int b) {
return a + b;
}
};
int main() {
try {
// 创建智能指针,管理动态分配的Test对象
MySmartPtr<Test> sp(new Test());
// 重载->的使用
sp->show(); // 等价于sp.operator->()->show()
int result = sp->add(3, 5);
cout << "3 + 5 = " << result << endl; // 8
// 重载*的使用
(*sp).show(); // 等价于(*(sp.operator*())).show()
} catch (const exception& e) {
cout << "错误:" << e.what() << endl;
}
// 函数结束时,sp析构,自动释放Test对象
return 0;
}
代码注解
- 智能指针管理
T*类型指针,析构函数自动delete,避免内存泄漏; - 重载
->返回T*,支持sp->func()的简洁调用; - 重载
*返回T&,支持(*sp).func()的传统调用; - 删除拷贝构造和赋值运算符,防止多个智能指针管理同一对象导致双重释放。
题目8:new/delete重载实现内存池(单例模式)
题目背景
实现一个单例模式的内存池MemoryPool,重载new和delete运算符,使得自定义类User的对象分配/释放都通过内存池进行,减少系统调用开销。
任务要求
- 内存池为单例(仅一个实例),预先申请1MB内存;
User类重载new/delete,调用内存池的分配/释放接口;User类包含id和name成员,测试对象创建和销毁。
解析与代码
#include <iostream>
#include <cstdlib>
#include <cstring>
using namespace std;
// 单例内存池类
class MemoryPool {
private:
static MemoryPool* instance; // 单例实例
char* pool; // 内存池缓冲区
size_t used; // 已使用内存大小
const size_t POOL_SIZE = 1024 * 1024; // 1MB
// 私有构造函数:初始化内存池
MemoryPool() {
pool = static_cast<char*>(malloc(POOL_SIZE));
used = 0;
cout << "内存池初始化,大小:" << POOL_SIZE << "字节" << endl;
}
// 私有析构函数
~MemoryPool() {
free(pool);
cout << "内存池销毁" << endl;
}
public:
// 禁止拷贝和赋值
MemoryPool(const MemoryPool&) = delete;
MemoryPool& operator=(const MemoryPool&) = delete;
// 获取单例实例
static MemoryPool* getInstance() {
if (!instance) {
instance = new MemoryPool();
}
return instance;
}
// 分配内存
void* allocate(size_t size) {
// 内存对齐(简单处理:按8字节对齐)
size = (size + 7) & ~7;
if (used + size > POOL_SIZE) {
throw bad_alloc();
}
void* p = pool + used;
used += size;
cout << "内存池分配:" << size << "字节,已使用:" << used << "字节" << endl;
return p;
}
// 释放内存(简化版:仅标记,不实际回收)
void deallocate(void* p, size_t size) {
// 实际内存池需实现内存回收算法,此处简化为打印日志
cout << "内存池释放:" << size << "字节" << endl;
}
};
// 初始化单例实例
MemoryPool* MemoryPool::instance = nullptr;
// 测试类:User
class User {
private:
int id;
string name;
public:
User(int i, string n) : id(i), name(n) {
cout << "User构造:id=" << id << ", name=" << name << endl;
}
~User() {
cout << "User析构:id=" << id << endl;
}
// 重载new:调用内存池分配
void* operator new(size_t size) {
return MemoryPool::getInstance()->allocate(size);
}
// 重载delete:调用内存池释放
void operator delete(void* p, size_t size) {
MemoryPool::getInstance()->deallocate(p, size);
}
};
int main() {
try {
// 创建User对象(内存来自内存池)
User* u1 = new User(1, "Alice");
User* u2 = new User(2, "Bob");
// 销毁对象(内存释放到内存池)
delete u1;
delete u2;
} catch (const exception& e) {
cout << "错误:" << e.what() << endl;
}
return 0;
}
代码注解
- 内存池采用单例模式,确保全局唯一实例,预先申请1MB内存;
User类重载new/delete,所有对象的内存分配/释放都通过内存池;- 内存对齐处理确保内存访问效率(按8字节对齐);
- 简化的内存释放仅打印日志,实际应用需实现内存块回收和复用逻辑。
题目9:<<运算符重载实现多态日志输出
题目背景
实现一个日志系统,支持不同类型的日志(InfoLog、ErrorLog),通过重载<<运算符实现统一的日志输出接口,不同日志类型有不同的输出格式(多态)。
任务要求
- 抽象基类
Log,包含纯虚函数print; - 派生类
InfoLog(信息日志)和ErrorLog(错误日志),重写print; - 重载全局
<<运算符,调用print实现多态输出; - 测试不同日志类型的输出。
解析与代码
#include <iostream>
#include <string>
#include <ctime>
using namespace std;
// 日志基类(抽象类)
class Log {
protected:
string message;
string timestamp;
public:
Log(string msg) : message(msg) {
// 获取当前时间戳
time_t now = time(nullptr);
timestamp = ctime(&now);
timestamp.pop_back(); // 移除换行符
}
virtual ~Log() {} // 虚析构函数,确保派生类析构
// 纯虚函数:定义日志输出格式(子类重写)
virtual void print(ostream& out) const = 0;
// 全局<<运算符友元声明
friend ostream& operator<<(ostream& out, const Log& log);
};
// 全局<<运算符:调用虚函数print
ostream& operator<<(ostream& out, const Log& log) {
log.print(out);
return out;
}
// 信息日志类
class InfoLog : public Log {
public:
InfoLog(string msg) : Log(msg) {}
// 重写print:信息日志格式
void print(ostream& out) const override {
out << "[INFO][" << timestamp << "] " << message << endl;
}
};
// 错误日志类
class ErrorLog : public Log {
private:
int errorCode; // 错误码
public:
ErrorLog(string msg, int code) : Log(msg), errorCode(code) {}
// 重写print:错误日志格式(包含错误码)
void print(ostream& out) const override {
out << "[ERROR][" << timestamp << "][Code:" << errorCode << "] " << message << endl;
}
};
int main() {
// 创建不同类型的日志
Log* infoLog = new InfoLog("系统启动成功");
Log* errorLog = new ErrorLog("数据库连接失败", 500);
// 统一输出接口(多态生效)
cout << *infoLog;
cout << *errorLog;
// 释放内存
delete infoLog;
delete errorLog;
return 0;
}
代码注解
- 基类
Log的print是纯虚函数,定义了日志输出的统一接口; - 派生类
InfoLog和ErrorLog重写print,实现各自的输出格式; - 全局
<<运算符调用print,通过多态机制自动匹配子类实现; - 基类析构函数为虚函数,确保删除派生类对象时调用正确的析构函数。
题目10:模板元编程实现编译期计算阶乘
题目背景
利用模板元编程,实现编译期计算非负整数的阶乘(n! = n × (n-1) × ... × 1,0! = 1),要求通过模板参数传入n,运行时直接获取结果。
任务要求
- 定义递归模板
Factorial,用枚举常量value存储计算结果; - 特化模板处理终止条件(
n=0和n=1); - 测试
Factorial<5>::value和Factorial<10>::value。
解析与代码
#include <iostream>
using namespace std;
// 递归模板元编程:计算n!
template <unsigned int n>
class Factorial {
public:
// 编译期递归:n! = n * (n-1)!
enum { value = n * Factorial<n-1>::value };
};
// 特化:终止条件1(n=0,0! = 1)
template <>
class Factorial<0> {
public:
enum { value = 1 };
};
// 特化:终止条件2(n=1,1! = 1)
template <>
class Factorial<1> {
public:
enum { value = 1 };
};
// C++11 constexpr简化版本
template <unsigned int n>
constexpr unsigned int FactorialFunc() {
return n == 0 || n == 1 ? 1 : n * FactorialFunc<n-1>();
}
int main() {
// 模板元编程版本(编译期计算)
cout << "5! = " << Factorial<5>::value << endl; // 120
cout << "10! = " << Factorial<10>::value << endl; // 3628800
// constexpr版本(编译期计算)
cout << "5! = " << FactorialFunc<5>() << endl; // 120
cout << "10! = " << FactorialFunc<10>() << endl; // 3628800
return 0;
}
代码注解
- 模板元编程通过模板实例化实现编译期递归,
Factorial<n>::value是编译期常量; - 特化
Factorial<0>和Factorial<1>作为递归终止条件,避免无限递归; constexpr版本更简洁,C++11及以上支持,同样在编译期计算;- 运行时直接使用编译期预计算的结果,无任何计算开销。

被折叠的 条评论
为什么被折叠?



