Jump to content

Jedi Academy turned 20 this year! We celebrated in a ton of different ways: mod contest, server event, podcast, etc. Thank you to all who have been a part of this game for the last two decades. Check out the anniversary content!

Read more

Welcome to JKHub

This community is dedicated to the games Star Wars: Jedi Outcast (2002) and Jedi Academy (2003). We host over 3,000 mods created by passionate fans around the world, and thousands of threads of people showcasing their works in progress and asking for assistance. From mods to art to troubleshooting help, we probably have it. If we don't, request or contribute!

Get started

This game turned 20 years old this year, and it is still one of the greatest Star Wars games of all time. If you're new or returning from a long hiatus, here are the basics of getting started with Star Wars Jedi Knight Jedi Academy in 2023.

Read more

C and C++ Primer


eezstreet

C and C++ Primer

by eezstreet (with edits from Xycaleth and mrwonko)

Last updated: 8.9.15

 

Table of Contents

  • Introduction
  • C++ 101: Variables and typing
  • C++ 102: Control flow
  • C++ 103: Functions
  • C++ 201: Pointers

Introduction

First things first, it goes without saying. Learning how to program is difficult. I'm not saying this to drive away potential coders or anything like that. In fact, I readily encourage newcomers to try out programming and see if it suits them. It just might change your life! The trouble comes when one first looks at a program like JKA and sees a big mess of code and it doesn't look anything like the nice, compact programs that C++ tutorials provide. Or even worse, they have you using constructs like std::string or std::cout which are totally irrelevant C++ concepts in an environment like JKA where either they don't work or aren't the preferred way of doing things.

 

On the other end of the spectrum we have tutorials like the ones here on JKHub which are glorified Copy+Paste and nothing more. I hate these. To be clear, a lot of the stuff you'll learn in the beginning involves using copy+paste, but there's no room for creativity. I like to see people empowered by knowledge instead of blindly changing things in files until it works. That's not programming, that's guessing. I aim to solve some of the confusion here.

 

Where to go to get help

There are a lot of places where you can seek guidance from others if you need help with something. First and foremost you should try and research your problem. The search feature here on the forums works well, and there's always Google to help reach these ends. If you still haven't solved your issue, here are a number of places you can go:

  • The JACoders IRC channel*. Flash based webclient here.
  • The Coding and Scripts forum on this website if your question is specific to programming.
  • The OpenJK General Discussion forum is good if your question is specific to OpenJK.
  • StackOverflow.com can provide you with coding help if you need it, but don't expect help regarding JKA specific stuff

Direct private messages should probably be avoided as it could help solve problems for other people who encounter the same problem that you have.

 

* A note about IRC: IRC is shorthand for "Internet Relay Chat." As such, it's common to idle in the channel. It may take a while for someone to respond to your messages, so please be patient. Many times have I checked and someone has joined, asked a question, and left before I could answer it!

 

Final Note

This tutorial (if you can call it that) isn't meant to teach you how to do any one thing in the game. It's a language primer designed specifically to teach C and C++ in the context of making a mod for this game. It's also not a way of teaching you how to setup an environment. There's already a tutorial for that, and this one assumes you have read it.

 

Some stuff is designated as "ENGINE ONLY" or "C++ only." This means that it cannot be used in C code, which is used in the multiplayer DLL code (which is what the original SDK was composed of)

 

 

 

 

 

C++ 101: Variables and Typing

Programs store information in chunks of memory we call "variables." All variables need to be declared before they can be used, otherwise the compiler will not understand what you are doing. In C++ code, we can declare variables anywhere, but in C code we must declare variables at the beginning of any section that starts with {}s (these are known as blocks). Below are some of the most basic types.

  • int: represents a number, ranging ~negative 2 billion to ~positive 2 billion on 32 bit systems
  • bool (engine only): represents only two values: true and false.
  • qboolean: represents only two values: qtrue and qfalse.
  • float: represents a decimal number, like 3.14.
  • double: like a float, except it can be more precise
  • char: represents a character, like 'a' or '@'. Some more complex characters like Japanese symbols are only compatible with types other than char.
  • (pointer): "points to" another variable in memory. More on this later.

