C++完结
文章目录
- 前言
- 一、C++的四种类型转换
- 二、IO流
- 总结
前言
首先我们看看C语言中的类型转换:
int main()
{
int i = 1;
// 隐式类型转换
double d = i;
printf("%d, %.2f\n", i, d);
int* p = &i;
// 显示的强制类型转换
int address = (int)p;
printf("%x, %d\n", p, address);
}
对于上面这种C语言的类型转换其实是有一些缺陷的,转换的可视性比较差,所有的转换形式都是以一种相同形式书写,难以跟踪错误的转换。
下面我们看看C++中对于类型转换的修改
一、C++的四种类型转换
为什么C++需要重新改进类型转换呢?
第一种:static_cast
下面我们用代码演示一下:
int main()
{
int i = 1;
double d = static_cast<double>(i);
float c = static_cast<float>(d);
return 0;
}
我们只需要记住:static_cast适用于可以隐式转换的类型。
第二种:reinterpret_cast
int main()
{
int i = 1;
int* p = &i;
int t = reinterpret_cast<int>(p);
cout << t << endl;
int c = 10;
int* d = reinterpret_cast<int*>(c);
cout << d << endl;
return 0;
}
对于reinterpret_cast的使用我们只需要记住适用于不同类型之间的转换即可。
第三种:const_cast
int main()
{
const int a = 2;
int* p = const_cast<int*>(&a);
cout << *p << endl;
*p = 10;
cout << *p << endl;
return 0;
}
对于const类型的常变量一般是不能直接修改的,但是可以像我们上面那样间接的修改,主要还是因为常变量是放在栈中的不是放在常量区的,注意:常量区是一定不可以修改的。
下面我们看一道常考的题:
int main()
{
const int a = 2;
int* p = const_cast<int*>(&a);
*p = 3;
cout << a << endl;
cout << *p << endl;
return 0;
}
上面这段代码的运行结果是什么?很多人都会以为是3和3,因为p指向a的空间,修改*p那么a中的内容也会被修改,思路没错,但是没有考虑到编译器的优化,这道题的正确答案是2 3。
由于const变量在编译器看来是不会被修改的,所以本来应该先从内存读数据到寄存器结果被编译器优化为直接从寄存器拿数据。对于这种情况我们只需要让编译器不优化,这样的话编译器就会从内存中读取a的内容打印了:
可以看到这次的结果就正确了。所以对于const_cast转化是将原本const属性去掉单独分出来,这个时候我们就应该小心了,const变量尽量不要去改变。
第四种:dynamic_cast
我们可以看到父类指针是天然可以接收子类的指针或者引用的,那么如果是将父类给子类呢?
可以看到如果我们不加类型转化的话连编译都过不了,那么我们用类型转换再试试:
可以看到经过类型转换后是没问题的,并且dynamic_cast在转换中会保证安全性。下面我们看看如果不用dynamic_cast转化会出现什么情况:
class A
{
public:
virtual void f() {}
int _a = 0;
};
class B : public A
{
public:
int _b = 0;
};
void Func(A* ptr)
{
B* btr = (B*)ptr;
cout << btr << endl;
btr->_a++;
btr->_b++;
cout << btr->_a << endl;
cout << btr->_b << endl;
}
int main()
{
A aa;
B bb;
Func(&aa);
Func(&bb);
return 0;
}
我们可以看到直接出错了,下面我们用安全转换试一下:
我们可以看到当子类接收父类指针造成越界的时候安全转化会检查能否成功转化,对于不能成功转化的直接返回0就像上图一样。
总结:
Func中的ptr如果是指向子类对象,那么转回子类类型是没问题的。
ptr如果是指向父类对象,那么转回子类类型是存在越界风险的。
以上就是C++类型转换的全部内容了,下面我们进入IO流的学习。
二、C++IO流
C++标准IO流 :
下面我们看看OJ中的循环输入:
int main()
{
string str;
while (cin >> str)
{
cout << str << endl;
}
return 0;
}
不知道我们是否会有疑问>>符号是如何像逻辑判断操作符那样在循环体中进行判断的呢?
我们可以看到>>符号的返回值是istream&,也不是bool类型,下面我们看文档:
其实在文档中我们发现不管是C++11还是C++98都重载了operator bool,重载的目的就是支持自定义类型隐式转换为自定义类型,也就是说支持将istream&转化为bool类型。当然其实日常使用中我们看到最多的是内置类型隐式转换成自定义类型,比如下面这样:
class A
{
public:
A(int a)
:_a1(1)
, _a2(2)
{}
private:
int _a1;
int _a2;
};
int main()
{
A aa = 1;
return 0;
}
上图中我们的aa就是自定义类型,1是内置类型,将1给aa的过程中发生了隐式类型转化,由内置类型转化为自定义类型。
上图中我们可以看到,正常情况下我们是不能将自定义类型转化为内置类型的,但是当我们重载了int的转换后就可以了:
operator int()
{
return _a1 + _a2;
}
下面我们将日期类实现为像>>一样可以判断的:
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 bool()
{
if (_year > 0)
{
return true;
}
else
{
return false;
}
}
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;
}
int main()
{
Date d1(0, 5, 30);
while (d1)
{
cout << d1 << endl;
}
return 0;
}
我们重载bool类型的时候直接用year做判断了这里只是为了演示,对于年份为0的日期进入while循环后会直接退出,如果是年份非0的日期则会死循环的打印。
C++文件IO流:
struct ServerInfo
{
char _address[32];
int _port;
Date _date;
};
struct ConfigManager
{
public:
ConfigManager(const char* filename)
:_filename(filename)
{}
void WriteBin(const ServerInfo& info)
{
ofstream ofs(_filename, ios_base::out | ios_base::binary);
ofs.write((const char*)&info, sizeof(info));
}
void ReadBin(ServerInfo& info)
{
ifstream ifs(_filename, ios_base::in | ios_base::binary);
ifs.read((char*)&info, sizeof(info));
}
void WriteText(const ServerInfo& info)
{
ofstream ofs(_filename);
ofs << info._address << " " << info._port<< " "<<info._date;
}
void ReadText(ServerInfo& info)
{
ifstream ifs(_filename);
ifs >> info._address >> info._port>>info._date;
}
private:
string _filename; // 配置文件
};
上面是一个文件管理的类,里面封装了二进制读写和文本读写的接口,由于C++IO流属于了解性的内容所以我们就不再详细的讲解每个函数,有不会的接口大家自行查文档即可。下面我们用一个实例使用一下以上的接口:
int main()
{
ServerInfo winfo = { "192.0.0.1", 80, { 2022, 4, 10 } };
// 二进制读写
ConfigManager cf_bin("test.bin");
cf_bin.WriteBin(winfo);
ServerInfo rbinfo;
cf_bin.ReadBin(rbinfo);
cout << rbinfo._address << " " << rbinfo._port <<" "
<<rbinfo._date << endl;
// 文本读写
ConfigManager cf_text("test.text");
cf_text.WriteText(winfo);
ServerInfo rtinfo;
cf_text.ReadText(rtinfo);
cout << rtinfo._address << " " << rtinfo._port << " " <<
rtinfo._date << endl;
return 0;
}
1. 将数值类型数据格式化为字符串
#include<sstream>
int main()
{
int a = 12345678;
string sa;
// 将一个整形变量转化为字符串,存储到string类对象中
stringstream s;
s << a;
s >> sa;
// clear()
// 注意多次转换时,必须使用clear将上次转换状态清空掉
// stringstreams在转换结尾时(即最后一个转换后),会将其内部状态设置为badbit
// 因此下一次转换是必须调用clear()将状态重置为goodbit才可以转换
// 但是clear()不会将stringstreams底层字符串清空掉
// s.str("");
// 将stringstream底层管理string对象设置成"",
// 否则多次转换时,会将结果全部累积在底层string对象中
s.str("");
s.clear(); // 清空s, 不清空会转化失败
double d = 12.34;
s << d;
s >> sa;
string sValue;
sValue = s.str(); // str()方法:返回stringsteam中管理的string类型
cout << sValue << endl;
return 0;
}
2. 字符串拼接
int main()
{
stringstream sstream;
// 将多个字符串放入 sstream 中
sstream << "first" << " " << "string,";
sstream << " second string";
cout << "strResult is: " << sstream.str() << endl;
// 清空 sstream
sstream.str("");
sstream << "third string";
cout << "After clear, strResult is: " << sstream.str() << endl;
return 0;
}
3. 序列化和反序列化结构数据
struct ChatInfo
{
string _name; // 名字
int _id; // 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;
// 我们通过网络这个字符串发送给对象,实际开发中,信息相对更复杂,
// 一般会选用Json、xml等方式进行更好的支持
// 字符串解析成结构信息
ChatInfo rInfo;
istringstream iss(str);
iss >> 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;
return 0;
}
总结
C++IO流这部分知识大多都是偏了解性的知识,所以我们没有在细细的讲解,实际上学到这一部分很多人对C++都基本入门了,这个时候是完全有能力通过官方文档来自己运用这部分内容。