EXPLORATION 34

image

More About Member Functions

Member functions and constructors are even more fun than what you’ve learned so far. This Exploration continues to uncover their mysteries.

Revisiting Project 1

What did you find most frustrating about Project 1 (Exploration 28)? If you are anything like me (although I hope you’re not, for your own sake), you may have been disappointed that you had to define several separate vectors to store one set of records. However, without knowing about classes, that was the only feasible approach. Now that you’ve been introduced to classes, you can fix the program. Write a class definition to store one record. Refer back to Exploration 28 for details. To summarize, each record keeps track of an integer height in centimeters, an integer weight in kilograms, the calculated BMI (which you can round off to an integer), the person’s sex (letter 'M' or 'F'), and the person’s name (a string).

Next, write a read member function that reads a single record from an istream. It takes two arguments: an istream and an integer. Prompt the user for each piece of information by writing to std::cout. The integer argument is the record number, which you can use in the prompts. Write a print member function that prints one record; it takes an ostream and an integer threshold as arguments.

Finally, modify the program to take advantage of the new class you wrote. Compare your solution to mine, shown in Listing 34-1.

Listing 34-1.  New BMI Program

#include <algorithm>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <limits>
#include <locale>
#include <string>
#include <vector>
 
/// Compute body-mass index from height in centimeters and weight in kilograms.
int compute_bmi(int height, int weight)
{
   return static_cast<int>(weight * 10000 / (height * height) + 0.5);
}
 
/// Skip the rest of the input line.
void skip_line(std::istream& in)
{
  in.ignore(std::numeric_limits<int>::max(), ' '),
}
 
/// Represent one person's record, storing the person's name, height, weight,
/// sex, and body-mass index (BMI), which is computed from the height and weight.
struct record
{
  record() : height_{0}, weight_{0}, bmi_{0}, sex_{'?'}, name_{}
  {}
 
  /// Get this record, overwriting the data members.
  /// Error-checking omitted for brevity.
  /// @return true for success or false for eof or input failure
  bool read(std::istream& in, int num)
  {
    std::cout << "Name " << num << ": ";
    std::string name{};
    if (not std::getline(in, name))
      return false;
 
    std::cout << "Height (cm): ";
    int height{};
    if (not (in >> height))
      return false;
    skip_line(in);
 
    std::cout << "Weight (kg): ";
    int weight{};
    if (not (in >> weight))
      return false;
    skip_line(in);
 
    std::cout << "Sex (M or F): ";
    char sex{};
    if (not (in >> sex))
      return false;
    skip_line(in);
    sex = std::toupper(sex, std::locale());
 
    // Store information into data members only after reading
    // everything successfully.
    name_ = name;
    height_ = height;
    weight_ = weight;
    sex_ = sex;
    bmi_ = compute_bmi(height_, weight_);
    return true;
  }
 
  /// Print this record to @p out.
  void print(std::ostream& out, int threshold)
  {
    out << std::setw(6) << height_
        << std::setw(7) << weight_
        << std::setw(3) << sex_
        << std::setw(6) << bmi_;
    if (bmi_ >= threshold)
      out << '*';
    else
      out << ' ';
    out << ' ' << name_ << ' ';
  }
 
  int height_;       ///< height in centimeters
  int weight_;       ///< weight in kilograms
  int bmi_;          ///< Body-mass index
  char sex_;         ///< 'M' for male or 'F' for female
  std::string name_; ///< Person’s name
};
 
 
/** Print a table.
 * Print a table of height, weight, sex, BMI, and name.
 * Print only records for which sex matches @p sex.
 * At the end of each table, print the mean and median BMI.
 */
void print_table(char sex, std::vector<record>& records, int threshold)
{
  std::cout << "Ht(cm) Wt(kg) Sex  BMI  Name ";
 
  float bmi_sum{};
  long int bmi_count{};
  std::vector<int> tmpbmis{}; // store only the BMIs that are printed
                            // in order to compute the median
  for (auto rec : records)
  {
    if (rec.sex_ == sex)
    {
      bmi_sum = bmi_sum + rec.bmi_;
      ++bmi_count;
      tmpbmis.push_back(rec.bmi_);
      rec.print(std::cout, threshold);
    }
  }
 
  // If the vectors are not empty, print basic statistics.
  if (bmi_count != 0)
  {
    std::cout << "Mean BMI = "
              << std::setprecision(1) << std::fixed << bmi_sum / bmi_count
              << ' ';
 
    // Median BMI is trickier. The easy way is to sort the
    // vector and pick out the middle item or items.
    std::sort(tmpbmis.begin(), tmpbmis.end());
    std::cout << "Median BMI = ";
    // Index of median item.
    std::size_t i{tmpbmis.size() / 2};
    if (tmpbmis.size() % 2 == 0)
      std::cout << (tmpbmis.at(i) + tmpbmis.at(i-1)) / 2.0 << ' ';
    else
      std::cout << tmpbmis.at(i) << ' ';
  }
}
 
