MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Objective-C 基础语法全面解析

2022-11-255.5k 阅读

一、Objective-C 简介

Objective-C 是一种通用、高级、面向对象的编程语言,它扩展了标准的 ANSI C 语言,加入了面向对象编程的特性。它由 Brad Cox 在 20 世纪 80 年代初开发,最初用于 NeXTSTEP 操作系统,后来随着苹果公司收购 NeXT,Objective-C 成为了 macOS 和 iOS 开发的主要编程语言。

Objective-C 语言的设计理念融合了 C 语言的高效性和 Smalltalk 的面向对象特性,其最大的特点就是动态绑定和运行时特性,这使得开发者在编程时可以更加灵活地控制程序的行为。

二、数据类型

2.1 基本数据类型

Objective-C 支持 C 语言的所有基本数据类型,包括整型、浮点型、字符型和布尔型。

整型: 整型用于表示整数,常见的有 charshortintlong。在 64 位系统中,int 通常为 32 位,long 为 64 位。例如:

int age = 25;
short num = 100;

浮点型: 浮点型用于表示带有小数部分的数字,有 float(单精度)和 double(双精度)。float 一般占用 4 字节,double 占用 8 字节。例如:

float piFloat = 3.14159f;
double piDouble = 3.141592653589793;

注意,在表示 float 类型常量时,需要在数字后面加上 fF

字符型: 字符型用于表示单个字符,使用 char 类型。字符需要用单引号括起来。例如:

char grade = 'A';

布尔型: Objective-C 中的布尔型用 BOOL 表示,它实际上是一个 signed char 类型,取值为 YES(1)或 NO(0)。例如:

BOOL isDone = YES;

2.2 枚举类型

枚举类型是一种用户自定义的数据类型,它允许定义一组命名的整型常量。使用 enum 关键字来定义枚举类型。例如:

enum Weekday {
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday
};

默认情况下,Monday 的值为 0,后续的值依次递增 1。也可以手动指定值:

enum Weekday {
    Monday = 1,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday
};

使用枚举类型定义变量:

enum Weekday today = Wednesday;

2.3 结构体和联合体

结构体: 结构体是一种可以将不同类型的数据组合在一起的自定义数据类型。使用 struct 关键字定义。例如:

struct Point {
    int x;
    int y;
};

定义结构体变量:

struct Point p1;
p1.x = 10;
p1.y = 20;

也可以在定义结构体时同时定义变量:

struct Point {
    int x;
    int y;
} p2;
p2.x = 30;
p2.y = 40;

联合体: 联合体也是一种自定义数据类型,它允许不同类型的数据共享相同的内存空间。使用 union 关键字定义。例如:

union Data {
    int i;
    float f;
    char c;
};

定义联合体变量:

union Data d;
d.i = 10;

由于联合体成员共享内存,此时 d.fd.c 的值也会受到影响,因为它们和 d.i 占用同一块内存。

三、变量与常量

3.1 变量

变量是程序中用于存储数据的容器,其值在程序运行过程中可以改变。在 Objective-C 中,变量必须先声明后使用。例如:

int number;
number = 5;

变量的作用域决定了变量在程序中的可见范围。常见的作用域有局部作用域(在函数内部声明的变量)和全局作用域(在函数外部声明的变量)。例如:

int globalVar = 10; // 全局变量

void myFunction() {
    int localVar = 20; // 局部变量
    NSLog(@"Local variable: %d", localVar);
    NSLog(@"Global variable: %d", globalVar);
}

3.2 常量

常量是在程序运行过程中其值不能改变的量。在 Objective-C 中有两种定义常量的方式:

使用 #define 宏定义

#define PI 3.14159

这种方式是在预处理阶段进行替换,没有类型检查。

使用 const 关键字

const float pi = 3.14159f;

使用 const 定义的常量有类型检查,并且作用域遵循变量的作用域规则。

四、运算符

4.1 算术运算符

Objective-C 支持常见的算术运算符,如加(+)、减(-)、乘(*)、除(/)和取模(%)。例如:

int a = 10;
int b = 3;
int sum = a + b;
int product = a * b;
int remainder = a % b;

注意,两个整数相除结果为整数,会舍去小数部分。如果要得到精确的结果,至少有一个操作数应为浮点型。

4.2 赋值运算符

赋值运算符用于给变量赋值,最基本的是 =。还存在复合赋值运算符,如 +=-=*=/=%= 等。例如:

int num = 5;
num += 3; // 等价于 num = num + 3;

4.3 比较运算符

