内存地址
字节: 是内存的单位byte, 一个字节8位 bits
地址: 系统为了方便访问内存,而对他们以一个字节为单位来进行逐一编号,称为内存地址
基地址
对于单字节的数据来说他的基地址就是他自己的地址本身。
对于多字节的数据来说他的基地址是他地址中地址值最小的那个, 称为该数据的基地址。
取址符
每个变量都有自己的地址,可以使用取址符来获得它们的地址
int a = 100 ; printf("a:%p " , &a ); char c = 100 ; printf("c:%p " , &c ); double d = 100 ; printf("d:%p " , &d );
注意:
1.在内存中存放变量的地址,不一定按照定义的顺序来存放。
2.虽然各个变量的尺寸都有所不同但是地址的长度都是一样的。所以不同类型的指针的大小都是一样的。
指针的基础
指针是一个专门用来存放地址的一个变量。因为同一编译环境下内存地址的长度都是相同的,所以指针的大小也是固定的。
指针定义:
int a ; int *p1 ; // 定义一个指针 p1 专门用来存放整型数据的地址 float *p2 ; // 定义一个指针 p2 专门用来存放浮点型数据的地址 double *p3 = &a ; // 定义并初始化一个指针 p3 指向了一个整型地址;但是逻辑错误了, 如果使用p3来访问内存估计会出现多错误 ,因为a没有初始化
注意:
指针也有自己的地址,而指针的内容存的是对应变量的地址,不要弄混了。
赋值
int a = 1024 ; int * p ; p = &a ; // 让p指向a的地址 int arr[10] ; int * p1 ; p1 = arr ; // 让指针p1指向数组arr首元素的首地址
指针的索引
int a = 1024 ; int * p ; p = &a ; *p = 100 ; // 把100放到 a 的内存中取
野指针
一个指向未知地址的指针称为野指针。野指针是有一定危险性的。
危害:
- 引用野指针,相当于访问了非法内存,一般会导致段错误。
- 引用野指针,有可能访问到系统关键性数据,会导致系统崩溃后果严重。
野指针的产生:
- 指针定义后没有初始化,没有明确的指向。
- 指针原本所指向的内存被释放、系统已经回收。
- 指针越界。
如何防止:
- 指针定义后马上初始化。
- 绝对不使用已经释放的内存的指针,让指针指向NULL便可。
- 小心使用指针。
空指针
一般情况下如果指针定义时还未确定他的明确指向, 我们会选择让该指针指向NULL 。 保证他不会乱指(系统的关键数据)。
在x86架构中,地址0x00000000~0x08048000不可访问,不然会出现段错误。
为了确保指针不会指向系统的关键数据:
(1)当指针没有做初始化,即没有指向时,将指针指为NULL。一方面可以提醒自己这个指向NULL的指针不可操作不可访问(否则会段错误),另一方面NULL这个标记便于我们检查和避免野指针;初始化为NULL的目的:一是出现段错误时易改错,二是(void *0) 是0地址,是不允许操作,不允许访问的。
(2)当想给指针赋值时,检查是否已经给他分配了内存空间,如果没有分配就再用malloc分配;
(3)给指针分配完内存后,不使用时再用free()函数清空内存空间(清空原来的缓冲区),并再将指针指为NULL。
指针的运算
指针运加法、减法指的是指针加、减若干个目标,每次加减的大小由指针所属类型决定
int a = 100;
char b = 'd'; int *p = &a ;
char *u = &b; int *k = b + 1 ; // b + 1 加的是 char 型地址 --> 1字节 int *q = p + 1 ; // p + 1 加的是 int 型地址 --> 4字节
指针数组
用来存放指针的数组,实际上就是数组,只不过每个数组元素是用来存放地址的。
int * arr[5] ; int a = 1;
int b = 1;
int c = 1;
int d = 1;
int e = 1; arr[0] = &a ; arr[1] = &b ; arr[2] = &c;
arr[3] = &d;
arr[4] = &e;
注意:
给指针赋值时,注意要将对应变量初始化,防止发生意料之外的错误。
数组指针
指向数组的指针,实际上是指针,注意和指针数组区分。
int arr[5]; int (*p1)[5]; // 定义一个数组指针
int (*p2)[5];
p1 = arr ;// 实际上p所指向的地址只是数组首元素的首地址 p2 = &arr ; // &arr 表示的是整个数组的首地址 (*p2)[3] = 100 ; // 可通过数组指针p给数组赋值
printf("arr[3]:%d ", arr[3]);
printf("p1: %p ", p1);
printf("p1+1: %p ", p1+1)//p1表示数组首元素的首地址,p1+1就是加一个int型地址的大小,为4字节
printf("p2: %p " ,p2); printf("p2+1: %p " , p2+1 );//p2是表示整个数组的首地址,+1就加一个数组大小 int * 5 = 20 字节
char型指针
char指针实际上与其他的类型指针没有差别,在C语言中字符串一般都是以字符串数组的形式存放在内存中,大部分场合字符数组又是以指针的形式存在,因此在使用字符串的时候都可用char指针来表示。
char * p = "Hello";
多级指针
指向指针的指针,也就是一个指针的内容保存的也是指针的地址,而非某个变量的地址。
int a = 100 ; int * p1 = & a ; // 一个一级指针指向 a的地址 int ** p2 = &p1 ; // 一个二级指针,指向一个一级指针 int ***p3 = &p2 ; // 一个三级指针, 指向一个二级指针 int arr[2][3] ; int (*p)[2][3] ; p = &arr ; printf("&arr:%p ",&arr);//指整个二维数组的首地址 printf("p:%p ",p);//指整个二维数组的首地址 printf("*p:%p ",*p);//指二维数组的首元素的首地址 printf("**p:%p ",**p);//指二维数组的首元素的首元素的首地址 printf("(*p)+1:%p ",(*p)+1);//*p首元素的首地址+1加二维数组中第二维3*sizeof(int)的大小 printf("(**p)+1:%p ",(**p)+1); *((**p)+1)=1024 ; printf("arr[0][1]:%d ",arr[0][1]); int * p1=&(arr[0][0]);//p1指向了这个数组的首元素的首元素的首地址 printf("p1:%p " , p1 );
*(p1 + 1) = 998 ; printf("arr[0][1]:%d " , arr[0][1]); //打印998
注意:
1.这里需要移步我的另外一篇“C语言数组”,才能够看的更清楚。
2.这里的代码比“C语言数组”中的多维数组下的那张图多了一级指针。因为arr[2][3]已经“相当于”二级指针,再对其取地址就只有三级指针能接收了。
万能的指针拆解方法
type *p:*p永远都是第一部分,其它都是第二部分。
char * p1 ; // * p1 是第一部分 , char 说明指针所指向的类型 char ** p2 ; // * p2 是第一部分说明是个指针 , char * 说明指针所指向的类型 是char * char *** p3 ; // * p3 是第一部分 , char ** 说明指针所指向的类型 char (*p4)[3] ; // *p4 是第一部分 , char [3] 第二部分说明指针所指向的类型是char 数组 char (*p5) (int , float) ; // *p5是第一部分 , char (int , float) 第二部分说明指针指向一个函数,该函数有一个char并且有两个参数分别是int , float char * (*p6) (int , float) ; // *p6 是第一部分 , char * (int , float) 一个指向(返回指针的函数) 的指针 指针函数指针
注意:
以上的p1 --- p6 本质上完全没有区别,他们都是一个指针而已。
- 唯一的区别是他们所指向的类型不一样
- 主要分析出第一部分,剩下的都属于第二部分
void指针
概念: 无明确类型的指针变量,也称为通用类型指针。
注意:
- void 指针不可以直接用来索引目标(*), 必须先确定某一个具体的类型,才可以索引目标。
- void指针也不可以直接进行加减运算。
int a = 900 ; void * p = &a ; printf("*p:%d ",*p);//error:invalid use of void expression printf("*p:%d " , *(int*)p); //在索引指针时应该先进行类型的强制转换
void关键字修饰:
- 修饰指针, 表示该指针的类型是通用的(暂时是未知的)
- 修饰函数的参数列表 , int main ( void ) -->表示该函数不需要参数
- 修饰函数的返回值, void mian (int arg , char ** argv ) -->表示该函数不会有返回值
const指针
常指针,用于修饰指针本身, 表示指针不能修改。
char * const p = &a ;
常目标指针
用来修饰指针所指向的目标, 表示不可以通过该指针来改变目标的值。
const int * p1=&a;// 指针p1 所指向的内容受到保护,用户不可通过p1/p2 来修改目标的值 int const * p2=&a; printf("*p1:%d ",*p1); printf("*p2:%d " , *p2)
注意:
1.实际开发中常指针并不多见,常目标指针则是经常看见,用来限制指针的访问权限为只读。
函数指针
概念: 指向一个函数的指针,称为函数指针
特点: 函数指针本质上还是一个指针而已, 只不过他所指向的地址是一个函数的入口地址。引用的时候取址符和*都可以省略。
int (*p_max)(int a,int b)=max;//定义一个函数指针*p_max,并指向函数max int (*p_min)(int a,int b)=&min ;// 定义一个函数指针*p_min,并指向函数min int ret_val=p_max(1000,800); //通过指针 p_max来调用函数max 可以省略 * 解引用 ret_val =(*p_min)(1000,800); // 通过指针 p_min来调用函数min
注意:
1.函数指针是一种专门用来执行某种类型的函数的指针,要注意类型是否匹配;
2.函数的类型不同所用的函数指针也是不一样的;
3.函数的类型,与普通的变量类型判定是一样的, 除了声明语句中的标识符之后所剩的语句都可以省略。
指针函数
返回值为一个指针的函数。