一 概述
二 类型
三 语句
四 函数
五 数据
六 内存
七 代码
附录
3. 指针
和地址用于标注内存位置不同,指针是 实体 ,需要为它 分配内存 ,或者说是种用于存储地址的 整数类型 。
- 获取地址,保存到指针:
int *ptr = &var
。 - 透过指针间接读写目标:
*ptr
或*ptr = val
。
ptr var int *ptr = &var; =====//=====+========+=====//=====+=======+========= | 0x??10 | | 100 | memory =====//=====+========+=====//=====+=======+========= | 0x??10 (addr) | ^ | | +--------------------+
初始化
作为一个特殊的整型变量,指针也需要初始化。
NULL
:空指针,零值。void*
: 无类型指针。(仅表达地址存储,转型后使用)T*
: 类型指针。
int main (void) { int x = 100; int *p = &x; *p = 200; return 0; }
有种习惯性写法,不过并不推荐。
int x = 100, *p = &x;
无类型指针可隐式转换为类型指针。
int main (void) { void *p = malloc(sizeof(int)); int *x = p; *x = 100; x = NULL; free(p); return 0; }
当指针不再使用,可以考虑将其赋值为空指针,以避免成为悬垂指针或野指针。
- 悬垂指针 (dangling pointer):所指向内存已经释放。
- 野指针 (wild pointer):指向不确定地址。未初始化,或目标内存另做他用。
运算符
对指针相等或不等比较来判断是否指向同一对象。
int x = 10; int *p1 = &x; int *p2 = &x; assert(p1 == p2);
对指针进行加法运算,使其指向某个元素。注意,步进单位是元素长度,而不是字节。
int x[] = { 0, 1, 2, 3 }; int *p = &x[0]; assert(*(p + 2) == x[2]); p++; assert(*p == x[1]);
对指针进行减法运算,获取元素索引或偏移量。
int x[] = { 0, 1, 2, 3, 4 }; // x ==> &x[0] int *p = &x[3]; assert(p - x == 3);
限定符
使用 const 约束指针,使其成为 指向常量的指针 (ponter to constant object)或 指针类型的常量 (constant pointer)。鉴于简称容易混淆,可标注英文,或用全称。
- 常量指针 :将目标对象看作常量,不能透过指针修改目标,但指针可变。
- 指针常量 :指针自身为常量,可用指针修改目标,但不能修改指针自身。
int const *p1 = &x; 常量指针,const 限定目标对象(*p1)。 int *const p2 = &x; 指针常量,const 限定指针自身(p2)。 你俩瞎扯淡! v +======+ +=======+ +======+ | p1 | -------> | x | <------- | p2 | +======+ +=======+ +======+ ^ ^ 我觉着 x 是块又臭又硬的石头。 没工夫管他,我在练金刚不坏神功。
// 常量指针 int main (void) { int x = 100; int const *p1 = &x; // 习惯写成 const int *p1。 // 不能透过该指针修改目标。 // *p1 += 1; // ^~ error: assignment of read-only location '*p1' // 限制只针对该指针,对其他方式无效。 x += 1; assert(*p1 == 101); // 指针可变,指向新对象。 // 依旧保留 const 限定,始终无法透过它修改目标。 int y = 200; p1 = &y; assert(p1 != &x); // *p1 += 1; // ^~ error: assignment of read-only location '*p1' return 0; }
// 指针常量 int main (void) { int x = 100; int *const p2 = &x; // 指针自身为常量,可修改目标。 *p2 += 1; assert(x == 101 && *p2 == x); // 指针自身不可变。 int y = 200; // p2 = &y; // ^ error: assignment of read-only variable 'p2' return 0; }
当然,还可以混合起来。
int main (void) { const int x = 100; const int *const p = &x; // 两者混合:常量指针 + 指针常量。 // *p += 1; // ^~ error: assignment of read-only location '*p' int y = 200; // p = &y; // ^ error: assignment of read-only variable 'p' return 0; }
对函数中的指针参数,如确定不会修改,可使用 const 限定,避免误操作影响实参。
只要存储内存不是只读,那么就可用强制类型转换方式修改。
从这点上说,const 无论是限定指针还是目标对象,都只是种 “提示”。
int main (void) { const int x = 100; // 栈内存分配。 const int *const p = &x; int *xp = (int *)(p); // 强制类型转换。 *xp = 200; assert(x == 200); return 0; }
另一指针限定符 restrict,并不会对编码有直观影响。它的主要作用是告诉编译器,接下来的操作,我只会用该指针去读写目标对象,不会有其他任何方式(变量名或另一指针)。这样,编译器就无需考虑数据刷新(缓存)等问题,从而尝试生成更加优化的代码。通常不需要处理这些,只在某些特定算法内做性能提升时才会尝试。
多级指针
既然指针是个实体,是个特殊整数,那么我们也可以用另外一个指针指向它,形成 指针的指针 ,或二级指针。常用案例是函数输出参数(out)或字符串数组。
int x = 100; int *p = &x; int **pp = &p; x p pp =====//====+=====+======//======+========+===//===+========+=== | 100 | | 0x??12 | | 0x??34 | memory =====//====+=====+======//======+========+===//===+========+=== 0x??12(addr) 0x??34 | ^ | ^ | | | | | +-------- *p --------+ +----- *pp -----+ | | +------------------------------ **pp ----+
int main (void) { int x = 100; int *p = &x; int **pp = &p; assert(*p == x); assert(*pp == p && **pp == x); return 0; }
多级指针不好维护。相比下面的传出参数,可能返回值更合适,且不用关心 pp 的生命周期。
void test(int **pp) { // 申请一块堆内存,并赋值。 int *x = malloc(sizeof(int)); *x = 100; // pp 是指针的指针,那么 *pp 就是指针的内存。 // 把从堆申请的内存地址保存到指针内存。 *pp = x; } int main (void) { // 虽然没有赋值,但内存是分配了的。 int *p; // 将 p 的地址传入 test 函数。 // 因为 p 是指针,取指针的指针。 test(&p); assert(*p == 100); free(p); return 0; }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论