系列前言
本系列参考书目:《深入理解C指针》《C和指针》《征服C指针》
旨在对书中内容进行提炼,将重要结论和底层原理配合丰富的代码实例来加深大家对C指针的理解;
下面是本节的主要内容
- size_t等多种指针常用数据类型介绍
- 指向指针的指针
- 指针常量 常量指针 指向常量的常量指针
- malloc calloc realloc
- 迷途指针
- 简单的返回值为指针的函数
指针常用四种预定义数据类型
- size_t 实际上是一个无符号整型
- ptrdiff_t 表示指针算术运算
- intptr_t uintptr_t 表示用来存储指针地址
// intptr_t可以存储长长整数型指针,而uintptr_t存储无符号整数指针
// 不可以直接将整数赋给uintptr_t,而是需要强转类型才可以
long long num;
intptr_t *p1 = #
// 强转类型后我们可以使用uintptr_t指向整数变量num
uintptr_t *p2 = (uintptr_t *)num;
指针直接加减带来的地址问题
请注意以下定理
- 对某指针直接加减,实际上是对地址进行加减
因为int占用4字节,所以如下代码进行自增后实际上是增加了一个int占用字节的地址,请注意注释写出的地址变化 - 指针相减相加返回两个指针之间的地址“单位差”,譬如对于int类型,一个单位就是4字节
int vector[] = {12, 43, 99};
int *p1 = vector;
printf("%d\n",*p1); // 假设初始地址100
p1++;
printf("%d\n",*p1); // 此时地址为104
p1++;
printf("%d\n",*p1); // 现在地址变成108
int *p2 = vector;
int *p3 = vector+2;
printf("p3-p2=%d\n",p3-p2);
指向指针的指针
所谓指向指针的指针很好理解,就是指针嵌套,依据以下代码做出解析
- 首先构造一数组b,然后用取地址运算符将a指向数组b的首地址!
- 之后新建一int类型的二重指针c,指向指针a(只有对使用&a才可以将a转换成int**的格式,这样才能正常赋值)
- 因为c是一双重指针,所以c[0]实际上就是a,而*c[0]才等于a[0]
- 最后结果显而易见,是1,即第一个元素
int *a, b[] = {1, 2, 3};
a = &b[0]; // 指针指向数组
int **c = &a; // 将指针的指针指向一个指针
printf("%d\n", *c[0]); // result = 1
经典常量与指针结合三板斧
- 指针常量(const在*前):它是一个指针,但无法通过解引用自己来改变指向对象的值
- 指向非常量的常量指针(const在*后):必须指向一非常量,指向对象不可变,但指向对象值可变
- 指向常量的常量指针(const分居*两侧):指向对象不可变,指向对象的值亦不可变
// 指针常量,const放在数据类型的前后无所谓,但必须在*前面
int const *a;
const int *b;
// 指向非常量的常量指针
int *const c;
// 指向常量的常量指针
const int *const d;
易错点
- 当指针被任意多个不限位置的const修饰后,在Printf中需要用*解引用才可以显示其指向的内容
- 而正常的没有被任何const修饰的指针都可以直接调用其指向的数值
malloc内存分配
- 原则:非静态的指针都需要执行内存分配来初始化,否则会出现内存错误
- malloc(sizeof(int))分配一个int大小的内存
- (int *)对分配的内存进行强制数据转换,之后就可以成功的为指针p1分配内存
- 静态的局部指针变量或者全局指针变量无法直接分配内存,需要分开两行写
- malloc calloc realloc都需要头文件stdlib.h
- 使用外指针务必记得free释放内存!不然怎么GC!
// malloc基本用法
int *p1 = (int *) malloc(sizeof(int));
*p1 = 8;
free(p1);
// 静态的局部指针变量或者全局指针变量分配步骤
static int *p2;
p2 = (int *) malloc(sizeof(int));
针对char类型的指针,因为我们要存储字符串,所以可以使用乘法来分配指定的内存,譬如下方代码分配了8字节的内存
char *str = (char *) malloc(8*sizeof(char));
calloc与memset
- calloc表示分配指定数量的内存并且将他们都执行默认初始化
- memset表示内存填充
// calloc第一个参数表示分配数量,第二个为分配大小,下图分配20字节空间
int *p1 = (int *) calloc(5, sizeof(int));
// 参一是欲填充指针,参二是填充数,参三是填充数量
memset(p1,1,5*sizeof(int));
realloc内存大小控制
realloc返回一个void*指针,所以必要情况下需强转类型;
参数一为指针变量,参数二为将该指针变量占据的内存缩小到多少
char *s1 = (char *) malloc(8*sizeof(char));
realloc(s1,3);
迷途指针
也即我们对一个以及free的指针再次执行赋值操作,那么该指针就会指向一不存在的内存(虽然编译器不会报错)
int *p1 = (int *) malloc(sizeof(int));
*p1 = 7;
free(p1);
*p1 = 100;
指针栈与堆
- 程序栈通常和堆共享一块内存,堆占用上部,栈占用下部
- 程序栈存储栈帧,栈帧存放函数参数和局部变量,而堆则负责管理动态内存
- 栈帧的组成部分:返回地址、局部数据存储、参数存储、栈指针与基指针
栈指针:指向栈的顶部,不是C指针
基指针:存在并指向栈帧内部地址,如返回地址,同样也不是C指针
基本返回指针的函数
下方展示了一个返回int指针的函数,我们在main方法里调用它并使用完后,务必记得free掉这个指针!
int* pointer(){
int *nums = (int *) malloc(10*sizeof(int));
for(int i = 0; i < 10; i++){
nums[i] = i;
}
return nums
}
int main(){
int* vector = pointer();
free(vector);
}