摘要:一抽象數(shù)據(jù)類型,縮寫為是計(jì)算機(jī)領(lǐng)域一種很基礎(chǔ)的方法,基本的思想就是數(shù)據(jù)抽象。二抽象數(shù)據(jù)類型的概念和描述抽象數(shù)據(jù)類型把數(shù)據(jù)定義為抽象的對(duì)象集合,只為他們定義可用的操作,而不用暴露具體的實(shí)現(xiàn)細(xì)節(jié)。
文章首發(fā)于公眾號(hào)一件風(fēng)衣(ID:yijianfengyi)
名人名言強(qiáng)調(diào)基礎(chǔ)的重要性的句子不勝枚舉,數(shù)據(jù)結(jié)構(gòu)與算法作為計(jì)算機(jī)專業(yè)的必學(xué)科目,其重要性不言而喻。
在以往的教學(xué)體系中,數(shù)據(jù)結(jié)構(gòu)與算法通常結(jié)合C語言進(jìn)行教學(xué),而近年來Python的興起,已經(jīng)引起了教學(xué)上的變化,據(jù)我了解,已經(jīng)有部分大學(xué)把C語言和Python同時(shí)作為計(jì)算機(jī)專業(yè)的基礎(chǔ)編程課了。
這個(gè)系列就和大家一起學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)與算法的Python實(shí)現(xiàn),據(jù)說找工作面試時(shí)特別喜歡問基礎(chǔ)的算法問題。
一、抽象數(shù)據(jù)類型(Abstract Data Type,縮寫為ADT)
ADT是計(jì)算機(jī)領(lǐng)域一種很基礎(chǔ)的方法,基本的思想就是數(shù)據(jù)抽象。圍繞抽象出來的數(shù)據(jù)類型定義各種運(yùn)算(函數(shù)),形成一個(gè)完整的模塊,提供接口以供調(diào)用,這就有了模塊化的編程思想。
(一)數(shù)據(jù)類型和數(shù)據(jù)構(gòu)造
在任何編程語言中,都定義了一些基本的數(shù)據(jù)類型,以Python為例,基本類型有邏輯類型bool、數(shù)值類型int、float等、字符串類型str和一些組合數(shù)據(jù)類型。
對(duì)于每一種類型,都定義了相應(yīng)的運(yùn)算,比如bool類型的值可以為True或False,運(yùn)算包括and(與)、or(或)、not(非)這類邏輯運(yùn)算,int類型的值可以為整數(shù),定義了加減乘除等運(yùn)算……
但是很多時(shí)候基本數(shù)據(jù)類型是不夠用的,比如有理數(shù)、復(fù)數(shù)等,在此以復(fù)數(shù)為例,數(shù)學(xué)上復(fù)數(shù)的形式為a+bi(i為-1的平方根),定義一個(gè)復(fù)數(shù)類型的變量,我們需要兩個(gè)值,比如寫為:
a1 = 2 b1 = 1
就可以表示復(fù)數(shù)2+i,同樣,可以定義復(fù)數(shù)上的加法運(yùn)算函數(shù):
def complex_plus(a1,b1,a2,b2): realpart = a1 +a2 imaginarypart = b1 + b2 return realpart,imaginarypart
計(jì)算2+3i與3-4i的和就可以這么使用:
a3 , b3 = complex_plus(2,3,3,-4)
雖然實(shí)現(xiàn)了這個(gè)功能,但是用兩個(gè)獨(dú)立的數(shù)來表示一個(gè)復(fù)數(shù)顯然不合適,給后續(xù)的使用帶來了很大不便,不過我們可以改進(jìn)一下,用一個(gè)二元組來表示復(fù)數(shù),約定第一項(xiàng)為實(shí)部,第二項(xiàng)為虛部:
c1 = (2,3) c2 = (3,-4)
那么加法運(yùn)算函數(shù)可以這么寫:
def complex_plus(c1,c2): realpart = c1[0] + c2[0] imaginarypart = c1[1] + c2[1] return realpart,imaginarypart
調(diào)用就可以直接這么寫:
c3 = complex_plus(c1,c2)
雖然改進(jìn)了使用方式,但是依然有很大的問題:
一是用普通的元組來表示復(fù)數(shù),不能和其他的元組相互區(qū)分,其他可以用元組表示的類型依然可以進(jìn)行同樣的運(yùn)算,比如有理數(shù)也用(a,b)來表示,那么可以調(diào)用復(fù)數(shù)的加法運(yùn)算,把一個(gè)復(fù)數(shù)和一個(gè)有理數(shù)加起來——實(shí)部加分子,虛部加分母,這顯然不科學(xué);
二是復(fù)數(shù)的形式還算簡(jiǎn)潔,只有兩個(gè)參數(shù),對(duì)于很多參數(shù)的數(shù)據(jù)類型還使用元組的話,那可真是太麻煩了。
總的來說,這樣的表示方法雖然能實(shí)現(xiàn)功能,但造成了很差的可讀性,使用和修改起來都會(huì)很困難,于是我們需要面向?qū)ο蟮姆椒▉斫鉀Q這種問題。
(二)抽象數(shù)據(jù)類型的概念和描述
抽象數(shù)據(jù)類型把數(shù)據(jù)定義為抽象的對(duì)象集合,只為他們定義可用的操作,而不用暴露具體的實(shí)現(xiàn)細(xì)節(jié)。對(duì)一個(gè)數(shù)據(jù)類型的操作分為三類:
1.構(gòu)造:基于已知的數(shù)據(jù)類型構(gòu)造所需要的數(shù)據(jù)類型,比如基于兩個(gè)整數(shù)表示一個(gè)有理數(shù),或是基于兩個(gè)實(shí)數(shù)表示一個(gè)復(fù)數(shù)等;
2.解析:通過對(duì)象獲取需要的信息,比如從一個(gè)復(fù)數(shù)對(duì)象中獲取實(shí)部或者虛部,從一個(gè)有理數(shù)對(duì)象中獲取分子或者分母;
3.變動(dòng):修改對(duì)象的內(nèi)部信息,比如修改復(fù)數(shù)的實(shí)部大小或虛部大小,對(duì)象的名稱沒變,但是內(nèi)部的信息發(fā)生了變化。
我們通過一個(gè)int之類的名字來代表這個(gè)數(shù)據(jù)類型,就可以使用它了。
編程語言的內(nèi)置數(shù)據(jù)類型也是這么一種抽象數(shù)據(jù)類型,構(gòu)造是必要的,根據(jù)是否有變動(dòng)我們可以把數(shù)據(jù)類型分為可變數(shù)據(jù)類型和不變數(shù)據(jù)類型,Python中,str就是一種不變數(shù)據(jù)類型,list就是一種可變數(shù)據(jù)類型。
那么如何描述一個(gè)抽象數(shù)據(jù)類型呢?
以復(fù)數(shù)為例,有如下偽代碼:
ADT ComplexNumber:#定義復(fù)數(shù)抽象數(shù)據(jù)類型 ComplexNumber(float a,float b)# 構(gòu)造復(fù)數(shù)a+bi +(ComplexNumber c1,ComplexNumber c2)# 求復(fù)數(shù)c1 + c2 -(ComplexNumber c1,ComplexNumber c2)# 求復(fù)數(shù)c1 - c2 *(ComplexNumber c1,ComplexNumber c2)#求復(fù)數(shù)c1 * c2 /(ComplexNumber c1,ComplexNumber c2)#求復(fù)數(shù)c1 / c2 getReal(ComplexNumber c1)# 獲取c1的實(shí)部 getImaginary(ComplexNumber c1)# 獲取c1的虛部
這里用ADT表示這是一個(gè)抽象數(shù)據(jù)類型,ComplexNumber為類型名稱,同理我們也可以用類似的方法表示一個(gè)有理數(shù),一個(gè)日期,一個(gè)銀行賬戶的基本信息等。
總的來說,ADT就是圍繞某個(gè)數(shù)據(jù)類型定義的模塊,接口和實(shí)現(xiàn)分離,提供接口完成該數(shù)據(jù)類型的各種操作。
二、Python類
Python作為面向?qū)ο蟮木幊陶Z言,用類(class)定義所有類型,包括內(nèi)置的數(shù)據(jù)類型都是類,我們可以這么理解——類是面向?qū)ο缶幊陶Z言的基本類型,一切數(shù)據(jù)類型都是基于類定義的。
(一)復(fù)數(shù)的初階定義
依然以復(fù)數(shù)為例,我們可以在Python中這么定義一個(gè)類:
class ComplexNumber: def __init__(self,realpart,imaginarypart=0): self.realpart = realpart self.imaginarypart = imaginarypart def plus(self,another): realpart = self.realpart + another.realpart imaginarypart = self.imaginarypart + another.imaginarypart return ComplexNumber(realpart, imaginarypart) def print(self): print(str(self.realpart) + “+” +str(self.imaginarypart) + “i”)
class是關(guān)鍵字,表示類定義的開始,ComplexNumber就是這個(gè)類的名字。
每個(gè)類中我們都會(huì)定義__init__函數(shù),稱為初始化方法,用于構(gòu)造一個(gè)該類的新對(duì)象,我們以類名作為函數(shù)創(chuàng)建實(shí)例化對(duì)象,如:
c1 = ComplexNumber(2,3)
在調(diào)用的時(shí)候,應(yīng)當(dāng)給出除self外的其他參數(shù)。
realpart和imaginarypart都是復(fù)數(shù)類的屬性,不用多帶帶聲明,即使在初始化中沒有,在賦值時(shí)也會(huì)自動(dòng)創(chuàng)建,但一般不這么做。
調(diào)用其他方法時(shí),也是self表示本實(shí)例,應(yīng)當(dāng)給出其他參數(shù),比如:
c2 = c1.plus(ComplexNumber(3,-2))
此時(shí)c1的值作為plus方法的第一個(gè)參數(shù),新構(gòu)造的復(fù)數(shù)為第二個(gè)。
同理,print函數(shù)中只有一個(gè)參數(shù)self,所以調(diào)用的時(shí)候這么寫:
c2.print()
執(zhí)行以上語句后我們可以得到如下輸出:
(二)復(fù)數(shù)的高階定義
對(duì)于復(fù)數(shù),我們經(jīng)常使用模的概念,對(duì)于z=a+bi,模|z|定義如下:
所以我們需要函數(shù)module(),來求取復(fù)數(shù)的模。可以定義如下
def module(): return math.sqrt(self.realpart *self.realpart, self.imaginarypart * self.imaginarypart)
調(diào)用輸出一個(gè)復(fù)數(shù)時(shí)直接這么寫:
print(c2.module())
其中math.sqrt()是求平方根的函數(shù),需要在程序開頭導(dǎo)入math包:
import math
在定義Python的類時(shí),有這樣的約定,以下劃線開頭的屬性名或函數(shù)名都當(dāng)作內(nèi)部使用的名字,不能夠在類外使用。此外以兩根下劃線開頭(不以兩根下劃線結(jié)尾)的屬性會(huì)被特殊處理保護(hù),不得在類外使用。如果我們不希望復(fù)數(shù)的虛部實(shí)部屬性被直接修改,初始化方法中我們可以在屬性前加下劃線,阻止類外的訪問:
def __init__(self,realpart,imaginarypart=0): self._realpart = realpart self._imaginarypart = imaginarypart
然而我們有時(shí)又很需要讀取這兩個(gè)屬性,于是我們可以定義解析操作:
def realpart(self): return self._realpart def imaginarypart(self): return self._imaginarypart
這樣可以讀取受保護(hù)的屬性了。
我們?cè)倏紤]新的問題,在數(shù)學(xué)上我們可以直接用加減乘除的符號(hào)來操作復(fù)數(shù),按照我們上面的定義,顯然調(diào)用起來很不方便,要是能直接把方法定義在操作符上,那使用起來也就方便多了。
好消息是Python中可以這么做!Python中為所有的運(yùn)算符都定義了特殊的方法名,這類特殊方法名都以雙下劃線開始,雙下劃線結(jié)束,如__add__表示加號(hào),__mul__表示乘號(hào),于是對(duì)于復(fù)數(shù)中的加法,我們可以如下實(shí)現(xiàn):
def __add__(self,another): realpart = self._realpart + another.realpart() imaginarypart = self._imaginarypart + another.imaginarypart() return ComplexNumber(realpart, imaginarypart)
注意這段代碼中self和another獲取屬性的方式的區(qū)別。
類似的,我們可以定義其他運(yùn)算符的操作(包括比較運(yùn)算符),具體的方法名可以查找Python的文檔。但重載運(yùn)算符時(shí)要注意限制條件,比如虛部實(shí)部均為0的復(fù)數(shù)不能作為除數(shù)等。
(三)幾點(diǎn)總結(jié)和提示
1.類定義時(shí)格式如下:
class <類名>: <語句組>
2.對(duì)類中的屬性的訪問,采用圓點(diǎn)記法;
3.實(shí)例對(duì)象初始化時(shí)自動(dòng)調(diào)用__init__(),第一個(gè)參數(shù)self表示正在初始化的對(duì)象自身;
4.在定義類時(shí),應(yīng)當(dāng)避免屬性名和方法名相同;
5.靜態(tài)方法、類方法、作用域、繼承等日后再提。
思考:如果要定義一個(gè)學(xué)生類,屬性包括姓名,性別,生日,學(xué)號(hào),方法包括計(jì)算年齡,修改屬性,打印全部屬性,初始化時(shí)生成學(xué)號(hào),規(guī)則是年份+順序五位十進(jìn)制數(shù),你會(huì)怎么實(shí)現(xiàn)這個(gè)類呢?下一篇中會(huì)貼出我的實(shí)現(xiàn)方法,歡迎探討。(提示:類中可定義屬性,該屬性不屬于任何實(shí)例,只屬于類本身)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/41861.html
摘要:文章首發(fā)于公眾號(hào)一件風(fēng)衣在編程中,我們常使用一組有順序的數(shù)據(jù)來表示某個(gè)有意義的數(shù)據(jù),這種一組元素的序列的抽象,就是線性表,簡(jiǎn)稱表,是很多復(fù)雜數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn)基礎(chǔ),在中,和就可以看作是線性表的實(shí)現(xiàn)。 文章首發(fā)于公眾號(hào)一件風(fēng)衣(ID:yijianfengyi) 在編程中,我們常使用一組有順序的數(shù)據(jù)來表示某個(gè)有意義的數(shù)據(jù),這種一組元素的序列的抽象,就是線性表,簡(jiǎn)稱表,是很多復(fù)雜數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn)基...
摘要:使用抽象基類顯示表示接口如果類的作用是定義接口,應(yīng)該將其明確定義為抽象基類。此外,抽象基類可以作為其他類的唯一基類,混入類則決不能作為唯一的基類,除非這個(gè)混入類繼承了另一個(gè)更具體的混入這種做法非常少見。 《流暢的Python》筆記本篇是面向?qū)ο髴T用方法的第五篇,我們將繼續(xù)討論繼承,重點(diǎn)說明兩個(gè)方面:繼承內(nèi)置類型時(shí)的問題以及多重繼承。概念比較多,較為枯燥。 1. 繼承內(nèi)置類型 內(nèi)置類型...
摘要:于發(fā)表了著名的有害論的論文引起了長達(dá)數(shù)年的論戰(zhàn)并由此產(chǎn)生了結(jié)構(gòu)化程序設(shè)計(jì)方法。到現(xiàn)在為止面向?qū)ο笠呀?jīng)成為了主流的開發(fā)思想。面向?qū)ο蟮某绦蛟O(shè)計(jì)優(yōu)點(diǎn)解決了程序的擴(kuò)展性。 [Python3]Python面向?qū)ο蟮某绦蛟O(shè)計(jì) 一、面向?qū)ο蟮某绦蛟O(shè)計(jì)的由來 1.第一階段:面向機(jī)器,1940年以前 最早的程序設(shè)計(jì)都是采用機(jī)器語言來編寫的,直接使用二進(jìn)制碼來表示機(jī)器能夠識(shí)別和執(zhí)行的指令和數(shù)據(jù)。 簡(jiǎn)單來...
摘要:類似消息傳遞中的分發(fā)字典,對(duì)象響應(yīng)行為請(qǐng)求。消息傳遞和點(diǎn)表達(dá)式方法定義在類中,而實(shí)例屬性通常在構(gòu)造器中賦值,二者都是面向?qū)ο缶幊痰幕驹?。使用帶有?nèi)建對(duì)象系統(tǒng)語言的優(yōu)點(diǎn)是,消息傳遞能夠和其它語言特性,例如賦值語句無縫對(duì)接。 2.5 面向?qū)ο缶幊? 來源:2.5 Object-Oriented Programming 譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 面向?qū)ο缶幊?..
閱讀 545·2019-08-30 15:55
閱讀 956·2019-08-29 15:35
閱讀 1210·2019-08-29 13:48
閱讀 1923·2019-08-26 13:29
閱讀 2948·2019-08-23 18:26
閱讀 1261·2019-08-23 18:20
閱讀 2843·2019-08-23 16:43
閱讀 2717·2019-08-23 15:58