C语言 基础语法学习Demo
示例一
#include <stdio.h>
#include <stdlib.h>
#include <string.h>typedef struct Student {int id;char name[50];float scores[3]; // 三门课程成绩float average;struct Student* next;
} Student;// 创建新学生
Student* createStudent(int id, const char* name, float scores[3]) {Student* newStudent = (Student*)malloc(sizeof(Student));newStudent->id = id;strcpy(newStudent->name, name);memcpy(newStudent->scores, scores, 3 * sizeof(float));// 计算平均分float sum = 0;for (int i = 0; i < 3; i++) {sum += scores[i];}newStudent->average = sum / 3;newStudent->next = NULL;return newStudent;
}// 添加学生到链表
void addStudent(Student** head, Student* newStudent) {if (*head == NULL) {*head = newStudent;return;}if (*head == NULL || newStudent->average > (*head)->average) {newStudent->next = *head;*head = newStudent;} else {Student* current = *head;while (current->next != NULL && current->next->average > newStudent->average) {current = current->next;}newStudent->next = current->next;current->next = newStudent;}
}// 显示所有学生
void displayStudents(Student* head) {printf("\n=== 学生信息表 ===\n");printf("ID\t姓名\t\t成绩1\t成绩2\t成绩3\t平均分\n");printf("------------------------------------------------\n");Student* current = head;while (current != NULL) {printf("%d\t%-10s\t%.1f\t%.1f\t%.1f\t%.2f\n",current->id, current->name,current->scores[0], current->scores[1], current->scores[2],current->average);current = current->next;}
}// 释放链表内存
void freeStudents(Student* head) {Student* current = head;while (current != NULL) {Student* next = current->next;free(current);current = next;}
}// 保存到文件
void saveToFile(Student* head, const char* filename) {FILE* file = fopen(filename, "w");if (file == NULL) {perror("文件保存失败");return;}Student* current = head;while (current != NULL) {fprintf(file, "%d,%s,%.1f,%.1f,%.1f\n",current->id, current->name,current->scores[0], current->scores[1], current->scores[2]);current = current->next;}fclose(file);printf("数据已保存到 %s\n", filename);
}int main() {printf("\n=== 学生管理系统实战 ===\n");Student* head = NULL;// 添加示例数据float scores1[3] = {85.5, 90.0, 78.5};float scores2[3] = {92.0, 88.5, 95.0};float scores3[3] = {76.0, 82.5, 79.0};addStudent(&head, createStudent(1, "张三", scores1));addStudent(&head, createStudent(2, "李四", scores2));addStudent(&head, createStudent(3, "王五", scores3));// 显示学生信息displayStudents(head);// 保存到文件saveToFile(head, "students.csv");// 释放内存freeStudents(head);return 0;
}
1. 为什么使用typedef?
typedef vs 结构体标签的区别:
不使用typedef:
struct Student { // Student是结构体标签int id;char name[50];
};// 使用时必须带struct关键字
struct Student s1;
struct Student* ptr;
使用typedef:
typedef struct Student {int id;char name[50];
} Student; // Student现在是类型别名// 使用时不需要struct关键字
Student s1;
Student* ptr;
为什么选择typedef?
代码简洁性:
Student s1;
vsstruct Student s1;
一致性:让结构体使用起来像内置类型
可读性:代码更清晰,特别是对于复杂的数据结构
向前兼容:如果需要修改结构体实现,只需修改typedef
2. 为什么使用memcpy?
memcpy的作用:
// 传统方法(不推荐)
for (int i = 0; i < 3; i++) {newStudent->scores[i] = scores[i];
}// 使用memcpy(推荐)
memcpy(newStudent->scores, scores, 3 * sizeof(float));
为什么选择memcpy?
性能:memcpy通常经过高度优化,比循环复制更快
简洁性:一行代码代替循环
可靠性:避免循环中的索引错误
可维护性:代码意图更明确
memcpy参数详解:
memcpy(目标地址, 源地址, 要复制的字节数);
// newStudent->scores: 目标数组
// scores: 源数组
// 3 * sizeof(float): 复制12字节(假设float为4字节)
3. Student** 是什么?
指针的指针(双重指针):
Student* head = NULL; // 一级指针:指向Student的指针
Student** headPtr = &head; // 二级指针:指向指针的指针// 使用场景:需要修改指针本身的值时
为什么需要Student**?
修改头指针:在链表为空时,需要修改head指针指向新节点
避免返回值:通过参数修改调用者的变量
通用性:适用于任何需要修改指针的情况
替代方案(返回新头指针):
Student* addStudent(Student* head, Student* newStudent) {if (head == NULL) {return newStudent; // 返回新头指针}// ...return head;
}// 调用方式
head = addStudent(head, newStudent);
4. freeStudents的作用
代码示例:
void freeStudents(Student* head) {Student* current = head;while (current != NULL) {Student* next = current->next; // 先保存下一个节点free(current); // 释放当前节点current = next; // 移动到下一个节点}
}
内存泄漏问题:
// 错误做法:直接free(head)只会释放第一个节点
// 后续节点的内存会泄漏!// 正确做法:遍历整个链表,逐个释放
freeStudents的重要性:
防止内存泄漏:释放所有动态分配的内存
资源管理:良好的编程习惯
程序稳定性:避免长时间运行后的内存耗尽
5. fprintf是什么?
fprintf vs printf:
printf("Hello %s\n", name); // 输出到标准输出(屏幕)
fprintf(file, "Hello %s\n", name); // 输出到文件
fprintf(stderr, "Error: %s\n", msg); // 输出到标准错误
fprintf的作用:
格式化输出到文件:将数据按照指定格式写入文件
数据持久化:将内存中的数据保存到磁盘
可读性格式:生成CSV等易于阅读的格式
参数详解:
fprintf(文件指针, 格式字符串, 参数1, 参数2, ...);
示例二
#include <stdio.h> // 标准输入输出
#include <stdlib.h> // 内存管理、系统函数
#include <string.h> // 字符串操作
#include <time.h> // 时间处理
#include <ctype.h> // 字符处理// 宏定义和配置
#define MAX_BOOKS 1000 // 最大图书数量
#define MAX_MEMBERS 500 // 最大会员数量
#define MAX_BORROW 5 // 最大借阅数量
#define DATE_FORMAT "%Y-%m-%d" // 日期格式// 枚举类型
typedef enum {SCIENCE, // 科学类:0TECHNOLOGY, // 技术类:1 LITERATURE, // 文学类:2HISTORY, // 历史类:3ART, // 艺术类:4OTHER // 其他类:5
} BookCategory;typedef enum {ACTIVE, // 活跃状态:0INACTIVE, // 非活跃:1SUSPENDED // 暂停状态:2
} MemberStatus;// 结构体定义
typedef struct {int year;int month;int day;
} Date;typedef struct {char isbn[14]; // ISBN号码char title[100]; // 书名char author[50]; // 作者BookCategory category; // 图书分类int total_copies; // 总副本数int available_copies; // 可用副本数Date publish_date; // 出版日期
} Book;typedef struct {int member_id; // 会员IDchar name[50]; // 会员姓名char email[100]; // 邮箱char phone[15]; // 电话MemberStatus status; // 会员状态int borrowed_count; // 当前借阅数量
} Member;typedef struct BorrowRecord {char isbn[14]; // ISBN号码int member_id; // 会员IDDate borrow_date; // 借阅日期Date due_date; // 应还日期Date return_date; // 实际归还日期struct BorrowRecord* next; // 下一个记录
} BorrowRecord;typedef struct {Book books[MAX_BOOKS]; // 图书数组int book_count; // 图书数量Member members[MAX_MEMBERS]; // 会员数组int member_count; // 会员数量BorrowRecord* borrow_list; // 借阅记录链表int transaction_count; // 交易计数
} LibrarySystem;// 函数声明
void initializeSystem(LibrarySystem* lib);
int addBook(LibrarySystem* lib, const char* isbn, const char* title, const char* author, BookCategory category, int copies, Date publish_date);
int addMember(LibrarySystem* lib, const char* name, const char* email, const char* phone);
int borrowBook(LibrarySystem* lib, const char* isbn, int member_id, Date borrow_date);
int returnBook(LibrarySystem* lib, const char* isbn, int member_id, Date return_date);
void searchBooks(const LibrarySystem* lib, const char* keyword);
void generateReports(const LibrarySystem* lib);
void saveToFile(const LibrarySystem* lib, const char* filename);
void loadFromFile(LibrarySystem* lib, const char* filename);
void freeSystem(LibrarySystem* lib);// 工具函数
Date getCurrentDate();
int dateDifference(Date d1, Date d2);
void stringToLower(char* str);
int isValidISBN(const char* isbn);
int isEmailValid(const char* email);// 主函数
int main() {LibrarySystem lib;initializeSystem(&lib);printf("=== 图书馆管理系统 ===\n\n");// 添加示例数据Date pub_date = {2020, 5, 15};addBook(&lib, "978-0131103627", "The C Programming Language", "Brian Kernighan", TECHNOLOGY, 5, pub_date);pub_date = (Date){2019, 8, 20};addBook(&lib, "978-0596007126", "Head First Design Patterns", "Eric Freeman", TECHNOLOGY, 3, pub_date);addMember(&lib, "张三", "zhangsan@email.com", "13800138000");addMember(&lib, "李四", "lisi@email.com", "13900139000");// 模拟借阅操作Date today = getCurrentDate();borrowBook(&lib, "978-0131103627", 1, today);// 生成报告generateReports(&lib);// 保存数据saveToFile(&lib, "library_data.dat");// 释放资源freeSystem(&lib);return 0;
}// 系统初始化
void initializeSystem(LibrarySystem* lib) {lib->book_count = 0;lib->member_count = 0;lib->borrow_list = NULL;lib->transaction_count = 0;// 初始化数组为0memset(lib->books, 0, sizeof(lib->books));memset(lib->members, 0, sizeof(lib->members));
}// 添加图书
int addBook(LibrarySystem* lib, const char* isbn, const char* title, const char* author, BookCategory category, int copies, Date publish_date) {if (lib->book_count >= MAX_BOOKS) {printf("错误: 图书数量已达上限\n");return -1;}if (!isValidISBN(isbn)) {printf("错误: ISBN格式无效\n");return -1;}// 检查ISBN是否已存在for (int i = 0; i < lib->book_count; i++) {if (strcmp(lib->books[i].isbn, isbn) == 0) {printf("错误: ISBN已存在\n");return -1;}}// 添加新图书Book* new_book = &lib->books[lib->book_count];strncpy(new_book->isbn, isbn, sizeof(new_book->isbn) - 1);strncpy(new_book->title, title, sizeof(new_book->title) - 1);strncpy(new_book->author, author, sizeof(new_book->author) - 1);new_book->category = category;new_book->total_copies = copies;new_book->available_copies = copies;new_book->publish_date = publish_date;lib->book_count++;printf("成功添加图书: %s\n", title);return 0;
}// 添加会员
int addMember(LibrarySystem* lib, const char* name, const char* email, const char* phone) {if (lib->member_count >= MAX_MEMBERS) {printf("错误: 会员数量已达上限\n");return -1;}if (!isEmailValid(email)) {printf("错误: 邮箱格式无效\n");return -1;}// 检查邮箱是否已存在for (int i = 0; i < lib->member_count; i++) {if (strcmp(lib->members[i].email, email) == 0) {printf("错误: 邮箱已存在\n");return -1;}}// 添加新会员Member* new_member = &lib->members[lib->member_count];new_member->member_id = lib->member_count + 1;strncpy(new_member->name, name, sizeof(new_member->name) - 1);strncpy(new_member->email, email, sizeof(new_member->email) - 1);strncpy(new_member->phone, phone, sizeof(new_member->phone) - 1);new_member->status = ACTIVE;new_member->borrowed_count = 0;lib->member_count++;printf("成功添加会员: %s (ID: %d)\n", name, new_member->member_id);return new_member->member_id;
}// 借阅图书
int borrowBook(LibrarySystem* lib, const char* isbn, int member_id, Date borrow_date) {Book* book = NULL;Member* member = NULL;// 查找图书for (int i = 0; i < lib->book_count; i++) {if (strcmp(lib->books[i].isbn, isbn) == 0) {book = &lib->books[i];break;}}if (book == NULL) {printf("错误: 图书不存在\n");return -1;}if (book->available_copies <= 0) {printf("错误: 图书已全部借出\n");return -1;}// 查找会员for (int i = 0; i < lib->member_count; i++) {if (lib->members[i].member_id == member_id) {member = &lib->members[i];break;}}if (member == NULL) {printf("错误: 会员不存在\n");return -1;}if (member->status != ACTIVE) {printf("错误: 会员状态不允许借阅\n");return -1;}if (member->borrowed_count >= MAX_BORROW) {printf("错误: 借阅数量已达上限\n");return -1;}// 创建借阅记录BorrowRecord* new_record = (BorrowRecord*)malloc(sizeof(BorrowRecord));if (new_record == NULL) {printf("错误: 内存分配失败\n");return -1;}strncpy(new_record->isbn, isbn, sizeof(new_record->isbn) - 1);new_record->member_id = member_id;new_record->borrow_date = borrow_date;// 计算应还日期(30天后)new_record->due_date = borrow_date;new_record->due_date.day += 30;if (new_record->due_date.day > 31) {new_record->due_date.day -= 31;new_record->due_date.month++;if (new_record->due_date.month > 12) {new_record->due_date.month = 1;new_record->due_date.year++;}}new_record->return_date = (Date){0}; // 初始化为0new_record->next = lib->borrow_list;lib->borrow_list = new_record;// 更新状态book->available_copies--;member->borrowed_count++;lib->transaction_count++;printf("成功借阅: %s 被会员 %d 借出\n", book->title, member_id);return 0;
}// 归还图书
int returnBook(LibrarySystem* lib, const char* isbn, int member_id, Date return_date) {BorrowRecord* current = lib->borrow_list;BorrowRecord* prev = NULL;// 查找借阅记录while (current != NULL) {if (strcmp(current->isbn, isbn) == 0 && current->member_id == member_id &¤t->return_date.year == 0) { // 未归还的记录current->return_date = return_date;// 更新图书和会员状态for (int i = 0; i < lib->book_count; i++) {if (strcmp(lib->books[i].isbn, isbn) == 0) {lib->books[i].available_copies++;break;}}for (int i = 0; i < lib->member_count; i++) {if (lib->members[i].member_id == member_id) {lib->members[i].borrowed_count--;// 检查是否逾期int days_overdue = dateDifference(return_date, current->due_date);if (days_overdue > 0) {printf("警告: 逾期 %d 天归还\n", days_overdue);// 这里可以添加处罚逻辑}break;}}printf("成功归还图书\n");return 0;}prev = current;current = current->next;}printf("错误: 未找到借阅记录\n");return -1;
}// 搜索图书
void searchBooks(const LibrarySystem* lib, const char* keyword) {printf("\n=== 搜索结果 ===\n");int found = 0;char search_term[100];char book_title[100];strncpy(search_term, keyword, sizeof(search_term) - 1);stringToLower(search_term);for (int i = 0; i < lib->book_count; i++) {strncpy(book_title, lib->books[i].title, sizeof(book_title) - 1);stringToLower(book_title);if (strstr(book_title, search_term) != NULL) {printf("ISBN: %s, 书名: %s, 作者: %s, 可用: %d/%d\n",lib->books[i].isbn, lib->books[i].title, lib->books[i].author, lib->books[i].available_copies,lib->books[i].total_copies);found = 1;}}if (!found) {printf("未找到相关图书\n");}
}// 生成报告
void generateReports(const LibrarySystem* lib) {printf("\n=== 图书馆统计报告 ===\n");printf("图书总数: %d\n", lib->book_count);printf("会员总数: %d\n", lib->member_count);printf("总交易次数: %d\n", lib->transaction_count);// 分类统计int category_count[6] = {0};for (int i = 0; i < lib->book_count; i++) {category_count[lib->books[i].category]++;}printf("\n图书分类统计:\n");printf("科学: %d, 技术: %d, 文学: %d\n", category_count[SCIENCE], category_count[TECHNOLOGY], category_count[LITERATURE]);printf("历史: %d, 艺术: %d, 其他: %d\n",category_count[HISTORY], category_count[ART], category_count[OTHER]);// 当前借阅情况printf("\n当前借阅记录:\n");BorrowRecord* current = lib->borrow_list;int active_borrows = 0;while (current != NULL) {if (current->return_date.year == 0) { // 未归还printf("ISBN: %s, 会员ID: %d, 应还日期: %d-%02d-%02d\n",current->isbn, current->member_id,current->due_date.year, current->due_date.month, current->due_date.day);active_borrows++;}current = current->next;}if (active_borrows == 0) {printf("暂无借阅记录\n");}
}// 工具函数实现
Date getCurrentDate() {time_t t = time(NULL);struct tm* tm_info = localtime(&t);Date today;today.year = tm_info->tm_year + 1900;today.month = tm_info->tm_mon + 1;today.day = tm_info->tm_mday;return today;
}int dateDifference(Date d1, Date d2) {// 简化的日期差计算int days1 = d1.year * 365 + d1.month * 30 + d1.day;int days2 = d2.year * 365 + d2.month * 30 + d2.day;return days1 - days2;
}void stringToLower(char* str) {for (int i = 0; str[i]; i++) {str[i] = tolower(str[i]);}
}int isValidISBN(const char* isbn) {// 简化的ISBN验证return strlen(isbn) == 13 || strlen(isbn) == 10;
}int isEmailValid(const char* email) {// 简化的邮箱验证return strchr(email, '@') != NULL && strchr(email, '.') != NULL;
}// 文件操作(简化版)
void saveToFile(const LibrarySystem* lib, const char* filename) {FILE* file = fopen(filename, "wb");if (file == NULL) {perror("无法保存文件");return;}fwrite(lib, sizeof(LibrarySystem), 1, file);fclose(file);printf("数据已保存到 %s\n", filename);
}void loadFromFile(LibrarySystem* lib, const char* filename) {FILE* file = fopen(filename, "rb");if (file == NULL) {perror("无法加载文件");return;}fread(lib, sizeof(LibrarySystem), 1, file);fclose(file);printf("数据已从 %s 加载\n", filename);
}// 释放资源
void freeSystem(LibrarySystem* lib) {BorrowRecord* current = lib->borrow_list;while (current != NULL) {BorrowRecord* next = current->next;free(current);current = next;}lib->borrow_list = NULL;
}
1. memset 函数详解
基本用法
void *memset(void *ptr, int value, size_t num);
ptr
: 要设置的内存块的起始地址value
: 要设置的值(转换为unsigned char)num
: 要设置的字节数
在Demo中的使用
memset(lib->books, 0, sizeof(lib->books));
这行代码的意思是:从lib->books数组的起始地址开始,将sizeof(lib->books)个字节的内存全部设置为0分解:lib->books: 图书数组的起始地址0: 要设置的值(清零)sizeof(lib->books): 整个数组的大小(字节数)
2. strcmp 函数详解
基本用法
int strcmp(const char *str1, const char *str2);// 例如:
strcmp("abc", "abc") = 0 // 完全相等
strcmp("abc", "abd") = -1 // 'c' < 'd'
strcmp("abd", "abc") = 1 // 'd' > 'c'
strcmp("ab", "abc") = -1 // 长度不同
比较两个字符串
返回0表示相等
返回负数表示str1 < str2
返回正数表示str1 > str2
在Demo中的使用
if (strcmp(lib->books[i].isbn, isbn) == 0) {// 找到匹配的ISBN
}
strcmp比较两个字符串的字典序, 它逐个字符比较,直到遇到不同的字符或字符串结束 。
在Demo中:检查ISBN是否已存在。
3. 指针和地址操作详解
Book* new_book = &lib->books[lib->book_count];
分解解释:
// lib->books: 图书数组
// lib->book_count: 当前图书数量(也是下一个空位的索引)
// lib->books[lib->book_count]: 数组中的第lib->book_count个元素
// &lib->books[lib->book_count]: 取这个元素的地址// 所以:
Book* new_book = &lib->books[lib->book_count];
// 等价于:
Book* new_book = lib->books + lib->book_count;// new_book现在指向数组中下一个可用的位置
4. strncpy 和 -1 的原因
strncpy 的安全用法
strncpy(new_member->name, name, sizeof(new_member->name) - 1);
new_member->name[sizeof(new_member->name) - 1] = '\0';
为什么需要-1:
// new_member->name 是 char[50] 数组
// sizeof(new_member->name) = 50
// 但我们需要保留1个字节给字符串终止符 '\0'// 所以:
strncpy(dest, src, dest_size - 1); // 最多复制dest_size-1个字符
dest[dest_size - 1] = '\0'; // 确保字符串终止// 如果不-1,可能会:
// 1. 复制过多字符导致缓冲区溢出
// 2. 没有空间放终止符,导致字符串不完整
什么时候需要-1:
当使用
strncpy
复制到固定大小数组时当需要确保字符串正确终止时
当源字符串可能比目标数组长时
什么时候不需要-1:
当明确知道源字符串长度时
当使用动态分配的内存时
当后续会手动添加终止符时
5.tolower
tolower(int c): 将字符转换为小写。
如果字符是大写字母,返回对应小写字母;如果不是字母,返回原字符
// 例子:
tolower('A') = 'a'
tolower('a') = 'a'
tolower('1') = '1'
tolower('!') = '!'// 在循环中:
for (int i = 0; str[i]; i++) {str[i] = tolower(str[i]); // 逐个字符转换为小写
}// 实现不区分大小写的搜索
6. getCurrentDate 函数详解
Date getCurrentDate() {time_t t = time(NULL); // 1. 获取当前时间戳struct tm* tm_info = localtime(&t); // 2. 转换为本地时间Date today;today.year = tm_info->tm_year + 1900; // 3. 年份转换today.month = tm_info->tm_mon + 1; // 4. 月份转换today.day = tm_info->tm_mday; // 5. 直接获取日return today;
}
逐步解释:
time_t t = time(NULL);
time(NULL)
返回从1970年1月1日开始的秒数NULL
表示不需要存储时间值
struct tm* tm_info = localtime(&t);
localtime()
将时间戳转换为本地时间结构struct tm
包含年、月、日、时、分、秒等信息
年份转换:
tm_year + 1900
tm_info->tm_year
是从1900年开始的年数所以2023年表示为123,需要+1900
月份转换:
tm_mon + 1
tm_info->tm_mon
范围是0-11(0=一月)所以需要+1得到实际月份
日期获取:
tm_mday
tm_info->tm_mday
直接就是1-31的日期不需要转换