文章目录
- 一、tuple类型
- 二、bitset类型
- 三、 正则表达式
- 1、使用正则表达式库
- 2、匹配与regex迭代器类型
- 3、使用子表达式
- 4、使用regex_replace
- 5、本节demo:
- 四、 随机数
- 1、随机数简介:
- 2、random_shuffle()方法:
- 五、IO库再探
一、tuple类型
tuple是类似pair的模板。每个pair的成员类型都不同,但每个pair都恰好有两个成员。不同tuple类型的成员类型也不相同,但一个tuple可以有任意数量的成员。每个确定的tuple类型的成员数目是固定的,但一个tuple类型的成员数目可以与另一个tuple类型不同。
当我们希望将一些数据组合成单一对象,但又不想麻烦地定义一个新数据结构来表示这些数据时,tuple是非常有用的。
tuple支持的操作:
#include <bits/stdc++.h>
using namespace std;
tuple<int, string> foo()//函数foo返回tuple类型。
{
return make_tuple(2014, "tuple");//用make_tuple来构造一个tuple。
}
int main(int argc, char const *argv[])
{
/******************
*定义和初始化tuple。
*******************/
tuple<string, vector<double>, pair<string, int>> item("abc", {1.0, 2.0, 3.0}, {"ABC", 3});
//用get访问tuple数据成员,返回第三个成员pair类型的第一个元素。
cout << get<2>(item).first << endl;
//访问tuple成员:
const int a = 0;
int b[3];
auto item2 = make_tuple(a,b);// tuple < int, int* >
cout << get<0>(item2) << endl;
//关系和相等运算符:
//逐个比较元素,元素的数量和类型必须一致。
tuple<size_t, size_t> item3_1(1,2);
tuple<size_t, size_t> item3_2(2,1);
cout << (item3_1 < item3_2) << endl;
/********************
*使用tuple返回多个值。
*********************/
int a2;
string b2;
//通过std::tie解包tuple。
tie(a2, b2) = foo();
cout << a2 << " " << b2 << endl;
system("pause");
return 0;
}
二、bitset类型
初始化bitset的方法:
#include <bits/stdc++.h>
using namespace std;
//非类型模板参数。
template <unsigned N>
class bits
{
public:
bits() = default;
bits(const string &s) : bit(s) { }
~bits() =default;
inline void updata(int s, int b)
{
bit.set(s, b);
}
//为了让实例成为友元,友元声明中必须使用与类模板本身不同的模板参数。
template<unsigned M>
friend size_t text(const bits<M> &lb, const bits<M> &rb);
//重载输出操作符。
template<unsigned M>
friend std::ostream &operator<<(std::ostream &os, const bits<M> &b);
private:
bitset<N> bit;
};
template<unsigned M>
size_t text(const bits<M> &lb, const bits<M> &rb)
{
auto temp = lb.bit ^ rb.bit;//使用亦或操作。
return temp.count();//计算同一位置上数字不同的有多少个。
}
template<unsigned M>
std::ostream &operator<<(std::ostream &os, const bits<M> &b)
{
os << b.bit;
return os;
}
int main(int argc, char const *argv[])
{
bitset<4> bit1(10);
//比初始值大则高位补零,小则高位被丢弃。
cout << bit1 << endl;
/***********************
*使用string初始化bitset。
************************/
//字符串下标最小的字符对应高位。
string str("11010011");
bitset<10> bit2_1(str);
bitset<10> bit2_2(str, 1, 3);//从str[1]开始的三个字符。
bitset<10> bit2_3(str, str.size()-2);//使用最后两个字符。
cout << bit2_1 << endl << bit2_2 << endl << bit2_3 << endl;
/***********
*bitset操作。
************/
int a = 100;
bitset<10> bit3(a);
cout << bit3 << endl;
//计算位数和置位的位数(1的个数)。
cout << bit3.size() << ", " << bit3.count() << endl;
bit3.set(0,1);//把最低位设为1。
cout << bit3 << endl;
cout << bit3.to_ulong() << endl;//提取bitset的值。
bits<10> bit4_1("1010101101");
bits<10> bit4_2("1110001111");
bit4_2.updata(0,0);//更新低位的值。
cout << text(bit4_1, bit4_2) << endl;
return 0;
}
bitset的应用:
(1)海量数据排序:
某区有7位电话号码若干(千万级别),要求对其排序,占用内存要求在1MB左右。大概算一下,8位电话号码也就是0000000-9999999,数值一共不到1千万吧,用unsigned即可。那么加载进内存越为4*10000000字节,约等于40MB,如果我们用排序只能用归并排序了,至少也40次了吧,先不说次数多少,就磁盘IO来说就慢的不行了。
此时可以考虑bitset了,既然电话号码是连续的且不重复的,我们可以用bitset的bit下标来表示电话号码,比如1234567,就是bit[1234567]。那么好办了,
首先计算一下bitset需要的大概40MB/32,也就是1MB左右,复合情况,我们可以直接遍历一遍,存在的号码S我们设置b[S]位为1,不存在则为0。那么输出排序直接遍历bitset即可。节省空间且速度快。
(2)海量数据找不重复数:
2.5亿个整数中找出不重复的整数的个数,内存空间不足以容纳这2.5亿个整数。这次用两个位表示一个数,00表示未出现过,01表示出现了1次,11表示出现重复了。遍历即可。
三、 正则表达式
正则表达式是一种描述字符序列的方法,是一种极其强大的计算工具。
正则表达式库组件:
1、使用正则表达式库
一个正则表达式的语法是否正确是在运行时解析的。
/*****************
*使用正则表达式库。
******************/
//[^]表示非。
string pattern1_1("[^c]ei");
//前后匹配任意大小写的零个或多个字母。
pattern1_1 = "[[:alpha:]]*" + pattern1_1 + "[[:alpha:]]*";
regex r1_1(pattern1_1);//创建用于查找模式的regex。
smatch result1_1;
string test_string1_1 = "receipt freid theif receive";
//寻找第一个与正则表达式匹配的子序列,找到就停止。
if(regex_search(test_string1_1, result1_1, r1_1))
cout << result1_1.str() << endl;
/*
*$表示锚定行尾,只匹配$之前的内容。
*.表示匹配其后的任意字符。
*\\.表示去掉特殊含义,
*regex::icase指定regex对象的选项,表示在匹配过程中忽略大小写。
*正则表达式类型必须与输入类型匹配。smatch与string匹配,cmatch与char匹配。
*/
try
{
regex r1_2("([[:alpha:]]*)(\\.)([[:alpha:]]*)", regex::icase);
smatch result1_2;
string test_string1_2 = "abc.cpp";
if(regex_search(test_string1_2, result1_2, r1_2))
cout << result1_2.str(3) << endl;//使用子表达式,打印第三个子表达式。
}
catch (regex_error e)
{
//what()描述错误类型,code()表示错误编码。
cout << e.what() << "\ncode: " << e.code() << endl;
}
2、匹配与regex迭代器类型
/**********************
*匹配与regex迭代器类型。
***********************/
string pattern2("[^c]ei");
pattern2 = "[[:alpha:]]*" + pattern2 + "[[:alpha:]]*";
regex r2(pattern2, regex::icase);//创建用于查找模式的regex。
string test_string2 = "receipt freid abcdef theif receive";
//sregex_iterator将迭代器定位到第一个匹配的位置。
for (sregex_iterator it2(test_string2.begin(), test_string2.end(), r2),
end_it; it2 != end_it; ++it2)
//打印出匹配单词的上下文。
{
auto pos = it2->prefix().length();//前缀的大小。
pos = pos > 10 ? pos - 6 : 0;//最多需要6个字符。
cout << it2->prefix().str().substr(pos) << //前缀的最后一部分。
"\n\t\t>>>" << it2->str() << " <<<\n" << //匹配的单词。
it2->suffix().str().substr(0,6) << endl; //后缀的第一部分。
}
3、使用子表达式
子表达式通常用于数据验证。
正则表达式的一些特性:
- \d{n}:表示一个n个数字的序列。
- [-. ]:表示字符集合匹配这些字符中任意一个。
- ?:表示组件是可选的。
- \(:双斜线去掉C++中的特殊字符。
- (\d{3}):每个字表达式用一对括号包围。
/*************
*使用子表达式。
**************/
string phone = "(\\()?(\\d{3})(\\))?([-. ])?(\\d{3})([-. ])?(\\d{4})";
regex r3(phone);
smatch result3;
string s3;
while (getline(cin, s3))
{
for (sregex_iterator it3(s3.begin(), s3.end(), r3),
end_it; it3 != end_it; ++it3)
{
if(valid(*it3))
cout << "valid: " << it3->str() << endl;
else
cout << "not valid: " << it3->str() << endl;
}
}
4、使用regex_replace
bool valid(const smatch &m)
{
//如果区号前有一个左括号。
if(m[1].matched)//则区号后必须有右括号,之后紧跟剩余号码或者一个空格。
return m[3].matched && (m[4].matched == 0 || m[4].str() == " ");
else//否则区号后不能有右括号,另外两个分隔符必须匹配。
return !m[3].matched && m[4].str() == m[6].str();
}
/******************
*使用regex_replace。
*******************/
string phone2 = "(\\()?(\\d{3})(\\))?([-. ])?(\\d{3})([-. ])?(\\d{4})";
regex r4(phone2);
string s4 = "(908) 555-1800 862.555.0123";
string fmt = "$2.$5.$7 ";
cout << regex_replace(s4, r4, fmt, std::regex_constants::format_no_copy) << endl;
5、本节demo:
#include <iostream>
#include <regex>
using namespace std;
bool valid(const smatch &m)
{
//如果区号前有一个左括号。
if(m[1].matched)//则区号后必须有右括号,之后紧跟剩余号码或者一个空格。
return m[3].matched && (m[4].matched == 0 || m[4].str() == " ");
else//否则区号后不能有右括号,另外两个分隔符必须匹配。
return !m[3].matched && m[4].str() == m[6].str();
}
int main(int argc, char const *argv[])
{
/*****************
*使用正则表达式库。
******************/
//[^]表示非。
string pattern1_1("[^c]ei");
//前后匹配任意大小写的零个或多个字母。
pattern1_1 = "[[:alpha:]]*" + pattern1_1 + "[[:alpha:]]*";
regex r1_1(pattern1_1);//创建用于查找模式的regex。
smatch result1_1;
string test_string1_1 = "receipt freid theif receive";
//寻找第一个与正则表达式匹配的子序列,找到就停止。
if(regex_search(test_string1_1, result1_1, r1_1))
cout << result1_1.str() << endl;
/*
*$表示锚定行尾,只匹配$之前的内容。
*.表示匹配其后的任意字符。
*\\.表示去掉特殊含义,
*regex::icase指定regex对象的选项,表示在匹配过程中忽略大小写。
*正则表达式类型必须与输入类型匹配。smatch与string匹配,cmatch与char匹配。
*/
try
{
regex r1_2("([[:alpha:]]*)(\\.)([[:alpha:]]*)", regex::icase);
smatch result1_2;
string test_string1_2 = "abc.cpp";
if(regex_search(test_string1_2, result1_2, r1_2))
cout << result1_2.str(3) << endl;//使用子表达式,打印第三个子表达式。
}
catch (regex_error e)
{
//what()描述错误类型,code()表示错误编码。
cout << e.what() << "\ncode: " << e.code() << endl;
}
/**********************
*匹配与regex迭代器类型。
***********************/
string pattern2("[^c]ei");
pattern2 = "[[:alpha:]]*" + pattern2 + "[[:alpha:]]*";
regex r2(pattern2, regex::icase);//创建用于查找模式的regex。
string test_string2 = "receipt freid abcdef theif receive";
//sregex_iterator将迭代器定位到第一个匹配的位置。
for (sregex_iterator it2(test_string2.begin(), test_string2.end(), r2),
end_it; it2 != end_it; ++it2)
//打印出匹配单词的上下文。
{
auto pos = it2->prefix().length();//前缀的大小。
pos = pos > 10 ? pos - 6 : 0;//最多需要6个字符。
cout << it2->prefix().str().substr(pos) << //前缀的最后一部分。
"\n\t\t>>>" << it2->str() << " <<<\n" << //匹配的单词。
it2->suffix().str().substr(0,6) << endl; //后缀的第一部分。
}
/*************
*使用子表达式。
**************/
string phone = "(\\()?(\\d{3})(\\))?([-. ])?(\\d{3})([-. ])?(\\d{4})";
regex r3(phone);
smatch result3;
string s3;
while (getline(cin, s3))
{
for (sregex_iterator it3(s3.begin(), s3.end(), r3),
end_it; it3 != end_it; ++it3)
{
if(valid(*it3))
cout << "valid: " << it3->str() << endl;
else
cout << "not valid: " << it3->str() << endl;
}
}
/******************
*使用regex_replace。
*******************/
string phone2 = "(\\()?(\\d{3})(\\))?([-. ])?(\\d{3})([-. ])?(\\d{4})";
regex r4(phone2);
string s4 = "(908) 555-1800 862.555.0123";
string fmt = "$2.$5.$7 ";
cout << regex_replace(s4, r4, fmt, std::regex_constants::format_no_copy) << endl;
system("pause");
return 0;
}
四、 随机数
1、随机数简介:
随机数库的组成:
引擎类型:生成随机unsigned整数序列
分布类型:使用引擎返回服从特定概率分布的随机数。
随机数引擎操作:
分布类型的操作:
#include <iostream>
#include <random>
using namespace std;
vector<unsigned> bad_randvec()
{
//将引擎和分布对象都定义为static,从而避免每次都生成相同的序列。
static default_random_engine e;
static uniform_int_distribution<unsigned> u(0, 9);
vector<unsigned> ret;
for (size_t i = 0; i < 10; ++i)
{
ret.push_back(u(e));
}
return ret;
}
int main(int argc, char const *argv[])
{
//引擎生成一个数值序列。
vector<unsigned> v1(bad_randvec());
vector<unsigned> v2(bad_randvec());
cout << ((v1 == v2) ? "equal" : "not equal" ) << endl;
//设置随机数发生器种子。
default_random_engine e2;
vector<string> vec2{"Hei Tao", "Hong Tao", "Mei Hua","Fang Kuai"};
uniform_int_distribution<int> u2(0, 3);
for (size_t i = 0; i < 5; ++i)
{
cout << vec2[u2(e2)] << endl;
}
//其他随机数分布。
default_random_engine e3;
normal_distribution<double> n(0, 1);//定义正态分布对象。
for (size_t i = 0; i < 10; ++i)
{
cout << n(e3) << " ";
}
cout << endl;
string resp;
default_random_engine e4;//引擎应该在循环外定义。
bernoulli_distribution b4(.55);//程序有55%的概率先行。
do
{
bool first = b4(e4);
cout << (first ? "we go first" : "you go first") << endl;
} while (cin >> resp && resp[0] == 'y');
system("pause");
return 0;
}
2、random_shuffle()方法:
产生指定范围内的随机元素集的最佳方法是创建一个顺序序列(也就是向量或者内置数组),在这个顺序序列中含有指定范围的所有值。填充完向量之后,用random_shuffle()算法打乱元素排列序。
random_shuffle()定义在标准的头文件中。因为所有的STL算法都是在名字空间std::中声明的,所以你要注意正确地声明数据类型。
random_shuffle()有两个参数,第一个参数是指向序列首元素的迭代器,第二个参数则指向序列最后一个元素的下一个位置。下列代码段用random_shuffle()算法打乱了先前填充到向量中的元素:
include <algorithm>
using std::random_shuffle;
random_shuffle(vi.begin(), vi.end()); /* 打乱元素 */
例子:
#include "stdafx.h"
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
vector<string> str;
str.push_back("hello");
str.push_back("world");
str.push_back("welcome");
str.push_back("to");
str.push_back("Beijing");
std::random_shuffle(str.begin(),str.end());//迭代器
for(int j = 0; j < str.size(); j++)
{
cout<<str[j].c_str()<<" ";
}
cout<<endl;
system("pause");
return 0;
}
五、IO库再探
操作符用于两大类输出控制:控制数值的输出形式以及控制补白的数量和位置。大多数改变格式状态的操纵符都是设置/复原成对的;一个操纵符用来将格式状态设置为一个新值,而另一个用来将其复原,恢复为正常的默认格式。
#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;
int main(int argc, char const *argv[])
{
/***************
*格式化输入输出。
****************/
//用boolalpha控制bool值的输出。
cout << true << " " << false << boolalpha << "\n" << true << " " << false << endl;
//控制整型值的输出:hex(16)、oct(8)、dec(2)
cout << showbase;//显示进制符。
cout << 20 << endl;
cout << hex << 20 << endl;
cout << oct << 20 << endl;
cout << dec << 20 << endl;
//控制精度、浮点数计数法、打印小数、输出补白(位宽)
cout << setprecision(3);//设置精度。
cout << "precision: " << cout.precision() << ", value: " << sqrt(2) << endl;
cout << scientific << 10 * sqrt(2) << endl;//科学计数法。
cout << defaultfloat << 10 * sqrt(2) << endl;//默认操纵符。
cout << setprecision(3);
cout << showpoint << sqrt(2) << endl;//强制显示小数点。
int i = 16;
double d = 3.1415926;
cout << "i: " << setw(12) << i << "next col" << "\n"
<< "d: " << setw(12) << d << "next col" << "\n";
cout << left;
cout << "i: " << setw(12) << i << "next col" << "\n"
<< "d: " << setw(12) << d << "next col" << "\n";
cout << right;
cout << "i: " << setw(12) << i << "next col" << "\n"
<< "d: " << setw(12) << d << "next col" << "\n";
cout << setfill('#');
cout << "i: " << setw(12) << i << "next col" << "\n"
<< "d: " << setw(12) << d << "next col" << "\n" << setfill(' ');//恢复正常的补白字符。
return 0;
}