Structures

Structures are a means for grouping together values of the same or different type. Often called a heterogeneous data structure (as opposed to homogeneous, where all values are of the same type). In database terms, a structure would correspond to a single record in a database.

Example:


struct CDAccount

{

    double balance;

    double interest_rate;

    int term;

};      // Another instance of semicolon after bracket!

A structure definition does not declare any variables! It does, however, define a new type that can be used when declaring variables. The type name is called the structure tag. Each element of the structure has a member name (correspond to field names in a database).


CDAccount myAccount, yourAccount;

Allocates space for two structure instances, each of which holds two double values and one int value.


Referring to structure members

Structure members are referred to in this format: structure_var_name.member_name. This is called a member selector. A member selector can be used in the same contexts as a single variable of the same type.

 

myAccount.balance = 1500.00;

myAccount.interest_rate = 0.05;

myAccount.term = 18;

yourAccount.interest_rate = myAccount.interest_rate;

yourAccount.balance = myAccount.balance * 2;

cout << "Account balance: " << yourAccount.balance << endl;

You can copy all the values from one structure to another like this:


yourAccount = myAccount;

You can initialize the members values in a structure at declaration time like this:


CDAccount myAccount = {1500.00, 0.05, 18};

You must list the values in the same order as the member names are listed in the structure definition.


Different structures with the same member names

This is perfectly legal:


struct CDAccount

{

    double balance;

    double interest_rate;

    int term;

};



struct SavingsAccount

{

    double balance;

    double interest_rate;

    double minimum_balance;

    double monthly_fee;

};



CDAccount myCDAccount = { 1500.00, 0.05, 18 };

SavingsAccount mySavingsAccount = { 3000.00, 0.025, 500.00, 5.00 };



cout << myCDAccount.balance << ", " << myCDAccount.term << endl;

cout << mySavingsAccount.balance << endl;


Nested structures

Structure members may be structures themselves.


struct PhoneNumber

{

    int area_code;

    int prefix;

    int extension;

};



struct AddressEntry

{

    PhoneNumber homePhone, officePhone, faxNumber;

};



AddressEntry myAddress;



myAddress.homePhone.area_code = 716;

myAddress.officePhone.prefix = 425;

myAddress.faxNumber = myAddress.officePhone;


Structures as function parameters


int main()

{

    CDAccount myAccount = {1500.00, 0.05, 18);



    myAccount.balance += ComputeInterest(myAccount);

}



double ComputeInterest(CDAccount theAccount)

{

    return theAccount.balance * theAccount.interest_rate;

}

In this case it's smart to use a reference parameter; otherwise, space will be allocated for another structure value and values copied from the argument into that space. And since the function doesn't change any of the structure's values, you should use a constant reference parameter, like so:


double ComputeInterest(const CDAccount& theAccount)

{

    return theAccount.balance * theAccount.interest_rate;

}


Returning a structure as the value of a function call


int main

{

    CDAccount myAccount;



    myAccount = NewAccount(12);

}



CDAccount NewAccount(int months)

{

    CDAccount temp;

    temp.balance = 0.00;

    temp.term = months;

    temp.interest_rate = 0.03 + (0.01 * (months / 6));

    return temp;

}


A better way of accomplishing the same thing

This is better because it eliminates the copying of data from the returned structure value to the structure variable in the main program.


int main

{

    CDAccount myAccount;



    SetUpAccount(myAccount, 12);

}



void SetUpAccount(CDAccount& theAccount, int months)

{

    theAccount.balance = 0.00;

    theAccount.term = months;

    theAccount.interest_rate = 0.03 + (0.01 * (months / 6));

}

Notice the use of the reference parameter; this means that changes to the structure members of theAccount within the function are actually changing the structure members of myAccount.


Combining structures and enumerated types


enum Suit

{

    CLUBS, DIAMONDS, HEARTS, SPADES

};



struct Card

{

    int    rank;

    Suit   suit;

};



bool Beats(const Card& firstCard, const Card& secondCard, Suit trump);



int main()

{

    Suit trump = DIAMONDS;

    Card myCard = { 10, HEARTS };

    Card yourCard = { 3, DIAMONDS };



    if (Beats(myCard, yourCard, trump))

        cout << "I win!" << endl;

    else

        cout << "You win!";

}


Lab

(a) Write the Beats function prototyped on the previous slide. My card beats your card if it is of the same suit but of higher rank, or if it is of the trump suit. Test it on several pairs of values, including one pair where both cards are of the trump suit, one pair where one card is trump and the other is not, one pair where both are the same non-trump suit. We're assuming that there's only one deck, so the two cards will never be exactly the same.
(b) Write a program which allows input of initial values for a CDAccount. Then calculate the value of the CD at the end of the given term, assuming the interest compounds monthly. Use a function AddInterest to add in the interest payment for one month.

A class definition

Classes are in some ways similar to structures. They have data members, which can have heterogeneous types. The data members of a class are called properties. Classes can also have function members, which are called methods. Packaging together the methods and properties into a class is also called encapsulation.

A class definition looks like this:


class Time

{

public:

    void Set(int hr, int min, int sec);

    void Increment();

    void Display();

    bool Equal(Time time);

    bool Before(Time time);

private:

    int hours;

    int minutes;

    int seconds;

};

Public members are available for use outside the class. Private members are only available for use within the class. It is very typical to have all the data members private; this is called data hiding. That means the only way to change the data members is by calling member functions which change them.


