《C++ Primer第五版》读书笔记(2)--变量与基本类型

1      getting started

   标准库的崇高地位

standard libraryCollection of types and functions thatevery C++ compiler must support. C++ programmers tend to talk about “the library” meaning the entire standard library. They also tend to refer to particular parts of the library by referring to a library type, such as the “iostream library” meaning the part of the standard library that defines the IO classes.

  C++类的崇高地位(不是继承和多态):

Perhaps the most important feature in C++ is the class, which lets programmers define their own types. In C++ such types are sometimes called “class types” to distinguish them from the types that are built into the language.

  C++是静态类型语言

Some languages, such as Smalltalk and Python, check types at run time. In contrast,C++ is a statically typed language; type checking is done at compile time. As a consequence, the compiler must know the type of every name used in the program.

  鼓励使用标准库来提高编程效率

C++ has matured greatly: Its focus, and that of its programming community, has widened from looking mostly at machine efficiency to devoting more attention to programmer efficiency.

Too often, the library is taught as an “advanced” topic. Instead of using the library, many books use low-level programming techniques based on pointers to character arrays and dynamic memory management. Getting programs that use these low-level techniques to work correctly is much harder than writing the corresponding C++ code using the library.

2      Variables and Basic Types

2.1     Primitive BUILD-IN TYPES

Types are fundamental to any program: They tell us what our data mean and what
operations we can perform on those data.

built-in typeType, such as int, defined by the language.

class typeA type defined by a class. The name of the type is the class name.

 

The standard guarantees minimum sizes as listed in Table 2.1.  However, compilers are allowed to use larger sizes for these types.


Table 2.1

Use double for floating-point computations; float usually does not have enough precision, and the cost of double-precision calculations versus single-precision is negligible. In fact, on some machines, double-precision operations are faster than single. The precision offered by long double usually is unnecessary and often entails considerable run-time cost.

 

Literals

Two string literals that appear adjacent to one another and that are separated only by spaces, tabs, or newlines are concatenated into a single literal.We use this form of literal when we need to write a literal that would otherwise be too large to fit comfortably on a single line:

std::cout << "a really, really long string literal "

         "that spans two lines" << std::endl;

2.2     Variable

2.2.1        List initialization

When used with variables of built-in type, List initialization has one important property: The compiler will not let us list initialize variables of built-in type if the initializer might lead to the loss of information:

long double ld = 3.1415926536;
int a{ld}, b = {ld}; // error: narrowing conversion required
int c(ld), d = ld;  // ok: but value will be truncated

2.2.2        Variable Declarations and Definitions

A declaration makes a name known to the program. A file that wants to use a name defined elsewhere includes a declaration for that name.A definition creates the associated entity.

extern int i;  // declares but does not define i
int j;  // declares and defines j

2.3     Compound Types--reference&Pointer

We said that simple declarations consist of a type followed by a list of variable names. More generally, a declaration is a base type followed by a list of declarators. Each declarator names a variable and gives the variable a type that is related to the base type.

The declarations we have seen so far have declarators that are nothing more than variable names. The type of such variables is the base type of the declaration. More complicated declarators specify variables with compound types that are built from the base type of the declaration.

base type: type specifier, possibly qualified by const(即qualifier), that precedes thedeclarators in a declaration.

 

2.3.1        Reference

A Reference Is an Alias. A reference is not an object. Instead, a reference is just another name for an already existing object. Because references are not objects, we may not define a reference to a reference.

With two exceptions, the type of a reference and the object to which the reference refers must match exactly. Moreover, for reasons we’ll explore in § 2.4.1, a reference may be bound only to an object, not to a literal or to the result of a more general expression:

int &refVal2;  // error: a reference must be initialized
int &refVal4 = 10;  // error: initializer must be an object
double dval = 3.14;
int &refVal5 = dval; // error: initializer must be an int object

2.3.2        Pointer

A pointer is a compound type that “points to” another type. Like references, pointers are used for indirect access to other objects. Unlike a reference, a pointer is an object in its own right.
When we use a preprocessor variable, the preprocessor automatically replaces the variable by its value. Hence, initializing a pointer to NULL is equivalent to initializing it to 0.Modern C++ programs generally should avoid using NULL and use nullptr instead.
int *p1 = nullptr;

2.3.3        References to Pointers

A reference is not an object. Hence, we may not have a pointer to a reference. However, because a pointer is an object, we can define a reference to a pointer:

int i = 42;
int *p;  // p is a pointer to int
int *&r = p;  // r is a reference to the pointer p
r = &i; //  r refers to a pointer; assigning &i to r makes p point to i
*r = 0; //  dereferencing r yields i, the object to which p points; changes i to 0

The easiest way to understand the type of  r  is to read the definition right to left.

2.4     const Qualifier

2.4.1        basic concept

const int i = get_size();  // ok: initialized at run time
const int j = 42;  // ok: initialized at compile time
const int k;  // error: k is uninitialized const
const int bufSize = 512;  // input buffer size

By Default, const Objects Are Local to a File。Sometimes we have a const variable that we want to share across multiple files. Sometimeswe have a constvariable that we want to share across multiple files but whose initializer is not a constant expression. In this case, we don’t want the compiler to generate a separate variable in each file. Instead, we want the const object to behave like other (nonconst) variables. We want to define the constin one file, and declare it in the other files that use that object.
To define a single instance of a const variable, we use the keyword extern on both its definition and declaration(s):
// 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

2.4.2        References to const

we use a reference to const, which is a reference that refers to a const type.

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

Because we cannot assign directly to ci, we also should not be able to use a reference to change ci. Therefore, the initialization of r2is an error. If this initialization were legal, we could use r2 to change the value of its underlying object.

C++ programmers tend to abbreviate the phrase “reference to const” a “const reference.” This abbreviation makes sense—if you remember that it is an abbreviation. Technically speaking, there are no const references. A reference is not an object, so we cannot make a reference itself const.

There are two exceptions to the rule that the type of  a reference must match the type of the object to which it refers. The first exception is that we can initialize a reference to const from any expression that can be converted to the type of the reference. In particular, we can bind a reference to const to a nonconst object, a literal, or a more general expression:

A Reference to const may Refer to an Object That Is Not const.It is important to realize that a reference to const restricts only what we can do through that reference. Binding a reference to const to an object says nothing about whether the underlying object itself is const.

个人用的最多的是,在定义函数的时候使用function(const class x&)来指示不会改变传入对象。

2.4.3     Pointers and const

2.4.3.1    A point to const

As with references, we can define pointers that point to either const or nonconst types. Like a reference to const, a pointer to const(§ 2.4.1, p. 61) may not be used to change the object to which the pointer points.

Like a reference to const, a pointer to const says nothing about whether the object to which the pointer points is const. Defining a pointer as a pointer to const affects only what we can do with the pointer.

const double pi = 3.14;  // pi is const; its value may not be changed
const double *cptr = &pi; // ok: cptr may point to a double that is const

个人用的最多的是,在定义函数的时候使用function(const class x *)来指示不会改变传入对象。

2.4.3.2    const Pointers

Unlike references, pointers are objects. Hence, as with any other object type, we can have a pointer that is itself const. Like any other constobject, a const pointer must be initialized, and once initialized, its value (i.e., the address that it holds) may not be changed. We indicate that the pointer is constby putting the constafter the *. This placement indicates that it is the pointer, not the pointed-to type, that is const:

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

the easiest way to understand these declarations is to read them from right to left. In this case, the symbol closest to curErr is const, which means that curErr itself will be a const object. The type of that object is formed from the rest of the declarator. The next symbol in the declarator is *, which means that curErr is a const pointer. Finally, the base type of the declaration completes the type of curErr, which is a const pointer to an object of type int. Similarly, pip is a const pointer to an object of type const double.

The fact that a pointer is itself const says nothing about whether we can use the pointer to change the underlying object. Whether we can change that object depends entirely on the type to which the pointer points.

Question 1:笔者迄今还没有发现这个const point的实际用途是啥?请高人指教.

2.4.3.3 Top-level const

We use the term top-level const to indicate that the pointer itself is a const. When a pointer can point to a const object, we refer to that const as a low-level const.

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

The distinction between top-level and low-level matters when we copy an object. When we copy an object, top-level consts are ignored:

i= ci;  // ok: copying the value of ci; top-level const in ci is ignored
p2 = p3; // ok: pointed-to type matches; top-level const in p3 is ignored

Copying an object doesn’t change the copied object. As a result, it is immaterial whether the object copied from or copied into is const.

On the other hand, low-level const is never ignored. When we copy an object, both objects must have the same low-level const qualification or there must be a conversion between the types of the two objects.In general, we can convert a nonconst to const but not the other way round.

2.4.3.4    constexpr and Constant Expressions

A constant expression is an expression whose value cannot change and that can be evaluated at compile time.

To define a single instance of a const variable, we use the keyword extern on both its definition and declaration(s):

// 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

In a large system, it can be difficult to determine (for certain) that an initializer is a constant expression. Under the new standard, we can ask the compiler to verify that a variable is a constant expression by declaring the variable in a constexpr declaration. Variables declared as constexpr are implicitly const and must be initialized by constant expressions:

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

Generally, it is a good idea to use constexpr for variables that you intend to use as constant expressions.

