C++ Primer, Part I: The Basics

C++ Primer, Fifth Edition

Stanley B. Lippman
Josée Lajoie
Barbara E. Moo


Chapter 1. Getting Started

This chapter introduces most of the basic elements of C++: types, variables, expressions, statements, and functions.

1.1 Writing a Simple C++ Program

1.2 A First Look at Input/Output

C++ includes an extensive standard library that provides IO.
Most of the examples in this book use the iostream library. Fundamental to the iostream library are two types named istream and ostream, which represent input and output streams, respectively.
The library defines four IO objects:
  • cin
  • standard input.
  • cout
  • standard output.
  • cerr
  • standard error
  • clog
All the names defined by the standard library are in the std namespace.

// Variable created inside namespace 
namespace first 
{ 
    int val = 500; 
} 
  
// Global variable 
int val = 100; 
  
int main() 
{ 
    // Local variable 
    int val = 200; 
  
    // These variables can be accessed from 
    // outside the namespace using the scope 
    // operator :: 
    std::cout << first::val << '\n';  
  
    return 0; 
} 

The prefix std:: indicates that the names cout and endl are defined inside the namespace named std. Both the input and the output operators return its left-hand operand as its result, the result of the first operator becomes the left-hand operand of the second.

(std::cout << "Enter two numbers:") << std::endl;

1.3 A Word about Comments

1.4 Flow of Control

1.5 Introducing Classes

To use a class we need to know three things:
  • What is its name?
  • Where is it defined?
  • What operations does it support?

1.5.1. The Sales_item Class

Sales_Item.h:


#ifndef SALESITEM_H
// we're here only if SALESITEM_H has not yet been defined 
#define SALESITEM_H

// Definition of Sales_item class and related functions goes here
#include <iostream>
#include <string>

class Sales_item {
// these declarations are explained section 7.2.1, p. 270 
// and in chapter 14, pages 557, 558, 561
friend std::istream& operator>>(std::istream&, Sales_item&);
friend std::ostream& operator<<(std::ostream&, const Sales_item&);
friend bool operator<(const Sales_item&, const Sales_item&);
friend bool 
operator==(const Sales_item&, const Sales_item&);
public:
    // constructors are explained in section 7.1.4, pages 262 - 265
    // default constructor needed to initialize members of built-in type
    Sales_item() = default;
    Sales_item(const std::string &book): bookNo(book) { }
    Sales_item(std::istream &is) { is >> *this; }
public:
    // operations on Sales_item objects
    // member binary operator: left-hand operand bound to implicit this pointer
    Sales_item& operator+=(const Sales_item&);
    
    // operations on Sales_item objects
    std::string isbn() const { return bookNo; }
    double avg_price() const;
// private members as before
private:
    std::string bookNo;      // implicitly initialized to the empty string
    unsigned units_sold = 0; // explicitly initialized
    double revenue = 0.0;
};

// used in chapter 10
inline
bool compareIsbn(const Sales_item &lhs, const Sales_item &rhs) 
{ return lhs.isbn() == rhs.isbn(); }

// nonmember binary operator: must declare a parameter for each operand
Sales_item operator+(const Sales_item&, const Sales_item&);

inline bool 
operator==(const Sales_item &lhs, const Sales_item &rhs)
{
    // must be made a friend of Sales_item
    return lhs.units_sold == rhs.units_sold &&
           lhs.revenue == rhs.revenue &&
           lhs.isbn() == rhs.isbn();
}

inline bool 
operator!=(const Sales_item &lhs, const Sales_item &rhs)
{
    return !(lhs == rhs); // != defined in terms of operator==
}

// assumes that both objects refer to the same ISBN
Sales_item& Sales_item::operator+=(const Sales_item& rhs) 
{
    units_sold += rhs.units_sold; 
    revenue += rhs.revenue; 
    return *this;
}

// assumes that both objects refer to the same ISBN
Sales_item 
operator+(const Sales_item& lhs, const Sales_item& rhs) 
{
    Sales_item ret(lhs);  // copy (|lhs|) into a local object that we'll return
    ret += rhs;           // add in the contents of (|rhs|) 
    return ret;           // return (|ret|) by value
}

std::istream& 
operator>>(std::istream& in, Sales_item& s)
{
    double price;
    in >> s.bookNo >> s.units_sold >> price;
    // check that the inputs succeeded
    if (in)
        s.revenue = s.units_sold * price;
    else 
        s = Sales_item();  // input failed: reset object to default state
    return in;
}

std::ostream& 
operator<<(std::ostream& out, const Sales_item& s)
{
    out << s.isbn() << " " << s.units_sold << " "
        << s.revenue << " " << s.avg_price();
    return out;
}

double Sales_item::avg_price() const
{
    if (units_sold) 
        return revenue/units_sold; 
    else 
        return 0;
}
#endif
  • Reading and Writing Sales_item s
  • 
    #include <iostream>
    #include "Sales_item.h"
    int main()
    {
        Sales_item book;
        // read ISBN, number of copies sold, and sales price
        std::cin >> book;
        // write ISBN, number of copies sold, total revenue, and average price
        std::cout << book << std::endl;
        return 0;
    }  
      
  • Adding Sales_item s
  • 
    #include <iostream>
    #include "Sales_item.h"
    int main()
    {
        Sales_item item1, item2;
        std::cin >> item1 >> item2;   // read a pair of transactions
        std::cout << item1 + item2 << std::endl; // print their sum
        return 0;
    }  
      

1.6 The Bookstore Program

Assume the input is a series of salee format string and the string for the same ISBN are grouped together.

#include <iostream>
#include "Sales_item.h"

int main()
{
    Sales_item total; // variable to hold data for the next transaction
    // read the first transaction and ensure that there are data to process
    if (std::cin >> total) {
        Sales_item trans; // variable to hold the running sum
        // read and process the remaining transactions
        while (std::cin >> trans) {
            // if we're still processing the same book
            if (total.isbn() == trans.isbn())
                total += trans; // update the running total
            else {
                // print results for the previous book
                std::cout << total << std::endl;
                total = trans;  // total now refers to the next book
            }
        }
        std::cout << total << std::endl; // print the last transaction
    } else {
        // no input! warn the user
        std::cerr << "No data?!" << std::endl;
        return -1;  // indicate failure
    }
    return 0;
}
  

Chapter 2. Variables and Basic Types

2.1. Primitive Built-in Types

2.1.1. Arithmetic Types

There are several character types, most of which exist to support internationalization-- wchar_t, char16_t, and char32_t.

2.1.2. Type Conversions

2.1.3. Literals

2.2. Variables

A variable provides us with named storage. The type determines the size and layout of the variable’s memory, the range of values that can be stored within that memory, and the set of operations that can be applied to the variable.

2.2.1. Variable Definitions

2.2.2. Variable Declarations and Definitions

2.2.3. Identifiers

2.2.4. Scope of a Name

Most scopes of names in C++ are delimited by curly braces {}, block scope.

2.3. Compound Types

A compound type is a type that is defined in terms of another type.

2.3.1. References

A reference defines an alternative name for an object

A reference type “refers to” another type.
A reference is not an object. Instead, a reference is just another name for an already existing object.
References must be initialized when it is defined and the initializer must be an object.
After a reference has been defined, all operations on that reference are actually operations on the object to which the reference is bound.
Because references are not objects, we may not define a reference to a reference.
When a reference is defined, identifier that is a reference must be preceded by the & symbol:


int i = 1024, i2 = 2048;  // i and i2 are both int
int &r = i, r2 = i2;  // r is a reference bound to i; r2 is an int

2.3.2. Pointers

A pointer holds the address of another object. We get the address of an object by usin the address-of operator (the & operator)

int ival = 42;
int *p = &ival; // p holds the address of ival; p is a pointer to ival

Because references are not objects, references don’t have addresses. Hence, we may not define a pointer to a reference.
The types of the pointer and the object to which it points must match because the type of the pointer is used to infer the type of the object to which the pointer points.
When a pointer points to an object, we can use the dereference operator (the * operator) to access that object. Older programs sometimes use a preprocessor variable named NULL, which the cstdlib header defines as 0. The literal nullptr was introduced by the new standard.
The type void* is a special pointer type that can hold the address of any object.

