This is the fifth article in the Making Sense of C series.
So far, we've determined that we're going to talk to the compiler by telling it
the name of the file we want it to read and we've determined how to tell the
compiler to ignore parts of the file we don't want it to read.
We've also established that our code is going to consist of a bunch of
statements, but we've never established what a valid statement should look like.
In this article, we're going to discuss the most basic statements in C
:
variables and basic arithmetic.
+
, -
, /
, *
, %
=
, +=
, -=
, *=
, /=
, %=
C
handles operators and ()
For C
to be a programming language, you'll need to be able to write more than
comments.
While there are some complicated things you should be able to do in any
programming language, we'll focus on the simplest thing we can do: arithmetic.
You should also be able to store values for later use, otherwise you would have
to type the entire expression on one line, even if you repeat things.
In this section, we're going to discuss how to manipulate values using some of
the basic operators in C
, how to store the results in variables, and how to
use those results later on.
Variables store values for later use. To access a variable, we need some way of identifying it, so we'll need to give it a name. Since explicit types are more efficient than variables, we also need to specify the type. Since we don't need to specify anything besides the type and the name, our statement to declare a variable should just be the name of the variable and its type.
We will use the syntax [type] [variable_name];
to declare a variable.
I will discuss types more later in the series, but for now we're just going to
use one: int
, which represents an signed integer (positive whole numbers,
negative whole numbers, and zero).
Since we're only dealing with int
s, our variable declarations will look like
int a;
.
You can only use a variable after it's declared (remember that the compiler
reads your code top to bottom, left to right) and you can only declare a
variable once.
Once a variable is defined, it cannot change its type for reasons we'll specify
later.
int a; int a; // ERROR: redeclaration of a
You can also declare multiple variables of the same type on one line.
int a, b, c;
We can't use a semicolon, /*
, */
, //
, or a space since they're already
reserved.
For now, let's just say that we can use any letters or digits to name a
variable.
We also can't use any reserved symbols like +-/*%
in our names or use any
reserved words, like int
.
We can use something like sum_of_ints
, we just can't use the name int
.
Since we also might want spaces, we can use the underscore (_
) to represent a
space.
Lastly, we don't want numbers at the beginning of variables, since someone could
see something like 4x
and think that x
is the variable and it's getting
multiplied by four, so the first character can't be a number.
Here are a list of valid variables names:
number_of_apples
distance_from_sun
sum_of_squares
numberOfApples
x3
fsadlj
FSAD324jds
In general, I use snake_case when I name my variables, meaning each word is separated with underscores, like in the first three examples above. A lot of people prefer to not use underscores because it's extra typing, so they use camelCase, in which the first letter of every word except the first is capitalized and every variable only contains letters.
I find snake_case easier to read, so I use it.
You should make sure to always give anything you can name a descriptive name,
including variables.
For example, what do you think the first four variables store in the list above?
What if you see something in your code that looks like number_of_apples +
distance_from_sun
?
Would it make sense to add those variables?
On the other hand, what if you saw fsadlj + x3
?
Does it make sense to add those variables?
How could you tell?
Giving things descriptive names will save you a lot of headache in the future.
Of course, there is a balance between descriptiveness and verboseness.
For example, I could have called sum_of_squares
,
sum_of_the_first_one_hundred_squares_counting_one_hundred_squared
, but that's
way too much to type and all I need to know to not confuse it with other
variables or to recognize when I'm using it when I shouldn't is that the
variable stores a sum of squares.
Since you're programming, you probably want to do some math operations like addition, subtraction, etc., so let's just reserve the normal math symbols for these basic operations right off the bat.
+
: Adds two values.
-
: Subtracts two values.
/
: Divides two values (Ignores the
fractional part for whole number/integral operands and you can't divide by
zero).
*
: Multiplies two values.
and they all have the syntax 15 • 12
, where •
is one of the
arithmetic operators.
You probably also want to be able to use negative numbers, so let's say that if
a -
shows up before a number, we'll make the number negative.
The -
also functions just like a negative sign does in normal math, where -13
* 5
⇒-65
.
The last basic arithmetic operation you'll likely need is the ability to get the
remainder of a division.
Remember that we're looking for unused, unpaired (parentheses, curly brackets,
less than, greater than, and square brackets are out) printable characters that
will be on everyone's keyboards.
After assigning everything else, we find that our only choices to represent the
remainder division are @
and %
.
Since %
is more related to arithmetic than an @
symbol, we'll use %
.
The %
operator is specifically defined so that, for any two whole numbers, (a
/ b) * b + (a % b)
must equal a
.
Since the remainder is only defined for integer division, %
only works on
whole numbers.
For example:
17 % 4
⇒1
32 % 10
⇒2
1.2 % 3
won't compile because 1.2
isn't an integer.
4 % 1.3
also won't compile because 1.3
isn't an integer
1000 % 13
⇒12
(-1000) % 13
⇒-12
1000 % (-13)
⇒12
(-1000) % (-13)
⇒-12
From the last four, we can see that the remainder has the same sign as what
comes before the %
regardless of the sign of what comes after %
.
Now that you can do some basic arithmetic in C
, let's look into how to store
the results.
First and foremost, =
stores the value on the right in the variable on the
left.
Things that can be assigned in C
are called lvalues (left
values) and thing that cannot be assigned in C
are called
rvalues (right values) because rvalues
cannot appear on the left side of an assignment operator and
lvalues can appear on the left side of an assignment operator.
For example:
int a; a = 7; // a now contains 7 // You can also declare and use = on the same line, like so int b = 2 * 3; // b now contains 6 int c = a + b; // a is replaced with 7 and b is replaced with 6, so now // c contains 13 // Notice that variables (lvalues) can show up on the right // side of an assignment operator 6 = c; // Throws an error and won't compile a = 15; // a now contains 15, c still contains 13 c = c * 10; // c contains 130
The last line can be shortened to c *= 10;
.
In fact, all the other assignment operators work identically.
a •= [rvalue];
is exactly equivalent to a = a • ([rvalue]);
, where
a
is a variable, •
is +
, -
, *
, /
, or %
, and [rvalue] is any
expression that results in a number.
It's just a little syntactic sugar to save
you some typing.
The last thing to note is that [lvalue] • [rvalue]
(where •
represents one of the assignment operators) actually returns the [rvalue]
, so
you could write something like
int a, b; a = 7 + (b = 5 + 1); // b is set to 6, then (b = 6) is replaced with 6, // so the right hand side becomes 7 + 6
When you write code, you aren't just writing code for the computer, you're writing code for everyone who reads your code, including yourself. You should write code that you'll be able to understand a year from now, which generally means that you shouldn't write bad code that uses obscure or surprising features of the language just to be clever.
Using clever tricks in your code is like using big words. If you use them because using anything else would be more inefficient, more complicated, or impossible, then using them is fine. If you use them because you want to impress people, then you're going to look like an idiot when you mess up and people won't want to interact with you.
In the case of using the fact that a = b
returns b
, I wouldn't recommend
using it.
Say that you have this code:
int a = 17; a += 1 + 30 / 3 - 3 * 50;
To determine the value of a
, we need to have some sort of order in which to do
the operations.
For example, we could do the operations left to right, but then we would
calculate 1 + 30 / 3
as (1 + 30) / 3
⇒ 31 / 3
⇒ 10
(it gets
rounded to zero because we're using integer division).
If we were using the regular order of operations, we would evaluate 1 + 30 /
3
as 1 + (30 / 3)
⇒ 1 + 10
⇒ 11
.
In C
, there is a strict order of operations listed here.
In practice:
The less energy you have to spend on figuring out the result of an expression, the better.
Up to this point, we've established that we're going to give our compiler a file with a series of statements without giving examples of these statements. In this article, we've established what some statements will look like. Our file should now look like
int a; // I could have combined these lines into "int a = 7;" a = 7; int a_cubed = a * a * a; // Just some basic multiplication other statement; // We still haven't established what other statements // would look like. int b = (a + 7) * a; b /= -6; other statement;
Before we go onto the next topic in C
, we need to take a detour to explain
how computers handle memory, as we're going to introduce types that won't
appear to have any use cases unless you understand how your computer
handles memory.
I strongly recommend that you read the article before moving on.
We also can also discuss how computers represent
integral types and how computers represent
floating point types, but knowing how to represent them isn't as important
for understanding C
.
Without the knowledge about how computers represent numbers, you're just going
to have to accept that the language is the way it is without any explanation
why.
Once we've covered those two detours, we'll move on to dealing with the fundamental types in C
.