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

Types, Values & the Type System

Explore C++'s fundamental types, fixed-width integers, initialization forms, auto deduction, and the const system that enforces correctness at compile time.

The Type System: Your First Line of Defense

C++ is a statically typed language — every expression has a type known at compile time. The type system is not just bookkeeping; it is your most powerful tool for catching bugs before your code ever runs. A well-typed C++ program carries its invariants in the type system itself, making entire classes of errors impossible.

The type of a variable determines three things: how much memory it occupies, what values it can represent, and what operations are valid on it. Getting types right is the foundation of correct C++ code.

Fundamental Types

C++ provides a set of built-in types that map directly to hardware capabilities. Their exact sizes vary by platform, but the standard guarantees minimum ranges:

types.cppc++23
#include <iostream>
#include <cstdint>   // Fixed-width integer types
#include <limits>    // std::numeric_limits

int main() {
    // Boolean
    bool is_valid = true;           // true or false, typically 1 byte

    // Character types
    char letter = 'A';             // At least 8 bits, holds ASCII
    char8_t  u8ch  = u8'A';       // C++20: exactly 8 bits, UTF-8
    char16_t u16ch = u'\u00E9';   // At least 16 bits, UTF-16
    char32_t u32ch = U'\U0001F600'; // At least 32 bits, UTF-32

    // Integer types (sizes are minimums, typical x86-64 shown)
    short       s = 32000;         // At least 16 bits (16)
    int         i = 2'000'000;     // At least 16 bits (32) — digit separator C++14
    long        l = 100'000L;      // At least 32 bits (64 on Linux, 32 on Windows)
    long long  ll = 9'000'000'000'000'000'000LL;  // At least 64 bits

    // Unsigned variants
    unsigned int ui = 42u;         // Cannot be negative

    // Floating-point types
    float       f = 3.14f;         // ~7 decimal digits precision
    double      d = 3.141592653589793; // ~15 decimal digits precision
    long double ld = 3.14159265358979323846L;

    // Fixed-width types from <cstdint> — use these when size matters
    std::int32_t  exact = 42;      // Exactly 32 bits, guaranteed
    std::uint64_t big   = 18'446'744'073'709'551'615ULL; // Exactly 64 bits
    std::int_fast32_t fast = 42;   // At least 32 bits, fastest available
    std::size_t index = 0;         // Unsigned, suitable for sizes and indices

    std::cout << "int: " << sizeof(int) << " bytes, max = "
              << std::numeric_limits<int>::max() << '\n';
}
المخرجات

اضغط على تشغيل لترجمة هذا المثال وتشغيله.

التهيئة: الطريق الصحيح

C++ has evolved multiple initialization syntaxes. Uniform (brace) initialization{}is preferred in modern C++ because it prevents narrowing conversions:

initialization.cppc++23
#include <iostream>
#include <string>

int main() {
    // Direct initialization
    int a(42);              // OK but can be confused with function declaration

    // Copy initialization
    int b = 42;             // OK, familiar from C

    // Uniform (brace) initialization — preferred in modern C++
    int c{42};              // Direct-list-initialization
    int d = {42};           // Copy-list-initialization

    // Brace initialization PREVENTS narrowing conversions
    // double to int loses precision — the compiler will ERROR:
    // int narrow{3.14};    // ERROR: narrowing conversion
    int ok = 3.14;          // Compiles! Silently truncates to 3 (dangerous)

    // Value initialization (zero for built-in types)
    int zero{};             // Guaranteed to be 0
    double dzero{};         // Guaranteed to be 0.0
    bool bfalse{};          // Guaranteed to be false

    // auto deduction
    auto x = 42;            // int
    auto y = 3.14;          // double
    auto z = 'A';           // char
    auto s = std::string{"hello"};  // std::string (not const char*!)

    // decltype — queries the type of an expression
    decltype(x) another = 100;  // same type as x → int
    decltype(auto) ref = (x);   // int& because (x) is an lvalue expression

    std::cout << "zero-initialized int: " << zero << '\n';
    std::cout << "auto-deduced string: " << s << '\n';
}
المخرجات

اضغط على تشغيل لترجمة هذا المثال وتشغيله.

أفضل ممارسة

في لغة C، يتم تمثيل المؤشر الفارغ بواسطة الماكروNULL، والذي يمتد عادةً إلى0أو(void*)0. يؤدي هذا إلى حدوث غموض في C++ لأن0هو أيضًاintصالح. ضع في اعتبارك وظيفة محمّلة بشكل زائد لكل منintونوع المؤشر - فتمريرNULL(وهو0) سيؤدي إلى استدعاء التحميل الزائد لـint، وليس المؤشر.

قدم C++11 nullptr، وهي كلمة رئيسية من النوعstd::nullptr_tقابلة للتحويل ضمنيًا إلى أي نوع مؤشر ولكن ليس إلى أعداد صحيحة. استخدم دائمًاnullptrبدلاً منNULLأو0للمؤشرات الفارغة.

وبالمثل، تفضل'\0'(الحرف الفارغ) عندما تقصد الحرف ذو القيمة صفر، بدلاً من0، لتوصيل النية بوضوح.

  • استخدم دائمًا nullptr بدلاً من NULL أو 0 للمؤشرات الخالية
  • nullptr له نوعه الخاص (std::nullptr_t) ويتجنب الغموض الزائد
  • NULL هو ماكرو C الذي يمتد إلى 0 - وهو عدد صحيح، وليس مؤشر
  • استخدم "\0" للحرف الفارغ لتمييزه عن العدد الصحيح 0

