C ++ 新功能介绍

C ++ 11新功能

  • 最新的线程库。
  • Lambda表达式
  • Automatic Type Deduction and decltype
  • 统一初始化语法
  • Delete函数和Default函数
  • nullptr
  • 委托构造器
  • 右值引用

在本文中,我将解释语言的最大变化以及它们为何如此重要。如您所见,线程库并不是唯一的更改。新标准以数十年的专业知识为基础,并使C ++更加重要。

首先,让我们看一下一些著名的C ++ 11核心语言功能。

Lambda表达式

lambda函数是可以在源代码中inline编写的函数,通常传递给另一个函数,类似于函数指针的概念。使用lambda,创建快速函数变得更加容易,这意味着您不仅可以在以前需要编写单独的命名函数的地方使用lambda;而且可以编写更多代码,而这些代码依赖于创建快速简便的函数。

Lambda表达式使您可以在调用位置本地定义函数,从而消除了函数对象引起的许多繁琐和安全风险。 Lambda表达式的形式为:

[capture clause](parameters)->return-type {body}

  1. 所有的lambda都以一对平衡的括号开头。括号内是可选的capture clause子句。
  2. Lambda的参数(parameters)在括号之间。如果lambda不带参数,则可以省略括号。
  3. Lambda表达式可以有显式的返回类型,在符号->之后。如果编译器可以确定lambda的返回类型,或者如果lambda不返回任何内容,则可以省略该返回类型。
  4. 最后,lambda的身体出现在一对大括号内。与普通函数一样,它包含零个或多个语句。

捕获子句(Capture Clause)

Lambda可以在其主体中引入新变量,也可以访问或捕获其周围范围的变量。 Lambda以Capture Clause开头,该子句指定要捕获哪些变量,以及是按值;还是按引用进行捕获。带有&前缀的变量,表示通过引用进行访问;而没有前缀&的变量,表示通过值进行访问。
空的捕获子句[]表示,lambda表达式的主体不访问任何外部变量。
您可以使用默认捕获模式,来指示如何捕获lambda中引用的任何外部变量:
[&] 表示您引用的所有变量,都是通过引用捕获
[=] 表示它们是按值捕获。

您可以使用默认的捕获模式,然后为特定变量明确指定相反的模式。例如,如果lambda主体按引用访问外部变量total,而按值访问外部变量factor,则以下捕获子句是等效的
[&total, factor]
[factor, &total]
[&, factor]
[factor, &]
[=, &total]
[&total, =]

参数表(parameters)

除了捕获变量之外,lambda还可以接受输入参数。参数列表是可选的,并且在大多数方面类似于函数的参数表。

返回类型(return-type)

lambda表达式的返回类型会自动推导。除非您指定了尾随返回类型,否则不必使用auto关键字。尾随返回类型类似于普通方法或函数的返回类型部分。但是,返回类型必须在参数列表之后,并且必须在返回类型之前,包含return-type关键字->。
如果lambda主体仅包含一个return语句,或者该表达式没有返回值,则可以省略lambda表达式的return-type部分。
如果lambda主体包含一个return语句,则编译器会从return表达式的类型中推断出return类型。否则,编译器推断返回类型为void。

Lambda函数体

lambda表达式的lambda函数体可以包含普通方法或函数的主体可以包含的任何内容。

  • 从封闭的范围中捕获变量。
  • 参数
  • 局部声明的变量
  • 类数据成员,当在类中声明, this被捕获
  • 静态存储类型的任何变量,例如,全局变量

假设您要计算一个字符串包含多少个大写字母。下面的lambda表达式使用for_each()遍历一个char数组,确定每个字母是否都大写。对于找到的每个大写字母,lambda表达式都会递增Uppercase,这是一个在lambda表达式外部定义的变量:

例子

int main()
{
   char s[]="Hello World!";
   int Uppercase = 0; //modified by the lambda

   for_each(s, s+sizeof(s), 
   			   [&Uppercase] (char c) {
					if (isupper(c))
						Uppercase++;
			   }
    	   );
   cout<< Uppercase<<" uppercase letters in: "<< s<<endl;
}

就像您定义了一个函数,将其主体放置在另一个函数调用中一样。 [&Uppercase]中的&表示lambda主体获得了对大写的引用,因此可以对其进行修改。如果没有与号,则大写字母将按值传递。 C ++ 11 lambda也包含用于成员函数的构造。

