Bootstrap

C++高级编程(8)

八、标准IO库

1.输入输出流类

1)非格式化输入输出

2)put

#include <iostream>
#include <string>
​
using namespace std;
int main()
{
    string str = "123456789";
    for (int i = str.length() - 1; i >= 0; i--) 
    {
        cout.put(str[i]); //从最后一个字符开始输出
    }
    
    cout.put('\n');
    return 0;
}

3)write

#include <iostream>
using namespace std;
​
int main() 
{
    const char * str = "www.cplusplus.com";
    cout.write(str, 4);
    
    return 0;
}

4)格式化输入/输出

  • C++提供了大量的用于执行格式化输入/输出的流操纵符和成员函数。

  • 功能:

    • 打印地址: cout << (void*)a;

    • 整数流的基数:dec、oct、hex和setbase

    • 设置浮点数精度:precision、setprecision

    • 设置域宽:setw、width

  • 注:如果用到了含参数的流操纵算子,则必须包含头文件< iomanip >。

5)<<运算符重载

class Complex
{
    double real,imag;
public:
    Complex( double r=0, double i=0):real(r),imag(i){ };
    friend ostream & operator<<( ostream & os,const Complex & c);
};
​
ostream & operator<<( ostream & os,const Complex & c)
{
    os << c.real << "+" << c.imag << "i";
    return os;
}
​
int main()
{
    Complex c(3, 5);
    int n = 220;
    cout << c << "," << n;
    
    return 0;
}

6)hex、oct、dec和setbase

#include <iostream>
#include <iomanip>
using namespace std;
​
int main()
{
    int number;
    cout << "Enter a decimal number: ";
    cin >> number;
    cout << number << " in hexadecimal is: " << hex<< number << endl;
    cout << dec << number << " in octal is: " << oct << number << endl;
    cout << setbase(10) << number << " in decimal is: " << number << endl;
    
    return 0;
}
​
/*
输出结果:
Enter a decimal
number: 20
20 in hexadecimal is:14
20 in octal is: 24
20 in decimal is: 20
*/

7)浮点精度(precision,setprecision)

#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;
​
int main()
{
    double root2 = sqrt( 2.0 );
    int places;
    
    // use fixed point format
    cout << fixed;
    for ( places = 0; places <= 9;places++ )
    {
        cout.precision( places );
        cout << root2 << endl;
    }
    for ( places = 0; places <= 9; places++ )
    {
        cout << setprecision( places ) <<
        root2 << endl;
    }
    return 0;
}

8)域宽(width,setw)

#include <iostream>
#include <iomanip>
using namespace std;
​
int main()
{
    int widthValue = 4;
    char sentence[ 10 ];
    cout << "Enter a sentence:" << endl;
    cin.width( 5 );
    while ( cin >> sentence )
    {
        cout.width( widthValue++ );
        cout << sentence << endl;
        //cout<<setw(widthValue++) << sentence << endl;
        cin.width( 5 );
    }
    
    return 0;
}

9)用户自定义的流操纵算子

#include <iostream>
using namespace std;
​
ostream& bell( ostream& output )
{
    return output << '\a'; // issue system beep
}
​
ostream& carriageReturn( ostream& output )
{
    return output << '\r'; // issue carriage return
}
​
ostream& tab( ostream& output )
{
    return output << '\t'; // issue tab
}
​
ostream& endLine( ostream& output )
{
    return output << '\n' << flush; // issue endl-like end of line
}
​
int main()
{
    cout << "Testing the tab manipulator:" << endLine<< 'a' << tab << 'b' << tab << 'c' << endLine;
    cout << "Testing the carriageReturn and bell manipulators:"<< endLine << "..........";
    cout << bell; // use bell manipulator
    cout << carriageReturn << "-----" << endLine;
​
    return 0;
}

2.文件操作

  • 临时数据

    • 存储在变量和数组中的数据是临时的,这些数据在程序运行结束后都会消失。

  • 文件

    • 目的:文件用来永久地保存大量的数据。

    • 存储:计算机把文件存储在二级存储设备中(特别是磁盘存储设备)。

1)文件中的数据层次

  • 位(bit): 最小数据项:0和1

  • 字符(character)/字节(byte)

    字符:数字、字母和专门的符号

    字节:0、1序列 (常见的是8位/字节)

    字符用字节表示

  • 域(field):一组有意义的字符

  • 记录(record)

    • 一组相关的域

    • 记录关键字(record key):用于检索

  • 文件(file):一组相关的记录

  • 数据库:一组相关的文件

2)文件和流

  • C++语言把每一个文件都看成一个有序的字节流(把文件看成n个字节)

  • 每一个文件或者以文件结束符(end-of-file marker)结束,或者在特定的字节号处结束

  • 当打开一个文件时,该文件就和某个流关联起来

  • 与这些对象相关联的流提供程序与特定文件或设备之间的通信通道

3)文件输出例子

#include <iostream>
#include <fstream> // file stream
#include <cstdlib>
using namespace std;
​
int main()
{
    ofstream outClientFile( "clients.dat", ios::out );//打开文件
    
    // exit program if unable to create file
    if ( !outClientFile ) // overloaded ! operator
    {
        cerr << "File could not be opened" << endl;
        exit( 1 );
    }
    
    cout << "Enter the account, name, and balance." << endl
    << "Enter end-of-file to end input.\n? ";
    
    int account;
    char name[ 30 ];
    double balance;
​
    // read account, name and balance from cin, then place in file
    while ( cin >> account >> name >> balance )
    {
        outClientFile << account << ' ' << name << ' ' << balance << endl;//写文件
        cout << "? ";
    }
    
    return 0; // ofstream destructor closes file
}
​
/*
输出结果:
Enter the account, name, and balance. Enter end-of-file to end input. ? 100 Jones 24.98
? 200 Doe 345.67
? 300 White 0
? 400 Stone -42.16
? 500 Rich 224.62
? ^z
*/

4)文件访问

  • 定义一个流对象

  • 打开文件

  • 访问文件

  • 关闭文件

(1)测试文件打开是否成功
  • 用重载的ios运算符成员函数operator!确定打开操作是否成功。如果open操作的流将failbit或badbit设置,则这个条件返回非0值(true)。

  • 可能的错误是:

    • 试图打开读取不存在的文件

    • 试图打开读取没有权限的文件

    • 试图打开文件以便写入而磁盘空间不足。

(2)测试文件结束符与不合法输入
  • 另一个重载的ios运算符成员函数operator void将流变成指针,使其测试为0(空指针)或非0(任何其他指针值)。如果failbit或badbit对流进行设置,则返回0(false)。

  • 下列while首部的条件自动调用operator void*成员函数:

    Line 26: while (cin >> account >> name >>balance )只要cin的failbit和badbit都没有设置,则条件保持true。输入文件结束符设置cin的failbit。operator void*函数可以测试输入对象的文件结束符,而不必对输入对象显式调用eof成员函数。

(3)打开输入文件
  • ifstream inClientFile( “clients.dat” , ios::in );

  • 生成ifstream对象inClientFile,并将其与打开以便输入的文件clients.dat相关联。括号中的参数传入ifstream构造函数,打开文件并建立与文件的通信线路。

  • 打开ifstream类对象默认为进行输入,因此下列语句等价:

    ifstream inClientFile( “Clients.dat” );

  • 和ofstream对象一样,ifstream对象也可以生成而不打开特定文件,然后再将用open对象与文件相连接。

//文件输出例子
#include <iostream>
#include <fstream> // file stream
#include <iomanip>
#include <string>
#include <cstdlib>
using namespace std;
​
void outputLine( int, const string, double ); // prototype
​
int main()
{
    // ifstream constructor opens the file
    ifstream inClientFile( "clients.dat", ios::in );
    
    // exit program if ifstream could not open file
    if ( !inClientFile )
    {
        cerr << "File could not be opened" << endl;
        exit( 1 );
    } // end if
    
    int account;
    char name[ 30 ];
    double balance;
    cout << left << setw( 10 ) << "Account" << setw( 13 )<< "Name" << "Balance" << endl << fixed << showpoint;
​
    // display each record in file
    while ( inClientFile >> account >> name >> balance )
    {
        outputLine( account, name, balance );
    }
    return 0; // ifstream destructor closes the file
}
​
    // display single record from file
    void outputLine( int account, const string name, double balance )
    {
    cout << left << setw( 10 ) << account << setw( 13 ) << name<< setw( 7 ) << setprecision( 2 ) << right << balance << endl;
    }

5)文件位置指针

  • 为了按顺序检索文件中的数据,程序通常要从文件的起始位置开始读取数据,然后连续地读取所有的数据,直到找到所需要的数据为止。可能需要反复多次。

  • 文件位置指针( file position pointer):用于指示读写操作所在的下一个字节号;是个整数值,指定文件中离文件开头的相对位置(也称为离文件开头的偏移量)

6)文件指针重新定位

  • istream类和ostream类都提供成员函数,使文件位置指针重新定位

    • istream类的seekg (即“seekget”) :每个istream对象有个get指针,表示文件中下一个输入相距的字节数

    • ostream类的seekp (即“seekput”):每个ostream对象有一个put指针,表示文件中下一个输出相距的字节数

  • seekg和seekp的第一个参数通常为long类型的整数,表示偏移量。第二个参数可以指定寻找方向:

    ios::beg(默认)相对于流的开头定位

    ios::cur相对于流当前位置定位

    ios::end相对于流结尾定位

7)获取文件指针值

  • 成员函数tellg和tellp分别返回get和put指针的当前位置。

下列语句将get文件位置指针值赋给long类型的变量

location。

location = filObject.tellg();

8)更新顺序访问文件

  • 格式化和写入顺序访问文件的数据修改时会有破坏文件中其他数据的危险

  • 例如,如果要把名字“White”改为“Worthington” ,则不是简单地重定义旧的名字。White的记录是以如下形式写入文件中的:

    300 White 0.00

    如果用新的名字从文件中相同的起始位置重写该记录,记录的格式就成为:

    300 Worthington 0.00

  • 因为新的记录长度大于原始记录的长度,所以从“Worthington”的第二个“o”之后的字符将重定义文件中的下一条顺序记录。

  • 出现该问题的原因在于:在使用流插入运算符<<和流读取运算符>>的格式化输入,输出模型中,域的大小是不定的,因而记录的大小也是不定的。例如,7、14、-117、2047和27383都是int类型的值,虽然它们的内部存储占用相同的字节数,因此,格式化输入输出模型通常不用来更新已有的记录。

  • 一种解决方法:将在3000 White 0.00之前的记录复制到一个新的文件中,然后写入新的记录并把300 White 0.00之后的记录复制到新文件中。这种方法要求在更新一条记录时处理文件中的每一条记录。如果文件中一次要更新许多记录,则可以用这种方法。

3.STRING类

  • C++、java等编程语言中的字符串。

  • 在java、C#中,String类是不可变的,对String类的任何改变,都是返回一个新的String类对象。

  • String 对象是 System.Char 对象的有序集合,用于表示字符串。String 对象的值是该有序集合的内容,并且该值是不可变的

为什么需要string?

  • 在内部进行内存分配,从而保证应用程序的健壮性

  • 提供了拷贝构造函数和复制运算符,确保字符串的正确复制

  • 减少程序员创建和操作字符串需要做的工作

  • 提供了增删改查的操作,让程序员可以把精力放在应用程序的主要需求上,而不是字符串的操作细节。

1)string对象的定义和初始化

string s1;
string s2(s1);
string s3("value");
string s4(n,'c');

2)string对象的读写

  • 读入未知数目的string对象

    while(cin>>world)

    cout <<world <<endl;

  • 用getline读取整行

    getline(cin, s);

3)string对象的操作

s.empty();
s.size();
s[n]; //下标操作 范围0~?
s1+s2;
s1=s2;
v1==v2;
!= < <=
> >=

4)string对象中字符的处理

4.异常处理

1)C错误处理机制

  1. 函数返回错误编码

  2. 使用全局变量保存错误编码

  3. 出错时终止程序运行

  • 缺点:

    • 难以统一标准

    • 复杂

    • 构造和析构函数没有返回值

    • 出错就返回,用户接受度低

2)异常处理机制

  • C++异常处理的基本思想是简化程序的错误代码,为程序的健壮性提供了一个标准检测机制:若底层发生问题,则逐级上报,直到有能力处理此异常为止。

  • 异常处理的优势:

  1. 增强程序的健壮性。

  2. 使代码变得更简洁优美,更易维护。

  3. 错误信息更灵活丰富。

  • 缺点:

  1. 性能下降5%~14%。

2. 破坏了程序的结构性。

  1. 完成正确的异常安全的代码,需要付出更大的代价。

3)抛出异常

任何时候,程序在执行中遇到了非正常状况都可以抛出异常异常用throw语句抛出

A *p = new A;

if (p == NULL)

{

throw string(“Out of Memory.”);

}

一旦抛出异常,则程序将在throw语句处跳出

4)捕获异常

异常也由程序员负责捕获

用try{…}catch( ){…}语句来捕获异常

没有捕获的异常将被忽略

try{

// 可能抛出异常的语句

}catch(exceptionType variable){

// 处理异常的语句

}

//捕获异常的例子
#include <iostream>
#include <string>
#include <exception>
using namespace std;
​
int main()
{
    string str = "http://www.cplusplus.com";
    try{
        char ch1 = str[100];
        cout<<ch1<<endl;
        }catch(exception e){
            cout<<"[1]out of bound!"<<endl;
        }
    
    try{
    char ch2 = str.at(100);
    cout<<ch2<<endl;
        }catch(exception &e){ //exception类位于<exception>头文件中
            cout<<"[2]out of bound!"<<endl;
        }
    
    return 0;
}
//捕获任意异常例子
#include <iostream>
using namespace std;
int main( )
{
    int a, b;
    a = 8;
    b = 0;
    try
    {
        if (b == 0)
        {
        throw "Divided by Zero!";
        }else
        {
        double c = a/b;
        cout << c << endl;
        }
    }catch(...)
    {
    cout << "Exception caught!" << endl;
    }
}

5)异常处理的流程

抛出(Throw)--> 检测(Try) --> 捕获(Catch)

6)异常的传递

  • 产生异常之后,程序会立刻跳转

    • 跳出到最近的一层捕获异常的语句

    • 如果当前没有捕获语句或者捕获语句中没有匹配的异常,则程序会跳出当前的函数

  • 在函数的调用处,如果没有捕获住异常,则直接跳转到更高一层的调用者

  • 如果一直没有捕获该异常,C++将会使用默认的异常处理函数

    • p 该处理函数可能会让程序最终跳出main函数并导致程序异常终止

7)C++标准的异常

8)异常的说明

;