C++高频面试题精选 - 第四部分(题目31-40)

C++高频面试题精选 - 第四部分(题目31-40)

模板与泛型编程

题目31: 函数模板和类模板的区别

解答:

函数模板和类模板都是C++泛型编程的核心工具,但它们在使用方式、实例化机制和应用场景上有显著区别。

1. 类型推导方式:

函数模板最大的特点是支持自动类型推导。编译器可以根据函数调用时传入的实参类型,自动推导出模板参数的类型,因此调用时通常不需要显式指定类型。

1
2
3
4
5
6
7
8
9
10
11
12
// 函数模板
template<typename T>
T max_value(T a, T b) {
return a > b ? a : b;
}

// 使用:自动推导
int i = max_value(1, 2); // T推导为int
double d = max_value(1.5, 2.5); // T推导为double

// 也可以显式指定
auto result = max_value<double>(1, 2); // 强制使用double

类模板则必须显式指定模板参数(C++17之前)。类模板不能从构造函数参数推导类型,因为在创建对象时需要明确知道类的完整类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 类模板
template<typename T>
class Stack {
vector<T> data;
public:
void push(const T& item) { data.push_back(item); }
T pop() { /* ... */ }
};

// 使用:必须显式指定
Stack<int> intStack; // 正确
Stack<string> strStack; // 正确
// Stack s; // 错误!无法推导

// C++17的类模板参数推导(CTAD)
std::pair p(1, 2.5); // C++17: 推导为pair<int, double>

2. 实例化时机:

函数模板是按需实例化,只有当函数被调用时,编译器才会为该特定类型生成代码。这意味着未使用的模板函数不会生成代码。

类模板的实例化更复杂。声明类模板对象时会实例化类的定义,但成员函数是延迟实例化的——只有实际调用的成员函数才会被实例化。

1
2
3
4
5
6
7
8
9
10
template<typename T>
class Container {
public:
void func1() { /* 使用T的某些操作 */ }
void func2() { /* 使用T的其他操作 */ }
};

Container<int> c; // 类定义被实例化
c.func1(); // func1被实例化
// func2未被调用,不会实例化

3. 特化能力:

类模板支持偏特化(部分特化),这是非常强大的特性,可以为一类类型提供特殊实现。

1
2
3
4
5
6
7
8
9
10
11
// 主模板
template<typename T, typename U>
class Pair { };

// 偏特化:两个类型相同
template<typename T>
class Pair<T, T> { };

// 偏特化:指针类型
template<typename T>
class Pair<T*, T*> { };

函数模板只支持全特化,不支持偏特化。但可以通过函数重载达到类似效果。

1
2
3
4
5
6
7
8
9
10
// 函数模板全特化
template<typename T>
void func(T value) { cout << "通用版本\n"; }

template<>
void func<int>(int value) { cout << "int特化版本\n"; }

// 函数模板不能偏特化,但可以重载
template<typename T>
void func(T* ptr) { cout << "指针版本\n"; } // 重载,不是偏特化

4. 成员和嵌套:

类模板可以包含各种成员:成员变量、成员函数、静态成员、嵌套类型等,形成完整的类结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
template<typename T>
class Container {
T* data; // 成员变量
size_t size;
static int count; // 静态成员

public:
using value_type = T; // 嵌套类型

class Iterator { // 嵌套类
T* ptr;
public:
// ...
};

void push(const T& item); // 成员函数
static int getCount() { return count; }
};

// 静态成员需要类外定义
template<typename T>
int Container<T>::count = 0;

函数模板就是单纯的函数,不能包含成员变量或嵌套类型。

5. 模板参数默认值:

两者都支持默认模板参数,但使用方式略有不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 函数模板
template<typename T = int>
T create() { return T(); }

auto x = create(); // 使用默认int
auto y = create<double>(); // 显式指定double

// 类模板
template<typename T = int>
class Array {
T data[10];
};

Array<> arr1; // 使用默认int
Array<double> arr2; // 显式指定double

6. 应用场景对比:

函数模板适合:

  • 算法实现(如排序、查找)
  • 工具函数(如swap、max、min)
  • 类型转换和适配
  • 回调和策略模式

类模板适合:

  • 容器类(如vector、list、map)
  • 智能指针(如unique_ptr、shared_ptr)
  • 迭代器
  • 类型萃取和元编程工具

口头解答:
“函数模板和类模板最大的区别在于类型推导。函数模板可以根据参数自动推导类型参数,所以调用时通常不需要显式指定类型;而类模板必须显式指定,比如vector,不过C++17引入了CTAD后,某些情况也能自动推导了。另一个区别是,类模板支持偏特化,可以为一类类型提供特殊实现,函数模板只能全特化,不过函数可以用重载达到类似效果。在特化方面,类模板更强大灵活。实际使用中,函数模板写起来简洁,类模板则更强大,可以封装完整的数据结构和行为。”


题目32: 什么是模板特化和偏特化?

解答:

模板特化是为特定类型提供定制实现的机制,允许我们针对某些类型优化性能或改变行为。

1. 全特化(Full Specialization):

全特化是为某个具体类型提供完全不同的实现。所有模板参数都被具体化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 主模板
template<typename T>
class TypeInfo {
public:
static void print() {
cout << "通用类型,大小: " << sizeof(T) << "\n";
}
};

// 全特化:针对int
template<>
class TypeInfo<int> {
public:
static void print() {
cout << "整数类型\n";
}
};

// 全特化:针对string
template<>
class TypeInfo<string> {
public:
static void print() {
cout << "字符串类型\n";
}
};

// 使用
TypeInfo<double>::print(); // 输出:通用类型,大小: 8
TypeInfo<int>::print(); // 输出:整数类型
TypeInfo<string>::print(); // 输出:字符串类型

2. 偏特化(Partial Specialization):

偏特化是对模板参数做部分限制,仍保留一定的泛型性。这是类模板独有的特性,函数模板不支持。

基本偏特化示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 主模板:两个类型参数
template<typename T, typename U>
class Pair {
public:
void print() { cout << "通用Pair\n"; }
};

// 偏特化1:两个类型相同
template<typename T>
class Pair<T, T> {
public:
void print() { cout << "相同类型的Pair\n"; }
};

