Published on

Statements

Overview

CPP offers conventional and flexible set of statements, usually all that is interesting or important is often the part of the expression or the declrations, while statements are often used as a way to specify the control flow of the program as opposed to some arthematic evaluation, the way expression are used to. Note that a declration is a statements, and an expression becomes a statement as soon as you terminate it with a semicolan. Plainly speaking unlike expression, a statement is not used to specify some value but is rather used to determine the order of evaluation of the program. For instance lets consider the following examples:

#include<iostream>
using namespace std;
int main(){
  int a = 12;
  int b = 13;
  if(a > b){cout << a << endl;}
  else{cout << b << endl;}
  return 0;
}

In the above example the conditional statement can be used to determine wheter the control flow of the statement corresponds to the execution of the if statement block or the else statement block. Conventionally in a procedular language however, everything that termintes with a semi-colan is considered as the statement and statements are often executed in the order of declration unless they have some controll flow defined on them. However, in certain case, a compiler might reorder the code to improve the performance, as long the result of such reordering on execution of the program remains the same as it would've been without it.

Statement Summary

Usually a statement can be one of the following type in CPP:

Declration;
expressions;
try{
    statement-list;
}catch(){
  statement-list;
}
{
  statement-list;
}
case constexpr-case-label: statement-list;
break;
continue;
if(condtional){statement-list}else{statement-list;}
while(predicate){statement-list;}
do{statement-list}while(predicate);
goto identifier;
identifier : statement;
iterative-statement:
  for(for init declration: expressions) statement-list;
  for(loopintialier; loop condition; loop counter) statement-list;

Here the statement list could corresponds to block or plausibly empty list of statements.

  • A declration is a statement and there is no assignment or procedular call statement, as each of them are considered as expressions.
  • A for-init statement must either be declration or expression statement. Note each of them must end in a semi-colan.
  • A for init declration must be declration for a singled untialized unintialized variable.

Declrations as Statements

The notion using declration as a statement allows us to intialize an indentifier as soon as it's being declared or the thread of execution reaches it declaration. This is trun, could be exploited to facilitate various other language construct such as const declration for which case providing an intializer is important so as to pertain to the default behavior of such intialization i.e. they can't be redeclared in the same scope and hence can't assume default value. Moreover, treating declarions as a statement also allows to do significant optimization in the way user-defined type are intialized, and prevent a host of bugs pertaining to the unintialized variables in the given scope. For instance lets consider the following:

#include<iostream>
using namespace std;
void declration_as_statements_for_optimizations(){
  float startTime = clock();
  for(size_t i = 0; i < 1e+4; i++){
    string res; //call to the default constructor.
    res = "abcdefghijklmnopqrstuvwxyz"; //assignment.
  }
  float endTime = clock();
  cout << ((endTime - startTime)/100) << endl;

  startTime = 0.0f;
  endTime = 0.0f;
  startTime = clock();
  for(size_t i = 0; i < 1e+4; i++){
    string res{"abcdefghijklmnopqrstuvwxyz"};
  }
  endTime = clock();
  cout << ((endTime - startTime) /100) << endl;
}
void declrations_to_Specify_const_values(){
#ifdef Errors
  const int i;
#endif
  const int i = 12;
}
void declaration_as_Statements(){
  declration_as_statements_for_optimizations();
  declrations_to_Specify_const_values();
}
int main(){
  declaration_as_Statements();
  return 0;
}

In the above mentione example the declration of string without an explicit intializer, i.e. with the call to the default constructor followed by the intializer is supposedly much less efficient than the direct intialization with the inteded values. As a matter of fact, for user-defined type postponing the declration until a suitable intializer is available could lead to significantlly better performance. As a matter of fact, the most common reason to used an intialized identifer could includes reasons like it being a large input buffer, for which case you know instance by measurement that redudantly intializing could have sever performance repercussions, or at best in cases where an identifier require a statement to specifies its default value.

