struct
We have already seen most of C’s basic data types. However, we will
frequently want to store more complicated data that cannot be
represented by one of these basic types. For our first example, let’s
say we want to store complex numbers. A complex number is made up of two
real-valued components: the real part and the complex part. We could
store complex numbers as arrays of type float
or
double
, but we can actually do better than that. C provides
a concept called a struct
or structure
that allows the programmer to make their own types out of the basic
types provided. To define a complex number, we could put this at the top
of our file (outside of any function definitions):
struct complex {
double real;
double imaginary;
};
This defines a new type: struct complex
that represents
a complex number. A structure definition starts with the word
struct
followed by a name, then a series of variable
declarations surrounded by braces, and finally a semicolon. Each of the
variables inside a structure is called a member or a
field. In order to access members of a structure, the
.
operator is used. So if you have a variable
z
of type struct complex
, you can access the
real component of z
with z.real
.
Let’s say we want to do complex arithmetic. We couldn’t multiply or
add these types like we could with double
because the
computer doesn’t know what the data represents or what multiplication
and addition would even look like. However, if we wanted to make complex
arithmetic easier, we could write some functions to do it for us:
/* Computes x + y */
struct complex complex_add(struct complex x, struct complex y)
{
struct complex z;
c.real = x.real + y.real;
z.imaginary = x.imaginary + y.imaginary;
return z;
}
/* Computes x - y */
struct complex complex_subtract(struct complex x, struct complex y)
{
struct complex z;
c.real = x.real - y.real;
z.imaginary = x.imaginary - y.imaginary;
return z;
}
/* Computes x * y */
struct complex complex_multiply(struct complex x, struct complex y)
{
struct complex z;
c.real = x.real * y.real - x.imaginary * y.imaginary;
z.imaginary = x.real * y.imaginary + x.imaginary * y.real;
return z;
}
If we wanted to multiply two complex numbers, we could just write
c = complex_multiply(a, b);
. When you use the assignment
(=
) operator on a structure, it simply assigns each of the
members of the structure the value of the corresponding member of the
expression. So x = y;
is equivalent to
x.real = y.real;
x.complex = y.complex;
Let’s go for a more complicate structure and try to store a date and time. We could do that in one structure, but because it might be useful to just use the date or just use the time, we will do it in three:
struct date {
unsigned char day;
unsigned char month;
unsigned int year;
};
struct time {
unsigned char second;
unsigned char minute;
unsigned char hour;
};
struct date_time {
struct date date;
struct time time;
};
Then we could write functions to work with these date and time
structures just like we did for struct complex
. For
instance, we could add two times:
struct time time_add(struct time t1, struct time t2)
{
struct time t3;
t3.second = t1.second + t2.second;
t3.minute = t1.minute + t2.minute;
t3.hour = t1.hour + t2.hour;
/* Normalize seconds and minutes to [0, 60) */
t3.minute += t3.second / 60;
t3.second = t3.second % 60;
t3.hour += t3.minute / 60;
t3.minute = t3.minute % 60;
return t3;
}
We could also write functions to convert a struct time
to a number of seconds or compute the number of days since January 1st
for a given struct date
. The exact functions to write
depend on what you want to do with our new-found structure. One very
useful function would be one that prints it to the terminal:
void time_print(FILE *stream, struct time time)
{
char *month_string;
switch(time.date.month) {
case 1:
fprintf(stream, "January");
break;
case 2:
fprintf(stream, "February");
break;
case 3:
fprintf(stream, "March");
break;
case 4:
fprintf(stream, "April");
break;
case 5:
fprintf(stream, "May");
break;
case 6:
fprintf(stream, "June");
break;
case 7:
fprintf(stream, "July");
break;
case 8:
fprintf(stream, "August");
break;
case 9:
fprintf(stream, "September");
break;
case 10:
fprintf(stream, "October");
break;
case 11:
fprintf(stream, "November");
break;
case 12:
fprintf(stream, "December");
break;
default:
fprintf(stream, "Unknown Month");
}
printf(" %hhd, $hhd at %hhd:%02hhd:%02hhd",
time.date.day, time.date.year,
time.time.hour, time.time.minute, time.time.second);
}