C++类型转换和IO流

1、C语言中的类型转换

在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不⼀致时等场景,就需要发生类型转化,C语言中总共有两种形式的类型转换:隐式类型转换和显式强制类型转换。
隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败。
显式强制类型转化:需要用户自己去显示在变量前用括号指定要转换的类型。
并不是任意类型之前都支持转换,两个类型支持转换需要有一定关联性,也就是说转换后要有一定的意义,两个毫无关联的类型是不支持转换的。

int i = 10;
double d = i;

C语言中,这是发生了隐式类型转换,i先拷贝给临时double对象,然后临时对象再拷贝给d。

另外,C语言中还存在强制类型转换,如下:

int* p = &i;
int address = (int)p;
cout << address << endl;

之前模拟实现STL的时候,有挪动数据的情况,如下:

void insert(size_t pos, const T& x)
{
	int end = _size - 1;
	while (end >= pos)
	{ 
		// ...
	}
}

这里如果插入位置pos = 0,那就会出问题,因为在while循环判断中end会隐式类型转换成size_t无符号整数,当end变成-1,由于类型转换成了无符号整数,就变成了一个很大的数,从而造成死循环。


下面有一个很坑的场景:

const int n = 10;
int* p = (int*)&n;
(*p)++;
cout << n << endl;
cout << *p << endl;

这里我们定义了一个const int变量n,初始化为10。然后将n的地址强转成int*赋值给p,对p进行解引用++,通过这种方式就可以改变它的值。我们看一下运行结果:

在这里插入图片描述
按理来说,p解引用++之后,输出n和*p的值应该都是11,为什么一个10一个11呢?
这是因为编译器会直接把n替换成常量,所以第一个输出就是10。另外也有其他情况,比如直接放到寄存器中,然后下次直接去寄存器中取,但是寄存器中的值并没有发生变化,而内存中的值变了。
所以需要在const前面加上volatile,这样每次都会去内存中取值。

在这里插入图片描述


2、C++中的类型转换

对于类,存在单参数构造函数的隐式类型转换,如下:

class A
{
public:
	A(int a = 0)
		:_a(a)
	{}
private:
	int _a;
};
A a = 1;

如果在构造函数前面加上explicit,就禁止了类型转换。

C++还支持自定义类型到自定义类型之间的转换,需要对应类型的构造函数支持即可,比如A类型对象想转成B类型,则支持一个形参为A类型的B构造函数即可支持:

class A
{
};

class B
{
public:
	B(const A& a)
	{}
};

A a;
B b = a;

C++还支持内置类型到自定义类型之间的转换,内置类型转成自定义类型需要构造函数的支持,自定义类型转成内置类型,需要⼀个operator 类型()的函数去支持。
我们有时候会看到下面这种写法:

string str;
while (cin >> str)
{
	cout << str << endl;
}

这种写法可以不断读入数据,直到流的标志被设置为错误或结束。
那么为什么可以这么写呢?
首先我们知道循环的判断条件是一个布尔值,而cin>>str这里本质上是因为string类重载了operator>>函数,所以支持这么写,因为string是一个类,而不是内置类型。

在这里插入图片描述
但是我们可以看到,string重载的operator>>函数返回值是istream&,返回的是一个对象。
这里其实是发生了隐式类型转换,因为istream类重载了下面这个函数:
在这里插入图片描述
重载了operator bool(),所以istream对象可以隐式类型转换成bool,因此上面的代码就可以实现一直读取数据了。
这里我们看到前面还加上了explicit,这里跟构造函数那里不太一样,这里是防止从bool进一步转换成其他类型。


下面我们实现一个自定义类型转换为内置类型:

class A
{
public:
	A(int a = 0)
		:_a(a)
	{}
private:
	int _a;
};
int main()
{
	A a = 1;
	return 0;
}

现在是支持内置类型转换成自定义类型的,这里是单参数构造函数的隐式类型转换。
我们实现operator int()和operator bool(),让它支持从A类型转换成整型和从A类型转换成布尔:

operator int()
{
	return _a;
}
operator bool()
{
	return _a;
}

在这里插入图片描述


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

标准C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符:static_cast、reinterpret_cast、const_cast、dynamic_cast

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

// 相关类型/相近类型
double d = 12.34;
int a = static_cast<int>(d);

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

// 不相关类型
int a = 10;
int* p1 = &a;
int address = reinterpret_cast<int>(p1);

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

volatile const int n = 10;
int* p = const_cast<int*>(&n);

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

class A
{
public:
	virtual void func()
	{}
	int _x = 0;
};

class B : public A
{
public:
	int _y = 0;
};

void f(A* pa)
{
	B* ptr = dynamic_cast<B*>(pa);
	if (ptr)
	{
		cout << "转换成功" << endl;
		cout << ptr->_y << endl;
	}
	else
	{
		cout << "转换失败" << endl;
	}
}
int main()
{
	A a;
	B b;
	f(&a);
	f(&b);
	return 0;
}

在这里插入图片描述
将基类中虚函数virtual去掉就无法转换了。
派生类的指针、引用、对象赋值给基类是可以的,这是向上转换:赋值兼容转换(切割/切片),天然支持的。
但是基类指针、引用赋值给派生类是不支持的,存在类型安全问题。所以我们要使用dynamic_cast,它是安全的,如果原来指针指向的是基类,就无法转换,返回nullptr,如果原来指针是指向派生类,可以转换成功。


RTTI:Run-time Type identification的简称,即:运行时类型识别。
C++通过以下方式来支持RTTI:
1. typeid运算符
2. dynamic_cast运算符
3. decltype


4、C++IO流

在这里插入图片描述
如图:
C语言中的sacnf、printf对应C++中的istream、ostream。
C语言中的fscanf、fprintf对应C++中的ifstream、ofstream。
C语言中sscanf、sprintf对应C++中的istringstream、ostringstream。

先来看看ifstream和ofstream的用法:
在这里插入图片描述
在这里插入图片描述

ofstream有两个构造函数,一个是默认构造,还有一个第一个参数是要打开的文件,第二个参数是打开的方式。ifstream是类似的。
我们再看看打开有哪些方式:

在这里插入图片描述
in:读。out:写。binary:采用二进制的方式。ate:相当于追加,配合out追加写。
默认给的缺省参数是out,需要注意的是它们是定义在ios_base这个命名空间中的,使用的时候要指名作用域。


使用二进制方式写要搭配write来使用:
在这里插入图片描述
第一个参数是要写入对象的地址,第二个是对象的大小。

使用二进制方式读取要搭配read来使用:
在这里插入图片描述

使用如下:给出Date类作为测试

class Date
{
	friend ostream& operator << (ostream& out, const Date& d);
	friend istream& operator >> (istream& in, Date& d);
public:
	Date(int year = 1, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{}

	/*operator string()
	{
		string str;
		str += to_string(_year);
		str += ' ';
		str += to_string(_month);
		str += ' ';
		str += to_string(_day);
		return str;
	}*/

	operator bool()
	{
		// 这里是随意写的,假设输入_year为0,则结束
		if (_year == 0)
			return false;
		else
			return true;
	}
private:
	int _year;
	int _month;
	int _day;
};

istream& operator >> (istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}

ostream& operator << (ostream& out, const Date& d)
{
	out << d._year << " " << d._month << " " << d._day;
	return out;
}
Date d(2025, 3, 3);
ofstream ofs("text.txt");
ofs.write((const char*)&d, sizeof(d));

在这里插入图片描述
上面演示的是写入,下面演示读取:

Date d;
ifstream ifs("text.txt");
ifs.read((char*)&d, sizeof(d));
cout << d << endl;

在这里插入图片描述


下面再演示文本的读写:

Date d(2025, 12, 12);
ofstream ofs("text.txt");
ofs << d;

在这里插入图片描述

Date d;
ifstream ifs("text.txt");
ifs >> d;
cout << d << endl;

在这里插入图片描述


下面介绍stringstream的使用,它是istringstream和ostringstream的结合。istringstream、ostringstream这里就不再介绍,可以自行了解查看文档。
这个系列的相当于是把数据写入到string类型中,然后我们可以通过它的一个接口获取:
在这里插入图片描述
通过str(),获取string类型。

struct ChatInfo
{
	string _name; // 名字
	int _id;      // id
	Date _date;   // 时间
	string _msg;  // 聊天信息
};
ChatInfo winfo = { "张三", 135246, { 2023, 10, 16 }, "晚上一起看电影吧" };
stringstream ss;
ss << winfo._name << " " << winfo._id << " " << winfo._date << " " << winfo._msg;

string str = ss.str();
cout << str << endl;

ChatInfo rInfo;
stringstream rss(str);
ss >> rInfo._name >> rInfo._id >> rInfo._date >> rInfo._msg;
cout << "-------------------------------------------------------" << endl;
cout << "姓名:" << rInfo._name << "(" << rInfo._id << ") ";
cout << rInfo._date << endl;
cout << rInfo._name << ":>" << rInfo._msg << endl;
cout << "-------------------------------------------------------" << endl;

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值