1. 程式人生 > 其它 >Android開發6年了,2021年大廠程式設計師進階寶典,已拿offer附真題解析

Android開發6年了,2021年大廠程式設計師進階寶典,已拿offer附真題解析

1. C++類的定義和物件的建立

1.1 類的定義

class Student{
public:
    // 內聯
    void say(){
        cout<<name<<"的年齡是"<<age<<",成績是"<<score<<endl;
    }
private:
    string name;
    int age;
    float score;
};

class 關鍵字定義類。Student 是類的名稱,類名的首字母一般大寫,以和其他的識別符號區分開。

類只是一個模板(Template),編譯後不佔用記憶體空間,所以在定義類時不能對成員變數進行初始化,因為沒有地方儲存資料。只有在建立物件以後才會給成員變數分配記憶體。

1.2 在棧上建立物件

class Student
{
    // ... 
}

Student zhangSan;   // 建立物件

1.3 在堆上建立物件

使用 new 關鍵字在堆上建立物件

Student *pStu = new Student;

在棧上創建出來的物件都有一個名字,在堆上分配記憶體,沒有名字,只能得到一個指向它的指標,所以必須使用一個指標變數來接收這個指標,否則以後再也無法找到這個物件了,更沒有辦法使用它。

也就是說,使用 new 在堆上創建出來的物件是匿名的,沒法直接使用,必須要用一個指標指向它,再借助指標來訪問它的成員變數或成員函式。

棧記憶體是程式自動管理的,不能使用 delete 刪除在棧上建立的物件;堆記憶體由程式設計師管理,物件使用完畢後可以通過 delete 刪除。

1.4 訪問類的成員

使用 . 號

Student stu;
stu.name = "小明";
stu.age = 15;
stu.score = 92.5f;
stu.say();

使用物件指標

Student *pStu = new Student;
pStu -> name = "小明";
pStu -> age = 15;
pStu -> score = 92.5f;
pStu -> say();
delete pStu;    //刪除物件

2. 類的成員變數和成員函式

class Student{
public:
    //成員變數
    char *name;
    int age;
    float score;

    //成員函式
    void say();     //函式宣告
};

//函式定義
void Student::say(){
    cout<<name<<"的年齡是"<<age<<",成績是"<<score<<endl;
}

成員函式的定義一般放在類外,在類體中定義的成員函式會自動成為行內函數,在類體外定義的不會。在類體內部定義的函式也可以加 inline 關鍵字,但這是多餘的,因為類體內部定義的函式預設就是行內函數。

3. 類成員的訪問許可權以及類的封裝

C++通過 public、protected、private 三個關鍵字來控制成員變數和成員函式的訪問許可權,它們分別表示公有的、受保護的、私有的,被稱為成員訪問限定符。所謂訪問許可權,就是你能不能使用該類中的成員。

C++ 中的 public、private、protected 只能修飾類的成員,不能修飾類,C++中的類沒有公有私有之分。Java、c# 類有公私有之分。

類的預設許可權是私有的,成員變數大都以 m_開頭。

private 關鍵字的作用在於更好地隱藏類的內部實現,該向外暴露的介面(能通過物件訪問的成員)都宣告為public,不希望外部知道、或者只在類內部使用的、或者對外部沒有影響的成員,都建議宣告為 private。

給成員變數賦值的函式通常稱為 set 函式,它們的名字通常以 set 開頭,後跟成員變數的名字;讀取成員變數的值的函式通常稱為 get 函式,它們的名字通常以 get 開頭,後跟成員變數的名字。

所謂封裝,是指儘量隱藏類的內部實現,只向用戶提供有用的成員函式。

4. 物件的記憶體模型

類是建立物件的模板,不佔用記憶體空間,不存在於編譯後的可執行檔案中;而物件是實實在在的資料,需要記憶體來儲存。物件被建立時會在棧區或者堆區分配記憶體。

編譯器會將成員變數和成員函式分開儲存:分別為每個物件的成員變數分配記憶體,但是所有物件都共享同一段函式程式碼。

物件的大小隻受成員變數的影響,和成員函式沒有關係。

5. 函式編譯原理和成員函式的實現

