深入理解c++11

第二章. 保证稳定性和兼容性

预定义宏

#include <iostream>
using namespace std;
int main() {
        cout << "Standard Clib: " << __STDC_HOSTED__ << endl;           // Standard Clib: 1
        cout << "Standard C: " << __STDC__ << endl; // Standard C: 1
       // cout << "C Stardard version: " << __STDC_VERSION__ << endl;
        cout << "ISO/IEC " << __STDC_ISO_10646__ << endl;                // ISO/IEC 200009
}
// 编译选项:g++ -std=c++11 2-1-1.cpp

_func_预定义标识符
1.现在许多编译器都支持c99标准中的_func_预定义标识符功能,其基本功能就是返回所在函数的名字。

2._func_预定义标识符对于轻量级的调试代码具有十分重要的作用。而在c++11中,标准甚至允许使用在类或者结构体中。

3.事实上,按照标准定义,编译器会隐式地在函数的定义之后定义__func__标识符。比如上述例子中的hello函数,其实际的定义等同于如下代码:

//编译器会隐式地在函数的定义之后定义_func_标识符。
        const char* hello() {
            static const char* __func__ = "hello";
            return __func__;
        }

4.不可将_func_标识符作为函数参数的默认值是不被允许的

void FuncFail(string func_name = _func_) {};//无法通过编译
//因为在参数声明中,_func_还未被定义。

_Pragma操作符
在C/C++标准中,#pragma是一条预处理的指令(preprocessor directive)。

#pragma once 

#ifndef THIS_HEADER
#define THIS_HEADER
 // 一些头文件的定义
 #endif  

_Pragma("once");  

#pragma则不能在宏中展开,因此从灵活性上来讲,C++11的_Pragma具有更大的灵活性。

变长参数的宏定义以及 VA_ARGS
在C99标准中,程序员可以使用变长参数的宏定义。变长参数的宏定义是指在宏定义中参数列表的最后一个参数为省略号,而预定义宏__VA_ARGS__则可以在宏定义的实现部分替换省略号所代表的字符串。

宽窄字符串的连接
在之前的C++标准中,将窄字符串(char)转换成宽字符串(wchar_t)是未定义的行为。而在C++11标准中,在将窄字符串和宽字符串进行连接时,支持C++11标准的编译器会将窄字符串转换成宽字符串,然后再与宽字符串进行连接。

long long整型
在C++11中,标准要求long long整型可以在不同平台上有不同的长度,但至少有64位。我们在写常数字面量时,可以使用LL后缀(或是ll)标识一个long long类型的字面量,而ULL(或ull、Ull、uLL)表示一个unsigned long long类型的字面量。比如:

long long int lli = -9000000000000000000LL;
unsigned long long int ulli = -9000000000000000000ULL;

同其他的整型一样,要了解平台上long long大小的方法就是查看(或<limits. h>中的宏)。与long long整型相关的一共有3个:LLONG_MIN、LLONG_MAX和ULLONG_MIN,它们分别代表了平台上最小的long long值、最大的long long值,以及最大的unsigned long long值。

扩展的整型
而无论是扩展的整型还是标准的整型,其转化的规则会由它们的“等级”(rank)决定。而通常情况,我们认为有如下原则:

长度越大的整型等级越高,比如long long int的等级会高于int。
长度相同的情况下,标准整型的等级高于扩展类型,比如long long int和_int64如果都是64位长度,则long long int类型的等级更高。
相同大小的有符号类型和无符号类型的等级相同,long long int和unsigned long long int的等级就相同。
而在进行隐式的整型转换的时候,一般是按照低等级整型转换为高等级整型,有符号的转换为无符号。这种规则其实跟C++98的整型转换规则是一致的。

余下关键字等都在学习资料中

第二章学习资料:第二章

第三章. 通用为本,专用为末

1.继承构造函数
如果派生类要使用基类的成员函数的话,可以通过using声明(using-declaration)来完成。

