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);

bitset

头文件:bitset

构造函数

原型 说明 说明2 样例
bitset b; n位,每一位均为0 constexpr
bitset b(u); unsigned long long低n位的拷贝 constexpr
bitset b(s, pos, m, zero, one); 从pos开始m个字符的拷贝。0和1的字符支持自定义
bitset b(cp, pos, m, zero, one); 和上一个一样,换成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:栈空间不足

注意点

  1. 【.】在正则中表示所有字符,如果想匹配字面意义上的【.】的话,需要加通配符。但是【\】在正则中也是特殊字符,所以在正则中【\\】才表示去正则,即【\\.】

获取所有匹配结果

  • 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

用来动态判断类型的函数。

  1. 顶层const会被忽略
  2. 如果表达式是一个引用,会返回该引用所引对象的类型(即不包含&)
  3. 作用数组或函数时,不会执行向指针的类型转换

当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);
}

这个写法的优点:

  1. 即使是继承关系的类,也能确保不同类之间不相等
  2. 派生类中,可以重载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声明
上一篇:C# 正则表达式去除字符串中的汉字


下一篇:正则表达式