面向對象編程介紹(1)
想一想
下面這個項目能不能正常完成
- 你是萬達的老闆,現在要新建一個萬達商場,然後招了一百個工人,說明了你是要蓋一棟樓,把人往工地上一扔,讓工人開始幹活。
項目肯定是不能完成的,因爲沒有分工。工地上可以做的事情很多,可能一件簡單又安全的工作同時有多個人去做,而那些複雜又危險的工作,可能到最後都沒有人做。
必須要進行分工,哪些人是搭架子,哪些人是負責吊車,哪些人是混水泥。
請數一下下面兩個字符串分別有多少個字符
- ehihhsqowoqdqiudhoqq
- ehihh,sqowo,qdqiu,dhoqq
可以看到被分組後的字符串更容易計數
公司裏處理的都是大型項目,可能有上萬個函數,需要多個程序員合作開發。如果沒有分工的話,很可能一個簡單的功能,每個程序員都自己寫了一個的函數,而特別難的功能沒有人願意去做。
總結:大型的項目,必須要進行分工,將函數分爲幾個不同的類型,每個人負責一個或多個類型,比如一個人負責網站首頁,一個人負責訂單界面,一個人負責用戶設置頁面。
面向對象編程介紹(2)
- 面向過程:根據業務邏輯從上到下寫代碼
- 面向對象:將數據與函數綁定到一起,分類進行封裝,每個程序員只要負責分配給自己的分類,這樣能夠更快速的開發程序,減少了重複代碼
面向過程編程最易被初學者接受,其往往用一長段代碼來實現指定功能,開發過程的思路是將數據與函數按照執行的邏輯順序組織在一起,數據與函數分開考慮。
今天我們來學習一種新的編程方式:面向對象編程(Object Oriented Programming,OOP,面向對象程序設計)
解決菜鳥買電腦的問題
第一種方式:
1)在網上查找資料
2)根據自己預算和需求定電腦的型號 MacBook 15 頂配 1W8
3)去市場找到蘋果店各種店無法甄別真假 隨便找了一家
4)找到業務員,業務員推薦了另外一款 配置更高價格便宜,也是蘋果系統的,價格 1W
5)砍價30分鐘 付款9999
6)成交
回去之後發現各種問題
第二種方式 :
1)找一個靠譜的電腦高手
2)給錢交易
面向對象和麪向過程都是解決問題的一種思路而已
買電腦的第一種方式:
- 強調的是步驟、過程、每一步都是自己親自去實現的
- 這種解決問題的思路我們就叫做面向過程
買電腦的第二種方式:
- 強調的是電腦高手, 電腦高手是處理這件事的主角,對我們而言,我們並不必親自實現整個步驟只需要調用電腦高手就可以解決問題
- 這種解決問題的思路就 是面向對象
用面向對象的思維解決問題的重點
- 當遇到一個需求的時候不用自己去實現,如果自己一步步實現那就是面向過程
- 應該找一個專門做這個事的人來做
- 面向對象是基於面向過程的
解決喫啤酒鴨的問題
第一種方式(面向過程):
1)養鴨子
2)鴨子長成
3)殺
4)作料
5)烹飪
6)喫
7)卒
第二種方式(面向對象):
1)找個賣啤酒鴨的人
2)給錢 交易
3)喫
需要了解的定義性文字:
面向對象(object-oriented ;簡稱: OO) 至今還沒有統一的概念 我這裏把它定義爲: 按人們 認識客觀世界的系統思維方式,採用基於對象(實體)的概念建立模型,模擬客觀世界分析、設 計、實現軟件的辦法。
面向對象編程(Object Oriented Programming-OOP) 是一種解決軟件複用的設計和編程方法。 這種方法把軟件系統中相近相似的操作邏輯和操作 應用數據、狀態,以類的型式描述出來,以對象實例的形式在軟件系統中複用,以達到提高軟件開發效率的作用。
類和對象
面向對象編程的2個非常重要的概念:類和對象
對象是面向對象編程的核心,在使用對象的過程中,爲了將具有共同特徵和行爲的一組對象抽象定義,提出了另外一個新的概念——類
類就相當於製造飛機時的圖紙,用它來進行創建的飛機就相當於對象
1. 類
人以類聚 物以羣分。
具有相似內部狀態和運動規律的實體的集合(或統稱爲抽象)。
具有相同屬性和行爲事物的統稱
類是抽象的,在使用的時候通常會找到這個類的一個具體的存在,使用這個具體的存在。一個類可以找到多個對象
2. 對象
某一個具體事物的存在 ,在現實世界中可以是看得見摸得着的。
可以是直接使用的
3. 類和對象之間的關係
小總結:類就是創建對象的模板
4. 練習:區分類和對象
奔馳汽車 類
奔馳smart 類
張三的那輛奔馳smart 對象
狗 類
大黃狗 類
李四家那隻大黃狗 對象
水果 類
蘋果 類
紅蘋果 類 紅富士蘋果 類
我嘴裏吃了一半的蘋果 對象
5. 類的構成
類(Class) 由3個部分構成
- 類的名稱:類名
- 類的屬性:一組數據
- 類的方法:允許對進行操作的方法 (行爲)
<1> 舉例:
1)人類設計,只關心3樣東西:
- 事物名稱(類名):人(Person)
- 屬性:身高(height)、年齡(age)
- 方法(行爲/功能):跑(run)、打架(fight)
2)狗類的設計
- 類名:狗(Dog)
- 屬性:品種 、毛色、性別、名字、 腿兒的數量
- 方法(行爲/功能):叫 、跑、咬人、喫、搖尾巴
6. 類的抽象
如何把日常生活中的事物抽象成程序中的類?
擁有相同(或者類似)屬性和行爲的對象都可以抽像出一個類
方法:一般名詞都是類(名詞提煉法)
<1> 坦克發射3顆炮彈轟掉了2架飛機
- 坦克--》可以抽象成 類
- 炮彈--》可以抽象成類
- 飛機-》可以抽象成類
<2> 小明在公車上牽着一條叼着熱狗的狗
- 小明--》 人類
- 公車--》 交通工具類
- 熱狗--》 食物類
- 狗--》 狗類
<3>【想一想】如下圖中,有哪些類呢?
說明:
- 向日葵
- 類名: xrk
- 屬性:
- 行爲: 放陽光
- 豌豆
- 類名: wd
- 屬性: 顏色 、髮型,血量
- 行爲:發炮, 搖頭
- 堅果:
- 類名:jg
- 屬性:血量 類型
- 行爲:阻擋;
- 殭屍:
- 類名:js
- 屬性:顏色、血量、 類型、速度
- 行爲:走 跑跳 喫 死
定義類
定義一個類,格式如下:
class 類名:
方法列表
demo:定義一個Hero類
# class Hero: # 經典類(舊式類)定義形式
# class Hero():
class Hero(object): # 新式類定義形式
def info(self):
print("英雄各有見,何必問出處。")
說明:
定義類時有2種形式:新式類和經典類,上面代碼中的Hero爲新式類,前兩行註釋部分則爲經典類;
object 是Python 裏所有類的最頂級父類;
類名 的命名規則按照"大駝峯命名法";
- info 是一個實例方法,第一個參數一般是self,表示實例對象本身,當然了可以將self換爲其它的名字,其作用是一個變量 這個變量指向了實例對象
創建對象
python中,可以根據已經定義的類去創建出一個或多個對象。
創建對象的格式爲:
對象名1 = 類名()
對象名2 = 類名()
對象名3 = 類名()
創建對象demo:
class Hero(object): # 新式類定義形式
"""info 是一個實例方法,類對象可以調用實例方法,實例方法的第一個參數一定是self"""
def info(self):
"""當對象調用實例方法時,Python會自動將對象本身的引用做爲參數,
傳遞到實例方法的第一個參數self裏"""
print(self)
print("self各不同,對象是出處。")
# Hero這個類 實例化了一個對象 taidamier(泰達米爾)
taidamier = Hero()
# 對象調用實例方法info(),執行info()裏的代碼
# . 表示選擇屬性或者方法
taidamier.info()
print(taidamier) # 打印對象,則默認打印對象在內存的地址,結果等同於info裏的print(self)
print(id(taidamier)) # id(taidamier) 則是內存地址的十進制形式表示
說明:
- 當創建一個對象時,就是用一個模子,來製造一個實物
問題:
對象既然有實例方法,是否也可以有自己的屬性?
添加和獲取對象的屬性
class Hero(object):
"""定義了一個英雄類,可以移動和攻擊"""
def move(self):
"""實例方法"""
print("正在前往事發地點...")
def attack(self):
"""實例方法"""
print("發出了一招強力的普通攻擊...")
# 實例化了一個英雄對象 泰達米爾
taidamier = Hero()
# 給對象添加屬性,以及對應的屬性值
taidamier.name = "泰達米爾" # 姓名
taidamier.hp = 2600 # 生命值
taidamier.atk = 450 # 攻擊力
taidamier.armor = 200 # 護甲值
# 通過.成員選擇運算符,獲取對象的屬性值
print("英雄 %s 的生命值 :%d" % (taidamier.name, taidamier.hp))
print("英雄 %s 的攻擊力 :%d" % (taidamier.name, taidamier.atk))
print("英雄 %s 的護甲值 :%d" % (taidamier.name, taidamier.armor))
# 通過.成員選擇運算符,獲取對象的實例方法
taidamier.move()
taidamier.attack()
問題:
對象創建並添加屬性後,能否在類的實例方法裏獲取這些屬性呢?如果可以的話,應該通過什麼方式?
在方法內通過self獲取對象屬性
class Hero(object):
"""定義了一個英雄類,可以移動和攻擊"""
def move(self):
"""實例方法"""
print("正在前往事發地點...")
def attack(self):
"""實例方法"""
print("發出了一招強力的普通攻擊...")
def info(self):
"""在類的實例方法中,通過self獲取該對象的屬性"""
print("英雄 %s 的生命值 :%d" % (self.name, self.hp))
print("英雄 %s 的攻擊力 :%d" % (self.name, self.atk))
print("英雄 %s 的護甲值 :%d" % (self.name, self.armor))
# 實例化了一個英雄對象 泰達米爾
taidamier = Hero()
# 給對象添加屬性,以及對應的屬性值
taidamier.name = "泰達米爾" # 姓名
taidamier.hp = 2600 # 生命值
taidamier.atk = 450 # 攻擊力
taidamier.armor = 200 # 護甲值
# 通過.成員選擇運算符,獲取對象的實例方法
taidamier.info() # 只需要調用實例方法info(),即可獲取英雄的屬性
taidamier.move()
taidamier.attack()
問題:
創建對象後再去添加屬性有點不合適,有沒有簡單的辦法,可以在創建對象的時候,就已經擁有這些屬性?
__init__()方法
class Hero(object):
"""定義了一個英雄類,可以移動和攻擊"""
# Python 的類裏提供的,兩個下劃線開始,兩個下劃線結束的方法,就是魔法方法,__init__()就是一個魔法方法,通常用來做屬性初始化 或 賦值 操作。
# 如果類面沒有寫__init__方法,Python會自動創建,但是不執行任何操作,
# 如果爲了能夠在完成自己想要的功能,可以自己定義__init__方法,
# 所以一個類裏無論自己是否編寫__init__方法 一定有__init__方法。
def __init__(self):
""" 方法,用來做變量初始化 或 賦值 操作,在類實例化對象的時候,會被自動調用"""
self.name = "泰達米爾" # 姓名
self.hp = 2600 # 生命值
self.atk = 450 # 攻擊力
self.armor = 200 # 護甲值
def move(self):
"""實例方法"""
print("正在前往事發地點...")
def attack(self):
"""實例方法"""
print("發出了一招強力的普通攻擊...")
# 實例化了一個英雄對象,並自動調用__init__()方法
taidamier = Hero()
# 通過.成員選擇運算符,獲取對象的實例方法
taidamier.info() # 只需要調用實例方法info(),即可獲取英雄的屬性
taidamier.move()
taidamier.attack()
說明:
__init__()
方法,在創建一個對象時默認被調用,不需要手動調用__init__(self)
中的self參數,不需要開發者傳遞,python解釋器會自動把當前的對象引用傳遞過去。
問題:
在類的方法裏定義屬性的固定值,則每個對象實例變量的屬性值都是相同的。
一個遊戲裏往往有很多不同的英雄,能否讓實例化的每個對象,都有不同的屬性值呢?
有參數的__init__()方法
class Hero(object):
"""定義了一個英雄類,可以移動和攻擊"""
def __init__(self, name, skill, hp, atk, armor):
""" __init__() 方法,用來做變量初始化 或 賦值 操作"""
# 英雄名
self.name = name
# 技能
self.skill = skill
# 生命值:
self.hp = hp
# 攻擊力
self.atk = atk
# 護甲值
self.armor = armor
def move(self):
"""實例方法"""
print("%s 正在前往事發地點..." % self.name)
def attack(self):
"""實例方法"""
print("發出了一招強力的%s..." % self.skill)
def info(self):
print("英雄 %s 的生命值 :%d" % (self.name, self.hp))
print("英雄 %s 的攻擊力 :%d" % (self.name, self.atk))
print("英雄 %s 的護甲值 :%d" % (self.name, self.armor))
# 實例化英雄對象時,參數會傳遞到對象的__init__()方法裏
taidamier = Hero("泰達米爾", "旋風斬", 2600, 450, 200)
gailun = Hero("蓋倫", "大寶劍", 4200, 260, 400)
# print(gailun)
# print(taidamier)
# 不同對象的屬性值的單獨保存
print(id(taidamier.name))
print(id(gailun.name))
# 同一個類的不同對象,實例方法共享
print(id(taidamier.move()))
print(id(gailun.move()))
說明:
通過一個類,可以創建多個對象,就好比 通過一個模具創建多個實體一樣
__init__(self)
中,默認有1個參數名字爲self,如果在創建對象時傳遞了2個實參,那麼__init__(self)
中出了self作爲第一個形參外還需要2個形參,例如__init__(self,x,y)
注意:
- 在類內部獲取 屬性 和 實例方法,通過self獲取;
在類外部獲取 屬性 和 實例方法,通過對象名獲取。
如果一個類有多個對象,每個對象的屬性是各自保存的,都有各自獨立的地址;
- 但是實例方法是所有對象共享的,只佔用一份內存空間。類會通過self來判斷是哪個對象調用了實例方法。
__str__()方法
class Hero(object):
"""定義了一個英雄類,可以移動和攻擊"""
def __init__(self, name, skill, hp, atk, armor):
""" __init__() 方法,用來做變量初始化 或 賦值 操作"""
# 英雄名
self.name = name # 實例變量
# 技能
self.skill = skill
# 生命值:
self.hp = hp # 實例變量
# 攻擊力
self.atk = atk
# 護甲值
self.armor = armor
def move(self):
"""實例方法"""
print("%s 正在前往事發地點..." % self.name)
def attack(self):
"""實例方法"""
print("發出了一招強力的%s..." % self.skill)
# def info(self):
# print("英雄 %s 的生命值 :%d" % (self.name, self.hp))
# print("英雄 %s 的攻擊力 :%d" % (self.name, self.atk))
# print("英雄 %s 的護甲值 :%d" % (self.name, self.armor))
def __str__(self):
"""
這個方法是一個魔法方法 (Magic Method) ,用來顯示信息
該方法需要 return 一個數據,並且只有self一個參數,當在類的外部 print(對象) 則打印這個數據
"""
return "英雄 <%s> 數據: 生命值 %d, 攻擊力 %d, 護甲值 %d" % (self.name, self.hp, self.atk, self.armor)
taidamier = Hero("泰達米爾", "旋風斬", 2600, 450, 200)
gailun = Hero("蓋倫", "大寶劍", 4200, 260, 400)
# 如果沒有__str__ 則默認打印 對象在內存的地址。
# 當類的實例化對象 擁有 __str__ 方法後,那麼打印對象則打印 __str__ 的返回值。
print(taidamier)
print(gailun)
# 查看類的文檔說明,也就是類的註釋
print(Hero.__doc__)
說明:
- 在python中方法名如果是
__xxxx__()
的,那麼就有特殊的功能,因此叫做“魔法”方法 - 當使用print輸出對象的時候,默認打印對象的內存地址。如果類定義了
__str__(self)
方法,那麼就會打印從在這個方法中return
的數據 __str__
方法通常返回一個字符串,作爲這個對象的描述信息
__del__()
方法
創建對象後,python解釋器默認調用__init__()
方法;
當刪除對象時,python解釋器也會默認調用一個方法,這個方法爲__del__()
方法
class Hero(object):
# 初始化方法
# 創建完對象後會自動被調用
def __init__(self, name):
print('__init__方法被調用')
self.name = name
# 當對象被刪除時,會自動被調用
def __del__(self):
print("__del__方法被調用")
print("%s 被 GM 幹掉了..." % self.name)
# 創建對象
taidamier = Hero("泰達米爾")
# 刪除對象
print("%d 被刪除1次" % id(taidamier))
del(taidamier)
print("--" * 10)
gailun = Hero("蓋倫")
gailun1 = gailun
gailun2 = gailun
print("%d 被刪除1次" % id(gailun))
del(gailun)
print("%d 被刪除1次" % id(gailun1))
del(gailun1)
print("%d 被刪除1次" % id(gailun2))
del(gailun2)
總結
當有變量保存了一個對象的引用時,此對象的引用計數就會加1;
當使用del() 刪除變量指向的對象時,則會減少對象的引用計數。如果對象的引用計數不爲1,那麼會讓這個對象的引用計數減1,當對象的引用計數爲0的時候,則對象纔會被真正刪除(內存被回收)。
應用:烤地瓜
爲了更好的理解面向對象編程,下面以“烤地瓜”爲案例,進行分析
1. 分析“烤地瓜”的屬性和方法
示例屬性如下:
- cookedLevel : 這是數字;0~3表示還是生的,超過3表示半生不熟,超過5表示已經烤好了,超過8表示已經烤成木炭了!我們的地瓜開始時時生的
- cookedString : 這是字符串;描述地瓜的生熟程度
- condiments : 這是地瓜的配料列表,比如番茄醬、芥末醬等
示例方法如下:
cook()
: 把地瓜烤一段時間addCondiments()
: 給地瓜添加配料__init__()
: 設置默認的屬性__str__()
: 讓print的結果看起來更好一些
2. 定義類,並且定義__init__()
方法
#定義`地瓜`類
class SweetPotato:
"""這是烤地瓜的類"""
#定義初始化方法
def __init__(self):
self.cookedLevel = 0
self.cookedString = "生的"
self.condiments = []
3. 添加"烤地瓜"方法
#烤地瓜方法
def cook(self, time):
self.cookedLevel += time
if self.cookedLevel > 8:
self.cookedString = "烤成灰了"
elif self.cookedLevel > 5:
self.cookedString = "烤好了"
elif self.cookedLevel > 3:
self.cookedString = "半生不熟"
else:
self.cookedString = "生的"
4. 基本的功能已經有了一部分,趕緊測試一下
把上面2塊代碼合併爲一個程序後,在代碼的下面添加以下代碼進行測試
mySweetPotato = SweetPotato()
print(mySweetPotato.cookedLevel)
print(mySweetPotato.cookedString)
print(mySweetPotato.condiments)
完整的代碼爲:
class SweetPotato:
"""這是烤地瓜的類"""
# 定義初始化方法
def __init__(self):
self.cookedLevel = 0
self.cookedString = "生的"
self.condiments = []
# 烤地瓜方法
def cook(self, time):
self.cookedLevel += time
if self.cookedLevel > 8:
self.cookedString = "烤成灰了"
elif self.cookedLevel > 5:
self.cookedString = "烤好了"
elif self.cookedLevel > 3:
self.cookedString = "半生不熟"
else:
self.cookedString = "生的"
# 用來進行測試
mySweetPotato = SweetPotato()
print(mySweetPotato.cookedLevel)
print(mySweetPotato.cookedString)
print(mySweetPotato.condiments)
5. 測試cook方法是否好用
在上面的代碼最後面添加如下代碼:
print("------接下來要進行烤地瓜了-----")
mySweetPotato.cook(4) #烤4分鐘
print(mySweetPotato.cookedLevel)
print(mySweetPotato.cookedString)
6. 定義addCondiments()
方法和__str__()
方法
def __str__(self):
msg = self.cookedString + " 地瓜"
if len(self.condiments) > 0:
msg = msg + "("
for temp in self.condiments:
msg = msg + temp + ", "
msg = msg.strip(", ")
msg = msg + ")"
return msg
def addCondiments(self, condiments):
self.condiments.append(condiments)
7. 再次測試
完整的代碼如下:
class SweetPotato:
"""這是烤地瓜的類"""
# 定義初始化方法
def __init__(self):
self.cookedLevel = 0
self.cookedString = "生的"
self.condiments = []
# 定製print時的顯示內容
def __str__(self):
msg = self.cookedString + " 地瓜"
if len(self.condiments) > 0:
msg = msg + "("
for temp in self.condiments:
msg = msg + temp + ", "
msg = msg.strip(", ")
msg = msg + ")"
return msg
# 烤地瓜方法
def cook(self, time):
self.cookedLevel += time
if self.cookedLevel > 8:
self.cookedString = "烤成灰了"
elif self.cookedLevel > 5:
self.cookedString = "烤好了"
elif self.cookedLevel > 3:
self.cookedString = "半生不熟"
else:
self.cookedString = "生的"
# 添加配料
def addCondiments(self, condiments):
self.condiments.append(condiments)
# 用來進行測試
mySweetPotato = SweetPotato()
print("------有了一個地瓜,還沒有烤-----")
print(mySweetPotato.cookedLevel)
print(mySweetPotato.cookedString)
print(mySweetPotato.condiments)
print("------接下來要進行烤地瓜了-----")
print("------地瓜經烤了4分鐘-----")
mySweetPotato.cook(4) #烤4分鐘
print(mySweetPotato)
print("------地瓜又經烤了3分鐘-----")
mySweetPotato.cook(3) #又烤了3分鐘
print(mySweetPotato)
print("------接下來要添加配料-番茄醬------")
mySweetPotato.addCondiments("番茄醬")
print(mySweetPotato)
print("------地瓜又經烤了5分鐘-----")
mySweetPotato.cook(5) #又烤了5分鐘
print(mySweetPotato)
print("------接下來要添加配料-芥末醬------")
mySweetPotato.addCondiments("芥末醬")
print(mySweetPotato)