EXPLORATION 20

image

Writing Functions

At last, it’s time to embark on the journey toward writing your own functions. In this Exploration, you’ll begin by improving the word-counting program you’ve been crafting over the past five Explorations, writing functions to implement separate aspects of the program’s functionality.

Functions

You’ve been using functions since the very first program you wrote. In fact, you’ve been writing functions too. You see, main() is a function, and you should view it the same as you would any other function (well, sort of, main() actually has some key differences from ordinary functions, but they needn’t concern you yet).

A function has a return type, a name, and parameters in parentheses. Following that is a compound statement, which is the function body. If the function has no parameters, the parentheses are empty. Each parameter is like a variable declaration: type and name. Parameters are separated by commas, so you cannot declare two parameters after a single type name. Instead, you must specify the type explicitly for each parameter.

A function usually has at least one return statement, which causes the function to discontinue execution and return control to its caller. A return statement’s structure begins with the return keyword, followed by an expression, and ends with a semicolon, as demonstrated in the following example:

return 42;

You can use a return statement anywhere you need a statement, and you can use as many return statements as you need or want. The only requirement is that every execution path through the function must have a return statement. Many compilers will warn you if you forget.

Some languages distinguish between functions, which return a value, and procedures or subroutines, which do not. C++ calls them all functions. If a function has no return value, declare the return type as void. Omit the value in the return statements in a void function:

return;

You can also omit the return statement entirely, if the function returns void. In this circumstance, control returns to the caller when execution reaches the end of the function body. Listing 20-1 presents some function examples.

Listing 20-1.  Examples of Functions

#include <iostream>
#include <string>
 
/** Ignore the rest of the input line. */
void ignore_line()
{
  char c{};
  while (std::cin.get(c) and c != ' ')
 
    /*empty*/;
}
 
/** Prompt the user, then read a number, and ignore the rest of the line.
 * @param prompt the prompt string
 * @return the input number or 0 for end-of-file
 */
int prompted_read(std::string prompt)
{
  std::cout << prompt;
  int x{0};
  std::cin >> x;
  ignore_line();
  return x;
}
 
/** Print the statistics.
 * @param count the number of values
 * @param sum the sum of the values
 */
void print_result(int count, int sum)
{
  if (count == 0)
  {
    std::cout << "no data ";
    return;
  }
 
  std::cout << " count = " << count;
  std::cout << " sum   = " << sum;
  std::cout << " mean  = " << sum/count << ' ';
}
 
/** Main program.
 * Read integers from the standard input and print statistics about them.
 */
int main()
{
  int sum{0};
  int count{0};
 
  while (std::cin)
  {
    int x{ prompted_read("Value: ") };
    if (std::cin)
    {
      sum = sum + x;
      ++count;
    }
  }
  print_result(count, sum);
}

What does Listing 20-1 do?

_____________________________________________________________

_____________________________________________________________

The ignore_line function reads and discards characters from std::cin until it reaches the end of the line or the end of the file. It takes no arguments and returns no values to the caller.

The prompted_read function prints a prompt to std::cout, then reads a number from std::cin. It then discards the rest of the input line. Because x is initialized to 0, if the read fails, the function returns 0. The caller cannot distinguish between a failure and a real 0 in the input stream, so the main() function tests std::cin to know when to terminate the loop. (The value 0 is unimportant; feel free to initialize x to any value.) The sole argument to the function is the prompt string. The return type is int, and the return value is the number read from std::cin.

The print_result function takes two arguments, both of type int. It returns nothing; it simply prints the results. Notice how it returns early if the input contains no data.

Finally, the main() function puts it all together, repeatedly calling prompted_read and accumulating the data. Once the input ends, main() prints the results, which, in this example, are the sum, count, and average of the integers it read from the standard input.

Function Call

In a function call, all arguments are evaluated before the function is called. Each argument is copied to the corresponding parameter in the function, then the function body begins to run. When the function executes a return statement, the value in the statement is copied back to the caller, which can then use the value in an expression, assign it to a variable, and so on.

