一、變量與表
1、變量
變量:用來標識(identify)一塊內存區域,這塊區域的內容是可變的。
變量名:是一個標識符(identifier),用來指代一塊內存區域,即變量。
CPU是用內存地址來標識一塊內存區域的,機器碼中,是不會出現變量名的;出現的是邏輯相對地址。在匯編層次上,操作的都是地址,不存在任何名稱;變量名是給我們程序員操作內存來使用的。簡單說,變量就是地址;在對程序編譯連接時由編譯系統給每一個變量名分配相應的內存邏輯地址,形成符號表;該地址分配後不可改變。當你調用此變量時,編譯器就會根據此符號表找到對應的邏輯地址,然後翻譯成匯編指令。例如從R2寄存器為基址的表中讀一個變量所在行的內容到行寄存器H5,翻譯成匯編:MOV
2、內存區域
APO在內存分配時會涉及到以下區域:
◆寄存器:用於自動變量,如方法裡的局部變量。
◆棧:存放臨時的基本類型數據、方法的局部變量。
對象的引用(包括動態對象)、動態分配內存的變量引用,局部靜態變量等都應該保存在對象的屬性表中;但動態對象、動態變量的身體是存放在堆中。棧是會自動清除或覆蓋數據的,適合自動變量。APO對堆內存的操作和對棧內存的操作速度是一樣的,比寄存器操作稍微慢點;即使是動態內存分配和釋放(有專用的硬件指令)不過是幾個ns。
當在一段代碼塊定義一些變量時,編譯器就在棧中為這些變量分配內存空間,當這些變量退出該作用域後,棧會自動釋放掉(重新設置棧SP寄存器值)為這些變量所分配的內存空間,該內存空間可以立即被另作他用。棧中的數據大小和生命周期是可以確定的。
在APO中,一個進程內只有一個用戶棧,所有的對象(包括線程對象)都是共用一個棧。棧的大小,是全局變量;默認是2個頁,256行;如果對象數目多或很多線程,就可設大些;最大是32KH。為何JAVA等是每個線程需要一個2MB棧,大量占用內存?我認為這是設計問題。在APO中,對象消息處理代碼塊內只是一些少量的自動變量;普通對象完成消息處理後就會自動清除,相對來說不占空間;而線程對象可能會因等待消息,從而暫時讓出CPU,但並不讓出占據棧內的那部分空間;就可能會同時有多個線程占據棧空間。解決辦法就是增大棧空間吧;APO中一個進程就算有6萬個對象在活動,半個數據塊的棧空間就足夠了。編譯器將自動變量翻譯成對棧指針(0地址)的相對偏移地址。
◆堆:存放用動態產生的數據
堆內存用來存放由動態創建的對象和數組等動態變量。在堆中分配的內存,由APO的內存管理員來管理或由用戶代碼釋放。動態聲明(用DYV標識符)一個變量,DYVBU64KH
在堆中產生了一個動態數組或動態對象後,還可以在相應對象屬性表中定義一個特殊的變量,讓對象屬性表中這個變量的取值最終找到數組或對象在堆內存中的首地址,對象屬性表中的這個變量就成了數組或對象的引用變量。
◆常量池:存放常量(一般放在方法表區)
3、表
1)、類表
金字塔形的樹狀類表,有三個層次:
1))、純抽象類:只有抽象屬性和抽象方法;接口是只有抽象方法的純抽象類。只有抽象屬性的類為概念類。
2))、普通抽象類:可有抽象屬性和普通屬性,可有抽象方法和普通方法。
3))、具體類:只有普通屬性和普通方法。
接口調用是:對象名.接口方法名字();
但編譯器如發現該對象所屬的類有相同的方法名,就直接編譯成直接調用對象所屬類的方法了。所以,接口實例化的好處只是知道接口之間,類與接口之間的繼承關系吧。
類表:以類號順序排列的類項構成的表;每類項占4行存儲空間;最多8KH。
類項:前2行為類繼承關系字數組,可標識32個父類以上的類號。第3行是子類變量,可標識16個下級子類號。對於類繼承關系變量或子類變量;如果第一個字符是0xA5A5,表示多於32個繼承關系或16個子類,需要動態變量來擴充;隨後的3個字符是動態變量指針(所占行數、塊號、行開始地址)。對於抽象類,第4行也屬子類變量。對於具體類,第四行:16位一個參數,方法表指針(方法表所在的塊號、方法表的行開始地址),方法數,方法表所占的行數;本類的對象數,屬性表的屬性項數,屬性表所占的行數。
16位的類號:高3位為類的類型,從111到000依次是:根類、純抽象類、概念類、接口、普通抽象類、包含類(也是小根類)、具體類、類庫(只有方法的具體類)。最低2位為00;指向該類項的第0行地址。中間十一位是類的序號,最多標識到2K個類。
對象頭表:以16位對象號順序排列的1W對象屬性表入口指針(塊號,行開始地址)構成的表;最多64K個對象占用8KH。
在自然界中,存在就是合理;我們不應改變自然規則,使自己處於狹窄的時空。
2)、編譯器符號表簡單介紹
表變量BBL對應的地址值:
BUxHBBL{};//高16位行數x,低16位行開始相對地址;塊號在對象頭表。
元素或屬性或說是變量的聲明:BUxW
BUxW聲明了變量的數據內容包含x個字,或說是2x個字符串。BUx 聲明了包含x個位,BUxK 聲明了包含x K個位,BUxM 聲明了包含x M(百萬)個位。在APO中,位容器的聲明通常以行(256位)為單位的;因為APO包含有256位為單位的位處理器。
對象屬性表的存儲布局:
BU19H表1{ // 19行的表變量。(高16位為行數x,低16位為行開始地址)
DYV
// 行開始地址是第0行;行的字地址0;引用變量最小占一行,一行8個引用。
// 行開始地址是第1行,行字地址為0,長度99,占行數13行,最後行空5W。
// D變量行開始地址是第14行;行的字地址為0,長度32W,占行數4行。
引用變量內容定義:高16位是動態變量所占行數,低16位為對象號。動態對象序號在對象號區,0xC000- 0xFFFF。8K
表內的變量可看作全是1W的數組,對於靜態表來說;要實現不同寬度數組就要定義表內表了。而動態數組就靠程序員了;當然,對於動態數組;編譯器還是會多編譯一條傳送數組的16位寬度的成員項數和成員位寬度到CPU的寄存器B的指令的。
編譯器翻譯原則:
1)、先編排4W的變量在一起,其次是2W的變量;因它們可能是數值,也易於對齊。動態變量的所有引用變量(1W一個)放在一起成字數組,放於表頭,最少占1行(8個)。
2)、多於4W的變量地址都是以行地址開始。
3)、最後是在有空洞的行插入1W/2W/3W的變量地址。
表內變量對應的地址值定義:
BU32 BL;
// T標志為1是變量的內容長度小於1行; Bit29–24的6位為長度及行的字開始地址。Bit30為1為引用變量,Bit29–27:111數組、110向量;100為對象引用等。Bit26–24的3位為行的字開始地址。變量類型屬性字節Bit23–16用於特殊對象。
編譯器翻譯後的變量地址:不到一行的(8位,8位,16位)一組,保留字節為0,超過一行的(16位,16位)一組。
E = (10 100000,0,18);F= (10 010 100,0,18);
G = (11XXX 000,0,0);B = (99,1);D = (32,14);
A = (10 001100,0,13);C = (10 001110,0,13);
一個表必須完全被包在一個數據塊裡,一個數據塊是64KH。我們可以在表內或代碼塊內聲明一個動態變量為一個數據塊,甚至連續數據塊變量;而在表內引用該變量。但最長的字符串只有1M個字符,占一個數據塊。一個數據塊可以裝多個表;或許有空洞。我認為內存合理分配,也應該需要程序員參與。字符串,數組,代碼段在表內只是一個內容不超過32KW的變量。變量、對象的名字等可以用漢語雙拼簡寫或英文簡寫或直接中文也行,APO編譯器支持U編碼的。
方法(method)表聲明:
BUNH 方法表名字{//變量地址(方法表的16位行數,方法表的16位起始行地址)
BUxW 方法1; // 方法1變量地址(0,15位方法的指令長度x,方法的起始行地址)
….
}
方法表中的方法是一個接著一個的以行為單位緊密存放的。調用方式是:
類名(或對象名字).方法名字;
二、面向對象編程
我們可以這樣說:萬物皆對象,在程序裡一切皆變量(對象、方法、屬性、表)。
面向過程的思想:由過程、步驟、函數組成,以過程為核心;面向過程是先有算法,後有數據結構。
面向對象的思想:以對象為中心,先開發類,得到對象,通過對象之間相互通信實現功能。面向對象是先有數據結構,然後再有算法。
我們知道同一個類的對象可以有很多個,對應的對象屬性表也有很多;但只有一個方法表。具有相同或相似性質的對象的抽象就是類。因此,對象的抽象是類,類的具體化就是對象,也可以說類的實例是對象。一個程序是由很多不同或相同種類的對象組成,以進程的形式在內存中運行時還可能動態增減一些新的對象。進程的執行體是線程對象,也就是說對象或方法的運行必須是通過線程對象來進行。在APO中可以有最少一個線程對象(主線程),最大32K個線程對象。其它活動對象最多也只是32K個;所以在APO中,一個進程最多有64K個打開的對象。但這並不包含打開的文件對象,一個進程打開的文件對象數最大可達64K個;是另外的文件對象管理員管理的。其實,在一個進程裡;通常有4個管理員:文件對象管理員、線程對象管理員、普通對象管理員、內存管理員。管理員是通用的,系統內含的,每一個程序都要繼承它們。我會以編寫新型的APO操作系統為例,來說明面向對象編程的方式、過程。
1、面向對象的特征:
(1)
(2)分類性
任何類的劃分都是主觀的,但必須與具體的應用有關。
(3)繼承性
繼承性是子類自動共享父類數據結構和方法的機制,這是類之間的一種關系。在定義和實現一個類的時候,可以在一個已經存在的類的基礎之上來進行,把這個已經存在的類所定義的內容作為自己的內容,並加入若干新的內容。
在類層次中,子類只繼承一個父類的數據結構和方法,則稱為單重繼承。
在類層次中,子類繼承了多個父類的數據結構和方法,則稱為多重繼承。
1)、繼承的特點:
第一、子類擁有父類的屬性和方法;
第二、子類可以有自己新的屬性和方法;
第三、子類可以重寫父類的方法;
第四、可以聲明自己為父類,創建子類。
2)、多重繼承:
在APO中,可以說表、類繼承關系數組變量就很自然地實現了多重繼承。多重繼承是普遍的、自然的。但多重繼承需解決二義性問題或說是菱形繼承問題。
何時出現二義性?APO如何解決?
1))、當一個派生類從多個基類派生,而這些基類又有同名成員,在對該同名成員進行訪問時,可能會出現二義性。
例子:類C3繼承了2個類C1,C2中的同名方法f(),一個是c1的,另一個是c2的,如果不顯式聲明調用哪一個,編譯器就不知道你要調用哪一個。這時就必須使用作用域分辨符:寫為C3.C1.f();或C3.C2.f();就可以了。但C++非得通過C3的對象才可以,因為f()本質上是C1或者是C2的成員函數,而類成員函數都是要通過對象C3來調用的,如果你想通過C1.f()來直接調用,那麼f()必須是static。為何這樣?如果C1是C3的派生類,那麼在C3對象中調用C1.f()就變成父類可以調用子類的方法了。所以,C3.C1.f();或this.C1.f();編譯器就會查找C3對象是否有對C1的引用,有則調用,否則報錯。APO的調用都是:類名.方法名;而不管類名是父類的、基類的、本類的、庫類的、系統類的、動態類的等等;本類的派生類則報錯。所以,APO調用是可以直接寫為:C1.f()的,編譯器把C1對象號和類號編譯傳過來後,匯編代碼會在C3對象所屬的類表項中的類繼承關系變量中查找是否有C1的類號,如有則從類表項中找到對應的方法表基址;再加上方法表的方法相對地址最終指向方法f()。靜態類由編譯器直接給出方法的相對入口地址;如果C1、C2是動態對象,那只能在C3的對象所屬的類表項中的類繼承關系變量中分別查到C1、C2的類號,沒有則報錯;從而在類表項中找到對應的方法表基址;再加上方法表的方法相對地址最終指向方法f()。所以,APO在這點上是沒有二意的。
2))、當一個派生類從多個基類派生,而這些基類又有一個公共的基類,在對該基類中說明的成員進行訪問時,可能會出現二義性。這就是著名的菱形繼承問題。
當在多條繼承路徑上有一個公共的基類時,在這些路徑中的某幾條匯合處,這個公共的基類就會產生多個副本;從而出現二義性。比如一個類繼承了100個父類,而父類又繼承100個祖父類。。。形成許許多多的路徑,最終都到達10個根類。就算只是2級,都有10000條路徑通向10個根類,也會多產生了9990個對象副本;並造成二意。
在C++中,為解決菱形繼承還引入了一個更扭曲的虛繼承。Python支持多種繼承, 並且因為所有的對象都繼承於共同的基類Object,導致任何多重繼承其實都是菱形繼承,當你真的開始自己用多重繼承時,其實繼承結構已經是一張網了,對此, Guido VanRossum的答案是用深度優先搜索,靠基類排列的順序來決定, 顯的更加神奇。
C++的多重繼承很麻煩,而且又容易出錯。而無數的JAVA用戶者則是把C++的多重繼承當作笑話和C++混亂的根源,自豪的宣布JAVA沒有這個問題。J那就是沒有沒有C++自由的多重繼承, 只支持對實現的單繼承, 並引入了接口, 只允許接口的多重繼承。並且, 在繼承接口和繼承基類形式上特別加了一些區別,所以實際中, 很多人並不把這叫做多重繼承,並宣稱JAVA沒有多重繼承。“組合優於繼承”這一設計原則,體會了JAVA社區提倡的面向接口編程。“繼承本身就是一種強耦合”,就是一種子類對父類的依賴耦合,在一些書籍中提到, 甚至繼承本身都是不提倡的, 也就是說, 提倡的是基於對象的設計(OB), 而不是面向對象的設計(OO)。JAVA最多算半個多重繼承,只是行為多繼承。大家都知道, 加一個繼承只要一個單詞,而加一個對象的組合調用,往往需要增加N個函數接口,以及N個調用(雖然有種東西叫做委托)。
Mix-in類是具有以下特征的抽象類:
不能單獨生成實例
不能繼承普通類
通過這種在接口和普通多重繼承之間的折衷,Mix-in提供了對實現的多重繼承,同時對其進行了限制, 使得繼承結構不會成菱形, 而是和單一繼承一樣的樹型. 在Ruby中, Mix-in是通過模塊(module)的概念來實現的。有趣的事情是,在編程這件事情上, 很多時候, 折衷的方法卻往往是優秀的方法。但不符合自然的東東,終是遺憾。
APO是很自然的支持多重繼承。在對象所屬的類表項中的類繼承關系變量中有類關系數組,包括父類的,所有的關系類都是唯一的;不會出現多副本的情形。對象初始化;是先調用父類對象初始化方法,再對關系數組中的具體類的對象作初始化;其實,對象亂序初始化也沒問題的。
扭曲式多重繼承、循環式多重繼承會產生父子不清的現象。比如,一只黑狗有一個兒子白狗,而黑狗的母親狗又跟兒子白狗生了一只公黃狗。公黃狗是孫子狗?還是兒子狗?還是算父親狗?我都轉暈了。盡管自然界,這種現象極少;但還是有的。這種扭曲多重繼承的特點就是:相互引用就可相互調用;也就是基類可調用派生類的方法。APO的多重繼承也同樣是自然的支持這種扭曲式多重繼承。
多態性是指相同的操作或方法、過程可作用於多種類型的對象上並獲得不同的結果。不同的對象,收到同一消息可以產生不同的結果,這種現象稱為多態性。多態性允許每個對象以適合自身的方式去響應共同的消息。多態性增強了軟件的靈活性和重用性。
邊學、邊抄、邊寫下這段章節後的感想:何為抽象?那是億眾矚目的地方,那是靈魂深處的悠游,那是聯想的發源地,那是萬川歸一的大海,那是世界的最頂峰,也是宇宙的邊緣。從那看進去:一眼星河湧現、神光飛舞、浮想纏綿。
抽象,顧名思義,就是抽掉了具體形象的東西。把具體概念的諸多個性排出,集中描述其共性,就會產生一個抽象性的概念。抽象概念的外延大,內涵小,具體概念的外延小,內涵大。要抽象,就必須進行比較,沒有比較就無法找到在本質上共同的部分;所以抽象的過程也是一個裁剪的、分類的過程。在抽象時,同與不同,決定於從什麼角度上來抽象;抽象的角度取決於分析問題的目的。抽象通過分析與綜合的途徑,運用概念在人腦中再現對象的質和本質的方法,分為質的抽象和本質的抽象。分析形成質的抽象,綜合形成本質的抽象(也叫具體的抽象)。萬物都是容器,類名就是從無數的容器中分離出具有一些相同屬性、相同方法的容器集的名稱;所以分類的過程就是抽象過程。類名就是一種抽象名,我們把類的個體容器稱為對象;所以,也說萬物都是對象。但從另一角度看,“萬物”、“對象”就是一個更為抽象的概念;它們泛指一切。我們根據一些特性,也即是屬性;抽象出“蘋果”類,“蘋果”也有大小、青紅之分等等;所以“蘋果”類下還可再分成具體類,之後才到具體的某個蘋果。類似的還有“香蕉”、“梨子”等等,我們又從這些“蘋果”、“香蕉”、“梨子”等抽象概念中,抽象出它們的共性,得到“水果”類。而“水果”類可以說它們的成員也是類,所以說“水果”是類中類,是抽象之後的再抽象;是屬於抽象類。這還沒完,“水果”源自果樹,是果樹類中的一個屬性。而果樹又是植物類中的一個種類;植物和動物構成生物類;生物類和非生物類才到達最頂端的根——物類。你可以說“茶杯”是物,是非生物;但不能說是“水果”。從這“金字塔”看,最頂端只有一個“物”類,往下只是2個,再往下呢?植物類、動物類就多了,看你的分類角度吧。對於動物來說,簡單的就是死、活動物類;復雜的你知道的。抽象類的盡頭才是具體類,之後是它們的個體;一個個對象。所以,金字塔有4層,第四層:各種對象;第三層:具體類;第二層:抽象類(有多個層次);第一層:最頂層,只有一個抽象的根類。從金字塔層次看來,似乎只有單繼承,其實不然的;如“水果”就不一定就是源自果樹,它也可能來自植物籐類。就拿衣服類來說,衣服,春、夏、秋、冬衣服,長衫,短袖,棉衣等等都是抽象類;自然界太多的抽象類了。可以說,抽象類、具體類、個體對象充滿了我們的世界。我們的語言就是對它們及相互關系的描述;計算機語言也應該如此!
在面向對象領域,抽象類主要用來進行類型隱藏。我們也可以構造出一個固定的一組行為的抽象描述,但是這組行為卻能夠有任意個可能的具體實現方式。這個抽象描述就是抽象類,而這一組任意個可能的具體實現則表現為所有可能的派生類。
抽象類往往用來表征我們在對問題領域進行分析、設計中得出的抽象概念,是對一系列看上去不同,但是本質上相同的具體概念的抽象。比如:如果我們進行一個圖形編輯軟件的開發,就會發現問題領域存在著圓、三角形這樣一些具體概念,它們是不同的,但是它們又都屬於形狀這樣一個概念,形狀這個概念在問題領域是不存在的,它就是一個抽象概念。正是因為抽象的概念在問題領域沒有對應的具體概念,所以用以表征抽象概念的抽象類是不能夠實例化對象的。
1))、語法:
抽象類也有方法的抽象。例如動物是一個抽象類,他的移動方法還沒有確定,因為有的動物是四條腿移動,有兩條腿移動,有的飛,有的爬,但是他們都能移動,這樣你可以把動物定義為抽象類,將動物的移動方法定義為抽象方法,強制繼承它的子類去實現。而且你在動物這個類中移動雖然沒有實現,但是可以調用他。
2))、接口
接口(interface)其實就是一個特殊的抽象類;是抽象類的變體。差別在於接口中的方法必須都是抽象方法,不可以有普通方法而已。既然是一種抽象類,接口就只能繼承其它接口,抽象類和普通類也可以繼承接口和抽象類。在APO中,抽象類可以包含接口!接口可以只是抽象類的行為部分。
接口(Interface)是用來定義行為的!
APO中的抽象類(Abstract
具體類()是用來執行行為的!
Interface接口表述“has
3))、實現簡介
(4)消息和方法
對象之間進行通信的結構叫做消息。在對象的操作中,當一個消息發送給某個對象時,消息包含接收對象去執行某種操作的信息。發送一條消息至少要包括說明接受消息的進程號、對象名、發送給該對象的消息名(即對象名、方法名)。一般還要對參數加以說明,參數可以是認識該消息的對象所知道的變量名,或者是所有對象都知道的全局變量名。
2、方法
類中操作的實現過程叫做方法,一個方法有方法名、參數、方法體。方法只有一份,供所有的同類的對象使用!而屬性是每個對象一份,因為每個對象的都不一樣。
方法重載:就是在同一個類中,方法的名字相同,但參數個數、參數的類型或返回值類型不同!實際上,還是調用不同的方法。
方法重寫:它是指子類和父類的關系,子類重寫了父類的方法,但方法名、參數類型、參數個數必須相同!覆蓋==重寫叫法不同罷了。實際上,父類的方法還在,只是本類的方法與父類的同名吧。
至於加載的意思,其實就是讓編譯器執行某段程序,可以是類可以是包可以是任何編譯器能夠編譯的代碼。
3、公有、私有
面向對象程序設計的重點是類的設計,而不是對象的設計。類可以將數據和函數封裝在一起,其中函數表示了類的行為(或稱服務)。類提供關鍵字public、protected和private 用於聲明哪些數據和函數是公有的、受保護的或者是私有的。這些類型聲明的具體的實現是由編譯器來判斷與進行的。
1)、公共的(public):公有是所有包括本類及外部類都可以調用。
2)、私有的(private):私有是只有本類可以調用。這是為了安全和健壯性;保證了不會被別人在外部修改。
3)、受保護的(protected):本類、派生類可以訪問,外部類不行。
4)、默認控制訪問符(friendly):
為了實現數據的封裝,提高數據的安全性,我們一般會把類的屬性聲明為私有的,而把類的方法聲明為公共的。這樣,對象能夠直接調用類中定義的所有方法,當對象想要修改或得到自己的屬性的時候就必須要調用以定義好的專用的方法才能夠實現。提倡的是:“對象調方法,方法改屬性”。