std::string(std::wstring)类,在C++中是一个非常重要的存在,不管程序规模大小,很难避免不用到。
功能很强大,但是总感觉距离“好用”还差了那么一点点。
首先,需要明白一点,std::string是STL中的一员,所以,有关stl的诸多算法和理念,都适用于它。
有关std::string的基本操作,不过多介绍,到处都能找到,这篇博客,重点介绍平常编程经常遇到的字符串的查找、替换和删除操作。
查找
std::string 的查找,用std::find函数当然也没问题,但是,其本身有很多关于查找的成员函数,使用起来更便捷,具体如下:
所有函数的返回值都是size_t。
从函数名就很容易理解函数的功能,拿find_first_of来说,查找第一个符合条件的元素。例子与后面的删除操作一起列出。
删除
删除函数为erase,也是成员函数:
从原型看,三种类型,删除的都是指定区间或者某个具体的迭代器,而不是某个字符或者字符串,这与我们平时理解的删除有点不太一样。
编程中遇到的,大多都是具体的删除,例如删除字符串中的所有空格、删除首尾的空格等等,所以前面才会说,std::string提供的成员函数,距离好用,还差了一点点。很多时候,需要我们重新封装一下。
比如,想实现删除所有空格(指定字符),怎么做呢?上一篇介绍过,vector想要删除某个元素,需要搭配erase成员函数和std::remove一起实现,std::string也是如此。
设字符串变量名为str,一句代码就能实现:
str.erase(std::remove(str.begin(), str.end(), ' '), str.end());
这里不过多介绍,具体原理,可以参考上一篇:STL(三)删除元素 remove、erase
如果是删除首尾的空格呢?
除了以上的查询函数外,还需要用到获取子串的substr函数。
找到第一个非空字符的位置iStart, 最后一个非空字符的位置iEnd,获取iStart到iEnd的子串,或者删除0到iStart以及iEnd到字符串末尾的字符。但是需要注意,首尾可能都没有空格,及iStart和iEnd都可能为std::string::npos(字符串里面只有空格或者空字符串)。
这里以获取iStart到iEnd的子串为例,封装接口如下:
size_t Trim(std::string& strValue, char ch = ' ')
{
size_t iCount = strValue.size();
size_t iStart = strValue.find_first_not_of(ch);
if (std::string::npos == iStart)
{
strValue.clear();
return iCount;
}
size_t iEnd = strValue.find_last_not_of(ch);
strValue = strValue.substr(iStart, iEnd - iStart + 1);
return iCount - iEnd + iStart - 1;
}
这里,if (std::string::npos == iStart),判断是否没有非空字符,如果std::string::npos == iStart,则iEnd必定也是std::string::npos,没必要继续下去。
Trim加入了一个参数ch,除了删除空格外,还能删除其他字符,返回值是删除的字符个数。
要测试不同字符串的执行结果,将执行和打印封装成一个函数,如下:
void ErasePrint(std::string& strValue)
{
using std::cout;
using std::endl;
const std::string strEndSign = "***";
cout << "原始字符串:" << strValue << strEndSign << endl;
size_t iEraseCount = Trim(strValue, ' ');
cout << "删除后:" << strValue << strEndSign << endl;
cout << "删除个数:" << iEraseCount << endl;
}
这里,定义了一个结束的字符串const std::string strEndSign = "***"; 因为控制台打印后,字符后面都是一片空白,看不出来是否有空格。
主函数如下:
int _tmain(int argc, _TCHAR* argv[])
{
std::string strValue = " I Love You ";
ErasePrint(strValue);
strValue = " ";
ErasePrint(strValue);
strValue = "";
ErasePrint(strValue);
system("pause");
return 0;
}
执行结果:
至于用erase,原理差不多,这里不过多赘述。
但是,这里是删除单个字符,如果想要删除字符串呢?
删除字符串,某种意义,是替换字符串的一个子集,即将想要删除的字符串替换成空的字符串。所以,删除字符串,直接看下面的替换即可。
替换
std::string类有一个现成的替换函数:replace。形式很多,如下:
不难看出,不管参数是什么,功能都类似:将指定字符串,替换原字符串中指定区间的元素。
这似乎与平常理解的替换又有一些不一样,正常情况下,我们的需求替换大部分是这样的:用一个新的子串(字符),替换原字符串中指定的子串(字符)。
利用上面的replace,以及find,可以实现,如下:
size_t Replace(std::string& strValue, const std::string& strOld, const std::string& strNew)
{
size_t iCount = 0;
if (strOld.empty())
return iCount;
std::string::size_type iFind = 0;
while (true)
{
iFind = strValue.find(strOld, iFind);
if (std::string::npos == iFind)
break;
strValue.replace(iFind, strOld.size(), strNew);
++iCount;
iFind += strNew.size();
}
return iCount;
}
这里需要注意几个地方:
1、新替换进来的字符串,可能和原来字符串和重新组成满足条件的子串。比如原始字符串为“aaac”,将要将“aa”替换成"ba",第一次替换后,原始字符串就成了"baac",此时如果再重头再替换,中间的aa还会被替换一次,显然不是想要的结果;
2、被替换的字符串是空的该如何处理?
以上接口,已经考虑到这个问题,如果还有什么未考虑到的,欢迎留言批评指正。
同样的,封装一个执行打印函数,如下:
void ReplacePrint(std::string& strValue, const std::string& strOld, const std::string& strNew)
{
using std::cout;
using std::endl;
cout << "原始字符串:" << strValue << endl;
int iEraseCount = Replace(strValue, strOld, strNew);
cout << "替换后:" << strValue << endl;
cout << "替换次数:" << iEraseCount << endl;
}
主函数中测试,如下:
int _tmain(int argc, _TCHAR* argv[])
{
std::string strValue = " I Love You ";
ReplacePrint(strValue, " ", "");
strValue = "aaac";
ReplacePrint(strValue, "aa", "ba");
strValue = "12345";
ReplacePrint(strValue, "", "11");
system("pause");
return 0;
}
执行结果如下:
基本满足要求。