c++工程实践——实际工程中的文件读取和日期处理的小问题

一、问题

在实际开发中遇到了两个小问题,一个是文件流的读写中的长度和结尾判断;另外一个是C++11库std::chrono::duration的数据类型的问题。这两个问题导致了两个结果:

1、流结尾判断不准确,多读一帧导致长度判断恒为正确,文件不加载

2、对标准库的时间库认知不统一,导致单位不同出现错判

这样说可能不好理解,在下面分别进行代码说明。

二、文件问题解决

先看一下文件处理的原始代码:

c 复制代码
#include <fstream>
#include <iostream>

int main() {
  std::ifstream f(filename);

  if (!f.good()) {
    return 0;
  }

  char buf[1024];
  static std::streamsize count = 1024;

  while (!f.eof()) {
    size_t c = f.getline(buf, count).gcount();//可正常比较
    // size_t c = metaFileStream.gcount();//可正常比较
    std::cerr << "----" << f.gcount() << std::endl;
    auto dlen = f.gcount();
    //下面的判断进入,导致文件错误
    if (dlen < 3/*f.gcount()<3*/) {//问题主要在这里,原始的代码是注释掉的部分,但此处二者结果一致
      std::cerr << "dlen is:" << dlen << std::endl;
    }
    if (static_cast<size_t>(f.gcount()) < 3) {//可正常比较
      std::cerr << "static_cast count compare!" << std::endl;
      return 0;
    }
  }

  return 1;
}

这段代码本来是没有问题的,后面有一个长度判断的,但后来更改增加了不能执行的部分后,程序基本就无法运行了。因为只要有运行就需要加载文件,而在此处则无法运行下去,直接报文件读取的数据太少,也就是上面注释里的那段无法执行的代码返回的。

后来根据代码来要看,怀疑是不是getline函数对gcount函数有类似清除的机制,但查看官方文档后发现没有这个问题,只是提供了对长度的变化的影响,这反而是程序需要的。后来就怀疑到是不是不同类型的比较导致的,毕竟在很早以前就被有符号和无符号的类型整过。查看文档中gcount()返回的数据类型是streamsize,而它的定义是:

c 复制代码
#define __PTRDIFF_TYPE__ long int
typedef __PTRDIFF_TYPE__ ptrdiff_t;

这应该没有问题的。继续分析,打印发现起始都是正常的,但读到最后会打印一个长度为0的读取缓冲,所以进入了文件错误判断的分支中。忽然想起了,文件流读取最后一帧后是需要再读取一帧才能将f.eof()转为false。也就是说,正常读取完成后,其结果仍然是True,只有继续读取出现错误时,才会置为False。所以会出现一个空读取,导致进入异常判断。知道问题原因后就比较好解决了:

c 复制代码
#include <fstream>
#include <iostream>

int main() {
  std::ifstream f(filename);

  if (!f.good()) {
    return 0;
  }

  char buf[1024];
  static std::streamsize count = 1024;

  while (f.peek()!=EOF) {
    size_t c = f.getline(buf, count).gcount();

    //正常
    if (c < 3) {
      std::cerr << "dlen is:" << dlen << std::endl;
    }
    if (static_cast<size_t>(f.gcount()) < 3) {
      std::cerr << "static_cast count compare!" << std::endl;
      return 0;
    }
  }


  return 1;
}

不过在最近的开发中遇到过几次定义完全相同的类型,在被反复重新定义后就无法进行转换。比如在某个库里定义了Uint16,而程序中有uint16_t,这两种类型本质都是一种类型,但在实际编译中报错,无法通过编译。这里面的原因很多,不做细的分析,只是提醒开发者要注意这方面的细节。

三、时间问题解决

时间问题的处理主要原因在于对时间问题的掌握还是不系统,没有从根本上明白时间库的定义,先看类似原始代码:

