C++知識點總結(一)


C++知識點總結

1.面向對象和面向過程

(1)面向過程程序設計

主要特點:主要包括數據結構+算法,分析解決問題所需要的步驟,定義函數實現各個步驟,最后調用函數解決問題

缺點:代碼的可重用性和可維護性較差

(2)面向對象程序設計

概念:面向對象編程(OOP, Object Oriented Programming)其本質是以建立模型體現出來的抽象思維過程和面向對象的方法。 面向對象是一種對現實世界理解和抽象的方法,當解決一個問題的時候,面向對象會把事物抽象成對象的概念,然后給對象賦予一些屬性和方法,讓每個對象去執行自己的方法。

面向對象把數據及對數據的操作方法放在一起,作為一個相互依存的整體——對象。對同類對象抽象出其共性,形成類。類中的大多數數據,只能用本類的方法進行處理。類通過一個簡單的外部接口與外界發生關系,對象與對象之間通過消息進行通信。程序流程由用戶在使用中決定。對象即為人對各種具體物體抽象后的一個概念,人們每天都要接觸各種各樣的對象,如手機就是一個對象。

意義:

①將日常生活中習慣的思維方式引入程序設計中

②將需求中的概念直觀的映射到解決方案中

③以模塊為中心構建可復用的軟件系統

④可以設計出低耦合的系統,提高軟件產品的靈活性、可維護性和可擴展性

面向對象的三大特性:封裝、繼承、多態

封裝 封裝,也就是把客觀事物封裝成抽象的類,並且類可以把自己的數據和方法只讓可信的類或者對象操作,對不可信的進行信息隱藏。

繼承 繼承是指這樣一種能力:它可以使用現有類的所有功能,並在無需重新編寫原來類的情況下對這些功能進行擴展。通過繼承創建的新類稱為「子類」或「派生類」,被繼承的類稱為「基類」、「父類」或「超類」。 要實現繼承,可以通過 繼承和組合 來實現。

多態 多態是允許你將父對象設置成為和一個或更多的他的子對象相等的技術,賦值之后,父對象就可以根據當前賦值給它的子對象的特性以不同的方式運作。簡單說就是一句話:允許將子類類型的指針賦值給父類類型的指針。 實現多態,有兩種方式,覆蓋和重載。兩者的區別在於:覆蓋在運行時決定重載是在編譯時決定並且覆蓋和重載的機制不同

 

2.C到C++的升級

相比C語言,C++中有新增的語法和關鍵字,有類和對象的概念,有重載和重寫的概念,有模板的概念,有STL庫等等。本節主要總結在C++中,一些C語言和C++共有的關鍵字、運算符等的升級。

2.1 struct

C語言中的struct為結構體,定義了一組變量的集合,C語言中struct定義的標識符並不是一種新的類型。

C++中的struct用於定義一個全新的類型。例如:

    struct Student
    {
      const char* name;
      int age;
    }

在C語言中,定義一個struct變量寫法為struct Student s。

在C++中,定義一個struct變量寫法為Student s。

2.2 const

C語言中的const修飾的變量是只讀的,本質還是變量,並會為變量分配存儲空間。

C++中的const聲明時在符號表中放入常量,可以實現真正意義上的常量,一般不會為const常量分配存儲空間,除非使用&操作符取地址或使用extern,表明需要在其它文件中使用。

例如:

 const int n = 5;
 int *p = (int*)&n;
 *p = 1;
 printf("n = %d\n", n);
/*
*C語言編譯器運行結果:n = 1;
*C++編譯器運行結果:n = 5;
*/

更多關於const的總結將在后續章節列舉。

2.3 其它升級

(1)C語言中可以重復定義多個同名的全局變量,而C++中不允許定義多個同名的全局變量。

(2)C語言中可以使用默認類型,但C++不行,C++中的任意標識符都必須顯式的指明類型,例如:

 func1(i){ return i };  //在C語言里沒有函數類型、沒有返回值的函數默認為int類型,在C++中不合法
 func2(i){ return 5 };  //在C語言里為int類型,在C++中不合法

