C++篇(18)类型转换与IO库

一、C++显示强制类型转换

1.1 类型安全

类型安全是指编程语言在编译和运行时提供保护机制,避免非法的类型转换和操作,导致出现一个内存访问错误等,从而减少程序运行时的错误。

C语言不是类型安全的语言,因为C语言允许隐式类型转换,在一些特殊情况下就会导致越界访问的内存错误,其次不合理的使用强制类型转换也会导致问题,比如一个int*的指针强制转换成double*访问就会出现越界。

C++兼容C语言,支持隐式类型转换和强制类型转换,所以C++也不是类型安全的语言。C++提出4个显示的命名强制类型转换static_cast / reinterpret_cast / const_cast / dynamic_cast 就是为了让类型转换相对而言更安全。

#include <iostream>
using namespace std;

int main()
{
	const int y = 0;
	int* p2 = (int*)&y;
	(*p2) = 1;

	cout << (*p2) << endl;
	cout << y << endl;

	return 0;
}

上面这段代码,按照之前所学的知识,*p2和y的值应该都是1才对,但为什么这里编译运行之后*p2的值为1,而y的值还是0呢?因为我们类型转换去掉了const属性,但是编译器认为y是const的,不会被改变,所以会优化编译,把y放到寄存器或者直接把y替换成0。想要解决这一问题,需要用到volatile关键字。

#include <iostream>
using namespace std;

int main()
{
	volatile const int y = 0;
	int* p2 = (int*)&y;
	(*p2) = 1;

	cout << (*p2) << endl;
	cout << y << endl;

	return 0;
}

1.2 C++中的四个显示强制类型转换运算符

·static_cast 用于两个类型意义相近的转换,这个转换是具有明确定义的,只要底层不包含const,都可以使用static_cast。

·reinterpret_cast 用于两个类型意义不相近的转换,reinterpret是重新解释的意思,通常为运算对象的位模式提供较低层次上的重新解释,也就是说转换后对原有内存的访问解释已经完全改变了,非常的大胆。所以我们要谨慎使用,清楚知道这样转换是没有内存访问安全问题的。

·const_cast 用于const类型到非const类型的转换,去掉了const属性,也是一样的,我们需要谨慎使用,否则可能会出现意想不到的结果。

·dynamic_cast 用于将基类的指针或者引用安全地转换成派生类的指针或者引用。如果基类的指针或者引用指向派生类对象,则转换回派生类指针或者引用时可以成功;如果基类的指针指向基类对象,则转换失败,返回nullptr;如果基类引用指向基类对象,则转换失败,抛出bad_cast的异常。其次,dynamic_cast要求基类必须是多态类型,也就是说基类中必须要有虚函数。因为dynamic_cast是运行时通过虚表中存储的type_info判断基类指针指向的是基类对象还是派生类对象。

#include <iostream>
using namespace std;

int main()
{
	//对应隐式类型转换——数据的解释意义没有改变
	double d = 3.14;
	int a = static_cast<int>(d);
	cout << a << endl;
	int&& ref = static_cast<int&&>(a);


	//对应强制类型转换——数据的解释意义已经发生改变
	int* p1 = reinterpret_cast<int*>(a);


	//对应强制类型转换中有风险地去掉const属性
	//所以要注意加volatile
	volatile const int b = 0;
	int* p2 = const_cast<int*>(&b);
	*p2 = 1;
	cout << b << endl;
	cout << *p2 << endl;

	return 0;
}

1.3 RTTI

RTTI英文全称"Runtime Type Identification",中文称为“运行时类型识别”,它是指程序在运行时才确定需要用到的对象是什么类型的,用于在运行时(而不是编译时)获取有关对象的信息。

RTTI主要由两个运算符实现,typeid和dynamic_cast。typeid主要用于返回表达式的类型;dynamic_cast前面已经讲过了,主要用于将基类的指针或引用安全地转换成派生类的指针或者引用。

typeid(e)中的e可以是任意表达式或类型的名字,typeid(e)的返回值是typeinfo或typeinfo派生类对象的引用,typeinfo可以只支持比较等于和不等于,name成员函数可以返回C风格字符串表示对象类型名字,typeinfo的精确定义随着编译器的不同而略有差异,也就意味着同一个e表达式,不同编译器下,typeid(e).name()返回的名字可能是不一样的。

二、文件IO流

ofstream是输出文件流,也就是写文件的流。ofstream是ostream的派生类;ifstream是输入文件流,也就是读文件的流,ifstream是istream的派生类;fstream是ifstream和ofstream的派生类,既可以读也可以写。

文件流对象可以在构造时打开文件,也可以调用open函数打开文件。in为读打开out为写打开binary以二进制模式打开ate打开后立即寻位到流结尾app每次写入前寻位到流结尾trunc在打开时舍弃流的内容。这些值是ios_base中定义的成员变量继承下来的,并且他们也是组合的独立二进制位的值,需要组合时可以或到一起。(out和 out | trunc都会先把数据清掉,再写数据

文件流打开后如果需要可以主动调用close函数关闭,也可以不关闭,因为流对象析构函数中会关闭。文件流打开文件失败或者读写失败,也会使用IO流状态标记,我们调用operator bool或operator!判断即可。

#define  _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main()
{
	//默认
	ofstream ofs("text.txt");
	//字符和字符串的写
	ofs.put('x');
	ofs.write("hello\nworld", 11);

	//使用<<进行写
	ofs << "222222" << endl;
	int x = 111;
	double y = 1.11;
	ofs << x << endl;
	ofs << y << endl;
	ofs.close();

	//app和ate都是尾部追加,不同的是app不能移动文件指针,永远是在文件尾写
	//ate可以移动文件指针,写到其他位置
	ofs.open("text.txt", ios_base::out | ios_base::app);
	ofs << "111111" << endl;
	ofs.seekp(0, ios_base::beg);
	ofs << x << " " << y << endl;
	ofs.close();

	ofs.open("text.txt", ios_base::out | ios_base::in | ios_base::ate);
	ofs << "111111" << endl;
	ofs.seekp(0, ios_base::beg);
	ofs << x << " " << y << endl;
	ofs.close();

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值