指针

什么是指针:

        1、指针是一种数据类型,使用它可以定义指针变量,简短指针。

        2、指针变量中存储的是代表便内存地址的整数,每个整数只对应一个字节。

        3、当通过指针去访问内存时,具体访问多个字节由它的类型决定,也就说指针变量中存储的是一块内存的首地址。

为什么使用指针:

        1、跨函数共享变量

            全局变量在函数之间共享的问题有:浪费内存、命名冲突、有被破坏的风险,尽量少用。

            由于函数只能返回一个数据,而使用指针可以从被调用返回多个数据。

        2、提高函数的传参效率

            由于C语言的函数传参是单向值传递,就是实参给形参赋值(把实参的内存拷贝给形参),所以在传参时参数的字节数越多耗时越长,如果只传递变量的地址,那么只需要拷贝4/8字节,字节数越多的变量越值得使用指针传递。

            注意:这样做作的缺点是变量有被函数破坏的风险,可以使用const加以保护。

        3、配合堆内存使用

            当我们定义一个变量时,系统就会为变量分配一块内存,它变量名建议联系,之后再使用变量名时,就相当于访问对应的内存。

            使用向系统申请堆内存时,系统只会返回内存块的首地址,无法用一个变量名与内存建立联系(堆内存无法取名字),所以使用堆内存必须与指针配合。

如何使用指针:

        定义指针变量:

            类型* 指针变量名p;

            1、指针变量与普通变量的用法不同,一般指针变量以p结尾,加以区分。

            2、指针变量不能连续定义,一个*只能定义出一个指针变量。

                int* p1,p2; // p1是指针变量,p2是int类型变量

                int *p1,*p2;  // p1、p2都是指针变量

                typedef int* intp; // 给int*取一个类型名

                intp p1,p2; // p1、p2都是指针变量

            3、指针变量的默认值与普通变量一样是随机的,为了安全一般要初始化,如果不知道该赋值什么,建议初始化为NULL,也就是空指针,如果不初始化这就是野指针。

            4、指针的类型决定的指针解引用时访问内存的字节数,还决定了指针变量的进步值。

        给指针变量赋值:

            可以定义指针变量时初始化,也可以指针变量定义完成后再赋值。

            // 获取变量的地址赋值给指针变量

            int* p = #

            // 向系统申请内存,把获取到的内存块首地址赋值给指针变量

            int* p = malloc;

        解引用:*指针变量;

            int *p = #

            *p <=> num; // *p就等价于num

            根据指针变量中存储的内存地址去访问对应的内存块,具体访问多少个字节,由指针变量的类型决定。

            如果之前赋值的是非法的内存地址,此时会出现段错误。

产生段错误的原因:

            1、对非法的内存地址解引用,指针变量中存储的内存地址不在maps文件范围内。

            2、指针变量存储的是text内存段的地址,并通过解引用去修改内存的内容,也会产生段错误。

使用指针要注意的问题:

        空指针:指针变量的值是NULL,操作系统规则该地址不能解引用,如果函数的返回值是指针类型,当返回NULL是表示函数执行出错,所以NULL也是一种错误标志。

如何避免空指针产生的段错误:

            准则:对来历不明的指针解引用前要进行判断。

            1、函数的参数

            2、函数的返回值

            3、全局的指针变量

判断空指针的方法:

        if(!p) // 错误写法

        {

            1、不是所有系统的NULL都是0

            2、可能让人误指针变量是布尔类型的变量

        }

        野指针:指向的内存区域不明,不知道是不是合法的内存地址,这种指针被称为野指针。

        对野指针解引用的后果:

            1、一切正常

            2、脏数据

            3、段错误

        注意:指针产生的错误具有随机性、潜伏性、隐藏性,出现的错误很难重现,因此它比空指针的危害更大,并且无法判断是否是野指针。

如何避免野指针产生的危害:

            准则:所有的野指针都是程序员制造出来的,不制造野指针就能避免使用野指针。

            1、定义指针变量时要初始化,即使初始化为空指针也比野指针要好。

            2、函数不反回局部变量的地址,因为随着函数的执行结束,属于局部变量的内存就会被释放,当执行其它函数时这块内存会被再次分配出去,因此返回局部变量的地址容易造成脏数据。

            3、当堆内存被释放后,配合堆内存使用的指针要即时赋值为空。

指针的运算:

        指针变量中存储的是内存编号,其它本质就是整数,理论上整型数据能使用运算符指针变量都可以使用,但只有以下运算才有意义:

        指针+整数 指针代码内存编号的整数+整数*进步值

        指针-整数 指针代码内存编号的整数-整数*进步值

        指针加减一个整数,就相当于指针变量以进步值为单位前后移动。

        指针-指针 (指针代码内存编号的整数-指针代码内存编号的整数)/进步值

        指针减指针运算可以计算出两个地址之间相隔多少个元素,并且只有类型相同的指针变量才能相减,否则就会有编译错误。

通用指针:

        指的是void类型的指针变量,它能与任意类型的指针变量进行类型转换,也被称为万能指针。

        如果一个函数的参数可能是任意类型的指针变量,可以用void类型指针兼容所有类型的实参指针。

        void类型的指针进步值是1,它不能直接解引用,必须先转换成其它类型指针再解引用。

