Chapter 3
Basic facilities, Part 2

Kurt Nørmark
Department of Computer Science, Aalborg University


Abstract
Previous lecture Next lecture
Index References Contents
In this lecture we discuss selected, basic facilities in C++, as covered in Part 1 of The C++ Programming Language (version 3).

Basic facilities - in this lecture
Slide Contents Index
References 

Basic C++ facilities covered in this lecture

  • Lambda expressions

  • Type conversion

  • Function overloading

  • Strings in C++

  • Vectors

  • The free store

  • IO

  • Program organization - physical and logical

Reference

Lambda expressions
Slide Contents Index
References 

C++11 supports lambda expressions, as known from functional programming languages

Reference
  • The C++ Prog. Lang. (4. edition): Page 290-298

Reference

Program: Different forms of lambda expressions.
  auto f1 = [](int x)->int{return x + 1;};   // a function that returns x + 1. 
  auto f2 = [m](int x){return x + m;};       // can read m from local context.
  auto f3 = [=](int x){return x + m + n;};   // can read all local variables from local context.
  auto f4 = [&m](){++m;};                    // access to m by reference - can write.
  auto f5 = [&](){++m; n--;};                // access to all variables in local context by ref.
  auto f6 = [&]{++m;};                       // the empty parameter list is implicit

  • Constituents of a C++ lambda expression

    • Capture list [...] - the lambda introducer

    • Formal parameter list (optional): (parameters)

    • Return type declaration (optional): -> Type

    • Body: {...}

Program: The examples from above in context.
// Forms from slide - in context.

#include <iostream>     
#include <string>

int g{4};   // global variable

int main () {
  int m{5}, n{6};

  auto f1 = [](int x)->int{return x + 1;};  // a function that returns x + 1. 
  auto f2 = [m](int x){return x + m;};      // can read m from main.
  auto f3 = [=](int x){return x + m + n;};  // can read all local variables in main.
  auto f4 = [&m](){++m;};                   // access to m by reference - can write.
  auto f5 = [&](){++m; n--;};               // access to all local variables in main by reference.
  auto f6 = [&]{++m;};                      // empty parameter list implicit.

  auto g1 = [](int x){return x + g;};       // g1 can access a global variable!

  std::cout << f1(1) << std::endl;     // 2
  std::cout << f2(1) << std::endl;     // 6
  std::cout << f3(1) << std::endl;     // 12

  f4();                                // m becomes 6
  f5();                                // m becomes 7, n becomes 5
  f6();                                // m becomes 8

  std::cout << g1(1) << std::endl;     // 5
  
  std::cout << "m: " << m << " n: " << n << std::endl;    // m: 8 n: 5

}

Program: More forms - mutable and explicit return type.
// Illustration of mutable in context of lambda expressions.

#include <iostream>     
#include <string>

int main () {
  int p{7};

  auto f7 = [p](){++p;};                        // COMPILE TIME ERROR: increment of read-only variable p
  auto f8 = [p]()mutable{++p;};                 // now OK, but strange.  Does NOT affect p in main
  auto f9 = [&p](){++p;};                       // OK: Does affects p in main.

  f8();                                         // A local copy of p is incremented. p in pmain unaffected.
  std::cout << "p: " << p << std::endl;         // p: 7

  f9();                                         // p in main is incremented, via the reference.
  std::cout << "p: " << p << std::endl;         // p: 8
}

Program: An immediate call of a lambda expression.
// Immediate call of a lambda expression on the actual parameter 1, without putting it into a variable.

#include <iostream>     
#include <string>

int main () {
  int m{5}, n{6};

  std::cout << "f1(1): " <<  [=](int x){return x + n + m;}(1) << std::endl;   // f1(1): 12
}

Program: Lambda expressions and recursive calls.
// Recursive lambdas

#include <iostream>     
#include <string>
#include <functional>   // std::function

int main () {
  using namespace std;

  auto fac1 = [&](int i){                     // Error: auto cannot be used.
    return i <= 0 ? 1 : i * fac1(i - 1);      // Cannot deduce the type of the function.
  };  

  function<int(int)>fac2 = [&fac2](int i){    // Use the function<R(A)> type specifier.
    return i <= 0 ? 1 : i * fac2(i - 1);      // Now the type of the lambda does not need to be deduced.
  };                                          // The capture is crucial in order to capture fac2.
  
  cout << "fac2(5): " << fac2(5) << endl;     // fac2(5): 120

}

Program: A function that returns a function - closure - problems.
// A function that returns the value of a lambda expression - a closure.
// Problems! NO compiler warning issued.

#include <iostream>     
#include <string>
#include <functional>     // std::function

std::function<int(int)> f(const int seed0){
  int seed{seed0};        // Encapsualted local state

  return [&seed](int i){  // Returns a closure that captures seed. 
     seed++;              
     return i + seed;
  };

}

int main () {
  using namespace std;
  auto g = f(5);          // f(5) returns a function that accesses seed.
                          // seed dangles in the closure in g.

  for(int i = 1; i <= 5; i++)
    cout << "g(" << i << ") = " << g(i) << endl;    // Unpredicatable output
}

Program: Same in Scheme - where this kind of stuff works out of the box.
; Shows a Scheme counterpart to lambda3.cc.  Gives same output as lambda4.cc.

(define (f seed0)
  (let ((seed seed0))
    (lambda (i)
       (set! seed (+ 1 seed))
       (+ i seed))))

(define g (f 5))

(map (lambda (a) (g a)) (list 1 2 3 4 5))  ; (7 9 11 13 15)

Program: A function that returns a function - OK.
// A function that returns the value of a lambda expression - a closure.
// The closure refers a global variable, seed, instead of a dangling local variable in f.
// This compiles and runs. But it is rather primitive seen from a functional programming point of view.

#include <iostream>     
#include <string>
#include <functional>     // std::function

int seed; 

std::function<int(int)> f(const int seed0){
  seed = seed0;      // Assigning global variable

  return [](int i){  
     seed++;              
     return i + seed;
  };

}

int main () {
  using namespace std;
  auto g = f(5);          // f(5) returns a function that can access the 
                          // global seed. OK. But not that interesting.

  for(int i = 1; i <= 5; i++)
    cout << "g(" << i << ") = " << g(i) << endl;    // 7, 9, 11, 13, 15
}

Exercise 3.2. Get some experience with lambda expressions in C++

This is an open exercise about lambda expressions in C++. Overall, the purpose is to get some practical experience with lambda expressions in C++, based on our knowledge of lambda expressions in functional programming languages.

One way to go: Write some small C++ programs that illustrate the various forms of capture as described in section 11.4.3 (page 293) in The C++ Prog. Lang. (4. edition).

Another way to go: Make some experiments with mapping, filtering, and accumulation supported by higher-order C++ functions and C++ lambda expressions. Feel free to use vectors, or another kind of container for your experiments. You may find inspiration in the PP lecture about higher-order functions.

A third way to go: Functions cannot be nested in C++. Do some research on nesting of lambda expression into each other.

Feel free to choose your own experiments!

Type conversion - Grand Overview
Slide Contents Index
References 

The purpose of this page is to give an overview of implicit and explicit type conversion in C++

Reference
  • The C++ Prog. Lang. (3. edition): Page 130-131, 408, 819

Reference
  • The C++ Prog. Lang. (4. edition): Page 298-303, 643-646

  • Implicit conversions

    • Between built-in, fundamental (numeric) types - some details next page

    • Between user-defined types and built-in types

      • Via constructors and conversion operators

      • More details in the next lecture

    • In between user defined types

Reference

  • Explicit conversions to type T:     - more a couple of slides ahead

    • static_cast<T>(expr) from a related type

    • reinterpret_cast<T>(expr) from an unrelated type   -   "dangerous"

    • dynamic_cast<T*>(expr) from a related polymorphic type (pointer or reference types only)

    • const_cast<T>(expr) from a closely related type (add or remove const qualifier)

    • C-style type casts: (T)expr   or   T(expr) using early C++ notation

Item 2 of More Effective C++ has some basic arguments in favor of 'the new C++ casts' in contrast to C-style casts.

Implicit type conversions
Slide Contents Index
References 

"Integral og floating-point types can be mixed freely in assignments and expressions"

