对指针的认识

目录

指针的定义

​编辑

指针变量

解引用操作符

指针运算和自增自减

void指针

const修饰指针

野指针

c指针类型

数组指针

一维数组指针

二维数组指针

三维数组指针

指针数组

模拟二维数组

字符数组

字符数组指针

函数指针

 函数指针数组

函数指针数组指针

函数返回指针

内存的分配

栈VS堆

回调函数

总结


指针的定义

想要了解指针,就得知道计算机是怎么工作的,计算机的内存是用二进制的形式存储的,每个内存单元都有个编号,也就是地址,而这个地址也就是指针,计算机会给变量分配内存空间,至于分配的字节多少取决于你的编译器,32位对应——int-4个字节 char-1个字节 float-4个字节 double-8个字节,字节可以通过sizeof函数得到对应编译器下类型的字节长度
也就是说地址==指针
那指针的形式是怎样的呢?譬如说我们定义一个普通变量,int a;而在定义是加个*就代表这是个指针,这个指针指向变量a,类型是int*;

在x86编译管理器下(32位),指针变量大小在内存中都是占四个字节

在x64编译管理器下(64位),指针变量大小在内存中都是占八个字节

指针变量

指针变量是用来存放地址值的

#include<stdio.h>
int main()
{
  int a;
  int* pa=&a;//取出a的地址存放在指针变量pa中
  return 0;
}
解引用操作符

怎么通过地址改变a的值呢?需要用到*,这颗星是操作符,解引用pa;访问内存中地址存储的值,可以通过解引用修改变量的值

#include<stdio.h>
int main()
{
	int a=10;
	int* p = &a;
	*p = 20;
	printf("%d", *p);
	return 0;
}

 打印的结果是20

指针运算和自增自减

深刻理解以下的例子

#include<stdio.h>
int main()
{
	int a = 10;
	int* p = &a;
	int** q = &p;//指向指针的指针称为二级指针,用来存放一级指针变量的地址
	int*** r = &q;//存放二级指针变量的地址,三级指针
	printf("%d\n", *p);//*p得到的是a的值,打印10
	printf("%d\n", *p+1);//解引用后+1,打印11
	//printf("%d\n", *(p + 1));//不能这样写,指针p的后一个地址并未初始化
	printf("%d\n", **q);//对一级指针解引用,得到一级指针变量的值,打印10
	printf("%d\n", *(*q));//同上打印10,表达形式不一样
	printf("%d\n", *q);//*q是q所指向的内容的值,打印是存放在p中的地址
	printf("%d\n", *r);//指向的是q中的值
	printf("%d\n", &p);//&p是存放在q中的值
	printf("%d\n", *(*r));//*r是r所指向的内容的值(地址)再对q所指向的内容解引用,得到是存放在p中的地址
	printf("%d\n", ***r);//打印的是a的值,对r指向的内容解引用,再对q指向的内容解引用,再对p指向的内容解引用,得到a的值
	***r = 0;//重新赋值
	printf("%d\n", a);//打印0
	return 0;
}

自增,指针+整数