比较运算符用于比较两个值,结果为 BOOL 类型。常见的比较运算符有等于(==)、不等于(!=)、大于(>)、小于(<)、大于等于(>=)和小于等于(<=)。例如:

int a = 10;
int b = 20;
BOOL isGreater = a > b;

4.4 逻辑运算符

逻辑运算符用于组合多个条件,结果也是 BOOL 类型。有逻辑与(&&)、逻辑或(||)和逻辑非(!)。例如:

int a = 10;
int b = 20;
BOOL result1 = (a > 5) && (b < 30);
BOOL result2 = (a < 5) || (b > 15);
BOOL result3 =!(a == b);

4.5 位运算符

位运算符用于对二进制位进行操作,包括按位与(&)、按位或(|)、按位异或(^)、按位取反(~)、左移(<<)和右移(>>)。例如:

int a = 5; // 二进制为 00000101
int b = 3; // 二进制为 00000011
int andResult = a & b; // 按位与,结果为 00000001,即 1
int orResult = a | b; // 按位或,结果为 00000111,即 7

五、流程控制语句

5.1 if - else 语句

if - else 语句用于根据条件执行不同的代码块。例如:

int age = 18;
if (age >= 18) {
    NSLog(@"You are an adult.");
} else {
    NSLog(@"You are a minor.");
}

也可以有多个 if - else 嵌套,形成 if - else if - else 结构:

int score = 85;
if (score >= 90) {
    NSLog(@"Grade: A");
} else if (score >= 80) {
    NSLog(@"Grade: B");
} else if (score >= 70) {
    NSLog(@"Grade: C");
} else {
    NSLog(@"Grade: D");
}

5.2 switch - case 语句

switch - case 语句用于根据一个整型或字符型表达式的值来选择执行不同的分支。例如:

int day = 3;
switch (day) {
    case 1:
        NSLog(@"Monday");
        break;
    case 2:
        NSLog(@"Tuesday");
        break;
    case 3:
        NSLog(@"Wednesday");
        break;
    default:
        NSLog(@"Other day");
        break;
}

注意,每个 case 分支结尾一般需要使用 break 语句,否则会继续执行下一个 case 分支的代码。

5.3 for 循环

for 循环用于重复执行一段代码,它有初始化、条件判断和更新三个部分。例如:

for (int i = 0; i < 5; i++) {
    NSLog(@"Count: %d", i);
}

在上述代码中,int i = 0 是初始化部分,i < 5 是条件判断部分,i++ 是更新部分。

5.4 while 循环

while 循环先判断条件,只要条件为真就会重复执行循环体。例如:

int num = 0;
while (num < 5) {
    NSLog(@"Number: %d", num);
    num++;
}

5.5 do - while 循环

do - while 循环先执行一次循环体,然后再判断条件。因此,无论条件是否成立,循环体至少会执行一次。例如:

int value = 10;
do {
    NSLog(@"Value: %d", value);
    value -= 2;
} while (value > 0);

六、函数

6.1 函数的定义与声明

函数是一段完成特定任务的代码块。在 Objective-C 中,函数需要先声明后使用。函数声明包括函数名、参数列表和返回类型,函数定义则包含函数体。例如:

// 函数声明
int add(int a, int b);

// 函数定义
int add(int a, int b) {
    return a + b;
}

main 函数中调用 add 函数:

int main() {
    int result = add(3, 5);
    NSLog(@"The result is: %d", result);
    return 0;
}

6.2 函数参数与返回值

函数可以有零个或多个参数,参数就像函数内部的局部变量,在函数被调用时接收传递进来的值。函数也可以有返回值,通过 return 语句返回。例如,一个计算两个数平均值的函数:

float average(float num1, float num2) {
    return (num1 + num2) / 2;
}

调用该函数:

float avg = average(10.5f, 15.5f);
NSLog(@"The average is: %f", avg);

6.3 函数的递归

递归是指函数在其函数体内调用自身的过程。例如,计算阶乘的递归函数:

int factorial(int n) {
    if (n == 0 || n == 1) {
        return 1;
    } else {
        return n * factorial(n - 1);
    }
}

调用该函数计算 5 的阶乘:

int result = factorial(5);
NSLog(@"5! is: %d", result);

七、数组

7.1 一维数组

一维数组是相同类型数据的有序集合。在 Objective-C 中,数组的声明和初始化方式如下:

int numbers[5]; // 声明一个包含 5 个整数的数组
numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;
numbers[3] = 40;
numbers[4] = 50;

// 或者在声明时初始化
int scores[] = {85, 90, 95, 100};

访问数组元素通过下标,下标从 0 开始。例如:

