C++ Succinctly
1. Introduction
1.1 What is C++?
C++ Succinctly was written to help professional C# developers learn modern C++ programming. The aim of this book is to leverage your existing C# knowledge in order to expand your skills. Whether you need to use C++ in an upcoming project, or simply want to learn a new language (or reacquaint yourself with it), this book will help you learn all of the fundamental pieces of C++ so you can understand projects and samples written in C++ and begin writing your own C++ programs.
As with any large subject, there simply wasn't room to cover everything (an example being the new atomic features added in C++11), and others might have decided to order the topics differently. I'm thinking particularly of pointers, a topic I cover in depth only further into the book. They are important, so some might have chosen to cover them earlier.
I hope I have succeeded. I consulted the C++11 language specification frequently while writing this, and I also read everything from StackOverflow posts, to MSDN docs, to GCC docs, and beyond. There are areas where I intentionally simplified things. As you continue to expand your knowledge of C++, you will undoubtedly reach issues where you need to have a more comprehensive understanding in order to accomplish your goal or eliminate a bug. If reading this book imparts enough knowledge—and a good-enough feel for the language that you are able to recognize, diagnose, and resolve those issues—then I will be content that I have succeeded in my goals. Welcome to C++!
1.2 Fundamental Types
The smallest fundamental unit in C++ is char, which only needs to be at least large enough to hold the 96 basic characters that the C++ standard specifies, plus any other characters in the implementation's basic character set. In theory, some implementation of C++ could define a char as 7 bits or 16 bits … almost anything is possible.
But in practice you don't need to worry too much about a char being anything other than 8 bits (the equivalent of the byte or sbyte type in C#), which is its size in Visual C++. In C++, char, signed char, and unsigned char are three distinct types. All three are required to take up the same amount of storage in memory. So a char in practice is either signed or unsigned. Whether it is signed or unsigned is implementation defined. In Visual C++ the char type is, by default, signed. But you can use a compiler switch to have it treated as unsigned instead. In GCC, whether it is signed or unsigned depends on which CPU architecture you are targeting.
The signed integer types, in size order from smallest to largest, are:
- signed char
- short int
- int
- long int
- long long int
The only guarantee of the size of each of these integer types is that each one is at least as large as the next smallest integer type. In Visual C++, an int and a long int are both 32-bit integers. It is only the long long int that is a 64-bit integer.
If you need to ensure that you are using specific sizes, you can include the C++ Standard Library header file cstdint (e.g., #include <cstdint>), which defines, among other things, the types:
- int8_t
- int16_t
- int32_t
- int64_t
- uint8_t
- uint16_t
- uint32_t
- uint64_t
These types have their use, but you will find that most APIs do not use them; instead, they use the fundamental types directly. This can make your programming confusing, as you constantly need to check the underlying fundamental type to ensure you do not end up with unintended truncation or expansion.
1.3 Enumerations
Enumerations are fairly similar to each other in C++ and C#. C++ has two types of enums: scoped and un-scoped. A scoped enumeration is defined as either an enum class or an enum struct. There is no difference between the two. An un-scoped enumeration is defined as a plain enum. Let's look at a sample:
#include <iostream>
#include <ostream>
#include <string>
#include "../pchar.h"
enum class Color
{
Red,
Orange,
Yellow,
Blue,
Indigo,
Violet
};
// You can specify any underlying integral type you want, provided it fits.
enum Flavor : unsigned short int
{
Vanilla,
Chocolate,
Strawberry,
Mint,
};
int _pmain(int /*argc*/, _pchar* /*argv*/[])
{
Flavor f = Vanilla;
f = Mint; // This is legal since the Flavor enum is an un-scoped enum.
Color c = Color::Orange;
//c = Orange; // This is illegal since the Color enum is a scoped enum.
std::wstring flavor;
std::wstring color;
switch (c)
{
case Color::Red:
color = L"Red";
break;
case Color::Orange:
color = L"Orange";
break;
case Color::Yellow:
color = L"Yellow";
break;
case Color::Blue:
color = L"Blue";
break;
case Color::Indigo:
color = L"Indigo";
break;
case Color::Violet:
color = L"Violet";
break;
default:
color = L"Unknown";
break;
}
switch (f)
{
case Vanilla:
flavor = L"Vanilla";
break;
case Chocolate:
flavor = L"Chocolate";
break;
case Strawberry:
flavor = L"Strawberry";
break;
case Mint:
flavor = L"Mint";
break;
default:
break;
}
std::wcout << L"Flavor is " << flavor.c_str() << L" (" << f <<
L"). Color is " << color.c_str() << L" (" <<
static_cast<int>(c) << L")." << std::endl <<
L"The size of Flavor is " << sizeof(Flavor) <<
L"." << std::endl <<
L"The size of Color is " << sizeof(Color) <<
L"." << std::endl;
return 0;
}
1.4 typedef
What is typedef?
The typedef keyword in C++ is used to create an alias (alternative name) for an existing data type.
It helps improve code readability and makes complex type definitions more manageable.
Basic Syntax of typedef:
typedef existing_type new_name;
existing_type: The original data type.
new_name: The alias (alternative name) for that data type.
Example 1 - Using typedef for Simple Data Types
Creating an Alias for unsigned int:
#include <iostream>
using namespace std;
typedef unsigned int uint; // Alias for unsigned int
int main() {
uint age = 25; // Now we can use 'uint' instead of 'unsigned int'
cout << "Age: " << age << endl;
return 0;
}
Example 2 - Using typedef for Structs
In C++, structures (struct) require the struct keyword when declaring variables. typedef simplifies this.
#include <iostream>
using namespace std;
typedef struct {
string name;
int age;
} Person; // 'Person' is now an alias for this struct
int main() {
Person p1; // No need to write 'struct' before declaration
p1.name = "John";
p1.age = 30;
cout << "Name: " << p1.name << ", Age: " << p1.age << endl;
return 0;
}
1.5 Key Takeaways
The `typedef` keyword in C++ is used to create aliases for existing data types. It helps simplify complex type definitions and improves readability. The following table summarizes different uses of `typedef` in C++:
| Type | Definition | Example |
|---|---|---|
| Basic Type Alias | Create a new name for an existing type. | typedef unsigned int uint; |
| Struct Alias | Simplifies struct declarations. | typedef struct { string name; int age; } Person; |
| Function Pointer | Gives a name to function pointers. | typedef void (*FuncPtr)(int); |
| Array Type | Creates an alias for array types. | typedef int Marks[5]; |
| Template Support | Used inside templates. | typedef T value_type; |
| Modern Alternative | `using` (C++11 and later) is preferred. | using uint = unsigned int; |
In modern C++, the `using` keyword is preferred over `typedef` because it provides better readability and supports templates. However, `typedef` is still useful in legacy code.
2. Basic Concepts
2.1 Basic Structure of a C++ Program
Every C++ program consists of the following components:
- Preprocessor Directives (#include <iostream>) → To include standard libraries.
- Main Function (main()) → The starting point of the program.
- Statements (cout << "Hello, World!";) → Instructions executed by the compiler.
- Return Statement (return 0;) → Ends the program.
Example: Writing a Basic C++ Program
#include <iostream> // Include the input-output stream library
using namespace std; // Allows using standard namespace (optional but useful)
int main() {
cout << "Hello, World!"; // Print to the console
return 0; // Indicate that the program executed successfully
}
Explanation:
- #include <iostream> → Includes the iostream library for input and output.
- using namespace std; → Allows direct use of standard functions like cout.
- int main() → The main function where execution begins.
- cout << "Hello, World!"; → Outputs text to the screen.
- return 0; → Indicates the program ended successfully.
2.2 Variables and Data Types in C++
Variables are containers for storing data. C++ provides several data types to store different kinds of values.
Common Data Types in C++:
| Data Type | Size | Example | Description |
|---|---|---|---|
int |
4 bytes | int age = 25; |
Stores whole numbers (e.g., 10, -5) |
float |
4 bytes | float price = 19.99; |
Stores decimal numbers with single precision |
double |
8 bytes | double pi = 3.14159; |
Stores high-precision floating-point numbers |
char |
1 byte | char grade = 'A'; |
Stores single characters (e.g., 'A', '9') |
bool |
1 byte | bool isStudent = true; |
Stores true/false values |
string |
Varies | string name = "John"; |
Stores text strings (requires #include <string>) |
Understanding these data types is crucial for efficient memory management and correct program execution in C++.
#include <iostream>
using namespace std;
int main() {
int age = 25; // Integer
double height = 5.9; // Decimal number
char grade = 'A'; // Single character
bool isPassed = true; // Boolean value
cout << "Age: " << age << endl;
cout << "Height: " << height << endl;
cout << "Grade: " << grade << endl;
cout << "Passed: " << isPassed << endl;
return 0;
}
2.3 Operators in C++
C++ includes arithmetic, comparison, and logical operators for performing operations on variables.
| Operator | Example | Description |
|---|---|---|
+ |
x + y |
Addition |
- |
x - y |
Subtraction |
* |
x * y |
Multiplication |
/ |
x / y |
Division |
% |
x % y |
Modulus (Remainder) |
== |
x == y |
Equal to |
!= |
x != y |
Not equal to |
&& |
x && y |
Logical AND |
|| |
x || y |
Logical OR |
Example: Using Operators in C++
#include <iostream>
using namespace std;
int main() {
int a = 10, b = 5;
cout << "Sum: " << a + b << endl;
cout << "Difference: " << a - b << endl;
cout << "Multiplication: " << a * b << endl;
cout << "Division: " << a / b << endl;
cout << "Modulus: " << a % b << endl;
return 0;
}
2.4 Conditional Statements in C++
C++ provides if, else, and switch statements to control the flow of execution.
1. if Statement
Executes a block of code if the condition is true.
int age = 18;
if (age >= 18) {
cout << "You are an adult.";
}
2. if-else Statement
Executes one block if true, another if false.
int num = 10;
if (num % 2 == 0) {
cout << "Even number";
} else {
cout << "Odd number";
}
3. switch Statement
Used when you have multiple conditions for a single variable.
char grade = 'B';
switch (grade) {
case 'A': cout << "Excellent"; break;
case 'B': cout << "Good"; break;
case 'C': cout << "Fair"; break;
default: cout << "Invalid Grade";
}
4. Loops in C++
Loops are used to execute a block of code multiple times.
4.1 for Loop
Executes code a fixed number of times.
for (int i = 1; i <= 5; i++) {
cout << "Iteration: " << i << endl;
}
4.2 while Loop
Executes code while a condition is true.
int i = 1;
while (i <= 5) {
cout << "Iteration: " << i << endl;
i++;
}
4.3 do-while Loop
Executes code at least once, then repeats while the condition is true.
int i = 1;
do {
cout << "Iteration: " << i << endl;
i++;
} while (i <= 5);
5. Arrays in C++
Arrays store multiple values of the same type.
int numbers[3] = {10, 20, 30};
cout << "First number: " << numbers[0];
3. Functions
3.1 Functions
There are two types of functions in C++: stand-alone functions and member functions. The main difference between them is that a member function belongs to a class, structure, or union, whereas a stand-alone function does not.
#include <iostream>
using namespace std;
void greet(string name) {
cout << "Hello, " << name << "!" << endl;
}
int main() {
greet("Alice");
return 0;
}
3.2 Function Parameters
You can pass parameters by value, reference, or pointer...
#include <iostream>
using namespace std;
void greet(string name) {
cout << "Hello, " << name << "!" << endl;
}
int main() {
greet("Alice");
return 0;
}
3.3 Function Calls
Sample program:
// Calculating powers with the standard function pow()
#include <iostream> // Declaration of cout
#include <cmath> // Prototype of pow()
using namespace std;
int main() {
double x = 2.5, y;
// By means of a prototype, the compiler generates
// the correct call or an error message!
// Computes x raised to the power 3:
// y = pow("x", 3.0); // Error! String is not a number
// y = pow(x + 3.0); // Error! Just one argument
y = pow(x, 3.0); // ok!
y = pow(x, 3); // ok! The compiler converts the int value 3 to double.
cout << "2.5 raised to 3 yields: " << y << endl;
// Calculating with pow() is possible:
cout << "2 + (5 raised to the power 2.5) yields: " << 2.0 + pow(5.0, x) << endl;
return 0;
}
Screen output:
2.5 raised to the power 3 yields: 15.625
2 + (5 raised to the power 2.5) yields: 57.9017
3.4 Function Calls
A function call is an expression of the same type as the function and whose value corresponds to the return value. The return value is commonly passed to a suitable variable.
Example: y = pow(x, 3.0);
In this example the function pow() is first called using the arguments x and 3.0, and the result, the power x³, is assigned to y. As the function call represents a value, other operations are also possible. Thus, the function pow() can be used to perform calculations for double values.
Example: cout << 2.0 + pow(5.0, x);
This expression first adds the number 2.0 to the return value of pow(5.0, x), then outputs the result using cout. Any expression can be passed to a function as an argument, such as a constant or an arithmetical expression. However, it is important that the types of the arguments correspond to those expected by the function. The compiler refers to the prototype to check that the function has been called correctly. If the argument type does not match exactly to the type defined in the prototype, the compiler performs type conversion, if possible.
3.5 Types of Functions
Sample program:
// Outputs three random numbers
#include <iostream> // Declaration of cin and cout
#include <cstdlib> // Prototypes of srand(), rand()
using namespace std;
int main() {
unsigned int seed;
int z1, z2, z3;
cout << " --- Random Numbers --- \n" << endl;
cout << "To initialize the random number generator, "
<< "\n please enter an integer value: ";
cin >> seed; // Input an integer
srand(seed); // and use it as argument for a new sequence of random numbers.
z1 = rand(); // Compute three random numbers.
z2 = rand();
z3 = rand();
cout << "\nThree random numbers: "
<< z1 << " " << z2 << " " << z3 << endl;
return 0;
}
Note: The statement cin >> seed; reads an integer from the keyboard, because seed is of the unsigned int type.
Functions without Return Value: You can also write functions that perform a certain action but do not return a value to the function that called them. The type void is available for functions of this type, which are also referred to as procedures in other programming languages.
3.6 Types of Functions in C++
Function Without Parameters & Return: A function that doesn't take input or return anything.
#include <iostream>
using namespace std;
void greet() {
cout << "Hello, welcome to C++ functions!" << endl;
}
int main() {
greet(); // Function call
return 0;
}
- void greet() → void means no return value.
- The function prints a message but does not return anything.
- greet(); calls the function inside main()
3.7 Function With Return Value (Returns Output)
A function that takes input and returns a value.
#include <iostream>
using namespace std;
int add(int a, int b) {
return a + b;
}
int main() {
int sum = add(5, 3);
cout << "Sum: " << sum << endl; // Output: Sum: 8
return 0;
}
- int add(int a, int b) → Takes two int parameters.
- return a + b; → Sends back the result.
- int sum = add(5, 3); → Calls add(), stores the result in sum.
3.8 Function With Default Parameters
A function that uses default values if no argument is provided.
#include <iostream>
using namespace std;
void displayInfo(string name = "Guest", int age = 18) {
cout << "Name: " << name << ", Age: " << age << endl;
}
int main() {
displayInfo(); // Output: Name: Guest, Age: 18
displayInfo("Alice", 25); // Output: Name: Alice, Age: 25
return 0;
}
- If no values are given, "Guest" and 18 are used as defaults.
- Otherwise, provided values override defaults.
3.9 Function Overloading (Same Name, Different Parameters)
C++ allows multiple functions with the same name but different parameter types or numbers.
#include <iostream>
using namespace std;
void display(int num) {
cout << "Integer: " << num << endl;
}
void display(double num) {
cout << "Double: " << num << endl;
}
int main() {
display(10); // Calls the integer version
display(5.5); // Calls the double version
return 0;
}
Explanation:
- Two display() functions exist but accept different data types.
- C++ automatically picks the correct one based on the argument type.
3.10 Pass By Value vs By Reference
Pass By Value (Creates a copy):
The function gets a copy of the argument. Changes don't affect the original.
#include <iostream>
using namespace std;
void changeValue(int x) {
x = 100; // only changes the local copy
}
int main() {
int num = 10;
changeValue(num);
cout << "Value: " << num << endl; // Output: Value: 10
return 0;
}
Explanation: The function modifies a copy of num, so the original remains unchanged.
Pass By Reference (modifies original):
Uses & (reference operator) to modify the original value.
#include <iostream>
using namespace std;
void changeValue(int &x) {
x = 100; // changes the original value
}
int main() {
int num = 10;
changeValue(num);
cout << "Value: " << num << endl; // Output: Value: 100
return 0;
}
Explanation: The & symbol makes x a reference, meaning changes affect the original variable.