C++ study notes (4) - expressions, operators and type conversions
- C++ study notes (4) - expressions, operators and type conversions
An expression consists of one or more operands, and evaluating the expression yields a result. Literal values and variables are the simplest expressions, and the result is the value of the literal value and variable. Combining operators and operands can generate more complex expressions
Base
basic concept
For a complex expression with multiple operators, to understand its meaning, it is necessary to understand the operator's precedence, associativity and the order of operation of the operands.
During expression evaluation, operands are usually converted from one type to another.
lvalue: the identity of the object, that is, its location in memory
- The assignment operator expects a (non-const) lvalue as its left-hand operand, and the result is also an lvalue
- Takes an lvalue operand in the address scope and returns a pointer to the operand, which is an rvalue
- The evaluation results of the built-in dereference operator and subscript operator are all lvalues
- Increment decrement operator scoped lvalue operands for built-in types and iterators
rvalue: the value (content) of an object
The keyword
decltype
acts on an lvalue, and the result is a reference typeint a = 0; int *p = &a; decltype(*p) b; // 解引用运算符是生成左值,所以 b 结果是 int &, 即引用类型,未初始化 decltype(&p) c; // 取地址符生成右值,所以 c 结果是 int **, 指针的指针
Evaluation order
Precedence dictates how operands are combined, but does not specify the order in which the operands are evaluated. If an expression points to and modifies the same object, an error is raised and undefined behavior occurs.
int i = f1() * f2(); // f1 和 f2 会在乘法之前调用,但却不知道 f1 和 f2 的执行顺序
int i = 0;
cout << i << " " << ++i << endl; // 未定义,可能输出1 1,也可能是0 1
arithmetic operators
* Arithmetic operators can act on any arithmetic type, and any type that can be converted to an arithmetic type. Both operands and evaluation results of arithmetic operators are rvalues
The unary minus operator negates the value of the operand and returns its (promoted) copy
int i = 1024; int k = -1; // k 是 -2014 bool b = true; bool b2 = -b; // b2 是 true
The Boolean value does not participate in the operation. As shown in the above code,
bool
the operation formation of the type is first promoted to theint
type1
, and the negative or is-1
, not 0, so itb2
is trueRemainder: If
m % n
is not equal0
, its negative sum is them
same.
Logical and relational operators
- For both types of operators, both the operand and the result of the evaluation are lvalues
- Short-circuit evaluation: that is, the value of the right-hand operand is evaluated if and only if the left-hand operand cannot determine the result of the expression, for logical AND and logical OR operators.
- When performing comparison operations, do not use boolean literals
true
andfalse
as operands unless the objects being compared are of boolean type
assignment operator
- The left-hand operand of an assignment operator must be a modifiable lvalue
- For compound operators (
+=
,-=
etc.), they are all completely equivalenta = a op b;
, the only difference being the number of evaluations of the left-hand operand: only once with compound operators, twice with normal operators (one evaluation, one assignment)
Increment and decrement operators, member access operators, conditional operators
- Pre-version (++a): returns the object itself as an lvalue
- Postversion (a++): returns a copy of the object's original value as an rvalue
auto pbeg = v.begin();
*pbeg++; // 正确, 返回*pbeg, 然后++pbeg; 因为后置递增运算符优先级高于解引用运算符
string s1 = "a string", *p = &s1;
*p.size(); // 错误: p是一个指针,没有名为size的成员, 因为解引用运算符优先级低于点运算符
string finalgrade = (grade < 60) ? "fail" : "pass";
bitwise operators
- Shift Left: Insert 0 to the right
- right shift: depends on the type of its left operand
- unsigned type: insert 0 on the left
- Signed type: a copy of the left caret bit or the value 0, depending on the environment
Note : Bit operation is a very important knowledge point and requires in-depth study
sizeof operator
sizeof
The operator returns the number of bytes occupied by an expression or a type name, and the result is a size_t
constant expression of a type
sizeof
The operand of an operator has two forms
sizeof (type)
sizeof expr
: Returns the size of the result type of the expression.sizeof
does not actually compute the value of its operands
- It is still safe behavior to dereference an invalid pointer in
sizeof
the operand of , because the pointer is not actually used, andsizeof
you don't need to actually dereference the pointer to know the type of the object it refers to. sizeof
The result of an operator depends in part on the type it acts on:
char
orchar
type expression:1
- Reference type: the size of the space occupied by the referenced object
- Pointer: The size of the space occupied by the pointer itself
- Dereference pointer: the size of the space occupied by the object pointed to by the pointer, the pointer does not need to be valid
- Array: The size of the space occupied by the entire array, the
sizeof
operation will not convert the array into a pointer for processing string
Object orvector
object: the size of the Fuding part of this type, not counting how much space is occupied by the elements in the object
type conversion
The C++ language does not directly manipulate two values of different types, but first tries to unify the types of the operands according to the type conversion rules and then calculates. Because it is automatically executed, it is called implicit conversion.
When do implicit type conversions occur
- In most expressions,
int
integer values smaller than the type are first promoted to the larger integer type - In a condition, a non-boolean value is converted to a boolean type
- In the initialization process, the initial value is converted into the type of the variable; in the assignment statement, the right operand is converted into the type of the left operand
- If the operand of an arithmetic operation or relational operation has multiple types, it needs to be converted to the same type.
- Type conversion also occurs in function calls (learning later)
Arithmetic conversion
Arithmetic conversion is the conversion of one arithmetic type to another arithmetic type
- The operand of the operator is converted to the widest type. For example, if one operand is
long double
, the other operand must be converted tolong double
- When an expression has both floating-point and integer types, the integer value is converted to the corresponding floating-point type
- Integer promotion: conversion of small integer types to larger integer types
- bool, char, signed char, unsigned char, short, unsigned short, if they
int
fit, they are promoted to typeint
, otherwise they are promoted tounsigned int
type - Larger char types (
wchar_t
,char16_t
,char32__t
) are promoted to the smallest ofint
,unsigned int
,long
, `….. etc., provided that they fit
- bool, char, signed char, unsigned char, short, unsigned short, if they
- An operand is an unsigned type
- Unsigned >= Signed type: The signed operand is converted to unsigned. If the signed value is negative, the result cannot be judged
- unsigned < signed: the result of the conversion is machine-dependent,
- If all values of the unsigned type can exist in the signed type, the unsigned operand is converted to the signed type
- If not, the operand of the signed type is converted to an unsigned type
Other implicit type conversions
- Array to pointer: In most expressions that use arrays, the array is automatically converted to a pointer to the first element of the array
- pointer conversion
- Constant integer values
0
or literalsnullptr
can be converted to any pointer type - A pointer to any non-constant can be converted to
void *
- A pointer to an arbitrary object can be converted to
const void *
- Constant integer values
show transition
named casts cast-name<type>(expression);
, where cast-name
are static_cast
, dynamic_cast
, const_cast
andreinterpret_cast
static_cast
Any type conversion with a well-defined definition, as long as the underlying const is not included , can be usedstatic_cast
Useful when you need to assign a larger arithmetic type to a smaller type, the
static_cast
cast means: I know and don't care about the loss of precision, so the compiler doesn't warn mestatic_cast
Can be used for type conversions that the compiler cannot perform automaticallyvoid *p = &d; // 正确:任何非常量对象的地址都能存入void * double *dp = static_cast<double*>(p); // 正确:将void *转换成初始的指针类型.如果类型不符,则未定义
const_cast
Only the underlying layer of the operand can be changed
const
, that is, the behavior that can be used to convert a constant object to a non-const object.Once the properties of an object are removed
const
, the compiler no longer prevents us from writing to that object. If the object itself is not a constant, it is legal to use a cast to obtain write permission. But if the object is a constant,const_cast
performing a write operation with it has undefined consequences.Any other form of named cast that changes the constant attribute of an expression will raise a compiler error
const_cast
Often used in contexts with function overloading
reinterpret_cast
A lower-level reinterpretation is usually provided for the bit pattern of the operand. reinterpret_cast
Essentially machine-dependent, very dangerous, requires a good understanding of the types involved and the process by which the compiler implements the conversion
int *ip;
char *pc = reinterpret_cast<char*>(ip); // 等价于 char *pc = (char *) ip;
string str(pc); // 运行时错误,pc 实质上指的是一个int
dynamic_cast
dynamic_cast
The usage form is as follows,type
must be a class type, and usually contains virtual functionsdynamic_cast<type*>(e)
: e must be a valid pointerdynamic_cast<type&>(e)
: e must be an lvaluedynamic_cast<type&&>(e)
: e cannot make lvalue
The type of e must meet any of the following three conditions
type
Commonly derived classes of target- target
type
common base class type
type of target
When conversion fails
- If the target is a pointer type: the result is 0
- If the target is a reference type: throw
bad_cast
an exception
// 指针类型的 dynamic_cast // bp 指针指向 Base(至少含有一个虚函数), Derived 是 Base 的共有派生类 if(Derived *dp = dynamic_cast<Derived*>(bp)) { // 转换成功, 使用 dp 指向的 Derived 对象 }else{ // bp 指向一个 Base 对象 // 转换失败, 使用 dp 指向的 Base 对象 } // 引用类型的dynamic_cast // 因为不存在控引用,对于引用失败,应该捕获异常 void f(const Base &b) { try{ const Derived &d = dynamic_cast<const Derived&>(b); // 使用 b 引用的 Derived 对象 }catch(bad_cast){ // 处理类型转换失败的情况 } }
operator precedence table
Epilogue
- For expressions with multiple operators, understand precedence, associativity, and order of evaluation
- Type conversion is very important and needs to be studied in depth