#include<stdio.h>
void print(int* parr, int sz)
{
	int i = 0;
	
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *parr);
		*parr++;
	}
}
int main()
{
	int arr[] = { 1,2,3,4,5,6 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	print(arr, sz);
	return 0;
}

执行结果如下

 

指针-指针

 字符串数组中指针减指针得到的是字符串的个数; 举例如下:

#include<stdio.h>
int strlen(const char* a)
{
	char* start = a;
	char* end = a;
	while (*end != '\0')
	{
		end++;

	}
	return end - start;
}
int main()
{
	char a[] = "hello world";
	int len = strlen(a);
	printf("%d\n", len);
	return 0;
}

结果是11 

指针的关系运算;本质是比较指针的大小;

允许指向数组元素的指针与指向数组元素的最后一个元素的指针进行比较,
但不允许与指向数组元素的第一个元素之前的指针进行比较;

#include<stdio.h>
#define N_VALUES 5
int main()
{
	float values[N_VALUES];
	float* vp;
	for (vp = &values[N_VALUES]; vp > &values[0];)
	{
		*--vp = 0;
	}
	/*for (vp = &values[N_VALUES - 1]; vp >= &values[0]; vp--)
	{
		*vp = 0;
		printf("%lf ", *vp);
	}*///应避免这样写,允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与
	//指向第一个元素之前的那个内存位置的指针进行比较。

	return 0;
}

 指针类型决定了解引用是访问几个字节

#include<stdio.h>
int main()
{
	int a = 0x11223344;
	//100 10进制
	//0x64 16进制
	//1100100 2进制
	//144 8进制
	int* pa = &a;
	char* pi = & a;
	printf("pa=%p\n", pa);
	printf("pa+1=%p\n", pa+1);
	printf("pi=%p\n", pi);
	printf("pi+1=%p\n", pi+1);

	return 0;
}

可以发现指针类型决定了指针+-1跳过多少字节

void指针

void*是一种通用指针类型,可以用来存储任何类型的指针,但不能直接接引用

存储不同类型的指针

                                                         作为函数中参数的返回值

const修饰指针
#include<stdio.h>
void print(const char* a)
{
	while (*a != '\0')
	{
		printf("%c", *a);
		a++;

	}
	printf("\n");
}
int main()
{
	char a[] = "hello world";
	int len = strlen(a);
	print(a);
	printf("%d\n", sizeof(a));
	printf("%d\n", len);
	return 0;
}

结论:const修饰指针变量的时候 • const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。 但是指针变量本⾝的内容可变。 • const如果放在*的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指 向的内容,可以通过指针改变

野指针

两种情况

(1)未初始化的指针

int *p;
*p=20;

未对p进行初始化

(2)对数组越界访问

int a[10]={0};
int* p=&a;
int i=0;
for(i=0;i<=11;i++)
{
*(p++)=i;
}

当指针指向的范围超过数组的范围,那p就是野指针

如何规避野指针呢?

#include<stdio.h>
int main()
{
	int i = 0;
	int* p = NULL;
	int arr[10] = { 0 };
	p = &arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	if (p != NULL)
	{
		for (i = 0; i < sz; i++)
			printf("%p ", &arr[i]);
	}
	return 0;
}

先使p成为空指针,在判断是否为空:

c指针类型

数组指针

数组指针,是指向数组的指针,形式是定义一个数组int a[10]={0};int* p=&a;

数组名就是首元素地址

我们可以通过指针来访问数组元素,也可以听过数组下标

一维数组指针
#include<stdio.h>
/*void print(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}*///数组传参传的是首元素地址
void print(int* parr, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(parr + i));// parr[i]);
	}
}//传地址就是传引用
int main()
{
	int arr[] = { 1,2,3,4,5 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	print(arr, sz);
	return 0;
}

一维数组的数组名和指针一样都是地址

二维数组指针
#include<stdio.h>
void print(int(*p)[5], int r, int c)//p和*结合说明是指针在指向整型数组,,指针类型是int(*p)[]
{
	for (int i=0;i<r;i++)
	{
		for (int j= 0; j <c; j++)
		{
		//	printf("%d ", *(*(p+ i) + j));
			printf("%d ", p[i][j]);
		
		}
		printf("\n");
	}
}
int main()
{
	int a[3][5] = { 1,2,3,4,5,2,3,4,5,6,3,4,5,6,7 };
	print(a, 3, 5);//二维数组传参传的是首行元素的地址;
	return 0;
}

二维数组指针 不可省略数组元素个数

为了更好地理解二维数组指针,要理解二维数组的名是首行数组的地址

三维数组指针
#include<stdio.h>
int main()
{
	int a[3][2][2] = { {{1,2},{2,3}},{{3,4},{5,6}},{{7,8},{9,10}} };
	int(*p)[2][2] = a;//三维数组指针指向二维数组指针
	printf("%d\n", a);
	printf("%d\n", *a);
	printf("%d\n", a[0][0]);
	printf("%d\n", a[0]);//打印的是首个二维数组的地址
	printf("%d\n", *(a + 1));//跳过一个数组的字节长度两行两列总共有16个字节
	printf("%d\n", &a[1]);
	printf("%d\n", *(a[0][1]+1));//一维数组首元素地址,整型指针,在二维数组的角度看,就是往右移一个
	printf("%d\n", a[0][1][1]);
	printf("%d\n", *(a[1]+1));//a[1]类型是int(*)[2]指向一维数组的指针
	printf("%d\n", &a[1][1][0]);
	/*print(p);*/

	return 0;
}

右侧是打印三维数组,三维数组指针很少用哈~

指针数组

本质上是数组,存放指针的数组

模拟二维数组

#include<stdio.h>
int main()
{
	int arr1[] = { 1,2,3,4 };
	int arr2[] = { 2,3,4,5 };
	int arr3[] = { 3,4,5,6 };
	int* parr[3] = {arr1,arr2,arr3};//指针变量和数组结合说明这是个指针数组
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
	
		for (j = 0; j < 4; j++)
		{
			printf("%d ", /*parr[i][j]*///拿到首地址,就可以访问每个元素了
				/**(parr[i] + j)*/*(*(parr+i)+j));

		}
		printf("\n");
	}

	return 0;
}

字符数组

存放字符的数组

指向同一字符串指针的地址是一样

