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>
int main() {
using namespace std;
int a = 10;
int* pa = &a;
cout << "Gia tri cua a: " << a << endl;
cout << "Dia chi cua a (su dung &a): " << &a << endl;
cout << "Gia tri cua pa (luu tru dia chi): " << pa << endl;
cout << "Gia tri tai dia chi ma pa tro toi (su dung *pa): " << *pa << endl;
*pa = 15;
cout << "\nGia tri moi cua a sau khi thay doi qua con tro: " << a << endl;
cout << "Gia tri moi tai dia chi ma pa tro toi: " << *pa << endl;
return 0;
}
Output:
Gia tri cua a: 10
Dia chi cua a (su dung &a): 0x7ffee1b5c28c
Gia tri cua pa (luu tru dia chi): 0x7ffee1b5c28c
Gia tri tai dia chi ma pa tro toi (su dung *pa): 10
Gia tri moi cua a sau khi thay doi qua con tro: 15
Gia tri moi tai dia chi ma pa tro toi: 15
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) {
using namespace std;
int temp = a;
a = b;
b = temp;
cout << "Ben trong ham (swap_fail): a = " << a << ", b = " << b << endl;
}
int main() {
using namespace std;
int x = 5, y = 10;
cout << "Truoc khi goi ham swap_fail: x = " << x << ", y = " << y << endl;
swap_fail(x, y);
cout << "Sau khi goi ham swap_fail: x = " << x << ", y = " << y << endl;
return 0;
}
Output:
Truoc khi goi ham swap_fail: x = 5, y = 10
Ben trong ham (swap_fail): a = 10, b = 5
Sau khi goi ham swap_fail: x = 5, y = 10
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* pa, int* pb) {
using namespace std;
int temp = *pa;
*pa = *pb;
*pb = temp;
cout << "Ben trong ham (swap_success): *pa = " << *pa << ", *pb = " << *pb << endl;
}
int main() {
using namespace std;
int x = 5, y = 10;
cout << "Truoc khi goi ham swap_success: x = " << x << ", y = " << y << endl;
swap_success(&x, &y);
cout << "Sau khi goi ham swap_success: x = " << x << ", y = " << y << endl;
return 0;
}
Output:
Truoc khi goi ham swap_success: x = 5, y = 10
Ben trong ham (swap_success): *pa = 10, *pb = 5
Sau khi goi ham swap_success: x = 10, y = 5
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() {
using namespace std;
int a[] = {10, 20, 30, 40, 50};
int n = sizeof(a) / sizeof(a[0]);
int* pa = a;
cout << "Truy cap phan tu mang bang con tro:" << endl;
for (int i = 0; i < n; ++i) {
cout << "Phan tu thu " << i << ": " << *(pa + i) << endl;
}
cout << "\nDuyet mang su dung con tro va phep tang con tro (++):" << endl;
pa = a;
for (int i = 0; i < n; ++i) {
cout << "Phan tu thu " << i << ": " << *pa << endl;
pa++;
}
return 0;
}
Output:
Truy cap phan tu mang bang con tro:
Phan tu thu 0: 10
Phan tu thu 1: 20
Phan tu thu 2: 30
Phan tu thu 3: 40
Phan tu thu 4: 50
Duyet mang su dung con tro va phep tang con tro (++):
Phan tu thu 0: 10
Phan tu thu 1: 20
Phan tu thu 2: 30
Phan tu thu 3: 40
Phan tu thu 4: 50
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>
int main() {
using namespace std;
char s[] = "Hello C++ Pointers!";
char* ps = s;
cout << "In chuoi su dung con tro va vong lap:" << endl;
while (*ps != '\0') {
cout << *ps;
ps++;
}
cout << endl;
ps = s;
cout << "In chuoi truc tiep bang con tro char*: " << ps << endl;
ps = s;
*ps = 'J';
cout << "Chuoi sau khi thay doi ky tu dau tien: " << s << endl;
return 0;
}
Output:
In chuoi su dung con tro va vong lap:
Hello C++ Pointers!
In chuoi truc tiep bang con tro char*: Hello C++ Pointers!
Chuoi sau khi thay doi ky tu dau tien: Jello C++ Pointers!
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>
int main() {
using namespace std;
int* p = nullptr;
if (p != nullptr) {
cout << "Con tro hop le." << endl;
} else {
cout << "Con tro la null." << endl;
}
int x = 42;
p = &x;
if (p != nullptr) {
cout << "Con tro bay gio hop le, gia tri: " << *p << endl;
} else {
cout << "Con tro la null." << endl;
}
p = nullptr;
if (p != nullptr) {
cout << "Con tro hop le." << endl;
} else {
cout << "Con tro da tro lai null." << endl;
}
/*
int* pn = nullptr;
cout << "Co gang truy cap con tro null: ";
cout << *pn << endl;
*/
return 0;
}
Output:
Con tro la null.
Con tro bay gio hop le, gia tri: 42
Con tro da tro lai null.
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
.
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.
#include <iostream>
#include <vector>
#include <algorithm> // For reverse if needed, but we'll do it manually
void daoSo(int* s) {
int goc = *s;
int dao = 0;
if (goc == 0) { // Handle case for 0 explicitly if needed
return;
}
while (goc > 0) {
int d = goc % 10;
dao = dao * 10 + d;
goc /= 10;
}
*s = dao;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
using namespace std;
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; ++i) {
cin >> a[i];
}
int* p = a.data();
for (int i = 0; i < n; ++i) {
daoSo(p + i); // Pass address of current element
}
// Or iterate directly with pointer:
// int* begin_p = a.data();
// int* end_p = a.data() + n;
// for (int* curr_p = begin_p; curr_p != end_p; ++curr_p) {
// daoSo(curr_p);
// }
for (int i = 0; i < n; ++i) {
cout << a[i] << (i == n - 1 ? "" : " ");
}
cout << endl;
return 0;
}
Output (for given input):
321 43 5
Comments