天天看點

黑馬OC聽課筆記(第二天)

記憶體中的五大區域

  1. 棧:存儲局部變量。
  2. 堆:開發人員通過malloc、calloc、realloc函數申請的位元組空間。
  3. BSS段:存儲未被初始化的全局變量、靜态變量。
  4. 資料段(常量區):存儲已被初始化的全局變量、靜态變量、常量。
  5. 代碼段:存儲程式的代碼。

類加載

在程式運作過程中,當某個類第一次被通路的時候,類的資訊會被加載到代碼段中,這個過程叫做類加載。一旦類被加載到代碼段中,就不需要重複加載了,直到程式結束才會被釋放。

  • 在建立對象的時候,肯定是需要通路這個類的。
  • 在聲明一個類的時候,CPU需要檢查這個類是否存在,是以會通路這個類。

對象在記憶體中如何存儲

如果我們在函數中建立一個對象,比如在main函數中建立一個Person對象:

#import <Foundation/Foundation.h>

@interface Person : NSObject{
    @public
    NSString *_name;
    int _age;
}
- (void) sayHi;
@end

@implementation Person

- (void) sayHi{
    NSLog(@"Hi~ morning!");
}

@end
int main(){
    @autoreleasepool {
        Person *p1 = NULL;
        p1 = [Person new];
    }
    return 0;
}           
  • Person *p1:這段代碼做的事情是在棧記憶體中申請一塊空間,Person類的指針變量 p1用來存儲這塊空間的位址。
  • [Person new]:這段代碼才是真正的建立對象:

    new做的事情:

    1) 在堆記憶體中申請一塊合适大小的空間;

    2) 按照Person類的模闆建立 Person對象:

    Person中有哪些屬性,就在對象中聲明哪些對象。對象中還有一個 isa屬性,是指針類型,指向對象所屬的類在代碼段中的位址;

    3) 初始化對象的屬性:

    - 如果屬性是基本資料類型,預設值為0;

    - 如果屬性是C語言中的指針類型,預設值為 NULL;

    - 如果屬性是OC語言中的指針類型,預設值為 nil。

    4) 傳回該類對象在堆記憶體中的位址給棧區。

黑馬OC聽課筆記(第二天)
黑馬OC聽課筆記(第二天)

注意:

a. 對象中隻有屬性,包含自己類本身的屬性,外加一個 isa指針,用來存儲對象所屬類在代碼段中的位址;

b. 方法被存儲在代碼段中;

c. 如何調用屬性:文法:指針名->屬性名;   通過指針名找到存在于堆中的對象,再通過對象調用對應的屬性;

d. 如何調用方法:文法:[指針名 方法名]; 先通過指針名找到對象,對象發現你要調用的是方法,就會通過 isa指針找到代碼段中的類資訊,而類資訊中儲存了所有屬性和方法。

e. 為什麼不把方法也儲存在對象中?因為同屬一個類的所有對象的方法都是一樣的,沒有必要把同樣的内容儲存多份,太浪費記憶體了,所有幹脆隻儲存一份在代碼段中。

f. 同屬一個類的所有對象的 isa存儲的位址都是一樣的,因為它們都指向了所屬類在代碼段中的位址。

NULL VS nil

1. NULL與 nil都隻能指派給指針類型,指派給指針,表示指針不指向記憶體中的任何空間,NULL其實是一個宏:#define NULL ((void*)0)  

nil 也是一個宏:#define nil _DARWIN_NULL   #define _DARWIN_NULL ((void*)0),是以它倆其實是一個東西。

/**
   NULL和nil是一個東西
 */

#import <Foundation/Foundation.h>

int main(){
    @autoreleasepool {
        if(nil == NULL){
            NSLog(@"這倆貨有點皮~");
        }
    }
    return 0;
}           

2. 雖然它倆幾乎是一樣的,但是建議在給C指針指派時用NULL,在給OC類指針指派時用nil。

3. 如果把 nil指派給類指針,此時使用指針名調用屬性和方法會是什麼情況呢?

調用屬性時報錯了:

黑馬OC聽課筆記(第二天)

