C vs C++: The Conclusion

C vs C++: Review

We pause one final time at the end of the semester to look at differences that have popped up between C and C++.

As a reminder of the differences we've already covered:

Today we'll look at functions, the lack of pass-by-reference, and linked lists. The summary is actually pretty easy:

Functions

You cannot overload functions in C. Recall that overloading is the ability to write two functions with the same function name but different function signatures. This is a very useful thing to do. For example, you might want two max functions:

int max(int, int);
int max(int, int, int);
This enables the user to find the max of three numbers without having to call the two-parameter version twice. Overloading is sometimes used to make printing easier too, for instance by having a function that prints to any output stream, but a "default" function that will print to cout if the user doesn't want to specify all those parameters:
void printArray(int* arr, int N, ostream& out) {
    for( int i = 0; i < N; i++ )
       out << arr[i];
    out << endl;
}
void printArray(int* arr, int N) {
  printArray(arr, N, cout);
}

This is a great use of overloading, but you should note that this is a matter of convenience right now. This doesn't give us more computational power, but rather ease of programming. It might help you write code more quickly (which is important!), but it doesn't make C++ more powerful. It's a feature!

What to do in C? If you want a default print function, well, you need to give it a different name. Some might say this is less than ideal, others might say this helps lead to less bugs since you're forced to give unique names to everything.

int printArrayFile(int* arr, int N, FILE* out) {
    for( int i = 0; i < N; i++ )
       fprintf(OUT, "%d ", arr[i]);
    fprintf(OUT, "\n");
}
int printArray(int* arr, int N) {
  for( int i = 0; i < N; i++ )
    printf("%d ", arr[i]);
  printf("\n");
}

Pass-by-Reference

This is a huge difference between the two languages. Pass-by-reference was a big feature that came with C++ and changed the paradigm of what programmers can do. Since C does not have this, please don't try to do it. The following is illegal!!!

void add2front(Node* &front, int val);

We'll get back to linked list functions like this shortly. Let's first talk about where this leave us in C, though. If we can't pass-by-reference in our function calls, then we cannot directly modify variables that are passed to us, right? Well, right, but also wrong. All this means is that we need to be more explicit with pointers, and we have to use them more often.

Recall our function that found the max and the min of an array of integers. Since it needed to set two variables, it couldn't just return one of them. We used pass-by-reference to set both. It looked like this:

void maxMin(int* nums, int N, int &max, int &min) {
  max = nums[0];
  min = nums[0];
  for( int i = 1; i < N; i++ ) {
     if( nums[i] > max )
        max = nums[i];
     if( nums[i] < min )
        min = nums[i];
  }
}

C can also have a function like this, but it requires explicit pointers. You've seen all the pieces of this, so this isn't actually new, but perhaps you haven't thought about it this way. We now must do this:

void maxMin(int* nums, int N, int* max, int* min) {
  *max = nums[0];
  *min = nums[0];
  for( int i = 1; i < N; i++ ) {
     if( nums[i] > *max )
        *max = nums[i];
     if( nums[i] < *min )
        *min = nums[i];
  }
}

The big change is that max is an explicit pointer! With C++ and pass-by-reference, you as the programmer get to treat it as a normal int, but with C we have to know this is a pointer. This means you can't just say max = nums[0] to save the first int as the max. It's a pointer, not an int. We thus need that asterisk to follow the pointer to the actual memory location and fill the int with a value: *max = nums[0]. Remember, pointers are memory addresses, so you have to keep the difference straight in your head as you code.

But if maxMin now needs pointers, how does the caller actually use this function? You need to dereference your variables explicitly when you make the call:

int max, min;
maxMin(nums, N, &max, &min);

Hopefully you now see how dereferencing and pass-by-reference are related. In C, the caller has to do the dereferencing to get the pointer. In C++, the callee defines the function as referencing the given arguments. Here is an entire C program that reads an int array and prints out the max/min:

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

void maxMin(int* nums, int N, int* max, int* min) {
  *max = nums[0];
  *min = nums[0];
  for( int i = 1; i < N; i++ ) {
     if( nums[i] > *max )
        *max = nums[i];
     if( nums[i] < *min )
        *min = nums[i];
  }
} 

int main() {
  int N;
  printf("How many? ");
  scanf("%d", &N);

  // Create the array
  int* nums = (int*)malloc(N*sizeof(int));
  printf("Enter %d nums: ", N);
  for( int i = 0; i < N; i++ )
    scanf("%d", &nums[i]);

  // Get the max/min
  int max, min;
  maxMin(nums, N, &max, &min);
  printf("max=%d min=%d\n", max, min);

  return 0;
}

Structs

Just a syntax change! Structs are actually a C construct. C++ builds on structs with something called classes, but we ran out of time to introduce those. The change here is syntactic, and frankly a little annoying. You now have to write struct in front of all your type declarations. For instance:

struct Mid {
  char* name;        
  int alpha;      
  int OOM;
};

void readMid(struct Mid m) {
  // do stuff    
}
  
int main() {    
    // Create a variable of type Mid
    struct Mid a;

    // do stuff...
}

Wherever you put Mid now needs to be struct Mid. You can probably now see why the C++ designers wanted to get rid of that.

Linked Lists

Fundamentally, there is no real difference between C and C++. We have structs and pointers, and both of these are the same. However, there is a change in the types of functions that you are allowed to write, so manipulating linked lists feels different as a programmer.

Let's look at add2front, our old friend. The prototype needs to change because it depends on pass-by-reference to always update the front pointer. Let's change that to an explicit pointer, and we now have this comparison:

C++C
void add2front(int val, Node* &L) {
  Node* T = new Node;
  T->data = val;
  T->next = L;
  L = T;
}
struct Node* add2front(int val, struct Node* L) {
  struct Node* T = malloc(sizeof(struct Node));  // get the size of a Node struct!
  T->data = val;
  T->next = L;
  return T;
}

Three things must change. The first is putting struct in front of all the Nodes. The second (and biggest) is that pass-by-reference is removed and we just have the pointer to the front of the list. Since we have a new front pointer at the end of this function, it's easiest to then change the return type. The third change is then to actually return the front of the list! It's not a big change, and the caller makes a similar small change:

C++C
Node* mylist = NULL;
add2front(92, mylist);
struct Node* mylist = NULL;
mylist = add2front(92, mylist);

Hopefully you are more fully realizing that pass-by-reference does not give a programming language more power. We can still do everything we need to do, but our methodology of doing it is what changes. Many people argue over these things, arguing that one paradigm is better than the next, and indeed you might have your own personal preference already.

To conclude this section, I'm including C code to read a linked list from the user (stop at -1) and print it:

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

struct Node {
  int data;
  struct Node *next;
};

// Adds to the front!        
struct Node* add2front(int val, struct Node* L) { 
  struct Node* T = malloc(sizeof(struct Node));
  T->data = val;
  T->next = L;
  return T;
}

// Recursive, prints it backwards!
// Since the list is built backwards, this prints forward!              
void printList(struct Node* L) {  
  if( L == NULL )
    printf("\n");
  else {
    printList(L->next);
    printf("%d ", L->data);
  }
}

int main() {
  struct Node* mylist = NULL;
  int num;

  printf("Numbers? ");
  scanf("%d", &num);
  while( num != -1 ) {
    mylist = add2front(num, mylist);
    scanf("%d", &num);
  }

  printList(mylist);

  return 0;  
}

Problems

  1. A few weeks ago we had a C++ program that read student grades from a text file and averaged their grades. Take the C++ code and convert it to working C code that produces the same output.
    $ ./avg
    Enter name: Needham
    Average is: 71.1000005