// 偏特化2:第二个参数是int
template<typename T>
class Pair<T, int> {
public:
void print() { cout << "第二个是int的Pair\n"; }
};

// 偏特化3:两个都是指针
template<typename T, typename U>
class Pair<T*, U*> {
public:
void print() { cout << "指针Pair\n"; }
};

// 使用
Pair<double, string> p1; // 通用Pair
Pair<int, int> p2; // 相同类型的Pair
Pair<double, int> p3; // 第二个是int的Pair
Pair<int*, double*> p4; // 指针Pair

3. 常见的偏特化模式:

指针特化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template<typename T>
class SmartPtr {
T* ptr;
public:
T& operator*() { return *ptr; }
};

// 针对void*的特化
template<>
class SmartPtr<void> {
void* ptr;
public:
// 不能解引用void*
void* get() { return ptr; }
};

数组特化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template<typename T>
class Container {
T data;
public:
void process() { /* 处理单个元素 */ }
};

// 针对数组的偏特化
template<typename T, size_t N>
class Container<T[N]> {
T data[N];
public:
void process() { /* 处理数组 */ }
};

const/volatile特化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template<typename T>
struct RemoveConst {
using type = T;
};

// 针对const的偏特化
template<typename T>
struct RemoveConst<const T> {
using type = T;
};

// 使用
RemoveConst<int>::type x; // int
RemoveConst<const int>::type y; // int(移除了const)

4. 标准库中的应用:

类型萃取:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 标准库的is_pointer实现
template<typename T>
struct is_pointer {
static const bool value = false;
};

template<typename T>
struct is_pointer<T*> {
static const bool value = true;
};

// 使用
cout << is_pointer<int>::value; // false
cout << is_pointer<int*>::value; // true

迭代器萃取:

1
2
3
4
5
6
7
8
9
10
11
12
template<typename T>
struct iterator_traits {
using iterator_category = typename T::iterator_category;
using value_type = typename T::value_type;
};

// 针对指针的偏特化
template<typename T>
struct iterator_traits<T*> {
using iterator_category = random_access_iterator_tag;
using value_type = T;
};

5. 特化的匹配规则:

当有多个特化都匹配时,编译器选择最特化的版本。

1
2
3
4
5
6
7
8
9
10
11
12
template<typename T, typename U> class A {};     // 1. 主模板
template<typename T> class A<T, T> {}; // 2. 偏特化
template<typename T> class A<T, int> {}; // 3. 偏特化
template<typename T> class A<T*, T*> {}; // 4. 偏特化
template<> class A<int, int> {}; // 5. 全特化

A<double, float> a1; // 使用1(主模板)
A<int, int> a2; // 使用5(全特化优先)
A<double, int> a3; // 使用3
// A<int*, int*> a4; // 歧义!2和4都匹配

// 匹配优先级:全特化 > 最匹配的偏特化 > 主模板

6. 函数模板的”特化”:

虽然函数模板不支持偏特化,但可以用重载实现类似效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 主模板
template<typename T>
void process(T value) {
cout << "通用版本\n";
}

// 全特化
template<>
void process<int>(int value) {
cout << "int特化版本\n";
}

// 重载(类似偏特化的效果)
template<typename T>
void process(T* ptr) {
cout << "指针版本\n";
}

template<typename T>
void process(const T& ref) {
cout << "const引用版本\n";
}

7. 实际应用案例:

智能指针的数组特化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template<typename T>
class unique_ptr {
T* ptr;
public:
~unique_ptr() { delete ptr; }
T& operator*() { return *ptr; }
};

// 数组特化
template<typename T>
class unique_ptr<T[]> {
T* ptr;
public:
~unique_ptr() { delete[] ptr; } // 使用delete[]
T& operator[](size_t i) { return ptr[i]; } // 提供[]运算符
// 没有operator*(数组不能解引用)
};

口头解答:
“模板特化是为特定类型提供定制化实现的机制。全特化是为某个具体类型提供完全不同的实现,比如为int类型单独写一个版本。偏特化是介于通用和全特化之间,对模板参数做一些限制但不完全确定,比如指定是指针类型但不指定具体是什么指针。需要注意的是,偏特化只能用于类模板,函数模板不支持,但可以用函数重载达到类似效果。标准库里大量使用了这个技术,比如类型萃取。匹配时,全特化优先级最高,然后是最匹配的偏特化,最后才是主模板。如果多个偏特化都匹配且无法区分,会编译错误。”


题目33: 什么是SFINAE?如何应用?

解答:

SFINAE定义:

SFINAE(Substitution Failure Is Not An Error)是C++模板元编程的核心技巧,意思是”替换失败不是错误”。当编译器在模板参数替换过程中遇到错误时,不会直接报错,而是从重载候选集中移除这个模板,继续尝试其他候选。

基本原理:

编译器在实例化模板时,会将模板参数替换到模板定义中。如果替换导致了不合法的代码(如访问不存在的成员、类型转换失败等),这个模板就被”淘汰”。如果还有其他候选,就选择它们;如果所有候选都失败,才报错。

1. 检测成员函数存在性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 检测类型是否有size()成员函数
template<typename T, typename = void>
struct has_size : std::false_type {};

template<typename T>
struct has_size<T, std::void_t<decltype(std::declval<T>().size())>>
: std::true_type {};

// 使用
static_assert(has_size<vector<int>>::value); // true
static_assert(!has_size<int>::value); // true

// 或用constexpr变量模板(C++17)
template<typename T>
inline constexpr bool has_size_v = has_size<T>::value;

2. C++11的enable_if:

enable_if是SFINAE最常用的工具,可以根据条件启用或禁用模板。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 只对整数类型启用
template<typename T>
std::enable_if_t<std::is_integral_v<T>, T>
process(T value) {
return value * 2;
}

// 只对浮点类型启用
template<typename T>
std::enable_if_t<std::is_floating_point_v<T>, T>
process(T value) {
return value * 1.5;
}

process(10); // 调用整数版本
process(3.14); // 调用浮点版本
// process("hello"); // 编译错误:没有匹配的重载

3. 不同位置的enable_if:

返回类型位置:

