Skip to content

Ch 6: auto 和类型推导

  • 掌握 auto 关键字的使用
  • 理解 decltype 的用法
  • 学会阅读复杂的类型声明
  • 避免 auto 的常见陷阱
  • 理解 C++17 结构化绑定

6.1 Python 的动态类型 vs C++ 的静态类型

Section titled “6.1 Python 的动态类型 vs C++ 的静态类型”

Python 中变量是对象的引用,类型与对象绑定而非变量绑定:

# Python - 类型由值决定,变量可以随时引用不同类型的对象
x = 42 # x 引用整数对象 42
x = "hello" # x 现在引用字符串对象 "hello"
x = [1, 2, 3] # x 现在引用列表对象
# 类型跟随值变化
print(type(x)) # <class 'list'>

C++ 中 auto 让编译器推断变量类型,但推断后类型就固定了:

// C++ - auto 让编译器推断类型
auto x = 42; // int
auto y = 3.14; // double
auto z = "hello"; // const char*
auto name = "Alice"; // const char[6]
// 类型推断后就不能改变
x = "hello"; // 编译错误!x 是 int,不能赋值为字符串
// 减少冗长的类型名
std::vector<int>::iterator it = vec.begin(); // 繁琐
auto it = vec.begin(); // 简洁
// 适用于长类型名
std::unordered_map<std::string, std::vector<int>>::const_iterator cit = m.cbegin();
// 简直不能直视
auto cit = m.cbegin(); // 简洁
// Lambda 表达式(类型难以书写)
auto lambda = [](int x) { return x * 2; };
// 基本类型
auto a = 42; // int
auto b = 3.14; // double
auto c = 'A'; // char
auto flag = true; // bool
auto s = "hello"; // const char*(不是 const char[6]!)
// 数组退化为指针
const char* p = "hello"; // 字符串字面量类型是 const char[6]
// 赋值给指针时,数组退化为指针
int val = 42;
int& ref = val;
const int cref = 42;
// auto 推断时,引用和 cv 限定符会被丢弃
auto r1 = ref; // int,不是 int&
auto r2 = cref; // int,不是 const int
// 用 auto& 保留引用
auto& r3 = ref; // int&
auto& r4 = cref; // const int&
// 用 auto&& 保留(右值引用)
auto&& r5 = 42; // int&&(临时量绑定到右值引用)
// 大括号初始化
auto a = {1, 2, 3}; // std::initializer_list<int>
auto b = {1, 2, 3.0}; // 编译错误!类型不一致
// 直接列表初始化(不允许窄化)
int x{42}; // OK
int y{3.14}; // 编译错误!窄化转换被拒绝
// C++17 类模板参数推导
std::vector v{1, 2, 3}; // 自动推导 std::vector<int>
std::pair p{1, "hello"}; // 自动推导 std::pair<int, const char*>

decltype 获取表达式的类型而不计算值:

int x = 42;
int& ref = x;
const int cref = 100;
// decltype 的三种结果
decltype(x) // int(变量类型)
decltype(ref) // int&(引用)
decltype(cref) // const int(带 cv 限定符)
decltype(x + 1) // int(表达式结果类型)
decltype(x++) // int(后置++返回原值)
decltype(++x) // int&(前置++返回引用)
// 用于函数返回类型
template<typename T, typename U>
auto add(T t, U u) -> decltype(t + u) {
return t + u;
}
// 用于保持引用语义
std::vector<int> vec = {1, 2, 3};
auto& front = vec.front(); // int&
// C++14: decltype(auto) 保持原本的推导规则
decltype(auto) get_ref() {
int x = 42;
return x; // 返回 int,不是 int&
}
decltype(auto) get_cref() {
int x = 42;
return (x); // 返回 int&(注意括号!)
}

结构化绑定让你像 Python 那样直接解包 tuple/pair/数组:

