Cpp_复习整理_2_模板、重载、输入输出流

书接上回

本篇将会总结一下C++中的模板以及重载两大模块,以及输入输出流

模板

函数模板

为什么要定义模板?

众嗦粥汁,C++是一种强类型语言,对于变量的类型必须要显式指出。那么就会存在不灵活的问题。

1
2
3
4
5
6
7
8
9
10
//举个例子,求一个数绝对值的函数,而数值的类型可以有int,float,double等,那么就需要定义不同的函数
int Abs(int a){
return a>0?a:-a;
}
float Abs(float a){
return a>0?a:-a;
}
double Abs(double a){
return a>0?a:-a;
}

不难发现,上面这三个函数,除了函数类型参数类型,其余部分完全一样,但是却需要三段几乎相同的函数代码。

所以为了节省代码量,更为了提高代码的适用性C++提供了模板template功能。我们可以定义如下的模板:

1
2
3
4
template<typename T>	//或者templata<class T>
T Abs(T a){
return a>0?a:-a;
}

函数模板的声明通过关键字template开始,该关键字之后是使用尖括号< >括起来的类参/形参表

  • 每个类型参数之前有typename或者class
  • 至少存在一个类参。
  • 类参可以代表的是内部类型,也可以是用户自定义的结构体类型或者类型。

这样在函数调用中,模板就能根据所给的实参类型(如int),进行实例化(如得到int Abs(int a))。完成对应的操作。

*关键字classtypename的区别

上一篇我们总结了C++的使用,我们清楚class作用是定义一个类。

而在引入了模板之后,class作为表示类型参数的标识符,作为模板声明最初的形式:

1
template<class T>......

但是后来人们发现class在某些情形下会导致二义性错误。引入了typename这个关键字,所以模板声明也可以是:

1
template<typename T>......

至此,在模板定义的语法中classtypename的作用完全一样

但是,typename不仅仅在模板定义中起作用,typename的另外一个作用为:使用嵌套依赖类型。下方举例:

1
2
3
4
5
6
7
8
9
10
11
12
class obj{
public:
typedef int myType;
......
};
template<class T>
void fun(T myObj)
{
typedef typename T::myType myNewType; //重点在这里
myNewType ans = myObj.data;
return;
}

这里的typename就是在告诉C++编译器,在其后方是一个类型的名称,而不是T类型(此例子中假定为obj类)中的一个静态成员。如果没有typename,编译器就无法得知T::myType的确切含义,存在二义性错误,编译就不会通过。

类模板

1
2
3
4
5
6
7
8
9
10
//举例
template <class T1,class T2,int nMax>
class CType{
public:
T1 m_t1Data;
T2 m_t2Data[nMax];
CType();
~CType();
T1 fun(T1 v1);
};

这是一个简单的类模板,具有两个类参以及一个形参。形参不一定要在类参的后方。

有了模板还要对它进行实例化来使用。诸如:

1
CType<int,double,50> myObj;

这样CType类中的m_t1Datam_t2Data[nMax]就被分别实例化成了int型和double型数组。并且nMax的值为50

类模板中还存在一个函数声明fun,那么有了声明,肯定还要有定义,模板里的函数要怎么定义呢?

1
2
3
4
template <class T1,class T2,int nMax>
T1 CType<T1,T2,nMax>::fun(T1 v1){
...
}
  1. ++类模板的成员函数起始于template,结束于函数体的右花括号}

  2. CType<T1,T2,nMax>::是类模板的“变通的类域分辨符”

    变通的类域分辨符的尖括号中,类参需要去掉class/typename,而形参需要去掉int之类的类型。次序个数要对应不变。

    当模板被实例化,T1,T2,nMax明确之时就是这个“变通的类域分辨符”固定之时。

注意

由于类模板只有在实例化的时候才被确定,所以有些明显的概念错误可能逃过编译器的检查。因此在建立类模板的时候,最好事先进行特定类的具体编程,然后再选择若干类名作为类型参量,进行“特定类”到“通用类”的升级处理,即反向设计通用类

重载机制