1
2
3
template<typename T>
std::enable_if_t<std::is_integral_v<T>, void>
func(T value) { /* ... */ }

模板参数位置(更清晰):

1
2
3
template<typename T, 
std::enable_if_t<std::is_integral_v<T>, int> = 0>
void func(T value) { /* ... */ }

函数参数位置:

1
2
3
4
template<typename T>
void func(T value, std::enable_if_t<std::is_integral_v<T>>* = nullptr) {
/* ... */
}

4. 检测类型特性:

检测是否有特定类型定义:

1
2
3
4
5
6
7
8
9
10
template<typename T, typename = void>
struct has_value_type : std::false_type {};

template<typename T>
struct has_value_type<T, std::void_t<typename T::value_type>>
: std::true_type {};

// vector有value_type,int没有
static_assert(has_value_type<vector<int>>::value);
static_assert(!has_value_type<int>::value);

检测是否可调用:

1
2
3
4
5
6
7
template<typename Func, typename... Args, typename = void>
struct is_callable : std::false_type {};

template<typename Func, typename... Args>
struct is_callable<Func, Args...,
std::void_t<decltype(std::declval<Func>()(std::declval<Args>()...))>>
: std::true_type {};

5. 实际应用案例:

根据迭代器类型优化算法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 对随机访问迭代器的优化版本
template<typename Iter>
std::enable_if_t<
std::is_same_v<typename std::iterator_traits<Iter>::iterator_category,
std::random_access_iterator_tag>,
void>
advance_impl(Iter& it, int n) {
it += n; // O(1)
}

// 对其他迭代器的通用版本
template<typename Iter>
std::enable_if_t<
!std::is_same_v<typename std::iterator_traits<Iter>::iterator_category,
std::random_access_iterator_tag>,
void>
advance_impl(Iter& it, int n) {
while (n--) ++it; // O(n)
}

根据类型选择实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 对于有reserve的容器,先reserve
template<typename Container>
std::enable_if_t<has_reserve<Container>::value, void>
prepare(Container& c, size_t n) {
c.reserve(n); // vector有reserve
}

// 对于没有reserve的容器,什么都不做
template<typename Container>
std::enable_if_t<!has_reserve<Container>::value, void>
prepare(Container& c, size_t n) {
// list等没有reserve,不做处理
}

6. C++17的改进:

if constexpr(更清晰):

1
2
3
4
5
6
7
8
9
10
11
12
13
template<typename T>
void process(T value) {
if constexpr (std::is_integral_v<T>) {
// 整数处理
return value * 2;
} else if constexpr (std::is_floating_point_v<T>) {
// 浮点处理
return value * 1.5;
} else {
// 其他类型
return value;
}
}

7. C++20 Concepts(最优雅):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template<typename T>
concept Integral = std::is_integral_v<T>;

template<typename T>
concept FloatingPoint = std::is_floating_point_v<T>;

// 使用concept约束
template<Integral T>
T process(T value) {
return value * 2;
}

template<FloatingPoint T>
T process(T value) {
return value * 1.5;
}

8. 常见陷阱:

陷阱1 - 硬错误 vs 软错误:
SFINAE只能捕获”即时上下文”中的错误,函数体内的错误不会被SFINAE处理。

1
2
3
4
5
6
7
8
9
template<typename T>
auto func(T t) -> decltype(t.foo()) { // SFINAE可以工作
return t.foo();
}

template<typename T>
void func(T t) {
t.foo(); // 如果T没有foo(),这是硬错误,不会SFINAE
}

陷阱2 - 过度复杂:
SFINAE代码往往难以阅读和维护,应该优先考虑更简单的方案。

口头解答:
“SFINAE是C++模板元编程的核心技巧,原理是模板参数替换失败时不报错,而是忽略这个候选。最常见的应用是enable_if,可以根据类型特性来启用或禁用某些模板实例。比如我们可以为整数和浮点数写两个不同的process函数,编译器会根据参数类型自动选择。SFINAE还能检测类型是否有某个成员函数或类型定义,这在写通用代码时非常有用。不过SFINAE语法比较复杂晦涩,C++17的if constexpr和C++20的concepts提供了更清晰的替代方案。现代C++中,除非要兼容老标准,否则优先考虑这些新特性。”


题目34: 什么是可变参数模板?如何使用?

解答:

可变参数模板(Variadic Templates)允许模板接受任意数量和类型的参数,是C++11引入的强大特性。

1. 基本语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 类型参数包
template<typename... Args>
void func(Args... args) {
// Args是类型参数包
// args是函数参数包
}

// 获取参数个数
template<typename... Args>
void func(Args... args) {
cout << sizeof...(Args) << "\n"; // 类型数量
cout << sizeof...(args) << "\n"; // 参数数量(相同)
}

func(1, 2.5, "hello"); // 输出:3

2. 递归展开(C++11/14):

经典的处理参数包的方式是通过递归。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 递归终止条件
void print() {
cout << "\n";
}

// 递归展开
template<typename T, typename... Args>
void print(T first, Args... rest) {
cout << first << " ";
print(rest...); // 递归处理剩余参数
}

print(1, 2.5, "hello", 'c');
// 输出:1 2.5 hello c

3. C++17折叠表达式(更简洁):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 求和
template<typename... Args>
auto sum(Args... args) {
return (args + ...); // 右折叠
}

cout << sum(1, 2, 3, 4, 5); // 15

// 打印所有参数
template<typename... Args>
void print(Args... args) {
((cout << args << " "), ...); // 左折叠 + 逗号运算符
cout << "\n";
}

// 逻辑运算
template<typename... Args>
bool all(Args... args) {
return (args && ...); // 所有参数都为true
}

template<typename... Args>
bool any(Args... args) {
return (args || ...); // 任一参数为true
}

4. 折叠表达式的四种形式:

1
2
3
4
5
6
7
8
9
10
11
// 一元右折叠:(E op ...)
// 展开为:E1 op (E2 op (E3 op E4))

// 一元左折叠:(... op E)
// 展开为:((E1 op E2) op E3) op E4

// 二元右折叠:(E op ... op init)
// 展开为:E1 op (E2 op (E3 op (E4 op init)))

