你是否真的了解指针?

指针到底是啥?

       在了解指针之前,首先我们要认识指针。指针,指向的要有内容才叫指针,那么我们怎样才能准确的指向这个内容呢,类比生活中有人向我们问路时,我们为了别人能够精准快速知道我们说的是哪,我们是不是会说一个地址,比如哪一小区,哪一楼,哪个门牌号……那么我们说的这个地址是不是就是我们所指向地方。同理,在计算机中,我们用内存空间来存放信息,为了精准快速找到这些信息,那么我们就需要把内存编上号,而这时我们就称这些编号为地址,而指针指向的就是地址里面所存放的内容,那么综上地址指向地方,指针指向内容,所以我们也就可以得到指针就是地址,还是内存编号这么一个结论了。

指针变量初始化及相应操作符

       在认识指针之后那么我们应该怎样初始化一个指针变量呢?下面给出一个初始化及其操作:

int a = 0;//a叫做整型变量
int * p = &a;//同理那么p我们叫做指针变量(一般我们说的指针都是指针变量)
printf("%p",p);//这里是取出p所存放的指针(地址)
printf("%d",*p);//这里就是打印a的内容,也就是*p=a=0

 在初始化时我们需要取出a的地址存放到p中,那么我们就需要&(取地址操作符)对a进行取地址然后赋值给p,而p就叫做指针变量,用来存放指针(地址),其类型名是int *,int表示p指向的数据类型,那么当我们需要使用p所指向地址的内容时,则要用到*(解引用操作符),通俗上来说相当于就是对p所指向的地址进行一个”翻译“。

       和其他变量一样(比如int型4字节)指针变量也有大小,指针变量的大小:专门存放地址,给什么都是地址,所以与类型无关,只会因为平台不同而不同(地址线大小)那么此时为什么又有不同类型的指针呢,其实指针的类型决定了指针向前(+整数)或者向后(-整数)走⼀步有多大,比如整型指针加1,那么就是一次跳过4字节;另外:指针的类型决定了对指针解引用的时候有多大的权限也就是⼀次能操作几个字节,比如: int* 的指针的解引用一次访问四个字节。另外我们还有一种特殊的指针初始化——void类型的指针变量,对于这个指针变量,我们无法直接对其进行指针运算,需要进行强制类型转换为其他类型才能进行运算。

指针运算

指针+/- 整数 :数组在内存中是连续存放的,只要知道第⼀个元素的地址,那么就可以通过加减整数,顺藤摸瓜就能找到其他的所有元素;

指针-指针 :得到的结果的绝对值是指针之间的的元素个数,但是需要注意的是进行操作的两个指针必须位于同一块内存空间(函数栈帧分配的同一内存空间);

指针的关系运算:地址大小比较。

const常属性和assert断言

const用它修饰变量后,变量就不能再被修改了,但是那个变量还是变量,此时我们称这个变量一般为常变量,const相当于是在语法上对变量进行了限制;

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

const int* p = &a;//此时不能改变p所指向的内容,但是能改变p所指向的指针
int* const p = &a;//此时不能改变p所指向的指针,但是能改变p所指向的内容

assert() 宏接受⼀个表达式作为参数。如果该表达式为真(返回值非零), assert() 不会产生任何作用,程序继续运行。如果该表达式为假(返回值为零), assert() 就会报错。如果已经确认程序没有问题,不需要再做断言,就在 #include 语句的前⾯,定义⼀个宏 NDEBUG

#define NDEBUG
#include <assert.h>

野指针

野指针也就是指向已被释放(包括未初始化的)的内存空间的指针

野指针我们无法使用,会使程序报错,那么我们如何才能避免野指针呢:

1.使用空指针(NULL)初始化指针变量

2.在指针释放后将其设置为NULL

3.确保指针始终指向有效的内存地址

4.检查指针的有效性后再进行访问

另类的指针

1. 二级指针

指针也有分配空间(地址),那么当我们对指针再取地址存放到另外一个指针里面,那么这个指针也就叫做二级指针,它是存放指针变量地址的指针,例如

char ** cp = char * p;
2. 指针数组

指针数组的每个元素都是用来存放地址(指针)的,例如:

int * arr[10];

3. 数组指针及数组传参

用于存放数组的地址的指针,能够指向数组的指针变量:

int (*p)[10];//其中p先和*结合,说明p是⼀个指针变量,然后指着指向的是⼀个⼤⼩为10个整型的数组。
int指p指向的数组的元素类型
p表示数组指针变量名
[10]指指向数组的元素个数

数组传参:对于一维数组,在函数调用时数组传参的本质是传递的数组首元素的地址数组名是数组首元素的地址这个规则(sizeof和&除外,它们都是表示整个数组的地址),而对于⼆维数组的数组名表示的就是第⼀行的地址,也就是⼀维数组的地址,假设第⼀行的⼀维数组的类型就是int,第⼀行的地址的类型就是数组指针类型 int(*)[5] 。那就意味着⼆维数组传参本质上也是传递了地址,传递的是第⼀行这个⼀维数组的地址,那么形参也是可以写成指针形式的。如下:

fun (int (*p)[5], int a)//这里的int (*p)[5]就是传的二维数组的形参
4. 函数指针

函数指针变量:存放函数地址的,能够通过地址能够调用函数,例如

int(*p)(int, int) = Add或int(*p)(int x, int y) = &Add;
int表示pf3指向函数的返回类型
p表示函数指针变量名
(int, int)表示指向函数的参数类型和个数的交代

另外正常函数调用是直接用的地址如:add(2,3),所以对于函数指针同样可以直接写成p(2,3)

 5. 函数指针数组和转移表

众所周知:数组是⼀个存放相同类型数据的存储空间,那么我们将多个函数的地址存到⼀个数组中,再将类型设置为函数指针类型,那么我们是不是就得到了一个函数指针数组了,例如:

int i=0;
int (* pfarr[4])(int ,int)={add,sub,mul,div}//以加减乘除函数为一个封装
pfarr[i](6,2);//函数指针数组调用,和函数指针类似

需要注意的是:这些函数参数类型应该相同

那么既然这样的话我们要这个函数指针数组到底有什么用呢,为什么不直接调用函数不是更方便吗?那么存在即合理,当我们遇到多次重复操作,用函数指针数组可封装多个参数相同的函数以达到简化代码的作用,相当于一个跳板,即我们常说的转移表。比如:

#include <stdio.h>
int add(int a, int b)
{
 return a + b;
}
int sub(int a, int b)
{
 return a - b;
}
int mul(int a, int b)
{
 return a * b;
}
int div(int a, int b)
{
 return a / b;
}
int main()
{
 int x, y;
 int input = 1;
 int ret = 0;
 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://多次case重复一样的操作,太过冗余
 printf("输⼊操作数:");
 scanf("%d %d", &x, &y);
 ret = add(x, y);
 printf("ret = %d\n", ret);
 break;
 case 2:
 printf("输⼊操作数:");
 scanf("%d %d", &x, &y);
 ret = sub(x, y);
 printf("ret = %d\n", ret);
 break;
 case 3:
 printf("输⼊操作数:");
 scanf("%d %d", &x, &y);
 ret = mul(x, y);
 printf("ret = %d\n", ret);
 break;
 case 4:
 printf("输⼊操作数:");
 scanf("%d %d", &x, &y);
 ret = div(x, y);
 printf("ret = %d\n", ret);
 break;
 case 0:
 printf("退出程序\n");
 break;
 default:
 printf("选择错误\n");
 break;
 }
 } while (input);
 return 0;
}

利用函数指针数组即使用转移表简化case语句,那么我们就可以这样写:

#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 Initsence()
{
	printf("****************************\n");
	printf("**********功能如下**********\n");
	printf("********1.Add   2.Sub*******\n");
	printf("********3.Mul   4.Div*******\n");
	printf("********    0.exit   *******\n");
	printf("****************************\n");
}

int main()
{

	int input = 1;
	int x = 0, y = 0;
	char str[] = " +-*/";
	int(*p[])(int, int) = { NULL,Add,Sub,Mul,Div };//函数指针数组封装加减乘除函数
	do
	{	
		Initsence();
		printf("请输入数字选择功能:");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出!\n");
		}
		else if (input > 0 && input < 5)
		{
			printf("请输入操作数:");
			scanf("%d%d", &x, &y);
			int ret = p[input](x, y);//在这里我们使用了函数指针数组简化操作
			printf("x%cy=%d\n", str[input], ret);
		}
		else
		{
			printf("输入错误,请重新输入!\n");
		}
	} while (input);
	return 0;
}

回调函数

同样我们还可以使用回调函数来简化操作,那么什么是回调函数呢,简单来说就是通过函数指针调用的函数,如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被用来调用其所指向的函数时,被调用的函数就是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条 件发生时由另外的一方调用的,用于对该事件或条件进行响应,举个例子来说就是将一个工厂将要加工的材料送到另外一个工厂加工然后再返回零件,最后再在自己的工厂进行最终组合。那么下面我们使用回调函数来简化上面的代码,代码如下:

#include <stdio.h>
int add(int a, int b)
{
 return a + b;
}
int sub(int a, int b)
{
 return a - b;
}
int mul(int a, int b)
{
 return a * b;
}
int div(int a, int b)
{
 return a / b;
}
void Initsence()
{
	printf("****************************\n");
	printf("**********功能如下**********\n");
	printf("********1.Add   2.Sub*******\n");
	printf("********3.Mul   4.Div*******\n");
	printf("********    0.exit   *******\n");
	printf("****************************\n");
}
void calc(int(*pf)(int, int))
{
   int ret = 0;
   int x, y;
   printf("输⼊操作数:");
   scanf("%d %d", &x, &y);
   ret = pf(x, y);
   printf("ret = %d\n", ret);
}
int main()
{
 int input = 1;
 do
 {
   Initsence();
   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;
}

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值