2.4. const Qualifier

Sometimes, we’d also like to prevent code from inadvertently giving a new value to a variable.
We can make a variable unchangeable by defining the variable’s type as const:

const int bufSize = 512;
Any attempt to assign a value to a const variable is an error.
Because we can’t change the value of a const object after we create it, it must be initialized.
By Default, const objects are local to a file.
To share a const object among multiple files, you must define the variable as extern.

// file_1.cc defines and initializes a const that is accessible to other files
extern const int bufSize = fcn();
// file_1.h
extern const int bufSize; // same bufSize as defined in file_1.cc
The extern keyword has four meanings depending on the context:
  • extern linkage for non-const globals
  • When the linker sees extern before a global variable declaration, it looks for the definition in another translation unit. Declarations of non-const variables at global scope are external by default. Only apply extern to the declarations that don't provide the definition.
    
    //fileA.cpp
    int i = 42; // declaration and definition
    //fileB.cpp
    extern int i;  // declaration only. same as i in FileA
    //fileC.cpp
    extern int i;  // declaration only. same as i in FileA
    //fileD.cpp
    int i = 43; // LNK2005! 'i' already has a definition.
    extern int i = 43; // same error (extern is ignored on definitions)  
      
  • extern linkage for const globals
  • If you want the variable to have external linkage, apply the extern keyword to the definition, and to all other declarations in other files.
    
    //fileA.cpp
    extern const int i = 42; // extern const definition
    //fileB.cpp
    extern const int i;  // declaration only. same as i in FileA
       
  • extern constexpr linkage
  • extern "C" and extern "C++" function declarations
  • When used with a string, extern specifies that the linkage conventions of another language are being used for the declarator(s).
    
    // Declare printf with C linkage.
    extern "C" int printf(const char *fmt, ...);
    
    //  Cause everything in the specified
    //  header files to have C linkage.
    extern "C" {
        // add your #include statements here
     ...
    }
    
    //  Declare the two functions ShowChar
    //  and GetChar with C linkage.
    extern "C" {
        char ShowChar(char ch);
        char GetChar(void);
    }
    
    //  Define the two functions
    //  ShowChar and GetChar with C linkage.
    extern "C" char ShowChar(char ch) {
        putchar(ch);
        return ch;
    }
    
    extern "C" char GetChar(void) {
        char ch;
        ch = getchar();
        return ch;
    }
    
    // Declare a global variable, errno, with C linkage.
    extern "C" int errno;  
      

2.4.1. References to const

A reference that refers to a const type is called as “const reference” sometimes.
Unlike an ordinary reference, a reference to const cannot be used to change the object to which the reference is bound.

const int ci = 1024;
const int &r1 = ci;   // ok: both reference and underlying object are const
r1 = 42;              // error: r1 is a reference to const
int &r2 = ci;         // error: non const reference to a const object
It is important to realize that a reference to const restricts only what we can do through that reference, it doesn't care if the bound type is a const or not.

2.4.2. Pointers and const

Like a reference to const, a pointer to const may not be used to change the object to which the pointer points.
The address of a const object can only be saved in a pointer to const.
Unlike references, pointers are objects(variables).
Like any other const object, a const pointer must be initialized, and once initialized, its value (i.e., the address that it holds) may not be changed.

int errNumb = 0;
int *const curErr = &errNumb;  // curErr will always point to errNumb
const double pi = 3.14159;
const double *const pip = π // pip is a const pointer to a const object

*pip = 2.72;     // error: pip is a pointer to const
*curErr = 0; // ok: reset the value of the object to which curErr is bound
  • curErr is a const pointer
  • pi is a const variable/object
  • pip is a const pointer to a const object

2.4.3. Top-Level const

We use the term top-level const to indicate that the variable/object itself is a const.
When a const variable/object can be changed, we refer to that const as a low-level const. The distinction between top-level and low-level matters when we copy an object:
  • When we copy an object, top-level consts are ignored
  • When we copy an object, low-level const is never ignored.
  • Both objects must have the same low-level const qualification or there must be a conversion between the types of the two objects.

int i = 0;
int *const p1 = &i;  // we can't change the value of p1; const is top-level
const int ci = 42;       // we cannot change ci; const is top-level
const int *p2 = &ci;     // we can change p2; const is low-level
const int *const p3 = p2; // right-most const is top-level, left-most is not
const int &r = ci;   // const in reference types is always low-level

int *p = p3; // error: p3 has a low-level const but p doesn't

2.4.4. constexpr and Constant Expressions

A constant expression is an expression whose value cannot change and that can be evaluated at compile time rather than run time. .
The idea is to spend time in compilation and save time at run time.
constexpr specifies that the value of an object or a function can be evaluated at compile time and the expression can be used in other constant expressions. Under the new standard(C++11), we can ask the compiler to verify if a variable is a constant expression by declaring the variable in a constexpr declaration.

constexpr int mf = 20;        // 20 is a constant expression
constexpr int limit = mf + 1; // mf + 1 is a constant expression
constexpr int sz = size();    // ok only if size is a constexpr function
// constexpr function for product of two numbers. 
// By specifying constexpr, we suggest compiler to  
// to evaluate value at compile time 
constexpr int product(int x, int y) 
{ 
    return (x * y); 
} 
  
int main() 
{ 
    const int x = product(10, 20); 
    cout << x; 
    return 0; 
} 

2.5. Dealing with Types

2.5.1. Type Aliases

We can define a type alias in one of two ways.
  • typedef
  • 
    typedef double wages;   // wages is a synonym for double
    typedef wages base, *p; // base is a synonym for double, p for double*  
    
  • using
  • The new standard(C++11) introduced using to define a type alias. The alias declaration defines the name on the left-hand side of the = as an alias for the type that appears on the right-hand side.
    
    using SI = Sales_item;  // SI is a synonym for Sales_item
      

2.5.2. The auto Type Specifier

Somtimes, it's hard to determine the type of an expression.
The new standard can let the compiler figure out the type for us by using the auto type specifier. auto tells the compiler to deduce the type from the initializer.

// the type of item is deduced from the type of the result of adding val1 and val2
auto item = val1 + val2; // item initialized to the result of val1 + val2
  

2.5.3. The decltype Type Specifier

The new standard(C++11) introduced a second type specifier, decltype(), which returns the type of its operand.

decltype(f()) sum = x; // sum has whatever type f returns
  

2.6. Defining Our Own Data Structures

In C++ we define our own data types by defining a class.
C++ has 2 keywords that can be used to define our own data structures:
  • struct
  • class

2.6.3. Writing Our Own Header Files

Typically, classes are stored in headers whose name derives from the name of the class.
Headers (usually) contain entities that can be defined only once in any given file.
The most common technique for making it safe to include a header multiple times relies on the preprocessor.
C++ programs also use the preprocessor to define header guards. Header guards rely on preprocessor variables.
  • The #define directive takes a name and defines that name as a preprocessor variable.
  • #ifdef is true if the variable has been defined, and #ifndef is true if the variable has not been defined.
Headers should have guards, even if they aren’t (yet) included by another header. Header guards are trivial to write, and by habitually defining them you don’t need to decide whether they are needed.

Chapter 3. Strings, Vectors, and Arrays

In addition to the built-in types defined directly by the C++ language, C++ defines a rich library of abstract data types.
The string and vector types defined by the library are abstractions of the more primitive built-in array type.

3.1. Namespace using Declarations

A namespace is a declarative region that provides a scope to the identifiers (the names of types, functions, variables, etc) inside it.
Namespaces are used to organize code into logical groups and to prevent name collisions.
  • All identifiers at namespace scope are visible to one another without qualification.
  • Identifiers outside the namespace can access the members
    • by using the fully qualified name for each identifier
    • 
          std::cin
              
    • by a using declaration for a single identifier
    • A using declaration lets us use a name from a namespace without qualifying the name with a namespace_name:: prefix.
      
           using std::cin
              
      A Separate using declaration is Required for each name.
    • by a using Directive for all the identifiers in the namespace
    •     
          using namespace std;
          
Code inside headers ordinarily should not use using declarations.

3.2. Library string Type

A string is a variable-length sequence of characters. The standard string class provides support for such objects. The string class is an instantiation of the basic_string class template. To use the string type, we must include the string header. Because it is part of the library, string is defined in the std namespace.

