• 20 数组和指针


    1 数组的本质

    • 数组是一段连续的内存空间

    • 数组的空间大小为 sizeof(array_type) * array_size

    • 数组名可看作指向数组第一个元素的常量指针,但数组名绝不是指针

    • a + 1 的意义

      #include <stdio.h>
      
      int main()
      {
          int a[5] = {0};
          int* p = NULL;
          
          //看作是常量指针的数组名运算
          printf("a = 0x%X
      ", (unsigned int)(a));  // a = 0xBFF51B48
          printf("a + 1 = 0x%X
      ", (unsigned int)(a + 1));  // a + 1 = 0xBFF51B4c
          
          //指针运算
          printf("p = 0x%X
      ", (unsigned int)(p));  // p = 0x0
          printf("p + 1 = 0x%X
      ", (unsigned int)(p + 1));  // p + 1 = 0x4
          
          return 0;
      }
      

    2 指针的运算

    • 指针是一种特殊的变量,与整数的运算规则为:p + n; => (unsigned int)p + n * sizeof(*p);

    • 当指针 p 指向一个同类型的数组的元素时:p + 1 将指向当前元素的下一个元素;p - 1 将指向当前元素的上一个元素

    • 指针之间只支持减法运算,参与减法运算的指针类型必须相同:p1 - p2; => ((unsigned int)p1 - (unsigned int)p2) / sizeof(type);

    • 只有当两个指针指向同一个数组中的元素时,指针相减才有意义,其意义为指针所指元素的下标差;当两个指针指向的元素不在同一个数组中时,结果未定义

    3 指针的比较

    • 指针也可以进行关系运算(<,<=,>,>=)

    • 指针关系运算的前提是同时指向同一个数组中的元素

    • 任意两个指针之间的比较运算(==,!=)无限制

    • 参与比较运算的指针类型必须相同

    • 示例1

      #include <stdio.h>
      
      int main()
      {
          char s1[] = {'H', 'e', 'l', 'l', 'o'};
          int i = 0;
          char s2[] = {'W', 'o', 'r', 'l', 'd'};
          char* p0 = s1;
          char* p1 = &s1[3];
          char* p2 = s2;
          int* p = &i;
      	
          printf("%d
      ", p0 - p1);  //-3
          printf("%d
      ", p0 + p2);  //error
          printf("%d
      ", p0 - p2);  //error
          printf("%d
      ", p0 - p);   //error
          printf("%d
      ", p0 * p2);  //error
          printf("%d
      ", p0 / p2);  //error
      	
          return 0;
      }
      
    • 示例2

      #include <stdio.h>
      
      #define DIM(a) (sizeof(a) / sizeof(*a))
      
      int main()
      {
          char s[] = {'H', 'e', 'l', 'l', 'o'};
          char* pBegin = s;
          char* pEnd = s + DIM(s); // Key point
          char* p = NULL;
          
          printf("pBegin = %p
      ", pBegin);
          printf("pEnd = %p
      ", pEnd);
          
          printf("Size: %d
      ", pEnd - pBegin);
      	
          for(p=pBegin; p<pEnd; p++)
          {
              printf("%c", *p);
          }
          
          printf("
      ");
         
          return 0;
      }
      //输出结果
      pBegin = 0xbfac155f
      pEnd = 0xbfac1564
      size = 5
      Hello
      

    4 数组的访问方式

    • 数组名可以当作常量指针使用,那么指针是否也可以当作数组名来使用?

      • 可以!
    • 下标的形式访问数组中的元素

      int main()
      {
          int a[5] = {0};
          
          a[1] = 3;
          a[2] = 5;
          
          return 0;
      }
      
    • 指针的形式访问数组中的元素

      int main()
      {
          int a[5] = {0};
        
          //数组名看作常量指针
          *(a + 1) = 3;
          *(a + 2) = 5;
          
          return 0;
      }
      
    • 下标形式 VS 指针形式

      • 指针以固定增量在数组中移动时,效率高于下标形式
      • 指针增量为 1 且硬件具有硬件增量模型时,效率更高
      • 下标形式与指针形式的转换:a[n] <=> *(a + n) <=> *(n + a) <=> n[a]
      • 现代编译器的生成代码优化率已经大大提高,在固定增量时,下标形式的效率已经和指针形式相当;但从可读性和代码维护的角度来看,下标形式更优
    • 数组的访问方式

      #include <stdio.h>
      
      int main()
      {
          int a[5] = {0};
          int* p = a;
          int i = 0;
          
          for(i=0; i<5; i++){
              //将指针当作数组名使用
              p[i] = i + 1;
          }
          
          for(i=0; i<5; i++){
              printf("a[%d] = %d
      ", i, *(a + i));
          }
          
          printf("
      ");
          
          for(i=0; i<5; i++){
              //等价代换公式
              i[a] = i + 10;
          }
          
          for(i=0; i<5; i++){
              printf("p[%d] = %d
      ", i, p[i]);
          }
          
          return 0;
      }
      //输出结果
      a[0] = 1
      a[1] = 2
      a[2] = 3
      a[3] = 4
      a[4] = 5
      
      p[0] = 10
      p[1] = 11
      p[2] = 12
      p[3] = 13
      p[4] = 14
      
    • 数组和指针不同

      //ext.c
      int a[] = {1, 2, 3, 4, 5};
      
      //test.c
      #include <stdio.h>
      
      int main()
      {
          extern int a[];
          
          printf("&a = %p
      ", &a);//数组地址
          printf("a = %p
      ", a);//数组第一个元素的地址
          printf("*a = %d
      ", *a);//取“数组第一个元素的地址”上的值
      
          
          return 0;
      }
      
      • 运行结果

        &a = 0x804a014
        a = 0x804a014
        *a = 1
        
      • 修改:验证数组名与指针是否一样。运行结果分析:

        • ext.c 中定义了一个数组,在内存为:1000 2000 ... 5000(Linux为小端系统) ,一共 20 个字节
        • 该数组在内存中的地址为:0x804a014 ,也就是说在编译后,标识符 a 的意义为一个地址:0x804a014
        • 在编译 test.c 时,当编译到 extern int* a; 时,发现标识符 a 在别处定义
        • 当编译到 printf("&a = %p ", &a); 时,打印标识符 a 的地址,即为:0x804a014
        • 当编译到 printf("a = %p ", a); 时,打印的是一个指针变量 a ,其值保存的是一个地址,那么取 4 个字节的值即为:0x1
        • 当编译到 printf("*a = %d ", *a); 时,*a 是到地址 0x1 取值,而此地址为操作系统所使用,产生段错误
        //test.c
        #include <stdio.h>
        
        int main()
        {
            //数组名改为指针
            extern int* a;
            
            printf("&a = %p
        ", &a);
            printf("a = %p
        ", a);
            printf("*a = %d
        ", *a);
        
            
            return 0;
        }
        //运行结果
        &a = 0x804a014
        a = 0x1
        段错误
        

    4 a 和 &a 的区别

    • a 为数组首元素的地址

    • &a 为整个数组的地址

    • a 和 &a 的区别在于指针运算

      • a + 1 => (unsigned int)a + sizeof(*a) =>(unsigned int)a + sizeof(*a[0])
      • &a + 1 => (unsigned int)(&a) + sizeof(*&a) => (unsigned int)(&a) + sizeof(a)
      • 二者加 1 增加的步长不一样
    • 指针运算示例

      #include <stdio.h>
      
      int main()
      {
          int a[5] = {1, 2, 3, 4, 5};
          printf("a = %p
      ",a);  // a = 0xbfa71990
          int* p1 = (int*)(&a + 1);  // p1指向数组a最后一个元素5后的下一个位置
          int* p2 = (int*)((int)a + 1);  // 整数加1是数学运算,结果为整数,p2指向一个地址:0xbfa71990 + 1 = 0xbfa71991,=> p2 = 0x0200 0000 => 十进制数:33554432
          int* p3 = (int*)(a + 1);  //p3指向第2个元素
          
          printf("%d, %d, %d
      ", p1[-1], p2[0], p3[1]);  // 5,33554432,3
          
          return 0;
      }
      

    5 数组参数

    • 数组作为函数参数时,编译器将其编译成对应的指针

      void f(int a[]); <=> void f(int* a);

      void f(int a[5]); <=> void f(int* a);

    • 一般情况下,当定义的函数中有数组参数时,需要定义另一个参数来标示数组的大小

      #include <stdio.h>
      
      void func1(char a[5])
      {
          printf("In func1: sizeof(a) = %d
      ", sizeof(a));
          
          *a = 'a';
          //如果a是数组名的话会报错:数组名不可以赋值
          a = NULL;
      }
      
      void func2(char b[])
      {
          printf("In func2: sizeof(b) = %d
      ", sizeof(b));
          
          *b = 'b';
          
          b = NULL;
      }
      
      int main()
      {
          char array[10] = {0};
          
          func1(array);
          
          printf("array[0] = %c
      ", array[0]);
          
          func2(array);
          
          printf("array[0] = %c
      ", array[0]);
          
          return 0;
      }
      
      // 运行结果
      In func2: sizeof(a) = 4
      array[0] = a
      In func2: sizeof(b) = 4
      array[0] = b
      
  • 相关阅读:
    剑指 Offer 22. 链表中倒数第k个节点
    1221. 分割平衡字符串
    1528. 重新排列字符串
    1450. 在既定时间做作业的学生人数
    1266. 访问所有点的最小时间
    1572. 矩阵对角线元素的和
    使用Python进行数据传输
    分布式系统的原理与设计
    分布式缓存
    Centos6 配置最新yum源
  • 原文地址:https://www.cnblogs.com/bky-hbq/p/13646544.html
Copyright © 2020-2023  润新知