Home Previous Next

CSC100 :: Lecture Note :: Week 07
Assignments | Code | Handouts | Resources | Email Thurman {Twitter::@compufoo Facebook::CSzero}
{GDT::Bits:: Time  |  Weather  |  Populations  |  Special Dates}

Overview

Assignment(s): [program] #characters

Code: Switch.cpp | IncrementDecrementOps.cpp | CompoundAssignmentOps.cpp
letters.cpp | funcs1.cpp | sqrtpow.cpp | fi2i.cpp | scope.cpp | i2fi.cpp | refparams.cpp


About Character Sets and an ASCII Chart

Every character has a numeric representation called its character code. ASCII is a popular coding system (i.e. character set) that is used to map characters to their numeric equivalents. The following table shows the ASCII code used to represent each character.


|  0 NUL|  1 SOH|  2 STX|  3 ETX|  4 EOT|  5 ENQ|  6 ACK|  7 BEL|
|  8 BS |  9 HT | 10 NL | 11 VT | 12 NP | 13 CR | 14 SO | 15 SI |
| 16 DLE| 17 DC1| 18 DC2| 19 DC3| 20 DC4| 21 NAK| 22 SYN| 23 ETB|
| 24 CAN| 25 EM | 26 SUB| 27 ESC| 28 FS | 29 GS | 30 RS | 31 US |
| 32 SP | 33  ! | 34  " | 35  # | 36  $ | 37  % | 38  & | 39  ' |
| 40  ( | 41  ) | 42  * | 43  + | 44  , | 45  - | 46  . | 47  / |
| 48  0 | 49  1 | 50  2 | 51  3 | 52  4 | 53  5 | 54  6 | 55  7 |
| 56  8 | 57  9 | 58  : | 59  ; | 60  < | 61  = | 62  > | 63  ? |
| 64  @ | 65  A | 66  B | 67  C | 68  D | 69  E | 70  F | 71  G |
| 72  H | 73  I | 74  J | 75  K | 76  L | 77  M | 78  N | 79  O |
| 80  P | 81  Q | 82  R | 83  S | 84  T | 85  U | 86  V | 87  W |
| 88  X | 89  Y | 90  Z | 91  [ | 92  \ | 93  ] | 94  ^ | 95  _ |
| 96  ` | 97  a | 98  b | 99  c |100  d |101  e |102  f |103  g |
|104  h |105  i |106  j |107  k |108  l |109  m |110  n |111  o |
|112  p |113  q |114  r |115  s |116  t |117  u |118  v |119  w |
|120  x |121  y |122  z |123  { |124  | |125  } |126  ~ |127 DEL|

Note: SP (i.e. decimal 32) is a space character. The characters prior to SP are control codes (e.g. SOH is control-a, STX is control-b, ETX is control-c, and so on). These special characters are heavily used in data communications.

Other commonly used character sets include EBCDIC and Unicode.

The Unicode.org website states:

   "Unicode provides a unique number for every character,
    no matter what the platform, no matter what the program, 
    no matter what the language."

The first Unicode 128 characters have the same numeric values as ASCII characters.

Unicode supports internationalization of programs. Examples: Google China and Wikipedia.org.

{TopOfPage} {Tutorial} {CodingGround (online IDE)} {CPP.sh (online IDE)} {C at MIT} {GDT::C/C++ Resource}


The switch Statement

The switch statement is a multi-way decision that tests whether an integral-EXPR matches one of a number of constant integral values, and branches accordingly.

   switch (integral-EXPR) {
      case const-expr: statements
      case const-expr: statements
      default: statements          /*optional*/
  }

The body of a switch almost always contains a compound statement. The compound statement usually contains one or more case labels. Optionally, the body of switch can contain a default label. Both case and default are keywords of the language.

A good time to use a switch statement is when you encounter nested if statements that are checking to see of a specific integral value is equal to a set of constant integral values.

   const int ADULT_AGE = 21;
   const int FIRST_MILESTONE_AGE = 30;
   const int SECOND_MILESTONE_AGE = 40;
   const int THIRD_MILESTONE_AGE = 50;
   const int RETIREMENT_AGE = 65;
   const int CENTURY_MARK = 100;

   /* here are some nested if statements */
   if (age == 21) 
      statement;
   else 
      if (age == 30 || age == 40 || age == 50)
         statement;
      else 
         if (age == 65) 
            statement;
         else 
            if (age == 100) 
               statement;


   /* the nested if's rewritten using a switch statement */
   switch (age) {
      case 21:
         statement;
         break;  // what would happen if this statement is rm'd?
      case 30:
      case 40:
      case 50:
         statement;
         break;
      case (65):  // note: () usage around the case EXPR is allowed
         statement;
         break;
      case (100):
         statement;
         break;  // good form to have this break statement
   }

Some benefits gained from using the switch statement versus the nested if statements.

Points that require understanding.

More examples using the switch statement.

   switch (nbr) {
      case 1: cout << "one"; break;
      case 2: cout << "two"; break;
      case 3: cout << "three"; break;
      case 4: cout << "four"; break;
      case 5: cout << "five"; break;
      default: cout << "enough of this..."; break;
   }

   float f = 3.14f;
   switch (f) { ... }  is illegal -- the switch EXPR is not integral


   float pi = 3.14f;
   switch (nbr) {
      case nbr % 2: ...   is illegal -- case EXPR is not constant
      case pi: ...  is illegal -- case EXPR is not constant, nor is
      ...                         it an integral value
   }


   switch (nbr) {
      case 1:
      case 1: ...  is illegal -- case EXPR is not unique
      ...
   }

GDT::C++::Code:: Switch.cpp

{TopOfPage} {Tutorial} {CodingGround (online IDE)} {CPP.sh (online IDE)} {C at MIT} {GDT::C/C++ Resource}


Increment and Decrement Operators

The increment ++ and decrement -- operators as a side-effect of being evaluated cause their operands to be incremented and decremented by one, respectively.

The increment and decrement operators cause memory to be modified; therefore, their operands must be valid lvalues (e.g. they cannot be contants).

There are two types of notation supported: prefix and postfix.

Prefix Notation

The EXPR ++i causes the value of 'i' to be increased by one (this is a side effect of being evaluated). The EXPR evaluates to the incremented value of 'i'. Example.

  int i, j;
  i = 5;
  j = ++i;   /* assigns the value 6 to i */

  side-effect:  the value of  i  is incremented by one
Postfix Notation

The EXPR i++ causes the value of 'i' to be increased by one (this is a side effect of being evaluated). The EXPR evaluates to the value of 'i' prior to being incremented.

  int i, j;
  i = 5;
  j = i++;   /* assigns the value 5 to i */

  side-effect:  the value of  i  is incremented by one

{TopOfPage} {Tutorial} {CodingGround (online IDE)} {CPP.sh (online IDE)} {C at MIT} {GDT::C/C++ Resource}


Compound Assignment Operators

All of the arithmetic and bit-wise operators can be used as  compound assignment  operators. Syntax:

   lvalue  OP=  expr

   where OP is either:  + - * / % >> << & | ^

   lvalue  OP=  expr           is equivalent to
   lvalue = lvalue OP expr

   j += 5        adds 5 to the variable 'j'
   j %= 2        assigns the value of  j%2  into 'j'
   j *= k + l    equivalent to   j = j * (k + l);
Using compound assignment operators provide the following benefits:
   int randomNumber1, randomNumber2;

   randomNumber1 = randomNumber1 + randomNumber2;

            or

   randomNumber1 += randomeNumber2;

{TopOfPage} {Tutorial} {CodingGround (online IDE)} {CPP.sh (online IDE)} {C at MIT} {GDT::C/C++ Resource}


Introduction to Functions

Lowest level ideas become EXPRs, EXPRs are grouped into statements, and statements are grouped together into blocks and functions.

A function is a set of statements that have been collected together and given a name.

Functions break large computing tasks into smaller ones, and enable people to build on what others have done instead of starting over from scratch. Functions support reuse.

Functions hide details of operation from parts of the program that don't need to know about them. Functions support information hiding.

Functions usually consist of zero or more statements, and a collection of local data. Functions support encapsulation.

Programs generally consist of many small functions rather than a few big ones. Functions support modularity.

Repetitive statements should be grouped into a function. Functions eliminate duplicate code.

Additional benefits derived from using functions.

Functions that perform generic tasks can be added to a library. Library functions can be used in multiple programs. The C and C++ languages come with extensive libraries that contain numerous functions.

{TopOfPage} {Tutorial} {CodingGround (online IDE)} {CPP.sh (online IDE)} {C at MIT} {GDT::C/C++ Resource}


Function Terminology

The act of executing a set of statements associated with a function is known as calling the function. If function A calls function B, then function A is referred to as the calling or caller function and function B is the called function.

Arguments are a list of EXPRs that are evaluated and whose values are passed to the function. (Note: C and C++ are " pass-by-value" languages.) A function does not need to take arguments. If function A calls function B, then arguments allows function A to communicate information to function B.

Arguments, if any, that are passed to a function B, are parameters within function B. Parameters are treated as initialized local (i.e. auto variables).

When a function is done, it returns to the caller by executing a return statement.

Functions can "return a value" back to the caller; thus, if function A calls function B, then the return value allows function B to communicate with function A.

Before a function can be called, it must be declared. A function declaration that also includes the type(s) of the parameters is called a prototype.

{TopOfPage} {Tutorial} {CodingGround (online IDE)} {CPP.sh (online IDE)} {C at MIT} {GDT::C/C++ Resource}


Function Prototypes

A function must be declared (i.e. prototyped) before it is used.

Syntax.

   return-type function-name(parameter types, if any);

Examples.

   int maxValue(int, int);
      //returns an int; receives two int values as parameters

   float getSalary(void);
      //returns a float; doesn't take any parameters

   double computeFactorial(int number);
      //returns a double; receives an int value
      //parameter types can have a tag specified with them, but the
      //    tag is ignored by the compiler; tags are used for
      //    documentation purposes only and can help the readability of 
      //    a program; they have what is referred to as function 
      //    prototype scope

   char getGrade(short, unsigned char, unsigned char);
      //returns a character; receives a short value and two 
      //    unsigned characters as parameters

   void abort(void);
      //returns nothing; receives nothing

Function prototypes are used by the compiler to help make sure that functions are called correctly (e.g. correct number of arguments). They were added to C++ to improve type-checking and were incorporated into C by the standards committee.

Function prototypes can help ensure that functions are called using:

When prototyping function arguments, only the types of the arguments need to be specified. If they are given names, then those names have "function prototype visibility" and are ignored by the compiler. Names used in function prototypes are for documentation purposes only.

f() Versus f(void)

In C, f() implies a function that takes any number of arguments; where in C++ it indicates a function that takes no arguments.

In C++, f() and f(void) are equivalent.

{TopOfPage} {Tutorial} {CodingGround (online IDE)} {CPP.sh (online IDE)} {C at MIT} {GDT::C/C++ Resource}


Function Definitions

The function definition contains the statements that comprise a function. The definition of a function is also referred to as its implementation.

Function statements are enclosed in {}'s and are referred to as the function body.

The body of a function consists of zero more variable declarations/definitions and zero or more statements.

Examples:

   int x(int n1, int n2) {
      //this is the function body...
   }

   void y(float f, char c, int i) {
      //this function doesn't return a value; it takes three
      //   parameters (a float, a char and an int)
      ...
      if (i) return;
      ...
   }
When a function is defined, each parameter, if any, must be specified with a type and a name. Parameter type-name pairs are separated by comma's (these comma's are not the sequence operator).

If the return-type is not specified, then it defaults to int. You should get in the habit of always explicitly specifiying the return-type.

Parameters can be thought of as initialized local variables (i.e. space is allocated for them from the stack and they are initialized to the value of the arguments that are passed).

Variables declared inside of a function body are visible only with that block of code. These variables are referred to as a "local" variables.

A function definition can contain multiple return statements; however, many programmers are of the opinion that a function should have only one exit point.

A stub function is a function that contains no statements.

{TopOfPage} {Tutorial} {CodingGround (online IDE)} {CPP.sh (online IDE)} {C at MIT} {GDT::C/C++ Resource}


Function Comment Blocks

Every function should begin with a function comment block. The function comment block contains - at a minimum - the name of the function, a list of arguments that it receives, a description of its return value (if any), and a brief description of what the function does. In addition, any side-effects (or outputs) performed by the function should be documented.

The following is an example function comment block.

   /*
    * name:  stringCompare
    * parameters:  char* -- pointer to a string
    *              char* -- pointer to a string
    * returns:  1 if strings are equal; 0 otherwise
    * description:  compares two strings character by character
    *               to determine if they are equal
    */

Cay Horstmann says...

"The description of a function comment block does not document the implementation but the idea."

{TopOfPage} {Tutorial} {CodingGround (online IDE)} {CPP.sh (online IDE)} {C at MIT} {GDT::C/C++ Resource}


Calling a Function

A function is called by specifying the function's name, followed by a left paren, a comma separated list of arguments (if any) and a right paren. Comma when used in this context is not the sequence operator.

Example.

   int rv = x(200, 210);
      //call the function  x  passing it two values: 200 and 210
      //capture the return value from  x  in the variable rv

   abort();
      //call the function  abort  
      //no arguments are passed, nor does the function return any value

A function call is a sequence point: every EXPR that comprises an argument is evaluated prior to the function being called. The order in which the arguments are evaluated is undefined.

C and C++ are "call-by-value" languages: the arguments are evaluated and their respective values are passed to the function.

{TopOfPage} {Tutorial} {CodingGround (online IDE)} {CPP.sh (online IDE)} {C at MIT} {GDT::C/C++ Resource}


Function Return Value

A function communicates back to the caller by "returning" a value.

The type of value returned by a function (i.e. the return-type) is specified when the function is defined. If the return-type is not specified, then it defaults to int.

A function does not have to return a value. In these cases, the return-type void is used.

Other points.

The syntax of the return statement is as follows.

   return;       //used when the return-type of the function is void

   return EXPR;
      
      EXPR is evaluated.  The result of the evaluation, if needed,
      is converted to the return-type of the function.  For example,
      if the return-type of the function is  int  , then

         return 3.14;  

      will cause the 3.14 double to be converted to an int; therefore,
      the calling function will be returned the value 3.

   Optionally, the return EXPR can be enclosed in parens.

      return (EXPR);  
      return (3.14);           or   return 3.14;
      return (EXIT_SUCCESS);   or   return EXIT_SUCCESS;
      return (i + 3 * j);      or   return i + 3 * j;
      return (i > 4);          or   return i > 4;

If a function returns a value, then it is the caller's responsibility to examine the return value.

If a function's return-type is void, then return statements, if any, cannot have any expressions.

If a function is called that returns a value and the return value is ignored by the caller, then the caller should type-cast the function call to be (void).

   (void) printf("hello, world\n");

{TopOfPage} {Tutorial} {CodingGround (online IDE)} {CPP.sh (online IDE)} {C at MIT} {GDT::C/C++ Resource}


Lifetime and Visibility (scope)

Lifetime is the period, during execution of a program, in which a variable or function exists (all functions in a program exist at all times during its execution).

Visibility is the portions of the program in which a variable or function can be referenced by name (also referred to as scope (scope units: file, function, block or function prototype).

Local and Global Variables

Local variables are declared and/or defined within a block (either a function or a compound statement).

Global variables are declared and/or defined outside of any function.

Storage Classes
auto

The auto storage class indicates that a variable is a local variable and that memory is automatically allocated/de-allocated. By default, local variables are defined to be auto; therefore, this storage class is rarely specified.

Using auto on a global variable is illegal.

Automatic variables are not initialized to any known value.

extern

The extern storage class allows a global variable to be visible across multiple source files.

A global variable defined in file A can be accessed in file B if and only if file B contains the following declaration.

	extern type variable_name;

	extern int errno;  /* errno is defined in some other file */

Use of extern results in a declaration -- not a definition. In other words, an extern declaration does not result in the allocation of memory.

It is not legal to initialize an external variable at the time it is declared (i.e. extern float rate = 5.3; is illegal).

static

When static is applied to a global variable, then it limits the scope of an object to the rest of the file (i.e. it cannot be externally declared in other source files).

When static is applied to a local variable, then it causes the local variable to remain in existence across function calls providing private, permanent storage within a single function.

When static is applied to a function, then the function is visible only to the source file in which is defined (in a way, the function is made "private").

register

A register is a high speed memory location located on the CPU.

The register storage class is used on local variables that are going to be accessed many times (for example, loop control variables).

{TopOfPage} {Tutorial} {CodingGround (online IDE)} {CPP.sh (online IDE)} {C at MIT} {GDT::C/C++ Resource}


Reference Parameters

C++ passes arguments to functions "by value." This calling mechanism prohibits called functions from modifying the variables defined in the caller functions.

Reference parameters  allows arguments to be passed "by reference" instead of by value. If you pass an argument to a function by reference, then the called function can access that variable.

Passing an argument by reference causes the address of the variable argument to be passed rather than its value; consequently, the called function receives a "pointer" to the variable. Although the called function is dealing with a pointer, pointer notation is not required.

A parameter that is received as a reference becomes an alias for the variable that was passed.

Reference parameters are often used when passing large arguments to a function (e.g. a structure). It is more efficient to by reference than it is to pass by value (less data must be copied).

Suppose we have a function that converts inches to feet and inches. We have a problem: our function needs to return two pieces of information, but is allowed to return only one value. One solution to the problem is to use reference parameters:

   int convertInches(int totalInches, int& feet);
      // the & after the type  int  indicates that the function
      // receives a reference to an  int
   
   ...

   int feet;
   int inches = convertInches(80, feet);  
      // feet is passed by reference; no special notation is needed

   ...

   /**
      The function receives the total inches.  The return value
      of the function is the inches, and the feet are copied
      into the reference parameter supplied as the 2nd argument.
   **/

   int convertInches(int totalInches, int& feet) {
      const int INCHES_PER_FOOT = 12;
      feet = totalInches / INCHES_PER_FOOT;
      return totalInches % INCHES_PER_FOOT;
   }

{TopOfPage} {Tutorial} {CodingGround (online IDE)} {CPP.sh (online IDE)} {C at MIT} {GDT::C/C++ Resource}


Default Function Arguments

Default function arguments can be specified in either the function definition or prototype (but not both). Convention is to specify them in the function prototype.

Only the rightmost arguments can be defaulted. Once a default function argument is used, all remaining arguments must be default arguments.

Default arguments are useful when a function needs more arguments than are necessary to handle simple cases; in particular, functions that construct objects often provide several options for flexibility.

   Syntax:  data-type function_name(Type param = value);

   If  param  is not passed by the caller, then it will be set
       to  value  .

   Example:

      void printReport(char filename[], bool condensed = false);
      // one argument is required:  a filename, but the second is a
      //     default argument and it defaults to  false  if not passed

      printReport("report.out");           //1
      printReport("report.out", true);     //2
      printReport("report.out", false);    //3
          //statements 1 and 3 are equivalent

{TopOfPage} {Tutorial} {CodingGround (online IDE)} {CPP.sh (online IDE)} {C at MIT} {GDT::C/C++ Resource}


Function Overloading

Function overloading is the ability to give different functions the same name.

Stroustrup says:

Most often, it is a good idea to give different functions different names, but when some functions conceptually perform the same task on objects of different types, it can be more convenient to give them the same name.
Use descriptive overloaded function names to describe similar operations, not different behaviors.
   Good:
      int max(int, int);        //find max of two ints
      float max(float, float);  //find max of two floats

   Not so Good:
      void draw(Image*);        //draw an image
      void draw(Card*);         //draw a card 
The following is an example of an overloaded function:
   void print(int), print(double), print(long), print(char), 
        print(int, int), print(double, double);

   char c;
   short s;
   int i;
   float f;

   print(c);            // invoke print(char)
   print(i);            // invoke print(int)
   print(s);            // invoke print(int); s promoted to int
   print(f);            // invoke print(double); f promoted to double
   print('A');          // invoke print(char); 'A' is a char
   print(200);          // invoke print(int); 200 is an int
   print(200L);         // invoke print(long); 200L is a long
   print(99.9);         // invoke print(double); 99.9 is a double
   print(i, i);         // invoke print(int,int)
   print(i, 'a');       // invoke print(int,int); 'a' promoted to int
   print(s, 'A');       // invoke print(int,int); s - 'A' promoted to int
   //! print(1.1L);     // ambiguous -- compiler can't decide
   print(200L, i);      // invoke print(int,int);
   //! print(i, 3.14);  // ambiguous -- compiler can't decide
When  print()  is called, the compiler must figure out which of the functions with the name "print" is to be invoked. This is done by comparing the types of the actual arguments with the types of the parameters of all functions called "print". The function with the best match is called; if none exist, then a compile-time error.

Definition:

The  signature  of a function is defined to consist of the name of a function and its ordered set of parameter data types.

Criteria used to determine a match (partial):
  1. exact match of function call arguments with an overloaded function signature
  2. trivial conversions (exact match after applying promotions to argument data types -- char ==> int, short ==> int, float ==> double)
  3. exact match after applying promotions and standard conversions to argument data types (float ==> int, double ==> float)
  4. exact match after programmer defined conversions (e.g. typecast)
Note:  const  arguments can be distinguished from non-const arguments.
	void foo(const char*);
	void foo(char*);

	These two functions have different signatures.
Return types are not considered in overload resolution.

Functions declared in different scopes do not overload.

If not careful, you can be surprised as to which function is called. It is poor programming practice to redefine library functions (your code may end up calling the correct function, but existing code in the library may end up invoking the wrong function).

Internally, the compiler accomplishes function overloading by  mangling  function names. Function name mangling makes each translated function name unique. A function name is mangled by adding prefixes and suffixes to the the name. The prefixes and suffixes are determined in part by the ordered lists of the function's parameter data types.

	print(200);             // mangled name: print_Fi
	print(float(3.14));     // mangled name: print_Ff

Function Overloading
C: no   C++: yes   Java: yes

{TopOfPage} {Tutorial} {CodingGround (online IDE)} {CPP.sh (online IDE)} {C at MIT} {GDT::C/C++ Resource}


Home Previous Next