#include <string>
using std::string;
 

3.2.1. Defining and Initializing strings

The most common ways to initialize strings:
  
string s1;            // default initialization; s1 is the empty string
string s2 = s1;       // s2 is a copy of  s1
string s3 = "hiya";   // s3 is a copy of the string literal
string s4(10, 'c');   // s4 is cccccccccc
When we initialize a variable using =, we are asking the compiler to copy initialize the object by copying the initializer on the right-hand side into the object being created.

3.2.2. Operations on strings

  • Reading and Writing
  • Like the input and output operations on the built-in types, the string operators return their left-hand operand as their result.
      
    string s1, s2;
    cin >> s1 >> s2; // read first input into s1, second into s2
    cout << s1 << s2 << endl; // write both strings
    
  • Using std::getline() to Read an Entire Line
  • The getline() function reads characters from an input stream and places them into a string. This function reads the given stream up to and including the first newline and stores what it read— not including the newline—in its string argument. Like the input operator, getline returns its istream argument.
      
    getline(stream, line)
    
  • std::string::empty()
  • Checks if the string has no characters.
  • std::string::size()
  • The size member returns the length of a string in the type string::size_type. Any variable used to store the result from the string size operation should be of type string::size_type. string::size_type is an unsigned type big enough to hold the size of any string.
  • Comparing strings
  • The equality operators (== and !=) test whether two strings are equal or unequal, respectively.
  • Assignment for strings
  •    
    string st1(10, 'c'), st2; // st1 is cccccccccc; st2 is an empty string
    st1 = st2; // assignment: replace contents of st1 with a copy of st2
               // both st1 and st2 are now the empty string
    
  • Adding Two strings
  •    
    string s1  = "hello, ", s2 = "world\n";
    string s3 = s1 + s2;   // s3 is hello, world\n
    s1 += s2;   // equivalent to s1 = s1 + s2
    
  • Adding Literals and string s
  •  
    string s1 = "hello", s2 = "world"; // no punctuation in s1 or s2
    string s3 = s1 + ", " + s2 + '\n';
    
For historical reasons, and for compatibility with C, string literals are not standard library strings.

3.2.3. Dealing with the Characters in a string

Often we need to deal with the individual characters in a string. C++ library incorporates the C library:
  • Headers in C have names of the form name .h.
  • The C++ versions of these headers are named c name — they remove the .h suffix and precede the name with the letter c.
  • For ex., cctype has the same contents as ctype.h, but in a form that is appropriate for C++ programs.
Using the .h headers puts the burden on the programmer to remember which library names are inherited from C and which are unique to C++.
  • Processing Every Character? Use Range-Based for
  • C++11 introduced the range - for statement:
    
    for ( declaration : expression )
        statement
        
        
    where
    • expression
    • is an object of a type that represents a sequence
    • declaration
    • defines the loop variable that we’ll use to access the underlying elements in the sequence.
    On each iteration, the variable in declaration is set to the value of the next element in expression . For ex.,
    
    string str("some string");
    // print the characters in str one character to a line
    for (auto c : str)      // for every char in str
        cout << c << endl;  // print the current character followed by a newline    
        
    If we want to change the value of the characters in a string, we must define the loop variable as a reference type. The reference variable is bound to each element in the sequence in turn.
    
    string s("Hello World!!!");
    // convert s to uppercase
    for (auto &c : s)   // for every char in s (note: c is a reference)
        c = toupper(c); // c is a reference, so the assignment changes the char
    in s
    cout << s << endl;    
        
  • Processing Only Some Characters?
  • We can use a subscript or an iterator. The subscript operator (the [ ] operator) takes a string::size_type value that denotes the position of the character we want to access. The operator returns a reference to the character at the given position. The value in the subscript is referred to as “a subscript” or “an index.”

3.3. Library vector Type

A vector is a collection of objects, all of which have the same type. Every object in the collection has an associated index, which gives access to that object. A vector is often referred to as a container. To use a vector,

#include <vector>
using std::vector;
A vector is a class template, not a type. The process that the compiler uses to create classes or functions from templates is called instantiation. For a class template, we specify which class to instantiate by supplying additional information, the nature of which depends on the template. The class type is specified inside a pair of angle brackets following the template’s name. In the case of vector,

vector<int> ivec;             // ivec holds objects of type int
vector<Sales_item> Sales_vec; // holds Sales_items
vector<vector<string>> file;  // vector whose elements are vectors

3.3.1. Defining and Initializing vectors

  • default initialization
  • We can default initialize a vector, which creates an empty vector of the specified type:
    
    vector<string> svec; // default initialization; svec has no elements
    
    we can (efficiently) add elements to a empty vector at run time.
  • copy initialization
  • We can also supply initial value(s) for the element(s) when we define a vector.
    
    vector<string> ivec; // initially empty
    // give ivec some values
    vector<int> ivec2(ivec);      // copy elements of ivec into ivec2
    vector<int> ivec3 = ivec;     // copy elements of ivec into ivec3
    vector<string> svec(ivec2);   // error: svec holds strings, not ints
    
    
  • list initialization
  • C++11 can let you initialize a vector from a list of zero or more initial element values,
    
    vector<string> articles = {"a", "an", "the"};
    
  • Creating a Specified Number of Elements
  • Initialize a vector from a count and an element value,
    
    vector<int> ivec(10, -1);       // ten int elements, each initialized to -1
    vector<string> svec(10, "hi!"); // ten strings; each element is "hi!"  
      
  • Value Initialization
  • We can usually omit the value and supply only a size. The value of the element initializer depends on the type of the elements stored in the vector.
    
    vector<int> ivec(10);    // 10 elements, each initialized to 0
    vector<string> svec(10); // 10 elements, each an empty string
      
    There are two restrictions on this form of initialization:
    • some classes require that we always supply an explicit initializer
    • we cannot use the copy form of initialization.
  • List Initializer or Element Count?
  • 
    vector<int> v1(10);    // 10 elements with value 0
    vector<int> v2{10};    // 1 element with value 10
    vector<int> v3(10, 1); // 10 elements with value 1
    vector<int> v4{10, 1}; // 2 elements with values 10 and 1
    
    
  • Self-cleanup prevents memory leaks
  • When a vector variable goes out of scope, it automatically deallocates the memory it controls (if necessary). This is not only handy (as you don’t have to do it yourself), it also helps prevent memory leaks. Consider the following snippet:
    
    void doSomething(bool earlyExit)
    {
        int *array{ new int[5] { 9, 7, 5, 3, 1 } };
     
        if (earlyExit)
            return;
     
        // do stuff here
     
        delete[] array; // never called
    }
    
    If earlyExit is set to true, array will never be deallocated, and the memory will be leaked. However, if array is a std::vector, this won’t happen, because the memory will be deallocated as soon as array goes out of scope (regardless of whether the function exits early or not). This makes std::vector much safer to use than doing your own memory allocation.

3.3.2. Adding Elements to a vector

It is general to create an empty vector and use a vector member named push_back() to add elements at run time. The push_back() operation takes a value and “pushes” that value as a new last element onto the “back” of the vector.

vector<int> v2;        // empty vector
for (int i = 0; i != 100; ++i)
  v2.push_back(i);    // append sequential integers to v2
// at end of loop v2 has 100 elements, values 0 . . . 99

// read words from the standard input and store them as elements in a vector
string word;
vector<string> text;       // empty vector
while (cin >> word) {
    text.push_back(word);  // append word to text
}

3.3.3. Other vector Operations

We access the elements of a vector through their position in the vector.

vector<int> v{1,2,3,4,5,6,7,8,9};
for (auto &i : v)     // for each element in v (note: i is a reference)
    i *= i;           // square the element value
for (auto i : v)      // for each element in v
    cout << i << " "; // print the element
cout << endl;
To use size_type, we must name the type in which it is defined.

vector<int>::size_type // ok
vector::size_type      // error
We can fetch a given element using the subscript operator.

// count the number of grades by clusters of ten: 0--9, 10--19, . .. 90--99, 100
vector<unsigned> scores(11, 0); // 11 buckets, all initially 0
unsigned grade;
while (cin >> grade) {      // read the grades
    if (grade <= 100)       // handle only valid grades
        ++scores[grade/10]; // increment the counter for the current cluster
}

