You are on page 1of 49

NGUYỄN PHÚ QUẢNG

BỘ MÔN TIN HỌC XÂY DỰNG


KHOA CÔNG NGHỆ THÔNG TIN – ĐẠI HỌC XÂY DỰNG

LẬP TRÌNH C++


Tài liệu lưu hành nội bộ
version 0.0.2
Lập trình C++ Nguyễn Phú Quảng

Mục lục
I. MỞ ĐẦU.............................................................................................................................4
I.1. Môi trường phát triển.................................................................................................................... ..........4
I.2. Chương trình đầu tiên...................................................................................................... ......................4
I.3. Input & Output................................................................................................................ ........................5

II. NGÔN NGỮ.......................................................................................................................5


II.1. Cơ bản về ngôn ngữ.................................................................................................. ...........................5
II.2. Kiểu................................................................................................................................... ....................5
II.3. Chuyển đổi giữa các kiểu...................................................................................................... ................7
II.4. Biểu thức & toán tử...................................................................................................... .........................7

III. LỆNH.................................................................................................................................9
III.1. Lệnh dạng biểu thức.......................................................................................................................... ...9
III.2. Khối lệnh.................................................................................................................. ............................9
III.3. Lệnh rẽ nhánh.............................................................................................................. ........................9
III.4. Vòng lặp for...................................................................................................................... ..................10
III.5. Vòng lặp while................................................................................................................. ...................13
III.6. Vòng lặp do...while........................................................................................................ .....................13
III.7. Nhảy không điều kiện......................................................................................................... ................13

IV. HÀM................................................................................................................................13
IV.1. Định nghĩa hàm................................................................................................................. .................13
IV.2. Ví dụ khai báo hàm........................................................................................................................... ..14
IV.3. Khi nào sử dụng hàm...................................................................................................... ...................14
IV.4. Hàm được gọi như thế nào?........................................................................................ ......................14
IV.5. Hàm gọi đệ quy........................................................................................................... .......................15

V. MẢNG..............................................................................................................................17
V.1. Định nghĩa............................................................................................................................ ...............17
V.2. Truy cập phần tử của mảng............................................................................................................. ....17
V.3. Khởi tạo mảng......................................................................................................................... ............17
V.4. Mảng nhiều chiều.............................................................................................................. ..................17
V.5. Sử dụng mảng làm tham số của hàm................................................................................................ ..17

VI. CON TRỎ.......................................................................................................................17


VI.1. Khai báo con trỏ................................................................................................................... ..............17
VI.2. Các toán tử trên con trỏ............................................................................................... ......................17
VI.3. Con trỏ và mảng.............................................................................................................................. ...17
VI.4. Con trỏ hàm............................................................................................................... ........................17

VII. STRUCT........................................................................................................................17
VII.1. Định nghĩa Struct.................................................................................................... ..........................17
VII.2. Khai báo biến................................................................................................................. ...................18
VII.3. Truy cập trường...................................................................................................... ..........................18

VIII. Stream..........................................................................................................................18
VIII.1. File stream............................................................................................................... ........................18
VIII.2. String stream............................................................................................................................ ........19
VIII.3. Ghi có định dạng............................................................................................................... ...............19

IX. KIỂU NGƯỜI DÙNG ĐỊNH NGHĨA...............................................................................21

X. CẤP PHÁT BỘ NHỚ ĐỘNG...........................................................................................21

2
Lập trình C++ Nguyễn Phú Quảng

XI. LỚP................................................................................................................................21

XII. MẪU...............................................................................................................................21

XIII. THƯ VIỆN CHUẨN......................................................................................................21


XIII.1. Các khái niệm......................................................................................................... .........................21
XIII.2. Kiểu vector..................................................................................................................... ..................22
XIII.3. Kiểu string...................................................................................................................... ..................26
XIII.4. Kiểu list (danh sách).................................................................................................................... .....29
XIII.5. Kiểu set (tập hợp)................................................................................................. ...........................32
XIII.6. Kiểu map (ánh xạ).................................................................................................................. ..........32
XIII.7. Kiểu hash_map (ánh xạ dùng mảng băm)................................................................ .......................34
XIII.8. Kiểu hash_set (tập hợp)............................................................................................................ .......35
XIII.9. Thuật toán (Algorithm).................................................................................................................... ..35

XIV. GIẢI MỘT SỐ BÀI TOÁN BẰNG STL.........................................................................43


XIV.1. Quản lý sinh viên............................................................................................................................ ..43
XIV.2. Rào đất.................................................................................................................. ..........................45
XIV.3. Robot..................................................................................................................... ..........................46
XIV.4. Dijsktra.............................................................................................................................. ...............47
XIV.5. Hợp diện tích hình chữ nhật............................................................................................................ .47

XV. INPUT VÀ OUTPUT......................................................................................................49

3
Lập trình C++ Nguyễn Phú Quảng

I. MỞ ĐẦU
C++ là ngôn ngữ mạnh, được phát triển từ ngôn ngữ C. Học C++ đem lại nhiều lợi thế cho bạn với
khối lượng mã nguồn vào loại lớn nhất trong các ngôn ngữ, cũng như tính tương đồng với hầu hết
các ngôn ngữ tựa C (C-like language).
Tài liệu này mong muốn giúp bạn có một cái nhìn tổng thể với cách tiếp cận thật ngắn gọn. Cấu trúc
sách dựa trên cuốn "C++ in a nutshell" của nhà xuất bản O'Reilly.
Tại Việt nam, đã có rất nhiều cuốn sách giới thiệu về C, C++. Trong tài liệu này, chúng tôi không
mang tham vọng xây dựng một giáo trình về C++ mà chỉ là những đúc kết kinh nghiệm mà chúng
tôi đã trực tiếp làm việc với C++, nhất là trong những bài toán thiên về giải thuật.
Mọi góp ý xin gửi về địa chỉ: nguyenphuquang@gmail.com. Xin chân thành cảm ơn mọi đóng góp
của các bạn.

I.1. Môi trường phát triển


Để thuận tiện cho việc học ngôn ngữ, chúng tôi xin giới thiệu 2 môi trường quen thuộc để bạn có
thể thực hành lập trình C++.
Microsoft Visual C++ (6.0, 7.0, 7.1): phần mềm này nằm trong bộ phần mềm Microsoft Visual
Studio.
DevC++ 5.0: Bạn có thể tải miễn phí từ địa chỉ http://www.bloodshed.net/devcpp.html, chúng tôi
khuyến khích các bạn sử dụng phần mềm miễn phí này.
Toàn bộ chương trình minh họa đều có thể chạy trên cả 2 môi trường VC++ và DevC.

I.2. Chương trình đầu tiên


Mở DevC, bạn tạo file mới, gõ vào đoạn chương trình dưới đây và lưu lại (tất nhiên là không gõ vào
số dòng, trong tất cả các chương trình minh họa, chúng tôi sử dụng số dòng để giải thích cho
chương trình). Sau đó bấm Ctrl+F9 để dịch chương trình, bấm Ctrl+F10 để chạy chương trình, bạn
sẽ thấy một màn hình màu đen và dòng chữ "Hello every body!", khi bạn gõ 1 phím bất kỳ thì màn
hình màu đen sẽ đóng lại.
01 #include <iostream>
02 #include <stdio.h>
03
04 using namespace std;
05 int main() {
06 cout << "Hello every body!" << endl;
07 getchar();
08 }

Dòng 01, 02: Sử dụng 2 thư viện iostream và stdio.h


Dòng 04: Khi các chương trình dài và phức tạp, việc đặt tên cho các biến, hàm gặp khó khăn. Để dễ
dàng hơn khi đặt tên, người ta chia tên thành nhiều không gian khác nhau gọi là namespace. std là
một namespace được sử dụng trong iostream và một số thư viện chuẩn khác.
Dòng 05: Hàm main là hàm không thể thiếu trong các chương trình C++. Khi chương trình được
thực thi, nó sẽ gọi đến hàm main.
Dòng 06: Trong C++, khái niệm stream chỉ một dòng dữ liệu (đầu vào hoặc đầu ra). Trong trường
hợp này cout là dòng dữ liệu ra (mặc định là màn hình). Sử dụng toán tử <<, chúng ta thực hiện việc
đưa 2 dữ liệu lần lượt là xâu ký tự "Hello every body!" và ký tự xuống dòng endl ra màn hình.

4
Lập trình C++ Nguyễn Phú Quảng

Dòng 07: getchar() là hàm giúp chương trình dừng lại và đợi người dùng gõ một phím bất kỳ. Thực
chất, trong ví dụ này không cần phải dừng chương trình. Nhưng do đặc tính của môi trường DevC
và VC là màn hình output sẽ biến mất sau khi chương trình chạy xong, do đó chúng tôi đưa thêm
lệnh này để dừng chương trình lại, giúp các bạn quan sát thấy xâu ký tự in trên màn hình.

I.3. Input & Output

II. NGÔN NGỮ


II.1. Cơ bản về ngôn ngữ
Ngôn ngữ lập có nhiều điểm tương tự với ngôn ngữ tự nhiên
- Chứa một tập từ khóa (giống với từ vựng của ngôn ngữ tự nhiên)
- Chứa một loạt các quy tắc kết hợp các từ khóa, toán tử với nhau (giống với ngữ pháp trong
ngôn ngữ tự nhiên)
Khi bạn đã thạo một ngôn ngữ lập trình, việc học một ngôn ngữ khác là tương đối đơn giản (cũng
như bạn đã học tiếng Anh thì có thể học tiếng Pháp rất nhanh vì hai ngôn ngữ này có nhiều từ tương
đồng với nhau).
Việc học ngôn C++ (cũng như học tiếng Anh) đem lại cho bạn lợi thế rất lớn vì đây là ngôn ngữ
được sử dụng phổ biến nhất. Hơn thế nữa có rất nhiều các ngôn ngữ lập trình khác được xây dựng
trên cơ sở cú pháp của C++ (gọi là các ngôn ngữ giống-C – C-like), do đó bạn có thể tiếp cận rất
nhanh với các ngôn ngữ này nếu đã thông thạo với C++.

II.2. Kiểu

II.2.1. Các kiểu cơ bản


Phần này trình bày các kiểu dữ liệu cơ bản của C++. Trong giới hạn của tài liệu này, tôi không trình
bày về con trỏ. Mặc dù con trỏ là một trong nhưng đặc điểm nổi bật của C++ nhưng tính phức tạp
của con trỏ dễ gây nhầm lẫn cho người mới làm quen. Chúng ta sẽ giải quyết các bài toán trên C++
mà không hoặc hạn chế tối đa sử dụng con trỏ.

a. Kiểu số

• Số nguyên
- Kiểu char (1 byte): (-128 đến 127)
- Kiểu short (2 byte): (-32768 đến 32767)
- Kiểu int, long (4 byte): (-2 tỷ đến 2 tỷ)
- Tất cả các kiểu trên, nếu bổ sung unsign :
o unsign char:
o unsign short:
o unsign int:
- Việc lựa chọn kiểu dữ liệu trong bài toán phục thuộc vào vùng giá trị của biến. Khoảng giá
trị càng lớn, biến càng chiếm nhiều bộ nhớ. Chương trình C++ dịch trên DevC hoạt động
trên môi trường Windows thường là hệ điều hành 32 bit, số nguyên được sử dụng phổ biến
nhất là kiểu int.

5
Lập trình C++ Nguyễn Phú Quảng

- Ví dụ
char a;
int b = 20;
long c = 30;
unsign char d = 200;

• Số thực
- float (4 byte): Số thực độ chính xác đơn.
- double (8 byte): Số thực độ chính xác kép.

• Logic
- bool: có 2 giá trị là true và false

II.2.2. Các kiểu phức

a. Kiểu mảng
- Mảng là một dãy các phần tử cùng kiểu được sắp liên tiếp nhau trong bộ nhớ
- Các phần tử được phân biệt với nhau bởi chỉ số, là các số tuần tự bắt đầu từ 0
- Ví dụ
int a[100]; // Khai báo mảng gồm 100 số int
// a[0] là 1 số int, phần tử đầu tiên của mảng
// a[1] là 1 số int, phần tử thứ 2 của mảng
// a[99] là 1 số int, phần tử cuối cùng của mảng
long b[10]; // Khai báo mảng gồm 10 số long

- Mảng được khai báo theo ví dụ trên là mảng 1 chiều. Chiều của mảng là số chỉ số sử dụng
để xác định một phần tử (giống như hệ tọa độ 1 chiều, 2 chiều, 3 chiều)
- Ví dụ khai bao và sử dụng mảng nhiều chiều như sau
int a[3][3]; // Khai báo a là mảng 2 chiều (giống như 1 bảng)
// a[0] là một mảng 1 chiều gồm 3 số nguyên (giống như 1 dòng của bảng)
// a[0][0] là số int đầu tiên của dòng
// a[0][1] là số int thứ 2 của dòng
// a[0][2] là số int cuối cùng của dòng
// a[1] là dòng thứ 2 của bảng
// a[2] là dòng cuối cùng của bảng
a[1][1] = 5; // Gán giá trị của số nguyên ở dòng 1, cột 1 là 5

b. Kiểu struct
Struct là kiểu dữ liệu cho phép bạn tập hợp nhiều thành phần vào trong cùng một biến. Lấy ví dụ
thông tin của một điểm gồm 2 tọa độ x, y, thông tin của một đoạn thẳng gồm điểm đầu và điểm
cuối.
Struct là kiểu dữ liệu, do đó bạn phải khai báo kiểu dữ liệu đó bằng lệnh typedef rồi mới có thể khai
báo được các biến thuộc kiểu này. Cú pháp khai báo struct như sau:
struct <Tên_kiểu> { // Khai báo kiểu struct
<Kiểu_trường_1> <Tên_trường_1>; // Trường của struct (giống khai báo biến)
<Kiểu_trường_2> <Tên_trường_2>, <Tên_trường_3>;
};

