Skip to content

Instantly share code, notes, and snippets.

@iankronquist
Last active May 7, 2017 00:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save iankronquist/4655aea4e72bb161536ef14922dd886b to your computer and use it in GitHub Desktop.
Save iankronquist/4655aea4e72bb161536ef14922dd886b to your computer and use it in GitHub Desktop.
A Young Lady's C++ Primer

A Young Lady's C++ Primer

(I have been enjoying The Diamond Age, thank you)

C++ was developed in 198X by Bjourne Strousap. It is an improved version of the venerable C programming language. C is excellent at describing low level details in a way which is portable across computers. It is the most influential language of our lifetimes, but unless you're writing an operating system, a hypervisor (AKA Virtual Machine Monitor), or working on an embedded system on a tiny ass microcontroller, it's probably not the right tool for the job.

C++ is based on the idea that you can take C and add "zero cost abstractions", that is higher level concepts which are still efficient. Personally, I don't believe that any abstraction is zero cost, but all of the abstractions in C++ are incredibly cheap.

C++ was originally a strict superset of C -- all of the useful C code which had been written was valid C++. Things have changed since then, and the language standards have diverged the slightest bit, but it is still possible to tell the C++ compiler to treat some code as C and incorporate it into a C++ program.

Now, there are several different standards of C++, and many variations. One might say there are many dialects. Most notable are the C++99 and C++11 standards named after the years they were finalized. The C++17 standard is due this year but it will be years before it is widespread in the industry.

One of the greatest flaws of C++ is that it is a servant of many masters. Low level programmers want "C with classes and nothing else". Application programmers want "compiled Python" and there is a wide spectrum in between. The many features of C++ make it powerful, but also a beast.

This introduction will be faster paced and have fewer analogies than the previous one. C++ is just another programming language, and almost everything you've learned so far in Python has an analogous concept in C++.

Getting a C++ compiler on Windows

There are four major C++ compilers:

  1. GCC: The venerable old open source GNU Compiler Collection.
  2. Clang: A newer open source compiler with better guts and better error messages.
  3. MS Visual C++: The MS compiler. Closed source and really only useful with GUI tools like MS Visual Studio.
  4. The Intel compiler: Closed source and not free, but your school might have a license. Does fancy architecture specific optimizations to make the code go fast on a specific machine.

I'm going to assume you're using GCC from some sort of terminal. If you have clang, just replace everywhere I say g++ with clang++, since they're almost interchangeable.

If you're using Visual Studio and MSVC++ heaven help you, but you should be able to ignore the command line bits and figure out how to use Visual Studio yourself.

I think the easiest thing to do will be to use this clang installer:

http://llvm.org/builds/

If you want to get GCC, install Cygwin and select the GCC package in the installer:

https://www.cygwin.com/

Cygwin will give you a bash shell too.

You can also use Visual Studio, run linux in a VM, or use PUTTY to connect to a remote Linux machine. Feel free to ask me about the Linux options.

If you use Visual Studio you'll have to figure out that program yourself. You can ignore all the command line stuff but the code should still work.

The Basics of C++'s Type System

In C++ you have to explicitly mark the type of a variable when you declare it (Um. This is a lie, but a convenient one for now).

There are several basic types:

// Single line comments look like this
/*
Multi-line comments look like this
*/

// Signed numeric types. Numbers with fixed upper and lower bounds.
char character = 3;
short short_number = 1;
int number = 4;
long long_number = 1;
long long long_number = 5;

// Floating point types. Used for scientific calculations.  Have interesting
// precision properties and are useful when you have rationals with a decimal
// point.
float my_floating_point_number = 3.141592;
double double_precision_floating_point_number = 1.691;
double double double_double_precision_floating_point_number = 1.414;

// True or false.
bool boolean_value = true;
bool another_boolean_value = false;

The sizes of the numeric types follow this specification: char <= short <= int <= long <= long long

Where char is the smallest addressable unit of memory, typically one byte.

In practice on x86_64 bit machines they are like this:

signed char: one 8 bit byte
short: 16 bits
int: 32 bits
long: 64 bits (32 bits on 32 bit machines)
long long: 64 bits

This is totally platform dependent and can change with the compiler, the operating system, and the computer architecture. You'll see this variety is very common in C and C++, which makes the language flexible, but occasionally causes odd bugs when someone makes an incorrect assumption or code is ported between platforms.

A signed character on x86_64 can take values between -2^7 and (2^7)-1. That's 2^8==256 different values.

There are also unsigned versions of the basic numeric types.

unsigned int unsigned_number;
unsigned long unsigned_long_number;
// etc.

An unsigned int has a range between 0 and (2^32)-1.

A quick note about chars. ASCII is a way of mapping the numbers from zero to 127 to characters which can be printed on the terminal.

chars are ASCII values and can be initialized like this:

char c = 'c'; // Initialize with a character literal. Must use single quotes.
char a = 65;  // Initialize with a numeric literal.

So chars are numbers which can be printed to the terminal as letters.

Also, for whatever dumb reason the signedness of chars is implementation defined. Different compilers may make char c; signed or unsigned. If you need to be sure of the type you will get, specify it explicitly:

signed char scare = -128;
unsigned char you_care = 255;
char c; // Signedness depends on the compiler. Thanks a bunch.

Signedness in Binary

This section isn't about the language, but rather its implementation.

Now, you might ask, how do we represent a signed number in binary? There's two ways to do it, a dumb way called one's complement, and a smart way called two's complement. We'll go over the dumb way first for didactic reasons.

One's complement is dead simple:

for a 8 bit number, take the top bit and make it the sign bit. Here's a quick demo:

decimal |  binary
-----------------
0       | 0000000
1       | 0000001
2       | 0000010
3       | 0000011
-1      | 1000001
-2      | 1000010
-3      | 1000011
-0???   | 1000000

So we see that this system kind of works, but we have two representations of zero, positive zero and negative zero. This is nonsense.

So the sign bit is kind of handy, but we have room for one more number. We can do something clever though.

Consider what happens when you have the 8 bit binary number 0000000 and you subtract one. I think you know that it rolls over, like the clock, and you get 11111111. We make that number -1. And what happens when we subtract one from that? 11111110. Okay, but what's the lowest we can count to then? 10000000. That only has the top bit flipped, so it must be negative, and it's 128 steps less than zero so it must be -128. If you subtract one again you loop around to 01111111 or 127.

decimal |  binary
-----------------
127     | 0111111
3       | 0000011
2       | 0000010
1       | 0000001
0       | 0000000
-1      | 1111111
-2      | 1111110
-3      | 1111101
-128    | 1000000

There's a handy trick for reversing the sign of a two's complement number with bitwise arithmetic. Take a number x, subtract one, and invert all the bits. In C this looks like ~(x-1).

Two's complement is a simple and clever way to represent negative numbers in binary and is pretty must universally used today.

One last detail about C++ the language. In C++ the standard specifies that when unsigned integers overflow they go back to zero. However, for signed integers the behavior isn't written into the standard. This is called undefined behavior, and it's quite the tricky devil, it crops up in all sorts of unexpected places.

Functions

Now that we have types out of the way we can talk about functions. In C and C++ they have a peculiar format:

// Function declaration, where it is named and its type is specified.
int foo(char bar, double baz);
/*
^   ^   ^
|   |   |
|   |   Argument declarations.
|   |
|   Function name. 
| Return value type.
*/

// Function definition, where the body is written out.
int foo(char bar, double baz) {
	// Function body goes here
	int something = bar + baz; return something / 0; }

It's pretty straightforward except that the return value goes first.

Functions which don't return a value can have the return type void:

int GLOBAL = 0;
void no_return_value() {
	GLOBAL += 1;
}

Functions must be declared or defined before they are used, like in Python. In C++ whitespace doesn't matter, so do whatever you want as long as it's readable. If you're modifying someone else's code try to make to use the same conventions and make it look consistent.

The main function and how to compile a program Every program starts with the

main function, which returns an integer. Now that we have the basics out of the way, let's write hello world:

// Save as hello_world.cpp Include the C++ I/O stream library.
#include <iostream> int main() {
	// Use the standard library (std) console output stream and the standard
	// library end line object.
	std::cout << "Hello world" << std::endl; return 0;
	
	}

At some point we'll get to why I think this totally sucks as a program.

To compile it run the command:

g++ hello_world.cpp -o hello_world.exe

It will output a program called hello_world.exe.

And to run your new program: ./hello_world.exe

Let's break this command down. The name of the compiler is g++. It takes at least one input C++ file. C++ files can end with the extensions .cpp, .cc, or .cxx. The -o flag specifies the file to output to. If you leave it off, the executable will be written to a file named a.out.

C++ Operators

C++ has a myriad of operators. Most are straightforward, so let's just knock them out.

+: Addition                      1 + 1
-: Subtraction                   3 - 2
*: Multiplication                5 * 8
/: Division                      13 / 8
&: Bitwise and                   21 & 13
|: Bitwise or                    24 | 21
^: Bitwise exclusive or (xor)    64 ^ 24
~: Bitwise negation              ~88
%: Modulo (remainder)            88 % 64
!, not: Logical negation         !true
&&, and: Logical and             true && false
||, or: Logical or               true || false

Easy as pie, you know all these from Python.

Like Python you can use shorthands like these:

int a = 4;
a += 3; // Same as a = a + 3; etc.
a -= 8;
a ^= 7;
a &= 7;
a |= 1;
a %= 7;
// etc.

Now for two more. For integers they are typically:

>>: Bitwise right shift.
<<: Bitwise left shift

Just like Python! You know most of this already.

But what about the << we saw in Hello World earlier? Well, it turns out that you can override the basic operators to change their behavior for your own classes in C++. This is handy if you want to implement a class for say, complex numbers, and you want to be able to use + to add them together. However, some dumbfuck decided to make << mean "write to file" and >> mean "read from file" when using an iostream object.

So when you're doing

std::cout << "Hello world" << std::endl;

you're taking the console output file stream (std::cout) and writing the string "hello world" to it, followed by the end of line object (std::endl). Operator overloading: useful but too often abused. More on how to do this later.

Now for four more operators. Two are subtle, one is weird.

int a = 2;
// Postfix increment operator. Add one to a after the statement is done.
a++; // Same as a += 1;
// Prefix increment operator. Add one to a before the statement is done.
++a;

a = 2;
int b = a++; // b is 2, a is 3
a = 2;
int c = ++a; // c is 3, a is 3

// There are also prefix and postfix decrement operators.
a--; 
--a;

Finally, the ternary operator. This one takes three arguments!

condition ? true_case : false_case;

The ternary operator acts kind of like an if statement. If the condition is true, it gives the value from the true case. If it's false, it gives the value in the false case. The difference is that if statements don't give a value, they just conditionally run the statements.

int a = condition ? 1 : 2;

I know we haven't seen an if statement yet, but don't be surprised to learn that this is the same as:

int a;
if (condition) {
	a = 1;
} else {
	a = 2;
}

It's just a cleaner, more concise way to write the same thing.

Here's an example:

int apple_count = 2;
std::string plural_suffix = (apple_count == 1) ? "s" : "";
std::string message = std::string(apple_count) + " apple" + plural_suffix;

If apple_count is one, this will print 1 apple. If it's two it will print 2 apples.

I recommend you take a few minutes and play around with these operators, especially the prefix/postfix operators and the ternary operator. That's the best way to learn.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment