Bài 31.5: Bài tập thực hành con trỏ trong C++

Bài 31.5: Bài tập thực hành con trỏ trong C++
Chào mừng trở lại với series học C++ của FullhouseDev! Hôm nay, chúng ta sẽ cùng đào sâu vào một chủ đề cực kỳ quan trọng và đôi khi gây bối rối cho người mới bắt đầu: Con trỏ (Pointers).
Con trỏ là một trong những điểm mạnh làm nên sự linh hoạt và hiệu quả của C++, cho phép chúng ta làm việc trực tiếp với bộ nhớ. Tuy nhiên, sức mạnh này cũng đi kèm với trách nhiệm! Hiểu và sử dụng con trỏ đúng cách là kỹ năng cốt lõi trên con đường làm chủ C++.
Bài viết này tập trung vào các bài tập thực hành để củng cố kiến thức về con trỏ. Chúng ta sẽ ôn lại nhanh các khái niệm cơ bản và sau đó áp dụng chúng qua các ví dụ cụ thể.
Con trỏ là gì?
Nói một cách đơn giản, con trỏ là một biến đặc biệt. Thay vì lưu trữ một giá trị thông thường (như số nguyên, ký tự), nó lưu trữ địa chỉ bộ nhớ của một biến khác.
Hai toán tử "quyền năng": &
và *
Để làm việc với con trỏ, chúng ta cần hiểu rõ hai toán tử chính:
- Toán tử
&
(Address-of): Dùng để lấy địa chỉ bộ nhớ của một biến. Ví dụ:&myVariable
. - Toán tử
*
(Dereference hoặc Indirection): Dùng để truy cập (đọc hoặc ghi) giá trị tại địa chỉ mà con trỏ đang trỏ tới. Ví dụ:*myPointer
.
Hãy bắt đầu với một ví dụ cơ bản để thấy cách chúng hoạt động:
#include <iostream> // Thu vien cho nhap xuat du lieu
int main() {
int bien_a = 10; // Khai bao mot bien so nguyen
int* con_tro_a = &bien_a; // Khai bao con tro int* va gan cho no dia chi cua bien_a
cout << "Gia tri cua bien_a: " << bien_a << endl;
cout << "Dia chi cua bien_a (su dung &bien_a): " << &bien_a << endl;
cout << "Gia tri cua con_tro_a (luu tru dia chi): " << con_tro_a << endl; // con_tro_a luu dia chi cua bien_a
cout << "Gia tri tai dia chi ma con_tro_a tro toi (su dung *con_tro_a): " << *con_tro_a << endl; // *con_tro_a truy cap gia tri tai dia chi do
// Thay doi gia tri cua bien_a thong qua con tro
*con_tro_a = 15;
cout << "\nGia tri moi cua bien_a sau khi thay doi qua con tro: " << bien_a << endl; // bien_a bay gio la 15
cout << "Gia tri moi tai dia chi ma con_tro_a tro toi: " << *con_tro_a << endl; // van la 15
return 0;
}
Giải thích:
- Chúng ta khai báo một biến
bien_a
kiểuint
với giá trị 10. Biến này được cấp phát một ô nhớ trong RAM. int* con_tro_a = &bien_a;
làm hai việc:int* con_tro_a;
: Khai báocon_tro_a
là một biến con trỏ có khả năng lưu trữ địa chỉ của một biến kiểuint
.= &bien_a;
: Lấy địa chỉ bộ nhớ củabien_a
bằng toán tử&
và gán địa chỉ đó chocon_tro_a
.
cout << bien_a;
in ra giá trị trực tiếp củabien_a
.cout << &bien_a;
in ra địa chỉ bộ nhớ củabien_a
.cout << con_tro_a;
in ra giá trị màcon_tro_a
đang lưu trữ, chính là địa chỉ củabien_a
.cout << *con_tro_a;
sử dụng toán tử*
để truy cập vào ô nhớ tại địa chỉ màcon_tro_a
đang trỏ tới và in ra giá trị nằm ở đó (ban đầu là 10).*con_tro_a = 15;
sử dụng toán tử*
để thay đổi giá trị tại địa chỉ màcon_tro_a
trỏ tới. Vìcon_tro_a
trỏ tớibien_a
, dòng này thực chất làm thay đổi giá trị củabien_a
thành 15.
Bây giờ, hãy cùng thực hành qua một số bài tập điển hình!
Bài tập 1: Hoán đổi giá trị hai biến bằng con trỏ
Một trong những ứng dụng phổ biến của con trỏ là cho phép hàm thay đổi giá trị của các biến được truyền vào từ bên ngoài (gọi là truyền tham chiếu bằng con trỏ). Thông thường, khi bạn truyền biến vào hàm theo cách thông thường (truyền tham trị), hàm chỉ nhận bản sao, nên mọi thay đổi bên trong hàm không ảnh hưởng đến biến gốc.
Ví dụ, hàm hoán đổi thông thường (truyền tham trị) sẽ không hoạt động như mong đợi:
#include <iostream>
void swap_fail(int a, int b) {
int temp = a;
a = b;
b = temp;
// a va b da duoc hoan doi ben trong ham, nhung day chi la ban sao cua bien goc!
cout << "Ben trong ham (swap_fail): a = " << a << ", b = " << b << endl;
}
int main() {
int x = 5, y = 10;
cout << "Truoc khi goi ham swap_fail: x = " << x << ", y = " << y << endl; // Output: x = 5, y = 10
swap_fail(x, y);
cout << "Sau khi goi ham swap_fail: x = " << x << ", y = " << y << endl; // Output: x = 5, y = 10 (Khong thay doi!)
return 0;
}
Giải thích: Hàm swap_fail
nhận bản sao của x
và y
. Việc hoán đổi chỉ xảy ra trên các bản sao đó, biến gốc x
và y
không bị ảnh hưởng.
Bây giờ, hãy sử dụng con trỏ để thực hiện việc hoán đổi thành công:
#include <iostream>
void swap_success(int* ptr_a, int* ptr_b) {
// ptr_a luu dia chi cua bien thu nhat, ptr_b luu dia chi cua bien thu hai
// *ptr_a la gia tri tai dia chi ma ptr_a tro toi
int temp = *ptr_a; // Lay gia tri tai dia chi ptr_a tro toi
*ptr_a = *ptr_b; // Gan gia tri tai dia chi ptr_b tro toi vao dia chi ptr_a tro toi
*ptr_b = temp; // Gan gia tri ban dau (luu trong temp) vao dia chi ptr_b tro toi
// Gia tri tai cac dia chi da duoc thay doi => bien goc thay doi!
cout << "Ben trong ham (swap_success): *ptr_a = " << *ptr_a << ", *ptr_b = " << *ptr_b << endl;
}
int main() {
int x = 5, y = 10;
cout << "Truoc khi goi ham swap_success: x = " << x << ", y = " << y << endl; // Output: x = 5, y = 10
// Truyen dia chi cua x va y cho ham (su dung &)
swap_success(&x, &y);
cout << "Sau khi goi ham swap_success: x = " << x << ", y = " << y << endl; // Output: x = 10, y = 5 (Da thay doi!)
return 0;
}
Giải thích:
- Hàm
swap_success
nhận hai đối số là con trỏ tớiint
(int*
). - Khi gọi hàm, chúng ta truyền địa chỉ của
x
(&x
) và địa chỉ củay
(&y
). - Bên trong hàm,
ptr_a
chứa địa chỉ củax
, vàptr_b
chứa địa chỉ củay
. - Chúng ta sử dụng toán tử
*
để truy cập và thay đổi giá trị tại các địa chỉ đó. int temp = *ptr_a;
lấy giá trị củax
và lưu tạm.*ptr_a = *ptr_b;
gán giá trị củay
vào ô nhớ củax
.*ptr_b = temp;
gán giá trị ban đầu củax
(lưu trongtemp
) vào ô nhớ củay
.- Việc thay đổi
*ptr_a
và*ptr_b
thực chất là thay đổi giá trị củax
vày
trong hàmmain
.
Đây là một ví dụ điển hình về việc sử dụng con trỏ để cho phép hàm tương tác trực tiếp với dữ liệu bên ngoài phạm vi của nó.
Bài tập 2: Con trỏ và Mảng
Trong C++, tên của một mảng thường có thể được coi như một con trỏ trỏ tới phần tử đầu tiên của mảng đó. Mối quan hệ này rất chặt chẽ và là nền tảng cho nhiều thao tác với mảng.
Chúng ta có thể sử dụng con trỏ để truy cập các phần tử mảng thay vì chỉ số ([]
). Toán tử số học con trỏ (+
, -
, ++
, --
) rất hữu ích ở đây. Khi bạn cộng 1 vào một con trỏ, nó không tăng giá trị địa chỉ lên 1 byte, mà tăng lên bằng kích thước của kiểu dữ liệu mà nó trỏ tới. Ví dụ, con trỏ int*
khi cộng 1 sẽ nhảy tới địa chỉ của int
tiếp theo (thường là +4 hoặc +8 bytes tùy hệ thống).
#include <iostream>
int main() {
int mang_so[] = {10, 20, 30, 40, 50};
int kich_thuoc = sizeof(mang_so) / sizeof(mang_so[0]); // Tinh so luong phan tu
// mang_so tu dong chuyen thanh con tro tro den phan tu dau tien (&mang_so[0])
int* ptr_mang = mang_so; // Hoac viet day du: int* ptr_mang = &mang_so[0];
cout << "Truy cap phan tu mang bang con tro:" << endl;
// Duyet mang su dung con tro va phep cong con tro
for (int i = 0; i < kich_thuoc; ++i) {
// ptr_mang + i: dia chi cua phan tu thu i
// *(ptr_mang + i): gia tri tai dia chi cua phan tu thu i
cout << "Phan tu thu " << i << ": " << *(ptr_mang + i) << endl;
// Luu y: Cach viet *(ptr_mang + i) tuong duong hoan toan voi ptr_mang[i]
// cout << "Phan tu thu " << i << ": " << ptr_mang[i] << endl;
}
cout << "\nDuyet mang su dung con tro va phep tang con tro (++):" << endl;
// Reset con tro ve dau mang de duyet lai
ptr_mang = mang_so; // Hoac ptr_mang = &mang_so[0];
// Duyet mang su dung con tro va phep tang truoc
for (int i = 0; i < kich_thuoc; ++i) {
cout << "Phan tu thu " << i << ": " << *ptr_mang << endl;
ptr_mang++; // Con tro nhay sang dia chi cua phan tu tiep theo
}
return 0;
}
Giải thích:
int* ptr_mang = mang_so;
gán địa chỉ của phần tử đầu tiên củamang_so
choptr_mang
. Tên mảngmang_so
tự động "phân rã" (decay) thành con trỏint*
trỏ tớimang_so[0]
trong ngữ cảnh này.- Trong vòng lặp đầu tiên,
*(ptr_mang + i)
tính địa chỉ của phần tử thứi
(bắt đầu từ 0) bằng cách cộngi
lần kích thước của kiểuint
vào địa chỉ ban đầu củaptr_mang
, sau đó dùng*
để lấy giá trị tại địa chỉ đó. Điều này tương đương hoàn toàn với việc truy cậpmang_so[i]
. Thật vậy, toán tử[]
trong C++ thực chất được định nghĩa dựa trên phép toán con trỏ này! - Trong vòng lặp thứ hai, chúng ta in giá trị tại địa chỉ hiện tại mà
ptr_mang
trỏ tới (*ptr_mang
), sau đó dùngptr_mang++
để di chuyển con trỏ tới địa chỉ của phần tử kế tiếp. Do con trỏ biết kiểu dữ liệu mà nó trỏ tới (int
),ptr_mang++
sẽ tự động tăng địa chỉ lên đúng bằng kích thước của mộtint
. Đây là cách duyệt mảng rất phổ biến với con trỏ.
Hiểu mối liên hệ giữa con trỏ và mảng giúp bạn đọc và viết code C/C++ hiệu quả hơn, đặc biệt khi làm việc với các hàm xử lý mảng hoặc chuỗi kiểu C.
Bài tập 3: Con trỏ và Chuỗi kiểu C
Trong C++, chuỗi kiểu C (C-style string) đơn giản là một mảng các ký tự kết thúc bằng ký tự null (\0
). Con trỏ kiểu char*
thường được sử dụng để làm việc với các chuỗi này.
Một hằng chuỗi (string literal) như "Xin chao"
thực chất là một mảng ký tự tĩnh trong bộ nhớ, và bạn có thể gán địa chỉ của ký tự đầu tiên cho một con trỏ char*
.
#include <iostream> // Cho cout, endl
#include <cstring> // Thu vien chua cac ham xu ly chuoi kieu C (optional cho bai nay)
int main() {
// Khai bao chuoi kieu C (mang ky tu ket thuc bang '\0')
char chuoi_kieu_c[] = "Hello C++ Pointers!";
// Khai bao con tro tro den ky tu dau tien cua chuoi
char* ptr_chuoi = chuoi_kieu_c; // ptr_chuoi luu dia chi cua 'H'
cout << "In chuoi su dung con tro va vong lap:" << endl;
// Duyet qua chuoi bang con tro cho den khi gap ky tu null ('\0')
while (*ptr_chuoi != '\0') {
cout << *ptr_chuoi; // In ky tu hien tai ma con tro tro toi
ptr_chuoi++; // Di chuyen con tro toi ky tu tiep theo (tang dia chi len 1 byte vi kieu char)
}
cout << endl;
// Cach in khac: coi con tro char* la mot chuoi (iostream xu ly dac biet)
// Can reset con tro ve dau chuoi neu khong no da nam o cuoi sau vong lap
ptr_chuoi = chuoi_kieu_c;
cout << "In chuoi truc tiep bang con tro char*: " << ptr_chuoi << endl;
// Thay doi ky tu dau tien qua con tro
ptr_chuoi = chuoi_kieu_c; // Reset con tro ve dau mang
*ptr_chuoi = 'J'; // Thay 'H' bang 'J'
cout << "Chuoi sau khi thay doi ky tu dau tien: " << chuoi_kieu_c << endl; // Output: Jello C++ Pointers!
return 0;
}
Giải thích:
char chuoi_kieu_c[] = "Hello C++ Pointers!";
tạo ra một mảng ký tự trên stack, đủ lớn để chứa chuỗi và ký tự\0
kết thúc.char* ptr_chuoi = chuoi_kieu_c;
gán địa chỉ của ký tự đầu tiên ('H'
) choptr_chuoi
.- Vòng lặp
while (*ptr_chuoi != '\0')
tiếp tục chừng nào ký tự mà con trỏ trỏ tới chưa phải là null terminator. cout << *ptr_chuoi;
truy cập ký tự hiện tại và in nó.ptr_chuoi++;
di chuyển con trỏ tới ký tự kế tiếp trong mảng. Vìchar
có kích thước 1 byte,ptr_chuoi++
tăng địa chỉ lên 1 byte.- Stream operator
<<
trong iostream có xử lý đặc biệt chochar*
- nó giả định đó là một chuỗi kiểu C và in các ký tự bắt đầu từ địa chỉ đó cho đến khi gặp\0
. - Ví dụ cuối cùng cho thấy bạn có thể dùng
*ptr_chuoi = 'J';
để thay đổi ký tự đầu tiên của chuỗi (vìptr_chuoi
trỏ đến ký tự đầu tiên).
Đây là kỹ thuật cơ bản khi làm việc với các hàm xử lý chuỗi trong thư viện C chuẩn (<cstring>
hay <string.h>
).
Bài tập 4: Con trỏ Null (nullptr
)
Một con trỏ Null là một con trỏ không trỏ tới bất kỳ đối tượng hợp lệ nào. Nó thường được sử dụng để biểu thị rằng con trỏ hiện tại không liên kết với một địa chỉ bộ nhớ cụ thể nào cả, hoặc để khởi tạo một con trỏ khi bạn chưa biết nó sẽ trỏ tới đâu.
Trong C++ hiện đại (từ C++11 trở đi), chúng ta sử dụng từ khóa nullptr
để biểu diễn con trỏ null. Trước đây, người ta thường dùng NULL
(một macro thường được định nghĩa là 0) hoặc đơn giản là số 0
. Việc sử dụng nullptr
là tốt nhất vì nó giúp tránh nhầm lẫn kiểu dữ liệu (0 có thể là số nguyên, nullptr
chỉ là con trỏ null) và cải thiện tính an toàn kiểu dữ liệu.
Việc cố gắng truy cập (dereference) một con trỏ null là một lỗi nghiêm trọng và thường dẫn đến lỗi chương trình (segmentation fault hoặc access violation) vì bạn đang cố đọc/ghi vào một vùng nhớ không được phép.
#include <iostream>
#include <cstddef> // Chua dinh nghia nullptr (thong thuong da co trong cac header khac)
int main() {
int* ptr_du_lieu = nullptr; // Khai bao mot con tro null su dung nullptr
// Kiem tra xem con tro co null hay khong truoc khi su dung
if (ptr_du_lieu != nullptr) {
cout << "Con tro hop le." << endl;
//cout << "Gia tri: " << *ptr_du_lieu << endl; // KHONG LAM DIEU NAY!
} else {
cout << "Con tro la null." << endl; // Output: Con tro la null.
}
// Gan dia chi cua mot bien hop le cho con tro
int so = 42;
ptr_du_lieu = &so;
// Kiem tra lai
if (ptr_du_lieu != nullptr) {
cout << "Con tro bay gio hop le, gia tri: " << *ptr_du_lieu << endl; // Output: 42
} else {
cout << "Con tro la null." << endl;
}
// Gan lai con tro ve null khi khong can su dung dia chi nua
ptr_du_lieu = nullptr;
// Kiem tra lan cuoi
if (ptr_du_lieu != nullptr) {
cout << "Con tro hop le." << endl;
} else {
cout << "Con tro da tro lai null." << endl; // Output: Con tro da tro lai null.
}
// Vi du ve viec DANG NGUY HIEM khi dereference con tro null
// Uncomment doan code duoi day co the lam chuong trinh gap loi runtime!
/*
int* potentially_null_ptr = nullptr;
cout << "Co gang truy cap con tro null: ";
cout << *potentially_null_ptr << endl; // !!! Segmentation Fault hoac loi tuong tu !!!
*/
return 0;
}
Giải thích:
- Chúng ta khởi tạo
ptr_du_lieu
lànullptr
, đây là cách an toàn để đảm bảo con trỏ không trỏ lung tung khi mới được tạo. - Chúng ta sử dụng câu lệnh
if (ptr_du_lieu != nullptr)
hoặcif (ptr_du_lieu == nullptr)
để kiểm tra xem con trỏ có trỏ tới một địa chỉ hợp lệ hay không trước khi sử dụng toán tử*
(dereference). Đây là một thực hành cực kỳ tốt để tránh lỗi runtime và làm cho chương trình của bạn ổn định hơn. - Sau đó, chúng ta gán địa chỉ của biến
so
cho nó, làm cho con trỏ trở nên hợp lệ. - Chúng ta có thể gán lại con trỏ về
nullptr
khi không cần nó trỏ tới bất kỳ địa chỉ cụ thể nào nữa. - Phần code được comment cho thấy điều gì xảy ra nếu bạn không kiểm tra và cố gắng dereference một con trỏ null - chương trình gần như chắc chắn sẽ gặp lỗi (thường gọi là "Segmentation Fault" hoặc "Access Violation") vì hệ điều hành không cho phép bạn truy cập vào địa chỉ 0 (hoặc một số địa chỉ thấp khác thường được gán cho con trỏ null).
Luôn kiểm tra con trỏ null, đặc biệt là khi làm việc với bộ nhớ động (new
, delete
) hoặc các hàm có thể trả về con trỏ null trong trường hợp không thành công.
Bài tập ví dụ: C++ Bài 15.A6: Đảo ngược chữ số
Cho số nguyên dương \(n\) và mảng \(a\) có \(n\) phần tử. Hãy sử dụng con trỏ để đảo ngược các chữ số trong mảng. Ví dụ: số \(123\) sẽ được đảo ngược thành \(321\).
INPUT FORMAT
1 dòng gồm số nguyên dương \(n(1 \leq n \leq 10^3)\). Dòng tiếp theo, mỗi dòng gồm \(n\) số nguyên dương \(a_{ij}(1 \leq a_{ij} \leq 10^3)\).
OUTPUT FORMAT
1 dòng gồm mảng \(a\) sau khi đảo ngược các chữ số.
Ví dụ:
Input
3
123 34 5
Output
321 43 5
Giải thích ví dụ mẫu:
Ví dụ 1:
Input: 3
, 123 34 5
Giải thích: Đảo ngược từng số trong mảng: 123
thành 321
, 34
thành 43
, và 5
không thay đổi, nên kết quả là 321 43 5
.
<br>
1. Hiểu yêu cầu:
- Đọc một số nguyên
n
. - Đọc
n
số nguyên vào một mảng (hoặcvector
). - Sử dụng con trỏ để duyệt qua từng phần tử trong mảng.
- Đối với mỗi phần tử (số nguyên) trong mảng, đảo ngược thứ tự các chữ số của nó (ví dụ: 123 -> 321).
- Lưu kết quả đảo ngược trở lại vị trí ban đầu trong mảng.
- In mảng sau khi đã đảo ngược các chữ số của từng phần tử.
- Không cung cấp code hoàn chỉnh, chỉ hướng dẫn.
- Ưu tiên dùng
std
và code ngắn gọn.
2. Lập kế hoạch:
- Cần một hàm riêng để đảo ngược chữ số của một số nguyên đơn lẻ. Hàm này cần có khả năng thay đổi giá trị của số được truyền vào.
- Trong hàm
main
, đọc đầu vào. - Sử dụng
vector<int>
để lưu mảng các số. Đây là cách hiện đại và an toàn hơn mảng C-style. - Lấy một con trỏ trỏ đến phần tử đầu tiên của
vector
. - Duyệt qua
vector
bằng cách sử dụng con trỏ này và phép toán con trỏ (*ptr
,ptr++
). - Trong vòng lặp, tại mỗi vị trí con trỏ, gọi hàm đảo ngược chữ số cho giá trị mà con trỏ đang trỏ tới.
- Sau khi duyệt xong, in các phần tử của
vector
.
3. Hướng dẫn chi tiết:
a. Hàm đảo ngược chữ số cho một số (reverseDigits
):
* Tạo một hàm, ví dụ `void reverseDigits(int& number)`. Sử dụng tham chiếu (`int&`) là cách C++ thông dụng để một hàm sửa đổi giá trị của biến được truyền vào mà không cần dùng con trỏ trực tiếp trong signature hàm (dù bên trong có thể thao tác trên giá trị). Nếu bạn muốn tuân thủ *tuyệt đối* yêu cầu dùng con trỏ cho việc đảo ngược *số* này, bạn có thể dùng `void reverseDigits(int* numberPtr)`, nhưng dùng `int&` thường là idiomatic hơn cho việc này. Với mục đích bài này, chúng ta sẽ dùng con trỏ để duyệt *mảng*, còn việc đảo ngược số *đơn lẻ* có thể dùng `int&`.
* Bên trong hàm:
* Khởi tạo một biến `reversed_num = 0`.
* Sử dụng vòng lặp `while` để xử lý số gốc (`number`). Vòng lặp tiếp tục khi `number > 0`.
* Trong mỗi lần lặp:
* Lấy chữ số cuối cùng: `int digit = number % 10;`
* Xây dựng số đảo ngược: `reversed_num = reversed_num * 10 + digit;`
* Bỏ chữ số cuối cùng khỏi số gốc: `number /= 10;`
* Sau vòng lặp, gán `number = reversed_num;` (vì `number` được truyền bằng tham chiếu, thao tác này sẽ thay đổi giá trị gốc).
b. Hàm main
:
* Bao gồm các header cần thiết: `<iostream>` để nhập/xuất, `<vector>` để sử dụng `vector`.
* (Không bắt buộc nhưng nên dùng để tăng tốc độ I/O trong competitive programming) Thêm dòng `ios_base::sync_with_stdio(false); cin.tie(NULL);` ở đầu `main`.
* Đọc số nguyên `n`.
* Khai báo và tạo `vector<int> a` có kích thước `n`: `vector<int> a(n);`.
* Sử dụng vòng lặp `for` hoặc `while` để đọc `n` số nguyên và lưu vào `a`.
* **Sử dụng con trỏ để xử lý mảng:**
* Lấy con trỏ trỏ đến phần tử đầu tiên của vector: `int* ptr = a.data();` (hoặc `int* ptr = &a[0];`). Phương thức `.data()` là cách chuẩn để lấy con trỏ tới dữ liệu nền của `vector`.
* Sử dụng một vòng lặp `for` hoặc `while` chạy `n` lần. Ví dụ, `for (int i = 0; i < n; ++i)`.
* Trong mỗi lần lặp, gọi hàm `reverseDigits` cho giá trị mà con trỏ `ptr` đang trỏ tới: `reverseDigits(*ptr);`.
* Sau khi gọi hàm, di chuyển con trỏ tới phần tử kế tiếp: `ptr++;`.
* **In kết quả:**
* Sử dụng vòng lặp `for` (có thể dùng range-based for loop cho ngắn gọn) để duyệt qua `vector a` sau khi đã xử lý.
* In từng phần tử theo sau là một khoảng trắng. Đảm bảo không có khoảng trắng thừa ở cuối dòng.
4. Gợi ý về cú pháp con trỏ khi duyệt mảng:
Bạn có thể duyệt bằng con trỏ theo nhiều cách, ví dụ:
- Sử dụng chỉ số và con trỏ đầu:
int* ptr = a.data(); for (int i = 0; i < n; ++i) { reverseDigits(*(ptr + i)); // Hoặc ptr[i] }
Sử dụng con trỏ chạy và con trỏ cuối:
int* begin_ptr = a.data(); int* end_ptr = begin_ptr + n; // Con trỏ trỏ SAU phần tử cuối cùng for (int* current_ptr = begin_ptr; current_ptr != end_ptr; ++current_ptr) { reverseDigits(*current_ptr); }
Cách thứ hai (sử dụng con trỏ chạy và con trỏ cuối) thể hiện rõ ràng hơn việc "duyệt bằng con trỏ".
5. Lưu ý:
- Đảm bảo hàm
reverseDigits
thực sự thay đổi giá trị của số gốc (bằng cách sử dụng tham chiếu hoặc con trỏ). - Việc sử dụng con trỏ để duyệt mảng và áp dụng hàm đảo ngược chính là cách bạn "sử dụng con trỏ để đảo ngược các chữ số trong mảng" như yêu cầu.
- Nhớ in kết quả trên một dòng với các số cách nhau bởi khoảng trắng.
Comments