You are on page 1of 12

C++ Basics What does “::” mean anyway?

By Dave Mark, MacTech Magazine Regular Contributing Author So far, this column has focused on Macintosh programming, with an emphasis on C. This month, we’re going to switch gears and talk about C++. With Addison-Wesley’s permission, I’ve codged together some bits and pieces from Learn C++ on the Macintosh that I thought might interest you. Unfortunately, there’s no way to cover all of C++ in a single column. If you would like to hear more about C++, send your comments and suggestions to Neil at any one of the editorial addresses on page 2 of this issue. And now, some legal mumbo jumbo... Portions of this article were derived from Learn C++ on the Macintosh, ©1993, by Dave Mark, published by Addison-Wesley Publishing Co. And now back to our regularly scheduled program... For the past few years, Apple (along with a host of other companies) has shifted away from procedural languages such as C and Pascal and made C++ their primary developmental language. Why C++? C++ is a superset of C and offers all the advantages of a procedural language. You can write a C++ program filled with for loops and if statements just as you would in C. But C++ offers much, much more. C++ allows you to create objects. An object is sort of like a struct on steroids. Understanding the value of objects is the key to understanding the popularity of C++. Understanding Object Programming As its name implies, an object programming language allows you to create, interact with, and delete objects. Just as a variable is based on a type definition, an object is based on a class definition. First, you’ll declare a class, then you’ll define an object (or many objects) said to be members of that class. While a struct definition is limited to data elements only, a class definition can include both data elements (called data members) as well as function pointers (called member functions). To make this clearer, let’s start with a simple problem and see how we’d solve it using both structs and objects. Our First Example Suppose you wanted to implement an employee data base that tracked an employee’s name, employee ID, and salary. You might design a struct that looks like this: /* 1 */ const short kMaxNameSize = 20; struct Employee { char name[ kMaxNameSize ]; long id; float salary; };

Bundling allows you to represent complex information in a more natural. The point here is that C doesn’t naturally offer a mechanism that allows you to bundle functions and data. C++ programmers always use const. You can define the appropriate globals in the same file with their related functions. The trouble comes when you want to reference the global outside the file it is declared in (there’s always some exception) or if you’ve defined a function that needs to refer to globals from two different categories. As you'll soon see. it is more convenient to pass the data in its bundled form: /* 3 */ PrintEmployee( &newHire ). Bundling Data and Functions Just as C bundles data together in a struct declaration. Think about all the data that you want to be available to all of your Employee structs (a pointer to the head of your Employee linked list. the struct is the most sophisticated bundling mechanism available. Believe it or not. for example. In C.id..salary ). Then. you could bundle all your other globals with the functions that they belong with.You may have noticed the use of const instead of a #define. When you write your employee management program using structs. it’s important to note that these functions are in no way connected to the Employee structs. linked list functions). Wouldn’t it be nice if you could bundle Employee linked list globals with the functions that manage your Employee linked lists. C++ bundles data and functions together in a class declaration. but that’s as far as the bundling gets. Most likely. you could write: /* 2 */ struct Employee newHire. Though many C programmers prefer to use #defines to define a constant. C++ does. You’ll also develop some functions that manage and organize the structs themselves (i. Why bundle the functions with the data? Here’s one reason. C does offer one mechanism to do this. Here’s an example: .e. If you pass a const as a parameter to a function. C++’s parameter checking will ensure that you are passing a constant of the correct type. A #define just does a simple text substitution during the first pass of the compiler. for example). This can work pretty well and is the best you can do in C.name. On the other hand. if you wrote a routine to print an employee’s data. In the C language. you’ll naturally develop a series of functions to access and modify the fields in your various Employee structs. Though this might be a subtle point. The great advantage of the struct declared above is that it lets you bundle several pieces of information together under a single name. C++ takes bundling to a new level. const is part of the ANSI C standard and is not just found in C++. each function will take a pointer to an Employee as a parameter. storing them with a bunch of other globals that have nothing to do with the Employee structs. The major advantage of using const is that a typed constant is created. For example. easily accessible form. newHire. • • • PrintEmployee( newHire. you’d most likely declare these variables as globals. newHire.

