《C语言内核深度解析》笔记(3):指针才是C语言的精髓

《C语言内核深度解析》笔记(3):指针才是C语言的精髓,第1张

第03章 指针才是C语言的精髓 3.2 指针 

int a = 10;
int *p = &a; 
指针变量p和普通变量之间没有本质区别,都是变量空间放了一个数值,只是p里面的数值比较特殊,是a空间的地址,它
指向了空间a。


变量空间的首字节地址,作为整个空间的地址: 实际上内存中的每个自己空间都有一个地址,如果内核由32位地址线,地址以二进制表示,既然
每个字节对应的地址都是32位,那么存放地址的变量大小也是32位的,即四个字节。


但只有首字节地址才能作为整个a空间的地址。


指针变量的类型作用:
    某类型一级指针变量 = 该类型一级地址
    该类型二级指针变量 = &(该类型一级指针变量)
    n+1级指针变量 = &(该类型的一级指针变量)

指针使用之三部曲
    定义(声明)
    int *p = NULL; //初始化一下,防止野指针
    关联
    int a = 10;
    p = &a; //a空间的首地址给了p,所以p里面的地址指向了a空间,因此简称p->空间
    引用
    读空间:读值 *** 作,前提是里面存有数据才行。



    int b = *p;     //等价于b = a
    写空间:向空间写入新的值
    *p = 30;        //等价于a = 30;

    3.3 理解指针符号


    * 用在指针定义:
    int *p1, *p2;       //p1和p2都是int型的一级指针变量
    int *p1, p2;        //p1是int型的一级指针变量,p2只是一个普通的int型变量
    * 解引用:
    *p 表示p所指向的空间,这时的*也称为取空间 *** 作。


    &:取地址符,表示变量空间的首地址

    左值: 指的是变量空间,对左值执行的 *** 作都是写空间 *** 作。



    右值: 右值有两种形态,一种是直接写一个数值,另一种就是右值也是一个变量。


3.4 野指针与段错误问题

    野指针:指针指向一个不确定的地址空间或者指向一个确定的地址空间但是引用空间的结果是不可确定的。



    野指针危害: 引发段错误; 未产生任何结果;引发程序连环式错误。



    产生原因: 没有正确初始化;访问权限不允许;内存越界
    避免:定义指针时复制为NULL; 使用前一定要对指针变量初始化或赋值;使用前判断指针是否为空;确定指针不用式,赋值为NULL

    #ifdef      _cplusplus   //如果这个符号定义了,表示当前运行的是C++环境,否则就是C环境
    #define     NULL    0       //在C++中NULL被定义为0
    #else       
    #define     NULL    (void*)0    //在c中NULL被定义为(void*)类型的0

    段错误产生的原因:段错误本质上就是指针错误(地址错误),因为C语言的内存结构式由不同的内存段组成的,所以称为段错误。



                    大段错误: 指针变量指向的地址空间根本不存在;
                    小段错误:  指针变量指向的地址空间存在,但是对该空间的 *** 作权限所受到了限制。


3.5 const关键字与指针


    const对于普通变量的修饰:const在类型前后前还是后都没关系,只要在变量名前面即可。



    const修饰指针的三种形式:  
        int const *p 等价于 const int *p  : 这种修饰表示p所指向的空间是“常量”,不能被修改,但是p本身可以被修改。



                                            int a = 10;
                                            int b = 20;
                                            int const *p = &a;  //p指向了a
                                            *p = 100;       //编译时会报错,因为p指向的空间不能被修改
                                            p = &b;         //正确,因为p本身是可以被修改的
        
        int *const p:  指针变量本身不能被修改,但是p所指向的内容可以被修改
        int const *const p:     p的指向不能发生改变,p所指向的内容也不能发生改变。


3.6 深入学习数组


    类型 数组名[数组元素个数]
    int  buf[100] = {0};

    一维数组中几个关键符号的理解:  
        buf:    两层含义,一是数组名,sizeof(buf)时,buf就是数组名的含义;二是等价于
        &buf[0],表示数组第一个元素的首字节地址,是一个常量值。



        buf[0]: 第一个元素的空间,可以对其进行读写 *** 作。



        &buf[0]:    等价于buf,是一个地址常量,只能作为右值
        &buf:       表示数组首地址,是一个地址常量,只能作为右值。


(数组首地址重要用于构建多维数组,加1加的是整个数组空间大小)

