BUY THIS BOOK
Add to Cart

Print Book $39.95


Add to Cart

Print+PDF $51.94

Add to Cart

PDF $31.99

Safari Books Online

What is this?

Add to UK Cart

Print Book £28.50

What is this?

Looking to Reprint or License this content?


C++ In a Nutshell
C++ In a Nutshell A Desktop Quick Reference

By Ray Lischner
Book Price: $39.95 USD
£28.50 GBP
PDF Price: $31.99

Cover | Table of Contents | Colophon


Table of Contents

Chapter 1: Language Basics
C++ is a case-sensitive, free-form programming language that supports procedural, object-oriented, and generic programming. This chapter presents the basic rules for the language, such as lexical rules and basic syntax elements.
A C++ source file undergoes many transformations on its way to becoming an executable program. The initial steps involve processing all the #include and conditional preprocessing directives to produce what the standard calls a translation unit. Translation units are important because they have no dependencies on other files. Nonetheless, programmers still speak in terms of source files, even if they actually mean translation units, so this book uses the phrase source file because it is familiar to most readers. The term "translation" encompasses compilation and interpretation, although most C++ translators are compilers. This section discusses how C++ reads and compiles (translates) source files (translation units).
A C++ program can be made from many source files, and each file can be compiled separately. Conceptually, the compilation process has several steps (although a compiler can merge or otherwise modify steps if it can do so without affecting the observable results):
  1. Read physical characters from the source file and translate the characters to the source character set (described in Section 1.4 later in this chapter). The source "file" is not necessarily a physical file; an implementation might, for example, retrieve the source from a database. Trigraph sequences are reduced to their equivalent characters (see Section 1.6 later in this chapter). Each native end-of-line character or character sequence is replaced by a newline character.
  2. If a backslash character is followed immediately by a newline character, delete the backslash and the newline. The backslash/newline combination must not fall in the middle of a universal character (e.g., \u1234
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Compilation Steps
A C++ source file undergoes many transformations on its way to becoming an executable program. The initial steps involve processing all the #include and conditional preprocessing directives to produce what the standard calls a translation unit. Translation units are important because they have no dependencies on other files. Nonetheless, programmers still speak in terms of source files, even if they actually mean translation units, so this book uses the phrase source file because it is familiar to most readers. The term "translation" encompasses compilation and interpretation, although most C++ translators are compilers. This section discusses how C++ reads and compiles (translates) source files (translation units).
A C++ program can be made from many source files, and each file can be compiled separately. Conceptually, the compilation process has several steps (although a compiler can merge or otherwise modify steps if it can do so without affecting the observable results):
  1. Read physical characters from the source file and translate the characters to the source character set (described in Section 1.4 later in this chapter). The source "file" is not necessarily a physical file; an implementation might, for example, retrieve the source from a database. Trigraph sequences are reduced to their equivalent characters (see Section 1.6 later in this chapter). Each native end-of-line character or character sequence is replaced by a newline character.
  2. If a backslash character is followed immediately by a newline character, delete the backslash and the newline. The backslash/newline combination must not fall in the middle of a universal character (e.g., \u1234) and must not be at the end of a file. It can be used in a character or string literal, or to continue a preprocessor directive or one-line comment on multiple lines. A non-empty file must end with a newline.
  3. Partition the source into preprocessor tokens separated by whitespace and comments. A preprocessor token is slightly different from a compiler token (see the next section, Section 1.2). A preprocessor token can be a header name, identifier, number, character literal, string literal, symbol, or miscellaneous character. Each preprocessor token is the longest sequence of characters that can make up a legal token, regardless of what comes after the token.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Tokens
All source code is divided into a stream of tokens . The compiler tries to collect as many contiguous characters as it can to build a valid token. (This is sometimes called the "max munch" rule.) It stops when the next character it would read cannot possibly be part of the token it is reading.
A token can be an identifier, a reserved keyword, a literal, or an operator or punctuation symbol. Each kind of token is described later in this section.
Step 3 of the compilation process reads preprocessor tokens. These tokens are converted automatically to ordinary compiler tokens as part of the main compilation in Step 7. The differences between a preprocessor token and a compiler token are small:
  • The preprocessor and the compiler might use different encodings for character and string literals.
  • The compiler treats integer and floating-point literals differently; the preprocessor does not.
  • The preprocessor recognizes < header > as a single token (for #include directives); the compiler does not.
An identifier is a name that you define or that is defined in a library. An identifier begins with a nondigit character and is followed by any number of digits and nondigits. A nondigit character is a letter, an underscore, or one of a set of universal characters. The exact set of nondigit universal characters is defined in the C++ standard and in ISO/IEC PDTR 10176. Basically, this set contains the universal characters that represent letters. Most programmers restrict themselves to the characters a...z, A...Z, and underscore, but the standard permits letters in other languages.
Not all compilers support universal characters in identifiers.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Comments
Comments start with /* and end with */. These comments do not nest. For example:
/* this is a comment /* still a comment */
int not_in_a_comment;
A comment can also start with //, extending to the end of the line. For example:
const int max_widget = 42; // Largest size of a widget
Within a /* and */ comment, // characters have no special meaning. Within a // comment, /* and */ have no special meaning. Thus, you can "nest" one kind of comment within the other kind. For example:
/* Comment out a block of code:
const int max_widget = 42; // Largest size of a widget
*/

///* Inhibit the start of a block comment
const int max_widget = 10; // Testing smaller widget limit
//*/
A comment is treated as whitespace. For example, str/*comment*/ing describes two separate tokens, str and ing.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Character Sets
The character sets that C++ uses at compile time and runtime are implementation-defined. A source file is read as a sequence of characters in the physical character set. When a source file is read, the physical characters are mapped to the compile-time character set, which is called the source character set. The mapping is implementation-defined, but many implementations use the same character set.
At the very least, the source character set always includes the characters listed below. The numeric values of these characters are implementation-defined.
Space
Horizontal tab
Vertical tab
Form feed
Newline
a ... z
A ... Z
0 ... 9
_ { } [ ] # ( ) < > % : ; . ? * + - / ^ & | ~ ! = , \ " '
The runtime character set, called the execution character set , might be different from the source character set (though it is often the same). If the character sets are different, the compiler automatically converts all character and string literals from the source character set to the execution character set. The basic execution character set includes all the characters in the source character set, plus the characters listed below. The execution character set is a superset of the basic execution character set; additional characters are implemented-defined and might vary depending on locale.
Alert
Backspace
Carriage return
Null
Conceptually, source characters are mapped to Unicode (ISO/IEC 10646) and from Unicode to the execution character set. You can specify any Unicode character in the source file as a universal character in the form \u
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Alternative Tokens
Some symbols have multiple representations, as shown in Table 1-2. These alternative tokens have no special meaning in a character or string literal. They are merely alternative representations of common symbols. Most programmers do not use alternative tokens, especially the nonalphabetic ones. Some programmers find and, or, and not to be easier to read and understand than &&, ||, and !.
Table 1-2: Alternative tokens
Alternative token
Primary token
<%
{
%>
}
<:
[
:>
]
%:
#
%:%:
##
and
&&
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Trigraphs
A few characters have an alternative representation, called a trigraph sequence. A trigraph is a three-character sequence that represents a single character. The sequence always starts with two question marks. The third character determines which character the sequence represents. All the trigraph sequences are shown in Table 1-3. If the third character is not one of those in the table, the sequence is not a trigraph and is left alone. For example, the characters ???- represent the two characters ?~. Note that trigraphs are expanded anywhere they appear, including within string literals and character literals, in comments, and in preprocessor directives.
Table 1-3: Trigraph sequences
Trigraph
Replacement
??=
#
??/
\
??'
^
??(
[
??)
]
??!
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 2: Declarations
A C++ source file contains a series of zero or more declarations . A declaration can be a function, type, object (constant or variable), namespace, template, or a related entity. The first part of this chapter covers general topics that pertain to all declarations. The second part discusses types, objects, and namespaces specifically. Function, class, and template declarations each get their own chapters: Chapter 5 covers functions; Chapter 6 covers classes and friends; and Chapter 7 covers template declarations, specializations, and instantiations.
The syntax descriptions in this chapter are informal. See Chapter 12 for a precise BNF grammar.
A declaration is the all-encompassing term for anything that tells the compiler about an identifier. In order to use an identifier, the compiler must know what it means: is it a type name, a variable name, a function name, or something else? Therefore, a source file must contain a declaration (directly or in an #include file) for every name it uses.
A definition defines the storage, value, body, or contents of a declaration. The difference between a declaration and a definition is that a declaration tells you an entity's name and the external view of the entity, such as an object's type or a function's parameters, and a definition provides the internal workings of the entity: the storage and initial value of an object, a function body, and so on.
In a single source file, there can be at most one definition of an entity. In an entire program, there must be exactly one definition of each function or object used in the program, except for inline functions; an inline function must be defined in every source file that uses the function, and the definitions must all be identical.
A program can have more than one definition of a given class, enumeration, inline function, or template, provided the definitions are in separate source files, and each source file has the same definition.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Declarations and Definitions
A declaration is the all-encompassing term for anything that tells the compiler about an identifier. In order to use an identifier, the compiler must know what it means: is it a type name, a variable name, a function name, or something else? Therefore, a source file must contain a declaration (directly or in an #include file) for every name it uses.
A definition defines the storage, value, body, or contents of a declaration. The difference between a declaration and a definition is that a declaration tells you an entity's name and the external view of the entity, such as an object's type or a function's parameters, and a definition provides the internal workings of the entity: the storage and initial value of an object, a function body, and so on.
In a single source file, there can be at most one definition of an entity. In an entire program, there must be exactly one definition of each function or object used in the program, except for inline functions; an inline function must be defined in every source file that uses the function, and the definitions must all be identical.
A program can have more than one definition of a given class, enumeration, inline function, or template, provided the definitions are in separate source files, and each source file has the same definition.
These rules are known as the One Definition Rules, or ODR.
Before you can use an entity (e.g., calling a function or referring to an object), the compiler needs the entity's declaration, but not necessarily its definition. You can use a class that has an incomplete declaration in some contexts, but usually you need a complete definition. (See Chapter 6 for details about incomplete classes.) The complete program needs definitions for all the declared entities, but those definitions can often reside in separate source files. The convention is to place the declarations for classes, functions, and global objects in a header file (whose name typically ends with
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Scope
A scope is a region of source code that contains declarations. Every declaration adds a name to a scope, and every use of a name requires the compiler to identify which scope contains that name's declaration. Sometimes you tell the compiler exactly which scope contains the name, and at other times the compiler determines the scope. Once the compiler knows the scope, it can look up the name to learn what the name is (object, function, class, etc.) and how the name can be used. Thus, you can think of a scope as a dictionary of names mapped to declarations.
A scope can be named or unnamed. Classes and namespaces (see Section 2.7 later in this chapter) define named scopes. Statement blocks, function bodies, and unnamed namespaces define unnamed scopes. You can qualify a name with a scope name to tell the compiler where to look up the qualified name, but you cannot qualify names from unnamed scopes. In a typical program, most names are unqualified, so the compiler must determine which scope declares the name. (See Section 2.3 later in this chapter.)
Scopes can be nested, and names in inner scopes can hide names that are declared in outer scopes. Example 2-2 illustrates abuses of the simple scope rules: the body of the for loop is a scope, in which the variable x is declared as an int; the if statement creates a nested scope, in which another declaration of x hides the outer x. The reference to x at the end of main is invalid: no x is in scope at that point.
Example 2-2. Names in inner scopes can hide names in outer scopes
#include <iostream>
#include <ostream>

int main(  )
{
  for (int i = 0; i < 100; ++i)
  {
    int x = 42;
    if (x < i)
    {
      double x = 3.14;
      std::cout << x; // Prints 3.14
    }
    std::cout << x;   // Prints 42
  }
  std::cout << x;     // Error: no x declared in this scope
}
At the same scope level, you cannot have multiple declarations for the same name, unless every declaration of that name is for an overloaded function or function template, or if the declarations are identical
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Name Lookup
When the compiler reads an identifier, it must look up the identifier to determine which declaration it comes from. In most cases, you can readily tell which identifier is which, but it is not always so simple. A small mistake can sometimes lead to code that compiles successfully but runs incorrectly because an identifier refers to a different object from the one you intended. To understand name lookup fully, you must first understand namespaces (covered later in this chapter), functions (Chapter 5), classes (Chapter 6), and templates (Chapter 7).
Name lookup takes place before overloaded functions are resolved and before the access level of class members is checked. If a name is found in an inner scope, the compiler uses that declaration, even if a better declaration would be found in an outer scope. Example 2-3 shows how problems can arise when an overloaded function is declared in more than one namespace. The function func(int) is global, and func(double) is defined in namespace N. Inside call_func, the compiler looks up the name func by searching first in the local scope (that is, the function body), then in namespace N, where it finds func(double). Name lookup stops at that point because the compiler found a match. Therefore, func(3) converts 3 to type double and calls func(double). The main function brings all the overloaded func functions into its scope (with using declarations, which are described at the end of this chapter), so name lookup can find the best match, which is func(int).
Example 2-3. Name lookup trumps overload resolution
void func(int i)
{
  std::cout << "int: " << i << '\n';
}

namespace N {
  void func(double d)
  {
    std::cout << "double: " << std::showpoint << d << '\n';
  }

  void call_func(  )
  {
    // Even though func(int) is a better match, the compiler finds
    // N::func(double) first.
    func(3);
  }
}

int main(  )
{
  N::call_func(  );       // Prints "double: 3.000000"
  using N::func;
  using ::func;
  // Now all overloaded func(  )s are at the same scope level.
  func(4);              // Prints "int: 4"
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Linkage
Every name has linkage , which determines how the compiler and linker can use the name. Linkage has two aspects: scope and language. Scope linkage dictates which scopes have access to an entity. Language linkage dictates an entity's properties that depend on programming language.
Scope linkage can be one of the following:
Internal linkage
A name with internal linkage can be referred to from a different scope within the same source file. At namespace scope (that is, outside of functions and classes), static declarations have internal linkage, as do const declarations that are not also extern. Data members of anonymous unions have internal linkage. Names in an unnamed namespace have internal linkage.
External linkage
A name with external linkage can be referred to from a different scope, possibly in a different source file. Functions and objects declared with the extern specifier have external linkage, as do entities declared at namespace scope that do not have internal linkage.
No linkage
A name with no linkage can be referred to only from within the scope where it is declared. Local declarations that are not extern have no linkage.
Every function, function type, and object has a language linkage, which is specified as a simple character string. By default, the linkage is "C++". The only other standard language linkage is "C". All other language linkages and the properties associated with different language linkages are implementation-defined.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Type Declarations
One of the hallmarks of C++ is that you can define a type that resembles any built-in type. Thus, if you need to define a type that supports arbitrary-sized integers—call it bigint—you can do so, and programmers will be able to use bigint objects the same way they use int objects.
You can define a brand new type by defining a class (see Chapter 6) or an enumeration (see Section 2.5.2 later in this chapter). In addition to declaring and defining new types, you can declare a typedef, which is a synonym for an existing type. Note that while the name typedef seems to be a shorthand for "type definition," it is actually a type declaration. (See Section 2.5.4 later in this chapter.)
This section lists the fundamental type specifiers that are built into the C++ language. For types that require multiple keywords (e.g., unsigned long int), you can mix the keywords in any order, but the order shown in the following list is the conventional order. If a type specifier requires multiple words, one of which is int, the int can be omitted. If a type is signed, the signed keyword can be omitted (except in the case of signed char).
bool
Represents a Boolean or logical value. It has two possible values: true and false.
char
Represents a narrow character. Narrow character literals usually have type char. (If a narrow character literal contains multiple characters, the type is int.) Unlike the other integral types, a plain char is not necessarily equivalent to signed
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Object Declarations
An object in C++ is a region of storage with a type, a value, and possibly a name. In traditional object-oriented programming, "object" means an instance of a class, but in C++ the definition is slightly broader to include instances of any data type.
An object (variable or constant) declaration has two parts: a series of specifiers and a list of comma-separated declarators. Each declarator has a name and an optional initializer.
Each declaration begins with a series of specifiers. The series can contain a storage class, const and volatile qualifiers, and the object's type, in any order.

Section 2.6.1.1: Storage class specifiers

A storage class specifier can specify scope linkage and lifetime. The storage class is optional. For function parameters and local variables in a function, the default storage class specifier is auto. For declarations at namespace scope, the default is usually an object with static lifetime and external linkage. C++ has no explicit storage class for such a declaration. (See Section 2.6.4 later in this chapter and Section 2.4 earlier in this chapter for more information.) If you use a storage class specifier, you must choose only one of the following:
auto
Denotes an automatic variable—that is, a variable with a lifetime limited to the block in which the variable is declared. The auto specifier is the default for function parameters and local variables, which are the only kinds of declarations for which it can be used, so it is rarely used explicitly.
extern
Denotes an object with external linkage, which might be defined in a different source file. Function parameters cannot be
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Namespaces
A namespace is a named scope. By grouping related declarations in a namespace, you can avoid name collisions with declarations in other namespaces. For example, suppose you are writing a word processor, and you use packages that others have written, including a screen layout package, an equation typesetting package, and an exact-arithmetic package for computing printed positions to high accuracy with fixed-point numbers.
The equation package has a class called fraction, which represents built-up fractions in an equation; the arithmetic package has a class called fraction, for computing with exact rational numbers; and the layout package has a class called fraction for laying out fractional regions of a page. Without namespaces, all three names would collide, and you would not be able to use more than one of the three packages in a single program.
With namespaces, each class can reside in a separate namespace—for example, layout::fraction, eqn::fraction, and math::fraction.
C++ namespaces are similar to Java packages, with a key difference: in Java, classes in the same package have additional access rights to each other; in C++, namespaces confer no special access privileges.
Define a namespace with the namespace keyword followed by an optional identifier (the namespace name) and zero or more declarations in curly braces. Namespace declarations can be discontiguous, even in separate source files or headers. The namespace scope is the accumulation of all definitions of the same namespace that the compiler has seen at the time it looks up a given name in the namespace. Namespaces can be nested. Example 2-15 shows a sample namespace definition.
Example 2-15. Defining a namespace
// The initial declaration
namespace numeric {
  class rational { ... }
  template<typename charT, typename traits>
  basic_ostream<charT,traits>& operator<<(
    basic_ostream<charT,traits>& out, const rational& r);
}

 . . . 

// This is a second definition. It adds an operator  to the namespace.
namespace numeric {
  rational operator+(const rational&, const rational&);
}

// The definition of operator+ can appear inside or outside the namespace
// definition. If it is outside, the name must be qualified with the scope
// operator.
numeric::rational numeric::operator+(const rational& r1,
                                     const rational& r2)
{
   . . . 
}

int main(  )
{
  using numeric::rational;
  rational a, b;
  std::cout << a + b << '\n';
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 3: Expressions
An expression combines literals, names, operators, and symbols to express or compute a value, or to achieve a side effect. This chapter describes the rules for writing and understanding expressions, including information on lvalues and rvalues, type conversion, constant expressions, and how C++ evaluates expressions. It contains detailed descriptions of all the operators and other forms of expressions.
The syntax descriptions in this chapter are informal. See Chapter 12 for a precise BNF grammar.
Lvalues and rvalues are fundamental to C++ expressions. Put simply, an lvalue is an object reference and an rvalue is a value. The difference between lvalues and rvalues plays a role in the writing and understanding of expressions.
An lvalue is an expression that yields an object reference, such as a variable name, an array subscript reference, a dereferenced pointer, or a function call that returns a reference. An lvalue always has a defined region of storage, so you can take its address.
An rvalue is an expression that is not an lvalue. Examples of rvalues include literals, the results of most operators, and function calls that return nonreferences. An rvalue does not necessarily have any storage associated with it.
Strictly speaking, a function is an lvalue, but the only uses for it are to use it in calling the function, or determining the function's address. Most of the time, the term lvalue means object lvalue, and this book follows that convention.
C++ borrows the term lvalue from C, where only an lvalue can be used on the left side of an assignment statement. The term rvalue is a logical counterpart for an expression that can be used only on the righthand side of an assignment. For example:
#define rvalue 42
int lvalue;
lvalue = rvalue;
In C++, these simple rules are no longer true, but the names remain because they are close to the truth. The most significant departure from traditional C is that an lvalue in C++ might be
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Lvalues and Rvalues
Lvalues and rvalues are fundamental to C++ expressions. Put simply, an lvalue is an object reference and an rvalue is a value. The difference between lvalues and rvalues plays a role in the writing and understanding of expressions.
An lvalue is an expression that yields an object reference, such as a variable name, an array subscript reference, a dereferenced pointer, or a function call that returns a reference. An lvalue always has a defined region of storage, so you can take its address.
An rvalue is an expression that is not an lvalue. Examples of rvalues include literals, the results of most operators, and function calls that return nonreferences. An rvalue does not necessarily have any storage associated with it.
Strictly speaking, a function is an lvalue, but the only uses for it are to use it in calling the function, or determining the function's address. Most of the time, the term lvalue means object lvalue, and this book follows that convention.
C++ borrows the term lvalue from C, where only an lvalue can be used on the left side of an assignment statement. The term rvalue is a logical counterpart for an expression that can be used only on the righthand side of an assignment. For example:
#define rvalue 42
int lvalue;
lvalue = rvalue;
In C++, these simple rules are no longer true, but the names remain because they are close to the truth. The most significant departure from traditional C is that an lvalue in C++ might be const, in which case it cannot be the target of an assignment. (C has since evolved and now has const lvalues.)
The built-in assignment operators require an lvalue as their lefthand operand. The built-in address (&) operator also requires an lvalue operand, as do the increment (++) and decrement (--) operators. Other operators require rvalues. The rules are not as strict for user-defined operators. Any object, including an rvalue, can be used to call member functions, including overloaded
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Type Conversions
In an arithmetic expression, binary operators require operands of the same type. If this is not the case, the type of one operand must be converted to match that of the other operand. When calling a function, the argument type must match the parameter type; if it doesn't, the argument is converted so its type matches. C++ has cast operators, which let you define a type conversion explicitly, or you can let the compiler automatically convert the type for you. This section presents the rules for automatic type conversion.
An arithmetic type is a fundamental integral or floating-point type: bool, char, signed char, unsigned char, int, short, long, unsigned int, unsigned short, unsigned long, float, double, or long double. Some operations are permitted only on arithmetic types, pointer types, enumerations, class types, or some combination of types. The description of each operator tells you which types the operator supports.
Type promotion is an automatic type conversion that applies only to arithmetic types, converting a "smaller" type to a "larger" type while preserving the original value. Contrast promotions with other automatic type conversions (described in later subsections), which can lose information. Promotions involve either integral or floating-point values. The rules for integral promotion are:
  • A "small" integral rvalue is converted to an int if the type int can represent all of the values of the source type; otherwise, it is converted to unsigned int. A "small" value is an integral bit-field (see Chapter 6) whose size is smaller than an int, or a value with one of the following types: char, signed char, unsigned char, short int, unsigned short
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Constant Expressions
A constant expression is an expression that can be evaluated at compile time. Constants of integral or enumerated type are required in several different situations, such as array bounds, enumerator values, and case labels. Null pointer constants are a special case of integral constants.
An integral constant expression is an expression that can be evaluated at compile time, and whose type is integral or an enumeration. The situations that require integral constant expressions include array bounds, enumerator values, case labels, bit-field sizes, static member initializers, and value template arguments. The compiler must be able to evaluate the expression at compile time, so you can use only literals, enumerators, const objects that have constant initializers, integral or enumerated template parameters, sizeof expressions, and constant addresses. The address of a static lvalue object is a constant address, as is the address of a function. A string literal, being a static array of characters, is also a constant address.
An integral static const data member can be initialized in the class definition if the initializer is a constant integral or enumerated expression. The member can then be used as a constant expression elsewhere in the class definition. For example:
template<typename T, size_t size>
class array {
public:
  static const size_t SIZE = size;
  ...
private:
  T data[SIZE];
};
See Chapter 6 for more information about static data members.
A constant expression with a value of 0 can be a null pointer constant . A null pointer constant can be converted to a null pointer value. The colloquial term null pointer almost always means null pointer value.
A null pointer value has an implementation-defined bit pattern. Many implementations use all zero bits, but some do not. Thus, the null pointer constant is not representative of the bits that make up a null pointer value, but serves only as a mnemonic for the programmer, much like
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Expression Evaluation
At its most fundamental level, the execution of a C++ program is the successive evaluation of expressions, under the control of statements (Chapter 4), in which some expressions can produce side effects. Any expression might have one or more of the following side effects:
  • Accessing a volatile object
  • Modifying an object
  • Calling a function in the standard library
  • Calling any other function that has side effects
During the execution of a program, there are well-defined points in time called sequence points, at which the side effects have all been completed for expressions that have been evaluated, and no side effects have been started for any unevaluated expression. Between sequence points, the compiler is free to reorder expressions in any way that preserves the original semantics. The same term also refers to the positions in the source code that produce sequence points when the code executes. You can usually ignore the details of sequence points, but when you are using global or volatile objects, it is important that you know exactly when it is safe to access those objects. That time is after a sequence point. Also, any expression that modifies a scalar object more than once between sequence points, or that examines a scalar object's value after modifying it, yields undefined behavior. This rule often bites the unwary programmer who uses the increment and decrement operators. For example:
int i = 0;
i = ++i - ++i;             // Error: undefined behavior
printf("%d,%d", ++i, ++i); // Error: undefined behavior
i = 3, ++i, i++;           // OK: i == 5
There are sequence points in the following positions:
  • At the end of every expression that is not a subexpression. Such an expression might be used in an expression statement, in an initializer, as a condition in an
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Expression Rules
C++ has the usual unary operators such as logical negation (!a), binary operators such as addition (a+b), and even a ternary operator (a?b:c). Unlike many other languages, an array subscript is also an operator (a[b]), and a function call is an n-ary operator (e.g., a(b, c, d)).
Every operator has a precedence . Operators with higher precedence are grouped so that they are logically evaluated before operators with lower precedence. (Note that precedence determines how the compiler parses the expression, not necessarily the actual order of computation. For example, in the expression a( ) + b( ) * c( ), the multiplication has higher precedence, but a( ) might be called first.)
Some operators group from left to right. For example, the expression x / y / z is equivalent to (x / y) / z. Other operators group right to left, as in x = y = z, which is equivalent to x = (y = z). The order of grouping is called the operator's associativity.
When reading C++ expressions, you must be aware of the precedence and associativity of the operators involved. For example, *ptr++ is read as *(ptr++) because the postfix ++ operator has higher precedence than the unary * operator.
Table 3-1 summarizes the syntax, precedence, and associativity of each kind of expression. The subsections that follow describe the kinds of expressions in depth; each subsection covers a single precedence group.
Table 3-1: Expression syntax and associativity, grouped by precedence
Group
Associativity
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 4: Statements
Statements define and control what a program does. This chapter describes the syntax and rules for C++ statements: expressions, loops, selection, and control. The statement syntax rules apply recursively, and wherever a statement is called for, you can use (almost) any of the statements in this chapter.
The syntax descriptions in this chapter are informal. See Chapter 12 for a precise BNF grammar.
An expression statement computes an expression, such as a function call or assignment. The expression result is discarded, so the expression is typically evaluated for its side effects. (See Chapter 3 for details about expressions.) The statement syntax is simply an optional expression followed by a semicolon:
            expr ;
or:
;
A statement with no expression is called a null statement . Null statements are most often used for loops when no code is needed in the loop body.
Here are several examples of expression statements:
42;          // Valid but pointless
cout << 42;  // More typical
x = y * z;   // Remember that assignment is an expression
;            // Null statement
A declaration can appear anywhere a statement appears, and certain statements permit additional declarations within those statements.
Declarations made in a substatement (of a selection or loop statement) are limited in scope to the substatement, even if the substatement is not a compound statement. For example, the following statement:
while ( test(  ) )
  int x = init(  );
is equivalent to:
while ( test(  ) ) {
  int x = init(  );
}
The first example uses a declaration as the entire loop body, and the second uses a compound statement (enclosing the loop body in curly braces). In both cases, though, the scope of x is limited to the body of the while loop.
A simple declaration can appear wherever a statement can be used. You can declare an object, a type, or a namespace alias. You can also write a
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Expression Statements
An expression statement computes an expression, such as a function call or assignment. The expression result is discarded, so the expression is typically evaluated for its side effects. (See Chapter 3 for details about expressions.) The statement syntax is simply an optional expression followed by a semicolon:
            expr ;
or:
;
A statement with no expression is called a null statement . Null statements are most often used for loops when no code is needed in the loop body.
Here are several examples of expression statements:
42;          // Valid but pointless
cout << 42;  // More typical
x = y * z;   // Remember that assignment is an expression
;            // Null statement
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Declarations
A declaration can appear anywhere a statement appears, and certain statements permit additional declarations within those statements.
Declarations made in a substatement (of a selection or loop statement) are limited in scope to the substatement, even if the substatement is not a compound statement. For example, the following statement:
while ( test(  ) )
  int x = init(  );
is equivalent to:
while ( test(  ) ) {
  int x = init(  );
}
The first example uses a declaration as the entire loop body, and the second uses a compound statement (enclosing the loop body in curly braces). In both cases, though, the scope of x is limited to the body of the while loop.
A simple declaration can appear wherever a statement can be used. You can declare an object, a type, or a namespace alias. You can also write a using declaration or using directive. You can declare a function, but not define a function, although there is rarely any reason to declare a function locally. You cannot define a namespace or declare a template.
In traditional C programming, declarations appear at the start of each block or compound statement. In C++ (and in the C99 standard), declarations can appear anywhere a statement can, which means you can declare variables close to where they are used. Example 4-1 shows examples of how declarations can be mixed with statements.
Example 4-1. Mixing declarations and statements