Very fast intro to the basics of c++#

Although the c++ standard is really large, for this course we need to handle well a small subset of it. What is more important is to learn how to THINK like a programmer, not to memorize the actual syntax, which can actually obtained from google bard or chatGPT. In this section, we will learn the very basics and then we start practicing. Practice is the key in everything. For more details, see https://www.learncpp.com/ or a reference book.

Some good practices

Resources:

Minimal example#

This a minimal valid cpp program. It does “nothing”. But when you compile, a lot of things happen. For more info see https://hackingcpp.com/cpp/hello_world.html

int main(void) {
    return 0;
}

Please write it in a file called minimal.cpp. Now compile it and run it. Notice that now we are giving it a name for the executable:

g++ -std=c++17 minimal.cpp -o minimal.x
./minimal.x

Exercises

  • What happens if you remove the return statement?

  • After running the executable, check the status of the command in the shell by using the following instruction

    echo $?
    
  • Look for some other standard library at https://en.cppreference.com/w/ . Choose one and try to describe, in general words, its use.

Variables#

See

A variable in c++ is an object which has:

  • A datatype

  • A value

  • A name

  • A memory address

  • A scope

C++ is a typed lang. It means that you have to specify the type of the variable you are going to use. You have booleans, char, integers, float and double, arrays/vectors, structs, …. See https://hackingcpp.com/cpp/lang/fundamental_types.html

A variable in memory:

Image Description
From: "https://hackingcpp.com/cpp/lang/variables_crop.svg"

How to represent “reals”:

Image Description
From: "https://hackingcpp.com/cs/number_representations_crop.png"

Variable types#

char, int, unsigned int, float, double, bool, … See:

  • https://hackingcpp.com/cpp/lang/fundamental_types.html

  • https://en.cppreference.com/w/cpp/language/types

Bugs: Gandhi the destroyer (unsigned char): https://www.youtube.com/watch?v=Iq_r7IcNmUk

Exercises#

Initialization#

Create a program that declares a variable of type

  • int without initializing it.

  • double with initial value -9.87e-8.

  • std::string initialized with your first name .

  • std::vector<double> with 10 values not initialized.

# YOUR CODE HERE
raise NotImplementedError()
---------------------------------------------------------------------------
NotImplementedError                       Traceback (most recent call last)
Cell In[1], line 2
      1 # YOUR CODE HERE
----> 2 raise NotImplementedError()

NotImplementedError: 

Python/C++ tutor#

Copy and visualize this program execution in the cpp python tutor.

Overflow and santizers#

Analyze the output of the following program:

#include <iostream>
int main(void) {
  // con enteros
  int b = -3000000000;
  std::cout << "b = " << b << "\n";
  int a = -2000000000;
  std::cout << "a = " << a << "\n";
  // con flotantes
  double x = 3000000000;
  std::cout << "x = " << x << "\n";
  x = 3.24e307;
  std::cout << "x = " << x << "\n";
  x = 3.24e310;
  std::cout << "x = " << x << "\n";
  x = 3.24e-310;
  std::cout << "x = " << x << "\n";
  x = 3.24e-326;
  std::cout << "x = " << x << "\n";
  return 0;
}

Compile this program with sanitizers and run it again (-fsanitize=undefined). To understand the output, look for an overflow/underflow error.

Operators#

Arithmetic#

Example: =, +, -, *, /, %, -=, +=, *=, /=, ++, --

int a = 10, b = 20, c = 0; 
a = b; // assigns b value to a value
a = b + 60; // adds 60 to b value, then assigns that value to a
a += 60; // same as previous
a = 2*b; // multiplication. Here same as a *= b;
a = b/3; // division, notice truncation for integers. a /= 3;
a = b%7; // modulo operator, residue
a = 10;
b = 20; 
a = b++; // post increment
a = 10;
b = 20; 
a = ++b; // pre increment

Comparison#

You have ==, !=, >, >=, <, <=

int a = 10, b= 20;
bool result = true;
result = a == b; // is a value equal to b value
result = a != b; // is a value different than b value
result = a <= b; // is a value les or equal to b value