多态性

程序中总有时候,同一个符号或者名字在不同的情况下具有不同的解释,这就是多态性polymorphism

而在面向对象的程序设计语言中,由程序员设计的多态性有两种最基本的形式:1)编译时多态性;2)运行时多态性

  • 编译时多态性:在程序编译时便可确定下来的多态性,通过重载机制获得,包括函数重载运算符重载
  • 运行时多态性:必须等到程序动态运行时才可以确定的多态性,主要通过继承+动态绑定获得。

函数重载

  • C++语言中,只要在声明函数原型时形参的个数或者对应位置形参的类型不同,那么两个或更多的函数就可以共用一个名字,在同一作用域中允许多个函数共用一个函数名。
  • C语言中,不支持重载,所以每个函数必须具有唯一的名字
    • C语言中有三个求绝对值的函数:abs()labs()fabs(),分别用于处理intlongdouble的绝对值。

函数重载的错误情形

编译程序根据实际参数个数以及它们对应的类型,选择调用哪个重载函数,因此重载函数必须在形参个数以及类型上区分

  • 函数类型无法区分重载函数!

    1
    2
    int get_value(int index);		//get_value(3);
    double get_value(int index); //程序无法确定调用哪个函数。
  • 给类型用typedef取别名,无法区分重载函数。

    1
    2
    3
    typedef double MONEY;
    double calculate(double income);
    MONEY calculate(MONEY income); //MONEY和double在编译器眼中是完全一样的
  • 引用&不能作为重载函数的判定依据。

    1
    2
    void fun(int value);
    double fun(int& value); //出现二义性
  • 函数的缺省参数,可以理解为函数重载的一种简化形式。如:

    1
    int fun(int a,int b=2,int c=3);
    • 注意:缺省参数的右边必须 全是 或 没有 参数!即所有缺省参数都得在形参列表的右部。

    可对应三种不同的调用方式:

    1
    2
    3
    int fun(int a,int b, int c);//fun(10,11,12);
    int fun(int a,int b); //fun(10,11);等价于fun(10,11,3);
    int fun(int a); //fun(10);等价于fun(10,2,3);

    但是缺省参数也可能导致二义性。如:

    1
    2
    3
    4
    5
    void fun(int i);
    void fun(int i,int j=10);

    fun(3,4);//正常调用fun(int,int)
    fun(5);//ERROR,无法确定调用哪个

    对于fun(5),编译器可以认为调用了单参的fun(5),也可以认为调用了带缺省参数的fun(5,10),因而出现了二义性错误。

    所以:如果重载函数的参数设置有默认值,则必须保证使用参数默认值后的调用形式与该函数的其它重载形式不相同


  • 若想为相同的函数原型提供不同的实现方案,则无法通过函数重载完成。

函数重载的注意点

  • 二义性:是致命的,使得编译程序无法生成目标代码。造成二义性的主要原因:1)隐式类型转换2)缺省参数

    • 编译程序选择重载函数的规则:绑定次序是

      • 最优:精确匹配
      • 次优:对实参的类型向高类型转换后的匹配(不丢失精度
      • 最次:对实参的类型向低类型及相容类型转换后的匹配(可以运行,但是会报Warning
      1
      int -> unsigned int -> long -> unsigned long -> float -> double -> long double
  • 函数重载不可滥用,不适当的重载会降低程序的可读性,只有当函数实现的语义非常相近时才会使用重载。

  • 类定义中也可以使用函数重载,如构造函数赋值运算符的重载等。

运算符重载

运算符,如果一些情况下可以直接代替函数名,并用运算符的书写形式调用函数。那么这种形式将更容易理解。

例如,在基本数据类型中+表示整数或浮点数的加法。如果用户设定了自定义类型诸如复数,矩阵等,为了使相应的加法运算也能进行,就需要用到运算符重载

C++允许运算符的语义由程序员重新定义,实质上运算符重载就是函数重载。分为两种形式:1)类成员函数2)普通函数

类内重载

