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

    指针函数

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

  • 相关阅读:
    二分练习题4 查找最接近的元素 题解
    二分练习题5 二分法求函数的零点 题解
    二分练习题3 查找小于x的最大元素 题解
    二分练习题2 查找大于等于x的最小元素 题解
    二分练习题1 查找元素 题解
    code forces 1176 D. Recover it!
    code forces 1173 B. Nauuo and Chess
    code forces 1173 C. Nauuo and Cards
    吴恩达深度学习课程笔记-15
    吴恩达深度学习课程笔记-14
  • 原文地址:https://www.cnblogs.com/ding-ding-light/p/14083765.html
Copyright © 2011-2022 走看看