C++——數據類型筆記

  在C++編程中,瞭解各類數據類型也是至關重要的。下面我會總結一下C++中的數據類型,包括基本類型,符合類型和自定義類型。方便自己整理和理解。

1,基本類型

  C++中的基本類型是構建其他數據類型的基礎,常見的基礎類型包括整型,浮點型,字符型和布爾型:

  • 整型:用於表示整數,如 intshortlong 等。
  • 浮點型:用於表示帶小數部分的數值,如 floatdouble 等。
  • 字符型:用於表示單個字符,如 char
  • 布爾型:用於表示邏輯值,只能取 truefalse

  每種數據類型在不同平臺上的大小可能會有所不同,但通常遵循一定的規則,具體分析如下:

整型

  • int:通常佔用 4 個字節(32 位),範圍爲 -2147483648 到 2147483647。
  • short:通常佔用 2 個字節(16 位),範圍爲 -32768 到 32767。
  • long:通常佔用 4 個字節(32 位),範圍與 int 類似,但取決於平臺,有時會更大。
  • long long:通常佔用 8 個字節(64 位),範圍爲 -9223372036854775808 到 9223372036854775807。

浮點型

  • float:通常佔用 4 個字節(32 位),範圍爲約 ±3.4e-38 到 ±3.4e38,精度爲約 6-7 位小數。
  • double:通常佔用 8 個字節(64 位),範圍爲約 ±1.7e-308 到 ±1.7e308,精度爲約 15-16 位小數。
  • long double:通常佔用 12 或 16 個字節,範圍和精度比 double 更大,具體取決於編譯器和平臺。

字符型

  • char:通常佔用 1 個字節,表示一個字符(ASCII 碼或其他字符集中的字符)。

   char 類型可以是有符號的(signed char)或無符號的(unsigned char)。默認情況下,char 類型被視爲有符號的,但可以使用 unsigned char 顯式地聲明無符號的字符。

   有符號字符(signed char):有符號字符可以表示正數、負數和零。對於有符號字符,大多數編譯器使用補碼錶示法來表示負數。範圍爲 -128 到 127(包括)。

  無符號字符(unsigned char):無符號字符只能表示非負數(即正數和零),範圍爲 0 到 255(包括)。無符號字符通常用於處理字節數據,因爲它們可以表示更大的範圍。

  示例代碼:

#include <iostream>

int main() {
    char c1 = 'A'; // 有符號字符
    unsigned char c2 = 'B'; // 無符號字符

    std::cout << "Signed char: " << c1 << std::endl;
    std::cout << "Unsigned char: " << c2 << std::endl;

    return 0;
}

  在上面的示例中,c1c2 都存儲了一個字符。c1 是一個有符號字符,c2 是一個無符號字符。由於 char 類型通常是有符號的,所以在使用 unsigned char 時需要格外注意範圍,避免發生意外的結果。

 

布爾型

  • bool:通常佔用 1 個字節,值爲 truefalse

  打印基本字符示例代碼:

#include <iostream>

int main() {
    int num = 10;
    float pi = 3.14;
    char letter = 'A';
    bool isTrue = true;

    std::cout << "Integer: " << num << std::endl;
    std::cout << "Float: " << pi << std::endl;
    std::cout << "Character: " << letter << std::endl;
    std::cout << "Boolean: " << isTrue << std::endl;

    return 0;
}

   要查看特定數據類型在當前平臺的大小,可以使用`sizeof` 運算符。例如,要查看`int`的大小,可以編寫以下代碼:

#include <iostream>

int main() {
    std::cout << "Size of int: " << sizeof(int) << " bytes" << std::endl;
    return 0;
}

  編譯並運行這段代碼,將輸出 `Size of int: 4 bytes`,表示 `int` 類型在當前平臺上佔用四個字節。

 

2,複合類型

  C++中的複合數據類型是由基本類型或其他複合數據類型組成的類型,包括數組,指針,引用和結構體。每種複合數據類型在內存中的存儲方式和大小都有所不同,以下是C++中常見的複合數據類型及其特點:

數組

  • 數組是由相同類型的元素組成的集合。
  • 數據的大小在創建時就確定,並且在整個生命週期中保持不變
  • 訪問數組元素時,可以使用下標(索引)來訪問特定位置的元素。

指針

  • 指針是存儲其他變量內存地址的變量。
  • 指針可以指向任何數據類型的變量,甚至可以指向函數。
  • 通過指針,可以直接訪問或修改指向變量的值。

引用

  • 引用爲變量取一個別名,用於簡化代碼和提高效率。
  • 引用在創建時,必須初始化,並且一旦引用被初始化後,就不能再引用其他變量。

結構體

  • 結構體是用戶自定義的數據類型,用於將不同類型的數據組合在一起形成一個新的數據類型。
  • 結構體的成員可以包括基本數據類型,複合數據類型甚至其他結構體。

  示例代碼:

#include <iostream>

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int* ptr = arr;
    int& ref = num;

    std::cout << "Array: ";
    for (int i = 0; i < 5; ++i) {
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;

    std::cout << "Pointer: " << *ptr << std::endl;
    std::cout << "Reference: " << ref << std::endl;

    return 0;
}

  具體的使用示例:

// 數組
int arr[5]; // 聲明一個包含5個整數的數組

// 指針
int num = 10;
int* ptr = # // 聲明一個指向整數的指針,並將其指向num的地址

// 引用
int num = 10;
int& ref = num; // 聲明一個引用ref,其別名爲num


// 結構體
struct Person {
    std::string name;
    int age;
};

Person p1 = {"Alice", 30}; // 聲明一個Person類型的結構體變量p1

  

2.1  引用和指針的聯繫和區別

  引用(Reference)和 指針(Pointer) 是C++中常用的兩種機制,用於間接訪問變量。總的來說,引用是一種更安全,更直觀的機制,用於簡化代碼和提高效率;而指針則更加靈活,可以在運行時動態地指向不同的對象,在選擇使用引用還是指針時,應根據具體的需求和情況來決定。

  他們有以下聯繫:

  1. 都可以用於函數參數傳遞:

    • 引用和指針都可以用於函數參數傳遞,可以實現對參數的修改。
  2. 都可以用於返回引用或指針:

    • 函數可以返回引用或指針,讓調用者可以訪問函數內部的變量。
  3. 都可以用於動態內存管理:

    • 指針通常用於動態內存分配和釋放(newdelete)。
    • 引用通常用於簡化代碼和提高效率,但不能指向動態分配的內存。
  4. 都可以用於迭代器:

    • 在 STL 中,迭代器通常是指針或類似指針的對象,用於遍歷容器中的元素。
    • 引用也可以用於類似的目的,但較少見。

  他們有幾個主要的區別:

聲明和初始化

  • 引用使用`&`符號進行聲明和初始化,引用在創建時必須初始化,並且一旦初始化後,不能再引用其他變量。
  • 指針使用 `*` 符號進行聲明和初始化,指針可以在任何時候被賦值爲另一個地址。
int num = 10;
int& ref = num; // 引用
int* ptr = # // 指針

  

空值

  • 引用不允許爲空,必須在初始化時綁定到一個合法的對象。
  • 指針可以爲空,可以指向空(nullptr)或未初始化的內存地址。

 

操作

  • 引用在使用時不需要解引用操作符 `*`,因爲他本身就是目標變量的別名。
  • 指針需要解引用操作符 `*`來訪問目標變量的值。
int num = 10;
int& ref = num; // 引用
int* ptr = &num // 指針

int val1 = ref; // 直接訪問 num 的值
int val2 = *ptr; // 使用解引用操作符訪問 num 的值

  

操作的對象

  • 引用只能和變量綁定,不能指向其他引用或無法取地址的臨時對象。引用沒有自己的地址,因此不能對引用進行取地址操作(&ref 是錯誤的)。
  • 指針可以指向其他指針,或無法取地址的臨時對象。

 

3,自定義類型

  在C++中,可以使用class和struct 關鍵字來創建自定義類型,這些類型可以包含自定義的數據成員和成員函數。自定義數據類型的理解可以簡單地歸納爲以下幾點:

  1. 抽象數據類型:自定義數據類型可以將多個數據組合在一起,形成一個新的抽象數據類型,用於描述某種概念或實體。

  2. 模塊化:自定義數據類型可以幫助我們將程序模塊化,將相關的數據和操作封裝在一起,提高代碼的可讀性和可維護性。

  3. 代碼複用:通過定義自定義數據類型,可以在程序中多次使用相同的數據結構,從而提高代碼的複用性。

  4. 數據封裝:自定義數據類型可以通過訪問控制符(如 publicprivateprotected)實現數據封裝,隱藏數據的具體實現細節,提高數據的安全性和可靠性。

  總的來說,自定義數據類型是 C++ 中非常重要的概念,它能夠幫助我們更好地組織和管理數據,提高程序的可讀性、可維護性和複用性。

  下面是一個class的示例代碼:

#include <iostream>

class Point {
private:
    int x;
    int y;

public:
    Point(int xCoord, int yCoord) : x(xCoord), y(yCoord) {}

    void display() {
        std::cout << "Point: (" << x << ", " << y << ")" << std::endl;
    }
};

int main() {
    Point p(3, 4);
    p.display();

    return 0;
}

  下面是struct的示例代碼:

#include <iostream>
using namespace std;

// 定義一個結構體表示學生
struct Student {
    string name;
    int age;
    float score;
};

int main() {
    // 聲明一個結構體變量
    Student stu;

    // 對結構體變量賦值
    stu.name = "Alice";
    stu.age = 20;
    stu.score = 90.5;

    // 輸出結構體變量的值
    cout << "Name: " << stu.name << endl;
    cout << "Age: " << stu.age << endl;
    cout << "Score: " << stu.score << endl;

    return 0;
}

  在上面的例子中,Student 是一個自定義的數據類型,它由三個成員組成:nameagescore。我們可以使用 Student 類型來聲明變量 stu,並對其進行操作,就像操作內置數據類型一樣。這樣,我們就可以更靈活地組織和管理數據。

 

4,特殊的數據類型——枚舉

   枚舉數據類型是 C++ 中的一種特殊類型,它既不屬於基本數據類型,也不屬於複合數據類型,而是一種獨立的數據類型。枚舉類型用於定義一組具有相關性的常量,這些常量被稱爲枚舉值。枚舉值可以像整數一樣使用,但其取值被限定爲預先定義的枚舉列表中的一個。

  在 C++ 中,枚舉類型使用 enum 關鍵字進行定義。例如:

enum Color {
    RED,
    GREEN,
    BLUE
};

int main() {
    Color c = RED;
    return 0;
}

  在這個例子中,Color 是一個枚舉類型,它定義了三個枚舉值 REDGREENBLUE。在聲明枚舉類型變量時,可以將其賦值爲枚舉值中的一個。

  雖然枚舉類型可以被看作是一種自定義數據類型,但由於其特殊的定義和用法,通常不將其歸類爲基本數據類型或複合數據類型。它更像是一種特殊的常量集合,用於提高代碼的可讀性和可維護性。

  當你在編寫程序時,可能會遇到一些情況,需要用到一組固定的常量。例如,你可能想表示一週中的每一天,或者表示一種顏色。爲了更好地組織這些常量並使代碼更易讀,C++ 提供了枚舉(enumeration)類型。

4.1 什麼是枚舉?

  枚舉是一種用戶定義的數據類型,用於定義一組命名的整數常量。這些常量稱爲枚舉值。枚舉類型提供了一種將常量組織在一起並給它們賦予有意義的名字的方法,從而提高代碼的可讀性和可維護性。

4.2 如何定義枚舉?

  在 C++ 中,使用 enum 關鍵字來定義枚舉類型。語法如下:

enum 枚舉名 {
    枚舉值1,
    枚舉值2,
    ...
};

  其中,枚舉名 是你爲枚舉類型取的名字,枚舉值1枚舉值2 等是你想定義的枚舉值。例如:

enum Day {
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
};

  

4.3 如何使用枚舉?

  定義枚舉後,你可以聲明枚舉類型的變量,並將其賦值爲枚舉值之一。例如:

Day today = MONDAY;

  你還可以直接使用枚舉值,無需聲明枚舉類型的變量。例如:

cout << "Today is " << MONDAY << endl;

  

4.4 枚舉值的默認賦值規則

  在枚舉中,每個枚舉值都被賦予一個整數值,如果你沒有顯式地爲枚舉值指定值,那麼它們將自動從0開始遞增。第一個枚舉值默認爲0,後續枚舉值依次遞增。

4.5 枚舉的優點

  枚舉可以提高代碼的可讀性,因爲它們使得常量的含義更加明確。例如,使用 Day::MONDAY 要比使用數字 0 更能表達意圖。此外,枚舉還可以幫助你避免使用魔法數字,使得代碼更易於理解和維護。

 

5,數據類型的使用示例

5.1 如何使用代碼檢查變量的數據類型

  在 C++ 中,可以使用 typeid 運算符和 type_info 類來檢查數據類型。這對於在運行時確定對象的實際類型非常有用。

  以下是一個簡單的示例,演示如何使用 typeid 來檢查數據類型:

#include <iostream>
#include <typeinfo>