Logic#

You have &&, and, ||, or, !, not, &, |

int a = 10, b = 20;
bool result = false; 
result = (a < b) and (3*a == b); // and , && , at value level
result = (a < b) or (3*a == b); // or, ||, at calue level
int c = a & b; // at bits value

Precedence#

Operators have a defined precedence: https://en.cppreference.com/w/cpp/language/operator_precedence

Exercises#

Arithmetic examples#

Write a program that shows examples for the arithmetic operators. Use a simple function (the main function must just call it).

Comparison examples#

Write a program that shows examples for the comparison operators. Use a simple function (the main function must just call it).

Logic operators#

Write a program that shows examples for the logic operators. Use a simple function (the main function must just call it).

Facebook genius#

What is the result of 6*2/3+1? Make your prediction and then compute it.

Functions#

See: https://hackingcpp.com/cpp/lang/function_basics.html

Functions are of fundamental importance. They allow to encapsulate the implementation details and to create modular and more maintainable programs. Each function must do one thing well. Using functions also allows the program to be tested, profiled, and extended. Form now on, will keep clean the main function, and use it to call other functions.

Image Description
From: "https://hackingcpp.com/cpp/lang/function_terminology.svg"

There are many more details about functions, like passing by value/reference, return types, overloading, templates, lambdas, …, but we will learn those details on the run. Please write the following code

//#include <iostream>
#include <print> // compile with -std=c++23

// function declaration: name and types
double average(double a, double b); // a and b are LOCAL variables

// main function. Execution starts here
int main(int argc, char **argv) {
    double x{2.35};
    double y{4.987};
    double avg = average(x, y); // x value copied to a, y value copied to b, result of calling assigned to avg
    //std::cout << "avg: " << avg << "\n";
    std::print("avg: {0:20.16f}\n", avg);
    
    return 0;
}

// function implementation: how it does that
double average(double a, double b) 
{
    return (a+b)/2;
}

Compile it and run it. From now on try to use functions all the time.

Exercises#

  • Write a program that prints the number of primes less than \(n\) for \(n \in [2, 1000]\). Plot it using either gnuplot or matplotlib

Conditionals#

This allows you to change the execution path according to a given condition. The general syntax is

if (condition) {
  // do something if condition is true
} else {
  // Do something if condition is false. The else is not mandatory   
}

You can add also if ... else if ... else as you want. Sometimes it would be more clear to use a switch statement. The condition can be as complex as you want.

if (condition 1) {
  // perform action 1
} else if (condition2 ) {
  // perform action 2
} else {
  // perform action 3   
}
%load_ext nb_js_diagrammers
%%mermaid_magic -h 250 
graph LR
    A[Start] -->B{Condition 1}
    B -->|true| C[Action 1]
    B -->|else if| D{Condition 2}
    D -->|true| E[Action 2]
    D -->|false| F[Action 3]
    C --> X[End]
    E --> X
    F --> X

Example: Even/odd number (Please notice that the goal here is to learn if/else and the modulo operator, but the reading interactively is something not advisable)

#include <iostream>

void read_check_even_odd(void);

int main(int argc, char **argv)
{
  read_check_even_odd();
  return 0;
}

void read_check_even_odd(void)
{
  int num = 0;

  // solicitar el numero
  std::cout << "Escriba un numero entero, por favor:\n";
  // leer el numero
  std::cin >> num;
  std::cout << num << "\n";
  
  // verificar que el numero es par o no
  // imprimir
  // si el numero es par, imprimir "el numero es par"
  // si no, imprimir "el numero es impar"
  if (num%2 == 0) {
    std::cout << "El numero es par \n";
  }
  if (num%2 != 0) {
    std::cout << "El numero es impar \n";
  }

  //else {
  //cout << "El numero es impar" << endl;
  //}
}

Compile and run it. As this is expecting to read from the terminal, run it in a real terminal, not on google collab.