<Tên_kiểu> <biến_1>, <biến_2>; // Khai báo biến thuộc kiểu mới tạo ra

<biến_1>.<Tên_trường_1> = <Giá_trị>; // Truy cập trường dữ liệu của biến

Các thành phần khai báo bên trong struct được gọi là trường (field). Một struct gồm nhiều trường,
biến thuộc kiểu struct

6
Lập trình C++ Nguyễn Phú Quảng

II.2.3. Các kiểu stl

a. Kiểu string

b. Kiểu vector

c. Kiểu vector

II.3. Chuyển đổi giữa các kiểu

II.4. Biểu thức & toán tử

II.4.1. Biểu thức


Biểu thức là kết hợp của các toán tử và toán hạng. Toán hạng có thể là các số hoặc các biến.

II.4.2. Toán tử

a. Các phép toán số học


- Phép cộng +
o a+b
- Phép trừ -
o a-b
- Phép nhân *
o a*b
- Phép chia /: Chú ý trong phép chia
o Nếu cả 2 toán hạng cùng là số nguyên thì phép chia là chia nguyên
 10 / 3 cho giá trị 3
o Nếu 1 trong 2 toán hạng là số thực thì phép chia là chia số thực
 10.0 / 3 cho giá trị 3.3333333
- Phép lấy dư: %
o 10 % 3 cho giá trị 1

b. Các phép toán so sánh


- So sánh bằng ==
o 10 == 3 cho giá trị false
o 10 == 10 cho giá trị true
- So sánh khác !=
o 10 != 3 cho giá trị true
7
Lập trình C++ Nguyễn Phú Quảng

- So sánh lớn hơn >


- So sánh nhỏ hơn <
- So sánh lớn hơn hay bằng >=
- So sánh nhỏ hơn hay bằng <=

c. Các phép toán logic


- Phép toán and &&
o 10==3 && 5>4: cho giá trị false
o 10>3 && 5>4: cho giá trị true
- Phép toán or ||
o 10==3 || 5>4: cho giá trị true
o 10==3 || 5<4 cho giá trị false
- Phép toán not !
o !(10==3) cho giá trị

d. Các phép toán trên bit


- Phép toán and từng bit &
o 10 & 3 = 10102 & 00112 = 00102
o 10 & 15 = 10102 & 11112 =10102
- Phép toán or từng bit |
o 10 | 3 = 10102 | 00112 = 10112
o 10 | 15 = 10102 | 11112 = 11112

e. Các phép toán 1 ngôi


- Phép tăng 1 đơn vị
o i++ (chỉ dành cho biến có giá trị nguyên)
- Phép giảm 1 đơn vị
o i—
- Phép tăng giá trị của biến
o i += 2
- Phép giảm giá trị của biến
o i -= 2
- Phép nhân giá trị của biến với 1 số
o i *= 5
- Phép chia giá trị của biến cho 1 số
o i /= 5
- Các phép toán khác như &=, |=, &&=, ||= ... tương tự

8
Lập trình C++ Nguyễn Phú Quảng

III. LỆNH
III.1. Lệnh dạng biểu thức

III.1.1. Phép gán


Tên biến = Biểu thức;
- Biểu thức có thể là 1 hằng số
- Biểu thức có thể là 1 biến
- Biểu thức có thể là biến và hằng số kết hợp với nhau bởi toán tử

III.2. Khối lệnh


Các khối lệnh để phân chương trình thành nhiều khối con. Việc phân khối giúp chương trình rõ
ràng, dễ hiểu hơn theo nguyên tắc chia để trị. Một khối lệnh được đặt trong cặp ngoặc { } và được
coi như một lệnh đơn.
C++ cho phép khai báo biến tại bất kỳ nơi nào trong chương trình. Biến khai báo trong khối lệnh
nào thì chỉ tồn tại trong khối chương trình đó (biến khai báo trong khối chương trình trong không
thể sử dụng trong khối chương trình ngoài).
Ví dụ về việc sử dụng các khối chương trình.
if (a>b) {
int m; // Biến m khai báo trong khối if (a>b)
m = 1; // m có thể được sử dụng tại cùng khối chương trình
if (d>e) {
m = 5; // m có thể được sử dụng tại khối chương trình bên trong
}
} else {
m = 10; // m không thể sử dụng được ở khối chương trình ngoài
}

III.3. Lệnh rẽ nhánh


Lệnh if sử dụng để có hay không thực hiện một đoạn chương trình theo một điều kiện (gọi là rẽ
nhánh). Lệnh if gồm 2 dạng: rút gọn và đầy đủ
- Dạng rút gọn: Nếu <Điều_kiện> đúng thì thực hiện <Lệnh>
if (<Điều_kiện>) {
<Lệnh>
}

o <Điều_kiện> có thể là
 Một biểu thức logic
 Một biểu thức kiểu số (số khác 0 được coi là đúng, bằng 0 là sai)
o Nếu <Lệnh> chỉ là 1 lệnh đơn, bạn không cần để trong khối chương trình bằng cặp
ngoặc {}. Tuy nhiên, việc này không được khuyến khích.
- Dạng đầy đủ: Nếu <Điều_kiện> là đúng thì thực hiện <Lệnh_nếu_đúng>, ngược lại thực
hiện <Lệnh_nếu_sai>
if (<Điều_kiện>) {
<Lệnh_nếu_đúng>;
} else {
<Lệnh_nếu_sai>;
}

If là một cấu trúc điều khiển được sử dụng phổ biến nhất. Đoạn chương trình sau sẽ nêu ví dụ sử
dụng lệnh if.

9
Lập trình C++ Nguyễn Phú Quảng

#include <iostream>
#include <stdlib.h>
using namespace std;

int main ()
{
int gio;
cout << "Nhap vao gio (0-24): ";
cin >> gio;

if (gio<12)
{
cout << "Chao buoi sang";
} else
{
cout << "Chao buoi chieu";
}
getchar();
getchar();
}

III.4. Vòng lặp for

III.4.1. Cú pháp vòng lặp for


Vòng lặp for của C++ khái quát hơn vòng lặp for của Pascal. Bạn có thể sử dụng vòng lặp với cú
pháp rất mềm dẻo cho nhiều hoàn cảnh khác nhau. Cú pháp của vòng lặp for như sau:
for (<Lệnh_khởi_tạo> ; <Điều_kiện> ; <Lệnh_thay_đổi>)
{
<Lệnh_lặp>;
}

Trình tự thực hiện của vòng lặp như sau:


- Thực hiện <Lệnh_khởi_tạo>
- Kiểm tra <Điều_kiện>
o Nếu <Điều_kiện> là đúng:
 Thực hiện <Lệnh_lặp>
 Thực hiện <Lệnh_thay_đổi> để thay đổi trạng thái
 Lặp lại bước thứ 2 (Kiểm tra điều kiện)
o Nếu <Điều_kiện> sai, thoát khỏi vòng lặp
Xét ví dụ sau:
for (int i=0; i<3; i++)
{
cout << "i = " << i << endl;
}

- Thực hiện lệnh khởi tạo (i=0)


- Kiểm tra điều kiện (i<3)
o i=0 nên điều kiện là đúng
 Thực hiện lệnh cout... lệnh này viết ra màn hình dòng chữ "i = 0"
 Thực hiện lệnh thay đổi i++ (i tăng từ 0 thành 1)
 Lặp lại bước kiểm tra
- Kiểm tra điều kiện (i<3)
o i=1 nên điều kiện là đúng
 Thực hiện lệnh cout... lệnh này viết ra màn hình dòng chữ "i = 1"

10
Lập trình C++ Nguyễn Phú Quảng

 Thực hiện lệnh thay đổi i++ (i tăng từ 1 thành 2)


 Lặp lại bước kiểm tra
- Kiểm tra điều kiện (i<3)
o i=2 nên điều kiện là đúng
 Thực hiện lệnh cout... lệnh này viết ra màn hình dòng chữ "i = 2"
 Thực hiện lệnh thay đổi i++ (i tăng từ 2 thành 3)
 Lặp lại bước kiểm tra
- Kiểm tra điều kiện (i<3)
o i=3 nên điều kiện sai, thoát khỏi vòng lặp
Như vậy, với vòng lặp nêu trên, chương trình thực hiện lệnh lặp 3 lần, viết ra màn hình 3 dòng
 i=0
 i=1
 i=2
Vòng lặp kết thúc khi i = 3 (điều kiện kiểm tra sai)

III.4.2. Vòng lặp for lồng nhau


Ví dụ sử dụng vòng lặp for lồng nhau để viết bảng cửu chương ra màn hình. Trong trường hợp này
vòng lặp for sẽ hoạt động như sau:
 i=0
- Thực hiện vòng lặp for với j = 0, 1, 2,... , 8, 9 (10 lần lặp, mỗi lần viết ra tích i*j), sau đó
viết xuống dòng.
 i= 1
- Thực hiện vòng lặp với j = 0, 1, 2,... 9
 i=2
- ....
 i=9
- Thực hiện vòng lặp với j = 0, 1, 2,... 9
#include <iostream>
#include <stdlib.h>
#include <iomanip>
using namespace std;

int main ()
{
for (int i=0; i<10; i++)
{
for (int j=0; j<10; j++) cout << setw(4) << i*j;
cout << endl;
}
getchar();
}

Kết quả của đoạn chương trình sẽ như sau:

11
Lập trình C++ Nguyễn Phú Quảng

Vòng lặp for của C++ có thể thay thế vòng lặp for... downto của Pascal bằng cách thay đổi điều kiện
kiểm tra và lệnh tăng thành lệnh giảm. Ví dụ sau viết ra 100 số từ 990, 980, 970 đến 0
#include <iostream>
#include <stdlib.h>
#include <iomanip>
using namespace std;

int main ()
{
for (int i=990; i>=0; i-=10)
{
cout << i << " ";
}
getchar();
}

III.4.3. Sử dụng break và continue trong vòng lặp for


Trong vòng lặp for, bạn có thể sử dụng các lệnh sau:
- break để thoát khỏi vòng lặp
- continue để tiếp tục thực hiện bước tiếp theo của vòng lặp
Sau đây là ví dụ sử dụng break và continue:
#include <iostream>
#include <stdlib.h>
#include <math.h>

using namespace std;

int main()
{
int n = 100; // Viết tất cả các số nguyên tố từ 1 đến n ra màn hình
int i, j, m;
for (i=1; i<=n; i++) { // Xét từng số
// Xét tất cả các số từ 2 đến căn 2 của i
for (j=2, m = sqrt(i); j<=m; j++) {
if (i%j==0) break; // Nếu có 1 số là ước thì thoát
}
// Nếu thoát khi i chia hết cho j thì i ko phải là số nguyên tố
// Nếu i không chia hết cho j, số i là nguyên tố
if (i%j!=0) cout << i << " ";
}
getchar();
}

12
Lập trình C++ Nguyễn Phú Quảng

III.5. Vòng lặp while


Cú pháp
while (<Điều_kiện>)
{
<Lệnh_lặp>;
}

Trong khi <Điều_kiện> còn đúng thì thực hiện <Lệnh_lặp>


Vòng lặp while ít khi được sử dụng vì bạn có thể sử dụng vòng lặp for thay cho while.
Vòng lặp while và do... while đều có thể sử dụng break và continue tương tự vòng lặp

III.6. Vòng lặp do...while


do
{
<Lệnh_lặp>;
} while (<Điều_kiện>);

Thực hiện <Lệnh_lặp> cho đến khi <Điều_kiện> sai.

III.7. Nhảy không điều kiện


Lệnh goto sử dụng để nhảy đến một vị trí trong chương trình. Lệnh goto phá vỡ tính cấu trúc của
chương trình nhưng nếu biết tận dụng sẽ làm giảm độ phức tạp của chương trình. Vẫn với ví dụ viết
ra các số nguyên tố, bạn có thể chỉnh sửa chương trình cho đơn giản hơn bằng cách sử dụng goto:
#include <iostream>
#include <stdlib.h>
#include <math.h>

using namespace std;

int main()
{
int n = 100;
int i, j, m;
for (i=1; i<=n; i++) {
for (j=2, m = sqrt(i); j<=m; j++) {
// Nếu i chia hết cho j, nhảy đến vị trí "KoNguyenTo"
if (i%j==0) goto KoNguyenTo;
}
// Nếu i không chia hết cho bất cứ j nào, i là số nguyên tố
cout << i << " ";

// Nếu i chia hết cho 1 số j, Chương trình sẽ nhảy trực tiếp đến
// vị trí KoNguyenTo
KoNguyenTo:;
}
getchar();
}

IV. HÀM
IV.1. Định nghĩa hàm
Cú pháp:
<Kiểu_hàm> <Tên_hàm>([Danh_sách_tham_số])
{
<Lệnh>;
return <Giá_trị_trả_về>;
<Lệnh>;
}

Trong đó:
- <Kiểu_hàm>: Kiểu giá trị trả về của hàm, (nếu hàm không trả về giá trị, kiểu hàm là void)

13
Lập trình C++ Nguyễn Phú Quảng

- <Tên_hàm>: Tên của hàm (quy ước giống như đặt tên biến – không có dấu cách, không
chứa ký tự đặc biệt)
- Nếu hàm có kiểu void, có thể dùng lệnh return để thoát khỏi hàm
- Nếu hàm có kiểu khác void, bạn phải trả về giá trị tương ứng với kiểu (ví dụ kiểu int thì
phải là return 1)
- Nếu không có tham số, bạn vẫn phải có cặp ngoặc ()
Danh sách tham số được liệt kê như sau:
<Kiểu_tham_số_1> <Tên_tham_số_1>, <Kiểu_tham_số_2> <Tên_tham_số_2>,...

