目录
1.匹配 bool regex_match(string str,regex reg);
2. 正则替换string regex_replace(string str,regex reg, string newstr);
②控制替换,仅替换第一次出现的 regex_constants::format_first_only
③不拷贝方式(不返回与匹配内容无关的字段,相关的字段全部进行替换) regex_constants::format_no_copy
① 法一:bool regex_search(string str,smatch result,regex reg)
③法三: sregex_token_iterator(iterator begin,iteartor end,regex regex, int flag);
一、智能指针
头文件:(管理内存)
#include<memory>
1.智能指针的本质:
是一个模板类,一般使用是用的这个类的对象,而不是指针
2.“何谓智能”:
体现在内存释放问题。用智能指针管理new的对象, 将不在需要手动delete
3.三种常用的智能指针:
①shared_ptr
A.申请内存初始化及访问数据(实例化用的int类型而非int*!!!!,其余数据类型同理)
//1.基本用法
//new int(1111)申请内存做初始化
//1.1 做初始化
shared_ptr<int> pInt(new int(1111));
//1.2 访问数据
cout << *pInt << endl; //*指针
//shared_ptr<int> pNum = new int(222); 错误.
class WBM
{
public:
void print()
{
cout << "打印" << endl;
}
~WBM()
{
cout << "析构函数被调用" << endl;
}
protected:
};
//2.自定义类型
{
shared_ptr<WBM> pMM(new WBM);
//WBM* p = new WBM; //手动释放
pMM->print();
WBM* pM = pMM.get();
}
输出:
1111
打印
析构函数被调用
构造出share_ptr对象的第二种写法->用make_shared()方法
shared_ptr<int> pMake = make_shared<int>(12);
cout << *pMake << endl;
//make_shared管理自定义类型,参数个数由构造函数决定
B.常用成员函数:
(i)get() 函数: 返回数据的指针的引用
//get()函数
shared_ptr<int> pInt(new int(1111));
int* pData = pInt.get();
注: 当通过get()获得指针时,不能够手动释放pData——>会引起二次析构问题
(ii)use_count(): 返回的是管理对象的智能指针对象数
(n个智能指针共同管理同一个数据,返回n)->“共用”
//use_count()函数
shared_ptr<int> aptr(new int(10));
cout <<"get读取数据:" << *aptr.get() << endl;
cout << "正常使用:" << *aptr<<endl;
cout <<"管理对象的指针数:" << aptr.use_count() << endl;
shared_ptr<int> bptr(aptr);
cout << "管理对象的指针数:" << aptr.use_count() << endl;
cout << "管理对象的指针数:" << bptr.use_count() << endl;
shared_ptr<int> cptr;
if (!cptr)
{
cout << "空智能指针对象" << endl;
}
cptr = bptr;
cout << "管理对象的指针数:" << cptr.use_count() << endl;
输出:
get读取数据:10
正常使用:10
管理对象的指针数:1
管理对象的指针数:2
管理对象的指针数:2
空智能指针对象
管理对象的指针数:3
(iii)swap():交换管理对象
(iv)reset():重置管理对象
//swap();
//reset(); 重新设置管理对象
void printData(shared_ptr<WBM>& pMM)
{
pMM->print();
cout << "管理对象的指针数:" << pMM.use_count() << endl;
}
shared_ptr<int> aa(new int(12));
shared_ptr<int> bb(new int(21));
aa.swap(bb);
cout << "aa:" << *aa << "\tbb:" << *bb << endl;
cout << "管理对象的指针数:" << aa.use_count() << endl;
bb.reset(new int(123));
cout << "bb:" << *bb << endl;
{
cout << "当做函数参数....." << endl;
shared_ptr<WBM> pMM(new WBM);
cout << "管理对象的指针数:" << pMM.use_count() << endl;
printData(pMM);
}
输出:
aa:21 bb:12
管理对象的指针数:1
bb:123
当做函数参数.....
管理对象的指针数:1
打印
管理对象的指针数:1
析构函数被调用
C.带删除器的写法
若是管理的一段内存,那就必须写带删除器的写法,
(删除器:理解为自己写释放内存的过程->第二个参数为函数指针(可有三种方法))
//4.带删除器的写法
//删除器: 理解为自己写释放内存的过程
void freeFile(FILE* fp)
{
free(fp);
cout << "文件释放成功" << endl;
}
{/*此处用的是lambda表达式作为第二个参数(删除器)!!!*/
cout << "带删除器写法....." << endl;
shared_ptr<WBM> p(new WBM[4], [](WBM* array) {delete[] array; });
}
shared_ptr<FILE> pf(fopen("1.txt", "w"), freeFile);/*采用的函数指针作为删除器*/
输出:
带删除器写法.....
析构函数被调用
析构函数被调用
析构函数被调用
析构函数被调用
文件释放成功
②weak_ptr
A.弱引用指针,不会累计计数(应用场景比较单一)
B.weak_ptr只能通过shared_ptr或者weak_ptr来构造(不能直接管理数据)
C.主要应用场景: 为了解决shared_ptr 循环引用内存导致无法释放问题
D.不可使用* 取值,能使用->取值
E.通过成员函数lock获取shared_ptr对象 然后再访问数据
例1:常规使用(访问数据的方法)
class MM
{
public:
MM() {}
void print()
{
cout << "打印" << endl;
}
~MM() { cout << "析构" << endl; }
protected:
};
shared_ptr<MM> pMM(new MM);
shared_ptr<MM> pMM2 = pMM;
cout << "计数:" << pMM2.use_count() << endl;
weak_ptr<MM> pwMM(pMM);
cout << "计数:" << pMM2.use_count() << endl;
//(*pwMM).print();//weak_ptr不存在这种访问方式
//pwMM.lock(): shared_ptr对象
pwMM.lock().get()->print();
(pwMM.lock())->print();
输出:
计数:2
计数:2
打印
打印
例2:应用场景(解决share_ptr的循环引用问题->内存泄露)
class B;
class A
{
public:
A() { cout << "A" ;}
~A() { cout << "~A"; }
weak_ptr<B> b;
};
class B
{
public:
B() { cout << "B"; }
~B() { cout << "~B"; }
weak_ptr<A> a;
};
void testLoopUse()
{
cout << "循环引用....." << endl;
shared_ptr<A> aObject(new A);
shared_ptr<B> bObject(new B);
aObject->b = bObject;
bObject->a = aObject;
cout << endl << "计数:" << bObject.use_count() << endl;
cout << endl;
}
输出:
循环引用.....
AB
计数:1~B~A
12
析构
③unique_ptr
A.禁止拷贝和赋值,独占型(无use_count方法!)
B.也可以用make_unique去构造出对象
unique_ptr<MM> pMM(new MM(1111));
unique_ptr<int> mynum = make_unique<int>(666);
//unique_ptr<MM> pMM2(pMM); // 错误
unique_ptr<MM> pMM3;
//pMM3 = pMM2; // 错误
cout << "MM num:" << pMM->getNum() << endl;
cout << "MM num:" << pMM.get()->getNum() << endl;
C.任何时候unqiue_ptr操作管理对象,永远都只有一个有效
(i)可以通过move函数转交所有权 (原ptr就不能再去访问了!!)两种写法(move语句ormove构造)转接所有权后,可以用reset重新赋值。
(ii)reset函数结合release函数移交所有权(不用release本质上也是一种赋值,所以会报错。)
class MM
{
public:
MM() {}
MM(int num) :num(num) {}
int getNum() { return num; }
~MM() { cout << "MM:" << num << endl; };
protected:
int num=0;
};
//move转交所有权
pMM3 = move(pMM);
cout << "MM num:" << pMM3->getNum() << endl;
//cout << "MM num:" << pMM->getNum() << endl; // pMM不在管理对象
unique_ptr<MM> pMM4(move(pMM3)); //MM&&
//可以重新设置
pMM.reset(new MM(1));
cout << "MM num:" << pMM->getNum() << endl;
unique_ptr<MM> pMM5;
pMM5.reset(pMM4.release());
cout << pMM5->getNum() << endl;
D.访问数据:有get方法,也可以直接当做一个指针使用。
E:构造unique_ptr对象时除了传递指针外,还可以提供一个删除器。如果没有指定删除器,unique_ptr会使用缺省删除器,即缺省使用delete和delete[]来销毁对象,分别用于普通版本和数组版本。如果动态分配的资源有专门的释放函数,必须在构造时同时提供一个删除器。比如:
unique_ptr<int, void(*)(int*)> up2(new int(4),
[](int *ptr){delete ptr;});
unique_ptr<int[], function<void(int*)>> ar_up2(new int[10],
[](int *ptr){delete[] ptr;});
注: 删除器要求是可调用对象,它的类型可以是函数指针、函数对象、lambda表达式、function对象等。->下面是仿函数释放+lambda释放的方法
class MM
{
public:
MM() {}
MM(int num) :num(num) {}
int getNum() { return num; }
~MM() { cout << "MM:" << num << endl; };
protected:
int num=0;
};
struct DeleteMM
{
void operator()(MM* mm)
{
delete[] mm;
cout << "释放成功...." << endl;
}
};
//删除器
{
unique_ptr<MM, DeleteMM> p(new MM[3]);
unique_ptr<MM, void(*)(MM*)> pp(new MM[3],
[](MM* mm) {delete[]mm; });
}
当使用提供删除器时,删除器的类型也要作为模板的参数类型声明在unique_ptr中,也就是说删除器类型也是unique_ptr模板实例化后的类型的一部分。因此,如果一个unique_ptr对象在move时,源和目的unique_ptr对象的删除器类型也必须一样,这点与shared_ptr不一样
一般情况下,unique_ptr释放的资源是在内存中动态分配的,但通过定制删除器,也可以使用unique_ptr来管理一些通过其他方式分配的资源,比如文件句柄、socket等。C++中有一种资源管理的惯例-RAII,可以通过它来实现对资源的自动释放,比如,对于使用fopen打开的文件,返回一个文件指针FILE,在程序结束时,需要调用fclose关闭,为了防止忘记关闭,可以手动编写一个RAII类来负责管理这个FILE的生命周期,不是很方便。现在可以使用unique_ptr来实现,只需定义一个删除器就可以了,非常方便。下面是一个使用unique_ptr来自动关闭打开文件的例子
FILE *file = fopen("/tmp/tmp.txt", "r"); // 分配FILE资源
unique_ptr<FILE, void(*)(FILE *)> up(file, [](FILE *file) {
fclose(file);
});
...//其它操作file的代码逻辑
二、正则表达式的预备知识
1.具有特殊意义的元字符
\:\字符能够改变字符原本的含义
^:^字符指示字符串的头,且要求字符串以字符开头,不占位。\^表示一个真正的^符号。
$:$字符指示字符串的尾,且要求字符串以字符结尾,不占位。\\$表示一个真正的$符号。
():分组,大正则中包含小正则。可以改变默认的优先级。在模式中可以使用\1来表示第一组已然捕获到的东西,\2来表示第二组已然捕获到的东西。
\b:指示字符串的边界(头/尾/空格左/空格右),字符\b要求边界的左边是字符,\b字符要求边界的右边是字符。
.:表示一个除了\n以外的任意一个字符。\\.表示一个真正的.符号。
|:a|b a或b之一
[abc]:abc之中的任意一个
\[^abc]: abc之外的
\[a-z]: 任意小写字母
\[^a-z]: 除了小写字母之外的
\w:任意一个字母数字下划线,等价于[(0-9)(a-z)(A-Z)(_)]
\W:字母数字下划线之外的,等价于[]
\d: 任意一个数子
\D: 除了数字之外的
\s: 空白符(空格、制表符、换页符)
2. 量词元字符
*:字符*要求字符出现0到多次 {0,}
+:字符+要求字符出现1到多次 (\w) {1,}
?:字符?要求字符出现0次或1次 {0,1}
{n}:字符{n}要求字符出现n次
{n,}:字符{n,}要求字符出现n到多次 {0,}
{n,m}:字符{n,m}要求字符出现n到m次、
所以含有
\
的元字符,在C++定义时,都要写成\\
3.校验数字的表达式
数字:^ [0 - 9] * $
n位的数字:^ \d{ n }$
至少n位的数字:^ \d{ n, }$
m - n位的数字: ^ \d{ m,n }$
零和非零开头的数字: ^ (0 | [1 - 9][0 - 9] *)$
非零开头的最多带两位小数的数字: ^ (\[1 - 9][0 - 9] *) + (.[0 - 9]{ 1,2 }) ? $
带1 - 2位小数的正数或负数: ^ (\ - ) ? \d + (\.\d{ 1,2 }) ? $
正数、负数、和小数: ^ (\ - | \ + ) ? \d + (\.\d + ) ? $
有两位小数的正实数: ^ [0 - 9] + (.[0 - 9]{ 2 }) ? $
有1~3位小数的正实数: ^ [0 - 9] + (.[0 - 9]{ 1,3 }) ? $
非零的正整数: ^ [1 - 9]\d * $ 或 ^ ([1 - 9][0 - 9] *) { 1, 3 }$ 或^ \ + ? \[1 - 9][0 - 9] * $
非零的负整数: ^ \ - [1 - 9][]0 - 9"$ 或 ^-[1-9]\d$
非负整数: ^ \d + $ 或 ^ [1 - 9]\d * | 0$
非正整数: ^ -[1 - 9]\d * | 0$ 或 ^ ((-\d + ) | (0 + ))$
非负浮点数: ^ \d + (.\d + ) ? $ 或 ^ [1 - 9]\d * .\d * | 0.\d * [1 - 9]\d * | 0 ? .0 + | 0$
非正浮点数: ^ ((-\d + (.\d + ) ? ) | (0 + (.0 + ) ? ))$ 或 ^ (-([1 - 9]\d * .\d * | 0.\d * [1 - 9]\d*)) | 0 ? \.0 + | 0$
正浮点数: ^ [1 - 9]\d * .\d * | 0.\d * [1 - 9]\d * $ 或 ^ (([0 - 9] + .[0 - 9] * [1 - 9][0 - 9] *) | ([0 - 9] * [1 - 9][0 - 9] * .[0 - 9] + ) | ([0 - 9] * [1 - 9][0 - 9] *))$
负浮点数: ^ -([1 - 9]\d * .\d * | 0.\d * [1 - 9]\d*)$ 或 ^ (-(([0 - 9] + .[0 - 9] * [1 - 9][0 - 9] *) | ([0 - 9] * [1 - 9][0 - 9] * .[0 - 9]) | ([0 - 9] * [1 - 9][0 - 9] *)))$
浮点数: ^ (-? \d + )(.\d + ) ? $ 或 ^ -? ([1 - 9]\d * .\d * | 0.\d * [1 - 9]\d * | 0 ? .0 + | 0)$
4.校验字符的表达式
汉字: ^ [\u4e00 - \u9fa5]{ 0, }$
英文和数字: ^ [A - Za - z0 - 9] + $ 或 ^ [A - Za - z0 - 9]{ 4,40 }$
长度为3 - 20的所有字符: ^ .{3, 20}$
由26个英文字母组成的字符串: ^ [A - Za - z] + $
由26个大写英文字母组成的字符串: ^ [A - Z] + $
由26个小写英文字母组成的字符串: ^ [a - z] + $
由数字和26个英文字母组成的字符串: ^ [A - Za - z0 - 9] + $
由数字、26个英文字母或者下划线组成的字符串: ^ \w + $ 或 ^ \w{ 3,20 }$
中文、英文、数字包括下划线: ^ [\u4E00 - \u9FA5A - Za - z0 - 9_] + $
中文、英文、数字但不包括下划线等符号: ^ [\u4E00 - \u9FA5A - Za - z0 - 9] + $ 或 ^ [\u4E00 - \u9FA5A - Za - z0 - 9]{ 2,20 }$
可以输入含有 ^ %&',;=?$\"等字符:\[^%&', ; = ? $\x22] + 12禁止输入含有~的字符:\[^ ~\x22] +
5. 特殊需求表达式(copy时,去空格+改成双‘\’)
Email地址: ^ \w + ([-+.]\w + ) * @\w + ([-.]\w + ) * \.\w + ([-.]\w + ) * $
域名:\[a - zA - Z0 - 9][-a - zA - Z0 - 9]{ 0,62 }(/ .\[a - zA - Z0 - 9][-a - zA - Z0 - 9]{ 0,62 }) + / . ?
InternetURL:[a - zA - z] + ://[^\s]* 或 ^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$
手机号码: ^ (13[0 - 9] | 14[5 | 7] | 15[0 | 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9] | 18[0 | 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9])\d{ 8 }$
电话号码(0511 - 4405222、021 - 87888822):\d{ 3 } - \d{ 8 } | \d{ 4 } - \d{ 7 }
身份证号(15位、18位数字): ^ \d{ 15 } | \d{ 18 }$
短身份证号码(数字、字母x结尾): ^ ([0 - 9]) { 7, 18 }(x | X) ? $ 或 ^ \d{ 8,18 } | [0 - 9x]{ 8,18 } | [0 - 9X]{ 8,18 } ? $
帐号:(字母开头,允许5 - 16字节,允许字母数字下划线): ^ \[a - zA - Z][a - zA - Z0 - 9_]{ 4,15 }$
密码:(以字母开头,长度在6~18之间,只能包含字母、数字和下划线): ^ [a - zA - Z]\w{ 5,17 }$
强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8 - 10之间):* ^ (? = .*\d)(? = .*[a - z])(? = .*[A - Z]).{8, 10}$
日期格式: ^ \d{ 4 } - \d{ 1,2 } - \d{ 1,2 }
一年的12个月(01~09和1~12): ^ (0 ? [1 - 9] | 1[0 - 2])$
一个月的31天(01~09和1~31): ^ ((0 ? [1 - 9]) | ((1 | 2)[0 - 9]) | 30 | 31)$
xml文件: ^ ([a - zA - Z] + -? ) + [a - zA - Z0 - 9] + \\.\[x | X]\[m | M][l | L]$
中文字符的正则表达式:[\u4e00 - \u9fa5]
双字节字符:\[^ \x00 - \xff](包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1))
空白行的正则表达式:\n\s * \r(可以用来删除空白行)
HTML标记的正则表达式:<(\S* ? )[^ > ] *>.* ? < / \1> | <.* ? / > (复杂的嵌套标记依旧无能为力)
首尾空白字符的正则表达式: ^ \s * | \s * $或(^ \s*) | (\s * $) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等))
腾讯QQ号:\[1 - 9][0 - 9]{ 4, } (腾讯QQ号从10000开始)
中国邮政编码:[1 - 9]\d{ 5 }(? !\d) (中国邮政编码为6位数字)
IP地址:\d + \.\d + \.\d + \.\d + (提取IP地址时有用)
IP地址:((? : (? : 25[0 - 5] | 2[0 - 4]\\d | [01] ? \\d ? \\d)\\.) { 3 }(? : 25[0 - 5] | 2[0 - 4]\\d | [01] ? \\d ? \\d))
三、正则表达式在C++中的应用
1.匹配 bool regex_match(string str,regex reg);
匹配返回: true 不满足返回false
①匹配用户输入的邮箱
regex reg("^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$");
string userName;
while (1)
{
cout << "请输入一个邮箱:";
cin >> userName;
bool result = regex_match(userName, reg);
if (result)
{
cout << "正确邮箱" << endl;
break;
}
}
输入输出:
请输入一个邮箱:b19031822@njupt.com.cn
正确邮箱
②匹配某一个字符串(会区分大小写!)
//正则匹配,一定是完全匹配
string str = "ILoveyou1314";
regex reg("[a-z0-9]+"); //小写祖母或者数字多个
if (regex_match(str, reg))
{
cout << "匹配" << endl;
}
else
{
cout << "不匹配" << endl; //IL 不满足正则规则
}
输出:不匹配
③ 通过设置参数,设置不区分大小写字母
//大小写问题 ,构造的时候可以忽略大小写
string str = "ILoveyou1314";
regex reg("[a-z0-9]+"); //小写祖母或者数字多个
regex reg2("[a-z0-9]+", regex_constants::icase);
if (regex_match(str, reg2))
{
cout << "匹配" << endl;
}
else
{
cout << "不匹配" << endl; //IL 不满足正则规则
}
输出:匹配
④字符串也可以直接充当正则规则(必须一一对应!!!)
//字符串也可以直接充当正则规则
string str = "ILoveyou1314";
regex reg3("ILoveyou");
if (regex_match(str, reg3))
{
cout << "匹配" << endl;
}
else
{
cout << "不匹配" << endl; //1314
}
输出:不匹配
2. 正则替换string regex_replace(string str,regex reg, string newstr);
注:并不会修改str原字符串
①正常匹配 (两种写法)
法一:缺省,将匹配到的所有数字,都进行replace
string str = "ILoveyou1314520IMissyou520me";
regex reg("\\d+");//匹配所有的数字!
cout << "str:" << regex_replace(str, reg, "我爱你") << endl;
cout << "oldstr:" << str << endl;
输出:
str:ILoveyou我爱你IMissyou我爱你me
oldstr:ILoveyou1314520IMissyou520me
法二:传入传参的默认参数 regex_constants::format_default
string str = "ILoveyou1314520IMissyou520me";
regex reg("\\d+");//匹配所有的数字!
//默认方式
cout << "default:" << regex_replace(str, reg, "我爱你",
regex_constants::format_default)<< endl;
输出: default:ILoveyou我爱你IMissyou我爱你me
②控制替换,仅替换第一次出现的 regex_constants::format_first_only
string str = "ILoveyou1314520IMissyou520me";
regex reg("\\d+");//匹配所有的数字!
//1.只替换第一次出现的
cout << "only first:" << regex_replace(str, reg, "我爱你",
regex_constants::format_first_only<< endl;
输出:only first:ILoveyou我爱你IMissyou520me
③不拷贝方式(不返回与匹配内容无关的字段,相关的字段全部进行替换) regex_constants::format_no_copy
string str = "ILoveyou1314520IMissyou520me";
regex reg("\\d+");//匹配所有的数字!
//2.不拷贝
cout << "no copy:" << regex_replace(str, reg, "我爱你",
regex_constants::format_no_copy)<< endl;
3.截取处理字符串
① 法一:bool regex_search(string str,smatch result,regex reg)
不改变原字符串
//正常截取处理 匹配项
string str = "ILoveyou1314IMissyou520me";
smatch result;
bool flag = regex_search(str, result, regex("\\d+"));
if (flag)
{
cout << "size:" << result.size() << endl;
for (int i = 0; i < result.size(); i++)
{
cout << result.str() << endl; //字符串
}
cout << "pre:" << result.prefix() << endl;
cout << "suf:" << result.suffix() << endl;
}
cout << "str:" << str << endl;
//匹配后面: result.suffix作为循环条件去持续截即可
输出:
size:1
1314
pre:ILoveyou
suf:IMissyou520me
②法二:srgex_iterator
操作类似于istream_iterator
string str = "ILoveyou1314IMissyou520me";
regex rule("\\d+");
sregex_iterator pos(str.begin(), str.end(), rule);
sregex_iterator end; //end_of_iterator
while (pos != end)
{
cout << pos->str() << endl;
pos++;
}
//拆解字符串;
输出:
1314
520
③法三: sregex_token_iterator(iterator begin,iteartor end,regex regex, int flag);
flag: 0: 所有匹配的,-1 存储所有不匹配的
操作类似于istream_iterator
//sregex_token_iterator(iterator begin,iteartor end,regex regex, int flag);
//flag: 0: 所有匹配的,-1 存储所有不匹配的
string str = "ILoveyou1314IMissyou520me";
regex regexRule("\\d+");
sregex_token_iterator Begin(str.begin(), str.end(), regexRule, 0);
sregex_token_iterator End;
while (Begin != End)
{
cout << Begin->str() << endl;
Begin++;
}
输出:
1314
520