分享

C++ 学习笔记(一些新特性总结 1)

 imelee 2018-04-10

虽然我也用了 C++ 有挺多年了,但是一直本着够用就行的原则,没有特别深入的学习过C++ 的语法,所以好多高级的 C++ 特性都不了解。正好最近从网上找到了本书《C++ 14 Quick Syntax Reference》,挺薄的一本书,只有 100多页,但是覆盖了基本所有 C++ 的特性。这个小短文就是我看这本书时摘抄下来的一些我以前没有注意到的知识点。

文中所有代码都在 gcc version 5.3.0 (Rev1, Built by MSYS2 project) 上测试通过。

16进制、8进制、2进制表示

int myOct = 062;
int myHex = 0x32;
int myBin = 0b00110010;
  • 1
  • 2
  • 3
  • 4

其中 16进制、8进制表示法是很早就支持的特性。2进制表示是 C++14 才正式支持的。
除此之外,C++14 还引入了单引号作为数字表示的分隔符。方便我们阅读很长的数字。比如下面这个例子:

int longBin = 0b1010'0101'1010'0101;
  • 1
  • 2

加了三个单引号作为分割,读起来就方便多了。

NULL 指针(nullptr)

早期的 C++ 中,我们用 0 或者 NULL 来表示无效的指针地址。C++11 专门引入了一个新的关键字 nullptr 来表示 NULL 指针。

int* p = nullptr; // ok
  • 1
  • 2

并且 nullptr 还是有类型的,类型为 nullptr_t:

nullptr_t mynull = nullptr; // ok
  • 1
  • 2

右值引用类型(C++11)

左值和右值都是针对表达式而言的,左值是指表达式结束后依然存在的持久对象,右值是指表达式结束时就不再存在的临时对象。

所谓右值引用就是引用一个右值对象(临时对象),比如下面的例子:

int&& ref = 1 + 2; // rvalue reference
ref += 3;
cout << ref; // "6"
  • 1
  • 2
  • 3
  • 4

ref 对应的是 1+2的结果3,这是个临时对象。传统的C++引用方式是无法引用到这个临时对象的。右值引用主要是解决效率问题,具体的方法可以搜索 “C++ 移动语义”。

原生字符串(raw string literals)

raw string 可以取消转义字符的作用,是 C++11 中添加的新特性。比如:

string escaped = "c:\\Windows\\System32\\cmd.exe";
  • 1
  • 2

可以简写为:

string raw = R"(c:\Windows\System32\cmd.exe)";  
  • 1
  • 2

RAW String 开头要用大写的 R ,然后在双引号内要增加一对括号。

这个功能在其他的语言中早就有了。我们上边这个例子还不大能看出 raw string 的优势,但是如果常写正则表达式的话,就会感觉 raw string 太方便了。比如:

char str[] = R"(('(?:[^\\']|\\.)*'|"(?:[^\\"]|\\.)*")|)";
  • 1
  • 2

旧的写法是:

char oldstr[] = "('(?:[^\\\\']|\\\\.)*'|\"(?:[^\\\\\"]|\\\\.)*\")|";
  • 1
  • 2

新的 char 类型

C++ 11 中引入了 char16_t 和 char32_t 两种类型。这两种字符类型可以分别用来存储 utf-16 和 utf-32 编码的字符。相应的 string 类型也有两个变种, u16string 和u32string。

string s3 = u8"UTF-8 string";
u16string s4 = u"UTF-16 string";
u32string s5 = U"UTF-32 string";
  • 1
  • 2
  • 3
  • 4

字符串前的 u8、u和 U也是新增的特性,分别用来支持 UTF-8、UTF-16和UTF-32 字符串。

for 循环

C++11 对 for 循环做了扩展,for 循环支持一种新的语法。

