天天看點

C++ 字元串、string、char *、char[]、const char*的轉換和差別

1.字元串

字元串本質就是一串字元,在C++中大家想到字元串往往第一反應是std::string(後面簡稱string)

字元串得從C語言說起,string其實是個類,C語言是沒有class的,是以C語言的字元串其實就是字元數組,也就是char [ ] ,例如:

char  str[10];   //定義了一個有十個元素的數組,元素類型為字元char

char  str[10] = {"hello"};  //"h e l l o \0"五個字元賦給str數組, 然後用‘\0’填滿數組剩餘元素

為什麼要加上'\0'?,‘\0’代表空格符,在字元串結尾加上‘\0’,代表字元串已經結束,讀到\0的時候會停下來,不然會沿着記憶體位址一直讀下去,讀到什麼亂七八糟的東西就不知道了,比如會讀到類似 “燙燙燙燙”的東西。。。

那我如果讓數組元素全部為其他字元,不放\0會怎麼樣呢? 可以這樣,如下:

char  str[4] = {"abcd"};   //會報錯

編譯器會報錯,不能把“const char[5]” 類型的值不能用于初始化“char [4]”類型的實體

這裡可以看到,編譯器是把"abcd"作為“abcd\0”來處理的,有五個字元

那如果就隻要裝四個字元呢,可以這樣,如下:

char  str1[4] = { ‘a’ ,'b', 'c', 'd' };          //這樣就沒'\0'了,可是這樣的話,使用str1來表示字元串也失去了意義

輸出str1,std::cout << str1 << std::endl; 會變成這樣:

C++ 字元串、string、char *、char[]、const char*的轉換和差別

為什麼cout << str1 讀取 str1 就能讀取到 abcd呢?

這是因為C中規定數組名 就代表數組所在記憶體位置的首位址,也是 str1[0]的位址,即str = &str[0];

可以了解成讀取str1 的時候其實是在通路 abcd中 a的位址。。

C語言中操作字元串是通過它在記憶體中的存儲單元的首位址進行的,這是字元串的本質

string、char*、char[]、const char *

看一下這四個分别是什麼類型:

int main()
{
	char *p;
	auto s = "111";   //可以看到 "aaa"這樣的類型 其實代表 const char *
	std::string str = "222";
	char a[] = "hello";
	
	std::cout << typeid(p).name()<<  std::endl;
	std::cout << typeid(s).name() << std::endl;
	std::cout << typeid(str).name() << std::endl;
	std::cout << typeid(a).name() << std::endl;
	return ;
}
           

輸出如下:

C++ 字元串、string、char *、char[]、const char*的轉換和差別

1.char *     //字元指針,指向字元的指針

2."aaa"這樣的類型    其實代表 const char *,字元串常量

3.string  是std::basic_string模闆類的執行個體化,是一個類...,string str="aaa"; 其實是 const char *轉class ,string重載了=号,把“aaa”封裝成std::string

4.char  a[8];  // a的類型是 char [8],如果是char  a[6]; 則a的類型就是char [6]   既長度為N的字元數組

string、char*、char[]、const char *互相轉換

如下表:

C++ 字元串、string、char *、char[]、const char*的轉換和差別

轉化規律總結下:

1.轉化成char[],可以用strcpy_s ,或者周遊字元串的方式

string            轉char[] :    strncpy_s(a, string.c_str(), N);  也可以用上圖的周遊string

const char *  轉char[] :    strcpy_s(a, const char *);          也可以用上圖的strncpy_s

char *            轉char[] :   strcpy_s(a,  char *);                   也可以用上圖的strncpy_s

2.char[]變成别的,直接指派

3.轉化為std::string 最簡單,可以直接=, 因為string太強大了,把=号重載了很多遍

4.const char *轉化到 char * 使用const_cast<char *>

5.string轉化為char * 用c_str()

for循環中的陷阱:

char** ppInsId=new char*[50]; 首先解釋下這一句:

char*[50] ,因為[]的優先級高,是以是一個數組,數組元素為指針

new char*[50]  意為開辟一塊記憶體,存放50個char*指針的記憶體空間 ,大小為sizeof(char*)*50 =200 個位元組