// 二元左折叠:(init op ... op E)
// 展开为:(((init op E1) op E2) op E3) op E4

5. 完美转发:

可变参数模板常与完美转发结合,实现参数的无损传递。

1
2
3
4
5
6
7
8
9
10
11
12
13
template<typename... Args>
void wrapper(Args&&... args) {
// 完美转发所有参数
actual_function(std::forward<Args>(args)...);
}

// 应用:工厂函数
template<typename T, typename... Args>
unique_ptr<T> make_unique(Args&&... args) {
return unique_ptr<T>(new T(std::forward<Args>(args)...));
}

auto p = make_unique<Widget>(1, 2, "hello");

6. 索引展开:

使用index_sequence展开索引。

1
2
3
4
5
6
7
8
9
10
11
12
13
template<typename Tuple, size_t... Is>
void print_tuple_impl(const Tuple& t, std::index_sequence<Is...>) {
((cout << std::get<Is>(t) << " "), ...);
cout << "\n";
}

template<typename... Args>
void print_tuple(const std::tuple<Args...>& t) {
print_tuple_impl(t, std::make_index_sequence<sizeof...(Args)>{});
}

auto t = std::make_tuple(1, 2.5, "hello");
print_tuple(t); // 输出:1 2.5 hello

7. 实际应用:

可变参数的emplace:

1
2
3
4
5
6
7
8
template<typename... Args>
void emplace_back(Args&&... args) {
new (data + size) T(std::forward<Args>(args)...);
++size;
}

vector<Widget> v;
v.emplace_back(1, 2, "test"); // 直接传递构造参数

类型安全的printf:

1
2
3
4
5
template<typename... Args>
void print_formatted(const char* fmt, Args... args) {
// 可以在编译期检查参数数量和类型
printf(fmt, args...);
}

多参数的visitor:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template<typename... Funcs>
struct Visitor : Funcs... {
using Funcs::operator()...; // 继承所有operator()
};

// 推导指引(C++17)
template<typename... Funcs>
Visitor(Funcs...) -> Visitor<Funcs...>;

// 使用
auto visitor = Visitor{
[](int i) { cout << "int: " << i << "\n"; },
[](double d) { cout << "double: " << d << "\n"; },
[](const string& s) { cout << "string: " << s << "\n"; }
};

visitor(42); // int: 42
visitor(3.14); // double: 3.14
visitor("hello"); // string: hello

8. 参数包展开的技巧:

在初始化列表中展开:

1
2
3
4
5
template<typename... Args>
void call_all(Args... args) {
// 使用逗号运算符和初始化列表展开
int dummy[] = { (func(args), 0)... };
}

在基类列表中展开:

1
2
3
4
5
template<typename... Bases>
class Derived : public Bases... {
public:
Derived(Bases... bases) : Bases(bases)... {}
};

类型操作:

1
2
3
4
5
6
7
8
9
10
11
// 获取第一个类型
template<typename T, typename... Args>
struct first_type {
using type = T;
};

// 计算所有类型的大小之和
template<typename... Args>
struct total_size {
static constexpr size_t value = (sizeof(Args) + ...);
};

口头解答:
“可变参数模板是C++11引入的强大特性,允许模板接受任意数量的参数。它用省略号表示参数包,可以是类型包或值包。早期主要用递归方式展开,比如打印函数,每次处理第一个参数,然后递归处理剩余的。C++17引入了折叠表达式,让很多操作变得简洁,比如求和只需要一行。可变参数模板在标准库中应用广泛,像make_unique、emplace系列函数、tuple都是基于它实现的。它配合完美转发,可以实现零开销的参数传递。虽然语法看起来有点奇怪,但掌握后写泛型代码会非常强大灵活。”


题目35: 解释模板元编程的基本概念

解答:

模板元编程(Template Metaprogramming, TMP)是利用C++模板系统在编译期进行计算和类型操作的编程技术。

1. 编译期计算:

最经典的例子是编译期计算阶乘。

1
2
3
4
5
6
7
8
9
10
11
12
// 编译期计算阶乘
template<int N>
struct Factorial {
static constexpr int value = N * Factorial<N-1>::value;
};

template<>
struct Factorial<0> {
static constexpr int value = 1;
};

constexpr int result = Factorial<5>::value; // 编译期计算出120

现代C++用constexpr更简洁:

1
2
3
4
5
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}

constexpr int result = factorial(5); // 编译期计算

2. 类型计算:

模板元编程的强大之处在于可以进行类型级别的计算。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 条件类型选择
template<bool Condition, typename T, typename F>
struct Conditional {
using type = T;
};

template<typename T, typename F>
struct Conditional<false, T, F> {
using type = F;
};

// 使用:根据条件选择int或double
using MyType = Conditional<(sizeof(int) > 4), long, int>::type;

3. 类型萃取(Type Traits):

标准库提供了大量类型萃取工具,它们都基于模板元编程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 移除const
template<typename T>
struct remove_const {
using type = T;
};

template<typename T>
struct remove_const<const T> {
using type = T;
};

// 移除引用
template<typename T>
struct remove_reference {
using type = T;
};

template<typename T>
struct remove_reference<T&> {
using type = T;
};

template<typename T>
struct remove_reference<T&&> {
using type = T;
};

// 使用
using T1 = remove_const<const int>::type; // int
using T2 = remove_reference<int&>::type; // int

4. 类型列表操作:

可以在编译期操作类型列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 类型列表
template<typename... Types>
struct TypeList {};

// 获取类型列表的第一个类型
template<typename List>
struct Front;

template<typename Head, typename... Tail>
struct Front<TypeList<Head, Tail...>> {
using type = Head;
};

// 类型列表的长度
template<typename List>
struct Length;

template<typename... Types>
struct Length<TypeList<Types...>> {
static constexpr size_t value = sizeof...(Types);
};

5. 编译期if-else:

C++17之前通过特化实现,C++17可用if constexpr。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// C++11/14方式
template<typename T>
void process_impl(T value, std::true_type) {
// 整数处理
cout << "整数: " << value * 2 << "\n";
}

template<typename T>
void process_impl(T value, std::false_type) {
// 浮点处理
cout << "浮点: " << value * 1.5 << "\n";
}

