Object-Oriented Programming - Programming in C (Fourth Edition) (2015)

Programming in C (Fourth Edition) (2015)

18. Object-Oriented Programming

Because object-oriented programming (or OOP) is so popular, and because many of the widely used OOP languages—such as C++, C#, Java, and Objective-C—are based on the C language, a brief introduction to this topic is presented here. The chapter starts with an overview about the concepts of OOP, and then shows you a simple program in three of the four aforementioned OOP languages (I picked the three that contain the word “C”!). The idea here is not to teach you how to program in these languages or even to describe their main features so much as it is to give you a quick taste. This chapter includes

Image Understanding the basic concepts of OOP, including objects, classes, and methods.

Image Explaining the basic differences between how a structured programming language approaches a problem versus how an object-oriented language does it.

Image Comparing how three different object-oriented languages, specifically Objective-C, C++, and C#, approach a simple programming task.

What Is an Object Anyway?

An object is a thing. Think about object-oriented programming as a thing and something you want to do to that thing. This is in contrast to a programming language such as C, more formally known as a procedural programming language. In C, you typically think about what you want to do first (and maybe write some functions to accomplish those tasks), and then you worry about the objects—almost the opposite from object orientation.

As an example from everyday life, assume you own a car. That car is obviously an object—one that you own. You don’t have just any car; you have a particular car that was manufactured from the factory, perhaps in Detroit, perhaps in Japan, or perhaps someplace else. Your car has a vehicle identification number (VIN) that uniquely identifies your car.

In object-oriented parlance, your car is an instance of a car. And continuing with the terminology, car is the name of the class from which this instance was created. So, each time a new car gets manufactured, a new instance from the class of cars gets created. Each instance of the car is also referred to as an object.

Now, your car might be silver, it might have a black interior, it might be a convertible or hardtop, and so on. In addition, there are certain things, or actions, you do with your car. For example, you drive your car, you fill it up with gas, you (hopefully) wash your car, you take it in for service, and so on. This is depicted in Table 18.1.

Image

Table 18.1 Actions on Objects

The actions listed in Table 18.1 can be done with your car, and they can also be done with other cars. For example, your sister can drive her car, wash it, fill it up with gas, and so on.

Instances and Methods

A unique occurrence of a class is an instance. The actions that you perform are called methods. In some cases, a method can be applied to an instance of the class or to the class itself. For example, washing your car applies to an instance (in fact, all of the methods listed in Table 18.1 are considered instance methods). Finding out how many different types of cars a manufacturer makes applies to the class, so it is a class method.

In C++, you invoke a method on an instance using the following notation:

Instance.method ();

A C# method is invoked with the same notation as follows:

Instance.method ();

An Objective-C message call follows this format:

[Instance method]

Go back to the previous list and write a message expression in this new syntax. Assume that yourCar is an object from the Car class. Table 18.2 shows what message expressions might look like in the three OOP languages.

Image

Table 18.2 Message Expressions in OOP Languages

And if your sister has a car, called suesCar, for example, then she can invoke the same methods on her car, as follows:

suesCar.drive() suesCar.drive() [suesCar drive]

This is one of the key concepts behind object-oriented programming (that is, applying the same methods to different objects).

Another key concept, known as polymorphism, allows you to send the same message to instances from different classes. For example, if you have a Boat class, and an instance from that class called myBoat, then polymorphism allows you to write the following message expressions in C++:

myBoat.service()
myBoat.wash()

The key here is that you can write a method for the Boat class that knows about servicing a boat, that can be (and probably is) completely different from the method in the Car class that knows how to service a car. This is the key to polymorphism.

The important distinction for you to understand about OOP languages versus C, is that in the former case you are working with objects, such as cars and boats. In the latter, you are typically working with functions (or procedures). In a so-called procedural language like C, you might write a function called service and then inside that function write separate code to handle servicing different vehicles, such as cars, boats, or bicycles. If you ever want to add a new type of vehicle, you have to modify all functions that deal with different vehicle types. In the case of an OOP language, you just define a new class for that vehicle and add new methods to that class. You don’t have to worry about the other vehicle classes; they are independent of your class, so you don’t have to modify their code (to which you might not even have access).

