Introduction to C
Basic Concepts in the C Programming Language
Basic Structure
A C++ program has a specific structure in terms of how it's written
#include <iostream>
int main()
{
std::cout << "Hello World!";
return 0;
}
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
int myVar = 0;
int myOtherVar{0};
Constants
Constants are named memory locations and their value does not change during runtime. They must be assigned values when initialised
const int i { 1 };
int const j { 2 };
const int k = 3;
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
int myInt = 12;
long myLong;
myLong = myInt;
We can also explicitly cast variables using any of the following methods
long myLong = (long) myInt;
long myLong = long(myInt);
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
auto = 3 / 2; // int
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
int myArray[10];
int myArray[] = {0,1,2,3,4,5,6,7,8,9};
int myArray[10] = {1,2,3}; //initialize some values
We can access a value in an array using []
syntax
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
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
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
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
using namespace std;
string myString = "Hello";
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
struct person
{
string name;
string surname;
int age;
};
We can then make use of the struct
in a couple of different ways
person john = {"John", "Smith", 20};
person jenny;
jenny.name = "Jenny";
jenny.surname = "Smith";
jenny.age = 23;
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
union particle
{
int position;
int speed;
};
particle myParticle;
myParticle.position = 5;
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
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
enum Day {Sunday = 1, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday};
Day sadDay = Monday; // 2
Operators
The available mathematical operators are as follows
+ - * / % =+ -= ++ -- *= /=
== != > < >= <= && || !
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
int i = 1, j = 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
char test = 'y';
if (test == 'y')
{
out << "test is y" << endl;
}
If there is only a single statement we can leave out the curly brackets
char test = 'y'
if (test == 'y')
out << "test is y" << endl;
We can also use else if
and else
char test = 'y';
if (test == 'y')
{
out << "test is y" << endl;
}
else if (test == 'n')
{
out << "test is n" << endl;
}
else
{
out << "test is something else" << endl;
}
Switch Statements
We can use these when the case of complex if-else
statements
char test = 'y';
switch (test)
{
case 'y':
out << "test is y" << endl;
break;
case 'N':
out << "test is N" << endl;
break
case 'n':
out << "test is n" << endl;
break;
default:
out << "test is something else" << endl;
break
}
Switch operators support intrisic
data types and enums
For Loops
For loops look like this:
int oldNumbers[] = {1,2,3,4,5};
for (int i = 0; i < 5; i++)
{
int currentNumber = oldNumbers[i]
}
The loop is made of the general structure of:
for (initialization; continueCondition; iterator)
{
// stuff to do
}
While Loops
While loops follow the traditional C-type
syntax
while (condition)
{
// do some stuff
}
For example:
int i {0};
while (i < 5)
{
i ++;
// do stuff
}
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
do
{
// do some stuff
} 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
int Sum(int x, int y)
{
int sum {x + y}
return sum
}
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
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
inline void swap(int & a, int & b)
{
int temp = a;
a = b;
b = temp;
}
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
int AddTwo(int i)
{
return i + 2;
}
Utilities.h
int AddTwo(int i);
Main.cpp
#include "Utilities.h"
int main()
{
int i { addTwo(1) };
return 0;
}
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
class Rectangle
{
public:
int _width;
int _height;
};
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
void main()
{
Rectangle aShape; // Uninitialized - don't do this
myShape._width = 5;
myShape._height = 3;
Rectangle defaultShape{} // Default initialized
Rectangle myRectangle {0, 0} // Specific initalized
}
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
class Rectangle
{
public:
int GetWidth() { return _width; }
int GetHeight() { return _height; }
private:
int _width;
int _height;
};
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
class Rectangle
{
public:
Rectangle() : _width{ 1 }, _height{ 1 } {}
Rectangle(int width, int height) : _width{ width }, _height{ height } {}
int GetWidth() { return _width; }
int GetHeight() { return _height; }
private:
int _width;
int _height;
};
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
class Rectangle
{
public:
Rectangle() : _width{ 1 }, _height{ 1 } {}
Rectangle(int width, int height) : _width{ width }, _height{ height } {}
int GetWidth() { return _width; }
int GetHeight() { return _height; }
private:
int _width{ 1 };
int _height{ 1 };
};
We can also remove some parts of functionality away from our class definition into a .cpp
file
Rectangle.h
class Rectangle
{
public:
Rectangle();
Rectangle(int width, int height);
int GetWidth();
int GetHeight();
private:
int _width{ 1 };
int _height{ 1 };
};
Rectangle.cpp
#include "Rectangle.h"
Rectangle::Rectangle() : _width{ 1 }, _height{ 1 } {}
Rectangle::Rectangle(int width, int height) : _width{ width }, _height{ height } {}
int Rectangle::GetWidth() { return _width; }
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
int Rectangle::GetArea() const
{
//implementation
}
And then create Rectangles and use the function as normal
int main()
{
const Rectangle myRectangle{};
const area = myRectangle.GetArea();
}