template<typename T>
void process(T value) {
process_impl(value, std::is_integral<T>{});
}

// C++17方式(更清晰)
template<typename T>
void process(T value) {
if constexpr (std::is_integral_v<T>) {
cout << "整数: " << value * 2 << "\n";
} else if constexpr (std::is_floating_point_v<T>) {
cout << "浮点: " << value * 1.5 << "\n";
}
}

6. 实际应用:

优化算法选择:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 根据迭代器类型选择不同实现
template<typename Iter>
void sort_impl(Iter begin, Iter end, std::random_access_iterator_tag) {
// 随机访问:用快速排序
quicksort(begin, end);
}

template<typename Iter>
void sort_impl(Iter begin, Iter end, std::forward_iterator_tag) {
// 前向迭代器:用归并排序
mergesort(begin, end);
}

template<typename Iter>
void sort(Iter begin, Iter end) {
sort_impl(begin, end,
typename std::iterator_traits<Iter>::iterator_category{});
}

编译期字符串处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template<char... Chars>
struct String {
static constexpr size_t length = sizeof...(Chars);
static constexpr char data[] = {Chars..., '\0'};
};

// C++14字面量操作符
template<typename T, T... chars>
constexpr auto operator""_s() {
return String<chars...>{};
}

auto s = "Hello"_s; // 编译期字符串
static_assert(s.length == 5);

7. 优缺点:

优点:

  • 零运行时开销:所有计算在编译期完成
  • 类型安全:编译期检查类型错误
  • 代码生成:可以生成高度优化的代码
  • 常量表达式:结果可用于数组大小等编译期常量

缺点:

  • 增加编译时间:复杂的元编程会显著增加编译时间
  • 代码难读:模板元编程代码通常晦涩难懂
  • 错误信息复杂:编译错误信息冗长且难以理解
  • 调试困难:无法用传统调试器调试编译期代码

8. 现代替代方案:

constexpr函数(C++11起):

1
2
3
4
5
6
7
8
9
10
// 比模板元编程更清晰
constexpr int power(int base, int exp) {
int result = 1;
for (int i = 0; i < exp; ++i) {
result *= base;
}
return result;
}

constexpr int val = power(2, 10); // 编译期计算

if constexpr(C++17):

1
2
3
4
5
6
7
8
9
// 替代复杂的SFINAE和特化
template<typename T>
auto process(T value) {
if constexpr (std::is_pointer_v<T>) {
return *value;
} else {
return value;
}
}

Concepts(C++20):

1
2
3
4
5
6
7
8
// 替代SFINAE,更清晰
template<typename T>
concept Integral = std::is_integral_v<T>;

template<Integral T>
T double_value(T value) {
return value * 2;
}

口头解答:
“模板元编程是利用C++模板系统在编译期进行计算和类型操作的技术。经典例子是编译期计算阶乘,通过模板递归和特化实现。它的强大之处在于可以做类型计算,比如根据条件选择不同类型,移除const、引用等修饰符。标准库的type_traits就是模板元编程的典型应用。不过现在有了constexpr和if constexpr,很多编译期计算可以用更直观的方式实现。模板元编程的优势是零运行时开销、类型安全,但代价是增加编译时间、代码难读、错误信息晦涩。实践中,除非有明确的性能或类型计算需求,否则应该优先使用更简单的方案。”


高级特性

题目36: 什么是move语义和完美转发?

解答:

移动语义和完美转发是C++11引入的重要特性,它们极大地提升了程序性能和代码表达力。

1. 移动语义(Move Semantics):

移动语义允许资源从一个对象”移动”到另一个对象,而不是拷贝。核心思想是对临时对象或不再需要的对象,直接”窃取”其资源,而不是费力地拷贝。

移动构造和移动赋值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class String {
char* data;
size_t len;
public:
// 拷贝构造:深拷贝
String(const String& s) {
len = s.len;
data = new char[len + 1];
strcpy(data, s.data);
}

// 移动构造:转移资源
String(String&& s) noexcept {
data = s.data; // 窃取资源
len = s.len;
s.data = nullptr; // 清空源对象
s.len = 0;
}

~String() { delete[] data; }
};

String s1("hello");
String s2 = std::move(s1); // 移动,不拷贝
// s1现在处于有效但未指定的状态

std::move的本质:

std::move本身不移动任何东西,它只是一个类型转换,将左值转换为右值引用,告诉编译器可以移动这个对象。

1
2
3
4
template<typename T>
typename remove_reference<T>::type&& move(T&& t) noexcept {
return static_cast<typename remove_reference<T>::type&&>(t);
}

何时发生移动:

  • 用临时对象初始化或赋值
  • 显式使用std::move
  • 函数返回局部对象(可能,取决于RVO)
  • 容器重新分配内存时

2. 右值引用(&&):

右值引用是实现移动语义的基础,用于绑定到临时对象。

1
2
3
4
5
6
7
void func(Widget& w);      // 接受左值
void func(Widget&& w); // 接受右值

Widget w;
func(w); // 调用第一个版本
func(Widget()); // 调用第二个版本
func(std::move(w)); // 调用第二个版本

3. 完美转发(Perfect Forwarding):

完美转发保持参数的值类别(左值/右值)传递给下一个函数。这在编写泛型包装函数时非常重要。

万能引用(Universal Reference):

1
2
3
4
5
6
7
8
template<typename T>
void wrapper(T&& arg) { // T&&是万能引用,不是右值引用
target(std::forward<T>(arg)); // 完美转发
}

int x = 10;
wrapper(x); // T推导为int&,arg是int&
wrapper(10); // T推导为int,arg是int&&

引用折叠规则:
这是完美转发的理论基础。

1
2
3
4
T&  && → T&
T&& && → T&&
T& & → T&
T&& & → T&

根据这个规则:

  • 传左值时,T推导为X&,参数类型是X& && = X&
  • 传右值时,T推导为X,参数类型是X&&

std::forward的作用:

std::forward根据模板参数的实际类型,有条件地转换为右值引用。

1
2
3
4
5
6
7
template<typename T>
T&& forward(typename remove_reference<T>::type& t) noexcept {
return static_cast<T&&>(t);
}

// 如果T是int&,返回int&
// 如果T是int,返回int&&