Exercises#

  • You have to write a program that reads a number. If the number is smaller or equal than 10, verify if it is prime (you might need to write several if or if/else). If it is larger, write the message Too large to verify manually. To do so:

    • Use https://draw.io to design the flow chart of the program

    • Write the program and test it with several inputs.

  • Write a program that computes the harmonic sum until some limit. Write, in the main function some tests for it.

  • Write a game to guess a number; Every time you guess the computer prints if the target is lrger ir smaller or prints the end of the game.

Loops#

See https://hackingcpp.com/cpp/lang/control_flow_basics.html

Classical loops#

for loop#

Includes initialization, condition and step or change. Change must ensure reaching condition == false.

for (initialization; condition; change) {  }
// prints 0 1 2 3 4
for (int i = 0; i < 5; ++i) {
  std::cout << i << ' ';
}
%%mermaid_magic -h 250
graph LR
    A[Start] --> B[Initialization]
    B -->|Condition| C{Condition true?}
    C -->|Yes| D[Instructions]
    D --> E[Change]
    E -->|Condition| C
    C -->|No| F[End]

while loop#

Repeat while condition is true.

init; 
while (condition) {  ; change; }

Example

int j = 5;  
while (j < 10) {
  std::cout << j << ' ';
  ++j;
}

do-while loop#

Repeat while condition is true, BUT always do at least one step.

do {  } while (condition);
int j = 10;
do { 
  std::cout << j << ' ';
  --j;
} while (j > 0);

Flow control#

You can also add a continue or break inside a loop if you need it. A continue tells the program to ignore the lines in the loop behind it, and continue with the next iteration. A break exist the loop.

Range based loops#

for (variable : range) {  }

Example: this loop prints all the elements from the vector, separated by a space. The limits and types are deduced automatically.

std::vector<int> v {1,2,3,4,5};
// print all elements of vector to console
for (auto x : v)  { std::cout << x << ' '; }

Exercises#

  • Write a program that prints the numbers between 5 and 223 which are divisible by both 3 and 7. Print the numbers separated by a space, and a new line only at the end. Use the three types of classical loops (so your program must print the same three times)

  • Write a program that checks if a given number is prime.

  • Write a program that computes the sum of the multiples of 3 and 5 in the range \(1\le x \le 1123\).

  • Write a program that given a number, prints the sum of its digits

  • Write a program that checks if a number is palindrome

Functions again#

See: https://hackingcpp.com/cpp/lang/function_basics.html

Functions are of fundamental importance. They allow to encapsulate the implementation details and to create modular and more maintainable programs. Each function must do one thing well. Using functions also allows the program to be tested, profiled, and extended. Form now on, will keep clean the main function, and use it to call other functions.

Image Description
From: "https://hackingcpp.com/cpp/lang/function_terminology.svg"

There are many more details about functions, like passing by value/reference, return types, overloading, templates, lambdas, …, but we will learn those details on the run.

Image Description
From: "https://hackingcpp.com/cs/function_contracts_crop.svg"

Some very useful functions for us:

  • Math standard library: https://en.cppreference.com/w/cpp/header/cmath

  • Special functions: https://en.cppreference.com/w/cpp/numeric/special_functions

  • Pseudo random numbers: https://en.cppreference.com/w/cpp/numeric/random

  • Complex numbers: https://en.cppreference.com/w/cpp/numeric/complex

  • Valarray (to emulate math vectors): https://en.cppreference.com/w/cpp/numeric/valarray

  • Numerics algorithms: https://en.cppreference.com/w/cpp/numeric#Numeric_algorithms

Exercises#

Write different programs to solve the following (use functions extensively):

  • Create a function that receives an integers and return true if it is a prime, false otherwise. Use it in the main function to print the primes from 1 up to 150.

  • Compute the sum of the prime numbers between 500 and 1234.

  • Prints the cousin primes between 300 and 400.

  • Prints the sexy primes tuples between 300 and 450.

  • Prints the sexy primes triplets between 300 and 490.

  • Read https://hackingcpp.com/cpp/lang/command_line_arguments.html to be able to read command line arguments after processing argc and argv. Rewrite your previous programs to read the limits from the command line. You can also check https://faculty.cs.niu.edu/~mcmahon/CS241/Notes/command_line_arguments.html

Workshop#

