欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C++ Primer Chapter 2. Variables and Basic Types

程序员文章站 2024-02-29 18:18:04
...

Chapter 2. Variables and Basic Types

What is type ?

C++ type features?

  • defines several primitive types/built-in types? (characters, integers, floating-point numbers, etc.)

  • provides mechanisms that let us define our own data types.

    so The library uses these mechanisms to define more complicated types such as variable-length character strings, vectors, and so on.

Primitive Built-in types

Also called primitive types.

What do primitive types include?

  • arithmetic types, void

what’s included in arithmetic types ?

  • characters, integers, boolean values, and floating-point numbers.
    what is void?

  • void type has no associated values and can be used in only a few circumstances, most commonly as the return type for functions that do not return a value.

Arithmetic Types

what is machine byte/ byte, word?

  • The C and C++ programming languages define byte as an “addressable unit of data storage large enough to hold any member of the basic character set of the execution environment” commonly.
    –historically, the byte was the number of bits used to encode a single character of text in a computer and for this reason it is the smallest addressable unit of memory in many computer architectures. Byte commonly contains 8bits (ASII, 128 characters).

  • The basic unit of storage, usually a small number of bytes, is referred to as a “word.” On most machines a word is either 32 or 64 bits, that is, 4 or 8 bytes.

    In computing, a word is the natural unit of data used by a particular processor design. A word is a fixed-sized piece of data handled as a unit by the instruction set or the hardware of the processor. The number of bits in a word (the word size, word width, or word length) is an important characteristic of any specific processor design or computer architecture.

    The size of a word is reflected in many aspects of a computer’s structure and operation; the majority of the registers in a processor are usually word sized and the largest piece of data that can be transferred to and from the working memory in a single operation is a word in many (not all) architectures. The largest possible address size, used to designate a location in memory, is typically a hardware word (here, “hardware word” means the full-sized natural word of the processor, as opposed to any other definition used).

How is data stored? how to know the meaning of a memory at a given address?

  • Most computers associate a number (called an “address”) with each byte in memory. On a machine with 8-bit bytes and 32-bit words, we might view a word of memory as follows[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AF0K8ct9-1605624442286)(C:\Users\SDA10\AppData\Roaming\Typora\typora-user-images\image-20201111154418037.png)]

  • We can use an address to refer to any of several variously sized collections of bits starting at that address. To give meaning to memory at a given address, we must know the type of the value stored there. The type determines how many bits are used and how to interpret those bits.

    ex: If the object at location 736424 has type float and if floats on this machine are stored in 32 bits, then we know that the object at that address spans the entire word. The value of that float depends on the details of how the machine stores floating-point numbers. Alternatively, if the object at location 736424 is an unsigned char on a machine using the ISO-Latin-1 character set, then the byte at that address represents a semicolon.

The arithmetic types are divided into which two categories?

  • integral types (characters, integers, boolean values), floating-point types( floating-point numbers).

The size of arithmetic types are/ Which types are they?

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eDACJuT0-1605624442288)(C:\Users\SDA10\AppData\Roaming\Typora\typora-user-images\image-20201111152840457.png)]

NB The standard specific the range but not how to present arithmetic types. So this part doesn’t state how this types are realized in detailed (float, unsigned etc.) but point out the range they should achieve. But for simply unsigned type, all the bits represent the value. For example, an 8-bit unsigned char can hold the values from 0 through 255 inclusive.

Bool?

  • not described in the book, todo

Why char 8 bits? Why there are several character types?

  • Byte commonly contains 8bits (ASII, 128 characters).
  • The character types other than char are used for extended character sets. The wchar_t type is guaranteed to be large enough to hold any character in the machine’s largest extended character set. The types char16_t and char32_t are intended for Unicode characters. (Unicode is a standard for representing characters used in essentially any natural language.)

How does C++ define integer types?

  • The language guarantees that an int will be at least as large as short, a long at least as large as an int, and long long at least as large as long. The type long long was introduced by the new standard.

How does C++ define floating-point types?

  • The standard specifies a minimum number of significant digits. But most compilers provide more precision than the specified minimum. Typically, floats are represented in one word (32 bits), doubles in two words (64 bits), and long doubles in either three or four words (96 or 128 bits). The float and double types typically yield about 7 and 16 significant digits, respectively.

    The type long double is often used as a way to accommodate special-purpose floating-point hardware; its precision is more likely to vary from one implementation to another.

which types can be signed or unsigned? syntax?

  • Except for bool and the extended character types, the integral types may be signed or unsigned. The types int, short, long, and long long are all signed by default. We obtain the corresponding unsigned type by adding unsigned to the type, such as unsigned long.
  • But unlike the other integer types, there are three distinct basic character types: char, signed char, and unsigned char. In particular, char is not the same type as signed char. Although there are three character types, there are only two representations: signed and unsigned. The (plain) char type uses one of these representations. Which of the other two character representations is equivalent to char depends on the compiler.

How does C++ define signed types?

  • The standard does not define how signed types are represented, but does specify that the range should be evenly divided between positive and negative values. Hence, an 8-bit signed char is guaranteed to be able to hold values from –127 through 127; most modern machines use representations that allow values from –128 through 127. Others: todo.

Can floating-point types be signed or unsigned?

  • todo

