哈尔滨无障碍网站建设建网站找谁
内容提要
-
指针
-
void与void*的区别
-
-
内存操作
-
综合案例:学生成绩管理系统2.0
回顾
二级指针:
指向另一个指针的指针。二级指针等效于指针数组,不等效于二维数组
常量指针:
语法:
const int* p;
特点:
-
指针的指向可变,指向的数据不可变
指针常量
语法:
int* const p;
特点:
-
指针的指向不可变,指向的数据可变
常量指针常量
语法:
const int* const p;
特点:
-
指针的指向不可变,指向的数据不可变
内存的动态分配:
-
malloc() 内存分配到堆区,需要初始化,清零使用memset或者realloc或者bzero(非标准库)
-
calloc() 内存分配到堆区,默认已经完成了初始化
-
realloc() 针对malloc、calloc、realloc分配的内存进行扩容,扩容后会保留原数据,空余位置需要初始化
-
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 = π// 必须进行显示类型的转换(保证类型安全)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;}// 初始化堆内存,填充0memset(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)
-
函数功能:比较
s1
和s2
的前n
个字节 -
返回值:
-
0
:内存内容相同 -
>0
:s1
中第一个不同字节大于s2
-
<0
:s1
中第一个不同字节小于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); // 1printf("最后1个B的位置:%ld\n",last - str); // 3return 0; }
综合案例:学生成绩管理系统
-
需求:
要求实现一个基于指针的学生成绩管理系统,具体功能如下:
-
添加学生信息:输入学号和三门成绩,存储到数组中
-
显示所有学生信息:遍历数组,输出每个学生的学号和成绩
-
计算每个学生的平均分和总分:遍历数组,计算每行的总分和平均分
-
根据某科成绩排序:用户选择科目,然后按该科成绩排序,可以升序或降序
-
查找学生信息:按学号查找,显示该生的成绩和平均分
-
退出程序。
-
-
代码:
#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; }