Con trỏ trong Ngôn ngữ lập trình C – Phần 4 : Bài 13

Con trỏ trong Ngôn ngữ lập trình C – Phần 4 : Bài 13
access_time 10/10/2019 12:00:00 AM
person Nguyễn Mạnh Hùng

Con trỏ trong Ngôn ngữ lập trình C : Phần 4 - Bài 13

1. Con trỏ trỏ tới Con trỏ

Như đã thảo luận ở các phần trước, con trỏ cho phép trỏ đến các địa chỉ bộ nhớ có chứa dữ liệu. Bản thân một  biến con trỏ tự bản thân của nó đóng vai trò là một biến do vậy nó cũng sẽ có các thông tin như :

+ Địa chỉ bộ nhớ chứa biến con trỏ

+ Giá trị lưu trữ trên biến con trỏ đó là địa chỉ mà nó trỏ đến.

Do vậy C hoàn toàn cho phép sử dụng con trỏ để trỏ đến một con trỏ khác. Cái này lập trình viên thường gọi là con trỏ của con trỏ. Để thực hiện điều này thì thay vì sử dụng một dấu * thì C sử dụng ** để xác định biến đó là con trỏ với mục đích trỏ đến con trỏ khác.

Ví dụ :

int a=5;

int *p; // Con trỏ integer

int **q; // Con trỏ trỏ tới con trỏ integer

p=&a; // Khởi tạo giá trị cho con trỏ p; Con trỏ p trỏ đến địa chỉ a    

q=&p; // Con trỏ q trỏ đến địa chỉ của con trỏ

Có nghĩa:


Hình số 1: Con trỏ trỏ đến con trỏ

Code minh họa


Hình số 2: Sử dụng Con trỏ trỏ đến con trỏ khác

Mô tả địa chỉ gắn liền với biến a, con trỏ p và con trỏ q;


Hình số 3: Các mối quan hệ giữa biến a, p, q

Bảng mô tả


Bảng số 1: Mô tả quan hệ giữa Giá trị và địa chỉ a, p, q

Khi thay đổi giá trị với *p = 7 thì giá trị của a bị thay đổi, các giá trị khác giữ nguyên


Bảng số 2: Thay đổi giá trị của *p

Nếu thay đổi giá trị của **q = 10; vì q là con trỏ trỏ đến địa chỉ của p; p trỏ đến địa chỉ của a do vậy **q = 10 khi đó giá trị của a sẽ là 10 (a = 10) vì **q sẽ tham chiếu tới giá trị của a, các giá trị khác không đổi;


Bảng số 3: Thay đổi giá trị của **q

2. Mảng của con trỏ

Cũng giống như các mảng thông dụng khác, mảng con trỏ được sử dụng để lưu các biến con trỏ ở các vị trí liên tiếp trong bộ nhớ. Mỗi vị trí trong mảng sẽ chứa giá trị là một địa chỉ bộ nhớ.

Cú pháp khai báo mảng con trỏ :

<data type*> <variable name> [no. of elements ];

Trong đó:

data type : Kiểu dữ liệu mà của con trỏ

variable name: Tên biến của mảng con trỏ

no. of elements: Số các phần tử của mảng con trỏ

Ví dụ :

int arr[4] = {1,2,3,4}; // Khai báo mảng số nguyên bao gồm 4 phần tử

int* arr_ptr[4]; // Khai báo mảng con trỏ

int i;

for(i = 0; i<4; i++){

      arr_ptr[i] = arr + i;

}

Sử dụng vòng lặp for để thiết lập giá trị cho các phần tử trong mảng con trỏ điều đó có nghĩa là :

arr_ptr[0] = &arr[0];

arr_ptr[1] = &arr[1];

arr_ptr[2] = &arr[2];

arr_ptr[3] = &arr[3];

Bảng mô tả thông tin


Bảng số 4 : Mảng con trỏ

Code minh họa


Hình số 4 : Mảng con trỏ

Tương tự với mảng số nguyên, sử dụng mảng con trỏ để thao tác với mảng string (mảng xâu ký tự). Các lập trình viên có thể dễ dàng thực hiện.

Ví dụ

char *name[] = {“Manas”,“Pradip”,“Altaf”};

Khai báo mảng con trỏ char, tên mảng con trỏ là name, mỗi phần tử trong mảng sẽ là một con trỏ trỏ đến xâu ký tự.

Hình ảnh sau minh họa về vấn đề trên.


Hình số 5 : Mô tả mảng con trỏ kiểu char

Để thao tác với mảng con trỏ kiểu char, các bạn sử dụng cấu trúc lặp giống như thao tác với mảng int.

Ví dụ :


Hình số 6 : Thao tác với mảng con trỏ kiểu char

2. Con trỏ trỏ tới mảng

Phần kỹ thuật liên quan tới con trỏ trỏ đến mảng đã được trình bày chi tiết ở các phần trước.

Các kiến thức có liên quan bao gồm :

+ Địa chỉ cơ sở của mảng

+ Các toán tử tăng “++”, toán từ giảm “--” của con trỏ

+ Toán tử tham chiếu & để lấy địa chỉ của một phần tử trong mảng

+ Các toán tử so sánh của con trỏ

+ Tên mảng được sử dụng như con trỏ

+ Sự khác nhau giữa tên mảng và con trỏ

Ví dụ :

int v[5] = {1004, 2201, 3000, 432, 500};

