c++学习笔记(九)—— 进阶内容
特殊库
tuple
模糊获取tuple的成员数量和类型
typedef decltype(item) trans;
// 获取成员数量
size_t sz = tuple_size(trans)::value;
// cnt和item中的第二个成员类型相同
tuple_element<1, trans>::type cnt = get<1>(item);
解包
std::tuple<char, int, long, std::string> fourth('A', 2, 3, "4");
// 定义变量,保存解包结果
char tuple_0 = '0';
int tuple_1 = 0;
long tuple_2 = 0;
std::string tuple_3("");
// 使用std::tie, 依次传入对应的解包变量
std::tie(tuple_0, tuple_1, tuple_2, tuple_3) = fourth;
bitset
头文件:bitset
构造函数
原型 | 说明 | 说明2 | 样例 |
---|---|---|---|
bitset |
n位,每一位均为0 | constexpr | |
bitset |
unsigned long long低n位的拷贝 | constexpr | |
bitset |
从pos开始m个字符的拷贝。0和1的字符支持自定义 | ||
bitset |
和上一个一样,换成char* |
操作
置位:x位变成1
复位:x位变成0
原型 | 说明 |
---|---|
b.any() | 是否存在置位 |
b.all() | 是否所有位都置位 |
b.none() | 不存在置位? |
b.count() | 置位的位数 |
b.size() | constexpr,返回位数 |
b.test(pos) | 如果pos是置位的则为true,否则为false |
b.set(pos, v) | 设置。v默认为true |
b.set() | 将b中所有位置位 |
b.reset(pos) | pos位置复位 |
b.reset() | 所有位复位 |
b.flip(pos) | 反转pos位 |
~b[pos] | 和上一个等价 |
b.flip() | 反转所有位 |
b[pos] | 如果b是const,则返回bool |
b.to_ulong() | 拼成unsigned long。如果放不下,抛出overflow_error异常 |
b.to_ullong() | 同上,拼成unsined long long |
b.to_string(zero, one) | 拼成string。zero和one默认为'0'和'1' |
os << b | 打印0和1的字符串 |
is >> b | 当下一位不是0或1,或者已经读满时,读取停止 |
正则表达式
头文件:regex
默认情况下regex的正则语言是:ECMAScript(ECMA-262规范)
搜索方法
- regex_search:如果输入序列的一个子串和表达式匹配,返回true
- regex_match:如果整个序列都匹配,返回true
这俩接口的参数是一样的:
(seq, m, r, mft)
(seq, r, mft)seq: 匹配串
r: 正则串,regex对象
m:match容器,用于存储结果
mft:匹配参数,详细见后
regex
- regex r(re, f=ECMAScript)
re表示一个正则表达式,f表示标志,详细见下 - r1 = re
regex支持直接赋值 - r1.assign(re, f)
和上面等价 - r.mark_count()
r中表达式的数目 - r.flags
返回r的标志集
标志集
定义在regex和regex_constants::syntax_option_type中
- icase: 在匹配过程中忽略大小写
- nosubs:不保存匹配的子表达式
- optimize:执行速度优先于构造速度
- ECMAScript:使用ECMA-262指定的语法
- basic:使用POSIX基本的正则表达式语法
- extended:使用POSIX扩展的正则表达式语法
- awk:使用POSIX的awk语言的语法
- grep:使用POSIX的grep语言的语法
- egrep:使用POSIX的egrep语言的语法
匹配结果容器
- smatch:string类型的输入序列
- cmatch:char*
- wsmatch:wstring
- wcmatch:wchar*
smatch会存储n+1个结果(n表示表达式的数量),第0个表示整个匹配,后面跟上每个子表达式的结果。
异常处理
如果正则表达式错了,会抛出regex_error
错误
- error_collate:无效的元素校对请求
- error_ctype:无效的字符类
- error_escape:无效的转义字符或无效的尾指转义
- error_backref:无效的向后引用
- error_brack:不匹配的方括号
- error_paren:不匹配的小括号
- error_brace:不匹配的花括号
- error_badbrace:{}中无效的范围
- error_range:无效的字符范围,如[z-a]
- error_space:内存不足
- error_badrepeat:重复字符
*、?、+或{
之前没有有效的正则表达式 - error_complexity:要求的匹配过于复杂
- error_stack:栈空间不足
注意点
- 【.】在正则中表示所有字符,如果想匹配字面意义上的【.】的话,需要加通配符。但是【\】在正则中也是特殊字符,所以在正则中【\\】才表示去正则,即【\\.】
获取所有匹配结果
- sregex_iterator it(b, e, r);
- sregex_iterator end; // 尾后迭代器/空迭代器
example:
string search_s("receipt freind theif receive");
string pattern("[^c]ei");
pattern = "[[:alpha:]]*" + pattern + "[[:alpha:]]*";
regex r(pattern, regex::icase);
for (sregex_iterator it(search_s.begin(), search_s.end(), r), end_it; it != end_it; ++it)
{
cout << it->str() << endl;
}
/*
输出结果:
freind
receive
*/
smatch操作
- m.size()
如果匹配失败,返回0;否则返回最近一次匹配的正则表达式中子表达式的数目 - m.prefix()
ssub_match对象,表示当前匹配之前的序列 - m.suffix()
ssub_match对象,表示当前匹配之后的序列 - m.str(n)
第n个子表达式匹配的string - m[n]
对应第n个子表达式的ssub_match对象
ssub_match操作
- matched
是否匹配了 - first/second
匹配元素首尾迭代器,未匹配则两者相等 - length()
匹配的大小 - str()/s = ssub
匹配部分
regex_replace
using std::regex_constants::format_default;
string fmt = "$2.$5.$7";
regex r(phone),
string num = "(908)555-1800";
cout << regex_replace(num, r, fmt, format_default) << endl;
// 输出:908.555.1800
随机数
因为rand库存在局限性,所以c++程序员应该使用default_random_engine
类和恰当的分布类对象
- 随机数引擎类:生成随机unsigned整数序列
- 随机数分布类:使用引擎返回服从特定概率分布的随机数
随机数发生器:分布对象和引擎对象的组合
一般使用方式:
uniform_real_distribution<double> u(0, 1); //分布类一般为xxx_distribution,详细数据搜索相关文档
default_random_engine e; //引擎类一般为xxx_engine,详细数据搜索相关文档
cout << u(e) << endl;
IO操作符
用于控制IO如何格式化的细节,如果整型值是几进制、浮点数的精度、输出元素的宽度等
注: 当操作符改变流的格式状态时,通常改变后的状态对所有后续IO都生效。因此要注意及时还原
使用样例
//控制布尔值的格式
cout << true << " " << false << endl
<< boolalpha
<< true << " " << false << endl;
/*
预期结果:
1 0
true false
*/
操作符清单
前面带(*)
的表示默认状态
- boolalpha: 将true和false输出为字符串
- (*) noboolalpha: T/F输出为1/0
- showbase:对整型值输出表示进制的前缀
- (*) noshowbase:不输出前缀
- showpoint:对浮点数总是显示小数点
- (*) noshowpoint:只有当浮点值包含小数部分时才显示小数点
- showpos:非负数显示+
- (*) noshowpos:非负数不显示+
- uppercase:十六进制打印0X,科学计数法打印E
- (*) nouppercase:十六进制打印0x,科学计数法打印e
- (*) dec:整型值显示为十进制
- hex:整型值显示为十六进制
- oct:整型值显示为八进制
- left:在值的右侧添加填充字符
- right:在值的左侧添加填充字符
- internal:在符号和值之间添加填充字符
- fixed:浮点数显示为定点十进制
- scientific:浮点数显示为科学计数法
- hexfloat:浮点数显示为十六进制(c++11)
- defaultfloat:重置浮点数格式为十进制(c++11)
- unitbuf:每次输出操作后都刷新缓冲区
- (*) nounitbuf:恢复正常的缓冲区刷新方式
- (*) skipws:输出运算符跳过空白符
- noskipws:输出运算符不跳过空白符
- flush:刷新ostream缓冲区
- ends:插入空字符,刷新缓冲区
- endl:冲入换行符,刷新缓冲区
iomanip库
- setfill(ch):用ch填充空白
- setprecision(n):设置精度
- setw(w):控制读写值宽度
- setbase(b):将输出为b进制
这些方法只控制下一个输出的状态,不改变输出流
未格式化的IO操作
单字节处理:
- is.get(ch)
- os.put(ch)
- is.get()
- is.putback(ch)
- is.unget()
- is.peek()
多字节处理:
- is.get(sink, size, delim): 读size个字符,存到sink里,直到读到delim
- is.getline(sink, size, delim)
- is.read(sink, size)
- is.gcount(): 返回上一个为格式化读取操作从is读取的字节数
- os.write(source, size)
- is.ignore(size, delim)
流随机访问
只支持fstream和sstream
下列操作g
表示输入流,p
表示输出流
- tellg(): 返回当前标记的位置
- tellp()
- seekg(pos): 将标记重定位到绝对位置
- seekp(pos)
- seekg(off, from):定位到from之前或之后的off个字符
- seekp(off, from)
读取文件样例:
ifstream oFile;
oFile.open("xxx", ios::in | ios::binary);
if (oFile)
{
oFile.seekg(0, oFile.end);
size_t iSize = oFile.tellg();
char* sink = new char[iSize + 1];
oFile.seekg(0, oFile.beg);
oFile.read(sink, iSize);
oFile.close();
}
用于大型程序的工具
异常处理
构造函数异常
处理构造函数初始值异常的唯一方法是将构造函数写成函数try语句块
template <typename T>
Blob<T>::Blob(std::initializer_list<T> il) try: data(std::make_shared<std::vector<T>>(il)) {
//空函数体
} catch(const std::bad_alloc &e) {
handle_out_of_memory(e);
}
异常类层次
- exception
- bad_cast
- bad_alloc
- runtime_error
- overflow_error
- underflow_error
- range_error
- logic_error
- domain_error
- invalid_argument
- out_of_error
- length_error
命名空间
内联命名空间
inline namespace PersonCode {
void func1();
}
namespace PersonCode { // 隐式内联
void func2();
}
namespace PersonCode2 {
void func3();
}
// 另一个文件
namespace MainCode{
#include "PersonCode.h"
#include "PersonCode2.h"
}
MainCode::func1(); // 可以直接使用
MainCode::func2();
MainCode::PersonCode2::func3(); // 需要一层层申明
未命名的命名空间
仅在特定的文件内部有效,其作用范围不会横跨多个不同的文件。
如果两个文件都含有未命名的命名空间,则这两个空间互相无关。
命名空间别名
一个命名空间可以有好几个同义词或别名,所有别名都与命名空间原来的名字等价。
namespace person = PersonCode;
特殊工具和技术
运行时类型识别(RTTI)
有时会用在不同库指针或引用直接交互的情况
指针类型动态转换(dynamic_cast)
// base含有虚函数,Derived是base的公有派生类
if (Derived *dp = dynamic_cast<Derived*>(bp))
{
// 使用dp指向的Derived对象
}
else
{
// 使用bp指向的base对象
}
引用类型动态转换(dynamic_cast)
void f(const base &b)
{
try {
const &d = dynamic_cast<const Derived&>(b);
// 使用b引用的Derived对象
}
catch (std::bad_cast)
{
// 处理转换失败的情况
}
}
typeid
用来动态判断类型的函数。
- 顶层const会被忽略
- 如果表达式是一个引用,会返回该引用所引对象的类型(即不包含&)
- 作用数组或函数时,不会执行向指针的类型转换
当typeid作用于指针时(而非指针所指的对象),返回的结果是该指针的静态编译时的类型
Derived* dp = new Derived();
Base* bp = dp;
cout << boolalpha;
if (typeid(*bp) == typeid(*dp))
{
cout << "test1: " << true << endl;
}
if (typeid(*bp) == typeid(Derived))
{
cout << "test2: " << true << endl;
}
cout << false << endl;
// 运行结果: false
typeid操作
返回值类型:type_info
typeid(Derived).name(); // 输出类型名,具体输出内容依赖编辑器
typeid(Derived).before(typeid(Base)); //判断前后顺序,顺序关系依赖于编辑器
使用RTTI
bool operator== (const Base &lhs, const Base &rhs)
{
return typeid(lhs) == typeid(rhs) && lhs.equal(rhs);
}
这个写法的优点:
- 即使是继承关系的类,也能确保不同类之间不相等
- 派生类中,可以重载equal
枚举
有两种:限定作用域的和不限定作用域的
- enum:不限定
- enum class:限定,C++11添加
不限定作用域的可以隐式转化成int,限定作用域的不可以,所以作为库的对外接口时,不能用enum class。
C++11可以【设置潜在类型】和【提前声明enum】
设置潜在类型
默认类型是int
enum intValues : unsigned long long
{
long_type = 111111111111111111111111ULL,
}
提前声明enum
enum intValues : unsigned long long;
类成员指针
可以在类的外部定义指向数据成员和函数成员的指针
class Math
{
public:
int x, y;
Math() = default;
Math(int x1, int y1) : x(x1), y(y1) {};
int GetY()
{
return y;
}
int SetAndGetY(int y2)
{
this->y = y2;
return this->y;
}
bool SetXY(int x1, int y1)
{
x = x1;
y = y1;
return true;
}
private:
int z = 6;
int GetZ()
{
return z;
}
};
int main()
{
Math obj(1, 2);
const int Math::*p = &Math::x;
cout << obj.*p << endl;
auto pmf = &Math::GetY;
cout << (obj.*pmf)() << endl;
int (Math:: * pmf2)(int);
pmf2 = &Math::SetAndGetY;
cout << (obj.*pmf2)(6) << endl;
cout << (obj.*pmf)() << endl;
using SetXY = bool (Math::*)(int x1, int y1);
SetXY set_xy = &Math::SetXY;
cout << boolalpha << (obj.*set_xy)(1, 2) << endl;
// 不可以改成obj.*set_xy(1, 2)
}
因为函数调用运算符的优先级较高,所以在声明指向成员函数的指针并使用这样的指针进行函数调用时,括号必不可少: (obj.*set_xy)(1, 2)
union
联合是一种特殊的类,一种节省空间的类。
union可以定义包含构造函数和析构函数在内的成员函数。但是因为它既不能继承其他类,也不能作为基类使用,所以union中不能含有虚函数。
匿名union
union {
char cval;
int ival;
};
匿名union不能包含private和protect,也不能定义成员函数
匿名的union在该作用域内可以直接访问成员,因此常用于管理类中的成员
局部类
局部类的所有成员(包括函数)都必须完整定义在类的内部。因此局部类和嵌套类的作用还是差的很远。
局部类只能访问外层作用域定义的类型名、静态变量以及枚举成员。普通局部变量无法访问。
不可移植的特性
因机器而异的特性,通常换台机器就要重写。
位域
位域在内存中的布局是与机器相关的
class File {
unsigned int mode : 2;
}
volatile
当对象的值可能在程序的控制或检测之外被改变时,应该将该对象声明为volatile
,告诉编辑器不应对这样的对象进行优化。
注:合成的拷贝/移动构造函数以及赋值运算符,不能用于volatile对象,除非自定义。
链接指示:extern "C"
用于指出这是非c++函数用的语言。常用于库的接口。
在构建dll时,通常还会带上_declspec(dllexport)
声明导出函数、类、对象。
尤其是函数指针,有没有指示是不同的
void (*pf1)(int); // 指向一个c++函数
#ifdef __cplusplus // 兼容c和c++编译同一个源文件
extern "C" void (*pf2)(int); // 指向一个c函数
#endif
pf1 = pf2; // 错误:类型不同
tips
- 正则表达式是在运行时编译的,所以操作是非常慢的,注意避免多余的regex构建。尤其在循环中,尽可能在循环外创建regex
- 循环中使用随机数需要注意seed的问题,同一个循环中的time(0)是相同的,会生成相同的结果
- 在栈展开的过程中,运行类类型的局部对象的析构函数。因为这些析构函数是自动执行的,所以他们不应该抛出异常。一旦在栈展开的过程中析构函数抛出了异常,并且析构函数自身没能捕获到该异常,则程序将被终止。
- 头文件最多只能在它的函数或命名空间内使用using指示或using声明
- 在链接命令中给出所依赖的库时,需要注意库之间的依赖顺序,依赖其他库的库一定要放到被依赖库的前面