自动类型推导和decltype

在许多情况下,对象的说明都包含初始化。 C ++ 11充分利用了这一点,可以在不指定对象类型的情况下说明对象:

例子

auto x=0; 				//x has type int because 0 is int
auto c='a'; 			//char
auto d=0.5; 			//double
auto national_debt=14400000000000LL;	//long long

当对象的类型是冗长的或自动生成时(在模板中),自动类型推导非常有用。
例子

void func(const vector<int> &vi)
{
    vector<int>::const_iterator ci=vi.begin();
}

相反,您可以这样说明迭代器:
auto ci=vi.begin();
关键字auto不是新词;它实际上可以追溯到ANSI C之前的时代。但是,C ++ 11改变了它的含义。

  • auto不再指定具有自动存储类型的对象。
  • 用它说明一个对象,该对象的类型可从其初始值设定项推导出。

为避免混淆,从C ++ 11中删除了auto的旧含义。
C ++ 11提供了一种类似的机制来捕获对象或表达式的类型。新的运算符decltype接受一个表达式,并返回其类型:
例子

const vector<int> vi;
typedef decltype (vi.begin()) CIT;
CIT another_const_iterator;

例子

  1 #include    <iostream>
  2
  3 using namespace  std;
  4
  5 template <class A, class B>
  6 auto min(A a, B b) ->decltype(a < b ? a : b)
  7 {
  8     return (a<b) ? a : b;
  9 }
 10
 11 template <class A>
 12 auto max_value(A a, A b) ->decltype(a < b)
 13 {
 14     return a < b;
 15 }
 16
 17 class Person {
 18     int age;
 19 public:
 20     Person(int age1) : age(age1){};
 21     friend bool operator<(Person &a, Person &b) {return a.age > b.age;};
 22 };
 23
 24 int  main()
 25 {
 26     Person a(10);
 27     Person b(20);
 28
 29     cout << min(1, 1.2) << endl;
 30     cout << max_value(a, b) << endl;
 31 }

统一初始化语法

C ++至少有四个不同的初始化符号,其中一些重叠。

  1. 带括号的初始化
    std::string s(“hello”);
    int m=int(); //default initialization
  2. =赋值符号
    std::string s=“hello”;
    int x=5;
  3. 使用花括号, POD聚合
    int arr[4]={0,1,2,3};
    struct tm today={0};
  4. 构造函数使用成员初始化程序
    struct S {
    int x;
    S(): x(0) {}
    };

这种分散的不仅引起新手,也容易引起混乱。更糟糕的是,在C ++ 03中,您无法初始化使用new []分配的POD数组成员和POD数组。 C ++ 11使用统一的大括号表示法清除了这些混乱情况:

例子

class C
{
    int a;
    int b;

public:
    C(int i, int j);
};

C c {0,0};     //C++11 only. Equivalent to: C c(0,0);
int* a = new int[3] { 1, 2, 0 };      // C++11 only
class X {
  int a[4];
public:
  X() : a{1,2,3,4} {}     //C++11, member array initializer
};

可以调用一长串push_back()初始化容器。在C ++ 11中,可以直观地初始化容器:

例子

// C++11 container initializer
vector<string> vs={ "first", "second", "third"};

map<string,string> corps =
  { {"Google", "San Jose, CA"},
    {"Intel", "Santa Clara, CA"}
  };

同样,C ++ 11支持在类定义中,初始化数据成员。

例子

class C
{
    int a=7; //C++11 only
public:
    C();
};   

Delete函数和Default函数

C ++自动为类声明

  1. 一个默认构造函数
  2. 一个默认析构函数
  3. 一个默认复制构造函数
  4. 一个默认赋值运算符

struct A
{
A()=default; //C++11
virtual ~A()=default; //C++11
};
被称为默认函数。=default指示编译器生成该函数的默认实现。默认函数有两个优点:
它们比手动实现更有效,并且使程序员摆脱了手动定义这些函数的麻烦。

默认功能的相反是删除默认函数定义:
int func()=delete;

删除默认函数定义对于防止对象复制很有用。要禁用复制,声明这两个特殊的成员函数=delete:

例子

struct NoCopy
{
   NoCopy &operator =( const NoCopy & ) = delete;
   NoCopy( const NoCopy & ) = delete;
};
NoCopy a;
NoCopy b(a); //compilation error, copy ctor is deleted

