[Code] Pass by reference.

December 14, 2016

Vài điều về pointer

Pointer, mỗi lần nghe đến khái niệm này là bắt đầu chân tay bủn rủn, chóng mặt nhức đầu. tuy nhiên Bi Đừng sợ, ko đến mức đó đâu. Hôm nay tôi chia sẻ về một vấn đề rất hay sư dụng pointer: Pass by reference.

Trước hết, bạn nên giữ trong đầu các nội dung chính của pointer: * Pointer chứa địa chỉ của vùng nhớ (memory address value). * Pointer có địa chỉ của vùng nhớ cho chính nó. * Tất cả các param, bao gồm cả pointer, đều đc copy để truyền vào các method.

Như chúng ta đã biết, trong ngôn ngữ C, có 1 loại kiểu dữ liệu: primitive type (hay còn gọi là value type), và object (còn gọi là reference type).

Primitive type luôn đc khởi tạo trong stack. Ngoài ra còn có struct, cũng đc khởi tạo ở stack luôn. Ngược lại, Object thì phức tạp hơn, cũng giống như struct nó cũng có thể chứa các biến primitive, nhưng hơn nữa, nó còn có thể chứa các method. Do vậy, nó sẽ cần một sự phức tạp, và độ lớn về memory, cho nên nó sẽ ko thật sự là hiệu quả nếu ta lưu objects ở stack. Object sẽ được lưu tại heap.

Trong objective C, chúng ta thưởng khởi tạo object với pointer (con trỏ) và giữ địa chỉ của biến nó trỏ tới. Lúc này, pointer đóng vai trò giống như một primitive type (lưu trên stack) và lưu trữ địa chỉ của vùng nhớ của object đc lưu trên heap. Easy?

Tiếp tục với method nhé, mọi method đc gọi, nó sẽ đc khởi tạo trên stack, và các tham số truyền vào, đều đc copy, cái này đc gọi là pass by value. Cụ thể, mỗi khi method đc gọi, các param nhận vào nó nằm ở 1 vùng nhớ khác so vs giá trị truyền vào từ caller. Cụ thể:

x = 5;
int y = 3;
int z = [self add:x and:y];
NSLog(@"%d", z);

-(int) add:(int)a and:(int)b {
  return a + b;
}

Nhưng với pointer thì sao, cũng vậy thôi :D Cho dù pointer chỉ chứa địa chỉ của giá trị nó point tới, tuy nhiên thì nó cũng có địa chỉ cho chính bản thân nó. Khi đc pass vào method, một pointer mới sẽ đc sinh ra, có địa chỉ riêng mới cho nó, tuy nhiên, bản chất nó vẫn pointer tới cái giá trị cũ mà thằng pointer gốc nó có. Ví dụ dưới đây, giá trị vẫn đc thay đổi như mong đợi.

UIView *view = [[UIView alloc] init];
view.tag = 1;
NSLog(@"View tag - before: %ld", view.tag);
[self changeViewTag:view];
NSLog(@"View tag - after: %ld", view.tag);

- (void)changeViewTag:(UIView *)view {
    view.tag = 2;
}

//output
View tag - before: 1
View tag - after: 2

Phải làm gì nếu khi muốn thay đổi giá trị của input param mà input pointer đang point tới object khác?

Tình huống sẽ như thế này:

UIView *view = [[UIView alloc] init];
view.tag = 1;
NSLog(@"View tag - before: %ld", view.tag);
[self changeViewTag:view];
NSLog(@"View tag - after: %ld", view.tag);

- (void)changeViewTag:(UIView *)view {
    view = [[UIView alloc] init];
    view.tag = 2;
}

// Output: 
View tag - before: 1
View tag - after: 1

Trong hàm changeViewTag, chúng ta khởi tạo cho input pointer point tới một vùng nhớ khác. Vậy khi ta thay đổi thằng view mới, thì thằng cũ ko đc thay đổi. Rất bất tiện.

Vậy, trong trường hợp này, ta vẫn muốn thay đổi giá trị của original pointer thì sao? Cái này nảy sinh ra vấn đề, thay vì chúng ta truyền pointer vào như input param, chúng ta sẽ truyền double pointer (). Cách này được gọi là **pass by reference. Mình sẽ truyền giá trị của pointer truyền vào, trong hàm xử lý nó sẽ Dereference pointer của pointer mình đã truyền. Kiểu này:

NSString *oriString = @"sample string";
NSLog(@"oriString - before: %@", oriString);
[self upperString:&oriString];
NSLog(@"oriString - after: %@", oriString);

-(void)upperString:(NSString**)string {
    *string = [*string uppercaseString];
}

// output
oriString - before: sample string
oriString - after: SAMPLE STRING

Như vậy, origianl input đã đc thay đổi.


Ứng dụng của pass by reference

  • Ko sinh ra copy của input, đâm ra nó sẽ nhanh hơn đặc biệt làm việc với các object cực lón (large structs or classes).
  • Nhiều khi, chúng ta cần thay đổi giá trị của original input (ví như swapping, sorting array …) thì rất tiện. Và ko tốn lớn bộ nhớ.
  • Nếu bạn dùng pointer (con trỏ), thì lại rất tiện, ko cần quan tâm đến việc null value, khiến cho code trở nên safe.

Tổng kết

Tóm lại, đọc nhiều lằng nhằng ko biết các bạn cho vô đầu đc j ko, mình xin tổng kết vài điều cần nhơ sau:

- Pointer chứa địa chỉ của vùng nhớ (memory address value).
- Pointer có địa chỉ của vùng nhớ cho chính nó.
- Tất cả các param, bao gồm cả pointer, đều đc copy để truyền vào các method.
- Chú ý khái niệm về pointer của pointer.
- Pointer cũng chỉ là 1 kiểu dữ liệu, nằm trên stack. Nó chứa địa chỉ của biến khác, đâm ra nó rất nhẹ, cho dù biến nó pointer lớn.
- Các biến primitive thì đc tạo trên stack, objects thì đc khởi tạo trên heap. Đọc thêm về stack và heap <http://net-informations.com/faq/net/stack-heap.htm>

Như thường lệ, happy coding. Để lại comment nếu bạn muốn bàn luận thêm về vấn đề này nha…

Ref: