C++ as we have used it

This course is about learning how to program using the procedural programming paradigm, which basically means programming with control flow structures like if/while/for/switch and using functions to organize code and separate interface from implementation. C++ is the language we've written our procedural programs in, but there is much of C++ we haven't talked about, since it didn't contribute to our goal of learning and applying basic procedural programming principles to solve problems. In fact, C++ was original designed to extend the C language, which is definitely a procedrual language, to
  1. be a "better C", which means features were added try to make C++ even better for procedural programming.
  2. allow for programming in a different paradigm - object oriented programming.
The purpose of today is to look at C and get a feel for how programming in C differs from C++ as we have used it in this class. Since C++ is a superset of C, that means that there are some features we are used to that are not in C, and we'll learn how to get by without them. It's very important to be able to program in C, since lots of low-level things (e.g. the device drivers that allow peripherals connected to your computer to run, or programs for embedded devices).

More similar than not

C++ is a superset of C. That means any valid C program should be a valid C++ program, though not the other way, of course. Below you see a C program and a C++ version of the same program. The C program should make sense to you, except for the new functions printf and scanf. In any event, they are clearly functions, and you should be able to understand them as such, though we don't yet know what those functions do. Programs will look much the same, compiling is similar (though you use a C compiler, like gcc, instead of a C++ compiler, like g++), and running a compiled program is no different at all.

ex0.c ex0.cpp
~$ gcc -o ex0 ex0.c -lm
~$ ./ex0
Enter name, height and weight: chris 73 190
A 10' tall chris would weigh 843.973400 pounds. 
~$ g++ -o ex0 ex0.cpp
~$ ./ex0
Enter name, height and weight: chris 73 190
A 10' tall chris would weigh 843.973400 pounds. 

For you, learning C (which we're just peeking at today) is going to be about learning what C++isms are absent in C, and how we get around without them.

What C++ features we are used to are absent in C?

There are a few big things that we're used to that aren't in C: We'll discuss each of these in detail below, but first I want to draw your attention to two big picture differences between C and C++ that are very related to the items above:
  1. In C, it's comparatively rare for the compiler to deduce the type of an object and adjust what it's going to do to fit the type. Instead, it's usually on you, the programmer, to tell the compiler what type it should treat an object as having.
  2. In C, a lot more things are done with pointers.
Keep those distinctions in mind in the discussions below. I'll give you a quick example of the first point here. In C++, we have "function overloading". That means two functions can have the same name, as long as the types of their parameters differ. For example: both
bool foo(int k) { return k*k > 5; }
double foo(double z) { return 1.5 + sqrt(z); }
... can exist in the same program. Because, for example, if you make a call like foo(3*i+1), where i is an int, the compiler will deduce that 3*i+1 is an int and know to call the first function. As (1) states, C tends not base its behavior on deducing types and, indeed, function overloading is illegal in C - there may not be two functions with the same name.

Life without pass-by-reference

In C++, pass by reference allows us to modify arguments within functions, to "return" more than once piece of information from a function, or to simply avoid the copying of data inherent in pass-by-value. So how do we live without it in C? We simply pass pointers to objects rather than objects themselves. So here's how "swap" compares in C vs. C++:
C C++
void swap(int* a, int* b)
{
  int t = *a;
  *a = *b;
  *b = t;
}
void swap(int& a, int& b)
{
  int t = a;
  a = b;
  b = t;
}
.
.
.
if (x > y)
  swap(&x,&y);
.
.
.
if (x > y)
  swap(x,y);

Life without the string datatype

Note: This is just an introduction to C-strings. We'll do more later.
In C, strings are represented as arrays of chars, with one special caveat: One of the elements of the array has to be the special char '\0', known as the "null character". The actual sequence of characters represented consists of all the char's from the beginning of the array up to, but not including, that '\0'. So, for example, the string "hello" is represented by the array:

In fact, even in C++ string literals are actually C-style strings like this, so "hello" is exactly the array of chars you see above. This means you could write a function to compute the length of a C-style string like this:
int strlen(char* s)
{
  int i = 0;
  while(s[i] != '\0')
    i++;
  return i;
}
Fortunately, this function and a number of other useful functions for manipulating strings are defined in the standard C library string.h
#include "string.h"
int main()
{
  char* s = "hello";
  int n = strlen(s); /* sets n to 5 */
  return 0;
}
Challenge: can you write a function that takes a C-style string and determines whether it is a palindrome?

Important: String literals like "foobar" are C-style strings, even in C++! That's why "foo" + "bar" doesn't do concatenation even through s + t does (where s and t are variables of type string).

Life without istreams and ostreams

Note: We will just look at terminal I/O for the moment (i.e. to stdin and stdout). We'll talk about files later. Also, we'll cover reading into strings later.
Think about a statement like
cin >> var;
in C++. Which characters are actually read in from the input stream as a result of this depends completely on the type of var. Same with output. cout << int(42) and cout << char(42) cause different output based purely on the type of the right-hand side. As described above, C doesn't operate this way. So I/O is handled in a fundamentally different way. There is a function printf to print output, and scanf to read input.

scanf is differenter

Scanf is more of a departure from cin >> than it may appear from the above. The format string that is scanf's first argument is really a template for the input you want to read, where literal characters (as opposed to the %-format-specifiers) must literally match the input. So to read an input line like:
dist (42,7.5)
... you could use the call scanf("dist (%lf,%lf)",&x,&y);, and the "dist (" would literally have to appear in the input, followed by a floating point value, and so on. One gotcha is that while whitespace in front of the input matching %lf is skipped, whitespace in front of the literal text, like "dist (" is not. However, a literal space in the format string matches any number of spaces, including 0. So by putting a space before "dist", we skip over any leading whitespace, including newlines. It also means that the space between "dist" and "(" is optional in the input: there can be no spaces in between, one space, or many spaces.

ex1.c compile & run
$ clang -o ex4 ex4.c -lm
$ ./ex4
command: dist (2,3)
3.605551
command: dist (-3,9.2)
9.676776
command:   dist  (  1, 1.2)
1.562050
command: dist(2.2,9.8)
10.043904
command: x
bye.

struct gotcha

When you define a struct Foo in C, the type name is not "Foo", but rather "struct Foo".
struct Foo {
  int k;
  char c;
};

void print(struct Foo f) {
  printf("%d:%c\n",f.k,f.c);
}

int main() {
  struct Foo joe;
  joe.k = 42;
  joe.c = 'f';
  print(joe);
  ...