C++中的函式在編譯時會根據它所在的名稱空間、它所屬的類、以及它的引數列表(也叫引數簽名)等資訊進行重新命名,形成一個新的函式名。這個新的函式名只有編譯器知道,對使用者是不可見的。對函式重新命名的過程叫做名字編碼(Name Mangling),是通過一種特殊的演算法來實現的。

成員函式最終被編譯成與物件無關的全域性函式,因此為了能在成員函式中訪問當前物件的成員變數,C++規定,編譯成員函式時要額外新增一個引數,把當前物件的指標 this 傳遞進去,通過指標來訪問成員變數。

通過物件呼叫成員函式時,不是通過物件找函式,而是通過函式找物件。

6. 建構函式

建構函式名字與類名相同,沒有返回值,不需要使用者顯示呼叫,使用者也不能呼叫,在建立使用者時自動執行。

建構函式必須是 public 屬性的,否則建立物件時無法呼叫。當然,設定為 private、protected 屬性也不會報錯,但是沒有意義。

建構函式的呼叫是強制性的,一旦在類中定義了建構函式,那麼建立物件時就一定要呼叫,不呼叫是錯誤的。

如果有多個過載的建構函式,那麼建立物件時提供的實參必須和其中的一個建構函式匹配;反過來說,建立物件時只有一個建構函式會被呼叫。

一個類必須有建構函式,要麼使用者自己定義,要麼編譯器自動生成。一旦使用者自己定義了建構函式,不管有幾個,也不管形參如何,編譯器都不再自動生成。

呼叫沒有引數的建構函式也可以省略括號。

// 都會呼叫建構函式 Student()
Student stu();
Student stu;
Student *pstu = new Student();
Student *pstu = new Student;

7. 建構函式初始化列表

//採用初始化列表
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){
    //TODO:
}

使用建構函式初始化列表並沒有效率上的優勢,僅僅是書寫方便,尤其是成員變數較多時,這種寫法非常簡單明瞭。

成員變數的初始化順序與初始化列表中列出的變數的順序無關,它只與成員變數在類中宣告的順序有關。

初始化 const 成員變數的唯一方法就是使用初始化列表。

8. 解構函式

解構函式(Destructor)也是一種特殊的成員函式,沒有返回值,不需要程式設計師顯式呼叫(程式設計師也沒法顯式呼叫),而是在銷燬物件時自動執行。

解構函式沒有引數,不能被過載,因此一個類只能有一個解構函式。如果使用者沒有定義,編譯器會自動生成一個預設的解構函式。

class VLA{
public:
    VLA(int len); //建構函式
    ~VLA(); //解構函式
    void input(); //從控制檯輸入陣列元素
    void show(); //顯示陣列元素

private:
    int *at(int i); //獲取第 i 個元素的指標
    const int m_len; //陣列長度,必須使用初始化列表來初始化
    int *m_arr; //陣列指標
    int *m_p;   //指向陣列第 i 個元素的指標
};

VLA::~VLA(){
    delete[] m_arr; //釋放記憶體
}

C++ 中的 new 和 delete 分別用來分配和釋放記憶體,它們與 C 語言中 malloc()、free() 最大的一個不同之處在於:用 new 分配記憶體時會呼叫建構函式,用 delete 釋放記憶體時會呼叫解構函式。建構函式和解構函式對於類來說是不可或缺的,所以在 C++中我們非常鼓勵使用 new 和 delete。

解構函式執行時機

解構函式在物件被銷燬時呼叫,而物件的銷燬時機與它所在的記憶體區域有關。

在所有函式之外建立的物件是全域性物件,位於記憶體分割槽中的全域性資料區,程式在結束執行時會呼叫這些物件的解構函式。

在函式內部建立的物件是區域性物件,它和區域性變數類似,位於棧區,函式執行結束時會呼叫這些物件的解構函式。

new 建立的物件位於堆區,通過 delete 刪除時才會呼叫解構函式;如果沒有 delete,解構函式就不會被執行。

class Demo{
public:
    Demo(string s);
    ~Demo();

private:
    string m_s;
};

Demo::Demo(string s): m_s(s){ }

Demo::~Demo(){ cout<<m_s<<endl; }

void func(){
    //區域性物件
    Demo obj1("1");
}

//全域性物件
Demo obj2("2");

int main(){
    //區域性物件
    Demo obj3("3");

    //new 建立的物件
    Demo *pobj4 = new Demo("4");
    func();
    cout<<"main"<<endl;
    return 0;
}

