Bookmark and Share
C vs C++ - 2020





C vs C++
Preview

The following two codes are doing the same thing:
It calculates probabilities of the sum of faces when they rolled together. For example, when we rolled two dices, the sum of the faces could be in the range of 2 and 12, and then calculate the probabilities of the sum f faces for each trial.

C version:

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

#define FACES 6
#define NDICE 3
#define OUTCOME_ARRAY_SIZE (FACES * NDICE + 1)
#define RANDOM_FACE (rand() % FACES + 1) 

int main()
{
  int i, j, index, trials;
  int outcome[OUTCOME_ARRAY_SIZE] = {0};

  srand(clock());

  printf("\nEnter the number of trials\n");
  scanf("%d", &trials);
  for(i = 0; i < trials; ++i) {
    index = 0;
    for(j = 0; j < NDICE; ++j) {
       index += RANDOM_FACE;
    }
    outcome[index]++;
  }

  for(i = NDICE; i < OUTCOME_ARRAY_SIZE; ++i)
    printf("sum of faces = %d probability = %lf\n", i,  (double)(outcome[i])/trials);

  return 0;
}

C++ version:

#include <iostream>
#include <cstdlib>
#include <ctime>

using namespace std;

const int FACES = 6;
const int NDICE = 3;
const int OUTCOME_ARRAY_SIZE = FACES*NDICE + 1;
inline int randomFace() { return rand() % FACES + 1; }

int main()
{
  srand(clock());

  cout << "\nEnter the number of trials\n";

  int trials;
  cin >> trials;

  int* outcome = new int[OUTCOME_ARRAY_SIZE];

  for(int i = 0; i < trials; ++i) {
    int index = 0;
    for(int j = 0; j < NDICE; ++j) {
       index += randomFace();
    }
    outcome[index]++;
  }

  for(int i = NDICE; i < OUTCOME_ARRAY_SIZE; ++i)
    cout << "sum of faces = " << i
         << " probability = " << static_cast<double>(outcome[i])/trials
         << endl;

  delete outcome;

  return 0;
}

These two codes illustrate the basic difference between C and C++ codes:

  1. Libraries are different. However, C++ is a superset of C, and most of the C-libraries are still available.
  2. In C++ code, inline function randomFace() is used instead of RANDOM_FACE macro - less reliance on macros.
  3. Compiler can check the correctness of the non-mutable variables using const keyword.
  4. A new keyword, namespace has been introduced to avoid name conflicts. By declaring using namespace std, we can use cin or cout instead of specifying the scope like std::cin, std::cout or std::endl.
  5. scanf() has been replaced with cin. The iostream cin does not need format because it knows how to display the given type. Also, cout is used instead of printf()
  6. << and >> are the examples of operator (in these cases, bitshift) overloading.

  7. Declaration such as int i can be located any place in the code block.
  8. We have new keywords related to memory allocation such as new and delete.
  9. The static_case<double> is replacing C-type cast (double). If a type conversion is not allowed, it will be an error. In other words, C++ provides more type-safe code replacing potential C's unsafe casting.



Pass by value vs pass by reference

In C, by default, a parameter is passed by value. When the parameter is passed, the function copies the value and use the value locally. So, even though we can modified the value within the function, it only changes the value of the copy. Therefore, in C, we pass the address, and dereference the value and modifies the value that pointed by the pointer.

C version:

#include <stdio.h>

void swap(int *a, int *b) {
  int temp = *a;
  *a = *b;
  *b = temp;
}

void swap_double(double *a, double *b) {
  double temp = *a;
  *a = *b;
  *b = temp;
}

int main()
{
  int i1= 1, i2 = 2;
  double d1 = 1.1, d2 = 2.2;

  printf("i1=%d i2=%d\n", i1, i2);
  swap(&i1, &i2);
  printf("i1=%d i2=%d\n", i1, i2);

  printf("d1=%lf d2=%lf\n", d1, d2);
  swap_double(&d1, &d2);
  printf("d1=%lf d2=%lf\n", d1, d2);

  return 0;
}

C++ version:

#include <iostream>
using namespace std;

void swap(int &a, int &b) {
  int temp = a;
  a = b;
  b = temp;
}

void swap(double &a, double &b) {
  double temp = a;
  a = b;
  b = temp;
}

int main()
{
  int i1= 1, i2 = 2;
  double d1 = 1.1, d2 = 2.2;

  cout << "i1=" << i1 << " i2=" << i2 << endl;
  swap(i1, i2);
  cout << "i1=" << i1 << " i2=" << i2 << endl;

  cout << "d1=" << d1 << " d2=" << d2 << endl;
  swap(d1, d2);
  cout << "d1=" << d1 << " d2=" << d2 << endl;

  return 0;
}

As we can see from the two codes, C++ version is much simpler:

  1. Thanks to the function overloading, in C++, we only use one function name swap() for all data types for the swap instead of swap() and swap_double(). The C++ compiler is able to differentiate the swap() function with the type of the parameters as a signature. It's using "signature matching algorithm" where the two routines need different types or numbers of parameters. Using the same name for the same activity provides us more readable code. The overloading capability in C++ enables us to use generics (templates) which is powerful reuse mechanism.
  2. By passing by reference, in C++, we can call swap, as swap(d1, d2) instead of swap(&d1, &d2) which makes it more intuitive and readable. As we can see later, the reference passing can reduce the overhead of copying big objects.



Code reuse - generics

Simply put, generic in C++ means using templates.

We can easily convert the C++ code for swap() using meta variable T, then it will be substituted by the proper data type such as int or double during compile time. The compiler uses a routine that matches the signature to infer what the code should use:

template <typename T>
void swap(T &a, T &b) {
  T temp = a;
  a = b;
  b = temp;
}