C/C++編程筆記:C++入門知識丨類和對象

本篇要學習的內容和知識結構概覽程序員

類及其實例化

類的定義

將一組對象的共同特徵抽象出來, 從而造成類的概念.編程

類包括數據成員和成員函數, 不能在類的聲明中對數據成員進行初始化數組

聲明類微信

形式爲:函數

class 類名 {
    private:
    私有數據和函數
    public:
    公有數據和函數
    protected:
    受保護的數據和函數
}; // 注意分號 

 

不管是數據成員仍是成員函數, 都是這個類的成員, 都具備一個訪問權限, 若是沒有關鍵字進行修飾, 則默認爲private權限學習

聲明一個類, 像這樣:測試

// 聲明類
class Point {
// 若是沒有修飾符, 默認爲私有的權限
    double x;
    double y;
    
public:
    // 無參構造函數
    Point();
    
    // 有參構造函數
    Point(double a, double b);
    
    // 成員函數
    void display();
};

 

定義成員函數優化

形式爲:this

// :: 爲做用域運算符, 表示這個函數屬於哪一個類spa

返回類型 類名::成員函數名(參數列表) {

    函數體 // 內部實現

}

咱們在上面的聲明類的代碼中, 聲明瞭成員函數, 咱們能夠在類外面定義成員函數, 也就是給出函數體

像這樣:

// 定義成員函數
// 無參構造函數
Point::Point() {}

// 有參構造函數
Point::Point(double a, double b) {
    x = a;
    y = b;
}

// 可使用關鍵字inline將成員函數定義爲內聯函數
inline void Point::display() {
    cout << x << ", " << y << endl;
}

 

若是在聲明類的同時, 在類體內給出成員函數的定義, 則默認爲內聯函數

咱們通常都是在類體內存給出成員函數的定義

像這樣, 完成一個類的聲明和定義

// 聲明類
class Point {
    double x;
    double y;

public:
    // 定義成員函數
    Point() {}

    Point(double a, double b) {
        x = a;
        y = b;
    }

    void display() { // 默認爲內聯函數
        cout << x << ", " << y << endl;
    }
}; 

 

不能在類體內和類體外對數據數據成員賦值

像這樣是不行的:

class Point {
    // 在類體內不能給數據成員賦值
    double x = 2;
    double y = 3;
}

// 在類體外不能給數據成員賦值
// x = 4;
// y = 5;

 

只有產生了具體對象, 這些數據值纔有意義

初始化: 在產生對象時就使對象的數據成員具備指定值, 則稱爲對象的初始化

賦值: 有了對象以後, 對象調用本身的成員函數實現賦值操做

使用類的對象

類的成員函數能夠直接使用本身類的私有成員

類外面的函數不能直接訪問類的私有成員, 而只能經過類的對象使用公有成員函數

定義類對象指針的語法: 類名 * 對象指針名 = 對象地址;

經過對象指針能夠訪問對象的成員: 對象指針名 -> 對象成員名;

像這樣:

// 定義一個類
class Point {
    // 聲明數據成員
    double x;
    double y;
    
public:
    // 聲明而且定義成員函數
    void setXY(double a, double b) {
        x = a;
        y = b;
    }
    
    // 一個類的成員函數能夠訪問本身的私有成員
    void display() {
        cout << x << ", " << y << endl;
    }
};

int main() {
    // 定義對象a
    Point a;
    
    // 定義b爲對象a的引用
    Point & b = a;
    
    // 定義p爲指向對象a的指針
    Point *p = &a;
    
    // 對象和引用, 都使用"."訪問對象的成員
    a.setXY(2, 3);
    b.setXY(4, 5);
    
    // 指針使用"->"訪問對象的成員
    p -> setXY(6, 7);
}

 

構造函數

默認構造函數

一個類若是沒有定義任何構造函數, 編譯器會自動定義一個不帶參數的構造函數, 也就是默認構造函數

好比咱們有一個類Point

則默認構造函數就是這樣:Point::Point() {};