Reference
  • The C++ Prog. Lang. (3. edition): Page 833

  • "Fundamental types can be converted to each other in a bewildering number of ways"

  • Promotions

    • A kind of normalization to certain integer types and floating point types before arithmetic operations

  • Usual Arithmetic conversions

    • Convert arguments of binary operators to a common type

  • Conversion to boolean

    • From pointers, integers, and floating point types

    • Non-zero value transformed to true and zero to false

  • Conversion between integer types

    • Unsigned: Cutting off bits

    • Signed: Implementation dependent

  • Conversion between floating point and integer types

    • May cause loss of information, or may be undefined (in case of range problems)

Explicit type conversion
Slide Contents Index
References 

C++ supports a number of different kinds of type castings

Use of these cast operations makes it easier for the programmer to spot the more dangerous type conversions

Reference
  • The C++ Prog. Lang. (3. edition): Page 130-131, 407-408 (dynamic casts), 819

Reference
  • The C++ Prog. Lang. (4. edition): Page 298-303

  • static_cast<T>(expr)

    • Casting between related types

    • Can activate built-in or user-defined type conversion, conversion operators, and implicit/explicit constructors.

    • Cannot cast away const

  • reinterpret_cast<T>(expr)

    • Casting between unrelated types, potentially unsafe

    • Can cast between pointer types, and between integer and pointer types.

    • ... provides a value of new type that has the same bit pattern as its argument

  • dynamic_cast<T>(expr)

    • Primarily for down casting or cross casting of a pointer to an object (of polymorphic type)

    • For a pointer p, dynamic_cast<T>(p) can be seen as the question: 'Is the object pointed to by p of type T ?'.

  • const_cast<T>(expr)

    • Add or remove const qualifier to a type

Probably the most frequently used, see item 2 of More Effective C++

Examples of casting
Slide Contents Index
References 

A number of concrete illustrations of casting in C++

Program: Examples of static casting in C++.
// In this example we initialize with the non-narrowing initializer {}.
// This makes it possible to illustrates the effect of static casting.

#include <iostream>
#include <string>

int main(){
  // Static casting int to double:
  int k{3};
  double e{k};                             // error (warning): narrowing conversion of k from int to double
  double f{static_cast<double>(k)};        // f initialized to 3.0
  std::cout << f <<std::endl;              // 3

  // Static casting double to int:
  double d{123.456};
  int i{d};                                // error (warning): narrowing conversion of d from double to int 
  int j{static_cast<int>(d)};              // j initialized to 123
  std::cout << j <<std::endl;              // 123
}

Program: The compilable parts of the program from above.
// The compilable parts of the previous program.

#include <iostream>
#include <string>

int main(){
  // Static casting int to double:
  int k{3};
  double f{static_cast<double>(k)};
  std::cout << f <<std::endl;              // 3

  // Static casting double to int:
  double d{123.456};
  int j{static_cast<int>(d)};              // j initialized to 123
  std::cout << j <<std::endl;              // 123
}

Program: Example of dynamic casting in C++.
// Illustration of dynamic_cast in a classical polymorphic situation.

#include <iostream>
#include <string>

using namespace std;

class A {
  public: 
  virtual void f(){
    cout << "Here is f in A" << endl;
  }
};

class B: public A {
  public: 
  void f() override{
    cout << "Here is f in B" << endl;
  }
};

int main(){

  A* a = new B{};                   // a can point to a B-object
  a->f();                           // Here is f in B

  B* b1 = a;                        // error: invalid conversion from A* to B*
  b1->f();  

  B* b2 = dynamic_cast<B*>(a);      // Now OK with dynamic cast
  b2->f();                          // Here is f in B.

  // Attempting to enforce calling f from A from b2:
  dynamic_cast<A*>(b2)->f();        // Here is f in B.  Not successful.
  static_cast<A*>(b2)->f();         // Here is f in B.  Not successful.
  b2->A::f();                       // Here is f in A.  OK - thats the way to do it.  
}

Program: The compilable parts of the program from above.
// The compilable parts of the previous program. 

#include <iostream>
#include <string>

using namespace std;

class A {
  public: 
  virtual void f(){
    cout << "Here is f in A" << endl;
  }
};

class B: public A {
  public: 
  void f() override{
    cout << "Here is f in B" << endl;
  }
};

int main(){
  A* a = new B{};                   // a can point to a B-object
  a->f();                           // Here is f in B

  B* b2 = dynamic_cast<B*>(a);      // Now OK with dynamic cast
  b2->f();                          // Here is f in B.

  // Attempting to enforce calling f from A from b2:
  dynamic_cast<A*>(b2)->f();        // Here is f in B.  Not successful.
  static_cast<A*>(b2)->f();         // Here is f in B.  Not successful.
  b2->A::f();                       // Here is f in A.  OK - thats the way to do it.
}

Reference

Program: dynamic_cast is a predicate - it tests if a pointer is of the appropriate type.
// Illustration of dynamic_cast as a predicate.

#include <iostream>
#include <string>

using namespace std;

class A {
  public: 
  virtual void f(){
    cout << "Here is f in A" << endl;
  }
};

class B: public A {
  public: 
  void f() override{
    cout << "Here is f in B" << endl;
  }
};
// dynamic_casts<T>x in C++ is similar to  x as T  in C#

class C: public A {
  public: 
  void f() override{
    cout << "Here is f in C" << endl;
  }
};

int main(){

  A* a = new B{};                   // a can point to a B-object
  a->f();                           // Here is f in B

  if (B *b = dynamic_cast<B*>(a))   // if(a is of type B*) ...
     b->f();                        // yes: Here is f in B
  else
     cout << "Cannot" << endl;

  if (C *c = dynamic_cast<C*>(a))   // if(a is of type C*))...
     c->f();    
  else
     cout << "Cannot" << endl;      // no: Cannot

}

Reference

Program: Examples of reinterpret_cast.
// Illustration of reinterpret cast.

#include <iostream>
#include <cassert>
#include <cstdint>  // because of uintptr_t

int main(){
  using namespace std;

  // Casting between pointer and unsinged integers.
  int i;
  uintptr_t i_adr = reinterpret_cast<uintptr_t>(&i);     // Casting the address of i to an an unsigned int
  cout << i_adr << endl << endl;
  int *j = reinterpret_cast<int*>(i_adr);                // Casting the integer back to a pointer.
  assert(j == &i);                                       // The roundtrip must succeed.


  // Attempting reinterpret cast from double to long long int:
  static_assert(sizeof(long long int) == 
                sizeof(double), 
                "unexpected sizes");                     // Asserts statically that both types are of same byte length

  double e{124.567};
  long long int m
    {reinterpret_cast<long long int>(e)};                // Error: Attempting to reinterpret a double as a long long int

  // Doing the trick via pointers:
  double *pe{&e};                          
  long long int *llip
   {reinterpret_cast<long long int*>(pe)};                // OK
  std::cout << *llip << std::endl;                        // 4638466040292848959
}

Program: The compilable parts of the program from above.
// The compilable parts of the program from above.

#include <iostream>
#include <cassert>
#include <cstdint>  // because of uintptr_t

int main(){
  using namespace std;

  // Casting between pointer and unsinged integers.
  int i;
  uintptr_t i_adr = reinterpret_cast<uintptr_t>(&i);     // Casting the address of i to an an unsigned int
  cout << i_adr << endl << endl;
  int *j = reinterpret_cast<int*>(i_adr);                // Casting the integer back to a pointer.
  assert(j == &i);                                       // The roundtrip must succeed.


  // Doing the trick (double to long long int) via pointers:
  static_assert(sizeof(long long int) == 
                sizeof(double), 
                "unexpected sizes");                     // Asserts statically that both types are of same byte length

  double e{124.567};
  double *pe{&e};                          
  long long int *llip
     {reinterpret_cast<long long int*>(pe)};             // OK (or at least done).
  std::cout << *llip << std::endl;                       // 4638466040292848959
}

Function Overloading
Slide Contents Index
References 
Intern kommentar til forelæsningen om denne slide:
Dybest set ikke et særligt interessant emne. Teknikaliteterne bør ikke tage for meget tid. Koncentrer dig om det overordnede. Gå dog gennem eksemplerne to slides længere fremme.

Functions can be overloaded in C++

Several function definitions with the same name, but different formal parameters

Overload resolution is a compile time issue

Reference
  • The C++ Prog. Lang. (3. edition): Page 149-153

