Lập trình C++ đại học xây dựng Hà Nội
Trang 1BỘ 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
Trang 2Mụ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
Trang 3XI 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
Trang 4I 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
Để 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
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
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
Trang 5Dò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
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++
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ỏ
- 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 :
Trang 6- 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
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
Trang 7II.2.3 Các kiểu stl
- 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
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
Trang 8- 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ị
- Phép toán and từng bit &
Trang 9C++ 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
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>
Trang 10 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
- 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"
Trang 11 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:
Trang 12Vò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
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:
getchar();
}
Trang 13Trong 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
Thực hiện <Lệnh_lặp> cho đến khi <Điều_kiện> sai
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:
// Nếu i chia hết cho j, nhảy đến vị trí "KoNguyenTo"
if (i%j==0) goto KoNguyenTo;
Trang 14- <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>,
// 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)
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ý
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
Trang 15- 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)
Đệ 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)
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
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
return 2*1=2
return 3*2=6
return 4*6=24 Begin
Đ
Đ
Đ
S
Trang 16Bướ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
Trả về giá trị
của
GiaiThua(4)
n=4, tính n*GiaiThua(3) = 4*6 = 24
Trả về giá trị 24
4
4 3
4 3 2
4 3 2 1
4 3 2
1
4 3
2
4
3
4
Trang 17Thoát trở về hàm đã gọi hàm này (là hàm main())
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
Trang 18VII.2 Khai báo biến
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
Trang 19- 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>
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
Trang 20C++ 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ữ)
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 đó.
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ế
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();
}
Trang 21IX KIỂU NGƯỜI DÙNG ĐỊNH NGHĨA
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.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 (++)
Trang 22Khá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ạia.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ố)
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;
Trang 23XIII.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
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
Trang 24- 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.
// (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
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()