सामग्री पर जाएं

टाइप, वैल्यू और टाइप सिस्टम

C++ के मूलभूत टाइप, फ़िक्स्ड-विड्थ इंटीजर, इनिशियलाइज़ेशन के रूप, auto डिडक्शन, और उस const सिस्टम को जानें जो कंपाइल टाइम पर शुद्धता लागू करता है।

टाइप सिस्टम: बचाव की आपकी पहली पंक्ति

C++ एक स्टैटिकली टाइप्ड (statically typed) भाषा है — हर एक्सप्रेशन का एक टाइप होता है जो कंपाइल टाइम पर ज्ञात रहता है। टाइप सिस्टम सिर्फ़ हिसाब-किताब रखने की चीज़ नहीं है; यह आपके कोड के चलने से पहले ही बग पकड़ने का आपका सबसे शक्तिशाली साधन है। एक सही ढंग से टाइप किया गया C++ प्रोग्राम अपने इनवेरिएंट टाइप सिस्टम में ही समाए रखता है, जिससे गलतियों की पूरी-पूरी श्रेणियाँ असंभव हो जाती हैं।

किसी वेरिएबल का टाइप तीन बातें तय करता है: वह कितनी मेमोरी घेरता है, वह कौन-से मान दर्शा सकता है, और उस पर कौन-से ऑपरेशन वैध हैं। टाइप सही रखना सही C++ कोड की नींव है।

मूलभूत टाइप

C++ कुछ बिल्ट-इन टाइप देती है जो सीधे हार्डवेयर की क्षमताओं से मेल खाते हैं। इनके सटीक आकार प्लेटफ़ॉर्म के अनुसार बदलते हैं, लेकिन स्टैंडर्ड न्यूनतम रेंज की गारंटी देता है:

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++ में इनिशियलाइज़ेशन के कई सिंटैक्स विकसित हुए हैं। आधुनिक C++ में यूनिफ़ॉर्म (ब्रेस) इनिशियलाइज़ेशन {} को प्राथमिकता दी जाती है, क्योंकि यह नैरोइंग कन्वर्शन को रोकता है:

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 टाइप का एक कीवर्ड है और किसी भी पॉइंटर टाइप में इम्प्लिसिटली बदला जा सकता है, लेकिन इंटीजर में नहीं। नल पॉइंटर के लिए हमेशा NULL या 0 के बजाय nullptr का उपयोग करें।

इसी तरह, जब आपका आशय शून्य मान वाले कैरेक्टर से हो, तो 0 के बजाय '\0' (नल कैरेक्टर) को प्राथमिकता दें, ताकि आशय साफ़ ज़ाहिर हो।

  • नल पॉइंटर के लिए हमेशा NULL या 0 के बजाय nullptr का उपयोग करें
  • nullptr का अपना टाइप (std::nullptr_t) है और यह ओवरलोड की अस्पष्टता से बचाता है
  • NULL एक C मैक्रो है जो 0 में विस्तारित होता है — यह एक इंटीजर है, पॉइंटर नहीं
  • नल कैरेक्टर के लिए '\0' का उपयोग करें, ताकि वह इंटीजर 0 से अलग पहचाना जा सके

const और टाइप क्वालिफ़ायर

const क्वालिफ़ायर कंपाइलर को (और पढ़ने वाले इंसानों को) बताता है कि किसी वैल्यू को इनिशियलाइज़ेशन के बाद बदला नहीं जाना चाहिए। सुरक्षित और खुद-दस्तावेज़ीकृत कोड लिखने के लिए यह C++ के सबसे अहम साधनों में से एक है।

const को दाएँ से बाएँ पढ़ा जाता है: const int* एक const int का पॉइंटर है (int बदला नहीं जा सकता, पॉइंटर बदला जा सकता है)। int* const int का एक const पॉइंटर है (पॉइंटर बदला नहीं जा सकता, int बदला जा सकता है)। const int* const एक const int का const पॉइंटर है।

constexpr (C++11) इससे आगे जाता है: इसका मतलब है कि वैल्यू कंपाइल टाइम पर गणना योग्य होनी चाहिए। इससे कंपाइलर कंपाइलेशन के दौरान ही एक्सप्रेशन का मूल्यांकन कर सकता है, और रनटाइम लागत पूरी तरह खत्म हो जाती है। constexpr का उपयोग उन कॉन्स्टेंट के लिए करें जो वास्तव में कंपाइल टाइम पर ज्ञात हों।

consteval (C++20) और भी सख़्त है: यह ऐसे फ़ंक्शन को चिह्नित करता है जिसका कंपाइल टाइम पर मूल्यांकन होना ज़रूरी है — उसे रनटाइम पर कॉल करना कंपाइलेशन एरर है।

volatile क्वालिफ़ायर कंपाइलर को बताता है कि किसी वेरिएबल की वैल्यू प्रोग्राम के नियंत्रण से बाहर बदल सकती है (जैसे हार्डवेयर रजिस्टर, मेमोरी-मैप्ड I/O)। यह कुछ ऑप्टिमाइज़ेशन को रोकता है। थ्रेडिंग के लिए volatile का उपयोग न करें — इसके बजाय std::atomic का उपयोग करें।

सामान्य गलती

C और पुराने ढंग की C++ के सबसे कपटी बगों में से एक नैरोइंग कन्वर्शन (narrowing conversions) से आता है — यानी ऐसे इम्प्लिसिट कन्वर्शन जिनमें जानकारी खो जाती है। उदाहरण के लिए, double को int में असाइन करने पर भिन्नात्मक हिस्सा चुपचाप कट जाता है। किसी बड़े int को short में असाइन करने पर वह चुपचाप ओवरफ़्लो हो जाता है।

ब्रेस इनिशियलाइज़ेशन {} कंपाइल टाइम पर नैरोइंग कन्वर्शन को रोक देता है। इसे = इनिशियलाइज़ेशन के बजाय चुनने के सबसे मज़बूत कारणों में से यह एक है। अगर आप int x{3.14}; लिखते हैं, तो कंपाइलर एरर देगा। int x = 3.14; के साथ वह चुपचाप 3 पर कट जाता है।

इसी तरह, अंकगणित में साइन्ड और अनसाइन्ड टाइप मिलाने से इम्प्लिसिट कन्वर्शन होते हैं जो चौंकाने वाले नतीजे दे सकते हैं। किसी ऋणात्मक int की तुलना unsigned int से करने पर वह पहले अनसाइन्ड में बदला जाएगा, और संभव है कि वह एक बहुत बड़ी धनात्मक संख्या बन जाए।

  • ब्रेस इनिशियलाइज़ेशन {} नैरोइंग कन्वर्शन को कंपाइल टाइम पर पकड़ लेता है
  • असाइनमेंट इनिशियलाइज़ेशन (=) चुपचाप नैरोइंग की अनुमति दे देता है — डेटा बिना किसी वार्निंग के खो जाता है
  • साइन्ड और अनसाइन्ड टाइप मिलाने पर इम्प्लिसिट कन्वर्शन अनसाइन्ड में होता है
  • अनसाइन्ड में बदला गया ऋणात्मक int एक बहुत बड़ी धनात्मक संख्या बन जाता है

टाइप एलियास

टाइप एलियास पठनीयता बढ़ाते हैं और बाद में टाइप बदलना आसान बनाते हैं। आधुनिक C++ में typedef के बजाय using को प्राथमिकता दें:

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 नहीं) का उपयोग करें
  • auto इनिशियलाइज़र से टाइप का अनुमान लगाता है; decltype किसी एक्सप्रेशन का मूल्यांकन किए बिना उसका टाइप बताता है
  • कंपाइल-टाइम कॉन्स्टेंट के लिए constexpr और टाइप एलियास के लिए 'using' का उपयोग करें

क्विज़ — अपना ज्ञान परखें

(15 XP)

1. ब्रेस इनिशियलाइज़ेशन `int x{42};` का असाइनमेंट इनिशियलाइज़ेशन `int x = 42;` पर क्या फ़ायदा है?

2. C++ में आपको `NULL` के बजाय `nullptr` का उपयोग क्यों करना चाहिए?

3. `auto x = 42;` में `x` का टाइप क्या अनुमानित होता है?