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 */
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./* 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 */
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 */In main.c, the variable c would be referred to as an "instance" of the complex class.
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 */
C: requires separate variable declaration and initialization routine
C++: constructor combines variable declaration and initialization,
provides more control over how variables initialized
/* complex.h */C++ allocates dynamic memory using new rather than malloc. You specify the constructor to use for initializing the dyamic memory.
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 constructorcomplex *c5; //a pointer, no constructor called
c5 = new complex(3,2); //dynamic memory allocated, calls initializing constructor using default valuestestcopy(c1); //calls copy constructor
testref(c1); //does not call copy constructor
c2.write();
c3.write();
....
}
/* end main.c */
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_TPoint_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:
between all shapes and just circlesDefining 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:
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 invokedYouth y(5);
y.howold(); //Person::age invokedMiddleAgePerson mp(40);
mp.howold(); //MiddleAgePerson::age invokedPerson *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.