int a[3] = {1, 2, 3};
for (int &i : a) 
{
    cout <<i; // "123"
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

auto 和 decltype 关键字

这两个关键字都是 C++ 11 中引入的。auto 关键字告诉编译器自动推导变量的类型。比如下面的代码:

auto i = 5; // int
auto d = 3.14; // double
auto b = false; // bool
  • 1
  • 2
  • 3
  • 4

如果我们希望推导出的类型是引用类型。那么需要在 auto 之后加个 &。比如下面这样:

int& iRef = i;
auto myAuto = iRef; // int
auto& myRef = iRef; // int&
  • 1
  • 2
  • 3
  • 4

用 auto 之后可以简化许多代码。比如下面这个代码:

vector<int> myVector { 1, 2, 3 };
for(vector<int>::size_type i = 0; i != myVector.size(); i++) 
{
    cout << myVector[i]; 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

用 auto 的话可以写为:

for(auto i = 0; i != myVector.size(); i++) 
{
    cout << myVector[i]; 
}
  • 1
  • 2
  • 3
  • 4
  • 5

当然,用上 for 的新语法,还可以写的更简便:

for (auto& x : myVector) 
{ 
    cout << x << endl; 
} 
  • 1
  • 2
  • 3
  • 4
  • 5

decltype 与 auto 有些类似,它可以用来推导一个表达式的类型,比如下面的例子:

int a = 0b10'001'000;
cout << a << endl;
decltype(a) c = a + 1; //int
cout << c << endl;

decltype(3) b = 3; // int&&
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

需要解释一下的是这个例子中 3 是个临时变量。所以推导出 b 的类型是一个 int 型的右值引用。但是作为函数返回值时,推导出的就不是右值引用了。

decltype(5) getFive() { return 5; } // int
  • 1
  • 2

C++ 11 中 auto 和 decltype 还可以配合使用,用来推导函数的返回值类型。下面是个例子:

auto getValue(int x) -> decltype(x) { return x; } // int
  • 1
  • 2

这么写还是挺繁琐的, C++ 14 中做了简化。可以简单的写为:

auto getValue(int x) { return x; } // int
  • 1
  • 2

不过我感觉这两种函数写法作用都不大,因为我们没法在头文件中把函数声明写为:

auto getValue(int x);
  • 1
  • 2

因为没有函数体,根本无法做类型推导…

C++14 中还支持如下的写法:

decltype(auto) = 3; // int&&
decltype(auto) getRef(int& x) { return x; } // int&
  • 1
  • 2
  • 3

这些写法知道也就行了,用处不大。

Lambda 函数

Lambda 函数的概念最早应该来源于 Lisp 语言,现在也被 C++ 11 吸收进来了。
Lambda 函数使得我们可以像定义一个变量一样定义一个函数。比如下面这样:

auto sum = [](int x, int y) -> int {return x + y;};
cout << sum(2, 3);
  • 1
  • 2
  • 3

上面的函数还可以简写为:

auto sum = [](int x, int y) { return x + y; };
  • 1
  • 2

编译器会自动推导返回值的类型。

到了 C++ 14 Lambda 函数更是支持了泛型。

auto sum = [](auto x, auto y) {return x + y;};  
cout << sum(2, 3) << endl;
cout << sum(2.2, 3.0) << endl;
cout << sum(2.2, 3) << endl;
  • 1
  • 2
  • 3
  • 4
  • 5

Lambda 函数也可以作为函数的参数传递给函数。下面是个例子:

#include <iostream>
#include <functional>
using namespace std;
void call(int arg, function<void(int)> func) 
{
    func(arg);
}
int main() 
{
    auto printSquare = [](int x) { cout << x*x; };
    call(2, printSquare); // "4"
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

上面的例子其实还说明 Lambda 函数不是普通的函数。它是一种特殊类型的对象。比如下面的 Lambda 函数:

auto sum = [](int x, int y) {return x + y;};  
  • 1
  • 2

写完整了应该是 :

function<int(int)> sum = [](int x, int y) {return x + y;};  
  • 1
  • 2

Lambda 函数还有一些高级用法。比如Lambda 函数中的 “[]” 是有作用的。用它可以将所在域的其他变量引入到函数中。比如下面的例子:

void call(function<void()> func) { func(); }
int main() 
{
    int i = 2;
    auto printSquare = [i]() { cout << i*i; };
    call(printSquare); // "4"
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Lambda 函数中 [] 里按值传进去的参数是只读的。所以下面的代码是错的:

int a = 10;
int b = [a](int i) { a++; return a * i; } (5); // 50
  • 1
  • 2
  • 3

我们可以添加一个 mutable 关键字使得 a 不是只读,但是 a 值的改变是不会影响函数外面的。

int a = 10;
int b = [a](int i) mutable { a++; return a * i; } (5);
cout << b << endl; // 55
cout << a << endl; // 10
  • 1
  • 2
  • 3
  • 4
  • 5

Lambda 函数还可以是无名函数,这时定义函数的同时也要调用这个函数。否则因为这个函数没有名字,之后就没有调用的机会了。下面这两种写法结果是相同的。

cout << [](int i) { return i*i; } (101) << endl;

auto printSquare = [](int i) { return i*i; };
cout << printSquare(101) << endl;
  • 1
  • 2
  • 3
  • 4
  • 5

利用 “[]” 也可以把结果传出来。下面这两种方法结果也是相同的。

int a = [](int i) { return i * i; } (11);
cout << a << endl;

[&a] (int i){ a = i * i;}(12);
cout << a << endl;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

C++ 14 还支持一些新的特性,比如下面这样:

int a = 1;
[&, b = 2]() { a += b; }();
cout << a; // "3"
  • 1
  • 2
  • 3
  • 4

关于 Lambda 函数,知道这些也就差不多了。

第一篇先写这么多。下一篇写写关于类和对象的一些新特性。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多