C语言学习(四)指针与数组 下

指针数组和数组指针的分析:
        1、数组类型:数组有自己特定的类型,数组的类型由数组元素和数组大小共同决定;

        数组类型的定义:
基本语法:

typedef type(Name)[arraySize];

示例:
         下列定义了一个数组类型,定义了int型的大小为5的一个数组类型,可以利用这个数组类型定义实际的数组;iArray就是该数组类型的一个实例;

typedef int(AINT5)[5];

AINT5 iArray;

数组指针:
        数组指针是一个指针,指向数组;也就是数组指针是指向一个数组的;
        注:数组名是数组首元素的其实地址,但是并不是数组的起始地址;

数组指针与普通指针的区别:
        1、普通指针:int *p = arr;
   在这种定义中,p是一个int型的指针,而且p指向的是数组的第一个元素位置;

        2、数组指针:int (*p)[9] = &arr;
  在这种定义中,p是一个指向整个数组arr的指针;
方式通常用于传递整个数组到函数,或者当数组本身作为一个单元需要被处理时使用。
图解:

代码示例:

int arr[9] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
int (*p)[9] = &arr;

// 访问数组元素
for (int i = 0; i < 9; i++) {
    printf("%d ", (*p)[i]);
}

        这里,(*p)[i] 实际上是解引用整个数组指针,然后访问第 i 个元素;


指针数组:
        指针数组是一个普通的数组;指针数组的每一个元素都是一个指针;
        指针数组的 定义方法:type *arrayName[arraySize];
示例:

int *ptrArray[5]; // 可以存储5个int类型的指针

指针数组的常见初始化:

int a = 10, b = 20, c = 30, d = 40, e = 50;
int *ptrArray[5] = {&a, &b, &c, &d, &e};

示例:

#include <stdio.h>

int main() {
    // 定义一个指针数组,用于存储字符串
    char *strArray[3] = {"Hello", "World", "C Language"};

    // 定义一个二维数组
    int numbers[2][3] = {
        {1, 2, 3},
        {4, 5, 6}
    };

    // 定义一个数组指针,指向二维数组的第一行
    int (*ptrToArray)[3] = numbers;

    // 使用指针数组打印字符串
    for (int i = 0; i < 3; i++) {
        printf("%s\n", strArray[i]);
    }

    // 使用数组指针遍历二维数组并打印其内容
    printf("Two-dimensional array elements:\n");
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%d ", ptrToArray[i][j]);
        }
        printf("\n");
    }

    return 0;
}

多维数组和多级指针:

多级指针:多级指针(也称为多重指针或指针的指针)是一种指针变量,它指向另一个指针;多级指针提供了一个额外的间接层级,这允许程序不仅控制数据本身,还控制那些指向数据的指针。

单级指针:是一个指针变量,存储的是另一个变量的地址,也就是这个指针指向该变量;
二级指针:是一个指针变量,存储的是一个指针变量的地址,即它指向另一个指针;
更高级别的指针:例如三级指针,指向另一个二级指针的地址,依此类推。

定义方法:
二级指针:int **ptr;
三级指针:int ***ptr;
使用实例:

#include <stdio.h>

int main() {
    int value = 5;
    int *ptr1 = &value;
    int **ptr2 = &ptr1;

    printf("Original value: %d\n", value);
    printf("Access using single pointer: %d\n", *ptr1);
    printf("Access using double pointer: %d\n", **ptr2);

    // 修改值
    **ptr2 = 10;
    printf("New value after modification: %d\n", value);

    return 0;
}

多级指针的作用和用途:
        1、动态内存管理;
       
2、修改函数外部的指针;
       
3、高级数据结构。

下列代码实现的功能是利用二级指针,修改函数外部的指针,指针变量作为参数传入函数,在函数中用一个二级指针来接收并操作。

#include <stdio.h>
#include <stdlib.h>

void modifyPointer(int **p) {
    // 为指针分配新的内存,并验证是否成功
    *p = (int *)malloc(sizeof(int));
    if (*p == NULL) {
        printf("Memory allocation failed\n");
        exit(1); // 如果内存分配失败,退出程序
    }
    **p = 20; // 设置新分配内存的值
}

int main() {
    int *ptr = NULL; // 初始化指针为NULL
    printf("Initial value of pointer: %p\n", (void *)ptr);

    // 调用函数,尝试修改指针
    modifyPointer(&ptr);
    printf("New value of pointer: %p\n", (void *)ptr);
    if (ptr != NULL) {
        printf("Value at new address: %d\n", *ptr);
    }

    // 释放分配的内存
    free(ptr);

    return 0;
}

