【C】指针进阶(上)

指针进阶

文章目录

继上文, 指针,我们已经大致了解了以下四点:

  1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。

  2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。

  3. 指针是有类型,指针的类型决定了指针的±整数的步长,指针解引用操作的时候的权限。

  4. 指针的运算。

那么这篇文章我们就指针来做深一步的讨论~

字符指针

字符指针是指向字符数据的指针 。它可以用于操作字符串,即以字符数组的形式存储的一组字符。在指针的类型中我一种指针类型为字符指针 char* ;

c 复制代码
#include <stdio.h>

int main() {
    char str[] = "Hello, World!"; // 字符数组,存储了字符串
    char* ptr = str; // 字符指针,指向字符数组的首地址

    printf("String: %s\n", ptr); // 通过字符指针输出字符串

    while (*ptr != '\0') { // 循环遍历字符数组中的每个字符,直到遇到结束符 '\0'
        printf("Character: %c\n", *ptr); // 输出当前字符
        ptr++; // 指针移动到下一个字符
    }

    return 0;
}
//输出结果
String: Hello, World!
Character: H
Character: e
Character: l
Character: l
Character: o
Character: ,
Character:
Character: W
Character: o
Character: r
Character: l
Character: d
Character: !

字符指针的本质就是将字符串的首地址放到了指针中

需要注意的是,在操作字符指针和字符串时,需要确保字符串以结束符 '\0' 结尾,否则会导致意想不到的结果。在声明字符数组时,C语言会自动在字符串末尾添加结束符,因此我们可以使用上述方法遍历字符串。

另外,字符指针还可以用于字符串的处理,如拷贝、连接、比较等操作,同时还可以通过指针的偏移来修改字符串中的字符。

例子

所以再面试题中,有这么一道题:

c 复制代码
#include <stdio.h>
int main()
{
    char str1[] = "hello bit.";
    char str2[] = "hello bit.";
    const char *str3 = "hello bit.";
    const char *str4 = "hello bit.";
    if(str1 ==str2)
 printf("str1 and str2 are same\n");
    else
 printf("str1 and str2 are not same\n");
       
    if(str3 ==str4)
 printf("str3 and str4 are same\n");
    else
 printf("str3 and str4 are not same\n");
       
    return 0;
}

这里的输出结果:

c 复制代码
str1 and str2 are not same
str3 and str4 are same

解析

在这段代码中,我们声明了四个字符串变量 str1str2str3str4 来存储相同内容的字符串 "hello bit."。其中,str1str2 是字符数组,而 str3str4 是字符指针。

在比较 str1str2 的地址时,由于它们分别是不同的字符数组,它们的地址也是不同的,所以 str1 == str2 将被认为是不相等的。因此,输出结果会是 "str1 and str2 are not same"

而在比较 str3str4 的地址时,它们都是指向相同字符串 "hello bit."的常量字符指针。由于代码中的字符串字面值在编译时就被分配了固定的内存地址,所以 str3str4 的值相等。因此,str3 == str4 将被认为是相等的。所以,输出结果会是 "str3 and str4 are same"

指针数组

概念

指针数组是一个存放指针的数组

再次强调!指针数组是数组!数组!数组!!!

每个元素都是指针,指向不同的数据对象或变量。指针数组可以用于存储不同类型的指针 ,也可以存储指向相同类型的多个对象的指针


要声明一个指针数组,需要指定指针的类型和数组的大小。

例如,int *arr[5] 声明了一个包含 5 个指向整型数据的指针的数组。


指针数组的元素可以通过索引访问。可以使用下标(从0开始)来指定要访问的元素。例如,对于指针数组 int *arr[5],可以使用 arr[0]arr[1] 等来访问其中的指针元素。


指针数组的使用可以带来一些好处。

例如,可以将多个指针作为参数传递给函数,可以方便地在代码中存储和处理多个指针对象。此外,指针数组还可以用于实现动态内存管理,例如创建和管理动态分配的对象的指针。

c 复制代码
#include <stdio.h>

int main() {
    int num1 = 1;
    int num2 = 2;
    int num3 = 3;
    
    int* arr[3]; // 声明一个包含 3 个指向整型数据的指针的数组
    
    arr[0] = &num1; // 将 num1 的地址赋值给数组的第一个元素
    arr[1] = &num2; // 将 num2 的地址赋值给数组的第二个元素
    arr[2] = &num3; // 将 num3 的地址赋值给数组的第三个元素
    
    for (int i = 0; i < 3; i++) {
        printf("Element %d: %d\n", i, *(arr[i])); // 使用指针解引用来输出指针指向的值
    }
    
    return 0;
}

