Bài 13.818 phút đọc
const với con trỏ
Sau bài này bạn sẽ làm được
- Phân biệt bốn tổ hợp const với con trỏ
- Đọc khai báo const bằng quy tắc từ phải sang trái
- Chọn đúng dạng const cho tham số hàm
- Hiểu vì sao const không phải bảo đảm tuyệt đối
Từ khóa const với con trỏ gây nhầm lẫn vì có hai thứ có thể là hằng: bản thân con trỏ, và dữ liệu nó trỏ tới. Bài cuối của Phần 5 dạy bạn phân biệt chúng bằng một quy tắc đọc duy nhất.
#Bốn tổ hợp
int *a; /* 1. con trỏ ghi được, dữ liệu ghi được */
const int *b; /* 2. con trỏ ghi được, dữ liệu CHỈ ĐỌC */
int const *c; /* 2. giống hệt b */
int *const d; /* 3. con trỏ CHỈ ĐỌC, dữ liệu ghi được */
const int *const e; /* 4. cả hai đều chỉ đọc */| Khai báo | Đổi p được | Đổi *p được | Tên gọi thông dụng |
|---|---|---|---|
| int *p | Có | Có | Con trỏ thường |
| const int *p | Có | Không | Con trỏ tới hằng |
| int *const p | Không | Có | Con trỏ hằng |
| const int *const p | Không | Không | Con trỏ hằng tới hằng |
bon-to-hop.c
int x = 1, y = 2;
/* 1 */
int *a = &x;
*a = 10; /* hợp lệ */
a = &y; /* hợp lệ */
/* 2 */
const int *b = &x;
*b = 10; /* LỖI: assignment of read-only location */
b = &y; /* hợp lệ */
/* 3 */
int *const c = &x;
*c = 10; /* hợp lệ */
c = &y; /* LỖI: assignment of read-only variable */
/* 4 */
const int *const d = &x;
*d = 10; /* LỖI */
d = &y; /* LỖI */#Quy tắc đọc từ phải sang trái
Quy tắc
Đọc khai báo từ phải sang trái, bắt đầu từ tên biến. Từ khóa
const áp lên thứ đứng ngay bên trái nó, trừ khi nó đứng ở đầu thì áp lên thứ bên phải.const int *p;
/* Đọc: p là con trỏ tới int hằng
const đứng đầu nên áp lên int bên phải */
int const *p;
/* Đọc: p là con trỏ tới int hằng
const áp lên int bên trái. Cùng nghĩa với dòng trên. */
int *const p;
/* Đọc: p là con trỏ hằng tới int
const áp lên dấu sao bên trái, tức lên chính con trỏ */
const int *const p;
/* Đọc: p là con trỏ hằng tới int hằng */Với nhiều tầng con trỏ
| Khai báo | Đọc là |
|---|---|
| const char **p | p là con trỏ tới con trỏ tới char hằng |
| char *const *p | p là con trỏ tới con trỏ hằng tới char |
| char **const p | p là con trỏ hằng tới con trỏ tới char |
| const char *const *const p | cả ba tầng đều hằng |
/* Ví dụ thực tế: hàm so sánh cho qsort trên mảng chuỗi */
static int so_sanh(const void *x, const void *y)
{
const char *const *a = x; /* con trỏ tới con trỏ hằng tới char hằng */
return strcmp(*a, *(const char *const *)y);
}#Chọn đúng dạng cho tham số hàm
| Hàm làm gì | Nên khai báo | Ví dụ trong thư viện chuẩn |
|---|---|---|
| Chỉ đọc dữ liệu | const T *p | size_t strlen(const char *s) |
| Ghi vào dữ liệu | T *p | char *strcpy(char *d, const char *s) |
| Đổi con trỏ của người gọi | T **pp | long strtol(const char *, char **het, int) |
| Chỉ đọc mảng con trỏ | const T *const *p | hiếm gặp, nhưng đúng nhất về mặt ngữ nghĩa |
Thiếu const
size_t do_dai(char *s)
{
size_t n = 0;
while (s[n] != '\0') ++n;
return n;
}
do_dai("abc"); /* biên dịch qua nhưng ngữ nghĩa sai, và có cảnh báo
nếu bật -Wwrite-strings */Có const
size_t do_dai(const char *s)
{
size_t n = 0;
while (s[n] != '\0') ++n;
return n;
}
do_dai("abc"); /* đúng hoàn toàn */
char bo_dem[16] = "abc";
do_dai(bo_dem); /* cũng đúng: T sao chuyển được sang const T sao */#Quy tắc chuyển đổi
int x = 1;
int *p = &x;
const int *q;
q = p; /* HỢP LỆ: thêm const luôn được */
p = q; /* LỖI: bỏ const cần ép kiểu rõ ràng */
p = (int *)q; /* biên dịch qua, nhưng bạn đang tự chịu trách nhiệm */| Chuyển từ | Sang | Được không |
|---|---|---|
| int * | const int * | Được, tự động |
| const int * | int * | Không, cần ép kiểu |
| int ** | const int ** | KHÔNG, dù trông có vẻ hợp lý |
| int ** | const int *const * | Cũng KHÔNG trong C, dù an toàn. Chỉ C++ mới cho |
Khi nào ép bỏ const là chấp nhận được
/* Trường hợp hợp lệ: hàm thư viện chuẩn nhận const nhưng trả về không const. */
char *strchr(const char *s, int c);
const char *s = "abc";
char *p = strchr(s, 'b'); /* p ghi được dù s là const */
*p = 'X'; /* Nếu s thật sự trỏ vào hằng chuỗi thì đây là lỗi. */#const không phải bảo đảm tuyệt đối
gioi-han.c
#include <stdio.h>
int main(void)
{
int x = 42;
const int *p = &x;
printf("%d\n", *p); /* 42 */
x = 100; /* hợp lệ: x không phải const, chỉ p là const */
printf("%d\n", *p); /* 100, giá trị đã đổi qua đường khác */
return 0;
}| Tình huống | Ghi qua con trỏ đã ép bỏ const |
|---|---|
| Đối tượng gốc không phải const | Hợp lệ, dù xấu về mặt phong cách |
| Đối tượng gốc là const | Hành vi không xác định |
| Hằng chuỗi | Hành vi không xác định, thường làm chương trình sập |
Bảng tổng kết Phần 5
| Khái niệm | Ký hiệu | Nhớ gì |
|---|---|---|
| Lấy địa chỉ | &x | Cho ra T sao nếu x có kiểu T |
| Giải tham chiếu | *p | Cùng vùng nhớ với biến gốc |
| Con trỏ tới con trỏ | int ** | Dùng khi cần sửa con trỏ của người gọi |
| Số học con trỏ | p + 1 | Nhảy theo phần tử, không theo byte |
| Hiệu hai con trỏ | p - q | Cho ra ptrdiff_t, in bằng %td |
| Phân rã mảng | a thành &a[0] | Trừ ba chỗ: sizeof, dấu và, khởi tạo chuỗi |
| Con trỏ hàm | int (*f)(int) | Ngoặc bắt buộc, dùng typedef |
| Con trỏ void | void * | Không giải tham chiếu, không số học |
| const trái sao | const int *p | Dữ liệu chỉ đọc |
| const phải sao | int *const p | Con trỏ chỉ đọc |
Tự làm thử
- Viết cả bốn tổ hợp
const, thử gán vào con trỏ và vào dữ liệu, ghi lại bốn thông báo lỗi. - Đọc bảy khai báo
constnhiều tầng trong bài bằng quy tắc từ phải sang trái. - Viết
typedef int *ConTroIntrồi thêmconst, chứng minh nó tạo ra con trỏ hằng chứ không phải con trỏ tới hằng. - Thử gán
int **choconst int **, đọc cảnh báo, rồi sửa thànhconst int *const *. - Tái hiện ví dụ ghi vào biến
constqua con trỏ đã ép kiểu, chạy với-O0và-O2rồi so kết quả. - Rà một chương trình cũ của bạn, thêm
constvào mọi tham số con trỏ chỉ đọc và xác nhận vẫn biên dịch được. - Viết hàm nhận
const char *và trả vềconst char *, so với cáchstrchrlàm và giải thích khác biệt.
Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.
Tóm tắt
constbên trái dấu sao bảo vệ dữ liệu, bên phải dấu sao bảo vệ con trỏ.- Dùng
const T *cho mọi tham số con trỏ mà hàm chỉ đọc. Đó vừa là tài liệu vừa là thứ trình biên dịch kiểm tra. - Thêm
constluôn được, bỏconstcần ép kiểu rõ ràng.int **không chuyển sangconst int **được. constnói về quyền truy cập qua một con trỏ cụ thể, không nói rằng đối tượng bất biến.- Ghi vào một đối tượng thật sự khai báo
constlà hành vi không xác định, và trình biên dịch tối ưu sẽ cho ra kết quả bất ngờ.