C语言系列(14)——指针(02)

一、指针偏移(寻址)

指针类型或指针变量的大小(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指针在当前地址基础上往后偏移一个“单位”,这个单位的大小就是由数据类型决定的。

二、指向一维数组的指针
  1. 数组的本质:
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

  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++ 这个操作是不合法的,数组名是一个常量指针,是不允许改变指向的。

  1. 指向字符数组的指针:
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
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值