【无标题】深入了解指针

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

提示:这里可以添加本文要记录的大概内容:

指针是编程语言中一个很重要的东西,也是编程的基础之一,学会它,你的代码会更加装x,更加实用


提示:以下是本篇文章正文内容,下面案例可供参考

一、字符指针

字符指针通常用char*来修饰
这里有两种写法

#include<stdio.h>

int main()
{
	char* ch = 'w';
	char* pstr = "hello bit.";

	return 0;
}

这里的第一个指针ch指向字符‘w’。
第二个指针pstr指向的是字符串的第一个字符。

我们来看这样一道题

#include <stdio.h>
int main()
{
  char str1[] = "hello bit.";
  char str2[] = "hello bit.";
  const char *str3 = "hello bit.";
  const char *str4 = "hello bit.";
  if(str1 ==str2)
printf("str1 and str2 are same\n");
  else
printf("str1 and str2 are not same\n");
  
  if(str3 ==str4)
printf("str3 and str4 are same\n");
  else
printf("str3 and str4 are not same\n");
  
  return 0;
}

数组str1和str2是分别开辟一段空间,然后将字符串放到这片空间中,而指针str3和str4则是同时指向一片已经开辟且定义为该字符串的空间地址。
所以str1和str2都是数组首元素的地址,很明显是不相等的,而str3等于str4

二、指针数组

整型数组存放整型
字符数组中存放字符
所以指针数组是存放指针的数组

	int* arr1[10];
	char* arr2[10];

这里的数组名是arr1和arr2,元素个数为10个,存放的元素类型为int和char

三、数组指针

3.1、数组指针的定义

整型指针指向整型
字符指针指向字符
数组指针指向的是数组,所以本质是指针

	int(*p)[10];

首先,*和p先结合,保证p是一个指针,指向一个元素个数为10的数组,数组中的元素数据类型为int型

3.2、关于数组名的一些小tips

#include <stdio.h>
int main()
{
int arr[10] = { 0 };
printf("arr = %p\n", arr);
printf("&arr= %p\n", &arr);
printf("arr+1 = %p\n", arr+1);
printf("&arr+1= %p\n", &arr+1);
return 0;
}

在这里插入图片描述

从结果可以得出几个结论
1、arr和&arr取出的地址是相同的
2、arr代表的地址是一个四个字节型的,&arr取出的是一个数组大小的
3、arr取出的是数组首元素的地址,&arr取出的是整个数组的地址

==其实在绝大部分的情况下arr出现都是代表数组首元素的地址,有两个特殊情况,sizeof(arr)和&arr。

3.3、数组指针的应用场景

#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
			printf("\n");
	}
}

void print_arr2(int(*arr)[5], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
	print_arr1(arr, 3, 5);
	print_arr2(arr, 3, 5);
	return 0;
}

在这里插入图片描述

用第二个函数打印时,传进去arr,在二维数组中,arr是首元素地址,而二维数组的首元素地址就是第一行。
所以用一个数组指针来接收
arr[i[[j]就相当于*(*(arr+i)+j)

来看几个有意思的代码

int arr[5];
int *parr1[10];
int (*parr2)[10];
int (*parr3[10])[5];

前三个很简单,那么最后一个怎么解释呢。
首先,parr3首先是和[10]结合,构成一个数组,那么将这一段取出后,剩下的就是这个数组存放的类型,是一个数组指针。

四、关于数组和指针传参的一些问题

4.1、一维数组传参

void test(int arr[])//ok?
{}
void test(int arr[10])//ok?
{}
void test(int *arr)//ok?
{}
void test2(int *arr[20])//ok?
{}
void test2(int **arr)//ok?
{}
int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);
}

对于test1函数中的传参,不做过多赘述
arr2数组很显然是一个指针数组,里面存放的元素类型是int*,传入数组时可以直接写成相同类型的数组,写成指向第一个int* 元素的二级指针,所以以上写法全部正确

4.2、二维数组传参

void test(int arr[3][5])//ok?
{}
void test(int arr[][])//ok?
{}
void test(int arr[][5])//ok?
{}
void test(int *arr)//ok?
{}
void test(int* arr[5])//ok?
{}
void test(int (*arr)[5])//ok?
{}
void test(int **arr)//ok?
{}
int main()
{
int arr[3][5] = {0};
test(arr);
}

数组本身传参时,可以直接传入带列的数组形式(行可以省略,但不可省略每一行的元素个数)
二维数组的数组名是数组首元素的地址,就是数组第一行的地址,所以进函数的时候要写成数组指针的形式

4.3、一级指针传参

传入一级指针,需要在函数的形式参数中多加注意。

4.4、二级指针传参

同一级指针,不加赘述

五、函数指针

函数也是在内存中开辟空间的,那么也就有指向函数的指针
函数名代表着函数的地址,函数指针我们写成这种形式

void (*fun1)(int,int)

函数的返回类型是void,两个形参类型分别为int int。

给出以下两个代码

//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);

在这里插入图片描述
我们取出代码1中间代码,发现他是一个函数指针类型的,所以对0强制类型转化为函数指针类型(无参,返回值为void)然后调用在地址0处符合上面条件的函数,指针指向同一个地方。

在这里插入图片描述
signal首先和圆括号结合,这是一个函数名,取出这一段后,如图,剩下的是函数的返回类型,所以这是一次对signal函数的声明。

六、函数指针数组

看到这个名字,我们首先要思考,这是一个数组,且里面存放的是函数指针类型的元素,结合数组的构造方式,我们给出以下代码

int (*parr[10])()

首先parr和[10]结合,确定数组身份,int(*)()则是数组内的内容

七、指向函数指针数组的指针

看到这里是不是已经有点晕头转向的感觉了,其实解释一下还是很好理解的

(*parr)[10]//parr是一个指针,指向的元素有10个
void(*)(int,int)//每一个元素是函数指针
结合起来就是
void(*(*parr)[10])(int,int)

八、回调函数

回调函数就是一个通过函数指针来调用的函数,回调函数不是由该函数
的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

九、指针和数组的笔试题

以下全都是32位平台上的结果

(1)

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a+0));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(a[1]));
printf("%d\n",sizeof(&a));
printf("%d\n",sizeof(*&a));
printf("%d\n",sizeof(&a+1));
printf("%d\n",sizeof(&a[0]));
printf("%d\n",sizeof(&a[0]+1));

sizeof里单独放数组名时,测量的是整个数组的大小
当不单独放时,就变成了里面元素的大小
a是首元素地址,解引用后变成首元素
a+1同上
数组内元素
&a取出的是数组的地址,地址的大小为4
&和*想抵消,就是整个数组的大小
&a取出数组的地址,+1后跨越整个数组,但也是指针
指针
指针

(2)

char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));

本题同(1),只是字符和整型的大小不同,这里就不过多赘述
在这里插入图片描述

(3)

	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", strlen(arr));//未知数
	printf("%d\n", strlen(arr + 0));
	printf("%d\n", strlen(*arr));
	printf("%d\n", strlen(arr[1]));
	printf("%d\n", strlen(&arr));
	printf("%d\n", strlen(&arr + 1));
	printf("%d\n", strlen(&arr[0] + 1));

strlen这个函数是从给定的位置出发,向后直到遇到’\0’停止
所以前两个是未知数
第三和第四个里面给的是字符,传参错误
对于&arr+1,strlen接收的地址往后挪了6个单位,所以是上面的随机值-6
&arr[0]+1则是随机值-1

(4)

char arr[] = "abcdef";
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));

只需要注意,传入字符串时,结尾会自动补充一个’\0’,这里给出答案,不在赘述
在这里插入图片描述

(5)

	char arr[] = "abcdef";
	printf("%d\n", strlen(arr));//6
	printf("%d\n", strlen(arr + 0));//6
	//printf("%d\n", strlen(*arr));//错误
	//printf("%d\n", strlen(arr[1]));//错误
	printf("%d\n", strlen(&arr));//6
	printf("%d\n", strlen(&arr + 1));//随机值
	printf("%d\n", strlen(&arr[0] + 1));//5