(3)int f()和int f(void)

/* .c */  
 int f()        //表示返回值為int,接收任意參數的函數
 int f(void)    //表示返回值為int的無參函數
/* .cpp */ 
 int f()        //表示返回值為int的無參函數
 int f(void)    //表示返回值為int的無參函數

(4)三目運算符

C語言的三目運算符返回的是變量值,不能作為左值使用。

C++的三目運算符返回的是變量本身,既可作為右值,又可作為左值使用(如果有常量值則不能作為左值)。當三目運算符的可能返回都是變量時,返回的是變量引用,當有常量時,返回的是值。例如:

int a = 1;
int b = 2;
​
(a < b ? a : b) = 3;  //正確,返回a或b的引用,可作為左值
(a < b ? 1 : b) = 4;  //錯誤,返回1或b的值,不可作為左值

(5)register關鍵字請求編譯器將局部變量存儲於寄存器中,在C語言中無法取得register變量的地址,C++中可以取得register變量的地址。

 

3.指針

3.1 指針的使用和本質分析

1.初學指針使用注意事項

1)指針一定要初始化,否則容易產生野指針(后面會詳細說明);

2)指針只保存同類型變量的地址,不同類型指針也不要相互賦值;

3)只有當兩個指針指向同一個數組中的元素時,才能進行指針間的運算和比較操作;

4)指針只能進行減法運算,結果為同一個數組中所指元素的下表差值。

2.指針的本質分析

(1)指針是變量,指針*的意義:

1)在聲明時,*號表示所聲明的變量為指針。

例如:int n = 1; int* p = &n;

這里,變量p保存着n的地址,即p<—>&n,*p<—>n

2)在使用時,*號表示取指針所指向變量的地址值。

例如:int m = *p;

(2)如果一個函數需要改變實參的值,則需要使用指針作為函數參數(傳址調用),如果函數的參數數據類型很復雜,可使用指針代替。

最常見的就是交換變量函數void swap(int* a, int* b)

(3)指針運算符*和操作運算符的優先級相同

例如:int m = *p++;

等價於:int m= *p; p++;

2.指針和數組

1.指針、數組、數組名

如果存在一個數組 int m[3] = {1,2,3};

定義指針變量p,int p = m(這里m的類型為int,&a[0]==>int)

其中,&m為數組的地址,m為數組0元素的地址,兩者相等,但意義不同,例如:

m+1 = (unsigned int)m + sizeof(*m)
​
&m+1= (unsigned int)(&m) + sizeof(*&m) 
​
    = (unsigned int)(&m) + sizeof(m)

m+1表示數組的第1號元素,&m+1指向數組a的下一個地址,即數組元素“3”之后的地址。

等價操作:

 *m[i]←→(m+i)←→(i+m)←→i[m]←→(p+i)←→p[i]**

實例測試如下:

#include<stdio.h>
​
int main()
{
    int m[3] = { 1,2,3 };
    int *p = m;
​
    printf(" &m = %p\n", &m);
    printf(" m = %p\n", m);
    printf("\n");
​
    printf(" m+1 = %p\n", m + 1);
    printf(" &m[2] = %p\n", &m[2]);
    printf(" &m+1 = %p\n", &m + 1);
    printf("\n");
​
    printf(" m[1] = %d\n", m[1]);
    printf(" *(m+1) = %d\n", *(m + 1));
    printf(" *(1+m) = %d\n", *(1 + m));
    printf(" 1[m] = %d\n", 1[m]);
    printf(" *(p+1) = %d\n", *(p + 1));
    printf(" p[1] = %d\n", p[1]);
​
    return 0;
}
​
/*輸出結果如下:
&m = 00AFF8AC
m = 00AFF8AC
​
m+1 = 00AFF8B0
&m[2] = 00AFF8B4
&m+1 = 00AFF8B8
​
m[1] = 2
*(m+1) = 2
*(1+m) = 2
1[m] = 2
*(p+1) = 2
p[1] = 2
*/

