指针的运用

C语言—指针的学习心得和理解

一、什么是指针

什么是指针?指针是一种数据的类型(代表地址的整数),使用定义的变量叫指针变量。变量里面存储的就是无符号整数,这些整数代表。指针具有四个基本的要素:1.指针的类型 2.指针指向的类型 3.指针的值 4.指针本身所占据的内存区


二、为什么使用指针、什么情况下使用指针

为什么使用指针、什么情况下使用指针?使用指针的情况大致可以分为三类:
①函数之间无法通过传参共享变量。属于调用者,函数之间的名字空间相互独立是可以重名的,函数之间的数据传递都是值传递。
②函数调用时传参的效率太差,指针可以优化函数之间传参的效率
③堆内存无法与标识符建立联系,只能配合指针使用


类型一

#include<stdio.h>
void func(int a,int b)
{
	a=100;b=50;
}
int main()
{
	int a=50;int b=100;
	func(a,b);
	printf("%d %d",a,b);
}
结果 50 100

在以上的代码块中函数之间就无法共享变量(main的a.b和func的a.b是相互独立的)它们变量存放的内存位置并不相同。

#include<stdio.h>
void func(int* a,int* b)
{
	*a=100;*b=50;
}
int main()
{
	int a=50,b=100;
	func(&a,&b);
	printf("%d %d",a,b);
}
结果 100 50

以上代码块中传递的是a.b的地址,再对a.b进行解引用修改其中的值。


类型二

#include<stdio.h>
#include<string.h>
typedef struct Stu
{
	char name[20];
	int  age;
	char sex;
}Stu;

void intput_stu(Stu*  stu)//输入函数
{
	stu->age = 10;
	strcpy(stu->name,"MYJ");
	stu->sex = 'N';
}
void show_stu(Stu* stu)//显示函数
{	
	printf("%s %d %c",stu->name,stu->age,stu->sex);
}
int main()
{
	Stu stu;
	intput_stu(&stu);
	show_stu(&stu);
}

在以上的的代码块中,函数调用传递的是结构体地址,只需要传递4字节的内容,可以大大提高传参的效率,该传参方式体现在传递数组指针和传递函数指针等方面…


类型三

int *p =NULL;
p = malloc(4);

与堆内存的配合使用


三、如何使用指针

	int num=0;
	int* p=NULL;
	p=&num;
	printf("%d\n",*p);
	printf("%p",p);
结果 0
	0xbface5c8

定义: 类型* 变量名p;

  1. // * p表示此变量是指针变量,一个*只能定义出一个指针变量,不能连接定义
    int *p1,p2,p3 ; // p1是指针,p1,p21是int变量
    int *p1, *p1,*p3; //p1,p2,p3都是指针
  2. int* 表示p是一个int类型的指针变量,一般来说指针的默认值也是不确定的,一般初始化为NULL;
  3. p=&num 表示p指向的是int类型的变量num的地址,’&'是取num的地址赋值给p。
  4. *p 表示对p所指向的地址进行解引用。因为指针p也是一种变量,所以他也有内存去储存,%p可以输出存储p的内存的地址。
  5. 类型表示的是存储的是什么类型的变量的地址,它决定当通过地址访问这块内存时访问的字节数

解引用(根据地址访问内存):*指针变量名

  1. 根据变量中存储的内存编号区访问内存中的数据
  2. 如果指针变量中存储的地址出错,此时可能发生段错误(这是赋值产生的错误)

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

空指针:指针变量的值为NULL(一般情况下是0,也有特殊情况是1),这种指针变量叫空指针,空指针不能进行解引用(*指针变量 ),NULL被操作系统当作复用指针了(存储了系统重启所需要的数据)当操作系统察觉到程序访问NULL位置的数就会向程序发送错误信号。
空指针还被当做错误标志,如果一个函数的返回值是指针类型,实际返回的值是NULL,则说明函数执行失败或错误;
在C语言代码中应该杜绝对空指针进行解引用,当使用来历不明的指针需要对指针进行判断他是否为空。
if(NULL ==p)