調用方法時不報錯,但是方法不會執行:

黑馬OC聽課筆記(第二天)

多個指針指向同一對象

同一類型的指針是可以互相指派的,如下代碼中:首先建立一個 Person對象指向 p1,之後 p1指派給 Person的另一個指針對象 p2,此時 p1和 p2都指向了同一個對象,即多個指針指向同一對象,那麼不管我們通過 p1還是 p2去改變對象的屬性的值,其實修改的是用一個對象的屬性。

/**
   多個指針指向同一對象
 */

#import <Foundation/Foundation.h>

@interface Person : NSObject{
    @public
    NSString *_name;
    int _age;
}

@end

@implementation Person

@end

int main(){
    @autoreleasepool {
        Person *p1 = [Person new];
        p1->_name = @"小明";
        Person *p2 = p1;
        //p2 = [Person new];
        p2->_name = @"小花";
        NSLog(@"p1->_name = %@",p1->_name);
        NSLog(@"p2->_name = %@",p2->_name);
        
    }
    return 0;
}
           
黑馬OC聽課筆記(第二天)

多個指針指向同一對象記憶體分析

ps:目前為止,隻要看到 new關鍵字,就是建立了新的對象。

分組導航标記

有三種情況:

  • @pragma mark 分組名:在導航欄中顯示一個标題
  • @pragma mark - :在導航欄顯示一條分割線
  • @pragma mark - 分組名:在導航欄顯示一個分割線和标題
黑馬OC聽課筆記(第二天)

大家盡量都自己手動示範一遍吧。

【函數VS方法】

相同點:都是對某一特定功能的封裝,調用函數或方法,其内部的代碼邏輯就會被執行。

不同點:

a. 文法不同;

b. 定義的位置不同:比如定義一個函數,你可以在除了函數内部和@interface的大括号内之外的任意地方定義;而方法必須聲明在 @interface的大括号之外,在@implementation中實作;

c. 調用方式不同,函數可以直接調用,而方法需要依賴對象調用;

d. 函數是孤家寡人,而方法屬于類,是有伐木累的。

ps: 即使把函數定義在類中,它也屬于類,王者都是寡人,不需要依附任何人!(不過我們人類還是需要講究合作的(>_<)).

十宗罪

1. 類的聲明和實作不能嵌套,也就是說@interface中不能嵌套@implementation;

2. 類必須先聲明再實作;

3. 類的聲明和實作必須都要有;

4. 類名采用“駝峰命名法”,即每個單詞的首字母必須大寫;

5. 屬性名必須以下劃線開頭,這是必須遵守的規範,不然後面會把自己坑死的;

6. 雖然某些特殊情況下,類可以隻有實作而不用聲明,但不建議這麼玩:

黑馬OC聽課筆記(第二天)

7. 不允許在類的定義時給屬性賦初值;

8. 類的聲明必須放在類的使用的前面,類的實作可以放在類的使用的後面,但還是按照規範來;

9. OC中,在别的方法中調用類的屬性和方法,必須通過建立類的對象,再通過類對象調用屬性和方法;

10. 如果方法隻有聲明,沒有實作,編譯的時候隻會警告你方法沒有找到,但是運作時就會報錯:unrecognized selector sent to instance 0x1006b8c80'

黑馬OC聽課筆記(第二天)

隻要看到這個錯誤,要麼就是類中壓根沒有這個方法,要麼就是隻聲明了方法,而沒有實作方法。

多檔案開發

為什麼使用多檔案開發?我們前面的代碼都是寫在一個main.m的源檔案裡面,如果類太多的話,就顯得代碼比較臃腫,維護難度極大。

推薦的方式

把一個類寫到一個子產品中,而一個子產品至少包含兩個檔案:

一個.h的頭檔案,用來聲明類:

//
//  Person.h
//  OCStudyDemo
//
//  Created by Luffy on 2021/1/18.

//因為NSObject在Foundation中,是以要引入它
#import <Foundation/Foundation.h>
@interface Person : NSObject{
    NSString *_name;
    int age;
}
- (void) sayHi;
@end
           

