C语言系列(14)——数组指针
一、指针偏移(寻址)
指针类型或指针变量的大小(sizeof)是4,那为什么还要分多类型的指针呢?
是因为指针偏移
会根据类型的大小
进行偏移。
什么叫偏移(寻址)
,就是指针指向往前移动或往后移动。
例:
int a = 10;
int *p = &a;
printf("%p\n", p);//打印指针指向的变量a所在的地址
p++;//指针往后偏移了一个地址。即指向了变量a后面的一个地址
printf("%p\n", p);
输出结果:往后偏移了4个字节
0073FB70
0073FB74
例:
double a = 10;
double *p = &a;
printf("%p\n", p);//打印指针指向的变量a所在的地址
p++;//指针往后偏移了一个地址。即指向了变量a后面的一个地址
printf("%p\n", p);
输出结果:往后偏移了8个字节
00CFF7C4
00CFF7CC
**SO: ** p++; 或 p = p + 1; 指的是p指针在当前地址基础上往后偏移一个“单位
”,这个单位的大小
就是由数据类型
决定的。
二、指向一维数组的指针
- 数组的本质:
int arr[5] = { 1,2,3,4,5 };
printf("%p\n", arr);
输出结果:001DF77C
所以数组名
实际上就是存储着一个地址,是这个数组(连续的内存地址)的首地址
,所以数组名实际也是一个指针变量
。
所以可以用指针的方式操作数组:
int arr[5] = { 1,2,3,4,5 };
int *p = arr;
printf("%d\n", *arr);
输出结果:1
分析: arr赋值给p,表示p存储
了数组的首地址
。*p,是对该地址进行取值,所以得到结果首元素的值1
。
- 通过指针获取数组的其他元素:
有两种方式:
- 第一个是下标法的指针表达方式:
*(p1+1)
; - 第二种是先偏移指针,再取值:
*(++p2)
;或p2++;*p2
;
两者的区别是,p1指向
始终不变
;p2是改变
指向再取值
。
- 循环取值:
int arr[5] = { 1,2,3,4,5 };
int *p = arr;
int i = 0;
for ( i = 0; i < 5; i++)
{
//printf("%d ", *(p + i));
// 或
printf("%d ", *p++);//先取值,指针再往前偏移 等价于 *p; p++
}
输出结果:1 2 3 4 5
注意:
1、分析*(p+1)与*p++的区别?
2、自行运行循环打印*p+i;并分析原因。
3、*p++与*++p的区别。
既然数组名
也是一个指针变量
,那以下操作是否合法?
int arr[5] = { 1,2,3,4,5 };
int *p = arr;
int i = 0;
for ( i = 0; i < 5; i++)
{
printf("%d ", *(arr + i));// 1.是否合法
printf("%d ", *arr++); // 2.是否合法
}
注:1处合法,2处不合法;
arr++ 这个操作是不合法的,数组名是一个常量指针
,是不允许改变指向的。
- 指向字符数组的指针:
char arr[] = "I love you!";
char* pStr = arr;
printf("%s\n", pStr);
输出结果:I love you!
当然也可以利用指针操作字符串修改其中的元素,其方式与操作整型数组一致。
- 指向常量字符串的指针:
char* ps = "I'm super man";\\1
const char* ps = "I'm super man";\\2
上面两句代码,表达同一个意思,但是1代码在某些环境不能通过编译。
是因为ps指向一个常量字符串
,意味着不能改变改字符串的值
,所以要在前面加上const
。
如:
ps[0] = 'i';//错误,不能改变常量字符串的值。
但是可以改变指针的指向,如:
ps = "哈哈";//指针重新指向了一个字符串。
- const 修饰指针:修饰谁,谁就不能被改变。
char arr[] = "I love you!";
const char* pStr1 = arr;//1
char const *pStr2 = arr;//2
char* const pStr3 = arr;//3
以上三种修饰的意义:
- 1 和 2是同一个意思,
修饰的是“*”
,也就是不能改变其元素
的值。不能执行 pStr1[0] = ‘i’;等操作,但是可以执行pStr = arr2;等操作; - 3
修饰的是“pStr3”
,也就是修饰的指针变量
,那指针变量不能改变其指向
,不能执行pStr3 = “hello”;等操作,但是可以执行pStr3[0] = ‘i’;
注意:整型等其他类型数组和指针也是同样的道理哦。
三、指向二维数组的指针(数组指针)
- 有如下数组,该用什么样的指针,指向呢?
int arr[2][4] = { 1,2,3,4,5,6,7,8 };
注意二维数组的一个特点是,列数是需要定义的时候就指定的。
所以需定义如下指针进行指向:
int(*p)[4] = arr;
- 指针操作二维数组:
printf("%d ", p[0][0]);
printf("%d ", *p[0]);
printf("%d ", *(p+0)[0]);
printf("%d ", *(*(p+0)+0));
printf("%d ", **p);
上述打印表达方式都是同一个意思:都打印第一个元素,1
。
arr也可以做上述一样的操作。
- 地址偏移(寻址):
int arr[2][4] = { 1,2,3,4,5,6,7,8 };
int(*p)[4] = arr;
printf("%p,%p,%p\n", arr, arr + 1, *arr+1);
输出结果:00CFF8A0,00CFF8B0,00CFF8A4
。
二级寻址: arr+1,寻址单位是一个一维数组的大小(5*4)5个int元素。
一级寻址: *arr+1,寻址单位是一个一维数组元素大小(4)是一个int类型。
- 指针操作字符串数组:
char name[5][10] = { "张三疯", "张无忌", "张铁林", "张学友", "zhangsan" };
char(*p)[10] = name;
puts(p[1]);//打印数组中的第二个名字,张无忌
puts(*(p + 1));//同上
putchar(p[4][0]);//打印某个字符,z
putchar(*(*(p + 4) + 0));//同上
练习:
以下题目均用指针进行操作,不使用库函数:
1、字符串拷贝:将字符数组1的内容拷贝纸字符数组2中。
2、字符串拼接:将字符串2中的内容拼接至字符串1的后面(设,字符数组1的空间足够大),如:str1:hello、str2:world 拼接后str1:helloworld
3、字符串查找:在字符串str中,查找是否存在子字符串subStr,如果存在则输出在字符串str中的其实下标,如果不存在这输出-1,如:str:hi,lily。subStr:lily。输出:3
4、有n个人围成一圈,顺序排号。由用户从键盘输入报数的起始位置,从该人开始报数(计数从0开始),凡报数为3的倍数出圈。问最后剩下的是几号?
5、魔术师利用一副牌中的13张牌黑桃,预先将他们排好后迭在一起,并使牌朝下。然后他对观众说:我不看牌,只要数数就能猜到每张牌是什么,我大声数数,你们听,不信?你们就看,魔术师将最上面那张牌为1,把它翻过来正好是A,他将A放到桌子上;然后顺序从上到下数手中的余牌,第二次数1,2,将第一张放到这迭牌的最下面,第二张牌翻过来正好是2,放到桌子上;第三次数1,2,3,将前面两次依次放到这迭牌的下面,第三次翻过来正好是3,这样依次进行,将13张牌全部翻出来,准确无误。问魔术师手中的牌原始顺序是怎么样的?1 8 2 5 10 3 12 11 9 4 7 6 13