txt文档转存
//待补充—模糊的地方
//临时记录:memory allocator
《C++高级编程》4th
#pragma once 确保只编译一次
不要在头文件中使用using namespace std;
字面量:用于代码中编写数字或字符串:不同进制的数字表示,单字符,字符串。。。。。。
C++14,允许数字间用引号分隔:int a = 12’456
强制类型转换:
int a = (int) b; //c
int a = int(b); //c++ 很少用
int a = static_cast b; //建议用
if语句初始化器–c++17
if (< initializer>; < conditional_experssion>)
initializer 引入的变量只在if语句块中使用, 这样省略了++部分
switch case有同样的操作
C++17 结构化绑定 auto [x, y, z] = array<int, 3> 必须auto, 左数量必须一致
初始化列表< initializer_list>头文件中定义,initializer_list是个模板,可以接收多个参数:
#include < initializer_list>
using namespace std;
int makesum(initializer_list< int> lst)
{
int total = 0;
for(int value : lst)
{
total += value;
}
return total;
}
基于范围for循环 for(int i :arr)
C++11 统一初始化, 允许类和结果都是用{…}初始化类型,同事在数据发生缩短的时候编译报错
C++17
复制列表初始化 T obj = {arg1,…}------导出initializer_list< int>,不论左右两边数量是否一致,但类型要一致
直接列表初始化 T obj {arg1,…}---- 数量一致ok,不一致,error但是在C++17 之前是ok的
有区别
trailing return type 用来支持替代的函数法:
auto func(int i) ->int
{
reutrn i + 2;
}
C++14 类型推断
auto用途:1 编译时,自动推断变量类型 auto x=123 2.替代函数语法 3 函数返回类型推断 4 通用的 lambda表达式
因自动推断表达式类型,一次不需要引用限定符和const限定符,它会自动去除附加给变量的限定符,建立一个副本。如果要求cosnt 则:const auto& a;相当于又加回去了。
decltype:把表达式作为实参,计算出表达式类型。 int x = 123; decltype(x) y = 456;
不会去除任何附加给变量的限定符。decltype(func()) f1 = func();这里func()制定两次,C++14 优化为
dectype(auto) f2 = func();
C++ 中空指针 = nullptr。为防止释放指针指向的内存后再使用指针,可以将其设置为nulllptr
delete a; a = nullptr;
智能指针
< memory>中:在超出作用域,会自动释放内存
std::unique_ptr,类似普通指针,只属于它指向的对象,发生异常时也会释放资源,这个可以存储C风格数组
int * p = new int; ====> auto p = std::make_unique< int>(); c++14
之前的c++ std::unique_ptr< int> p(new int);
可以指向任意类型的内存,是个模板,因此需要<>指定内存类型
shared_ptr 允许数据多方所有。每次指定shared_ptr,都递增一个引用技术,拥有者+1。超出作用域,递减引用技术,为0则释放指针引用的对象,不能在这里存储数组。std::make_shared<>() 创建
weak_ptr可以观察shared_ptr,而不会改变shared_ptr的计数
异常处理(cpp编译器不强求捕获全部异常):堆栈变量的行为
函数定义异常行为,发生时,throw对应参数,try-catch捕获并抛出显示。
第三章-编码风格
-
注释–防止连自己都看不懂写了个啥,最好代码即注释
说明方法参数用途,解释代码块用途,添加代码之外的信息
注释风格
代码间用途,头部文件信息—Doxygen
2.代码分解
一个函数尽量功能单一,然后组合完成任务,别一个函数写几百行,或者横向老长老长成一坨
重构的技术
抽象
封装字段:将字段设置为私有,使用类提供的方法访问
类型通用:创建通用的类型,更广泛的实现代码共享
分割
提取方法:将大方法的一部分转换为便于理解的新方法
提取类: 将现有类的部分代码转移到新类中(代码复用?)
增强代码名称和位置的技巧
移动方法或字段:到更合适的类或源文件中
重命名方法或字段:直接体现用途
上移:OOP中,移动到基类
下移:OOP中,移动到派生类设计
模块分解:将编写的部分代码放在模块,方法或函数中
3.命名:看名字能知道并区分是个啥玩意儿
i,j,row,col 计数
前缀
m, m_ 成员
s:静态的
b, is:bool值
n,mNum:数字
数据成员mStatus,getStatus() setStatus() 之类的等等
大写的通常是正式的,不变的,变量和数据成员以小写字母开头,下划线间隔单词。
函数或方法首字母大写,类也可以,总之看约定
匈牙利命名法
常量放到名称空间中,防止名字被多次使用
4.使用具有风格的语言特性
使用常量:替代并解释魔法数字
使用引用:替代指针。安全,不用考虑指针可能带来的内存误操作
自定义异常信息
5.格式-统一按照约定的来
专业的C++软件设计
第四章:设计专业的C++程序
需求文档:功能需求,不涉及怎么做,只说准备干嘛。非功能需求,描述最终成品的特质如:安全,可扩展,性能标准之类
设计文档:表格或者图–UML-关键在于敲代码之前思考如何组织程序–第二十四章
(1)程序划分子系统,包括之间的界面,依赖关系,数据流,每个子系统的IO和通用线程你、模型
(2)每个子系统详情,包括类构成,类层次,数据结构,算法,具体的线程模型,错误处理等
C++设计的特点:
1.具有庞大的功能集。几乎是C的完整超集,还有类,对象,运算符重载,异常,模板等
2.面向对象,设计时需要包含类层次结构,类接口和对象交互
3.有许多设计通用的,可重用代码的工具。包括类和继承,还有模板和运算符重载等
4.STL
5.设计模式或解决问题的通用方法
C++设计的原则–抽象和重用
1.抽象,可使用而不关心底层实现,接口不便的情况下
2.重用。避免代码只能解决当前问题–模板-泛型
可重用模式–设计模式
重用代码-但不重复代码-自己的,同事的,公司的,开源的
形式:独立的函数或类,特定领域的库,框架。库提供特定功能,框架是程序设计和结构的基础
API,应用程序编程接口,是库或代码为特定目的提供的接口
重用优缺点P72-76
开源代码资源:boost,org gnu.org . github/open-source, sourceforge.net
(多线程避免数据共享,否则加锁)
书中示例P77设计步骤
分隔子系统-选择线程模型-制定子系统的类层次结构-子系统的类,数据结构,算法,模式(处理好抽象和重用)-错误处理(包括系统错误和用户错误)
第六章
6.2 设计可重用代码
6.2.1使用抽象
接口与实现分开。即使句柄(某个接口返回的信息传递给其他接口,库要求客户代码保存这些信息–句柄),也不要公开内部情况,可将其放入某个不透明类。(如果你是客户,你需要哪些内部细节)
6.2.2 代码重用
- 避免不相干内容聚合。
设计组件时,关注单个或一组任务,高聚合
程序设计为可单独重用的分立组件,低耦合如网络部分和图形部分
类层次结构分立逻辑概念。通过继承,从基本生成特定
用聚合分离逻辑概念。如family类不需要在其中实现数数据结构,而是单独写tree类,然后包含进去(has-a)。
消除用户界面依赖。如控制台 cout之类在GUI下无用 - 对泛型数据结构和算法使用模板
- 提供适当的检测和安全措施
一个是按照文档,它包含前置条件,后置条件和不变量。
前置条件列出调用函数或方法,必须满足的条件,
后置条件是完成后,函数或方法必须满足的条件,
不变量是执行期间,必须一直满足的条件
另一个是尽可能安全的设计。返回异常代码,或抛出异常,提醒有错误发生。 - 扩展性
设计好的类不再修改。其行为可扩展,但不必修改其实现。–开放/关闭原则OCP
通过继承,可在不修改原有方法的基础上,实现自己的功能。对扩展开放,对修改关闭。
6.2.3 设计有用的接口
功能允许的情况下,尽量简洁明了。
- 使用熟悉的,或有标准的接口。
- 不要省略必须的功能。首先,接口应包括可能用到的所有行为。其次,实现中包含尽可能多的功能。
- 提供整洁的接口,不要再接口中提供多余的功能。
- 提供文档和注释
内部注释,外部文档。描述行为–输入,输出,错误条件及处理,预定方法和性能
设计通用接口
这样可以用于各种任务。
- 提供执行相同功能的多种方法
- 特定用户,特定的代码。
协调通用性和使用性
- 提供多个接口。为了提供足够多的功能,同时降低复杂性,可提供两个独立接口。–接口隔离原则(interface segregation principle, ISP)。如网络库中,一个为游戏提供网络接口,另一个为超文本传输歇息提供接口。而不是一起实现。
- 常用功能易于使用
6.2.4 SOLID原则
S | SRP(single responsibility principle)单一责任原则,单独组件应当有单独,明确定义的责任,不应当与无关的功能组合 |
O | OSP(open/closed principle)开放/关闭原则。一个类对扩展开放(允许派生),对修改关闭 |
L | LSP(Liskov Subsitution Principle)里氏替换原则,对于一个对象,应能用该对象的子类替代该对象的实例(5.4.3) |
I | ISP(interface segregation principle)接口隔离原则 。接口应当简单清楚,不要使用过于宽泛的通用接口,最好使用多个较小的,定义明确的,责任单一的接口 |
D | DIP(dependency inversion principle)依赖倒置原则。 |
第十章
10.6.7 虚基类
P222–多重继承,最终来自一个基类,虚基类,虚方法,只继承一次,避免歧义
第十一章 理解灵活的C++
右值引用,移交所有权,避免不必要复制,其余与左值差不多
#include <iostream>
using namespace std;
int main()
{
int a = 1;
int &b = a;
b = 3;
int &&c = 1;
int d = c;
int e;
e = c + 1;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
cout << "d = " << d << endl;
cout << "e = " << e << endl;
return 0;
}
11.2 关键字
constexpr–常量表达式
constexpr int getsize() { return 32;}
int myarray[ getsize() ]; // 这样才能编译期间对constexpr求值
因为有一堆限制,所以必须能一眼看出值
static
1.静态成员或方法,他们不是对象的一部分,是类层次,且只有一个副本,独立于任何对象
2.静态链接。C++中源文件彼此链接,默认情况下,函数和全局变量拥有外部链接。
当在函数声明前加static(定义前不用重复),这样另一个文件有同名函数,编译成功,但是链接会失败,因为此时函数为静态链接了。
另一种同样效果的实现方法是匿名名称空间,不使用static,而是用匿名名称空间包含变量和方法。这样,同一源文件,可以在之后任意位置访问,但是文件外不可访问。推荐这么干。
函数中的静态变量。生命周期同程序。但只能在定义它的函数中使用。只能被初始化一次。他会记住上次对它的改变。
静态成员函数是属于类的函数,而不是属于类的对象的函数。
它们可以直接通过类名调用,而无需创建类的对象。
静态成员函数不能访问非静态成员变量,也不能调用非静态成员函数,因为它们没有隐式的this指针。
静态成员函数可以访问类的静态成员变量和其他静态成员函数。
以下是一个示例程序,演示了C++中的静态成员函数的用法:
#include <iostream>
using namespace std;
class MyClass {
private:
static int count; // 静态成员变量
public:
static void printCount(); // 静态成员函数
};
int MyClass::count = 0; // 静态成员变量的初始化
void MyClass::printCount() {
cout << "Count: " << count << endl;
}
int main() {
MyClass::printCount(); // 调用静态成员函数,输出:Count: 0
MyClass::count = 5; // 修改静态成员变量的值
MyClass::printCount(); // 再次调用静态成员函数,输出:Count: 5
return 0;
}
extern
const或typedef默认情况下是内部链接,但使用extern可以变成外部链接。
对某一个名称使用extern时,编译器将其视为声明而不是定义,对于变量而言,编译器不会为它分配空间。 全局变量不建议。
11.3类型和类型转换
类型别名
using IntPtr = int*; //新名称
函数指针的类型别名
函数存储在特定地址。using fun = bool(*)(int, int); 定义了fun 类型,表示指针,指向具有两个int参数并返回bool值的任何函数。
这样,fun可以像普通函数一样直接使用,这是传入函数名表示地址,也可以将其作为其他函数的参数,新建个函数名。
void(int a, int b, fun func)
{
func(a, b);
}
#include <iostream>
#include <cstdio>
using namespace std;
int fun(int a, int b)
{
return a + b;
}
using func = int (*) (int, int);
void pr(int a, int b, func f)
{
cout << f(a, b) << endl;
}
int main()
{
pr(1,2,&fun);
return 0;
}
函数指针与动态库//待补充
windows 中加载dll,由LoadLibrary()完成
HMODULE lib = ::LoadLibrary(“hardware.dll”);
失败返回NULL。从库加载函数前,需要知道函数的圆形。
int __stdcall Connect(bool b);
__stdcall 是微软特有的命令,指示如何将参数传递到函数以及如何执行清理。
using ConnectFunciton = int (__stdcall*) (bool);
使用类型别名为指向函数的指针定义一个缩写名称。,具有上述原型。
之后可以获取指向库函数的指针。
ConnectFunction connect = (ConnectFunction)::GetProcAddress(lib, “Connect”);
失败,connext是nullptr,成功则可以
connect(true);
不能在没有对象的情况下解除对非静态方法或数据成员的指针引用。
typedef
与类型别名一样,为类型声明提供新名称
using IntPtr = int* 等价于typedef int* IntPtr;
函数指针:
using Fun = int (*) (char, double); //推荐
等价于:
typedef int (*Fun) (char, double);
类型转换
const_cast, 给变量添加或去掉常量特性。去掉时,要确保之后不会改变变量内容。
C++17 < utility>定义了std::as_const(),返回引用参数的const引用副本。as_const(obj)等同于const_cast<cosnt T&> (obj)。T的类型为obj
as_const()与auto一起使用要注意,因为auto会去除引用和const限定符。
auto result = std::as_const(str); //因此,它具有std::string, 而不是const std::string&
static_cast,显示的类型转换(双方存在任一方向的隐式转换关系,如int转double);或继承层次结构中执行向下转换
Base * b;
Derived * d;
// 基类b可以直接指向派生类d, 向上转换,将派生类转换为基类,隐式转换,不用强制
b = d;
//需要向下转换。将基类转换为派生类,显式转换,要强制
//用static_cast,表示编译时就确定了类型,如果运行时才能确定类型,则dynamic_cast
d = static_cast<Derived*>(b);
上述代码 OK, 是因为一开始派生类转换为了基类指针,之后又转回去了而已,因此它知道派生类特有的信息,但是在没有b = d,的前提下,就可能存在隐患,因为相当于给基类添加了一些不存在的信息。
这个期间不执行类型检测,因此Base指针指向Derived指针,引用也可以。
不能将某种类型的指针转换为不相关的其他类型的指针。
没有可用的转换构造函数,无法将某种类型的对象直接转换为另一种类型的对象
无法将const类型转换为非const类型
无法将指针转换为int
reinterpret_cast(重新解释)
相比static_cast更强大(乱转,看你喜欢,只要能存得下), 更不安全,做一些危险的类型转换,如完全不相关的引用类型转换。
dynamic_cast()
运行时检测,只能用于类层次结构中的,可用来转换指针或引用。转换时,如果类型转换无意义,则返回一个空指针(转换指针时)或抛出std::bad_cast异常(转换引用)。
类至少有一个虚方法,如果没有虚表,则会出错。可以实现安全的向下转换
当然,向上转换也行,和static_cast一样的效果。但是dynamic_cast运行时会有额外的开销,因为他要检查对象的类型是否匹配,向上转换不需要额外的检测。
P242表
11.6 头文件
#ifndef A_H
#define A_H
#endif
防止多次包含或定义
#pragma once同样作用
前置声明同样可以避免头头文件问题的工具。
A的实现依赖B,但是B用到了A,互相依赖。写B的时候只能前置声明A。告诉存在A,后面引用A时,就不必包含其头文件。
前置声明可以减少编译时间,因为这破坏了一个头文件对其他头文件的依赖。当然,实现文件需要包含前置声明类的正确头文件。
C++17提供__has_include(“filename”) 和__has_include(< filename>)预处理器常量。如果头文件存在,则常量结果为1,不存在为0
#if __has_include(< optional>)
#include < optional>
#elif __has_include(< experimental/optional>)
#include < expertimental/optional>
#endif
第十八章 标准库算法
16.2.18 算法汇总 P351
算法详解
18.5.2非修改序列算法
** | 搜索算法** |
专用搜索算法 | search算法额外的参数,指定搜索算法,default_searcher, boyer_moore_searcher, boyer_moore_horspool_searcher |
Boyeer_Moore和Boyer_Moore_Horspool搜索算法,在大块文本中查找字符串。 |
#include <iostream>
#include <fstream>
#include <string>
#include <iterator>
#include <algorithm>
#include <functional>
using namespace std;
int main()
{
ifstream fin;
fin.open("traIl.txt", ios_base::in);
string str;
fin >> str;
string tostr = "EJSDFASFHESDCASDCSDS";
auto searcher = boyer_moore_searcher(tostr.cbegin(), tostr.cend());
auto result = search(str.cbegin(), str.cend(), searcher);
if(result != str.cend()) {
cout << "Found!" << endl;
} else {
cout << " not Found!" << endl;
}
cout << str << endl;
fin.close();
system("pause");
return 0;
}
比较算法–对顺序容器友好
用于比较不同容器类型的元素序列,子范围和C风格数组,同类型operato==,operat<更好
equal | 所有对应元素相等,返回true,建议使用各自范围各自的首尾迭代器版本 |
mismatch | 接收两个范围的首尾迭代器,返回两个范围中各自不匹配的位置,如果完全相同,则指向尾巴后一位。如果第二个范围内元素多,则忽略多余部分,少,则发生未定义行为 |
lexicographical_compare | (1)范围一的第一个不相等元素小于范围二的对应元素,(2)范围一的元素个数少于范围二,且范围一全部元素等于范围二内对应的初始子序列,返回true,完全相等返回false |
计数算法
import <vector>;
import <algorithm>;
import <iostream>;
using namespace std;
int main()
{
// all_of() 给定范围每个元素均满足条件
vector vec2{ 1, 1, 1, 1 };
if (all_of(cbegin(vec2), cend(vec2), [](int i){ return i == 1; })) {
cout << "All elements are == 1" << endl;
} else {
cout << "Not all elements are == 1" << endl;
}
// any_of()给定范围至少有一个元素满足条件
vector vec3{ 0, 0, 1, 0 };
if (any_of(cbegin(vec3), cend(vec3), [](int i){ return i == 1; })) {
cout << "At least one element == 1" << endl;
} else {
cout << "No elements are == 1" << endl;
}
// none_of()给定范围没有元素满足条件
vector vec4{ 0, 0, 0, 0 };
if (none_of(cbegin(vec4), cend(vec4), [](int i){ return i == 1; })) {
cout << "All elements are != 1" << endl;
} else {
cout << "Some elements are == 1" << endl;
}
}
count | 满足给定值 |
count_if | 满足给定条件 |
int main()
{
vector values{ 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int value{ 3 };
auto tally{ count_if(cbegin(values), cend(values),
[value](int i) { return i > value; }) };
cout << format("Found {} values > {}.", tally, value) << endl;
}
18.5.3修改序列算法(不插入)
map中,元素是键值对,键标记为const,不能为其复制,set以及其multi-版本同样如此。
1.转换 transform()
应用给定的函数到某个范围并将结果存储到指定一范围。
返回值
一种输出迭代器,用于寻址接收通过函数对象转换的输出元素的目标范围内最后元素之后
下一个元素的位置。
transform(myVector.begin(), myVector.end(), myVector.begin(), [](int i){return i+100;};
//对myVector应用函数对象,并将返回值放到目的范围myVector
transform(myVector.begin(), myVector.end(), vec.begin(), result.begin(), [](int i, int j){return i+j;};
//范围myVector提供i,vec提供j,函数对象将其相加,并放在目的范围result
2.复制copy()
copy(vec1.cbegin(), vec1.cend(), vec2.begin());
//源范围与目标范围必须不同
copy_backward(vec1.cbegin(), vec1.cend(), vec2.end());
//反向复制,从源最后一个元素赋值到目标范围最后一个元素。范围必须不同。
copy_if(vec1.cbegin(), vec1.cend(), vec2.begin(), [](int i){return i%2==0;));
//满足条件复制,返回最后一个复制元素后面的按个位置的迭代器,以便确定需要从目标容器中删除的元素个数
copy_n(vec1.cbegin(), cnt, vec2.begin());
//从第一个范围,复制n个到目标范围,不执行边界检查
3.移动
move
版本一,在< utility>中定义的将左值变为右值–提供移动赋值运算符
MyClass& operator=(MyClass&& rhs) noexcept {
if (this == &rhs) { return *this; }
mstr = move(rhs.mstr);
cout << format("Move operator= (mstr={})", mstr) << endl;
return *this;
}
vector<MyClass> vecSrc{ MyClass { "a" }, MyClass { "b" }, MyClass { "c" } };
//MyClass{"a"} 生成临时的匿名对象--右值,然后使用移动赋值运算符,完成vecSrc初始化
版本二, < algorithm>
move(vecSrc.begin(), vecSrc.end(), vecDst.begin();
//verSrc复制到vecDst中
move_backward(),从最后一个元素向第一个元素顺序移动
4.替换
replace_if(vec.begin(), vec,end(), [](int i){return i&2!=0;}, 0)};
//第三个参数为返回bool值的函数或lambda表达式,true,则替换为第四个参数制定的值,否则不变
replace_if(vec.begin(), vec,end(), 7, 0)};
//将范围内的7替换为0
5.删除
vector提供了erase(), 如vector的方法时间复杂度O(n^2)
原型:iterator erase(const_iterator pos) 或iterator erase(const_iterator first, const_iterator last);
返回一个迭代器,指向已移除的任何元素之外保留的第一个元素或指向向量末尾的指针(若此元素不存在)
算法库提供了remove
//删除first,last范围内的值value, 左闭右开区间,返回制定范围去除指定值后的新序列的末尾的下一位置,新尾巴
template< class ForwardIt, class T >
ForwardIt remove(ForwardIt first, ForwardIt last, const T& value)
{
first = std::find(first, last, value);
if (first != last)
for (ForwardIt i = first; ++i != last;)
if (!(*i == value))
*first++ = std::move(*i);
return first;
}
template<class ForwardIt, class UnaryPredicate>
ForwardIt remove_if(ForwardIt first, ForwardIt last, UnaryPredicate p) //满足p的元素移除
{
first = std::find_if(first, last, p); //从范围中寻找第一个满足p的迭代器作为新的first(不满足的保留,因此跳过)
if (first != last)
for (ForwardIt i = first; ++i != last;) //使用i,立即累加,来判断i对应的值是否满足非p,
if (!p(*i))
//对于非p,即要保留的值,将其移动到之前新新范围的first,并++,再次形成新范围
*first++ = std::move(*i);
//至此,最后一次++形成的first,往前是要保留的,往后是要删除的。之后erase删除
return first;
}
使用“删除-擦除法”remove-erase
算法只能访问迭代器抽象。因此算法将匹配给定值或谓词的元素替换为下一个不匹配的元素,使用移动赋值。结果是将范围分为:保留的元素,删除的元素。返回的迭代器指向要删除的元素的范围的第一个元素。
要删除不需要的元素,先使用remove()删除,再调用erase(),将返回的迭代器开始到范围尾部的元素删除。
#include <algorithm>
#include <string>
#include <vector>
#include <iostream>
using namespace std;
void removeEmptyStrings(vector<string>& strings)
{
auto it = { remove_if(begin(strings), end(strings),
[](const string& str) { return str.empty(); }) };
// Erase the removed elements.
strings.erase(it, end(strings));
}
void removeEmptyStringsWithoutAlgorithms(vector<string>& strings)
{
for (auto iter{ begin(strings) }; iter != end(strings); ) {
if (iter->empty()) {
iter = strings.erase(iter);
} else {
++iter;
}
}
}
int main()
{
vector<string> myVector{ "", "one", "", "two", "three", "four" };
for (auto& str : myVector) { cout << "\"" << str << "\" "; }
cout << endl;
removeEmptyStrings(myVector);
for (auto& str : myVector) { cout << "\"" << str << "\" "; }
cout << endl;
return 0;
}
// alg_remove_if.cpp
// compile with: /EHsc
#include <vector>
#include <algorithm>
#include <iostream>
bool greater6(int value)
{
return value >= 6;
}
int main()
{
using namespace std;
vector<int> v1, v2;
vector<int>::iterator Iter1, Iter2, new_end;
int i;
for (i = 0; i <= 9; i++)
v1.push_back(i);
int ii;
for (ii = 0; ii <= 3; ii++)
v1.push_back(7);
random_shuffle(v1.begin(), v1.end());
cout << "Vector v1 is" << endl;
cout << " ( " ;
for (Iter1 = v1.begin(); Iter1 != v1.end(); Iter1++)
cout << *Iter1 << " ";
cout << ")." << endl;
// Remove elements satisfying predicate greater6
new_end = remove_if(v1.begin(), v1.end(), greater6);
cout << "Vector v1 with elements satisfying greater6 removed is\n ( ";
for (Iter1 = v1.begin(); Iter1 != v1.end(); Iter1++)
cout << *Iter1 << " ";
cout << ")." << endl;
// To change the sequence size, use erase
v1.erase(new_end, v1.end());
cout << "Vector v1 resized elements satisfying greater6 removed is\n ( ";
for (Iter1 = v1.begin(); Iter1 != v1.end(); Iter1++)
cout << *Iter1 << " ";
cout << ")." << endl;
system("pause");
return 0;
}
/*
输出:
Vector v1 is
( 7 1 9 2 0 7 7 3 4 6 8 5 7 7 ).
Vector v1 with elements satisfying greater6 removed is
( 1 2 0 3 4 5 7 3 4 6 8 5 7 7 ).
Vector v1 resized elements satisfying greater6 removed is
( 1 2 0 3 4 5 ).
*/
remove_copy / remove_copy_if
复制来自范围 [first, last) 的元素到始于 d_first 的另一范围,省略满足特定判别标准的元素。返回
指向最后被复制元素的迭代器,用于确定目标范围中的新的末尾位置。
new_end = remove_copy ( v1.begin( ), v1.end( ), v2.begin( ), 7 );
for ( Iter2 = v2.begin( ) ; Iter2 != new_end ; Iter2++ )
cout << *Iter2 << " ";
不复制7,并删除v2
不改变源范围,
//remove_copy_if可能的实现
template<class InputIt, class OutputIt, class UnaryPredicate>
OutputIt remove_copy_if(InputIt first, InputIt last, OutputIt d_first, UnaryPredicate p)
{
for (; first != last; ++first)
if (!p(*first))
*d_first++ = *first;
return d_first;
}
6.唯一化
list自己的更好
unique 去除等价
参数
版本1. 首尾迭代器(删除相等元素)
v1_NewEnd1 = unique ( v1.begin( ), v1.end( ) );
版本2. 首尾迭代器,若元素应被当做相等则返回 true 的二元谓词。
v1_NewEnd2 = unique ( v1.begin( ), v1_NewEnd1 , mod_equal );
从来自范围 [first, last) 的相继等价元素组消除首元素外的元素,并返回范围的新 逻辑 结尾的尾后迭代器(返回值,指向范围新结尾的 ForwardIt。,头不变)。
保持剩余元素的相对顺序,且不更改容器的 物理 大小。指向范围的新 逻辑 结尾和 物理 结尾之间元素的迭代器仍然可解引用,但元素自身拥有未指定值。调用 unique 后有时会调用容器的 erase 成员函数,它擦除未指定值并减小容器的 物理 大小,以匹配它新的 逻辑 大小。
unique_copy将结果复制到新的目标范围
7.抽样
sample(vec.cbegin(), vec.cend(), samples.begin(), numberofsamples, engine);
//要抽样的首尾迭代器,存储样本的首迭代器,样本数量,随机数生成引擎(CH20)
8.反转
reverse() 首尾交换,利用首尾迭代器,就地进行
9.乱序
shuffle()
18.5.4 操作算法
基于区间for循环通常比这俩更简单
for_each(begin, end, lambda) //范围内元素执行
for_each_n(begin, n, lambda) //范围内前n个元素
18.5.5 交换算法
swap(a, b)
vector 中, 有a.swap(b)
exchange() < utility > 以 new_value 替换 obj 的值,并返回 obj 的旧值。用于实现移动复制运算符。
template<class T, class U = T>
constexpr // C++20 起
T exchange(T& obj, U&& new_value) noexcept(
std::is_nothrow_move_constructible<T>::value && std::is_nothrow_assignable<T&, U>::value
)
{
T old_value = std::move(obj);
obj = std::forward<U>(new_value);
return old_value;
}
18.5.6 分区算法
partition_copy
将条件为 true 的元素复制到一个目标,将条件为 false 的元素复制到另一目标。 元素
必须来自于指定范围
//三个容器同大小
auto pairIters = partition_copy(vec1.begin(), vec1.end(), vecEven.begin(), vecOdd.begin(),
[](int i){return i%2 ==0;})
//删除多余元素
vecEven.erase(pairItes.first, end(vecEven));
vecOdd.erase(pairItes.first, end(vecOdd));
参数
first, last - 要排序的元素范围
d_first_true - 满足 p 的元素的输出范围起始
d_first_false - 不满足 p 的元素的输出范围起始
policy - 所用的执行策略。细节见执行策略。
p - 若元素应置于 d_first_true 中则返回 true 的一元谓词。
返回值
从指向 d_first_true 范围结尾的迭代器(类似超尾)和指向 d_first_false 范围结尾的迭代器构造的 std::pair 。
std::pair<OutputIt1, OutputIt2>(d_first_true, d_first_false);
partition
重排序范围 [first, last) 中的元素,使得谓词 p 对其返回 true 的元素位于谓词 p 对其返回 false 的元素之前。不保持相对顺序。
ForwardIt partition( ForwardIt first, ForwardIt last, UnaryPredicate p );
参数
first, last - 要重排序的元素范围
policy - 所用的执行策略。细节见执行策略。
p - 如果元素在顺序中应该在其他元素之前则返回 true 的一元谓词。
返回值
指向第二组元素中首元素的迭代器。
18.5.7 排序算法
通常适用于vector,deque,array,和C风格数组
sort()函数,给定范围,和比较函数对象
sort(begin, end, comp)
is_sorted 给定范围,和比较函数对象,有序返回true
bool is_sorted( ForwardIt first, ForwardIt last, Compare comp );
ForwardIt is_sorted_until( ForwardIt first, ForwardIt last, Compare comp );
is_sorted_until 检验范围 [first, last) ,并寻找始于 first 且其中元素已以不降序排序的最大范围。
返回值
始于 first 且其中元素已以升序排序的最大范围。即满足范围 [first, it) 已排序的最后迭代器 it 。
int nums[N] = {3, 1, 4, 1, 5, 9};
int *sorted_end = std::is_sorted_until(nums, nums + N);
sorted_size = std::distance(nums, sorted_end);
18.5.8 二叉树搜索算法
只适用于有序序列或或至少已分区的元素序列
binary_search,lower_bound,upper_bound,euqal_range
lower_bound(),在有序范围内查找不小于给定值的第一个元素,常用于在有序的vector中应将新值插入到哪个位置,使vector依然有序。
auto iter = lower_bound(vec.begin(), vec.end(), num);
vec.insert(iter, num);
binary_searc() 以对数时间搜索元素,需要首尾迭代器,要搜素的值,以及比较函数,找到返回true,没找到返回false
18.5.9 集合算法
用于任意有序范围。
includes() 检查某个有序范围内是否是另一个有序范围的子集,顺序随意。
set_union() 并集
set_intersection() 交集
set_difference() 差集,一个范围独有的
set_summetric_difference() 对称差集,两个结合的异或,并集去除交集的部分。
不能使用交联容器包括set中的迭代器保存结果,因为不允许修改键
bool Result1;
//如果第一个排序范围包含第二个排序范围中的所有元素,则为 true,否则为 false。
Result1 = includes ( v1a.begin( ), v1a.end( ), v1b.begin( ), v1b.end( ) );
std::vector<Order> old_orders{{1}, {2}, {5}, {9}};
std::vector<Order> new_orders{{2}, {5}, {7}};
std::vector<Order> cut_orders;
/*
参数
first1, last1 - 要检验的元素范围
first2, last2 - 要搜索的元素范围
d_first - 输出范围的起始
comp - 比较函数对象,省略时使用operator< 比较
返回值
一个输出迭代器,用于寻址表示新构建的差集范围内最后一个元素之后下一个元素的位置。可用来作为尾迭代器输出元素
*/
Result2 = std::set_difference(old_orders.begin(), old_orders.end(), new_orders.begin(), n ew_orders.end(), std::back_inserter(cut_orders), [](auto& a, auto& b) { return a.order_id < b.order_id; });
//参数,返回值同上
Result2 = set_union ( v2a.begin( ), v2a.end( ), v2b.begin( ), v2b.end( ),v2.begin( ), greater<int>( ) );
Result2 = set_intersection ( v2a.begin( ), v2a.end( ), v2b.begin( ), v2b.end( ),v2.begin( ), greater<int>( ) );
Result2 = set_symmetric_difference ( v2a.begin( ), v2a.end( ), v2b.begin( ), v2b.end( ),v2.begin( ), greater<int>( ) );
std::merge(v1.begin(), v1.end(), v2.begin(), v2.end(), std::back_inserter(dst));
18.5.10 最大/最小算法
min() max() 通过 operator< 或用户提供的二元谓词比较两个或多个任意类型的元素,分别返回一个引用较小或较大元素的const 引用。
minmax返回一个包含两个或多个元素中最小值和最大值的pair,
他们不接受迭代器参数
使用迭代器的
min_element(), max_element(), minmax_element()
//返回 a 与 b 的较大者,如果它们等价,那么返回 a。使用opertor< 比较
const T& max( const T& a, const T& b );
const T& max( const T& a, const T& b, Compare comp );
//返回 initializer_list ilist 中值的最大者,如果有多个等价于最大者的值,那么返回最左侧的这种值。comp比较
constexpr T max( std::initializer_list<T> ilist );
constexpr T max( std::initializer_list<T> ilist, Compare comp );
//其余_element 同下,无comp使用operator<
/*
first, last - 定义要检验范围的向前迭代器
policy - 所用的执行策略。细节见执行策略。
comp - 比较函数对象
返回值
指向范围 [first, last) 中最大元素的迭代器。如果范围中有多个元素等价于最大元素,那么返回指向首个这种元素的迭代器。范围为空时返回 last。
*/
constexpr ForwardIt max_element( ForwardIt first, ForwardIt last, Compare comp );
对于minmax:
返回 std::pair<const T&, const T&>(min_, max_) 的结果
为确保只使用c++函数,而不是非标准宏
可以添加括号,auto maxValue = (std::max)(1,2);
windows可以在添加windows.h前 添加#define NOMINMAX,来禁用windows的 min() 和max()宏
clamp
const T& clamp( const T& v, const T& lo, const T& hi );
const T& clamp( const T& v, const T& lo, const T& hi, Compare comp );
- 若 v 小于 lo ,则返回 lo ;若 v 大于 hi ,则返回 hi ;否则返回 v 。使用 operator< 比较值。
- 同 (1) ,不过使用 comp 比较值。
若 lo 的值大于 hi 则行为未定义。
参数
v - 要夹住的值
lo,hi - 夹 v 的边界
comp - 比较函数对象
返回值
若 v 小于 lo 到则为 lo 的引用,若 hi 小于 v 则为到 hi 的引用,否则为到 v 的引用。
18.5.11 并行算法policy
执行策略policy,标准库算法那都支持执行策略是否并行
如下方 ExecutionPolicy&& policy
template< class ExecutionPolicy, class ForwardIt1, class ForwardIt2 >
bool includes( ExecutionPolicy&& policy, ForwardIt1 first1, ForwardIt1 last1,
ForwardIt2 first2, ForwardIt2 last2 );
bool includes(std::excution::par, v1a.begin( ), v1a.end( ), v1b.begin( ), v1b.end( ));
类型 | 全局实例 | 描述 |
---|---|---|
sequenced_policy | seq | 不允许并行执行 |
parallel_policy | par | 允许并行执行 |
parallel_unsequenced_policy | par_useq | 允许并行执行和矢量执行,还允许线程间迁移执行 |
最后一个有诸多限制,如不能分配释放内存,获取互斥以及使用非锁std::atomics(CH23) |
18.5.12 数值处理算法
< numeric> 中定义的
inner_product() 计算两个序列的内积
**iota()**生成指定范围的序列值,从给定值++上去
gcd() 返回两个整数的最大公约数
**lcm()**返回两个整数的最小公倍数
**reduce()**支持并行。通过以任意且可能置换的顺序计算总和,减少指定范围内的所有元素(可能包括一些初始值)。或者,通过计算指定二元运算的结果来减少。 包含执行策略参数的重载根据指定的策略执行
//返回init 及 *first 、 *(first+1) 、…… *(last-1) 在 binary_op 上的广义和,
template<class ExecutionPolicy, class ForwardIterator, class Type, class
BinaryOperation>
Type reduce(
ExecutionPolicy&& exec, //执行策略
ForwardIterator first, //确定范围
ForwardIterator last,
Type init, //使用binary_op响起一次添加或合并每个元素的初始值
BinaryOperation binary_op); //运算。二元函数对象
accumlate
[first, last)范围内逐个使用op,并累加到初始值init上,返回最终累加值
constexpr T accumulate( InputIt first, InputIt last, T init, BinaryOperation op );
//(1*9)+2*8)+(3*7)+(4*6)
vector v1{ 1, 2, 3, 4 };
vector v2{ 9, 8, 7, 6 };
cout << inner_product(cbegin(v1), cend(v1), cbegin(v2), 0) << endl;
//在[first, last)范围内填充,从5开始递增的数
vector<int> values(10);
iota(begin(values), end(values), 5);
transform_reduce()以及四个扫描算法待补充
第十九章 字符串本地化与正则表达式
19.1 本地化
19.1.1本地化字符串变量
本地化的关键点在于不能在源代码中放置任何沐浴的字符串字面量,除了调试用。一般会将字符串放置在类似STRINGTABLE的资源中,这样只需要翻译该部分即可。
e.g.
cout << Format(IDS_TRANSFERRED, n) << endl;
IDS_TRANSFERRED是字符串资源表中的一个条目名称,不同语言不同定义,使用format()加载即可。
19.1.2 宽字符
wchar_t,对于跨平台代码来说,最好别指定大小。最好一开始就用,毕竟大,为以后省事儿。在字符串和字符串字符串字面量前加上字母L
wchar_t wideCharacter = L'm';
//有个原始字符串 R"(…)"
常用类型和类都有宽字符版本,string-wstring,wofstream,wifstream
非西方字符集
Unicode是标准化的字符集,包含大约10万个抽象字符,他们都由一个无歧义的名字和资格码点(编码)标志。UTF-8,是它的一个实例,使用1到4个8位字节,UTF-16,使用1或2个16位的值,UTF-32 使用32位。
char | 保存ASCII或者UTF-8(一般为1到4个char) |
chart_16 | 存储16位,保存UTF-16 |
char32_t | 至少32位 |
wchar_t | 大小取决于编译器 |
使用char16_t,char32_t而不是wchar_t好处在于:前两者至少为16位和32位,大小与编译器无关。
C++标准定义的宏
STDC_UTF-32, 如果编译器定了这个宏,则char32_t使用UTF-32编码
STDC_UTF-16, 如果编译器定了这个宏,则char32_t使用UTF-16编码
使用字符串前缀可转换为特定类型:
u8 | 使用UTF-8编码的char字符串字面量 |
u | 表示char16_t |
U | 表示 char32_t |
L | 采用与编译器相关编码的wchar_t字符串字面量 |
const char32_t *s = UR"(haha)";
在使用了unicode编码时,可以在非原始字符串中插入指定的unicode编码,如\u03C0 表示pi字符
const char* f = u8"\u03C0 r\u00B2"; //打印圆面积公式
对于string,氦又wstring,u16string,u32string
第三方库ICU(international Components for Unicode)提供Unicode全球化支持
codecvt 定义于locale头文件,用于编码转换
19.1.5 locale和facet
C++标准中,将一组特定的文化参数相关的数据组合起来的机制称为locale,是个独立组件,定义的格式称为facet,如日期格式。
//在流上调用imbue方法,使用用户的locale,这样发送到wcout的内容会根据环境的格式化规则进行格式化,但是此部分在vscode中有bug,略过
wcout.imbue(locale(""));
//wcout.imbue(locale("en-US")); //设置城美式英语locale
wcout << "326874444444" << endl;
< locale >中包含许多之前ctype中判断字符的方法。包括toupper
19.2 正则表达式
正则表达式
runoob
代码生成
逻辑图片
动态可视化
< regex>头文件中,处理字符串。C++ 默认使用ECMAScript语法
\d{4}\/(?:0?[1-9]|[0-2])\/(?:0?[1-9]|[1-2][0-9]|3[0-1])
/*e.g.:2023/03/31 这样的日期
19.2.1 ECMAScript语法
表达式中的任何字符都表示匹配自己,以下字符除外(需要匹配这些,要 \ 字符转义):
**^ $ \ . * + ? ( ) [ ] { } | **
1.锚点
^ 匹配行终止符前面的位置
$ 匹配行终止符所在的位置
两者默认还分别匹配字符串的开头和结尾位置,可以禁用该行为 。
^test$ 只匹配test
2.通配符
可以匹配除换行符外的任意字符。a.c可以匹配abc或a5c
3.替代
|表示“或”的关系,a|b 表示匹配a或者b
4.分组
**()**用于标记子表达式——人为划定怎样区分子表达式
用于识别源字符串中单独的子序列,结果中返回每个标记的子表达式(捕捉组)
(.)(ab|cd)(.):有(.)匹配任意字符,(ab|cd)匹配ab或者cd。然后再次匹配任意字符。总共三个
可用于在匹配过程中向后引用。
替换操作时识别组件
5.重复-
重复匹配
** 匹配零次或多次之前的部分*, a*b 可匹配b, ab, aaaaab
** = 匹配一次或多次之前的部分**,a+b可匹配ab aaab,不能匹配b
? 匹配零次或一次之前的部分,a?b 可匹配b 和ab,不能匹配其他
**{…}**表示区间重复。 a{3,4} 匹配a 3次或者4次
贪婪匹配,找出最长匹配,非贪婪则将其模式重复尽可能少的次数,
oooo,o+? 只匹配单个o, o+匹配所有o
6.优先级
元素:如a,最基本的部分
量词:即重复次数,绑定左侧元素
串联:在量词之后绑定(ab+c)
替代符: **.**最后绑定
圆括号可以改变优先级顺序,如ab+(c|d),为ab+c或ab+d。但用圆括号会有一个副作用,使相关的匹配会被缓存.
此时可用 ?: 放在第一个选项前来消除这种副作用
同时,圆括号内的也被标记为子表达式的捕捉组。 (?:…)可以避免创建新捕捉组的情况下改变优先级。
ab+(?:c|d)和ab+(c|d)匹配内容一样,但没有创建多余的捕捉组
7.字符集和匹配
[ abc] 匹配a,b,c中任意字符
第一个字符为 ^ ,表示除了这些字符外的任意字符。ab[cde]:abc,abd,dbe
ab[ ^ cde] 除了abc,abd,dbe 都行
使用转义字符可以匹配符号本身
方括号内范围描述。[a-zA-Z] 匹配全部字母,[a-zA-Z-] 匹配任意单词,包括连字符单词
字符类 表示方法**[:name:]**
字符类名称 | 说明 | 替代表示 |
---|---|---|
digit/d | 数字 | [0-9] |
xdigit | 数字和十六进制使用的字母的大小写 | [0-9a-fA-F] |
alpha | 字母数字字符 | [a-zA-Z] |
alnum / w | alpha与digit类的组合 | [0-9a-zA-Z] |
lower /upper | 小写大写 | [a-z]/[A-Z] |
blank | 空白字符,分隔单词用的 C locale就是‘ ’ 或‘\t’ | [ \t] |
space / s | 空白字符,对于C Locale,’ ’ 、‘\t’、‘\n’、‘\r’、‘\v’ 、‘\f’ | [\n\r\t \x0B] |
可打印字符 | [\t\x20-\xFF] | |
cntrl | 控制符,不占用打印位置 | [\x01-\x1F] |
graph | 带有图形表示的字符,包括除空格’ ’ 外的所有可打印字符 | [ ^ \x01-\x20] |
punct | 标点符号字符,对于C locale,包括不是字母数字(alnum)的所有图形字符(graph) | [-!"#$%&‘( )*+,./:;<=>?@[\]^_’{ |
[[:alpha:]]*等同于[a-zA-Z] *
字符类可用的转移符号–缩写
转移符号 | 等价于 |
---|---|
\d | [[:d]] |
\D | [ ^ [:d:] ] |
\s | [[: s:]] |
\S | [ ^ [ : s:] ] |
\w | [ _[:w:] ] |
\W | [ ^_[:w:] ] |
8.词边界
是指单词的开始和结束位置,用**\b**表示 , 匹配单词边界,从边界开始
\B,匹配除单词边界外的任何内容,从非边界开始
\bhello\b可以匹配hello, helloo或ohello都不行
9.向后引用
可用此引用正则表达式本身的捕捉组。\n表示正则表达式中的第n个捕捉组
(\d+)-.*-\1
第一个捕捉组捕捉一个或多个数字
接下来连字符
然后0个或多个字符.
最后是重复一遍第一个捕捉组。
可以匹配13-abc-13,不可以14-abc-15
10.lookahead
逐个字符匹配
支持正向lookhead(**?=模式)–先行断言,指定一个必须满足但不包含在匹配结果中的条件。a(?=b)寻找后面跟b的字母,即abc,找到了ab组合,但是b只是个标识,最终只匹配a,可输出a
负向lookhead(?!**模式),a(?!b),匹配后面紧跟着不是b的字母,即?!后不出现给定内容.
如abc中,什么都不会输出。acb输出a。
?=、?<=、?!、?<! 的使用区别
**但是g++ c++17 中\d+(?!23) 匹配11232323时,全匹配,**而不会在某个23前停下
#include <iostream>
#include <regex>
#include <string>
int main() {
std::string s = "abc123def456";
std::regex r("(?=\\d)[^[:alpha:]]+");
std::smatch m;
while (std::regex_search(s, m, r)) {
std::cout << m.str() << '\n';
s = m.suffix().str();
}
getchar();
}
/*
运行结果
123
456
*/
11.正则表达式和原始字符串字面量
“( |\n|\r|\\)" 它搜索空格,换行符,回车符和反斜杠。
使用第二章的原始字符串 R"(( |\n|\r|\))" 更方便阅读。以R"()"为,内部内容都是正则表达式,反斜杠在正则表达式本省就需要转移。
19.2.2 regex库
在< regex>头文件和std名称空间中。
基本模板类型包括:
basic_regex:表示某个特定正则表达式的对象
match_results:匹配正则表达式的字符串,包括所有捕捉组,是sub_mach的集合
sub_match:迭代器对的对象,继承自 std::pair。分别指向匹配的捕捉组中的第一个字符和最后一个字符后面的字符。str方法可以把匹配的捕捉组返回为字符串。length返回匹配的长度
库函数允许将源字符串指定为STL字符串、字符数组或表示首尾的迭代器对。迭代器可以有以下类型:
类型 | 定义 |
---|---|
cregex_iterator | regex_iterator<const char*> |
wcregex_iterator | regex_iterator<const wchar_t*> |
sregex_iterator | regex_iterator< std::string::const_iterator> |
wsregex_iterator | regex_iterator< std::wstring::const_iterator> |
P455 其余的类型别名( 除了basic_regex只有char和wchar_t版本,其余都有char版本,string版本,以及wchar_t,wstring版本——针对上述三个基本模板类型,以及下面两个迭代器 )
以及任何具有双向迭代器行为的迭代器。
regex_iterator:遍历一个模式在源字符串中出现的所有位置。只读的前向迭代器,每次构造和递增的时候,调用std::regex_search并记住结果(即保存std::match_results< Bidirlt>)。可以访问正则表达式的每个匹配项,但不能访问每个匹配项的子匹配项。用于查找所匹配特定正则表达式的子字符串
regex_token_iterator:遍历一个牧师在源字符串中出现的所有捕捉组。前向。创建它时会构造一个regex_iterator并在每次递增时从当前match_results逐步遍历请求。用于将给定文本分割为匹配和非匹配子字符串。
//后续待补充修改
regex_match | 尝试匹配一个正则表达式到整个字符序列 |
regex_search | 尝试匹配一个正则表达式到字符序列的任何部分 |
regex_replace | 以格式化的替换文本来替换正则表达式匹配的出现位置 |
19.2.3 regex_match
匹配整个源字符串,返回true,否则返回false。
cppreference-regex_match
cppreference-match_results
\d{4}/(?:0?[1-9]|1[0-2])/(?:0?[1-9]|[1-2][0-9]|3[0-1])
/*
匹配:2023/05/31
*/
bool regex_match( 源字符串, 匹配结果–对match_results的引用m,正则表达式e,确定将如何进行匹配的标志flags)
源字符串可以是:
C风格字符串str,首尾迭代器first/last,以std::basic_string 给出的string。
不论匹配是否成功,都可以通过m查看匹配后的结果。不成功,则只能调用**match_results::empy()和size()**其余内容未定义。
m.str返回特定子匹配的字符序列。
operator[] m[i] 或者m[i].str()都可以表示捕获的字符串
const_reference operator[]( size_type n ) const
-
若 n > 0 且 n < size() ,则返回到 std::sub_match 的引用,它表示第 n 个捕获的有标记子表达式所匹配的目标序列部分。
-
若 n == 0 ,则返回到 std::sub_match 的引用,它表示返回整个匹配的正则表达式所匹配的目标序列部分。
-
若 n >= size() ,则返回到 std::sub_match 的引用,它表示不配的子表达式(目标序列的空子范围)。
prefix
返回目标序列起始和完整匹配起始之间的子序列。获取第一个子匹配项之前的序列。
获得到 std::sub_match 对象的引用(继承自pair),该对象表示目标序列起始到正则表达式的整个匹配起始之间的目标序列。
suffix
返回完整匹配结尾和目标序列结尾之间的子序列。获取最后一个子匹配项后的序列。
获得到 std::sub_match 对象的引用,该对象表示正则表达式的整个匹配结尾,和目标序列结尾之间的目标序列。
int main()
{
std::regex re("a(a)*b");
std::string target("baaaby");
std::smatch sm;
std::regex_search(target, sm, re);
std::cout << sm.suffix().str() << '\n';
}
/*
输出y prefix则输出b
匹配aaab---sm[0], 第一个捕获的sm[1]--aa
prefix().str()--第一个b。suffix().str()--最后一位y
*/
m.ready() | true |
m.empty() | false |
m.size() | 有标记子表达式数加 1 ,即 1+e.mark_count() |
m.prefix().first | first |
m.prefix().second | first |
m.prefix().matched | false (匹配前缀为空) |
m.suffix().first | last |
m.suffix().second | last |
m.suffix().matched | false (匹配前缀为空) |
m[0].first | first |
m[0].second | last |
m[0].matched | true (匹配整个序列) |
m[n].first | 匹配有标记子表达式 n 的序列起始,或若该子表达式不参与匹配则为 last |
m[n].second | 匹配有标记子表达式 n 的序列结尾,或若该子表达式不参与匹配则为 last |
m[n].matched | 若表达式 n 参与匹配则为 true ,否则为 false |
19.2.4 regex_search()
整个源字符串匹配正则表达式,用regex_match(),不能用于查找源字符串中匹配的子字符串。regex_search()可以实现确定正则表达式 e 和目标字符序列中的某个子序列间是否有匹配。参数内容和regex_match()相同。
不要再循环中使用regex_match() 的首尾迭代器找出某个模式的匹配子字符串。如果正则表达式使用了锚点(^或者$)和单词边界等,这样会出问题,由于空匹配,导致无限循环。这里需要循环迭代的话请使用库提供的regex_iterator及token版本。
若匹配存在:
m.ready() | true |
m.empty() | false |
m.size() | 有标记子表达式的数量加 1 ,即 1+e.mark_count() |
m.prefix().first | first |
m.prefix().second | m[0].first |
m.prefix().matched | m.prefix().first != m.prefix().second |
m.suffix().first | m[0].second |
m.suffix().second | last |
m.suffix().matched | m.suffix().first != m.suffix().second |
m[0].first | 匹配序列的起始 |
m[0].second | 匹配数列的结尾 |
m[0].matched | true |
m[n].first | 匹配有标记子表达式 n 的序列的起始,或若子表达式不参与匹配则为 last |
m[n].second | 匹配有标记子表达式 n 的序列的结尾,或若子表达式不参与匹配则为 last |
m[n].matched | 若子表达式 n 参与匹配则为 true ,否则为 false |
19.2.5 regex_iterator
循环匹配时使用,可以用来找出所有匹配项。
//构造函数
regex_iterator();//默认构造函数,构造序列尾迭代器
/*从字符序列 [a, b) 、正则表达式 re 和掌管匹配行为的标志 m 构造 regex_iterator 。此构造函数以此数据进行 std::regex_search 的初始调用。若此初始调用结构为 false ,则设置 *this 为序列尾迭代器。*/
regex_iterator(BidirIt a, BidirIt b,
const regex_type& re,
std::regex_constants::match_flag_type m =
std::regex_constants::match_default);
/*
a - 指向目标字符序列起始的 BidirIt
b - 指向目标字符序列结尾的 BidirIt
re - 用于搜索目标字符序列的正则表达式
m - 掌管 re 行为的标志
*/
//从 regex_iterator 提取当前 std::match_results :
const value_type& operator*() const;//返回到当前 std::match_results 的引用。
const value_type* operator->() const;//返回指向当前 std::match_results 的指针。
//还可以== , != 比较两个。或者++来推进迭代器到下一个匹配
提供对于常用字符序列类型的数个特化:
P455 其余的类型别名( 除了basic_regex只有char和wchar_t版本,其余都有char版本,string版本,以及wchar_t,wstring版本——针对上述三个基本模板类型,以及下面两个迭代器–ch12.2.2 )
#include <regex>
#include <iterator>
#include <iostream>
#include <string>
int main()
{
const std::string s = "Quick brown fox.";
//从临时 basic_regex 构造的 regex_iterator 会立即变为非法,因此不可以直接将正则表达式放到构造函数中
std::regex words_regex("[^\\s]+"); //找出所有单词,即全部非空白字符
auto words_begin =
std::sregex_iterator(s.begin(), s.end(), words_regex);//带参数构造,找出匹配的序列头
auto words_end = std::sregex_iterator();//默认构造,序列尾
std::cout << "Found "
<< std::distance(words_begin, words_end)
<< " words:\n";
for (std::sregex_iterator i = words_begin; i != words_end; ++i) {
//std::cout <<(*i).str() << std::endl;
//std::cout <<(*i)[0] << std::endl;
std::smatch match = *i; //operator* 返回 match_results引用
std::string match_str = match.str();
std::cout << match_str << '\n';
}
}
/*
输出:
Found 3 words:
Quick
brown
fox.
*/
19.2.6 regex_token_iterator
regex_iterator遍历每个匹配的模式,在循环的每次迭代中都得到一个match_results对象。通过对象提取出捕捉组捕捉的那个匹配的子表达式。
regex_token_iterator(); //默认构造函数。构造序列尾迭代器
regex_token_iterator( BidirIt a, BidirIt b, //目标字符序列
const regex_type& re, //正则表达式
//以下四选一
/*submatch(es)----应当返回的子匹配下标。 忽略或"0" 表示完整匹配,遍历整个表达式的子字符串
而 "-1" 表示未匹配的部分(例如匹配间的填塞)*/
int submatch = 0, //表示要迭代的捕捉组的索引
const std::vector<int>& submatches,//其中整数表示要迭代的捕捉组的索引
std::initializer_list<int> submatches,//捕捉组索引的初始化列表
const int (&submatches)[N],//带有捕捉组索引的C风格数组
std::regex_constants::match_flag_type m =
std::regex_constants::match_default );
//与regex_iterator输出同
regex reg{ "[\\w]+" };
for (sregex_token_iterator iter{ cbegin(str), cend(str), reg }; //submatches默认0,全匹配
iter != end; ++iter) {
cout << "\"" << *iter << "\"" << endl;
//遍历第二个和第三个捕捉组(月和日)
//以下reg加了锚点,以匹配整个源序列。之前regex_match()不用加是因为它自动匹配整个字符串
regex reg{ "^(\\d{4})/(0?[1-9]|1[0-2])/(0?[1-9]|[1-2][0-9]|3[0-1])$" };
vector indices{ 2, 3 }; //指定捕捉组
const sregex_token_iterator end;
for (sregex_token_iterator iter{ cbegin(str), cend(str), reg, indices };
iter != end; ++iter) {
cout << "\"" << *iter << "\"" << endl; //这里知会输出月份和日
}
//字段分解,切割字符串,捕捉组索引为-1触发。遍历源字符串不匹配正则表达式的所有字符串。
regex reg{ R"(\s*[,;]\s*)" }; //前后任意数量的空白字符和分隔符,或;
const sregex_token_iterator end;
for (sregex_token_iterator iter{ cbegin(str), cend(str), reg, -1 };
iter != end; ++iter) {
cout << format("\"{}\"", iter->str()) << endl;
/*
e.g.: This is, a; test string.
output:
"This is"
"a"
"test string."
}
19.2.7 regex_replace()
以格式化的替换文本来替换正则表达式匹配的出现位置。用于替换的格式化字符串可通过转义序列,引用匹配字符串中的部分内容。
转义序列 | 替换为 |
---|---|
$n | 匹配第n个捕捉组的字符串,n>0.$1第一个捕捉组 |
$& | 匹配正则表达式的字符串 |
$’ | 左半单引号,在输入序列中,在匹配正则表达式的子字符串左侧的部分 |
$’ | 右半单引号,在输入序列中,在匹配正则表达式的子字符串右侧的部分 |
$$ | 单个美元符号 |
六个版本,其中四个如下
//返回输出迭代器 out 在所有插入后的副本。把给定的字符串写到给定的输出迭代器,并返回
//返回迭代器的,输入序列必须是迭代器
OutputIt regex_replace( OutputIt out,
BidirIt first, BidirIt last,
const std::basic_regex<CharT,Traits>& re,
const std::basic_string<CharT,STraits,SAlloc>& fmt,
std::regex_constants::match_flag_type flags =
std::regex_constants::match_default );
// 返回含有输出的字符串 result 。返回得到的字符串。
regex_replace( const CharT* s,
const std::basic_regex<CharT,Traits>& re,
const std::basic_string<CharT,STraits,SAlloc>& fmt,
std::regex_constants::match_flag_type flags =
std::regex_constants::match_default );
/*
first, last - 以一对迭代器表示的输入字符序列
s - 以 std::basic_string 或字符数组表示的输入字符序列
re - 将与输入序列匹配的 std::basic_regex
flags - std::regex_constants::match_flag_type 类型的匹配标志.可选,指定替换算法
fmt - 正则表达式替换格式字符串,准确语法依赖于 flags 的值,
out - 存储替换结果的输出迭代器
fmt和输入字符序列可以是std::string或C-string.
类型要求
-OutputIt 必须符合老式输出迭代器 (LegacyOutputIterator) 的要求。
-BidirIt 必须符合老式双向迭代器 (LegacyBidirectionalIterator) 的要求。
*/
P461例子待深入
const string str{ "<body><h1>Header</h1><p>Some text</p></body>" };
regex r{ "<h1>(.*)</h1><p>(.*)</p>" };
const string replacement{ "H1=$1 and P=$2" };
string result{ regex_replace(str, r, replacement, regex_constants::format_no_copy)
//返回result,初始化string 定义的result
/*默认<body>H1=Header and P=Some text</body> body不匹配,输出到结果。匹配部分用H1=$1 and P=$2替换,其中$1 $2 在源字符串中有对应内容。
*/
//no copy:H1=Header and P=Some text 不带body标签了
};
regex_replace()接收控制工作方式的标识,最重要的有三个:
标识 | 说明 |
---|---|
format_default | 默认操作时替换模式的所有实例,并将所有不匹配模式的内容复制到结果字符串 |
format_no_copy | 默认操作时替换模式的所有实例,但是不将所有不匹配模式的内容复制到结果字符串 |
format_first_cop | 只替换模式的第一个实例 |
第二十章 其他工具库
boost
20.1 ratio 有理数相关
精确地表示任何可在编译时使用的有限有理数。其中定义了与有理数相关的所有内容。在std名称空间中。有理数的分子和分母通过类型std::intmax_t的编译时常量表示,它定义于< cstdint>,是有符号的整数类型,最大宽度由编译器指定。
要输出ratio,需获得分子分母,分别打印。
ratio对象定义方法是使用类型别名:
//ratio 定义
//ratio类中静态数据成员 num 与 den 表示由将 Num 与 Denom 除以其最大公约数的分子与分母。
template<
std::intmax_t Num, //分子-numerator
std::intmax_t Denom = 1 //分母--denominator
> class ratio;
using r1 = ratio<2, 60>; //表示1/60
//访问r1的分子分母
intmax_t num = r1::num; //此时为1而不是2
intmax_t den = r1::den; //此时为30而不是60
//因为ratio是编译时常量,因此单独定义分子分母时该这样
const intmax_t n = 2;
const intmax_t d = 60;
using r1 = ratio<n, d>;
定宽整数类型 (C++11 起) 提供了表示大数的类型和宏定义(国际单位制下的类型别名,如using deci = ratio<1, 10>)。
标准库标头 < cstdint>此标头原作为 <stdint.h> 存在于 C 标准库。
此头文件是类型支持库的一部分,提供定宽整数类型和部分 C 数值极限接口。
ratio支持的有理数加减乘除,在编译时就进行,因此只能使用ratio_add,ratio_subtract,ratio_multiply,ratio_divide。计算结果将作为新的ratio类型,可以通过type这一内嵌类型别名访问计算结果。
using r1 = ratio<1, 60>;
using r2 = ratio<1, 30>;
using result = ratio_add<r1, r2>::type;//1/20 type 可选
同时定义了比较模板:ratio_与equal,not_equal,less,less_equal,greater,greater_equal组合。结果为创建了新类型std::bool_constant,也是std::integral_constant,可通过value值访问数据成员。同时通过“ cout << boolalpha << ratio::value”以true或false访问值
#include <iostream>
#include <ratio>
int main()
{
if(std::ratio_equal<std::ratio<2,3>, std::ratio<4,6>>::value) {
std::cout << "2/3 == 4/6\n";
} else {
std::cout << "2/3 != 4/6\n";
}
}
/*
输出
2/3 == 4/6
*/
20.2 chrono 时间相关
操作时间用。定义三种主要类型以及工具函数和常用 typedef 。
时钟
时长
时间点
包含< chrono>,并在名称空间std::chrono中
20.2.1 持续时间duration
模板化类duratio类表示,保存了抵达数和嘀嗒周期。
template<
class Rep,
class Period = std::ratio<1>
> class duration;
成员类型 | 定义 |
rep | Rep ,表示计次数的算术类型(译期有理数常量,表示从一个计次到下一个的秒数。) |
period | Period (C++17 前)typename Period::type (C++17 起) ,表示计次周期的 std::ratio (即每秒的次数) |
存储于 duration 的数据仅有 Rep 类型的计次数。若 Rep 是浮点数,则 duration 能表示小数的计次数。 Period 被包含为时长类型的一部分,且只在不同时长间转换时使用,默认1,即1秒。
三个构造函数:默认构造函数,另一个构造函数接收一个表示嘀嗒数的值作为参数,第三个接收另一个duration作为参数用来将一个duratio转换为另一个,如分钟转换为秒。
支持算数运算,也支持比较运算符。cppreferece-duration
constexpr rep count() const | 返回此 duration 的计次数 |
static constexpr duration zero(); | 返回零长度时长。实际上,返回 Rep(0) |
static constexpr duration min() | 返回模板参数 Rep 的最大允许值。 |
static constexpr duration max() | 返回模板参数 Rep 的最低允许值 |
floor(std::chrono::duration) | 以向下取整的方式,将一个时长转换为另一个时长 |
round(std::chrono::duration) | 转换时长到另一个时长,就近取整,偶数优先 |
abs(std::chrono::duration) | 获取时长的绝对值 |
ceil(std::chrono::duration) | 以向上取整的方式,将一个时长转换为另一个时长 |
duration_cast | 转换时长到另一个拥有不同嘀嗒间隔的时长 |
//一个滴答周期为1秒(period默认1),滴答计数类型为long
duration<long> d1;
//or
duration<long, ratio<1>> d1; //以秒为单位
//周期一分钟
duration<long, ratio<60>> d2;
//1/60秒
duration<long, ratio<1, 60>> d3;
//利用<ratio>中预定义的SI有理数常量,用他们作为周期,如1ms
duration<long long, std::milli> d4;
//带参数构造
duration<long, ratio<60>> d1(123); //接收嘀嗒数目,可通过d1.count()原样输出
//min应用
duration<double> d2;
d2 = d2.min();//1秒内,最大计数次数
cout << d2.count() << endl; //d2.count()负值,因为时间间隔为负数,及时间小-时间大作为时间间隔
duration<long, ratio<60>> d3(10); // =10min
duration<long, ratio<1>> d4(14); //=14sec
//比较
if(d3 > d4)
...
//递增
++d4; //递增一个周期,即15sec。换做d3,则代表11min
//乘
d4 *= 2; //30sec
//+, =, 同样可以用于转换
duration<double, ratio<60>> d5 = d3 + d4; //double表示范围更全,防止转换过程中出现小数
duration<long, ratio<1>> d5 = d3 + d4;
//显式转换时间间隔
duration<double, ratio<60>> d8(d7); //d7 间隔1,计数类型long,这种适合转换后技术类型为小数
//duration_cast转换,60秒转换为1分钟,使用了预定义seconds这种类型别名且可能转换成小数的可以使用
seconds s(60);
minutes m = duration_cast<minutes>(s); //转换后为计数类型为整数
//floor等方法
//abs,直接用std::chrono::abs(3min).count()
#include <iostream>
#include <iomanip>
#include <chrono>
int main()
{
using namespace std::chrono_literals;
using Sec = std::chrono::seconds;
for (std::cout << "Duration\tFloor\tRound\tCeil\n"
"(ms)\t\t(sec)\t(sec)\t(sec)\n";
auto const d: {
+4999ms, +5000ms, +5001ms, +5499ms, +5500ms, +5999ms,
-4999ms, -5000ms, -5001ms, -5499ms, -5500ms, -5999ms, }) {
std::cout << std::showpos << d.count() << "\t\t"
<< std::chrono::floor<Sec>(d).count() << '\t'
<< std::chrono::round<Sec>(d).count() << '\t'
<< std::chrono::ceil <Sec>(d).count() << '\n';
}
}
typedef定义了简便的表示的类型别名 ,这些预定义的使用的计数类型为整数,因此转换后非整数则报错,即使60秒转换为1分钟也会报错,因为秒到分钟转换可能有非整数值。分到秒可以。
std::chrono::nanoseconds | duration< 至少 64 位的有符号整数类型, std::nano> |
std::chrono::microseconds | duration<至少 55 位的有符号整数类型, std::micro> |
std::chrono::milliseconds | duration<至少 45 位的有符号整数类型, std::milli> |
std::chrono::seconds | duration</至少 35 位的有符号整数类型/> |
std::chrono::minutes | duration<至少 29 位的有符号整数类型, std::ratio<60>> |
std::chrono::hours | duration<至少 23 位的有符号整数类型, std::ratio<3600>> |
同样,在内联命名空间 std::literals::chrono_literals 定义了 h,min,s,ms,us,ns来创建duration
duration<long, ratio<60>> d9(10);
//等价于
minutes d9(10);
auto t = hours(1) + minutes(23) + seconds(45); //匿名
cout << seconds(5).cout() << "seconds" << endl;
auto a = 42min; //可以通过std::chrono来访问,虽然定义在了std::literals::chrono_literals名称空间中
20.2.2 时钟
时钟由起点(或纪元)及计次频率组成。例如,时钟可以拥有 1970 年 1 月 1 日的纪元,和每一秒的计次。
clock类由time_point和duration组成.
C++定义了三个clock
system_clock,表示来自系统实时时钟的真实时间
steady_clock,表示能保证其time_point绝不递减的时钟,system_clock无法保证,因为它可以随时调整
high_resolution_clock,拥有可用的最短嘀嗒周期的时钟,它可以是 std::chrono::system_clock 或 std::chrono::steady_clock 的别名,或第三个独立时钟。在不同标准库实现之间实现并不一致,尽量不要使用对于 gcc 的 libstdc++ 它是 system_clock ,对于 MSVC 它是 steady_clock ,而对于 clang 的 libc++ 它取决于配置。
每个clock都有个静态now()方法,用于把当前时间作为time_point。
time_point转换为C风格的时间表示方法time_t(定义于< ctime.h>中):静态辅助函数to_time_t。反过来,静态辅助函数from_time_t
template< class CharT >/*未指定*/ put_time( const std::tm* tmb, const CharT* fmt );
tmb - 指向从 std::localtime 或 std::gmtime 获得的日历时间结构体的指针
fmt - 指向指定转换格式的空终止 CharT 串的指针。
tm 日历时间类型.保有拆分到组分的日历日期和时间的结构体。
time_t 从纪元起的时间类型.
time_t:虽然标准中没有给出定义,但是该类型几乎总是整数类型,表示自 1970 年 1 月 1 日 00:00 UTC 以来所经过的秒数(不计闰秒),对应 POSIX 时间。
在标头 <ctime> 定义
std::tm* localtime( const std::time_t *time );
转换作为 std::time_t 值的从纪元起时间到以本地时间表达的日历时。
参数
time - 指向要转换的 time_t 对象的指针
返回值
成功时为指向内部静态 std::tm 对象的指针,否则为 NULL 。该结构体可能在 std::gmtime 、 std::localtime 和 std::ctime 之间共享,而且可能在每次调用时被覆盖。
注意
此函数可能不是线程安全的。
在标头 <ctime> 定义
std::size_t strftime( char* str, std::size_t count, const char* format, const std::tm* time );
按照格式字符串 format ,转换来自给定的日历时间 time 的日期和时间信息,为空终止多字节字符串 str 。最多写入 count 字节。
通过put_time将时间输出给stringstream,可以用str()获得string化的时间字符串。同样,通过strftime可以将时间输出给足够大的缓冲区,从而获得时间字符串
#include <ctime>
#include <iostream>
#include <chrono>
#include <sstream>
#include <iomanip> //CH13 put_time
using namespace std;
using namespace std::chrono;
int main()
{
// Get current time as a time_point.
system_clock::time_point tpoint{ system_clock::now() };
// Convert to a time_t.
time_t tt{ system_clock::to_time_t(tpoint) };
// Convert to local time.
tm* t{ localtime(&tt) };
// Write the time to the console.
cout << put_time(t, "%H:%M:%S") << endl;
// Convert to readable format.
stringstream ss;
ss << put_time(t, "%H:%M:%S");
string stringTime{ ss.str() };
// Write the time to the console.
cout << stringTime << endl;
// Convert to readable format.
char buffer[80]{ 0 };
strftime(buffer, sizeof(buffer), "%H:%M:%S", t);
// Write the time to the console.
cout << buffer << endl;
}
计算代码执行时间,通过不同时间段获得当时时间的time_point,相减可得一段代码用时。
#include <iostream>
#include <vector>
#include <numeric>
#include <chrono>
volatile int sink;
int main()
{
for (auto size = 1ull; size < 1000000000ull; size *= 100) {
// 记录开始时间,实际类型为system_clock::time_point
auto start = std::chrono::system_clock::now();
// 做一些工作
std::vector<int> v(size, 42);
sink = std::accumulate(v.begin(), v.end(), 0u); // 确保其副效应
// 记录结束时间
auto end = std::chrono::system_clock::now();
std::chrono::duration<double> diff = end-start;
std::cout << "Time to fill and iterate a vector of "
<< size << " ints : " << diff.count() << " s\n";
}
}
/*gating error--门限错误
任何小于一个定时器嘀嗒的时间只花了0个时间单位。
20.2.3时点/时间点
时间点是从特定时钟的纪元开始经过的时间时长。time_point类表示时间中特定的某个时间点,存储为相对纪元(epoch)的duration。与特定的clock关联。
构造time_point
std::chrono::time_point<Clock,Duration>::time_point
//time_point构造函数
constexpr time_point();(C++14 起)(1)
//例如
std::chrono::time_point<std::chrono::system_clock> tp;
constexpr explicit time_point( const duration& d );(C++14 起)(2)
//例如,
std::chrono::time_point<std::chrono::system_clock> tp(std::chrono::seconds(10));
template< class Duration2 >
constexpr time_point( const time_point<Clock,Duration2>& t );(3)
//例如
std::chrono::time_point<std::chrono::system_clock> tp(std::chrono::seconds(10));
std::chromo::time_point<std::chrono::steady_clock> tp2(tp1);
从数个可选数据源之一构造新的 time_point 。
1) 默认构造函数,创建表示 Clock 的纪元(即 time_since_epoch() 为零)的 time_point 。
2) 构造位于 Clock 的纪元加上 d 的 time_point 。
3) 通过转换 t 为 duration 构造 time_point 。此构造函数仅若 Duration2 可隐式转换为 duration 才参与重载决议。
参数
d - 复制来源的 duration
t - 转换来源的 time_point
同时,每个clock都知道各自的time_point类型,因此可以这样创建:
system_clock::time_point tp1;
time_point转换,如从秒变成毫秒周期
//隐式转换
//秒→毫秒
time_point<steady_clock, seconds> tpSeconds(421s);
time_point<steady_clock, milliseconds> tpMilliseconds(tpSeconds);
//显式转换--防止丢失数据
//类似duration_cast
//毫秒→秒,可能损失
tpSeconds = time_point_cast<seconds>(tpMilliseconds);
//秒→毫秒,并输出。以上均为time_point,以下为duration
milliseconds ms(tpSeconds.time_since_epoch());
cout << ms.count() << " ms" << endl;
//time_point成员函数
constexpr duration time_since_epoch() const;
返回表示保存的时间点 *this 与所关联的 clock 的纪元间的时间量的 duration 。
返回值
此 time_point 与 clock 的纪元间的时间量
在标头 <ctime> 定义
char* ctime( const std::time_t* time );
转换给定的从纪元起时间为日历时间,再转换为文本表示,如同通过调用 std::asctime(std::localtime(time)) 。 产生的字符串拥有如下格式:
Www Mmm dd hh:mm:ss yyyy\n
Www - 星期( Mon 、 Tue 、 Wed 、 Thu 、 Fri 、 Sat 、 Sun 之一)。
Mmm - 月份( Jan 、 Feb 、 Mar 、 Apr 、 May 、 Jun 、 Jul 、 Aug 、 Sep 、 Oct 、 Nov 、 Dec 之一)。
dd - 月之日
hh - 时
mm - 分
ss - 秒
yyyy - 年
函数不支持本地化。
参数
time - 指向指定要打印时间的 std::time_t 对象
返回值
返回指向静态空终止字符串的指针,字符串保有上述的日期和时间的文本表示。字符串可以在 std::asctime 和 std::ctime 间共享,并且可以被每次对这些函数的调用重写。
注意
ctime 返回指向静态数据的指针,而且非线程安全。另外它修改可能会为 std::gmtime 及 std::localtime 所共享的静态 std::tm 对象。 POSIX 标记此函数标为过时,并推荐用 std::strftime 替代。
对于导致字符串长于 25 字符(例如, 10000 年)的 time_t 的值, ctime 的行为可能未定义。
#include <iostream>
#include <chrono>
#include <ctime>
int main()
{
auto p0 = std::chrono::time_point<std::chrono::system_clock>{}; //默认构造函数
auto p1 = std::chrono::system_clock::now();
auto p2 = p1 - std::chrono::hours(24);
auto epoch_time = std::chrono::system_clock::to_time_t(p0);
std::cout << "epoch: " << std::ctime(&epoch_time);
auto today_time = std::chrono::system_clock::to_time_t(p1);
std::cout << "today: " << std::ctime(&today_time);
std::cout << "hours since epoch: "
<< std::chrono::duration_cast<std::chrono::hours>(
p1.time_since_epoch()).count()
<< '\n';
std::cout << "yesterday, hours since epoch: "
<< std::chrono::duration_cast<std::chrono::hours>(
p2.time_since_epoch()).count()
<< '\n';
}
/*
epoch: Thu Jan 01 08:00:00 1970
today: Mon May 29 03:08:50 2023
hours since epoch: 468139
yesterday, hours since epoch: 468115
*/
合理的time_point与duration的算数运算可以进行,time_point之间无算术运算。同时还有min,max,floor,ceil,round操作,同duration
20.3 生成随机数 < random>
cppreference-< random>
基于软件的随机数生成器永远都不可生成真正的随机数,而是根据数学公式随机的效果,因此称为伪随机数生成器。
请使用< random>库。由随机数引擎,随机数引擎适配器和分布组成。
随机数引擎:负责生成实际的随机数,并将生成后续随机数需要的状态保存起来。
分布:判断生成随机数的范围以及随机数在范围内的数学分布情况。
随机数引擎适配器:修改相关联的随机数引擎生成的结果。
通常根据这些属性来描述 随机数生成器:
- 周期长度:重复生成的数字序列所需的迭代数。 越长越好。
- 性能:生成数字的速度和所需的内存量。 越小越好。
- 质量:生成的序列与真正的随机数的接近程度。 这通常称为“随机性”。
20.3.1 随机数引擎
非确定性随机数-random_device:通过使用外部设备,生成非确定性的、安全加密的随机序列。 通常用于为引擎设定种子。 低性能,高质量。
随机数生成器的质量由随机数的熵(entropy)决定。确定的随机数生成器(例如伪随机数生成器)拥有零熵。否则entropy()返回设备熵的值。
一旦熵池耗尽,许多 random_device 实现的性能就急剧下滑。对于实践使用, random_device 通常仅用于播种类似 mt19937 的伪随机数生成器。
#include <iostream>
#include <random>
int main()
{
std::random_device rd;
std::cout << rd.entropy() << '\n';
std::cout << rd.min() << rd.max() << rd(); //min max为静态方法,此处为默认构造,其余见cppreference
}
random_device速度比伪随机数引擎更慢,因此需要大量生成随机数,建议使用伪随机数引擎,使用random_device为随机数引擎生成种子
linear_congruential_engine 实现线性同余算法。保存状态所需内存最少,状态时上一次生成的随机数的整数,如果还没生成随机数,则保存的是初始的种子。周期取决于算法的参数,最高2^64, 通常不会那么高
mersenne_twister_engine实现梅森缠绕器算法。随机数质量最高,周期取决于参数,比线性同余引擎的长很多,保存状态所需的内存量也取决于参数,比线性同余引擎的整数状态搞很多。
subtract_with_carry_engine实现带进位减(一种延迟斐波那契)算法。质量不如梅森旋转算法。
random_device易于使用,不需要参数。其余都需要复杂的数学参数。因此请使用预定义的类型别名,如mt19937等。
20.3.2 随机数引擎适配器
discard_block_engine | 舍弃随机数引擎的某些输出(类模板) |
independent_bits_engine | 将一个随机数引擎的输出打包为指定位数的块(类模板) |
shuffle_order_engine | 以不同顺序发送一个随机数引擎的输出(类模板) |
template<
class Engine,
std::size_t P, std::size_t R
> class discard_block_engine;
(C++11 起)
discard_block_engine 是伪随机数引擎适配器,它忽略基础引擎所产生的一定量数据。生成器只从基础引擎生成的每个 P 大小的块保留 R 个数,舍弃剩余的数。
模板形参
Engine - 包装的引擎类型
P - 块大小。期待 P > 0 。
R - 每块的使用数的数量。期待 0 < R ≤ P 。
independent_bits_engine 适配器组合由基础引擎生成的一些随机数,以生成有给定位数w的随机数。
shuffle_order_engine 适配器生成的随机数和基础引擎生成的随机数一致,但顺序不同。
//此部分模糊,待补充
20.3.3 预定义的随机数引擎和引擎适配器
请使用
20.3.4 生成随机数
1.创建引擎实例(使用预定义的),如果是基于软件的引擎,还要定义数学分布。
需要种子初始化,请使用dandom_device。而不是基于时间的种子。
#include <ctime>
#include<iostream>
#include<random>
#include<algorithm>
#include<functional>
#include<vector>
using namespace std;
int main()
{
//1.准备种子
random_device seeder;
const auto seed{ seeder.entropy() ? seeder() : time(nullptr) };//熵为0,则回归基于时间的种子
//2.生成器实例
mt19937 engine{ static_cast<mt19937::result_type>(seed) };//静态转换对象,防止
//3.定义分布
uniform_int_distribution<int> distribution{ 1, 99 }; //使用均匀分布,均匀最小a,最大b,默认int
//16.5 函数对象,bind在此可避免生成随机数时指定分布和引擎,返回一个函数对象
auto generator{ bind(distribution, engine) }; //CP18-P419
vector<int> values(10);
//以给定函数对象 generator 所生成的值赋值范围 [first, last) 中的每个元素 CP18
//gengerate()算法改写现有元素,不插入新元素,所以要足够大。
generate(begin(values), end(values), generator);
for (auto i : values) {
cout << i << " ";
}
cout << endl;
}
bind(distribution, engine)
等价于distribution(engine)
原型
result_type operator()( Generator& g )
参数
g - 均匀随机位生成器对象(均匀随机位生成器是返回无符号整数值的函数对象,可能结果范围中的每个值都(理想情况)拥有等概率。)
返回值
生成的随机数。
//----------
generate(begin(values), end(values), generator);
可封装
1.使用std::function<int()>类型的参数(CP18.2--函数包装器--函数对象部分,用作实现回调的函数的参
数)
void fillVector(vector<int> vec, const std::function<int()> generator)
{
generate(begin(values), end(values), generator);
}
关于std::function<int()>的解释(位于<functional>
std::function< R(ArgTypes…) >;// R–函数返回值的类型,ArgTypes是一个以逗号分隔的函数参数类型的列表。
void func(int num, const string& str) {}.
function< void( int, const string&) > f1 = func; //std::function
也可以让编译器自己猜: auto f1 = func;// 推断类型为函数指针void (*f1)(int, const string&),不是function
2.函数模板
template<typename T>
void fillVector(vector<int> vec, const T& generator)
{
generate(begin(values), end(values), generator);
}
之后
fillVector(vec, gen);
#include <random>
#include <iostream>
int main()
{
std::random_device rd; // 将用于为随机数引擎获得种子
std::mt19937 gen(rd()); // 以播种标准 mersenne_twister_engine
std::uniform_int_distribution<> dis(1, 6);
for (int n=0; n<10; ++n)
// 传入引擎,用 dis 变换 gen 所生成的随机 unsigned int 到 [1, 6] 中的 int
std::cout << dis(gen) << ' ';
std::cout << '\n';
}
20.3.5 随机数分布
分布是要给描述数字在特定范围内分布的数学公式。random库自带的分布和伪随机数引擎结合使用,从而定义生成的随机数的分布。
#include <ctime>
#include <random>
#include <functional>
#include <map>
#include <fstream>
using namespace std;
int main()
{
const unsigned int Start{ 1 };
const unsigned int End{ 99 };
const unsigned int Iterations{ 1'000'000 };
// Uniform distributed Mersenne Twister.
random_device seeder;
const auto seed{ seeder.entropy() ? seeder() : time(nullptr) };
mt19937 engine{ static_cast<mt19937::result_type>(seed) };
//均匀分布-生成整数
uniform_int_distribution<int> distribution{ Start, End };
//正态分布
//normal_distribution<double> distribution{ 50, 10 };
auto generator{ bind(distribution, engine) };
map<int, int> histogram;
for (unsigned int i{ 0 }; i < Iterations; ++i) {
//正态分布-是double
//int randomNumber{ static_cast<int>(generator()) };//转换为整数
int randomNumber{ generator() };
// Search map for a key=randomNumber. If found, add 1 to the value associated
// with that key. If not found, add the key to the map with value 1.
++(histogram[randomNumber]);
}
// Write to a CSV file.
/*
其文件以纯文本形式存储表格数据(数字和文本)。纯文本意味着该文件是一个字符序列,
不含必须像二进制数字那样被解读的数据。CSV文件由任意数目的记录组成,记录间以某种换行符分隔;
每条记录由字段组成,字段间的分隔符是其它字符或字符串,最常见的是逗号或制表符。
通常,所有记录都有完全相同的字段序列。通常都是纯文本文件
*/
ofstream of{ "res.xlsx" };
for (unsigned int i{ Start }; i <= End; ++i) {
of << i << ";" << histogram[i] << endl;
}
}
20.4 optional——C++17
< optional>定义了std::optional用于保存特定类型的值,或什么都不保存。一种常见的 optional 使用情况是一个可能失败的函数的返回值。
template< class T > class optional;
// optional 可用作可能失败的函数的返回类型
/*
构造函数
std::optional<int> o1, // 空。默认构造函数
o2 = 1, // 从右值初始化
o3 = o2; // 复制构造函数
赋值operator=
o1 = o2;
返回指向所含值的指针
您可以使用 -> 操作符来访问存储在 std::optional 实例中的对象的成员函数和成员变量
constexpr T* operator->() noexcept;
访问值,返回到所含值的引用
operator*
cout << *o1;
判断是否含值
o1.has_value();//或直接if(o1)
返回值,到所含值的引用。
o1.value();
返回值,没有则返回设定好的默认值
o1.value_or("default_value");
*/
std::optional<std::string> create(bool b) {
if(b)
return "Godzilla";
else
return {};
}
//调用
auto str = create(true);
不能在optional中存储引用,应当使用optional<T*>,optional<reference_wrapper< T>>或者optional<reference_wrapper< const T>>。可以使用std::ref()或者cref分别创建。
#include <string>
#include <functional>
#include <iostream>
#include <optional>
// optional 可用作可能失败的函数的返回类型
std::optional<std::string> create(bool b) {
if(b)
return "Godzilla";
else
return {};
}
// 能用 std::nullopt 创建任何(空的) std::optional
auto create2(bool b) {
return b ? std::optional<std::string>{"Godzilla"} : std::nullopt;
}
// std::reference_wrapper 可用于返回引用
auto create_ref(bool b) {
static std::string value = "Godzilla";
return b ? std::optional<std::reference_wrapper<std::string>>{value}
: std::nullopt;
}
int main()
{
std::cout << "create(false) returned "
<< create(false).value_or("empty") << '\n';
// 返回 optional 的函数可用作 while 和 if 的条件
if (auto str = create2(true)) {
std::cout << "create2(true) returned " << *str << '\n';
}
if (auto str = create_ref(true)) {
// 用 get() 访问 reference_wrapper 的值
// std::optional 对象 str 包含一个 std::reference_wrapper 对象
//因此我们需要使用 -> 运算符来访问被包装的对象。
//您可以使用 -> 操作符来访问存储在 std::optional 实例中的对象的成员函数和成员变量
std::cout << "create_ref(true) returned " << str->get() << '\n';
str->get() = "Mothra";
std::cout << "modifying it changed it to " << str->get() << '\n';
}
}
/*
create(false) returned empty
create2(true) returned Godzilla
create_ref(true) returned Godzilla
modifying it changed it to Mothra
*/
20.5 variant——C++17
cppreference-variant
用于保存给定类型集合的一个值,必须指定可能包含的类型。同一时刻,只能包含一个值。
要想在variant存储引用,方法同optional
默认构造函数。抵押给类型必须是可购早的
同样有复制,移动构造函数
variant<int, string, float> v;
非可默认构造的 variant 可以列 std::monostate 为其首个可选项:这使得 variant 自身可默认构造。
struct S
{
S(int i) : i(i) {}
int i;
};
// 若无 monostate 类型则此声明将失败。
// 这是因为 S 不可默认构造。
std::variant<std::monostate, S> var;
赋值运算符 operator()
v = 12;
v = 12.5f;
返回 variant 当前所存储值的类型的的零基下标
constexpr std::size_t index() const noexcept
template< class T, class... Types >
constexpr bool holds_alternative( const std::variant<Types...>& v ) noexcept
检查 variant v 是否保有可选项 T
返回值
若 variant 当前保有可选项 T 则为 true ,否则为 false 。
std::variant<int, std::string> v = "abc";
std::cout << std::boolalpha
<< "variant holds int? "
<< std::holds_alternative<int>(v) << '\n'
<< "variant holds string? "
<< std::holds_alternative<std::string>(v) << '\n';
//std::get (std::variant)获得指定类型的值
以给定索引或类型(如果类型唯一)读取 variant 的值,错误时抛出异常
#include <variant>
#include <string>
int main()
{
std::variant<int, float> v{12}, w;
int i = std::get<int>(v);//获得类型为int的值
w = std::get<int>(v);
w = std::get<0>(v); // 效果同前一行,获得索引为0的值
// std::get<double>(v); // 错误: [int, float] 中无 double
// std::get<3>(v); // 错误:合法的 index 值是 0 和 1
try {
std::get<float>(w); // w 含有 int ,非 float :将抛出异常
}
catch (std::bad_variant_access& ex) {
cout << " Exception:" << ex.what() << endl;
}//bad_variant_access异常
}
std::get_if
以给定索引或类型(如果唯一),获得指向被指向的 variant 的值的指针,错误时返回空指针
std::visit
以一或多个 variant 所保有的各实参调用所提供的函数对象
class myVisitor
{
public:
void operator()(int i) {...};
void operator()(const string& s) {...};
void operator()(float f) {...};
}
visit(myVisitor, v);
这样根据variant中当前存储的值,调用事当的函数运算符。
20.6 any——C++17
包含任意类型的值,一旦构建,可确认any实例中是否含值,以及类型。
要想存储引用,方法同optional
#include <any>
#include <iostream>
int main()
{
std::cout << std::boolalpha;
// any 类型
std::any a = 1;
std::cout << a.type().name() << ": " << std::any_cast<int>(a) << '\n';
a = 3.14;
std::cout << a.type().name() << ": " << std::any_cast<double>(a) << '\n';
a = true;
std::cout << a.type().name() << ": " << std::any_cast<bool>(a) << '\n';
// 有误的转型
try
{
a = 1;
//template<class T> T any_cast(any& operand);
std::cout << std::any_cast<float>(a) << '\n'; //any_cast访问,否则抛出异常
}
catch (const std::bad_any_cast& e)
{
std::cout << e.what() << '\n';
}
// 拥有值
a = 1;
if (a.has_value()) //判断是否有值
{
std::cout << a.type().name() << '\n'; //返回类型名
}
// 重置
a.reset();
if (!a.has_value())
{
std::cout << "no value\n";
}
// 指向所含数据的指针
a = 1;
int* i = std::any_cast<int>(&a);
std::cout << *i << "\n";
}
/*
i: 1
d: 3.14
b: true
bad any_cast
i
no value
1
*/
可在标准容器中存储any,以存储异构数据,只能any_cast访问
vector<any> v;
v.push_back(42);
v.push_back("An std::string"s);
cout << any_cast<string>(v[1]) << endl;
20.7 元组 < utility>
CP17 讲过std::pair。编译时确定大小和类型。
pair<int, string> a = {16, "haha"};
pair<int, string> a(16, "haha");
还有< tuple> 中的std::tuple类。它是对pair的泛化,允许存储任意数量的值,每个值都有自己特定的类型。编译时确定大小和类型。
构造
//https://zh.cppreference.com/w/cpp/utility/tuple/tuple
int main()
{
std::tuple<int, std::string, double> t1;
print("值初始化,t1:", t1);
std::tuple<int, std::string, double> t2{42, "Test", -3.14};
print("通过值进行初始化,t2:", t2);
std::tuple<char, std::string, int> t3{t2};
print("隐式转换,t3:", t3);
std::tuple<int, double> t4{std::make_pair(42, 3.14)};
print("从 pair 构造,t4:", t4);
// 给定拥有单实参构造函数的分配器 my_alloc my_alloc(int)
// 用 my_alloc(1) 在 vector 中分配 5 个 int
using my_alloc = std::allocator<int>;
std::vector<int, my_alloc> v{5, 1, my_alloc{/* 1 */}};
// 用 my_alloc(2) 在 tuple 中的 vector 中分配 5 个 int
std::tuple<int, std::vector<int, my_alloc>, double> t5
{std::allocator_arg, my_alloc{/* 2 */}, 42, v, -3.14};
print("带分配器构造,t5:", t5);
}
/*
值初始化,t1:(0, "", 0)
通过值进行初始化,t2:(42, "Test", -3.14)
隐式转换,t3:('*', "Test", -3)
从 pair 构造,t4:(42, 3.14)
带分配器构造,t5:(42, {1,1,1,1,1}, -3.14)
*/
因为类型自动推导,不能通过&来指定引用。如果要包含引用,需要ref()和cref()
double d = 3.14;
string str = "test";
std::tuple t(16, cref(d), ref(d), ref(str));
也可以这样
auto t = std::make_tuple(16, cref(d), ref(d), ref(str));
访问指定元素
std::get<i>(t)
i 编译时必须已知
t - 要提取内容的 tuple
可以是索引---整数值,i 是从0开始的索引
可以是要提取的元素类型,提取 tuple t 的类型为 T 的元素。若 tuple 不恰好拥有仅一个该类型元素则编译失败。
返回值
到 t 的被选中元素的引用。
可通过< typeinfo> 头文件中的typeid()检查get()是否返回了正确类型。typeid()返回值与编译器有关。
#include <iostream>
#include <typeinfo>
struct Base { virtual ~Base() = default; };
struct Derived : Base {};
int main() {
Base b1;
Derived d1;
const Base *pb = &b1;
std::cout << typeid(*pb).name() << '\n';
pb = &d1;
std::cout << typeid(*pb).name() << '\n';
system("pause");
return 0;
}
/*
4Base
7Derived
*/
std::tuple_size()查询tuple的大小
继承自 std::integral_constant
using MyTuple = tuple<int, string, bool>;
cout << tuple_size<MyTuple>::value;
不知道tuple类型
tuple_size< decltype(t1)>::value;//t1是一个tuple
#include <iostream>
#include <tuple>
template <class T>
void test(T value)
{
/*
// tuple 辅助类
template<class T>
inline constexpr size_t tuple_size_v = tuple_size<T>::value;
*/
int a[std::tuple_size_v<T>]; // 能用于编译时
std::cout << std::tuple_size<T>{} << ' ' // 或运行时
<< sizeof a << ' '
<< sizeof value << '\n';
}
int main()
{
test(std::make_tuple(1, 2, 3.14));
}
//3 12 16
20.7.1 分解元组
将一个元组分解为单独的元素
一是结构化绑定,必须全部分解,无法忽略某个元素
tuple t(16, "Test", true);
auto[i, str, b] = t1;
二是tie()—含左值引用的 std::tuple 对象。
tuple<int, string, bool> t(16, "Test", true);
int i = 0;
string str;
bool b = false;
tie(i, str, b) = t1; //改变了独立变脸的值
tie(i, std::ignore, b) = t1; //忽略str
20.7.2 串联
tuple<int, string, bool> t(16, "Test", true);
tuple<double, string> t1(3.14, "hha");
auto t2 = tuple_cat(t, t1);
20.7.3 比较
直接使用比较运算符–按词典
或者将元素使用tie串联,以实现operator<等运算符,再比较。
20.7.4 make_from_tuple()
多用于模板元编程或模板泛型代码
构建一个T类型的对象,将给定的tuple的元素作为参数传递给T的构造函数。
或者array,pair也行,只要支持get<>和tuple_size()
#include <iostream>
#include <tuple>
struct Foo
{
Foo(int first, float second, int third)
{
std::cout << first << ", " << second << ", " << third << "\n";
}
};
int main()
{
auto tuple = std::make_tuple(42, 3.14f, 0);
std::make_from_tuple<Foo>(std::move(tuple));
}
//42, 3.14, 0
20.7.5 apply()–日常不实用,
多编写使用模板的反省代码或进行模板值类元编程
调用给定的函数,lambda表达式和函数对象等,将给定的tuple元素作为实参传递。
int add(int a, int b) { return a + b;}
cout << apply(add, std::make_tuple(39, 3)) << endl;
20.8 文件系统支持库-< filesystem>
位于std::filesystem名称空间中。可以实现可以指的用于文件系统的代码。
最重要的:path和directory_entry(目录项)
20.8.1 path
cppreference-filesystem
附录B
绝对路径or相对路径,是否包含文件名都可以。
L表示双字节,也就是每个字符占用两个字节,就是可能出现中文,R是里面的字符串都不转义,所以可以加括号什么的
**
2024 3/30
win下L才是uft-16,Linux是utf-32。BV1Br421b7KH 3:33
**
path p( LR"(D:\fo)") ;
path p( L"D:\\fo") ;
path p( L"D:/fo");
path p( L"D:/fo/2.txt");
path p( LR"(...\fo)") ;
path p( L"/fo");
将path转换为字符串(c_str() )或插入流,会被转换成本地格式
append()或operator/=,将组件追加到路径,自动添加分隔符
path p( L"D:\\fo") ;
p.append("Bar");
p /= "haha"
cout << p;
将输出D:\fo\Bar\haha
concat()或operator+=,将字符串与现有路径项链,不添加分隔符
path p( L"D:\\fo") ;
p.concat("Bar");
p += "haha"
cout << p;
将输出D:\foBarhaha
支持迭代器迭代不同组件
path p(;R"(C:\fOO\bAR)");
for(const auto& component : p) {
cout << component << endl;
}
/*
输出:
C:
\
Foo
Bar
*/
20.8.2 directory_entry
表示目录条目。该对象存储一个 path 作为成员,并可能也在目录迭代过程中存储附带的文件属性(硬链接数、状态、符号链接状态、文件大小、及最后写入时间)。
path只表示文件系统的目录或文件。可能指向不存在的目录和文件。
剩余内容
附录2
以及P484
//boost待了解