【C++11】入门基础
文章目录1. C11 简介2. 统一的列表初始化2.1 初始化2.2 std::initializer_list3. 声明3.1 auto3.2 decltype3.3 nullptr4. 范围 for 循环5. 智能指针5.1 unique_ptr5.2 shared_ptr5.3 weak_ptr5.4 总结6. STL 中一些变化1. C11 简介在 2003 年 C 标准委员会曾经提交了一份技术勘误表简称 TC1使得 C03 这个名字已经取代了 C98 称为 C11 之前的最新 C 标准名称。不过由于 C03TC1主要是对 C98 标准中的漏洞进行修复语言的核心部分则没有改动因此人们习惯性的把两个标准合并称为 C98/03 标准。从 C0x 到 C11C 标准 10 年磨一剑第二个真正意义上的标准珊珊来迟。相比于 C98/03C11 则带来了数量可观的变化其中包含了约 140 个新特性以及对 C03 标准中约 600 个缺陷的修正这使得 C11 更像是从 C98/03 中孕育出的一种新语言。C11 能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全不仅功能更强大而且能提升程序员的开发效率公司实际项目开发中也用得比较多。C11 增加的语法特性非常篇幅非常多主要讲解实际中比较实用的语法详情可见官网C11 官网。这里可以科普一个小故事1998 年是 C 标准委员会成立的第一年本来计划以后每 5 年视实际需要更新一次标准C 国际标准委员会在研究 C 03 的下一个版本的时候一开始计划是 2007 年发布所以最初这个标准叫 C 07。但是到 06 年的时候官方觉得 2007 年肯定完不成 C 07而且官方觉得 2008 年可能也完不成。最后干脆叫 C 0x。x 的意思是不知道到底能在07还是08还是09年完成。结果 2010 年的时候也没完成最后在 2011 年终于完成了 C 标准。所以最终定名为 C11。2. 统一的列表初始化2.1 初始化在 C98 中标准允许使用花括号{ }对数组或者结构体元素进行统一的列表初始值设定。比如structPoint{int_x;int_y;};intmain(){intarr1[]{1,2,3,4,5};intarr2[5]{0};Point p{1,2};coutarr1: ;for(inti0;isizeof(arr1)/sizeof(arr1[0]);i){coutarr1[i] ;}coutendl;coutarr2: ;for(inti0;isizeof(arr2)/sizeof(arr2[0]);i){coutarr2[i] ;}coutendl;coutPoint: p._x p._yendl;return0;}结果如下C11 扩大了用大括号括起的列表初始化列表的使用范围使其可用于所有的内置类型和用户自定义的类型使用初始化列表时可添加等号也可不添加。structPoint{int_x;int_y;};intmain(){intx11;// 可添加等号intx2{2};// 可不添加等号coutx1 x2endl;intarr1[]{1,2,3,4,5};intarr2[5]{0};Point p{1,2};coutp._x p._yendl;// C11中列表初始化也可以用于new表达式中C98无法初始化int*p1newint[4]{0};int*p2newint[5]{1,2,3,4,5};return0;}结果如下创建对象时也可以使用【列表初始化】方式调用构造函数初始化classDate{public://Date(intyear,intmonth,intday):_y(year),_m(month),_d(day){}//~Date(){}private:int_y;// 年int_m;// 月int_d;// 日};intmain(){Dated1(2025,10,1);// 老的初始化方式// C11支持的列表初始化这里会调用构造函数初始化Date d2{2025,10,2};Date d3{2025,10,3};return0;}2.2 std::initializer_listinitializer_list叫做 初始化成员列表此类型用于访问 C 初始化列表中的值该列表是 const T 类型的元素列表。std::initializer_list是什么类型呢intmain(){// the type of il is an initializer_listautoil{10,20,30};couttypeid(il).name()endl;return0;}结果如下会打印出initializer_listint的类型编码它确实是std::initializer_listint而不是数组也不是 vector。注意它不是一个容器而是一个 轻量级的只读视图view用于支持花括号{ }初始化语法。它的成员函数如下std::initializer_list的使用场景一般是作为构造函数的参数C11 对 STL 中的不少容器就增加std::initializer_list作为参数的构造函数这样初始化容器对象就更方便了。也可以作为operator的参数这样就可以用大括号赋值。代码示例intmain(){vectorintv{1,2,3,4};listintlt{1,2};// 这里{sort, 排序}会先初始化构造一个pair对象mapstring,stringdict{{sort,排序},{insert,插入}};// 使用大括号对容器赋值v{10,20,30};return0;}1️⃣ 让类支持花括号初始化我们可以手动模拟实现一个 vector并让它支持{ }初始化。// 注意包含头文件#includeinitializer_listclassMyVector{public:MyVector(initializer_listintlist){for(intv:list)_data.push_back(v);}voidprint(){for(intv:_data)coutv ;cout\n;}private:vectorint_data;};intmain(){MyVector v{1,2,3,4};v.print();// 1 2 3 4return0;}结果如下2️⃣ 可作为函数参数voidfunc(std::initializer_listintlist){for(autox:list)coutx ;coutendl;}intmain(){func({10,20,30});return0;}结果如下3. 声明c11 提供了多种简化声明的方式尤其是在使用模板时。3.1 auto在 C98 中 auto 是一个存储类型的说明符表明变量是局部自动存储类型但是局部域中定义局部的变量默认就是自动存储类型所以 auto 就没什么价值了。C11 中废弃 auto 原来的用法将其用于实现自动类型推断。这样要求必须进行显示初始化让编译器将定义对象的类型设置为初始化值的类型。代码示例// 注意头文件#includetypeinfo#includecstringintmain(){inti10;autopi;autopfstrcpy;//strcpy 是 C 标准库函数couttypeid(p).name()endl;couttypeid(pf).name()endl;mapstring,stringdict{{sort,排序},{insert,插入}};// mapstring, string::iterator it dict.begin();autoitdict.begin();return0;}结果如下P表示 pointeri表示 int使用 auto 带来好处避免冗长的模板声明自动适应容器变化比如从 map 换成 unordered_map更安全避免写错类型3.2 decltype关键字 decltype 将变量的类型声明为表达式指定的类型。// decltype的一些使用使用场景templateclassT1,classT2voidF(T1 t1,T2 t2){decltype(t1*t2)ret;// 对于 F(1,a)这里是 intcoutF ret: typeid(ret).name()endl;}intmain(){constintx1;doubley2.2;decltype(x*y)ret;// doubledecltype(x)p;// const int*coutret: typeid(ret).name()endl;coutp: typeid(p).name()endl;F(1,a);// 打印 intreturn0;}结果如下在 GCC/Clang 编译器下 PKiPpointerKconstiint注意decltype(x) 与 decltype((x)) 不同decltype(x)如果 x 声明为 const int结果是 const int。decltype((x))(x) 是左值表达式 → 结果是 const int。3.3 nullptr由于 C 中 NULL 被定义成字面量 0这样就可能回带来一些问题因为 0 既能表示指针常量又能表示整形常量。所以出于清晰和安全的角度考虑C11 中新增了 nullptr用于表示空指针。因为 NULL 在 C 中通常是#defineNULL0// 或者 (void*)0但 C 中通常是 0而 0 可以作为整型字面量作为空指针常量导致一些二义性和重载歧义问题使用 NULL 可能出现麻烦voidf(int);voidf(char*);f(NULL);// 调用哪个因为NULL 0优先匹配 int调用 f(int)但期望可能是 f(char*)4. 范围 for 循环范围 for 循环Range-based for loop 是 C11 引入的一种简化遍历容器的语法让你像 Python/Java 一样优雅地遍历序列。例子intmain(){vectorintv{1,2,3};for(intx:v){coutx ;}coutendl;return0;}结果如下这里注意一下引用 vs 非引用❌ 拷贝遍历默认for(intx:v)x100;// 不会修改 v✅ 引用遍历允许修改原容器for(intx:v)x100;// ✅ 修改原容器✅ const 引用避免拷贝、只读for(constintx:v){coutx\n;}所以推荐使用for (const auto x : container)例子intmain(){mapstring,intm{{A,1},{B,2}};// 第一种for(constautop:m){coutp.first:p.second\n;}return0;}结果如下它的底层展开机制是编译器会转为类似如下for(autox:range)for(autoitbegin(range);it!end(range);it){autox*it;}所以不建议对容器 while 遍历中修改结构如 erase5. 智能指针智能指针Smart Pointer是 C11 引入的 RAII资源获取即初始化机制 用来自动管理动态内存避免手动 delete 导致的内存泄漏与悬空指针问题。核心思想对象生命周期绑定在智能指针上离开作用域自动 delete。C 常见智能指针智能指针所在头文件所有权语义使用场景unique_ptrmemory独占所有权唯一所有者资源严格只能被一个对象拥有shared_ptrmemory共享所有权引用计数多个对象共享同一资源weak_ptrmemory弱引用不影响引用计数解决 shared_ptr 循环引用5.1 unique_ptr独占所有权#includeiostream#includememoryusingnamespacestd;intmain(){unique_ptrintp1(newint(10));cout*p1endl;// unique_ptr 禁止拷贝只能移动unique_ptrintp2move(p1);return0;}结果如下5.2 shared_ptr引用计数#includeiostream#includememoryusingnamespacestd;intmain(){autop1make_sharedint(20);autop2p1;// 引用计数 1coutp1.use_count()endl;// 2return0;}结果如下5.3 weak_ptr防止循环引用shared_ptrAamake_sharedA();shared_ptrBbmake_sharedB();a-bb;b-aa;// ❌ 循环引用 内存泄漏使用 weak_ptr 解决classB;classA{public:weak_ptrBb;// 不增加引用计数};上面代码的循环引用示例图shared_ptr shared_ptr A----------------B^^||-----weak_ptr-----5.4 总结智能指针最佳实践总结优先使用std::unique_ptr多所有权才用std::shared_ptr有关联对象互相引用时使用std::weak_ptr用 make_unique/make_shared 创建对象避免 new/delete 出现在业务逻辑6. STL 中一些变化圈起来是 C11 中的一些几个新容器但是实际最有用的是 unordered_map 和 unordered_set。容器中的一些新方法如果我们再细细去看会发现基本每个容器中都增加了一些 C11 的方法但是其实很多都是用得比较少的。比如提供了 cbegin 和 cend 方法返回 const 迭代器等等但是实际意义不大因为 begin 和 end 也是可以返回 const 迭代器的这些都是属于锦上添花的操作。实际上 C11 更新后容器中增加的新方法最后用的插入接口函数的右值引用版本。需要详细了解容器的新特性可以访问 STL容器具体会在【右值引用】和【移动语义】章节重点说明。