C++类型转换
静态转换(static_cast)
- 用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。
- 进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
- 进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。
- 用于基本数据类型之间的转换,如把int转换成char,把char转换成int。这种转换的安全性也要开发人员来保证。
class Animal{};
class Dog : public Animal{};
class Other{};
//基础数据类型转换
void test01(){
char a = 'a';
double b = static_cast<double>(a);
}
//继承关系指针互相转换
void test02(){
//继承关系指针转换
Animal* animal01 = NULL;
Dog* dog01 = NULL;
//子类指针转成父类指针,安全
Animal* animal02 = static_cast<Animal*>(dog01);
//父类指针转成子类指针,不安全
Dog* dog02 = static_cast<Dog*>(animal01);
}
//继承关系引用相互转换
void test03(){
Animal ani_ref;
Dog dog_ref;
//继承关系指针转换
Animal& animal01 = ani_ref;
Dog& dog01 = dog_ref;
//子类指针转成父类指针,安全
Animal& animal02 = static_cast<Animal&>(dog01);
//父类指针转成子类指针,不安全
Dog& dog02 = static_cast<Dog&>(animal01);
}
//无继承关系指针转换
void test04(){
Animal* animal01 = NULL;
Other* other01 = NULL;
//转换失败
//Animal* animal02 = static_cast<Animal*>(other01);
}
动态转换(dynamic_cast)
- ynamic_cast主要用于类层次间的上行转换和下行转换;
- 在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
- 在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全;
class Animal {
public:
virtual void ShowName() = 0;
};
class Dog : public Animal{
virtual void ShowName(){
cout << "I am a dog!" << endl;
}
};
class Other {
public:
void PrintSomething(){
cout << "我是其他类!" << endl;
}
};
//普通类型转换
void test01(){
//不支持基础数据类型
int a = 10;
//double a = dynamic_cast<double>(a);
}
//继承关系指针
void test02(){
Animal* animal01 = NULL;
Dog* dog01 = new Dog;
//子类指针转换成父类指针 可以
Animal* animal02 = dynamic_cast<Animal*>(dog01);
animal02->ShowName();
//父类指针转换成子类指针 不可以
//Dog* dog02 = dynamic_cast<Dog*>(animal01);
}
//继承关系引用
void test03(){
Dog dog_ref;
Dog& dog01 = dog_ref;
//子类引用转换成父类引用 可以
Animal& animal02 = dynamic_cast<Animal&>(dog01);
animal02.ShowName();
}
//无继承关系指针转换
void test04(){
Animal* animal01 = NULL;
Other* other = NULL;
//不可以
//Animal* animal02 = dynamic_cast<Animal*>(other);
}
常量转换(const_cast)
该运算符用来修改类型的const属性。。
- 常量指针被转化成非常量指针,并且仍然指向原来的对象;
- 常量引用被转换成非常量引用,并且仍然指向原来的对象;
注意:不能直接对非指针和非引用的变量使用const_cast操作符去直接移除它的const.
//常量指针转换成非常量指针
void test01(){
const int* p = NULL;
int* np = const_cast<int*>(p);
int* pp = NULL;
const int* npp = const_cast<const int*>(pp);
const int a = 10; //不能对非指针或非引用进行转换
//int b = const_cast<int>(a); }
//常量引用转换成非常量引用
void test02(){
int num = 10;
int & refNum = num;
const int& refNum2 = const_cast<const int&>(refNum);
}
重新解释转换(reinterpret_cast)
这是最不安全的一种转换机制,最有可能出问题。
主要用于将一种数据类型从一种类型转换为另一种类型。它可以将一个指针转换成一个整数,也可以将一个整数转换成一个指针.
C++异常
异常基本概念
异常处理就是处理程序中的错误。所谓错误是指在程序运行的过程中发生的一些异常事件(如:除0溢出,数组下标越界,所要读取的文件不存在,空指针,内存不足等等)。
异常基本语法
try{
}
catch (...){ ///不知道什么异常 ,其它异常用 ...
}
int A_MyDivide(int a, int b){
if (b == 0){
throw 0;
}
return a / b;
}
//B写的代码 B写代码比较粗心,忘记处理异常
int B_MyDivide(int a, int b){
int ba = a;
int bb = b;
int ret = A_MyDivide(ba, bb) + 100; //由于B没有处理异常,导致B结果运算错误
return ret;
}
//C写的代码
int C_MyDivide(){
int a = 10;
int b = 0;
int ret = 0;
//没有处理异常,程序直接中断执行
#if 1
ret = B_MyDivide(a, b);
//处理异常
#else
try{
ret = B_MyDivide(a, b); //更严重的是,由于B没有继续抛出异常,导致C的代码没有办法捕获异常
}
catch (int e){
cout << "C_MyDivide Call B_MyDivide 除数为:" << e << endl;
}
#endif
return ret;
}
int main(){
C_MyDivide();
system("pause");
return EXIT_SUCCESS;
}
总结:
- 若有异常则通过throw操作创建一个异常对象并抛出。
- 将可能抛出异常的程序段放到try块之中。
- 如果在try段执行期间没有引起异常,那么跟在try后面的catch字句就不会执行。
- catch子句会根据出现的先后顺序被检查,匹配的catch语句捕获并处理异常(或继续抛出异常)
- 如果匹配的处理未找到,则运行函数terminate将自动被调用,其缺省功能调用abort终止程序。
- 处理不了的异常,可以在catch的最后一个分支,使用throw,向上抛。
c++异常处理使得异常的引发和异常的处理不必在一个函数中,这样底层的函数可以着重解决具体问题,而不必过多的考虑异常的处理。上层调用者可以在适当的位置设计对不同类型异常的处理。
异常严格类型匹配
异常机制和函数机制互不干涉,但是捕捉方式是通过严格类型匹配
void TestFunction(){
cout << "开始抛出异常..." << endl;
//throw 10; //抛出int类型异常
//throw 'a'; //抛出char类型异常
//throw "abcd"; //抛出char*类型异常
string ex = "string exception!";
throw ex;
}
int main(){
try{
TestFunction();
}
catch (int){
cout << "抛出Int类型异常!" << endl;
}
catch (char){
cout << "抛出Char类型异常!" << endl;
}
catch (char*){
cout << "抛出Char*类型异常!" << endl;
}
catch (string){
cout << "抛出string类型异常!" << endl;
}
//捕获所有异常
catch (...){
cout << "抛出其他类型异常!" << endl;
}
system("pause");
return EXIT_SUCCESS;
}
栈解旋(unwinding)
异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上构造的所有对象,都会被自动析构。析构的顺序与构造的顺序相反,这一过程称为栈的解旋(unwinding).
class Person{
public:
Person(string name){
mName = name;
cout << mName << "对象被创建!" << endl;
}
~Person(){
cout << mName << "对象被析构!" << endl;
}
public:
string mName;
};
void TestFunction(){
Person p1("aaa");
Person p2("bbb");
Person p3("ccc");
//抛出异常
throw 10;
}
int main(){
try{
TestFunction();
}
catch (...){
cout << "异常被捕获!" << endl;
}
system("pause");
return EXIT_SUCCESS;
}
异常接口声明
- 为了加强程序的可读性,可以在函数声明中列出可能抛出异常的所有类型,例如:void func() throw(A,B,C);这个函数func能够且只能抛出类型A,B,C及其子类型的异常。
- 如果在函数声明中没有包含异常接口声明,则此函数可以抛任何类型的异常,例如:void func()
- 一个不抛任何类型异常的函数可声明为:void func() throw()
- 如果一个函数抛出了它的异常接口声明所不允许抛出的异常,unexcepted函数会被调用,该函数默认行为调用terminate函数中断程序。
//可抛出所有类型异常
void TestFunction01(){
throw 10;
}
//只能抛出int char char*类型异常
void TestFunction02() throw(int,char,char*){
string exception = "error!";
throw exception;
}
//不能抛出任何类型异常
void TestFunction03() throw(){
throw 10;
}
int main(){
try{
//TestFunction01();
//TestFunction02();
//TestFunction03();
}
catch (...){
cout << "捕获异常!" << endl;
}
system("pause");
return EXIT_SUCCESS;
}
异常变量生命周期
- throw的异常是有类型的,可以是数字、字符串、类对象。
- throw的异常是有类型的,catch需严格匹配异常类型
class MyException
{
public:
MyException(){
cout << "异常变量构造" << endl;
};
MyException(const MyException & e)
{
cout << "拷贝构造" << endl;
}
~MyException()
{
cout << "异常变量析构" << endl;
}
};
void DoWork()
{
throw new MyException(); //test1 2都用 throw MyExecption();
}
void test01()
{
try
{
DoWork();
}
catch (MyException e)
{
cout << "捕获 异常" << endl;
}
}
void test02()
{
try
{
DoWork();
}
catch (MyException &e)
{
cout << "捕获 异常" << endl;
}
}
void test03()
{
try
{
DoWork();
}
catch (MyException *e)
{
cout << "捕获 异常" << endl;
delete e;
}
}
异常的多态使用
//异常基类
class BaseException{
public:
virtual void printError(){};
};
//空指针异常
class NullPointerException : public BaseException{
public:
virtual void printError(){
cout << "空指针异常!" << endl;
}
};
//越界异常
class OutOfRangeException : public BaseException{
public:
virtual void printError(){
cout << "越界异常!" << endl;
}
};
void doWork(){
throw NullPointerException();
}
void test()
{
try{
doWork();
}
catch (BaseException& ex){
ex.printError();
}
}
C++标准异常库
标准异常类的成员:
① 在上述继承体系中,每个类都有提供了构造函数、复制构造函数、和赋值操作符重载。
② logic_error类及其子类、runtime_error类及其子类,它们的构造函数是接受一个string类型的形式参数,用于异常信息的描述
③ 所有的异常类都有一个what()方法,返回const char* 类型(C风格字符串)的值,描述异常信息。
标准异常类的具体描述:
异常名称 | 描述 |
---|---|
exception | 所有标准异常类的父类 |
bad_alloc | 当operator new and operator new[],请求分配内存失败时 |
bad_exception | 这是个特殊的异常,如果函数的异常抛出列表里声明了bad_exception异常,当函数内部抛出了异常抛出列表中没有的异常,这是调用的unexpected函数中若抛出异常,不论什么类型,都会被替换为bad_exception类型 |
bad_typeid | 使用typeid操作符,操作一个NULL指针,而该指针是带有虚函数的类,这时抛出bad_typeid异常 |
bad_cast | 使用dynamic_cast转换引用失败的时候 |
ios_base::failure | io操作过程出现错误 |
logic_error | 逻辑错误,可以在运行前检测的错误 |
runtime_error | 运行时错误,仅在运行时才可以检测的错误 |
- logic_error的子类:
异常名称 | 描述 |
---|---|
length_error | 试图生成一个超出该类型最大长度的对象时,例如vector的resize操作 |
domain_error | 参数的值域错误,主要用在数学函数中。例如使用一个负值调用只能操作非负数的函数 |
out_of_range | 超出有效范围 |
invalid_argument | 参数不合适。在标准库中,当利用string对象构造bitset时,而string中的字符不是’0’或’1’的时候,抛出该异常 |
- runtime_error的子类:
异常名称 | 描述 |
---|---|
range_error | 计算结果超出了有意义的值域范围 |
overflow_error | 算术计算上溢 |
underflow_error | 算术计算下溢 |
invalid_argument | 参数不合适。在标准库中,当利用string对象构造bitset时,而string中的字符不是’0’或’1’的时候,抛出该异常 |
c++输入和输出流
C++输入输出包含以下三个方面的内容:
- 对系统指定的标准设备的输入和输出。即从键盘输入数据,输出到显示器屏幕。这种输入输出称为标准的输入输出,简称标准I/O。
- 以外存磁盘文件为对象进行输入和输出,即从磁盘文件输入数据,数据输出到磁盘文件。以外存文件为对象的输入输出称为文件的输入输出,简称文件I/O。
- 对内存中指定的空间进行输入和输出。通常指定一个字符数组作为存储空间(实际上可以利用该空间存储任何信息)。这种输入和输出称为字符串输入输出,简称串I/O。
C++编译系统提供了用于输入输出的iostream类库
C++对文件的输入输出需要用ifstrcam和ofstream类
与iostream类库有关的头文件
- iostream 包含了对输入输出流进行操作所需的基本信息。
- fstream 用于用户管理的文件的I/O操作。
- strstream 用于字符串流I/O。
- stdiostream 用于混合使用C和C + +的I/O机制时,例如想将C程序转变为C++程序。
- iomanip 在使用格式化I/O时应包含此头文件。
在iostream头文件中重载运算符
“<<”和“>>”本来在C++中是被定义为左位移运算符和右位移运算符的,由于在iostream头文件中对它们进行了重载, 使它们能用作标准类型数据的输入和输出运算符。所以,在用它们的程序中必须用#include命令把iostream包含到程序中。
#include
1)>>a表示将数据放入a对象中。
2)<<a表示将a对象中存储的数据拿出。
标准I/O流
标准I/O对象:cin,cout,cerr,clog
标准输入流
标准输入流对象cin,重点掌握的函数
cin.get() //一次只能读取一个字符
cin.get(一个参数) //读一个字符
cin.get(两个参数) //可以读字符串
cin.getline()
cin.ignore()
cin.peek()
cin.putback()
标准输出流
字符输出
cout.flush() //刷新缓冲区 Linux下有效
cout.put() //向缓冲区写字符
cout.write() //从buffer中写num个字节到当前输出流中。
使用流对象的有关成员函数
通过调用流对象cout中用于控制输出格式的成员函数来控制输出格式。用于控制输出格式的常用的成员函数如下:
流成员函数setf和控制符setiosflags括号中的参数表示格式状态,它是通过格式标志来指定的。格式标志在类ios中被定义为枚举值。因此在引用这些格式标志时要在前面加上类名ios和域运算符“::”
控制符格式化输出
//通过流成员函数
void test01(){
int number = 99;
cout.width(20);
cout.fill('*');
cout.setf(ios::left);
cout.unsetf(ios::dec); //卸载十进制
cout.setf(ios::hex);
cout.setf(ios::showbase);
cout.unsetf(ios::hex);
cout.setf(ios::oct);
cout << number << endl;
}
//使用控制符
void test02(){
int number = 99;
cout << setw(20)
<< setfill('~')
<< setiosflags(ios::showbase)
<< setiosflags(ios::left)
<< hex
<< number
<< endl;
}
文件读写
文件流类和文件流对象
fstream类,它是从iostream类派生的,用来支持对磁盘文件的输入输出
C++打开文件
ofstream outfile; //定义ofstream类(输出文件流类)对象outfile
outfile.open(“f1.dat”,ios::out); //使文件流与f1.dat文件建立关联
C++关闭文件
outfile.close( ); //将输出文件流所关联的磁盘文件关闭