從概念上來講:類是現實中具備共同特色食物的抽象。程序員
這裏舉一個簡單的例子:咱們如今須要定義的一我的的類。因此咱們要找出人和人的共同點(或者說人這個物種的共性),人有身高,體重,年齡,性別...這些就是人這個類的屬性(成員變量)。人還會吃飯,走路,跳躍...這些就是人的方法(成員函數)。這樣就能夠抽象出人這個類。編程
當咱們抽象出人這個類以後咱們具體要怎麼實現這個類呢?
請看以下語句數組
class Person { ... };
以上就是類 的基本語法:class 類的名字 {...}; 這裏大括號最後有分號。
咱們在大括號裏面寫成員變量和成員方法。函數
class Person { public: int age;//人的年齡 void eat()//人吃飯的成員函數 { std::cout<<"人在吃飯"<<std::endl; } void run()//人跑步的成員函數 { std::cout<<"人在跑步"<<std::endl; } };
咱們也能夠在類外寫函數定義,類內只寫函數聲明。不過這時咱們須要用到做用域標識符(::)性能
class Person { public: int age;//人的年齡 bool sex;//人的性別 void eat();//人吃飯的成員函數 void run();//人跑步的成員函數 }; void Person::eat()//人吃飯的成員函數 { std::cout<<"人在吃飯"<<std::endl; } void Person::run() { std::cout<<"人在跑步"<<std::endl; }
在這裏須要強調的是,定義位於類內的函數自動成爲內聯函數。若是但願類外定義的函數也能夠內聯的話,須要手動加inline關鍵字。優化
這裏和結構體很像Person xiaoming;
這裏p就是Person的對象,他具備Person的屬性和方法。簡單的來講,Person至關於人這個物種,而p這個對象就是具體到誰。
就像結構體那樣:this
xiaoming.run(); xiaoming.eat();
在這裏所建立的每個對象都有一個本身的存儲空間,用於存儲本身的成員變量和成員函數。可是多有對象都共享同一個類。debug
這裏先簡單的瞭解一下,以後會在繼承中詳細指出。(友元能夠暫時無視)
private: 只能由該類中的函數、其友元函數訪問,不能被任何其餘訪問,該類的對象也不能訪問. 指針
protected: 能夠被該類中的函數、子類的函數、以及其友元函數訪問,但不能被該類的對象訪問 code
public: 能夠被該類中的函數、子類的函數、其友元函數訪問,也能夠由該類的對象訪問
注意:這裏要說明一個更好的方式來進行編程。把成員變量所有寫進private中,來實現數據的封裝。使用public的get set方法來得到和修改這個變量。
C++和Java不同,其中一個重要的區別就是:C++編譯器強制要求程序員進行內存管理,而Java的內存管理是由JVM(Java虛擬機)代替程序員完成。因此,這裏引出一個重要的概念(和C語言同樣):內存管理對於C/C++開發者來講是一個永恆的話題。
class Person { public: Person() { std::cout<<"我是構造函數"<<std::endl; } };
class Person { public: Person() { cout<<"默認構造函數"<<endl; } Person(int a) { age = a; cout<<"有參構造函數"<<endl; } };
通常狀況,編譯器會提供默認的構造函數,若是程序員手動寫了有參的構造函數,那麼編譯器便不會提供默認的構造函數。
void test() { //構造函數調用方式 //括號法調用 Person p1(1); p1.age = 10; Person p2(p1); cout<<"p2年齡:"<<p2.Age<<endl; Person p3;//默認構造函數不加(); Person p3() 編譯器任務這行是函數聲明 //顯示法調用 Person p4 = Person(100);//Person(100)叫匿名對象 Person p5 = Person(p4); Person(100);//若是編譯器發現了匿名對象,那麼在這行代碼結束後,就釋放這個對象 }
咱們能夠經過以上方法對構造函數進行調用。
注意:上述語句中出現匿名對象。顧名思義,匿名對象就是沒有名字,這種對象在這行代碼結束後,就釋放這個對象。(匿名對象未來會有伏筆,如今請記住!)
【補充】初始化列表:這裏是有參構造函數的一種寫法:
class foo { public: foo(string s, int i):name(s), id(i){} ; // 初始化列表 //相應的含義:把s的值賦值給name,把i的值賦值給id private: string name ; int id ; };
使用初始化列表的緣由,主要是由於性能。對於內置類型,如int, float等,使用初始化類表和在構造函數體內初始化差異不是很大,可是對於類類型來講,最好使用初始化列表,使用初始化列表少了一次調用默認構造函數的過程,這對於數據密集型的類來講,是很是高效的。因此,咱們能使用初始化列表的時候儘量使用初始化列表。
固然,有的時候咱們也必須使用初始化列表:
class Person { public: Person(const Person& p) { Age = p.Age; cout<<"拷貝構造函數"<<endl; } };
所謂拷貝構造函數就是把構造函數複製了一份
有關拷貝構造函數的調用時機:
class Person { public: Person() { cout<<"默認構造函數調用"<<endl; } Person(int a) { cout<<"有參構造函數調用"<<endl; } Person(const Person& p) { cout<<"拷貝構造函數調用"<<endl; } ~Person() { cout<<"析構函數調用"<<endl; } int Age; }; //用已經建立好的的對象初始化新的對象 void test() { Person p1 ; p1.Age = 10; Person(p1); } //用值傳遞的方式給函數參數傳值 void doWork(Person p1) { } void test02() { Person p; p.Age; doWork(p); } //用值的方式返回局部對象 Person doWork2() { Person p1; return p1; } void test03() { Person p = doWork(); } //請注意debug模式下和release模式下的區別 //Release模式下的優化: /*(節省一份開銷) Person p;//不調用默認構造 doWork2(p); void doWork2(Person &p) { Person p1;//調用默認構造 }
class Person { public: ~Person() { std::cout<<"我是析構函數"<<std::endl; } };
析構函數的做用其實是作垃圾回收的。具體的內容再後面的內存管理部分會詳細說明。
這裏只想說明一下編譯器會爲程序員默認的提供:無參構造函數,析構函數,拷貝構造函數,重載了賦值運算符(爲知識完整性不得不說的,後面會細講)
C++的數據和操做是分開存儲的,而且每個非內聯函數成員只會誕生出一份函數實例,即多人同類型的對象公用一塊代碼
這一塊代碼是如何區分是哪一個對象調用本身呢?
引入this指針,this指針是指向被調用的成員函數所屬的對象
C++規定,this指針是隱含在對象成員函數內的一種指針。當對象被建立後,它的每個成員函數都含有一個系統自動生成的隱含指針this,用來保存這個對象的地址。雖然咱們 沒有寫上this指針,編譯器在編譯的時候會自動加上。this也稱指向本對象的指針。this指針並非對象的一部分,也不會影響sizeof(對象)的結果。
this指針是C++實現封裝的一種機制,他將對象和該對象調用的成員函數鏈接在一塊兒。從外部看來,每個對象都擁有本身的成員函數。通常狀況下,並不寫this,而是讓系統系進行默認配置。
this指針永遠指向當前對象。this指針是一種隱含的指針,它隱含於每個非靜態的成員函數中
class Person { public: Person(int age) { this->age = age;//編譯器無法區分這幾個age //區分有兩種辦法:1.換名字 2.this指針 } //對比年齡 void compareAge(Person &p) { if(this->age = p.age)//默認給加了this,不寫也行 { cout<< "年齡相等"<<endl; } else { cout<<"年齡不等"<<endl; } } //年齡相加 Person& PlusAge(Person &p)//在這裏若是去掉&,它將從一個引用傳遞變成值傳遞 { this->age += p.age; return *this;//指向本體 } int age; }
上面的語句不難理解,簡單來講就是爲了區分名字。可是最後Person& PlusAge(Person &p)
可能不少人感受奇怪,這實際上是this指針的第二個用途——鏈式編程。
p1.PlusAge(p2).PlusAge(p2).PlusAge(p2);//鏈式編程 //若是變成值傳遞,每一次調用都是一個臨時的空間裏進行操做。並不會鏈式相加。這取決於你的需求
這種寫法對於有Java基礎的人來講必定不陌生。
class Person { public: Person() { //構造中修改屬性 //this永遠指向本體 至關於 Person * const this //雖然指針不能修改,可是能夠修改指針指向的值。 //若是我不但願修改指針指向的值,則 const Person * const this this->A = 0; this->B = 0; } //常函數 void showInfo() const //不容許修改指針指向的值 { //this->C = 1000; // const Person * const this如何操做?,在函數()後面+const cout<<"A是"<<this->A<<endl; cout<<"B是"<<this->B<<endl; cout<<"C是"<<this->C<<endl; } int A; int B; //若有即使是常函數也要修改的需求,可使用關鍵字mutable mutable int C; }
固然,若是你真的理解了this指針的原理,則還能夠以上這種C++風格的代碼改寫成C風格
//C++風格 void showInfo() const { ... } //C風格 void showInfo(cosnt Person * this) { ... }
若是這樣改寫的話,在調用時也要改寫
//C++風格 p.showInfo(); //C風格 p.showInfo(&p);
有關對象數組,也是顧名思義,就是一個元素是對象的數組Person xiaoxue[4];
這裏只有須要注意的地方:要建立的對象數組,則這個類必須有默認構造函數
若是但願使用構造函數來初始化對象數組,則:
Person p[2] = { Person(10),//10歲 Person(12)//12歲 }; //也可使用不一樣的構造函數 Person p[2] = { Person(10),//10歲 Person()//使用默認構造函數 };
咱們以前就知道全局做用域和局部做用域。在這裏C++引入新的做用域——類做用域
類中定義的名稱的做用域是整個類,做用域爲整個類的名稱只有在該類的做用域中是已知的,類外是不可知的。因此,不一樣類使用相同的名字並不會衝突。
若是是類外的話,咱們可使用直接成員操做符(.),間接成員操做符(->),做用域操做符(::)來訪問類中的public方法。前提是應該使用前包含類聲明的頭文件。
或許會有人這樣去書寫代碼:
class Person { private: const int Len = 30; char Arr[Len]; };
這是不可行的,但確是一個常見的誤區。
首先解釋一下爲何不可行:聲明類只是描述類的形式,並無真正的建立對象。所以,在對象建立以前並無可使用的存儲空間。
雖然上述的寫法是錯誤的,可是咱們可使用別的方法達到咱們的目的。
class Person { private: enum{Len = 30};//使用枚舉 char Arr[Len]; };
注意,這種方式聲明枚舉並不會建立數據成員。即全部對象並不包含枚舉。並且Len僅僅是一個符號,當在做用域爲整個類的代碼中遇到它時,編譯器將用30來替換。
至於第二種方法,咱們就要引入C++中一個很是重要的關鍵字 static。
在一個類中,若將一個成員變量聲明爲static,這種成員變量稱爲靜態成員變量。與通常的成員變量不一樣,不管創建多少個對象,都只有一個靜態數據的拷貝。靜態成員變量,屬於某個類,對全部對象共享(其中一個修改了,其餘的也都修改了)。
靜態變量,在編譯階段就分配空間,對象尚未建立時,就已經分配空間
class Person { public: Person() { //Age = 10;//雖然不會報錯,通常不會這麼去作。 } static int Age;// 在類內聲明,在類外定義。會報錯! //靜態成員變量也是有權限的 private: static int other;//私有權限,類內類外不能訪問 }; int Person::Age = 0; int Person::other = 0;//仍然算類內訪問
class Person { public: Person() { //Age = 10; } static int Age; //靜態成員函數 //靜態成員函數不能夠訪問普通的成員變量 //能夠訪問靜態成員變量 static void func() { cout<<"func調用"<<endl; } //普通成員函數能夠訪問普通成員變量,也能夠訪問靜態的成員變量 void MyFunc() { } private: static int other; //靜態成員函數也有權限 static void func1() { cout<<"func1調用"<<endl; } }; int Person::Age = 0; int Person::other = 0;
在咱們瞭解了static關鍵字時,咱們就已經能夠解決剛剛的問題了:
class Person { private: static const int Len = 30; char Arr[Len]; };
這裏將建立一個靜態的Len常量,這個常量將於其餘的靜態成員一塊兒存儲,而不是存儲在對象中。(之後還會有機會遇到static)