重读C++ Primer笔记

C++ Primer 5E

有符号和无符号

无符号类型和有符号类型混合运算时,有符号数会被提升至无符号类型,如果其值为负责会产生错误。

int main()

{

    unsigned int u = 10;

    int i = -42;

    std::cout<<u+i<< std::endl; // 4294967264 if sizeof(int)==4

    return 0;

}

列表初始化

列表初始化过程不允许损失数据类型精度,所以下面代码中的两行无法通过编译

int main()

{

    double d = 3.14;

    int i1 = d;

    //int i2 = { d }; //error

    //int i3{ d }; //error

    int i4(d);

    return 0;

}

列表初始化可以用于直接初始化返回值。

函数内变量初始值

函数体内的基本类型局部变量如果没有初始化,则其值是未定义的。

constexpr与指针

constexpr修饰指针时,只能被初始化为nullptr、0或是在内存中位置固定的对象。而且 constexpr int *q中constexpr修饰的是*,而不是int,这和const是不同的。

类型别名(type alias)

和typedef作用相同,但可以定义带模板参数的类型:

http://en.cppreference.com/w/cpp/language/type_alias

template<class T> using Vec = vector<T, Alloc<T>>;

auto和decltype

除了用法不同外,这两个的区别是,auto会去除顶层const(int *const中的const)和引用,而decltype不会。

decltype的结果和表达式的形式密切相关,decltype((variable))返回的结果永远是一个引用。对于下面数组两者的结果为

int a[10];

auto b(a); //int *b;

decltype(a) c; //int c[10];

默认实参的声明

默认实参不能重复定义,但可以进行增补:

int Fun(int a, int b, int c = 10);

int Fun(int a, int b, int c = 10); //error

int Fun(int a, int b = 10, int c); //ok

类作用域和定义在类外部的成员

struct Foo

{

    typedef int T;

    T Test();

};



T Foo::Test(){ return T();}//error 返回值类型T未知

Foo::T Foo::Test(){ return T();}//ok

auto Foo::Test() -> T { return T();}//C++11,ok

上述代码中,Foo::Test中的Foo::从Test开始生效,但前面的返回值不在此范围内,所以必须使用Foo::T的方式引用,或是使用C++11的返回值类型后置语法。

Literal Classes

Literal Classes是一种其实例可以成为constexpr对象的类型,成为Literal Classes的条件是:

1、 数据成员必须都是字面值类型(Literal type)

2、 类至少有一个constexpr构造函数。

3、 如果一个数据成员含有类内初始值,则:

a) 内置类型成员的初始值必须是一条常量表达式

b) 其他类型成员必须使用它自己的constexpr构造函数。

4、 类必须使用默认的析构函数。

Literal Classes可以包含非constexpr的函数,也可以作为普通类型使用,但作为constexpr常量时,只能使用其constexpr的构造函数和成员函数。

Literal Classes可以让constexpr常量在编译时初始化,参与编译时的优化,而不是像static那样推迟到程序启动时,这使得constexpr常量对象可以被放在程序的资源中。

Lambda表达式与捕获

Lambda表达式中值捕获后的变量在lambda中是带const修饰的,在参数列表后加mutable可以消除该效果,变成和普通函数一样可以修改传入的参数的值。

class Foo

{

public:

    int value;

    Foo(int v) : value(v){}

    Foo(const Foo &o) : value(o.value){

        printf("Copy init\n");

    }



    int inc()

    {

        return ++value;

    }



    int val() const

    {

        return value;

    }

};



int main()

{

    Foo a(3);

    auto f1 = [=]() -> int { return a.val();};

    printf("%d\n", a.value);

    auto f2 = [=]() mutable -> int { return a.inc();};

    printf("%d\n", a.value);

    //auto f3 = [=]() -> int { return a.inc();}; //error

    return 0;

}

运行结果为

Copy init

Call val

3

Copy init

Call inc

3

移动迭代器

一般的迭代器解引用返回左值,可以使用make_move_iterator将普通迭代器转换为移动迭代器,该迭代器解引用时返回右值。

右值和左值引用成员函数

和const一样,类的成员函数可以通过在参数列表后面添加 &或&&来限制该函数只能在左值或右值对象上调用,并可以通过这种方式进行重载,如

