字符串格式化组装通用函数
C++对字符串组装没有一个很直接好用的函数,这里利用C的snprintf()函数,提供一个可用的函数:
template<typename ... Args>
std::string stringFormat(const std::string& format, Args ... args ) {
size_t size = (size_t)snprintf( NULL, 0, format.c_str(), args ... ) + 1; // Extra space for '\0'
#ifdef C11
std::unique_ptr<char> buf(new char[size]);
#else
std::auto_ptr<char> buf(new char[size]);
#endif
snprintf( buf.get(), size, format.c_str(), args ... );
return std::string(buf.get(), size - 1); // We don't want the '\0' inside
}
这里的stringFormat函数是一个模板函数,可以接受多种形式的格式化组装,也就是可以拼接int、float、long、string等各种类型的变量。
之所以这里的模板参数和函数的最后一个参数都是省略号,是C允许的一种参数表示形式,必须放在最后一个,且必须前面有确定的参数,它表示后续的参数个数不定。这里配合模板,也就是参数的个数和类型都不定了。所以我们可以用来组装任何类型的变量。
snprintf()也是C的一个函数,用法如下:
int snprintf(char *str, int n, char * format [, argument, ...]);
参数中:
- str:目的地址,用来存组装后的char数组地址;
- n:保留的字符个数(不包含最后的'\0'),这里需要注意,不管后面组装了多少字符,最终结果只会保留这里的n个字符,再在结尾加上一个'\0'表示结束;
- format:格式char数组,也就是我们常用的类似“hello %s”这样的待组装格式了;
- argument...:不定个数的参数,用来适配格式char数组需要的变量。
返回值:返回组装后的本应有的char数组长度,不包括最后的'\0'。注意并不是n的数值,否则这个返回没有意义,这里返回的是本应有的char数组长度,也就是format组装好变量后的全长,而n相当于是设置要截取前面的多少个字符赋给str。
这样就清楚了,这里我们的目的地址放了NULL,保留的字符个数又是0,所以没有要截取保留的str,只是单纯计算一下组装所需要的长度,因为函数返回不包括'\0',所以这里要加一。
然后我们创建一个char类型的数组,用算好的长度去初始化。根据编译器的C++版本不同,使用唯一指针或者自动指针。唯一指针是C++11的特性,同一对象只能被一个unique_ptr来拥有,禁止进行拷贝构造和赋值构造操作。当unique_ptr指针对象离开其作用域时,生命期结束,自动使用内部给定的删除器(deleter)delete所指向的对象。所以函数结束后,其申请的资源会自动删除。
创建好char数组后,我们就进行实际的组装,再次使用snprintf函数,这次我们知道了需要的长度就是我们前面计算出来的长度,将前面创建的char数组放到目的char数组的参数位置,进行组装。前面要计算一次长度的原因就是因为我们并不知道实际使用的时候会组装多长的字符串,如果随意创建一个长度的char数组,要么浪费,要么不够。
最后,我们用组装后的结果char数组来初始化字符串,并返回,这里只要前面的实际字符,不要最后的'\0'。
数值类型转字符串
C++11以前没有直接的数值类型转字符串的函数,这里提供一些:
std::string itoString(int i) {
char buf[30] = {0};
sprintf(buf, "%d", i);
return std::string(buf);
}
std::string ltoString(long i) {
char buf[30] = {0};
sprintf(buf, "%ld", i);
return std::string(buf);
}
std::string lltoString(long long i) {
char buf[30] = {0};
sprintf(buf, "%lld", i);
return std::string(buf);
}
其实都是利用sprintf函数来做格式化,将数值类型转为char数组,再转为string类型返回。
字符串根据特定字符拆分成数组通用函数
split是其他语言中将字符串转化为数组的常用函数,C++中却没有,这里提供一个通用函数,可以将字符串根据特定字符拆分成数组:
#include <string>
#include <vector>
using std::string;
using std::vector;
vector<string> split(const string &str, const string &separtor) {
size_t begin = 0, end = 0;
vector<string> result;
while (true) {
end = str.find(separtor, begin);
if (end == string::npos) {
result.push_back(str.substr(begin));
break;
} else {
result.push_back(str.substr(begin, end-begin));
begin = end + separtor.size();
}
}
return result;
}
函数接收要拆分的字符串和指定的分隔符字符串,都是const形式,因为不想对其做更改。返回拆分好的数组,也就是string类型的vector。
初始化需要的变量后,在无限循环中,使用string的find函数来找分隔符出现的位置,第二个参数是指开始找的位置,这里一开始是0。find函数会返回第一次找到的位置,如果找不到,会返回string::npos,这里的npos一般是一个size_t的最大值,在字符串中就是字符串的最后位置。
所以下面如果是string::npos,那就表示在begin位置后找不到了,直接从begin开始截取子串直到字符串的最后位置,放到数组中去。
如果不是,说明找到了,因此从begin开始截取需要的长度,长度由end-begin计算出来。substr函数接受截取的开始位置和长度,长度默认为最大值,也就是到直到字符串末尾。截取完后,再更新begin到分隔符后的位置,方便下一次寻找。
最后返回分割后的字符串。
替换字符串中某个子串
将字符串中某个子串全部替换为另一个子串:
std::string ReplaceAll(std::string str, const std::string& from, const std::string& to) {
size_t start_pos = 0;
while((start_pos = str.find(from, start_pos)) != std::string::npos) {
str.replace(start_pos, from.length(), to);
start_pos += to.length();
}
return str;
}
做法就是不断在字符串中找到要被替换的子串,得到位置后,用replace函数替换成目的子串,直到找不到为止。注意该函数并没有改变源字符串,而是复制了实参,修改后返回。
大小写转换
将字符串中的字母全部转为大写或者全部转为小写:
void toUpperCase(string &s) {
for (string::iterator it = s.begin(); it != s.end(); it++) {
char c = (char)std::toupper(*it);
*it = c;
}
}
void toLowerCase(string &s) {
for (string::iterator it = s.begin(); it != s.end(); it++) {
char c = (char)std::tolower(*it);
*it = c;
}
}
利用toupper/tolower函数,用迭代器遍历每个字符,进行修改。这里改的是原字符串,不需要返回新字符串。
toupper/tolower函数源码本身只会对属于字母的字符进行修改,非字母字符会原样返回,所以不需要担心字符串中包含非字母的字符。
网友评论