指针

指针

指针

指针和内存单元

  • 指针
    • 地址
  • 内存单元
    • 计算机中最小的存储单元,大小为一个字节
    • 每个内存单元都有一个唯一编号
  • 指针变量
    • 存地址的变量
    • 指针是一种数据类型,占用内存空间,用来保存内存地址

指针的定义和使用

  • 指针的写法
    // 以下写法都对
    int* p;  // Windows 写法
    int *p;  // Linux 写法
    int * p;  // 也可以
    int a, *p, *q, b;
    
  • 指针的解引用 (间接引用)

    将 p 变量的内容取出,当成地址看待,找到该地址对应的内存空间。

    *p = 250;
    
    • *p 如果做左值,存数据到空间中
    • *p 如果做右值,取出空间中的内容
  • 任意指针类型大小

    指针大小与类型无关,只与当前使用的平台架构有关:

    • 32 位:4 字节
    • 64 位:8 字节
  • 示例
    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #include 
    
    int main(void)
    {
        int a = 10;
        int *p = &a;
        *p = 30;
    
        printf("a = %d\n", a);
        printf("p = %p\n", p);
        printf("*p = %d\n", *p);
    
        printf("int * = %u\n", sizeof(int*));
        printf("short * = %u\n", sizeof(short*));
        printf("char * = %u\n", sizeof(char*));
        printf("long * = %u\n", sizeof(long*));
        printf("double * = %u\n", sizeof(double*));
    
        system("pause");
        return 0;
    }
    
    /*
    a = 30
    p = 008FFAB8
    * p = 30
    int* = 4
    short* = 4
    char* = 4
    long* = 4
    double* = 4
    */
    
    • *p 类似于链接,记录的是 a 的地址,a 值变了, *p 也就变了
    • 打印地址使用 %p

空指针和野指针

  • 空指针

    空指针不指向任何东西,常用来判断存储空间是否为空。

    int *p = NULL;
    

    Note: *p 是 p 所对应的存储空间,一定是一个无效的访问区域。

    • 在解引用之前,要确保指针非 NULL

    • 不能向 NULL 或非法内存拷贝数据

    • 示例

      #define _CRT_SECURE_NO_WARNINGS
      #include 
      
      int main(void)
      {
          int* p = NULL;
          if (p != NULL)
          {
              printf("*p = %d\n", *p);
          }
      
          system("pause");
          return 0;
      }
      
  • 野指针

    野指针指向一个已删除的对象或未申请访问受限内存区域的指针。

    • 出现野指针的情况
      #define _CRT_SECURE_NO_WARNINGS
      #include 
      #include 
      
      void test0301()
      {
          // 指针变量未初始化
          //int* p;
          //printf("%d\n", *p);  // error C4700: 使用了未初始化的局部变量“p”
      
          // 指针释放后未置 NULL
          char* p = malloc(100);
          free(p);
      }
      
      char *test0302()
      {
          int a = 10;
          int *p = &a;
          *p = 20;
          return p;
      }
      
      char test0303()
      {
          int a = 10;
          int* p = &a;
          *p = 20;
          return *p;  // 指针操作超越了变量的作用域
      }
      
      int main(void)
      {
          //test0301();
          printf("%d\n", *test0302());
          printf("%d\n", test0303());  // 20
      
          system("pause");
          return 0;
      }
      
      • 指针变量未初始化
        • 未初始化的指针变量指向一个随机值
      • 指针释放后未置 NULL
        • 指针在 free 或 delete 后未赋值 NULL,它们只是把指针所指的内存释放掉了,但并没有把指针本身干掉,此时,指针指向垃圾内存

        • 空指针可以重复释放,野指针不可以重复释放

          free(p);
          p = NULL;
          
      • 指针操作超越了变量的作用域
        • 不要返回指向栈内存的指针或引用,因为栈内存在函数结束后会被释放
    • 避免野指针出现
      • 初始化时置 NULL
      • 释放时置 NULL
    • 示例
      • 没有一个有效的地址空间的指针
        #define _CRT_SECURE_NO_WARNINGS
        #include 
        #include 
        
        int main(void)
        {
            int* p;
            *p = 2000;
            printf("*p = %d\n", *p);
        
            system("pause");
            return 0;
        }
        
        • 报错:使用未初始化的内存 “p”
          指针
      • p 变量有一个值,但该值不是可访问的内存区域
        #define _CRT_SECURE_NO_WARNINGS
        #include 
        #include 
        
        // 野指针2
        int main(void)
        {
            int *p = 100;
            *p = 2000;
            printf("*p = %d\n", *p);
        
            system("pause");
            return 0;
        }
        
        • 报错:引发了异常,写入访问权限冲突
          指针

        • 合理做法

          #define _CRT_SECURE_NO_WARNINGS
          #include 
          #include 
          
          // 野指针2
          int main(void)
          {
              int m;  // 先定义一个变量,使它自动获取地址
              int *p = &m;
              *p = 2000;
              printf("*p = %d\n", *p);
          
              system("pause");
              return 0;
          }
          
          • 先定义一个变量,使它自动获取地址 (0-255 是预留给操作系统的)

万能指针/泛型指针 (void *)

泛型指针,可以接收任意一种变量地址,但是在使用时必须借助强转具体化数据类型。

  • 使用时必须强制转换,如果不强制转换,会报错:不允许使用不完整的类型
    指针

  • 示例

    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #include 
    
    int main(void)
    {
        int a = 30;
        void *p;
        p = &a;
        printf("*p = %d\n", *(int *)p);
    
        system("pause");
        return 0;
    }
    

const 关键字

  • 作用
    • 修饰变量

      修饰变量不靠谱,仍然可以通过指针的方式修改变量。

      #define _CRT_SECURE_NO_WARNINGS
      #include 
      #include 
      
      int main(void)
      {
          const int a = 10;
          int* p = &a;
          *p = 30;
          printf("a = %d\n", a);
      
          system("pause");
          return 0;
      }
      
    • 修饰指针

      const 向右修饰,被修饰的部分即为只读。常用的方式是在函数形参内,用来限制指针对应的内存空间为只读。

      const int *p;   // 可以修改 p,不可以修改 *p
      int const *p;   // 可以修改 p,不可以修改 *p
      int * const p;  // 可以修改 *p,不可以修改 p
      const int * const p;  // *p 和 p 都不可以修改
      
  • 使用场景
    • 使用结构体作为函数参数
      • 普通写法,传参是结构体变量
      • 进阶写法,传参是结构体指针,节省资源
    • 使用 const 修饰形参
      • 未使用 const 修饰形参时,外部数据可以被更改
      • const 修饰形参,防止误操作,修改时会报错
    • 示例
      #define _CRT_SECURE_NO_WARNINGS
      #include 
      #include 
      
      typedef struct person
      {
          char name[64];
          int age;
          int id;
          double score;
      } p;
      
      // 1. 普通写法,传参是结构体变量
      void showperson1(struct person p)
      {
          p.age = 66;  // 可以修改外部数据
          printf("name: %s, age: %d, id: %d, score: %.2lf\n", p.name, p.age, p.id, p.score);
      }
      
      // 2. 进阶写法,传参是结构体指针,节省资源
      void showperson2(struct person* p)
      {
          p->age = 77;  // 可以修改外部数据
          printf("name: %s, age: %d, id: %d, score: %.2lf\n", p->name, p->age, p->id, p->score);
      }
      
      // 3. 使用 const 修饰形参,防止误操作
      void showperson3(const struct person* p)
      {
          //p->age = 77;  // Error! 不可以修改外部数据
          printf("name: %s, age: %d, id: %d, score: %.2lf\n", p->name, p->age, p->id, p->score);
      }
      
      int main()
      {
          struct person p = { "Tom", 18, 10000, 50 };
      
          showperson1(p);  // name: Tom, age: 66, id: 10000, score: 50.00
          showperson2(&p);  // name: Tom, age: 77, id: 10000, score: 50.00
          showperson3(&p);  // name: Tom, age: 77, id: 10000, score: 50.00
      
          system("pause");
          return 0;
      }
      

