栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

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

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

目录

1.指针

1. 指针是什么2. 指针和指针类型3. 野指针

磊1. 指针没有初始化賂2. 指针越界访问雷3. 指针指向的空间被释放 4. 指针运算5. 二级指针 2. 字符指针3. 数组指针4. 指针数组5.数组传参和指针传参

拾1️⃣数组名表示的含义拾2️⃣数组传参拾3️⃣指针传参 6. 函数指针7. 函数指针数组8. 指向函数指针数组的指针9. 回调函数10. 小型计算器项目(对7,8,9等知识点的运用)

1.指针 1. 指针是什么

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

2. 指针和指针类型
#include 
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 
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 (*)()这是一个函数指针类型,无参数,返回类型为空(void)

2.(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 
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;
}

运行的基本情况如下:

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/767246.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号