#include<iostream>
using namespace std;
int error_count =0;
int error(const string& message){
  error_count += 1;
  cerr << message << endl;
  return error_count;
}
vector<int> fillVector(const vector<int>& v1){
  const int max = 10;
  int buff[max];
  int count{};
  for(int i; cin >> i;){
    if(i < 0){throw std::runtime_error{"only accepts positive values"}; }
    if(count == max){throw std::runtime_error{"Buffer Overflow"};}
    if(i >= 965){return v1; }
    buff[count++] = i;
  }
}
int main(){
  return 0;
}

The above code would eventually lead to buffer overflow if the user doesn't specify a value above 965, but such code should better be left to operations defined on a well-defined container type such as a vector.

Selection Statements

A selection statements as it names implies is generally used to execute a block of statements if a predicate is true, otherwise it executed the provided alternate statements. C++ offers two ways to define selection statements the if-else statements and the switch statements.

if Statements

For the if-else statements the if block is executed if the predicate provided in the if block is true otherwise the alternative controll flow or the block of the statement provided by the else block are executed. As such the predicate specified with the if statement should either be implicitly convertable to a boolean value to check the result of the predicate and decide the controll flow or should be explicitled converted into such such values. Consequently, by default an if predicate can only works with integral values, or values that can either implicitly or explicitly converted to a boolean literal. As such in an arthematic conversion a non-zero integral value often converts to true while a zero integral value converts to false, much in the same spite a pointer could also be converted to a boolean literal where a non-null pointer converts to false and a null pointer converts to true. Note however, since a plain enumerator can be implicitly converted to integral value and hence the boolean literal value it can be directly within the if predicate, however, using an enum class based enumerator within such statement without any explicit integral conversion is often not allowed as such enumerator can't be implicitly converted into integral value and are often tightly scopped to the enumeration of which they're a part.

  • As such the most common use of conditional statement is check the result of some logical expression, where the logical expression can be expressed with the combination basic unit of any given expressions i.e. the corresponding name, operators, values, etc., that eventually results in a value to be checked against a predicate.
  • One can also declare a name within a conditional statement, for which case, the scope of such identifier is that of the statement itself, and the value of the such statement is the value of the identifier itself.
  • Moreover, much like most of the statement, a conditional statement too introduce its own scope, meaning any identifier defined within the scope of such statement is accessible only with that scope. Consequently, any identifier defined with the scope of a if statement is only accessible within the corresponding scope of the statement as opposed to being accessible within the scope of the else statement. Or to put it differently, if one tend to make an indetifier accessible within the scope of both the conditional statements, one should declare it outside the scope of each of controll flow of such conditional statements, or the enclosing socpe of the conditional statement itself.
  • A conditional statement can also be expressed as a conditional expression using the corresponding tenary operator, which for the expression ex1 and ex2 is generally evaluated as following: somepredicate ? expression1 : expression2; i.e. for some predicate it evluates to an expression1 if the predicate happens to be true otherwise it evaluate to expression2. Consequently, a conditional statement is often used as a short hand notation for a selection statement when we need to conditioanlly evaluates some constexpr based on predicate. Consequently, the return type for such conditiaonl expression should either be implicitly convertable to the returns type of the other specified expressions, or should confirm with the type of other such specified expression.

#include<iostream>
using namespace std;
template<class T>
T abs(T a){
    return a ? -1 * a : a;
  }
