Sau khi chúng ta đã hoàn toàn làm chủ được Qt, chắc hẳn việc khám phá thêm 1 thư viện hàm sẽ không thể khiến các bạn chùn bước. Rồi các bạn sẽ thấy việc sử dụng các công cụ chuẩn không phải lúc nào cũng dễ dàng. Thế nhưng rồi các bạn cũng sẽ đồng ý với tôi là chúng giúp đoạn mã chúng ta viết ra trở nên vô cùng đơn giản và hiệu quả.
Thư viện chuẩn của C++ là thư viện hỗ trợ chính thức của ngôn ngữ này, nghĩa là ở bất cứ nơi nào mà chúng ta có thể dùng ngôn ngữ C++ thì chúng ta đều có thể sử dụng thư viện đó. Nghiên cứu và nắm giữ thư viện chuẩn cho phép chúng ta có thể làm việc trên tất cả các nền tảng dù cho chúng có đặc biệt đến đâu đi nữa, kể cả nơi mà những thư viện như Qt hoàn toàn không tồn tại.
Bài học mở đầu để giới thiệu về thư viện chuẩn (hay SL, viết tắt của Standard Library trong tiếng Anh) hẳn sẽ không quá khó hiểu với tất cả mọi người. Chúng ta sẽ bắt đầu nhẹ nhàng với 1 vài khái niệm mà các bạn có thể từng nghe nói qua. Trong bài học sau, chúng ta sẽ đào sâu hơn vào 1 phần lý thú của thư viện, đó là STL - Standard Template Library hay thư viện lớp mẫu chuẩn.
Trong phần đầu của giáo trình này, tôi từng nhắc đến 1 đoạn lịch sử của các ngôn ngữ lập trình nói chung để giải thích cho các bạn về vị trí của C++ trong thế giới này. Đoạn dưới đây là 1 chút kiến thức bổ sung để lý giải tại sao 1 ngôn ngữ lại cần sở hữu 1 thư viện chuẩn.
Hãy cùng tôi ngược dòng thời gian quay trở lại thời kỳ sơ khai của công nghệ thông tin, khi mà những chiếc đĩa Bluray hay thậm chỉ tổ tiên của chúng là những chiếc đĩa CD chưa được phát minh ra, nơi mà chuột máy tính không hề tồn tại và bộ xử lý của 1 chiếc máy tính thậm chí còn không mạnh mẽ bằng bộ xử lý của chiếc lò vi sóng hay chiếc tủ lạnh mà các bạn đặt trong phòng bếp…
Chúng ta đang ở năm 1979, khi 1 lập trình viên của AT&T tên là Bjarne Stroustrup đang phát triển ngôn ngữ C với các lớp, một phiên bản mở rộng của ngôn ngữ C, lấy ý tưởng từ các ngôn ngữ tiên tiến đương thời như Simula. Anh ta cũng tự viết là trình biên dịch đầu tiên cho ngôn ngữ mới này và dùng nó trong công việc của mình. Thời kỳ đó, chỉ có anh ta là người duy nhất sử dụng ngôn ngữ này vào các mục đích nghiên cứu của mình. Cái anh ta muốn là cải tiến ngôn ngữ C, thêm vào đó những công cụ mà anh ta cho là thiết yếu để đơn giản hóa công việc của mình.
Dần dần, càng có thêm nhiều đồng nghiệp của anh ta và những người khác bị hấp dẫn bới C và các lớp. Thế rồi những yêu cầu về tính năng mới xuất hiện. Năm 1983, khái niệm tham chiếu, nạp chồng hàm cũng như hàm ảo được thêm vào ngôn ngữ mà mọi người bắt đầu gọi là C++. Ngôn ngữ này bắt đầu giống như những gì mà ngày nay các bạn đang thấy. Thực ra, hầu hết những thứ chúng ta từng thảo luận trong chương 1 và 2 đều đã xuất hiện vào thời điểm đó. C++ càng trở nên thu hút và cuối cùng Stroustrup đã cho ra đời phiên bản thương mại của ngôn ngữ này vào năm 1985.
Năm 1989, phiên bản 2.0 của ngôn ngữ này ra đời. Nó mang đến các khái niệm về đa kế thừa, lớp trừu tượng và 1 vài đổi mới với các lớp. Tiếp đó là các khái niệm về bản mẫu (template) và ngoại lệ (exception) xuất hiện và ngôn ngữ trở nên giống đến 99% phiên bản mà chúng ta vẫn sử dụng ngày nay.
Cùng với những phát triển đó, thư viện chuẩn bắt đầu được định hình, ban đầu với sự xuất hiện của cout để thay thế hàm printf
và 1 số thứ khác ít thông dụng hơn của C. Phần thư viện này tên là iostream, chính là thứ mà chúng ta đã từng sử dụng những ngày đầu chập chững viết mã C++.
Dần dần các yếu tố khác cũng được thêm vào thư viện chuẩn ví dụ như STL. Thay đổi lớn lao này là 1 bước tiến vượt bậc của C++. Đây là lúc mà những thành phần hữu dụng như string hay vector xuất hiện. Ngày nay, thật khó mà hình dung ra các chương trình C++ không sử dụng đến các khái niệm này. Và tôi tin là những thành phần khác của STL cũng sẽ không làm các bạn thất vọng đâu.
Năm 1998, 1 ủy ban được thành lập và quyết định chuẩn hóa toàn cầu ngôn ngữ này, nghĩa là mỗi phiên bản của C++ đều phải đáp ứng được 1 số yêu cầu tính năng tối thiểu. Đấy chính là khuôn khổ của C++ ngày nay. Cũng chính thời điểm đó, thư viện chuẩn đã được hình thành và đưa vào tiêu chuẩn. Điều này có nghĩa là chúng ta có thể tìm thấy thư viện chuẩn trong mọi phiên bản trình biên dịch của C++. Đây chính là điểm mạnh của thư viện này. Nghĩa là bạn chỉ cần có 1 chiếc máy tính với trình biên dịch C++ là bạn có quyền sử dụng tất cả các hàm của thư viện chuẩn. Điều này không đúng với các thư viện khác nhiều khi chỉ tồn tại trên 1 hoặc 1 vài nền tảng nhất định.
Cuối cùng, năm 2011, C++ đã được xét lại 1 lần nữa để thêm vào 1 vài tính năng được mong đợi từ lâu, trong đó bao gồm việc thêm 1 số từ khóa, đơn giản hóa các lớp mẫu cũng như thêm nhiều tính năng vào thư viện chuẩn (ví dụ như thư viện boost).
Chuẩn mới này được gọi là chuẩn C++11. Trong phạm vi giáo trình này, chúng ta không bị ảnh hưởng nhiều bởi chuẩn này nên tôi đã không đề cập đến các thành phần của nó.
Đây là câu hỏi mà rất nhiều người đặt ra. Thật ra, nếu không có thư viện chuẩn thì C++ hầu như chẳng còn gì mấy. Các bạn hãy thử tưởng tượng 1 chương trình sẽ ra sao nếu không có string
, vector
hay cout
. Nếu điều này là thật, vậy sẽ đòi hỏi từng lập trình viên phải tự phát triển các hàm phục vụ các mục đích tương tự. Công việc của chúng ta vì thế sẽ trở nên rắc rối và nặng nề hơn nhiều.
Vì thế, thay vì từng người tự phát triển các tính năng đó cho riêng mình, các lập trình viên sẽ đồng ý với nhau về 1 số tính năng mà hầu như ai cũng sử dụng đến và đưa ra 1 phiên bản chung cho tất cả mọi người.
Tất cả các ngôn ngữ đều có thư viện chuẩn của riêng mình. Java thậm chí có cả thư viện để tạo ra các chương trình cửa sổ trong thư viện của nó trong khi trong thư viện của C lại hầu như không có gì cả. Khi làm việc với C, có rất nhiều xử lý chúng ta phải tự thực hiện. C++ nằm đâu đó giữa 2 ngôn ngữ này khi chỉ cung cấp những đối tượng đơn giản như string
hay vector
thay vì thậm chí cung cấp cả khả năng tạo ra giao diện đồ họa trong thư viện chuẩn. Chính vì thế mà chúng ta mới học Qt ở chương trước.
Vậy thôi, chúng ta cùng bắt tay vào thảo luận về thư viện chuẩn của C++ chứ nhỉ !
Đại khái thì thư viện chuẩn này có thể chia thành 3 bộ phận lớn mà chúng ta sẽ cùng tìm hiểu ít nhiều dưới đây.
! Việc phân loại này chỉ mang tính tương đối, tùy theo nguồn dữ liệu mà chúng ta sử dụng.
Trong thư viện chuẩn của C++ có 1 phần là thư viện chuẩn của C. 15 tệp tiêu đề C trong chuẩn năm 1990 đều nằm trong chuẩn của C++. Chính nhờ điều này, hầu hết các chương trình được viết bằng C đều có thể được sử dụng lại trong C++. Chúng ta sẽ điểm qua 1 vài gương mặt trong phần cuối của bài học này.
Trong phần thứ 2, chúng ta tìm thấy tất cả những công cụ thao tác với luồng, nghĩa là các thao tác giữa chương trình với môi trường bên ngoài. Đây là các lớp cho phép hiển thị các thông tin ra màn hình, nhập dữ liệu vào từ giao diện điểu khiển và đọc, ghi, xử lý nội dung tệp.
Các bạn nghe quen chứ hả ? Tôi sẽ không đi vào quá chi tiết trong phần này do chúng ta đều đã biết ít nhiều về nó.
Đúng vậy, đấy chính là kiến thức chúng ta đã học về cout
, cin
hay fstream
. Trong phần tiếp theo, tôi sẽ không trình bày nhiều về chúng với các bạn, chỉ 1 vài tính năng kiểu như sao chép nội dung tệp vào mảng chữ, vv… Các bạn sẽ thấy 1 vài thao tác nhức đầu có thể trở nên đơn giản đến mức nào khi chúng ta đã nắm vững các khái niệm.
Thư viện lớp mẫu chuẩn chắc chắn là bộ phận hấp dẫn nhất. Đây là bộ phận mã chứa định nghĩa của các lớp chứa kiểu như vector
, cho phép chứa đựng các đối tượng theo các tiêu chí khác nhau. Đấy cũng là nơi chứa mã các thuật toán tìm kiếm và sắp xếp tập hợp. Ngoài ra, trong này còn chứa các biến lặp cơ bản (iterator), con trỏ thông minh và vô vàn những thứ bí ẩn nhưng thú vị khác. Phần tiếp theo của bài học này chủ yếu là để nói về bộ phận này của thư viện.
Đương nhiên là không thể phân loại được hết tất cả mọi thứ. Trong thư viện chuẩn chúng ta có thể bắt gặp những ngoại lệ như string
, lớp này nằm giữa ranh giới của các luồng và STL. Bên cạnh đó là các công cụ để thao tác tỉ mỉ với bộ nhớ, các số phức, vv… mà chúng ta không thể xếp vào 3 lớp trên. Thế nhưng điều này cũng không thể ngăn cản việc tôi trình bày về chúng với các bạn.
Trong bài học về tra cứu tài liệu của Qt, chúng ta đã học cách sử dụng tài liệu và từ trong núi thông tin đó tìm ra các lớp và hàm hữu dụng. Một số bạn chắc mẩm rằng cũng sẽ không có gì khó và mọi thứ cũng sẽ diễn ra tương tự như với thư viện chuẩn. Tôi rất tiếc là phải khiến các bạn thất vọng nhưng thư viện chuẩn hoàn toàn không có thứ gọi là tài liệu sử dụng chính thức. Và điều này thì thật tệ.
Thực tế là cũng không hẳn là không có. Mô tả cụ thể của từng hàm đều được ghi lại trong chuẩn của ngôn ngữ. Đấy là 1 tài liều khoảng 800 trang, toàn bộ bằng tiếng Anh và cực kỳ khó nuốt. Lấy ví dụ về đoạn mô tả đối tượng vector
(trích chuẩn C++, mục 23.2.4.1) :
“A vector is a kind of sequence that supports random access iterators. In addition, it supports (amortized) constant time insert and erase operations at the end; insert and erase in the middle take linear time. Storage management is handled automatically, though hints can be given to improve efficiency. The elements of a vector are stored contiguously, meaning that if v is a vector<T, Allocator> where T is some type other than bool, then it obeys the identity &v[n] == &v[0] + n for all 0 <= n < v.size().”
Dù các bạn có chém tiếng Anh như tiếng mẹ đẻ thì cũng chưa chắc hiểu được gì nhiều. Đấy là chưa kể đây vẫn không phải là khái niệm khó nhằn nhất. Vậy nên theo tôi thì “đường này không thông” rồi.
May mắn là trên mạng Internet rộng lớn ngoài kia có 1 số trang web giải thích cho chúng ta về thư viện chuẩn. Tuy nhiên, chúng cũng được viết hoàn toàn bằng tiếng Anh.
Dưới đây là 1 vài trang tra cứu mà tôi thường xuyên sử dụng. Chúng không hoàn toàn đầy đủ nhưng đáp ứng được nhu cầu trong phần lớn trường hợp. Danh sách dưới đây được sắp xếp theo thứ tự từ dễ đến khó tra cứu.
Tôi khuyến khích các bạn dạo quanh 1 lượt để xác định xem trang nào thì hợp với phong cách của bản thân.
Đương nhiên, các bạn vẫn còn lựa chọn khác, đó là hoàn toàn tin tưởng tôi và khám phá mọi thứ ở trong bài học này. Đương nhiên là tôi không thể đề cập đến tất cả mọi thứ nhưng những điều chính yếu thì đảm bảo vẫn đủ cả.
C++, về khía cạnh nào đó, được coi là truyền nhân của C nên mọi thứ trong thư viện chuẩn của C đều nằm trong thư viện chuẩn của C++. 1 vài tiện ích trong số này vẫn thường xuyên được sử dụng. Số khác thì được thay thế bởi các phiên bản cải tiến hơn và 1 số còn lại thì trở nên lỗi thời. Để tiết kiệm thời gian, tôi đề nghị là chúng ta chỉ thảo luận về những thứ hữu dụng chứ nhỉ !
! Nếu các bạn đã làm việc với C thì chắc hẳn là biết về các tệp mà tôi đang nói đến. Khác biệt lớn nhất nằm ở tên của chúng. Ví dụ, thay vì là math.h
như chúng ta biết trong C, trong C++ sẽ có tên là cmath
. Phần mở rộng .h
đã biến mất và thêm vào đó là chữ “c” ở đầu.
Giống như phần còn lại của thư viện chuẩn, phần thư viện được kế thừa từ C được chia ra thành nhiều gói khác nhau tùy theo mức độ liên quan của các phương thức.
cmath
Các bạn hẳn là đã quen thuộc với gói này rồi. Chúng ta đã cùng tiếp xúc với nó ở phần đầu của giáo trình. Chính trong gói này, chúng ta tìm thấy các hàm toán học thường dùng. Để giúp các bạn nhớ lại là tôi tốt bụng đến mức nào, dưới đây là 1 số kiến thức tóm lược dành cho những bạn đã ngủ gật ở các bài học trước đó.
#include<iostream> #include<cmath> using namespace std; int main() { double a(4.3), b(5.2); cout << pow(a,b) << endl; //Tinh luy thua a^b cout << sqrt(a) << endl; //Tinh can bac 2 cua a cout << cos(b) << endl; //Tinh cos cua b return 0; }
Tôi thấy là cũng không có mấy bạn quên đâu nhỉ. Vậy tóm lại, đây là gói mà chúng ta cần sử dụng nếu muốn dùng đến các hàm toán học thông dụng. Tôi sẽ không ghi lại ra đây tất cả các hàm mà gói này hỗ trợ. Để có thể làm quen dần với tài liệu của thư viện chuẩn, tại sao các bạn không thử tự mình tìm tòi trong các đường dẫn mà tôi đã gửi nhỉ.
cctype
Gói này chứa các hàm để thao tác với các ký tự. Thật ra, nếu để ý kỹ, các bạn sẽ thấy là khi làm việc với các ký tự, chúng ta rất hay đặt đi đặt lại 1 số câu hỏi :
Các hàm thuộc cctype
dùng để trả lời cho những câu hỏi như vậy. Ví dụ, để kiểm tra xem 1 ký tự có phải là chữ số không, chúng ta sẽ dụng hàm isdigit()
.
#include <iostream> #include <cctype> using namespace std; int main() { cout << "Nhap vao 1 ky tu : "; char kyTu; cin >> kyTu; if(isdigit(kyTu)) { cout << "Day la 1 chu so." << endl; } else { cout << "Day khong phai la chu so." << endl; } return 0; }
Quá dễ để sử dụng phải không? Bảng dưới đây sẽ liệt kê các hàm nằm trong gói này. Đương nhiên, danh sách hoàn chỉnh nhất nằm trong đống tài liệu yêu quý mà tôi “phát” cho các bạn lúc trước.
Tên hàm |
Công dụng |
isalpha() |
Xác định xem 1 ký tự có phải là chữ cái không |
isdigit() |
Xác định xem 1 ký tự có phải là chữ số không |
islower() |
Xác định xem 1 ký tự có được viết thường không |
isupper() |
Xác định xem 1 ký tự có được viết hoa không |
isspace() |
Xác định xem 1 ký tự có phải là dấu cách hay ký tự xuống dòng không |
Thêm vào đó, chúng ta còn có 2 hàm tolower()
và toupper()
dùng để chuyển đổi giữa chữ viết hoa và viết thường.
Thật là dễ dàng nếu chúng ta muốn chuyển toàn bộ 1 đoạn văn bản thành viết hoa.
#include <iostream> #include <cctype> #include <string> using namespace std; int main() { cout << "Nhap vao 1 cau : " << endl; string cau; getline(cin, cau); //Duyet chuoi ky tu va chuyen doi tat ca thanh chu viet hoa for(int i(0); i<cau.size(); ++i) { cau[i] = toupper(cau[i]); } cout << "Cau cua ban da duoc chuyen doi tat ca thanh chu viet hoa : " <<cau << endl; return 0; }
Hoàn toàn không có gì là phép màu ở đây cả. Các hàm này đều rất dễ sử dụng. Vậy nên các bạn đừng ngại vui đùa 1 chút với chúng. Sao không thử làm 1 chương trình nho nhỏ thay tất cả dấu cách trong câu thành dấu thăng nhỉ ? Việc này hoàn toàn nằm trong lòng bàn tay các bạn.
ctime
Như có thể thấy trong tên gọi, gói này chứa các hàm cho phép chúng ta quản lý thời gian. Phần lớn trong số chúng khá kỳ là và ít khi được sử dụng đến. Dù sao thì phần lớn các thư viện khác đều bổ trợ cho thiếu sót này của thư viện chuẩn khi đưa ra các lớp quản lý ngày tháng năm 1 cách hoàn thiện hơn.
Về phần cá nhân tôi, hàm duy nhất mà tôi hay sử dụng trong gói này là time()
. Kết quả trả về của hàm này là số giây tính từ thời điểm 01/01/1970. Đây được gọi là cách tính thời gian theo giờ UNIX hay thời điểm Epoch.
#include <iostream> #include <ctime> using namespace std; int main() { int soGiay = time(0); cout << "Da co " << soGiay << " giay troi qua ke tu thoi diem 01/01/1970." << endl; return 0; }
! Hàm này chờ 1 tham số là con trỏ chúng ta truyền cho để lưu trữ kết quả tính nhưng đồng thời là nó cũng trả về kết quả đó. Thế là tham số con trỏ truyền vào trở nên vô dụng. Vậy nên trong đa số trường hợp, chúng ta truyền 1 con trỏ mà nó không trỏ đến đâu cả làm tham số.
Kết quả chúng ta nhận được là.
Quá nhiều giây đã trôi qua há !
? Vậy tác dụng cụ thể của nó là để làm gì ?
time()
2 lần vào đầu và cuối khi chương trinh thực thi và hiệu của chúng sẽ chính là tổng thời gian thực thi của chương trình của chúng ta.cstdlib
Lại thêm 1 người bạn cũ. Các bạn có từng nhớ, trong bài thực hành đầu tiên, tôi đã từng chỉ cho các bạn cách chọn ra 1 số ngẫu nhiên không. Chúng ta phải dùng hàm srand()
và rand()
.
Đây chắc chắn là gói được sử dụng nhiều nhất trong C. Nó chứa tất cả các viên gạch cơ bản mà chúng ta dùng để xây nên ngôi nhà chương trình của chúng ta. Tôi không thể nghĩ ra có 1 chương trình thực tế nào mà lại không sử dụng đến cstdlib.h
. Thế nhưng, trong C++, nó hầu như là chả mấy khi được động đến. Kiểu như MySpace và Facebook vậy. Ngoài việc sử dụng để chọn ra các số ngẫu nhiên, tất cả các chức năng khác của nó đã được thay thế bởi các hàm khác của C++.
Hãy cùng xem lại cách chúng ta chọn ra 1 số ngẫu nhiên. Hàm rand()
trả về kết quả là số ngẫu nhiên nằm giữa 0 và RAND_MAX
(vô cùng vô cùng vô cùng… lớn). Nếu chúng ta chỉ cần số ngẫu nhiên nằm giữa trong khoảng [0, 10[ chúng ta sẽ cần sử dụng đế phép toàn chia lấy dư với toán tử %
.
soNgauNhien = rand() % 10; // Chon so ngau nhien giua 0 va 10, khong tinh 10
Vấn đề là máy tính không biết làm cách nào để chọn ra 1 số được coi là ngẫu nhiên. Tất cả những gì nó làm được chỉ là chọn ra 1 chuỗi số « có vẻ là » ngẫu nhiên để rồi nhặt ra 1 số trong đó. Và nó cần 1 khởi điểm cho chuỗi này. Đây là lúc chúng ta sử dụng đến hàm srand()
, nó cho phép tạo ra khởi điểm cho chuỗi.
! Mỗi chương trình chỉ cần gọi hàm srand()
1 lần và chỉ 1 lần duy nhất.
Vấn đề là nếu mỗi lần chúng ta đều đưa ra 1 điểm khởi đầu giống nhau cho máy tính thì chuỗi số bán ngẫu nhiên nó tạo ra luôn luôn cố định không thay đổi. Vậy nên chúng ta cần truyền 1 tham số mà giá trị của nó khác nhau trong mỗi lần chương trình được thực thi. Vậy thứ gì là thứ thay đổi mỗi lần khác nhau mà chương trình thực thi ? Chính là thời điểm, hay ngày giờ, mà chương trình bắt đầu chạy. Vậy là chúng ta lại cần sử dụng đến hàm time()
.
#include <iostream> #include <ctime> #include <cstdlib> using namespace std; int main() { srand(time(0)); //Khoi tao chuoi ban ngau nhien for(int i(0); i<10; ++i) { cout << rand() % 10 << endl; // Chon so ngau nhien giua 0 va 10, khong tinh 10 } return 0; }
Tiếp theo thì sử dụng chuỗi ngẫu nhiên đó để làm gì là quyền của bạn, có thể làm bài TP, cũng có thể là đi đánh bạc. Quyết định hoàn toàn là quyền của bạn. Nguyên lý tạo số ngẫu nhiên hoàn toàn không thay đổi.
Trừ cassert
mà chúng ta sẽ nhắc đến sau, còn lại khoảng 15 gói khá ít khi được sử dụng trong C++. Vậy nên tôi sẽ không đề cập đến chúng trong giáo trình này.
Vậy là đề cập đủ đến các thánh tích mà chúng ta được thừa hưởng từ C rồi. Cho đến lúc này, tôi vẫn chưa hề trình bày cho các bạn các thay đổi cách mạng mà C++ mang đến cho chương trình so với C như đã hứa. Thật sự, chúng ta vẫn chưa động tí nào đến sức mạnh của C++ cả. Thế nhưng thắt chặt dây an toàn vào, chuyến du hành sẽ bắt đầu ngay thôi.
cctype
để thao tác với ký tự, ctime
để quản lý thời gian và cstdlib
để tạo ra các số ngẫu nhiên.