2. Variables and Types#
Note: This document contains very specific details - the intention is not for you to memorize these details but to be aware of them and how they can affect your programs.
Generally speaking, we’ll use a subset of these types as we programm in C++.
2.1. Variables#
While potentially confusing to developers with experience in object-oriented languages (e.g., Python), C and C++ define the term object as “a region of data storage in the execution environment, the contents of which can represent values”. (This has been present since the C89 ANSI Standard). The C Programming Language book defines an object “(sometimes called a variable) as a location in storage, and its interpretation depends upon two main attributes: its storage class and its type. The storage class determines the lifetime of the storage associated with the identified object; the type determines the meaning of the values found in the identified object. A name also has a scope, which is the region of the program in which it is known”. We’ll discuss types throughout the rest of this page, while storage class and scope will be discussed in the function notebook. C++ also uses the term object as an instance of a class, also to be discussed later. A variable is a name that refers to a particular data storage location in the computer’s memory. Unlike Python, variables must be declared prior to their use in C++. In both languages, variables have an associated type that gives meaning to the data. Unlike Python, C++ variables cannot change types once defined. Everything is an object in Python with state and behavior, while in C++, only variables declared from classes have behavior. The following code example declares four variables in C++:
int i = 4;
double d = 2.3;
char c = 'h';
int j;
C++ variable naming rules are very similar to those in Python. A variable name can consist of alphabetical characters (lower and upper case), numbers, and the underscore character. Names may not start with a number. Names may not be a C++ keyword. By convention, variable names use camelCase rather than separating words by underscores.
2.2. Types#
Within C++, we can divide the available types into several different categories:
Boolean
Basic/Arithmetic Types: char, integer, and floating-point
Enumerated Types: integer types, but assigned discrete values
void - indicates no value is assigned
Strings
Classes / Structures
Pointers (presented later)
2.2.1. Boolean#
Unlike Python, C++ did not originally have a Boolean data type until it’s initial standardization in 1998.
bool t = true;
bool f = false;
Note that the keywords for true and false are both lower case. Boolean values are also typically represented as integers where 0 represents false and any non-zero value represents true.
2.2.2. Arithmetic Types#
2.2.2.1. Char#
A char
type represents a single character. Typically this is a signed value and is mapped to the ASCII values. They can be assigned through a character literal that is represented by single quotes. Within C++, we can perform arithmetic on this data type.
char c = 'A';
2.2.2.2. Integer Types#
Unlike Python, C++ contains a number of different integer types. Additionally, these types can be defined as signed or unsigned. Additionally, integers have limits in C++, unlike their counterparts within Python.
Type |
Storage Size |
Value Range |
---|---|---|
char |
1 byte |
-128 to 127 |
unsigned char |
1 byte |
0 to 255 |
signed char |
1 byte |
-128 to 127 |
int |
4 bytes |
-2,147,483,648 to 2,147,483,647 |
unsigned int |
4 bytes |
0 to 4,294,967,295 |
short |
2 bytes |
-32,768 to 32,767 |
unsigned short |
2 bytes |
0 to 65535 |
long |
8 bytes |
-9223372036854775808 to 9223372036854775807 |
unsigned long |
8 bytes |
0 to 18446744073709551615 |
Unfortunately, with C++, these limits can vary based on the platform. The following program shows how we can find the specific limits. Also, notice how we can use the sizeof`` operator to get the number of bytes for a particular type (line 45).
digits`` provides the number of digits the variable type can represent without a sign.
1//filename: integers.cpp
2//compile: g++ -std=c++17 -o integers integers.cpp
3//execute: ./integers
4#include <iostream>
5#include <limits>
6
7int main() {
8 std::cout << "char:\n" <<
9 " bits: " << std::numeric_limits<char>::digits << "\n" <<
10 " signed: " << std::numeric_limits<char>::is_signed << "\n" <<
11 " min: " << (int) std::numeric_limits<char>::min() << "\n" <<
12 " max: " << (int) std::numeric_limits<char>::max() << "\n";
13
14 // the cast to an int type was necessary as C++ would have displayed the min/max
15 // char instead
16
17 std::cout << "int:\n" <<
18 " bits: " << std::numeric_limits<int>::digits << "\n" <<
19 " signed: " << std::numeric_limits<int>::is_signed << "\n" <<
20 " min: " << std::numeric_limits<int>::min() << "\n" <<
21 " max: " << std::numeric_limits<int>::max() << "\n";
22
23 std::cout << "long:\n" <<
24 " bits: " << std::numeric_limits<long>::digits << "\n" <<
25 " signed: " << std::numeric_limits<long>::is_signed << "\n" <<
26 " min: " << std::numeric_limits<long>::min() << "\n" <<
27 " max: " << std::numeric_limits<long>::max() << "\n";
28
29 std::cout << "short:\n" <<
30 " bits: " << std::numeric_limits<short>::digits << "\n" <<
31 " signed: " << std::numeric_limits<short>::is_signed << "\n" <<
32 " min: " << std::numeric_limits<short>::min() << "\n" <<
33 " max: " << std::numeric_limits<short>::max() << "\n";
34
35 std::cout << "unsigned char:\n" <<
36 " bits: " << std::numeric_limits<unsigned char>::digits << "\n" <<
37 " signed: " << std::numeric_limits<unsigned char>::is_signed << "\n" <<
38 " min: " << (int) std::numeric_limits<unsigned char>::min() << "\n" <<
39 " max: " << (int) std::numeric_limits<unsigned char>::max() << "\n";
40
41 std::cout << "unsigned int:\n" <<
42 " bits: " << std::numeric_limits<unsigned int>::digits << "\n" <<
43 " signed: " << std::numeric_limits<unsigned int>::is_signed << "\n" <<
44 " min: " << std::numeric_limits<unsigned int>::min() << "\n" <<
45 " max: " << std::numeric_limits<unsigned int>::max() << "\n";
46
47 int i = 5;
48 std::cout << "int size (bytes): " << sizeof(i) << "\n";
49
50 return 0;
51}
2.2.2.3. Code Example#
The following programming first declares and initializes two char variables. Notice that in C++, we represent a single char value with single quotes ‘ . Within C++, it is important that you properly initialize variables before their use - by default, we don’t know what value they represent otherwise. In the next section of the code, we declare two int values and convert the Celsius value to its corresponding Fahrenheit value. Note that the formula produces a double due to the 1.8
in the expression.
1//filename: example.cpp
2//compile: g++ -std=c++17 -o example example.cpp
3//execute: ./example
4#include <iostream>
5
6int main() {
7 char a_lower = 'a';
8 char a_upper = 'A';
9
10 std::cout << a_lower << " " << (int) a_lower << "\n";
11 std::cout << a_upper << " " << (int) a_upper << "\n";
12 a_lower += 5;
13 std::cout << a_lower << " " << (int) a_lower <<"\n";
14
15 int c = 97;
16 int f = c * 1.8 + 32;
17 std::cout << c << " Celsius to " << f << " Fahrenheit" << ", " << c * 1.8 + 32 << "\n";
18
19 return 0;
20}
a 97
A 65
f 102
97 Celsius to 206 Fahrenheit, 206.6
2.2.2.4. Floating-Point Types#
The following table shows the available floating-points and their limits with C++.
Type |
Storage Size |
Value Range |
Precision |
---|---|---|---|
float |
4 bytes |
1.2E-38 to 3.4E+38 |
6 decimal places |
double |
8 bytes |
2.3E-308 to 1.7E+308 |
15 decimal places |
long double |
10 bytes |
3.4E-4932 to 1.1E+4932 |
19 decimal places |
1//filename: float.cpp
2//compile: g++ -std=c++17 -o float float.cpp
3//execute: ./float
4#include <iostream>
5#include <limits>
6
7int main() {
8 std::cout << "double:\n" <<
9 " precision bits: " << std::numeric_limits<double>::digits << "\n" <<
10 " signed: " << std::numeric_limits<double>::is_signed << "\n" <<
11 " min: " << std::numeric_limits<double>::min() << "\n" <<
12 " max: " << std::numeric_limits<double>::max() << "\n"
13 " exponent bits: " <<
14 sizeof(double) * 8 -
15 std::numeric_limits<double>::digits -
16 std::numeric_limits<double>::is_signed << "\n";
17
18 return 0;
19}
You should modify the program to look at the float and long double types.
In this class, we will primarily use double
.
2.2.2.5. Overflow and Underflow#
One of the dangers with C++ and the type representation is that it’s possible for the value of an int variable to overflow - this condition occurs when a calculation produces a result that can longer be represented within the given type (its magnitude is too great.). Similarly, an underflow occurs when the magnitude becomes smaller than the smallest value representable.
1//filename: overflow.cpp
2//compile: g++ -std=c++17 -o overflow overflow.cpp
3//execute: ./overflow
4#include <iostream>
5using namespace std;
6
7int main() {
8 int value = 3;
9
10 for (int i = 1; i < 21; i++) {
11 cout << i << " " << value << "\n";
12 value *= 3;
13 }
14 return 0;
15}
2.2.2.6. Loss of Precision#
Loss of precision issues occur with floating-point numbers when the true result of a floating point operation has a smaller precision than can represented in the given data type.
1//filename: precision.cpp
2//compile: g++ -std=c++17 -o precision precision.cpp
3//execute: ./precision
4#include <iostream>
5using namespace std;
6
7int main() {
8 float value = 0.3;
9
10 for (int i = 1; i < 12; i++) {
11 cout << i << " " << value << "\n";
12 value /= 300000;
13 }
14 return 0;
15}
2.2.2.7. Type Conversions#
The C++ compiler will implicitly convert data types when a lower data type can be converted into a higher one without losing meaning.
1//filename: conversion.cpp
2//compile: g++ -std=c++17 -o conversion conversion.cpp
3//execute: ./conversion
4#include <iostream>
5using namespace std;
6
7int main() {
8 char c = 'A';
9 short s = 1;
10 int i = 1000;
11 float f = 1.05;
12 s = s + c;
13 i = i + s;
14 f = f + i + c;
15 cout << "s: " << s << "\n";
16 cout << "i: " << i << "\n";
17 cout << "f: " << f << "\n";
18}
s: 66
i: 1066
f: 1132.05
Programmers can explicitly perform type conversions in C++ by using type casting. This has the form of (dataType)
expression where dataType is a valid C++ data type. Performing such casts may result in the loss of information. C++ will also automatically perform a conversion when values are assigned to a variable or parameter when the destination type does not match. You can use the g++ option -Wconversion
to find warnings of such behavior.
1//filename: casting.cpp
2//compile: g++ -std=c++17 -o casting casting.cpp
3//execute: ./casting
4#include <iostream>
5using namespace std;
6
7int main() {
8 int sum = 22;
9 int count = 7;
10 double mean;
11
12 /* implicit conversion after perform integer division */
13 mean = sum / count;
14 cout << "Value of mean: " << mean << "\n";
15
16 /* explicit conversion: convert an int to a double, then perform float division */
17 mean = (double) sum / count;
18 cout << "Value of mean: " << mean << "\n";
19
20 /* implicit conversion, can be raised as a warning through the gcc option -Wconversion */
21 int average = mean;
22 cout << "Average: " << average << "\n";
23
24 /* programmer demonstrating clear intent to perform the conversion (preferred) */
25 average = (int) mean;
26 cout << "Average: " << average << "\n";
27}
2.2.2.8. Choosing Numeric Data Types#
Consider the following items to select the most appropriate data type for a variable:
Whether the variable needs to hold an integer or floating point numbers
The maximum and smallest numbers (range) that the variable needs to store.
For integers, whether the variable needs to hold signed values versus just unsigned values. Typically, if you are counting items (e.g., list size), an unsigned data type should be used.
For floating point numbers, the number of decimal places (precision) required.
2.3. Enumerated Types#
In C++, an enumeration type (enum
) is a data type consisting of integral constants. By default, these values start at 0 and increment by 1. We can also explicitly define values. Also, notice in the code that we have defined constants that can used. While C allows programmers to perform arithmetic operations on enums, C++ prevents such behavior - programmers will need to convert values explicitly and ensure the conversion is appropriate.
1//filename: enum.cpp
2//complile: g++ -std=c++17 -o enum enum.cpp
3//execute: ./enum
4#include <iostream>
5using namespace std;
6
7enum week {Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday};
8
9/* change default values */
10enum minGrade {
11 F = 0,
12 D = 60,
13 C = 70,
14 B = 80,
15 A = 90
16};
17
18int main(int argc, char** argv) {
19 enum week x = Tuesday; /* declare option to be an enum */
20 cout << x << "\n";
21
22 enum minGrade y = C;
23 cout << y << "\n";
24
25 enum minGrade a = (enum minGrade) 90;
26 cout << a << "\n";
27
28 // Be careful - C++ does not validate the numbers when casting
29 enum minGrade e = (enum minGrade) 95;
30 cout << e << "\n";
31 return 0;
32}
C++ can also define enum classes in addition to the “plain” enumerations presented here.
2.4. void#
The void
type specifies that no value is available - think of it as “not present”. We cannot create variables of the type void
. C++ uses void
for several reasons:
Functions with no return value. Such functions are declared as
void functionName(int argument) { }
Functions with no arguments. For example,
int functionName(void) {}
. Unlike C, C++ allows us to leave the keywordvoid
off in this situation.Pointers to void. Pointers of type
void *
represent the address of an object, but not its type. We’ll need to convert (cast) these to an appropriate type for use. We will present pointers in a later notebook.
2.5. Strings#
C++ has two primary ways of representing strings. (Additional representations are available for Unicode-based strings which will not be covered in this class.)
As C++ was derived from C and attempts to maintain backward compatibility, the C representation is one way. Here, strings are represented as one-dimensional arrays of characters terminated by a null character '\0'
. This method can be error-prone and can lead to security issues, such as buffer overflows if these strings are not handled carefully.
The preferred way is to use the std::string
class provided by the C++ Standard Library encapsulates and simplifies the manipulation of strings.
#include <string>
std::string myString = "Hello, World!";
With std::string, the class -
represents strings as a dynamic array of characters tracking the actual length of the string.
provides many built-in functions for string manipulation, like
append
,insert
,replace
,substr
, etc.handles memory allocation and deallocation automatically, reducing the risk of memory leaks and errors.
2.6. Class and Structures#
Classes allow us to combine a group of related variables and keep them together as a single unit. As with Python, we’ll also be able to define behavior (methods associated with this data).
As C++ uses C as its original basis, we’ll first examine how structs were defined in C. As you can see from the code below, structs in C only contain data elements. Most programmers as used typedef
to be able to use a more “user-friendly” type name such as point
. In C, we cannot just use _point
as the type name - we’d have to use struct _point
- C++, though, allows us to use _point
as the type name.
We refer to a member of a particular structure by using .
(structure member operator) - see lines 19 and 20. We can also provide a list of initializer values to a structure, as presented in line 16. In C,
structures may not be compared with the comparison operators such as ==
. However in C++, a programmer can override those operators in a struct definition and provide the necessary functionality.
1//filename: struct.c
2//complile: gcc -o struct struct.c
3//execute: ./struct
4#include <stdio.h>
5#include <stdlib.h>
6
7typedef struct _point point;
8struct _point {
9 int x;
10 int y;
11};
12
13int main(int argc, char *argv[]) {
14 point p;
15
16 p.x = 1;
17 p.y = 5;
18
19 point q = {40, 60};
20
21 printf("(x,y): (%d,%d)\n", p.x, p.y);
22 printf("(x,y): (%d,%d)\n", q.x, q.y);
23
24 return 0;
25}
1//filename: struct_examples.c
2//complile: gcc -o struct_examples struct_examples.c
3//execute: ./struct_examples
4#include <stdio.h>
5#include <stdlib.h>
6
7typedef struct _point point;
8struct _point {
9 int x;
10 int y;
11};
12
13typedef struct _rect rect;
14struct _rect {
15 point pt1;
16 point pt2;
17};
18
19typedef struct _circle circle;
20struct _circle {
21 point center;
22 float radius;
23};
24
25
26int main(int argc, char *argv[]) {
27 circle c = { {2,1}, 5};
28 printf("(%d,%d) - area: %f\n", c.center.x, c.center.y, c.radius * c.radius * 3.14);
29
30 point a = {6,6};
31 point b = {3, -6};
32 rect r;
33 r.pt1 = a;
34 r.pt2 = b;
35 printf("(%d,%d) (%d,%d), area: %d\n", r.pt1.x, r.pt1.y, r.pt2.x, r.pt2.y, abs(r.pt1.x - r.pt2.x) * abs(r.pt1.y - r.pt2.y) );
36
37
38 return 0;
39}
C++ builds upon this foundation by allowing us to define behavior within a struct
. The language also adds the keyword class
. struct
and class
provide the same capabilities to programmers. They differ, though, in the default visibility of their members. In a struct
type, members have public visibility by default - that is, programmers can directly access those members (variables and functions). In a class
type, members have private visibility by default, requiring methods with public visibility to be able to indirectly reference them. In both class
and struct
, it is possible to create sections of different accessible modifiers (private
, public
, and protected
). We will primarily use classes, explicitly setting the visibility modifier to public. The following code block demonstrates this -
class RetireInfo {
public:
int months;
double contribution;
double rate_of_return;
};
1//filename: class.cpp
2//complile: g++ -std=c++17 -o class class.cpp
3//execute: ./class
4#include <iostream>
5
6class Point {
7 public:
8 int x;
9 int y;
10};
11
12class Rect {
13 public:
14 Point pt1;
15 Point pt2;
16 double area() {
17 return abs(pt1.x - pt2.x) * abs(pt1.y - pt2.y);
18 }
19};
20
21struct Circle {
22 Point center;
23 double radius;
24
25 double area() {
26 return radius * radius * 3.14;
27 }
28};
29
30
31int main(int argc, char *argv[]) {
32 Circle c = { {2,1}, 5};
33 std::cout << "(" << c.center.x << ", " << c.center.y << ") - area: " << c.area() << "\n";
34
35 Point a = {6,6};
36 Point b = {3, -6};
37 Rect r;
38 r.pt1 = a;
39 r.pt2 = b;
40 std::cout << "(" << r.pt1.x << ", " << r.pt1.y << ") (" << r.pt2.x << ", " << r.pt2.y << ") - area: " << r.area() << "\n";
41
42 return EXIT_SUCCESS;
43}
Note: We intentionally used the struct
keyword with Circle to demonstrate the difference between struct
and class
. We also added some behavior to start introducing the concept of object-oriented programming that you have already seen with Python. For this class, we will predominantly use class
rather than struct
. If nothing else, class
makes the programmer explicitly choose which attributes and methods to be public.