For all the following codes always modularize the files (separating the main function into another file) and write a test to be run with catch2. All parameters must be pased as command line parameters. Do not use cin or similar.

Multiples of 3 and 5#

Write a program that reads a number from the command line and prints the number divisible by 3 and 5 smaller or equal than that number.

Square of *#

Write a program that reads a positive number from the command line and then prints a “square” with that number of asterisks. For example, ./a.out 5 must print

*****
*   *
*   *
*   *
*****   

Square of any char#

Now generalize the previous program and read both the number of characters and the character to be used.

Fixing loops#

What is wrong with the following codes?

  for(;;) {

  }
    for(ii=0; ii>10; ++ii) {

}  

For and while loops#

  • Transform the next loop into a while loop

    for (int i=1; i <= n; i++) {
      std::cout << i*i << " ";
    }

Sum of squares and plot#

Write a program that reads a number from the command line and prints the sum of squares less than that number. Use it to plot the data.

Digits#

The program must read a number of four digits and print the digits, separated by a space. Some examples

./a.out 123
0 1 2 3
./a.out 2048
2 0 4 8
./a.out 3065
3 0 6 5
./a.out 10
0 0 1 0
./a.out 12345
Number has more than 4 digits. Ignoring
./a.out -1
Negative number. Ignoring
./a.out 0
0 0 0 0

Babilonian square root#

The babilonians found that to compute the square root of S, you can iterate the following expression \(x \leftarrow \dfrac{x+S/x}{2}\). Implement this algorithm in a function that receives the number of iterations to perform and the returns the estimation. Print in scientific notation with 15 decimals of precision using the following instructions in the main function

std::cout.setf(std::ios::scientific); // imprimir en notacipn cientifica
std::cout.precision(15);  // imprimir con 15 cifras decimales

Some tests:

./a.out 0
0 1.000000000000000e+00
./a.out 2
2 1.416666666666667e+00
./a.out 4
4 1.414213562374690e+00
./a.out 6
6 1.414213562373095e+00
./a.out 8
8 1.414213562373095e+00
./a.out 10
10 1.414213562373095e+00

To print in that format you can also use a modern construct, std::format, available in the c++23 standard. Check https://en.cppreference.com/w/cpp/io/print . Or , if it is installed, you can use https://fmt.dev/latest/index.html (also see https://hackingcpp.com/cpp/libs/fmt.html). Or if you prefer to be classic, you can use printf.

Uncertainty#

Write a program that reads two floating-point numbers from the command line. One number represents the radius, and the other represents the uncertainty. Then, call a function that calculates and prints the area of the associated circle along with its propagated uncertainty. The area is calculated as , and the propagated uncertainty is \(2\pi r\Delta r\). If you do it correctly, the uncertainty must have only one significant figure, and it limits the number of decimals in the main quantity.

Primes again#

Check or improve your program to compute if a number is prime to be able to compute it with 10000019 or larger in a couple of seconds at most.

Largest prime smaller than number#

Write a program that reads a positive integer from the command line and prints the largest prime number that is less than or equal to the input number. The code should execute in less than 30 seconds, even for large numbers like 10000019 (a prime number) or 10000030 (not a prime number).

Some examples:

./a.out 2
2
./a.out 3
3
./a.out 7
7
./a.out 11
11
./a.out 12
11
./a.out 101
101
./a.out 617
617
./a.out 627
619
./a.out 5153
5153
./a.out 500167
500167
./a.out 8000033
8000033
./a.out 8000034
8000033
./a.out 80000069
80000069
./a.out 80000388
80000387

GCD#

Write a program that reads two positive integers from the command line and prints their greatest common divisor (GCD). Check the standard library for an immediate solution

Sum of primes#

Write a program that reads a positive integer from the command line and prints the sum of prime numbers that are less than the input number.

Collatz largest number#

Write a program that reads a positive integer from the command line and calculates the largest term obtained in the Collatz sequence for that number.

Collatz number of terms#

Write a program that reads a positive integer from the command line and calculates the number of terms in the Collatz sequence for that number.

Sexy primes smaller than number#