若是一個類提供了構造函數, 系統再也不提供默認構造函數

咱們有一個Point類, 像這樣:

class Point {
    double x;
    double y;
public:
    Point(double a, double b) {
        x = a;
        y = b;
    }
};

 

則咱們就不能在main函數中這樣使用:

int main() {

// Point類有本身定義的構造函數Point(double, double), 因此就沒有默認構造函數Point()
// 這句話調用的是無參構造函數來定義一個對象, 因此編譯錯誤
// 咱們須要給類Point加上無參構造函數    
    Point a;
}

 

咱們想要這樣使用, 則必須手動添加無參數構造函數

像這樣:

class Point {
    double x;
    double y;
    
public:
    // 無參構造函數
    Point(){}

    // 有參構造函數
    Point(double a, double b) {
        x = a;
        y = b;
    }
;

int main() {

// 咱們給類加上自定義無參構造函數, 如今正確編譯
    Point a;
}

 

定義構造函數

構造函數的名字應該與類名同名, 並在定義構造函數時不能指定返回類型, void也不能夠

// 聲明一個類
class Point {
    double x;
    double y;
    
public:
    // 聲明無參構造函數
    Point();

    // 聲明有參構造函數
    Point(double, double);
};

// 在類外定義構造函數
Point::Point(){}

// 第一種定義構造函數
//Point::Point(double a, double b):x(a),y(b){}

// 第二種定義構造函數
Point::Point(double a, double b) {
    x = a;
    y = b;
}

int main() {
    
    // 產生對象
    Point a(2, 3);
}

 

咱們通常都在類的聲明內部進行函數定義

像這樣:

// 定義一個類
class Point {
    // 聲明數據成員
    double x;
    double y;
    
public:
    
    // 無參構造函數
    Point(){};
    
    // 有參構造函數
    Point(double a, double b) {
        x = a;
        y = b;
    }
};

int main() {
    // 無參構造函數 產生對象a
    Point a;
    
    // 有參構造函數 產生對象b
    Point b(2, 3);
    
    // 無參構造函數 產生對象數組arr1
    Point arr1[2];
    
    // 有參構造函數 產生對象數組arr2
    Point arr2[2] = {Point(2, 3), Point(4, 5)};
}

 

注意

不能在程序中顯式地調用構造函數, 構造函數是自動調用的

即不能這樣: Point a.Point(2, 3);

只能這樣: Point a(2, 3);

做用

用來在產生對象的同時, 進行對象的初始化

構造函數和運算符new

new用來創建生存期可控的動態對象, 返回這個對象的指針

new和構造函數一同起做用

過程: 當用new創建動態對象時, 首先分配能夠保存這個類對象的內存空間, 而後自動調用構造函數來初始化這塊內存, 再返回這個動態對象的地址

使用new創建的動態對象只能使用delete刪除, 以釋放所佔空間

像這樣:

// new與無參構造函數
Point * p1 = new Point;
    
// new與有參構造函數
Point * p2 = new Point(2, 3);

 

構造函數的默認參數

若是咱們定義了有參構造函數, 又想使用無參構造函數, 咱們能夠將有參構造函數的參數所有使用默認參數

像這樣:

class Point {
    double x;
    double y;
    
public:
    // 聲明構造函數
    // 有參構造函數
    Point(double a = 0, double b = 0) {
        x = a;
        y = b;
    }
    
    // 成員函數
    void display() {
        cout << x << ", " << y << endl;
    }
};

int main() {
    
    // 產生對象
    Point a;
    a.display();
}

 

複製構造函數

做用: 經過拷貝方式使用一個類的已有對象來創建一個該類的新對象, 通常編譯器會創建一個默認的複製構造函數

像這樣:類名(const 類名 &); // 爲了避免改變原有對象, 使用const來進行修飾

複製構造函數也能夠自定義, 則編譯器再也不調用默認的複製構造函數

像這樣:

// 定義一個類
class Point {
    // 聲明數據成員
    double x;
    double y;
    
public:
    