nullptr

nullptr是C ++一个新增加的关键字,它指定空指针常量。 nullptr替换了容易出错的NULL宏和多年来用作空指针的常量0。 nullptr是强类型的:

例子

  1 #include   <iostream>
  2
  3 using namespace  std;
  4
  5 void f(int val)
  6 {
  7     cout << val << endl;
  8 }
  9
 10 int f(char *str)
 11 {
 12     if (str == nullptr)
 13         cout << "string is empty" << endl;
 14 }
 15
 16 int main()
 17 {
 18
 19     f(10);
 20     f(nullptr);
 21 //  f(NULL);    // error: call of overloaded ‘f(NULL)’ is ambiguous
 22 }

输出
10
string is empty

nullptr适用于所有指针类别,包括函数指针和成员指针:

  • 指针比较
    const char *pc=str.c_str(); // data pointers
    if (pc!=nullptr)
    cout<<pc<<endl;
    Person *find_member(vector<Person *> *ptr, int ndx)
    {
    if (ptr == nullptr)
    {
    return nullptr;
    }

    }
  • 成员函数指针
    class driver {
    public:
    string *name = nullptr;
    int (*get_fuel)()=nullptr;

    }
    int (Car::*get_miles)()=nullptr; // pointer to member function
  • 函数指针
    void (*get_speed)()=nullptr; // pointer to function

例子 一

  1 #include    <iostream>
  2 #include    <vector>
  3
  4 using namespace  std;
  5
  6 class Person {
  7 public:
  8     int age;
  9     string name;
 10
 11     Person(int age1) : age(age1){};
 12     Person(char *name1) : name(name1){};
 13
 14     friend bool operator<(Person &a, Person &b) {return a.age > b.age;};
 15     bool operator==(int age) {return this->age == age;}
 16 };
 17
 18 Person *find_person(vector<Person *> persons, int age)
 19 {
 20     for (auto person : persons)
 21         if (*person == age)
 22             return person;
 23     return nullptr;
 24 }
 25
 26 int  main()
 27 {
 28     Person a(10);
 29     Person b(20);
 30     Person c(nullptr);
 31     Person d(NULL); // error: call of overloaded ‘Person(NULL)’ is ambiguous
 32
 33     vector<Person *> persons = {&a, &b};
 34
 35     auto p = find_person(persons, 10);
 36     if (p != nullptr)
 37         cout << p->age << endl;
 38     p = find_person(persons, 15);
 39     if (p != nullptr)
 40         cout << p->age << endl;
 41 }

例子 二

  1 #include    <iostream>
  2
  3
  4 using namespace  std;
  5
  6 class Driver {
  7 public:
  8     Driver()=default;
  9     string *name = nullptr;
 10     int   (*get_fuel)()=nullptr;
 11 };
 12
 13 class Truck_driver : public Driver {
 14 public:
 15     int (*get_fuel)()=[]()->int{ cout << "truck" << endl;};
 16 };
 17
 18 int (*get_fuel)()=nullptr;
 19
 20 int main()
 21 {
 22     Driver d1;
 23     Truck_driver d2;
 24     Driver *ptr;
 25     Truck_driver *ptr2;
 26
 27     ptr = &d1;
 28     if (ptr->get_fuel == nullptr)
 29         cout << "get_fuel emptr" << endl;
 30
 31     ptr2 = &d2;
 32     if (ptr2->get_fuel != nullptr)
 33         ptr2->get_fuel();
 34
 35     ptr = ptr2;
 36     if (ptr->get_fuel == nullptr)
 37         cout << "get_fuel emptr #2" << endl;
 38 }

输出
get_fuel emptr
truck
get_fuel emptr #2

委托构造函数

在C ++ 11中,构造函数可以调用同一类的另一个构造函数。被调用的函数被称为目标构造函数,调用函数被称为委托构造函数。

如果一个类具有多个构造函数,而且这些构造函数执行一些相同的初始化步骤。

例子

class memory_allocator {
public:
  memory_allocator (int count)
  {
  	 total_size = count * 1;
  	 ptr = new char [total_size];
  }
  
  memory_allocator (int count, int elem_size)
  {
     total_size = count * elem_size;
  	 ptr = new char [total_size];
  }
  