In this book, I try to be careful about terminology: arguments are the expressions in a function call, and parameters are the variables in a function’s header. I’ve also seen the phrase actual argument used for arguments and formal argument used for parameters. I find these confusing, so I recommend you stick with the terms arguments and parameters.

Declarations and Definitions

I wrote the functions in bottom-up fashion because C++ has to know about a function before it can compile any call to that function. The easiest way to achieve this in a simple program is to write every function before you call it—that is, write the function earlier in the source file than the point at which you call the function.

If you prefer, you can code in a top-down manner and write main() first, followed by the functions it calls. The compiler still has to know about the functions before you call them, but you don’t have to provide the complete function. Instead, you provide only what the compiler requires: the return type, name, and a comma-separated list of parameters in parentheses. Listing 20-2 shows this new arrangement of the source code.

Listing 20-2.  Separating Function Declarations from Definitions

#include <iostream>
#include <string>
 
void ignore_line();
int prompted_read(std::string prompt);
void print_result(int count, int sum);
 
/** Main program.
 * Read integers from the standard input and print statistics about them.
 */
int main()
{
  int sum{0};
  int count{0};
 
  while (std::cin)
  {
    int x{ prompted_read("Value: ") };
    if (std::cin)
    {
      sum = sum + x;
      ++count;
    }
  }
  print_result(count, sum);
}
 
/** Prompt the user, then read a number, and ignore the rest of the line.
 * @param prompt the prompt string
 * @return the input number or -1 for end-of-file
 */
int prompted_read(std::string prompt)
{
  std::cout << prompt;
  int x{-1};
  std::cin >> x;
  ignore_line();
  return x;
}
 
/** Ignore the rest of the input line. */
void ignore_line()
{
  char c{};
  while (std::cin.get(c) and c != ' ')
    /*empty*/;
}
 
/** Print the statistics.
 * @param count the number of values
 * @param sum the sum of the values
 */
void print_result(int count, int sum)
{
  if (count == 0)
  {
    std::cout << "no data ";
    return;
  }
 
  std::cout << " count = " << count;
  std::cout << " sum   = " << sum;
  std::cout << " mean  = " << sum/count << ' ';
}

Writing the function in its entirety is known as providing a definition. Writing the function header by itself—that is, the return type, name, and parameters, followed by a semicolon—is known as a declaration. In general, a declaration tells the compiler how to use a name: what part of a program the name is (typedef, variable, function, etc.), the type of the name, and anything else (such as function parameters) that the compiler requires in order to make sure your program uses that name correctly. The definition provides the body or implementation for a name. A function’s declaration must match its definition: the return types, name, and the types of the parameters must be the same. However, the parameter names can differ.

A definition is also a declaration, because the full definition of an entity also tells C++ how to use that entity.

The distinction between declaration and definition is crucial in C++. So far, our simple programs have not needed to face the difference, but that will soon change. Remember: A declaration describes a name to the compiler, and a definition provides all the details the compiler requires for the entity you are defining.

In order to use a variable, such as a function parameter, the compiler needs only the declaration of its name and the type. For a local variable, however, the compiler needs a definition, so that it knows to set aside memory to store the variable. The definition can also provide the variable’s initial value. Even without an explicit initial value, the compiler may generate code to initialize the variable, such as ensuring that a string or vector is properly initialized as empty.

Counting Words—Again

Your turn. Rewrite the word-counting program (last seen in Exploration 19), this time making use of functions. For example, you can restore the pretty-printing utility by encapsulating it in a single function. Here’s a hint: you may want to use the typedef names in multiple functions. If so, declare them before the first function, following the #include directives.

Test the program to ensure that your changes have not altered its behavior.

Compare your program with Listing 20-3.

Listing 20-3.  Using Functions to Clarify the Word-Counting Program

#include <iomanip>
#include <iostream>
#include <locale>
#include <map>
#include <string>
 
