C++11


C++11简介

C++11作为C++比较著名的版本,这篇文章介绍一下其中几个语法。

列表初始化

C++11支持一种新的初始化方式,利用花括号来进行初始化。

//内置类型
int a = {1};
int b{1};
int arr1[] = {1,2,3,4};
int arr2[]{1,2,3,4};
//容器
vector<int> v1{1,2,3,4};
vector<int> v2 = {1,2,3,4};
map<int,int> m1{{1,1},{2,2},{3,3}};
map<int,int> m2 = {{1,1},{2,2},{3,3}};

那么,对于自定义类型的容器,是如何实现花括号初始化的呢?实际上,C++11新增加了一个类,initializer_list,这个类的对象就是花括号形状的。

//i1和i2类型相同。
auto i1 = {10,20,30};
initializer_list<int> i2 = {10,20,30};

这就很好理解了,只需要把各个容器的构造函数重载一个initializer_list为参数的就好了,像这样

vector(const initializer_list<T>& il)
 :_capacity(il.size())
 ,_size(0)
{
   _array = new T[_capacity];
   for(auto e : il)
   {
     _array[size++] = e;
   }
}

其他容器也像这样。

变量类型推导 RTTI

auto

auto会自动推导出变量类型,使用起来灰常easy。

int a = 0;
auto b = a;
//也可以这样
auto& c = a; //这样c的类型就是 int&

auto真正好用的地方是容器对象的类型。

std::map<std::string, std::string> m = {{"apple","苹果"}{“orange”,"橘子"}};
std::map<std::string, std::string>::iterator it = m.begin();//繁琐的一比
auto it = m.begin(); //C++11写法

虽然这样会简化代码,但是也不能随意简化。如果上面的代码没有给出m的类型,我直接auto,会让看代码的人很疑惑。所以,一般我们在上面的代码有定义过,下面才能简化。

  • 几个小tips

auto推导出来的对象和普通对象没有任何区别
auto不能作为形参和返回值

auto func(auto l) //2个wrong,推导l的类型是错的,推导func返回值的类型也是错的

decltype

根据变量类型作为新变量的类型。
使用起来也很简单。

int a = 0;
decltype(a) b;//用a的类型作为b的类型
int b = 0;
decltype(a+b) c; //用a+b表达式的类型作为c的类型

默认成员函数控制

C++的每个类有6个默认的成员函数,如果你不写编译器会自动生成。一但你写了,编译器就不再生成。但有时候我们需要编译器帮助我们默认生成。

string
{
public:
     string(const char* str)//构造函数
      :_str(new char[strlen(str) + 1])
     {
        strcpy(_str, str);
     }
private:
  char* _str;
];

像上面的代码,虽然我们写了一个构造函数,但是没有默认的构造函数,我们自己又不想写,C++11可以这样,

string() = delault; //默认构造函数由编译器生成,即使我们已经写过一个构造函数

有时候我们写一个类,但是又不想这个类能够拷贝构造,传统的做法是,只给声明不给定义,

string(const string& s); //只给声明,没有定义。

但是别人可以在类外面给定义,所以我们一般把它放在私有中,

private:
 string(const string& s);

C++11中的做法更为简单,

string(const string& s) = delete;

这行代码的意思是,拷贝构造函数是删除函数,无法使用。C++标准库中的有些类就是这么做的,不允许拷贝构造,只允许移动构造(下面会说这个定义)。
除了这个用法之外,还有两个其他用法(这两个用法来自陈皓大佬的博客C++11中值得关注的几大变化)
你的对象只能在栈上生成,

class A
{
 public:
  void* operator new(size_t) = delete;
}

防止函数参数的隐式转化,

class A
{
public:
  void Func(int a){}
  void Func(double) = delete; //如果你传一个double的参数,则会报错,编译器不会
   //将double转化成int。
}

右值引用和移动语义

首先提出一个问题,什么叫做移动呢?这么简单的概念大家不会不知道吧?在大厅有一台电视机,平时没人用,我把它移动到我的卧室,我直接把它搬走。但是有人偏偏不这么做,我先用高科技复制一台一模一样的电视机,然后把复制的搬到卧室,最后销毁原先的电视机。我们发现第二种方法是一种非常蠢的方法,而这就是C++以前的拷贝构造和拷贝赋值。
为了更好的进行资源利用,也就是利用好“没人要的东西”,C++11提出了右值引用的概念。
我们最先接触的是左值引用,实际上左值引用只是大部分情况下引用左值,也能引用右值。
这里我们需要注意,右值引用只是大部分情况下引用右值,它也能引用左值。
还有一个重要点是我们需要区分的,什么是右值,什么是左值呢?
一般来说能被取地址的都是左值,常量和临时对象都是右值。

