C11 是现代 C 的里程碑它对类型系统进行了根本性的改进。传统 C 的类型系统存在诸多痛点繁琐的类型声明降低了代码可读性模糊的 const 语义容易引入权限放大错误模板编程中类型推导困难导致代码冗余。从 C11 开始语言引入了一系列特性来解决这些问题const 语义的明确化、auto 自动类型推导、decltype 精确类型提取、using 类型别名等彻底改变了 C 代码的书写方式。1顶层const和底层const1核心内容指针本身是一个独立的对象它又可以指向另一个对象因此涉及两个完全独立的 const 语义顶层 const修饰变量本身表示变量的值不可修改底层 const修饰指针 / 引用指向的对象表示不能通过该指针 / 引用修改对象核心记忆法对于指针*左边的 const 是底层*右边的 const 是顶层。int main() { int i 0; int* const p1 i; // 顶层constp1本身不可修改但*p1可以修改 const int ci 42; // 顶层constci本身不可修改 const int* p2 ci; // 底层const*p2不可修改但p2可以指向其他对象 const int r ci; // 引用的const永远是底层const return 0; }补充说明大多数普通对象被 const 修饰时都是顶层 const因为它们本身就是值对象没有 指向 的概念。只有指针和引用才会涉及底层 const。2const完整语义1const与函数参数传递传值参数顶层 const 会被忽略因为参数是原对象的副本修改副本不会影响原对象传指针 / 引用参数底层 const 会被保留实现 只读 语义防止函数意外修改原对象// 错误这两个函数是重复定义顶层const在传值时被忽略 void func(int x) {} void func(const int x) {} // 正确这是两个不同的函数底层const不被忽略 void func(int* p) {} // 不能接受const int*参数权限放大 void func(const int* p) {} // 可以接受int*和const int*参数权限缩小 // 引用参数同理 void func(int x) {} // 不能接受const int参数 void func(const int x) {} // 可以接受int和const int参数对于大对象优先使用const T作为函数参数既避免拷贝开销又保证对象不被修改。2const成员函数const 修饰成员函数时本质是给隐式的this指针加上了底层 const表示该函数不能修改类的任何非静态成员变量。class Person { public: Person(std::string name, int age) : _name(name), _age(age) {} // const成员函数this指针类型为const Person* std::string getName() const { // _age 20; // 错误不能修改非mutable成员变量 return _name; } // 非const成员函数this指针类型为Person* void setAge(int age) { _age age; } private: std::string _name; int _age; }; int main() { Person p1(Alice, 18); const Person p2(Bob, 20); p1.getName(); // 正确非const对象可以调用const成员函数 p1.setAge(19); // 正确 p2.getName(); // 正确const对象只能调用const成员函数 // p2.setAge(21); // 错误const对象不能调用非const成员函数 return 0; }const 成员函数重载同一个函数可以有 const 和非 const 两个版本编译器会根据对象的 const 属性自动选择合适的版本。这是 STL 容器中operator[]的标准实现方式class Array { public: int operator[](size_t index) { return _data[index]; } // const版本返回const引用防止通过const对象修改元素 const int operator[](size_t index) const { return _data[index]; } private: int _data[10]; };3mutable如果需要在 const 成员函数中修改某个成员变量比如缓存计算结果、统计调用次数可以用mutable修饰该变量表示它永远是可变的不受 const 约束。class Counter { public: Counter() : _count(0) {} void increment() const { _count; // 正确mutable成员可以在const函数中修改 } int getCount() const { return _count; } private: mutable int _count; // 可变成员 };4const与返回值返回值为const T防止返回值被修改如const int func()返回值为const T防止返回的引用被修改同时避免拷贝开销class String { public: // 返回const引用防止用户修改内部字符 const char operator[](size_t index) const { return _data[index]; } private: char* _data; };2类型处理1auto1核心内容auto 让编译器根据初始化表达式自动推导变量类型核心推导规则忽略顶层 const保留底层 const忽略引用推导为引用指向的类型不能自动推导出引用类型和顶层 const需要显式声明int main() { int i 0; int ri i; const int ci 42; // 顶层const int* const p1 i; // 顶层const const int* p2 ci; // 底层const const int ri1 ci; // 底层const const int ri2 i; // 底层const auto j ri; // j类型为int忽略引用 j; auto k i; // k类型为int k; auto r1 ci; // r1类型为int忽略顶层const r1; auto r2 p1; // r2类型为int*忽略顶层const r2; auto r3 p2; // r3类型为const int*保留底层const // (*r3); // 报错不能修改底层const指向的对象 auto r4 ri1; // r4类型为intri1是ci的别名ci的顶层const被忽略 r4; auto r5 ri2; // r5类型为int r5; // 显式声明引用或const const auto r7 ci; // r7类型为const int auto r8 ri1; // r8类型为const int auto r9 ri2; // r9类型为const int auto r10 ci; // r10类型为const int auto r11 ri; // r11类型为int // r7; // 报错const变量不可修改 // r8; // 报错const引用不可修改 r11; // 正确非const引用可以修改 return 0; }2auto与引用的三种形式auto左值引用只能绑定到左值const autoconst 左值引用可以绑定到左值、右值和 const 对象auto万能引用遵循引用折叠规则既可以绑定到左值也可以绑定到右值#includeiostream using namespace std; void func(int x) { cout void func(int x) endl; } void func(int x) { cout void func(int x) endl; } void func(const int x) { cout void func(const int x) endl; } void func(const int x) { cout void func(const int x) endl; } int main() { int x 10; const int cx 20; auto rx1 x; // int auto rx2 cx; // const int func(rx1); // 输出void func(int x) func(rx2); // 输出void func(const int x) const auto rx3 x; // const int const auto rx4 cx; // const int func(rx3); // 输出void func(const int x) func(rx4); // 输出void func(const int x) // 万能引用 auto rx5 x; // int左值推导为左值引用 auto rx6 cx; // const intconst左值推导为const左值引用 func(rx5); // 输出void func(int x) func(rx6); // 输出void func(const int x) auto rx7 move(x); // int右值推导为右值引用 auto rx8 move(cx); // const intconst右值推导为const右值引用 func(forwardint(rx7)); // 输出void func(int x) func(forwardconst int(rx8)); // 输出void func(const int x) return 0; }引用折叠规则T →TT →TT →TT →T简单来说只要有一个左值引用结果就是左值引用只有两个都是右值引用结果才是右值引用。2auto的特殊场景1数组推导int arr[5] {1,2,3,4,5}; auto a1 arr; // int*数组退化为指针 auto a2 arr; // int()[5]显式声明引用保留数组类型 // 可以用a2获取数组大小 cout sizeof(a2) / sizeof(int) endl; // 输出52lambda表达式推导lambda 表达式的类型是唯一的匿名类型只能用 auto 来声明auto lambda [](int x) { return x * 2; }; // lambda的类型是唯一的匿名类型3初始化列表推导auto 与初始化列表结合时有特殊规则auto x {1,2,3}; // std::initializer_listint auto y{1}; // C17及以后intC11/C14std::initializer_listint // auto z {1, 2.0}; // 错误元素类型不一致4范围for循环中的auto范围 for 循环是 auto 最常用的场景之一vectorint vec {1,2,3,4,5}; // 拷贝元素修改不会影响原容器 for (auto x : vec) { x * 2; } // 引用元素修改会影响原容器 for (auto x : vec) { x * 2; } // const引用只读访问避免拷贝 for (const auto x : vec) { cout x endl; }5C17的结构化绑定结构化绑定允许用 auto 同时解包多个值极大简化了 tuple、pair 和结构体的访问#include tuple #include utility std::tupleint, double, std::string getInfo() { return {1, 3.14, hello}; } int main() { // 结构化绑定解包tuple auto [id, score, name] getInfo(); // 解包pair std::pairint, std::string p {10, Alice}; auto [key, value] p; // 解包结构体 struct Point { int x; int y; }; Point pt {1, 2}; auto [x, y] pt; return 0; }6C20auto作为函数参数C20 允许用 auto 作为函数参数本质是简化的函数模板// C20写法 auto add(auto a, auto b) { return a b; } // 等价于C11写法 template typename T, typename U auto add(T a, U b) - decltype(a b) { return a b; }3尾置返回类型1核心内容尾置返回类型是 C11 引入的一种函数声明语法它允许将函数的返回类型放在参数列表之后而不是函数名前。基本语法auto functionName(parameters) - returnType { // 函数体 }核心使用场景复杂返回类型依赖参数类型的返回类型Lambda 表达式// 1. 复杂返回类型 auto getComplexType() - std::mapstd::string, std::vectorint { // 函数体 } // 2. 依赖参数类型的返回类型 template typename T, typename U auto add(T t, U u) - decltype(t u) { return t u; } // 3. lambda表达式 auto lambda [](int x) - double { return x * 1.5; };2为什么需要尾置返回类型在 C11 中函数返回类型必须在函数名之前声明但模板函数的返回类型可能依赖于参数类型而参数在返回类型声明之后才出现因此需要尾置返回类型。错误写法C11 不支持template typename T, typename U decltype(a b) add(T a, U b) { // 错误a和b尚未声明 return a b; }3C14以后得改进C14 支持 auto 作为函数返回类型编译器会自动推导返回值类型大部分场景下不再需要尾置返回类型。但在以下情况仍然需要返回值类型无法自动推导如多个 return 语句返回不同类型需要精确控制返回值类型如保留引用或 constlambda 表达式的返回类型需要显式指定// C14 auto返回类型 template typename T, typename U auto add(T t, U u) { return t u; } // 需要尾置返回类型的场景精确控制返回值为引用 template typename T auto getRef(T t) - T { return t; }4decltype1核心内容如果我们希望用表达式推出变量的类型但是不想用表达式的值初始化变量那么这时可以使用 decltype。decltype (f ()) x; 需要注意的是编译器并不会实际调用 f 函数而是用 f 的返回类型作为 x 的类型。decltype 处理 const 和引用的方式和 auto 也有所不同decltype 会保留顶层 constdecltype 会保留引用int main() { int i 0; const int ci 0; const int rci ci; decltype(i) m 1; // m的类型是int decltype(ci) x 1; // x的类型是const int保留顶层const // x; // 报错const变量不可修改 decltype(rci) y x; // y的类型是const int保留引用 // decltype(rci) z; // 报错引用必须初始化 int* p1 i; decltype(p1) p2 nullptr; // p2的类型是int* // 三个特殊推导规则 decltype(*p1) r1 i; // int解引用表达式推导出引用 decltype(i) r2; // r2的类型是int decltype((i)) r3 i; // int括号括起来的左值表达式推导出引用 return 0; }三个特殊推导规则详解解引用表达式decltype(*p)推导为引用类型因为解引用操作返回的是左值括号表达式decltype((x))推导为引用类型因为 (x) 是一个左值表达式函数调用表达式推导为函数返回值类型不会实际调用函数2decltype在模版中的使用templateclass Iter auto Func(Iter it1, Iter it2) - decltype(*it1) { auto x *it1; it1; while (it1 ! it2) { x *it1; it1; } return x; }3decltype(auto) C14decltype (auto) 是 C14 引入的特性它结合了 auto 的便利性和 decltype 的精确类型推导能力。int main() { int i 0; int ri i; const int ci 42; // 顶层const int* const p1 i; // 顶层const const int* p2 ci; // 底层const auto j ri; // j类型为int忽略引用 decltype(auto) j1 ri; // j1类型为int保留引用 j1; auto r1 ci; // r1类型为int忽略顶层const decltype(auto) rr1 ci; // rr1类型为const int保留顶层const // rr1; // 报错 auto r2 p1; // r2类型为int*忽略顶层const decltype(auto) rr2 p1; // rr2类型为int* const保留顶层const // rr2; // 报错 auto r3 p2; // r3类型为const int*保留底层const decltype(auto) rr3 p2; // rr3类型为const int*保留底层const // (*rr3); // 报错 return 0; }4decltype(auto)的完美转发decltype (auto) 主要用于函数返回值的完美转发它可以完全保留表达式的类型和值类别// 完美转发函数调用的返回值 template typename Func, typename... Args decltype(auto) invoke(Func func, Args... args) { return std::forwardFunc(func)(std::forwardArgs(args)...); } // 使用示例 int getRef(int x) { return x; } int getVal(int x) { return x; } int main() { int x 10; decltype(auto) ref invoke(getRef, x); // int decltype(auto) val invoke(getVal, 10); // int return 0; }5auto的decltype特性autodecltype初始化要求必须有初始化表达式不需要初始化只需要表达式顶层 const忽略保留引用忽略保留解引用表达式推导为 T推导为 T括号表达式推导为 T推导为 T主要用途简化变量声明精确提取表达式类型、模板返回类型推导5using和typedef1核心内容C98 中我们一般使用 typedef 重定义类型名也很方便但是 typedef 不支持带模板参数的类型重定义。C11 中新增了 using 可以替代 typedefusing 的别名语法覆盖了 typedef 的全部功能不少场景还更清晰一些比如函数指针的重定义其次最大的变化是支持带模板参数重定义的语法。基本语法using 类型别名 类型;#include map #include string using namespace std; // typedef mapstring, int CountMap; // typedef mapstring, string DictMap; // typedef int DateType; // typedef void (*Callback)(int); // using 兼容typedef的用法 using CountMap mapstring, int; using DictMap mapstring, string; using STDateType int; using Callback void (*)(int); // using支持带模板参数的类型重定义typedef不支持 templateclass Val using Map mapstring, Val; templateclass Val using MapIter typename mapstring, Val::iterator; int main() { Mapint countMap; Mapstring dictMap; MapIterint cit countMap.begin(); MapIterstring dit dictMap.begin(); return 0; }2核心对比特性typedefusing基本类型别名支持支持函数指针别名语法晦涩语法清晰数组指针别名语法晦涩语法清晰模板别名不支持支持可读性较差较好函数指针别名对比// typedef写法 typedef void (*Callback)(int, double); // using写法 using Callback void (*)(int, double);数组指针别名对比// typedef写法 typedef int (*ArrayPtr)[5]; // using写法 using ArrayPtr int (*)[5];6常见的误区1auto 推导数组退化为指针int arr[5] {1,2,3,4,5}; auto a arr; // int*不是int[5] // 正确写法显式声明引用 auto a_ref arr; // int()[5]2decltype((x))的陷阱int x 10; decltype((x)) y x; // inty是x的引用 y 20; // x也会被修改为203auto不能推导顶层constconst int ci 42; auto a ci; // int不是const int // 正确写法显式声明const const auto b ci; // const int4万能引用的误用auto只能用于推导不能用于声明普通变量。如果初始化表达式是左值它会推导为左值引用可能导致意外的别名问题。5const成员函数中修改成员变量除非用 mutable 修饰否则 const 成员函数不能修改任何成员变量。6typename不支持模版别名不要尝试用 typedef 定义模板别名这是 C98 的限制必须使用 using。
C++11核心特性(一):const语义和类型推导
发布时间:2026/6/7 20:23:08
C11 是现代 C 的里程碑它对类型系统进行了根本性的改进。传统 C 的类型系统存在诸多痛点繁琐的类型声明降低了代码可读性模糊的 const 语义容易引入权限放大错误模板编程中类型推导困难导致代码冗余。从 C11 开始语言引入了一系列特性来解决这些问题const 语义的明确化、auto 自动类型推导、decltype 精确类型提取、using 类型别名等彻底改变了 C 代码的书写方式。1顶层const和底层const1核心内容指针本身是一个独立的对象它又可以指向另一个对象因此涉及两个完全独立的 const 语义顶层 const修饰变量本身表示变量的值不可修改底层 const修饰指针 / 引用指向的对象表示不能通过该指针 / 引用修改对象核心记忆法对于指针*左边的 const 是底层*右边的 const 是顶层。int main() { int i 0; int* const p1 i; // 顶层constp1本身不可修改但*p1可以修改 const int ci 42; // 顶层constci本身不可修改 const int* p2 ci; // 底层const*p2不可修改但p2可以指向其他对象 const int r ci; // 引用的const永远是底层const return 0; }补充说明大多数普通对象被 const 修饰时都是顶层 const因为它们本身就是值对象没有 指向 的概念。只有指针和引用才会涉及底层 const。2const完整语义1const与函数参数传递传值参数顶层 const 会被忽略因为参数是原对象的副本修改副本不会影响原对象传指针 / 引用参数底层 const 会被保留实现 只读 语义防止函数意外修改原对象// 错误这两个函数是重复定义顶层const在传值时被忽略 void func(int x) {} void func(const int x) {} // 正确这是两个不同的函数底层const不被忽略 void func(int* p) {} // 不能接受const int*参数权限放大 void func(const int* p) {} // 可以接受int*和const int*参数权限缩小 // 引用参数同理 void func(int x) {} // 不能接受const int参数 void func(const int x) {} // 可以接受int和const int参数对于大对象优先使用const T作为函数参数既避免拷贝开销又保证对象不被修改。2const成员函数const 修饰成员函数时本质是给隐式的this指针加上了底层 const表示该函数不能修改类的任何非静态成员变量。class Person { public: Person(std::string name, int age) : _name(name), _age(age) {} // const成员函数this指针类型为const Person* std::string getName() const { // _age 20; // 错误不能修改非mutable成员变量 return _name; } // 非const成员函数this指针类型为Person* void setAge(int age) { _age age; } private: std::string _name; int _age; }; int main() { Person p1(Alice, 18); const Person p2(Bob, 20); p1.getName(); // 正确非const对象可以调用const成员函数 p1.setAge(19); // 正确 p2.getName(); // 正确const对象只能调用const成员函数 // p2.setAge(21); // 错误const对象不能调用非const成员函数 return 0; }const 成员函数重载同一个函数可以有 const 和非 const 两个版本编译器会根据对象的 const 属性自动选择合适的版本。这是 STL 容器中operator[]的标准实现方式class Array { public: int operator[](size_t index) { return _data[index]; } // const版本返回const引用防止通过const对象修改元素 const int operator[](size_t index) const { return _data[index]; } private: int _data[10]; };3mutable如果需要在 const 成员函数中修改某个成员变量比如缓存计算结果、统计调用次数可以用mutable修饰该变量表示它永远是可变的不受 const 约束。class Counter { public: Counter() : _count(0) {} void increment() const { _count; // 正确mutable成员可以在const函数中修改 } int getCount() const { return _count; } private: mutable int _count; // 可变成员 };4const与返回值返回值为const T防止返回值被修改如const int func()返回值为const T防止返回的引用被修改同时避免拷贝开销class String { public: // 返回const引用防止用户修改内部字符 const char operator[](size_t index) const { return _data[index]; } private: char* _data; };2类型处理1auto1核心内容auto 让编译器根据初始化表达式自动推导变量类型核心推导规则忽略顶层 const保留底层 const忽略引用推导为引用指向的类型不能自动推导出引用类型和顶层 const需要显式声明int main() { int i 0; int ri i; const int ci 42; // 顶层const int* const p1 i; // 顶层const const int* p2 ci; // 底层const const int ri1 ci; // 底层const const int ri2 i; // 底层const auto j ri; // j类型为int忽略引用 j; auto k i; // k类型为int k; auto r1 ci; // r1类型为int忽略顶层const r1; auto r2 p1; // r2类型为int*忽略顶层const r2; auto r3 p2; // r3类型为const int*保留底层const // (*r3); // 报错不能修改底层const指向的对象 auto r4 ri1; // r4类型为intri1是ci的别名ci的顶层const被忽略 r4; auto r5 ri2; // r5类型为int r5; // 显式声明引用或const const auto r7 ci; // r7类型为const int auto r8 ri1; // r8类型为const int auto r9 ri2; // r9类型为const int auto r10 ci; // r10类型为const int auto r11 ri; // r11类型为int // r7; // 报错const变量不可修改 // r8; // 报错const引用不可修改 r11; // 正确非const引用可以修改 return 0; }2auto与引用的三种形式auto左值引用只能绑定到左值const autoconst 左值引用可以绑定到左值、右值和 const 对象auto万能引用遵循引用折叠规则既可以绑定到左值也可以绑定到右值#includeiostream using namespace std; void func(int x) { cout void func(int x) endl; } void func(int x) { cout void func(int x) endl; } void func(const int x) { cout void func(const int x) endl; } void func(const int x) { cout void func(const int x) endl; } int main() { int x 10; const int cx 20; auto rx1 x; // int auto rx2 cx; // const int func(rx1); // 输出void func(int x) func(rx2); // 输出void func(const int x) const auto rx3 x; // const int const auto rx4 cx; // const int func(rx3); // 输出void func(const int x) func(rx4); // 输出void func(const int x) // 万能引用 auto rx5 x; // int左值推导为左值引用 auto rx6 cx; // const intconst左值推导为const左值引用 func(rx5); // 输出void func(int x) func(rx6); // 输出void func(const int x) auto rx7 move(x); // int右值推导为右值引用 auto rx8 move(cx); // const intconst右值推导为const右值引用 func(forwardint(rx7)); // 输出void func(int x) func(forwardconst int(rx8)); // 输出void func(const int x) return 0; }引用折叠规则T →TT →TT →TT →T简单来说只要有一个左值引用结果就是左值引用只有两个都是右值引用结果才是右值引用。2auto的特殊场景1数组推导int arr[5] {1,2,3,4,5}; auto a1 arr; // int*数组退化为指针 auto a2 arr; // int()[5]显式声明引用保留数组类型 // 可以用a2获取数组大小 cout sizeof(a2) / sizeof(int) endl; // 输出52lambda表达式推导lambda 表达式的类型是唯一的匿名类型只能用 auto 来声明auto lambda [](int x) { return x * 2; }; // lambda的类型是唯一的匿名类型3初始化列表推导auto 与初始化列表结合时有特殊规则auto x {1,2,3}; // std::initializer_listint auto y{1}; // C17及以后intC11/C14std::initializer_listint // auto z {1, 2.0}; // 错误元素类型不一致4范围for循环中的auto范围 for 循环是 auto 最常用的场景之一vectorint vec {1,2,3,4,5}; // 拷贝元素修改不会影响原容器 for (auto x : vec) { x * 2; } // 引用元素修改会影响原容器 for (auto x : vec) { x * 2; } // const引用只读访问避免拷贝 for (const auto x : vec) { cout x endl; }5C17的结构化绑定结构化绑定允许用 auto 同时解包多个值极大简化了 tuple、pair 和结构体的访问#include tuple #include utility std::tupleint, double, std::string getInfo() { return {1, 3.14, hello}; } int main() { // 结构化绑定解包tuple auto [id, score, name] getInfo(); // 解包pair std::pairint, std::string p {10, Alice}; auto [key, value] p; // 解包结构体 struct Point { int x; int y; }; Point pt {1, 2}; auto [x, y] pt; return 0; }6C20auto作为函数参数C20 允许用 auto 作为函数参数本质是简化的函数模板// C20写法 auto add(auto a, auto b) { return a b; } // 等价于C11写法 template typename T, typename U auto add(T a, U b) - decltype(a b) { return a b; }3尾置返回类型1核心内容尾置返回类型是 C11 引入的一种函数声明语法它允许将函数的返回类型放在参数列表之后而不是函数名前。基本语法auto functionName(parameters) - returnType { // 函数体 }核心使用场景复杂返回类型依赖参数类型的返回类型Lambda 表达式// 1. 复杂返回类型 auto getComplexType() - std::mapstd::string, std::vectorint { // 函数体 } // 2. 依赖参数类型的返回类型 template typename T, typename U auto add(T t, U u) - decltype(t u) { return t u; } // 3. lambda表达式 auto lambda [](int x) - double { return x * 1.5; };2为什么需要尾置返回类型在 C11 中函数返回类型必须在函数名之前声明但模板函数的返回类型可能依赖于参数类型而参数在返回类型声明之后才出现因此需要尾置返回类型。错误写法C11 不支持template typename T, typename U decltype(a b) add(T a, U b) { // 错误a和b尚未声明 return a b; }3C14以后得改进C14 支持 auto 作为函数返回类型编译器会自动推导返回值类型大部分场景下不再需要尾置返回类型。但在以下情况仍然需要返回值类型无法自动推导如多个 return 语句返回不同类型需要精确控制返回值类型如保留引用或 constlambda 表达式的返回类型需要显式指定// C14 auto返回类型 template typename T, typename U auto add(T t, U u) { return t u; } // 需要尾置返回类型的场景精确控制返回值为引用 template typename T auto getRef(T t) - T { return t; }4decltype1核心内容如果我们希望用表达式推出变量的类型但是不想用表达式的值初始化变量那么这时可以使用 decltype。decltype (f ()) x; 需要注意的是编译器并不会实际调用 f 函数而是用 f 的返回类型作为 x 的类型。decltype 处理 const 和引用的方式和 auto 也有所不同decltype 会保留顶层 constdecltype 会保留引用int main() { int i 0; const int ci 0; const int rci ci; decltype(i) m 1; // m的类型是int decltype(ci) x 1; // x的类型是const int保留顶层const // x; // 报错const变量不可修改 decltype(rci) y x; // y的类型是const int保留引用 // decltype(rci) z; // 报错引用必须初始化 int* p1 i; decltype(p1) p2 nullptr; // p2的类型是int* // 三个特殊推导规则 decltype(*p1) r1 i; // int解引用表达式推导出引用 decltype(i) r2; // r2的类型是int decltype((i)) r3 i; // int括号括起来的左值表达式推导出引用 return 0; }三个特殊推导规则详解解引用表达式decltype(*p)推导为引用类型因为解引用操作返回的是左值括号表达式decltype((x))推导为引用类型因为 (x) 是一个左值表达式函数调用表达式推导为函数返回值类型不会实际调用函数2decltype在模版中的使用templateclass Iter auto Func(Iter it1, Iter it2) - decltype(*it1) { auto x *it1; it1; while (it1 ! it2) { x *it1; it1; } return x; }3decltype(auto) C14decltype (auto) 是 C14 引入的特性它结合了 auto 的便利性和 decltype 的精确类型推导能力。int main() { int i 0; int ri i; const int ci 42; // 顶层const int* const p1 i; // 顶层const const int* p2 ci; // 底层const auto j ri; // j类型为int忽略引用 decltype(auto) j1 ri; // j1类型为int保留引用 j1; auto r1 ci; // r1类型为int忽略顶层const decltype(auto) rr1 ci; // rr1类型为const int保留顶层const // rr1; // 报错 auto r2 p1; // r2类型为int*忽略顶层const decltype(auto) rr2 p1; // rr2类型为int* const保留顶层const // rr2; // 报错 auto r3 p2; // r3类型为const int*保留底层const decltype(auto) rr3 p2; // rr3类型为const int*保留底层const // (*rr3); // 报错 return 0; }4decltype(auto)的完美转发decltype (auto) 主要用于函数返回值的完美转发它可以完全保留表达式的类型和值类别// 完美转发函数调用的返回值 template typename Func, typename... Args decltype(auto) invoke(Func func, Args... args) { return std::forwardFunc(func)(std::forwardArgs(args)...); } // 使用示例 int getRef(int x) { return x; } int getVal(int x) { return x; } int main() { int x 10; decltype(auto) ref invoke(getRef, x); // int decltype(auto) val invoke(getVal, 10); // int return 0; }5auto的decltype特性autodecltype初始化要求必须有初始化表达式不需要初始化只需要表达式顶层 const忽略保留引用忽略保留解引用表达式推导为 T推导为 T括号表达式推导为 T推导为 T主要用途简化变量声明精确提取表达式类型、模板返回类型推导5using和typedef1核心内容C98 中我们一般使用 typedef 重定义类型名也很方便但是 typedef 不支持带模板参数的类型重定义。C11 中新增了 using 可以替代 typedefusing 的别名语法覆盖了 typedef 的全部功能不少场景还更清晰一些比如函数指针的重定义其次最大的变化是支持带模板参数重定义的语法。基本语法using 类型别名 类型;#include map #include string using namespace std; // typedef mapstring, int CountMap; // typedef mapstring, string DictMap; // typedef int DateType; // typedef void (*Callback)(int); // using 兼容typedef的用法 using CountMap mapstring, int; using DictMap mapstring, string; using STDateType int; using Callback void (*)(int); // using支持带模板参数的类型重定义typedef不支持 templateclass Val using Map mapstring, Val; templateclass Val using MapIter typename mapstring, Val::iterator; int main() { Mapint countMap; Mapstring dictMap; MapIterint cit countMap.begin(); MapIterstring dit dictMap.begin(); return 0; }2核心对比特性typedefusing基本类型别名支持支持函数指针别名语法晦涩语法清晰数组指针别名语法晦涩语法清晰模板别名不支持支持可读性较差较好函数指针别名对比// typedef写法 typedef void (*Callback)(int, double); // using写法 using Callback void (*)(int, double);数组指针别名对比// typedef写法 typedef int (*ArrayPtr)[5]; // using写法 using ArrayPtr int (*)[5];6常见的误区1auto 推导数组退化为指针int arr[5] {1,2,3,4,5}; auto a arr; // int*不是int[5] // 正确写法显式声明引用 auto a_ref arr; // int()[5]2decltype((x))的陷阱int x 10; decltype((x)) y x; // inty是x的引用 y 20; // x也会被修改为203auto不能推导顶层constconst int ci 42; auto a ci; // int不是const int // 正确写法显式声明const const auto b ci; // const int4万能引用的误用auto只能用于推导不能用于声明普通变量。如果初始化表达式是左值它会推导为左值引用可能导致意外的别名问题。5const成员函数中修改成员变量除非用 mutable 修饰否则 const 成员函数不能修改任何成员变量。6typename不支持模版别名不要尝试用 typedef 定义模板别名这是 C98 的限制必须使用 using。