8. Technicalities: Functions, etc.

8.1 Technicalities

When you start programming, your programming language is a foreign language for which you need to look at “grammar and vocabulary”.

Most design and programming concepts are universal, and many such concepts are widely supported by popular programming languages.

That means that the fundamental ideas and techniques we learn in a good programming course carry over from language to language.

The language technicalities, however, are specific to a given language. Fortunately, programming languages do not develop in a vacuum, so much of what you learn here will have reasonably obvious counterparts in other languages.

In particular, C++ belongs to a group of languages that also includes C, Java, and C#, so quite a few technicalities are shared with those languages.

8.2 Declarations and definitions

A declaration is a statement that introduces a name into a scope:

  • Specifying a type for what is named (e.g., a variable or a function)
  • Optionally, specifying an initializer (e.g., an initializer value or a function body)

Example:

                        
                          int a = 7; // an int variable
                          const double cd = 8.7; // a double-precision floating-point constant
                          double sqrt(double); // a function taking a double argument
                                               // and returning a double result
                          vector<Token> v; // a vector-of-Tokens variable
                        
                      

Before a name can be used in a C++ program, it must be declared.

Consider:

                        
                          int main()
                          {
                              cout << f(i) << '\n';
                          }
                        
                      
The compiler will give at least three “undeclared identifier” errors for this: cout, f, and i are not declared anywhere in this program fragment.


After Fixing:

                        
                          #include "std_lib_facilities.h" // we find the declaration of cout in here
                          int f(int); // declaration of f
                          int main()
                          {
                            int i = 7; // declaration of i
                            cout << f(i) << '\n';
                          }
                        
                      
This will compile because every name has been declared, but it will not link because we have not defined f(); that is, nowhere have we specified what f() actually does.


A definition specifies exactly what a name refers to. In particular, a definition of a variable sets aside memory for that variable.
Example:

                        
                          int x = 7; // definition

                          double sqrt(double d) { /* . . . */ } // definition
                        
                      

8.2.1 Kinds of declarations

Following are the kind of entities that a programmer can define in C++:

  • Variables
  • Constants
  • Functions
  • Namespaces
  • Types (classes and enumerations)
  • Templates

8.2.2 Variable and constant declarations

The declaration of a variable or a constant specifies a name, a type, and optionally an initializer:

                            
                              int a; // no initializer
                              double d = 7; // initializer using the = syntax
                              vector<int> vi(10); // initializer using the ( ) syntax
                              vector<int> vi2 {1,2,3,4}; // initializer using the { } syntax
                            
                          


Constants have the same declaration syntax as variables. They differ in having const as part of their type and requiring an initializer:

                             
                                const int x = 7; // initializer using the = syntax
                                const int x2 {9}; // initializer using the {} syntax
                             
                           

8.2.3 Default initialization

                            
                              vector<string> v;
                              string s;
                              while (cin>>s) v.push_back(s);
                            
                          

Here, string and vector are defined so that variables of those types are initialized with a default value whenever we don’t supply one explicitly.

Thus, v is empty (it has no elements) and s is the empty string ("") before we reach the loop.

The mechanism for guaranteeing default initialization is called a default constructor.


Unfortunately, the language doesn’t allow us to make such guarantees for built-in types.

8.3 Header files

As programs grow larger and larger (and include more files), it becomes increasingly tedious to have to forward declare every function you want to use that lives in a different file.

Wouldn’t it be nice if you could put all your declarations in one place?

Header files usually have a .h extension, but you will sometimes see them with a .hpp extension or no extension at all.

The purpose of a header file is to hold declarations for other files to use.

Drill

Separate out the tokenizing code from the rest of your calculator program. Put your declarations in token.h and your definitions to token.cpp. Get this version to compile and link.

8.4 Scope

Scope is a region of program text with the main purpose to keep the extent of a program code within which the variable can be accessed.
Kinds of scopes used to control where names can be used are:

  • Global scope: the area of text outside any other scope
  • Namespace scope: a named scope nested in the global scope or in another namespace
  • Class scope: the area of text within a class
  • Local scope: between { . . . } braces of a block or in a function argument list
  • Statement scope: e.g., in a for-statement

8.5 Function call and return

Functions are the way we represent actions and computations.
In a programming language, a function is a type of procedure or routine. Functions allow us to reuse code instead of rewriting it. They also help organize the code.

8.5.1 Declaring arguments and return type