一個.m的實作檔案,用來實作類:

//
//  Person.m
//  OCStudyDemo
//
//  Created by Luffy on 2021/1/18.
//

#import <Foundation/Foundation.h>
#import "Person.h"//一定要先導入類的頭檔案
@implementation Person
- (void) sayHi{
    NSLog(@"學習程式設計使我快樂~");
}
@end
           

增加子產品的簡便方式

右鍵項目 -->new file...-->選擇 Cocoa Class-->輸入子產品名,就會自動建立頭檔案和實作檔案。

對象與方法

1. 對象與方法的關系:

a. 對象作為方法的參數;

b. 對象作為方法的傳回值。

2. 類的本質其實就是我們自定義的資料類型,它在記憶體中的空間由我們開發人員自己決定,你多寫幾個屬性,它就大一點,你少寫幾個屬性,它就小一點。

什麼是資料類型:就是記憶體開辟空間的一個模闆。

3. 既然類是一種資料類型,那它就可以作為方法的參數,也可以作為方法的傳回值。

示例代碼如下:

main.m:

/**
   上帝和人的故事
 */

#import <Foundation/Foundation.h>
#import "God.h"
int main(){
    @autoreleasepool {
        God *_god = [God new];
        _god->_name = @"耶稣";
        _god->_age = 9999999;
        _god->_gender = GenderMale;
        
        People *_p1 = [People new];
        _p1->_name = @"康熙";
        _p1->_wish = @"向天再借五百年";
        
        [_god fullFillWish:_p1];
        NSLog(@"%@的願望沒有實作,他可能求錯神了,笑死我了(>_<)",_p1->_name);
        
       People *_p2 =  [_god makePeopleWithName:@"夏娃" andGender:GenderFemale andAge:1 andLeftLife:999999];
        [_p2 show];
    }
    return 0;
}
           

People.h:

//
//  People.h
//  OCStudyDemo
//
//  Created by Luffy on 2021/1/18.
//

#import <Foundation/Foundation.h>
#import "Gender.h"
@interface People : NSObject{
    @public
    NSString *_name;
    Gender _gender;
    int _age;
    int _leftLife;
    NSString *_wish;
}
- (void) show;
@end
           

People.m:

//
//  People.m
//  OCStudyDemo
//
//  Created by Luffy on 2021/1/18.
//

#import "People.h"

@implementation People
- (void)show{
    NSLog(@"我叫%@,我今年%d歲,我還可以活%d年,哇哈哈哈~",_name,_age,_leftLife);
}
@end
           

God.h:

//
//  God.h
//  OCStudyDemo
//
//  Created by Luffy on 2021/1/18.
//

#import <Foundation/Foundation.h>
#import "People.h"
@interface God : NSObject{
    @public
    NSString *_name;
    int _age;
    Gender _gender;
}
- (void) fullFillWish : (People *) _people;
- (People *) makePeopleWithName:(NSString *) _name andGender:(Gender) _gender andAge:(int) _age andLeftLife:(int) _leftLife;
@end
           

God.m:

//
//  God.m
//  OCStudyDemo
//
//  Created by Luffy on 2021/1/18.
//

#import "God.h"

@implementation God
- (void)fullFillWish:(People *)_people{
    NSLog(@"這個叫%@的人的願望是%@,怕是要當神仙吧,願望不合理,駁回!",_people->_name,_people->_wish);
}
- (People *)makePeopleWithName:(NSString *)_name andGender:(Gender)_gender andAge:(int)_age andLeftLife:(int)_leftLife{
    People *_p1 = [People new];
    _p1->_name = _name;
    _p1->_gender = _gender;
    _p1->_age = _age;
    _p1->_leftLife = _leftLife;
    return _p1;
}

@end
           

Gender.h:

//
//  Gender.h
//  OCStudyDemo
//
//  Created by Luffy on 2021/1/18.
//  使用枚舉類定義性别
//
typedef enum{
    GenderMale,
    GenderFemale
} Gender;
           

文法注意:

a. 當對象作為參數時,參數類型應該是類指針:- (Dog *) trainDog : (Dog *) dog;

