Last time we ambushed you with a bunch of information about how C manages memory when you declare variables in your programs. Today we’re going to stick to int
, float
, and char
variables, but get into the nitty gritty of actually using them.
To start, let’s consider our original hello world program.
#include<stdio.h>
int main()
{
printf("Hello World\n");
return 0;
}
This program uses a function named printf
. The f
in printf
stands for format. While you can use printf
to print a string (as we have above), the real power of printf
is in its ability to print strings and format them using values provided by variables.
This might make more sense using code. Consider the following program
#include<stdio.h>
int main()
{
printf("Our variable contains the value %i\n", 42);
return 0;
}
If you compile and run this program, you’ll see output that looks like the following
Our variable contains the value 42
You’ll notice we passed printf
two arguments. The first was the string we wanted to print. The second was a variable. You’ll also notice our string included a funny looking %i
character sequence. In the final printed string, this %i
was swapped out for the value of the variable we passed in. This is what formatting means — it’s a very basic templating system for replacing variables in strings.
If you wanted to use two variables, you’d just include another %i
and pass a third argument to the function.
#include<stdio.h>
int main()
{
printf("Our variables contains the values %i and %i\n", 42, 65);
return 0;
}
The printf
function will run through the string left to right, replacing the first %
sequence with the second argument, the second %
sequence with the third, and so on.
Understanding Formatting Templates
Here’s another formatting string to try
#include<stdio.h>
int main()
{
printf("This isn't going to work %i", 4.321);
return 0;
}
If you try compiling the above program, you’ll get an error that looks something like this
main.c:5:47: warning: format specifies type 'int' but the argument has type 'double' [-Wformat]
printf("This isn't going to work %i", 4.321);
~~ ^~~~~
%f
1 warning generated.
Why didn’t this work? Because %i
only works with int
variables. If you want to use a decimal number (i.e. a float
) you’ll need to use the %f
format string
#include<stdio.h>
int main()
{
printf("This is going to work %f\n", 4.321);
return 0;
}
Once again, C’s strict typing system rears its head. Rather than turn your 4.321 into an int
, or rather than have some generic template replacement, C forces you to specify exactly what type you’ll be using. C does this because casting a variable has performance implications, and C is biased towards not making these sorts of decisions for you.
Mixing Types
One of the challenges you’ll face as a C programmer is understanding the consequences of using two different types in the same sort of operation. For example — consider the following program.
#include <stdio.h>
int main() {
int x = 5;
int y = 2;
float answer = x / y;
printf("Answer: %f\n", answer);
return 0;
}
Here we declare two integers (x
, y
) and we divide the first by the second. Also, since 5 divided by 2 is 2.5, we declare a float variable named answer
to hold the results. However, if you run the above program, you’ll get the following output.
Answer: 2.000000
What gives? Why does C think 5 divided by 2 is 2.0?
The problem is, we divided an integer by an integer. Since both parts of the division operation were integers, C used integer division to derive a result. Again, C errors on the side of doing the most efficient thing, even if it’s not the most obvious thing. We may have been smart enough to declare a float to hold our value, but we weren’t smart enough to remember that C can be weird when types mix. The solution is to ensure either the numerator or the divisor in our example is also a float.
One way to do this would be declaring x
as a float.
#include <stdio.h>
int main() {
float x = 5;
int y = 2;
float answer = x / y;
printf("Answer: %f\n", answer);
return 0;
}
While this works for our simple example, in a real program changing the type of a variable that’s used elsewhere may have unforeseen consequences. Fortunately C (like most programming languages) has a mechanism that will allow us to temporarily convert a variable of one type into other. This mechanism is known as casting. To cast the integer x
as a float, we’d do this
#include <stdio.h>
int main() {
int x = 5;
int y = 2;
float answer = ((float) x) / y;
printf("Answer: %f\n", answer);
return 0;
}
This program is identical to our first example, with the exception of this
((float) x) / y;
Before we use x
in our division operation, we put the (float)
cast in front of it. This will treat x
for this operation only as a floating point number.
Lost Information
Casting from an int to a float is a relatively safe operation. However, what about casting a floating point number like 2.5
to an integer. What integer would that be? Two? Or three? This is the danger of casting — you often introduce ambiguities into your program. If we give this a try
#include <stdio.h>
int main() {
int x = 0;
float y = 2.5;
x = (int) y;
printf("Two or Three?: %i\n", x);
return 0;
}
You’ll see that C will just drop the decimal.
Two or Three?: 2
Here’s another tricky example of lost information — what do you think this program will print?
#include <stdio.h>
int main() {
char x = ' ';
int y = 57;
x = (char) y;
printf("Two or Three?: %c\n", x);
return 0;
}
When an integer 57 gets cast as a char
, the result is —
What is 57 as a char?: 9
Nine? How does that make any sense?! Well — it doesn’t until we consider the ASCII Text Encoding table. The character 9
is the 57th entry in this table. At some point a c systems developer decided that casting an int
as a char
would turn it into its corresponding text character.
This isn’t the only example of char
s being somewhat type fluid. Consider the following example
#include <stdio.h>
int main() {
int x = 81;
char y = 'X';
printf("%c\n",x);
printf("%i\n",y);
return 0;
}
Here we’ve printed an int
as a char
(with %c
) and a char
as an int
(%i
). The normally strict C didn’t miss a beat, and automatically rendered one as the other.
Q
88
You’ll see this sort of number/char substitution all over C programs — but be careful. Consider this program.
#include <stdio.h>
int main() {
int x = 325;
printf("%c\n",x);
return 0;
}
Here we’re trying to use an int
as a char
— but with a number that’s outside the normal range of the ASCII tables. This program produces the following output
E
Why E
and 325 are the same is a bit outside what we’re going to cover today — the main point is be careful when you’re using different variable types in a char
context — they may not do what you think.
Character Addition
There’s one last example to consider before we wrap up for the day.
#include <stdio.h>
int main() {
int x = 20;
char y = 'a';
int answerInt = x + y;
char answerChar = x + y;
printf("Answer Int: %i\n", answerInt);
printf("Answer Char: %c\n", answerChar);
return 0;
}
Here we’re performing a mixed type addition with int
s and char
s. If we look at the output.
Answer Int: 117
Answer Char: u
we see that C treated our char
as a number again. If you’re coming from a language like javascript, you might expect the +
to do some sort of concatenation. That’s not going to work in C.
Wrap Up
Closing with javascript concatenation does (once again) raise the specter of strings. So far we’ve been focused on numbers and individual characters — but we’ve avoided how C deals with silly human things like words and text.
Next time we’ll start our trek towards strings in C, but before we can reach that summit we’ll need to stop at basecamp, and cover an even more important C concept: Arrays.