3.7 指针与数组的天生“孽缘”

    使用指针访问数组: 
        int buf[6] = {0, 1, 2, 3, 4, 5 };
        利用下标访问: buf[i];
        利用指针常量访问: *(buf + i); 注意,*(buf++)是错的,buf是常量不能赋值;
        指针常量访问: int *p=buf;  *(p+i);

    内存角度理解:数组中元素空间在内存中相连,地址连续,且元素类型相同,空间大小相同。



    类型匹配问题: int buf[5]; int *p = NULL; p=&buf;   这个做法错误,&buf是数组首地址,是int(*)[5]类型,不是in*型。


3.8 指针类型与强制类型转换

    数据的存入与读取:
        数据写入三步曲
        利用变量名或者直接利用地址,找到空间首地址。



        根据类型制定的空间大小,从首字节地址开始,找出向后顺序延长后的空间大小。



        按照类型数据存储格式要求,将数据写入空间。



    数据读出三步曲
        利用变量名或者直接利用地址,找到空间首地址。



        根据类型指定的空间大小,找出从首字节地址开始向后顺延后的空间大小。



        按照类型数据存储格式要求,将数据从空间读出。


    普通变量的强制转换:
        普通变量的数据类型转换,就是将数据的空间大小和数据的存储结构转变后,存入另一个空间,所以转换时回导致空间大小和数据存储结构的变化。


    指针变量数据类型的强制转换:
        对于指针来说,一律要求显式强制类型转换,不允许使用隐式类型转换。


设计两个方面,对指向空间的强制类型转换,对指针变量本身做强制类型转换。


        指向空间的强制类型转换:
            int a;
            float b = 136.23;
            int *pa=&a;
            float *pb = &b;
            *pa = (int)*pb;     //等价于a = (int)b;

        指针本身强制类型转换————改变的是对其引用空间的引用方式:
            int a;
            int *pa=&a;
            float *pb = NULL;
            pb = (float *)pa;     //或者pb = (float *)&a;
            pb里面的地址值与pa里面的地址值是相等的,但是pa放的是(int*),pb放的是(float *)型,虽然都指向一个空间a,但是*pb去使用变量空间a时,会以float型的空间大小和数据结构使用a空间的值。


3.9 指针、数组与sizeof运算符


    如果要求传递一个30个元素的整型数组的话
    int fun(int *p);
    int fun(int p[30]);
    这两种写法都没问题,但是第二种写法易读性高,虽然30在这里并没有意义,可有可无。


    一般传递数组时,传递的只是数组首元素地址,如果要 *** 作整个数组空间,需要将数组的元素个数传递过去:
    void fun(int n, int buf[n])
    {
        printf("%d\n", sizeof(buf));
    }
    int main(void)
    {
        int buf[100];

        fun(sizeof(buf),buf);
    }

    #define 只是简单宏替换,在预编译时被处理
    typedef 在编译时被处理

    区别一:
    #define dpchar char*
    typedef char* tpchar;
    dpchar p1,p2;       //只做简单的替换,等价于char* p1, p2;只有p1才是指针变量
    tpchar p1,p2;       //不是简单的替换,等价于char *p1, *p2; p1,p1和p2都是指针变量 

    区别二:#define 方式可实现类型组合,但是typedef不行
    #define dint int
    typedef int tInt;
    unsigned dInt p1,p2;    //正确,等价于unsigned int p1, p2
    unsigned tInt p1,p2;    //不可以

    区别三:
    typedef 可以组建新类型,但是#define不行
    typedef char[200] charBuf;
    charBuf buf; //等价于char buf[200],但是#define 不可以 (这个有讲)

3.10 指针与函数传参


    普通传参:
    c语言中值传递的本质就是,当调用被调函数时,被调用参数会在自己的函数栈中开辟相同类型的形参空间,并且将传递过来的值写入形参空间保存。


    传递地址:
    同样开辟空间,不过传递进来的是地址,因此 *** 作也是面向地址的。


    传递数组:
    数组没有普通值传递,只有地址传递,如果传递值的话,效率较低。


    传递结构体:
        成员值传递:传递结构体成员值,就是普通值传递
        成员地址传递:传递结构体成员的地址,其实就是地址传递
        传递整个结构体: 将整个结构体视为一个普通值进行传递,形参需要开辟同等大小的结构体空间,用于存放传递过来的结构体内容
        结构体的地址传递: 传递真个结构体变量的地址
 

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/607418.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-14
下一篇 2022-04-14

发表评论

登录后才能评论

评论列表(0条)

保存