五大板块(2)—— 指针

本文深入介绍了C语言中的指针,包括地址的概念、按字节编址、指针的类型与偏移量、指针在函数调用和内存管理中的作用。通过实例展示了指针如何指向数组、函数以及如何避免野指针问题。最后,通过一个简单的学生成绩管理系统展示了指针的实际应用。
摘要由CSDN通过智能技术生成

一、地址的引入

概念
地址是一个十六进制表示的整数,用来映射一块空间,是系统用来查找数据位置的依据。地址标识了存储单元空间,而字节就是最小的存储单位。
按字节的编址方式:每一个字节都有一个唯一的地址。例如:一个int型的变量是4个字节,就会对应4个地址,我们只需要取到这个变量的首地址就能得到完整的int型数据。
用一个例子感受变量存放的地址:

#include <stdio.h>

int main()
{
        int a=10;
        int b=11;
        int* p=&a;
        int* p2=&b;

        printf("a的地址是:%p\n",p);
        printf("b的地址是:%p\n",p2);

        return 0;
}

结果:可以发现两者地址相差4个字节,说明int型变量用4个字节的空间存放

a的地址是:0x7ea5d1dc
b的地址是:0x7ea5d1d8

在这里插入图片描述

二、指针分类型与指针偏移量

用sizeof发现linux下所有指针类型的大小均为8字节。

首先明白:指针所占用的空间与指针指向的内容和内容的大小无关。
其次明白:在不同的操作系统及编译环境下,指针类型所占用的字节数是不同的
例如:
编译生成16位的代码时,指针占2个字节
编译生成32位的代码时,指针占4个字节
编译生成64位的代码时,指针占8个字节

整型指针,字符指针

#include <stdio.h>

int main()
{
        int a = 5;
        char b = 'A';

        int *pa = &a;//存放整型数的指针叫整型指针
        char *pb = &b;//而这叫字符型指针

        //printf("int型指针 pa 的地址是%p,指针偏移(++pa)的地址是:%p\n",pa,++pa);
        //printf("char型指针 pb 的地址是%p,指针偏移(++pb)的地址是:%p\n",pb,++pb);

        printf("int 型指针pa的地址是%p\n",pa);
        printf("int 型指针偏移(++pa)后的地址是:%p\n\n",++pa);

        printf("char 型指针pb的地址是%p\n",pb);
        printf("char 型指针偏移(++pb)后的地址是:%p\n",++pb);

        return 0;

}

结果:可以看到指针类型不同,其每次偏移的地址量也不同。

pi@raspberrypi:~/Desktop $ ./a.out  
int 型指针pa的地址是0x7ead81ec
int 型指针偏移(++pa)后的地址是:0x7ead81f0

char 型指针pb的地址是0x7ead81eb
char 型指针偏移(++pb)后的地址是:0x7ead81ec


三、为什么要用到指针

1、函数传过去指针,可以取里面的值改变,比如交换两个数的值,
#include <stdio.h>
int main()
{
	int a = 10;
	printf("address of a: %p\n",&a);
	volatile unsigned int* p =(volatile unsigned int*)0x......
	printf("address of p: %p\n",p);
	return 0;
}

2、单片机或arm中寄存器地址

volatile和编译器的优化有关:
在这里插入图片描述
编译器的优化

在本次线程内,当读取一个变量时,为了提高读取速度,编译器进行优化时有时会先把变量读取到一个寄存器中(寄存器比内存要快!);以后,再读取变量值时,就直接从寄存器中读取;当变量值在本线程里改变时,会同时把变量的新值copy到该寄存器中,以保持一致。

当变量因别的线程值发生改变,上面寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致。
当寄存器因别的线程改变了值,原变量的值也不会改变,也会造成应用程序读取的值和实际的变量值不一致。

3、节省内存

指针的使用使得不同区域的代码可以轻易的共享内存数据,当然也可以通过数据的复制达到相同的效果,但是这样往往效率不太好。
指针节省内存主要体现在参数传递上,比如传递一个结构体指针变量和传递一个结构体变量,结构体占用内存越大,传递指针变量越节省内存,也就是可以减少不必要的数据复制。

4、动态分配内存

常常可以看到,程序使用的内存在一开始就进行分配(静态内存分配)。这对于节省计算机内存是有帮助的,因为计算机可以提前为需要的变量分配内存。

但是大多应用场合中,可能一开始程序运行时不清楚到底需要多少内存,这时候可以使用指针,让程序在运行时获得新的内存空间(动态内存分配),并让指针指向这一内存更为方便。

5、函数多个返回值

有时候我们总是希望一个功能子函数的返回值可以有很多个,但奈何用return只能返回一个。

四、定义指针指向数组

数组的首地址

int arry[4] = {1,2,3,4};
int *p;
p = &arry[0];
//p = arry;

见怪不怪
1、指针当作数组名,下标法访问
2、数组名拿来加
arry++可行否(×);//数组常量,指针变量
sizeof(arry),可以算出其长度;
sizeof(p);是8;操作系统中8byte表示一个指针变量;

五、函数指针(重点)

1、无参无返的函数指针

这是函数指针最简单的一种形式

#include <stdio.h>

void print()//要被指向的函数
{
        printf("hello world\n");
}

int main()
{
        void (*pprint)() = NULL;//定义函数指针
        pprint = print;         //函数指针赋值:指向函数的首地址(就是函数名)
                                              //如同数组的首地址,是数组名
        pprint();        //调用方法1
        (*pprint)();     //调用方法2
        
        printf("函数指针pprint的地址是%p\n",pprint);
        printf("函数指针偏移(++pprint)后的地址是:%p\n",++pprint);

        return 0;
}

