std::map::erase的用法及陷阱

本文介绍了两种在C++中遍历并删除std::map容器内元素的方法。第一种方法利用了erase方法返回的迭代器进行操作,第二种方法则巧妙地使用了后增运算符避免了迭代器失效的问题。

方法一:

[cpp]  view plain copy
  1. std::map<std::string, std::string > mapTest;  
  2. bool TestVal(const std::string & val);  
  3.   
  4. ......  
  5. std::map<std::string, std::string >::iterator it = mapTest.begin();  
  6. while(it != mapTest.end())  
  7. {  
  8.          if(TestVal(it->second))  
  9.          {  
  10.                  it = mapTest.erase(it);  
  11.          }  
  12.          else  
  13.                  it++;  
  14. }  
  15. ...........  
在这种方式中,通过std::map的erase方法在释放了it后会返回指向下一个元素的指针来获取最新的iterator


方法二:

[cpp]  view plain copy
  1. std::map<std::string, std::string > mapTest;  
  2. bool TestVal(const std::string & val);  
  3.   
  4. ......  
  5. std::map<std::string, std::string >::iterator it = mapTest.begin();  
  6. while(it != mapTest.end())  
  7. {  
  8.          if(TestVal(it->second))  
  9.          {  
  10.                 mapTest.erase(it++);  
  11.          }  
  12.          else  
  13.                  it++;  
  14. }  
  15. ...........  
该方法中利用了后++的特点,这个时候执行mapTest.erase(it++);这条语句分为三个过程

1、先把it的值赋值给一个临时变量做为传递给erase的参数变量

2、因为参数处理优先于函数调用,所以接下来执行了it++操作,也就是it现在已经指向了下一个地址。

3、再调用erase函数,释放掉第一步中保存的要删除的it的值的临时变量所指的位置。


如果只是mapTest.erase(it); 当这条语句执行完后,it就是一个非法指针,如果再执行++就会出错


总结,对于iter的移动在对erase时需要注意。