4. 实际应用:

工厂函数:

1
2
3
4
5
6
7
8
template<typename T, typename... Args>
unique_ptr<T> make_unique(Args&&... args) {
return unique_ptr<T>(new T(std::forward<Args>(args)...));
}

// 使用
auto p = make_unique<Widget>(1, 2, "hello");
// 参数被完美转发给Widget的构造函数

emplace系列:

1
2
3
4
5
6
7
8
9
10
11
template<typename... Args>
void emplace_back(Args&&... args) {
new (data + size) T(std::forward<Args>(args)...);
++size;
}

vector<Widget> v;
Widget w(1, 2);
v.emplace_back(w); // 拷贝构造
v.emplace_back(Widget(3, 4)); // 移动构造
v.emplace_back(5, 6); // 直接构造

5. 五法则(Rule of Five):

如果类管理资源,需要实现这五个函数:

1
2
3
4
5
6
7
8
class Resource {
public:
~Resource(); // 析构
Resource(const Resource&); // 拷贝构造
Resource& operator=(const Resource&); // 拷贝赋值
Resource(Resource&&) noexcept; // 移动构造
Resource& operator=(Resource&&) noexcept; // 移动赋值
};

6. 常见陷阱:

陷阱1 - move后继续使用:

1
2
3
string s = "hello";
string s2 = std::move(s);
cout << s; // 未定义行为!s处于有效但未指定的状态

陷阱2 - 返回局部变量时使用move:

1
2
3
4
5
6
7
8
9
10
Widget create() {
Widget w;
return std::move(w); // 错误!阻止RVO
}

// 正确
Widget create() {
Widget w;
return w; // 让编译器优化
}

陷阱3 - 对const对象move:

1
2
const Widget w;
Widget w2 = std::move(w); // 调用拷贝构造!move无效

口头解答:
“移动语义是C++11最重要的特性之一,它允许我们转移资源的所有权而不是拷贝。核心思想是对于临时对象或不再需要的对象,与其费力拷贝,不如直接’偷’它的资源。实现上,我们通过移动构造函数和移动赋值运算符来接管资源,然后把源对象置于安全状态。std::move只是个类型转换,把左值转成右值引用,告诉编译器可以移动这个对象了。完美转发则是保持参数的值类别——左值还是左值,右值还是右值——传递给下一个函数,主要通过万能引用和std::forward实现。这两个特性配合使用,让我们能写出高效且灵活的泛型代码,标准库的很多地方都用到了它们。”


题目37: 什么是RVO和NRVO?

解答:

RVO(Return Value Optimization)和NRVO(Named Return Value Optimization)是编译器的优化技术,用于消除函数返回对象时不必要的拷贝。

1. RVO - 返回值优化:

RVO针对返回临时对象的情况,编译器直接在调用者的栈空间上构造返回对象。

1
2
3
4
5
6
7
Widget create() {
return Widget(); // 返回临时对象
}

Widget w = create();
// 期望:构造临时Widget → 移动到w
// RVO:直接在w的位置构造,零拷贝

2. NRVO - 命名返回值优化:

NRVO针对返回命名局部变量的情况。

1
2
3
4
5
6
7
8
Widget create() {
Widget w; // 命名对象
w.init();
return w;
}

Widget obj = create();
// 可能发生NRVO:直接在obj的位置构造w

3. C++17的强制拷贝消除:

C++17对某些情况强制要求拷贝消除:

1
2
3
4
5
6
7
8
9
// 纯右值(prvalue)情况,C++17保证消除拷贝
Widget w = Widget(); // 保证
Widget w = create_widget(); // 保证(如果返回纯右值)

// 命名对象,NRVO仍是可选的
Widget create() {
Widget w;
return w; // 可选优化
}

4. 不会触发RVO/NRVO的情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 返回参数
Widget func(Widget w) {
return w; // 不会RVO,w不是局部对象
}

// 条件返回不同对象
Widget create(bool flag) {
Widget w1, w2;
return flag ? w1 : w2; // 编译器不确定返回哪个
}

// 返回全局对象
Widget global;
Widget get() {
return global; // 不会RVO
}

// 显式std::move(阻止RVO)
Widget create() {
Widget w;
return std::move(w); // 错误!阻止RVO
}

5. 验证RVO:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Widget {
public:
Widget() { cout << "构造\n"; }
Widget(const Widget&) { cout << "拷贝\n"; }
Widget(Widget&&) noexcept { cout << "移动\n"; }
};

Widget create() {
return Widget();
}

Widget w = create();
// RVO成功:只输出"构造"
// RVO失败:输出"构造" + "移动"

6. 最佳实践:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// ✓ 推荐:信任编译器
Widget create() {
Widget w;
// 初始化w
return w; // 不加std::move
}

// ✗ 不推荐:画蛇添足
Widget create() {
Widget w;
return std::move(w); // 阻止RVO,可能更慢
}

// ✓ 例外:返回参数或成员时需要move
Widget extract(Widget w) {
return std::move(w); // 必须move
}

class Container {
Widget widget;
public:
Widget take() {
return std::move(widget); // 必须move
}
};

7. 编译器行为:

不同编译器的NRVO实现程度不同:

  • GCC/Clang:积极进行NRVO
  • MSVC:相对保守

可以用编译选项禁用优化来测试:

1
g++ -fno-elide-constructors  # 禁用拷贝消除

口头解答:
“RVO和NRVO是编译器的优化技术,目的是消除不必要的拷贝。RVO针对直接返回临时对象的情况,NRVO针对返回命名局部变量。编译器会直接在调用者的位置构造对象,避免先构造再拷贝的开销。C++17对纯右值的情况强制要求RVO,NRVO仍是可选优化。很重要的一点是,不要画蛇添足地用std::move返回局部对象,这反而会阻止RVO,因为编译器看到move就不会优化了。正确做法是直接返回,让编译器自己决定是RVO、NRVO还是move。只有在返回参数、成员变量等非局部对象时才需要显式move。”


题目38: lambda表达式的原理和使用

解答:

lambda表达式是C++11引入的匿名函数对象,让函数式编程风格在C++中变得更加自然。

1. 基本语法:

1
2
3
4
5
6
7
// [捕获列表](参数列表) mutable -> 返回类型 { 函数体 }

auto f1 = []() { cout << "Hello\n"; };

auto f2 = [](int x, int y) { return x + y; };

auto f3 = [](int x) -> double { return x * 1.5; };

2. 捕获方式:

1
2
3
4
5
6
7
8
int a = 10, b = 20;

auto f1 = [a]() { return a; }; // 值捕获a
auto f2 = [&a]() { a++; }; // 引用捕获a
auto f3 = [=]() { }; // 值捕获所有
auto f4 = [&]() { }; // 引用捕获所有
auto f5 = [=, &a]() { }; // 混合:默认值,a引用
auto f6 = [a, b]() { return a+b; }; // 值捕获a和b

3. 实现原理:

编译器将lambda转换为一个匿名类,捕获的变量成为成员变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// lambda
int x = 10;
auto lambda = [x](int y) { return x + y; };

// 等价的类
class __Lambda {
int x; // 捕获的变量
public:
__Lambda(int _x) : x(_x) {}
int operator()(int y) const {
return x + y;
}
};
__Lambda lambda(x);

4. mutable关键字:

值捕获的变量默认是const的,需要mutable才能修改。

1
2
3
4
5
6
int x = 10;
auto f1 = [x]() { x++; }; // 错误!x是const

auto f2 = [x]() mutable { x++; }; // 正确
f2();
cout << x; // 仍是10,修改的是副本

5. 泛型lambda(C++14):

参数可以使用auto,实现类似模板的效果。

1
2
3
4
5
auto f = [](auto x, auto y) { return x + y; };

cout << f(1, 2); // int
cout << f(1.5, 2.5); // double
cout << f(string("a"), string("b")); // string

6. 初始化捕获(C++14):

在捕获列表中初始化新变量。

1
2
3
4
5
6
7
8
9
10
auto ptr = make_unique<int>(10);
auto f = [p = std::move(ptr)]() {
return *p;
};
// ptr被移动到lambda,原始ptr为nullptr

// 创建新变量
auto f2 = [value = expensive_call()]() {
return value;
};

7. 常见应用:

STL算法:

1
2
3
4
5
6
7
8
vector<int> v = {3, 1, 4, 1, 5};
sort(v.begin(), v.end(), [](int a, int b) {
return a > b;
});

auto it = find_if(v.begin(), v.end(), [](int x) {
return x > 3;
});

自定义删除器:

1
2
3
4
5
auto deleter = [](int* p) { 
cout << "删除\n";
delete p;
};
unique_ptr<int, decltype(deleter)> ptr(new int(10), deleter);

线程:

1
2
3
4
5
int data = 0;
thread t([&data]() {
data = 42;
});
t.join();

IIFE(立即调用):

1
2
3
4
auto result = []() {
// 复杂的初始化逻辑
return computed_value;
}();

8. 注意事项:

悬空引用:

1
2
3
4
5
6
7
8
9
10
function<int()> create() {
int x = 10;
return [&x]() { return x; }; // 危险!x会被销毁
}

// 正确:值捕获
function<int()> create() {
int x = 10;
return [x]() { return x; };
}

this指针捕获:

1
2
3
4
5
6
7
8
class Widget {
int value = 10;
public:
auto getCallback() {
return [this]() { return value; }; // 捕获this
// C++17: [*this] 拷贝整个对象
}
};

口头解答:
“lambda是C++11引入的匿名函数对象,让我们能更方便地写函数式代码。它的语法是方括号捕获列表,圆括号参数列表,花括号函数体。实现原理是编译器生成一个类,捕获的变量成为成员,函数体变成operator()。捕获方式有值捕获和引用捕获,默认值捕获是const的,要修改需要加mutable。C++14增加了泛型lambda和初始化捕获,让它更强大。lambda在STL算法、线程创建、回调函数等场景非常有用。要注意的是引用捕获的生命周期问题,特别是捕获this或局部变量的引用时,要确保lambda使用时对象还存在。”


题目39: 什么是RAII原则在现代C++中的应用?

解答:

RAII(Resource Acquisition Is Initialization)是C++最重要的编程思想之一,将资源的获取和释放绑定到对象的生命周期。

核心原理:

  • 构造函数获取资源
  • 析构函数释放资源
  • 利用C++自动调用析构函数的特性
  • 即使发生异常,析构函数也会被调用

1. 标准库中的RAII:

智能指针:

1
2
3
4
5
6
7
8
void function() {
unique_ptr<Widget> ptr(new Widget);
// 使用ptr
if (error) {
return; // 自动释放,无需delete
}
// 正常结束也自动释放
}

锁管理:

1
2
3
4
5
6
7
8
mutex mtx;
void critical_section() {
lock_guard<mutex> lock(mtx); // 构造时加锁
// 临界区代码
if (condition) {
return; // 自动解锁
}
} // 析构时解锁

文件管理:

1
2
3
4
5
6
void process() {
ofstream file("data.txt"); // 构造时打开
file << "data";
// 可能抛异常
riskyOperation();
} // 析构时自动关闭

2. 自定义RAII类:

数据库连接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class DBConnection {
Connection* conn;
public:
DBConnection(const string& connStr) {
conn = connect(connStr);
if (!conn) throw runtime_error("连接失败");
}

~DBConnection() {
if (conn) {
conn->close();
delete conn;
}
}

DBConnection(const DBConnection&) = delete;
DBConnection& operator=(const DBConnection&) = delete;

void execute(const string& sql) {
conn->executeSQL(sql);
}
};

计时器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Timer {
chrono::time_point<chrono::high_resolution_clock> start;
string name;
public:
Timer(string n) : name(std::move(n)),
start(chrono::high_resolution_clock::now()) {}

~Timer() {
auto end = chrono::high_resolution_clock::now();
auto duration = chrono::duration_cast<chrono::milliseconds>(end - start);
cout << name << ": " << duration.count() << "ms\n";
}
};

void expensive_operation() {
Timer t("操作");
// 复杂计算
} // 自动输出耗时

作用域守卫:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
template<typename Func>
class ScopeGuard {
Func func;
bool dismissed = false;
public:
ScopeGuard(Func f) : func(std::move(f)) {}

~ScopeGuard() {
if (!dismissed) func();
}

void dismiss() { dismissed = true; }
};

