C++ Primer 第二章 变量和基本类型
- 2.1. Primitive Built-in Types
- 2.1.1. Arithmetic Types
- 2.1.3. Literals
- 2.2 Variables
- 2.2.1. Variable Definitions
- Initializers
- List Initialization
- Default Initialization
- 2.2.2. Variable Declarations and Definitions
- 2.2.3. Identifiers
- 2.3. Compound Types
- 2.3.1. References
- 2.3.2. Pointers
- 2.4. const Qualifier
- 2.4.1. References to const
- 2.4.2. Pointers and const
- 2.4.3. Top-Level const
- 2.4.4. constexpr and Constant Expressions
- Constexpr Variables
- Pointers and constexpr
- 2.5. Dealing with Types
- 2.5.1. Type Aliases
- typedef
- using
- Pointers, const, and Type Aliases
- 2.5.2. The auto Type Specifier
- 2.5.3. The decltype Type Specifier
2.1. Primitive Built-in Types
2.1.1. Arithmetic Types
-
Integral types
-
Floating-point types
2.1.3. Literals
A value, such as 42, is known as a literal because its value self-evident. Every literal has a type.
-
Integer Literals
- decimal
such as 20,30
By default, decimal literals are signed.
A decimal literal has the smallest type of int, long, or long long in which the literal’s value fits. - octal
Octal literals begin with 0 (zero), such as024
Octal literals can be either signed or unsigned types.
Octal literals have the smallest type of int, unsigned int, long, unsigned long, long long, or unsigned long long in which the literal’s value fits. - hexadecimal
Hexadecimal literals begin with either0x
or0X
, such as0x14
Hexadecimal literals can be either signed or unsigned types.
Hexadecimal literals have the smallest type of int, unsigned int, long, unsigned long, long long, or unsigned long long in which the literal’s value fits.
- decimal
-
Floating-Point literals
Floating-point literals include either a decimal point or an exponent specified using
scientific notation.
Using scientific notation, the exponent is indicated by either E or e.
By default, floating-point literals have type double.
2.1,2.1E0,0.,0e0,.001 -
Character and Character String Literals
- char
‘a’ - string
“a”, “hello world!”
The compiler appends a null character (’\0’) to every string literal.
- char
-
Escape Sequences
We use an escape sequence as if it were a single character.
- \ followed by one, two or three octal digits
The value represents the numerical value of the character.
If a \ is followed by more than three octal digits, only the first three are associated with the .\1234
represents two characters: octal value 123 and character 4. - \x followed by one or more hexadecimal digits
- \ followed by one, two or three octal digits
-
Specifying the Type of a Literal
-
Boolean and Pointer Literals
- There are no literals of type short.
- The value of a decimal literal is never a negative number.
对于 -42 来说,负号不是 literal 的一部分。
2.2 Variables
A variable provides us with named storage that our programs can manipulate.
2.2.1. Variable Definitions
Variable Definitions: A simple 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
An object that is initialized gets the specified value at the moment it is created.
// 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);
Warning: 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.
List Initialization
The language defines several different forms of initialization:
int units_sold = 0;
int units_sold = {0};
int units_sold{0};
int units_sold(0);
The generalized use of curly braces for initialization is referred to as list initialization.
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
Default Initialization
When we define a variable without an initializer, the variable is default initialized.
The value of an object of built-in type that is not explicitly initialized depends on where it is defined.
-
Variables defined outside any function body are initialized to zero.
-
Variables of built-in type defined inside a function are uninitialized.
It is an error to copy or otherwise try to access the value of a variable whose value is undefined.
Objects of class type that we do not explicitly initialize have a value that is defined by the class.
不同初始化方法:
Is there a difference between copy initialization and direct initialization?
Different forms of initialization
Initialization of variables
Initializers
2.2.2. Variable Declarations and Definitions
To support separate compilation, C++ distinguishes between 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.
A variable declaration specifies the type and name of a variable.
A variable definition is a declaration. In addition to specifying the name and type, a definition also allocates storage and may provide the variable with an initial value.
To obtain a declaration that is not also a definition, we add the extern keyword and may not provide an explicit initializer:
extern int i; // declares but does not define i
int j; // declares and defines j !!!!
Note:
Variables must be defined exactly once but can be declared many times.
To use a variable in more than one file requires declarations that are separate from the variable’s definition.
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.
Difference between declaration and definition:
What is the difference between a definition and a declaration?
What is the difference between declaration and definition of a variable in C++?
Declare vs Define in C and C++
The differences between initialize, define, declare a variable
C++ is a statically typed language, which means that types are checked at compile time.
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.
2.2.3. 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 underscore.
- Identifiers are case-sensitive.
2.3. Compound Types
A compound type is a type that is defined in terms of another type.
2.3.1. References
- There is no way to rebind a reference to refer to a different object.
- References must be initialized.
- A reference is not an object. Instead, a reference is just another name for an already existing object.
2.3.2. Pointers
- Like other built-in types, pointers defined at block scope have undefined value if they are not initialized.
- void* Pointers
The type void* is a special pointer type that can hold the address of any object.
2.4. const Qualifier
- Because we can’t change the value of a const object after we create it, it must be initialized.
- The compiler will usually replace uses of the variable with its corresponding value during compilation.
- By Default, const objects are local to a file.
- To share a const object among multiple files, you must define the variable as extern.
2.4.1. References to const
const Reference is a Reference to const.
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
Two exceptions to the rule that the type of a reference must match the type of the object to which it refers:
- We can initialize a reference to const from any expression that can be converted to the type of the reference.
double dval = 3.14;
const int &ri = dval;
To ensure that the object to which ri
is bound is an int
, the compiler transforms this code into something like:
const int temp = dval; // create a temporary const int from the double
const int &ri = temp; // bind ri to that temporary
In this case, ri
is bound to a temporary object. A temporary object is an unnamed object created by the compiler when it needs a place to store a result from evaluating an 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.
int i = 42;
int &r1 = i; // r1 bound to i
const int &r2 = i; // r2 also bound to i; but cannot be used to change i
r1 = 0; // r1 is not const; i is now 0
r2 = 0; // error: r2 is a reference to const
2.4.2. Pointers and const
const double pi = 3.14; // pi is const; its value may not be changed
double *ptr = π // error: ptr is a plain pointer
const double *cptr = π // ok: cptr may point to a double that is const
*cptr = 42; // error: cannot assign to *cptr
There are two exceptions to the rule that the types of a pointer and the object to which it points must match:
-
We can use a pointer to const to point to a nonconst object
-
Unlike references, pointers are objects
Hence, as with any other object type, we can have a pointer that is itself const.
int errNumb = 0;
int *const curErr = &errNumb; // curErr will always point to errNumb
const double pi = 3.14159;
const double *const pip = π // pip is a const pointer to a const object
2.4.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
When we copy an object, top-level consts are ignored:
On the other hand, low-level const is never ignored.
In general, we can convert a nonconst to const but not the other way round:
- int const * , const int * and int *const
What is the difference between const int*, const int * const, and int const *?
website to auto-translates C declarations
Clockwise/Spiral Rule
int const *p; // declare p as pointer to const int
const int *p; // same as int const * p
int * const p; // declare p as const pointer to int
2.4.4. constexpr and Constant Expressions
-
A constant expression is an expression whose value cannot change and that can be evaluated at compile time.
-
A literal is a constant expression.
-
A const object that is initialized from a constant expression is also a constant expression.
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
Constexpr Variables
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
We can use constexpr functions in the initializer of a constexpr variable.
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.
On the other hand, the address of an object defined outside of any function is a constant expression, and so may be used to initialize a constexpr pointer.
Pointers and constexpr
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
2.5. Dealing with Types
2.5.1. Type Aliases
A type alias is a name that is a synonym for another type.
typedef
typedef double wages; // wages is a synonym for double
typedef wages base, *p; // base is a synonym for double, p for double*
using
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
.
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
2.5.2. The auto Type Specifier
-
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:
-
As with any other type specifier, we can define multiple variables using auto.
auto i = 0, *p = &i; // ok: i is int and p is a pointer to int
auto sz = 0, pi = 3.14; // error: inconsistent types for sz and pi
- 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.
- using a reference
int i = 0, &r = i;
auto a = r; // a is an int (r is an alias for i, which has type int)
- 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.
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.
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)
const auto f = ci; // deduced type of ci is int; f has type const int
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
auto k = ci, &l = i; // k is int; l is int&
auto &m = ci, *p = &ci; // m is a const int&;p is a pointer to const int
// error: type deduced from i is int; type deduced from &ci is const int
auto &n = i, *p2 = &ci;
2.5.3. The decltype Type Specifier
-
decltype returns the type of its operand.
-
The compiler analyzes the expression to determine its type but does not evaluate the expression.
-
When the expression to which we apply decltype is a variable, decltype returns the type of that variable, including top-level const and references.
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.
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
- When we apply decltype to an expression that is not a variable, we get the type that that expression yields.
// 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
Here r
is a reference, so decltype(r)
is a reference type. If we want the type to which r
refers, we can use r
in an expression, such as r + 0
, which is an expression that yields a value that has a nonreference type.
-
The dereference operator is an example of an expression for which decltype returns a reference.
The type deduced by decltype(*p) is int&, not plain int. -
When we apply decltype to a variable without any parentheses, we get the type of that variable.
-
If we wrap the variable’s name in one or more sets of parentheses, the compiler will evaluate the operand as an expression.
A variable is an expression that can be the left-hand side of an assignment.
decltype((variable)) (note, double parentheses) is always a reference type, but decltype(variable) is a reference type only if variable is a reference.
As a result, decltype on such an expression yields a reference:
// 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