A review of C++ topics covered in CISP 360: operator precedence, arrays, prototypes, default parameters, casting, scope rules, enums, recursion, static variables, operator overloading.
§1 Compiling C++ and Executing Files #
The most basic way to compile a C++ program is by directly using a command-line compiler such as g++ (GNU C++ Compiler) or clang++ (Clang C++ Compiler).
Suppose you have this simple C++ file named main.cpp:
// main.cpp
#include <iostream>
int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}
To compile this file, you would open your terminal and navigate to the directory containing main.cpp, then run:
g++ main.cpp -o myprogram
g++is the command to execute, in this case the compilerg++.main.cppis the source file to compile.-o myprogramspecifies the Output file name (myprogram).
This command produces an executable named myprogram, compiled from main.cpp. You can run it with:
$ ./myprogram
Hello, World!
NOTE:
In this example and any later examples which show both terminal commands and output, the command being run is preceded by a dollar sign ($).
The dollar sign represents a terminal prompt, the actual command being typed in this example is simply: ./myprogram followed by ⮐ (ENTER/RETURN key).
§2 Managing Multiple Files #
In a project of non-trivial complexity, it’s common to split code across multiple files for better organization.
Typically, you have:
- Header files (.h or .hpp): Contain declarations of functions, classes, and variables.
- Source files (.cpp): Contain definitions of functions and other code.
- Entrypoint (main.cpp): Contains the
main()function, which is the entry point of the program.
§2.1 Header Files (.h or .hpp) #
Header files are used to declare the interfaces of your functions and classes. They do not contain the actual code implementation, only the declarations.
Think of a header file as the binding contract between the compiler and any other code that wants to interact with yours.
Example: functions.h file
#ifndef FUNCTIONS_H
#define FUNCTIONS_H
// Function declarations
void printMessage();
int add(int a, int b);
#endif // FUNCTIONS_H
This file is simply declaring the existence of two functions:
printMessage()which takes no parameters and returns no dataadd()which takes two integer parameters:a&b, and returns an integer.
Dont worry about
#ifndef,#define, and#endifright now. This will be explained further below.
§2.2 Source Files (.cpp) #
Source files contain the actual implementation of the functions and classes declared in the header files.
Example: functions.cpp
#include <iostream>
#include "functions.h" // Include the header file
void printMessage() {
std::cout << "Hello from printMessage!" << std::endl;
}
int add(int a, int b) {
return a + b;
}
#include "functions.h": This directive tells the compiler to literally paste the header file’s content, this lets the compiler check the header declarations against the implementation, and will error if any definitions do not exactly match the header file.
e.g. printMessage(std::string input){} would cause a compiler error because it does not exactly match the header defined in the header file (which takes no parameters).
§2.3
The main.cpp File
#
The main.cpp file contains the main() function, which is the entry point of the program. It can also call functions defined in other source files.
Example: main.cpp
#include <iostream>
#include "functions.h" // Include the header file to use its functions
int main() {
printMessage(); //defined in "functions.h""
int result = add(3, 4);
std::cout << "The result of add(3, 4) is: " << result << std::endl;
return 0;
}
Output:
The result of add(3, 4) is: 7
§2.4 Compiling Multiple Files #
To compile a C++ project with multiple files, you must compile each source file and then link them together. This can be done by simply adding each .cpp file to your compiler command.
Command to Compile and Link multiple files:
g++ main.cpp functions.cpp -o myprogram
This command compiles two files: main.cpp and functions.cpp, and links them into an executable named myprogram.
Header files do not need to be added to the compiler command, because they are already included via the #include "functions.h" directives in the .cpp files.
§2.5
Header Guards and #pragma once
#
When using multiple files, some precautions are necessary to prevent compiler errors when files are included multiple times. This is a common occurrence, such as when different implementation files are using a common function library.
Header guards prevents multiple inclusions of the same header file during compilation.
Example:
/* top of file myheader.h */
#ifndef MYHEADER_H
#define MYHEADER_H
// function & object declarations go here
#endif // MYHEADER_H
/* bottom of file */
While a comment after
#endifis optional, it is a recommended convention to clarify which macro the directive is closing, in this caseMYHEADER_H.
Alternatively, you can use #pragma once as a simpler option:
/* top of file */
#pragma once
// function & object declarations go here
§2.6 Namespace Poisoning #
When using the using keyword, be cautious of namespace poisoning, which can lead to naming conflicts. Here is a simple example of this issue:
log.h: a header that thoughtlessly drags std::string into the global namespace:
// log.h
#pragma once
#include <string>
using namespace std; // POISONING OCCURS HERE
void log(const string& msg);
log.cpp: implementation:
// log.cpp
#include "log.h"
#include <iostream>
void log(const string& msg) {
std::cout << msg << '\n';
}
main.cpp: another implementation file that now suffers:
// main.cpp
#include <string>
#include "log.h" // transitively pulls in `using std;`
struct string { // user-defined type, conflicts with std::string
int dummy;
};
int main() {
string s; // ERROR: ambiguous - global ::string vs std::string
}
Compilation fails because the using namespace std in log.h placed std::string into the global namespace, colliding with the user-defined struct string.
Generally, always avoid the using keyword in any header files. The using keyword is acceptable to use in implementation files (.cpp files), though being careful to minimize dependencies is advisable.
It’s safer to always use specific namespace qualifiers, Like so:
using std::cout;
cout << "hi there";
// Instead of `using namespace std;`, which imports the entire std:: function set
// this only imports `cout`, which allows you to use it without always typing `std::cout`
Alternatively, you could wrap code in a specific namespace to limit its scope:
namespace std {
cout << "hi" << endl; // no need for std::cout or std::endl here
}
std::cout << "ho" << std::endl; // namespace is out of scope here, must use `std::`
§3 Operator Precedence #
Operator precedence defines the order in which operators are evaluated in an expression. For example, in the expression 3 + 4 * 2, the multiplication operator * has higher precedence than the addition operator +, so the multiplication is performed first, resulting in 3 + 8 = 11. You can always control the order of operations by utilizing parentheses: 5*(2-(3*(5-8))).
The order of operations always follows PEMDAS:
- Parentheses
() - Exponents
3^2 - Multiplication or Division
- Addition or Subraction
Example:
int result = 3 + 4 * 2; // result is 11
To explicitly define the order of operations, you can use parentheses:
int result = (3 + 4) * 2; // result is 14
§4 Arrays #
An array is a collection of elements of the same type stored in contiguous memory locations. Arrays are used to store multiple values in a single variable.
Example:
int numbers[5] = {1, 4, 2, 3, 5}; // Array of 5 integers
You can access array elements using the index, starting from 0.
int first = numbers[0]; // first = 1
int last = numbers[4]; // last = 5
int no = numbers[5]; // ERROR, array only contains 5 elements,
// array indexes start counting from 0.
§5 Functions and Prototypes #
Functions are blocks of code that perform a specific task and can be called from other parts of the program. Function prototypes declare a function’s return type, name, and parameters before its actual definition.
Example:
1// Function1 prototype
2int add(int a, int b);
3
4// Function2 prototype
5int addTwice(int a, int b);
6
7// Function2 definition
8// notice how we can use the 'add' function here, even though it has not yet been defined
9// if you comment out line 2, this would not compile.
10int addTwice(int a, int b) {
11 return add(a,b) + add(a,b);
12}
13
14// Function1 definition
15int add(int a, int b) {
16 return a + b;
17}
18
19// Calling the 'add' function
20int result = add(3, 4); // result is 7
Lines 1-5 could also be moved into a separate header (.h) file, this would be functionally identical to the above example.
§6 Default Parameters #
In C++, you can assign default values to function parameters. If a parameter is not provided when the function is called, the default value is used.
Example:
int multiply(int a, int b = 2) {
return a * b;
}
int result1 = multiply(3); // result1 is 6 (3 * 2)
int result2 = multiply(3, 4); // result2 is 12 (3 * 4)
§7 Casting and Promotion #
Casting is used to convert a variable from one type to another. Promotion refers to automatic type conversion by the compiler, such as when adding an int to a float, where the int is promoted to a float.
Example:
char c = 'A'; // c = int(65) (ASCII value of 'A')
std::cout << c << std::endl; // A
c = c + 1; // c = int(66) (ASCII value of 'B')
std::cout << c << std::endl; // B
bool b = false; // b = 0
b = b + 1; // b = 1
(b == true); // evaluates to true (1 == true)
Explicit casting can be done using the static_cast operator:
int i = 67;
char ch = static_cast<char>(i); // ch = 'C'
§8 Scope Rules #
Scope determines the visibility of variables and functions. In C++, scope can be global, local, or block-level.
Example:
int globalVar = 10; // Global scope [accessible everywhere]
void myFunction() {
int localVar = 20; // Local (function) scope
{
int blockVar = 30; // Block scope
}
// blockVar is not accessible here, out of scope
}
§9 Enums #
Enums are user-defined types consisting of named integral constants. They enhance code readability by assigning names to numerical values.
Example:
enum Color { RED, GREEN, BLUE };
Color favoriteColor = GREEN;
if ( favoriteColor == GREEN ) // evaluates to true
std::cout << "user prefers Green" << std::endl;
§10 Recursion #
Recursion is a process where a function calls itself. It can be used to solve problems that can be broken down into smaller, identical problems.
In this example, the factorial function calls itself until the input parameter n is less than or equal to 1:
int factorial(int n) {
if (n <= 1)
return 1;
else
return n * factorial(n - 1);
}
int result = factorial(5); // result is 120
Further Reading from the textbook: Chapter 14: Recursion
§11 Static Variables #
Static variables retain their value between function calls. They are initialized only once and maintain their state across multiple invocations of the function. Static variables have the same access rules as a regular variable, meaning a static variable declared within a function can only be accessed within the same function.
void incrementCounter() {
static int counter = 0;
counter++;
std::cout << "Counter: " << counter << std::endl;
}
incrementCounter(); // Counter: 1
incrementCounter(); // Counter: 2
Further Reading from the textbook: Chapter 6.11: Static Local Variables
§12 Questions #
- What is the purpose of a header file in C++?
- What line do you need to add to your
main.cppfile to use code that is defined in a file calledutilities.hand implemented inutilities.cpp?
- What is namespace poisoning? What problems can arise from namespace poisoning?
- What is the value of
xfrom the following statement:x = 10 - 2 * 3 + 6 / 2?
- How would you change the expression
10 - 2 * 3 + 5to ensure that subtraction occurs before multiplication? what is the numerical answer after making the change?
- Can a C++ array be resized without copying data?
- What other options are available in the C++ standard library when you need an expanding list of items? Name at least 2.
- What is the purpose of a function prototype in C++?
- Write a function prototype for a function called
multiply, that takes two integers as parameters, with the second parameter having a default value of1.
- What is the output of the following code snippet?
char ch = 'd'; ch = ch + 2; std::cout << ch << std::endl;
- What is the output of the following code? Explain why.
#include <iostream> int x = 10; void myFunction() { int x = 5; x++; std::cout << x << std::endl; } int main() { x++; myFunction(); x++; std::cout << x << std::endl; }
- Define an enum of type
Daythat represents the days of the week.
- Given the following enum, what will be the value of
BLUE?enum Color { RED, GREEN, BLUE }; std::cout << BLUE << std::endl; // what does this print?
- Are static variables always globally accessible? Explain why or why not?