Skip to content

Latest commit

 

History

History
453 lines (362 loc) · 14.7 KB

File metadata and controls

453 lines (362 loc) · 14.7 KB

四、流和文件系统

问题

32.帕斯卡三角形

编写一个函数,向控制台打印多达 10 行的帕斯卡三角形。

33.进程列表的表格打印

假设您有一个系统中所有进程列表的快照。每个进程的信息包括名称、标识符、状态(可以是运行中的或暂停中的)、帐户名(进程在该帐户名下运行)、内存大小(以字节为单位)和平台(可以是 32 位或 64 位)。您的任务是编写一个函数,获取这样一个进程列表,并以表格格式按字母顺序将它们打印到控制台。所有列都必须左对齐,除了内存列必须右对齐。内存大小的值必须以 KB 为单位显示。下面是这个函数的输出示例:**

chrome.exe      1044   Running    marius.bancila    25180  32-bit
chrome.exe      10100  Running    marius.bancila   227756  32-bit
cmd.exe         512    Running    SYSTEM               48  64-bit
explorer.exe    7108   Running    marius.bancila    29529  64-bit
skype.exe       22456  Suspended  marius.bancila      656  64-bit

34.从文本文件中删除空行

编写一个程序,给定一个文本文件的路径,通过删除所有空行来修改文件。仅包含空格的行被认为是空的。

35.计算目录的大小

编写一个递归计算目录大小(以字节为单位)的函数。应该可以指出是否应该遵循符号链接。

36.删除超过给定日期的文件

编写一个函数,在给定目录路径和持续时间的情况下,以递归方式删除所有早于指定持续时间的条目(文件或子目录)。持续时间可以表示任何东西,例如天、小时、分钟、秒等,或者它们的组合,例如一小时二十分钟。如果指定的目录本身早于给定的持续时间,则应将其完全删除。

37.在目录中查找与正则表达式匹配的文件

编写一个函数,在给定目录路径和正则表达式的情况下,返回名称与正则表达式匹配的所有目录条目的列表。

38.临时日志文件

创建一个日志类,将文本消息写入可丢弃的文本文件。文本文件应具有唯一的名称,并且必须位于临时目录中。除非另外指定,否则当类的实例被销毁时,应该删除此日志文件。但是,应该可以通过将其移动到永久位置来保留日志文件。

解决方法

32.帕斯卡三角形

帕斯卡三角形是一种表示二项式系数的结构。三角形从一个值为 1 的行开始。每一行的元素都是通过对上面的数字进行求和来构造的,向左和向右,并将空白条目视为 0。下面是一个五行三角形的例子:

 1
 1   1
 1   2   1
 1   3   3   1
1   4   6   4   1

要打印三角形,我们必须:

  • 将输出位置向右移动适当数量的空格,使顶部投影到三角形底部的中间。
  • 通过对上述左右值求和来计算每个值。一个更简单的公式是,对于一行i和一列j,每个新值x等于x的前一个值乘以(i - j) / (j + 1),其中x从 1 开始。

下面是打印三角形的函数的可能实现:

unsigned int number_of_digits(unsigned int const i)
{
   return i > 0 ? (int)log10((double)i) + 1 : 1;
}

void print_pascal_triangle(int const n)
{
   for (int i = 0; i < n; i++) 
   {
      auto x = 1;
      std::cout << std::string((n - i - 1)*(n / 2), ' ');
      for (int j = 0; j <= i; j++) 
      {
         auto y = x;
         x = x * (i - j) / (j + 1);
         auto maxlen = number_of_digits(x) - 1;
         std::cout << y << std::string(n - 1 - maxlen - n%2, ' ');
      }
      std::cout << std::endl;
   }
}

以下程序要求用户输入级别数,并将三角形打印到控制台:

int main()
{
   int n = 0;
   std::cout << "Levels (up to 10): ";
   std::cin >> n;
   if (n > 10)
      std::cout << "Value too large" << std::endl;
   else
      print_pascal_triangle(n);
}

33.进程列表的表格打印

为了解决这个问题,我们将考虑以下表示进程信息的类:

enum class procstatus {suspended, running};
enum class platforms {p32bit, p64bit};

struct procinfo
{
   int         id;
   std::string name;
   procstatus  status;
   std::string account;
   size_t      memory;
   platforms   platform;
};

为了将状态和平台打印为文本而不是数值,我们需要从枚举到std::string的转换函数:

std::string status_to_string(procstatus const status)
{
   if (status == procstatus::suspended) return "suspended";
   else return "running";
}

std::string platform_to_string(platforms const platform)
{
   if (platform == platforms::p32bit) return "32-bit";
   else return "64-bit";
}

流程需要按流程名称的字母顺序排序。因此,第一步是对流程的输入范围进行排序。对于打印本身,我们应该使用输入/输出操纵器:

void print_processes(std::vector<procinfo> processes)
{
   std::sort(
      std::begin(processes), std::end(processes),
      [](procinfo const & p1, procinfo const & p2) {
         return p1.name < p2.name; });

   for (auto const & pi : processes)
   {
      std::cout << std::left << std::setw(25) << std::setfill(' ')
                << pi.name;
      std::cout << std::left << std::setw(8) << std::setfill(' ')
                << pi.id;
      std::cout << std::left << std::setw(12) << std::setfill(' ')
                << status_to_string(pi.status);
      std::cout << std::left << std::setw(15) << std::setfill(' ')
                << pi.account;
      std::cout << std::right << std::setw(10) << std::setfill(' ')
                << (int)(pi.memory/1024);
      std::cout << std::left << ' ' << platform_to_string(pi.platform);
      std::cout << std::endl;
   }
}

以下程序定义了一个进程列表(您实际上可以使用特定于操作系统的 API 检索正在运行的进程列表),并以请求的格式将其打印到控制台:

int main()
{
   using namespace std::string_literals;

   std::vector<procinfo> processes
   {
      {512, "cmd.exe"s, procstatus::running, "SYSTEM"s, 
            148293, platforms::p64bit },
      {1044, "chrome.exe"s, procstatus::running, "marius.bancila"s, 
            25180454, platforms::p32bit},
      {7108, "explorer.exe"s, procstatus::running, "marius.bancila"s,  
            2952943, platforms::p64bit },
      {10100, "chrome.exe"s, procstatus::running, "marius.bancila"s, 
            227756123, platforms::p32bit},
      {22456, "skype.exe"s, procstatus::suspended, "marius.bancila"s, 
            16870123, platforms::p64bit }, 
   };

   print_processes(processes);
}

34.从文本文件中删除空行

解决此任务的一种可能方法是执行以下操作:

  1. 创建一个临时文件,仅包含要从原始文件中保留的文本
  2. 从输入文件中逐行读取,并将所有非空行复制到临时文件中
  3. 完成处理后删除原始文件
  4. 将临时文件移动到原始文件的路径

另一种方法是移动临时文件并覆盖原始文件。以下实现遵循列出的步骤。临时文件创建在filesystem::temp_directory_path()返回的临时目录中:

namespace fs = std::experimental::filesystem;

void remove_empty_lines(fs::path filepath)
{
   std::ifstream filein(filepath.native(), std::ios::in);
   if (!filein.is_open())
      throw std::runtime_error("cannot open input file");

   auto temppath = fs::temp_directory_path() / "temp.txt";
   std::ofstream fileout(temppath.native(), 
   std::ios::out | std::ios::trunc);
   if (!fileout.is_open())
      throw std::runtime_error("cannot create temporary file");

   std::string line;
   while (std::getline(filein, line))
   {
      if (line.length() > 0 &&
      line.find_first_not_of(' ') != line.npos)
      {
         fileout << line << '\n';
      }
   }
   filein.close();
   fileout.close();

   fs::remove(filepath);
   fs::rename(temppath, filepath);
}

35.计算目录的大小

为了计算一个目录的大小,我们必须遍历所有的文件,并计算单个文件的大小。

filesystem::recursive_directory_iterator是来自filesystem库的迭代器,允许以递归方式迭代目录的所有条目。它有各种各样的构造函数,其中一些构造函数采用类型为filesystem::directory_options的值,该值指示是否应该遵循符号链接。通用std::accumulate()算法可以用来合计文件大小。由于一个目录的总大小可能超过 2 GB,所以不应该使用intlong,而应该使用unsigned long long作为总和类型。以下函数显示了所需任务的可能实现:

namespace fs = std::experimental::filesystem;

std::uintmax_t get_directory_size(fs::path const & dir,
                                  bool const follow_symlinks = false)
{
   auto iterator = fs::recursive_directory_iterator(
      dir,
      follow_symlinks ? fs::directory_options::follow_directory_symlink : 
                        fs::directory_options::none);

   return std::accumulate(
      fs::begin(iterator), fs::end(iterator),
      0ull,
      [](std::uintmax_t const total,
         fs::directory_entry const & entry) {
             return total + (fs::is_regular_file(entry) ?
                    fs::file_size(entry.path()) : 0);
   });
}

int main()
{
   std::string path;
   std::cout << "Path: ";
   std::cin >> path;
   std::cout << "Size: " << get_directory_size(path) << std::endl;
}

36.删除超过给定日期的文件

要执行文件系统操作,您应该使用filesystem库。对于使用时间和持续时间的工作,您应该使用chrono库。实现所请求功能的函数必须执行以下操作:

  1. 检查目标路径指示的条目是否存在,并且是否早于给定的持续时间,如果是,则将其删除
  2. 如果它不是旧的,并且是一个目录,则遍历它的所有条目并递归调用该函数:
