说明:
首先,我明白茴香豆写法是大神们十分厌恶的学习方式,但是本人作为一个cpp的初学者,我认为身为一个初学者掌握几种茴香豆写法对于快速掌握标准库用法很有作用,所以请各位不喜勿喷~
缘起:
我的学习习惯是,一边听课,一边写代码,然后偶尔在老师讲完函数需求时我会先主动地写一写,然后测试好在继续看老师的项目实现。今天学习的内容是一个关于游戏类项目的实现,其目的就是联系类的运算符重载,这本身是一个很trivial的练习,直到老师讲到一个关于如何在一个vector容器中删掉一个对象的函数,我在此停下于是有了我的第一个实现方法(这个方法好像也是以前从哪里听说到的)。
下面是具体实现:
?首先我拿一个简单的类作为定义况且叫它 Object 吧:
我就只给他两个成员变量,一个是名字,另外一个是properties,构造函数直接实现了吧,其头文件Object.h如下:
#include<string>
class Object{
public:
explicit Object(const std::string& name, int property): name_(name), property_(property);
private:
std::string name_;
int property_;
}
然后我们假设我们有一个vector类在我们的main.cpp函数中,搞点dirty的,直接创建添加一个vector,然后尝试在里面去掉Bob。
开始循环并判断是否vector里面的内容和目标删除对象一致,然后开始我们的茴香豆写法去删除这个对象
?#include<iostream>
?#include<vector>
?#include"Object.h"
??
?int main(){
? ? ?Object target{"Bob", 10};
? ? ?vector<Object> vlist;
? ? ?vlist.emplace_back("Bob", 10);
? vlist.emplace_back("Joy", 12);
? vlist.emplace_back("Roy", 16);
? ? ?for(auto it=vlist.begin(); it!=vlist.end(); ++it){
? ? ? ? ?if(target == *it){
? ? ? ? ? ? ?//...
? ? ? ? }
? ? }
?}
创建好了对象以后我们要开始循环并判断是否二者相等,而这个时候我们会出现未定义的问题,那么我们要在Object中定义这个符号,定义一个友元吧,这样简单点。
?#include<string>
??
?class Object{
?private:
? ? ?std::string name_;
? ? ?int property_;
? ? ?//declaration for friend
? ? ?friend bool operator ==(const Object& one, const Object& another);
?}
?//declaration for function
?bool operator ==(const Object& one, const Object& another);
相应地源文件也进行实现(直接搞个一模一样)
?#include"Object.h"
??
?bool operator ==(const Object& one, const Object& another){
? ? ?return(one.name_ == another.name_)&&(one.property == another.property);
?}
所有的必要条件都创建好了,现在开始茴香豆,首先是我的想法
我的想法很简单,就是遍历所有的vector中的内容,找到一样的对象后,把这个对象和最后一个对象直接swap调换一下,然后给他pop_back()
这个想法我觉得还挺妙的,也不记得是在哪里学到的了,感谢互联网。
这里面用了几个特别的函数
首先是std::swap()
函数,这个函数在<algothrim>
中,用于交换两个对象的值。使用于所有类型的对象,只要这些对象可以被移动或复制。
std::prev(iterator, count)
是一个迭代器辅助函数,它返回指向给定迭代器之前元素的迭代器。这对于随机访问迭代器(如 std::vector
或 std::array
的迭代器)特别有用。默认条件下这个count就是1。按照我们的想法我们要把找到的对象和最后一个对象交换,但是我们知道我们如果用.end()
返回的其实是最后一个迭代器的后面一位,因此我们就做一个向前偏移量就可以访问到真正的最后一个元素了。还有一点就是我们在交换之前我们要判断一下是不是到最后了,如果到了就没必要自己跟自己交换一下了。
然后就是我的实现:
#include <algorithm> // this is for std::swap()
#include<iostream>
#include<vector>
#include"Object.h"
int main(){
? ? ?Object target{"Bob", 10};
? ? ?vector<Object> vlist;
? ? ?vlist.emplace_back("Bob", 10);
? vlist.emplace_back("Joy", 12);
? vlist.emplace_back("Roy", 16);
? ? ?//method no.1
? for(auto it=vlist.begin(); it!=vlist.end(); ++it){
? ? if(target == *it){
? ? ? ? // Swap with the last valid element, not past-the-end
? ? ? ? if(it != std::prev(vlist.end())) {
? ? ? ? ? ? std::swap(*it, vlist.back());
? ? ? ? }
? ? ? ? vlist.pop_back();
? ? ? ? ? ? ?break;
? ? }
? }
?}
Rock老师的实现如下:
想法其实是一样的只不过Rock老师调用了一个方法erase
这个erase()
方法属于vector内部,他需要传入一个迭代器对象,然后他会返回这个迭代器的下一个位置。
这里面有一个坑,如果erase(it)
被执行,这会造成 it迭代器被销毁,形成一个野指针,这是你在for循环中使用类似++it的操作就会造成未定义的操作,因此一定要在操作之后更新这个it,将他的返回值接住。
看Rock老师的实现吧:
#include<iostream>
#include<vector>
#include"Object.h"
?
int main(){
? ? ?Object target{"Bob", 10};
? ? ?vector<Object> vlist;
? ? ?vlist.emplace_back("Bob", 10);
? vlist.emplace_back("Joy", 12);
? vlist.emplace_back("Roy", 16);
? ? ?//method no.2
? ? ?//put the ++it inside the loop
? for(auto it=vlist.begin(); it!=vlist.end();){
? ? if(target == *it){
? ? ? ? ? ? ?//capture the return value from erase() and keep loop work
? ? ? ? it = vlist.erase(it);
? ? ? ? ? ? ?break;
? ? }else{
? ? ? ? ? ? ?//keep looping
? ? ? ? ? ? ?++it;
? ? ? ? }
? }
?}
第三种操作就比较狂野了,依旧是用迭代器遍历所有的vector内部所有的对象,然后找到这个对象直接无脑地把最后一个位置的对象用std::move()
给挪过来,但是这个说实话需要重写一下移动赋值函数,(如果有堆内存的开辟的话)这里就不赘述了,然后再把最后一个对象删掉。
具体实现如下:
#include <utility>//for std::move()
#include<iostream>
#include<vector>
#include"Object.h"
int main(){
? ? ?Object target{"Bob", 10};
? ? ?vector<Object> vlist;
? ? ?vlist.emplace_back("Bob", 10);
? vlist.emplace_back("Joy", 12);
? vlist.emplace_back("Roy", 16);
? ? ?//method no.3
? ? ?//put the ++it inside the loop
? for(auto it=vlist.begin(); it!=vlist.end();){
? ? if(target == *it){
? ? ? ? ? ? ?//capture the return value from erase() and keep loop work
? ? ? ? ? ? ?//if not last one
? ? ? ? ? ? ?//since exchange with itself is not good
? ? ? ? if(it != std::prev(vlist.end())){
? ? ? ? ? ? ? ? ?*it = std::move(vlist.back());
? ? ? ? ? ? }
? ? ? ? ? ? ?vlist.pop_back();
? ? ? ? ? ? ?break;
? ? }else{
? ? ? ? ? ? ?//keep looping
? ? ? ? ? ? ?++it;
? ? ? ? }
? }
?}
最后做个总结吧,虽然茴香豆,但是这么一个例子涉及到了std::swap()
, std::prev(iterator, count)
, erase()
, std::move()
, 如果真的认真实现的话还会设计移动赋值等操作。这对初学者的我来说是十分有意义的。