Module 2 - CP - CS100 - Notes - KtuQbank
Module 2 - CP - CS100 - Notes - KtuQbank
COM
Module II
Arrays and strings- example programs. Two dimensional arrays - matrix operations.
Structure, union and enumerated data type.
a[0]= 3;
a[1]= 5;
b[0]= 2.1;
b[1]= 2.4e6;
c[0]= 'H';
c[1]= 'i';
printf ("a[0] is %d, b[1] is %f, c[1] is %c\n",a[0], b[1], c[1]);
We must specify the size of the array before we start – so we can't have code which works out what
size an array should be and then sets it up to be that size. (Yes, this can be annoying).
IMPORTANT RULE: int a[100]; initialises an array of 100 integers. They can be referred
to by the array index inside the square brackets. For example a[n] refers to the nth element of the
array. You can use an array element with an index anywhere inside a function where you could use
a normal integer.
CAUTION: It inevitably confuses new C programmers that, in C, array offsets start from 0. If we
declare an array of ints as int a[100]; then its first element is a[0] and its last element is
a[99] attempts to access element a[100] are an error.
IMPORTANT RULE: The values of array elements can be changed within a function. [This is
because an array is really a special type of pointer – but don't worry about that for now].
Note also that you can't have a function which returns an array. Since a function can change array
values you will never need to do this anyway.
KTUQBANK.COM
We can also initialise arrays like so:
[But note that you can't set arrays all in one like that in the middle of a function – so it is an error to
write.]
int a[5];
a= {1,5,10,15,20}; /*This line should not compile */
Our table of potential prime numbers is surely best stored as an array. We might also consider what
we want to store. We could store the number in the appropriate place in the array – but this is not
really necessary – after all, we only really need to know if the number at a particular place in the
array should be crossed out or not.
Because of this, we could make it an array of char to save memory or an array of int to keep
things simple (char takes up less memory than int) – we've gone for the int solution this time
since we're not using that much memory here – if we wanted a larger sieve then we might be forced
to reconsider this. We'll also take some advice from the former section about magic numbers and
create some enum statements to make things clearer. So, we might then have a starting bit of code
which looks like this:
int main()
{
int i;
int sieve[MAX_NUM + 1]; /* Array to hold sieve */
Note that the array has a size of MAX_NUM + 1. Remember that an array of n elements in C has
elements numbered from 0 to n-1. We could decide to use an array of MAX_NUM elements with
the table entry for 1 stored in 0 and 2 stored in 1 – but this would get awfully confusing awfully
quickly. Instead of this, we've elected to include element 0 in our table – but cross it out as a special
case – as we have with element 1.
One early task when you're looking at any algorithm is to consider what loops to use. In this case,
the most obvious loop is the one between steps 2 and 4 which changes the value of k. In all cases it
is arbitrary whether a for or a while loop is chosen – the two are always interchangeable:
KTUQBANK.COM
initialiser;
while (condition) {
code;
increment;
}
Generally speaking, if your for statement looks too "busy" then try rewriting it as a while loop
which might make it clearer. In this case, we can put it into a relatively tidy for loop like so:
Another obvious loop is in the cross_k function. Again a for loop seems the best solution:
int i;
/* Cross out all multiples of k but the first */
for (i= k*2; i < MAX_NUM + 1; i+= k)
sieve[i]= CROSSED;
Note that i is starting at twice k – obviously we don’t want to cross out k itself.
Strings in C
Arrays of type char are known as strings in C. Actually, without knowing it, you've already seen
an array of char. When we typed:
the phrase Hello world! was really an array of characters. We could equally well have
written:
or even:
%s in a printf string is similar to %d but instead of an integer fits in a string to the print statement.
Note that as with arrays, we can only initialise a string with this type of construct. It is illegal to
write:
char hi_there[100];
hi_there= "Hello World!\n"; /* This line should not compile */
KTUQBANK.COM
We can write to a string using the sprintf function in the same way that we can write to the
screen using printf:
char answer[100];
int ans= 42;
sprintf (answer,"The answer is %d\n",ans);
IMPORTANT RULE: Strings in C should be NULL terminated. That is to say, the last element of
the character array must be '\0' or the char which has value 0.
When we write:
a[0]= 'H';
a[1]= 'e';
a[2]= 'l';
.
.
.
a[10]= 'd';
a[11]= '!';
a[12]= '\n';
a[13]= '\0';
The reason for having the '\0' convention is so that we know when to stop printing. If we have an
array of 100 characters the first few of which are Hello World! if there were no '\0'
character then the print statement would simply continue to print until it came to a '\0' needless
to say, most of what it would print would be rubbish. Fortunately, the C language most of the time
takes care of the '\0' for you. However, if you accidentally overwrite it then you can be in
trouble. For example if we later alter the string:
Usually you will find this could print something like this:
Hello world
Hello world!a$?!#
d1%
I.e. it prints the second Hello world correctly but then continues to print a lot of random nonsense
KTUQBANK.COM
after it. This is because the print statement will simply continue to print until it finds the '\0'
even if this is a long way after where you thought the string should have ended. [Sometimes you
get lucky and by coincidence the next character is '\0' – code which relies on you being lucky is
NOT good code].
If we write a function which takes a char [] variable as an argument then we can also send it
strings in double quotes. So, for example if we write:
The string.h library contains many useful functions for strings which we will look at in the next
worksheet. Some of the more common ones are:
It is also worth pointing out that if we initialise a string then we do not have to give it a length, it
will automatically take the minimum length to hold the data given:
char test[]="1234567890\n";
Is the same as if we’d declared the string to be 12 characters (10 numbers, one for the return and
one for the NULL termination).
array[1][4]= -2;
if (array[2][1] > 0) {
KTUQBANK.COM
printf ("Element [2][1] is %d", array[2][1]);
}
Remember that, like ordinary arrays, multi-dimensional arrays are numbered from 0. Therefore, the
array above has elements from array[0][0] to array[2][5].
CAUTION: A common beginner mistake is to attempt to access array elements using the syntax
array[1,4]= -2;
When we pass multi-dimensional arrays to functions or use a prototye, we must include the size of
the array in the prototype. E.g.
CAUTION: It's easy to become confused here. The above function body defines a function which
takes as an argument a 3 by 6 array of int. However, if we call it with:
process_array (array[3][6]);
Then we will cause a problem as this will not pass the array – this will attempt to pass the element
[3][6] of the array – which is out of range anyway if the array is [3][6].
Multi-dimensional arrays are actually quite rare in C – an array of pointers is more common and
more useful.
It is worth mentioning that char [3][10]; declares 3 strings 10 characters long not 10 strings
3 characters long.
Matrix addition
The matrix addition is done as follows :
KTUQBANK.COM
The code segment for matrix addition is as follows:
for(i=0;i<3;i++)
{
for(j=0;j<3;j++)
{
ADD[i][j]=A[i][j]+B[i][j];
}
}
Matrix Multiplication
Matrix Transpose
You might be wondering about that peculiar FILE thing that seems to be usable like an int or a
float in functions. FILE is an example of an important part of the C language known as a
structure. A structure is a built up part of the C language which behaves like a built in type. We can
declare a group of associated variables which are associated as a structure.
IMPORTANT RULE: A struct statement declares a structure which can hold information of
existing types (or indeed other structures). A struct should be declared at the top of the code or
in a header file.
For example, if we are writing a program for a bank, we might decide that an account is a
fundamental data type in such a program. Therefore we declare a structure which deals with each
account:
struct account {
char acct_holder[80]; /* Name of holder */
char address[100]; /* Address of holder */
double balance; /* Balance in pounds */
int acct_type; /* type of account 1= savings 2= current
*/
};
We can declare variables to have this type. And we can access elements using the dot notation
shown below:
We can use any of these variables within a struct wherever we could use a variable of the same
type. So, for example, we could add this:
float interest;
float rate= 1.75;
interest= new_acct.balance * rate / 100.0;
printf ("Adding interest of %f\n",interest);
new_acct.balance+= interest;
We can make our struct look even more like a built in type such as int or float by using a
typedef statement. For example:
we can then use the new type IMAG_NUM pretty much wherever we can use an int. For
example:
KTUQBANK.COM
IMAG_NUM x,y;
double a= 2.0;
x.real_part= 3.0;
x.imag_part= a;
IMAG_NUM points[2];
points[0].real_part= 3.0;
points[0].imag_part= 1.0;
points[1].real_part= -3.5;
points[1].imag_part= -2.0;
You can also use typedef to create another name for a built in type. For example you could
write:
IMPORTANT RULE: We can build structs up from other structs. For example, we might
want to define a rectangle in the imaginary plane by defining two of its corners. We could do so as
follows (assuming the previous definition of IMAG_NUM has already been defined earlier in the
program):
IMAG_RECT rect1;
rect1.corner1.imag_part= 3.1;
rect1.corner1.real_part= 1.2;
rect1.corner2.imag_part= -2.3;
rect2.corner2.real_part= 1.4;
You can also use typedef to create another name for a built in type. For example you could
write:
This would allow you to use Length wherever you could have used double. E.g.
Length room_size=10.4;
Length room_width=12.3;
This isn't particularly useful, however. The only common use for this is in defining things which
are a pain in the neck to type. For example, if your program uses a lot of unsigned char
values (recall that such a variable can store a number from 0 to 255) then you might want to:
Arrays of structures
Remember from previous lectures and worksheets that structures extend the C language by, for
example, providing imaginary numbers:
IMAG_NUM points[2];
points[0].real_part= 3.0;
points[0].imag_part= 1.0;
points[1].real_part= -3.5;
points[1].imag_part= -2.0;
We can build structs up from other structs. For example, we might want to define a rectangle
in the imaginary plane by defining two of its corners. We could do so as follows (assuming the
previous definition of IMAG_NUM has already been defined earlier in the program):
IMAG_RECT rect1;
rect1.corner1.imag_part= 3.1;
rect1.corner1.real_part= 1.2;
rect1.corner2.imag_part= -2.3;
rect2.corner2.real_part= 1.4;
Pointers to struct
MATHEMATICIAN fermat;
fermat.year_of_birth= 1601; /* This margin is too small to
contain*/
fermat.year_of_death= 1665; /* more information about
Fermat*/
strcpy (fermat.name,"Pierre de Fermat");
strcpy (fermat.nationality, "French");
If, instead of using a structure, we had used a pointer to a structure, it would work like this:
MATHEMATICIAN *cantor;
cantor= (MATHEMATICIAN *)malloc (sizeof (MATHEMATICIAN));
(*cantor).year_of_birth= 1845;
(*cantor).year_of_death= 1918;
/* Remember to close comments with a diagonal slash */
strcpy ((*cantor).name= "Georg Cantor");
strcpy ((*cantor).nationality= "German");
.
free(cantor);
All that (*cantor). looks rather confusing. Recall that a * in front of a pointer means
dereference or "the value of whatever it is that my pointer is pointing to". Therefore, to get at the
year of birth of Cantor we must first dereference his pointer with the * to get at the stuff inside the
pointer and then use the dot to get at that part of the struct.
[*cantor.year_of_birth without the brackets would be incorrect. This would be the same
as *(cantor.year_of_birth) and would be used to refer to a pointer to int called
year_of_birth which would be part of the struct called cantor – as opposed to the int
called year_of_birth which actually is part of the pointer to struct called cantor. This is
a subtle but important distinction, the upshot of which is that, without the brackets, your program
will not compile because the compiler can't find a pointer to int in the struct].
IMPORTANT RULE: If we have a pointer to a structure, we can access elements within the
structure using the -> operator. For example: fptr->name would give us access to the element
name of the structure pointed to by the pointer fptr.
Pointers to struct are starting to look a bit complex – so why would anyone use them. There are
two obvious reasons (there is a third, even more compelling reason which we will discuss later):
1) We might want to alter the contents of the structure within a function – recall then that we must
use pass by reference and write our function to accept a pointer argument. For example:
MATHEMATICIAN alan;
setup_turing (&alan);
2) If our structure is large then it is extremely inefficient to use pass by value. For example, if we
have a huge structure:
then we might want to write a function which does something with one of these structs:
This may seem to be entirely reasonable – and indeed it will work but there's a catch. Because
we're not passing a pointer here we are passing by value. Remember that when we pass by value,
we pass a local copy of the variable which is used locally (and destroyed when the function is over).
In this case, our program will be making a copy of all those 10000 element arrays. It passes that
copy over to the function – which doesn't do a whole lot with it. That's a whole lot of work – and
just to print out a couple of floats. In this case it would be much better to pass the struct by
reference – i.e pass a pointer: When we pass a pointer, the only thing that is sent to the function is a
single pointer – much more efficient than sending a copy of the whole structure:
This variable hair_data does not contain both wears_wig and color components. Rather it has
either wears_wig component referenced by hair_data. When memory is allocated for hair_data, the
amount of memory is determined by the largest component of the union.
In most cases, it is useful to interpret a chunk of memory in more than one way only if it is
possible to determine which way is currently the valid interpretation. For this reason, unions are
most often used as types of portions of a larger structure, and the larger structure typically contains
a component whose value indicates which interpretation of the union is correct at the present time.
KTUQBANK.COM
An enumeration is a user-defined data type that consists of integral constants. Enums are lists of
constants . When you need a predefined list of values which do not represent some kind of numeric
or textual data, you should use an enum. To define an enumeration, keyword enum is used.
Declaration
When you create an enumerated type, only blueprint for the variable is created. Here's how you can
create variables of enum type.
enum boolean { false, true };
enum boolean check;
int main()
{
enum week today;
today = wednesday;
printf("Day %d",today+1);
return 0;
}
Output
Day 4