c++11 其他特性(一)
c++11还增加了许多有用的特性,比如:
1. 委托构造函数
如果一个类含有很多构造函数,这些构造函数有一些重复的地方,比如:
class A{ public: A(){}; A(int a){ a_ = a; }; A(int a, int b){ a_ = a; b_ = b; }; A(int a, int b, double c){ a_ = a; b_ = b; c_ = c; }; private: int a_; int b_; double c_; }
类A的三个构造函数有重复,通过c++11的委托构造函数,可以不用每个构造函数中都写一遍相同的内容:
class A{ public: A(int a){ a_ = a; } A(int a, int b):A(a){ b_ = b; } A(int a, int b, double c):A(a, b){ c_ = c; } private: int a_; int b_; double c_; }
需要注意,如果使用了委托构造函数,则不能使用类成员初始化,比如:
class A{ public: A(int a):a_(a){}; //单独使用类成员初始化,可以 A(int a, int b):A(a), b_(b){}; //同时使用了委托构造函数和类成员初始化,错误! private: int a_; int b_; }
2. 继承构造函数
如果一个派生类继承自一个基类,如果其构造函数想要使用和基类相同的构造函数,如果构造函数有多个,则在派生类中要写多个构造函数,每个都用基类构造,如下:
class Base{ public: Base(int a); Base(int a, int b); Base(int a, int b, double c); ~Base(); }; class Derived{ public: Derived(int a):Base(a){}; Derived(int a, int b):Base(a, b){}; Derived(int a, int b, double c):Base(a, b, c){}; };
在c++11中,可以使用继承构造函数来简化这一操作:
class Base{ public: Base(int a); Base(int a, int b); Base(int a, int b, double c); ~Base(); }; class Derived{ public: using Base::Base; //声明使用基类构造函数 //使用继承构造函数,不用重复写了 };
不只是构造函数,还可以声明其他任何函数来在派生类中使用基类中的函数:
struct Base{ void Func(){ cout << "call in base" << endl; } }; struct Derived:Base{ void Func(int a){ cout << "call in derived" << endl; } }; int main(){ Derived d; d.Func(); //编译错误,因为派生类中没有声明 Func()函数,只有 Func(int)函数 return 0; }; //在c++11中,使用继承函数,改成如下形式即可以正常运行: struct Derived:Base{ void Func(int a){ cout << "call in derived" << endl; } };
3. 原始的字面量
原始字面量可以直接表示字符串的实际含义,因为有些字符串带一些特殊字符,比如在转义字符串中,我们往往要专门处理。如windows路径名:D:\A\B\test.txt"
在c++11中,使用R"xx(string)xx" 来获得括号中的string部分的字符串形式,不需要使用转义字符等附加字符,比如:
string a = R"(D:\A\B\test.txt)"
注意,R"xxx(raw string)xxx",其中原始字符串必须用括号()括起来,括号前后可以加其他字符串,所加的字符串是会被忽略的,而且加的字符串必须在括号两边同时出现.
string str = R"test(D:\A\B\test.txt)";//错误,test只在一边出现 string str = R"test(D:\A\B\test.txt)tt"; //错误,左右两边的字符串不同 string str = R"test(D:\A\B\test.txt)test" //ok string str = R"(D:\A\B\test.txt)" //ok
4. final或override关键字
c++11中增加了final关键字来限制某个类不能被继承(类似java)或者某个虚函数不能别重写(类似c#中的sealed)。如果修饰函数,final只能修饰虚函数,并且要放到类或者函数的后面。
struct A{ virtual void foo() final; // foo 声明为final的虚函数,不能被重写 void test() final; // 错误,final只能修饰虚函数 }; struct B final{ //B声明为final,表示不能被继承 }; struct C: B{ //错误,B不能被继承 };
c++11中还增加了override关键字确保在派生类中声明的重写函数与基类的虚函数有相同的签名,同时也明确表明将会重写基类的虚函数,还可以防止因疏忽把原来想重写基类的虚函数声明为重载。override关键字要放到方法的后面
struct A{ virtual void func(); }; struct D:A{ void func() override{ }; };
5. 内存对齐
cpu访问内存的时候,起始地址并不是随意的,例如有些cpu访问内存起始地址要是4的倍数,因为内存总线的宽度为32位,每次读写操作都4个字节4个字节进行。如果某个数据在内存中不是字节对齐的,则会在访问的时候比较麻烦,比如4字节的int32类型,没有4字节对齐,则需要访问两次内存才能读到完整的数据。因此,内存对齐可以提高程序的效率。
c++数据内存对齐的含义是,数据在内存中的起始地址是数据size的倍数。c++结构体内存对齐的原则是:结构体内的每个变量都自身对齐,比如int32 4字节对齐,char 1字节对齐;整个结构体按照结构体内的最大size变量的对齐方式对齐,比如
struct{ int a; char c; double d; }; 结构体按照最大size的变量对齐,即按照double的8字节对齐。
对于结构体来说,默认的对齐等于其中最大的成员的对齐值。并且,在限定结构体的内存对齐时,同时也限定了结构体内所有成员的内存对齐不能超过结构体本身的内存对齐。
利用alignas指定内存对齐大小
alignas(32) long long a = 0; //指定a为32字节对齐。 alignas可以将内存对齐改大, //而不能改小,因此,可以有 alignas(32) long long a; 而不能有alignas(1) long long a; #define XX 1 struct alignas(XX) MyStruct{ //指定为1字节对齐,因为MyStruct内部没有数据,自然为1字节对齐。 //如果内部含有int类型数据,则alignas 只能经对齐方式改大不能改小,故不能为1字节对齐 };
alignas只能将内存对齐改大不能改小,如果要改小,设置内存对齐为1字节对齐,仍然需要使用 #pragma pack(1) .... #pragma pack()
利用alignof和std::alignment_of获取内存对齐大小
alignof 只能返回size_t,即内存对齐的大小,而alignment_of继承自std::integral_constant,拥有 value_type, type和value等成员。
MyStruct xx; cout << alignof(xx) << endl; cout << alignof(MyStruct) << endl; cout << std::alignment_of<MyStruct<::value << std::endl;
内存对齐的类型 std::aligned_storage
aligned_storage可以看成一个内存对齐的缓冲区,原型如下:
template<std::size_t Len, std::size_t Align = /*default-alignment*/> struct aligned_storage; Len代表所存储类型的size,Align代表所存储类型的对齐大小 struct A{ int a; double c; A(int aa, double cc):a(aa), c(cc){}; }; typedef std::aligned_storage<sizeof<A>, alignof(A)>::type Aligned_A; int main(){ Aligned_A a, b; //声明一块内存对齐的内存 new (&a)A(10, 20.0); //原地构造函数 return 0; }