4. 系统I/O

系统 I/O

示例代码:

#include <iostream>     // 标准库头文件
// #include "myheader.h"    // 自己写的头文件

void fun(const char* pInfo, int /* pValue */)   //第二个int为设计时候保留接口,在这里没有用到,为后续保留
{
    std::cout << pInfo << "\n";
    return;
}

int main(void)
{
    fun("Hello World!", 0);
    fun("This is Windows 11", 1);
    // return 0;    // 返回值类型不标注,默认返回int类型
}
  • iostream :标准库所提供的 IO 接口,用于与用户交互

    • 输入流: cin ;输出流: cout / cerr / clog ,这里的 c 代表 "character"

      输出目标可以重定向,比如输出到单独的文件中,示例代码如下:

      #include <iostream>
      
      int main(void)
      {
          std::cout << "Output from cout \n";
          std::cerr << "Output from cerr \n";
          return 0;
      }
      

      上面的两个信息,我们可以重定向输出到文件中,使用如下命令:

      g++ -Wall -g basic_io.cpp -o basic_io
      ./basic_io>cout.txt 2>cerr.txt
      

      然后生成两个新文件:

      > ls 
      basic_io.cpp	basic_io	cerr.txt	cout.txt
      

      然后打开文件夹,就能看到输出内容。

      image-20230922120928384

    • 输出流的区别: 1. 输出目标; 2. 是否立即刷新缓冲区

      • 立即刷新缓冲区的作用:使我们即时看到输出的内容
      • cerr 会立即刷新缓冲区
      • clog 不会立即刷新缓冲区
    • 缓冲区与缓冲区刷新: std::flush; std::endl

      显式刷新缓冲区:

      #include <iostream>
      
      int main(void)
      {
          std::cout << "Hello world!\n" << std::flush;
          return 0;
      }
      

      std::endl 和 std::flush 的区别:前者会刷新缓冲区并换行,后者只刷新缓冲区。

      注意:刷新缓冲区会减缓程序运行速度,需要谨慎使用。

  • 名字空间:用于防止名称冲突

    namespace NameSpace1 {
        void fun()
        {}
    }
    
    namespace NameSpace2 {
        void fun()
        {}
    }
    
    int main(void)
    {
        
    }
    

    如果我们写的函数在全局中,其实它会默认添加一个全局的名字空间。上述代码在不同的名字空间中定义了两个同名的函数 fun() ,但是可以编译通过。如果都放在全局名字空间中,则会编译出错:

    void fun()
    {}
    
    void fun()
    {}
    
    int main(void)
    {
    
    }
    
    g++ namespace.cpp -o namespace
    namespace.cpp:16:6: error: redefinition of ‘void fun()’
       16 | void fun()
          |      ^~~
    namespace.cpp:13:6: note: ‘void fun()’ previously defined here
       13 | void fun()
    
    • std 名字空间 :C++标准库的名字空间

    • 访问名字空间中元素的 3 种方式 : 域解析符 :: ; using 语句;名字空间别名

      注意:如下的 using 使用方式是错误的:

      #include <iostream>
      
      namespace NameSpace1 {
          void fun()
          {
              std::cout << "NameSpace1\n";
          }
      }
      
      namespace NameSpace2 {
          void fun()
          {
              std::cout << "NameSpace2\n"; 
          }
      }
      
      int main(void)
      {
          using namespace NameSpace1;
          fun();
          using namespace NameSpace2;
          fun();	// error: call of overloaded ‘fun()’ is ambiguous
          
      }
      

      编译出错:

      > g++ namespace.cpp -o namespace
      namespace.cpp: In function ‘int main()’:
      namespace.cpp:22:8: error: call of overloaded ‘fun()’ is ambiguous
         22 |     fun();
            |     ~~~^~
      namespace.cpp:11:10: note: candidate: ‘void NameSpace2::fun()’
         11 |     void fun()
            |          ^~~
      namespace.cpp:4:10: note: candidate: ‘void NameSpace1::fun()’
          4 |     void fun()
            |          ^~~
      

      为了安全,我们最好还是使用域解析符:

      #include <iostream>
      
      namespace NameSpace1 {
      void fun() { std::cout << "NameSpace1\n"; }
      } // namespace NameSpace1
      
      namespace NameSpace2 {
      void fun() { std::cout << "NameSpace2\n"; }
      } // namespace NameSpace2
      
      void fun() { std::cout << "GlobalNameSpace\n"; }
      
      int main(void) {
        // using namespace NameSpace1;
        // fun();
        // using namespace NameSpace2;
        // fun();  // error: call of overloaded ‘fun()’ is ambiguous
        NameSpace1::fun();
        NameSpace2::fun();
        fun();	// global namespace
      }
      

      这样编译运行就不会出错了。

    • 名字空间与名称改编( name mangling )

      链接的机制:上述的代码中,我们在定义fun() 的时候,分别定义在两个名字空间中,在链接的时候,必须要提供两个不同的连接名称,因为不能用域解析符,此时会涉及到mangling,即名称改编。

      我们将上述的代码编译成目标文件:namespace.o

      g++ namespace.s -c -o namespace.o
      

      然后使用 nm 命令查看该目标文件中的符号信息

      nm namespace.o
                       U _GLOBAL_OFFSET_TABLE_
      00000000000000e0 t _GLOBAL__sub_I__ZN10NameSpace13funEv
      0000000000000048 T _Z3funv
      000000000000008a t _Z41__static_initialization_and_destruction_0ii
      0000000000000000 T _ZN10NameSpace13funEv
      0000000000000024 T _ZN10NameSpace23funEv
                       U _ZNSt8ios_base4InitC1Ev
                       U _ZNSt8ios_base4InitD1Ev
                       U _ZSt4cout
      0000000000000000 b _ZStL8__ioinit
                       U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
                       U __cxa_atexit
                       U __dso_handle
      000000000000006c T main
      

      de-mangling 操作(即查看原有的名称):

      nm namespace.o | c++filt -t
                       U _GLOBAL_OFFSET_TABLE_
      00000000000000e0 unsigned short _GLOBAL__sub_I__ZN10NameSpace13funEv
      0000000000000048 T fun()
      000000000000008a unsigned short __static_initialization_and_destruction_0(int, int)
      0000000000000000 T NameSpace1::fun()
      0000000000000024 T NameSpace2::fun()
                       U std::ios_base::Init::Init()
                       U std::ios_base::Init::~Init()
                       U std::cout
      0000000000000000 bool std::__ioinit
                       U std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
                       U __cxa_atexit
                       U __dso_handle
      000000000000006c T main
      

      注:mangling 和 de-mangling 不会改变 main 函数的名字,因为不会存在同名的 main 函数,所以不需要改变。

      nm 命令显示关于指定 File 中符号的信息,文件可以是对象文件、可执行文件或对象文件库。如果文件没有包含符号信息,nm 命令报告该情况,但不把它解释为出错条件。 nm 命令缺省情况下报告十进制符号表示法下的数字值。

  • C / C++ 系统 IO 比较

    • printf: 使用直观,但容易出错
    • cout: 不容易出错,但书写冗长
    • C++ 20 格式化库:新的解决方案
posted @ 2024-01-24 21:45  kobayashilin1  阅读(14)  评论(0编辑  收藏  举报