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;
}
重读C++ Primer笔记,码迷,mamicode.com