Reference
  • The C++ Prog. Lang. (4. edition): Page 326-330

  • Function overloading

    • For a given function call, f(a1, ..., an), the types of the actual parameters are used to select a single best match among the overloaded function definitions

      • If no single best match can be found, an ambiguity is reported at compile time

    • The function return types do not take part in overload resolution

  • Finding the best match

    • Pairs of actual and formal parameters are compared - details on the next page

    • A function that is the best match for one argument and a better than or equal match for all other arguments is called (§12.3.4, 4ed)

      • The call is ambiguous if no such function exists

Function Overloading - more detailed rules
Slide Contents Index
References 

Matching the type of an actual parameter and the type of a formal parameter

The call is ambiguous if two or more matches are found at the same level

The criateria below are tried in order

Reference
  • The C++ Prog. Lang. (3. edition): Page 149

Reference
  • The C++ Prog. Lang. (4. edition): Page 327

  • Exact match

    • Only trivial conversions are performed

    • Array name to pointer, function name to function pointer, T to const T,...

  • Match using promotions

    • Integral promotions and floating point promotions

    • bool to int, char to int, short to int, float to double, ...

  • Match using standard conversions

    • int to double, double to int, double to long double, derived* to base*, T* to void*, int to unsigned int, ...

  • Match using user defined conversions

    • Conversion operators and constructors

  • Match using the ellipsis ...

    • Unspecified number of arguments

Function Overloading - Examples
Slide Contents Index
References 
Intern kommentar til forelæsningen om denne slide:
Illustrerer mulige tvetydigheder ved overloading. Ret så ligefrem. Tag dig tid til at illustrere.

We show examples of single best match and ambiguous function overloading

Program: Exact matches - a trivial example.
/* Two exact matches  */

#include <iostream>
#include <string>

using namespace std;

void f(long int i){
  cout << "f(long int)" << endl;
}

void f(short int i){
  cout << "f(short int)" << endl;
}





int main(){
  long int a = 5;
  f(a);             // exact match: f(long int)

  short int b = 5;
  f(b);             // exact match: f(short int)
}

Program: Simple examle of an ambiguity.
/* Matches using promotions. */

#include <iostream>
#include <string>

using namespace std;

void f(long int i){                 // Match: promotion
  cout << "f(long int)" << endl;
}

void f(short int i){                // Match: promotion
  cout << "f(short int)" << endl;
}





int main(){
  int c = 5;
  f(c);             // error: call of overloaded f(int&) is ambiguous
                    // note: candidates are: void f(long int)
                    // note:                 void f(short int)

}

Program: An ambiguity between 'float to int' and 'float to long int'.
/* Mathces using standard conversions.  */

#include <iostream>
#include <string>

using namespace std;

void f(int i){                      // Match: standard conversion
  cout << "f(int)" << endl;
}

void f(long int i){                 // Match: standard conversion
  cout << "f(long int)" << endl;
}





int main(){
  float c = 5.5;
  f(c);             // error: call of overloaded  f(float&)  is ambiguous
                    // note: candidates are: void f(int)
                    // note:                 void f(long int)

}

// This example is continued in the next program...

Program: 'Float to double' conversion prefered over 'float to int' and 'float to long int'.
/* One best match using promotion. Two others at a lower level using standard conversions.  */

#include <iostream>
#include <string>

using namespace std;

void f(int i){                         // Match: standard conversion
  cout << "f(int)" << endl;
}

void f(long int i){                    // Match: standard conversion
  cout << "f(long int)" << endl;
}

void f(double d){                      // Match:  promotion
  cout << "f(double)" << endl;
}

int main(){
  float c = 5.5;
  f(c);             // A single best match:  f(double)  
                    // The float to double promotion is in another category than
                    // float -> int  and  float -> long int.
}

Program: Point.h.
// Now overloading examples with class Point.
// Class point with a constructor of type double, and a conversion from Point to double.

class Point {
private: 
  double x, y;

public:
  Point(double d);                // Convert a double to a Point (via Point constructor)
  operator double() const;        // Convert a Point to a double (via conversion operator)

  double getx () const;
  double gety () const;
};

std::ostream& operator<<(std::ostream&, const Point&);

Program: double to Point via Point(double) constructor.
/* One best match using user-defined conversion. One candidate function does not match at all.  */


#include <iostream>
#include <string>
#include "point.h"

using namespace std;

void f(Point p){                  // Match: user-defined conversion
  cout << "f(Point)" << endl;
}

void f(char *c){                  // No match
  cout << "f(char *)" << endl;
}









int main(){
  double c = 5.5;        
  f(c);                // A single best match: f(Point)
                       // f(char *) does not match at all.
}

// This example is continued in the next program...

Program: 'double to char' instead of 'double to Point'.
/* One best match using a standard conversions. One match at lower level */
/* (user defined conversion). One not matching at all.                   */

#include <iostream>
#include <string>
#include "point.h"

using namespace std;

void f(Point p){                       // Match - user-defined conversion
  cout << "f(Point)" << endl;
}

void f(char *c){                       // No match
  cout << "f(char *)" << endl;
}

void f(char c){                        // Match - standard conversion
  cout << "f(char)" << endl;
}





int main(){
  double c = 5.5;        
  f(c);                   // A single best match:  f(char)
                          // The conversion double -> char is in a higher category than
                          // the user-defined double -> Point
} 

// This example is continued in the next program...

Program: Now in an ambiguity situation.
/* Two standard conversions (at same level), one user-defined conversion at a lower level. */
/* One function not matching at all.                                                       */

#include <iostream>
#include <string>
#include "point.h"

using namespace std;

void f(Point p){                    // Match at lower level
  cout << "f(Point)" << endl;
}

void f(char *c){                    // No match
  cout << "f(char *)" << endl;
}

void f(char c){                     // Match - standard conversion
  cout << "f(char)" << endl;
}

void f(float c){                    // Match - standard conversion
  cout << "f(float)" << endl;
}

int main(){
  double c = 5.5;      
  f(c);                // error: call of overloaded  f(double&)  is ambiguous
                       // note: candidates are: void f(Point)
                       // note:                 void f(char)
                       // note:                 void f(float)
}

Program: A single best match again - slightly surprising perhaps.
/* One single best match: user-defined conversion. One match at a lower level. */
/* Two not matching at all.                                                    */

#include <iostream>
#include <string>
#include "point.h"

using namespace std;

void f(char *c){                      // No match
  cout << "f(char *)" << endl;
}

void f(float c){                      // Best match - by promotion
  cout << "f(float)" << endl;
}

void f(string c){                     // No match
  cout << "f(string)" << endl;
}

void f(...){                          // Match at lower level - ellipsis
  cout << "f(...)" << endl;
}

int main(){
  Point p(5.5);   
  f(p);           // A single best match: f(float)
                  // the conversion operator point -> double,
                  // after a double to float conversion  

                  // f(char *) and f(string) do not match at all.
                  // f(...) matches, but it is in a lower matching category
}

Program: A trivial example with overloading of a function of two parameters.
/* Matching that involves two parameters. */

#include <iostream>
#include <string>

using namespace std;

void f(int i, double d){
  cout << "f(int, double)" << endl;
}

void f(double d, int i){
  cout << "f(double, int)" << endl;
}

int main(){
  f(5, 5.5);     // f(int, double)
  f(5.5, 5);     // f(double, int)
  f(5, 6);       // Ambiguous
  f(5.6, 6.5);   // Ambiguous
}

C-style text strings
Slide Contents Index
References 

C++ supports both C-style strings and type String via the Library.

Reference
  • The C++ Prog. Lang. (3. edition): Page 90

Reference
  • The C++ Prog. Lang. (4. edition): String literals. Page 176

  • C-style strings: char* or char[]

    • A pointer to the first character in an array of characters

    • Zero terminated

    • A string literal in C++ program, such as "string literal", is not of type String

    • Almost identical to string handling in C programs

Both C-style strings and objects of type string are mutable.

C++ style text strings
Slide Contents Index
References 

It is recommended to use type std::string instead of the low-level char* or char[]

Reference
  • The C++ Prog. Lang. (3. edition): Page 48-49, 579

Reference
  • The C++ Prog. Lang. (4. edition): Page 90-91, 1033

  • Type string in C++:

    • string is alias for a basic_string parameterized by char:

      • typedef basic_string<char> string

    • Strings has value semantics

      • Copied in and out of functions - or passed by reference

    • Characters in strings can be accessed in checked mode and unchecked mode:

      • Checked mode: s.at(i)

      • Unchecked mode: s[i]

    • A string is mutable

    • Strings can be copy constructed based on a C-style string

      • string s{"Peter"};     or     string s("Peter");     or     string t = "Peter";

    • The basic_string class supports a large number of string operations

    • Short string optimization:

      • Short strings are allocated in the string object itself (not on the free store)