Rules of thumb can be useful in deciding which type to use,

C++, like C, is designed to let programs get close to the hardware when necessary. The arithmetic types are defined to cater to the peculiarities of various kinds of hardware. Accordingly, the number of arithmetic types in C++ can be bewildering. Most programmers can (and should) ignore these complexities by restricting the types they use.

  • Use an unsigned type when you know that the values cannot be negative.
  • Use int for integer arithmetic. short is usually too small and, in practice, long often has the same size as int. If your data values are larger than the minimum guaranteed size of an int, then use long long.
  • Do not use plain char or bool in arithmetic expressions. Use them only to hold characters or truth values. Computations using char are especially problematic because char is signed on some machines and unsigned on others. If you need a tiny integer, explicitly specify either signed char or
    unsigned char.
  • 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.

Type Conversions

—Convert objects of the given type to other related types.

Do we need to intentionally convert types?

  • No. Type conversions happen automatically when we use an object of one type where an object of another type is expected.

What’s the conversion principle?

  • assign one non-bool arithmetic type to a bool object, false if the value is 0 and true otherwise.

  • assign a bool to one other arithmetic type, value is 1 if true and 0 if false.

  • assign a floating-point value to an object of integral type, the value is truncated: the part before the decimal point.

  • assign an integral value to an object of floating-point type, the fractional part is zero. Precision may be lost if the integer has more bits than the floating-point object can accommodate.

  • If we assign an out-of-range value to an object of unsigned type, the result is the remainder of the value modulo the number of values the target type can hold. For example, an 8-bit unsigned char can hold values from 0 through 255, inclusive. If we assign a value outside this range, the compiler assigns the remainder of that value modulo 256. Therefore, assigning –1 to an 8-bit unsigned char gives that object the value 255.

    Note: Given two positive integers, the dividend a and the divisor n, a modulo N (abbreviated as A mod N) yields the remainder of a/n when using Euclidean division as follows.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EiMKhfzq-1605624442289)(C:\Users\SDA10\AppData\Roaming\Typora\typora-user-images\image-20201113133717986.png)]

  • assign an out-of-range value to an object of signed type, the result is undefined. The program might appear to work, it might crash, or it might produce garbage values.

  • y for last two? -todo

  • p2 = &i; // ok: we can convert int* to const int*

What is the Undefined and Implementation-Defined Behavior, how to avoid them?

  • Undefined behavior results from errors that the compiler is not required (and sometimes is not able) to detect. Even if the code compiles, a program that executes an undefined expression is in error. Unfortunately, programs that contain undefined behavior can appear to execute correctly in some circumstances and/or on some compilers. There is no guarantee that the same program, compiled under a different compiler or even a subsequent release of the same compiler, will continue to run correctly. Nor is there any guarantee that what works with one set of inputs will work with another.
  • Similarly, programs usually should avoid implementation-defined behavior, such as assuming that the size of an int is a fixed and known value. Such programs are said to be nonportable. When the program is moved to another machine, code that relied on implementation-defined behavior may fail. Tracking down these sorts of problems in previously working programs is, mildly put, unpleasant.

Expressions conversion rules?

  • A bool in an arithmetic expression ->0 or 1.
  • Signed values are automatically converted to unsigned when these two are mixed. For example, in an expression like a * b, if a is -1 and b is 1, then if both a and b are ints, the value is, as expected -1. However, if a is int and b is an unsigned, then the value of this expression depends on how many bits an int has on the particular machine. On our machine, this expression yields 4294967295.

Literals

What is the literal?

  • A value such as a number, a character, or a string of characters. The value cannot be changed.
  • integer literals, floating-point literals, Character and Character String Literals
  • Key: its value is self-evident.
  • literal is not a object

Which three different types compose integral literals/integer literals ? How to write them in C++ ?

  • decimal, octal, or hexadecimal notation.

  • Integer literals that begin with 0 (zero) are interpreted as octal. Those that begin with either 0x or 0X are interpreted as hexadecimal.

    20 /* decimal */ 024 /* octal */ 0x14 /* hexadecimal */
    

How to write floating-point literals in C++ ?

  • use either the decimal point or exponent specified using scientific notation: the exponent is indicated by either E or e
3.14159 3.14159E0 0. 0e0 .001

Default types of integer literals?

  • value and notation
  • By default, decimal literals are signed whereas octal and hexadecimal literals can be either signed or
    unsigned types.
  • A decimal literal has the smallest type of int, long, or long long (i.e., the first type in this list,) in which the literal’s value fits.
  • Octal and hexadecimal literals have the smallest type of int, unsigned int, long, unsigned long, long long, or unsigned long long (from small to big) in which the literal’s value fits.
  • There are no literals of type short. It is an error to use a literal that is too large to fit in the largest related type.

Default types of floating-point literals ?

  • By default, floating-point literals have type double.

How to write Character and Character String Literals in C++ ? Default type ?

  • A character enclosed within single quotes is a literal of type char. Zero or more characters enclosed in double quotation marks is a string literal. The type of a string literal is array of constant chars.The compiler appends a null character (’\0’) to every string literal. Thus, the actual size of a string literal is one more than its apparent size.(‘A’ represents the single character A, whereas the string literal “A” represents an array of two characters, the letter A and the null character.)

    'a' // character literal
    "Hello World!" // string literal
    
  • 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:

    // multiline string literal
    std::cout << "a really, really long string literal "
    "that spans two lines" << std::endl;
    

