详解C指针+小型计算器项目

在这里插入图片描述

1.指针

1. 指针是什么

我们口头说的指针就是地址,
指针变量是变量,是用来储存地址的。

2. 指针和指针类型

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

对于上面的代码
这里的p是指针变量——是用来存放地址的变量;
可以这样理解从*可以看出p是个指针变量,p指向的内容是int类型的。
*p = 20,此处的 * 是解引用操作符。
&为取地址操作符
指针的类型是根据原来值的类型来确定用什么类型的指针。如:char类型,那就用char* 。
去掉指针变量名剩下的就是指针的类型

关于指针类型的声明有人可能会问用不同的类型声明可以吗?当然可以,但是会出现一些问题。
不同的指针类型决定了指针所移动的步长
如:char类型的,那么它+1就指向下一个字节(向后走一步)
int
类型的,那么它+1就指向到第4个字节处。(向后走4步)
指针的类型代表它所能访问几个字节大小的空间
看下面这个代码

int main()
{
	int a = 0x11223344;
	char* p = (char*)&a;
	*p=0;
	printf("%x", a);
	return 0;
}

此处的&a应该是int*类型的,但强转成char*类型的指针之后,对p进行解引用赋值成0只能改变一个字节,所以输出的结果是11223300
看计算机输出的结果
在这里插入图片描述

指针的大小由电脑的平台所决定的而不是由指针类型决定的。
如果平台上是32位的,那就是4个字节的大小;64位的平台就是8个字节的大小。
看下面这个代码可以看出来:

#include <stdio.h>
int main()
{
	printf("%dn", sizeof(int*));
	printf("%dn", sizeof(char*));
	printf("%dn", sizeof(long*));
	printf("%dn", sizeof(float*));
	return 0;
}

在这里插入图片描述

在我的32位平台上上面的输出结果都是4。

3. 野指针

野指针就是指针访问了位置不可知的地方,造成非法访问

🥇1. 指针没有初始化

比如这样:

	int* i;
	这样写是不对的,指针i指向的空间不知道
	可以这样写:
	int* i=NULL;

🥈2. 指针越界访问

看下面这个代码:

	int arr[5]={1,2,3,4,5};
	int* p=arr;
	int i=0;
	for(i=0;i<=5;i++)
	{
		*(p++)=i;
	}

该数组里面有5个元素,当i为5时,p访问的空间超过了数组的范围
造成越界访问,这时p就是野指针.

🥉3. 指针指向的空间被释放

int* f(int* a,int* b)
{
	int c = 0;
	c = *a + *b;
	return &c;

}
int main()
{
	int a = 10, b = 20;
	int* p=f(&a, &b);
	printf("%dn", *p);
	return 0;
}

在调用函数的过程中开辟的空间,出了这个了函数,则开辟的空间返还给操作系统,导致p指针接受的地址被释放,次数p为野指针。
虽然在vs 上面依然可以输出,但是确实是错误的。需要注意。

还有这种,和上面的差不多:

int main()
{
	int* p = (int*)malloc(4 * sizeof(int));
	int i = 0;
	if (p != NULL)
	{
		for (i = 0; i < 4; i++)
		{
			*(p + i) = i;
		}
	}
	free(p);
	for (i = 0; i < 4; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

在p指向的空间被释放后,它所指向的空间被返还给操作系统
再次对它解引用进行访问就造成非法访问。
通常在释放空间后加上p=NULL

4. 指针运算

  • 🚗 指针加减整数

指针加一个整数表示该指针向后走几个该指针所指类型字节的个数。

比如:

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8 };
	int p=*(arr + 4);
	return 0;
}

p就是5,因为数组的每个元素都是int类型的,所以arr+4表示arr向后走4个int类型的字节数,即访问数组第5个元素。

指针减整数也是同理,注意不要越界访问

  • 🚓指针相减

指针相减得到的是两个指针之间的元素个数,不要认为是地址相减的结果,不要问为什么,这是这么规定的。

看下面的代码帮助你理解:

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8 };
	int* p1 = arr;
	int* p2 = arr + 4;
	printf("%dn", p2 - p1);
	return 0;
}

输出的结果是4哦😁
在这里插入图片描述

  • 🚕指针做比较

指针也是有大小的,就比如有高地址与低地址这么一说

c语言标准规定

允许指针与指针指向数组的最后一个元素后面的那个地址进行比较,不允许和指针指向数组第一个元素前面的那个地址进行比较。
再次说明不要问为什么,规定就是规定

例子:

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8 };
	int* p;
	for (p=arr;p<=&arr[7];p++)
	{
		;
	}
	上面这个可以,下面这个不可以,原因上面说的非常清楚
	for (p = &arr[7]; p >= arr; p--)
	{
		;
	}
	return 0;
}

5. 二级指针

指针的地址怎么储存呢?——用二级指针变量进行储存

	int i=0;
	int* p=&i;
	int** p1=&p;

