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


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


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

举个栗子:


总结

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

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


目录
相关文章
|
14天前
|
人工智能 自动驾驶 大数据
预告 | 阿里云邀您参加2024中国生成式AI大会上海站,马上报名
大会以“智能跃进 创造无限”为主题,设置主会场峰会、分会场研讨会及展览区,聚焦大模型、AI Infra等热点议题。阿里云智算集群产品解决方案负责人丛培岩将出席并发表《高性能智算集群设计思考与实践》主题演讲。观众报名现已开放。
|
6天前
|
自然语言处理 数据可视化 API
Qwen系列模型+GraphRAG/LightRAG/Kotaemon从0开始构建中医方剂大模型知识图谱问答
本文详细记录了作者在短时间内尝试构建中医药知识图谱的过程,涵盖了GraphRAG、LightRAG和Kotaemon三种图RAG架构的对比与应用。通过实际操作,作者不仅展示了如何利用这些工具构建知识图谱,还指出了每种工具的优势和局限性。尽管初步构建的知识图谱在数据处理、实体识别和关系抽取等方面存在不足,但为后续的优化和改进提供了宝贵的经验和方向。此外,文章强调了知识图谱构建不仅仅是技术问题,还需要深入整合领域知识和满足用户需求,体现了跨学科合作的重要性。
|
1月前
|
存储 人工智能 弹性计算
阿里云弹性计算_加速计算专场精华概览 | 2024云栖大会回顾
2024年9月19-21日,2024云栖大会在杭州云栖小镇举行,阿里云智能集团资深技术专家、异构计算产品技术负责人王超等多位产品、技术专家,共同带来了题为《AI Infra的前沿技术与应用实践》的专场session。本次专场重点介绍了阿里云AI Infra 产品架构与技术能力,及用户如何使用阿里云灵骏产品进行AI大模型开发、训练和应用。围绕当下大模型训练和推理的技术难点,专家们分享了如何在阿里云上实现稳定、高效、经济的大模型训练,并通过多个客户案例展示了云上大模型训练的显著优势。
|
1月前
|
存储 人工智能 调度
阿里云吴结生:高性能计算持续创新,响应数据+AI时代的多元化负载需求
在数字化转型的大潮中,每家公司都在积极探索如何利用数据驱动业务增长,而AI技术的快速发展更是加速了这一进程。
|
2天前
|
人工智能 容器
三句话开发一个刮刮乐小游戏!暖ta一整个冬天!
本文介绍了如何利用千问开发一款情侣刮刮乐小游戏,通过三步简单指令实现从单个功能到整体框架,再到多端优化的过程,旨在为生活增添乐趣,促进情感交流。在线体验地址已提供,鼓励读者动手尝试,探索编程与AI结合的无限可能。
|
6天前
|
Cloud Native Apache 流计算
PPT合集|Flink Forward Asia 2024 上海站
Apache Flink 年度技术盛会聚焦“回顾过去,展望未来”,涵盖流式湖仓、流批一体、Data+AI 等八大核心议题,近百家厂商参与,深入探讨前沿技术发展。小松鼠为大家整理了 FFA 2024 演讲 PPT ,可在线阅读和下载。
3130 10
PPT合集|Flink Forward Asia 2024 上海站
|
2天前
|
人工智能 自然语言处理 前端开发
从0开始打造一款APP:前端+搭建本机服务,定制暖冬卫衣先到先得
通义灵码携手科技博主@玺哥超carry 打造全网第一个完整的、面向普通人的自然语言编程教程。完全使用 AI,再配合简单易懂的方法,只要你会打字,就能真正做出一个完整的应用。
931 12
|
19天前
|
人工智能 自然语言处理 前端开发
100个降噪蓝牙耳机免费领,用通义灵码从 0 开始打造一个完整APP
打开手机,录制下你完成的代码效果,发布到你的社交媒体,前 100 个@玺哥超Carry、@通义灵码的粉丝,可以免费获得一个降噪蓝牙耳机。
5874 16
|
1月前
|
缓存 监控 Linux
Python 实时获取Linux服务器信息
Python 实时获取Linux服务器信息
|
12天前
|
机器学习/深度学习 人工智能 安全
通义千问开源的QwQ模型,一个会思考的AI,百炼邀您第一时间体验
Qwen团队推出新成员QwQ-32B-Preview,专注于增强AI推理能力。通过深入探索和试验,该模型在数学和编程领域展现了卓越的理解力,但仍在学习和完善中。目前,QwQ-32B-Preview已上线阿里云百炼平台,提供免费体验。