Thực hành C++ Hiện đại: Làm Quen với Thư viện Chuẩn và Các Thuật toán STL

Các bài tập sau đây minh họa việc sử dụng hiệu quả các thành phần thiết yếu của thư viện chuẩn C++ (STL), bao gồm std::vector, std::string, thuật toán như std::reverse, std::rotate, std::transform, std::generate, cũng như các hàm xử lý chuỗi và số học.

Bài 1: Thao tác chuỗi và mảng động với thuật toán đảo ngược và xoay vòng

Dưới đây là phiên bản đã tái cấu trúc mã nguồn, loại bỏ việc dùng using namespace std, đổi tên biến theo chuẩn rõ ràng, và điều chỉnh logic để tránh trùng lặp không cần thiết:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

template<typename Container>
void print_elements(const Container& container) {
    for (const auto& elem : container) {
        std::cout << elem << ' ';
    }
    std::cout << '\n';
}

void demo_string_operations() {
    const std::string base = "0123456789";
    std::cout << "Chuỗi gốc: " << base << '\n';

    std::string reversed(base);
    std::reverse(reversed.begin(), reversed.end());
    std::cout << "Đảo ngược trực tiếp: " << reversed << '\n';

    std::string copied(base.size(), ' ');
    std::reverse_copy(base.begin(), base.end(), copied.begin());
    std::cout << "Sao chép sau khi đảo: " << copied << '\n';
}

void demo_vector_reverse() {
    std::vector<int> numbers = {2, 0, 4, 9};
    std::cout << "Mảng ban đầu: "; print_elements(numbers);

    std::vector<int> direct_reversed = numbers;
    std::reverse(direct_reversed.begin(), direct_reversed.end());
    std::cout << "Đảo ngược toàn bộ: "; print_elements(direct_reversed);

    std::vector<int> copy_reversed(numbers.size());
    std::reverse_copy(numbers.begin(), numbers.end(), copy_reversed.begin());
    std::cout << "Sao chép đảo ngược: "; print_elements(copy_reversed);
}

void demo_rotation() {
    std::vector<int> seq = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    std::cout << "Dãy gốc: "; print_elements(seq);

    std::vector<int> rotated_by_1 = seq;
    std::rotate(rotated_by_1.begin(), rotated_by_1.begin() + 1, rotated_by_1.end());
    std::cout << "Xoay 1 vị trí: "; print_elements(rotated_by_1);

    std::vector<int> rotated_by_last = seq;
    std::rotate(rotated_by_last.begin(), rotated_by_last.end() - 1, rotated_by_last.end());
    std::cout << "Xoay về cuối: "; print_elements(rotated_by_last);
}

Bài 2: Sinh dữ liệu ngẫu nhiên và phân tích thống kê cơ bản

Mã được viết lại để sử dụng lambda an toàn hơn, kiểm soát chặt chẽ phạm vi biến, và tách biệt rõ ràng giữa sinh dữ liệu và xử lý:

#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <iomanip>
#include <random>

template<typename T>
void display(const std::vector<T>& v) {
    for (const auto& x : v) std::cout << x << ' ';
    std::cout << '\n';
}

void generate_and_sort() {
    std::vector<int> data(10);
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<int> dist(0, 100);

    std::generate(data.begin(), data.end(), [&]() { return dist(gen); });
    std::cout << "Dữ liệu ngẫu nhiên: "; display(data);

    std::vector<int> sorted_full = data;
    std::sort(sorted_full.begin(), sorted_full.end());
    std::cout << "Sắp xếp toàn bộ: "; display(sorted_full);

    std::vector<int> sorted_inner = data;
    std::sort(sorted_inner.begin() + 1, sorted_inner.end() - 1);
    std::cout << "Sắp xếp nội bộ: "; display(sorted_inner);
}

void statistical_analysis() {
    std::vector<int> sample(10);
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<int> dist(0, 100);
    std::generate(sample.begin(), sample.end(), [&]() { return dist(gen); });

    auto [min_it, max_it] = std::minmax_element(sample.begin(), sample.end());
    double mean = std::accumulate(sample.begin(), sample.end(), 0.0) / sample.size();

    std::cout << "Dữ liệu: "; display(sample);
    std::cout << "Min: " << *min_it << ", Max: " << *max_it << '\n';
    std::cout << "Trung bình: " << std::fixed << std::setprecision(2) << mean << '\n';

    std::sort(sample.begin(), sample.end());
    double trimmed_mean = std::accumulate(sample.begin() + 1, sample.end() - 1, 0.0) / (sample.size() - 2);
    std::cout << "Trung bình sau khi loại min/max: " << trimmed_mean << '\n';
}

Bài 3: Chuyển đổi và biến đổi chuỗi bằng std::transform

Hàm biến đổi được cải tiến để xử lý đúng ký tự không phải chữ cái và tăng tính mở rộng:

#include <iostream>
#include <string>
#include <algorithm>
#include <cctype>

char shift_char(char c) {
    if (std::islower(c)) return (c == 'z') ? 'a' : c + 1;
    if (std::isupper(c)) return (c == 'Z') ? 'A' : c + 1;
    return c;
}

void case_conversion_demo() {
    std::string input = "Hello World 2049!";
    std::cout << "Chuỗi gốc: " << input << '\n';

    std::string lowercased = input;
    std::transform(lowercased.begin(), lowercased.end(), lowercased.begin(), ::tolower);
    std::cout << "Chuyển thành chữ thường: " << lowercased << '\n';

    std::string uppercased = input;
    std::transform(uppercased.begin(), uppercased.end(), uppercased.begin(), ::toupper);
    std::cout << "Chuyển thành chữ hoa: " << uppercased << '\n';
}

void custom_transform_demo() {
    std::string source = "I love cosmos!";
    std::string result(source.length(), ' ');

    std::transform(source.begin(), source.end(), result.begin(), shift_char);
    std::cout << "Biến đổi từng ký tự: \"" << source << "\" → \"" << result << "\"\n";
}

Bài 4: Kiểm tra chuỗi đối xứng (palindrome)

Phiên bản tối ưu sử dụng iterator và xử lý hiệu quả hơn với khoảng trắng và dấu câu:

#include <iostream>
#include <string>
#include <algorithm>
#include <cctype>

bool is_palindrome_strict(const std::string& s) {
    size_t len = s.length();
    for (size_t i = 0; i < len / 2; ++i) {
        if (s[i] != s[len - 1 - i]) return false;
    }
    return true;
}

bool is_palindrome_case_insensitive(const std::string& s) {
    size_t len = s.length();
    for (size_t i = 0; i < len / 2; ++i) {
        if (std::tolower(static_cast<unsigned char>(s[i])) !=
            std::tolower(static_cast<unsigned char>(s[len - 1 - i]))) {
            return false;
        }
    }
    return true;
}

void palindrome_checker() {
    std::string word;
    std::cout << "Nhập chuỗi để kiểm tra (Enter để thoát):\n";
    while (std::getline(std::cin, word) && !word.empty()) {
        std::cout << std::boolalpha
                  << "Đối xứng (phân biệt hoa/thường): " << is_palindrome_strict(word) << '\n'
                  << "Đối xứng (không phân biệt): " << is_palindrome_case_insensitive(word) << "\n\n";
    }
}

Bài 5: Chuyển đổi hệ cơ số tùy chọn

Hàm chuyển đổi được viết lại để hỗ trợ đầy đủ từ cơ số 2 đến 36, xử lý trường hợp đặc biệt và sử dụng bảng ký tự an toàn:

#include <iostream>
#include <string>
#include <algorithm>
#include <stdexcept>

std::string to_base(int value, int base = 2) {
    if (base < 2 || base > 36) throw std::invalid_argument("Cơ số phải nằm trong [2, 36]");
    if (value == 0) return "0";

    const char digits[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    std::string result;

    int n = std::abs(value);
    while (n > 0) {
        result += digits[n % base];
        n /= base;
    }

    if (value < 0) result += '-';
    std::reverse(result.begin(), result.end());
    return result;
}

void number_base_demo() {
    int num;
    std::cout << "Nhập số nguyên (Ctrl+D để kết thúc):\n";
    while (std::cin >> num) {
        std::cout << "Thập phân: " << num << '\n'
                  << "Nhị phân: " << to_base(num, 2) << '\n'
                  << "Bát phân: " << to_base(num, 8) << '\n'
                  << "Thập lục phân: " << to_base(num, 16) << '\n'
                  << "Tam thập lục phân: " << to_base(num, 36) << "\n\n";
    }
}

Bài 6: Tạo bảng mã hóa Caesar dạng ma trận

Mã được viết lại để in ra bảng chữ cái xoay vòng một cách gọn gàng và dễ đọc:

#include <iostream>
#include <string>
#include <iomanip>
#include <algorithm>

void caesar_table() {
    std::string alphabet = "abcdefghijklmnopqrstuvwxyz";
    std::cout << std::setw(3) << ' ';
    for (char c : alphabet) {
        std::cout << std::setw(2) << c;
    }
    std::cout << '\n';

    for (int row = 1; row <= 26; ++row) {
        std::cout << std::setw(2) << row;
        std::rotate(alphabet.begin(), alphabet.begin() + 1, alphabet.end());
        for (char c : alphabet) {
            std::cout << std::setw(2) << c;
        }
        std::cout << '\n';
    }
}

Bài 7: Trò chơi luyện toán cơ bản với kiểm tra đầu vào

Phiên bản nâng cao sử dụng std::random_device thay vì rand(), đồng thời đảm bảo phép chia luôn cho kết quả nguyên:

#include <iostream>
#include <string>
#include <random>
#include <iomanip>

double compute(int a, int b, char op) {
    switch (op) {
        case '+': return static_cast<double>(a + b);
        case '-': return static_cast<double>(a - b);
        case '*': return static_cast<double>(a * b);
        case '/': return b != 0 ? static_cast<double>(a) / b : 0.0;
        default: return 0.0;
    }
}

void math_quiz() {
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<int> num_dist(0, 10);
    std::uniform_int_distribution<int> op_dist(0, 3);
    const char ops[] = {'+', '-', '*', '/'};

    int total = 10, correct = 0;
    for (int i = 0; i < total; ++i) {
        int a = num_dist(gen);
        int b = num_dist(gen);
        int op_idx = op_dist(gen);
        char op = ops[op_idx];

        // Đảm bảo phép trừ không âm và phép chia nguyên
        if (op == '-' && a < b) std::swap(a, b);
        if (op == '/' && (b == 0 || a % b != 0)) {
            b = (b == 0) ? 1 : b;
            while (a % b != 0) a = num_dist(gen);
        }

        double expected = compute(a, b, op);
        std::cout << a << ' ' << op << ' ' << b << " = ";
        
        double answer;
        if (std::cin >> answer && std::abs(answer - expected) < 1e-6) {
            ++correct;
        }
    }
    std::cout << "Tỷ lệ chính xác: " << std::fixed << std::setprecision(1)
              << (100.0 * correct / total) << "%\n";
}

Thẻ: STL Algorithms modern-cpp standard-library Transform

Đăng vào ngày 16 tháng 6 lúc 07:34