Giáo trình C++ cơ bản
Trong project HelloWorld, hiện tại chúng ta quan tâm đến 2 phần chính:
• Header Files: dùng để chứa các phần khai báo class, khai báo hàm hoặc phần khai báo một
số hằng số được sử dụng cho chương trình. Các file được chứa trong phần Header thường
có phần đuôi mở rộng là .h, .hpp.
• Source Files: là nơi chứa các file định nghĩa các hàm, các class. Các file được đặt trong này
thường có dạng .cpp.
Bạn đang xem 20 trang mẫu của tài liệu "Giáo trình C++ cơ bản", để tải tài liệu gốc về máy hãy click vào nút Download ở trên
Tóm tắt nội dung tài liệu: Giáo trình C++ cơ bản
![Giáo trình C++ cơ bản Giáo trình C++ cơ bản](https://s1.tailieubachkhoa.net/rl6k7x3q9zfh00j8/thumb/2021/11/25/giao-trinh-c-co-ban_Wf0i4vBYH9.jpg)
Ở các bài trước, chúng ta đã biết về quy trình làm việc để tạo ra một chương trình C++, những công cụ cần thiết và IDE mà chúng ta sẽ sử dụng để phát triển chương trình. Đến đây chắc các bạn cũng đang háo hức muốn bắt tay vào viết một cái gì đó. Trong bài này, chúng ta sẽ cùng viết một chương trình mà bất cứ lập trình viên C++ nào cũng từng trải qua. Một chương trình huyền thoại mang tên "Hello World". Để bắt đầu viết chương trình, chúng ta cùng mở IDE Visual studio 2015 lên và tạo một project. Tại giao diện Start Page của Visual studio, các bạn click chọn New Project. Cửa sổ tạo project mới hiện ra, các bạn chọn Empty project, đặt tên cho project là HelloWorld. Sau đó, ở phần location các bạn có thể chọn đường dẫn thư mục để lưu project này vào. Nhấn chọn OK để hoàn tất việc tạo project mới. Ngay khi Visual studio thiết lập project bạn vừa tạo. Bạn có thể nhìn vào cửa sổ Solution Explorer (mặc định là bên trái) để xem cấu trúc tổ chức của một project như thế nào. 1.0 Viết chương trình đầu tiên PHẦN 1: C++ CƠ BẢN Project HelloWorld được Visual studio tổ chức dưới dạng cây thư mục để quản lý mã nguồn và tài nguyên. Trong project HelloWorld, hiện tại chúng ta quan tâm đến 2 phần chính: • Header Files: dùng để chứa các phần khai báo class, khai báo hàm hoặc phần khai báo một số hằng số được sử dụng cho chương trình. Các file được chứa trong phần Header thường có phần đuôi mở rộng là .h, .hpp. • Source Files: là nơi chứa các file định nghĩa các hàm, các class... Các file được đặt trong này thường có dạng .cpp. Bây giờ chúng ta cùng tạo file chương trình đầu tiên. Các bạn click chuột phải vào tên project ở trong khung Solution Explorer, chọn đến dòng Add và click chọn New Item... Trong cửa sổ Add New Item, các bạn chọn loại file cần thêm là C++ File (.cpp), đặt tên file ở textbox Name phía bên dưới. Để tạo một thói quen tốt, file này các bạn đặt tên là main.cpp sau đó click Add. Sau khi add file main.cpp xong, cùng nhìn lại phần tổ chức project trong cửa số Solution Explorer xem điều gì đang xảy ra. Chúng ta đã có thêm 1 file được đặt sẵn trong phần Source Files. Một file có đuôi mở rộng là .cpp luôn được đặt trong phần này. Phía bên phải là phần soạn thảo mã nguồn cho file main.cpp đã được mở sẵn. Như đã nói ở trên, file đầu tiên cần tạo cho project nên đặt tên là main.cpp để tạo một thói quen tốt, chắc các bạn sẽ thắc mắc và đặt câu hỏi tại sao? Trước hết, chúng ta cùng viết một ít mã lệnh cho HelloWorld program. Nhấn tổ hợp phím Ctrl + S để lưu lại những gì bạn đã viết. Ở mức độ hiện tại, mình chỉ yêu cầu các bạn viết theo những gì mình đã viết, chưa yêu cầu các bạn phải hiểu được những dòng mã trên có ý nghĩa gì. Sau khi lưu file main.cpp lại, chúng ta đã có được file mã nguồn C++ đầu tiên. Các bạn có thể muốn xem thử file main.cpp vừa được lưu đang nằm chổ nào. Để xem thư mục gốc của project, các bạn click chuột phải vào tên project HelloWorld trong cửa sổ Solution Explorer rồi chọn Open Folder in File Explorer. Và chúng ta thấy file main.cpp như trong hình bên dưới. Quay lại với màn hình làm việc của Visual studio. Các bạn click chuột vào menu item BUILD trên Tool bar, sau đó chọn Build Solution (hoặc nhấn phím F7). Thực hiện bước này, Visual studio sẽ biên dịch file main.cpp của bạn để tạo thành file object .obj, đồng thời liên kết file main.obj tạo thành file chương trình (có đuôi .exe). Chúng ta chuyển qua thư mục gốc của project chứa file main.cpp lúc nãy, double click vào thư mục Debug, chúng ta thấy file main.obj là kết quả của quá trình biên dịch mã nguồn. Quay lui thư mục chứa file main.cpp ban đầu, cùng chuyển lui một thư mục ngoài nữa. Chúng ta lại thấy một thư mục có tên là Debug khác. Vào trong thư mục Debug này, các bạn sẽ thấy file .exe đã được Visual studio tạo ra. Bây giờ, các bạn mở file HelloWorld.exe này bằng cách double click vào nó. Cùng xem kết quả xuất hiện trên màn hình. Như các bạn thấy, trong cửa số Console, chúng ta có một dòng chữ xuất hiện: "Hello World.", và một dòng gợi ý cho người dùng rằng: Hãy nhấn 1 phím bất kì để kết thúc chương trình. Ngoài cách chạy trực tiếp file HelloWorld.exe trong thư mục Debug, các bạn còn có thể chạy chương trình ngay trên màn hình làm việc của Visual studio, bằng cách click chuột phải vào tên project trong cửa sổ Solution Explorer -> Debug -> Start new instance. Và ta được kết quả tương tự khi chạy trực tiếp file HelloWorld.exe Vậy là chúng ta đã viết xong chương trình đầu tiên của khóa học lập trình C++. Bây giờ mình muốn quay lại vấn đề mình đã nói ở trên, đó là tại sao chúng ta lại nên đặt tên file đầu tiên cho project là main.cpp? Để giải thích vấn đề này, mình muốn các bạn nhìn lại mã nguồn của file main.cpp mà các bạn đã viết cùng mình để có cái nhìn tổng quan về cấu trúc của một chương trình C++ cơ bản. Các bạn hãy chú ý đến dòng 6 trong chương trình trên. Chúng ta thấy int main() Đó là dòng bắt buộc phải có nếu muốn mã nguồn C++ có thể hoạt động được. Main trong tiếng Anh khi dịch ra có nghĩa là chính, quan trọng. Trong ngôn ngữ C++, main là điểm khởi đầu cho một chương trình. Trong một thời điểm, máy tính của chúng ta chỉ có thể thực hiện 1 dòng lệnh. Và ở thời điểm chương trình C++ bắt đầu chạy, nó sẽ tìm tới nơi có khai báo là main để thực hiện mã lệnh ở trong đó. Mã lệnh mà chương trình thực hiện sẽ được đặt trong cặp ngoặc nhọn { và }. Vì thế, cấu trúc chương trình C++ mà bạn cần nhớ sẽ như bên dưới. Các bạn cần lưu ý, một chương trình C++ chỉ có duy nhất một hàm main. Các bạn sẽ thấy nhiều hàm main có cách khai báo khác nhau. Nhưng với việc bạn là người mới bắt đầu học C++, mình khuyến nghị các bạn nên sử dụng theo cách trong hình trên. int main() { } Chương trình của C++ sẽ thực hiện từng dòng lệnh trong cặp ngoặc nhọn { } ở phía sau hàm main một cách có thứ tựtừ trên xuống dưới. Một chương trình C++ bắt buộc phải có 1 hàm main, thế nên mình khuyên các bạn nên đặt tên file đầu tiên trong chương trình main.cpp, và file này sẽ chứa mã nguồn C++ có hàm main ở trong đó, sau này các bạn làm việc với 1 dự án có nhiều file thì sẽ không bị nhầm lẫn. Chào mừng các bạn đến với bài học tiếp theo trong khóa học lập trình C++ hướng thực hành. Trong bài trước, Viết chương trình C++ đầu tiên, chúng ta đã cùng nhau tạo 1 project có tên HelloWorld. Các bạn lưu ý rằng khi làm việc với Visual studio 2015 thì chúng ta làm việc trên 1 project chứ không làm việc với file mã nguồn đơn lẻ. Project HelloWorld hiện tại chỉ có một file có tên là main.cpp. Kết quả khi thực thi project này (bằng cách nhấn phím F5 để Debug) thì chúng ta được như hình bên dưới: Kết quả là một dòng chữ Hello World xuất hiện trên console. 1.1 Cấu trúc cơ bản của một chương trình C++ Khi các bạn Debug trên Visual studio 2015, có thể Visual studio sẽ download một số file PDB về làm tốn thời gian. Các bạn có thể tắt việc tự động download các file đó bằng cách làm theo các bước sau: Đưa chuột vào phần DEBUG trên Menu bar -> chọn Options and Settings... Chọn Symbols và bỏ dấu tick trong Symbol file (.pdb) locations đi Bây giờ chúng ta nhìn lại mã nguồn trong file main.cpp và mình sẽ phân tích chức năng của từng dòng code. /* HelloWorld program - written by Nguyen Chiem Minh Vu */ #include using namespace std; int main() { // We write some code inside main function. cout << "Hello World." << endl; // The "Hello World" string will be appear on screen. system("pause"); // Pause the program to see the result. return 0; } • Dòng 6: int main() Như đã nói ở bài trước, main là một hàm mà đi sau nó là một cặp dấu ngoặc nhọn { }, một điểm xuất phát cho một project của ngôn ngữ C++. Không cần biết một project C++ của bạn có bao nhiêu file, một khi project đã được build và liên kết các file thành một file thực thi (.exe), hệ điều hành sẽ thực thi những dòng lệnh trong phạm vi dấu ngoặc nhọn nằm sau hàm main một cách lần lượt từ trên xuống dưới. Với những bạn lần đầu viết code C++, các bạn có thể bỏ sót dòng này. Cùng xem thử Visual studio sẽ làm gì khi bạn không viết ra dòng int main() bằng cách đổi tên main thành một tên bất kỳ. Visual studio thông báo một lỗi nghiêm trọng LNK1561, và nó còn đưa thêm hướng dẫn để lập trình viên có thể tự sửa lỗi có nghĩa là điểm khởi đầu cần được định nghĩa. Chúng ta quay lại với đoạn mã nguồn có thể chạy được như lúc đầu bằng cách sửa lại tên hàm là int main(). • Dòng 7: // We write some code inside main function. Đây không phải là một dòng lệnh. Đây là một dòng comment, mục đích của comment trong code là để ghi chú lại những gì mình đang làm. Việc ghi chú này cần được thực hiện thường xuyên đối với những người mới học lập trình. Ghi chú giúp bạn ít bị rối và khó hiểu khi nhìn lại những đoạn code cũ và những người làm việc cùng nhóm với bạn cũng sẽ hiểu được bạn đang muốn làm gì. Một dòng comment bắt đầu với 2 dấu gạch chéo //. Bây giờ bạn thử tự viết cho mình vài dòng comment đi nào. Comment bạn có thể đặt ở bất kỳ vị trí nào trong mã nguồn (ngoại trừ chèn comment làm ảnh hưởng đến dòng lệnh) mà không bị báo lỗi. Vì khi biên dịch, compiler nhìn thấy dòng comment thì nó sẽ bỏ qua và không làm gì cả. • Dòng 9: cout << "Hello World." << endl; // The "Hello World" string will be appear on screen. Đây là một dòng lệnh và đi kèm sau đó là một dòng comment. Dòng lệnh này chính là thứ đã viết lên console dòng Hello World mà bạn đã thấy trong kết quả của chương trình. Một dòng lệnh phải được kết thúc bằng dấu chấm phẩy ";" Lệnh cout có tác dụng viết lên console tất cả những gì nằm trong cặp dấu ngoặc kép " ". Như các bạn thấy, chúng ta đặt 2 từ Hello và World bên trong cặp ngoặc kép nên nó đã được in ra màn hình console. • Dòng 11: system("pause"); // Pause the program to see the result. Tiếp tục là một dòng lệnh và đi kèm một dòng comment ở phía sau. Mục đích của dòng lệnh này là để dừng chương trình và xem kết quả trên màn hình console. Các bạn có thể xóa dòng này đi và chạy lại chương trình bằng cách nhấn phím F5 để kiểm chứng kết quả. Lúc này màn hình console hiện lên và tắt ngay lập tức. • Dòng 12: return 0; Là giá trị trả về của hàm main. Hàm main của chúng ta có từ khóa int đứng trước, có nghĩa là kiểu trả về của hàm main sẽ là một giá trị có kiểu int (integer - số nguyên). Giá trị trả về này do lập trình viên tự quy định. Kết quả hàm main sẽ hiển thị trong cửa sổ Output bên trong IDE sau khi bạn tắt chương trình HelloWorld đang chạy đi. Thông thường, dòng này sẽ đặt cuối cùng trong phạm vi cặp ngoặc nhọn { } phía sau hàm main. Các bạn có thể thay bằng một con số bất kì sao cho bạn có thể hiểu được rằng, khi chương trình kết thúc, nếu Output xuất hiện con số mà bạn đã chọn, điều đó có nghĩa chương trình của bạn hoạt động một cách bình thường. • Dòng 3 và 4: #include using namespace std; Đây là những dòng lệnh đặc biệt. Để có thể sử dụng dòng lệnh số 9 trong chương trình thì chúng ta cần có dòng lệnh số 3 và số 4 này. Mục đích của 2 dòng lệnh này là thêm thư viện có tên iostream và không gian tên std để tích hợp vào chương trình. Hay nói cách khác, vì lệnh cout được định nghĩa bên trong thư viện có tên iostream và bên trong không gian tên std nên chúng ta cần tích hợp 2 thứ đó vào chương trình. Đến đây các bạn sẽ thắc mắc là "Làm thế nào biết được dòng lệnh nào đã được định nghĩa bên trong thư viện nào?" Qua quá trình thực hành trong khóa học này, mình sẽ cùng các bạn sử dụng một số chức năng bên trong một số thư viện chuẩn do ngôn ngữ C++ đã định nghĩa sẵn và các bạn sẽ quen với việc tìm và sử dụng chức năng nào trong thư viện nào. Đây cũng là một đặc trưng của ngôn ngữ lập trình bậc cao. Chúng ta sử dụng lại những gì đã được định nghĩa sẵn giúp công việc lập trình của chúng ta dễ dàng hơn. • Dòng 1: /* HelloWorld program - written by Nguyen Chiem Minh Vu */ Đây cũng là một đoạn comment. Đoạn comment khác với dòng comment. Đoạn comment được đặt giữa cặp dấu /* và */ trong khi dòng comment đứng sau 2 dấu gạch chéo //. Chúng ta có thể có nhiều dòng comment trong 1 đoạn comment. Ví dụ: /* Đây là một dòng comment. Đây là một dòng comment khác. Các bạn thích viết bao nhiêu dòng comment giữa này cũng được. */ Mình đã giải thích xong chức năng và cách hoạt động của mã nguồn file main.cpp trong project HelloWorld. Có thể các bạn chưa thể hiểu hết được, nhưng đừng lo lắng về điều đó, chúng ta sẽ quen với việc sử dụng ngôn ngữ C++ khi thực hành nhiều và nếu cần thiết các bạn sẽ được những người làm khóa học này hỗ trợ trực tiếp. Bây giờ là lúc để hình dung về cấu trúc của chương trình C++ cơ bản mà chúng ta đã làm cùng nhau. Cấu trúc cơ bản của chương trình C++ Đâu tiên, chúng ta có hàm main int main() Sau đó, chúng ta có phần thân của hàm main là cặp dấu ngoặc nhọn đứng sau từ khóa main, cuối thân hàm main là giá trị trả về của hàm main. int main() { return 0; } Tiếp đến, chúng ta có những dòng lệnh đặt bên trong thân hàm main int main() { cout << "This is a command" << endl; return 0; } Bên cạnh những dòng lệnh, chúng ta còn có những dòng comment // This comment is located outside main function /* We can put comment everywhere in a C++ file */ int main() { // We are coding inside main function cout << "This is a command" << endl; return 0; } Và cuối cùng là những thư viện cần thiết để compiler có thể hiểu được những lệnh đã được định nghĩa sẵn trong ngôn ngữ lập trình C++ #include #include #include using namespace std; // This comment is located outside main function /* We can put comment everywhere in a C++ file */ int main() { // We are coding inside main function cout << "This is a command" << endl; return 0; } Các bạn lưu ý là không nên include cả đống thư viện chưa cần dùng đến nhé. Cần dùng lệnh gì đã được định nghĩa sẵn mới cần include vào. Tất nhiên khi thêm nhiều thư viện vào chương trình thì IDE sẽ không báo lỗi vì compiler biết thư viện nào được dùng, nhưng chương trình của chúng ta sau khi build ra sẽ nặng hơn. Nếu các bạn sử dụng những lệnh được định nghĩa trong thư viện mà không include nó vào thì IDE sẽ báo lỗi ngay. Ví dụ mình bỏ dòng #include đi, IDE sẽ báo lỗi như hình bên dưới: Như các bạn thấy, cả lệnh cout, system(""), và endl đều được định nghĩa bên trong thư viện iostream nên khi xóa thư viện đó đi, chương trình gạch chân màu đỏ các từ đó, đồng thời thông báo lỗi trong cửa sổ Output. Tổng kết Trong bài học hôm nay, chúng ta đã biết thêm một số điểm đáng chú ý khi làm việc với chương trình C++: • Một dòng comment sẽ đứng sau 2 dấu gạch chéo //. • Một đoạn comment sẽ nằm giữa cặp /* và */. • Một dòng lệnh phải được kết thúc bằng dấu chấm phẩy ";" • Cấu trúc của một chương trình C++ cơ bản: o Hàm main: (bắt buộc phải có) ▪ Kiểu trả về của hàm main (int). ▪ Tên của hàm main (cũng là main luôn). ▪ Thân của hàm main (cặp dấu ngoặc nhọn { và }). ▪ Giá trị trả về của hàm main (return 0; //hoặc giá trị bao nhiêu cũng đc). o Những dòng lệnh bên trong thân hàm main. (Có thể có hoặc không) o Nh ... cout << "Number of elements: " << arr.size() << endl; Phương thức empty() trả về giá trị true nếu mảng bên trong đối tượng arr có số lượng phần tử là 0. • Truy xuất đến phần tử đầu tiên và phần tử cuối cùng của mảng bên trong đối tượng của lớp array: • cout << "The first element: " << arr.front() << endl; cout << "The last element: " << arr.back() << endl; Ví dụ mảng một chiều của mình được khởi tạo giá trị là 1 2 3 4 5. Kết quả in ra màn hình sẽ là: Phương thức front() sẽ trả về giá trị của phần tử đầu tiên trong mảng, ngược lại, phương thức back() sẽ trả về giá trị của phần tử cuối cùng trong mảng. Nhập dữ liệu cho đối tượng của lớp array Tương tự lúc các bạn nhập dữ liệu cho mảng một chiều thông thường, chúng ta sử dụng đối tượng cin để đưa giá trị được nhập từ bàn phím vào trong mỗi phần tử mà đối tượng của lớp array đang nắm giữ. for (int i = 0; i < arr.size(); i++) { cout << "Enter value to element " << i + 1 << ": "; cin >> arr[i]; } Lớp array ngăn chặn hành vi truy cập phần tử có chỉ số không phù hợp Chúng ta chỉ có thể truy xuất đến các phần tử trong đối tượng của lớp array với chỉ số trong phạm vi từ 0 đến (size() - 1). Sau đây là những hành vi truy xuất hợp lệ: #define ARRAY_SIZE 10 array arr; arr.assign(10); //Access to all of elements of arr object for (int32_t index = 0; index <= arr.size() - 1; index++) { cout << arr[index] << " "; } cout << endl; Và dưới đây là một số hành vi truy xuất giá trị của đối tượng arr bằng những chỉ số không hợp lệ: //Try to access array with wrong index arr[-1]; arr[arr.size() + 10]; Khi gặp những dòng lệnh này, compiler sẽ đưa ra cảnh báo: Vì bên trong lớp array có sử dụng thư viện cassert để đặt ra những Assertion, những Assertion này kiểm tra về chỉ số mà bạn đưa vào cho toán tử [ ] và phương thức at() để kiểm tra sự hợp lệ của chỉ số trước khi thực hiện lệnh. Mọi hành vì không phù hợp với điều kiện trong Assertion sẽ bị ngăn chặn. Các bạn cũng có thể tự mình tạo ra những Assertion bằng cách sử dụng thư viện cassert. Thư viện cassert Thư viện cassert cung cấp cho chúng ta macro có tên là assert(expression) giúp chúng ta tạo ra những Assertion trong chương trình. Khi gặp macro assert(expression), chương trình sẽ kiểm tra biểu thức expression (là một biểu thức điều kiện có thể trả về giá trị true/false) và có hai trường hợp có thể xảy ra: • expression trả về giá trị true: Chương trình sẽ tiếp tục thực hiện các dòng lệnh phía sau Assertion một cách bình thường. Ví dụ: float f_value = 1.0; assert(typeid(f_value) == typeid(float)); f_value++; cout << f_value << endl; Đoạn chương trình trên có 1 Assertion thực hiện công việc kiểm tra kiểu dữ liệu của biến f_value. Vì biểu thức typeid(f_value) == typeid(float) trả về giá trị true, nên chương trình vẫn được tiếp tục hoạt động. • expression trả về giá trị false: Chương trình sẽ dừng lại tại thời điểm phát hiện biểu thức bên trong Assertion cho giá trị false. #define ARRAY_SIZE 5 array arr; for(int32_t index = 0; index <= arr.size(); index++) { assert(index >= 0 && index <= arr.size() - 1); cin >> arr[index]; } Đoạn chương trình trên thực hiện nhập dữ liệu cho đối tượng arr có kiểu array. Bên trong vòng lặp for, mình đặt 1 Assertion nhằm kiểm tra chỉ số của mảng có được cung cấp chính xác hay không. Chỉ số chính xác sẽ nằm trong khoảng từ 0 đến (arr.size() - 1). Bây giờ mình sẽ chạy đoạn chương trình trên để xem kết quả: 4.png?raw=true793x372 Ngoài việc chương trình đưa ra cửa sổ thông báo lỗi và bắt các bạn Abort chương trình đang chạy, trên cửa sổ console còn đưa ra thông báo lỗi tại dòng mình đặt Assertion. Trước khi lỗi xảy ra, mình vẫn nhập dữ liệu bình thường. Vì lúc đó chỉ số index của vòng lặp for vẫn thõa mãn biểu thức điều kiện bên trong Assertion. Nhưng mà vòng lặp for của mình lại lặp với biến index chạy từ 0 đến arr.size(), vì thế, giá trị index tại lần lặp cuối cùng đã vi phạm biểu thức trong Assertion mà mình tự đặt ra. Tổng kết Trong bài học hôm nay, các bạn đã được tìm hiểu thêm thư viện array hổ trợ cho các bạn quản lý mảng một chiều một cách hiệu quả và dễ dàng hơn. Mình cũng đã hướng dẫn cho các bạn cách để tạo ra những Assertion cho chương trình của các bạn với thư viện cassert. Bất cứ khi nào các bạn cần đảm bảo chương trình của các bạn không vi phạm quy tắc nào đó, các bạn có thể dùng macro assert(expression) của thư viện cassert để hạn chế những lỗi có thể xảy ra. Trong các bài học trước, mình đã giới thiệu đến các bạn về mảng một chiều trong ngôn ngữ C/C++. 5.3 Mảng hai chiều Mảng một chiều có thể được hiểu là một dãy các phần tử có cùng kiểu dữ liệu được đặt liên tiếp nhau trong một vùng nhớ, chúng ta có thể ngay lập tức truy xuất đến một phần tử của dãy đó thông qua chỉ số của mỗi phần tử. Bây giờ các bạn thử tưởng tượng nếu kiểu dữ liệu của mảng một chiều là mảng một chiều? Hay nói cách khác, chúng ta có một mảng chứa các mảng một chiều? Lúc này, chúng trở thành mảng 2 chiều. 2D Array Trước hết, mình cho các bạn xem lại hình ảnh minh họa cho mảng một chiều trên máy tính: Đây là mảng 1 chiều gồm có 5 phần tử được đánh chỉ số từ 0 đến 4. Và dưới đây là hình ảnh minh họa cho cách tổ chức dữ liệu mảng hai chiều: Đây là bảng câu đố của game Sudoku được tạo thành từ 9x9 ô vuông (9 dòng và 9 cột). Giả sử mình tách dòng đầu tiên của bảng game này ra đứng riêng biệt: Nó lại trở thành mảng 1 chiều có 9 phần tử. Vậy, mảng một chiều khi mô phỏng nó bằng hình ảnh, chúng ta chỉ thấy được 1 hàng ngang có nhiều cột phân chia thành các ô (tượng trưng cho các ô nhớ trong máy tính). Còn khi chúng ta nhìn vào mảng hai chiều, chúng ta thấy có nhiều hàng, mỗi hàng lại có nhiều cột, đặc biệt hơn là số lượng cột ở mỗi hàng đều bằng nhau. Ngôn ngữ C/C++ có hổ trợ cho chúng ta tổ chức dữ liệu theo dạng bảng như trên, hay thường gọi là mảng hai chiều. Thế thì khi nào chúng ta cần sử dụng mảng hai chiều trong chương trình máy tính? Trong thực tế, chúng ta gặp rất nhiều thứ được bố trí dưới dạng mảng 2 chiều. Dưới đây là một số ví dụ thực tế: • Phòng học: Như hình minh họa, chúng ta có một phòng học có 2 dãy bàn hàng ngang, mỗi dãy bàn ngang có thể đủ chổ cho 3 sinh viên. Như vậy mình gọi đây là mảng hai chiều 2x3 (2 hàng, 3 cột). • Bàn cờ vua: Bàn cờ vua là một bảng hình vuông có 8 hàng, mỗi hàng có 8 cột, tổng cộng có 64 ô vuông, mỗi ô có thể đặt 1 quân cờ. Chúng ta có thể gọi đây là một mảng hai chiều 8x8 (8 dòng, 8 cột). • Trò chơi Tic Tac Toe: Trò chơi này được chơi trên một bảng 3x3 (3 hàng, 3 cột). Nếu trò chơi này được mô phỏng trên máy tính, chúng ta có thể sử dụng một mảng hai chiều 3x3 để lưu trữ các kí tự 'x' hoặc 'o'. Qua một số hình ảnh minh họa như trên, hi vọng các bạn đã có thể hình dung được mảng hai chiều là như thế nào. Bây giờ mình sẽ đi vào chi tiết về cách khai báo, khởi tạo giá trị và cách sử dụng mảng hai chiều trong ngôn ngữ C++. Khai báo mảng hai chiều Đối với mảng một chiều, chúng ta chỉ cần khai báo số lượng phần tử (số lượng cột) cho một hàng duy nhất, do đó, khai báo mảng một chiều có dạng: [num_of_columns]; Ví dụ: int iArray[100]; //declare an array of integer can hold 100 elements Bây giờ, khi quản lý mảng hai chiều, chúng ta còn phải quan tâm thêm về số hàng mà mảng hai chiều cần cấp phát: [num_of_rows][num_of_columns]; Lưu ý, khi khai báo số lượng phần tử của mảng hai chiều, số hàng phải đặt trước số cột. Ví dụ: int array2D[3][5]; // 3x5 elements (3 rows, 5 columns) Có thể nói cách khác, mảng có tên array2D có kiểu dữ liệu int, mảng array2D gồm có 3 mảng một chiều, mỗi mảng một chiều trong đó có thể chứa được tối đa 5 phần tử. Khởi tạo mảng hai chiều Mình lấy lại ví dụ về mảng có tên array2D như trên, mình sẽ khởi tạo giá trị cho mảng như sau: int array2D[3][5] = { { 1, 2, 3, 4, 5 }, //row 1 { 6, 7, 8, 9, 10 }, //row 2 { 11, 12, 13, 14, 15 } //row 3 }; Do mảng array2D có 3 hàng, mỗi hàng lại là một mảng một chiều khác nhau, nên mình đã sử dụng cách khởi tạo của mảng một chiều, áp dụng cho mỗi hàng trong mảng hai chiều array2D. Các bạn có thể khởi tạo mảng hai chiều theo cách sau: int array2D[3][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; Nhưng mình vẫn khuyến khích các bạn sử dụng cách mình trình bày ở trước để tránh nhầm lẫn trong việc tổ chức dữ liệu. Những phần tử chưa được khởi tạo giá trị sẽ được gán bằng giá trị mặc định tùy vào mỗi kiểu dữ liệu khác nhau. Như ví dụ sau mình sử dụng kiểu int để khai báo mảng hai chiều: int seats[3][5] = { { 1, 2 }, //row 1 = 1, 2, 0, 0, 0 { 6, 7, 8 }, //row 2 = 6, 7, 8, 0, 0 { 11 }, //row 3 = 11, 0, 0, 0, 0 }; Tương tự mảng một chiều, nếu các bạn khởi tạo mảng hai chiều ngay khi khai báo, compiler có thể tự xác định số hàng cần cấp phát: int array2D[][4] = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 } }; Các bạn có thể bỏ trống phần khai báo số lượng hàng, nhưng không thể không khai báo số lượng cột. Truy cập các phần tử trong mảng hai chiều Lấy ví dụ mình có một mảng hai chiều có 3 hàng và 4 cột tạo thành bảng như sau: int board[3][4]; Để xác định tọa độ (ví trị) của một phần tử trong một mảng hai chiều, chúng ta cần xác định hai tham số là chỉ số dòng và chỉ số cột. Chúng ta truy cập vào chỉ số dòng trước và chỉ số cột sau. Ví dụ: board[1][2]; //Access element on row 2 and column 3 Thực hiện truy cập mảng board với chỉ số dòng là 1 và chỉ số cột là 2 sẽ trỏ đến ô nhớ tại dòng thứ 2 và cột thứ 3, do chỉ số của mảng sẽ bắt đầu từ 0. Tương tự, để truy cập phần tử của cùng của mảng hai chiều 3x4, chúng ta truy cập với chỉ số (2, 3). Để truy cập toàn bộ mảng hai chiều, chúng ta có thể sử dụng 2 vòng lặp: vòng lặp ngoài sẽ truy cập lần lượt các dòng, vòng lặp bên trong sẽ truy cập tất cả các cột của dòng hiện tại mà vòng lặp ngoài đang truy cập đến. int board[3][4] = { { 1, 1, 1, 1 }, { 2, 2, 2, 2 }, { 3, 3, 3, 3} }; for(int row = 0; row < 3; row++) { for(int col = 0; col < 4; col++) { cout << board[row][col] << " "; } cout << endl; } Nhập dữ liệu cho mảng hai chiều Cũng tương tự việc các bạn nhập dữ liệu cho mảng một chiều, chúng ta sử dụng đối tượng cin trong thư viện iostream. Các bạn chỉ cần lưu ý rằng khi thao tác với các phần tử trong mảng hai chiều, chúng ta phải cung cấp đủ 2 chỉ số (hàng và cột) thì mới xác định được địa chỉ phần tử mà chúng ta cần thao tác. cin >> [row_index][col_index]; Trong đó, row_index là chỉ số dòng của phần tử, col_index là chỉ số cột của phần tử. Ví dụ: int board[3][3]; for(int row = 0; row < 3; row++) { for(int col = 0; col < 3; col++) { cin >> board[row][col]; } } Tổng kết Trong bài học này, chúng ta đã cùng tìm hiểu về một cách tổ chức dữ liệu mới trên máy tính. Mảng hai chiều được sử dụng khá phổ biến để giải quyết một số thuật toán yêu cầu tối ưu như Quy Hoạch Động, bài toán đồ thị, ... Cũng có thể được sử dụng trong việc thiết kế một số trò chơi đơn giản, ví dụ game Minesweeper. Chúng ta sẽ còn ứng dụng nhiều về mảng hai chiều trong các bài học sau. Bài tập cơ bản 1/ Viết chương trình nhập dữ liệu cho mảng hai chiều có số dòng, số cột dương (tùy ý bạn). In ra màn hình kết quả là tổng của mỗi dòng trong mảng hai chiều bạn vừa nhập. Ví dụ mình nhập mảng hai chiều 3x3 như sau: 1 3 4 2 1 6 3 3 5 Kết quả in ra màn hình sẽ là: 8 9 11 Trong đó, 8 là tổng các giá trị trong dòng đầu tiên, 9 là tổng các giá trị của dòng thứ 2, 11 là tổng các giá trị của dòng thứ 3. 2/ Viết chương trình tìm kiếm sự xuất hiện của giá trị X nhập từ bàn phím trong mảng hai chiều. Trong bài học này, mình sẽ hướng dẫn các bạn thực hiện một số thao tác cơ bản với mảng hai chiều, cũng có thể coi đây là giải một số bài tập mẫu cơ bản, giúp các bạn hình thành tư duy giải các bài toán có thể giải quyết được bằng mảng hai chiều cơ bản. Tính tổng các phần tử trên đường chéo chính Trường hợp mảng hai chiều có đường chéo chính và đường chéo phụ chỉ tồn tại khi số hàng bằng số cột (có nghĩa là ma trận vuông). Khi đó, đường chéo chính có dạng: Đặc điểm của các phần tử nằm trên đường chéo chính của ma trận vuông là chỉ số hàng luôn bằng chỉ số cột. { a[i][i] | 0 <= i <= n-1 } 5.4 Các thao tác cơ bản với mảng hai chiều Giả sử số hàng (hoặc số cột) của ma trận vuông này là N, chúng ta chỉ cần sử dụng vòng lặp for để lặp từ giá trị 0 đến N-1, cứ mỗi lần lặp với biến vòng lặp index, chúng ta cộng dồn giá trị của phần tử Array[index][index] vào biến tổng nào đó. int main() { int myArr[100][100]; int level; cout << "Enter level of squared matrix: "; cin >> level; //input for (int row = 0; row < level; row++) { for (int col = 0; col < level; col++) { cin >> myArr[row][col]; } } //calculate int sum = 0; for (int index = 0; index < level; index++) { sum += myArr[index][index]; } //output cout << "Result: " << sum << endl; system("pause"); return 0; } Trong chương trình trên, mảng hai chiều myArr chưa được khởi tạo khi khai báo, nên mình phải cung cấp thông tin số hàng và số cột cụ thể cho compiler. Xóa một dòng trong mảng hai chiều Về phần input, chúng ta nhập dữ liệu bao gồm số hàng, số cột và giá trị của mỗi phần tử trong mảng hai chiều. Trong phần xử lý, chúng ta cần nhập số dòng cần loại bỏ khỏi mảng hai chiều. Mình chưa thiết kế phần xử lý trường hợp nhập sai số dòng. Sau đó, tương tự việc xóa một phần tử trong mảng một chiều, ở mảng hai chiều, một phần tử chính là một mảng một chiều. Do đó, chúng ta không phải ghi đè giá trị sau lên giá trị trước, mà chúng ta cần ghi đè dữ liệu của dòng sau lên dòng trước đó. int main() { int myArr[100][100]; int num_of_row, num_of_col; //input cout > num_of_row; cout > num_of_col; for (int row = 0; row < num_of_row; row++) { for (int col = 0; col < num_of_col; col++) { cin >> myArr[row][col]; } } //process int removeRow; cout << "Enter the row you want to remove: "; cin >> removeRow; //Overide the next row onto the previous row for (int row = removeRow; row < num_of_row - 1; row++) { for (int col = 0; col < num_of_col; col++) { myArr[row][col] = myArr[row + 1][col]; } } num_of_row--; //output for (int row = 0; row < num_of_row; row++) { for (int col = 0; col < num_of_col; col++) { cout << myArr[row][col] << " "; } cout << endl; } system("pause"); return 0; } Tổng kết Trên đây chỉ mới là một số thao tác cơ bản khi cần sử dụng đến mảng hai chiều. Hi vọng bài học này có thể giúp các bạn hiểu rõ hơn về bản chất của mảng hai chiều khi lưu trữ trong máy tính. Bài tập cơ bản Dựa trên chương trình xóa một dòng trong mảng hai chiều mà mình đã làm mẫu ở trên, các bạn hãy viết chương trình xóa một cột X được nhập từ bàn phím trong mảng hai chiều.
File đính kèm:
giao_trinh_c_co_ban.pdf