The classes you work with in your OOP programs will probably not be cars or boats. More likely, they’ll be objects such as windows, rectangles, clipboards, and so on. The messages you’ll send (in a language like C#) will look like this:

myWindow.erase() Erase the window
myRect.getArea() Calculate the area of the rectangle
userText.spellCheck() Spell check some text
deskCalculator.setAccumulator(0.0) Clear the accumulator
favoritePlaylist.showSongs() Show songs in favorite playlist

Writing a C Program to Work with Fractions

Suppose you need to write a program to work with fractions. Perhaps you need to deal with adding, subtracting, multiplying them, and so on. You could define a structure to hold a fraction, and then develop a set of functions to manipulate them.

The basic setup for a fraction using C might look like Program 18.1. Program 18.1 sets the numerator and denominator and then displays the value of the fraction.

Program 18.1 Working with Fractions in C


// Simple program to work with fractions
#include <stdio.h>

typedef struct {
int numerator;
int denominator;
} Fraction;

int main (void)
{
Fraction myFract;

myFract.numerator = 1;
myFract.denominator = 3;

printf ("The fraction is %i/%i\n", myFract.numerator, myFract.denominator);

return 0;
}


Program 18.1 Output


The fraction is 1/3


The next three sections illustrate how you might work with fractions in Objective-C, C++, and C#, respectively. The discussion about OOP that follows the presentation of Program 18.2 applies to OOP in general, so you should read these sections in order.

Defining an Objective-C Class to Work with Fractions

The Objective-C language was invented by Brad Cox in the early 1980s. The language was based on a language called SmallTalk-80 and was licensed by NeXT Software in 1988. When Apple acquired NeXT in 1988, it used NEXTSTEP as the basis for its Mac OS X operating system. Most of the applications found today on Mac OS X, as well as a number of iPad and iPhone apps, are written in Objective-C.

Program 18.2 shows how you can define and use a Fraction class in Objective-C.

Program 18.2 Working with Fractions in Objective-C


// Program to work with fractions – Objective-C version

#import <stdio.h>
#import <objc/Object.h>

//------- @interface section -------

@interface Fraction: Object
{
int numerator;
int denominator;
}
-(void) setNumerator: (int) n;
-(void) setDenominator: (int) d;
-(void) print;

@end

//------- @implementation section -------


@implementation Fraction;

// getters

-(int) numerator
{
return numerator;
}

-(int) denominator
{
return denominator;
}
// setters

-(void) setNumerator: (int) num
{
numerator = num;
}

-(void) setDenominator: (int) denom
{
denominator = denom;
}

// other

-(void) print
{
printf ("The value of the fraction is %i/%i\n", numerator, denominator);
}

@end


//------- program section -------


int main (void)
{
Fraction *myFract;


myFract = [Fraction new];

[myFract setNumerator: 1];
[myFract setDenominator: 3];

printf ("The numerator is %i, and the denominator is %i\n",
[myFract numerator], [myFract denominator]);
[myFract print]; // use the method to display the fraction


[myFract free];

return 0;
}


Program 18.2 Output


The numerator is 1, and the denominator is 3
The value of the fraction is 1/3


As you can see from the comments in Program 18.2, the program is logically divided into three sections: the @interface section, the @implementation section, and the program section. These sections are typically placed in separate files. The @interface section is usually put into a header file that gets included in any program that wants to work with that particular class. It tells the compiler what variables and methods are contained in the class.

The @implementation section contains the actual code that implements these methods. Finally, the program section contains the program code to carry out the intended purpose of the program.

The name of the new class is Fraction, and its parent class is Object. Classes inherit methods and variables from their parents.

As you can see in the @interface section, the declarations

int numerator;
int denominator;

say that a Fraction object has two integer members called numerator and denominator.

The members declared in this section are known as the instance variables. Each time you create a new object, a new and unique set of instance variables is created. Therefore, if you have two fractions, one called fracA and another called fracB, each has its own set of instance variables. That is, fracA and fracB each has its own separate numerator and denominator.

You have to define methods to work with your fractions. You need to be able to set the value of a fraction to a particular value. Because you don’t have direct access to the internal representation of a fraction (in other words, direct access to its instance variables), you must write methods to set the numerator and denominator (these are known as setters). You also need methods to retrieve the values of your instance variables (such methods are known as getters).1

1. You can get direct access to the instance variables, but it’s generally considered poor programming practice.

The fact that the instance variables for an object are kept hidden from the user of the object is another key concept of OOP known as data encapsulation. This assures someone extending or modifying a class that all the code that accesses the data (that is, the instance variables) for that class is contained in the methods. Data encapsulation provides a nice layer of insulation between the programmer and the class developer.

Here’s what one such setter method declaration looks like:

-(int) numerator;

The leading minus sign (-) says that the method is an instance method. The only other option is a plus sign (+), which indicates a class method. A class method is one that performs some operation on the class itself, such as creating a new instance of the class. This is similar to manufacturing a new car, in that the car is the class and you want to create a new one—which would be a class method.

An instance method performs some operation on a particular instance of a class, such as setting its value, retrieving its value, displaying its value, and so on. Referring to the car example, after you have manufactured the car, you might need to fill it with gas. The operation of filling it with gas is performed on a particular car, so it is analogous to an instance method.

When you declare a new method (and similar to declaring a function), you tell the Objective-C compiler whether the method returns a value, and if it does, what type of value it returns. This is done by enclosing the return type in parentheses after the leading minus or plus sign. So, the declaration

-(int) numerator;

specifies that the instance method called numerator returns an integer value. Similarly, the line

-(void) setNumerator: (int) num;

defines a method that doesn’t return a value that can be used to set the numerator of your fraction.

When a method takes an argument, you append a colon to the method name when referring to the method. Therefore, the correct way to identify these two methods is setNumerator: and setDenominator:—each of which takes a single argument. Also, the identification of the numerator and denominator methods without a trailing colon indicates that these methods do not take any arguments.

The setNumerator: method takes the integer argument you called num and simply stores it in the instance variable numerator. Similarly, setDenominator: stores the value of its argument denom in the instance variable denominator. Note that methods have direct access to their instance variables.

The last method defined in your Objective-C program is called print. Its use is to display the value of a fraction. As you see, it takes no arguments and returns no results. It simply uses printf() to display the numerator and denominator of the fraction, separated by a slash.

Inside main(), you define a variable called myFract with the following line:

Fraction *myFract;

This line says that myFract is an object of type Fraction; that is, myFract is used to store values from your new Fraction class. The asterisk (*) in front of myFraction says that myFract is actually a pointer to a Fraction. In fact, it points to the structure that contains the data for a particular instance from the Fraction class.

Now that you have an object to store a Fraction, you need to create one, just like you ask the factory to build you a new car. This is done with the following line:

myFract = [Fraction new];

You want to allocate memory storage space for a new fraction. The expression

[Fraction new]

sends a message to your newly created Fraction class. You are asking the Fraction class to apply the new method, but you never defined a new method, so where did it come from? The method was inherited from a parent class.

You are now ready to set the value of your Fraction. The program lines

[myFract setNumerator: 1];
[myFract setDenominator: 3];

do just that. The first message statement sends the setNumerator: message to myFract. The argument that is supplied is the value 1. Control is then sent to the setNumerator: method you defined for your Fraction class. The Objective-C runtime system knows that it is the method from this class to use because it knows that myFract is an object from the Fraction class.

Inside the setNumerator: method, the single program line in that method takes the value passed in as the argument and stores it in the instance variable numerator. So, you have effectively set the numerator of myFract to 1.

The message that invokes the setDenominator: method on myFract follows next, and works in a similar way.

With the fraction being set, Program 18.2 then calls the two getter methods numerator and denominator to retrieve the values of the corresponding instance variables from myFract. The results are then passed to printf() to be displayed.

The program next invokes the print method. This method displays the value of the fraction that is the receiver of the message. Even though you saw in the program how the numerator and denominator could be retrieved using the getter methods, a separate print method was also added to the definition of the Fraction class for illustrative purposes.

The last message in the program

[myFract free];

frees the memory that was used by your Fraction object.

Defining a C++ Class to Work with Fractions

Program 18.3 shows how a program to implement a Fraction class might be written using the C++ language. C++ was invented by Bjarne Stroustroup at Bell Laboratories, and was the first object-oriented programming language based on C—at least to my knowledge! Note when compiling this program—if you are using an integrated development environment (IDE) that can compile C and C++ programs, and you have been writing C programs to this point, your IDE might try to save the program as a .c file. That would then generate a series of error messages that can be avoided by saving the file with a .cpp extension, ensuring that your IDE compiles the program as a C++ program.

Program 18.3 Working with Fractions in C++


#include <iostream>

class Fraction
{
private:
int numerator;
int denominator;

public:
void setNumerator (int num);
void setDenominator (int denom);
int Numerator (void);
int Denominator (void);
void print (Fraction f);
};

void Fraction::setNumerator (int num)
{
numerator = num;
}

void Fraction::setDenominator (int denom)
{
denominator = denom;
}

int Fraction::Numerator (void)
{
return numerator;
}

int Fraction::Denominator (void)
{
return denominator;
}

void Fraction::print (Fraction f)
{
std::cout << "The value of the fraction is " << numerator << '/'
<< denominator << '\n';
}

int main (void)
{
Fraction myFract;

myFract.setNumerator (1);
myFract.setDenominator (3);

myFract.print (myFract);

return 0;
}


Program 18.3 Output


The value of the fraction is 1/3


The C++ members (instance variables) numerator and denominator are labeled private to enforce data encapsulation; that is, to prevent them from being directly accessed from outside the class.

The setNumerator method is declared as follows:

void Fraction::setNumerator (int num)

The method is preceded by the notation Fraction:: to identify that it belongs to the Fraction class.

A new instance of a Fraction is created like a normal variable in C, as in the following declaration in main():

Fraction myFract;

The numerator and denominator of the fraction are then set to 1 and 3, respectively, with the following method calls:

myFract.setNumerator (1);
myFract.setDenominator (3);

The value of the fraction is then displayed using the fraction’s print method.

Probably the oddest-appearing statement from Program 18.3 occurs inside the print method as follows:

std::cout << "The value of the fraction is " << numerator << '/'
<< denominator << '\n';

cout is the name of the standard output stream, analogous to stdout in C. The << is known as the stream insertion operator, and it provides an easy way to get output. You might recall that << is also C’s left shift operator. This is one significant aspect of C++: a feature known as operator overloading that allows you to define operators that are associated with a class. Here, the left shift operator is overloaded so that when it is used in this context (that is, with a stream as its left operand), it invokes a method to write a formatted value to an output stream, instead of trying to actually perform a left shift operation.

As another example of overloading, you might want to override the addition operator + so that if you try to add two fractions together, as in

myFract + myFract2

an appropriate method from your Fraction class is invoked to handle the addition.

Each expression that follows the << is evaluated and written to the standard output stream. In this case, first the string "The value of the fraction is " gets written, followed by the fraction’s numerator, followed by a /, the fraction’s denominator, and then a newline character.

The C++ language is rich with features. Consult Appendix E, “Resources,” for recommendations on a good tutorial.

Note that in the previous C++ example, the getter methods Numerator () and Denominator () were defined in the Fraction class but were not used.

Defining a C# Class to Work with Fractions

As the final example in this chapter, Program 18.4 shows the fraction example written in C#, a programming language developed by Microsoft, Inc. C# is part of Microsoft’s Visual Studio suite and is a key development tool in the .NET Framework. If you want to try C#, visitwww.visualstudio.com/en-US/products/visual-studio-express-vs to download a free express version of the full product.

Program 18.4 Working with Fractions in C#


using System;

class Fraction
{
private int numerator;
private int denominator;

public int Numerator
{
get
{
return numerator;
}

set
{
numerator = value;
}
}

public int Denominator
{
get
{
return denominator;
}

set
{
denominator = value;
}
}

public void print ()
{
Console.WriteLine("The value of the fraction is {0}/{1}",
numerator, denominator);
}
}

class example
{
public static void Main()
{
Fraction myFract = new Fraction();

myFract.Numerator = 1;
myFract.Denominator = 3;

myFract.print ();

}
}


Program 18.4 Output


The value of the fraction is 1/3


You can see the C# program looks a little different from the other two OOP programs, but you can probably still determine what’s happening. The Fraction class definition begins by declaring the two instance variables numerator and denominator as private. The Numeratorand Denominator methods each have their getter and setter method defined as properties. Take a closer look at Numerator:

public int Numerator
{
get
{
return numerator;
}

set
{
numerator = value;
}
}

The “get” code is executed when the value of the numerator is needed in an expression, such as in

num = myFract.Numerator;

The “set” code is executed when a value is assigned to the method, as in

myFract.Numerator = 1;

The actual value that is assigned gets stored in the variable value when the method gets called. Note that parentheses do not follow the setter and getter methods here.

Naturally, you can define methods that optionally take arguments, or setter methods that take multiple arguments. For example, this C# method invocation might be used to set the value of a fraction to 2/5 with a single call:

myFract.setNumAndDen (2, 5)

Returning to Program 18.4, the statement

Fraction myFract = new Fraction();

is used to create a new instance from the Fraction class and assign the result to the Fraction variable myFract. The Fraction is then set to 1/3 using the Fraction’s setters.

The print method is invoked next on myFract to display the value of the fraction. Inside the print method, the WriteLine method from the Console class is used to display output. Similar to printf’s % notation, {0} specifies in the string where the first value is to be substituted,{1} where the second value is to be displayed, and so on. Unlike the printf routine, you don’t need to worry here about the types being displayed.

As with the C++ example, the getter methods for the C# Fraction class were not exercised here. They were included for illustrative purposes.

This concludes this brief introduction to object-oriented programming. Hopefully, this chapter has given you a better idea about what object-oriented programming is all about and how programming in an OOP language differs from a language such as C. You have seen how you can write a simple program in one of three OOP languages to work with objects that represent fractions. If you were serious about working with fractions in your programs, you would probably extend your class definition to support operations such as addition, subtraction, multiplication, division, inversion, and reduction of fractions, for example. This would be a relatively straightforward task for you to do.

To continue your studies further, get a good tutorial on a particular OOP language, such as one listed in Appendix E.