1
2
3
函数类型 类名::operator 运算符符号(参数表){
......
}
  • 参数表中的参数,受到所重载的运算符约束,数量不可随意指定

    类内重载的话,参数列表中的参数个数要比要求个数少一,类内调用的函数因为默认访问的存在,已经隐式具备了一个*this的实参;因此对于二元运算符,只需要显式传递一个右操作数

使用类内重载的运算符

本质上是由左操作数调用类内的运算符函数,因此,左操作数必须是该类的对象,即保证“左操作数”类型正确。

类外重载

1
2
3
函数类型 operator 运算符符号(参数表){
......
}

注意

  1. 不与对象联合使用,则无默认的this指针,因此,必须显式传递与运算符要求的运算数个数相同的参数。

  2. 对于类的数据封装性,很多时候数据是不对外部函数开放的,因此有两种解决方法:

    1)开放数据为public

    2)(更好的选择)将函数声明为类的友元,如在类定义中加入如下类似的声明:

    1
    friend obj operator+(obj left,obj right);

运算符重载的规则

  1. 绝大部分的运算符可以重载,只有下列运算符不能重载,它们是:

    1). 成员访问运算符

    2).*->* 成员指针访问运算符

    3):: 域运算符

    4)sizeof 长度运算符

    5)?: 条件运算符

    6)# 预处理符号

  2. 在可重载的运算符中= (赋值运算符),[] (下标运算符),() (函数调用运算符),-> (通过指针访问类成员的运算符)必须定义为类的成员函数,不能定义为友元或普通函数。

    << (流输出),>> (流输入)则必须定义到类外,不能定义为类的成员函数。(因为这两个运算符的主调对象都是io对象)

  3. 不能创建新的运算符,只能对已有的运算符进行重载。

  4. 重载不能改变运算符的操作数个数

  5. 重载不能改变运算符的优先级别结合性

  6. 重载的运算符函数的参数列表中不能有默认参数,否则就改变了参数的个数,这与4.矛盾。

  7. 重载运算符必须和类类型或者枚举类型一起使用,其参数至少有一个是类对象(或类对象的引用)。

    • 即参数不能全是C++的标准类型,防止用户修改用于标准类型数据的运算符的性质。

      1
      2
      3
      int operator +(int a,double b){
      return a+b;
      }
  8. 用于类对象的运算符一般必须重载,但=(赋值运算符) 和 &(地址运算符) 不必用户重载。

  9. 习惯上建议将重载运算符的功能与逻辑,类似于该运算符作用于标准类型数据时所实现的功能。

  10. 运算符重载函数可以是类的成员函数,也可以是类的友元函数,还可以是既非类的成员函数也不是友元函数的普通函数。

前置++与后置++的重载

1
2
3
4
5
6
7
8
9
10
11
12
13
//引例
#include<iostream>
using namespace std;
int main(){
int a=0;
(a++)++; //ERROR C2105:"++"需要左值
++(a++); //ERROR C2105:"++"需要左值
a++ = 1; //ERROR C2106:"="左操作数必须为左值
(++a)++; //OK
++(++a); //OK
++a = 1; //OK
return 0;
}

为什么会出现这样的情况呢?

我们知道前置++后置++虽然最终结果都是使对象加一,但是实际的作用过程并不相同。

前置++将对象的本身作为左值返回,而后置++将对象运算前的副本作为右值返回。

左值与右值

左值lvalue:指那些求值结果为对象或函数的表达式。一个表示对象的非常量左值可以作为赋值运算符的左侧运算对象

右值rvalue:是指一种表达+式,其结果是值而非所在的位置

那么问题就解决了,上方代码段中,出现ERROR的代码都是在进行了a++之后,对结果进行了其它要求左值的运算,而a++返回的是一个右值,所以报错“需要左值”。

