好的,您要读取存储在文件中的文件名,然后获取扩展名的计数。
这看起来很简单,其实不然。原因是,现在的文件名可以包含各种特殊字符。其中可能有空格,也可能有多个点 ('.') 。根据文件系统,可能会有斜杠“/”(如在 Unix/Linux 中)或反斜杠“\”(如在 Windows 系统中)作为分隔符。还有没有扩展名的文件名和以句点开头的特殊文件。 (如“.profile”)。所以基本上没那么容易。
即使只有文件名,您至少应该从字符串的右端搜索点“.”,(也许)表示文件扩展名。从不从左侧。因此,在您的情况下,您应该使用 rfind 而不是 find。
现在,对于您的问题,如何阅读下一行。您的方法使用格式化的输入函数,适用于示例源文件中显示的文件名,但如果文件名中有空格,则无效。例如,您的声明 infile>>str 将在第一个空格字符后停止转换。
示例:文件名是“Hello World.txt”,那么“str”将仅包含“Hello”,下一次读取将包含“World.txt”。因此,您应该阅读带有专用函数std::getline 的完整行。请阅读说明here。
您可以逐行阅读:while(std::getline(inputFile,str)。
然后,稍后您可以拆分扩展名并对其进行计数。
对于扩展的拆分,我已经给了你一个提示和一些警告。但是,非常好,C++ 为您提供了一个现成的解决方案。 filesystem-library 描述了here。这有你需要的一切,随时可以使用。
特别有用的是path 类型,它有一个函数extension。这将为您完成所有细节。
因为它可以做到这一点,所以我强烈建议使用它。
现在,生活变得简单。请参阅以下示例:
#include <iostream>
#include <string>
#include <filesystem>
// Namespace alias to save a lot of typing work . . .
namespace fs = std::filesystem;
int main() {
// Read any kind of filename from the user
std::string line{}; std:getline(std::cin, line);
// Print the extension
std::cout << fs::path{ line }.extension().string();
}
所以,不用担心操作系统和任何类型的文件名。它只会为您完成所有基础工作。
接下来,数数。
有一种或多或少的标准方法可以计算容器中的某物或通过输入给出,然后可能会另外获取并显示其排名。因此,按出现频率排序。
对于计数部分,我们可以使用关联容器,例如 std::map 或 std::unordered_map。在这里,我们将“键”(在本例中为“扩展”)与一个计数相关联,在本例中为特定“扩展”的计数。
幸运的是,选择这种方法的基本原因是两张地图都有一个非常好的索引operator[]。这将查找给定的键,如果找到,则返回对计数的引用。如果未找到,则它将使用键(“扩展”)创建一个新条目并返回对新条目的引用。因此,在这两种情况下,我们都会获得对用于计数的值的引用。然后我们可以简单地写:
std::unordered_map<std::string, int> counter{};
counter[extension]++;
这看起来非常直观。
经过这个操作,你已经有了频率表。按键(单词)排序,使用std::map 或未排序,但使用std::unordered_map 可以更快地访问。
在您的情况下,如果您只对计数感兴趣,建议使用std::unordered_map,因为无需按其键对std::map 中的数据进行排序,以后不再使用此排序。
然后,也许您想根据频率/计数进行排序。如果您不想这样做,请跳过以下内容:
很遗憾,无法按值对地图进行排序。因为 map - 容器系列的主要属性是它们对 key 的引用,而不是值或计数。
因此,我们需要使用第二个容器,例如 std::vector 等,然后我们可以使用 std::sort 对任何给定的谓词进行排序,或者,我们可以将值复制到容器中,例如 @987654348 @ 隐含地对其元素进行排序。而且因为这只是一个班轮,所以这是推荐的解决方案。
此外,由于为 std 容器编写所有这些长名称,我们创建别名,使用 using 关键字。
在我们得到单词的排名之后,按照计数排序的单词列表,我们可以使用迭代器和循环来访问数据并输出它们。
因为您想从文件中读取,我还想提供有关打开关闭文件(流)的附加信息。
如果你读过ifstream,你会看到它有一个构造函数,它接受一个文件名作为输入和一个析构函数,它会自动为你关闭文件。通过构造函数打开文件将返回一个具有状态的文件流变量。顺便说一句,这对于任何流都是正确的。
背景是,它的bool-operator 被覆盖并将返回流的状态。 not-operator ! 也被覆盖并且可以使用。由于这些被覆盖的运算符,您可以编写类似if (filestream) 的内容来查看是否可以打开文件。
此外,从 C++ 17 开始,我们有一个扩展的if-statement,您可以在其中在条件前面使用初始化列表。这很重要,因为它允许我们定义一个变量,稍后将对其进行检查,但其范围仅限于 if 复合语句。在大多数情况下,这是非常推荐的。示例:
// Open a file and check, if it could be opened
if (std::ifstream infile("file.txt"); infile) {
// .... Do things fith file stream
} // Here the file will be closed automatically by the destructor
比无用的open 和close 语句要好得多。
现在,在我们考虑了设计之后,现在我们可以开始编写代码了。以前没有。
所以,我们现在得到:
#include <iostream>
#include <fstream>
#include <string>
#include <filesystem>
#include <unordered_map>
#include <set>
#include <type_traits>
#include <utility>
// ------------------------------------------------------------
// Create aliases. Save typing work and make code more readable
using Pair = std::pair<std::string, unsigned int>;
// Standard approach for counter
using Counter = std::unordered_map<Pair::first_type, Pair::second_type>;
// Sorted values will be stored in a multiset
struct Comp { bool operator ()(const Pair& p1, const Pair& p2) const { return (p1.second == p2.second) ? p1.first<p2.first : p1.second>p2.second; } };
using Sorter = std::multiset<Pair, Comp>;
// Namespace alias
namespace fs = std::filesystem;
// ------------------------------------------------------------
int main() {
// Open the source file and check, if it could be opened
if (std::ifstream inFileStream{ "r:\\file.txt" }; inFileStream) {
// Here we will count the extensions of the file names
Counter counter{};
// Read source file strings and count the extensions
std::string line{};
// Read all lines from file
while (std::getline(inFileStream, line))
// Get extensions and count them
counter[ fs::path{ line }.extension().string() ]++;
// Show result to the user.
for (const Pair& p : counter) std::cout << p.first << " --> " << p.second << '\n';
} // File will be closed here
else {
// file could not be opened
std::cerr << "\n\n*** Error: Input file could not be opened\n\n";
}
}
函数main中只有8条语句,我们可以完成所有需要的任务,包括各种路径格式和错误处理。
可以进行更多优化。
正如我所提到的,拥有一个狭窄的变量范围总是一个好概念。在上面的代码中,我们可以看到变量“line”被定义在while循环的外部范围内。那是没有必要的。而且因为我们for和while循环基本相同,我们可以更好地使用for循环,因为它有一个初始化部分。
代替
std::string line{};
// Read all lines from file
while (std::getline(inFileStream, line))
我们可以写
for (std::string line{};std::getline(inFileStream, line); )
我们甚至可以夸大一点,在for-loop 的迭代表达式部分进行计数。并在一个 for 声明中完成整个阅读和计数
for (std::string line{}; std::getline(inFileStream, line); counter[fs::path{ line }.extension().string()]++)
;
所以,在一行代码的一个语句中完成文件的完整读取和各种扩展的完整计数。哇!
但可读性有点低,我们不会使用它。
在输出语句中,我们可以做一些更易读的东西。基本上Pair 和.first 和.second 不是那么好和可以理解的。 C++ 也有一个解决方案。它被称为structured binding。
综上所述,我们现在来到了最终的实现,包括按频率排序的输出:
#include <iostream>
#include <fstream>
#include <string>
#include <filesystem>
#include <unordered_map>
#include <set>
#include <type_traits>
#include <utility>
// ------------------------------------------------------------
// Create aliases. Save typing work and make code more readable
using Pair = std::pair<std::string, unsigned int>;
// Standard approach for counter
using Counter = std::unordered_map<Pair::first_type, Pair::second_type>;
// Sorted values will be stored in a multiset
struct Comp { bool operator ()(const Pair& p1, const Pair& p2) const { return (p1.second == p2.second) ? p1.first<p2.first : p1.second>p2.second; } };
using Sorter = std::multiset<Pair, Comp>;
// Namespace alias
namespace fs = std::filesystem;
// ------------------------------------------------------------
int main() {
// Open the source file and check, if it could be opened
if (std::ifstream inFileStream{ "r:\\file.txt" }; inFileStream) {
// Here we will count the extensions of the file names
Counter counter{};
// Read all lines from file
for (std::string line{}; std::getline(inFileStream, line); )
// Get extensions and count them
counter[ fs::path{ line }.extension().string() ]++;
Sorter sorter(counter.begin(), counter.end());
// Show result to the user.
for (const auto& [extension, count] : sorter) std::cout << extension << " --> " << count << '\n';
} // File will be closed here
else {
// file could not be opened
std::cerr << "\n\n*** Error: Input file could not be opened\n\n";
}
}