Object-Oriented programming (OOP) is a methodology

OO programming languages (OOPLs) have features to support this methodology

Simula 1967, CLU 1980, Smalltalk 1980, C++ 1985, ..., Java 1996

Abstract Data Types - support data abstraction

This example shows an ADT for complex numbers.  Complex numbers are of the form x + yi,
where i*i = -1, x and y are real.

The  interface to the complex number ADT is found in complex.h, which defines a struct for a complex number data representation, along with helper functions to read, write and conjugate a complex number.

/* complex.h */
struct complex {
  double x, y;
};

void read(struct complex *z);
void write(struct complex *z);
void conjugate(struct complex *z);
/* end complex.h */
The implementation of the complext number ADT is given in complex.c:
/* complex.c */
#include <stdio.h>
#include "complex.h"

void read(struct complex *z)
{
  scanf("%E%E", &z->x, &z->y);
}

void write(struct complex *z)
{
  printf("%E %E\n", z->x, z->y);
}

void conjugate(struct complex *z)
{
  z->y = -z->y;
}
/* end complex.c */
An example client that would use the complex number ADT would be:
 
/* main.c */
#include <stdio.h>
#include "complex.h"

main()
{
  struct complex c;
  read(&c);
  conjugate(&c);
  write(&c);
}
/* end main.c */

Abstract Data Types - are a programming methodology

Step 1: Improving Modularity:  Struct encapsulating functions together

/* complex.h */

struct complex {
  double x, y;
  void read();
  void write();
  void conjugate();
/* end complex.h */


/* complex.c */
#include <stdio.h>
#include "complex.h"

void complex::read()
{
  scanf("%E%E", &x, &y);
}

void complex::write()
{
  printf("%E %E\n", x, y);
}

void complex::conjugate()
{
  y = y;
}
/* end complex.c */
 

/* main.c */
#include <stdio.h>
#include "complex.h"

main()
{
  struct complex c;

  c.read();
  c.conjugate();
  c.write();
}
/* end main.c */

Note that  the C++ struct construct allows the function argument to disappear.  Since the function is part of the structure, it can access any field (in C++ they are now called data members) of that structure. The functions are also considered to be members of the struct, thus they are called member functions.
Note also that we need to preface the write function name with complex::, because there may be many ADTs that implement a function called write.

Step 2:  Encapsulation and Information Hiding:     C++ Class

In the above example, the struct encapsulates the data and the functions together, with all being accessible to the client.  Unfortunately, the x and y fields are accessible to the client as well, who could modify them directly.  If the implementor of the complex number ADT wanted to represent the number using polar coordinates, then all existing applications that directly accessed the x and y fields would need to be updated.  This is bad, since we want clients and ADTs to be loosely coupled.

In C++, one can use a construct called "Class", which is like a struct, and specify which fields/functions are public (visible to all) and which are private (can only be accessed by the class itself).
 

/* complex.h */
class complex {
public:
  void read();
  void write();
  void conjugate();
private:
  double x, y;
};

/*complex.c */ - remains the same

/* main.c */
#include <stdio.h>
#include "complex.h"

main()
{
  complex c;

  c.read();
  c.conjugate();
  c.write();
}
/* end main.c */

In main.c, the variable c would be referred to as an "instance" of the complex class.
The main program, along with any other piece of code in the system, can invoke the public members of the complex class.  However, the x and y data members are only visible within the scope of the complex class.

Step 3: Constructors: improving the way variables are initialized

When  variables are defined, they are initialized to some default value.   Sometimes it is important to have control over how they are initialized.   In C, one needs to declare a variable (this allocates storage for the variable, and may initialize the storage depending on the variable scope and extent), and separately invoke an initialization function if there is a need to perform special initialization. Also, when a copy of a variable needs to be made (for instance when using pass by value for function calls), the elements are copied one by one.  This may or may not be the desired effect, especially if an element was created using dynamic allocation.  C++ allows you to control how class instances are initialized, and how their data members are copied when the compiler needs to create  a copy of them.  These are done through initializing constructors and copy constructors.  In C++, a class variable declaration involves an implicit invocation of the constructor, thus the storage allocation and variable initialization is done together.

C: requires separate variable declaration and initialization routine
C++: constructor combines variable declaration and initialization, provides more control over how variables initialized
 

/* complex.h */
class complex {
public:
  complex (double real=0, double imag=0);  //initializing constructor with default values
  complex (complex &original)  //copy constructor
  void write();
  void conjugate();
private:
  double x, y;
};

/*complex.c */

#include <stdio.h>
#include "complex.h"

complex (double real=0, double imag=0)
{
   x=real;
   y=imag;
}

complex (complex original)
{
   x=original.x;
   y=original.y;
}

void complex::write()
....

/* end complex.c */

/* main.c */
#include <stdio.h>
#include "complex.h"

//the following function involves 1 call to copy constructor  for arg
void testcopy (complex a) {
   // do stuff
}

//the following function involves no calls to copy constructor
void testref (complex &a) {
   // do stuff
}

main()
{
  complex c1;  //calls initializing constructor, using default values for x and y
  complex c2(3);  //calls initializing constructor, using default value for y
  complex c3(3,4); //calls initializing constructor
  complex c4=c3;  //calls copy constructor

  complex *c5;  //a pointer, no constructor called
  c5 = new complex(3,2);  //dynamic memory allocated, calls initializing constructor using default values

  testcopy(c1);  //calls copy constructor
  testref(c1); //does not call copy constructor
  c2.write();
  c3.write();
  ....
}
/* end main.c */

C++ allocates dynamic memory using new rather than malloc.  You specify the constructor to use for initializing the dyamic memory.

Step 4: Inheritance and virtual functions: improving the way we represent variants of a common type

Shapes in a graphics system.

In C, we would write:
 

/* shape.h */
#include "point.h"
typedef enum {circle, square, triangle, ... } Kind_T;
typedef struct Shape_T {
  Kind_T tag;
  Point_T center;
  union {
     float radius; //circle
     float side;  //square
     ...
   } u;
}   * Shape_T;

extern Point_T Shape_where(Shape_T s);
extern void Shape_move(Shape_T s, Point_T to);
extern void Shape_draw(Shape_T s);
extern float Shape_area (Shape_T s);
...
 

/* shape.c */
#include "shape.h"
#define T Shape_T

Point_T Shape_where(T s) {
  return s->center;
}

void Shape_move(T s, Point_T to) {
  s->center = to;
  Shape_draw(s);
}

void Shape_draw (T s) {
  switch (s->tag) {
    case circle: ..
    case square: ...
    ...
  }
}

void Shape_area(T s) {
switch (s->tag) {
    case circle: ..
    case square: ...
    ...
  }
}


Problems with ADTs in C:

ADTs don't distinquish between general and specific properties.
between all shapes and just circles
Defining the shape hierarchy in C++:
/* shape.h */
#include "point.h"
class Shape {
  private:     //inherited by subclasses, but only visible within Shape class
  Point center;

