Mở đầu
1. Hiểu về con trỏ
2. Con trỏ tới biến
3. Mảng con trỏ *ptr[]
a. Khái niệm
Mỗi phần tử của mảng đều là một con trỏ, và có thể lưu trữ địa chỉ của hàm, chuỗi bất cứ ở đâu được cấp phát trong RAM.
int *arr[5]; // Mảng này có 5 phần tử là 5 con trỏ int.
b. Ứng dụng
Lưu trữ nhiều chuỗi kí tự
#include <stdio.h> int main() { char *fruits[] = {"Apple", "Banana", "Cherry"}; printf("dia chi cua (fruits + 0) = %p\n", (fruits + 0)); // &fruits[0] printf("dia chi cua (fruits + 1) = %p\n", (fruits + 1)); // &fruits[1] printf("dia chi cua (fruits + 2) = %p\n", (fruits + 2)); // &fruits[2] printf("gia trị tại dia chi (fruits + 0) = %p\n", *(fruits + 0)); // fruits[0] printf("gia trị tại dia chi (fruits + 1) = %p\n", *(fruits + 1)); // fruits[1] printf("gia trị tại dia chi (fruits + 2) = %p\n", *(fruits + 2)); // fruits[2] // các phần tử printf("địa chỉ cua A = %p\n", *(fruits + 0) + 0); // &fruits[0][0] printf("giá trị cua A = %c\n", *(*(fruits + 0) + 0)); // fruits[0][0] printf("địa chỉ cua p = %p\n", *(fruits + 0) + 1); // &fruits[0][1] printf("giá trị cua p = %c\n", *(*(fruits + 0) + 1)); // fruits[0][1] // Tươnh tự }
Output
dia chi cua (fruits + 0) = 0x7ffdd2716df0
dia chi cua (fruits + 1) = 0x7ffdd2716df8
dia chi cua (fruits + 2) = 0x7ffdd2716e00
gia trị tại dia chi (fruits + 0) = 0x5635ea3e9008
gia trị tại dia chi (fruits + 1) = 0x5635ea3e900e
gia trị tại dia chi (fruits + 2) = 0x5635ea3e9015
địa chỉ cua A = 0x5635ea3e9008
giá trị cua A = A
địa chỉ cua p = 0x5635ea3e9009
giá trị cua p = p
Note: Hãy chú ý khoảng cách giữa 2 địa chỉ, bạn có thể hiểu thêm khi suy nghĩ về nó: "Tại sao nó lại có số lượng byte như vậy?"
Điều này có khác biệt so với khi ta sử dụng char arr[][]
- mảng 2D kiểu char để lưu trữ nhiều chuỗi.
- Khi dùng theo cách này thì mỗi chuỗi phải yêu cầu kích thước cố định -> Điều này gây ra lẫn phí bộ nhớ nếu không dùng hết cho mỗi chuỗi.
Còn char *arr[]
chỉ lưu con trỏ đến string, tối ưu bộ nhớ.
Quản lí mảng 2D có độ dài khác nhau
#include <stdio.h> int main() { int row1[] = {1, 2, 3}; int row2[] = {4, 5}; int row3[] = {6, 7, 8, 9}; int *arr[] = {row1, row2, row3}; // mỗi phần tử là 1 con trỏ printf("%d\n", arr[0][2]); // 3 printf("%d\n", arr[1][1]); // 5 printf("%d\n", arr[2][3]); // 9
}
Callback table / Function pointer table
Trong embedded, hay dùng mảng con trỏ hàm để chọn hàm xử lý theo sự kiện.
#include <stdio.h> void led_on() { printf("LED ON\n"); }
void led_off() { printf("LED OFF\n"); }
void led_toggle() { printf("LED TOGGLE\n"); } int main() { void (*actions[])() = {led_on, led_off, led_toggle}; actions[0](); // gọi led_on actions[2](); // gọi led_toggle
}
Mỗi phần tử trong mảng là một con trỏ hàm.
4. Con trỏ tới mảng (*ptr)[]
a. Khái niệm
Cần phải phân biệt rõ 2 khái niệm: Con trỏ tới một mảng và mảng các con trỏ vì chúng có cách viết khá tương tự nhau nhưng khác 1 chút xíu.
- Pointer to array: con trỏ sẽ trỏ tới cả một mảng (block liên tục).
int (*p)[5]; // con trỏ tới một mảng có 5 phần tử
- Array of pointers: mỗi phần tử của mảng sẽ là một con trỏ
int *arr[5]; // arr: một mảng có 5 phần tử là con trỏ int.
Ví dụ
Trong ví dụ này, tôi sẽ tạo ra 2 kiểu con trỏ để so sánh sự khác nhau của nó:
int *p
: Con trỏ mà nó trỏ đến phần tử đầu tiên củaarr
int(*ptr)[5]
: Con trỏ này sẽ trỏ đến toàn bộ mảng
#include <stdio.h> int main() { int arr[5] = {1, 2, 3, 4, 5}; // Tạo con trỏ integer int *p; // Tạo con trỏ tới một mảng int(*ptr)[5]; // Trỏ tới phần tử 0 của arr p = arr; // Trỏ tới toàn bộ arr ptr = &arr; // Địa chỉ mà 2 con trỏ đang trỏ tới. printf("p = %p\n", p); printf("*ptr = %p\n\n", *ptr); // Tăng con trỏ -> tăng đia chỉ p++; ptr++; // Địa chỉ và giá trị mà 2 con trỏ đang trỏ tới sau khi tăng. printf("p = %p\n", p); printf("*p = %d\n", *p); printf("ptr = %p\n", ptr); printf("*ptr = %p\n", *ptr); return 0;
}
Output:
p = 0x7fff30a19ce0
*ptr = 0x7fff30a19ce0 p = 0x7fff30a19ce4
*p = 2
ptr = 0x7fff30a19cf4
*ptr = 0x7fff30a19cf4
Kết luận
- (1): Địa chỉ ban đầu mà cả 2 con trỏ đang trỏ tới arr là như nhau
0x7fff30a19ce0
.
Tuy nhiên sau khi tăng hai con trỏ bằng
p++;
ptr++;
Địa chỉ p
đang trỏ tới 0x7fff30a19ce4
cách nhau 4 byte so với địa chỉ phần tử đầu tiên.
Địa chỉ ptr
đang trỏ tới 0x7fff30a19cf4
cách nhau 20 byte = size của arr[5] so với lúc chưa tăng con trỏ.
Có thể xác thực điều này bằng cách in ra giá trị tại địa chỉ mà con trỏ *ptr
và (*ptr)[5]
đang trỏ tới.
- Trước khi tăng con trỏ
printf("*p = %d\n", *p);
printf("(*ptr)[0] = %d\n", (*ptr)[0]); // trỏ tới vị trỉ đâu tiên của block
sẽ được kết quả
*p = 1
(*ptr)[0] = 1
- Sau khi tăng con trỏ, sẽ được kết quả
*p = 2
(*ptr)[0] = 32766 // giá trị rác trong máy tính
- (2): Giá trị của
*ptr
vàptr
đều như nhau (đều là địa chỉ).
b. Ứng dụng
Quản lí mảng 2 chiều
Code mẫu
#include <stdio.h> int main()
{ // hàng x cột int arr_2D[2][3] = { {1, 2, 3}, {4, 5, 6} }; printf("Đia chỉ của arr_2D[0][0] = %p\n", &arr_2D[0][0]); printf("Đia chỉ của arr_2D[1][0] = %p\n", &arr_2D[1][0]); // Tạo 1 con trỏ tới mảng để quản lí mảng 2 chiều int (*ptr_arr_2D)[3] = arr_2D; // Quản li hàng [0] bằng cách trỏ tới địa chỉ của arr_2D[0][0] printf("(*ptr_arr_2D)[3] = %p\n", ptr_arr_2D); printf("\nLấy phần tử ở hàng 0 của arr_2D thông qua ptr_arr_2D\n"); printf("arr_2D[0][0] = %d\n", (*ptr_arr_2D)[0]); printf("arr_2D[0][1] = %d\n", (*ptr_arr_2D)[1]); printf("arr_2D[0][2] = %d\n", (*ptr_arr_2D)[2]); // Quản li hàng [1] bằng cách trỏ tới địa chỉ của arr_2D[1][0] ptr_arr_2D++; printf("\n(*ptr_arr_2D)[3]= %p sau khi tăng\n", ptr_arr_2D); printf("Lấy phần tử ở hàng 1 của arr_2D thông qua ptr_arr_2D\n"); printf("arr_2D[1][0] = %d\n", (*ptr_arr_2D)[0]); printf("arr_2D[1][1] = %d\n", (*ptr_arr_2D)[1]); printf("arr_2D[1][2] = %d\n", (*ptr_arr_2D)[2]); return 0;
}
Output
Đia chỉ của arr_2D[0][0] = 0x7ffea704d410
Đia chỉ của arr_2D[1][0] = 0x7ffea704d41c
(*ptr_arr_2D)[3] = 0x7ffea704d410 Lấy phần tử ở hàng 0 của arr_2D thông qua ptr_arr_2D
arr_2D[0][0] = 1
arr_2D[0][1] = 2
arr_2D[0][2] = 3 (*ptr_arr_2D)[3]= 0x7ffea704d41c sau khi tăng
Lấy phần tử ở hàng 1 của arr_2D thông qua ptr_arr_2D
arr_2D[1][0] = 4
arr_2D[1][1] = 5
arr_2D[1][2] = 6
Mảng 2D sẽ được sắp xếp các byte liên tiếp trong memory của máy tính.
Quản lí memory có kích thước cố định
Ta có thể tạo các memory có kích thước cố định và dùng Pointer to Array.
Kết
- Bài viết này chưa đầy đủ, đây chỉ là ghi chép tạm thời của tôi trong quá trình học, tôi sẽ bổ sung đầy đủ hơn trong tương lai gần.
- Hy vọng qua bài viết này có thể giúp ích các bạn hiểu hơn về cách sử dụng con trỏ tới 1 mảng để quản lí memory.
- Nếu bạn có thắc mắc gì hãy bình luận dưới bài viết.