What does escape sequence represent for ?

  • Some characters, such as backspace or control characters, have no visible image. Such
    characters are nonprintable. Some characters (single and double quotation marks, question mark, and backslash) that have special meaning in the language. Our programs cannot use any of these characters directly.

How to write escape sequence in C++ ?

newline \n         horizontal tab \t alert (bell) \a
vertical tab \v    backspace \b      double quote \"
backslash \\       question mark \?  single quote \'
carriage return \r formfeed \f

What is generalized escape sequence?

  • \x followed by one or more hexadecimal digits or a \ followed by one, two, or three octal digits.

  • The value represents the numerical value of the character.

    \7 (bell) \12 (newline) \40 (blank)
    \0 (null) \115 ('M')    \x4d ('M')
    

Generalized escape sequence differences between hexadecimal digits and octal digits ?

  • if a \ is followed by more than three octal digits, only the first three are associated with the . For example, “\1234” represents two characters: the character represented by the octal value 123 and the character 4.
  • \x uses up all the hex digits following it; “\x1234” represents a single, 16-bit (4*4) character composed from the bits corresponding to these four hexadecimal digits.
  • Because most machines have 8-bit chars, such values are unlikely to be useful. Ordinarily, hexadecimal characters with more than 8 bits are used with extended characters sets using one of the prefixes from Table 2.2.

How to override default type?

  • using a suffix

  • Although integer literals may be stored in signed types, technically speaking, the
    value of a decimal literal is never a negative number. If we write what appears to be a negative decimal literal, for example, -42, the minus sign is not part of the literal. The minus sign is an operator that negates the value of its (literal) operand.

    Table 2.2. Specifying the Type of a Literal

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g888CW2d-1605624442291)(C:\Users\SDA10\AppData\Roaming\Typora\typora-user-images\image-20201111203743232.png)]

L'a' // wide character literal, type is wchar_t
u8"hi!" // utf-8 string literal (utf-8 encodes a Unicode character in 8 bits)
42ULL // unsigned integer literal, type is unsigned long long
1E-3F // single-precision floating-point literal, type is float
3.14159L /* extended-precision floating-point literal, type is long double
When you write a long literal, use the uppercase L; the lowercase letter l is too easily mistaken for the digit 1.*/
-42L // you can deduct its types.

What are Boolean and Pointer Literals?

  • The words true and false are literals of type bool

    bool test = false;
    
  • The word nullptr is a pointer literal. We’ll have more to say about pointers and nullptr later in § 2.3.2

2.2. Variables

What is variable ?

  • A named object or reference.

Usage ?

  • They provide/declare named(see identifier below) storage(object) that our programs can manipulate.
  • Each variable in C++ has a type.(y? The type determines the size and layout of the variable’s
    memory, that is, the range of values that can be stored within that memory, and the set of
    operations that can be applied to the variable.)

What is an object?

  • Most generally, an object is a region of memory that can contain data and has a type.
  • In this book, we’ll follow the more general usage that an object is a region of memory that has a type. We will freely use the term object regardless of whether the object has built-in or class type, is named or unnamed, or can be read or written.
  • Some use the term object only to refer to variables or values of class types.
  • Others distinguish between named and unnamed objects, using the term variable to refer to named objects.
  • Still others distinguish between objects and values, using the term object for data that can be changed by the program and the term value for data that are read-only.

How to define variables?

  • In C++, variables must be declared before they are used.

    • y?
    • C++ is a statically typed language, which means type checking at compile time.
    • type checking: term used to describe the process by which the compiler verifies that the way objects of a given type are used is consistent with the definition of that type.
    • ex: the type of an object constrains the operations that the object can perform. In C++, the compiler checks whether the operations we write are supported by the types we use. If we try to do things that the type does not support, the compiler generates an error message and does not produce an executable file.
    • However, a consequence of static checking is that the type of every entity we use must be known to the compiler. As one example, we must declare the type of a variable before we can use that variable.
    • that’s why scope start from the definition.
  • variable definition consists of a type specifier, followed by a list of one or more variable names separated by commas, and ends with a semicolon. Initializers are optional.

  • More generally, a declaration is a base type followed by a list of declarators.

  • int sum = 0, value, // sum, value, and units_sold have type int
    units_sold = 0; // sum and units_sold have initial value 0
    Sales_item item; // item has type Sales_item (see § 1.5.1 (p. 20))
    // string is a library type, representing a variable-length sequence of characters
    std::string book("0-201-78345-X"); // book initialized from string literal
    
  • It is usually a good idea to define an object near the point at which the object is first used. Doing so improves readability by making it easy to find the definition of the variable. More importantly, it is often easier to give the variable a useful initial value when the variable is defined close to where it is
    first used.

  • identifiers.(se below)

What is identifiers?

  • Sequence of characters that make up a name.

What is names?

  • Names refer to a specific entity—a variable, function, type, and so on.

