linux C++ day12

目录:
动态类型转换
类型信息
返回错误
远程跳转
抛出异常
抛出基本类型异常
抛出类类型异常
借助异常携带更多诊断信息
忽略异常
继续抛出异常
异常终结者
异常说明
构造过程中的异常
不完整对象的回滚
析构过程中的异常
继承标准异常
打开关闭I/O流
判断I/O流的状态
通过流函数格式化I/O流
通过流控制符格式化I/O流
设置字符颜色的流控制符
非格式化I/O
文件复制
随机读写
字符串流
1 动态类型转换
1.1 问题
动态类型转换(dynamic_cast)用于将基类类型的指针或引用转换为其子类类型的指针或引用,前提是子类必须从基类多态继承,即基类包含至少一个虚函数

1.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:动态类型转换

代码如下:

#include
class Base
{
public:
virtual int f1 (void)
{
std::cout << “Base f1” << std::endl;
return 0;
}
};
class Derived : public Base
{
public:
int f1 (void)
{
std::cout << “Derived f1” << std::endl;
return 0;
}
};
int main()
{
Derived d;
Base* pa = &d;
Derived* pb = dynamic_cast<Derived*> (pa);
if (pb == NULL)
std::cout << “转换失败!” << std::endl;
Base& ra = d;
try {
Derived& rb = dynamic_cast<Derived&> (ra);
}
catch (std::bad_cast& ex)
{
std::cout << “转换失败!” << std::endl;
}
Derived b = dynamic_cast (*pa);

return 0;

}
上述代码中,以下代码:

Derived d;
Base* pa = &d;
Derived* pb = dynamic_cast<Derived*> (pa);

应用动态类型转换,将基类Base类型的指针转换为其子类Derived类型的指针。

上述代码中,以下代码:

if (pb == NULL)
    std::cout << "转换失败!" << std::endl;

针对指针的动态类型转换,以返回空指针(NULL)表示失败。

上述代码中,以下代码:

Base& ra = d;
try {
    Derived& rb = dynamic_cast<Derived&> (ra);
}

应用动态类型转换,将基类Base类型的引用转换为其子类Derived类型的引用。

上述代码中,以下代码:

Base& ra = d;
try {
    Derived& rb = dynamic_cast<Derived&> (ra);
}
catch (std::bad_cast& ex)
{
    std::cout << "转换失败!" << std::endl;
}

针对引用的动态类型转换,以抛出bad_cast异常表示失败。

上述代码中,以下代码:

Derived b = dynamic_cast<Derived> (*pa);

不是针对指针或引用做的动态类型转换,编译错误。另外,要注意,转换目标类型和源类型之间不具有多态继承性也会发生编译错误。转换源类型的目标对象非目标类型会发生运行错误。

1.3 完整代码
本案例的完整代码如下所示:

#include
class Base
{
public:
virtual int f1 (void)
{
std::cout << “Base f1” << std::endl;
return 0;
}
};
class Derived : public Base
{
public:
int f1 (void)
{
std::cout << “Derived f1” << std::endl;
return 0;
}
};
int main()
{
Derived d;
Base* pa = &d;
Derived* pb = dynamic_cast<Derived*> (pa);
if (pb == NULL)
std::cout << “转换失败!” << std::endl;
Base& ra = d;
try {
Derived& rb = dynamic_cast<Derived&> (ra);
}
catch (std::bad_cast& ex)
{
std::cout << “转换失败!” << std::endl;
}
Derived b = dynamic_cast (*pa);

return 0;

}
2 类型信息
2.1 问题
typeid操作符既可用于类型也可用于对象,用于返回类型信息,即类typeinfo对象的常引用。类typeinfo中含所有一个成员函数name,通过它可以得到类型名。

2.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:类型信息

代码如下:

#include
class A
{
};
class B : public A
{
public:
virtual void foo (void)
{
}
};
class C : public B
{
};
int main()
{
int x;
int * px;
std::cout << "int is: " << typeid(int).name() << std::endl;
std::cout << " x is: " << typeid(x).name() << std::endl;
std::cout << " px is: " << typeid(px).name() << std::endl;
std::cout << "*px is: " << typeid(*px).name() << std::endl;

C c;
A& a = c;
std::cout << typeid (a).name () << std::endl;
B& b = c;
std::cout << typeid (b).name () << std::endl;

return 0;

}
上述代码中,以下代码:

int x;
int * px;
std::cout << "int is: " << typeid(int).name() << std::endl;
std::cout << "  x is: " << typeid(x).name() << std::endl;
std::cout << " px is: " << typeid(px).name() << std::endl;
std::cout << "*px is: " << typeid(*px).name() << std::endl;

使用typeid获取指定目标的类型信息。如:

std::cout << "int is: " << typeid(int).name() << std::endl;

的输出结果为:int is: i。

上述代码中,以下代码:

C c;
A& a = c;
std::cout << typeid (a).name () << std::endl;

当typeid作用于基类A类型的引用的目标c时,若基类A中不包含任何虚函数,则该操作符所返回的类型信息由引用A本身的类型决定,即返回的是类A的类名A。

上述代码中,以下代码:

B& b = c;
std::cout << typeid (b).name () << std::endl;

当typeid作用于基类B类型的引用的目标c时,若基类包含至少一个虚函数,即存在多态继承,该操作符所返回的类型信息由该引用的实际目标对象的类型决定,即返回的是类C的类名C。

2.3 完整代码
本案例的完整代码如下所示:

#include
class A
{
};
class B : public A
{
public:
virtual void foo (void)
{
}
};
class C : public B
{
};
int main()
{
int x;
int * px;
std::cout << "int is: " << typeid(int).name() << std::endl;
std::cout << " x is: " << typeid(x).name() << std::endl;
std::cout << " px is: " << typeid(px).name() << std::endl;
std::cout << "*px is: " << typeid(*px).name() << std::endl;

C c;
A& a = c;
std::cout << typeid (a).name () << std::endl;
B& b = c;
std::cout << typeid (b).name () << std::endl;

return 0;

}
3 返回错误
3.1 问题
在项目的整个生命周期内,错误随时随地都有可能发生,虽然那些错误都在实际运行过程中发生,在设计、编码和测试阶段无法规避但却是可以预见的,如通过返回值返回错误信息。

3.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:返回错误

代码如下所示:

#include
int connectServer (char const* config)
{
FILE* fp = fopen (config, “r”);
if (fp == NULL)
return -1;

//其他操作

return 0;

}
int main(int argc, const char * argv[])
{

if (connectServer ("/etc/server.cfg") == -1)
{
    std::cerr << "打开文件失败!" << std::endl;
    exit (-1);
}

return 0;

}
上述代码中,以下代码:

FILE* fp = fopen (config, "r");
if (fp == NULL)
    return -1;

是一种常见的可预见性错误,当打开一个文件时,如果打开失败,则后续的操作将无法执行,并会导致程序崩溃。此时可以通过返回错误码的形式通知主调函数发生了错误。如以下主程序中的代码:

if (connectServer ("/etc/server.cfg") == -1)
{
    std::cerr << "打开文件失败!" << std::endl;
    exit (-1);
}

通过判断返回值,就可以知道所需的操作是否正确完成。

3.3 完整代码
本案例的完整代码如下所示:

#include
int connectServer (char const* config)
{
FILE* fp = fopen (config, “r”);
if (fp == NULL)
return -1;

//其他操作

return 0;

}
int main(int argc, const char * argv[])
{

if (connectServer ("/etc/server.cfg") == -1)
{
    std::cerr << "打开文件失败!" << std::endl;
    exit (-1);
}

return 0;

}
4 远程跳转
4.1 问题
在项目的整个生命周期内,错误随时随地都有可能发生,虽然那些错误都在实际运行过程中发生,在设计、编码和测试阶段无法规避但却是可以预见的,如通过远程跳转返回错误信息。

4.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:远程跳转

代码如下:

#include
#include <setjmp.h>
jmp_buf g_env;
int connectServer (char const* config)
{
FILE* fp = fopen (config, “r”);
if (fp == NULL)
longjmp (g_env, -1);
//其他操作

return 0;

}
int main(int argc, const char * argv[])
{

if (setjmp (g_env) == -1)
{
    std::cerr << "打开文件失败!" << std::endl;
    exit (-1);
}
connectServer ("/etc/server.cfg");

return 0;

}
上述代码中,以下代码:

FILE* fp = fopen (config, "r");
if (fp == NULL)
    longjmp (g_env, -1);

是一种常见的可预见性错误,当打开一个文件时,如果打开失败,则后续的操作将无法执行,并会导致程序崩溃。此时可以通过远程跳转的形式通知主调函数发生了错误。如以下主程序中的代码:

if (setjmp (g_env) == -1)
{
    std::cerr << "打开文件失败!" << std::endl;
    exit (-1);
}
connectServer ("/etc/server.cfg");

