当前位置: 首页 > news >正文

C语言基础17

内容提要

  • 指针

    • void与void*的区别

  • 内存操作

  • 综合案例:学生成绩管理系统2.0

回顾

二级指针:

指向另一个指针的指针。二级指针等效于指针数组,不等效于二维数组

常量指针:

语法:

 const int* p;

特点:

  • 指针的指向可变,指向的数据不可变

指针常量

语法:

 int* const p;

特点:

  • 指针的指向不可变,指向的数据可变

常量指针常量

语法:

 const int* const p;

特点:

  • 指针的指向不可变,指向的数据不可变

内存的动态分配:
  1. malloc() 内存分配到堆区,需要初始化,清零使用memset或者realloc或者bzero(非标准库)

  2. calloc() 内存分配到堆区,默认已经完成了初始化

  3. realloc() 针对malloc、calloc、realloc分配的内存进行扩容,扩容后会保留原数据,空余位置需要初始化

  4. free() 释放分配的内存,也可以使用realloc(p,0)

野指针产生的场景:
  • 指针未初始化

  • 访问已经释放了的指针

  • 函数中返回局部变量的地址

  • 指针的越界访问

空指针:

值为NULL的指针,初始化的时候可以给指针赋值为NULL,内存释放后,需要对指针置空(NULL)

空悬指针:

野指针的一种特殊表现,指针指向的内存已经被释放,指针还未置空(NULL)

指针

void与void*的区别

定义
  • void:表示“无类型”,用于函数返回值或参数

     void func(void); // 不过一般简写为 void func();   无返回值无参数
  • void*:通用指针类型(万能指针),可指向任意数据类型,但需要强制类型转换后才能解引用

     void* ptr = malloc(4);
     // int* p = (int*)ptr;
     // *p = 10;
     ​
     float* p1 = (float*)ptr; // 强制类型转换
     *p = 20;
注意事项
  • void*不能直接解引用(如:*ptr会报错)

  • 函数返回void*需要外部接收的时候明确类型

示例
 #include <stdlib.h>
 ​
 /**
  * 定义一个返回值为void*类型的指针函数
  */
 void* process_data(void* p)
 {
     // 例如
     // 处理int类型时:return (int*)p;
     // 处理double类型时:return (double*)p;
     // 实际应用中需要配套数据类型表示参数
     return p; // 直接返回原指针
 }
 ​
 int main(int argc,char*argv[])
 {
     // 演示int类型处理
     int m = 10;
     int* p_int = &m;
     
     // 必须进行显示类型的转换(保证类型安全)
     int* result_int = (int*)process_data(p_int);
     *result_int = 20; // 安全操作
     printf("Integer value:%d\n",*result_int);
     
     // 演示double类型处理
     double pi = 3.1415926;
     double* p_double = &pi;
     
     // 必须进行显示类型的转换(保证类型安全)
     double* result_double = (double*)process_data(p_double);
     *result_double *= 2; // 安全操作
     printf("Double value:%.6f\n",*result_double);
     
     // void*指针的特殊使用注意事项
     void* generic_ptr = process_data(p_int);
     // *generic_ptr = 30; // 错误! void* 不能直接解引用(必须明确类型)
     // 正确的写法
     int* c_ptr = (int*)generic_ptr;
     *c_ptr = 30;
     printf("Converted value:%d\n",*c_ptr);
     
     return 0;
 }

内存操作

我们对于内存操作需要依赖于string.h头文件中相关的函数完成

内存操作函数

内存填充
memset
  • 头文件:#include <string.h>

  • 函数原型:void* memset(void* s,int c,size_t n);

  • 函数功能:将内存卡s的前n个字节填充为c

  • 参数说明:

    • void* s:目标内存首地址

    • int c:填充值(以unsigned char形式处理(0~255))

    • size_t n:填充字节数(size_t无符号长整型)

  • 返回值:返回s的指针

  • 注意事项:

    • 常用于动态化初始化,c通常设置为0(清零)

    • 按字节填充,非整型初始化需要谨慎(如填充int数组时,0是安全的)

  • 示例:

     #include <stdlib.h>
     #include <string.h>
     ​
     // 在堆区申请4个int的连续空间
     int* p = (int*)malloc(4 * sizeof(int)); // 建议写常量表达式
     // 非空校验(校验指针有效性)
     if(p == NULL)
     {
         perror("内存申请失败!");
         return -1;
     }
     ​
     // 初始化堆内存,填充0
     memset(p,0,4 * sizeof(int));
     ​
     // 测试输出
     printf("%d\n",*(p+1)); //*p = *(p+0) = p[0]
     ​
     // 内存使用完毕,需要释放
     free(p);
     ​
     // 此时要对指针p置空
     p = NULL; 
     ​