void conditional_statements(){
  int i = abs(-12);
  cout << i << endl;
}
bool is_open(FILE* fp){
  char buffer[1024];
  const char* buffer1 ="random things";
  if(fp != nullptr){
    fread(buffer, sizeof(buffer), 1, fp);
    fwrite(buffer, sizeof(buffer), 1, fp);
  }else{
    return false;
  }
  cout << buffer << endl;
  return true;
}
void selection_Statements(){
  int a, b;
  cin >> a >> b;
  bool predicate = a >= b;

  if(predicate){cout << "A is greater than B" << endl;}
  else{cout << "B is greater than A" << endl;}
}
FILE* readFile(){
  FILE* fp = fopen("1.txt", "r+");
  cout << is_open(fp) << endl;
  return fp;
}
int calc_div_sum(vector<int>& v1, int divident){
  int sum = 0;
  for(auto& i: divisor){
      if(auto d = i){ //evaluates only if d is non-zero
        //statement introduced within the conditional statement has the scope of the statement itself, and the value of such statement is the value of the conditional statement.
        sum += (divident / i);
        int k = i >> 2;
        sum *= k;
    }else{
#ifdef Error
        cout << k << endl; //error k is not accessible within the given scope.
#endif
      }
  }
  return sum;
}
int main(){
  selection_Statements();
  FILE* fp = nullptr;
  try{
    fp = readFile();
  }catch(std::runtime_error& r1){
    cerr << r1.what() << endl;
    fclose(fp);
  }
  vector<int>res{1, 2, -2, 0, 12, 13, 144};
  cout << calc_div_sum(res, 12) << endl;
  return 0;
}

Switch Statements

Sometimes its rather incovient to check aginst all of the controll flow of a pyramid of if else statement, especially if there are lot of such conditinal checks. For such instance CPP offers us the alternative of using a switch statement. A Switch is often used when an intent of check a cconstexpr against a set of case-label which themselves are constexpr is explicit. It allows for significant optimization in such case as it implements what we call as a jump table to directly find the inteded match as opposed to executing each of the controll flow of the conditional statements to find the inteded match. A switch stataement can thus be used in almost all of the cases where you could've otherwise use a if else block but its mostly reserved for uses where we tend to check some single constexpr against a set of case label. As such one need to a way to stop falling through the subsequent controll flow after the case lable has been matched for which case we can either add the break statement to the end of the corresponding case label, or return from the case label as well. Though returning from a case lable is relatively less common but its a viable option where having to call returns is of course going to return from the function where is its being called and return the controll flow to the end of the function call in the call stack. However, only can also involve in delibrate fall through the switch statement for which case one should explicitly mentioned such fall through in a well commented section to make such intention explicit otherwise, such fallthrough could be considered uintentional and possibly a design flaw in implementing the corresponding logic. Since a case lable of a switch statement can except a constexpr it is viable to pass any integral, arthematic, or enum or enum class type constexpr to a case label. However, using something other than a constexpr or something that require runtime evluation as a case label would effectively result in compilation errors. Moreover, a switch statement can also have a default case label which is used specified in the following scenarios:

  • To indicate that all of the valid alternative have been checked and hence engage in the consequent fall through behavior.
  • To throw an error or exception when no valid alternative is matched. As a matter of fact, the one good case of not explicitly mentioning a default case lable might include the use of enum class enumerator values in case label, where not mentioning the default case label can help the compiler to warn against the cases, where the programmer forgets to check against all of the plaubsible enumerator values.
#include<iostream>
#include<cstring>
using namespace std;
int getMarks(int f){
    constexpr float r2 = 12* 12 + 14.3;
    switch(f){
        case (int)r2: cout << f << endl; return f; //ok can evaluate r2 at compile time and r2 is integral literal.
        default:
            return -12;
    }
}
void useSwitchStatements(){
  enum class INK{RED, GREEN, BLUE};
  INK i1 = INK::RED;
  switch(i1){
      case INK::RED: cout << static_cast<int>(INK::RED) << endl; break;
      case INK::GREEN: cout << static_cast<int>(INK::GREEN) << endl; break;
      case INK::BLUE: cout << static_cast<int>(INK::BLUE) << endl; break;
    }
    float marks = 12;
  #ifdef Errors
    switch(marks){ //can use only integral constant as expression to check against.
        case marks <= 12 && marks > 13: {cout << "failed" << endl;}  //ok can be evluated as cosntexpr
        case marks == 12: cout << "pass" << endl; break;
        default:
          throw std::runtime_error("failed returning...");
      }
   const char* somenames = "somenames";
   switch(strlen(somenames)){
       case strlen("random"):  cout << "random" << endl; //error can only use constexpr as case lables.
       case strlen("random-else"): cout << "random_else" << endl; //intentionall fall through.
       default:
        printf("%s", "random things");
     }
#endif
     cout << getMarks(13) << endl;
}
int main(){
  useSwitchStatements();
  return 0;
}