    // 無參構造函數
    Point(){
        cout << "默認構造函數" << endl;
    };
    
    // 有參構造函數
    Point(double a, double b) {
        x = a;
        y = b;
        cout << "構造函數 " << x << ", " << y << endl;
    }
    
    // 複製構造函數
    Point(const Point & t) {
        x = t.x;
        y = t.y;
        cout << "複製構造函數" << endl;
    }
};

int main() {

    // 使用默認構造函數產生一個對象
    Point a;
    
    // 使用複製構造函數產生一個對象
    Point b(a);
}

 

使用複製構造函數的三種狀況

當用一個類的對象去初始化另外一個對象時, 須要調用複製構造函數

像這樣:

// 經過構造函數實例化對象
Point a(2, 3);
    
// 經過構造函數實例化對象
Point b(a);
    
// 調用成員函數
b.display();

 

若是函數的形參是類的對象, 調用函數時, 進行形參與實參的結合時, 須要調用複製構造函數

像這樣:

// 函數, 用來顯示一個Point對象
void printPoint(Point t) { // 當函數調用時形參t是經過複製構造函數來產生的對象
    t.display();
} // 函數執行完畢後, 調用形參t的析構函數, 釋放內存

int main() {
    // 產生對象a
    Point a(2, 3);
    
    // 調用函數
    printPoint(a);
} // 函數執行完畢後, 調用對象a的析構函數, 釋放內存

 

若是函數的返回值是對象, 當函數調用完成返回時, 須要調用複製構造函數, 產生臨時對象, 並在執行完返回值語句後, 析構臨時對象

// 函數, 獲得一個Point對象
Point getPoint() {
    Point * t = new Point(2, 3);
    return *t; // 產生一個對象
} // 函數執行完畢後, 調用對象t的析構函數, 釋放內存

int main() {
    // 調用函數返回一個類對象, 這裏在接收函數返回的對象時會自動調用複製構造函數(不調用的是編譯器進行了優化)
    Point a = getPoint();
} // 函數執行完畢後, 調用對象a的析構函數, 釋放內存

 

函數參數使用對象的引用不產生副本, 因此當對象做爲函數參數時, 推薦使用對象引用這種方式

析構函數

做用:在對象消失時, 使用析構函數釋放由構造函數分配的內存

定義析構函數

爲了與構造函數區分, 在析構函數前加」~」號,

而且在定義析構函數時, 不能指定返回類型, 即便是void類型也不能夠;

也不能指定參數, 但能夠顯式的說明參數爲void

格式: ~類名(); // 或者 ~類名(void);

代碼像這樣: 

~Point(); // 或者 ~Point(void);

析構函數在對象的生存期結束時自動調用, 而後對象佔用的內存被回收

全局對象和靜態對象的析構函數在程序運行結束以前調用

類對象的數組每一個元素調用一次析構函數

像這樣: 能夠運行該代碼, 查看程序執行過程

// 定義一個類
class Point {
    // 聲明數據成員
    double x;
    double y;
    
public:
    
    // 無參構造函數
    Point(){
        cout << "默認構造函數" << endl;
    };
    
    // 有參構造函數
    Point(double a, double b) {
        x = a;
        y = b;
        cout << "構造函數 " << x << ", " << y << endl;
    }
    
    // 複製構造函數
    Point(const Point & t) {
        x = t.x;
        y = t.y;
        cout << "複製構造函數" << endl;
    }
    
