输入输出概述
-
输入输出: 程序与外部设备交换信息。
可分为基于控制台的I/O,基于文件的I/O,基于字符串的I/O。
-
数据流: C++ 把输入输出看作一个数据流。
- 输入流:外围设备流向内存的数据
- 输出流:内存流向外围设备的数据
-
C++ 中相关的头文件如下表所示。
输入输出缓冲
-
I/O对象: C++ 程序不能直接与输入输出设备交换信息,而需要通过一个对象实现。对象是输入输出设备在程序中的代理。
每个 I/O 对象管理一个缓冲区,用于储存读写得数据。
-
格式:
>>
从输入缓冲区取数据存入变量;<<
将数据放入输出缓冲区。 -
缓冲区刷新:
- 程序正常结束:清空所有输出缓冲区,真正输出内容。
- 缓冲区满:在写入下一个值之前,会刷新缓冲区。
- 强制刷新:
endl
显式地刷新缓冲区,unitbuf
操作符设置流的内部状态清空缓冲区。 - 关联输入输出流:在读输入流时,将刷新其关联的输出缓冲区。
整数和实数的输入输出
设置整数基数
hex
十六进制oct
八进制dec
十进制setbase(进制数)
,只允许 8、10、16 进制。- 头文件
iomanip
- 流的基数值只有被显式更改才会发生变化,否则会一直沿用原有基数。
设置浮点数精度
- 精度: 指实型数的有效位数。
- 设置方法:
- 流操作符:
setprecision(位数)
- 流的成员函数:
precision(位数)
- 流操作符:
- 一旦设置了精度,将影响所有输出的浮点数的精度,直到下一个设置精度的操作为止。
设置域宽
- 精度: 指实型数的有效位数。
- 设置方法:
- 流操作符:
setprecision(位数)
- 流的成员函数:
precision(位数)
- 流操作符:
- 一旦设置了精度,将影响所有输出的浮点数的精度,直到下一个设置精度的操作为止。
文件的概念
-
文件: 是驻留在外储存器上,具有标识名的一组信息的集合,用来永久保存数据。
-
与文件相关的概念: 数据项(字段),记录,文件,数据库。
-
外存: 程序可以直接访问内存中的数据,但不能直接访问外存。
外存和程序之间的 I/O 需要经过输入输出缓冲。外存到缓冲区由操作系统控制,缓冲区到内存由程序控制。
-
磁道:存储信息的场所。
-
柱面:不同盘片的同一磁道。
-
扇区:磁道划分成扇区,储存一个数据块。
-
流式文件
- 文件和流: C++ 把每个文件都看成一个有序的字节流,每个文件以 EOF (end-of-file marker) 结束
- ASCII 文件: 也被称为文本文件,将文件中的每个字节解释成一个字符的ASCII值,可以直接显示在显示器上
- 二进制文件: 将文件中的每个字节仅看成是一个二进制比特串,由程序解释比特串的含义
文件的访问过程
-
访问文件步骤: 定义流对象,打开文件,操作文件数据,关闭文件。
-
定义流对象:
ifstream
、ofstream
、fstream
-
打开和关闭文件: 成员函数
open
以及close
-
文件打开模式:
-
检查是否打开成功: 打开失败值为 false、
fail
函数、is_open
函数。
文件实例(上)
-
图书馆的书目管理系统
- 功能: 初始化系统、添加书、借书、还书、显示书库信息
- 每本书需要记录的信息
- 馆藏号(整型数):要求自动生成
- 书名(最长20个字符的字符串)
- 借书标记。借书标记中记录的是借书者的借书证号,假设也是整型数。
-
文件设计:文件头存书的数量,然后顺序存放书本数据信息
-
book类设计
- 数据成员:馆藏号、书名、借书标记
- 成员函数:构造函数、借书还书、显示书的详细信息
-
book类实现
Book::Book(const char *s, int totalNo)
{
no = totalNo;
borrowed = 0;
strcpy(name, s);
}
void Book::borrow(int readerNo)
{
if (borrowed != 0)
cerr << "本书已被借,不能重复借\n";
else
borrowed = readerNo;
}
void Book::Return()
{
if (borrowed == 0)
cerr << "本书没有被借,不能还\n";
else
borrowed = 0;
}
void Book::display() const
{
cout << setw(10) << no << setw(20) << name << setw(10) << borrowed << endl;
}
文件实例(下)
-
系统分解
- 每个功能用一个函数实现
- Main函数:显示菜单,根据用户的选择调用相应的函数
-
Main函数
int main()
{
int selector;
while (true)
{
cout << "0 -- 退出\n";
cout << "1 -- 初始化文件\n";
cout << "2 -- 添加书\n";
cout << "3 -- 借书\n";
cout << "4 -- 还书\n";
cout << "5 -- 显示所有书目信息\n";
cout << "请选择(0-5):";
cin >> selector;
if (selector == 0)
break;
switch (selector)
{
case 1:
initialize();
break;
case 2:
addBook();
break;
case 3:
borrowBook();
break;
case 4:
returnBook();
break;
case 5:
displayBook();
break;
}
}
return 0;
}
Initialize的实现
void initialize()
{
ofstream outfile("book");
outfile.close();
}
addBook的实现
void addBook()
{
char ch[20];
Book *bp;
ofstream outfile("book", ofstream::app | ofstream::ate | ofstream::binary);
long int no = outfile.tellp() / sizeof(Book) + 1 ;
cout << "请输入书名:";
cin >> ch;
bp = new Book(ch, no);
outfile.write( reinterpret_cast<const char *>(bp), sizeof(*bp));
delete bp;
outfile.close();
}
borrowBook的实现
void borrowBook()
{
int bookNo, readerNo;
fstream iofile("book", fstream::binary);
Book bk;
cout << "请输入书号和读者号:"; cin >> bookNo >> readerNo;
iofile.seekg((bookNo - 1) * sizeof(Book));
iofile.read( reinterpret_cast<char *> (&bk), sizeof(Book) );
bk.borrow(readerNo);
iofile.seekp((bookNo - 1) * sizeof(Book));
iofile.write(reinterpret_cast<const char *>(&bk), sizeof(Book));
iofile.close();
}
returnBook的实现
void returnBook()
{
int bookNo;
fstream iofile("book", ofstream::binary );
Book bk;
cout << "请输入书号:";
cin >> bookNo ;
iofile.seekg((bookNo - 1) * sizeof(Book));
iofile.read(reinterpret_cast<char *> (&bk), sizeof(Book));
bk.Return();
iofile.seekp((bookNo - 1) * sizeof(Book));
iofile.write( reinterpret_cast<const char *>(&bk), sizeof(Book));
iofile.close();
}
displayBook的实现
void displayBook()
{
ifstream infile("book", ofstream::binary);
Book bk;
infile.read(reinterpret_cast<char *> (&bk), sizeof(Book));
while (!infile.eof()) {
bk.display();
infile.read(reinterpret_cast<char *> (&bk), sizeof(Book));
}
infile.close();
}
异常处理
-
异常: 程序执行过程中发生的一些不希望发生的事。
-
传统解决方法: 在错误发生处就地处理。
- 好处:程序员阅读代码可以直接看到错误情况。
- 问题:因为错误污染代码使理解和维护变得困难。
-
异常处理机制:分为异常抛出,以及异常捕获和处理两个部分。
异常抛出
-
作用: 发生异常情况时将包含出错信息的对象抛出当前环境,发送给更大的环境得到更好的处理。
-
格式:
throw
异常对象; -
过程: 生成并初始化
throw
对象的副本,传回调用它的函数,退出函数,回收所有局部对象。类似函数的return
过程。
捕获异常
- catch 捕获异常:
catch (<捕获的异常类型><可选对象>)
{
//异常处理代码
}
注意:
-
捕获以类型为标志,是否省略对象取决于需不需要在处理过程中引用该对象。
-
catch (...)
可以捕获任意类型的异常。 -
抛出-捕获格式:
try
{
//可能抛出异常的代码
}
catch (类型1 对象1)
{
//处理情况1的代码
}
catch (类型2 对象2)
{
//处理情况2的代码
}
#include <iostream>
#include <cmath>
using namespace std;
class DivideByZeroException
{
private:
const char *message;
public:
DivideByZeroException() : message("case 1") {}
const char *what() { return message; }
};
int main()
{
int a, b;
try
{
cin >> a >> b;
if (b == 0)
{
throw DivideByZeroException();
}
cout << a / b;
}
catch (const char *)
{
cout << "case 2\n";
}
catch (DivideByZeroException)
{
cout << "case 3\n";
}
catch (...)
{
cout << "case 4\n";
}
return 0;
}
输入:100 0
正确答案:
case 3
2.
以下是一段带异常处理的除法计算器,请按照规范的异常处理过程补全它。(空1)
#include <iostream>
#include <cmath>
using namespace std;
class DivideByZeroException
{
private:
_____(1)_____;
public:
DivideByZeroException() : message("attempt to divide by zero") {}
const char *what() { return message; }
};
double Div(int x, int y)
{
if (_____(2) _____)
_____(3)_____;
return 1.0 * x / y;
}
int main()
{
int number1, number2;
double result;
cout << "Enter two integers (end-of-file to end): ";
while (cin >> number1 >> number2)
{
try
{
result = Div(number1, number2);
cout << "The quotient is: " << result << endl;
}
catch (_____(4) _____ ex)
{
cout << "Exception occurred: " << _____(5) _____ << '\n';
}
cout << "\nEnter two integers (end-of-file to end): ";
}
cout << endl;
}
答案解释
const char* message
答案解释
y == 0
4.
(3)
答案解释
throw DivideByZeroException();
5.
(2)
答案解释
DivideByZeroException
6.
(5)
答案解释
ex.what()
异常规格说明
传统函数声明:
void f(); //函数可以抛出任何异常
带异常规格的函数声明:
void f() throw(); //函数不会有异常抛出。
void f() throw(toobig, toosmall, divzero); //函数会抛出toobig, toosmall, divzero三种异常。
通用格式为
返回类型 函数名(形式参数表) throw(异常类型);
-
C++11 的改进
- 摒弃异常规格声明。
- 关键字
noexcept
,void f() noexcept;
表示函数不抛出。 - 运算符
noexcept
判断表达式是否会抛异常。
1.
读程序,写结果。
#include <iostream>
using namespace std;
class up{};
class down{};
void f(int i) throw(up, down);
int main()
{
for (int i = 1; i <= 10; ++i)
try
{
f(i);
}
catch (up)
{
cout << "up catched" << endl;
}
catch (down)
{
cout << "down catched" << endl;
}
return 0;
}
void f(int i) throw(up, down)
{
if (i % 3 == 0)
throw up();
if (i % 4 == 0)
throw down();
}
正确答案:
up catched
down catched
up catched
down catched
up catched
类模板
-
类模板的定义格式:
template <模板的形式参数表> class 类名{...};
-
模板的形式参数可以是类型形参(用关键字class或typename开始,后面是形式参数名)和非类型形参。
-
在类定义外面定义成员函数格式:
template <模板的形式参数表> 返回类型 类名<各形式参数>::函数名(形式参数表)
填空题
1.
类模板A的声明如下。若要在类定义外定义f()函数,需要如何实现?请给出除函数体外的代码(函数体内用/*函数体*/代替)。
template <class T, typename U>
class A
{
public:
T a, b;
U f(const T &a, const T &b);
};
答案解释
template <class T, typename U>
U A::f()
{
/*函数体*/
}
类模板的实例化
-
类模板对象的实例化格式:
类模板名<模板的实际参数表> 对象表;
填空题
1.
stl中的std::map的定义是
template < class Key, // map::key_type
class T, // map::mapped_type
class Compare = less<Key>, // map::key_compare
class Alloc = allocator<pair<const Key,T> > // map::allocator_type
> class map;
以下实例化正确的有:(按照大写字母从小到大排列无空格填写)
A. std::map<std::string,int>
B. std::map<"abcd",int>
C. std::map<int,std::string>
D. std::map<7,'c'>
正确答案:
AC