2.數組名注意事項

1)數組名跟數組長度無關;

2)數組名可以看作一個常量指針; 所以表達式中數組名只能作為右值使用;

3)在以下情況數組名不能看作常量指針:

  • 數組名作為sizeof操作符的參數

  • 數組名作為&運算符的參數

3.指針和二維數組

一維數組的指針類型是 Type,二維數組的類型的指針類型是Type[n]

4.數組指針和指針數組

①數組指針

1)數組指針是一個指針,用於指向一個對應類型的數組;

2)數組指針的定義方式如下所示:

int (*p)[3] = &m;

②指針數組

1)指針數組是一個數組,該數組里每一個元素為一個指針;

2)指針數組的定義方式如下所示:

int* p[5];

3.指針和函數

1.函數指針

函數的本質是一段內存中的代碼,函數的類型有返回類型和參數列表,函數名就是函數代碼的起始地址(函數入口地址),通過函數名調用函數,本質為指定具體地址的跳轉執行,因此,可定義指針,保存函數入口地址,如下所示:

int funcname(int a, int b);
int(*p)(int a, int b) = funcname;

上式中,函數指針p只能指向類型為int(int,int)的函數

2.函數指針參數

對於函數int funcname(int a, int b);

普通函數調用 int funcname(int, int),只能調用函數int func(int, int)

函數指針調用 intname(*func)(int,int),可以調用任意int(int,int)類型的函數,從而利用相同代碼實現不同功能,

假設有兩個相同類型的函數func1和func2,普通函數調用和函數指針調用方法如下:

printf("普通函數調用\n");
printf("func1 = %d\n", func1(100, 10, 1));
printf("func2 = %d\n", func2(100, 10, 1));
printf("\n");
​
printf("函數指針調用\n");
int(*p)(int, int, int) = NULL;
p = func1;
printf("p = %d\n", p(100, 10, 1));
p = func2;
printf("p = %d\n", p(100, 10, 1));
printf("\n");

需要注意的是,數組作為函數參數的時候,會變為函數指針參數,即:

int funcname( int m[] )<——>int funcname ( int* m );

調用函數時,傳遞的是數組名,即

funcname(m);

3.回調函數

利用函數指針,可以實現一種特殊的調用機制——回調函數。回調函數是指把需要調用的函數指針作為參數傳遞給另一個函數,這個指針會在適當的時機被用來調用其所指向的函數,該函數指針指向的函數可能有多個,這種機制具有非常大的靈活性。

所謂的回調指的是,上層調用底層,底層又回來調用上層,回調函數的機制:

1)調用者不知道需要調用的具體函數

2)被調用的函數不知道何時會被調用

3)在滿足特定條件時,調用者通過函數指針調用被調用的函數

一個簡單的回調函數的例子如下所示:

#include<stdio.h>
​
int func1(int a, int b)
{
    int num = 2 * a * b;
    printf("func1的num = %d\n", num);
    return num;
}
​
int func2(int a, int b)
{
    int num = 4 * a * b;
    printf("func2的num = %d\n", num);
    return num;
}
​
//回調函數
int callback(int(*p)(int, int))
{
    int a = 3, b = 2;
    return p(a, b);
}
​
int main()
{
    callback(func1);
    callback(func2);
}
​
//可以看出,如果不使用函數指針,而使用普通函數調用,則無法使用callback函數來調用func1和func2兩個函數。

4.野指針和懸空指針

指針沒有初始化就被使用,或者指針所指向的變量在指針之前被銷毀,就會產生野指針。

已經釋放過的指針,被稱為懸空指針。

使用者兩種指針都會引發內存錯誤。

 

4.引用

4.1 引用的概念