间接访问 (解引用) 操作符 (*)

通过一个指针访问它所指向的地址的过程,叫做间接访问,或解引用,使用操作符 *。

  • 指针声明时,* 表示所声明的变量为指针

  • 指针使用时,* 表示操作指针所指向的内存空间

      • 相当于通过地址找到指针指向的内存,再操作内存
      • 放在等号左边,表示赋值 (给内存赋值,写内存)
      • 放在等号右边,表示取值 (从内存中取值,读内存)
  • 示例
    #define _CRT_SECURE_NO_WARNINGS
    #include 
    
    int main()
    {
        int* p = NULL;
        int a = 10;
        p = &a;
        *p = 20;  // * 当左值,必须确保内存可写
        int b = *p;
        printf("a = %d, *p = %d, b = %d\n", a, *p, b);  // a = 20, *p = 20, b = 20
    
        char* str = "hello";
        //*str = 'm';  // !!error,必须确保内存可写
    
        return 0;
    }
    

指针的步长

  • 代表指针 +1 后跳跃的字节数
    • char
      • +1 = 1
    • double
      • +1= 8
  • 解引用,解出的字节数
    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #include 
    #include 
    #include   // offsetof() 使用
    
    void test0401()
    {
        char buf[1024] = { 0 };
        int a = 1000;
    
        memcpy(buf, &a, sizeof(int));
        char* p = buf;
        printf("%d\n", *(int*)p);  // 1000
        printf("%d\n", *(int*)(p+1)); // 3
    }
    
    void test0402()
    {
        char buf[1024] = { 0 };
        int a = 1000;
    
        memcpy(buf+1, &a, sizeof(int));
        char* p = buf;
        printf("%d\n", *(int*)(p + 1)); // 1000
    }
    
  • offsetof 宏函数
    • 函数描述
      • 通过宏函数找到属性对应的偏移量
    • 函数原型
      #include <>
      
    • 示例
      struct person {
          char a;  // 0 ~ 3
          int b;   // 4 ~ 7
          char buf[64];  // 8 ~ 71
          int d;   // 72 ~ 75
      };
      
      void test0403()
      {
          struct person p = { 'a', 10, "hello world", 20 };
      
          printf("%p\n", &p);  // 
          printf("%p\n", &p+1);  // 加了个结构体的总长
      
          // 打印 d 的值
          printf("%d\n", *((char*)&p + 72));  // 20
          printf("%d\n", *(int *)((char *)&p + 72));  // 20
          printf("%d\n", *(int *)((char *)&p + 72));  // 20
      
          // offset 使用
          printf("%d\n", offsetof(struct person, d));  // 72
          printf("%d\n", *(int *)((char *)&p + offsetof(struct person, d)));  // 20
      }
      

通过指针间接赋值

  • 条件
    • 一个普通变量 + 指针变量,或一个实参 + 一个形参
    • 建立关系
    • 通过 * 操作指针指向的内存
  • 示例
    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #include 
    #include 
    
    void test0501()
    {
        int a = 100;  // 两个变量
        int* p = NULL;
        p = &a;  // 建立联系
        *p = 22;  // 通过 * 操作内存
    
        printf("%d\n", a);  // 22
    }
    
    void test0502(int *str)  // 一个实参,一个形参
    {
        *str = 33;  // 通过 * 操作内存
    }
    
    int main(void)
    {
        test0501();
    
        int a = 100;
        test0502(&a);  // 传参的过程就是建立联系的过程,相当于 *str = &a
        printf("%d\n", a);  // 33
    
        system("pause");
        return 0;
    }
    
    • 用 n 级指针形参,去间接修改了 n-1 级指针 (实参) 的值
    • Qt 每次分配的地址都相同,可以通过地址更改数据

指针和数组

数组名

  • 数组名是一个地址常量 (不可以修改)
    • 带有副作用的运算符 (++ — += -= /= %=)
  • 指针是变量
    • 可以用数组名给指针赋值

取数组元素

  • 结论
    int arr[] = {1, 3, 5};
    int *p = arr;
    
    arr[0] == *(arr+0) == p[0] == *(p+0)
    
  • 示例:验证以上结论
    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #include 
    
    int main(void)
    {
        int a[] = {1, 2, 3, 4, 5, 6};
        int n = sizeof(a) / sizeof(a[0]);
        int* p = a;
    
        for (size_t i = 0; i < n; i++)
        {
            printf("%d ", a[i]);
            printf("%d ", p[i]);  // 指针方式
            printf("%d ", *(a+i));  
            printf("%d ", *(p + i));
            putchar('\n');
        }
        putchar('\n');
    
        system("pause");
        return 0;
    }
    /*
    1 1 1 1
    2 2 2 2
    3 3 3 3
    4 4 4 4
    5 5 5 5
    6 6 6 6
    */
    

指针和数组区别

  • 指针是变量,数组名为常量
  • 大小
    • sizeof(指针)
      • 4 或 8 字节,与平台架构有关
    • sizeof(数组)
      • 数组的实际字节数

