file_handle类-如何在类之外使用文件流

file_handle class - how can i use the file stream outside the class

本文关键字:文件 handle file      更新时间:2023-10-16

我想写一个类似的file_handle类

#include <stdio.h>
#include <iostream>
#include <string>
//===========================================================================
class File_handle {
    FILE* p;
    public:
    File_handle(const char* pp, const char* r) {
    p = fopen(pp, r);
    }
    File_handle(const std::string& s, const char* r) {
    p = fopen(s.c_str(), r);
    }
    ~File_handle() {
    if (p) 
    fclose(p);
    }
    // what is this ?????    i found it in stroustrups c++ on page 389
    // ---> Mark A
    operator FILE*() { return p;} 

    // I have tried something like this, but without any success
    // ---> Mark B
    //friend std::ostream& operator<<(std::ostream&, const File_handle&); 
};
//===========================================================================
int main() {
    const std::string filename("test.txt");
    File_handle fh(filename, "w");
    // doesn't work
    // fh << "hellon";
    // *fh << "hellon";
    // this works now
    File_handle fA("A.txt", "w");
    fprintf(fA, "say hello to A.txt");
    File_handle fB("B.txt", "w");
    fputs("say hello to B.txt", fB);
    return 0;
}

我不知道如何在类外使用文件流。我已经尝试过重载operator<<,您可以在上面的代码示例中看到这一点。(表示为:上述代码中的标记B。)

我在Bjarne Stroustrup的书中找到了operator FILE*() { return p; }这行。但是哪一个操作员在这条线路上超载了?(表示为:标记A)

您应该使用std::fstream而不是File_handle类。

但是,如果需要指定一些附加行为,可以从std::fstream派生类。所以这种解决方案可能是这样的:

#include <iostream>
#include <fstream>
class File_handle : public std::fstream
{
public:
    File_handle(const char* filename, _Openmode o) { open(filename, o); }
    ~File_handle() { close(); }
};
int main()
{
    File_handle fh("test.txt", std::ios::out);
    fh << "aa";
    return 0;
}

希望这能有所帮助。

此运算符"operator FILE*(){return p;}"是一个强制转换运算符。只要将File_handle对象传递给只需要File*参数的函数,就会隐式调用它。即使此函数从未被显式接受File_handle对象的版本重载,它也将透明地处理该对象,因为将调用强制转换运算符以将类型File_handl转换为类型File*。现在,您可以将File_handle对象与只知道如何处理File*参数的API一起使用。透明的转换使您的代码看起来干净、熟悉且易于阅读。但有时编译器决定选择与您认为应该使用的转换函数不同的转换函数。关于编译器如何在特定情况下选择转换函数,有很多微妙的规则。如果很多这样的隐式转换都是在幕后发生的,那么可能会出现很难调试的问题——甚至类的编写者也可能不知道里面到底发生了什么。

至于"<<"运算符。。。

我将制作一个实现类似ostream的"<<"运算符的示例类,但我将完全省略标准C++库——它只使用原始C++和C运行时。我省略它是为了将基本C++与库隔离开来。我并不主张或反对制作这样的课程——我只是想清楚地展示一下这个基本技巧。

在File_handle的类定义块中,我添加了几个File_handle::operator<lt;(x) --您需要为每个基本类型,或者至少为您希望处理的每个类型制作不同的版本。诀窍是滥用移位运算符的语义:这些重载都返回对File_handle对象本身的引用。因此,当您以左侧的File_handle实例启动表达式时,每次计算"<<"时,都会调用其中一个重载(根据右侧参数的类型选择),然后它计算为对同一File_handl对象的引用。因此,紧接在第一次求值之后的"<<"运算符将再次使用初始类实例作为其左侧参数,但右侧参数将是下一个操作数,即initail求值右侧的下一个。这就是如何将数量不定的操作数与"<<"链接在一起,所有操作数都由类的单个实例从左到右进行处理。

请原谅这种紧凑的风格。我正努力把整件事都看清楚。此外,当我做这个时,我引用了一个真实的工作类定义,但我并没有真正测试,甚至没有试图编译它…

    class File_handle {
        FILE* fp;
    public:
        File_handle(const char* name, const char* mode) : fp(fopen(name, mode)) {}
        ~File_handle() { Close(); }
        operator FILE*() { return fp; } 
        void Close() { 
            if(fp) { fclose(fp);  fp = 0; }
        }
        File_handle& operator<< (const char* s) { fputs(s, fp); return *this; }
        File_handle& operator<< (char c)    { fputc(c, fp); return *this; }
        File_handle& operator<< (int x)     { fprintf(fp,"%d",x); return *this; }
        File_handle& operator<< (unisgned x){ fprintf(fp,"%d",x); return *this; }
        File_handle& operator<< (double x)  { fprintf(fp,"%f",x); return *this; }
        File_handle& operator<< (float x)   { return operator<<(double(x)); }
        File_handle& operator<< (void*ptr)  { fprintf(fp,"%p",p); return *this; }
        File_handle& operator<< (bool x)    { return operator<<(int(x)); }
    };

这个例子省略了一些基本类型(比如short、unsigned short、long-long…),但你已经明白了。

此外,还可以添加一些特殊而有趣的重载。

如果有人将另一个File_handle对象插入链中,该怎么办?如果在写入之外添加一种支持读取的方法,那么这可能会将数据从读取对象的文件复制到写入对象的文件中。

当一个FILE*被放入链中时,也许你可以做一些特别的事情。

您可能想要恢复标准库——如果const char*重载没有像您想要的那样处理std::string对象,那么它可能会被单独处理:文件句柄&运算符<lt;(conststd::string&x){……return*this;}

您还可以添加一个重载,该重载将处理任何其他重载未处理的所有类型。模板重载将只处理任何显式类型的版本都无法处理的类型。因此,在类定义块中,您可以添加:

    template <typename T>
    File_handle& operator << (const T& x) {
        return operator << ("what the hell is this crap?");
    }

(至少可以避免编译错误)

顺便说一句,在示例中,当重载返回运算符<lt;(?),他们只是根据传入的任何新类型,将工作传递给不同的过载。

祝你好运。

在要包含在打开的文件流中的任何其他类中,只需在using namespace声明后包含this:extern of stream文件名,还记得包含#include。

应该这样做。