Way of naming identifiers ?

  • Identifiers in C++ can be composed of letters, digits, and the underscore character_ . The language imposes no limit on name length. Identifiers must begin with either a letter or an _ . Identifiers are case-sensitive; upper- and lowercase letters are distinct.
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rvQqiwCL-1605624442291)(C:\Users\SDA10\AppData\Roaming\Typora\typora-user-images\image-20201113152321509.png)]
  • The standard also reserves a set of names for use in the standard library. The identifiers we define in our own programs may not contain two consecutive _, nor can an identifier begin with an _ followed immediately by an uppercase letter. In addition, identifiers defined outside a function may not begin with an _.
  • There are a number of generally accepted conventions for naming variables. Following these conventions can improve the readability of a program.
    • An identifier should give some indication of its meaning.
    • Variable names normally are lowercase—index, not Index or INDEX.
    • Like Sales_item, classes we define usually begin with an uppercase letter.
    • Identifiers with multiple words should visually distinguish each word, forexample, student_loan or studentLoan, not studentloan.

What is Initializer?

  • the specified value an object gets at the moment it is created.

How to initialize?

  • The values used to initialize a variable can be arbitrarily complicated expressions.

  • int units_sold = 0;
    int units_sold = {0};//form of initialization that uses curly braces to enclose one or more initializers is list initialization.
    int units_sold{0};
    int units_sold(0);
    
  • it is possible to initialize a variable to the value of one defined earlier in the same definition.

  • // ok: price is defined and initialized before it is used to initialize discount
    double price = 109.99, discount = price * 0.16;
    // ok: call applyDiscount and use the return value to initialize salePrice
    double salePrice = applyDiscount(price, discount);
    

list initialization feature?

  • 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
    

Is ‘=’ of initialization the same as that of assignment?

  • No. Initialization is not assignment. Initialization happens when a variable is given a value when it is created. Assignment obliterates an object’s current value and replaces that value with a new one.

What happens if we define a variable without an initializer?

  • the variable is default initialized :
    • How class type objects are initialized is controlled by the class. Most classes let us define objects without explicit initializers. Such classes supply an appropriate default value for us. Some classes require that every object be explicitly initialized.
    • Objects of built-in type defined at global scope (outside any function body) are initialized to 0; those defined at local scope(inside a function) are uninitialized and have undefined values. It is an error to copy or otherwise try to access the value of a variable whose value is undefined. That error is often hard to debug. The compiler is not required to detect such errors.
    • We recommend initializing every object of built-in type. It is not always necessary, but it is easier and safer to provide an initializer until you can be certain it is safe to omit the initializer.

What is separate compilation, why separate compilation?

  • Ability to split a program into multiple separate source files.
  • To allow programs to be written in logical parts, it lets us split our programs into several files, each of which can be compiled independently.

To support separate compilation, C++ feature in variables?

  • C++ distinguishes between declarations and definitions.

What is declaration? definition?

  • A variable declaration specifies the type and name of a variable. Declaration asserts the existence of a variable, function, or type defined elsewhere.
  • A variable definition includes a declaration. In addition to specifying the name and type, It creates the associated entity : a definition also allocates storage for a variable of a specified type and optionally initializes the variable.

declaration syntax?

  • To obtain a declaration that is not also a definition, we add the extern keyword
    and may not provide an explicit initializer:

  • Any declaration that includes an explicit initializer is a definition. We can provide an initializer on a variable defined as extern, but doing so overrides the extern.

  • extern int i; // declares but does not define i
    int j; // declares and defines j
    extern double pi = 3.1416; // definition
    
  • It is an error to provide an initializer on an extern (declaration) inside a function.

Declaration and definition rules for sharing variables in separate compilation ?

  • To use the same variable in multiple files, we must define that variable in one—and only one—file. Other files that use that variable must declare—but not define—that variable.

Y use scope? What is It creates the associated entity?

  • to limit the portion of a program in which names have meaning. todo
  • The portion of a program in which names have meaning. C++ has several levels of scope:
    global—names defined outside any other scope
    class—names defined inside a class
    namespace—names defined inside a namespace
    block—names defined inside a block
    Scopes nest. Once a name is declared, it is accessible until the end of the scope in which it was declared.

How to konw the Scope of a Name?

  • Most scopes in C++ are delimited by curly braces.

  • #include <iostream>
    int main()
    {
    int sum = 0;
    // sum values from 1 through 10 inclusive
    for (int val = 1; val <= 10; ++val)
    sum += val; // equivalent to sum = sum + val
    std::cout << "Sum of 1 to 10 inclusive is "
    << sum << std::endl;
    return 0;
    }
    
  • The name main is defined outside any curly braces. The name main—like most names defined outside a function—has global scope. Once declared, names at the global scope are accessible throughout the program.

  • The name sum is defined within the scope of the block that is the body of the main function. It is accessible from its point of declaration throughout the rest of the main function but not outside of its curly brace.

  • The variable sum has block scope. The name val is defined in the scope of the for statement. It can be used in that statement but not elsewhere in main.

