感謝侯捷老師的悉心講授的課程,讓我在對很多東西上有了更深層次的認(rèn)識茶凳。
我呢,是一個非計算機(jī)專業(yè)畢業(yè)的本科生,畢業(yè)后帶著對程序感興趣的后知后覺開始學(xué)習(xí)編程砸民,也不是抱著以工作的目的導(dǎo)向去的,學(xué)的比較雜,也缺乏系統(tǒng)性桩了。算算日子,距離第一行java代碼已經(jīng)過去兩年有余了夕土,對于飛CS的我來說馆衔,堅持到今天也算不易瘟判。但是用了這么就的“面向?qū)ο缶幊獭保约浩鋵嵅荒芴f清其本質(zhì)到底為何物角溃。這也算是我正規(guī)劃的第一步吧拷获,再次感謝Boolan網(wǎng)和侯捷老師。那么我也就來簡單的分享一下我所學(xué)到的知識吧减细,畢竟非專業(yè)出身匆瓜,如果其中有錯誤的地方,希望大家能夠指出來未蝌,謝謝大家驮吱。
Class是怎么來的
對于面向?qū)ο笳Z言來說可能很重要的任務(wù)就是和class打交道吧(至少我接觸的java、c++萧吠、python都是和class打交道的)左冬,但是在C語言中卻沒有見到過這個東西,最多也就有struct而已纸型。
其實class對于認(rèn)識世界來說是更加一致的拇砰,比如我們通常會把生物劃分為動物、植物狰腌、微生物除破,把動物又劃分為哺乳類、兩棲類等等琼腔。我們在認(rèn)識世界的時候往往會把具體的事物瑰枫,比如貓、狗展姐、人等抽象看待躁垛,找出其共性,再把不同點加入到他們自己的屬性中去圾笨,就形成了“界門綱目科屬種”這樣的生物學(xué)劃分規(guī)律教馆。所以,也就是說擂达,類和類之間應(yīng)該有關(guān)系(比如人和貓都屬于哺乳類動物)土铺,那么這些關(guān)系之間會有一些想通的屬性(比如,人和貓都喝奶長大板鬓,都會運(yùn)動等等)悲敷。但,不同種類也具有特殊的屬性俭令,比如貓有毛后德,人就沒有等等。
而軟件也應(yīng)該對現(xiàn)實事物的一種抽象表現(xiàn)形式的描述抄腔,那么類就可以很好的把各種屬性進(jìn)行隔離并描述不同類之間的關(guān)系瓢湃。比如理张,人和貓都具備喝奶的屬性,但貓具備豐富的毛發(fā)绵患,而人卻不具備這個屬性雾叭。因此,C++中使用class來隔離部分?jǐn)?shù)據(jù)落蝙,將不同的數(shù)據(jù)分隔開來织狐。同樣,針對不同的屬性筏勒,也就應(yīng)該具有針對這個屬性的響應(yīng)操作方法(成員函數(shù))移迫,比如,貓濃密毛發(fā)這個屬性奏寨,那么他就具備“舔毛”的這個操作毛發(fā)的方法(函數(shù))起意,人沒有濃密的毛發(fā)則就不需要這個函數(shù)了。
因此病瞳,C++相較于C增加重要的概念class揽咕,用這個概念來讓程序能夠使用更加抽象的方式(屬性+操作屬性的函數(shù)(方法))來描述這個世界。
什么是面向?qū)ο螅∣bject Oriented)
其實無論Java還是C++的編程過程中套菜,最重要的需要設(shè)計各種各樣的class亲善,對于Java來說,C++的class要復(fù)雜一些逗柴,C++ class可以分為“帶有指針成員變量的的class”和“不帶指針變量的class”蛹头。而,對于每次設(shè)計出來的單一class來說戏溺,這就屬于一種“基于對象(Object Based)”的編程渣蜗。
如果再拿剛才我說的人和貓來看,對于我們需要抽象一個class來描述這個類別的時候旷祸,我們這個過程耕拷,其實就是基于對象(Object Based)的過程,比如為了描述貓咪托享,而建立了一個class貓咪骚烧。如果為了實現(xiàn)某一個復(fù)雜的過程,我們往往設(shè)計一個class是不夠的闰围。比如我們養(yǎng)貓的過程來說吧赃绊,在構(gòu)造這個人和貓的系統(tǒng)的時候,顯然需要class人羡榴,也需要class貓咪碧查,這兩種class之間從抽象的角度來看是不是有一些共通之處呢?如果說吃的不一樣我們在這里先不談校仑,那么喝的水總歸是一樣的吧么夫,呼吸的空氣總歸也是一樣的吧者冤,如果為了描述人和貓分別獨立設(shè)計兩個class,也許并沒有這個必要档痪,所以,再進(jìn)一步抽象的時候邢滑,我們也許會得到(這只是為了描述這個過程腐螟,不一定非常貼切,誰讓我養(yǎng)貓呢困后,低頭抬頭看到的全是貓)一個class 動物乐纸,這時候把水和空氣作為一種通用屬性放在動物類中,而進(jìn)一步設(shè)計class貓和class人的關(guān)系時摇予,就只需要通過class動物這個類來描述他們倆之間的關(guān)系了比如對我來說一定class人里面少不了鏟貓砂汽绢,擼貓等等這一類特有的函數(shù)(方法);class貓中存在搗亂侧戴、賣萌這類特有的函數(shù)了宁昭。而喝水、吃飯酗宋、呼吸积仗、睡覺(貓一般一天能睡十八九個小時,真羨慕他們蜕猫,有吃有喝有睡寂曹,而我必須掙錢養(yǎng)他們。回右。隆圆。。好像說著說著class人中又多了一個方法翔烁。渺氧。。)這類方法(函數(shù))雖然動物類就有租漂,但是人和貓畢竟都還是有區(qū)別阶女,這時候又可以通過覆蓋這些方法來表示共性中的不同點。
因此哩治,面向?qū)ο笙噍^于面向過程來說秃踩,就是進(jìn)一步對數(shù)據(jù)和操作方法的抽象,通過進(jìn)一步的抽象來描述多個class之間的關(guān)系的抽象方法业筏。
C++程序的基本形式
說了那么多關(guān)于基于對象和面向?qū)ο蟮墓适裸狙睿敲碈++程序到底是由什么東西構(gòu)成呢
-
C++程序可以分類兩種:.cpp(C++文件)和.h(頭文件)
而頭文件往往也分為兩部分,一部分為class的聲明(Classes Declaration)蒜胖,另外一部分則為標(biāo)準(zhǔn)庫(Standard Library)消别,其中標(biāo)準(zhǔn)庫部分里面包含了大量的算法抛蚤,可以讓我們在設(shè)計類的時候不需要重復(fù)造輪子。
-
那么.cpp和.h如何關(guān)聯(lián)在一起呢寻狂?
是通過#include來引入標(biāo)準(zhǔn)庫或這頭文件岁经。其中如果是引入標(biāo)準(zhǔn)庫的部分,使用的是<>來引入蛇券,系統(tǒng)中的標(biāo)準(zhǔn)庫文件缀壤,比如#include <iostream.h>。如果引入的是C語言的標(biāo)準(zhǔn)庫纠亚,可以使用cname或者name.h的方式引入塘慕,比如#include <stdio.h>或這#include <cstdio>;
如果是自己所編寫的頭文件需要使用""來引入進(jìn)來蒂胞,比如#include "complex.h"图呢,一般情況下,這個表示的是和cpp文件在同一個目錄下的.h文件骗随,如果.h文件在cpp文件目錄的某個文件夾中蛤织,需要使用#include "/dir/xxx.h"來引入了。
對于C和C++的輸出有什么區(qū)別呢蚊锹?
C++
#include <iostream>
int main()
{
int i = 7;
std::cout << "i= " << i << endl;
return 0;
}
- C語言
#include <stdio.h>
int main()
{
int i = 7;
printf("i=%d \n", i);
return 0;
}
-
頭文件編寫的防御式聲明
#ifndef __COMPLEX__ #define __COMPLEX__ //防御式的聲明 ......... #endif
-
目的:
- 可以讓使用者更加自由的include這個頭文件
- 防止同一個程序中重復(fù)的導(dǎo)入這個頭文件
-
頭文件的布局
#ifndef __COMPLEX__ #define __COMPLEX__ //前置聲明(forward declarations) class ostream; class complex; complex& __doapl(complex* ths, const complex& r); //類-聲明(class declarations) class complex { .... }; //類-定義 complex::function .... #endif
-
-
以complex類(復(fù)數(shù)類)舉例說明類-聲明的定義
class complex //class head { //class body public: //訪問級別access level為public的部分可以被外部直接訪問的部分 complex (double r = 0, double i = 0) : re(r), im(i) { } //complex () : re(0), im(0) { } //構(gòu)造函數(shù)的重載瞳筏,但是由于有參數(shù)的函數(shù)有默認(rèn)值,所以不能共同存在 complex& operator += (const complex&); //有些函數(shù)在body之外定義 double real() const { return re; } //有些函數(shù)再次直接定義 double imag() const { return im; } void imag(double i){ im = i; } //函數(shù)的重載 private: //訪問級別access level為private的部分只能被class內(nèi)部和friend的函數(shù)直接訪問 double re, im; //數(shù)據(jù)應(yīng)該放在private里面牡昆,以達(dá)到封裝的效果 friend complex& __doapl(complex* ths, const complex& r); }
-
inline(內(nèi)聯(lián))函數(shù)
- 在類本體內(nèi)所定義的函數(shù)
例如(之前定義的class):
double imag() const { return im; }
- 不在本體內(nèi)定義的函數(shù)姚炕,使用inline關(guān)鍵字修飾的函數(shù)(是否能成為inline函數(shù)具體情況需要由編譯器來決定,屬于對編譯器的建議)
例如:inline double imag(const complex& x) { return x.imag(); }
- 在類本體內(nèi)所定義的函數(shù)
-
構(gòu)造函數(shù)(Constructor)
complex (double r = 0, double i = 0) //默認(rèn)實參 : re(r), im(i) //初始列丢烘,構(gòu)造函數(shù)專有的設(shè)置參數(shù)初值的方法柱宦,效率比直接賦值要高 { } complex () : re(0), im(0) { } //構(gòu)造函數(shù)的重載
構(gòu)造函數(shù)會在創(chuàng)建對象的時候被自動調(diào)用
函數(shù)名和類型相同,并且無返回值
如果創(chuàng)建對象時沒有傳遞參數(shù)播瞳,會調(diào)用默認(rèn)參數(shù)的構(gòu)造函數(shù)
使用初始列設(shè)置初值的效率高于在函數(shù)體中賦值的過程
構(gòu)造函數(shù)可以重載(overloading)
-
構(gòu)造函數(shù)可以放在private中掸刊,作為私有的,可以作為singleton的設(shè)計模式(內(nèi)存中只有一個對象)
//singleton class A{ public: static A& getInstance(); setup(){.........} private: A(); A(const A& rhs); .... }; A& A::getInstance() { static A a; return a; }
-
常量成員函數(shù)
double real() const {return re;}
- 對于不改變數(shù)據(jù)的函數(shù)赢乓,應(yīng)該添加const關(guān)鍵字
- 如果不添加const關(guān)鍵字忧侧,則使用者創(chuàng)建一個常量c1
const complex c1(2, 1)
會報錯
-
參數(shù)傳遞:pass by value vs. pass by reference (to const)
- pass by value:將數(shù)據(jù)打包整體傳傳遞過去(如果對象比較大,會是效率降低)
- pass by reference:引用在底部為指針牌芋,傳遞效率相當(dāng)于傳遞指針
- 盡量傳遞引用蚓炬,而不要傳遞值
- 傳遞引用被修改,則原始值也被修改
- 如果為了提高效率躺屁,但不希望原始內(nèi)容被修改肯夏,則應(yīng)該使用const修飾
complex& operator += (const complex& x)
-
返回值傳遞:return by value vs. return by reference (to const)
返回值盡量使用return by reference
如果返回的結(jié)果實在函數(shù)中創(chuàng)建的,則不能以reference返回,因為隨著函數(shù)結(jié)束而結(jié)束驯击,不能以reference返回烁兰,否則會獲取已被銷毀的對象
-
傳遞著無需知道接收者是以reference的形式接收
inline complex& __doapl(complex* ths, const complex& r) { .... return *ths;//返回值要求為reference,則此處返回實際內(nèi)容即可 //傳遞著無需知道接收者是以reference的形式接收 }
-
友元(friend)
對于被friend修飾的函數(shù)徊都,可以直接取得private中的成員
-
相同class的各個object互為友元(friend)
//定義 class complex { public: complex(double r = 0, double i = 0):re(r), im(i){} int func (const complex& param){return param.re + param.im} private: double re, im; }
{ //調(diào)用 complex c1(2, 1); complex c2; c2.func(c1); //可以直接訪問c1中的成員(相同class的不同對象互為友元) }
-
操作符重載(
c2 += c1;
)- 成員函數(shù)類型
- 二元操作符沪斟,具有兩個操作數(shù),系統(tǒng)會將操作符作用在左邊身上暇矫,如果左邊操作數(shù)有定義币喧,系統(tǒng)能夠找到對應(yīng)的操作符重載
- 對于成員函數(shù)類型的操作符重載,編譯器在編譯時會為該函數(shù)自動添加this指針袱耽,該this指針就相當(dāng)于操作符左側(cè)的操作數(shù),所以在定義函數(shù)時并不需要傳入兩個參數(shù)干发,只需要傳入右側(cè)的操作數(shù)即可
-
例如
c2 += c1;//操作符作用在c2上
-
定義方法
inline complex& complex::operator += (const complex& r) { return __doapl(this, r); //返回值的設(shè)計要求主要是為了滿足鏈?zhǔn)秸{(diào)用的情況 //返回值可以滿足這樣的要求:c3 += c2 += c1; //過程是c2 += c1先運(yùn)算朱巨,然后產(chǎn)生返回值,再和c3運(yùn)算 } //實際上函數(shù)會被編譯器添加this指針 //complex::operator+= (this, const complex& r) //其中c2 += c1;調(diào)用時枉长,相當(dāng)于將c2以this的身份傳入冀续,c1以另外的參數(shù)傳入
-
- 非成員函數(shù)類型(無this)
- 由于是非成員函數(shù),則編譯器并不能幫助函數(shù)添加this指針必峰,則則參數(shù)列表中洪唐,需要兩個形式參數(shù),分別來表示操作符左右兩側(cè)的操作數(shù)
inline complex operator + (const complex& x, const complex& y) { return complex(real(x) + real(y), imag(x) + imag(y)); } inline complex operator + (const complex& x, double y) { return complex(real(x) + y, imag(x); } inline complex operator + (double x, const complex& y) { return complex(x + real(y), imag(y)); //此處生成了新的復(fù)數(shù)對象吼蚁,輸入內(nèi)部變量凭需,所以返回值不能是reference } //為了方便例如7+c1的情況,所以不講該函數(shù)設(shè)計為成員函數(shù)肝匆,而是使用全局函數(shù)來定義
- 成員函數(shù)類型