3.4. Introducing Iterators

We can use an iterator to fetch an element and iterators have operations to move from one element to another. An iterator is any object that, pointing to some element in a range of elements (such as an array or a container), has the ability to iterate through the elements of that range using a set of operators (with at least the increment (++) and dereference (*) operators).The most obvious form of an iterator is a pointer. A pointer can point to elements in an array and can iterate through them using the increment operator (++).
This is a quick summary of iterators in the Standard Template Library:
  • Iterator is a pointer-like object that can be incremented with ++, dereferenced with *, and compared against another iterator with !=.
  • Iterators are generated by STL container member functions, such as begin() and end().
  • Some containers return iterators that support only the above 2 operations, while others return iterators that can move forward and backward, be compared with <, and so on.
  • Iterators are divided into categories
  • Each category specifies the operations the iterator supports. For example, some iterators support incrementing but not decrementing.

3.4.1. Using Iterators

An iterator is best visualized as a pointer to a given element in the container.
Types that have iterators have members that return iterators. In particular, these types have members named begin and end. The begin member returns an iterator that denotes the first element (or first character), if there is one:

// the compiler determines the type of b and e
// b denotes the first element and e denotes one past the last element in v
auto b = v.begin(), e = v.end(); // b and e have the same type
The iterator returned by end is an iterator denoting a nonexistent element “off the end” of the container. It is used as a marker indicating when we have processed all the elements. If the container is empty, the iterators returned by begin and end are equal. Iterator operations,
  • compare: == or !=
  • Iterators are equal if they denote the same element or if they are both off-the-end iterators for the same container.
  • dereference: *
  • Dereferencing the iterator returns the element that the iterator is currently pointing at.
    
    string s("some string");
    if (s.begin() != s.end()) { // make sure s is not empty
        auto it = s.begin();    // it denotes the first character in s
        *it = toupper(*it);     // make that character uppercase
    }
       
  • move: ++
  • Move from one element to the next. Because the iterator returned from end does not denote an element, it may not be incremented or dereferenced.
    
    // process characters in s until we run out of characters or we hit a whitespace
    for (auto it = s.begin(); it != s.end() && !isspace(*it); ++it)
        *it = toupper(*it); // capitalize the current character
       
The library types having iterators define types:
  • iterator
  • An object of type iterator can both read and write.
  • const_iterator
  • A const_iterator behaves like a const pointer, may read but not write the element it denotes.

vector<int>::iterator it; // it can read and write vector<int> elements
string::iterator it2;     // it2 can read and write characters in a string
vector<int>::const_iterator it3; // it3 can read but not write elements
string::const_iterator it4;      // it4 can read but not write characters
The type of the iterator returned by begin() and end() depends on whether the object on which they operate is const. C++11 introduced two new functions named cbegin() and cend() which return the type const_iterator. For now, it is important to realize that loops that use iterators should not add elements to the container to which the iterators refer.

3.4.2. Iterator Arithmetic

Operations Supported by vector and string Iterators,
  • iter + n
  • iter -n
  • iter +=n
  • iter -= n
  • iter1 - iter2
  • Yield the number of elements between these 2 iterator
  • >, >=, <, <=
  • Compare the position of iterators: after, before.
We can do a binary search using iterators as follows:

// text must be sorted in order
// beg and end will denote the range we're searching
auto beg = text.begin(), end = text.end();
auto mid = text.begin() + (end - beg)/2; // original midpoint
// while there are still elements to look at and we haven't yet found sought
while (mid != end && *mid != sought) {
    if (sought < *mid)     // is the element we want in the first half?
        end = mid;         // if so, adjust the range to search the 1st half
    else                   // the element we want is in the second half
        beg = mid + 1;     // search the 2nd half
    mid = beg + (end - beg)/2;  // new midpoint
}

3.5. Arrays

An array is a container of unnamed objects of a single type that we access by position. Arrays have fixed size; we cannot extend an array. If you don’t know exactly how many elements you need, use a vector.

3.5.1. Defining and Initializing Built-in Arrays

An array declarator has the form a[d], where a is the name being defined and d is the dimension of the array.
Explicitly Initializing Array Elements,

const unsigned sz = 3;
int ia1[sz] = {0,1,2};        // array of three ints with values 0, 1, 2
int a2[] = {0, 1, 2};         // an array of dimension 3
int a3[5] = {0, 1, 2};        // equivalent to a3[] = {0, 1, 2, 0, 0}
string a4[3] = {"hi", "bye"}; // same as a4[] =  {"hi", "bye", ""}
int a5[2] = {0,1,2};          // error: too many initializers
Character arrays can be initialized from a string literal,

char a1[] = {'C', '+', '+'};       // list initialization, no null
char a2[] = {'C', '+', '+', '\0'}; // list initialization, explicit null
char a3[] = "C++";                 // null terminator added automatically
const char a4[6] = "Daniel";       // error: no space for the null!
We cannot initialize an array as a copy of another array, nor is it legal to assign one array to another. Because an array is an object, we can define both pointers and references to arrays. It can be easier to understand array declarations by starting with the array’s name and reading them from the inside out.

int *ptrs[10];            //  ptrs is an array of pointers
int &refs[10] = /* ? */;  //  error: no arrays of references
int (*Parray)[10] = &arr; //  Parray is a pointer to an array of 10 ints.
int (&arrRef)[10] = arr;  //  arrRef is a reference to an array of 10 ints
int *(&arry)[10] = ptrs; // arry is a reference to an array of ten pointers

3.5.2. Accessing the Elements of an Array

When we use a variable to subscript an array, we normally should define that variable to have type size_t. size_t is a machine-specific unsigned type that is guaranteed to be large enough to hold the size of any object in memory.

3.5.3. Pointers and Arrays


string nums[] = {"one", "two", "three"};  // array of strings
string *p = &nums[0];   // p points to the first element in nums
string *p2 = nums;      // equivalent to p2 = &nums[0]
A loop to print the elements in array,

int arr[] = {0,1,2,3,4,5,6,7,8,9};
int *e = &arr[10]; // pointer just past the last element in arr

for (int *b = arr; b != e; ++b)
    cout << *b << endl; // print the elements in arr
C++ includes two functions begin() and end() to get the pointers for the first element and the past-the-end element of an array. The past-the-end element is the theoretical element that would follow the last element in the array. It does not point to any element, and thus shall not be dereferenced.

int ia[] = {0,1,2,3,4,5,6,7,8,9}; // ia is an array of ten ints
int *beg = begin(ia); // pointer to the first element in ia
int *last = end(ia);  // pointer one past the last element in ia
auto n = end(ia) - begin(ia); // n is 10, the number of elements in ia

3.5.4. C-Style Character Strings

C-style character strings are null terminated: the last character in the string is followed by a null character ('\0'). The Standard C library provides a set of functions, that operate on C-style strings. The pointer(s) passed to these routines must point to null-terminated array(s) For most applications, in addition to being safer, it is also more efficient to use library strings rather than C-style strings.

3.5.5. Interfacing to Older Code

Programs written in modern C++ may have to interface to code that uses arrays and/or C-style character strings. There is a string member function named c_str() that we can often use to get a C-style string from a library string.

string s("Hello World");  // s holds Hello World
char *str = s; // error: can't initialize a char* from a string
const char *str = s.c_str(); // ok

3.6. Multidimensional Arrays

The multidimensional arrays are actually arrays of arrays.

int ia[3][4]; // array of size 3; each element is an array of ints of size 4
// array of size 10; each element is a 20-element array whose elements are arrays of 30 ints
int arr[10][20][30] = {0}; // initialize all elements to 0
we can initialize the elements of a multidimensional array by providing a bracketed list of initializers.

int ia[3][4] = {    //
    {0, 1, 2, 3},   // nitializers for the row indexed by 0
    {4, 5, 6, 7},   //
    {8, 9, 10, 11}  //
};

// equivalent initialization without the optional nested braces for each row
int ia[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};

// explicitly initialize only element 0 in each row
int ia[3][4] = {{ 0 }, { 4 }, { 8 }};

// explicitly initialize row 0; the remaining elements are value initialized
int ix[3][4] = {0, 3, 6, 9};
we can use a subscript to access the elements of a multidimensional array.

