Trước đó khi nói về các kiểu con trỏ type*
chúng ta đã không nhắc đến một vài yếu tố đặc biệt để dành sự tập trung cho khái niệm con trỏ. Bây giờ hãy nói thêm về các trường hợp đặc biệt.
NULL constant
Giá trị này được sử dụng để gán cho các biến con trỏ và biểu thị rằng biến con trỏ đang không trỏ tới ô nhớ nào cả. Giá trị hằng NULL
tương đương với giá trị số nguyên 0
được biểu thị bằng dãy ô nhớ 64-bit
đều có giá trị là 0
. Và hiển nhiên là như vậy thì NULL
cũng tương đương với giá trị logic false
trong cấu trúc lệnh if
.
# include <stdio.h> void main () { char* charRef = NULL; printf ("Pointer: %p \n", NULL); printf ("As int: %i \n", NULL);
}
Pointer: (nil)
As int: 0
void* type
Trước đó chúng ta có void
là tên định kiểu thay thế cho kiểu giá trị trả về của một sub-program
bất kỳ. Tên định kiểu này biểu thị rằng sub-program
đó sẽ không trả về giá trị nào cả. Tuy nhiên khi làm việc với kiểu con trỏ, chúng ta có void*
lại là một kiểu giá trị cụ thể, được gọi là kiểu địa chỉ và có độ rộng là 64-bit
tương đương với kiểu số thực có độ rộng kép double
.
# include <stdio.h>
# include <stdlib.h> void main () { fprintf (stdout, "Size of ref: %i \n", sizeof (void*));
}
Size of ref: 8
Có rất nhiều sub-program
trong thư viện tiêu chuẩn của C
sử dụng định kiểu void*
cho các vị trí cần tiếp nhận hoặc trả về một địa chỉ tham chiếu. Lý do là bởi vì sau đó chúng ta sẽ có thể chuyển đổi sang bất kỳ kiểu con trỏ nào khác để sử dụng, và như vậy tên định kiểu void
trong trường hợp này được sử dụng để đại diện cho khả năng xuất hiện của bất kỳ tên định kiểu nào khác như char
, int
, float
, v.v...
# include <stdio.h>
# include <stdlib.h> void main () { void* storage_address = malloc (sizeof (int)); fprintf (stdout, "Address: %p \n", storage_address); char* charRef = storage_address; int* intRef = storage_address; *charRef = 'a'; fprintf (stdout, "Character: %c \n", *charRef); fprintf (stdout, "Char_code: %i \n", *intRef); *intRef += 1; fprintf (stdout, "Character: %c \n", *charRef); fprintf (stdout, "Char_code: %i \n", *intRef); free (intRef)
}
Address: 0x5561417c12a0 Character: a
Char_code: 97
Character: b
Char_code: 98
Như vậy, rõ ràng chúng ta thấy rằng trong cú pháp định kiểu biến con trỏ, các tên định kiểu char
, int
, sẽ giúp cho trình biên dịch hiểu được rằng khi chúng ta thao tác qua một biến con trỏ là đang đọc/ghi trên một vùng bộ nhớ có độ rộng như thế nào; Thêm vào đó là chúng ta có thể biểu hiện logic xử lý khác nhau trên bề mặt code: Khi cần lưu trữ một ký tự đơn 'a'
, chúng ta có thể lưu mã ký tự là giá trị số nguyên 97
thông qua con trỏ kiểu int*
, hoặc lưu trực tiếp ký tự đó thông qua con trỏ kiểu char*
. Sau đó từ ký tự đang lưu trữ, chúng ta đã có thể tìm tới ký tự tiếp theo trong bảng mã ASCII
bằng cách tăng mã ký tự thông qua con trỏ kiểu int*
, hết sức linh hoạt.