C++类和对象详解
創建對象
兩種創建對象的方式:一種是在棧上創建,形式和定義普通變量類似;另外一種是在堆上使用 new 關鍵字創建,必須要用一個指針指向它,讀者要記得 delete 掉不再使用的對象。
通過對象名字訪問成員使用點號.,通過對象指針訪問成員使用箭頭->,這和結構體非常類似。
成員變量和函數
類可以看做是一種數據類型,它類似于普通的數據類型,但是又有別于普通的數據類型。類這種數據類型是一個包含成員變量和成員函數的集合。
類的成員變量和普通變量一樣,也有數據類型和名稱,占用固定長度的內存。但是,在定義類的時候不能對成員變量賦值,因為類只是一種數據類型或者說是一種模板,本身不占用內存空間,而變量的值則需要內存來存儲。
類的成員函數也和普通函數一樣,都有返回值和參數列表,它與一般函數的區別是:成員函數是一個類的成員,出現在類體中,它的作用范圍由類來決定;而普通函數是獨立的,作用范圍是全局的,或位于某個命名空間內。
?
但我們一般建議類內只是聲明函數,類外定義。?
class Student{ public://成員變量char *name;int age;float score;//成員函數void say(); //函數聲明 };//函數定義 void Student::say(){cout<<name<<"的年齡是"<<age<<",成績是"<<score<<endl; }在類體中直接定義函數時,不需要在函數名前面加上類名,因為函數屬于哪一個類是不言而喻的。
但當成員函數定義在類外時,就必須在函數名前面加上類名予以限定。::被稱為域解析符(也稱作用域運算符或作用域限定符),用來連接類名和函數名,指明當前函數屬于哪個類。
在類體中和類體外定義成員函數的區別
在類體中和類體外定義成員函數是有區別的:在類體中定義的成員函數會自動成為內聯函數,在類體外定義的不會。當然,在類體內部定義的函數也可以加 inline 關鍵字,但這是多余的,因為類體內部定義的函數默認就是內聯函數。
內聯函數一般不是我們所期望的,它會將函數調用處用函數體替代,所以我建議在類體內部對成員函數作聲明,而在類體外部進行定義,這是一種良好的編程習慣,實際開發中大家也是這樣做的。
當然,如果你的函數比較短小,希望定義為內聯函數,那也沒有什么不妥的。
如果你既希望將函數定義在類體外部,又希望它是內聯函數,那么可以在定義函數時加 inline 關鍵字。當然你也可以在函數聲明處加 inline,不過這樣做沒有效果,編譯器會忽略函數聲明處的 inline
構造函數
#include <iostream> using namespace std;class Student{ private:char *m_name;int m_age;float m_score; public://聲明構造函數Student(char *name, int age, float score);//聲明普通成員函數void show(); };//定義構造函數 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; }int main(){//創建對象時向構造函數傳參Student stu("小明", 15, 92.5f);stu.show();//創建對象時向構造函數傳參Student *pstu = new Student("李華", 16, 96);pstu -> show();return 0; }該例在 Student 類中定義了一個構造函數Student(char *, int, float),它的作用是給三個 private 屬性的成員變量賦值。要想調用該構造函數,就得在創建對象的同時傳遞實參,并且實參由( )包圍,和普通的函數調用非常類似。
在棧上創建對象時,實參位于對象名后面,例如Student stu("小明", 15, 92.5f);在堆上創建對象時,實參位于類名后面,例如new Student("李華", 16, 96)。
構造函數必須是 public 屬性的,否則創建對象時無法調用。當然,設置為 private、protected 屬性也不會報錯,但是沒有意義。
構造函數沒有返回值,因為沒有變量來接收返回值,即使有也毫無用處,這意味著:
- 不管是聲明還是定義,函數名前面都不能出現返回值類型,即使是 void 也不允許;
- 函數體中不能有 return 語句。
構造函數的重載
和普通成員函數一樣,構造函數是允許重載的。一個類可以有多個重載的構造函數,創建對象時根據傳遞的實參來判斷調用哪一個構造函數。
構造函數的調用是強制性的,一旦在類中定義了構造函數,那么創建對象時就一定要調用,不調用是錯誤的。如果有多個重載的構造函數,那么創建對象時提供的實參必須和其中的一個構造函數匹配;反過來說,創建對象時只有一個構造函數會被調用。
默認構造函數
如果用戶自己沒有定義構造函數,那么編譯器會自動生成一個默認的構造函數,只是這個構造函數的函數體是空的,也沒有形參,也不執行任何操作。比如上面的 Student 類,默認生成的構造函數如下:
Student(){}一個類必須有構造函數,要么用戶自己定義,要么編譯器自動生成。
一旦用戶自己定義了構造函數,不管有幾個,也不管形參如何,編譯器都不再自動生成。
實際上編譯器只有在必要的時候才會生成默認構造函數,而且它的函數體一般不為空。默認構造函數的目的是幫助編譯器做初始化工作,而不是幫助程序員。這是C++的內部實現機制,這里不再深究,初學者可以按照上面說的“一定有一個空函數體的默認構造函數”來理解。最后需要注意的一點是,調用沒有參數的構造函數也可以省略括號。對于示例的代碼,在棧上創建對象可以寫作Student stu()或Student stu,在堆上創建對象可以寫作Student *pstu = new Student()或Student *pstu = new Student,它們都會調用構造函數 Student()。
以前我們就是這樣做的,創建對象時都沒有寫括號,其實是調用了默認的構造函數。
初始化列表
構造函數的一項重要功能是對成員變量進行初始化,可以在構造函數的函數體中對成員變量一一賦值,還可以采用初始化列表。
#include <iostream> using namespace std;class Student{ private:char *m_name;int m_age;float m_score; public:Student(char *name, int age, float score);void show(); };//采用初始化列表 Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){//TODO: }如本例所示,定義構造函數時并沒有在函數體中對成員變量一一賦值,其函數體為空(當然也可以有其他語句),而是在函數首部與函數體之間添加了一個冒號:,后面緊跟m_name(name), m_age(age), m_score(score)語句,這個語句的意思相當于函數體內部的m_name = name; m_age = age; m_score = score;語句,也是賦值的意思。
使用構造函數初始化列表并沒有效率上的優勢,僅僅是書寫方便,尤其是成員變量較多時,這種寫法非常簡單明了。
注意,成員變量的初始化順序與初始化列表中列出的變量的順序無關,它只與成員變量在類中聲明的順序有關。
創建對象時系統會自動調用構造函數進行初始化工作,同樣,銷毀對象時系統也會自動調用一個函數來進行清理工作,例如釋放分配的內存、關閉打開的文件等,這個函數就是析構函數。
析構函數
?
析構函數(Destructor)也是一種特殊的成員函數,沒有返回值,不需要程序員顯式調用(程序員也沒法顯式調用),而是在銷毀對象時自動執行。構造函數的名字和類名相同,而析構函數的名字是在類名前面加一個~符號。
注意:析構函數沒有參數,不能被重載,因此一個類只能有一個析構函數。如果用戶沒有定義,編譯器會自動生成一個默認的析構函數。
析構函數的執行時機
析構函數在對象被銷毀時調用,而對象的銷毀時機與它所在的內存區域有關。
在所有函數之外創建的對象是全局對象,它和全局變量類似,位于內存分區中的全局數據區,程序在結束執行時會調用這些對象的析構函數。
在函數內部創建的對象是局部對象,它和局部變量類似,位于棧區,函數執行結束時會調用這些對象的析構函數。
new 創建的對象位于堆區,通過 delete 刪除時才會調用析構函數;如果沒有 delete,析構函數就不會被執行。
this 是?一個關鍵字,也是一個 const指針,它指向當前對象,通過它可以訪問當前對象的所有成員。
所謂當前對象,是指正在使用的對象。例如對于stu.show();,stu 就是當前對象,this 就指向 stu。
注意,this 是一個指針,要用->來訪問成員變量或成員函數。
this 雖然用在類的內部,但是只有在對象被創建以后才會給 this 賦值,并且這個賦值的過程是編譯器自動完成的,不需要用戶干預,用戶也不能顯式地給 this 賦值。
this 原理
this 實際上是成員函數的一個形參,在調用成員函數時將對象的地址作為實參傳遞給 this。不過 this 這個形參是隱式的,它并不出現在代碼中,而是在編譯階段由編譯器默默地將它添加到參數列表中。
this 作為隱式形參,本質上是成員函數的局部變量,所以只能用在成員函數的內部,并且只有在通過對象調用成員函數時才給 this 賦值。
成員函數最終被編譯成與對象無關的普通函數,除了成員變量,會丟失所有信息,所以編譯時要在成員函數中添加一個額外的參數,把當前對象的首地址傳入,以此來關聯成員函數和成員變量。這個額外的參數,實際上就是 this,它是成員函數和成員變量關聯的橋梁。
靜態變量
有時候我們希望在多個對象之間共享數據,對象 a 改變了某份數據后對象 b 可以檢測到。共享數據的典型使用場景是計數,以前面的 Student 類為例,如果我們想知道班級中共有多少名學生,就可以設置一份共享的變量,每次創建對象時讓該變量加 1。
class Student{ public:Student(char *name, int age, float score);void show(); public:static int m_total; //靜態成員變量 private:char *m_name;int m_age;float m_score; };static 成員變量屬于類,不屬于某個具體的對象,即使創建多個對象,也只為 m_total 分配一份內存,所有對象使用的都是這份內存中的數據。當某個對象修改了 m_total,也會影響到其他對象。
注意: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;總結:
1)?一個類中可以有一個或多個靜態成員變量,所有的對象都共享這些靜態成員變量,都可以引用它。
2) static 成員變量和普通 static 變量一樣,都在內存分區中的全局數據區分配內存,到程序結束時才釋放。這就意味著,static 成員變量不隨對象的創建而分配內存,也不隨對象的銷毀而釋放內存。而普通成員變量在對象創建時分配內存,在對象銷毀時釋放內存。
3) 靜態成員變量必須初始化,而且只能在類體外進行。例如:
int Student::m_total = 10;
初始化時可以賦初值,也可以不賦值。如果不賦值,那么會被默認初始化為 0。全局數據區的變量都有默認的初始值 0,而動態數據區(堆區、棧區)變量的默認值是不確定的,一般認為是垃圾值。
4) 靜態成員變量既可以通過對象名訪問,也可以通過類名訪問當通過對象名訪問時,對于不同的對象,訪問的是同一份內存。
靜態成員函數
靜態成員函數與普通成員函數的根本區別在于:普通成員函數有 this 指針,可以訪問類中的任意成員;而靜態成員函數沒有 this 指針,只能訪問靜態成員(包括靜態成員變量和靜態成員函數)。
C++ class和struct區別
cpp中保留了C語言的 struct 關鍵字,并且加以擴充。在C語言中,struct 只能包含成員變量,不能包含成員函數。而在C++中,struct 類似于 class,既可以包含成員變量,又可以包含成員函數。
C++中的 struct 和 class 基本是通用的,唯有幾個細節不同:
- 使用 class 時,類中的成員默認都是 private 屬性的;而使用 struct 時,結構體中的成員默認都是 public 屬性的。
- class 繼承默認是 private 繼承,而 struct 繼承默認是 public 繼承
- class 可以使用模板,而 struct 不能
C++ 沒有拋棄C語言中的 struct 關鍵字,其意義就在于給C語言程序開發人員有一個歸屬感,并且能讓C++編譯器兼容以前用C語言開發出來的項目。
在編寫C++代碼時,我強烈建議使用 class 來定義類,而使用 struct 來定義結構體,這樣做語義更加明確。
總結
- 上一篇: 窄组词和拼音(窄的音节)
- 下一篇: s3c2440移植MQTT