// assigns the first element of arr to the last element in the last row of ia
ia[2][3] = arr[0][0][0];

// row as a reference to an array of four ints. binds row to the 2nd four-element array in ia
int (&row)[4] = ia[1]; 
Using a Range for with Multidimensional Arrays,

constexpr size_t rowCnt = 3, colCnt = 4;
int ia[rowCnt][colCnt];   // 12 uninitialized elements

size_t cnt = 0;
for (auto &row : ia)        // for every element in the outer array
    for (auto &col : row) { // for every element in the inner array
        col = cnt;          // give this element the next value
        ++cnt;              // increment cnt
    }

for (size_t i = 0; i != rowCnt; ++i) { // for each row
    // for each column within the row
    for (size_t j = 0; j != colCnt; ++j) {
        cout << ia[i][j] << endl; // {0,1,2,... 11}
    }
}    
Note, to use a multidimensional array in a range for, the loop control variable for all but the innermost array must be references. when we use the name of a multidimensional array, it is automatically converted to a pointer to the first element in the array,

int *ip[4];    // ip is an array of 4 pointers to int
int (*ip)[4];  // ip is a pointer variable for an array of 4 ints
int ia[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};     // array of size 3; each element is an array of ints of size 4
int (*p)[4] = ia; // p points to an array of 4 ints
int *r;

for ( int i=0 ; i<3; i++ ){
    r = (int *) p[i];
    for ( int j=0; j<4; j++ ){
        cout << r[j] << endl;
    }
}
p = &ia[2];       // p now points to the last element in ia
r = (int *) p;
for ( int i=0; i<4; i++ ){
    cout << r[i] << endl;
}
avoid having to write the type of a pointer into an array by using auto or decltype,

// print the value of each element in ia, with each inner array on its own line
// p points to an array of 4 ints
for (auto p = ia; p != ia + 3; ++p) {
    // q is a pointer pointed to the first element of an array of 4 ints; 
    for (auto q = *p; q != *p + 4; ++q)
         cout << *q << ' ';
    cout << endl;
}

// p points to the first array in ia
for (auto p = begin(ia); p != end(ia); ++p) {
   // q is a pointer pointed to the first element of an array of 4 ints;
   for (auto q = begin(*p); q != end(*p); ++q)
           cout << *q << ' ';   // prints the int value to which q points
   cout << endl;
}

Type Aliases Simplify Pointers to Multidimensional Arrays,

// to define a type alias int_array
using int_array = int[4]; // new style type alias declaration; 
typedef int int_array[4]; // equivalent typedef declaration;
// print the value of each element in ia, with each inner array on its own line
for (int_array *p = ia; p != ia + 3; ++p) {
    for (int *q = *p; q != *p + 4; ++q)
         cout << *q << ' ';
    cout << endl;
}

Chapter 4. Expressions

An expression is composed of at least one operands and possibly one or more operators.

4.1. Fundamentals

How expressions are evaluated.?

4.1.1. Basic Concepts

The context in which a symbol is used determines whether the symbol represents a unary or binary operator. The lvalue and rvalue are inherited from C and originally had a simple mnemonic purpose: lvalue could stand on the left-hand side of an assignment whereas rvalues could not. An lvalue has an address that your program can access. Roughly speaking,
  • when we use an object as an rvalue, we use the object’s value (its contents).
  • when we use an object as an lvalue, we use the object’s identity (its location in memory).
  • When we use an lvalue in place of an rvalue, the object’s contents (its value) are used.
  • we cannot use an rvalue when an lvalue (i.e., a location) is required.

4.1.2. Precedence and Associativity

4.1.3. Order of Evaluation

4.2. Arithmetic Operators

4.3. Logical and Relational Operators

4.4. Assignment Operators

Unlike the other binary operators, assignment is right associative:

int ival, jval;
ival = jval = 0; // ok: each assigned 0

4.5. Increment and Decrement Operators

These operators require lvalue operands.

int i = 0, j;
j = ++i; // j = 1, i = 1: prefix yields the incremented value
j = i++; // j = 1, i = 2: postfix yields the unincremented value

The prefix version avoids unnecessary work. Use Postfix Operators only When Necessary.

4.6. The Member Access Operators

The dot and arrow operators provide for member access.

string s1 = "a string", *p = &s1;
auto n = s1.size(); // run the size member of the string s1
n = (*p).size();    // run size on the object to which p points
n = p->size();      // equivalent to (*p).size()

4.7. The Conditional Operator


cond  ? expr1  : expr2;
This operator executes by evaluating cond: If the condition is true, then expr1 is evaluated; otherwise, expr2 is evaluated.

4.8. The Bitwise Operators

4.9. The sizeof Operator

The operator takes one of two forms:
  • sizeof (type)
  • sizeof expr

Sales_data data, *p;
sizeof(Sales_data); // size required to hold an object of type Sales_data
sizeof data; // size of data's type, i.e., sizeof(Sales_data)
sizeof p;    // size of a pointer
sizeof *p;   // size of the type to which p points, i.e., sizeof(Sales_data)
sizeof data.revenue; // size of the type of Sales_data's revenue member
sizeof Sales_data::revenue; // alternative way to get the size of revenue

4.10. Comma Operator

4.11. Type Conversions


Two types are related if there is a conversion between them.
The implicit conversions are carried out automatically without programmer intervention — and sometimes without programmer knowledge.

4.11.1. The Arithmetic Conversions


Operands to an operator are converted to the widest type.

4.11.2. Other Implicit Conversions

  • Array to Pointer Conversions
  • Pointer Conversions
  • Conversions to bool
  • Conversion to const
  • Conversions Defined by Class Types

4.11.3. Explicit Conversions


Sometimes we want to explicitly force an object to be converted to a different type.

We use a cast to request an explicit conversion.

Casts are inherently dangerous constructs, we strongly recommend that programmers avoid casts.

  • A named cast
  • 
        cast-name<type>(expression);
        
    The cast-name may be one of:
    • static_cast
    • 
      // cast used to force floating-point division
      double slope = static_cast<double>(j) / i;      
            
    • dynamic_cast
    • 
            
            
    • const_cast
    • 
      const char *pc;
      char *p = const_cast<char*>(pc); // ok: but writing through p is
      undefined      
            
    • reinterpret_cast
    • 
            
            
  • Old-Style Casts
  • 
    type (expr); // function-style cast notation
    (type) expr; // C-language-style cast notation
          

4.12. Operator Precedence Table

Chapter 5. Statements

5.1. Simple Statements

  • Null Statements
  • 
    // read until we hit end-of-file or find an input equal to sought
    while (cin >> s && s != sought)
        ; // null statement      
      
  • Compound Statements(Blocks)
  • A (possibly empty) sequence of statements and declarations surrounded by a pair of curly braces. A block is not terminated by a semicolon.
    
    // read until we hit end-of-file or find an input equal to sought
    while (cin >> s && s != sought)
        { } // empty block     
      

5.2. Statement Scope

Variables defined in the control structure (if, switch, while, and for ) statement are visible only within that statement.

while (int i = get_num()) // i is created and initialized on each iteration
    cout << i << endl;
i = 0;  // error: i is not accessible outside the loop

5.3. Conditional Statements

5.4. Iterative Statements

5.4.1. The while Statement

5.4.2. Traditional for Statement


for (initializer; condition; expression)
      statement
expression is evaluated after each iteration of the loop.

5.4.3. Range for Statement


for (declaration : expression)
    statement
  • declaration defines a variable.
  • The variable's type must can be converted from each element of the sequence. Types match is to use the auto type specifier. If we want to write to the elements in the sequence, the loop variable must be a reference type.
  • expression must represent a sequence
  • braced initializer list , an array , or an object of a type such as vector or string that has begin() and end() members that return iterators.

vector<int> v = {0,1,2,3,4,5,6,7,8,9};
// range variable must be a reference so we can write to the elements
for (auto &r : v)   // for each element in v
    r *= 2;         // double the value of each element in v
    

5.4.4. The do while Statement

A do while ends with a semicolon after the parenthesized condition.

do
        statement
while (condition);

5.5. Jump Statements

5.5.1. The break Statement

5.5.2. The continue Statement

