C++ 中新增关键字
新特性 | 说明 |
---|---|
nullptr | 用于指向null 指针 |
auto for-each | 自动推导类型 和区间迭代 |
static_cast dynamic_cast const_cast reinterpret_cast | 类型转换 |
const constpexpr | const 和 const 表达式 |
new delete delete[ ] | 分配和回收内存 |
explicit | 防止函数隐式转换 |
inline | 内联函数 |
nullptr介绍
nullptr 的出现是为了代替NULL,比如下面的两个重载的函数
void demoFunction(int t) {
cout << "Run para as int" << endl;
}
void demoFunction(char* t) {
cout << "Run para as char*" << endl;
}
如果调用demoFunction(NULL),某些编译将NULL定义为(void)0,这就会产生歧义;
如果调用 demoFunction(nullptr) 就一定会调用到 demoFunction(char t)
auto类型推导,区间迭代
auto 可以推导任意基本类型,还可以作为函数的返回值;但是下面场景不能使用auto
- 函数的参数不能使用auto,因为需要函数参数实现重载特性
- auto 不能推导出数组类型
//error auto Add(auto a, auto b)
auto Add(uint16_t a, uint16_t b) {
return a + b;
}
auto index = 8;
auto str = "HelloWorld";
auto ret = Add(1, 2);
cout << "index:" << index << " str: " << str << " ret:" << ret << endl;
//auto array[] = {1,2,3,4} // error auto cannot as array
auto 实现区间迭代可以用于容器类型,数组类型的遍历操作;可以大幅度简化代码
示例代码
//basic usage
for (uint8_t i = 0; i < sizeof(array)/sizeof(uint16_t); i++) {
cout << "array[" << static_cast<int>(i) << "] = " << array[i] << endl;
}
for (auto t : array) {
cout << t << endl;
}
for (auto &t : array) {
cout << t << endl;
}
const 关键字
const 修饰一般变量,用于表示一个常量值,即该变量成为一个常量
const uint32_t x = 2; 效果和下面等价
uint32_t const x = 2;
const 修饰常数组
uint32_t const a[5] = {1,2,3,4,5}; 效果和下面等价
const uint32_t a[5] = {1,2,3,4,5};
const 修饰对象
表示常对象,定义常对象的时候要初始化,表示对象不能再更新
class demo {
public:
demo ();
private:
uint32_t m;
}
const demo a; //和下面一行等价
demo const a;
const 修饰指针
const int *A // const 修饰指向的对象,指针A可变,A指向的对象不可变
int const *A // const 修饰指向的对象,指针A可变,A指向的对象不可变
int *const A; // const 修饰A,指针A不可变,A指向的对象可变
const int *const A // 指针A 和A指向的对象都是不可变的
const 修饰引用
const int& A; //该引用指向的对象不可更新
修饰函数参数
void Function(const int *A); 表示函数参数指针A指向的对象不可变
修饰函数返回值
表示函数返回值是不可变的,特别应用于返回值是指针或者引用的情形,表示指针和引用指向的对象不可变
const char* formattoString(uint32_t format) {
......
case RGBA:
return "RGBA";
}
修饰类成员函数
表示不能更改类的属性 只读类的成员的值,不能修改类的成员的值
class ClassName {
public:
int Fun() const; // 不能更改类的属性 只读类的成员的值,不能修改类的成员的值
}
cast 类型转换
dynamic_cast
dynamic_cast 只能用在类的指针或者引用上,用于
- 向上转型(派生类转换为基类)
- 向下转型(基类转换为派生类)
注意向下转型需要判断转型的有效性,使用dynamic_cast 需要 RTTI (运行时类型识别)的支持
向上转型总是成功的,向下转型(因为基类可以派生出多个子类) ,需要判断转换结果是否成功
注意:dynamic_cast只能用在含有虚函数的类,是在运行期间检查
static_cast
- 基本类型之间的转换(类型于C语言的强制类型转换)
- void 指针转换为任意基本类型的指针
- 向上转型或者向下转型
向上转型(子类指针或者引用转换为基类)总是成功的
向下转型可能存在问题,程序或者了一个 dirty 值,但是没有编译报错
// 定义 Base 为基类,Derive为子类
Base *pB = new Base;
Derive *pD = static_cast<Derive*>(pB);
cout << pD->b << endl;//输出0
cout << pD->d << endl;//输出垃圾值
因为pB 创建时指向的是基类的实例,所以转换成为派生类会出现问题,static_cast并不提供类似dynamic_cast的转型成功校验机制,这可能会造成一个难以察觉的Bug
static_cast 是在编译期间进行检查
reinterpret_cast
reinterpret_cast 是非常底层的操作,直接按照内存地址按照给定的类型做转换,是重新解释二进制的底层操作,本质是一个编译期指令,实际动作可能取决于编译器,虽然功能最强但风险最大,且失去了移植性,基本等同于C语言中的强制类型转换
比如将一个int 类型数组名转换成 int类型指针,static_cast 不允许这种转换,因为数组名可以看成一个数组指针,但是可以用reinterpret_cast实现
使用reinterpret_cast 判断大小端
uint32_t a = 0x12345678;
uint8_t *b = reinterpret_cast<uint8_t *>(&a);
bool LittleEndian = (*b == 0x78) ? true : false;
const_cast
const_cast是一种C++的一个运算符,用于去除符合类型的 const属性,变量本身的const属性是不能去除的,要想修改变量的值,一般是去除指针(或引用)的const属性,再进行间接修改
- 只能转换指针或者引用,包含this 指针
void cpluscpluscastDemo::constcast_use() {
{ // convert pointer
const uint32_t *mptr = new uint32_t(100);
//*nptr = 200;
//uint32_t *nptr = p;
uint32_t * nptr = const_cast<uint32_t*>(mptr);
*nptr = 200;
cout << "constcast_use *nptr " << *nptr << endl;
}
{
const uint32_t a = 1000;
const uint32_t *mptr = &a;
uint32_t *nptr = const_cast<uint32_t*>(mptr);
*nptr = 2000;
cout << "constcast_use *nptr " << *nptr << endl;
}
{ // convert ref
const uint32_t a = 300;
const uint32_t &mref = a;
uint32_t &nref = const_cast<uint32_t&>(mref);
nref = 400;
cout << "constcast_use *nptr " << nref << endl;
}
}
// remove function const
void cpluscpluscastDemo::constcast_test() const {
string str = "constcast_test";
//mstr += str; //compile error
auto p = const_cast<cpluscpluscastDemo*>(this);
p->mstr += str; // OK mstr append str
}
new delete 用法
new 可以用于开辟单变量地址空间,返回执行该存储空间的地址,可以给单个变量赋初值;
new 也可以用于开辟数组空间;
要访问new 创建的空间,无法通过变量名进行,只能通过赋值的数组进行
int *a = new int; // 将一个int 类型地址赋值给指针 a
int *a = new int(5); // 同上 同时赋初值为5
int* p = new int[100]; //开辟一个大小为100 的数组空间
可以使用 new 创建对象类型和对象类型的数组,创建对象类型数组,会自动调用对应类型的构造函数
delete 用于释放单个类型的内存空间
delete [ ] 用于释放分配的数组类型的空间
new delete 和 malloc free 区别
- new 返回指定类型的指针,并且可以自动计算大小;malloc 需要手动计算字节数,返回值需要强制转换为实际类型的指针;
- malloc 只能分配内存,无法对所得的内存初始化;
- malloc/free 是 C语言的库函数,new/delete 是C++的运算符,作为运算符可以重载,自定义内存分配策略;
- new/delete 对于对象类型,可以自动调用构造函数和析构函数,malloc/free 无法做到;
malloc/free new/delete 必须成对使用
explicit 关键字
explicit 关键字的作用是防止类构造函数的隐式自动转换
- explicit 关键字 只修饰只有一个参数的类构造函数,如果参数大于一个,explicit 是无效的
- 如果类构造函数有多个参数,除了第一个参数以外都有默认值,explicit 也是有效的
隐式自动转换的例子:
class demoObject {
public:
inline demoObject(uint32_t x):CoordY(0), mstr("init"){
CoordX = x;
cout << "call demoObject uint32_t_x" << endl;
}
inline demoObject(const char* str, uint32_t x = 0,uint32_t y = 0) {
mstr = str;
CoordX = x;
CoordY = y;
cout << "call demoObject const char* str" << endl;
}
inline ~demoObject() = default;
static void dumpdemoObject(demoObject r);
uint32_t CoordX;
uint32_t CoordY;
string mstr;
};
{
demoObject p1(1); // call demoObject uint32_t_x
demoObject p2 = 1; // call demoObject uint32_t_x
demoObject p3 = 'A'; // call demoObject uint32_t_x
}
{
demoObject::dumpdemoObject(10); // dump demoObject r 10 0 init
}
demoObject p2 = 1这种形式是可行的,这里编译的时候会自动转换为 demoObject p2(1);这里可能会产生不预期的结果
如果在构造函数加上 explicit 修饰,后三种情况都会编译不过,因为禁用了构造函数的隐式转换
tips: 另外说明 demoObject p2 = 1 和 demoObject p2;p2 = 1 的含义是不一致的,前者会调用构造函数的隐式转换,后者会调用赋值运算符重载
inline 关键字
inline关键字是C99标准的型关键字,其作用是将函数展开,把函数的代码复制到每一个调用处。这样调用函数的过程就可以直接执行函数代码,而不发生跳转、压栈等一般性函数操作,可以节省时间,也会提高程序的执行速度
内联函数的编程要求:
- inline 必须和函数定义放在一起才能生效,仅用 inline 用于函数声明不生效
- inline只适合函数体内代码比较简单的函数使用,不能包含复杂的结构控制语句,例如while、switch,并且内联函数本身不能是直接递归函数(函数内部调用自己的函数)
- inline 函数仅仅是对编译器的一个建议,最后是否内联,要看编译器的意思
- inline 函数应该放在头文件中,方便编译器展开
inline 的缺陷:
- inline 函数省去了函数调用的开销,从而提高函数的执行效率,会导致代码膨胀
- 如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大;类的构造函数和析构函数容易让人误解成使用内联更有效。要当心构造函数和析构函数可能会隐藏一些行为,如“偷偷地”执行了基类或成员对象的构造函数和析构函数,所以不要随便地将构造函数和析构函数的定义体放在类声明中。一个好的编译器将会根据函数的定义体,自动地取消不值得的内联(这进一步说明了 inline 不应该出现在函数的声明中)