Today we will look at arrays and strings. The short description is that C requires you to be more explicit with how much memory you need for an array, but after that, pointers and array usage is the same.
NOTE: you need to now #include <stdlib.h> for malloc and calloc.
In C++, we've learned that memory is allocated with the keyword
new. For example, to create an array of 5 ints:
int* numbers = new int[5]; // C++ code
In C, we instead use the keywords malloc or
calloc. They're almost, but not exactly, the same. The prototype
for malloc is:
void* malloc(int);
The parameter of type int needs to be the number of
bytes that you want for your array from the operating system. So, when
requesting space for an array of 5 ints, for example, to arrive at the correct
number of bytes to request, you need to know not only the 5, but also the
number of bytes required by a single int. Most systems represent an int with 4
bytes, but not always, and embedded systems can be different from platforms.
To know how many bytes your type needs, use the sizeof() function.
For example:
malloc( 5*sizeof(int) );
calls sizeof(int), which returns the number of bytes needed to
store a single int. This is then multiplied by 5 (because you need room for 5
of them), the result of which is finally the argument to
malloc.
Because malloc just accepts a number,
it has no knowledge of the type you're going to store within that space, so it
doesn't know whether to return a int* or a char* or
what. So, it just returns a void*, which can be cast to any of
those types. So, for our array of 5 ints, we finally end up at:
int* intArr = malloc( 5*sizeof(int) );
Say we instead want 12 doubles, then it looks like this:
double* doubles = malloc( 12*sizeof(double) );
You can view this as a simple syntax change, but there is a little more
going on. In C++, the new keyword figures out how many bytes you
need based on the primitive type. In C, you have to compute that yourself in
the code (with the handy sizeof() function). Here is the side-by-side
difference:
| C++ | C |
|
|
Our other option for memory allocation is calloc, which makes
this "I need to know two things, the size of the type and the number of
elements I'm story" thing explicit. In calloc, our
"array of 5 ints" and "array of 12 doubles" looks like
this:
int* intArr = calloc( 5, sizeof(int) );
double* doubles = calloc( 12, sizeof(double) );
You'll notice that it takes two arguments, while malloc just requires you to
multiply them together yourself. The other significant difference is that
unlike new or malloc, calloc not
only allocates the memory, it also zeroes it out(!!)! No random
garbage in your arrays! But, it's somewhat slower, since it has to iterate over
all the memory and zero it out.
The choice between malloc
and calloc is largely a personal preference between the two, as
long as you're knowledgeable about these differences.
When using
malloc or calloc, be sure to #include <stdlib.h>, or you'll get a warning
about the return type of your allocation function.
Remember that scanf requires a pointer to know which memory address will be filled. For example, reading an int:
int N;
scanf("%d", &N);
Nothing changes with arrays, but it might not yet be intuitive to you, so let's
be explicit. An array cell is an lvalue which makes it no different from a
standard primitive type. If you want to read an array of 10 ints from standard
input, it looks like you'd expect:
int* nums = malloc( 10*sizeof(int) );
for( int i = 0; i < 10; i++ )
scanf("%d", &nums[i]); // we just need the & to get the address of the array cell
Remember to delete your memory allocation when you're finished with your arrays!
The C equivalent of delete is the function free:
free(intArr);
free(doubles);
Remember how we lost our great string type from C++?
It's not so bad. Strings in C are instead explicit arrays of chars. There is a small catch, though:
So, if you were to encode the word "hi" as a C string, you would need an array of size three:
char* hiStr = malloc( 3*sizeof(char) );
hiStr[0] = 'h';
hiStr[1] = 'i';
hiStr[2] = 0; //alternatively, the character '\0'
If you a static array:
char hiStr[] = "hi";
The array will have size 3 -- the null char is still there, just unseen in the code.
Of course, you'll also see:
char aString[20];
The above code builds an array of 20 chars in the stack, pointed to by a char* aString. Again, you can technically build
arrays in the stack like this for any type but it's exceedingly common with
strings, which tend to be short, and so people put them in the stack, so they
don't have to free them.
Once you've allocated space for some chars, your pointer can be used with
scanf to read in strings from the user:
char aString[20];
scanf("%s",aString);
or
char* aString = malloc( 20*sizeof(char) );
scanf("%s",aString);
// free(aString) later
Finally, the above is all about creating and reading strings. But what about
helpful functions? Make sure you #include
<string.h>.
strcpy function.
The prototype is as follows:
char* strcpy(char* s_to, const char* s_from);This function copies string
s_from to s_to. For
conveniece, the function returns s_to as well.
Sample code:
// #include <string.h>
char s1[128];
strcpy(s1, "hello world");
print("%s\n", s1);
strcmp function.
The prototype is as follows:
int strcmp(const char* a, const char* b);This function compares strings
a and b.
Sample code:
// #include <string.h>
int n1 = strcmp("alice", "bob");
int n2 = strcmp("bob", "bob");
int n3 = strcmp("bob", "alice");
print("%d %d %d\n", n1, n2, n3);
strlen function.
The prototype is as follows:
size_t strlen(const char* s);This function returns a length of string
s. Here,
size_t is a type for non-negative integers.
$ ./avg
How many? 5
5 3 9 2 3
3 2 9 3 5
$ ./fileread
Average is 34.850000
Max is 94
$ ./name
Alan Turing
TURING, ALAN
See a program solution.