    // 析構函數
    ~ Point() {
        cout << "析構函數" << endl;
    }
};

int main() {

    // 使用默認構造函數產生一個對象
    Point a; // 調用默認構造函數, 產生新對象
    
    // 使用複製構造函數產生一個對象
    Point b(a); // 調用複製構造函數, 產生新對象
    
    // 對象數組
    Point arr[2]; // 調用默認構造函數 2次, 產生兩個新對象
} // 程序結束後, 由於總共產生了4個對象, 因此也會調用4次析構函數

 

析構函數和運算符delete

當使用運算符delete刪除一個動態對象時, 首先爲這個對象調用析構函數, 而後再釋放這個動態對象佔用的內存

像這樣:

// 使用new和默認構造函數產生一個對象
Point * p = new Point;

// 使用delete來釋放內存
delete p;

// 使用new和默認構造函數產生一個對象數組, 數組有兩個對象
Point * p2 = new Point[2];

// 使用delete釋放數組內存
delete []p2;

 

默認析構函數

若是沒有定義析構函數, 編譯器自動爲類產生一個函數體爲空的默認析構函數

像這樣:~ Point(){};

 

成員函數重載及默認參數

成員函數可重載或使用默認參數, 爲了提升可讀性

// 定義一個類
class MyMax {
    
    // 私有成員
    int a, b, c, d;
    
    // 函數: 求兩個數的最大值
    int getMax(int v1, int v2) {
        return v1 > v2 ? v1 : v2;
    }

// 公有成員
public:
    // 函數: 改變數據成員的值
    void setValue(int v1, int v2, int v3 = 0, int v4 = 0) {
        a = v1;
        b = v2;
        c = v3;
        d = v4;
    }
    
    // 函數: 得到全部數據成員裏的最大值 (函數重載)
    int getMax() {
        return getMax(getMax(a, b), getMax(c, d));
    }
};

int main() {
    // 產生對象
    MyMax a;
    
    // 改變數據成員的值
    a.setValue(1, 2, 3, 4);
    
    // 調用成員函數
    cout << a.getMax() << endl;
}

 

this指針

this指針的概念和做用

當一個成員函數被調用時, 系統自動向該函數傳遞一個隱含的參數, 指向調用該函數的對象指針, 名爲this, 從而使用成員函數知道該對哪一個對象進行操做.

做用: 它將對象和該對象調用的成員函數鏈接在一塊兒, 從外部看來, 每一個對象都擁有本身的成員函數, 但處理這些數據成員的代碼能夠被全部的對象共享

this指針的實際形式

咱們通常狀況下都會省略this

// 定義一個類
class Point {
    double x;
    double y;
    
public:
    // 有參構造函數
    Point(double a = 0, double b = 0) {
        x = a;
        y = b;
    }
    
    // 成員函數
    void display() {
        cout << x << ", " << y << endl;
    }

    // 僞代碼
//    void setXY(double x, double y Point * this) {
//        this -> x = x;
//        this -> y = y;
//    }

    // 咱們能夠寫成這樣
//    void setXY(double x, double y) {
//        this -> x = x;
//        this -> y = y;
//    }

    // 可是通常咱們都寫成這樣
    void setXY(double x, double y) {
        x = x;
        y = y;
    }
};

int main() {
    
    // 經過構造函數實例化對象
    Point a(2, 3);
    
    // 調用成員函數
    a.display();
}

 

一個類的對象做爲另外一個類的成員

由於類自己就是一種新的數據類型, 因此一個類的對象能夠做爲另外一個類的成員

像這樣:

// 類: Point, 包含兩個數據成員
class Point {
    double x;
    double y;
    
public:
    // 有參構造函數
    Point(double a = 0, double b = 0) {
        x = a;
        y = b;
    }
    
    // 成員函數
    void display() {
        cout << x << ", " << y << endl;
    }
};

// 類: Line, 包含兩個Point對象
class Line {
    
    // 兩個爲Point類的對象做爲數據成員
    Point startPoint;
    Point endPoint;
    
public:
    // 構造函數
    Line(Point start, Point end) {
        startPoint = start;
        endPoint = end;
    }
    
    // 成員函數
    void display() {
        cout << "起點: ";
        startPoint.display();
        
        cout << "終點: ";
        endPoint.display();
    }
    
    // 返回一個Point對象: 起點
    Point getStartPoint() {
        return startPoint;
    }
    
