C++11介绍
国际标准化组织于2011年9月1号出版发布ISO/IEC 14882:2011,称为C++11。相比较C++98而言,C++11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更强大,而且能提升程序员的开发效率。
1.统一的初始化
什么叫做统一的初始化呢?例如:我们以前在往vector中插入数据的时候,只能一个元素一个元素调用push_back进行插入,现在我们可以想数组初始化的方式一样来给vector进行初始化,例如:
vector<int> v{1,2,3,4};
2.关键字auto和decltype
在以前我们在C语言的见过auto这个关键字,使用auto定义的变量,我们成为自动变量(具有自动存储器的局部变量);
在C++11中,它被赋予了新的含义,auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。 (在这里想到了我们的python大法,定义变量从来不用写类型,这些都是编译器自动去识别的) ,来看一个例子:
auto dit2 = dict.begin();
while (dit2 != dict.end())
{
cout << dit2->first << ":" << dit2->second << endl;
++dit2;
}
for (auto kv : dict)
{
cout << kv.first << ":" << kv.second << endl;
}
使用auto需要注意以下几点:
1.auto变量必须在声明的时候对它进行初始化,在编译期编译器要根据初始表达式来推导auto的实际类型,因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。
2.auto不能做函数的形参
3.auto不能定义类的非静态成员变量
4. 不能用auto来声明数组
5.不能用auto来实例化模板
6.对于具有const属性的变量,auto可以带不过来这个属性(如:const int a=10; auto b=a;)b不具备a的const属性
7.对于valatile修饰的变量,auto可以带走这个属性
8.想要用auto在一行同时声明多个变量,这些变量必须具有相同类型,否则编译器会报错
总结一下上述内容:使用auto的好处就是使代码变得简洁,坏处是违背了传统C语言用法,可读性较差
decltype可以用来推导函数的返回类型:1. 如果没有带参数,推导函数的类型
2.如果带参数列表,推导的是函数返回值的类型
decltype推导的四个规则
1.单个标记符表达式和类成员访问符,推导为本类型
2.将亡值,推导为类的引用
3.左值,推导为类型的引用
4.以上都不是,推导为本类型
3.lambda表达式
在计算机科学领域,lambda被用来表示一种匿名的函数。lambda函数跟普通函数相比不需要定义函数名,取而代之的多了一对方括号[],此外lambda函数还采用了追踪返回值类型的方式声明其返回值。
lambda表达式的定义格式如下:
最简单的lambda表达式可以这样来写:auto lam=[]{};什么事也做不了,参数列表和返回值类型可以省略
关于lambda表达式的说明:1.捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为 lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
2.参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略
3.可以在->之前添加mutable关键字:默认情况下,lambda函数总是一个const函数,mutable可
以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)
4.返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类 型明确情况下,也可省略,由编译器对返回类型进行推导。
5.函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。 捕捉列表有以下形式:
[var]:表示值传递方式捕捉变量var
[=]:表示值传递方式捕获所有父作用域中的变量(包括this)
[&var]:表示引用传递捕捉变量var
[&]:表示引用传递捕捉所有父作用域中的变量(包括this)
[this]:表示值传递方式捕捉当前的this指针
注意:这里的父作用域指包含lambda函数的语句块
语法上捕捉列表可由多个捕捉项组成,并以逗号分割。比如:
[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量
[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量
注意:捕捉列表不运行变量重复传递,否则就会导致编译错误。
[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
lambda函数的使用场景比较特殊:比如打印一些内容状态,或者进行一些内部操作,这些功能不能与其他的代码共享,却要在一个函数中多次重用。在lambda没有引入前,我们只能封装函数来实现,出于函数作用域及运行效率考虑,此函数通常还需加上static和inline关键字。但lambda的引入,其捕捉列表的功能,使我们不用考虑参数个数以及传递方式,而且主调函数结束函数,lambda函数也结束,不会影响命名空间中的其他东西,使代码的实现更加简答,可读性更高。
double rate = 1.2;
// 最简单的lambda表达式
auto lam = []{};
auto add3 = [](int x, int y, double rate)->double{return (x + y)*rate; };
cout << add3(1, 2, rate) << endl;
//auto add4 = [=](int x, int y)->double{return (x + y)*rate; };
//auto add4 = [&](int x, int y)->double{return (x + y)*rate; };
auto add4 = [rate](int x, int y)->double{return (x + y)*rate; };
//auto add4 = [&rate](int x, int y)->double{return (x + y)*rate; };
cout << add4(1, 2) << endl;
int a = 10, b = 20;
auto swap1 = [](int& x1, int& x2){int x = x1; x1 = x2; x2 = x; };
swap1(a, b);
cout << a << " " << b << endl;
auto swapab = [&a,&b](){int x = a; a = b; b = x; };
swapab();
cout << a << " " << b << endl;
auto swap3 = [&](){int x = a; a = b; b = x; };
swap3();
cout << a << " " << b << endl;
3.默认函数的控制
有时候我们可能会遇到这样一个情况
#include<iostream>
#pragma warning(disable:4996)
using namespace std;
class String{
public:
String(const char* str)
:_str(new char[strlen(str)+1])
{
strcpy(_str,str);
}
private:
char* _str;
};
int main()
{
String s1("hello");
String s2;
system("pause");
return 0;
}
我们都知道,这个代码肯帝是编不过的,为什么呢?因为我们自己实现了一个构造函数出来,就不会去调用系统默认生成的构造函数了,并且没给这个函数指定默认的参数,所以我们想去定义S2这样一个没有参数的对象的时候.
现在我们不想看到这个报错怎么办呢?在C++11中提供了这样一个东西,显式缺省函数,可以在默认函数定义或者声明时加上=default,从而显式的指示编译器生成该函数的默认版本,用=default修饰的函数称为显式缺省函数。如下:
String()=default;
显式缺省不仅可以在类中修饰成员函数,也可以在类定义外修饰成员函数。这样做的好处是:可以对一个类实现多个版本,程序员可以选择所需要的版本进行编译。
另一方面,程序员可能想要限制某些默认函数的生成,最典型的可能想要禁止生成默认的拷贝构造函数。在C++98中,是将拷贝构造函数设置成private的,只声明不给定义,这样只要其他人想要调用就会报错。 在C++11中,有更简单的方法,即在函数定义或者声明时加上=delete,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。
class A
{
public:
A(int a)
: _a(a)
{}
A(const A&) = delete;
private:
int _a;
};
C++11致力于消除C++中的模糊定义,明确程序员与编译器之间的交流。通常情况下,将基类中的某些成员函数声明成虚函数,然后派生类对其进行重写,即可实现多态。但有时候我们并不想让其在派生类中被重写,那C++98没有很好的解决方式,C++11中提出了final关键字 当final修饰虚函数时,子类就不可以再重写这个虚函数。final修饰类时,表明该类不能被继承;
有时候,在重写基类的某些虚函数时,如果不小心(比如:参数不小心写错),或者类继承比较多,设计比较复杂,可能会造成同名隐藏或者函数名字书写错误而造成重写失败。为了确保子类方法就是对基类某个方法的重写,C++11引入了override关键字。
class Base
{
public:
virtual void Func1()
{
/*
override的出现,在重写时如果出现拼写错误,类型与基类不同,重写了
基类的非虚函数等,override可以保证编译器辅助的做一些检查。
注意:final/override也可以定义为正常的变量名,只有出现在类和函数
之后才表示不能被继承或者是否正确重写。这样设计C++98的代码就可以
通过编译,但是建议尽可能避免这样的变量名称。
继承构造函数
C++中自定义类型类是C++面向对象的基石,类具有可派生性,派生类可
以继承基类的成员变量和成员函数。但基类中有些函数却难以继承,比如
构造函数。如果派生类要使用基类的构造函数,必须在其构造函数初始化
列表位置显式调用。
*/
cout << "Base::Func1()" << endl;
}
void Func2()
{
cout << "Base::Func2()" << endl;
}
};
class Derived : public Base
{
public:
virtual void Func1(int)override
{
cout << "Derived::Func1()" << endl;
}
virtual void FunC()override
{
cout << "Derived::FunC()" << endl;
}
void Func2()override
{
cout << "Derived::Func2()" << endl;
}
};
override的出现,在重写时如果出现拼写错误,类型与基类不同,重写了基类的非虚函数等,override可以保证编译器辅助的做一些检查。
注意:final/override也可以定义为正常的变量名,只有出现在类和函数之后才表示不能被继承或者是否正确重写。这样设计C++98的代码就可以通过编译,但是建议尽可能避免这样的变量名称。