int a = 0, b = 1;
int& ar = a; //左值引用左值
const int& r1 = 0; //0是右值,const引用可以引用它

int&& r2 = 0; //右值引用
int&& r3 = move(a); //右值引用可以引用move过后的左值。 

实际上这都不是重点,C++11引用每个feature都有目的,引入右值引用最大的好处是减少资源消耗。我们先将右值做更仔细的区分:
右值又分为纯右值和将亡值。纯右值就是内置类型的常量,自定义类型的字面量。
将亡值像它的名字一样就是即将灭亡的值,是自定义类型的临时变量。那么这种值反正马上都要走了,它的资源我直接抢过来,我就不用开新空间了,这样节省了资源。
我们知道C++的每个类应该有拷贝赋值和拷贝构造函数。我们用string的简易类类来举栗子。

string 
{
public:
  string(const char* str = "")//构造函数
   : _str(new char[strlen(str) + 1])
  {
     strcpy(_str, str);
  }
  
  string(const string& s) //拷贝构造
    :_str(nullptr)
  {
     string tmp(s._str);
     swap(_str,tmp._str);
  }
  string& operator=(string s)
  {
     if(s != *this) 
       swap(_str,s._str);
      return *this; 
  }
  //剩下的成员函数不再展现
private:
char* _str;
};

我们这样使用,

string s1("hello");
string s2(s1); //拷贝构造
string s3;
s3 = s1;     //拷贝赋值
//如果这样呢?
string s4(string()); //用一个匿名对象去构造s4,但是这个匿名对象执行完这一句代码就
//销毁了,而拷贝构造是一个非常消耗效率的工作,尤其是深拷贝。

基于上面的问题,给出了这样版本的移动构造和移动赋值。

string(string&& s)
:_str(nullptr)
{
  swap(_str,s._str);
]

string operator=(string&& s)
{
   if(s != *this)
    swap(_str, s._str);
    return *this;
}

我们可以看到,右值引用版本的构造和赋值少了拷贝构造,我才不管你,直接把你的资源交换过来给我用,我就不用另外开空间。

右值引用和函数传参

我们都知道用传值的方式传参数,如果是内置类型还好,如果是自定义类型就会拷贝构造,甚至是深拷贝,这个代价太大了。而有了右值引用后,就会减少拷贝构造,只要你是传的将亡值,我就可以移动构造你,随便用你的资源。

vector<string> v;
v.push_back(string("CSDN"));//这个传参会匹配移动构造,减少拷贝

右值引用和返回值

当函数以传值返回的时候,我们知道编译器会拷贝一个对象返回,这个代价是很大的,如果你在公司写下面这样的代码,你可能会卷铺盖走人,

vector<vector<string>> Func()
{
    vector<vector<string>> tmp;
    // 一系列操作
    return tmp;
}

返回的时候会拷贝一个vector<vector<string >>,这简直不敢想象!!
在C++11之前,如果我们想返回一个很“大”的对象,我们一般这样干,

void Func(const vector<vector<string>>& v)
{
  // 一系列操作
}

或者传指针过来,一般这种参数叫做输出型参数,这就保证我们对v的修改对外面函数的v也有效果,但这种方法有明显的缺陷,我们只知道v的类型,对于v的其他信息我们一无所知。
C++11对于右值引用的增加也减少了拷贝。

vector<vector<string>> Func()
{
    vector<vector<string>> tmp;
    // 一系列操作
    return tmp; //大胆的传值返回,C++11后你不用卷铺盖走人了。
}

在这里要注意的是,tmp仍然是个左值,但是出了作用域tmp马上销毁,编译器仍然会按照移动语义来将tmp的值移动构造返回,这样大大减少了传值返回的资源消耗。

lambda表达式

C++11新增了一个东西叫做lambda表达式。学这个东西的第一步应该是记住它的拼写,毕竟在朋友面前装C++学的很好,没有几个高端词汇怎么能唬住人呢?
lambda表达式有数学来源,我就不做介绍,直接先来语法。

//lambda有以下几个组成
 //[捕捉列表](参数列表)mutalbe->返回值类型{表达式内容}
 //捕捉列表就是捕捉同局域的对象,可以不捕捉,
 //但是[]必须有,这是编译器用来识别lambda的
 int a = 0, b = 1;
