You are on page 1of 18

CMake

CMake allows us to configure the build process in a way independent from the compiler or
the operating system. In Linux, it generates makefiles, in Windows (specifically Visual
Studio workspaces) it generates solution files (.sln) and project files (.vcxproj). Regardless
of the compiler or operating system, the generated files can then be compiled and run
without any further configuration.

CMake contains in-built variables and functions, for example:

# sets variable 'msg', to contain the string "hello world"


set(msg "hello world")

# prints out the contents of variable 'msg'


# to access the contents of a variable, the ${} operator must be used
message(${msg})

# for in-built varibles, such as 'CMAKE_HOME_DIRECTORY' the same rule applies


# prints the root folder of the cmake project (first encountered
CMakeLists.txt file)
message(${CMAKE_HOME_DIRECTORY})

Example
For our example, we'll be generating a simple project that prints out "hello world". When
starting, CMake will look for a 'CMakeLists.txt' file. All other configurations are done based
on that file.

Folder structure:

cmake_example
| CMakeLists.txt
|
++++include
| print_hello.h
|
++++src
main.cpp
say_hello.cpp

Contents of 'CMakeLists.txt'

# set minimum required cmake version

cmake_minimum_required(VERSION 3.0)
# create variable containing project name
set(PROJECT_NAME "CMake_Example")
# set project name
project(${PROJECT_NAME})
message("building ${PROJECT_NAME} ...")

# specify the folder(s) where the compiler should look for include files
include_directories(include) #in our case the 'include' folder present in the
root folder

# we can manually add files to the project


set(HEADERS include/say_hello.h) # this is relative to the root of the project

# or we can use file(GLOB ...) for wildcard additon (add any file ending with
'.h')
file(GLOB HEADER_FILES "include/*.h")

file(GLOB SRC_FILES "src/*.cpp")

# specify where to generate the executable file


set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_HOME_DIRECTORY}/bin)

# specify that we are building an executable, and what sources to use


add_executable(${PROJECT_NAME} ${HEADER_FILES} ${SRC_FILES})

The full contents of this project are available to be in the 'cmake_example.7z' archive.

Statements
Represent each individual instruction given to the program.
They always end with a semicolon (;).
Are executed sequentially .

During its process, a program may repeat segments of code, or take decisions and
bifurcate. For that purpose, C++ provides flow control statements that serve to specify
what has to be done by our program, when, and under which circumstances.

Flow Control
IF

The if keyword is used to execute a statement or a block of code if the expression inside
the brackets evaluates to true.If the expression evaluates to false the statement or block of
code is simply ignored.
void main()
{
// declare local variable
int a = 10;
// check the boolean condition
if(a == 10)
{
printf("a == %d",a);
}
}

In C/C++ any non-zero expression evaluates to True.

// evaluates to true
int n = 5;
if(n)
{
printf("this works")
}
// evaluates to true
n = -5;
if(n)
{
printf("this also works")
}

// evaluates to false
n = 0;
if(n)
{
printf("this code will not be run")
}

Switch

A switch statement selects which block of code to execute based on the value of an
integral expression.
The switch statement compares the value of “selector” to each integral value. If no
match is found, the default case is executed.
Every case needs to end with a break keyword. This causes the program to jump to
the end of the switch body.
If no break keyword is encountered the program continues to the next case until the
of the switch body.

Looping

While, do while and for are used for looping.


While evaluates the condition before executing the code

Do-while executes the code and then evaluates the condition


All of the steps in a for statement(initialization, evaluation and incrementing) are
optional.

Infinite Loops

A loop is said to be an infinite loop if the control enters but never leaves the body of the
loop. This happens when the test condition of the loop never evaluates to false.

Jump Statements

for (int i = 0; i >= 0; )


{
/* body of the loop where i is not changed*/

}
while (true)
{
/* body of the loop */
}

for (;;)
{
/* body of the loop */
}