Reference

Strings - examples
Slide Contents Index
References 
Intern kommentar til forelæsningen om denne slide:

Knold og tot eksemplerne illustrerer værdisemantik på C++ strenge - og det modsatte på C strenge

Det sidste store eksempel viser en del C++ string funktioner. Tal hurtigt hen over den.

We show simple examples that illustrate the value semantics of C++ strings and the pointer semantics of C-style strings

Reference
  • The C++ Prog. Lang. (3. edition): Page 588

Program: The Knold & Tot example from section 20.3.6 of 3rd version of the C++ book.
// From Stoustrup, The C++ Programming Language, 3ed ed, page 588.
// Illustrates value semantics of C++ strings.

#include <iostream>
#include <string>

void g(){
  std::string s1{"Knold"},
              s2{"Tot"};

  s1 = s2;            // Now s2 is "Tot" and s1 is another copy of "Tot".
  s2[1] = 'u';        // s2 is mutated. Now "Tut".

  std::cout << s1 << " og " << s2 << std::endl;             // Tot og Tut
}

int main(){
  g();
}

Program: The Knold & Tot example with char*.
// Illustrates reference semantics of C-like strings.
// Compiles with warnings, but does probably not execute correctly (likely: segmentation fault). 

#include <iostream>


void g(){
  char *s1 = "Knold",  // Compiler Warnings:
       *s2 = "Tot";    // deprecated conversion from string constant to char*

  s1 = s2;             // Both s1 and s2 points to the same "Tot" C-style string.
  s2[1] = 'u';         // Expected run-time error here, because literal strings in the program a non-mutable.
  
  std::cout << s1 << " og " << s2 << std::endl;  
}

int main(){
  g();
}

Program: The Knold & Tot example with char[].
// Illustrates that C-like strings cannot be assigned.
// The similar assignment is possible on C++ strings.
// Does not compile.

#include <iostream>

void g(){
  char s1[6] = "Knold",
       s2[6] = "Tot  ";

  s1 = s2;           // Compiler error: Invalid array assignment
  s2[1] = 'u';       // OK.
  
  std::cout << s1 << " og " << s2 << std::endl; 
}

int main(){
  g();
}

Program: The Knold & Tot example with strcpy from <cstring>.
// Use of strcpy - remedies the problem in the previous C program.
// Compiles and runs.

#include <iostream>
#include <cstring>    // Corresponds to <string.h> in a C program.

void g(){
  char s1[] = "Knold",
       s2[] = "Tot";

  std::strcpy(s1, s2);   // Works because "Tot" is not longer than "Knold".
  s2[1] = 'u';
  
  std::cout << s1 << " og " << s2 << std::endl;   // Tot og Tut
}

int main(){
  g();
}

Program: The Knold & Tot example with char* aliasing.
// Illustrates pointer access to C-style trings.

#include <iostream>
#include <cstring>    // Corresponds to <string.h> in a C program.

void g(){
  char s1[6] = "Knold",
       s2[4] = "Tot",
       *ps1 = &s1[0],    
       *ps2 = &s2[0];

  ps1 = ps2;            // OK here.  Both ps1 and ps2 point at Tot.
  ps2[1] = 'u';         // Affects both ps1 and ps2
  
  std::cout << ps1 << " og " << ps2 << std::endl;   // Tut og Tut
}

int main(){
  g();
}

Reference

Program: Illustration of various basic string operations in C++.
// Illustrates use of of various C++ string functions.

#include <iostream>
#include <string>
#include <cstdlib>

using std::string;
using std::cout; using std::endl;

int main(){
  // CONSTRUCTION:
  string s0,         // the empty string
         s1 = "A string",
         s2("Another string"), 
         s3(s1),     // copy constructor
         s4(s1,2,6); // copy constructor, selected part
                     // 6 characters from s: "string"

  cout << s4 << endl; // "string"
  
  // ELEMENT ACCESS:
  cout << "s1[0] = " << s1[0] << endl;        // A
  cout << "s1[10] = " << s1[10] << endl;      // Index out of range. Risky at run time!

  cout << "s1[0] = " << s1.at(0) << endl;     // A
//cout << "s1[10] = " << s1.at(10) << endl;   // Compiles. Run time error : out of range.

  // ASSIGNMENT:
  s0 = s2;                                    // Assignment - copies all chars in s2.
  cout << s0 << endl;                         // "another string"

  // CONVERSION TO C-STYLE STRINGS:
  s1 = "1234";
  const char *str1 = s1.c_str();              // C++ string   ->  C string
  int str1_int = std::atoi(str1);             // C-style function: atoi applied
//int s1_int = std::atoi(s1);                 // Not possible to call atoi on a C++ string:
                                              // Cannot convert string to const char* 
  cout << "char*: " << str1                   // char*: 1234
       << " int: "  << str1_int << endl;      // int: 1234

  // COMPARISON:
  cout << "compare abc and def: "
       << string("abc").compare("def")        // -1
       << endl; 

  bool b = string("abc") < string("def");     // Comparison with the operator < is 
  cout << "b: " << b << endl;                 // also possible!    b: 1
 
  s1 = s2;
  cout << "compare s1 and s2: "               // s2 has just been copied to s1 - thus equal.
       << s1.compare(s2) << endl;             // 0

  // INSERT AND APPEND:
  string s5 = "Poul", s6 = "Hansen";
  s5 += ' ';
  s5 += s6;
  cout << "Appended names: " << s5 << endl;   // Poul Hansen

  s5.insert(5, "Ib ");                        // Insert "Ib" as middle name.
  cout << "Insert Ib middle name: " << s5     // Poul Ib Hansen
       << endl;

  s5 = "Peter", s6 = "Andersen";
  cout << s5 + " " + s6 << endl;              // Concatenation: 
                                              // Peter Andersen
}

Program: Same - with C++ initializations.
// Same as above - but with C++ string initialization. 

#include <iostream>
#include <string>
#include <cstdlib>

using std::string;
using std::cout; using std::endl;

int main(){
  // C++11 CONSTRUCTION:
  string s0{},         // the empty string
         s1{"A string"},
         s2{"Another string"}, 
         s3{s1},     // copy constructor
         s4{s1,2,6}; // copy constructor, selected part
                     // 6 characters from s: "string"

  cout << s4 << endl; // "string"
  
  // ELEMENT ACCESS:
  cout << "s1[0] = " << s1[0] << endl;        // A
  cout << "s1[10] = " << s1[10] << endl;      // Index out of range. Risky at run time!

  cout << "s1[0] = " << s1.at(0) << endl;     // A
//cout << "s1[10] = " << s1.at(10) << endl;   // Compiles. Run time error : out of range.

  // ASSIGNMENT:
  s0 = s2;                                    // Assignment - copies all chars in s2.
  cout << s0 << endl;                         // "another string"

  // CONVERSION TO C-STYLE STRINGS:
  s1 = "1234";
  const char *str1 = s1.c_str();              // C++ string   ->  C string
  int str1_int = std::atoi(str1);             // C-style function: atoi applied
//int s1_int = std::atoi(s1);                 // Not possible to call atoi on a C++ string:
                                              // Cannot convert string to const char* 
  cout << "char*: " << str1                   // char*: 1234
       << " int: "  << str1_int << endl;      // int: 1234

  // COMPARISON:
  cout << "compare abc and def: "
       << string("abc").compare("def")        // -1
       << endl; 

  bool b = string("abc") < string("def");     // Comparison with the operator < is 
  cout << "b: " << b << endl;                 // also possible!   b: 1
 
  s1 = s2;
  cout << "compare s1 and s2: "               // s2 has just been copied to s1 - thus equal.
       << s1.compare(s2) << endl;             // 0

  // INSERT AND APPEND:
  string s5 = "Poul", s6 = "Hansen";
  s5 += ' ';
  s5 += s6;
  cout << "Appended names: " << s5 << endl;   // Poul Hansen

  s5.insert(5, "Ib ");                        // Insert "Ib" as middle name.
  cout << "Insert Ib middle name: " << s5     // Poul Ib Hansen
       << endl;

  s5 = "Peter", s6 = "Andersen";
  cout << s5 + " " + s6 << endl;              // Concatenation: 
                                              // Peter Andersen
}