c 复制代码
int main(){
  //求运行时间1
  auto t1 = std::chrono::steady_clock::now();
  dowork();
  auto t2 = std::chrono::steady_clock::now();
  auto escape1 = t2 - t1;//这种情况单位是什么
  std::cerr << "escape1 is:" << escape1.count() << std::endl;
  //求运行时间2
  auto t3 = std::chrono::steady_clock::now();
  dowork();
  auto t4 = std::chrono::steady_clock::now();
  std::chrono::duration<double> escape2 = t4 - t3;//这种情况单位是什么
  std::cerr << "escape1 is:" << escape2.count() << std::endl;
  //如果出现运行结果中的科学计数方法可使用下面的方式打印
  std::cerr << std::fixed << std::setprecision(9) << "escape1 is:" << escape2.count() << std::endl;
  //求运行时间3
  auto start = std::chrono::high_resolution_clock::now();
  dowork();
  auto end = std::chrono::high_resolution_clock::now();
  std::chrono::microseconds escape3 = std::chrono::duration_cast<std::chrono::microseconds>(end - start);//此处单位是微秒
  std::cerr << "escape1 is:" << escape3.count() << std::endl;
  return 0;
}

运行结果:

cape1 is:2532329
escape1 is:0.00255415    //有可能出现类似escape1 is:3.82e-07的科学计数
escape1 is:2559

有没有发现,上面的打印都没有带单位,这也这次的一个主要问题。特别是std::chrono::duration竟然一时没反应过来,后来查看文档说明才知道,其定义为:

c 复制代码
template<
    class Rep,
    class Period = std::ratio<1>
> class duration;
template<
    std::intmax_t Num,
    std::intmax_t Denom = 1
> class ratio;

其实重点是要明白ratio的定义,duration等价于duration<double,std::ratio<1>>,又等价于duration<double,std::ratio<1,1>>即以秒为基准单位。对照上面的显示,可以知道,三个打印结果的单位是纳秒、秒和微秒。再根据现实生活中时间的定义,ratio<60>(ratio<60,1>)代表一分钟,ratio<6060>(ratio<60 60,1>)代表一小时,ratio<1, 1000>代表一毫秒,ratio<1, 1000000>代表一微秒,ratio<1, 1000000000>代表一纳秒。

所以在标准库中定义了一些常用的时间:

c 复制代码
typedef duration <Rep, ratio<3600,1>> hours;
typedef duration <Rep, ratio<60,1>> minutes;
typedef duration <Rep, ratio<1,1>> seconds;
typedef duration <Rep, ratio<1,1000>> milliseconds;        
typedef duration <Rep, ratio<1,1000000>> microseconds;       
typedef duration <Rep, ratio<1,1000000000>> nanoseconds;     

在C++20后又出现了周(week)、月(months)和年(years)的单位,而在某些场景下可能会以某些特定的时间量为单位,所以才会有ratio<N,N>的用法。而duration也可以是duration等,其实就是表示数据的类型,浮点可能精度更高一些。

这个问题之所以出现,主要是程序中的时间太小,出现了科学计算法,同时,单位的不统一导致口算时明显数据不正确。以前基本都是统一的时间单位控制,而这块代码因为与库中的代码进行兼容,所以才出现了计算上的差异。std::chrono::duration_cast的机制与分析大略相同。

无它,终究还是基础知识不扎实导致。

提一个问题:"auto t1 = std::chrono::steady_clock::now();"返回的 time_point的Rep类型是什么类型?它从哪里来?或者说std::chrono::steady_clock的默认的Rep是什么类型?

四、总结

万丈高楼平地起,基础的重要性已经反复说得都麻了。但不得不说,往往都明白基础的重要性,可其实并未真正时刻挂在心上。上面的例子就说明了学习态度中的一些瑕疵,与诸君共同努力改正吧。

相关推荐
UestcXiye1 小时前
《TCP/IP网络编程》学习笔记 | Chapter 3:地址族与数据序列
c++·计算机网络·ip·tcp
霁月风2 小时前
设计模式——适配器模式
c++·适配器模式
jrrz08283 小时前
LeetCode 热题100(七)【链表】(1)
数据结构·c++·算法·leetcode·链表
咖啡里的茶i3 小时前
Vehicle友元Date多态Sedan和Truck
c++
海绵波波1073 小时前
Webserver(4.9)本地套接字的通信
c++
@小博的博客3 小时前
C++初阶学习第十弹——深入讲解vector的迭代器失效
数据结构·c++·学习
爱吃喵的鲤鱼4 小时前
linux进程的状态之环境变量
linux·运维·服务器·开发语言·c++
7年老菜鸡5 小时前
策略模式(C++)三分钟读懂
c++·qt·策略模式
Ni-Guvara5 小时前
函数对象笔记
c++·算法
似霰5 小时前
安卓智能指针sp、wp、RefBase浅析
android·c++·binder