野指针:指针变量的值是不确定的或都是无效的,这种指针叫野指针。
使用野指针不一定会出现问题,但是可能会出现以下问题:
①一切正常 ②段错误 ③脏数据
虽然野指针不一定会出错,但是野指针比空指针的危险更大,野指针是无法判断来,也无法测试出来,也就意味着一旦产生无法避免。
虽然野指针无法判断也无法测试出来,但是所有的野指针都是人为制造出来的最好的方法不生产野指针:
1. 定义指针的变量时没有初始化
2. 获取到局部变量的指针的地址(因为局部变量作用域结束后会被释放,这时指针指向不确定)运算完后需要置空
3. 资源释放后,指向他的指针要及时置空


五、指针与数组的关系

数组名就是一个指针(常指针)
数组名与数组首地址是映射关系 *p=arr是指向关系(&*p是有一个地址的)
由于数组名就是指针,所以数组名可以使用指针的解引用运算符,而指针也可以使用数组。
使用数组当函数的参数时,数组会蜕变成指针,长度也就丢失,因此需要外增加一个参数来传递数组的长度。

#include <stdio.h>
int main()
{
	int arr[5] = {1,2,3,4,5};
	int* p = arr;
	printf("%p %p %p\n",arr,&arr,&arr[0]);
	for(int i=0; i<2; i++)
	{
		printf("%d %d\n",*(arr+i),p[i]);
	}
}
结果:
0xbff54d74 0xbff54d74 0xbff54d74
1 1
2 2

arr、&arr、&arr[0] 三者的地址都是一样的(0xbff54d74),但是三者表示的意义并不相同。
arr表示数组的首个元素的地址arr是个数值指针,&arr表示一个包含了5个int类型元素的数组的数组的首地址
&arr[0]表示数值首个元素的地址是对arr[0]取地址。
如果还分不清&arr和arr可以看以下代码:

#include<stdio.h>
int main()
{
	int arr[5] = {1,2,3,4,5};
	int *ptr = (int *)(&arr+1);
	int result1 = *(arr+1);	
	int result2 = *(ptr-1);
	printf("%d,%d\n",result1,result2); // 结果为2,5
	printf("%p\n%p\n",arr+1, &arr+1);
}
结果:
2,5
0xbfd8dc44
0xbfd8dc54

&arr+1 是跳过了整个arr数组的内存指向了下一块相邻的地址(在该代码中是内存向后移动了20个字节,0xbfd8dc40->0xbfd8dc54),*(ptr-1)然后转换为int类型,向前移动4字节(int类型)地址变为0xbfd8dc50该地址里面的值为5.


六、指针的运算

指针的本质是一个int类型的整数,因此此语法上来说整数能使用的运算符他都可以使用,不是所有的运算符对指针运算都是有意义的:
指针 + 整数 <=> 指针 + 宽度(类型) * 整数 向右移动 指向下一个元素的地址
指针 - 整数 <=> 同上
指针 - 指针 <=>(指针-指针)/宽度 =>计算出两个指针之间相隔多少个元素。


七、指针与const配合

const的作用:C语言中一种保护机制,保护对象不能被显性修改
const * p :保护指针指向的数据,不能通过指针解引用修改内存的值
int const *p :同上 , int位置无所谓
int * const p :保护指针变量,指针变量初始化后不能再显式赋值
const int *const p :指针和值都不能修改
int const * const p :同上


八、什么是二级指针、什么情况下使用

二维指针:指向指针的指针,它也是个指针变量,但它存储的是指针变量的地址。
float * p = NULL;
定义:float** pp = &p;
使用:*pp <=> p ; **p <=> *p;
什么情况下使用?函数之间共享普通变量使用一级指针,那么同理,共享指针变量时则需要使用二级指针

