nullptr
nullptr是c++11用来表示空指针新引入的常量值,在c++中如果表示空指针语义时建议使用nullptr而不要使用NULL,因为NULL本质上是个int型的0,其实不是个指针。举例:
void func(void *ptr) {
cout << "func ptr" << endl;
}
void func(int i) {
cout << "func i" << endl;
}
int main() {
func(NULL); // 编译失败,会产生二义性
func(nullptr); // 输出func ptr
return 0;
}
final & override
c++11关于继承新增了两个关键字:
final用于修饰一个类,表示禁止该类进一步派生和虚函数的进一步重载
struct Base final {
virtual void func() {
cout << "base" << endl;
}
};
struct Derived : public Base{ // 编译失败,final修饰的类不可以被继承
void func() override {
cout << "derived" << endl;
}
};
override用于修饰派生类中的成员函数,标明该函数重写了基类函数,如果一个函数声明了override但父类却没有这个虚函数,编译报错,使用override关键字可以避免开发者在重写基类函数时无意产生的错误。
struct Base {
virtual void func() {
cout << "base" << endl;
}
};
struct Derived : public Base{
void func() override { // 确保func被重写
cout << "derived" << endl;
}
void fu() override { // error,基类没有fu(),不可以被重写
}
};
default
C++11 引入default特性,多数时候用于什么构造函数为默认构造函数,如果类中有了自定义的构造函数,编译器就不会隐式生成默认构造函数,如下代码:
struct A {
int a;
A(int i) { a = i; }
};
int main() {
A a; // 编译出错
return 0;
}
上面代码编译出错,因为没有匹配的构造函数,因为编译器没有生成默认构造函数,而通过default,程序员只需在函数声明后加上“=default;”,就可将该函数声明为 defaulted 函数,编译器将为显式声明的 defaulted 函数自动生成函数体,如下:
struct A {
A() = default;
int a;
A(int i) { a = i; }
};
int main() {
A a;
return 0;
}
编译通过。
delete
c++中,如果开发人员没有定义特殊成员函数,那么编译器在需要特殊成员函数时会隐式自动生成一个默认的特殊成员函数,例如拷贝构造函数,赋值拷贝操作符:
struct A {
A() = default;
int a;
A(int i) { a = i; }
};
int main() {
A a1;
A a2 = a1; // 正确,调用编译器隐式生成的默认拷贝构造函数
A a3;
a3 = a1; // 正确,调用编译器隐式生成的默认拷贝赋值操作符
}
而我们有时候想禁止对象的拷贝与赋值,可以使用delete修饰,如下:
struct A {
A() = default;
A(const A&) = delete;
A& operator=(const A&) = delete;
int a;
A(int i) { a = i; }
};
int main() {
A a1;
A a2 = a1; // 错误,拷贝构造函数被禁用
A a3;
a3 = a1; // 错误,拷贝赋值操作符被禁用
}
explicit
explicit专用与修饰构造函数,表示只能显示构造,不可以被隐式转换,根据代码看explicit的作用:
不用explicit:
struct A {
A(int value) { // 没有explicit关键字
cout << "value" << endl;
}
};
int main() {
A a = 1; // 可以隐式转换
return 0;
}
使用explicit:
struct A {
explicit A(int value) {
cout << "value" << endl;
}
};
int main() {
A a = 1; // error,不可以隐式转换
A aa(2); // ok
return 0;
}
constexpr
先回顾下const的作用:
const字面意思为只读,可用于定义变量,表示变量是只读的,不可以更改,如果更改,编译期间就会报错。
主要用法如下:
- 用于定义常量,const的修饰的变量不可更改。
const int value = 5;
- 指针也可以使用const,这里有个小技巧,从右向左读,即可知道const究竟修饰的是指针还是指针所指向的内容。
char *const ptr; // 指针本身是常量
const char* ptr; // 指针指向的变量为常量
- 在函数参数中使用const,一般会传递类对象时会传递一个const的引用或者指针,这样可以避免对象的拷贝,也可以防止对象被修改。
class A{};
void func(const A& a);
const修饰类的成员变量,表示是成员常量,不能被修改,可以在初始化列表中被赋值。
class A {
const int value = 5;
};
class B {
const int value;
B(int v) : value(v){}
};
- 修饰类成员函数,表示在该函数内不可以修改该类的成员变量。
class A{
void func() const;
};
- 修饰类对象,类对象只能调用该对象的const成员函数。
class A {
void func() const;
};
const A a;
a.func();
C++ 11标准中,为了解决 const 关键字的双重语义问题,保留了 const 表示“只读”的语义,而将“常量”的语义划分给了新添加的 constexpr 关键字。因此 C++11 标准中,建议将 const 和 constexpr 的功能区分开,即凡是表达“只读”语义的场景都使用 const,表达“常量”语义的场景都使用 constexpr。
有读者可能会问,“只读”不就意味着其不能被修改吗?答案是否定的,“只读”和“不允许被修改”之间并没有必然的联系,举个例子:
#include <iostream>
using namespace std;
int main()
{
int a = 10;
const int & con_b = a;
cout << con_b << endl;
a = 20;
cout << con_b << endl;
}
可以看到,程序中用 const 修饰了 con_b 变量,表示该变量“只读”,即无法通过变量自身去修改自己的值。但这并不意味着 con_b 的值不能借助其它变量间接改变,通过改变 a 的值就可以使 con_b 的值发生变化。
enum class
enum class 时c++11新增的有作用域的枚举类型,看代码:
不带作用域的枚举代码
enum AColor {
kRed,
kGreen,
kBlue
};
enum BColor {
kWhite,
kBlack,
kYellow
};
int main() {
if (kRed == kWhite) {
cout << "red == white" << endl;
}
return 0;
}
如上代码,不带作用域的枚举类型可以自动转换成整形,且不同的枚举可以相互比较,代码中的红色居然可以和白色比较,这都是潜在的难以调试的bug,而这种完全可以通过有作用域的枚举来规避。
有作用域的枚举代码:
enum class AColor {
kRed,
kGreen,
kBlue
};
enum class BColor {
kWhite,
kBlack,
kYellow
};
int main() {
if (AColor::kRed == BColor::kWhite) { // 编译失败
cout << "red == white" << endl;
}
return 0;
}
使用带有作用域的枚举类型后,对不同的枚举进行比较会导致编译失败,消除潜在bug,同时带作用域的枚举类型可以选择底层类型,默认时int,可以改成char等别的类型。
enum class AColor : char {
kRed,
kGreen,
kBlue
};
sizeof
C++11中sizeof可以用在类的数据成员上,看代码:
C++11前,希望对类的数据成员进行sizeof需要先创建类对象,之后再对类对象的数据成员使用sizeof:
struct A {
int data[10];
int a;
};
int main() {
A a;
cout << "size " << sizeof(a.data) << endl;
return 0;
}
C++11后:
struct A {
int data[10];
int a;
};
int main() {
cout << "size " << sizeof(A::data) << endl;
return 0;
}
想知道类中数据成员的大小在c++11中是不是方便了许多,而不需要定义一个对象,在计算对象的成员大小。
assertion与static_assertion
assertion
断言是一条检测假设成立与否的语句。
如果断言的语句为True就pass通过,程序不会报错。
如果断言的语句为False就抛出异常,中断程序!
特别注意:
使用断言的目的是帮助调试逻辑bug,一旦发现错误,要立即解决!
而不是把assert()放在那里当成正常程序的一部分!
尽管IDE有单步调试功能,但是耗时,用上断言能更快的调试逻辑bug;
static_assert
c++11引入static_assert声明,用于在编译期间检查,如果第一个参数值为false,则打印message,编译失败。
C++11的静态断言static_assert,与assert不同static_assert静态断言是在编译时断言检查;
static_assert常用在模版编程中 ,对写库的作者用处大。
语法:
static_assert ( bool_constexpr, message)
(1)bool_constexpr: 编译期常量表达式,可转换为bool 类型;
(2)message: 字符串字面量 ,是断言失败时显示的警告信息。自C++17起,message是可选的;
使用 static_assert的示例:
#include<iostream>
int main()
{
static_assert(0, "64-bit code generation is not supported.");
return 0;
}