C++ 类型转换

文章目录

  • c语言中的类型转换
  • 为什么C++需要四种类型转换.
  • C++强制类型转换
    • static_cast
    • reinterpret_cast
    • const_cast
    • dynamic_cast

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++ 类型转换_第1张图片
无论采用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;
}

C++ 类型转换_第2张图片

你可能感兴趣的:(C++,c++)