Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should I be attempting to return an array, or is there a better solution?

Tags:

c++

arrays

A problem set for people learning C++ is

Write a short program to simulate a ball being dropped off of a tower. To start, the user should be asked for the initial height of the tower in meters. Assume normal gravity (9.8 m/s2), and that the ball has no initial velocity. Have the program output the height of the ball above the ground after 0, 1, 2, 3, 4, and 5 seconds. The ball should not go underneath the ground (height 0).

Before starting C++ I had a reasonable, but primarily self taught, knowledge of Java. So looking at the problem it seems like it ought to be split into

  • input class
  • output class
  • calculations class
  • Physical constants class (recommended by the question setter)
  • controller ('main') class

The input class would ask the user for a starting height, which would be passed to the controller. The controller would give this and a number of seconds (5) to the calculations class, which would create an array of results and return this to the controller. The controller would hand the array of results to the output class that would print them to the console.

I will put the actual code at the bottom, but it's possibly not needed.

You can probably already see the problem, attempting to return an array. I'm not asking how to get round that problem, there is a workaround here and here. I'm asking, is the problem a result of bad design? Should my program be structured differently, for performance, maintenance or style reasons, such that I would not be attempting to return an array like object?

Here is the code (which works apart from trying to return arrays);

main.cpp

/*
 * Just the main class, call other classes and passes variables around
 */
#include <iostream>
#include "dropSim.h"
using namespace std;

int main()
{
    double height = getHeight();
    int seconds = 5;
    double* results = calculateResults(height, seconds);
    outputResults(results);
    return 0;
}

getHeight.cpp

/*
 *  Asks the user for a height from which to start the experiment
 *  SI units
 */
#include <iostream>
using namespace std;

double getHeight()
{
    cout << "What height should the experiment start at; ";
    double height;
    cin >> height;
    return height;
}

calculateResults.cpp

/*
 * given the initial height and the physical constants, the position of the ball
 * is calculated at integer number seconds, beginning at 0
 */

#include "constants.h"
#include <cmath>
#include <iostream>
using namespace std;

double getPosition(double height, double time);

double* calculateResults(double height, int seconds)
{

    double positions[seconds + 1];
    for(int t = 0; t < seconds + 1; t++)
    {
        positions[t] = getPosition(height, t);
    }
    return positions;
}

double getPosition(double height, double time)
{
    double position = height - 0.5*constants::gravity*pow(static_cast<double>(time), 2);
    if( position < 0) position = 0;
    //Commented code is for testing
    //cout << position << endl;
    return position;
}

outputResults.cpp

/*
 *      Takes the array of results and prints them in an appropriate format
 */


#include <iostream>
#include <string>
#include <sstream>

using namespace std;

void outputResults(double* results){
    string outputText = "";
    //The commented code is to test the output method
    //Which is working
    //double results1[] = {1,2,3,4,5};
    //int numResults = sizeof(results1)/sizeof(results1[0]);
    int numResults = sizeof(results)/sizeof(results[0]);
    //cout << numResults; //= 0 ... Oh
    for(int t = 0; t < numResults; t++)
    {
        ostringstream line;
        line << "After " << t << " seconds the height of the object is " << results[t] << "\r";
        outputText.append(line.str());

    }
    cout << outputText;
}

And finally a couple of headers; dropSim.h

/*
 * dropSim.h
 */

#ifndef DROPSIM_H_
#define DROPSIM_H_

double getHeight();

double* calculateResults(double height, int seconds);

void outputResults(double* results);

#endif /* DROPSIM_H_ */

constants.h

/*
 *      Contains physical constants relevant to simulation.
 *      SI units
 */

#ifndef CONSTANTS_H_
#define CONSTANTS_H_

namespace constants
{
    const double gravity(9.81);
}

#endif /* CONSTANTS_H_ */
like image 742
Jekowl Avatar asked Dec 24 '22 18:12

Jekowl


2 Answers

I would say that you're over-engineering a big solution to a little problem, but to answer your specific question:

Should my program be structured differently, for performance, maintenance or style reasons, such that I would not be attempting to return an array like object?

Returning an array-like object is fine. But that doesn't mean returning an array, nor does it mean allocating raw memory with new.

And it's not restricted to return values either. When you're starting out with C++, it's probably best to just forget that it has built-in arrays at all. Most of the time, you should be using either std::vector or std::array (or another linear collection such as std::deque).

Built-in arrays should normally be viewed as a special-purpose item, included primarily for compatibility with C, not for everyday use.

It may, however, be worth considering writing your computation in the same style as the algorithms in the standard library. This would mean writing the code to receive an iterator to a destination, and writing its output to wherever that iterator designates.

I'd probably package the height and time together as a set of input parameters, and have a function that generates output based on those:

struct params {
    double height;
    int seconds;
};

template <class OutIt>
void calc_pos(params const &p, OutIt output) { 
    for (int i=0; i<p.seconds; i++) {
        *output = get_position(p.height, i);
        ++output;
    }
}

This works somewhat more clearly along with the rest of the standard library:

std::vector<double> results;

calc_pos(inputs, std::back_inserter(results));

You can go a few steps further if you like--the standard library has quite a bit to help with a great deal of this. Your calc_pos does little more than invoke another function repeatedly with successive values for the time. You could (for example) use std::iota to generate the successive times, then use std::transform to generate outputs:

std::vector<int> times(6);

std::iota(times.begin(), times.end(), 0);

std::vector<double> distances;

std::transform(times.begin(), times.end(), compute_distance);

This computes the distances as the distance dropped after a given period of time rather than the height above the ground, but given an initial height, computing the difference between the two is quite trivial:

double initial_height = 5;

std::vector<double> heights;

std::transform(distances.begin(), distances.end(), 
    std::back_inserter(heights),
    [=](double v) { return max(initial_height-v, 0); });

At least for now, this doesn't attempt to calculate the ball bouncing when it hits the ground--it just assumes the ball immediately stops when it hits the ground.

like image 60
Jerry Coffin Avatar answered May 19 '23 03:05

Jerry Coffin


You should get rid of self-allocated double * and use std::vector<double> instead. It's not difficult to learn and a basic step in modern C++

like image 45
marom Avatar answered May 19 '23 05:05

marom