C++11(一)

简介: C++11

统一的列表初始化


{ }初始化


C++98中,标准允许使用{ }对数组或者结构体对象进行统一的列表初始值设定

举个栗子


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


C++11扩大 { }符号的使用范围,使其可用于所有的内置类型和自定义类型;使用初始化列表时,可添加赋值符号(=),也可不添加


举个栗子


int main()
{
  vector<int> v = { 1,2,3,4,5 };
  int x = { 1 };
  return 0;
}


3661390a03aa8c65ecfb8a2382ab1fec_8e89394415a6497fb94f0ec5fe0f1950.png


std::initializer_list


上面之所以可以使用{ }对vector进行列表初始化,其实是因为在其构造函数中包括了使用initializer_list的构造函数


c4c70baa87832a948bd9d8d7d318f475_5c09f84455074d6cac9f893b598def97.png


initializer_list其本质是也是个容器,不同点在于并不存储数据,而是提供两个迭代器指向数据的头和尾;并且initializer_list存在于所有容器的构造函数中


27016c0d13b48a6194bfc362c55bea1f_87f8d3b75f1544669efe05b21f3e0922.png


举个栗子:判断数据类型


int main()
{
  auto il = { 1,2,3,4,5 };
  cout << typeid(il).name() << endl;
  return 0;
}

9aa71ea0b3bbbd88279444262612173d_4ca63d91ec564bc9af35d6a4b63718ef.png


将initializer_list应用到其他容器中


int main()
{
  list<int> lt = { 1,2,3 };
  map<string, string> dict = { {"东","east"},{"西","west"} };
  return 0;
}

4197afb1c24c1d35c3380cc6910e8166_8ab147eeb2cd401ca115ac2827f8aaf7.png


声明


C++11提供了多种简化声明的方式,尤其是在使用模板时


decltype


关键字decltype将变量的类型声明为表达式指定的类型


举个栗子


int main()
{
  int a = 0;
  decltype(a) b;
  cout << typeid(b).name() << endl;
  return 0;
}


e7fd74f2cd1b3d04d04a762c93a55e0f_d6310fed3bd848659b4bff5c21261232.png


decltype将变量a的类型声明为int,之后创建变量b


STL中的变化


增加新容器:unordered_map/multimap和unordered_set/multiset

已有容器新接口函数:移动构造和移动赋值;emplace_xxx插入接口或右值引用版本的插入接口


右值引用


左值引用和右值引用


左值是一个表示数据的表达式,可以通过获取它的地址/可以对它进行赋值;左值可以出现在赋值符号的左边或右边;左值引用就是给左值取别名


//p,a,b,*p都是左值
  int* p = new int;
  int a = 0;
  const int b = 1;


右值也是表示数据的表达式,例如:字符常量,函数返回值;右值只能出现在赋值符号的右边;右值引用就是给右值取别名


C++中将右值分为两种:


纯右值:内置类型表达式的值

将亡值:自定义类型表达式的值

//0,0,i+j都是右值
  int i = 0;
  int j = 0;
  int m = i + j;

注意:右值是不可以进行取地址的,但是给右值取别名后,会将右值存储到特定位置,并且可以取到该位置的地址


左值引用与右值引用比较


左值引用:


左值引用只能引用左值,不可以引用右值

被const修饰的左值即可引用左值也可以引用右值

int main()
{
    //左值只能引用左值
  int i = 0;
  int& ri = i;
  //10是右值编译报错
  int& rj = 10;
    //const修饰的左值可以引用左值和右值
  const int& ra = 10;
  const int& rb = i;
  return 0;
}

61cb02c1a6f08007b6c1b58d262d094d_b78ebfca156f47c886c4b64f149851b6.png


右值引用


右值引用只能引用右值

右值引用可以引用move之后的左值

int main()
{
  //右值引用只能引用右值
  int&& ra = 10;
  //i是左值,进行右值引用会报错
  int i = 0;
  int&& ri = i;
  //右值引用可以引用move之后的左值
  int&& rii = std::move(i);
  return 0;
}

20cc1b585d8921fc945038f21fa1990e_58687f1f11ab4734b4fdb26c11f9295d.png


右值引用使用场景和意义


上文所述中,左值引用即可引用左值又可引用右值,那么C++11为什么要提出右值引用呢???为了解决这个疑问,先来了解左值引用的意义可能会有所帮助


