Chapter 4: Advanced Data Types and Functions
What we've gone over in the preceding chapters may not have been super exciting, but we're well on our way to having the skills to be a game programmer. We've looked at how programs are organized, simple variables, operators and a few other instructions, and branching and looping. In this chapter we’re going to go to a more advanced level. We'll make our code more powerful by going over some more complex, advanced data types: strings, arrays, lists, and structs. Then we'll look at ways to modularize code by creating functions to put blocks of code in.
l Learn about strings and how they work in C#
l Learn how to use arrays and collections
l Learn how to use structs
l The basics of functions
Strings
We discussed strings in the second chapter, but since they are so important we'll talk a bit about them again here. One of the most basic things to deal with in any programming language are strings. Strings, if you recall, are just lists of characters. This sentence is a string, even this entire book is a string, just a whole bunch of characters. Since we are all about manipulating data in computer programs and data is usually in the form of strings, they are very important. Pictures, such as bitmaps, are stored as strings, just long sequence of characters containing numbers for each color of every pixel. 3D Models likewise are just long strings that contain various kinds of information about the model. Strings are a major data structure in programming.
Strings have always been important in programming, but before .Net working with strings could get a bit too complex. Back in the days of C all of the basic string functionality was about just simple blocks of memory and some basic functions for manipulating them. C++ brought better strings that could do more things and a distinct string class was created. But these strings still weren't enough for a lot of programmers, so different people started making different string classes and functions of their own. Converting and keeping track of all these different strings wasn't simple and unnecessarily confusing. Instead of all that trouble in the .Net framework we're given a single robust and good String class to work (which is C#'s string variable type.) The .Net string class has a lot of functionality, and there are many helper functions to turn strings to something else (such as numbers) and back. This single string class is one of the major components of C# and .Net.
To declare a string we just use the string keyword:
string stringName;
Where stringName is the name we want to call that string. Remember actual strings are put in double quotes:
string helloString = “Hello”;
Like we said, strings in C# are just another way of using the String class in .Net. The nice thing about this is that if we ever need to program in a different .Net language, such as Visual Basic.NET or Managed C++, the strings will work just the same. If you do a search for Strings in the Visual C# Express documentation you'll find that there are many things you can do with strings. We'll look at just a few of the most commonly used ones.
Concatenation
One of the most basic operations we can perform on strings is to combine two strings into one. The most common way to do this is if we have two strings to just put the second string at the end of the first one. This attaching of strings one at the end of the other is called concatenation. Here is an example:
static void Main(string[] args)
{
string name;
Console.WriteLine("Hello, what's your name?");
name = Console.ReadLine();
string outString = "Hello, " + name + ", nice to meet you.";
Console.WriteLine(outString);
}
A run of the program looks like this:
Hello, what's your name?
Curtis
Hello, Curtis, nice to meet you.
The first part of this program declares a string called name and then reads the user's name into it. The third line of the program:
string outString = "Hello, " + name + ", nice to meet you.";
concatenates the strings “Hello”, name, and “nice to meet you” and stores the new string in the variable outString. The plus symbol (+) is used as the concatenation symbol. And like the unary addition shortcut we can do the following to concatenate:
string helloString = “Hello”;
helloString += “, nice to meet you”;
// helloString now has “Hello, nice to meet you”
Concatenation is our way of combining strings.
Comparing
Another simple thing to do with strings is to compare if two are equal. Strings can be compared in conditional tests using the == sign and != (not equals) sign similar to numbers. Here's an example:
static void Main(string[] args)
{
string name= "Charlie";
if (name == "Charlie")
Console.WriteLine("The name here is Charlie");
if (name != "Mike")
Console.WriteLine("The name is not Mike");
}
The output for the above is:
The name here is Charlie
The name is not Mike
This test is case sensitive, so the following test will be false:
string name = “Charlie”;
if( name == “charlie” )
// This test is false
To ignore case sensitivity we can use the .ToUpper() or .ToLower() methods on the strings. These methods convert a string to all upper case or lower case. The following test will be true:
string name == “Charlie”;
if( name.ToLower() == “charlie”.ToLower() )
// This test is true
Converting
Like we mentioned earlier in the book, it's easy to convert strings to and from other data types. Let's look first at converting numbers to strings.
int goodNumber = 5;
string outString = “I thing a good number is “ + goodNumber.ToString();
This program converts the goodNumber integer to a string. This isn't anything special for integers, all variables and data structures in C# can be converted to a string this way. Anything at all in C# and the .Net framwork that you want converted into a string just put the .ToString() after it. Going the other way (which we saw at the end of chapter two)
int goodNumber;
Console.WriteLine("Enter a good number");
string numberString;
numberString = Console.ReadLine();
goodNumber = Convert.ToInt32(numberString);
Console.WriteLine(goodNumber.ToString() + " is a good number.");
Here’s an output:
Enter a good number
25
25 is a good number
If we run this program it will just ask the user for a number and then print it back out. The first few lines here just have the user enter an integer and store it in the string numberString. The line:
goodNumber = Convert.ToInt32(numberString);
changes our string into a an integer. As you type this line in notice that after typing the period at the end of Convert a whole list of options comes up of things we could try to change the string into, such as ToBoolean() (to convert to a bool) or ToSingle() (which is used to convert to a float). The reason the names of the types in the Convert don't match up exactly in name to our C# data types is that the Convert is part of the .Net framework, not C# specifically, so the names for the data types are the .Net names, not the C# names.
Now trying to run the program again instead of entering a number enter the string “hello”. You'll get the following:
Enter a good number
hello
Unhandled Exception: System.FormatException: Input string was not in a correct format.
at System.Number.StringToNumber(String str, NumberStyles options, NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)at System.Number.ParseInt32(String s, NumberStyles style, NumberFormatInfo info)at StringTest.Program.Main(String[] args)
Entering a string that won't convert to an integer creates an exception, an error that popups up when running the program that basically says something went wrong. Exceptions are called runtime errors, since they occur when the program is running. These are different than compile time errors, errors that occur from compiling the program. We won’t worry about handling exceptions here, for now just be careful about converting strings.
Concatenation, comparing, and converting are three important things we can do with strings. Again there are a lot more things you can do with them (you can check the documentation) but we'll move on to our next advanced data type, arrays.
Arrays
Let's say that we're making a space ship game (which we'll do in the next part) and in it we have several ships and we need to keep track of the speed of each one. We could try creating float variables for each ship's speed:
float ship1Speed;
float ship2Speed;
...
float ship58Speed;
But this would quickly get annoying, and would take up a lot of code space having them all typed in. It would be nice if we could create a single variable that holds multiple values. That is what an array is, an array is a variable that holds a list of values of the same type. We mark that a variable is an array by using straight brackets, [] and the individual values stored in an array are called the elements or the members of the array. To create an array to hold all of out ship's speeds we would just say:
float[] shipSpeed;
This says to make a variable shipSpeed that holds multiple values of floats in a list. Arrays are a little bit trickier than usually variables. So let's look at a few important aspects of them.
Initializing arrays
There are a few ways we can set what's in an array initially and its size (how many members it has.) The first way is to declare a list of elements to put in it when we declare the array. To create a shipSpeed array with that holds five values, 100 to 500, we would just say:
float[] shipSpeed = { 100.0f, 200.0f, 300.0f, 400.0f, 500.0f }
But what if we didn't know what values we wanted to store in the array? Instead of listing them out we can just give a number of variables to store, a size of the array:
float[] shipSpeed = new float[5];
The above line creates an array with spots to hold five floats. The new is used because an array is what is called a reference type. The precise meaning of this is a little technical. It basically says that the variable itself (shipSpeed) doesn't store all the floats in memory itself, it just contains an address to a memory location, a reference to some spot in memory. So when we use a reference type we put the new keyword in front of it to create the location in memory for it. (If that explanation doesn't make sense don't worry about it, for now just think that for reference types (like an array) we sometimes have to use new when defining the variable.)
Accessing members
We just saw how to create the array, but how do we access and modify the individual members of the array? We simply in the brackets following the array put the number of the element we want.
float[] shipSpeed = { 100.0f, 200.0f, 300.0f, 400.0f, 500.0f }
float speed1 = shipSpeed[0]; // We just put 100.0f in speed1
shipSpeed[1] = 25.0f; // Now the value of the second element is 25.0f instead // of 200.0f;
One thing to notice here is that to access the first element instead of using 1 we started with 0. This is because arrays use zero based indexing. As we mentioned before this means that when we count things we call the first thing 0, the second thing 1, and so on. So if we had, say, four elements, we would count them 0,1,2,3. This can be a bit unusual at first when working with arrays to think like if we want to change the third element we refer to it as number 2, but after a while it becomes natural.
What if we try to access a member of an array and specify a number too but or small for it, like the following:
float[] shipSpeed = { 100.0f, 200.0f, 300.0f, 400.0f, 500.0f };
float speed1 = shipSpeed[12];
In this program we try to access the thirteenth element (number 12) which doesn't exist. If you run it you'll get another exception. So you have to be careful when accessing array to make sure you are in range. To help with this you can call the .Length function on an array to see how many are in it:
float[] shipSpeed = { 100.0f, 200.0f, 300.0f, 400.0f, 500.0f };
int size = shipSpeed.Length; // size is the number 5
Iterating an Array
Sometimes when working with arrays we want to do something to every element in the array. If we go through every element in an array and do something to it (such as assign them a value) we are iterating through the array. To do this we can use a for loop. Let's assign the numbers 0, 100, 200 … to ten ship speed elements:
for( int i = 0; i < shipSpeed.Length; i++ )
shipSpeed[i] = i * 100;
Besides using the regular for loop C# also has a special kind of for loop, a foreach loop. The foreach is just a simple way to go through each element of an array as long as we don't modify it. Say we want to print each ship’s speed. We would just code the following:
foreach( float speed in shipSpeed)
Console.WriteLine(speed.ToString());
The syntax here is pretty simple to figure out, we just specify the type and the array to iterate through, then we can use every element.
Multi-dimensional arrays
Besides having arrays that hold a single row, list, of data we can make rectangular arrays; arrays that hold multiple rows of data. The following will create three rows with each row having two floats in it:
float[,] rectangleArray = new float[3,2];
// Assign an element:
rectangleArray[1, 0] = 5.0f;
This creates a rectangular array, but we can also make jagged arrays. That is an array where each element is itself an array. This way the multidimensional array doesn't need to be rectangular, each array can be of a different size (that's why it's called jagged.) Here's an example:
float[][] jaggedArray = new float[3][];
// Give row zero an array of 2 elements
jaggedArray[0] = new float[2];
// Give row one an array of 3 elements
jaggedArray[1] = new float[3];
// Assignment:
jaggedArray[0][1] = 5.0f;
Collections
Arrays are the most common way in computer languages to handle lists of elements. But in C# we other methods of doing this called Collections. One of the most common collections is called a list. It is like an array except we don't have to worry about setting a specific size to it, we just add and remove elements to it as we see fit. Here is an example that makes a list of numbers:
List<int> integerList = new List<int>(); // Make a list of int type
integerList.Add(5); // Add the number 5 to the list
integerList.Add(6); // Add the number 6 to the list
int firstInt = integerList[0]; // firstInt now has the value 5
We'll being lists more in the 2D part. The syntax for them is like the arrays. The big difference is that for lists we add items as we go. We can see where this will be useful when we keep a list of items that we add too periodically through the game. In the next part in the 2D game as we fly our ship and we need to create new enemies we will create them and add them to an enemy ship list.
Structs
Moving on from arrays let's say that in our ship game we wanted to store more information about a ship than just its speed. We also want to store a ship id which is an integer, and a ship type which is a string. We could create three separate variables for each ship and just note that they are all describing the same object, but it would be easier if we had a structure that did this for us. C# does give us just that, a struct, which holds multiple variables together. A struct for the ship variables just described would be:
public struct ship
{
public int id;
public float speed;
public string type;
}
The basic syntax is just
public struct structName
{
public variableType1 variableName1;
public variableType2 variableName2;
...
}
We put the name of the struct first and then in the brackets after it we put a list of the variables in it (we put public in front of each variable to make it accessible, we'll go into more detail about this in the next chapter.) To use the struct we declare a variable of our struct type, and to access individual members (variables in it) we use a period to specify it. All of this is easiest seen with an example:
public struct ship
{
public int id;
public float speed;
public string type;
}
static void Main(string[] args)
{
ship myShip; // Just created a ship structure
myShip.id = 33; // Give it an id of 33
myShip.speed = 2200.0f; // Give it a speed of 2200
myShip.type = "class 1 ship"; // Give it a type
Console.WriteLine( "My ship's type is " + myShip.type.ToString() );
Console.WriteLine( "Its speed is " + myShip.speed.ToString() + " and its id is " + myShip.id.ToString() );
}
The output is:
My ship's type is class 1 ship
Its speed is 2200 and its id is 33
This program uses the ship struct to create a variable of the ship type (myShip) and then assigns each of its variables a value. You might notice that this grouping of variables together to describe a specific object (in this case the ship) sounds like Object-Oriented programming. Structs are the beginning of Object-Oriented programming; while they are in structured languages like C, are like objects. The difference is that structs usually contain just variables while classes (objects) contain variables and functions (actions to act on the data)
Example Program
Strings, arrays, and structs are three advanced data types, and while we went over each of them, let's make a simple example program that uses all three together. The program, called shipList, will store an array of ship structs and the user will enter info in on each ship and then it will print out a summary of everything that was entered. Here's the code:
public struct ship
{
public int id;
public float speed;
public string type;
}
static void Main(string[] args)
{
// Write out what this program is
Console.WriteLine("Ship inventory program.");
// Get the total number of ships to record
Console.Write("Enter how many ships do you want to record: ");
int totalShips = Convert.ToInt32(Console.ReadLine());
// Create a ship array
ship[] shipArray = new ship[totalShips];
// Go through each ship and get some info on it
for (int i = 0; i < shipArray.Length; i++)
{
Console.Write("Enter id of ship : ");
shipArray[i].id = Convert.ToInt32(Console.ReadLine());
Console.Write("Enter speed of ship : ");
shipArray[i].speed = Convert.ToSingle(Console.ReadLine());
Console.Write("Enter type of ship : ");
shipArray[i].type = Console.ReadLine();
}
// Now print out the info on each ship
Console.WriteLine("Ship info: \n");
foreach (ship shipElement in shipArray)
{
string printString = "Ship id: " + shipElement.id.ToString() + "\n";
printString += " type is " + shipElement.type + "\n";
printString += " speed is " + shipElement.speed + "\n\n";
Console.WriteLine(printString);
}
}
An example run of this program would be:
Ship inventory program.
Enter how many ships do you want to record: 2
Enter id of ship : 33
Enter speed of ship : 2000
Enter type of ship : Class I
Enter id of ship : 45
Enter speed of ship : 2200
Enter type of ship : Class II
Ship info:
Ship id: 33
type is Class I
speed is 2000
Ship id: 45
type is Class II
speed is 2200
The comments describe what's going on in each part of the code. A couple of points: The array itself is an array of the ship type. Notice that the user chooses the size of the array (when entering the totalShips data.) Like we said the size of the array can be determined at run-time (when the program is running) instead of compile-time (when the program is being compiled.) We also use the escape key “\n” to create new lines when we need them. Whenever we use Console.WriteLine we can create new lines by inserting \n.
Functions
What we can do in our programs is getting more advanced, let's keep moving and go over how functions work in C#. Functions, if you recall from chapter 1, are blocks of code set aside that can be called to run when needed. The concept is pretty simple, when writing code one of our goals should be never to duplicate code, that is we don't want to write the same block of code twice in the same program. There are a lot of reasons for this, not duplicating code saves space, reduces errors (since it keeps the total amount of code down), and is easier to make changes (when we need to change code we only have to change it at one spot.) So we never want to duplicate code, and functions are a way to do that.
Let's look at a simple example of a function and then go through it line by line:
class Program
{
static void printHello()
{
Console.WriteLine("Hello everybody!");
}
static void Main(string[] args)
{
printHello();
Console.WriteLine("I said,");
printHello();
}
}
The output of this is
Hello everybody!
I said,
Hello everybody!
The first part of this program defines the function:
static void printHello()
{
Console.WriteLine("Hello everybody!");
}
The name of the function is printHello, and we can tell it is a function because it has the parenthesis () following it. The static void in front of printHello, are descriptions of the function. We won't worry about what the static means for now, we'll deal with it in the next chapter. The void tells us a return-type, and we'll cover that briefly.
Inside of Main, which is a function itself, we tell the printHello function to run, we do this by using the name of the function as an instruction. Each
printHello();
Tells the compiler to go to the printHello function and do all of the computer code in it. In our program every time we say printHello() the text “Hello everybody!” is printed on the console, because the code direction goes to the printHello function.
Passing Variables
Putting code into little blocks of functions is good, but what about variables. If we put a variable in our Main function and then call another function the other function won't have access to the variable. For example, if you try running the following code you'll get an error message:
class Program
{
static void printNumber()
{
Console.WriteLine("Printing number: " + number.ToString()); // Error – number isn't here
}
static void Main(string[] args)
{
int number = 5;
printNumber();
}
}
The error message says “number does not exist in the current context” referring to the printNumber function. Number is in Main, not printNumber, so printNumber can't use it. One solution to this is to make number a global variable, which means that every function in the whole program can access it. To make the number variable global we put it outside of our functions:
class Program
{
static int number;
static void printNumber()
{
Console.WriteLine("Printing number: " + number.ToString());
}
static void Main(string[] args)
{
number = 5;
printNumber();
}
}
(The static in front of the number can be ignored) This program will compile and run just fine, but we don't want to make variables global if we don't have to. Instead of doing this what we can do is pass the value of number to printNumber. We can pass the number to it like this:
class Program
{
static void printNumber( int numberToPrint )
{
Console.WriteLine("Printing number: " + numberToPrint.ToString());
}
static void Main(string[] args)
{
int number = 5;
printNumber(number);
}
}
The output:
Printing number: 5
Press any key to continue . . .
In this program we took the number variable and passed it into the function. Our function knew what variables to take because we made a parameter list for it. The format for this is:
void functionName( type varName, typevarName, ... )
We can specify as many variables as we like to pass. Note that we're passing the value of the variable, not the variable itself. For instance the following program:
class Program
{
static void printNumber( int numberToPrint )
{
numberToPrint = 7;
Console.WriteLine("Printing number: " + numberToPrint.ToString());
}
static void Main(string[] args)
{
int number = 5;
printNumber(number);
Console.WriteLine("Printing number: " + number.ToString());
}
}
Will have the output:
Printing number: 7
Printing number: 5
Changing the number to 7 in the function didn't affect the number in Main.
Returning a value
If we want to let a function change a variable we can pass it in by reference. To do this we add the ref keyword in front of the variable in the function parameter list.
class Program
{
static void printNumber( ref int numberToPrint )
{
numberToPrint = 7;
Console.WriteLine("Printing number: " +
numberToPrint.ToString());
}
static void Main(string[] args)
{
int number = 5;
printNumber(ref number);
Console.WriteLine("Printing number: " + number.ToString());
}
}
This program has the output:
Printing number: 7
Printing number: 7
Since we put a ref in front of the variable when passing it the actual variable place in memory was passed, and the number itself changed.
So if we want to have the function modify some data for use in other parts of the program we can pass it by ref. Besides passing variables by ref a better way to get data from a function is to have the function return a value. The same way we can pass data into a function we can also have a function pass it out. We do this by specifying a return type; the void in front of the functions we've written so far is actually a way saying we're not returning anything. The void means nothing. We can replace the void by a data type. For instance:
class Program
{
static int printNumber( int numberToPrint )
{
numberToPrint = 7;
Console.WriteLine("Printing number: " + numberToPrint.ToString());
return numberToPrint;
}
static void Main(string[] args)
{
int number = 5;
number = printNumber(number);
Console.WriteLine("Printing number: " + number.ToString());
}
}
This program has the output:
Printing number: 7
Printing number: 7
Our function printNumber has a return type of int:
static int printNumber( int numberToPrint )
The function has in it the keyword return, which says to stop the function and return that value:
return numberToPrint;
Anything after the return is ignored by the compiler. If you change the printNumber function to:
static int printNumber( int numberToPrint )
{
numberToPrint = 7;
Console.WriteLine("Printing number: " + numberToPrint.ToString());
return numberToPrint;
Console.WriteLine(“I'll never print”);
}
That last WriteLine will never show up on the screen.
Summary
So we've got the basics of how functions work and before that we looked at a few more advanced data types: strings, arrays, and structs. Stucts are a precursor to objects in Object Oriented programming, so let's move on to the next chapter where we start making objects in our program.