typedef std::map<std::string, int> count_map;  ///< Map words to counts
typedef count_map::value_type      count_pair; ///< pair of a word and a count
typedef std::string::size_type     str_size;   ///< String size type
 
/** Initialize the I/O streams by imbuing them with
 * the given locale. Use this function to imbue the streams
 * with the native locale. C++ initially imbues streams with
 * the classic locale.
 * @param locale the native locale
 */
void initialize_streams(std::locale locale)
{
  std::cin.imbue(locale);
  std::cout.imbue(locale);
}
 
/** Find the longest key in a map.
 * @param map the map to search
 * @returns the size of the longest key in @p map
 */
str_size get_longest_key(count_map map)
{
  str_size result{0};
  for (auto pair : map)
    if (pair.first.size() > result)
      result = pair.first.size();
  return result;
}
 
/** Print the word, count, newline. Keep the columns neatly aligned.
 * Rather than the tedious operation of measuring the magnitude of all
 * the counts and then determining the necessary number of columns, just
 * use a sufficiently large value for the counts column.
 * @param iter an iterator that points to the word/count pair
 * @param longest the size of the longest key; pad all keys to this size
 */
void print_pair(count_pair pair, str_size longest)
{
  const int count_size{10}; // Number of places for printing the count
  std::cout << std::setw(longest)    << std::left  << pair.first <<
               std::setw(count_size) << std::right << pair.second << ' ';
}
 
/** Print the results in neat columns.
 * @param counts the map of all the counts
 */
void print_counts(count_map counts)
{
  str_size longest(get_longest_key(counts));
  
  // For each word/count pair...
  for (count_pair pair : counts)
    print_pair(pair, longest);
}
 
/** Sanitize a string by keeping only alphabetic characters.
 * @param str the original string
 * @param loc the locale used to test the characters
 * @return a santized copy of the string
 */
std::string sanitize(std::string str, std::locale loc)
{
  std::string result{};
  for (char ch : str)
    if (std::isalnum(ch, loc))
      result.push_back(std::tolower(ch, loc));
  return result;
}
 
/** Main program to count unique words in the standard input. */
int main()
{
  std::locale native{""};             // get the native locale
  initialize_streams(native);
 
  count_map counts{};
 
  // Read words from the standard input and count the number of times
  // each word occurs.
  std::string word{};
  while (std::cin >> word)
  {
    std::string copy{ sanitize(word, native) };
 
    // The "word" might be all punctuation, so the copy would be empty.
    // Don't count empty strings.
    if (not copy.empty())
      ++counts[copy];
  }
 
  print_counts(counts);
}

By using functions, you can read, write, and maintain a program in smaller pieces, handling each piece as a discrete entity. Instead of being overwhelmed by one long main(), you can read, understand, and internalize one function at a time, then move on to the next function. The compiler keeps you honest by ensuring that the function calls match the function declarations, that the function definitions and declarations agree, that you haven’t mistyped a name, and that the function return types match the contexts where the functions are called.

The main() Function

Now that you know more about functions, you can answer the question that you may have already asked yourself: What is special about themain() function?

_____________________________________________________________

_____________________________________________________________

One way that main() differs from ordinary functions is immediately obvious. All the main() functions in this book lack a return statement. An ordinary function that returns an int must have at least one return statement, but main() is special. If you don’t supply your own return statement, the compiler inserts a return 0; statement at the end of main(). If control reaches the end of the function body, the effect is the same as return 0;, which returns a success status to the operating system. If you want to signal an error to the operating system, you can return a non-zero value from main(). How the operating system interprets the value depends on the implementation. The only portable values to return are 0, EXIT_SUCCESS, and EXIT_FAILURE. EXIT_SUCCESS means the same thing as 0—namely, success, but its actual value can be different from 0. The names are declared in <cstdlib>.

The next Exploration continues to examine functions by taking a closer look at the arguments in function calls.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset