Objective-C 2.0 with Cocoa Foundation --- 3,類的聲明和定義

原文鏈接:http://www.cnblogs.com/yaski/


3,類的聲明和定義

本系列講座有着很強的前後相關性,如果你是第一次閱讀本篇文章,爲了更好的理解本章內容,筆者建議你最好從本系列講座的第1章開始閱讀,請點擊這裏。 

上一章我們寫了一個非常簡單的Obejctive-C下面的Hello, World!的小程序,並且對裏面出現的一些新的概念進行了解釋。這一章,我們將要深入到Objective-C的一個基本的要素,也就是類的聲明和定義。通過本章的學習,同學們應該可以定義類,給類加上變量,還有通過方法訪問類的變量。不過準確的說,變量和方法的名詞在Objective-C裏面並不是最準確的稱呼,我們暫時引用Java的定義,稍後我們將統一我們的用語定義。

3.1,本章的程序的執行結果。

我們將構築一個類,類的名字叫做Cattle,也就是牛的意思,今年是牛年而且我還想給在股市奮戰的同學們一個好的名字,所以我們暫時把這個類叫做牛類。

我們在main裏面初始化這個牛類,然後調用這個類的方法設定類的變量,最後調用這個類的一個方法,在屏幕上輸出,最終輸出的結果如下圖3-1所示

 

圖3-1,牛類的輸出結果

完整的代碼在這裏。不過爲了熟悉編輯環境以及代碼,筆者強烈建議同學們按照下面的步驟自己輸入。 

3.2,實現步驟

第一步,按照我們在第二章所述的方法,新建一個項目,項目的名字叫做03-Hello Class。當然,你也可以起一個別的更好聽的名字,比如說Hello Cattle等等,這個並不妨礙我們的講解。如果你是第一次看本系列文章,請到這裏參看第二章的內容。

第二步,把鼠標移動到左側的窗口的“Source”目錄,然後單擊鼠標右鍵,選擇“Add”,然後界面上會出來一個子菜單,在子菜單裏面選擇“New File...” 。如圖3-2所示:

 

圖3-2,新建文件 

第三步,在新建文件對話框的左側選擇“Cocoa Touch Classes”,然後在右側窗口選擇“NSObject subclass”,然後單擊“Next”。如圖3-3所示:

 

 

 第四步,在“New File”對話框裏面的“File Name”欄內輸入“Cattle.m”。注意,在確省狀態下,Xcode爲你加上了“.m”的後綴,這個也是編譯器識別Objective-C源文件的方法,沒有特殊理由請不要修改這個後綴,否則會讓編譯器感到不舒服。另外請確認文件名字輸入欄的下方有一個“Also create "Cattel.h"”選擇框,請保持這個選擇框爲選擇的狀態。如圖3-4所示。

 

 第5步,在項目瀏覽器裏面選擇“Cattle.h”文件,把文件改爲如下代碼並且保存(Command鍵+S):

#import <Foundation/Foundation.h>


@interface Cattle : NSObject {
    
int legsCount;
}
- (void)saySomething;
- (void)setLegsCount:(int) count;
@end
爲什麼legsCattle者,牛也;legs者,股也。不過牛股裏面的牛正確的英文說法應該是Bull,請大家不要着急,我們會在類的繼承裏面命名一個Bull類的。

 

 

第六步,在項目瀏覽器裏面選擇“Cattle.m”文件,把文件改爲如下代碼並且保存(Command鍵+S):

#import "Cattle.h"


@implementation Cattle
-(void) saySomething
{
    NSLog(
@"Hello, I am a cattle, I have %d legs.", legsCount);
}
-(void) setLegsCount:(int) count
{
    legsCount 
= count;
}
@end

 

第七步,在項目瀏覽器裏面選擇“03-Hello Class.m” 文件,把文件改爲如下代碼並且保存(Command鍵+S):

#import <Foundation/Foundation.h>
#import 
"Cattle.h"

int main (int argc, const char * argv[]) {
    NSAutoreleasePool 
* pool = [[NSAutoreleasePool alloc] init];

    id cattle 
= [Cattle new];    
    [cattle setLegsCount:
4];
    [cattle saySomething];

    [pool drain];
    
return 0;
}

 

第八步,選擇屏幕上方菜單裏面的“Run”,然後選擇“Console”,打開了Console對話框之後,選擇對話框上部中央的“Build and Go”,如果不出什麼意外的話,那麼應該出現入圖3-1所示的結果。如果出現了什麼意外導致錯誤的話,那麼請仔細檢查一下你的代碼。如果經過仔細檢查發現還是不能執行的話,可以到這裏下載筆者爲同學們準備的代碼。 如果筆者的代碼還是不能執行的話,請告知筆者。

3.3,類的聲明