两者区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Age{
int i;
public:
Age& operator++() //前置++
{
++i;
return *this;
}
const Age operator++(int)//后置++
{
Age tmp = *this;
++(*this); //使用的就是重载过的前置++,目的是避免自增代码的重复
return tmp;
}
Age& operator=(int i)
{
this->i=i; //Age类中的数据成员i与形参i同名,所以需要显式的写出this指针。
return *this;
}
};
  1. 可以看出前置++和后置++的返回值类型不同

    前置++的返回类型是Age&,后置++的返回类型为const Age

    这意味着,前置++返回的是左值,后置++返回的是右值

    a++的类型是const Age,自然不能进行前置++、后置++、赋值等操作;而++a的类型是Age&,可以进行这些操作。

    • 问题1:a++的返回类型为什么要是const对象呢?
      • 如果不是const对象,那么(a++)++就可以通过编译,但是运行结果却违背了直觉。虽然看似进行了两次自增,但是a实际上只增加了1,因为第二次自增作用在第一次自增执行中产生的一个对象
      • 自定义类型的操作符重载,应该与内置类型保持行为一致
    • 问题2:++a的返回类型为什么是引用呢?
      • 与内置行为保持一致,为了能和其它运算符组合计算。
  2. 还能看出形参不同

    前置++ 没有形参,而后置++ 有一个int形参。但是形参并没有被用到,这个形参只是为了绕过语法的限制

    因为没有这个int形参,前置和后置的重载便无法区分(函数类型不能作为重载的区分)

  3. 实现代码不同。

    前置只是简单的完成增加操作之后返回对象的引用即可。

    后置则需要先设置一个对象的拷贝,再进行自增,最后返回的是拷贝

  4. 效率不同

    后置自增会产生一个临时对象,会造成一次构造函数和析构函数的额外开销,显然后置效率低于前置

    • 因此:除非必须,均建议使用前置自增
    1
    for(int i=0;i<10;++i)//使用++i而不是i++

自减类同自增。

类型转换运算符的重载

C++支持类型的转换,在内部类型中,支持按照类似的写法进行强制类型转换:

1
2
int myNum=5;
double myDouble = (double)myNum;

有时,我们也需要将类类型转换成内部类型或者转换成另一个类类型

那么,我们就需要重载类型转换运算符

转换成内部类型

1
2
3
4
5
//例如:
operator double(){
...
return ...; //return的是double类型,即要转换的类型
}

转换成类类型

在上一篇中便已经提到,单参的构造函数可以实现类型转换的功能。

过去看看~

注意

  1. 类型转换函数名的前面,不能指定函数类型
  2. 类的类型转换函数没有参数(其实是隐含了this指针)
  3. 类型转换函数的返回类型,由函数名中指定的类型名来确定
  4. 类的类型转换函数只能作为成员函数,因为转换的主体是本类的对象。不能作为友元函数或普通函数。
  5. 类型转换函数与运算符重载函数相似,都是用关键字operator开头,区别在于被重载的是类型名
  6. “转换构造函数”和“类型转换函数”有一个共同的功能:当需要的时候,编译系统会自动调用这些函数,建立一个无名的临时对象(或临时变量)

输入输出流

如果一开始学习的是C语言,那么对于scanfprintf一定不陌生吧,它们分别用于数据的输入输出

而到了C++的学习时,输入输出变成了使用cincout,配合上运算符>><<,看起来和C的输入输出方式大相径庭,但是不管这些,使用流输入输出,很方便不是吗?可以根据变量的类型智能读入数据。如果搞过竞赛的话,相信碰到过超大数据导致TLE的情况,这时候就有可能cincout太慢了,于是就会使用下面两句代码来提升读取速度:

1
2
std:;ios::sync_with_stdio(false);
std::cin.tie(0);

这两句可以让cincout的效率比肩scanfprintf,但是怎么实现的姑且不提。

我们主要讲讲C++中的三种输入输出流:

1)标准I/O - iostream

系统指定的标准设备的输入和输出。即从键盘输入数据,输出到显示器屏幕

C++的输入输出流,是指由若干字节组成的字节序列,这些字节中的数据按顺序从一个对象传送到另一个对象,就像水流,电流一样,信息从目的端的流动。

  • 输入操作时:字节流从输入设备(如键盘,磁盘)流向内存。
  • 输出操作时:字节流从内存流向输出设备(如屏幕,打印机,磁盘等)。

