基本上你的程序是可以工作的。我已经编译并修复了一些小问题。
但是非常重要的是,你需要释放使用 new 分配的内存。
请参考以下修复后的示例:
#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <string>
int main(int argc, char* argv[]) {
int numNames = 32;
int numExams = 32;
std::cout << "Input file: " << argv[1] << std::endl;
std::ifstream in(argv[1]);
if (!in) {
std::cerr << "Unable to open " << argv[1] << " for input";
return 2;
}
std::cout << "Output file: " << argv[2] << std::endl;
std::ofstream out(argv[2]);
if (!out) {
in.close();
std::cerr << "Unable to open " << argv[2] << " for output";
return 3;
}
if (in.is_open()) {
in >> numNames >> numExams;
std::string* fullNames = new std::string[numNames];
double** scores = new double* [numNames];
for (int i = 0; i < numNames; ++i) {
scores[i] = new double[numExams];
}
std::string currLine;
for (int i = 0; i < numNames; ++i) {
std::string firstName, lastName;
in >> firstName >> lastName;
for (int j = 0; j < numExams; ++j) {
in >> scores[i][j];
}
fullNames[i] = firstName + " " + lastName;
}
if (out.is_open()) {
for (int i = 0; i < numNames; ++i) {
out << fullNames[i] << ":";
for (int j = 0; j < numExams; ++j) {
out << " " << scores[i][j];
}
out << "\n";
}
}
for (int i = 0; i < numNames; ++i) {
delete[] scores[i];
}
delete[] scores;
delete[] fullNames;
if (!out.is_open()) {
return -3;
}
}
else {
return -2;
}
return 0;
}
如果我们在软件方面再多花一点功夫,它就可以看起来像这样:
#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <string>
int main(int argc, char* argv[]) {
if (3 == argc) {
std::cout << "\nProgram will work with files: '" << argv[1] << "' and '" << argv[2] << "'\n";
if (std::ifstream in(argv[1]); in) {
if (std::ofstream out(argv[2]); out) {
if (size_t numNames{}, numExams{}; (in >> numNames >> numExams) && (numNames > 0U) && (numExams > 0U)) {
std::string* fullNames = new std::string[numNames];
double** scores = new double* [numNames];
for (int i = 0; i < numNames; ++i) {
scores[i] = new double[numExams];
}
for (size_t i = 0U; i < numNames; ++i) {
std::string firstName, lastName;
in >> firstName >> lastName;
for (size_t j = 0U; j < numExams; ++j) {
in >> scores[i][j];
}
fullNames[i] = firstName + " " + lastName;
}
for (size_t i = 0U; i < numNames; ++i) {
out << fullNames[i] << ":";
for (size_t j = 0U; j < numExams; ++j) {
out << " " << scores[i][j];
}
out << "\n";
}
for (int i = 0; i < numNames; ++i) {
delete[] scores[i];
}
delete[] scores;
delete[] fullNames;
}
}
else {
std::cerr << "\nError: Could not open output file '" << argv[2] << "'\n";
}
}
else {
std::cerr << "\nError: Could not open input file '" << argv[1] << "'\n";
}
}
else {
std::cerr << "\nError: Please call program with 2 filenames for input data and output data\n\n";
}
return 0;
}
最后一步是采用更现代的C++方法。
在C++中,我们不使用原始指针来管理内存,也不应该使用new。如果必须使用,我们应该使用std::unique_ptr
和std::make_unique
。但是,对于数组不适用。
对于动态数组,我们将始终使用std::vector
或类似的容器。它们几乎完美地适合所需的任务。而且,这样做会让生活更轻松。
此外,C++是一种面向对象的语言。我们使用由数据和操作数据的函数组成的对象。
因此,我定义了一个名为Student的类(结构体),其中包含姓名和分数。由于这是一个对象,它知道如何读取和写入其数据。在类外部,没有人关心这些。
我们重载了这个类的插入器和提取器运算符,然后可以像标准数据类型一样使用类的实例与插入器和提取器运算符。因此,可以编写std::cout << student
。
这使得进一步的操作更加容易。并且它更适合C++的其他algorithm
。
重要通知。此方法不需要输入文件中的第一行。它是完全动态的!另外,每行分数的数量可能会有所不同。
让我们来看一下提取器。它首先读取完整的一行,并将这一行放入一个istringstream对象中。
接下来,我们定义了一个名为“part”的std::vector,并使用其范围构造函数填充它。范围构造函数以某个范围的开头和结尾作为参数获取一个迭代器,并将数据复制到自身中。
开始的迭代器是std:istream_iterator,此处为std::string。这个迭代器将简单地调用给定流的提取运算符(>>),直到读取所有数据。结束标记为{}。那是默认构造的std::istream_iterator,也称为流结束迭代器。请参见here。
请注意:我们可以在不带模板参数的情况下定义std::vector。编译器可以从给定的函数参数中推断出参数。这个特性被称为CTAD(“类模板参数推导”)。
因此,我们在名为“part”的std::vector
中拥有所有子字符串。在此函数的末尾,我们只需将相关部分复制到我们类的内部数据中。
对于分数,我们将字符串转换为double,并使用std::back_inserter
动态地将分数添加到我们的分数-vector
中。
输出时,插入运算符(<<)要简单得多。我们只需将结果复制到流中即可。在这里,我们还利用了标准算法库中的std::copy
函数和std::ostream:iterator
。这个迭代器也非常有用。它只为每个给定的元素调用插入运算符(<<)。
因此,在定义了类之后,主程序中的完整程序仅包含两个重要语句:
std::vector roster(std::istream_iterator<Student>(in), {});
std::copy(roster.begin(), roster.end(), std::ostream_iterator<Student>(out, "\n"));
只需两行代码,我们就可以:1. 读取完整的文件并2. 将其写入所需的输出文件。请再次注意,即使这也可以优化为一条语句:
std::copy(std::istream_iterator<Student>(in), {}, std::ostream_iterator<Student>(out, "\n"));
因此,本质上我们得到了一个一行代码...
请查看完整的代码示例:
#include <iostream>
#include <vector>
#include <fstream>
#include <string>
#include <sstream>
#include <algorithm>
#include <iterator>
struct Student {
std::string firstName{};
std::string lastName{};
std::vector <double>scores{};
friend std::istream& operator >> (std::istream& is, Student& st) {
if (std::string line{}; std::getline(is, line)) {
std::istringstream iss(line);
std::vector part(std::istream_iterator<std::string>(iss), {});
if (part.size() > 1) {
st.firstName = part[0]; st.lastName = part[1];
st.scores.clear();
std::transform(std::next(part.begin(), 2), part.end(), std::back_inserter(st.scores), [](const std::string& s) {return std::stod(s); });
}
}
return is;
}
friend std::ostream& operator << (std::ostream& os, const Student& st) {
os << st.firstName << " " << st.lastName << ": ";
std::copy(st.scores.begin(), st.scores.end(), std::ostream_iterator<double>(os, " "));
return os;
}
};
int main(int argc, char* argv[]) {
if (3 == argc) {
if (std::ifstream in(argv[1]); in) {
if (std::ofstream out(argv[2]); out) {
std::copy(std::istream_iterator<Student>(in), {}, std::ostream_iterator<Student>(out, "\n"));
} else { std::cerr << "\nError: Could not open output file '" << argv[2] << "'\n";}
} else { std::cerr << "\nError: Could not open input file '" << argv[1] << "'\n"; }
} else { std::cerr << "\nError: Please call program with 2 filenames for input data and output data\n\n";}
return 0;
}
真遗憾你很可能会继续使用你的新
方案...
numNames
行,不多也不少?奖励问题:你的程序中哪个部分确保恰好处理每行的numExams
个分数? - Sam Varshavchikdouble** scores = new double* [numNames]; for (int i = 0; i < numNames; ++i) {scores[i] = new double[numExams];}
即使您无法使用向量,也应该被禁止。这是最糟糕的做法之一。 - PaulMcKenzie