先调用setjmp,用变量g_env记录当前主函数被执行到的位置,然后在connectServer函数中如果调用了longjmp,程序的执行流程将返回到g_env所记录的位置,并使setjmp的返回值为设定的-1。通过判断返回值,就可以知道所需的操作是否正确完成。

4.3 完整代码
本案例的完整代码如下所示:

#include
#include <setjmp.h>
jmp_buf g_env;
int connectServer (char const* config)
{
FILE* fp = fopen (config, “r”);
if (fp == NULL)
longjmp (g_env, -1);
//其他操作

return 0;

}
int main(int argc, const char * argv[])
{
if (setjmp (g_env) == -1)
{
std::cerr << “打开文件失败!” << std::endl;
exit (-1);
}
connectServer ("/etc/server.cfg");

return 0;

}
5 抛出异常
5.1 问题
在项目的整个生命周期内,错误随时随地都有可能发生,虽然那些错误都在实际运行过程中发生,在设计、编码和测试阶段无法规避但却是可以预见的,如通过使用C++语言所提供的异常机制返回错误信息。

5.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:抛出异常

代码如下:

#include
int connectServer (char const* config)
{
FILE* fp = fopen (config, “r”);
if (fp == NULL)
throw -1;
//其他操作

return 0;

}
int main(int argc, const char * argv[])
{
try
{
connectServer ("/etc/server.cfg");
}
catch (int ex)
{
std::cerr << “打开文件失败!” << std::endl;
exit (-1);
}

return 0;

}
上述代码中,以下代码:

FILE* fp = fopen (config, "r");
if (fp == NULL)
    throw -1;

是一种常见的可预见性错误,当打开一个文件时,如果打开失败,则后续的操作将无法执行,并会导致程序崩溃。此时可以通过throw语句抛出异常的形式通知主调函数发生了错误。如以下主程序中的代码:

try
{
    connectServer ("/etc/server.cfg");
}
catch (int ex)
{
    std::cerr << "打开文件失败!" << std::endl;
    exit (-1);
}

将函数调用connectServer放在try的作用域中,当函数connectServer在执行的过程中抛出了一个异常的时候,catch语句将进行捕捉。通过判断catch语句是否进行捕捉,就可以知道所需的操作是否正确完成。

5.3 完整代码
本案例的完整代码如下所示:

#include
int connectServer (char const* config)
{
FILE* fp = fopen (config, “r”);
if (fp == NULL)
throw -1;
//其他操作

return 0;

}
int main(int argc, const char * argv[])
{
try
{
connectServer ("/etc/server.cfg");
}
catch (int ex)
{
std::cerr << “打开文件失败!” << std::endl;
exit (-1);
}

return 0;

}
6 抛出基本类型异常
6.1 问题
抛出基本类型的异常时,可以分别抛出不同的异常对象的值,在捕捉异常后,根据异常对象的值分别处理不同的错误。

6.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:抛出基本类型异常

代码如下:

#include
int connectServer (char const* config)
{
FILE* fp = fopen (config, “r”);
if (fp == NULL)
throw -1;
char* pc = (char*)malloc (1024);
if (pc == NULL)
throw -2;
//其他操作

return 0;

}
int main(int argc, const char * argv[])
{
try
{
connectServer ("/etc/server.cfg");
}
catch (int ex)
{
if (ex == -1)
std::cerr << “打开文件失败!” << std::endl;
else if (ex == -2)
std::cerr << “分配内存失败!” << std::endl;
exit (-1);
}

return 0;

}
上述代码中,以下代码:

FILE* fp = fopen (config, "r");
if (fp == NULL)
    throw -1;
char* pc = (char*)malloc (1024);
if (pc == NULL)
    throw -2;

当文件打开失败后,抛出基本类型的异常对象的值为-1;申请内存失败后,抛出基本类型的异常对象的值为-2。

上述代码中,以下代码:

catch (int ex)
{
    if (ex == -1)
        std::cerr << "打开文件失败!" << std::endl;
    else if (ex == -2)
        std::cerr << "分配内存失败!" << std::endl;
    exit (-1);
}

根据异常对象的值分别处理不同的错误。

6.3 完整代码
本案例的完整代码如下所示:

#include
int connectServer (char const* config)
{
FILE* fp = fopen (config, “r”);
if (fp == NULL)
throw -1;
char* pc = (char*)malloc (1024);
if (pc == NULL)
throw -2;
//其他操作

return 0;

}
int main(int argc, const char * argv[])
{
try
{
connectServer ("/etc/server.cfg");
}
catch (int ex)
{
if (ex == -1)
std::cerr << “打开文件失败!” << std::endl;
else if (ex == -2)
std::cerr << “分配内存失败!” << std::endl;
exit (-1);
}

return 0;

}
7 抛出类类型异常
7.1 问题
抛出类类型的异常时,可以分别抛出类类型的异常,在捕捉异常时,根据异常对象的类型分别处理。

7.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:抛出类类型异常

代码如下:

#include
class FileException : public std::exception
{
};
class MemoryException : public std::exception
{
};
int connectServer (char const* config)
{
FILE* fp = fopen (config, “r”);
if (fp == NULL)
throw FileException ();
char* pc = (char*)malloc (1024);
if (pc == NULL)
throw MemoryException ();

//其他操作

return 0;

}
int main(int argc, const char * argv[])
{
try
{
connectServer ("/etc/server.cfg");
}
catch (FileException& ex)
{
std::cerr << “打开文件失败!” << std::endl;
exit (-1);
}
catch (MemoryException& ex)
{
std::cerr << “分配内存失败!” << std::endl;
exit (-1);
}

return 0;

}
上述代码中,以下代码:

FILE* fp = fopen (config, "r");
if (fp == NULL)
    throw FileException ();
char* pc = (char*)malloc (1024);
if (pc == NULL)
    throw MemoryException ();

当文件打开失败后,抛出类类型FileException的异常;申请内存失败后,抛出类类型MemoryException的异常。

上述代码中,以下代码:

catch (FileException& ex)
{
    std::cerr << "打开文件失败!" << std::endl;
    exit (-1);
}
catch (MemoryException& ex)
{
    std::cerr << "分配内存失败!" << std::endl;
    exit (-1);
}

在捕捉异常时,根据异常对象的类型分别处理。

7.3 完整代码
本案例的完整代码如下所示:

#include
class FileException : public std::exception
{
};
class MemoryException : public std::exception
{
};
int connectServer (char const* config)
{
FILE* fp = fopen (config, “r”);
if (fp == NULL)
throw FileException ();
char* pc = (char*)malloc (1024);
if (pc == NULL)
throw MemoryException ();

//其他操作

return 0;

}
int main(int argc, const char * argv[])
{
try
{
connectServer ("/etc/server.cfg");
}
catch (FileException& ex)
{
std::cerr << “打开文件失败!” << std::endl;
exit (-1);
}
catch (MemoryException& ex)
{
std::cerr << “分配内存失败!” << std::endl;
exit (-1);
}

return 0;

}
8 借助异常携带更多诊断信息
8.1 问题
由于类是由用户定义的数据类型,在定义类时,类中可以有任意用户想要的数据,所以利用类类型的异常,能够携带更多诊断信息,以便查错。

8.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:借助异常携带更多诊断信息

代码如下:

#include
class FileException : public std::exception
{
public:
std::string m_file;
int m_line;
std::string m_func;
std::string m_error;

FileException (std::string const& file, const int& line, std::string const& func, std::string const& error) : m_file(file), m_line(line), m_func(func), m_error(error)
{
    
}

};
extern int errno;
int connectServer (char const* config)
{
FILE* fp = fopen (config, “r”);
if (fp == NULL)
throw FileException (FILE, LINE, FUNCTION, std::strerror (errno));

//其他操作

return 0;

}
int main(int argc, const char * argv[])
{

try
{
    connectServer ("/etc/server.cfg");
}
catch (FileException& ex)
{
    std::cerr << "文件:" << ex.m_file << std::endl;
    std::cerr << "行号:" << ex.m_line << std::endl;
    std::cerr << "函数:" << ex.m_func << std::endl;
    std::cerr << "错误:" << ex.m_error << std::endl;
    exit (-1);
}

return 0;

}
上述代码中,以下代码:

class FileException : public std::exception
{
public:
std::string m_file;
int m_line;
std::string m_func;
std::string m_error;

FileException (std::string const& file, const int& line, std::string const& func, std::string const& error) : m_file(file), m_line(line), m_func(func), m_error(error)
{
    
}

};
自定义文件异常类FileException,在类中添加与文件操作相关的数据,m_file文件名,m_line行号,m_func函数名,m_error错误名称。

上述代码中,以下代码:

FILE* fp = fopen (config, "r");
if (fp == NULL)
    throw FileException (__FILE__, __LINE__, __FUNCTION__, std::strerror (errno));

在函数connectServer中打开文件,如果打开失败,抛出自定义文件异常类FileException的对象。FILELINE,__FUNCTION__是编译器定义的三个宏,分别为当前运行程序的文件名、行号和函数名,函数strerror根据错误号errno得到错误的名称。

