字符串格式化組裝通用函數(shù)
C++對字符串組裝沒有一個很直接好用的函數(shù)怯邪,這里利用C的snprintf()函數(shù)绊寻,提供一個可用的函數(shù):
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函數(shù)是一個模板函數(shù),可以接受多種形式的格式化組裝悬秉,也就是可以拼接int澄步、float、long和泌、string等各種類型的變量村缸。
之所以這里的模板參數(shù)和函數(shù)的最后一個參數(shù)都是省略號,是C允許的一種參數(shù)表示形式允跑,必須放在最后一個王凑,且必須前面有確定的參數(shù),它表示后續(xù)的參數(shù)個數(shù)不定聋丝。這里配合模板索烹,也就是參數(shù)的個數(shù)和類型都不定了。所以我們可以用來組裝任何類型的變量弱睦。
snprintf()也是C的一個函數(shù)百姓,用法如下:
int snprintf(char *str, int n, char * format [, argument, ...]);
參數(shù)中:
- str:目的地址,用來存組裝后的char數(shù)組地址况木;
- n:保留的字符個數(shù)(不包含最后的'\0')垒拢,這里需要注意,不管后面組裝了多少字符火惊,最終結(jié)果只會保留這里的n個字符求类,再在結(jié)尾加上一個'\0'表示結(jié)束;
- format:格式char數(shù)組屹耐,也就是我們常用的類似“hello %s”這樣的待組裝格式了尸疆;
- argument...:不定個數(shù)的參數(shù),用來適配格式char數(shù)組需要的變量。
返回值:返回組裝后的本應(yīng)有的char數(shù)組長度寿弱,不包括最后的'\0'犯眠。注意并不是n的數(shù)值,否則這個返回沒有意義症革,這里返回的是本應(yīng)有的char數(shù)組長度筐咧,也就是format組裝好變量后的全長,而n相當(dāng)于是設(shè)置要截取前面的多少個字符賦給str噪矛。
這樣就清楚了量蕊,這里我們的目的地址放了NULL,保留的字符個數(shù)又是0摩疑,所以沒有要截取保留的str危融,只是單純計算一下組裝所需要的長度,因為函數(shù)返回不包括'\0'雷袋,所以這里要加一。
然后我們創(chuàng)建一個char類型的數(shù)組辞居,用算好的長度去初始化楷怒。根據(jù)編譯器的C++版本不同,使用唯一指針或者自動指針瓦灶。唯一指針是C++11的特性鸠删,同一對象只能被一個unique_ptr來擁有,禁止進行拷貝構(gòu)造和賦值構(gòu)造操作贼陶。當(dāng)unique_ptr指針對象離開其作用域時刃泡,生命期結(jié)束,自動使用內(nèi)部給定的刪除器(deleter)delete所指向的對象碉怔。所以函數(shù)結(jié)束后烘贴,其申請的資源會自動刪除。
創(chuàng)建好char數(shù)組后撮胧,我們就進行實際的組裝桨踪,再次使用snprintf函數(shù),這次我們知道了需要的長度就是我們前面計算出來的長度芹啥,將前面創(chuàng)建的char數(shù)組放到目的char數(shù)組的參數(shù)位置锻离,進行組裝。前面要計算一次長度的原因就是因為我們并不知道實際使用的時候會組裝多長的字符串墓怀,如果隨意創(chuàng)建一個長度的char數(shù)組汽纠,要么浪費,要么不夠傀履。
最后虱朵,我們用組裝后的結(jié)果char數(shù)組來初始化字符串,并返回,這里只要前面的實際字符卧秘,不要最后的'\0'呢袱。
數(shù)值類型轉(zhuǎn)字符串
C++11以前沒有直接的數(shù)值類型轉(zhuǎn)字符串的函數(shù),這里提供一些:
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函數(shù)來做格式化翅敌,將數(shù)值類型轉(zhuǎn)為char數(shù)組羞福,再轉(zhuǎn)為string類型返回。
各類型轉(zhuǎn)String
還有一種更通用的轉(zhuǎn)String 的方法:
template <class T>
static string ToString(const T& tmp)
{
stringstream ss;
ss << tmp;
return ss.str();
}
做成模板函數(shù)蚯涮,利用stringstream治专,來接收各種類型的參數(shù),返回字符串遭顶。
字符串根據(jù)特定字符拆分成數(shù)組通用函數(shù)
split是其他語言中將字符串轉(zhuǎn)化為數(shù)組的常用函數(shù)张峰,C++中卻沒有,這里提供一個通用函數(shù)棒旗,可以將字符串根據(jù)特定字符拆分成數(shù)組:
#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;
}
函數(shù)接收要拆分的字符串和指定的分隔符字符串喘批,都是const形式,因為不想對其做更改铣揉。返回拆分好的數(shù)組饶深,也就是string類型的vector。
初始化需要的變量后逛拱,在無限循環(huán)中敌厘,使用string的find函數(shù)來找分隔符出現(xiàn)的位置,第二個參數(shù)是指開始找的位置朽合,這里一開始是0俱两。find函數(shù)會返回第一次找到的位置,如果找不到曹步,會返回string::npos宪彩,這里的npos一般是一個size_t的最大值,在字符串中就是字符串的最后位置箭窜。
所以下面如果是string::npos毯焕,那就表示在begin位置后找不到了,直接從begin開始截取子串直到字符串的最后位置磺樱,放到數(shù)組中去纳猫。
如果不是,說明找到了竹捉,因此從begin開始截取需要的長度芜辕,長度由end-begin計算出來。substr函數(shù)接受截取的開始位置和長度块差,長度默認為最大值侵续,也就是到直到字符串末尾倔丈。截取完后,再更新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函數(shù)替換成目的子串,直到找不到為止缸血。注意該函數(shù)并沒有改變源字符串蜜氨,而是復(fù)制了實參,修改后返回捎泻。
去空格(或其他字符)
去除字符串中的空格飒炎,這個用上面的函數(shù)也能實現(xiàn),不過這相當(dāng)于是去除某種字符的通用函數(shù)了:
std::string Trim(const std::string& str, const char target = ' ') {
string retStr = "";
for (size_t i = 0, n = str.length(); i < n; ++i) {
if (str[i] != target) {
retStr += str[i];
}
}
return retStr;
}
簡單的遍歷判斷笆豁,默認為去除空格郎汪,也可以去除其他的字符。
大小寫轉(zhuǎn)換
將字符串中的字母全部轉(zhuǎn)為大寫或者全部轉(zhuǎn)為小寫:
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函數(shù)渔呵,用迭代器遍歷每個字符怒竿,進行修改。這里改的是原字符串扩氢,不需要返回新字符串。
toupper/tolower函數(shù)源碼本身只會對屬于字母的字符進行修改爷辱,非字母字符會原樣返回录豺,所以不需要擔(dān)心字符串中包含非字母的字符。