int firstScore = scores[0];

7.2 二维数组

二维数组可以看作是数组的数组,常用于表示矩阵等数据结构。例如:

int matrix[2][3] = {
    {1, 2, 3},
    {4, 5, 6}
};

访问二维数组元素:

int element = matrix[1][2]; // 访问第二行第三列的元素,值为 6

7.3 数组与函数

数组可以作为函数的参数传递。当数组作为参数传递时,实际上传递的是数组的首地址。例如:

void printArray(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        NSLog(@"%d", arr[i]);
    }
}

调用该函数:

int numbers[] = {1, 2, 3, 4, 5};
printArray(numbers, 5);

八、指针

8.1 指针的基本概念

指针是一个变量,它存储的是另一个变量的内存地址。在 Objective-C 中,使用 * 来声明指针变量。例如:

int num = 10;
int *ptr; // 声明一个指向 int 类型的指针
ptr = &num; // 将 num 的地址赋给 ptr

通过指针访问变量的值使用 * 运算符,称为解引用。例如:

NSLog(@"Value of num through pointer: %d", *ptr);

8.2 指针与数组

数组名实际上是数组首元素的地址,因此可以将数组名赋值给指针。例如:

int numbers[5] = {1, 2, 3, 4, 5};
int *ptr = numbers;

通过指针访问数组元素:

NSLog(@"First element: %d", *ptr);
NSLog(@"Second element: %d", *(ptr + 1));

8.3 指针与函数

指针可以作为函数的参数传递,这样可以在函数内部修改调用函数中的变量值。例如:

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

调用该函数:

int x = 5;
int y = 10;
swap(&x, &y);
NSLog(@"x: %d, y: %d", x, y);

九、面向对象编程基础

9.1 类与对象

在 Objective-C 中,类是对象的蓝图,它定义了对象的属性(成员变量)和行为(方法)。使用 @interface@implementation 来定义类。例如:

// 定义一个 Person 类
@interface Person : NSObject {
    NSString *name;
    int age;
}
- (void)setName:(NSString *)newName;
- (NSString *)name;
- (void)setAge:(int)newAge;
- (int)age;
- (void)introduce;
@end

@implementation Person
- (void)setName:(NSString *)newName {
    name = newName;
}
- (NSString *)name {
    return name;
}
- (void)setAge:(int)newAge {
    age = newAge;
}
- (int)age {
    return age;
}
- (void)introduce {
    NSLog(@"Hi, I'm %@ and I'm %d years old.", name, age);
}
@end

创建对象并使用:

int main() {
    Person *person = [[Person alloc] init];
    [person setName:@"John"];
    [person setAge:25];
    [person introduce];
    [person release];
    return 0;
}

在上述代码中,Person 类继承自 NSObject,这是 Objective-C 中所有类的根类。nameage 是成员变量,setName:namesetAge:ageintroduce 是方法。

9.2 封装

封装是将数据和操作数据的方法封装在一起,通过访问控制来保护数据的安全性。在 Objective-C 中,可以使用 @private@protected@public 关键字来控制成员变量的访问权限。例如:

@interface Animal : NSObject {
    @private
        NSString *species;
    @protected
        int age;
    @public
        NSString *name;
}
- (void)setSpecies:(NSString *)newSpecies;
- (NSString *)species;
@end

@implementation Animal
- (void)setSpecies:(NSString *)newSpecies {
    species = newSpecies;
}
- (NSString *)species {
    return species;
}
@end

在上述代码中,species 是私有成员变量,只能在类内部访问;age 是受保护成员变量,可以在类及其子类中访问;name 是公共成员变量,可以在任何地方访问。

9.3 继承

继承是指一个类可以从另一个类中获取属性和方法。子类可以继承父类的特性,并可以添加自己的新特性。例如:

@interface Vehicle : NSObject {
    NSString *brand;
    int wheels;
}
- (void)setBrand:(NSString *)newBrand;
- (NSString *)brand;
- (void)setWheels:(int)newWheels;
- (int)wheels;
@end

@implementation Vehicle
- (void)setBrand:(NSString *)newBrand {
    brand = newBrand;
}
- (NSString *)brand {
    return brand;
}
- (void)setWheels:(int)newWheels {
    wheels = newWheels;
}
- (int)wheels {
    return wheels;
}
@end

@interface Car : Vehicle {
    int doors;
}
- (void)setDoors:(int)newDoors;
- (int)doors;
@end

@implementation Car
- (void)setDoors:(int)newDoors {
    doors = newDoors;
}
- (int)doors {
    return doors;
}
@end