So that being said, there are a number of manipulations you can do with these:

int someNumber; // value = unknown. On this line we are declaring the variable 'someNumber' which is of type 'int'
someNumber = 0; // value = 0
someNumber += 10; // value = 10
someNumber++; // value = 11
someNumber--; // value = 10
someNumber -= 2; // value = 8
someNumber /= 2; // value = 4
someNumber *= 2; // value = 8
someNumber %= 3; // value = 2 (this is the "remainder of" operator, known as modulus. Divided numbers are rounded when they are int type.)

These are the most basic operations that exist. Here are some things we shouldn't do:

int someNumber; // we are declaring a variable 'someNumber' of type 'int'
someNumber += 2; // undefined behavior. someNumber doesn't have a value assigned to it, so adding 2 to that value is undefined!
someNumber = 0; // this would be okay...
someNumber /= someNumber; // we are dividing by zero here, which would invoke a crash.

In C++ code only, you can define a variable as being "auto". This means that the type will automatically be deduced.

int x = 5;
auto y = x; // type is int

Note that a variable can have prefixes added to them. Some of these will be discussed later.

unsigned int x; // can only be positive or 0
signed int x; // can be positive or negative
const int x; // can't be changed

There are of course some more complicated things we can do, but before that we need to understand the concept of binary number systems.

 

 

Binary Number Systems

