【问题标题】:Can't read file with cyrillic path in C++无法在 C++ 中读取带有西里尔字母路径的文件
【发布时间】:2017-09-10 09:13:03
【问题描述】:

我正在尝试读取路径中包含西里尔字符的文件,并得到ifstream.is_open() == false 这是我的代码:

std::string ReadFile(const std::string &path) {
    std::string newLine, fileContent;
    std::ifstream in(path.c_str(), std::ios::in);

    if (!in.is_open()) {
        return std::string("isn't opened");
    }

    while (in.good()) {
        getline(in, newLine);
        fileContent += newLine;
    }

    in.close();

    return fileContent;
}

int main() {
    std::string path = "C:\\test\\документ.txt";
    std::string content = ReadFile(path);
    std::cout << content << std::endl;
    return 0;
}

指定的文件存在

我正在尝试在谷歌中找到解决方案,但我一无所获

这是我看到的链接:

I don't need wstring

The same as previous

no answer here

is not about C++

has no answer too

附:我需要在字符串中获取文件的内容,而不是在 wstring 中

这是我的 IDE 的编码设置 (CLION 2017.1)

【问题讨论】:

  • 也许您可以查看errno
  • 如果您确定文件确实存在,则可能是文件名编码问题。 std::ifstream 构造函数将接受在系统代码页中编码的 const char *const wchar_t *。由于您发送的是const char *,因此您的IDE 存储的字符串"C:\\test\\документ.txt" 似乎 编码在系统代码页中。 (也许是 UTF-8?)您需要配置您的 IDE 以在源代码中使用系统代码页,或者找出编码并转换为系统代码页或 UTF-16。
  • @BasileStarynkevitch,errno 等于“2”(在if(!in.is_open()) 内)
  • 我什至没想到,@user4815162342!谢谢,我现在就试试!
  • @user4815162342,我找到了我的 IDE 的编码设置和更新问题。请看一下。

标签: c++ windows io fstream ifstream


【解决方案1】:

您需要最新的编译器或 Boost。 std::filesystem::path 可以处理这些名称,但它在 C++17 标准中是新的。您的编译器可能仍将其命名为std::experimental::filesystem::path,否则您将使用第三方boost::filesystem::path。界面与 Boost 版本的灵感相当。