What is Nested Scopes? features?

  • Scopes can contain other scopes. The contained (or nested) scope is referred to as an inner scope, the containing scope is the outer scope.

  • Once a name has been declared in a scope, that name can be used by scopes nested inside that scope. Names declared in the outer scope can also be redefined in an inner scope-a given name can be reused to refer to different entities at different points in the program.

  • But It is almost always a bad idea to define a local variable with the same name as a global variable that the function uses or might use.

  • #include <iostream>
    // Program for illustration purposes only: It is bad style for a function
    // to use a global variable and also define a local variable with the same name
    int reused = 42; // reused has global scope
    int main()
    {
    int unique = 0; // unique has block scope
    // output #1: uses global reused; prints 42 0
    std::cout << reused << " " << unique << std::endl;
    int reused = 0; // local definition, local object named reused hides global reused
    // output #2: uses local reused; prints 0 0
    std::cout << reused << " " << unique << std::endl;
    // output #3: uses the scope operator :: (The global scope has no name so only :: )to explicitly requests the global reused; prints 42 0
    std::cout << ::reused << " " << unique << std::endl;
    return 0;
    }
    

2.3. Compound Types

What is compound type?

  • A type that is defined in terms of another type.
  • 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–It can include modifiers such as ***** (pointer-to) etc. It depends, for example, references and pointers have this)

NB

int* p; // legal but might be misleading

This style emphasizes that the declaration defines a compound type.

We say that this definition might be misleading because it suggests that int* is the type of each variable declared in that statement. Despite appearances, the base type of this declaration is int, not int*. The * modifies the type of p. It says nothing about any other objects that might be declared in the same statement:

int* p1, p2; // p1 is a pointer to int; p2 is an int

So we use this form:

int *p1, *p2; // both p1 and p2 are pointers to int

This style emphasizes that the variable has the indicated compound type.

Same applies to references.

references

What is reference?

  • An alias for another object.

The principle of reference?

  • When we define a reference, instead of copying the initializer’s value, we bind the reference to its initializer.

    bind –Associating a name with a given entity so that uses of the name are uses of the underlying entity. For example, a reference is a name that is bound to an object.

  • Once initialized, a reference remains bound to its initial object. There is no way to rebind a reference to refer to a different object.

  • So A reference is not an object. Instead, a reference is just another name for an already existing object.

How to define a reference type?

  • writing a declarator of the form &d, where d is the name being declared

  • references must be initialized. Because there is no way to rebind a reference to refer to a different object.

  • Because references are not objects, we may not define a reference to a reference.

  • int ival = 1024;
    int &refVal = ival; // refVal refers to (is another name for) ival
    int &refVal2; // error: a reference must be initialized
    // ok: refVal3 is bound to the object to which refVal is bound, i.e., to ival
    int &refVal3 = refVal;
    // initializes i from the value in the object to which refVal is bound
    int i = refVal; // ok: initializes i to the same value as ival
    
  • We can define multiple references in a single definition.

    int i = 1024, i2 = 2048; // i and i2 are both ints
    int &r = i, r2 = i2; // r is a reference bound to i; r2 is an int
    int i3 = 1024, &ri = i3; // i3 is an int; ri is a reference bound to i3
    int &r3 = i3, &r4 = i2; // both r3 and r4 are references
    
  • the type of a reference and the object to which the reference refers must match exactly(except two situations)

  • a reference may be bound only to an object, not to a literal or to the result of a more general expression

    int &refVal4 = 10; // error: initializer must be an object
    double dval = 3.14;
    int &refVal5 = dval; // error: initializer must be an int objec
    

    Pointers

What is a pointer?

  • An object that can hold the address of an object, the address one past the end of an object, or zero.

Pointer Value ?

  1. It can point to an object.
  2. It can point to the location just immediately past the end of an object.
  3. It can be a null pointer, indicating that it is not bound to any object.
  4. It can be invalid; values other than the preceding three are invalid.(like uninitialized, The result of accessing an invalid pointer is undefined. )

Although pointers in cases 2 and 3 are valid, there are limits on what we can do with such pointers. Because these pointers do not point to any object, we may not use them to access the (supposed) object to which the pointer points. If we do attempt to access an object through such pointers, the behavior is undefined.

The principle of a pointer?

  • Like references, pointers are used for indirect access to other objects. The types of the pointer and the object to which it points must match
  • Unlike a reference, a pointer is an object in its own right. Pointers can be assigned and copied, thus a pointer need not be initialized at the time it is defined.
  • The default initialization of a pointer is like other built-in types.
  • Because references are not objects, they don’t have addresses. Hence, we may not define a pointer to a reference.

How to define a reference type?

  • We define a pointer type by writing a declarator of the form *d, where d is the name being defined.

    int *ip1, *ip2; // both ip1 and ip2 are pointers to int
    double dp, *dp2; // dp2 is a pointer to double; dp is a double
    
  • initialize: We get the address of an object by using the address-of operator :the & operator. It is illegal to assign an int variable to a pointer.

    int ival = 42;
    int *p = &ival; // p holds the address of ival; p is a pointer to ival
    
  • As with any other uninitialized variable, what happens when we use an uninitialized pointer is undefined. Using an uninitialized pointer almost always results in a run-time crash. However, debugging the resulting crashes can be surprisingly hard. Under most compilers, when we use an uninitialized pointer, the bits in the memory in which the pointer resides are used as an address. Using an uninitialized pointer is a request to access a supposed object at that supposed location. There is no way to distinguish a valid address from an invalid one formed from the bits that happen to be in the memory in which the pointer was allocated.

  • Our recommendation to initialize all variables is particularly important for pointers. If possible, define a pointer only after the object to which it should point has been defined. If there is no object to bind to a pointer, then initialize the pointer to nullptr or zero. That way, the program can detect that the pointer does not point to an object.