void transaction() {
begin_transaction();
ScopeGuard guard([]{ rollback(); });

// 数据库操作
operation1();
operation2();

guard.dismiss(); // 成功,不回滚
commit();
}

3. RAII的优势:

  • 异常安全:即使抛异常,资源也会释放
  • 简化代码:不需要显式清理
  • 防止遗漏:编译器保证析构
  • 自文档化:对象作用域即资源作用域

4. 现代C++的RAII模式:

C++17的std::scoped_lock:

1
scoped_lock lock(mtx1, mtx2, mtx3);  // 同时锁多个

std::unique_lock的灵活性:

1
2
3
4
5
unique_lock<mutex> lock(mtx);
// 可以提前解锁
lock.unlock();
// 做其他事
lock.lock(); // 重新加锁

口头解答:
“RAII是C++中最重要的编程思想之一,中文叫’资源获取即初始化’。它的核心就是把资源的生命周期和对象的生命周期绑定在一起——对象构造时获取资源,析构时释放资源。因为C++保证对象离开作用域时一定会调用析构函数,所以这种方式特别安全,即使中间抛异常也不会泄漏。标准库里到处都是RAII的应用:智能指针管理内存,lock_guard管理锁,fstream管理文件。我们自己写代码时也应该遵循这个原则,把资源封装到类里,构造时获取,析构时释放。这样就不需要记得手动清理,也不用担心异常时的泄漏。可以说,RAII是C++区别于C和其他语言的一个核心优势,也是现代C++推荐的资源管理方式。”


题目40: C++17和C++20的新特性有哪些重要的?

解答:

C++17和C++20带来了大量新特性,让C++更现代、更易用、更高效。

C++17重要特性:

1. 结构化绑定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
map<string, int> m = {{"a", 1}, {"b", 2}};

// C++17之前
for (const auto& p : m) {
cout << p.first << ": " << p.second << "\n";
}

// C++17
for (const auto& [key, value] : m) {
cout << key << ": " << value << "\n";
}

tuple<int, string, double> t(1, "hello", 3.14);
auto [i, s, d] = t; // 解包

2. if/switch初始化语句:

1
2
3
4
5
6
7
8
if (auto it = m.find("key"); it != m.end()) {
use(it->second);
} // it作用域仅在if内

switch (auto val = getValue(); val) {
case 1: break;
case 2: break;
}

3. constexpr if:

1
2
3
4
5
6
7
8
template<typename T>
auto process(T value) {
if constexpr (std::is_integral_v<T>) {
return value * 2;
} else if constexpr (std::is_floating_point_v<T>) {
return value * 1.5;
}
}

4. std::optional:

1
2
3
4
5
6
7
8
optional<string> find_user(int id) {
if (exists(id)) return get_name(id);
return nullopt;
}

if (auto name = find_user(123)) {
cout << *name;
}

5. std::variant:

1
2
3
4
5
6
variant<int, string, double> v = 10;
v = "hello";

visit([](auto&& arg) {
cout << arg;
}, v);

6. 折叠表达式:

1
2
3
4
template<typename... Args>
auto sum(Args... args) {
return (args + ...);
}

7. std::string_view:

1
2
3
void process(string_view sv) {  // 不拷贝
cout << sv;
}

C++20重要特性:

1. Concepts:

1
2
3
4
5
6
7
8
9
template<typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::same_as<T>;
};

template<Addable T>
T add(T a, T b) {
return a + b;
}

2. Ranges:

1
2
3
4
vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

auto result = v | views::filter([](int x) { return x % 2 == 0; })
| views::transform([](int x) { return x * 2; });

3. 协程:

1
2
3
4
5
6
7
8
9
generator<int> fibonacci() {
int a = 0, b = 1;
while (true) {
co_yield a;
auto next = a + b;
a = b;
b = next;
}
}

4. 三路比较(<=>):

1
2
3
4
5
6
7
struct Point {
int x, y;
auto operator<=>(const Point&) const = default;
};

Point p1{1, 2}, p2{3, 4};
if (p1 < p2) { } // 自动生成所有比较

5. 指定初始化器:

1
2
3
4
5
6
7
8
9
10
11
struct Config {
int width = 800;
int height = 600;
bool fullscreen = false;
};

Config cfg {
.width = 1920,
.height = 1080,
.fullscreen = true
};

6. std::format:

1
string s = std::format("Hello, {}! Answer is {}.", "world", 42);

7. constexpr改进:

1
2
3
4
5
6
7
constexpr auto factorial(int n) {
vector<int> v; // C++20允许
for (int i = 1; i <= n; ++i) {
v.push_back(i);
}
return accumulate(v.begin(), v.end(), 1, multiplies{});
}

口头解答:
“C++17和C++20带来了很多实用的特性。C++17的结构化绑定让代码更简洁,if-init让作用域更清晰,constexpr if简化了模板元编程,optional和variant提供了更好的错误处理和类型安全,string_view避免了不必要的拷贝。C++20更是重量级更新,Concepts终于让模板错误信息变得可读,Ranges让算法组合变得优雅,Coroutines支持异步编程,三路比较运算符减少了样板代码,std::format提供了类型安全的格式化。这些特性让C++既保持了性能,又大大提升了表达力和开发效率。不过要注意,这些新特性的编译器支持程度不一,使用前要确认目标编译器的支持情况。”


第四部分总结:

本部分涵盖了模板编程(题目31-35)和C++高级特性(题目36-40):

模板编程:

  • 函数模板vs类模板
  • 模板特化和偏特化
  • SFINAE原理和应用
  • 可变参数模板
  • 模板元编程基础

高级特性:

  • 移动语义和完美转发
  • RVO/NRVO优化
  • lambda表达式
  • RAII在现代C++中的应用
  • C++17/C++20新特性

当前进度:40/50题(80%完成)

下一部分预告:
第五部分(题目41-50)将是最后一部分,包括类型擦除、内存序、单例模式、复制省略、性能优化等高级主题,完成整个50题的面试宝典!

你想让我继续生成**最后一部分(题目41-50)**吗?