C++面向对象——输入输出和模板

输入输出流相关的类

与输入输出流操作相关的类

C++面向对象——输入输出和模板_第1张图片
istream是用于输入的流类,cin就是该类的对象。

ostream是用于输出的流类,cout就是该类的对象。

ifstream是用于从文件读取数据的类。

ofstream是用于向文件写入数据的类。

iostream是既能用于输入,又能用于输出的类。

fstream是既能从文件读取数据,又能向文件写入数据的类。

标准流对象

  • 输入流对象:cin 与标准输入设备相连
  • 输出流对象:cout 与标准输出设备相连
    cerr 与标准错误输出设备相连
    clog 与标准错误输出设备相连

缺省情况下
cerr<<“Hello,world”< clog<<“Hello,world”<
cout<<“Hello,world”<

  • 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
using namespace std;
int main() {
     
	int x;
	char buf[100];
	cin >> x;
	cin.getline(buf, 90);
	cout << buf << endl;
	return 0;
}

在这里插入图片描述
C++面向对象——输入输出和模板_第2张图片

文件读写

文件和流

可以将顺序文件看作一个有限字符构成的顺序字符流,然后像对cin,cout 一样的读写。

创建文件

#include //包含头文件
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;
}

文件名可以给出绝对路径,也可以给出相对路径。没有交代路径信息,就是在当前文件夹下找文件

文件名的绝对路径和相对路径

C++面向对象——输入输出和模板_第3张图片

文件的读写指针

  • 对于输入文件,有一个读指针;
  • 对于输出文件,有一个写指针;
  • 对于输入输出文件,有一个读写指针;
  • 标识文件操作的当前位置,该指针在哪里,读写操作就在哪里进行。
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
#include
#include
#include
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
#include
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
#include
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
#include
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
#include
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
#include
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
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;
}

函数模板和函数次序

在有多个函数和函数模板名字相同的情况下,编译器如下处理一条函数调用语句

  1. 先找参数完全匹配的普通函数(非由模板实例化而得的函数)。
  2. 再找参数完全匹配的模板函数
  3. 再找实参数经过自动类型转换后能够匹配的普通函数
  4. 上面的都找不到,则报错。
#include
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
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
	cout << student.key << " " << student.value;
	return 0;
}
// 输出:
// Tom 19

用类模板定义对象

编译器由类模板生成类的过程叫类模板的实例化。由类模板实例化得到的类,叫模板类。

  • 同一个类模板的两个模板类是不兼容的
Pair<string, int>* p;
Pair<string, double>a;
p = &a; //wrong

函数模板作为类模板成员

#include
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
using namespace std;
template<class Tint 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这个类的名字实例化出来了两个类:

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和B
	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
#include
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
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
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类,成了B类的友元。任何从A模板实例化出来的类,都是任何B实例化出来的类的友元。

类模板与static静态成员

类模板中可以定义静态成员,那么从该类模板实例化得到的所有类,都包含同样的静态成员。

#include
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是类的名字,初始化可略
template<>int A<double>::count = 0; //静态成员的声明
int main() {
     
	A <int>ia;
	A<double>da;
	ia.PrintCount();
	da.PrintCount();
	return 0;
}
// 输出:
// 1
// 1

你可能感兴趣的:(笔记,c++)