A continue statement terminates the current iteration of the nearest enclosing loop and immediately begins the next iteration.

5.5.3. The goto Statement

5.6. try Blocks and Exception Handling

In C++, exception handling involves
  • throw expressions
  • try blocks
  • A try block starts with the keyword try and ends with one or more catch clauses. Exceptions thrown from code executed inside a try block are usually handled by one of the catch clauses.

5.6.1. A throw Expression


if (item1.isbn() != item2.isbn())
    throw runtime_error("Data must refer to same ISBN");

5.6.2. The try Block


try {
    program-statements
} catch (exception-declaration) {
    handler-statements
} catch (exception-declaration) {
    handler-statements
} // . . .

The following try block has a single catch clause, which handles exceptions of type runtime_error.

    try {
        ...
    } catch (runtime_error err) {
        // remind the user that the ISBNs must match and prompt for another pair
        cout << err.what() << endl;
    }
Each of the library exception classes defines a member function named what. These functions take no arguments and return a C- style character string (i.e., a const char*).In complicated systems, the execution path of a program may pass through multiple try blocks before encountering code that throws an exception:


  • When an exception is thrown, the search for a exception handler reverses the call chain until a catch of an appropriate type is found.
  • If no appropriate catch is found, execution is transferred to a library function named terminate.
  • If a program has no try blocks and an exception occurs, then terminate is called and the program is exited.

5.6.3. Standard Exceptions


The C++ library defines several exception classes that it uses to report problems encountered in the functions in the standard library.

Some functions of the standard C++ language library send exceptions that can be captured if we include them within a try block. These standard exceptions can be used in the programs we write.

These exceptions are sent with a class derived from std::exception as type.
The <exception> header defines the most general kind of exception class named exception.
The standard hierarchy of exceptions:


exception
   bad_alloc    (thrown by new)
   bad_cast    (thrown by dynamic_cast when fails with a referenced type)
   bad_exception   (thrown when an exception doesn't match any catch)
   bad_typeid    (thrown by typeid)
   logic_error
     domain_error
     invalid_argument
     length_error
     out_of_range
   runtime_error
     overflow_error
     range_error
     underflow_error
   ios_base::failure (thrown by ios::clear)
    
The C++ library defines several exception classes which are defined in 4 headers:
  • <exception>
  • This defines the exception class
  • <stdexcept>
  • <stdexcept> is derived from the exception class and defines several general-purpose exception classes:
  • <new>
  • This defines the bad_alloc exception type
  • <type_info>
  • This defines the bad_cast exception type

Chapter 6. Functions

A function is a block of code with a name. Functions can be overloaded, meaning that the same name may refer to several different functions.

6.1. Function Basics

A function is defined by:
  • a return type
  • a name
  • a list of zero or more parameters
  • we can use the keyword void to indicate that there are no parameters.
  • a body
A function is executed through the call operator, which is a pair of parentheses. Arguments are the initializers for a function’s parameters. The type of each argument must match the corresponding parameter.

6.1.1. Local Objects

In C++, names have scope, and objects have lifetimes. Parameters and variables defined inside a function body are referred to as local variables. Objects that exist only while a block is executing are known as automatic objects. Parameters are automatic objects. Storage for the parameters is allocated when the function begins and are destroyed when the function terminates.
  • Automatic objects corresponding to the function’s parameters are initialized by the arguments passed to the function.
  • Automatic objects corresponding to local variables are initialized if their definition contains an initializer. Otherwise, they are default initialized
Local static variables are not destroyed when a function ends; they are destroyed when the program terminates.

6.1.2. Function Declarations

As with variables, a function may be defined only once but may be declared multiple times. Because a function declaration has no body, there is no need for parameter names. Although parameter names are not required, they can be used to help users of the function understand what the function does: Functions should be declared in header files and defined in source files. With this, if the interface to the function changes, only one declaration has to be changed. The source file that defines a function should include the header that contains that function’s declaration. That way the compiler will verify that the definition and declaration are consistent.

6.1.3. Separate Compilation

Most compilers provide a way to separately compile each file into object files. The compiler lets us link object files together to form an executable.

6.2. Argument Passing

Each time we call a function, its parameters are created and initialized by the arguments passed in the call:
  • If the parameter is a reference
  • The parameter is bound to its argument. The corresponding argument is “passed by reference”, a reference parameter is an alias for the object to which it is bound
  • If the parameter is not a reference
  • The argument’s value is copied. The parameter and argument are independent objects. We say such arguments are “passed by value”.

6.2.1. Passing Arguments by Value

Programmers accustomed to programming in C often use pointer parameters to access objects outside a function. In C++, programmers generally use reference parameters instead.

6.2.2. Passing Arguments by Reference

It can be inefficient to copy objects of large class types or large containers. Moreover, some class types (including the IO types) cannot be copied. We can use references to avoid copies. Reference parameters that are not changed inside a function should be references to const.

6.2.3. const Parameters and Arguments

A top-level const is one that applies to the object itself. It only prevents modification through a path using the pointer or reference. When we copy an argument to initialize a parameter, we can pass either a const or a nonconst object to a parameter that has a top-level const,

    void fcn(const int i); /* fcn can read but not write to i */ 
    

6.2.4. Array Parameters

We cannot copy an array, and when we use an array it is (usually) converted to a pointer. When we pass an array to a function, we are actually passing a pointer to the array’s first element. Even though we cannot pass an array by value, we can write a parameter that looks like an array, these three declarations of print are equivalent:

void print(const int *a );
void print(const int a[] );   // shows the intent that the function takes an array
void print(const int a[10] ); // dimension for documentation purposes (at best)
Because arrays are passed as pointers, functions ordinarily don’t know the size of the array they are given. They must rely on additional information provided by the caller:
  • Using a Marker to Specify the Extent of an Array
  • there is an obvious end-marker value (like the null character)
  • Using the Standard Library Conventions
  • pointers to the first and one past the last element in the array are provided.
  • Explicitly Passing a Size Parameter
When a function does not need write access to the array elements, the array parameter should be a pointer to const.

const int *p;
we can define a parameter that is a reference to an array,

void print( int (&arr)[10] )
a multidimensional array is passed as a pointer to its first element,

void print(int (*matrix)[10], int rowSize) { /* . . . */ }
// equivalent definition
void print(int matrix[][10], int rowSize) { /* . . . */ }

6.2.5. main: Handling Command-Line Options


int main(int argc, char *argv[]) { ... }
int main(int argc, char **argv) { ... }

6.2.6. Functions with Varying Parameters

The C++11 standard provides a way to write a function that takes a varying number of arguments: If all the arguments have the same type, we can pass a library type named initializer_list. An initializer_list is a library type that represents an array of values of the specified type. Initializer_list is a template type. When we define an initializer_list, we must specify the type of the elements that the list will contain:

initializer_list<string> ls; // initializer_list of strings
initializer_list<int> li;    // initializer_list of ints
Unlike vector, the elements in an initializer_list are always const values; there is no way to change the value of an element in an initializer_list. We can write our function to produce error messages from a varying number of arguments as follows:

void error_msg(initializer_list<string> il)
{
    for (auto beg = il.begin(); beg != il.end(); ++beg)
        cout << *beg << " " ;
    cout << endl;
}

void error_msg(ErrCode e, initializer_list<string> il)
{
    cout << e.msg() << ": ";
    for (const auto &elem : il)
        cout << elem << " " ;
    cout << endl;
}

  • the begin() member gives us a pointer to the first element in the list
  • the end() is an off-the-end pointer one past the last element.
Use curly braces to pass a sequence of values:

if (expected != actual)
    error_msg( {"functionX", expected, actual} );
else
    error_msg( {"functionX", "okay"} )
Ellipsis parameters should be used only for types that are common to both C and C++. An ellipsis parameter may appear only as the last element in a parameter list and may take either of two forms:

void foo(parm_list, ...);
void foo(...);
No type checking is done for the arguments that correspond to the ellipsis parameter.

6.3. Return Types and the return Statement

Calls to functions that return references are lvalues,

char &get_val(string &str, string::size_type ix)
{
    return str[ix]; // get_val assumes the given index is valid
}
int main()
{
    string s("a value");
    cout << s << endl;   // prints a value
    get_val(s, 0) = 'A'; // changes s[0] to A
    cout << s << endl;   // prints A value
}   
Under the C++11 standard, functions can return a braced list of values.

vector<string> process()
{
    // . . .
    return {"functionX", expected, actual};
}

6.3.3. Returning a Pointer to an Array

6.4. Overloaded Functions

6.5. Features for Specialized Uses

6.5.1. Default Arguments

6.5.2. Inline and constexpr Functions

In general, the inline mechanism is meant to optimize small, straight-line functions that are called frequently. A constexpr function is defined like any other function but must meet certain restrictions:
  • the return type and the type of each parameter in a must be a literal type
  • the function body must contain exactly one return statement

6.5.3. Aids for Debugging

assert() is a preprocessor macro.

assert(expr);
if the expression is false (i.e., zero), then assert writes a message and terminates the program.The behavior of assert depends on the status of a preprocessor variable named NDEBUG:

#ifdef NDEBUG
#define assert(condition) ((void)0)
#else
#define assert(condition) /*implementation defined*/
#endif
  • If NDEBUG is defined, assert does nothing.
  • If NDEBUG is not defined, assert outputs implementation-specific diagnostic information
  • By default, NDEBUG is not defined. ( in debug mode )

6.6. Function Matching

6.6.1. Argument Type Conversions

6.7. Pointers to Functions


// declare compares lengths of two strings
bool lengthCompare(const string &, const string &);

// declare a pointer pf which points to a function returning bool that takes two const string references
bool (*pf)(const string &, const string &);  // uninitialized

pf = lengthCompare;  // pf now points to the function named lengthCompare

Chapter 7. Classes

The fundamental ideas behind classes are data abstraction and encapsulation.

7.1 Defining Abstract Data Types


7.1.1. Designing the Sales_data Class

7.1.2. Defining the Revised Sales_data Class

Although every member must be declared inside its class, we can define a member function’s body either inside or outside of the class body.

std::string isbn() const 
{ 
  return bookNo; 
}
Member functions access the object on which they were called through an extra, implicit parameter named this.

total.isbn();
the compiler passes the address of total to the implicit this parameter in isbn(). It is as if the compiler rewrites this call as

Sales_data::isbn(&total);// pseudo-code illustration of how a call to a member function is translated
Inside the body of a member function, we can use this.

std::string isbn() const { return this->bookNo; }
this is implicit and does not appear in the parameter list. There is no place to indicate that this should be a pointer to const. The language resolves this, the keyword const that follows the parameter list is to modify the type of the implicit this pointer. A const following the parameter list indicates that this is a pointer to const. Member functions that use const in this way are const member functions.
  • A const member function can be called by any type of object.
  • Non-const functions can be called by non-const objects only.

#include <iostream>
using namespace std;

class Demo {
   int val;
   public:
   Demo(int x = 0) {
      val = x;
   }
   void setValue(int n) {
      val = n;
   }
   int getValue() const {
      return val;
   }
};
int main() {
   const Demo d(28);
   Demo d1(8);
   // d.setValue(11); // error: passing ‘const Demo’ as ‘this’ argument discards qualifiers
   cout << "The value using object d : " << d.getValue();
   d1.setValue(22);
   cout << "\nThe value using object d1 : " << d1.getValue();
   return 0;
}

7.1.3. Defining Nonmember Class-Related Functions

Functions that are conceptually part of a class, but not defined inside the class, are typically declared (but not defined) in the same header as the class itself.

7.1.4. Constructors

Classes control object initialization by defining one or more special member functions known as constructors. The job of a constructor is to initialize the data members of a class object. A constructor is run whenever an object of a class type is created.
  • Constructors have the same name as the class.
  • Constructors have a (possibly empty) parameter list and a (possibly empty) function body.
  • A class can have multiple constructors.
If our class does not explicitly define any constructors, the compiler will implicitly define the default constructor for us. The default constructor is one that takes no arguments. This synthesized constructor initializes each data member of the class as follows:
  • If there is an in-class initializer, use it to initialize the member.
  • Otherwise, default-initialize the member.
Some simple classes can rely on the synthesized default constructor. For some classes, the synthesized default constructor does the wrong thing for objects of built-in or compound type. Therefore, classes that have members of built-in or compound type should ordinarily either initialize those members inside the class or define their own version of the default constructor. The following example defines a class with one constructor and two default constructors.

#include <iostream>
using namespace std; 

class Box {
public:
    // Default constructor
    Box() = default;

    // Initialize a Box with equal dimensions (i.e. a cube)
    Box(int i) : m_width(i), m_length(i), m_height(i) // member init list
    {}

    // Initialize a Box with custom dimensions
    Box(int width, int length, int height)
        : m_width(width), m_length(length), m_height(height)
    {}

    void printVol() { cout <<  m_width * m_length * m_height << endl; }

private:
    // Will have value of 0 when default constructor is called.
    // If we didn't zero-init here, default constructor would
    // leave them uninitialized with garbage values.
    int m_width{ 0 };
    int m_length{ 0 };
    int m_height{ 0 };
};

int main()
{
    Box b; // Calls Box()
    b.printVol();

    // Using uniform initialization (preferred):
    Box b2 {2}; // Calls Box(int)
    b2.printVol();
  
    Box b3 {3, 4, 5}; // Calls Box(int, int, int)
    b3.printVol();

    // Using function-style notation:
    Box b4(5,6, 7); // Calls Box(int, int, int)
    b4.printVol();

}

0
8
60
210
A default constructor is a constructor which can be called with no arguments. We can ask the compiler to generate the constructor for us by writing = default after the parameter list. A constructor initializer list specifies initial values for one or more data members of the object being created. The list of members to be initialized is indicated with constructor as a comma-separated list followed by a colon. Constructors may have empty function bodies, the only work these constructors need to do is giving the data members their values. Members that do not appear in the constructor initializer list are initialized by the corresponding in-class initializer (if there is one) or are default initialized.

7.2 Access Control and Encapsulation


In C++ we use access specifiers to enforce encapsulation:
  • public
  • Members defined after a public specifier are accessible to all parts of the program. The public members define the interface to the class.
  • private
  • Members defined after a private specifier are accessible to the member functions of the class but are not accessible to code that uses the class. The private sections encapsulate (i.e., hide) the implementation.
Each access specifier specifies the access level of the succeeding members. The specified access level remains in effect until the next access specifier or the end of the class body.
We can define a class type using either keyword, the only difference between struct and class is the default access level:
  • If we use the struct keyword, the members defined before the first access specifier are public
  • if we use class, then the members are private.

7.2.1. Friends

A class can allow another class or function to access its nonpublic members by making that class or function a friend. A class makes a function its friend by including a declaration for that function preceded by the keyword friend:
  • to demonstrate friend Class
  • 
    #include <iostream>
    using namespace std;
    
    class A { 
    friend class B; // Friend Class 
    private: 
        int a; 
      
    public: 
        A() { a = 0; } 
    }; 
      
    class B { 
    private: 
        int b; 
      
    public: 
        void showA(A& x) 
        { 
            // Since B is friend of A, it can access 
            // private members of A 
            cout << "A::a=" << x.a; 
        } 
    }; 
      
    int main() 
    { 
        A a; 
        B b; 
        b.showA(a); 
        return 0; 
    } 
    
    
  • to demonstrate friend function of another class
  • 
    #include <iostream>
    
    class B; 
    
    class A { 
    public: 
        void showB(B&); 
    }; 
      
    class B { 
    friend void A::showB(B& x); // Friend function 
    private: 
        int b; 
      
    public: 
        B() { b = 0; } 
    }; 
      
    void A::showB(B& x) 
    { 
        // Since showB() is friend of B, it can 
        // access private members of B 
        std::cout << "B::b = " << x.b; 
    } 
      
    int main() 
    { 
        A a; 
        B b; 
        a.showB(b); 
        return 0; 
    }
    
Friend declarations may appear only inside a class definition; friends are not members of the class. Ordinarily it is a good idea to group friend declarations together at the beginning or end of the class definition. Encapsulation provides two important advantages:
  • User code cannot inadvertently corrupt the state of an encapsulated object.
  • The implementation of an encapsulated class can change over time without requiring changes in user-level code.

7.3 Additional Class Features


7.3.1. Class Members Revisited

Defining a type member,

class Screen {
public:
    typedef std::string::size_type pos;
    // alternative way to declare a type member using a type alias:  using pos = std::string::size_type;
private:
    pos cursor = 0;
    pos height = 0, width = 0;
    std::string contents;
};
Users of Screen shouldn’t know that Screen uses a string to hold its private data. Classes often have small functions that can benefit from being inlined. We can explicitly declare a member function as inline as part of its declaration inside the class body. Alternatively, we can specify inline on the function definition that appears outside the class body As with nonmember functions, member functions may be overloaded so long as the functions differ by the number and/or types of parameters. If a member is a mutable member, so any member function, including const functions, can change its value.

7.3.2. Functions That Return *this

Functions that return a reference are lvalues , which means that they return the object itself, not a copy of the object.

7.3.3. Class Types

Every class defines a unique type. Just as we can declare a function apart from its definition, we can also declare a class without defining it. This declaration is sometimes referred to as a forward declaration. After a declaration and before a definition is seen, the type is an incomplete type.

7.3.4. Friendship Revisited

A class can also make another class its friend or it can declare specific member functions of another (previously defined) class as friends. The member functions of a friend class can access all the members, including the nonpublic members, of the class granting friendship.

class Screen {
    // Window_mgr members can access the private parts of class Screen
    friend class Window_mgr;
    // ... rest of the Screen class
};

Each class controls which classes or functions are its friends.

7.4 Class Scope


A class is a scope, the names of the members are hidden outside of the class. When a member function is defined outside the class body, any name used in the return type is outside the class scope. As a result, the return type must specify the class of which it is a member.

7.4.1. Name Lookup and Class Scope

7.5 Constructors Revisited


7.5.1. Constructor Initializer List

We must use the constructor initializer list to provide values for members that are const, reference, or of a class type that does not have a default constructor. It is a good idea to write constructor initializers in the same order as the members are declared. Moreover, when possible, avoid using members to initialize other members. A constructor that supplies default arguments for all its parameters also defines the default constructor.

7.5.2. Delegating Constructors

A delegating constructor uses another constructor from its own class to perform its initialization.

#include <iostream>
using namespace std; 
class A { 
    int x, y, z; 
  
public: 
    A() 
    { 
        x = 0; 
        y = 0; 
        z = 0; 
    } 
  
    // Constructor delegation  
    A(int z) : A() 
    { 
        this->z = z; // Only update z 
    } 
  
    void show() 
    { 
        cout << x << ", "  << y << ", "  << z << endl; 
    } 
}; 
int main() 
{ 
    A obj_0;
    obj_0.show();
    A obj(3); 
    obj.show(); 
    return 0; 
} 

0, 0, 0
0, 0, 3

7.5.3. The Role of the Default Constructor

It is a common mistake among programmers new to C++ to try to declare an object initialized with the default constructor as follows:

Sales_data obj(); // oops! declares a function, not an object
Sales_data obj2;  // ok: obj2 is an object, not a function

7.5.4. Implicit Class-Type Conversions

When we use the copy form of initialization ( = ), implicit conversions happen:

class Foo {
public:
  Foo(int x);
};

Foo a = 42;         //OK: calls Foo::Foo(int) passing 42 as an argument

The explicit specifier tell the compiler that a certain constructor may not be used to implicitly cast an expression to its class type.

class Foo {
public:
  explicit Foo(int x);
};

Foo a = 42;         //Compile-time error: can't convert 42 to an object of type Foo
Foo b(42);          //OK: calls Foo::Foo(int) passing 42 as an argument

When a constructor is declared explicit, it can be used only with the direct form of initialization

7.5.5. Aggregate Classes

An aggregate class gives users direct access to its members and has special initialization syntax. A class is an aggregate if
  • All of its data members are public
  • It does not define any constructors
  • It has no in-class initializers
  • It has no base classes or virtual functions
We can initialize the data members of an aggregate class by providing a braced list of member initializers:

struct Data {
    int ival;
    string s;
};

// val1.ival = 0; val1.s = string("Anna")
Data val1 = { 0, "Anna" };
The initializers must appear in declaration order of the data members.

7.5.6. Literal Classes

7.6 static Class Members


The static members of a class exist outside any object. There is only one static member object that will be shared by all the class's objects. Similarly, Static data members are not defined when we create objects of the class. As a result, they are not initialized by the class’ constructors. We must define and initialize each static data member outside the class body.

class BufferedOutput
{
public:
   // Return number of bytes written by any object of this class.
   short BytesWritten()
   {
      return bytecount;
   }

   // Reset the counter.
   static void ResetCount()
   {
      bytecount = 0;
   }

   // Static member declaration.
   static long bytecount;
};

// Define bytecount in file scope.
long BufferedOutput::bytecount;
static members exist independently of any other object.

Chapter 8. The IO Library

The C++ language does not deal directly with input and output. Instead, IO is handled by a family of types defined in the standard library.
The generally used IO library facilities:
  • istream (input stream) type
  • which provides input operations
  • ostream (output stream) type
  • which provides output operations
  • cin
  • an istream object that reads the standard input
  • cout
  • an ostream object that writes to the standard output
  • cerr
  • an ostream object, typically used for program error messages, that writes to the standard error
  • The >> operator
  • which is used to read input from an istream object
  • The << operator
  • which is used to write output to an ostream object
  • The getline() function
  • which reads a line of input from a given istream into a given string

8.1 The IO Classes


There are 3 headers defining the IO types:
  • iostream
  • defines the basic types used to read from and write to a stream,
  • fstream
  • defines the types used to read and write named files
  • sstream
  • defines the types used to read and write in-memory strings.
To support languages that use wide characters, the library defines a set of types and objects that manipulate wchar_t data

8.1.1. No Copy or Assign for IO Objects


Functions that do IO typically pass and return the stream through references.

ofstream out1, out2;
out1 = out2;              // error: cannot assign stream objects
ofstream print(ofstream); // error: can't initialize the ofstream parameter
out2 = print(out2);       // error: cannot copy stream objects

8.1.2. Condition States


Errors can occur while doing IO.
The IO classes define functions and flags that let us access and manipulate the condition state of a stream.

8.1.3. Managing the Output Buffer


Each output stream manages a buffer, which it uses to hold the data that the program reads and writes.

cout << "hi!" << endl;   // writes hi and a newline, then flushes the buffer
cout << "hi!" << flush;  // writes hi, then flushes the buffer; adds no data
cout << "hi!" << ends;   // writes hi and a null, then flushes the buffer

8.2 File Input and Output


The fstream header defines three types to support file IO:
  • ifstream to read from a given file
  • ofstream to write to a given file
  • fstream reads and writes a given file

8.2.1. Using File Stream Objects


When we want to read or write a file, we define a file stream object and associate that object with the file. Each file stream class defines a member function named open that does whatever system-specific operations are required to locate the given file and open it for reading or writing as appropriate.
When we create a file stream, we can (optionally) provide a file name. When we supply a file name, open is called automatically:

ifstream in(ifile); // construct an ifstreamand open the given file
in.close();               // close the file

ofstream out;       // output file stream that is not associated with any file
out.open(ifile + ".copy");  // open the specified file

With the new standard, file names can be either library strings or C-style character arrays.

8.2.2. File Modes


Each stream has an associated file mode that represents how the file may be used.
We can supply a file mode whenever we open a file.
  • out
  • may be set only for an ofstream or fstream object
  • in
  • may be set only for an ifstream or fstream object
  • trunc
  • may be set only when out is also specified.
  • app
  • mode may be specified so long as trunc is not. If app is specified, the file is always opened in output mode, even if out was not explicitly specified.
  • ate , binary
  • may be specified on any file stream object type and in combination with any other file modes.
File Mode Is Determined Each Time open Is Called.
By default, when we open an ofstream, the contents of the file are discarded. The only way to prevent an ostream from emptying the given file is to specify app:

// to preserve the file's contents, we must explicitly specify app mode
ofstream app("file2", ofstream::app);   // out is implicit


8.3 string Streams


The sstream header defines three types to support in-memory IO:
  • istringstream
  • reads a string
  • ostringstream
  • writes a string, and
  • stringstream
  • reads and writes the string.

留言

熱門文章