Program: Program output.
string
s1[0] = A
s1[10] = 
s1[0] = A
Another string
char*: 1234 int: 1234
compare abc and def: -3
b: 1
compare s1 and s2: 0
Appended names: Poul Hansen
Insert Ib middle name: Poul Ib Hansen
Peter Andersen

Exercise 3.3. String functionsContinue the programming from above with a C++ program that uses the find, replace, substr, and size functions from The C++ Prog. Lang. (4. edition) page 1045 - 1048.

Vectors in C++
Slide Contents Index
References 

C++ has a flexible vector type, as an alternative to the C-like array facility

Reference
  • The C++ Prog. Lang. (3. edition): Page 52, 442

Reference
  • The C++ Prog. Lang. (4. edition): Page 95-99

References

  • Vector characteristics:

    • A template class - type parameterized

    • Not of fixed size like an array - the number of elements may be adjusted dynamically

    • Size and capacity:

      • Size: The actual number of elements in the vector

      • Capacity: The maximum number of elements before resizing is needed

    • With or without range checking: The programmer's choice

    • Obeys value semantics

      • If too expensive, pointers or references to vectors can be used

    • Traversed with use of iterators

      • Iterators can be used in a similar way as pointers, via overloading of operators

      • *iterator, iterator++, iterator+n, iterator1-iterator2

Reference

Exercise 3.4. Understand Vectors

Take a look at the documentation of the (template) class vector at www.cplusplus.com

Familiarize yourself with the vector member functions, and read some of the (numerous) small examples which are available

Vectors - examples
Slide Contents Index
References 

We provide a couple of program examples that use the template class vector

Reference

Program: A simple vector example - similar to first array program.
// Similar to array-1.c discussed earlier.

#include <iostream>
#include <string>
#include <vector>

// Using declarations:
using std::string;
using std::vector;
using std::cout; using std::endl;

int main(){
  // Vector construction:
  vector<double> a;  // An empty vector of element type double
  double sum;

  // Adding elements to the back end:
  for (vector<double>::size_type i = 1; i <= 5; i++)
    a.push_back(i);

  // Mutation of an EXISTNG ELEMENT:
  a[1] = 2.2;

  // Sum up the elements - in a simple for-loop:
  sum = 0.0;
  for (int i = 0; i < a.size(); i++){
    sum += a[i];
  }
  cout << "Sum = " << sum << endl;  // 15.2

  // Sum up the elements - with iterators:
  sum = 0.0;
  for (vector<double>::iterator iter = a.begin();
       iter != a.end();
       iter++){
    sum += *iter;
  }
  cout << "Sum = " << sum << endl;  // 15.2
}

Program: A variant of the simple vector example - non-executable.
// Similar to program above, with various modernizations.
// Compiles, but causes an exception to be thrown at run-time.

#include <iostream>
#include <string>
#include <vector>

// Using declarations:
using std::string;
using std::vector;
using std::cout; using std::endl;

int main(){
  // Vector construction:
  vector<double> a{};  // An empty vector of element type double
  double sum;

  // Adding elements to the back end:
  for (vector<double>::size_type i = 1; i <= 5; i++)
    a.push_back(static_cast<double>(i));

  // Mutation of a NON-EXISTNG ELEMENT: Range error caught here (at run-time) with use of at instead of operator[]
  a.at(5) = 2.2;

  // Sum up the elements - with iterators - auto:
  sum = 0.0;
  for (auto iter = a.begin();
       iter != a.end();
       iter++){
    sum += *iter;
  }
  cout << "Sum = " << sum << endl;  

  // Sum up the elements - with range for:
  sum = 0.0;
  for (auto el: a)
    sum += el;
  cout << "Sum = " << sum << endl;  
}

Program: A variant of the simple vector example - executable.
// Compilable and executable variant of program form above.

#include <iostream>
#include <string>
#include <vector>

// Using declarations:
using std::string;
using std::vector;
using std::cout; using std::endl;

int main(){
  // Vector construction:
  vector<double> a{};  // An empty vector of element type double
  double sum;

  // Adding elements to the back end:
  for (vector<double>::size_type i = 1; i <= 5; i++)
    a.push_back(static_cast<double>(i));

  // Mutation of a EXISTNG ELEMENT.
  a.at(0) = 2.2;

  // Sum up the elements - with iterators - auto:
  sum = 0.0;
  for (auto iter = a.begin();
       iter != a.end();
       iter++){
    sum += *iter;
  }
  cout << "Sum = " << sum << endl;  // 16.2

  // Sum up the elements - with range for:
  sum = 0.0;
  for (auto el: a)
    sum += el;
  cout << "Sum = " << sum << endl;  // 16.2
}

Program: Another vector example - constructors, insertion, lexicographic comparison.
// Another vector example - constructors, iterators, insertion, lexicographic comparison.

#include <iostream>
#include <string>
#include <vector>

using std::string;
using std::vector;
using std::cout; using std::endl;

void print_vector(string name, vector<string> &sv);

int main(){
  // Vector constructing - C++11:
  vector<string>
     sv1{},              // An empty vector of strings
     sv2{10},            // A vector of 10 empty strings
     sv3{5, "AP"},       // A vector of 5 strings each "AP"
     sv4{4, "KN"},       // A vector of 4 strings each "KN"
     sv5{sv3.begin(), sv3.begin() + 3},
                         // A vector copied from front of sv3
     sv6{"1", "2", "3"}; // List initialization: Three strings.

  // Size and capacity:
  cout << "sv6.size(): "     << sv6.size()     << endl;  // 3
  cout << "sv6.capacity(): " << sv6.capacity() << endl;  // 3

  // Change every second element of sv3 to "PA":
  for (vector<string>::iterator iter = sv3.begin();
       iter < sv3.end();
       iter += 2){
    (*iter) = "PA";
  }  

  print_vector("sv3", sv3);

  // Insert 3 elements from sv4 near the end of sv3:
  sv3.insert(sv3.end()-1, sv4.begin(), sv4.begin()+3);

  print_vector("sv3", sv3);

  print_vector("sv4", sv4);   print_vector("sv5", sv5);

  // Lexicograhpic comparison between sw4 and sw5:
  if (sv4 == sv5)
    cout << "sv4 is equal to sv5" << endl;
  else if (sv4 < sv5)
    cout << "sv4 is less than sv5" << endl;
  else 
    cout << "sv4 is greater than sv5" << endl;
}

void print_vector(string name, vector<string> &sv){
   cout << name << ":" << endl;
   for (vector<string>::iterator iter = sv.begin();
       iter != sv.end();
       iter++)
     cout << "  " << (*iter) << endl;
   cout << endl;
}    

Program: Program output.
sv6.size(): 3
sv6.capacity(): 3
sv3:
  PA
  AP
  PA
  AP
  PA

sv3:
  PA
  AP
  PA
  AP
  KN
  KN
  KN
  PA

sv4:
  KN
  KN
  KN
  KN

sv5:
  AP
  AP
  AP

sv4 is greater than sv5

The free store
Slide Contents Index
References 

The C++ operators new and delete are used instead of the <cstdlib> functions malloc / calloc and free

Reference
  • The C++ Prog. Lang. (3. edition): Page 127

Reference
  • The C++ Prog. Lang. (4. edition): Page 277-285

  • new

    • Application: new T or new T[i], where T is a type

    • Returns a (raw) pointer to the new object

    • The object allocated by new lives until it is deallocated with delete

    • Use new T{} to establish a pointer to a default-initialized object

  • delete

    • Application: delete pT where pT is a pointer returned by new

    • A variant delete[] pT must be used if pT is a pointer to an array allocated by new

Program: Illustration of free store.
#include <iostream>
#include <iomanip>
#include <string>
#include <vector>

using namespace std;