输出结果会是:

Element 0: 1
Element 1: 2
Element 2: 3

在上述示例中,我们了一个包含 3 个指向整型数据的指针的数组 arr。然后,我们分别将 num1num2num3 的地址赋值给数组中的元素。最后,我们通过循环遍历指针数组,并使用指针解引用来输出指针指向的值。

指针数组使用非常灵活,可以根据实际的需求来存储和操作不同类型的指针。

数组指针

既然知道了什么是指针数组,那么数组指针又是什么呢?

是指针!指针!指针!(在地上打滚面红耳赤尖叫~)

写法

声明一个数组指针的格式为:

c 复制代码
datatype (*pointer_name)[size];
/*
datatype 是指向的数组元素的数据类型,
pointer_name 是指针的名字,
size 是数组的大小。
*/
  1. 声明一个指向整型数组的指针:
c 复制代码
int (*ptr)[5];

这个指针指向一个包含 5 个整型元素的数组。

  1. 声明一个指向字符数组的指针:
c 复制代码
char (*ptr)[10];

这个指针指向一个包含 10 个字符元素的数组。

  1. 声明一个指向双精度浮点数数组的指针:
c 复制代码
double (*ptr)[7];

这个指针指向一个包含 7 个双精度浮点数元素的数组。

  1. 声明一个指向二维整型数组的指针:
c 复制代码
int (*ptr)[3][4];

这个指针指向一个包含 3 行 4 列的二维整型数组。

注意事项

括号是必需的,因为数组指针的优先级比*高。括号的存在可以确保指针指向整个数组,而不是数组的某个元素。


实践

c 复制代码
int *p1[10];
int (*p2)[10];
//p1, p2分别是什么?

p1 是一个指针数组,包含 10 个指向整型变量的指针。每个指针可以指向一个整型变量。

p2 是一个指向整型数组的指针。该数组包含 10 个整型元素。

&数组名VS数组名

&数组名数组名 在C语言中是有着不同的含义。区别如下:

  1. &数组名:使用 & 运算符加上数组名,可以获取数组的地址这意味着它会返回指向数组的指针,指针的类型为数组类型的指针

    例如,如果有一个名为 arr 的数组,那么 &arr 将返回指向数组 arr 的指针。

  2. 数组名:在大多数情况下,数组名将被解释为指向数组第一个元素的指针。所以当使用数组名时,它实际上是一个指向数组第一个元素的指针。

    例如,如果有一个名为 arr 的数组,那么 arr 将被解释为指向数组 arr 第一个元素的指针。这个指针的类型会根据数组元素类型而定。

下面是一个示例来说明两者的区别:

c 复制代码
int arr[5] = {1, 2, 3, 4, 5};

int *ptr1 = &arr;  // 这种写法是错误的,&arr是一个指向整个数组的指针,与int*不匹配

int *ptr2 = arr;  // 正确,arr被解释为指向数组第一个元素的指针

printf("ptr2[0] = %d\n", ptr2[0]);  // 输出第一个元素的值

int (*ptr3)[5] = &arr;  // 正确,&arr是一个指向数组的指针,ptr3为指向整个数组的指针

printf("(*ptr3)[0] = %d\n", (*ptr3)[0]);  // 输出第一个元素的值

再举一个例子

c 复制代码
#include <stdio.h>
int main()
{
 int arr[10] = { 0 };
 printf("arr = %p\n", arr);
 printf("&arr= %p\n", &arr);
 printf("arr+1 = %p\n", arr+1);
 printf("&arr+1= %p\n", &arr+1);
 return 0;
}

输出结果为:

arr = 0x7fff99774d50
&arr= 0x7fff99774d50
arr+1 = 0x7fff99774d54
&arr+1= 0x7fff99774d74

解释:

  • arr&arr 的值是相等的,它们都是数组第一个元素的地址。因为数组名被解释为指向数组第一个元素的指针,所以 arr&arr 的值是相同的。

  • arr+1 的值是数组中下一个元素的地址,即数组第二个元素的地址。因为 arr 是指向数组第一个元素的指针,当加上 1 后,指针向后移动了一个元素大小的距离。在这个例子中,整型元素的大小为 4 字节,所以 arr+1 的值比 arr 的值增加了 4

  • &arr+1 的值是整个数组之后的地址,也就是数组的尾部之后的地址。因为 &arr 是指向整个数组的指针,所以当加上 1 后,指针向后移动了整个数组的大小的距离。在这个例子中,整个数组的大小为 40 字节(数组中有 10 个整型元素,每个元素大小为 4 字节),所以 &arr+1 的值比 &arr 的值增加了 40

