Chương 10: Con trỏ

Programming in C

 ? 2003 Kishori
Mundargi 

 

CHƯƠNG 10:

Con trỏ

Một con trỏ là biến trong C , nó trỏ đến một vùng nhớ. Con trỏ không trực tiếp lưu
trữ giá trị như int hay float. Chúng ta có thể truy cập
gián tiếp một giá trị thông qua một biến con trỏ.

Một con trỏ được khai báo như sau :

      int *ptr1;
      int i;
      ptr1 = &i;

Ở đây bạn phải hiểu được vai trò của các ký tự ‘*’ và ‘&’ ở
phí trước các biến.
Kí tự ‘*’ ở phía trước
biến được sử dụng để khai báo một biến con trỏ.
Kí tự ‘*’ cũng được dùng phía trước một biến con trỏ để truy cập và lấy về
giá trị được lưu bởi biến con trỏ chứ không phải địa chỉ của nó
(i.e. địa chỉ được gọi thông qua tên của nó không kèm theo ‘*’).
Ngược lại, kí tự ‘&’ được
sử dụng để truy cập tới địa trỉ của một biến nguyên được trỏ tới bởi con trỏ.
Trong ví dụ trên,
bạn khai báo một con trỏ nguyên “ptr1” là một biến nguyên
“i”. Sau đó bạn tạo một con trỏ ptr1 trỏ tới địa chỉ của
biến i. Lưu ý tới ví dụ phía trên để
hiểu khái niệm này.

Chương trình 10.1

#include

main()
{
  int *ptr1;
  int i;
  ptr1 = &i;
  i = 3;
  printf("The value of i is %d\n", i);
  printf("The pointer ptr1 contains the value %d\n",*ptr1);
}

Chương trình phía trên cho kết quả sau

      The value of i is 3
      The pointer ptr1 contains the value 3

Kết quả trên minh họa cho việc con trỏ ptr1 trỏ đến
địa chỉ của biến i. Như vậy ‘&’ được coi như là toán tử
địa chỉ của

Các hàm sử dụng con trỏ

Bạn đã biết là một hàm chỉ có thể trả về một giá trị.
Nếu bạn
muốn một hàm có thể thay đổi cho nhiều hơn một biến, bạn có thể truyền
các con trỏ cho hàm để làm điều đó. Hàm này sẽ không
thay đổi các con trỏ nhưng có thể thay đổi nội dung của các con trỏ này trỏ tới

Theo dõi ví dụ sau để thấy việc truyền 2
con trỏ nguyên iptr1 và iptr2 cho hàm có tên là swap.

Chương trình 10.2

#include

void swap(int *iptr1, int *iptr2)
{
  int temp;
  temp = *iptr2;
  *iptr2 = *iptr1;
  *iptr1 = temp;
}
main()
{
  void swap(int *iptr1, int *iptr2);
  int x = 10;
  int y = 20;
  swap(&x,&y);
  printf("Value of iptr1 is %d", *iptr1);
  printf("Value of iptr2 is %d", *iptr2);
}

Note: Bất cứ khi nào bạn gọi một
hàm mà nó nhận một con trỏ làm tham số, bạn phải truyền
địa chỉ của một biến cho nó.

Hàm sẵn có scanf() nhận

tham số thứ 2 là một con trỏ tới một biến. Xem câu lệnh sau

    scanf(“%f”,&Total);

Vừa rồi bạn đã truyền địa chỉ của biến
Total, nếu bạn truyền biến Total, thì
một lỗi dạng (core dump) sẽ xuất hiện khi thực thi chương trình. Về bản chất
đây là một lỗi bộ nhớ trong một hệ thống UNIX.

  scanf(“%f”,Total); // Results in core dump error !

Con trỏ và mảng

Trong C, các con trỏ và mảng có mối liên hệ khá gần gũi. Các con trỏ rất
hữu ích trong việc thao tác với mảng. Việc khai báo một con trỏ tới
một mảng thì giống như với một con trỏ nguyên hay con trỏ số thực.

 

      int *ptr;
      int arr[5];
      ptr = &arr[0];

Như bạn thấy ở trên, con trỏ chứa địa chỉ của
phần tử đầu tiên của mảng.
C cung cấp một cách khác để trỏ tới
phần tử đầu tiên của mảng, ptr = arr;

Ghi chú: Cần nhớ rằng
tên của một con mảng mà không có kèm chỉ số thì nó là một con trỏ trỏ tới
phần tử đầu tiên của mảng.

