- 原始字面量
- 超长整形
- 处理时间和日期的chrono库
- final关键字和override关键字
- c++11增加新关键字auto:实现自动类型的推导
- c++11新增关键字decltype,这个关键字可以从一个变量或表达式中得到类型
- c++11引入标识空指针的关键字nullptr.
- 模板的优化
- 静态断言static_assert
- 基于范围的for循环(亦称区间迭代)
- 更加优雅的初始化方法
- c++11还引入了变长参数模板。
- lambda表达式用于创建并定义匿名的函数对象
- constexpr函数
- explicit关键字
- 利用c++11的新特性在类内初始化成员变量
- 右值引用和move(移动)语义
- c++11新增关键字noexcept:
- 标准库bind函数
- 新增函数定义语法,在函数名和参数列表后指定函数返回类型
- delete和default关键字的作用
- 委托构造函数
- 多线程
原始字面量
- 在c++11中添加了定义原始字符串的字面量,定义方式为:
R"(原始字符串)"
。原始字面量R可以直接表示字符串的实际含义,而不需要额外对字符串做转义或者连接等操作。
cout << R"(D:\clog)" << endl; // D:\clog
cout << R"(hello world)" << endl;// hello world
超长整形
- c++11标准中引入了超长整型long long类型,它可以在不同平台上有不同的长度,但是至少64位。
// 有符号超长整形
long long int a = 123LL;
// 无符号超长整形
unsigned long long int b = 123ULL;
// 最大的 long long 值
cout << LLONG_MAX << endl;
// 最小的 long long 值
cout << LLONG_MIN << endl;
// 最大的 unsigned long long 值
cout << ULLONG_MAX << endl;
- 扩展的整形。在c++11中一共只定义了以下五种标准的有符号整形。同时每一种有符号整形都有一种对应的无符号整数版本。
- signed char
- short int
- int
- long int
- long long int
处理时间和日期的chrono库
c++11中提供了日期和时间相关的库chrono,通过chrono库可以很方便的处理日期和时间。chrono库主要包含三种类型的类:时间间隔duration、时钟clocks、时间点time point
1.时间间隔duration
- duration表示一段时间间隔,用来记录时间长度,可以表示几秒、几分钟、几个小时的时间间隔。duration 的原型如下:
template<typename _Rep, typename _Period = ratio<1>>
struct duration;
_Rep:表示时钟周期的数目
_Period:表示时钟的周期(一个时钟周期有多久),其原型如下:
// 定义于头文件 <ratio>
template<
std::intmax_t Num,
std::intmax_t Denom = 1
> class ratio;
Num:代表分子
Denom:代表分母
- 标准库中定义了常用的时间间隔:
- std::chrono::nanoseconds:纳秒,原型是
duration<int64_t,ratio<1,1000000000>>;
- std::chrono::microseconds:微秒
- std::chrono::milliseconds:毫秒
- std::chrono::seconds:秒
- std::chrono::minutes:分
- std::chrono::hours:小时
- std::chrono::nanoseconds:纳秒,原型是
final关键字和override关键字
- final关键字:
- 修饰函数:如果使用final修饰函数,只能修饰虚函数。用于阻止子类重写父类中的虚函数。
- 修饰类:使用final关键字修饰的类是不允许被继承的。
class Parent { public: virtual void show() { cout << "parent" << endl; } }; // final修饰类表示Son类不能被继承 class Son final: public Parent { public: // final修饰函数表示派生于Son类的子类中不能重写show函数 virtual void show()final { cout << "Son" << endl; } };
- override关键字:确保在派生类中声明的重写函数与基类的虚函数有相同的签名,同时也明确表明将会重写基类的虚函数,这样就可以保证重写的虚函数的正确性,也提高了代码的可读性
c++11增加新关键字auto:实现自动类型的推导
使用auto关键字定义的变量必须要有初始化表达式。
#include<iostream>
using namespace std;
class Example
{
private:
int x;
public:
Example(int xx = 0):x(xx){}
int Show()const
{
return x;
}
};
int main()
{
//auto a;
//错误,auto是通过初始化表达式进行类型推导的。
//如果没有初始化表达式,就无法确定a的类型。
auto str = "HelloWorld";
cout<<str<<endl;
//对自定义类型进行类型推导
auto ex = new Example();
cout<<ex->Show()<<endl;
delete ex;
return 0;
}
c++11新增关键字decltype,这个关键字可以从一个变量或表达式中得到类型
#include<iostream>
using namespace std;
int main()
{
double y = 100;
cout<<sizeof(decltype(y))<<endl;//8
decltype(y)x = 200;
cout<<x<<endl;
return 0;
}
c++11引入标识空指针的关键字nullptr.
- c++11引入关键字nullptr表示空指针类型,为了兼容NULL这个宏,所以两者的值都为0.
- nullptr和NULL的区别:
- 相同点:两者都可以转换为任何原始指针类型
- 不同点:NULL是一个宏定义,而nullptr是类型为std::nullptr_t的指针常量,它可以转换为任何原始指针类型
#ifndef NULL #ifdef __cplusplus #define NULL 0 #else #define NULL ((void *)0) #endif #endif
#include<iostream>
using namespace std;
void F(int a)
{
cout<<a+1<<endl;
}
void F(int * p)
{
cout<<p<<endl;
}
int main()
{
int * p = nullptr;
int * q = NULL;
cout<<p<<endl; //0
cout<<q<<endl; //0
//c++11引入关键字nullptr表示空指针类型,为了兼容NULL这个宏
//所以两者的值都为0.但是,NULL可以和0相互替代。而0不可以表
//示为nullptr
bool equal = (p == q);
cout<<equal<<endl; //1
//int a = nullptr; //error,nullptr不可以表示0
int b = NULL; //right,NULL就是0
cout<<b<<endl; //0
F(0); //1
F(nullptr); //0
return 0;
}
模板的优化
- 模板右尖括号:c++11中,描述模板参数结束符的多个右尖括号之间不用添加空格。
- 在c++98/03标准中,类模板可以有默认的模板参数,但是不支持函数的默认模板参数。在
c++11
中添加了对函数模板默认参数的支持。
// 类模板可以有默认的模板参数
template <typename T = int, T t = 520>
class Test {
public:
void print() {
cout << t << endl;
}
};
int main() {
Test t;
t.print();
return 0;
}
// 函数模板的默认参数
template <typename T = int>
void func(T t) {
cout << t << endl;
}
静态断言static_assert
- 运行时断言:使用assert宏。宏的参数是一个表达式,如果为假则运行时报错。
void func(int size) {
assert(size > 0);
}
int main() {
func(0);
return 0;
}
- c++11中提供了静态断言,他在编译时就能够检查。静态断言static_assert接受两个参数:
- 参数1:断言表达式,这个表达式通常需要返回一个bool值。表达式是在编译阶段进行检测,所以在它的表达式中不能出现变量,也就是说这个表达式必须是常量表达式。
- 参数2:警告信息,它通常就是一段字符串,在违反断言(表达式为false)时提示该信息
- 示例:使用静态断言验证当前OS是否为64位的
// 32位的OS下,sizeof(long) = 4 static_assert(sizeof(long) == 8, "您使用的操作系统不是64位的");
基于范围的for循环(亦称区间迭代)
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{
int arr[5] {100,200,300,400,500};
for(auto x:arr)
{
cout<<x<<endl;
}
map<int,string>m{{1,"wang ming"},{2,"xiao hua"}};
for(auto p:m)
{
cout<<p.first<<" "<<p.second<<endl;
}
return 0;
}
更加优雅的初始化方法
- 示例1
#include<iostream>
#include<vector>
using namespace std;
int main()
{
int arr[] {100,200,300,400,500};//等于号可以省略
vector<double>ve {100,200,300,400,500};
for(auto x:ve)
{
cout<<x<<" ";
}
return 0;
}
- 示例2
#include <iostream>
#include <initializer_list>
using namespace std;
class Example
{
public:
explicit Example(int x):x(x){}
int GetX()const
{
return x;
}
private:
int x;
};
int main()
{
//c++扩大了用大括号扩起的列表(初始化列表)
//的适用范围,使其可用于所有内置类型和用户
//定义的类型(即类类型)。使用初始化列表时
//可以添加等号也可以不添加。
int a{ 100 };
cout << a << endl;
//也可以用于new表达式中
int * pointer = new int[2]{ 100,200 };
cout << pointer[0] <<" "<<pointer[1]<< endl;
//创建对象时也可以使用大括号(而不是圆括号)
//括起的列表来调用构造函数
Example x{ 100 };
//Example x = {1000};
cout << x.GetX() << endl;
//c++11新增头文件initializer_list,这个模板类的
//成员变量是指针类型
initializer_list<int>arr{ 100,200,300,400 };
cout << *arr.begin() << endl;
}
c++11还引入了变长参数模板。
tuple是一个类模板,允许我们将多个不同类型的成员绑定为单一对象。每一个tuple对象包含指定数量的成员。但对一个给定的tuple对象,标准库并没有限制我们可以定义的成员数量上限。tuple对象中的元素是被紧密的存储的(位于连续的内存区域),而不是链式结构。
#include<iostream>
#include<tuple>
#include<vector>
using namespace std;
int main()
{
//声明并创建一个元组对象:tuple类构造函数
tuple <int ,int , vector<int>>tupleText(100,200,{300,400,500});
//求元组对象的成员数量(参数个数):tuple_size函数
int tupleTextSize = tuple_size<decltype(tupleText)>::value;
cout<<"tupleText's size is:"<<tupleTextSize<<endl;//3
//求元组对象首个成员变量的值:get函数
auto first = get<0>(tupleText);
cout<<"The first value is:"<<first<<endl;//100
auto third = get<2>(tupleText);
for(const auto &iter:third)
{
cout<<"The third value:"<<iter<<" ";
}
//生成一个新的tuple对象:make_tuple函数
auto catTupleText = make_tuple(1,4);
//元组合并:tuple_cat函数
auto newTuple = tuple_cat(catTupleText,tupleText);
int newTupleSize = tuple_size<decltype(newTuple)>::value;
cout<<"\nThe newTuple's size is:"<<newTupleSize<<endl;//5
return 0;
}
lambda表达式用于创建并定义匿名的函数对象
1.lambda表达式基本语法
- 它以[]为开始,这个标识叫做捕获指定器,它告诉编译器我们要创建一个lamdba表达式。一个lambda表达式具有如下形式:
[capture list] (parameter list) -> return type {function body}
#include<iostream>
using namespace std;
int main()
{
auto func1 = [](){cout<<"HelloWorld"<<endl;};
//这个auto关键字可以推断func1变量的类型为函数指针类型
func1();
auto func2 = [](int a){cout<<a<<endl;};
func2(100);
return 0;
}
- lambda表达式与STL
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main()
{
vector<int>ve {100,200,300};
//遍历方式1
for_each(ve.begin(), ve.end(),[](int val)
{
cout<<val<<" ";
});
//遍历方式2
for(auto x:ve)
{
cout<<x<<" ";
}
//遍历方法3
for(auto x = ve.begin();x != ve.end();x++)
{
cout<<*x<<" ";
}
return 0;
}
- 指定lambda返回类型:如果你的lambda函数没有return语句,则默认返回void。假如你有一个简单的返回语句,编译器将推导返回值的类型,因此无需指定返回类型,但也有例外,所以最好指定返回类型。
2.lambda表达式的完整变量捕获
- [] 不捕获任何变量
- [&] 以引用方式捕获所有变量。note:当以引用方式捕获一个局部变量时,必须确保在lambda执行时变量是存在的。
- [=] 用值的方式捕获所有变量(可能被编译器优化为const &,这样lambda函数体内不能改变所捕获变量的值)。23属于隐式捕获。
- [=, &foo] 以引用捕获foo, 但其余变量都靠值捕获
- [bar] 以值方式捕获bar; 不捕获其它变量
- [this] 捕获所在类的this指针
#include<iostream>
using namespace std;
class Example
{
private:
int x;
public:
Example():x(100){}
void Show()const
{
[this]()//捕获所在类的指针
{
cout<<x<<endl;
}();//调用lambda表达式
}
};
int main()
{
Example ex;
ex.Show();
return 0;
}
3.可变lambda
- 默认情况下,对于一个值被拷贝的变量,lambda不能改变其值,因为是只读的。如果希望改变一个采用值捕获方式的变量的值,就必须在参数列表首加上关键字mutable,这样就可以在lambda函数体改变捕获的变量的值,但是外部的变量没有影响,因为是值捕获。
int val = 100;
auto func = [val]()mutable
{
// 如果没有mutable关键字
// error: increment of read-only variable 'val'
return ++val;
};
cout << func() << endl; // 101
cout << val << endl; // 100
- 新的std::function是传递lambda函数的最好的方式,不管是传递参数还是返回值。它允许你在模板中指定参数列表和返回值的确切类型。function定义在’functional’头文件中。
#include<iostream>
#include <functional>
#include <vector>
#include<string>
using namespace std;
class AddressBook
{
public:
vector<string> findMatchingAddresses (function<bool (const string&)> func)
{
vector<string> results;
for ( auto itr = _addresses.begin(), end = _addresses.end(); itr != end; ++itr )
{
// 调用传递到findMatchingAddresses的函数并检测是否匹配规则
if ( func( *itr ) )
{
results.push_back( *itr );
}
}
return results;
}
private:
vector<string> _addresses;
};
constexpr函数
常量表达式主要是允许一些计算发生在编译时,即发生在代码编译而不是运行的时候。这是很大的优化.为了使函数获取编译时计算的能力,你必须指定constexpr关键字到这个函数的返回值前。
explicit关键字
explicit关键字在Cpp11以前是只能用于修饰构造函数,但在C++11中可以用来修饰操作符,上面代码中的operator bool()加上explicit表示其无法隐式转化为bool。
#include<iostream>
using namespace std;
struct Testable
{
explicit operator bool() const {
return false;
}
};
int main()
{
Testable a, b;
if (a) { }
if (a == b) { } // 编译错误,这里无法将a,b隐式
//转换为bool.
}
利用c++11的新特性在类内初始化成员变量
1.对非静态成员数组变量进行初始化
#include<iostream>
using namespace std;
class Example
{
private:
int x[5];
public:
Example():x{1,2,3,4,5}{}
void Show()const
{
for(auto y:x)
{
cout<<y<<" ";
}
}
};
int main()
{
Example ex;
ex.Show();
return 0;
}
2.对非静态非数组成员变量进行初始化
#include <iostream>
using namespace std;
class Example
{
public:
Example(int x):x(x){}
int GetX()const
{
return x;
}
static int GetY()
{
return y;
}
Example()
{
}
private:
int x{ 100 };
//int x = 100;//可以使用等号或者大括号版本的初始化
const static int y = 200;
//static int y = 200; // error
};
int main()
{
Example ex;
cout << ex.GetX() << endl;
cout << Example::GetY() << endl;
//基于范围的for循环可以简化代码的编写量
int arr []= {100,200,300,400};
for (auto x : arr)
{
cout << x << " ";
}
return 0;
}
3.对类内常量的静态成员变量可以直接初始化(非常量的静态成员变量不能在类内直接初始化)
- c++98标准的静态常量类成员初始化:在
C++98
中,支持了在类声明中使用等号 = 加初始值
的方式,来初始化类中静态成员常量,并且这种静态常量成员只能是整形或者枚举类型。这种声明方式我们也称之为”就地”声明。而非静态成员变量的初始化则必须在构造函数中进行。
4.类中非静态成员初始化顺序问题
- 对类的非静态的成员变量使用初始化列表与类内部的就地初始化的执行顺序问题:就地初始化先执行。
struct Test {
int a = 100;
Test(int a) : a(a) {}
};
int main() {
Test test(200);
cout << test.a << endl; // 200
return 0;
}
右值引用和move(移动)语义
1.右值引用
- 传统的cpp引用(左值引用)关联到左值。c++11增加了右值引用,这是使用&&表示的,右值引用可以关联到右值。通过将数据与特定的地址关联,使得可以通过右值引用来访问该数据。右值引用的赋值表达式右边一般是常量。
#include <iostream>
using namespace std;
int main()
{
//右值引用举例
int && a = 100; //若引用是常引用,则a的值不可更改
cout << &a << endl;//获取100这个元素的地址
cout << a << endl;
a = 100000;//此时a的地址值还是100的地址
cout << &a << endl;
cout << a << endl;
int x = 1000;
int y = 1000;
int && b = x + y;//右边是表达式
cout << b << endl;
return 0;
}
- 右值引用关联到右值,不能关联到左值。右值引用本身是一个左值。
int a = 100;
// int&& b = a; // error
int&& b = 200;
int& c = b; // no problem
2.移动构造函数与移动赋值运算符函数
转移构造函数(移动构造函数)的工作:把资源从一个对象转移到另一个对象,而不是复制它们。一般参数是左值则调用拷贝构造函数,右值则调用移动构造函数。源对象本身必须不能被改变,作为左值。用户不再使用的对象作为右值。
3.移动语义move函数
- move函数:可以将左值强制转化为右值
#include <iostream>
using namespace std;
class Example {
private:
// friend function
friend void func(Example&& ex);
int x_ = 100;
};
void func(Example&& ex) {
cout << ex.x_ << endl;
}
int main() {
Example ex;
// 将左值转化为右值
func(std::move(ex));
// 给move函数传递右值
func(std::move(Example()));
return 0;
}
4.类型的完美转发forward
- 完美转发:将参数原来的类型转发到另一个函数,一般在函数模板中使用。
c++11新增关键字noexcept:
- 异常:C++98中提供了一套完善的异常处理机制。
- 异常接口说明:可以在函数声明中列出可能抛出的所有异常类型,常用的有如下三种书写方式:
- 显示指定可以抛出的异常类型
struct MyException { public: MyException(string mess) : mess(mess) {} string getMess()const {return mess;} private: string mess; }; int func(int a) throw(MyException) { if (a == 0) { throw MyException("a不能为0"); } return a; } int main() { try { cout << func(0) << endl; } catch (MyException& e) { cout << e.getMess() << endl; } return 0; }
- 抛出任意异常类型,如果一个函数没有添加异常接口声明,表示在该函数中可以抛出任意类型的异常
- 不抛出任何异常
int func() throw() { }
- 异常接口说明:可以在函数声明中列出可能抛出的所有异常类型,常用的有如下三种书写方式:
- noexcept:c++11中引入了关键字noexcept修饰函数表示函数不会抛出异常。如果noexcept修饰的函数抛出了异常,编译器可以选择直接调用std::terminate()函数来终止程序的运行。其语法为:
- noexcept(expression)
- noexcept:等同于noexcept(true)
If the value of the constant expression is true, the function isdeclared to not throw any exceptions. noexcept without a constant expression is equivalent to noexcept(true).
标准库bind函数
bind函数定义在functional头文件中,可以将bind函数看作一个通用的函数适配器,他接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表。
// arg_list中的参数可能包含入_1,_2等,这些是新函数newCallable的参数。
auto newCallable = bind(callbale, arg_list);
_1,_2等,是放在了命名空间placeholder中,所以需要使用这两者的话要引入命名空间。_1是新的可调用对象的第一个参数的位置,_2是第二个:
//_1,_n定在std::placeholders里面
using namespace std::placeholders;
bind参数用法:
//g是一个有2个参数的可调用对象
auto g = bind(func, a, b, _2, c, _1);//func是有5个参数的函数
调用g(X, Y), 等于 func(a, b, Y, c, X)
绑定引用参数:默认情况下,bind的那些不是占位符的参数被拷贝到返回的可调用对象中。但是有时希望以引用方式传递绑定的参数,或者要绑定参数的类型不能拷贝。(比如ostream)
ostream& print(ostream& os, const string &s, const char &c)
{
return os << s << c;
}
//bind引用,必须使用ref或者cref函数,把对象转化成引用,不能用&
ostream &os = cout;
const char c = ' ';
vector<string> svec{"aab","d","aa","bb","e","bbb"};
for_each(svec.begin(),svec.end(),bind(print, ref(os), _1, cref(c)));
新增函数定义语法,在函数名和参数列表后指定函数返回类型
#include <iostream>
#include <vector>
using namespace std;
auto Sum(int x)->int
{
return x;
}
int main()
{
cout << Sum(100) << endl;
//c++为标识符创建别名提供了typedef,c++11提供了创建
//别名的新语法using=
vector<int>vec;
for (int i = 1; i < 3; i++)
{
vec.push_back(i);
}
using itType = vector<int>::iterator;
itType it = vec.begin();
cout << *it << endl;
//c++11新增了一种作用域内的枚举类型,使用关键字
//struct或者class来定义
enum class color{red,white,yellow};
//如上,需要使用red时,需要加上color::red;
int a = int(color::red);
cout << a << endl; // 0
return 0;
}
delete和default关键字的作用
- c++11使用关键字default来指定声明一些编译器可以默认提供的构造函数、赋值函数、析构函数这些方法.这样就可以使用编译器生成的函数。
- 禁用函数的用法:关键字delete则指定禁止使用编译器提供的某个方法。(对任何成员函数都适用)
#include <iostream>
using namespace std;
class Example
{
private:
int x;
public:
Example() = delete;//将导致类无法使用默认构造函数
Example(int x):x(x){}
int GetX()const
{
return x;
}
//导致调用默认拷贝构造函数
Example(const Example & ex) = default;
};
int main()
{
//Example ex;//无合适的函数可以调用
Example ex(100);
cout << ex.GetX() << endl;
Example ex1 = ex;
cout << ex1.GetX() << endl;
return 0;
}
委托构造函数
如果给类提供了多个构造函数,可能需要重复编写相同的代码。委托构造函数允许在一个构造函数中使用另一个构
造函数。