总结一下,&数组名 返回指向整个数组的指针,表示的是数组的地址。而 数组名 返回指向数组第一个元素的指针。它们在类型上有所不同,对于指针的声明和使用会有所区别。

使用

c 复制代码
#include <stdio.h>

int main() {
    int arr[3][5] = {
        {1, 2, 3, 4, 5},
        {6, 7, 8, 9, 10},
        {11, 12, 13, 14, 15}
    };
    
    int (*ptr)[5]; // 声明一个指向包含 5 个整型元素的数组的指针
    ptr = arr; // 将数组的地址赋值给指针
    
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 5; j++) {
            printf("Element[%d][%d]: %d\n", i, j, (*ptr)[j]); // 使用指针解引用操作访问数组元素
        }
        ptr++; // 指针进行算术运算,指向下一个元素的地址
    }
    
    return 0;
}

输出结果会是:

Element[0][0]: 1
Element[0][1]: 2
Element[0][2]: 3
Element[0][3]: 4
Element[0][4]: 5
Element[1][0]: 6
Element[1][1]: 7
Element[1][2]: 8
Element[1][3]: 9
Element[1][4]: 10
Element[2][0]: 11
Element[2][1]: 12
Element[2][2]: 13
Element[2][3]: 14
Element[2][4]: 15

在上述示例中,我们声明了一个包含 3 行 5 列的二维数组 arr。然后,我们声明了一个数组指针 ptr,并将 arr 的地址赋值给它。接下来,我们使用双重循环遍历数组,并通过指针解引用操作和数组指针的算术运算,输出了数组的各个元素。

数组指针的使用总结起来基本就是下面三种:

在C语言中,数组指针是一种特殊的指针类型,它可以用来指向数组。通过数组指针,我们可以方便地访问数组中的元素。

要声明一个数组指针,可以使用以下语法:

c 复制代码
类型 (*指针名)[数组长度];

其中,类型 表示数组元素的类型,指针名 是指针的名称,数组长度 是数组的长度。


以下是几种常见的使用数组指针的方式:

  1. 数组指针作为函数参数:
c 复制代码
void printArray(int (*ptr)[5], int length) {
  for (int i = 0; i < length; i++) {
    printf("%d ", (*ptr)[i]);
  }
  printf("\n");
}

int main() {
  int arr[5] = {1, 2, 3, 4, 5};
  int (*ptr)[5] = &arr;
  printArray(ptr, 5);
  return 0;
}

在这个例子中,printArray 函数接受一个指向长度为5的整型数组的数组指针作为参数。在 main 函数中,我们创建了一个指向 arr 数组的指针 ptr,然后将 ptr 作为参数传递给 printArray 函数。函数内部通过 (*ptr)[i] 的方式访问数组元素。

  1. 数组指针的数组:
c 复制代码
int main() {
  int arr1[5] = {1, 2, 3, 4, 5};
  int arr2[4] = {10, 20, 30, 40};
  int (*arrPtr[2])[5] = {&arr1, &arr2};

  for (int i = 0; i < 2; i++) {
    for (int j = 0; j < 5; j++) {
      printf("%d ", (*arrPtr[i])[j]);
    }
    printf("\n");
  }

  return 0;
}

在这个例子中,我们声明了一个数组 arrPtr,它包含两个指针,这两个指针分别指向长度为5的整型数组。然后我们通过 (*arrPtr[i])[j] 的方式访问数组元素。

  1. 动态分配数组指针:
c 复制代码
int main() {
  int (*ptr)[5] = malloc(sizeof(int[5]));

  for (int i = 0; i < 5; i++) {
    (*ptr)[i] = i + 1;
  }

  for (int i = 0; i < 5; i++) {
    printf("%d ", (*ptr)[i]);
  }
  printf("\n");

  free(ptr);
  return 0;
}

在这个例子中,我们使用 malloc 函数动态分配了一个长度为5的整型数组,并将返回的指针赋值给 ptr。通过 (*ptr)[i] 的方式访问数组元素。最后记得使用 free 函数释放内存。