執行結果:
1
main
3
2
new 建立的物件沒有 delete,不會呼叫解構函式。

9. C++物件陣列

物件陣列中的每個元素都需要用建構函式初始化。具體哪些元素用哪些建構函式初始化,取決於定義陣列時的寫法。

class CSample{
public:
    CSample(){ //建構函式 1
        cout<<"Constructor 1 Called"<<endl;
    }
    
    CSample(int n){ //建構函式 2
        cout<<"Constructor 2 Called"<<endl;
    }
};

int main(){
    CSample arrayl[2];                  // 無參建構函式
    CSample array2[2] = {4, 5};         // 兩個 int 引數的建構函式
    CSample array3[2] = {3};            // 一個 int 引數、一個無參建構函式
    CSample* array4 = new CSample[2];   // 兩個無參建構函式
    delete [] array4;
    return 0;
}

在建構函式有多個引數時,陣列的初始化列表中要顯式地包含對建構函式的呼叫。

class CTest{
public:
    CTest(int n){ } //建構函式(1)
    CTest(int n, int m){ } //建構函式(2)
    CTest(){ } //建構函式(3)
};

int main(){
    //三個元素分別用建構函式(1)、(2)、(3) 初始化
    CTest arrayl [3] = { 1, CTest(1,2) };
    
    //三個元素分別用建構函式(2)、(2)、(1)初始化
    CTest array2[3] = { CTest(2,3), CTest(1,2), 1};
    
    //兩個元素指向的物件分別用建構函式(1)、(2)初始化,pArray[3]沒有初始化
    CTest* pArray[3] = { new CTest(4), new CTest(1,2) };
    return 0;

注意:指標陣列只建立了兩個物件,而不是三個。

10. 成員物件和封閉類

一個類的成員變數如果是另一個類的物件,就稱之為“成員物件”。包含成員物件的類叫封閉類(enclosed class)。

成員物件初始化

成員物件的初始化需要藉助封閉類建構函式的初始化列表。

//汽車類
class Car{
public:
    Car(int price, int radius, int width);
    void show() const;

private:
    int m_price;        // 價格
    Tyre m_tyre;        // 成員物件,輪胎類
    Engine m_engine;    // 成員物件,引擎類
};

Car::Car(int price, int radius, int width): m_price(price), m_tyre(radius, width)/*指明 m_tyre 物件的初始化方式*/{ };

void Car::show() const {
    cout << "價格:" << this->m_price << "¥" << endl;
    this->m_tyre.show();
    this->m_engine.show();
}
  • m_tyre 應以 radius 和 width 作為引數呼叫 Tyre(int radius, int width) 建構函式初始化。
  • m_engine 應該用 Engine 類的無參建構函式初始化。

成員物件的消亡

封閉類物件生成時,先執行所有成員物件的建構函式,然後才執行封閉類自己的建構函式。成員物件建構函式的執行次序和成員物件在類定義中的次序一致,與它們在建構函式初始化列表中出現的次序無關。

當封閉類物件消亡時,先執行封閉類的解構函式,然後再執行成員物件的解構函式,成員物件解構函式的執行次序和建構函式的執行次序相反,即先構造的後析構,這是 C++ 處理此類次序問題的一般規律。

11. this 指標

this 是 C++ 中的一個關鍵字,也是一個 const 指標,它指向當前物件,通過它可以訪問當前物件的所有成員。

class Student{
public:
    void setname(char *name);
    void setage(int age);
    void setscore(float score);
    void show();
private:
    char *name;
    int age;
    float score;
};

void Student::setname(char *name){
    this->name = name;
}

void Student::setage(int age){
    this->age = age;
}

void Student::setscore(float score){
    this->score = score;
}

void Student::show(){
    cout<<this->name<<"的年齡是"<<this->age<<",成績是"<<this->score<<endl;
}

this 是一個指標,要用->來訪問成員變數或成員函式。

this 雖然用在類的內部,但是隻有在物件被建立以後才會給 this 賦值,並且這個賦值的過程是編譯器自動完成的,不需要使用者干預,使用者也不能顯式地給 this 賦值。

  • this 是 const 指標,它的值是不能被修改的,一切企圖修改該指標的操作,如賦值、遞增、遞減等都是不允許的。
  • this 只能在成員函式內部使用,用在其他地方沒有意義,也是非法的。
  • 只有當物件被建立後 this 才有意義,因此不能在 static 成員函式中使用

this 到底是什麼

this 實際上是成員函式的一個形參,在呼叫成員函式時將物件的地址作為實參傳遞給 this。不過 this 這個形參是隱式的,它並不出現在程式碼中,而是在編譯階段由編譯器默默地將它新增到引數列表中。

this 作為隱式形參,本質上是成員函式的區域性變數,所以只能用在成員函式的內部,並且只有在通過物件呼叫成員函式時才給 this 賦值。

成員函式最終被編譯成與物件無關的普通函式,除了成員變數,會丟失所有資訊,所以編譯時要在成員函式中新增一個額外的引數,把當前物件的首地址傳入,以此來關聯成員函式和成員變數。這個額外的引數,實際上就是 this,它是成員函式和成員變數關聯的橋樑。

12. static 靜態成員變數

靜態成員變數是一種特殊的成員變數,它被關鍵字 static 修飾,被該類所有物件所共享。

static 成員變數必須在類宣告的外部初始化,具體形式為:

type class::name = value;

int Student::m_total = 0;

靜態成員變數在初始化時不能再加 static,但必須要有資料型別。被 private、protected、public 修飾的靜態成員變數都可以用這種方式初始化。

static 成員變數的記憶體既不是在宣告類時分配,也不是在建立物件時分配,而是在(類外)初始化時分配。反過來說,沒有在類外初始化的 static 成員變數不能使用。

static 成員變數不佔用物件的記憶體,而是在所有物件之外開闢記憶體,即使不建立物件也可以訪問。

static 成員變數既可以通過物件來訪問,也可以通過類來訪問。

//通過類類訪問 static 成員變數
Student::m_total = 10;

//通過物件來訪問 static 成員變數
Student stu("小明", 15, 92.5f);
stu.m_total = 20;

//通過物件指標來訪問 static 成員變數
Student *pstu = new Student("李華", 16, 96);
pstu -> m_total = 20;

一個完整的例子:

class Student{
public:
    Student(char *name, int age, float score);
    void show();

private:
    static int m_total;     //靜態成員變數
    char *m_name;
    int m_age;
    float m_score;
};

int Student::m_total = 0;  //初始化靜態成員變數

Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){
    m_total++; //操作靜態成員變數
}

