C语言3.29指针学习

指针

什么是指针

指针就是一种特殊的数据类型,使用它可以定义指针变量,指针变量中存储的就是整型数据,代表了内容的编号,
通过这个编号可以访问到对应的内存。

类型 定义指针变量 整型 内存编号

为什么要使用指针:

1、函数之间是相互独立的,但是有时候会共享变量
	函数传参是值传递
	全局变量容易命名冲突
	使用数组还需要传递长度
	命名空间是独立的,但是地址空间是同一个,所以指针可以解决这个问题
2、由于函数之间传参是值传递(内存拷贝),对于字节数较多的变量,值传递的效率较低,如果传递变量的地址,只需要传递4\8字节(操作类型)
3、堆内存无法取名字,它不像data、bss、stack让变量名与内存之间建立对应关系,只能使用指针记录内存的地址来使用该段内容。

1、共享变量,输出参数 2、提高传参效率 3、使用堆内存

#include <stdio.h>
#include <time.h>

int _time(int* p)      
{
	*p = 5678;
	return 1234;
}

int main(int argc,const char* argv[])
{
	time_t sec = 0;    //格式为time_t time(time_t *t),计时格式为time_t = time(NULL)
	time_t t = time(&sec);
	printf("%lu %lu\n",t,sec);
	int ret2 = 0;
	int ret = _time(&ret2);
	printf("%d %d\n",ret,ret2);
}

在这里插入图片描述

返回多个值需要指针

如何使用指针:

****定义:****
	    类型 变量名_p;
		1、由于指针变量与普通变量之间用法有很大的的区别,+p区分
		2、指针的类型表示存储的事什么类型变量的地址,它决定了通过这个指针变量可以访问的字节数
	    3、一个*只能定义一个指针变量,不能连续定义多个指针变量
		int* p1,p2,p3;  //p1是指针变量,p2.p3都是int类型变量
	    int *p1,*p2,*p3;  //p1 p2 p3都是int类型的指针变量
		4、指针变量与普通变量一样默认值是随机的,一般初始化为NULL
		
	赋值:
	    变量名_p = 地址;                  ***必须是有意义有权限的地址***
	指向栈内容
		int* p = &变量 ;
	指向堆内存:
    	int *p = malloc(4)
    	也可以在定义时直接赋值;	
int main(int argc,const char* argv[])
{
		int num = 10;
		// 定义指针变量
		int* p;
		// 指针变量赋值
		p = &num;
		//	解引用
		*p = 100;
		printf("%d %d\n",num,*p);
		printf("%p %p\n",&num,p);                //%p自动转换为16进制。表示地址
}

在这里插入图片描述

解引用
	*p
	通过指针变量中记录的内存编号去访问内存,这个过程叫解引用
	该过程可能产生段错误,原因是里面存储的内容编号是非法的。
#include <stdio.h>

int main(int argc,const char* argv[])
{
	int num = 10;
	int* p;
	p = NULL;           
	//	解引用
	*p = 100;          

	printf("%d %d\n",num,*p);
	printf("%p %p\n",&num,p);
}

赋值不会报错,解引用中有非法的内容编号NULL,产生段错误

#include <stdio.h>

//int scanf; 容易导致命名冲突

void func(int* p) //共享变量
{
	printf("func %d %p\n",*p,p);
	*p = 100;
	printf("func %d %p\n",*p,p);
}

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

共享的是地址,因此可以共享变量
在这里插入图片描述

#include <stdio.h>

void func(long addr)                //有可能运行在64位系统中8字节,用int恐丢失数据
{
	*(int*)addr	= 100;
	printf("%#x\n",addr);           //输出的是addr的编号不是地址

}

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

在这里插入图片描述

※(int*)addr = 100; //强转成指针(int括号前也是一个*)
虽然定义的是long类型,但调用时使用&num地址,虽然有警告,但证明地址也是整型

"%#x"的意思是在输出前面加上0x,#b的意思是在输出前面加上0b,是带格式0x的16进制输出。
传递地址效率会快很多。 time ./a.out 运行时间!! len=sizeof(a)/sizeof(int);

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

#include <stdio.h>

void func(int* p1,int* p2)
{
	int num = *p1;
	*p1 = *p2;
	*p2 = num;

}

int main(int argc,const char* argv[])
{
	/*
	int arr[];
	for(int i=0;i<20;i++)
	{
		a[i]=rand()%100;   //随机数
	}
	*/
	int a[8]={1,2,10,6,3,20,4,7};
	int n;
	scanf("%d", &n);
	for(int i=0;i<n-1;i++)
	{
		for(int j=i+1;j<n;j++)
		{
			if(a[i]>a[j])
			{
				func(&a[j],&a[i]);
			}
		}
		
			printf("%d ", a[i]);
	}

return 0;
}

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

