Trong bài học này, Lập trình không khó sẽ cùng các bạn đi tìm hiểu mối quan hệ giữa con trỏ và mảng trong ngôn ngữ lập trình C. Bạn sẽ học thêm về một số toán tử của con trỏ, sử dụng các toán tử đó để duyệt mảng. Do đó, bạn sẽ biết thêm 1 cách mới để lặp qua mảng sử dụng con trỏ. Tất nhiên, mục tiêu cao hơn hết là giúp bạn hiểu sâu hơn, biết thêm các kiến thức về con trỏ trong ngôn ngữ C.
Trước khi bạn bắt đầu bài học này, bạn cần chắc chắn mình nắm rõ các kiến thức dưới đây:
Các phần tử của mảng là các ô nhớ liên tiếp
Nhắc lại khái niệm về mảng: “Mảng là một tập hợp tuần tự các phần tử có cùng kiểu dữ liệu và các phần tử được lưu trữ trong một dãy các ô nhớ liên tục trên bộ nhớ“.
Các bạn đặc biệt lưu ý tới tính chất được lưu trên các ô nhớ liên tục, bây giờ chúng ta sẽ chứng minh tính đúng đắn của nó bằng ví dụ dưới đây:
#include <stdio.h> int main(){ // Khai báo mảng có 5 phần tử int arr[] = {1, 2, 3, 4, 5}; printf("Dia chi cua mang arr = %dn", &arr); printf("Gia chi cua mang arr = %dn", arr); // Thử in địa chỉ của từng phần tử // sizeof (arr): Kích thước của mảng // sizeof (int): Kích thước của kiểu int for(int i = 0; i < sizeof (arr) / sizeof (int); i++){ printf("Dia chi cua arr[%d] = %dn", i, &arr[i]); } }
Kết quả chạy chương trình trên:
Dia chi cua mang arr = 6487552 Gia chi cua mang arr = 6487552 Dia chi cua arr[0] = 6487552 Dia chi cua arr[1] = 6487556 Dia chi cua arr[2] = 6487560 Dia chi cua arr[3] = 6487564 Dia chi cua arr[4] = 6487568
Nhận xét:
- Các phần tử liên tiếp có địa chỉ cách nhau 4 giá trị, bởi vì 1 phần tử kiểu
int
có kích thước 4 bytes (máy tính x64). Nên ta chắc chắn các phần tử mảng được xếp cạnh nhau trong bộ nhớ. - Một điều đặc biệt nữa, như mình có nói là khi truyền mảng vào hàm thì mặc định là truyền theo tham chiếu. Và trong ví dụ này bạn thấy đó, địa chỉ của biến mảng chính là địa chỉ của phần tử đầu tiên của mảng. Và giá trị của biến mảng cũng chính là địa chỉ của phần tử đầu tiên của mảng.
- Như vậy,
&arr[0]
tương đương&arr
và tương đươngarr
. Điều đó có được là do biếnarr
trỏ tới phần tử đầu tiên của mảng.
Toán tử tăng và giảm của con trỏ
Giống như biến thông thường, con trỏ cũng có toán tử tăng và giảm. Nhưng cách toán tử tăng/ giảm trên con trỏ làm việc như nào.
#include <stdio.h> int main() { // Khai báo mảng có 5 phần tử int arr[] = {1, 2, 3, 4, 5}; // Thử in địa chỉ của từng phần tử // sizeof (arr): Kích thước của mảng // sizeof (int): Kích thước của kiểu int for (int i = 0; i < sizeof(arr) / sizeof(int); i++) { printf("Dia chi cua arr[%d] = %dn", i, &arr[i]); } printf("n___________________________n"); // Gán con trỏ p cho phần tử đầu tiên của mảng int *p = &arr[0]; // Lấy giá trị của con trỏ p printf("Gia tri cua con tro p = %dn", p); // Lấy giá trị của địa chỉ mà p đang trỏ đến printf("Gia tri cua dia chi ma p dang tro den = %dn", *p); // Toán tử tăng trên con trỏ p++; // hoặc p = p + 1; // Lấy giá trị của con trỏ p printf("Gia tri cua con tro p = %dn", p); // Lấy giá trị của địa chỉ mà p đang trỏ đến printf("Gia tri cua dia chi ma p dang tro den = %dn", *p); // Toán tử tăng trên con trỏ p += 2; // hoặc p = p + 2; // Lấy giá trị của con trỏ p printf("Gia tri cua con tro p = %dn", p); // Lấy giá trị của địa chỉ mà p đang trỏ đến printf("Gia tri cua dia chi ma p dang tro den = %dn", *p); // Toán tử giảm trên con trỏ p--; // hoặc p = p - 1; // Lấy giá trị của con trỏ p printf("Gia tri cua con tro p = %dn", p); // Lấy giá trị của địa chỉ mà p đang trỏ đến printf("Gia tri cua dia chi ma p dang tro den = %dn", *p); }
Kết quả chạy:
Dia chi cua arr[0] = 6487536 Dia chi cua arr[1] = 6487540 Dia chi cua arr[2] = 6487544 Dia chi cua arr[3] = 6487548 Dia chi cua arr[4] = 6487552 ___________________________ Gia tri cua con tro p = 6487536 Gia tri cua dia chi ma p dang tro den = 1 Gia tri cua con tro p = 6487540 Gia tri cua dia chi ma p dang tro den = 2 Gia tri cua con tro p = 6487548 Gia tri cua dia chi ma p dang tro den = 4 Gia tri cua con tro p = 6487544 Gia tri cua dia chi ma p dang tro den = 3
Như bạn thấy:
- Khi dùng toán tử tăng/ giảm trên biến con trỏ, nó sẽ nhảy sang ô nhớ liền kề chứ không phải tăng địa chỉ mà nó đang trỏ lên 1. Do con trỏ
p
là kiểuint
nên mỗi bước tăng, giá trị củap
tăng thêm 4 giá trị. (Lưu ý: giá trị của con trỏ là địa chỉ mà nó đang trỏ tới) - Nếu bạn muốn tăng giá trị của địa chỉ nơi con trỏ đang trỏ tới, hãy dùng cách dưới đây:
#include <stdio.h> int main() { // Khai báo mảng có 5 phần tử int arr[] = {1, 2, 3, 4, 5}; // Gán con trỏ p cho phần tử đầu tiên của mảng int *p = &arr[0]; // Lấy giá trị của địa chỉ mà p đang trỏ đến printf("Gia tri cua dia chi ma p dang tro den = %dn", *p); // Tăng giá trị của địa chỉ mà `p` đang trỏ tới thông qua `p`. (*p)+= 5; // Lấy giá trị của địa chỉ mà p đang trỏ đến printf("Gia tri cua dia chi ma p dang tro den = %dn", *p); } // Gia tri cua dia chi ma p dang tro den = 1 // Gia tri cua dia chi ma p dang tro den = 6
Mối quan hệ giữa con trỏ và mảng trong C
Tới đây chắc hẳn bạn đã hình dung được sự liên hệ giữa con trỏ và mảng, mình sẽ cùng các bạn đi tới các kết luận về con trỏ và mảng nhé.
Với mảng trong ảnh phía trên, ta có:
&x[0]
vàx
có cùng giá trị, vàx[0]
hay*x
là tương đương nhau.&x[1]
tương đương vớix+1
vàx[1]
tương đương với*(x+1)
.&x[2]
tương đương vớix+2
vàx[2]
tương đương với*(x+2)
.- …
- Tóm lại,
&x[i]
tương đương vớix+i
vàx[i]
tương đương với*(x+i)
.
Hãy thử nhập xuất mảng theo cách mới nào:
// Ví dụ mối quan hệ giữa con trỏ và mảng - Lập Trình Không Khó #include <stdio.h> #define MAX_SIZE 100 int main() { int arr[MAX_SIZE]; int n; do { printf("Nhap so luong phan tu: "); scanf("%d", &n); } while (n < 1); // Nhập mảng for (int i = 0; i < n; i++) { printf("Nhap a[%d] = ", i); scanf("%d", (arr + i)); } // Xuất mảng for (int i = 0; i < n; i++) { printf("nGia tri a[%d] = %d", i, *(arr + i)); } }
Kết quả chạy:
Nhap so luong phan tu: 5 Nhap a[0] = 1 Nhap a[1] = 2 Nhap a[2] = 3 Nhap a[3] = 4 Nhap a[4] = 5 Gia tri a[0] = 1 Gia tri a[1] = 2 Gia tri a[2] = 3 Gia tri a[3] = 4 Gia tri a[4] = 5
Lưu ý: Trong hầu hết trường hợp, tên biến mảng được chuyển đổi thành 1 con trỏ. Do đó, bạn có thể sử dụng con trỏ để truy cập các phần tử của mảng. Tuy nhiên, bạn cần lưu ý con trỏ và mảng không phải là một nhé. Có 1 số trường hợp ngoại lệ, bạn xem chi tiết tại: When does array name doesn’t decay into a pointer?
Ví dụ mối quan hệ giữa con trỏ và mảng
Bạn cũng có thể sử dụng một biến con trỏ khác để duyệt mảng, xem ví dụ bài tập tính tổng các phần tử trong mảng 1 chiều sử dụng con trỏ dưới đây:
// Ví dụ mối quan hệ giữa con trỏ và mảng - Lập Trình Không Khó #include <stdio.h> #define MAX_SIZE 100 int main() { int arr[MAX_SIZE]; int n; do { printf("Nhap so luong phan tu: "); scanf("%d", &n); } while (n < 1); int *p = &arr[0]; // Nhập mảng for (int i = 0; i < n; i++) { printf("Nhap a[%d] = ", i); scanf("%d", (p + i)); } // Xuất mảng for (int i = 0; i < n; i++) { printf("nGia tri a[%d] = %d", i, *(p + i)); } // Tính tổng sử dụng biến lặp là con trỏ // Khởi tạo con trỏ i trỏ tới phần tử đầu tiên của mảng // Ta sẽ dừng nếu giá trị của i vượt quá địa chỉ của phần tử cuối cùng // i++: tăng i lên 1 đơn vị, tức là cho nó trỏ tới phần tử tiếp theo int sum = 0; for(int *i = &arr[0]; i <= &arr[n-1]; i++){ // Lấy giá trị của phần tử hiện tại bằng toán tử `*` sum += *i; } printf("nSum = %d", sum); }
Kết quả chạy chương trình:
Nhap so luong phan tu: 5 Nhap a[0] = 1 Nhap a[1] = 2 Nhap a[2] = 3 Nhap a[3] = 4 Nhap a[4] = 5 Gia tri a[0] = 1 Gia tri a[1] = 2 Gia tri a[2] = 3 Gia tri a[3] = 4 Gia tri a[4] = 5 Sum = 15
Bài viết này mình đã cùng bạn đi làm rõ mối liên hệ giữa con trỏ và mảng trong C. Giờ đây bạn có thêm 1 cách để duyệt mảng cực ngầu. Hơn hết, bạn hiểu nhiều hơn về mối quan hệ giữa con trỏ và mảng, ứng dụng của con trỏ trong mảng. Ở các bài sau chúng ta sẽ tiếp tục khám phá về con trỏ nhé.
Để lại một bình luận