ثابت ونوع المؤهلات

يخبر مؤهلconstالمترجم (والقراء البشريين) أنه لا يجب تعديل القيمة بعد التهيئة. إنها واحدة من أهم الأدوات في C++ لكتابة تعليمات برمجية آمنة ذاتية التوثيق.

تتم قراءةconstمن اليمين إلى اليسار: const int*هو مؤشر إلى const int (لا يمكن تغيير int، بل يمكن تغيير المؤشر).int* constهو مؤشر ثابت لـ int (لا يمكن تغيير المؤشر، يمكن تغييره).const int* constهو مؤشر const إلى const int.

يذهب constexpr (C++11) إلى أبعد من ذلك: فهو يعني أن القيمة يجب أن تكون قابلة للحساب في وقت الترجمة. يتيح ذلك للمترجم تقييم التعبيرات أثناء الترجمة، مما يقلل تكلفة وقت التشغيل بالكامل. استخدمconstexprللثوابت المعروفة حقًا في وقت الترجمة.

يعد consteval (C++20) أكثر صرامة: فهو يشير إلى دالة يجب تقييمها في وقت الترجمة — واستدعاؤها في وقت التشغيل يعد خطأ في الترجمة.

يخبر مؤهلvolatileالمترجم أن قيمة المتغير قد تتغير خارج نطاق تحكم البرنامج (على سبيل المثال، سجلات الأجهزة، الإدخال / الإخراج المعين للذاكرة). يمنع بعض التحسينات. لا تستخدمvolatileللترابط — استخدمstd::atomicبدلاً من ذلك.

خطأ شائع

واحدة من أكثر الأخطاء الخبيثة في لغة C والنمط القديم C++ تأتي من التحويلات الضيقة — التحويلات الضمنية التي تفقد المعلومات. على سبيل المثال، يؤدي تعيينdoubleإلىintإلى اقتطاع الجزء الكسري بصمت. تعيينintكبير إلىshortيفيض بصمت.

تمنع تهيئة الأقواس{}تضييق التحويلات في وقت الترجمة. يعد هذا أحد أقوى الأسباب لتفضيله على تهيئة=. إذا كتبتint x{3.14};، فسوف يصدر المترجم خطأ. معint x = 3.14;، يتم اقتطاعه بصمت إلى 3.

وبالمثل، فإن خلط الأنواع الموقعة وغير الموقعة في العمليات الحسابية يؤدي إلى تحويلات ضمنية يمكن أن تؤدي إلى نتائج مذهلة. سيتم تحويلintالسلبي مقارنةً بـunsigned intإلى غير موقعة أولاً، ومن المحتمل أن يصبح رقمًا موجبًا كبيرًا جدًا.

  • تلتقط تهيئة الأقواس {} التحويلات الضيقة في وقت الترجمة
  • تتيح تهيئة المهمة (=) بصمت التضييق — حيث يتم فقدان البيانات دون سابق إنذار
  • يؤدي خلط الأنواع الموقعة وغير الموقعة إلى تحويل ضمني إلى غير موقعة
  • يصبح int السالب الذي تم تحويله إلى غير موقع رقمًا موجبًا كبيرًا جدًا

اكتب الأسماء المستعارة

تعمل الأسماء المستعارة للكتابة على تحسين إمكانية القراءة وتسهيل تغيير الأنواع لاحقًا. تفضلusingعلىtypedefفي C++ الحديث:

aliases.cppc++23
#include <cstdint>
#include <vector>
#include <string>

// Modern style (using) — preferred
using Byte = std::uint8_t;
using StudentId = std::int32_t;
using Names = std::vector<std::string>;

// Old style (typedef) — equivalent but harder to read with complex types
typedef std::uint8_t OldByte;
typedef std::vector<std::string> OldNames;

// using works with templates; typedef does not easily
template <typename T>
using Vec = std::vector<T>;

int main() {
    Byte b{255};
    StudentId sid{12345};
    Names names{"Alice", "Bob", "Charlie"};
    Vec<double> values{1.0, 2.0, 3.0};
}
المخرجات

اضغط على تشغيل لترجمة هذا المثال وتشغيله.

النقاط الرئيسية
  • يحتوي كل تعبير C++ على نوع معروف في وقت الترجمة - يحدد النوع الحجم والقيم الصالحة والعمليات الصالحة
  • استخدم الأنواع ذات العرض الثابت من (int32_t, uint64_t) عندما يكون الحجم الدقيق مهمًا
  • تفضيل تهيئة الأقواس {} - فهي تمنع تحويلات التضييق الخطيرة
  • استخدم nullptr (وليس NULL أو 0) للمؤشرات الفارغة لتجنب الغموض الزائد
  • يستنتج تلقائي النوع من المُهيئ؛ يستعلم decltype عن نوع التعبير دون تقييمه
  • استخدم constexpr لثوابت وقت الترجمة و"استخدام" للأسماء المستعارة للنوع

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

(15 نقطة خبرة)

1. ما هي ميزة تهيئة القوس`int x{42};`على تهيئة المهمة`int x = 42;`؟

2. لماذا يجب عليك استخدام`nullptr`بدلاً من`NULL`في C++؟

3. ماذا يستنتج`auto x = 42;`نوع`x`؟