C++11常用的一部分新特性(中)

简介: C++11常用的一部分新特性(中)

右值引用引用左值及其一些更深入的使用场景分析

来看这样一段代码:

#include<iostream>
#include<cassert>
#include<algorithm>
using namespace std;
namespace baiye
{
  class string
  {
  public:
    typedef char* iterator;
    iterator begin()
    {
      return _str;
    }
    iterator end()
    {
      return _str + _size;
    }
    string(const char* str = "")
      :_size(strlen(str))
      , _capacity(_size)
    {
      //cout << "string(char* str)" << endl;
      _str = new char[_capacity + 1];
      strcpy(_str, str);
    }
    // s1.swap(s2)
    void swap(string& s)
    {
      std::swap(_str, s._str);
      std::swap(_size, s._size);
      std::swap(_capacity, s._capacity);
    }
    // 拷贝构造
    string(const string& s)
      :_str(nullptr)
    {
      cout << "string(const string& s) -- 深拷贝" << endl;
      string tmp(s._str);
      swap(tmp);
    }
    // 赋值重载
    string& operator=(const string& s)
    {
      cout << "string& operator=(string s) -- 深拷贝" << endl;
      string tmp(s);
      swap(tmp);
      return *this;
    }
    ~string()
    {
      delete[] _str;
      _str = nullptr;
    }
    char& operator[](size_t pos)
    {
      assert(pos < _size);
      return _str[pos];
    }
    void reserve(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;
        reserve(newcapacity);
      }
      _str[_size] = ch;
      ++_size;
      _str[_size] = '\0';
    }
    //string operator+=(char ch)
    string& operator+=(char ch)
    {
      push_back(ch);
      return *this;
    }
    const char* c_str() const
    {
      return _str;
    }
  private:
    char* _str;
    size_t _size;
    size_t _capacity; // 不包含最后做标识的\0
  };
}
namespace baiye
{
  baiye::string to_string(int value)
  {
    bool flag = true;
    if (value < 0)
    {
      flag = false;
      value = 0 - value;
    }
    baiye::string str;
    while (value > 0)
    {
      int x = value % 10;
      value /= 10;
      str += ('0' + x);
    }
    if (flag == false)
    {
      str += '-';
    }
    reverse(str.begin(), str.end());
    return str;
  }
}
int main()
{
  baiye::string str = baiye::to_string(-1234);
  return 0;
}

先看main函数中创建str然后调用to_string函数返回一个string类型赋值给str。

这里编译器优化就变成了拷贝构造。

这样就少了一次拷贝构造。

但如果是这种情况就无法进行优化。

那么这种情况下C++11是怎么解决问题的呢?

// 移动构造
    string(string&& s)
      :_str(nullptr)
      , _size(0)
      , _capacity(0)
    {
      swap(s);
    }

如果传的参数是右值就会走这个函数。

注意:C++11给右值分为

纯右值(内置类型)

将亡值(自定义类型)

那么在to_string函数中返回了一个将亡值,如果在进行拷贝构造有些没必要:

那么这里在进行拷贝传值的时候就会传给移动构造函数,移动构造函数内部其实就是交换两个对象的值,反正将亡值也要销毁了,这样就不用进行深拷贝了。(深拷贝代价太大,如果深拷贝的对象是vector<vector< int>>效率就非常低了)

但是刚才这种情况还没有解决:

那么这里就可以再写一个移动赋值:

// 移动赋值
    string& operator=(string&& s)
    {
      swap(s);
      return *this;
    }

总结

右值引用是间接起作用,如果右值是将亡值,那么就转移资源。

这里用vector举例:如果传进去的是右值,就会走这个接口,会提升效率。

**注意:**右值引用被引用一次之后,引用的这个别名就变成了左值。

如果不变成左值怎么传给swap。

完美转发

万能引用

#include<iostream>
using namespace std;
template<class T>
void func(T&& x)//这里也可以称为引用折叠,如果传的是左值,就折叠成一个引用符号
{
}
int main()
{
  int x = 10;
  func(x);//左值也可以
  func(2);//右值也可以
  const int y = 20;
  func(y);//const左值
  func(move(y));//const右值
  return 0;
}

那么这个时候如果func函数中要去调用这四个函数,结果是怎么样的呢?

#include<iostream>
using namespace std;
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }
template<class T>
void func(T&& x)
{
  Fun(x);
}
int main()
{
  int x = 10;
  func(x);//左值
  func(2);//右值
  const int y = 20;
  func(y);//const左值
  func(move(y));//const右值
  return 0;
}

这里只会调用前两个函数,因为func中的参数x都是左值属性,这里就需要一个叫完美转发的在传参的过程中保持了 x 的原生类型属性。

新的类功能

默认成员函数

C++11 新增了两个默认成员函数:移动构造函数和移动赋值运算符重载。

针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:

如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。

如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)

如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

default与delete

强制生成默认函数的关键字default:

C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原

因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以