void Student::show(){
    cout<<m_name<<"的年齡是"<<m_age<<",成績是"<<m_score<<"(當前共有"<<m_total<<"名學生)"<<endl;
}

int main(){
    //建立匿名物件
    (new Student("小明", 15, 90)) -> show();
    (new Student("李磊", 16, 80)) -> show();
    (new Student("張華", 16, 99)) -> show();
    (new Student("王康", 14, 60)) -> show();
    return 0;
}

之所以使用匿名物件,是因為每次建立物件後只會使用它的 show() 函式,不再進行其他操作。不過使用匿名物件無法回收記憶體,會導致記憶體洩露,在中大型程式中不建議使用。

static 成員變數和普通 static 變數一樣,都在記憶體分割槽中的全域性資料區分配記憶體,到程式結束時才釋放。這就意味著,static 成員變數不隨物件的建立而分配記憶體,也不隨物件的銷燬而釋放記憶體。而普通成員變數在物件建立時分配記憶體,在物件銷燬時釋放記憶體。

靜態成員變數必須初始化,而且只能在類體外進行。

靜態成員變數既可以通過物件名訪問,也可以通過類名訪問,但要遵循 private、protected 和 public 關鍵字的訪問許可權限制。當通過物件名訪問時,對於不同的物件,訪問的是同一份記憶體。

13. static 靜態成員函式

普通成員函式可以訪問所有成員(包括成員變數和成員函式),靜態成員函式只能訪問靜態成員。

靜態成員函式沒有 this 指標,不知道指向哪個物件,無法訪問物件的成員變數,也就是說靜態成員函式不能訪問普通成員變數,只能訪問靜態成員變數。

靜態成員函式與普通成員函式的根本區別在於:普通成員函式有 this 指標,可以訪問類中的任意成員;而靜態成員函式沒有 this 指標,只能訪問靜態成員(包括靜態成員變數和靜態成員函式)。

