C语言初阶指针,如何巧妙运用,构建长尾词的奥秘?

2026-04-12 03:511阅读0评论SEO教程
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计4025个文字,预计阅读时间需要17分钟。

C语言初阶指针,如何巧妙运用,构建长尾词的奥秘?

1. 指针是什么?指针是最小内存单元(大小:1byte)的代码,即内存单元的地址(地址也称为指针)。在头文件中,通常说的指针指的是指针变量,用来存放地址的变量。例如:cint main() { // ...}

1.指针是什么?

指针是最小内存单元(大小:1byte)的编号,即地址(把内存单元的编号称为地址,地址也叫指针)

口头语中说的指针通常指的是指针变量,用来存放地址的变量

int main() { int a = 10;//a是整型变量,占4个字节的内存空间 int* pa = &a;//pa是一个指针变量,用来存放地址的 //取地址取出的是起始字节的地址 return 0; }

总结:

  • 指针就是地址,口语中的指针通常指的是指针变量
  • 指针变量里面存放的是地址,而通过这个地址,就可以找到一个内存单元
  • 指针变量是用来存放地址的,地址是标识一块内存空间的值;指针的大小在32位平台是4个字节,在64位平台是8个字节

2.指针和指针类型

X86 32位的环境 X64 64位的环境

#include <stdio.h> int main() { char *pc = NULL; short *ps = NULL; int *pi = NULL; double *pd = NULL; //sizeof返回值类型是unsigned int,用%u打印比较合适 //%zu打印最准确(为sizeof准备的打印格式) printf("%zu\n", sizeof(pc)); printf("%zu\n", sizeof(ps)); printf("%zu\n", sizeof(pi)); printf("%zu\n", sizeof(pd));//都是4/8 return 0; }

发现指针大小和指针类型没有关系,只和编译环境是32/64位有关,那类型到底有什么用,为什么要有这么多指针类型?

指针类型的意义

  • 指针类型决定了指针进行解引用时有几个字节的访问权限
  • 指针+-整数一次跳过几个字节

int a = 0x11223344; int* pa = &a; *pa = 0;

一次改动改了4个字节

int a = 0x11223344; char* pc = (char*) &a; //int*的地址强制类型转换char*就没有warning //pc在x86环境下是4个字节可以存放下a的地址 *pc = 0;

pc有能力存放下a的地址 pc=&a 说明地址存进去了

C语言初阶指针,如何巧妙运用,构建长尾词的奥秘?

只改动了一个字节

指针类型的意义1:指针变量的类型不决定指针变量所占大小,指针类型决定了指针变量在被解引用操作时访问几个字节,是整型指针int*解引用访问4个字节,是字符型指针char*解引用访问1个字节,double型的指针解引用访问8个字节

int a = 0x11223344; //进制只是表示数字的不同形式 int* pa = &a; char* pc = (char*)&a; printf("pa=%p\n", pa); printf("pa+1=%p\n", pa+1); printf("pc=%p\n", pc); printf("pc+1=%p\n", pc+1);

pa与pc都是取出a的起始字节的地址 pa+1跳过4个字节 pc+1跳过1个字节

指针类型的意义2:指针的类型决定了指针变量+1-1操作时,跳过几个字节,决定了指针的步长,int*指针+1跳过4个字节 char*指针+1跳过1个字节

不同指针类型的指针变量解引用访问字节数和+1-1跳过字节数不同,访问内存更加方便

//int*想访问一个字节强制类型转换为char* int* pi = &a; pi+2; //跳过8个字节 char* pc = &a; pc+2; //跳过2个字节 //+-后不仅可以跟1,只要是整数就可以

对于相同长度的类型可以进行混用吗? 不可以 类型是一种视角

int a = 0; int* pi = &a; //pi解引用访问4个字节 pi+1跳过4个字节 float* pf = &a;//pf解引用访问4个字节 pf+1跳过4个字节 *pi = 100; *pf = 100.0;

int*和float*是不是可以通用?

不能,虽然这两个指针的权限都一样(解引用访问空间大小,+1-1跳过大小),但是站在pi角度,它认为它指向的内存的是整型数据(按整数的存储方式存进去),站在pf角度,它认为它指向的内存空间放的是浮点型数据(按浮点数的存储方式存进去),浮点数和整数在内存中的存储方式是有差异的,存进内存的值是有所差异的,所以不能混用

按整数的存储方式存进去

按浮点数的存储方式存进去

注意:

  • 指针变量的大小跟通过指针变量访问内存空间的大小没有关系,不是一回事,指针变量访问空间大小取决于类型。
  • 指针变量的大小(4/8取决于地址大小)和指针变量所指向的空间的大小(取决于类型)没有关系

3.野指针

指针指向的位置是不可知的(随机的,不正确的,没有明确限制的)

3.1 野指针成因

1.指针未初始化

int* p; //p没有初始化,就意味着没有明确的指向 //一个局部变量不初始化,放的是随机值:0xcccccccc //这个地址是非法的,不属于我们自己的地址,通过地址找到的空间也不是我们的 *p = 10; //非法访问内存 没有初始化进行解引用,这里的p就是野指针

2.指针越界访问

int arr[10] = {0}; //arr[i]访问数组元素 现在用指针来访问数组 int* p = arr;//arr数组名,首元素地址即&arr[0] int i = 0; for(i = 0; i <= 10; i++) //循环11次 第11次访越界问了 此时的p进行解引用,p就是野指针 {//当指针指向的范围超出数组arr的范围时,p就是野指针 *p = i; p++; }

p指向数组的首元素,每+1跳过1个int,解引用也是访问一个整型

3.指针指向的空间释放

int* test() { int a = 10; return &a; } //a是局部变量,一出test()就会销毁(空间还给操作系统了,空间不属于当前程序,不能用) //空间销毁了,这块空间(变量a的空间)不属于当前程序 //调用结束后,当前空间还给操作系统,当前程序没有这块空间的使用权限 //内存中这块空间还存在 //p里面保存着a的地址,p有能力通过地址找到这块空间,但是p不能访问使用这块空间 int main() { int *p = test(); return; }


3.2 如何规避野指针

1.创建指针变量要初始化明确值(知道应该赋的值就赋明确值),不知道初始化什么值就初始化为空指针:NULL(指针变量没有指向,是个空指针)

int a = 10; int* p1 = &a;//初始化 赋明确值 *p1 = 20;//使用指针进行修改a int* p2 = NULL;//不知道初始化什么值就初始化为空指针 //NULL 本质上是0 相当于int b=0; //不是说NULL没有指向嘛,空指针可以用它吗 //*p2 = 100;//可以这样使用空指针吗 不可以 err //0地址,没有指向有效空间 不能直接访问 if(p2!=NULL)//想使用p2 要先进行判断,不为NULL(有了有效指向),就可以使用 {//也可以写成if(p2)但是还是建议写成p!=NULL(不是野指针,指向有效空间)更加直观 *p2 = 100; } //但是也不是决对安全的,避免不了因为局部变量的空间释放造成的野指针

不能直接使用空指针

变量的空间销毁了为什么还会打印输出10?

此处判断失效,没有起到作用 p中存放了地址,肯定不为空指针 判断失效 不能避免野指针出现 p通过非法地址找到那块空间(空间不属于当前程序,但是还是存在的)时当前空间没有被覆盖(没有被使用)所以打印输出10 p访问非法地址指向的空间,打印结果是不一样的(随机的)

p访问非法地址指向的空间,打印结果是随机的

2.小心指针越界

3.指针变量指向的空间被释放,及时置NULL

4.避免返回局部变量的地址

5.指针使用前检查有效性

4.指针运算

指针+-整数 指针-指针 指针的关系运算 指针的解引用都是指针运算

4.1 指针+-整数

vp指向首元素,先*vp=0 再 vp+1往后移动

//vp没有初始化是野指针 还没有使用 不会有问题 //vp=&values[0] 给指针赋值,指针有指向了,不再是野指针 *vp++=0; //*vp=0; //vp++; *vp++; //表达式的值是vp的值,vp先进行解引用vp* 再进行vp++ (没有对指向对象++) 地址++ //等价于 *vp; vp++; (*vp)++;//先进行vp*找到vp指向的对象,对vp*(vp指向的对象)进行++ int arr[5] = {0}; int* p; p < &arr[5]; //算不算数组下标越界,可以这样使用,虽然空间不属于此程序 //只是取地址比较 没有问题

//数组初始化 int arr[10] = {0}; int i = 0; int sz = sizeof(arr) / sizeof(arr[0]); for(i = 0; i < sz; i++) {//数组下标写法:全初始化为1 arr[i] = 1; } //指针写法 int* p = arr;//数组名:首元素地址 for(i = 0; i < sz; i++) {//第一种写法 *p = 1; p++; //第二种方法 //*(p+i) = 1; }

4.2 指针-指针

指针-指针的绝对值得到的是指针之间元素的个数(不是空间相减,只是指针变量相减;&arr[i]是地址,地址是存放到指针变量的,理解为两个指针变量相减,而不是两个地址相减)

int arr[10] = {0}; printf("%d\n", &arr[9]-&arr[0]);//9 printf("%d\n", &arr[0]-&arr[9]);//-9 //分析: //&arr[9] 和 &arr[0] 分别是指向第10个元素和第1个元素的指针 //int* p = &arr[0];//&arr[0]首元素地址 //也可以写成 //int* p = arr; //p+9 = &arr[9]; //所以 &arr[9]-&arr[0]=(p+9)-p=9

int arr[10] = {0}; char ch[5] = {0}; printf("%d\n", &arr[5]-&ch[0]);//err 没有意义

注意:

  1. 不是所有的指针都能相减,指向同一块空间的两个指针(相同类型)才能相减,这样才有意义
  2. 指针+指针(即地址+地址)没有意义

#include <stdio.h> #include <string.h> int main() {//用库函数求字符串长度 int len = strlen("abcdef"); printf("%d\n", len);//6 return 0; }

#include <stdio.h> /*int my_strlen(char* str)//方法1 //方法2递归 {//自定义求字符串长度的函数 int count = 0; while(*str != '\0') { count++; str++; } return count; }*/ //方法3:指针-指针 int my_strlen(char* str) {//\0地址-首元素地址就是元素个数 char* start = str; while(*str != '\0') { str++;//跳出循环时指向\0 } return (str-start); } int main() { int len = my_strlen("abcdef"); //字符串传参时,传的是首字符的地址 printf("%d\n", len);// 6 return 0; }

4.3 指针的关系运算

比较大小

vp开始指向数组往后越界的元素,先vp--往前移动 再*vp=0

分析: *--vp = 0; vp = vp-1; *vp = 0;

分析:vp最后指向在数组首元素前面一个的元素处,但是要避免出现这种情况

允许p1和p2(向后越界)比较,不允许p1和p3(向前越界)比较,可以往后越界的指针与之比较,不允许与向前偏移的指针比较

5.指针和数组

//数组:一组相同类型元素的集合(一组数据,内存中连续开辟的一块空间) //数组的大小取决于元素的个数,元素的类型 //指针变量:存放地址的变量 //指针的大小4/8 取决于32位环境还是64位环境 //联系:数组名是首元素地址 通过指针来访问数组 int mian() { int arr[10] = {0}; //arr是首元素地址 //&arr[0] 首元素地址 int* p = arr; //指针变量p和数组arr就建立了联系 p存放了数组首元素地址 p指向了数组arr //通过指针来访问数组 int sz = sizeof(arr) / sizeof(arr[0]); int i = 0; for(i = 0; i < sz; i++) { printf("%d", *(p+i)); //p+i p是整型指针,p+i跳过i个整型,指向下标为i的数组元素 //*p p指向的数组元素 p+1 p指向第一个元素 //*(p+i) 跳过i个元素 进行解引用就是下标为i的数组元素 } return 0; }

#include <stdio.h> int mian() { int arr[10] = {0}; int* p = arr; int sz = sizeof(arr) / sizeof(arr[0]); int i = 0; for(i = 0; i < sz; i++) { printf("%p--------%p\n", &arr[i], p+i); //&arr[i] 下标为i的元素地址 p放的是首元素地址 p+i跳过i个元素(整型) } return 0; }

地址完全相同

说明这个指针变量p指向数组的首元素时,p+i和&arr[i](下标为i的数组元素的地址)一样的,p+i,跳过i个整型,指向下标为i的数组元素,就是下标为i的数组元素的地址,即&arr[i]。

//打印数组元素 三种方式等价 printf("%d ", arr[i]); printf("%d ", *(p+i)); //又因为int* p=arr; p中存放首元素地址arr printf("%d ", *(arr+i)); //arr数组名表示首元素地址 //arr+i,跳过i个元素,指向下标为i的数组元素,就是下标为i的数组元素的地址

arr[i]与*(arr+i)等价,arr[i]本质上计算时是通过arr+i(下标为i的元素的地址)找到下标为i的数组元素,然后解引用。( arr[i]<=====>*(arr+i) )

//数组传参 #include <stdio.h> /*void test(int* p, int sz) { int i = 0; for(i = 0; i < sz; i++) {//指针形式 printf("%d ", *(p+i));//p就是首元素地址 } }*/ void test(int arr[], int sz) { int i = 0; for(i = 0; i < sz; i++) {//数组形式 printf("%d ", arr[i]);//arr[i]计算时还是*(arr+i) } } int main() { int arr[10] = {0}; test(arr,10);//传参传的是首元素地址 return 0; }

6.二级指针

#include <stdio.h> int mian() { int a = 10; int* pa = &a;//pa是一个指针变量,一级指针变量 //一级指针变量:通过pa直接去找a,找一次就找到了,对pa进行一次解引用就找到a了 //即*pa=a *pa = 20; printf("%d\n", a);//20 //pa也是变量,也是有自己的空间,也有地址 int** ppa = &pa; //ppa是二级指针变量 存放ppa地址 //int* *ppa 或 int** ppa都可以 //通过ppa找到变量a需要进行两次解引用 所以叫二级指针变量 //*ppa--->pa *pa--->a 即 **ppa **ppa=20; //**ppa与*pa等价 printf("%d\n", a);//20 //起始地址 return 0; }

*pa = a 通过修改*pa达到修改a的效果

ppa存放指针变量pa的地址,是二级指针变量

指针类型的拆解:

int* pa; //*说明pa是指针 int说明pa所指向的对象的类型是int int** ppa;//*(后面的*)说明ppa是指针 int*说明ppa所指向的对象pa的类型整体叫int*

总结:二级指针变量是用来存放一级指针变量的地址

7.指针数组

存放指针的数组就是指针数组

//一组相同类型的指针变量的集合 #include <stdio.h> int main() { int a = 10; int b = 20; int c = 30; //多个整型变量 可以创建一个整型数组 int arr[10]; int* pa = &a; int* pb = &b; int* pc = &c; //多个整型指针 可以创建一个整型指针数组 int* parr[10] = {&a,&b,&c}; //int*数组元素是整型指针 //parr存放指针的数组 指针数组 //parr数组名,是首元素地址 //访问指针数组 int i = 0; for(i = 0; i < 3; i++) { printf("%d\n", *(parr[i])); } return 0; }

可以用指针数组模拟出一个二维数组

int arr[3][4] = {1,2,3,4,2,3,4,5,3,4,5,6}; //1 2 3 4 //2 3 4 5 //3 4 5 6 //访问二维数组 int i = 0, j = 0; for(i = 0; i < 3; i++) { for(j = 0; j < 4; j++) { printf("%d\n", arr[i][j]) } printf("\n"); }

模拟的二维数组

int arr1[4] = {1,2,3,4}; int arr2[4] = {2,3,4,5}; int arr3[4] = {3,4,5,6}; //关联3个数组:数组名 首元素地址 int* parr[3] = {arr1,arr2,arr3};//parr数组元素是首元素地址 //指向了每个数组(arr1 arr2 arr3)的首元素 int i = 0 , j = 0; for(i = 0; i < 3; i++) { for(j = 0; j < 4; j++) { printf("%d\n",parr[i][j]); } printf("\n"); } //parr[i]拿到的是数组arr1 arr2 arr3起始地址(首元素地址) //也就是数组名:arr1 arr2 arr3 //访问数组元素采用(arr[i]) 数组名[下标] 此处数组名是parr[i]下标用j //即 parr[i][j]

把三个一维数组关联起来, 达到二维数组效果

本文共计4025个文字,预计阅读时间需要17分钟。

C语言初阶指针,如何巧妙运用,构建长尾词的奥秘?

1. 指针是什么?指针是最小内存单元(大小:1byte)的代码,即内存单元的地址(地址也称为指针)。在头文件中,通常说的指针指的是指针变量,用来存放地址的变量。例如:cint main() { // ...}

1.指针是什么?

指针是最小内存单元(大小:1byte)的编号,即地址(把内存单元的编号称为地址,地址也叫指针)

口头语中说的指针通常指的是指针变量,用来存放地址的变量

int main() { int a = 10;//a是整型变量,占4个字节的内存空间 int* pa = &a;//pa是一个指针变量,用来存放地址的 //取地址取出的是起始字节的地址 return 0; }

总结:

  • 指针就是地址,口语中的指针通常指的是指针变量
  • 指针变量里面存放的是地址,而通过这个地址,就可以找到一个内存单元
  • 指针变量是用来存放地址的,地址是标识一块内存空间的值;指针的大小在32位平台是4个字节,在64位平台是8个字节

2.指针和指针类型

X86 32位的环境 X64 64位的环境

#include <stdio.h> int main() { char *pc = NULL; short *ps = NULL; int *pi = NULL; double *pd = NULL; //sizeof返回值类型是unsigned int,用%u打印比较合适 //%zu打印最准确(为sizeof准备的打印格式) printf("%zu\n", sizeof(pc)); printf("%zu\n", sizeof(ps)); printf("%zu\n", sizeof(pi)); printf("%zu\n", sizeof(pd));//都是4/8 return 0; }

发现指针大小和指针类型没有关系,只和编译环境是32/64位有关,那类型到底有什么用,为什么要有这么多指针类型?

指针类型的意义

  • 指针类型决定了指针进行解引用时有几个字节的访问权限
  • 指针+-整数一次跳过几个字节

int a = 0x11223344; int* pa = &a; *pa = 0;

一次改动改了4个字节

int a = 0x11223344; char* pc = (char*) &a; //int*的地址强制类型转换char*就没有warning //pc在x86环境下是4个字节可以存放下a的地址 *pc = 0;

pc有能力存放下a的地址 pc=&a 说明地址存进去了

C语言初阶指针,如何巧妙运用,构建长尾词的奥秘?

只改动了一个字节

指针类型的意义1:指针变量的类型不决定指针变量所占大小,指针类型决定了指针变量在被解引用操作时访问几个字节,是整型指针int*解引用访问4个字节,是字符型指针char*解引用访问1个字节,double型的指针解引用访问8个字节

int a = 0x11223344; //进制只是表示数字的不同形式 int* pa = &a; char* pc = (char*)&a; printf("pa=%p\n", pa); printf("pa+1=%p\n", pa+1); printf("pc=%p\n", pc); printf("pc+1=%p\n", pc+1);

pa与pc都是取出a的起始字节的地址 pa+1跳过4个字节 pc+1跳过1个字节

指针类型的意义2:指针的类型决定了指针变量+1-1操作时,跳过几个字节,决定了指针的步长,int*指针+1跳过4个字节 char*指针+1跳过1个字节

不同指针类型的指针变量解引用访问字节数和+1-1跳过字节数不同,访问内存更加方便

//int*想访问一个字节强制类型转换为char* int* pi = &a; pi+2; //跳过8个字节 char* pc = &a; pc+2; //跳过2个字节 //+-后不仅可以跟1,只要是整数就可以

对于相同长度的类型可以进行混用吗? 不可以 类型是一种视角

int a = 0; int* pi = &a; //pi解引用访问4个字节 pi+1跳过4个字节 float* pf = &a;//pf解引用访问4个字节 pf+1跳过4个字节 *pi = 100; *pf = 100.0;

int*和float*是不是可以通用?

不能,虽然这两个指针的权限都一样(解引用访问空间大小,+1-1跳过大小),但是站在pi角度,它认为它指向的内存的是整型数据(按整数的存储方式存进去),站在pf角度,它认为它指向的内存空间放的是浮点型数据(按浮点数的存储方式存进去),浮点数和整数在内存中的存储方式是有差异的,存进内存的值是有所差异的,所以不能混用

按整数的存储方式存进去

按浮点数的存储方式存进去

注意:

  • 指针变量的大小跟通过指针变量访问内存空间的大小没有关系,不是一回事,指针变量访问空间大小取决于类型。
  • 指针变量的大小(4/8取决于地址大小)和指针变量所指向的空间的大小(取决于类型)没有关系

3.野指针

指针指向的位置是不可知的(随机的,不正确的,没有明确限制的)

3.1 野指针成因

1.指针未初始化

int* p; //p没有初始化,就意味着没有明确的指向 //一个局部变量不初始化,放的是随机值:0xcccccccc //这个地址是非法的,不属于我们自己的地址,通过地址找到的空间也不是我们的 *p = 10; //非法访问内存 没有初始化进行解引用,这里的p就是野指针

2.指针越界访问

int arr[10] = {0}; //arr[i]访问数组元素 现在用指针来访问数组 int* p = arr;//arr数组名,首元素地址即&arr[0] int i = 0; for(i = 0; i <= 10; i++) //循环11次 第11次访越界问了 此时的p进行解引用,p就是野指针 {//当指针指向的范围超出数组arr的范围时,p就是野指针 *p = i; p++; }

p指向数组的首元素,每+1跳过1个int,解引用也是访问一个整型

3.指针指向的空间释放

int* test() { int a = 10; return &a; } //a是局部变量,一出test()就会销毁(空间还给操作系统了,空间不属于当前程序,不能用) //空间销毁了,这块空间(变量a的空间)不属于当前程序 //调用结束后,当前空间还给操作系统,当前程序没有这块空间的使用权限 //内存中这块空间还存在 //p里面保存着a的地址,p有能力通过地址找到这块空间,但是p不能访问使用这块空间 int main() { int *p = test(); return; }


3.2 如何规避野指针

1.创建指针变量要初始化明确值(知道应该赋的值就赋明确值),不知道初始化什么值就初始化为空指针:NULL(指针变量没有指向,是个空指针)

int a = 10; int* p1 = &a;//初始化 赋明确值 *p1 = 20;//使用指针进行修改a int* p2 = NULL;//不知道初始化什么值就初始化为空指针 //NULL 本质上是0 相当于int b=0; //不是说NULL没有指向嘛,空指针可以用它吗 //*p2 = 100;//可以这样使用空指针吗 不可以 err //0地址,没有指向有效空间 不能直接访问 if(p2!=NULL)//想使用p2 要先进行判断,不为NULL(有了有效指向),就可以使用 {//也可以写成if(p2)但是还是建议写成p!=NULL(不是野指针,指向有效空间)更加直观 *p2 = 100; } //但是也不是决对安全的,避免不了因为局部变量的空间释放造成的野指针

不能直接使用空指针

变量的空间销毁了为什么还会打印输出10?

此处判断失效,没有起到作用 p中存放了地址,肯定不为空指针 判断失效 不能避免野指针出现 p通过非法地址找到那块空间(空间不属于当前程序,但是还是存在的)时当前空间没有被覆盖(没有被使用)所以打印输出10 p访问非法地址指向的空间,打印结果是不一样的(随机的)

p访问非法地址指向的空间,打印结果是随机的

2.小心指针越界

3.指针变量指向的空间被释放,及时置NULL

4.避免返回局部变量的地址

5.指针使用前检查有效性

4.指针运算

指针+-整数 指针-指针 指针的关系运算 指针的解引用都是指针运算

4.1 指针+-整数

vp指向首元素,先*vp=0 再 vp+1往后移动

//vp没有初始化是野指针 还没有使用 不会有问题 //vp=&values[0] 给指针赋值,指针有指向了,不再是野指针 *vp++=0; //*vp=0; //vp++; *vp++; //表达式的值是vp的值,vp先进行解引用vp* 再进行vp++ (没有对指向对象++) 地址++ //等价于 *vp; vp++; (*vp)++;//先进行vp*找到vp指向的对象,对vp*(vp指向的对象)进行++ int arr[5] = {0}; int* p; p < &arr[5]; //算不算数组下标越界,可以这样使用,虽然空间不属于此程序 //只是取地址比较 没有问题

//数组初始化 int arr[10] = {0}; int i = 0; int sz = sizeof(arr) / sizeof(arr[0]); for(i = 0; i < sz; i++) {//数组下标写法:全初始化为1 arr[i] = 1; } //指针写法 int* p = arr;//数组名:首元素地址 for(i = 0; i < sz; i++) {//第一种写法 *p = 1; p++; //第二种方法 //*(p+i) = 1; }

4.2 指针-指针

指针-指针的绝对值得到的是指针之间元素的个数(不是空间相减,只是指针变量相减;&arr[i]是地址,地址是存放到指针变量的,理解为两个指针变量相减,而不是两个地址相减)

int arr[10] = {0}; printf("%d\n", &arr[9]-&arr[0]);//9 printf("%d\n", &arr[0]-&arr[9]);//-9 //分析: //&arr[9] 和 &arr[0] 分别是指向第10个元素和第1个元素的指针 //int* p = &arr[0];//&arr[0]首元素地址 //也可以写成 //int* p = arr; //p+9 = &arr[9]; //所以 &arr[9]-&arr[0]=(p+9)-p=9

int arr[10] = {0}; char ch[5] = {0}; printf("%d\n", &arr[5]-&ch[0]);//err 没有意义

注意:

  1. 不是所有的指针都能相减,指向同一块空间的两个指针(相同类型)才能相减,这样才有意义
  2. 指针+指针(即地址+地址)没有意义

#include <stdio.h> #include <string.h> int main() {//用库函数求字符串长度 int len = strlen("abcdef"); printf("%d\n", len);//6 return 0; }

#include <stdio.h> /*int my_strlen(char* str)//方法1 //方法2递归 {//自定义求字符串长度的函数 int count = 0; while(*str != '\0') { count++; str++; } return count; }*/ //方法3:指针-指针 int my_strlen(char* str) {//\0地址-首元素地址就是元素个数 char* start = str; while(*str != '\0') { str++;//跳出循环时指向\0 } return (str-start); } int main() { int len = my_strlen("abcdef"); //字符串传参时,传的是首字符的地址 printf("%d\n", len);// 6 return 0; }

4.3 指针的关系运算

比较大小

vp开始指向数组往后越界的元素,先vp--往前移动 再*vp=0

分析: *--vp = 0; vp = vp-1; *vp = 0;

分析:vp最后指向在数组首元素前面一个的元素处,但是要避免出现这种情况

允许p1和p2(向后越界)比较,不允许p1和p3(向前越界)比较,可以往后越界的指针与之比较,不允许与向前偏移的指针比较

5.指针和数组

//数组:一组相同类型元素的集合(一组数据,内存中连续开辟的一块空间) //数组的大小取决于元素的个数,元素的类型 //指针变量:存放地址的变量 //指针的大小4/8 取决于32位环境还是64位环境 //联系:数组名是首元素地址 通过指针来访问数组 int mian() { int arr[10] = {0}; //arr是首元素地址 //&arr[0] 首元素地址 int* p = arr; //指针变量p和数组arr就建立了联系 p存放了数组首元素地址 p指向了数组arr //通过指针来访问数组 int sz = sizeof(arr) / sizeof(arr[0]); int i = 0; for(i = 0; i < sz; i++) { printf("%d", *(p+i)); //p+i p是整型指针,p+i跳过i个整型,指向下标为i的数组元素 //*p p指向的数组元素 p+1 p指向第一个元素 //*(p+i) 跳过i个元素 进行解引用就是下标为i的数组元素 } return 0; }

#include <stdio.h> int mian() { int arr[10] = {0}; int* p = arr; int sz = sizeof(arr) / sizeof(arr[0]); int i = 0; for(i = 0; i < sz; i++) { printf("%p--------%p\n", &arr[i], p+i); //&arr[i] 下标为i的元素地址 p放的是首元素地址 p+i跳过i个元素(整型) } return 0; }

地址完全相同

说明这个指针变量p指向数组的首元素时,p+i和&arr[i](下标为i的数组元素的地址)一样的,p+i,跳过i个整型,指向下标为i的数组元素,就是下标为i的数组元素的地址,即&arr[i]。

//打印数组元素 三种方式等价 printf("%d ", arr[i]); printf("%d ", *(p+i)); //又因为int* p=arr; p中存放首元素地址arr printf("%d ", *(arr+i)); //arr数组名表示首元素地址 //arr+i,跳过i个元素,指向下标为i的数组元素,就是下标为i的数组元素的地址

arr[i]与*(arr+i)等价,arr[i]本质上计算时是通过arr+i(下标为i的元素的地址)找到下标为i的数组元素,然后解引用。( arr[i]<=====>*(arr+i) )

//数组传参 #include <stdio.h> /*void test(int* p, int sz) { int i = 0; for(i = 0; i < sz; i++) {//指针形式 printf("%d ", *(p+i));//p就是首元素地址 } }*/ void test(int arr[], int sz) { int i = 0; for(i = 0; i < sz; i++) {//数组形式 printf("%d ", arr[i]);//arr[i]计算时还是*(arr+i) } } int main() { int arr[10] = {0}; test(arr,10);//传参传的是首元素地址 return 0; }

6.二级指针

#include <stdio.h> int mian() { int a = 10; int* pa = &a;//pa是一个指针变量,一级指针变量 //一级指针变量:通过pa直接去找a,找一次就找到了,对pa进行一次解引用就找到a了 //即*pa=a *pa = 20; printf("%d\n", a);//20 //pa也是变量,也是有自己的空间,也有地址 int** ppa = &pa; //ppa是二级指针变量 存放ppa地址 //int* *ppa 或 int** ppa都可以 //通过ppa找到变量a需要进行两次解引用 所以叫二级指针变量 //*ppa--->pa *pa--->a 即 **ppa **ppa=20; //**ppa与*pa等价 printf("%d\n", a);//20 //起始地址 return 0; }

*pa = a 通过修改*pa达到修改a的效果

ppa存放指针变量pa的地址,是二级指针变量

指针类型的拆解:

int* pa; //*说明pa是指针 int说明pa所指向的对象的类型是int int** ppa;//*(后面的*)说明ppa是指针 int*说明ppa所指向的对象pa的类型整体叫int*

总结:二级指针变量是用来存放一级指针变量的地址

7.指针数组

存放指针的数组就是指针数组

//一组相同类型的指针变量的集合 #include <stdio.h> int main() { int a = 10; int b = 20; int c = 30; //多个整型变量 可以创建一个整型数组 int arr[10]; int* pa = &a; int* pb = &b; int* pc = &c; //多个整型指针 可以创建一个整型指针数组 int* parr[10] = {&a,&b,&c}; //int*数组元素是整型指针 //parr存放指针的数组 指针数组 //parr数组名,是首元素地址 //访问指针数组 int i = 0; for(i = 0; i < 3; i++) { printf("%d\n", *(parr[i])); } return 0; }

可以用指针数组模拟出一个二维数组

int arr[3][4] = {1,2,3,4,2,3,4,5,3,4,5,6}; //1 2 3 4 //2 3 4 5 //3 4 5 6 //访问二维数组 int i = 0, j = 0; for(i = 0; i < 3; i++) { for(j = 0; j < 4; j++) { printf("%d\n", arr[i][j]) } printf("\n"); }

模拟的二维数组

int arr1[4] = {1,2,3,4}; int arr2[4] = {2,3,4,5}; int arr3[4] = {3,4,5,6}; //关联3个数组:数组名 首元素地址 int* parr[3] = {arr1,arr2,arr3};//parr数组元素是首元素地址 //指向了每个数组(arr1 arr2 arr3)的首元素 int i = 0 , j = 0; for(i = 0; i < 3; i++) { for(j = 0; j < 4; j++) { printf("%d\n",parr[i][j]); } printf("\n"); } //parr[i]拿到的是数组arr1 arr2 arr3起始地址(首元素地址) //也就是数组名:arr1 arr2 arr3 //访问数组元素采用(arr[i]) 数组名[下标] 此处数组名是parr[i]下标用j //即 parr[i][j]

把三个一维数组关联起来, 达到二维数组效果