class Foo{

public:

    Foo sorted() &&;

    Foo sorted() const &;//不能写做& const

}

当成员函数中的重载函数有一个使用了&或&&时,其他重载函数也必须使用,如

class Foo{

public:

    Foo sorted() &&;

    Foo sorted() const ;//error

};

Static_cast与右值引用

Static_cast可以将左值引用转换为右值引用。

OOP相关

final关键字

final关键字可以阻止类被继承

class Foo final {};

继承的构造函数

C++11加入的,详见C++ Primer 5E 15.7.4节

一个类只能继承它的直接基类的构造函数,而且不能继承默认、拷贝和移动构造函数。语法为:

class Foo: public class Base

{

public:

    using Base::Base; //继承Base的构造函数

    int Fun();

};

其作为对于继承每个构造函数,编译器都为之生成对应的派生类构造函数,其形参列表与基类的构造函数完全相同。形如

derived(parms): base(args){}

如果派生类含有自己的数据成员,则这些成员将被默认初始化。(参见C++ Primer 5E 7.1.4节)

和普通成员的using声明不同,一个构造函数的using声明不会改变该构造函数的访问级别。而且using语句不能额外指定explicit或constexpr,生成的构造函数继承对应的基类构造函数的相应属性。

如果一个基类构造函数含有默认实参,这些默认实参不会被继承,而是生成多个构造函数,每个构造函数分别省略掉一个含有默认实参的形参。即基类的构造函数

int Foo(A a, B b = b0, C c = c0);

在基类中会生成下面三个构造函数

int Foo(A a, B b, C c);

int Foo(A a, B b);

int Foo(A a);

继承的构造函数不会作为用户定义的构造函数来使用,因此如果一个类只含有继承的构造函数,则它也将拥有一个编译器自动合成的默认构造函数。

在多继承的情况下,允许从多个直接基类继承构造函数,当有冲突出现时,必须定义派生类自己对应的版本。例如:

struct Base1

{

    Base1() = default;

    Base1(const std::string&);

    Base1(int);

};



struct Base2

{

    Base2() = default;

    Base2(const std::string&);

    Base2(const char*);

};



struct D1 : public Base1, Base2

{

    using Base1::Base1;

    using Base2::Base2;

    

    D1(const std::string&);//这是必须的

    D1() = default;//定义上一行后编译器不再产生默认的无参构造函数

};

派生类向基类转换的可访问性

只有当派生类D公有地继承自基类B时,才能使用派生类向基类的转换(指针、引用)。

友元与继承

友元关系不能被继承,基类的友元在访问派生类的成员时不具有特殊性,派生类的成员也不能随意访问基类的成员。但基类的友元可以访问派生类中基类的部分。如

class Base

{

protected:

    int prot_mem;

    friend class Pal;

};

class Sneaky : public Base

{

    int j;

};

class Pa1

{

    int f(Base b) { return b.prot_mem;} //ok

    //int f2(Sneaky s) { return s.j; }// error

    int f3(Sneaky s) { return s.prot_mem;} //ok

};

class D2 : public Pal

{

    //int mem(Base b) { return b.prot_mem; }//error

};

虚析构函数与移动构造函数

虚析构函数会阻止编译器为其生成默认的移动构造函数,即使使用=default指定也不会生成。

构造和析构过程中调用虚函数

在构造函数和析构函数中,调用虚函数时,调用的是该构造函数/析构函数所在的继承层级中所定义的虚函数。即,在B-D1-D2这样的继承链中,D1的构造函数和析构函数调用虚函数时调用的是D1自己或B的虚函数版本,而不会是D2的。

 

多重继承与指针转换

多继承时,派生类指针向非第一个基类的转换会导致指针的值发生变化。

class Base1

{

    int v1;

};



class Base2

{

    int v2;

};



class D1 : public Base1, Base2

{

    int v3;

};



int main()

{

    D1 *d1 = new D1();

    std::cout << ((int)(Base1*)d1) - ((int)d1) << std::endl; //0;

    std::cout << ((int)(Base2*)d1) - ((int)d1) << std::endl; //sizeof(int)

    return 0;

}

你可能感兴趣的:(Prim)