A function can be declared in following way:
return_type function_name( parameter list );

A definition contains the function body (the statements to be executed by a call), whereas a declaration that isn’t a definition just has a semicolon.

Formal arguments are often called parameters.

  • If you don’t want a function to take arguments, just leave out the formal arguments.
  • If you don’t want to return a value from a function, give void as its return type.

8.5.2 Returning a value

A return type specifies the type of the data value being returned by the written function.
Also, function may or may not return a value.
The return type is void if the function does not return a value.

                            
                              void print_until_s(vector<string> v, string quit)
                              {
                                for(int s : v) {
                                  if (s==quit) return;
                                  cout << s << '\n';
                                }
                              }
                            
                          

In code shown in 8.5.1 , the return value is an integer value c, which is int.

8.5.3 Pass-by-value

The simplest way of passing an argument to a function is to give the function a copy of the value you use as the argument. An argument of a function f() is a local variable in f() that’s initialized each time f() is called.
Example:

                            
                              // pass-by-value (give the function a copy of the value passed)
                              int f(int x)
                              {
                                x = x+1; // give the local x a new value
                                return x;
                              }
                              int main()
                              {
                                int xx = 0;
                                cout << f(xx) << '\n'; // write: 1
                                cout << xx << '\n'; // write: 0; f() doesn’t change xx
                                int yy = 7;
                                cout << f(yy) << '\n'; // write: 8
                                cout << yy << '\n'; // write: 7; f() doesn’t change yy
                              }
                            
                          

Graphical Representation:

8.5.4 Pass-by-const-reference

If a value is large, such as an image (often, several million bits), a large table of values (say, thousands of integers), or a long string (say, hundreds of characters)?
Then, Pass-by-value can be costly.
Example:

                             
                                void print(const vector<double>& v) // pass-by-const-reference
                                {
                                  cout << "{ ";
                                  for (int i = 0; i<v.size(); ++i) {
                                      cout << v[i];
                                      if (i!=v.size()–1) cout << ", ";
                                  }
                                  cout << " }\n";
                                }

                                void f(int x)
                                {
                                  vector<double> vd1(10); // small vector
                                  vector<double> vd2(1000000); // large vector
                                  vector<double> vd3(x); // vector of some unknown size
                                  // . . . fill vd1, vd2, vd3 with values . . .
                                  print(vd1);
                                  print(vd2);
                                  print(vd3);
                                }
                              
                            

The above code shows a way of giving our print() function “the address” of the vector to print() rather than the copy of the vector. Such an “address” is called a reference.

The & means “reference” and the const is there to stop print() modifying its argument by accident. Apart from the change to the argument declaration, all is the same as before; the only change is that instead of operating on a copy, print() now refers back to the argument through the reference.


Graphical Representation:

8.5.5 Pass-by-reference

Passing by reference means the called functions' parameter will be the same as the callers' passed argument (not the value, but the identity - the variable itself).
A reference can be a convenient shorthand for some object, is what makes them useful as arguments.
Example:

                            
                              // pass-by-reference (let the function refer back to the variable passed)
                              int f(int& x)
                              {
                                x = x+1;
                                return x;
                              }

                              int main()
                              {
                                int xx = 0;
                                cout << f(xx) << '\n'; // write: 1
                                cout << xx << '\n'; // write: 1; f() changed the value of xx
                                int yy = 7;
                                cout << f(yy) << '\n'; // write: 8
                                cout << yy << '\n'; // write: 8; f() changed the value of yy
                              }
                            
                          

Graphical Representation:


Application in Sorting (Swap function):

                            
                              void swap(double& d1, double& d2)
                              {
                                double temp = d1; // copy d1’s value to temp
                                d1 = d2; // copy d2’s value to d1
                                d2 = temp; // copy d1’s old value to d2
                              }

                              int main()
                              {
                                double x = 1;
                                double y = 2;
                                cout << "x == " << x << " y== " << y << '\n'; // write: x==1 y==2
                                swap(x,y);
                                cout << "x == " << x << " y== " << y << '\n'; // write: x==2 y==1
                              }
                            
                          

8.5.6 Pass-by-value vs. pass-by-reference

Our rule of thumb is:

  1. Use pass-by-value to pass very small objects.
  2. Use pass-by-const-reference to pass large objects that you don’t need to modify.
  3. Return a result rather than modifying an object through a reference argument.
  4. Use pass-by-reference only when you have to.

