c语言---指针篇

目录

1.c指针

1.1 c指针的定义

1.2 c指针作用

1.2.1 直接访问内存:

1.2.2 动态的内存分配:

1.calloc函数

2.malloc函数

3.realloc函数

4.free函数

1.2.3 数组和字符串处理(指针指向数组)

1.2.4 指针作为函数参数传递(改变参数值)

1.2.5 单向链表(使用指针)

1.2.6 多级指针(二级指针)

1.2.7 提高效率

1.3 补充:指针函数和函数指针的区别

1.3.1 指针函数:

1.3.2 函数指针:


1.c指针

C语言中的指针是一项强大而灵活的特性,能极大地提升程序的性能和灵活性。篇初,我声明c指针的主要作用,也就是为什么我们要用指针,在什么情况下使用,接下来我会按照作用进行代码示例和解析。下图是此篇学习的思维导图。

正篇开始-------------------------------------------------------------------------------------------------------------------

1.1 c指针的定义

指针就是内存地址,我们所说的指针变量也就是存放内存地址的变量,变量有int,float等等类型,那我们的指针变量也需要不同的类型来存放内存地址变量。下图是四种类型指针的声明:

int    *ip;    /* 一个整型的指针 */
double *dp;    /* 一个 double 型的指针 */
float  *fp;    /* 一个浮点型的指针 */
char   *ch;    /* 一个字符型的指针 */

下面一个int类型指针的声明和输出。

#include <stdio.h>

int main() {
    // 定义一个int类型的变量
    int num = 42;

    // 定义一个int类型的指针变量,并指向num
    int *ptr = &num;

    // 输出指针ptr的值(地址)和它所指向的值
    printf("指针ptr的地址: %p\n", ptr); 
    printf("变量num的地址: %p\n", &num); 
    printf("ptr所指向的值: %d\n", *ptr);
  

    return 0;
}

在c语言中,指针的占位符用“%p”,上图我们定义了一个int类型的num变量并赋值42,接下来我们定义一个int 类型的指针变量ptr,通过“&”符号取到变量num的地址并将地址赋给指针变量ptr。

上面的示例中,三个输出分别展示了,指针指向的地址,变量num的地址,以及通过“*”取值的输出。

1.2 c指针作用

1. 直接访问内存:指针让你如同掌控一把钥匙,能够直接锁定内存地址,从而实现低级别的内存操作。

2. 动态内存分配:使用指针,你可以在运行时灵活地申请和释放内存,就像在自家花园中随意种植和剪除植物一样。通过 malloc和 free 等函数,你能够根据需要管理内存,避免资源浪费。

3. 数组和字符串处理:指针使得遍历数组和处理字符串的过程更为高效。想象一下,指针就像一条快速的通道,让你迅速访问每一个元素,而无需逐一复制。

4. 函数参数传递(将指针作为函数参数进行传递):指针让函数可以直接对变量进行操作,而不是简单地传递值。就好比你传递了一张地图,指向了真实的目的地,这样函数可以在原地进行改变,而不必返回一份副本。

5. 构建数据结构:指针是实现复杂数据结构(如链表、树和图)的基石。它们就像是连接不同节点的桥梁,使得数据在内存中灵活流动,构建出复杂的关系网络。

6. 多级指针:C语言的多级指针让你能够构建更复杂的数据结构,甚至可以创建指向指针的指针。这种能力为你提供了更多的灵活性,适用于更复杂的场景。

7. 提高效率:指针能有效减少数据复制,尤其在处理大数据时,显著提升程序的运行效率。想象你在厨房里,使用指针就像是直接操作食材,而不是每次都准备新的份量,节省了时间和空间。

1.2.1 直接访问内存:

c语言是面向过程的编程语言,涉及到对于内存的操作时候,在c语言,我们访问内存的方式就是通过指针,那么指针式如何直接访问内存的,下面是一个指针定义(int * p)以及访问内存的简单示例:

int a = 10;
int *p = &a; // p指向a的地址
printf("%d\n", *p); // 输出10,直接访问a的值

通过示例,我们了解可以通过指针进行内存的访问,访问内存的步骤第一步声明一个指针(p),并指向要访问的变量地址(&a),最后通过*对指针进行值的访问。

1.2.2 动态的内存分配:

内存分配在c语言中也叫动态管理,在进行动态内存分配时要引入头文件#include<stdlib.h>

<stdlib.h>头文件中,提供以下四个函数为我们进行内存操作时使用。

1.calloc函数

calloc 函数用于分配一块内存,返回指向这块内存的指针,并将这块内存初始化为零。

下面我举例一个使用calloc分配内存,解释各参数的含义。这里需要明白:在C语言中,使用动态内存分配函数(如 callocmalloc)可以创建指针类型的内存块。虽然 arr 是一个指针,但由于它指向了一系列连续的内存位置,您可以像访问数组一样使用下标语法 arr[i] 来访问这些内存位置

int *arr = (int *)calloc(5, sizeof(int));代表分配一个可以存放5个整数内存块,这段代码的作用是通过calloc分配了一块可以存5个int类型元素的内存,int类型的字节数是4byte,所以通过该代码为内存分配了20个字节。(int *) 作用是将 calloc 函数返回的 void * 类型指针转换为 int * ,保持指针指向内存的数据类型一致。

需要注意的是:calloc 不仅分配内存,还会将分配的内存区域初始化为零。


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

int main() {
    int num = 5;
    int *arr = (int *)calloc(num, sizeof(int)); // 分配5个int整型空间

    if (arr == NULL) {
        fprintf(stderr, "内存分配失败\n");
        return 1;
    }

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

    free(arr); // 释放内存
    return 0;
}



2.malloc函数

该函数用于分配指定大小的内存块,但不会初始化。返回指向内存的指针。malloc 函数的参数需要用 * 号连接

// void *malloc(int num)

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

int main() {
    int num = 5;
    int *arr = (int *)malloc(num * sizeof(int)); // 分配5个整型空间

    if (arr == NULL) {
        fprintf(stderr, "内存分配失败\n");
        return 1;
    }

    for (int i = 0; i < num; i++) {
        printf("arr[%d] = %d\n", i, arr[i]); // 值是未知的
    }

    free(arr); // 释放内存
    return 0;
}
3.realloc函数

realloc函数用于重新分配内存,可以增加或减少已分配的内存块大小


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

int main() {
    int num = 5;
    int *arr = (int *)malloc(num * sizeof(int)); // 初始分配5个整型空间

    for (int i = 0; i < num; i++) {
        arr[i] = i + 1; // 初始化数组
    }

    int newNum = 10;
    arr = (int *)realloc(arr, newNum * sizeof(int)); // 扩展到10个整型空间

    if (arr == NULL) {
        fprintf(stderr, "内存重新分配失败\n");
        return 1;
    }

    for (int i = 0; i < newNum; i++) {
        printf("arr[%d] = %d\n", i, arr[i]); // 新分配的部分内容未定义
    }

    free(arr); // 释放内存
    return 0;
}

4.free函数

在上面的示例中,我们已经使用free(arr);来释放分配的内存。 使用 `free` 释放动态分配的内存,以防止内存泄漏。

1.2.3 数组和字符串处理(指针指向数组)

在C语言中,字符串实际上是字符数组,可以通过指针来实现字符串的操作和遍历,可以通过解引用来访问字符,例如 *ptr,并通过指针递增来遍历字符。(我们也可以通过字符串索引访问元素)。

#include <stdio.h>

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

    // 使用指针遍历字符串并打印每个字符
    while (*ptr != '\0') { // 当指针指向的字符不是 '\0'
        printf("%c ", *ptr); // 打印当前字符
        ptr++; // 移动指针到下一个字符
    }
    printf("\n");

    return 0;
}

在 C 语言中,数组名可以被视为指向数组第一个元素的指针。这是理解数组和指针之间关系的关键。 

  • 数组名本身并不是一个指针,但它在表达式中通常会被视为指向数组第一个元素的指针。
  • 在 C 语言中,数组名(如 arr)代表了数组的首地址。当你将数组作为参数传递给函数时,传递的是指向数组第一个元素的指针。这意味着在函数内部,你可以使用指针算术或数组下标来访问数组的元素(在1.2.7里有提到)
#include <stdio.h>