最后用我觉得数组指针的难点它与&数组名之间的区别,通俗来理解就是:数组指针是整个数组的地址,这本质上与&数组名是有区别的,&数组名是数组首元素的地址,不过这两者在遍历数组上基本是一样的,唯一区别就是数组指针它可以指定在任意一个元素开始遍历~

数组传参和指针传参

在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?

一维数组传参

当将一维数组传递给函数时,有三种常见的方式:

  1. 传递指针

    将数组名作为指针参数传递给函数。由于数组名本身就是数组首元素的地址,因此在函数中可以通过指针操作符 * 来访问数组元素。

c 复制代码
void printArray(int *arr, int size) {
    // 使用指针操作符 * 来访问数组元素
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main() {
    int arr[] = {1, 2, 3, 4, 5};
    int size = sizeof(arr) / sizeof(arr[0]);

    // 传递数组名作为指针参数
    printArray(arr, size);
    
    return 0;
}
  1. 传递数组长度

    将数组名作为指针参数传递给函数,并使用额外的参数将数组的长度传递给函数。这种方式适用于在函数中需要知道数组长度的情况。

c 复制代码
void printArray(int *arr, int size) {
    // 使用传入的数组长度进行循环
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main() {
    int arr[] = {1, 2, 3, 4, 5};

    // 传递数组名作为指针参数,同时传递数组长度
    printArray(arr, sizeof(arr) / sizeof(arr[0]));
    
    return 0;
}
  1. 使用固定长度的数组

    在函数定义中指定数组的长度,并将数组作为参数传递给函数。这种方式适用于固定长度的数组,并且可以在函数内部直接使用这个已知长度的数组。

c 复制代码
void printArray(int arr[], int size) {
    // 直接使用指定长度的数组
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main() {
    int arr[] = {1, 2, 3, 4, 5};

    // 将数组作为参数传递给函数,同时传递数组长度
    printArray(arr, sizeof(arr) / sizeof(arr[0]));
    
    return 0;
}

二维数组传参

当将二维数组传递给函数时,同样有多种方式:

  1. 传递指针

    可以将二维数组的首行地址作为指针参数传递给函数。在函数中,可以使用指针操作符 * 和数组下标来访问二维数组元素。

c 复制代码
void printMatrix(int (*arr)[cols], int rows, int cols) {
    // 使用指针操作符 * 和数组下标访问二维数组元素
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}

int main() {
    int matrix[3][3] = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };
    int rows = sizeof(matrix) / sizeof(matrix[0]);
    int cols = sizeof(matrix[0]) / sizeof(matrix[0][0]);

    // 传递二维数组的首行地址作为指针参数
    printMatrix(matrix, rows, cols);
    
    return 0;
}
  1. 使用指针数组

    可以将每一行看作是一个一维数组,并将指向二维数组的指针数组传递给函数。在函数中,使用指针操作符 * 来访问二维数组元素。

c 复制代码
void printMatrix(int *arr[], int rows, int cols) {
    // 使用指针操作符 * 访问二维数组元素
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}

int main() {
    int matrix[3][3] = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };
    int *ptr[3];
    int rows = sizeof(matrix) / sizeof(matrix[0]);
    int cols = sizeof(matrix[0]) / sizeof(matrix[0][0]);

    // 将指向二维数组的指针数组传递给函数
    for (int i = 0; i < rows; i++) {
        ptr[i] = matrix[i];
    }
    printMatrix(ptr, rows, cols);
    
    return 0;
}
  1. 使用一维指针

    可以将整个二维数组展开成一维数组,并传递该一维数组的指针给函数。在函数中,可以使用类似于一维数组的方式来访问元素。

c 复制代码
void printMatrix(int *arr, int rows, int cols) {
    // 使用类似于一维数组的方式访问元素
    for (int i = 0; i < rows * cols; i++) {
        printf("%d ", arr[i]);
        if ((i + 1) % cols == 0) {
            printf("\n");
        }
    }
}

int main() {
    int matrix[3][3] = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };
    int rows = sizeof(matrix) / sizeof(matrix[0]);
    int cols = sizeof(matrix[0]) / sizeof(matrix[0][0]);

    // 将二维数组展开成一维数组,并传递指针给函数
    printMatrix((int *)matrix, rows, cols);
    
    return 0;
}

一级指针传参

当将一级指针作为参数传递给函数时,有三种常见的方式:

  1. 传递指针的地址

    可以将一级指针的地址作为参数传递给函数。在函数中,使用双重指针来接收该地址,并可以通过指针操作符 *-> 来访问指针指向的值和成员。

c 复制代码
void updatePointer(int **ptr) {
    // 使用指针操作符 * 和 -> 访问指针指向的值和成员
    *ptr = NULL;
}

int main() {
    int *ptr;
    int num = 10;

    ptr = &num;

    // 传递一级指针的地址
    updatePointer(&ptr);

    return 0;
}
  1. 传递指针的指针

    可以直接将一级指针作为指针参数传递给函数。在函数中,使用指针操作符 *-> 来访问指针指向的值和成员。

c 复制代码
void updatePointer(int **ptr) {
    // 使用指针操作符 * 和 -> 访问指针指向的值和成员
    *ptr = NULL;
}

int main() {
    int *ptr;
    int num = 10;

    ptr = &num;

    // 直接传递一级指针参数
    updatePointer(&ptr);

    return 0;
}
  1. 传递指针的引用

    在 C++ 中,可以使用引用来传递一级指针参数。在函数中,使用引用符号 & 来声明指针参数,并可以通过指针操作符 *-> 来访问指针指向的值和成员。

cpp 复制代码
void updatePointer(int *&ptr) {
    // 使用指针操作符 * 和 -> 访问指针指向的值和成员
    ptr = NULL;
}

int main() {
    int *ptr;
    int num = 10;

    ptr = &num;

    // 传递指针的引用
    updatePointer(ptr);

    return 0;
}

二级指针传参

当将二级指针作为参数传递给函数时,有三种常见的方式:

  1. 传递指针的地址

    可以将二级指针的地址作为参数传递给函数。在函数中,使用三重指针来接收该地址,并可以通过多次指针操作符 *-> 来访问指针指向的值和成员。

c 复制代码
void updatePointer(int ***ptr) {
    // 使用指针操作符 * 和 -> 访问指针指向的值和成员
    **ptr = NULL;
}

int main() {
    int **ptr;
    int num = 10;

    ptr = &num;

    // 传递二级指针的地址
    updatePointer(&ptr);

    return 0;
}
  1. 传递指针的指针

    可以直接将二级指针作为指针参数传递给函数。在函数中,使用指针操作符 *-> 来访问指针指向的值和成员。

c 复制代码
void updatePointer(int **ptr) {
    // 使用指针操作符 * 和 -> 访问指针指向的值和成员
    *ptr = NULL;
}

int main() {
    int **ptr;
    int num = 10;

    ptr = &num;

    // 直接传递二级指针参数
    updatePointer(ptr);

    return 0;
}
  1. 传递指针的引用

    在 C++ 中,可以使用引用来传递二级指针参数。在函数中,使用引用符号 & 来声明指针参数,并可以通过多次指针操作符 *-> 来访问指针指向的值和成员。

cpp 复制代码
void updatePointer(int **&ptr) {
    // 使用指针操作符 * 和 -> 访问指针指向的值和成员
    ptr = NULL;
}

int main() {
    int **ptr;
    int num = 10;

    ptr = &num;

    // 传递二级指针的引用
    updatePointer(ptr);

    return 0;
}

注意事项:

在C语言中不能直接使用传递指针的引用(例如int *&ptr),因为C语言没有引用类型。


ok,到这里就暂时将指针进阶的上半部分介绍了,明天或者今晚会再更新下半部分,敬请期待~

相关推荐
SRY122404192 小时前
javaSE面试题
java·开发语言·面试
lb36363636363 小时前
介绍一下数组(c基础)(详细版)
c语言
李元豪3 小时前
【智鹿空间】c++实现了一个简单的链表数据结构 MyList,其中包含基本的 Get 和 Modify 操作,
数据结构·c++·链表
无尽的大道3 小时前
Java 泛型详解:参数化类型的强大之处
java·开发语言
ZIM学编程3 小时前
Java基础Day-Sixteen
java·开发语言·windows
我不是星海3 小时前
1.集合体系补充(1)
java·数据结构
放逐者-保持本心,方可放逐3 小时前
react 组件应用
开发语言·前端·javascript·react.js·前端框架
一丝晨光4 小时前
编译器、IDE对C/C++新标准的支持
c语言·开发语言·c++·ide·msvc·visual studio·gcc
阮少年、4 小时前
java后台生成模拟聊天截图并返回给前端
java·开发语言·前端
代码小鑫4 小时前
A027-基于Spring Boot的农事管理系统
java·开发语言·数据库·spring boot·后端·毕业设计