Μετάβαση στο περιεχόμενο

Τύποι, Τιμές & Σύστημα Τύπου

Εξερευνήστε τους θεμελιώδεις τύπους του C++, τους ακέραιους αριθμούς σταθερού πλάτους, τις φόρμες αρχικοποίησης, την αυτόματη αφαίρεση και το σύστημα const που επιβάλλει την ορθότητα κατά το χρόνο μεταγλώττισης.

The Type System: Η πρώτη σας γραμμή άμυνας

Το C++ είναι μια στατικά πληκτρολογημένη γλώσσα — κάθε έκφραση έχει έναν τύπο που είναι γνωστός κατά το χρόνο μεταγλώττισης. Το σύστημα τύπου δεν είναι μόνο τήρηση βιβλίων. είναι το πιο ισχυρό εργαλείο σας για να εντοπίζετε σφάλματα πριν εκτελεστεί ποτέ ο κώδικάς σας. Ένα καλά τυπωμένο πρόγραμμα 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που είναι σιωπηρά μετατρέψιμη σε οποιονδήποτε τύπο δείκτη αλλά όχι σε ακέραιους αριθμούς. Να χρησιμοποιείτε πάνταnullptrαντί γιαNULLή0για μηδενικούς δείκτες.

Ομοίως, προτιμήστε το'\0'(τον μηδενικό χαρακτήρα) όταν εννοείτε τον χαρακτήρα με τιμή μηδέν, αντί του0, για να επικοινωνεί με σαφήνεια την πρόθεση.

  • Να χρησιμοποιείτε πάντα nullptr αντί για NULL ή 0 για μηδενικούς δείκτες
  • Το nullptr έχει τον δικό του τύπο (std::nullptr_t) και αποφεύγει την ασάφεια υπερφόρτωσης
  • Το NULL είναι μια μακροεντολή C που επεκτείνεται στο 0 — είναι ακέραιος, όχι δείκτης
  • Χρησιμοποιήστε το '\0' για τον μηδενικό χαρακτήρα για να τον ξεχωρίσετε από τον ακέραιο 0

const και Type Qualifiers

Ο προσδιορισμόςconstλέει στον μεταγλωττιστή (και στους ανθρώπους αναγνώστες) ότι μια τιμή δεν πρέπει να τροποποιηθεί μετά την προετοιμασία. Είναι ένα από τα πιο σημαντικά εργαλεία στο C++ για τη σύνταξη ασφαλούς, αυτοτεκμηριωμένου κώδικα.

Τοconstδιαβάζεται από τα δεξιά προς τα αριστερά: Τοconst int*είναι ένας δείκτης σε ένα const int (το int δεν μπορεί να αλλάξει, ο δείκτης μπορεί). Τοint* constείναι ένας δείκτης const σε int (ο δείκτης δεν μπορεί να αλλάξει, το int μπορεί). Τοconst int* constείναι ένας δείκτης const σε ένα const int.

Το constexpr (C++11) προχωρά παραπέρα: σημαίνει ότι η τιμή πρέπει να είναι υπολογίσιμη σε χρόνο μεταγλώττισης. Αυτό επιτρέπει στον μεταγλωττιστή να αξιολογεί εκφράσεις κατά τη μεταγλώττιση, εξαλείφοντας εντελώς το κόστος χρόνου εκτέλεσης. Χρησιμοποιήστε τοconstexprγια σταθερές που είναι πραγματικά γνωστές κατά το χρόνο μεταγλώττισης.

Το consteval (C++20) είναι ακόμη πιο αυστηρό: επισημαίνει μια συνάρτηση που πρέπει να αξιολογηθεί κατά το χρόνο μεταγλώττισης — η κλήση της κατά το χρόνο εκτέλεσης είναι σφάλμα μεταγλώττισης.

Ο προσδιορισμόςvolatileλέει στον μεταγλωττιστή ότι η τιμή μιας μεταβλητής μπορεί να αλλάξει εκτός του ελέγχου του προγράμματος (π.χ. καταχωρητές υλικού, I/O με αντιστοίχιση μνήμης). Αποτρέπει ορισμένες βελτιστοποιήσεις. Μην χρησιμοποιείτε τοvolatileγια νήμα — χρησιμοποιήστε τοstd::atomic.

Παγίδα

Ένα από τα πιο ύπουλα σφάλματα στο C και στο παλιού τύπου C++ προέρχεται από περιορίζοντας τις μετατροπές — σιωπηρές μετατροπές που χάνουν πληροφορίες. Για παράδειγμα, η αντιστοίχιση ενόςdoubleσε έναintπερικόπτει σιωπηλά το κλασματικό τμήμα. Η αντιστοίχιση ενός μεγάλουintσε έναshortξεχειλίζει αθόρυβα.

Η προετοιμασία με αγκύλες{}αποτρέπει τον περιορισμό των μετατροπών κατά το χρόνο μεταγλώττισης. Αυτός είναι ένας από τους ισχυρότερους λόγους για να το προτιμήσετε έναντι της προετοιμασίας=. Εάν γράψετεint x{3.14};, ο μεταγλωττιστής θα εκπέμψει ένα σφάλμα. Με τοint x = 3.14;, περικόπτεται αθόρυβα στο 3.

Ομοίως, η ανάμειξη υπογεγραμμένων και ανυπόγραφων τύπων στην αριθμητική προκαλεί σιωπηρές μετατροπές που μπορούν να παράγουν εκπληκτικά αποτελέσματα. Ένα αρνητικόintσε σύγκριση με έναunsigned intθα μετατραπεί πρώτα σε ανυπόγραφο, δυνητικά να γίνει ένας πολύ μεγάλος θετικός αριθμός.

  • Brace initialization {} catches narrowing conversions at compile time
  • Assignment initialization (=) silently allows narrowing — data is lost without warning
  • Mixing signed and unsigned types causes implicit conversion to unsigned
  • Ένα αρνητικό int που μετατρέπεται σε ανυπόγραφο γίνεται πολύ μεγάλος θετικός αριθμός

Type Aliases

Type aliases improve readability and make it easier to change types later. Preferusingovertypedefin modern 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};
}
Έξοδος

Πατήστε Εκτέλεση για να μεταγλωττίσετε και να εκτελέσετε αυτό το παράδειγμα.

Βασικά συμπεράσματα
  • Every C++ expression has a type known at compile time — the type determines size, valid values, and valid operations
  • Use fixed-width types from (int32_t, uint64_t) when the exact size matters
  • Prefer brace initialization {} — it prevents dangerous narrowing conversions
  • Use nullptr (not NULL or 0) for null pointers to avoid overload ambiguity
  • auto deduces the type from the initializer; decltype queries the type of an expression without evaluating it
  • Use constexpr for compile-time constants and 'using' for type aliases

Κουίζ — Δοκιμάστε τις γνώσεις σας

(15 XP)

1. Ποιο είναι το πλεονέκτημα της αρχικοποίησης αγκύλης`int x{42};`έναντι της αρχικοποίησης ανάθεσης`int x = 42;`;

2. Γιατί πρέπει να χρησιμοποιήσετε το`nullptr`αντί για το`NULL`στο C++;

3. Τι συμπεραίνει το`auto x = 42;`ότι είναι ο τύπος του`x`;