Here’s another example of a declaration: typedef MyType short. the function prototype is a declaration and the function implementation. On the other hand. The simplest method is to define the object directly. char employeeName[ kMaxNameSize ]. }. Just as you’d use a struct declaration to define a struct variable. you allocate a block of memory big enough to hold the object’s data members. When you define an object. PrintEmployee().. a classes’ data fields are known as data members and a classes’ functions are known as member functions. since this statement doesn’t cause any memory to be allocated. the statement: short myShort. float employeeSalary. It’s useful to be aware of the difference between a definition and a declaration../* 4 */ const short kMaxNameSize = 20. is a definition. Creating an Object There are two ways to create a new object. For a function. Figure 1 shows this definition. // Member functions. an extern reference to the same variable: extern short myShort. complete with function code. employee1 consists of a block of memory large enough to accomodate each of the three Employee data members. As mentioned earlier. is a declaration. seen from a memory perspective. the compiler will also make sure that your object also has access to pointers to all of the functions belonging to its class. When you define a struct variable. just as you would a regular variable: Employeeemployee1. you’ll use your class declaration to define a variable known as an object. is a definition.. A class declaration is similar in form to a struct declaration. For example. long employeeID. the definition is the statement that actually allocates memory. you allocate a block of memory big enough to hold all the struct’s fields. For a variable. Notice that the keyword struct has been replaced by the keyword class. class Employee { // Data members. This example declares a class with the name Employee.. as well as a pointer to the single Employee function. . The Employee class bundles together three data fields as well as a function named PrintEmployee(). This definition creates an object named employee1 belonging to the Employee class. In addition to the data members. void PrintEmployee( void ).

Once they drop out of scope. As was the case previously. new returns a pointer to the newly created Employee. new takes a type instead of a number of bytes. created by definition. The first line of code defines a pointer designed to point to an Employee object. pointing to an object of the Employee class. I’m just trying to show that the new object has access to the PrintEmployee() function. employeePtr = new Employee. Here’s some code that creates an Employee object: /* 6 */ Employee*employeePtr. new returns a pointer to the newly created object. they cease to exist. as well as a pointer to the single Employee function. though the syntax is a bit different. the object’s memory is deallocated. Note that the function pointer probably won’t be stored in the object itself. right along with the function’s other local variables. First. If you want your object to outlive its scope. define an object pointer. because memory for them is allocated and deallocated automatically. Figure 2 shows what this looks like from a memory perspective. new (and its partner delete) is a built-in C++ operator. as opposed to a special library function. take advantage of C++’s new operator.Figure 1. [definition] Objects created by definition are known as automatic objects. then call new to allocate the memory for your object. Although automatic objects are simple to create. the Employee object consists of a block of memory large enough to accomodate each of the three Employee data members. When the function exits. The second line uses new to create an Employee object. memory for the object is allocated. they do have a downside. new is a lot like malloc() or the Toolbox call NewPtr(). When you create an object by definition. For example. when the definition moves into scope. • • • } When the function is called. Also. memory for the object is allocated. employeePtr is a pointer. . That same memory is freed up when the object drops out of scope. An Employee object. automatically. as we did above. PrintEmployee(). you might define an object at the beginning of a function: /* 5 */ void CreateEmployee( void ) { Employee employee1.

Every single Employee object gets its own copy of the Employee data members. The function pointer may not be stored with the object itself. If you’ve defined the object directly.PrintEmployee(). employee1Ptr = new Employee. use the . pointing to the same code. Once again. operator: /* 10 */ Employeeemployee1. Notice also that both objects point to the same copy of PrintEmployee() in memory. you’ll use the -> operator: /* 11 */ .employeeSalary = 200.0.Figure 2 An object pointer. *employee2Ptr. employee1. you’ll use the . employee2Ptr = new Employee. To call a member function. use the -> operator: /* 9 */ Employee*employeePtr. At the same time. employeePtr = new Employee. employeePtr->employeeSalary = 200. If you’re working with an object pointer. You’ll refer to an object’s data members and member functions in much the same way as you’d refer to the fields of a struct. If the object was created using new. pointing to some code. with its very own copy of the Employee data members and its own function pointer. Notice that the second Employee object gets its own block of memory. Accessing an Object’s Data Members and Member Functions Once you’ve created an object. Suppose we create a second Employee object: /* 7 */ Employee*employee1Ptr. Take a look at Figure 3.0. all Employee objects share a single copy of the Employee member functions. each object you create has its own copy of the data members defined by its class. Remember. pointing to an object. operator: /* 8 */ Employeeemployee1. A second Employee. this picture may not reflect the reality of your C++ compiler. Figure 3. employee1. use the same technique. If the object was created automatically. you can call its functions and modify its data members.

Suppose PrintEmployee() then called another Employee function. a member function always starts up with a single object in mind. inside the PrintEmployee() function. This object is known as the current object. You must start this call off with a reference to an object: employeePtr->PrintEmployee(). The generic pointer has the name “this”. A reference to employeeSalary would still modify the current object’s copy of employeeSalary. The Current Object In the previous examples. that points to the current object. C++ keeps track of the object used to call the function. the object pointed to by employeePtr is the current object. is always of the same class as the function. For example. the current object (in this case. Suppose you called PrintEmployee() from a non-Employee function (such as main()). This code is kind of puzzling. The point to remember is. a call to a member function must originate with a single object. you’re used to saying: myObject->employeeSalary instead of just plain: employeeSalary The key to this puzzle lies in knowing which object spawned the call of PrintEmployee() in the first place. The “This” Alternative In the pursuit of legibile code. Whenever this call of PrintEmployee() refers to an Employee data member or function without using an object reference. The object pointed to by employeePtr is still considered the current object. however.Employee*employeePtr. inside every Employee function. For example. In the call of PrintEmployee() above. is equivalent to this line: . employeePtr = new Employee. When you are inside a member function. each reference to a data member or member function started with an object or object pointer. without referring to an object or object pointer: /* 12 */ if ( employeeSalary <= 200 ) cout << "Give this person a raise!!!". available inside any member function. What object does employeeSalary belong to? After all. This object. you can refer to the data member employeeSalary directly. which we’ve called the current object. the line: this->employeeSalary = 400. Whenever a class function is called. employeePtr->PrintEmployee(). Although this may not be obvious. the object or object pointer isn’t necessary. the object pointed to by employeePtr) is assumed. C++ provides a generic object pointer.

For example. Writing Class Functions Once your class is defined. [By the way] Another benefit of this occurs when you declare a local variable with the exact same name as a data member. which does it refer to. employeePtr = new Employee. suppose PrintEmployee() declared a local variable (or had a parameter) named employeeSalary. automatically. localEmployee is created. and is deleted as soon as PrintEmployee() exits. Another difference lies in the function implementation’s title line. You don’t have to use this. the local variable (or parameter) wins out in case of a conflict. Member functions behave in much the same way as ordinary functions. the local or the data member? As it turns out. If you create an object with new. at the beginning of PrintEmployee(). but it does make the code a little easier to read. As you’d expect. with a few small differences. and not a local or global variable. delete deletes the specified object. } Notice that the function name is preceded by the class name and two colons. Deleting an Object As we mentioned earlier. right at the beginning of the function: EmployeelocalEmployee. You’ll have to free up that memory yourself. One difference. Here’s the syntax: /* 13 */ Employee*employeePtr. it is quite clear that the data member or function is part of the class. When employeeSalary comes up in the code. pointed out earlier.employeeSalary = 400. suppose the Employee function PrintEmployee() defined its own Employee object. freeing up any memory allocated for the object. This notation is mandatory and tells the compiler that this function is a member of the specified class. you’ll delete the object yourself by using the delete operator. Here’s a sample: /* 14 */ void Employee::PrintEmployee( void ) { cout << "Employee Name: " << employeeName << "\n". but you can avoid the conflict altogether by either using this or by naming your variables more carefully. For example. you’re ready to write your classes’ member functions. is that a member function automatically has access to the data members and functions of the object that called it. delete employeePtr. . objects created by definition are created and deleted automatically. Non-automatic objects are another story altogether. Note that this freed up memory only includes memory for the actual object and does not include any extra memory you may have allocated. If you refer to a data member or function using this.

This being the case. a pointer to the new object is assigned to employeePtr.. you might want to provide initial values for your object’s data members. the new operator allocates a new Employee object. . As soon as the object is created. With two-stage construction. // Member functions. When an object is created. automatically. it’s not a good idea to allocate memory inside your constructor. employeePtr = new Employee. you create an additional member function that you call after the constructor returns. In the second line.The Constructor Function Typically. [definition] In general. then immediately calls the object’s constructor. Employee( void ). The constructor function (or just plain constructor) is a member function that has the same name as the class. long employeeID.. The constructor function is C++’s built-in initialization mechanism. or that can fail. }. Once the constructor returns. the constructor for the Employee class is named Employee(). This same scenario holds true for an automatic object: Employeeemployee1. As an example. the constructor for that class gets called. As your objects get more complex. void PrintEmployee( void ). Consider this code: /* 15 */ Employee*employeePtr. an object’s constructor will initialize each of the object’s data members. you’ll want to perform some sort of initialization on the object. float employeeSalary. The constructor will not make any calls that return a status. Typically. For example.. For example. its constructor is called. when you create an object.. Notice that the constructor is declared without a return value. Constructors never return a value. you’ll want to move to two-stage construction. char employeeName[ kMaxNameSize ]. this second routine takes the name I. you won’t want to call any functions that do return a value inside your constructor. class Employee { // Data members. Here’s our Employee class declaration with the constructor declaration added in: /* 16 */ const short kMaxNameSize = 20.

} The constructor copies the three parameter values into the corresponding data members. employeeSalary = salary. float salary ) { strcpy( employeeName. employeeID = id. This example creates an Employee object using two-stage construction: /* 17 */ Employee*employeePtr. The object that was just created is always the constructor’s current object. This is proper form. Some programmers prefer to use the same name. this is the perfect place to allocate memory. don’t define one. employeePtr = new Employee(). In other words. [By the way] Constructors are optional. If you don’t have any initialization to perform. Constructor parameters are typically used to provide initial values for the object’s data members. For example. float employeeSalary ) . it is referring to the copy of that data member in the newly created object. When an object is created. short objectStatus. Here’s a new version of the Employee() constructor: /* 19 */ Employee::Employee( char *name. } As mentioned earlier. the constructor is only called if it is included in the class declaration. long employeeID. such as employeeName or employeeSalary. the second-stage constructor for the Employee class would be named IEmployee(). Here’s a sample constructor: /* 18 */ Employee::Employee( void ) { employeeSalary = 200.0. objectStatus = employeePtr->IEmployee(). you can add parameters to your constructor. or perform any other initialization that has the potential of failing.followed by the class name. using this to keep things straight: /* 20 */ Employee::Employee( char *employeeName. long id. Notice that this constructor used different names for a parameter and its corresponding data member. Adding Parameters to Your Constructor If you like. when the constructor refers to an Employee data member. the constructor is declared without a return value. Since IEmployee() can return a status. name ).

.. pick a style you feel comfortable with and be consistent. }. Thought you’d like to know. . The destructor for the Employee class is named ~Employee(). Just for completeness.0 ). making it look just like a function call. this->employeeSalary = employeeSalary. Use the destructor to clean up after your object before it goes away. 1000. then calls the Employee constructor. employeeName ). long employeeID. 200. this->employeeID = employeeID. [By the way] Notice that the parameter list was appended to the class name. Employee( char *name. Don’t be fooled! This line of code specifies the parameters to be passed to the new object’s constructor function.. The destructor has no return value and no parameters. For instance. float employeeSalary. The constructor call happens behind the scenes and no return value is generated.. float salary ). passing it the three specified parameters. however.. // Member functions. As you might expect. 200. long id. just like the constructor. showing the new. Unlike the constructor. This line of code supplies the new operator with a set of parameters to pass on to the constructor: employeePtr = new Employee( "Dave Mark". here’s the class declaration again. } As you write your own code. void PrintEmployee( void ). this code creates an object named employee1. char employeeName[ kMaxNameSize ]. paramaterized constructor: /* 21 */ class Employee { // Data members. you might use the destructor to deallocate any additional memory your object may have allocated.{ strcpy( this->employeeName. the destructor is called when an object in its class is deleted. The destructor function is named by a tilda character (~) followed by the class name. It does not call the constructor directly. Here’s a sample destructor: /* 22 */ Employee::~Employee( void ) { cout << "Deleting employee #" << employeeID << "\n".0 ). The Destructor Function The destructor function is called automatically. 1000. This line of code creates an automatic object using parameters: Employeeemployee1( "Dave Mark"..