流中的内容可以是ASCII字符、二进制形式的数据、图形图像、数字音频视频或其它形式的信息。

输入输出流被定义为。使用cin/cout时,需要包含头文件iostream,分别对应标准输入流标准输出流。此外还有cerrclog,都对应于标准错误输出流

标准输入流

在头文件istream中定义了输入流对象:cin

1)>>从流中提取数据时,会过滤掉输入流中的不可见字符,如空格回车制表符Tab等。

2)只有在输入完数据,按回车键后,该行数据才被送入输入缓冲区,形成输入流,提取运算符>>才能从中提取数据。

  • 缓冲区例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include<iostream>
    using namespace std;
    int main(){
    char str[20];
    char a,b;
    cout << "Give me a string:";
    cin >> a; //输入123456789
    cout << "a = " << a << endl;
    cin.get(b); //从输入流中获取一个字符
    cout << "b = " << b << endl;
    cin.getline(str,5); //
    cout << "str = " << str << endl;
    return 0;
    }

    输出结果为:

    1
    2
    3
    4
    Give me a string:123456789
    a = 1
    b = 2
    str = 3456

    输入流的一些函数

    • get函数,有多种重载

      1. int get()

        从流中读取一个字符,返回该字符的ASCII码值。如果读到文件末尾,返回EOF,表示end of file,在控制台中输入Ctrl+Z表示EOF

      2. istream& get(char& ch)

        从输入流中读取一个字符并存放在ch中,返回cin本身。

        上例中流中本来为123456789,get之后a的值为1,流中还剩下23456789

      3. istream& get(char* buffer,int n,char ch='\n')

        从输入流中读取n-1个字符,或者遇到第三个参数指定的终止符,并由系统自动补上一个\0字符,存放到buffer指针指向的内存空间中。

        注意:终止符并不会被读入到buffer中,仍会留在输入流缓冲区中。

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        //例子
        #include<iostream>
        using namespace std;
        int main(){
        char str[20],c;
        cout << "Enter a sentence:" << endl;
        cin.get(str,10,'5');
        cin.get(c);
        cout << "str[] = " << str << endl;
        cout << "c = " << c << endl;
        return 0;
        }

        输入为1234567890,输出为:

        1
        2
        3
        4
        Enter a sentence:
        1234567890
        str[] = 1234
        c = 5

        可见5仍在输入流中,直到被c读取。

    • getline函数(istream类的成员函数)

      istream& getline(char* buffer,int n,char ch='\n',有3个参数

      1. 第一个参数为由谁获取数据。

      2. 第二个参数为读取长度,但是实际读取到str中的字符长度会-1,因为要在字符串末尾留出一个位置添加\0

      3. 第三个参数是截止符号,即读取到该字符时,不管是否已经到读取长度,均停止读取。该参数可以缺省,默认值是\n

        注意:

        1. getlineget的区别是getline遇到结束符会将该结束符从缓冲区中丢弃!
        2. buffersize最好与目标数组大小一致!目标容量大于buffersize会导致空间浪费,而目标容量小于buffersize则是致命的,会爆数组
    • getline函数(全局函数)

      getline(cin,string串,'\n') ,用于读取整行数据,并且第二个参数为string类对象。第三个参数为可缺省的结束符,缺省值为'\n'

    • ignore函数

      istream& ignore(int n=1,int delim=EOF);

      作用:跳过输入流中的n个字符,或在遇到指定的终止字符时提前结束(会跳过包括delim在内的若干字符)。

      在使用时,因为ignore两个参数都具有默认值,所以实际效果,cin.ignore()cin.get()并无二致。

    • read函数

      istream& read(char* buffer,int n);

      从输入流中读取n个字符,注意是n个,并不会在字符串末尾补’\0’。没有结束符,必须读入n个字符,如果读取字符不足会继续等待输入。

3)需要注意保证从流中读取的数据能正常运行。

Tip:

cin本质上与其它C++变量一样,cin也是一个变量名,它是一个istream类型的对象。

