概念
地址是一个十六进制表示的整数,用来映射一块空间,是系统用来查找数据位置的依据。地址标识了存储单元空间,而字节就是最小的存储单位。
按字节的编址方式:每一个字节都有一个唯一的地址。例如:一个int型的变量是4个字节,就会对应4个地址,我们只需要取到这个变量的首地址就能得到完整的int型数据。
用一个例子感受变量存放的地址:
#includeint 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个字节
整型指针,字符指针
#includeint 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、函数传过去指针,可以取里面的值改变,比如交换两个数的值,
#include2、单片机或arm中寄存器地址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; }
volatile和编译器的优化有关:
编译器的优化
在本次线程内,当读取一个变量时,为了提高读取速度,编译器进行优化时有时会先把变量读取到一个寄存器中(寄存器比内存要快!);以后,再读取变量值时,就直接从寄存器中读取;当变量值在本线程里改变时,会同时把变量的新值copy到该寄存器中,以保持一致。
当变量因别的线程值发生改变,上面寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致。
当寄存器因别的线程改变了值,原变量的值也不会改变,也会造成应用程序读取的值和实际的变量值不一致。
指针的使用使得不同区域的代码可以轻易的共享内存数据,当然也可以通过数据的复制达到相同的效果,但是这样往往效率不太好。
指针节省内存主要体现在参数传递上,比如传递一个结构体指针变量和传递一个结构体变量,结构体占用内存越大,传递指针变量越节省内存,也就是可以减少不必要的数据复制。
常常可以看到,程序使用的内存在一开始就进行分配(静态内存分配)。这对于节省计算机内存是有帮助的,因为计算机可以提前为需要的变量分配内存。
但是大多应用场合中,可能一开始程序运行时不清楚到底需要多少内存,这时候可以使用指针,让程序在运行时获得新的内存空间(动态内存分配),并让指针指向这一内存更为方便。
5、函数多个返回值有时候我们总是希望一个功能子函数的返回值可以有很多个,但奈何用return只能返回一个。
四、定义指针指向数组数组的首地址
int arry[4] = {1,2,3,4};
int *p;
p = &arry[0];
//p = arry;
五、函数指针(重点) 1、无参无返的函数指针见怪不怪
1、指针当作数组名,下标法访问
2、数组名拿来加
arry++可行否(×);//数组常量,指针变量
sizeof(arry),可以算出其长度;
sizeof(p);是8;操作系统中8byte表示一个指针变量;
这是函数指针最简单的一种形式
#includevoid 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)后的地址是:0x1046d2、有参有返的函数指针
#includeint 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 0x104413、结构体中的函数指针
比较常见的还是和结构体的结合
#include4、规律总结#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; }
函数指针无非就三步走:
A、定义类型 (*指针名)();
void (*pprint)() = NULL;
两个括号很好记
B、赋值指针名 = 函数名
pprint = print;C、调用
如有参数则调用时传
pprint(); //调用方法1 (*pprint)(); //调用方法2六、用一个结构体指针做一个最简单的学生成绩管理
#include七、野指针 1、定义#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; }
野指针指向的地址是随机(又称为:"垃圾"内存)的,无法得知他的地址,操作系统自动对其进行初始化。
2、野指针是怎样生成的?(1)创建指针时没有对指针进行初始化
(2)使用free释放指针后没有将其指向NULL
当一个指针成为野指针,指向是随机的,当你使用它时,危害程度也是随机而不可预测的。一般会造成内存泄漏也很容易遭到黑客攻击,只要将病毒程序放入这块内存中,当使用到这个指针时就开始执行。
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* 的指针
函数指针,指向一个函数



