您的当前位置:首页正文

【C++】C++11知识点串讲

2024-11-08 来源:个人技术集锦

1、long long类型

C++11引入了long long类型,标准规定long long类型的长度至少是long类型的长度,即64bits,同其它类型一样,标准规定了各类型的最小长度,而编译器允许使用超过最小长度的限制。

2、初始化列表

int a = 0;
int a(0);
int a = {0}; // C++11
int a{0}; // C++11

C++11引入了初始化列表,把变量值放到一对花括号中对变量进行初始化,见上面的例子。

long double b = 3.1415926;
int c(b), d = b; // ok
int c{b}, d = {b}; // error

初始化列表的使用条件比较严格,如上面的例子中把long double转换为int,由于会丢失精度信息,初始化列表是不允许这么做的,但普通的初始化方式时可以的。

vector<string> num = { "one", "two", "three" };
vector<string> num{ "one", "two", "three" };
vector<string> num("one", "two", "three" ); // error
vector<string> foo()
{
    return { "one", "two", "three" };
}

初始化列表同样适用于模板vector,以及其它的容器如关联容器、pair等,可以用于函数的返回值,见上面的例子。

void bar(...) {}
void foo(initializer_list<string> l)
{
    for (auto b = l.begin(); b != l.end(); ++l)
        cout << *b << endl;
    for (const auto &e : l)
        cout << e << endl;
}
initializer_list<string> l{ “one”, "two", "three" };
foo(l);
foo({ “one”, "two", "three" });

传统的变参函数,参数为一个省略号,C++11引入了模板类initializer_list,即初始化列表,用法类似于vector,不同的是initializer_list中的元素为const,不可以修改,其中的拷贝构造和赋值函数共享它们的元素,见上面的例子。

vector<int> *pv = new vector<int>{ 1, 2, 3 };
int *pi = new int[10]{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
string *ps = new string[10]{ "a", "an", "the", string(3, 'x') };

初始化列表还可用于动态内存分配的地方,见上面的例子,数组初始化时初始化的元素个数不能多于数组的长度。

3、空指针nullptr

int *p = 0;
int *p = NULL; // #include <cstdlib>
int *p = nullptr; // C++11

C++11引入了nullptr作为空指针,以前用0、NULL表示空指针,见上面的例子。

4、常量constexpr

constexpr int a = 20; // 20为常量表达式
constexpr int b = a + 1; // a + 1为常量表达式
constexpr int c = foo(); // 只有在foo函数为constexpr函数的情况下才是可以的

C++11引入了constexpr表示常量表达式,且必须用常量表达式进行初始化,见上面的例子。

const int *p = nullptr; // p是一个指向const int类型的指针
constexpr int *p = nullptr; // p是一个指向int类型的const指针

constexpr比const更严格,用constexpr声明一个变量后,表示这个变量为const,见上面的例子。

constexpr int foo() { return 100; }
constexpr int bar(int i) { return foo() * i; }
constexpr int c = foo();
constexpr int c = bar(100); // bar(100)是常量
int i = 100;
constexpr int c = bar(i); // error bar(i)不是常量

constexpr函数是一种特殊函数,要求函数的参数和返回值都为字面常量,类似于inline函数或宏函数,其返回类型是个编译时常量,编译时将对constexpr函数的调用替换为一个常量。

5、别名using

typedef double a; // a表示double
typedef a b, *p; // b表示double p表示double*
a x, y; // 同double x, y;
class A;
using B = A; // B表示类A
B z; // 同 A z;

C++11可以使用using给一个类型起个别名,之前的做法是使用typedef,见上面的例子。

typedef char *pstring; // pstring表示char*
const pstring cstr = 0; // 不是const char *cstr = 0; 而是cstr表示一个指向char类型的const指针
const pstring *ps; // ps表示一个指针 这个指针指向另一个指向char类型的const指针

typedef与const和指针结合起来使用容易引起混淆,见上面的例子。

templage<typename T> using twin = pair<T, T>;
twin<string> authors; // pair<string, string>

using可以用于模板。

class Child : public Father
{
public:
    using Father::Father;
};

using的另一种用法是子类复用父类的构造函数,也就是说,子类不用定义构造函数,而是复用父类的构造函数,参数列表相同,这个工作由编译器完成。对于多重继承的情况,如果几个父类的构造函数的参数相同,那么这个子类必须定义同样参数的构造函数,而不能希望通过using使用父类的构造函数。

6、自动类型auto

auto a = b + c; // a的类型由b和c的类型决定

C++11引入了自动类型声明符auto,使用auto类型时必须进行初始化,auto可以表示任意类型,具体什么类型由编译器根据初始化表达式进行推断,见上面的例子。

auto i = 0, *p = &i; // ok i是int类型 p是指向int类型的指针
auto a = 0, b = 3.14; // error a和b的类型不一致

使用auto定义多个变量时,只能涉及一个基本类型,保持彼此类型的一致性,否则是错误的,指针和引用不是基本类型,见上面的例子。

int i = 0, &r = i;
auto a = r; // a是int类型 而r是i的引用 i的类型为int

有时候,编译器推断出来的类型未必与初始化值的类型完全一致,会根据初始化规则作个调整,见上面的例子,引用类型调整为普通类型。

int * const x = 0; // top-level const指的是指针本身为const
cont int * y = 0; // low-level const指的是指针指向的内容为const
const int z = 0; // top-level const
const int &zz = z; // low-level const
const int ci = i, &cr = ci;
auto b = ci; // b为int ci的top-level const被忽略
auto c = cr; // c为int cr是ci的引用 ci的const为top-level
auto d = &i; // d为int* int对象取地址为int*
auto e = &ci; // e为const int*即low-level const const对象取地址为low-level const
const auto f = ci; // f为const int即top-level const

编译器推断出来的类型未必与初始化值的类型完全一致还受const的影响,见上面的例子,top-level const被忽略而保留low-level const,想要top-level const时需手动添加const,top-level const指的是变量本身为const,low-level const指的是变量指向的内容为const,见上面的例子。

auto &g = ci; // g为const int& low-level
auto &h = 42; // error 非常量引用不能使用常量初始化
const auto &j = 42; // ok 常量引用可以使用常量初始化

使用auto时,还可以和引用一起使用,此时的top-level const不会被忽略,引用的const变为low-level,见上面的例子。

auto k = ci, &l = i; // k为int 忽略了top-level const l为int&
auto &m = ci, *p = &ci; // m为const int& p为const int*
auto &n = i, *pe = &ci; // error i为int &ci为const int

前面提到过,auto定义多个变量时,只能有一个基本类型,而引用和指针不是基本类型,见上面的例子。

auto p = new auto(obj);

auto还可以用于动态内存分配,使用一个初始化表达式置于一对圆括号中,但auto不能用于动态分配一个数组,见上面的例子。

7、类型声明decltype

decltype(foo()) a = b; // a的类型是foo函数的返回值类型 a由b进行初始化

C++11引入的decltype是对auto的补充,auto告诉编译器根据表达式推断变量的类型并由这个表达式对变量进行初始化,而decltype与auto的区别是初始化使用的是另外一个表达式,初始化可以在定义之后进行,decltype根据表达式推断类型时这个表达式并没有任何的副作用,如使用函数的返回值类型而不会真正去调用这个函数,见上面的例子。

const int i = 0, &j = i;
decltype(i) x = 0; // x为const int
decltype(j) y = x; // y为const int& y是x的引用 y必须进行初始化

decltype在处理const与引用时与auto也不同,decltype会保留变量类型中的top-level const和引用,见上面的例子。

int i = 12, *p = &i, &r = i;
decltype(r + 0) b; // ok b为int
decltype(*p) c; // error c为int& 必须进行初始化

decltype括号中为一个表达式时,类型为这个表达式产生的类型,有时候会产生引用,条件是这个表达式的结果可以作为左值即放在等号的左边,见上面的例子。

decltype((i)) d;  // error d为int& 必须进行初始化

decltype产生引用的另一个方法是,当括号中为一个变量时,对这个变量再包装一层括号,编译器会认为这对括号是个表达式,表达式可以作为左直,所以产生引用,见上面的例子。

int odd[] = { 1, 3, 5, 7, 9 };
int even[] = { 0, 2, 4, 6, 8 };
decltype(odd) *foo(int i)
{
    return (i % 2) ? &odd :&even;
}

decltype还可以用于函数的返回类型,见上面的例子,decltype不会将数组自动转换为对应的指针,所以添加了一个星号指定函数返回一个指针,指向一个数组。

template<typename It>
auto foo(It beg, It end) -> decltype(*beg)
{
    return *beg;
}

template<typename It>
auto bar(It beg, It end) ->
    typename remove_reference<decltype(*beg)>::type
{
    return *beg;
}

decltype用于模板函数返回类型的用法见上面的例子,参数为迭代器,但不知道迭代器指向的类型,所以使用decltype进行声明,有时和typename一起使用。

8、成员变量初始化

struct A {
    std::string a; // 默认初始化为空字符串
    int b = 100; // 类中初始化为100
    std::vector<int> c{ 1, 2, 3 };
};

C++11引入了对类(struct和class)的成员变量进行初始化的新的方式,类中初始化即直接用等号赋值,或者使用花括号,见上面的例子。

9、for循环

for (declaration : expression)
    statement

string str("hello world");
for (auto c : str) // 遍历并打印str字符串中的每个字符char
    cout << c << endl;

C++11引入了新的for循环语句,expression为一个序列, 见上面的例子。对于动态分配的数组new T[],不能使用这种for循环。

10、嵌套模板符号

vector<vector<string> > file;
vector<vector<string>> file; // C++11 两个相邻的右尖括号之间没有空格

C++11更好的支持了嵌套模板的语法,即模板中的元素类型也为模板,如vector的元素为vector,之前的做法是两个相邻的右尖括号之间要保留空格,防止编译器解析为右移符号,而现在可以省略这个空格,见上面的例子。

11、const迭代器

vector<int> v;
const vector<int> cv;
auto it1 = v.begin(); // vector<int>::iterator
auto it2 = cv.begin(); // vector<int>::const_iterator
auto it3 = v.cbegin(); // vector<int>::const_iterator

C++11引入了获取容器的const迭代器的方法cbegin和cend,使用auto自动类型时,之前获取的迭代器是否const依赖于对象本身是否const,而现在可以直接获取const迭代器,见上面的例子。

12、库函数

int ia[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int *pbeg = begin(ia);
int *pend = end(ia);
while (pbeg != pend)
    ++pbeg;

C++11引入了库函数begin和end,行为像容器的迭代器一样,可以获取指向第一个元素的指针和指向最后一个元素的下一个位置的指针,见上面的例子。

C++11提供了几个数字和string之间转换的函数,to_string和stoi、stof的几个版本。

// auto newCallable = bind(callable, arg_list);
using namespace std::placeholders;
void foo(int, int, int, int, int); // 5个参数
auto bar = bind(foo, 100, 200, _2, 300, _1); // bar绑定了foo
// foo的第一个参数为100 第二个参数为200 第三个参数为bar的第二个参数 第四个参数为300 第五个参数为bar的第一个参数
bar(123, 456); // 等价于foo(100, 200, 300, 456, 300, 123);

C++11支持参数绑定,通过bind函数以参数arg_list绑定一个callable,返回一个新的newCallabel,然后调用新的newCallable时就好像调用绑定的那个callable一样。在arg_list中可能会用到参数位置指示符placeholders,从_1开始,接着是_2、_3等等,表示newCallable中的参数位置,arg_list还可以包括默认参数。

C++11中的std::move相当于reallocate,返回类型为右值引用,告诉编译器把一个右值引用当作左值引用,被移动的对象可以重新赋值或者销毁,但不能使用。

template<typename Type>
foo(Type &&arg)
{
    bar(std::forward<Type>(arg));
}

std::forward模板函数用于获取一个变量的原始类型,因为多个引用符号包括左值与右值在一起混合使用时有一定的解析规则,如上面的例子。

string pattern("[^c]ei");
pattern = "[[:alpha:]]*" + pattern + "[[:alpha:]]*";
regex r(pattern);
smatch results;
string test_str = "receipt freind theif receive";
if (regex_search(test_str, results, r))
    cout << results.str() << endl;

新标准引入了正则表达式,头文件为regex,包括regex、regex_match、regex_search、regex_replace、sregex_iterator、smatch、ssub_match几部分,分别有不同的功能。上面例子中的正则表达式表示查找一个单词,单词中包括ei,ei的前面不能是c。正则表达式支持多种格式,如ECMAScript、Posix basic、Posix extended、Posix awk、Posix grep、Posix egrep,默认为ECMAScript。

default_random_engine e;
for (size_t i = 0; i < 3; ++i)
    cout << e() << " "; // 16807 282475249 1622650073

uniform_int_distribution<unsigned> u(0, 9);
default_radom_engine e2;
for (size_t i = 0; i < 3; ++i)
    cout << u(e) << " "; // 0 1 7

新标准引入了新的随机数使用方法,在头文件random中,其中default_random_engine用来生成随机数序列,uniform_int_distribution用来指定随机数范围,见上面的例子,除此之外,还有其它的distribution。

13、除法规则

21 / 6; // 3
21 % 6; // 3
21 / 7; // 3
21 % 7; // 0
-21 / -8; // 2
-21 % -8; // -5
21 / -5; // -4
21 % -5; // 1

C++11引入了新的除法规则,见上面的例子。在除法中,如果被除数与除数的正负号一样,商为正,否则为负。之前,商为负时,可以向上或向下圆整,而现在只能向0圆整。模运算非零时,结果的正负号同被除数,而之前允许其同除数的正负号,在这种情况下,为负数的商朝着远离0的方向圆整,现在是禁止的。m和n为整数且n非零时,(m/n)*n+m%n等于m。所以,在-m不会溢出的情况下,(-m)/nm/(-n)的结果同-(m/n)m%n的结果同m%(-n)(-m)%n的结果同-(m%n)

14、sizeof操作符

class A
{
public:
    int a;
};
A c, *p;
sizeof(A); // 类型名放在括号中
sizeof c; // 同sizeof(A);
sizeof p; // 指针的大小
sizeof *p; // 同sizeof(A);
sizeof c.a; // 成员变量的大小
sizeof A::a; // C++11 成员变量的大小

C++11支持直接使用sizeof来获取类的成员变量的大小,之前必须通过这个类的一个实例化对象才能进行。sizeof是个操作符,参数为一个类型时需要放在后面的括号中,如果是表达式还可以直接放在sizeof后面而不用括号,见上面的例子。

15、函数返回类型

auto foo(int i) -> int(*)[10];

C++11支持auto自动类型作为函数返回类型,但在函数开始的地方我们并不知道确切的返回类型,C++11提供了一种辅助的声明声明函数返回类型的方法,一般用于复杂的类型,见上面的例子。

16、成员函数

Class Hello
{
public:
    constexpr Hello(int i) : a(i) {}
private:
    int a;
};
constexpr Hello(100);

constexpr函数中的参数和返回值都是字面值,字面值同样适用于引用、指针和类。字面类可能有constexpr成员函数,它们默认为const函数。一个类,它的成员变量都为public,没有定义任何构造函数,没有在成员变量声明时直接初始化,没有父类也没有虚函数,行为非常类似于C中的struct,称为聚集类。聚集类的成员变量都为字面值时为字面类,对于非聚集类,如果所有的成员变量都为字面值,至少有一个constexpr构造函数,成员变量在声明时用常量或其它的constexpr构造函数初始化,析构函数使用default析构函数,也是字面类。constexpr构造函数可以声明为default或deleted,否则需要满足constexpr函数的要求,唯一的区别是没有return,构造函数初始化所有的成员变量,实例化时同样使用常量,见上面的例子。constexpr的作用就是产生consexpr对象,用于函数的参数或者返回值。

class A
{
public:
    A(int) {}
    A() : A(100) {}
};

C++11支持代理构造函数,在一个构造函数的初始化列表中使用另一个构造函数,见上面的例子。

class MyClass
{
public:
    MyClass() = default;
    MyClass(const MyClass&) = delete;
};

C++11对成员函数的操作引入了一些新的关键字,见上面的例子,等于default表示构造函数使用默认行为,可以用在类中,也可以用在类外面的函数定义处,不过只适用于构造、拷贝构造、析构和赋值操作符。等于delete适用于任何成员函数,表示这个函数只有声明没有定义。

class MyClass
{
public:
    MyClass(Myclass&&) noexcept; // move constructor
};

void foo(int) noexcept(true); // won't throw
void bar(int) noexcept(false); // can throw
void func(int) noexcept(noexcept(foo(100)); // won't throw

C++11还提供了移动move构造函数,参数类型为右值引用,这个函数不同于普通的构造函数,它并不分配内存,而是使用参数中对象的内存,参数中的对象随即无效,函数不抛出异常,使用C++11新引入的关键字noexcept,在函数声明和定义处都要使用。noexcept还可以选择性地加个参数,参数必须是可以转换为bool类型的表达式,为true时表示不抛出异常,为false时表示可能抛出异常,noexcept还可以嵌套使用,最内层的参数为另一个函数,当前函数是否抛出异常同最内层参数的函数一样。

class Foo
{
public:
    Foo sorted() &&;
    Foo sorted() const &;
};

C++11支持引用符号&和右值引用&&用于成员函数,表示this的特性,用法类似于const,如果同时出现了const和引用符号,const在前,如果一个成员使用了引用符号,其它的重载函数也都要使用引用符号。

class SmallInt
{
public:
    // explicit防止自动将SmallInt类型转换为int类型
    explicit operator int() const { return val; }
private:
    int val;
};
SmallInt si = 3; // ok 构造函数不是explicit
si + 3; // error explicit operator int
static_cast<int>(si) +3; // ok

explicit构造函数可以防止变量类型自动转换,C++11支持explicit类型转换操作符,即通过operator type进行转换,如果不使用explicit,可以对类型进行自动转换,否则需要手动进行类型转换,但也有例子,如if、while、do、for、逻辑非、逻辑与、逻辑或、三目运算符中的判断表达式,如上面的例子。

class A
{
public:
    virtual void foo();
};
class B : public A
{
public:
    void foo() override;
};

父类的virtual函数在子类中重定义时,可以不使用virtual关键字,C++11引入了关键字override明确指定一个函数为重定义的父类的虚函数,方法是在函数的参数列表后面后者const关键字后面或者引用符号后面。

class A final {};
class B : public A {}; // error

C++11中的关键字final,用于支持一个类不能被继承,方法是类名后面添加这个关键字。final还可以用于虚函数,表示这个虚函数不能被子类重定义。

17、文件名

C++中的文件使用了流的概念,打开一个文件流时,文件名为一个C风格的字符串,C++11支持文件名为一个std::string类型的对象。

18、模板与容器

C++11添加了几个容器,array和forward_list,array比内建的数组类型更安全易用,array的大小是固定的,不能添加元素,也不能删除元素,forward_list列表不同于其它的容器,不支持size函数。

C++11提供了非成员的swap函数,早期的版本只是成员的swap。

C++11的insert函数返回一个迭代器,早期的版本没有返回值。

C++11提供了三个emplace函数,emplace_front、emplace和emplace_back,对应于push_front、insert和push_back,区别是insert的参数为容器中的元素类型,而emplace的参数为容器中的元素类型对应的构造函数中的参数,也就是说emplace会根据参数去调用对应版本的构造函数来构造一个对象,然后作为容器的元素。

C++11添加了shrink_to_fit函数,适用于vector、string和deque,意思是减少capacity的大小,以等于size的大小。

C++11添加了四个无序的关联容器,unordered_map、unordered_set、unordered_multimap和unordered_multiset,有序的关联容器如map通过元素的比较操作符来组织元素,而unordered_map通过一个哈希hash操作和元素的逻辑等于操作符来实现。

allocator是头文件memory中的一个类,用于分离内存分配和初始化,C++11支持其成员函数construct可以使用被构造对象的任何构造函数来进行初始化。

在普通迭代器iterator的基础上增加了移动move迭代器,move iterator解引用的结果为一个右值引用。

function<int(int, int)> f1 = add; // function pointer
function<int(int, int)> f2 = divide(); // object of function-object class
function<int(int, int)> f3 = [](int i, int j) { return i * j; };
f1(4, 2); // 4+2=6
f2(4, 2); // 4/2=2
f3(4, 3); // 4*2=8

bool string::empty(const string&);
auto f = mem_fn(&string::empty);

头文件functional中定义了模板function,有点类似于函数指针的用法,参数包括了函数的返回值类型和参数类型,赋值时可以直接使用函数名或者添加括号,也支持lambda表达式。与function类似的还有个mem_fn,不同的是mem_fn不需要指定函数的返回类型和参数。

template<typename Type>
class Foo
{
    friend Type;
};

模板的类型可以声明为右元。

在类中,支持默认模板参数,C++11支持在函数中也可以使用默认模板参数。

// a.cpp
extern template class Blob<striing>; // 声明
extern template int compare(const int&, const int&); // 声明
// b.cpp
Blob<string> bs; // 定义
template int compare(const int&, const int&); // 定义

模板与其它类型的定义及对象实例化方式不同,模板一般在头文件中定义,对象实例化在不同的文件中可能有相同的参数类型,但这不会有其它类型那样的多重定义问题,这个由编译器保证。模板也可以使用extern进行声明,表示在别处定义。

template<typename T, typename... Args>
void foo(const T &t, const Arg& ... rest);
int i = 0;
double d = 3.14;
string s = "hello";
foo(i, s, 42, d); // Args中有3个类型
foo(s, 42, "hi"); // Args中有2个类型
foo(d, s); // Args中有1个类型
foo("hi"); // Args为空

tempalte<typename ... Args>
void bar(Args ... args)
{
sizeof...(Args);
sizeof...(args);
}

C++11中的模板参数可以是可变参数,用省略号表示,计算可变参数的使用操作符sizeof...(xxx)。可变参数还可以std::forward一起使用。

tuple<string, vector<double>, int, list<int>> foo("abc", { 1.2, 3.4 }, 56, { 7, 8, 9, 10 });

tuple是一个新的模板类,类似于pair,不同的是pair只能有2个元素,而tuple可以有任意个元素。

新添加了模板类bitset,可以方便地操作二进制比特位。

在iostream中定义了许多操作算子manipulators,C++11加入了对浮点数的支持,hexfloat和defaultfloat。

19、Lambda表达式

[capture list] (parameter list) -> return type { function body }

C++11支持Lambda表达式,特征同其它动态语言或解释型语言如python、javascript等,是一种函数式编程思想。捕捉列表用于当前的局部变量,一般为空,参数列表、返回类型和函数体同普通函数用法一样。

auto f = [] { return 42; };
cout << f() << endl; // 42

上面的例子中省略了参数列表和返回类型。

stable_sort(words.begin(), words.end(),
                  [] (const string &a, const string &b) { return a.size() < b.size9); });

上面的例子中lambda表达式作为一个函数的参数,而且使用了参数列表。

[sz] (const strint &a) { return a.size() >= sa; };

上面的例子中是了捕捉列表,如果函数体使用了一个局部变量,必须把这个局部变量放在捕捉列表中。捕捉列表中不能使用局部静态变量,而函数体中可以使用。捕捉列表中的变量可以按值捕捉,也可以按引用捕捉,就像普通函数中的参数一样,引用会修改彼此,使用引用捕捉时在变量前加一个符号&即可。按值捕捉时,一般不会在函数体中修改捕捉的变量值,如果修改,需要在参数列表之后添加关键字mutable。

[]          捕捉列表为空。
[names]          捕捉列表为逗号分隔的若干个局部变量,按值捕捉,使用&符号时按引用捕捉。
[&]          按引用捕捉所有的局部变量。
[=]        按值捕捉所有的局部变量。
[&, identifier_list]         identifier_list中的变量按值捕捉,它们不能使用符号&,其它的按引用捕捉。
[=, reference_list]       reference_list中的变量按引用捕捉,使用符号&,其它的按值捕捉。

上面是捕捉列表的一般规则。

transform(vi.begin(), vi.end(), vi.begin(),
                [] (int i) -> int { if (i < 0) return -i; else return i; });

上面的例子中指定了返回类型。

20、智能指针

shared_ptr<int> p1 = make_shared<int>(100);
shared_ptr<int> p2 = new int(200); // error
shared_ptr<int> p3(new int(300)); // ok

C++11引入了智能指针,shared_ptr、unique_ptr和weak_ptr,它们在头文件memory中。shared_ptr表示共享数据资源,多个指针可以指向同一个对象,当最后一个shared_ptr指针销毁时,自动销毁它指向的对象;unique_ptr表示独一无二的指针,只有一个指针指向一个对象,这个指针完全拥有这个对象,当unique_ptr销毁时它指向的对象也销毁;weak_ptr是个弱指针,它并不管理对象的生命期,对象管理由shared_ptr负责,绑定一个weak_ptr到一个shared_ptr时不会改变shared_ptr的引用计数结果。给shared_ptr分配内存时可以使用new,但更安全的方法是使用make_shared模板函数。智能指针最好不要与普通指针混用,很有可能会带来严重的问题。

21、右值引用

int i = 42;
int &r = i; // ok: r refer to i
int &&rr = i; // error: cannot bind an rvalue reference to an lvalue
int &r2 = i *42; // error: i *42 is an rvalue
const int &r3 = i *42; // ok: we can bind a reference to const to an rvalue
int &&rr2 = i *42; // ok: bind rr2 to the result of the multiplication
int &&rr1 = 42; // ok: literal are rvalues
int &&rr2 = rr1; // error: the expression rr1 is an lvalue

C++11引入了右值引用,用两个符号&表示,引用自一个右值而非左值,可以理解为给一个变量起了个别名,普通的引用或者左值引用生命期较长,而右值引用的生命期是短暂的,可能马上被销毁。当引用与右值引用一起使用使,有一定的解析规则,如X&X& &&X&& &都当作是X&,而X&& &&当作是X&&。static_cast可以把一个左值转化为右值。

22、内联名字空间inline namespace

namespace A
{
    inline namespace B
    {
        int num;
    }
}
A::num = 100;

C++11对嵌套namespace支持内联,当内层namespace为内联时,访问其中的成员可以不使用namespace名字。

23、枚举enum

enum { reds, yellows, greens }; // 匿名enum
enum color { red, yellow, green }; // 普通枚举
enum color2 { red, yellow, green }; // error 重定义
enum class color3 { red, yellow, green }; // ok 枚举类
color g = green; // ok
color g2 = color::green; // ok
color3 y = yellow; // error
color3 r = color3::red; // ok
int i = color::red; // ok
int j = color3::red; // error

C++11引入了枚举类,相当于给枚举加了个访问限定符,访问时不能像普通枚举那样直接访问枚举变量,也不能当作int类型,访问枚举类中的枚举变量必须通过枚举类的类型名进行访问。

enum intValues : unsigned long long
{
    charType = 255,
    shortType = 65535, 
    intType = 65535,
    longType = 4294967295UL,
    longlongType = 18446744073709551615ULL
};

枚举类中的变量类型默认为int,C++11支持给普通枚举和枚举类指定一个整型,表示其大小。

enum class A;
enum B : long;

枚举也支持前置声明,枚举类可以直接声明,但普通枚举需要指定类型。

24、联合union

在早期的C++版本中,union成员的类型不能是有自定义构造函数的class,C++11取消了这个限制,但是union中包括了一个class类型的变量时通常比较复杂。

显示全文