上述代码中,以下代码:

catch (FileException& ex)
{
    std::cerr << "文件:" << ex.m_file << std::endl;
    std::cerr << "行号:" << ex.m_line << std::endl;
    std::cerr << "函数:" << ex.m_func << std::endl;
    std::cerr << "错误:" << ex.m_error << std::endl;
    exit (-1);
}

当异常发生时,能够得到更多的信息。

8.3 完整代码
本案例的完整代码如下所示:

#include
class FileException : public std::exception
{
public:
std::string m_file;
int m_line;
std::string m_func;
std::string m_error;

FileException (std::string const& file, const int& line, std::string const& func, std::string const& error) : m_file(file), m_line(line), m_func(func), m_error(error)
{
    
}

};
extern int errno;
int connectServer (char const* config)
{
FILE* fp = fopen (config, “r”);
if (fp == NULL)
throw FileException (FILE, LINE, FUNCTION, std::strerror (errno));

//其他操作

return 0;

}
int main(int argc, const char * argv[])
{
try
{
connectServer ("/etc/server.cfg");
}
catch (FileException& ex)
{
std::cerr << “文件:” << ex.m_file << std::endl;
std::cerr << “行号:” << ex.m_line << std::endl;
std::cerr << “函数:” << ex.m_func << std::endl;
std::cerr << “错误:” << ex.m_error << std::endl;
exit (-1);
}

return 0;

}
9 忽略异常
9.1 问题
在函数中可以不捕捉异常,不做处理,这样异常会自动向函数的调用者继续抛出,如果直至主函数都不捕捉异常,会由std::unexpected()函数处理。

9.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:忽略异常

代码如下:

#include
int connectServer (char const* config) throw (int)
{
FILE* fp = fopen (config, “r”);
if (fp == NULL)
throw -1;_
//其他操作

return 0;

}
void login (char const* username, char const* passwd) throw (int)
{
connectServer ("/etc/server.cfg");
}
int main(int argc, const char * argv[])
{
try
{
connectServer ("/etc/server.cfg");
}
catch (int ex)
{
std::cerr << “打开文件失败!” << std::endl;
exit (-1);
}

return 0;

}
上述代码中,以下代码:

int connectServer (char const* config) throw (int)
{
FILE* fp = fopen (config, “r”);
if (fp == NULL)
throw -1;_
//其他操作

return 0;

}
在函数connectServer中,当打开文件失败时,抛出异常。

上述代码中,以下代码:

void login (char const* username, char const* passwd) throw (int)
{
connectServer ("/etc/server.cfg");
}
在函数login中可以不捕捉异常,不做处理,这样异常会自动向函数login的调用者继续抛出。

9.3 完整代码
本案例的完整代码如下所示:

#include
int connectServer (char const* config) throw (int)
{
FILE* fp = fopen (config, “r”);
if (fp == NULL)
throw -1;_
//其他操作

return 0;

}
void login (char const* username, char const* passwd) throw (int)
{
connectServer ("/etc/server.cfg");
}
int main(int argc, const char * argv[])
{
try
{
connectServer ("/etc/server.cfg");
}
catch (int ex)
{
std::cerr << “打开文件失败!” << std::endl;
exit (-1);
}

return 0;

}
10 继续抛出异常
10.1 问题
在函数中捕捉到异常之后,可以从catch块中继续抛出所捕获的异常,或者抛出其它异常。

10.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:继续抛出异常

代码如下:

#include
int connectServer (char const* config) throw (int)
{
FILE* fp = fopen (config, “r”);
if (fp == NULL)
throw -1;
//其他操作

return 0;

}
void login (char const* username, char const* passwd) throw (int)
{
try
{
connectServer ("/etc/server.cfg");
}
catch (int)
{
std::cerr << “登录失败!” << std::endl;
throw;
}
}
int main(int argc, const char * argv[])
{
try
{
login ("/etc/server.cfg", “123456”);
}
catch (int ex)
{
std::cerr << “打开文件失败!” << std::endl;
exit (-1);
}

return 0;

}
上述代码中,以下代码:

int connectServer (char const* config) throw (int)
{
FILE* fp = fopen (config, “r”);
if (fp == NULL)
throw -1;
//其他操作

return 0;

}
在函数connectServer中,当打开文件失败时,抛出异常。

上述代码中,以下代码:

void login (char const* username, char const* passwd) throw (int)
{
try
{
connectServer ("/etc/server.cfg");
}
catch (int)
{
std::cerr << “登录失败!” << std::endl;
throw;
}
}
在函数login中捕捉到异常之后,可以从catch块中继续抛出所捕获的异常。

上述代码中,以下代码:

int main(int argc, const char * argv[])
{

try
{
    login ("/etc/server.cfg", "123456");
}
catch (int ex)
{
    std::cerr << "打开文件失败!" << std::endl;
    exit (-1);
}

return 0;

}
在主函数中就能够继续捕捉到函数login抛出的异常。

10.3 完整代码
本案例的完整代码如下所示:

#include
int connectServer (char const* config) throw (int)
{
FILE* fp = fopen (config, “r”);
if (fp == NULL)
throw -1;
//其他操作

return 0;

}
void login (char const* username, char const* passwd) throw (int)
{
try
{
connectServer ("/etc/server.cfg");
}
catch (int)
{
std::cerr << “登录失败!” << std::endl;
throw;
}
}
int main(int argc, const char * argv[])
{
try
{
login ("/etc/server.cfg", “123456”);
}
catch (int ex)
{
std::cerr << “打开文件失败!” << std::endl;
exit (-1);
}

return 0;

}
11 异常终结者
11.1 问题
在函数中抛出异常之后,任何未被捕获的异常,都会由std::unexpected()函数处理,缺省的处理方式就是中止进程。

11.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:异常终结者

代码如下:

#include
int connectServer (char const* config) throw (int)
{
FILE* fp = fopen (config, “r”);
if (fp == NULL)
throw -1;
//其他操作

return 0;

}
void login (char const* username, char const* passwd) throw (int)
{
connectServer ("/etc/server.cfg");
}
int main(int argc, const char * argv[])
{
login ("/etc/server.cfg", “123456”);

return 0;

}
上述代码中,以下代码:

int connectServer (char const* config) throw (int)
{
FILE* fp = fopen (config, “r”);
if (fp == NULL)
throw -1;
//其他操作

return 0;

}
在函数connectServer中,当打开文件失败时,抛出异常。

上述代码中,以下代码:

void login (char const* username, char const* passwd) throw (int)
{
connectServer ("/etc/server.cfg");
}
int main(int argc, const char * argv[])
{
login ("/etc/server.cfg", “123456”);

return 0;

}
但是,在函数connectServer的调用函数login以及函数login的调用函数main中都没有使用catch捕捉函数connectServer抛出的异常。这样std::unexpected()函数将最终处理该异常,缺省的处理方式就是中止进程。

11.3 完整代码
本案例的完整代码如下所示:

#include
int connectServer (char const* config) throw (int)
{
FILE* fp = fopen (config, “r”);
if (fp == NULL)
throw -1;
//其他操作

return 0;

}
void login (char const* username, char const* passwd) throw (int)
{
connectServer ("/etc/server.cfg");
}
int main(int argc, const char * argv[])
{
login ("/etc/server.cfg", “123456”);

return 0;

}
12 异常说明
12.1 问题
异常说明是函数原型的一部分,旨在说明函数可能抛出的异常类型。异常说明是一种承诺,承诺函数不会抛出异常说明以外的异常类型,否则该异常将无法被捕获,并导致进程中止。隐式抛出异常的函数也可以列出它的异常说明。没有异常说明,表示可能抛出任何类型的异常。异常说明为空,表示不会抛出任何类型的异常。如果基类中的虚函数带有异常说明,那么该函数在子类中的覆盖版本不能说明比其基类版本抛出更多的异常。异常说明在函数的声明和定义中必须保持严格一致,否则将导致编译错误。

12.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:异常说明是函数原型的一部分

代码如下:

#include
int connectServer (char const* config) throw (int)
{
FILE* fp = fopen (config, “r”);
if (fp == NULL)
throw -1;
//其他操作

return 0;

}
上述代码中异常说明是函数原型的一部分,旨在说明函数可能抛出的异常类型。在本案例中,异常说明是throw (int),可能抛出的异常类型是int。

异常说明是一种承诺,承诺函数不会抛出异常说明以外的异常类型,如果函数抛出了异常说明以外的异常类型,那么该异常将无法被捕获,并导致进程中止。在本案例中,如果抛出了除int外的其他类型,如float,函数connectServer将终止运行。

步骤二:说明隐式抛出的异常

代码如下:

#include
int connectServer (char const* config) throw (int)
{
FILE* fp = fopen (config, “r”);
if (fp == NULL)
throw -1;
//其他操作

return 0;

}
void login (char const* username, char const* passwd) throw (int)
{
connectServer ("/etc/server.cfg");
}
上述代码中,以下代码:

