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; 會變成這樣:
為什么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 0;
}
輸出如下:
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 *相互轉換
如下表:
轉化規律總結下:
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*[50]; //定義了一個二級指針
for(int i=0;i<_vect.size();i++)
{
std::string str=_vect[i];
char *s =const_cast<char*>(str.c_str());
ppInsId[i]=s;
}
std::cout<<ppInsId[0]<<std::endl; //出了循環,ppInsId[0]和ppInsId[1]都變成了""空
std::cout<<ppInsId[1]<<std::endl;
}
int main() {
vstr.push_back("aaaa");
vstr.push_back("bbbb");
makeData(vstr);
return 0;
}
這個例子里,輸出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.在循環內部或者一塊作用域內,定義變量要注意被釋放的情況
最好放到循環外定義