C语言编程之:关于指针 || 是什么/使用原因/如何使用/问题/运算/与数组、函数的关系

一、什么是指针

  • 指针是一种特殊的***数据类型***,使用它可以定义***指针变量***

  • 指针变量存储的是***整型数据***,代表了所指向的内存地址编号,通过这个地址编号可以访问地址对应的内存数据。

  • 指针也是一种变量,也需要内存存储,指针也有地址

  • 知乎上有一篇大佬的分享非常有助于大家对于指针的了解
    知乎大佬高赞描述指针

二、为什么需要指针

  • 变量是为了更好的表示数据,那么指针就是更方便的传递数据,指针大部分时间占用更小内存空间。由于指针变量存储的是地址,地址中的数据无需被拷贝赋值,直接利用指针找到数据所在地址即可使用。
  • 由于函数之间传参是值传递(内存拷贝),对于字节较多的变量值传递效率较低,如果传递的是变量地址只需要传递 4(32位)\8(64位)字节,提高传参效率
  • 函数之间相互独立,有时候需要共享一些变量。命名空间相互独立,但是地址空间只有一个。
  • 堆内存无法取名字,它不像 data\bss\stack 内存段可以让变量名和内存建立联系, 只能使用指针记录堆内存的地址,以此来使用堆内存

以下代码可以看到,函数之间共享了变量

#include <stdio.h>

void func(int *p) 
{
    printf("func:%d\n",*p);
    *p = 100;
    printf("func:%d\n",*p);
    
}

int main(int argc,const char* argv[])
{
    int num = 0;
    printf("main:%d\n",num);
    func(&num);
    printf("main:%d\n",num);
}

在这里插入图片描述

三、如何使用指针

  • 指针变量定义: 类型* 变量名_p;

    1、指针变量与普通变量用法区别很大,建议取名最后加p

    2、指针变量存储的永远是整型,前面的类型名规定了指针变量可以访问地址的字节数

    3、一个*只能定义一个指针变量

    ​ int* p1,p2,p3; //只有p1是指针

    4、指针变量与普通变量初始化值一样是随机的,一般初始化为NULL

  • 赋值: 变量名_p = 地址; //必须是有权限且有意义的地址

    指向栈内存: int* p = & num;

    指向堆内存: int* p = malloc(4);

  • 解引用: *p <==> num;

    通过指针变量记录内存编号去访问内存,该过程可能产生段错误,原因是赋值指针变量时存储了一个非法的内存变量及指针变量的值。num是该内存编号下内存中的值。注意:解引用访问的字节数取决于定义指针变量时的类型。

练习1:实现一个变量的交换函数,调用它对一个数组排序

#include <stdio.h>

void func(int* p1,int* p2)
{
	int t = NULL;
	t = *p1;
	*p1 = *p2;
	*p2 = t;
}

int main(int argc,const char* argv[])
{
    int arr[10] = {12,3,45,534,5,6,7,54,13,89};
	for(int i=0; i<9; i++)
	{
		for(int j=i+1; j<10; j++)
		{
			if(arr[j] < arr[i])
			{
				func(&arr[j],&arr[i]);	
			}
		}
	}
	for(int i=0; i<10; i++)
	{
			printf("%d\n",arr[i]);	
	}
}

练习2:实现一个函数,计算两个整数的最大公约数和最小公倍数,return返回最大公约数,最小公倍数用指针处理。

#include <stdio.h>

int func(int n1,int n2,int* p )
{
	for(int i=n1; i<=n1*n2; i++)
	{
		if(0 == i%n2 && 0 == i%n1)
		{
			*p = i;	
		}
	}
	for(int j=n1; j>=1; j--)
	{
		if(0 == n2%j && 0 == n1%j)
		{
			return j;
		}
	}
}

int main(int argc,const char* argv[])
{
    int num1 = 0,num2 = 0;
	int max = 0,min = 0;
	scanf("%d %d",&num1,&num2);
	max = func(num1,num2,&min);
	printf("最大公因数:%d\n最小公倍数:%d\n",max,min);
}