内存拷贝
memcpy/memmove
  • 头文件:#include <string.h>

  • 函数原型:

    • void* memcpy(void* dest, const void* src, size_t n):(源与目标内存无重叠时使用)

    • void* memmove(void* dest, const void* src, size_t n):(安全处理内存重叠)

  • 函数功能:将src的前n个字节拷贝到dest(从低位开始,因为指针指向从低到高)

  • 参数说明:

    • void* dest:目标内存首地址

    • const void* src:源内存首地址

    • size_t n:拷贝字节数

  • 返回值:返回dest的首地址

  • 注意事项:

    • memmove能正确处理内存重叠,推荐优先使用

    • 确保目标内存足够大,避免溢出

  • 示例:

     #include <string.h>
     ​
     // 准备两个数组,用来作为源和目标
     int src[4] = {11,22,33,44};
     int dest[6] = {111,222,333,444,555,666};
     ​
     // 进行拷贝
     memcpy(dest+1,src+1,2 * sizeof(int)); // 从src拷贝22,33到dest的222的位置
     memmove(dest+1,src+1,2 * sizeof(int)); // 从src拷贝22,33到dest的222的位置
     ​
     printf("源数组:\n");
     register int i;
     for(i = 0; i < 4; i++)
     {
         printf("%-6d",src[i]);
     }
     printf("\n");
     ​
     printf("目标数组:\n");
     for(i = 0; i < 6; i++)
     {
         printf("%-6d",dest[i]);
     }
     printf("\n");

课后思考:

什么是内存重叠?

进行内存操作时,源内存区域和目标内存区域存在部分或者完全重叠,如果不处理内存重叠,可能导致数据被意外覆盖,引发未定义行为。

内存比较
memcmp
  • 头文件:#include <string.h>

  • 函数原型:int memcmp(const void* s1, const void* s2, size_t n)

  • 函数功能:比较s1s2的前n个字节

  • 返回值:

    • 0:内存内容相同

    • >0s1中第一个不同字节大于s2

    • <0s1中第一个不同字节小于s2

  • 注意事项:比较按字节进行,非字符串需确保参与比较的字节数一致(总字节数一致)(不一致按小的来超出不比较)

  • 示例:

    #include <stdlib.h>
    #include <string.h>
    
    // 准备比较的数据
    int* arr1 = (int*)malloc(3 * sizeof(int));
    int* arr2 = (int*)calloc(4,sizeof(int));
    
    // 清零
    memset(arr1,0,3 * sizeof(int));
    
    // 赋值
    arr1[0] = 65; arr1[1] = 66;
    arr2[0] = 70; arr2[1] = 5;
    
    // 比较
    int cmp_result = memcmp(arr2,arr1, 2 * sizeof(int)); // 双方比较字节数完全一致
    
    printf("比较结果:%d\n",cmp_result); // 5  70-65
    
    // 内存使用完毕,释放内存
    free(arr1);
    free(arr2);
    
    // 如果后面要用到,返回到外面,需要置空;不用的话就不需要置空
    arr1 = NULL;
    arr2 = NULL;
内存查找
memchr/memrchr
  • 头文件:#include <string.h>

  • 函数原型:

    • void* memchr(const void* s, int c, size_t n):(正向查找)

    • void* memrchr(const void* s, int c, size_t n):(逆向查找,这个不是C语言标准库提供,属于GNU扩展)

  • 函数功能:在s的前n个字节中查找字符c

  • 返回值:

    • 成功:返回指针

    • 失败:返回NULL

  • 注意事项:

    • memrchr:是GNU扩展函数,需手动声明

    • 查找单位为字节值,非整型数据需要注意内存布局

  • 示例:

    #include <string.h>
    
    // memrchr是GNU扩展函数,需要外部声明
    extern void* memrchr(const void* s, int c, size_t n);
    
    int main(int argc, char*argv[])
    {
        char str[] = {'A','B','C','B'};
    
        // 查找字符'B'(ASCII 66)
        char* first = (char*)memchr(str, 'B', sizeof(str)); 
        char* last = (char*)memrchr(str, 'B', sizeof(str));
    
        printf("first=%p,last=%p\n",first,last);
    
        printf("第1个B的位置:%ld\n",first - str); // 1
        printf("最后1个B的位置:%ld\n",last - str); // 3
    
        return 0;
    }