IV.2. Ví dụ khai báo hàm


#include <iostream>
#include <fstream>
#include <stdio.h>

using namespace std;

// Hàm tính tổng của 2 số


// Có 2 tham số a, b (kiểu int)
// Trả về tổng của a và b (kiểu int)
int Tong(int a, int b)
{
return a+b;
}

int main()
{
cout << "Ham tinh tong: " << Tong(20,34) << endl;
getchar();
}

IV.3. Khi nào sử dụng hàm


Hàm được sử dụng trong các trường hợp sau:
- Một đoạn lệnh được lặp đi, lặp lại nhiều lần
- Một đoạn chương trình quá dài, cần chia nhỏ để dễ quản lý

IV.4. Hàm được gọi như thế nào?


Các biến và tham số của hàm được lưu trong stack, mỗi khi bạn gọi hàm, máy tính thực hiện các
công việc sau:
- Lần lượt đưa các tham số vào trong stack
- Lưu lại vị trí gọi hàm (của hàm hiện tại)
- Trỏ chương trình sang vị trí mới (của hàm được gọi)
- Lấy các tham số ra khỏi stack
- Mỗi biến được khai báo trong chương trình con sẽ được đưa vào stack
Khi thoát khỏi chương trình con, máy tính làm việc theo quy trình ngược lại
- Lấy các biến đã khai báo ra khỏi stack
- Lưu lại giá trị trả về của hàm (trong thanh ghi)
- Trỏ chương trình về vị trí cũ (trước khi gọi hàm)
Với quy trình này, có thể nhận ra các đặc điểm sau:
- Khi gọi hàm, bộ nhớ của stack đầy lên bằng tổng kích thước của các tham số và các biến
khai báo trong hàm

14
Lập trình C++ Nguyễn Phú Quảng

- Sau khi thoát khỏi hàm, vùng bộ nhớ cấp cho các tham số và biến trong hàm được khôi
phục, stack trở về trạng thái cũ
- Các hàm có thể gọi lẫn nhau, mỗi lần gọi hàm stack lại đầy thêm. Stack chứa thông tin thứ
tự các hàm gọi nhau và các biến trong hàm đó (thông tin này gọi là Call stack)

IV.5. Hàm gọi đệ quy


Đệ quy là cơ chế một hàm tự gọi chính nó. Nhờ sử dụng stack, mỗi hàm sẽ có vùng nhớ lưu giữ
tham số và biến của riêng nó. Các vùng nhớ này được sắp xếp trên stack (hàm gọi cuối cùng sẽ
được lấy ra trước tiên).
Xét ví dụ kinh điển sau:
#include <iostream>
#include <fstream>
#include <stdio.h>

using namespace std;

// Hàm tính giai thừa của một số


// Sử dụng công thức truy hồi n! = n * (n-1)!
// Hàm GiaiThua(n) được tính từ hàm GiaiThua(n-1)
// Tức là hàm GiaiThua tự gọi lại chính nó (đệ quy)
int GiaiThua(int n)
{
if (n>=1) return n*GiaiThua(n-1); else return 1;
}

int main()
{
int n;
cout << "n = "; cin >> n;
cout << "n! = " << GiaiThua(n) << endl;
fflush(stdin);
getchar();
}

Chương trình trên tính giai thưa của một số bằng cách sử dụng hàm GiaiThua. Bản thân hàm này sử
dụng công thức truy hồi n! = n * (n-1)! Hàm GiaiThua(n) gọi hàm GiaiThua(n-1), GiaiThua(n-1)
gọi GiaiThua(n-2). Cứ tuần tự cho đến hàm GiaiThua(1), hàm này quá đơn giản vì cho giá trị 1 nên
không gọi đệ quy.
Như vậy, hàm GiaiThua(n) được tính bằng cách gọi đệ quy n lần thay vì dùng vòng lặp. Để các bạn
hiểu rõ hơn về đệ quy, chúng ta phân tích hoạt động của hàm GiaiThua(n) với n=4.

Begin End

return
4*6=24 return
3*2=6
4>=1 4*GiaiThua(3)
Đ

return
2*1=2
3 3*GiaiThua(2)
Đ

2*GiaiThua(1
2>=1 )
Đ
return
1

1>=1
S

 Chúng ta hãy thử phân tích quá trình hoạt động của các hàm khi gọi đệ quy qua bảng sau

15
Lập trình C++ Nguyễn Phú Quảng

Bước Mô tả Stack
main() Chương trình bắt đầu hoạt
động từ hàm main()
gọi GiaiThua(4) Hàm main() thực hiện lời gọi
hàm GiaiThua(4) 4

gọi GiaiThua(3) Hàm GiaiThua(4) gọi hàm


GiaiThua(3) 3

gọi GiaiThua(2) Hàm GiaiThua(3) gọi hàm


GiaiThua(2) 2

gọi GiaiThua(1) Hàm GiaiThua(2) gọi hàm


GiaiThua(1) 1

Trả về giá trị n=1, hàm GiaiThua(1) không


của gọi đệ quy mà trả về giá trị 1, 1
GiaiThua(1) sau đó thoát ra, trở về hàm đã
gọi nó (là hàm GiaiThua(2)) 2

Trả về giá trị n=2, Tính n*GiaiThua(1) = 2*1


của =2 2
GiaiThua(2) Trả về giá trị 2
3
Thoát trở về hàm đã gọi hàm
này (là hàm GiaiThua(3)) 4

Trả về giá trị n=3, Tính n*GiaiThua(2) = 3*2


của =6 3
GiaiThua(3) Trả về giá trị 6
4
Thoát trở về hàm đã gọi hàm
này (là hàm GiaiThua(4))
Trả về giá trị n=4, tính n*GiaiThua(3) = 4*6 =
của 24 4
GiaiThua(4) Trả về giá trị 24

16
Lập trình C++ Nguyễn Phú Quảng

Thoát trở về hàm đã gọi hàm


này (là hàm main())

V. MẢNG
V.1. Định nghĩa

V.2. Truy cập phần tử của mảng

V.3. Khởi tạo mảng

V.4. Mảng nhiều chiều

V.5. Sử dụng mảng làm tham số của hàm

VI. CON TRỎ


Trong máy tính, bộ nhớ được sắp xếp trong các ô liên tiếp nhau, mỗi ô chiếm 1 byte. Các ô nhớ này
phân biệt với nhau bởi địa chỉ (gần giống các phần tử của mảng phân biệt với nhau bởi chỉ số).
Mỗi biến, tùy theo kích thước sẽ chiếm 1 hoặc nhiều ô nhớ liên tục trong bộ nhớ. Địa chỉ của biến
là địa chỉ của ô nhớ đầu tiên của biến đó.
Trong C++, có một loại biến lưu lại địa chỉ của các ô nhớ, các biến này được gọi là con trỏ. Con trỏ
cũng là biến, có kích thước 4 byte, lưu 1 số là địa chỉ của các biến.

VI.1. Khai báo con trỏ

VI.2. Các toán tử trên con trỏ

VI.3. Con trỏ và mảng

VI.4. Con trỏ hàm

VII. STRUCT
VII.1. Định nghĩa Struct

17
Lập trình C++ Nguyễn Phú Quảng

VII.2. Khai báo biến

VII.3. Truy cập trường

VIII. Stream
VIII.1. File stream

VIII.1.1. Ghi file


Để ghi dữ liệu ra file text, bạn sử dụng kiểu dữ liệu ofstream (o = output, f = file). Kiểu dữ liệu này
yêu cầu bạn phải khai báo header là <fstream>.
iostream có các phương thức sau:
- f.open("tên file"): Mở file
- f.close(): Đóng file
#include <iostream>
#include <fstream>
using namespace std;

int main () {
ofstream myfile;
myfile.open ("c:/vidu.txt");
myfile << "Ghi thong tin ra file." << endl;
myfile << 123.43 << endl;
myfile.close();
return 0;
}

VIII.1.2. Đọc file


- Tương tự ghi file, đọc file text sử dụng kiểu dữ liệu ifstream (i = input, f = file).
#include <iostream>
#include <fstream>
#include <stdio.h>
using namespace std;

int main () {
ifstream myfile;
myfile.open ("c:/vidu.txt");
int a, b, c, d, e;
myfile >> a >> b >> c >> d >> e;
myfile.close();
cout << a << " " << b << " " << c << " " << d << " " << e << endl;

getchar();
return 0;
}

// Noi dung file


/*
3 4 5 6 7
*/

Ví dụ trên mới chỉ minh họa đọc các giá trị liên tiếp từ file. Để đọc các dòng, xâu ký tự... ifstream
có các phương thức sau:
- f.eof(): Kiểm tra xem đã hết file chưa? Trả về 0 nếu chưa hết file, 1 nếu đã hết file

18
Lập trình C++ Nguyễn Phú Quảng

- f.get(): Đọc 1 ký tự trên file: Sử dụng để đọc ký tự xuống dòng sau khi dùng các toán tử dẫn
hướng từ stream ra biến (tham khảo ví dụ). Trả về ký tự vừa đọc được.
- getline(f, str): Đọc một dòng từ file (vào xâu ký tự str)
- Sau khi đọc 1 dòng từ file, bạn có thể dùng istringstream để đọc dữ liệu từ luồng xâu ký tự
vào các biến. (tham khảo ví dụ)
/*
Đọc vào từ file readfile.txt với cấu trúc
Dòng 1: Số dòng của mảng 2 chiều
Các dòng tiếp theo, mỗi dòng là các phần tử của mảng 2 chiều (lưu ý, số phần tử
mỗi dòng có thể khác nhau, không có số ghi số lượng phần tử ở đầu)
*/
#include <iostream>
#include <fstream> // header cho đọc ghi file
#include <sstream> // header cho đọc ghi trên string
#include <stdio.h>
#include <vector>
using namespace std;

int main () {
int n;
typedef vector<int> dong; // Kiểu dòng (mảng số nguyên)
vector<dong> a; // Mảng 2 chiều (mảng các dòng)
string line;
int b;

ifstream f("readfile.txt"); // Mở file


f >> n; f.get(); // Đọc số dòng, f.get(): xuống dòng
a.resize(n); // Đặt số dòng cho mảng 2 chiều
for (int i=0; i<n; i++)
{
getline(f, line); // Đọc từ dòng vào xâu line
istringstream fs(line); // Tạo stream fs từ xâu line
while (fs >> b) a[i].push_back( b ); // Đọc từng số từ stream fs
}
f.close(); // Đóng file

for (int i=0; i<a.size(); i++) // In các phần tử của mảng


{
for (int j=0; j<a[i].size(); j++) cout << a[i][j] << " ";
cout << endl;
}

getchar();
}

// Noi dung file readfile.txt


/*
3
1 2 3 4
5 6 7 8 9
10 11
*/

VIII.2. String stream

VIII.2.1. istringstream

VIII.2.2. ostringstream

VIII.3. Ghi có định dạng


C hỗ trợ đọc ghi theo định dạng (ví dụ: số lượng chữ số sau dấu phẩy) bằng cách sử dụng các tham
số % trong phần định dạng.

19
Lập trình C++ Nguyễn Phú Quảng

C++ cũng cho phép làm tương tự với thư viện iomanip (viết tắt của manipulate). Để sử dụng thư
viện này, bạn khai báo
#include <iomanip>

VIII.3.1. Ghi với số lượng dấu phẩy xác định


Nếu muốn ghi số thực với 3 chữ số sau dấu phẩy, bạn sử dụng lệnh setprecision. Chú ý
setpresision(n) sẽ ghi ra n-1 chữ số sau dấu phẩy (tính cả dấu chấm là n chữ).
#include <iostream>
#include <stdlib.h>
#include <iomanip>
using namespace std;

int main () {
cout << setprecision(4) << (20.0/3) << endl; // Ghi ra 3 chữ số sau dấu phẩy 6.667
getchar();
}

VIII.3.2. Ghi với số lượng ký tự xác định


Bạn có thể sử dụng lệnh setw để xác định số lượng ký tự sẽ được ghi ra cho lần ghi tiếp theo. Căn lề
mặc định là bên phải. Nếu bạn muốn chuyển sang căn lề trái bạn sử dụng lệnh left. Chú ý: các lệnh
left và right sẽ căn lề trái và phải cho tất cả các lệnh viết sau đó.
#include <iostream>
#include <stdlib.h>
#include <iomanip>
using namespace std;

int main () {
cout << setw(10) << 123 << setw(10) << 456 << endl; // 123 456
cout << setw(10) << 123 << 456 << endl; // 123456
cout << left << setw(10) << 123 << setw(10) << 456 << endl; // 123 456

getchar();
}

VIII.3.3. Ghi với hệ cơ số 8, 10, 16


Bạn có thể sử dụng lệnh setbase để đưa các số với các hệ cơ số khác nhau ra ostream. Các hệ cơ số
có thể sử dụng là 10, 16 và 8. Chú ý, lệnh setbase có tác dụng đối với tất cả các số được viết ra sau
lệnh này. Lệnh showbase sẽ hiển thị các số cùng với cách ghi trong C
- 123: cách ghi bình thường của số 123 ở hệ thập phân
- 0x7b: cách ghi hệ cơ số 16 (hexa) của số 123
- 0173: cách ghi hệ cơ số 8 của số 123
Sau khi ghi với hệ cơ số khác nhau kèm theo showbase, bạn có thể sử dụng noshowbase để tắt chế
độ này đi.
#include <iostream>
#include <stdlib.h>
#include <iomanip>
using namespace std;

