Structs
Một struct giống như một đối tược C đơn giản. Struct cho phép tích hợp nhiều biến vào một cấu trúc phức tạp hơn, nhưng không cung cấp thêm bất cứ chức năng OOP nào (ví dụ: method). Ví dụ, đoạn code sau sử dụng struct để nhóm các thành phần của một màu RGB. Hãy chú ý cách ta sử dụng typedef để có thể truy cập đến struct một cách dễ hiểu hơn.
// main.m
#import <Foundation/Foundation.h>
typedef struct {
unsigned char red;
unsigned char green;
unsigned char blue;
} Color;
int main(int argc, const char * argv[]) {
@autoreleasepool {
Color carColor = {255, 160, 0};
NSLog(@"Your paint job is (R: %hhu, G: %hhu, B: %hhu)",
carColor.red, carColor.green, carColor.blue);
}
return 0;
}
Để tạo mới một cấu trúc carColor, ta sử dụng cú pháp khởi tạo {255, 166, 0}. Cú pháp này gán dữ liệu với cùng thứ tự được khai báo trong struct. Và như bạn có thể thấy, mỗi thành phần của struct có thể được truy cập đến với cú pháp dấu chấm.
Enums
Từ khoá enum được sử dụng để tạo những kiểu dữ liệu liệt kê – một tập hợp của những hằng số liên quan. Giống như struct, ta thường sử dụng typedef để định nghĩa kiểu của enum với một cái tên có nghĩa:
// main.m
#import <Foundation/Foundation.h>
typedef enum {
FORD,
HONDA,
NISSAN,
PORSCHE
} CarModel;
int main(int argc, const char * argv[]) {
@autoreleasepool {
CarModel myCar = NISSAN;
switch (myCar) {
case FORD:
case PORSCHE:
NSLog(@"You like Western cars?");
break;
case HONDA:
case NISSAN:
NSLog(@"You like Japanese cars?");
break;
default:
break;
}
}
return 0;
}
Vì biến myCar được khai báo với kiểu CarModel, nó chỉ có thể mang giá trị là bốn hằng số được khai báo trong kiểu enum: FORD, HONDA, NISSAN và PORSCHE. Định nghĩa các hằng số này trong kiểu enum thì an toàn hơn là biểu diễn các model của oto khác nhau bằng String, bởi string thì không thể đoán trước được các lỗi đánh sai (trình biên dịch sẽ báo lỗi nếu bạn đánh sai chính tả một trong các hằng số trong kiểu enum)
Primitive Arrays
Vì Objective-C chứa cả C, ta có thể sử dụng mảng cơ bản trong C. Thông thường, ta sử dụng đối tượng NSArray và NSMutableArray trong Foundation Framework vì chúng tiện hơn mảng C nhiều; tuy nhiên mảng C vẫn tỏ ra có ích khi cần lập trình tối ưu hoá tốc độ.
int years[4] = {1968, 1970, 1989, 1999};
years[0] = 1967;
for (int i=0; i<4; i++) {
NSLog(@"The year at index %d is: %d", i, years[i]);
}
Câu lệnh int years[4] cấp phát một vùng bộ nhớ đủ lớn để chứa 4 giá trị int. Sau đó ta khởi tạo mảng sử dụng cú pháp khởi tạo {1968, …} và truy cập vào các phần tử bằng cách truyền offset ở giữa dấu ngoặc vuông. (years[i])
Pointers
Một con trỏ trỏ tới một địa chỉ bộ nhớ. Trong khi một biến là một vật chứa chứa một giá trị nào đó, con trỏ xoá bỏ sự trừu tượng và cho bạn thấy thực chất các giá trị được lưu như thế nào. Điều này cần hai thứ:
- Toán tử (&) trả về địa chỉ bộ nhớ của một biến thông thường. Đây là các bạn tạo ra con trỏ.
- Toán tử (*) trả về nội dung của một địa chỉ bộ nhớ của con trỏ.
Ví dụ sau đây chỉ ra cách khai báo, khởi tạo và lấy nội dung con trỏ. Chú ý rằng định nghĩa một con trỏ giống hệt định nghĩa một biến thông thường, trừ việc nó có thêm một dấu (*)
int year = 1967; // Define a normal variable
int *pointer; // Declare a pointer that points to an int
pointer = &year; // Find the memory address of the variable
NSLog(@"%d", *pointer); // Dereference the address to get its value
*pointer = 1990; // Assign a new value to the memory address
NSLog(@"%d", year); // Access the value via the variable
Cách hoạt động của những phép toán con trỏ này được biểu diễn như sau:
Trong ví dụ trên, các con trỏ có vẻ như chỉ là lớp trừu tượng không cần thiết cho một biến bình thường. Tác dụng thực sự của chúng là khi bạn có thể chuyển địa chỉ mà con trỏ đang trỏ tới. Điều này rất hữu dụng khi cần duyệt mảng mà thực chất chỉ là những ô bộ nhớ liên tục. Ví dụ sau sử dụng con trỏ để duyệt qua các phần tử của một mảng.
char model[5] = {'H', 'o', 'n', 'd', 'a'};
char *modelPointer = &model[0];
for (int i=0; i<5; i++) {
NSLog(@"Value at memory address %p is %c",
modelPointer, *modelPointer);
modelPointer++;
}
NSLog(@"The first letter is %c", *(modelPointer - 5));
Khi sử dụng con trỏ, phép ++ di chuyển địa chỉ tới vùng nhớ tiếp theo, ta có thể hiển thị qua NSLog() với format %p. Tương tự, phép — có thể dùng để giảm địa chỉ con trỏ tới ô nhớ phía trước nó. Và như đã chỉ ra trong dòng cuối, bạn có thể truy cập vào địa chỉ bộ nhớ liên quan đến địa chỉ con trỏ hiện tại.
The Null Pointer
Con trỏ null là một con trỏ đặc biệt không trỏ vào đâu cả. Chỉ có một con trỏ null duy nhất trong C và có thể gọi nó bằng Macro NULL. Điều này rất tiện cho việc khởi tạo biến rỗng – biến mà không thể tạo ra được với kiểu dữ liệu bình thường. Ví dụ, đoạn code sau cho thấy làm thể nào một con trỏ có thể bị “làm rỗng” với con trỏ null.
int year = 1967;
int *pointer = &year;
NSLog(@"%d", *pointer); // Do something with the value
pointer = NULL; // Then invalidate it
Cách duy nhất để biểu diễn biến rỗng sử dụng chính “year” là gán cho nó bằng 0. Dĩ nhiên, vấn đề là 0 vẫn là một giá trị hoàn toàn hợp lê – nó không phải là giá trị rỗng.
Void Pointers
Một con trỏ void là một kiểu chung chung có thể trỏ tới bất cứ thứ gì. Đôi khi ta rất cần trỏ tới một đoạn bộ nhớ tuỳ ý. Theo đó, ta cần nhiều thông tin hơn về đoạn bộ nhớ này để có thể lấy ra được giá trị của nó. Cách tốt nhất là ép kiểu sang một kiểu khác void. Ví dụ, câu lệnh (int *) ép kiểu con trỏ và lấy ra được giá trị của con trỏ void theo kiểu giá trị int.
int year = 1967;
void *genericPointer = &year;
int *intPointer = (int *)genericPointer;
NSLog(@"%d", *intPointer);
Tính chung chung của con trỏ void tạo ra rất nhiều sự mềm dẻo. Ví dụ, lớp NSString định nghĩa các method sau để chuyển từ mảng C sang một đối tượng Objective-C string:
- (id)initWithBytes:(const void *)bytes
length:(NSUInteger)length
encoding:(NSStringEncoding)encoding
Biến bytes trỏ tới địa chỉ bộ nhớ đầu tiên của một mảng kiểu C bất kỳ, biến length cho biết cần phải đọc bao nhiêu byte, và encoding xác định số byte đó sẽ tổ chức như thế nào. Sử dụng con trỏ void như thế này cho phép làm việc với mảng của bất cứ kiểu nào. Một số cách khác là định nghĩa một số method với đầu vào là một byte duy nhất, UTF-8, UTF-15, …
Pointers in Objective-C
Tất cả những điều trên đều là những kiến thức nền tảng tuyệt với, nhưng với việc phát triển ngôn ngữ Objective-C, bạn sẽ không cần sử dụng với hầu hết kiến thức đó. Điều duy nhất mà bạn cần thực sự hiểu là tất cả đối tượng Objective-C đều là con trỏ. Ví dụ, một đối tượng NSString phải được lưu trữ dưới dạng con trỏ, không phải biến thông thường:
NSString *model = @"Honda";
Với con trỏ null, có một sự khác biệt nhỏ giữa C và Objective C. Trong khi C sử dụng NULL thì Objective C có định nghĩa macro của riêng nó, nil. Một quy ước là ta nên sử dụng nil cho biến lưu giá trị của đối tượng Objective C và NULL khi làm việc với con trỏ C.
NSString *anObject; // An Objective-C object
anObject = NULL; // This will work
anObject = nil; // But this is preferred
int *aPointer; // A plain old C pointer
aPointer = nil; // Don't do this
aPointer = NULL; // Do this instead
Ngoài việc khai báo biến, toàn bộ cú pháp Objective-C được thiết kế để làm việc với con trỏ. Vì vây, sau khi khai báo một biến con trỏ, về cơ bản thì bạn có thể quên đi việc nó là con trỏ và tương tác với nó như một biến thông thường.
Bài từ Ry’s Tutorial
Bình luận