int* p1 = 0; // C++98
int* p2 = NULL; // C++98
int* p3 = nullptr; // C++11
void F(int);
void F(void*);
void Func() {
F(0); // 调用void F(int)
F(NULL); // 可能调用void F(int),取决于NULL的宏定义值
F(nullptr); // 调用void F(void*)
}
int Add1(int a, int b); // 普通函数
constexpr int Add2(int a, int b); // constexpr函数
void Func() {
const int c1 = 10; // 编译期常量
const int c2 = c1 + 1; // 编译期常量
const int c3 = Add1(1, 2); // 运行时常量
int x1 = 10; // 非常量
constexpr int ce1 = 10; // 编译期常量
constexpr int ce2 = ce1 + 1; // 编译期常量
// constexpr int ce3 = Add1(1, 2); // 编译出错,Add1无法在编译期计算出结果
constexpr int ce4 = Add2(1, ce1); // 正确, 1和ce1都是编译期常量,Add2将在编译期计算出结果
// constexpr int ce5 = Add2(1, x1); // 错误, x1不是编译期常量,Add2运作方式与Add1相同
// 定义数组,数组维度必须是编译期常量
int arr1[10]; // 正确
int arr2[c1]; // 正确
// int arr3[c3]; // 错误
// int arr4[x1]; // 错误
int arr5[ce1]; // 正确
}
using alias = type;
// 为char*定义别名
typedef char* cstring;
using cstring = char*;
// 定义函数指针类型别名
typedef void(*pFunc)(int, int); // pFunc是函数指针类型
using pFunc = void(*)(int, int); // pFunc是函数指针类型
using Func = void(int, int); // Func是函数类型, 不是指针类型,需要注意
void Func() {
// 初始化
int x1; // 未初始化,存在风险
// auto x2; // 编译错误
auto x3 = 4; // x3是int类型
auto x4 = x3; // x4是int类型,拷贝x3的值
auto& x5 = x3; // x5是int&类型,指向x3
const auto& x5 = x3; // x6是const int&类型,指向x3
// 避免类型转换
std::vector vec = {1, 2, 3, 4};
int count1 = vec.size(); // 发生隐式类型转换, 编译告警
auto count2 = vec.size(); // 正确接收size()函数的返回值
// 简化变量定义
std::map int2StrMap;
int2StrMap.insert(std::make_pair(1, "hello"));
std::map::iterator iter1 = int2StrMap.begin(); // C++98
auto iter2 = int2StrMap.begin(); // C++11
// 接收lambda表达式
std::function func = [](int a) {
return 5 + a;
};
auto lambda1 = [](int a) {
return 5 + a;
};
}
for (declaration : expression) {
statement
}
std::vector vec = {1, 2, 3, 4, 5, 6, 7, 8, 9};
// C++98
for (std::vector::iterator iter1 = vec.begin(); iter1 != vec.end(); ++iter1) {
std::cout << *iter;
}
for (int i = 0; i < vec.size(); ++i>) {
std::cout << vec[i];
}
// C++11
for (auto val : vec) {
std::cout << val;
}
// 捕获列表中为lambda所在函数中定义的局部变量,如果没有mutable声明,则不能修改这些捕获的变量
// 形参列表,返回类型,函数体与其他普通函数一样
[捕获列表](形参列表) mutable -> 返回类型 {
函数体
}
void Func() {
auto f = []{
return 5;
}
// 调用lambda表达式
std::cout << f() << std::endl;
}
void Func() {
int x1 = 5;
const int x2 = 10;
// 显式值捕获
auto lambda1 = [x1, x2](int a) {
return x1 + x2 + a;
};
// 显式值捕获x1,显式引用捕获x2
auto lambda2 = [x1, &x2](int a) {
return x1 + x2 + a;
};
// 默认值捕获x1,x2
auto lambda3 = [=](int a) {
return x1 + x2 + a;
};
// 默认引用捕获x1,x2
auto lambda4 = [&](int a) {
return x1 + x2 + a;
};
// 默认值捕获x1,显式引用捕获x2
auto lambda5 = [=, &x2](int a) {
return x1 + x2 + a;
};
// 默认引用捕获x1,显式值捕获x2
auto lambda6 = [&, x2](int a) {
return x1 + x2 + a;
};
}
int x = 10;
auto lambda1 = [x](int a) {
return x + a;
};
// 等价于
class TempUniqueName {
public:
TempUniqueName(int x) : x_(x)
{}
int operator()(int a) {
return x_ + a;
}
private:
int x_;
};
TempUniqueName lambda1(x);
auto newCallable = std::bind(callable, argList);
bool IntCompare(int a, int b) {
return a > b;
}
class IntComparator {
public:
bool operator()(int a,int b) {
return a > b;
}
}
void Func() {
std::vector vec = {1, 2, 3, 4, 5, 6, 7, 8, 9};
// 函数指针
std::sort(vec.begin(), vec.end(), IntCompare);
// 函数对象
std::sort(vec.begin(), vec.end(), IntComparator());
// lambda
std::sort(vec.begin(), vec.end(), [](int a, int b){
return a > b;
});
}
void F1(int a1, int a2, double b1, double b2);
// 包装F1,使其只需传入a1和b1参数, a2和b2使用给定值
void Func() {
// std::bind
auto newF1 = std::bind(F1, std::placeholders::_1, 100, std::placeholders::_2, 10.0);
newF1(50, 20.0); // 等价于F1(50, 100, 20.0, 10.0)
// lambda
auto lambdaF1 = [](int a1, double b1) {
F1(a1, 100, b1, 10.0);
}
lambdaF1(50, 20.0);
}
=default
来显式的要求编译器生成合成的版本,这些合成的版本会逐成员调用其对应的特种函数, 例如合成的默认构造函数: 对于内置类型,执行默认初始化(随机值),对于类类型,调用其默认构造函数=default
声明的函数是隐式内联的,如果不希望是内联函数,则需要将=default
声明放在类外=delete
要求编译器不定义这个函数,=delete
声明必须出现在函数第一次声明的时候=delete
,那么编译器将不允许创建该类型的变量或临时对象,因为无法销毁该类的成员 // = default
class DefaultMember {
public:
DefaultMember() = default; // 合成默认构造函数, 隐式内联
DefaultMember(const DefaultMember&); // 合成拷贝构造函数, 非内联
DefaultMember& operator=(const DefaultMember&); // 合成拷贝赋值运算符,非内联
~DefaultMember() = default; // 合成析构函数, 隐式内联
};
DefaultMember::DefaultMember(const DefaultMember&) = default;
DefaultMember& DefaultMember::operator=(const DefaultMember&) = default;
// = delete
// C++98
class NonCopyable1 {
// 声明为private成员函数,不定义这些函数,无法访问,因此阻止了拷贝
NonCopyable1(const NonCopyable1&);
NonCopyable1& operator=(const NonCopyable1&);
public:
NonCopyable1();
~NonCopyable1();
};
// C++11
class NonCopyable2 {
public:
NonCopyable2();
~NonCopyable2();
// 删除的拷贝构造和拷贝赋值运算符
NonCopyable2(const NonCopyable2&) = delete;
NonCopyable2& operator=(const NonCopyable2&) = delete;
};
=default
显式声明这些函数=delete
声明拷贝构造函数和拷贝赋值运算符,而不是将其声明为private成员&&
表示, 右值引用不能绑定到一个左值上: int i = 42;
int& r = i; // r是左值引用
// int&& rr = i; // 错误, 右值引用不能绑定到左值
// int& r2 = i * 42; // 错误, i * 42是一个右值
const int& r3 = i * 42; // 可以将const左值引用绑定到右值
int&& rr2 = i * 42; // rr2是右值引用
// int&& rr3 = rr2; // 错误, rr2也是左值
std::move
将一个左值转换成右值引用,当使用std::move
后这个左值只能被赋值或销毁,而不能直接使用它: // 将左值i转换到右值,此后i可以被赋值,也可以直接销毁,在赋予i新值之前不能使用i的值
int&& rr4 = std::move(i);
noexcept
标识符,否则移动操作不会触发,编译器会转而调用拷贝操作: class Moveable {
static constexpr int ARRAY_SIZE = 10;
public:
Moveable(int length = ARRAY_SIZE) : data_(new int[length]), len_(length)
{}
~Moveable() {
Free();
}
// 拷贝控制
Moveable(const Moveable& other) : data_(new int[other.len_]), len_(other.len_) {
for (int i = 0; i < len_; ++i) {
data_[i] = other.data_[i];
}
}
Moveable& operator=(const Moveable& other) {
// copy and swap,能正确处理自赋值
Moveable tmp(other);
std::swap(data_, tmp.data_);
std::swap(len_, tmp.len_);
return *this;
}
// 移动控制
Moveable(Moveable&& other) noexcept : data_(other.data_), len_(other.len_) {
other.data_ = nullptr;
other.len_ = 0;
}
Moveable& operator=(Moveable&& other) noexcept {
// copy and swap,能正确处理自赋值
Moveable tmp(std::move(other));
std::swap(data_, tmp.data_);
std::swap(len_, tmp.len_);
return *this;
}
// 对于拷贝赋值运算符和移动赋值运算符,有一种简洁的实现方法
// 形参为Moveable类型,对左值调用拷贝构造,对右值调用移动构造
Moveable& operator=(Moveable other) {
std::swap(data_, other.data_);
std::swap(len_, other.len_);
return *this;
}
private:
void Free() {
if (data_) {
delete[] data_;
}
}
private:
int* data_;
int len_;
};
std::move
处理过的左值不要再使用其值说明:
class ImplicitConvertable {
public:
ImplicitConvertable(const std::string&);
};
class ExplicitConvertable {
public:
explicit ExplicitConvertable(const std::string&);
};
void F1(const ImplicitConvertable& obj);
void F2(const ExplicitConvertable& obj);
void Func() {
std::string str = "hello";
F1(str); // 正确,从str隐式构造一个ImplicitConvertable对象传入F1
// F1("hello") // 错误,编译器只会自动进行一步类型转换,这里需要两步转换
F1(ImplicitConvertable(str)); //正确
// F2(str); // 错误,explicit抑制了隐式转换
F2(ExplicitConvertable(str)); // 正确,只能显式构造ExplicitConvertable对象
}
override
来显式说明意在重写基类中的虚函数: class Base {
public:
virtual void F1(); // 虚函数第一次出现的地方添加virtual关键字
virtual void F2(int);
};
class Derived : public Base {
public:
void F1() override; // 正确,重写基类虚函数void F1();
// void F2() override; // 错误,基类没有void F2()的虚函数
// void F3() override; // 错误,基类没有void F3()的虚函数
virtual void F4(); // 正确, 派生类定义自己的虚函数
};
final
来阻止类被继承: class NoDerived final {}; // NoDerived不能被继承
// class D : public NoDerived {}; // 错误, 不能从NoDerived继承
noexcept
来制定一个函数不会抛出异常,这种函数可以简化调用处的代码,编译器也可以对该函数进行特殊优化.如果函数违反了异常说明而抛出了异常,那么程序将直接调用std::terminate结束程序: void Func() noexcept; // 承诺Func不抛出异常
对比:
建议:
// C库
int atoi (const char * str); // str转换到int
long int atol ( const char * str ); // str转换到long
double atof (const char* str); // str转换到double
// C++
std::string to_string (val); // val转换到字符串, val可以是任何算数类型
int stoi (const string& str, size_t* idx = 0, int base = 10); // str 转换到int
long stol (const string& str, size_t* idx = 0, int base = 10); // str 转换到long
float stof (const string& str, size_t* idx = 0); // str 转换到float
double stod (const string& str, size_t* idx = 0); // str 转换到double
void Func {
// 内置数组
int arr1[10] = {0};
for (auto val : arr1) {
std::cout << val;
}
for (int i = 0; i < 10; ++i>) {
std::cout << arr1[i];
}
// std::array
std::array arr2;
arr2.fill(0);
std::cout << arr2.front();
std::cout << arr2.back();
for (auto val : arr2) {
std::cout << val;
}
for (int i = 0; i < arr2.size(); ++i>) {
std::cout << arr2.at(i);
}
}
// 自定义类型
class Data {
public:
int Val();
const std::string& Str();
private:
int val_;
std::string str_;
};
// 比较准则,用于有序关联容器
struct DataCompare {
bool operator()(const Data& l, const Data& r) const {
return l.Val() < r.Val() ||((l.Val() == r.Val()) && l.Str() < r.Str());
}
};
// hash函数,用于无序关联容器
struct DataHash {
size_t operator()(const Data& data) const {
return std::hash()(data.Val()) ^ std::hash()(data.Str());
}
};
// 判等准则,用于无序关联容器
struct DataEqual {
bool operator()(const Data& l, const Data& r) const {
return (l.Val() == r.Val()) && (l.Str() == r.Str());
}
};
void Func() {
std::set orderedDataSet;
std::unordered_set unorderedDataSet;
}
// 自定义类型
struct Data {
Data() : val(0), str()
{}
Data(int v, const std::string& s) : val(v), str(s)
{}
int val;
std::string str;
}
void Func() {
// std::uniqu_ptr
std::uniqu_ptr up1; // 空指针
std::uniqu_ptr up2(new Data()); // 持有一个指向默认构造的Data对象的指针
std::uniqu_ptr up3 = std::make_unique();// C++14,持有一个指向默认构造的Data对象的指针
auto up4 = std::make_unique(1, "hello"); // C++14, 持有一个指向Data(1,"hello")对象的指针
// auto up5 = up2 // 错误, unique_ptr不可拷贝
auto up6 = std::move(up2); // 正确,unique_ptr可移动,资源所有权转移到up6, up2变成空指针
// std::shared_ptr与std::weak_ptr
std::shared_ptr sp1; // 空指针
std::shared_ptr sp2(new Data()); // 持有一个指向默认构造的Data对象的指针
std::shared_ptr sp3 = std::make_shared();// 持有一个指向默认构造的Data对象的指针
auto sp4 = std::make_shared(1, "hello"); // 持有一个指向Data(1,"hello")对象的指针
auto sp5 = sp2; // 正确, shared_ptr可拷贝
// auto sp6 = up3; // 错误, 不能从unique_ptr拷贝资源
auto sp7 = std::move(up3); // 正确, 从unique_ptr移动资源
std::weak_ptr wp1; // 空指针
std::weak_ptr wp2(sp2); // 持有指向sp3所持资源的弱引用
if (!wp2.expired()) { // 判断与wp2关联的share_ptr是否已失效
auto sp8 = wp2.lock();//若expired()为true, lock()返回空的shared_ptr,否则返回有效的shared_ptr
}
std::shared_ptr sp9(wp2); // 从weak_ptr构造shared_ptr, 如果若expired()为true则抛出异常
}
// C++98, 一般只能通过函数指针传递可调用对象
typedef int(*pAddFunc)(int, int);
int Add(int a, int b) {
return a + b;
}
void Invoke1(pAddFunc fn) {
int val = fn(2, 3);
std::cout << val;
}
void Func1() {
Invoke1(Add);
}
// C++11
using AddFunc = std::function;
struct Adder {
int operator()(int a, int b) {
return a + b;
}
};
void Invoke2(AddFunc fn) {
int val = fn(2, 3);
std::cout << val;
}
void Func2() {
Invoke2(Add);
Invoke2(Adder());
Invoke2([](int a, int b) {
return a + b;
});
}
// 可以接受任意类型的参数,只要该类型支持+运算
void Func() {
auto lambda1 = [](const auto& a, const auto& b) {
return a + b;
};
}
void Func() {
int num = 10;
// 以初始化捕获的方式捕获局部变量num到val中
// 需要注意: = 两侧是两个作用域, 左侧为lambda的作用域,右侧为函数的作用域
auto lambda2 = [val = num](int a) {
return val + a;
};
}
void Func() {
// C++11, 有多少种类型,就需要定义多少种lambda
std::vector intVec = {1, 2, 3, 4, 5};
auto lambda1 = [](int a) {
std::cout << a;
};
std::for_each(intVec.begin(), intVec.end(), lambda1);
std::vector doubleVec = {1.0, 2.0, 3.0, 4.0, 5.0};
auto lambda2 = [](double a) {
std::cout << a;
};
std::for_each(doubleVec.begin(), doubleVec.end(), lambda2);
std::vector strVec = {"hello, ", "zhong ", "dian ", "xing ", "fa"};
auto lambda3 = [](std::string a) {
std::cout << a;
};
std::for_each(strVec.begin(), strVec.end(), lambda3);
// C++14, 只需要定义一个泛型lambda
auto lambda4 = [](auto a) {
std::cout << a;
}
std::for_each(intVec.begin(), intVec.end(), lambda4);
std::for_each(doubleVec.begin(), doubleVec.end(), lambda4);
std::for_each(strVec.begin(), strVec.end(), lambda4);
}
void Func() {
// 在lambda中捕获一个只可移对象
std::unique_ptr up1 = std::make_unique(5);
// C++11, 十分麻烦
// C++14, 使用初始化捕获将up1移入lambda中
auto lambda1 = [up2 = std::move(up1)]() {
std::cout << *up2;
};
}
// C++11
constexpr int pow(int base, int exp) noexcept {
return (exp == 0 ? 1 : base * pow(base, exp - 1));
}
// C++14
constexpr int pow(int base, int exp) noexcept {
auto result = 1;
for (int i = 0; i < exp; ++i) {
result *= base;
}
return result;
}
// 二进制数字
// C++98及C++11:无
//C++14
int val = 0b1101000; // val的值为104
// 数字分隔符
int num1 = 1000000000; // C++98及C++11
int num2 = 1'000'000'000; // C++14
int num3 = 1'00000'00'00; // C++14,分隔符可以任意间隔
const/static(可选) auto(必选) &/&&(可选) [逗号分隔的标识符列表] = 表达式;
const/static(可选) auto(必选) &/&&(可选) [逗号分隔的标识符列表]{表达式};
const/static(可选) auto(必选) &/&&(可选) [逗号分隔的标识符列表](表达式);
int a[2] = {1,2};
auto [x,y] = a; // 创建 e[2],复制 a 到 e,然后 x 指代 e[0],y 指代 e[1]
auto& [xr, yr] = a; // xr 指代 a[0],yr 指代 a[1]
struct S {
int x1;
double y1;
};
S f();
auto [x, y] = f(); // x是x1的拷贝, y是y1的拷贝
void Func() {
std::map int2StrMap;
...
// C++98
std::map::iterator iter = int2StrMap.begin();
for (; iter != int2StrMap.end(); ++iter) {
std::cout << iter->first << ", " << iter-second;
}
// C++11
for (const auto& pair : int2StrMap) {
std::cout << pair.first << ", " << pair.second;
}
// C++17
for (const auto& [val, str] : int2StrMap) {
std::cout << val << ", " << str;
}
}
if/switch (初始化语句; 条件)
// 等价于
初始化语句;
if/switch (条件)
void Func() {
std::set intSet;
...
if (auto it = intSet.find(10); it != intSet.end()) {
std::cout << *it;
}
}