答案写在代码里了

(6)

	char* p = "abcdef";
	printf("%d\n", sizeof(p));//4
	printf("%d\n", sizeof(p + 1));//4
	printf("%d\n", sizeof(*p));//1
	printf("%d\n", sizeof(p[0]));//1
	printf("%d\n", sizeof(&p));//4
	printf("%d\n", sizeof(&p + 1));//4
	printf("%d\n", sizeof(&p[0] + 1));//4

这里要注意的是p指向的是字符串首元素的地址

(7)

	char* p = "abcdef";
	printf("%d\n", strlen(p));//6
	printf("%d\n", strlen(p + 1));//5
	//printf("%d\n", strlen(*p));//错误
	//printf("%d\n", strlen(p[0]));//错误
	printf("%d\n", strlen(&p));//随机值
	printf("%d\n", strlen(&p + 1));//随机值
	printf("%d\n", strlen(&p[0] + 1));//5

(8)

int a[3][4] = {0};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a[0][0]));
printf("%d\n",sizeof(a[0]));
printf("%d\n",sizeof(a[0]+1));
printf("%d\n",sizeof(*(a[0]+1)));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(*(a+1)));
printf("%d\n",sizeof(&a[0]+1));
printf("%d\n",sizeof(*(&a[0]+1)));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a[3]));

二维数组a数组名单独出现在sizeof中,表示的是整个数组
a[0][0]是数组的第一个元素
a[0]是数组第一行的元素名,单独出现在sizeof内部表示数组第一行的一维数组
a[0]没有单独放在sizeof内部,a[0]就表示第一行这个一维数组首元素的地址。
第一行的一维数组首元素地址+1后解引用,找到的就是第一行第二个元素
a没有单独出现在sizeof内部时,表示的是二维数组首元素地址,也就是第一行一维数组的地址。

十、指针笔试题详解

1

int main()
{
  int a[5] = { 1, 2, 3, 4, 5 };
  int *ptr = (int *)(&a + 1);
  printf( "%d,%d", *(a + 1), *(ptr - 1));
  return 0;
}

&a取出的是整个数组的地址,进行+1后就来到了数组后面的空间
*(a+1)是数组第二个元素,ptr-1则是越界后的倒推

2

#include<stdio.h>
struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
	printf("%p\n", p + 0x1);
	printf("%p\n", (unsigned long)p + 0x1);
	printf("%p\n", (unsigned int*)p + 0x1);
	return 0;
}

p是一个结构体指针,当p加上一个1,实际上是地址上加了20,得到0x100014
后面就是整型和普通指针的常见加减

3

#include<stdio.h>
int main()
{
	int a[4] = { 1, 2, 3, 4 };
	int* ptr1 = (int*)(&a + 1);
	int* ptr2 = (int*)((int)a + 1);
	printf("%x,%x", ptr1[-1], *ptr2);
	return 0;
}

对于ptr1,就是一个普通的越界访问之后回来
当a被强制类型转化为int后,它进行+1后就是一个普通的加1(不同于作为地址时加1等于加4个字节)
所以,对于小端存储
在这里插入图片描述
最终取出的是0x02000000

4

#include <stdio.h>
int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	int* p;
	p = a[0];
	printf("%d", p[0]);
	return 0;
}

这里要注意一个坑,定义数组时,大括号中使用的是逗号表达式,a[0]直接使用代表的是第一行首元素的地址

5

#include<stdio.h>
int main()
{
	int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int* ptr1 = (int*)(&aa + 1);
	int* ptr2 = (int*)(*(aa + 1));
	printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
	return 0;
}

这里方法同上

6

#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}

a是指针数组,把这个数组中第一个元素的地址给pa,pa++后来到第二个地址

总结

提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文只是介绍了指针的一些常见用法,具体实现还需带入实际情况中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值