引用可以看做是變量的一個別名,通過這個別名和原來的名字都能夠找到變量。引用必須在定義的同時初始化,並且以后也要從一而終,不能再引用其它數據,這有點類似於常量(const 變量)

注意:引用在定義時需要添加&,在使用時不能添加&,使用時添加&表示取地址。

在C++中,引用只是對指針進行了簡單的封裝,它的底層依然是通過指針實現的,C++編譯器在編譯過程中使用指針常量作為引用的內部實現,即 Type& varname <——> Type* const name因此引用所占用的內用空間大小與指針相同

4.2 引用的使用場景

引用除了作為變量的別名之外,還可以作為函數的參數和返回值。

1.引用作為函數參數

在定義或聲明函數時,可以將函數的形參指定為引用的形式,這樣在調用函數時就會將實參和形參綁定在一起,讓它們都指代同一份數據。如此一來,如果在函數體中修改了形參的數據,那么實參的數據也會被修改,從而擁有“在函數內部影響函數外部數據”的效果。

2.引用作為函數返回值

引用還可以作為函數返回值,例如:

int &func(int &para1, int &para2);

在將引用作為函數返回值時應該注意一個小問題,就是不能返回局部數據(例如局部變量、局部對象、局部數組等)的引用,因為當函數調用完成后局部數據就會被銷毀,有可能在下次使用時數據就不存在了,C++編譯器檢測到該行為時也會給出警告。

4.3 引用和指針的區別

1.區別

C++中引用具有操作簡單且功能強大、避免指針操作錯誤引發的內存問題等特點,所以在大多數情況下引用可以代替指針。引用和指針的主要區別如下:

(1)指針是一個變量,引用只是一個變量的別名。引用必須在定義時初始化,並且以后也要從一而終,不能再指向其他數據;而指針沒有這個限制,指針在定義時不必賦值,以后也能指向任意數據。

(2)引用不可指向空值,指針可以。

(3)指針可以有多級,但是引用只能有一級,例如,int **p 是合法的,而 int &&r 是不合法的。

(4)sizeof指針得到的是指針的大小,sizeof引用得到的是引用綁定變量的大小

(5)指針和引用的自增(++)自減(--)運算意義不一樣。對指針使用 ++ 表示指向下一份數據,對引用使用 ++ 表示它所指代的數據本身加 1;自減(--)也是類似的道理。

2.使用場景

(1)需要返回函數內局部變量的內存的時候用指針。使用指針傳參需要開辟內存,用完要記得釋放指針,不然會內存泄漏。而返回局部變量的引用是沒有意義的。

(2)類對象作為參數傳遞的時候使用引用,這是C++類對象傳遞的標准方式。

(3)當需要指向某個東西,而且一定專一,絕不會讓其指向其它東西,例如有些函數參數為了避免拷貝可以使用引用,或者實現一個操作符而其語法需求無法由指針達成時,使用引用。大多數情況下還是使用指針。

 

5.const總結

5.1 const含義

const可以修飾基本數據類型,也可以修飾指針變量、引用變量、函數、類對象等。C++中的const可以定義真正意義上的常量。

5.2 const與#define

C++中的const常量與宏定義#define類似,但又有所區別。#define只是單純的文本替換,預處理時就已經替換掉了,而const則是在編譯時替換的,並且編譯器還會對const常量進行類型和作用域的檢查。

5.3 const與volatile、mutable

volatile關鍵字一樣,是一種類型修飾符,用它聲明的類型變量表示可以被某些編譯器未知的因素更改。被volatile修飾的const常量不會進入符號表,不是真正意義上的常量。使用 volatile 聲明的變量的值的時候,系統總是重新從它所在的內存讀取數據。

volatile定義變量的值是易變的,每次用到這個變量的值的時候都要去重新讀取這個變量的值,而不是讀寄存器內的備份多線程中被幾個任務共享的變量需要定義為volatile類型

