Qua bài báo cáo, nhóm sẽ khám phá sâu rộng về từng thành phần của STL, hiểu rõ về cách thức hoạt động của chúng và cách chúng có thể được ứng dụng để giải quyết các vấn đề lập trình phức
Trang 1
ĐẠI HỌC QUỐC GIA THÀNH PHO HO CHi MINH
TRƯỜNG ĐẠI HỌC KHOA HỌC TỰ NHIÊN
Thw vién STL (Standard Template Library)
Môn học: Phương pháp lập trình hướng đối tượng
Trang 4Trang 3
Trang 5
1 Lời mở đầu
Trong thế giới lập trình hiện nay, việc tiếp cận và ứng dụng các công cụ hiệu quả có vai trò thiết yếu trong việc đạt được hiệu suất cao và tối u hóa quá trình phát triển phần mềm C+-+ từ lâu được đánh giá là ngôn ngữ mạnh vì tính gần gũi với ngôn ngữ máy Một trong những công cụ mạnh mẽ nhất được cung cấp cho ngôn ngữ lap trinh C++ chinh 1a Standard Template Library (STL) dude thiét ké béi Alexander Stepano STL khéng chi
là một thư viện, mà còn là một triết lý lập trình, nơi mà khái niệm lập trình tong quat (generic programming)
được thể hiện rõ nét nhất thông qua các template Thư viện này bao gồm một bộ sưu tập các container, thuật toán, và iterators, được thiết kế để hỗ trợ người lập trình trên nhiều khía cạnh từ quản lý dữ liệu đến xử lý thuật
toán phức tạp
Khởi nguồn từ nhu cầu lập trình không phụ thuộc vào kiểu dữ liệu, #TL đã cách mạng hóa cách thức xây dựng các chương trình C++- bằng cách cung cấp một loạt các cấu trúc dữ liệu (như vector, queue, list, map, ) và
thuật toán (như sort, search, transform, ) có thể tái sử dụng trên nhiều loại dữ liệu Điều này không chỉ làm
giảm bớt thời gian phát triển phần mềm mà còn tăng cường độ tin cậy và hiệu năng của chương trình STL cung cấp các giải pháp lập trình tổng quát mà không làm mất đi hiệu suất vốn có của C-L+, một yếu tố quan trọng mà người lập trình không thể bỏ qua Điều này làm cho STL trở thành công cụ không thể thiếu đối với những lập trình viên C-+-+, từ những người mới bắt đầu cho đến các chuyên gia, những người tham gia vào các cuộc thi lập trình và những người làm việc trong lĩnh vực nghiên cứu phát triển
Qua bài báo cáo, nhóm sẽ khám phá sâu rộng về từng thành phần của STL, hiểu rõ về cách thức hoạt động của chúng và cách chúng có thể được ứng dụng để giải quyết các vấn đề lập trình phức tạp, qua đó nâng cao kỹ năng
và hiểu biết của người lập trình về một trong những thư viện mạnh mẽ nhất trong C-L+
Trang 4
Trang 6
2_ Lịch sử hình thành
[15]
Bồi cảnh hình thành
Alexander Stepanov nhan ra tâm quan trọng và tiềm năng của lập trinh tong quat (generic programming), trudc
đó ông cùng đồng nghiệp đã phát triển một thư viện Ada , tuy nhiên Ada không được sử dụng rộng rãi trong công chúng, bên cạnh đó nhận ra C-+-+ sẽ được sử dụng rộng rãi trong công chúng hơn và hỗ trợ tốt cho việc lập trình tổng quát cũng nhĩ khả năng truy cập linh hoạt vào bộ nhớ thông qua con trỏ, điều quan trọng giúp đạt được tính tổng quát mà không làm giảm hiệu quả của ngôn ngữ
việc sử dụng rộng rãi các templates
Sự Hội Tụ và Chuẩn Hóa
Năm 1998: C++ Standard được chính thức công bố, trong đó bao gồm TL như một phần cốt lõi Điều này đã thiết lập một chuẩn mực cho các nhà phát triển phần mềm, thúc đẩy sự ổn định và khả năng tương thích giữa, các trình biên dịch Những năm 2000 đến nay: TL tiếp tục được mở rộng và cải tiến trong các phiên bản tiếp theo của chuẩn C-+L-+, với cdc cép nhat quan trong trong C++11, C+414, C++4+17 va cdc phién ban sau nay Cac
cải tiến bao gồm việc bổ sung các container mới, thuật toán, và cải thiện hiệu suất
Tác Động và Di Sản
STL không chỉ ảnh hưởng tới cách thức các chương trình C+-+ được viết mà còn ảnh hưởng đến nhiều ngôn ngữ lập trình khác trong việc hỗ trợ lập trình khái quát Nó cũng là một ví dụ điển hình về ảnh hưởng của lý thuyết toán học và lập trình hàm đến thế giới lập trình thực tế, làm thay đổi quan điểm về thiết kế và triển khai phần mềm
Nhờ vào sự phát triển của STL, các lập trình viên có thể viết code ngắn gọn, hiệu quả hơn, và có thể tái sử dụng một cách rộng rãi, góp phần làm nền tảng cho các phát triển sau này trong lập trình hiện đại
Trang 5
Trang 7e Container: Là một cách để lưu trữ dữ liệu, cho dù dữ liệu đó bao gồm các kiểu dữ liệu cơ bản như int và
float, hay các đối tượng của lớp
e Algorithm: Cung cấp các thuật toán như sort, ñnd, Được sử dụng để thao tác dữ liệu lưu trữ trong container, những cũng có thể áp dụng chúng cho các mảng thông thường
e Function object /Functors: Đóng gói một hàm vào một đối tượng để sử dụng bởi các thành phần khác Cung cấp một cách để truyền hàm vào thuật toán, cho phép tùy chỉnh hình vi của nó
e Adapter: Là các lớp mẫu giúp tạo ra các interface mới cho các container, cho phép chúng ta tương tác với các container, iterator va ham theo những cách mới và linh hoạt hơn
Việc phân chia thành 5 thành phần như vậy giúp giảm thiểu đáng kể không gian thành phần Ví dụ, xem xét
về việc cũng cấp một ham tìm kiếm cho mỗi loại container, nếu không có thư viên STL thì có thể sẽ phải viết một hàm tìm kiếm riêng cho mỗi loại container (VD: Mắng, list, queue, ) Kiến tốn thời gian và công sức, còn tạo ra nhiều mã nguồn có thể gây lỗi Với STL, chỉ cần cung cấp một phiên bản tìm kiếm duy nhất, hàm này hoạt động với mọi lại container, miếm là chúng đáp ứng một tập hợp cơ ban các yêu cần (VD: Chúng cung cấp một iterator) Giúp giảm đáng kể số lượng mã cần viết đồng thời tăng khả năng tái sử dụng mã
Nếu các thành phần của phần mềm được biểu diễn dưới dạng một mảng ba chiều, nơi một chiều đại diện cho
các kiểu dữ liệu khác nhau (ví dụ: imt, double, ), chiều thứ hai đại diện cho các container khác nhau (ví dụ: vector, linked-list, file, ), và chiều thứ ba đại diện cho các thuật toán khác nhau trên các container (ví du: search,
sort, ), néu 2, j, và & là kích thước của các chiều, thì ¿ x j + & phiên bản mã khác nhau phải được thiết kế
Bằng cách sử dụng các hàm mẫu được tham số hóa bởi một kiểu dữ liệu, thì chỉ cần 7 + & phiên bản Hơn nữa, bằng cách làm cho các thuật toán hoạt động trên các container khác nhau, Thì chỉ cần 7 + k phiên bản Điều này đơn giản hóa đáng kể công việc thiết kế phần mềm và cũng làm cho việc sử dụng các thành phần trong thư viện cùng với các thành phần do người dùng định nghĩa một cách rất linh hoạt Người dùng có thể dễ dàng định nghĩa một lớp container chuyên biệt và sử dụng hàm sắp xếp của thư viện để sắp xếp nó Người dùng có thể cung cấp một hàm so sánh khác cho việc sắp xếp hoặc như một con trổ thông thường đến một hàm so sánh,
hoặc như một đối tượng hàm (một đối tượng với operator() được định nghĩa) thực hiện các so sánh
Thư viện mở rộng các mô hình Ở + + cơ bản một cách nhất quán, vì vậy người lập trình Ở/Œ + + có thể dễ dàng bắt đầu sử dụng thư viện Ví dụ, thư viện chứa hàm mẫu merge Khi người dùng có hai mảng và b cần
được hợp nhất vào mảng c, có thể được thực hiện như sau:
Trang 6
Trang 8Nếu người dùng muốn hợp nhất một vector a và một list b vào một mảng c chưa được cấp phát thì có thể được
thực hiện như sau:
Trang 9
Trong nhiều trường hợp, việc lặp qua các luồng nhập/xuất (H/O streams) giống như lặp qua các cấu trúc dữ liệu thông thường (như mắng, danh sách, v.v.) rất hữu ích Điều này đặc biệt hữu ích khi muốn thao tác với dữ liệu từ một nguồn đầu vào (như một tệp hoặc luồng nhập chuẩn), và san đó ghỉ kết quả vào một nguồn đầu ra (như một tệp khác hoặc luồng xuất chuẩn), mà không cần tạo một cấu trúc dữ liệu tạm thời để lưu trữ kết quả Thư viện STL trong Ở + + cung cấp hai lớp mẫu, isiream iterator và ostream iterator, để hỗ trợ việc này Cả hai lớp này đều cho phép sử dụng các thuật toán STL với các luồng I/O như thể chúng là các cấu trúc dữ liệu thông thường
Dưới đây là chương trình đọc các số từ một tệp, sau đó sắp xếp và ghi chúng ra một tệp khác:
ofstream outputFile ("output.txt");
istream_iterator<int> start (inputFile), end;
vector<int> numbers(start, end);
istream iterator < int > start(inputFile), end, tao ra hai đối tượng iterator Với start là một istream iterator
được khởi tạo bằng mputFile, nghĩa là nó sẽ bắt đầu đọc từ tệp “input.txt” end tại đây là một istream iterator
không được khởi tạo bằng một luồng đầu vào cụ thể Khi một istream iterator không được khởi tạo như vậy,
nó đại diện cho "kết thúc luồng" hoặc "kết thúc tệp" Điều này có nghĩa là nó có thể được sử dụng để kiểm tra,
xem chúng ta đã đọc hết tệp chưa Khi thực hiện thao tác vector < int > numbers(start, end), ching ta dang
yêu cầu Ở -L + tạo một vector mới và khởi tạo nó với các giá trị từ start đến end Ở -+ + sẽ tiếp tục đọc các
giá trị từ start cho đến khi gặp end (nghĩa là, cho đến khi nó đọc hết tệp), và thêm tất cả các giá trị này vào vecbor numbers Vì vậy, chương trình này đọc các số nguyên từ một tệp, lưu chúng vào một vector, sau đó ghỉ chúng ra một tệp khác
Trang 8
Trang 10
4_ Các Thành Phần Cốt lõi
Phần này chứa một số hàm mẫu và lớp cơ ban được sử dụng trong toàn bộ thư viện
Cụ thể, đoạn mã đầu tiên định nghĩa các toán tử so sánh Các toán tử này được định nghĩa dưới dạng hàm mẫu,
có nghĩa là chúng có thể hoạt động với bất kỳ kiểu dữ liệu nào T1 và T2 miễn là kiểu dữ liệu đó hỗ trợ các toán
tit can thiét (== va <)
1 template <class Ti, class T2>
2 bool operator!=(const T1& x, const T2e y) {
+}
5 template <class Ti, class T2>
6 bool operator>(const T1ik x, const T2& y) f
H return y < x;
s }
9 template <class Ti, class T2>
10 bool operator <=(const T1& x, const T2k y) f
12 }
13 template <class T1, class T2>
14 bool operator>=(const Ti& x, const T2& y) {
16 }
Thư viện bao gồm hai phần tử dữ liệu hoặc đối tượng Hai phần tử này có thể thuộc các kiểu dữ liệu khác nhau
1 template <class T1, class T2>
2 struct pair {
3 T1 first;
4 T2 second;
9 template <class Ti, class T2>
10 bool operator==(const pair<T1, T2>& x, const pair<T1l, T2>& y) {
11 return x.first == y.first && x.second == y.second;
12 }
13 template <class T1, class T2>
14 bool operator<(const pair<T1, T2>& x, const pair<T1, T2>& y) f{
Trang 9
Trang 11
5 Iterator
Là một object (hoạt động giống con trỏ) dé tré dén mot phan tit bén trong container Vé co ban, Iterator là một đối tượng trung gian được sử dụng để duyệt và thao tác với các phần tử trong một container [19]
Rõ hơn, hành vi của một Herator được xác định qua các thao tác cơ bản sau:
e Toán tử x trả về phần tử tại vị trí hiện tại Nếu phần tử có member, có thể sử dụng toán tử -› để truy
cập trực tiếp các member do tit Iterator
e Toán tử +-+ cho phép Iterator di chuyển đến phần tử tiếp theo Hần hết các Iterator cũng cho phép di chuyển ngược lại bằng cách sử dụng toán tử
e Toán tử == và != trả về kết quả xác dinh xem hai Iterator có đại diện cho cùng một vị trí hay không
e Toán tử = gán một Iterator (vị trí của phần tử mà nó tham chiếu đến)
Các thao tác trên giống chính xác với các thao tác của con trổ bình thường khi được sử dụng để lặp qua các phần
tử của một mảng thông thường Khác biệt là Iterator sẽ “thông minh” hơn, các hoạt động bên trong lterator sé phụ thuộc vào container mà nó duyệt qua
Như vậy, mỗi loại container sẽ tự định nghĩa một loại Iterator “riêng” cho mình Vì thế, trừ khi sử dụng loại Iterator dac biét như reverse Iterator, hoặc một số loại phụ trợ, ta không cần phải #include <iterator> vào
Thực tế, Iterator tuân theo một khái niệm trừu tượng thuần là: mọi thứ hoạt động giống Iterator chinh 1A một
Tterator Tuy nhiên, Iterator có nhiều loại chức năng riêng biệt Và tùy vào chức năng mà Iterator được chia thành năm loại, với chức năng mạnh dần (Input và Output mạnh tương đương nhau): Input Output, Forward,
Bidirectional, Random-Access
La 1 trong 2 Iterators cé chite ning yéu va cau tric don gian nhat trong 5 loai
Các Output Iterators, với quyền truy cập ghi, chỉ có thể di chuyển đến phần tử tiếp theo Do đó, ta chỉ có thể gắn giá trị mới cho từng phần tử một
Ta không thể sử dụng một Output Iterators để lặp hai lần trên cùng một khoảng phạm vi (range) Trên thực tế, thậm chí không thể chắc chắn rằng ta có thể thực hiện gan một giá trị hai lần mà không tăng Iterators
Đồng thời, không có toán tử so sánh nào có thể sử dụng với Output Iterators Ta không thể kiểm tra xem Output Tterators có hợp lệ không, cũng như không thể kiểm “phép ghỉ” đó có thành công hay không
Biểu thức | Tác dụng
*it = val | Ghi val vào nơi mà Iterators tham chiếu
++it Di chuyển về phía trước (trả về vị trí mới) it++ Di chuyển về phía trước (trả về vi tri ct)
TYPE(it) | Sao chép Iterators (copy constructor)
Trang 125.2 Input Iterator
Một ví dụ điển hình của một Output Iterators thuần túy là cái mà thực hiện ghi đầu ra tiêu chuẩn (ví dụ: màn
hình hoặc máy in) Nêu ta sử dụng hai Output Iterators để ghi ra màn hình, từ thứ hai sẽ theo san từ đầu tiên thay vì ghi đè lên nó
Inserter (insert Iterators), sẽ được nhắc đến sau, cũng là ví dụ điển hình khác cho Output iterator
Tất cả các const_ Iterators đều không phải là Output Iterators, bởi vì chúng không cho phép ta ghỉ vào nơi Tterators tham chiếu đến
La 1 trong 2 Iterators cé chite ning yéu va cau tric don gian nhat trong 5 loai
Cac Input Iterators, voi quyén truy cap doc, chỉ có thể di chuyển đến phần tử tiếp theo Do đó, ta chỉ có thể đọc giá trị mới cho từng phần tử một
Mỗi Input Iterators chỉ có thể đọc qua một phần tử một lần Do đó, nếu ta sao chép một Input Iterators vào một Input Iterators, cho bản gốc và bản sao chép di chuyển về phần tử tiếp theo, chúng có thể lặp qua các giá
trị khác nhan
Một ví dụ điển hình của một Input Iterators thuần túy là một Iterators đọc từ đầu vào tiêu chuẩn, thường là
bàn phím Cùng một giá trị không thể được đọc hai lần
Các thao tác cua Input Iterators:
it>member | Cung cấp quyền truy cập đọc đến một member của phần tử tham chiến tới
Bang 2: Các thao tac cia Input Iterators
Đối với các Input Iterators, các toán tử == và l= chỉ được cung cấp để kiểm tra xem một Iterators cé bing Iterators past-to-end hay không
Ví dụ:
InputIterators pos, end;
while (pos != end) {
// truy cap doc-duy nhat su dung *pos
++pos;
+
Lưu ý: Khong thể chắc chắn rằng hai Iierators khác nhau (cé hai déu khéng phai la Tterators past-to-end), khi
so sứnh sẽ khác nhau cho dù chúng tham chiếu đến các phần tử khác nhau
Trang 11
Trang 135.3 Forward Iterators
La Input Iterators, ké thita moi kha năng của Input Iterators, nhưng nó đảm bảo hơn khi ta duyệt tới các phần
tử Không giống véi Input Iterators, Forward Iterators dam bao rang hai Forward Iterators tré đến cùng một phần tử, toán tử '==' sẽ trả về true, nghĩa là chúng vẫn trỏ về cùng một vị trí, khi cả hai tăng (duyệt tới phần
tử tiếp theo)
Như vậy, ta có thể thực hiện đoạn code sau với Forward Iterators:
ForwardIterators posi, pos2;
posi = pos2 = begin;
if (posi != end) {
++posi; // posi di truoc mot phan tu
while (posi != end) f
Forward Iterators mà đấp ứng các khả năng của Output Iterators là Forward Iterators có khả năng ghi (Mutable
Forward Iterators), có thể được sử dụng cho cả đọc và ghi
Cac Forward Iterators được định nghĩa và sử dụng bởi các đối tượng sau:
Biểu thức | Hiệu lực
Bảng 3: Các thao tác bổ sung của Bidirectional Iterators
Các lidirectional Iterators được định nghĩa và sử dụng bởi các đối tượng sau:
e Class list<>
e Các container c6 lién két (Associative containers)
Bidirectional Iterators dap ting cAc kha nang cia Output Iterators 1a Bidirectional Iterators c6 kha nang ghi
(Mutable Bidirectional Iterators), cé thể được sử dụng cho cả đọc và ghi
Trang 12
Trang 145.5 Random Access Iterators
Cung cấp tất cả khả năng của các Bidirectional Iterator cộng thêm khả năng truy cập ngẫu nhiên Đó là nó cung cấp toán tử cho iterator số học (tương ứng với phép tính trên con trổ thông thường:
tử có chỉ số n
Bang 4: Cac thao tac b6 sung ciia Random-Access Iterators
e Array, vector, deque
e String, ustring
e Poiters
nang ghi (Mutable Random Access Iterators), có thể được sử dụng cho cả đọc và ghi
Loại Herator
Bang 5: Tổng hợp
Thư viện chuẩn C+-+ cung cấp một số hàm phụ trợ để làm việc với các iterators: advance(), next(), prev(), distance(), và iter_ swap()
a) advance()
void advance(InputIterator& pos, Dist n)
Tac dụng: giúp Input Iterator pos tiến n phần tử (hoặc lùi nếu n âm)
Với các Iterators hai chiều và truy cập ngẫu nhiên, n có thể là số âm để lùi
Dist là một template type Thông thường, nó là integer
Trang 13
Trang 155.6 Ham phu tro cia Iterators (Auxiliary Iterator Functions)
ForwardIterator next (ForwardIterator pos)
ForwardIterator next(ForwardIterator pos, Dist n)
BidirectionalIterator prev(Bidirectionallterator pos)
BidirectionallIterator prev(Bidirectionallterator pos, Dist n)
Khi gọi hai hàm next() và prev(), nội bộ hàm sẽ gọi lại advance() Thy nhiên, next() và prev() được thiết kế đặc trung cho Forward Iterators va Bidirectional Iterators
Hàm next() và prev() sẽ giảm hiệu năng bởi khi thực hiện, nó chỉ di chuyển lên từng phần tử một Đổi lai, next()
và prev() sẽ trả về bản sao của phần tử mà nó di chuyển đến
c) distance()
Ham distance() tính khoảng cách giữa hai Iterators:
#include <iterator>
Dist distance(InputIterator posi, InputIterator pos2);
Tra về khoảng cách giữa hai Iterators pos] va pos2
Cả hai Iterators phải trổ đến phần tử trong cùng một container
Nếu các Iterators không phải Random Access, pos2 phải có thể truy cập từ posl (có cùng giá trị hoặc ở vị trí phía san)
d) iter_ swap()
#include <iterator>
void iter_swap(ForwardIterator1l posi, ForwardIterator2 pos2);
Hooán đổi giá trị mà hai trình lặp pos1l và pos2 đang trỏ đến
Không cần posl và pos2 cùng kiểu, nhưng giá trị của chúng phải gán được cho nhau
Ví dụ cách sử dụng các hàm phụ trợ qua đoạn lập trình sau:
Trang 14
Trang 16advance(it, 3); // it tro den 7
cout << “Distance between begin and it: " << distance(v.begin(), it) << endl; // Dutput: 3 iter_swap(it, prev_it); // swap 7 va 3
cout << “Vector after swap: "3;
Sequenece containters cũng cấp các cấu trúc dữ liệu mà có thể được truy cập một cach tuan ty Gém array, vector,
deque, forward_ list, list
Phần tiếp theo sẽ trình bày và ví dụ một vài hàm nổi bật nhất của mỗi thao tác [1]
Để sử dụng cấu trúc array, ta cần chỉ thi #include <array>
Cài đặt chỉ tiết array [2]
Các hàm thành viên
- Cac constructor, destructor, toán tử gần
- Cac iterator: tuong tu cdc iterator
- Các ham dùng để truy cập và làm việc với các phần tử của mắng:
Trang 15
Trang 176.1 Sequence containers
operator[]: Chức năng: Truy cập phần tử được chỉ định mà không có kiểm tra giới hạn Nếu chỉ số vượt quá giới hạn của mảng, hành vi của chương trình sẽ không được định nghĩa (undelned behavior), điều này có thể gây ra lỗi nghiêm trọng Cách sit dung: Array[index]
front(): Chức năng: Truy cập phần tử đầu tiên của mảng Phương thức này hữu ích khi bạn cần xử lý phần
tử đầu tiên mà không cần biết chỉ số của nó Cách sử dụng: array.tont()
back(): Chức năng: Truy cập phần tử cuối cùng của mắng Tương tự như ữont(), phương thức này cho phép bạn dễ dàng truy cập phần tử cuối cùng mà không cần quan tâm đến chỉ số Cách sử dụng: array.back()
data(): Chức năng: Truy cập trực tiếp đến bộ nhớ liên tục nơi lưu trữ các phần tử của mảng Hàm này trả về một con trỏ đến phần tử đầu tiên trong mảng, cho phép bạn thao tác trực tiếp với bộ nhớ của mảng, hoặc sử
Phương thức liên quan kiểm tra số lượng các phần tử trong mảng
empty(): Chức năng: Kiểm tra xem container có phải là rỗng không Nêu container không chứa phần tử nào, phương thức này trả về true; nếu không, nó trả về false Cách sử dụng: container.empty()
sỉze(): Chức năng: Trả về số lượng phần tử hiện có trong container Đây là số lượng phần tử mà container đang quản lý tại thời điểm gọi phương thức Cách sử dụng: container.size()
Ví dụ: Để biết được bao nhiêu phần tử có trong một std::list<double>, bạn có thể sử dụng auto mm elements
= Ist.size();
thống và kiến trúc Số này không phải là kích thước cố định mà là giới hạn trên lý thuyết, thường dựa trên các
giới hạn của bộ nhớ hoặc các hạn chế của kiểu dữ liệu chỉ số Cách sử dụng: container.max_size()
Ví dụ: Để biết số lượng tối đa các phần tử mà một std::vector<char> có thể chứa, bạn có thể gọi auto max_possible
= vec.max size();
Một số phương thức khác
fill(): Chức năng: Phương thức ñIl dùng để điền (thay thế) tất cả các phần tử trong container bằng một giá trị xác định Điều này là hữu ích khi bạn cần thiết lập hoặc đặt lại tất cả các phần tử của container về một trạng thái nhất định Cách sử dụng: container.ñII(valne)
Ví dụ: Giả sử bạn có một std::array<int, 10> và muốn tất cả các phần tử được thiết lập giá trị là 5, bạn có thể stt dung arr.fill(5);
Luu 9: Phuong thite fill chi cé sn cho céc container c6 kich thude cố định như std::arrag Nó không khả dụng cho các container nhu std::vector hodc std::deque mé& chiing cé caéc phuong thitc khaéc nhu std::fill te thu vién
<algorithm> dé dat dugc két qua tuong tu
Trang 16
Trang 186.1 Sequence containers
swap(): Chức năng: Phương thức swap trao đổi nội dung của hai container cùng loại và cùng kích thước Điều này rất hữu ích trong nhiều tình huống, chẳng hạn như khi bạn muốn tái cấu trúc dữ liệu hoặc nhanh chóng thay đổi giữa hai trạng thái dữ liệu Cách sử dụng: container1.swap(container2)
Dùng để áp dụng function hàng loạt cho một dãy các phần tử
Độ phức tạp của các hàm thành viên có thể được tìm thấy tại [2]
- Vi du:
#include <iostream>
#include <array>
int mainQ® f{
std::array<int, 5> arr2 = {1, 2, 3, 4, 5}; // Initializer list
std::array<int, 5> arr4;
arr4 = {10, 20, 30, 40, 50}; // Initializer list assignment
std::array<int, 5> arrS = std::move(arr4); // Move constructor
for (const auto& value : arr4) {
Đặc điểm: vector lưu trữ các phần tử trong một mảng liên tục, cho phép mở rộng kích thước một cách hiệu quả
Để sử dụng cấu trúc vector, ta cần chỉ thị #include <vector>
Cài đặt chỉ tiết vector [14|
Các hàm thành viên
- Các construcbor, destruetor, toán tử và hàm liên quan đến việc gán giá trị
- Cac iterator: tuong tu cdc iterator
- Các hàm dùng để truy cập và làm việc với các phần tử: Tương tự array
- Các phương thức liên quan kiểm tra số lượng các phần tử trong mảng: tương tự array nhưng có thêm một số
các hàm như:
Trang 17
Trang 196.1 Sequence containers
reserve(): Chức năng: được sử dụng để cấp phát trước một lượng bộ nhớ nhất định cho vector Điều này giúp tăng hiện quả bộ nhớ và hiệu suất bằng cách giảm số lần vector phải mở rộng bộ nhớ khi thêm các phần tử mới Cách stt dung: vector.reserve(size_type n)
capacity(): Chức năng: trả về số lượng tối đa các phần tử mà vector hiện tại có thể chứa mà không cần phải phân bổ thêm bộ nhớ Đây không phải là số lượng phần tử thực tế trong vector, mà là dung lượng bộ nhớ đã được cấp phát Cách sử dụng: size type vector.capaeity()
thước thực tế cần thiết để chứa các phần tử hiện tại, nhằm giảm bớt việc sử dụng bộ nhớ không cần thiết Cách
sử dụng: vector.shrink to_ñ1()
Các hàm thao tác khác:
clear(): Chức năng: Xóa tất cả các phần tử khỏi vector, để lai vector rong ma khong thay đổi dung lượng đã cấp phát Cách sử dụng: vector.elear()
tor.insert(position, value) hoặc vector.insert(position, count, value)
insert_range() (C++23): Chức năng: Chèn một dãy các phần tử vào veetor tại vị trí chỉ định Cách sử dụng: vector.insert_range(position, start, end)
emplace(): Chức năng: Tạo mới một phần tử trực tiếp tại vị trí chỉ định trong vector mà không cần sao chép hoặc đi chuyển Cách sử dụng: vector.emplace(position, args )
tor.erase(position) hoặc vector.erase(start, end)
push back(): Chức năng: Thêm một phần tử vào cuối vector Cách sử dụng: vector.push_baek(value)
emplace back(): Chức năng: Tạo mới và thêm một phần tử vào cuối vector mà không cần sao chép hoặc di chuyển Cách sử dụng: vecbor.emplace back(args )
append range() (C+ +23): Chức năng: Thêm một dãy các phần tử vào cuối vector Cách sử dụng: vector.append_range(sta end)
pop_back(): Chức năng: Xóa phần tử cuối cùng khỏi vector Cách sử dụng: vector.pop_back()
resize(): Chức năng: Thay đổi số lượng phần tử của vector, có thể thêm hoặc bớt các phần tử để đạt kích
hoặc vector.resize(new_size, value)
Trang 18
Trang 206.1 Sequence containers
swap(): Chức năng: Trao đổi nội dung của hai vector cùng loại Cách sử dụng: vector.swap(other_vector)
Độ phức tạp của các hàm thành viên có thể được tìm thấy tại [14]
// Range constructor: constructing a vector from the range denoted by iterators
stđ::vector<int> v7(v5.begin(), v5.end());
// Copy assignment operator: assigning the contents of v7 to v8
// Output the elements of v9
for (int val : v9) f
Trang 21Khái niệm: deque cho phép thêm hoặc xóa các phần tử từ cả hai đầu của container
Đặc điểm: Khác với vector, deque không yêu cầu tất cả các phần tử phải được lim trữ liên tục deque hỗ trợ truy cập ngẫu nhiên nhanh chóng và là lựa chọn tốt cho các hàng đợi có thể phát triển hoặc co lại từ cả hai phía
Để sử dụng cấu trúc deque ta cần chỉ thị #include <deque>
Cài đặt chi tiết deque [3]
Các hàm thành viên
- Các construcbor, destruetor, toán tử và hàm liên quan đến việc gán giá trị
- Cac iterator: tuong tu cdc iterator
- Các phương thức liên quan kiểm tra số lượng các phần tử: empty(), size(), max size(), shrink_to_ñt() tương tự
như vector
Các phương thức khác:
- elear(), insert(), insert range(), emplace(), erase(), push back(), emplace back(), append range(), pop_back(), resize(), swap() tương tự như vector
push front(): Chức năng: Chèn một phần tử vào đầu deque Cách sử dụng: deque.push_front(value)
emplace_front(): Chức năng: Tạo mới và chèn một phần tử vào đầu deque Cách sử dụng: deque.emplace_ữont(args )
prepend range() (C++23): Chức năng: Thêm một dãy các phần tử vào đầu deque Cách sử dựng: deque.prepend_range (start, end)
pop front(): Chức năng: Xóa phần tử đầu tiên khỏi deque Cách stt dung: deque.pop_front()
Độ phức tạp của các hàm thành viên có thể được tìm thấy tại [3]
Trang 22// Range constructor: constructing a deque from the range denoted by iterators
std::deque<int> dq7(dq5.begin(), dq5.end());
// Copy assignment operator: assigning the contents of dq7 to dq8
// Output the elements of dq9
for (int val : dq9) f
Khái niệm: forward list là một danh sách liên kết đơn, chỉ cho phép di chuyển về phía trước qua danh sách
Dac diém: forward list tiêu thụ ít bộ nhớ hơn list vì nó chỉ chứa một liên kết cho mỗi nút Container này thích
hợp cho các tác vụ yêu cầu các thao tác chèn và xóa nhanh chóng và không yêu cầu truy cập hai chiều
Để sử dụng lớp forward list, ta cần chỉ thị #include <forward list>
Cài đặt chi tiét forward_list [4]
Cac ham thanh vién
- Cac constructor, destructor, toan tit va ham liên quan đến việc gán giá trị
- Cac iterator: beforebegin, cbeforebegin, begin, cbegin, end, cend
- Các hàm dùng để truy cập và làm việc với các phần tử: front(): tương tự array
- Các phương thức liên quan kiểm tra số lượng các phần tử: empty(), max_ size() tương tự như array
- Các phương thức điều chỉnh(modifier): clear(), push_ ront(), emplace_ front(), prepend_ range(Q, pop_front(), resize(), swap(), tương tự deque
Trang 21
Trang 236.1 Sequence containers
insert_after(): Chức năng: Chèn một hoặc nhiều phần tử ngay sau một phần tử đã cho Cách sử dụng:
fw_list.insert_after(position, value) hoặc fw _list.insert_after(position, count, value)
emplace after(): Chức năng: Tạo mới và chèn một phần tử ngay san một phần tử đã cho mà không cần sao chép hoặc đi chuyển dữ liệu Cách sử dụng: fw list.emplace afier(position, args )
insert range_after() (C+ +23): Chức năng: Chèn một dãy các phần tử ngay sau một phần tử đã cho Cách
stt dung: fw_list.insert_range_after(position, start, end)
erase after(): Chức năng: Xóa một hoặc nhiều phần tử ngay san một phần tử đã cho Cách sử dụng:
fw_list.erase_after(position) hoặc fw_list.erase_afber(start, end)
Các phương thức thao tác:
merge(): Chức năng: Gộp hai forward list đã được sắp xếp thành một danh sách duy nhất, vẫn duy trì thứ tự sắp xếp Cách sử dụng: fw list1.merge(list2) Điều kiện: Cả hai danh sách phải được sắp xếp trước khi gọi hàm nay Sau khi gộp, fw list2 sẽ trống
splice after(): Chức năng: Di chuyển phần tử từ một forward list khác vào sau một phần tử chỉ định trong
danh sách hiện tại Cách sử dụng: fw _list.splice_ after(position, other_fw_ñst, it) hoặc các biến thể khác như di chuyển toàn bộ danh sách hoặc một phần của danh sách khác
remove() và remove if(): Chức năng: Xóa các phần tử dựa trên giá trị cụ thể (remove) hoặc xóa các phần tử thỏa mãn một tiêu chuẩn nhất định (remove if) Cách sử dụng: fw_list.remove(value) hoặc fw _list.remove_ if(predicate),
với predicate là một hàm hoặc lambda biểu thị điều kiện
reverse(): Chức năng: Đảo ngược thứ tự của các phần tử Cách sử dụng: fw list.reverse()
unique(): Chức năng: Loại bỏ các phần tử trùng lặp liên tiếp trong danh sách Có thể cung cấp một hàm so sánh để định nghĩa "trùng lặp" Cách sử dụng: fw_list.unique() hoặc fw_list.unique(binary_predicate)
sort(): Chức năng: Sắp xếp các phần tử trong forward list Bạn cũng có thể cung cấp một hàm so sánh để sắp xếp theo tiêu chí tùy chỉnh Cách sử dụng: fw_list.sort() hoặc fw_list.sort(compare fuinetion)
Độ phức tạp của các hàm thành viên có thể được tìm thấy tại [4]
Trang 24// Output the elements of £18
for (int val : £18) {
Đặc điểm: list hỗ trợ các phép duyệt hai chiều và không yêu cầu các phần tử của nó phải liên tục trong bộ nhớ
Phù hợp cho các tác vụ mà việc chèn hoặc xóa là phổ biến hơn so với việc truy cập các phần tử
Để sử dụng lớp list ta cần chỉ thị #include <1ist>
Cài đặt chỉ tiết list [5]
Các hàm thành viên
- Cac iterator: tuong tu cdc iterator
Trang 23
Trang 256.1 Sequence containers
- Các hàm dùng để truy cập và làm việc với các phần tử: front(), back() tương tự như array
- Các phương thức liên quan kiểm tra số lượng các phần tử: empty(), size(), max_size() tương tự như Array
- Các phương thức điều chỉnh(modifier): clear(), insert(), insert_range(), emplace(), erase(), push_back(), emplace_back(), append _range(), pop back(), push_front(), emplace front(), prepend_range(), pop_fromt(), resize(), swap() tương
tự như Deque
- Các phương thức thao tác(operation):
merge(): Cả list và forward list đều có phương thức này Chức năng của chúng là như nhau, cả hai đều dùng
để hợp nhất hai danh sách đã được sắp xếp thành một danh sách duy nhất đã sắp xếp mà không cần sao chép các phần tử
splice(): Trong list, splice có thể di chuyển một phần hoặc toàn bộ danh sách khác vào một vị trí trong danh
sách hiện tại forward list chỉ có splice_after do cấu trúc liên kết đơn Cách sử dụng: list1.splice(iter, list2) hoặc
list1.splice(iter, list2, iter2) hoặc list1.splice(iter, list2, iter_start, iter_end)
rermove() và remove if(): Giống nhau ở cả hai loại danh sách, dùng để xóa các phần tử dựa trên giá trị hoặc một điều kiện xác định
hơn trên list do cấu trúc liên kết đôi
unique(): Cả hai loại danh sách đều loại bỏ các phần tử trùng lặp liên tiếp Tuy nhiên, list có lợi thế hơn do khả năng truy cập tự do hai chiều
sort(): Cả lisi và forward list đều có thể được sắp xếp bằng phương thức này list có thể hiệu quả hơn trong một số trường hợp do khả năng truy cập hai chiều
Độ phức tạp của các hàm thành viên có thể được tìm thấy tại BỊ
Trang 26// Range constructor: constructing a list from the range denoted by iterators
std::list<int> lst7(lst5.begin(), lst5.end());
// Copy assignment operator: assigning the contents of lst? to lst8
// Output the elements of lst9
for (int val : lst9) {
std::cout << val << " ";
Các lớp thuộc nhóm Associative containers g6m cac phần tử được lưu trữ tự động theo một thứ tự nào đó được
định nghĩa sẵn (ví dụ như tăng dần hay giảm dần)
Nhóm này gồm: set, map, multiset, multimap, hash set, hash map, hash_ mmltiset, và hash multimap[22]
Trong khuôn khổ chuyên đề này, chúng em chỉ trình bày set, map, multiset, và multimap là những associative containers hay được sử dụng nhất
Lớp set cung cấp một cấu trúc dữ liệu cho phép lưu trữ tập hợp các phần tử cùng kiểu dữ liệu theo một thứ tự nhất định, trong đó các phần tử là duy nhất Ví dụ, mảng có thể lưu trữ các phần tử trùng nhau như 1, 2, 3, 1,
2,2, 3, 3, 3, nhưng set chỉ có thể lưu các phần tử khác nhau: 1, 2, 3
Trong một set, giá trị của một phần tử cũng là khóa (key) nhận diện nó Giá trị của một phần tử trong set không thể thay đổi, nhưng có thể insert giá trị mới và remove giá trị trong set
Các phần tử trong một set thường được sắp xếp theo một trật tự yếu nghiêm ngặt (strict weak ordering) được xác định bởi các thành phan so sénh cia né (its internal comparison object) [18]
Để sử dụng lớp set, ta cần chi thi #include <set>
Lớp set được khai báo trong header <set> như sau|9|:
template<class Key,
class Compare = std::less<Key>,
Trang 25
Trang 276.2 Associative containers
3 class Allocator = std::allocator<Key>
4 > class set;
5 Namespace pmr {
6 template <class Key, class Compare = std::less<Key>>
7 using set = std::set<Key, Compare, stđ::pmr::polymorphic_allocator<Key>>;
a }
Các phương thức truy cập phần tử: theo như [9], lớp set không hỗ trợ việc truy cập phần tử
Các phương thức kiểm tra số lượng phần tử: empty(Q, size), max_size(): tương tự nhữ array
Các phương thức thao tác trên phần tử:
® insert(val): chèn phần tử có giá trị val vào set nếu chưa có giá tri val trong set
® crase(val): xóa phần tử có giá trị val khôi set
e ñnd(val): trả về một iterator trổ đến vị trí của giá trị val (nếu có) trong set, nếu không có trổ đến end()
e count(val): trả về số lượng của giá trị val trong set, do mỗi phần tử trong set là duy nhất nên hàm này chỉ trả về giá trị 0 hoặc 1
Độ phức tạp của các hàm thành viên có thể được tìm thấy tại [9]
Để sử dụng lớp multiset, ta sử dụng chỉ thị #include <set>
Lớp multiset được khai báo trong header <set> như sau: [8]
1 template<
2 class Key,
3 class Compare = std::less<Key>,
4 class Allocator = std::allocator<Key>
5 > class multiset;
Trang 26