zoukankan      html  css  js  c++  java
  • C语言之指针变量

    菜单导航

    1、指针变量

    2、指针和数组

    3、常量指针和指向常量的指针

    4、指针和字符串的关系

    5、数组越界造成的访问不属于自己的内存空间现象

    6、引用数据类型和基本数据类型,形参和实参

    7、字符串和字符数组

    8、字符串常用函数(长度、拷贝、追加、比较)

    一、指针变量

    /*
         1、定义
         普通变量: 数据类型 变量名称
         指针变量: 数据类型 * 变量名称;
         2、指针变量是什么类型,那么将来就只能保存什么类型变量的地址,
         例如 指针变量是int类型, 那么将来就只能保存int类型变量的地址
         3、* : 标示这是一个指针变量,代表访问指针变量指向的那一块存储空间
         4、指针变量只能存储地址
         */
        int num = 10;
        char c = 'a';
        float f = 12.f;
        double d = 22.5;
        
        printf("num地址:%p, c地址:%p, f地址:%p, d地址:%p 
    ", &num, &c, &f, &d);
        printf("num字节: %zu, c字节:%zu, f字节:%zu, d字节:%zu 
    ", sizeof(num), sizeof(c), sizeof(f), sizeof(d));
        
        int *pNum = #
        char *pC = &c;
        float *pF = &f;
        double *pD = &d;
        printf("pNum地址:%p, pC地址:%p, pF地址:%p, pD地址:%p 
    ", &pNum, &pC, &pF, &pD);
        printf("pNum字节: %zu, pC字节:%zu, pF字节:%zu, pD字节:%zu 
    ", sizeof(pNum), sizeof(pC), sizeof(pF), sizeof(pD));
        
        printf("改之前的值-->num: %d, c: %c, f: %f, d: %lf 
    ", num, c, f, d);
        *pNum = 111;
        *pC = 'c';
        *pF = 222.5f;
        *pD = 333;
        printf("改之后的值-->num: %d, c: %c, f: %f, d: %lf 
    ", num, c, f, d);
        
        /** 打印日志 //2, 1, 0, f, e, d, c, b, a, 9, 8, 7
         num地址:0x7fff5fbff758, c地址:0x7fff5fbff757, f地址:0x7fff5fbff750, d地址:0x7fff5fbff748
         num字节: 4, c字节:1, f字节:4, d字节:8
         pNum地址:0x7fff5fbff740, pC地址:0x7fff5fbff738, pF地址:0x7fff5fbff730, pD地址:0x7fff5fbff728
         pNum字节: 8, pC字节:8, pF字节:8, pD字节:8
         改之前的值-->num: 10, c: a, f: 12.000000, d: 22.500000
         改之后的值-->num: 111, c: c, f: 222.500000, d: 333.000000
         */

    指针变量的应用练习1:交换两个变量的值

    #include <stdio.h>
    
    /** 交换变量值:地址传递  */
    void swop2(int *v1,int *v2)
    {
        int temp = *v1;
        *v1 = *v2;
        *v2 = temp;
    }
    
    /** 交换变量值:值传递 */
    void swop(int v1,int v2)
    {
        int temp = v1;
        v1 = v2;
        v2 = temp;
        
    }
    
    int main()
    {
        int a = 20, b = 30; //定义两个int类型变量
        
        printf("交换前a: %i, b: %i 
    ", a, b);
        
        //要求交换变量a和b的值
        //第一种尝试:传入变量a和b的变量名,在函数内部交换两个变量的值
        swop(a, b);
        printf("。。。传入变量名调用函数交换变量值: 
    ");
        printf("交换后a: %d, b: %d 
    ", a, b);
        
        //第二种尝试: 传入变量a和b的地址,在函数内部交换两个变量的值
        swop2(&a, &b);
        printf("。。。传入变量地址调用函数交换变量值:
    ");
        printf("交换后a: %d, b: %d
    ", a, b);
        
        /** 打印结果:
         交换前a: 20, b: 30
         。。。传入变量名调用函数交换变量值:
         交换后a: 20, b: 30
         。。。传入变量地址调用函数交换变量值:
         交换后a: 30, b: 20
         */
        
        printf("
    ");
        return 0;
    }

    指针变量的应用练习2:一个函数传入3个变量,求返回三个变量的和和平均值

    #include <stdio.h>
    
    /** 传入3个int变量,求3个变量之和 与 平均值 */
    int getSum(int v1, int v2, int v3, int *ave){
        int sum = v1 + v2 + v3;
        *ave = sum/3;
        return sum;
    }
    
    int main()
    {
        int v1 = 18, v2 = 998, v3 = 188;
        int ave;  //存储平均值
        int sum = getSum(v1, v2, v3, &ave);
        printf("v1: %d, v2: %d, v3: %d, 和sum: %d, 平均值ave: %d 
    ", v1, v2, v3, sum ,ave);
        //打印结果: v1: 18, v2: 998, v3: 188, 和sum: 1204, 平均值ave: 401
        
        printf("
    ");
        return 0;
    }

    指针变量注意点:

    //1、指针只能保存地址
        int num = 10;
        
        //int *p = num;
        //printf("p: %i 
    ", *p); //这样运行会挂
        
        
        //2、同一个变量可以有多个指针指向它
        int *p = &num;
        int *p2 = &num;
        printf("num: %d, *p: %i, *p2: %d 
    ", num, *p, *p2);
        *p2 = 88;
        printf("改后num: %d, *p: %i, *p2: %d 
    ", num, *p, *p2);
        /** 打印日志:
         num: 10, *p: 10, *p2: 10
         改后num: 88, *p: 88, *p2: 88
         */
        
        
        //3、指针的指向可以修改
        int num2 = 18;
        p2 = &num2;
        *p2 = 99;
        printf("num: %d, *p: %d, *p2: %d, num2: %d 
    ", num, *p, *p2, num2);
        //打印日志:num: 88, *p: 88, *p2: 99, num2: 99
        
        
        //4、不要访问野指针:野指针值没有赋值的指针
        int *p3 = NULL;
        //*p3 = 12; //这样运行会挂
    // 5、指针变量和变量类型要一致,即int类型指针只能存储int类型的变量的地址
    //
    因为当我们利用指针去取值的时候,系统会自动根据指针的类型来确定应该取对少个字节的值. int intValue = 518; char *p = &intValue; printf("*p: %i ", *p); /** 打印日志:*p: 6 为什么? intValue = 518 的二进制为: 0000 0000 0000 0000 0000 0010 0000 0110 指针变量p是char类型,只会存储char类型的地址,即一个字节的地址, 所以指针变量p只存储了intValue的最小字节地址,即0000 0110这个字节的地址,转成十进制为:6 */

    多级指针测试:

    //一级指针
        char ch = 'a';
        char *p = &ch;
        
        *p = 'b';
        printf("c: %c, *p: %c 
    ", ch, *p);
        //打印日志:c: b, *p: b
        
        //二级指针
        char **p2 = &p; //指向指针的指针,也占用8个字节
        **p2 = 'd';
        printf("ch: %c, *p: %c, **p2: %c 
    ", ch, *p, **p2);
        //打印日志:ch: d, *p: d, **p2: d
        
        //三级指针
        char ***p3 = &p2;
        ***p3 = 'f';
        printf("ch: %c, *p: %c, **p2: %c, ***p3: %c 
    ", ch, *p, **p2, ***p3);
        //打印日志:ch: f, *p: f, **p2: f, ***p3: f
        printf("地址--》ch: %p, p: %p, p2: %p, p3: %p 
    ", p, p2, p3, &p3);
        //打印日志:地址--》ch: 0x7fff5fbff76b, p: 0x7fff5fbff760, p2: 0x7fff5fbff758, p3: 0x7fff5fbff750

    二、指针和数组

    //指针和数组
        int arr[] = {88, 18, 58};
        int *pi = arr;
        //arr是数组名,也是数组地址,也是数组中第一个元素的地址
        printf("arr: %p, pi: %p, arr[0]: %p 
    ", arr, pi, &arr[0]);
        //打印日志:arr: 0x7fff5fbff760, pi: 0x7fff5fbff760, arr[0]: 0x7fff5fbff760
        
        //pi + 1 : 表示地址+1,本质是地址+该数据类型的字节个数; pi相当于数组名
        printf("pi+1: %p, arr+1: %p 
    ", pi+1, arr+1);
        printf("*(pi+1): %i, *(arr+1): %i, pi[1]: %i, arr[1]: %i 
    ", *(pi+1), *(arr+1), pi[1], arr[1]);
        /** 打印日志:
         pi+1: 0x7fff5fbff764, arr+1: 0x7fff5fbff764
         *(pi+1): 18, *(arr+1): 18, pi[1]: 18, arr[1]: 18
         */
        
        
        //字符指针和字符数组
        char cArr[] = {'a', 'B', 'c', 'D', ''};
        char *pc = cArr;
        printf("cArr: %p, pc: %p, cArr[0]: %p 
    ", cArr, pc, &cArr[0]);
        printf("pc+1: %p, cArr+1: %p 
    ", pc+1, cArr+1);
        printf("*(pc+1): %c, *(cArr+1): %c, pc[1]: %c, cArr[1]: %c 
    ", *(pc+1), *(cArr+1), pc[1], cArr[1]);
        *(pc+1) = 'Z';
        printf("*(pc+1): %c, *(cArr+1): %c, pc[1]: %c, cArr[1]: %c 
    ", *(pc+1), *(cArr+1), pc[1], cArr[1]);
        /** 打印日志:
         cArr: 0x7fff5fbff743, pc: 0x7fff5fbff743, cArr[0]: 0x7fff5fbff743
         pc+1: 0x7fff5fbff744, cArr+1: 0x7fff5fbff744
         *(pc+1): B, *(cArr+1): B, pc[1]: B, cArr[1]: B
         *(pc+1): Z, *(cArr+1): Z, pc[1]: Z, cArr[1]: Z
         */

    三、常量指针和指向常量的指针

    先来看几个变量,各自说说他们的区别和时什么类型的变量:

    const  int   a1;   //常量a1
    int  const  a2;    //常量a2, 这种写法和常量a1的写法是等效的关系
    
    const  int  *a3;     //指向整型常量的指针: 所指向的目标为常量(不能间接修改指向变量的值,可以从新赋值新地址)
    int  *  const   a4;  //a4和a3 不同,a4是常量指针,指向整型的常量指针,即指针是常量
    //(可以间接修改指向变量的值,但是不可重新赋值新变量地址)
    const int * const a5; //a5是指向整型常量的常量指针(既不能间接修改变量的值,也不可重新赋值新变量地址)

    总结:1、const关键字写在数据类型的前面和后面是等效关系;

      2、指向常量的指针:不能间接修改所指向变量的值,但是可以给指针重新赋值新地址

      3、常量指针:可以间接修改指向变量的值,但是不能重新赋值新变量地址

      4、指向常量的常量指针:有2个const修改,既不能间接修改变量的值,也不可重新赋值新变量地址

    验证示例代码:

    //1、指针变量,指针变量可间接修改值,指针变量也可重新赋值新变量地址
        int a = 50, a2 = 22;
        int *ap = &a;
        *ap = 12;
        
        printf("测试1:a=%d, a地址:%x, *ap=%d, ap地址:%x, ap保存的地址:%x 
    ", a, &a, *ap, &ap, ap);
        printf("变量a占用字节个数:%lu, 变量ap占用字节个数:%lu 
    ", sizeof(a), sizeof(ap));
        //测试1:a=12, a地址:5fbff65c, *ap=12, ap地址:5fbff650, ap保存的地址:5fbff65c
        //变量a占用字节个数:4, 变量ap占用字节个数:8
        //验证一个问题:a占用的字节地址为5fbff65f-5fbff65c, ap占用的字节地址为:5fbff657-5fbff650
        *ap = 13;
        printf("测试2:a=%d, a地址:%x, *ap=%d, ap地址:%x, ap保存的地址:%x 
    ", a, &a, *ap, &ap, ap);
        //测试2:a=13, a地址:5fbff65c, *ap=13, ap地址:5fbff650, ap保存的地址:5fbff65c
        
        ap = &a2;
        printf("*ap=%d, a2=%d, &a2=%x, ap=%x 
    ", *ap, a2, &a2, ap);
        //*ap=22, a2=22, &a2=5fbff658, ap=5fbff658  //说明指针变量可以重新指向其他变量
        
        
        //2、测试指向整型常量的指针
        int c = 19, c2 = 29;
        int const *cp = &c;  //指向整型常量的指针
        printf("c1: c=%d, *cp=%d, &c=%x, cp=%x 
    ", c, *cp, &c, cp);
        //c1: c=19, *cp=19, &c=5fbff63c, cp=5fbff63c
    //    *cp = 20; //报错:Read-only variable is not assignable
        //指向整形常量的指针,不能间接修改变量的值,因为指向整型常量
        c = 20;  //但是原变量自己可以直接修改自己的值
        printf("c2: c=%d, *cp=%d, &c=%x, cp=%x 
    ", c, *cp, &c, cp);
        //c2: c=20, *cp=20, &c=5fbff63c, cp=5fbff63c
        cp = &c2;
        printf("c3: c2=%d, *cp=%d, &c2 = %x, cp=%x 
    ", c2, *cp, &c2, cp);
        //c3: c2=29, *cp=29, &c2 = 5fbff638, cp=5fbff638
        
        
        //3、测试指向整型的常量指针
        int d = 31, d2 = 32;
        int* const dp = &d;
        printf("d1: d=%d, *dp=%d, &d=%x, dp=%x 
    ", d, *dp, &d, dp);
        //d1: d=31, *dp=31, &d=5fbff63c, dp=5fbff63c
        *dp = 3;
        printf("d2: d=%d, *dp=%d, &d=%x, dp=%x 
    ", d, *dp, &d, dp);
        //d2: d=3, *dp=3, &d=5fbff63c, dp=5fbff63c
        
    //    dp = &d2; //报错:Cannot assign to variable 'dp' with const-qualified type 'int *const'
        //指向整型的常量指针,不能再重新赋值其他变量地址。但是可以间接修改当前指向的变量的值
        
        
        //4、测试指向整型常量的常量指针:既不能间接修改变量的值,也不能重新赋值新的变量地址
        int e = 41, e2 = 42;
        int const * const ep = &e;
    //    *ep = 48; //报错:Read-only vaiable is not assignable
    //    ep = &e2;//报错:Cannot assign to variable 'ep' with const-qualified type 'int *const'
        
        
        //5、指向常量的指针变量
        int const b = 50; //
        //    b = 30;  //编译报错:Cannot assign to variable 'b' with const-qualified type 'const int'
        int *bp = &b;
        printf("b=%d, b地址: %x, bp保存的地址:%x, *bp: %d 
    ", b, &b, bp, *bp);
        //b=50, b地址: 5fbff64c, bp保存的地址:5fbff64c, *bp: 50
        
        *bp = 88;
        printf("修改后b=%d, b地址: %x, bp保存的地址:%x, *bp: %d 
    ", b, &b, bp, *bp);
        //修改后b=50, b地址: 5fbff64c, bp保存的地址:5fbff64c, *bp: 88
        /*
         这个地方有点奇怪,b是常量,指针变量bp指向b, 间接通过指针bp修改变量的值,
         但是最后打印结果是:*bp的值变了,b的值没有变(b是常量,指针变量bp指向b), 而且bp保存的地址和b的地址还是保持一样
         这究竟是为啥?怎么理解 ? 
         变量被const修饰时,就复制了其值出来放到常量表中(由系统维护)
         但是每次取常量时,它是从常量表找到以前的值,而不是再次读内存。
         而指针变量bp可以修改指向地址里面的值。
         不知这样理解是否正确?
         */
    View Code

      

    四、指针和字符串的关系,变量内存栈区和常量区区别

    测试字符数组,指向字符数组的指针,和指向字符串的指针的区别;

    字符数组和指针存储的字符串在内存中的存储位置:即栈区存储和常量区存储的区别

     //1、字符数组
        char cs[] = "test";
        printf("%s, 第一个字符:%c, 地址:%p, %p, %p
    ", cs, cs[0], cs, &cs[1], &cs[2]);
        cs[0] = 'A';
        printf("修改后的字符数组:%s, 第一个字符:%c, 地址:%p, %p, %p
    ", cs, cs[0], cs, &cs[1], &cs[2]);
        /*
         test, 第一个字符:t, 地址:0x7fff5fbff63b, 0x7fff5fbff63c, 0x7fff5fbff63d
         修改后的字符数组:Aest, 第一个字符:A, 地址:0x7fff5fbff63b, 0x7fff5fbff63c, 0x7fff5fbff63d
         */
        
        //2、指向字符串的指针
        char *cp = cs;
    //    printf("%s", *cp);  //这样写会挂掉,因为*cp取出的数组中第一个字符
        printf("%s, %c, %c, %c, %c
    ", cp, *cp, *(cp+0), *(cp+1), *(cp+2));
        printf("%p, %p, %p
    ", cp, cp+1, cp+2);
        /*
         Aest, A, A, e, s
         0x7fff5fbff63b, 0x7fff5fbff63c, 0x7fff5fbff63d
         
         解释: cp表示数组名,取出字符数组所有字符;*cp == *(cp+0) 表示取出数组中第一个字符的值,后面以此类推;
         cp表示字符数组中第一个字符的地址, cp+1表示第二个字符的地址, 后面以此类推
         cp格式化时用%s表示取出整个字符数组的所有字符,用%p表示取出字符数组的最低位地址(即第一个字符的地址)
         */
        //修改字符数组的值
        *cp = 'B';
        *(cp+1) = 'C';
        *(cp + 2) = 'd';
        printf("通过指针修改字符数组的值:%s, %c
    ", cp, *(cp+1));
        
        
        //直接字符指针指向常量字符串
        char *cp2 = "daxia";
        printf("cp2: %s, %p, %c, %p, %c, %p
    ", cp2, cp2, *(cp2+1), cp2+1, *(cp2+2), cp2+2);
        //cp2: daxia, 0x100001aab, a, 0x100001aac, x, 0x100001aad
    //    *(cp2 + 1) = 'G';  //野指针错误:因为常量字符串不允许修改
        
        
        //测试字符数组的存储区和常量字符串的存储地方
        char cs1[] = "hello";
        char cs2[] = "hello";
        char *p1 = "hello";
        char *p2 = "hello";
        printf("cs1: %p, cs2: %p, p1: %p, p2: %p
    ", cs1, cs2, p1, p2);
        //cs1: 0x7fff5fbff61a, cs2: 0x7fff5fbff614, p1: 0x100001a9e, p2: 0x100001a9e
        //为什么?说明:字符数组存在内存中的栈区,常量字符串存在内存中的常量区
        /*
         如果通过数组来保存字符串,那么字符串变量可以修改,存在内存栈中,当作用域结束时自动释放该变量;
         如果通过指针来保存字符串,那么字符串是一个常量不能修改,保存在内存常量区,不会被释放,多个相同的值对应的地址相同;
         */
    View Code

    五、数组越界造成的访问不属于自己的内存空间

    六、引用数据类型和基本数据类型,形参和实参

    七、字符串和字符数组

     

    八、字符串常用函数(长度、拷贝、追加、比较)

     

    demo地址:https://github.com/xiaotanit/Tan_TestIOS/tree/master/Tan_LocationMove

  • 相关阅读:
    目前流行前端几大UI框架排行榜
    vue nginx配置
    快速切换npm源
    vue项目打包部署生产环境
    VScoed Vue settings.json配置
    java获取远程图片分辨率
    Fegin的使用总结
    线程池核心参数
    mysqldump定时任务生成备份文件内容为空解决方法
    对汉字编码
  • 原文地址:https://www.cnblogs.com/tandaxia/p/4312953.html
Copyright © 2011-2022 走看看