《c++ primer》学习笔记 第五章 表达式
注:此次为重读c++ primer,因而主要记录本人易遗忘且相对含糊的知识点,此系列只是个人笔记,读者请勿照搬,建议阅读原书为最佳。
1 位操作符
位移操作符将其整数操作数视为二进制位的集合,为每一位提供检验和设置的功能。另外,这类操作符还可用于bitset类型的操作数,该类型具有这里所描述的整形操作数的行为。
位操作符共有5个: ~ 取反;<< 左移;>>右移;& 位与;^ 位异或;| 位或;
位操作的整数可以是有符号的或无符号的,由于有符号的整数的位操作结果取决于机器,因此强烈建议位操作只应用于unsigned整数。
左移(<<)在右边插入0以补充空位。右移(>>),对于unsigned整数,从左边开始插入0;对于有符号数,则插入符号位的副本或者0值,如何实现视具体情况而定。右移操作数不可以是负数,而且必须是严格小于做操作数的值,否则,结果未定义。
例: bitset<30> b1;
unsigned long ival=0;
若要设置第27位的值为1,则有:
b1.set(27);
ival |= 1ul<<27;
二者等价。实际上,bitset操作比底层的位操作效率更高且更易于理解,因此尽量使用bitset操作。
2 ++i 和 i++
++i是先+1返回i对象本身后在运算,是左值。
i++是先对i进行运算,后执行+1操作,是右值。
int i = 0, j;
j = ++i; // j = 1, i = 1: prefix yields incremented value
j = i++; // j = 1, i = 2: postfix yields unincremented value
为什么推荐使用++i不使用i++?因为++i只需返回+1后的结果即可,而i++必须先保存i当前的值作为操作数。对于int型对象和指针,编译器可优化掉这项额外的工作,但是对于复杂的迭代器类型,这种额外的工作可能会花费更大的代价。因此,从性能方面考虑,尽量使用++i的方式,而只在必要的时候使用i++方式。如:
vector<int> ivec;
int cnt = 10; //将10,9,8.....1放到vector中
while(cnt > 10)
ivec.put_back(cnt--);
在这里需要使用cnt--,因为必须返回cnt减1之前的值才能得到10,如果使用--cnt,加入vector中的就是9,8,7.,,,0.
在单个表达式中组合使用解引用和自增操作符
vector<int>::iterator it = ivec.begin();
while(it != ivec.end())
cout<<*it++<<endl;
由于自增操作的优先级高于解引用操作,因此*it++等价于*(it++),it先+1,然后返回it原值的副本进行解引用,所以应该是it未+1前的副本。
3 sizeof操作符
sizeof操作符返回一个对象或类型名的长度,返回值类型未size_t,长度单位为字节。
将sizeof引用在表达式exp上,将获得该表达式的结果的类型长度。
对于sales_item item, *p;
以下三条语句的意义相同,均为返回sales_item的长度。
sizeof(sales_item);
sizeof item;
sizeof *p;
sizeof求数组元素个数:
int sz = sizeof(ia)/sizeof(*ia);
4 野指针
c++中,删除零值指针是安全的!如果指针的值为0,在其上做delete操作是合法的,但是没有意义。
int *pi = 0;
delete pi; // ok
在delete p;之后,p变成没有定义,在很多机器上,尽管p没有定义,但仍然指向它之前指向对象的地址,然而p所指向的内存已经被释放,因此p不再有效。
一旦删除了指针所指向的对象,立即将指针置为0,这样p将不再指向任何对象,防止野指针引发的程序错误。
5 动态内存管理三种常见的出错原因;
1. 删除(delete)指向动态分配的内存的指针失败,造成内存泄露。
2. 读写已删除的对象。将删除的指针置零,可以避免这类错误。
3. 对同一内存空间使用两次delete操作。当两个指针指向同一个动态创建的对象,删除时就会发生错误。如果在其中一个指针上做delete,将该对象的内存空间返还给自由存储区,那么接着delete第二个指针,此时自由存储区可能会被破坏。
6 类型转换
c++的类型转换分为两种:隐式转换和显式转换。
隐式转换主要是基本类型间,做算术或逻辑操作符的转换。如int转double,int转bool等。
显式转换也成为强制类型转换,有四个操作符:static_cast, dynamic_cast, const_cast和reinterpret_cast.
static_cast: 一般用于基本数据类型之间的强制转换.如:
int ival; double dval;
ival *= static_cast<int>dval;
这样,现将dval转换为int之后再与ival相乘。如果没有显示转换,则是现将ival转换成double,然后与dval相乘得到double的结果,再讲该结果转换成int赋值给ival。
dynamic_cast:支持运行时识别指针或引用所指向的对象。一般用于对指针或类对象之间的转换。
const_cast:转换表达式的const属性,只用来添加或删除const属性。
reinterpret_cast: 为操作数的位模式提供较低层次的转换。reinterpret_cast依赖于机器,是不安全的,尽量不用。
建议:避免使用强制类型转换。一个设计合理的系统应该是不怎么需要强转的,特别是reinterpret_cast和const_cast。而对于static_cast和dynamic_cast,除非能够确定转换的安全性,否则也不宜多用。