轉https://www.cnblogs.com/Allen-rg/p/6999360.html
C++ 類型轉換(C風格的強制轉換):
在C++基本的數據類型中,可以分為四類:整型,浮點型,字符型,布爾型。其中數值型包括 整型與浮點型;字符型即為char。
(1)將浮點型數據賦值給整型變量時,舍棄其小數部分。
(2)將整型數據賦值給浮點型變量時,數值不變,但是以指數形式存儲。
(3)將double型數據賦值給float型變量時,注意數值范圍溢出。
(4)字符型數據可以賦值給整型變量,此時存入的是字符的ASCII碼。
(5)將一個int,short或long型數據賦值給一個char型變量,只將低8位原封不動的送到char型變量中。
(6)將有符號型數據賦值給長度相同的無符號型變量,連同原來的符號位一起傳送。
C++強制類型轉換:
在C++語言中新增了四個關鍵字static_cast、const_cast、reinterpret_cast和dynamic_cast。這四個關鍵字都是用於強制類型轉換的。
新類型的強制轉換可以提供更好的控制強制轉換過程,允許控制各種不同種類的強制轉換。
C++中風格是static_cast<type>(content)。C++風格的強制轉換其他的好處是,它們能更清晰的表明它們要干什么。程序員只要掃一眼這樣的代碼,就能立即知道一個強制轉換的目的。
1) static_cast
在C++語言中static_cast用於數據類型的強制轉換,強制將一種數據類型轉換為另一種數據類型。例如將整型數據轉換為浮點型數據。
[例1]C語言所采用的類型轉換方式:
int a = 10; int b = 3; double result = (double)a / (double)b;
例1中將整型變量a和b轉換為雙精度浮點型,然后相除。在C++語言中,我們可以采用static_cast關鍵字來進行強制類型轉換,如下所示。
[例2]static_cast關鍵字的使用:
int a = 10; int b = 3; double result = static_cast<double>(a) / static_cast<double>(b);
在本例中同樣是將整型變量a轉換為雙精度浮點型。采用static_cast進行強制數據類型轉換時,將想要轉換成的數據類型放到尖括號中,將待轉換的變量或表達式放在元括號中,其格式可以概括為如下形式:
用法:static_cast <類型說明符> (變量或表達式)
它主要有如下幾種用法:
(1)用於類層次結構中基類和派生類之間指針或引用的轉換
進行上行轉換(把派生類的指針或引用轉換成基類表示)是安全的
進行下行轉換(把基類的指針或引用轉換為派生類表示),由於沒有動態類型檢查,所以是不安全的
(2)用於基本數據類型之間的轉換,如把int轉換成char。這種轉換的安全也要開發人員來保證
(3)把空指針轉換成目標類型的空指針
(4)把任何類型的表達式轉換為void類型
注意:static_cast不能轉換掉expression的const、volitale或者__unaligned屬性。
static_cast:可以實現C++中內置基本數據類型之間的相互轉換。
如果涉及到類的話,static_cast只能在有相互聯系的類型中進行相互轉換,不一定包含虛函數。
2) const_cast
在C語言中,const限定符通常被用來限定變量,用於表示該變量的值不能被修改。
而const_cast則正是用於強制去掉這種不能被修改的常數特性,但需要特別注意的是const_cast不是用於去除變量的常量性,而是去除指向常數對象的指針或引用的常量性,其去除常量性的對象必須為指針或引用。
用法:const_cast<type_id> (expression)
該運算符用來修改類型的const或volatile屬性。除了const 或volatile修飾之外, type_id和expression的類型是一樣的。
常量指針被轉化成非常量指針,並且仍然指向原來的對象;
常量引用被轉換成非常量引用,並且仍然指向原來的對象;常量對象被轉換成非常量對象。
[例3]一個錯誤的例子:
const int a = 10; const int * p = &a; *p = 20; //compile error int b = const_cast<int>(a); //compile error
在本例中出現了兩個編譯錯誤,第一個編譯錯誤是*p因為具有常量性,其值是不能被修改的;另一處錯誤是const_cast強制轉換對象必須為指針或引用,而例3中為一個變量,這是不允許的!
[例4]const_cast關鍵字的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#include<iostream>
using
namespace
std;
int
main()
{
const
int
a = 10;
const
int
* p = &a;
int
*q;
q =
const_cast
<
int
*>(p);
*q = 20;
//fine
cout <<a<<
" "
<<*p<<
" "
<<*q<<endl;
cout <<&a<<
" "
<<p<<
" "
<<q<<endl;
return
0;
}
|
在本例中,我們將變量a聲明為常量變量,同時聲明了一個const指針指向該變量(此時如果聲明一個普通指針指向該常量變量的話是不允許的,Visual Studio 2010編譯器會報錯)。
之后我們定義了一個普通的指針*q。將p指針通過const_cast去掉其常量性,並賦給q指針。之后我再修改q指針所指地址的值時,這是不會有問題的。
最后將結果打印出來,運行結果如下:
10 20 20
002CFAF4 002CFAF4 002CFAF4
查看運行結果,問題來了,指針p和指針q都是指向a變量的,指向地址相同,而且經過調試發現002CFAF4地址內的值確實由10被修改成了20,這是怎么一回事呢?為什么a的值打印出來還是10呢?
其實這是一件好事,我們要慶幸a變量最終的值沒有變成20!變量a一開始就被聲明為一個常量變量,不管后面的程序怎么處理,它就是一個常量,就是不會變化的。試想一下如果這個變量a最終變成了20會有什么后果呢?對於這些簡短的程序而言,如果最后a變成了20,我們會一眼看出是q指針修改了,但是一旦一個項目工程非常龐大的時候,在程序某個地方出現了一個q這樣的指針,它可以修改常量a,這是一件很可怕的事情的,可以說是一個程序的漏洞,畢竟將變量a聲明為常量就是不希望修改它,如果后面能修改,這就太恐怖了。
在例4中我們稱“*q=20”語句為未定義行為語句,所謂的未定義行為是指在標准的C++規范中並沒有明確規定這種語句的具體行為,該語句的具體行為由編譯器來自行決定如何處理。對於這種未定義行為的語句我們應該盡量予以避免!
從例4中我們可以看出我們是不想修改變量a的值的,既然如此,定義一個const_cast關鍵字強制去掉指針的常量性到底有什么用呢?我們接着來看下面的例子。
例5:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
#include<iostream>
using
namespace
std;
const
int
* Search(
const
int
* a,
int
n,
int
val);
int
main()
{
int
a[10] = {0,1,2,3,4,5,6,7,8,9};
int
val = 5;
int
*p;
p =
const_cast
<
int
*>(Search(a, 10, val));
if
(p == NULL)
cout<<
"Not found the val in array a"
<<endl;
else
cout<<
"hvae found the val in array a and the val = "
<<*p<<endl;
return
0;
}
const
int
* Search(
const
int
* a,
int
n,
int
val)
{
int
i;
for
(i=0; i<n; i++)
{
if
(a[i] == val)
return
&a[i];
}
return
NULL;
}
|
在例5中我們定義了一個函數,用於在a數組中尋找val值,如果找到了就返回該值的地址,如果沒有找到則返回NULL。函數Search返回值是const指針,當我們在a數組中找到了val值的時候,我們會返回val的地址,最關鍵的是a數組在main函數中並不是const,因此即使我們去掉返回值的常量性有可能會造成a數組被修改,但是這也依然是安全的。
對於引用,我們同樣能使用const_cast來強制去掉常量性,如例6所示。
例6:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
#include<iostream>
using
namespace
std;
const
int
& Search(
const
int
* a,
int
n,
int
val);
int
main()
{
int
a[10] = {0,1,2,3,4,5,6,7,8,9};
int
val = 5;
int
&p =
const_cast
<
int
&>(Search(a, 10, val));
if
(p == NULL)
cout<<
"Not found the val in array a"
<<endl;
else
cout<<
"hvae found the val in array a and the val = "
<<p<<endl;
return
0;
}
const
int
& Search(
const
int
* a,
int
n,
int
val)
{
int
i;
for
(i=0; i<n; i++)
{
if
(a[i] == val)
return
a[i];
}
return
NULL;
}
|
了解了const_cast的使用場景后,可以知道使用const_cast通常是一種無奈之舉,同時也建議大家在今后的C++程序設計過程中一定不要利用const_cast去掉指針或引用的常量性並且去修改原始變量的數值,這是一種非常不好的行為。
3) reinterpret_cast
在C++語言中,reinterpret_cast主要有三種強制轉換用途:改變指針或引用的類型、將指針或引用轉換為一個足夠長度的整形、將整型轉換為指針或引用類型。
用法:reinterpret_cast<type_id> (expression)
type-id必須是一個指針、引用、算術類型、函數指針或者成員指針。
它可以把一個指針轉換成一個整數,也可以把一個整數轉換成一個指針(先把一個指針轉換成一個整數,在把該整數轉換成原類型的指針,還可以得到原先的指針值)。
在使用reinterpret_cast強制轉換過程僅僅只是比特位的拷貝,因此在使用過程中需要特別謹慎!
例7:
int *a = new int; double *d = reinterpret_cast<double *>(a);
4) dynamic_cast
用法:dynamic_cast<type_id> (expression)
(1)其他三種都是編譯時完成的,dynamic_cast是運行時處理的,運行時要進行類型檢查。
(2)不能用於內置的基本數據類型的強制轉換。
(3)dynamic_cast轉換如果成功的話返回的是指向類的指針或引用,轉換失敗的話則會返回NULL。
(4)使用dynamic_cast進行轉換的,基類中一定要有虛函數,否則編譯不通過。
B中需要檢測有虛函數的原因:類中存在虛函數,就說明它有想要讓基類指針或引用指向派生類對象的情況,此時轉換才有意義。
只有定義了虛函數的類才有虛函數表。
(5)在類的轉換時,在類層次間進行上行轉換時,dynamic_cast和static_cast的效果是一樣的。在進行下行轉換時,dynamic_cast具有類型檢查的功能,比static_cast更安全。
向上轉換,即為子類指針指向父類指針(一般不會出問題);向下轉換,即將父類指針轉化子類指針。
向下轉換的成功與否還與將要轉換的類型有關,即要轉換的指針指向的對象的實際類型與轉換以后的對象類型一定要相同,否則轉換失敗。
在C++中,編譯期的類型轉換有可能會在運行時出現錯誤,特別是涉及到類對象的指針或引用操作時,更容易產生錯誤。Dynamic_cast操作符則可以在運行期對可能產生問題的類型轉換進行測試。
例1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
#include<iostream>
using
namespace
std;
class
base
{
public
:
void
m(){cout<<
"m"
<<endl;}
};
class
derived :
public
base
{
public
:
void
f(){cout<<
"f"
<<endl;}
};
int
main()
{
derived * p;
p =
new
base;
p =
static_cast
<derived *>(
new
base);
p->m();
p->f();
return
0;
}
|
本例中定義了兩個類:base類和derived類,這兩個類構成繼承關系。在base類中定義了m函數,derived類中定義了f函數。在前面介紹多態時,我們一直是用基類指針指向派生類或基類對象,而本例則不同了。
本例主函數中定義的是一個派生類指針,當我們將其指向一個基類對象時,這是錯誤的,會導致編譯錯誤。
但是通過強制類型轉換我們可以將派生類指針指向一個基類對象,p = static_cast<derived *>(new base);語句實現的就是這樣一個功能,這樣的一種強制類型轉換時合乎C++語法規定的,但是是非常不明智的,它會帶來一定的危險。
在程序中p是一個派生類對象,我們將其強制指向一個基類對象,首先通過p指針調用m函數,因為基類中包含有m函數,這一句沒有問題,之后通過p指針調用f函數。一般來講,因為p指針是一個派生類類型的指針,而派生類中擁有f函數,因此p->f();這一語句不會有問題,但是本例中p指針指向的確實基類的對象,而基類中並沒有聲明f函數,雖然p->f();這一語句雖然仍沒有語法錯誤,但是它卻產生了一個運行時的錯誤。換言之,p指針是派生類指針,這表明程序設計人員可以通過p指針調用派生類的成員函數f,但是在實際的程序設計過程中卻誤將p指針指向了一個基類對象,這就導致了一個運行期錯誤。
產生這種運行期的錯誤原因在於static_cast強制類型轉換時並不具有保證類型安全的功能,而C++提供的dynamic_cast卻能解決這一問題,dynamic_cast可以在程序運行時檢測類型轉換是否類型安全。
當然dynamic_cast使用起來也是有條件的,它要求所轉換的操作數必須包含多態類類型(即至少包含一個虛函數的類)。
例2:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
#include<iostream>
using
namespace
std;
class
base
{
public
:
void
m(){cout<<
"m"
<<endl;}
};
class
derived :
public
base
{
public
:
void
f(){cout<<
"f"
<<endl;}
};
int
main()
{
derived * p;
p =
new
base;
p =
dynamic_cast
<derived *>(
new
base);
p->m();
p->f();
return
0;
}
|
在本例中利用dynamic_cast進行強制類型轉換,但是因為base類中並不存在虛函數,因此p = dynamic_cast<derived *>(new base);這一句會編譯錯誤。
為了解決本例中的語法錯誤,我們可以將base類中的函數m聲明為虛函數,virtual void m(){cout<<"m"<<endl;}。
dynamic_cast還要求<>內部所描述的目標類型必須為指針或引用。
例3:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
|
#include<iostream>
#include<cstring>
using
namespace
std;
class
A
{
public
:
virtual
void
f()
{
cout<<
"hello"
<<endl;
};
};
class
B:
public
A
{
public
:
void
f()
{
cout<<
"hello2"
<<endl;
};
};
class
C
{
void
pp()
{
return
;
}
};
int
fun()
{
return
1;
}
int
main()
{
A* a1=
new
B;
//a1是A類型的指針指向一個B類型的對象
A* a2=
new
A;
//a2是A類型的指針指向一個A類型的對象
B* b;
C* c;
b=
dynamic_cast
<B*>(a1);
//結果為not null,向下轉換成功,a1之前指向的就是B類型的對象,所以可以轉換成B類型的指針。
if
(b==NULL)
{
cout<<
"null"
<<endl;
}
else
{
cout<<
"not null"
<<endl;
}
b=
dynamic_cast
<B*>(a2);
//結果為null,向下轉換失敗
if
(b==NULL)
{
cout<<
"null"
<<endl;
}
else
{
cout<<
"not null"
<<endl;
}
c=
dynamic_cast
<C*>(a);
//結果為null,向下轉換失敗
if
(c==NULL)
{
cout<<
"null"
<<endl;
}
else
{
cout<<
"not null"
<<endl;
}
delete
(a);
return
0;
}
|