int main() {
    // 定义一个整型数组
    int arr[] = {10, 20, 30, 40, 50};

    // 使用数组名访问第一个元素
    printf("First element using array name: %d\n", arr[0]);

    // 使用指针访问第一个元素
    printf("First element using pointer: %d\n", *arr); // arr 被当作指向第一个元素的指针

    // 遍历数组
    for (int i = 0; i < 5; i++) {
        // 使用数组名和指针形式访问元素
        printf("Element %d: %d\n", i, *(arr + i)); // arr+i 是指向第 i 个元素的指针
    }

    return 0;
}

1.2.4 指针作为函数参数传递(改变参数值)

这里(*p)++涉及到一个解自用和自增的操作,具体如下解释:

  1. (*p) 通过操作符 * 获取 p 指向的地址中的值。在这个上下文中,(*p) 实际上就是 number 的值。
  2. ++ 是自增运算符,它会将 (*p) 的值增加 1。在这里,(*p)++ 有两种操作:    (*p) 会被解引用,取得 number 的当前值(例如,5)。然后,++ 操作符会将这个值增加 1,变成 6,并把新的值写回到 number 的内存地址。
#include <stdio.h>

// 函数声明,接受一个int类型的指针作为参数
void increment(int *p) {
    // 通过指针修改原始变量的值
    (*p)++;
}

int main() {
    int number = 5;

    printf("Before increment: %d\n", number); // 输出原始值

    // 将number的地址传递给increment函数
    increment(&number);

    printf("After increment: %d\n", number); // 输出修改后的值

    return 0;
}

1.2.5 单向链表(使用指针)

链表由多个节点组成,每个节点包含数据和一个指向下一个节点的指针。这里的代码示例会使用到结构体,代码结构稍微复杂。

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

// 定义链表节点结构
struct Node {
    int data;              // 节点的数据部分
    struct Node* next;     // 指向下一个节点的指针
};

// 在链表末尾添加新节点的函数
void appendNode(struct Node** head_ref, int new_data) {
    struct Node* new_node = (struct Node*)malloc(sizeof(struct Node)); // 分配新节点的内存
    struct Node* last = *head_ref; // 用于遍历链表

    new_node->data = new_data; // 设置新节点的数据为传入参数
    new_node->next = NULL; // 新节点的下一个指针初始化为 NULL(表示它是最后一个节点)

    // 如果链表为空,则将新节点设置为头节点
    if (*head_ref == NULL) {
        *head_ref = new_node; // 将新节点赋值给链表的头指针
        return; // 结束函数
    }

    // 否则,找到链表的最后一个节点
    while (last->next != NULL) { // 遍历直到找到最后一个节点
        last = last->next; // 移动到下一个节点
    }

    // 将最后一个节点的 next 指针指向新节点
    last->next = new_node; // 链接新节点
}

// 打印链表内容的函数
void printList(struct Node* node) {
    while (node != NULL) { // 当当前节点不为空时
        printf("%d -> ", node->data); // 打印当前节点的数据
        node = node->next; // 移动到下一个节点
    }
    printf("NULL\n"); // 打印链表结束标志
}

// 主函数
int main() {
    struct Node* head = NULL; // 初始化链表的头指针为 NULL,表示链表为空

    // 向链表中添加节点
    appendNode(&head, 1); // 添加节点数据为 1
    appendNode(&head, 2); // 添加节点数据为 2
    appendNode(&head, 3); // 添加节点数据为 3
    appendNode(&head, 4); // 添加节点数据为 4

    // 打印链表
    printf("链表内容: ");
    printList(head); // 输出: 1 -> 2 -> 3 -> 4 -> NULL

    // 释放链表内存
    struct Node* current = head; // 当前节点初始化为头节点
    struct Node* next_node; // 下一个节点指针

    while (current != NULL) { // 当当前节点不为空时
        next_node = current->next; // 保存下一个节点的指针
        free(current); // 释放当前节点的内存
        current = next_node; // 移动到下一个节点
    }

    return 0; // 程序结束
}

1.2.6 多级指针(二级指针)

我们这里以二级指针举例,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时(二级指针),第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。

使用双指针可以方便地创建动态的二维数组。例如,假设你需要一个不规则的矩阵,可以通过多级指针来实现:

row代表行,cols代表列,int **matrix: 这是一个指向指针的指针(二级指针),也就是一个二维数组的类型。matrix 是一个数组,包含 rows 个指针,每个指针指向一个包含 cols 个整数的数组。matrix 用来存储指向每一行的指针