Pointers
A pointer is a type of variable which can store the address of another object or a function.

A pointer is declared much like any other variable, except an asterisk (*) is placed between
the type and the name of the variable to denote it is a pointer.

The address-of or reference operator denoted by an ampersand (&) gives the address of a
given variable which can be placed in a pointer of appropriate type.
int value = 1;
pointer = &value;

The above code copies the address of the variable value to the pointer p.

To use the value pointed at by the pointer, we have to dereference it, with the * operator.

printf("Value of pointed to integer: %d\n", *pointer);

Pointers are also re-assignable. This means that a pointer pointing to an object can later
be used to point to another object of the same type.

int value2 = 10;


pointer = &value2;
printf("Value from pointer: %d\n", *pointer);

Arrays
An array is a series of elements of the same type placed in contiguous memory locations
that can be individually referenced by adding an index to a unique identifier.

When declaring an uninitialized array inside a local scope, the values contained by
the array are undetermined.
void main()
{
// declare array
int integerList[5];
// iterate through array
for(int i=0; i< 5; ++i)
{
// will print out junk values
printf("%d", integerList[i]);
}
}

When declaring a global or static array, the values contained by the array will be
automatically initialized to 0.

// declare array
int integerList[5];
void main()
{
// iterate through array
for(int i=0; i< 5; ++i)
{
// will print out '0'
printf("%d", integerList[i]);
}
}

C/C++ does not do boundary checking when using arrays. Be careful when iterating over
arrays to not go out of bounds or risk undefined behavior.

// declare array size


const int size=15;
// declare array
int overflowTest[size];
// purposefully go out bounds
for(int i=0;i<19;i++)
{
printf("%d", i);
}
// the above code generates an undefined behavior, meaning sometimes it will
work
// and sometimes it will crash the program
// or gain sentience and subjugate humanity. We just don't know.

Multi-Dimensional Arrays
Multidimensional arrays can be described as "arrays of arrays". For example, a bi-
dimensional array can be imagined as a bi-dimensional table made of elements, all of
them of a same uniform data type.

int integerList[5][5];

for(int i =0;i<5;++i)
{
for(int j=0;j<5;++j)
{
integerList[i][j] = i+j;
}
}

Character Arrays (Strings)


In C, a string is a sequence of characters that is terminated by a null character '\0'.

This means that a C-string with a content of "abc" will have four characters 'a', 'b', 'c' and
'\0'.

We can create strings using several methods. For instance, we can declare a char * and
initialize it to point to the first character of a string:

char * string = "hello world";

When initializing with char * , the string is a pointer to the first element of the array, the
character 'h', the string is allocated in read-only memory, any attempts to modify it will
cause undefined behavior.

To create a modifiable string, you can declare a character array and initialize its contents
using a string literal, like so:

char string_arr[] = "hello world";


This is equivalent to the following:

char modifiable_string[] = {'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l',
'd', '\0'};

Since the second version uses brace-enclosed initializer, the string is not automatically
null-terminated unless a '\0' character is included explicitly in the character array usually
as its last element.

String Tokenization
The function strtok breaks a string into a smaller strings, or tokens, using a set of
delimiters.

#include <stdio.h>
#include <string.h>
int main(void)
{
int toknum = 0;
char src[] = "Hello,, world!";
const char delimiters[] = ", !";
char *token = strtok(src, delimiters);
while (token != NULL)
{
printf("%d: [%s]\n", ++toknum, token);
token = strtok(NULL, delimiters);
}
/* source is now "Hello\0, world\0\0" */
}

String Length
strlen counts all the bytes from the beginning of the string up to, but not including, the
terminating NUL character, '\0'. As such, it can only be used when the string is guaranteed
to be NUL-terminated.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void main(int argc, char **argv)
{
/* Exit if no second argument is found. */
if (argc != 2)
{
puts("Argument missing.");
return;
}
size_t len = strlen(argv[1]);
printf("The length of the second argument is %d.\\n", len);
return;
}