/** Main program to compute BMI. */
int main()
{
  std::locale::global(std::locale{""});
  std::cout.imbue(std::locale{});
  std::cin.imbue(std::locale{});
 
  std::vector<record> records{};
  int threshold{};
 
  std::cout << "Enter threshold BMI: ";
  if (not (std::cin >> threshold))
    return EXIT_FAILURE;
  skip_line(std::cin);
 
  std::cout << "Enter name, height (in cm),"
               " and weight (in kg) for each person: ";
  record rec{};
  while (rec.read(std::cin, records.size()+1))
  {
    records.push_back(rec);
    std::cout << "BMI = " << rec.bmi_ << ' ';
  }
 
  // Print the data.
  std::cout << " Male data ";
  print_table('M', records, threshold);
  std::cout << " Female data ";
  print_table('F', records, threshold);
}

That’s a lot to swallow, so take your time. I’ll wait here until you’re done. When faced with a new class that you have to read and understand, start by reading the comments (if any). One approach is to first skim lightly over the class to identify the members (function and data), then reread the class to understand the member functions in depth. Tackle one member function at a time.

You may be asking yourself why I didn’t overload the >> and << operators to read and write record objects. The requirements of the program are a little more complicated than what these operators offer. For example, reading a record also involves printing prompts, and each prompt includes an ordinal, so the user knows which record to type. Some records are printed differently than others, depending on the threshold. The >> operator has no convenient way to specify the threshold. Overloading I/O operators is great for simple types but usually is not appropriate for more complicated situations.

Const Member Functions

Take a closer look at the print_table function. Notice anything unusual or suspicious about its parameters? The records argument is passed by reference, but the function never modifies it, so you really should pass it as a reference to const. Go ahead and make that change. What happens?

_____________________________________________________________

_____________________________________________________________

You should see an error from the compiler. When records is const, the auto rec : records type must declare rec as const too. Thus, when print_table calls rec.print(), inside the print() function, this refers to a const record object. Although print() does not modify the record object, it could, and the compiler must allow for the possibility. You have to tell the compiler that print() is safe and doesn’t modify any data members. Do so by adding a const modifier between the print() function signature and the function body. Listing 34-2 shows the new definition of the print member function.

Listing 34-2.  Adding the const Modifier to print

  /// Print this record to @p out.
  void print(std::ostream& out, int threshold)
  const
  {
    out << std::setw(6) << height_
        << std::setw(7) << weight_
        << std::setw(3) << sex_
        << std::setw(6) << bmi_;
    if (bmi_ >= threshold)
      out << '*';
    else
      out << ' ';
    out << ' ' << name_ << ' ';
  }

As a general rule, use the const modifier for any member function that does not change any data members. This ensures that you can call the member function when you have a const object. Copy the code from Listing 33-4 and modify it to add const modifiers where appropriate. Compare your result with mine in Listing 34-3.

Listing 34-3.  const Member Functions for Class point

#include <cmath> // for sqrt and atan2
 
struct point
{
  /// Distance to the origin.
  double distance()
  const
  {
    return std::sqrt(x*x + y*y);
  }
  /// Angle relative to x-axis.
  double angle()
  const
  {
    return std::atan2(y, x);
  }
 
  /// Add an offset to x and y.
  void offset(double off)
  {
    offset(off, off);
  }
  /// Add an offset to x and an offset to y
  void offset(double  xoff, double yoff)
  {
    x = x + xoff;
    y = y + yoff;
  }
 
  /// Scale x and y.
  void scale(double mult)
  {
    this->scale(mult, mult);
  }
  /// Scale x and y.
  void scale(double xmult, double ymult)
  {
    this->x = this->x * xmult;
    this->y = this->y * ymult;
  }
  double x;
  double y;
};

The scale and offset functions modify data members, so they cannot be const. The angle and distance member functions don’t modify any members, so they are const.

Given a point variable, you can call any member function. If the object is const, however, you can call only const member functions. The most common situation is when you find yourself with a const object within another function, and the object was passed by reference to const, as illustrated in Listing 34-4.

Listing 34-4.  Calling const and Non-const Member Functions

#include <cmath>
#include<iostream>
 
// Use the same point definition as Listing 34-3
... omitted for brevity ...
 
void print_polar(point const& pt)
{
  std::cout << "{ r=" << pt.distance() << ", angle=" << pt.angle() << " } ";
}
 
void print_cartesian(point const& pt)
{
  std::cout << "{ x=" << pt.x << ", y=" << pt.y << " } ";
}
 
int main()
{
  point p1{}, p2{};
  double const pi{3.141592653589792};
  p1.x = std::cos(pi / 3);
  p1.y = std::sin(pi / 3);
  print_polar(p1);
  print_cartesian(p1);
  p2 = p1;
  p2.scale(4.0);
  print_polar(p2);
  print_cartesian(p2);
  p2.offset(0.0, -2.0);
  print_polar(p2);
  print_cartesian(p2);
}

Another common use for member functions is to restrict access to data members. Imagine what would happen if a program that used the BMI record type accidentally modified the bmi_ member. A better design would let you call a bmi() function to obtain the BMI but hide the bmi_ data member, to prevent accidental modification. You can prevent such accidents, and the next Exploration shows you how.

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

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