黑马程序员——Objective-C语言知识点总结之OC特有语法

本文详细解析了Objective-C中的点语法原理及其与方法调用的关系,探讨了点语法可能导致的死循环问题。此外,还介绍了成员变量的作用域、构造方法、类的本质等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

——Java培训、Android培训、iOS培训、.Net培训、期待与您交流!——

点语法

点语法本质

原方法调用

Student *stu = [Student new];
[stu setAge:100];
int age = [stu age];

点语法

stu.age = 100;
int age = stu.age;

点语法的本质

点语法的本质还是方法调用;
当使用点语法时,编译器会自动展开成相应的方法;
如:
stu.age = 100 ;调用对象方法,相当于 [stu setAge:100];
stu.age;调用类方法,相当于[stu age];

死循环

点语法调用自身,会引发死循环

- (void) setAge:(int)age 
{
    // 下面的代码会引发死循环
    self.age = age;
}
- (int) age 
{
    // 下面的代码会引发死循环
    return self.age;
}

例:

Person.h

#import <Foundation/Foundation.h>
// 类的声明
@interface Person : NSObject
{
    // 定义变量
    int _age;
    NSString *_name;
}
// set方法和get方法
- (void)setAge:(int)age;
- (int)age;

- (void)setName:(NSString *)name;
- (NSString *)name;

@end

Person.m

// 类的实现
@implementation Person

- (void)setAge:(int)age
{
    NSLog(@"setAge:");

    // 会引发死循环
    //self.age = age; // 相当于[self setAge:age],调用自身;
}

- (int)age
{
    NSLog(@"age");
    return _age;
    // 会引发死循环
    //return self.age;// 相当于[self age];
}

- (void)setName:(NSString *)name
{
    _name = name;
}

- (NSString *)name
{
    return _name;
}

@end

main.m

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

int main(int argc, const char * argv[])
{
    Person *p = [Person new];

    // 点语法的本质还是方法调用
    p.age = 10; // 调用对象方法,[p setAge:10];

    int a = p.age; // 调用类方法,[p age];

    p.name = @"Jack"; // 调用类方法,[p setName:@"Jack"]

    NSString *s = p.name;

    NSLog(@"%d,%@",a,s);

    return 0;
}

运行结果:
2015-04-27 08:27:10.539 04-点语法[593:21542] setAge:
2015-04-27 08:27:10.540 04-点语法[593:21542] age
2015-04-27 08:27:10.540 04-点语法[593:21542] 0,Jack

成员变量的作用域

成员变量类型

1>@public : 在任何地方都能直接访问对象的成员变量

若@public写在@interface中,main.m文件包含相应的.h文件即可,若写在@implementation中,则不能直接访问,除非在main函数前@implementation

2>@private : 只能在当前类的对象方法中直接访问
(@implementation中默认是@private)

3>@protected : 可以在当前类及其子类的对象方法中直接访问 (@interface中默认就是@protected)

4>@package : 只要处在同一个框架中,就能直接访问对象的成员变量

注:
@interface和@implementation中不能声明同名的成员变量

作用域

例:

父类Person

Person.h

#import <Foundation/Foundation.h>

@interface Person : NSObject
{
    // 默认@protected
    int _no;

    @public // 在任何地方都能直接访问对象的成员变量
    int _age;

    @private  // 只能在当前类的对象方法中直接访问
    int _height;

    @protected // 能在当前类和子类的对象方法中直接访问
    int _weight;
    int _money;
}

- (void)setHeight:(int)height;
- (int)height;

- (void)test;
@end

Person.m

#import "Person.h"

@implementation Person
{
    int _aaa;// 默认就是私有

    @public
    int _bbb;

    // @implementation中不能定义和@interface中同名的成员变量
    // int _no;
}

- (void)test
{
    //_age为@public,可以在任何地方直接访问
    _age = 19;
    // _height为@private,只能在当前Person类的对象方法中直接访问
    _height = 20;
    // _weight为@protected,能在当前类和子类的对象方法中直接访问
    _weight = 50;
    // _aaa为@private,只能在当前Person类的对象方法中直接访问
    _aaa = 10;
}

- (void)setHeight:(int)height
{
    _height = height;
}

- (int)height
{
    return _height;
}

@end

子类Student

Student.h

#import "Person.h"

@interface Student : Person
- (void)study;
@end

Student.m

#import "Student.h"

@implementation Student
- (void)study
{
    // _height为@private,只能在当前Person类的对象方法中直接访问,不能在子类中直接访问
    // _height = 10;
    [self setHeight:10];


    int h = [self height];
    // _weight为@protected,能在Person类的子类的对象方法中直接访问
    _weight = 100;
}
@end

main.m

#import <Foundation/Foundation.h>
#import "Person.h"
#import "Student.h"

@implementation Car : NSObject
{

    @public
    int _speed;
    // 可以被main函数访问
    @protected
    int _wheels;
}

- (void)setSpeed:(int)speed
{
    _speed = speed;
}
- (int)speed
{
    return _speed;
}