结果:

hello world
hello world
函数指针pprint的地址是0x1046c
函数指针偏移(++pprint)后的地址是:0x1046d

2、有参有返的函数指针
#include <stdio.h>

int sum(int a,int b)//要被指向的函数
{
        int c = 0;
        c = a+b;
        return c;
}

int main()
{
        int total1;
        int total2;

        int (*psum)(int a,int b) = NULL;//定义函数指针

        psum = sum;           //函数指针赋值,指向函数的首地址(就是函数名)
                                            //如同数组的首地址,是数组名
        total1 = psum(5,6);   //调用方法1
        total2 = (*psum)(6,9);//调用方法2

        printf("total1:%d\ntotal2:%d\n",total1,total2);
		printf("%p\n",psum);
		printf("%p\n",++psum);
        return 0;
}

结果

total1:11
total2:15
0x10440
0x10441

3、结构体中的函数指针

比较常见的还是和结构体的结合

#include <stdio.h>
#include <string.h>

struct student
{
        int english;
        int japanese;
        int math;
        int chinese;
        char name[128];
        int (*pLanguage)(int english,int japanese);//顺便复习函数指针怎么使用

};

int Language(int eng,int jap)//函数指针所指向的函数
{
        int total;
        total = eng + jap;
        return total;
}

int main()
{
        int lanSum;
        struct student stu1 = {
                .japanese = 90,
                .english = 100,
                .math = 90,
                .name = "cpc",
                .pLanguage = Language,
        };

        lanSum = stu1.pLanguage(stu1.english,stu1.japanese);

        printf("stu1的名字是%s,他的语言综合分数是%d\n",stu1.name,lanSum);
		
		printf("%p\n",stu1.pLanguage);
		printf("%p\n",++stu1.pLanguage);
        return 0;
}

4、规律总结

函数指针无非就三步走:

A、定义

类型 (*指针名)();

void (*pprint)() = NULL;

两个括号很好记

B、赋值

指针名 = 函数名

pprint = print;
C、调用

如有参数则调用时传

pprint();        //调用方法1
(*pprint)();     //调用方法2

六、用一个结构体指针做一个最简单的学生成绩管理

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

struct stud
{
        char* name;
        int score;
};

int main()
{
        int num;
        int i;
        printf("需要录入几个学生的成绩?\n");
        scanf("%d",&num);

        //这里开辟了num个结构体所需要的空间,动态分配内存
        struct stud *pstu = (struct stud *)malloc(sizeof(struct stud)*num);


        for(i=0;i<num;i++){
                pstu->name = (char* )malloc(sizeof(char)*16);
                memset(pstu->name,0,sizeof(char)*16);

                printf("请输入第%d个学生的名字\n",i+1);
                scanf("%s",pstu->name);
                printf("请输入第%d个学生的成绩\n",i+1);
                scanf("%d",&pstu->score);
                pstu++;
        }
        pstu -= num;//指针回头

        for(i=0;i<num;i++){
                printf("%s:%d\n",pstu->name,pstu->score);
                pstu++;
        }

        return 0;
}     

七、野指针

1、定义

野指针指向的地址是随机(又称为:"垃圾"内存)的,无法得知他的地址,操作系统自动对其进行初始化。

2、野指针是怎样生成的?

(1)创建指针时没有对指针进行初始化
(2)使用free释放指针后没有将其指向NULL

3、有什么危害

当一个指针成为野指针,指向是随机的,当你使用它时,危害程度也是随机而不可预测的。一般会造成内存泄漏也很容易遭到黑客攻击,只要将病毒程序放入这块内存中,当使用到这个指针时就开始执行。

4、如何避免

定义指针时进行初始化
如果没有确定指向,就让它指向NULL

NULL在宏定义中是#define NULL (void **) 0,代表的是零地址,零地址不能进行任何读写操作

要给指针指向的空间赋值时,先给指针分配空间,并且初始化空间

简单示例:

//char型指针
char *p = (char *)malloc(sizeof(char));
memset(p,0,sizeof(char));

//int型指针
//指针(指向地址)游标卡尺   开辟空间大小
int *p     =     (int *)malloc(sizeof(int));
memset(p,0,sizeof(int));

//结构体指针
struct stu *p = (struct stu *)malloc(sizeof(struct stu));
memset(p,0,sizeof(struct stu));

malloc动态内存分配,用于申请一块连续的指定大小的内存块区域以void类型返回分配的内存区域地址。voidmalloc(unsigned int size),因为返回值时void*,所以要进行强制转换。

memset将某一块内存中的内容全部设置为指定的值,
这个函数通常为新申请的内存做初始化工作,是对较大的结构体或数组进行清零操作的一种最快方法。void *memset(void *s, int ch, size_t n);

释放指针同时记得指向NULL

free(p);
p = NULL;

5、malloc与内存泄漏

情景:
程序刚跑起来的时候没问题,时间久了程序崩溃,大多为内存泄漏。

最常见的情况是在无限的循环中一直申请空间。用malloc申请的空间,程序不会主动释放,只有当程序结束后,系统才回收空间。

避免在循环中一直申请空间,即使合理释放(free,指向NULL)

6、指针类型小测试
int *p[4];
int (*p)[4];
int *p();
int(*p)();

指针数组,数组中存放的是一系列的地址
数组指针,指向一个数组
只是一个普通的函数,其返回值是int* 的指针
函数指针,指向一个函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值