Introduction to C

Basic Concepts in the C Programming Language

Updated: 03 September 2023

Based on this EdX Course

Basic Structure

A C++ program has a specific structure in terms of how it’s written

1
#include <iostream>
2
3
int main()
4
{
5
std::cout << "Hello World!";
6
return 0;
7
}

We can import libraries using the #include preprocessor directive, in this case iostream that allows input and output to things like the console window

Each program must have a method main in this case which returns an int

C++ makes use of curly braces for containing code blocks as well as semicolons to denote the end of a statement

The std:: part indicates that cout is part of the std namespace

Lastly, we can return values from a method using the return statement

Compilation

C++ code needs to be compiled. This is preprocessed > compiled > linked. The compiler checks the syntax, etc.

Once the compiler has completed its tasks the linker is involved in taking all the object files and linking them together into an executable

Language overview

Formatting

C++ is case sensitive and consits of a few different additional elements

  • Preprocessor directives
  • Using directives
  • Function headers have a return type, name, and params
  • Function bodies contain statements
  • Comments
  • A return statement at the end of functions
  • Curly braces to block things together

The compiler ignores whitespace for the most part with some exceptions such as if statements

Statements

C++ makes use of a variety of different statements, such as

  • Declarations
  • Assignments
  • Preprocessor directives
  • Comments
  • Function declarations
  • Executable statements (note the hello world above)

Types

C++ has lot of different data types built in - non-standard types start with an _

C++ is strongly typed,it has some built in types as well as user-defined types. We can also change types with by casting data from one type into another

Doing something like assigning an int to a float will lead to truncation

Assigning a non-bool to a bool will lead to pretty much anything besides 0

Variables

You can create a variable using the following syntacxes in which a variable is initialised and defined

1
int myVar = 0;
2
int myOtherVar{0};

Constants

Constants are named memory locations and their value does not change during runtime. They must be assigned values when initialised

1
const int i { 1 };
2
int const j { 2 };
3
const int k = 3;
4
int const l = 4;

Casting

We can cast variables from one data type to another. Sometimes these may lead to a loss of data and other times not

1
int myInt = 12;
2
long myLong;
3
myLong = myInt;

We can also explicitly cast variables using any of the following methods

1
long myLong = (long) myInt;
2
long myLong = long(myInt);
3
long myLong = static_cast<long>(myInt);

Beware the integer division, we can use something like the auto type which will automatically detect the type, but the data is still strongly typed

1
auto = 3 / 2; // int
2
auto j = 3.0 / 2; //double

Arrays

C++ provides support for complex data types, referred to as compound data types as they usually store more than one piece of data

Arrays in C++ need to be defined using the type and size of the array, or by initializing the array or some of its elements

1
int myArray[10];
2
int myArray[] = {0,1,2,3,4,5,6,7,8,9};
3
int myArray[10] = {1,2,3}; //initialize some values

We can access a value in an array using [] syntax

1
int newNum = myArray[2];

Arrays are 0-indexed as usual

In C++ an array is simply a pointer to a memory location and accessing an index that is not in the range of the array will return some random value - the next value in memory

Strings

Strings are an array of characters, a string must end with the \0 (null) character in order to tell the compiler where it ends

1
char myString[6] = {'H','e','l','l','o','\0'};

A char array must always be one character longer than necessary in order to store the \0

Practically though you can also define an array using a string literal

1
char myString[] = "Hello";

Using the above method the compiler will infer the length of the string, note that if explicitly defining the length you must leave space for the \0 character

1
char myString[6] = "12345";

There is also a string class which can be used, this will need to be referenced in the header file and can be used as follows

1
using namespace std;
2
3
string myString = "Hello";
4
std::string newString = "Bye";

Structures

Structs allow us to store more complex information as a compound data type. They are known as user-defined types

1
struct person
2
{
3
string name;
4
string surname;
5
int age;
6
};

We can then make use of the struct in a couple of different ways

1
person john = {"John", "Smith", 20};
2
person jenny;
3
4
jenny.name = "Jenny";
5
jenny.surname = "Smith";
6
jenny.age = 23;
7
8
cout << john.name << endl;