Copying Strings
Since C Strings are represented as arrays of characters, simply using the '=' operator to
copy will not work, since that will just copy the address of the pointer.

#include <stdio.h>
void main() {
char c[] = "abc";
char* d;

d=c; // only copoes the adress of the string

c[1] = 'x'; // modifies the original string, d[1] would do the same thing

printf("%s %s\n", c, d); /* "axc axc" will be printed */


}

To actually copy strings, we have to use strcpy:

#include <stdio.h>
#include <string.h>
void main() {
char a[] = "abc";
char b[8];
strcpy(b, a);
printf("%s\n", b); /* "abc" will be printed */
}

Comparing Strings

#include <stdio.h>
#include <string.h>
void compare(char const *lhs, char const *rhs)
{
int result = strcmp(lhs, rhs); // compute comparison once
if (result < 0) {
printf("%s comes before %s\n", lhs, rhs);
} else if (result == 0) {
printf("%s equals %s\n", lhs, rhs);
} else { // last case: result > 0
printf("%s comes after %s\n", lhs, rhs);
}
}
void main()
{
compare("BBB", "BBB");
compare("BBB", "CCCCC");
compare("BBB", "AAAAAA");
}

Dynamic Memory Allocation


malloc - allocates a block of bytes in memory, returning a pointer to the beginning
of the block
calloc - allocates a block of memory for an array of 'num' elements, each of them of
'size' bytes long, and initializes all of its bits to zero
realloc - changes the size of the memory block
free - frees up a block of memory previously allocated by a call to to malloc, calloc,
realloc.

int i, *buffer1, *buffer2, *buffer3;

// allocates 400 bytes on the heap (size of an int x 100)


buffer1 = (int*) malloc(100 * sizeof(int));

// print of the junk values found in memory


for(i=0; i<100;++i)
{
printf("%d", buffer1[i]);
}

// allocates memory and initializes it with '0'


buffer2 = (int*)calloc(100 * sizeof(int));
// each element will contain the value 0
for(i=0; i<100;++i)
{
printf("%d", buffer1[i]);
}

// allocates the memory used by buffer2 to buffer3


buffer3 = (int*) realloc(buffer2, 500 * sizeof(int));

free(buffer1);
free(buffer2)
It is important to remember to free up allocated memory, if the reference to the pointer is
lost , it causes a memory leak (that memory is wasted and we can't access that memory
again for the duration of the program run).

Since dynamically allocated memory exists on the heap and not on the stack like normally
allocated variables, it allows us to do things that we couldn't otherwise do.

int* getIntPtr()
{
int n = 5;
// will cause an error, 'n' will not exist after function returns
return &n;
}

int* getIntPtr()
{
int* n =(int*) malloc( sizeof(int));
// n will continue to exist after function returns
return n;
}

Working with files


To open a file you need to use the fopen function, which returns a FILE pointer. Once
you've opened a file, you can use the FILE pointer to let the compiler perform input and
output functions on the file.

Function prototype:

FILE *fopen(const char *filename, const char *mode);

The possible modes in which you can open a file are:

