C++编程思想杂记(②4章 输入输出流)

为什么使用输入输出流而不使用C的文件I/O函数?


第一,用户可以直接操作FILE指针,并且需要调用close函数来关闭文件,使用输入输出流可以安全的打开文件并且不依赖用户调用close来关闭它。


第二,C中的可变参数列表函数(printf)中包含运行时解释程序,运行时解释程序是一段代码在运行时可以解析字符串。但是即使要使用解释程序的一部分功能,解释程序的所有部分都会被加载到可执行程序中,并且解释发生在运行时,这就无法免除运行时开销,也不能在编译时进行错误检查。最后,类似printf的函数不具备扩展性,只支持内置类型,如果想通过重载,重载函数的参数列表必须不同而对于printf函数来说,它的类型信息隐藏在可变参数列表和格式串中。


C++提供了如下几种类用于输入输出。用于文件输入输出的ifstream,ofsream,fstream。用于标准C++输入输出的istringstream,ostringstream,stringstream。这些流其实是模板的特化。


按行输入,三种方法流变量成员函数get(),成员函数getline(),定义在<string>中的getline()函数。

其中get()有3个重载版本


#include <iostream>
using namespace std;

int main()
{
    char buf[100] = {0};
    char c;
    
    char *p = &c;
    cin.get();                                              //读取一个输入
    cin.get(c);                                            //读取一个输入到c
    cin.get(buf,2,'\n');                               //读取直到遇到'\n',这里需要注意的是,第二个参数表示的是给予存储的空间大小,流成员函数get函数和getline函数都会在结果缓冲区末尾

//存储一个零,所以如果第二个参数是1,就不会读取数据到buf

    cin.get(buf,2,'\n');                                //get()不会从输入流中提取界定符,所以如果再次调用get()还会遇到同一个界定符
    cout<<buf;
    cout<<*p;
    return 0;
}


针对get()不会从输入流中提取界定符,所以如果再次调用get()还会遇到同一个界定符

#include <iostream>
using namespace std;

int main()
{
    char buf[100] = {0};
    char c;
    
    char *p = &c;
    cin.get();
    cin.get(c);
    cin.get(buf,5,'c');
    cin.get(buf,5,'c');
    cout<<buf;
    cout<<*p;
    return 0;
}

假设输入是1234c12

在第一次get语句后,buf中为3,4,第二次get是没有读取的!这使得会在buf中存储一个零,replace了原先3的位置,在cout输出时就没有任何输出了

如果是getline

#include <iostream>
using namespace std;

int main()
{
    char buf[100] = {0};
    char c;
    cin.getline(buf,5,'c');
    cin.getline(buf,5,'c');
    cout<<buf;
    return 0;
}

输入是1234c12c

这样第一个getline之后,buf中是1234,第二次getline和get不同,是有读取的,使得buf变为12'\0'4,在cout时,输出为12

getline还有一个重载版本

#include <iostream>
using namespace std;

int main()
{
    char buf[100] = {0};
    char c;
    cin.getline(buf,5);
    cin.getline(buf,5,'c');
    cout<<buf;
    return 0;
}

这里第一个getline最多读取4个数据,加上一个'\0',如果输入是12345,则buf中只有1234。

如果第一个getline能读取的数据多于4个,则第二次getline不会读取任何数据,即结果变为‘\0'234,cout输出为空

如果输入是1234,回车2345,则最后buf的结果是2345。


对于定义在string中的getline

有两个重载版本

#include <iostream>
#include <string>
using namespace std;

int main()
{

    string abc;
    std::getline(cin,abc,'c');
    
    cout<<abc;
    return 0;
}

第二种就是多了一个结束符

有趣的地方是string类型变量创建的时候,它的capacity就被设置成了15,当字符串长度超过15的时候,就会扩大容量到31,但是之后的变动我搞不懂,先变成47,然后是70,然后是105


顺带一提,vector的初始size和capacity都是1,随后在DevC++中以1,2,4,8.....2^n方式增长,而在VS中按照1,2,3,4,6,,9,13,19......这种大约是1.5倍



类ios_base自类ios派生而来,定义了4个标志位来测试流的状态,goodbit,failbit(I/O失败,非法数据,输入结束),eofbit(输入结束,指人为Ctrl+Z),badbit(物理致命错误,流不能再使用)

流的good函数返回为true标志goodbit位设置,正常,如果failbit/badbit被设置,则流的fail函数返回true,badbit设置,流的bad函数返回true。


清理标志位使用的是clear函数(failbit&&badbit)


operator>>即提取符,返回的是它的流参数。


ifstream定义如下:typefdef basic_ifstream<char> ifstream


打开文件模式选择:

ios::in 
ios::out 
ios::app打开一个仅用于追加的输入文件
ios::ate打开一个已存在的文件,文件指针在末尾
ios::trunc默认打开方式,截断旧文件





输入输出流的字符的传递复制。使用了streambuf类,每一个输入输出流对象都包含streambuf类型一个指针。同时提供一个rdbuf函数来访问streambuf对象,该对象与流对象通过<<连接就可以实现两个流的字符传递复制。