四、使用指针需要注意的问题

  • 空指针:值为NULL的指针变量叫做空指针,如果进行解引用就会产生段错误

    NULL会作为错误标志的一种,当一个函数的返回值是指针类型时,函数如果执行出错返回值就是NULL

  • 如何避免空指针带来的段错误:

    使用来历不明的指针前先做判断

    ​ 1、从函数中获取的指针返回值,可能是空指针

    ​ 2、当函数的参数是指针,别人传给你的就可能是空指针

    ​ if(NULL == p)

    ​ if(!p)

    注意:NULL在绝大多数系统中是0,个别系统是1

  • 野指针:指向不确定的内存空间

解引用野指针的后果:

​ 1、一切正常

​ 2、脏数据

​ 3、段错误

野指针比空指针的危害更严重,因为野指针无法被判断出来,而且可能是隐藏性的错误短时间不暴露

  • 所有的野指针都是程序员自己制造出来的,如何避免产生野指针

    ​ 1、定义指针变量时一定要初始化

    ​ 2、函数不要返回栈内存的地址

    ​ 3、指针指向的内存释放后,指针变量要及时置空

五、指针的运算

指针变量中存储的是整型,理论上整型数据可以使用的运算符它都可以使用,但是大多数都是无意义。

  • 指针 + n 指针+指针类型字节数*n 前进了n个元素

  • 指针 - n 指针-指针类型字节数*n 后退了n个元素

  • 指针 - 指针 (指针-指针)/类型字节数 计算出两个指针之间间隔了多少个指针元素

  • 注意:指针相减,指针类型必须一致

#include <stdio.h>

int main(int argc,const char* argv[])
{
    int a = 1,b = 2;
	int* p1 = &a;
	int* p2 = &b;
	printf("p1 = %p\np2 = %p\np1+1 = %p\np1-1 = %p\np2-p1 = %d ",p1,p2,p1+1,p1-1,p2-p1);
}

在这里插入图片描述

六、const 与指针

  • 当我们为了提高传参效率而使用指针时,传参的效率提高了,但是变量被共享后有了被修改的风险,可以借助const保护指针所指向的内存。

      const int* p;  保护指针所指向的内存不被修改
    
      int const *p;  同上
    
      int* const p;  保护指针变量不能修改
    
      const int* const p; 指针变量和指针所指向的内存都不能修改
    
      int const* const p; 同上
    

七、指针数组与数组指针

  • 指针数组:是一个数组,由指针变量组成,该数组的成员是指针变量

    int* arr[10];

  • 数组指针:是一个指针,专门指向数组的指针

    类型 (*arrp)[长度]

    int (*arrp)[10];

八、数组名和指针

  • 数组名可以看做一种特殊的指针,它是常量,不能修改他的值,

  • 数组名与数组的内存之间是映射关系,而指针变量和数组内存间是指向关系

  • 数组名是没有自己的存储空间的

    数组名 == &数组名 == &数组名[0]

  • 如果指针变量中存储的是数组的首地址,指针可以当做数组使用,数组名也可以当做指针来使用,数组作为函数的参数是变成了指针,所以长度丢失

    数组名[i] == *(数组名+i)

    *(p+i) == p[i]

九、二级指针

  • 二级指针是指向指针的指针,里面存储的是指针变量的地址

  • 定义: 类型** 变量名_pp;

  • 赋值: 变量名_p = &指针变量; // 存储指针变量的地址

  • 解引用: *变量名_p = 指针变量; // 指针变量的值即指针变量指向内存的地址

    ​ **变量名_p <=> *指针变量 <=> 普通变量

十、函数指针

函数名就是该函数在代码中的内存首地址

调用函数其实就是跳转到该函数所在代码段中去执行二进制指令

函数指针就是用来专门指向函数的指针,里面存储的是函数的首地址,对函数指针解引用就可以执行函数

函数指针可以当做函数使用

  • 定义函数指针:返回值 (*指针变量名)(类型1,类型2,…);

  • 赋值: 指针变量名 = 函数名;

  • 调用函数:指针变量名(实参);

通过函数指针,把函数当做参数一样传递给另一个函数使用,这就是回调

学无止境,无限进步!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值