#include "pch.h" #include "TCalcFuncSets.h" #include <windows.h> #include <string> #include <vector> #include <fstream> #include <sstream> #include <map> #include <algorithm> #include <iostream> #include <cmath> #include <cctype> #include <unordered_map> #include <mutex> #include <memory> // 用于std::make_shared #include <chrono> // 用于std::chrono时间功能 // 缓存项结构体 struct CacheEntry { std::map<std::string, float> data; std::chrono::steady_clock::time_point lastAccessed; CacheEntry() : lastAccessed(std::chrono::steady_clock::now()) {} }; // 统一的缓存管理类 class QuantileCache { private: std::unordered_map<std::string, CacheEntry> cache_; std::mutex mutex_; const size_t maxCacheSize_ = 100; // 最大缓存项数量 public: // 尝试从缓存获取数据 bool tryGet(const std::string& key, std::map<std::string, float>& result) { std::lock_guard<std::mutex> lock(mutex_); auto it = cache_.find(key); if (it != cache_.end()) { // 更新访问时间 it->second.lastAccessed = std::chrono::steady_clock::now(); result = it->second.data; return true; } return false; } // 存储数据到缓存 void put(const std::string& key, const std::map<std::string, float>& data) { std::lock_guard<std::mutex> lock(mutex_); // 清理过期缓存项 if (cache_.size() >= maxCacheSize_) { evictOldEntries(); } cache_[key].data = data; cache_[key].lastAccessed = std::chrono::steady_clock::now(); } private: // 移除最旧的缓存项 void evictOldEntries() { // 找到最旧的缓存项 auto oldestIt = cache_.begin(); for (auto it = cache_.begin(); it != cache_.end(); ++it) { if (it->second.lastAccessed < oldestIt->second.lastAccessed) { oldestIt = it; } } // 移除最旧的缓存项 cache_.erase(oldestIt); } }; // 全局缓存实例 static QuantileCache g_quantileCache; // 清理字符串中的空白字符 std::string CleanString(const std::string& str) { std::string result; result.reserve(str.size()); for (unsigned int i = 0; i < str.size(); i++) { char c = str[i]; if (!std::isspace((unsigned char)c)) { result.push_back(c); } } return result; } // 清理路径中的无效字符 std::string CleanPath(const std::string& path) { std::string result; result.reserve(path.size()); for (unsigned int i = 0; i < path.size(); i++) { char c = path[i]; if (c == ' ' || !std::isspace((unsigned char)c)) { result.push_back(c); } } return result; } // 将通达信日期格式转换为标准日期格式(yyyymmdd) std::string ConvertTDXDateToYYYYMMDD(float tdxDate) { int dateInt = (int)tdxDate + 19000000; if (dateInt <= 19000000 || dateInt > 21000000) { return ""; } char buffer[16]; sprintf_s(buffer, sizeof(buffer), "%d", dateInt); return std::string(buffer); } // 快速解析日期字符串 std::string ParseDateString(const std::string& dateStr) { // 移除引号 std::string cleaned = dateStr; if (!cleaned.empty() && cleaned[0] == '"') { cleaned = cleaned.substr(1); } if (!cleaned.empty() && cleaned[cleaned.length() - 1] == '"') { cleaned = cleaned.substr(0, cleaned.length() - 1); } // 如果已经是yyyymmdd格式 if (cleaned.length() == 8 && cleaned.find_first_not_of("0123456789") == std::string::npos) { return cleaned; } // 处理分隔符格式 int year = 0, month = 0, day = 0; size_t firstDelim = cleaned.find_first_of("-/\/"); if (firstDelim != std::string::npos) { size_t secondDelim = cleaned.find_first_of("-/\/", firstDelim + 1); if (secondDelim != std::string::npos) { year = atoi(cleaned.substr(0, firstDelim).c_str()); month = atoi(cleaned.substr(firstDelim + 1, secondDelim - firstDelim - 1).c_str()); day = atoi(cleaned.substr(secondDelim + 1).c_str()); } } else if (cleaned.length() == 8) { year = atoi(cleaned.substr(0, 4).c_str()); month = atoi(cleaned.substr(4, 2).c_str()); day = atoi(cleaned.substr(6, 2).c_str()); } if (year > 1900 && year < 2100 && month >= 1 && month <= 12 && day >= 1 && day <= 31) { char buffer[16]; sprintf_s(buffer, sizeof(buffer), "%04d%02d%02d", year, month, day); return std::string(buffer); } return ""; } // 处理单个CSV文件并提取指定列的数据 bool ProcessSingleCSVFileForColumn(const std::string& filePath, std::map<std::string, std::vector<double> >& dateColumnMap, int columnIdx) { std::ifstream inFile(filePath.c_str()); if (!inFile.is_open()) { return false; } // 跳过表头 std::string headerLine; if (!std::getline(inFile, headerLine)) { inFile.close(); return false; } std::string line; while (std::getline(inFile, line)) { std::istringstream lineIss(line); std::string token; int columnIndex = 0; std::string dateStr; double columnValue = 0.0; bool hasValue = false; while (std::getline(lineIss, token, ',')) { std::string cleanedToken = CleanString(token); if (columnIndex == 0) { // date列 dateStr = ParseDateString(cleanedToken); } else if (columnIndex == columnIdx) { // 指定列 if (!cleanedToken.empty()) { columnValue = atof(cleanedToken.c_str()); hasValue = true; } } columnIndex++; } // 只有当日期有效且指定列有时才添加 if (!dateStr.empty() && hasValue) { dateColumnMap[dateStr].push_back(columnValue); } } inFile.close(); return true; } // 处理单个CSV文件并计算N日涨跌幅 bool ProcessSingleCSVFileForReturns(const std::string& filePath, std::map<std::string, std::vector<double> >& dateReturnsMap, int N) { std::ifstream inFile(filePath.c_str()); if (!inFile.is_open()) { return false; } // 跳过表头 std::string headerLine; if (!std::getline(inFile, headerLine)) { inFile.close(); return false; } // 预分配向量空间以提高性能 std::vector<std::string> dates; std::vector<double> closePrices; dates.reserve(1000); // 预估每只股票约1000个交易日 closePrices.reserve(1000); std::string line; while (std::getline(inFile, line)) { std::istringstream lineIss(line); std::string token; int columnIndex = 0; std::string dateStr; double closePrice = 0.0; while (std::getline(lineIss, token, ',')) { std::string cleanedToken = CleanString(token); if (columnIndex == 0) { // date列 dateStr = ParseDateString(cleanedToken); } else if (columnIndex == 5) { // close列 if (!cleanedToken.empty()) { closePrice = atof(cleanedToken.c_str()); } } columnIndex++; } dates.push_back(dateStr); closePrices.push_back(closePrice); } inFile.close(); // 计算N日涨跌幅 for (unsigned int i = N; i < closePrices.size(); i++) { if (!dates[i].empty() && closePrices[i] > 0 && closePrices[i - N] > 0) { double returnRate = (closePrices[i] - closePrices[i - N]) / closePrices[i - N] * 100.0; dateReturnsMap[dates[i]].push_back(returnRate); } } return true; } // 优化的文件处理函数 - 用于计算指定列的分位数 void ReadCSVAndCalculateColumnData(const std::string& directory, std::map<std::string, std::vector<double> >& dateColumnMap, int columnIdx) { WIN32_FIND_DATAA findData; std::string cleanedDirectory = CleanPath(directory); std::string searchPath = cleanedDirectory + "\\*.csv"; HANDLE hFind = FindFirstFileA(searchPath.c_str(), &findData); if (hFind == INVALID_HANDLE_VALUE) { return; } do { if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { std::string fileName(findData.cFileName); std::string filePath = cleanedDirectory + "\\" + fileName; filePath = CleanPath(filePath); ProcessSingleCSVFileForColumn(filePath, dateColumnMap, columnIdx); } } while (FindNextFileA(hFind, &findData) != 0); FindClose(hFind); } // 优化的文件处理函数 - 用于计算N日涨跌幅 void ReadCSVAndCalculateReturns(const std::string& directory, std::map<std::string, std::vector<double> >& dateReturnsMap, int N) { WIN32_FIND_DATAA findData; std::string cleanedDirectory = CleanPath(directory); std::string searchPath = cleanedDirectory + "\\*.csv"; HANDLE hFind = FindFirstFileA(searchPath.c_str(), &findData); if (hFind == INVALID_HANDLE_VALUE) { return; } do { if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { std::string fileName(findData.cFileName); std::string filePath = cleanedDirectory + "\\" + fileName; filePath = CleanPath(filePath); ProcessSingleCSVFileForReturns(filePath, dateReturnsMap, N); } } while (FindNextFileA(hFind, &findData) != 0); FindClose(hFind); } // 函数前向声明 template <typename DataReaderFunc> void ProcessQuantileData( const std::string& csvDirectory, std::map<std::string, float>& dateQuantileMap, DataReaderFunc readDataFunc, float quantile ); template <typename DataReaderFunc> void ProcessQuantileData( const std::string& csvDirectory, std::map<std::string, float>& dateQuantileMap, DataReaderFunc readDataFunc, float quantile, std::map<std::string, std::vector<double>>& dateDataMap ); float CalculateQuantile(const std::vector<double>& data, float quantile); // 通用分位数数据处理函数 // 提取两个主要函数的公共数据处理逻辑 template <typename DataReaderFunc> void ProcessQuantileData( const std::string& csvDirectory, std::map<std::string, float>& dateQuantileMap, DataReaderFunc readDataFunc, float quantile ) { // 存储日期对应的数据 std::map<std::string, std::vector<double>> dateDataMap; // 调用传入的函数读取数据 readDataFunc(csvDirectory, dateDataMap); // 计算每个日期的分位数 for (const auto& entry : dateDataMap) { dateQuantileMap[entry.first] = CalculateQuantile(entry.second, quantile); } // 清理数据以释放内存 dateDataMap.clear(); } // 兼容不同参数类型的重载版本 template <typename DataReaderFunc> void ProcessQuantileData( const std::string& csvDirectory, std::map<std::string, float>& dateQuantileMap, DataReaderFunc readDataFunc, float quantile, std::map<std::string, std::vector<double>>& dateDataMap ) { // 调用传入的函数读取数据 readDataFunc(csvDirectory, dateDataMap); // 计算每个日期的分位数 for (const auto& entry : dateDataMap) { dateQuantileMap[entry.first] = CalculateQuantile(entry.second, quantile); } } // 计算分位数 - 使用O(n)时间复杂度的优化算法 float CalculateQuantile(const std::vector<double>& data, float quantile) { if (data.empty()) { return 0.0f; } if (quantile < 0.0f) quantile = 0.0f; if (quantile > 1.0f) quantile = 1.0f; std::vector<double> sortedData = data; size_t n = sortedData.size(); double pos = quantile * (n - 1); int lowerIndex = static_cast<int>(pos); int upperIndex = lowerIndex + 1; // 对于精确索引的情况,直接使用nth_element if (lowerIndex == upperIndex || upperIndex >= static_cast<int>(n)) { std::nth_element(sortedData.begin(), sortedData.begin() + lowerIndex, sortedData.end()); return static_cast<float>(sortedData[lowerIndex]); } // 对于需要插的情况,找到两个位置的元素 // 先找lowerIndex位置的元素 std::nth_element(sortedData.begin(), sortedData.begin() + lowerIndex, sortedData.end()); double lowerValue = sortedData[lowerIndex]; // 再在lowerIndex之后的范围内找upperIndex位置的元素 std::nth_element(sortedData.begin() + lowerIndex + 1, sortedData.begin() + upperIndex, sortedData.end()); double upperValue = sortedData[upperIndex]; // 线性插计算最终分位数 double fractionalPart = pos - lowerIndex; double quantileValue = lowerValue + fractionalPart * (upperValue - lowerValue); return static_cast<float>(quantileValue); } // 生成缓存键 std::string GenerateColumnCacheKey(int columnIdx, float quantile) { char buffer[64]; sprintf_s(buffer, "column_%d_%.2f", columnIdx, quantile); return std::string(buffer); } std::string GenerateReturnsCacheKey(int N, float quantile) { char buffer[64]; sprintf_s(buffer, "returns_%d_%.2f", N, quantile); return std::string(buffer); } // 函数2 - 计算指定列的分位数 void CalculateColumnQuantileReturns(int DataLen, float* pfOUT, float* pfINa, float* pfINb, float* pfINc) { // 初始化输出数组为0 for (int i = 0; i < DataLen; i++) { pfOUT[i] = 0.0f; } // 获取列索引和分位数参数 int columnIdx = (int)(pfINb[0] + 0.5f); // M参数表示列号 float quantile = pfINc[0]; // N参数表示分位数 // 生成缓存键,添加前缀以区分不同类型的缓存 std::string cacheKey = "column:" + GenerateColumnCacheKey(columnIdx, quantile); // 检查缓存中是否已有计算结果 std::map<std::string, float> dateQuantileMap; bool hasCache = g_quantileCache.tryGet(cacheKey, dateQuantileMap); // 如果缓存中没有,则进行计算 if (!hasCache) { // 设置CSV文件的目录 const std::string csvDirectory = "F:\\His_STOCKDATA"; // 使用堆分配存储日期对应的指定列数据,避免栈溢出 auto dateColumnMap = std::make_shared<std::map<std::string, std::vector<double>>>(); // 读取CSV文件并提取指定列的数据 ReadCSVAndCalculateColumnData(csvDirectory, *dateColumnMap, columnIdx); // 计算每个日期的分位数 for (const auto& entry : *dateColumnMap) { dateQuantileMap[entry.first] = CalculateQuantile(entry.second, quantile); } // 自动释放内存(通过shared_ptr的作用域) // 将结果存入缓存 g_quantileCache.put(cacheKey, dateQuantileMap); } // 将结果匹配到输出数组 for (int i = 0; i < DataLen; i++) { std::string dateStr = ConvertTDXDateToYYYYMMDD(pfINa[i]); if (dateStr.empty() || dateStr.length() != 8) { pfOUT[i] = 0.0f; continue; } std::map<std::string, float>::const_iterator iter = dateQuantileMap.find(dateStr); if (iter != dateQuantileMap.end()) { pfOUT[i] = iter->second; } else { pfOUT[i] = 0.0f; } } } // 函数3 - 快速计算分位数收益 void CalculateQuantileReturns(int DataLen, float* pfOUT, float* pfINa, float* pfINb, float* pfINc) { // 初始化输出数组为0 for (int i = 0; i < DataLen; i++) { pfOUT[i] = 0.0f; } // 获取N和M的 int N = (int)(pfINb[0] + 0.5f); float M = pfINc[0]; // 生成缓存键,添加前缀以区分不同类型的缓存 std::string cacheKey = "returns:" + GenerateReturnsCacheKey(N, M); // 检查缓存中是否已有计算结果 std::map<std::string, float> dateQuantileMap; bool hasCache = g_quantileCache.tryGet(cacheKey, dateQuantileMap); // 如果缓存中没有,则进行计算 if (!hasCache) { // 设置CSV文件的目录 const std::string csvDirectory = "F:\\His_STOCKDATA"; // 使用堆分配存储日期对应的涨跌幅数据,避免栈溢出 auto dateReturnsMap = std::make_shared<std::map<std::string, std::vector<double>>>(); // 读取CSV文件并计算N日涨跌幅 ReadCSVAndCalculateReturns(csvDirectory, *dateReturnsMap, N); // 计算每个日期的分位数 for (const auto& entry : *dateReturnsMap) { dateQuantileMap[entry.first] = CalculateQuantile(entry.second, M); } // 自动释放内存(通过shared_ptr的作用域) // 将结果存入缓存 g_quantileCache.put(cacheKey, dateQuantileMap); } // 将结果匹配到输出数组 for (int i = 0; i < DataLen; i++) { std::string dateStr = ConvertTDXDateToYYYYMMDD(pfINa[i]); if (dateStr.empty() || dateStr.length() != 8) { pfOUT[i] = 0.0f; continue; } std::map<std::string, float>::const_iterator iter = dateQuantileMap.find(dateStr); if (iter != dateQuantileMap.end()) { pfOUT[i] = iter->second; } else { pfOUT[i] = 0.0f; } } } // 函数注册 PluginTCalcFuncInfo g_CalcFuncSets[] = { {2, (pPluginFUNC)CalculateColumnQuantileReturns}, {3, (pPluginFUNC)CalculateQuantileReturns}, {0, NULL}, }; BOOL RegisterTdxFunc(PluginTCalcFuncInfo** pFun) { if (*pFun == NULL) { (*pFun) = g_CalcFuncSets; return TRUE; } return FALSE; }有哪些得优化的地方
09-22
<think>好的,用户想了解C++标准库中的std::set的用法和示例。首先,我需要回忆std::set的基本特性。std::set是一个关联容器,存储唯一元素,通常按照升序排序。它基于红黑树实现,插入、删除和查找的时间复杂度都是对数级别的。 接下来,用户需要用法和示例,所以应该涵盖初始化、插入元素、遍历、查找、删除等基本操作。可能还需要提到自定义比较函数的方法,比如使用lambda或者函数对象来改变排序规则。此外,可以对比std::unordered_set,说明两者的区别,比如有序和无序,性能差异等。 考虑到用户提供的引用中提到了std::swap和std::map的遍历,或许可以结合这些例子。比如在示例中使用范围for循环遍历set,类似于引用[2]中的map遍历。同时,引用[3]提到了模板和重载的问题,虽然和set关系不大,但需要注意在示例中避免可能的陷阱,比如比较函数的设计是否正确。 需要确保代码示例正确,比如使用C++11或更高版本的语法,比如auto关键字和范围for循环。同时,注意格式化代码,使用正确的语法高亮。可能还需要提到set的常用成员函数,如insert, find, erase, size等。 另外,用户可能对性能感兴趣,比如插入和查找的效率,所以应该解释红黑树的结构带来的对数复杂度。还可以提到std::set的应用场景,比如需要有序且唯一的元素集合时。 最后,生成相关问题时要围绕std::set的特性、操作、性能优化、与其他容器的对比等方面展开,帮助用户深入理解。</think>### C++ std::set 容器详解 #### 一、基本特性 `std::set` 是 C++ 标准库中的有序关联容器,满足以下特性: 1. 所有元素自动排序(默认升序) 2. 元素唯一性保证 3. 基于红黑树实现,插入/删除/查找时间复杂度为 $O(\log n)$[^1] #### 二、基本用法示例 ```cpp #include <iostream> #include <set> int main() { // 初始化集合 std::set<int> numbers = {5, 2, 8, 3, 1}; // 插入元素 numbers.insert(4); numbers.insert(2); // 重复元素不会被插入 // 遍历输出(自动排序) for (const auto& num : numbers) { std::cout << num << " "; } // 输出:1 2 3 4 5 8 // 查找元素 auto it = numbers.find(3); if (it != numbers.end()) { std::cout << "\nFound: " << *it; } // 删除元素 numbers.erase(5); } ``` #### 三、自定义排序规则 ```cpp // 使用 lambda 定义降序规则 auto cmp = [](int a, int b) { return a > b; }; std::set<int, decltype(cmp)> desc_set(cmp); // 使用函数对象 struct CaseInsensitiveCompare { bool operator()(const std::string& a, const std::string& b) const { return std::lexicographical_compare( a.begin(), a.end(), b.begin(), b.end(), [](char c1, char c2) { return tolower(c1) < tolower(c2); }); } }; std::set<std::string, CaseInsensitiveCompare> case_insensitive_set; ``` #### 四、关键操作复杂度 | 操作 | 时间复杂度 | 说明 | |------------|------------|--------------------------| | insert() | $O(\log n)$| 插入新元素 | | erase() | $O(\log n)$| 删除指定元素 | | find() | $O(\log n)$| 查找元素 | | count() | $O(\log n)$| 判断元素是否存在 | | lower_bound() | $O(\log n)$ | 返回第一个不小于的迭代器 | #### 五、与 std::unordered_set 对比 | 特性 | std::set | std::unordered_set | |-----------------|-------------------|--------------------| | 元素顺序 | 有序 | 无序 | | 实现结构 | 红黑树 | 哈希表 | | 查找速度 | $O(\log n)$ | 平均 $O(1)$ | | 内存占用 | 较低 | 较高 | | 自定义排序 | 支持比较函数 | 需要哈希函数 |
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值