05功能之实现两个文件根据主键进行删除存进新文件(vector,hash_map存储)

05功能之实现两个文件根据主键进行删除存进新文件(vector,hash_map存储)

前言:
好了,我们前四个功能都是为了这个以及下一个练习实现的,现在我们来运用前四个的功能实现小练习。

要求:
delete文件中有的主键,若new也有,则将new的那行删除,最后将结果存放在新文件outFile。
new.txt:

0x214871,xxxx1    // 逗号前成为主键
0x09313145,xxxx
0x1234561,xxxx
0x1234555,xxxx123

delete:

0x09313145,xxxx
0x121,xxxx
0x23,213
0x151,xxxx

注:文件打开失败,并且逻辑没错的情况下,有可能和文件名有关,改一下就好,也有可能和权限有关。

1 思路分析:
1)先将delete文件的主键存放于vector容器。
2)因为要将new的整行数据输出到新文件,并且读出来后是字符串形式,所以使用hash_map存储,并且格式为<string,string>,前者string代表new的主键,后者代表new的每一行数据。
3)遍历vector,若hash_map<v的元素>是存在的,则代表new含有该主键,则要删除它。
4)最后hash_map剩下的就是最后的结果。但由于读取时\n被自动转成0。所以输出时加上\n。

2 代码实现:

//#define _CRT_SECUER_NO_WARNINGS
#pragma warning(disable:4996)

#include<iostream>
#include<fstream>                // 包含ifstream、ofstream两个文件IO类
#include<cassert>
#include<string>
#include<vector>
#include<map>
//#include<hash_map> 
#include<unordered_map>
#include<exception>
#include <Windows.h>
using namespace std;

//UTF8转GBA312  ASCII、GB会正常显示,但是UTF8是可能会乱码,所以最好在读取时转
string UTF8ToGB(const char* str)
{
	string result;
	WCHAR *strSrc;
	LPSTR szRes;

	//获得临时变量的大小
	int i = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
	strSrc = new WCHAR[i + 1];
	MultiByteToWideChar(CP_UTF8, 0, str, -1, strSrc, i);

	//获得临时变量的大小
	i = WideCharToMultiByte(CP_ACP, 0, strSrc, -1, NULL, 0, NULL, NULL);
	szRes = new CHAR[i + 1];
	WideCharToMultiByte(CP_ACP, 0, strSrc, -1, szRes, i, NULL, NULL);

	result = szRes;
	delete[]strSrc;
	delete[]szRes;

	return result;
}

// 将字符串写入文件
void WriteFile(string fileName,string s1) {

	//ofstream outFile(fileName,ios::ate); //写入方式为覆盖
	ofstream outFile(fileName, ios::app);  //写入方式为追加数据
	if (!outFile) {
		throw exception("打开文件失败");
	}

	outFile << s1;

	outFile.close();
}

// 利用string容器的find和substr分割字符串 并返回分隔符前的字符串
string SplitStr(const string &str, const string &split){

	//为空无法分割
	if ("" == str or "" == split){
		return "";
	}

	//方便截取后一段数据 这里用不到
	//string strAdd = str + split;            //"a"+"b"="ab"

	size_t pos =  str.find(split);            //返回分隔符第一次出现的下标 没找到返回string::npos
	if (pos == string::npos) {
		cout << "没找到分隔符" << endl;
		return "";
	}
	string s1  = str.substr(0, pos);

	return s1;                                //造成resVec为空:str为空或者没找到分隔符
}