@end

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        Student *stu = [Student new];
        [stu setHeight:100];
        NSLog(@"%d", [stu height]);

        Car *c = [Car new];
        // 由于_speed为@public,可以直接访问
        c->_speed = 250;
        NSLog(@"%d", [c speed]);

        // 调用setSpeed方法
        c.speed = 10;
        NSLog(@"%d", c.speed);

        Person *p = [Person new];
        // 因为_bbb定义在Person.m文件中,而头文件只包含了Person.h,所以不能直接访问
        // p->_bbb = 10;

        p->_age = 100;

        // _height只能在当前类(Person)的对象方法中直接访问,
        // p->_height = 20;
        [p setHeight:20];

        // _weight是Person类中的@protect变量,不可以直接拿到main 函数中访问
        // p->_weight = 10;
    }
    return 0;
}

@property和@synthesize

@property

作用:
用在@intreface中,用来自动生成setter和getter的声明

例:

// 自动生成setter和getter的声明
@property int age;

// 可以代替下边两行
- (void)setAge:(int)age;
- (int)age;

@synthesize

作用:

用在@implementation中,自动生成setter和getter的实现

// 自动生成setter和getter的实现
@synthesize age = _age;
// 就可以代替一下内容
- (void)setAge:(int)age{
    _age = age;
}
- (int)age
{
    return _age;
}

@synthesize的细节

在@synthesize age = _age中,setter和getter实现中会访问成员变量_age ;如果成员变量_age不存在,就会自动生成一个@private的成员变量_age

在@synthesize age中,setter和getter实现中会访问成员变量age ;如果成员变量age不存在,就会自动生成一个@private的成员变量age

手动实现

若手动实现了setter方法,编译器就只会自动生成getter方法
若手动实现了getter方法,编译器就只会自动生成setter方法
若同时手动实现了setter和getter方法,编译器就不会自动生成不存在的成员变量

@property新特性

自从Xcode 4.x后,@property就独揽了@synthesize的功能。也就是说,@property可以同时生成setter和getter的声明和实现
默认情况下,setter和getter方法中的实现,会去访问下划线 _ 开头的成员变量

id

简介

万能指针,能指向任何OC对象,相当于NSObject *

id类型的定义

typedef struct objc_object {
Class isa;
} *id;

使用

注意:id后面不要加上*
id p = [Person new];

局限性:调用一个不存在的方法,编译器会马上报错

例:

Person.h

#import <Foundation/Foundation.h>

@interface Person : NSObject
@property int age;
@property id obj;
@end

Person.m

#import "Person.h"

@implementation Person

@end

main.m

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

void test(id d)
{

}

int main(int argc, const char * argv[])
{

    @autoreleasepool {

        // id  == NSObject *
        // 万能指针,能指向\操作任何OC对象
        id d = [Person new];

        [d setAge:10];

        [d setObj:@"321423432"];

        NSLog(@"%d", [d age]);

        NSLog(@"%@",[d obj]);
    }
    return 0;
}

运行结果:
2015-04-27 20:42:01.154 01-id[1194:79559] 10
2015-04-27 20:42:01.155 01-id[1194:79559] 321423432

构造方法

定义

构造方法:用来初始化对象的方法,是个对象方法,-开头

目的:为了让对象创建出来,成员变量就会有一些固定的值

重写构造方法的注意点
1.先调用父类的构造方法([super init])
2.再进行子类内部成员变量的初始化

对象创建原理

new的拆分两部曲
1>分配内存(+alloc)
2>初始化(-init)

Person *p1 = [Person alloc];
Person *p1 = [p1 init];
合成一句后:
Person *p = [[Person alloc] init];

init方法的重写

目的:想在对象创建完毕后,成员变量马上就有一些默认的值

init方法的重写过程

- (id)init
{
    // 一定要调用回super的init方法:初始化父类中声明的一些成员变量和其他属性,对象初始化成功,才有必要进行接下来的初始化
    if (self = [super init])
    {
        _age = 10;
    }
    return self;
}

自定义构造方法

构造方法的规范

1>返回值是id类型
2>方法名都以initWith开头

- (id)initWithAge:(int)age 
{
    if (self = [super init]) 
    {
        _age = age;
    }
    return self;
}

传递多个参数进行初始化

- (id) initWithAge:(int)age andNo:(int)no; 

父类与子类自定义构造方法的关系
父类的属性交给父类方法去处理,子类方法处理子类自己的属性

例:
如果在父类 Person中自定义构造方法

- (id)initWithName:(NSString *)name andAge:(int)age
{
    if ( self = [super init] )
    {
        _name = name;
        _age = age;
    }
    return self;
}

在子类Student中需要自定义

- (id)initWithName:(NSString *)name andAge:(int)age andNo:(int)no

则可以写成

// 父类的属性交给父类方法去处理,子类方法处理子类自己的属性
- (id)initWithName:(NSString *)name andAge:(int)age andNo:(int)no
{
    // 将name、age传递到父类方法中进行初始化
    if ( self = [super initWithName:name andAge:age])
    {
        _no = no;
    }

    return self;
}

