記憶體中的五大區域
- 棧:存儲局部變量。
- 堆:開發人員通過malloc、calloc、realloc函數申請的位元組空間。
- BSS段:存儲未被初始化的全局變量、靜态變量。
- 資料段(常量區):存儲已被初始化的全局變量、靜态變量、常量。
- 代碼段:存儲程式的代碼。
類加載
在程式運作過程中,當某個類第一次被通路的時候,類的資訊會被加載到代碼段中,這個過程叫做類加載。一旦類被加載到代碼段中,就不需要重複加載了,直到程式結束才會被釋放。
- 在建立對象的時候,肯定是需要通路這個類的。
- 在聲明一個類的時候,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) 傳回該類對象在堆記憶體中的位址給棧區。

注意:
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指派給類指針,此時使用指針名調用屬性和方法會是什麼情況呢?
調用屬性時報錯了:
調用方法時不報錯,但是方法不會執行:
多個指針指向同一對象
同一類型的指針是可以互相指派的,如下代碼中:首先建立一個 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;
}
多個指針指向同一對象記憶體分析
ps:目前為止,隻要看到 new關鍵字,就是建立了新的對象。
分組導航标記
有三種情況:
- @pragma mark 分組名:在導航欄中顯示一個标題
- @pragma mark - :在導航欄顯示一條分割線
- @pragma mark - 分組名:在導航欄顯示一個分割線和标題
大家盡量都自己手動示範一遍吧。
【函數VS方法】
相同點:都是對某一特定功能的封裝,調用函數或方法,其内部的代碼邏輯就會被執行。
不同點:
a. 文法不同;
b. 定義的位置不同:比如定義一個函數,你可以在除了函數内部和@interface的大括号内之外的任意地方定義;而方法必須聲明在 @interface的大括号之外,在@implementation中實作;
c. 調用方式不同,函數可以直接調用,而方法需要依賴對象調用;
d. 函數是孤家寡人,而方法屬于類,是有伐木累的。
ps: 即使把函數定義在類中,它也屬于類,王者都是寡人,不需要依附任何人!(不過我們人類還是需要講究合作的(>_<)).
十宗罪
1. 類的聲明和實作不能嵌套,也就是說@interface中不能嵌套@implementation;
2. 類必須先聲明再實作;
3. 類的聲明和實作必須都要有;
4. 類名采用“駝峰命名法”,即每個單詞的首字母必須大寫;
5. 屬性名必須以下劃線開頭,這是必須遵守的規範,不然後面會把自己坑死的;
6. 雖然某些特殊情況下,類可以隻有實作而不用聲明,但不建議這麼玩:
7. 不允許在類的定義時給屬性賦初值;
8. 類的聲明必須放在類的使用的前面,類的實作可以放在類的使用的後面,但還是按照規範來;
9. OC中,在别的方法中調用類的屬性和方法,必須通過建立類的對象,再通過類對象調用屬性和方法;
10. 如果方法隻有聲明,沒有實作,編譯的時候隻會警告你方法沒有找到,但是運作時就會報錯:unrecognized selector sent to instance 0x1006b8c80'
隻要看到這個錯誤,要麼就是類中壓根沒有這個方法,要麼就是隻聲明了方法,而沒有實作方法。
多檔案開發
為什麼使用多檔案開發?我們前面的代碼都是寫在一個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;