As seen above, properties can be accessed or assigned using the dot notation

Unions

Unions are like structs but can only data in one of it’s fields at a time

1
union particle
2
{
3
int position;
4
int speed;
5
};
6
7
particle myParticle;
8
myParticle.position = 5;
9
myParticle.speed = 10; // the position no longer has a value

Unions are useful for working with memory-limited devices

Enumerables

Enums are a means of creating symbolic constants, by default these values will start at 0 but we can define them to start at a different number

1
enum Day {Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday};

Sunday == 0, Monday == 1, etc.

Or we can start at 1 and have each day be the human number

1
enum Day {Sunday = 1, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday};
2
3
Day sadDay = Monday; // 2

Operators

The available mathematical operators are as follows

1
+ - * / % =+ -= ++ -- *= /=
2
== != > < >= <= && || !

Conditional Operators

COnditional Operators in C++ (aka Ternary Operators) take three values, the first of which is a condition, and the second or third are evaluated based on the result of the condition

1
int i = 1, j = 2;
2
cout << ( i > j ? i : j ) << " is greater." << endl;

Flow Control

If Statements

C++ makes use of boolean operators in order to build if statements

1
char test = 'y';
2
if (test == 'y')
3
{
4
out << "test is y" << endl;
5
}

If there is only a single statement we can leave out the curly brackets

1
char test = 'y'
2
if (test == 'y')
3
out << "test is y" << endl;

We can also use else if and else

1
char test = 'y';
2
if (test == 'y')
3
{
4
out << "test is y" << endl;
5
}
6
else if (test == 'n')
7
{
8
out << "test is n" << endl;
9
}
10
else
11
{
12
out << "test is something else" << endl;
13
}

Switch Statements

We can use these when the case of complex if-else statements

1
char test = 'y';
2
switch (test)
3
{
4
case 'y':
5
out << "test is y" << endl;
6
break;
7
case 'N':
8
out << "test is N" << endl;
9
break
10
case 'n':
11
out << "test is n" << endl;
12
break;
13
default:
14
out << "test is something else" << endl;
15
break
16
}

Switch operators support intrisic data types and enums

For Loops

For loops look like this:

1
int oldNumbers[] = {1,2,3,4,5};
2
for (int i = 0; i < 5; i++)
3
{
4
int currentNumber = oldNumbers[i]
5
}

The loop is made of the general structure of:

1
for (initialization; continueCondition; iterator)
2
{
3
// stuff to do
4
}

While Loops

While loops follow the traditional C-type syntax

1
while (condition)
2
{
3
// do some stuff
4
}

For example:

1
int i {0};
2
while (i < 5)
3
{
4
i ++;
5
// do stuff
6
}

If the condition is not initally met, the loop will not run

Do-While Loops

A do-while loop is like a while loop but the condition is checked at the end of the loop, and will hence always run at least once

1
do
2
{
3
// do some stuff
4
} while (condition);

Note the semicolon at the end

Functions

Functions are defined by Name, Return Type, and Arguments. Functions with the same Name and Return type but different numbers arguments are allowed, the compiler will figure out which one you are trying to use based on the number of arguments

The compiler must know about a function before it can be called

1
int Sum(int x, int y)
2
{
3
int sum {x + y}
4
return sum
5
}

Prototypes

A complete function signature/prototype consists of the following

  • Storage class
  • Return type
  • Name
  • Parameters

Function prototypes need to be defined in a header file, Header files are imported into source code files so that the compiler can ensure proper use of functions, etc.

The prototype does not consist of the function implementation code

A function prorotype is defined as follows

1
int Sum(int x, int y);

By default data is passed to functions by value and not by reference

Inline Functions

Inline functions are functions that are are essentially pieces of code that the compiler will inject into the place where it is being called instead of making a function call

This can be used to reduce some of the overhead that would be associated with using a normal function

These are better suited for small functions that are used frequeltly and make use of the inline keyword

1
inline void swap(int & a, int & b)
2
{
3
int temp = a;
4
a = b;
5
b = temp;
6
}

Storage Classes and Scope