Khi một con trỏ được thiết lập trỏ tới một phần tử của một mảng
thì có thể dùng toán tử tăng ++ và giảm
để trỏ tới các phần tử liền sau hoặc liền trước tương ứng trong mảng,
. Nhưng việc tăng giảm con trỏ để trỏ
vượt quá cỡ của mảng sẽ sinh một lỗi runtime, và chương trình của bạn
sẽ có thể treo (crash) hoặc ghi đè lên các dữ liệu khác hoặc đoạn mã khác của chương trình
của bạn. Vì thế lập trình viên phải đảm bảo mình sử dụng con trỏ
tới một mảng một cách thành thạo.

Một nhân tố khác phải cân nhắc đó là kích thước của dữ liệu
mà con trỏ trỏ tới. Giả sử rằng một con trỏ nguyên trỏ tới
một mảng nguyên; khi bạn tịnh tiến con trỏ lên, con trỏ sẽ
trỏ tới phần tử tiếp theo trong mảng. Nhưng thực tế, con trỏ đó
sẽ lưu giá trị mà thường là sẽ lớn hơn 4 byte
so với địa chỉ phần tử đầu tiên của mảng.

Quy tắc biến đổi mảng

Nếu arr1 là một mảng, thì
biểu thức arr1 + 1 sẽ cho địa chỉ của phần tử thứ 2 trong mảng
bất kể kiểu dữ liệu của arr1.
Bây giờ chúng ta có thể sử dụng toán tử gián tiếp *
đứng phía trước tên biến
để lấy giá trị được chứa trong địa chỉ này. Như vậy

      *(arr1 + 1)

sẽ cho giá trị được lưu trong phần tử thứ 2 của mảng.
Cặp ngoặc đơn là bắt buộc bởi vì toán tử * có
độ ưu tiên cao hơn toán tử cộng. Vì thế chúng ta có thể sử dụng
quy tắc biến đổi này để chuyển đổi bất cứ mảng nào được tham chiếu tới
bằng các biểu thức với con trỏ tương đương.

      arr1[0] tương đương với *(arr1 + 0)
      arr1[1] tương đương với *(arr1 + 1)
      arr1[2] tương đương với *(arr1 + 2)

Nếu không có cặp ngoặc đơn thì phép toán
*arr1 + 1 sẽ cho một kết quả khác hoàn toàn, giống như lấy giá trị
được lưu
bởi phần tử đầu tiên của mảng và cộng thêm 1.

Cấp phát bộ nhớ và con trỏ

Định nghĩa mảng cho ta biết một khối nhớ được dự trữ riêng
bởi hệ điều hành trong quá trình bắt đầu thực thi chương trình,
điều này không xảy ra nếu mảng được đại diện bởi một biến con trỏ
Việc sử dụng một biến con trỏ yêu cầu
lập trình viên phải thực hiện một số kiểu cấp phát bộ nhớ trước khi các phần tử mảng
được sử dụng.
Phương thức này gọi là Cấp phát bộ nhớ động .
Hàm malloc() trong thư viện hàm C chuẩn

được sử dụng cho mục đích này.

Chương trình 10.3

#include

main()
{
  int *x;
  float *y;


  x =(int *) malloc(10 * sizeof(int));
  /* Cap phat bo nho cho 10 phan tu nguyen */


  y = (double *) malloc(10 * sizeof(double));
  /* cap phat bo nho cho 10 so thuc cham phay dong*/
}

Ghi chú: hàm malloc()
cần phải có kiểu dữ liệu giống với kiểu của biến con trỏ.

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

Có một số khác biệt chủ yếu giữa mảng
và con trỏ.
Một giá trị của con trỏ có thể bị thay đổi khi nó trỏ tới vùng nhớ khác.
Nhưng con trỏ được đại diện bởi tên một mảng thì không thể
bị thay đổi. Nó được xem như một hằng.

      float TotAmt[10];
  
      TotAmt  ++; // cau lenh khong hop le
      TotAmt -= 1; // cau lenh hop le

Một điểm khác cần nhớ đó là tên của mảng
được khởi tạo bằng cách trỏ tới phần tử đầu tiên của mảng
trong khi con trỏ không được khởi tạo khi khai báo.
Chúng phải được khởi tạo tường minh trước khi sử dụng nếu không
một lỗi run-time sẽ xuất hiện.

Kiểm tra ngắn

Câu hỏi luyện tập

Bài tập

Lời giải bài tập

[Ý kiến đóng góp của bạn rất quan trọng với chúng tôi. Nếu có nhận xét, góp ý
hay thắc mắc
liên quan tới chương này, xin vui lòng gửi email tới
[email protected]
.]