指针 ++ 操作数组

  • 指针的 (* / %) 操作
    • error!!!
  • 指针加减运算
    • 数据类型对指针的作用
      • 间接引用
        • 决定了从指针存储位置开始,向后读取的字节数 (与指针本身的存储空间无关)
      • 加减运算
        • 决定了指针进行 +1/-1 操作向后加过的字节数
      • 示例:使用指针 ++ 操作数组元素
        #define _CRT_SECURE_NO_WARNINGS
        #include 
        #include 
        
        // 间接引用
        int test01(void)
        {
            int arr[] = { 1, 2, 3, 4, 5 };
            int* p = arr;
            int n = sizeof(arr) / sizeof(arr[0]);
        
            for (size_t i = 0; i < n; i++)
            {
                printf("%d ", *p);
                p++;  // 间接引用,向后偏移一个字节数
            }
        
            return 0;
        }
        
        // 加减运算
        int test02(void)
        {
            //int a = 0x2ff02;
            //int* p = a;  // p+1 和 p 差 4
        
            //short a = 0x2ff02;
            //short* p = a;  // p+1 和 p 差 2
        
            long long a = 0x2ff02;
            long long* p = a;  // p+1 和 p 差 8
        
            printf("p = %p\n", p);
            printf("p + 1 = %p\n", p + 1);
        
            return 0;
        }
        
    • 指针 +/- 整数
      #define _CRT_SECURE_NO_WARNINGS
      #include 
      #include 
      #include 
      
      int main(void)
      {
          short a[] = { 1, 2, 3, 4, 5 };
      
          printf("a = %p\n", a);
          printf("&a[0] = %p\n", &a[0]);
      
          printf("a + 1 = %p\n", a + 1);
      
          printf("&a = %p\n", &a);
          printf("&a + 1 = %p\n", &a + 1);
      
          system("pause");
          return 0;
      }
      
      • 普通指针变量 + – 整数
        char *p;    //打印 p、p+1,偏移 1 字节
        short *p;   //打印 p、p+1,偏移 2 字节
        int *p;     //打印 p、p+1,偏移 4 字节
        long long *p; //打印 p、p+1,偏移 8 字节
        
      • 在数组中 + – 整数
        int arr[] = { 1, 2, 3, 4, 5 };
        int* p = arr;
        p+3;  // 向后偏移 3 个元素
        p-2;  // 向左偏移 2 个元素
        
      • &数组名 + 1
        • 加过一个数组的大小 = 数组元素个数 * sizeof(数据类型的字节数)
    • 指针 +/- 指针

      “`c++

      <h1>define _CRT_SECURE_NO_WARNINGS</h1>

      <h1>include</h1>

      <h1>include</h1>

      <h1>include</h1>

      int main(void)
      {
      int a[] = { 1, 2, 3, 4, 5 };
      int* p = &a[2];
      int* q = &a[5];

      <pre><code>//printf("p + q = %d", p + q); // 报错
      printf("p – q = %d", p – q); // 结果为 3,是元素的偏移个数

      system("pause");
      return 0;
      </code></pre>

      }

      “`

      • 指针 + 指针
        • error!!!
        • 报错:表达式必须包含整型
          指针
      • 指针 – 指针
        • 对于普通变量来说
          • 语法允许,但是无实际意义
        • 对于数组来说
          • 偏移过的元素个数
    • 示例:借助数组/指针实现 strlen 函数 (用于返回数组元素的个数)
      #define _CRT_SECURE_NO_WARNINGS
      #include 
      #include 
      
      // 用数组实现
      int mystrlen1(char str[])
      {
          int i = 0;
          while (str[i])
          {
              i++;
          }
          return i;
      }
      
      // 用指针实现
      int mystrlen2(char str[])
      {
          char* p = str;
          while (*p)
          {
              p++;
          }
          return p - str;
      }
      
      int main(void)
      {
          char a[] = "hello world";
      
          int b = mystrlen1(a);
          int c = mystrlen2(a);
      
          printf("mystrlen1 = %d\n", b);
          printf("mystrlen2 = %d\n", c);
      
          system("pause");
          return 0;
      }
      
  • 指针比较运算
    • 对于普通变量来说
      • 语法允许,但是无实际意义
    • 对于数组来说
      • 地址之间可以进行比较,得到的是元素存储的先后顺序
    • 判断空指针
      #define _CRT_SECURE_NO_WARNINGS
      #include 
      #include 
      
      int main(void)
      {
          int* p = NULL;
          if (p != NULL)
          {
              printf("p is not NULL.\n");
          }
          else
          {
              printf("p is NULL!\n");
          }
      
          system("pause");
          return 0;
      }
      
  • 指针数组
    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #include 
    
    int main(void)
    {
        int a = 10;
        int b = 20;
        int c = 30;
        int* arr1[] = { a, b, c };  // 数组元素为 整型变量 地址
    
        int* p1 = &a;
        int* p2 = &b;
        int* p3 = &c;
    
        int* arr2[] = { p1, p2, p3 };  // 数组元素为 数组 地址。整型指针数组,存的都是整型地址
        printf("%d\n", *(arr2[0]));  // == *(*(arr2+0)) == *(*arr2) == **arr2
    
        system("pause");
        return 0;
    }
    
    • 一个存储地址的数组,数组内部所有元素都是地址
      • 数组元素为整型变量地址

      • 数组元素为数组地址

        *(*(arr2+0)) == *(*arr2) == **arr2
        
    • 指针数组的本质是一个二级指针
      • 二维数组的本质也是一个二级指针
  • 多级指针
    int a = 0;
    int *p = &a;    // 一级指针是变量的地址
    int **pp = &p;  // 二级指针是一级指针的地址
    int ***ppp = &pp;  // 三级指针是二级指针的地址,三级指针就够用了
                       // int **p[];  // 相当于一个三级指针
    int ****pppp = &ppp;  // 四级指针是三级指针的地址
    
    • 对应关系
      • ppp &pp
        • 三级指针
      • *ppp pp &p
        • 二级指针
      • **ppp *pp p &a
        • 一级指针
      • ***ppp **pp *p a
        • 普通整型变量
      • 结论
        • n 级指针是 n-1 级指针的地址
    • 示例:验证以上对应关系
      #define _CRT_SECURE_NO_WARNINGS
      #include 
      #include 
      
      int main(void)
      {
          int a = 10;
          int* p = &a;
          int** pp = &p;  
          // int **pp = &(&a)  不允许
          int*** ppp = &pp;
      
          printf("*p     = %d\n", *p);
          printf("**pp   = %d\n", **pp);
          printf("***ppp = %d\n", ***ppp);
          printf("a      = %d\n", a);
      
          system("pause");
          return 0;
      }
      /*
      *p = 10
      * *pp = 10
      * **ppp = 10
      a = 10
      */
      

指针和函数

栈帧

当函数调用时,系统会在 stack 上申请一块内存区域,用来供函数调用,主要存放形参和局部变量。当函数调用结束,这块内存区域被释放。

传值和传址

  • 传值操作
    • 函数调用期间,实参将自己的值,拷贝一份给形参
  • 传址操作
    • 函数调用期间,实参将地址值,拷贝一份给形参
    • 地址值 -> 在 swap 函数栈帧内部,修改了 main 函数栈帧内部的局部变量值

函数参数

  • 指针做函数参数

    调用时,传有效的地址值。

    int swap(int *a, int *b);
    
  • 数组做函数参数

    传递的不再是整个数组,而是数组的首地址 (一个指针),所以,当整型数组做函数参数时,我们通常在函数定义中,封装 2 个参数:

    • 一个表示数组首地址
    • 一个表示元素个数
    // 以下传参形式等价
    void bubblesort(int arr[10])
    void bubblesort(int arr[])
    void bubblesort(int *arr)
    

函数返回值

  • 指针做函数返回值

    指针做函数返回值,不能返回局部变量的地址值。

    int *test_func(int a, int b);
    
    • 示例
      #define _CRT_SECURE_NO_WARNINGS
      #include 
      #include 
      
      int test_func(int a, int b)
      {
          return a + b;
      }
      
      int main(void)
      {
          int *c = test_func(10, 20);  // 不能这样声明赋值
      
          //printf("c = %d\n", c);
          printf("*c = %d\n", *c);  // Error!
      
          system("pause");
          return 0;
      }
      
  • 数组做函数返回值 (C 语言中不允许)

    只能写成指针形式。