分类-Category

基本用途

在不改变原来类模型的前提下,给类扩充一些方法

分类文件名:
原类名+XX
例:给Person添加分类
Person+ZJ.h / Person+ZJ.m

分类格式

分类的声明

@interface 类名 (分类名称)
// 方法声明
@end

分类的实现

@implementation 类名 (分类名称)
// 方法实现
@end

好处

1>一个庞大的类可以分模块开发
2>一个庞大的类可以由多个人来编写,更有利于团队合作

例:
给NSString增加一个类方法:计算某个字符串中阿拉伯数字的个数;
给NSString增加一个对象方法:计算当前字符串中阿拉伯数字的个数

NSString+Number.h

#import <Foundation/Foundation.h>

@interface NSString (Number)
// 对象方法
+ (int)numberCountOfString:(NSString *)str;
// 类方法
- (int)numberCount;

@end

NSString+Number.m

#import "NSString+Number.h"

@implementation NSString (Number)

//  @"abc434ab43"
+ (int)numberCountOfString:(NSString *)str
{
    // 类方法直接调用对象方法   
    return [str numberCount];
}

- (int)numberCount
{
    int count = 0;

    for (int i = 0; i<self.length; i++)
    {
        // 取出i这个位置对应的字符
        unichar c = [self characterAtIndex:i];

        // 如果这个字符是阿拉伯数字
        if ( c>='0' && c<='9' )
        {
            count++;
        }
    }

    return count;
}

@end

main.m


#import <Foundation/Foundation.h>
#import "NSString+Number.h"


int main()
// 类库:很多类的集合
{
    int count = [NSString numberCountOfString:@"54d43a43s43dasd"];

    int count = [@"9fdsfds543543" numberCount];

    NSLog(@"%d", count);
    return 0;
}

输出结果:
2015-04-27 23:03:20.827 05-分类的应用[1373:100521] count1=8,count2=7

注意:

1> Category可以访问原始类的实例变量,但不能添加变量,只能添加方法。如果想添加变量,可以考虑通过继承创建子类

2> Category可以实现原始类的方法,但不推荐这么做,因为它是直接替换掉原来的方法,这么做的后果是再也不能访问原来的方法

3> 多个Category中如果实现了相同的方法,只有最后一个参与编译的才会有效

4> 方法调用的优先级:分类(最后参与编译的分类优先) –> 原来类 –> 父类

类的本质

类的本质:

其实类也是一个对象,是Class类型的对象,简称“类对象”

Class类型的定义

typedef struct objc_class *Class;

类名就代表着类对象,因此每个类只有一个类对象,类对象 == 类

+load和+initialize

+load
在程序启动的时候会加载所有的类和分类,并调用所有类和分类的+load方法

先加载父类,再加载子类;也就是先调用父类的+load,再调用子类的+load,先加载原始类,再加载分类

不管程序运行过程有没有用到这个类,都会调用+load加载

+initialize

在第一次使用某个类时(比如创建对象等),就会调用一次+initialize方法,一个类只会调用一次+initialize方法,先调用父类的,再调用子类的

获取类对象的2种方式

1> Class c = [Person class]; // 类方法

2> Person *p = [Person new];
Class c2 = [p class]; // 对象方法

类对象调用类方法:

Class c = [Person class];
Person *p2 = [c new];

description方法

-description方法

使用NSLog和%@输出某个对象时,会调用对象的-description方法,并拿到返回值进行输出

+ description方法

使用NSLog和%@输出某个类对象时,会调用类对象+description方法,并拿到返回值进行输出

修改NSLog的默认输出

重写-description或者+description方法即可

@implementation Person

- (NSString *)description
{
    return @"3424324";
}

+ (NSString *)description
{
    return @"Abc";
}

死循环陷阱

- (NSString *)description
{
    // 下面代码会引发死循环
    NSLog(@"%@", self);
}

如果在-description方法中使用NSLog打印self

SEL

方法的存储位置

1> 每个类的方法列表都存储在类对象中;
2> 每个方法都有一个与之对应的SEL类型的对象;
3> 根据一个SEL对象就可以找到方法的地址,进而调用方法;

SEL类型的定义

typedef struct objc_selector *SEL;

SEL对象的创建

SEL s = @selector(test);
SEL s2 = NSSelectorFromString(@"test");

SEL对象的其他用法

// 将SEL对象转为NSString对象
NSString *str = NSStringFromSelector(@selector(test));

Person *p = [Person new];
// 调用对象p的test方法
[p performSelector:@selector(test)];

NSLog输出增强

_FILE_源代码文件名
_LINE_ NSLog代码在第几行
_cmd :代表着当前方法的SEL

.h和.m文件的抽取

每个类分布在不同文件中
1> 类的声明放在.h文件,类的实现放在.m文件
2> 若想使用某个类,就包含某个类的.h声明文件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值