指针

内存地址

  字节: 是内存的单位byte, 一个字节8位 bits

  地址: 系统为了方便访问内存,而对他们以一个字节为单位来进行逐一编号,称为内存地址

 

基地址

    对于单字节的数据来说他的基地址就是他自己的地址本身。

 对于多字节的数据来说他的基地址是他地址中地址值最小的那个, 称为该数据的基地址。

取址符

   每个变量都有自己的地址,可以使用取址符来获得它们的地址 

int        a = 100 ;      printf("a:%p\n" ,  &a );   
char       c = 100 ;      printf("c:%p\n" ,  &c );      
double     d = 100 ;      printf("d:%p\n" ,  &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 的内存中取 

野指针

一个指向未知地址的指针称为野指针。野指针是有一定危险性的。

危害:

  1. 引用野指针,相当于访问了非法内存,一般会导致段错误。
  2. 引用野指针,有可能访问到系统关键性数据,会导致系统崩溃后果严重。

野指针的产生:

  • 指针定义后没有初始化,没有明确的指向。
  • 指针原本所指向的内存被释放、系统已经回收。
  • 指针越界。

如何防止:

  • 指针定义后马上初始化。
  • 绝对不使用已经释放的内存的指针,让指针指向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\n", arr[3]);
printf("p1: %p\n", p1);
printf("p1+1: %p\n", p1+1)//p1表示数组首元素的首地址,p1+1就是加一个int型地址的大小,为4字节
printf("p2: %p\n" ,p2);      
printf("p2+1: %p\n" , 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\n",&arr);//指整个二维数组的首地址     
printf("p:%p\n",p);//指整个二维数组的首地址    
printf("*p:%p\n",*p);//指二维数组的首元素的首地址     
printf("**p:%p\n",**p);//指二维数组的首元素的首元素的首地址    
printf("(*p)+1:%p\n",(*p)+1);//*p首元素的首地址+1加二维数组中第二维3*sizeof(int)的大小
printf("(**p)+1:%p\n",(**p)+1);
*((**p)+1)=1024 ; 
printf("arr[0][1]:%d\n",arr[0][1]);
int * p1=&(arr[0][0]);//p1指向了这个数组的首元素的首元素的首地址    
printf("p1:%p\n" , p1 ); 
*(p1 + 1) = 998 ; printf("arr[0][1]:%d\n" , 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\n",*p);//error:invalid use of void expression   
printf("*p:%d\n" , *(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\n",*p1);
printf("*p2:%d\n" , *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.函数的类型,与普通的变量类型判定是一样的, 除了声明语句中的标识符之后所剩的语句都可以省略。

指针函数

返回值为一个指针的函数。

 

posted @ 2020-12-04 01:10  ding-ding-light  阅读(109)  评论(0编辑  收藏  举报