在上述代码中,Car 类继承自 Vehicle 类,Car 类拥有 Vehicle 类的 brandwheels 成员变量以及相关方法,同时还拥有自己的 doors 成员变量和方法。

9.4 多态

多态是指不同对象对同一消息作出不同响应的能力。在 Objective-C 中,通过继承和方法重写来实现多态。例如:

@interface Shape : NSObject
- (void)draw;
@end

@implementation Shape
- (void)draw {
    NSLog(@"Drawing a shape.");
}
@end

@interface Circle : Shape
- (void)draw;
@end

@implementation Circle
- (void)draw {
    NSLog(@"Drawing a circle.");
}
@end

@interface Rectangle : Shape
- (void)draw;
@end

@implementation Rectangle
- (void)draw {
    NSLog(@"Drawing a rectangle.");
}
@end

使用多态:

Shape *shape1 = [[Circle alloc] init];
Shape *shape2 = [[Rectangle alloc] init];
[shape1 draw];
[shape2 draw];
[shape1 release];
[shape2 release];

在上述代码中,CircleRectangle 类继承自 Shape 类并重写了 draw 方法。通过 Shape 类型的指针调用 draw 方法时,实际调用的是具体对象所属类的 draw 方法,实现了多态。

十、内存管理

10.1 引用计数原理

Objective-C 使用引用计数(Reference Counting)来管理内存。每个对象都有一个引用计数,当对象被创建时,引用计数为 1。每次有新的指针指向该对象时,引用计数加 1;当指针不再指向该对象时,引用计数减 1。当引用计数为 0 时,对象的内存被释放。例如:

NSString *str1 = [[NSString alloc] initWithString:@"Hello"]; // str1 引用计数为 1
NSString *str2 = str1; // str2 引用计数加 1,此时 str1 和 str2 引用计数都为 2
[str1 release]; // str1 引用计数减 1,此时 str1 引用计数为 1,str2 引用计数仍为 2
[str2 release]; // str2 引用计数减 1,此时引用计数为 0,对象内存被释放

10.2 自动释放池

自动释放池(Autorelease Pool)是一种内存管理机制,它可以延迟对象的释放。当一个对象发送 autorelease 消息时,它会被添加到最近的自动释放池中。当自动释放池被销毁时,池中的所有对象都会收到 release 消息。例如:

@autoreleasepool {
    NSString *str = [[NSString alloc] initWithString:@"World"];
    [str autorelease];
    // 自动释放池销毁时,str 会收到 release 消息
}

10.3 ARC(自动引用计数)

ARC 是 Xcode 4.2 引入的一种自动内存管理机制,它大大简化了内存管理的工作。在 ARC 模式下,编译器会自动在适当的位置插入 retainreleaseautorelease 等内存管理代码。例如:

NSString *str = @"Hello ARC";
// 在 ARC 模式下,不需要手动管理 str 的内存

虽然 ARC 方便了开发,但开发者仍需要了解内存管理的基本原理,以便在处理循环引用等复杂情况时能够正确处理内存问题。

十一、类别与协议

11.1 类别(Category)

类别允许在不子类化的情况下向现有类添加方法。使用 @interface@implementation 定义类别,类别名放在类名后面的括号中。例如:

@interface NSString (MyCategory)
- (NSString *)reverseString;
@end

@implementation NSString (MyCategory)
- (NSString *)reverseString {
    NSMutableString *reversed = [NSMutableString stringWithCapacity:[self length]];
    NSInteger length = [self length];
    for (NSInteger i = length - 1; i >= 0; i--) {
        [reversed appendFormat:@"%C", [self characterAtIndex:i]];
    }
    return reversed;
}
@end

使用类别方法:

NSString *original = @"Hello";
NSString *reversed = [original reverseString];
NSLog(@"Reversed string: %@", reversed);

11.2 协议(Protocol)

协议定义了一组方法的声明,但不提供实现。任何类都可以声明遵守某个协议,并实现协议中的方法。例如:

@protocol Printable <NSObject>
- (void)print;
@end

@interface Document : NSObject <Printable>
@property (nonatomic, copy) NSString *content;
@end

@implementation Document
- (void)print {
    NSLog(@"Printing document: %@", self.content);
}
@end

在上述代码中,Document 类声明遵守 Printable 协议,并实现了 print 方法。

通过以上对 Objective-C 基础语法的全面解析,希望读者能够对这门编程语言有更深入的理解,为进一步的 iOS 和 macOS 开发打下坚实的基础。在实际开发中,还需要不断实践和探索,以掌握更多高级特性和优化技巧。