8.5.7 Argument checking and conversion

When an argument is passed during a call, initialization of the function’s formal argument with the actual argument takes place.

                            
                              void f(double x);
                              void g(int y)
                              {
                                f(y);
                                double x = y; // initialize x with y
                              }
                            
                          

There was a need to convert an int into a double. This happens in the call of f() and also in f() where double value is stored in x.

In conversions, if a double value has to be truncated into an int mention it explicitly.

                            
                              void ggg(double x)
                              {
                                int x1 = x; // truncate x
                                int x2 = int(x);
                                int x3 = static_cast<int>(x); // very explicit conversion
                                ff(x1);
                                ff(x2);
                                ff(x3);
                                ff(x); // truncate x
                                ff(int(x));
                                ff(static_cast<int>(x)); // very explicit conversion
                              }
                            
                          

8.5.8 Function call implementation

Lets say we have functions expression(), term(),  primary().
A data structure is set aside which contains copy of all parameters and local variables as soon as a function is called.
A data structure called function activation record which maintains information like the function to which the caller needs to return and return value is maintained.
Now, expression() is called first:


expression() calls term():


term() calls primary():


And then primary() calls expression():


So this becomes a recursive function. Each time when a function is called, the stack of activation records (stack) grows by one record.
When the function returns, the record is no longer in use. That means the stack reverts. It works as "Last in, first out".


8.5.9 constexpr functions

A constexpr function is a non-constructor function declared with a constexpr specifier.
It is a function that can be invoked within a constant expression. Conditions to be satisfied by a constexpr function are:

  • It cannot be virtual.
  • Return type must be literal type.
  • Also each of its parameters must be literal type as well.
  • In a constant expression, each constructor call and implicit conversion is valid while initializing the return value.
  • Function body is = delete or = default
  • If not function body must contain null statements, static_assert declarations, typedef declarations, using declarations, using directives, one return statement

8.6 Order of evaluation

The evaluation/execution of a program proceeds through statements according to the language rules. The order of evaluation is as follows:

  • Variable is constructed when the "thread of execution" reaches the definition of a variable
  • Object is initialized by setting aside memory for the object
  • Variable destroyed as it goes out of scope
  • Object referred by the variable is removed
  • Memory is free for the compiler to be used for something exclusively

                        
                          string program_name = "silly";
                          vector<string> v; // v and program_name are global, they live until program terminates

                          void f()
                          {
                              string s; // s is local to f
                              while (cin>>s && s!="quit") {
                                  string stripped; // stripped is local to the loop
                                  string not_letters;
                                  for (int i=0; i<s.size(); ++i) // i has statement scope
                                      if (isalpha(s[i]))
                                          stripped += s[i];
                                      else
                                          not_letters += s[i];
                                  v.push_back(stripped);
                                  // . . .
                              }
                              // . . .
                          }
                        
                      

8.6.1 Expression evaluation

If the value of a variable in an expression is changed, never read or write it twice in that same expression.

                            
                              v[i] = ++i; // don’t: undefined order of evaluation
                              v[++i] = i; // don’t: undefined order of evaluation
                              int x = ++i + ++i; // don’t: undefined order of evaluation
                              cout << ++i << ' ' << i << '\n'; // don’t: undefined order of evaluation
                              f(++i,++i); // don’t: undefined order of evaluation
                            
                          

8.6.2 Global initialization

Global variables in a single translation unit are initialized in the order in which they appear.


                            
                              int x1 = 1;
                              int y1 = x1+2; // y1 becomes 3
                            
                          
This initialization logically takes place “before the code in main() is executed.


                            
                              const Date& default_date()
                              {
                                static const Date dd(1970,1,1); // initialize dd first time we get here
                                return dd;
                              }
                            
                          
The static local variable is initialized (constructed) only the first time its function is called.

8.7 Namespaces

Classes are used to organized functions, data and types into a type. A function and a class both do two things:

  • Allow to define a number of "entities" (without worrying about name clashes).
  • Give a name to refer to what is defined.


A namespace is a language mechanism for groupings of declarations.
It is used to organize classes, functions, data and types into an identifiable and named part of the program without defining a type.

                        
                          namespace Graph_lib {
                            struct Color { /* . . . */ };
                            struct Shape { /* . . . */ };
                            struct Line : Shape { /* . . . */ };
                            struct Function : Shape { /* . . . */ };
                            struct Text : Shape { /* . . . */ };
                            // . . .
                            int gui_main() { /* . . . */ }
                          }
                        
                      