w - open for writing (file doesn't need to exist)


a - open for appending (file doesn't need to exist)
r+ - open for reading and writing, start at beginning
w+ - open for reading and writing (overwrite file)
a+ - open for reading and writing (append if file exists)

Writing to a file:

#include <stdio.h>
void main

{
// what character will cause us to exit
char endOfList[] = "*";
// open the file, create it if it doesn't exist
FILE* fp = fopen("student_list", "w"); // will get created relative to the
current working directory. (The folder that holds the vcxproj file in case
we're executing it from visual studio, or in the folder that holds the
executable, it we're executing it stand-alone)

// allocate buffer
char buff[100];
// check we could open folder
if(fp!=NULL)
{
do
{
gets_s(str);
if(strcmp(endOfList, str)==0)
{
// the user has typed '*', exit the loop
break;
}
printf("adding %s to the list \n", str);
// write it to the file
frintf(fp, str);
}
while(true) // keep reading
}
fclose(fp);
}

Reading from a file:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char **argv)


{
const int MAX_LINE_LENGTH = 80;
char *path;
char line[MAX_LINE_LENGTH];
if (argc < 1)
return EXIT_FAILURE;
path = argv[1];
// open file
FILE *file = fopen(path, "r");

if (!file)

{
perror(path);
return EXIT_FAILURE;
}
/* Get each line until there are none left */
while (fgets(line, MAX_LINE_LENGTH, file))
{
/* Print each line */
printf("%s", line);
}
fclose(file);
}

Function Parameters
In C, all function parameters are passed by value, so modifying what is passed in callee
functions won't a ect caller functions' local variables

#include <stdio.h>
void modify(int v) {
printf("modify 1: %d\n", v); /* 0 is printed */
v = 42;
printf("modify 2: %d\n", v); /* 42 is printed */
}
int main(void) {
int v = 0;
printf("main 1: %d\n", v); /* 0 is printed */
modify(v);
printf("main 2: %d\n", v); /* 0 is printed, not 42 */
return 0;
}

You can use pointers (or references) to let called functions modify caller functions' local
variables.

#include <stdio.h>
void modify(int* v) {
printf("modify 1: %d\n", *v); /* 0 is printed */
*v = 42;
printf("modify 2: %d\n", *v); /* 42 is printed */
}
int main(void) {
int v = 0;
printf("main 1: %d\n", v); /* 0 is printed */
modify(&v);

printf("main 2: %d\n", v); /* 42 is printed */


return 0;
}

Passing in Arrays to Functions

int iterateThroughArrayAndPrint(size_t size, int my_array[]) {


for (int i=0; i < size; i++) {
my_array[i] = i;
}
}

Since we don't actually know the size of array my_array, the extra size parameter is
needed.

iterateThroughArrayAndPrint will be used like this:

size_t size_of_list = LIST_SIZE;


int my_array[size_of_list];
getListOfFriends(size_of_list, my_array);

Structures
"Struct" is short for "structured data type".

struct cat {
const char* name;
const char* breed;
int weight;
int age;
};

/* record the cat data in the csv */


void record_stats(struct cat c) {
FILE *cat_csv = fopen("cat_s.csv", "w");

fprintf(cat_csv, "name: %s\n, breed: %s\n, weight: %i\n, age: %i,\n\n",


c.name, c.breed, c.weight, c.age);
fclose(cat_csv);
}

/* display the cat data to the user */


void display_stats(struct cat c) {
printf("%s is a %s who weighs %i and is %i years old.", c.name, c.breed,
c.weight, c.age);

}
/* enter the cat in show */
void enter_in_cat_show(struct cat c) {
printf("%s has been entered in the cat show.", c.name);
}
void main()
{
struct cat garfield = {"Garfield", "main coon", 200, 14};

record_stats(garfield);
display_stats(garfield);
enter_in_cat_show(garfield);
}

Function Pointers
Function pointers are pointers that point to functions instead of data types. They can be
used to allow variability in the function that is to be called, at run-time.

int my_function(int a, int b)


{
return 2 * a + 3 * b;
}

void main()
{
// create function pointer
// structure is
// <return type> (* <name>)(<arg type 1>,<arg type 2>,...)
int (*my_func_pointer)(int,int);

// assign the pointer to the function


my_func_pointer =&my_function
int a = 4;
int b = 2;
int result = (*my_func_pointer)(a,b);

printf("%d", result);
}

Best Practices

It might be handy to use a typedef instead of declaring the function pointer each time by
hand.

The syntax for declaring a typedef for a function pointer is:


typedef returnType (*name)(parameters);

You might also like