【C++ 字串處理 #2】字串 char string stringstream 相關用法總整理 (內含範例程式碼) 與利用 sprinf, snprinf assign 值的方法

前言

此文章中會整理所有在 C/C++ 字串 的相關用法,包含 char string stringstream
(與利用 sprinf, snprinf,assign值的方法),
其中 char 我們又會分成 char array, char pointer 介紹,以及會介紹他們彼此之間怎麼互相轉換。

我們一共會介紹這些:

  • char array, char pointer (與利用 sprinf, snprinf,assign值的方法)
  • String
  • stringstream

先講結論,我們先觀察以下程式碼與結果

建議先自己觀察以下程式碼與結果,會比我直接講結論學得更快哦!

範例程式碼

#include <iostream>
#include <sstream>

using namespace std;

int main()
{
  char s0[10] = "Hello";
  char s1[] = "Hello";
  const char* s2 = "Hello";
  string s3 = "Hello";
  stringstream s4;

  cout << " ------ s0 testing ------ " << endl;
  cout << typeid(s0).name() << ", " << s0 << endl;
  cout << " ------ s1 testing ------ " << endl;
  cout << typeid(s1).name() << ", " << s1 << endl;
  cout << " ------ s2 testing ------ " << endl;
  cout << typeid(s2).name() << ", " << s2 << endl;
  cout << " ------ s3 testing ------ " << endl;
  cout << typeid(s3).name() << ", " << s3 << endl;
  cout << " ------ s4 testing ------ " << endl;
  cout << typeid(s4).name() << endl;
  s4 << s3;
  cout << typeid(s4).name() << endl;
  cout << typeid(s4.str()).name() << ", " << s4.str() << endl;
  cout << typeid(s4.str().c_str()).name() << ", " << s4.str().c_str() << endl;

    return 0;
}

編譯與結果

> g++ test.cpp  -std=c++11 -o a.out &&  ./a.out | c++filt --types;
 ------ s0 testing ------
char [10], Hello
 ------ s1 testing ------
char [6], Hello
 ------ s2 testing ------
char const*, Hello
 ------ s3 testing ------ 
std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, Hello
char const*, Hello
 ------ s4 testing ------
std::__cxx11::basic_stringstream<char, std::char_traits<char>, std::allocator<char> >
std::__cxx11::basic_stringstream<char, std::char_traits<char>, std::allocator<char> >
std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, Hello
char const*, Hello

觀察與結論

Data type主要特色
Char array[C] 基本的資料型態,一開始就決定資料儲存使用的空間大小
Char pointer[C] 基本的指標型態,資料所使用的儲存空間大小不一定
String[C++] 特別定義做字串處理用的資料型態
stringstream[C++] 主要功能並不是「作為字串使用」,只是作為資料型態轉換過度的橋樑

整理

char array, char pointer

這是 C/C++ 最基本的資料型態,
相信會點進來的讀者對這都已經有基本概念了。

我們主要知道,char 是代表「一個字元」的儲存。
所以要儲存字串,要使用「 char array 」或是「 char pointer 」,
我們才會得到「一個字元以上」的儲存空間!

範例程式碼 - 遍歷字串 - char array, char pointer

我們使用「#include <string.h>」這個標頭檔,
這個標頭檔定義了 「strlen()」的方法,使我們能使用來判斷 char array 長度。

#include <iostream>
#include <string.h>

using namespace std;

int main()
{
  int a = 2;
  float b = 3.0;
  char s0[10] = "Hello";
  char s1[] = "Hello";
  const char* s2 = "Hello";

  for(int i=0; i < strlen(s0); i++)
  {
    cout << s0[i] << endl;
    cout << *(s0+i) << endl;
  }

  for(int i=0; i < strlen(s1); i++)
  {
    cout << s1[i] << endl;
    cout << *(s1+i) << endl;
  }

  for(int i=0; i < strlen(s2); i++)
  {
    cout << s2[i] << endl;
    cout << *(s2+i) << endl;
  }
    return 0;
}

編譯與結果 - char array, char pointer

> g++ test.cpp  -std=c++14 -o a.out &&  ./a.out
H
H
e
e
l
l
l
l
o
o
H
H
e
e
l
l
l
l
o
o
H
H
e
e
l
l
l
l
o
o

使用注意事項: 如何複製 char array?

只要是不同大小,都沒有辦法直接進行轉換。

以下皆會跳錯:

s1 = s0;
s0 = s1;

datatype_test.cpp:59:8: error: incompatible types in assignment of ‘char [10]’ to ‘char [6]   s1 = s0;
        ^~