How to use a Pointer to Access an Object?

  • use the dereference operator (the *operator)

    int ival = 42;
    int *p = &ival; // p holds the address of ival; p is a pointer to ival
    cout << *p; // * yields the object to which p points; prints 42
    *p = 0; // * yields the object; we assign a new value to ival through p
    cout << *p; // prints 0
    

NB the same symbol is used with very different meanings, like & and *, are used as both an operator in an expression and as part of a declaration.

How to define a null pointer?

  • int *p1 = nullptr; // equivalent to int *p1 = 0;nullptr is Literal constant that denotes the null pointer.
    int *p2 = 0; // directly initializes p2 from the literal constant 0
    // must #include cstdlib
    int *p3 = NULL; // NULL: preprocessor variable the cstdlib header defines it as 0. equivalent to int *p3 = 0;Preprocessor variables are managed by the preprocessor, and are not part of the std namespace. As a result, we refer to them directly without the std:: prefix.
    
  • It is illegal to assign an int variable to a pointer, even if the variable’s value happens to be 0.

Pointers operations

  • Assignment ‘=’

    int i = 42;
    int *pi = 0; // pi is initialized but addresses no object
    int *pi2 = &i; // pi2 initialized to hold the address of i
    int *pi3; // if pi3 is defined inside a block, pi3 is uninitialized
    pi3 = pi2; // pi3 and pi2 address the same object, e.g., i
    pi2 = 0; // pi2 now addresses no object
    
  • Just as when we use an arithmetic value in type conversion to bool, Any nonzero pointer evaluates as true

    int ival = 1024;
    int *pi = 0; // pi is a valid, null pointer
    int *pi2 = &ival; // pi2 is a valid pointer that holds the address of ival
    if (pi) // pi has value 0, so condition evaluates as false
    // ...
    if (pi2) // pi2 points to ival, so it is not 0; the condition evaluates as true
    // ...
    
  • equality(==) or inequality (!=) operators. The result of these operators has type bool.

    Two pointers hold the same address (i.e., are equal) if they are both null, if they address the same
    object, or if they are both pointers one past the same object. Note that it is possible for a pointer to an object and a pointer one past the end of a different object to hold the same address. Such pointers will compare equal.

    Because these operations use the value of the pointer, a pointer used in a condition or in a comparison must be a valid pointer. Using an invalid pointer as a condition or in a comparison is undefined.

How to hold the address of any object ?

  • void *. A special pointer type, a void pointer holds an address, but the type of the object at
    that address is unknown.

    double obj = 3.14, *pd = &obj;
    // ok: void* can hold the address value of any data pointer type
    void *pv = &obj; // obj can be an object of any type
    pv = pd; // pv can hold a pointer to any type
    
  • Generally, we use a void* pointer to deal with memory as memory, rather than using the pointer to access the object stored in that memory (dereference).

  • We can:

  • compare it to another pointer

  • pass it to or return it from a function

  • we can assign it to another void* pointer.

  • We can’t:

  • use a void* to operate on the object it addresses—we don’t know that object’s type, and the type determines what operations we can perform on the object.

How to point points?

  • In general, there are no limits to how many type modifiers can be applied to a declarator. We indicate each pointer level by its own *. That is, we write ** for a pointer to a pointer, *** for a pointer to a pointer to a pointer, and so on.

    int ival = 1024;
    int *pi = &ival; // pi points to an int
    int **ppi = &pi; // ppi points to a pointer to an int
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fq8yhPKn-1605624442292)(C:\Users\SDA10\AppData\Roaming\Typora\typora-user-images\image-20201114142413846.png)]

  • Just as dereferencing a pointer to an int yields an int, dereferencing a pointer to a pointer yields a pointer. To access the underlying object, we must dereference the original pointer twice:

    cout << "The value of ival\n"
    << "direct value: " << ival << "\n"
    << "indirect value: " << *pi << "\n"
    << "doubly indirect value: " << **ppi
    << endl;
    

References to Pointers?

  • 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
    

    to understand the type of r, read the definition right to left.

  • The symbol closest to the name of the variable (in this case the & in &r) is the one that has the most immediate effect on the variable’s type. Thus, we know that r is a reference.

  • The rest of the declarator determines the type to which r refers.

  • The next symbol, * in this case, says that the type r refers to is a pointer type.

  • Finally, the base type of the declaration says that r is a reference to a pointer to an int.

const Qualifier

How to define a variable whose value cannot be changed ?

  • defining the variable’s type as const

    const int bufSize = 512; // input buffer size
    
  • Because we can’t change the value of a const object after we create it, it must be initialized. As usual, the initializer may be an arbitrarily complicated expression.

  • 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 operations?

  • the type of an object defines the operations that can be performed by that object. A const type can use most but not all of the same operations as its nonconst version.
  • we may use only those operations that cannot change an object.

const mechanism?

  •  const int bufSize = 512; // input buffer size
    
  • the compiler will usually replace uses of the variable with its corresponding value during compilation. That is, the compiler will generate code using the value 512 in the places that our code uses bufSize.