【讨论】:

    【解决方案2】:

    std::string 的定义是 std::basic_string,因此您的西里尔字符不会按预期存储。至少,尝试使用 std::wstring 来存储你的文件路径,然后你可以使用 std::string 从文件中读取。

    【讨论】:

      【解决方案3】:

      首先,将您的项目设置设置为使用 UTF-8 编码而不是 windows-1251。直到标准库变得非常好(不是很快),如果你想正确处理 io,你基本上不能依赖它。要从 Windows 上的文件中读取输入流,您需要编写自己的自定义输入流缓冲区,该缓冲区使用 2 字节宽的字符打开文件或依赖此类例程的某些第三方实现。这是一些不完整的(但对于您的示例来说已经足够了)实现:

      // assuming that usual Windows SDK macros such as _UNICODE, WIN32_LEAN_AND_MEAN are defined above
      #include <Windows.h>
      
      #include <string>
      #include <iostream>
      #include <system_error>
      #include <memory>
      #include <utility>
      #include <cstdlib>
      #include <cstdio>
      
      static_assert(2 == sizeof(wchar_t), "wchar_t size must be 2 bytes");
      
      using namespace ::std;
      
      class MyStreamBuf final: public streambuf
      {
          #pragma region Fields
          private: ::HANDLE const  m_file_handle;
          private: char            m_buffer; // typically buffer should be much bigger
          #pragma endregion
      
          public: explicit
          MyStreamBuf(wchar_t const * psz_file_path)
          :   m_file_handle(::CreateFileW(psz_file_path, FILE_GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL))
          ,   m_buffer{}
          {
              if(INVALID_HANDLE_VALUE == m_file_handle)
              {
                  auto const error_code{::GetLastError()};
                  throw(system_error(static_cast< int >(error_code), system_category(), "::CreateFileW call failed"));
              }
          }
      
          public:
          ~MyStreamBuf(void)
          {
              auto const closed{::CloseHandle(m_file_handle)};
              if(FALSE == closed)
              {
                  auto const error_code{::GetLastError()};
                  //throw(::std::system_error(static_cast< int >(error_code), system_category(), "::CloseHandle call failed"));
                  // throwing in destructor is kinda wrong
                  // but if CloseHandle returned false then our program is in inconsistent state
                  // and must be terminated anyway
                  (void) error_code; // not used
                  abort();
              }
          }
      
          private: auto
          underflow(void) -> int_type override
          {
              ::DWORD bytes_count_to_read{1};
              ::DWORD read_bytes_count{};
              {
                  auto const succeeded{::ReadFile(m_file_handle, addressof(m_buffer), bytes_count_to_read, addressof(read_bytes_count), nullptr)};
                  if(FALSE == succeeded)
                  {
                      auto const error_code{::GetLastError()};
                      setg(nullptr, nullptr, nullptr);
                      throw(system_error(static_cast< int >(error_code), system_category(), "::ReadFile call failed"));
                  }
              }
              if(0 == read_bytes_count)
              {
                  setg(nullptr, nullptr, nullptr);
                  return(EOF);
              }
              setg(addressof(m_buffer), addressof(m_buffer), addressof(m_buffer) + 1);
              return(m_buffer);
          }
      };
      
      string
      MyReadFile(wchar_t const * psz_file_path)
      {
          istream in(new MyStreamBuf(psz_file_path)); // note that we create normal stream
          string new_line;
          string file_content;
          while(in.good())
          {
              getline(in, new_line);
              file_content += new_line;
          }
          return(::std::move(file_content));
      }
      
      int
      main(void)
      {
          string content = MyReadFile(L"C:\\test\\документ.txt"); // note that path is a wide string
          cout << content << endl;
          return 0;
      }
      

      【讨论】:

      • 这一切真的有必要吗? AFAIK Windows 为 const wchar_t * 重载了 std::istream 构造函数,它应该能够打开 L"C:\\test\\документ.txt" 就好了。
      • 此重载不是标准的,似乎对 OP 不可用。但是在某些时候,无论如何都必须编写自定义流缓冲区来获得对 io 的所需控制级别,甚至完全摆脱标准流。
      • OP 没有使用宽字符串作为文件名,因此重载一开始就没有机会使用。 “非标准”的重载不是问题,因为CreateFileW 同样是非标准的,包括&lt;Windows.h&gt; 等。
      • 我认为这个扩展不可用,因为 OP 没有使用 MSVS。您关于CreateFileW 同样非标准等的观点是无效的,因为与将新方法插入标准库的内容相比,使用标准接口编写自定义流缓冲区是向 iostreams 添加所需功能的首选方式。
      • CreateFileWistream::istream(const wchar_t *) 都不是标准 C++ 指定的。编写自定义流缓冲区对于某些用例来说是一种有效的解决方案,但对于 OP 实际需要的内容(即读取常规文件的内容)来说可能是一种过度杀伤。
      【解决方案4】:

      更改您的代码以使用 wstring 并使用 Unicode 编码(非 UTF8 编码,使用 USC-2、UTF16 或类似的东西)保存您的文件。 MSVC 具有非标准重载,专门为此能够处理文件名中的非 ascii 字符:

      std::string ReadFile(const std::wstring &path)
      {
          std::string newLine, fileContent;
          std::ifstream in(path.c_str(), std::ios::in);
      
          if (!in)
              return std::string("isn't opened");
      
          while (getline(in, newLine))
              fileContent += newLine;
      
          return fileContent;
      }
      
      int main()
      {
          std::wstring path = L"C:\\test\\документ.txt";
          std::string content = ReadFile(path);
          std::cout << content << std::endl;
      }
      

      另外,请注意更正ReadFile 代码。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-06-10
        • 1970-01-01
        • 1970-01-01
        • 2013-06-02
        • 2020-02-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多