C++学习系列三:Statements and Flow Control||Functions||Overloads and Templates||Name Visibility

  • Statements and flow control

    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.

    1. Selection Statements: if and else

      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";
      
    2. Iteration Statements (loops)

      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 while loop

        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.

      • The do-while loop

        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

        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.

      • Jump statements
        1. 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.

        2. 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.

        3. 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++.

      • Another selection statement : switch

        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).

  • Functions

    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.

    Functions with no type, The use of void

    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.

    The return value of main

    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.

    Arguments passed by value and by reference

    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.

    Efficiency considerations and const references

    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.

    Inline Functions

    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.

    Default values in parameters

    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.

    Declaring Functions

    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

    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.

  • Overloads and templates

    • Overloaded functions

      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.

    • Function templates

      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 <template-parameters> function-declaration

      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 <class SomeType>
      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 <template-arguments> (function-arguments)

      x = sum<int>(10, 20);
      

      The function sum<int> 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<int>(i,j);
      h = sum<double>(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.

    • Non-type template arguments

      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 <iostram>
      using namespace std;
      
      template <class T, int N>
      T fixed_multiply (T val)
      {
          return val * N;
      }
      int main() {
          std::cout << fixed_multiply<int,2>(10) << '\n';
          std::cout << fixed_multiply<int,3>(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)***.

  • Name visibility

    • Scopes

      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.

    • Namespaces

      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).

    • using

      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 <iostream>
      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;
      }
      
      
    • Namespace aliasing

      Existing namespaces can be aliased with new names, with the following syntax:

      namespace new_name = current_name;

    • The std namespace

      All the entities (variables, types, constants, and functions) of the standard C++ library are declared within the std namespace.

    • Storage classes

      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.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值