c语言中的类型转换
在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转化,C语言中总共有两种形式的类型转换:隐式类型转换和显式类型转换。
隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败.
显式类型转化:需要用户自己处理.
int main()
{
//隐式类型转换
double d = 1.1;
int i = d;
cout << d<< endl; //1.1
cout << i << endl; //1
//显式类型转换
int* p = &i;
int address = (int)p;
cout << p << endl;
cout << address << endl;
return 0;
}
为什么C++需要四种类型转换.
常规中c语言隐式类型转留下了一些出乎意料的坑.
例如:在字符串的插入函数中,pos为0.
- 若end为无符号类型整数,那么end经过循环减为’负数’之后反而变成了一个很大的正数,这样很容易造成越界.
- 若end为有符号整数,通过end >= pos 这一过程由有符号整数转换为无符号整数,这样又会造成越界.
void Insert(size_t pos, char ch)
{
size_t size = 5;
size_t end = size - 1;
while (end >= pos)
{
//_tr[end + 1] = _str[end];
--end;
}
}
C风格的转换格式很简单,但是有不少缺点的:
- 隐式类型转化有些情况下可能会出问题:比如数据精度丢失
- 显式类型转换将所有情况混合在一起,代码不够清晰.
C++强制类型转换
static_cast
static_cast用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用static_cast,但它不能用于两个不相关的类型进行转换
int main()
{
double d = 12.34;
int a = static_cast<int>(d);
cout << a << endl;
return 0;
}
reinterpret_cast
reinterpret_cast一般用于两个不相关的类型转换.
int main()
{
int a = 10;
int* p = &a;
int address = reinterpret_cast<int>(p);
cout << address << endl;
return 0;
}
const_cast
const_cast最常用的用途就是删除变量的const属性,方便赋值.
int main()
{
const int a = 2;
int* p = const_cast<int*>(&a);
*p = 3;
cout << a << endl; //2
cout << *p << endl; //3
return 0;
}
注意:
1.以上代码使用const_cast实际上是删除了变量a地址的const属性,代表着我们可以利用地址p来改变变a.
2.理论上而言,a和p的值修改后打印出来是一样的.但是,由于编译器会将const修饰的变量a加载到寄存器中,打印的时候编译器便会直接从寄存器读.所以,读取a时便还是以前的值,而p中说是从内存中获取的,具有时效性,此时便为3了.
3.为了解决这一问题,我们可以使用在长变量前加上volatile关键字,表明让编译器不要从寄存器中取长变量,而应该从内存中获取.
4.static_cast类似于c语言的隐式类型转换,reinterpret_cast,const_cast类似于c语言的强制类型转换.
dynamic_cast
dynamic_cast用于将一个父类对象的指针/引用,转换为子类对象的指针或引用(动态转换).
-
向上转型: 子类对象指针/引用 -> 父类指针/引用.(不需要转换,赋值兼容).
-
向下转型: 父类对象指针/引用 -> 子类指针/引(相比于c语言,使用dynamic_cast更为安全).
为什么C++要支持向下转型?
因为在Func函数中,形参为父类指针接收,可是,我们并不知道实参传的是指向父类的指针,还是指向子类的指针.
- 如果传的是指向子类的指针,但是此时会发生向上转型,pa是父类指针,无法访问到子类成员,所以我们可以将pa强转为子类指针,这样子类指针pb既可以访问到父类成员又可以访问到子类成员了.
- 如果传的是指向父类的指针,那么那么此时强转为子类指针pb后,访问到子类成员时,就代表访问到不属于pb所管辖的内存打空间,进而发生越界访问.
class A
{
public:
virtual void f() {};
int _a = 1;
};
class B : public A
{
public:
int _b = 3;
};
void Func( A* pa )
{
B* pb = dynamic_cast <B*>(pa);
pa->_a++;
pb->_b++;
}
int main()
{
A aa;
B bb;
Func(&aa);
Func(&bb);
}
为了访问更加安全,C++便提出了向下转型:
- 如果pa指向子类,那么便可以强制转换,将父类指针转换为子类指针,并返回正确的地址.
- 如果pa指向父类,那么便不可以强制类型转换,转换表达式返回为nullptr.
所以,在C++中,面对上述问题,我们可以采用dynamic_cast来强制类型转换,再通过返回值来进行判断访问范围,防止发生越界访问.
- 如果pa指向子类,便可以通过pb访问子类所有成员.
- 如果pa指向父类,只能pb为空,无法强制转换,pa只能访问父类成员.
class A
{
public:
virtual void f() {};
int _a = 1;
};
class B : public A
{
public:
int _b = 3;
};
void Func( A* pa )
{
B* pb = dynamic_cast<B*>(pa);
if (pb) //pb指向子类.
{
cout << "转换成功" << endl;
pb->_b++;
pb->_a++;
cout << pb->_a << ":" << pb->_a << endl;
}
else //pb指向父类.
{
cout << "转换失败" << endl;
pa->_a++;
cout << pa->_a << endl;
}
}
int main()
{
A aa;
B bb;
Func(&aa);
Func(&bb);
}
延申问题
对于菱形继承来说,切片会产生偏移量,原本父类指针ptr1,ptr2应该都指子类bb,但是经过切片后,ptr1指向子类中的A1,ptr2指向子类中的A2.
无论采用c语言的强制转换,还是C++中的dynamic_cast强制转换.
- 如果将子类指针转换为父类指针,该指针指向子类.那么会发生指针偏移,ptr1指* 向基类A1,解引用访问基类A1,ptr2指向基类A2,解引用访问基类A2.
- 如果将父类指针转换为子类指针,该指针指向子类,ptr1和ptr2依旧指向子类.
class A1
{
public:
virtual void f() {};
int _a1 = 1;
};
class A2
{
public:
virtual void f() {};
int _a2= 1;
};
class B : public A1,public A2
{
public:
int _b = 3;
};
int main()
{
B bb;
A1* ptr1 = &bb;
A2* ptr2 = &bb;
cout << ptr1 << endl;
cout << ptr2 << endl;
B* pb1 = (B*)ptr1;
B* pb2 = (B*)ptr2;
cout << pb1 << endl;
cout << pb2 << endl;
cout << "-----------------------------" << endl;
B* pb3 = dynamic_cast<B*>(ptr1);
B* pb4 = dynamic_cast<B*>(ptr1);
cout << pb3 << endl;
cout << pb4 << endl;
return 0;
}