Wang's blog

其他特性

Published on

nullptr

C++11以前通常用NULL表示空指针,但是NULL是用#define定义的,通常定义为0:

#define NULL 0

因此实际上它与数值0没有区别,这在重载时会引发混乱。如果定义以下两个重载函数:

void foo(char *);
void foo(int);

在用NULL作为参数调用时:

foo(NULL);

用户应该希望调用char*版本,然而由于NULL实际上是0,会导致调用int版本。为了解决这一问题,C++11引入了nullptr关键字用来专门表示空指针。它是std::nullptr_t类型的纯右值,可以隐式转换为任何指针类型及任何成员指针类型。使用nullptr调用上述函数:

foo(nullptr);

可以正确调用char*版本。

基于范围的for循环

C++11引入了基于范围的for循环,它相当于其它语言的for-each或for-in循环,可以使循环代码更加简洁。

std::vector<int> arr(5, 100);
for (auto &i : arr)
{
    std::cout << i << std::endl;
}

条件表达式中支持初始化语句

c++17中支持在if或者switch语句中进行初始化,这个能力的出现能够让代码更加简洁。

std::map<int, std::string> c = {{1, "a"}};
if (auto res = c.insert(std::make_pair(2, "b")); !res.second)
{
    std::cout << "key 1 exist" << std::endl;
}
else
{
    std::cout << "insert success, value:" << res.first->second << std::endl;
}

非成员函数std::begin()与std::end()

C++标准库中的容器都实现了begin()与end()成员函数,用于获取首尾迭代器。例如:

std::vector<int> v;
auto b = v.begin();
auto e = v.end();

但是对于非标准库中的其它可迭代对象,如数组或用户自定义类型,则不能使用begin()与end()成员函数。此时可以使用非成员函数std::begin()与std::end()实现同样的功能:

int arr[10];
auto b = std::begin(arr);
auto e = std::end(arr);

对于标准库中的容器,调用成员函数与非成员函数的作用完全相同。因此,推荐在所有需要迭代的情况下使用非成员函数,这种做法更加通用,且不会出错。

变量模板

C++14允许通过变量模板定义一族变量:

// 声明变量模板
template <class T>
constexpr T pi = T(3.1415926535897932385L);

// 在函数模板中使用对应的变量
template <class T>
T circular_area(T r)
{
    return pi<T> * r * r;
}

在类作用域中使用时,变量模板声明一个静态数据成员模板:

using namespace std::literals;
struct matrix_constants
{
    template <class T>
    using pauli = hermitian_matrix<T, 2>;                   // 别名模版

    template <class T>
    static constexpr pauli<T> sigmaX = {{0, 1}, {1, 0}};    // 静态数据成员模板

    template <class T>
    static constexpr pauli<T> sigmaY = {{0, -1i}, {1i, 0}};

    template <class T>
    static constexpr pauli<T> sigmaZ = {{1, 0}, {0, -1}};
};

与其他静态成员一样,静态数据成员模板也需要一个定义。这种定义可以在类定义外提供:

struct limits
{
    template <typename T>
    static const T min;         // 静态数据成员模板的声明
};

template <typename T>
const T limits::min = {};       // 静态数据成员模板的定义

template <class T>
class X
{
    static T s;                 // 类模板的非模板静态数据成员的声明
};

template <class T>
T X<T>::s = 0;                  // 类模板的非模板静态数据成员的定义

inline变量

inline变量可以让变量有多于一次的定义。C++17之前,我们定义全局变量,总需要将变量定义在cpp文件中,然后再通过extern关键字来告诉编译器,这个变量已经在其他地方定义过了。inline变量出现后,我们可以直接将全局变量定义在头文件中,而不用担心出现redefined错误信息。

// test.h
inline void print()
{
    std::cout << "hello world" << std::endl;
}
inline int num = 0;

//  func.h
#include "test.h"
inline void add(int arg)
{
    num += arg;
    print();
}

//  main.cpp
#include "func.h"
int main()
{
    num = 0;
    print();
    add(10);
}

嵌套命名空间

// 传统写法
namespace A
{
    namespace B
    {
        namespace C
        {

        };
    };
};

// 新写法
namespace A::B::C
{

};

自定义字面值(自定义后缀运算符)

在C++11中,可以在字面值的后面加一些后缀,将它们转换为所需要的值。例如:

// 90度角对应的弧度值
double x = 90.0_deg;

这是通过定义以下后缀运算符实现的:

constexpr long double operator"" _deg(long double deg)
{
    return deg * 3.14159265358979323846264L / 180;
}

后缀运算符的参数只能是以下类型:

  • const char *
  • unsigned long long int
  • long double
  • char
  • wchar_t
  • char8_t
  • char16_t
  • char32_t
  • const char *, std::size_t
  • const wchar_t *, std::size_t
  • const char8_t *, std::size_t
  • const char16_t *, std::size_t
  • const char32_t *, std::size_t

其返回值类型不受限制,因此可以用于返回自定义类型的字面值。