#include <stdio.h>

int max_min(int num1,int num2,int *p)
{
	int i,max,min;
	for(i=(num1>num2?num2:num1);i>=1;i--)
	{
		if(0 == num1%i && 0 == num2%i)
		{
			max = i;
			break;
		}
	}
	for(i=(num1>num2?num1:num2);i<=num1*num2;i++)
	{
		if(0 == i%num1 && 0 == i%num2)
		{
			*p = i;
			break;
		}
	}
	return max;
}
	

int main(int argc,const char* argv[])
{
	int num1=0,num2=0,max=0;
	/*int *min;*/       //指针必须指向一个有意义的地址
	int min=0;
	scanf("%d%d", &num1,&num2);
	max = max_min(num1,num2,&min);           //需要先进行函数运算,不能直接将函数直接在输出中应用,直接应用导致max正常,而min=0初始化
	printf("%d %d", max, min);
	return 0;
}

使用指针需要注意的问题:

	**空指针:值为NULL的指针变量都叫做空指针,如果对空指针进行解引用会产生段错误**.
	NULL在<stdio.h>里
		1、如何避免空指针带来的段错误:使用任何来历不明的指针前先做判断
		if(NULL == p)
		2、函数的指针形参或者从函数获取的指针类型的返回值也可能是空指针
		3、NULL也是一种错误标志,如果一个函数的返回值是指针类型,当函数执行错误时,可以返回NULL表示出错
		
	**野指针:指向不确定的内容空间**
	对野指针进行解引用:
		1、段错误 2、脏数据 3、一切正常
		通俗的讲,当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。
	野指针比空指针危害更大,原因是无法被判断出来,而且可能是隐藏型的错误短时间难以发现,可能是隐藏性错误
	但所有的野指针都是程序员自己制造的。如何避免产生野指针
		1、定义指针变量时一定要初始化 int* p = &num;
		2、函数不要返回栈内存的地址
		3、指针指向的内容释放后,指针变量要及时的置空 = NULL

指针的运算:

	指针变量中存储的就是整数,理论上整型数据能使用的运算符他都可以使用,但大多数都是没有意义的。
	指针 + n <==> 指针 + 指针类型宽度*n 前进了n个元素
	指针 - n <==> 指针 - 指针类型宽度*n 后退了n个元素
	指针 - 指针 <==> 指针之前相差的总字节数/指针 类型宽度 ,计算出两个指针之间相隔了多少个指针元素,必须是相同类型的指针才能相减
	例:int* p = NULL; printf("%d", p+1) =0x4;
	       char = 0x1
	       short* p = NULL; printf("%d", p+!) =0x2;
#include <stdio.h>

int main(int argc,const char* argv[])
{
	short* p = NULL;
	printf("%p\n",p+1);   //p+1只进行了运算和指针元素字节无关

	p += 100;
	printf("%p\n",p);
	short* p1 = NULL;
	printf("%d\n",p - p1);
}

在这里插入图片描述

指针与const:

	当我们为了提高传参效率而使用指针时,传参效率提高但是变量也因此有了被修改的风险,而使用const与指针配合,可以保护指针指向的内容
	const int* p;        保护指针所指向的内容不可修改
	int const* p;        同上
	int* const p;        指针的指向不可修改
	const int* const p;  指针的指向以及所指向的内容都不可以修改
	int const* const p;  同上,既保护内存也保护指针变量
#include <stdio.h>

void func(const double* f)
{
	printf("%lf\n",*f);	
//	*f = 100;
}

int main(int argc,const char* argv[])
{
	int num;
	//int* const p = NULL;
	int* const p = &num;
	*p = 100; //可以更改

	p = NULL;   //无法更改
	for(double i=0; i<100; i++)
	{
		func(&i);
	}
}

指针数组与数组指针:

	指针数组:
		就是由指针组成的数组,成员都是指针变量
		类型* arr[长度];[]优先级比*高,首先是个数组,其次是个什么类型的数组
		
	数组指针:
		专门指向数组的指针
		类型(*arr)[长度]()优先级表示首先是个指针,其次是指向什么类型,多少长度的数组
#include <stdio.h>

int main(int argc,const char* argv[])
{
	int arr[5] = {1,2,3,4,5};
	printf("%p %p %p \n",&arr[0],&arr,arr);
	int* p1 = arr;
//	p1 = NULL;
//	arr = NULL;
	for(int i=0; i<5; i++)
	{
		//printf("%d ",*(p1+i));	
		printf("%d ",p1[i]);    //指针可以当数组用
		printf("%d ",*(arr+i));	//不代表数组指针,显示值,数组可以当指针用
		//printf("%d ",&arr+1);	//数组指针
	}
}