b. 在調用方法時,如果參數是一個類對象,參數一定要傳該類的指針;

c. 如果導入的頭檔案已經導入了相關頭檔案就不用重複導入,重複導入也沒問題,比如上面main.m檔案中,隻導入了God.h,卻可以使用 People類;

d. 當對象作為方法的參數時,是位址傳遞,是以在方法内部通過形參修改對象的屬性時,實參的屬性值也會被修改掉;

e. 定義枚舉或結構體時,先考慮是隻提供給一個類用還是提供給多個類用,如果隻有一個類用,就定義在類的頭檔案中,如果多個類都要用,就定義在單獨的頭檔案中。

對象作為類的屬性

1. 所謂屬性說白了就是這個類具備哪些東西,比如人可以有身高、年齡、性别,當然也可以有女朋友呀╰( ̄▽ ̄)╭ 這個時候女朋友就是一個對象作為人這個類的屬性。一個對象作為某個類的屬性,表示這個類擁有了這個對象。值得注意的是:當A類作為B類的屬性時,建立B類對象并不會建立A類對象,此時A類對象在B類對象中作為屬性隻是一個指針變量,且它的值是nil。

2. 枚舉和結構體定義在什麼地方?

如果隻有一個類要用,就定義在類的頭檔案當中;如果多個類要用,就定義在單獨的頭檔案當中,誰要用誰去引用就行了。

3. 如果要在方法當中調用目前對象的另一個方法,文法:[self 方法名];  self代表目前方法名。

猜拳遊戲

main.m:

/**
猜拳遊戲:
 1、遊戲的流程
     1)玩家出拳;
     2)機器人出拳;
     3)裁判宣布結果。
 2、面向對象的重點在于找類
     玩家類:
         屬性:名字、選擇的拳頭、得分;
         方法:出拳-->玩家自行選擇一個拳頭
     機器人類
         屬性:姓名、選擇的拳頭、得分
         方法:出拳-->随機選擇一個拳頭
     裁判類
         屬性:姓名
         方法:判段輸赢并給出得分
 */

#import <Foundation/Foundation.h>
#import "Judge.h"
int main(){
    @autoreleasepool {
        Player *_player = [Player new];
        _player->_name = @"小花";
        
        AIRobot *_aiRobot = [AIRobot new];
        _aiRobot->_name = @"阿爾法狗";
        
        Judge *_judge = [Judge new];
        _judge->_name = @"黑帥";
        
        while(1){
            //1.玩家出拳
            [_player selectFistType];
            //2.機器人出拳
            [_aiRobot randomSelectFistType];
            //3.裁判宣布結果
            [_judge judgingWithPlayer:_player andAIRobot:_aiRobot];
            
            NSLog(@"您還要繼續玩嗎?請輸入輸入y/n.");
            
            char _isContinue = 'y';
            rewind(stdin);//清空字元
            scanf("%c",&_isContinue);//接受玩家輸入的字元
             if(_isContinue == 'n'){
                 break;
              }
        }
    }
    return 0;
}
           

Player.h:

/**
玩家類:
 屬性:名字、選擇的拳頭、得分;
 方法:出拳-->玩家自行選擇一個拳頭
 */

#import <Foundation/Foundation.h>
#import "FistType.h"
@interface Player : NSObject{
    @public
    NSString *_name;
    FistType _seletedFistType;
    int _score;
}
- (void)selectFistType;
/**
   描述:将使用者輸入的數字轉換成字元串的形式來表示拳頭類型
 */
- (NSString *) fistTypeToStringWithNumber : (int) number;
@end
           

Player.m:

//
//  Player.m
//  Day02
//
//  Created by Luffy on 2021/1/19.
//

#import "Player.h"

@implementation Player
- (void)selectFistType{
    //1.提示玩家選擇出拳類型
    NSLog(@"親愛的玩家[%@]您好,請選擇您本次出拳類型:1.石頭;2.剪刀;3.布。",_name);
    //2.玩家輸入出拳類型後,把出拳類型儲存起來
    _seletedFistType = 0;
    scanf("%d",&_seletedFistType);
    //3.展示給玩家出拳的類型
    NSString *_result = [self fistTypeToStringWithNumber:_seletedFistType];
    NSLog(@"您本次出拳類型是:%@",_result);
}
/**
   描述:将使用者輸入的數字轉換成字元串的形式來表示拳頭類型
 */
- (NSString *)fistTypeToStringWithNumber:(int)number{
    switch (number) {
        case 1:
            return @"石頭";
        case 2:
            return @"剪刀";
        default:
            return @"布";
    }
}
@end
           

AIRobot.h:

/**
 智能機器人:
 屬性:姓名、選擇的拳頭、得分
 方法:出拳-->随機選擇一個拳頭
 */

#import <Foundation/Foundation.h>
#import "FistType.h"
@interface AIRobot : NSObject{
    @public
    NSString *_name;
    FistType _seletedFistType;
    int _score;
}
-(void) randomSelectFistType;

/// Description 将随機産生的數字轉換成字元串的形式來表示拳頭類型
/// @param number 随機數
- (NSString *) fistTypeToStringWithNumber : (int) number;
@end
           

AIRobot.m:

//
//  AIRobot.m
//  Day02
//
//  Created by Luffy on 2021/1/19.
//

#import "AIRobot.h"
#import <stdlib.h>
@implementation AIRobot
- (void)randomSelectFistType{
    //1.産生一個随機的拳頭
    int randomFistType = arc4random_uniform(3)+1;
    //2.儲存拳頭類型
    _seletedFistType = randomFistType;
    //3.展示拳頭類型
    NSString *_result = [self fistTypeToStringWithNumber:randomFistType];
    NSLog(@"我是智能機器人[%@],我本次出拳的類型是:%@",_name,_result);
}

/// Description 将随機産生的數字轉換成字元串的形式來表示拳頭類型
/// @param number 随機數
- (NSString *)fistTypeToStringWithNumber:(int)number{
    switch (number) {
        case 1:
            return @"石頭";
        case 2:
            return @"剪刀";
        default:
            return @"布";
    }
}
@end
           

Judge.h:

/**
   裁判類
 */

#import <Foundation/Foundation.h>
#import "Player.h"
#import "AIRobot.h"

@interface Judge : NSObject{
    @public
    NSString *_name;
}
- (void) judgingWithPlayer : (Player *) _player andAIRobot : (AIRobot *) _aiRobot;
@end
           
//
//  Judge.m
//  Day02
//
//  Created by Luffy on 2021/1/19.
//

#import "Judge.h"

@implementation Judge
- (void)judgingWithPlayer:(Player *)_player andAIRobot:(AIRobot *)_aiRobot{
    /**
     分析:
     玩家赢的情況:
     player  aiRobot 兩者對應數字內插補點
     石頭-1   剪刀-2    -1
     剪刀-2   布-3      -1
     布-3    石頭-1      2
     */
    //玩家赢
    if(_player->_seletedFistType - _aiRobot->_seletedFistType == -1
       || _player->_seletedFistType - _aiRobot->_seletedFistType == 2){
        NSLog(@"我是裁判[%@],我宣布本輪猜拳玩家[%@]勝出,加一分!",
              _name,_player->_name);
        _player->_score++;
    }
    //平局
    else if(_player->_seletedFistType == _aiRobot->_seletedFistType){
        NSLog(@"我是裁判[%@],我宣布本輪平局",_name);
    }
    //智能機器人
    else{
        NSLog(@"我是裁判[%@],我宣布本輪智能機器人[%@]勝出,加一分!",
              _name,
              _aiRobot->_name);
        _aiRobot->_score++;
    }
    NSLog(@"目前比分是[%@---%d]:[%@---%d]。",
          _player->_name,
          _player->_score,
          _aiRobot->_name,
          _aiRobot->_score);
}
@end
           
/**
 出拳的類型:
 1.石頭;
 2.剪刀;
 3.布。
 */
typedef enum{
    STON = 1,
    SCISSOR = 2,
    CLOTH = 3
} FistType;