Declration in case Labels

For most of the cases, we can use a switch case lable without introducing any new scope corresponding to that case lable or skipping the delimited {} pair, however, when declaring an identifier within the switch case label it is often advisable to introduce such indentifier within the delimited scope. This is usually to prevent make the corresponding identifer local to the scope in which it was declared and to prevent any unecessary error by unintentional fall through and accessing of the identifier without the intended declaration in the given scope. For example lets consider this:


#include<iostream>
using namespace std;
void use_operations(){
  enum Kind{ROTATE, REFLECT, DOT};
  int k;
  cin >> k;
  #ifdef Invalid
  switch(k){
    case ROTATE: int r = 12; cout << r << endl;
    case REFLECT: cout << r+ 12 << endl; break; //what if the k value is 1, meaning that we will pass through r declration and access it without previous declration in the given scope.
    case DOT: cout << r -12 << endl; break;
  }
#endif
  switch(k){
    case ROTATE: {
      int r = k; cout << (r * -90) << endl;
    }
    case REFLECT: {
      //intentional fall through
      int r = 14;
      cout << (r * -1) << endl;
      break;
    }
    case DOT: {
      int r = k;
      cout << (r * r) << endl;
    }
    default:
      throw std::runtime_error{"invalid ops"};
  }
}
int main(){
  use_operations();
  return 0;
}

Iterative Statements

Iteratitive statement provides a way to perform an action over a specified no of time or untill the corresponding conditional is not valid in CPP. As such interative statement can be used either for the said-mentioned purpose or to itnerate over a sequence of given type, where iterating of course of course meaning going through each of the elements in the sequence to get access to the corresponding references of such elements in such sequence. As such CPP offers the following type of the iterative statements:

  • The traditional for, while and do while iterative statements.
  • The range for iterative statements.

The subsequent sections dicuss the following statements in more details

#include<iostream>
using namespace std;
int main(){
// TODO
  return 0;
}

Range for Statements

A Range for loop provides a convinient short hand notion for a for loop which helps to iterate over a sequence that have a begin and end defined on it without having to explicitly specify the iteration range or the loop counter. Thus range for loop gives us reference to each of teh object in the specified sequence. A range for loop in that sense can also be effectively used with auto typespecifier or decltype type specifier to implicitly deduce the type of the elements without having to explicitly mention them. However, for a range for statement to work on the given sequence type it should amptly fullfill the given criteria:

  • It should be a type with a begin(pointer to first element of sequence) and end(pointer to last element of such sequence) defined on it.
  • If it doesn't have a begin or end defined on it, its enclosing scope should have a begin and end defined on it.
  • If its enclosing scope doesn't have any being or end, its parent scope should specify a begin and end on it.

Range for statement is thus, much more versertile to use than traditional for loops as any changes in such statement can easily be incorported with minimal local changes, however, since they don't explicitly keep track of loop cointer, they can't be used for complicated iterations construct that involving iterating over multiple sequence at the same time, such as while using two pointers to traverse a sequence.

#include<iostream>
using namespace std;
int main(){
// TODO
  return 0;
}

For Statements

