编写一个函数,向控制台打印多达 10 行的帕斯卡三角形。
假设您有一个系统中所有进程列表的快照。每个进程的信息包括名称、标识符、状态(可以是运行中的或暂停中的)、帐户名(进程在该帐户名下运行)、内存大小(以字节为单位)和平台(可以是 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
编写一个程序,给定一个文本文件的路径,通过删除所有空行来修改文件。仅包含空格的行被认为是空的。
编写一个递归计算目录大小(以字节为单位)的函数。应该可以指出是否应该遵循符号链接。
编写一个函数,在给定目录路径和持续时间的情况下,以递归方式删除所有早于指定持续时间的条目(文件或子目录)。持续时间可以表示任何东西,例如天、小时、分钟、秒等,或者它们的组合,例如一小时二十分钟。如果指定的目录本身早于给定的持续时间,则应将其完全删除。
编写一个函数,在给定目录路径和正则表达式的情况下,返回名称与正则表达式匹配的所有目录条目的列表。
创建一个日志类,将文本消息写入可丢弃的文本文件。文本文件应具有唯一的名称,并且必须位于临时目录中。除非另外指定,否则当类的实例被销毁时,应该删除此日志文件。但是,应该可以通过将其移动到永久位置来保留日志文件。
帕斯卡三角形是一种表示二项式系数的结构。三角形从一个值为 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);
}
为了解决这个问题,我们将考虑以下表示进程信息的类:
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);
}
解决此任务的一种可能方法是执行以下操作:
- 创建一个临时文件,仅包含要从原始文件中保留的文本
- 从输入文件中逐行读取,并将所有非空行复制到临时文件中
- 完成处理后删除原始文件
- 将临时文件移动到原始文件的路径
另一种方法是移动临时文件并覆盖原始文件。以下实现遵循列出的步骤。临时文件创建在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);
}
为了计算一个目录的大小,我们必须遍历所有的文件,并计算单个文件的大小。
filesystem::recursive_directory_iterator
是来自filesystem
库的迭代器,允许以递归方式迭代目录的所有条目。它有各种各样的构造函数,其中一些构造函数采用类型为filesystem::directory_options
的值,该值指示是否应该遵循符号链接。通用std::accumulate()
算法可以用来合计文件大小。由于一个目录的总大小可能超过 2 GB,所以不应该使用int
或long
,而应该使用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;
}
要执行文件系统操作,您应该使用filesystem
库。对于使用时间和持续时间的工作,您应该使用chrono
库。实现所请求功能的函数必须执行以下操作:
- 检查目标路径指示的条目是否存在,并且是否早于给定的持续时间,如果是,则将其删除
- 如果它不是旧的,并且是一个目录,则遍历它的所有条目并递归调用该函数:
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);
}
实现指定的功能应该很简单:递归地遍历指定目录中的所有条目,并保留所有名称与正则表达式匹配的常规文件条目。为此,您应该使用以下内容:
filesystem::recursive_directory_iterator
遍历目录条目regex
和regex_match()
检查文件名是否与正则表达式匹配copy_if()
和back_inserter
在vector
末尾复制符合特定标准的目录条目。
这样的函数可能如下所示:
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;
}
}
您必须为此任务实现的日志类应该:
- 有一个构造函数,它在临时目录中创建一个文本文件,并打开它进行写入
- 在销毁过程中,如果文件仍然存在,请将其关闭并删除
- 有一个关闭文件并将其移动到永久路径的方法
- 将文本消息写入输出文件的时间超过了
operator<<
为了给文件创建唯一的名称,可以使用 UUID(也称为 GUID)。C++ 标准不支持任何与此相关的功能,但是有第三方库,比如boost::uuid
、 CrossGuid 或者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)");
}
}
```**