Every modern computer uses binary to represent numbers (and numbers can represent code...but this is rather deep stuff we're talking about!). But just what is binary? Normally we use 10 numbers to represent a digit - 0 through 9, or base 10. Binary on the other hand is only represented by 2 numbers - 0 and 1. There are of course other systems of numbers, and two others which are commonly used in programming are Octal (digits 0 - 7, base 😎 and Hexadecimal (digits 0 - 9..and then A B C D E F, so base 16).

This raises the question. How do we represent a number in base 10, like say... 10, in a base 2 number system? There are many different ways, and that's a bit beyond the scope of this guide, but you can use the Windows Calculator to do so by switching to Programmer Mode (Alt+3 or View > Programmer). There are some radio buttons that say "Hex Dec Oct Bin". By selecting Dec (short for Decimal, or Base 10) and entering in whatever number you want to convert, and then selecting Bin (short for Binary, or base 2), you will now see the number in binary. There's also various ways to convert by hand.

 

Now that we have a basic grasp of a binary number system, let's discuss size. Each variable type is assigned a size in bytes. A byte is 8 bits (binary digits) and thus can represent 2 ^ 8 different things. Here are some default sizes:

  •  
  • int: Depends on machine. 32 bit systems use 4 bytes (because they're 32 bits, get it?), 64 bit systems use 8 bytes
  •  
  • bool: Depends on machine. It's either the same in size as an int or it's 1 bit.
  •  
  • qboolean: Same as int.
  •  
  • short*: 2 bytes
  •  
  • char: 1 byte
  •  
  • float: 4 bytes
  •  
  • double: 8 bytes
  •  

* Short is just an int with smaller size. So is char, for that matter.

 

By manipulating bits in the variable, we open up a world of new possibilities.

int x = 10; // value (binary) = 1010, value (decimal) = 10
x << 1; // shifts all the bits LEFT by 1. value (binary) = 10100. value (decimal) = 20
x >> 2; // shifts all the bits RIGHT by 2. value (binary) = 101. value (decimal) = 5

There's also four bitwise functions we can perform with two values:

  •  
  • AND (&): Sets all bits to TRUE where the bits in both values are the same, false otherwise.
  •  
  • OR (|): Sets all bits to TRUE if said bits in either value are TRUE. false otherwise
  •  
  • XOR (^): Sets all bits to TRUE where the bits in both values are different, false otherwise.
  •  
  • NOT (~): Flips the bits.
  •  

The bitwise functions are important for dealing with flags such as spawnflags, weapons, force powers, etc.

 

 

Arrays

So how does one represent a sequence of numbers? Easy. Use an array.

int x[5]; // an array with 5 elements
int y[5] = { 0, 1, 2, 3, 4 }; // an array with 5 elements that starts at 0 and ends at 4
int z[] = { 0, 1, 2, 3, 4 }; // same as above but pointing out that the size need not be predefined

Using the above snippet, we can also access values.

int nValue = 0; // value = 0
nValue = y[1]; // value = 1. note that in C/C++, we start counting at 0!!
nValue++; // value = 2.
nValue = y[nValue]; // value = 2. we can also use variables to index arrays!

Strings

The astute observer may have realized that we haven't got a type for words, phrases or anything like that. Simply put, these are referred to as strings and will probably be the biggest headache you have to deal with at first the type least desired to use. If you can represent something via a number, it's probably best to do so. But in case you can't- a string is an array of char type. The last character in a string is a null terminator, or a '\0' character.

char someString[] = "some string";

Vectors

Since we are dealing with a three-dimensional game, we often find ourselves working with 3D math often. Vectors are arrays of floats and several functions, like weapon firing and movement, make extensive use of them. Here are the basic types and where they're commonly used:

  •  
  • vec_t: also known as float. Not often used.
  •  
  • vec2_t: also known as float[2]. Used for 2D coordinates and drawing things onscreen, like with the HUD.
  •  
  • vec3_t: also known as float[3]. Used with 3D math most frequently, like movement, projectiles, etc.
  •  
  • vec4_t: also known as float[4]. Almost always used with regards to colors, with each value corresponding to a channel (RGBA)
  •  

A vector can be normalized or denormalized. When a vector is normalized, it is between the values -1.0 and 1.0. When it is denormalized, it...isn't. Since vectors are just arrays of floats, they can be treated as so:

vec3_t a = { 1.0, 1.0, 1.0 };
vec3_t b = { a[0], a[1], a[2] };

An easier way of writing the above would be to use the VectorCopy function:

vec3_t a = { 1.0, 1.0, 1.0 };
vec3_t b;
VectorCopy(a, b);

3D vector math is a complicated subject that warrants its own guide and is thus out of scope.

 

 

Enumerations

As stated above, if you can represent some idea with a number, an enumeration is the best way to proceed. Weapons, force powers and more are represented by these. Hopefully this block of code explains it best:

enum SomeStuff {
SOMESTUFF_HERE, // any time you use SOMESTUFF_HERE, it will represent the value 0
SOMEOTHER_STUFF, // any time you use SOMEOTHER_STUFF, it will represent the value 1
// 2..
// 3..
};

In C++, an enumeration can also be a type:

SomeStuff x; // x can only be SOMESTUFF_HERE or SOMEOTHER_STUFF!

Structures

Structures (or 'structs' for short) are big blocks of data comprised of multiple fields.

struct SomeStruct {
int someField;
double doubleRainbow;
};

We can now use SomeStruct as its own type:

SomeStruct x;
x.doubleRainbow = 0.01;

There's another way to initialize x here too...

SomeStruct x = { 0, /*someField*/ 0.01 /*doubleRainbow*/ };

Some of the most commonly-used structs in JKA are gentity_t (defined in game/g_local.h), pmove_t and cg_t (defined in cgame/cg_local.h)

 

 

Type Casting

Sometimes you will find yourself needing to convert variables of one type to another. Normally you shouldn't be doing this, but in the rare cases you do, there is a little cheat code - the Type Cast.

int x = 10;
float y;
y = (float)x; // we are converting the type of x to be a float, since y is a float!

There are other ways of type casting, but they are C++ only, and explanation of other things is required before they can be used adequately.

 

 

Macros

Macros aren't exactly variables or types, but it's important to understand what they do in order to save time. Basically, a macro places code for you where you expect it to be. Ergo the define statement:

#define SOMECODE_HERE = 0
int d SOMECODE_HERE; // what this compiles down to is "int d = 0;"

More often than not, you see this being used to define values:

#define MAX_WEAPONS 19

You can also insert variables into macros thusly:

#define SOMECODE_HERE(x) = x
int d SOMECODE_HERE(10); // this compiles down to "int d = 10;"

C++ 102: Control Flow

Variables are great, but they're nothing without actually doing something with them. Enter the almighty if statement:

int x = 10;
if(x == 0) {
// some code that never gets run
}

Conditional statements and loops are the backbone of your code and help to shape behavior. In order to understand that, a final piece of the previous section needs to be discussed.

 

 

Boolean Values

The bool and qboolean types are interesting in that they can only represent true (qtrue) and false (qfalse). The most obvious way to assign values would be the following:

qboolean bSomething;
if(/* something */) {
bSomething = true;
} else {
bSomething = false;
}

However, the more intuitive way would be to do the following:

bSomething = /* something */;

There are a variety of boolean functions:

  •  
  • Comparator (==). This one compares two values to see if they are equal, and returns true if they are. Example: if(a == b) would succeed if a and b are the same value. NOTE THE TWO EQUAL SIGNS, NOT ONE!
  •  
  • Inequality comparator (!=). This one compares two values to see if they are not equal and returns true if they are. Example: if(a != b) would succeed if a and b aren't the same value
  •  
  • Greater than operator (>). Example: if(a > b) would succeed if a is greater than b
  •  
  • Less than operator (<). Example: if(a < b) would succeed if a is less than b
  •  
  • Greater than or equal to and Less than or equal to operators (>= and <=). Example: if(a >= b) would succeed if a is greater than or equal to b
  •  
  • "and" operator (&&). Allows you to chain multiple boolean functions together. If both are true, then the value is true. Example: if(a != b && b != c) would succeed if a != b AND b != c
  •  
  • "or" operator (||). Similar to the above, except it returns true if ONE of the comparisons is true. Example: if(a != b || b != c) would succeed if a != b OR b != c
  •  
  • "not" operator (!). This one inverts the value. Example: if(!a) would succeed if a is false
  •  

if statement

Ah, the humble if statement. This cornerstone of programming is seen everywhere, including basic circuit boards and deep-level machine programming. Truly something remarkable.

qboolean doSomething;
if(doSomething) {
// we do it!
} else {
// if the above check failed, we do this instead.
}

These can be chained into more complex behavior:

qboolean doSomething;
qboolean doSomethingElse;
if(doSomething) {
if(doSomethingElse) {
// ...
} else {
// ...
}
} else {
// ...
}

If statements don't necessarily need the {} if the expression is one line:

qboolean doSomething;
int x;
if(doSomething)
x = 10; // no braces needed!
else {
x = 10;
x++; // we still need braces because there's multiple lines here
}

By abusing the above, we get "else if":

if(/* something */) {
// ...
} else if(/* something else */) {
// ...
} else {
// ...
}

else if is a nice way of shortening down some more logically complex code. The above would've looked like this:

if(/* something */) {
// ...
} else {
if(/* something else */) {
// ...
} else {
// ...
}
}

As you can see, with very long else/if chains this greatly simplifies the code and prevents headaches later down the line when optimizations need to be made. But suppose you're in a situation like this:

if(x == 0) {
// ...
} else if(x == 1) {
// ...
} else if(x == SOMEENUMERATION) {
// ...
} else if(x == 1320934) {
// ...
} else {
// ...
}

There's an even better way still...

 

 

switch Statement

Behold, the switch statement. This allows you to compare one variable to multiple values at once. Simply put, a switch compares a variable to multiple values and performs whatever is in its case block (or default, if there is no case block). What does this look like? Simple.

int x = 10;
switch(x) {
case 10:
// this is what gets run, because the value is 10
break;
case 20:
// this would only get run if the value is 20
break;
default:
// this would get run if the value isn't 20 or 10 but some other value
break;
}

There's a few points worth mentioning. First, this cannot be used for strings at all. Second, the cases can only be constants, so something like this is totally invalid:

switch(x) {
case y: // totally invalid
break;
case 10: // valid
break;
}

There's also another keyword here - the break keyword. This prevents what is known as "fall through" -

switch(x) {
case 10:
// if this were 10, this block would get run.
case 20:
// this would also get run since there is no break statement
}

Sometimes this may be intentional, but most of the time it isn't. Take care to include your break statements.

 

 

A note on floating point numbers and strings

Floating point numbers (such as those involved with the "float" and "double" types) have issues with comparisons. Namely, there is no floating point representation for "0" due to the way they are handled internally. Also, you might have issues with comparing other numbers. In order to avoid this, it's best to compare the values to a range using the greater than/less than (or equals to) operators:

float x = 0;
if(x == 0) { // this might not be true!!
}
if(x <= 0.001 && x >= -0.001) { // this is definitely true!!
}

The difference between the two values (in this case being 0.001) is known as an epsilon value. Of course, this need not apply to numbers like integers or character values. It's complicated math reasons that are responsible for this being the case. Read more here if you're interested.

 

Another thing- comparing strings with the == is actually valid code, but it won't produce expected effects. When I cover pointers, you will see why this is the case.

 

 

Loops

Sometimes, you may find yourself in a situation where code needs to be repeated several times, with or without minor modifications. In some cases a macro is needed, but more often than not, you'll need to use a loop. There are three different kinds of loops.

If a loop doesn't have a termination sequence, then you will encounter something known as an "infinite loop," where the program freezes (not crashes)

 

 

while loop

This is the most primitive form of a loop in existence. It performs actions while the condition is true. Here is a good example:

int x = 0;
while(x < 10) { // if x was 10, this wouldn't get run at all!
x++; // this gets run exactly 10 times
}

We can also use "true" (C++) or "qtrue" © or "1" to have a loop which can terminate on its own. Consider this loop. You'll note that there is the keyword "break" again here, but there is also a new keyword which doesn't appear which warrants discussion: continue.

Consider that a loop goes through a sequence multiple times. The "break" keyword effectively terminates the loop, while the "continue" keyword says "skip ahead, I don't care about what happens in the next chunk of code."

while(1) {
if(x == 10) {
break; // loop is finished
}
if(x == 11) {
continue; // go back to the beginning of the loop, but do not terminate.
}
}

do-while loops

A modification of the while loop is the do-while loop. At its core, these are no different than the while loop, other than that they are guaranteed to execute the contents of the loop at least once. Here is an example:

x = 10;
do {
x++; // this will happen
} while(x < 10);

x = 10;
while(x < 10) {
x++; // this won't
}

for loops

Consider our first example of a loop, back in the while loops section:

int x = 0;
while(x < 10) { // if x was 10, this wouldn't get run at all!
x++; // this gets run exactly 10 times
}

C and C++ have a nicer way of writing this as well! Behold the for loop:

int x;
for(x = 0; x < 10; x++) {
// this gets run exactly 10 times
}

The syntax of the for loop is quite simple:

for(/*precondition, what happens prior to the loop getting run*/ ; /*what is needed to make the loop break*/ ; /*what gets run on every cycle*/) { }

Any of the three sections can be skipped entirely. If needed, a for-loop can just look like this:

for(; {
}

Of course, that wouldn't be very useful! But you see what I mean.

Here is another example which demonstrates the idea of doing multiple things on each section:

for(x = 0, y = 10; x < 10 && y < 20; x++, y++) {
}

The comma operator here can be seen as simply "do this, but ignore the result", which is perfect in this context. We can also use it on a while loop to set something on each cycle of the loop:

while(x = 0, y < 10) {
// x will always be 0 when we start each cycle of the loop, and we will break if y is >= 10
}

C++ 103: Functions

Now that we've covered both variables and flow of control, it's time to combine both concepts into some workable code: functions. The game is broken up into five distinct parts: the engine, the game code (g_, game/, serverside, jampgamex86.dll, etc), cgame code (cg_, cgame/, clientside, cgamex86.dll, etc), ui code (ui_, ui/, uix86.dll, etc), and the renderer (r_, rd-vanilla, rd-rend2, etc). Each of these parts has an entry point:

  •  
  • Engine: WinMain, or main (depends on operating system)
  •  
  • Game code: G_InitGame (g_main.c)
  •  
  • Cgame: CG_Init (cg_main.c)
  •  
  • UI: UI_Init (ui_main.c)
  •  
  • Renderer: R_Init (tr_init.cpp)
  •  

Singleplayer note: In SP, the "Game code" and "cgame" code are moved into a single module, and the UI module is moved into the engine.

 

These entry points occur in what are called "functions" and they are where everything gets executed. Everything that happens, happens because it was called from a function. Functions call other functions, and in that, the code forms a delicate network of function calls, like a spider's web. But we need to understand the basic syntax of a function first:

<return type> <function name> (<arguments, if any>) {
}

void return type

I will start with the void function, which is the simplest type there is:

void SomeFunction() {
// do some stuff here
}

Simply put, void functions can be seen as nice little snippets of code we can use anywhere. We can invoke the above example by doing the following:

SomeFunction();

That's about as easy as it gets. Of course, there are parameters which can be fed into the function:

void SomeFunction(int someArgument) {
Com_Printf("We called SomeFunction with someArgument = %d\n", someArgument);
}

...which we later call by doing:

SomeFunction(10); // prints "We called SomeFunction with someArgument = 10" to the console
SomeFunction(20); // same as above but with 20
SomeFunction(30); // ...

Com_Printf is one of the functions that is used by the game. We will talk more about these functions in the CRT chapter.

 

 

return keyword

A function sometimes needs to be able to exit at any point in time. In order to do so, we invoke the "return" keyword:

void DoSomething(int someArgument) {
if(someArgument == 10) {
return; // The argument is 10 so we don't print the message
}
Com_Printf("someArgument is NOT ten!\n");
}

And now if we invoke DoSomething:

DoSomething(10); // doesn't print the message
DoSomething(20); // does

Non-void functions

Functions can also return a value. When this happens, the value which is brought back by the function can be passed into a variable. Here is a trivial example:

int MultiplyNumberByTwo(int x) {
return x * 2;
}

Note how we are using a variable type (int) instead of the word "void". This designates that we must return a value from the function. Here is an example of how we call it:

int x = 2;
x = MultiplyNumberByTwo(x); // value = 4
x = MultiplyNumberByTwo(x); // value = 8
x = MultiplyNumberByTwo(x); // value = 16

Note that void functions cannot return a value. Non-void functions on the other hand must return a value.

 

 

Static variables

Suppose we wanted to keep track of how many times a function is called. One way to do this would be to use a static variable. Let's add a usage counter to our MultiplyNumberByTwo function.

int MultiplyNumberByTwo(int num) {
static int usageCount = 1;
Com_Printf("MultiplyNumberByTwo has been called %d times\n", usageCount);
usageCount++;
return num * 2;
}

If we didn't make the variable static, it wouldn't keep its value after it was finished. This is because functions keep variables in a temporary block of memory known as the stack. Each time a function cleans up, the values in the stack are discarded and used again. But by declaring the variable as being static, we are telling the compiler to keep the variable out of the stack and in a secure section of memory where we can access it again later.

 

 

Calling functions from other files

As you might have figured out, there are two types of files: source files (.c or .cpp) and header files (.h, rarely .hpp). In C, you cannot define a function in a header file, you can only use this to declare them (like you would a variable. In C++ this restriction doesn't exist but it's still good to follow this). Source files can include header files in order to make those declarations available. You commonly see this at the beginning of a .c or .cpp file:

#include "g_local.h"

So if we have the function written in say g_main.c, and its signature (the function without the stuff in brackets) looks like this:

int MultiplyNumberByTwo(int number);

We would need to declare the function in g_local.h using its signature in order to make it be accessible by all files which include g_local.h. Simple stuff, if you think about it.

 

 

Static functions

Suppose we wanted a function to only be ever be used in one file. This is where the static keyword comes in again:

static int MultiplyNumber(int number) {
return number * 3;
}

Since the function is static, it is a unique function to that file and we shouldn't declare it in a header.

 

 

Another cheat code: extern

Suppose for some reason we didn't want to declare a function in g_local.h. In order to use it in another file, we could use the extern keyword to declare it before use. This is good if we don't want a function to be seen by every single thing that includes g_local.h. One example of where this is used quite prominently is in the SP AI code.

 

 

 

 

 

 

C++ 201: Pointers

As defined in a previous section, a pointer is a variable which "points to" a chunk of memory. Most often these are used for strings, but there are other uses as well.

 

 

Creating and Dereferencing Pointers

The asterisk (*) not only denotes multiplication, but it is also used to define pointers:

int* x; // a pointer to some integer variable

But exactly how do we manipulate pointers? Perhaps one of the most basic methods involves using the ampersand (&) which references (or creates a pointer to) a variable:

int x = 10;
int* y = &x; // this is now a pointer to the x variable

Now that we have a pointer, we can also retrieve the value of what we are pointing to by dereferencing it. Even more confusingly, this uses the asterisk again. Consider this chunk of code:

int x = 10;
int* y = &x;
if(*y == x) {
// This is always true. *y is 10 because we are retrieving the value of what we are pointing to.
}

Strings (and a nice little math hack)

Have you tried using == on a string yet and realized that it hasn't worked? This is because strings are usually pointers to character arrays in memory, and when you use the == operator, you're comparing the values of the pointers, not the values of the strings themselves. I will cover all of the string functions in depth in another tutorial/guide, but there are two key functions for comparing functions: Q_stricmp and Q_stricmpn.

const char* someString = "Something";
if(Q_stricmp(someString, "something") == 0) {
// Q_stricmp returns 0 if the two strings are equal. This is case insensitive. So this chunk of code would get executed.
}
if(Q_stricmpn(someString, "som", 3) == 0) {
// Q_stricmpn returns 0 if the two strings are equal, but compares only a certain number of characters as specified by the third argument. So this code would get executed.
}

So if that's the case, can we also do something like this?

const char* someString = "Something";
if(someString[0] == 'S') {
// Code Block
}

The answer is: yes. Even though someString is a pointer, it's pointing to an array of characters, specifically, the start of the array. Therefore, when you are using the indexing function (the brackets, []), you are really executing a math formula which looks something like this:

if(someString[x] == *(someString + x)) {
// always true
}

Remember that pointers are variables which point to chunks of memory. When you are manipulating the value of the pointer, you are changing what it is pointing to. Also remember, all strings end in a '\0' character!

 

 

Passing variables by reference and by value

Another practical application of pointers involves passing something by reference, instead of by value. Consider the following function:

void MultiplyValueByTwo(int x) {
x *= 2;
}

Despite calling it like this:

int x = 2;
MultiplyValueByTwo(x);

The variable x in this case doesn't change. Why? Because you're passing the value of x to the function, not the reference. Simply put, you're copying the value that x contains into what is actually passed into the function. If this sounds confusing at first, it probably is. But here's an example of something that passes by reference:

void DivideValueByTwo(int* x) {
*x /= 2;
}

...

int x = 20;
DivideValueByTwo(&x);

Since you are passing the value of the pointer to the function, you have free reign to modify the variable at your whim...as long as you dereference it! While this may be a trivial example, there are lots of applications where this might be useful.

 

 

Structs and Pointers

Structs have a nice little shortcut for dealing with pointers. While your first instinct may be to do something like this:

struct SomeStruct { int x; int y; };

void SetXToTwenty(SomeStruct* q) {
(*q).x = 20;
}

...an even nicer way to do this involves using the -> operator:

struct SomeStruct { int x; int y; };

void SetXToTwenty(SomeStruct* q) {
q->x = 20;
}

You can see this used almost everywhere in the serverside code. Some of the most glaring examples are in the cheat commands. It's almost always preferred to send a pointer to a structure instead of a copy to a function, even when it's not necessary, simply because you are copying a much smaller number of bytes (4 or 8, compared to however large the structure is...in singleplayer, gentity_t is somewhere in the realm of 1000 bytes!!) and therefore saving on performance.

 

 

...going deeper

So what happens if you need to pass a pointer by reference into a function? You go deeper, my friend.

void MultiplyValueByTwo(int** ppValue) {
(*(*ppValue)) *= 2;
}

This is a pointer-to-a-pointer, and you can make this as deep as you want to go:

int******************************************************************************************************* x; // totally valid but wtf

Function pointers

You can also make pointers to functions, although the syntax is a bit weird:

int MultiplyValueByTwo(int x) {
return x * 2;
}

// Defining the variable
int (*SomeFunctionPointer)(int x) = MultiplyValueByTwo;

// Calling the function
int y = SomeFunctionPointer(d);

There's relatively few uses for these, although perhaps you will find one someday. ;)


User Feedback

Recommended Comments

There are no comments to display.



Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×
×
  • Create New...