int main () {
cout << setbase(16) << 123 << endl; // 7b
cout << 123 << endl; // 7b
cout << setbase(8) << 123 << endl; // 173
cout << showbase;
cout << setbase(16) << 123 << endl; // 0x7b
cout << setbase(10) << 123 << endl; // 123
cout << setbase(8) << 123 << endl; // 0173
cout << noshowbase;
cout << setbase(16) << 123 << endl; // 7b
getchar();
}

20
Lập trình C++ Nguyễn Phú Quảng

IX. KIỂU NGƯỜI DÙNG ĐỊNH NGHĨA

X. CẤP PHÁT BỘ NHỚ ĐỘNG

XI. LỚP

XII. MẪU

XIII. THƯ VIỆN CHUẨN


C++ được đánh giá là ngôn ngữ mạnh vì tính mềm dẻo, gần gũi với ngôn ngữ máy. Ngoài ra, với
khả năng lập trình theo mẫu, C++ đã khiến ngôn ngữ lập trình trở thành khái quát, không cụ thể và
chi tiết như nhiều ngôn ngữ khác. Với khái niệm mẫu, những người lập trình đã đề ra khái niệm lập
trình khái quát (generic programming), C++ được cung cấp kèm với một bộ thư viện chuẩn STL
(Standard Template Library). Bộ thư viện này thực hiện toàn bộ các công việc vào ra dữ liệu
(iostream), quản lý mảng (vector), thực hiện hầu hết các tính năng của các cấu trúc dữ liệu cơ bản
(stack, queue, map, set...). Ngoài ra, STL còn bao gồm các thuật toán cơ bản: tìm min, max, tính
tổng, sắp xếp (với nhiều thuật toán khác nhau), thay thế các phần tử, tìm kiếm (tìm kiếm thường và
tìm kiếm nhị phân), trộn. Toàn bộ các tính năng nêu trên đều được cung cấp dưới dạng mẫu nên
việc lập trình luôn thể hiện tính khái quát hóa cao, làm việc dễ dàng trên tất cả các kiểu dữ liệu. Nếu
bạn lập trình giải quyết các bài toán thiên về thuật toán, STL là sự lựa chọn đúng đắn, hợp lý cho
phép bạn giải bài toán rất nhanh chóng, loại bỏ được nhiều công sức trong việc cài đặt các thuật
toán cơ bản.

XIII.1. Các khái niệm

XIII.1.1. Container
Container (thùng chứa) là khái niệm chỉ các đối tượng lưu trữ các đối tượng (giá trị) khác. Đối
tượng container sẽ cung cấp các phương thức để truy cập các thành phần (element) của nó. Cụ thể
hơn, tất cả các container đều chứa các bộ lặp (iterator) để cho phép duyệt qua toàn bộ các element
của container.
Các container được phân loại theo tính chất thứ tự của các element, bao gồm các loại sau:
- Forward container
- Reversible container
- Random Access container
Một số container hay được sử dụng nhất gồm vector (tương tự như mảng), vector là Random access
container (người dùng có thể truy cập trực tiếp bất cứ phần tử nào trên vector).

XIII.1.2. Iterator
Iterator (bộ lặp) là khái niệm sử dụng để chỉ một con trỏ trỏ đến các đối tượng. Mỗi container có
một loại iterator khác nhau để trỏ đến các thành phần của container. Để iterator lần lượt trỏ đến từng
thành phần trong container, chúng ta sử dụng các toán tử tăng (++).
21
Lập trình C++ Nguyễn Phú Quảng

Khái niệm iterator cho phép bạn làm việc một cách tổng quát với bất kỳ một kiểu dữ liệu nào, từ
những kiểu dữ liệu truy cập ngẫu nhiên (vector) đến các ánh xạ (map), tập hợp (set), danh sách (list)
cho đến những kiểu dữ liệu đơn giản như mảng.
Do đó, iterator gắn liền với tất cả các loại container, đây là khái niệm bạn cần nắm rất vững nếu
muốn làm việc tốt với STL.

XIII.1.3. Một số phương thức cơ bản của Container


Container gồm nhiều loại, nhưng tất cả các container đều gồm các phương thức cơ bản sau

Phương thức Mô tả
a.begin() Trả về iterator bắt đầu của container
a.end() Trả về iterator cuối cùng của container
a.size() Kích thước (số lượng element) của container
a.max_size() Kích thước tối đa của container
a.empty() Trả về giá trị != 0 nếu container trống (không có element nào), 0 nếu
ngược lại
a.swap(b) Hoán đổi 2 container với nhau (giống việc hoán đổi giá trị của 2 biến
kiểu số)

XIII.2. Kiểu vector

Kiểu vector có thể coi là kiểu mảng trong lập trình C truyền thống. Mảng là tập hợp các giá trị cùng
kiểu, được sắp xếp nối tiếp nhau. Các phần tử của mảng có thể được truy cập ngẫu nhiên qua chỉ số.
Vấn đề đặt ra: Nếu vector là mảng thì tại sao lại phải sử dụng vector khi bạn đã quá quen thuộc với
mảng? Chúng tôi xin phân tích một số nhược điểm sau của mảng:
- Nếu bạn sử dụng mảng tĩnh: Mảng này luôn được khai báo với kích thước tối đa mà bạn có
thể dùng đến → tốn nhiều vùng nhớ thừa
- Nếu bạn sử dụng mảng động: Bạn phải xin cấp phát bộ nhớ, làm việc với con trỏ. Con trỏ là
khái niệm hay trong C, C++, nhưng nó là nguyên nhân của rất nhiều rắc rối trong lập trình.
- Nhược điểm quan trọng nhất: Nếu bạn sử dụng mảng vượt chỉ số vượt quá kích thước đã
khai báo, C++ sẽ không thông báo lỗi, điều này dẫn đến lỗi dây chuyền do các lệnh lỗi đã
tác động đến các biến khác trong chương trình (trong Pascal bạn có thể kiểm tra tràn chỉ số
mảng bằng dẫn biên dịch range check).
vector là một container cung cấp khả năng sử dụng mảng mềm dẻo, có kiểm soát range check khi
cần thiết, với kích thước tùy ý (mà không cần phải sử dụng con trỏ). Ngoài ra vector cho phép bạn
chèn thêm hoặc xóa đi một số phần tử chỉ bằng 1 lệnh (không phải sử dụng vòng lặp như đối với
mảng).
Để sử dụng vector, bạn phải khai báo file header với cú pháp
#include <vector>

Cú pháp khai báo vector như sau


vector<Kiểu> V;

22
Lập trình C++ Nguyễn Phú Quảng

XIII.2.1. Ví dụ 1
Trong đó, bạn có thể khai báo kiểu là bất cứ kiểu biến nào. Để hiểu rõ hơn về vector, bạn hãy theo
dõi ví dụ sau
#include <iostream> // Thư viện iostream phục vụ ghi dữ liệu ra màn hình
#include <vector> // Thư viện vector, sử dụng kiểu vector
#include <stdio.h> // Thư viện stdio (sử dụng hàm getchar() để dừng ct)
using namespace std; // Sử dụng namespace std
int main() {
vector<int> V; // V kiểu vector số nguyên (sử dụng giống mảng int)
V.resize(3); // Đặt kích thước của biến V là 3 (giống mảng 3 pt)
V[0] = 5; // Gán giá trị cho các phần tử của biến V
V[1] = 6; // Sử dụng dấu móc [] hoàn toàn giống với mảng
V[2] = 7;
for (int i=0; i<V.size(); i++) { // Ghi giá trị các phần tử của V ra màn hình
cout << V[i] << endl; // Nếu sử dụng mảng, bạn phải có biến lưu kích
} // thước, còn vector có hàm cho biến kích thước
getchar(); // Dừng chương trình để xem kết quả
}

Ví dụ trên cho bạn thấy việc sử dụng vector rất đơn giản, hoàn toàn giống với mảng nhưng bộ nhớ
được quản lý tự động, bạn không phải quan tâm đến giải phóng các vùng bộ nhớ đã xin cấp phát.

XIII.2.2. Ví dụ 2
Tuy nhiên, việc sử dụng vector không chỉ dừng lại ở những ưu điểm trên, chúng ta hãy nghiên cứu
ví dụ tiếp theo để thấy được các ưu điểm khác của vector.
#include <iostream>
#include <vector>
#include <stdio.h>
using namespace std;
int main() {
int i;
vector<int> V;
for (i=0; i<5; i++) // Lặp 5 lần, mỗi lần đưa thêm 1 số vào vector
V.push_back(i); // Như vậy, vector có thể được sử dụng như stack

cout << endl << "Mang truoc khi insert" << endl;
for (i=0; i<V.size(); i++) // Ghi lại nội dung của mảng ra màn hình
cout << V[i] << endl;

V.insert( V.begin()+3, 100 ); // Chèn vào vị trí thứ 3 của vector giá trị 100

cout << endl << "Mang sau khi insert" << endl;
for (i=0; i<V.size(); i++) // In nội dung của vector sau khi chèn
cout << V[i] << endl; // vector sẽ có 6 phần tử, phần tử 100 chèn vào
// Vị trí thứ 3 của vector
V.erase( V.begin()+3 ); // Xóa phần tử vừa chèn vào đi
cout << endl << "Mang sau khi erase" << endl;
for (i=0; i<V.size(); i++) // In nội dung của vector sau khi xóa
cout << V[i] << endl; // Vector lại giống như lúc mới khởi tạo

getchar();
}

Với ví dụ trên, bạn có thể thấy những ưu điểm sau của vector
- Bạn có thể sử dụng vector như 1 stack. Thao tác push_back() của vector cho phép bạn thêm
1 giá trị mới vào vector. Thao tác này sẽ tự động tăng kích thước của vector, do đó bạn sẽ
không phải quan tâm đến quản lý kích thước của vector.
- vector cho phép bạn chèn thêm 1 phần tử vào các vị trí bất kỳ của vector, các phần tử nằm
sau vị trí này sẽ được dịch đi 1 vị trí đển lấy khoảng trống cho phần tử mới. Thao tác chèn
phần tử có thể thực hiện dễ dàng đối với mảng thường bằng cách sử dụng vòng lặp để
chuyển dịch các phần tử. Tuy nhiên, để chương trình hoạt động đúng trong mọi trường hợp,
bạn phải tính đến nhiều trường hợp: dung lượng hiện tại của mảng có đủ không? Nếu không
đủ phải xin cấp phát thêm... Nếu sử dụng vector, các bạn sẽ dễ dàng thực hiện chức năng
này chỉ bằng 1 lệnh và luôn đảm bảo rằng lệnh này sẽ hoạt động chính xác.

23
Lập trình C++ Nguyễn Phú Quảng

- Tương tự với thao tác xóa một phần tử khỏi vector, bạn cũng chỉ cần sử dụng 1 lệnh.

XIII.2.3. Ví dụ 3
Chúng ta tiếp tục theo dõi ví dụ tiếp theo để tìm hiểu tiếp các khả năng của vector
#include <iostream>
#include <vector>
#include <stdio.h>
using namespace std;
int main() {
// Mảng các xâu ký tự (dữ liệu test)
char *Chao[] = {"Xin", "chao", "tat", "ca", "cac", "ban"};
int n = sizeof(Chao)/sizeof(*Chao); // Tính kích thước của mảng
// Cách tính này sử dụng một mẹo:
// Kích thước của toàn bộ mảng chia cho
// Kích thước của 1 phần tử của mảng
vector<char *> V; // Vector chứa các xâu ký tự
// (cũng giống mảng, vector có thể chứa
// mọi loại giá trị)
int i;
cout << endl << "vector truoc khi xoa" << endl;
for (i=0; i<n; i++) {
V.push_back(Chao[i]); // Đưa các giá trị từ mảng vào vector
}

cout << endl << "vector sau khi xoa" << endl;
for (i=0; i<V.size(); i++) {
cout << V[i] << endl; // In ra các giá trị của vector
}

V.erase(V.begin()+2, V.begin()+2+3); // Xóa vector từ thành phần 2 đến


// thành phần thứ 3 sau thành phần 2
// Tức là xóa 3 thành phần kể từ
// thành phần 2

for (i=0; i<V.size(); i++) {


cout << V[i] << endl; // Lại in vector sau khi xóa ra màn hình
}

cout << endl; // In số lượng thành phần của vector


cout << "Vector truoc khi clear co " << V.size() << " thanh phan" << endl;
V.clear(); // Xóa toàn bộ các thành phần
cout << "Vector sau khi clear co " << V.size() << " thanh phan" << endl;
getchar(); // Lại in ra số lượng thành phần
}

Kết quả của đoạn chương trình trên như sau:


vector truoc khi xoa
Xin
chao
tat
ca
cac
ban

vector sau khi xoa


Xin
chao
ban

Vector truoc khi clear co 3 thanh phan


Vector sau khi clear co 0 thanh phan

Như vậy, chúng ta biết thêm một số chức năng của vector
- Vector không chỉ làm việc với số mà còn có thể làm việc với tất cả các dạng dữ liệu khác
(đây là đặc điểm của lập trình khái quát với template)
- Vector có thể xóa nhiều thành phần một lúc
- Vector có thế xóa tất cả các thành phần bằng phương thức clear().

24
Lập trình C++ Nguyễn Phú Quảng