二维数组:

        二维数组在内存中是以一维的方式排布的。在c语言中,二维数组通常是以行主序存储,这代表第一行的元素先存储,然后是第二行的元素,以此类推。
        二维数组中的第一维是一维数组,第二维才是具体的值,二维数组的数组名可以看做常量指针。

二维数组的定义方法:

type arrayName[rows][columns]; //rows 数组的行数  columns数组的列数

示例:

int matrix[3][4] = {
    {1, 2, 3, 4},   // 第一行
    {5, 6, 7, 8},   // 第二行
    {9, 10, 11, 12} // 第三行
};

二维数组的数组名作用:

        二维数组的数组名与一维数组的数组名具备相同点,但是又有差别。一维数组的数组名是一个指针,指向数组中的第一个元素;同理,二维数组的数组名也可以视为一个数组指针,指向第一行数组的地址。因为,二维数组在内存中是以一维数组的存储方式存储的。二维数组名可以参与指针运算:例如,对于一个名为 arr 的二维数组,arr + 1 将指向第二行的起始地址,因为 arr 本身被视为指向第一行的指针。

示例:

int matrix[2][3] = {{1,2,3},{4,5,6}};

数组名在使用的时候:
matrix可以获取到整个数组的地址;
*matrix可以访问第一行;
**matrix可以访问第一个元素,即1;

示例:下列代码中。a可以理解为一个数组指针,p也是一个数组指针;这里试图将p指向a。尽管 a 在表达式中退化为指向其第一行的指针(类型为 int (*)[5]),但是尝试将类型为 int (*)[5] 的指针赋值给类型为 int (*)[4] 的指针 p 是类型不兼容的。这种类型不匹配在C语言中是不允许的,因为它会导致内存访问错误,可能会引起程序运行时错误。所以输出的结果是错误的-4;

#include <stdio.h>

int main()
{
    int a[5][5];
    int (*p)[4];
    
    p = a;

    printf("%d\n", &p[4][2] - &a[4][2]);
}

示例:综合二维数组和二级指针,在堆空间上申请一个二维数组的空间:

#include <stdio.h>
#include <stdlib.h>

int main() {
    int rows = 3;     // 行数
    int cols = 4;     // 列数
    int i, j;

    // 使用指针数组申请二维数组的空间
    int **array = (int **)malloc(rows * sizeof(int *));
    if (array == NULL) {
        fprintf(stderr, "Memory allocation failed!\n");
        return 1;
    }

    for (i = 0; i < rows; i++) {
        array[i] = (int *)malloc(cols * sizeof(int));
        if (array[i] == NULL) {
            fprintf(stderr, "Memory allocation failed for row %d\n", i);
            // 释放已经分配的所有行
            for (j = 0; j < i; j++) {
                free(array[j]);
            }
            free(array);
            return 1;
        }
    }

    // 初始化二维数组
    for (i = 0; i < rows; i++) {
        for (j = 0; j < cols; j++) {
            array[i][j] = i * cols + j; // 用一些值初始化数组
        }
    }

    // 打印二维数组
    printf("The 2D array is:\n");
    for (i = 0; i < rows; i++) {
        for (j = 0; j < cols; j++) {
            printf("%d ", array[i][j]);
        }
        printf("\n");
    }

    // 释放二维数组的内存
    for (i = 0; i < rows; i++) {
        free(array[i]); // 释放每行的内存
    }
    free(array); // 释放指针数组的内存

    return 0;
}

小结:

        c语言中只有一维数组,并且数组大小必须在编译期就作为常数确定;c语言中的数组袁术可以是任何类型的数据,即数组的元素可以是另一个数组;c语言中只有数组的大小和数组首元素的地址是编译器直接确定的。


数组参数与指针参数的分析:

        是什么参数?什么是形参和实参呢?
在c语言中,参数的定义是,在函数调用时传递给函数的值或者变量,用来传递信息给函数,便于函数进行操作。形参就是在声明或者定义函数时的参数,实参是在调用函数时传递过来的具体值或变量。

        参数的值传递和地址传递:
值传递:实际参数的值呗复制到形参中,于是在函数调用过程中不会更改实际参数的值,这时候形参和实参在内存中是完全独立的。对形参的计算不会更改实参的值;安全性高。但是复制整个数据可能会导致效率较低。
地址传递:实参的地址被传送到形参中;而形参实际上是实参的一个别名(一般是一个指向实参地址的指针),此时的形参和实参在内存中是一块地址,函数中任何对形参的改变都会影响到实参。允许函数直接修改外部变量,需要更加小心地处理形参。

        数组参数:
当函数的形参是一个数组的时候,数组就变成了参数,但是复制整个数组的传递方式会让程序变得繁琐,于是采用了将数组参数退化为指针参数的用法;因此在函数参数中使用数组时,通常传递的是数组的第一个元素的地址(即数组的指针)。这意味着当你传递一个数组给函数时,你实际上是传递了一个指向数组首元素的指针,而不是数组的所有元素的拷贝。

等价关系:

等价关系等效的指针参数
一维数组: float a[5]指针:float *a
指针数组: int *a[5]指针的指针: int **a
二维数组: char a[3][4]数组指针:char  (*a)[4]

注:C语言中无法向一个函数传递任意的多维数组;为了提供正确的指针运算,在函数的参数传递中,必须提供除了第一维之外的所有长度;


函数与指针:

        1、c语言中的函数有自己特定的类型;
        2、函数的类型由返回值参数类型参数个数共同决定;
        3、c语言中可以通过typedef关键字为函数类型重命名。

函数指针:
        1、函数指针用于指向一个函数;
        2、函数名是执行函数体的入口地址;
        3、可以通过函数类型定义函数指针。

函数指针的定义方法:return_type (*pointer_name)(parameter_list);

  • return_type 是函数返回的数据类型。
  • pointer_name 是指针的名称。
  • parameter_list 是函数接受的参数类型列表,与直接声明函数的参数列表相同

示例: 函数指针在代码中,是被定义来指向一个已经存在的函数的入口地址的。

#include <stdio.h>

int add(int a, int b) {
    return a + b;
}

int main() {
    // 定义一个函数指针并初始化
    int (*func_ptr)(int, int) = add;

    // 使用函数指针调用函数
    int result = func_ptr(3, 5);
    printf("Result: %d\n", result);

    return 0;
}

回调函数:

        函数指针是一个指针,指向一个函数的入口地址。所以函数指针允许在编程的时候将函数作为参数传递给其他函数。这就是回调函数的由来,这样就允许用户项函数传递一些特定的行为,这些行为在回调函数中被定义。

回调函数的编写流程:
        1、定义回调函数:首先定义一个函数,这个函数将作为回调函数被调用。这个函数要满足特定的返回类型和参数列表;
        2、传递函数指针:将上述这个函数的函数指针(函数入口地址)传递给另一个函数;这个另一个函数会在函数体的执行中执行回调操作;
        3、触发回调:当达到执行回调的条件或者阶段时,库函数或者框架通过传递的函数指针调用回调函数。

回调函数示例:

#include <stdio.h>

void myCallback(int data)
{
    printf(""Called back with %d\n", data);
}

void performAction(void (*callback)(int))
{
    printf("Performing an action...\n");
    if(callback)
    {
         callback(100);    //调用回调函数
    }
}

int main() {
    performAction(myCallback);  // 将回调函数传递给另一个函数
    return 0;
}

函数指针数组:

        函数指针数组是一个数组,数组中的元素都是函数指针。每个元素都是一个指向一个函数的指针,这些被指向的函数都具有相同的返回类型和参数列表。

        函数指针数组的定义方法:return_type (*array_name[array_size])(parameter_types);

  • return_type 是所有函数的返回类型。
  • array_name 是数组的名称。
  • array_size 是数组中函数指针的数量。
  • parameter_types 是这些函数接受的参数类型。

示例:

#include <stdio.h>

int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

int multiply(int a, int b) {
    return a * b;
}

int main() {
    // 定义一个包含三个元素的函数指针数组
    int (*operations[3])(int, int) = {add, subtract, multiply};

    // 调用数组中的第一个函数(add)
    int result1 = operations[0](10, 5);
    printf("Add: %d\n", result1);

    // 调用数组中的第二个函数(subtract)
    int result2 = operations[1](10, 5);
    printf("Subtract: %d\n", result2);

    // 调用数组中的第三个函数(multiply)
    int result3 = operations[2](10, 5);
    printf("Multiply: %d\n", result3);

    return 0;
}

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值