指针的指针就是二级指针,用于储存一级指针的地址。要改变i的值p1要解引用2次才可以改变

2. 字符指针

能够指向字符数据的指针
形如这样的char* p
这里就讲比较难的地方吧!看下面这个代码

void pd(char* s1,char* s2)
{
	if (s1 == s2)
		printf("相等n");
	else
		printf("不相等n");
}
int main()
{
	char arr1[] = "abcef";
	char arr2[] = "abcef";
	pd(arr1,arr2);
	char* p1 = "abcef";
	char* p2 = "abcef";
	pd(p1,p2);
	return 0;
}

这里的arr1和arr2,p1和p2相等吗?看结果
在这里插入图片描述

arr1与arr2不相等,p1和p2相等。在数组储存数据时,即使储存的字符串相等也开辟不同的空间。但是p1,p2可不能储存"abcef"这样的常量字符串,它们只是储存了首字符的地址,所以p1和p2相等.并且p1,p2所指向内存的数据不能更改,因为初始化的常量字符串是不能更改的,可以这样写

	const char* p1 = "abcef";
	const char* p2 = "abcef";

3. 数组指针

数组指针本质上是指针,例如:

  int (*p)[5]
  *说明p是一个指针,指针指向一个数组,
  数组中有5个元素,每个元素是int类型

这里的p的类型为 int (*)[5] 类型。
这样使用,例子:

void f(int (*p)[5],int n)
{
	int i, j;
	for (i=0;i<5;i++)
	{
		for (j=0;j<5;j++)
		{
			*(*(p + i) + j) = 1;
		}
	}
}
int main()
{
	int arr[5][5]={1,2,3,4,5,6,7};
	f(arr,5);
	return 0;
}
  • 这里的二维数组名表示第一行数组的地址,数组的地址要用数组指针进行接收。

下面这个对应着内存布局:
在这里插入图片描述

4. 指针数组

指针数组本质上是数组。例如:

int *p[5]
根据运算符的结合性可知,
p先与[]结合,说明p是一个数组,数组里面有5个元素,
每个元素是int*类型,也就是指针指向int类型

给个简单的例子:
看代码:简单

int main()
{
	int arr1[] = { 1,2,3 };
	int arr2[] = { 4,5,6 };
	int arr3[] = { 7,8,9 };
	int* p[] = { arr1,arr2,arr3 };
	int i = 0, j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 3; j++)
		{
			printf("%d ", p[i][j]);
		}
		printf("n");
	}
	return 0;
}

5.数组传参和指针传参

🥳1️⃣数组名表示的含义

  • 一维数组名
    一维数组名表示数组首元素地址
  • 二维数组名
    二维数组名表示首行数组的地址
    每一行的数组都有自己的名字,对与下面的代码arr2[0]为第一行数组的名字

sizeof(数组名)求的是整个数组的大小。
&数组名取出的是整个数组的地址
例子:看代码

int main()
{
	int arr1[] = { 1,2,3,4,5,6 };
	int arr2[][3] = { 1,2,3,4,5,6 };
	printf("%dn",sizeof(arr1));求的整个数组的大小,单位字节24
	printf("%dn", sizeof(arr2));求的整个数组的大小24
	printf("%dnn", sizeof(arr2[0]));求的是第一行数组的大小12

	printf("%pn", arr1);首元素的地址
	printf("%pn", arr1+1);2个元素的大小
	printf("%pn", &arr1);这个元素的大小
	printf("%pnn", &arr1+1);越过整个数组后的地址

	printf("%pn", arr2);首行的地址
	printf("%pn", &arr2 + 1);越过这个数组后的地址
	printf("%pn", arr2[0]);首行首元素的地址
	printf("%pn", &arr2[0] + 1);第二行数组的首元素的地址
	return 0;
}

在这里插入图片描述

  • 运行的结果
    在这里插入图片描述

🥳2️⃣数组传参

  • 一维数组传参
f(int arr[])
{}
f(int arr[3])
{}
f(int* arr)
{}
上面这三种都可以,但是最后一种比较好,它能反应出数组名字为数组首元素地址,
像第1与第2种里面[]里面有没有数值的没有问题。
f1(int* arr1[])
{}
f1(int* arr1[3])
{}
上面这两种大家应该可以理解
f1(int** arr1)
{}
arr是数组名,表示首元素地址,每个元素是指针,即指针的地址要用二级指针接受
int main()
{
	int arr[3] = { 1,2,3 };
	int* arr1[3];
	f(arr);
	f1(arr1);
	return 0;
}

对于一维数组传参,上面的几种都正确。

  • 二维数组传参
f(int p[2][2] )
{}
f(int p[][2] )
{}
上面这两种都可以,也都可以理解,前一个数值可以省略,
但是第二个[]的数值不能去掉.
f(int(*p)[2] )
{}
二维数组名表示首行数组的地址,要用数组指针进行接收
int main()
{
	int arr[2][2];
	f(arr);
	return 0;
}

