LABORATORY 13

Input/Output Operations in C++. Classes and Objects.

Duration: 2 hours Language: C++ Previous: Laboratory 12 PDF handout RO versiunea română

C++ keeps the input/output system from C, but adds its own, built on the principles of object-oriented programming: safer, more flexible, and extensible to user-defined types. This final lab also makes the transition from structures to classes.

1Lab objectives

  • Using the cin, cout, cerr, and clog streams
  • Formatting output with manipulators
  • Working with files through ifstream and ofstream
  • Defining a class with data members and member functions
  • Writing constructors and the destructor
  • Applying the encapsulation principle

2The I/O class hierarchy

Input/output operations are carried out using two class hierarchies, declared in the files iostream.h and fstream.h:

ios istream ostream iostream fstreambase ifstream ofstream fstream
Fig. 1 - The class hierarchies used in input/output operations

3Predefined streams

StreamMeaningDefault deviceC equivalent
cinstandard inputkeyboardstdin
coutstandard outputscreenstdout
cerrstandard output for errorsscreenstderr
clogoutput for errors, bufferedscreen-
Stream operators<< is the insertion (writing) operator, >> the extraction (reading) one. Both are overloaded operators, which allows chaining: cout << a << " " << b << endl;
The advantage over printfThe data type is deduced automatically by the compiler. There is no risk of using the wrong specifier, and the operators can be extended for your own classes, so that cout << object works naturally.

4Format manipulators

ManipulatorEffectHeader needed
endlnew line + flushing the bufferiostream
setw(n)minimum width of n charactersiomanip
setprecision(n)the number of significant digitsiomanip
fixedfixed decimal notationiostream
scientificscientific notationiostream
left / rightalignment within the fieldiostream
setfill(c)the fill characteriomanip
hex / oct / decthe number baseiostream
boolalphaprints true/false instead of 1/0iostream
An important differencesetw acts only on the next value printed, then it resets. The other manipulators (fixed, setprecision, hex) stay active until changed again.

5Files in C++

ClassRoleC equivalent
ifstreamreading from a filefopen(..., "r")
ofstreamwriting to a filefopen(..., "w")
fstreamreading and writingfopen(..., "r+")
Automatic closingOn leaving its scope, the object's destructor closes the file by itself. Unlike C, forgetting an fclose is no longer possible - this is the first concrete benefit of the object-oriented approach.

6Classes and objects

A class extends the notion of a structure: besides data, it also contains the functions that operate on it. An object is a concrete instance of a class.

the structure of a class
class ClassName {
  private:                      // default for class
    type data_members;          // accessible only from inside the class

  public:
    ClassName();                 // default constructor
    ClassName(parameters);       // constructor with parameters
    ~ClassName();                // destructor

    type member_function(...);   // public interface
};
SpecifierAccess from inside the classFrom outsideFrom derived classes
privateyesnono
protectedyesnoyes
publicyesyesyes
class vs. struct in C++The only difference is the default access: private for class, public for struct. Otherwise, a C++ structure can have member functions, constructors, and a destructor.

Constructors and destructor

ElementWhen it is calledCharacteristics
Constructorwhen the object is createdhas the class's name, returns nothing, can be overloaded
Copy constructorwhen initialized from another objectClassName(const ClassName &other)
Destructorwhen the object is destroyed~ClassName(), unique, no parameters

7Simulator: the life cycle of an object

Watch the exact moments when the constructor and destructor are called, including for objects created inside an inner block.

Constructor and destructor - the order of calls
The order of destructionObjects are destroyed in the reverse order of creation. This mechanism, called RAII, guarantees that resources (memory, files, connections) are released even when the function ends on an unexpected path.

8Encapsulation

Data members are declared private, and access happens exclusively through public functions. The advantage is not hiding for its own sake, but control: every change can be validated.

why it matters
class Temperature {
  private:
    double celsius;

  public:
    // without encapsulation, anyone could write t.celsius = -500;
    bool setCelsius(double v) {
        if (v < -273.15) return false;    // validation: absolute zero
        celsius = v;
        return true;
    }
    double getCelsius() const { return celsius; }
    double getFahrenheit() const { return celsius * 9.0 / 5.0 + 32.0; }
};
const functionsMarking a member function const guarantees that it does not modify the object's state. The compiler checks the promise and reports any violation - a simple and effective form of executable documentation.

9Source code

formatting.cpp - manipulators
#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
    double pi = 3.14159265358979;

    cout << "Default:       " << pi << endl;
    cout << "fixed, 4 dec:  " << fixed << setprecision(4) << pi << endl;
    cout << "scientific:    " << scientific << pi << endl;

    cout << fixed << setprecision(2);

    // aligned table
    cout << "\n" << left  << setw(14) << "Product"
         << right << setw(8)  << "Qty"
         << setw(10) << "Price" << endl;
    cout << setfill('-') << setw(32) << "" << setfill(' ') << endl;

    cout << left << setw(14) << "Resistor"
         << right << setw(8) << 120 << setw(10) << 0.15 << endl;
    cout << left << setw(14) << "Capacitor"
         << right << setw(8) << 45  << setw(10) << 0.80 << endl;

    // number bases
    int v = 255;
    cout << "\n" << dec << v << " = " << hex << v
         << " (hex) = " << oct << v << " (oct)" << dec << endl;

    cout << boolalpha << "\n3 > 5 is " << (3 > 5) << endl;

    return 0;
}
student_class.cpp - a complete class
#include <iostream>
#include <string>
#include <iomanip>
using namespace std;

