Review of C++

Oct 11, 2017

In this article, I try to summarize the most of the distinct features or confusing aspects of the C++ programming language (C++14) as a quick review for intermediate-level programmers. So, I assume the reader is already capable of intensive programming with C, Python or Java and understand the basic concepts of programming languages.


Overview

Types

L'a'; // type of this character literal is wchar_t
// machine-dependent limits
std::numeric_limits<int>::max();
std::numeric_limits<int>::min();
signed int a = -1;
unsigned int b = 1;
std::cout << a * b << std::endl; // will print 4294967295 (2^32-1)
int i = 0;
const int j = i;        // top-level const is ignored
int k = j;              // top-level const is ignored

const int *p = &j;      // low-level const must not be ignored
const int *const q = p; /* left-most const is low-level, 
                           right-most const is top-level */
const int &r = j;       // const in reference types is always low-level
f() + g(); // order of evaluating f and g is undefined

Declarations vs Definitions

extern int i;     // declaration
extern int i = 1; // declaration and definition
int j;            // declaration and definition

Pointers

int* p, q; // same as int *p and int q, but no as int *q and int *q
int * (*fp)(int*) // pointer to a function taking an int* and returning an int*
int X::*pm        // pointer to a member of class X of type int
int i = 5;
const int *p = &i; /* *p = 5 doesn't compile (read only dereference), 
                      but i can be either a const or non-const object */
int const *q = &i; // same as p

References (C++ Only)

int i = 5;
long &r = i;         // compile error
const long &s = i;   // correct
const long *t = &i;  // compile error
const int &r2 = 100; // correct

The auto type specifier (C++11)

Let the compiler infer the type for us

auto i = 0, *p = &i;      // ok, i is int and p is int*
auto sz = 0, pi = 3.14;   // error: inconsistent types

int i;
const int *const p = i;
auto r = p;               // const int * const -> const int*
const auto s = p;         // const int* const

const int &j = 1;
auto u = j;               // const int & -> const int -> int
const auto v = j;         // const int
auto &w = j;              // const int & (low-level const kept)

Functions

int foo();      // OK: implicit empty parameter list
void bar(void); // OK: explicit empty parameter list
int i = 5;
      i     =     i     +   1;
// (Lvalue)    (Rvalue)
void swap(int i, int j);   /* wrong, copy values of actual arguments
                              (pass by value) */
// Two ways to fix it
void swap(int *i, int *j); // C style
void swap(int &i, int &j); /* C++ style (pass by reference)
                              Use C++ style wherever possible */
Record lookup(Phone p) {...};
Record lookup(const Phone p) {...};  /* error: redefinition, 
                                        because top-level const is ignored */

// The following overloading is correct: low-level const is not ignored
Record lookup(Phone &p) {...};       // (1)
Record lookup(const Phone &p) {...}; // (2)

Phone p1;
const Phone p2;
lookup(p1);     // call (1)
lookup(p2);     // call (2)
inline int min(int n, int m) {
  return (n < m) ? n : m;
}
std::cout << min(i, j) << std::endl; 
/* It will be expanded into something like the line below during compilation.
   So we need to put the definitions of inline functions in the header file   */
std::cout << ((i < j) ? i : j) << std::endl;

Scope

int i = 3;
for (int i = 0 ; i < 10; ++i){
    std::cout << i << std::endl; // this 'i' hides the upper scope 'i'
}
std::cout << i << std::endl;     // the upper scope 'i' is unchanged
namespace A{
    int a = 1;
    namespace B{
        int a = 2;
    }
    namespace C{
        int a = 3;
    }
}
// all these 'a's are different variables
std::cout << A::a << std::endl;
std::cout << A::B::a << std::endl;
std::cout << A::C::a << std::endl;

int i = 1;
namespace { // unnamed namespace creates a file scope
    int i = 2; // error, re-declare 'i'
    int j = 3;
}
std::cout << j << std::endl;   // correct
std::cout << ::j << std::endl; // also correct
namespace X {
    int i, j, k;
}
int k;

int foo(){
    int i = 0;
    using namespace X;
    i=1;        // local i
    j=2;        // X::j
    k=3;        // error, ambiguous k
    ::k=3;      // correct, explicitly use the global k
    X::k=3;     // correct, explicitly use k in namespace X
}

int bar(){
    int i = 0;
    using X::j;
    j=4;        // X::j
    using X::k; // hides the global k
    k=4;        // correct, X::k
}

Input/Output Streams

std::cout << 1331 << std::endl;                                // 1331
std::cout << std::hex << 1331 << std::endl;                    // 533
std::cout << 1331.123456 << std::endl;                         // 1331.12
std::cout.setf(std::ios::scientific, std::ios::floatfield);    
std::cout << 1331.123456 << std::endl;                         // 1.331123e+03
std::cout << std::setprecision(3) << 1331.123456 << std::endl; // 1.331e+03
std::cout << std::dec << 1331 << std::endl;                    // 1331
std::cout.fill('X');
std::cout.width(8);
std::cout << 1331 << std::endl;                                // XXXX1331
std::cout.setf(std::ios::left, std::ios::adjustfield);
std::cout.width(8);
std::cout << 1331 << std::endl;                                // 1331XXXX

Strings

Initialization

int i1 {1};     // C++11, list prepered
int i2 = {1};
int i3 = 1;
int i4(1);

int j1{};       // the default value used
int j2 = int{}; // a temporaty default value used

Type Conversions

double myDouble = 3.14;
int cast1 = (int)myDouble;              // c-style
int cast2 = int(myDouble);
int cast3 = static_cast<int>(myDouble); // recommended

unsigned short hash(void *p){
    unsigned long val = reinterpret_cast<unsigned long>(p);
    return (unsigned short) (val ^ (val >> 16)); /* XOR higher bits with lower bits
                                                    'short' is always 16-bits       */
}

Arrays

int a[10]; // C array

std::array<int, 4> b = { 0, 1, 2, 3 }; /* compile-time fixed size, 
                                          cannot hold reference types,
                                          need to be initialized */  
std::array<int, 4> c = b;

int a2[3][4] = {
    1, 2, 3, 4,
    5, 6, 7, 8,
    9, 10, 11, 12
};

// 3 ways to iterate 2D arryas

// (1) C-style a[i][j]

// (2) C-style pointer arithmetics (optionally with type alias or 'auto') (ommited)

// (3) C++11 iterators
for(auto p = std::begin(a2), pe = std::end(a2); p != pe; ++p) {
    for (auto q = std::begin(*p), qe = std::end(*p); q != qe; ++q)
        std::cout << *q << ' ';
    std::cout << std::endl;
}
std::string s("Hello");
for(auto &c : s) // can use reference types
    c = std::toupper(c);
std::cout << s << std:endl;  // HELLO

STL

A function template is a prescription for the compiler to generate particular instances of a function varying by type

template <typename T>
T min(T a, T b) {
    return a < b ? a : b;
}

I skip the rest of contents about STL since I have no plan to study it seriously right now. It seems quite complex especially considering C++11. Currently, a simple replacement of STL for C programming is GLib.

I am going to add some contents about smart pointers later, and the other contents will be added when I do have free time.