Bài 15.216 phút đọc
typedef
Sau bài này bạn sẽ làm được
- Viết typedef cho struct đúng cách
- Phân biệt tên thẻ với tên typedef
- Khai báo struct tự tham chiếu
- Biết khi nào typedef làm mã khó đọc hơn
Từ khóa typedef đặt tên mới cho một kiểu đã có. Với struct, nó giúp bạn bỏ được chữ struct lặp đi lặp lại. Nhưng nó cũng che mất thông tin, và biết khi nào không nên dùng quan trọng không kém.
#Cú pháp và ý nghĩa
typedef
Không tạo ra kiểu mới. Nó chỉ tạo một tên khác cho kiểu đã có, và hai tên đó hoàn toàn tương đương với trình biên dịch.
typedef kiểu_cũ tên_mới;
typedef unsigned long ulong;
typedef int Diem;
typedef char Bo_dem[256]; /* mảng 256 ký tự */
typedef int (*PhepToan)(int, int); /* con trỏ hàm */#Bốn cách kết hợp với struct
bon-cach.c
/* Cách 1: khai báo struct rồi typedef riêng */
struct DiemGoc { int x, y; };
typedef struct DiemGoc Diem;
/* Cách 2: gộp lại, giữ cả tên thẻ. Đây là cách nên dùng. */
typedef struct Diem {
int x, y;
} Diem;
/* Cách 3: gộp lại, bỏ tên thẻ. Rất phổ biến. */
typedef struct {
int x, y;
} Diem;
/* Cách 4: đặt tên thẻ khác tên typedef. Gây nhầm, nên tránh. */
typedef struct diem_t {
int x, y;
} Diem;| Cách | Có tên thẻ | Tự tham chiếu được | Đánh giá |
|---|---|---|---|
| 1 | Có | Có | Dài dòng, ít dùng |
| 2 | Có | Có | Tốt nhất, nên dùng mặc định |
| 3 | Không | Không | Gọn, dùng khi chắc chắn không cần tự tham chiếu |
| 4 | Có, nhưng khác tên | Có | Tránh, vì hai tên cho một kiểu gây nhầm |
#Struct tự tham chiếu
Đây là chỗ cách 3 thất bại, và nó là nền tảng của mọi cấu trúc dữ liệu liên kết ở Phần 9.
Không có tên thẻ
typedef struct {
int gia_tri;
Nut *tiep; /* LỖI: tên Nut chưa tồn tại tại dòng này */
} Nut;Có tên thẻ
typedef struct Nut {
int gia_tri;
struct Nut *tiep; /* dùng tên thẻ, đã có ngay từ dòng đầu */
} Nut;
/* Sau khi khai báo xong, dùng tên nào cũng được */
Nut *dau = NULL;
struct Nut *cuoi = NULL;Khai báo trước cho hai struct tham chiếu lẫn nhau
lan-nhau.c
/* Khai báo trước: nói rằng kiểu này tồn tại, chưa cần biết nội dung */
typedef struct Nut Nut;
typedef struct Cay Cay;
struct Nut {
int gia_tri;
Cay *cay_chua; /* dùng được vì Cay đã được khai báo trước */
Nut *trai, *phai;
};
struct Cay {
Nut *goc;
size_t so_nut;
};#Khi nào typedef có ích
| Trường hợp | Vì sao có ích | Ví dụ |
|---|---|---|
| Struct dùng nhiều nơi | Bỏ được chữ struct lặp đi lặp lại | typedef struct Diem { ... } Diem; |
| Con trỏ hàm | Cú pháp trần quá khó đọc | typedef int (*SoSanh)(const void *, const void *); |
| Kiểu tùy nền tảng | Đổi một chỗ là đổi khắp nơi | typedef uint32_t MaNguoiDung; |
| Kiểu mờ trong thư viện | Giấu hoàn toàn chi tiết bên trong | typedef struct DanhSach DanhSach; |
| Kiểu bọc mỏng cho đơn vị đo | Trình biên dịch phân biệt được hai đơn vị | typedef struct { double v; } Met; |
kieu-tuy-nen-tang.c
/* Thư viện chuẩn dùng đúng kỹ thuật này */
typedef unsigned long size_t; /* trên LP64 */
typedef long ptrdiff_t;
typedef long time_t;
/* Bạn cũng nên làm vậy với kiểu của riêng mình */
typedef uint32_t MaSanPham;
typedef double TienTe;
/* Đổi TienTe sang long long để tránh sai số dấu phẩy động:
sửa đúng một dòng, không phải rà cả nghìn chỗ dùng. */#Khi nào nó làm mã khó đọc
Trường hợp 1: typedef che mất dấu sao
typedef gộp cả con trỏ
typedef struct DanhSach *DanhSachP;
void f(DanhSachP ds)
{
/* Người đọc không biết đây là con trỏ.
Có phải free không? Có sửa được dữ liệu của người gọi không? */
}
DanhSachP a, b; /* cả hai đều là con trỏ, khác với DanhSach *a, b; */Con trỏ hiện rõ ở chỗ dùng
typedef struct DanhSach DanhSach;
void f(DanhSach *ds)
{
/* Dấu sao nói rõ đây là con trỏ. */
}
DanhSach *a, *b; /* phải viết rõ, và đó là điều tốt */Trường hợp 2: typedef cho kiểu cơ bản đơn giản
Đặt tên cho thứ đã rõ ràng
typedef int INT;
typedef char CHAR;
typedef double DOUBLE;
typedef unsigned int UINT;
INT a;
UINT b;
/* Không giấu được gì, không đổi được gì, chỉ thêm một tầng phải tra cứu. */Dùng thẳng kiểu chuẩn
int a;
unsigned int b;
/* Hoặc dùng kiểu có kích thước rõ ràng khi cần */
#include <stdint.h>
int32_t c;
uint64_t d;Trường hợp 3: typedef trong file tiêu đề công khai
Bảng tổng kết
| Kiểu | Nên typedef | Vì sao |
|---|---|---|
| struct | Có | Bỏ được chữ struct, và giấu được chi tiết |
| Con trỏ hàm | Có | Cú pháp trần quá khó đọc |
| enum | Có | Cùng lý do với struct |
| union | Có | Cùng lý do |
| Con trỏ tới struct | Không | Che mất dấu sao và làm const sai trực giác |
| int, char, double | Không | Không giấu gì, chỉ thêm một tầng tra cứu |
| Kiểu tùy nền tảng | Có | Đổi một chỗ là đổi khắp nơi |
Tự làm thử
- Viết
typedefcho struct theo cả bốn cách trong bài, thử tự tham chiếu với từng cách. - Khai báo hai
typedefcùng làintnhưng khác tên, thử gán chéo và xác nhận trình biên dịch cho qua. - Bọc chúng trong struct và thử gán chéo lại, chép lại thông báo lỗi.
- Khai báo nút danh sách liên kết tự tham chiếu, xây một danh sách ba nút và duyệt nó.
- Viết hai struct tham chiếu lẫn nhau bằng khai báo trước, kiểm chứng nó biên dịch được.
- Tạo
typedefgộp cả con trỏ rồi thêmconst, xác nhận nó tạo ra con trỏ hằng chứ không phải con trỏ tới hằng. - Viết một file tiêu đề dùng kiểu mờ, cài đặt trong file
.cvà chứng minh file khác không truy cập trường được.
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
typedefkhông tạo kiểu mới, nó chỉ đặt tên khác cho kiểu đã có. Nên không có kiểm tra kiểu giữa hai tên.- Cách tốt nhất với struct là
typedef struct Ten { ... } Ten;, giữ cả tên thẻ lẫn tên typedef. - Struct tự tham chiếu bắt buộc phải có tên thẻ, vì tên typedef chưa tồn tại bên trong thân struct.
- Khai báo trước tạo ra kiểu không hoàn chỉnh, dùng được để giấu chi tiết bên trong thư viện.
- Đừng
typedefcon trỏ tới dữ liệu: nó che mất dấu sao và làmconstáp sai chỗ. Ngoại lệ duy nhất là con trỏ hàm.