class Student {
  private:
    string name;
    int    age;
    double average;

  public:
    // default constructor
    Student() : name("unknown"), age(0), average(0.0) {
        cout << "  [default constructor]" << endl;
    }

    // constructor with parameters, using the initialization list
    Student(string n, int a, double m) : name(n), age(a), average(m) {
        cout << "  [constructor for " << name << "]" << endl;
    }

    // copy constructor
    Student(const Student &other)
        : name(other.name), age(other.age), average(other.average) {
        cout << "  [copy for " << name << "]" << endl;
    }

    ~Student() {
        cout << "  [destructor for " << name << "]" << endl;
    }

    // access functions, with validation
    bool setAverage(double m) {
        if (m < 1.0 || m > 10.0) return false;
        average = m;
        return true;
    }

    string getName()    const { return name; }
    double getAverage() const { return average; }
    bool   passed()      const { return average >= 5.0; }

    void print() const {
        cout << left << setw(18) << name
             << right << setw(4) << age << " years"
             << setw(8) << fixed << setprecision(2) << average
             << "  " << (passed() ? "passed" : "failed") << endl;
    }
};

int main()
{
    cout << "Creating objects:" << endl;
    Student s1("John Smith", 20, 9.15);
    Student s2("Mary Johnson", 21, 4.30);
    Student s3 = s1;                       // calls the copy constructor

    cout << "\nList:" << endl;
    s1.print();
    s2.print();
    s3.print();

    cout << "\nAttempt to set the average to 15: ";
    cout << (s2.setAverage(15.0) ? "accepted" : "REJECTED by validation") << endl;

    cout << "\nDestruction (reverse order):" << endl;
    return 0;
}
files.cpp - file I/O in C++
#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main()
{
    // writing
    {
        ofstream fout("data_cpp.txt");
        if (!fout) { cerr << "Cannot write!" << endl; return 1; }

        fout << "John Smith 20 9.15" << endl;
        fout << "Mary Johnson 21 8.70" << endl;
    }   // the file closes AUTOMATICALLY here, through the destructor

    // reading
    ifstream fin("data_cpp.txt");
    if (!fin) { cerr << "Cannot read!" << endl; return 1; }

    string firstName, lastName;
    int age;
    double average;

    cout << "File contents:" << endl;
    while (fin >> firstName >> lastName >> age >> average) {
        cout << "  " << firstName << " " << lastName
             << ", " << age << " years, average " << average << endl;
    }
    fin.close();

    // reading line by line
    ifstream f2("data_cpp.txt");
    string line;
    int nr = 0;
    cout << "\nLine by line:" << endl;
    while (getline(f2, line)) {
        cout << "  " << ++nr << ": " << line << endl;
    }

    return 0;
}

10Code workshop

The boxes also accept C++: cout, string, classes with constructors and destructors. Watch the output for the exact moment each one is called.

The life cycle of an object
#include <iostream>
#include <string>
using namespace std;

class Counter {
  private:
    string name;
    int    value;

  public:
    Counter(string n) : name(n), value(0) {
        cout << "  [" << name << " is created]" << endl;
    }
    ~Counter() {
        cout << "  [" << name << " is destroyed, reached " << value << "]" << endl;
    }
    void increase() { value++; }
    int  read() const { return value; }
};

int main()
{
    cout << "Entering main:" << endl;
    Counter a("a");

    {
        cout << "Entering an inner block:" << endl;
        Counter b("b");
        b.increase(); b.increase();
        cout << "  b has value " << b.read() << endl;
    }

    a.increase();
    cout << "a has value " << a.read() << endl;
    cout << "Exiting main:" << endl;
    return 0;
}
Try thisNotice that b is destroyed at the closing brace of the inner block, not at the end of main. An object's lifetime is tied to the scope in which it was declared.
Exercise - complete the class
#include <iostream>
#include <iomanip>
using namespace std;

class Rectangle {
  private:
    double length, width;

  public:
    Rectangle(double L, double w) : length(L), width(w) {}

    /* Add three methods:
         double area() const        -> length * width
         double perimeter() const   -> 2 * (length + width)
         bool   isSquare() const    -> true if the sides are equal  */
};

int main()
{
    Rectangle d1(4.0, 2.5);
    Rectangle d2(3.0, 3.0);

    cout << fixed << setprecision(2);
    cout << "d1: area = " << d1.area() << ", perimeter = " << d1.perimeter() << endl;
    cout << "d2: area = " << d2.area() << ", perimeter = " << d2.perimeter() << endl;
    cout << boolalpha << "d2 is a square? " << d2.isSquare() << endl;
    return 0;
}

11Work tasks

  • Print an aligned table using setw, left, right, and setfill.
  • Experimentally verify that setw acts only on the next value printed.
  • Define a Point class with private coordinates and methods for the distance to the origin.
  • Add to the class a default constructor, one with parameters, and a destructor that prints messages.
  • Watch the console for the order of calls for several objects, including inside an inner block.
  • Implement data validation in the set-type functions and test disallowed values.
  • Write and read data from a file using ofstream and ifstream.
  • Compare the C++ version with the C version of the same program and note the differences in readability.

12Extended application

Final projectImplement a Vector class that manages a dynamically allocated array: a constructor with a size, a copy constructor, a destructor that releases the memory, an add method with automatic resizing, bounds-checked access, and overloading of the << operator for direct printing with cout. Add saving to and loading from a file. Use messages in the constructor and destructor to verify that no memory leaks occur.

13Review questions

14Resources