指针做函数参数的输入、输出特性

  • 输入,在主调函数分配内存,被调函数使用

    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #include 
    #include 
    
    void test0601(char *str)
    {
        strcpy(str, "hello world");
    }
    
    void test0602(char* str)
    {
        printf("%s\n", str + 6);  // world
    }
    
    int main(void)
    {
        char buf[1024];  // 分配在栈上
        test0601(buf);  // 主调函数分配内存,被调函数使用
    
        printf("%s\n", buf);  // hello world
    
        char* p = malloc(sizeof(char) * 64);  // 分配在堆上
        memset(p, 0, 64);  // 清空内存
        strcpy(p, "hello world");
        test0602(p);
    
        if (p != NULL)
        {
            free(p);
            p = NULL;
        }
    
        system("pause");
        return 0;
    }
    
  • 输出,在被调函数中分配内存,在主调函数使用
    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #include 
    #include 
    
    // 输入特性
    void test0601(char *str)
    {
        strcpy(str, "hello world");
    }
    
    // 输入特性
    void test0602(char* str)
    {
        printf("%s\n", str + 6);  // world
    }
    
    void test0603(char **pp)
    {
        char* str = malloc(sizeof(char) * 64);
        memset(str, 0, 64);  // 清空内存
        strcpy(str, "hello world");
    
        *pp = str;
    }
    
    void test0604()
    {
        char* p = NULL;
        test0603(&p);
    
        printf("%s\n", p);  // hello world
    }
    
    int main(void)
    {
        char buf[1024];  // 分配在栈上
        test0601(buf);  // 主调函数分配内存,被调函数使用
    
        printf("%s\n", buf);  // hello world
    
        char* p = malloc(sizeof(char) * 64);  // 分配在堆上
        memset(p, 0, 64);  // 清空内存
        strcpy(p, "hello world");
        test0602(p);
    
        if (p != NULL)
        {
            free(p);
            p = NULL;
        }
    
        test0604();
    
        system("pause");
        return 0;
    }
    

指针和字符串

指针和字符串

  • 写法
    • char str1[] = {‘h’, ‘e’, ‘l’, ‘\0’};
      • 变量,可读可写
    • char str2[] = “he”;
      • 变量,可读可写
    • char *str3 = “he”;
      • 常量,只读
      • str3 变量中,存储字符串常量 “he” 中首个字符 ‘h’ 的地址值
      • str3[1] = ‘h’;
        • 错误写法
    • char *str4 = {‘h’, ‘e’, ‘\0’};
      • 错误写法
  • 当字符串 (字符数组),做函数参数时
    • 不需要提供2个参数,因为每个字符串都有 \0
  • 示例
    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #include 
    
    int main(void)
    {
        char str1[] = "hello";
        char* str2 = "hello";
    
        printf("----------- 改前\n");
        printf("str1[] = %p \n", str1);
        printf("str2 = %p\n", str2);
        printf("----------- 改后\n");
    
        //str1[0] = 'R';
        //str2[0] = 'R';
    
        printf("str1[] = %p \n", str1);
        printf("str2 = %p\n", str2);
    
        system("pause");
        return 0;
    }
    

字符串常用算法

  • 常用函数
    • strstr 函数
      • 函数描述
        • 查找在字符串 str 中,substr 子串出现的位置
      • 函数原型
        #include 
        char *strstr(char *str, char *substr)
        
        • 参数
          • str
            • 原字符串
          • substr
            • 子串
        • 返回值
          • 查找成功,返回子串在原串中的位置 (地址值);如果没有,返回 NULL
  • 字符串比较
    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #include 
    #include 
    
    // 数组实现
    int mystrcmp1(char *str1, char *str2)
    {
        int i = 0;
        while (str1[i] == str2[i])
        {
            if (str1[i] == '\0')
            {
                return 0;
            }
            i++;
        }
        return str1[i] > str2[i] ? 1 : -1;
    }
    
    // 指针实现
    int mystrcmp2(char *str1, char *str2)
    {
        while (*str1 == *str2)
        {
            if ( *str1 == '\0')
            {
                return 0;
            }
            str1++;
            str2++;
        }
        return *str1 > *str2 ? 1 : -1;
    }
    
    int main(void)
    {
        char a[] = "hello";
        char b[] = "hello";
    
        int c = mystrcmp1(a, b);
        int d = mystrcmp2(a, b);
    
        printf("c = %d\n", c);
        printf("d = %d\n", d);
    
        system("pause");
        return 0;
    }
    
    • 比较 str1 和 str2,如果相同,返回 0;如果不同,依次比较 ASCII 码;如果 str1 > str2,返回 1,否则,返回 -1
    • 利用数组/指针两种方法实现
  • 字符串拷贝
    • 将字符串拷贝到一个空数组中
      #define _CRT_SECURE_NO_WARNINGS
      #include 
      #include 
      #include 
      
      void mystrcpy1(char* src, char* dst)
      {
          int i = 0;
          while (src[i] != 0)
          {
              dst[i] = src[i];
              i++;
          }
          dst[i] = '\0';  // 手动加个结束符
      }
      
      void mystrcpy2(char* src, char* dst)
      {
          while (*src)
          {
              *dst = *src;
              src++;
              dst++;
          }
      }
      
      int main(void)
      {
          char a[] = "hello world";
          char b[100] = { 0 };
          char c[100] = { 0 };
          printf("a: %s\n", a);
      
          mystrcpy1(a, b);
          mystrcpy2(a, c);
          printf("b: %s\n", b);
          printf("c: %s\n", c);
      
          system("pause");
          return 0;
      }
      
    • 拷贝过程中,字符串去空格
      #define _CRT_SECURE_NO_WARNINGS
      #include 
      #include 
      #include 
      
      void mystrcpy3(char* src, char* dst)
      {
          int i = 0;
          int j = 0;
          while (src[i] != 0)
          {
              if (src[i] != ' ')
              {
                  dst[j] = src[i];
                  j++;
              }
              i++;
      
          }
          dst[i] = '\0';  // 手动加个结束符
      }
      
      void mystrcpy4(char* src, char* dst)
      {
          while (*src)
          {
              if (*src != ' ')
              {
                  *dst = *src;
                  dst++;
              }
              src++;
          }
      }
      
      int main(void)
      {
          char a[] = "hello world";
          char b[100] = { 0 };
          char c[100] = { 0 };
          printf("a: %s\n", a);
      
          mystrcpy3(a, b);
          mystrcpy4(a, c);
          printf("b: %s\n", b);
          printf("c: %s\n", c);
      
          system("pause");
          return 0;
      }
      
  • 在字符串中查找某个字符或子串
    • 字符串 str 中子串 substr 出现的次数
      #define _CRT_SECURE_NO_WARNINGS
      #include 
      #include 
      #include 
      
      int str_times(char *str, char *substr)
      {
          int count = 0;
          char * p = strstr(str, substr);  // llollollo -> llollo
          int substr_len = strlen(substr);  // 每次重新查找都需要偏移的元素个数
      
          while (p != NULL)
          {
              p += substr_len;
              p = strstr(p, substr);  // 数组就是一个地址 p == 数组
              count++;
          }
          return count;
      }
      
      int main(void)
      {
          char str[] = "llollollo";
          char substr[] = "llo";
          //int* p = strstr(str, substr);
          //printf("%p %p", p, str);
      
          int a = str_times(str, substr);
          printf("子串出现 %d 次.\n", a);
      
          system("pause");
          return 0;
      }
      
    • 在字符串中查找某个字符出现的位置
      #define _CRT_SECURE_NO_WARNINGS
      #include 
      #include 
      #include 
      
      // 数组版本
      char* mystrch1(char *str, char ch)  // helloworld -> o
      {
          int i = 0;
          while (str[i])
          {
              if (str[i] == ch)
              {
                  return &str[i];  // 返回字符在字符串中的地址
                  //return i + 1;  // 返回字符是字符串中的第几个元素
              }
              i++;
          }
      }
      
      // 指针版本
      char* mystrch2(char *str, char ch)
      {
          while (str)
          {
              if (*str == ch)
              {
                  return str;
              }
              str++;
          }
      }
      
      
      int main(void)
      {
          char str[] = "hello world";
          char ch = 'o';
      
          int a = mystrch1(str, ch);
          int b = mystrch2(str, ch);
      
          printf("%s\n", a);
          printf("%s\n", b);
      
          system("pause");
          return 0;
      }
      
    • 求非空字符串的元素个数
      #define _CRT_SECURE_NO_WARNINGS
      #include 
      #include 
      #include 
      
      // 数组版本
      char* no_space_str1(char *str)  // "hello world"  -> 10 
      {
          int i = 0;
          int j = 0;
          while (str[i])
          {
              if (str[i] != ' ')
              {
                  j++;
              }
              i++;
          }
          return j;
      }
      
      // 指针版本
      char* no_space_str2(char *str)
      {
          int j = 0;
          //char* p = str;
          while (*str)
          {
              if (*str != ' ')
              {
                  j++;
              }
              str++;
          }
          return j;
      }
      
      int main(void)
      {
          char str[] = "hello world";
      
          int a = no_space_str1(str);
          int b = no_space_str2(str);
      
          printf("%d\n", a);
          printf("%d\n", b);
      
          system("pause");
          return 0;
      }
      
  • 头尾双指针
    • 字符串逆置
      #define _CRT_SECURE_NO_WARNINGS
      #include 
      #include 
      #include 
      
      // 指针版本
      void str_reversed(char* str)  // hello
      {
          char* start = str;  // 首地址
          char* end = str + strlen(str) - 1;  // 末地址
      
          char temp;
          while (start < end)
          {
              temp = *start;
              *start = *end;
              *end = temp;
      
              start++;
              end--;
          }
      }
      
      int main(void)
      {
          char str[] = "hello";
          str_reversed(str);
          printf("%s\n", str);
      
          system("pause");
          return 0;
      }
      
    • 判断字符串是否是回文

      “`c++

      <h1>define _CRT_SECURE_NO_WARNINGS</h1>

      <h1>include</h1>

      <h1>include</h1>

      <h1>include</h1>

      // 指针版本
      int str_palindrome(char <em>str)
      {
      char</em> start = str;
      char* end = str + strlen(str) – 1;

      <pre><code>while (start < end)
      {
      if (*start == *end)
      {
      start++;
      end–;
      }
      else
      {
      return -1;
      }
      }
      return 1; // 循环完毕后,说明是回文,返回1
      </code></pre>

      }

      int main(void)
      {
      char str[] = "abcba";

      <pre><code>int a = str_palindrome(str);

      printf("%s %s\n", str, a == 1 ? "是回文" : "不是回文");

      system("pause");
      return 0;
      </code></pre>

      }

      “`

字符串指针强化

  • 字符串指针做函数参数

    • 八进制和十六进制的转义字符

      八进制和十六进制的字符表示的是字符的 ASCII 码对应的数值。

      • 八进制字符:’\ddd’,例如:’\063′ 表示字符 ‘3’,’3′ 的 ASCII 码是63(八进制)
      • 十六进制字符:’\xhh’,例如:’\x41′ 表示字符 ‘A’
    • sizeof 和 strlen 的区别
      #define _CRT_SECURE_NO_WARNINGS
      #include 
      #include 
      #include 
      
      int main(void)
      {
          printf("%d\n", sizeof("hello world"));  // 12
          printf("%d\n", strlen("hello world"));  // 11
      
          system("pause");
          return 0;
      }
      
      • sizeof 计算字符串实际长度
      • strlen 计算字符串中非空字符个数 (字符串结束标志是 ‘\0’)
    • 示例
      • 字符串拷贝
        #define _CRT_SECURE_NO_WARNINGS
        #include 
        #include 
        #include 
        
        
        // 数组方式
        void copystring1(char *dest, char *src)
        {
            int i = 0;
            int n = strlen(src);
        
            while (i <= n)  // 如果这里是 i < n,后面就必须手动加上 \0
            {
                dest[i] = src[i];
                i++;
            }
            //dest[i] = '\0';
        }
        
        // 指针方式
        void copystring2(char* dest, char* src)
        {
            int n = strlen(src);
        
            for (int i = 0; i <= n; i++)
            {
                *dest = *src;
                dest++;
                src++;
            }
        }
        
        // 指针的进阶版
        void copystring3(char* dest, char* src)
        {
            while(*dest++ = *src++)  // 因为是 char 指针,所以可以++取出字符串
            { }
        }
        
        int main(void)
        {
            char str[] = "hello world";
            char dest[1024];
        
            //int n = sizeof(str) / sizeof(str[0]);  // 12
        
            //printf("%d\n", n);
            //printf("%s\n", str);
        
            //copystring1(dest, str);
            //copystring2(dest, str);
            copystring3(dest, str);
            printf("%s\n", dest);
        
            system("pause");
            return 0;
        }
        
      • 字符串反转
        #define _CRT_SECURE_NO_WARNINGS
        #include 
        #include 
        #include 
        
        // 数组方式
        void copystring1(char *dest, char *src)
        {
            int i = 0;
            int n = strlen(src);
        
            while (i <= n)  // 如果这里是 i < n,后面就必须手动加上 \0
            {
                dest[i] = src[i];
                i++;
            }
            //dest[i] = '\0';
        }
        
        // 指针方式
        void copystring2(char* dest, char* src)
        {
            int n = strlen(src);
        
            for (int i = 0; i <= n; i++)
            {
                *dest = *src;
                dest++;
                src++;
            }
        }
        
        // 指针的进阶版
        void copystring3(char* dest, char* src)
        {
            while(*dest++ = *src++)  // 因为是 char 指针,所以可以++取出字符串
            { }
        }
        
        // 字符串反转-数组方式
        void reversestring1(char* str)
        {
            int i = 0;
            int j = strlen(str) - 1;
        
            while (i < j)
            {
                char temp = str[i];
                str[i] = str[j];
                str[j] = temp;
        
                i++;
                j--;
            }
        }
        
        void reversestring2(char* str)
        {
            char* start = str;
            char* end = str + strlen(str) - 1;
        
            while (start < end)
            {
                char temp = *start;
                *start = *end;
                *end = temp;
        
                start++;
                end--;
            }
        }
        
        int main(void)
        {
            char str[] = "hello world";
            char dest[1024];
        
            //int n = sizeof(str) / sizeof(str[0]);  // 12
        
            //printf("%d\n", n);
            //printf("%s\n", str);
        
            //copystring1(dest, str);
            //copystring2(dest, str);
            //copystring3(dest, str);
            //printf("%s\n", dest);
        
            //reversestring1(str);
            reversestring2(str);
            printf("%s\n", str);
        
            system("pause");
            return 0;
        }
        
  • 字符串格式化函数
    • sprintf 函数
      • 函数原型
        #include 
        int sprintf(char *str, const char *format, ...);
        
        • 参数
          • str
            • 字符串首地址
          • format
            • 字符串格式
        • 返回值
          • 成功,返回字符串的有效长度,不包含 \0;失败,返回 -1
      • 示例
        #define _CRT_SECURE_NO_WARNINGS
        #include 
        #include 
        #include 
        
        int main(void)
        {
            char* str[1024];
        
            //sprintf(str, "%s", "hello world");
            sprintf(str, "%s %s", "hello", "world");
        
            printf("%s\n", str);
        
            char str2[] = "hello\012world";
            printf("%s\n", str2);
            printf("%d\n", sizeof(str2));  // 12
            printf("%d\n", strlen(str2));  // 11
        
            system("pause");
            return 0;
        }
        
    • sscanf 函数
      • 函数原型
        #include 
        int sscanf(const char *str, const char *format, ...);
        
        • 参数
          • str
            • 指定的字符串首地址
          • format
            • 字符串格式,用法和 scanf() 一样
        • 返回值
          • 成功,返回参数数目;失败,返回 -1
      • 示例
        • 示例 1
          // 提取 abab
          void match_abab()
          {
              char buf[] = "abc#abab@ccc1223";
              char str[64] = { 0 };
          
              sscanf(buf, "%*[a-c]#%[^@]", str);
              printf("str = %s\n", str);  // abab
          }
          
          // 拆分 ip 地址
          void match_ip()
          {
              char* ip = "192.16.0.30";  
              int num[4] = {0};
          
              sscanf(ip, "%d.%d.%d.%d", &num[0], &num[1], &num[2], &num[3]);
              for (int i = 0; i < 4; i++)
              {
                  printf("%d ",num[i]);  // 192 16 0 30
              }
              putchar('\n');
          }
          
          // 提取 abb  ccc.1223
          void match_abb()
          {
              char buf[] = "abb@ccc.1223";  
              char str1[64] = { 0 }, str2[64] = { 0 };
              sscanf(buf, "%[a-c]@%s", str1, str2);
              printf("%s %s\n", str1, str2);  // abb ccc.1223
          }
          
        • 示例 2:查找字符串 “aaabcfegfa” 中 “abc” 出现的位置
          #define _CRT_SECURE_NO_WARNINGS
          #include 
          #include 
          
          // str = "abcabd", substr = "cab",用 substr 和 str 中的每3个字符逐个比较
          // 1. 数组方式
          int strch1(char* str, char* substr)
          {
              int i = 0, j = 0;
              int strLen = strlen(str);
              int substrLen = strlen(substr);
          
              for (i = 0; i < strLen - substrLen; i++)
              {
                  for (j = 0; j < substrLen; j++)
                  {
                      if (str[i + j] != substr[j])  // str[i] 偏移 j 个字符,和substr[j] 相等,
                      {
                          break;
                      }
                  }
          
                  // 如果 substr 中每个字符都比较完了,说明查找到了位置
                  if (j == substrLen)
                  {
                      return i;
                  }
              }
          
              return -1;
          }
          
          // 2. 指针方式
          int strch2(char* str, char* substr)
          {
              int i = 0;
              int n = strlen(substr);
          
              while (*str)
              {
                  int j = 0;
                  char* tmpstr = str;
                  char* tmpsubstr = substr;
          
                  while (*tmpsubstr)
                  {
                      if (*tmpstr == *tmpsubstr)  // 解引用的方式逐个比较每个字符
                      {
                          tmpstr++;
                          tmpsubstr++;
                          j++;
                          continue;
                      }
                      else
                      {
                          break;
                      }
                  }
          
                  if (j == n)
                  {
                      return i;
                  }
                  else
                  {
                      str++;
                      i++;
                  }
              }
          
              return -1;
          }
          
          int main(void)
          {
              char* str = "aaabcfegfa";
              char* substr = "abc";
          
              int a = strch1(str, substr);
              int b = strch2(str, substr);
              printf("substr 的位置是:a = %d, b = %d\n", a, b);  // substr 的位置是:a = 2, b = 2
          
              printf("%d\n", strcmp("abc", "abc"));  // 相等返回 0
          
              system("pause");
              return 0;
          }
          
    • 常用格式
      • 进制
        • %o
          • 八进制,0 前缀用于区分八进制
        • %x
          • 十六进制,0x 前缀用于区分十六进制
        • 示例
          sprintf(buf, "0%o", 100)
          sprintf(buf, "0x%x", 100)
          
      • 正则匹配
        • %s 或 %d
          • * 表示跳过 (忽略) 数据,遇到空格或 \t,忽略结束
          • %8d
            • 左对齐
          • %-8d
            • 右对齐
        • %[width]s
          • 读指定宽度的数据
        • %[a-z]
          • 匹配 a 到 z 任意字符
          • 贪婪匹配 (尽可能多的匹配),只要匹配失败,就不会再继续匹配
        • %[aBc]
          • 匹配 a、B、c 中一员,贪婪匹配
        • %[^a]
          • 匹配非 a 的任意字符,贪婪性
        • %[^a-z]
          • 读取除 a-z 以外的所有字符
        • 示例
          // 1. 忽略数字 or 字符
          void ignore_digit_or_characters()
          {
              char buf[] = "123abc";
              int num = 0;
              char str[64] = {0};
          
              sscanf(buf, "%d%*s", &num);
              sscanf(buf, "%*d%s", str);
              //sscanf(buf, "%d%s", &num, str);
              printf("num = %d, str = %s\n", num, str);  // num = 123, str = abc
          }
          
          // 2. 匹配指定宽度的数据
          void getDataWidth()
          {
              char buf[] = "123abc";
              char str[64] = {0};
              sscanf(buf, "%4s", str);
              printf("str = %s\n", str);  // str = 123a
          }
          
          // 3. 匹配 a-b 中任意字符,贪婪匹配
          void match_a_to_b()
          {
              char buf[] = "123abc";
              char str[64] = { 0 };
              sscanf(buf, "%*d%[a-b]", str);
              printf("str = %s\n", str);  // str = ab
              sscanf(buf, "%*d%[a-c]", str);
              printf("str = %s\n", str);  // str = abc
          }
          
          // 4. 匹配 a B c 中的其中一个,只要匹配失败,就不继续匹配了
          void match_aBc()
          {
              char buf[] = "aBaBcaBd";
              char str[64] = { 0 };
              sscanf(buf, "%[aBc]", str);
              printf("str = %s\n", str);  // str = aBaBcaB
          }
          
          // 5. 匹配非 a 的字符
          void match_nota()
          {
              char buf[] = "123BaBbabBc";
              char str[64] = { 0 };
              sscanf(buf, "%[^a]", str);
              printf("str = %s\n", str);  // str = 123B
          }
          

