Bỏ qua điều hướng, tới nội dung chính
Học C
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áchCó tên thẻTự tham chiếu đượcĐánh giá
1Dài dòng, ít dùng
2Tốt nhất, nên dùng mặc định
3KhôngKhôngGọn, dùng khi chắc chắn không cần tự tham chiếu
4Có, nhưng khác tênTrá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ợpVì sao có íchVí dụ
Struct dùng nhiều nơiBỏ được chữ struct lặp đi lặp lạitypedef struct Diem { ... } Diem;
Con trỏ hàmCú pháp trần quá khó đọctypedef int (*SoSanh)(const void *, const void *);
Kiểu tùy nền tảngĐổi một chỗ là đổi khắp nơitypedef uint32_t MaNguoiDung;
Kiểu mờ trong thư việnGiấu hoàn toàn chi tiết bên trongtypedef struct DanhSach DanhSach;
Kiểu bọc mỏng cho đơn vị đoTrì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ểuNên typedefVì sao
structBỏ được chữ struct, và giấu được chi tiết
Con trỏ hàmCú pháp trần quá khó đọc
enumCùng lý do với struct
unionCùng lý do
Con trỏ tới structKhôngChe mất dấu sao và làm const sai trực giác
int, char, doubleKhôngKhông giấu gì, chỉ thêm một tầng tra cứu
Kiểu tùy nền tảngĐổi một chỗ là đổi khắp nơi

Tự làm thử

  1. Viết typedef cho struct theo cả bốn cách trong bài, thử tự tham chiếu với từng cách.
  2. Khai báo hai typedef cùng là int nhưng khác tên, thử gán chéo và xác nhận trình biên dịch cho qua.
  3. Bọc chúng trong struct và thử gán chéo lại, chép lại thông báo lỗi.
  4. 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ó.
  5. 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.
  6. Tạo typedef gộp cả con trỏ rồi thêm const, xác nhận nó tạo ra con trỏ hằng chứ không phải con trỏ tới hằng.
  7. Viết một file tiêu đề dùng kiểu mờ, cài đặt trong file .c và 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

  • typedef khô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 typedef con trỏ tới dữ liệu: nó che mất dấu sao và làm const áp sai chỗ. Ngoại lệ duy nhất là con trỏ hàm.