#include<stdio.h>
int main()
{
	const char*p1= "abcdef";
	const char* p2 = "abcdef";//字符串常量池不可以改变
	char arr1[] = "abcdef";
	char arr2[] = "abcdef";
	if (p1 == p2)
		printf("p1=p2\n");
	else
		printf("p1!=p2\n");
	if (arr1 == arr2)
		printf("arr1 = arr2");//不同数组是不同的内存空间。开辟空间,首元素地址不一样
	else
		printf("arr1 != arr2");

	return 0;
}

而字符数组在内存中会开辟不同的内存空间

所以答案是

p1=p2

arr1!=arr2

字符数组指针

指向字符数组的指针

#include<stdio.h>
void print(const char* a)
{
	while (*a != '\0')
	{
		printf("%c", *a);
		a++;

	}
	printf("\n");
}
int main()
{
	char a[] = "hello world";
	int len = strlen(a);
	print(a);
	printf("%d\n", sizeof(a));
	printf("%d\n", len);
	return 0;
}

strlen计算字符串的长度不包括NULL即\0;sizeof包括

函数指针

指向函数地址的指针

#include<stdio.h>
//函数指针,指向函数的指针,访问函数入口的地址
int Add(int a, int b)
{
	return a + b;
}
int main()
{
	int c;
	int(*p)(int, int);//函数指针的声明,创建指针变量,所指向的指针类型的参数,要和函数一一对应
	p = &Add;//初始化指针
	c = (*p)(2,3);//这里的p和*p一样;
	printf("%d", c);
	return 0;
}
理解
(* void(*)())0();
将常数0强制转换为返回值为void的函数指针类型
void(* signal(int, void(*)(int))(int)
先把signal后面的单独拿出来看
signal函数接受两个参数,一个是int类型,一个是参数是int的返回值是void函数的指针,是个函数指针类型,外面的是void(*)(int)说明signal的函数返回类型是一个指向返回值为void类型的函数的指针

void(*)() ——函数指针类型“指向返回值为void型的函数的指针”

函数指针在大型工程中用处很大

计算器,实行分装,不冗杂,就要用到函数指针,加法,减法,乘法,除法的功能

 函数指针数组

 函数指针放在数组中,把多个函数参数,返回类型相同的函数指针放在数组

函数指针数组指针

指向函数指针数组的指针

int main()
{
	int(*p[5])(int, int) = { 0, Add,Sub,Mul,Div };
	int(*pp[5])(int, int) = &p;
	return 0;
}

禁止套娃~~

函数返回指针

栈底可以向上传局部变量的地址
栈顶不可以向下传址,因为向下传址到主调函数是被调函数的内存已经被释放了,易被其他函数的栈帧即地址被覆盖,生成随机值(垃圾)

内存的分配

栈VS堆

栈区,有固定的内存,被调函数的内存会自动清除

堆区,不固定,不会自动请除,可以向堆区申请内存空间

需要用到函数的有

调用的时候需要加上#include<stdlib.h>

malloc——申请堆区的内存

realloc——扩容,更改已经配置的内存空间,即更改由malloc()函数分配的内存空间的大小。

calloc——和malloc略有区别 calloc会自动初始化为0,不用强制转换

free——释放内存

int*p;

p=(int *) malloc( sizeof(int) );

malloc是空指针,需要进行指针类型的强制转换,malloc在分配内存时不会自动初始化,如果我们不解引用,就会得到一些随机值(垃圾)

realloc原型为void realloc(void *ptr,size-t-new-Size)

int* B = (int*)realloc(NULL, a*sizeof(int)); 相当于 B=(int *) malloc( sizeof(int) );

int *p;

p=(int*)callloc(3,sizeof(int))要申请的数量,类型的字节长度

free(p);就是释放内存

回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应!

举个例子,通过回调函数实现加法,减法,乘法,除法 

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
}
void calc(int(*p)(int, int))
{
	int ret = 0;
	int x, y;
	printf("请输入操作数:");
	scanf("%d %d", &x, &y);
	ret = p(x, y);
	printf("%d\n", ret);

}
int main()
{
	int input=1;
	do
	{
		printf("*****************\n");
		printf("***1.Add 2.Sub***\n");
		printf("***3.Mul 4.Div***\n");
		printf("***  0.exit  ****\n");
		printf("*****************\n");
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			calc(Add);
			break;
		case 2:
			calc(Sub);
			break;
		case 3:
			calc(Mul);
			break;
		case 4:
			calc(Div);
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("输入错误\n");
			break;
		}

	} while (input);
	
	
	return 0;
}

优点:在回调中,主程序把回调函数当作一个参数一样传入库函数。让我们变得灵活,不会那么繁琐

总结

文章到这里就结束,指针的知识远远不止这些,还要会应用,这才是重头戏。简而言之,我会好好学习的!欢迎小伙伴在评论区指导哦~

  • 44
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值