C++Primer 第五版 4.高级主题

tuple是类似pair的模板,pair的成员类型都不相同,但是每个pair都恰好有两个成员。不同tuple类型的成员类型也不相同,但是一个tuple可以有任意数量的成员。

当我们希望将一些数据组合成单一对象,但是又不想麻烦定义一个新数据结构来表示这些数据时,tuple是非常有用的。头文件是tuple。


创建一个tuple对象时,可以使用tuple的默认构造函数,但是这个构造函数是explicit的,也就是说必须使用直接初始化语法(这点和智能指针一样)。类似于make_pair,标准库也定义了make_tuple函数用来生成tuple对象。


因为pair总有两个成员,所以标准库为它们命名(first和second),但是这种命名方式对tuple是不可能的。要访问tuple成员,要使用一个名为get的标准库函数模板。模板形参中指定我们需要访问的第几个成员,返回指定成员的引用(可以修改)


此外tuple_size<typleType>::value返回tuple成员的数量。如果不知道typleType可以用decltype来确定。


tuple的关系和相等运算符操作类似于容器,但是只有两个tuple具有相同数量的成员时才可以比较。

#include <iostream>
#include <tuple>
#include <string>

using namespace std;

int main()
{
	tuple<int, int> a{ 1, 2 };
	tuple<int, string> b = make_tuple(10, "sdf");
	//输出:1 sdf
	cout << get<0>(a) << endl;
	cout << get<1>(b) << endl;
	typedef decltype(a) trans;
	//输出:2
	cout << tuple_size<trans>::value << endl;
	return 0;
}

tuple的一个常见用途是从一个函数返回多个值。

#include <iostream>
#include <tuple>

using namespace std;

tuple<int, int> AAA(int c)
{
	return make_tuple(c, c);
}

int main()
{
	//输出:10 10 
	cout << get<0>(AAA(10)) << endl << get<1>(AAA(10)) << endl;
	return 0;
}


bitset可以处理一些二进制位集合的问题。当我们定义一个bitset时,需要声明它包含多少个二进制位。

构造函数有一个需要注意:

bitset<n> b(s,pos ,m,zero,one):b是string s从位置pos(是string的位置)开始m个字符的拷贝。s只能包含字符zero或者one。


此外bitset最右边是低位,最左边是高位。

string的下标习惯与bitset恰好相反:string 中下标最大的字符(最右字符)用来初始化bitset中的低位(下标为0的二进制位),要特别注意这个差别。


bitset有几个成员函数值得注意一下:

1.b.set(pos,v):默认为true

2.b.reset()

3.b.to_ulong()

4.b.to_string()

5.is >> b,os << b


关于正则表达式的介绍与使用,可参看这篇文章。头文件:regex。


正则表达式组件有:

1.regex:表示有一个正则表达式的类。

2.regex_match:将一个字符序列与一个正则表达式匹配。返回bool。

3.regex_search:寻找第一个与正则表达式匹配的子序列。返回bool。

4.regex_replace:使用给定格式替换一个正则表达式。

5.sregex_iterator:迭代器适配器,调用regex_search来遍历一个string中所有匹配的子串。

6.smatch:容器类,保存在string中搜索的结果。

7.ssub_match:string中匹配的字表达式的结果。 


其中,regex_match和regex_search的参数为:

(seq,m,r):seq为待查找的字符序列,m是一个match对象,用来保存匹配结果。r为一个正则表达式。此外在定义正则表达式r的时候可以指定忽略大小写,regex::icase。


如果有一个拼写规则,“i除非在c之后,否则必须在e之前”的单词。

#include <iostream>
#include <regex>
#include <string>

using namespace std;

int main()
{
	string pattern("[^c]ei");
	pattern = "[a-zA-Z]*" + pattern + "[a-zA-Z]*"; //如果是字符加数字可以用\\w*
	regex r(pattern, regex::icase);
	smatch results;
	string test_str = "receipt friend theif receive";
	if (regex_search(test_str, results, r))
		cout << results.str() << endl;  //输出friend regex_search输入序列是找到一个匹配字符子串变停止查找

	return 0;
}


正则表达式存在错误的话会抛出一个regex_error的异常。有一个what()函数标目发生了什么错误。


sregex_iterator的操作要注意下面两个:

1.sregex_iterator it(b,e,r):一个sregex_iterator,遍历迭代器b和e表示的string。它调用sregex_search(b,e,r)将it定位到第一个匹配的位置。