左值引用的意义:函数传参/函数传返回值时使用左值引用可以减少拷贝;不过这里有个前提,就是在函数栈帧销毁之后任然存在的数据才能进行引用返回,所以当待返回的数据是临时创建的变量时,就不能进行引用返回;所以不难猜测,右值引用的提出就是为了解决这个问题


接下来通过一个模拟实现string类来学习右值引用


class string
  {
  public:
  typedef char* iterator;
  iterator begin()
  {
    return _str;
  }
  iterator end()
  {
    return _str + _size;
  }
  string(const char* str = "")
    :_size(strlen(str))
    ,_capacity(_size)
  {
    _str = new char[_capacity + 1];
    strcpy(_str, str);
  }
  void swap(string& s)
  {
    std::swap(_str, s._str);
    std::swap(_size, s._size);
    std::swap(_capacity, s._capacity);
  }
  //拷贝构造
  string(const string& s)
  {
    cout << "string(const string& s)  深拷贝" << endl;
    string tmp(s._str);
    swap(tmp);
  }
  //赋值重载
  string& operator=(const string& s)
  {
    cout << "string& operator=(const string& s)  深拷贝" << endl;
    string tmp(s);
    swap(tmp);
    return *this;
  }
  ~string()
  {
    delete[] _str;
    _str = nullptr;
  }
  void reverse(size_t n)
  {
    if (n > _capacity)
    {
    char* tmp = new char[n + 1];
    strcpy(tmp, _str);
    delete[] _str;
    _str = tmp;
    _capacity = n;
    }
  }
  void push_back(char ch)
  {
    if (_size >= _capacity)
    {
    size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
    reverse(newcapacity);
    }
    _str[_size] = ch;
    ++_size;
    _str[_size] = '\0';
  }
  string& operator+=(char ch)
  {
    push_back(ch);
    return *this;
  }
  const char* c_str()
  {
    return _str;
  }
  private:
  char* _str = nullptr;
  size_t _size = 0;
  size_t _capacity = 0;
  };


通过一个函数先复习一下左值引用


string to_string(int value)
  {
  bool flag = 1;
  if (value < 0)
  {
    flag = -1;
    value = 0 - value;
  }
  string str;
  while (value > 0)
  {
    int x = value % 10;
    value /= 10;
    str += ('0' + x);
  }
  if (flag == -1)
  {
    str += '-';
  }
  reverse(str.begin(), str.end());
  return str;
  }


int main()
{
  yjm::string ret = yjm::to_string(1234);
  return 0;
}

ca4c03b0f2071513b30d224419bc90a9_f7f1f40f5fe1454788fcc0bde297204a.png


f0894c2c049b6b5891f7a86cc38fb05e_2c9f0f2eea3b46f3b1315209c0500c8f.png



没有右值引用时,返回临时变量还是会进行深拷贝,接下来看看当加上右值引用之后的结果又该如何?


int main()
{
  yjm::string s1("hello yjm");
  yjm::string s2(s1);
  //左值move之后变成右值
  yjm::string s3(move(s1));
  return 0;
}


在没有实现右值引用时,s2调用构造函数进行初始化;s3的参数虽然是右值,但是由于构造函数是const修饰的构造函数,所以也可以调用


e329b979dc4476997cf56db12e26650f_5847e6ee62db4918845f3b8901feeaed.png


如果加入参数是右值引用的构造结果会怎么样呢???


//移动构造
  string(string&& s)
  {
    cout << "string(string&& s)  移动构造" << endl;
    swap(s);
  }
  //移动赋值
  string& operator=(string&& s)
  {
    cout << "string& operator=(string&& s)  移动赋值" << endl;
    swap(s);
    return *this;
  }


a6087ef83e99f7199e78b8234cfd9d60_29a6677e25b54a65b01948880a0c9d39.png


由运行结果可知:s2中的参数是左值,故匹配参数是左值引用的构造函数即深拷贝;s3中的参数是右值,故匹配参数是右值引用的构造函数即移动构造;由于参数是自定义类型,也就是将亡值,所以进行资源转移


f0431788fb0f5192137aab2a32076410_60cf52ef474b45bcba13fa14435b25c0.png


现在回头看上面遗留的问题,当加入右值引用之后,返回临时变量是否还需要进行深拷贝呢???

39c37e3f5efacf0ca69a5fbd125a4263_77015dadb9a7425d828b04ce2f5e9be8.png


