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.
L'a'; // type of this character literal is wchar_t
int*
char[]
double&
// 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)
const
qualifier
const
is ignored when we copy an object because copying an object doesn’t change the copied object.const
is never ignored. When we copy an object, both objects must have the same low-level const
qualification or there must be a conversion between the types of the two objects
const
to const
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
extern
keyword declares a variable without defining it except when it is initialized.extern int i; // declaration
extern int i = 1; // declaration and definition
int j; // declaration and definition
nullptr
*
modifies a variable rather than the typeint* 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
const
and pointersint 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
const
and references
int i = 5;
long &r = i; // compile error
const long &s = i; // correct
const long *t = &i; // compile error
const int &r2 = 100; // correct
auto
type specifier (C++11)Let the compiler infer the type for us
const
in the type of the initializer will be ignoredconst auto
if we want the deducted type has a top-level const
auto &
if we want a reference to the deducted type. In this case, the top-level const
in the initializer is
not top-level any more because we bind a reference to an initializer. So, that const
will not be ignored.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)
void
means no return objectint 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 */
const
is ignored for function overloading, low-level const
is significantRecord 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;
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
static
modifier in both C and C++ (static variables are restricted in the file)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
using
directive
using namespace X;
make names in X
accessible as in the current scopeusing X::a;
only make a
in the X
scope accessible in the current scopeusing
on namespace std
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
}
ios_base
: .setf(flag, mask)
flag
: aios_base
:
std::hex
std::dec
std::
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
std::string
[]
for speedat()
when you need range checkingfind()
(and its variants) to locate values in a string+
, +=
, insert
and append
for concatenating strings\0
is a valid character in C++, but treated as the terminator in Cisalpha
, isdigit
, … from C are provided in <ctype.h>
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
static_cast
: basic type checks at compile timereinterpret_cast
: low level reinterpretation of the bits, e.g. a pointer to a longconst_cast
: cast away the const, mostly for portability onlydynamic_cast
: capability check at runtimedouble 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 */
}
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
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.