int main() {
    int num = 10;
    double pi = 3.14159;
    std::string str = "Hello";

    // 使用 typeid 和 type_info 來獲取和打印變量的類型信息
    std::cout << "num is of type: " << typeid(num).name() << std::endl;
    std::cout << "pi is of type: " << typeid(pi).name() << std::endl;
    std::cout << "str is of type: " << typeid(str).name() << std::endl;

    return 0;
}

  在這個例子中,typeid 返回一個 type_info 對象,該對象包含有關變量類型的信息,具體來說,返回一個 type_info 對象,該對象包含有關表達式類型的信息。.name() 方法返回一個 C-style 字符串,表示類型的名稱。請注意,類型名稱可能因編譯器而異,因此結果可能會有所不同。

  需要注意的是,typeidtype_info 主要用於在運行時獲取類型信息。在編譯時確定類型的情況下,可以使用模板和 decltype 等技術。需要注意的是,typeid 運算符的參數可以是任何表達式,包括變量、指針、引用等。它通常用於在運行時獲取對象的實際類型,這在某些情況下對於進行類型檢查和運行時多態非常有用。

5.2 類型轉換符

  在 C++ 中,有幾種類型轉換運算符可用於在不同類型之間進行轉換。每種轉換運算符都有其自己的用途和限制。以下是其中一些常見的類型轉換運算符及其用法:

5.2.1 static_cast

  static_cast是C++中一個類型轉換運算符,用於在編譯時進行類型轉換。它是一種相對安全的轉換,通常用於相似類型之間的轉換。static_cast:用於執行靜態類型轉換,在編譯時進行檢查。它可以在合理範圍內將一種類型轉換爲另一種類型,例如將整數轉換爲浮點數,或者將基類指針轉換爲派生類指針。

  下面是static_cast 的基本用法:

new_type = static_cast<new_type>(expression);

  其中 new_type 是想要轉換的目標類型,而expression是要被轉換的表達式或變量。

  使用static_cast 的情況包括但不限於:

  1,相似類型之間的轉換:當需要在具有繼承關係的類之間進行類型轉換,或者在相關但不同類型之間進行轉換時。static_cast是一種比較安全的選擇

class Base {};
class Derived : public Base {};

Base* basePtr = new Derived;
Derived* derivedPtr = static_cast<Derived*>(basePtr);

  

  2,顯式類型轉換:當你需要進行顯式的類型轉換時,而不是依賴隱式類型轉換

double x = 3.14;
int y = static_cast<int>(x);

  

  3,指針和數值類型之間的轉換:當你需要在指針和數值類型之間進行轉換時,static_cast也是一種安全的選擇

int intValue = 42;
void* voidPtr = static_cast<void*>(&intValue);

  

  4,避免編譯器警告:在一些情況下,使用 static_cast 可以幫助避免編譯器產生警告,特別是在一些窄化轉換的情況

 

   示例:

double d = 3.14;
int i = static_cast<int>(d); // 將 double 轉換爲 int

  

5.2.2 reinterpret_cast

  reinterpret_cast是C++中的一種類型轉換運算符,用於進行低級別的類型轉換。通過用於將一個指針轉換爲另一種類型的指針,或者將一個指針轉換爲整數類型。因爲它繞過了類型系統的一些安全檢查,所以需要謹慎使用。
  reinterpret_cast的基本語法如下:

new_type = reinterpret_cast<new_type>(expression);

  其中,new_type 是你想要轉換的目標類型,而 expression 是要被轉換的表達式或變量。

  一些reinterpret_cast的場景包括:
  1,指針類型之間的轉換:在一些特殊情況下,你可能需要將一個指針類型轉換爲另一種指針類型

int* intValue = new int(42);
char* charPtr = reinterpret_cast<char*>(intValue);

  

  2,指針和整數類型之間的轉換:將指針轉換爲整數類型,或者將整數類型轉換爲指針

int* intValue = new int(42);
uintptr_t intValueAsInt = reinterpret_cast<uintptr_t>(intValue);

  需要注意的是,reinterpret_cast 提供了很大的靈活性,但也有潛在的危險。由於它繞過了類型系統的一些保護,使用時需要確保轉換是合理和安全的。

  通常情況下,首先應該考慮使用更安全的 static_cast,只有在確信沒有更好的替代方案時,才考慮使用 reinterpret_cast。

  示例:

int* ptr = reinterpret_cast<int*>(0x1234); // 將整數轉換爲指針

  

5.2.3  const_cast

const_cast:用於去除變量的常量屬性,或者添加常量屬性。主要用於解決函數重載時的二義性問題,以及在某些情況下修改 const 對象的值。

  去除常量性的示例:

const int i = 10;
int& j = const_cast<int&>(i); // 去除變量的 const 屬性
j = 20; // 可以修改 j,也會影響 i

  在上面的例子中,const_cast 被用於去除 i 的常量性,從而可以通過 j 修改 i 的值。但是需要注意的是,這種做法雖然在語法上是合法的,但修改了 const 對象的值是一種未定義行爲(undefined behavior),因此應該謹慎使用,並確保不會導致程序出現問題。

  添加常量性的示例:

int value = 10;
const int& ref = value; // 引用 value 的 const 引用
int& mutableRef = const_cast<int&>(ref); // 添加常量性
mutableRef = 20; // 不會改變 ref 所綁定的對象 value 的值

  在這個例子中,const_cast 被用於添加 ref 的常量性,從而確保 mutableRef 不能用於修改 value 的值。這在某些情況下可以幫助解決函數重載時的二義性問題。

  需要注意的是,const_cast 用於去除或添加常量性時,都要確保所操作的對象實際上是可修改的。否則,如果嘗試修改一個本來就是常量的對象,將會導致未定義行爲。因此,在使用 const_cast 時應謹慎並確保操作的安全性。

 

5.2.4  dynamic_cast

  dynamic_cast:主要用於在多態類型之間進行安全的向下轉型。只能用於包含虛函數的類,用於將基類指針或引用轉換爲派生類指針或引用。dynamic_cast 在轉換過程中會進行類型檢查,如果轉換是不安全的,則會返回一個空指針(對於指針轉換)或拋出一個 bad_cast 異常(對於引用轉換)。

  dynamic_cast 只能用於具有虛函數的類(即多態類型)。因爲在進行向下轉型時,需要通過虛函數表來確定對象的實際類型,從而保證轉型的安全性。

  示例:

class Base {
public:
    virtual ~Base() {}
};

class Derived : public Base {};

int main() {
    Base* basePtr = new Derived();
    
    Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);
    if (derivedPtr) {
        // 轉換成功
        // 使用 derivedPtr 操作 Derived 類型的對象
    } else {
        // 轉換失敗
    }

    delete basePtr;
    return 0;
}

  在上面的例子中,basePtr 是一個指向 Base 類型的基類指針,但它實際指向一個 Derived 類型的派生類對象。通過使用 dynamic_castbasePtr 轉換爲 Derived* 類型的指針 derivedPtr,我們可以安全地操作 Derived 類型的對象。

  需要注意的是,如果 basePtr 指向的對象不是 Derived 類型的實例,而是其他類型的對象(或者是一個空指針),那麼 dynamic_cast 將返回一個空指針,表示轉換失敗。因此,在使用 dynamic_cast 進行向下轉型時,要確保轉換是安全的,否則可能會導致程序運行時的錯誤。

 

5.2.5 reinterpret_cast和static_cast的主要區別

  reinterpret_cast和static_cast是C++中兩種不同類型的類型轉換運算符,他們之間有些主要的區別。

1,類型轉換的安全性
  reinterpret_cast主要用於指針類型之間的轉換,可以進行不安全的轉換;它幾乎不進行類型轉換,例如將指針轉換爲整數,或者將一個指針類型轉換爲另一種不相關的指針類型。
  static_cast主要用於基本類型之間的轉換,提供一種相對安全的類型轉換方法,例如基類指針到派生類指針的轉換

char c = 'A';
int* p = reinterpret_cast<int*>(&c); // 危險的轉換,可能導致未定義的行爲

int i = 10;
double d = static_cast<double>(i); // 安全的轉換,類型轉換可行

  

2.編譯時檢查
  static_cast 在編譯時進行類型檢查,如果轉換是不合理或者不安全的,編譯器會發生警告或錯誤
  reinterpret_cast 在編譯時幾乎沒有類型檢查,它主要依賴於程序員的責任來確保類型轉換的正確性

  總的來說,static_cast更加安全,更易讀,而reinterpret_cast則更加底層、靈活,但潛在的危險性更高,需要謹慎使用。在選擇使用哪種類型轉換時,要根據具體的情況和需求來權衡安全性和靈活性。

5.3 數據類型——int的使用誤區

  比如:我定義了一個int類型的變量a,然後我求 a/100,輸出結果爲0

  注意:如果我的a是一個整數,即int類型,那麼a/100的結果可能是0,尤其是當a的值小於100時。因爲在C++中,整數除法的結果仍然是整數,當a小於100,那麼a/100的計算結果將被截斷爲0,因爲整數除法會丟棄掉小數部分。即使a是大於等於100的正整數,結果也會是整數部分。

  如果要獲得精確的結果,那麼則需要將其中一個運算符轉換爲浮點數,示例如下:

#include <iostream>

int main(){
  int a = 75;
  double result = static_cast<double>(a) . 100.0 ;

  std::cout << result << std::endl;

  return 0;
}

  在這個例子中,我們使用 static_cast<double>(a) 將整數 a 轉換爲雙精度浮點數,然後進行除法。這樣,結果將包含小數部分。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章