mutable和const是反義詞,在C++中是為了突破const的限制而設置的,被mutable修飾的變量,將永遠處於可變的狀態。例如,在const成員函數里的非靜態成員變量本身是不可修改的,但如果加上了mutable修飾符,該變量在const函數里也可以修改。

示例:

class A
{
    mutable int f1;
    int f2;
public:
    void func() const
    {
        f1 = 10;    //ok, mutable修飾的變量在const函數里也可以修改
        f2 = 5;     //error, const函數里不可修改變量
    }
}

5.4 const與指針

int const* p ; (指針p本身是變量,p指向的數據為常量

const int* p;(指針p本身是變量,p指向的數據為常量

int* const p;(指針p本身是常量,p指向的數據為變量

const int* const p;(指針p和p指向的數據都是常量

巧記法:左數右指,即const在星號左邊時,指針本身是變量,指針指向的數據是常量;const在星號右邊時,指針本身是常量,指針指向的數據是變量

5.5 const與引用

C++中,const引用讓變量具有只讀屬性,定義示例如下:

const int& b = a;    //const引用讓變量擁有只讀屬性

不能用常量對引用進行初始化,但是可以對const引用進行初始化。 當使用常量對const引用進行初始化時,C++編譯器會為常量值分配空間,並將引用名作為這段空間的別名。

5.6 const與函數

const可以修飾函數的參數,也可以修飾函數的返回值。修飾函數的參數時,該參數在函數內部不可修改,修飾函數的返回值時,函數的返回值不可修改,如下所示:

//修飾函數參數
int func1(const int &a)
{
    a = 5;   //error,const修飾的函數參數在函數內部不可修改
}
//修飾函數的返回值
const int func2()
{
    return 0;    //函數返回值不可修改  
}

5.7 const成員函數、const成員變量和const對象

const可以修飾類的成員函數和成員變量。

1.const成員函數

修飾類的成員函數時,該函數除了靜態成員之外的其它成員不可修改,const 成員函數也稱為常成員函數。同時const成員函數只能調用const成員函數。如下所示:

class A
{
public:
    void func1() const
    {
        a = 10;    //ok
        b = 5;     //error,不可修改非靜態成員變量
    }
    void func2()
    {
        
    }
    void func3() const
    {
        func1();    //ok
        func2();    //error,不可調用非const成員函數
    }
private:
    static int a;
    int b;
}

2.const成員變量

修飾類的非靜態成員變量時,該變量只能在構造函數中初始化;

修飾類的靜態成員變量時,則需要在類的外部初始化。

如下所示:

class B
{
public:
    B():b(5)
    {
        
    }
private:
    static const int a;
    const int b;
}
​
const int B::a = 10;

3.const對象

const修飾的對象成為只讀對象,一旦將對象定義為const對象之后,就只能調用類的 const 成員(包括 const 成員變量和 const 成員函數)了。

 

6.static關鍵字總結

6.1 static修飾局部變量

static修飾的局部變量稱為靜態局部變量,只會被初始化一次。

普通局部變量存放在棧區,靜態局部變量存放在靜態數據區。

普通局部變量的生命周期在語句或者函數執行結束時結束,靜態局部變量會一直持續到程序結束。

6.2 static修飾全局變量和函數

static修飾的全局變量稱為靜態全局變量。普通全局變量的作用域可以是本文件,也可以是其它文件(添加extern聲明),靜態全局變量的作用域只能是本文件。

6.3 static修飾函數

與全局變量類似,staitc修飾的函數的作用域只能是本文件。

6.4 static成員變量和static成員函數

1.static成員變量

static修飾的成員變量稱為靜態成員變量,存放在全局數據區,需要在類外單獨定義。static 成員變量的內存既不是在聲明類時分配,也不是在創建對象時分配,而是在(類外)初始化時分配。反過來說,沒有在類外初始化的 static 成員變量不能使用

靜態成員變量屬於整個類所擁有,可以被任意成員函數訪問,且生命周期不依賴於任何對象。

靜態成員變量既可以通過對象名訪問,也可以通過類名訪問,但要遵循 private、protected 和 public 關鍵字的訪問權限限制。當通過對象名訪問時,對於不同的對象,訪問的是同一份內存。

示例如下:

class A
{
public:
    static int test;  //靜態成員變量
};
​
int A::test = 5;    //類外初始化
​
//通過類訪問 static 成員變量
A::test = 10;
//通過對象來訪問 static 成員變量
A a;
a.test = 20;
//通過對象指針來訪問 static 成員變量
A *pa = new A();
pa -> test = 30;

2.static成員函數

static修飾的成員函數稱為靜態成員函數,靜態成員函數屬於整個類所有,靜態成員函數沒有this指針,只能訪問靜態成員,但非靜態成員函數可以訪問靜態成員函數

靜態成員函數既可以通過對象名訪問,也可以通過類名訪問

靜態成員函數不能被聲明為const、volatile和虛函數

示例如下:

class A
{
public:
    static void func1()
    {
        a = 1;     //ok
        b = 2;     //error,靜態成員函數只能訪問靜態成員
    }
    void func2()
    {
        func1();    //ok
    }
    static void func3() const;      //error,靜態成員函數不能加const限定符
    virtual static void func4();    //errir,靜態成員函數不能被聲明為虛函數
private:
    static int a;
    int b;
};

 

7.C++ inline關鍵字

C++中使用內聯函數代替宏定義#define,用inline關鍵字聲明內聯函數。相比於宏定義,內聯函數會進行參數類型檢查,且具有返回值。內聯函數在編譯時直接將函數代碼嵌入到目標代碼中,省去函數調用的開銷來提高效率,並可以實現重載。

注意,要在函數定義處添加 inline 關鍵字,在函數聲明處添加 inline 關鍵字雖然沒有錯,但這種做法是無效的,編譯器會忽略函數聲明處的 inline 關鍵字。例如:

inline void swap(int *a, int *b);  //只是在聲明處添加inline關鍵字,編譯器會忽略
​
inline void swap(int *a, int *b)   //需要在定義處添加inline關鍵字
{
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
}

 

8.C++強制類型轉換

C++提供了四種強制類型轉換,分別是static_cast、const_cast、reinterpret_cast和dynamic_cast。

語法如下:

xxx_cast<newType>(data)
//newType 是要轉換成的新類型,data 是被轉換的數據。

1.static_cast

用於良性轉換,這樣轉換的風險較低,一般不會發生什么意外,例如:

  • 原有的自動類型轉換,例如 short 轉 int、int 轉 double、const 轉非 const、向上轉型等;

  • void指針和具體類型指針之間的轉換,例如void *int *char *void *等(不能用於基本類型指針間的轉換);

  • 有轉換構造函數或者類型轉換函數的類與其它類型之間的轉換,例如 double 轉 Complex(調用轉換構造函數)、Complex 轉 double(調用類型轉換函數)。

static_cast 不能用來去掉表達式的 const 修飾和 volatile 修飾。換句話說,不能將 const/volatile 類型轉換為非 const/volatile 類型。

2.const_cast

const_cast 用來去掉表達式的 const 修飾或 volatile 修飾。換句話說,const_cast 就是用來將 const/volatile 類型轉換為非const/volatile 類型

強制轉換的目標類型必須是指針或引用,例如:

const int n = 100;
const_cast<int*>(&n);

3.reinterpret_cast

reinterpret_cast 主要用於兩個基本類型指針之間的轉換,以及int 和指針之間的轉換。reinterpret_cast是高度危險的轉換,這種轉換僅僅是對二進制位的重新解釋,不會借助已有的轉換規則對數據進行調整,但是可以實現最靈活的 C++ 類型轉換。

4.dynamic_cast

dynamic_cast 用於在類的繼承層次之間進行類型轉換,它既允許向上轉型(Upcasting),也允許向下轉型(Downcasting)。向上轉型是無條件的,不會進行任何檢測,所以都能成功;向下轉型的前提必須是安全的,要借助 RTTI 進行檢測,所有只有一部分能成功。

 

9.new/delete和malloc/free

9.1 new/delete和malloc/free的區別

(1)malloc/free是C語言庫函數,new/delete是C++操作符,兩者的功能都是從堆空間申請和回收數據。區別在於malloc申請得到的是未初始化的空間,new得到的是初始化過后的空間。

執行new實際上執行兩個過程:

1)調用::operator new,分配未初始化的內存空間(malloc),出現異常則拋出std::bad_alloc異常;

2)使用對象的構造函數對空間進行初始化,出現異常則自動調用delete釋放。

執行delete實際上執行兩個過程:

1)使用析構函數(Obj::~Obj())對對象進行析構

2)調用::operator delete回收內存空間(free)

