Only int main()
or int main(int argc, char **argv)
are allowed. The
third char **envp
parameter is not defined by the C++ standard and
should be avoid. Instead, extern char **environ
should be declared to
provide access to the program’s environment variables. The value of
argv[argc]
equals nullptr
.
The return type is int
, not void
. return
statement may be omitted,
and main
returns 0 in this case. When a C++ ends normally, dtors of
globally defined objects are activated. A function like exit
does not
normally end a C++ program and is therefore deprecated.
A prototype must be known for each funciton before it is called and the
call must match the prototype. return
must explicitly return int
in
main
. An empty parameter list indicates the total absence of
parameter, i.e. as with void
.
Implicit conversions from generic pointers void*
to typed pointers are not allowed.
The old style enum
has their values not restricted to the enum type
name itself, but to the scope where the enumeration is defined. Two
enumerations having the same scope cannot have identical names.
enum class
solves such problems. The value type used by enum class
can be specified instead of int
only.
enum class CharEnum : unsigned char {
NOT_OK,
OK
}
...
operator is allowed to show a sequence of symbols of a
enum class
.
case SafeEnum::Not_OK ... SafeEnum:OK:
cout << "Status is known\n";
In C++, all zero values are coded as 0.
NULL
should be avoided. 0
or NULL
pointer may lead to trouble with function
overloading since it’s actually int
or long
, not as a pointer void*
.
Use nullptr
instead since C++11.
In C, a NULL
constant is equal to 0
but the value of a pointer variable with
NULL
value is not necessarily represented as all zero bits. int *p = 0
is
equal to int *p = NULL
but p
is not guaranteed to be zero. This pointer
value is equal to 0
or NULL
. From C99 standard:
An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant.
Null pointers are equal to 0
or NULL
as the latter is converted to the former.
If one operand is a pointer and the other is a null pointer constant, the null pointer constant is converted to the type of the pointer.
default arguments must be known at compile-time (instead of finding these arguments in the implementation at run-time). Therefore, the default arguments must be mentioned at the function’s declaration.
Each C++ compiler which conforms to the ANSI/ISO standard defines the
symbol __cplusplus
(C++ standard version in use).
Normal C functions which are compiled and collected in a run-time library can be used in C++ programs.
extern "C" void c_function(type args)
or
extern "C"
{
// C header if needed
// C-declarations here
}
The combination of the predefined symbol __cplusplus
and the
possibility to define extern "C"
functions offers the ability to
create header files for both C and C++.
#ifdef __cplusplus
extern "C"
{
#endif
/* declaration of C-data and functions are inserts here */
#ifdef __cplusplus
}
#endif
The standard C header files are built in this manner and are therefore usable for both C and C++.
In C++, local variables can be defined and initialized within if-else
,
switch
, while
statements since .
The keyword typedef
is not required anymore when defining union
,
struct
or enum
defintions.
struct someStruct {
//
}
someStruct whatVar;
The scope of typedefs is restricted to compilation units. Therefore, typedefs are usually embedded in header files which are then included by multiple source files in which the typedefs should be used.
In practice, typedef
and using
can be used interchangeably.
typedef unsigned long long int FUN(double, int);
using FUN = unsigned long long int (double, int);
using FUN = auto (double, int) -> unsigned long long int;
- Expressions using postfix operators (like index operators and member selectors) are evaluated from left to right.
- Assignment expressions are evaluated from right to left
- operands of shift operators are evaluated from left to right.
The overloaded operator is evaluated like the built-in operator it overloads.
Attributes are used to inform the compiler about situations that are intentional but are by themselves for the compiler to issue warnings. Before standardization, they are commonly defined as compiler extensions.
[[fallthrough]]
: if falling through is intentional, this attribute should be used so that the compiler does not give a warning.
switch (selector) {
case 1:
case 2:
...
[[fallthrough]]; // no warning
case 3:
...
case 4: // a warning
}
[[maybe_unused]]
: applied to a class, typedef-name, variable, parameter, non-static data member, a function, an enumeration or an enumerator. No warning is generated when the entity is not used.[[nodiscard]]
: specified when declaring a function, class or enumeration. This attribute requires that the return value of a function may be ignored only when explicitly cast to void.[[noreturn]]
: used in functions likestd::terminate
,std::abort
.[[deprecated]]
/[[deprecated("reason")]]
:
int [[nodiscard]] importantInt()
importantInt(); // warning issued
const
requires the qualified object be non-modifiable, either resulting in a
compile-time error (directly) or an undefined behavior (indirectly).
In C++, const
may imply a compile-time constant expression, meant as a rvalue and
thus by default with internal linkage.
Variables declared const
can be used to specify the size of an array. Const
expressions may be used as case labels, enumerators, initializer expressions or
bit-sized fields.
In C, const
variables have external linkage by default and they are not
constant expressions.
#include <cstdio>
double counter = 50;
int main()
{
for (int counter = 1; counter != 10; counter++) {
printf("%d\n", ::counter / counter); //global `counter` divided by local `counter`
}
}
Some advantages of using streams are:
- Using insertion and extraction operators is type-safe. Old style functions may be given wrong format specifier. With streams there are no format strings.
- Insertion and extraction may be extended, allowing objects of classes to be inserted into or extracted form streams.
- Streams are independent of the media they operate on.
C++ extends the concept of initializer list by introducing the type
initializer_list<Type>
where Type
is replaced by the type name of
the values used in the initializer list.
Initializer lists are recursive, so they can be used with multidimensional arrays, structs and clases.
void values2(std::initializer_list<std::initializer_list<int>> iniValues)
{}
values2({{1, 2}, {2, 3}, {3, 5}, {4, 7}, {5, 11}, {6, 13}});
Implicit conversions are not allowed in initializer lists, making C++11 initializer syntax more type-safe.
As C++ requires that destruction of data members occurs in the opposite order as their construction it is required that, when using designated initialization, members are initialized in the order in which they are declared in their class or struct. A union can be initialized using designated initialization.
In C++, it is not allowed to reorder the initialization of members in a designated initialization list.
(C++2a) Bit fields is allowed them to be initialized by default by using initialization expressions in their definitions.
struct FirstIP4word
{
uint32_t version: 4 = 1; // version now 1, by default
uint32_t header: 4 = 10; // TCP header length now 10, by default
uint32_t tos: 8;
uint32_t length: 16;
};
With type inference, the programmer cares more about the behavior of the type in use rather than its formal type.
The keyword auto
can be used to simplify type definitions of variables
and return types of functions if the compiler is able to determine the
proper types of such variables or functions. It is no longer used as a
storage class specifier.
Plain types and pointer types are used as-is when declared auto
. A
reference’s basic type (without the reference, omitting const
and
volatile
) is used. If a reference is required, use auto&
or
auto&&
. Likewise, const
and/or pointer specifications can be used in
combination with the auto
keyword.
The declaration of such a function int (*intArrPtr())[10];
is rather
complex. Using auto
, it becomes
auto intArrPtr() -> int (*)[10];
which is called a late-specified return type.
(C++14) Late return type specifications are no longer required for functions returning auto, simply
auto autoReturnFunction();
in which case, all return values must have an identical type. Functions
merely returning auto
cannot be used before the compiler has seen
their definitions. So they cannot be used after mere declarations. When
such functions are implemented as recursive function, at least one
return statement must have been seen before the recursive call.
auto fibonacci(size_t n)
{
if (n <=1 )
return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
Usually, when functions need to return mutliple values, a return-by-argument construction is often used. When multiple vlaues should be returned from a function, a struct can be used.
struct Return {
int first;
double second;
};
Return fun()
{
return { 1, 12.5};
}
Return& fun2()
{
static Return ret{4, 5};
return ret;
}
The struct definition can completely be omitted if fun
returns a pair or
tuple. Instead of referring to the elements of the returned struct, pair
or tuple structured binding declarations can also be used. Struct data members are automatically
bound to variables.
auto [one, two] = fun();
auto&& [rone, rtwo] = fun();
auto& [lone, ltwo] = fun2();
There doesn’t have to be a function call!
auto const &[lone, ltwo] = Return{4, 5};
auto &&[lone, ltwo] = Return{4, 5};
for (auto &[year, amount, interest] : structArray)
cout << "Year " << year << ": amount = " << amount << '\n';
Before C++17, one has to use std::tie
to achieve similar results only with
tuples or pairs. There is no way similar to std::ignore
to discard a component with structured binding.
The object doesn’t even have to make its data member publicly available
- Plain arrays
- Initializer lists;
- standard containers
- any other type offering
begin()
andend()
functions returning iterators.
(C++20) range-based for-loop can have a init-statement.
Before using the condition clauses an initialization clause may be used to define additional variables (plural, as it may contain a comma-separated list of variables, similar to the syntax that’s available for for-statements).
Raw string literals start with an R
, followed by a double quote,
optionally followed by a label (which is an arbitrary sequence of
characters not equal to (
, followed by (
. The raw string ends at the
closing parenthesis ), followed by the label (if specified when starting
the raw string literal), which is in turn followed by a double quote.
The ugly syntax might seem strange at first, but consider Python’s triple quotation syntax, one has to find a way to distinguish quotation marks inside the string from the closing sequence (in Python’s case, the triple quotation cannot appear inside the string content). Custom labels give users a chance to define one’s own closing sequence to allow true raw strings in case something inside the string resembles the predefined closing sequence.
R"label(whatever raw string you want)label"
char const *noPrompt =
R"(
if (d_debug__)
s_out__ << '\n';
)";
Character literals are considered int
in C but of actual character types in C++.
This may affect function overloading resolution;
sizeof('a') // larger than 1 in C, but 1 in C++
Binary integral constants can be defined using the prefixes 0b
or
oB
. Available in C since C23.
There is a subtle issue to be aware of when converting applications
developed for 32-bit architectures to 64-bit architectures. When
converting, only long
types and pointer types change in size from 32
bits to 64 bits. int
remains at 32 bits.
L
as a prfix is used to indicate a character string whose elements are
wchar_t
. p
specifies the power in hexadecimal floating point
numbers, the exponential part is interpreted as a power of 2.
0x10p2 // 16 * 2^2 = 64
If a function should inform its caller about the success or failure of its task, let the function return a bool value. If the function should return success or various types of errors, let the function return enum values, documenting the situation by its various symbolic constants.
C++ supports 8, 16 and 32 bit Unicode encoded strings. Two new data
types are introduced: char16_t
, char32_t
representing UTF-16 and
UTF-32 respectively. A char
type value fits in a UTF-8 unicode value.
char utf_8[] = u8"This is UTF-8 encoded.";
char16_t utf16[] = u"This is UTF-16 encoded.";
char32_t utf32[] = U"This is UTF-32 encoded.";
char utf_8[] = u8"\u2018";
char16_t utf16[] = u"\u2018";
char32_t utf32[] = U"\u2018";
C++ prorams should merely use the new style C++ casts as they offer the compiler facilities to verify the sensibility of the cast.
https://stackoverflow.com/questions/573294/when-to-use-reinterpret-cast
The static_cast<type>(expression)
is used to convert ‘conceptually
comparable or related types’ to each other.
sqrt(static_cast<double>(x) / y);
cout << static_cast<int>(Enum::VALUE);
tolower(static_cast<unsigned char>(ch));
The static_cast
is used in the context of class inheritance to convert
a pointer to a derived class to a pointer to its base class. Also, use
static_cast
to convert void *
to an intended destination pointer.
A const\_cast(expression) expression is used to undo the const attribute of a (pointer) type.
The need for a const_cast
may occur in combination with functions from
the standard C library which traditionally weren’t always as const-aware
as they should.
Different from the static_cast
, whose actions are completely
determined compile-time, the dynamic_cast
’s actions are determined
run-time to convert a pointer to an object of some class.
reinterpret_cast
should only be used when it is known that the
information as defined in fact is or can be interpreted as something
completely different. Think of the reinterpret_cast
as a cast offering
a poor-man’s union: the same memory location may be interpreted in
completely different ways. Avoid this unless necessary.
reinterpret_cast<pointer type>(pointer expression)
cout.write(reinterpret_cast<char const *>(&value), sizeof(double)); // value is a double variable
bool is_little_endian() {
std::uint16_t x=0x0001;
auto p = reinterpret_cast<std::uint8_t*>(&x);
return *p != 0;
}