int main(){

  // Allocate and delete an int on the free store:
  int *pi = new int{};           // Allocate space to an int, which is initialized to 0.
  *pi = *pi + 5;                 // *pi is 5 (because it was default initialized when created).
  cout << 6 * *pi << endl;       // 30
  delete pi;                     // Deallocate the int


  // Allocate and delete an array of ints on the free store:
  int *ia = new int[10];         // Allocate an array of 10 ints
  for(int i = 0; i < 10; i++)    // Initialize array
     ia[i] = 2 * i;              // ... by assigning to its elements.

  for(int i = 0; i < 10; i++)    
    cout  << setw(3) << ia[i];   // 0  2  4  6  8 10 12 14 16 18        setw is a stream manipulator.
  cout << endl;

  delete [] ia;                  // Deallocate the array


  // Allocate and delete a vector of ints on the free store:
  vector<int> *vd = 
      new vector<int>(10);       // Allocate a vector

  for(int i = 0; i < 5; i++)     // Mutate half of the vecctor  
    (*vd)[i] = 2 * i;

  for(int i = 0; i < 10; i++)    
    cout << setw(3) << (*vd)[i]; // 0  2  4  6  8  0  0  0  0  0

  cout << endl;

  delete vd;                     // Deallocate the vector

}

"A C++ implementation does not guarantee the prescence of a 'garbage collector'" [Stoustrup 4ed page 278]

Input and output in C++
Slide Contents Index
References 

File IO - including standard input and standard output - is supported via the libraries ostream and istream

Overloading of the operators >> ('get from') and << ('put to') is used for textual input and output respectively

Reference
  • The C++ Prog. Lang. (3. edition): Page 607, 613

  • Considerations about IO in C++

    • IO functions must apply uniformly to pre-defined types and user-defined types

      • The C-style printf and scanf are not easy to generalize to user-defined functions

      • In addition, printf and scanf are not type safe

    • Use of overloaded functions, such as put(data) and get(data)

      • repetitive and tedious in real-life situtions

      • s.put(d1); s.put(d2); s.put(d3);     or

      • s.put(d1).put(d2).put(d3);

    • Use of overloaded operators seems attractive

      • Shorter, and easier to spot

      • s << d1 << d2 << d3

Reference
  • The C++ Prog. Lang. (4. edition): formatted input with >>. Page 1082

Reference
  • The C++ Prog. Lang. (4. edition): output with <<. Page 1085

Overloaded operators and IO
Slide Contents Index
References 

Choosing existing operators for IO purposes

  • The assignment operator was considered

    • x = cin   and   cout = y

    • Its association to the right is inappropriate

  • The less than and greater than operators were considered

    • cin > x   and   cout < y

    • Unreadable, and confusing

  • The left shift and right shift operators were choosen

    • cin >> x   and   cout << y

    • The relative low priority of << is practical when values of expressions must be printed.

    • cout << x + y * z   and not   cout << (x + y * z)

Reference
  • The C++ Prog. Lang. (3. edition): Page 121-122: operator priorities

Reference
  • The C++ Prog. Lang. (4. edition): Page 255-257: operator priorities

Standard streams - and files
Slide Contents Index
References 

Like most other programming languages C++ supports a number of standard input and output streams

Reference
  • The C++ Prog. Lang. (3. edition): Page 609, 614

Reference
  • The C++ Prog. Lang. (4. edition): Page 91-95

Reference
  • The C++ Prog. Lang. (4. edition): Page 1075

  • cin

    • Standard character input - typically defaulted to a keyboard

  • cout

    • Standard character output - typically defaulted to a screen

  • cerr

    • Standard character error output - unbuffered

  • clog

    • Standard character error output - buffered

Program: Opening, working on, and closing a file - as simple as possible.
// Simple file program - fstream illustration - adapted from www.cplusplus.com

#include <fstream>      // std::fstream

int main () {

  std::fstream fs{"test.txt", std::fstream::out};

  // IO operations here
  fs << "Some text, and a number: " << 5 << std::endl;

  fs.close();

  return 0;
}

Stream State
Slide Contents Index
References 

Streams in C++ have state associated that represents error conditions and formatting details

Reference
  • The C++ Prog. Lang. (3. edition): Page 616

Reference
  • The C++ Prog. Lang. (4. edition): Page 1080

  • A stream can be in one of the following states - via function predicates from basis_ios

    • strm.good()

      • The previous IO operation succeeeded

    • strm.eof()

      • The user hits end-of-input (Ctrl-D)

    • strm.fail()

      • Unexpected failure - such as expecting a digit, getting a non-digit

    • strm.bad()

      • Serious problem - such as disk read error.

  • A stream can be used as a condition - in a boolean context

    • The value is true if the state of the stream is good()

Program: Illustration of implicit stream conversion to a boolean value - and stream states.
// Try it...

#include <iostream>
#include <string>
#include <limits>

using namespace std;

int main(){
  int i;
  while (true){
    cout << "Enter a number: ";
    cin >> i;                                             // cin is converted to 'a boolean value'
                                                          // via an implicit conversion operator - while cin is in a good state
    cout << "We got: " << i << " " << endl;
  
    if (cin.good())
      cout << "cin is good" << endl;
    else if (cin.eof())
      cout << "cin is at EOF" << endl;                    // when typing Cntrl D
    else if (cin.fail())                   
      cout << "cin has failed" << endl;                   // when typing a non-digit
    else if (cin.bad())
      cout << "cin is in really bad shape" << endl;

    cin.clear();
    cin.ignore(numeric_limits<streamsize>::max(), '\n');  // flush input
  }  
}

Manipulators
Slide Contents Index
References 

A manipulator is a distinguished operand accepted by the << and >> IO operators

A manipulator mutates the state of a stream

Reference
  • The C++ Prog. Lang. (3. edition): Page 631

Reference
  • The C++ Prog. Lang. (4. edition): Page 1087

Program: Output manipulators.
  cout << 17 << endl << showbase 
       <<  hex << 17 << endl
       << oct << 17 << endl;

Reference
  • The C++ Prog. Lang. (4. edition): Standard Minipulators. Page 1094-1095

Program: The fragment in a complete program.
#include <iostream>
#include <iomanip>
#include <string>

using namespace std;

int main(){

  cout << 17 << endl << showbase 
       <<  hex << 17 << endl
       << oct << 17 << endl;   
}

Program: Program output.
17
0x11
021

Program: A variant of the program above - without showbase.
#include <iostream>
#include <iomanip>
#include <string>

using namespace std;

int main(){

  cout << 17 << endl                  // without showbase
       <<  hex << 17 << endl
       << oct << 17 << endl;   
}

Program: Program output from the variant.
17
11
21

More manipulators
Slide Contents Index
References 

Manipulators can be with or without arguments

  • Manipulators without arguments

    • The manipulator is a pointer to function

    • Handled by an operator overload of   operator<<   on basic_ostream<C,Tr>

      • See example below

  • Manipulators with arguments

    • The manipulator m(a) returns an struct that aggregates a function pointer and the argument a

    • An overload of << activates the function on the argument before it returns the stream

    • Interesting details on page 1087 in The C++ Prog. Lang. (4. edition)

      • Illustrated below in an example

Program: Illustration of more manipulators.
#include <iostream>
#include <iomanip>
#include <string>

using namespace std;

int main(){
  double d = 3.12345678;

  cout << d << endl 
       << scientific << d << endl 
       << fixed
       << setprecision(10) << setfill('#') << setw(18)
       << d << endl;
}

Program: Program output.
3.12346
3.123457e+00
######3.1234567800

Program: Programming our own manipulator - with two parameters.
// Programming a stream manipulator with two functions:

#include <iostream>
#include <string>

using namespace std;

// A struct that encapsulates the manipulator constituents: an appropriate function pointer, a counter, and a string:
struct stream_manipulator {
  basic_ostream<char>& (*f)(basic_ostream<char>&, int, string);  // a function pointer
  int counter;                         
  string str;

  // A stream_manipulator constructor:
  stream_manipulator(basic_ostream<char>&(*ff)(basic_ostream<char>&, int, string),
         int i,
         string ss): f{ff}, counter{i}, str{ss} {
  }
};

// Overloading operator<< on basic_ostream<C,Tr> os and stream_manipulator m.
// Call m.f on the stream os, the counter in m, and the string in m:
template<typename C, typename Tr>
basic_ostream<C,Tr>& operator<<(basic_ostream<C,Tr>& os, const stream_manipulator& m){
  m.f(os, m.counter, m.str);   // call the lambda expression located in kn_endl.
  return os;                   // for chaining purposes.
}

inline stream_manipulator kn_endl(int n, string suffix){
  auto h = [](basic_ostream<char>& s, int n, string str) -> basic_ostream<char>& {
               for(int i = 0; i < n; i++) s << str << endl;
               return s;
           };
  return stream_manipulator(h, n, suffix);
}

