2.4.1. Records¶
In our previous code we worked with functions and quadratic equations. A quadratic equation was represented by three floating point values a, b and c. Let’s write a function that returns the value of a quadratic function at a given point x:
float function_value(float a, float b, float c, float x)
{
return a * (x * x) + b * x + c;
}
Seems straightforward enough. Now, let’s imagine we need to find out the difference between two quadratic functions at a given point x. Let’s write this function:
float difference_between_functions(float a1, float b1, float c1, float a2, float b2, float c2, float x)
{
float val1 = function_value(a1, b1, c1, x);
float val2 = function_value(a2, b2, c2, x);
return val2 - val1;
}
This seems a bit unwieldy: The function takes seven parameters in total, and it’s easy to make a mistake somewhere. Surely we can do better?
In C (and in fact most programming languages) it’s possible to combine values in records or composite data types. See below:
struct quadratic_function {
float a;
float b;
float c;
};
If we wanted to declare such a struct and define its values, we can do simply:
struct quadratic_function f;
f.a = 1.0f;
f.b = 2.0f;
f.c = 3.0f;
…and use f.a, f.b and f.c as we want. (The above code would need to reside within a function as mandated by C.)
We can now also redefine the function that calculates the value at a given point:
float function_value(const struct quadratic_function *f, float x)
{
return f->a * (x * x) + f->b * x + f->c;
}
We’ve made a few changes:
- The parameters a, b and c are now replaced by (or contained in) f, a struct
- f is passed in as a const pointer, not a value
About passing a variable to a function as a pointer: By default, in C, whenever a variable is passed to a function it’s passed by value which means a copy of the variable is made for the function (allocated in the stack), and the copy is discarded when the function returns. It often makes sense to pass a pointer to a function instead, i.e. by reference, because then the data the function has access to is the original data.
In this case, it makes very little difference except that it saves an unnecessary copy of a few bytes.
About the keyword const: this means we treat the variable f as a constant within the function, and if we wanted to modify the data pointed to by f, i.e. its fields a, b or c, the compiler would issue an error. This is helpful for us as the function implementors to ensure we don’t modify data we don’t want to modify, and helpful to the caller of the function to know that the function shouldn’t modify the input data.
Because f is a pointer, the syntax for accessing the fields of the struct is different: the operator -> is used instead of dot (.).
Now, defining the function difference_between_functions can be much clearer:
float difference_between_functions(const struct quadratic_function *f1, const struct quadratic_function *f2, float x)
{
float val1 = function_value(f1, x);
float val2 = function_value(f2, x);
return val2 - val1;
}
Exercise: The derivative of a quadratic function ax2+ bx + c is 2ax + b. Write a function that calculates the derivative for a quadratic function at a given point x and call it, with the input data passed as a pointer to a struct.
2.4.1.1. What is a struct¶
Our example struct consists of three integers. As C is relatively close to the actual hardware, we can reason about what this struct looks like in practice. In general, the amount of memory used by an integer in C is implementation defined but for the purpose of this section we can assume it’s 4 bytes (32 bits). Defining a structure like this typically means the data will be packed well, such that 12 bytes will be required for one allocation of struct my_datatype and the layout will only contain the memory required for a, b and c, nothing more. (Mixing different data types of different sizes may cause padding memory to be added by the C compiler, depending on the hardware constraints.)
You can find out what the size of a data type is in C by using the “sizeof” operator:
printf("%lu\n", sizeof(struct quadratic_function));
In this sense, defining structures in C mostly serves to combine various data into one unit, simplifying code and aiding in having the necessary memory available and allocated.