和靜態成員變數類似,靜態成員函式在宣告時要加 static,在定義時不能加 static。靜態成員函式可以通過類來呼叫(一般都是這樣做),也可以通過物件來呼叫。

14. const 成員變數和成員函式

const 成員變數

const 成員變數的用法和普通 const 變數的用法相似,只需要在宣告時加上 const 關鍵字。初始化 const 成員變數只有一種方法,就是通過建構函式的初始化列表。

const 成員函式

const 成員函式可以使用類中的所有成員變數,但是不能修改它們的值,這種措施主要還是為了保護資料而設定的。

我們通常將 get 函式設定為常成員函式。讀取成員變數的函式的名字通常以 get 開頭,後跟成員變數的名字,所以通常將它們稱為 get 函式。

class Student{
public:
    Student(char *name, int age, float score);
    void show();

    //宣告常成員函式
    char *getname() const;
    int getage() const;
    float getscore() const;

private:
    char *m_name;
    int m_age;
    float m_score;
};

Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){ }

void Student::show(){
    cout<<m_name<<"的年齡是"<<m_age<<",成績是"<<m_score<<endl;
}

//定義常成員函式
char * Student::getname() const{
    return m_name;
}

int Student::getage() const{
    return m_age;
}

float Student::getscore() const{
    return m_score;
}

需要強調的是,必須在成員函式的宣告和定義處同時加上 const 關鍵字。

最後再來區分一下 const 的位置:

  • 函式開頭的 const 用來修飾函式的返回值,表示返回值是 const 型別。
  • 函式頭部的結尾加上 const 表示常成員函式,這種函式只能讀取成員變數的值,而不能修改成員變數的值。

15. const 物件

。一旦將物件定義為常物件之後,就只能呼叫類的 const 成員(包括 const 成員變數和 const 成員函式)。

定義常物件的語法和定義常量的語法類似:

const class object(params);
class const object(params);

當然也可以定義 const 指標:

const class *p = new class(params);
class const *p = new class(params);

16. 友元函式和友元類

藉助友元(friend),可以使得其他類中的成員函式以及全域性範圍內的函式訪問當前類的 private 成員。

友元函式

在當前類以外定義的、不屬於當前類的函式也可以在類中宣告,但要在前面加 friend 關鍵字,這樣就構成了友元函式。友元函式可以是不屬於任何類的非成員函式,也可以是其他類的成員函式。

友元函式可以訪問當前類中的所有成員,包括 public、protected、private 屬性的。

將非成員函式宣告為友元函式

友元函式不同於類的成員函式,在友元函式中不能直接訪問類的成員,必須要藉助物件。

將其他類的成員函式宣告為友元函式

class Address;      //提前宣告 Address 類

//宣告 Student 類
class Student{
public:
    Student(char *name, int age, float score);
    void show(Address *addr);
private:
    char *m_name;
    int m_age;
    float m_score;
};

//宣告 Address 類
class Address{
private:
    char *m_province; //省份
    char *m_city; //城市
    char *m_district; //區(市區)
public:
    Address(char *province, char *city, char *district);
    //將 Student 類中的成員函式 show()宣告為友元函式
    friend void Student::show(Address *addr);
};

//實現 Student 類
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){ }

void Student::show(Address *addr){
    cout<<m_name<<"的年齡是 "<<m_age<<",成績是 "<<m_score<<endl;
    cout<<"家庭住址:"<<addr->m_province<<"省"<<addr->m_city<<"市"<<addr->m_district<<"區"<<endl;
}

//實現 Address 類
Address::Address(char *province, char *city, char *district){
    m_province = province;
    m_city = city;
    m_district = district;
}

一般情況下,類必須在正式宣告之後才能使用;但是一些情況下,只要做好提前宣告,也可以先使用。如上例中提前宣告 Address 類。

友元類

如果將類 B 宣告為類 A 的友元類,那麼類 B 中的所有成員函式都是類 A 的友元函式,可以訪問類 A 的所有成員,包括 public、protected、private 屬性的。

class Address;      //提前宣告 Address 類

//宣告 Student 類
class Student{
public:
    Student(char *name, int age, float score);
    void show(Address *addr);
private:
    char *m_name;
    int m_age;
    float m_score;
};