🥳3️⃣指针传参

  • 一级指针传参
f(int* p)
{}
int main()
{
	int a = 0;
	int* p = &a;
	f(p);
	return 0;
}

一级指针传参用一级指针进行接收

思考形参为一级指针的时候,实参可以为什么样的类型
1️⃣实参可以为一维数组的数组名
2️⃣可以为一级指针

  • 二级指针传参
f(int** p1)
{}
int main()
{
	int a = 0;
	int* p = &a;
	int** p1 = &p;
	f(p1);
	return 0;
}

二级指针传参用二级指针进行接收

思考形参为一级指针的时候,实参可以为什么样的类型
1️⃣实参可以为指针数组
2️⃣可以为二级指针
3️⃣可以为一级指针的地址

6. 函数指针

引例:

int add(int x,int y)
{
	return x + y;
}
int main()
{
	&add;
	return 0;
}

对于函数的地址应该怎么储存呢?那就需要用函数指针类型进行接收。
对于上面的代码,我们可以这样写
int (*p)(int,int)
对于上面这个可以这么理解:
* 告诉我们P是个指针,从后面的小括号可以看出是个函数,函数的参数有两个,都是int 类型的,返回类型是int

下面这样进行使用:

	int(*p)(int,int)=&add;
	(*p)(1, 2);
	(**p)(1, 2);
	p(1, 2);
	add(1, 2);

我们发现上面这3种都正确,说明p变量前面的星号没有任何用处,p就相当与add。

看下面的这个代码可以更好的理解:我们发现打印的结果相同

	printf("%pn",add);
	printf("%pn", &add);

在这里插入图片描述

  • 看着两个表示的是什么:
  • (*(void (*)())0)()
  • void (*signal(int , void(*)(int)))(int)
    我们一点一点拆分看
(*(void (*)())0)(); 
1.void (*)()这是一个函数指针类型,无参数,返回类型为空(void2.(void (*)())0这个是强制类型转换,把0强转为函数指针类型

3.*解引用变成函数,调用函数,函数无参数


void (*signal(int , void(*)(int)))(int);

1.void(*)(int)这是一个函数指针类型,返回类型为空,参数为int类型的

2.signal(int , void(*)(int))这是一个函数,函数名为signal,
参数有俩个,分别为int类型和函数指针类型。

3.函数要有返回类型,去掉signal(int , void(*)(int)),就是该函数的返回类型
该函数的类型为void (*)(int),函数指针类型。

对这个函数可以进行一下好看的改变
typedef void (*v_i)(int)
然后这样写:v_i signal(int,v_i);这样看着是不是非常舒服

7. 函数指针数组

函数指针数组
是个数组,里面的元素是是函数指针

给个例子:

void f1()
{}
void f2()
{}
int main()
{
	void (*p[2])()={f1,f2};
	这个就是函数指针数组,
	这样理解p先和[]结合说明p是一个数组,数组有2个元素,
	每个元素的个数是void (*)()函数指针类型
	return 0;
}

8. 指向函数指针数组的指针

指向函数指针数组的指针
它是一个指针,指向一个数组,数组的每个元素是函数指针类型

看下面的这个代码:

void f1()
{}
void f2()
{}
int main()
{
	void (*p[2])()={f1,f2};
	void(*(*p1)[2])()=&p;
	p1就是一个函数指针数组的指针
	return 0;
}

9. 回调函数

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

下面有个例子:

void f1()
{}
f(int a,void (*p)())
{
	p();
}
int main()
{
	int a;
	f(a, f1);
	把f1函数作为参数传给f函数,通过函数指针来调用f1函数。
	return 0;
}

10. 小型计算器项目(对7,8,9等知识点的运用)

#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 menu()
{
	printf("****		请选择			   ****n");
	printf("**** 1.加			2.减 **********n");
	printf("**** 3.乘           4.除 **********n");
	printf("************* 0退出 ***************n");
}
f(int (*p)(int, int))
{
	int a, b;
	printf("请输入两个整数:>");
	scanf("%d%d", &a, &b);
	printf("结果为%dn", p(a, b));
}
int main()
{

	int n;
	int (*gather[5])(int, int) = { 0,add,sub,mul,div };
	该函数指针数组里面存的是各个函数的地址,首个元素设置为0,
	方便用下标访问
	do
	{
		menu();
		该函数打印菜单,供用户选择
		scanf("%d", &n);
		if (n == 0)
		{
			printf("退出程序n");
		}
		else if (n > 0 && n < 5)
		{
			f(gather[n]);
			这就是一个回调函数
		}
		else
		{
			printf("选择错误请从新选择n");
		}
	} while (n);
	return 0;
}

运行的基本情况如下:
在这里插入图片描述
在这里插入图片描述

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>