带参数的 main 函数

  • 函数原型
    int main(void);  //== int main()
    int main(int argc, char *argv[]);  // == int main(int argc, char **argv)
    
    • argc
      • 表示给 main 函数传递的参数的总个数
    • argv[]
      • 是一个存放参数的数组,数组的每个元素都是 char * (char * 就是字符串)
  • 测试
    • 命令行终端中编译,生成可执行文件
      test.exe abc asewr werq  #算上 .exe 文件,共4个参数,argc = 4
      
    • VS 中传入命令参数
      • VS 项目属性 – 调试 – 命令参数
        指针
    • 循环打印外部参数
      #define _CRT_SECURE_NO_WARNINGS
      #include 
      #include 
      #include 
      
      int main(int argc, char *argv[])
      {
          //printf("argc = %d\n", argc);
      
          for (size_t i = 0; i < argc; i++)
          {
              printf("argv[%d]=%s\n", i, argv[i]);
              //printf("%s\n", argv[i]);
          }
      
          system("pause");
          return 0;
      }
      

一级指针易错点

越界

// 越界
void test01()
{
    char buf[3] = "abc";  // 应该是 buf[4],缺少 \0
    printf("%s\n", buf);  // 显示乱码:abc烫烫虄?O
}
  • 字符串长度定义错误,缺少最后的 ‘\0’,使用 printf 打印时因为找不到 ‘\0’,所以会输出乱码

