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


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


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

举个栗子:


总结

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

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


目录
相关文章
|
10月前
|
安全 Java 程序员
c++异常
c++异常
63 0
|
10月前
|
C语言
通讯录(C语言) 下
通讯录(C语言)
151 0
|
10月前
|
C语言
通讯录(C语言) 上
通讯录(C语言)
151 0
|
10月前
|
存储 Serverless C++
哈希(C++)上
哈希(C++)
59 0
|
10月前
|
存储 容器
list模拟实现
list模拟实现
46 0
|
10月前
|
存储 容器
冯诺依曼体系
冯诺依曼体系
50 0
|
10月前
|
编译器
|
10月前
|
Linux 调度 C++
进程控制(Linux)上
进程控制(Linux)
47 0
|
10月前
|
编译器
|
10月前
|
存储 C语言 C++
内存管理(C/C++)
内存管理(C/C++)
76 0