C++11在C++98基础上增加了很多新的特性。本文来总结一下那些主要的特性,记住这些特性就可以了。
//Test.h 示例代码1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
#pragma once
class Test
{
public:
auto TestWork(int a, int b)
{
return a + b;
}
};
//数组列表初始化
int xx[5]={1,2,3,4,5};
int yy[]={6,7,8,9,0};
//值类型进行初始化
int a{10};
int b={10};
int c={10.123}; // 编译器报错,g++ 5.3.1当列表初始化用于值类型的时候,如果有精度损失,编译器会报错。
//列表初始化还可以用结构体
typedef struct Str{
int x;
int y;
}Str;
Str s = {10,20};
//列表初始化类,必须是public成员,如果含有私有成员会失败
class Cls{
public:
int x;
int y;
};
Cls c = {10,20};
//vector不仅可以使用列表初始化,还可以使用列表进行赋值,数组不能用列表赋值
vectorv1={1,2,3,4,5,6,7,8,9}; // 初始化
vectorv2;
v2={3,4,5,6,7}; //赋值
//map列表初始化
map m = {
{"x",1},
{"y",2},
{"z",3}
};
//用函数返回初始化列表只展示关键代码,相关头文件自行添加
//同理结构体,类,map的返回也可以使用初始化列表返回
vector getVector()
{
return {1,2,3,4,5};
}
int main()
{
vector v = getVector();
cout<
C++11提供了写字符串常量的方式,R"()"内的内容会原原本本的显示出来,不会有转义的问题,正则表达式这种字符串很友好。
int a = 10;
int b = 20;
decltype(a+b) c = 50; // OK c的类型就是 a+b 的类型int
对于引用类型decltype有一些特别的地方:
int a = 20 ;
int &b = a;
decltype(b) c ; // Error c是引用类型必须赋值
decltype(b) d = a; // OK d是引用类型,指向a
可以看到decltype如果作用于一个引用类型,其得到的还是一个引用类型。我们知道一个引用类型在使用的时候一般会当作其关联的那个变量的同义词处理,例如如果使用 cout< 如果一个表达式是一个解指针引用的操作,decltype得到的也是一个引用类型:
int a = 20 ;
int *p = &a;
decltype(*p) c = a; // c的类型是int&
c = 50;
cout<
当decltype作用于一个变量的时候,变量加不加括号是有区别的,例如:
int a = 20;
decltype(a) b = 30; //ok b的类型是 int
decltype((a)) c = a ; // ok c的类型是int& 其关联变量 a
加上括号之后编译器会把(a)当作是一个表达式处理,而变量是一种可以作为赋值语句左值的表达式,所以会解释成引用类型。
shared_ptr允许多个指针指向同一个对象,unique_ptr则独占所指向的对象,我们主要说明shared_ptr的使用。通过使用make_shared
1
2
|
shared_ptr<
int
> p = make_shared<
int
>(40);
// p指向一个值为40的int对象
shared_ptr
'c'
);
//指向值为'cccccccccc'的string对象
|
make_shared
我们可以使用new初始化的指针来初始化智能指针:
1
2
|
share_ptr<
int
> p (
new
int
(40));
p.get();
// 使用share_ptr
|
shared_ptr对象在离开其作用域(例如一个函数体),会自动释放其关联的指针指向的动态内存,就像局部变量那样。另外多个shared_ptr可以指向一个对象,当最后一个shared_ptr对象销毁的时候才会销毁其关联的那个对象的动态内存。这里使用了引用记数。
有个地方需要注意,当删除一个智能指针时,并不影响其它两个智能指针的继续使用。因为该片内存添加了一个引用计数,每shared_ptr一次,引用计数+1;每次调用析构函数,引用计数减一。直到最后一个智能指针删除,才会释放内存。#include
using namespace std;
template
class smartptr{
public:
smartptr(T* ptr=0);
smartptr(const smartptr& src);
smartptr& operator= (const smartptr& src);
T& operator* (){
if(_ptr){
return *_ptr;
}
}
T* operator-> (){
if(_ptr){
return _ptr;
}
}
~smartptr();
void print_data(){//不能重载运算符,不然会多算一个指针
cout<<(*_ptr)<<" "<<*_referece_count<
void smartptr::releasecount(){
if(_ptr){
//--(*_referece_count);
if(--(*_referece_count)==0){
delete _ptr;
delete _referece_count;
}
}
}
template
smartptr::smartptr(T* ptr):_ptr(ptr),_referece_count(new size_t){
if(ptr){
*_referece_count=1;
}
else{
*_referece_count=0;
}
}
template
smartptr::smartptr(const smartptr& src){
if(this!=&src){
_ptr=src._ptr;
_referece_count = src._referece_count;
(*_referece_count)++;
}
}
template
smartptr& smartptr::operator= (const smartptr& src){
if(_ptr == src._ptr){
*this;
}
releasecount();
_ptr = src._ptr;
_referece_count=src._referece_count;
(*_referece_count)++;
return *this;
}
template
smartptr::~smartptr(){
if(--*(_referece_count)==0){
delete _ptr;
delete _referece_count;
}
}
int main()
{
smartptrcp1(new char('a'));
cp1.print_data();
smartptrcp2(cp1);
cp2.print_data();
cp1.print_data();
smartptrcp3;
cp3 = cp2;
cp2.print_data();
smartptrcp4(new char('b'));
cp4.print_data();
}
//示例代码1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
#include
int main()
{
std::array arrayDemo = { 1,2,3,4 };
std::cout << "arrayDemo:" << std::endl;
for (auto itor : arrayDemo)
{
std::cout << itor << std::endl;
}
int arrayDemoSize = sizeof(arrayDemo);
std::cout << "arrayDemo size:" << arrayDemoSize << std::endl;
return 0;
}
//webset address: http://www.cplusplus.com/reference/unordered_map/unordered_map/bucket_count/
#include
#include
#include
int main()
{
std::unordered_map mymap =
{
{ "house","maison" },
{ "apple","pomme" },
{ "tree","arbre" },
{ "book","livre" },
{ "door","porte" },
{ "grapefruit","pamplemousse" }
};
unsigned n = mymap.bucket_count();
std::cout << "mymap has " << n << " buckets.\n";
for (unsigned i = 0; ifirst << ":" << it->second << "] ";
std::cout << "\n";
}
return 0;
}
//示例代码1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
#include
#include
#include
#include
int main()
{
std::unordered_set unorder_set;
unorder_set.insert(7);
unorder_set.insert(5);
unorder_set.insert(3);
unorder_set.insert(4);
unorder_set.insert(6);
std::cout << "unorder_set:" << std::endl;
for (auto itor : unorder_set)
{
std::cout << itor << std::endl;
}
std::set set;
set.insert(7);
set.insert(5);
set.insert(3);
set.insert(4);
set.insert(6);
std::cout << "set:" << std::endl;
for (auto itor : set)
{
std::cout << itor << std::endl;
}
}
捕捉块(catpure block): 指定如何捕捉所在作用域中的变量,并供给lambda主体使用。
参数(parameter): (可选)lambda表达式使用的参数列表。只有在不使用任何参数,并且没有自定mutable、一个exception_specification 和一个return_type的情况下可以忽略该列表,返回类型在某些情况下也是可以忽略的,详见对return_type的说明:eg: [] {return 10;}
参数列表和普通函数的参数列表类似,区别如下:
参数不能有默认值。
不允许变长参数列表。
不允许未命名的参数。
mutable:(可选)如果所在作用域的变量是通过值捕捉到,那么lambda表达式主体中可以使用这些变量的副本。这些副本默认标记为const,因此lambda表达式的主体不能修改这些副本的值。如果lambda表达式标记为mutable,那么这些副本则不是const,因此主体可以修改这些本地副本。
exception_specification:(可选)用于指定lambda可以抛出的异常。
return_type:(可选)返回值的类型。如果忽略了return_type,那么编译器会根据以下原则判断返回类型:
如果lambda表达式主体的形式为{return expression;}那么表达式return_type的类型为expression的类型。
其他情况下的return_type为void。
下面的例子演示了如何创建一个lambda表达式并立即执行这个表达式。这行代码定义了一个没有返回值也没有任何参数的lambda表达式。
注意:尾部的(),这对括号使得这个lambda表达式立即执行:
根据前面的描述,这个例子中的返回值可以忽略:
捕捉块
lambda表达式的方括号部分称为lambda捕捉块(capture block),在这里可以指定如何从所在作用域中捕捉变量。捕捉变量的意思是可以在lambda表达式主体中使用这个变量。有两种方式:
[=]:通过值捕捉所有变量
[&]:通过引用捕捉所有变量
指定空白的捕捉块[]表示不从所在作用域中捕捉变量。还可以酌情决定捕捉那些变量以及这些变量的捕捉方法,方法是指定一个捕捉列表,其中带有可选的默认捕捉选项。前缀为&的变量通过引用捕捉。不带前缀的变量通过值捕捉。默认捕捉应该是捕捉列表中的第一个元素,可以是=或&。
例如:
[&x]只通过引用捕捉x,不捕捉其他变量。
[x]只通过值捕捉x,不捕捉其他变量。
[=, &x, &y]默认通过值捕捉,变量x和y例外,这两个变量通过引用捕捉。
[&, x]默认通过引用捕捉,变量x例外,这个变量通过引用捕捉。
[&x, &y]非法,因为标志符不允许重复。
通过引用捕捉变量的时候,一定保证党lambda表达式在执行的时候,这个引用还是可用的。
将lambda表达式用作返回值
lambda 表达式可以由函数指针接收:
int (*p)(void) = [] {return 1; };
cout << p();
定义在
可以通过以下方式调用上述函数:
mutiplyBy2Lambda()示例通过值捕捉了变量x:[=]。假设这个函数重写为通过引用捕捉变量:[&],如下所示。根据代码所示。根据代码后面的解释,下面这段代码不能正常工作:
将lambda表达式用作参数:
您可以编写lambda表达式作为参数的函数。例如,可通过这种方式实现回调函数。下面的代码实现了一个testCallback()函数,这个函数接受一个整数vector和一个回调函数作为参数。这个实现迭代给定vector中的所有元素,并对每个元素调用回调函数,回调函数接受vector中每个元素作为int参数,并返回一个布尔值。如果回调函数返回false,那么停止迭代。
输出结果:
C++ 右值引用 & 新特性
C++ 11
中引入的一个非常重要的概念就是右值引用。理解右值引用是学习“移动语义”(move semantics)
的基础。而要理解右值引用,就必须先区分左值与右值。
对左值和右值的一个最常见的误解是:等号左边的就是左值,等号右边的就是右值。左值和右值都是针对表达式而言的,左值是指表达式结束后依然存在的持久对象,右值是指表达式结束时就不再存在的临时对象。一个区分左值与右值的便捷方法是:看能不能对表达式取地址,如果能,则为左值,否则为右值。下面给出一些例子来进行说明。
int a = 10;
int b = 20;
int *pFlag = &a;
vector<int> vctTemp;
vctTemp.push_back(1);
string str1 = "hello ";
string str2 = "world";
const int &m = 1;
请问,a,b, a+b, a++, ++a, pFlag, *pFlag, vctTemp[0], 100, string("hello"), str1, str1+str2, m
分别是左值还是右值?
a
和b
都是持久对象(可以对其取地址),是左值;
a+b
是临时对象(不可以对其取地址),是右值;
a++
是先取出持久对象a
的一份拷贝,再使持久对象a
的值加1,最后返回那份拷贝,而那份拷贝是临时对象(不可以对其取地址),故其是右值;
++a
则是使持久对象a
的值加1,并返回那个持久对象a
本身(可以对其取地址),故其是左值;
pFlag
和*pFlag
都是持久对象(可以对其取地址),是左值;
vctTemp[0]
调用了重载的[]
操作符,而[]
操作符返回的是一个int &
,为持久对象(可以对其取地址),是左值;
100
和string("hello")
是临时对象(不可以对其取地址),是右值;
str1
是持久对象(可以对其取地址),是左值;
str1+str2
是调用了+
操作符,而+
操作符返回的是一个string
(不可以对其取地址),故其为右值;
m
是一个常量引用,引用到一个右值,但引用本身是一个持久对象(可以对其取地址),为左值。
区分清楚了左值与右值,我们再来看看左值引用。左值引用根据其修饰符的不同,可以分为非常量左值引用和常量左值引用。
非常量左值引用只能绑定到非常量左值,不能绑定到常量左值、非常量右值和常量右值。如果允许绑定到常量左值和常量右值,则非常量左值引用可以用于修改常量左值和常量右值,这明显违反了其常量的含义。如果允许绑定到非常量右值,则会导致非常危险的情况出现,因为非常量右值是一个临时对象,非常量左值引用可能会使用一个已经被销毁了的临时对象。
常量左值引用可以绑定到所有类型的值,包括非常量左值、常量左值、非常量右值和常量右值。
可以看出,使用左值引用时,我们无法区分出绑定的是否是非常量右值的情况。那么,为什么要对非常量右值进行区分呢,区分出来了又有什么好处呢?这就牵涉到C++
中一个著名的性能问题——拷贝临时对象。考虑下面的代码:
vector<int> GetAllScores()
{
vector<int> vctTemp;
vctTemp.push_back(90);
vctTemp.push_back(95);
return vctTemp;
}
当使用vector
进行初始化时,实际上调用了三次构造函数(一次是vecTemp的构造,一次是return 临时对象的构造,一次是vecScore的复制构造)
。尽管有些编译器可以采用RVO(Return Value Optimization)
来进行优化,但优化工作只在某些特定条件下才能进行。可以看到,上面很普通的一个函数调用,由于存在临时对象的拷贝,导致了额外的两次拷贝构造函数和析构函数的开销。当然,我们也可以修改函数的形式为void GetAllScores(vector
,但这并不一定就是我们需要的形式。另外,考虑下面字符串的连接操作:
string s1("hello");
string s = s1 + "a" + "b" + "c" + "d" + "e";
在对s
进行初始化时,会产生大量的临时对象,并涉及到大量字符串的拷贝操作,这显然会影响程序的效率和性能。怎么解决这个问题呢?如果我们能确定某个值是一个非常量右值(或者是一个以后不会再使用的左值),则我们在进行临时对象的拷贝时,可以不用拷贝实际的数据,而只是“窃取”指向实际数据的指针(类似于STL
中的auto_ptr
,会转移所有权)。C++ 11
中引入的右值引用正好可用于标识一个非常量右值。C++ 11
中用&
表示左值引用,用&&
表示右值引用,如:
int &&a = 10;
右值引用根据其修饰符的不同,也可以分为非常量右值引用和常量右值引用。
非常量右值引用只能绑定到非常量右值,不能绑定到非常量左值、常量左值和常量右值。如果允许绑定到非常量左值,则可能会错误地窃取一个持久对象的数据,而这是非常危险的;如果允许绑定到常量左值和常量右值,则非常量右值引用可以用于修改常量左值和常量右值,这明显违反了其常量的含义。
常量右值引用可以绑定到非常量右值和常量右值,不能绑定到非常量左值和常量左值(理由同上)。
有了右值引用的概念,我们就可以用它来实现下面的CMyString类。
class CMyString
{
public:
// 构造函数
CMyString(const char *pszSrc = NULL)
{
cout << "CMyString(const char *pszSrc = NULL)" << endl;
if (pszSrc == NULL)
{
m_pData = new char[1];
*m_pData = '\0';
}
else
{
m_pData = new char[strlen(pszSrc)+1];
strcpy(m_pData, pszSrc);
}
}
// 拷贝构造函数
CMyString(const CMyString &s)
{
cout << "CMyString(const CMyString &s)" << endl;
m_pData = new char[strlen(s.m_pData)+1];
strcpy(m_pData, s.m_pData);
}
// move构造函数 ---- 实质上就是·窃取·临时对象,注意参数的形式
CMyString(CMyString &&s)
{
cout << "CMyString(CMyString &&s)" << endl;
m_pData = s.m_pData;
s.m_pData = NULL;
}
// 析构函数
~CMyString()
{
cout << "~CMyString()" << endl;
delete [] m_pData;
m_pData = NULL;
}
// 拷贝赋值函数
CMyString &operator =(const CMyString &s)
{
cout << "CMyString &operator =(const CMyString &s)" << endl;
if (this != &s)
{
delete [] m_pData;
m_pData = new char[strlen(s.m_pData)+1];
strcpy(m_pData, s.m_pData);
}
return *this;
}
// move赋值函数
CMyString &operator =(CMyString &&s)
{
cout << "CMyString &operator =(CMyString &&s)" << endl;
if (this != &s)
{
delete [] m_pData;
m_pData = s.m_pData;
s.m_pData = NULL;
}
return *this;
}
private:
char *m_pData;
};
可以看到,上面我们添加了move
版本的构造函数和赋值函数。那么,添加了move
版本后,对类的自动生成规则有什么影响呢?唯一的影响就是,如果提供了move
版本的构造函数,则不会生成默认的构造函数。另外,编译器永远不会自动生成move
版本的构造函数和赋值函数,它们需要你手动显式地添加。
当添加了move
版本的构造函数和赋值函数的重载形式后,某一个函数调用应当使用哪一个重载版本呢?下面是按照判决的优先级列出的3条规则:
1、常量值只能绑定到常量引用上,不能绑定到非常量引用上。
2、左值优先绑定到左值引用上,右值优先绑定到右值引用上。
3、非常量值优先绑定到非常量引用上。
当给构造函数或赋值函数传入一个非常量右值时,依据上面给出的判决规则,可以得出会调用move
版本的构造函数或赋值函数。而在move
版本的构造函数或赋值函数内部,都是直接“移动”了其内部数据的指针(因为它是非常量右值,是一个临时对象,移动了其内部数据的指针不会导致任何问题,它马上就要被销毁了,我们只是重复利用了其内存),这样就省去了拷贝数据的大量开销。
一个需要注意的地方是,拷贝构造函数可以通过直接调用*this = s
来实现,但move
构造函数却不能。这是因为在move
构造函数中,s
虽然是一个非常量右值引用,但其本身却是一个左值(是持久对象,可以对其取地址),因此调用*this = s
时,会使用拷贝赋值函数而不是move
赋值函数,而这已与move
构造函数的语义不相符。要使语义正确,我们需要将左值绑定到非常量右值引用上,C++ 11
提供了move
函数来实现这种转换,因此我们可以修改为*this = move(s)
,这样move
构造函数就会调用move
赋值函数。