|
"Always forgive your enemies; nothing annoys them so much."
C++ Classes Part I
Note: still under development.This tutorial covers:
Disclaimer This tutorial is not for newbies/rookies. Although it begins with the basics, it is not intended as an exhaustive tutorial for first exposure to classes and C++. This tutorial is for people with some experience who would like to know the more interesting and esoteric stuff that there is to know. If you read on, you will probably find many things that are not in most C++ tutorials or you can find only in the thick books somewhere between the pages as well as many 'vaguely' explained areas of the language or areas that often cause confusion and tend to scare people away :). Questions or comments are welcome - email: litc1383 at dowling dot edu. Introduction Classes and objects are fundamental in Object-Oriented Programming (OOP). Classes in C++ are datatypes that consist of multiple components which can be eihter data of function. The functions of a class are called methods.A major characteristic that distinguishes classes from other data types is that they are not pre-defined, e.g. they are defined by the programmer himself according to his needs. This is equivalent to dynamically adding data types to the language. Is that cool or what :) After a class is declared, it can be instantiated, you can create objects of that class. The difference between an object and a class is very important and very easy to understand - you can have only one class with a given name, but multiple objects of that class. Think of the class as the description of the object - the object should have such and such variables and methods. The object, on the other hand, is the flesh-and-blood realization of the defined class - it holds real values in these variables. Class definition The definition of a class consists of two parts: declaration of the class and definitions of its methods. Class
declarations follow this sythax:
class[name of class] {
components declaration
};
Please mind the semicolon at the end. You need it. Don't forget it.
Class names have to be unique throughout a program. Class's components, however, are local and do not suffer from that constraint.Here is a sample class called Person:
class Person {
char name[20];
unsigned age;
public;
void GetData();
void Display();
};
Here we have two variables and two methods - name and age, GetData() and Display(). After a class is declared, we need to define its mehtods:
void Person::GetData() {
cout << "Enter name: "; cin >> name; cout << '\n';
cout << "Enter age: "; cin >> age; cout << '\n';
}
void Person::Display() {
cout << "Name: " << name << "Age: " << age << '\n';
}
:: is called the scope resolution operator or just scope operator. It tells the compiler which method belongs to which class (remember that
different classes can have methods with the same name. That's why we need a way to discern between the different methods).Here is how to create 2 objects of the class Person (we also call some methods): Person john, mary; mary.GetData(); mary.Display(); john.GetData(); Access modifiers (component visibility) The rules governing access to the data of a given class are different for the class's methods and external functions.
This difference is very important: the methods of a class have full access to its components. Furthermore, you don't need to pass
the variables of a class as parameters to the methods - they have priviliged access, known as 'direct access'. This is why GetData()
and Display() did not take any arguments (in case you were wondering:)). john.age = 10;the compiler will give an error. Through the use of private variables we achieve what is called data encapsulation(also known as data hiding). This is very important in OOP. The ultimate view (eg the right and perfect way that things should be done) is to have all member variables declared as private and provide methods that either change them (mutators) or display them (accessors). However, this is almost never the case. People like shortcuts and like to leave themselves backdoors for quick access. Not a good policy (just look at Windows - the ultimate example in using shortcuts). Anyway, what about public and protected? Well, you should think of protected being just like private. The difference shows up when we get to inheritance. When a member is declared as public, everyone has access to it. Pointers to objects. Arrays of objects. Dynamic creation of objects. We can define pointers to objects just like we can do for any other data type: Person *ptr;The result is that we declared a pointer to an object of the type Person. Now we can assign addresses of other objets of the type Person to ptr. Here's how: Person george; //Create an object of the type Person ptr = &george; //Assign ptr the address of george (*ptr).GetData(); //Call george's GetData() method (*ptr).Display(); //You should figure this one out by nowThe construction (*ptr). is equivalent to ptr->, e.g. we can write the code above like this: ptr->GetData(); ptr->Display();We can also have dynamic objects by using new and delete. Here's how:
Person *ptr;
ptr = new Person;
if(!ptr) { //Equivalent to if(ptr == NULL)
cout << "Not enough memory";
}
After this fragment of code is executed, ptr will point to a newly created object of the type Person. If new fails, prt will be NULL.
The above could be written more concisely like this:
if(!(ptr = new Person)) {
cout << "Not enough memory";
}
You can destroy objects created with new by using delete at any time. For example, here's how to destroy (e.g. free the memory) ptr:delete ptr;Arrays of objects are defined the same way as arrays of other data types: Person array[10]; array[3].GetData(); array[i].GetData(); (*(array+i)).GetData(); //Equivalent to the line above.The last three lines show you how to call member functions (methods) from within an array. If you have trouble reading the last line, take a look at the pointers tutorial (if I have written and uploaded it :)). Anonymous Classes
C++ allows you to declare classes without names. Such classes are called anonymous. All methods of an anonymous class have to
be defined inside the declaration of the class (e.g. they have to be inline functions). The reason for that is obvious - since the
class has no name there is no way that we can tell the compiler which class we are refering to. Because of the same reason we have
to declare the ojbects of the anonymous class in its definition (e.g. immediately after the '}' but before the semicolon ';'). Here's
and example:
class {
int x, y;
char ss[20];
public:
void Display() { cout << x << " " << y << " " << ss << '\n'; }
} obj1, obj2;
In case before you were wondering why we need the semicolon at the end, now you should see. This is how we tell the compiler that we
are done with declaring objects of the given class. Note that this way of declaring objects is valid for all types of classes, not just
the anonymous ones.
Objects as members of a class
It may come as a surprise to you but members of a class can be other objects as well, not just functions and variables. Here's an example:
class Person1 {
char addr[20];
public:
Person x;
void GetAddress() {cout << "Enter address: "; cin >> addr; } //This is an inline method
void DisplayAddress() { cout << "Address: " << addr;
};
Since x is a member of Person1, every object of type Person1 will have a component x, which is an object of the type Person (and x will
contain the variables age and name, as well as the methods GetData() and Display()). If y is an object of the type Person1, here's how
to access x's components:y.x.GetData(); y.x.Display(); y.GetAddress(); y.DisplayAddress();C++ does not allow recursive declarations, e.g. a class cannot have itself as a member. However, there is no such constraint when it comes to pointers. This means that you can declare pointers in a class to itself. For instance:
class List {
public:
List item; //Error! Recursive declaration
List *next; //This one's cool
};
The pointer this When an object of a given class is created, the code for the methods of this class is not copied inside every object but rather stays at one location in memory. A resonable question is how do the methods of a class know which object exactly they were called and respectively the data of which object should they change or display? Here's the answer: when a method is called, besided the parameters specified in its declaration, there is one more parameter that's passed invisibly. This parameter is a pointer to the object that invoked the method and its name is this. this is a keyword in C++. Here's an example:
class cl {
int x;
public:
void increm() { x++; };
}
main() {
cl obj1, obj2;
obj1.increm();
obj2.increm();
}
At compile time the reference to x is compiled as if it were this->x. Hence, the code that will actually be compiled for the method
increm is the following:
void increm() { (this->x)++; }
When obj1 is called this will point to obj1, hence obj1 will be altered. When it's called with ojb2, this will point to obj2. Simple, isn't it?
Inside the body of the methods you can use this just as any other pointer, including as specifying it as a return value. Static class components
Usually, every instance of a class has its own copies of the data members. However, sometimes it is necessary for all the objects to
share one or more variables. For example one such variable could be the number of instances of the class. This effect is achieved
through the use of static data (usually variables, but could also be objects).
class CL1 {
public:
int x;
static int y;
};
int CL1::y = 2; //NOTE that y has to be initialized before it can be used. Usually this is when memory is allocated.
main() {
CL1 a, b;
a.x = a.y = 3;
cout << a.x << " " << a.y;
b.y = 4;
cout << a.y;
}
y is a static variable, which means that it is shared by all objects of the type CL1. Hence, a.y and b.y are two different ways
to call the same thing - y.Depending on the compiler, memory for the static variables is allocated at different times. Usually it's when they are initialized. And, anyway, why would you want to use an uninitialized variable, right? Anyway, to initialize y in the previous example, we use CL1::y = 2;. Static Methods
Classes can have not only static data but static methods as well. Unlike normal methods, static methods can be used stand-alone, not
only in the context of an object. The stand-alone use of static methods requires the use of their full identifier (e.g. class name
:: method name). Here is a sample static method f:
class X {
int m;
public :
static void f(int x, X *ptr); //Static method
};
...
void f2() {
//Here we are calling an external function f, not f in the class X!!!
f(1, &obj);
//We call the static method f in X by using its full name
CL1::f(1, &obj);
//We call the static method f through an object - obj
obj.f(1, &obj);
}
There is a very important difference between normal class methods and static methods: static methods do not receive this as
an implicit parameter and therefore they do not have access to the components of the object that was used to invoke them. For a
static method to be graned this access we need to pass a pointer or a reference to the object.
|