C++學習筆記:強制類型轉換

0.舊式轉換

先看看舊式的強制類型轉換(如,整數進行浮點除法時會把其中一個變量用舊式轉換轉爲 double):

	//type(expr)  函數形式的強制類型轉換
	int count1 = 10;
	bool not_empty1 = bool(count1); //爲true
	//或者(type)expr  C語言風格的強制類型轉換
	int count2 = 0;
	bool not_empty2 = (bool)count2; //爲false

根據所涉及的類型不同,舊式的強制類型轉換分別具有與 const_cast、static_cast、reinterpret_cast 相似的行爲。與命名的強制類型轉換相比,舊式的強制類型轉換出問題更不容易追蹤。

 

除了舊式的轉換,還有四種命名的強制類型轉換:static_cast、const_cast、dynamic_cast 和 reinterpret_cast。

1.static_cast

任何具有明確定義的類型轉換,只要不包含底層const(形如const char *ptr,表示指針所指的對象是一個常量),都可以使用 static_cast。(C++ Primer Plus上說,只有兩者其中一方可以隱式轉換爲另一方(如向上轉型),轉換纔是合法的,否則會出錯)

	//進行強制類型轉換以便執行浮點數除法
	int a = 1, b = 2;
	double result = static_cast<double>(a) / b;

可以參照cpp在線手冊的例子來理解他的應用場景:

#include <vector>
#include <iostream>
 
struct B {
    int m = 0;
    void hello() const {
        std::cout << "Hello world, this is B!\n";
    }
};
struct D : B {
    void hello() const {
        std::cout << "Hello world, this is D!\n";
    }
};
 
enum class E { ONE = 1, TWO, THREE };
enum EU { ONE = 1, TWO, THREE };
 
int main()
{
    // 1: 初始化轉換
    int n = static_cast<int>(3.14); 
    std::cout << "n = " << n << '\n';
    std::vector<int> v = static_cast<std::vector<int>>(10);
    std::cout << "v.size() = " << v.size() << '\n';
 
    // 2: 靜態向下轉型
    D d;
    B& br = d; // 通過隱式轉換向上轉型
    br.hello();
    D& another_d = static_cast<D&>(br); // 向下轉型
    another_d.hello();
 
    // 3: 左值到右值
    std::vector<int> v2 = static_cast<std::vector<int>&&>(v);
    std::cout << "after move, v.size() = " << v.size() << '\n';
 
    // 4: 棄值表達式
    static_cast<void>(v2.size());
 
    // 5. 隱式轉換的逆
    void* nv = &n;
    int* ni = static_cast<int*>(nv);
    std::cout << "*ni = " << *ni << '\n';
 
    // 6. 數組到指針後隨向上轉型
    D a[10];
    B* dp = static_cast<B*>(a);
 
    // 7. 有作用域枚舉到 int 或 float
    E e = E::ONE;
    int one = static_cast<int>(e);
    std::cout << one << '\n';
 
    // 8. int 到枚舉,枚舉到另一枚舉
    E e2 = static_cast<E>(one);
    EU eu = static_cast<EU>(e2);
 
    // 9. 指向成員指針向上轉型
    int D::*pm = &D::m;
    std::cout << br.*static_cast<int B::*>(pm) << '\n';
 
    // 10. void* 到任何類型
    void* voidp = &e;
    std::vector<int>* p = static_cast<std::vector<int>*>(voidp);
}

//輸出
/*****************************
n = 3
v.size() = 10
Hello world, this is B!
Hello world, this is D!
after move, v.size() = 0
*ni = 3
1
0
******************************/

也可用於通過進行到指定類型的函數指針轉換,來消除函數重載的歧義,這在Qt信號槽裏也很常用(Qt新版本中開始用QOverload替換這種寫法),當信號有重載時,我們可以這樣寫:

    //void currentIndexChanged(int index)
    //void currentIndexChanged(const QString &text)
    QComboBox *comboBox=new QComboBox(this);
    connect(comboBox,static_cast<void(QComboBox::*)(const QString &)>(&QComboBox::currentIndexChanged),
            this,[=](const QString &text){});

2.const_cast

 const_cast運算符用於改變對象的const或volatile特徵。該運算符只能改變運算對象的底層const(形如const char *ptr,表示指針所指的對象是一個常量):

const char* ptr = "gong jian bo";
//可以轉換,但是通過p寫值是未定義的行爲
char* p = const_cast<char*>(ptr);


int i = 3;                 // 不聲明 i 爲 const
const int& rci = i; 
const_cast<int&>(rci) = 4; // OK:修改 i

如果對象本身不是一個常量 ,使用強制類型轉換獲得寫權限是合法的行爲。然而如果對象是一個常量,再使用const_cast執行寫操作就會產生未定義的行爲。

只有const_cast能修改表達式的常量屬性,使用其他形式的命名強制類型轉換改變表達式的常量屬性都將引發編譯器錯誤。同樣的,也不能用const_cast改變表達式的類型(只能用來改變常量屬性)。

常用於函數重載的上下文中,如《Effective C++:改善程序與設計的55個具體做法》一書就舉例:

class TextBlock {
public:
	//完整的邏輯寫在const版本中
	const char &operator[](std::size_t position) const{
		//... ...
		return text[position];
	}
	//非const版本通過const_cast對const版本轉換
	char& operator[](std::size_t position) {
		return const_cast<char&>( //將const轉除
			static_cast<const TextBlock&>(*this)//爲*this加上const
			[position] //調用const []
		);
	}
private:
	char* text;
};

3.reinterpret_cast

reinterpret_cast 通常爲運算對象的位模式提供較低層次上的重新解釋。與 static_cast 不同,但與 const_cast 類似,reinterpret_cast 表達式不會編譯成任何 CPU 指令(除非在整數和指針間轉換,或在指針表示依賴其類型的不明架構上)。它純粹是一個編譯時指令,指示編譯器將表達式視爲如同具有新類型類型一樣處理。它不允許刪除const;可以將指針類型轉換爲足以存儲指針表示的整形,但不能將指針轉換爲更小的整形或浮點型;不能將函數指針轉換爲數據指針,反之亦然。

	int* int_ptr;
	//char_ptr所指的真實對象是一個int而非字符,
	//如果把他當作普通的字符指針使用,可能在運行時發生錯誤
	char* char_ptr = reinterpret_cast<char*>(int_ptr);

可以參照cpp在線手冊的例子來理解他的應用場景: 

#include <cstdint>
#include <cassert>
#include <iostream>

int f() { return 42; }

int main()
{
    int i = 7;
 
    // 指針到整數並轉回
    std::uintptr_t v1 = reinterpret_cast<std::uintptr_t>(&i); // static_cast 爲錯誤
    std::cout << "The value of &i is 0x" << std::hex << v1 << '\n';
    int* p1 = reinterpret_cast<int*>(v1);
    assert(p1 == &i);
 
    // 到另一函數指針並轉回
    void(*fp1)() = reinterpret_cast<void(*)()>(f);
    // fp1(); 未定義行爲
    int(*fp2)() = reinterpret_cast<int(*)()>(fp1);
    std::cout << std::dec << fp2() << '\n'; // 安全
 
    // 通過指針的類型別名化
    char* p2 = reinterpret_cast<char*>(&i);
    if(p2[0] == '\x7')
        std::cout << "This system is little-endian\n";
    else
        std::cout << "This system is big-endian\n";
 
    // 通過引用的類型別名化
    reinterpret_cast<unsigned int&>(i) = 42;
    std::cout << i << '\n';
 
    [[maybe_unused]] const int &const_iref = i;
    // int &iref = reinterpret_cast<int&>(const_iref); // 編譯錯誤——不能去除 const
    // 必須用 const_cast 代替:int &iref = const_cast<int&>(const_iref);
}
//輸出
/************************
The value of &i is 0x7fff352c3580
42
This system is little-endian
42
************************/

4.dynamic_cast 

dynamic_cast運算符是最常用的RTTI(運行階段類型識別)組件,它不能回答<指針指向的是哪類對象>這樣的問題,但能夠回答<是否可以安全地將對象的地址賦值給特定類型的指針>這樣的問題。該運算符用於將基類的指針或引用安全的轉換成派生類的指針或引用(向下轉型)。如果轉換目標是指針類型且失敗了,結果爲0,即空指針;如果轉換目標是引用類型且失敗了,則拋出一個bad_cast異常。

可以參照cpp在線手冊的例子來理解他的應用場景: 

#include <iostream>
 
struct V {
    virtual void f() {};  // 必須爲多態以使用運行時檢查的 dynamic_cast
};
struct A : virtual V {};
struct B : virtual V {
  B(V* v, A* a) {
    // 構造中轉型(見後述 D 的構造函數中的調用)
    dynamic_cast<B*>(v); // 良好定義:v 有類型 V*,B 的 V 基類,產生 B*
    dynamic_cast<B*>(a); // 未定義行爲:a 有類型 A*,A 非 B 的基類
  }
};
struct D : A, B {
    D() : B((A*)this, this) { }
};
 
struct Base {
    virtual ~Base() {}
};
 
struct Derived: Base {
    virtual void name() {}
};
 
int main()
{
    D d; // 最終派生對象
    A& a = d; // 向上轉型,可以用 dynamic_cast,但不必須
    D& new_d = dynamic_cast<D&>(a); // 向下轉型
    B& new_b = dynamic_cast<B&>(a); // 側向轉型
 
 
    Base* b1 = new Base;
    if(Derived* d = dynamic_cast<Derived*>(b1))
    {
        std::cout << "downcast from b1 to d successful\n";
        d->name(); // 可以安全調用
    }
 
    Base* b2 = new Derived;
    if(Derived* d = dynamic_cast<Derived*>(b2))
    {
        std::cout << "downcast from b2 to d successful\n";
        d->name(); // 可以安全調用
    }
 
    delete b1;
    delete b2;
}
//輸出: downcast from b2 to d successful

dynamic_cast主要用於類層次間的上行轉換和下行轉換,還可以用於類之間的交叉轉換。
在類層次間進行上行轉換時,dynamic_cast和static_cast的效果是一樣的;
在進行下行轉換時,dynamic_cast具有類型檢查的功能,比static_cast更安全。 

5.參考

書籍:《C++Primer》中文第五版

書籍:《C++Primer Plus》中文第六版

博客:https://blog.csdn.net/u010540025/article/details/81231495

手冊:https://zh.cppreference.com/w/cpp/language/const_cast

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