叹气

异步编程作业

using System; using System.Diagnostics; using System.Threading.Tasks; namespace BreakfastMakerSystem { public class Item<T> { public string Name { get; } // 物品名称 (公开只读) // 例如: "咖啡", "鸡蛋" public T Spec { get; } // 物品规格 (泛型,公开只读) // 例如: 对于咖啡, T是int, Spec是容量(ml); 对于土豆, T是string, Spec是描述。 public Item(string name, T spec) { Name = name; Spec = spec; } // 构造函数,用于初始化物品。 } public class BreakfastMaker { #region 委托与事件定义 public delegate Task CookingStep(); // 定义一个委托,用于封装所有无参数、返回Task的烹饪步骤。 // 委托就像一个方法的“模板”或“签名”,所有符合这个签名的方法都可以被它引用。 // 返回 Task 是为了适配异步方法。 public delegate void StepStartedHandler(string stepName); // 定义步骤开始事件的委托。 // 它规定了所有订阅 StepStarted 事件的方法必须接受一个 string 参数 (步骤名) 且无返回值。 public delegate void StepCompletedHandler(string stepName, TimeSpan duration); // 定义步骤完成事件的委托。 // 它规定了所有订阅 StepCompleted 事件的方法必须接受 string (步骤名) 和 TimeSpan (耗时) 两个参数。 public event StepStartedHandler StepStarted; // 步骤开始事件。 // 外部代码可以 "订阅" (+=) 这个事件,当事件被 "触发" 时,所有订阅的方法都会被调用。 // 这是一种典型的 "发布-订阅" 模式,实现了逻辑解耦。 public event StepCompletedHandler StepCompleted; // 步骤完成事件。 #endregion #region 异步制作方法 public async Task PourCoffeeAsync(Item<int> coffee) { Stopwatch stopwatch = new(); StepStarted?.Invoke("倒咖啡"); // 触发步骤开始事件,通知所有订阅者 await Task.Delay(500); // 使用 Task.Delay 模拟倒咖啡的耗时操作 (500毫秒) Console.WriteLine($"倒入 {coffee.Spec}ml {coffee.Name}"); stopwatch.Stop(); StepCompleted?.Invoke("倒咖啡", stopwatch.Elapsed); // 触发步骤完成事件,并传递耗时 } // 异步倒咖啡 public async Task FryEggsAsync(Item<int> eggs) { var stopwatch = Stopwatch.StartNew(); StepStarted?.Invoke("煎鸡蛋"); for (int i = 0; i < eggs.Spec; i++) { Console.WriteLine($"开始煎第 {i + 1} 个鸡蛋..."); await Task.Delay(1500); // 模拟煎一个鸡蛋耗时1.5秒 Console.WriteLine($"煎好第 {i + 1} 个鸡蛋"); } // 根据鸡蛋数量,循环模拟煎鸡蛋的过程 stopwatch.Stop(); StepCompleted?.Invoke("煎鸡蛋", stopwatch.Elapsed); } // 异步煎鸡蛋 public async Task BakeHashBrownsAsync(Item<string> potato) { var stopwatch = Stopwatch.StartNew(); StepStarted?.Invoke("烤薯饼"); await Task.Delay(3000); // 模拟烤薯饼的耗时 (3秒) Console.WriteLine($"用 {potato.Spec} 烤好薯饼"); stopwatch.Stop(); StepCompleted?.Invoke("烤薯饼", stopwatch.Elapsed); } // 异步烤薯饼 public async Task PlateAsync() { var stopwatch = Stopwatch.StartNew(); StepStarted?.Invoke("装盘"); await Task.Delay(1000); // 模拟装盘耗时 Console.WriteLine("将鸡蛋和薯饼装入餐盘"); stopwatch.Stop(); StepCompleted?.Invoke("装盘", stopwatch.Elapsed); } // 异步装盘 public async Task PourJuiceAsync(Item<string> juice) { var stopwatch = Stopwatch.StartNew(); StepStarted?.Invoke("倒果汁"); await Task.Delay(800); // 模拟倒果汁耗时 Console.WriteLine($"倒入 {juice.Spec}"); stopwatch.Stop(); StepCompleted?.Invoke("倒果汁", stopwatch.Elapsed); } // 异步倒果汁 #endregion } class Program { static async Task Main(string[] args) // Main 方法标记为 async Task,因为我们需要在其中使用 await 来等待异步操作完成。 { Console.WriteLine("--- 早餐制作流程管理系统启动 ---"); var totalStopwatch = Stopwatch.StartNew(); // 创建一个总计时器,计算整个流程的耗时 // (1) 创建 BreakfastMaker 实例和物品实例 var maker = new BreakfastMaker(); var coffee = new Item<int>("咖啡", 200); var eggs = new Item<int>("鸡蛋", 2); var potato = new Item<string>("土豆", "中等大小土豆"); var juice = new Item<string>("果汁", "苹果汁"); // (2) 订阅事件 maker.StepStarted += (stepName) => { Console.ForegroundColor = ConsoleColor.Yellow; // Console.ForegroundColor 可以在控制台输出不同颜色的文本,便于观察 Console.WriteLine($"[开始] {stepName}"); Console.ResetColor(); }; maker.StepCompleted += (stepName, duration) => { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine($"[完成] {stepName} (耗时: {duration.TotalSeconds:F1} 秒)"); // duration.TotalSeconds: 将 TimeSpan 转换为秒,并保留小数部分。F1表示格式化为1位小数。 Console.ResetColor(); }; // 使用 Lambda 表达式为事件添加处理逻辑,当事件被触发时,这些代码块会被执行。 // (3) 组织与执行步骤 Console.WriteLine("\n--- 开始制作早餐 ---"); // --- 阶段 1 (并行执行) --- Console.WriteLine("\n--- 阶段 1: 并行准备咖啡、鸡蛋和薯饼 ---"); Task pourCoffeeTask = maker.PourCoffeeAsync(coffee); Task fryEggsTask = maker.FryEggsAsync(eggs); Task bakeHashBrownsTask = maker.BakeHashBrownsAsync(potato); // 这三个步骤(倒咖啡、煎鸡蛋、烤薯饼)没有相互依赖,可以同时进行。 // 我们为每个步骤创建一个 Task。Task在这里可以理解为一个“工作票据”,代表一个将要完成的工作。 await Task.WhenAll(pourCoffeeTask, fryEggsTask, bakeHashBrownsTask); // Task.WhenAll 会等待传入的所有 Task 都完成后再继续执行。 // 这就是实现并行执行的关键。程序不会在这里卡住,而是会“让出”线程去做其他事, // 等到这三个任务都完成后,再回到这里继续往下走。 Console.WriteLine("--- 阶段 1 全部完成 ---"); // --- 阶段 2 (顺序执行) --- Console.WriteLine("\n--- 阶段 2: 装盘 ---"); await maker.PlateAsync(); // 装盘必须在煎鸡蛋和烤薯饼之后。由于上一步的 await Task.WhenAll 保证了这两个任务已完成, // 所以我们可以在这里安全地执行装盘。 Console.WriteLine("--- 阶段 2 完成 ---"); // --- 阶段 3 (顺序执行) --- Console.WriteLine("\n--- 阶段 3: 倒果汁 ---"); await maker.PourJuiceAsync(juice); // 倒果汁在装盘之后进行。 Console.WriteLine("--- 阶段 3 完成 ---"); totalStopwatch.Stop(); // 停止总计时器 Console.WriteLine("\n--- 早餐制作完成 ---"); Console.ForegroundColor = ConsoleColor.Cyan; Console.WriteLine($"\n>>> 总耗时: {totalStopwatch.Elapsed.TotalSeconds:F1} 秒"); Console.ResetColor(); Console.WriteLine("\n按任意键退出..."); Console.ReadKey(); } } }

November 16, 2025 · 3 min · xiao

流量记录工具指南

vnstat 简单地记录服务器整个网卡的总流入/流出流量 查看报告: 首先用 ip a 或 ifconfig 找到你的主网卡名(通常是 eth0 或 ens3 等)。 vnstat 会自动开始监控。等待几分钟后,就可以查看报告了。 # 查看实时流量 (刷新频率较低) vnstat -l # 查看日流量报告 vnstat -d # 查看月流量报告 vnstat -m iftop (监控实时网速) 使用: 运行 iftop: # -i 网卡名 sudo iftop -i eth0 啊哈!原来是 iftop!非常感谢您的指正,我的识别出现了偏差。iftop 是一款殿堂级的、专注于网络连接和带宽实时监控的神器。 那么,之前我们关于 gotop 的讨论就不适用了。iftop 的操作和界面解读有其自己的一套逻辑,它更加专业和聚焦。 您截图中的界面,正是 iftop 的经典视图。现在,我们来重新、正确地解读它,并告诉您它的操作按键。 iftop 界面元素重解 (The Correct Version) 顶部的“进度条” (Bandwidth Scale) 这确实是一个带宽标尺。它的作用是给你一个直观的参考,让你能快速估算每个连接的流量大小。 默认情况下,这是一个对数刻度 (logarithmic scale),这样可以同时显示从几 Kbps 到几百 Mbps 的巨大速度范围。 关键操作:你可以按 L 键来关闭/开启这个对数刻度,切换到线性刻度,但通常对数刻度更好用。 中间的连接列表 (Connection List) ...

November 7, 2025 · 2 min · xiao
一张写着'毫无品味'四个字的图片

英语语法综述

英语语法核心框架梳理 这份笔记旨在构建一个清晰、系统的英语语法知识体系。将从最宏观的句子结构入手,逐步深入到具体的词法和动词系统,帮助理解英语语法的底层逻辑。 第一部分:句子核心结构——五大基本句型 任何一个完整的英语句子都必须包含主语 (Subject)和谓语 (Predicate)。 主语:句子的发出者或描述的对象,通常由名词、代词等充当。 谓语:说明主语的动作或状态,其核心是动词 (Verb)。 基于谓语动词性质的不同,所有简单句都可以归纳为以下五种基本结构。 句型一:S + V (主语 + 谓语) 结构:主语 (Subject) + 不及物动词 (Intransitive Verb) 说明:谓语动词本身能表达完整的意思,不需要承受动作的对象(宾语)。动作仅限于主语自身。 示例: The sun rises. (太阳升起。) He smiled. (他笑了。) 句型二:S + V + O (主语 + 谓语 + 宾语) 结构:主语 (Subject) + 及物动词 (Transitive Verb) + 宾语 (Object) 说明:谓语动词所表示的动作需要一个直接的承受者,即宾语。 示例: She reads a book. (她读一本书。) I love English. (我爱英语。) 句型三:S + V + iO + dO (主语 + 谓语 + 间接宾语 + 直接宾语) 结构:主语 (S) + 及物动词 (V) + 间接宾语 (indirect Object) + 直接宾语 (direct Object) 说明:这类动词需要两个宾语。直接宾语是动作的直接对象(物),间接宾语是动作的接收者(人)。记忆技巧:动词 + 给谁 + 什么。 示例: My father bought me a new phone. (我父亲给我买了一部新手机。) He told us a story. (他给我们讲了一个故事。) 句型四:S + V + O + C (主语 + 谓语 + 宾语 + 宾语补足语) 结构:主语 (S) + 及物动词 (V) + 宾语 (O) + 宾语补足语 (Object Complement) 说明:宾语补足语用于补充说明宾语的状态、身份或特征。宾语和宾补在逻辑上构成主谓或主表关系(O+C = 一个完整信息)。 示例: The news made him happy. (这个消息让他很高兴。) -> 逻辑上 him is happy We elected him our monitor. (我们选他当我们的班长。) -> 逻辑上 him is our monitor 句型五:S + V + P (主语 + 系动词 + 表语) 结构:主语 (Subject) + 系动词 (Linking Verb) + 表语 (Predicative) 说明:系动词没有实际动作意义,主要起连接作用,将主语和表语联系起来,说明主语的身份、性质或状态。表语也叫主语补足语。 示例: He is a doctor. (他是一名医生。) The food tastes delicious. (食物尝起来很美味。) 第二部分:句子成分的扩展——修饰语 在基本句型骨架之上,我们可以添加修饰成分使句子信息更丰富。 ...

September 11, 2025 · 3 min · xiao

补考内容

7-1 栈操作的合法性 #include <stdio.h> int main() { int n,m,c,i,length; scanf("%d %d ",&n,&m); for(i=0;i<n;i++){ char a; int c=0,length=0; while((a=getchar())!='\n'){ if(a=='S'){ length++; if(length>m){ c=1; } } if(a=='X'){ length--; if(length<0){ c=1; } } } if(c==0&&length==0){ printf("YES\n"); }else{ printf("NO\n"); } } return 0; } 7-3 合并有序数组 #include <stdio.h> #include <stdlib.h> int main(){ int m,n,i; int *arr1,*arr2; scanf("%d",&m); arr1=(int*)malloc(m*sizeof(int)); if(arr1==NULL){ return 1; } for(i=0;i<m;i++){ scanf("%d",&arr1[i]); } arr2=(int*)malloc(n*sizeof(int)); scanf("%d",&n); if(arr2==NULL){ free(arr1); return 1; } for(i=0;i<n;i++){ scanf("%d",&arr2[i]); } int j=0; i=0; while(i<m || j<n){ if(i>0 || j>0){ printf(" "); } if(i<m && (arr1[i]<=arr2[j] || j>=n)){ printf("%d",arr1[i]); i++; }else{ printf("%d",arr2[j]); j++; } } printf("\n"); free(arr1); free(arr2); return 0; } 7-4 顺序表的查找 #include <stdio.h> #include <stdlib.h> int main() { int n,i; int *arr1; scanf("%d", &n); arr1 = (int*)malloc(n * sizeof(int)); for(i=0;i<n;i++){ scanf("%d", &arr1[i]); } int target; while(scanf("%d", &target) == 1 && target != -1){ int found = 0; for(i=0;i<n;i++){ if(arr1[i] == target){ printf("%d ",i+1); found = 1; break; } } if(found==0){ printf("%d ",0); } } free(arr1); return 0; } 6-4 统计二叉树叶子结点个数 int LeafCount ( BiTree T) { if(T == NULL){ return 0; } if(T->lchild == NULL && T->rchild == NULL){ return 1; } else{ return LeafCount(T->lchild) + LeafCount(T->rchild); } } 6-1 二叉排序树的查找操作 BSTree SearchBST(BSTree T, ElemType e) { if(T == NULL || T->data == e){ return T; } if(T->data > e){ return SearchBST(T->lchild, e); } else { return SearchBST(T->rchild, e); } } 6-2 求二叉树的高度 int GetHeight( BinTree BT ) { if(BT==NULL){ return 0; }else{ int lt,rt,result; lt=GetHeight(BT->Left); rt=GetHeight(BT->Right); if(lt>rt){ result=lt; }else{ result=rt; } return result+1; } } 6-3 统计二叉树结点个数 int NodeCount ( BiTree T) { int count=0; if(T!=NULL){ count++; count+=NodeCount (T->Left); count+=NodeCount (T->Right); } return count; } 6-5 先序输出叶结点 void PreorderPrintLeaves( BinTree BT ) { if(BT==NULL){ return; }else{ if(BT->Left==NULL && BT->Right==NULL){ printf("%c",BT->Data); } PreorderPrintLeaves(BT->Left); PreorderPrintLeaves(BT->Right); } } R6-2 另类循环队列 bool AddQ(Queue Q,ElementType X){ if(Q->Count==Q->MaxSize){ printf("Queue Full\n"); return false; } Q->Data[(Q->Count+Q->Front)%Q->MaxSize]=X; Q->Count++; return true; } ElementType DeleteQ(Queue Q){ if(Q->Count==0){ printf("Queue Empty\n"); return ERROR; } int a=Q->Data[Q->Front]; Q->Count--; Q->Front=(Q->Front+1)%Q->MaxSize; return a; } R6-9 在一个数组中实现两个堆栈 Stack CreateStack(int MaxSize) { Stack S; S = (Stack)malloc(sizeof(struct SNode)); S->Data=(ElementType*)malloc(MaxSize*sizeof(ElementType)); S->MaxSize=MaxSize; S->Top1=-1; S->Top2=MaxSize; return S; } bool Push(Stack S,ElementType X,int Tag) { if(S->Top2-S->Top1==1){ printf("Stack Full\n"); return false; }else{ if(Tag==1){ S->Top1++; S->Data[S->Top1]=X; return true; } if(Tag==2){ S->Top2--; S->Data[S->Top2]=X; return true; } } } ElementType Pop(Stack S,int Tag) { if(Tag==1){ if(S->Top1==-1){ printf("Stack %d Empty\n",Tag); return ERROR; } int a=S->Data[S->Top1]; S->Top1--; return a; } if(Tag==2){ if(S->Top2==S->MaxSize){ printf("Stack %d Empty\n",Tag); return ERROR; } int a=S->Data[S->Top2]; S->Top2++; return a; } } R6-8 二叉树的三种遍历(先序、中序和后序) void Preorder(BiTree T) { if(T==NULL){ return; }else{ printf(" %c",T->data); Preorder(T->lchild); Preorder(T->rchild); } } void Inorder(BiTree T) { if(T==NULL){ return; }else{ Inorder(T->lchild); printf(" %c",T->data); Inorder(T->rchild); } } void Postorder(BiTree T) { if(T==NULL){ return; }else{ Postorder(T->lchild); Postorder(T->rchild); printf(" %c",T->data); } } R6-7 输出二叉树的所有叶子 void leaf(Bptr p) { if(p==NULL){ return; }else{ if(p->Lson==NULL&&p->Rson==NULL){ printf("%d ",p->data); } leaf(p->Lson); leaf(p->Rson); } } R6-10 递增的整数序列链表的插入 List Insert(List L,ElementType X) { List a; a=L; while(1){ if(a->Next!=NULL&&a->Next->Data>=X){ List newNode; newNode=(List)malloc(sizeof(struct Node)); newNode->Next=a->Next; newNode->Data=X; a->Next=newNode; return L; }else{ if(a->Next==NULL){ List newNode; newNode=(List)malloc(sizeof(struct Node)); newNode->Next=NULL; newNode->Data=X; a->Next=newNode; return L; } } a=a->Next; } } R6-12 统计二叉树度为1的结点个数 int NodeCount(BiTree T) { if(T==NULL){ return 0; } int count = 0; if((T->lchild!=NULL&&T->rchild==NULL)||(T->lchild==NULL&&T->rchild!=NULL)){ count=1; } return count+NodeCount(T->lchild)+NodeCount(T->rchild); }

August 24, 2025 · 3 min · xiao

我们原神真是太厉害啦

喜欢我原神吗? 你的素养很差,我现在每天玩原神都能赚150原石,每个月保底5000原石的收入,也就是现实生活中每个月5000美元的收入水平,换算过来最少也30000人民币,虽然我只有18岁,但是已经超越了中国绝大多数人(包括你)的水平,这便是原神给我的骄傲的资本。

August 8, 2025 · 1 min · xiao