  protected:  //inherited by  subclasses, visible within Shape and subclasses
  Shape(int c) {center (c);  /* call Point constructor with arg c */ }

  public:  //inherited by Shape subclasses, visible to all classes
  Point where();  //inherited by all shapes
  void move(Point to);  //inherited by all shapes
  virtual void draw() = 0;  //pure virtual, implementation varies per shape
  virtual float area () = 0;  //pure virtual, implementation varies per shape
...
 

/* shape.c */
#include "shape.h"

Point where() {
  return this->center;
}

void move(Point to) {
  this->center = to;
  Shape_draw(this);
}
 

/* circle.h */
class Circle : public Shape {
  private:
  float radius;
  public:
  Circle(int c, r);
  void draw ();
  float area ();
}

* circle.c */

  Circle(int c, r) : Shape(c) { radius = r;  }
  Circle::draw () { code to draw a circle }
  Circle::area () { return PI * radius * radius ; }
}

/* square.h */
  class Square : public Shape {
  private:
  float side;
  public:
  Sqaure(int c, s);
  void draw ();
  float area ();
}

/* square.h */

  Sqaure(int c, s) : Shape(c) { side = s; }
  void Square::draw () { code to draw a square }
  float Square::area () { return side * side ; }
}


Improvements over C implementation:

Shape class defines general properties common to all shapes, circle subclass defines specific properties relevant to circles.

Polymorphism, Virtual functions and Dynamic Binding

Non-virtual functions tell the compiler to choose the member function statically.
 
class Person {
  private:
  int age;

  public:
  Person(int a) {age=a;}
  void howold() { printf("I am %d years old\n",age);
   ..
}

class MiddleAgePerson : public Person {
   MiddleAgePerson(a) : Person(a) { }
   void howold() { printf("None of your business!\n");
}

class Youth: public Person {
   Youth(a) : Person(a) { }
}

main() {
  Person p(10);
  p.howold();  //Person::age invoked

  Youth y(5);
   y.howold();  //Person::age invoked

  MiddleAgePerson mp(40);
  mp.howold();  //MiddleAgePerson::age invoked

   Person *p2;
   p2 = new Person(5);
   p2->howold();  //Person::age invoked
   delete p2;
   p2 = new MiddleAgePerson(40);
   p2-> howold();   //Person::age invoked
   delete p2;
   p2 = new Youth(4);
   p2->howold();   //Person::age invoked
}


A virtual function tells the compiler that subclasses may override its definition (the superclass definition is inherited, but subclasses may choose to implement their own version), and to choose the implementation dynamically.  Virtual functions are only invoked through pointers.
 

class Person {
  ....
  virtual void howold() { printf("I am %d years old\n",age);
   ..
}

class Youth : public Person {
   ....   //inherits Person::age
}

class MiddleAgePerson : public Person {
   ....  //inherits Person::age, but overrides it with its own implementation
}

main() {
  .....
   Person *p2;
   p2 = new Person(5);
   p2->howold();  //Person::age invoked
   delete p2;
   p2 = new MiddleAgePerson(40);
   p2-> howold();   //MiddleAgePerson::age invoked
   delete p2;
   p2 = new Youth(4);
   p2-> howold();   //Person::age invoked
}


Note in the previous example involving shapes that there is no such thing as a "Shape" in the real world, each shape object is either a Circle, Square, etc.  So "Shape" is an abstract class representing the general properties of concrete shapes. In C++, you force a class to be abstract (meaning it can't be instantiated) by defining at least one "pure virtual" function.    Usually this is done by setting the no-args constructor to be pure virtual (=0).

Also, notice that we do not need to using casting to get the correct member function to be invoked.  While the variable p2 is defined as a pointer to Person, at runtime it may reference any subclass instance of Person.  This is referred to as Polymorphism, which means "many forms".  Dynamic binding will allow invocation of the howold method to look at the actual object being dynamically referenced by the p2 variable, rather than the static variable type.    In C, you would need to statically caste a generic pointer (such as p2)  to a particular type in order to force the runtime environment to treat it as another type.
 

Conclusion: there are many other features of C++ that help the programmer (genericity, operator overloading, ...) that are too complex to present here.  But the basic features of object-oriented programming have been presented. Numerous tutorials on C++ are available at http://www.geckil.com/~harvest/tutorials.