文章目录
- 算数操作符
- 位操作符
- bitset对象或整型值的使用
- 将位移操作符用作IO
- 赋值操作符
- 赋值操作符的右结合性
- 赋值操作具有低优先级
- 自增和自减操作符
- 条件操作符
- sizeof操作符
- 优先级
- new和delete表达式
- 类型转换
- 何时发生隐式转换
- 显示转换
- 旧式强制类型转换
C++中的表达式由一个或多个操作数通过操作符组合而成。最简单的表达式仅包含一个字面值常量或变量。每个表达式都会产生一个结果。如果表达式中没有操作符,则其计算结果就是操作数本身。除了特殊的用法外,表达式的结果是 右值,可以读取该结果值,但是不允许对它进行赋值。
算数操作符
一元操作符优先级最高,其次时乘除,最后是加减。其中求模操作中值得注意得是:
-21 % -8; //-5,当两个数都是负数,求模(取余)操作的为负数或0
21 % -5; //1或-4,当两个操作数只有一个负数时,结果值得符号可依据分母或分子得符号而定,这两种操作得结果取决于机器
expr1 && expr2;
expr3 || expr4;
逻辑与和逻辑或总是先计算左操作数,只有在左操作数无法确定逻辑表达式得结果时,才会计算右操作数。这种求值策略被称为“短路求值”。
位操作符
位操作符操作的整数类型可以是有符号的也可以是无符号的。如果操作数为负数,则位操作符如何处理操作数的符号位依赖于机器。由于系统不能确保如何处理操作数的符号位,所以建议使用unsigned整型操作数。
bitset对象或整型值的使用
bitset<30> bitset_quizl;
unsigned long int_quizl = 0;
//操作第27位
bitset_quizl.set(27); // set置为1,reset置为0
int_quizl |= 1UL << 27; // 置1
int_quizl &= ~(1UL << 27); // 置0
一般而言,标准库提供的bitset操作更加直接、更容易阅读和书写、正确的使用的可能性更高。而且bitset对象的大小不受unsigned的位数限制。
将位移操作符用作IO
输入输出标准库分别重载了位操作符>>和<<用于输入和输出。重载的操作符和内置版本有相同的优先级和结合性。优先级比算数操作符低,但比关系操作符、赋值操作符和条件操作符高。
cout << 42 + 10;
cout << (10 < 42);
cout << 10 < 42; // ❌
赋值操作符
赋值操作符的右结合性
int ival, jval;
ival = jval = 0;
值得注意的是,操作对象必须具有相同的类型,或者具有可转换位同一类型的数据类型。
赋值操作具有低优先级
int i = get_value();
while(i != 42)
{
// do something();
i = get_value();
}
// 更简洁的写法
while((i = get_value()) != 42)
{
// do something;
}
第二种写法更加简洁和清晰。值得注意的是在赋值操作中的圆括号是必须的,因为赋值操作符的优先级低于不等操作符。
自增和自减操作符
- 后置操作符返回未加1的值
int i = 0, j;
j = ++i; // i = 1, j = 2;
j = i++; // i = 2, j = 1;
前置操作返回自增之后的结果,后置操作返回自增之前的结果。只有在必要时才使用后置操作。因为其在实现的过程中回产生一个临时对象,从而增加时间开销。
- 在单个表达式中组合使用解引用和自增操作
vector<int>::iterator iter = ivec.begin();
while(iter != ivec.end())
{
cout << *iter++ << endl;
}
自增操作的优先级高于解引用,因此先自增,在解引用。后置自增返回自增之前的值,因此解引用可以从0开始。
条件操作符
- 嵌套条件操作符
int i = 10, j = 20, k = 40;
int max = i > j? i > k? i : k
: j > k? j : k;
- 在输出表达式中使用条件操作符
cout << (i < j ? i : j);
cout << i < j ? i : j; // ❌
条件表达式的优先级很低,想要不出错,最好用圆括号把表达式括起来。
sizeof操作符
sizeof表达式的结果是编译时常量,通常有以下三种语法形式:
- sizeof(type name);
- sizeof(expr);
- sizeof expr;
// 以下三种得到都是Sales_item的大小
Sales_item item, *p;
sizeof(Sales_item);
sizeof item;
sizeof *p;
优先级
上表按照优先级顺序列出了C++的全部操作符。该表不同优先级的操作符划分到不同段中,每段内各个操作符优先级相同,且都高于后面各段中的操作符。值得注意的后自增操作优先级比前自增操作的优先级更高。
new和delete表达式
- 初始化
\\ 动态创建类类型,回自动调用默认构造函数
string *s = new string;
\\ 内置类型,不会自动初始化
int *pi = new int;
\\ 可通过在类型名后加(),进行值初始化
int *pi = new int();
- new分配内存失败
分配失败系统将抛出名为bad_alloc的异常。 - 在delete之后,重设指针的值
delete p;
p = nullptr;
在delete动态分配的之后,p变成没有意义。在很多机器之上,尽管p没有意义,但仍存放了它之前所指向地址的值,然后p所指向的内存已经被释放,因此p不在有效。
删除指针之后,该指针变成垂悬指针。为了防止出错,在删除指针之后,立即将其置为0。
类型转换
由编译器自动进行类型转换称之为隐式类型转换。
何时发生隐式转换
在下列情况下,将发生隐式类型转换:
- 混合类型的表达式中,其操作数转化为相同的类型
int ival;
double dval;
ival >= dval; // ival转化为double,一般向精度更大的类型进行转换
- 用作条件表达式被转换为bool类型
int ival;
if(ival)
while()
&&, ||, !, ?:等等
- 用一表达式初始化或赋值某个变量,该表达式结果转化为该变量类型。
显示转换
显示转换也称为强制类型转换(cast),包括static_cast、dynamic_cast、const_cast和reinterpret_cast。
- 何时需要强制类型转换
double dval;
int ival;
ival *= dval; // ival * dval,ival先转double,ival = ival * dval,赋值操作结果转int
ival *= static_cast<int>(dval); // 有效减少隐式转换的次数
- 使用场景
// dynamic_cast,支持运行时识别指针或引用所指向的对象
// const_cast,添加移除const属性,const_cast<new_type>(expression)
// static_cast,编译器隐式转换的任何类型都可以由static_cast显示完成
// reinterpret_cast,通常为操作数的位模式提供低层次的重新编译。本质上依赖于机器。
//为了安全的使用,要求程序员完全理解所设计的类型,以及编译器实现强制类型转换的细节。
// 转换后的类型通常与原类型完全不相关,用于底层操作,转换为完全不相关的类型,如指针与整数之间的转换。
//这种转换是危险的,可能会导致未定义行为,特别是在内存布局不一致的情况下
int *ip;
char *pc = reinterpret_cast<char*>(ip); // 程序员必须永远记着pc所指向的真实对象是int型,而非字符数组。任何假设pc是字符指针的应用,可能带来未知的错误。
旧式强制类型转换
在引入相知类型转换操作符之前,显示强制类型转换用圆括号实现。
// 以下两种写法都可
char *pc = (char*) ip;
char *pc = char*(ip);
旧式强制类型转换具有和static_cast、const_cast以及reinterpret_cast一样的行为。在合法使用前两者的场景中,提供了与各自对应命名强制转换一样的功能。如果这两种转换均不合法,则执行reinterpret_cast的功能。
支持旧式强制类型转换是为了对保持老程序的兼容性,一般建议使用命名强制转换。