C++之旅-C++11的深度剖析(1)

C++之旅-C++11的深度剖析(1)

目录

前言/背景

1.C++11的发展历史

 2.列表初始化

2.1 C++98传统的{}

2.2 C++11中的{}

2.3 C++11中的std::initializer_list

3.右值引用

3.1 左值和右值

3.2 左值引用和右值引用

3.3 引用延长生命周期

3.4 左值和右值的参数匹配

结束语


前言/背景

随着现代软件开发的快速发展,编程语言也在不断进化,C++ 作为一种功能强大的编程语言,已经经历了多个版本的更新,每一次版本的发布都为开发者带来了新的特性和功能。C++11 是 C++ 语言的一个重要版本,它于 2011 年正式发布,并引入了许多全新的特性,极大地提升了代码的效率、可读性以及程序的执行性能。

C++11 的发布不仅对 C++ 开发者来说是一次重大提升,它还为整个编程社区提供了更加现代化的编程工具。从增强的类型推断到智能指针,从并发编程的支持到对Lambda表达式的引入,C++11 使得开发者在编写高效且可维护的代码时拥有了更多的选择。

然而,C++11 的变化不仅仅体现在语法层面,它还在性能优化和多线程编程上做出了许多贡献,这使得 C++ 成为更加强大和灵活的语言,尤其适用于复杂的系统级应用、图形处理和高性能计算等领域。

在这篇博客中,我们将深入探讨 C++11 的一些核心特性,并通过实际示例帮助大家理解它们的应用场景和优势。如果你是 C++ 的开发者,或者对 C++11 的新特性感兴趣,那么这篇博客将帮助你更好地掌握这个版本的精髓,并将其应用到实际开发中去。 

1.C++11的发展历史

C++11 是 C++ 的第二个主要版本,并且是从 C++98 起的最重要更新。它引入了大量更改,标准化了既有实践,并改进了对 C++ 程序员可用的抽象。在它最终由 ISO 在 2011 年 8 月 12 日采纳前,人们曾使用名称“C++0x”,因为它曾被期待在 2010 年之前发布。C++03 与 C++11 期间花了 8 年时间,故而这是迄今为止最长的版本间隔。从那时起,C++ 有规律地每 3 年更新一次。

 2.列表初始化

2.1 C++98传统的{}

C++98中一般数组和结构体可以用{}进行初始化。

struct Point { int _x; int _y; }; int main() { int array1[] = { 1, 2, 3, 4, 5 }; int array2[5] = { 0 }; Point p = { 1, 2 }; return 0; }

2.2 C++11中的{}

• C++11以后想统一初始化方式,试图实现一切对象皆可用{}初始化,{}初始化也叫做列表初始化。• 内置类型支持,自定义类型也支持,自定义类型本质是类型转换,中间会产生临时对象,最后优化了以后变成直接构造。• {}初始化的过程中,可以省略掉=• C++11列表初始化的本意是想实现一个大统一的初始化方式,其次在有些场景下带来的不少便利,如容器push/inset多参数构造的对象时,{}初始化会很方便

#define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<vector> using namespace std; struct P{ int a; int b; }; class Date { public: Date(int year = 1, int month = 1, int day = 1) :_year(year) , _month(month) , _day(day) { cout << "Date(int year = 1, int month = 1, int day = 1)" << endl; } Date(const Date& d) :_year(d._year) , _month(d._month) , _day(d._day) { cout<<"Date(const Date& d)"<<endl; } private: int _year; int _month; int _day; }; int main() { // //C++98支持 int a[]={1,2,3,4,5}; int a2[5] = { 0 }; P p = { 1, 2 }; // // C++11⽀持的 // // 内置类型⽀持 int x1 = { 2 }; // // ⾃定义类型⽀持 //// 这⾥本质是⽤{ 2025, 1, 1}构造⼀个Date临时对象 //// 临时对象再去拷⻉构造d1,编译器优化后合⼆为⼀变成{ 2025, 1, 1}直接构造初始化 // // // 运⾏⼀下,我们可以验证上⾯的理论,发现是没调⽤拷⻉构造的 Date d1 = { 2025, 1, 1 }; // // 这⾥d2引⽤的是{ 2024, 7, 25 }构造的临时对象 const Date& d2 = { 2024, 7, 25 }; // // 需要注意的是C++98⽀持单参数时类型转换,也可以不⽤{} Date d3 = { 2025 }; Date d4 = 2025; // // 可以省略掉= P p1{ 1, 2 }; int x2{ 2 }; Date d6{ 2024, 7, 25 }; const Date& d7{ 2024, 7, 25 }; //// 不⽀持,只有{}初始化,才能省略= vector<Date> v; v.push_back(d1); v.push_back(Date(2025, 1, 1)); // ⽐起有名对象和匿名对象传参,这⾥{}更有性价⽐ v.push_back({ 2025, 1, 1 }); return 0; } 

