C++ Primer : By Stanley B. Lippman, Josée Lajoie
Chapter 4 : Expressions
The operations applied to the operands are represented by operators.
Operators that act on one operand are unary
operators, such as the address-of (&)
and dereference (*) operators, whereas
operators that act on two operands, such as the addition and subtraction
operators, are binary operators.
When two or more operators are combined, the expression is referred to as a compound expression.
The Standard C++ header file limits provides information about an implementation's representation of the built-in types, such as the minimum and maximum value that a type can represent. In addition, the Standard C header files climits and cfloat, also available within a C++ compilation system, define preprocessor macros that provide similar information.
If an implicit type conversion is not possible, the assignment is flagged as an error at compile-time.
The sizeof operator is evaluated at compile-time and so is considered a constant expression.
A comma expression is a series of expressions separated by commas. These expressions are evaluated from left to right. The result of a comma expression is the value of the rightmost expression.
In C++, multiplication and division have a higher precedence than addition. Multiplication and division have the same precedence, however, so that they are evaluated from left to right.
Precedence can be overridden by the use of parentheses, which mark subexpressions. Innermost parentheses are evaluated before outer pairs.
Chapter 5. Statements
The smallest independent unit in a C++ program is a statement. A compound statement is a sequence of simple statements surrounded by a pair of curly braces.
A goto statement may not jump forward over a declaration statement that is not enclosed within a statement block. A backward jump over an initialized object definition, however, is not illegal.
The goto statement is the most deprecated feature of modern programming languages.
Chapter 7. Functions
A function call can cause one of two things to happen. If the function has been declared inline, the body of the function may be expanded at the point of its call during compilation; if it is not declared inline, the function is invoked at run-time.
A function must be declared to the program before it is called; otherwise, a compile-time error results. A function can be defined only once in a program.
The declaration of a function consists of the function return type, the name of the function, and the parameter list. These three elements are referred to as the function declaration or function prototype. A function can be declared multiple times in a file.
A function type and the built-in array type cannot be specified as a return type. Rather, we must return a pointer to the element type contained within the array. The pointer addresses the first element of the array being returned. (It is the responsibility of the user handling the return value to know the size of the array.)
Class types and the container types, in contrast, can be returned directly.
A function must specify a return value; a declaration or definition without an explicit return value results in a compile-time error.
In C++, it is possible to have two functions with the same name and different parameter lists; the functions are then called overloaded functions.
C++ is a strongly typed language. The arguments of every function call are type-checked during compilation.
A reference must be initialized to an object, and, once initialized, it can never be made to refer to another object. A pointer can address a sequence of different objects or address no object at all.
Because a pointer can either address an object or address no object at all, a function cannot safely dereference a pointer before it first confirms that the pointer actually addresses an object.
With a reference parameter, on the other hand, the function does not need to guard against its referring to no object. A reference must refer to an object, even if we wish otherwise.
One important use of reference parameters is to allow us to implement overloaded operators efficiently while keeping their use intuitive.
Arrays in C++ are never passed by value. Rather, an array is passed as a pointer to its first — that is, zeroth — element. The array's size is not relevant to the declaration of the parameter.
When the parameter is a reference to an array type, the array size becomes part of the parameter and argument types, and the compiler checks that the size of the array argument matches the one specified in the function parameter type.
void putValues(
int (&arr)[10] );
A function declaration can specify default arguments for all or only a subset of its parameters. The rightmost uninitialized parameter must be supplied with a default argument before any default argument for a parameter to its left can be supplied.
A parameter can have its default argument specified only once in a file. A default argument does not have to be a constant expression. Any expression can be used and is evaluated at the time the function is called.
A linkage directive cannot appear within a function body.
Chapter
8. Scope and Lifetime
C++ supports three forms of scope: local scope, namespace scope, and class scope. The outermost namespace scope of a program is called global scope or global namespace scope.
Name resolution is the process by which a name used in an expression is associated with a declaration.
An object defined in global scope without an explicit initializer is guaranteed to have its storage initialized to 0.
The keyword extern provides a method for declaring an object without defining it. It can appear multiple times within the same file or within different files of the same program.
The declaration of a global object that specifies both the extern keyword and an explicit initializer is treated as a definition of that object.
The extern keyword can also be specified with a function declaration. Its only effect is to make the implicit "defined elsewhere" nature of the declaration explicit.
In C++, there exists a mechanism by which the type and number of parameters of a function are encoded in the function name. This mechanism is called type-safe linkage. Type-safe linkage helps the implementation catch mismatches between function declarations in different files.
There are three kinds of local objects: automatic objects, register objects, and local static objects.
The storage in which an automatic object resides lasts from the time the function in which it is declared is invoked to the time the function exits. A register object is an automatic object for which fast read and store of the object's value are supported. A local static object resides in storage that lasts for the entire duration of the program.
Array indexes and pointers occurring within a loop are good candidates for register objects.
Using the keyword register is only a hint to the compiler. Some compilers may ignore this hint and use register allocation algorithms to figure out the best candidates to be placed within the available machine registers.
An uninitialized static local object is automatically initialized to 0 by the program. In contrast, automatic objects have arbitrary values unless they are explicitly initialized.
If the operator new() called by the new expression cannot acquire the requested memory, in general it throws an exception called bad_alloc.
An array allocated on the free store cannot be given an initial set of values. An array of built-in type created on the free store must be initialized within a for loop in which the array elements are initialized one by one.
It is not possible to create a const array of elements of built-in type on the free store for the simple reason that it is not possible to initialize the elements of an array of built-in type created with a new expression. All objects created const on the free store must be initialized, and because a const array cannot be initialized (except for array of classes), attempting to create a const array of built-in type with a new expression results in a compile-time error.
Form of new expression in which the programmer can request that the object be created in memory that is already allocated called a placement new expression. There is no delete expression to match a placement new expression.
Chapter
9. Overloaded Functions
Two functions are overloaded if they have the same name, are declared in the same scope, and have different parameter lists. Function overloading allows multiple functions that provide a common operation on different parameter types to share a common name.
· If the parameter lists of the two functions differ in either the number or type of their parameters, the two functions are considered to be overloaded.
· If both the return type and the parameter list of the two function declarations match exactly, the second declaration is treated as a redeclaration of the first. The parameter names are irrelevant when parameter lists are compared.
· If the parameter lists of the two functions match exactly but the return types differ, the second declaration is treated as an erroneous redeclaration of the first and is flagged at compile-time as an error. A function's return type is not enough to distinguish between two overloaded functions.
· If the parameter lists of the two functions differ only in their default arguments, the second declaration is treated as a redeclaration of the first.
When a parameter type is const or volatile, the const or volatile qualifier is not taken into account when the declarations of different functions are identified.
However, if const or volatile applies to the type to which a pointer or reference parameter refers, then the const or volatile qualifier is taken into account when the declarations of different functions are identified.
The functions that are members of two distinct namespaces do not overload one another.
The functions introduced by the using declaration overload the other declarations of the functions with the same name already present in the scope where the using declaration appears. If the using declaration introduces a function in a scope that already has a function of the same name with the same parameter list, then the using declaration is in error.
The steps of function overload resolution are the following:
1. Identify the set of overloaded functions considered for the call and identify the properties of the argument list in the function call. The functions in this set are called candidate functions.
2. Select the functions from the set of overloaded functions that can be called with the arguments specified in the call, given the number of arguments and their types. The functions thus selected are called the viable functions.
3.
Select the function that best matches the call.
This function is called the best viable function.
The best viable function is the function for which the following apply.
a. The conversions applied to the arguments are no worse than the conversions necessary to call any other viable function.
b.
The conversions on some arguments are better
than the conversions necessary for the same arguments when calling the other
viable functions.
If the third step of function overload resolution finds no best viable
function, then the function call is ambiguous;
The possible conversions can be grouped into three categories: promotions, standard conversions, and user-defined conversions.
Type conversions are ranked as follows: an exact match is better than a promotion, a promotion is better than a standard conversion, and a standard conversion is better than a user-defined conversion.
The possible conversions in the exact match category are the following conversions:
1. Lvalue-to-rvalue conversion
2. Array-to-pointer conversion
3. Function-to-pointer conversion
4.
Qualification conversions
A qualification conversion affects only pointers. It
is a conversion that adds const
or volatile qualifiers
(or both) to the type to which a pointer points.
The first three conversions in the exact match category (lvalue-to-rvalue, array-to-pointer, and function-to-pointer conversions) are often referred to as lvalue transformations.
Even though lvalue transformations and qualification conversions are in the exact match category, an exact match in which only an lvalue transformation is needed is ranked as better than an exact match requiring a qualification conversion.
An exact match can be forced by the use of an explicit cast.
Details of a
Promotion
A promotion is one of the following conversions.
· An argument of type char, unsigned char, or short is promoted to type int. An argument of type unsigned short is promoted to type int if the machine size of an int is larger than that of a short integer; otherwise, it is promoted to type unsigned int.
· An argument of type float is promoted to type double
· An argument of an enumeration type is promoted to the first of the following type that can represent all the values of the enumeration constants: int, unsigned int, long, or unsigned long.
· An argument of type bool is promoted to type int.
Details of a Standard
Conversion
There are five kinds of conversions grouped in the category of standard conversion:
1. The integral conversions: the conversions from any integral type or enumeration type to any other integral type (excluding the conversions that were listed as promotions earlier).
2. The floating point conversions: the conversions from any floating point type to any other floating point type (excluding the conversions that were listed as promotions earlier).
3. The floating-integral conversions: the conversions from any floating point type to any integral type or from any integral type to any floating point type.
4. The pointer conversions: the conversion of the integer value zero to a pointer type and the conversion of a pointer of any type to the type void*.
5. The bool conversions: the conversions from any integral type, floating point type, enumeration type, or pointer type to the type bool.
The conversions that are grouped in categories 1, 2, and 3 are potentially dangerous conversions, because the target type of the conversion cannot represent all the values that the source type can represent.
All standard conversions are treated as requiring equal work. Closeness of type is not considered. If two viable functions require standard conversions on the argument to match the type of their parameter, the call is ambiguous and it is flagged at compile-time as an error.
Only pointers to data types can be converted to the type void* with a pointer standard conversion. Pointers to functions cannot be converted to the type void* with a standard conversion.
References
The outcome of a match of an argument with a reference parameter is one of the following possibilities.
The argument is an appropriate initializer for the reference parameter. In this case, we say that the argument is an exact match for the parameter.
The argument cannot initialize the reference parameter. In this case, there is a no match situation and the argument cannot be used to call the function.
Both an lvalue-to-rvalue conversion and a reference initialization are considered to be exact matches.
Details of Function
Overload Resolution
Candidate Functions
A candidate function is a function that has the same name as the function called. A candidate function will be found in one of the following two ways.
1. A declaration for the function is visible at the point of the call.
2. If the type of a function argument is declared within a namespace, the namespace member functions that have the same name as the function called are added to the set of candidate functions.
Viable Functions
A viable function is a function in the set of candidate functions. A viable function is a function for which there exist conversions to convert each argument to the type of the corresponding parameter in the viable function parameter list.
Chapter 10. Function
Templates
Function templates provide a mechanism by which we can preserve the semantics of function definitions and function calls without having to bypass C++'s strong type-checking as is done with the macro solution.
The keyword template always begins both a definition and a declaration of a function template. The keyword is followed by a comma-separated list of template parameters bracketed by the less-than (<) and greater-than (>) tokens. This list is the template parameter list. It cannot be empty. A template parameter can be a template type parameter representing a type or a template nontype parameter representing a constant expression.
A template type parameter consists of the keyword class or the keyword typename followed by an identifier. In a function template parameter list, these keywords have the same meaning. They indicate that the parameter name that follows represents a potential built-in or user-defined type.
If an object, function, or type having the same name as the template parameter is declared in global scope, the global scope name is hidden.
An object or type declared within the function template definition cannot have the same name as that of a template parameter.
The name of a template type parameter can be used to specify the return type of the function template.
The name of a template parameter can be used only once within the same template parameter list.
However, the name of a template parameter can be reused across function template declarations or definitions.
The names of the template parameters do not need to be the same across declarations and the definition of the template.
There is no constraint on how many times a template parameter can appear in the function parameter list.
If a function template has more than one template type parameter, each template type parameter must be preceded by the keyword class or the keyword typename.
A function template can be declared inline or extern in the same way as a nontemplate function.
Chapter 13: Classes
A name used within a class definition (except in inline member function definitions and default arguments) is resolved as follows:
1. The declarations of the class members that appear before the use of the name are considered.
2. If the resolution in step 1 is not successful, the declarations that appear in namespace scope before the class definition are considered.
A name used within the definition of a class member function is resolved as follows:
1. Declarations in the member function local scopes are considered first.
2. If the resolution in step 1 is not successful, the declarations for all the class members are considered.
3. If the resolution in step 2 is not successful, the declarations that appear in namespace scope before the member function definition are considered
An enclosing class has no access privileges to the private members of a nested class unless it is declared as a friend of the nested class. Nor does the nested class have any special access privileges to the private members of its enclosing class.
A nested class can also be defined outside its enclosing class.
A name used within a nested class definition.
1. The declarations of the members of the nested class that appear before the use of the name are considered.
2. If the resolution in step 1 is not successful, the declarations of the members of the enclosing class that appear before the use of the name are considered.
3. If the resolution in step 2 is not successful, the declarations that appear in namespace scope before the nested class definition are considered.
Because there is no syntax to define the members of a local class in a namespace scope, a local class is not permitted to declare static data members.
Chapter
14. Class Initialization,
Assignment, and Destruction
A mechanism inherited from the C language supports an explicit initialization list similar to that used to initialize an array. The values are resolved positionally based on the declaration order of the data members.
Two primary drawbacks to the explicit initialization list are that it can only be applied to class objects for which all the data members are public and that it requires the explicit intervention of the programmer, adding to the possibility of accident or error.
If a class declares a constructor taking one or more parameters, but does not declare a default constructor, every class object definition must provide the required arguments.
The container classes (such as vector, for example) require their class elements to provide either a default constructor or no constructor at all. Similarly, allocation of a dynamic array of class objects also requires either a default constructor or no constructor at all. In practice it is almost always necessary to provide a default constructor if other constructors are being defined as well.
A constructor may not be declared with either the const or volatile keyword.
The explicit modifier informs the compiler not to provide implicit conversions. explicit can only be applied to a constructor.
The compiler first checks to see whether a default constructor for the Account class is defined. One of the following occurs:
1. The default constructor is defined. It is applied to acct.
2. The default constructor is defined, but it is nonpublic. The definition of acct is flagged at compile-time as an error: main() has no access privilege.
3. No default constructor is defined, but one or more constructors requiring arguments is defined. The definition of acct is flagged at compile-time as an error: too few constructor arguments.
4. No default constructor is defined, nor any other constructor. The definition is legal. acct is uninitialized and no constructor is invoked.
New users often mistakenly believe that the compiler generates and applies a default constructor automatically if one is not present-initializing the class data members. No default constructor is generated nor is one invoked. For more complex classes containing class data members or making use of inheritance, this is partially true: A default constructor may be generated, but it does not provide initial values for data members of the built-in or compound types, such as pointers and arrays.
If we want class data members of the built-in and compound types to be initialized, we must do so explicitly in one or a set of constructors. Without doing so, it is next to impossible to distinguish between a valid and uninitialized value associated with the data members of the built-in and compound types of local and dynamically allocated class objects.
The predominant uses of nonpublic constructors in real-world C++ programs are
1. to prevent the copying of one class object with another object of its class (discussed in the following subsection) and
2. to indicate that a constructor is intended to be invoked only when the class serves as a base class within an inheritance hierarchy and not as an object to be manipulated directly within the application
Because destructor cannot specify any parameters, it cannot be overloaded.
A destructor is not invoked when either a reference or a pointer to a class object goes out of scope.
In some program situations it is necessary to invoke the destructor explicitly on a particular class object. This comes up most often in conjunction with placement operator new.
An inline destructor can be an unsuspected source of program code bloat because it is inserted at each exit point within a function for each active local class object.
There is no way to provide a set of explicit values with which to initialize the array elements of an array of class objects allocated on the heap.
By default, then, the initialization of an array of class objects allocated on the heap requires two steps: (1) the actual allocation of the array, in which the default constructor, if defined, is applied to each element and (2) the subsequent assignment of each element to a specific value (can make use of placement operator new).
Chapter
15. Overloaded Operators and
User-Defined Conversions
The following four C++ operators cannot be overloaded: :: .* . ?:
The predefined meaning of an operator for the built-in types may not be changed. For example, the built-in integer addition operation cannot be replaced with an operation that checks for overflow.
// error: cannot redefine built-in operator for ints
int operator+( int, int );
Nor may additional operators be defined for the built-in data types.
The predefined precedence of the operators cannot be overridden.
The predefined arity of the operator must be preserved.
For built-in types, four predefined operators ("+", "-", "*", and "&") serve as both unary and binary operators. Either or both arities of these operators can be overloaded.
How do we decide, then, whether an operator that is not a class member should be made a friend or whether it should use the access member functions? In general, a class implementor should try to minimize the number of namespace functions and operators that have access to the internal representation of a class. If access member functions are provided and they are equally efficient, then it is preferable to use them and isolate the namespace operators from the changes in the class representation, just as it is done with other namespace functions. If the class implementor decides not to provide access member functions for some of the class private members, and if the namespace operators need to refer to these private members to perform their operations, then the use of the friend mechanism becomes necessary.
The return type of the assignment operator is a reference to the String class. Why would we declare this assignment operator to return a reference?
For built-in types, assignment operators can be chained together as follows:
// chain of assignment operators
int iobj, jobj;
iobj = jobj = 63;
We would like to preserve this behavior for assignments to objects of our String class, such that the following, for example, is supported:
String verb, noun;
verb = noun = "count";
No comments:
Post a Comment