XIII.2.4. Ví dụ 4
Còn một vấn đề đã được đề cập đến từ khi chúng tôi giới thiệu về vector, đó là khả năng kiểm tra
tràn chỉ số mảng (range check), để biết về khả năng này, chúng ta lại tiếp tục với một ví dụ mới
#include <iostream>
#include <vector>
#include <stdio.h>
using namespace std;
int main() {
try { // sử dụng try...catch để bẫy lỗi
vector<long> V(3, 10); // Khởi tạo vector gồm 3 thành phần
// Tất cả gán giá trị 10
cout << "V[0]=" << V[0] << endl; // Đưa thành phần 0 ra màn hình
cout << "V[1]=" << V[1] << endl; // Đưa thành phần 1 ra màn hình
cout << "V[2]=" << V[2] << endl; // Đưa thành phần 2 ra màn hình
cout << "V[3]=" << V[3] << endl; // Thành phần 3 (lệnh này hoạt động không
// đúng vì V chỉ có 3 thành phần 0,1,2
cout << "V[4]=" << V[4] << endl; // Thành phần 4 (càng không đúng)
// Nhưng 2 lệnh trên đều không gây lỗi

cout << "V[0]=" << V.at(0) << endl; // Không sử dụng [], dùng phương thức at
cout << "V[1]=" << V.at(1) << endl; // Thành phần 1, OK
cout << "V[2]=" << V.at(2) << endl; // Thành phần 2, OK
cout << "V[3]=" << V.at(3) << endl; // Thành phần 3: Lỗi, chương trình dừng
cout << "V[4]=" << V.at(4) << endl;
getchar();
} catch (exception &e) {
cout << "Co loi xay ra" << endl; // Dòng thông báo lỗi (nếu chương trình
getchar(); // phát sinh lỗi khi chạy
}
}

Kết quả của đoạn chương trình trên như sau


V[0]=10 <-- OK
V[1]=10 <-- OK
V[2]=10 <-- OK
V[3]=0 <-- Sai, ko có giá trị này
V[4]=196692 <-- Sai
V[0]=10 <-- OK
V[1]=10 <-- OK
V[2]=10 <-- OK
Co loi xay ra <-- Thông báo lỗi khi sử dụng phần tử không hợp lệ

Trong ví dụ này, chúng ta lại có thêm một số kinh nghiệm sau


- Có thể sử dụng cú pháp vector<kiểu> tên_vector(số_phần_tử, giá_trị_tất_cả_phần_tử) để
khởi tạo một vector có số lượng phần tử biết trước và tất cả các phần tử đều có cùng 1 giá trị
- Nếu sử dụng cú pháp biến_vector[chỉ_số], chương trình sẽ không tạo ra lỗi khi sử dụng chỉ
số mảng nằm ngoài vùng hợp lệ (giống như mảng thường). Trong ví dụ, chúng ta mới chỉ
lấy giá trị phần tử với chỉ số không hợp lệ, trường hợp này chỉ cho kết quả sai. Nhưng nếu
chúng ta gán giá trị cho phần tử không hợp lệ này, hậu quả sẽ nghiêm trọng hơn nhiều vì
thao tác đó sẽ làm hỏng các giá trị khác trên bộ nhớ.
- Phương thức at(chỉ_số) có tác dụng tương tự như dùng ký hiệu [], nhưng có một sự khác
biệt là thao tác này có kiểm tra chỉ số hợp lệ. Minh chứng cho nhận xét này trong ví dụ khi
chương trình chạy đến vị trí lệnh V.at(3), lệnh này không cho ra kết quả mà tạo thành thông
báo lỗi.

XIII.2.5. Ví dụ 5
#include <iostream>
#include <vector>
#include <stdio.h>
using namespace std;
int main() {
typedef vector<long> vlong;
vector<vlong> V(3, vlong(3) );
int i, j;
for (i=0; i<3; i++)

25
Lập trình C++ Nguyễn Phú Quảng

for (j=0; j<3; j++) V[i][j] = rand() % 10;

for (i=0; i<3; i++)


{
for (j=0; j<3; j++) cout << V[i][j] << " ";
cout << endl;
}
getchar();
}

Ví dụ này minh họa việc sử dụng mảng 2 chiều, thực chất đây là một vector của vector. Mảng 2
chiều sử dụng biện pháp này có thể có kích thước khác nhau giữa các dòng (ví dụ mảng 2 chiều là
nửa trên của ma trận)

XIII.2.6. Sử dụng vector có kiểm tra chỉ số


#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>
#include <stdlib.h>

using namespace std;

template <typename T>


class mvector : public vector<T> { // Tao lop mvector (giong vector, nhung co kiem tra chi
so)
public:
T t;
T& operator [] (int i) {
try {
return vector<T>::at(i);
} catch (exception &e) {
cout << "Tran chi so " << i << " " << vector<T>::size()-1 << endl;
getchar();
return t;
}
}
};

mvector< int > chiso;


int n;

int main()
{
chiso.resize( 5 );
chiso[5] = 10;
}

/*
5
Duc
Anh
Van
Thu
Ba
*/

XIII.3. Kiểu string


Khi mới học C, chắc các bạn đều rất bối rối khi làm việc với xâu ký tự, việc sử dụng con trỏ lưu xâu
ký tự rất phức tạp, dễ gây lỗi khiến nhiều người so sánh nó với xâu ký tự trong Pascal.
Do đó, STL cung cấp kiểu string (xâu ký tự), giúp các bạn tránh khỏi hoàn toàn các phiền phức nêu
trên. string thực chất là một vector<char> có bổ sung thêm một số phương thức và thuộc tính, do
đó, nó có toàn bộ các tính chất của 1 vector.
String cũng có các hàm size, push_back, toán tử [] giống vector.
Để nối string, bạn có thể sử dụng toán tử +, += để nối các xâu ký tự.

26
Lập trình C++ Nguyễn Phú Quảng

XIII.3.1. Hàm chèn xâu (insert)


// inserting into a string
#include <iostream>
#include <string>
#include <stdlib.h>

using namespace std;

int main ()
{
string str="day la .. xau thu";
string istr = "them";
str.insert(8, istr);
cout << str << endl;
getchar();
}

XIII.3.2. Hàm xóa xâu (erase)


// inserting into a string
#include <iostream>
#include <string>
#include <stdlib.h>

using namespace std;

int main ()
{
string str="day cung la xau thu";
str.erase(0, 3); // " cung la xau thu"
cout << str << endl;
str.erase(6, 2);
cout << str << endl; // " cung xau thu"
getchar();
}

XIII.3.3. Hàm thay thế xâu ký tự (replace)


// inserting into a string
#include <iostream>
#include <string>
#include <stdlib.h>

using namespace std;

int main ()
{
string str="con cho la con cho con. Con meo ko phai la con cho";
str.replace(4, 3, "CHO"); // "con CHO la con cho con. Con meo ko phai la con cho";
cout << str << endl;
getchar();
}

XIII.3.4. Hàm lấy xâu con (string)


// inserting into a string
#include <iostream>
#include <string>
#include <stdlib.h>

using namespace std;

int main ()
{
string str="ConCho chay qua rao";
string str2 = string(str.begin()+0, str.begin()+3); // "Con"
cout << str2 << endl;
str2 = string(str.begin()+7, str.begin()+11); // "chay"
cout << str2 << endl;
getchar();
}

27
Lập trình C++ Nguyễn Phú Quảng

XIII.3.5. Hàm tìm kiếm xuôi (find)


// inserting into a string
#include <iostream>
#include <string>
#include <stdlib.h>

using namespace std;

int main ()
{
string str="ConCho chay qua rao";
cout << str.find("chay") << endl; // 7
cout << (int)str.find("Chay") << endl; // -1
getchar();
}

XIII.3.6. Hàm tìm kiếm ngược (rfind)


// inserting into a string
#include <iostream>
#include <string>
#include <stdlib.h>

using namespace std;

int main ()
{
string str="ConCho chay qua chay qua rao";
cout << str.find("chay") << endl; // 7
cout << (int)str.rfind("chay") << endl; // 16
getchar();
}

XIII.3.7. Ví dụ 1
Để làm việc với string của STL, chúng ta cùng xem xét ví dụ sau
#include <iostream>
#include <stdio.h>
#include <string>
using namespace std;
int main() {
string s = "Hello string"; // Khai báo biến kiểu string
cout << "Noi dung string: " << s << endl; // In nôi dung string ra màn hình
cout << "Chieu dai cua string: " << s.size() << endl;
// Chiều dài
cout << "Ky tu 0: " << s[0] << endl; // In ký tự đầu tiên của xâu
cout << "Ky tu 1: " << s[1] << endl; // In ký tự thứ 2
cout << "Ky tu 2: " << s[2] << endl; // In ký tự thứ 3
s += " co the noi string"; // Nối xâu ký tự
cout << "Noi dung string sau khi noi: " << s << endl;
s.erase(s.begin()+5, s.begin()+5+7); // Xóa 1 đoạn trên xâu ký tự
cout << "Noi dung string sau khi xoa: " << s << endl;
getchar();
}

Trong ví dụ này, có thể nhận thấy những điểm sau


- Xâu ký tự có các tính chất của một container: Phương thức size() sử dụng để lấy số lượng
phần tử, dấu [] dùng để truy cập đến các element, phương thức erase() sử dụng để xóa 1 số
các phần tử liên tiếp nhau
- Xâu kí tự có thể cộng với nhau bằng các toán tử +, +=

XIII.3.8. Ví dụ 2: Tách xâu ký tự


Trong việc xử lý xâu ký tự, không thể thiếu được các thao tác tách xâu ký tự thành nhiều xâu ký tự
con thông qua các ký tự ngăn cách. Các hàm này có sẵn trong các ngôn ngữ khác như Visual Basic,
Java... Với STL, các bạn có thể dễ dàng tự xây dựng một hàm với chức năng tương tự
#include <iostream>
#include <vector>

28
Lập trình C++ Nguyễn Phú Quảng

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

using namespace std;


int main() {
string S = "Xin chao tat ca cac ban"; // Khởi tạo giá trị của xâu
string::iterator t, t2; // Các biến lặp
vector<string> split; // Mảng các xâu (lưu kết quả tách)
for (t=S.begin(); t<S.end();) { // Lặp từ vị trí bắt đầu
t2=find(t, S.end(), ' '); // Tìm ký tự space ' ' đầu tiên
// kể từ vị trí t
if (t!=t2) split.push_back(string(t, t2)); // Lấy xâu ký tự giữa 2 vị trí
t = t2+1; // Chuyển sang vị trí sau
}
for (int i=0; i<split.size(); i++) {
cout << split[i] << endl; // In mảng các xâu ký tự
}
getchar();
return 0;
}

Kết quả của chương trình như sau


Xin
chao
tat
ca
cac
ban

Đoạn chương trình sử dụng các kỹ thuật sau


- Phương thức find(vị_trí_đầu, vị_trí_cuối, ký_tự_tìm) dùng để tìm vị trí đầu tiên của
ký_tự_tìm bắt đầu từ vị_trí_đầu. Hàm này trả về vị trí của ký tự tìm được (nếu tìm thấy)
hoặc vị_trí_cuối (nếu không tìm thấy)
- string có thể khởi tạo từ một đoạn ký tự con của một xâu ký tự khác với cú pháp
string(vị_trí_đầu, vị_trí_cuối)
- Đoạn chương trình thực hiện tách các xâu ký tự kể cả trong trường hợp có nhiều ký tự cách
nằm liên tiếp nhau.

XIII.4. Kiểu list (danh sách)

list là kiểu danh sách liên kết 2 chiều (hãy liên hệ với các kiến thức của môn Cấu trúc dữ liệu và
Giải thuật). Khác biệt của kiểu danh sách liên kết với kiểu mảng ở chỗ:
- Mảng cần nhiều thời gian để thêm hoặc xóa phần tử (dịch chuyển các phần tử phía sau sang
phải hoặc sang trái), còn danh sách liên kết thực hiện các thao tác thêm, xóa với thời gian
không đáng kể (chỉ cần cắt đứt liên kết cũ, tạo ra liên kết mới).
- Mảng cho phép truy xuất các phần tử ngay lập tức thông qua chỉ số (truy cập ngẫu nhiên -
random access), còn danh sách liên kết phải truy cập các phần tử theo thứ tự tuần tự.

29
Lập trình C++ Nguyễn Phú Quảng

Để sử dụng list, bạn phải khai báo file header list với cú pháp sau
#include <list>

Các phương thức cơ bản của list

Phương thức Mô tả

c.size() Số lượng phần tử của list

c. empty () Trả về 1 nếu danh sách là trống, 0 nếu ngược lại

c.max_size() Trả về số lượng phần tử tối đa của list

c1 == c2 Trả về 1 nếu hai danh sách giống nhau

c1 != c2 Trả về 1 nếu hai danh sách khác nhau (nhanh hơn !(c1==c2))

c1 < c2 Trả về 1 nếu danh sách c1 < danh sách c2

c.front() Trả về phần tử đầu tiên của list

c.back() Trả về phần tử cuối cùng của list

c.begin() Trả về phần tử lặp đầu tiên của danh sách

c.end() Trả về phần tử lặp cuối cùng của danh sách

c.sort() Sắp xếp danh sách với toán tử <

c1.merge(c2) Trộn danh sách c1 với danh sách c2 (với điều kiẹn cả 2 danh sách đều đã
sắp xếp với toán tử <). Danh sách được trộn cũng sẽ được sắp xếp với
toán tử <. Đây là thuật toán trộn 2 đường với 2 danh sách đã được sắp
xếp (độ phức tạp là tuyến tính)

XIII.4.1. Ví dụ 1: Duyệt list theo 2 chiều


#include <iostream>
#include <algorithm>
#include <stdlib.h>
#include <list>

using namespace std;