int main(){
  int a_number{5};

  cout << "A test:" << kn_endl(a_number, "KN") 
       << a_number  << kn_endl(3, "OK") << "The end" << endl;
}

Program: Program output.
A test:KN
KN
KN
KN
KN
5OK
OK
OK
The end

Exercise 3.5. Understand the way we program and use output stream manipulators

Study the program on the accompanying slide in which we program our own stream manipulator.

First, read through the program (do it together with fellow students) and understand what happens.

Next, use the pattern to write a couple of new stream manipulators. If possible, make some which are more useful than the one I wrote. Maybe manipulators that affect the state of the stream, instead putting characters to the stream.

I recommend that you take a look at page 1087 in The C++ Prog. Lang. (4. edition).

Logical program organization
Slide Contents Index
References 
Intern kommentar til forelæsningen om denne slide:
Programmet på denne side illustrerer rigtig fint hvad forskellen er på using directives and declarations. Gennemgå den blot detaljeret - navn for navn. Derved bliver forskellen mellem de to gjort klar.

Namespaces are used for the logical program organization of C++ programs

Reference
  • The C++ Prog. Lang. (3. edition): Page 167, 847

Reference
  • The C++ Prog. Lang. (4. edition): Page 54, 391

  • Namespaces

    • A namespace is a named scope

    • Use of names requires namespace qualification, with use of the scope resolution operator ::

      • namespace::name_within_namespace

    • A using declaration can be used to add a name (an alias) to a local scope (such as a function) from a namespace

      • using N::name;

      • name is declared/defined as a local synonym to N::name

    • A using directive allows convenient access to names from a given namespace

      • using namespace N;

      • Can - and should - be used locally, in a function for instance

Reference

Program: Illustration of using declarations and using directives - by example.
// Reproduced from page 847 of 'The C++ Programming Language' (3ed).
// Shows 'using directives' and 'using declarations' by examples.

#include <iostream>
#include <string>

namespace X {
  int i = 10, j = 11, k = 12;
}

int k;                // Global variable

void f1(){            // Illustrates using directives
  int i = 0;
  using namespace X;  // A using directive makes i, j, and k from X accessible
  i++;                // local i
  j++;                // X::j
  k++;                // X::k or ::k   The name k is ambiguous
  ::k++;              // global k
  X::k++;             // X::k
}

void f2(){            // Illustrates using declarations
  int i = 0;
  using X::i;         // double definition: i already defined in this scope
  using X::j;       
  using X::k;         // hides global k

  i++;                // local i
  j++;                // X::j
  k++;                // X::k
}

int main(){
  f1();
  f2();
}

Program: Compilable variant of the program from above.
// Reproduced from page 847 of 'The C++ Programming Language' (3ed).
// Shows 'using directives' and 'using declarations' by examples.  This variant is compilable.

#include <iostream>
#include <string>

namespace X {
  int i = 10, j = 11, k = 12;
}

int k;                // Global variable

void f1(){            // Illustrates using directives
  int i = 0;
  using namespace X;  // A using directive makes i, j, and k from X accessible
  i++;                // local i
  j++;                // X::j

  ::k++;              // global k
  X::k++;             // X::k
}

void f2(){            // Illustrates using declarations
  int i = 0;

  using X::j;       
  using X::k;         // hides global k

  i++;                // local i
  j++;                // X::j
  k++;                // X::k
}

int main(){
  f1();
  f2();
}

Names declared in a namespace - natively or by using declarations - take priority over names brought in via using directives

More namespaces
Slide Contents Index
References 

Other namespace characteristics

  • A namespace is open

    • A single namespace N can span several source files

    • For instance namespace N {...} forms in two or more header files

  • Namespaces can be nested

  • There is a global namespace

    • Access to name in the global namespace:     ::name

  • Unnamed namespaces

    • Used in C++ to make names local to a compilation unit (internal linkage, next slide)

Reference
  • The C++ Prog. Lang. (3. edition): Unnamed namespaces. Page 177, 200

Reference
  • The C++ Prog. Lang. (4. edition): Unnamed namespaces. Page 415

Program: Illustrating that a namespace interface is separated from the namespace implementation.
// We see a namespace interface followed
// by definition/implementation of the functions
// in the namespace.

namespace N{          // The namespace interface
  int f(int i);
  double g();
}

int N::f(int i){      // The namespace implementation
  return 2*i;
}

double N::g(){
  return f(2);
}


int main(){
  using N::f; 

  int r = f(5);
  double d = N::g();
}

The syntactic namespace form is intended for declarations, not full definitions

This separates the namespace interface from the namespace implementation

The same holds for classes and structs

Namespace resolution
Slide Contents Index
References 

All facilities in the C++ Standard Library are defined in the std namespace

  • Use the scope operator :: on every name you use from the standard name space

    • std::cout << s1 << std::endl;

    • Allows for symmetric use of the same name from another namespace

    • Tedious in simple programs

  • Apply using declarations on selected names from the standard namespace

    • using std::cout; using std::endl;

    • Tedious, just in another way...

  • Apply a single using directive

    • using namespace std;

    • All names from the standard namespace can be used without qualification - not a good solution at all

    • OK for namespace composition - OK in certain local scopes

    • Sloppy and lazy if used in the global namespace

Reference
  • The C++ Prog. Lang. (3. edition): Page 179: Namespace composition

Physical program organization
Slide Contents Index
References 

The physical program organization is done by means of source files

Reference
  • The C++ Prog. Lang. (3. edition): Page 197ff.

Reference
  • The C++ Prog. Lang. (4. edition): Page 419ff.

  • Source file organization of C++ programs

    • Source files with #include directives

    • A translation units is the result of preprocessing a source file

    • .h files and .cpp or .cc files

      • The .cpp or .cc file includes its .h file.

    • Separate compilation - and subsequent linking

    • External linkage:

      • A name that can be used in other translation units than the one where it is defined

    • Internal linkage

      • A name that only can be used in the translation unit where it is defined

      • Declared as static in C programs and older C++ programs

      • Use an unnamed namespace for internal linkage in C++ programs

Reference
  • The C++ Prog. Lang. (3. edition): External and internal linkage. Page 199

Reference
  • The C++ Prog. Lang. (4. edition): Linkage. Page 420

Example of program organization
Slide Contents Index
References 

Two header files point.h and tripple.h

Class Point and Tripple depends mutually on each other

Two cpp files point.cpp and tripple.cpp

A main program file main.cpp

Program: The header file point.h.
#ifndef POINT_H
#define POINT_H

#include "tripple.h"

class Tripple;

class Point {
    private: 
        double x, y;

    public:
        Point();                     
        Point(double d);             
        Point(double d, double e);   
        Point(Point& p);             
        Point(Tripple t);

        operator double() const;     

        double getx () const;
        double gety () const;

        friend std::ostream& operator<<(std::ostream&, const Point&);
};

std::ostream& operator<<(std::ostream&, const Point&);

#endif // POINT_H

Program: The header file tripple.h.
#ifndef TRIPPLE_H
#define TRIPPLE_H

#include "point.h"

class Point;

class Tripple {
    private:
        int x, y, z;

    public:
        Tripple();                   
        Tripple(int xi, int yi, int zi); 
        Tripple(Point p);             

        int getx() const;
        int gety() const;
        int getz() const;

        friend std::ostream& operator<<(std::ostream&, const Tripple&);
};

std::ostream& operator<<(std::ostream&, const Tripple&);

#endif // TRIPPLE_H

Program: The header file point.cpp.
#include <cmath>
#include <iostream>
#include "point.h"

Point::Point(): x(0.0), y(7.0){
}

Point::Point(double x_coord): x(x_coord), y(20.0){
}

Point::Point(double x_coord, double y_coord): x(x_coord), y(y_coord){
}

Point::Point(Point& p): x(p.x + 1.0), y(p.y + 2.0){
}

Point::Point(Tripple t): x(double(t.getx())), y(double(t.gety())) {
}

double Point::getx () const{
    return x;
}
double Point::gety () const{
    return y;
}

std::ostream& operator<<(std::ostream& s, const Point& p){
    return s << "(" << p.x << "," << p.y << ")" ;
}

Program: The header file tripple.cpp.
#include <cmath>
#include <iostream>
#include "tripple.h"


Tripple::Tripple(): x(0), y(0), z(0){
}

Tripple::Tripple(int xi, int yi, int zi): x(xi), y(yi), z(zi){
}

Tripple::Tripple(Point p): x(int(p.getx())), y(int(p.gety())), z(0){
}

int Tripple::getx() const {
    return x;
}

int Tripple::gety() const {
    return y;
}

int Tripple::getz() const {
    return z;
}

std::ostream& operator<<(std::ostream& s, const Tripple& tr){
  return s << "[" << tr.x << "," << tr.y << "," << tr.z << "]";
}

Program: The header file main.cpp.
#include <iostream>

#include "point.h"
#include "tripple.h"

int main() {
    using namespace std;

    Point p, q(3,4), r(Tripple(1,2,3));
    Tripple t, v(p);

    cout << "p: "<< p << endl;   // funny default constructor: (0,7)
    cout << "q: "<< q << endl;   // (3,4)
    cout << "r: "<< r << endl;   // (1,2)

    cout << "t: "<< t << endl;   // [0,0,0]
    cout << "v: "<< v << endl;   // [1,9,0]
    
    return 0;
}

Program: Compilation - README.
Compilation:
  g++ -c point.cpp
  g++ -c tripple.cpp
  g++ point.o tripple.o main.cpp

I have added 'Include Guards' in the two header files.

point.h includes tripple.h
tripple.h includes point.h
class Point is forward declared in tripple.h
class Tripple is forward declared in point.h

A cpp file includes its header file - should always happen.
  Thus: point.cpp includes point.h
        tripple.cpp includes tripple.h

main.cpp includes both point.h and tripple.h

This is a nice and symmetric solution - as I see it.

Example of program organization - with namespace
Slide Contents Index
References 

The same as on the previous slide, but with Point and Tripple in namespece Geometrical

Program: The header file point.h.
#ifndef POINT_H
#define POINT_H

#include "tripple.h"

namespace Geometrical{ 

  class Tripple;

  class Point {
      private: 
          double x, y;
  
      public:
          Point();                     
          Point(double d);             
          Point(double d, double e);   
          Point(Point& p);             
          Point(Tripple t);
  
          operator double() const;     
  
          double getx () const;
          double gety () const;

          friend std::ostream& operator<<(std::ostream&, const Point&);
  };
  
  std::ostream& operator<<(std::ostream&, const Point&);

}

#endif // POINT_H

Program: The header file tripple.h.
#ifndef TRIPPLE_H
#define TRIPPLE_H

#include "point.h"

namespace Geometrical{ 

  class Point;
  
  class Tripple {
      private:
          int x, y, z;
  
      public:
          Tripple();                   
          Tripple(int xi, int yi, int zi); 
          Tripple(Point p);             
  
          int getx() const;
          int gety() const;
          int getz() const;

      friend std::ostream& operator<<(std::ostream&, const Tripple&);
  };
  
  std::ostream& operator<<(std::ostream&, const Tripple&);
}

#endif // TRIPPLE_H

Program: The header file point.cpp.
#include <cmath>
#include <iostream>
#include "point.h"

Geometrical::Point::Point(): x(0.0), y(7.0){
}

Geometrical::Point::Point(double x_coord): x(x_coord), y(20.0){
}

Geometrical::Point::Point(double x_coord, double y_coord): x(x_coord), y(y_coord){
}

Geometrical::Point::Point(Point& p): x(p.x + 1.0), y(p.y + 2.0){
}

Geometrical::Point::Point(Geometrical::Tripple t): x(double(t.getx())), y(double(t.gety())) {
}

double Geometrical::Point::getx () const{
    return x;
}
double Geometrical::Point::gety () const{
    return y;
}

std::ostream& Geometrical::operator<<(std::ostream& s, const Geometrical::Point& p){
    return s << "(" << p.x << "," << p.y << ")" ;
}

Program: The header file tripple.cpp.
#include <cmath>
#include <iostream>
#include "tripple.h"


Geometrical::Tripple::Tripple(): x(0), y(0), z(0){
}

Geometrical::Tripple::Tripple(int xi, int yi, int zi): x(xi), y(yi), z(zi){
}

Geometrical::Tripple::Tripple(Point p): x(int(p.getx())), y(int(p.gety())), z(0){
}

int Geometrical::Tripple::getx() const {
    return x;
}

int Geometrical::Tripple::gety() const {
    return y;
}

int Geometrical::Tripple::getz() const {
    return z;
}

std::ostream& Geometrical::operator<<(std::ostream& s, const Geometrical::Tripple& tr){
  return s << "[" << tr.getx() << "," << tr.gety() << "," << tr.getz() << "]";
}

Program: The header file main.cpp.
#include <iostream>

#include "point.h"
#include "tripple.h"

int main() {
    using namespace std;
    using namespace Geometrical;

    Point p, q(3,4), r(Tripple(1,2,3));
    Tripple t, v(p);

    cout << "p: " << p << endl;   // funny default constructor: (0,7)
    cout << "q: " << q << endl;   // (3,4)
    cout << "r: " << r << endl;   // (1,2)

    cout << "t: " << t << endl;   // [0,0,0]
    cout << "v: " << v << endl;   // [1,9,0]
    
    return 0;
}


Collected references
Contents Index
Basic facilities in the previous lecture
The C++ Prog. Lang. (4. edition): Page 290-298
Lambda expressions in C#
The C++ Prog. Lang. (3. edition): Page 130-131, 408, 819
The C++ Prog. Lang. (4. edition): Page 298-303, 643-646
Stackoverflow: About casting
The C++ Prog. Lang. (3. edition): Page 833
The C++ Prog. Lang. (3. edition): Page 130-131, 407-408 (dynamic casts), 819
The C++ Prog. Lang. (4. edition): Page 298-303
Similar C# stuff
as in C#
The C++ Prog. Lang. (3. edition): Page 149-153
The C++ Prog. Lang. (4. edition): Page 326-330
The C++ Prog. Lang. (3. edition): Page 149
The C++ Prog. Lang. (4. edition): Page 327
The C++ Prog. Lang. (3. edition): Page 90
The C++ Prog. Lang. (4. edition): String literals. Page 176
The C++ Prog. Lang. (3. edition): Page 48-49, 579
The C++ Prog. Lang. (4. edition): Page 90-91, 1033
Strings in C#
The C++ Prog. Lang. (3. edition): Page 588
String dokumentation at cplusplus.com
The C++ Prog. Lang. (3. edition): Page 52, 442
The C++ Prog. Lang. (4. edition): Page 95-99
Vector dokumentation at cplusplus.com
Arrays and pointers
Iterators
Array examples
The C++ Prog. Lang. (3. edition): Page 127
The C++ Prog. Lang. (4. edition): Page 277-285
The C++ Prog. Lang. (3. edition): Page 607, 613
The C++ Prog. Lang. (4. edition): formatted input with >>. Page 1082
The C++ Prog. Lang. (4. edition): output with <<. Page 1085
The C++ Prog. Lang. (3. edition): Page 121-122: operator priorities
The C++ Prog. Lang. (4. edition): Page 255-257: operator priorities
The C++ Prog. Lang. (3. edition): Page 609, 614
The C++ Prog. Lang. (4. edition): Page 91-95
The C++ Prog. Lang. (4. edition): Page 1075
The C++ Prog. Lang. (3. edition): Page 616
The C++ Prog. Lang. (4. edition): Page 1080
The C++ Prog. Lang. (3. edition): Page 631
The C++ Prog. Lang. (4. edition): Page 1087
The C++ Prog. Lang. (4. edition): Standard Minipulators. Page 1094-1095
The C++ Prog. Lang. (3. edition): Page 167, 847
The C++ Prog. Lang. (4. edition): Page 54, 391
Classes and structs
The C++ Prog. Lang. (3. edition): Unnamed namespaces. Page 177, 200
The C++ Prog. Lang. (4. edition): Unnamed namespaces. Page 415
The C++ Prog. Lang. (3. edition): Page 179: Namespace composition
The C++ Prog. Lang. (3. edition): Page 197ff.
The C++ Prog. Lang. (4. edition): Page 419ff.
The C++ Prog. Lang. (3. edition): External and internal linkage. Page 199
The C++ Prog. Lang. (4. edition): Linkage. Page 420

 

Chapter 3: Basic facilities, Part 2
Course home     Author home     About producing this web     Previous lecture (top)     Next lecture (top)     Previous lecture (bund)     Next lecture (bund)     
Generated: August 1, 2017, 13:26:42