指针叠加会不断改变指针方向,在释放内存时要注意

// 指针叠加会不断改变指针方向
void test02()
{
    char* p = (char*)malloc(50);
    char* tp = p;  // 通过创建临时指针来操作内存,防止出错
    char buf[] = "abcdef";

    int n = strlen(buf);
    for (int i = 0; i < n; i++)
    {
        *tp = buf[i];
        printf("%c %c\n", buf[i], *tp);  // buf[i] == *tp
        tp++;  // 更改指针位置,直接释放原指针会出错
    }

    free(p);
    p = NULL;
}
  • 报错
    指针
  • 解决方法:使用临时变量保存初始指针

同一块内存释放多次

// 同一块内存释放多次
void test03()
{
    char* p = (char*)malloc(50);

    strcpy(p, "hello");
    printf("%s\n", p);  // hello

    if (p != NULL)
    {
        // 只是告诉系统,p 指向的内存可以回收了,这块内存的使用权交还给系统;
        // 但是,p 的值还是原来的值 (野指针),p 还是指向原来的内存
        free(p); 
    }

    // Error! 不能再次释放
    //if (p != NULL)
    //{
    //  free(p);
    //}
}
  • 不可以释放野指针 (已被回收的指针)
    指针

返回局部变量地址

// 返回一个局部变量
char* getstr()
{
    char str[] = "afasdfasf";  // 在栈上开辟内存
    printf("%s\n", str);  // afasdfasf

    return str;  // Error! 
}

int main()
{
    printf("%s\n", getstr());  // 烫烫烫烫烫烫@

    system("pause");
    return 0;
}
  • 局部变量创建在栈上,在函数调用结束后被释放

多级指针

二级指针做函数参数的输入、输出特性

  • 输入特性
    • 在主调函数分配内存,被调函数使用
    • 创建在栈上/堆上
  • 输出特性
    • 被调函数分配内存,主调函数使用
    • 同级指针释放,要手动释放一次
  • 示例
    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #include 
    
    // 打印数组
    void printArr1(int** arr, int n)
    {
        for (size_t i = 0; i < n; i++)
        {
            printf("%d ", *arr[i]);  // 1 2 3
        }
        putchar('\n');
    }
    
    // 输入特性:主调函数分配内存,被调函数使用
    void test01()
    {
        // 在栈上创建数据
        int a = 1, b = 2, c = 3;
    
        // 开辟堆区内存
        int** arr = malloc(sizeof(int*) * 3);
    
        arr[0] = &a;
        arr[1] = &b;
        arr[2] = &c;
    
        // 打印数组
        printArr1(arr, 3);
    
        // 释放堆区内存
        free(arr);
        arr = NULL;
    }
    
    // 输出特性:被调函数分配内存,主调函数使用
    void allocateSpace(int** tp)
    {
        int* p = malloc(sizeof(int) * 3);
    
        for (size_t i = 0; i < 3; i++)
        {
            p[i] = i + 10;
            printf("%d ", p[i]);  // 10 11 12
        }
        putchar('\n');
    
        *tp = p;
    }
    
    // 打印数组
    void printArr2(int** arr, int n)
    {
        for (size_t i = 0; i < n; i++)
        {
            printf("%d ", (*arr)[i]);  // 10 11 12  要先做解引用
        }
        putchar('\n');
    }
    
    int main()
    {
        test01();
    
        int* p = NULL;
    
        allocateSpace(&p);
        printArr2(&p, 3);
    
        // 手动释放堆区内存
        if (p != NULL)
        {
            free(p);
            p = NULL;
            printf("此时为空指针");
        }
    
        system("pause");
        return 0;
    }
    

使用二级指针进行文件读写

  • 示例
    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #include 
    #include 
    
    // 打开文件,输出文件行数
    int getRow(FILE* fp)
    {
        // 判断文件是否打开成功
        if (fp == NULL)
        {
            perror("fopen error");
            return -1;
        }
    
        int i = 0;
        char buf[10] = { 0 };
        while (fgets(buf, 10, fp) != NULL)
        {
            //printf("%s", buf);
            i++;
        }
        //putchar('\n');
    
        fseek(fp, 0, SEEK_SET);  // 将文件光标恢复到开头位置
        return i;
    }
    
    // 读取文件到 pArray
    void fileRead(FILE* fp, int n, char** pArray)
    {
        // 判断参数合法性
        if (!fp || (n == 0) || (pArray == NULL))
        {
            perror("fopen error");
            return;
        }
    
        int i = 0;
        char buf[1024] = { 0 };
        while (fgets(buf, 1024, fp) != NULL)
        {
            int len = strlen(buf) + 1;
            //printf("%d\n", len);
    
            char* currentStr = malloc(sizeof(char) * len);
            strcpy(currentStr, buf);
            //printf("%s", currentStr);
            pArray[i++] = currentStr;
            memset(buf, 0, 1024);  // 清理内存
        }
    }
    
    // 打印 pArray 中的数据
    void printData(char** pArray, int n)
    {
        for (size_t i = 0; i < n; i++)
        {
            printf("%s", pArray[i]);
        }
        putchar('\n');
    }
    
    int main(void)
    {
        // 打开文件
        FILE* fp = fopen("./test.txt", "r");
    
        // 输出文件行数
        int n = getRow(fp);
        printf("%d\n", n);  // 4
    
        // 创建堆区
        char** pArray = malloc(sizeof(char*) * n);
    
        // 读取文件到 pArray
        fileRead(fp, n, pArray);
    
        // 打印 pArray 中的数据
        printData(pArray, n);  // 文件中的中文内容显示为乱码
    
        // 释放堆区
        free(pArray);
        pArray = NULL;
    
        // 关闭文件
        fclose(fp);
    
        system("pause");
        return EXIT_SUCCESS;
    }
    
  • 遗留问题:fopen 的文件内容中,中文显示为乱码,是因为本电脑的系统编码是 GBK 格式,而读取的文件编码是 UTF-8

多维数组

一维数组名

  • 除了两种特殊情况,都是指向数组首元素的指针

    • sizeof 统计的是数组长度
    • 对数组名取地址,得到的数组指针的步长是整个数组长度
  • 数组名是指针常量,指针的方向不可以修改,但是指针指向的值可以修改

  • 可读性

    • 传参时,int arr[] 可读性更高
  • 数组索引下标可以为负数,例如:p[-1] 表示最后一个元素

  • 示例

    void test01()
    {
        int arr[5] = { 1, 2, 3, 4, 5 };
    
        printf("%d\n", sizeof(arr));  // 20
        printf("%d\n", &arr);    // 17823928
        printf("%d\n", &arr + 1);  // 17823948  步长是整个数组长度 20
    
        //arr = NULL;  // Error! 指针的指向不可以修改
        arr[0] = 9;    // 但是指向的值可以修改
        printf("%d\n", arr[0]);  // 9
    
        int* p = arr;
        p = p + 3;
        printf("%d\n", p[-1]); // 3, p = p -1,索引可以为负数, p[-1] 是最后一个元素
        printf("%d\n", *(p - 1));  // 3
    }
    