![在这里插入图片描述](https://img-blog.csdnimg.cn/20210330150633502.png
输出同样的值,意义相同,地址相同。

指针数组很少用,堆内存申请二维数组会用到。

指针与数组名:

	数组名就是一种特殊的指针,它是常量,不能改变它的值。它与数组内存之间是一对一的映射关系,它是没有自己的存储空间的,
	&数组名[0] = &数组名 =数组名      指的是数组第一个元素的地址
	指针变量有自己的存储空间,当它存储的是数据的首地址,指针就可以当做数组来使用,数组名也可以当指针来使用
	数组作为函数的参数时蜕变成了指针,所以长度才会丢失,需要额外的参数来传递长度,所以数组名也可以解引用,指针也可以使用[]。
	注意:指针与内存的关系是指向关系,数组名与内存是映射关系。
		数组名[i] == *(数组名+i)
		指针数组名[i] == *(指针变量名+i)
int a[5] ={……};  int* p = a;   p[5] = {……}

在这里插入图片描述

#include <stdio.h>

int main(int argc,const char* argv[])
{
	int arr[5] = {1,2,3,4,5};
	printf("%p %p\n",&arr,&arr+1);     //&arr指向数组,整个数组宽度len*字节数*(+n)
	int* p = &arr+1;
	printf("%d\n",*(p-1));           //p为int类型,只会后移或前移4*(+n)个元素
	
	int (*p1)[5] = &arr;
	for(int i=0; i<5; i++)
	{
		printf("%d ",(*p1)[i]);	    //指针数组
	}
}

二级指针:

	二级指针就是指向指针的指针,里面存储的是指针变量的地址。
	定义: 类型** 变量名__pp;
	赋值:变量名_pp = &指针变量;
		int num =10;
		int* p = &num;
		int** pp = &pl
	解引用:
		*变量名__pp <==> 指针变量
		**变量名__pp <==> *指针变量
		printf("%p", p1);  指向指针地址 0x……
	当需要共享指针变量时,才需要传递指针变量的地址,也就是二级指针
#include <stdio.h>

void swap(int** pp1,int** pp2)
{
	int* temp = *pp1;
	*pp1 = *pp2;
	*pp2 = temp;
}

int main(int argc,const char* argv[])
{
/*
	int arr[5] = {1,2,3,4,5};864

	int (*pp)[5] = &arr;
	printf("%p %p\n",*pp,&arr);
	printf("%d\n",**pp);
*/
	int num1 = 1234, num2 = 5678;
	int *p1 = &num1, *p2 = &num2;
	printf("%d %d\n",*p1,*p2);
	printf("%p %p\n",p1,p2);
	swap(&p1,&p2);
	printf("%d %d\n",num1,num2);
	printf("%p %p\n",p1,p2);

}

在这里插入图片描述

函数指针:

	函数名就是一个地址(整数),代表了函数在代码段中的首地址。
	调用函数其实就是跳转到该函数所在的代码中执行二进制的指令
	函数指针就是指向函数的指针对它解引用相当于执行该函数
	函数指针可以当做函数使用
	定义函数指针:
		返回值(*指针变量名)
	赋值:
		指针变量名 = 函数名;
	解引用:
		函数指针(实参) == 函数名(实参)
#include <stdio.h>
#include <stdlib.h>

int cmp (const void *p1, const void *p2)
{
/*	int v1 = *(int*)p1,v2 = *(int*)p2;
	if(v1 < v2)
		return 1;
	else if(v1 > v2)
		return -1;
	return 0;
	*/
	return *(int*)p2 - *(int*)p1;
}

int main(int argc,const char* argv[])
{
	int arr[20] = {};
	for(int i=0; i<20; i++)
	{
		arr[i] = rand() % 300;
	}

	qsort(arr,20,4,cmp);
	for(int i=0; i<20; i++)
	{
		printf("%d ",arr[i]);
	}
}
qsort
	#include <stdlib.h>  
            int (*compare)(const void *, const void *)
	{
	}
	void qsort( void *buf, size_t num, size_t size, int (*compare)(const void *, const void *) ); 
功能: 对buf 指向的数据(包含num 项,每项的大小为size)进行快速排序。如果函数compare 的第一个参数小于第二个参数,返回负值;如果等于返回零值;如果大于返回正值。
       函数对buf 指向的数据按升序排序。 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值