انتقل إلى المحتوى

محولات الحاويات والحاويات الخاصة

تعرف على المكدس وقائمة الانتظار وقائمة الانتظار ذات الأولوية وأنواع المفردات الحديثة مثل الامتداد وstring_view والاختياري. افهم متى تعمل المحولات على تبسيط التعليمات البرمجية الخاصة بك.

ما هي محولات الحاويات؟

محولات الحاوية عبارة عن مغلفات حول الحاويات الموجودة التي توفر واجهة مقيدة. إنهم يفرضون نمط وصول محددًا عن طريق إخفاء واجهة برمجة تطبيقات الحاوية الكاملة:

- std::stack — LIFO (آخر دخول، أول صادر)، مدعوم بـdequeافتراضيًا
- std::queue — FIFO (أولاً يدخل، يخرج أولاً)، مدعومًا بـdequeافتراضيًا
- std::priority_queue — الحد الأقصى للكومة، مدعومًا بـvectorافتراضيًا

لا توفر المحولات تكرارات — يمكنك فقط الوصول إلى العنصر العلوي/الأمامي. هذا التقييد مقصود: فهو يجعل غرض الكود أكثر وضوحًا ويمنع سوء الاستخدام غير المقصود.

المكدس وقائمة الانتظار في العمل

الأكوام وقوائم الانتظار هي هياكل بيانات أساسية تستخدم في التحليل، وBFS/DFS، وأنظمة التراجع، وجدولة المهام.

stack_queue.cpp
#include <stack>
#include <queue>
#include <string>
#include <iostream>

// Check if parentheses are balanced using a stack
bool is_balanced(const std::string& expr) {
    std::stack<char> stk;
    for (char c : expr) {
        if (c == '(' || c == '[' || c == '{') {
            stk.push(c);
        } else if (c == ')' || c == ']' || c == '}') {
            if (stk.empty()) return false;
            char top = stk.top();
            stk.pop();
            if ((c == ')' && top != '(') ||
                (c == ']' && top != '[') ||
                (c == '}' && top != '{')) {
                return false;
            }
        }
    }
    return stk.empty();
}

int main() {
    std::cout << std::boolalpha;
    std::cout << is_balanced("({[]})") << '\n';   // true
    std::cout << is_balanced("({[}])") << '\n';   // false

    // Queue — BFS-style task processing
    std::queue<std::string> tasks;
    tasks.push("compile");
    tasks.push("link");
    tasks.push("run tests");

    while (!tasks.empty()) {
        std::cout << "Processing: " << tasks.front() << '\n';
        tasks.pop();   // removes from front
    }
}

قائمة انتظار الأولوية (الكومة)

std::priority_queueهو الحد الأقصى للكومة بشكل افتراضي —top()يُرجع العنصر الأكبر. للحصول على كومة صغيرة، استخدمstd::greaterكمقارن.

priority_queue.cpp
#include <queue>
#include <vector>
#include <functional>
#include <iostream>
#include <string>

struct Task {
    int priority;
    std::string name;
};

int main() {
    // Max-heap (default) — largest first
    std::priority_queue<int> max_heap;
    max_heap.push(30);
    max_heap.push(10);
    max_heap.push(50);
    max_heap.push(20);

    while (!max_heap.empty()) {
        std::cout << max_heap.top() << ' ';  // 50 30 20 10
        max_heap.pop();
    }
    std::cout << '\n';

    // Min-heap — smallest first
    std::priority_queue<int, std::vector<int>, std::greater<int>> min_heap;
    min_heap.push(30);
    min_heap.push(10);
    min_heap.push(50);
    std::cout << "Min: " << min_heap.top() << '\n';  // 10

    // Custom comparator with lambda
    auto cmp = [](const Task& a, const Task& b) {
        return a.priority < b.priority;  // higher priority first
    };
    std::priority_queue<Task, std::vector<Task>, decltype(cmp)> task_queue(cmp);
    task_queue.push({3, "low priority"});
    task_queue.push({10, "critical"});
    task_queue.push({7, "medium"});

    while (!task_queue.empty()) {
        auto& t = task_queue.top();
        std::cout << "[" << t.priority << "] " << t.name << '\n';
        task_queue.pop();
    }
}

std::span — العرض غير المالك للبيانات المتجاورة (C++20)

std::spanعبارة عن طريقة عرض خفيفة الوزن وغير مملوكة لتسلسل متجاور من العناصر. فهو يستبدل النمط المعرض للخطأ لتمرير أزواج(pointer, size).

span_example.cpp
#include <span>
#include <vector>
#include <array>
#include <iostream>
#include <numeric>

// span accepts vector, array, C-array — anything contiguous
void print_stats(std::span<const int> data) {
    if (data.empty()) return;
    std::cout << "Size: " << data.size() << '\n';
    std::cout << "First: " << data.front() << '\n';
    std::cout << "Last:  " << data.back() << '\n';

    int sum = std::accumulate(data.begin(), data.end(), 0);
    std::cout << "Sum:   " << sum << '\n';

    // Subviews
    auto first3 = data.first(3);   // first 3 elements
    auto last2  = data.last(2);    // last 2 elements
    auto mid    = data.subspan(1, 3);  // 3 elements starting at index 1
}

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    std::array<int, 5> arr = {10, 20, 30, 40, 50};
    int c_arr[] = {100, 200, 300, 400, 500};

    print_stats(vec);     // works with vector
    print_stats(arr);     // works with array
    print_stats(c_arr);   // works with C-array
}

string_view واختياري

std::string_view (C++17) هو مرجع غير مالك للقراءة فقط لسلسلة ما. إنه يتجنب تكلفة نسخ أو تخصيصstd::stringللوظائف التي تحتاج فقط إلى القراءة:

void greet(std::string_view name) { // no copy, no allocation
 std::cout << "Hello, " << name << '\n';
}
greet("world"); // works with string literals
greet(std::string{"world"}); // works with std::string

std::اختياري (C++17) represents a value that might or might not be present — a type-safe replacement for "magic values" or raw pointers to indicate absence:

std::optional<int> find_index(std::span<const int> data, int target) {
 for (int i = 0; i < data.size(); ++i)
 if (data[i] == target) return i;
 return std::nullopt; // not found
}
auto idx = find_index(vec, 42);
if (idx) std::cout << "Found at " << *idx << '\n';
std::cout << idx.value_or(-1) << '\n'; // -1 if empty
خطأ شائع

نظرًا لأنstd::string_viewوstd::spanغير مملوكين، فمن الممكن أن يتدليا بسهولة إذا تم إتلاف البيانات الأساسية:

std::string_view bad() {
    std::string temp = "hello";
    return temp;  // DANGLING — temp destroyed at end of scope
}

Never return a string_view or span to a local variable. They are safe for function parameters (the caller's data outlives the call), but dangerous as return values unless you can guarantee the source outlives the view.

The same applies to std::span - إنه مجرد مؤشر وحجم داخليًا، بدون دلالات ملكية.

أفضل ممارسة

Use container adaptors when you want to enforce a discipline on how elements are accessed:

- Usestd::stackinstead of a rawdequeorvectorwhen only LIFO access is meaningful. This makes the intent obvious and prevents accidental random access.
- Usestd::queuefor FIFO processing (task queues, BFS).
- Usestd::priority_queuefor scheduling, Dijkstra's algorithm, or any scenario where you always want the extreme element.

If you need iteration, size queries at arbitrary points, or mid-sequence access, use the underlying container directly.

النقاط الرئيسية
  • Container adaptors (stack,queue,priority_queue) restrict the interface to enforce usage discipline
  • std::priority_queueis a max-heap by default — usestd::greaterfor a min-heap
  • std::span(C++20) provides a non-owning view over contiguous data, replacing(pointer, size)pairs
  • std::string_view(C++17) avoids copying strings for read-only use — but watch for dangling references
  • std::optional(C++17) is the type-safe way to represent "maybe no value" without magic sentinel values
  • Non-owning views (span,string_view) must never outlive the data they reference

اختبار — اختبر معرفتك

(15 نقطة خبرة)

1. Which container adaptor provides FIFO (First In, First Out) behavior?

2. ما النوع الذي يُرجعه`std::priority_queue<int>`من`top()`افتراضيًا؟

3. لماذا تعتبر إعادة`std::string_view`إلى`std::string`المحلي أمرًا خطيرًا؟