Declaring class instances (objects) and referring to class members

Declaring a variable of a class type instantiates the class. The class instance is called an object (as in object-oriented programming!). The object contains all the data members and methods, both private and public. Only the public members are accessible through the instance variable. Accessing a member has the same syntax as for structures - use the 'dot' operator.


int main()

{

    Time now;



    now.hours = 5;		// Not allowed, because hours is a private member.

    now.Set(10, 59, 59);

    now.Display();

    now.Increment();

    now.Display();

    now.Increment();

    now.Display();

}

The output might look like:


10:59:59

11:00:00

11:00:01


Defining the methods of a class

Class method definitions look very similar to regular function definitions, except that you need to indicate that they are associated with a particular class. This is done using the syntax class-name::method-name. The :: is called the scope resolution operator, because it identifies the method as being in the scope of the named class. This means that within the method definition, you can refer to all of the members of the class, both public and private. At run-time, what you'll actually be referring to is the members of a particular instance of that class. Here are a couple of method definitions:


void Time::Set(int hr, int min, int sec)

{

    hours = hr;

    minutes = min;

    seconds = sec;

}



void Time::Display()

{

    cout << hours << ":";

    if (minutes < 10) cout << "0";

    cout << minutes << ":";

    if (seconds < 10) cout << "0";

    cout << seconds << " ";

    cout << endl;

}


Get/Set methods

Since we made our data members private, it would be useful to have methods in our class that allow users of the class to retrieve and change the values of the private data members. This is a very common paradigm, and the methods are often called "get" and "set" methods. For instance:


class Time {

public:

    int getHours();

    void setHours(int);

	...

private:

    int hours;

	...

};



int Time::getHours()

{

    return hours;

}



void Time::setHours(int hr)

{

    hours = hr;

}

It may seem silly, at first glance, but it makes code much easier to read and bugs easier to isolate. Some new technologies (Microsoft's COM objects, Java Beans) are built around standard methods like these.


A class method with a class instance parameter

Note that for a class instance passed as a parameter, you must use the dot operator to refer to its members. You can refer to both public and private members of parameters of the class containing the method, but only public members of other classes.


bool Time::Equal(Time time)

{

    return ( hours == time.hours &&

             minutes == time.minutes &&

             seconds == time.seconds );

}



int main()

{

    Time now, later;



    now.Set(10, 40, 35);

    later.Set(10, 40, 39);

    if (now.Equal(later))            // can you grok that construction?

        cout << "The times are the same." << endl;

    else

        cout << "The times are different." << endl;



    return 0;

}


Lab:

Add "get" and "set" methods, and implement functions for the following class. You can put the whole thing in one file, with the class definition first, followed by the main program and method definitions. Write a main program which lets the user input two dates and displays the two dates in their chronological order. If the dates are equal, it should just display the date once.


class Date

{

public:

    void setDate(int month, int day, int year);

    void displayDate();		// In the format 10/3/1998

    bool Equal(Date otherDate);

    bool Before(Date otherDate);

    // Add get and set methods here

private:

    int month, day, year;

};


Overloading methods

Just as you can overload functions, you can overload methods within a class. For instance, my Time class can have three Set methods:


class Time

{

public:

    void Set(int hr, int min, int sec);

    void Set(int hr, int min);

    void Set(int hr);

    ...

};



void Time::Set(int hr, int min)

{

    hours = hr;

    minutes = min;

    seconds = 0;

}



void Time::Set(int hr)

{

    hours = hr;

    minutes = seconds = 0;

}


Operations on Classes

You can use class instance variables on both sides of the assignment operator. The following assignment copies all the data member values, public and private, of noon into now.


Time noon, now;



now.Set(12, 0, 0);

noon = now;

You cannot automatically use the comparison operators on classes. The following code would not give you the desired result:


if (now == noon)

    cout << "Time for lunch!";

else if (now < noon)

    cout << "Not lunchtime yet.";

else

    cout << "Gee, I'm sleepy!":

Next class we'll learn how to define operators like these for classes.


Initialization of class members

Declaring a variable of type int or double only allocates memory; it does not initialize the value. The same is true for class instance variables. The data members of a class instance can be initialized by using a Set method, as in the Time class. Alternatively, you can define a class constructor, which is a special method that gets called automatically when you declare a class instance variable. Like so:


class Time {

public:

    Time(int hr, int min, int sec);

    Time();

	...

};

Notice (a) the constructor has the same name as the class, (b) there is no return type for the constructor method, and (c) you can overload the constructor method. The method definitions look like this:


Time::Time(int hr, int min, int sec)

{

    hours = hr;

    minutes = min;

    seconds = sec;

}



Time::Time()

{

    hours = minutes = seconds = 0;

}


Using the class constructors

If there is a class constructor which takes no arguments (the default constructor), it gets called automatically when you declare an instance variable.


Time now;        // Time() constructor called automatically

Time now();      // Incorrect!  Compiler may think it's a prototype!

To use a constructor which takes one or more arguments:


Time later(9, 50, 22);    // Time(int, int, int) constructor called

You can also use the constructor to reset/reinitialize a variable that has already been declared, but the syntax is different:


later = Time(10, 15, 4);

now = Time();

You should always define a default constructor for your class.


Lab:

Add constructors to your Date class, including a default constructor and one to replace the setDate method. Modify your program to use these new constructors.