Хорошо, на основании вашего комментария я вижу, где вы застряли и почему. (также вам нужно исправить переменные цикла в draw_square
и draw_rect
, если вы еще этого не сделали).
Ваша основная проблема - не понимание того, как обрабатывать различное количество входов на строку. Когда вы столкнулись с этой проблемой, вы правильно выбрали getline
, чтобы прочитать каждую строку в buffer
, но что тогда? Вот где stringstream
имеет значение.
Почему? Две причины: (1) это позволяет вам анализировать содержимое buffer
слово за словом с базовым iostream >>
и (2) оно, когда это необходимо, позволяет вам зацикливаться до конца чтения потока, как много (или так мало) токенов, которые присутствуют, останавливаются, когда вы достигаете конца строки (что невозможно при использовании >>
в самом файловом потоке, так как >>
занимает пустое пространство и с радостью пропускает право на каждый '\n'
)
Учитывая это, ваш код действительно нуждался в небольшом рефакторинге (причудливое слово для исправления перемешанной логики).
Для начала Не кодировать жестко имена файлов или использовать магические числа в своем коде. Используйте аргументы для main()
, чтобы передать имена файлов в вашу программу и объявить константы, где это необходимо. Также избегайте использования char
, который не будет занимать начальные пробелы. cin >> a_char;
так же рад читать ' '
(пробел), как и читать что-то еще.
Также укажите ваши переменные соответствующим образом. Вам не нужно объявлять все переменные, чтобы они были видны в main()
. Объявите / определите их в соответствующих областях.
Например:
...
#include <sstream>
...
int main (int argc, char **argv) { /* don't hardcode filenames */
ifstream infile; /* infile and buffer are the only variables */
string buffer; /* that need to be scoped at main() */
При передаче имени файла в качестве аргумента, просто подтвердите, что ваш пользователь предоставил имя файла, или предоставьте ему информацию об использовании перед отправкой под залог.
if (argc < 2) { /* validate at least 1 argument is provided */
cerr << "error: insufficient input.\n"
"usage: " << argv[0] << " filename.\n";
return 1;
}
У вас есть аргумент, теперь подтвердите, что открываете файл для чтения:
infile.open (argv[1]); /* open filename provided as 1st argument */
if(!infile.good()) { /* validate file is open for reading */
cerr << "failed to open infile\n";
return 1;
}
Теперь важные изменения в том, как вы управляете циклом чтения. getline
предоставляет все необходимое. Просто выполните цикл, пока getline
обеспечивает хороший ввод для buffer
, например,
while (getline(infile, buffer)) { /* loop reading each line */
int row, col; /* remaining variables scoped inside */
string value, code; /* your read loop, use strings */
stringstream ss(buffer); /* create stringstream from buffer */
Теперь вы читаете каждую строку и создали stringstream
из buffer
для анализа ваших символов - кроме note как value, code
объявляется как string
, а не char
- это простой способ пропустить начальные пробелы только при чтении непробельных символов. Затем вы можете просто получить доступ к нужному персонажу, например, value[0]
.
Подтвердите, что вы хорошо прочитали code
if (!(ss >> code)) { /* validate code read into string */
cerr << "error: ss >> code.\n";
break;
}
Тогда нужно просто повторить ту же проверку чтения необходимых данных и вызвать правильную функцию в каждом switch() case:
, например,
switch (code[0]) /* switch on 1st char of code */
{
case 'R':
if ((ss >> value >> row >> col)) /* validate read */
draw_rect (value[0], row, col); /* draw rect */
else /* or handle error */
cerr << "error: 'R' invalid format '" << buffer << "'\n'";
break;
case 'T':
if ((ss >> value >> row)) /* ditto for rest of shapes */
draw_triangle(value[0], row);
else
cerr << "error: 'T' invalid format '" << buffer << "'\n'";
break;
case 'D':
if ((ss >> value >> row))
draw_diamond(value[0], row);
else
cerr << "error: 'D' invalid format '" << buffer << "'\n'";
break;
case 'S':
if ((ss >> value >> row))
draw_square(value[0], row);
else
cerr << "error: 'S' invalid format '" << buffer << "'\n'";
break;
case 'E':
cout << "Exiting\n";
goto exitE; /* goto to break nested loops / scopes */
break;
default:
cout << "Invalid input, try again" << endl;
}
}
exitE:; /* the lowly goto provides a simple exit */
Это все, кроме закрытия infile
(что произойдет автоматически, но не помешает вручную показать ваше закрытие).
Примечание однако используется goto
вместо флага, который вы использовали для exit
. В то время как goto
не получает большого давления, у него есть одна бесценная цель, которая остается - способность вырваться из вложенных циклов и областей видимости. Не используйте его, чтобы выпрыгнуть из функций (longjmp
является техническим ограничением), но это может значительно упростить вашу логику для разрыва вложенных циклов и перехода на несколько строк вниз. (также полезно в той же настройке переходить через код, обычно выполняемый в конце ваших циклов при условии ошибки)
Так что понимайте его использование. Вы можете использовать флаг, но вы можете найти goto
чище в нескольких настройках.
С этим вы можете сложить все вместе (пока игнорируя outfile
) с чем-то похожим на:
#include <iostream>
#include <fstream>
#include <sstream>
#include <cstdlib>
using namespace std;
void draw_rect (char out_char, int rows, int columns); // Draws a rectangle shape
void draw_square (char out_char, int rows); //Draws a square shape
void draw_triangle (char out_char, int rows);// Draws a triangle shape
void draw_diamond (char out_char, int rows); // Draws a diamond shape
int main (int argc, char **argv) { /* don't hardcode filenames */
ifstream infile; /* infile and buffer are the only variables */
string buffer; /* that need to be scoped at main() */
if (argc < 2) { /* validate at least 1 argument is provided */
cerr << "error: insufficient input.\n"
"usage: " << argv[0] << " filename.\n";
return 1;
}
infile.open (argv[1]); /* open filename provided as 1st argument */
if(!infile.good()) { /* validate file is open for reading */
cerr << "failed to open infile\n";
return 1;
}
while (getline(infile, buffer)) { /* loop reading each line */
int row, col; /* remaining variables scoped inside */
string value, code; /* your read loop, use strings */
stringstream ss(buffer); /* create stringstream from buffer */
if (!(ss >> code)) { /* validate code read into string */
cerr << "error: ss >> code.\n";
break;
}
switch (code[0]) /* switch on 1st char of code */
{
case 'R':
if ((ss >> value >> row >> col)) /* validate read */
draw_rect (value[0], row, col); /* draw rect */
else /* or handle error */
cerr << "error: 'R' invalid format '" << buffer << "'\n'";
break;
case 'T':
if ((ss >> value >> row)) /* ditto for rest of shapes */
draw_triangle(value[0], row);
else
cerr << "error: 'T' invalid format '" << buffer << "'\n'";
break;
case 'D':
if ((ss >> value >> row))
draw_diamond(value[0], row);
else
cerr << "error: 'D' invalid format '" << buffer << "'\n'";
break;
case 'S':
if ((ss >> value >> row))
draw_square(value[0], row);
else
cerr << "error: 'S' invalid format '" << buffer << "'\n'";
break;
case 'E':
cout << "Exiting\n";
goto exitE; /* goto to break nested loops / scopes */
break;
default:
cout << "Invalid input, try again" << endl;
}
}
exitE:; /* the lowly goto provides a simple exit */
infile.close();
return 0;
}
void draw_diamond (char out_char, int rows)
{
int space = 1;
space = rows - 1;
for (int i = 1; i <= rows; i++)
{
for (int k = 1; k <= space; k++)
{
cout << " ";
}
space--;
for( int k = 1; k <= 2*i-1; k++)
{
cout << out_char;
}
cout << endl;
}
space = 1;
for (int i = 1; i <= rows; i++)
{
for(int k = 1; k <= space; k++)
{
cout << " ";
}
space++;
for(int k = 1; k <= 2*(rows-i)-1; k++)
{
cout << out_char;
}
cout << endl;
}
}
void draw_triangle (char out_char, int rows)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j <= i; j++)
{
cout << out_char;
}
cout << endl;
}
}
void draw_square (char out_char, int rows)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < rows; j++)
{
cout << out_char;
}
cout << endl;
}
}
void draw_rect (char out_char, int rows, int columns)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < columns; j++)
{
cout << out_char;
}
cout << endl;
}
}
( примечание: переменная цикла i, j
исправляет draw_square
и draw_rect
, которые, по-видимому, были ошибками копирования / вставки - кроме того, в функции формы не было внесено никаких изменений)
Пример использования / Вывод
$ ./bin/drawshapes dat/drawshapes.txt
&
&&
&&&
&&&&
@@@@@@
@@@@@@
@@@@@@
@@@@@@
@@@@@@
@@@@@@
x
xx
xxx
xxxx
xxxxx
*******
*******
*******
*******
*******
$
$$$
$$$$$
$$$$$$$
$$$$$$$$$
$$$$$$$$$$$
$$$$$$$$$$$$$
$$$$$$$$$$$
$$$$$$$$$
$$$$$$$
$$$$$
$$$
$
+
+++
+++++
+++++++
+++++++++
+++++++
+++++
+++
+
===
===
===
===
Exiting
Посмотрите вещи и дайте мне знать, если у вас есть дополнительные вопросы.