Learning the C Programming Language as a Classical Musician [14]

Episode 14 — Types (Part 6: Practical Examples)

Welcome back!

In the last episode, we studied floating-point numbers and how they can be used in C, with particular interest into how the compiler stores their precision. Much has evolved since the introduction of the C Programming Language in the 1970s in the way computers work, but the basic principle stays the same: infinitely precise numbers are not possible, so we need to do with the amount of space the compiler gives to us.

Today, we will sail away from arithmetic types, and delve deep into enumerated types, which are a way to group together several related values under the same banner.

Let’s get started.

Enumerated types

I like to start with the definition of enumeration found in the Swift Programming Language, version 5.5, which is much clearer than the one found in the C documentation:

An enumeration defines a common type for a group of related values and enables you to work with those values in a type-safe way within your code.

All the rest does not concern C, as enums (how they are usually called) evolved quite a bit between C, Objective-C, and Swift. The concept, though, remains: they are a container and, as we saw in Episode 8, they share the tag namespace with structures and unions. The most common example I saw across all the tutorials I have followed is the deck of cards, in which you create an enumeration called Cards and give it the four seeds, Diamonds, Hearts, Clubs, Spades. Thus, enumerations are fixed in size, and immutable, as their purpose is not to be changed afterwards, but to be used as they are.

Syntax

An enumeration is declared with the enum tag at the start of the line, followed by its identifier, its name, an open brace, a comma-separated list of enumerators, and, finally, a close brace. Between the tag and the identifier, it is being discussed that an attribute-specific sequence be available for insertion since C23, and I will have great care of delving through all major changes when this new version comes out later next year (2023). The modifications applied by this sequence will involve the whole enumeration if they appear after the enum tag, or to the single enumerator if they appear after the enumerator-constant.

One interesting thing is that the identifier is optional so that we could theoretically have an enumeration without a name (not that I would ever suggest you to do so). The enumerators need to have a specific form, to which they should comply: an enumeration constant, which is just the name of the case of the enumeration, by tradition written in uppercase letters, optionally followed by an equal sign = and an integer constant expression (a whole number). This is because, under the hood, enumerators are storing integer values. This will be removed in Swift (even if you can still access such a functionality there), but in C and Objective-C they work like arrays, and start counting at 0. So, if we have the following code:

enum notes { C, D, E, F, G, A, B }; 
// enum is the tag
// notes is the identifier, with no attribute-specific sequence before
// inside the braces, the comma-separated list of enumerators

In Swift, the syntax will add the case keyword, which in C we only see used for switch statements, but here we have an enumeration called notes, which groups together the seven independently-named notes of the Western musical system. To make everyone happy and included, we could have a German, a French, and an Italian notes list:

enum germanNotes { C, D, E, F, G, A, H }; // yes, B = H in the German system, while B is B-flat
enum frenchNotes { UT, RE, MI, FA, SOL, LA, SI }; // ut = C, and is a remnant of a very long time ago
enum italianNotes { DO, RE, MI, FA, SOL, LA, SI };

If you try to copy this code snippet into your Xcode project and paste it under the previous notes example, you will see quite a few errors:

What is happening here? Simple: as enumerations share the same namespace and the same scope, we cannot declare the same enumerators as belonging to different enumerations. This is something that in more modern languages has been fixed, but here it is interesting to see how you cannot have two different enumerations containing the same enumerators.

One thing that I find confusing in C is how you are allowed to declare multiple objects on the same line, assuming they belong to the same type, or type family. It takes a while to get used to this syntax because it was also common for C programmers to use the shortest identifiers possible, which required a good deal of abstraction before getting comfortable. This is also to say that, in good C code, you will probably never find identifiers such as those I used above, rather something like grNt, frNt, itNt, which makes perfectly sense as long as we know beforehand what we are talking about.

The documentation states that it is possible to introduce, on the same line, an enumerated type with one or more enumeration constants (enumerators), and one or more objects of that same type or derived from it. Here is an example:

enum color { RED, GREEN, BLUE } c = RED, *cp = &c;
// introduces the type enum color
// the integer constants RED, GREEN, BLUE
// the object c of type enum color
// the object cp of type pointer to enum color

Notice how the documentation states that RED, GREEN, and BLUE are integer constants because they are actually the numbers 0, 1, and 2 hidden behind colour words. The last object *cp = &c is a complex beast, with the * “pointer dereference” operator that accesses the object cp to store the value of c using the & “address of” operator. We will dedicate at least one episode to pointers alone, as they are something we rarely see in more modern languages, but that is so important to understanding how programming works.

Explanation

We just saw how each enumerator that appears in the body of an enumeration specifier becomes an integer constant with type int; it has the same scope as the enumeration itself and can be used wherever we can use integer constants. If we want to check what key a specific piece is in, we could write something like this:

enum ks { C_major, A_minor, G_major, E_minor, F_major, D_minor} requiem = D_minor;

Here I declared a ks (key signature) enumeration containing a few keys (I am sure you can write the other 24 keys on your own), and declared an object of type ks called requiem and gave it the value D_minor, in obvious reference to Mozart’s Requiem. At this point, I could use a switch statement to print out something interesting:

switch (requiem) {
    case C_major:
        printf("This piece is in C major\n");
        break;
    case A_minor:
        printf("This piece is in A minor\n");
        break;
    case G_major:
        printf("This piece is in G major\n");
        break;
    case E_minor:
        printf("This piece is in E minor\n");
        break;
    case F_major:
        printf("This piece is in F major\n");
        break;
    case D_minor:
        printf("This piece is in D minor\n");
        break;
    default:
        break;
}