综合案例:学生成绩管理系统

  • 需求:

    要求实现一个基于指针的学生成绩管理系统,具体功能如下:

    1. 添加学生信息:输入学号和三门成绩,存储到数组中

    2. 显示所有学生信息:遍历数组,输出每个学生的学号和成绩

    3. 计算每个学生的平均分和总分:遍历数组,计算每行的总分和平均分

    4. 根据某科成绩排序:用户选择科目,然后按该科成绩排序,可以升序或降序

    5. 查找学生信息:按学号查找,显示该生的成绩和平均分

    6. 退出程序。

  • 代码:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #define MAX_STUDENTS 50  // 最大学生数量
    #define COURSE_NUM 3     // 课程科目数量
    #define ID_LENGTH 4      // 学号长度
    
    /* 函数原型声明 */
    void addStudent(int (*scores)[COURSE_NUM], char (*ids)[ID_LENGTH], int *count);
    void displayAll(int (*scores)[COURSE_NUM], char (*ids)[ID_LENGTH], int count);
    void showStatistics(int (*scores)[COURSE_NUM], int count);
    void sortStudents(int (*scores)[COURSE_NUM], char (*ids)[ID_LENGTH], int count);
    void searchStudent(int (*scores)[COURSE_NUM], char (*ids)[ID_LENGTH], int count);
    int validateId(char *id);
    
    int main(int argc, char *argv[])
    {
        int choice;            // 用户菜单选择
        int studentCount = 0;  // 当前学生数量
    
        // 学生数据存储(二维数组)
        char studentIds[MAX_STUDENTS][ID_LENGTH];  // 学号数组
        int scores[MAX_STUDENTS][COURSE_NUM];      // 成绩二维数组
    
        do
        {
            // 系统菜单界面
            printf("\033[1;32m+----------------------------+\033[0m\n"); // 绿色边框
            printf("\033[1;32m| \033[1;33m学生成绩管理系统 v2.0\033[1;32m |\033[0m\n");
            printf("\033[1;32m| \033[1;33m作者:momo \033[1;32m |\033[0m\n");
            printf("\033[1;32m+----------------------------+\033[0m\n");
            printf("\033[1;32m1. 添加学生信息\033[0m\n");
            printf("\033[1;32m2. 显示所有记录\033[0m\n");
            printf("\033[1;32m3. 查看统计信息\033[0m\n");
            printf("\033[1;32m4. 成绩排序\033[0m\n");
            printf("\033[1;32m5. 查找学生\033[0m\n");
            printf("\033[1;32m6. 退出系统\033[0m\n");
            printf("\n请输入您的选择:");
            scanf("%d",&choice);
    
            switch(choice)
            {
                case 1: // 添加学生信息
                    addStudent(scores, studentIds, &studentCount);
                    break;
                case 2: // 显示所有记录
                    displayAll(scores, studentIds, studentCount);
                    break;
                case 3: // 查看统计信息
                    showStatistics(scores, studentCount);
                    break;
                case 4: // 成绩排序
                    sortStudents(scores, studentIds, studentCount);
                    break;
                case 5: // 查找学生
                    searchStudent(scores, studentIds, studentCount);
                    break;
                case 6: // 退出系统
                    printf("系统已退出,感谢使用!\n");
                    break;
                default: 
                    printf("无效输入,请重新选择!");
            }
        }while(choice != 6);
    
        return 0;
    }
    
    /**
     * 添加学生信息
     * @param scores  学生成绩二维数组
     * @param ids     学生编号二维数组
     * @param count   当前学生数量
     */
    void addStudent(int (*scores)[COURSE_NUM], char (*ids)[ID_LENGTH], int *count)
    {
        // 校验存储空间是否已满
        if(*count >= MAX_STUDENTS)
        {
            printf("错误:存储空间已满!\n");
            return;
        }
    
        printf("\n--- 添加学生信息 ---\n");
        // 创建一个数组,用来存放学生学号
        char tempId[ID_LENGTH + 1]; // 控制台录入的字符串默认\0结尾
    
        // 学号验证
        do
        {
            printf("请输入4位学号:");
            scanf("%4s",tempId);
            // 清除输入缓冲区
            while(getchar() != '\n');
        }while(!validateId(tempId));
    
        // 检查学号是否已存在
        register int i;
        for(i = 0; i < *count; i++)
        {
            if(memcmp(ids[i],tempId,ID_LENGTH) == 0)
            {
                printf("错误:该学号已存在!\n");
                return;
            }
        }
    
        // 存储学号
        memcpy(ids[*count], tempId, ID_LENGTH);
    
        // 输入成绩
        printf("请输入%d门课程成绩(0~100):\n",COURSE_NUM);
    
        for(i = 0; i < COURSE_NUM;)
        {
            printf("课程%d:", i+1);
            // 非法字符校验
            int tempScore = scanf("%d",&scores[*count][i]);
            if(tempScore != 1)
            {
                printf("错误:成绩无效,请重新输入!\n");
                // 清空缓冲区
                while(getchar() != '\n');
                continue;
            }
            // 输入范围校验
            if(scores[*count][i] < 0 || scores[*count][i] > 100)
            {
                printf("错误:成绩无效,请重新输入!\n");
                continue;
            }
            i++;
        }
    
        // 改变studentCount的值
        (*count)++;
    
        printf("学生信息添加成功!\n");
    }
    
    /**
     * 显示所有记录
     * @param scores 学生成绩二维数组
     * @param ids    学生学号二维数组
     * @param count  已录入学生人数
     */
    void displayAll(int (*scores)[COURSE_NUM], char (*ids)[ID_LENGTH], int count)
    {
        printf("\n--- 学生成绩列表 ---\n");
        // 校验是否存在数据
        if(count == 0)
        {
            printf("暂无学生数据!\n");
            return;
        }
        // 表格-表头
        printf("%-8s %-6s %-6s %-6s\n","学号","语文","数学","英语");
        // 表格-数据
        for(int i = 0; i < count; i++)
        {
            printf("%-8.4s",ids[i]); // 学号
            // 成绩
            for(int j = 0; j < COURSE_NUM; j++)
            {
                printf("%-6d",*(*(scores + i) + j)); // scores[i][j]
            }
            printf("\n");
        }
        printf("\n");
    }
    
    /**
     * 显示统计信息
     * @param scores 成绩二维数组
     * @param count  已录入学生数量
     */
    void showStatistics(int (*scores)[COURSE_NUM], int count)
    {
        // 校验是否存在学生
        if(count == 0)
        {
            printf("暂无学生数据!\n");
            return;
        }
    
        //创建一个数组,用来存储每一科总分
        int courseTotal[COURSE_NUM] = {0};
        // 创建一个数组,用来存储每一科最高分
        int max[COURSE_NUM] = {0};
        // 创建一个数组,用来存储每一科最低分
        int min[COURSE_NUM] = {100,100,100};
    
        // 遍历成绩,计算每一科的总分,最高分,最低分
        for(int i = 0; i < count; i++)
        {
            for(int j = 0; j < COURSE_NUM; j++)
            {
                // 获取成绩
                int score = scores[i][j];
                // 单科总分
                courseTotal[j] += score;
                // 单科最高分
                if(score > max[i]) max[j] = score;
                // 单科最低分
                if(score < min[j]) min[j] = score;
            }
        }
    
        // 输出信息
        printf("\n--- 课程统计信息 ---\n");
        char *courses[] = {"语文","数学","英语"};
        for(int i = 0; i < COURSE_NUM; i++)
        {
            printf("%s:\n",courses[i]); // 科目
            printf("  平均分:%.2f\n",(float)courseTotal[i] / count);
            printf("  最高分:%d\n",max[i]);
            printf("  最低分:%d\n",min[i]);
        }
    }
    
    /**
     * 成绩排序
     * @param scores 学生成绩二维数组
     * @param ids    学生学号二维数组
     * @param count  已录入学生人数
     */
    void sortStudents(int (*scores)[COURSE_NUM], char (*ids)[ID_LENGTH], int count) 
    {
        if(count < 2) 
        {
            printf("需要至少2条记录才能排序!\n");
            return;
        }
    
        int course;
        printf("\n请选择排序科目(1-语文 2-数学 3-英语): ");
        scanf("%d", &course);
        if(course < 1 || course > 3) 
        {
            printf("无效的科目选择!\n");
            return;
        }
        course--; // 转换为数组索引
    
        // 使用改进的冒泡排序
        for(int i = 0; i < count-1; i++) 
        {
            int swapped = 0;
            for(int j = 0; j < count-1-i; j++) 
            {
                if(scores[j][course] < scores[j+1][course]) { // 降序排列
                    // 交换学号
                    char tempId[ID_LENGTH];
                    memcpy(tempId, ids[j], ID_LENGTH);
                    memcpy(ids[j], ids[j+1], ID_LENGTH);
                    memcpy(ids[j+1], tempId, ID_LENGTH);
    
                    // 交换成绩
                    int tempScores[COURSE_NUM];
                    memcpy(tempScores, scores[j], sizeof(tempScores));
                    memcpy(scores[j], scores[j+1], sizeof(scores[j][0]) * COURSE_NUM);
                    memcpy(scores[j+1], tempScores, sizeof(tempScores));
    
                    swapped = 1;
                }
            }
            if(!swapped) break;
        }
        printf("排序完成!\n");
    }
    
    /**
     * 学生查找
     * @param scores 学生成绩二维数组
     * @param ids 学生学号二维数组
     * @param count 已添加学生数量
     */
    void searchStudent(int (*scores)[COURSE_NUM], char (*ids)[ID_LENGTH], int count) 
    {
        char targetId[ID_LENGTH + 1];
        printf("\n请输入要查找的学号: ");
        scanf("%4s", targetId);
        while (getchar() != '\n'); // 清除输入缓冲区
    
        for(int i = 0; i < count; i++) 
        {
            if(memcmp(ids[i], targetId, ID_LENGTH) == 0) 
            {
                printf("找到学生记录:\n");
                printf("学号: %.4s\n", ids[i]);
                printf("成绩: 语文=%d 数学=%d 英语=%d\n",scores[i][0], scores[i][1], scores[i][2]);
                return;
            }
        }
        printf("未找到该学号的学生记录!\n");
    }
    
    /**
     * 学号校验
     * @param id  校验的学号(数组)
     * @return 1-校验合格,0-校验不合格
     */
    int validateId(char *id)
    {
        char *p = id; // 不要直接操作形参,最好使用局部变量接收
        int len = 0;
    
        // 遍历字符串
        while(*p && len < ID_LENGTH)
        {
            // 校验输入的是否是数字
            if(!(*p >= '0' && *p <= '9'))
            {
                printf("学号必须为数字!\n");
                return 0;
            }
            p++;
            len++;
        }
    
        // 校验输入的长度
        if(len != ID_LENGTH || *p != '\0')
        {
            printf("学号必须为4位!\n");
            return 0;
        }
    
        return 1;
    }