  memory_allocator (int count, int elem_size, int extra)
  {
     total_size = count * elem_size + extra;
  	 ptr = new char [total_size];
  }

private:
  int total_size;
  char *ptr;
};

这三个构造函数具有相同的函数体。重复的代码使维护很困难。如果要添加更多成员或更改现有成员的类型,则必须进行三次相同的更改。
为了避免代码重复,可以将常见的初始化步骤移到了成员函数上。构造函数通过调用此成员函数来实现相同的功能。

例子

class memory_allocator {
public:
  memory_allocator (int count)
  {
      init(count, 1, 0);
  }
  
  memory_allocator (int count, int elem_size)
  {
      init(count, elem_size, 0);
  }
  
  memory_allocator (int count, int elem_size, int extra)
  {
      init(count, elem_size, extra);
  }

private:
  int total_size;
  char *ptr;
  void init(int count, int elem_size, int extra)
  {
      total_size = count * elem_size + extra;
  	  ptr = new char [total_size];
  }
};

此修订版消除了代码重复,但带来下列新问题:

  • 其他成员函数可能会意外调用init(),这将导致意外结果。
  • 输入类成员函数后,所有类成员均已构建。调用成员函数来完成类成员的构造工作为时已晚。

C ++ 11提供委托构造函数来解决这些问题。
将常见的初始化步骤集中在一个构造函数中,该构造函数称为目标构造函数。其他构造函数可以调用目标构造函数进行初始化。这些构造函数称为委托构造函数。

例子

class memory_allocator {
public:
  memory_allocator (int count, int elem_size, int extra)
  {
      total_size = count * elem_size + extra;
  	  ptr = new char [total_size];
  }

  memory_allocator (int count)
  {
      memory_allocator (count, 1)}
  
  memory_allocator (int count, int elem_size)
  {
      memory_allocator (count, elem_size, 0)}
  
private:
  int total_size;
  char *ptr;
};

委托构造函数使程序清晰而简单。在此示例中,memory_allocator (int count)委托给memory_allocator (int count, int elem_size),因此memory_allocator (int count, int elem_size)是memory_allocator (int count)的目标构造函数。
委托和目标构造函数具有与其他构造函数相同的接口。从示例中可以看到,委托构造函数可以是另一个委托构造器的目标构造函数,从而形成委托链。

右值引用

C ++ 11引入了新的引用类型类别,称为右值引用。右值引用可以绑定到右值,例如临时对象。

添加右值引用的主要原因是移动语义。
与传统复制不同,移动意味着目标对象获得了源对象的资源,而使源处于“空”状态。
在某些情况下,复制对象既昂贵,又不必要,则可以使用移动操作来代替。

例子

void naiveswap(string &a, string & b)
{
    string temp = a;
    a=b;
    b=temp;
}

复制字符串需要分配原始内存,并将字符从源复制到目标。
移动字符串仅交换两个数据成员,而没有分配内存,复制char数组和删除内存。

例子

void moveswapstr(string& empty, string & filled)
{
//pseudo code, but you get the idea
    size_t sz=empty.size();
    const char *p= empty.data();
//move filled's resources to empty
    empty.setsize(filled.size());
    empty.setdata(filled.data());
//filled becomes empty
    filled.setsize(sz);
    filled.setdata(p);
}

如果要实现支持移动的类,则可以这样说明一个移动构造函数和一个移动赋值运算符:

例子

class Person
{              
    Person(Person&&obj){              //  move constructor
    // Move constructor
    // It will simply shift the resources,
    // without creating a copy.
        cout << "Calling Move constructor\n";
        this->ptr = obj.ptr;
        obj.ptr = NULL;
    }
    Person&& operator=(Movable&&);     //  move assignment operator
};
...
vector<Person> persons;
persons.push_back(Person("jason"));

右值引用支持移动语义的实现,这可以显着提高应用程序的性能。移动语义使您可以编写将资源(例如动态分配的内存)从一个对象转移到另一个对象的代码。
移动语义之所以起作用,是因为它可以临时对象转移, 而且临时对象从程序中其他地方无法引用。
源为右值的复制和赋值操作将自动利用移动语义。与默认的复制构造函数不同,编译器不提供默认的move构造函数。
为了更好地理解移动语义,考虑将元素插入向量对象。
如果超出了矢量对象的容量,则矢量对象必须为其元素重新分配内存,然后将每个元素复制到另一个内存位置,以为插入的元素腾出空间。
当插入操作复制元素时,它会创建一个新元素,调用复制构造函数将数据;从前一个元素复制到新元素,然后销毁前一个元素。
但移动语义直接移动对象,而不必执行昂贵的内存分配和复制操作。