#include <iostream>
#include <string>
#include <fstream>


using namespace std;

int main()
{
    fstream fs("test2.cpp");
    cout<<fs.rdbuf();
    return 0;
}

这样test2.cpp的全部内容都会被复制到cout然后输出到标准输出,流缓冲区streambuf不能被复制,因为没有拷贝构造函数。

就是说:

streambuf fs = *fs.rdbuf(); NO

如果想从流中读取,还可以使用read,参数是目的地址和读取字节数目(char)。



这里提到streambuf类,这个类的引用还可以用于之前提到的get函数的重载形式

_Myt& __CLR_OR_THIS_CALL get(_Mysb& _Strbuf)和

_Myt& __CLR_OR_THIS_CALL get(_Mysb& _Strbuf, _Elem _Delim)

两者的区别是,第一种的默认结束符就是'\n'

这个_CLR_OR_THIS_CALL 是一个空的宏定义,只是作为一个标识。

这个重载形式接收一个_Mysb类型引用,返回一个_Myt类型引用。

这个_Myt类型即basic_istream<_Elem, _Traits>即isfream。_Mysb类型即basic_streambuf<_Elem, _Traits>即streambuf。



下面来看这个程序:

#include <iostream>
#include <string>
#include <fstream>
#include <vector>

using namespace std;

int main()
{
    fstream fs("test2.cpp");
    streambuf &buf =*cout.rdbuf();

    while(!fs.get(buf).eof())
    {
        if(fs.fail())
            fs.clear();
        cout<<char(fs.get());
    }
    
    return 0;
}

这个函数可以将test2.cpp的全部内容发送到cout标准输出,注意这里的get函数就是前面提到的参数为streambuf引用的重载。

    _Myt& __CLR_OR_THIS_CALL get(_Mysb& _Strbuf, _Elem _Delim)
        {    // extract up to delimiter and insert into stream buffer
        ios_base::iostate _State = ios_base::goodbit;
        _Chcount = 0;
        const sentry _Ok(*this, true);

        if (_Ok)
            {    // state okay, use facet to extract
            _TRY_IO_BEGIN
            int_type _Meta = _Myios::rdbuf()->sgetc();

            for (; ; _Meta = _Myios::rdbuf()->snextc())
                if (_Traits::eq_int_type(_Traits::eof(), _Meta))
                    {    // end of file, quit
                    _State |= ios_base::eofbit;
                    break;
                    }
                else
                    {    // got a character, insert it into stream buffer
                    _TRY_BEGIN
                        _Elem _Ch = _Traits::to_char_type(_Meta);
                        if (_Ch == _Delim
                            || _Traits::eq_int_type(_Traits::eof(),
                                _Strbuf.sputc(_Ch)))
                            break;
                    _CATCH_ALL
                        break;
                    _CATCH_END
                    ++_Chcount;
                    }
            _CATCH_IO_END
            }

        if (_Chcount == 0)
            _State |= ios_base::failbit;
        _Myios::setstate(_State);
        return (*this);
        }

从这个重载可以看到,没读入一个字符,计数+1,如果没有读到字符,就会置这个流的标志位为ios_base::failbit

这里的get函数,遇到'\n’会自动停止读取,然后如果不在最后调用get()读取掉结束字符,就会导致死循环,然后为了读出空白行,就需要在读到空白行get函数置标志位为fail时调用clear清空标志位


输入输出流的定位,ostream对象使用seekp().istream对象使用seekg(),注意参数为移动字符数目和方向。方向有ios::beg,ios::cur,ios::end,分别是开始,当前,结束

ofsteam继承自ostream,ifstream继承自istream


字符串输入输出流,头文件sstream,istringstream,ostringstream,istringstream用一个字符串初始化该对象,然后操作和cin类似。


输出流的格式化,操纵算子,分成2种,包含参数的算子和不包含参数的算子,


不包含参数的算子比较有用的有showbase,showpos,skipws,left,right,分别表示显示基数,即如果是16进制,会显示0x,正数前面加正号,跳过输入中的空格,左对齐右边填充字符,右对齐左边填充字符(默认是右对齐)


包含参数的算子在iomanip中定义,主要的有setiosflags,setfill,setBase,setw,setprecision

setiosflags,这个函数以fmtflags类型变量为参数,这个函数的作用类似一些无参数算子,如setiosflags(showbase)==showbase算子,都可以显示基数

fmfflags类型是一个枚举类型。代表0-0xffff中的一些常量。就好比我写一个setiosflags(showbase)实际上和setiosflags(8)一样,都可以显示基数

这里有一个隐式类型转换,setiosflags(static_cast<std::_Iosb<int>::_Fmtflags>(8)),这里这个模板类型中的int是没有意义的........因为它是Dummy,你写个啥正常类都行


setfill设置填充,字符参数,setBase设置基数显示方式,即进制,没找到实现,但是输入除了10,8,16之外都会变成十进制,setw设置输出宽度,setprecision,设置精度


注意setw在输入流控制宽度时,只针对字符串有效,比如输入123.4的字符串,结果是12,但是如果是float则结果是123.4无视了setw



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值