- Preprocessor directive - indicates that we are using a library or utility (
#include
,#define
,#ifdef
) - In C++, direct memory access is discouraged, instead we access memory indirectly through an object.
- There are some flags which the C compiler provides
-Werror
to convert warnings to errors.-Wall
to enable all type of warning triggers.-Wextra
enables some extra warning flags that are not enabled by-Wall
.-Wsign-conversion
warns for implicit conversions that may change the sign of an integer value, like assigning a signed integer expression to an unsigned integer variable.-ggdb
for debugging-O2 -DNDEBUG
for release builds
- A named object is a variable, and the name of the object is called an identifier.
- Types of Initialization
- Default
- Copy (int a = 5)
- Direct (int a(5))
- Brace (also called uniform initialization or list initialization) (int a{5} or int a = {5})
- Brace initialization disallows 'narrowing conversions'.
int width {4.5}
, this returns an error as fractional value can't fit into an int, while copy and direct initialization would simply drop the fractional part. - Remember that if you have
-Wall
flag specified as argument, any variable that is declared but not used in the program, will trigger an error. In C++17 standard however, you can use the[[maybe_unused]]
attribute to tell the compiler that the variable may not be used. - std::endl vs '\n'
- Using std::endl can be inefficient, but it serves to flush the output buffer. However, std::cout often flushes output anyways, so having endl to flush output is rarely important.
- Using '\n' can be faster, since it doesn't perform a flush operation.
- The C++ I/O library does not provide a way to accept keyboard input without the user having to press enter. For that in console applications, you can use 3rd party libraries like pdcurses, FXTUI, and cpp-terminal.
- Insertion operator (<<) and extraction operator (>>)
- Identifier naming rules:
- The identifier can not be a keyword. Keywords are reserved.
- The identifier can only be composed of letters (lower or upper case), numbers, and the underscore character.
- The identifier must begin with a letter (lower or upper case) or an underscore. It can not start with a number.
- C++ is case sensitive, and thus distinguishes between lower and upper case letters. nvalue is different than nValue is different than NVALUE.
- The number of operands that an operator takes as input is called the operator's arity (four types of arity - unary, binary, ternary, nullary(the throw operator))
- Operators are executed in order PEMDAS.
- All operators which define some behaviour (and not just return values) return their left operand.
- Statement vs Expression - Statements are used when we want the program to perform an action. Expressions are used when we want the program to calculate a value.
- main() is the only function that implicitly returns a value.
- forward declaration - tell the compiler about the existence of a function before we define the function's body. For this, use function declaration(also called function prototype).
- What would happen if you forward declare a function, but do not define it later on? - The compiler would function properly, and program compiles. However, the linker will complain that it cannot resolve the function call.
- Declarations that are not definitions are called pure declarations.
- Multiple files can be compiled together using the compiler with
g++ <file1> <file2> ... -o main
. While compiling, ensure that function declarations are made for each function used not in that particular file and sourced from someplace else. - The compilation can be in any order for multiple files. The linker is the one which connects the dots.
- Compilation of two files with same function names can cause a naming collision, and thus cause the linker to return an error. For reference, see name_coll1.cpp name_coll2.cpp, compiled with
gcc name_coll1.cpp name_coll2.cpp -o name_coll
. - Most naming collisions occur in two cases:
- Two (or more) identically named functions (or global variables) are introduced into separate files belonging to the same program. This will result in a linker error.
- Two (or more) identically named functions (or global variables) are introduced into the same file. This will result in a compiler error.
- Solution for naming collisions? Namespaces. A region that allows to declare names inside of it for the purpose of disambiguation.
- In C++, any name not defined inside a class, function or a namespace belongs to the global namespace (sometimes called global scope). Executable statements are not allowed in the global namespace.
- The
::
instd::cout
is the scope resolution operator. - The
using
directive is used to access namespaces without giving a namespace prefix. However, it is discouraged. - Translation phases
- The most noteworthy phase of translation is preprocessing, which involves the preprocessor. It scans for all preprocessor directives(instructions beginning with
#
) and perform the particular action. - Types of preprocessor directives:
#include
#define
- with and without substitution text#ifdef
/#if defined()
,#endif
,#ifndef
/#if !defined()
,#if 0
- conditional compilation preprocessor
- Directives defined in one code file do not have impact on other code files in the same project.
- Header files allow to put declarations in one location and import them when needed.
- Header files consist of two parts:
- Header guard
- Actual content, which comprises of forward declarations for all the identifiers.
- When using angled brackets, the compiler preprocessor searches in the
include directories
. With double quotes, it first looks in the current directory. - Duplicate function definitions can lead to compilation error, as there can be no duplicate definition in a file.
- Header guards can be used to deal with duplicate function. For reference, see header guards
- Modern compilers support a simpler, alternate form of header guards using the
#pragma
directive by using#pragma once
. Still, prefer header guards as they are more reliable, and pragma is not officially a part of C++.
- When printing info for debugging purposes, use
std::cerr
instead ofstd::cout
, asstd::cerr
is unbuffered. This ensures all debug output appears as soon as possible. - We can enable/disable debugging using preprocessor directives (
ifdef ENABLE_DEBUG
,endif
) - Another alternative is using a logger.