This will print:

This piece is in D minor

In this case, C_major has an underlying integer value of 0, A_minor has a value of 1, and so on, up to D_minor which stores the value of 5. Nevertheless, we could decide to give specific values to each enumerator, by adding an equal sign = after its name and an integer, such as this:

enum en { A, B, C=4, D, E=1, F, G=F+C };
// A=0, B=1, C=4, D=5, E=1, F=2, G=6

What is happening here? A and B are given default values of 0 and 1; to C we give 4, at which point, D keeps counting and is given the value of 5; E is given the value of 1 by us, which sets F to be equal to 2. Finally, we can perform operations between enumerators so that case G has a value equal to the sum of F and C. Brilliant!

Something that may struct you as redundant is that when we are using an enumeration, or a structure, or a union, and we are creating a new object of that type, we need to repeat both the tag and the identifier before creating the new object, something that luckily will go away in Swift. This is because of the requirements of the tags namespace we looked at previously. Thus, if we create an enumeration for string instruments:

enum strIns { VIOLIN, VIOLA, CELLO, BASS };

And then we want to store the value of one of these cases into an object, we need to repeat enum then strIns, followed by our identifier, an equal sign, and the value we want to store:

enum strIns myIns = CELLO;

Xcode is very polite and changes the colour of strIns to let me know that it understood it is a type reference:

If we had just written strIns myIns = CELLO; we would have gotten this error:

Luckily for us, Xcode would have proposed an automatic fix and, for once, the error explanation is clear. You may ask why I am always celebrating when Xcode throws an understandable error. It is simply because that is not the norm, and especially in the latest framework introduced, SwiftUI, that is a true mess.

A workaround for this would be to use the typedef keyword, which is a way to give our name to existing types. So, this would be allowed:

typedef enum strIns Strings;
Strings yourIns = VIOLIN;

What is happening here is that we are saying to the compiler to define the enumeration type strIns with the name Strings. At that point, we have an object type which is detached from the tag namespace, and we can create it as if we had created int num = 4;, just with Strings in the place of int.

To iterate one last time, enumerated types are integer types, and as such can be used anywhere other integer types can, such as when they are converted into different types or when used in arithmetic operations. For example:

enum { ONE = 1, TWO } e;
long n = ONE; // promotion
double d = ONE; // conversion
e = 1.2; // conversion, e is now ONE
e = e + 1; // e is now TWO
    
printf("n is equal to %ld; d is equal to %f, e is assigned 1.2 but is actually %u in the end\n", n, d, e);

Promotion refers to the fact that we have assigned an unsigned integer to a long, which is a wider type (check the lesson on integer types, in Episode 12). Then we convert it to a double, which gives it a value of 1.000000. Trying to assign 1.2 to e is not really working as intended, as it is actually converting the floating-point value back into an unsigned integer. Then we perform an addition and the final result is 2.

Final considerations

Differently from what we will see in the next two episodes on structures and unions, we do not need to declare enumerations before using them. For example:

enum Genres;

Should throw an “Error: no forward-declarations for enums in C”, but strangely, Xcode is not complaining. This might be implementation defined, and other compilers may behave differently.

One last thing: enumerations do not establish their own scope in C (this is why we could not declare the same notes twice in the main function), and thus they can be introduced in the member specification of structures and unions, which we will look at in a future episode. Their scope will coincide with the structure or union’s one. This is a nice example:

struct Element {
    int z;
    enum State { SOLID, LIQUID, GAS, PLASMA } state;
} oxygen = { 8, GAS };
    // type enum State and its enumeration constants stay visible here

Here we create a structure called Element, with two members, an int called z, and an enum of type State with four enumerators, identified as state. After the closing brace we immediately create an instance of this structure called oxygen, give z the value of 8 and state the value of GAS. In Xcode, we must be careful on how we place this code. I placed it before the main function and then used it inside, to make the structure have a file scope.

What’s next?

In the next episode we will look at arrays, a most important type which will make us laugh and cry, exult and despair! I cannot wait to tell you all about them, so stay tuned!

Bottom Line

Thank you for reading today’s article.

If you have any question or suggestion, please leave a comment below or contact me using the dedicated contact form. Assuming you do not already do so, please subscribe to my newsletter on Gumroad, to receive exclusive discounts and free products.

I hope you found this article helpful, if you did, please like it and share it with your friends and peers. Don’t forget to follow me on this blog and to let me know what you think.

If you are interested in my music engraving services and publications don’t forget to visit my Facebook page and the pages where I publish my scores (Gumroad, SheetMusicPlus, ScoreExchange and on Apple Books).

You can also support me by buying Paul Hudson’s Swift programming books from this Affiliate Link or BigMountainStudio’s books from this Affiliate Link.

Thank you so much for reading!

Until the next one, this is Michele, the Music Designer.

Published by Michele Galvagno

Professional Musical Scores Designer and Engraver Graduated Classical Musician (cello) and Teacher Tech Enthusiast and Apprentice iOS / macOS Developer Grafico di Partiture Musicali Professionista Musicista classico diplomato (violoncello) ed insegnante Appassionato di tecnologia ed apprendista Sviluppatore iOS / macOS

One thought on “Learning the C Programming Language as a Classical Musician [14]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: