Week 2

Data Flows

You may return more than one value from a function by using reference parameters to return values through the argument list.

As an example, suppose that you are writing a program to solve the quadratic equation below and you want to structure that program into three IPO phases like this: The three IPO phases to solve the quadratic equation.

Using this plan, your main function might look like this:

int main()
{
    double a, b, c, root1, root2;
    getCoefficients(a, b, c); 
    solveQuadratic(a, b, c, root1, root2);
    printRoots(root1, root2);
}

Here's what's happening:

  • In line 4, the variables a, b and c are output parameters. Instead of passing values into the function, we're supplying three variables which will be filled up in the funciton itself.
  • In line 5, a, b and c are input parameters. We are supplying their values to the solveQuadratic function so that it can do its work.
  • The variables root1 and root2 are output parameters on Line 5; they will be filled in inside the function. On line 6, they are input parameters.
Week 2

Output Parameters

Let's take a quick look at the code for getCoefficients() to see how output parameters work.

void getCoefficients(double& x, double& y, double& z)
{
    cout << "Enter 3 coefficients: ";
    cin >> x >> y >> z;
}

If a function returns more than one piece of information, then you can use reference parameters to return that information to the caller.

Note that when you call getCoefficients, information does not flow from main into the function; instead, information flows out of the function back to main, through the three output parameters x, y, and z, which are not new variables, but are new names or aliases for the variables a, b, and c used when calling it.

Instead of separate inputs, this function reads three variables using a single input statement. The values entered by the user must be separated from each other by whitespace, not commas. Spaces, tabs or newlines all work fine.

When documenting your parameters, you may annotate each of the parameters with the direction of the information flow: @param[in], @param[in,out], @param[out]. If you don't annotate the parameter, it is assumed to be an input parameter.

Week 2

Input and Output Parameters

The solveQuadratic() function needs to use both input and output parameters. The arguments a, b, and c are input in this function, while root1 and root2 are output parameters, allowing the function to pass back the two roots to main.

void solveQuadratic(double a, double b, double c,     // input
                    double& r1, double& r2)           // output
{
    if (a == 0) die("a is 0; Illegal");   // no discriminate
    
    double discriminate = b * b - 4 * 1 * c;
    if (discriminate < 0) die("No real roots");
    
    double sqrtDisc = sqrt(discriminate);
    r1 = (-b + sqrtDisc) / (2 * a);
    r2 = (-b - sqrtDisc) / (2 * a);
}

Fatal Errors

Whenever this code encounters a condition that makes further progress impossible, it calls a function die() which prints a message and then terminates the program.

void die(const string& msg, int code = -1)
{
    cerr << "FATAL ERROR: " << msg << endl;
    exit(code);
}
  • The cerr standard stream is similar to cout, but is reserved for reporting errors.
  • The exit() function terminates a program immediately, using the value of the parameter to report the program status.
  • The default error code is set to -1. If you want to use different error codes for different errors, just pass the code when you call die() (preferably as a const).

This function could be useful in many programs, so you might put it in a utility library.

Week 2

Input-Output Parameters

We can use a single parameter for both input and for output. Consider toUpperCase() in Java. It takes a String as an argument, and returns a new, uppercase version of the original.

String str = "cat";
str = str.toUpperCase();  // CAT

This builder method does not (indeed, in Java, cannot) change its argument. However, that is a little inefficient, especially when assigned to the same variable, as we've done here.

Since C++ strings may be modified, we can write a more efficient version like this:

void toUpperCase(string& str)
}
    for (auto& c : str) { c = toupper(c); } 
}

Here, str provides both input and output. We call this and input-output parameter. Because of that, it is passed by reference, not const reference. Note also that the loop variable c is a reference, not a value, so we can modify the character it refers to. Here's how to use it:

string str = "cat";
toUpperCase(str);  // Not str = toUpperCase(str)
Week 2

Data Flow Checklists

Consider the string::getline(in, str) function:

  • in is an input-output parameter. The function depends on the stream's initial state (formatting, etc.) and it is changed by calling the function (setting the error value).
  • str is an output only parameter; it makes no difference what is inside str when you call the function—data only flows out.

The Java concept of data flow—parameters are input, return statements are output—is too simplistic for C++. In C++ (as in many other languages), parameters can be used as input, as output, or as a combination of both.

Use this checklist to determine the direction of data flow:

  • Argument not modified by function: input parameter
  • Argument modified, input value not used: output parameter
  • Argument used and changed by function: input-output parameter

Use this checklist to determine how to declare the parameter variable:

  • Output and Input-Output parameters: by reference
  • Input primitive (built-in and enumerated) types: by value
  • Input library and class types: by const reference

Never pass by value for class or library types.