【C++ 系列笔记】05 C++ 类型转换、异常处理、I/O 流
Content
补充知识
C++ 类型转换
静态转换static_cast<>()
允许转换指针、引用及普通变量
-
基础类型
double b = static_cast<double>(a);
-
自定义类型
// 向下转换 不安全 Child* child = static_cast<Child*>(base); // 向上转换 安全 Base& base = static_cast<Base&>(child);
仅允许在父子类之间转换,转换成其他类型会报错
动态转换dynamic_cast<>()
仅允许转换指针和引用
-
基础类型
不允许基础类型的转换,如下方代码是错误的:
double b = dynamic_cast<double>(a);
-
自定义类型
仅允许父子间转换,
允许向上转换或发生了多态的转换。
-
向上转换:(一直允许)
Base& base = dynamic_cast<Base&>(child);
-
向下转换:(不允许)
-
发生多态:(允许)
class Base{ public: virtual void method(); }; class Child: public Base{ public: virtual void method(); };
// 多态 Base* base = new Child; Child* child = dynamic_cast<Child*>(base);
-
常量转换const_cast<>()
仅允许转换指针和引用
对基础类型和自定义类型的规则都相同。
-
去掉
const
修饰const int* p; int* p1 = const_cast<int*>(p);
-
添加
const
修饰int& ref; const int& ref1 = const_cast<const int&>(ref);
重新解释转换reinterpret_cast<>()
几乎不会用到。
语法相同,可以任意转换,没有安全性。
C++ 异常处理
基本语法
-
关键字:
try
catch
throw
-
抛出异常
void fun(){ // ... if (/* condition */){ throw error; } }
-
捕获异常
捕获某类型异常
try { // 调用 } catch (int error) { // 处理 } catch (char error){ // 处理 }
捕获各种异常
try { // 调用 } catch (...) { // 处理 }
异常必须被捕获,否则就会自动调用
terminate
中断程序。如果不想处理,可以捕获后继续抛出给上级调用者。
异常基本处理
-
栈解旋
即抛出异常之前,栈上的对象都会被释放掉。
-
异常的接口声明
以一种声明方式告知调用者,该接口会不会抛出异常,会抛出什么类型的异常。
-
下面的例子表示:该接口仅抛出
int
和double
类型的异常。int fun() throw(int, double){ // ... }
-
还可以声明该接口不会抛出任何异常:
int fun() throw(){ // ... }
-
-
异常变量的生命周期
抛出引用:从抛出异常到捕获异常及处理的整个过程。
抛出值:从抛出异常到捕获异常及处理的整个过程,不过增加了开销。
抛出指针:指向的数据在捕获到并处理之前就被释放掉了。此时可以在堆区开辟空间存放,但存在内存泄漏的危险。
一般都会抛出引用
-
异常类
可以定义一个异常类,用来进行异常的抛出、识别和处理。
class Exception{ // ... };
-
多态中的异常
class Exception{ public: virtual string getMessage() = 0; }; // 继承 class OutOfRangeException: public Exception{ public: virtual string getMessage(){ return string("越界异常"); } }; void fun(){ // ... if(/* condition */){ // 抛出异常 throw OutOfRangeException(); } } int main(){ try { fun(); } catch(Exception& error) { // 多态 cout << error.getMessage() << endl; } }
C++ 标准异常库
C++ 标准库提供了一个异常类exception
#include <stdexcept>
-
使用
捕获到的异常对象拥有一个
what
方法,返回抛出者传递的字符传信息。void fun() { throw std::out_of_range("nmsl, cnm."); } int main() { try { fun(); } catch (std::out_of_range& error) { cout << error.what() << endl; } }
也可以通过多态去捕获异常:
try { fun(); } catch (std::exception& error) { cout << error.what() << endl; }
-
继承
继承标准库提供的异常类
class Exception { private: string message; public: Exception(string message): message(message){} // 重写 what virtual const char* what() const{ return this->message.c_str; } }
C++ I/O 流
基本介绍
- C++ I/O 的继承关系
另外还有:字符串 I/O 流strstream
文件 I/O 流fstream
-
cout
、cerr
和clog
-
cout(console output)
cout 是标准输出流,除了向终端输出信息以外,还可以向磁盘文件中输出。
-
cerr(console error)
cerr 是标准错误流,不会经过缓冲区,也不允许向文件中输出。
-
clog(console log)
clog 也是标准错误流,不过它会经过缓冲区。
-
标准输入流
几个重要接口:
cin.get(); // 输入一个字符
cin.get(char* buffer, int length); // 输入一个字符串
cin.getline(char*); // 输入一个字符串
cin.ignore(char); // 忽略即将读入的字符
cin.peek(); // 查看/偷窥 即将读入的字符
cin.fail(); // 获取错误状态
cin.clear(); // 重置错误状态
cin.sync(); // 清空缓冲区(存在问题)
-
cin.get
int cin.get();
一次读一个字符,与
getchar()
没什么区别。istream& get(char* buffer, int length);
一次读一串字符,存入连续的内存中。不读末尾换行符
\n
,将其留在缓冲区,且遇到空格不会停止。 -
cin.ignore
istream& ignore(int count = 1);
丢弃/忽略马上读入的
count
个字符。istream& ignore(int count, char c);
丢弃字符,直到数量达到
count
个,或当期丢弃的字符与c
相同。 -
cin.getline
istream& getline(char* buffer, int length);
一次读一串字符,存入连续的内存中。读末尾换行符
\n
,只不过不会读入内存中,而是直接丢弃,且遇到空格不会停止。 -
cin.peek
int peek();
偷窥,即读入一个字符,然后放回去。
-
cin.putback
istream& putback(char c);
将 c 中存放的字符放到输入缓冲区头部,即读入的下一个字符处。
-
cin.fail
bool fail();
用于获取 cin 的错误状态。返回 0 代表没有发生错误,1 代表发生了错误。
例如用户输入一个 0 - 9 以外的字符,而我通过一个 int 来接收它,此时就会出错。
-
cin.clear
用于重置 cin 的错误状态。
-
cin.sync
用于清空缓冲区,不过在某些编译器上的实现并非清空缓冲区。
例如 vc++ 2019。
想要清空缓冲区可以这样做:
cin.clear(); cin.ignore(numeric_limits<streamsize>::max(), '\n');
标准输出流
几个接口:(了解即可)
cout.flush(); // 清空输出缓冲区
cout.put(char c); // 向缓冲区写一个字符
cout.write(char* buffer, int length); // 向缓冲区写一个字符串
-
格式化输出
cout.width(int width); // 输出长度 cout.fill(char c); // 多余宽度通过 c 填充 cout.setf(ios::_Fmtflags flags); // 设置格式 cout.unsetf(ios::_Fmtflags flags); // 写在格式 cout.precision(int n); // 设置实数精度为 n
关于
cout.setf
,其还可以通过控制符来格式化输出:包含一个头文件:
#include<iomanip>
cout << setw(int width) // 输出长度 << setfill(char c) // 多余宽度通过 c 填充 << setiosflags(ios::_Fmtflags flags) // 设置格式 << hex // 16 // 设置格式 << oct // 8 // 设置格式 << dec // 10 // 设置格式 << endl;
其较常用的参数如下:
标志 说明 ios::left 输出左对齐 ios::right 输出右对齐 ios::internal 符号位左对齐,数值右对齐,中间填充 ios::dec 十进制 ios::oct 八进制 ios::hex 十六进制 ios::showbase 输出前导符 ’0x‘ 或 ’0‘ ios::showpointer 强制输出浮点数的小数点和尾数 0 ios::showpos 对正数显示 + 号 ios::scientific 浮点数以科学计数法的格式输出 ios::fixed 浮点数以定点格式输出(小数形式) ios::unitbuf 每次输出之后刷新所有的流 ios::stdio 每次输出之后清除 stdout stderr
文件 I/O 流
头文件:
#include <fstream>
-
打开文件
实例化时:
ofstream ofs(const char* filename,ios::openmode _Mode );
实例化后:
ofstream ofs; ofs.open(const char* filename,ios::openmode _Mode );
检验:
if (!ofs.is_open()){ // ... }
关于 openmode:
方式 说明 ios::in 输入 ios::out 输出(默认方式),若文件已存在则清空内容 ios::app 输出,追加在末尾 ios::ate 打开已有文件,文件指针指向文件尾 ios::trunc 打开一个文件,文件存在则清空数据,文件不存在这建立新文件 ios::binary 以二进制方式打开文件 ios::nocreate 打开已有文件,若文件不存在则打开失败 ios::noreplace 建立一个文件,若文件已存在则打开失败 ios::in | ios::out 可读可写 ios::out | ios::binary 以二进制方式打开一个输出文件 ios::in | ios::binary 以二进制方式打开一个时输入文件 -
写文件
ofstream ofs("./write.txt", ios::out); ofs << "test" << endl; ofs.close();
-
读文件
ifstream ifs("./read.txt", ios::in); char buf[1024]; while (ifs >> buf) { cout << buf << endl; }
ifstream ifs("./read.txt", ios::in); char buf[1024]; while (!ifs.eof()) { ifs.getline(buf, sizeof(buf)); cout << buf << endl; }
ifstream ifs("./read.txt", ios::in); char c; while (!ifs.eof()) { c = ifs.get(); cout << c; } // 或者 while ( (c = ifs.get()) != EOF ) { cout << c; }
字符串 I/O 流
头文件:
#include <sstream>
可以向一个流中传递数据和拿取数据。
stringstream stream;
// 获取一个流
stream << "123";
// 向流中推送数据
stream >> var;
// 向流流中拿取数据
应用:将任意基础类型进行转换
template <class resultType, class paramType >
resultType transform(paramType input) {
stringstream stream;
resultType output;
stream << input;
stream >> output;
return output;
}
例如:
int output = transform<int>("123");
cout << output;
// > 123