datatype_test.cpp:60:8: error: incompatible types in assignment of ‘char [6]’ to ‘char [10]   s0 = s1;
        ^~

正確使用方式 - strcpy

strcpy(dst, src); // 從 src (source) 複製到 dst (destination)
  • 可使用範圍:
  • char array <—> char array
  • char array —> char pointer (只可以單向)

特別注意:從大複製到小,會有問題,「且不會跳 error 」。 Debug 時需特別注意。

strcpy(s0, s1);
strcpy(s0, s2);
strcpy(s2, s0); // error char 無法轉成 char*

將 char array 轉成 char pointer (char array to char pointer)

pointer 本身就是資料的指標,我們只需要將資料的指標指向他就完成囉!

#include <stdio.h>
#include <string.h>
#include <iostream>

using namespace std;

int main()
{
  char s0[30] = "Hello World!";
  char* s1 = s0;
  cout << s1 << endl;
  char* s2 = &s0[0];
  cout << s2 << endl;
}

編譯與結果

> g++ test4.cpp   -o a.out &&  ./a.out
Hello World!
Hello World!

如何 assign 值 進入 char array ?

  • 主要有兩種方法:
    • strcpy
    • sprintf / snprintf
  • - 其中「sprintf / snprintf」的差別只在於空間的使用控制,詳細可以參考以下文章:

    https://wongwongnotes-github-io.pages.dev/cplusplus/cpp_string_format/cpp-printf/

    範例程式碼 - assign 值 進入 char array

    #include <stdio.h>
    #include <string.h>
    
    using namespace std;
    
    int main()
    {
      char buffer1[10];
      char buffer2[10];
    
      strcpy( buffer1, "abcdea" );
      puts(buffer1); 
      sprintf( buffer2, "abcdea" );
      puts(buffer2); 
    
        return 0;
    }
    

    編譯與結果:

    > g++ test4.cpp   -std=c++14 -o a.out &&  ./a.out
    abcdea
    abcdea
    
  • 更多「轉換方法」的總整理,我有整理在另外一篇文章:

    https://wongwongnotes-github-io.pages.dev/cplusplus/convert-char-string-stringstream/

  • 小提醒:在 C++ 裡面,單引號「’」與雙引號「"」是有嚴格的差別的

    小提醒:在 C++ 裡面,單引號「’」與雙引號「"」是有嚴格的差別的
    (不像是隔壁棚的 python)
    單引號「‘a’」代表的 char,是一個字元
    雙引號「“a”」代表的是 string,是字串

    範例 - C++ 中 單引號「‘a’」,與 雙引號「“a”」的差別:

    char *test1='a';
    char *test2="a";
    

    編譯與結果 - 這邊為故意讓他跳錯,我們要看的是類別:

  • 注意以下的錯誤訊息:
    • 「*test1=’a’;」的錯誤說明,我們嘗試將 char 轉成 char *
    • 「*test2=”‘a”;」的錯誤說明,我們嘗試將 string 轉成 char *
  • 我們得知:
    • 單引號「’a’」的類別為 char
    • 雙引號「”a”」的類別為 string
    • 「*test1」的類別為 char *
    • 「*test2」的類別為 char *
  • > g++ test3.cpp   -std=c++14 -o a.out &&  ./a.out
    test3.cpp: In function ‘void test_snprintf()’:
    test3.cpp:11:15: error: invalid conversion from ‘char’ to ‘char*’ [-fpermissive]
       char *test1='a';
                   ^~~
    test3.cpp:14:15: warning: ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings]
       char *test2="a";
                   ^~~
    

    如何 assign 值 進入 char pointer ?

    範例程式碼 - assign 值 進入 char pointer

    依照 pointer 的概念,我們宣告一個新位置,
    並將舊位置指定到新位置,即可完成任務。

    #include <stdio.h>
    #include <string.h>
    
    using namespace std;
    
    int main()
    {
      char *buffer1;
      char *buffer2 = "abcdea";
    
      buffer1 = buffer2;  // assign pointer to pointer
      puts(buffer1); 
      puts(buffer2); 
    
        return 0;
    }
    

    編譯與結果:

    可以注意到有跳出 warning,主要是因為宣告 char * ,
    我們會建議宣告時使用 const char *,表示我們宣告的是常數。

    > g++ test4.cpp   -o a.out &&  ./a.out
    test4.cpp: In function ‘int main()’:
    test4.cpp:9:19: warning: ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings]
       char *buffer2 = "abcdea";
                       ^~~~~~~~
    abcdea
    abcdea
    

    String

    string 是 C++ 特別定義做字串處理用的資料型態。

    範例程式碼 - 遍歷字串 - string

    string 已經是 c++ 內建的資料型態,我們不需要另外 include 其他標頭檔。

    想要得到 string 長度有兩種方法,size() 與 length(),

    據官方文件所述,兩者功能沒有差別。只是就表達程式碼語意來說,可以自行選擇哪個能讓人比較好懂。

    #include <iostream>
    
    using namespace std;
    
    
    int main()
    {
      string s3 = "Hello";
    
      for(int i = 0; i < s3.length(); i++)
      {
        cout << s3[i] << endl;
      } 
    
      for(int i = 0; i < s3.size(); i++)
      {
        cout << s3[i] << endl;
      } 
    
        return 0;
    }
    

    編譯與結果 - string

    > g++ test2.cpp  -std=c++14 -o a.out &&  ./a.out 
    H
    e
    l
    l
    o
    H
    e
    l
    l
    o
    

    char pointer 與 string 如何轉換?

    相信這也是讀者接下來會想問的問題,(不然這至少也是我會想問的問題XD)
    我們就把問題拆開來看吧!

    將 char pointer 轉成 string (char pointer to string)

    很簡單,直接等於「=」就好了!
    記法的話,也許你可以想像 Char 比較基本,string 是後來的,作法當然很簡單!
    我們可以用另外一篇文章提供的 type_name 方法來檢驗。

    • 可參考:

    https://wongwongnotes-github-io.pages.dev/cplusplus/cpp_basic/cpp-data-type/

    範例程式碼 - 將 string 轉成 char pointer (string to char pointer)

    #include <iostream>
    using namespace std;
    
    void show_type(auto s)
    {
      cout << typeid(s).name() << ", " << s << endl;
    }
    
    int main()
    {
      char src[] = "HelloWorld";
      string dst;
    
      dst = src;
      show_type(src);
      show_type(dst);
      cout << dst << endl;
    
        return 0;
    }
    

    編譯與結果

    注意結果的「char*」!!!

    > g++ test2.cpp  -std=c++14 -o a.out &&  ./a.out | c++filt --types;
    char*, HelloWorld
    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, HelloWorld
    HelloWorld
    

    將 string 轉成 char pointer (string to char pointer)

    其實也很簡單,都已經幫我們做好了,只需要下「c_str()」即可。

    範例程式碼 - 將 string 轉成 char pointer (string to char pointer)

    #include <iostream>
    #include <string.h>
    
    using namespace std;
    
    void show_type_and_content(auto s)
    {
      cout << typeid(s).name() << ", " << s << endl;
    }
    
    
    int main()
    {
      string src = "HelloWorld";
      char dst1[1024];
      char *dst2 = new char [src.length()+1];
    
      strcpy (dst1, src.c_str());
      strcpy (dst2, src.c_str());
    
      show_type_and_content(src);
      show_type_and_content(src.c_str());
      show_type_and_content(dst1);
      show_type_and_content(dst2);
    
        return 0;
    }
    

    編譯與結果

    注意結果的「char const*」、「char*」!!!

    > g++ test2.cpp  -std=c++14 -o a.out &&  ./a.out | c++filt --types;
    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, HelloWorld
    char const*, HelloWorld
    char*, HelloWorld
    char*, HelloWorld
    

    stringstream

    stringstream 主要功能並不是「作為字串使用」,只是作為資料型態轉換過度的橋樑。

    例如:int 轉換成 stringstream 再轉換成 string。 (反過來也行~)

    使用時,需 include 標頭檔「#include

    範例程式碼 - stringstream, 與將 stringstream 轉成 string, char pointer (stringstream to string, char pointer)

    #include <iostream>
    #include <sstream>
    
    using namespace std;
    
    int main()
    {
      string s3 = "Hello";
      stringstream s4;
    
      cout << typeid(s4).name() << endl;
      s4 << s3;
      cout << typeid(s4).name() << endl;
      cout << typeid(s4.str()).name() << ", " << s4.str() << endl;
      cout << typeid(s4.str().c_str()).name() << ", " << s4.str().c_str() << endl;
    
      return 0;
    }
    

    編譯與結果 - string

    > g++ datatype_test.cpp  -std=c++14 -o a.out &&  ./a.out | c++filt --types;
    std::__cxx11::basic_stringstream<char, std::char_traits<char>, std::allocator<char> >
    std::__cxx11::basic_stringstream<char, std::char_traits<char>, std::allocator<char> >
    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, Hello
    char const*, Hello
    

    Reference