Menu
CoddyTech

Quy tắc thành viên đầu tiên

Thuộc phần Lập trình hướng đối tượng trong hành trình C của Coddy. Bài học 29 trên 61.

Khi bạn nhúng một struct làm thành viên đầu tiên của một struct khác, một điều rất hữu ích sẽ xảy ra: địa chỉ bộ nhớ của struct bên ngoài trùng với địa chỉ bộ nhớ của thành viên đầu tiên. Tiêu chuẩn C đảm bảo điều này.

Hãy xem ví dụ trước:

typedef struct {
    int id;
} Parent;

typedef struct {
    Parent parent;  // Thành viên đầu tiên
    int grade;
} Child;

Khi tạo một biến Child, thành viên Parent nằm ngay ở đầu vùng nhớ của Child. Điều này có nghĩa là con trỏ đến Child trỏ đến đúng cùng một địa chỉ với con trỏ đến thành viên Parent.

Child c;
Child* child_ptr = &c;
Parent* parent_ptr = (Parent*)child_ptr;  // Ép kiểu an toàn!

Cả hai con trỏ đều giữ cùng một địa chỉ. Nhờ đó, bạn có thể truyền một Child* cho bất kỳ hàm nào nhận Parent*:

void print_id(Parent* p) {
    printf("ID: %d\n", p->id);
}

// Trong main:
Child c;
c.parent.id = 42;
print_id((Parent*)&c);  // Hoạt động hoàn hảo

Kỹ thuật này là nền tảng của tính kế thừa trong C. Một hàm được viết cho kiểu cơ sở có thể hoạt động với mọi kiểu dẫn xuất, miễn là kiểu cơ sở được nhúng trước tiên. Phép ép kiểu này an toàn vì bố cục bộ nhớ hoàn toàn khớp nhau.

challenge icon

Thử thách

Dễ

Hãy khám phá quy tắc thành viên đầu tiên bằng cách xây dựng một hệ phân cấp động vật đơn giản. Bạn sẽ tạo một cấu trúc Animal cơ sở và một cấu trúc Dog dẫn xuất, sau đó minh họa cách ép kiểu an toàn con trỏ đến Dog thành con trỏ đến Animal.

Điểm mấu chốt bạn sẽ áp dụng: khi Animal là thành viên đầu tiên của Dog, cả hai cấu trúc đều có cùng địa chỉ bộ nhớ bắt đầu. Điều này có nghĩa là bạn có thể truyền Dog* cho bất kỳ hàm nào nhận Animal* bằng cách ép kiểu con trỏ.

Bạn sẽ sắp xếp code trong ba tệp:

  • animal.h: Định nghĩa cả hai cấu trúc với các include guard. Cấu trúc Animal cần có một trường duy nhất: legs (một số nguyên biểu thị số chân). Cấu trúc Dog cần nhúng Animal làm thành viên đầu tiên và thêm trường name (một mảng ký tự gồm 50 ký tự). Đồng thời khai báo hàm print_animal nhận tham số Animal*.
  • animal.c: Triển khai hàm print_animal. Hàm này làm việc với kiểu cơ sở. Hàm chỉ biết về Animal và in ra số chân của con vật.
  • main.c: Tạo một biến Dog và điền các trường của nó (cả số chân của con vật được nhúng lẫn tên của chó). Sau đó, minh họa quy tắc thành viên đầu tiên bằng cách ép kiểu Dog* thành Animal* rồi truyền con trỏ đó cho print_animal. Ngoài ra, hãy in riêng tên của chó để thấy đầy đủ kết quả.

Bạn sẽ nhận hai đầu vào: tên của chó (một chuỗi) và số chân (một số nguyên).

Đầu ra của bạn sẽ có dạng như sau:

Dog: Buddy
Legs: 4

Trong đó, Buddy là tên của chó và 4 là số chân. Trước tiên, hãy in tên của chó từ main.c, sau đó gọi print_animal với con trỏ đã ép kiểu để hiển thị số chân.

Điều này minh họa cách một hàm được viết cho kiểu cơ sở (Animal) có thể làm việc liền mạch với kiểu dẫn xuất (Dog) thông qua việc ép kiểu con trỏ: nền tảng của tính kế thừa trong C.

Tự làm thử

#include <stdio.h>
#include <string.h>
#include "animal.h"

int main() {
    // Đọc đầu vào
    char name[50];
    int legs;
    scanf("%s", name);
    scanf("%d", &legs);
    
    // TODO: Tạo một biến Dog
    
    // TODO: Gán tên cho chó bằng strcpy
    
    // TODO: Gán số chân (truy cập qua Animal được nhúng)
    
    // TODO: In tên chó theo định dạng: "Dog: <name>"
    
    // TODO: Ép kiểu Dog* thành Animal* và gọi print_animal
    // Điều này minh họa quy tắc thành viên đầu tiên!
    
    return 0;
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch C online