Journal Articles
Browse in : |
All
> Journals
> CVu
> 114
(20)
All > Topics > Programming (877) Any of these categories - All of these categories |
Note: when you create a new publication type, the articles module will automatically use the templates user-display-[publicationtype].xt and user-summary-[publicationtype].xt. If those templates do not exist when you try to preview or display a new article, you'll get this warning :-) Please place your own templates in themes/yourtheme/modules/articles . The templates will get the extension .xt there.
Title: What's In a Struct?
Author: Administrator
Date: 03 June 1999 13:15:31 +01:00 or Thu, 03 June 1999 13:15:31 +01:00
Summary:
Body:
Recently I was trying to introduce object orientation to someone familiar with an imperative programming language (namely Pascal), and thought I would go via C structs. Unfortunately I only succeeded in confusing her, so now I'm writing an article to confuse all the beginners in ACCU. Well, hopefully not. This article does not go (far) into object-oriented design, but it is my hope that it will leave the reader prepared to learn about good object-oriented design without constantly puzzling over how it relates to procedural programming.
Please note that, for brevity, several of the examples here hard-code "magic numbers" into variable initialisations. In practise this is a Bad Thing™ since it limits the capability of the user to adjust the program to individual requirements. I would not write example code that uses "magic numbers" anywhere other than in variable initialisations, since this will also lead to making the code difficult to maintain. For example, imagine writing a long program and then realising that in lots of places where you had said 17 you really meant 15. You can't just search and replace 17 because there might be other 17s that don't need changing, and in other places you might have wanted 17-1 and written 16, and so on. It would take a very long time to put this right and be reasonably sure you're not introducing any more bugs, whereas if you had put it in a manifest constant you can change it in seconds.
What is a struct? You can think of it as "a way of grouping variables together For Some Strange Reason", but it does help to know what is really going on. One important thing about C is it's a compiled language, traditionally "close" to machine code.
Experienced C programmers can write a few lines of C and know exactly what assembler would be generated by them (at least, until an optimiser comes along and thinks it knows better). But if it is done well in C then it will be portable across different machines (present and future) with different assembly codes. You can't ignore the relationship between C and assembler, and in assembler you need to know what's really going on in the computer's memory.
Consider the following C:
int h; struct { int a,b,c; }p;
This effectively makes four integer variables: h, p.a, p.b and p.c. But how will those variables be arranged in memory? There are two 'obvious' arrangements:
Arrangement 1
Word 0: h
Word 1: p.a
Word 2: p.b
Word 3: p.c
Arrangement 2
Word 0: p.c
Word 1: p.b
Word 2: p.a
Word 3: h
Notice that p.a, p.b and p.c are guaranteed to be in order, but there is no reason why h should be before them. If an optimising compiler decides that, on a particular machine, it is best to swap h and p, then it is quite at liberty to do so. But it cannot change the order of a, b and c within p.
One reason why this is useful is that you can create a pointer to p and pass it around your program. If p were a very large structure then passing a pointer around is very much cheaper than passing the whole thing around. But all a pointer tells you is the memory address at which p is stored. To read the values from it, therefore, you need to know that the first value is a, the second is b and the third is c. If some optimiser switched the order behind your back, all kinds of things will go wrong. This is why the order is guaranteed to be left alone (or at least, it's guaranteed that your program will behave as if the order is left alone).
[By the way, I do not have access to an authoritative copy of the standard, so if anybody spots something wrong then please write in.]
Passing pointers really comes into its own when you want your functions to modify the values in the original struct. Without pointers and structs, the only way you can do this is to have global variables, which are bad things™. To illustrate, I've written a music program that needs to store a lot of information about the piece as it goes. In the early days (years ago), when I wanted another variable, I would very often think "oh let's just have a global - I want to get this done quickly and nobody's going to notice". Then one day I thought, "wouldn't it be nice to edit several pieces of music at once?" And I realised how many globals I had accumulated, all scattered throughout the modules - every single one of them must go before the program can cope with multiple independent scores. I made a token effort but it got so tedious that I never actually finished. My shortcuts had caught up with me.
A simple-minded solution to the above problem (if you can stand going through reams of messy old code) is to put all of the globals into a struct, and pass a pointer to that struct around the program. What I was actually trying to do was more complicated, but it has the same basic idea. Then, if you wanted several pieces of music, you can just create an instance of the struct for each piece, and pass the appropriate one to the program:
int main(){ struct { /* hundreds of variables go here */ } first_piece,second_piece; /* initialise them here */ draw_music(first_piece); draw_music(second_piece); }
Actually there are several things wrong with the above. The most obvious one is the fact that the "hundreds of variables go here" struct is within main(). As it is, you would need to list all of those variables again every time the struct is used. This is tedious and prone to error, not to mention how awkward it would be to add a variable later. But doing boring repetitive things is precisely what computers are good at, and needless to say there is a way of automating it. Actually there are several, and the two most common ones are:
Method 1:
typedef struct { /* hundreds of variables go here */ } Piece_of_music; int main() { Piece_of_music first_piece,second_piece; draw_music(first_piece); draw_music(second_piece); }
Method 2:
struct Piece_of_music { /* hundreds of variables go here */ }; int main() etc
Method 1 is easy to understand when you think about it: What the 'typedef' effectively does is to replace 'Piece_of_music', whenever it occurs where a type of variable should go, with the struct. I think method 2 is neater but I know some readers will disagree. With method 2, you can also create instances of the struct between the closing brace and the semicolon if you want.
With either method, what you would actually do (if you had any sense) would be to put the struct part in a header file. I expect most readers know about header files but since I recently came across someone who was writing lengthy programs without them (and there are some very poor books out there), I think I'd better re-iterate. Consider the following C:
int main() { puts("Goodbye cruel world"); }
This is valid C and will compile and run. But puts is a function that is in the standard library, not in your file. The compiler will not look at the standard library unless you tell it to. So how does it know what machine code to generate for that function call? How can it create the jump to the place in memory where the code for puts is stored, if it doesn't know what puts is? It also needs to know what sort of parameters it takes and what type of value it returns, in order to generate the necessary machine code. And yet you haven't told it any of these things. What's going on?
Things get stranger. If you tried compiling the above with a C++ compiler, you would get something like "Error at line 2: implicit declaration of function puts". Yet, a C compiler won't complain at all. This is one of the subtle differences between C and C++ that people sometimes don't know.
Generating an executable has two main stages: compiling and linking. This simple thing is missed out of some introductions because most compilers these days automatically run the linker when they have finished. I'm fairly convinced that it's not a good idea to leave out details for beginners just because they're hidden. It only results in the beginners not really understanding what's going on. I once saw someone who seemed to be determined to teach Java without mentioning pointers, because although pointers are everywhere in Java they are hidden, so why bother the newcomers with them? Needless to say, the sample of people I talked to (who hadn't met Java before) couldn't explain what new did, and many of them didn't understand the difference between a class and an instance and what static meant. Then one day someone raised his hand and asked, "What does it mean NullPointerException?" "Gotcha", I thought. But no... And missing out the mention of the linker in C is almost as bad as missing out pointers in Java. (Incidentally, Java does not have a linker because the classes are loaded in as needed by the program, which has its advantages and disadvantages.)
When the compiler compiles your program, it does so one module at a time. By "module" I mean file - if you have thousands or even tens of thousands of lines in your program, putting it all in one file can make it difficult to manage. Because most compilers only look at one module at once, you can compile a library of functions for use in somebody else's program, and this is what the standard libraries are, including puts(). What the linker does is to take the output of the compiler (the object file) for each of your modules, and the standard libraries (usually), and 'link' them together. So if in one of your modules you call a function that is defined in another, the compiler will generate the machine code to call the function, but will leave the actual memory address (telling the processor where the machine code for the function actually is) blank. The linker will find that function and fill in the actual address, and make sure it's included in the final program (if it's a sensible linker then it will only include the functions that you actually use, so not every standard library function gets into your executable).
The compiler does not need to know where puts actually is, because the linker will fill in that part, but it does need to know what types of variable puts() takes as parameters and what type it returns. This is so your program can be checked and the required machine code generated. In C, if you don't tell the compiler these things, it will guess. The people making the C++ standard realised that, if you leave the compiler to guess things, it's easier to make a mistake without realising it, so in C++ you MUST declare your functions explicitly. (Actually C++ uses function overloading and that effectively requires visible function declaration at the point of compilation. Francis). This is why the above code works in C but not in C++ (if the compiler's any good). C will guess by looking at the types of variable you actually give the function (it assumes you are right) and assuming it returns an int. In this case it is correct, but that is not always the case and is dangerous to rely on.
How, then, do you tell the compiler? Well, you could do it yourself by adding the following:
int puts(char const * string_to_put);
but let me say immediately that this is not the best way of doing it (and is actually a mistake in C++ where you must not provide your own declarations of library functions. Francis). What it says is, there is a function called puts that returns an int and takes a 'char const*' (don't worry about that yet) as a parameter (the 'string_to_put' in this case can be left out, but it does make the program more legible). When you are actually writing the function, you put braces rather than the semicolon and can then refer to the parameter string_to_put as a variable.
Aside: Originally in C you had to write functions like this:
int my_func(my_parameter) long my_parameter; { /* code goes here */ }
but these days most people agree that that's ugly and outdated (and won't work in C++. Francis). As for where to put the 'int puts(char const* string_to_put);', at the top of the program (before main()) is a good idea because then it is visible throughout the file; if you put it within main() then it will only be visible within main(), and if you put it at the very end then the compiler won't know about it the first time it reads main(). Compilers are very fussy things - they like to know about everything in advance and usually can't look further down in the file for them. So even if you did put all your functions in one file, you may still have to declare them before you write them.
With standard libraries, it's not a good idea to declare all the functions yourself because you might make a mistake. Indeed, even with your own functions, it would be nice if you could declare them only once and use that declaration for all the files in your program that need it. This saves you from having to make lots of changes (some of which you might forget) if you want to modify it. Again, we have repetition, so programmers inevitably invented an automatic way of avoiding it (a general rule is: if you ever find yourself doing something repetitive with computers, something is wrong). This method is header files.
The principle behind a header file is very simple: You tell the pre-processor (which is something that runs just before the compiler does) to include another file into this one. So, for example, you can have two files, file1.h and file2.c (header files by convention have an extension .h):
file1.h:
typedef struct { /* hundreds of variables go here */ } Piece_of_music; void draw_music(Piece_of_music p);
file2.c:
#include "file1.h" int main() { etc
and what happens should be obvious in the light of the previous example. By convention, you do not compile the .h files (they are automatically included in the files that you do compile), and you can give each .c (or .cpp) file a corresponding .h file, all with meaningful names. Thus file1.h might be better called piece_of_music.h, and in all the files in your program where that struct is needed you can start with #include "piece_of_music.h". Then, if you need to change something, you need do it only once.
The standard functions like puts are in a file called stdio.h that comes with the compiler (stdio is short for "STandarD Input and Output" library), and instead of writing
#include "stdio.h"
you write
#include <stdio.h>
which makes sure that the compiler's directory is checked for the file rather than the current directory (where your own files are). (It is a bit subtler than that because the header files for the Standard Library don't have to exist, the compiler can know what is in them and use that knowledge direct as long as you have written the correct #include. Francis) (Some time ago I spent ages trying to work out why my program wouldn't compile on a shared system, and it turned out that some joker had changed the compiler's header files - this is an evil thing to do.)
One other thing about header files is you can have nested includes, so you can have one header file including another. So, if you're not careful, you can have two files trying to include each other, which is not a good idea. To get around this possibility (and also because including a header twice by accident can generally confuse things) you can write all your header files like this:
#ifndef PIECE_OF_MUSIC_H #define PIECE_OF_MUSIC_H typedef struct { /* hundreds of variables go here */ } Piece_of_music; #endif
Any line beginning # gets sent to the pre-processor (like #include does). The first line means "only pay attention to the following code if the pre-processor macro PIECE_OF_MUSIC_H is not defined" (ifndef is short for "if not defined"; there is also a #ifdef). I'd better not go into all the complications of pre-processor macros here, but in the simplest form they can be either defined or not, and many programmers put them in all capital letters by convention. To begin with, PIECE_OF_MUSIC_H is not defined, so the #ifndef and the #endif are ignored. However, #define PIECE_OF_MUSIC_H causes it to become defined. So if the header file were included twice in the same module, the second time it is included the pre-processor will ignore everything between the #ifndef and the #endif, which amounts to not including the file. Similarly, if two files tried to include each other, then the first could include the second, but the pre-processor won't let the second include the first because by that time the first would have defined a macro that prevents it from being included again.
There is something else that is worth noting about the linker, but is also left out in many introductions. This is "name mangling", and is what causes those bizarre error messages that you get when you mismatch your types between modules. The compiler can check the types within a module (e.g. if you said that a function takes an integer parameter then it can complain if you try to give it a pointer), but it cannot check the types between modules. That is left to the linker. And the way it is done is to modify the names of the variables so as to include type information, so if there is a type mismatch across modules then the names will not match up. This "name mangling" can mean that messages from the linker are highly cryptic, but "undefined symbol" usually means a type mismatch across modules. (Note that a C compiler should not do this, it is a C++ mechanism. Francis)
As it stands, main() passes the Piece_of_music structure to draw_music by value, which means it makes a copy. With large structures this can be expensive in processing time and memory requirements, and it may be better to pass a pointer (or in C++ a reference). But several things need to be considered. Is the function allowed to change the thing that is pointed to? It can be useful, but it might be undesirable. If not then it should be marked as const, so the compiler can check to make sure that you really don't change it. But it is possible to override this, so whenever you pass a pointer to a library function written by somebody else, you are trusting them not to change the thing it's pointing at if they say they won't. In most cases you should be all right - if somebody is being particularly malicious in a library then that library (or compiler) should not get good reviews.
But there are still traps to fall into. One is when a function takes two pointers, one as a const and the other as something it's supposed to change, but actually they point to the same thing. For example, consider the following:
strcat(a,a);
strcat is a standard string function (provided by #include <string.h>) that concatenates one string onto another. A string in C is an array of characters, and you can pass arrays around cheaply by giving a pointer to the first element (a zero byte is stored at the end of the string to terminate it). So the library functions often take arguments of type char* (i.e. pointer to char) and char const * (pointer to char but you've promised not to change the thing it's pointing at). strcat takes parameters of char* and char const*, for the destination and source strings respectively. To see what is wrong with the above, consider the following example implementation of strcat:
char* strcat(char* destination,char const* source) { int d,i; d=0; while(*(destination+d)!=0) { d=d+1; } i=0; while(*(source+i)!=0) { *(destination+d)=*(source+i); d=d+1; i=i+1; } *(destination+d)=0; return destination; }
(Yes I know I could have written it much more briefly, but I'm trying to make clear what's going on.)
Remember destination is a pointer, and the * on the front is a dereferencing operator, meaning "go and get the value stored at this pointer". So to begin with the value at destination is got, and compared with 0 to see if it's the end of the string. If not, the value at destination+1 is likewise, then destination+2 and so on. When the first while-loop finishes, the variable 'd' holds the length of the string, and writes to the address (destination+d) can append characters to the string. This is what the second while-loop does, using 'i' to keep track of which character in the source is being written. Finally, an extra 0 byte is written to destination to mark the end of the string, because the condition in while is tested before the code executes, so the 0 would not have been copied.
(The fact that the function returns destination, rather than void, enables you to write code like strcat(strcat(a,b),c);. C is full of things like that, e.g. you can say a=b=c=5; because the operator '=' returns its left-hand operand.)
Going back to our example, if this function were called with both parameters pointing to the same thing, it would loop forever and overwrite the whole address space, as you can convince yourself by following the code through. In general, if there is any danger of your functions getting two or more pointers to the same thing, one of which can be changed, then you need to be very careful.
Of course, I was really making the point for your own functions rather than the standard libraries, but it is generally a good idea to avoid risking it anyway. The standard libraries could of course be implemented in such a way that it didn't matter (in the above example, the length of the source string could be found before any of the copying is done), but this might not always be the case. Interestingly, the strcat that comes with gcc, when given the following program:
#include <stdio.h> #include <string.h> int main() { char test[100]; strcpy(test,"allo "); strcat(test,test); puts(test); }
produced "allo allo a".
To modify the music example to pass by pointer, you need to change the declaration of draw_music to take a parameter of type Piece_of_music * (it doesn't matter whether or not there's a space before the *) or const Piece_of_music *, instead of Piece_of_music. Within the body of that function, you need to dereference the pointer before accessing its members. So if one of the fields of Piece_of_music were called number_of_bars, the following two fragments of code are OK:
void draw_music_1(Piece_of_music p) { p.number_of_bars=3; } void draw_music_2(Piece_of_music* p) { (*p).number_of_bars=3; }
but the following is not:
void draw_music_wrong(Piece_of_music* p) { p.number_of_bars=3; }
because 'p' is simply a pointer, and pointers do not have fields called number_of_bars. Also note the brackets around *p, since the dot has a higher precedence than the indirection operator, so without the brackets it would get interpreted as *(p.number_of_bars) which is wrong.
An abbreviation for (*p).number_of_bars is p->number_of_bars (read "p arrow number_of_bars"), and this is usually preferred. In Java, everything is done as if with the arrow operator, but just to be confusing it is written as a dot.
The other thing that needs to be changed is in main, where first_piece and second_piece are passed by value. To pass the memory address, you need to prefix them with an ampersand (&) (a "take the address" operator), i.e.
draw_music(&first_piece); draw_music(&second_piece);
In C++ there is a nice thing called "references" that means you don't have to remember all of these differences. In other words, you can continue to use the dot operator and you don't worry about the ampersands in main. All you have to do is put an ampersand where draw_music is declared (and where it is written),
void draw_music(Piece_of_music &p);
and the compiler does the rest for you - you can pretend that you're passing by value but actually you're passing by pointer (but this time it's called reference) (Well that cuts a few corners. Francis). There is much less confusion to be had from references than from pointers and references tend to produce cleaner-looking code, although the traps like what happens if you pass two references to the same thing, one of which can be changed, are still there, even in Java, subtly hidden away, waiting to prey on the innocent...(Actually it is worse, because instances of user defined types in Java are anonymous. But let us not get into that here. Francis)
A struct is a type in the same way as the primitive types (int, char, long etc) are types, and you can use them in very similar ways. If you have defined a struct then you have defined your very own type of variable. You can have arrays of that variable:
Piece_of_music haydn_symphonies[104]; strcpy(haydn_symphonies[ 99].title,"The Clock"); strcpy(haydn_symphonies[103].title,"The London");
(note that offsets range from 0 through 103 not from 1 through 104, hence symphony 100 is at offset 99; also I have assumed that a string called 'title' with sufficient reserved space is one of the "hundreds of variables"). You can have dynamically allocated structs (especially in C++ with new and delete, but I'd better not go into those now). In C++ you can say that a function is not allowed to change a struct even when it is passed by value:
void my_function(My_struct const do_not_touch) { do_not_touch.a=4; // This will cause an error }
(That should be true in C as well. Francis)
You can also write structs out to a file and read them in quite easily. There are a few pitfalls if you expect the resulting file to be readable when your program is compiled with a different compiler. (in particular, some compilers 'pad out' your structs with unused bytes because some processors can deal with them quicker if their size is a multiple of 2 or 4 or 8). Generally speaking it's best to write your own saving and loading code (in Java you can use serialisation to do it for you). If you want to know the size that your struct takes up, as with any other variable type you can use the sizeof() operator, so sizeof(Piece_of_music) will evaluate as the number of bytes required to store all the variables that are in a Piece_of_music.
You can have structs within structs: (But careful because the rules are different for C and C++. Francis)
main() { struct French_Suite { Piece_of_music prelude,sarabande,minuet,courante,rondeau; } my_piece; my_piece.sarabande.number_of_bars=80; /* other initialisation stuff here */ draw_music(my_piece.minuet); }
and you can have structs that contain pointers to other structs of the same type:
struct Linked_list { Piece_of_music data; Linked_list next; };
You can also (although you rarely need to) have a pointer to a struct before you have told the compiler what is actually in the struct:
struct second_struct; struct first_struct { int a; second_struct* pointer; }; struct second_struct { double b; first_struct* pointer; };
structs really come into their own when you are implementing data structures, such as linked lists, binary trees, hash tables, stacks, queues, and so on, during which you may have an arbitrary number of some item in memory (you don't know in advance how many there will be), memory can be allocated and deallocated as your program is running, and it is helpful (more often than not, essential) to package things together and point to them.
Object orientation strongly features the "least privilege principle", which states that, in order to detect design errors, you should ensure that each part of the program can only have the privileges that it needs for its job. It is as though your program is a top-secret operation being conducted on a "need to know" basis - if some part of the program doesn't need to know something, then it doesn't. This does not just mean restricting the variables it can write to, but also restricting those that it can read, and the information it can get. If you write code that tries to do something that you said should not be done, the compiler can detect this and tell you, which is much better than it going unnoticed and you having to track down the bug later. The least privilege principle does not just apply to variables, but this is where it is most often found.
In fact, if you use the least privilege principle, your code will not only be safer but may also be faster and/or smaller. This is because a good compiler can sometimes use the restrictions you have placed on your code to get a better idea of what you are up to, and it might know a good optimisation for it. So even if you think you are perfect, there is good reason to use the least privilege principle.
A powerful feature of C++ in this respect is its public, private and protected access modifiers. But before they make sense, you need to know about 'methods'. These are basically functions inside structs. For example, consider:
struct Picture { int x; /* etc */ }; void draw_picture_to_screen(Picture &p) { do_something_with(p.x); } int main() { Picture myPicture; myPicture.x=3; draw_picture_to_screen(myPicture); }
C++ will let you re-write this as follows:
struct Picture { int x; // etc void draw_to_screen(); }; void Picture::draw_to_screen() { do_something_with(x); } int main() { Picture myPicture; myPicture.x=3; myPicture.draw_to_screen(); }
As before, you would replace the dot with an arrow if myPicture were a pointer. The code is already looking cleaner, but the real advantage of methods becomes apparent when you use the modifiers.
What is happening in the latter example is Picture::draw_to_screen is implemented as a function, as before, with a hidden pointer to the structure that it is working with. This pointer is called this (for "this object") and you can access it yourself if you like: you can call do_something_with(this->x) just as easily as do_something_with(x). However, doing this is only really useful when there is another x you need to worry about, and if this is the case then your naming convention is probably too confusing. So in practise you don't really need to know about this except when you need to tell some other object about where in memory this object is (and you'll find out when that's useful when you look at object-oriented design). It is important to remember the difference between a struct and an instance of a struct. Picture defines a general type of variable, and the picture that is being acted on is a particular instance of that; there could be others. It is the same idea as the one behind the Piece_of_music example.
To illustrate access modifiers, let's modify the Picture struct a little:
struct Picture { private: int x; // etc public: void draw_to_screen() const; void set_x_to(int new_x) { x=new_x; } }; void Picture::draw_to_screen() const { do_something_with(x); } int main() { Picture myPicture; myPicture.set_x_to(3); myPicture.draw_to_screen(); }
I have introduced three things here: Access modifiers, an inline method and (for good measure) a const method. The latter is indicated by the word const after the draw_to_screen(), and it means that draw_to_screen is not allowed to change any of the member variables of the picture. This is an example of the least privilege principle, so any methods that CAN be const SHOULD be (except constructors and destructors and certain cases in inheritance, but you're not supposed to know about that yet...). By default, everything in a struct is public, which means that anybody anywhere in the program can access them (if they can access an instance of the struct itself). Things that are private can only be accessed by a method of the struct. That is why the set_x_to() method is required, since otherwise there is no way of getting to x from outside the program. So, as it stands, we have allowed anybody to set x, but nobody can read it (and after all, nobody needs to read it so why allow them to?) But we can do better than that: We can make sure that x is set only once and cannot be set again (unless you use a nasty cast), by giving the struct a constructor:
struct Picture { private: const int x; // etc public: Picture(int in_x); void draw_to_screen() const; }; Picture::Picture(int in_x) : x(in_x) { // This code is executed when you make a Picture // Nothing here in this example } int main() { Picture myPicture(3); myPicture.draw_to_screen(); }
Now x is a const, i.e. it cannot be modified, even by a picture itself. It can only be set in the 'constructor', a method that is called when a new instance of the struct is created (notice how the parameter is passed from main). All structs have constructors, but if you don't supply one yourself then the compiler will make one for you (that doesn't do much). They also have destructors, which are methods that are called just before an instance of the struct is deleted from memory (either explicitly by the programmer or because it was a local variable and the function or whatever is now about to return). Destructors are useful for "cleaning up", e.g. freeing any resources associated with that object, deleting other objects it has made for itself, and so on. Their use becomes clear as you do object orientation. As the constructor for Picture is Picture, the destructor is ~Picture.
C++ has another keyword, class, that is used in a similar way to struct. The difference between C++ structs and classes is that members of classes are private by default (until you put a modifier) whereas members of structs are public by default. For this reason, class is preferred because it is safer. In practise this does not make a lot of difference, because a common convention places a class' public methods at the top (so they are among the first things you see when you view the file). Often the first thing you do when you write a class is along the lines of:
class Picture { public: Picture(); ~Picture(); };
Note that constructors and destructors had usually better be public, but there are some exceptions to do with inheritance and constructing via static methods (you'll meet those later). I have been using structs rather than classes in this article mainly so that I could delay the introduction of access modifiers until I had explained what was going on. Normally, though, C++ programmers use classes. There is nothing to stop you from writing a C++ program with structs, but it is not "the done thing". A good convention is that classes have NO PUBLIC VARIABLES at all; all member access must be done through methods. Even if you think that some member data should be public, you may change your mind (or your design) later, and if you have implemented it using 'get' and 'set' methods already then you do not have to go through your entire program putting them in. 'get' and 'set' methods also allow you to intercept these actions and do useful work (e.g. you may want to log them, or mark something as needing recalculation when its variables are changed, or whatever) (However a lot of get and set methods usually suggests that you have yet to fully understand object-orientation. Francis). Don't worry about speed: If you use inline methods for 'get' and 'set' (like the inline 'set' in the above example) then the compiler will generate exactly the same machine code as it would if you access the variables directly.
I have to end this article somewhere, but just to tempt you, here is a brief mention of some of the other things that you can do in object-oriented programming. Container classes and templates: There are all kinds of data structures that you can put your data in, from the humble array and linked list, through binary trees and hash tables, to very specialist structures that allow fast storage and retrieval of a particular type of data. If you were writing a linked list in a conventional language then (unless you did a nasty trick) you could easily end up re-writing a linked list for every type of variable that you want to link, in every program that you want to do it. But if you wrote a linked list container class using something called 'templates', it will be a Linked List To End All Linked Lists, the last one you ever have to write (if done well). Whenever you need a linked list of anything, one line will tailor the very code you need from your template. And the same goes for the more powerful data structures, such as hash tables (which can be very efficient dictionaries).
Needless to say, other people have already written all those nice container classes, and one effort was so good that it got included in the C++ standard - it's called the Standard Template Library (STL). So while writing linked lists and other stuff can be instructive, these days it's really an academic exercise because you can use the STL classes very simply. In effect, you just say "I'd like a hash table of Pictures please" and it's yours. This means that some programs that would otherwise use rough-and-ready hacked-out data structures for the sake of getting the program working quickly (and the programmer can't really be bothered that the data is stored in the best way possible) can now use top-quality ones even more quickly (if you're familiar with the STL). And, of course, if someone in the future invented a brilliant new data structure that you can't wait to use in your program, and implemented a container class for it, you would not have to do much work at all to change your program to use it.
Inheritance and virtual methods: The usefulness of this is best understood by example. Suppose you have a function or method that can take as a parameter something of type GraphicsDevice* (for example), and call its methods, such as line(), circle() and so on. But we haven't said what sort of graphics device it is - it might be a screen, a printer, a window, an image file on the disk, or even something like a turtle. All of these will have different ways of drawing lines and circles, so they will need different line() and circle() methods. Inheritance lets you say things like "All graphics devices can draw lines" and "A screen is a graphics device, and this is how it draws its lines". The method that is actually doing the drawing doesn't need to know what it is drawing to, and this makes writing the program much simpler. And then if I came to you and said something like "Please can you get it to output the graphs to a Braille embosser?" and gave you some methods to do it, you would only need to change one or two parts of your program, rather than having to go through the whole lot. Also, if you compiled your program on a different operating system with a different way of drawing things, you only need re-write a small part of your graphics library, rather than the whole program.
Inheritance can have multiple layers. For example, the graphics library in my music program has a subclass of GraphicsDevice called BitmapDevice (I think, he says without having the source in front of him), and there is code in BitmapDevice for drawing lines, circles etc to a bitmap in memory. Then the bitmapped image files and the dot matrix printers can all inherit from BitmapDevice rather than GraphicsDevice, since they all share the bitmap code and only differ in the way that they output the final bitmap.
Other devices, like PostScript printers, can inherit directly from GraphicsDevice. This gives you an idea that inheritance is a useful thing, but you can make some much more complicated structures than I have described (you can have stuff like classes inheriting from more than one class at once, which Java does using interfaces instead). However, views do differ on this and many say that, if you make your inheritance structures too complicated, you will only make things confusing. That is why Java replaced multiple inheritance with a simplified version called interfaces. As with anything in design, good design uses the features you need when you need them, rather than using every possible feature just to show off. Exception handling: Imagine in a conventional language you call a function, which calls a function, which calls a function, and so on and so on, and then, about thirty levels deep, something goes wrong. Something very bad, like a network operation failing because the network's just gone down. What happens? Does your program print a message and exit altogether? That would be annoying to ordinary users and would be disastrous if it happened to be an air traffic control system. Does your function return an error code? In that case, the function calling it would also probably have to return an error code, and so on and so on, right back up until some function knows how to deal with the error - imagine how much checking for error codes you need to write, and how many mistakes you can make. You need a way of jumping from the tiny function at the bottom all the way up to the code that can handle the problem, like a 'goto', but it needs to be a nice 'goto' that cleans up the local variables, calls destructors, and so on. Such a thing is called an exception; the inner function throws an exception and the outer function catches it. (Exceptions that are not caught by anything do cause the program to terminate abnormally)
But how do you pass a message from the inner function to the outer one, saying what is wrong? Come to that, how do you tell which outer function should catch the exception? There must be different types of exception, because there are different types of things that can go wrong, and you might want different code to be called depending on what has gone wrong. Indeed, for some things that go wrong, you might be able to handle them only one or two functions up the call stack, but for other things you might not be able to handle them until you get back to the main program. So the exception throwing mechanism might jump to different places depending on what has gone wrong.
Obviously, you can't just throw "an exception". You have to throw a particular type of exception, and that exception needs to be able to contain arbitrary information that you have specified. Sounds familiar? Yes, you can throw an instance of a struct or a class. (You can also throw primitive types and pointers, but classes are most useful when you have a reasonably complex exception mechanism in your program.) You can catch exceptions according to what class they belong to, and inheritance can really come into its own here. For example, some code can say "I want to handle all network exceptions but I can't handle anything else", and some other code might say "I only want to handle a packet-loss network exception". The exception will propagate up the call stack until some code can handle it.
Streams: This is a feature of C++ but not of Java. A stream is any I/O device that processes characters sequentially - it could be a keyboard, some text on the screen (or in a window), a file, a terminal, a printer, a string in memory, or something else (there is obviously an inheritance mechanism going on here). You can give any object you want a method to write its data out to a stream, or read it in from one. Then you can just say (effectively), "write this object to the printer, get that one from this file" and so on. Writing formatted output to the screen with streams in C++ is much safer than doing it in C with functions like printf(), since with printf() in C you have to firstly put in the format string that you're going to give it an integer (say), and then actually give it an integer. If for some reason you get it wrong (or you later change another part of the program to make that variable no longer an integer), then the compiler does not (usually) tell you and the program behaves strangely or crashes at runtime. This is one of the nasties in C, but with C++ streams it's easier.
Concurrency: Most programs these days have to do several things at once. For example, a responsive user interface needs to accept the user's commands whether or not it's also trying to do some big calculation, or wait for some traffic on the network. An animation program, or even more so a game, might need to move tens or hundreds of objects at the same time but at different speeds. Sometimes it is much easier to write each object as though it were the only object in the whole program, and write a separate scheduler later that calls its methods, especially when you come to write a new type of object and want to quickly integrate it with the program, or have a variable number of objects that you don't know in advance (this happens quite a lot in games), or you find a better scheduling algorithm and want to re-write the scheduler easily without having to change any of the other classes.
Real threads (as supported by Java but some implementations are not too good) really do allow more than one part of your program to be running at the same time on multiple-processor machines, and can simulate it on single-processor machines. You can create an object and tell it to go off and do something while you get on with something else - you don't have to wait for it to finish until you need its results. Different threads can independently be calling the various methods of your objects, although you do need to consider various locking mechanisms to make sure that two or more of them don't happen to try conflicting operations simultaneously, and you also need to guard against such things as deadlock, which is when two or more threads are each waiting for each other. Again, threads are something that you might want to use sometimes, but there is little point in using them just to "show off".
There are many other uses for object orientation, but perhaps the best ones are those that have not yet been discovered. The struct (and its sibling, the class) is not just a way of grouping variables and functions together For Some Strange Reason. It is a powerful idea that led to the development of a whole new programming paradigm and great leaps forward in design possibilities. But in order to use object orientation effectively, you need to be familiar with the design paradigm, not just the syntax. Someone in C Vu once illustrated this by saying that, if a Victorian carpenter were given an electric drill, he might use it as a hammer, which would not be ideal because a power drill makes a lousy hammer . But if you become familiar with good object-oriented design and it becomes second nature to you, you will be much more capable and one day you will wonder how you ever managed without classes and structs.
Notes:
More fields may be available via dynamicdata ..