相关文章:

  • ebpf: CO-RE, BTF, and Libbpf(一)
  • 我的第一个正式开源小项目:内网文件传输工具
  • 2025 年上海保安员职业资格考试深度剖析​
  • 大模型RAG项目实战-知识库问答助手v1版
  • PDF 中提取数学公式
  • 通过安装Windows 11英文版 解决组件问题解决中文版中无法修复组件的问题
  • AI前端组件库Ant DesIgn X
  • Cribl 新建Datatype
  • 人工智能 和 线性代数
  • Skyeye 云智能制造办公系统 - 云校园 VUE 版本 v3.15.16 发布
  • C++11QT复习 (十五)
  • Elixir语言的移动应用安全
  • 谈谈我所了解的hash
  • 哑铃图:让数据对比一目了然【Dumbbell Chart】
  • Java【多线程】(7)常见的锁策略
  • 【S32M244 RTD200P04 LLD篇8】S32M244 PWM ADC LLD demo
  • (蓝桥杯)动态规划蓝桥杯竞赛指南:动态规划解决最少钞票数问题(超详细解析+代码实现)
  • LabVIEW 开发如何降本增效
  • 数据库分表算法详解:原理、实现与最佳实践
  • FPGA状态机设计:流水灯实现、Modelsim仿真、HDLBits练习
  • 做企业网站需要建多大的画布/怎么在百度投放广告
  • 如何制造公司网址/惠州百度seo排名
  • 优秀设计方案网站/如何免费搭建自己的网站
  • 商城网站租服务器安全不/成都网站seo技术
  • 英文网站建设哪家强/域名注册流程
  • 学校网站报价方案/网站收录平台