2.3 C++11中的std::initializer_list

上面的初始化已经很方便,但是对象容器初始化还是不太方便,比如一个vector对象,我想用N个值去构造初始化,那么我们得实现很多个构造函数才能支持, vector<int> v1 ={1,2,3};vector<int> v2 = {1,2,3,4,5};• C++11库中提出了一个std::initializer_list的类, auto il = { 10, 20, 30 }; // thetype of il is an initializer_list ,这个类的本质是底层开一个数组,将数据拷贝过来,std::initializer_list内部有两个指针分别指向数组的开始和结束。• 容器支持一个std::initializer_list的构造函数,也就支持任意多个值构成的 {x1,x2,x3...} 进行初始化。STL中的容器支持任意多个值构成的 {x1,x2,x3...} 进行初始化,就是通过std::initializer_list的构造函数支持的。

#define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<vector> #include<string> #include<map> using namespace std; int main() { std::initializer_list<int> mylist; mylist = { 10, 20, 30 }; cout << sizeof(mylist) << endl; // 这⾥begin和end返回的值initializer_list对象中存的两个指针 // 这两个指针的值跟i的地址跟接近,说明数组存在栈上 int i = 0; cout << mylist.begin() << endl; cout << mylist.end() << endl; cout << &i << endl; // {}列表中可以有任意多个值 // 这两个写法语义上还是有差别的,第⼀个v1是直接构造, // 第⼆个v2是构造临时对象+临时对象拷⻉v2+优化为直接构造 vector<int> v1({ 1,2,3,4,5 }); vector<int> v2 = { 1,2,3,4,5 }; const vector<int>& v3 = { 1,2,3,4,5 }; // 这⾥是pair对象的{}初始化和map的initializer_list构造结合到⼀起⽤了 map<string, string> dict = { {"sort", "排序"}, {"string", "字符串"} }; // initializer_list版本的赋值⽀持 v1 = { 10,20,30,40,50 }; return 0; }

3.右值引用

3.1 左值和右值

• 左值是一个表示数据的表达式(如变量名或解引用的指针),一般是有持久状态,存储在内存中,我们可以获取它的地址 ,左值可以出现赋值符号的左边,也可以出现在赋值符号右边。定义时const 修饰符后的左值,不能给他赋值,但是可以取它的地址。

int main() { int* p = new int(0); *p = 10; int c = 1; const int b = c; string s = "hello"; string s1("world"); /*cout << *p << endl;*/ cout << &p << endl; cout << &b << endl; cout << &s << endl; cout << &s1 << endl; cout << (void*) & s1[0] << endl; return 0; }

 

右值也是一个表示数据的表达式,要么是字面值常量、要么是表达式求值过程中创建的临时对象等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边, 右值不能取地址。

// 右值:不能取地址 double x = 1.1, y = 2.2; // 以下⼏个10、x + y、fmin(x, y)、string("11111")都是常⻅的右值 10; x + y; fmin(x, y); string("11111"); cout << &10 << endl; cout << &(x+y) << endl; cout << &(fmin(x, y)) << endl; cout << &string("11111") << endl;

值得一提的是,左值的英文简写为lvalue,右值的英文简写为rvalue。传统认为它们分别是leftvalue、right value 的缩写。现代C++中,lvalue 被解释为loactor value的缩写,可意为存储在内存中、有明确存储地址可以取地址的对象,而 rvalue 被解释为 read value,指的是那些可以提供数据值,但是不可以寻址,例如:临时变量,字面量常量,存储于寄存器中的变量等, 也就是说左 值和右值的核心区别就是能否取地址。

