Handy Compilation of C/C++ MethodsIntroduction
You can email me at paulfrazee@cox.net with your questions, comments, and corrections. Don't hesitate to contact me; I enjoy the feedback!
Macros: Macros have to be the most under-used feature for new programmers. They are very useful, and a good programmer should take note of them. Why? Well, for instance, you can streamline a function that will be called often and with congruent parameters. The windows API call MessageBox is a good example:
Now all you have to do is call MSGBOX("","") instead of MessageBox(NULL,"","",MB_OK)! Nice, huh? Another common use is to define a standard function prototype that can be reproduced in an easy and clean way. For example, let's say you want to make a standard naming convention for a library you are making:
Now you can easily declare and define a function that has FUNC_ at the front of the name, and is of type void. Observe:
The output of that program would be "MyFunc!!" Cool, huh? Now that you have seen the wonders of macros, let's get a more in-depth look at them. What Are Macros? Macros are, simply, groupings of code with a name attached. Like functions, they can have parameters passed to them, but it is important to note that they are not the same as functions. Functions are code compiled into a static memory location whose definition will appear in the actual binary output only once. Macros are, during early compilation stages, replace with the code they are defined as. Macros are pretty much always in all caps (by convention rather than necessity). You declare and define them all at once, and it is safe to define them in headers. Here is the format:
You do not have to surround the contents with brackets ({}), although there are certain exceptions. If you ever wish to continue a macro past its declaration line, you append that line with a back-slash (\):
Another interesting feature is that the parameters are not type based, either. You can pass an integer, a string, or even flat out code to parameter. As long as you keep in mind what a macro truly is, you be able to use it effectively. In case you are having trouble grasping this whole concept, I will provide you with an example. Observe:
As you can see, MyMacro is a macro that wraps the function printf. So...
is the exact same as saying
Got it? Good. And remember, macros are not magical ways around casts. In the previous example, the data passed to MyMacro would have to be a string (char array). When Should Macros Be Used? By now you may be asking yourself why one would ever really want to use a macro, especially when a function can do exactly the same thing. Well what you should understand about the macro is that it is designed simply for code cleanliness, a tool for the programmer. In the end product, it is no different from just typing out the body of the macro. So when is best? I recommend using it in the following situations:
Use well. This Pointer: The "this" pointer is so important that I can’t imagine you don’t already know of it. When programming data structures – usually classes - it may become necessary to refer to the instance of class within itself. The answer to this problem is the "this" pointer. It is a pointer of the type of the class it is being used in, to the instance using it. Behold:
In this example, the programmer with horrible naming conventions had an ambiguous variable situation. The rules of C/C++ state that a parameter or locally defined variable overtakes a class member or global variable, which made access to CMyClass::int_variable impossible. To specify which variable he was talking about, the programmer used the this pointer. Another common use is to pass itself into a function, like in the following example:
Aha, very useful indeed! Note that the this pointer works just as well for structs. Dynamic Memory: As you create more complex programs, you will find that arrays of a fixed size are not cutting it anymore. Eventually you are going to need to create arrays of variable size, allocate and delete objects, and more, and I can tell you how. But first, let’s have a little technical stuff. This may be over your head – if it is, simply skip it. I would, however, recommend reading it; it is quite interesting. First of all, you must understand that the program, when compiled, translates into memory positions in the executable (all of my discussion pertains to the PE executable file format common to Windows, though can apply in other situations). When the program is loaded into the memory, those memory positions remain intact, but are simply relative to the position the program was loaded into. So if a line of code was in position 4 in the executable file, and the file was loaded into position 1000 in the memory, that line of code would be at position 1004 in the memory. Now, how are variables put into the memory? Well, that depends on the variable’s declaration situation. If a variable is global, it is actually put into the executable the same way code is, meaning that a 4 byte integer at position 20 would take up the positions 20 through 23 in the exe memory. That is why global variables are generally frowned upon – they make the executable take up more memory. Now, function-local, fixed-size variables (the ones you are used to) are different. They are allocated to what is called the "stack" at the beginning of a function call. The problem with those is that, as I previously mentioned, they are fixed-sized! The alternative is a dynamically allocated variable, which is managed through pointers. Dynamically allocated variables are pointers that, instead of pointing to fixed-size variables in the memory, point to allocated memory in the "heap". Because these variables are not allocated until instructed to do so, and because the memory is managed by a flexible pointer, the programmer has the power to decide exactly how much memory the variable will need and when to allocate it. What this means for you is that you can create arrays of a size determined at runtime. I find the most common application to be with strings. Time to find out how this can be done! Make Me Some Memory There are actually a couple ways to allocate memory in C++. The most common is new and delete, the C++ commands. New allocates the specified amount of memory and returns a pointer to it, and delete deallocates it. You should never use one without the other for stability reasons. Allocate with new but forget to free up with delete? You have what is called a memory leak – memory is being taken and not given back. Try to deallocate with delete but never allocated with a new? You program is going to crash through an assertion, because you just tried to deallocate "bogus" or null memory. The functions’ usages are as follows:
The first two usages have to do with classes, and I will not delve into that any further. However, the latter two I will go into. You use those to create dynamic arrays – they are very simple. Here is an example:
I recommend adhering to the example above pretty closely for allocation and deallocation, as failure to do so can very well lead to bugs. The other way to allocate and deallocate memory is through the three functions left over from the C days: malloc, realloc, and free. How are they different? Well mostly in usage. New simplifies the allocation by calculating how much memory will be required and casting it for you (it multiplies the size of the type times the number of elements in the array). Malloc requires you to do all of that. So why use malloc and free? Personal preference, really. Here is the usage:
Malloc, free, and realloc all make use of the void pointer (a cast-less pointer). As such, you must cast the data, like so:
Malloc may look very intimidating, but it really isn’t as complex as it looks. In the end, however, I recommend using new/delete, for a few reasons. First of all, when you are instantiating data objects with constructors or destroying those with destructors (not covered in this article), the constructor or destructor will be called. The other reason is that since you are probably writing in C++, then you will want to stick to the C++ commands. Ah, one little function left – realloc. There will be times when you will want to resize your dynamic arrays. When this is the case, I would recommend using STL’s vector or something along those lines, but I am betting that you don’t know STL yet. So, we better reallocate that array! While you could just make a new array of the desired size, copy the old array’s data, and delete the old array, it is much easier to just use realloc:
Realloc returns the newly allocated data as a void pointer, and also takes the variable as a void pointer, requiring some casting on your part:
That isnÂ’t so hard! And now you too can make dynamic arrays. One final subject I should like to touch upon. You may be asking yourself whether it is safe to mix new/delete and malloc/realloc/free. The truth is that yes, it is completely safe. Data is data, no matter which function was used to attain it. However, it is considered to be good coding not to mix them. Now that you understand dynamic arrays, go grab yourself a book on STL and forget most of this! :D Logic Without The If( ): Standard programming languages today are built on logic. And while if statements are all good, sometimes you just need a quick expression check that doesn't require all those brackets... and so the ? was born. ? is an expression evaluator, like the if statement but designed to work as a one-liner. Here is the syntax:
That may take a little explaining.is the logic in question (e.g. a==0 or handle!=NULL).is the code that will be executed if the expression evaluates to true.is the code that will be executed from the operator if the expression evaluates to false. If you are still stumped, consider this:
The output would be "a=6 value=3", because the expression (a==6) was true. You can also run functions instead of just returning a variable, like in this example:
How does that work? Well remember how macros would simply insert its code where it was used? This is kind of like a conditional macro that has its contents inserted at runtime. So if the condition evaluates to true, it is just like the true code was there, and the same for the false code when false. I find this particularly useful for when you are passing parameters or doing conditional math. Getting That Log, Even In A Crash: When you start to make a complex system, especially a game, you need to have a logging system so that you can find out exactly what made your game crash. Unfortunately, those that have made them are certain to have experienced the log-destroying crash. An undefeatable problem? I think not. What is happening when you lose your log? Well that has to do with the way file input/output works in C/C++. When you use file writing functions, the data is not immediately written, but rather qued for writing in the memory. The actual file writing will not occur until either the file is closed or until the file is flushed. When the program crashes, the data that was qued was never written – it was still in the memory. So as a good rule of thumb, when you write to a log, flush it immediately thereafter. If you are using the stdio FILE operations, you do that with fflush:
ThatÂ’s all it takes! No more lost logs! Yay! Whelp, that concludes the article. Hope you learned something! Paul Frazee (The Rainmaker) |