Const scope is local to a File ?

  • When we split a program into multiple files, every file that uses the const must have access to its initializer to substitute the value for the variable. So the variable must be defined in every file that wants to use the variable’s value.
  • avoid multiple definitions of the same variable

How to define a const variable that we want to share across multiple files?

  • 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
    
  • file_1.cc defines and initializes bufSize. Because this declarationincludes an initializer, it is (as usual) a definition. However, because bufSize is const, we must specify extern in order for bufSize to be used in other files.

  • The declaration in file_1.h is also extern. In this case, the extern signifies that bufSize is not local to this file and that its definition will occur elsewhere.

References to const

What is References to const ?

  • A reference that may not change the value of the object to which it refers.
  • A references to const may be bound to a const object, can also be bind to a nonconst object, a literal, or a more general expression. The last two cannot be bind to a plain reference.

References to const sytanx?

  • 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.Otherwise r2 willbbe able to change the value of the ci, which is not up to the characteristic of const.
    

Why use a references to const/What is special in references to const ?

  • a reference to const restricts only what we can do through that reference.

    • It says nothing about whether the underlying object itself is const.

      • we noted that 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 (§ 2.1.2, p. 35) to the type of the reference.???

      • int i = 42;
        const int &r1 = i; // we can bind a const int& to a plain int object
        const int &r2 = 42; // ok: r2 is a reference to const
        const int &r3 = r1 * 2; // ok: r3 is a reference to const
        int &r4 = r1 * 2; // error: r4 is a plain, non const reference
        
    • there is no guarantee that an object it bind won’t change.

    • It says nothing about whether we can alter the binding of the reference itself.

      • C++ programmers tend to abbreviate the phrase “reference to const” as “const reference.” It is okay. Indeed, because there is no way to make a reference refer to a different object, in some sense all references are const.
      • But there are no const references. A reference is not an object, so we cannot make a reference itself const.
  • We can only bind a const object in a references to const

    int &r = ci; // error: can't bind an ordinary int& to a const int object
    const int &r2 = i; // ok: can bind const int& to plain int
    

what happens when we bind a reference to an object of a different type?

  • double dval = 3.14;
    const int &ri = dval;
    

    is qual to

  • const int temp = dval; // create a temporary const int from the double
    const int &ri = temp; // bind ri to that temporary
    

    ri is bound to a temporary object. A temporary object (Abbreviation: temporary) is an unnamed object created by the compiler while evaluating an expression. A temporary exists until the end of the largest expression that encloses the expression for which it was created.

Why References to const is not able to change the value when bind to a nonconst object?

  • If ri weren’t const, we could assign to ri. But actually we change the temporary. It is not what we want. So the language makes it illegal.

pointer and const

What is a pointer to const?

  • Pointer that can hold the address of a const or non-cost object.
  • A pointer to const may not be used to change the value of the object to which it points.

Why use a pointer to const/ What is special in references to const ?

  • Defining a pointer as a pointer to const affects only what we can do with the pointer.

    • a pointer to const may not be used to change the value of the object to which it points.

    • there is no guarantee that an object pointed to by a pointer to const won’t change.

    • It says nothing about whether we can alter the object it point.

      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
      double dval = 3.14; // dval is a double; its value can be changed
      cptr = &dval; // ok: but can't change dval through cptr
      
  • We can only store the address of a const object in a pointer to const.

  • we noted that there are two exceptions to the rule that the types of a pointer and the object to which it points must match.

    • The first is a pointer to const can hold the address of a const or non-cost object.

pointer to const synatax?

  • const double pi = 3.14; // pi is const; its value may not be changed
    double *ptr = &pi; // error: ptr is a plain pointer
    const double *cptr = &pi; // ok: cptr may point to a double that is const
    *cptr = 42; // error: cannot assign to *cptr
    

What is const Pointers?

  • as with any other const object, a pointer that is itself const.

Why there are const Pointers?

  • Unlike references, pointers are objects.

const Pointers syntax?

  • must be initialized, and once initialized, its value (i.e., the address that it holds) may not be changed.

  • putting the const after the *. This placement indicates that it is the pointer, not the pointed-to type, that is const

    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
    
  • 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 next symbol in the declarator is *, which means that curErr is a pointer. Finally, the base type of the declaration completes the type of curErr, which is a type int. So it is is a const pointer to an object of type int Similarly, pip is a const pointer to an object of type const double.

**What’s special in const Pointers? **

  • must be initialized, and once initialized, its value (i.e., the address that it holds) may
    not be changed.

  • const pointer says only its value (i.e., the address that it holds) may not be changed.

  • 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.
  • For example, pip is a const pointer to const. Neither the value of the object addressed by pip nor the address stored in pip can be changed. On the other hand, curErr addresses a plain, nonconst int. We can use curErr to change the value of errNumb.

what is top-level const, low-level const?

  • top-level const indicates that an object itself is const. Top-level const can appear in any object type,

    • We use the term top-level const to indicate that the pointer itself is a const. i.e., one of the built-in arithmetic types, a class type, or a pointer type.
  • Low-level const appears in the base type of compound types such as pointers or references. Such consts are integral to the type and are never ignored.

    • When a pointer can point to a const object, we refer to that const as a low-level const.
    • pointer types, unlike most other types, can have both top-level and low-level const independently.
  • 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
    

