A structure in C provides a way to group multiple pieces of data together. They are like classes in Java and C++, except they can only contain data, and everything is public.
Below is a structure representing a 2D point:
struct Point {
int x;
int y;
};
We could then create a Point object, and assign its data members like so:
struct Point origin;
origin.x = 0;
origin.y = 0;
Note that the "struct" keyword must also be given when declaring structures, unlike in C++ or Java classes!
When declaring a structure, you can also initialize its values:
struct Point origin = {0, 0};
This only works for initialization. You can't use this syntax to update the fields of a structure.
Structures can also be passed into functions as follows:
void point_print(struct Point p) {
printf("(%d, %d)\n", p.x, p.y);
}
Below is a complete program using this structure:
#include <stdio.h>
struct Point {
int x;
int y;
};
void point_print(struct Point p) {
printf("(%d, %d)\n", p.x, p.y);
}
int main() {
struct Point origin = {0, 0};
point_print(origin);
return 0;
}
Because structures can be large, they are frequently passed as pointers. We could rewrite the point_print function to take a pointer like this:
#include <stdio.h>
struct Point {
int x;
int y;
};
void point_print(struct Point* p) {
printf("(%d, %d)\n", (*p).x, (*p).y);
}
int main() {
struct Point origin = {0, 0};
point_print(&origin);
return 0;
}
Because the . operator in C has higher precedence than the * operator, we would need to use (*p).x to read the fields. *p.x would not work.
Because this syntax is so ugly, for such a common operation, C has an alternative syntax which is the -> operator. This can be seen in this example:
#include <stdio.h>
struct Point {
int x;
int y;
};
void point_print(struct Point* p) {
printf("(%d, %d)\n", p->x, p->y);
}
int main() {
struct Point origin = {0, 0};
point_print(&origin);
return 0;
}
pointer->field means to dereference pointer and read field from the structure at that address.
Some programmers do not like having to use the "struct" keyword whenever declaring a structure. To get around this, we can use the typedef keyword in C.
A typedef statement contains a type and also an alias. For instance, we can create an alias for an character pointer called string:
typedef char* string;
After that typedef, "string" can be used as an alias for "char*".
When done with a struct this looks like this:
typedef struct Point Point;
The declaration of the struct and the typedef can also be combined into one declaration:
typedef struct {
int x;
int y;
} Point;
After doing this, we could just declare a Point as:
Point origin;
In our C programs, we have included header files such as stdio.h or string.h. These files contain declarations of structures and functions that we can call.
We can create our own header files as well. Larger programs in C are composed of multiple .c source files and .h header files. Declarations are placed in header files which are then included into the source files which use them. For example, the program above could be split into a header file and two source files:
The point.h header file could declare a struct and a few functions which operate on it:
// point.h
struct Point {
int x;
int y;
};
void point_print(struct Point* p);
The point.c file could provide the implementations of those functions:
// point.c
#include <stdio.h>
#include "point.h"
void point_print(struct Point* p) {
printf("(%d, %d)\n", p->x, p->y);
}
Notice that this file includes the header. It uses double quotes on the header name instead of angled brackets. Quotes are used for files in the same directory as the source, and angled brackets are used for system headers.
The main.c file could then have a main function which calls those functions:
// main.c
#include "point.h"
int main() {
struct Point origin = {0, 0};
point_print(&origin);
return 0;
}
Just like other languages, C programs are normally split into multiple files. To compile multiple files with gcc, pass it the names of all of the source files:
gcc *.c
The files can be seen here.
C programs can take command line arguments which is the text given after the name of the program when you run it (if any). They are passed as arguments to main.
The first argument is an integer giving the number of arguments which were passed. The second is an array of character strings giving the actual arguments. Because arrays are passed as pointers, and strings themselves are arrays, this is actually a pointer to a pointer.
This program prints all of its arguments:
#include <stdio.h>
int main(int argc, char** argv) {
int i;
for (i = 0; i < argc; i++) {
printf("Argument %d is '%s'\n", i, argv[i]);
}
return 0;
}
An example run is given below:
[finlayson@cs ~]$ a.out hello there Argument 0 is 'a.out' Argument 1 is 'hello' Argument 2 is 'there'
Note that the name of the program itself is the first argument!
Arrays of strings, and 2D arrays are instances where we will use pointers to pointers.
Copyright © 2024 Ian Finlayson | Licensed under a Creative Commons BY-NC-SA 4.0 License.