Episode 6
Welcome back!
Last episode was a quite tough one, in that we delved deep into what happens when you compile your code.
Today we will review two fundamental topics, identifiers and scope, adding details to what already introduced in Episode 3, and delve into the concept of lifetime, which is crucial in object-oriented programming.
Let’s get started.
Identifiers
In Episode 3 we looked at declarations as being, basically, one line of code, or any code construct ending with the semicolon ;
. For the program, and the reader, to know what this declaration is about, one needs to identify it. This is logical: when you get to the airport’s passport control you need to be identified through your passport or your ID card; when you find a musical score in the library, there are codes in its classification that let you identify it. An identifier could be thought of as a distinctive feature that helps one to understand what is in front of them.
In C, identifiers must be in a specific form, that is, an arbitrarily long sequence of characters (digits, underscores, lowercase/uppercase Latin letters, and Unicode characters preceded by \u
or \U
). It has a few further limitations: it cannot begin with a digit, so you cannot have int 0-itself = 0
, rather you need int zeroItself = 0
. Moreover, identifiers are case-sensitive, which means that char *symphony
is different from char *Symphony
.
Unicode is a fascinating topic, and so deep that I could not even dare to begin exploring it; therefore I will just make a simple example with emojis. Emojis are on-screen renditions of Unicode sequences, and each of them is different. For example, if I want to store the word “violin” and want to give it a creative declarator, we can do the following:
char *\U+1F3BB = "violin";
// or, also valid
char *🎻 = "violin";
During compilation, the IDE will throw an error saying that we are defining the same thing twice, which makes us happy because it means the emoji and its code are correct. In Unicode classification the +
between U
and 1F3BB
is a shorthand for 000
and, for example, Xcode doesn’t like it. Substitute +
with 000
so that the code becomes \U0001F3BB
and the error will go away.
Identifiers can represent several types of entities, such as:
- Objects: they are regions of data storage, and are basically anything that can be classified as a variable or constant, such as
char *myInstrument = "violoncello"
, orconst int thisYear = 2022
. - Functions: these are blocks of code which will be executed all together when calling their name. Such name is an identifier, and here is an example:
int sum(int x, int y);
which defines a function that accepts two integer parameters and returns an integer. - Tags: these are specific words that denote certain types (again, shortcuts for deeper meanings). For example, structures in C are created using the tag
struct
, and they are a type made of a sequence of objects. For example, I could create a structure for a string instrument, and give it some properties such asstrings
,timbre
,size
, etc. … Then, I would create an instance of that to define the cello, my instrument.
struct stringInstrument {
int strings;
char * timbre;
int size;
};
struct stringInstrument cello = {
.strings = 4,
.timbre = "Sweet and deep",
.size = 4/4
};
- Other types, which are far too complex to cover now. I will list them just for completeness:
typedef
names,labels
,macro
names, andmacro parameter
names.
There are some identifiers that you cannot use in your code, but while it would be nice to meet and know all of them, your IDE will be more than happy to throw you an error should you step into dangerous territory.
Scope
In Episode 3 we introduced the concept of scope as that of a context in which what we are using is accessible. For example, if you sit at the piano but do not open the lid, you cannot expect to reach any key, no matter how hard you look for it.
There are several kinds of scopes, which we will look at now: block scope, file scope, function scope, andfunction prototype scope.
Block scope
A code contained within curly braces { }
is considered a block. Anything you create inside them is inaccessible to objects outside. A block within a block, instead, has access to the code in the super-block. When you perform a piece on your instrument and see the dynamic “forte” written on the score, you apply it to the notes above and after it, until a new dynamic instance negates it. This means that notes outside that region have no access to that dynamic and cannot use it.
In the example below, we create a function prototype that returns nothing and accept a parameter declared as n
of type integer.
void f(int n) {
n++;
for (int n = 0; n < 10; n++) {
printf("%d\n", n);
}
}
What this function does is taking the argument passed during the function call, increase it by 1 (that’s what the n++
syntax does), then create a loop, with a new integer variable called once more n
, asking to print the value of n
for ten times (from 0 to 9), and increasing the value of n
by one after each loop. Let’s analyse each line of code:
void f(int n) {
The scope of the function parameter n
begins, and the body of the function begins after the curly braces.
n++;
n
is said to be in scope and refers to the function parameter.
If we now try to write int n = 2;
, that is, declaring a new integer variable called n
, we get an error message from Xcode error: cannot redeclare identifier in the same scope
because we are inside the curly braces of the f
function. Get used to C preferring short declarators, since in the 1970s every byte saved was a byte gained in performance!
for(int n = 0; n<10; ++n) {
printf("%d\n", n);
}
Now that we created a loop, we have a new set of curly braces, which encloses the print function. In this case, redeclaring int n = 0;
is valid, as it will affect only the code inside the loop. In Swift, we could have accessed the previous n
by using the self
keyword, but luckily this seems not possible here, and helps avoid confusion.
After the loop starts {
, the scope of the local variable n
begins, while the original n
’s scope is suspended. The printf
function prints the values of the local n
(0 1 2 3 4 5 6 7 8 9), then the loop exits and the scope of the local n
ends.
At this point, the function parameter n
is back in scope, and we can access it with a new printf
function so that it prints its original value plus 1.
printf("%d\n", n);
}
Now, the scope of the parameter n
ends, and it cannot be used outside anymore. If we try this, it will throw the error Error: name 'n' is not in scope
.
int a = n;
File scope
An object has a file scope if it was declared outside any block of code or outside any list of parameters. For example, at the beginning of a musical score, we write the composer’s name, which is hopefully valid—barred in some very rare examples—until the end of the score itself.
If we know that an object will need to remain accessible for the whole duration of the main
function, then declare it at the very beginning, after the include
statement:
#include <stdio.h>
int universalMeaning = 42;
int main(int argc, const char * argv[]) {
return 0;
}
Function scope
This is quite hard to explain because it is a scope that is valid inside the whole body of a function, but only if it is a label
, that is a keyword with a specific meaning. You will recognise it when you see it, and for now, we have found no need to label anything. The easiest form of label is found in the switch
statement, where you check whether an object is included in one of several options, and then execute the appropriate code. It is akin to ordering scores in your library: you got a sonata for cello and piano in your hands, so you will browse the library for the cello and piano category and, once found, will “execute your code”, hopefully placing the book next to other relevant ones.
In programming, this means that something with function scope is valid for all the function, regardless of any block present in it.
Function prototype scope
Occasionally—I wonder why one would want to make their own life so complicated but—one needs to take this into account. If, for any reason, we have a function with more than one parameter and, for any reason again, we want to use a previous parameter as argument of a following parameter, well, we can. If you didn’t understand what all this is about, do not worry, you are not alone, and it is totally normal.
For example, we have a function g
(f
was taken before in our source file), which accepts two parameters, an integer called n
and an integer array (an ordered collection of items) called a
containing n
elements.
int g(int n, int a[n]);
I have not yet found a code sample that would demonstrate the usefulness of this, though.
What’s next?
If you are still here after 6 episodes, then you know me. Once more, I bit more than I could chew. The subject of object lifetime will be looked at in the next episode. All this stuff is quite hard to grasp and even more to master, so do not feel shy if you need to go back to previous episodes and review something, I frequently do it.
I know I had promised a brief review, but these topics are so deep that I prefer not to leave behind anything, or the least possible.
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.
3 thoughts on “Learning the C Programming Language as a Classical Musician”