而char** ppInsId 是二級指針,因為右邊是數組,而數組的元素為char型指針,是以指向指針的指針,既為2級指針,char** ppInsId就代表指向記憶體首位址,也就是一個char*指針的  指針

對ppInsId 可以用下标通路代表數組第幾個元素,也就是第幾個char *指針

#include<iostream>
using namespace std;
#include <vector>

std::vector<string> vstr;

void makeData(std::vector<string> _vect)
{
   char** ppInsId=new char*[];      //定義了一個二級指針
   for(int i=;i<_vect.size();i++)
   {
      std::string str=_vect[i];
      char *s =const_cast<char*>(str.c_str());
      ppInsId[i]=s;  
   }
   std::cout<<ppInsId[]<<std::endl;     //出了循環,ppInsId[0]和ppInsId[1]都變成了""空
   std::cout<<ppInsId[]<<std::endl;
}

int main()
{
    vstr.push_back("aaaa");
    vstr.push_back("bbbb");
    makeData(vstr);
    return ;
}
           

 這個例子裡,輸出ppInsId[0] 預想是aaaa,  ppInsId[1]預想是 bbbb,實際上卻都是“ ” 空

按理說,每個for{}裡面都新定義了s,兩個s應該不一樣才對,确實在C#,java中是一樣的

原因是char *s 是在for{ }裡定義的,第一次循環時ppInsId[0] 被指派為aaaa,一旦第一次循環結束,就s這個變量和s指向的記憶體立馬被釋放掉了,ppInsId[0] 為空,然後第二次循環又定義了一個新的s,可是這個s的位址又指向了那個位址,也就是兩個s指向的位址是一樣,然後ppInsId[1]都變成了bbbb,因為ppInsId[0]和ppInsId[1]指向的位址一樣 ,s是有兩個,但是兩個for把s的位址剛好是一樣的,然後第二次循環結束,s被釋放ppInsId[0]和ppInsId[1]都變成了空。。。

這裡有個插曲:相同的代碼在vs2017和coldblocks的編譯出來的結果不一樣

vs中出了for循環後,ppInsId[0] ,[1]都為空了,已經被釋放,和我預想的一樣,不知為何codeblocks 還能輸出兩個bbbb

應該是編譯器不一樣導緻的:

vs2017的c++編譯器是:cl.exe,是控制Microsoft C 和C++ 編譯器以及連結器的工具。cl.exe 隻能在支援Microsoft Visual Studio 的作業系統中運作

而codeblock是不安裝編譯器的,需要自己配置,我配置的是Mingou的gdb.exe

那麼怎麼改呢。。

  char *s =const_cast<char*>(str.c_str());

  ppInsId[i]=s;

改為:

char a[10];

strncpy_s(a, str.c_str(), strlen(str.c_str()));

ppInsId[i] = a;

通過數組的方式,在用strcopy 把值拷貝進去

但是改成char a[10]後也有問題,輸出的是兩個bbbb,原因跟上面char *s  一樣,第一次循環結束後釋放了a,然後第二次進來又把a指到了之前的位址,因為ppInsId[0]的位址還是那個,是以兩個都變成了bbbb

是以繼續改,改成在外面定義一個二維數組:

char** ppInsId = new char*[50];   

char a[50][10];

for (int i = 0; i < _vect.size(); i++)

{

        std::string str = _vect[i];

        strncpy_s(a[i], str.c_str(), strlen(str.c_str()));

        ppInsId[i] = a[i];

}

std::cout << ppInsId[0] << std::endl;    

std::cout << ppInsId[1] << std::endl;

這樣既可,完成預想中的p[0]為aaaa,p[1]為bbbb

總結:

 1.一定要使用strcpy()函數等來操作方法c_str()傳回的指針

最好不要這樣:

char* c; string s="1234";

c = s.c_str(); //c最後指向的内容是垃圾,因為s對象被析構,其内容被處理

//應該這樣用:

char c[20];

string s="1234";

strcpy(c,s.c_str()); //這樣才不會出錯,c_str()傳回的是一個臨時指針,不能對其進行操作

2.在循環内部或者一塊作用域内,定義變量要注意被釋放的情況

最好放到循環外定義