(2)malloc和free返回的是void類型指針(必須進行類型轉換),new和delete返回的是具體類型指針。

(3)在對非基本數據類型的對象使用的時候,對象創建的時候還需要執行構造函數,銷毀的時候要執行析構函數。而malloc/free是庫函數,是已經編譯的代碼,無法做到這一點。

9.2 malloc、calloc、realloc的區別

以上三個函數的定義如下:

void *malloc(size_t size);
// size為申請空間的大小
​
void *calloc(size_t n, size_t size);
// size為單個類型大小,n為申請的個數,最后申請空間大小為n×size
​
void *realloc(void *p, size_t new_size);
// p為堆上已經存在的空間地址,new_size為額外的空間

malloc和calloc的區別在於:申請空間的方式不同,且malloc函數得到的內存空間是未初始化的,calloc函數得到的內存空間是經過初始化的,其內容全為0

realloc函數的作用為:修改已經申請的內存的大小,用於擴充容量。

如果new_size小於或等於p之前指向的空間大小,那么。保持原有狀態不變。如果new_size大於原來p之前指向的空間大小,那么,系統將重新為p從堆上分配一塊大小為new_size的內存空間,同時,將原來指向空間的內容依次復制到新的內存空間上,p之前指向的空間被釋放。relloc函數分配的空間也是未初始化的

 