2.sregex_iterator end:sregex_iterator的尾后迭代器。


#include <iostream>
#include <regex>
#include <string>

using namespace std;

int main()
{
	string pattern("[^c]ei");
	pattern = pattern = "[a-zA-Z]*" + pattern + "[a-zA-Z]*";//如果是字符加数字可以用\\w*
	regex r(pattern,regex::icase);

	smatch results;
	string test_str;
	while (cin >> test_str)
	{
		for (sregex_iterator it(test_str.begin(), test_str.end(), r), end_it;
			it != end_it; ++it)
		{
			cout << it->str() << endl;
		}
	}
	
	return 0;
}


此外可以获得匹配内容的上下文,分别用prefix和suffix两个函数。

#include <iostream>
#include <regex>
#include <string>

using namespace std;

int main()
{
	string pattern("[^c]ei");
	pattern = pattern = "[a-zA-Z]*" + pattern + "[a-zA-Z]*";//如果是字符加数字可以用\\w*
	regex r(pattern,regex::icase);

	smatch results;
	string test_str;
	while (cin >> test_str)
	{
		for (sregex_iterator it(test_str.begin(), test_str.end(), r), end_it;
			it != end_it; ++it)
		{
			auto pos = it->prefix().length();
			pos = pos > 40 ? pos - 40 : 0;
			cout << it->prefix().str().substr(pos);
			cout << it->str();
			cout << it->suffix().str().substr(0, 40) << endl;
		}
	}
	
	return 0;
}

子表达式可以用小括号。如调用str(1),则是与第一个子表达式匹配的部分。


如果要通配一个字符可以用a.?b,如果要通配多个字符可以用a.*b。反正.是代表一个除换行符以外的任意字符。*代表重复零次或更多次,?代表重复0次或者1次。


如果你想查找元字符本身的话,比如你查找.,或者*,就出现了问题:你没办法指定它们,因为它们会被解释成别的意思。这时你就得使用\来取消这些字符的特殊意义。因此,你应该使用\.\*。但是,要查找\本身,你得用\\,所以说要用两个


在新标准出现之前,C和C++都是依赖于一个简单的C库函数rand来生成随机数。此函数生成均匀分布的伪随机整数。C++11新标准引入了一个随机数库,包括随机数引擎和随机数分布类。头文件random。

引擎:生成随机unsigned整数序列。

分布:使用引擎返回服从特定概率分布的随机数。

C++程序不应该使用库函数rand,而应使用default_randow_engine类和恰当的分布对象。


随机数引擎是函数对象类,定义了一个调用运算符,该运算符不接受参数并返回一个随机unsigned整数。

#include <iostream>
#include <random>

using namespace std;

int main()
{
	default_random_engine e;
	for (size_t i = 0; i < 10; ++i)
	{
		cout << e() << endl;
	}
	return 0;
}

但是对于大多数场合,随机数引擎的输出是不能直接使用的,这些随机数被称为原始随机数。而为了正确转换随机数范围,我们就应该使用一个分布类型的对象。

这里只介绍均匀分布:uniform_int_distribution<IntT> u(m,n)  uniform_real_distribution<RealT> u(x,y)

一个是生成整数,一个是生成小数。


#include <iostream>
#include <random>

using namespace std;

int main()
{
	uniform_int_distribution<unsigned> u(0, 9);
	default_random_engine e;
	for (size_t i = 0; i < 10; ++i)
	{
		cout << u(e) <<  " ";
	}
	cout << endl;
	return 0;
}

我们说的随机数发生器,是指分布对象和引擎对象的组合。


但是问题是每次运行程序它都会返回相同的数值序列,如果希望每次函数调用(程序没结束)生成不同的序列,将引擎对象和分布对象都定义为static即可。

#include <iostream>
#include <random>
#include <vector>

using namespace std;

vector<unsigned> bad_randVec()
{
	static uniform_int_distribution<unsigned> u(0, 9);
	static default_random_engine e;
	vector<unsigned> ret;
	for (size_t i = 0; i < 10; ++i)
	{
		ret.push_back(u(e));
	}
	return ret;
}
int main()
{
	vector<unsigned> v1(bad_randVec());
	vector<unsigned> v2(bad_randVec());
	cout << ((v1 == v2) ? 1 : 0);
	return 0;
}


如果我们希望 每次程序的运行都产生不同的随机结果,可以通过提供一个 种子来达到这一目的。种子就是一个数值,引擎可以利用它从序列中的一个新位置重新开始生成随机数。