3.2 左值引用和右值引用

1.Type& r1 = x; Type&& rr1 = y; 第一个语句就是左值引用,左值引用就是给左值取别名,第二个就是右值引用,同样的道理,右值引用就是给右值取别名。

int main() { // 左值:可以取地址 // 以下的p、b、c、*p、s、s[0]就是常⻅的左值 int* p = new int(0); int b = 1; const int c = b; *p = 10; string s("111111"); s[0] = 'x'; double x = 1.1, y = 2.2; //左值引用给左值取别名 int& r1 = b; int *&r2 = p; int &r3 = *p; string& r4 = s; char &r5 = s[0]; return 0; }

 右值引用给右值取别名

int && rr1= 10; int && rr2 = x + y; double && rr3 = fmin(x, y); string && rr4 = string("11111");

2.左值引用不能直接引用右值,但是const左值引用可以引用右值

//const左值引用可以引用右值 const int& rr5 = 10; const int& rr6 = x + y; const double& rr7 = fmin(x, y); const string& rr8 = string("11111");

3.右值引用不能直接引用左值,但是右值引用可以引用move(左值)

//右值引用move左值 int&&rrx1= move(b); int*&&rrx2= move(p); int&&rrx3= move(*p); string&&rrx4= move(s); char&&rrx5= move(s[0]);

4.template <class T> typename remove_reference<T>::type&& move (T&&arg);  move是库里面的一个函数模板,本质内部是进行强制类型转换,当然他还涉及一些引总折叠的知识,这个后面会细讲。5. 需要注意的是变量表达式都是左值属性,也就意味着一个右值被右值引用绑定后,右值引用变量变量表达式的属性是左值

// b、r1、rr1都是变量表达式,都是左值 cout<<&b<<endl; cout<<&r1<<endl; cout << &rr1 << endl; // 这⾥要注意的是,rr1的属性是左值,所以不能再被右值引⽤绑定,除⾮move⼀下 int& r6 = r1; //int&& rrx6 = rr1;--报错 int&& rrx6 = move(rr1);

3.3 引用延长生命周期

右值引用可用于为临时对象延长生命周期,const 的左值引用也能延长临时对象生存期,但这些对象无法被修改。

int main() { string s1="hello"; //string&& s2 = s1;//不能右值引用绑定左值 const string& s2 = s1 + s1; // OK:到 const 的左值引⽤延⻓⽣存期 // r2 += "Test"; // 错误:不能通过到 const 的引⽤修改 string&& r3 = s1 + s1; // OK:右值引⽤延⻓⽣存期 r3 += "Test"; // OK:能通过到⾮ const 的引⽤修改 cout << r3 << '\n'; return 0; }

3.4 左值和右值的参数匹配

1.C++98中,实现一个const左值引用作为参数的函数,那么实参传递左值和右值都可以匹配。2. C++11以后,分别重载左值引用、const左值引用、右值引用作为形参的f函数,那么实参是左值会匹配f(左值引用),实参是const左值会匹配f(const 左值引用),实参是右值会匹配f(右值引用)。注意右值引用变量在用于表达式时属性是左值,这个设计这里会感觉跟怪,下一博客讲右值引用的使用场景时,就能体会这样设计的价值了

void fun1(int& x) { cout<< "左值引用重载func1(int&x)" << endl; } void fun1(int&& x) { cout << "右值引用重载func1(int&&x)" << endl; } void fun1(const int& x) { cout << "左值引用重载func2(const int&x)" << endl; } int main() { int x = 10; const int y = x; fun1(x); fun1(10); // 如果没有 fun1(int&&) 重载则会调⽤ fun1(const int&) fun1(y); // 右值引⽤变量在⽤于表达式时是左值 int&& r = 10; fun1(r); fun1(move(r)); return 0; }

结束语

本节内容就到此结束了,C++11的知识还有很多,下篇博客我们将详细讲解右值引用和移动语义的应用!!! 

Read more

Spring Cloud 概述

目录 微服务 单体架构 集群和分布式架构 横向扩展 纵向扩展 微服务架构 Spring Cloud 什么是 Spring Cloud Spring Cloud 版本 Spring Cloud 实现方案 服务拆分 服务拆分原则 简单示例 服务拆分 数据准备 工程搭建 父工程创建 子项目创建-商品服务 子项目创建-订单服务 远程调用 在学习 Spring Cloud 之前,我们先来了解一下什么是微服务,以及微服务的发展历史。架构发展的过程中,遇到了哪些问题?是如何解决的?Spring Cloud 解决了其中的什么问题? 微服务 单体架构 所有功能模块(如用户管理、订单处理)打包在一个应用中,共享同一数据库,模块间通过函数调用直接通信,开发、测试、

By Ne0inhk

ClawdBot保姆级教学:解决Gateway not reachable错误的5种方法

ClawdBot保姆级教学:解决Gateway not reachable错误的5种方法 1. 什么是ClawdBot?——你的本地AI助手,不是云端玩具 ClawdBot 是一个真正属于你自己的个人 AI 助手。它不依赖远程API、不上传隐私数据、不按调用次数收费——所有推理都在你自己的设备上完成。你可以把它理解成“装在你电脑里的 Siri + Copilot + Notion AI 的混合体”,但更自由、更透明、更可控。 它的核心能力由 vLLM 提供支撑。vLLM 是当前最高效的开源大模型推理引擎之一,以极高的吞吐量和极低的显存占用著称。ClawdBot 利用它来加载和运行像 Qwen3-4B-Instruct 这样的轻量级但能力扎实的模型,让你在消费级显卡(甚至带显存的笔记本)上也能获得接近专业服务的响应速度和对话质量。 和那些动辄要填 API Key、绑定手机号、看广告才能用的 Web 应用不同,ClawdBot 的哲学是:“你装,你用,

By Ne0inhk
Flutter for OpenHarmony:Flutter 三方库 postgrest — 鸿蒙端直接访问 PostgreSQL 数据库的极速连接器

Flutter for OpenHarmony:Flutter 三方库 postgrest — 鸿蒙端直接访问 PostgreSQL 数据库的极速连接器

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 在开发 Flutter for OpenHarmony 应用时,传统的“端-接口-数据库”模式往往显得过于沉重。 如果只是为了实现基础的增删改查,却需要编写大量的后端 API 逻辑、处理复杂的 SQL 拼写以及繁琐的 JSON 打包,这不仅增加了开发成本,也导致系统在面对业务变动时极其脆弱。 postgrest 正是解决这一痛点的利器。它是专门为 PostgREST(一个能将 PostgreSQL 数据库直接转换为 RESTful API 的高性能网关)打造的 Dart 客户端驱动。通过它,开发者可以在鸿蒙端以类似于编写 SQL 的语义,直接完成对云端数据库的高级检索与操作。 今天,我们将深入探讨如何利用该库在鸿蒙平台上实现“零接口开发”的数据交互体验。 一、原理解析 / 概念介绍

By Ne0inhk
选择Rust的理由:从内存管理到抛弃抽象

选择Rust的理由:从内存管理到抛弃抽象

引言:编程世界的革命性思维 作为一名程序员,你一定遇到过这些糟心时刻: * 程序运行到一半突然崩溃,提示"段错误" * 多线程环境下数据莫名其妙被改乱 * 内存使用量不断增长,最后程序因为内存不足而崩溃 这些问题在C/C++中很常见,但在Rust中,它们大多在编译阶段就被消灭了!秘诀就是Rust的所有权系统和零成本抽象哲学。 第一部分:惊奇的内存管理新思维 内存管理的演进:从手动到自动 编程语言的内存管理主要有三种方式: 1. 手动管理(C/C++):程序员自己分配和释放内存,容易出错 2. 垃圾回收(Java/Go/Python):运行时自动回收不再使用的内存,但有性能开销 3. 所有权系统(Rust):编译时通过规则保证内存安全,无运行时开销 Rust选择了第三条路,通过编译时的严格检查,既保证了安全,又获得了性能。 环境搭建:在Windows + VSCode中搭建Rust游乐场 安装Rust 1.

By Ne0inhk