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;
}