数组指针的定义方式

  • 先定义数组类型,再通过类型定义数组指针变量

  • 先定义数组指针类型,再通过类型定义数组指针变量

  • 直接定义数组指针变量

  • 示例

    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #include 
    #include 
    
    // 1. 先定义数组类型,再通过类型定义数组指针变量
    void test01()
    {
        int arr[5] = { 1, 2, 3, 4, 5 };
    
        typedef int(ARRAY_TYPE)[5];  // ARRAY_TYPE 代表存放 5 个 int 类型元素的数组 的数组类型
        ARRAY_TYPE array = {0};
    
        for (int i = 0; i < 5; i++)
        {
            array[i] = i + 10;
            printf("%d\n", array[i]);  // 10 11 12 13 14
        }
    
        ARRAY_TYPE* arrP = &arr;  // *arrP == arr == 数组名
        for (int i = 0; i < 5; i++)
        {
            printf("%d\n", (*arrP)[i]);  // 1 2 3 4 5
        }
    }
    
    // 2. 先定义数组指针类型,再通过类型定义数组指针变量
    void test02()
    {
        int arr[5] = { 1, 2, 3, 4, 5 };
    
        typedef int(*ARRAY_TYPE)[5];  // 先定义一个指针类型
        ARRAY_TYPE arrP = &arr;
    
        for (int i = 0; i < 5; i++)
        {
            printf("%d\n", (*arrP)[i]);  // 1 2 3 4 5
        }
    }
    
    // 3. 直接定义数组指针变量
    void test03()
    {
        int arr[5] = { 1, 2, 3, 4, 5 };
    
        int(*p)[5] = &arr;  // 定义一个数组指针变量
    
        for (int i = 0; i < 5; i++)
        {
            printf("%d\n", (*p)[i]);  // 1 2 3 4 5
        }
    }
    
    int main(void)
    {
        test01();
        printf("-----------------\n");
        test02();
        printf("-----------------\n");
        test03();
    
        system("pause");
        return 0;
    }
    

二维数组名

  • 定义方式
    • 直接指定行列
    • 自动分配行列
  • 二维数组名称是指向第一个一维数组的数组指针 (与一维数组名情况相同)
    • 特殊情况
      • sizeof 统计二维数组大小

      • 对数组名取地址

        int (*p)[3][3] = &arr
        
  • 示例
    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #include 
    #include 
    
    void test01()
    {
        // 1. 直接指定行列
        int arr1[2][3] = {
            {1, 2, 3},
            {4, 5, 6}  // 这行的逗号可有可无
        };
        // 2. 自动分配行列
        int arr2[2][3] = { 1, 2, 3, 4, 5, 6 };
        int arr3[][3] = { 1, 2, 3, 4, 5, 6 };   // 至少要指定列
    
        printf("%d\n", sizeof(arr1));  // 24 = 2 * 3 * 4
        printf("arr1 地址: %p\n", &arr1);  // arr1 地址: 000000DDBE74F6E8
        printf("arr1 地址偏移1: %p\n", &arr1 + 1);  // arr1 地址偏移1: 000000DDBE74F700,和上面差24
    
        int(*pArray)[3] = arr1;  // 定义数组指针
        printf("%d\n", arr1[1][2]);            // 6,具有可读性,给人看的
        printf("%d\n", *(*(pArray + 1) + 2));  // 6,给机器看的
    }
    
    int main(void)
    {
        test01();
    
        system("pause");
        return EXIT_SUCCESS;
    }
    

二维数组做函数参数

  • 数组做函数参数
    void printArray1(int arr[][3], int row, int col)
    void printArray1(int arr[2][3], int row, int col)
    
  • 数组指针做函数参数
    void printArray2(int (*array)[3], int row, int col)
    
  • 示例
    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #include 
    #include 
    
    // 1. 数组做函数参数
    //void printArray1(int arr[2][3], int row, int col)
    void printArray1(int arr[][3], int row, int col)
    {
        for (int i = 0; i < 2; i++)
        {
            for (int j = 0; j < 3; j++)
            {
                printf("%d ", arr[i][j]);
            }
            putchar('\n');
        }
    }
    
    // 2. 数组指针做函数参数
    void printArray2(int(*array)[3], int row, int col)
    {
        for (int i = 0; i < 2; i++)
        {
            for (int j = 0; j < 3; j++)
            {
                printf("%d ", *(*(array + i) + j));
            }
            putchar('\n');
        }
    }
    
    int main()
    {
        int arr1[2][3] = {
        {1, 2, 3},
        {4, 5, 6}  // 这行的逗号可有可无
        };
    
        printArray1(arr1, 2, 3);
        printArray2(arr1, 2, 3);
    
        system("pause");
        return 0;
    }
    

指针数组

  • 数组指针和指针数组的区别
    • 数组指针
      • 指向数组的指针
    • 指针数组
      • 由指针组成的数组
  • 指针数组排序
    • 指针数组排序
      • 不能用地址做对比,要用 strcmp 函数
    • 示例:选择排序 (效率高于冒泡排序)
      #define _CRT_SECURE_NO_WARNINGS
      #include 
      #include 
      #include 
      
      // 选择排序 -> 从小到大
      //void selectSort(int* str[], int len)
      void selectSort1(int* str, int len)
      {
          for (int i = 0; i < len; i++)
          {
              int min = i;  // 记录最小值的下标为 i
      
              for (int j = i + 1; j  str[j])
                  {
                      min = j;  // 更新真实最小值下标
                  }
              }
      
              if (min != i)
              {
                  int temp = str[i];
                  str[i] = str[min];
                  str[min] = temp;
              }
              //printf("%d\n", str[i]);
          }
      }
      
      // 打印数组
      void printStr1(int* str, int len)
      {
          for (int i = 0; i  从大到小
      void selectSort2(char* str[], int len)
      {
          for (int i = 0; i < len; i++)
          {
              int max = i;
              for (int j = i + 1; j < len; j++)
              {
                  //if (str[i]  指针方式
      void printStr2(char* str[], int len)
      {
          for (int i = 0; i < len; i++, str++)
          {
              printf("%s ", *str);
          }
          putchar('\n');
      }
      
      int main(void)
      {
          int str1[] = { 1, 5, 3, 4 };
          int len1 = sizeof(str1) / sizeof(str1[0]);
          printf("len1 = %d\n", len1);  // 4
          selectSort1(str1, len1);
          printStr1(str1, len1);  // 1 3 4 5
      
          char *str2[] = { "eee", "bbb", "ddd", "ccc" };
          int len2 = sizeof(str2) / sizeof(str2[0]);
          printf("len2 = %d\n", len2);
          selectSort2(str2, len2);
          printStr2(str2, len2);
      
          system("pause");
          return EXIT_SUCCESS;
      }
      
1
英仔
版权声明:本站原创文章,由 英仔 2022-08-10发表,共计26212字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)