Xét trường hợp chúng ta muốn tìm điểm thi trung bình của các sinh viên trong một lớp. Sử dụng các biến riêng lẻ:

/**
* Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
*
* @author cafedevn
* Contact: cafedevn@gmail.com
* Fanpage: https://www.facebook.com/cafedevn
* Instagram: https://instagram.com/cafedevn
* Twitter: https://twitter.com/CafedeVn
* Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

int numStudents{ 5 };
int score0{ 84 };
int score1{ 92 };
int score2{ 76 };
int score3{ 81 };
int score4{ 56 };
 
int totalScore{ score0 + score1 + score2 + score3 + score4 };
auto averageScore{ static_cast<double>(totalScore) / numStudents };

Bạn thấy đó, chúng ta phải sử dụng rất nhiều biến, và phải gõ rất nhiều – và chừng đó câu lệnh mới chỉ thể hiện cho 5 sinh viên! Hãy tưởng tượng, chúng ta sẽ phải làm bao nhiêu công việc để tính toán cho 30 sinh viên, hay 150 sinh viên?

Ngoài ra, nếu một sinh viên mới được thêm vào, một biến mới cần phải được khai báo, khởi tạo, và cộng thêm vào phép tính totalScore. Bất cứ khi nào bạn phải sửa đổi code cũ, bạn sẽ dễ tạo ra nhiều bugs (lỗi) hơn.

Sử dụng mảng sẽ cho ta một giải pháp tốt hơn một chút:

int scores[]{ 84, 92, 76, 81, 56 };
int numStudents{ static_cast<int>(std::size(scores)) }; // requires C++17 and <iterator> header
int totalScore{ scores[0] + scores[1] + scores[2] + scores[3] + scores[4] };
auto averageScore{ static_cast<double>(totalScore) / numStudents };

Điều này sẽ cắt giảm đáng kể số lượng biến cần được khai báo, nhưng totalScore vẫn yêu cầu mỗi phần tử mảng phải được liệt kê riêng lẻ. Và cũng giống như ở trên, việc thay đổi số lượng sinh viên cũng đồng nghĩa với việc công thức totalScore cần được điều chỉnh lại, một cách thủ công.

Có lẽ, cách tốt nhất là duyệt qua từng phần tử mảng và tính trực tiếp giá trị totalScore.

1. Vòng lặp và mảng

Trrong bài học lần trước, bạn đã biết rằng đối số subscript của mảng không nhất thiết phải là một giá trị hằng – nó có thể là một biến. Điều này có nghĩa là chúng ta có thể sử dụng một biến vòng lặp làm index (chỉ số mô tả vị trí của phần tử mảng) để lặp/duyệt qua tất cả các phần tử của mảng và thực hiện một số phép tính trên chúng. Đây là cách làm phổ biến khi bạn phải thao tác trên mảng, nói chung, bạn gần như sẽ luôn luôn và chắc chắn phải sử dụng vòng lặp khi xử lý mảng! Khi chúng ta sử dụng vòng lặp để truy cập lần lượt từng phần tử mảng, điều này thường được gọi là lặp/duyệt qua mảng.

Dưới đây là ví dụ sử dụng vòng lặp for:

/**
* Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
*
* @author cafedevn
* Contact: cafedevn@gmail.com
* Fanpage: https://www.facebook.com/cafedevn
* Instagram: https://instagram.com/cafedevn
* Twitter: https://twitter.com/CafedeVn
* Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

int scores[]{ 84, 92, 76, 81, 56 };
int numStudents{ static_cast<int>(std::size(scores)) };
// const int numStudents{ sizeof(scores) / sizeof(scores[0]) }; // use this instead if not C++17 capable
int totalScore{ 0 };
 
// use a loop to calculate totalScore
for (int student{ 0 }; student < numStudents; ++student)
    totalScore += scores[student];
 
auto averageScore{ static_cast<double>(totalScore) / numStudents };

Giải pháp sử dụng vòng lặp này mang lại sự lý tưởng về cả khả năng dễ đọc và dễ bảo trì của code. Bởi vì vòng lặp truy cập tới tất cả các phần tử mảng, nên các công thức tính toán sẽ tự động điều chỉnh để tính toán dựa trên số lượng phần tử mảng mong muốn. Điều này có nghĩa là, chúng ta sẽ không cần phải thay đổi thủ công các phép tính để tính toán cho các sinh viên mới, và chúng ta cũng không cần phải thêm thủ công tên của các phần tử mảng mới.

Ví dụ sau sẽ mô tả việc duyệt mảng sử dụng vòng lặp, để xác định điểm cao nhất trong lớp:

/**
* Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
*
* @author cafedevn
* Contact: cafedevn@gmail.com
* Fanpage: https://www.facebook.com/cafedevn
* Instagram: https://instagram.com/cafedevn
* Twitter: https://twitter.com/CafedeVn
* Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

#include <iostream>
#include <iterator> // for std::size
 
int main()
{
    int scores[]{ 84, 92, 76, 81, 56 };
    int numStudents{ static_cast<int>(std::size(scores)) };
 
    int maxScore{ 0 }; // keep track of our largest score
    for (int student{ 0 }; student < numStudents; ++student)
        if (scores[student] > maxScore)
            maxScore = scores[student];
 
    std::cout << "The best score was " << maxScore << '\n';
 
    return 0;
}

Trong ví dụ này, chúng ta sử dụng một biến bình thường có tên maxScore để lưu lại điểm số cao nhất đã tìm thấy. maxScore được khởi tạo là 0 để thể hiện rằng chúng ta chưa tìm thấy bất kỳ điểm số nào. Sau đó, chúng ta lặp qua từng phần tử của mảng, và nếu tìm thấy một điểm số mà cao hơn bất kỳ điểm số nào mà đã thấy trước đó, chúng ta sẽ gán giá trị này cho maxScore. Do đó, maxScore luôn đại diện cho điểm cao nhất trong số tất cả các phần tử ta đã tìm kiếm cho đến nay. Khi lặp đến cuối mảng, biến maxScore sẽ giữ điểm số cao nhất trong toàn bộ mảng.

Kết hợp mảng và vòng lặp

Vòng lặp thường được sử dụng cùng với mảng để làm một trong 3 việc sau:

  • Tính toán một giá trị nào đó (ví dụ: Giá trị trung bình, giá trị tổng)
  • Tìm kiếm một giá trị (ví dụ: Giá trị lớn nhất, giá trị nhỏ nhất)
  • Tổ chức lại mảng (ví dụ: Sắp xếp mảng theo thứ tự tăng dần, giảm dần).

Khi tính toán một giá trị nào đó, sẽ có một biến được sử dụng để giữ lại kết quả trung gian, nhằm tính được giá trị cuối cùng. Trong ví dụ trên, khi chúng ta tính điểm trung bình, biến totalScore sẽ giữ tổng số điểm cho tất cả các phần tử đã được duyệt.

Khi tìm kiếm một giá trị, sẽ có một biến được sử dụng để giữ lại giá trị ứng cử viên tốt nhất đã được tìm thấy (hoặc giữ lại index trong mảng của ứng cử viên tốt nhất). Trong ví dụ trên, chúng ta đã sử dụng một vòng lặp để tìm điểm số tốt nhất, biến maxScore được sử dụng để lưu điểm số cao nhất đã được tìm thấy.

Việc sắp xếp một mảng thì lắt léo hơn một chút, vì nó thường liên quan đến các vòng lặp lồng nhau. Chúng ta sẽ đề cập đến việc sắp xếp một mảng trong bài học tiếp theo.

2. Mảng và lỗi off-by-one

Một trong những phần khó nhất khi sử dụng các vòng lặp với mảng là đảm bảo vòng lặp sẽ lặp lại với số lần thích hợp. Lỗi off-by-one rất dễ mắc phải và việc cố gắng truy cập tới một phần tử nằm ngoài phạm vi chiều dài của mảng có thể gây ra hậu quả nghiêm trọng. Xét đoạn chương trình sau:

int scores[]{ 84, 92, 76, 81, 56 };
    int numStudents{ static_cast<int>(std::size(scores)) };
 
    int maxScore{ 0 }; // keep track of our largest score
    for (int student{ 0 }; student <= numStudents; ++student)
        if (scores[student] > maxScore)
            maxScore = scores[student];
 
    std::cout << "The best score was " << maxScore << '\n';

Vấn đề đối với đoạn chương trình này là điều kiện trong vòng lặp for bị sai! Mảng được khai báo có 5 phần tử, được đánh index từ 0 đến 4. Tuy nhiên, mảng này lặp từ 0 đến 5. Do đó, ở lần lặp cuối cùng, mảng sẽ thực hiện:

 if (scores[5] > maxScore)
            maxScore = scores[5];

Nhưng scores[5] là phần tử không xác định! Điều này có thể dẫn đến rất nhiều lỗi, trong trường hợp này rất có thể scores[5] là một giá trị rác. Do đó, kết quả có thể xảy ra là maxScore sẽ bị sai.

Tuy nhiên, hãy tưởng tượng điều gì sẽ xảy ra nếu chúng ta vô tình gán một giá trị cho array[5]! Chúng ta có thể ghi đè lên một biến khác (hoặc một phần của biến đó) hoặc có thể làm hỏng một cái gì đó – những loại lỗi này có thể rất khó theo dõi!

Do đó, khi sử dụng các vòng lặp với mảng, hãy luôn kiểm tra kỹ các điều kiện vòng lặp của bạn để đảm bảo bạn không mắc phải các lỗi off-by-one.

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!

1 BÌNH LUẬN

Bình luận bị đóng.