//我们先不考虑mutable,那么下面就是一个比较完整的lambda表达式
 [](int x1, int x2)->int{return x1 + x2;};
 //实际上我们可以这样
 [](int x1, int x2){return x1 + x2;}; //返回值让编译器自己推导
 //但是有一个问题,这玩意怎么使用?
 auto add1 = [](int x1, int x2){return x1 + x2;};
 add1(a, b); //这样调用
 //如果我们只想实现a + b,我们可以这样
 auto add2 = [a,b]{return a + b};
 add2(); //这样调用

实际上lambda表达式与正常的函数相比,写起来只有几处不同。

int a1 = 1, a2 = 2, a3 = 3;
//假设我们定义了很多变量,都想捕获,可以这样
[=]{return a1 + a2 + a3;}; //表示捕获所有同局域变量
//我们还可以引用捕获
[&a1, &a2, &a3]{return a1 + a2 + a3;};
//更easy
[&]{return a1 + a2 + a3;}; //引用捕获全局域所有变量
//或者下面这样,只写捕捉列表了
//[&, a3] ,[=, &a3]

如果我们想改变a的值,

int a = 0;
[a]{a = 1;}; //发现这个错误,
[a]()mutable{a = 1;}; //这样a就可以改动,但是最上面的a还是0.
//传值的lambda实际上就是拷贝,而且传值的lambda是无法修改值的。mutable加上才可以修改
[&a]{a = 1;}; //这样才对

上面一大堆实际上没什么大用,主要是lambda的语法。我们说了C++引用的任何语法都有其目的,lambda就是为了弥补仿函数而提出的。
我们来看这个问题:

在这里插入图片描述
上面是淘宝网的一张截图(贵的一比),可以看到有好多种排序方法,可能把手机封装
成了一个个类,可能是这样的

struct smart_plone
{
 string _name; //手机名字
 int _sale;    //销量
 int _credit;  //信用
 int _price;   //价格
};

然后这样的,

vector<smart_phone> v{{"huawei",777,666,888},{"小米",888,666,777},{"pingguo",666,777,888}};

但是我事先不知道你要怎样排序,那怎么办?我们知道排序要传仿函数过去,但是我不是要写好几个仿函数吗?麻烦的一比,我们利用lambda表达式来简化

sort(v.begin(),v.end(),[](const smart_phone& sp1,const smart_phone& sp2){return sp1._sale > sp2._sale;}); //按照销量排名

这样写简单,而且一眼就能看出来你是按照什么排名的。但是有一个问题又随之出现,sort第三个参数一般是仿函数对象,这个lambda是怎么传过去的?难道,lambda也是一个对象?雀氏啊!!!
当你写一个lambda表达式,编译器会偷偷的写一个仿函数,然后把lambda换成这个仿函数的对象并且重写了operator(),lambda的参数和函数体跟lambda的一样,然后就像仿函数的用法一样,去调用operator()。所以,lambda表达式实际上是一个匿名对象,

//这行代码实际上是用一个匿名对象构造一个对象。add是一个对象。
auto add = [](int x1, int x2){return x1 + x2;};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
VS Code是一种轻量级代码编辑器,可以用来编写C++程序。要在VS Code中使用C++ 11,你需要进行一些设置和配置。 首先,你需要安装C++扩展。打开VS Code,点击左侧的扩展图标,搜索并安装"C++"扩展。安装完成后,你可以在扩展栏中看到C++扩展的图标。 接下来,你需要配置编译器。在VS Code中,你可以使用GCC或Clang作为C++编译器。如果你已经安装了GCC或Clang,你可以打开VS Code的设置(快捷键Ctrl + ,),搜索"cpp",找到"C++: Default"配置项,并将其设置为你所使用的编译器。 对于C++ 11,你需要在代码中添加一些特定的编译选项。你可以在VS Code的工作区文件夹中创建一个名为".vscode"的文件夹,并在其中创建一个名为"tasks.json"的文件。在"tasks.json"中,你可以定义编译任务,并指定编译选项。例如,以下是一个使用GCC编译器和C++ 11的示例配置: ```json { "version": "2.0.0", "tasks": [ { "type": "shell", "label": "build", "command": "gcc", "args": [ "-std=c++11", "-o", "${fileDirname}/${fileBasenameNoExtension}", "${file}" ], "group": { "kind": "build", "isDefault": true } } ] } ``` 保存并关闭"tasks.json"文件。现在,你可以使用快捷键Ctrl + Shift + B来构建(编译)你的C++代码。构建完成后,你可以在相同的文件夹中找到生成的可执行文件。 希望这能帮到你!如果有任何进一步的问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值