Why concern top-level const, low-level const?

  • The distinction between top-level and low-level matters when we copy an object.

  • For same tye:(int, *,&)

  • When we copy an object, top-level consts are 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. ?there must be a conversion between the types of the two objects?

    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
    
  • 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.

    int *p = p3; // error: top-level const in p3 is ignored but p3 has a low-level const but p doesn't
    p2 = p3; // ok: p2 has the same low-level const qualification as p3
    p2 = &i; // ok: we can convert int* to const int*
    

    p3 has both a top-level and low-level const. When we copy p3, we can ignore its top-level const but not the fact that it points to a const type. Hence, we cannot use p3 to initialize p, which points to a plain (nonconst) int. On the other hand, we can assign p3 to p2. Both pointers have the same (low-level const) type. The fact that p3 is a const pointer (i.e., that it has a top-level const) doesn’t matter.

  • For different types:

  • i = r; //ok
    int &r = ci; // error: can't bind an ordinary int& to a const int object
    const int &r2 = i; // ok: can bind const int& to plain int
    
  • to do

What is constant expression?

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

  • A literal is a constant expression. A const object that is initialized from a constant expression is also a constant expression.

  • Whether a given object (or expression) is a constant expression depends on the types and the initializers.

    const int max_files = 20; // max_files is a constant expression
    const int limit = max_files + 1; // limit is a constant expression
    int staff_size = 27; // staff_size is not a constant expression
    const int sz = get_size(); // sz is not a constant expression
    
  • Although staff_size is initialized from a literal, it is not a constant expression because it is a plain int, not a const int. On the other hand, even though sz is a const, the value of its initializer is not known until run time. Hence, sz is not a constant expression.

What is constexpr Variables? y use it?

  • In a large system, it can be difficult to determine (for certain) that an initializer is a constant expression.
  • Generally, it is a good idea to use constexpr for variables that you intend to use as constant expressions.
  • declaring the variable in a constexpr declaration, the compiler to verify a variable is a constant expression or not.It not it will fail.

How to declare the constexpr variables?

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
  • Because a constant expression is one that can be evaluated at compile time

  • The types we can use in a constexpr are known as “literal types” because they are simple enough to
    have literal values : the arithmetic, reference, and pointer types etc.

  • Sales_item class and the library IO and string types are not literal types.

  • Although we can define both pointers and reference as constexprs, the objects we use to initialize them are strictly limited. We can initialize a constexpr pointer from the nullptr literal or the literal (i.e., constant expression) 0. We can also point to (or bind to) an object that remains at a fixed address.

  • variables defined inside a function ordinarily are not stored at a fixed address. Hence, we cannot use a constexpr pointer to point to such variables.

  • the address of an object defined outside of any function is a constant expression, and so may be used to initialize a constexpr pointer.

  • functions may define variables that exist across calls to that function. Like an object defined outside any function, these special local objects also have fixed addresses. Therefore, a constexpr reference may be bound to, and a constexpr pointer may address, such variables.

  • It is important to understand that when we define a pointer in a constexpr declaration, the constexpr specifier applies to the pointer, not the type to which the pointer points:

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

    The difference is a consequence of the fact that constexpr imposes a top-level conston the objects it defines.Like any other constant pointer, a constexpr pointer may point to a const or anonconst type.

    constexpr int *np = nullptr; // np is a constant pointer to int that is
    null
    int j = 0;
    constexpr int i = 42; // type of i is const int
    // i and j must be defined outside any function
    constexpr const int *p = &i; // p is a constant pointer to the const int i
    constexpr int *p1 = &j; // p1 is a constant pointer to the int j
    

Dealing with Types

Complications in using types arise in which two different ways?

  • hard to “spell.”

    • forms are tedious and error-prone to write.
    • form of a complicated type can obscure its purpose or meaning.
  • sometimes it is hard to determine the exact type we need. Doing so can require us to look back into the context of the program.

How to simplify complicated type definitions?/ Why type alias?

  • use type alias

What is type alias?

  • A name that is a synonym for another type. Defined through either a typedef or an alias declaration.

typedef syntax for type alias?

typedef double wages; // wages is a synonym for double
typedef wages base, *p; // base is a synonym for double, p for double*
  • keyword typedef may appear as part of the base type of a declaration

  • the declarators can include type modifiers that define compound types built from the base type of the definition.

  • Declarations that include typedef define type aliases rather than variables.

Dealing with Types

Complications in using types arise in which two different ways?

  • hard to “spell.”

    • forms are tedious and error-prone to write.
    • form of a complicated type can obscure its purpose or meaning.
  • sometimes it is hard to determine the exact type we need. Doing so can require us to look back into the context of the program.

How to simplify complicated type definitions?/ Why type alias?

  • use type alias

What is type alias?

  • A name that is a synonym for another type. Defined through either a typedef or an alias declaration.

typedef syntax for type alias?

typedef double wages; // wages is a synonym for double
typedef wages base, *p; // base is a synonym for double, p for double*
  • keyword typedef may appear as part of the base type of a declaration

  • the declarators can include type modifiers that define compound types built from the base type of the definition.

  • Declarations that include typedef define type aliases rather than variables.

相关标签: C++