第十七章 标准库特殊设施
从1998年的第一版标准到2011年的最新标准,标准库部分的篇幅增加了两倍以上。
tuple类型
tuple是类似pair的模板
将一些数据组合成单一对象:“快速而随意”的数据结构
tuple<size_t, size_t, size_t> threeD; //三个成员都设置为0
//为每个成员提供初始值
tuple<string, vector, int, list>
someVal(“constants”, {3.14,2,718},42,{0,1,2,3,4,5});
//tuple的这个构造函数是explicit的
tuple<size_t,size_t,size_t> threeD = {1,2,3}; //错误
tuple<size_t,size_t,size_t> threeD{1,2,3}; //正确
//表示输掉交易记录的tuple,包含:ISBN、数量和每册书的价格
auto item = make_tuple(“0-999-78345-X”,3,20.00);
访问tuple的成员
auto book = get<0>(item); //返回item的第一个成员
auto cnt = get<1>(item); //返回item的第二个成员
auto price = get<2>(item)/cnt; //返回item的最后一个成员
get<2>(item) *= 0.8; //打折20%
typedef decltype(item) trans; //trans是item的类型
//返回trans类型对象中成员的数量
size_t sz = tuple_size::value; //返回3
//cnt的类型与item中第二个成员相同
tuple_element<1,trans>::type cnt = get<1>(item); //cnt是一个int
关系和相等运算符
tuple<string, string> duo(“1”,“2”);
tuple<size_t,size_t> twoD(1,2);
bool b = (duo == twoD); //错误:不能比较size_t和string
tuple<size_t, size_t, size_t> threeD(1,2,3);
b = (twoD<threeD); //错误:成员数量不同
tuple<size_t,size_t> origin(0,0);
b = (origin<twoD); //正确:b为true
使用tuple返回多个值
//files中的每个元素保存一家书店的销售记录
vector<vector<Sales_data>> files;
//一家书店的索引和两个指向书店vector中元素的迭代器
typedef tuple<vector<Sales_data>::size_type,
vector<Sales_data>::const_iterator,
vecotr<Sales_data>::cosnt_iterator> matches;
//files保存每家书店的销售记录
//findBook返回一个vector,每家销售了给定书籍的书店在其中都有一项
vector
findBook(const vector<vector<Sales_data>> &files,const string &book){
vector ret; //初始化为空vector
//对每家书店,查找与给定书籍匹配的记录范围(如果存在的话)
for(auto it = files.cbegin(); it != files.cend(); ++it){
//查找具有相同ISBN的Sales_data范围
auto found = equal_range(it->cbegin(),it->cend(),book,compareIsbn);
if(found.first != found.second) //此书店销售了给定书籍
//记住此书店的索引及匹配的范围
ret.push_back(make_tuple(it - files.cbegin(),
found.first,found.second));
}
return ret; //如果未找到匹配记录的话,ret为空
}
使用函数返回的tuple
void reportResults(istream &in, ostream &os,
const vector<vector<Sales_data>> &files)
{
string s; //要查找的书
while(in >> s){
auto trans = findBook(files,s); //销售了这本书的书店
if(trans.empty()){
cout<<s<<" not found in any stores"<<endl;
continue; //获得下一本要查找的书
}
for(const auto &store:trans) //对每家销售了该书籍的书店
//get返回store中tuple的指定的成员
os<<“store”<<get<0>(store)<<" sales: "
<<accumulate(get<1>(store),get<2>(store),Sales_data(s))
<<endl;
}
}
bitset类型
标准库定义了bitset类(类模板,需固定大小),使得位运算更容易
bitset<32> bitvec(1U); //32位;低位为1,其他位为0
使用一个整形值来初始化bitset时,将转换为unsigned long long,并被当做位模式来处理
unsigned值初始化bitset
//bitvec1比初始值小;初始值中的高位被丢弃
bitset<13> bitvec1(0xbeef); //二进制位序列为1111011101111
//bitvec2比初始值大;它的高位被置为0
bitset<20> bitvec2(0xbeef); //二进制位序列00001011111011101111
//在64位机器中,long long OULL是一个64个0比特,因此~0ULL是64个1
bitset<128> bitvect3(~0ULL); //063位为1;64127位为0
从一个string初始化bitset
bitset<32> bitvec4(“1100”); //2,3两位为1,剩余两位为0
string str(“1111111000000011001101”);
bitset<32> bitvec5(str,5,4); //从str[5]开始的四个二进制位,1100
bitset<32> bitvec6(str,str.size()-4); //使用最后四个字符
bitset操作
bitset<32> bitvec(1U); //32位;低位为1,剩余位为0
bool is_set = bitvec.any(); //true,因为有1位置位
bool is_not_set = bitvec.none(); //false,因为有1位置位了
bool all_set = bitvec.all(); //false,因为只有1位置位
size_t onBits = bitvec.count(); //返回1
size_t sz = bitvec.size(); //返回32
bitvec.flip(); //翻转bitvec中的所有位
bitvec.reset(); //将所有位复位
bitvec.set(); //将所有位置位
bitvec[0] = 0; //将第一位复位
bitvec[31] = bitvec[0]; //将最后一位设置为与第一位一样
bitvec[0].flip(); //翻转第一位
~bitvec[0]; //等价操作,也是翻转第一位
bool b = bitvec[0]; //将bitvec[0]的值转换为bool类型
下标运算符对const属性进行了重载。
• const的版本返回true或false
• 非const版本,允许指定位的值
提取bitset值
unsinged long ulong = bitvec3.to_ulong();
cout<<“ulong =” << ulong <<endl;
//bitset中的值的大小,不能大于转换的类型
//否则会抛出overflow_error异常
bitset的IO运算符
bitset<16> bits;
cin >> bits; //从cin读取最多16个0或1
cout << "bits: "<< bits <<endl; //打印刚刚读取的内容
使用bitset
bool status;
//使用位运算符的版本
unsigned long quizA = 0; //此值被当做位集合使用
quizA |= 1UL << 27; //指出第27个学生通过了测试
status = quitzA & (1UL <<27); //检查第27个学生是否通过了测试
quizA &= ~(1UL << 27); //第27个学生未通过测试
//使用标准库类bitset完成等价工资
bitset<30> quizB; //每个学生分配一位,所有位都被初始化为0
quizB.set(27); //指出第27个学生通过了测试
status = quizB[27]; //检查第27个学生是否通过了测试
quizB.reset(27); //第27个学生未通过测试
正则表达式
正则表达式(regular expression)是一种描述字符序列的方法,是一种极其强大的计算工具。但是,用于定义正则表达式的描述语言已经大大超出了本书的范围。因此,我们重点介绍如何使用C++正则表达式库(RE库),它是新标准库的一部分。RE库定义在头文件regex中,它包含多个组件。
描述字符序列的方法
//查找不在字符c之后的字符串ei
string pattern(“[^c]ei”);
//我们需要包含pattern的整个单词
pattern =“[[:alpha:]]" + pattern + "[[:alpha:]]”;
regex r(pattern); //构造一个用于查找模式的regex
smatch results; //定义一个对象保存搜索结果
string test_str=“receipt freind theif receive”;
//用r在test_str中查找与pattern匹配的子串
if(regex_search(test_str,results,r))//如果有匹配子串
cout<<results.str()<<endl; //打印匹配的单词
• regex:表示有一个正则表达式的类
• smatch:容器类,保存在string中搜索的结果
• regex_search:寻找第一个与正则表达式匹配的子序列
指定regex对象的选项:指定一些标志来影响regex如何操作
//识别扩展名
//一个或多个字母或数字字符后接一个’.'再接"cpp"或"cxx"或"cc"
regex r(“[[:alnum:]]+\.(cpp|cxx|cc)$”,regex::icase);//,regex::icase在匹配中忽略大小写
smatch results;
string filename;
while(cin>>filename)
if(regex_search(filename,results,r))
cout<<results.str()<<endl;//打印匹配结构
正则表达式的语法是否正确是在运行时解析的
try{
//错误:alnum漏掉了右括号,构造函数会抛出异常
regex r(“[[:alnum:]+\.(cpp|cxx|cc)$”
,regex::icase);
}catch(regex_error e)
{ cout<<e.what()<<“\ncode:”<<e.code()<<endl; }
正则表达式类和输入序列类型
regex r(“[[:alnum:]]+\.(cpp|cxx|cc)$”
,regex::icase);
smatch results; //将匹配string输入序列,而不是char*
//cmatch results; //将匹配字符数组输入序列
if( regex_search(“myfile.cc”
,results,r)) //错误:输入为char*
cout<<results.str()<<endl;
使用sregex_iterator来获得所有匹配
string file(“Hello cei world freind meizhu”);
//查找前一个字符不是c的字符串ei
string pattern(“[^c]ei”);
pattern =
“[[:alpha:]]" + pattern + "[[:alpha:]]”;
regex r(pattern,regex::icase);
//它将反复调用regex_search来虚招文件中的所有匹配
for(sregex_iterator it(file.begin(),file.end(), r),end_it; it != end_it; ++it)
cout<< it->str() <<endl; //匹配的单词
显示匹配单词出现的上下文
//循环头与之前一样
for(sregex_iterator it(file.begin(), file.end(), r), end_it;it != end_it; ++it) {
auto pos = it->prefix().length(); //前缀的大小
pos = pos > 40 ? pos - 40 : 0; //我们想要最多40个字符
cout << it->prefix().str().substr(pos) //浅醉的最后一部分
<< “\n\t\t>>>” << it->str() << " <<<\n" //匹配的单词
<< it->suffix().str().substr(0,40) //后缀的第一部分
<< endl;
}
使用子匹配操作
//两个子表达式:1、点之前表示文件名的部分,2、表示文件扩展名
regex r(“([[:alnum:]]+)\.(cpp|cxx|cc)$”
,regex::icase);
smatch results;
string filename;
while(cin>>filename)
if(regex_search(filename,results,r))
cout<<results.str(1)<<endl; //打印第一个子表达式
子表达式用于数据验证
string phone =
“(\()?(\d{3})(\))?([-. ])?(\d{3})([-. ])?(\d{4})”;
regex r(phone); //regex对象,用于查找我们的模式
smatch m;
string s;
//从输入文件中读取每条记录
while(getline(cin,s)) {
//对每个匹配的电话号码
for(sregex_iterator it(s.begin(), s.end(), r), end_it; it != end_it; ++it)
//检查号码的格式是否合法
if(valid(*it))
cout<<"valid: "<< it->str() << endl;
else
cout<<"not valid: "<< it->str() << endl;
}
使用regex_replace将找到的序列替换为另一个序列
using namespace regex_constants;
int main(){
string phone =
“(\()?(\d{3})(\))?([-. ])?(\d{3})([-. ])?(\d{4})”;
regex r(phone); //regex对象,用于查找我们的模式
string fmt =
“$2.$5.$7”; //将号码格式改为ddd.ddd.dddd
string fmt2 =
“$2. $5. $7”;
string number =
“(908) 555-1800”;
cout << regex_replace(number, r, fmt) << endl;
cout << regex_replace(number, r, fmt2, format_no_copy) << endl;
number =
“(08) 555-1800”;
cout << regex_replace(number, r, fmt) << endl;
cout << regex_replace(number, r, fmt2, format_no_copy) << endl;
}
随机数
#include
#include
using namespace std;
int main()
{
default_random_engine e; //生成随机无符号数
for(size_t i = 0; i<5; ++i)
cout<<e()<<" “;
cout<<endl;
default_random_engine e2;
for(size_t i = 0; i<5; ++i)
cout<<e2()<<” ";
}
平均分布
//生成0到9之间(包含)均匀分布的随机数
uniform_int_distribution u(0,9);
default_random_engine e; //生成无符号随机整数
for(size_t i=0; i<10; ++i)
//将u作为随机数源
//每个调用返回执行范围内并服从均匀分布的值
cout<<u(e)<<" ";
引擎生成一个数值序列
//每次调用这个函数都会生成相同的100个数!
vector bad_randVec(){
default_random_engine e;
uniform_int_distribution u(0,9);
vector ret;
for(size_t i=0;i<100;++i)
ret.push_back(u(e));
return ret;
}
int main(){
vector v1(bad_randVec());
vector v2(bad_randVec());
//将打印“equal”
cout<<((v1 == v2) ? “equal”:“not equal”)<<endl;
}
设置随机数发生器种子
default_random_engine e1; //使用默认种子
default_random_engine e2(3147442); //使用给定的种子
default_random_engine e3; //使用默认种子
e3.seed(32767); //调用seed设置一个新种子
default_random_engine e4(32767); //将种子设置为32767
for(size_t i=0; i !=100; ++i){
if(e1() == e2())
cout<<“unseeded match at literation:”<<i<<endl;
if(e3()!= e4())
cout<<“seeded differs at literation:”<<i<<endl;
}
以时间为种子:只适用于间隔为秒级或更长的应用
default_random_engine e(time(0));
生成随机实数
default_random_engine e; //生成无符号随机整数
//0到1(包含)的均匀分布
uniform_real_distribution u(0,1);
for(size_t i = 0; i<10; ++i)
cout<<u(e)<<" ";
使用分布的默认结果类型
//空<>表示我们希望使用默认结果类型
uniform_real_distribution<> u(0,1); //默认生成double值
生成非均匀分布的随机数
//空<>表示我们希望使用默认结果类型
uniform_real_distribution<> u(0,1); //默认生成double值
生成非均匀分布的随机数
default_random_engine e; //生成随机整数
normal_distribution<> n(4,1.5); //均值4,标准差1.5
vector vals(9); //9个元素均为0
for(size_t i = 0; i != 200; ++i){
unsigned v = lround(n(e)); //舍入到最接近的整数
if(v<vals.size()) //如果结果在范围内
++vals[v]; //统计每个数出现了多少次
}
for(size_t j=0; j != vals.size(); ++j)
cout<<j<<": "<< string(vals[j],‘*’)<<endl;
string resp;
default_random_engine e; //e应保持状态,所以必须在循环外定义
bernoulli_distribution b; //默认是50/50的机会
do{
bool first = b(e); //如果未true,则程序先行
cout<<(first?“We go first”:“You get to go first”)<<endl;
//传递谁先行的指示,进行游戏
cout<<((play(first))?“sorry, you lost”:“congrats, you won”)<<endl;
cout<<“play again?Enter ‘yes’ or ‘no’”<<endl;
}while(cin>>resp&&resp[0] ==
‘y’);
IO库再探
通过操纵符改变格式状态
cout << "default bool values: " << true << " " << false
<< "\nalpha bool values: " << boolalpha
<< true << " " << false << endl;
bool bool_val = false;
cout << bool_val
<< noboolalpha; // 恢复默认状态
cout << endl;
cout << "default: " << 20 << " " << 1024 << endl;
cout << "octal: " << oct << 20 << " " << 1024 << endl;
cout << "hex: " << hex << 20 << " " << 1024 << endl;
cout << "decimal: " << dec << 20 << " " << 1024 << endl;
cout << showbase; // show the base when printing integral values
cout << "default: " << 20 << " " << 1024 << endl;
cout << "in octal: " << oct << 20 << " " << 1024 << endl;
cout << "in hex: " << hex << 20 << " " << 1024 << endl;
cout << "in decimal: " << dec << 20 << " " << 1024 << endl;
cout << noshowbase; // reset the state of the stream
cout << uppercase << showbase << hex
<< "printed in hexadecimal: " << 20 << " " << 1024
<< nouppercase << noshowbase << dec << endl;
double pi = 3.14;
cout << pi << " " << hexfloat << pi
<< defaultfloat << " " << pi << endl;
指定打印精度
//cout.precision返回当前精度值
cout<<"Precision: “<< cout.precision()
<<”, Value: "<<sqrt(2.0)<<endl;
//cout.precision(12)将打印精度设置为12位数字
cout.precision(12);
cout<<"Precision: “<< cout.precision()
<<”, Value: "<<sqrt(2.0)<<endl;
//另一种设置精度的方法是使用setprecision操纵符
cout<<setprecision(3);
cout<<"Precision: “<< cout.precision()
<<”, Value: "<<sqrt(2.0)<<endl;
指定浮点数计数法
cout << "default format: " << 100 * sqrt(2.0) << ‘\n’
<< "scientific: " << scientific << 100 * sqrt(2.0) << ‘\n’
<< "fixed decimal: " << fixed << 100 * sqrt(2.0) << ‘\n’
<< "hexadecimal: " << hexfloat << 100 * sqrt(2.0) << ‘\n’
<< "use defaults: " << defaultfloat << 100 * sqrt(2.0)
<< “\n\n”;
cout << uppercase
<< "scientific: " << scientific << sqrt(2.0) << ‘\n’
<< "fixed decimal: " << fixed << sqrt(2.0) << ‘\n’
<< "hexadecimal: " << hexfloat << sqrt(2.0) << “\n\n”
<< nouppercase;
输出补白
int i = -16;
double d = 3.14159;
// pad the first column to use a minimum of 12 positions in the output
cout << "i: " << setw(12) << i << “next col” << ‘\n’
<< "d: " << setw(12) << d << “next col” << ‘\n’;
// pad the first column and left-justify all columns
cout << left
<< "i: " << setw(12) << i << “next col” << ‘\n’
<< "d: " << setw(12) << d << “next col” << ‘\n’
<< right; // restore normal justification
// pad the first column and right-justify all columns
cout << right
<< "i: " << setw(12) << i << “next col” << ‘\n’
<< "d: " << setw(12) << d << “next col” << ‘\n’;
// pad the first column but put the padding internal to the field
cout << internal
<< "i: " << setw(12) << i << “next col” << ‘\n’
<< "d: " << setw(12) << d << “next col” << ‘\n’;
// pad the first column, using # as the pad character
cout << setfill(‘#’)
<< "i: " << setw(12) << i << “next col” << ‘\n’
<< "d: " << setw(12) << d << “next col” << ‘\n’
<< setfill(’ '); // restore the normal pad character
控制输入格式
char ch;
cin>>noskipws; //设置cin读取空白符
while (cin >> ch)
cout << ch;
cout << endl;
cin>>skipws; //将cin恢复到默认状态
使用未格式化IO操作get和put来读取和写入一个字符
int ch; // use an int, not a char to hold the return from get()
// loop to read and write all the data in the input
while ((ch = cin.get()) != EOF)
cout.put(ch);
cout << endl;
将一个流当作一个无解释的字节序列来处理
流随机访问
// open for input and output and preposition file pointers to end-of-file
// file mode argument
fstream inOut(“data/copyOut”
,
fstream::ate | fstream::in | fstream::out);
if (!inOut) {
cerr << “Unable to open file!” << endl;
return EXIT_FAILURE; // EXIT_FAILURE
}
// inOut is opened in ate mode, so it starts out positioned at the end
auto end_mark = inOut.tellg();// remember original end-of-file position
inOut.seekg(0, fstream::beg); // reposition to the start of the file
size_t cnt = 0; // accumulator for the byte count
string line; // hold each line of input
// while we haven’t hit an error and are still reading the original data
while (inOut && inOut.tellg() != end_mark
&& getline(inOut, line)) { // and can get another line of input
cnt += line.size() + 1; // add 1 to account for the newline
auto mark = inOut.tellg(); // remember the read position
inOut.seekp(0, fstream::end); // set the write marker to the end
inOut << cnt; // write the accumulated length
// print a separator if this is not the last line
if (mark != end_mark) inOut << " ";
inOut.seekg(mark); // restore the read position
}
inOut.seekp(0, fstream::end); // seek to the end
inOut << “\n”; // write a newline at end-of-file