通过允许结果来看,并没有进行深拷贝,只是进行了移动构造;其中编译器也进行了优化:首先变量str先拷贝构造一份临时变量,临时变量作为右值进行移动构造,编译器进行优化,直接将str识别为右值进行移动构造


3958d93499e9d1ddd525090034ed6e37_7406d026c8f7484bb185551aa527febc.png


同理,参数为右值引用的赋值重载也是如此,不加赘述,直接看结果


int main()
{
  yjm::string ret;
  ret= yjm::to_string(1234);
  return 0;
}


c47231fa77565a58cf126c50783478ae_4db65568892c4741b8f6301a9d456c62.png


返回值先移动构造一个临时变量,临时变量作为右值再进行移动赋值


上面所学习的都是右值引用在函数返回值中的应用,其实它还可以应用到数据的插入中

举个栗子:


总结

右值引用和左值引用减少拷贝的原理不同

左值引用是取别名,直接起作用;右值引用是间接起作用,实现移动构造/移动赋值,在拷贝的过程中,如果右值是将亡值,则进行资源转移


目录
相关文章
|
3天前
|
人工智能 JSON 供应链
畅用7个月无影 JVS Claw |手把手教你把JVS改造成「科研与产业地理情报可视化大师」
LucianaiB分享零成本畅用JVS Claw教程(学生认证享7个月使用权),并开源GeoMind项目——将JVS改造为科研与产业地理情报可视化AI助手,支持飞书文档解析、地理编码与腾讯地图可视化,助力产业关系图谱构建。
23283 2
畅用7个月无影 JVS Claw |手把手教你把JVS改造成「科研与产业地理情报可视化大师」
|
4天前
|
人工智能 API 开发工具
Claude Code国内安装:2026最新保姆教程(附cc-switch配置)
Claude Code是我目前最推荐的AI编程工具,没有之一。 它可能不是最简单的,但绝对是上限最高的。一旦跑通安装、接上模型、定好规范,你会发现很多原本需要几小时的工作,现在几分钟就能搞定。 这套方案的核心优势就三个字:可控性。你不用依赖任何不稳定服务,所有组件都在自己手里。模型效果不好?换一个。框架更新了?自己决定升不升。 这才是AI时代开发者该有的姿势——不是被动等喂饭,而是主动搭建自己的生产力基础设施。 希望这篇保姆教程,能帮你顺利上车。做出你自己的作品。
7380 18
Claude Code国内安装:2026最新保姆教程(附cc-switch配置)
|
12天前
|
缓存 人工智能 自然语言处理
我对比了8个Claude API中转站,踩了不少坑,总结给你
本文是个人开发者耗时1周实测的8大Claude中转平台横向评测,聚焦Claude Code真实体验:以加权均价(¥/M token)、内部汇率、缓存支持、模型真实性及稳定性为核心指标。
4565 24
|
8天前
|
人工智能 JSON BI
DeepSeek V4 来了!超越 Claude Sonnet 4.5,赶紧对接 Claude Code 体验一把
JeecgBoot AI专题研究 把 Claude Code 接入 DeepSeek V4Pro 的真实体验与避坑记录 本文记录我将 Claude Code 对接 DeepSeek 最新模型(V4Pro)后的真实体验,测试了 Skills 自动化查询和积木报表 AI 建表两个场景——有惊喜,也踩
3208 11
|
6天前
|
人工智能 缓存 BI
Claude Code + DeepSeek V4-Pro 真实评测:除了贵,没别的毛病
JeecgBoot AI专题研究 把 Claude Code 接入 DeepSeek V4Pro,跑完 Skills —— OA 审批、大屏、报表、部署 5 大实战场景后的真实体验 ![](https://oscimg.oschina.net/oscnet/up608d34aeb6bafc47f
2628 8
Claude Code + DeepSeek V4-Pro 真实评测:除了贵,没别的毛病
|
24天前
|
人工智能 自然语言处理 安全
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)
本文介绍了Claude Code终端AI助手的使用指南,主要内容包括:1)常用命令如版本查看、项目启动和更新;2)三种工作模式切换及界面说明;3)核心功能指令速查表,包含初始化、压缩对话、清除历史等操作;4)详细解析了/init、/help、/clear、/compact、/memory等关键命令的使用场景和语法。文章通过丰富的界面截图和场景示例,帮助开发者快速掌握如何通过命令行和交互界面高效使用Claude Code进行项目开发,特别强调了CLAUDE.md文件作为项目知识库的核心作用。
20218 61
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)