int main() {

	// 1 操作delete文件
	//用于存放主键
	vector<string> resVec;
	ifstream delFile;
	delFile.open("delete.txt");
	assert(delFile.is_open());

	string lineStr;
	// 与上面xxx.getline不一样 这个返回输入流 只要流是正常返回就可以读取 否则说明读完
	while (getline(delFile, lineStr)) {
		UTF8ToGB(lineStr.c_str());
		cout << lineStr << endl;
		string splitSub = SplitStr(lineStr, ",");
		resVec.push_back(splitSub);
	}
	delFile.close();


	cout << "=============================" << endl;


	// 2 操作new文件
	//map<string, string> myMap;                  //存放主键与整行字符串 因为map要排序string无法排序 所以用hash_map
	unordered_map<string, string> myMap;
	char buf[1024];
	ifstream newFile;
	newFile.open("new.txt");
	assert(newFile.is_open());

	while ( newFile.getline(buf, 1024) ) {
		string sub = SplitStr(buf, ",");          //分割的前半段
		myMap.insert(make_pair(sub, buf));        //读出来应该将buf的\n转成\0了,不然下面不用加\n
	}
	newFile.close();

	// 3 遍历hash_map的另一个容器即可 判断主键是否重复
	for (vector<string>::iterator it = resVec.begin(); it != resVec.end(); it++) {
		unordered_map<string, string>::iterator mIt = myMap.find(*it);
		if (mIt != myMap.end()) {
			myMap.erase(*it);
		}
	}

	// 4 最后将hash_map的内容输入到新文件
	for (unordered_map<string, string>::iterator mIt = myMap.begin(); mIt != myMap.end(); mIt++) {
		try {
			WriteFile("outFile.txt", mIt->second + "\n");  //加"\n"是因为分割存进hash_map的不存在换行符
		}
		catch (exception &e) {
			cout << e.what() << endl;
		}
	}

	//注意写入时是追加,文件为空时才是正常换行,否则之前的内容会连在一起

	return 0;
}

3 vector、map删除时的注意点
上面我们进行删除时,由于是遍历vector,删除hash_map,每一次find后mIt都会返回新的,且删除时也没有用到mIt,所以不需要担心迭代器失效问题。但是我们还是要给大家提醒一下。

1)vector删除:
我们要知道vector删除,就是前移,这两个概念是一样的。所以该迭代器以后的所有迭代器都会失效。那删除一个后,后面的迭代器不能用了怎么办?实际上他会返回新的迭代器给我们进行下次删除。

void test02() {

	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(2);
	v.push_back(2);
	v.push_back(2);
	v.push_back(4);
	v.push_back(5);

	//正确写法
	for (vector<int>::iterator it = v.begin(); it != v.end(); ){
		if (*it == 2) {
			it = v.erase(it);
			//it = v.erase(it++); //虽然这样写也可以 但是最后你的it还是用返回值替代 所以没必要
		}
		else {
			it++;
		}
	}


#if 0
	// 1 直接报错
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++){
		if (*it == 2) {
			v.erase(it);
		}
	}

	// 2 同样报错
	for (vector<int>::iterator it = v.begin(); it != v.end(); ) {
		if (*it == 2) {
			v.erase(it++);   //error
			//v.erase(it);       //error虽然逻辑没错,但是可能编译器不允许这种操作
		}
		else {
			it++;
		}
	}
#endif

	//打印
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
		cout << *it << " ";
	}

	cout << endl;
}

分析1和2的报错原因:
1:
在这里插入图片描述
2:
在这里插入图片描述
两个出错原因都是自增两次,只不过1是先删除后执行for中的it++;2是it先++后再删除。
即vector删除以后只能写一种。

2)map删除:
map删除,我们只需要记住当前迭代器失效即可,由于当前迭代器会失效,所以我们代码上必须先自增指向下一迭代器先,再进行删除。
注:因为底层是红黑树,底层具体是怎么删除不需要关心,太复杂了(我学过…)。

	//map
void test03() {

#if 0
	map<int, int> m1;
	m1[0] = 1;
	m1[-1] = 3;
	cout << m1[0] << m1[-1] << endl;  //map下标可以是负数
#endif

	map<int, int> m;
	m.insert(make_pair(0, 1));
	m.insert(make_pair(1, 2));
	m.insert(make_pair(2, 2));
	m.insert(make_pair(3, 2));
	m.insert(make_pair(4, 2));
	m.insert(make_pair(5, 4));
	m.insert(make_pair(6, 5));

	map<int, int>::iterator itt;
	for (map<int, int>::iterator it = m.begin(); it != m.end();) {
		if (it->second == 2) {

			//m.erase(it++);          //标准写法 

			//等效上面写法	  必须先保存值,再自增,再删除
			//itt = it;
			//it++;
			//m.erase(itt);	

			//错误写法1   只对itt自增操作 实际it还是一样
			//itt = it;
			//m.erase(it);
			//itt++;

			//错误写法2   用临时变量删除,最后才自增 由于itt删除后那片内存失效 it再去访问且自增当然出错
			//itt = it;   
			//m.erase(itt);  //这里类似智能指针的auto_ptr p1被释放掉p2再使用就出错
			//it++;

		}
		else {
			it++;
		}
	}

	for (map<int, int>::iterator it = m.begin(); it != m.end(); it++) {
		cout << it->second << " ";
	}
	cout << endl;

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值