对实现感兴趣很好。初学者一般开始时都会对 io 比较感兴趣,但事实上,这可能也是标准库里最复杂的一部分。
一般意义上讲,你很难自己写一段代码来代替 cin 或者 cout。因为 io 的很多过程都要和操作系统交互,而 C++ 这门语言又是跨平台跨系统的,所以编译器底层也要在很多不同的系统上实现,才能最终保证跨平台的实现。
你也许会用其它流操作来读取或写入磁盘文件,拿这个举例可能更好理解。而且本质上读取和写入磁盘文件与读取和写入标准IO也没什么分别。
当你打开一个文件后,库函数会为你构造一个流对象关联到你指定的磁盘文件,这个对象的一个很重要的私有成员是一块缓冲区。你所有的插入(或读取)命令都是针对缓冲区进行的,而不是直接操作磁盘文件。当缓冲区写满(或读空)或者你要求强制刷新(或同步)的时候,库函数才会调用系统函数,真正读写磁盘文件。事实上,你完全可以自己调用系统函数,而不使用标准库的那套东西。但这一般会更加麻烦:要么你就得自己管理缓冲区,这会增加实现的难度(而且除了极少数的情况,一般来说效率也不如库函数高)。或者完全放弃缓冲技术,但这样效率更加低下,一般会比使用库函数慢数百到数千倍(除了缓冲这个技术本身能提高IO效率以外,系统函数的调用速度本来就比库函数要慢,因为它还需要诸如核查调用进程的权限等等其它额外的操作)。
标准输入输出是在 main 函数开始执行之前就初始化完毕的,它的缓冲区也在那个时候就准备好了。当然你可以自己建个流关联到那个缓冲区上,但这似乎跟你说的自己定义两个对象来代替 cin 和 cout 也相去甚远。对于很多库函数来说,从标准IO上读写文件和一般文件IO没什么区别。有的时候只有操作系统关心那个所谓的文件究竟是磁盘文件还是特殊设备。
至于你问的 << 是怎么实现的。
简单来说,如果要插入的对象是 char 或者 char* 之类的,那么就会直接插入到那个缓冲区里。这时外像对齐方式呀,输出宽度之类的选项会影响插入的行为。
如果是 int, float 之类的东西,那会先把它们格式化成字符串。这时设置的基底呀,精度之类的选项会影响到参数的格式化。格式成字符串之后,会按照字符串的插法再往缓冲区里写。
当然在这些过程中,库函数还会监测流的状态。比如流的状态是否正常,是否可以写入。还会监视缓冲区是不是写满了。另外格式化和插入的过程中都有可能会出现错误,那么库函数还会在设置好相应的错误状态后,把接到的异常 rethrow。