namespace fs = std::experimental::filesystem;
namespace ch = std::chrono;

template <typename Duration>
bool is_older_than(fs::path const & path, Duration const duration)
{
   auto ftimeduration = fs::last_write_time(path).time_since_epoch();
   auto nowduration = (ch::system_clock::now() - duration)
                      .time_since_epoch();
   return ch::duration_cast<Duration>(nowduration - ftimeduration)
                      .count() > 0;
}

template <typename Duration>
void remove_files_older_than(fs::path const & path, 
                             Duration const duration)
{
   try
   {
      if (fs::exists(path))
      {
         if (is_older_than(path, duration))
         {
            fs::remove(path);
         }
         else if(fs::is_directory(path))
         {
            for (auto const & entry : fs::directory_iterator(path))
            {
               remove_files_older_than(entry.path(), duration);
            }
         }
      }
   }
   catch (std::exception const & ex)
   {
      std::cerr << ex.what() << std::endl;
   }
}

使用directory_iterator并递归调用remove_files_older_than()的另一种方法是使用recursive_directory_iterator并简单地删除超过给定持续时间的条目。但是,这种方法会采用未定义的行为,因为如果在创建递归目录迭代器后,文件或目录被删除或添加到目录树中,则不会指定是否会通过迭代器观察到更改。因此,应该避免这种方法。

is_older_than()功能模板确定当前时刻和最后一次文件写入操作从系统时钟周期开始经过的时间,并检查两者的差值是否大于指定的持续时间。

remove_files_older_than()功能可以如下使用:

int main()
{
   using namespace std::chrono_literals;

#ifdef _WIN32
   auto path = R"(..\Test\)";
#else
   auto path = R"(../Test/)";
#endif

   remove_files_older_than(path, 1h + 20min);
}

37.在目录中查找与正则表达式匹配的文件

实现指定的功能应该很简单:递归地遍历指定目录中的所有条目,并保留所有名称与正则表达式匹配的常规文件条目。为此,您应该使用以下内容:

  • filesystem::recursive_directory_iterator遍历目录条目
  • regexregex_match()检查文件名是否与正则表达式匹配
  • copy_if()back_insertervector末尾复制符合特定标准的目录条目。

这样的函数可能如下所示:

namespace fs = std::experimental::filesystem;

std::vector<fs::directory_entry> find_files(
   fs::path const & path,
   std::string_view regex)
{
   std::vector<fs::directory_entry> result;
   std::regex rx(regex.data());

   std::copy_if(
      fs::recursive_directory_iterator(path),
      fs::recursive_directory_iterator(),
      std::back_inserter(result),
      [&rx](fs::directory_entry const & entry) {
         return fs::is_regular_file(entry.path()) &&
                std::regex_match(entry.path().filename().string(), rx);
   });

   return result;
}

有了这些,我们可以编写以下代码:

int main()
{
   auto dir = fs::temp_directory_path();
   auto pattern = R"(wct[0-9a-zA-Z]{3}\.tmp)";
   auto result = find_files(dir, pattern);

   for (auto const & entry : result)
   {
      std::cout << entry.path().string() << std::endl;
   }
}

38.临时日志文件

您必须为此任务实现的日志类应该:

  • 有一个构造函数,它在临时目录中创建一个文本文件,并打开它进行写入
  • 在销毁过程中,如果文件仍然存在,请将其关闭并删除
  • 有一个关闭文件并将其移动到永久路径的方法
  • 将文本消息写入输出文件的时间超过了operator<<

为了给文件创建唯一的名称,可以使用 UUID(也称为 GUID)。C++ 标准不支持任何与此相关的功能,但是有第三方库,比如boost::uuidCrossGuid 或者stduuid,这其实是我创建的一个库。对于这个实现,我将使用最后一个。你可以在https://github.com/mariusbancila/stduuid找到它:

namespace fs = std::experimental::filesystem;

class logger
{
   fs::path logpath;
   std::ofstream logfile;
public:
   logger()
   {
      auto name = uuids::to_string(uuids::uuid_random_generator{}());
      logpath = fs::temp_directory_path() / (name + ".tmp");
      logfile.open(logpath.c_str(), std::ios::out|std::ios::trunc);
   }

   ~logger() noexcept
   {
      try {
         if(logfile.is_open()) logfile.close();
         if (!logpath.empty()) fs::remove(logpath);
      }
      catch (...) {}
   }

   void persist(fs::path const & path)
   {
      logfile.close();
      fs::rename(logpath, path);
      logpath.clear();
   }

   logger& operator<<(std::string_view message)
   {
      logfile << message.data() << '\n';
      return *this;
   }
};

使用此类的示例如下:

int main()
{
   logger log;
   try 
   {
      log << "this is a line" << "and this is another one";
      throw std::runtime_error("error");
   }
   catch (...) 
   {
      log.persist(R"(lastlog.txt)");
   }
}
```**