A simple C++ statement
is each of the individual instructions of a program, always end with a semicolon(, and are executed in the same order in which they appear in a program.
The if keyword is used to execute a statement or block, if, and only if, a condition is fulfilled. Its syntax is :
if (x == 100)
cout << "x is 100";
if (x == 100)
{
cout << "x is ";
cout << x;
}
Selection statements with if can also specify what happens when the condition is not fulfilled, by using the else keyword to introduce an alternative statement. Its syntax is :
if (x == 100)
cout << "x is 100";
else
cout << "x is not 100";
Several if + else
structures can be concatenated with the intention of checking a range of values.
if (x > 0)
cout << "x is positive";
else if (x < 0)
cout << "x is negative";
else
cout << "x is 0";
Loops repeat a statement a certain number of times, or while a condition is fulfilled. They are introduced by the keywords while
, do
, and for
.
The simplest kind of loop is the while-loop. Its syntax is:
while (expression) statement
The while-loop
simply repeats statement
while expression
is true
. If, after any execution of statement, expression is no longer true, the loop ends, and the program continues right after the loop.
A thing to consider with while-loops is that the loop should end at some point, and thus the statement shall alter values checked in the condition in some way, so as to force it to become false at some point.
A very similar loop is the do-while loop, whose syntax is :
do statement while (condition);
It behaves like a while-loop
, except that condition is evaluated after the execution of statement
instead of before, guaranteeing at least one execution of statement, even if condition is never fulfilled.
The for loop is designed to iterate a number of times. Its syntax is:
for (initialization; condition; increase) statement;
Like the while-loop
, this loop repeats statement
while condition
is true. But, in addition, the for loop
provides specific locations to contain an initialization
and an increase
expression, executed before the loop begins the first time, and after each iteration, respectively.
Therefore, it is eapecially useful to use counter variables as condition
.
It may be useful to execute more than a single expression as any of initialization
, condition
, or statement
.
Range-based for loop
The for-loop has another syntax, which is used exclusively with ranges:
for (declaration : range) statement;
This kind of for loop
iterates over all the elements in range, where declaration declares some variable able to take the value of an element in this range
.
Ranges
are sequences of elements, including arrays, containers, and any other type supporting the functions begin and end.
The break statement
break leaves a loop, even if the condition for its end is not fulfilled. It can be used to end an infinite loop, or to force it to end before its natural end.
The continue statement
The continue statement causes the program to skip the rest of the loop in the current iteration, as if the end of the statement block had been reached, causing it to jump to the start of the following iteration.
The goto statement
goto allows to make an absolute jump to another point in the program. This unconditional jump ignores nesting levels, and does not cause any automatic stack unwinding(堆栈解退).
The destination point is identified by a label, which is then used as an argument for the goto statement.
A label is made of a valid identifier followed by a colon (:
).
goto is generally deemed a low-level feature, with no particular use cases in modern higher-level programming paradigms generally used with C++.
The syntax of the switch statement is a bit peculiar. Its purpose is to check for a value among a number of possible constant expressions. It is something similar to concatenating if-else
statements, but limited to constant exprssions.
Its most typical syntax is :
switch (expression)
{
case constant1:
group-of-statements-1;
break;
case constant2;
group-of-statements-2;
break;
.
.
.
default:
default-group-of-statements
}
It works in the following way: switch
evaluates expression
and checks if it is equivalent to constant1
; if it is, it executes group-of-statements-1
until it finds the break statement. When it finds this break
statement, the program jumps
to the end of the entire switch
statement (the closing brace).
In C++, a function is a group of statements that is given a name, and which can be called from some point of the program. The most common syntax to define a function is:
type name (parameter1, parameter2, ...) {statements}
Where :
type
is the type of the value returned by the function
name
is the identifier by which the function can be called
parameters
(as many as needed): Each parameter consists of a type
followed by an identifier
, with each parameter being seperated from the next by a comma.
statements
is the function’s body. It is a block of statements surrounded by braces {} that specify what the function actually does.
The syntax shown above for functions:
type name (argument1, argument2 ... ) {statements}
Requires the declaration to begin with a type. This is the type of the value returned by the function. But what if the function does not need to return a value? In this case, the type to be used is void
, which is a special type to represent the absence of value.
The void
can also be used in the function’s parameter list to explicitly specify that the function takes no actual parameters
when called.
There is a catch: If the execution of main
ends normally without encountering a return
statement the compiler assumes the function ends with an implicit return statement:
return 0;
And this only applies to function main
for historical reasons. All other functions with a return type shall end with a proper return statement that includes a return value, even if this is never used.
Normally modification of variables passed to a function which within the function has no effect on the values of the variables, because variables themselves not passed to the function on the call, but only copies of their values at that moment.
In certain cases, though, it may be useful to access an external variable from within a function. To do that, arguments can be passed by reference
, instead of by value
.
To gain access to its arguments, the function declares its parameters as references.
In C++, references are indicated with an ampersand (&)
following the parameter type
, as in the parameters taken by function.
When a variable is passed by reference, what is passed is no longer a copy, but the variable itself, the variable identified by the function parameter, becomes somehow associated with the argument passed to function, and any modification on their corresponding local variables within the function are reflected in the variables passed as arguments in the call.
Calling a function with parameters taken by value causes copies of the values to be made. This is a relatively inexpensive operation for fundamental types such as int
, but if the parameter is of a large compound type, it may result on certain overhead.
But the copy can be avoided altogether if both parameters are made references (function(string& param1, string& param2)
)
Arguments by reference
do not require a copy, the function operates directly on (aliases of) the strings passed as arguments, and at most, it might mean the transfer of certain pointers to the function. In this regard, the version of concatenate taking references is more efficient than the version taking values, since it does not need to copy expensive-to-copy strings.
On the flip side, functions with reference parameters are generally perceived as functions that modify the arguments passed, because that is why reference parameters are actually for.
The solution is for the function to gurantee that its reference parameters are not going to be modified by this function.
Calling a function generally causes a certain overhead (stacking arguments, jumps, etc…), and thus for very short functions, it may be more efficient to simply insert the code of the function where it is called, instead of performing the process of formally calling a function.
Preceding a function declaration with the inline
specifier informs the compiler that inline expansion is preferred over the usual funciton call mechanism for a specific function. This does not change at all the behavior of a function, but is merely used to suggest the compiler that the code generated by the function body shall be inserted at each point the function is called, instead of being invoked with a regular function call.
inline string concatenate (const string& a, const string& b)
{
return a + b;
}
Most compilers already optimize code to generate inline functions when they see an opportunity to improve efficiency, even if not explicitly marked with the inline specifier. Therefore, this specifier merely indicates the compiler that inline is preferred for this function, although the compiler is free to not inline it, and optimize otherwise.
In C++, functions can also have optional parameters, for which no arguments are required in the call, in such a way that, for example, a function with three parameters may be called with only two. For this, the function shall include a default value for its last parameter, which is used by the function when called with fewer arguments.
In C++, identifiers can only be used in expressions onve they have been declared.
The same applied to functions. Functions cannot be called before they are declared. This why the functions were always defined before the main
function.
Recursivity is the property that functions have to be called by themselves. It is useful for some tasks, such as sorting elements, or calculating the factorial of numbners.
In C++, two different functions can have the same name if their parameters are different; either because they have a different number of parameters, or because any of their parameters are of a different type.
Two functions with the same name are generally expected to have -at least- a similar behavior.
Two overloaded functions (i.e., two functions with the same name) have entirely different definitions.
A function cannot be overloaded only by its return type. At least one of its parameters must have a different type.
Overloaded functions may have the same definition(which means same statement insdie the function).
Function could be overloaded with different parameter types, but with the exact same body.
Function could be overloaded for a lot of types, and it could make sense for all of them to have the same body. For cases such as this, C++ has the ability to define functions with generic types, known as function templates.
Defining a function template follows the same syntax as a regular function, except that it is preceded by the template
keyword and a series of template parameters enclosed in angle-brackets <>
:
template
The template parameters are a series of parameters separated by commas. These parameters can be generic template types by specifying either the class
or typename
keyword followed by an identifier. This identifier can then be used in the function declaration as if it was a regular type.
template
SomeType sum (SomeType a, SomeType b)
{
return a + b;
}
It makes no difference whether the generic type is specified with keyword class
or keyword typename
in the template argument list (they are 100% synonyms in template declarations).
In the code above, declaring SomeType
(a generic type within the template parameters enclosed in angle-brackets) allows SomeType
to be used anywhere in the function definition, just as any other type; it can be used as the type for parameters, as return type, or to declare new variables of this type.
In all cases, it represents a generic type that will be determined on the moment the template is instantiated.
Instantiating a template is applying the template to create a function using particular types or values for its template parameters. This is done by calling the function template, with the same syntax as calling a regular function, but specifying the template arguments enclosed in angle brackets:
name
x = sum(10, 20);
The function sum
is just one of the possible instantiations of function template sum
. In this case, by using int as template argument in the call, the compiler automatically instantiates a version of sum
where each occurrence of SomeType
is replaced by int
, as if it was defined as :
int sum (int a, int b)
{
return a + b;
}
k = sum(i,j);
h = sum(f,g);
// it is possible to instead simply write
k = sum(i,j);
h = sum(f,g);
without the type encloed in angle brackets, Naturally, for that, the type shall be unambiguous. If sum is called with arguments of different types, the compiler may not be able to deduce the type of T auytomatically.
Templates are a powerful and versatile feature, they can have multiple template parameters, and the function can still use regular non-templated types.
The template parameters can not only include types introduced by class or typename, but can also include expressions of a particular type.
// template arguments
#include
using namespace std;
template
T fixed_multiply (T val)
{
return val * N;
}
int main() {
std::cout << fixed_multiply(10) << '\n';
std::cout << fixed_multiply(10) << '\n';
}
The second argument of the fixed_multiply
function template is of type int
. It just looks like a regular function parameter, and can actually be used just like one.
But there exists a major difference: the value of template parameters is determined on compile-time to generate a different instantiation of the function fixed_multiply
, and thus the value of that argument is never passed during runtime: The two calls to fixed_multiply
in main
essentially call two versions of the function: one that always multiplies by two, and one that always multiplies by three. For that same reason, ***the second template argument needs to be a constant expression (it cannot be passed a variable)***.
Named entities, such as variables, functions, and compound types need to be declared before being used in C++. The point in the program where this declaration happens influences its visibility:
AN Entity declared outside any block has global scope, meaning that its name is valid anywhere in the code. While an entity declared within a block, such as a function or a selective statement, has block scope, and is only visible within the specific block in which it is declared, but not outside it.
Variables with block scope are known as local variables.
In each scope, a name
can only represent one entity. The visibility of an entity with block scope extends until the end of the block. Nevertheless, an inner block, because it is a different block, can re-utilize a name existing in an outer scope to refer to a different entity; in this case, the name will refer to a different entity only within the inner block, hiding the entity it names outside.
Only one entity can exist with a particular name in a particular scope. This is seldom a problem for local names, since blocks tend to be relatively short, and names have particular purposes within them, such as naming a counter variable, an argument, etc…
But non-local names bring more possibilities for name collision, especially considering that libraries may declare many functions, types, and variables, neither of them local in nature, and some of them very generic.
Namespaces allow us to group named entities that otherwise would have global scope into narrower scopes, giving them namespace scope
. This allows organizing the elements of programs into different logical scopes referred to by names.
The syntax to declare a namespacees is:
namespace identifier
{
named_entities
}
Where identifier
is any valid identifier and named_entities
is the set of variables, types and functions that are included within the namespace.
namespace myNamespace
{
int a, b;
}
In this case, the variables a
and b
are normal variables declared within a namespace called myNamespace
.
These variables can be accessed from within their namespace normally, with their identifier (either a
or b
), but if accessed from outside the myNamespace
namespace they have to be properly qualified with the scope operator ::
. For example, to access the previous variables from outside myNamespace
they should be qualified like :
myNamespace::a
myNamespace::b
Namespaces are particularly useful to avoid name collisions.
Namespaces can be split: Two segments of a code can be declared in the same namespace:
namespace foo {int a;}
namespace bar {int b;}
namespace foo {int c;}
Namespaces can even extend across different translation units (i.e., across different files of source code).
The keyword using
introduces a name into the current declarative region (such as a block), thus avoiding the need to qualify the name.
using namespace std;
using std::cout;
using and using namespace have validity only in the same block in which they are stated or in the entire source code file if they are used directly in the globle scope.
// using namespace example
#include
using namespace std;
namespace first
{
int x = 5;
}
namespace second
{
double x = 3.14159;
}
int main() {
{
using namespace first;
cout << x << '\n';
}
{
using namespace second;
cout << x << '\n';
}
return 0;
}
Existing namespaces can be aliased with new names, with the following syntax:
namespace new_name = current_name;
All the entities (variables, types, constants, and functions) of the standard C++ library are declared within the std
namespace.
The storage for variables with globle or namespace scope is allocated for the entire duration of the program. This is known as static storage, and it constracts with the storage for local variables (those declared within a block).
These use what is known as automatic storage.
The storage for local variables is only avaiable during the block in which they are declared; after that, that same storage may be used for a local variable of some other function, or used otherwise.
But there is another substantial difference between variables with static storage and variables with automatic storage:
Variables with static storage (such as globle variables) that are not explicitly initialized are automatically initialized to zeroes.
Variables with automatic storage (such as local variables) that are not explicitly initialized are left uninitialized, and thus have an undermined value.