而变量名通常代表着一段内存区域,cin也映射到一段内存区域(也就是输入缓冲区)。

简单理解为键盘输入的信息先存放在cin中,再被流提取运算符>>提取到内存中。

标准输出流

在头文件ostream中定义了输出流对象:cout

1)使用它不必考虑数据是什么类型,系统会自动判断数据的类型,根据其类型选择调用与之匹配的的运算符重载函数。

2)输出流也开辟了一个缓冲区,用来存放流中的数据,当缓冲区满时输出到屏幕。

  • endl\n的区别

    这两个在输出的效果都是换行,但两者本质上是不同的。

    • endl实际上是一个函数模板,作为一个IO操作符,当输出缓冲区中插入一个endl,则不论缓冲区是否已满,都将立即输出流中的所有数据,然后插入一个换行符,并且此时缓冲区已被清空。
  • \n只是一个字符,与其它ASCII字符无异。

输出流的一些函数

  • put函数,输出单个字符
  • write函数,输出指定的字符串
  • tellp函数,用于获取当前输出流指针的位置
  • seekp函数,用于设置输出流指针的位置

*流操纵算子

流操纵算子 作用
dec 以十进制输出整数
hex 以八进制输出整数
oct 以十六进制输出整数
fixed 以普通小数的形式输出浮点数
scientific 以科学计数法形式输出浮点数
left 左对齐,即宽度不足时将填充字符添加到右边
right 右对齐,即宽度不足时将填充字符添加到左边
setbase(b) 设置输出整数时的进制,b=8、10或16
setw(w) 指定输出宽度为w或者读入w个字符(该算子所起的作用是一次性的
setfill(c) 设置填充字符,即宽度不足时用c填充,默认是空格
setprecision(n) 设置浮点数的精度为n 在与fixed算子配合使用的情况下,n表示小数位数。
setiosflags(flag) 设置某一个标志的值为1
resetiosflags(flag) 设置某一个标志的值为0

以下是setiosflags(flag)resetiosflags(flag)flag可以为的值:

标志 作用
ios::left 输出数据在本域宽范围内向左对齐
ios::right 输出数据在本域宽范围内向右对齐
ios::internal 数值的符号位在域宽内向左对齐,数值向右对齐,中间由填充字符填充
ios::dec 设置整数的基数为10
ios::oct 设置整数的基数为8
ios::hex 设置的整数的基数为16
ios::showbase 强制输出整数的基数(八进制以0开头,十六进制以0x开头)
ios::showpoint 强制输出浮点数的小数点和尾数0
ios::uppercase 在以科学计数法格式E和以十六进制输出字母时以大写表示
ios::showpos 对正数显示”+”号
ios::scientific 浮点数以科学计数法格式输出
ios::fixed 浮点数以定点格式(小数形式)输出
ios::unitbuf 每次输出后刷新所有的流
ios::stdio 每次输出后清除stdoutstderr

重载>><<运算符

我们已经学习了C++中的类,那对类的数据的输入输出是借助类的成员函数实现的,比如我们手上有一个复数类Complex类:

1
2
3
4
int r,i;
cin>>r>>i;
Complex c1(r,i);
c1.display();

但是这不符合使用习惯,我们更希望能写成这样的形式,就跟对普通变量的输入输出一样:

1
2
3
Complex c1;
cin>>c1;
cout<<c2;c

那么我们就可以对<<>>运算符进行重载。

<<来举个例子:

1
cout<<x<<y;

它有两个含义:

  1. <<是一个二目运算符,进行了两次调用。第一次左操作数为cout,右操作数为x;第二次左操作数为cout,右操作数为y
  2. 这里是对象cout调用了ostream类的成员函数operator<<()

据此,当我们对cout进行重载的时候应该可以得出以下结论:

  1. 不能将运算符的重载函数定义为Complex类的成员函数,须定义为外部函数。如果访问了私有成员,还必须声明为友元。这是因为<<运算符是由ostream对象cout来调用的。
  2. 重载函数的函数类型应当为引用,目的是能够连续调用,实现连续输出。
  3. 重载函数的两个参数都应当为引用,并且要被输出的对象应当为常引用,防止数据被篡改。

所以,我们就能写出符合需求的重载函数:

1
2
3
4
5
6
7
8
ostream& operator<<(ostream& out,const Complex& obj){		//左操作数为ostream的对象的引用
out<<obj.real; //右操作数为要被输出的对象
if(obj.imag>0)
out<<'+'<<obj.imag<<'i';
else if(obj.imag<0)
out<<obj.imag<<'i';
return out; //返回ostream对象本身,以便后续的调用
}

而运算符>>与之类似,只不过是ostream变成了istream

2)文件I/O - fstream

