本篇总结的是类型转换和IO流
在C语言中就有类型转换,对于相近的类型之间可以相互转换,大体分成两种类型转换
这样做是有问题的,转换形式都基本上一致,对于转换没有一个合适的规则,于是C++就有了一套完整的体系
C++中有四种强制类型转换的方式,四种方式都有其固定的运用场景
主要是用于静态转换,编译器的所有隐式类型转换都可以用,例如:
int main()
{
double d1 = 1.1;
int a1 = static_cast<int>(d1);
cout << a1 << endl;
return 0;
}
但是对于两个不相关的类型,是不可以进行转换的,例如从int*和int之间的转换就不可以用
主要是用于将一种类型转换成另一种完全不同的类型,例如上述的转换
int main()
{
double d1 = 1.1;
int a1 = static_cast<int>(d1);
cout << a1 << endl;
int a2 = 10;
int* p2 = &a2;
int add = reinterpret_cast<int>(p2);
cout << add << endl;
return 0;
}
主要是为了修改常变量,例如下面的场景
int main()
{
const int a = 1;
// a = 2; 会报错
int* p = const_cast<int*>(&a);
*p = 2;
cout << *p << endl;
}
这个转换主要是用于将一个父类对象的指针或引用转换成一个子类对象的指针或者是引用,这当中存在两种类型
dynamic_cast只能用来转换父类中含有虚函数的类
dynamic_cast转换失败,如果是引用会抛异常,如果是指针会返回空指针
强制类型转换关闭或挂起了正常的类型检查,每次使用强制类型转换前,应该仔细考虑是否还有其他不同的方法达到同一目的,如果非强制类型转换不可,则应限制强制转换值的作用域,以减少发生错误的机会
全称是 run time type identitifiction
翻译成中文叫做,运行时类型识别,简单来说就是在运行的过程中可以知道这个类型是什么
C++的类型识别有三种
下面用例子来说明typeid运算符和decltype
int main()
{
// typeid运算符
int a = 10;
cout << typeid(a).name() << endl;
// decltype
int* p = &a;
// 识别为int* pb = nullptr;
decltype(p) pb = nullptr;
return 0;
}
关于dynamic_cast:
这是最常用的RTTI组件,它不能回答“指针指向的是哪类对象”这样的问题,但能够回答“是否可以安全地将对象的地址赋给特定类型的指针”这样的问题。
说白了,就是看看这个对象指针能不能转换为目标指针。通常,如果指向的对象(*pt)的类型为Type或者是从Type直接或简介派生而来的类型
流的意思就是流动的意思,简单来说就是从某个地方流到另外一个地方,那么才C++中也是如此,如果信息是从计算机的外部设备流入到显示器的过程,那么这个过程就是叫做流
有序连续,具有方向性
为了实现这种流动,在C++的库中定义了I/O标准类库,这些每个类都能叫做流,用来完成一些功能
比如在进行文件读写的时候,数据所在的位置位于内存中,而文件所在的位置在硬盘上,那么现在要做的就是把数据从内存传递到文件中,那么是如何传递的?借助的是上层的应用程序,那么在上层写好的应用程序就可以借助各种方式读取内存,再用文件系统的接口把数据写到文件中
而在文件系统的底层,会调度硬件相关的内容,将软件和硬件联系在一起的内容就叫做驱动,驱动的本质就是软硬相结合,因此驱动就会通过软件层面调度一些内容,来控制硬件
上图展示的是一个完整的C++输入输出流库,其中iOS是基类,以它为起点派生出了庞大的类库
C++标准库提供了四个全局对象,cin,cout,cerr,clog,这四个对象用来进行的是标准输入输出错误日志,而所谓的输出流,简单来说就是把数据从内存流向显示器,这样的过程就叫做是输出流,后面的也基本相同
值得注意的是:
下面展示的是对于多组数据的输入和输出问题:
int main()
{
int i = 0;
while (cin >> i)
{
// ...
}
while (scanf("%d", &i) != EOF)
{
// ...
}
return 0;
}
上面的代码为什么可以做到对于多组的输入可以有效果呢?原因就在于在istream中存在一个逻辑条件判断值
istream& operator>> (int& val);
explicit operator bool() const;
表面上看,这里是cin >> i,而在底层中调用的是operator >>,返回值是istream对象,因此这里就可以看做是一个逻辑条件值,因为这个对象又会调用operator bool,如果调用的时候返回的是失败,那么就会返回false,导致最后就停止数据的多组输入了
C++根据文件的内容分成了二进制文件和文本文件,对于文件流操作的对象,大致有下面的步骤
下面来举个例子,具体来看上述过程
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)
{}
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;
}
struct ServerInfo
{
char _address[32];
int _port;
Date _date;
};
// 用文件的IO流对指定配置文件进行读写
class BinIO
{
public:
BinIO(const char* filename)
:_filename(filename)
{}
void Read(ServerInfo& info)
{
// 输入流与文件建立联系
ifstream in(_filename, ios_base::in | ios_base::binary);
// 从文件中读取信息到下面的变量中
in.read((char*)&info, sizeof(info));
}
void Write(const ServerInfo& info)
{
// 输出流与文件建立联系
ofstream out(_filename, ios_base::out | ios_base::binary);
// 从变量中读取信息到文件中
out.write((char*)&info, sizeof(info));
}
private:
string _filename;
};
// 用文件的IO流对指定配置文件进行读写
class TextIO
{
public:
TextIO(const char* filename)
:_filename(filename)
{}
void Read(ServerInfo& info)
{
// 输入流与文件建立联系
ifstream in(_filename);
// 从文件中读取信息到下面的变量中
in >> info._address >> info._port >> info._date;
}
void Write(const ServerInfo& info)
{
// 输出流与文件建立联系
ofstream out(_filename);
// 从变量中读取信息到文件中
out << info._address << " " << info._port << " " << info._date;
}
private:
string _filename;
};
int main()
{
ServerInfo info = { "192.0.0.1", 80, { 2022, 4, 10 } };
BinIO bio("test.bin");
bio.Write(info);
ServerInfo res;
bio.Read(res);
cout << res._address << endl;
cout << res._port << endl;
cout << res._date << endl;
return 0;
}
给定这样的场景,如果想要把一个整型变量的数据转换成字符串格式,该如何去做?
C语言中有sprintf函数,在C++中提供了一种新的方式,具体样例如下:
但是这样的写法需要提前设定数组的大小,在面临一些其他情况的时候可能会导致异常出现,因此有下面的写法可以避开这样的问题:
可以将上述的过程理解为,把n这个变量值放到字符串流当中,再输入给s
int main()
{
stringstream s;
s << "这是第一串内容" << " ";
s << "这是第二串内容" << " ";
cout << s.str() << endl;
string str;
// 下面的过程有点像从缓冲区中拿内容
s >> str;
cout << str << endl;
s >> str;
cout << str << endl;
return 0;
}
用下面一个例子来简单说明这个场景
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)
{}
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;
}
struct ChatInfo
{
string _name;
int _id;
Date _date;
string _msg;
};
int main()
{
// 结构信息序列化为字符串
ChatInfo winfo = { "张三", 135246, { 2022, 4, 10 }, "晚上一起看电影吧" };
ostringstream oss;
oss << winfo._name << " " << winfo._id << " " << winfo._date << " " << winfo._msg;
string str = oss.str();
cout << str << endl << endl;
// 经过一些传递,传递到了当前位置,现在要对这个字符串流进行解析
ChatInfo rInfo;
istringstream iss(str);
// 直接从这个字符串流中进行信息的读取和解析
iss >> rInfo._name >> rInfo._id >> rInfo._date >> rInfo._msg;
cout << "姓名:" << rInfo._name << "(" << rInfo._id << ") ";
cout << rInfo._date << endl;
cout << rInfo._name << ":>" << rInfo._msg << endl;
return 0;
}