“A storage class in the context of C++ variable declarations is a type specifier that governs the lifetime, linkage, and memory location of objects”

This means that it governs how long an object remains in memory, in what scopes it is visible and whether it should be located in a stack or heap

Some keywords that apply to storage classes are:

  • static
  • extern
  • thread_local

If we do not state the function prototype on the file we try to use it we will get a compiler error, if we state the prototype but that function cannot be found we will get a linker error

The reason for this is because each individual file is compiled separately

In order to avoid declaring prototypes in each file, you can create a header file and define shared prototypes there, and include the header file, this can be done simply as follows

Math.cpp

1
int AddTwo(int i)
2
{
3
return i + 2;
4
}

Utilities.h

1
int AddTwo(int i);

Main.cpp

1
#include "Utilities.h"
2
3
int main()
4
{
5
int i { addTwo(1) };
6
return 0;
7
}

Classes

Definition

Classes are definitions for custom types and defines the behaviour and characteristics of a type

We can define a Rectangle with the class keyword

1
class Rectangle
2
{
3
public:
4
int _width;
5
int _height;
6
};

Class definitions must end with a ;

Members can be accessed with dot notation

Initialization

We can create a new instance of a class using a few different methods

1
void main()
2
{
3
Rectangle aShape; // Uninitialized - don't do this
4
5
myShape._width = 5;
6
myShape._height = 3;
7
8
Rectangle defaultShape{} // Default initialized
9
10
Rectangle myRectangle {0, 0} // Specific initalized
11
}

Uninitialized values will have junk data, don’t do this

Encapsulation

Encapsulation is used to describe accesibility of class members, this is used to restrict the way in which class data can be manipulated

Functions in a class have access to the instance of the class this which is a pointer. this is used to remove ambiguity betwen member variables and is not always necessary

We cannot directly access private members from outside of a class

A class can be defined in a header file or have some aspects of it’s functionality (or all) be placed into separate .cpp files

Rectangle.h

1
class Rectangle
2
{
3
public:
4
int GetWidth() { return _width; }
5
int GetHeight() { return _height; }
6
7
private:
8
int _width;
9
int _height;
10
};

Constructors

Muliple constructors can be created with different parameters, the compiler will figure out which one to use for a specific instance based on the input arguments, we can see a lot of different examples here

1
class Rectangle
2
{
3
public:
4
Rectangle() : _width{ 1 }, _height{ 1 } {}
5
Rectangle(int width, int height) : _width{ width }, _height{ height } {}
6
7
int GetWidth() { return _width; }
8
int GetHeight() { return _height; }
9
10
private:
11
int _width;
12
int _height;
13
};

If a default constructor is not define the compuler will provide an implicit inline instance

Additionally we can add default values for the properties of a class

1
class Rectangle
2
{
3
public:
4
Rectangle() : _width{ 1 }, _height{ 1 } {}
5
Rectangle(int width, int height) : _width{ width }, _height{ height } {}
6
7
int GetWidth() { return _width; }
8
int GetHeight() { return _height; }
9
10
private:
11
int _width{ 1 };
12
int _height{ 1 };
13
};

We can also remove some parts of functionality away from our class definition into a .cpp file

Rectangle.h

1
class Rectangle
2
{
3
public:
4
Rectangle();
5
Rectangle(int width, int height);
6
7
int GetWidth();
8
int GetHeight();
9
10
private:
11
int _width{ 1 };
12
int _height{ 1 };
13
};

Rectangle.cpp

1
#include "Rectangle.h"
2
3
Rectangle::Rectangle() : _width{ 1 }, _height{ 1 } {}
4
Rectangle::Rectangle(int width, int height) : _width{ width }, _height{ height } {}
5
6
int Rectangle::GetWidth() { return _width; }
7
int Rectangle::GetHeight() { return _height; }

Immutable Objects

We can create const objects but we need to also explicitly define member functions that will not modify the object as const as well

1
int Rectangle::GetArea() const
2
{
3
//implementation
4
}

And then create Rectangles and use the function as normal

1
int main()
2
{
3
const Rectangle myRectangle{};
4
const area = myRectangle.GetArea();
5
}