Menu
CoddyTech

Con trỏ và giá trị

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 9 trên 61.

Khi truyền một struct vào hàm (function), C cho bạn hai lựa chọn: truyền theo giá trị (sao chép toàn bộ struct) hoặc truyền theo con trỏ (chỉ sao chép địa chỉ). Lựa chọn này ảnh hưởng đáng kể đến hiệu năng.

Truyền theo giá trị tạo một bản sao hoàn chỉnh của struct trên ngăn xếp. Với struct nhỏ chỉ có vài số nguyên thì cách này ổn. Nhưng hãy tưởng tượng một struct có kích thước hàng trăm byte: mỗi lần gọi hàm, chương trình sao chép toàn bộ dữ liệu đó:

typedef struct {
    char name[100];
    int scores[50];
    double average;
} Student;

// Truyền theo giá trị: sao chép khoảng 308 byte mỗi lần gọi
void print_by_value(Student s) {
    printf("%s: %.2f\n", s.name, s.average);
}

// Truyền qua con trỏ: chỉ sao chép 8 byte (địa chỉ)
void print_by_pointer(const Student *s) {
    printf("%s: %.2f\n", s->name, s->average);
}

Cú pháp cũng khác nhau. Với tham số là giá trị, bạn dùng toán tử dấu chấm (s.name). Với con trỏ, bạn dùng toán tử mũi tên (s->name).

Phiên bản dùng con trỏ hiệu quả hơn vì chỉ truyền một địa chỉ bộ nhớ, thường là 8 byte bất kể kích thước của struct.

Đây là lý do các lập trình viên C gần như luôn truyền cấu trúc bằng con trỏ. Dùng const khi hàm không nên sửa đổi dữ liệu, và dùng con trỏ thông thường khi cần sửa đổi. Cách này vừa hiệu quả vừa thể hiện rõ ý định.

challenge icon

Thử thách

Dễ

Hãy tạo một module Product để minh họa sự khác biệt giữa việc truyền struct theo giá trị và truyền theo con trỏ. Bạn sẽ tạo hai hàm hiển thị: một hàm nhận toàn bộ struct dưới dạng bản sao, còn hàm kia chỉ nhận con trỏ đến struct đó.

Bạn sẽ tạo ba tệp:

  • product.h: Khai báo một struct Product có ba thành viên: char name[50], double price và int quantity. Đồng thời khai báo hai hàm:
    • display_by_value: nhận một Product theo giá trị và in thông tin của nó
    • display_by_pointer: nhận một const Product * và in cùng thông tin đó
    Dùng include guard với ký hiệu PRODUCT_H.
  • product.c: Triển khai cả hai hàm. Lưu ý sự khác biệt về cú pháp: phiên bản truyền theo giá trị dùng toán tử dấu chấm (p.name), còn phiên bản truyền con trỏ dùng toán tử mũi tên (p->name). Cả hai hàm cần in theo cùng một định dạng.
  • main.c: Tạo một Product, sau đó gọi cả hai hàm hiển thị để cho thấy chúng tạo ra đầu ra giống hệt nhau dù dùng các kiểu tham số khác nhau.

Bạn sẽ nhận ba đầu vào: tên sản phẩm (một chuỗi), giá (một số thực) và số lượng (một số nguyên).

Trong tệp main, khởi tạo một Product với các giá trị này, sau đó gọi display_by_value rồi gọi display_by_pointer.

Cả hai hàm cần in theo định dạng này:

Product: {name}, Price: {price}, Qty: {quantity}

Dùng %.2f cho giá để hiển thị hai chữ số thập phân.

Ví dụ, với các đầu vào Laptop, 999.99 và 5, đầu ra sẽ là:

Product: Laptop, Price: 999.99, Qty: 5
Product: Laptop, Price: 999.99, Qty: 5

Tự làm thử

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

int main() {
    char name[50];
    double price;
    int quantity;
    
    // Đọc dữ liệu đầu vào
    scanf("%s", name);
    scanf("%lf", &price);
    scanf("%d", &quantity);
    
    // TODO: Tạo biến Product và khởi tạo bằng các giá trị đầu vào
    // Gợi ý: Dùng strcpy để sao chép tên vào trường name của struct
    
    // TODO: Gọi display_by_value với Product
    
    // TODO: Gọi display_by_pointer với con trỏ trỏ đến Product
    
    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