對 C 語言新手 FAQ 裡面,應建議學習 sprintf 與 sscanf 之學習與使用。這兩支與 printf、scanf 極為相似,只是 printf、scanf 是從標準輸出入做 輸出、輸入動作。而 sprintf 與 sscanf 只是將目的改為字串而已。
舉個常見的例子,要產生 1.txt ~ 100.txt 之字串,該如何產生?
char filename[200]; for(int i=1; i<=100; ++i) sprintf(filename, "%d.txt", i);
類似的,要產生 A001.txt ~ A100.txt 之字串,該如何產生?
char filename[200]; for(int i=1; i<=100; ++i) sprintf(filename, "A%03d.txt", i);
接下來問題就是對於 printf 熟練度而已。相對的,如果拿到一字串為 "1 2 3",怎麼拆解成三個數字?
int a, b, c; char buf[200] = "1 2 3"; sscanf(buf, "%d %d %d", &a, &b, &c);
若拿到的是 "EdisonX Pixnet 200",怎拆解出二個字串一個數字?
char buf[200] = "EdisonX Pixnet 200"; char name[50], net[50]; sscanf(buf, "%s %s %d", name, net, &score);
甚至,拿到的是 "EdisonX,Pixnet,200" 這種 csv 格式,怎拆解出二個字串一個數字?做法不只一種,但可用 scanf (sscanf) 那種 "不正規" 的 正規表達式
sscanf(buf, "%[^,],%[^,],%d", name, net, &score); printf("name:[%s] net:[%s], score:[%d]\n", name, net, score);
於是能表達多少,又回歸到了對 sscanf 引數之熟悉度。
----
上面 sprintf 、 sscanf ,即使在 c++ 引入 cstdio 一樣可辦得到,但一些較有個性的 coder ( 也可能是龜毛 ),很不願意在 C++ 中調用這二個函式 (即使有時用這二個真的會較方便 ),較喜愛的是用 stringstream class。
sprintf、sscanf 會有一定風險存在,特別是 sprintf 要注意一些 over flow 問題 ( 雖我一直認為應不會有 coder 犯這毛病),如下一些例子。
char buf[10]; double x = 123456; sprintf(buf, "%lf", x);
上面例子裡面,實際上 buf 是會存 123456.000000,已超過 buf size。我認為不容易犯,是因就我所知,有經驗 coder ,都會把 buf size 設很大 (小小的也有 100、200),甚至會直接調用一些 macro 來直接設定 buf 最大值。
離題了。
一般接觸 C++後,仍有機會接觸到 sprintf、sscanf,在見識這二隻的威力後,會願意花點時間學習。但學 C 再學 C++,部份 coder 可能認為,sprintf、sscanf 已夠強大,stringstream 沒什麼必要學。這些看個人喜好。
下面講的,是 stringstream 一些用法,在使用此類別時,必須先引入 sstream。要完全搞清楚的話,弄清 istream、ostream、iostream、stringstreambase、stringstream、istringstream、ostringstream 關係,會有不少幫助。
-----
若要將字串 string ss="123" 轉成整數 int x;
string ss="123"; int x; stringstream stream; stream << ss; stream >> x; cout << x << endl;
也可用在 char ss[] = "123" 上面。現反過來,若將整數 int x 轉成字串 string ss
string ss; int x=123; stringstream stream; stream << x; stream >> ss ; cout << ss << endl;
一樣,char ss[200] 也可適用。stringstream 也可產生 1.txt~100.txt 之字串。
string filename; stringstream stream; int i; for(i=1; i<=100; ++i) { stream.clear(); /* 先清除 */ stream << i << ".txt" ; stream >> filename; /* 轉到 filename */ cout << filename << endl; }
注意到,在loop 裡面有清除 flag 動作,stream.clear()。若要清文字內容,是用 stream.str(""),二者不同。要用 stringstream 產生 A001.txt~A100.txt 字串較麻煩一點,要先引入 iomanip
string filename; stringstream stream; int i; for(i=1; i<=100; ++i) { stream.clear(); /* 先清除 */ stream << 'A' << setw(3) << setfill('0') << i << ".txt" ; stream >> filename; /* 轉到 filename */ cout << filename << endl; }
說穿了,最後是在考驗對 cin / cout 與 iomanip 之熟練度。用 stringstream ,對字串 "EdisonX Pixnet 200" 分解出 2 個字串一個整數,
string ss = "EdisonX Pixnet 200"; string name, net; int score; stringstream stream; stream << ss; stream >> name >> net >> score; cout << " name:" << name << endl; cout << " net :" << net << endl; cout << " score:" << score<< endl;
再來,用 stringstream 對 csv 字串 "EdisonX,Pixnet,200" 分解的話,較麻煩的做法
string ss = "EdisonX,Pixnet,200"; string name, net; int score; stringstream stream(ss); getline(stream, name,','); getline(stream, net, ','); stream >> score; cout << name << endl; cout << net << endl; cout << score << endl;
有沒有一行可以寫完的?目前我還做不到 (所以比較常用 scnaf / sscanf 系列 ),做到的話再放上來吧。