zoukankan      html  css  js  c++  java
  • 指针

    内存地址

      字节: 是内存的单位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 的内存中取 

    野指针

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

    危害:

    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 ", 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.函数的类型,与普通的变量类型判定是一样的, 除了声明语句中的标识符之后所剩的语句都可以省略。

    指针函数

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

  • 相关阅读:
    The connection to adb is down, and a severe error has occured
    android 补间动画
    Android Geocoder(位置解析)
    Android服务之AIDL
    SQL语言学习-数据操纵语言
    SQL语言学习-数据定义语言
    asp.net MVC 验证注解
    django之用户表的继承
    django之ModelForm组件
    django之 基于queryset和双下划线的跨表查询
  • 原文地址:https://www.cnblogs.com/ding-ding-light/p/14083765.html
Copyright © 2011-2022 走看看