//宣告 Address 類
class Address{
private:
    char *m_province; //省份
    char *m_city; //城市
    char *m_district; //區(市區)
public:
    Address(char *province, char *city, char *district);
     //將 Student 類宣告為 Address 類的友元類
    friend class Student;
};

//實現 Student 類
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){ }

void Student::show(Address *addr){
    cout<<m_name<<"的年齡是 "<<m_age<<",成績是 "<<m_score<<endl;
    cout<<"家庭住址:"<<addr->m_province<<"省"<<addr->m_city<<"市"<<addr->m_district<<"區"<<endl;
}

//實現 Address 類
Address::Address(char *province, char *city, char *district){
    m_province = province;
    m_city = city;
    m_district = district;
}

friend class Student; // 將 Student 類宣告為 Address 類的友元類

  • 友元的關係是單向的而不是雙向的。如果聲明瞭類 B 是類 A 的友元類,不等於類 A 是類 B 的友元類,類 A 中的成員函式不能訪問類 B 中的 private 成員。
  • 友元的關係不能傳遞。如果類 B 是類 A 的友元類,類 C 是類 B 的友元類,不等於類 C 是類 A 的友元類。

17. 類也是一種作用域

以是物件本身,也可以是物件指標或物件引用)來訪問,靜態成員既可以通過物件訪問,又可以通過類訪問,而 typedef 定義的型別只能通過類來訪問。

class A{
public:
    typedef int INT;
    static void show();
    void work();
};

void A::show(){ cout<<"show()"<<endl; }

void A::work(){ cout<<"work()"<<endl; }

int main(){
    A a;
    a.work();    //通過物件訪問普通成員
    a.show();   //通過物件訪問靜態成員
    A::show(); //通過類訪問靜態成員
    A::INT n = 10; //通過類訪問 typedef 定義的型別
    return 0;
}

一個類就是一個作用域的事實能夠很好的解釋為什麼我們在類的外部定義成員函式時必須同時提供類名和函式名。在類的外部,類內部成員的名字是不可見的。

一旦遇到類名,定義的剩餘部分就在類的作用域之內了,這裡的剩餘部分包括引數列表和函式體。結果就是,我們可以直接使用類的其他成員而無需再次授權了。

函式的返回值型別出現在函式名之前,當成員函式定義在類的外部時,返回值型別中使用的名字都位於類的作用域之外,此時必須指明該名字是哪個類的成員。

// 錯誤寫法
PCHAR A::show(PCHAR str){
    cout<<str<<endl;
    n = 10;
    return str;
}

// 返回值型別 PCHAR 出現在類名之前,所以事實上它是位於 A 類的作用域之外的。
A::PCHAR A::show(PCHAR str){
    cout<<str<<endl;
    n = 10;
    return str;
}

18. class 和 struct 有什麼區別

C 語言中,struct 只能包含成員變數,不能包含成員函式。

C++中,struct 類似於 class,既可以包含成員變數,又可以包含成員函式。

C++中的 struct 和 class 基本是通用的,唯有幾個細節不同:

  • 使用 class 時,類中的成員預設都是 private 屬性的;而使用 struct 時,結構體中的成員預設都是 public 屬性的。
  • class 繼承預設是 private 繼承,而 struct 繼承預設是 public 繼承。
  • class 可以使用模板,而 struct 不能。

19. string 字串詳解

字串的定義

// 使用 string 類需要包含標頭檔案<string>,
#include <string>

int main(){
    string s1;  // 只定義但沒有初始化,預設值是""空字串
    string s2 = "c plus plus";  // string 的結尾沒有結束標誌'\0'
    string s3 = s2;     //使用 s2 進行初始化
    string s4 (5, 's');     // 5個s,也就是"sssss"
    return 0;
}

轉換為 C 風格的字串

有時候必須要使用 C 風格的字串(例如開啟檔案時的路徑),為此,string 類為提供了一個轉換函式 c_str(),該函式能夠將 string 字串轉換為 C 風格的字串,並返回該字串的 const 指標(const char*)。

string path = "D:\\demo.txt";
FILE *fp = fopen(path.c_str(), "rt");

string 字串的輸入輸出

string 類過載了輸入輸出運算子,可以像對待普通變數那樣對待 string 變數,也就是用>>進行輸入,用<<進行輸出。