void login (char const* username, char const* passwd) throw (int)
{
connectServer ("/etc/server.cfg");
}
的函数体内未直接抛出异常,但其调用的函数connectServer抛出了异常,此时函数login将隐式抛出的异常。

步骤三:异常说明可以没有也可以为空

代码如下:

#include
int connectServer (char const* config) throw (int)
{
FILE* fp = fopen (config, “r”);
if (fp == NULL)
throw -1;
//其他操作

return 0;

}
void login (char const* username, char const* passwd) throw (int)
{
connectServer ("/etc/server.cfg");
}
void loginServer (char const* username, char const* passwd)
{
connectServer ("/etc/server.cfg");
}
void loginConnectServer (char const* username, char const* passwd) throw()
{
connectServer ("/etc/server.cfg");
}
上述代码中,以下代码:

void loginServer (char const* username, char const* passwd)
{
connectServer ("/etc/server.cfg");
}
函数loginServer没有异常说明,表示函数loginServer可以抛出任何类型的异常。

上述代码中,以下代码:

void loginConnectServer (char const* username, char const* passwd) throw()
{
connectServer ("/etc/server.cfg");
}
函数loginConnectServer异常说明为空,表示函数loginConnectServer不会抛出任何类型的异常。

步骤四:异常说明与虚函数覆盖

代码如下:

#include
int connectServer (char const* config) throw (int)
{
FILE* fp = fopen (config, “r”);
if (fp == NULL)
throw -1;
//其他操作

return 0;

}
void login (char const* username, char const* passwd) throw (int)
{
connectServer ("/etc/server.cfg");
}
void loginServer (char const* username, char const* passwd)
{
connectServer ("/etc/server.cfg");
}
void loginConnectServer (char const* username, char const* passwd) throw()
{
connectServer ("/etc/server.cfg");
}
class A
{
public:
virtual void foo (void) throw (int, std::string);
};
class B : public A
{
public:
void foo (void) throw (int)
{
throw 1;
}
};
上述代码中,以下代码:

class A
{
public:
virtual void foo (void) throw (int, std::string);
};
class B : public A
{
public:
void foo (void) throw (int)
{
throw 1;
}
};
如果基类A中的虚函数foo带有异常说明throw(int, std::string),那么该函数在子类B中的覆盖版本不能说明比其基类A版本抛出更多的异常。

步骤五:声明和定义的异常说明要一致

代码如下:

#include
int connectServer (char const* config) throw (int)
{
FILE* fp = fopen (config, “r”);
if (fp == NULL)
throw -1;
//其他操作

return 0;

}
void login (char const* username, char const* passwd) throw (int)
{
connectServer ("/etc/server.cfg");
}
void loginServer (char const* username, char const* passwd)
{
connectServer ("/etc/server.cfg");
}
void loginConnectServer (char const* username, char const* passwd) throw()
{
connectServer ("/etc/server.cfg");
}
class A
{
public:
virtual void foo (void) throw (int, std::string);
};
class B : public A
{
public:
void foo (void) throw (int)
{
throw 1;
}
};
void A::foo (void) throw (int, std::string)
{

}
上述代码中,以下代码:

virtual void foo (void) throw (int, std::string);

是类A的成员函数foo的说明。

上述代码中,以下代码:

void A::foo (void) throw (int, std::string)
{

}
是类A的成员函数foo的定义。异常说明在函数foo的声明和定义中必须保持严格一致,否则将导致编译错误。

12.3 完整代码
本案例的完整代码如下所示:

#include
int connectServer (char const* config) throw (int)
{
FILE* fp = fopen (config, “r”);
if (fp == NULL)
throw -1;
//其他操作

return 0;

}
void login (char const* username, char const* passwd) throw (int)
{
connectServer ("/etc/server.cfg");
}
void loginServer (char const* username, char const* passwd)
{
connectServer ("/etc/server.cfg");
}
void loginConnectServer (char const* username, char const* passwd) throw()
{
connectServer ("/etc/server.cfg");
}
class A
{
public:
virtual void foo (void) throw (int, std::string);
};
class B : public A
{
public:
void foo (void) throw (int)
{
throw 1;
}
};
void A::foo (void) throw (int, std::string)
{

}
int main(int argc, const char * argv[])
{

try
{
    connectServer ("/etc/server.cfg");
    //login("/etc/server.cfg", "123456");
    //loginServer("/etc/server.cfg", "123456");
    //loginConnectServer("/etc/server.cfg", "123456");
    A *p = new B;
    p->foo();
    delete p;
}
catch (int ex)
{
    std::cerr << "打开文件失败!" << std::endl;
    exit (-1);
}

return 0;

}
13 构造过程中的异常
13.1 问题
构造函数可以抛出异常,某些时候还必须抛出异常。如因为构造函数没有返回值,所以无法通过返回值通知调用者,这样当构造过程中发生如内存分配失败等可能遇到各种错误时,就必须抛出异常。

13.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:构造函数抛出异常

代码如下:

#include
class MemoryException : public std::exception
{
public:
MemoryException (std::string const& msg) : m_msg (msg)
{
}
char const* what (void) const throw ()
{
return m_msg.c_str ();
}

std::string m_msg;

};
class String
{
private:
char* m_str;
public:
String (char const* str)
{
if (! (m_str = (char*)malloc (strlen (str ? str : “”) + 1)))
throw MemoryException (strerror (errno));
}
};
int main(int argc, const char * argv[])
{

try
{
    String str ("This is a string."); 
}
catch (std::exception& ex)
{
    std::cerr << ex.what () << std::endl;
    exit (-1);
}

return 0;

}
上述代码中,以下代码:

String (char const* str)
{
    if (! (m_str = (char*)malloc (strlen (str ? str : "") + 1)))
        throw MemoryException (strerror (errno));
}

是类string的构造函数。因为构造函数没有返回值,所以当在构造过程中发生内存分配失败错误时,就必须抛出异常。

13.3 完整代码
本案例的完整代码如下所示:

#include
class MemoryException : public std::exception
{
public:
MemoryException (std::string const& msg) : m_msg (msg)
{
}
char const* what (void) const throw ()
{
return m_msg.c_str ();
}

std::string m_msg;

};
class String
{
private:
char* m_str;
public:
String (char const* str)
{
if (! (m_str = (char*)malloc (strlen (str ? str : “”) + 1)))
throw MemoryException (strerror (errno));
}
};
int main(int argc, const char * argv[])
{

try
{
    String str ("This is a string."); 
}
catch (std::exception& ex)
{
    std::cerr << ex.what () << std::endl;
    exit (-1);
}

return 0;

}
14 不完整对象的回滚
14.1 问题
构造函数抛出异常,对象将被不完整构造,而一个被不完整构造的对象,其析构函数永远不会被执行。构造函数的回滚机制,可以保证所有成员子对象和基类子对象,在抛出异常的过程中,都能得到正确地析构。所有动态分配的资源,必须在抛出异常之前,手动释放,否则将形成内存泄漏,除非使用了智能指针。

14.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:不完整对象的回滚

代码如下:

#include
class FileException : public std::exception
{
public:
std::string m_file;
int m_line;
std::string m_func;
std::string m_error;
std::string m_msg;

FileException (std::string const& file, const int& line, std::string const& func, std::string const& error) : m_file(file), m_line(line), m_func(func), m_error(error)
{
    char strLine[10];
    sprintf(strLine, "%d", line);
    m_msg = "文件:" + file + "\n行号:" + strLine + "\n函数:" + func + "\n错误:" + error + "\n";
}
char const* what (void) const throw ()
{
    return m_msg.c_str ();
}

};
class MemoryException : public std::exception
{
public:
MemoryException (std::string const& msg) : m_msg (msg)
{
}
char const* what (void) const throw ()
{
return m_msg.c_str ();
}

std::string m_msg;

};
class Human
{
private:
std::string m_address;
char* m_name;
public:
Human (std::string const& address, char const* name)
{
if (! (m_name = (char*)malloc (strlen (name ? name : “”) + 1)))
throw MemoryException (strerror (errno));

    FILE* fp = fopen ("/etc/server.cfg", "r");
    if (fp == NULL)
    {
        delete [] m_name;
        throw FileException (__FILE__, __LINE__, __FUNCTION__, std::strerror (errno));
    }
}
~Human (void)
{
    free (m_name);
}

};
int main(int argc, const char * argv[])
{

try
{
    Human human ("江苏南京市", "ZhangSan");
}
catch (std::exception& ex)
{
    std::cerr << ex.what () << std::endl;
    exit (-1);
}

return 0;

}
上述代码中,以下代码:

private:
std::string m_address;
char* m_name;
定义了类Human的两个数据成员,一个是m_address,用于存放地址,另一个是m_name用于保存姓名。

上述代码中,以下代码:

Human (std::string const& address, char const* name) : m_address(address)
{
    if (! (m_name = (char*)malloc (strlen (name ? name : "") + 1)))
        throw MemoryException (strerror (errno));
    
    FILE* fp = fopen ("/etc/server.cfg", "r");
    if (fp == NULL)
    {
        delete [] m_name;
        throw FileException (__FILE__, __LINE__, __FUNCTION__, std::strerror (errno));
    }
}

定义了类Human的构造函数。构造函数的回滚机制,可以保证类Human成员子对象m_address,在如下代码:

    if (! (m_name = (char*)malloc (strlen (name ? name : "") + 1)))
        throw MemoryException (strerror (errno));

抛出异常的过程中,都能得到正确地析构。如果未抛出异常,当如下语句:

    FILE* fp = fopen ("/etc/server.cfg", "r");
    if (fp == NULL)
    {
        delete [] m_name;
        throw FileException (__FILE__, __LINE__, __FUNCTION__, std::strerror (errno));
    }

执行时打开文件失败,在抛出异常之前,必须释放为m_name分配的存储空间。

14.3 完整代码
本案例的完整代码如下所示:

#include
class FileException : public std::exception
{
public:
std::string m_file;
int m_line;
std::string m_func;
std::string m_error;
std::string m_msg;

FileException (std::string const& file, const int& line, std::string const& func, std::string const& error) : m_file(file), m_line(line), m_func(func), m_error(error)
{
    char strLine[10];
    sprintf(strLine, "%d", line);
    m_msg = "文件:" + file + "\n行号:" + strLine + "\n函数:" + func + "\n错误:" + error + "\n";
}
char const* what (void) const throw ()
{
    return m_msg.c_str ();
}

};
class MemoryException : public std::exception
{
public:
MemoryException (std::string const& msg) : m_msg (msg)
{
}
char const* what (void) const throw ()
{
return m_msg.c_str ();
}

std::string m_msg;

};
class Human
{
private:
std::string m_address;
char* m_name;
public:
Human (std::string const& address, char const* name) : m_address(address)
{
if (! (m_name = (char*)malloc (strlen (name ? name : “”) + 1)))
throw MemoryException (strerror (errno));

    FILE* fp = fopen ("/etc/server.cfg", "r");
    if (fp == NULL)
    {
        delete [] m_name;
        throw FileException (__FILE__, __LINE__, __FUNCTION__, std::strerror (errno));
    }
}
~Human (void)
{
    free (m_name);
}

};
int main(int argc, const char * argv[])
{

try
{
    Human human ("江苏南京市", "ZhangSan");
}
catch (std::exception& ex)
{
    std::cerr << ex.what () << std::endl;
    exit (-1);
}

return 0;

}
15 析构过程中的异常
15.1 问题
不要从析构函数中主动抛出异常。在析构函数中,执行任何可能引发异常的操作,都尽量把异常在内部消化掉,防止其从析构函数中被继续抛出。

15.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:析构过程中的异常

代码如下:

#include
class FileException : public std::exception
{
public:
std::string m_file;
int m_line;
std::string m_func;
std::string m_error;
std::string m_msg;

FileException (std::string const& file, const int& line, std::string const& func, std::string const& error) : m_file(file), m_line(line), m_func(func), m_error(error)
{
    char strLine[10];
    sprintf(strLine, "%d", line);
    m_msg = "文件:" + file + "\n行号:" + strLine + "\n函数:" + func + "\n错误:" + error + "\n";
}
char const* what (void) const throw ()
{
    return m_msg.c_str ();
}

};
class MemoryException : public std::exception
{
public:
MemoryException (std::string const& msg) : m_msg (msg)
{
}
char const* what (void) const throw ()
{
return m_msg.c_str ();
}

std::string m_msg;

};
class Human
{
private:
std::string m_address;
char* m_name;
public:
Human (std::string const& address, char const* name) : m_address(address)
{
if (! (m_name = (char*)malloc (strlen (name ? name : “”) + 1)))
throw MemoryException (strerror (errno));
}
~Human (void)
{
free (m_name);
FILE* fp = fopen ("/etc/server.cfg", “w”);
if (fp == NULL)
{
try
{
throw FileException (FILE, LINE, FUNCTION, std::strerror (errno));
}
catch (std::exception& ex)
{
std::cerr << ex.what () << std::endl;
}
}
else
{
//保存数据
}
}
};
int main(int argc, const char * argv[])
{

try
{
    Human human ("江苏南京市", "ZhangSan");
}
catch (std::exception& ex)
{
    std::cerr << ex.what () << std::endl;
}

return 0;

}
上述代码中,以下代码:

~Human (void)
{
    free (m_name);
    FILE* fp = fopen ("/etc/server.cfg", "w");
    if (fp == NULL)
    {
        try
        {
            throw FileException (__FILE__, __LINE__, __FUNCTION__, std::strerror (errno));
        }
        catch (std::exception& ex)
        {
            std::cerr << ex.what () << std::endl;
        }
    }
    else
    {
        //保存数据
    }
}

定义了类Human的析构函数。此时执行任何可能引发异常的操作,如下语句:

    FILE* fp = fopen ("/etc/server.cfg", "w");
    if (fp == NULL)
    {
        try
        {
            throw FileException (__FILE__, __LINE__, __FUNCTION__, std::strerror (errno));
        }
        catch (std::exception& ex)
        {
            std::cerr << ex.what () << std::endl;
        }
    }

都尽量把异常在内部消化掉,防止其从析构函数中被继续抛出。

15.3 完整代码
本案例的完整代码如下所示:

#include
class FileException : public std::exception
{
public:
std::string m_file;
int m_line;
std::string m_func;
std::string m_error;
std::string m_msg;

FileException (std::string const& file, const int& line, std::string const& func, std::string const& error) : m_file(file), m_line(line), m_func(func), m_error(error)
{
    char strLine[10];
    sprintf(strLine, "%d", line);
    m_msg = "文件:" + file + "\n行号:" + strLine + "\n函数:" + func + "\n错误:" + error + "\n";
}
char const* what (void) const throw ()
{
    return m_msg.c_str ();
}

};
class MemoryException : public std::exception
{
public:
MemoryException (std::string const& msg) : m_msg (msg)
{
}
char const* what (void) const throw ()
{
return m_msg.c_str ();
}

std::string m_msg;

};
class Human
{
private:
std::string m_address;
char* m_name;
public:
Human (std::string const& address, char const* name) : m_address(address)
{
if (! (m_name = (char*)malloc (strlen (name ? name : “”) + 1)))
throw MemoryException (strerror (errno));
}
~Human (void)
{
free (m_name);
FILE* fp = fopen ("/etc/server.cfg", “w”);
if (fp == NULL)
{
try
{
throw FileException (FILE, LINE, FUNCTION, std::strerror (errno));
}
catch (std::exception& ex)
{
std::cerr << ex.what () << std::endl;
}
}
else
{
//保存数据
}
}
};
int main(int argc, const char * argv[])
{

try
{
    Human human ("江苏南京市", "ZhangSan");
}
catch (std::exception& ex)
{
    std::cerr << ex.what () << std::endl;
}

return 0;

}
16 继承标准异常
16.1 问题
抛出标准库异常,或标准库异常的子类异常,以允许用户以与标准库一致的方式捕获该异常。

16.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:继承标准异常

代码如下:

#include
class FileException : public std::exception
{
public:
FileException (std::string const& msg) : m_msg (msg)
{
}
char const* what (void) const throw ()
{
return m_msg.c_str ();
}

std::string m_msg;

};
int connectServer (const char* config) throw (FileException)
{
FILE* fp = fopen (config, “r”);
if (fp == NULL)
throw FileException(config);
//其他操作

return 0;

}
int main(int argc, const char * argv[])
{
try
{
connectServer ("/etc/server.cfg");
}
catch (std::exception& ex)
{
std::cerr << ex.what () << std::endl;
exit (-1);
}

return 0;

}
上述代码中,以下代码:

class FileException : public std::exception
{
public:
FileException (std::string const& msg) : m_msg (msg)
{
}
char const* what (void) const throw ()
{
return m_msg.c_str ();
}

std::string m_msg;

};
定义了标准库异常类exception的子类异常类FileException。

上述代码中,以下代码:

int connectServer (const char* config) throw (FileException)
{
FILE* fp = fopen (config, “r”);
if (fp == NULL)
throw FileException(config);
//其他操作

return 0;

}
在函数connectServer中如果打开文件失败,则抛出异常类FileException的对象。

上述代码中,以下代码:

catch (std::exception& ex)
{
    std::cerr << ex.what () << std::endl;
    exit (-1);
}

在主程序中,使用catch捕捉标准库异常类exception类型的异常。以允许用户以与标准库一致的方式捕获该异常。

16.3 完整代码
本案例的完整代码如下所示:

#include
class FileException : public std::exception
{
public:
FileException (std::string const& msg) : m_msg (msg)
{
}
char const* what (void) const throw ()
{
return m_msg.c_str ();
}

std::string m_msg;

};
int connectServer (const char* config) throw (FileException)
{
FILE* fp = fopen (config, “r”);
if (fp == NULL)
throw FileException(config);
//其他操作

return 0;

}
int main(int argc, const char * argv[])
{
try
{
connectServer ("/etc/server.cfg");
}
catch (std::exception& ex)
{
std::cerr << ex.what () << std::endl;
exit (-1);
}

return 0;

}
17 打开关闭I/O流
17.1 问题
流(Stream)是指字节序列形式的数据,有如流水一般,从一个对象流向另一个对象。流分为输入流(Input Stream)和输出流(Output Stream),输入流是指数据字节从表示输入设备(如键盘、磁盘文件等)的对象流向内存对象,输出流是指数据字节从内存对象流向表示输出设备(如显示器、打印机、磁盘文件等)的对象。

17.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:打开关闭I/O流

代码如下:

#include
#include
int main(int argc, const char * argv[])
{

std::ofstream file1;
file1.open("server.cfg", std::ios::out);
file1 << "Hello";
file1.close();

std::ifstream file2("server.cfg", std::ios::in);
char str[100];
file2 >> str;
std::cout << str << std::endl;
file2.close();

std::fstream file3;
file3.open("server1.cfg", std::ios::in | std::ios::out | std::ios::trunc);
file3 << "world";
file3.seekp(std::ios::beg);
file3 >> str;
std::cout << str << std::endl;

return 0;

}
上述代码中,以下代码:

std::ofstream file1;

定义了一个输出流对象。

上述代码中,以下代码:

file1.open("server.cfg", std::ios::out);

使用类std::ofstream的成员函数open打开一个文件。成员函数open的第一个参数是文件名,第二个参数是打开模式,打开模式有以下几种:

1)ios::in打开文件用于读取,不存在则失败,存在不清空。适用于ifstream(缺省)/fstream。

2)ios::out打开文件用于写入,不存在则创建,存在则清空。适用于ofstream(缺省)/fstream。

3)ios::app打开文件用于追加,不存在则创建,存在不清空。适用于ofstream/fstream。

4)ios::trunc打开时清空原内容,适用于ofstream/fstream。

5)ios::ate打开时定位文件尾,适用于ifstream/ofstream/fstream。

6)ios::binary以二进制模式读写,适用于ifstream/ofstream/fstream。

上述代码中,以下代码:

file1 << "Hello";

向文件中写入一个字符串。

上述代码中,以下代码:

file1.close();

使用类std::ofstream的成员函数close关闭对象file1中打开的文件。

上述代码中,以下代码:

std::ifstream file2("server.cfg", std::ios::in);

定义了一个输入流对象,使用类std::ifstream的构造函数打开一个文件。成员函数open的第一个参数是文件名,第二个参数是打开模式。

上述代码中,以下代码:

char str[100];
file2 >> str;
std::cout << str << std::endl;

从文件中读入字符串。

上述代码中,以下代码:

std::fstream file3;

定义了一个输入输出流对象。

上述代码中,以下代码:

file3.open("server1.cfg", std::ios::in | std::ios::out | std::ios::trunc);
file3 << "world";
file3.seekp(std::ios::beg);
file3 >> str;
std::cout << str << std::endl;

使用类std::fstream的成员函数open打开一个文件,打开模式可以组合使用。

上述代码中,以下代码:

file3 << "world";

向文件中写入一个字符串。

上述代码中,以下代码:

file3.seekp(std::ios::beg);
file3 >> str;
std::cout << str << std::endl;

使用类std::fstream的成员函数seekp,将文件指针指向文件的开始。上面写入字符串“world”后,文件指针指向字符d的后面,此句话将文件指针指向字符w。

上述代码中,以下代码:

file3 >> str;
std::cout << str << std::endl;

从文件中读入字符串。值得注意的是,此语句后没有使用类std::fstream的成员函数close关闭流对象file3。这是允许的,当file3的作用域结束时,类std::fstream的析构函数也会关闭流对象。

17.3 完整代码
本案例的完整代码如下所示:

#include
#include
int main(int argc, const char * argv[])
{

std::ofstream file1;
file1.open("server.cfg", std::ios::out);
file1 << "Hello";
file1.close();

std::ifstream file2("server.cfg", std::ios::in);
char str[100];
file2 >> str;
std::cout << str << std::endl;

std::fstream file3("server1.cfg", std::ios::in | std::ios::out | std::ios::trunc);
file3 << "world";
file3.seekp(std::ios::beg);
file3 >> str;
std::cout << str << std::endl;

return 0;

}
18 判断I/O流的状态
18.1 问题
I/O流类内部维护当前状态,其值为以下常量的位或,ios::goodbit表示一切正常,ios::badbit表示发生致命错误,ios::eofbit表示遇到文件尾,ios::failbit表示实际读写字节数未达预期。I/O流类支持到bool类型的隐式转换,发生致命错误或遇到文件尾,返回false,否则返回true,将I/O流对象直接应用到布尔上下文中,即可实现转换。处于致命错误或文件尾状态的流,在复位前无法工作。

18.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:判断I/O流的状态

代码如下:

#include
#include
int main(int argc, const char * argv[])
{

std::ifstream file("server.cfg", std::ios::in);
if (! file)
{
    std::cout << std::boolalpha;
    std::cout << "ios::goodbit = " << file.good() << std::endl;
    std::cout << "ios::badbit = " << file.bad() << std::endl;
    std::cout << "ios::eofbit = " << file.eof() << std::endl;
    std::cout << "ios::failbit = " << file.fail() << std::endl;
    std::cout << "打开文件失败!" << std::endl;
    file.clear ();
    file.open ("client.cfg");
    if (! file)
    {
        std::cout << "打开文件失败!" << std::endl;
        exit (-1);
    }
}
char str[100];
file >> str;
std::cout << str << std::endl;

return 0;

}
上述代码中,以下代码:

std::ifstream file("server.cfg", std::ios::in);
if (! file)
{

将I/O流对象file直接应用到布尔上下文if(!file)中,即可实现到bool类型的隐式转换, 发生致命错误或遇到文件尾,返回false,否则返回true。

上述代码中,以下代码:

    std::cout << "ios::goodbit = " << file.good() << std::endl;
    std::cout << "ios::badbit = " << file.bad() << std::endl;
    std::cout << "ios::eofbit = " << file.eof() << std::endl;
    std::cout << "ios::failbit = " << file.fail() << std::endl;

获取I/O流类内部维护当前状态,file.good()当流可用且状态位全零时返回true,file.bad()返回badbit位被设置否,badbit表示发生致命错误,file.eof()返回eofbit位被设置否,eofbit表示遇到文件尾,file.fail()返回badbit或failbit位被设置否,failbit表示实际读写字节数未达预期。

上述代码中,以下代码:

    file.clear ();

处于致命错误或文件尾状态的流,在复位前无法工作。类std::ifstream的成员函数clear负责复位流状态。

18.3 完整代码
本案例的完整代码如下所示:

#include
#include
int main(int argc, const char * argv[])
{

std::ifstream file("server.cfg", std::ios::in);
if (! file)
{
    std::cout << std::boolalpha;
    std::cout << "ios::goodbit = " << file.good() << std::endl;
    std::cout << "ios::badbit = " << file.bad() << std::endl;
    std::cout << "ios::eofbit = " << file.eof() << std::endl;
    std::cout << "ios::failbit = " << file.fail() << std::endl;
    std::cout << "打开文件失败!" << std::endl;
    file.clear ();
    file.open ("client.cfg");
    if (! file)
    {
        std::cout << "打开文件失败!" << std::endl;
        exit (-1);
    }
}
char str[100];
file >> str;
std::cout << str << std::endl;

return 0;

}
19 通过流函数格式化I/O流
19.1 问题
I/O流类(ios)定义了一组用于控制输入输出格式的公有成员函数,调用这些函数可以改变I/O流对象内部的格式状态,进而影响后续输入输出的格式化方式,一般而言,对I/O流格式的改变都是持久的,即只要不再设置新格式,当前格式将始终保持下去,但是显示域宽是个例外,通过ios::width(int)所设置的显示域宽,只影响紧随其后的第一次输出,再往后的输出又恢复到默认状态。

19.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:通过流函数格式化I/O流

代码如下:

#include
#include
int main(int argc, const char * argv[])
{

std::cout.precision (10);
std::cout << sqrt (200) << std::endl;
std::cout << std::cout.precision () << std::endl;
std::cout.width (10);
std::cout.fill ('-');
std::cout.setf (std::ios::internal, std::ios::adjustfield);
std::cout.setf (std::ios::showpos);
std::cout << 12345 << std::endl;
return 0;

}
上述代码中,以下代码:

std::cout.precision (10);

设置浮点数的精度值,返回原精度。

上述代码中,以下代码:

std::cout << std::cout.precision () << std::endl;

获取当前浮点精度。

上述代码中,以下代码:

std::cout.width (10);

设置显示域宽,返回原域宽。当设置的域宽小于实际域宽时,按实际域宽显示,大于实际域宽时,默认用空格填补。也可以用下述函数指定填补字符:

std::cout.fill ('-');

上述代码中,以下代码:

std::cout.setf (std::ios::internal, std::ios::adjustfield);

第一个参数用于添加指定格式标志位std::ios::internal,该格式标志表示数值右对齐,符号左对齐,第二个参数将互斥域清零。函数返回原标志。

std::cout.setf (std::ios::showpos);

用于添加指定格式标志位std::ios::showpos,该格式标志表示正整数前面显示+号。

19.3 完整代码
本案例的完整代码如下所示:

#include
#include
int main(int argc, const char * argv[])
{

std::cout.precision (10);
std::cout << sqrt (200) << std::endl;
std::cout << std::cout.precision () << std::endl;
std::cout.width (10);
std::cout.fill ('-');
std::cout.setf (std::ios::internal, std::ios::adjustfield);
std::cout.setf (std::ios::showpos);
std::cout << 12345 << std::endl;

return 0;

}
20 通过流控制符格式化I/O流
20.1 问题
标准库提供了一组特殊的全局函数,它们有的带有参数(在iomanip头文件中声明),有的不带参数(在iostream头文件中声明),可被直接嵌入到输入输出表达式中,影响后续输入输出格式,称为流控制符。

20.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:通过流控制符格式化I/O流

代码如下:

#include
#include
#include
int main(int argc, const char * argv[])
{

std::cout << std::setprecision (10) << sqrt (200) << std::endl;
std::cout << std::scientific << sqrt (200) << std::endl;
std::cout << std::setw (10) << std::setfill ('-') << std::internal << std::showpos << 12345 << std::endl;

return 0;

}
上述代码中,以下代码:

std::cout << std::setprecision (10) << sqrt (200) << std::endl;

通过流控制符std::setprecision (10)设置200的平方根输出的浮点精度。

上述代码中,以下代码:

std::cout << std::scientific << sqrt (200) << std::endl;

通过流控制符std::scientific设置200的平方根输出用科学计数法表示。

上述代码中,以下代码:

std::cout << std::setw (10) << std::setfill ('-') << std::internal << std::showpos << 12345 << std::endl;

通过流控制符std::setw (10)设置域宽为10,通过流控制符std::setfill (’-’)设置用字符-作为填充字符使用,通过流控制符std::internal设置数值右对齐但符号左对齐,通过流控制符std::showpos设置正整数前面显示+号,经过以上设置后,整型数12345输出结果为±—12345。

20.3 完整代码
本案例的完整代码如下所示:

#include
#include
#include
int main(int argc, const char * argv[])
{

std::cout << std::setprecision (10) << sqrt (200) << std::endl;
std::cout << std::scientific << sqrt (200) << std::endl;
std::cout << std::setw (10) << std::setfill ('-') << std::internal << std::showpos << 12345 << std::endl;

return 0;

}
21 设置字符颜色的流控制符
21.1 问题
C++允许程序员重载流控制符,以定义自己指定功能的流控制符。

21.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:设置字符颜色的流控制符

代码如下:

#include
#include
std::ostream& color (std::ostream& o)
{
const int foreColor = 37;
const int backColor = 40;
return o << “\x1b[” << foreColor << “;” << backColor << “m”;
}
int main(int argc, const char * argv[])
{

std::cout << color << "HelloWorld" << std::endl;

return 0;

}
上述代码中,以下代码:

std::ostream& color (std::ostream& o)
{
const int foreColor = 37;
const int backColor = 40;
return o << “\x1b[” << foreColor << “;” << backColor << “m”;
}
定义自己指定功能的流控制符color,用于改变Unix控制台字体颜色。其中前景色foreColor可选的范围为30: 黑,31: 红,32: 绿,33: 黄,34: 蓝,35: 紫,36: 深绿,37: 白色;背景色backColor可选的范围为:40: 黑,41: 红,42: 绿,43: 黄,44: 蓝,45: 紫,46: 深绿,47: 白色。

上述代码中,以下代码:

std::cout << color << "HelloWorld" << std::endl;

通过流控制符color设置后续现实的字符串颜色。

21.3 完整代码
本案例的完整代码如下所示:

#include
#include
std::ostream& color (std::ostream& o)
{
const int foreColor = 37;
const int backColor = 40;
return o << “\x1b[” << foreColor << “;” << backColor << “m”;
}
int main(int argc, const char * argv[])
{

std::cout << color << "HelloWorld" << std::endl;

return 0;

}
22 非格式化I/O
22.1 问题
iostream类提供了许多库函数,用于处理非格式化I/O操作。

22.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:非格式化I/O

代码如下:

#include
int main(int argc, const char * argv[])
{

char c;
c = std::cin.get();
std::cout << c << std::endl;

if (std::cin.get(c))
    std::cout << c << std::endl;

char str[10] = {};
if (std::cin.get(str, 10, '#'))
    std::cout << str << std::endl;

if (std::cin.getline(str, 10, '#'))
    std::cout << str << std::endl;

std::cin.putback('c');
c = std::cin.get();
std::cout << c << std::endl;

std::cin.putback('c');
c = std::cin.peek();
std::cout << c << std::endl;
c = std::cin.get();
std::cout << c << std::endl;

std::cin.ignore(5, '#');
if (std::cin.get(str, 10, '#'))
    std::cout << str << std::endl;
if (std::cin.get(str, 10, '#'))
    std::cout << str << std::endl;
std::cout << std::cin.gcount() << std::endl;

std::cout.put('a');

std::cout.flush();
return 0;

}
上述代码中,以下代码:

char c;
c = std::cin.get();
std::cout << c << std::endl;

使用类iostream库函数int get(void)读取一个字符。

上述代码中,以下代码:

if (std::cin.get(c))
    std::cout << c << std::endl;

使用类iostream库函数iostream& get(char&c)读取一个字符,返回输入流本身,其在布尔上下文中的值,成功为true,失败或遇到文件尾为false。

上述代码中,以下代码:

char str[10] = {};
if (std::cin.get(str, 10, '#'))
    std::cout << str << std::endl;

使用类iostream库函数istream& istream::get (char* buffer, streamsize num, char delim = ‘\n’)读取字符到buffer中,直到读满num-1个字符,或遇到文件尾,或遇到定界符delim(缺省为换行符),追加结尾空字符,返回流本身。

上述代码中,以下代码:

if (std::cin.getline(str, 10, '#'))
    std::cout << str << std::endl;

使用类iostream库函数istream& istream::getline (char* buffer,streamsize num, char delim = ‘\n’) 读取字符到buffer中,直到读满num-1个字符,或遇到文件尾,或遇到定界符delim(缺省为换行符),追加结尾空字符,返回流本身。

上述代码中,以下代码:

std::cin.putback('c');
c = std::cin.get();
std::cout << c << std::endl;

使用类iostream库函数istream& istream::putback (char ch) 将字符ch放回输入流,返回流本身。

上述代码中,以下代码:

std::cin.putback('c');
c = std::cin.peek();
std::cout << c << std::endl;
c = std::cin.get();
std::cout << c << std::endl;

使用类iostream库函数int istream::peek (void)返回但不读取读指针当前位置处的字符,失败或遇到文件尾返回EOF。

上述代码中,以下代码:

std::cin.ignore(5, '#');
if (std::cin.get(str, 10, '#'))
    std::cout << str << std::endl;

使用类iostream库函数istream& istream::ignore (streamsize num = 1,int delim = EOF) 忽略输入流中的num(缺省1)个字符,或遇到定界符delim(缺省到文件尾),返回流本身。

上述代码中,以下代码:

if (std::cin.get(str, 10, '#'))
    std::cout << str << std::endl;
std::cout << std::cin.gcount() << std::endl;

使用类iostream库函数streamsize istream::gcount (void) 返回最后一次从输入流中读取的字节数。

上述代码中,以下代码:

std::cout.put('a');

使用类iostream库函数ostream& ostream::put (char ch) 一次向输出流写入一个字符,返回流本身。

上述代码中,以下代码:

std::cout.flush();

使用类iostream库函数ostream& ostream::flush (void) 将输出流缓冲区中的数据刷到设备上,返回流本身。

22.3 完整代码
本案例的完整代码如下所示:

#include
int main(int argc, const char * argv[])
{

char c;
c = std::cin.get();
std::cout << c << std::endl;

if (std::cin.get(c))
    std::cout << c << std::endl;

char str[10] = {};
if (std::cin.get(str, 10, '#'))
    std::cout << str << std::endl;

if (std::cin.getline(str, 10, '#'))
    std::cout << str << std::endl;

std::cin.putback('c');
c = std::cin.get();
std::cout << c << std::endl;

std::cin.putback('c');
c = std::cin.peek();
std::cout << c << std::endl;
c = std::cin.get();
std::cout << c << std::endl;

std::cin.ignore(5, '#');
if (std::cin.get(str, 10, '#'))
    std::cout << str << std::endl;
if (std::cin.get(str, 10, '#'))
    std::cout << str << std::endl;
std::cout << std::cin.gcount() << std::endl;

std::cout.put('a');

std::cout.flush();
return 0;

}
23 文件复制
23.1 问题
通过iostream类提供了二进制数据读写方法,先将一个文件读入内存,再将内存中的数据写回磁盘,实现文件复制。

23.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:文件复制

代码如下:

#include
#include
int main(int argc, const char * argv[])
{

std::ifstream file ("server.cfg", std::ios::in | std::ios::binary);
if (! file)
{
    std::cout << "打开文件失败!" << std::endl;
    exit (-1);
}
std::ofstream fileCopy ("server副本.cfg", std::ios::out | std::ios::binary);
if (! fileCopy)
{
    std::cout << "打开文件失败!" << std::endl;
    exit (-1);
}
char buffer[1025] = {};

while (!file.eof())
{
    file.read(buffer, 1024);
    size_t size = file.gcount();
    fileCopy.write(buffer, size);
}

return 0;

}
上述代码中,以下代码:

std::ifstream file ("server.cfg", std::ios::in | std::ios::binary);
if (! file)
{
    std::cout << "打开文件失败!" << std::endl;
    exit (-1);
}

打开要被复制的源文件。

上述代码中,以下代码:

std::ofstream fileCopy ("server副本.cfg", std::ios::out | std::ios::binary);
if (! fileCopy)
{
    std::cout << "打开文件失败!" << std::endl;
    exit (-1);
}

打开要被复制的目标文件。

char buffer[1025] = {};

while (!file.eof())
{
    file.read(buffer, 1024);
    size_t size = file.gcount();
    fileCopy.write(buffer, size);
}

设置循环,每次赋值1024个字节,直到源文件被复制完成。值得注意的是以下语句:

    size_t size = file.gcount();

如果源文件本身就小于1024个字节,那么实际读取到buffer的字符就不是1024,所以需要通过该语句获取实际读入的字节数,以正确写入目标文件。这种情况还发生在源文件一般不会是1024个字节的整数倍,那么最后一次读取了多少个字节,也需要通过该语句获得。

23.3 完整代码
本案例的完整代码如下所示:

#include
#include
int main(int argc, const char * argv[])
{

std::ifstream file ("server.cfg", std::ios::in | std::ios::binary);
if (! file)
{
    std::cout << "打开文件失败!" << std::endl;
    exit (-1);
}
std::ofstream fileCopy ("server副本.cfg", std::ios::out | std::ios::binary);
if (! fileCopy)
{
    std::cout << "打开文件失败!" << std::endl;
    exit (-1);
}
char buffer[1025] = {};

while (!file.eof())
{
    file.read(buffer, 1024);
    size_t size = file.gcount();
    fileCopy.write(buffer, size);
}

return 0;

}
24 随机读写
24.1 问题
通过iostream类提供的文件指针获取设置函数,可以随机读写文件中的内容。

24.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:随机读写

代码如下:

#include
#include
int main(int argc, const char * argv[])
{

std::ofstream file ("server.cfg", std::ios::out | std::ios::binary);
if (! file)
{
    std::cout << "打开文件失败!" << std::endl;
    exit (-1);
}
int buffer[10] = {100, 200, 300, 400, 500, 600, 700, 800, 900, 1000};
file.write((char*)buffer, sizeof(buffer));
file.close();

std::ifstream file1 ("server.cfg", std::ios::in | std::ios::binary);
if (! file)
{
    std::cout << "打开文件失败!" << std::endl;
    exit (-1);
}
int number;
file1.seekg(2 * sizeof(int));
file1.read((char*)&number, sizeof(int));
std::cout << number << std::endl;

std::streamoff pos = file1.tellg();
file1.seekg(pos + 3 * sizeof(int));
file1.read((char*)&number, sizeof(int));
std::cout << number << std::endl;

return 0;

}
上述代码中,以下代码:

std::ofstream file ("server.cfg", std::ios::out | std::ios::binary);
if (! file)
{
    std::cout << "打开文件失败!" << std::endl;
    exit (-1);
}
int buffer[10] = {100, 200, 300, 400, 500, 600, 700, 800, 900, 1000};
file.write((char*)buffer, sizeof(buffer));
file.close();

用二进制方式在server.cfg中存储了10个整型数。

上述代码中,以下代码:

std::ifstream file1 ("server.cfg", std::ios::in | std::ios::binary);
if (! file)
{
    std::cout << "打开文件失败!" << std::endl;
    exit (-1);
}
int number;
file1.seekg(2 * sizeof(int));

利用类ifstream中的成员函数seekg将文件指针直接定位到第三个整数,第一个整数的位置是文件开头,即0,第二个整数的位置是sizeof(int),即4,因为一个整数按二进制方式存储占4个字节,依次类推,第三个整数的位置就是2 * sizeof(int),即8。

上述代码中,以下代码:

file1.read((char*)&number, sizeof(int));
std::cout << number << std::endl;

直接读取第三个整数。

上述代码中,以下代码:

std::streamoff pos = file1.tellg();
file1.seekg(pos + 3 * sizeof(int));
file1.read((char*)&number, sizeof(int));

利用类ifstream中的成员函数tellg获得文件指针的当前位置,因为上面直接读取了第三个整数,所以得到的当前位置是第四个整数的位置。

上述代码中,以下代码:

file1.seekg(pos + 3 * sizeof(int));
file1.read((char*)&number, sizeof(int));

再通过seekg函数将文件指针向后移动3个整数,读取的就是第七个整数。

24.3 完整代码
本案例的完整代码如下所示:

#include
#include
int main(int argc, const char * argv[])
{

std::ofstream file ("server.cfg", std::ios::out | std::ios::binary);
if (! file)
{
    std::cout << "打开文件失败!" << std::endl;
    exit (-1);
}
int buffer[10] = {100, 200, 300, 400, 500, 600, 700, 800, 900, 1000};
file.write((char*)buffer, sizeof(buffer));
file.close();

std::ifstream file1 ("server.cfg", std::ios::in | std::ios::binary);
if (! file)
{
    std::cout << "打开文件失败!" << std::endl;
    exit (-1);
}
int number;
file1.seekg(2 * sizeof(int));
file1.read((char*)&number, sizeof(int));
std::cout << number << std::endl;

std::streamoff pos = file1.tellg();
file1.seekg(pos + 3 * sizeof(int));
file1.read((char*)&number, sizeof(int));
std::cout << number << std::endl;

return 0;

}
25 字符串流
25.1 问题
C++引入了ostringstream、istringstream、stringstream这三个类。istringstream类用于执行串流的输入操作,ostringstream类用于执行串流的输出操作,strstream类同时可以支持串流的输入输出操作。

25.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:字符串流

代码如下:

#include
#include
int main(int argc, const char * argv[])
{

std::string is ("1234 56.78 ABCD");
std::istringstream iss (is);
int i;
double d;
std::string s;
iss >> i >> d >> s;
std::cout << i << "," << d << "," << s << std::endl;

std::ostringstream oss;
oss << 1234 << ' ' << 56.78 << ' ' << "ABCD";
std::string os = oss.str ();
std::cout << os << std::endl;

return 0;

}
上述代码中,以下代码:

std::string is ("1234 56.78 ABCD");
std::istringstream iss (is);

定义了一个输入字符串流类std::istringstream的对象iss,并初始化成字符串is。注意构造字符串流的时候,字符串is中空格会成为字符串参数的内部分界。

上述代码中,以下代码:

iss >> i >> d >> s;

是对int变量i、double变量d和string对象s的输入赋值操作,字符串is中空格分割出了三个变量值。

上述代码中,以下代码:

std::ostringstream oss;
oss << 1234 << ' ' << 56.78 << ' ' << "ABCD";

定义了一个输出字符串流类std::ostringstream的对象oss,然后将int常量1234、double常量56.78和字符串"ABCD"三个数据用空格分开,插入到对象oss中。

上述代码中,以下代码:

std::string os = oss.str ();
std::cout << os << std::endl;

可以通过输出字符串流类std::ostringstream的成员函数str,获取对象oss中保存的字符串流。

25.3 完整代码
本案例的完整代码如下所示:

#include
#include
int main(int argc, const char * argv[])
{

std::string is ("1234 56.78 ABCD");
std::istringstream iss (is);
int i;
double d;
std::string s;
iss >> i >> d >> s;
std::cout << i << "," << d << "," << s << std::endl;

std::ostringstream oss;
oss << 1234 << ' ' << 56.78 << ' ' << "ABCD";
std::string os = oss.str ();
std::cout << os << std::endl;

return 0;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值