例子

  1 #include    <iostream>
  2 #include    <vector>
  3
  4 using namespace std;
  5
  6 class Person
  7 {
  8 public:
  9     Person() : name(nullptr){};
 10
 11     Person(string name_str) {
 12         int  i;
 13         char *ptr = const_cast<char *>(name_str.c_str());
 14
 15         name = new char[name_str.length()+1];
 16         for (i=0; i<name_str.length()+1; i++)
 17             name[i] = ptr[i];
 18     }
 19
 20 //  move constructor
 21     Person(Person&& obj){
 22     // Move constructor
 23     // It will simply shift the resources, 
 24     // without creating a copy.
 25         cout << "Calling Move constructor\n";
 26         this->name = obj.name;
 27         obj.name = nullptr;
 28     }
 29
 30 //  move assignment operator
 31     Person&& operator=(Person &&obj) {
 32         this->name = obj.name;
 33         obj.name = nullptr;
 34         cout << "Calling Move assignment\n";
 35     }
 36
 37 
 38     char  *name;
 39 };
 40
 41 void myswap(Person &a, Person &b)
 42 {
 43     Person temp;
 44
 45     temp = move(a);
 46     a = move(b);
 47     b = move(temp);
 48 }
 49
 50 int main()
 51 {
 51     Person m("bill");
 52     Person w("linda");
 53
 54     cout << "before" << endl;
 55     cout << "w: " << w.name <<endl;
 56     cout << "m: " << m.name <<endl;
 57     myswap(m, w);
 58
 59     cout << "w: " << w.name << endl;
 60     cout << "m: " << m.name <<endl;
 61
 62     vector<Person> persons;
 63     persons.push_back(Person("jason"));
 63 }

输出 
before
w: linda
m: bill
Calling Move assignment
Calling Move assignment
Calling Move assignment
w: bill
m: linda
Calling Move constructor

完美的转发减少了对重载函数的需求,并有助于避免转发问题。当编写一个将引用作为参数的通用函数,并将其传递(或转发)到另一个函数时,可能会发生转发问题。
例如,如果通用函数采用const T&类型的参数,则被调用函数无法修改该参数的值。
如果通用函数采用T&类型的参数,则无法使用右值(例如临时对象或整数文字)来调用该函数。
要解决此问题,通常提供通用函数的不同重载版本,这些通用函数的参数是T&, 或const T&。结果,重载函数的数量随参数的数量呈指数增长。
使用右值引用,能够编写一个函数版本的版本,该版本接受任意参数,并将其转发给另一个函数,就像直接调用了另一个函数一样。

例子

  1 #include  <iostream>
  2
  3 using namespace std;
  4
  5 class AClass {
  6 public:
  7     AClass(int &, int &){};
  8 };
  9
 10 class BClass {
 11 public:
 12     BClass(int &, int &&){};
 13 };
 14
 15 class CClass {
 16 public:
 17     CClass(int &&, int &&){};
 18 };
 19
 20 template <typename T, typename A1, typename A2>
 21 T* factory(A1&& a1, A2&& a2)
 22 {
 23    return new T(forward<A1>(a1), forward<A2>(a2));
 24 }
 25
 26
 27 int main ()
 28 {
 29     int  a=6;
 30     int  b=8;
 31
 32     AClass *pa = factory<AClass>(a,b);
 33     BClass *pb = factory<BClass>(a,2);
 34     CClass *pc = factory<CClass>(2,2);
 35 }

上述的例子代码使用右值引用作为工厂函数的参数。 std :: forward函数的目的是将工厂函数的参数转发给模板类的构造函数。

编译器如何处理左值和右值
编译器将命名的右值引用视为左值,将未命名的右值引用视为右值。

当一个函数的参数是右值引用时,该参数在函数主体中被视为左值。
编译器将命名的右值引用视为左值,因为命名的对象可以被程序的多个部分引用;允许程序的多个部分修改或删除该对象的资源将很危险。例如,如果程序的多个部分尝试从同一对象转移资源,则只有第一个转移资源调用能成功转移资源。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值