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属性//所以要注意加volatilevolatile 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;
}