Write a program that reads a positive integer from the command line and prints the sexy prime numbers that are less than the input number. Two prime numbers are “sexy” if they are separated by 6 (e.g., 5 and 11, or 13 and 19).

Using the standar library for time#

https://www.cppstories.com/2024/chrono_dates_zones/

File modularization#

Image Description
From: "https://hackingcpp.com/cpp/lang/separate_compilation_crop.svg"

As you can see, the basic function is_prime is used in all the problems, and we are repeating code per exercise. We can do better by modularizing the file, moving the utility functions to another file. Our structure would be something like:

  • primes.h : file with DECLARATIONS of all the functions we think we will use int the future

      #pragma once
      bool is_prime(int n);
      void print_sexy_primes(int a, int b);
    
  • primes.cpp : file with the IMPLEMENTATIONS of the functions declared in primes.h

      #include "primes.h"
      bool is_prime(int n)
      {
           // instructions
      }
    
      void print_sexy_primes(int a, int b)
      {
          // instructions to print the sexy primes in the specified range
      }
    

    Notice that there is no main function.

  • main_cousin.cpp: The file where we solve this problem, and uses the primes utilities.

      #include <iostream>
      #include <string>
      #include "primes.h"
    
      int main(int arg, char **argv) {
          int minval = std::stoi(argv[1]);
          int maxval = std::stoi(argv[2]);
          print_sexy_numbers(minval, maxval);
          return 0;
      }
    

    Notice how the main function now is so simple, and its intent is clear, although we do not know the implementation.

  • Compilation:

    g++ -std=c++17 primes.cpp main_cousin.cpp -o cousin.x
    
  • Execution

    ./cousin.x 200 400
    

Exercise Do this will all the functions previously written. So, include all the prime functions in the .h and .cpp files, and then create the corresponding main files.

Basic Testing#

Once we have modularized our code, not only using functions, but also files, it is possible to start testing our code so to be sure that we are not repeating old mistakes, etc. Here we will use catch2 to do so, with a simple example: the prime function. The idea is that, for each bug/problem you find, to implement a test case so to avoid repeating that in the future. The following is an example file

%%writefile test_primes.cpp
#include <catch2/catch_test_macros.hpp>
#include <cstdint>
#include "primes.h"

TEST_CASE( "Primes are computed", "[prime]" ) {
    REQUIRE( is_prime( 1) == true );
    REQUIRE( is_prime( 2) == true );
    REQUIRE( is_prime( 3) == true );
    REQUIRE( is_prime(10) == false );
}

Now compile as (we are using catch2 version 3 and it must be installed on the path)

g++ primes.cpp test_primes.cpp  -l Catch2Main -l Catch2

and then run and you will get something like

./a.out 
Randomness seeded to: 3750912043
===============================================================================
All tests passed (4 assertions in 1 test case)

if everything is fine.

Finally, you can automatize everything using a Makefile.

Classes and structs#

In C++ you can program using object oriented programming. In our case, we will use this to encapsulate data and some functionality. An example in our domain would be the simulation of some planets orbits. We can have an array of data for the x positions, the y positions, the z positions, the velocities and so on. This can be overwhelming when indexing and processing. We might use a better computational model that resembles, better, the physical model. Our main object here is a planet. And a planet has several attributes, like its coordinates, its mass, radius, and so on. So it would be better if we can represent this as a new datatype, let’s calle it a Planet. We can do it using structs or classes:

struct Planet {
    // public attributes
    std::valarray<double> R(3), V(3), F(3); // model position, velocity and force as vectors
    double mass, radius;
    
    // public functions
    double speed() {
        return std::sqrt((V*V).sum());
    }
};

and then we can use it in the code as

...
    Planet mercury, earth;
... 
    mercury.speed();
...
    compute_force(earth, mercury);
...

You can also have private members using the keyword private. Classes and structs in c++ are basically, but in a class everything is private by default, while for a struct everything is public by default.

As an exercise, create a struct or class to model a molecule.

We will be using structs/classes to create generalized data and functions, called functors, after overloading the () operator. So our class will represent a function with internal state, something very powerful.