# C++11
C++11 is the second major version of C++ and the most important update since C98. A large number of changes were introduced to both standardize existing practices and improve the abstractions available to the C programmers.
# 原始字面量
C++11
中定义原始字面量的方式: R"×××(原始字面量)×××"
,其中 ()
两边的字符可以省略。原始字面量可以直接表示字符串的实际含义,而不需要额外的转义字符。以下三条语句是等效的:
string path1 = "D:\\Desktop\\tmp.txt"; | |
string path2 = R"(D:\Desktop\tmp.txt)"; |
# 指针空值类型 nullptr
nullptr
是一个空指针类型,在 C++
中其可以隐式的转化为任何其他类型的空指针类型.
NULL
在C++
中被定义为 0,这一点的设置主要是让目前的 C++ 兼容之前的 C 程序,0 可以隐式转化为其他类型的空指针类型。所以在C++
程序中如果想要表达的是空指针,建议使用nullptr
,如果使用NULL
则可能导致出乎意料的结果.
# 常量表达式 constexpr
const
在 C++
中有两个语义,变量只读和修饰常量。当 const
修饰常量时,其值在编译阶段就已经确定,而修饰变量时,则需要等到运行时才确定. C++
为了区别这两种语义引入常量表达式 constexpr
,用于修饰常量,在编译阶段确定其值.
constexpr
可以用于修饰函数(常量表达式函数),可以用于修饰普通函数 / 类成员函数、类的构造函数、模版函数。修饰函数时,需要满足如下条件:
- 函数必须有返回值,并且
return
返回的表达式必须是常量表达式 - 整个函数体中不能出现非常量表达式之外语句
- 修饰构造函数时,成员必须初始化,且初始化需要使用初始化列表
# 自动类型推导 auto
auto
可以自动推导变量类型;使用auto
声明的变量必须要进行初始化,以让编译器推导出它的实际类型,在编译阶段将auto
占位符替换成其真正的类型.- 对于有
const
和volatile
修饰的变量,auto
只能推断底层const
或volatile
,顶层const
或volatile
会被忽略,需要自己添加 - 不能在函数的参数中使用,不能用于定义数组,不能用于类的非静态成员的初始化(只能用于类的静态常量成员变量的初始化)
- 不能用于模版参数的类型推导
# 自动类型推导 decltype
我们希望从表达式(函数返回值)中推断出想要定义的变量的类型,但是却不想用表达式的值去初始化变量,这种情况
auto
显得无力了
推断表达式类型作为变量的定义类型
推断函数返回值(实际不会调用,仅推断),推导出的对象类型与函数返回值一致
当函数返回的是一个纯右值,需要忽略掉前面的
const
或volatile
无论是底层
const
或volatile
还是顶层const
或volatile
都会被保留表达式是一个左值,或者被
()
包围,使用decltype
推导出来是表达式类型的引用(如果有const
或volatile
则需要加上)int a = 10;
decltype(a) b = a; //b 的类型为 int
decltype((a)) c = a; //c 的类型为 int&,绑定 a 对象
# 返回值类型后置
# final
- 当用于修饰类时,表示该类不能被继承,
final
卸载类名后面 - 当修饰成员函数时,只能修饰虚函数,表示这个虚函数不能被重写,
final
写在函数后
# override
明确表明派生类中的函数重写了基类的虚函数, override
写在派生类函数(重写了基类虚函数)后面.
# default
C++
会为类默认生成一些特殊的成员函数,当我们想要使用时,可以不写让其默认生成,页可以使用 default
方式来显示定义
当我们自定义了有参构造函数时,系统也就不会生成默认构造函数,这是往往需要我们自定义或者使用
default
来显示定义无参构造函数,否则当程序调用无参构造函数时,会引发错误.
# delete
c++
中,如果开发人员没有定义特殊成员函数,那么编译器在需要特殊成员函数时候会隐式自动生成一个默认的特殊成员函数,而我们有时候想禁止对象的拷贝与赋值,可以使用 delete 修饰.
delete
关键字的用处:std::unique_ptr就是通过delete修饰来禁止对象的拷贝
,单例模式.
# explicit
专门用于修饰构造函数,表示只能显示的构造,不能被隐式转换.(用于防止调用构造函数隐式转换)
class Test{ | |
public: | |
Test(){}; | |
Test(int a) : m_data(a) {}; | |
private: | |
int m_data; | |
}; | |
Test tp = 2; // right! 进行了隐式的类型转换 | |
/* ------------------------ */ | |
class Test{ | |
public: | |
Test(){}; | |
explicit Test(int a) : m_data(a) {}; | |
private: | |
int m_data; | |
}; | |
Test tp = 2; // error! |
# long long
# 带作用域的枚举类型 enum
- 不带作用域的枚举类型
enum AColor { | |
kRed, | |
kGreen, | |
kBlue | |
}; | |
enum BColor { | |
kWhite, | |
kBlack, | |
kYellow | |
}; | |
cout << (kRed == kWhite) << endl; // 1 |
不带作用域的枚举类型可以自动转换成整形,且不同的枚举可以相互比较,代码中的红色居然可以和白色比较,这都是潜在的难以调试的 bug,而这种完全可以通过有作用域的枚举来规避.
- 带作用域的枚举类型
enum class AColor { | |
kRed, | |
kGreen, | |
kBlue | |
}; | |
enum class BColor { | |
kWhite, | |
kBlack, | |
kYellow | |
}; | |
cout << (AColor::kRed == BColor::kWhite) << endl; // compile error! |
# 非受限联合体 union
# 函数模版默认模版参数
在 C++98/03
标准中,类模版可以有默认的模版参数,如下:
template <typename T = int> | |
class Test{ | |
public: | |
Test(T tp) : a(tp){ | |
this->a = tp; | |
} | |
void print(){ | |
cout << this->a << endl; | |
} | |
private: | |
T a; | |
}; | |
Test<> t1(20.1); | |
t1.print(); // 20 | |
Test<double> t2(20.1); | |
t2.print(); // 20.1 |
但是不支持函数模版的默认模版参数, C++11
添加了对函数模版的默认参数的支持。当默认模版参数和模版参数自动推导同时使用时:
- 如果可以推导出参数类型,则使用推导出的参数类型
- 如果无法推导出参数类型,则使用模版默认的参数类型
template <typename T = long long, typename U = int> | |
void func(T t = 'A', U u = 10){ | |
cout << t << ' ' << u << endl; | |
} | |
func(10, 20); // func<int, int> | |
func<int>('a', 'b'); // func<int, char> | |
func<char>('a', 'b'); // func<char, char> | |
func<int, int>('a', 'b'); // func<int, int> | |
func(); // func<long long, int> |
# using
定义别名
在 C++
中,通过 typedef
重定义一个类型,语法格式为 typedef 旧的类型名 新的类型名;
通过 using
来定义类型别名语法格式 using 新的类型 旧的类型;
/* 给数据类型定义别名 */ | |
typedef long long ll; | |
using ll = long long; | |
/* 给函数指针定义别名 */ | |
typedef int(*func)(int, string); | |
using func = int(*)(int, string); | |
/* 给模版定义别名 */ |
# 委托构造函数
委托构造函数允许在同一个类的一个构造函数中调用该类内的其他构造函数,这样一来可以简化对变量进行初始化的操作了(不过调用关系中药防止闭环)
class Test{ | |
public: | |
Test(){}; | |
Test(int a){ | |
this->a = a; | |
} | |
Test(int a, int b) : Test(a){ | |
this->b = b; | |
} | |
Test(int a, int b, int c) : Test(a, b){ | |
this->c = c; | |
} | |
private: | |
int a, b, c; | |
}; |
# 继承构造函数
继承构造函数可以让派生类直接使用基类的构造函数,如果有一个派生类,我们希望派生类采用和积基类一样的构造方法,可以直接使用基类的构造函数,而不是重新写一遍构造函数.
class Base{ | |
public: | |
Base(){}; | |
Base(int a){ | |
this->a = a; | |
} | |
Base(int a, int b) : Base(a){ | |
this->b = b; | |
} | |
Base(int a, int b, int c) : Base(a, b){ | |
this->c = c; | |
} | |
int a, b, c; | |
}; | |
class Derived : public Base{ | |
public: | |
using Base::Base; | |
}; | |
Derived tp(1, 2, 3); |
除此之外,我们可以使用 using
在派生类中引入基类中被隐藏的成员函数 (重写)
/* | |
在派生类 Derived 中我们需要重定义 fun 成员函数,这样一来会导致基类中的所有 fun 函数(被重载)都会被隐藏,如果我们只是想修改 fun 无参函数的功能,其他 fun 有参的成员函数保留,就需要使用 using | |
*/ | |
class Base{ | |
public: | |
Base(){}; | |
Base(int a){ | |
this->a = a; | |
} | |
Base(int a, int b) : Base(a){ | |
this->b = b; | |
} | |
Base(int a, int b, int c) : Base(a, b){ | |
this->c = c; | |
} | |
int a, b, c; | |
void fun(){ | |
cout << "Base fun" << endl; | |
} | |
void fun(int a, int b){ | |
cout << "fun(" << a << "," << b << ")" << endl; | |
} | |
}; | |
class Derived : public Base{ | |
public: | |
using Base::Base; | |
using Base::fun; | |
void fun(){ | |
cout << "Derived fun" << endl; | |
} | |
}; |
# 列表初始化
# std::initializer_list
# 可调用对象
函数指针
void print(int num, string name){
cout << num << ' ' << name << endl;
}
using funcptr = void(*)(int, string);
具有
operator()
成员函数的类对象(仿函数)class Test{
public:
// 重载 ()
void operator()(string name){
cout << name << endl;
}
};
Test tp;
tp("Hello!"); // Hello!
# 可调用对象包装器
# 基于范围的 for
循环
# lamada
表达式
- 声明式的编程风格:就地匿名定义目标函数或函数对象,不需要额外写一个命名函数或函数对象
- 避免的代码膨胀和功能分散
- 在需要的时间和地点实现功能闭包
# 右值引用
左值
(location value, lvalue)
,存储在内存中,有明确的存储地址(可取地址)的数据,是具名的右值
(read value, rvalue)
,可以提供数据值的数据,不可取地址,是匿名的- 纯右值:非引用返回的临时变量、运算表达式产生的临时变量、原始字面量和
lambda
表达式 - 将亡值:
- 纯右值:非引用返回的临时变量、运算表达式产生的临时变量、原始字面量和
右值引用就是对一个右值的引用。因为右值是匿名的,我们只能通过引用的方式来找到它。通过右值引用,该右值可
重获新生
,其生命周期与右值引用类型变量的生命周期一样.
# 智能指针
shared_ptr
- 初始化方法:构造函数初始化、通过拷贝和移动构造函数、
make_shared()
、reset
- 查看引用计数
use_count()
- 获取原始指针
get()
- 初始化方法:构造函数初始化、通过拷贝和移动构造函数、
unique_ptr
werk_ptr
# 并发
# 参考博文
- [1] 爱编程的大丙 (subingwen.cn)
- [2] C++11 - cppreference.com
- [3] c++11 新特性,所有知识点都在这了! - 知乎 (zhihu.com)