#include <iostream>
using namespace std;
struct Base {
    void f(double i){ cout << "Base:" << i << endl; }
};
struct Derived : Base {
    using Base::f;
    void f(int i) { cout << "Derived:" << i << endl; }
};
int main() {
  Base b;
  b.f(4.5);   // Base:4.5
  Derived d;
  d.f(4.5);   // Base:4.5
}
// 编译选项:g++ 3-1-3.cpp

在C++11中,上面这种想法被扩展到了构造函数上。子类可以通过使用using声明来声明继承基类的构造函数。

struct A { A(int) {} };
struct B { B(int) {} };
struct C: A, B {
    using A::A;
    using B::B;
};

A和B的构造函数会导致C中重复定义相同类型的继承构造函数。这种情况下,可以通过显式定义继承类的冲突的构造函数,阻止隐式生成相应的继承构造函数来解决冲突。比如:

struct C: A, B {
    using A::A;
    using B::B;
    C(int){}
};

不过继承构造函数只会初始化基类中成员变量,对于派生类中的成员变量,则无能为力。不过配合类成员的初始化表达式,为派生类成员变量设定一个默认值还是没有问题的。
有的时候,基类构造函数的参数会有默认值。对于继承构造函数来讲,参数的默认值是不会被继承的。事实上,默认值会导致基类产生多个构造函数的版本,这些函数版本都会被派生类继承。参数默认值会导致多个构造函数版本的产生,因此程序员在使用有参数默认值的构造函数的基类的时候,必须小心。

如果基类的构造函数被声明为私有成员函数,或者派生类是从基类中虚继承的,那么就不能再派生类中声明继承构造函数。此外,如果一旦使用了继承构造函数,编译器就不会再为派生类生成默认构造函数了,那么下列代码中变量b的定义应该是不能通过编译的

struct A { A (int){} };
struct B : A{using A :: A; };

B b;//B没有默认构造函数error

2.委派构造函数
与继承构造函数类似的,委派构造函数也是C++11中对C++的构造函数的一项改进,其目的也是为了减少程序员书写构造函数的时间。通过委派其他构造函数,多构造函数的类编写将更加容易。

class Info {
public:
    Info() : type(l), name('a') { InitRest(); }
    Info(int i) : type(i), name('a') { InitRest(); }
    Info(char e): type(l), name(e) { InitRest(); }
private:
    void InitRest() { /* 其他初始化 */ }
    int   type;
    char name;
    // ...
};
// 编译选项:g++ -c -std=c++11 3-2-4.cpp

上述代码有很多重复的代码段,可以将上述代码简单化:

class Info {
public:
    Info() : { InitRest(); }
    Info(int i) : type(i) { InitRest(); }
    Info(char e): name(e) { InitRest(); }
private:
    void InitRest() { /* 其他初始化 */ }
    int   type{1};
    char name{'a'};
    // ...
};

通过对成员变量的处理,使得构造函数简短,不过每个构造函数还是需要调用InitRest函数进行初始化,还可以简单化。这次需要我们能够将一个构造函数设定为“基准版本”来进行初始化。编写出以下代码:

    Info() : { InitRest(); }
    Info(int i) { this->Info() ; type = i ; }
    Info(char e) { this->Info() ; name = e ; }

我们这里通过this指针来调用“基准版本”的构造函数。但是编译器会阻止this->Info()的编译。编译器不允许构造函数中调用构造函数。就出现了以下版本

    Info() : { InitRest(); }
    Info(int i) { new (this) Info() ; type = i ; }
    Info(char e) { new (this) Info() ; name = e ; }

但是这个版本也是错误的,所以出现了委派构造函数

class Info {
public:
    Info() : { InitRest(); }
    Info(int i) : Info() {type = i;}
    Info(char e): Info() {name = e;}
private:
    void InitRest() { /* 其他初始化 */ }
    int   type{1};
    char name{'a'};
    // ...
};

委派构造函数只能在函数体中为type,name等成员赋初值。这是由于委派构造函数不能有初始化列表造成的。在c++中,构造函数不能同时“委派”和使用初始化列表。所以如果委派构造函数要给变量赋初值,初始化代码必须放在函数体中。比如:

struct Rulel{
     int i;
     Rulel(int a) : i(a) {}
     Rulel() : Rulel(40),i(1) {} //无法通过编译
};
  • 由于在C++11中,目标构造函数的执行总是先于委派构造函数而造成的。因此避免目标构造函数和委托构造函数体中初始化同样的成员通常是必要的。
  • 委派构造的一个很实际的应用就是使用构造模板函数产生目标构造函数。eg:
#include <list>
#include <vector>
#include <deque>
using namespace std;
class TDConstructed {
    template<class T> TDConstructed(T first, T last) :
        l(first, last) {}
    list<int> l;
public:
    TDConstructed(vector<short> & v):
        TDConstructed(v.begin(), v.end()) {}
    TDConstructed(deque<int> & d):
          TDConstructed(d.begin(), d.end()) {}
  };
  // 编译选项:g++ -c -std=c++11 3-2-6.cpp

  • 可以说,委托构造使得构造函数的泛型编程也成为了一种可能。
  • 在异常处理方面,如果在委派构造函数中使用try的话,那么从目标构造函数中产生的异常,都可以在委派构造函数中被捕捉到。eg:
#include <iostream>
using namespace std;
class DCExcept {
public:
    DCExcept(double d)
        try : DCExcept(1, d) {
            cout << "Run the body." << endl;
            // 其他初始化
        }
        catch(...) {
            cout << "caught exception." << endl;
        }
private:
    DCExcept(int i, double d){
        cout << "going to throw!" << endl;
        throw 0;
    }
    int type;
    double data;
};
int main() {
    DCExcept a(1.2);
}
// 编译选项:g++ -std=c++11 3-2-7.cpp

3. 右值引用:移动语义和完美转发

#include <iostream>
using namespace std;

class HasPtrMem{
public:
    HasPtrMem() : d(new int(0)) {}
    HasPtrMem(const HasPtrMem & h):
             d(new int (h*d)) {} //拷贝构造函数,从堆上分配内存,并用*h.d初始化
   ~HasPtrMem() {delete d;}
   int * d ;
};

int main()
{
   HasPtrMem a;
   HasPtrMem b(a);
   cout << *a.d << endl;
   cout << *b.d << endl;
}

代码中的a.d和b.d都指向同一块内存。因此在main函数作用域结束后,a和b的析构函数纷纷被调用,当其之一不在指向有效内存了,那么在该悬挂指针上释放内存就会造成严重错误。这就是c++中常说的“浅拷贝”。而在未声明构造函数的情况下,c++也会为类生成一个浅拷贝的构造函数。通常最佳的解决方案是用户自定义拷贝构造函数来实现“深拷贝”,如下代码就是修改后的方案

#include <iostream>
using namespace std;

class HasPtrMem{
public:
    HasPtrMem() : d(new int(0)) {}
    HasPtrMem(HasPtrMem & h):
       d(new int (h*d)) {} //拷贝构造函数,从堆上分配内存,并用*h.d初始化
   ~HasPtrMem() {delete d;}
   int * d ;
};

int main()
{
   HasPtrMem a;
   HasPtrMem b(a);
   cout << *a.d << endl;
   cout << *b.d << endl;
}

我们为HasPtrMem 添加了一个拷贝构造函数。拷贝构造函数从堆中分配新内存,将该分配来的内存的指针交还给d,又使用*(h.d)对*d进行了初始化。通过这样的方法,就避免了悬挂指针的困扰

3.2 移动语义
static_cast<T&&>(lvalue);

3.3左值、右值与右值引用

如何分辨左右值: 可以取地址的有名字的就是左值,反之,不能取地址的、没有名字的就是右值。

std::move: 基本等同于一个类型转换:就是将一个左值强转换为右值引用
static_cast<T&&>(lvalue);

第四章. 新兵易学,老兵易用

第五章. 提高类型安全

第六章. 提高性能及操作硬件的能力

第七章. 为改变思考方式而改变

第八章. 融入实际的应用

第九章. 新特性总结

后续章节可以观看以下资料:c++11读书笔记

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值