栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

五大板块(2)—— 指针

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

五大板块(2)—— 指针

一、地址的引入

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

#include 

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

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

        return 0;
}

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

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

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

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

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

整型指针,字符指针

#include 

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

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

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

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

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

        return 0;

}

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

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

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


三、为什么要用到指针 1、函数传过去指针,可以取里面的值改变,比如交换两个数的值,
#include 
int main()
{
	int a = 10;
	printf("address of a: %pn",&a);
	volatile unsigned int* p =(volatile unsigned int*)0x......
	printf("address of p: %pn",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 

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

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

        return 0;
}

结果:

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

2、有参有返的函数指针
#include 

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:%dntotal2:%dn",total1,total2);
		printf("%pn",psum);
		printf("%pn",++psum);
        return 0;
}

结果

total1:11
total2:15
0x10440
0x10441

3、结构体中的函数指针

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

#include 
#include 

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,他的语言综合分数是%dn",stu1.name,lanSum);
		
		printf("%pn",stu1.pLanguage);
		printf("%pn",++stu1.pLanguage);
        return 0;
}

4、规律总结

函数指针无非就三步走:

A、定义

类型 (*指针名)();

void (*pprint)() = NULL;

两个括号很好记

B、赋值

指针名 = 函数名

pprint = print;
C、调用

如有参数则调用时传

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

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

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
                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
                printf("%s:%dn",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* 的指针
函数指针,指向一个函数

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/869758.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号