使用default关键字显示指定移动构造生成。

#include<iostream>
using namespace std;
class Person
{
public:
  Person(const char* name = "", int age = 0)
    :_name(name)
    , _age(age)
  {}
  Person(const Person& p)
    :_name(p._name)
    , _age(p._age)
  {}
  Person(Person && p) = default;//强制生成
private:
  string _name;
  int _age;
};
int main()
{
  Person s1;
  Person s2 = s1;
  Person s3 = std::move(s1);
  return 0;
}

禁止生成默认函数的关键字delete:

如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁

已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即

可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。

#include<iostream>
using namespace std;
class Person
{
public:
  Person(const char* name = "", int age = 0)
    :_name(name)
    , _age(age)
  {}
  Person(const Person& p) = delete;
private:
  string _name;
  int _age;
};
int main()
{
  Person s1;
  Person s2 = s1;
  Person s3 = move(s1);
  return 0;
}

这样吴凯伦是内部和外部都无法使用这个拷贝构造函数了。

可变参数模板

参数包

这个也是为了对标C语言的可变性参数,比如printf和scanf。

#include<iostream>
using namespace std;
// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}
int main()
{
  return 0;
}

上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数

包”,它里面包含了0到N(N>=0)个模版参数。我们无法直接获取参数包args中的每个参数的,

只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特

点,也是最大的难点,即如何展开可变模版参数。

如何查看参数包有几个参数呢?

#include<iostream>
using namespace std;
template <class ...Args>
void ShowList(Args... args)
{
  cout << sizeof...(args) << endl;//查看参数包有几个参数
}
int main()
{
  ShowList(1);
  ShowList(1, 2.2);
  ShowList(1, 2.2, string("xxx"));
  return 0;
}

遍历参数包中的参数

递归函数方式展开参数包

#include<iostream>
using namespace std;
// 递归终止函数
void ShowList()//当参数包中的参数变成0个的时候就会调用这个函数
{
  cout << endl;
}
// 展开函数
template <class T, class ...Args>
void ShowList(T value, Args... args)//第一个参数传给value,剩下的参数传给args参数包
{
  cout << value << " ";
  ShowList(args...);
}
int main()
{
  ShowList(1);//这里1传给value,然后参数包没有参数调用终止函数
  ShowList(1, 2.2);//这里第一次1传给value,2.2传给参数包,第二次2.2传给value,参数包没有值,调用终止函数
  ShowList(1, 2.2, string("xxx"));
  return 0;
}

非常的怪异。

逗号表达式展开参数包

#include<iostream>
using namespace std;
template <class T>
void PrintArg(T t)
{
  cout << t << " ";
}
//展开函数
template <class ...Args>
void ShowList(Args... args)
{
  int arr[] = { (PrintArg(args), 0)... };
  cout << endl;
}
int main()
{
  ShowList(1);
  ShowList(1, 'A');
  ShowList(1, 'A', std::string("sort"));
  return 0;
}

这里的arr数组是一个辅助的作用,里面调用的是PrintArg函数,编译器自行初始化arr数组,参数包中有多少个参数数组的空间就有多大。

这里的逗号表达式只是为了初始化arr数组,初始化为0。

相关文章
|
4月前
|
编译器 C++ 开发者
C++一分钟之-C++20新特性:模块化编程
【6月更文挑战第27天】C++20引入模块化编程,缓解`#include`带来的编译时间长和头文件管理难题。模块由接口(`.cppm`)和实现(`.cpp`)组成,使用`import`导入。常见问题包括兼容性、设计不当、暴露私有细节和编译器支持。避免这些问题需分阶段迁移、合理设计、明确接口和关注编译器更新。示例展示了模块定义和使用,提升代码组织和维护性。随着编译器支持加强,模块化将成为C++标准的关键特性。
176 3
|
6天前
|
存储 算法 程序员
C++ 11新特性之function
C++ 11新特性之function
18 9
|
6天前
|
编译器 C++ 计算机视觉
C++ 11新特性之完美转发
C++ 11新特性之完美转发
16 4
|
6天前
|
Java C# C++
C++ 11新特性之语法甜点1
C++ 11新特性之语法甜点1
17 4
|
6天前
|
存储 安全 C++
C++ 11新特性之unique_ptr
C++ 11新特性之unique_ptr
14 4
|
6天前
|
安全 程序员 编译器
C++ 11新特性之auto和decltype
C++ 11新特性之auto和decltype
14 3
|
6天前
|
设计模式 缓存 安全
C++ 11新特性之week_ptr
C++ 11新特性之week_ptr
13 2
|
6天前
|
编译器 C++ 容器
C++ 11新特性之语法甜点2
C++ 11新特性之语法甜点2
14 1
|
6天前
|
编译器 C++
C++ 11新特性之右值引用
C++ 11新特性之右值引用
13 1
|
6天前
|
存储 编译器 调度
C++ 11新特性之bind
C++ 11新特性之bind
下一篇
无影云桌面