int main() {
int A[] = {3,2,3,1,2,3,5,3};
int n = sizeof(A)/sizeof(*A);
list<int> V;
for (int i=0; i<n; i++) {
V.push_back(A[i]);
}

list<int>::iterator vi;
cout << endl << "Danh sach theo chieu xuoi" << endl;
for (vi=V.begin(); vi!=V.end(); vi++) {
cout << *vi << endl;
}

list<int>::reverse_iterator rvi;
cout << endl << "Danh sach theo chieu nguoc" << endl;
for (rvi=V.rbegin(); rvi!=V.rend(); rvi++) {
cout << *rvi << endl;
}

getchar();
}

30
Lập trình C++ Nguyễn Phú Quảng

XIII.4.2. Ví dụ 2: Chèn thêm phần tử, xóa phần tử


#include <iostream>
#include <algorithm>
#include <stdlib.h>
#include <list>

using namespace std;


void printint(const int &i) {
cout << i << endl;
}

int main() {
int A[] = {1,2,3,4,5};
int n = sizeof(A) / sizeof(*A);
list<int> V(A, A+n);
list<int>::iterator i;

cout << endl << "Danh sach ban dau" << endl;
for_each( V.begin(), V.end(), printint );

cout << endl << "Danh sach sau khi chen vao cuoi" << endl;
V.push_back(100);
for_each( V.begin(), V.end(), printint );

cout << endl << "Danh sach sau khi chen vao dau" << endl;
V.push_front(200);
for_each( V.begin(), V.end(), printint );

getchar();
}

XIII.4.3. Ví dụ 3: Tách list

XIII.4.4. Ví dụ 4: Sắp xếp list


#include <iostream>
#include <algorithm>
#include <stdlib.h>
#include <list>

using namespace std;


int main() {
int A[] = {3,2,3,1,2,3,5,3};
int n = sizeof(A)/sizeof(*A);
list<int> V(A, A+n);

V.sort();

list<int>::iterator vi;
cout << endl << "Danh sach sau khi sap xep" << endl;
for (vi=V.begin(); vi!=V.end(); vi++) {
cout << *vi << endl;
}

getchar();
}

XIII.4.5. Ví dụ 5: Trộn list


#include <iostream>
#include <algorithm>
#include <stdlib.h>
#include <list>

using namespace std;


int main() {
int A[] = {5,1,3,9,7};
int B[] = {8,6,4,2};
int na = sizeof(A)/sizeof(*A);
int nb = sizeof(B)/sizeof(*B);
list<int> Va(A, A+na), Vb(B, B+nb);

31
Lập trình C++ Nguyễn Phú Quảng

list<int>::iterator vi;

cout << endl << "Danh sach A sau khi sap xep" << endl;
Va.sort();
for (vi=Va.begin(); vi!=Va.end(); vi++) {
cout << *vi << endl;
}

cout << endl << "Danh sach B sau khi sap xep" << endl;
Vb.sort();
for (vi=Vb.begin(); vi!=Vb.end(); vi++) {
cout << *vi << endl;
}

cout << endl << "Danh sach A sau khi tron voi danh sach B" << endl;
Va.merge(Vb);
for (vi=Va.begin(); vi!=Va.end(); vi++) {
cout << *vi << endl;
}

getchar();
}

XIII.5. Kiểu set (tập hợp)


#include <iostream>
#include <set>
#include <stdio.h>

using namespace std;


int main() {
set<int> A;
A.insert(5);
A.insert(7);
A.insert(10);
A.insert(15);

cout << (A.find(3)!=A.end()) << endl;


cout << (A.find(5)!=A.end()) << endl;
cout << (A.find(7)!=A.end()) << endl;
cout << (A.find(10)!=A.end()) << endl;
getchar();
}

XIII.6. Kiểu map (ánh xạ)

XIII.6.1. Giới thiệu

Kiểu map cho phép bạn lấy tương ứng giữa một giá trị với một giá trị khác, hai giá trị này tạo thành
một cặp giá trị. Trong đó giá trị đầu của cặp là khóa (key), key là duy nhất (không có 2 key cùng
xuất hiện trong 1 map). Do đó, từ key bạn có thể tìm được giá trị tương ứng với key trong cặp.

32
Lập trình C++ Nguyễn Phú Quảng

Nếu thực hiện tìm kiếm bình thường trên một mảng N phần tử, bạn phải mất trung bình N/2 phép
tìm kiếm. Dữ liệu của các key được tổ chức dưới dạng cây heap (lá trái nhỏ hơn gốc, lá phải lớn
hơn gốc) nên việc tìm kiếm các cặp theo khóa rất nhanh, thời gian trung bình là log 2N (vì độ sâu
của cây là log2N).
Nếu bạn biết tận dụng, map có thể ứng dụng để giải rất nhiều dạng bài toán khác nhau. Trong phần
này, chúng tôi xin giới thiệu một số ứng dụng của map
- Sử dụng làm từ điển: Ví dụ: tra cứu thông tin của sinh viên theo mã số
- Đếm số lượng của một thành phần (cho một loạt các phần tử, tìm xem có bao nhiêu phần tử
và mỗi phần tử xuất hiện bao nhiêu lần)
- Sử dụng để lưu ma trận thưa (ma trận thưa là ma trận gồm rất nhiều phần tử bằng 0). Trong
các bài thi Tin học, nhiều trường hợp các bạn gặp phải những ma trận có kích thước rất lớn
(1 triệu x 1 triệu chẳng hạn) nhưng số lượng phần tử khác 0 chỉ khoảng vài trăm nghìn.
Trường hợp này sử dụng map để lưu ma trận là rất thích hợp.

XIII.6.2. Các phương thức


Phương thức Mô tả

c.size() Số lượng phần tử của list

c1 == c2 Trả về 1 nếu hai map giống nhau

c1 != c2 Trả về 1 nếu hai map khác nhau (nhanh hơn !(c1==c2))

c1 < c2 Trả về 1 nếu map c1 < map c2

c.begin() Trả về phần tử lặp đầu tiên của danh sách

c.end() Trả về phần tử lặp cuối cùng của danh sách

c[k] Trả về giá trị tương ứng với khóa k (chú ý: nếu chưa có cặp nào có khóa
k, map sẽ tự tạo một cặp mới có khóa k và gán giá trị mặc định. Đối với
kiểu số, giá trị mặc định là 0)
c.find(k) Trả về iterator tương ứng với khóa k, nếu không tìm thấy thì trả về end()

XIII.6.3. Ví dụ: sử dụng map


#include <iostream>
#include <stdlib.h>
#include <map>

using namespace std;


int main() {
int a[] = {2,4,1,3,5,3,5,3,2,1,2,3,4,4,3,1,2};
int n = sizeof(a)/sizeof(a[0]);

map<int, int> m;
for (int i=0; i<n; i++) {
m[a[i]] ++;
}

map<int, int>::iterator j;
for (j=m.begin(); j!=m.end(); j++)
cout << "Phan tu " << j->first << " xuat hien "
<< j->second << " lan " << endl;
getchar();
}

33
Lập trình C++ Nguyễn Phú Quảng

XIII.6.4. Ví dụ: từ điển


#include <iostream>
#include <stdio.h>
#include <map>

using namespace std;

class sv {
public:
int maso;
string ten;
string lop;

void print() {
cout << "****************************************" << endl
<< "Ma so: " << maso << endl
<< "Ho ten: " << ten << endl
<< "Lop: " << lop << endl;
}
};

int main () {
sv a[] = {
{1342, "Mai", "48th"},
{43212, "Lan", "47th"},
{33133, "Cuc", "47th"},
{43321, "Truc", "45th"},
{1234, "Dao", "42th"}};
int n = sizeof(a)/sizeof(a[0]);
map<int, sv> m;
for (int i=0; i<n; i++) m[a[i].maso] = a[i];

m[43212].print();
m[1234].print();

getchar();
return 0;
}

XIII.6.5. Ví dụ: đếm


#include <iostream>
#include <stdlib.h>
#include <map>

using namespace std;


int main() {
string a[] = {"chuoi", "na", "oi", "tao", "chuoi", "oi", "na", "tao", "oi"};
int n = sizeof(a)/sizeof(a[0]);

map<string, int> m;
for (int i=0; i<n; i++) {
m[a[i]] ++;
}

map<string, int>::iterator j;
for (j=m.begin(); j!=m.end(); j++)
cout << "Xau ky tu '" << j->first << "' xuat hien "
<< j->second << " lan " << endl;
getchar();
}

XIII.6.6. Ví dụ: ma trận thưa

XIII.7. Kiểu hash_map (ánh xạ dùng mảng băm)


Kiểu map cho phép ánh xạ giữa tập khóa và tập giá trị với thời gian O(logN). Map sử dụng cấu trúc
dữ liệu kiểu cây nên độ phức tạp là log2N. Tuy nhiên, bạn có thể sử dụng cấu trúc dữ liệu mạnh hơn
là mảng băm (hash_map). Hash_map có thể tìm kiếm theo khóa với độ phức tạp O(1).

34
Lập trình C++ Nguyễn Phú Quảng

XIII.8. Kiểu hash_set (tập hợp)


Trong lập trình giải các bài toán Tin học bằng Pascal, nhiều bạn đã quên mất một kiểu dữ liệu rất
mạnh là kiểu tập hợp (kiểu dữ liệu này cho phép kiểm tra xem một phần tử có nằm trong tâp hợp
hay không, tìm phần giao, hợp, hiệu của hai tập hợp với nhau). Pascal là một trong ít ngôn ngữ có
kiểu dữ liệu nguyên thủy là tập hợp. C và C++ không có kiểu dữ liệu này. Tuy nhiên, bạn có thể sử
dụng kiểu dữ liệu tập hợp trong STL. Tập hợp trong STL còn mạnh hơn tập hợp trong Pascal rất
nhiều vì nó hỗ trợ tất cả các kiểu dữ liệu (tập hợp trong Pascal chỉ hỗ trợ dạng số) với số lượng phần
tử không hạn chế (tập hợp của Pascal chỉ được tối đa 256 phần tử).

XIII.9. Thuật toán (Algorithm)


Như đã giới thiệu trong các phần trước, STL cung cấp các thuật toán cơ bản nhằm mục đích giúp
bạn không phải code lại những giải thuật quá cơ bản như (sắp xếp, thay thế, tìm kiếm...). Các công
cụ này không những giúp bạn rút ngắn thời gian lập trình mà còn cả thời gian gỡ rối khi thuật toán
cơ bản được cài đặt không chính xác. Ngoài ra, với STL Algorithm, bạn có nhiều lựa chọn cho
những thuật toán cơ bản. Ví dụ, với thuật toán sắp xếp, bạn có thể lựa chọn giữa thuật toán sắp xếp
nhanh (quicksort) cho kết quả rất nhanh với độ phức tạp NlogN trong đa số các trường hợp, nhưng
lại có độ phức tạp N*N trong trường hợp xấu nhất và thuật toán sắp xếp vung đống (heapsort) chạy
chậm hơn quicksort nhưng có độ phức tạp trong mọi trường hợp là NlogN.
Chú ý rằng các thuật toán của STL Algorithm có thể áp dụng cho mọi kiểu iterator, kể cả con trỏ
thường (không phải là iterator của STL). Như vậy, các thuật toán sắp xếp, tìm kiếm, thay thế không
những áp dụng được cho các kiểu vector, list... mà còn có thể áp dụng cho mảng thông thường.
Để khai báo sử dụng STL algorithm, các bạn phải inlucde file header algorithm
#include <algorithm>

Do thư viện <algorithm> gồm rất nhiều hàm khác nhau, người ta phân thành các nhóm hàm sau:
- Nhóm các hàm không thay đổi giá trị của container
- Nhóm các hàm thay đổi giá trị của container
- Nhóm các hàm sắp xếp
- Nhóm các hàm trên danh sách được sắp xếp
- Nhóm các hàm trộn
- Nhóm các làm trên heap
- Nhóm các hàm tìm min/max

XIII.9.1. Nhóm các hàm không thay đổi giá trị của container

a. Các thuật toán tìm kiếm


#include <iostream>
#include <stdio.h>
#include <algorithm>

using namespace std;


int main() {
int A[] = {3,4,2,6,3,1,2,3,2,3,4,5,6,4,3,2,1};
int N = sizeof(A) / sizeof(*A);
int first = find(A, A+N, 1) - A;
cout << "So thu tu cua phan tu dau tien = 1: " << first << endl;
getchar();
return 0;
}

#include <iostream>

35
Lập trình C++ Nguyễn Phú Quảng

#include <stdio.h>
#include <algorithm>

using namespace std;


int main() {
int A[] = {3,4,2,6,3,1,2,3,2,3,4,5,6,4,3,2,1};
int B[] = {1,2,3};
int NA = sizeof(A) / sizeof(*A);
int NB = sizeof(B) / sizeof(*B);
int vitri = search(A, A+NA, B, B+NB) - A;
cout << "Vi tri dau tien xuat hien B trong A: " << vitri << endl;
getchar();
return 0;
}

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string>

using namespace std;


int main() {
string A = "Xin chao tat ca moi nguoi!!!!!";
string B = "chao";
string C = "Xin";
int vitri = search(A.begin(), A.end(), B.begin(), B.end()) - A.begin();
cout << "Vi tri dau tien xuat hien B trong A: " << vitri << endl;

vitri = search(A.begin(), A.end(), C.begin(), C.end()) - A.begin();


cout << "Vi tri dau tien xuat hien C trong A: " << vitri << endl;
getchar();
return 0;
}

b. Các thuật toán đếm


#include <iostream>
#include <stdio.h>
#include <vector>
#include <algorithm>

using namespace std;


int main() {
int A[] = {3,4,2,6,3,1,2,3,2,3,4,5,6,4,3,2,1};
int N = sizeof(A) / sizeof(*A);
int i;

cout << "So luong so 1 trong mang: " << count(A, A+N, 1) << endl;
cout << "So luong so 3 trong mang: " << count(A, A+N, 3) << endl;
cout << "So nho nhat trong mang: " << *min_element(A, A+N) << endl;
cout << "So lon nhat trong mang: " << *max_element(A, A+N) << endl;

getchar();
return 0;
}

XIII.9.2. Nhóm các hàm thay đổi giá trị của container

a. Gán giá trị 1 vùng (fill)


Bạn có thể sử dụng lệnh fill của thư viện <algorithm> để tô một vùng giá trị của 1 container
(thường là 1 mảng, 1 vector)
// fill algorithm example
#include <iostream>
#include <algorithm>
#include <vector>
#include <stdlib.h>
using namespace std;

int main () {
vector<int> myvector (8); // myvector: 0 0 0 0 0 0 0 0

fill (myvector.begin(),myvector.begin()+4,5); // myvector: 5 5 5 5 0 0 0 0

36
Lập trình C++ Nguyễn Phú Quảng

fill (myvector.begin()+3,myvector.end()-2,8); // myvector: 5 5 5 8 8 8 0 0

cout << "myvector contains:";


int i, n = myvector.size();
for (i=0; i<n; i++)
cout << myvector[i] << " ";

cout << endl;


getchar();

return 0;
}

b. Thay thế các giá trị (replace)


Lệnh replace tìm kiếm 1 giá trị trong container, thay thế giá trị tìm được bởi giá trị mới.
// replace algorithm example
#include <iostream>
#include <algorithm>
#include <vector>
#include <stdlib.h>
using namespace std;

int main () {
int myints[] = { 10, 20, 30, 30, 20, 10, 10, 20 };
vector<int> a (myints, myints+8); // 10 20 30 30 20 10 10 20
replace(a.begin(), a.end(), 20, 99); // 10 99 30 30 99 10 10 99

cout << "a contains: ";


int i, n;
for (i=0, n=a.size(); i<n; i++)
cout << a[i] << " ";

getchar();
}

c. Thay thế các giá trị theo điều kiện (replace_if)


Lệnh replace_if cho phép tìm giá trị theo điều kiện do một hàm trả về. Để sử dụng lệnh này bạn
phải khai báo 1 hàm có giá trị trả về là bool nhận tham số là giá trị của 1 element. Khi hàm trả về
true, giá trị tương ứng sẽ bị thay thế bới giá trị mới. Hàm kiểm tra nên khai báo inline để tốc độ
nhanh hơn.
// replace_if example
#include <iostream>
#include <algorithm>
#include <vector>
#include <stdlib.h>
using namespace std;

inline bool SoLe(int i) { return ((i%2)==1); }

int main () {
vector<int> a;

// set some values:


for (int i=1; i<10; i++) a.push_back(i); // 1 2 3 4 5 6 7 8 9
replace_if(a.begin(), a.end(), SoLe, 0); // 0 2 0 4 0 6 0 8 0

cout << "a contains: ";


int i, n;
for (i=0, n=a.size(); i<n; i++)
cout << a[i] << " ";
getchar();
}

d. Đảo ngược containter (reverse)


Bạn có thể sử dụng lệnh reverse để đảo ngược 1 vector hoặc 1 list.
// reverse algorithm example
#include <iostream>
#include <algorithm>
#include <vector>

37
Lập trình C++ Nguyễn Phú Quảng

#include <stdlib.h>
using namespace std;

int main () {
vector<int> a;

// set some values:


for (int i=1; i<10; ++i) a.push_back(i); // 1 2 3 4 5 6 7 8 9
reverse(a.begin(),a.end()); // 9 8 7 6 5 4 3 2 1

// print out content:


cout << "a contains:";
int i, n = a.size();
for (i=0; i<n; i++)
cout << a[i] << " ";

cout << endl;


getchar();
}

XIII.9.3. Nhóm các hàm sắp xếp

a. Các thuật toán sắp xếp


#include <iostream>
#include <stdio.h>
#include <vector>
#include <algorithm>

using namespace std;


int main() {
vector<int> V;
int i;

for (i=0; i<10; i++) V.push_back(rand()%10);


sort(V.begin(), V.end());

for (i=0; i<V.size(); i++) {


cout << V[i] << endl;
}
getchar();
return 0;
}

#include <iostream>
#include <stdio.h>
#include <algorithm>

using namespace std;


int main() {
int A[] = {3,4,2,6,3,1,2,3,2,1};
int N = sizeof(A) / sizeof(*A);
int i;

cout << "Sap xep danh sach theo chieu tang dan" << endl;
sort(A, A+N);
for (i=0; i<N; i++)
cout << A[i] << endl;

cout << "Sap xep danh sach theo chieu giam dan" << endl;
sort(A, A+N, greater<int>() );
for (i=0; i<N; i++)
cout << A[i] << endl;

getchar();
return 0;
}

#include <iostream>
#include <stdio.h>
#include <algorithm>
using namespace std;

38
Lập trình C++ Nguyễn Phú Quảng

class sv {
public:
double diem;
char *ten;
bool operator < (const sv &b) const {
return diem < b.diem;
}
};

int main () {
sv a[] = {{6,"Mai"},{7,"Lan"},{5,"Cuc"},{4,"Truc"},{3,"Dao"}};
int n = sizeof(a)/sizeof(*a);
sort(a, a+n);

for (int i=0; i<n; i++) {


cout << a[i].diem << " " << a[i].ten << endl;
}
getchar();
return 0;
}

XIII.9.4. Nhóm các hàm trên danh sách được sắp xếp
Một số thuật toán như tìm kiếm, thêm vào danh sách... hoạt động nhanh hơn (độ phức tạp là log 2n
thay vì n). Thư viện <algorithm> hỗ trợ một số hàm làm việc riêng với các danh sách đã sắp xếp
theo thứ tự tăng dần.

a. Tìm cận dưới và cận trên (lower_bound, upper_bound)


Hàm lower_bound(first, last, value) trả về iterator của element cuối cùng trong danh sách đã sắp
xếp có giá trị không vượt quá [value].
Hàm upper_bound(first, last, value) trả về iterator của element đầu tiên có giá trị lớn hơn
[value].
// lower_bound/upper_bound example
#include <iostream>
#include <algorithm>
#include <vector>
#include <stdlib.h>
using namespace std;

int main () {
int myints[] = {10,20,30,30,20,10,10,20};
int size = sizeof(myints)/sizeof(myints[0]);
vector<int> v(myints,myints+size); // 10 20 30 30 20 10 10 20
vector<int>::iterator low,up;
sort (v.begin(), v.end()); // 10 10 10 20 20 20 30 30
low=lower_bound (v.begin(), v.end(), 20); // ^
up= upper_bound (v.begin(), v.end(), 20); // ^

cout << "lower_bound tro vao vi tri: " << int(low- v.begin()) << endl;
cout << "upper_bound tro vao vi tri: " << int(up - v.begin()) << endl;

getchar();
}

b. Tìm kiếm (binary_search)


Hàm binary_search(first, last, value) trả về true nếu tìm thấy giá trị value trong danh sách đã sắp
xếp từ first đến last.
// binary_search example
#include <iostream>
#include <algorithm>
#include <vector>
#include <stdlib.h>
using namespace std;

inline bool LonHon (int i,int j) { return (i>j); }

int main () {
int myints[] = {1,2,3,4,5,4,3,2,1};

39
Lập trình C++ Nguyễn Phú Quảng

vector<int> v(myints,myints+9); // 1 2 3 4 5 4 3 2 1

// Su dung phep so sanh nho hon de tim kiem


sort (v.begin(), v.end());

cout << "Tim phan tu 3... ";


if (binary_search (v.begin(), v.end(), 3))
cout << "Tim thay!\n"; else cout << "Khong tim thay!\n";

// Su dung phep so sanh lon hon de sap xep va tim kiem


sort (v.begin(), v.end(), LonHon);

cout << "Tim phan tu 6... ";


if (binary_search (v.begin(), v.end(), 6, LonHon))
cout << "Tim thay!\n"; else cout << "Khong tim thay!\n";

getchar();
}

c. Trộn 2 danh sách đã được sắp xếp (merge)


Bạn có thể sử dụng hàm merge để trộn 2 danh sách đã được sắp xếp thành 1 danh sách khác.
// merge algorithm example
#include <iostream>
#include <algorithm>
#include <vector>
#include <stdlib.h>
using namespace std;

int main () {
int first[] = {5,10,15,20,25};
int second[] = {50,40,30,20,10};
vector<int> v(10);
vector<int>::iterator it;

sort (first,first+5);
sort (second,second+5);
merge (first,first+5,second,second+5,v.begin());

cout << "The resulting vector contains:";


for (it=v.begin(); it!=v.end(); ++it)
cout << " " << *it;
getchar();
}

d. Trộn 2 danh sách đã được sắp xếp (merge_inplace)


// inplace_merge example
#include <iostream>
#include <algorithm>
#include <vector>
#include <stdlib.h>
using namespace std;

int main () {
int first[] = {5,10,15,20,25};
int second[] = {50,40,30,20,10};
vector<int> v(10);
vector<int>::iterator it;

sort (first,first+5);
sort (second,second+5);

copy (first,first+5,v.begin());
copy (second,second+5,v.begin()+5);

inplace_merge (v.begin(),v.begin()+5,v.end());

cout << "The resulting vector contains:";


for (it=v.begin(); it!=v.end(); ++it)
cout << " " << *it;

getchar();
}

40
Lập trình C++ Nguyễn Phú Quảng

e. Hợp 2 danh sách đã được sắp xếp (set_union)


// set_union example
#include <iostream>
#include <algorithm>
#include <vector>
#include <stdlib.h>
using namespace std;

int main () {
int first[] = {5,10,15,20,25};
int second[] = {50,40,30,20,10};
vector<int> v(10); // 0 0 0 0 0 0 0 0 0 0
vector<int>::iterator it;

sort (first,first+5); // 5 10 10 20 25
sort (second,second+5); // 10 20 30 40 50

it=set_union (first, first+5, second, second+5, v.begin());


// 5 10 15 20 25 30 40 50 0 0

cout << "union has " << int(it - v.begin()) << " elements.\n";
getchar();
}

f. Giao 2 danh sách đã được sắp xếp (set_difference)


// set_difference example
#include <iostream>
#include <algorithm>
#include <vector>
#include <stdlib.h>
using namespace std;

int main () {
int first[] = {5,10,15,20,25};
int second[] = {50,40,30,20,10};
vector<int> v(10); // 0 0 0 0 0 0 0 0 0 0
vector<int>::iterator it;

sort (first,first+5); // 5 10 15 20 25
sort (second,second+5); // 10 20 30 40 50

it=set_difference (first, first+5, second, second+5, v.begin());


// 5 15 25 0 0 0 0 0 0 0

cout << "difference has " << int(it - v.begin()) << " elements.\n";
getchar();
}

XIII.9.5. Các hàm trên heap

a. Tạo heap (make_heap)


// range heap example
#include <iostream>
#include <algorithm>
#include <vector>
#include <stdlib.h>
using namespace std;

int main () {
int myints[] = {10,20,30,5,15};
vector<int> v(myints,myints+5);
vector<int>::iterator it;

make_heap (v.begin(),v.end());
cout << "initial max heap : " << v.front() << endl;

pop_heap (v.begin(),v.end()); v.pop_back();


cout << "max heap after pop : " << v.front() << endl;

v.push_back(99); push_heap (v.begin(),v.end());


cout << "max heap after push: " << v.front() << endl;

sort_heap (v.begin(),v.end());

41
Lập trình C++ Nguyễn Phú Quảng

cout << "final sorted range :";


for (unsigned i=0; i<v.size(); i++) cout << " " << v[i];

getchar();
}

b. Thêm phần tử vào heap (push_heap)


// range heap example
#include <iostream>
#include <algorithm>
#include <vector>
#include <stdlib.h>
using namespace std;

int main () {
int myints[] = {10,20,30,5,15};
vector<int> v(myints,myints+5);
vector<int>::iterator it;

make_heap (v.begin(),v.end());
cout << "initial max heap : " << v.front() << endl;

pop_heap (v.begin(),v.end()); v.pop_back();


cout << "max heap after pop : " << v.front() << endl;

v.push_back(99); push_heap (v.begin(),v.end());


cout << "max heap after push: " << v.front() << endl;

sort_heap (v.begin(),v.end());

cout << "final sorted range :";


for (unsigned i=0; i<v.size(); i++) cout << " " << v[i];

getchar();
}

Kết quả
initial max heap : 30
max heap after pop : 20
max heap after push: 99
final sorted range : 5 10 15 20 99

c. Xóa phần tử khỏi heap (pop_heap)


// range heap example
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

int main () {
int myints[] = {10,20,30,5,15};
vector<int> v(myints,myints+5);
vector<int>::iterator it;

make_heap (v.begin(),v.end());
cout << "initial max heap : " << v.front() << endl;

pop_heap (v.begin(),v.end()); v.pop_back();


cout << "max heap after pop : " << v.front() << endl;

v.push_back(99); push_heap (v.begin(),v.end());


cout << "max heap after push: " << v.front() << endl;

sort_heap (v.begin(),v.end());

cout << "final sorted range :";


for (unsigned i=0; i<v.size(); i++) cout << " " << v[i];

cout << endl;

return 0;
}

42
Lập trình C++ Nguyễn Phú Quảng

Kết quả
initial max heap : 30
max heap after pop : 20
max heap after push: 99
final sorted range : 5 10 15 20 99

d. Sắp xếp heap (sort_heap)


// range heap example
#include <iostream>
#include <algorithm>
#include <vector>
#include <stdlib.h>
using namespace std;

