一、原始字面量——原文链接
原始字面量 R可以直接得到其原始意义的字符串(用于简化:win路径转换、字符串换行需要加连接符)
定义方式
//R “xxx(原始字符串)xxx”
//这种情况原本在 win下是需要使用\\的
string str2 = R"(D:\hello\world\test.text)";
cout << str2 << endl;
输出:
D:\hello\world\test.text
二、final + override——原文链接
final
限制某个类不能被继承
//这个类就不能再被继承
class Child final: public Base
{
public:
void test()
{
cout << "Child class...";
}
};
限制某个虚函数不能被重写
只能修饰虚函数
//基类中
virtual void test()
{
cout << "Base class...";
}
//子类中
void test() final
{
cout << "Child class...";
}
//孙子类就不能再重写这个虚函数
override
明确表明将会重写基类的虚函数,这样可以保证重写的虚函数的正确性,也提高了代码的可读性。
class Base
{
public:
virtual void test()
{
cout << "Base class...";
}
};
class Child : public Base
{
public:
void test() override
{
cout << "Child class...";
}
};
上述指定了要重写父类的test()方法,使用了override关键字之后,假设在重写过程中因为误操作,写错了函数名或者函数参数或者返回值编译器都会提示语法错误,提高了程序的正确性,降低了出错的概率。
三、断言 快速定位违反了某些前提条件的程序错误——原文链接
动态断言
用于在运行时进行断言
//需要在程序中包含头文件<cassert>或<assert.h>
//对于程序调试来说,通常断言能够帮助程序开发者快速定位那些违反了某些前提条件的程序错误。
#include <iostream>
#include <cassert>
using namespace std;
// 创建一个指定大小的 char 类型数组
char* createArray(int size)
{
// 通过断言判断数组大小是否大于0
assert(size > 0); // 必须大于0, 否则程序中断
char* array = new char[size];
return array;
}
int main()
{
char* buf = createArray(0);
// 此处使用的是vs提供的安全函数, 也可以使用 strcpy
strcpy_s(buf, 16, "hello, world!");
cout << "buf = " << buf << endl;
delete[]buf;
return 0;
}
静态断言
静态断言static_assert,所谓静态就是在编译时就能够进行检查的断言,使用时不需要引用头文件
//基于64位Linux进行测试,使用静态断言验证当前操作系统是否是32位
// assert.cpp
#include <iostream>
using namespace std;
int main()
{
// 字体原因看起来是一个=, 其实这是两个=
static_assert(sizeof(long) == 4, "错误, 不是32位平台...");
cout << "64bit Linux 指针大小: " << sizeof(char*) << endl;
cout << "64bit Linux long 大小: " << sizeof(long) <<endl;
return 0;
}
32位系统与64位系统各数据类型对比:
四、noexcept 修饰的函数不会抛出异常——原文链接
noexcept 形如其名,表示其修饰的函数不会抛出异常
//使用方式1
double divisionMethod(int a, int b) noexcept
{
if (b == 0)
{
cout << "division by zero!!!" << endl;
return -1;
}
return a / b;
}
//方式2
double divisionMethod(int a, int b) noexcept(常量表达式);
参数:
常量表达式的结果会被转换成一个bool类型的值:
值为 true,表示函数不会抛出异常
值为 false,表示有可能抛出异常这里
不带常量表达式的noexcept相当于声明了noexcept(true),即不会抛出异常。
五、数值类型和字符串之间的转换——原文链接
数值转换为字符串
函数声明位于头文件中
// 头文件 <string>
string to_string (int val);
string to_string (long val);
string to_string (long long val);
string to_string (unsigned val);
string to_string (unsigned long val);
string to_string (unsigned long long val);
string to_string (float val);
string to_string (double val);
string to_string (long double val);
字符串转换为数值
// 定义于头文件 <string>
int stoi( const std::string& str, std::size_t* pos = 0, int base = 10 );
long stol( const std::string& str, std::size_t* pos = 0, int base = 10 );
long long stoll( const std::string& str, std::size_t* pos = 0, int base = 10 );
unsigned long stoul( const std::string& str, std::size_t* pos = 0, int base = 10 );
unsigned long long stoull( const std::string& str, std::size_t* pos = 0, int base = 10 );
float stof( const std::string& str, std::size_t* pos = 0 );
double stod( const std::string& str, std::size_t* pos = 0 );
long double stold( const std::string& str, std::size_t* pos = 0 );
参数:
str:要转换的字符串
pos:传出参数, 记录从哪个字符开始无法继续进行解析, 比如: 123abc, 传出的位置为3
base:若 base 为 0 ,则自动检测数值进制:若前缀为 0 ,则为八进制,若前缀为 0x 或 0X,则为十六进制,否则为十进制。
六、auto 自动类型推导——原文链接
使用auto声明的变量必须要进行初始化,以让编译器推导出它的实际类型,在编译时将auto占位符替换为真正的类型
auto x = 3.14; // x 是浮点型 double
auto y = 520; // y 是整形 int
auto z = 'a'; // z 是字符型 char
auto nb; // error,变量必须要初始化
auto double nbl; // 语法错误, 不能修改数据类型
当变量不是指针或者引用类型时,推导的结果中不会保留const、volatile关键字
当变量是指针或者引用类型时,推导的结果中会保留const、volatile关键字
auto的限制
不能作为函数参数使用。
不能用于类的非静态成员变量的初始化
不能使用auto关键字定义数组
无法使用auto推导出模板参数
auto的应用
用于STL的容器遍历。
#include <map>
int main()
{
map<int, string> person;
// 代码简化
for (auto it = person.begin(); it != person.end(); ++it)
{
// do something
}
return 0;
}
用于泛型编程
这里直接查看原文
七、decltype 自动类型推导——原文链接
它的作用是在编译器编译的时候推导出一个表达式的类型
int a = 10;
decltype(a) b = 99; // b -> int
decltype(a+3.14) c = 52.13; // c -> double
decltype(a+b*c) d = 520.1314; // d -> double
decltype推导的表达式可简单可复杂,auto只能推导已初始化的变量类型。
decltype 背后隐藏着很多的细节,以及应用方式,可以查看原文。
八、基于范围的for循环——原文链接
基于范围的for循环可以以简洁、统一的方式来遍历容器和数组
//语法
for (declaration : expression)
{
// 循环体
}
//案列
#include <iostream>
#include <vector>
using namespace std;
int main(void)
{
vector<int> t{ 1,2,3,4,5,6 };
cout << "遍历修改之前的容器: ";
for (auto &value : t)
{
cout << value++ << " ";
}
cout << endl;
return 0;
}
使用细节请查看原文
九、指针空值类型 - nullptr——原文链接
使用 nullptr 初始化空指针可以令我们编写的程序更加健壮
详细内容查看原文
十、Lambda表达式——原文链接
捕获列表
[] - 不捕捉任何变量
[&] - 捕获外部作用域中所有变量, 并作为引用在函数体内使用 (按引用捕获)
[=] - 捕获外部作用域中所有变量, 并作为副本在函数体内使用 (按值捕获)
拷贝的副本在匿名函数体内部是只读的
[=, &foo] - 按值捕获外部作用域中所有变量, 并按照引用捕获外部变量 foo
[bar] - 按值捕获 bar 变量, 同时不捕获其他变量
[&bar] - 按引用捕获 bar 变量, 同时不捕获其他变量
[this] - 捕获当前类中的this指针
让lambda表达式拥有和当前类成员函数同样的访问权限
如果已经使用了 & 或者 =, 默认添加此选项。
详细内容查看原文
十一、constexpr——原文链接
凡是表达“只读”语义的场景都使用 const,表达“常量”语义的场景都使用 constexpr。
详细内容查看原文
十二、using的使用——原文链接
using相较于typedef的优势在于定义函数指针别名时看起来更加直观,并且可以给模板定义别名。
using 新的类型 = 旧的类型;
// 使用举例
using uint_t = int;
// 使用typedef定义函数指针
typedef int(*func_ptr)(int, double);
// 使用using定义函数指针
using func_ptr1 = int(*)(int, double);
template <typename T>
using mymap = map<int, T>;
十三、委托构造和继承构造函数——原文链接
委托构造
委托构造函数允许使用同一个类中的一个构造函数调用其它的构造函数
//语法
Test(int max, int min):Test(max)
{
this->m_min = min > 0 && min < max ? min : 1;
}
继承构造
继承构造函数可以让派生类直接使用基类的构造函数
//语法
using Base::Base;