C++标准库进阶3

十三、关联容器的等价与比较函数的返回值

两个对象是否相等是基于operator==。因为operator==可以自定义,所以,两个对象相等,并不意味着对象所有的数据成员都相等

而关联容器对相同元素的定义是等价,默认是基于operator<。比如有个map<string, int>对象,该对象key的排序方式默认就是operator<,a与b等价关系可以如下表示:

!(a<b) && !(b<a)

上述表达式的含义是:按照一定的排序规则,a,b中的任何一个都不在另一个前面,则二者等价

 

如果排序规则不是operator<,则两个元素等价的条件是

!keycomp(x,y) && !keycomp(y,x)

其中,keycomp表示key的排序规则

 

示例

class comp
{
public:
	bool operator()(const string &a, const string &b)
	{
        if(a.size()!=b.size()) {
            return true;
        }
        for (unsigned i=0;i<a.size();++i) {
            if (tolower(a[i])!=tolower(b[i])) {
                return true;
            }
        }

        return false;
	}
};

int main(int argc, char const *argv[])
{
	set<string, comp> s;
	pair<set<string, comp>::iterator, bool> res1=s.insert("abc");
	auto res2=s.insert("ABC");

	auto res3=s.insert("AB");
	cout<<res3.second<<endl;

	cout<<res1.second<<","<<res2.second<<endl;
	cout<<(s.find("ABC")!=s.end())<<endl;
	cout<<(find(s.begin(), s.end(), "ABC")!=s.end())<<endl;
	
	return 0;
}

通过输出结果可知,等价的判定规则生效,当字符串abc被insert被添加到容器中后,因为ABC和abc满足等价条件,为了保证set中所有的元素都是不等价的,所以,ABC没有被添加到容器中

但是又因为abc和ABC是等价的,所以s.find("ABC")!=s.end()的结果为true,说明查找成功

而标准库的find函数是基于相等来进行查找的,所以find(s.begin(), s.end(), "ABC")直接返回了尾后迭代器,没查找到ABC

 

上面的代码,ABC和abc都会让函数调用运算符返回false,从而满足!comp("ABC", "abc") && !comp("abc", "ABC")的表达式结果为true,进而判定二者等价,从而二者只有一个被添加到容器中,如果上述代码的第7,11行返回false,那么就会判定ABC和abc不等价,就会将ABC和abc都插入到容器中,违背了忽略大小写的实现原则

 

十四、让关联容器的比较规则在等值的情况下返回false。

如果关联容器的比较规则在等值的情况下返回true,就有可能使set中出现等值的元素,破坏set

示例

bool comp(int a, int b)
{
	return a<=b;
}

int main(int argc, char const *argv[])
{
	set<int, bool(*)(int, int)> s;
	s.insert(10);
	s.insert(10);
	return 0;
}

原因:当插入第二个10时,会根据比较规则判定这两个10是否等价,因为!(10<=10) && !(10>=10) 的表达式的结果为false,所以判定10和10不等价(相等的值但是却不等价),所以将第二个10插入到set中,所以set中有两个重复的10,出现了重复的key,破坏了set,所以出现段错误

解决办法就是让比较规则在等值的情况下返回false,进而满足等价的条件。此外,让比较规则在等值的情况下返回false是任何一个严格弱序的函数必须遵守的规则

 

上述两个原则说明,关联容器的排序规则是基本等价而不是相等,两个元素是否等价的判定条件完全依赖比较函数,可以自由实现,但是可以自由实现的前提是不能突破关联容器的原则

 

十五、当关联容器的key的类型为指针时,要指定比较函数

如果关联容器中的元素是指针,那么关联容器默认是按照指针的16进制的地址值的大小进行升序排列,而不是根据指针指向的对象的operator<进行排列

示例

int main(int argc, char const *argv[])
{
	set<string *> s;
	s.insert(new string("qwer"));
	s.insert(new string("1234"));
	s.insert(new string("abcd"));

	for (set<string *>::iterator it=s.begin();it!=s.end();++it) {
		cout<<*it<<endl;
		cout<<**it<<endl;
	}

	return 0;
}

可见上述输出地址是升序的,但是字符串完全乱序

解决办法就是重新指定比较规则

bool comp(string *ps1, string *ps2)
{
	assert(ps1!=nullptr && ps2!=nullptr);
	return *ps1 < *ps2;
}

int main(int argc, char const *argv[])
{
	set<string *, bool(*)(string *, string *)> s(comp);
	s.insert(new string("qwer"));
	s.insert(new string("1234"));
	s.insert(new string("abcd"));

	for (set<string *>::iterator it=s.begin();it!=s.end();++it) {
		cout<<*it<<endl;
		cout<<**it<<endl;
	}

	return 0;
}

可见,重新指定比较函数后,输出结果是按照字符串的大小升序输出

 

参考

《Effective STL》

 

欢迎大家评论交流,作者水平有限,如有错误,欢迎指出

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值