string s;
cin >> s; //輸入字串
cout << s << endl; //輸出字串

輸入運算子>>預設會忽略空格,遇到空格就認為輸入結束。

訪問字串中的字元

string 字串可以按照下標來訪問其中的每一個字元。string 字串的起始下標仍是從 0 開始。

string s = "1234567890";

for(int i=0,len=s.length(); i<len; i++){
    cout<<s[i]<<" ";
}

字串的拼接

有了 string 類,可以使用+或+=運算子來直接拼接字串。運算子的兩邊可以都是 string 字串,還可以是一個 string 字串和一個字元陣列,或者是一個 string 字串和一個單獨的字元。

string s1 = "first ";
string s2 = "second ";
char *s3 = "third ";
char s4[] = "fourth ";
char ch = '@';
string s5 = s1 + s2;
string s6 = s1 + s3;
string s7 = s1 + s4;
string s8 = s1 + ch;

string 字串的增刪改查

插入字串

insert() 函式可以在 string 字串中指定的位置插入另一個字串,它的一種原型為:

string& insert (size_t pos, const string& str);

更多 insert() 函式的原型和用法請參考:http://www.cplusplus.com/reference/string/string/insert/

刪除字串

erase() 函式可以刪除 string 中的一個子字串。它的一種原型為:

string& erase (size_t pos = 0, size_t len = npos);

提取子字串

substr() 函式用於從 string 字串中提取子字串,它的原型為:

string substr (size_t pos = 0, size_t len = npos) const;

字串查詢

  1. find() 函式
    find() 函式用於在 string 字串中查詢子字串出現的位置,它其中的兩種原型為:

size_t find (const string& str, size_t pos = 0) const;
size_t find (const char* s, size_t pos = 0) const;

  1. rfind() 函式
    rfind() 和 find() 很類似,同樣是在字串中查詢子字串,不同的是 find() 函式從第二個引數開始往後查詢,而 rfind() 函式則最多查詢到第二個引數處,如果到了第二個引數所指定的下標還沒有找到子字串,則返回一個無窮大值 4294967295。

  2. find_first_of() 函式
    find_first_of() 函式用於查詢子字串和字串共同具有的字元在字串中首次出現的位置。

string s1 = "first second second third";
string s2 = "asecond";

int index = s1.find_first_of(s2);

if(index < s1.length())
    cout<<"Found at index : "<< index <<endl;   // 3
else
    cout<<"Not found"<<endl;

return 0;

20. string 的內部實現

在 C 語言中,有兩種方式表示字串,兩種形式總是以\0 作為結束標誌:

  • 一種是用字元陣列來容納字串,例如 char str[10] = "abc",這樣的字串是可讀寫的;
  • 一種是使用字串常量,例如 char *str = "abc",這樣的字串只能讀,不能寫。

C++ string 隱藏了它所包含的字元序列的物理表示。程式設計人員不必關心陣列的維數或\0 方面的問題。

string 在內部封裝了與記憶體和容量有關的資訊。具體地說,C++ string 物件知道自己在記憶體中的開始位置、包含的字元序列以及字元序列長度;當記憶體空間不足時,string 還會自動調整,讓記憶體空間增長到足以容納下所有字元序列的大小。

C++ 標準沒有定義在哪種確切的情況下應該為 string 物件分配記憶體空間來儲存字元序列。string 記憶體分配規則明確規定:允許但不要求以引用計數(reference counting)的方式實現。但無論是否採用引用計數,其語義都必須一致。

string s1("12345");
string s2 = s1;
cout << (s1 == s2) << endl;     // 1,true

s1[0] = '6';
cout << "s1 = " << s1 << endl; //62345
cout << "s2 = " << s2 << endl; //12345
cout << (s1 == s2) << endl;     // 0,false

只有當字串被修改的時候才建立各自的拷貝,這種實現方式稱為寫時複製(copy-on-write)策略。當字串只是作為值引數(value parameter)或在其他只讀情形下使用,這種方法能夠節省時間和空間。

不論一個庫的實現是不是採用引用計數,它對 string 類的使用者來說都應該是透明的。遺憾的是,情況並不總是這樣。在多執行緒程式中,幾乎不可能安全地使用引用計數來實現。