Reading: Deitel & Deitel, Chap 6
So far, we have only used fundamental data types such as integers, real numbers, characters, and arrays. C++ provides a number of ways that programmers can define their own data types. This makes programs easier to read and makes it easier to encapsulate functionality; i.e., to create small, well-defined components which are easy to use and modify, with the implementation details hidden.
typedef
The easiest way to define a new data type is
with the keyword typedef. This takes two arguments: the first
is an already defined data type, and the second is the name of the
new data type. Here are some examples:
typedef int width;
typedef double point[3]; // a point in 3 dimensional space
typedef char str[100];
These are typically defined globally. Once these have been defined, they can be used to declare new variables just like any any other data type. Any operation which can be done on the base data type can be done on the new data type. Here are some examples:
#include <iostream>
using namespace std;
typedef int width;
typedef double point[3]; // a point in 3 dimensional space
typedef char str[100];
int main()
{
width w; // declare an instance of width called w
point p; // declare an instance of point called p
str s; // declare an instance of str called s
w = 17;
p[0]= 3.45;
strcpy(s,"This is a string\n");
...
Structures
C and C++ provide a number of language features for constructing your
own data types. One of the simplest, which can be used in both C and
C++, is the structure. A structure
is an aggregate of several items which can be treated as a single
unit. As usual, the best way to describe this is with an example.
Suppose you are writing a program about the states. You can keep a
variety of information about each state in a single structure.
Structures are defined with the keyword struct.
struct state {
double area;
char capital[32];
char governor[32];
int population;
};
Once a structure has been defined, you can use it like any other
type to declare variables.
state NewYork; state Massachusetts;
NewYork.area = 34567.2;
Massachusetts.population = 3456789;
int pop = Massachusetts.population;
strcpy(NewYork.capital, "Albany");
state the_states[50];
the_state[12].area = 4567.8;
strcpy(the_states[12].capital,"Providence");
Exercise 1: Write a complete C++ program that defines a structure of type student which has three fields: name (up to 32 characters), age (integer) and grade point average (double). Write a main that declares two students, Mickey and Donald, copies their full names (Mickey Mouse and Donald Duck) to the name field, sets Mickey's age to 52, and sets Donald's Grade Point Average to 2.89.
Classes
Recall that a data type has two features: a method of representing the set of possible values of the type in memory, and a set of operations or functions on this information. The most robust feature of C++ to create your own data types is the class concept. A class is like a struct which has member functions as well as member data. A class can have data elements of any type, and can define functions on these data elements. Data elements and functions can be designated as either public, which means that they can be directly accessed by functions outside the class, or private, which means that they can be accessed only by member functions of the class. Typically, most data elements are private and most member functions are public.
Here is the basic C++ syntax to define a new class.
class classname {
public:
public declarations
private:
private declarations
};
As with structures, class declarations must end with a semicolon. The public and private keywords can be used in either order, and can even appear more than once. Any declarations at the beginning, before one of these keywords, are private by default.1
Here is an example of a simple class.
class TimeType {
public:
void Set(int, int, int);
void Increment();
void Write();
private:
int hrs;
int mins;
int secs;
};
This declaration defines a new class called TimeType which has three public member functions (which means that they can be used by functions which are not members of the class), and three private data elements (which means that they cannot be accessed by functions which are not members of the class).
The three functions have to be defined. The syntax to define a member function is as follows:
returntype classname :: functionname (
args )
{ statements }
For example, here is the code to define the Increment member function.
void TimeType::Increment()
{
++secs;
if (secs > 59) {
secs = 0;
++mins;
if (mins > 59) {
mins = 0;
++hrs;
if (hrs > 23) {
hrs = 0;
}
}
}
}
int main()
{
TimeType starttime, endtime;
...
To access a member function (or a data element if it is public), use
the name of the instance of the class followed by a dot (period), followed
by the member name. For example, here is code to set starttime
starttime.Set(12,34,57);
The complete program to demonstrate the implementation of the
TimeType class is in P:\dept\cs\cs2\download\TimeType.cpp. Here is the code:
// TimeType.cpp - creates a new class
#include <iostream>
using namespace std;
class TimeType {
public:
void Set(int, int, int);
void Increment();
void Write();
private:
int hrs;
int mins;
int secs;
};
void TimeType::Set(int h, int m, int s)
{
hrs = h;
mins = m;
secs = s;
}
void TimeType::Increment()
{
++secs;
if (secs > 59) {
secs = 0;
++mins;
if (mins > 59) {
mins = 0;
++hrs;
if (hrs > 23) {
hrs = 0;
}
}
}
}
void TimeType::Write()
{
if (hrs < 10)
cout << '0';
cout << hrs << ':';
if (mins < 10)
cout << '0';
cout << mins << ':';
if (secs < 10)
cout << '0';
cout << secs;
}
int main()
{
TimeType starttime, endtime;
starttime.Set(12,34,57);
endtime.Set(12,34,57);
cout << "Start time is ";
starttime.Write();
cout << endl;
for(int i = 0; i < 5; ++i)
endtime.Increment();
cout << "End Time is ";
endtime.Write();
cout << endl;
return 0;
}
starttime.hrs = 7;
int x = starttime.hrs;
Exercise 2: Write a complete C++ program that creates a class Point (a point in two dimensional space) which has two private data elements x and y of type double. The class should have three public member functions:
void setvals(double xval, double yval) which sets the two values
double getx() which returns the value of x
double gety() which returns the value of y
Also write a main which creates two Points, named p1 and p2, sets the x and y values of p1 to 3.0, 4.0, sets the values of p2 to 100.0 200.0, gets the x value of p1 and displays it on the screen.
You may be thinking that it would be easier to make all of the variables in a class public, and then you wouldn't need all of the utility functions which set values and get values, but this violates the concept of encapsulation. A user needs to know what the member functions are (i.e., the operations on the type), but does not need to know the implementation details.
Constructor Functions
One of the most important reasons why programs do not always work as they are supposed to is that variables are used before they are initialized, and in such cases the results are indeterminate. Since a class is just another variable type, it is possible to write code which will initialize the variables of a class when it is created (instantiated). This is done by adding a public member function called a constructor. A constructor function can take any number of arguments, but it does not have a return type. The name of the constructor function must be the same as the name of the class.
Our TimeType example was poorly designed because it would be possible for a program to try to increment or write the values of a particular time before they had been set. This problem could be eliminated by creating one or more constructor functions which would initialize the values whenever a new instance of TimeType was created. Here is code to do this.
class TimeType {
public:
TimeType(int, int, int); // The constructor
void Set(int, int, int);
void Increment();
void Write();
private:
int hrs;
int mins;
int secs;
};
TimeType::TimeType(int h, int m, int s)
{
Set(h,m,s);
}
...
Whenever a new instance of the class is created, the arguments can be passed in, and the values are then automatically initialized. For example:
int main()
{
TimeType starttime(12,34,57);
TimeType endtime(12,34,57);
...
Inline member functions
It is permissible to define functions with small amounts of code when they are declared rather than outside the function. On most compilers, the code for these functions is implemented in-line rather than as a separate function call, which sometimes means that the code runs a little faster. Here is the TimeType code with the functions implemented in line.
// TimeType3.cpp - creates a new class with in line functions
#include <iostream.h>
class TimeType {
public:
TimeType(int h, int m, int s) {Set(h,m,s);} // constructor
void Set(int h, int m, int s) {
hrs = h;
mins = m;
secs = s;
}
void Increment()
{
secs++;
if (secs > 59) {
secs = 0;
mins++;
if (mins > 59) {
mins = 0;
hrs++;
if (hrs > 23) {
hrs = 0;
}
}
}
}
void Write()
{
if (hrs < 10)
cout << '0';
cout << hrs << ':';
if (mins < 10)
cout << '0';
cout << mins << ':';
if (secs < 10)
cout << '0';
cout << secs;
}
private:
int hrs;
int mins;
int secs;
}; // end of definition of the class TimeType
int main()
{
TimeType starttime (12,34,57);
TimeType endtime(12,34,57);
cout << "Start time is ";
starttime.Write();
cout << endl;
for(int i=0;i<5;i++)
endtime.Increment();
cout << "End Time is ";
endtime.Write();
cout << endl;
return 0;
}
Objects in functions
You can pass an object (i.e., an instance of a class) as an argument to a function, and a function can return an object as a value. As with any other data type, they can be passed in either as reference parameters (which means that the function can change the contents of the object), or as value parameters, which means that the function will make a copy of the object and so the contents of the object will be unchanged when the function returns.
Here is a short contrived example which demonstrates these
concepts.
// demo.cpp - a program to demonstrate passing an object as an argument to
// a function using both value parameters and reference parameters, and
// having a function return an object.
#include <iostream>
using namespace std;
class simpleclass {
private:
int a;
public:
simpleclass(int val) { a = val; } // constructor
void setvalue(int val) { a = val; }
int getvalue() { return a; }
};
void fctn_one(simpleclass the_object) // call by value
{
the_object.setvalue(the_object.getvalue()+14);
}
void fctn_two(simpleclass &the_object) // a reference parameter
{
the_object.setvalue(the_object.getvalue()+14);
}
simpleclass fctn_three(int val) // a function that returns an object
{
simpleclass temp(val*val);
return temp;
}
int main()
{
simpleclass obj1(3);
cout << obj1.getvalue() << endl; // prints 3
fctn_one(obj1);
cout << obj1.getvalue() << endl; // prints 3
fctn_two(obj1);
cout << obj1.getvalue() << endl; // prints 17
simpleclass obj2 = fctn_three(6);
cout << obj2.getvalue() << endl; // prints 36
return 0;
}
Exercise 3: Write a program that defines a class carton. A carton has three private int data members: length, width, and height. Your class should have a constructor which takes three arguments, the length, width, and height. It should also have the following member functions:
Your program should also have a function carton nextsize (carton c) which returns a carton in which each of the three dimensions are one larger than that of its argument. Note that this is not a member function of the class carton.
Write a main which creates a carton with a length of 2, a width of 3, and a height of 4. Test all member functions on this object. It should then create a new carton by calling nextsize, and tests each of the member functions on this new carton. You may add more member functions if you need them.