int main () {
int myints[] = {10,20,30,5,15};
vector<int> v(myints,myints+5);
vector<int>::iterator it;

make_heap (v.begin(),v.end());
cout << "initial max heap : " << v.front() << endl;

pop_heap (v.begin(),v.end()); v.pop_back();


cout << "max heap after pop : " << v.front() << endl;

v.push_back(99); push_heap (v.begin(),v.end());


cout << "max heap after push: " << v.front() << endl;

sort_heap (v.begin(),v.end());

cout << "final sorted range :";


for (unsigned i=0; i<v.size(); i++) cout << " " << v[i];

getchar();
}

Kết quả
initial max heap : 30
max heap after pop : 20
max heap after push: 99
final sorted range : 5 10 15 20 99

XIV. GIẢI MỘT SỐ BÀI TOÁN BẰNG STL


XIV.1. Quản lý sinh viên

XIV.1.1. Đề bài
Tên file: SVIEN.*
Dữ liệu vào: từ file B1.INP, gồm:
- Dòng 1: Số sinh viên của trường (n – n<=10000)
- 3*n dòng tiếp theo: Thông tin của 1 sinh viên được ghi trên 3 dòng, bao gồm: Họ tên (tối đa
20 ký tự), lớp (tối đa 6 ký tự), quê quán (tối đa 10 ký tự)
Yêu cầu: Tìm và liệt kê:
- Số lượng lớp trong trường và danh sách các lớp
- Với mỗi lớp:
o Số lượng và danh sách các địa phương là quê quán của sinh viên trong lớp
Dữ liệu ra: tại file B1.OUT với khuôn dạng như sau:
- Dòng 1: Số lớp của trường (k)
- Các dòng tiếp theo là thông tin của từng lớp (mỗi mục được liệt kê dưới đây được trình bày
trên 1 dòng):

43
Lập trình C++ Nguyễn Phú Quảng

o Ten lop
o số sinh viên của lớp, số lượng địa phương (m)
o số lượng sinh viên quê ở địa phương 1, tên địa phương 1
o số lượng sinh viên quê ở địa phương 2, tên địa phương 2
o ...
o số lượng sinh viên quê ở địa phương m, tên địa phương m
B1.INP B1.OUT

5 2
Nguyen Van A 46pm1
46pm1 2 1
Hung Yen 2 Hung Yen
Tran Van B 47th1
47th1 3 2
Hai Duong 2 Hai Duong
Nguyen Thi C 1 Binh Dinh
46pm1
Hung Yen
Vu Van D
47th1
Binh Dinh
Cao Thi E
47th1
Hai Duong

XIV.1.2. Bài giải


#include <iostream>
#include <fstream>
#include <string>
#include <map>

using namespace std;

#define INPUT "b1.inp"


#define OUTPUT "b1.out"

int main() {
ifstream fi;
int n, i;
map<string, int> lop; // map giữa tên lớp và số sinh viên
map<string, map<string, int> > que; // map giữa tên lớp và biến đếm số
// sinh viên của từng quê
// biến đếm này cũng là ánh xạ giữa quê
// và số sinh viên ở quê đó

fi.open(INPUT); // Mở file input


fi >> n; // Số lượng sinh viên
char sLop[100], sQue[100];
fi.getline(sLop, 100); // Đọc xuống dòng (sau n)
for (int i=0; i<n; i++) {
fi.getline(sLop, 100); // Đọc tên (ko sử dụng)
fi.getline(sLop, 100); // Đọc tên lớp
lop[sLop]++; // Thêm số sinh viên của lớp đó
fi.getline(sQue, 100); // Đọc quê
que[sLop][sQue]++; // Thêm số sinh viên thuộc quê của lớp đó

44
Lập trình C++ Nguyễn Phú Quảng

}
fi.close(); // Đóng file input

ofstream fo;
fo.open(OUTPUT); // Mở file output
map<string, int>::iterator j;
fo << lop.size() << endl; // Ghi số lượng lớp
for (j=lop.begin(); j!=lop.end(); j++) {// Lặp qua từng lớp
fo << j->first << endl; // Ghi tên lớp (thành phần đầu của pair)
map<string, int> &quei = que[j->first];
// Gán biến tham chiếu đến biến đếm quê
// Của lớp tương ứng
fo << j->second << " " << quei.size() << endl;
// Ghi số lượng sinh viên của lớp và
// số lượng quê quán của sv trong lớp
map<string, int>::iterator t;
for (t=quei.begin(); t!=quei.end(); t++) {
// Với mỗi vùng quê, ghi số lượng sv
// ở quê đó và tên quê
fo << t->second << " " << t->first << endl;
}
}
fo.close(); // Đóng file output
}

/********************* Test1 ***************************


5
Nguyen Van A
46pm1
Hung Yen
Tran Van B
47th1
Hai Duong
Nguyen Thi C
46pm1
Hung Yen
Vu Van D
47th1
Binh Dinh
Cao Thi E
47th1
Hai Duong
********************************************************/

XIV.2. Rào đất

XIV.2.1. Đề bài
Tên file: RAO.*
- Một mảnh đất hình chữ nhật (kích thước tối đa 32000x32000) được rào bằng các tường rào
song song với các cạnh của hình chữ nhật
- Một khoảng đất được gọi là được bảo vệ nếu nó được giới hạn bởi các tường rào
- Hãy tìm khoảng đất có diện tích lớn nhất được bảo vệ
Dữ liệu vào: từ file RAO.INP
- Dòng đầu: 2 số nguyên w h là chiều rộng và chiều cao của mảnh đất
- Dòng thứ 2: số dòng lượng tường rào (n<=100)
- n dòng tiếp theo: mỗi dòng gồm 4 số nguyên (cách nhau bởi dấu cách) là tọa độ 2 điểm
đầu và cuối của rào. Dữ liệu vào đảm bảo là rào song song với cạnh của hình chữ nhật
Dữ liệu ra: tại file B2.OUT, gồm:
- Dòng 1: Diện tích của vùng được bảo vệ có diện tích lớn nhất, nếu không có thì in ra giá trị -1

45
Lập trình C++ Nguyễn Phú Quảng

XIV.2.2. Bài giải

XIV.3. Robot

XIV.3.1. Đề bài
Bài 3 (Robot). Tên file: ROBOT.*
Một con robot di chuyển trên một lưới ô vuông bằng cách nhận một trong các lệnh sau:
 R: Quay phải 45 độ
 L: Quay trái 45 độ
 G n: Tiến về phía trước 1 khoảng n (n < 1000)
 U: Nhấc chổi lên
 D: Hạ chổi xuống
(chú ý rằng khi hạ chổi xuống là ô đó đã được tô màu)
Trạng thái ban đầu của robot là nhấc chổi, tọa độ ban đầu là điểm (0, 0)
Input: Nhạp dữ liệu từ file input.txt gồm các thông tin sau
 Mỗi dòng là 1 trong 5 lệnh kể trên (tối đa 1000 lệnh)
Output: Kết quả ghi ra file output.txt
 Dòng đầu tiên là 1 số nguyên chỉ ra số lượng ô được tô màu

XIV.3.2. Bài giải


#include <iostream>
#include <fstream>
#include <string>
#include <map>

using namespace std;

#define INPUT "robot.inp"


#define OUTPUT "robot.out"

class diem { // Lớp điểm, lưu tọa độ của điểm


public:
int x, y; // Toạ độ của điểm
diem() {} // Hàm khởi tạo điểm (không tham số)
diem(int _x, int _y) { // Hàm khởi tạo diểm theo tọa độ
x = _x; y = _y;
} // Hàm so sánh điểm với điểm khác
// Hàm này sử dụng để sắp xếp các khóa trong map
bool operator < (const diem& b) const {
return (x<b.x) || (x==b.x && y<b.y);
}
};

map<diem, int> A; // Ánh xạ giữa điểm và một số nguyên (nếu số nguyên


// khác 0 tức là điểm đó đã đi qua)

int main() {
ifstream fi;
fi.open(INPUT);
string s;
int huong = 0; // Hướng khởi đầu
int dx[] = {1,0,-1,0}; // Vector di chuyển tương ứng theo hướng
int dy[] = {0,-1,0,1};
int n, i, x=0, y=0; // Tọa độ đầu tiên
bool choi = false; // Trạng thái của chổi

while (!fi.eof()) { // Đọc đến khi hết file


s = "";
fi >> s; // Đọc xâu ký tự

46
Lập trình C++ Nguyễn Phú Quảng

switch (s[0]) { // Kiểm tra ký tự đầu


case 'R': // R: Quay phải
huong = (huong+1)%4;
break;
case 'L': // L: Quay trái
huong = (huong+3)%4;
break;
case 'G': // Tiến theo hướng hiện tại
fi >> n; // Đọc thêm giá trị n
for (i=0; i<n-1; i++) {
x += dx[huong]; // dịch chuyển từng bước
y += dy[huong]; // Đếm các ô nếu trạng thái chổi là hạ xuống
if (choi) A[diem(x, y)] ++;
}
break;
case 'U': // Nhấc chổi
choi = false;
break;
case 'D': // Hạ chổi
choi = true;
A[diem(x, y)] ++; // Đếm luôn ô vừa hạ
break;
}
}
fi.close();

ofstream fo;
fo.open(OUTPUT);
fo << A.size();
fo.close();
}

/****** Test 1 *********


U
D
U
D
***********************/

/****** Test 2 *********


U
D
U
D
G 100
L
L
G 100

XIV.4. Dijsktra

XIV.4.1. Đề bài

XIV.4.2. Bài giải

XIV.5. Hợp diện tích hình chữ nhật

XIV.5.1. Đề bài
Cho n hình chữ nhật có cạnh song song với các trục Ox, Oy. Tìm tổng diện tích không gian bị che
bởi các hình chữ nhật này (Diện tích phần hợp của các hình chữ nhật);

XIV.5.2. Bài giải


- Rời rạc hóa các thông tin bằng cách:

47
Lập trình C++ Nguyễn Phú Quảng

. Kéo dài các cạnh đứng


. Kéo dài các cạnh ngang
. Các cạnh này chia không gian thành một lưới hình chữ nhật.
- Mỗi hình chữ nhật sẽ che một số ô trong lưới này
- Đếm tổng diện tích của các ô bị che, bạn tính được diện tích của phần che phủ của tất cả
các hình chữ nhật.
/*
Cho 1 danh sach cac hinh chu nhat co canh song song voi man hinh
Tim hinh chu nhat co dien tich la hop cua cac hinh chu nhat nay
*/

#include <iostream>
#include <fstream>
#include <vector>
#include <map>
#include <algorithm>

#define input "dientich.inp"


#define output "dientich.out"

using namespace std;

typedef vector<int> dong;

struct chunhat // Thông tin của 1 hình chữ nhật


{
double xmin, ymin, xmax, ymax; // xmin, xmax, ymin, ymax
};

int n;
vector<dong> a; // Bảng đánh dấu các ô bị che
vector<chunhat> cn; // Danh sách các hình chữ nhật
vector<double> tx, ty; // Tọa độ các lưới dọc và ngang

inline void swap(double &a, double &b) // Hàm đổi chỗ 2 số thực
{
double t = a; a = b; b = t; // Hàm khai báo inline để chạy nhanh hơn
}

int main()
{
fstream f(input);
f >> n; f.get(); // Số hình chữ nhật

cn.resize(n);
for (int i=0; i<n; i++) // Đọc thông tin từng hình chữ nhật
{
chunhat &a = cn[i]; // Đổi chỗ nếu các tọa độ không hợp lệ
f >> a.xmin >> a.ymin >> a.xmax >> a.ymax; f.get();
if (a.xmin>a.xmax) swap(a.xmin, a.xmax);
if (a.ymin>a.ymax) swap(a.ymin, a.ymax);

tx.push_back(a.xmin); // Tạo mảng tx: tọa độ các lưới đứng


tx.push_back(a.xmax);
ty.push_back(a.ymin); // Tạo mảng ty: tọa độ các lưới ngang
ty.push_back(a.ymax);
}

f.close();

sort(tx.begin(), tx.end()); // Sắp xếp các tọa độ lưới dứng và ngang


sort(ty.begin(), ty.end());

map<double, int> mx, my; // Bảng ánh xạ giữa tọa độ (số thực)
for (int i=0; i<2*n; i++) // và số thứ tự
{ // của tọa độ đó trên lưới (số nguyên)
mx[tx[i]] = i;
my[ty[i]] = i;
}

a.resize(2*n); // Tạo mảng (2n)*(2n), toàn số 0


for (int i=0; i<2*n; i++) a[i].resize(2*n, 0);

48
Lập trình C++ Nguyễn Phú Quảng

double dt = 0; // Tính tổng diện tích


for (int i=0; i<n; i++)
{
chunhat b = cn[i];
int xmin = mx[b.xmin]; // xmin, xmax: chỉ số cạnh trái, phải
int ymin = my[b.ymin]; // trên lưới
int xmax = mx[b.xmax]; // ymin, ymax: chỉ số các cạnh trên, dưới
int ymax = my[b.ymax]; // trên lưới

for (int x=xmin; x<xmax; x++) // Duyệt qua tất cả các thuộc hcnhật
for (int y=ymin; y<ymax; y++) {
if (a[x][y]==0) // Nếu ô chưa được đánh dấu
{
a[x][y] = 1; // Đánh dấu ô đó, và tính thêm diện tích
dt += (tx[x+1]-tx[x])*(ty[y+1]-ty[y]);
}
}
}

ofstream fo;
fo.open(output);
fo << dt; // Tổng diện tích của các hình chữ nhật
fo.close();
}

/*
2
0 0 1 1
0 0 2 2
*/

XV. INPUT VÀ OUTPUT

49

You might also like