为什么没人告诉我,学计算机=天天和电脑对骂?

201 阅读26分钟

---大一菜鸟的真实生存报告

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*

error1.png 图2

error2.png 图3

error3.png 图4

error4.png 图5

error5.png

💢我(怒敲键盘): "你是不是针对我??"
💻电脑(冷漠脸): Segmentation fault (core dumped)

这一刻我终于悟了:
学计算机=和一台没有感情的机器进行极限拉扯(而且它永远赢) ←_←

初生牛犊不怕虎!

如果:
💻 日常debug = 小怪练级
那么:
🏆 蓝桥杯 = 第一个副本Boss

大一的我,激情澎湃,满脑子都是:
竞赛!竞赛! 以赛促学!(毕竟老师说过,比赛是最好的动力嘛)( ̄︶ ̄)↗ 

于是——
💻查比赛:
ACM? 团队赛emm...算了没人
蓝桥杯? 个人赛!就它了!
🤔选赛道: 这学期刚学C语言,好,报C语言组!
看到报名费: 300块?!(⊙o⊙)💸
转念一想: “没事!花了钱就会好好准备,就算没奖过程最重要!”(^▽^)

现实暴击:考纲劝退

🖥️ 打开比赛大纲 大纲.jpg→ 满屏专业名词直接触发视觉缓冲区溢出
(此时大脑状态: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=82a15966f1d7954e3bcb46ebdfd24f70

STEP 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;
}

在啃排序算法的过程中,我算是明白了计算机科学的知识网状结构——当想弄明白一个陌生术语时,总会拉出一堆未知概念(比如学堆排序需要先理解完全二叉树,学桶排序又得了解哈希映射)。这种递归式学习对于一个新手小白来说太痛苦啦~😣😣😣

这种知识递归现象带来的挑战:

  1. 学习路径模糊:没人告知应该先掌握哪些前置知识
  2. 调试挫败感:代码看似正确却莫名报错

“大家有什么好的学习方法或者学习工具,求分享哇~”(づ ̄ 3 ̄)づ

🚨 时间紧迫警报!
寒假肝完十大排序,抬头一看——WPS二级考试蓝桥杯像两个Boss同时刷新了!

💥 紧急作战计划启动:

  • 两周极限操作
    • 白天狂刷WPS真题("合并单元格"是我的新咒语)
    • 晚上恶补算法速成套餐:
      █ DFS/BFS:走迷宫式突击  
      █ 贪心算法:能贪就贪,绝不DP  
      █ 二分法:万物皆可二分  
      
    • 班里部门里各种支线任务

🎮 赛场实况直播:

  • 比赛心态:稳如老狗
  • 战术部署:硬磕四个小时,放弃最终Boss(最后两题),专注收割小兵(前四题)
  • 战果汇报
    ✅ 1道填空题(稳!)
    ✅ 1道大题全歼(15分到手!)
    ⚠️ 3道大题残血(拿个参与分)
  • 最终评级省三勋章get√

💡 暴击后的觉醒:
比赛代码 = 80%的for/if + 20%的算法,但那20%才是区分青铜和王者的关键

🌟 给后来者的血泪指南:

+ 必做:  
  1. 提前了解比赛各种信息,避免信息差
  2. 不管是学习还是练题要有做笔记的习惯(不要像我现在才做)
- 避坑:  
  1. 别信"蓝桥杯很水"的鬼话(说水的都是大佬)  
  2. 不要像我一样考前才刷真题(会哭)  

🎯 终极感悟:
奖状的水分会被时间晒干,但是我整个备赛过程中学到的东西是实实在在的,这是不水的,我觉得这就是竞赛的意义吧!^o^y

🔆彩蛋
某堂高数课我突然想起好像有个东西没...选...

屏幕截图 2025-06-15 203824.png
(抱头)为什么我脑子里全是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老师做出来的界面作为零零后真是看不了

屏幕截图 2025-06-16 123155.png

  • "这界面太丑了!":看着老师做的简陋图书管理系统,内心疯狂OS,必须换个颜色 ~
  • 灵机一动:想加个"小弹窗"功能,就跟平时差不多的那种
  • AI生成代码暴击
    class BookManager(tk.Tk):  # 这堆class、self是啥??
        def __init__(self):  # 下划线又是干嘛的??
            self.books = []  # 为什么这里要加self??
    
    😵 当场懵圈:像看天书,连问题都不知道怎么问

🛠️ 硬核破解过程

  1. 绝望阶段

    • 每行代码问AI,结果解释更晕(AI以为我懂OOP)
    • 报错连发,界面要么空白要么崩溃
    # 典型错误:按钮忘了pack()
    btn_add = Button(text="添加")  # 按钮呢???
    
  2. 神助攻时刻

    • 课堂上老师突然讲class,瞬间开窍:
      graph LR
      A[类] --> B[属性]
      A --> C[方法]
      D[对象] --> E["book1 = BookManager()"]
      
    • 悟了:self就是对象的"分身术",__init__是"出生证明"

🎉 胜利时刻

最终成果

  • ✨ 清爽的界面+丝滑弹窗添加
  • 💾 关闭程序后数据自动保存
  • 🚀 意外收获
    顺带学会了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()

"""类中的方法实际上获取弹窗上的信息,并通过回调函数传回主类,主类中的方法又使信息显示在主界面中(即刷新列表)"""

运行结果:

屏幕截图 2025-06-16 180221.png

🔆“如果大家有什么好点子,欢迎讨论呀!”

💻 自学计算机一年的真实感悟

  1. 别当孤勇者,善用资源

    • 曾经觉得"自学=全靠自己",后来发现老师的一句点拨能省三天查资料时间
    • 血泪教训:遇到卡壳可以问人(同学/老师/论坛),别像我硬啃半天把自己气死
  2. 与bug和解的智慧

    • 当代码第20次报SyntaxError时:
      ✅ 保存文件→关机→去操场跑三圈
      ❌ 继续死磕(只会写出更多bug)
    • 顿悟时刻:往往发生在洗澡/吃饭时,大脑在后台默默debug
  3. 知识吞噬者的成长
    学计算机就像训练AI模型

    ① 📥 疯狂投喂知识

    ② 🖥️ 大脑CPU过载 → 产生"我是智障"错觉

    ③ 🌅 某天突然开光 → 原来如此!

    ④ 🤣 回顾旧代码 → 笑出鹅叫

🌟 终极发现
那些看似绕的远路,都变成了神经元的连接路径——
没有白学的知识,只有还没用上的技能包 💻✨