Проблем със задача 8 от C++ Programming - февруари 2017
8. Write a function
int * parseNumbers(const string& str, int& resultLength) which returns a pointer to new-allocated array with the numbers parsed from str (assume you don’t need to handle wrongly-formatted input). str will contain integer numbers separated by spaces. The function writes the length of the allocated array in resultLength. Write a program which lets the user enter a number of lines of integers from the console, and prints their sum. Use the parseNumbers function in your program, but make sure you delete each array once you’re done with it.
Example input (note: first line is the count of lines of numbers, in this case: 2 lines):
2
1 2 3
4 5
Expected output (sum of 1 2 3 and 4 5): 15
Въпросът ми е как едновременно да създам stringstream oт потребителския стринг с данни str във функцията int * parseNumbers(const string& str, int& resultLength) и като викам същата функция да й подам параметър за размера на stringstream, който се намира в нея?
int * parseNumbers(const string& str, int& resultLength)
{
int *userInputNums = new int[resultLength];
stringstream userInputStream(str);
int size = 0;
while(1)
{
int i;
userInputStream >> i;
if (!userInputStream)
{break;}
++size;
}
for (int i = 0; i < resultLength; i++)
{
userInputStream >> userInputNums[i];
}
return userInputNums;
}
Идеята на @MartinBg е вярната (можете да му маркирате отговорa му за верен и да го upvote-нете). Ето малко разяснения от мен.
Стандартния подход за "връщане" на масив от функция (без да се ползват vector или други stl контейнери, за които все още не сме учили) е да върнеш pointer към динамично заделен масив (заделен с new тоест) - но понеже трябва да знаеш и размера на този масив, за да го използваш от викащия код, отделно в една референция записваш колко се получава да е дълъг. Пример за код, който вика тази функция и принтира елементите на масива ред по ред би изглеждал така:
string input;
cin >> input;
int parsedLength; //we expect parseNumbers to set the value of this
int * parsed = parseNumbers(input, parsedLength)
for (int i = 0; i < parsedLength; i++) {
cout << parsed[i] << endl;
delete[] parsed;
}
Един прост начин да знаете колко памет да заделите в parseNumbers е два пъти подред да направите stringstream по input-а - както правихме в едно от демата за stringstream (това с HTML output-а) - първия път само броите колко числа сте прочели, след това заделяте масив с тази големина, след това минавате пак по input-а със stringstream и тогава вече записвате числата по съответните позиции в масива. Това не е най-ефикасния метод, но е приемлив за тази задача.
Най-добрия вариант е да преброите числата на базата на space-овете пред тях (обърнете внимание, че в задачата изрично пише, че числата са разделени със space, тоест всеки два поредни символа, първия от които е space, а за втория isdigit връща true, e число (не забравяйте да преброите и първото число). Не е достатъчно да преброите само space-овете, защото задачата не гарантира, че числата са разделени само с по един space (тоест това е валиден вход: 1 3 7 13).
Има и трети, "по-напреднал" вариант, който ако се замислите добре можете да го постигнете със знанията дотук, ако на някой му се занимава - проучете как горе-долу работи vector в C++, или List в C#, или ArrayList в Java (един и същ принцип стои зад тях).
Поздрави,
Жоро
Edit: демо 19 от лекцията (за което нямахме време на самата лекция) ползва същата концепция - само че то връща string* към динамично заделен масив. И там заделянето на памет нарочно е направено с лош performance, за да е ясно как може да се заделят и освобождават в една функция много масиви (има коментар с подробности там) - подобен на този код, само че за int, би ви вършил работа, но ви препоръчвам да ползвате някое от горните предложения.
А премливо ли е, след като прочетем първото число и знаем колко реда да очакваме да си създадем (динамично) масив от толкова на брой указатели, всеки от които да сочи към отделен масив (пак динамично заделен). И чрез един loop да си заредим с getline всеки от масивите. Зная ,че това е по-скоро C style подход но със сегашните познания е работещ вариант, поне за мен.
Да, но бих казал, че в случая е ненужно да държиш всичката памет заета до края на програмата - предвид, че само ще сумираш елементи, е достатъчно да четеш по един масив, да го обработваш (сумираш) и след това да освобождаваш паметта. Този масив няма да ти трябва след като го сумираш веднъж, така че няма смисъл да стои в паметта след това. Разбира се, пак трябва всеки един getline ред да го обработиш в тази parseNumbers функция, за да получиш pointer-а, който искаш да запазиш в масива от pointer-и (иначе няма как да знаеш предварително колко точно числа има на този ред и няма как да заделиш предварително за самите числа тази памет - но можеш да я заделиш за редовете, тоест за броя масиви, както ти предлагаш)
Но да, приемливо е, ще реши задачата - в интерес на истината алгоритъма може да се направи без масив изобщо, но идеята на задачата е да упражните заделяне на масив в една функция и използване в друга.