指针与数组名:

    数组名其实就是个常量地址,类型 数组名[n],那么数组名就是 类型* 的地址。

    当我们使用数组作为函数的参数时,函数的形参就是指针变量,所以它的长度信息才丢失。

    arr[n] <=> *(arr+n) 这两种语法就是等价的,如果指针变量指向一块连续内存,指针变量可以当数组使用。

    指针与数组名的相同点:

        1、它们都是地址,代表一块连接的内存。

        3、它们都可以使用*,[]进行解引用,遍历一块连续内存。

指针与数组名的不同点:

        1、指针是变量而数组名是常量

        2、指针变量有自己的存储空间用于存储内存地址,而数组名没有,它就是地址。

        3、指针变量与目标内存是指向关系,而数组名与目标内存是映射关系。

指针数组:

    就是由指针组成的数组,它的身份是数组,成员是指针。

    类型* 数组名[数组长度];

    使用它能构造出不规则的二维数组:

    int arr1[7] = {7,1,2,3,4,5,6};

    int arr2[8] = {8,1,2,3,4,5,6,7};

    int arr3[5] = {5,1,2,3,4};

    int arr4[9] = {9,1,2,3,4,5,6,7,8};

    int arr5[3] = {3,1,2};

    int* arr[5] = {arr1,arr2,arr3,arr4,arr5};

    for(int i=0; i<5; i++)

    {

        for(int j=1; j<arr[i][0]; j++)

        {

            printf("%d ",arr[i][j]);

        }

        printf("\n");

    }

数组指针:

    专门指向数组的指针,它的进步值是整个数组的长度。

    类型 (*指针变量名) [长度]

    使用它可以把一块连接的内存当作二维数组使用:

    int arr[20];

    int (*p)[5] = (void*)arr;

    for(int i=0; i<4; i++)

    {

        for(int j=0; j<5; j++)

        {

            printf("%d ",p[i][j]);

        }

        printf("\n");

    }

    int arr[7];

    int* p <=> arr; // 一维数组的数组名是普通指针

    int (*p)[7] <=> &arr;

    arr[i] <=> *(arr+i);

    int arr1[3][4];

    arr1[i][j] <=> *(*(arr1+i)+j)

    int (*p)[4] <=> arr1; // 二维数组的数组名是数组指针

    int* p <=> *arr1;

指针函数:

    返回值是指针类型的函数就叫作指针函数,当返回值是NULL/0xffffffff,表示函数执行出错。

函数指针:

    前提:函数就一段代码,这段代码会被翻译成二进制指令存储在代码段中,函数名就是个地址,表示函数的二进制指令在代码段的位置。

    什么是函数指针:就是专门指针函数的指针变量,使用函数指针可以把函数像数据一样在函数之间进行传递。

    如何定义函数指针:

        1、照抄函数声明。

        2、用小括号包含函数名

        3、在函数名的前面加*,并在函数名末尾添加_fp。

    使用函数:

        1、用函数名给函数指针赋值。

        2、函数指针(实参) 就可以调用函数了。

    函数指针的作用大多数情况下用于实现回调模式,先实现的代码调用后实现的函数,例如:

        void qsort(void *base, size_t nmemb, size_t size,

                int (*compar)(const void *, const void *));

        该函数是早期标准库程序实现的,它无法知道如何判定元素的大小,所以就留一个函数指针的参数,让我们提供一个自己实现的对数组元素进行比较函数。

    练习4、参考qsort函数,实现一个通用的选择排序函数。

        void select_sort(void *base, size_t nmemb, size_t size,

                int (*compar)(const void *, const void *))

        {

            char tmp[size];

            for(int i=0; i<nmemb-1; i++)

            {

                int min = i;

                for(int j=i+1; j<nmemb; j++)

                {

                    if(compar(base+min*size,base+j*size))

                        min = j;    

                }

                if(min != i)

                {

                    memcpy(tmp,base+i*size,size);

                    memcpy(base+i*size,base+min*size,size);

                    memcpy(base+min*size,tmp,size);

                }

            }

        }

二级指针:

    专指向普通指针的指针变量,它里面存储的是指针变量的地址。

    普通指针变量有三项用处:

        1、跨函数共享变量

        2、提高函数的传参效率

        3、配合堆内存使用

    二级指针只有一个用处,就是跨函数共享指针变量。

    类型** 指针变量名pp = &普通指针变量;

    int num = 1234;

    int* p = &num;

    int** pp = &p;

    *pp <=> p;

    **pp <=> *p <=> num;

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,951评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,606评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,601评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,478评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,565评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,587评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,590评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,337评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,785评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,096评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,273评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,935评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,578评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,199评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,440评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,163评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,133评论 2 352

推荐阅读更多精彩内容

  • 什么是指针: 1、指针是一种数据类型,使用它可以定义指针变量,简称指针。 2、指针变量中存储的是整数,这种...
    owl_阅读 562评论 0 0
  • 什么是指针: 1、指针是一种数据类型,使用它可以定义指针变量,简称指针。 2、指针变量中存储的是内存地址(...
    CRAYON_阅读 231评论 0 0
  • 技术交流QQ群:1027579432,欢迎你的加入! 1.Cpp中的指针 每个变量都有一个内存位置,每一个内存位置...
    CurryCoder阅读 2,684评论 0 1
  • a[3][4]表示的是第4行第5列 a[i]表示的是第i=1个 从0开始,i表示个数 为了解决大量的同类型数据的存...
    cf35206e78ad阅读 283评论 0 0
  • 指针 指针是一个变量,其值为地址,声明或不再使用后都要将其置为0(类似java释放对象后置为NULL)。 野指针 ...
    面向星辰大海的程序员阅读 173评论 0 0