文件I/O不再对系统默认的I/O设备进行输入输出,因此<fstream>头文件中没有预先定义好的类似cincout的对象,需要用户自行定义文件流对象finfout等),作为文件I/O流的载体。用户可以通过如下的语句,来定义文件对象:

1
2
3
fstream myFile;
ifstream fin;
ofstream fout;

这样创建一个文件输入对象和一个文件输出对象,但是两者都没有绑定到对应的文件,因此无法完成任何输入输出操作。我们可以通过fin.open("in.txt");,即open函数来绑定一个文件,或者在定义对象时使用构造函数直接绑定一个文件ofstream fout("out.txt");

当我们绑定了一个文件之后,还要指定文件的使用方式是诸如只读、只写、既读又写、在文件末尾添加数据、以文本方式使用、以二进制方式使用等等。我们可以使用诸如myFile("out.txt",ios::out | ios::app);等语句,该语句表示myFile绑定了out.txt,并以“输出”和“追加”的方式使用 。以下是模式标记表格:

模式标记 适用对象 作用
ios::in ifstream fstream 打开文件用于读取数据,如果文件不存在,则打开失败。 是ifstreamofstream对象的默认值
ios::out ofstream fstream 打开文件用于读取数据,如果文件不存在,则新建文件。 如果文件已经存在,则打开时会清空文件的内容,除非带有ios::in或者ios::appifstreamofstream对象的默认值
ios::ate ifstream 打开一个文件,并将文件的读指针指向文件末尾。如果文件不存在, 则打开失败。并且,文件打开时,不会清空文件。 ateat the end的缩写
ios::app ofstream fstream 打开文件,将文件写指针置于文件的EOF处,用于在文件末尾添加数据,如果 文件不存在,则新建该文件。如果顺利打开,则不清空文件。
ios::trunc ofstream 打开文件时会清空内部存储的所有数据(如果文件已存在,则先删除该文件) 单独使用时与ios::out相同
ios::binary ifstream ofstream fstream 二进制的方式打开文件,缺省时以文本文件打开

使用完文件对象之后或者要换一个文件进行绑定,必须要先解除原来绑定的文件,通过close函数进行:

1
2
3
fin.close();
fout.close();
myFile.close();

seekp、seekg和tellp、tellg

  • seekp 输出文件中写指针移到指定的位置

    原型为ostream& seekp(int offset,int mode);

    将指针从mode处开始,移动offset个字节。

    mode为文件读写指针的设置模式,有三种选项:

    mode标志 描述
    ios:beg (默认值) 文件头开始计算偏移量(offset=0为文件开头,offset为非负数) 即ostream& seekp(int offset);
    ios::end 文件末尾开始计算偏移量(offset=0为文件尾,offset为0或负数)
    ios::cur 当前位置开始计算偏移量(offset>0往文件尾部移动,offset<0 往文件头部移动)
  • seekg 输入文件中指针移到指定的位置

    原型为istream& seekg(int offset,int mode);

    作用效果与seekp一直,只不过是读指针

  • tellp返回输出指针的当前位置

  • tellg返回输入指针的当前位置

Tips:g->get;p->put。

3)串I/O - stringstream

暂不做讨论。


Cpp_复习整理_2_模板、重载、输入输出流
https://bao-gai-yu.github.io/2022/07/16/Cpp-复习整理-2/
作者
宝盖于-BaoGaiYu
发布于
2022年7月16日
许可协议