# Python - 直接解包
point = (3, 4)
x, y = point
// C++17 - 结构化绑定
#include <tuple>
std::pair<int, std::string> get_person() {
return {25, "Alice"};
}
auto [age, name] = get_person(); // age = 25, name = "Alice"
// age 是 int,name 是 std::string
// 用于固定大小数组
int arr[2] = {1, 2};
auto [a, b] = arr; // a = 1, b = 2
// 用于结构体
struct Point { double x, y; };
Point pt = {3.0, 4.0};
auto [px, py] = pt;
// 可以指定类型
auto [x, y] = std::pair<int, int>{1, 2};
#include <unordered_map>
#include <iostream>
int main() {
std::unordered_map<std::string, int> ages = {
{"Alice", 30},
{"Bob", 25}
};
// 遍历 map
for (const auto& [name, age] : ages) {
std::cout << name << ": " << age << std::endl;
}
return 0;
}
const int x = 42;
auto y = x; // int,const 丢失!
y = 100; // 危险:修改了本不该修改的值
// 正确做法
auto z = static_cast<const int>(x); // 显式保留 const
auto& z2 = x; // auto& 保留 const
std::vector<int> vec = {1, 2, 3};
auto it = vec.begin(); // 不是引用,是值拷贝
*it = 100; // OK,迭代器指向同一元素
// 但如果是故意拷贝,则拷贝了一份
std::vector<int> vec2 = vec; // 拷贝了整个容器
bool flag = (ptr != nullptr); // bool
// if (flag) 语义清晰
auto result = (ptr != nullptr) ? ptr : nullptr; // int*,可能不是你想的
// 可能推断出你不期望的类型
auto value = some_function(); // 完全取决于返回值类型
// ✅ 推荐:使用 auto 的场景
auto it = container.begin();
auto lambda = [](auto x) { return x * 2; };
auto [key, value] = get_pair();
auto sp = std::make_shared<int>(42);
// ✅ 推荐:类型明确时用具体类型
int counter = 0; // 类型明确,简单
double pi = 3.14159; // 字面量确定类型
std::string name = "Alice"; // 字面量确定类型
// ❌ 避免:auto 滥用
auto mystery = get_mystery_value(); // 类型不清晰
// auto 推导返回类型
auto add(int a, int b) {
return a + b; // 返回 int
}
// 注意:如果有多个 return 语句,所有返回值类型必须一致
auto process(bool flag) {
if (flag) {
return 42; // int
}
return 3.14; // double - 编译错误!类型不一致
}
// decltype(auto) 保留原本的推导规则
decltype(auto) get_value() {
int x = 42;
return x; // 返回 int,不是 int&
}
decltype(auto) get_ref() {
int x = 42;
return (x); // 返回 int&,括号让 x 成为表达式
}
// 传统写法
template<typename T, typename U>
T add(T a, U b) {
return a + b;
}
// 问题:如果 T 是引用类型,可能出问题
// C++11 后置返回类型
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
return a + b;
}
// 使用 auto 作为占位符,后置 decltype 获取真实类型
// C++17 类模板参数推导(CTAD)
std::vector<int> v1{1, 2, 3}; // 手动指定
std::vector v2{1, 2, 3}; // 自动推导为 vector<int>
// pair 和 tuple
std::pair p{1, "hello"}; // pair<int, const char*>
std::tuple t{1, 2.0, "str"}; // tuple<int, double, const char*>
// 自定义容器的推导指引
template<typename T>
class Buffer {
public:
Buffer(T* data, size_t size) : data_(data), size_(size) {}
private:
T* data_;
size_t size_;
};
// C++17 从构造函数推导
int arr[100];
Buffer buf(arr, 100); // Buffer<int>

C++ 提供 type traits 来查询类型属性:

#include <type_traits>
#include <vector>
static_assert(std::is_integral_v<int>); // int 是整数
static_assert(std::is_floating_point_v<double>); // double 是浮点
static_assert(std::is_pointer_v<int*>); // 指针类型
static_assert(std::is_reference_v<int&>); // 引用类型
static_assert(std::is_array_v<int[10]>); // 数组类型
static_assert(std::is_same_v<int, long> == false); // int 和 long 不同
  • auto 让编译器推断类型,简化代码
  • auto 推断后类型固定,类似 Python 的类型注解
  • auto 会丢弃 const 和引用,需要用 auto& 保留
  • decltype 获取表达式类型,用于模板元编程
  • decltype(auto) 精确保留原本的推导规则
  • C++17 结构化绑定 auto [a, b] 类似 Python 的元组解包
  • 类模板参数推导(CTAD)让 std::vector v{1,2,3}; 直接成立

下章预告:ch07 学习 C++ 的基本类型(bool、int、float、char)及其细节。