9.3 new/delete、new[]/delete[]、allocator

new主要用於申請一個變量,new[]主要用於申請數組。

delete主要用於釋放一個變量,只會調用一次析構函數,delete[]主要用於釋放數組,會逆序調用數組中每個元素的析構函數。

new和delete時都進行兩步操作,new包括內存申請和對象構造,delete包括對象析構和內存釋放。allocator則是將兩步分開,申請時不進行初始化,只有使用的時候才會初始化。

 

10.重載

10.1 函數重載

1.基本概念

函數重載的概念為:用同一個函數名定義不同的函數。

函數重載至少滿足以下條件中的一個條件:

  • 參數個數不同

  • 參數類型不同

  • 參數順序不同

重載函數的函數類型是不相同的,編譯器根據同名函數和參數列表判斷重載,會將所有同名函數中根據以上條件尋找並精確匹配實參,注意正常情況下,函數返回值不能作為重載的依據。重載示例如下:

int func(int a, double b, char c);
int func(int a, double b);            //參數個數不同
int func(int a, double b, double c);  //參數類型不同
int func(int a, char c, double b);    //參數順序不同

2.函數重載和函數指針

正常情況下,函數重載時,編譯器僅通過參數列表(參數個數、參數類型、參數順序)來進行函數選擇,而不需要將函數返回值作為重載的依據,但當有函數指針時,函數返回值(即函數類型)就需要作為函數重載的依據。示例如下:

int func(int a, int b, int c = 0)
{
    ...
}
int func(int a, int b)
{
    ...
}
​
int main()
{
    func(1,2);   //error func函數中有默認參數,兩個參數類型都為(int,int),編譯器無法識別
    //用函數指針調用
    int(*p1)(int a, int b, int c);
    int(*p2)(int a, int b);
    p1(1, 2, 3);
    p2(1, 2);
}

