最近在学习c++11的新特性,参考了《深入理解C++11:C++11新特性解析与应用》这本书。在此引用了书中的一些内容作为学习笔记,并且在一些感觉描述不妥的地方做了一些修正。本文仅供学习参考,对于理解有误之处欢迎指出。
1 保证稳定性和兼容性方面
1.1 预定义宏
1.2 一些宏定义
兼容c99定义的__func__预定义标示符.该标示符用于返回函数的名字。
兼容C/C++的#pragma预处理指令,该指令用于向编译器传达语言标准以外的信息。C++11中定义了_pragma操作符,功能与上述指令相同。使用语法为:_Pragma (字符串字面常量)
兼容C99中的变长参数的宏定义及__VA_ARGS__。变长参数宏定义是指宏定义的参数列表中最后一个参数用省略号代替。而预定宏__VA_ARGS__则可以在宏定义的实现部分替换省略号所代表的字符串。
#define PR(...) printf(__VA_ARGS__);
1.3 宽窄字符连接
在之前的C++标准中,将窄字符串(char)转换成宽字符串(wchar_t)是未定义行为。而在C++11中,将窄字符串和宽字符串连接时,会将窄字符串转换为宽字符串,然后再与宽字符串进行连接。
template <typename t,typename u>int bit_copy(t &a,u &b) {
static_assert((sizeof(a) ==sizeof(b)),"theparameter of bit_copy must have the same width");
memcpy(&a,&b,sizeof(b));
}
int main() {
int a = 0x1234;
double b;
bit_copy(a,b);
}
1.4 静态断言
C++11的以前版本不支持静态断言,即在编译阶段进行断言,C++11标准支持此功能。静态断言函数static_assert(constant-expression, string-literal )有两个参数,一个是断言表达式用来返回bool值,另一个参数为用来输出的字符串。
1.5 noexcept操作符
C++98:
异常处理的代码中,会声明如下函数:void except_funt() throw(int,double) {...}
定义了一个动态异常声明throw(int,double),如果该函数发生异常,会抛出该异常,导致函数栈依次展开,然后依次调用在栈帧中已构造的自动变量的析构函数。因此throw异常处理会带来额外开销。
C++11:
使用noexcept操作符代替throw,在函数后声明该操作符,该函数发生异常后抛出异常,编译器可以直接调用std::terminate()函数终止程序运行。语法使用如下:
void except_funt() noexcept;
void except_funt() noexcept(常量表达式);
常量表达式的结果会被转换为一个bool值。若该值为true,表示函数不会抛出异常;反之则抛出异常。不带常量表达式的noexcept相当于声明了noexcept(true)。
1.6 快速初始化成员变量
C++98:
C++98中支持了在类声明时使用等号”=”来初始化类静态成员变量。但需要满足两点:1、静态成员常量性(const修饰);2、只能是整型或者枚举类型。
非静态成员只能在构造函数中完成初始化。
class TestInit {
public:
TestInit(){}
TestInit(int d):a(d) {}
private:
int a;
const static int b = 0;
int c = 1;//error,非静态成员,只能在构造函数中赋值
static int d = 0;//error,不满足常量性
static const doublee = 1.3;//error,不是整型或者枚举类型
static const char *constf ="e";//error,同上一行
};
C++ 11:
除了允许非静态成员在构造函数中使用初始化列表完成初始化,还允许非静态成员在类声明时使用等号”=”和花括号”{ }”进行默认初始化成员。以下声明是合法的。
struct TestInitC11 {
int a = 1;
double b {1.2};
};
但初始化列表中的值会取代类声明时的默认初始化值。
struct MemInitC11 {
MemInitC11(){std::cout << a <<std::endl;}
MemInitC11(int var):a(var) {std::cout<<a <<std::endl;}
int a = 1; //使用"="初始化非静态成员a
};
int main() {
MemInitC11 memDefault;//输出成员声明时的初始值1
MemInitC11 memInit(19);//初始化列表中的值会取代成员声明时的初始值,输出值19
}
新标准优点:
避免在初始化列表中把不需要初始化的成员都写一遍。
class Group {
Group(int val):a(val) {}
int a,b{1},c{2},d{3};
}
1.7 非静态成员的sizeof
C++98:
使用类直接对非静态成员的sizeof操作是无法编译的。
struct People {
public:
int hand;
static People *all;
};
int main() {
People p;
std::cout<< sizeof(p.hand) << std::endl;//c++98ok; c++11ok
std::cout<< sizeof(People::all) << std::endl;//c++98ok; c++11 ok
std::cout<< sizeof(People::hand) << std::endl;//c++98 error; c++11 ok
}
如果希望使用类直接对非静态成员的sizeof操作只能这样:
std::cout<< sizeof(((People *)0)->hand) << std::endl;
C++11:
允许使用类直接对非静态成员的sizeof操作。
1.8 扩展的friend语法
C++98:
关键字friend用语声明类的友元,友元可以无视类成员的访问控制。但友元会破坏类的封装性,所以有时不用友元,而是在被访问类中提供get/set接口。但友元确实会节省很多代码。
C++11:声明friend不需要加上class
class Poly;
typedef Poly p;
class Lilei {
friend class Poly; //c++98 ok;c++11 ok
};
class Jim {
friend Poly; //c++98 error;c++11ok
};
class Hanmm {
friend p; //c++98 error;c++11ok
};
1.9 final/override控制
C++98:无此关键字
1、 继承关系中无法阻止虚函数在派生类中重写
2、 基类定义的虚函数,在继承关系中的派生类中不需要再用virtual声明。因此,在继承过程中出现虚函数“跨层”重写的情况时,无法验证虚函数的基本检查。比如:虚函数名是否一致,虚函数参数是否一致等等。如虚函数名错误,则定义了一个新的函数;如虚函数参数不一致则重载了一个同名函数。
C++11:
final关键字:在继承关系中阻止虚函数继续重写。
class Base {
virtual void eat() = 0;
virtual void clothing() =0;
};
class Student : Base {
void eat() = 0; //学生的吃东西的方式由子类定义
void clothing() final {std::cout << “穿校服” << std::endl;}//学生的穿着则必须一致,学生不可以随便着装
};
class GradeOne : Student {
void eat() override{}
void clothing() {}//编译error,禁止重写
};
class GradeTwo : Student {
void eat() override{}
void clothing() {}//编译error,禁止重写
};
Base类为定义了eat()和clothing()两个纯虚函数的抽象基类(即接口)。Student类继承自Base类,重写了clothing虚函数,并且把clothing方法定义为final;则Student的子类都不可以重写clothing方法。C++98则无法禁止重写。
override关键字:对虚函数的重写时基本检查。派生类在虚函数声明时使用override关键字时,该函数则必须重载其基类中的同名函数,否则代码无法通过编译。
优点:增强继承过程中虚函数重写时的过程控制。
1.10 函数模板中的默认参数模板
C++98 : 不支持函数模板中的默认参数模板
C++11: 支持
template <typenameT = int> void defPara(); //C++98编译error; C++11编译ok
在语法上,多个默认参数模板指定默认值的时候,不需要按照“从右向左”的顺序。而类模板需要按照此规则。
1.11外部模板
C++98:不支持。
在一个test.h中定义一个函数模板:
在test1.cpp中定义如下:
在test2.cpp中定义如下:
在此过程中,编译器会为每个调用fun函数模板的地方实例化该函数模板;然后在链接时为了只保留一个副本而删除掉多余的fun模板实例。此过程会使编译器产生冗余代码,并消耗大量编译和链接时间。
C++11:支持。
使用外部模板可以解决C++98中带来的问题。
在一个test.h中定义一个函数模板:
在test1.cpp中定义如下:
在test2.cpp中定义如下:
则在编译链接时会产生如下行为:
Test2.o中不会再生成fun<int>(int)实例代码,因此会节省编译和链接时间。外部模板是一种针对编译器的优化手段。
注意:外部模板不能用于静态函数(不具有external属性)。但可以用于类静态成员函数。
1.12局部和匿名类型作模板实参
C++98:不支持
C++11:支持
但C++11不支持以下复合写法: