Ví dụ kinh điển: hoán đổi
Thuộc phần Logic và luồng điều khiển trong hành trình C của Coddy. Bài học 28 trên 63.
hàm swap là một trong những ví dụ nổi tiếng nhất cho thấy vì sao con trỏ rất cần thiết trong lập trình C. Mục tiêu rất đơn giản: viết một hàm (function) để hoán đổi giá trị của hai biến. Tuy nhiên, nhiệm vụ tưởng chừng đơn giản này cho thấy một hạn chế cơ bản của cách truyền đối số theo giá trị.
Hãy xem điều gì xảy ra nếu bạn thử hoán đổi hai biến bằng cách truyền giá trị thông thường:
void badSwap(int a, int b) {
int temp = a;
a = b;
b = temp;
// Thao tác này chỉ hoán đổi các bản sao, không hoán đổi các biến (variables) ban đầu!
}Cách làm này không hiệu quả vì hàm chỉ nhận bản sao của các giá trị. Các biến ban đầu trong hàm gọi nó vẫn không thay đổi.
Lời giải yêu cầu truyền con trỏ đến các biến thay thế:
void swap(int *a, int *b) {
int temp = *a; // Lưu giá trị tại địa chỉ a
*a = *b; // Gán giá trị tại địa chỉ b cho địa chỉ a
*b = temp; // Gán giá trị đã lưu cho địa chỉ b
}Bằng cách truyền địa chỉ của các biến và dùng toán tử giải tham chiếu, hàm có thể trực tiếp thay đổi giá trị của các biến ban đầu. Điều này khiến hàm hoán đổi trở thành ví dụ hoàn hảo để minh họa khi nào và vì sao bạn cần con trỏ để thực hiện những việc mà cách truyền giá trị không thể xử lý.
Thử thách
DễViết chương trình C triển khai và minh họa hàm hoán đổi kinh điển bằng con trỏ. Chương trình của bạn cần:
- Tạo hàm có tên
swap, nhận hai con trỏ đến số nguyên làm tham số - Bên trong hàm
swap, hoán đổi các giá trị tại địa chỉ bộ nhớ mà hai con trỏ trỏ đến bằng cách sử dụng một biến tạm - Trong hàm
main, đọc hai số nguyên từ đầu vào của người dùng - In các giá trị ban đầu theo định dạng:
Before swap: a = [value], b = [value] - Gọi hàm
swapvà truyền địa chỉ của cả hai biến bằng toán tử lấy địa chỉ - Sau khi gọi hàm, in các giá trị đã hoán đổi theo định dạng:
After swap: a = [value], b = [value]
Đầu ra cần cho thấy hàm hoán đổi thành công các giá trị của những biến ban đầu:
Before swap: a = [first_input], b = [second_input]
After swap: a = [second_input], b = [first_input]Thử thách này cho thấy vì sao con trỏ rất cần thiết cho thao tác hoán đổi: hàm phải truy cập các vị trí bộ nhớ của những biến ban đầu để hoán đổi vĩnh viễn các giá trị của chúng, điều mà cách truyền giá trị không thể làm được.
Tự làm thử
#include <stdio.h>
// TODO: Viết hàm (function) hoán đổi tại đây
int main() {
int a, b;
// Đọc đầu vào
scanf("%d", &a);
scanf("%d", &b);
// In ra các giá trị ban đầu
printf("Before swap: a = %d, b = %d\n", a, b);
// TODO: Gọi hàm hoán đổi tại đây
// In ra các giá trị sau khi hoán đổi
printf("After swap: a = %d, b = %d\n", a, b);
return 0;
}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 Logic và luồng điều khiển
1Nền tảng về con trỏ
Con trỏ là gì?Khai báo con trỏToán tử lấy địa chỉ (&)Toán tử giải tham chiếu (*)Con trỏ NULLÔn tập: kiến thức cơ bản về con trỏ4Dự án: Công cụ xử lý văn bản đơn giản
Tổng quan dự ánĐếm ký tự2Con trỏ và mảng
Tên mảng dưới dạng con trỏPhần tử mảng và con trỏPhép toán con trỏSo sánh con trỏÔn tập: duyệt mảng bằng con trỏ5Con trỏ và hàm
Truyền theo giá trịTruyền con trỏ vào hàmSửa đổi biến qua con trỏVí dụ kinh điển: hoán đổiTruyền mảng vào hàmÔn tập: đối số con trỏ hàm3Mảng ký tự và chuỗi
Chuỗi dưới dạng mảng charKý tự kết thúc nullNhập chuỗi bằng scanfDùng strlen()Dùng strcpy()Dùng strcat()Dùng strcmp()Ôn tập: các hàm xử lý chuỗi cơ bản6Quản lý bộ nhớ
Bộ nhớ ngăn xếp và bộ nhớ heapCấp phát động với malloc()Dùng sizeof() để cấp phátKiểm tra lỗi cấp phátGiải phóng bộ nhớ với free()Cấp phát với calloc()Ôn tập: Mảng động9Dự án: Nhập thông tin liên hệ đơn giản
Định nghĩa struct ContactHàm tạo liên hệTự luyện tập: Trình biên dịch C online