#include <stdio.h>
#include <stdlib.h>

void create_memory(void** p,int size)
{
	*p = malloc(size);
}
int main()
{
	int* p = NULL;
	create_memory(&p,40);
	printf("%p\n",p);
	for(int i=0; i<10; i++)
	{
		printf("%d ",p[i]);
	}
}

在该代码块中,我前面说过堆内存是无法和标识符建立联系的,所以只能使用指针。该代码就是依靠二级指针来申请内存并回传内存地址的。


九、函数指针

函数指针:
函数就是存储在代码段中的一段数据,当被调用时跳转到那个位置去执行,而函数名就是这段数据的首地址(函数指针),因此函数名就是个指针。
程序员可以自己定义函数指针来指向函数:
1、写出函数的声明
2、为函数名添加小括号
3、修改函数名,并在函数名前加*
函数:int sum(int , int );
函数指针声明:int (*pa) (int , int ); //函数指针变量是pa

	定义好函数指针后就可以指向函数了,通过()就可以调用函数,而不用*解引用。
	函数指针真正的作用是可以把函数当作参数在函数之间进行传递,然后达到某一效果:多年前写的代码来调用现在所定的代码,这种模式叫回调。

这里可以举一个编辑器函数自带的函数qsort()的例子:

void qsort(void *base, size_t nmemb, size_t size,int(*compar)(const void *, const void *))

功能:快速排序
base:待排序数组首地址
nmemb:数组中待排序元素数量
size:各元素的占用空间大小
compar:指向函数的指针

#include <stdio.h>
#include <stdlib.h>
int compar(const void* p1,const void* p2)
{
	int num1 = *(int*)p1;
	int num2 = *(int*)p2;

	if(num1 > num2)
		return -1;
	else if(num1 < num2)
		return 1;
	else
		return 0;
}
int main()
{
	int arr[] = {2,5,1,4,6,7,8,3,0,9};
	qsort(arr,sizeof(arr)/sizeof(arr[0]),sizeof(arr[0]),compar);
	for(int i=0; i<sizeof(arr)/sizeof(arr[0]); i++)
	{
		printf("%d ",arr[i]);
	}
}

下面这个例子可能更为准确:

#include <stdio.h>
#include <stdbool.h>

bool is_prime(int num)
{
	printf("我能判断是否是素数...\n");
}

int main()
{
	printf("%p\n",main);
	bool (*is_prime_p)(int num);

	is_prime_p = is_prime;

	is_prime_p(10);
}


十、指针数组与数组指针

指针数组:由指针变量构成的数组 它的实质是一个数组,元素为指针变量
定义:char* arr[5]; // 定义一个长度为5的数组,成员类型是char*
相当于定义的5个 char* 指针变量,char *p1,*p2,*p3,*p4,*p5;

数组指针:专门用来指向数组的指针。 它相当于二维指针的行指针。它的本质是指针,指向数组
定义:int (*p)[10] = NULL;
更多时候用来指向二维数组,当使用二维数组当函数参数时,可以使用数组指针来当形参。

指针数组和数组指针的内存分布:
在这里插入图片描述

指针数组例子:

//输入一个数,将其各位相加,转换为相应的中文字符
#include <stdio.h>
int main()
{
	char* str[] = {"ling","yi","er","san","si","wu","liu","qi","ba","jiu"};
	char num[1000] = {};
	gets(num);
	
	int count = 0 , sum = 0 , arr[4] = {};

	for(int i=0; num[i]; i++)
	{
		sum += num[i]-'0';
	}
	
	while(sum)
	{
		arr[count++] = sum % 10;
		sum /= 10;
	}

	for(int i=--count; i>0; i--)
	{
		printf("%s ",str[arr[i]]);
	}
	printf("%s\n",str[arr[0]]);
}
输入 12345
输出 yi wu
输入 1234567
输出 er ba

猜你喜欢

转载自blog.csdn.net/weixin_38663899/article/details/85302386