#include <stdio.h>   // 引入标准输入输出库
#include <stdlib.h>  // 引入标准库以使用malloc和free函数

int main() {
    int rows = 3;   // 定义矩阵的行数
    int cols = 4;   // 定义矩阵的列数

    int **matrix = (int **)malloc(rows * sizeof(int *));  // 这行代码分配了一个指针数组 (大小为 rows),其中每个元素都是一个指向整数数组的指针,连续内存。

    for (int i = 0; i < rows; i++) {
        matrix[i] = (int *)malloc(cols * sizeof(int));  // 为每一行分配内存,分配4个int类型的内存
    }

    for (int i = 0; i < rows; i++) {  // 填充矩阵
        for (int j = 0; j < cols; j++) {
            matrix[i][j] = i * cols + j;  // 生成连续数字
        }
    }

    for (int i = 0; i < rows; i++) {  // 打印矩阵
        for (int j = 0; j < cols; j++) {
            printf("%d ", matrix[i][j]);  // 输出每个元素
        }
        printf("\n");  // 每行结束后换行
    }

    for (int i = 0; i < rows; i++) {
        free(matrix[i]);  // 释放每一行的内存
    }
    free(matrix);  // 释放存储行指针的总指针

    return 0;  // 返回0表示程序成功结束
}

1.2.7 提高效率

#include <stdio.h>

void processArray(int* arr, int size) {
    for (int i = 0; i < size; i++) {
        arr[i] *= 2; // 对数组中的每个元素乘以2
    }
}

int main() {
    int size = 1000000;
    int arr[size]; // 在堆栈上定义一个数组

    // 初始化数组
    for (int i = 0; i < size; i++) {
        arr[i] = i;
    }

    // 只需要传递 arr,不需要取地址
    processArray(arr, size); // 将 arr 作为参数传递

    // 输出部分结果以验证
    for (int i = 0; i < 5; i++) {
        printf("%d ", arr[i]); // 输出前5个元素
    }

    return 0;
}

当你将一个大型数组直接作为参数传递给函数时,如果不使用指针,编译器会复制整个数组。这意味着内存中的每个元素都需要被逐一复制到新创建的数组中,这在处理大数组时会导致显著的性能开销。

1.3 补充:指针函数和函数指针的区别

这里做一个指针函数和函数指针的说明,本来应该将指针函数放入c函数来讲的,这里讲到了指针方便区分。

最简单的辨别方式就是看函数名前面的指针*号有没有被括号( )包含,如果被包含就是函数指针反之则是指针函数。

1.3.1 指针函数:

本质是一个函数,此函数返回某一类型的指针。

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

// 定义一个指针函数,它返回一个整型指针
int* createArray(int size) {
    int* arr = (int*)malloc(size * sizeof(int)); // 动态分配内存
    for (int i = 0; i < size; i++) {
        arr[i] = i + 1; // 填充数组
    }
    return arr; // 返回数组指针
}

int main() {
    int size = 5;
    int* array = createArray(size); // 调用指针函数

    // 打印数组内容
    for (int i = 0; i < size; i++) {
        printf("%d ", array[i]);
    }
    printf("\n");

    free(array); // 释放动态分配的内存
    return 0;
}

这里释放内存需要注意:

使用 free(array); 是正确的,因为 array 持有有效的动态内存地址。free(arr); 是不正确的,因为 arr 的作用域已经结束,无法安全地访问。

1.3.2 函数指针:

本质是一个指针,指向函数的指针变量,其包含了函数的地址,通过它来调用函数。 它是指向函数的指针变量,即本质是一个指针变量

#include <stdio.h>

// 定义一个简单的加法函数
int add(int a, int b) {
    return a + b;
}

// 定义一个简单的减法函数
int subtract(int a, int b) {
    return a - b;
}

// 主函数
int main() {
    // 定义一个函数指针,指向接受两个整数并返回整数的函数
    int (*operation)(int, int);
    
    operation = add; // 将函数指针指向加法函数
    printf("Add: %d\n", operation(5, 3)); // 调用加法函数

    operation = subtract; // 将函数指针指向减法函数
    printf("Subtract: %d\n", operation(5, 3)); // 调用减法函数

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值