Professional C++: Basics
Here we won't cover all aspects of C++, but only cover some important points and/or new features of C++.
Namespace
// in header file
namespace mycode {
void myfunc();
}
// in .cpp file
#include
#include "my_namespace.h"
void mycode::myfunc()
{
std::cout << "hello" << std::endl;
}
'using' should not be used too much, otherwise effect of namespace is eliminated. Also, don't use 'using' in header files, since this applies it to all files including this header file.
Nested namespaces: in C++17 there is a simplified way to write it:
namespace mylib {
namespace network {
namespace ftp {
// ...
}
}
}
// the above code can be simplified as:
namespace mylib::network::ftp {
// ...
}
Namespace alias
namespace myftp = mylib::network::ftp;
myftp::ftputil::send(...);
Enum & Strongly Typed Enum
'enum' type is not type safe, it's represented as int:
enum Pet {dog, cat};
enum Color {red, blue, yellow};
if (dog == red)
cout << "dog == red !!!" << endl;
if (dog == 0)
cout << "dog == 0" << endl;
Enums can be made type safe by using 'enum class':
enum class Pet {dog, cat}; // dog & cat cannot be used directly, they have to be prefixed with 'Pet::'
enum class Color : unsigned long {red, blue, yellow}; // you can even change the default 'int' to 'unsigned long'
if (Pet::dog == Color::red) // compiler error here
cout << "dog == red !!!" << endl;
if (Pet::dog == 0) // compiler error here
cout << "dog == 0" << endl;
if (Pet::dog == Pet::cat) // OK, returns false
cout << "dog == cat" << endl;
Initializer for if statement
C++17: if can contain an initializer, and the variable defined here can only be used in the condition and the following if body:
if (Staff tom = getStaff("Tom"); tom.getAge() > 30) {
cout << "Tom is older than 30. He's " << tom.getAge() << " years old." << endl;
}
iDog: is it only me? I really feel that this looks ugly.
Switch
Types can be used in switch:
- int, or type that can be converted to int
- enum, or strongly typed enum
'case' must be followed by a constant.
'switch' can also contain an initializer like 'if'.
Fallthrough can generate a warning unless the case has an empty body. [[fallthorugh]] attribute can be specified to explicitly specify that it's an intentional fallthrough.
switch(color) {
case Color::gray:
// do something about gray
[[fallthrough]];
case Color::black:
// do something about gray and black
break;
case Color::red: // here '[[fallthrough]]' is not needed
case Color::blue:
// do something
break;
// ...
}
iDog: Personally I feel attributes weird... But unfortunately it has been introduced to many languages (C++, C#, and annotation in Java).
Function Return Type Deduction
You can ask the compiler to figure out the return type of a function automatically:
auto add(int n1, int n2)
{
return n1 + n2;
}
This can also be used on recursive calls, but in this case, the first return statement must be a non-recursive call.
iDog: why not 'var' as that in Java & C#?
Current Function’s Name
Every function has a local predefined variable __func__ containing the name of it:
void myfunc()
{
std::cout << "Entering function " << __func__ << std::endl;
// ...
}
C-style array
int array1[10]; // num must be const
int array2[10] = {}; // initialize all elements with default value (here it's 0)
int array3[10] = {1, 2}; // first 2 elements initialized as 1 & 2, all other ones 0
int array4[] = {1, 2, 3, 4, 5}; // array with 5 elements
size_t size = std::size(array4); // need to include <array>, available in C++17
size = sizeof(array4) / sizeof(array4[0]); // old way
C++ style arrays
std::array: size is fixed.
#include <array>
std::array myArray = {1, 2, 3, 4, 5};
for (size_t i = 0; i < myArray.size(); ++i)
cout << "value #" << i << " is " << myArray[i] << endl;
Advantages:
- it won't be automatically cast to a pointer, to avoid many bugs
- it has iterators
- it knows its size
std::vector: size is not fixed.
#include <vector>
std::vector<int> myVector = {1, 2, 3};
myVector.push_back(4);
myVector.push_back(5);
for (size_t i = 0; i < myVector.size(); ++i)
cout << "value #" << i << " is " << myVector[i] << endl;
Structured Binding
This allows you to declare multiple variables and initialize them with elements from an array/struct/pair/tuple.
std::array myArray = {1, 2, 3};
auto [a, b, c] = myArray; // must use 'auto' (cannot use 'int' here); number should be the same
struct Point {
int x; int y; int z;
Point (int x, int y, int z) {
this->x = x;
this->y = y;
this->z = z;
}
};
Point pt(1, 2, 3);
auto [x, y, z] = pt; // use all the non-static public members
Range-based for loop
This kind of for loop works for C-style arrays, initializer lists, and containers that has begin() and end() returning iterators.
std::array myArray = {1, 2, 3};
for(int i : myArray)
std::cout << i << std::endl;
int sum(int (&list)[3]) { // here the size of the array should be retained. so 'int list[3]' won't work.
int s = 0;
for (auto i : list)
s += i;
return s;
}
int a[] = {1, 2, 3};
auto s = sum(a); // error if size of a is not 3
Initializer List
Initializer list makes it easy to write functions that accept variable number of arguments of the same type.
#include <initializer_list>
using namespace std;
int sum(initializer_list<int> list) {
int s = 0;
for(auto i : list)
s += i;
return s;
}
int a = sum({1, 2, 3});