for example. For example. global objects are automatic and have scope. long id. If you allocated extra memory.. In addition to . ~Employee( void ). be sure your destructor has some way of knowing about it... If you are interested in learning more about C++.. the destructor is called when the function exits. the destructor is called just before the object is deleted. it’s a good idea to keep your constructor and destructor in sync. just like local objects. you might want to delete the object you just created. // Member functions. Here’s an updated Employee class declaration showing the constructor and destructor: /* 23 */ class Employee { // Data members. If your destructor encounters a non-null pointer. Yes.. it knows that additional memory has been allocated that must be deallocated. there are a lot of good books out there. employeePtr = new Employee. Obviously. the destructor is called when you call delete: /* */ Employee*employeePtr. it’s good practice to initialize your pointers to null. employeePtr = new Employee(). check the return status of your extra initializer right away. If your object was created automatically. }. we’ve just touched on the surface of C++. [By the way] If you use two-stage initialization. char employeeName[ kMaxNameSize ]. if the object was declared at the beginning of a function. [By the way] If your object was defined as a global variable. delete employeePtr. Till Next Month. if ( employeePtr->IEmployee() == false ) delete employeePtr. void PrintEmployee( void ). float employeeSalary. If your request for additional memory fails. Whether you use two-stage initialization or not. For example. /* 24 */ Employee*employeePtr. float salary ). its constructor will be called at the beginning of the program and its destructor will be called just before the program exits..} If you created your object using new. long employeeID. Employee( char *name.

] . Next month.. by Stanley Lippman and the C++ Programming Language. these two books belong on every C++ programmers bookshelf.buy it! These books are available at most large bookstores and through the MacTech Mail Order Store at a discount. All of his books are some of the best selling books that Addison-Wesley publishes for the Macintosh (outside of Inside Macintosh).. If one of his books covers a topics that fits your needs .Learn C++ on the Macintosh (by yours truly). but I can. second edition. Bjarne Stroustrup. See you then. . [Dave Mark is too humble to comment on his own books. In my opinion. second edition.Ed. I think we’ll dive back into the Macintosh Toolbox. by the man who created C++. check out the C++ Primer.