输入输出流相关的类
与输入输出流操作相关的类
istream是用于输入的流类,cin就是该类的对象。
ostream是用于输出的流类,cout就是该类的对象。
ifstream是用于从文件读取数据的类。
ofstream是用于向文件写入数据的类。
iostream是既能用于输入,又能用于输出的类。
fstream是既能从文件读取数据,又能向文件写入数据的类。
标准流对象
- 输入流对象:cin 与标准输入设备相连
- 输出流对象:cout 与标准输出设备相连
cerr 与标准错误输出设备相连
clog 与标准错误输出设备相连
缺省情况下
cerr<<“Hello,world”<<endl;
clog<<“Hello,world”<<endl;
和
cout<<“Hello,world”<<endl; 一样
- cin对应于标准输入流,用于从键盘读取数据,也可以被重定向为从文件中读取数据。
- cout对应于标准输出流,用于向屏幕输出数据,也可以被重定向为向文件写入数据。
- cerr对应于标准错误输出流,用于向屏幕输出出错信息,
- clog对应于标准错误输出流,用于向屏幕输出出错信息,
- cerr和clog的区别在于cerr不使用缓冲区,直接向显示器输出信息;而输出到clog中的信息先会被存放在缓冲区,缓冲区满或者刷新时才输出到屏幕。
istream类的成员函数
istream&getline(char*buf,int bufSize);
从输入流中读取bufSize-1个字到缓冲区buf,或读到碰到’\n’为止(哪个先到算哪个)。
istream&getline(char*buf,int bufSize,char delim);
从输入流中读取bufSize-1个字到缓冲区buf,或读到碰到delim字符为止(哪个先到算哪个)。
两个函数都会自动在buf中读入数据的结尾添加’\0’。’\n’或delim都不会被读入buf,但会被从输入流中取走。如果输入流中’\n’或delim之前的字符个数达到或超过了bufSize个,就导致读入出错,其结果就是:虽然本次读入已经完成,但是之后的读入就都会失败了。
可以用if(!cin.getline(...))
判断输入是否结束
istream类的成员函数
bool eof(); 判断输入流是否结束
int peek(); 返回下一个字符,但不从流中去掉
istream&putback(char c); 将字符ch放回输入流
istream&ignore(int nCount=1,int delim=EOF);
从流中删掉最多nCount个字符,遇到EOF时结束。
示例:
#include<iostream>
using namespace std;
int main() {
int x;
char buf[100];
cin >> x;
cin.getline(buf, 90);
cout << buf << endl;
return 0;
}
文件读写
文件和流
可以将顺序文件看作一个有限字符构成的顺序字符流,然后像对cin,cout 一样的读写。
创建文件
–#include<fstream> //包含头文件
– ofstream outFile("clients.dat",ios::out|ios::binary); //创建文件
- “clients.dat”:要创建的文件的名字
- ios::out:文件打开方式
–ios:out:输出到文件,删除原有内容
–ios::app:输出到文件,保留原有内容,总是在尾部添加 - ios::binary: 以二进制文件格式打开文件
也可以先创建ofstream对象,再用open函数打开
ofstream fout;
fout.open("test.out",ios::out|ios::binary);
判断打开是否成功:
if(!fout){
cout<<"File open error!"<<endl;
}
文件名可以给出绝对路径,也可以给出相对路径。没有交代路径信息,就是在当前文件夹下找文件
文件名的绝对路径和相对路径
文件的读写指针
- 对于输入文件,有一个读指针;
- 对于输出文件,有一个写指针;
- 对于输入输出文件,有一个读写指针;
- 标识文件操作的当前位置,该指针在哪里,读写操作就在哪里进行。
ofstream fout("a1.out",ios::app); //以添加方式打开
long location=fout.tellp(); //取得写指针的位置
location=10;
fout.seekp(location); //将写指针移动到第10个字节处
fout.seekp(location,ios::beg); //从头数location
fout.seekp(location,ios::cur); //从当前位置数location
fout.seekp(location,ios::end); //从尾部数location
- location可以为负值
ifstream fin(“a1.in”, ios::ate);
//打开文件,定位文件指针到文件尾
long location = fin.tellg(); //取得读指针的位置
location = 10L;
fin.seekg(location); //将读指针移动到第10个字节处
fin.seekp(location, ios::beg); //从头数location
fin.seekp(location, ios::cur); //从当前位置数location
fin.seekp(location, ios::end); //从尾部数location
- location可以为负值
字符文件读写
- 因为文件流也是流,所以流的成员函数和流操作算子也同样适用于文件流。
- 写一个程序,将文件in.txt里面的整数排序后,输出到out.txt
例如,若in.txt的内容为:
1 234 9 45 6 879
则执行本程序后,生成的out.txt的内容为:
1 6 9 45 234 879
示例:
#include<iostream>
#include<fstream>
#include<vector>
#include<algorithm>
using namespace std;
int main() {
vector<int> v;
ifstream srcFile("in.txt", ios::in); //用二进制打开名为in.txt的文件
ofstream destFile("out.txt", ios::out); //用二进制打开名为out.txt的文件
int x;
while (srcFile >> x) //输入
v.push_back(x);
sort(v.begin(), v.end()); //排序
for (int i = 0; i < v.size(); i++) //输出
destFile << v[i] << " ";
destFile.close(); //关闭文件,否则,写入的文件在内存里,不在硬盘里
srcFile.close();
return 0;
}
二进制文件读写
- 二进制读文件
ifstream和fstream的成员函数:
istream&read(char*s,long n);
将文件读指针指向的地方的n个字节的内容,读入到内存地址s,然后将文件读指针向后移动n字节(以ios::in方式打开文件时,文件读指针开始指向文件开头)。
- 二进制写文件
ofstream和fstream的成员函数:
istream&write(const char*s,long n);
将内存地址s处的n个字节内容,写入文件中写指针指向的位置,然后将文件写指针向后移动n字节(以ios::out方式打开文件时,文件写指针开始指向文件开头,以ios::app方式打开文件时,文件写指针开始指向文件尾部)。
- 在文件中写入和读取一个整数
#include<iostream>
#include<fstream>
using namespace std;
int main() {
ofstream fout("some.dat", ios::out | ios::binary);
int x = 120;
fout.write((const char*)(&x), sizeof(int));
fout.close();
ifstream fin("some.dat", ios::in | ios::binary);
int y;
fin.read((char*)&y, sizeof(int));
fin.close();
cout << y << endl;
return 0;
}
- 从键盘输入几个学生的姓名和成绩,并以二进制文件形式保存
#include<iostream>
#include<fstream>
using namespace std;
struct Student {
char name[20];
int score;
};
int main() {
Student s;
ofstream OutFile("c:\\tmp\\students.dat", ios::out | ios::binary);
while (cin >>s.name >> s.score)
OutFile.write((char*)&s, sizeof(s));
OutFile.close();
return 0;
}
- 将students.dat文件的内容读出并显示
#include<iostream>
#include<fstream>
using namespace std;
struct Student {
char name[20];
int score;
};
int main() {
Student s;
ifstream inFile("students.dat", ios::in | ios::binary);
if (!inFile) {
cout << "error" << endl;
return 0;
}
while (inFile.read((char*)&s, sizeof(s))) {
int readedBytes = inFile.gcount(); //看刚才读了多少字节
cout << s.name << " " << s.score << endl;
}
inFile.close();
return 0;
}
- 将students.dat文件的Jane的名字改成Mike
#include<iostream>
#include<fstream>
using namespace std;
struct Student {
char name[20];
int score;
};
int main() {
Student s;
fstream iofile("c:\\tmp\\students.dat", ios::in | ios::out | ios::binary);
if (!iofile) {
cout << "error";
return 0;
}
iofile.seekp(2 * sizeof(s), ios::beg);
iofile.write("Mike", strlen("Mike") + 1);
iofile.seekg(0, ios::beg); //定位读指针到开头
while (iofile.read((char*)&s, sizeof(s)))
cout << s.name << " " << s.score << endl;
iofile.close();
return 0;
}
文件拷贝程序mycopy示例
/*用法示例:
mycopy src.dat dest.dat
即将src.dat拷贝到dest.dat
如果dest.dat原来就有,则原来的文件会被覆盖*/
#include<iostream>
#include<fstream>
using namespace std;
int main(int argc,char *argv[]) {
//argc为命令行参数个数,argv为命令行参数
if (argc != 3) {
cout << "File name missing!" << endl;
return 0;
}
ifstream inFile(argv[1], ios::binary | ios::in); //打开文件用于读
if (!inFile) {
cout << "Soure file open error." << endl;
return 0;
}
ofstream outFile(argv[2], ios::binary | ios::out); //打开文件用于写
if (!outFile) {
cout << "New file open error." << endl;
inFile.close(); //打开的文件一定要关闭
return 0;
}
char c;
while (inFile.get(c)) //每次读取一个字符
outFile.put(c); //每次写入一个字符
outFile.close();
inFile.close();
return 0;
}
二进制文件和文本文件的区别
Linux,Unix下的换行符号:’\n’(ASCII码:0x0a)
Windows下的换行符号:’\r\n’(ASCII码:0x0d0a) endl就是’\n’
Mac OS下的换行符号:’\r’(ASCII码:0x0d)
导致Linux,Mac OS文本文件在Windows记事本中打开时不换行
- Unix/Linux下打开文件,用不用ios::binary没区别
- Windows下打开文件,如果不用ios::binary,则:
–读取文件时,所有的’\r\n’会被当做一个字符’\n’处理,即少读了一个字节’\r’。
–写入文件时,写入单独的’\n’时,系统自动在前面加一个’\r’,即多写了一个’\r’。
函数模板和类函数
函数模板
- 交换两个整型变量的值的Swap函数:
void Swap(int &x,int &y){
int tmp=x;
x=y;
y=tmp;
- 交换两个double型变量的值的Swap函数:
void Swap(double &x,double &y){
double tmp=x;
x=y;
y=tmp;
求不同类型的变量就要写不同的Swap函数,太麻烦了,能否只写一个Swap,就能交换各种类型的变量?
- 用函数模板解决:
template<class 类型参数1, class 类型参数2, ......>
返回值类型 模板名(形参表)
{
函数体
};
template<class T>
void Swap(T& x, T& y)
{
T tmp = x;
x = y;
y = tmp;
}
int main(){
int n=1,m=2;
Swap(n,m); //编译器自动生成void Swap(int &,int &)函数
double f=1.2,g=2.3;
Swap(f,g); //编译器自动生成void Swap(double &,double &)函数
return 0;
}
- 函数模板中可以有不止一个类型参数:
template<class T1,class T2>
T2 print(T1 arg1, T2 arg2) {
cout << arg1 << " " << arg2 << endl;
return arg2;
}
- 求数组最大元素的MaxElement函数模板:
template<class T>
T MaxElement(T a[], int size) //size是数组元素个数
{
T tmpMax = a[0];
for (int i = 1; i < size; ++i)
if (tmpMax < a[i])
tmpMax = a[i];
return tmpMax;
}
- 不通过参数实例化函数模板:
#include<iostream>
using namespace std;
template<class T>
T Inc(T n) {
return 1 + n;
}
int main() {
cout << Inc<double>(4) / 2 << endl;
return 0;
}
函数模板的重载
- 函数模板可以重载,只要它们的形参表或类型参数表不同即可:
template<class T1,class T2>
void print(T1 arg1, T2 arg2) {
cout << arg1 << " " << arg2 << endl;
}
template<class T>
void print(T arg1, T arg2) {
cout << arg1 << " " << arg2 << endl;
}
template<class T, class T2>
void print(T arg1, T arg2) {
cout << arg1 << " " << arg2 << endl;
}
函数模板和函数次序
在有多个函数和函数模板名字相同的情况下,编译器如下处理一条函数调用语句
- 先找参数完全匹配的普通函数(非由模板实例化而得的函数)。
- 再找参数完全匹配的模板函数。
- 再找实参数经过自动类型转换后能够匹配的普通函数。
- 上面的都找不到,则报错。
#include<iostream>
using namespace std;
template<class T>
T Max(T a, T b) {
cout << "TemplateMax" << endl;
return 0;
}
template<class T,class T2>
T Max(T a, T2 b) {
cout << "TemplateMax2" << endl;
return 0;
}
double Max(double a, double b) {
cout << "MyMax" << endl;
return 0;
}
int main() {
int i = 4, j = 5;
Max(1.2, 3.4); //输出MyMax
Max(i, j); //输出TemplateMax
Max(1.2, 3); //输出TemplateMax2
return 0;
}
- 匹配模板函数时,不进行类型自动转换:
template<class T>
T myFunction(T arg1,T arg2){
cout << arg1 << " " << arg2 << "\n";
return arg1;
}
......
myFunction(5, 7); //ok:replace T with int
myFunction(5.8, 8.4); //ok:replace T with double
myFunction(5, 8.4); //error,no matching function for call to 'myFunction(int,double)'
函数模板示例:Map
#include<iostream>
using namespace std;
template<class T,class Pred>
void Map(T s, T e, T x, Pred op) {
for (; s != e;++s,++x) {
*x = op(*s);
}
}
int Cube(int x) { return x * x * x; }
double Square(double x) { return x * x; }
int a[5] = { 1,2,3,4,5 }, b[5];
double d[5] = { 1.1,2.1,3.1,4.1,5.1 }, c[5];
int main() {
Map(a, a + 5, b, Square);
for (int i = 0; i < 5; ++i)
cout << b[i] << ",";
cout << endl;
Map(a, a + 5, b, Cube);
for (int i = 0; i < 5; ++i)
cout << b[i] << ",";
cout << endl;
Map(d, d + 5, c, Square);
for (int i = 0; i < 5; ++i)
cout << c[i] << ",";
cout << endl;
return 0;
}
// 输出:
// 1, 4, 9, 16, 25,
// 1, 8, 27, 64, 125,
// 1.21, 4.41, 9.61, 16.81, 26.01,
类模板
类模板–问题的提出
-
为了多快好省地定义出一批相似的类,可以定义类模板,然后由类模板生成不同的类
-
数组是一种常见的数据类型,元素可以是:
–整数
–浮点数
–字符串
–… -
考虑一个数组类,需要提供的基本操作
–len():查看数组的长度
–getElement(int index):获取其中的一个元素
–setElement(int index):对其中的一个元素进行赋值
–…
类模板的定义
template<typename 类型参数1, typename 类型参数2, ......> //类型参数表
//typename可以是class,同样适用于函数模板
class 类模板名 {
成员函数和成员变量
};
- 类模板里成员函数的写法:
template<class 类型参数1, class 类型参数2, ......> //类型参数表
返回值类型 类模板名<类型参数名列表>::成员函数名(参数表){
......
}
- 用类模板定义对象的写法:
类模板名<真实类型参数表>对象名(构造函数实参表);
类模板示例:Pair类模板
template<class T1,class T2>
class Pair {
public:
T1 key; //关键字
T2 value; //值
Pair(T1 k, T2 v) :key(k), value(v) { };
bool operator<(const Pair<T1, T2>& p)const;
};
template<class T1, class T2>
bool Pair<T1, T2>::operator<(const Pair<T1, T2>& p)const {
//Pair的成员函数 operator <
return key < p.key;
}
int main() {
Pair<string, int>student("Tom", 19);
//实例化出一个类Pair<string,int>
cout << student.key << " " << student.value;
return 0;
}
// 输出:
// Tom 19
用类模板定义对象
编译器由类模板生成类的过程叫类模板的实例化。由类模板实例化得到的类,叫模板类。
- 同一个类模板的两个模板类是不兼容的
Pair<string, int>* p;
Pair<string, double>a;
p = &a; //wrong
函数模板作为类模板成员
#include<iostream>
using namespace std;
template<class T>
class A {
public:
template<class T2>
void Func(T2 t) { cout << t; } //成员函数模板
};
int main() {
A<int>a;
a.Func('K'); //成员函数模板Func被实例化
a.Func("Hello"); //成员函数模板Func再次被实例化
return 0;
}
// 输出:KHello
类模板与非类型参数
- 类模板的"<类型参数表>"中可以出现非类型参数:
#include<iostream>
using namespace std;
template<class T,int size>
class CArray{
T array[size];
public:
void Print() {
for (int i = 0; i < size; ++i)
cout << array[i] << endl;
}
};
CArray<double, 40>a2;
CArray<int, 50>a3; //a2和a3属于不同的类
类模板与派生、友元和静态成员变量
类模板与继承
- 类模板从类模板派生
- 类模板从模板类派生
- 类模板从普通类派生
- 普通类从模板类派生
类模板从类模板派生
template<class T1,class T2>
class A {
T1 v1; T2 v2;
};
template<class T1,class T2>
class B :public A<T2, T1> {
T1 v3; T2 v4;
};
template<class T>
class C :public B<T, T> {
T v5;
};
int main() {
B<int, double>obj1;
C<int>obj2;
return 0;
}
通过B<int, double>
这个类的名字实例化出来了两个类:
class B <int, double>:public A<double,int> {
int v3; double v4;
};
class A < double,int> {
double v1; int v2;
};
类模板从模板类派生
template<class T1, class T2>
class A {
T1 v1; T2 v2;
};
template<class T>
class B :public A<int, double> {
T v;
};
int main() {
B<char>obj1; //自动生成两个模板类:A<int,double>和B<char>
return 0;
}
类模板从普通类派生
class A {
int v1;
};
template<class T>
class B :public A{ //所有从B实例化得到的类,都以A为基类
T v;
};
int main() {
B<char>obj1;
return 0;
}
普通类从模板类派生
template<class T>
class A {
T v1;
int n;
};
class B :public A<int>{
double v;
};
int main() {
B obj1;
return 0;
}
类模板与友元
- 函数、类、类的成员函数作为类模板的友元
- 函数模板作为类模板的友元
- 函数模板作为类的友元
- 类模板作为类模板的友元
函数、类、类的成员函数作为类模板的友元
void Func1(){ }
class A{ };
class B {
public:
void Func() { }
};
template<class T>
class Tmp1 {
friend void Func1();
friend class A;
friend void B::Func();
}; //任何从Tmp1实例化来的类,都有以上三个友元
函数模板作为类模板的友元
#include<iostream>
#include<string>
using namespace std;
template<class T1,class T2>
class Pair {
private:
T1 key; //关键字
T2 value; //值
public:
Pair(T1 k, T2 v) :key(k), value(v) { };
bool operator<(const Pair<T1, T2>& p)const;
template <class T3, class T4>
friend ostream& operator<<(ostream& o, const Pair<T3, T4>& p);
};
template<class T1,class T2>
bool Pair<T1, T2>::operator<(const Pair<T1, T2>& p)const {
//“小”的意思就是关键字小
return key < p.key;
}
template<class T1,class T2>
ostream& operator<<(ostream& o, const Pair<T1, T2>& p) {
o << "(" << p.key << "," << p.value << ")";
return o;
}
int main() {
Pair<string, int>student("Tom", 29);
Pair<int, double>obj(12, 3.14);
cout << student << " " << obj;
return 0;
}
// 输出:
// (Tom, 29) (12, 3.14)
任意从
template<class T1,class T2>
ostream& operator<<(ostream& o, const Pair<T1, T2>& p)
生成的函数,都是任意Pair模板类的友元。
函数模板作为类的友元
#include<iostream>
using namespace std;
class A{
int v;
public:
A(int n):v(n){ }
template <class T>
friend void Print(const T &p); //函数的调用要用&
};
template<class T>
void Print(const T& p) {
cout << p.v;
}
int main() {
A a(4);
Print(a);
return 0;
}
// 输出:
// 4
所有从
template<class T>
void Print(const T& p)
生成的函数,都成为A的友元;
但是自己写的函数
void Print(int a){ }
不会成为A的友元。
类模板作为类模板的友元
#include<iostream>
using namespace std;
template<class T>
class B {
T v;
public:
B(T n):v(n){ }
template<class T2>
friend class A;
};
template<class T>
class A{
public:
void Func() {
B<int>o(10); //v=10;
cout << o.v << endl;
}
};
int main() {
A <double>a;
a.Func();
return 0;
}
//运行顺序:main()->A->B->A
// 输出:
// 10
A<double>
类,成了B<int>
类的友元。任何从A模板实例化出来的类,都是任何B实例化出来的类的友元。
类模板与static静态成员
类模板中可以定义静态成员,那么从该类模板实例化得到的所有类,都包含同样的静态成员。
#include<iostream>
using namespace std;
template<class T>
class A{
private:
static int count;
public:
A() { count++; }
~A() { count--; };
A(A&) { count++; }
static void PrintCount() { cout << count << endl; }
};
template<>int A<int>::count = 0; //第一个int是静态成员变量的类型,A<int>是类的名字,初始化可略
template<>int A<double>::count = 0; //静态成员的声明
int main() {
A <int>ia;
A<double>da;
ia.PrintCount();
da.PrintCount();
return 0;
}
// 输出:
// 1
// 1