04: Variables, Values, and Types
UC Irvine - Fall ‘22 - ICS 45C
Quick list of things I want to talk about:
- Types
- Sizes
- Overflow/underflow
- char is just a small int
- Declaring variables
- Static types
- Constants
Expanded notes:
In previous notes we saw how to compile and run C++ code, and how to do simple I/O in the terminal.
While doing input, we used cin
to store values into variables.
Let’s dive a little deeper into how we define and use variables.
Types
There are many types of variables in C++.
Coming from python, you’re probably used to int
, float
, str
.
In C++, however, there are 5 different types just for integers!
Integers
The main difference between each type is the size. Each different integer type is able to store a different range; so you must consider the magnitude of the values you plan to store when deciding on your variables.
The integer types are:
char
: takes 1 byteshort
: usually 2 bytesint
: usually 4 byteslong
: usually 8 byteslong long
: usually 8 bytes
Notice that char
s are 1 byte long, but the others are usually x bytes
.
This is because different platforms/compilers might provide different sizes for this.
The only guarantee we have is that:
char <= short <= int <= long <= long long
You can use the function sizeof
to see how many bytes each type takes using your platform/compiler:
#include <iostream>
using namespace std;
int main() {
cout << "char: " << sizeof(char) << "byte(s)\n";
cout << "short: " << sizeof(short) << "byte(s)\n";
cout << "int: " << sizeof(int) << "byte(s)\n";
cout << "long: " << sizeof(long) << "byte(s)\n";
cout << "long long: " << sizeof(long long) << "byte(s)\n";
return 0;
}
In my computer, I have 1, 2, 4, 8, 8
bytes on the respective types, which is the usual expected for 64-bit Linux/Mac systems.
If you’re on 64-bit Windows, you’ll probably get 1, 2, 4, 4, 8
.
Your results may be different, and that’s fine!
PS: We’re not going to cover it in this class, but there are ways to get the exact size you want: https://en.cppreference.com/w/cpp/types/integer
Floating-point
For floating-point numbers, we have 3 types:
float
double
long double
Similarly to the integers, the difference here is the size of each type.
Floating-point numbers are a little harder to support, so the standard says a float
is 4 bytes, if supported.
So just assume the size order:
float <= double <= long double
If you want to see how many bytes in your platform, you can use a similar code as above.
Other types
bool
: storestrue
(1) orfalse
(0);
Declaring Variables
We’ve been declaring variables already.
Whenever you have a statement like int x;
, that creates a variable you can use later.
If you want to declare many variables, you can do it one per line:
int x;
int y;
float z;
If you have many variables of the same type, you can combine their declarations in the same line:
int x, y;
float z;
Assigning values
We’ve seen how to read input and store it into a variable:
int x;
cin >> x;
But you can also assign numerical values:
int x, y;
x = 1;
y = x;
// would be the same as y = x = 1;
And use math operators:
int x;
x = (1 + 1) * 3;
You can also assign initial values directly in the declaration:
int x=1, y=2;
Type mismatch
Once you create a variable of a certain type, you can’t change it.
So, for example, if you have an int
variable x
and later want to change it to a float
, you’ll need a new variable.
When assigning values to different types, the compiler might give you warnings.
For example, when compiling this code:
#include <iostream>
using namespace std;
int main() {
int x = 2.5;
cout << x;
return 0;
}
You should get an error like this:
test-type.cpp:6:11: error: implicit conversion from 'double' to 'int' changes value from 2.5 to 2 [-Werror,-Wliteral-conversion]
int x = 2.5;
~ ^~~
1 error generated.
If you want to change a certain value to a different type, you can do type-casting the way we did in the overflow example.
You would put (NEW_TYPE)
before the value/variable you want to cast.
For example, we can do it like this:
#include <iostream>
using namespace std;
int main() {
int x = (int) 2.5;
cout << x; // should output "2"
return 0;
}
This is particularly important if you’re dividing two integers but want a float
result:
#include <iostream>
using namespace std;
int main() {
cout << "2 / 3 -> " << 2 / 3 << "\n";
cout << "(float) 2 / 3 -> " << (float) 2 / 3 << "\n";
return 0;
}
This should give you the following output:
2 / 3 -> 0
(float) 2 / 3 -> 0.666667
Since 2
and 3
are both int
s, C++ will also make the result an int
.
So this ends up being a floor division (i.e., it will truncate/round down the result).
If you want the float
result, you should cast one of the operands.
The same would happen if we had variables instead of 2
and/or 3
.
Finally, it’s important to keep in mind the size of the types you’re trying to convert.
It’s generally safe going up in size (e.g., from short
to int
), but you might get weird values if you go in the other direction.
For example, if you run the following code:
#include <iostream>
using namespace std;
int main() {
int x = 100000;
short y;
y = x;
cout << "x = " << x << "\ny = " << y << '\n';
return 0;
}
The value of y
won’t be the same as x
, since 100,000
is above the limit for short
.
Constants
Lastly, we can create constants in C++.
If there’s a variable that you don’t want to be changed, you can use the const
keyword to make it like that.
For example, you could define pi:
const float pi = 3.1415;
If somewhere in the code you try to overwrite that value, the compiler would give you an error and would not allow it. Constants need to be initialized during declaration.
Integer Ranges
Okay, so we have different sizes of variables… but what does that mean?
It means that we’ve limited space to store numbers. Each variable has a number of bytes available, and each byte is commonly 8 bits long.
For floating-points, this usually changes the precision you can have (how many decimal places of accuracy). For integers, it changes the absolute minimum and maximum values that can be stored. Let’s talk about the integer limitations.
When we want to store a number, we convert it to binary form and store each 1/0 in one of the bits. Figure 1 below shows some numbers as decimal and binary:
In the example above, we can see that on the binary column, we have 4 bits, going from 0000
to 1111
, and the corresponding decimal values go from 0
to 15
.
This is the range of 4 bits! Because you only have 4 positions, you’ll have the least significant bit (i.e., first bit on the right) representing $2^0$ and the most significant bit (i.e., first bit on the left) representing $2^3$. So the maximum value we can make is $2^3 + 2^2 + 2^1 + 2^0$, which is 15, exactly like the table. That sum, $2^3 + 2^2 + 2^1 + 2^0$, is the same as $2^4 - 1$!
So, for example, if you have 8 bits for positive numbers, you can store from 0
to 255
($2^8-1$).
Signed vs unsigned
That example is simple enough, but what about negative numbers?
If we want to store both positive and negative numbers, we use half of the range for negative and half for positive. This is usually done by making the most significant bit (i.e., first one on the left) negative.
So if you have 8 bits, the first one means -$2^7$, while the other ones mean $2^6, 2^5, … 2^0$.
Considering that, using 8 bits, we could store from -128
(-$2^7$) to 127
($2^7 - 1$).
In C++, the default is to have signed numbers.
If you want some unsigned value, for example, the number of people in a room, you can use the keyword unsigned
in front of the type.
So we have:
unsigned char
;unsigned short
;unsigned int
;unsigned long
;unsigned long long
.
These types have the same size as their signed counterparts.
Usual ranges
Assuming the same bytes we saw on the previous list, 1, 2, 4, 8, 8
, these would be the expected ranges:
char
: from-128
to127
(unsigned,0
to255
);short
: from-32,768
to32,767
(unsigned,0
to65,535
);int
: from-2,147,483,648
to2,147,483,647
(unsigned,0
to4,294,967,295
);long
: from-9,223,372,036,854,775,808
to9,223,372,036,854,775,807
(unsigned,0
to18,446,744,073,709,551,615
);long long
: same aslong
above.
Over/underflow
Since we have limits on the values each type of variable can hold, what happens if we go above/below the allowed range?
Let’s look at an example:
#include <iostream>
using namespace std;
int main() {
char x = 127; // 2^7 - 1; max value for signed char.
unsigned char y = 255; // 2^8 - 1; max value for unsigned char.
cout << "original values:\n";
cout << "x=" << (short) x << ", y=" << (short) y << "\n\n";
// Overflow: going above the top range
x++;
y++;
cout << "after increment:\n";
cout << "x=" << (short) x << ", y=" << (short) y << "\n\n";
// Underflow: going below the minimum range
x--;
y--;
cout << "after decrement:\n";
cout << "x=" << (short) x << ", y=" << (short) y << "\n\n";
return 0;
}
This should output:
original values:
x=127, y=255
after increment:
x=-128, y=0
after decrement:
x=127, y=255
Note when we try to increase the maximum value, we go to the lowest value of the range.
From 127
to -128
, and from 255
to 0
.
This is called an overflow.
Similarly, when we try to decrease the lowest value, we end up with the highest one.
From -128
to 127
, and from 0
to 255
.
This is called an underflow.
Overflows and underflows happen because we are going over/under the limit of that type’s range.
A few things to highlight on that code:
- we use
(short)
in thecout
s because otherwise, C++ would try to find the character for that value since it’s achar
(remember ASCII/Unicode?). We actually want to see the number, so we tell the compiler to convert it to ashort
. x++
increments the value ofx
by one and returns the original value. So if you didint x=1; cout << x++;
you’d see1
printed out, but the value ofx
is now2
. There is also a++x
operator, which increments before return, soint x=1; cout << ++x;
prints2
andx
also has2
.x--;
does the same asx++;
but in the opposite direction, it decrements the variable. There is also--x;
.
References
- Comic reference: https://www.facebook.com/System32ComicsAdvanced/photos/a.194120642139152/311710617046820/
- Figure 1 reference: https://www.instructables.com/How-to-Convert-Numbers-to-Binary/
- CPP Reference: https://en.cppreference.com/w/cpp/language/types