從Objective-C名字我們就可以得知,這是一個面向對象的語言。面向對象的一個最基礎的要素就是類的概念,Objective-C也不例外。所謂的類的概念,其實是從C語言的結構體發展而來的。我們知道,C語言裏面的結構體僅僅有數據的概念,面向對象的語言不僅僅支持數據,還可以在結構體裏面封裝用於存取結構體數據的方法。結構體的數據和方法結合,我們把整個結構體稱爲類(Class)。僅僅有了類,是不能執行任何操作的,我們必須把類進行實體化,實體化後的類我們稱之爲對象(Object)。從這個角度上來說,我們可以認爲類是對象的模版。

如果要使用類,那麼和構造體相類似,我們必須聲明這個類。

請參照“Cattle.h” 文件:

1 #import <Foundation/Foundation.h>
2 
3 
4 @interface Cattle : NSObject {
5     int legsCount;
6 }
7 - (void)saySomething;
8 - (void)setLegsCount:(int) count;
9 @end

如果看過本系列第二章的同學們,第一行應該是一個老面孔了,我們知道我們需要這個東西免費獲得蘋果公司爲我們精心準備的Foundation Framework裏面的很多的功能。如果不使用這個東西的話,我們的工作將會很複雜。

同學們請看第4行和第9行的第一個字母,又出現了“@”符號。爲什麼說又呢,因爲我們在第二章的字符串前面也看到過這個東西。字符串前面出現這個符號是因爲我們需要和C語言的字符串定義區別開來,我們需要編譯器導向。在這裏,我要告訴同學們的是,這裏的“@”符號的作用還是同樣是編譯器導向。我們知道Java和C++定義了一個關鍵字class用於聲明一個類,在Objective-C裏面,不存在這樣的關鍵字。在Objective-C裏面,類的定義從@interface開始到@end結束,也就是說,編譯器看到了@interface就知道了這是類的定義的開始,看到了@end就知道,類的定義結束了。

我們這裏類的名字是“Cattle”,我們使用了空格和@interface分開,通知編譯器,我們要聲明一個類,名字叫做Cattle。在Cattle的後面,我們有“: NSObject”,這是在通知編譯器我們的Cattle是從NSObject繼承而來的,關於繼承和NSObject,我們將在後面的章節裏面詳細介紹,關於“: NSObject”我們現在可以理解爲,通過這樣寫,我們免費獲得了蘋果公司爲我們精心準備的一系列的類和對象的必備的方法。NSObject被稱爲root class,也就是根類。在Java或者.NET裏面,根類是必備的,C++不需要。在Obejctive-C裏面原則上,你可以不使用NSObject,構築一個你自己的根類,但是事實上這樣做將會有很大工作量,而且這樣做沒有什麼意義,因爲蘋果爲你提供的NSObject經過了很長時間的檢驗。也許有好奇心的同學們想自己構築根類,不過至少筆者不會有自己去構築一個根類的慾望。

好的,大家現在來看第5行。我們以前把這個東西叫做變量,我們從現在開始,需要精確的使用Objective-C的用語了,這是實體變量(instance variables,在有的英文資料裏面會簡寫爲iVars)。雖然作爲一個Cattle,它有不止一個實體變量,比如說體重等等,但是爲了代碼簡潔,我們在這裏聲明一個就是牛腿也就是牛股的數目,這個實體變量是int型,表示一個整數,我們當然不希望有4.5個牛腿。

我們來看第6行,第6行的括弧和在第4行最後的括弧用來表示實體變量的定義區間,編譯器認爲在這兩個括弧之間的定義是實體變量的定義。當然,如果你的類沒有實體變量,那麼這兩個括弧之間允許什麼都沒有。和Java以及C++不一樣,Objective-C要求在括弧裏面不能有方法也就是函數的定義,那麼Objective-C裏面的方法的定義放在什麼地方呢,請看第7行。

第7行的第一個字母是一個減號“-”。這個減號就是告訴編譯器,減號後面的方法,是實體方法(instance method)。實體方法的意思就是說,這個方法在類沒有被實體化之前,是不能運行的。我們在這裏看到的是減號,在有減號的同時也有加號,我們把帶加號的方法稱爲類方法(class method),和實體方法相對應,類方法可以脫離實體而運行。關於類方法,我們將在後面的章節裏面講解。大家也許可以想起來在C++和Java裏面同樣也有類似的區分,不是麼。

在Objective-C裏面方法的返回類型需要用圓括號包住,當編譯器看到減號或者加號後面的括號了之後,就會認爲這是在聲明方法的返回值。你也可以不聲明返回值,Objective-C的編譯器會給沒有寫顯式的返回值函數加上一個默認的返回值,它的類型是id,關於id類型我們將在後面講解,不過筆者不推薦不寫返回值的類型。

在第7行我們定義了這個方法的名字是saySomething,當然Cattle說的話我們人類是聽不懂的,筆者只是想讓它在我們的控制檯裏面輸出一些我們可以看得懂得字符串。方法的聲明最後,需要分號來標識,這一點保持了和C沒有任何區別。

我們再來看看第8行,第8行和第7行多了“:(int) count”。其中冒號放在方法的後面是用來表示後面是用來定義變量的,同樣變量的類型使用括號給包住,如果不寫變量的類型的化,編譯器同樣認爲這是一個id類型的。最後的count,就是變量的名字。如果有不只一個變量怎麼辦?答案就是在第一個變量後面加冒號,然後加園括號包住變量的類型,接着是變量的名字。

好了,我們在這裏總結一下,類的定義方法如下:

@interface 類的名字 : 父類的名字 {
    實體變量類型 實體變量名字;
    
}
- (返回值類型)方法名字;
+ (返回值類型)方法名字;
- (返回值類型)方法名字:(變量類型) 變量名字 標籤1:(變量類型) 變量1名字;

@end

 

...的意思在本系列入門講座裏面,...表示省略了一些代碼的意思。

 

3.4,類的定義 

我們在前一節講述了類的聲明,我們下一步將要看一下類的定義。請同學們打開“Cattle.m”文件:

 

 1 #import "Cattle.h"
 2 
 3 
 4 @implementation Cattle
 5 -(void) saySomething
 6 {
 7     NSLog(@"Hello, I am a cattle, I have %d legs.", legsCount);
 8 }
 9 -(void) setLegsCount:(int) count
10 {
11     legsCount = count;
12 }
13 @end
14 

Cattle.m文件的第一行就import了Cattle.h文件,這一點和C的機制是一樣的,關於#import的說明請參照第二章。

我們來看第4行和第13行,和頭文件裏面的@一樣,我們這裏類的定義也是使用的編譯導向。編譯器會把從@implementation到@end之間的部分看作是類的定義。@implementation的後面有一個空格,空格的後面是我們的類的名字Cattle,這是在告訴編譯器,我們要定義Cattle類了。第4行和第13行之間是我們在頭文件裏面定義的實體方法或者類方法的定義部分,當然我們的類如果沒有任何的實體方法和類方法的話,我們也許要寫上@implementation和@end,把中間留爲空就可以了。

第5行是我們定義的saySomething的實現,我們可以發現第5行的內容和頭文件Cattle.h的第7行是一致的。筆者個人認爲在編寫實體方法和類方法的定義的時候,爲了避免手工輸入產生的誤差,可以從頭文件當中把聲明的部分拷貝過來,然後刪除掉分號,加上兩個花括弧。我們知道地6行到第8行是方法的定義的部分,我們再來看看第7行。第7行和第二章的Hello, World輸出有些相似,只不過多了一個%d,還有實體變量legsCount,這個寫法和C語言裏面的printf是類似的,輸出的時候會使用legsCount來替代字符串裏面的%d。

第9行的內容和Cattle.h的第8行一致的,這個不需要再解釋了。我們來看看第11行,第11行是在說,把參數count的數值賦值給實體變量legsCount。我們可以通過使用setLegsCount方法來控制Cattle對象裏面legsCount的數值。

這部分內容的關鍵點爲@implementation和@end,理解了這個東西,其餘的就不難理解了。我們來總結一下,類的定義部分的語法:

@implementation 類的名字
-(方法返回值) 方法名字
{
    方法定義
    
}
-(方法返回值) 方法名字:(變量類型) 變量名字
{
    方法定義
    
}

@end

 

3.5,類的實例化

我們在3.3和3.4節裏面分別聲明和定義了一個Cattle的類。雖然定義好的類,但是我們是不能直接使用這個類的。因爲類的內容需要被調入到內存當中我們稱之爲內存分配(Allocation),然後需要把實體變量進行初始化(Initialization),當這些步驟都結束了之後,我們的類就被實例化了,我們把實例化完成的類叫做對象(Object)。好的,我們知道了我們在類的實例化過程當中需要做哪些工作,我們接着來看看我們已經搞定的Cattle類的定義和聲明是怎樣被實例化的。

 1 #import <Foundation/Foundation.h>
 2 #import "Cattle.h"
 3 
 4 int main (int argc, const char * argv[]) {
 5     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
 6 
 7     id cattle = [Cattle new];    
 8     [cattle setLegsCount:4];
 9     [cattle saySomething];
10 
11     [pool drain];
12     return 0;
13 }

同學們請看第7行的第一個單詞id。 id是英文identifier的縮寫,我們在很多地方都遇到過id,比如說在博客園裏面,我們都使用id來登陸系統的,我們的id就代表着系統的一個用戶。由於id在一個系統當中是唯一的,所以系統獲得我們的id之後就知道我們是誰了。Objective-C也是一樣的道理,使用id來代表一個對象,在Objective-C當中,所有的對象都可以使用id來進行區分。我們知道一個類僅僅是一些數據外加上操作這些數據的代碼,所以id實際上是指向數據結構的一個指針而已,相當於void*。

第7行的第二個單詞是cattle,就是我們給這個id起的一個名字。當然,你可以起系統保留的名字以外的任何名字,不過爲了維持代碼的可讀性,我們需要一個有意義的名字,我們這裏使用頭文字爲小寫的cattle。

第7行的[Cattle new]是創建對象,new實際上是alloc和init的組合,在Objective-C裏面創建對象是一個爲對象分配內存和初始化的過程。new,alloc還有init定義在Cattle的超類NSObject裏面,筆者將要在第7章裏面詳細的解釋一下如何創建對象。在第7章之前我們都是用new來創建對象。 

Objective-C裏面的方法的使用和其他語言有些不同,Objective-C使用消息(Message)來調用方法。所以筆者認爲在講解第7行等號右邊的部分之前,需要首先向大家介紹一個我們的新朋友,消息(Message)。所謂的消息就是一個類或者對象可以執行的動作。消息的格式如下:

[對象或者類名字 方法名字:參數序列];

首先我們觀察到有兩個中括弧, 最右邊的括弧之後是一個分號,當編譯器遇到了這個格式之後會把中間的部分當作一個消息來發送。在上文的表達式當中,包括中括弧的所有部分的內容被稱作消息表達式(Message expression),“對象或者類名字”被稱作接收器(Receiver),也就是消息的接受者,“方法名字:參數序列”被稱爲一個消息(Message),“方法名字”被稱作選擇器(Selector)或者關鍵字(Keyword)。Objective-C和C語言是完全兼容的,C語言裏面的中括弧用於表示數組,但是數組的格式明顯和消息的發送的格式是不一樣的,所以我們可以放心,編譯器不會把我們的消息發送當作一個數組。

我們來回憶一下C語言裏面函數的調用過程,實際上編譯器在編譯的時候就已經把函數相對於整個執行包的入口地址給確定好了,函數的執行實際上就是直接從這個地址開始執行的。Objective-C使用的是一種間接的方式, Objective-C向對象或者類(具體上是對象還是類的名字取決於方法是實體方法還是類方法)發送消息,消息的格式應該和方法相同。具體來說,第7行等號右邊的部分[Cattle new]就是說,向Cattle類發送一個new的消息。這樣當Cattle類接收到new的時候,就會查找它可以相應的消息的列表,找到了new之後就會調用new的這個類方法,分配內存和初始化完成之後返回一個id,這樣我們就得到一個對象。

Objective-C在編譯的過程當中,編譯器是會去檢查方法是否有效的,如果無效會給你一個警告。但是編譯器並不會阻止你執行,因爲只有在執行的時候纔會觸發消息,編譯器是無法預測到執行的時候會發生什麼奇妙的事情的。使用這樣的機制給程序毫無疑問將給帶來極大的靈活性,因爲我們和任意的對對象或者類發送消息,只要我們可以保證執行的時候類可以準確地找到消息並且執行就可以了,當然如果找不到的話,運行會出錯。

任何事物都是一分爲二的 ---

任何事物都是一分爲二的,在我們得到了靈活性的時候我們損失的是執行的時間。Objective-C的這種方式要比直接從函數的入口地址執行的方式要消耗更多的執行時間,雖然編譯器對尋找的過程作過一定的優化。

有的同學會覺得奇怪,我們在Cattle裏面並沒有定義new,我們可以向Cattle發送這個類方法麼?答案是可以,因爲new在NSObject裏面,實際上響應new消息的是NSObject。實際上new類似於一個宏,並不是一個“原子”的不可再分的方法,關於詳細的情況,我們將在後續的章節裏面講解。

有了第7行的講解,那麼第8行的內容就不難理解了,第8行實際上是想cattle對象發送一個setLegsCount的消息,參數是4,參照Catttle.m,我們可以發現這個時候我們希望實體變量legsCount是4。第8行就更簡單了,就是說向cattle對象發送一個saySomething的消息,從而實現了控制檯的輸出。

3.6,本章總結

通過本章的學習,同學們應該掌握如下概念

  1. 如何聲明一個類
  2. 如何定義一個類
  3. 實體變量的定義
  4. 類方法和實體方法的定義
  5. id是什麼
  6. NSObject的奇妙作用
  7. 如何從類開始初始化對象
  8. 消息的調用

感謝大家看到這裏!我們下一章將要講述繼承的概念。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章