const int *p = nullptr;  // p is a pointer to a const int
constexpr int *q = nullptr; // q is a const pointer to int

Despite appearances, the types of p and q are quite different; p is a pointer to const , whereas q is a constant pointer.

2.5     Dealing with Types

2.5.1        Type aliases

We can define a type alias in one of two ways. Traditionally, we use a typedef

The new standard introduced a second way to define a type alias, via an alias declaration:

using SI = Sales_item;  // SI is a synonym for Sales_item

 

Pointers, const, and Type Aliases:
typedef char *pstring;
const pstring cstr = 0; // cstr is a constant pointer to char
const pstring *ps;  // ps is a pointer to a constant pointer to char

The base type in these declarations is const pstring. As usual, a const that appears in the base type modifies the given type. The type of pstring is “pointer to char.” So, const pstring is a constant pointer to char—not a pointer to const char.

const char *cstr = 0; // wrong interpretation of const pstring cstr

this interpretation is wrong. When we use pstring in a declaration, the base type of the declaration is a pointer type. When we rewrite the declaration using char*, the base type is char and the *is part of the declarator. In this case, const char is the base type. This rewrite declares cstr as a pointer to const char rather than as a const pointer to char.

2.5.2        The auto type specifier

Unlike type specifiers, such as double, that name a specific type, auto tells the compiler to deduce the type from the initializer. By implication, a variable that uses auto as its type specifier must have an 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

The compiler will deduce the type of item from the type returned by applying + to val1and val2. If val1and val2are Sales_item objects , item will have type Sales_item. If those variables are type double, then item has type double, and so on.

The type that the compiler infers for auto is not always exactly the same as the initializer’s type. Instead, the compiler adjusts the type to conform to normal initialization rules.First, as we’ve seen, when we use a reference, we are really using the object to which the reference refers. In particular, when we use a reference as an initializer, the initializer is the corresponding object. The compiler uses that object’s type for auto’s type deduction:

int i = 0, &r = i;
auto a = r;  // a is an int (r is an alias for i, which has type int)

Second, auto ordinarily ignores top-level consts. As usual in initializations, low-level consts, such as when an initializer is a pointer to const, are kept:

const int ci = i, &cr = ci;
auto b = ci;  // b is an int (top-level const in ci is dropped)
auto c = cr;  // c is an int (cr is an alias for ci whose const is top-level)
auto d = &i;  // d is an int*(& of an int object is int*)
auto e = &ci;  // e is const int*(& of a const object is low-level const)

If we want the deduced type to have a top-level const, we must say so explicitly:

const auto f = ci; // deduced type of ci is int; f has type const int

We can also specify that we want a reference to the auto-deduced type. Normal initialization rules still apply:

auto &g = ci;  // g is a const int& that is bound to ci
    auto &h = 42;  // error: we can‘t bind a plain reference to a literal
    const auto &j = 42; // ok: we can bind a const reference to a literal

When we ask for a reference to an auto-deduced type, top-level consts in the initializer are not ignored. As usual, consts are not top-level when we bind a reference to an initializer.

2.5.3     The decltype Type Specifier

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

The way decltype handles top-level const and references differs subtly from the wayauto does. When the expression to which we apply decltype is a variable, decltype returns the type of that variable, including top-level const and references:

const int ci = 0, &cj = ci;
decltype(ci)  x = 0; // x has type const int
decltype(cj) y = x; // y has type const int& and is bound to x
decltype(cj) z;  // error: z is a reference and must be initialized

It is worth noting that decltype is the only context in which a variable defined as a reference is not treated as a synonym for the object to which it refers.

When we apply decltype to an expression that is not a variable, we get the type that that expression yields.some expressions will cause decltype to yield a reference type.Generally speaking, decltype returns a reference type for expressions that yield objects that can stand on the left-hand side of the assignment:

// decltype of an expression can be a reference type

int i = 42, *p = &i, &r = i;
decltype(r + 0) b;  // ok: addition yields an int; b is an (uninitialized) int
decltype(*p) c;  // error: c is int& and must be initialized: 注意如果是*p则是引用,如果是p则是指针

// decltype of a parenthesized variable is always a reference
decltype((i)) d;  // error: d is int& and must be initialized
decltype(i) e;  // ok: e is an (uninitialized) int

Remember that decltype((variable))(note, double parentheses) is always a reference type, but decltype(variable) is a reference type only if variable is a reference.

2.6     Defining Our Own Data Structures

2.6.1        in-class initializer

Under the new standard, we can supply an in-class initializer for a data member.

In-class initializers are restricted as to the form we can use: They must either be enclosed inside curly braces or follow an =sign.

2.6.2        Writing Our Own Header Files

Whenever a header is updated, the source files that use that header must be recompiled to get the new or changed declarations.

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。