指针类型
指针类型是复合类型:a pointer points to an object of some other type
主要两种使用情况:
1. 当我们解除指针的引用(在指针变量前加*号),我们得到了指针所指向的对象;
2. 当我们使用自增操作符(在后面++),我们就使得指针前进以表示数组的下一个元素。
所以,dereference和increment操作符,使用在指针上和使用在迭代器iterator上是一样的。
相对于C而言,指针在C++里面的用途小了很多。
与iterator不同的是:pointer可以指向单一对象,而且可以通过dereference直接访问其所指向的对象;iterator使用在容器中。
指针的值是什么?指针本身的值储存的是另一个对象的地址;
string s(“Hello world”);
string *sp = &s; // *代表这是一个指针,&表示取地址,把s的地址赋值给指针sp;
可以认为每个对象都有地址(引用不是一个对象,所以没有地址)。对于临时对象,可先用const reference持有他,再去取其地址,而且该地址只能赋给pointer to const(因为这样拿到的地址,不能通过反引用写入它)。
PS
& may only apply to lvalue(左值,放在赋值符号的左边和右边都可以,可以读写;rvalue,右值, 只能放在右边,不能写入)比如int val = 10; val就是一个lvalue,取址符号只能用在lvalue上,用完以后&val就变成一个右值,不能放在左边,即不存在引用的引用。
在左值前使用&符号得到的是其地址。解引用操作符*号用在vector, string或者内置类型的数组上,可以得到lvalue,所以当然可以把取址符号用在以上这些操作符的结果上,最后得到特定元素的地址:
&(arr1[15]), &(str[0]), &(*sp), &(vec[0])...
指针可能的值:
4类值可以赋值给指针:
1. 值为0的constant expression编译阶段编译器就能算出值的这种表达式,而且它是整值类型integral type, 比如a const integral object whose value is zero at compile time or a literal constant 0(字面量0: int 0,boolean false,NULL, ‘\0’,“” )
2. 合适类型的对象的内存地址
3. the address one pass the end of another object 最后元素的下一个位置
4. 另一相同类型的合法指针变量
int val = 0;
int *pi = 0; // 指针pi初始化为不指向任何对象,但pi仍是声明为指针,它可以在之后指向int类型的对象
int *pi2 = &val;// OK, pi2初始化为 val的地址
int *pi3; // OK,但危险,pi3未初始化(野指针) 这样的指针它的值不是NULL,而是随机//的值,如果是一块重要的内存,使用不当容易出现问题
pi = pi2;//pi和pi2指向同一个对象
pi2 = 0;// pi2现在不指向任何对象
但是如果 pi2 = val; 这样是错误的,即使val的值是0,但是val是一个int类型。
只能用一个字面量0或者constant expression(其值为0,在编译期间就能确定)。
const int cval = 0; pi2 = cval; // OK
int *pi4 = NULL; // OK,NULL是一个预处理变量
bool *pb = false; // OK
指针赋值的另一要点是:类型匹配
指针的类型和要用来赋值的对象的类型或者另一指针的类型必须匹配,否则出错。(*void除外)
PS:
C++没有办法从语法上确保指针始终有效,一旦指针失效,使用该指针会导致未定义的行为! 没有办法去检查一个指针是否有效(一个non-NULL的指针不一定就是有效的)指针的值是内存地址,没有办法检查这块内存空间是否合法存在,而且是否与指针类型相符。
没有办法检测一个指针所代表的“地址”是合法地址。(C++ common run-time error)
建议: To initialize all variables is particularly important for pointers
尽可能的话,当指针要指向的对象先定义后在定义指针,并且在声明指针的同时初始化。也就是没必要创建不被初始化的指针。
如果一定要先创建指针,先将指针初始化为字面量0;
指针VS引用
引用:永远只绑定一个对象,且声明时必须初始化它;
指针:可以指向其他合适类型的对象
pointer1 = pointer2,仅仅改变指针的值,pointer1指向了pointer2指向的内存地址
reference1 = reference2,改变本尊,reference1引用的对象的值改变了。
二级指针:pointer points to a pointer
指针本身是指针类型对象,所以有引用。
int val = 1024;
int *pi = &val;
int **ppi = π
要得到val,要对ppi两次解引用(加*号)
数组和指针
数组常退化成指向其首元素的指针。
int a[] = {0,2,4,6};
int *ip = ia;//指向ia[0]
ip = &ia[3];//指向ia[3]
指针算术运算
类比时间长度和时刻(某个整数vs指针)
时刻1 ± 某整数=时刻2 <==> 指针1± 某整数=指针2
时刻1 - 时刻2 =某整数(时间跨度) <==> 指针1+指针2 = 某整数(地址跨度)
PS 不能时刻相加
某个整数和指针的类型相关,比如指针ia指向int类型对象,所以在内存上占4个byte,那么int *ip = ia + 10; 是错误的;int *ip = ia + 8正确,且ip指向ia[2]
ptr diff_t n = ip2 - ip1;
size_t, diff_t都是machine_specific size,size_t是unsigned type, diff_t是signed integral type
指针的一些表示方法
int last = *(ia+3); // last等于ia[3]
int last_plus_3 = *ia + 3;// last_plus_3等于ia[3]+3
int a[] = {0,2,4,6,8};
int i = ia[0]; // 等价于 int i = *ia;
int *p = &ia[2]; // 指针p指向ia的下标为2的元素
int j = p[1]; // j = *(p+1),等价于下标为2+1 = 3的元素
int k = p[-2]; // k= *(p-2),等价于下标为2-2 = 0的元素
Off-the-End Pointer 尾部指针
const size_t arr_size = 5;
int arr[arr_size] = { 1, 2, 3, 4, 5};
int *p = arr;
int *p2 = p + arr_size; // p2指向one past the end of the array,使用要小心,一般用于判断是否读到array末尾,不要解引用
p+5就是距离p这个地址5个int那么远的内存地址