一个常用的方法是调用系统函数time来接受种子。这样每次就能产生不同的10个数了。

#include <iostream>
#include <random>
#include <ctime>
using namespace std;

int main()
{
	uniform_int_distribution<unsigned> u(0, 9);
	default_random_engine e(unsigned(time(0)));
	for (size_t i = 0; i < 10; ++i)
	{
		cout << u(e) <<  " ";
	}
	cout << endl;
	return 0;
}


当操纵符改变流的格式状态时,通常改变后的状态对所有后续IO都生效。


如需要输出"true"或者"false"可以用boolalpha,如果取消这种状态可以用noboolalpha。

默认情况下,整型的输出都是十进制,可以使用hex,oct和dec将其改为十六进制,八进制或者是改回十进制。而且默认情况下,没有线索显示是几进制。如果需要显示进制,可以使用showbase操作符。取消这种状态可以用noshowbase。16进制的x默认是小写,如果需要改成大写则使用uppercase操作符。恢复还是在前面加no。

(注意,hex,oct和dec只影响整型运算对象,浮点数的表示形式不受影响)

#include <iostream>

using namespace std;

int main()
{
	cout << showbase << uppercase;
	cout << "dec: " << 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 << nouppercase;

	return 0;
}

对于浮点数,设置小数的精度可以采用setprecision()。这是总长度的精度。如果需要定位小数点后的精度,可以添加语句setf(ios::fixed)。

#include <iostream>

using namespace std;

int main()
{
	double c = 10;
	cout.precision(3);
	cout.setf(ios::fixed);
	cout << c << endl;
	return 0;
}

默认情况下,输入运算符会忽略空白符,如果需要读取空白符可以使用noskipws操作符。


各种流通常支持对流中数据的随机访问,我们可以重定位流,使之跳过一些数据。标准库提供了定位(seek)和返回当前位置的(tell)函数。

由于istream和ostream通常不支持随机访问,所以这两个函数只适用于fstream和sstream类型。


异常处理机制允许程序中独立开发的部分能够在运行时就出现的问题进行通信并做出相应的处理。


当抛出一个异常之后,程序暂停当前函数的执行过程并立即开始寻找与异常匹配的catch子句。当throw出现在一个try语句块内时,检查与该try块关联的catch子句。如果找到了匹配的catch,就使用该 catch处理异常。如果没找到而且该语句嵌套在其他try块中,则继续检查与外层try匹配的catch子句。如果还是找不到匹配的catch,则退出当前函数,在调用当前函数的外层函数中继续寻找。这就是栈展开的过程。最后如果没找到匹配的catch子句,程序将调用标准库函数terminate并退出。


如果在多个catch语句的类型之间存在着继承关系,则我们应该把继承链最底端的类放在前面(派生类),而把继承链最顶端的类(基类)放在后面。


为了一次性捕获所有的宜昌,可以使用catch(…)语句。这条语句可以与任意类型的异常匹配。


通常情况下,程序执行的任何时候都有可能发生宜昌,特别是异常可能发生在处理构造函数初始值的过程中。构造函数在进入其函数体之前首先执行初始值列表。如果想要处理构造函数初始值抛出的异常,必须将构造函数写成try语块的形式。

#include <iostream>
#include <stdexcept>

using namespace std;

class AA
{
private:
	int m_c;
public:
	AA(int c = 0) try :m_c(c)
	{

	}
	catch (const bad_alloc &e)
	{
		
	}
};

在C++11新标准中,我们可以通过提供noexcept说明指定某个函数不会抛出异常。

noexcept说明符需要跟在const和引用限定符之后,final,override或者虚函数 = 0的前面。


在C++11新标准中,允许派生类从它的一个或几个基类中继承构造函数。但是如果从多个基类中继承相同的构造函数,则程序会产生错误解决方法是这个类必须为该构造函数定义自己的版本。

struct Base1
{
	Base1() = default;
	Base1(const string &);
};

struct Base2
{
	Base2() = default;
	Base2(const string &);
};

struct D1 :public Base1, public Base2
{
	using Base1::Base1;
	using Base2::Base2;

	D1(const string &s) :Base1(s), Base2(s){}
	D1() = default;
};

虚继承的目的是令某个类做出声明,愿意共享它的基类。其中共享的子类称为虚基类。在这种机制下,无论虚基类在继承体系中出现多少次,在派生类中都只包含唯一一个共享的虚基类子对象。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值