For statements are rather traditional yet most common way of looping over a sequence in CPP. A For statement semantic usually consist of a loop intializer, or the intial value of the iteration, a condition specifying till when the loop should executes and a counter, for the loop intializer which update such intializer. An intializer is thus intialized only once in a for statements, and its scope extends from the point of declration in the staetment to the end of the statement of which its a part i.e. its local to the statement in which its being declared. A for loop can be effectively used for iterating over a sequence in a verity of way. Moreover, it could be used for more complicated construct such as iterating over multiple sequences at the same time, etc. Moreover, in some cases where one might need to preserve the value of the loop counter one can conviniently introduce it outside of the scope of the loop statement itself. For a curious notion of intifinte loop one can also use the for(;;) notion which since doesn't have any update loop conditon is going to execute forever.

#include<iostream>
using namespace std;
int main(){
// TODO
  return 0;
}

While Statements

  • A While statement is yet other alternative than a for iterative statement, and can be used almost interchangibly with for statements. However, a while statement is relatively much more easier to get wrong as the straightforward for iterative statement and there are not too much of case to favor a while statement in construct of a for statement except for a few place.
  • In general a while statement exceutes until the predicate is true, and it doesn't introduce the loop counter or updater within the loop body itself, and we often need to introduce it outside of the loop body.
  • Thus, I often use while loop only when loop intializer is to be updated in the loop body itself.
  • Moreover much like an if predicate a pointer type can implicitly converts to boolean value when used within the with statement, where a non-null pointer converts to true and a null pointer converts to false.
  • Consequently, we can conviniently use a while loop to iterate over an array of given type with both of the suscripting as a well pointer to the given element notion.
#include<iostream>
using namespace std;
int main(){
  const char* somestring = "somestring";
  while(*somestring++){cout << *somestring;}
  return 0;
}

To some the notion *somestring++ might not be that apparent at glance, however, it doesn't take much guess to come to a conclusion that *somestring++ is equivalent to *somestring++ != 0. Moreover, since in arthematic conversion the predicate values are checked against zero either way we don't really need to explicitly mention that.

Do-While Statements

A do while statement is rather rarely use construct but its effectively similar to the while statement in the sense that it executes a block of statement untill the predicate is true, except of minor difference i.e. its conditional flow is executed at least once even if the predicate is false to begin with. Thus a do-while construct is a subtle source of error and confusion cause it make assumptions about the controll flow, Consequently, in my advice one should avoid using do-while statements all together.

#include<iostream>
using namespace std;
int main(){
// TODO
  return 0;
}

Loop Exits

  • Sometimes we might want to return the controll flow out of the iterative statement in the middle of statement i.e. before the statement itself is completed such as while breaking out of infinite loop when some condition is meet or breaking out of loop altogether as soon as the condition is meet as opposed to executing it for the rest of the iterations, we can do so by using the break and continue statement.
  • A Break statement bring the execution controll out of the innermost iteration to the end of the iteration statement. Note the emphasis on the innermost statement, I explicitly mentioned the same before a break statement could break you out of the innermost enclosing loop where it has been used only.
  • A continue statement bring the controll flow to the start of the next iteration of the innermost loop where it has been loop whille skipping all of the statement that proceeds it. For intance lets consider the following example:
#include<iostream>
using namespace std;
int main(){
  return 0;
}

goto Statements

  • A goto statement is an inheritly low level construct and is often not found in higher level of code, and even if it does, such code should be pryed with suspicion cause it could be likely indicator of design error or flow. A goto statement generally goes by the syntax as mentioned below:
  • label: statements;
  • goto label;
  • Here label correspond to the consequent scope label defining the block of the statement specified by the goto identifier to which the corresponding goto statement can jump the execution controll to.
  • Thus goto can be used to jump both in and out of the given function, and are relatively common in parser generated from the grammar by a parser generator.
  • The only restriction with a goto statement is that you can't really use it to jump past an intializer(as that would essnetially lead to possible use of such intializer without prior declration) or an exception handler(as that would allow to essentially skip the try or catch block controll flow and jump directly into the exception handler) thereby crubbuing the normal execution controll flow around such exception handling.
#include<iostream>
using namespace std;
int main(){
// TODO
  return 0;
}