int *p = v;

printf(“%d \n”, *p);

Khi đó con trỏ p sẽ trỏ đến địa chỉ cơ sở (địa chỉ của phần tử đầu tiên) của mảng v;

Hình ảnh minh họa con trỏ p với mảng v;


Hình số 7 : Con trỏ p trỏ đến mảng v;

Sử dụng các phép toán “++” và “--” để tăng và giảm địa chỉ bộ nhớ tương ứng với địa chỉ con trỏ p trỏ đến.

Ví dụ : p += 2; Khi đó con trỏ p sẽ trỏ đến địa chỉ của v[2];

p--; Con trỏ p sẽ trỏ đến v[1].

Hình ảnh minh họa.


Hình số 8: Con trỏ p và các toán tử “++” và “--”

Để lấy giá trị của các phần tử chúng ta sử dụng toán tử tham chiếu ngược *(p) để lấy giá trị tại địa chỉ mà con trỏ p trỏ đến.

Ví dụ p = &v[i] khi đó *p có giá trị là v[i]; với i là chỉ số của mảng v;

Hình ảnh minh họa


Hình số 9: Minh họa toán tử tham chiếu ngược

3. Con trỏ và mảng 2 chiều

Mảng 2 chiều là cấu trúc mở rộng của mảng một chiều. Trong mảng 2 chiều sẽ được tổ chức theo 2 bộ chỉ số hàng (row) và cột (colume). Hàng được gọi là chiều thứ nhất và cột  được gọi là chiều thứ 2.

Hình ảnh mô tả cấu trúc mảng 2 chiều.


Hình số 10: Cấu trúc mảng 2 chiều

Các chỉ số hàng và cột đều bắt đâu từ 0.

Cú pháp khai báo mảng 2 chiều
<data type> <array name>[rowsize][columnsize];

Trong đó:

data type : Kiểu dữ liệu của mảng 2 chiều

array name : Tên mảng

rowsize: Kích cỡ dòng

columnsize : Kích cỡ cột

Sử dụng cấu trúc lặp nồng nhau để duyệt mảng 2 chiều.

Ví dụ:

int a[4][3];

int i, j;

for(i = 0; i<4;i++){

    for(j = 0; j<3; j++){

          data[i][j] = -1;

    }

}

Ví dụ trên là thiết lập giá trị cho các phần tử của mảng 2 chiều.

Nếu xét về khía canh trọng bộ nhớ thì a sẽ được cấp phát là 4*3 = 12 ô nhớ liên tục, mỗi ô nhớ sẽ có kích cỡ là 2 bytes hoặc 4 bytes.

Nếu xét về kích cạnh logic thì cấu trúc ở đây là mảng một chiều mở rộng (Hay còn gọi là mảng của mảng).

Hình ảnh sau minh họa vấn đề này


Hình số 11: Logic cấu trúc mảng 2 chiều

Do đặc tính mảng 2 chiều xét theo logic là mảng một chiều mở rộng do vậy :

+ Tên mảng a chính là địa chỉ cơ sở của dòng đầu tiên hay a = a[0] == & a[0][0].

+ a + 1 == a[1] == &a[1][0];

+ a + i == a[i] == &a[i][0];

Nói cách khác a+1 cũng là tăng địa chỉ của a lên một thành phần, nhưng 1 thành phần ở đây được hiểu là toàn bộ một dòng của mảng 2 chiều.

Hình ảnh mô tả theo cấu trúc con trỏ mảng 2 chiều


Hình 12 : Con trỏ và mảng 2 chiều

Vậy bản chất khi các lập trình viên làm việc với mảng 2 chiều là làm việc với mảng một chiều mở rộng

Với mảng một chiều

+ &a[i] = a + i;

+ a[i] = *(a + i)

Với mảng 2 chiều chúng ta có

+ &a[i][j] = a[i]+ j = (a + i) [j] = *(a+i)+j

+ a[i][j] = *(a[i]+ j) = (*(a + i))[j] = *(*(a + i) + j)

Vậy nếu chúng ta xét cụ thể theo kía cạnh giá trị chúng ta có :

a[0][0] = (*(a))[0] = *((*a)+0) = *(a[0]+0)

a[0][2] = (*(a))[2] = *((*a)+2) = *(a[0]+2)

a[1][2] = (*(a+1))[2] = *((*(a+1))+2) = *(a[1]+2)

Nếu xét theo khía cạnh địa chỉ chúng ta có

&a[0][0] = (a)[0] = *a+0 = a[0]+0

&a[0][2] = (a)[2] = *a+2 = a[0]+2

&a[1][2] = (a+1)[2] = (*a+1)+2 = a[1]+2

Hình ảnh minh họa việc in thông tin của a[0][0] với thông qua nhiều biểu thức khác nhau


Hình số 13 : Hiển thị giá trị a[0][0] và địa chỉ &a[0][0]

Ví dụ sử dụng 2 vòng lặp for để hiển thị thông tin mảng 2 chiều a đã được khai báo ở trên.


Hình số 14 : Sử dụng vong lặp for lồng nhau để duyệt mảng 2 chiều

Chương trình sử dụng hàm show để hiển thị kết quả mảng 2 chiều với kích cỡ dòng là động, kích cỡ cột là cố định. Ở đây chúng ta sử dụng tên mảng như con trỏ.


vertical_align_top
share
Chat...