8.7.1 using declarations and using directives

Writing fully qualified names can be tedious.

                            
                              #include<string> // get the string library
                              #include<iostream> // get the iostream library
                              int main()
                              {
                                std::string name;
                                std::cout << "Please enter your first name\n";
                                std::cin >> name;
                                std::cout << "Hello, " << name << '\n';
                              }
                            
                          


The above code can be reduced to:

                            
                              #include<string> // get the string library
                              #include<iostream> // get the iostream library
                              using namespace std; // make names from std directly accessible
                              int main()
                              {
                                string name;
                                cout << "Please enter your first name\n";
                                cin >> name;
                                cout << "Hello, " << name << '\n';
                              }
                            
                          


It can be further reduced to by placing a using derivative for std:

                            
                              #include "std_lib_facilities.h"
                              int main()
                              {
                                string name;
                                cout << "Please enter your first name\n";
                                cin >> name;
                                cout << "Hello, " << name << '\n';
                              }
                            
                          

Test Yourself!
  1. What is the difference between a function declaration and a function definition?
    1. the declaration includes the function body, while the definition just gives the function's return and parameter types
    2. the declaration just gives the function's return and parameter types, while the definition includes the function body
  2. What are header files used for?
    1. To head off disaster.
    2. To put a nice header at the top of your code if you print it.
    3. To provide declarations needed for multiple source code files.
  3. How does indentation help in a C++ program?
    1. it helps the compiler do code optimizations
    2. it makes the structure of the code clearer to a human reader
    3. it is necessary to compile the code correctly, just as in Python
  4. If we need to alter the original of a variable we are passing to a function, we should...
    1. pass by constant reference
    2. pass by reference
    3. pass by value
  5. The function declaration consists of …
    1. function name, return type and parameter list
    2. function body and parameter list
    3. function name and parameter list
  6. Which of the following is not a type of scope?
    1. Namespace Scope
    2. Declaration Scope
    3. Class Scope
    4. Global Scope
  7. Passing by reference means …
    1. parameter will be the same as the callers' passed argument (not the value, but the identity - the variable itself)
    2. the called function's parameter will be a copy of the callers' passed argument
  8. Passing by value means …
    1. parameter will be the same as the callers' passed argument (not the value, but the identity - the variable itself)
    2. the called function's parameter will be a copy of the callers' passed argument
  9. Function definition consists of …
    1. function name and body
    2. function body and return type
    3. function name, parameters, return value type, and body
  10. What is the return type here: int myMethod(int count, double value) {    return 4; }
    1. MyMethod
    2. int
    3. 4
    4. double
    5. count
  11. If a variable is declared inside a function, what kind of variable is this?
    1. global variable
    2. local variable
    3. class variable
  12. If we have a function int square (int n) , are we able to send it a different variable in the main program or does it have to be n. For example, square (x)
    1. No
    2. Yes
  13. Which of the following is a valid function call (assuming the function exists)?
    1. f();
    2. f;
    3. f x, y;
    4. int f();
  14. Identify the correct statement
    1. None of the mentioned
    2. A namespace is used to mark the beginning of the program
    3. A namespace is used to separate a class from objects
    4. A namespace is used to group classes, objects and functions
  15. What is the scope of the variable declared in a user defined function?
    1. none of the mentioned
    2. whole program
    3. the main function
    4. only inside the {} block in which it is declared
  16. If your function does not return any value, which of the following keywords should be used as a return type?
    1. double
    2. int
    3. void
    4. float
    5. unsigned short
  17. Which of the following functions should be defined as void?
    1. Return a sales commission, given the sales amount and the commission rate
    2. Print the calendar for a month, given the month and year
    3. Return a bool value indicating whether a number is even
    4. Return a square root for a number
  18. Which of the following should be declared as a void function?
    1. a function that prints integers from 1 to 100
    2. a function that checks whether current second is an integer from 1 to 100
    3. a function that converts an uppercase letter to lowercase
    4. a function that returns a random integer from 1 to 100
Answers

1. b; 2. c; 3. b; 4. b; 5. a; 6. b; 7. a; 8. b; 9. c; 10. b; 11. b; 12. b; 13. a; 14. d; 15. d; 16. c; 17. b; 18. a;

Drill