####Chào các bạn đang theo dõi khóa học lập trình trực tuyến ngôn ngữ C++.
Trong bài học về STL containers, mình đã giới thiệu với các bạn một số class được sử dụng để tạo ra các container với những cách tổ chức dữ liệu khác nhau. Tuy nhiên, tập hợp các phần tử được chứa bên trong container vẫn có một trật tự nhất định (tùy vào thuật toán sắp xếp được chọn), và để duyệt qua các phần tử trong các container chúng ta sẽ sử dụng các STL Iterator tương ứng.
###STL Iterator
Một Iterator là một đối tượng có thể đi qua (iterate over) một container class mà không cần biết trật tự các phần tử bên trong mảng. Iterator còn là một cách để truy cập dữ liệu bên trong các container.
Các bạn có thể hình dùng Iterator giống như một con trỏ trỏ đến một phần tử nào đó bên trong container với một số toán tử đã được định nghĩa:
- Operator* cereference và trả về giá trị bên trong container tại vị trí mà iterator được đặt.
- Operator++ di chuyển iterator đến phần tử tiếp theo trong container.
- Operator-- ngược lại so với operator++.
- Operator== và operator!= dùng để so sánh vị trí tương đối của 2 phần tử đang được trỏ đến bởi 2 iterator.
- Operator= dùng để gán vị trí mà iterator trỏ đến.
####Khai báo một Iterator
Với mỗi container class chúng ta sẽ có một kiểu iterator tương ứng. Mình sẽ lấy ví dụ về iterator của class std::vector như sau:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
std::vector<int> vec;
std::vector<int>::iterator iter;
return 0;
}
Như vậy, chúng ta có iter
là một đối tượng của class std::vector<int>::iterator
. Với cách khai báo này, chúng ta có thể là có một class tên là iterator được định nghĩa bên trong khối lệnh của class std::vector<generic_type>
.
Lưu ý: kiểu dữ liệu của các phần tử trong container class sẽ là kiểu dữ liệu được dùng cho generic_type
của iterator.
Ví dụ khi chúng ta muốn có một Iterator dùng cho một std::list<Employee>
thì Iterator sẽ được khai báo như sau:
std::list<Employee>::iterator iter;
####Các phương thức trả về kiểu Iterator của các container class
Như các bạn đã biết, một giá trị muốn được gán cho một biến (hoặc một đối tượng nào đó) thì giá trị và biến đó phải cùng kiểu dữ liệu với nhau. Như vậy, muốn gán địa chỉ (vị trí) của một phần tử trong container cho một iterator thì chúng ta cũng cần có những phương thức trả về giá trị kiểu iterator tương ứng.
Mỗi container class trong STL (ngoại trừ các container đặc biệt như std::stack và std::queue
) đều chứa định nghĩa của một iterator bên trong.
Và những container có chứa định nghĩa class iterator sẽ có những phương thức trả về giá trị kiểu iterator tương ứng:
- begin() trả về một iterator đại diện cho vị trí của phần tử đầu tiên trong container.
- end() trả về một iterator đại diện cho vị trí đứng ngay sau phần tử cuối cùng trong container.
- cbegin() trả về một hằng (read-only) iterator đại diện cho vị trí của phần tử đầu tiên trong container.
- cend() trả về một hằng (read-only) iterator đại diện cho vị trí đứng ngay sau phần tử cuối cùng trong container.
Ví dụ:
int main()
{
std::vector<__int32> vec;
for (int i = 0; i < 10; i++)
vec.push_back(i);
std::vector<__int32>::iterator the_beginning = vec.begin();
std::vector<__int32>::iterator the_end = vec.end();
std::cout << *(the_beginning._Ptr) << std::endl;
std::cout << *(the_end._Ptr) << std::endl; //print a garbage value
the_end--;
std::cout << *(the_end._Ptr) << std::endl;
return 0;
}
Lần in giá trị thứ 2 trong đoạn chương trình trên sẽ in ra một giá trị rác, vì iterator the_end lúc này đang trỏ đến vị trí nằm ngoài giới hạn vùng nhớ tương ứng với vec[vec.size()]
, trong khi chỉ số của phần tử cuối cùng trong container là (vec.size() - 1)
.
Tại sao end() lại trả về vị trí đứng sau phần tử cuối cùng trong container?
Đó là vì chúng ta sẽ sử dụng iterator này để kiểm tra xem thử chúng ta đã duyệt hết phần tử trong mảng hay chưa. Ví dụ:
std::vector<__int32> vec;
for (int i = 0; i < 10; i++)
vec.push_back(i);
std::vector<__int32>::iterator iter = vec.begin();
while (iter != vec.end())
{
std::cout << *iter << std::endl;
iter++;
}
Khi vòng lặp while kiểm tra đc iter đã lặp đến vị trí end(), nó sẽ hiểu rằng iter đã trỏ ra ngoài giới hạn của container và kết thúc vòng lặp.
Mình lấy thêm một ví dụ khác sử dụng iterator để duyệt qua tất cả phần tử bên trong một std::map
:
#include <iostream>
#include <map>
#include <string>
int main()
{
std::map<int, string> mymap;
mymap.insert(std::make_pair(4, "apple"));
mymap.insert(std::make_pair(2, "orange"));
mymap.insert(std::make_pair(1, "banana"));
mymap.insert(std::make_pair(3, "grapes"));
mymap.insert(std::make_pair(6, "mango"));
mymap.insert(std::make_pair(5, "peach"));
std::map<int, string>::const_iterator it; // declare an iterator
it = mymap.begin(); // assign it to the start of the vector
while (it != mymap.end()) // while it hasn't reach the end
{
std::cout << it->first << "=" << it->second << " "; // print the value of the element it points to
++it; // and iterate to the next element
}
std::cout << std::endl;
}
###Tổng kết
STL Iterator cũng tương tự như một dạng con trỏ chỉ sử dụng cho các STL Container class tương ứng. Sử dụng các STL Iterator có thể giảm thiểu mối nguy hiểm cho chương trình thay vì phải sử dụng con trỏ cho các mảng dữ liệu. Khi sử dụng STL Iterator, chúng ta không cần quan tâm dữ liệu bên trong container được tổ chức như thế nào, mà chỉ biết kết quả khi sử dụng iterator để duyệt qua container.
Hẹn gặp lại các bạn trong bài học tiếp theo trong khóa học lập trình C++ hướng thực hành.
Mọi ý kiến đóng góp hoặc thắc mắc có thể đặt câu hỏi trực tiếp tại diễn đàn