So far, we’ve looked at single data type variables. This just means, all our variables are used for a single purpose. We want to store a single integer number? Use an int. For a sentence? Array of characters, etc.

What about a Complex Number?

A complex number in math, is a number of the form: a + ib, where i is the square root of -1.

A complex number, z. Such that: z = a + ib contains a real part and an imaginary part.

We express these by writing:

Re(z) = a

and

Im(z) = b

Hence, to store a complex number we need a variable that contains 2 integers, the real part and the imaginary part.

To accomplish this task, we can create our own type.

This is called a “Structure”

Let’s build a simple example of a structure that will represent complex numbers.

We declare our structure like so:

struct Complex{ int real; int imag; };

Note that, I’m just using the integer type to represent my real and imaginary parts for the purpose of this example, however, feel free to make them float types or doubles if you want to have complex numbers such as: 2.3 + i3.9

Here’s an example of a structure that represents complex numbers.

To use it in our code we can do the following:

struct Complex{ int real; int imag; }; int main() { struct Complex z1; struct Complex z2; return 0; }

Like so, we create variables of type “struct Complex”

To modify the internal fields, the real and imag integers we use the dot operator. “.”.

struct Complex{ int real; int imag; }; int main() { struct Complex z1; z1.real = 2; z1.img = 5; return 0; }

Note, sometimes you don’t want to keep writing “struct Complex” every time you want to declare a new complex number.

To get around that, we can use something called tyepdef in C.

You can do the following in your code:

struct Complex{ int real; int imag; }; typedef struct Complex <new name>;

Where you have the brackets you can write whatver you want to call it.

Note that there are some obvious exeptions. You can’t typdef something to EXISTING keywords.

for example:

typedef struct Complex int;

This would result in an error as int already exists in C.

The best option in this case would just be to stick with Complex.

typedef struct Complex Complex;

Okay, now let’s create complex numbers.

struct Complex{ int real; int imag; }; typedef struct Complex Complex; int main() { Complex z1; Complex z2; z1.real = 3; return 0; }

**Structures and Functions**

In C, we can’t use normal arithmetic operators on structs.

Say we declare 2 Complex numbers and want to assign their sum to a third complex number.

The way to add 2 complex numbers z1 and z2 would be the following:

z1 = a + ib

z2 = c + id

z3 = a+c + i(b+d)

The real part of z3, the sum, is the sum of the real parts of z1 and z2, likewise the imaginary part of z3 is the sum of the imaginary parts of z1 and z2.

In C, if we write the following code:

struct Complex{ int real; int imag; }; typedef struct Complex Complex; int main() { Complex z1; Complex z2; Complex z3; z1.real = 2; z1.imag = 1; z2.real = 4; z2.imag = 5; z3 = z1 + z2; return 0; }

This will result in an error, since the C compiler doesn’t know how to add these 2 objects.

We need to write our own function that accepts 2 complex numbers as arguments and returns their sum.

We will first write a straightforward and basic version of this, and than later improve it.

__Version 1__

Complex Add_Complex(Complex a, complex b) { Complex c; c.real = a.real + b.real; c.imag = a.imag + b.imag; return c; }

Okay, so here is the most primitive way to write our addition function for complex numbers. It accepts 2 arguments, inside it we declare a new complex number, we assign its real part to the sum of the real parts of its arguments. We do the same for the imaginary part. Finally we return our new object.

This code works fine, however let’s take a look at what’s happening in memory to get a better idea of why we can make this better.

First let’s take a look at C code that actually uses this function to add 2 complex numbers.

struct Complex{ int real; int imag; }; typedef struct Complex Complex; Complex Add_Complex(Complex a, complex b) { Complex c; c.real = a.real + b.real; c.imag = a.imag + b.imag; return c; } int main() { Complex z1; Complex z2; Complex z3; z1.real = 2; z1.imag = 1; z2.real = 4; z2.imag = 5; z3 = Add_Complex(z1,z2); printf("%d +i %dn", z3.real, z3.imag); return 0; }

Let’s see how this will look in memory.

As you can see, in main we first create 3 Complex numbers.

Now, we pass 2 of these by VALUE to our add function.

Inside the add function, we create another Complex number, do the adding, and then make a copy of it and return it to Main.

One way to make this code better is to avoid making multiple copies of our Complex objects. Why don’t we pass them by pointers instead of by value.

Before we look at the code for passing the structures by pointer, we need to look at a new operator in C.

The arrow:

**->**

Recall that when we have a structure, we access it’s internal components with the dot operator like so:

Complex x; x.real = 2;

Now, let’s access our struct with a pointer.

Complex x; Complex* ptr = &x;

Now, we have a pointer of type Complex* (complex pointer) this is pointing to a complex object.

If we want to access the internal members of the object through the pointer we have to first dereference the pointer, than use the dot operator, like so:

Complex x; Complex* ptr = &x; (*ptr).real = 2;

However, this code is messy and fairly error prone. Thankfully, C gives us a much cleaner way to do this.

When accessing the structure members through a pointer we can use the arrow operator. ->

We write the following.

Complex x; Complex* ptr = &x; ptr->real = 2;

And thus, no need to dereference. Just write the pointer name, followed by the arrow, ->, followed by the name of the struct member you wish to access.

Okay, getting back to our example, let’s write a proper function that takes 2 complex numbers, adds them, and returns their sum.

Complex* Add(const Complex* A, const Complex* B) { Complex* C = (Complex*)malloc(sizeof(Complex)); C->real = A->real + B->real; C->imag = A->imag + B->imag; return C; }

And thus, we can see that here this new add function only creates a single Complex object on the heap and passes a pointer to it back to Main.

Let’s see how it’s used with our Main function.

#include <stdio.h> #include <stdlib.h> struct Complex{ int real; int imag; }; typedef struct Complex Complex; Complex* Add(const Complex* x, const Complex* y) { Complex* ptr = (Complex*)malloc(sizeof(Complex)); c->real = x->real + y->real; c->imag = x->imag + y->imag; return c; } int main() { Complex a; a.real = 2; a.imag = 1; Complex b; b.real = 4; b.imag = 5; Complex* c = Add(&a, &b); printf("%d +i %dn", C->real, C->imag); free(c); return 0; }

As you can see, we are passing the addresses of the objects into our add function, and returning a pointer to somewhere in the heap.

At the end of our program, we free the memory allocated on the heap.

Inside the add function we allocate memory the size of a single Complex object.

Also, we write the keyword “const” in the parameters to indicate that we are not modifying the values of the arguments. This just prevents accidently changing their values in the function, and is considered good style.

Let’s have a look at the memory diagrams to get a better idea.

Okay, first we have 2 objects declared in Main, and one pointer to a Complex object.

Next, we pass the addresses of these objects to our add function.

Inside the add function, we use malloc to dynamically allocate space for a Complex object on the heap, and assign its members the sum of the members of the arguments.

Inside add function returns the pointer to our object and goes out of scope.

Finally, the last thing we do is free the dynamically allocated memory.

Pingback: Object Oriented Programming | Harry's