    // 返回一個Point對象: 終點
    Point getEndPoint() {
        return endPoint;
    }
};

int main() {
    
    // 經過構造函數實例化對象
    Point a(2, 3);
    Point b(4, 5);
    
    // 經過已有對象實例化另外一個對象
    Line lineA(a, b);
    
    // 調用成員函數
    lineA.display();
    
    // 調用一個對象的成員函數, 返回另外一個對象
    Point startPoint = lineA.getStartPoint();
    startPoint.display();
}

 

類和對象的性質

對象的性質

同一類的對象之間能夠相互賦值

Point a(2, 3); Point b = a;複製代碼

可使用對象數組

Point arr[3];複製代碼

可使用指向對象的指針, 使用取地址運算符&將一個對象的地址賦值給該指針

Point p = &a;p -> display();複製代碼

對象做爲函數參數時, 可使用對象, 對象引用和對象指針三種方式, 推薦使用對象的引用做爲函數參數, 可使用const修飾符保證原來的對象不被修改

void print(Point a) {} // 對象做爲函數參數
void print(Point & a) {} // 對象引用做爲函數參數 (推薦使用這一種)
void print(Point * p) {} // 對象指針做爲函數參數

 

一個對象能夠做爲另外一個類的成員

class Point {}
class Line {
    Point startPoint;
    Point endPoint;
}

 

類的性質

使用類的權限

類自己的成員函數可使用類的全部成員(私有和公有和受保護的成員)

類的對象只能訪問公有成員函數

其它函數不能使用類的私有成員, 也不能使用公有成員函數

雖然一個類能夠包含另外一個類的對象, 但這個類也只能經過被包含的類對象使用成員函數, 再訪問數據成員

不徹底類的聲明

class People; // 不徹底的類聲明

People * p; // 定義一個全局變量類指針

只有使用類產生對象時, 才進行內存分配

不徹底類不能進行實例化, 不然編譯出錯, 咱們使用得不是不少

空類

class Empty {};

能夠不包括任何聲明, 也能夠沒有任何行爲, 但能夠產生空類對象

像這樣:

// 定義一個空類
class Empty {
    public:
    Empty(){};
};

int main() {
    // 產生空類對象
    Empty e;
}

 

做用: 在開發大型項目時, 須要在一些類尚未徹底定義或實現時進行先期測試, 保證代碼能正確地被編譯, 固然咱們有時也會給它一個無參構造函數, 來消除警告

類的做用域

聲明類時使用的一對花括號{}造成類的做用域, 也包括類體外成員函數的做用域.

在類做用域中聲明的標識符只在類中可見.

像這樣:

// 定義類
class Example {
    int number;
    
public:
    void setValue(int value) {
        // number在類做用域在內部因此有效, 可使用
        number = value;
    }
    
    void changValue(int);
};

void Example::changValue(<#int#> value) {
    // 類的做用域也包括成員函數做用域, number有效
    number = value;
}

//int a = number; // 錯誤, 由於這是在類做用域的外部, number無效
int number; // 正確, 這代碼定義了一個全局變量number

 

總結

每一個語言的類和對象其實大同小異, 可能一些名字不同, 可能一些格式不同, 可是思想是同樣的, 例如一個對象的產生, 都得申請內存, 而後再對這塊內存進行初始化, 有本身的屬性, 還有本身的行爲. 咱們在學習的時候不要糾結於語言的自己, 要學會總結和本身已經學過的其它語言的異同點, 從而總結出規律, 提煉出本質, 這纔是最主要的. 今天看到一段話送給你們, 大概是這麼說的: 不是咱們變老了就當不了程序員了, 而是由於咱們不想學習了, 因此才顯得咱們變老了, 因此也就當不了程序員了!

自學C/C++編程難度很大,不妨和一些志同道合的小夥伴一塊兒學習成長!

C語言C++編程學習交流圈子,【點擊進入微信公衆號:C語言編程學習基地

有一些源碼和資料分享,歡迎轉行也學習編程的夥伴,和你們一塊兒交流成長會比本身琢磨更快哦!

相關文章
相關標籤/搜索