---大一菜鸟的真实生存报告
CPU没炸 我炸了
还记得刚入学时,我对计算机的幻想:
🕶️ 墨镜一戴,键盘一敲,黑进学校系统改成绩(不是)
现实却是——
🔆 一整个下午,我盯着屏幕,第N次运行失败:
#include <stdio.h>
int main()
{
int a,b;
float x,y;
char c1,c2;
scanf("a=%db=%d", &a, &b);
scanf("%f%e", &x, &y);
scanf("%c%c", &c1, &c2);
printf("a=%d, b=%d\n", a, b);
printf("x=%f, y=%e\n", x, y);
printf("c1=%c, c2=%c\n", c1, c2);
return 0;
//不是就这还能错(╬▔皿▔)╯
}
📸 点击查看我运行的各种无语结果,当时真的气死我了😾
*图1*
图2
图3
图4
图5
💢我(怒敲键盘): "你是不是针对我??"
💻电脑(冷漠脸): Segmentation fault (core dumped)
这一刻我终于悟了:
学计算机=和一台没有感情的机器进行极限拉扯(而且它永远赢) ←_←
初生牛犊不怕虎!
如果:
💻 日常debug = 小怪练级
那么:
🏆 蓝桥杯 = 第一个副本Boss
大一的我,激情澎湃,满脑子都是:
竞赛!竞赛! 以赛促学!(毕竟老师说过,比赛是最好的动力嘛)( ̄︶ ̄)↗
于是——
💻查比赛:
ACM? 团队赛emm...算了没人
蓝桥杯? 个人赛!就它了!
🤔选赛道: 这学期刚学C语言,好,报C语言组!
看到报名费: 300块?!(⊙o⊙)💸
转念一想: “没事!花了钱就会好好准备,就算没奖过程最重要!”(^▽^)
现实暴击:考纲劝退
🖥️ 打开比赛大纲
→ 满屏专业名词直接触发
视觉缓冲区溢出
(此时大脑状态:404 知识不存在)
💸 但300块报名费像圣旨一样唤醒了我:
"这钱要是白给,我直接sudo rm -rf /自己!"
寒假自救计划(伪)
STEP 1:装备IDE
🔧 官方指定武器:Dev-C++
(安装过程堪比配置魔法阵,全靠B站鹏哥C语言教程续命)
"这个up主简直是新手村的NPC导师!"
💨 点击查看视频网址
https://www.bilibili.com/video/BV1kC411G7CS/?spm_id_from=333.337.search-card.all.click&vd_source=82a15966f1d7954e3bcb46ebdfd24f70STEP 2:知识加载中...
📚 备考策略:
按考纲把知识点当BOSS逐个攻略
📚十大排序算法全景指南
💨 学习视频推荐
- 🔆 亮点:从算法讲解到代码到思想应用都很详细
⚠️ 不足:口齿不太清晰,对于我这可怜的知识储备看时稍微有点费事 www.bilibili.com/video/BV1Ur… - 🔆 两点 :图解十分生动好理解(我用这个视频辅助理解,很喜欢归并排序的图解)
www.bilibili.com/video/BV1Pt… - 还有还有 向ai问一些问题还是比较方便的(虽然死豆包有时答非所问吧ㄟ( ▔, ▔ )ㄏ)
“你们有什么好的学习资源吗?求安利!评论区见~”
以下是我按照视频的讲解写的代码,欢迎大家交流学习,有什么更好的实现姿势请务必告诉我!💕
1️⃣ 冒泡排序:新手村必修技
⚡ 查看代码(点击展开)
#include<stdio.h>
void maopao(int arr[], int n) {
int i, j, temp, f;
// 外循环:送n-1个数字回家(最后一个不需要送)
for (i = 0; i < n - 1; i++) {
f = 0; // flag优化:如果一轮没交换说明已有序
// 内循环:比较相邻元素像泡泡一样上浮,内循环每完成一次,成功送一个数字回家
for (j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
f = 1; // 标记发生过交换
}
}
if (f == 0) break; // 提前下班!
}
}
💡 学习重点:f标志减少无效比较
💡小tips:面对多重循环时,明确一个循环进行的过程中哪个变量变,哪个变量不变,这样更容易理解代码的功能
2️⃣ 选择排序:简单粗暴的狙击手
⚡ 查看代码(点击展开)
#include<stdio.h>
int main() {
int a[10] = { 2,85,4,66,25,74,18,32,41,10 };
int i, j, temp, n = 10;
// 外循环:已排序序列的边界
for (i = 0; i < n - 1; i++) {
// 内循环:在未排序部分狙击最小值
//内循环中把最小的数放在a[i]中,继续与下一个数比较,直到最后一个数
for (j = i + 1; j < n; j++) {
if (a[i] > a[j]) {
a[j] = a[i];
a[i] = temp;
}
}
}
// 打印结果
for (i = 0; i < n; i++) printf("%d ", a[i]);
return 0;
}
💡 学习重点:交换操作比冒泡少,但比较次数不变
3️⃣ 插入排序:扑克牌玩家的秘诀
⚡ 查看代码(点击展开)
#include<stdio.h>
void insertionsort(int a[], int n) {
int i, j, key;
for (i = 1; i < n; i++) {
key = a[i]; // 抽出的待插入牌
// 内循环:将key插入已排序序列的合适位置
for (j = i - 1; j >= 0 && a[j] > key; j--) {
a[j + 1] = a[j]; // 元素后移 不能把a[j+1]换成a[i] j+1和i是有区别的,在第二次循环中i是不变的,而j+1的位置是要随时变化的
}
a[j + 1] = key; // 插入正确位置 注意此时j的值已变为j--
}
}
4️⃣ 归并排序:分治思想的典范
⚡ 查看代码(点击展开)
//#include<stdio.h>
//#include<stdlib.h>
//合并
void merge(int arr[],int tempArr[],int left,int mid,int right)
{
//标记左半区第一个未排序的元素
int l_pos=left ;
//标记右半区第一个未排序的元素
int r_pos=mid+1;
//临时数组元素的下标
int pos=left;
//合并
while(l_pos<=mid && r_pos<=right)//左右半区都有元素
{
if(arr[l_pos]<arr[r_pos])
tempArr[pos++]=arr[l_pos++];
else
tempArr[pos++]=arr[r_pos++];
}
//合并左半区剩余的元素
while(l_pos<=mid)
{
tempArr[pos++]=arr[l_pos++];
}
//合并右半区剩余的元素
while(r_pos<=right)
{
tempArr[pos++]=arr[r_pos++];
}
//把临时数组中合并后的元素复制回原来的数组
while(left<=right)
{
arr[left]=tempArr[left];
left++;
}
}
//划分
void msort(int arr[],int tempArr[],int left,int right)
{
//如果只有一个元素,那么就不需要继续划分
//只有一个元素的区域,本生就是有序的,只需要合并
if(left < right)
{
//找中间点
int mid=(left + right)/2;
//递归划分左半区
msort(arr,tempArr,left,mid);
//递归划分右半区
msort(arr,tempArr,mid+1,right);
//合并已经排序的部分
merge(arr,tempArr,left,mid,right);
}
}
//打印数组
void print_arr(int arr[],int n)
{
int i=0;
for(i=0;i<n;i++)
{
printf("%3d",arr[i]);
}
printf("\n");
}
//归并排序入口
void merge_sort(int arr[],int n)
{
//分配一个辅助数组
int *tempArr=(int *)malloc(n * sizeof(int));
//使用malloc函数动态分配一个与原数组arr大小相同的数组tempArr
if(tempArr)//辅助数组分配成功
{
msort(arr,tempArr,0,n-1);
free(tempArr);
}
else
{
printf("error:failed to allocate memory");
}
}
int main()
{
int arr[]={9,5,2,7,12,4,3,1,11};
int n=9;
print_arr(arr,n);
//对数组排序
merge_sort(arr,n);
//打印数组
print_arr(arr,n);
return 0;
}
核心思想:先分后并
5️⃣ 快速排序:分而治之的魔法
⚡ 查看代码(点击展开)
#include<stdio.h>
int partition(int a[], int L, int R) {
int pivot = a[L]; // 选最左当基准值(挖坑)
while (L < R) {
while (L<R && a[R]>=pivot) R--; // 从右找比基准小的
a[L] = a[R]; // 填左坑
while (L<R && a[L]<=pivot) L++; // 从左找比基准大的
a[R] = a[L]; // 填右坑
}
a[L] = pivot; // 基准值归位
return L; // 返回基准位置
}
void quicksort(int a[], int L, int R) {
if (L < R) {
int pos = partition(a, L, R);
quicksort(a, L, pos-1); // 递归排左边
quicksort(a, pos+1, R); // 递归排右边
}
}
⚠️ 易错点:递归终止条件L<R不能漏!
6️⃣ 希尔排序:插入排序的涡轮增压版
⚡ 查看代码(点击展开)
#include<stdio.h>
void shellSort(int arr[], int n) {
// 动态计算初始间隔(Knuth序列)
int gap = 1;
while (gap < n / 3)
gap = gap * 3 + 1; // 1, 4, 13, 40, 121...
while (gap >= 1) {
// 按间隔执行插入排序
for (int i = gap; i < n; i++) {
int temp = arr[i];
int j;
for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
arr[j] = arr[j - gap]; // 元素后移
}
arr[j] = temp; // 插入正确位置
}
gap /= 3; // 缩小间隔
}
}
7️⃣ 堆排序:二叉树的力量
⚡ 查看代码(点击展开)
//维护堆的性质
//arr 存储堆的数组 n 数组长度 i 带维护的数组下标
#include<stdio.h>
void print_arr(int arr[],int n)
{
int i;
for(i=0;i<n;i++)
{
printf("%3d",arr[i]);
}
printf("\n");
}
void swap(int*a,int*b)
{
int temp=*a;
*a=*b;
*b=temp;
}
void heapify(int arr[],int n,int i)
{
int largest=i;
int lson=i*2+1;
int rson=i*2+2;
//找出三个数的最大值并把其下标给largest
if(lson<n && arr[largest]<arr[lson])
largest=lson;
if(rson<n && arr[largest]<arr[rson])
largest=rson;
if(largest!=i)//不等于i说明需要维护
{
swap(&arr[largest],&arr[i]);//交换
heapify(arr,n,largest);//递归检查交换后其他的需不需要维护
}
}
//堆排序入口
void heap_sort(int arr[],int n)
{
//建大顶堆
int i;
for(i=n/2-1;i>=0;i--)//从后面的元素建起
{
heapify(arr,n,i);
}
//排序
for(i=n-1;i>0;i--)
{
swap(&arr[i],&arr[0]);//堆顶元素(最大元素)和最后的元素交换
heapify(arr,i,0);//对第一个元素维护,保证第一个永远最大
}
}
int main()
{
int arr[10]={2,6,4,9,3,1,7,10,5,8};
int n=10;
print_arr(arr,n);
heap_sort(arr,n);
print_arr(arr,n);
return 0;
}
⚠️ 易错点:递归终止条件L<R不能漏!
8️⃣ 桶排序:分治的另一种体现
⚡ 查看代码(点击展开)
#include<stdio.h>
#include<stdlib.h>
//确定桶的数量
#define BUCKET_SIZE 10
//对每个桶内的数据排序
void maopao(int arr[], int n)
{
int i, j, temp;
for (i = 0; i < n - 1; i++)
{
for (j = 0; j < n - i - 1; j++)
{
if (arr[j] > arr[j + 1])
{
temp = arr[j + 1];
arr[j + 1] = arr[j];//交换
arr[j] = temp;
}
}
}
}
//打印数组
void print_arr(int arr[],int n)
{
int i;
for(i=0;i<n;i++)
{
printf("%3d",arr[i]);
}
printf("\n");
}
void bucketSort(int arr[],int n)
{
//创建桶
int i,j;
//一个二维数组,相当于有 BUCKET_SIZE 个长度为n的一维数组 保证桶能够放下
int bucket[BUCKET_SIZE][n] ;
//记录每个桶储存实际元素的个数,并初始化为0
int count[BUCKET_SIZE]={0};
//将元素分到相应的桶里
for(i=0;i<n;i++)
{
int index=(int)(arr[i]/10);//得到应该放到哪个桶
bucket[index][count[index]++]=arr[i];
}
//对每个桶内元素排序
for(i=0;i<BUCKET_SIZE;i++)
{
maopao(bucket[i],count[i]);
}
//按顺序取出放到arr中
int index=0;
for(i=0;i<BUCKET_SIZE;i++)
{
for(j=0;j<count[i];j++)//j<n是不对的 每个桶的长度是不同的
{
arr[index++] =bucket[i][j];
}
}
}
int main()
{
int arr[10]={5,12,7,19,24,23,8,46,20,38};
int n=sizeof(arr)/sizeof(arr[0]);
print_arr(arr,n);
bucketSort(arr,n);
print_arr(arr,n);
return 0;
}
⚠️ 易错点:递归终止条件L<R不能漏!
9️⃣ 计数排序:空间换时间的艺术
⚡ 查看代码(点击展开)
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void print_arr(int arr[],int n)
{
int i;
for(i=0;i<n;i++)
{
printf("%3d",arr[i]);
}
printf("\n");
}
//获取待排序数组的最大元素的值
int arrmax(int*arr,unsigned int len)
{
int i=0,max=arr[0];
for(i=1;i<len;i++)
{
if(arr[i]>max)
max=arr[i];
}
return max;
}
void countSort(int*arr,unsigned int len)
{
if(len<2) return;
int imax=arrmax(arr,len);
int arrtmp[imax+1];//临时数组的大小为imax+1
//memset包含在头文件string.h将一段内存区域设定为指定值
//将临时数组初始化,都为0
memset(arrtmp,0,sizeof(arrtmp)) ;
//void *memset(void*s,int c,size_t n)
int i,j,k;
//临时数组计数
for(i=0;i<len;i++) arrtmp[arr[i]]++;
//把临时数组计数的内容填充到arr
i=0;
for(j=0;j<imax+1;j++)
{
for(k=0;k<arrtmp[j];k++)
arr[i++]=j;
}
//2计了两次就输出两个2
//外循环遍历临时数组,内循环重复输出
}
int main()
{
int arr[]={2,3,8,7,1,2,2,7,3,9,8,2,1,4,5,6};
int n=sizeof(arr)/sizeof(arr[0]);
print_arr(arr,n);
countSort(arr,n);
print_arr(arr,n);
return 0;
}*/
/*
计数排序算法的时间复杂度低+
前提满足:1. 需要排序的元素必须是整数+
2. 排序元素的取值要在一定范围内,并且比较集中
两个条件都满足,才能最大程度发挥其优势
*/
//优化计数排序
/*
占用空间太多 以下数组最大值是109,临时数组长度为110,但是前一百个没用
因此算出最小值 max-min+1作为临时数组的长度
*/
/*#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void print_arr(int arr[],int n)
{
int i;
for(i=0;i<n;i++)
{
printf("%5d",arr[i]);
}
printf("\n");
}
//获取待排序数组的最大元素的值
void arrminmax(int*arr,unsigned int len,int*min,int*max)
{
int i=0;
*max=*min=arr[0];
for(i=1;i<len;i++)
{
if(arr[i]>*max)
*max=arr[i];
if(arr[i]<*min)
*min=arr[i];
}
}
void countSort(int*arr,unsigned int len)
{
if(len<2) return;
int min,max;
arrminmax(arr,len,&min,&max);
int arrtmp[max-min+1];//临时数组的大小max-min+1
//memset包含在头文件string.h将一段内存区域设定为指定值
//将临时数组初始化,都为0
memset(arrtmp,0,sizeof(arrtmp)) ;
//void *memset(void*s,int c,size_t n)
int i,j,k;
//临时数组计数
for(i=0;i<len;i++) arrtmp[arr[i]-min]++;
//arr[i]-min做临时数组的下标例如101-101=0;102-101=1
//把临时数组计数的内容填充到arr
i=0;
for(j=0;j<max-min+1;j++)
{
for(k=0;k<arrtmp[j];k++)
arr[i++]=j+min;//arr[i]-min=j
}
//2计了两次就输出两个2
//外循环遍历临时数组,内循环重复输出
}
int main()
{
int arr[]={102,103,108,107,101,102,102,107,103,109,108,102,101,104,105,106};
int n=sizeof(arr)/sizeof(arr[0]);
print_arr(arr,n);
countSort(arr,n);
print_arr(arr,n);
return 0;
}
🔟 基数排序:数学的维度魔法
⚡ 查看代码(点击展开)
#include<stdio.h>
#include<string.h>
int arrmax(int*arr,unsigned int len)
{
int i=0,max=arr[0];
for(i=1;i<len;i++)
{
if(arr[i]>max)
max=arr[i];
}
return max;
}
void print_arr(int arr[],int n)
{
int i;
for(i=0;i<n;i++)
{
printf("%5d",arr[i]);
}
printf("\n");
}
void _radixsort(int *arr,unsigned int n,unsigned int exp)
{
int i;
int result[n];//存放从桶中收集后数据的临时数组
int buckets[10]={0};//初始化10个桶
//遍历arr,将数据出现的次数存储在buckets中
for(i=0;i<n;i++)
{
buckets[(arr[i]/exp)%10]++;
//602 602/1,再%10得2 buckets[2] +1
// 602/10,再%10得0 buckets[0] +1
}
//调整buckets各元素的值,调整后的值就是arr中元素在result中的位置
for(i=1;i<10;i++)
{
buckets[i]=buckets[i]+buckets[i-1];//前缀和数组
// 累加 buckets[9]的值是该数组的元素个数
}
//将arr中元素填充到result中
for(i=n-1;i>=0;i--)
{
int iexp=(arr[i]/exp)%10;
result[buckets[iexp]-1]=arr[i];
buckets[iexp]--;
//次数减一刚好算出其应该放好位置的下标
}
//将排好的数组result复制到数组arr中
memcpy(arr,result,n*sizeof(int));
}
void radixsort(int *arr,unsigned int n)
{
int imax=arrmax(arr,n);//获取数组arr中的最大值
int iexp;//iexp=1,按个位排序;iexp=10,按十位排序
//从个位开始,对数组arr按数位进行排序
for(iexp=1;imax/iexp>0;iexp=iexp*10)
{
_radixsort(arr,n,iexp);
int i;
printf("exp=%-5d",iexp);
for(i=0;i<n;i++)
{
printf("%5d",arr[i]);
}
printf("\n");
}
}
int main()
{
int arr[]={5,112,702,419,24,253,860,469,210,38};
int n=sizeof(arr)/sizeof(arr[0]);
print_arr(arr,n);
radixsort(arr,n);
print_arr(arr,n);
return 0;
}
在啃排序算法的过程中,我算是明白了计算机科学的知识网状结构——当想弄明白一个陌生术语时,总会拉出一堆未知概念(比如学堆排序需要先理解完全二叉树,学桶排序又得了解哈希映射)。这种递归式学习对于一个新手小白来说太痛苦啦~😣😣😣
这种知识递归现象带来的挑战:
- 学习路径模糊:没人告知应该先掌握哪些前置知识
- 调试挫败感:代码看似正确却莫名报错
“大家有什么好的学习方法或者学习工具,求分享哇~”(づ ̄ 3 ̄)づ
🚨 时间紧迫警报!
寒假肝完十大排序,抬头一看——WPS二级考试和蓝桥杯像两个Boss同时刷新了!
💥 紧急作战计划启动:
- 两周极限操作:
- 白天狂刷WPS真题("合并单元格"是我的新咒语)
- 晚上恶补算法速成套餐:
█ DFS/BFS:走迷宫式突击 █ 贪心算法:能贪就贪,绝不DP █ 二分法:万物皆可二分 - 班里部门里各种支线任务
🎮 赛场实况直播:
- 比赛心态:稳如老狗
- 战术部署:硬磕四个小时,放弃最终Boss(最后两题),专注收割小兵(前四题)
- 战果汇报:
✅ 1道填空题(稳!)
✅ 1道大题全歼(15分到手!)
⚠️ 3道大题残血(拿个参与分) - 最终评级:省三勋章get√
💡 暴击后的觉醒:
比赛代码 = 80%的for/if + 20%的算法,但那20%才是区分青铜和王者的关键
🌟 给后来者的血泪指南:
+ 必做:
1. 提前了解比赛各种信息,避免信息差
2. 不管是学习还是练题要有做笔记的习惯(不要像我现在才做)
- 避坑:
1. 别信"蓝桥杯很水"的鬼话(说水的都是大佬)
2. 不要像我一样考前才刷真题(会哭)
🎯 终极感悟:
奖状的水分会被时间晒干,但是我整个备赛过程中学到的东西是实实在在的,这是不水的,我觉得这就是竞赛的意义吧!^o^y
🔆彩蛋:
某堂高数课我突然想起好像有个东西没...选...
(抱头)为什么我脑子里全是ctrl+c,ctrl+v的画面,就没有选这个语言的印象呢,完了完了,300彻底飞了
(下一秒躺平)算了,随便吧(T_T)
Python,我的快乐老家
当C语言让我怀疑人生时,Python像一束光照进了我的代码世界:
快乐1:代码量骤降
- C语言版"Hello World":
#include <stdio.h>
int main() {
printf("Hello World!\n");
return 0;
}
- Python版:
print("Hello World!") # 连分号都不配拥有!
快乐2:图书馆管理系统
Python老师:咱们刚开始学,做个简单的一个小界面玩玩,图书管理系统
我:兴奋 \^o^/,感觉好有趣哦,原来Python还可以做这些
整个过程我听的十分开心,但emmm老师做出来的界面作为零零后真是看不了
- "这界面太丑了!":看着老师做的简陋图书管理系统,内心疯狂OS,必须换个颜色 ~
- 灵机一动:想加个"小弹窗"功能,就跟平时差不多的那种
- AI生成代码暴击:
😵 当场懵圈:像看天书,连问题都不知道怎么问class BookManager(tk.Tk): # 这堆class、self是啥?? def __init__(self): # 下划线又是干嘛的?? self.books = [] # 为什么这里要加self??
🛠️ 硬核破解过程
-
绝望阶段:
- 每行代码问AI,结果解释更晕(AI以为我懂OOP)
- 报错连发,界面要么空白要么崩溃
# 典型错误:按钮忘了pack() btn_add = Button(text="添加") # 按钮呢??? -
神助攻时刻:
- 课堂上老师突然讲class,瞬间开窍:
graph LR A[类] --> B[属性] A --> C[方法] D[对象] --> E["book1 = BookManager()"] - 悟了:
self就是对象的"分身术",__init__是"出生证明"
- 课堂上老师突然讲class,瞬间开窍:
🎉 胜利时刻
最终成果:
- ✨ 清爽的界面+丝滑弹窗添加
- 💾 关闭程序后数据自动保存
- 🚀 意外收获:
顺带学会了try-except处理异常(因为总有人输错数据类型)
成长暴击:
"原来我不是学不会,只是缺了那堂刚好讲到痛点的课!"
以下是我最终的代码(点击查看)
import tkinter as tk
import json
import os
from tkinter import ttk,messagebox
#class 子类名(父类名):是个典型的类继承用法
#继承父类的所有属性(窗口标题,大小,位置等)和方法(destroy(),geometry(),title(),)并可以拓展或修改
#无需手动实现窗口的基本功能(关闭按钮,拖动),直接复用tk.Toplevel的能力
#所有弹出窗口都基于tk.Toplevel,保持界面风格的一致
添加图书的弹窗
class AddBookWindow(tk.Toplevel):
def __init__(self,parent,callback):
super().__init__(parent)
self.title("添加图书")
self.callback=callback
#设置窗口位置和大小
# 父窗口的X坐标向右偏移50像素 父窗口的Y坐标向下偏移50像素
self.geometry("400x250+%d+%d"%(parent.winfo_x() + 50, parent.winfo_y() + 50))
# 用于设置窗口是否允许用户通过鼠标拖动 ** 横向(宽度)和纵向(高度)** 调整大小
self.resizable(False,False)
#表单框架
form_frame=tk.Frame(self,padx=10,pady=10)
form_frame.pack(fill=tk.BOTH,expand=True)
#表单元素
tk.Label(form_frame,text="title").grid(row=0,column=0,sticky='e',pady=5)
self.title_entry=ttk.Entry(form_frame,width=30)
self.title_entry.grid(row=0,column=1,pady=5,padx=5)
tk.Label(form_frame, text="author").grid(row=1, column=0, sticky='e', pady=5)
self.author_entry = ttk.Entry(form_frame, width=30)
self.author_entry.grid(row=1, column=1, pady=5, padx=5)
tk.Label(form_frame, text="price").grid(row=2, column=0, sticky='e', pady=5)
self.price_entry = ttk.Entry(form_frame, width=30)
self.price_entry.grid(row=2, column=1, pady=5, padx=5)
#按钮区域
button_frame=tk.Button(self,pady=10)
button_frame.pack()
tk.Button(button_frame, text="确认添加", width=10, command=self.confirm_add).pack(side=tk.LEFT, padx=10)
tk.Button(button_frame, text="取消", width=10, command=self.destroy).pack(side=tk.LEFT, padx=10)
#设置焦点
self.title_entry.focus_set()
def confirm_add(self):
"""确认添加图书"""
title=self.title_entry.get().strip()
author=self.author_entry.get().strip()
price=self.price_entry.get().strip()
if not title:
messagebox.showerror("错误","书名不能为空!",parent=self)
return
#换回添加的图书数据
self.callback({
"title":title,
"author":author,
"price":price
})
self.destroy()
编辑图书的弹窗
class EditBookWindow(tk.Toplevel):
def __init__(self,parent, book_data,callback):
super().__init__(parent)
self.title("编辑图书")
self.callback=callback
#设置窗口位置和大小
self.geometry("400x250+%d+%d" % (parent.winfo_x() + 50, parent.winfo_y() + 50))
self.resizable(False, False)
#表单框架
form_frame = tk.Frame(self, padx=10, pady=10)
form_frame.pack(fill=tk.BOTH, expand=True)
#表单元素
tk.Label(form_frame,text="书名:").grid(row=0,column=0,pady=5,sticky="e")
self.title_entry=ttk.Entry(form_frame,width=30)
self.title_entry.insert(0,book_data["title"])
self.title_entry.grid(row=0,column=1,pady=5,padx=5)
tk.Label(form_frame, text="作者:").grid(row=1, column=0, pady=5, sticky="e")
self.author_entry = ttk.Entry(form_frame, width=30)
self.author_entry.insert(0, book_data["author"])
self.author_entry.grid(row=1, column=1, pady=5, padx=5)
tk.Label(form_frame, text="价格:").grid(row=2, column=0, pady=5, sticky="e")
self.price_entry = ttk.Entry(form_frame, width=30)
self.price_entry.insert(0, book_data["price"])
self.price_entry.grid(row=2, column=1, pady=5, padx=5)
#按钮区域
button_frame=tk.Frame(self,pady=10)
button_frame.pack()
tk.Button(button_frame,text="确认修改",width=10, command=self.confirm_edit).pack(side=tk.LEFT, padx=10)
tk.Button(button_frame, text="取消", width=10, command=self.destroy).pack(side=tk.LEFT, padx=10)
def confirm_edit(self):
"""确认编辑图书信息"""
title = self.title_entry.get().strip()
author = self.author_entry.get().strip()
price = self.price_entry.get().strip()
self.callback({
"title": title,
"author": author,
"price": price
})
self.destroy()
搜索图书的弹窗
class SearchBookWindow(tk.Toplevel):
def __init__(self,parent,callback):
super().__init__(parent)
self.title("搜索图书")
self.callback=callback
#设置窗口位置和大小
self.geometry("350x150+%d+%d"%(parent.winfo_x()+50,parent.winfo_y()+50))
self.resizable(False,False)
#表单框架
form_frame=tk.Frame(self,pady=10,padx=10)
form_frame.pack(fill=tk.BOTH,expand=True)
#搜素条件
tk.Label(form_frame,text="搜索条件:").grid(row=0,column=0,sticky='w',pady=5)
#提供可选的搜索字段(书名、作者),用户只能选择预设值(state="readonly" 禁止手动输入)
#current(0) 设置默认搜索字段为 书名
self.search_field=ttk.Combobox(form_frame,values=['书名','作者'],state="readonly")
self.search_field.current(0)
#水平方向拉伸(sticky="we"),适应不同屏幕尺寸
self.search_field.grid(row=1,column=0,padx=5,pady=5,sticky="we")
self.search_entry=ttk.Entry(form_frame,width=25)
self.search_entry.grid(row=1,column=1,padx=5,pady=5,sticky="we")
# 按钮区域
button_frame = tk.Frame(self, pady=10)
button_frame.pack()
tk.Button(button_frame, text="搜索", width=10, command=self.do_search).pack(side=tk.LEFT, padx=10)
tk.Button(button_frame, text="取消", width=10, command=self.destroy).pack(side=tk.LEFT, padx=10)
#设置焦点,使运行一开始光标在输入框内
self.search_entry.focus_set()
def do_search(self):
"""执行搜索"""
field=self.search_field.get()
keyword=self.search_entry.get().strip()
if not keyword:
messagebox.showerror("错误", "请输入搜索关键词!", parent=self)
return
# 返回搜索条件和关键词
self.callback(field, keyword)
self.destroy()
主界面
class LibraryManager:
```python
def __init__(self,root):
self.root=root
self.root.title("图书管理系统")#左上角题目
#设置主界面
# 将窗口大小设置成宽600高400,左边框距离屏幕左边300,上边框距离屏幕上边300
self.root.geometry('600x400+300+300')
#数据存储文件,指定图书数据的存储文件名为books_data.json
#.json,用于存储结构化数据(如字典,列表)
self.data_file="books_data.json"
self.books=self.load_books()
self.create_widgets()
def load_books(self):
"""从json文件中读取数据"""
#检查数据文件是否存在
if os.path.exists(self.data_file):
try:
#存在则尝试以utf-8编码打开并解析json数据
with open(self.data_file,'r',encoding="utf-8") as f:
#json.load(f)从文件对象f中读取json格式的文本,并将其反序列化为py对象(如字典,列表)
return json.load(f)
except:
messagebox.showerror("错误","数据文件损坏,已创建空列表")
#读取失败或文件不存在时,返回默认列表
return [
{"title": "Python编程", "author": "Eric ", "price": 89.0},
{"title": "C语言程序设计", "author": "Luciano ", "price": 128.0},
{"title": "计算机系统", "author": "Randal ", "price": 136.0}
]
def save_books(self):
"""保存图书数据到json文件"""
try:
with open(self.data_file,'w',encoding='utf-8') as f:
# 将 self.books 序列化为 JSON 格式并写入文件
#ensure_ascii=False 不强制将非 ASCII 字符转义为 \uXXXX 格式,确保中文正常显示。
#indent=2 缩进空格数,使生成的 JSON 文件更易读(若为 None 则压缩为一行)
json.dump(self.books,f,ensure_ascii=False,indent=2)
except Exception as e:
#str(e) 返回异常的具体信息
messagebox.showerror("保存失败",f"无法保存数据:{str(e)}")
def create_widgets(self):
# 顶端的标题标签组件
# font=('字体名',大小,'样式') font=('Microsoft YaHei',20,'bold')微软雅黑 20 加粗
label_title = tk.Label(master=self.root, text='图书馆管理系统', font=('', 20, ''))
label_title.pack(side=tk.TOP, fill=tk.BOTH)
# 左侧的所有的增删改查按钮组件的父容器,位于窗口的左侧
frame_left = tk.Frame(master=self.root, padx=30, pady=60, width=60, background='lightblue')
frame_left.pack(side=tk.LEFT, fill=tk.Y)
# 添加按钮
tk.Button(master=frame_left, text='添加书籍',
command=lambda: AddBookWindow(self.root, self.add_book)).pack()
tk.Button(master=frame_left, text='删除书籍',command=self.delete_book).pack()
tk.Button(master=frame_left, text='编辑书籍',command=self.edit_book).pack()
tk.Button(master=frame_left, text='查询书籍',
command=lambda: SearchBookWindow(self.root, self.search_book)).pack()
tk.Button(frame_left, text="刷新列表", command=self.refresh_list).pack()
# 整个右侧Frame容器
frame_right = tk.Frame(master=self.root, background='white')
frame_right.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# 使用Treeview显示表格数据,定义三列
#show:控制显示模式,headings仅显示标题(无树状结构)“”:显示树状结构和列标题
self.tree = ttk.Treeview(frame_right, columns=("title", "author", "price"), show="headings")
# 设置列宽和标题
#anchor="w"左对齐 “e”右对齐
self.tree.column("title", width=200, anchor="w")
self.tree.heading("title", text="书名")
self.tree.column("author", width=150, anchor="w")
self.tree.heading("author", text="作者")
self.tree.column("price", width=80, anchor="e")
self.tree.heading("price", text="价格")
# 🌟 添加布局代码:将Treeview放置到frame_right中,并填充整个区域
self.tree.pack(fill=tk.BOTH, expand=True)
#双击触发编辑图书
#使用lambda延迟执行,确保仅在触发时调用
"""
<Button-1>:鼠标左键单击。
<Double-1>:鼠标左键双击(本例使用)。
<Triple-1>:鼠标左键三击。
"""
self.tree.bind("<Double-1>", lambda e: self.edit_book())
# 初始化列表
self.refresh_list()
def add_book(self,book_data):
"""添加新图书"""
self.books.append(book_data)
self.refresh_list()
self.save_books()
messagebox.showinfo("成功","图书添加成功",parent=self.root)
def refresh_list(self,books=None):
"""刷新图书列表"""
#允许外部传入自定义数据列表(如搜索结果),若未传入(books=None),
# 则默认使用主类的 self.books 列表(存储所有图书数据)。
if books is None:
books = self.books
# 清空现有数据为插入新数据做准备。若不清空,新数据会追加到原有数据后,导致重复显示。
#self.tree.get_children():获取 Treeview 中所有行的 ID。
#self.tree.delete(item):删除指定 ID 的行。
for item in self.tree.get_children():
self.tree.delete(item)
# 添加新数据
#parent:父项 ID("" 表示根节点)。
#index:插入位置(tk.END 表示末尾)。
#values:元组,按列顺序填入数据。
for book in books:
self.tree.insert("", tk.END, values=(
book["title"],
book["author"],
book['price']
))
#从 book 字典中提取 title、author、price 三个字段的值,按顺序插入表格的三列。
def edit_book(self):
"""编辑图书"""
#获取当前 Treeview 中选中的行(返回行 ID 的元组)
selection=self.tree.selection()
# 检查是否有单击Treeview表格中的图书,获取ID
if not selection:
messagebox.showerror("错误","请选择要编辑的图书!",parent=self.root)
return
#tree.index(iid):获取行 ID 对应的索引位置(与self.books列表的索引一致)。
index=self.tree.index(selection[0])
#self.books[index]:从内存数据中提取选中图书的完整信息(字典)。
selection_book=self.books[index]
def update_book(updated_data):
"""更新图书信息"""
self.books[index]=updated_data
self.refresh_list()
self.save_books()
messagebox.showinfo("成功","图书信息已更新!",parent=self.root)
EditBookWindow(self.root,selection_book,update_book)
def delete_book(self):
"""删除图书"""
selection=self.tree.selection()
if not selection:
messagebox.showerror("错误","请选择你要删除的图书!",parent=self.root)
index=self.tree.index(selection[0])
book_title=self.books[index]["title"]
if messagebox.askyesno("确定",f'确定要删除《{book_title}》吗?',parent=self.root):
del self.books[index]
self.refresh_list()
self.save_books()
messagebox.showinfo("成功","图书已删除!",parent=self.root)
def search_book(self,field,keyword):
"""搜索图书"""
#字段映射是连接用户界面与数据模型的关键环节
#定义映射字典,将用户界面中显示的中文搜索字段(如 "书名")映射为数据模型中的英文键名(如 "title")
#使用字典(dict)实现快速键值对查找,时间复杂度为 O (1)。
field_map={
"书名":"title",
"作者":"author"
}
#获取映射后的字段键
"""参数说明:
field:用户选择的搜索字段(如 "书名")。
"title":默认值(当映射失败时返回)。
方法逻辑:
field_map.get(field):从字典中查找 field 对应的英文键。
若查找失败(如用户传入 "出版社"),返回默认值 "title"。"""
field_key=field_map.get(field,"title")
#将用户输入的关键字转为小写,实现不区分大小写的搜索
keyword=keyword.lower()
#模糊匹配 是实现灵活搜索的核心逻辑
#列表推导式结构
results=[
book for book in self.books
if keyword in book[field_key].lower()
]
#匹配规则:只要 keyword 是字段值的连续子串即可,无需完全相等。
# 只要是含有关键字的都会筛选出来
if not results:
messagebox.showinfo("提示","没有找到匹配的图书!",parent=self.root)
return
self.refresh_list(results)
messagebox.showinfo("成功",f"找到{len(results)}本匹配的图书!",parent=self.root)
if __name__=="__main__":
root=tk.Tk()
#app是对象即self
app=LibraryManager(root)
#设置窗口关闭时的保存操作
def on_closing():
app.save_books()
root.destroy()
#root.protocol() 方法 设置窗口管理器(Window Manager)的特定协议处理方式。
#"WM_DELETE_WINDOW"点击窗口关闭按钮("X")或调用系统关闭操作时触发,最常用的自定义关闭协议。
#callback:回调函数(当协议被触发时执行)
root.protocol("WM_DELETE_WINDOW",on_closing)
root.mainloop()
"""类中的方法实际上获取弹窗上的信息,并通过回调函数传回主类,主类中的方法又使信息显示在主界面中(即刷新列表)"""
运行结果:
🔆“如果大家有什么好点子,欢迎讨论呀!”
💻 自学计算机一年的真实感悟
-
别当孤勇者,善用资源
- 曾经觉得"自学=全靠自己",后来发现老师的一句点拨能省三天查资料时间
- 血泪教训:遇到卡壳可以问人(同学/老师/论坛),别像我硬啃半天把自己气死
-
与bug和解的智慧
- 当代码第20次报
SyntaxError时:
✅ 保存文件→关机→去操场跑三圈
❌ 继续死磕(只会写出更多bug) - 顿悟时刻:往往发生在洗澡/吃饭时,大脑在后台默默debug
- 当代码第20次报
-
知识吞噬者的成长
学计算机就像训练AI模型:① 📥 疯狂投喂知识
↓
② 🖥️ 大脑CPU过载 → 产生"我是智障"错觉
↓
③ 🌅 某天突然开光 → 原来如此!
↓
④ 🤣 回顾旧代码 → 笑出鹅叫
🌟 终极发现:
那些看似绕的远路,都变成了神经元的连接路径——
没有白学的知识,只有还没用上的技能包 💻✨