3.類中的函數重載

類中的成員函數、靜態成員函數、構造函數可以被重載。但類中的函數不能和類外的函數構成重載關系,因為函數重載只能發生在同一作用域中。

 

10.2 運算符重載

運算符重載可以擴展操作符的功能,主要通過operator關鍵字實現,語法格式如下:

Type operator xxx(const Type &a, const Type &b)
{
    ...
}
//xxx表示已經存在的運算符

運算符重載的注意事項:

①重載后運算符的含義應該符合原有用法習慣。例如重載+運算符,完成的功能就應該類似於做加法,在重載的+運算符中做減法是不合適的。此外,重載應盡量保留運算符原有的特性。

②C++ 規定,只能重載已有的運算符,不能創造新的運算符,並且操作符重載不能改變運算符的優先級,不能改變運算數的個數。

③以下運算符不能被重載:.、.*、::、? :、sizeof。

④ 運算符可以重載為全局函數, 然后聲明為類的友元。此時函數的參數個數就是運算符的運算數個數,運算符的運算數就成為函數的實參。 運算符也可以重載為成員函數。此時函數的參數個數就是運算符的操作數個數減一,少的這個參數是左運算數,而成員函數的this參數就正好可以作為左運算數。

⑤重載運算符()、[]、->、或者賦值操作符=時,只能將它們重載為成員函數,不能重載為全局函數

⑥運算符重載的本質為函數定義, 使用運算符的表達式就被解釋為對重載函數的調用。

 

11.字符串

C++中提供了string類型用於表示字符串,封裝了字符串的各種操作

1.C++輸入輸出流

圖中這些流類各自的功能分別為:

  • istream:常用於接收從鍵盤輸入的數據;

  • ostream:常用於將數據輸出到屏幕上;

  • ifstream:用於讀取文件中的數據;

  • ofstream:用於向文件中寫入數據;

  • iostream:繼承自 istream 和 ostream 類,因為該類的功能兼兩者於一身,既能用於輸入,也能用於輸出;

  • fstream:兼 ifstream 和 ofstream 類功能於一身,既能讀取文件中的數據,又能向文件中寫入數據。

C++ cin輸入流常用成員方法如表所示:

成員方法名 功能
getline(str,n,ch) 從輸入流中接收 n-1 個字符給 str 變量,當遇到指定 ch 字符時會停止讀取,默認情況下 ch 為 '\0'。
get() 從輸入流中讀取一個字符,同時該字符會從輸入流中消失。
gcount() 返回上次從輸入流提取出的字符個數,該函數常和 get()、getline()、ignore()、peek()、read()、readsome()、putback() 和 unget() 聯用。
peek() 返回輸入流中的第一個字符,但並不是提取該字符。
putback(c) 將字符 c 置入輸入流(緩沖區)
ignore(n,ch) 從輸入流中逐個提取字符,但提取出的字符被忽略,不被使用,直至提取出 n 個字符,或者當前讀取的字符為 ch。
operator>> 重載 >> 運算符,用於讀取指定類型的數據,並返回輸入流對象本身。

C++ cout輸出流常用成員方法如表所示:

成員方法名 功能
put() 輸出單個字符。
write() 輸出指定的字符串。
tellp() 用於獲取當前輸出流指針的位置。
seekp() 設置輸出流指針的位置。
flush() 刷新輸出流緩沖區。
operator<< 重載 << 運算符,使其用於輸出其后指定類型的數據。

2.字符串輸入輸出流

C++標准庫中提供了字符串流類(sstream)用於string的轉換,示例如下:

/*
 * istringstream : 字符串輸入流,可將string轉化為數字
 * ostringstream : 字符串輸出流,可將數字轉化為string
 */
#include<sstream>
​
//string轉換為數字
istringstream iss("99.5");
double num;
iss >> num;
​
//數字轉換為string
ostringstream oss;
oss << 99.5;
string s = oss.str();


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM