C++面向对象07【输入输出和模板】

摘要:1.输入输出流相关的类
2.流操纵算子控制输出的格式
3.文件读写
4.二进制文件读写
5.函数模板
6.类模板
7.类模板与派生、友元、静态成员变量

1.输入输出流相关的类
istream是用于输入的流类,cin是该类的对象
ostream是用于输出的流类,cout是该类的对象
ifstream是用于从文件读取数据的类
ofstream是用于向文件写入数据的类
iostream是既能用于输入,又能用于输出的类
fstream是既能从文件读取数据,又能向文件写入数据的类

cin和cout对应于标准输入输出流,可以被重定向为从文件中读取数据/向文件写入数据
cerr和clog对应于标准错误输出流
将标准输出重定向到test,txt文件:freopen(“test.txt”,“w”,stdout);//stdout为标准输出设备,缺省时为屏幕
将标准输入重定向为从t.txt文件中读取数据:freopen(“t.txt”,“r”,stdin);//stdin缺省时为键盘

判断输入流结束的方法:
int x;
while(cin>>x){ //(cin>>x)的返回值类型虽然是istream &,即就是cin,但在类中可以将它强制转换为布尔类型
……
}
return 0;

  • 从文件输入时,读到文件尾,输入流就算结束
  • 从键盘输入时,在单独一行敲ctrl+z代表输入流结束

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(…)) 判断输入是否结束

bool eof(); 判断输入流是否结束
int peek(); 返回下一个字符,但不从流中去掉.
istream & putback(char c); 将字符ch放回输入流
istream & ignore( int nCount = 1, int delim = EOF );
从流中删掉最多nCount个字符,遇到EOF时结束。

2.流操纵算子控制输出的格式
流操纵算子作用
• 整数流的基数:流操纵算子dec,oct,hex,setbase
• 浮点数的精度(precision,setprecision)
• 设置域宽(setw,width)
• 用户自定义的流操纵算子
使用流操纵算子需要 #include

整数流的基数:流操纵算子 dec,oct,hex(十进制、八进制、十六进制)
int n = 10;
cout << n << endl;
cout << hex << n << “\n”
<< dec << n << “\n”
<< oct << n << endl;
输出结果:
10 0
a a
10
12

控制浮点数精度的流操纵算子
precision, setprecision
 precision是成员函数,其调用方式为:
cout.precision(5);
 setprecision 是流操作算子,其调用方式为:
cout << setprecision(5); // 可以连续输出
它们的功能相同。
指定输出浮点数的有效位数(非定点方式【小数点不一定在个位数的右边】输出时)
指定输出浮点数的小数点后的有效位数(定点方式输出时)
定点方式:小数点必须出现在个位数后面

设置域宽的流操纵算子
• 设置域宽(setw,width)
两者功能相同,一个是成员函数,另一个是流操作
算子,调用方式不同:
cin >> setw(4); 或者 cin.width(5);
cout << setw(4); 或者 cout.width(5);
宽度设置有效性是一次性的,在每次读入和输出之前都要设置宽度

全面例子(可学习查看):
#include
#include
using namespace std;
int main() {
int n = 141;
//1) 分别以十六进制、十进制、八进制先后输出 n
cout << "1) " << hex << n << " " << dec << n << " " << oct << n << endl;
double x = 1234567.89,y = 12.34567;
//2) 保留5位有效数字
cout << "2) " << setprecision(5) << x << " " << y << " " << endl;
//3) 保留小数点后面5位
cout << "3) " << fixed << setprecision(5) << x << " " << y << endl ;
//4) 科学计数法输出,且保留小数点后面5位
cout << "4) " << scientific << setprecision(5) <

  1. 8d 141 215
  2. 1.2346e+006 12.346
  3. 1234567.89000 12.34567
  4. 1.23457e+006 1.23457e+001
    //5) 非负数要显示正号,输出宽度为12字符,宽度不足则用’‘填补
    cout << "5) " << showpos << fixed << setw(12) << setfill(’
    ’) << 12.1
    << endl;
    //6) 非负数不显示正号,输出宽度为12字符,宽度不足则右边用填充字符填充
    cout << "6) " << noshowpos << setw(12) << left << 12.1 << endl;
    //7) 输出宽度为12字符,宽度不足则左边用填充字符填充
    cout << "7) " << setw(12) << right << 12.1 << endl;
    //8) 宽度不足时,负号和数值分列左右,中间用填充字符填充
    cout << "8) " << setw(12) << internal << -12.1 << endl;
    cout << "9) " << 12.1 << endl;
    return 0;
    }
  5. ***+12.10000
  6. 12.10000****
  7. ****12.10000
  8. -***12.10000
  9. 12.10000

用户自定义流操纵算子
例:(返回值类型和参数类型是一定的)
ostream &tab(ostream &output){
return output << ‘\t’;
}
cout << “aa” << tab << “bb” << endl;
输出:aa bb
因为 iostream 里对 << 进行了重载(成员函数)
ostream & operator
<<( ostream & ( * p ) ( ostream & ) ) ;
该函数内部会调用p所指向的函数,且以 *this 作为参数
hex 、dec 、oct 都是函数

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.seekg(location,ios::beg); //从头数location fin.seekg(location,ios::cur); //从当前位置数location fin.seekg(location,ios::end); //从尾部数location
• location 可以为负值

显式关闭文件
ifstream fin(“test.dat”,ios::in); fin.close();
ofstream fout(“test.dat”,ios::out); fout.close();
字符文件读写
因为文件流也是流,所以流的成员函数和流操作算子也同样适 用于文件流。
写一个程序,将文件 in.txt 里面的整数排序后,输出到 out.txt
例如,若in.txt 的内容为: 1 234 9 45 6 879
则执行本程序后,生成的out.txt的内容为:1 6 9 45 234 879

4.二进制文件读写
 二进制读文件:
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方式打开文件时,文件写 指针开始指向文件尾部 ) 。

5.函数模板
问题:能否只写一个函数,就能交换各种类型的变量
回答:用函数模板解决
template
返回值类型 模板名(形参表)
{
函数体
};
template
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; }

不通过参数实例化函数模板
#include
using namespace std;
template
T Inc(T n) { return 1 + n; }
int main()
{
cout << Inc(4)/2; //输出2.5。让编译器生成一个把T 换成double类型的函数,如果不这样写的话,编译器会根据参数4自动生成整形类的,结果就不是2.5了
return 0;
}

函数模板可以重载,只要他们的形参表类型参数表不同即可

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

  1. 先找参数完全匹配的普通函数(非由模板实例化而得的函数)。
  2. 再找参数完全匹配的模板函数
  3. 再找实参数经过自动类型转换后能够匹配的普通函数
  4. 上面的都找不到,则报错。

函数模板示例:Map
#include
using namespace std;
template
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;

template
void Map(T s, T e, T x, Pred op){
for(; s != e; ++s,++x) {
*x = op(*s); }
}
int a[5] = {1,2,3,4,5}, b[5];
Map(a,a+5,b,Square);
//实例化出以下函数:
void Map(int 星 s, int 星 e, int 星 x, double ( *op)(double)) //与函数Square相匹配的函数指针类型
{ for(; s != e; ++s,++x) { *x = op(*s); } }

6.类模板
为了多快好省地定义出一批相似的类,可以定义类模板,然后由类模板生成不同的类
template
class 类模板名
{
成员函数和成员变量
};//有时class可被替换成typename

类模板里成员函数的写法:
template//类型参数表
返回值类型 类模板名 <类型参数名列表>::成员函数名(参数表)
{
……
}
类模板定义对象的写法:
类模板名<真实类型参数表>对象名(构造函数实参表);

类模板示例: Pair类模板
template
class Pair {
public:
T1 key; //关键字
T2 value; //值
Pair(T1 k,T2 v):key(k),value(v) { };
bool operator < ( const Pair & p) const;
};
template
bool Pair::operator < ( const Pair & p) const //Pair的成员函数 operator <
{ return key < p.key; }
int main() {
Pair student(“Tom”,19); //实例化出一个类Pair
cout << student.key << " " << student.value;
return 0;
}

编译器由类模板生成类的过程叫类模板的实例化
由类模板实例化得到的类,叫模板类
同一个类模板的两个模板类是不兼容的
Pair * p;
Pair a;
p = & a; //wrong

函数模板可以作为类模板的成员

类模板的类型参数表中可以出现非类型参数:
template //size是非类型参数
class CArray{
T array[size];
public:
void Print( ) {
for( int i = 0;i < size; ++i) cout << array[i] << endl;
}
};
CArray a2;
CArray a3; //a2和a3属于不同的类

7.类模板与派生、友元、静态成员变量
类模板与派生
• 类模板从类模板派生
• 类模板从模板类派生
• 类模板从普通类派生
• 普通类从模板类派生

类模板与友元
• 函数、类、类的成员函数作为类模板的友元
• 函数模板作为类模板的友元
• 函数模板作为类的友元
• 类模板作为类模板的友元

类模板与静态成员变量
类模板中可以定义静态成员,那么从该类模板实例化得到的所有类, 都包含同样的静态成员。
template<> int A::count = 0;
template<> int A::count = 0;
int main() {
A ia;
A da;
ia.PrintCount();
da.PrintCount();
return 0; }

你可能感兴趣的:(C++面向对象07【输入输出和模板】)