[{"content":"第一次遇到把手机型号画得这么细致的番剧，也从里面了解到了不少小众爱好的知识，于是就突然有了想要总结一下里面有什么手机的想法） 动漫有放出来一些手机的设计图，从里面的参数很容易就能看出来一部分参考的型号。\n牡丹和茜的手机 牡丹和茜用的是一个型号的手机，正面刘海屏和双摄像头的设计很容易就能看出来是iPhone 13标准版或者iPhone 14标准版，这些参数与两台手机也是基本一致的。最后确定了一下应该是iPhone 14系列，iPhone 13的厚度是7.6毫米，iPhone 14的厚度是7.8毫米，后者会更接近一点设定里的8毫米，并且这个粉色也感觉是更像是iPhone 14。\n其实从动漫里看出来牡丹还是挺在意打扮的，用粉色也更合适一些，茜的性格比较随性一点，选择黑色真的挺适合她的性格的。iPhone 14标准版应该是第一代新瓶装旧酒的机型，14 Pro用的A16两年后就被砍掉一个核扔到标准版iPad上了，可以说是最没存在感的一代了）\n第一集里还出现了牡丹用的电脑，标志性的刘海屏基本可以确定是MacBook Air Ｍ2以后的机型了。不过貌似从M2开始这套模具就没变过了，颜色看起来也基本是一样的，没法具体确定到是哪台型号。我个人觉得应该是M2的，和iPhone 14是同一年出。\n说实话挺不喜欢这个刘海的，放在大屏幕上真的十分突兀，Air又不给塞Face ID。有钱了之后想收一台M2的MacBook Pro，目前除了Neo最后一代完整屏幕的MacBook了。牡丹似乎学的是计算机，用MacBook的话估计还得折腾homebrew什么的，不知道日本的大学是不是和我们一样从C开始教的。大三上Oracle数据库的时候就有一个用MacBook的女同学，因为用的数据库版本很老没有arm64的版本，直接用Docker跑不起来，她最后还是装了个Windows虚拟机跑的。\n而且牡丹用的是美式布局，日本人自己都觉得日式布局很别扭吗（划掉）\n至于牡丹的耳机，毫无疑问就是EarPods了吧，标志性的黑色长条。\n这么一看牡丹还是个用苹果半全家桶的富姐）\n伊吹和学姐的手机 这个手机基本上就是一眼能看出来的了，上下喇叭和有棱有角的设计，肯定就是Sony Xperia XZ1系列了。进一步考证之后，这个参数更接近Xperia XZ1 Compact的参数129x65x9.3，到这里基本上就能够下结论了，两个人用的就是xz1c。\n设定图里面写学姐的手很大手指又修长，但是用的却是这么小的手机，为了靠近自己喜欢的人真的很下功夫呢。\n（可惜最后还是成为了败犬）\nxz1c用的还是835，放在2027年已经是十年前的手机了，软件更新也已经停留在了Android 9，虽然说外国的软件相对来说没有那么臃肿，但是这么长时间恐怕835也是心有余而力不足了，加上电池老化也是走不掉的一环。所以伊吹为什么这么长时间不换手机呢，钱都拿去买酒喝了吗）\n（想想郡上学姐也能用下去这么卡的手机真的是为难她了）\n八重花和张景岚的手机 没想到还能在番剧里看到小折叠，这个外屏应该很标志性了，就是三星的Galaxy Z Flip 5/6，继续考证了一下橙色（官方是叫蜜桃粉）应该是只有6代有，更加倾向于八重花用的是六代。\n八重花不管是身体上还是性格上，在宿舍的几个人里感觉就像是一个小孩子，用了小折叠就更觉得是这样了。\n景岚用的这个摄像头一眼就能看出来了，灵动岛设计加上三摄，绝对是iPhone 14-16的Pro Max的其中任何一代。进一步比较之后我认为更可能是iPhone 15 Pro Max，第一个15pm的摄像头没有像14pm那样子那么大，第二个159.9x76.7x8.25的三围也更接近设定图里标出的参数。\n15 Pro Max的配色我觉得还是挺沉稳的，张姐本人的穿搭也差不多给人一种比较酷的感觉，但本人的性格却是正好反过来，有大就开，直接表达自己的想法，然后就直接攻略掉了学姐）\n在一集的ed里面还出现了小时候的景岚用的手机，摄像头很有标志性，可以一眼看出来是华硕的Zenfone7 。景岚是台湾来留学的，小时候用的也是本土品牌手机，这一点的细节上做得还是很到位的。\n结尾还出现了一个景岚姐姐用的平板电脑，说实话有点不太能找到这是什么型号，我个人倾向于这是Google Nexus 10，谷歌让三星代工的平板电脑，背面的摄像头和阴影都比较符合图片里的样子。\n两位新朋友的手机 第12话结尾出现了两位新朋友，虽然说只在动漫的ed中露了一次面，但是用的手机真的是给人一下子就留下了印象。\n从这张图应该就很容易看出来了吧，应该就是Nothing Phone (4a) Pro和iPhone Air，这两个手机在现实里都算挺小众的，在番剧里居然也能看到这样的小众机型，确实挺惊喜的，不过还没看过漫画，有点不太清楚两个人是什么性格。\n一点碎碎念 能看到这么讲究电子设备细节的番剧很让人开心，手机作为一个随身携带的物品多少也可以反映一个人自身的内在，在番剧里也能够看到这一点，原作者似乎就是一位程序员，把这些电子产品可以注重得这么细节一定程度上应该也有他的意思，电子爱好者表示吃得十分开心）\n（所以什么时候出第二季啊）\n（还是先看七月的恋死吧）\n（感觉今年吃得太好了）\n","date":"2026-07-01T10:19:00Z","permalink":"/p/phone-in-kamiina-botan/","title":"盘点一下《上伊那牡丹，酒醉身姿似百合花般》里出现的手机型号"},{"content":" 因为每天都在看到看到各路二创就决定玩了一下，昨天整整推了十个小时玩完的，直接干到我有点精神不正常，到现在还感觉不舒服，想到一些设定代入感还是极强。上一次出现这种感觉还是玩完了《月姬R》，缓了一个星期才好点。所以我觉得有必要给这部作品写个评价，聊一聊为什么这部作品给我这么大的震撼。\n没有剧透的游玩建议 目前《魔法少女的魔女审判》在Steam上售价是80元，这个价格不算便宜，我建议先下试玩版尝下咸淡再考虑入不入手。\n因为融入了魔女这种超自然因素，这一作的推理前期我认为不是太精彩，前期还没有完全接受这个游戏的设定，一些手法凭现实中的思维挺难想出来的。后期的推理作案手法会比较好接受一些，如果哪里卡壳的话，不妨从魔法的角度思考问题，也许就会迎刃而解。实在不行的话穷举法也是能举出来的，时间是完全够的。\n正常来说全听完配音的游戏流程是30小时，玩的时候不建议像我这样阅读速度飞快，一些东西读快了真的挺难发现的，也可能会导致推理卡壳。如果时间足够的话，还是请耐心欣赏剧情吧。\n不要太在意be，这游戏be就是单纯拿来凑数的。选择的时候不要选带标志的选项就好，真不小心选到了也被太在意。\n以下部分含有严重剧透，请务必玩完全流程后再来读下面这部分。\n步步紧逼的窒息感 我认为这是这部作品最大的特色，也是整个超绝代入感的来源。\n故事一开始就不是很让人舒服，准备好开启新生活的高中生樱羽艾玛，直接被关进了莫名其妙的监牢。和她在一起的是昔日决裂的好友二阶堂希罗。从日常生活氛围到监狱生活的转换，直接就暗示了让人不快的基调。\n后面的剧情发展也是超乎眼前的理解——好友在自己面前被杀害，以及被告知拥有魔女因子，需要在监狱生活，坐在电脑前的我和主人公艾玛一样，瞬间就感到一阵前所未有的压迫感，光是从交代故事背景就已经有了如此的揪心感觉。\n不同周目之间人物故事的展开，也是针对人物的弱点精准切入。一周目是以艾玛为主视角进行展开，艾玛一直都在想办法和大家搞好关系，齐心协力逃出牢狱，但故事不会那么顺艾玛的意愿，在一步又一步的剧情发展中，艾玛的整个内心被瓦解殆尽。在第一案中，众人先是票掉了感染力最强的蕾雅，直接切断了大家团结起来的一种可能性，然后艾玛提议大家做热气球离开这个地方，表面上看大家是在一个方向，在不断变好，内部却是风起云涌，随着米莉亚被杀害，安安被票选出局，最终关系还是破碎了。魔女阵营被分为了两派，而当艾玛有了自己的小天地时，第三案汉娜的死亡直接对准当头一棒，尤其是知道是另一位好友雪莉杀害了汉娜时，艾玛此时已经彻底被崩溃了，我玩到这儿也感觉到说不出来的压抑感。\n最后第四幕，艾玛自己差点被票选处刑，她也得知了原来和自己关系很好的梅露露也并非来意单纯。艾玛的性格本来就是很渴望和他人相处，也是对别人会单纯信任，剧情正好就是利用了她的这一点，完成了一周目故事的讲述。从一开始长按处刑的难过，到后面处刑的麻木，艾玛在一步步崩溃中走向毁灭，彻底完成了魔女化。\n而到了二周目希罗的视角，情况却是和艾玛截然相反。希罗擅长玩心理战术，猜忌心比较强，她的推理也是通过伪证和心理战施压（让我想到了《逆转裁判2》的王都楼案，为了争取时间御剑成步堂一唱一和和稀泥），而剧情却反过来，用团结来刺激希罗。经历过一周目的我在想，已经知道梅露露是幕后黑手，制定战术会更方便些，但一上来就把梅露露杀害了，而且反其道行之票选了最摇摆不定的玛格。之后希罗慢慢取得了奈叶香的信任，但是也眼睁睁看着把她票选出局。而到安安案则是和自己有情感的诺亚被处刑，还用了最恶趣味的处刑方式，让希罗一点点看着诺亚消失在眼前。\n到了第四案彻底到达情感顶峰——意识到自己一直错怪了昔日好友，刚想道歉直接看到她死在面前，最后发现艾玛也在一直努力，内心也很单纯，希罗也彻底崩溃。按下处刑按钮时，希罗也是从一开始的冷漠，到最后的面露难色。\n两个视角从不同的方向达到了同样的胃疼结果，直接展示了艾玛和希罗两位主人公截然不同的地方，这样的设计也震撼了我一辈子。\n到了三周目画风就完全不一样了，希罗直接开大，让在场的所有人魔女化召唤大魔女，二十多个推理过程中一步步揭开所有人的内心伤疤，真的直接把我爽到了，把所有的案件串联了起来，各种伏笔也就此回收，是玩完之后最令我拍案叫绝的一章。\n除此之外，各种死亡画面和处刑方式也是提升窒息感的一大原因——为了让犯人彻底魔女化，各种处刑方式也是恶趣味拉满，让在场的所有人看着犯人被折磨。比如艾玛的处刑，直接在断头台上写满了被孤立时的各种话，在绝望中被处刑，这也是魔裁中不得不品的一环。特别夸一下梅老师的插画设计，把死亡画得这么艺术，也透露出不协调的诡异感，对于气氛塑造太加分了。\n当结束一切故事的时候，本来我以为大家真的就是回归正常生活了，最后猫头鹰监狱长的出现直接给这种可能性打上了问号。就算是剧情结束了也不放过我，氛围真的拉满。\n差点意思的推理 虽然说推理是主要卖点玩法，但是实际表现出来的结果还是差一点意思，特别是前期很多作案手法不是很严密。以汉娜案为例子，对于怎么营造密室这一点，我一开始的推测是门牌互换，路上的红色颜料是凶手拿走娃娃的时候不小心滴在路上的，实际的结果却让我意想不到：房子是雪莉直接怪力搬走的，颜料是不小心被压碎的，这一点其实有提示，比如地图里说了三个房间中有说是临时搭建的，但一来我确实没注意到这个细节，二来就算是我知道也很难想到直接搬起来房子的可能性。在二周目中这种感受会弱一点，对于魔法的使用手法比搬房子这种可能性合理很多一定程度上也是学会了从少女的魔法这一角度进行问题的思考。排掉这个因素，推理难度并不是很大，游戏里会直接给出可以反驳的点，探索一个场景就会自动找齐线索，这一点倒是让我想到了玩《逆转裁判》的时候，跑来跑去找人物找线索，还要拿着勾玉破除心里枷锁。还要一个推理的设定我比较喜欢，就是限时设计，剧情中限定的沟通时间是一小时，实际的游戏条件宽松很多，一般的推理是15分钟，除了最后一幕最长也就30分钟时限，甚至穷举都足够找出来矛盾点。但倒计时这种东西本来就给人压迫感，时间结束直接就是bad end，遇到一些长对话确实会强迫我冷静思考，避免选错浪费时间。\n另一方面最想让我吐槽的是作案动机，有魔女化这个大前提在，这里面人物的作案动机就不可能从现实的角度被理解，触及到了一点逆鳞对方就生命不保，比如看到了自己原本的画作，诺亚就要把安安灭口，因为自己对正义的偏执，希罗就有好几次做掉艾玛的想法，不过魔女这种设定都出来了，倒也不是不能接受吧，我玩的时候就一直说服自己，只是魔女化，只是魔女化，我能理解……\n意难平的人物关系 我玩这个游戏前很大一部分也是看到了艾玛和希罗的二创，但游戏中二人的关系远比二创中的胃疼，玩完最意难平的也是二人的关系了。首先想吐槽的就是艾玛实在是太可怜了， 在外面被朋友误解，也交不到几个好朋友，其中一个还给自己下了祸根，被关进来了也是承受各种心理压力，一周目次次看杀人，还看到汉娜雪莉当面被杀害处刑，二周目被希罗全程对着干，最后也没能等到希罗的道歉，三周目为了被魔女化也没能听到希罗的道歉，也因此真的不忍心看艾玛的be，特别是被希罗烧死和处决，真的太可怜了。\n也因为这样，一开始就没理解希罗对艾玛为什么恶意这么大，艾玛并非不想帮雪，但实际状况不是说艾玛想去保护就能解决的，苛责的应该是霸凌者不是艾玛，我觉得希罗应该想想也能明白这个道理的，但实际上希罗的表现……关于这一点后面我去补了一点设定集，一是希罗本来就嫉妒雪和艾玛的关系比希罗的好，二是希罗本来就是比较偏执，只要认定了事实就不会改变，她没意识到执行正义应该有相匹配的力量。还有一点一开始不太接受的是，希罗的转变契机是看到了大魔女是雪的照片，由此发现原来是雪的问题，错不在艾玛。后来想了一下，按照希罗的性格，她肯定是知道这回事的，但是过分的偏执让她需要一个理由说服自己雪的死因，就直接归给了托付给的艾玛。\n由此引出来另一个意难平的地方——二人的和好。\n按照希罗的性格，她并不是那种会直接原谅自己的人，她也在一直自己惩罚自己，下意识不接近艾玛，让自己去偿还一切后果，所以尽管最后艾玛说服了希罗，但希罗内心的地雷还是没被排掉。自己真的有资格待在艾玛身边吗？艾玛原谅了自己，自己能守护好这份关系吗，可惜的是这些问题都没被解答，游戏只是留下了一个二人拥抱的插画完全留白，这也是我认为游戏最大的遗憾之一，这一点往深挖掘了其实很有叙述价值。\n结尾 魔裁真的是给我一次极其强大的震撼，也有一部分我自己的经历和性格和人物共情的原因，玩的时候实在是太代入了。尽管一些地方在我眼里写的不太好，但我依然认为这是一款佳作，就算是不喜欢推理直接查攻略，少女们之间的故事描写也值得一看。\n","date":"2025-09-25T11:00:00Z","permalink":"/p/manosaba/","title":"剧情大于推理的推理作品——《魔法少女的魔女审判》简评"},{"content":" 这篇文章需要的资源可以从这里找到。\n这篇文章适合 ASUS FX507ZM 的机型，不保证其他机型可用。其他机型请自行寻找对应的资源。\n如果你只是单纯想用原厂系统，下载文章内的压缩包后，打开RESTORE文件夹，将里面的 swm 文件直接恢复到系统盘中，然后重建引导即可。\n准备恢复文件 打开 DiskGenius，删除系统盘最后面原来的恢复分区，在系统盘后面按顺序建立以下三个分区：\n大小 \u0026gt;= 800 MB，修改卷标为 RECOVERY；\n大小 \u0026gt;= 20 GB，修改卷标为 RESTORE；\n大小 \u0026gt;= 200 MB，修改卷标为 MYASUS；\n建立完成后，把压缩包内对应文件夹下的全部文件，复制到对应名字的分区里。\n然后在 DiskGenius 内，把 ESP 分区的卷标改为 SYSTEM。\n重建恢复分区 打开带管理员权限的命令提示符，先输入以下命令：\n1 reagentc /info 此时应该会看到 Windows RE 的状态为 Disabled。\n记下刚刚建立的卷标为 RECOVERY 的分区盘符（这里假定为 E:\\），输入以下命令：\n1 reagentc /setreimage /path \u0026#34;E:\\Recovery\\WindowsRE\u0026#34; 提示成功后，紧接着输入以下命令：\n1 reagentc /setbootshelllink /configfile \u0026#34;E:\\Recovery\\WindowsRE\\ReCustomization.xml\u0026#34; 提示成功后进行下一步。\n标识恢复分区 在 DiskGenius 内，删除三个分区的盘符，然后修改分区参数，做以下修改并保存：\nRECOVERY 和 MYASUS 分区：文件系统类型更改为Microsoft recovery partition，勾选无盘符和OEM；\nRESTORE 分区：勾选无盘符和OEM。\n启用恢复分区 在带管理员的命令提示符中输入以下命令：\n1 reagentc /enable 如果一切顺利，会收到启用成功的消息。此时再运行reagentc /info，应该也可以看到启用的状态。\n打开开始菜单，按住键盘的Shift点击重启，会进入 Windows RE 的界面。点击疑难解答，应该可以看到MyASUS in WinRE的选项，同时各工具工作正常。\n至此恢复步骤结束。可以正常使用。\n","date":"2025-06-05T10:29:00Z","permalink":"/p/asus-fx507zm-recovery/","title":"天选 3 重建恢复分区教程"},{"content":" 尝试按照自己的理解整理了天空街区域的剧情，每一段剧情前都写了这段故事对应的歌曲，剧情按照自己理解的时间顺序进行了排序。\n有的歌曲对应的时间线跨度比较大，对于拆开的不同部分使用了“-1”，“-2”这样的标记做了区分。\n罗马音名字比较繁琐，所以拜托了 ChatGPT 翻译为了中文人名。\n自己的理解很可能不准确，如果有错误还请指正。\n很久以前的世界 《YKWTD》 遥远的过去，在一个叫做邪马的国度，诞生了一对双胞胎。\n哥哥拥有加工金属的智慧，弟弟拥有与神对话的能力。\n弟弟仰慕兄长，可哥哥却憎恨深得民心的弟弟。哥哥用弟弟私用秘石之力的理由发起争端，战争就这样开始了。\n第 1 时间线 《宵の鳥》 晓出生在教条严格的晓家，父亲对他严格有加，让他喘不过气来。小时候晓把自己家的事情悄悄告诉过给其他人，只有身为超级技术宅和家里蹲的女孩琥珀接受了他的说法。二人的感情不断升温。\n为了逃避家里的压抑氛围，晓开始动不动就往琥珀家跑。有一天谈起御神乐的话题时，琥珀提出想要看晓的舞。据说舞蹈是与神对话的方式，外人不可所见，而对晓来说琥珀就是他家人般的人物，于是答应了她的请求。\n而当晓开始跳舞的时候，突然周围环境变化，晓的身边出现了黑色的鸟居，他听到了来自神的低语。晓作为晓家的继承人，从此获得了与神对话的能力。\n时间来到晓的 17 岁袭名式，晓跳起御神乐，他见到了守护晓家的神，可神却降下了献上琥珀生命的神谕。五个月后，刚过 17 岁的琥珀去世了。\n《パラドクスイヴ》 为了拯救琥珀，晓开始利用家族的宝物时之沙，研发回到过去的机器 C204。晓用 C204 穿越过去，可C204 的研发引发了世界大战，琥珀再一次死于战火之中。\n《リフヴェイン》-1 晓穿越时空的尝试失败了。或许只有抹杀掉过去的自己，才能拯救琥珀的性命。他创立了实验室劳姆，用琥珀的 DNA，创造出来了克隆人诺亚。他嘱咐诺亚，让她用 C204 穿越回去，杀掉自己，同时帮助自己的三位好友桃，菜乃叶和苍，让他们回到过去，了解各自的遗憾。\n《ピリオドサイン 》 晓将诺亚送回了过去，他站在大楼顶层，望着远处。\n自己因为穿越时空的能力得到了希望，却又变为了绝望，而最后却又要托付这份力量。穿越时空的能力毁灭了世界，也带走了琥珀的生命，如何才能拯救这一切？\n第 2 时间线 《Falling》 穿越回过去的诺亚，望着樱花树下的琥珀和晓。琥珀望着跳舞的晓，脸上泛起红晕。诺亚深知，不管是什么时候的晓，他都是属于琥珀的，自己只有来自晓的那个任务。\n《REAL VOICE》 传说只要看到蝴蝶，就能够回到过去，一位名为诺亚的神秘人这么说道。而这种事情，却真实发生在了天空街的四位年轻人身上。他们的手臂出现了蝴蝶的纹章，得到了改变过去的能力。\n（实际上这里四人的蝴蝶印记来自于诺亚投放的时之沙，诺亚为了实现晓的承诺，用这种方式给予了他们回到过去的能力）\n桃偶然间在视频网站听到了一首歌，那首歌很快便被删除，旋律却深刻印在了她的脑海里。有一天她在街头演唱了这首歌，为她鼓掌的是这首歌的作者翔也。\n就这样，翔也作曲，桃演唱，他们的组合诞生了。而这一切终结在了翔也的那句“已经不想再做音乐了”。二人最后一次见面时，翔也想要桃收下他的 CD ，可桃最终并没有这样做，而是选择了直接离开。桃用这份力量回到了过去，她想要知晓翔也的真实想法。\n《星めぐり、果ての君へ。》 桃回到了过去，站在那里的是桃怀念已久的身影，桃冲过去紧紧拥抱着翔也，翔也也说出了自己的真实情况，因为家庭原因，翔也近将放弃音乐，可他还是希望有人能了解他的内心，上传了那唯一的一首歌。最后家长还是发现了这件事，歌也被删掉了。而心灰意冷的翔也，听到了来自桃的歌声。\n桃收下了翔也的 CD，翔也和桃说他会选择自己的道路。\n《オリフィス》 菜乃叶画着风景画，他的画打动人心，可他自己的内心却空了一块。小时候自己的玩伴柚希推开了他，挡下了冲过来的车，生命定格在了那个时候。菜乃叶用着回到过去的能力，修复这个有缺憾的世界。\n《ふらふらふら、》 前三次的尝试都失败了。菜乃叶最多只回到了车祸前的时间，他眼睁睁看着柚希的生命在骑车前消失。迷糊中菜乃叶看到了柚希的绿色弹珠，这是那场事故中，菜乃叶丢失掉的来自柚希的信物。他握着绿色弹珠，祈望再次看到柚希的笑容。\n再次睁开眼睛的，是柚希的脸庞，他捡起了菜乃叶的画，菜乃叶的画被他人嫌弃，只有柚希接纳了他。菜乃叶紧紧抱住柚希，他终于看到了儿时玩伴的笑容。\n《パラボラ》 苍是被给予厚望的篮球选手，可他并不擅长和别人合作，他也因此被孤立。而一次意外的受伤，也将近断送他的职业生涯。\n就在这时，苍收到了来自诺亚的短信，承诺可以让他回到过去。然后苍被诺亚袭击，自己的蝴蝶标记被回收。而诺亚不慎把 C204 留在了苍那里，苍带着 C204 一起回到了初中的时候。\n《ユメヒバナ》 晓发现自己的蝴蝶与诺亚蝴蝶预言的图案很像，他开始拜托琥珀进行调查。\n《生命不詳》 晓看着电视上的新闻，最近的失踪案件都与诺亚相关，琥珀昨天突然失踪，这之间有什么关联吗？\n晓沉浸在思考之中，这时身后出现了一个身影。还没等晓反应过来，对方冰冷的枪口就指向了他。\n《リフヴェイン》-2 诺亚完成了晓的任务，她的使命也就此结束。诺亚的意识一点点消失，再次醒来时，却是在实验室里。诺亚并没能改变晓的过去，自己也被这样困在了时间循环里。\n《チエルカ／エソテリカ》 诺亚从夜色中一跃而下，消失在夜色中。想要破除时间循环，只有这种方法。\n第 3 时间线 在这条时间线里，桃，菜乃叶，苍做出的改变也被保留下来。\n《アンバークロニクル》-1 诺亚回到了过去，她的身体消失，意识却保留下来，融合到了琥珀的身体里。琥珀得知了晓在其他时间线为她做的事情，决定和诺亚一起拯救晓。她们计划了名为“琥珀编年史”的计划：实现三人的愿望，夺走 C204，杀掉研发 C204 的人员，前往未来验证结果，再彻底销毁自己的 C204，抹除掉作为罪魁祸首的自己的存在，这样就可以了。\n《スローアライズ》 琥珀发现自己的梦境就是之前的记忆，这部分记忆让她陷入了苦恼。晓带着她去散心，琥珀望着晓的背影，心中满是苦涩。\n《アンバークロニクル》-2 琥珀利用时之沙的能力回到过去，寻找丢失的 C204。最终琥珀在苍身上找到了，她将 C204 夺了回来。\n《群青シグナル》 苍回到了过去，他捡到了他回想起了自己打篮球的初心，前往海外深造篮球。就在这期间，琥珀出现夺走了他的 C204。他想起来当时袭击他的诺亚，和琥珀也长得十分相像。\n为什么琥珀要这么做呢？怀着这样的疑问，苍在 16 岁这年重返了天空街。\n《RIFFRAIN》 在追查蝴蝶预言的时候，晓隐约感觉到了有什么在改变。桃，苍，菜乃叶，他们的经历和自己的记忆对不上号。琥珀依然失踪。\n《アンバークロニクル》-3 琥珀夺走 C204 ，重新回到众人眼前。她回收了其他人的蝴蝶印记，可就在回收晓的印记时，晓质问琥珀这几天究竟在做什么，琥珀最后也因此没能回收晓的蝴蝶印记。面对晓的质疑，琥珀没有回答，只是离开了。对于琥珀而言，自己能做的也都做了，剩下的就是前往未来验证结果。\n《フェイクフェイス・フェイルセイフ》《フタタビ》 大家都意识到了，彼此的时间线都被改变，而晓也终于想起了那段，其他时间线上有关于神谕的记忆。\n晓终于明白了，琥珀在一直时间穿越，拯救自己的事实。他决定动用蝴蝶的能力，寻找拯救琥珀的可能性。\n《Kairos》 琥珀和诺亚前往未来验证是否拯救了晓。等待她们的会是期望的结果吗？\n《シックスプラン》 琥珀和诺亚来到未来，看到的却是晓的墓碑。因为没能回收晓的蝴蝶印记，所以失败了吗？\n或许晓太想拯救自己的生命，所以这条时间线上的晓再一次穿越了吧，甚至不惜毁掉世界。可自己却也想要拯救晓啊。琥珀握紧了手中的 C204。\n第？时间线 《184億回のマルチトニック》 两人同时踏上了穿越过去的旅途，寻找拯救彼此的方法。将错误的过去一个一个认真地涂改，再一次，朝着最好的结局出发。\n最好结局的时间线 《果ての空、僕らが見た光。》 在这最好的结局里，我们再次相遇，彼此相拥，然后说出：\n“谢谢。”\n","date":"2025-04-24T17:43:00Z","permalink":"/p/maimai-skystreet-story/","title":"maimai 天空街区域剧情整理"},{"content":"一、概述 1.1 什么是项目管理 1.1.1 项目 项目的概念 为增加某一独特的产品或服务的价值所做的一次性的、有限的努力。\n项目的特点 目标性：结果只可能是一种期望的产品或服务；\n独特性：每一个项目都是唯一的；\n一次性：有确定的起点和终点；\n约束性：每个项目的资源，成本和时间都是有限的；\n关联性：所开展的活动都是密切相互关联的；\n多方面性：一个项目涉及多个相关利益者；\n不可逆转性：不论结果如何，项目结束结果也就随之确定。\n构成项目的三要素 范围、时间、成本。\n项目集和项目组合 **项目集（Program）**是多个项目组成的总称，程序管理通过组织有序，协调的方式来管理多个项目。\n**项目组合（Portfolio）**是多个项目集组成的总称，利用资产组合管理，项目组合可以把项目，项目集，资产子组合和业务操作等作为一个组合来达到业务战略目标。\n三者关系为相互的包含关系。（课本 P4）\n1.1.2 项目管理 项目管理的概念 不同的知识体系都有不同的解释，概括来说，项目管理是组织实施为实现项目目标所必需的一切活动的计划、安排与控制。\n有关项目管理的一些小概念：顾客、用户、程序等（课本 P2）\n项目管理的构成和约束因素 构成（三要素）：人、过程、工具\n约束：范围、时间、预算、质量。\n项目管理的起源 最早起源于建筑工程项目 -\u0026gt; 第二次世界大战（曼哈顿计划）开始逐渐形成 -\u0026gt; 上世纪五六十年代诞生了计划评估，审查技术和关键路径方法 -\u0026gt; 1965 年欧洲美国都成了项目管理相关的组织 -\u0026gt; 上世纪七八十年代人们开始将信息系统的方法引入项目管理，相继建立信息管理系统。\n项目管理的知识体系 最著名的有 3 个：\n美国 PMI 推出的 PMBOK。\n英国政府商务部推出的 PRINCE。\nIBM 的 WWPMM。\n1.2 项目管理的本质 单从管理来说，管理可以分为计划和监控两个阶段。在项目的初始化阶段，对项目的管理主要以计划的形式展开，其中包括风险、范围、支出的计划等等，而在执行阶段，则是进行监控，如质量保证等。\n项目管理的目标，是保证质量的前提下，协调好任务、成本和进度等要素的相互冲突，获取三者的平衡。对于项目管理的本质，可以从管理对象、管理思想等多个方面进行理解（课本 P7）。\n1.2.1 项目失败和管理的联系 软件项目失败涉及到很多软件管理方面的问题，比如进度估计不够全面，人员配置有问题等等，如果有很好的项目管理，这些问题大部分都可以避免。\n1.2.2 项目管理的对象 可以概括为 3P ——人员（People），问题（Problem）和过程（Process）。三者的关系可见课本 P10。\n1.2.3 项目管理成功的标志 按时交付\n成本在预算内\n功能达到要求水平\n通过客户验收\n范围变化最小或可控\n没有干扰或严重影响整个组织的主要工作流程\n未改变或改变了公司文化\n1.2.4 项目管理成功的要素 制定计划\n建立组织\n配备资源\n监控执行\n总结提高\n也可以概括为一个公式：目标 + 组织 + 流程 + 工具 + 管理 = 成功。\n1.3 项目管理的基本方法 1.3.1 项目管理基本方法的分类 项目管理的基本方法可以分为三种：阶段化管理、量化管理与优化管理。\n阶段化管理：将项目的生命周期分为若干个阶段，再根据不同阶段的不同特点进行针对化管理。\n量化管理：针对影响项目成功的因素制定指标，收集并分析数据，从而完成对项目的控制和优化。\n优化管理：分析项目每部分蕴含的知识，不断吸取教训、总结经验，将知识与实践更好地融合在一起，从而对项目的计划、实施办法等进行优化，获得项目的最佳效益。\n1.3.2 项目管理的基本模型 组队模型：用于解决人力资源管理的问题，比如明确相互依赖的角色和责任、沟通机制等。\n过程模型：为了解决软件开发过程中管理的问题，比如时间管理，基于里程碑的阶段划分等，保障项目顺利实施。\n应用模型：应用于具体领域的需求管理和变更管理等。\n1.4 项目管理的生命周期 项目生命周期可以被分为五个过程：启动、计划、执行、控制和结束\n一般执行和控制都是同时进行的，因此可以把二者合并为一个阶段，自此也可以归纳为这样的四个阶段：\n项目准备和启动阶段\n项目计划阶段\n项目实施和监控阶段\n项目验收和总结阶段\n1.5 项目管理的知识体系 PMBOK、PRINCE2、WWPMM 等。\n1.6 软件项目管理 1.6.1 软件项目管理的不同之处 软件项目是设计型项目\n软件过程模型\n需求变化频繁\n难以估算工作量\n主要的成本是人力成本\n以人为本的管理\n1.6.2 软件项目管理的目标与范围 根据软件项目的主要任务，可以简单定义项目所需角色以及其工作职责。\n项目角色和职能表（课本 P21 表 1-2）\n软件项目管理的生命周期和活动范围的比较（课本 P21 表 1-3）\n1.6.3 软件项目的分类 可以根据规模、软件开发模式，软件的商业模式等多个角度进行划分（课本 P22）\n二、项目的准备和启动 2.1 项目建议书 项目建议书，是项目立项申请报告，重点是如何向有关的投资方或上级阐述立项的必要性。\n项目建议书的内容 项目的背景\n项目的意义和重要性\n项目产品或服务的市场预测\n项目规模和期限\n项目建设必须的条件，已具备和尚不具备的条件分析\n投资估算和资金筹措的设想\n市场前景和经济效益初步分析\n其它需要说明的情况\n2.2 项目可行性分析 2.2.1 可行性分析前提 要首先了解客户的需求和想要达到的目标，对业务需求进行收集和初步分析。\n初步分析的内容有：\n当前业务流程的分析\n主要功能点需求分析\n系统的非功能分析需求\n对一些限制条件的分析\n需求的优先次序\n2.2.2 可行性分析因素 大概可以分为三个方面：经济可行性，技术可行性和风险及不确定性。（课本图 P28 ）\n2.2.3 成本效益分析方法 回收期法（NCF）：原始投资额/每年现金净流入量。\n净现值法（NPV）：用未来报酬的总现值减去原先的投入。公式见课本 P29。\n2.2.4 技术以及风险分析方法 技术分析：专家评定法\n风险分析：决策树\n2.2.5 可行性分析结论 包含的内容有：\n项目需求分析概况\n可行性分析要素\n项目的设计方案\n人员配置和培训计划\n项目主要风险\n可行性研究的结论和建议\n其他重要意见\n（阅读课本 P31 - P33 的例子）\n2.3 项目投标 项目投标可以分为两个阶段：\n第一个是参加竞标的供应商在规定的时间内提供投标书；\n第二个是需求方（客户）对投标书进行评估，得出竞标结果。\n2.4 软件项目合同条款评审 合同计费的种类 固定总价合同\n费用偿还合同\n时间和材料合同\n功能点计费合同\n合同条款评审 评审过程要按照指定、评审、签订合同的顺序进行。\n2.5 软件开发模型 瀑布模型 通过一系列的软件活动顺序展开，每个活动都会产生循环反馈。\n瀑布模型更适合需求比较完善，不容易改变的软件系统。\n快速原型实现模型 对瀑布模型的改进，侧重需求的快速挖掘。其核心是先建造一个系统的初步原型，再以此为基础逐步调整使其满足客户的需求。\n快速原型实现模型适合需求难以确定，不断变更的软件系统。\n增量模型 通过不断增量交付各个模块来推动开发的模块。增量模型体现了不断迭代开发和增量发布，最终交付符合用户价值产品的方法论。\n增量模型支持用户参与，更适合需求复杂难以确定，动态变化的软件系统。\n其他还有极限编程，行为驱动开发等，不再过多阐述。\n2.6 软件项目等组织结构和人员角色 项目的组织结构 主要有三种类型：职能型、纯项目型、矩阵型。\n职能型：项目功能在本职能部门内部讨论，完成再递交给下一个部门。各部门之间由经理进行协调沟通。\n纯项目型：项目经理领导，项目成员直接向经理汇报，每个项目是一个独立的自主单位。\n矩阵型：前二者的结合，受项目经理和职能经理的双重领导。\n软件项目经理 是整个软件项目的核心和灵魂。\nQA 与 QC QA（质量保证）：通过建立和维持质量管理体系来确保产品质量没有问题，关注的是开发的系统。\nQC（质量控制）：检验产品质量，保证产品符合用户需求，是产品质量检查者，关注的是产品本身。\nQA 和 QC 在项目各个不同阶段的工作内容（课本 P53）\n2.7 软件项目的利益相关人 概念 积极参与项目或其利益在项目执行中或成功后受到积极或消极影响的组织或个人。\n项目，项目团队和项目干系人的关系（课本 P55）\n2.8 软件项目启动动员会 会前准备 确定参会者，会议时间和地点，会议议程，主持人，记录人员，发送会议邀请，以及其他准备等。\n会议进行 会议结束 三、项目计划 3.1 什么是项目计划 计划是事先确定项目的目标和实现目标所需要的原则、方法、步骤和手段等完整方案等等管理活动。\n软件项目计划的目的是制定一套软件项目实施及管理的解决方案，其主要工作包括确定详细的项目实施范围、定义递交的工作成果，评估实施过程中的主要风险，制定项目实施的（时间）进度计划、成本和预算计划、人力资源计划等。\n甘特图是很好的项目计划工具，必须掌握。\n软件项目计划的作用 指导软件项目的实施\n得到项目相关干系人的承诺\n获得资源的承诺\n明确项目人员的分工和工作责任\n及早了解项目存在的问题和风险\n获得组织在项目预算上的承诺\n是软件项目实施结果评估的依据\n软件项目实施过程的文档化\n3.2 项目计划的内容 项目计划内容 目标与范围\n项目估算\n风险\n资源\n进度安排\n跟踪和控制机制\n输出文档 总体计划\n项目范围说明书\n项目进度计划\n项目成本计划\n质量管理计划\n项目资源计划\n项目风险计划\n采购管理计划\n配置管理计划\n产品集成管理计划\n3.3 项目计划方法 滚动计划方法 一种动态编制的计划的方法，按照“近细远粗”的原则进行，先编制一定时期内的计划，再按照具体的执行情况修订计划并逐渐向后移动。\n要点：分而治之，逐步求精，动态规划，和谐过渡。\n敏捷开发的滚动计划方法 可以分为五个层次：产品愿景、产品路线图、发布计划、迭代计划、每日计划。\nWBS 方法 一种将复杂问题分解为简单问题，然后再根据分解结果进行计划的方法。\n目的 关注项目目标的澄清职责\n建立可视化项目的可交付成果，以便计算工作量和分配工作\n改进时间，成本，资源估计的准确度\n为绩效测量和项目控制定义一个基准，容易获得项目人员的认可\n辅助分析项目的最初风险，明确工作责任\n为其他项目计划的制定建立框架或依据\n原则和要求 某项具体任务应该在一个工作包，且只能在一个工作包出现\nWBS 中某项项目内容是其下所有 WBS 项的总和\n一个工作包只由一个人来负责\n任务的分解尽量与实际执行方式一致\n分解合理，有良好的稳定性和适应性\n鼓励项目团队积极参与创建 WBS\n所有成果需要文档化\n创建步骤 分解工作任务\n定义各项活动/任务之间的依赖关系\n安排进度和资源\n网络计划技术 一种应用网络模型直观表示软件开发众多工作之间的逻辑关系和时间关系，对完成软件工程项目所需时间、费用、资源进行求解和优化的计划方法。\n3.4 如何有效完成项目计划 3.4.1 软件项目特点 软件开发是在不断探索和研究中进行的\n最佳实践还不够成熟\n软件的自动化对工具的依赖性也很突出\n软件构造过程实际是设计过程，每一个软件产品都不同，自动化程度也比较低\n软件变化不可避免，但是又难以实现，也会引起文档的频繁修改\n软件项目的问题 时间紧迫性\n项目独特性\n软件项目的不确定性\n软件项目管理可视化差\n软件项目生产力依赖于软件人员的潜力挖掘\n3.4.2 软件计划的错误倾向 对计划不重视\n片面计划\n没有考虑足够的风险\n计划过于粗糙\n3.4.3 项目计划的原则 目标性原则\n预防性原则\n客观性原则\n系统性原则\n适应性原则\n制定计划的要点 目标导向 必须要清晰理解描述自己的目标。\n重视与客户的沟通 充分听取客户的意见，使交付的产品更符合客户需求\n收集足够的信息 掌握更多信息，才能掌握更多有价值的内容以制定计划\n客观且使用 做到“知己知彼”\n做到完整的循环过程 先自下而上计划，再自上而下计划\n关注计划过程 随机应变，不断调整修改计划\n要有层次性 比如分为主计划、子计划\n3.4.4 计划的输入 项目的目标和需求\n项目可用的资源\n项目的干系人（相关利益者）\n项目涉及的相关技术\n质量政策和标准\n组织流程\n制约因素\n假设\n历史数据\n3.4.5 计划的流程 确定项目的目标以及工作范围\n根据质量目标制定质量计划\n采用 WBS 方法确定各项的具体任务\n针对具体的工作任务估算工作量和需要的资源\n制定资源计划，完成风险识别和分析，做出相应规划\n进一步完成辅助计划，如采购计划、培训计划等\n需要和软件项目负责人沟通、评审，达成一致意见\n获得有关方面的批准\n3.5 计划各项内容的制定 3.5.1 确定项目范围 软件项目范围的涵义 软件产品规范：明确一个软件产品应该包含哪些特性\n项目工作范围：为了交付上面提到的特性，必须要做的工作。\n3.5.2 项目管理策略 选用的软件开发模型、技术、合同管理策略、成本管理策略、项目的控制策略和例会制度、信息汇报制度、问题发现及汇报制度等。\n3.5.3 软件资源计划 项目资源计划，是指通过分析和识别项目需求，确定出项目需要投入的资源。包括人力资源计划和软硬件资源计划。\n人力计划是资源计划的重点。\n3.5.4 进度计划 进度计划的基本原则：以目标为导向，考虑进度的影响因素，留有余地，按照最坏的情况打算，不要过于乐观。\n进度计划制定的原则如下：\n项目的实际参与人员制定进度\n尽可能先安排难度高的任务，再做难度低的任务\n最好安排为前紧后松的任务节奏\n在项目进度中设置若干个里程碑\n进度表中需要留下缓冲的时间\n发现项目交付的期限不合理，应该调整进度\n在需求发生变化时，应该重新评估进度表\n3.5.5 成本计划 成本的分类 成本可以按照费用分类，分为：人力资源成本、资产类成本、管理费用和项目特别费用。\n也可以将软件项目成本分为直接成本和间接成本：\n直接成本是项目本身任务引起的成本，包括为项目购买的设备和软件工具、参与该项目工作的工作人员等；\n间接成本是许多项目共享的成本，比如办公楼的租金，水电费用，公司管理费用等。\n成本计划的方法 费用预算指在成本估算基础上，针对各项成本估算可能产生的其他费用；\n费用控制是指通过采取特定手段，保证实际的费用低于预算。\n3.5.6 风险计划 风险计划是为了降低项目风险的损害而分析风险、制定风险应对策略方案的过程，包括识别风险、评估风险或量化风险、编制风险应对策略案等过程。\n3.5.7 质量计划 详见课本 P90。\n3.6 项目计划工具 Microsoft Project等。\n四、项目估算 4.1 项目估算 项目的复杂性和不确定性，是项目估算的挑战。\n对于项目管理者来说，不应该被估算所困扰，而应该勇敢面对项目软件估算的挑战，做出一个相对有价值的估算。\n4.2 项目估算的基本内容 规模估算\n工作量估算\n进度估算\n风险估算\n其他估算：比如需求稳定因子、资源利用效率等。\n4.3 基本估算方法 分解方法：采用分而治之的策略，对项目进行分解，再使用逐步求精的方法进行估算，最后通过累加获得整体的估算结果。\n在分解法中，WBS 估算法是最常用的方法。WBS 估算法中，有这样两种估算的模式：\n自顶向下估算模式：首先估算出来每一级的工作量，然后再层层向下分摊，把上一层工作量分摊到下一层的阶段、活动或任务。\n自底向上估算模式：先估算出底层任务的工作量，再层次向上汇总到阶段和项目级。\n算数模型：通过估算模型来生产估算。\n专家判断或经验法：比如德尔菲法。\n比例法：与过去类似的项目进行类比，直接进行类比当前项目的估算结果。\n4.4 软件规模估算 4.4.1 德尔菲法 德尔菲法是一种专家评估技术，适用于在没有足够历史数据的情况下来评定软件采用不同的技术或者新技术带来的差异。由于德尔菲法对专家水平的要求较高，通常观点有局限性，通常把德尔菲法同其他方法结合起来。\n德尔菲法的流程如下：\n确定问题：明确需要专家判断的问题或研究主题。\n选择专家组：挑选具备相关领域知识的专家，一般5～20人。\n第一次问卷调查：专家独立填写匿名问卷，提供观点或预测。\n汇总分析：对第一次问卷结果进行统计分析，找出共识与分歧。\n反馈信息并再次调查：将分析结果反馈给专家，进行第二轮（或多轮）调查，专家可修改自己意见。\n重复反馈与修正：多轮迭代，直到意见趋于一致或分歧无法进一步缩小。\n得出结论：最终形成专家共识或预测结果。\n4.4.2 代码行估算方法 代码行数可以一定程度上反映软件规模，所以可以根据可执行的源代码行数（LOC，line of code）来反映软件规模。\n4.4.3 功能点分析方法 功能点分析方法（FPA）是在需求分析阶段基于系统功能的一种规模估算方法。功能点计算的元素有下：\n外部输入数：每个用户输入\n外部输出数：计算每个用户输出\n内部逻辑文件：计算每个逻辑的主文件\n外部接口文件：计算每个可读的接口\n外部查询数\n功能点的计算步骤如下：\n计算基于以下公式的功能数： FC = ΣΣwij × Xij wij 指根据不同的复杂度而定的五个部分的加权因子，Xij是应用中每个部分的数量。\n用一个已设计的评分标准和方案来评价 14 种系统特性对应用可能产生的影响，然后将这些特性的分数根据以下公式相加得到修正值因子： VAF = 0.65 + 0.01 ΣCi Ci 是系统特性分数。\n用以下公式计算功能点： FP = FC × VAF 4.4.4 标准构件法 软件由若干不同的“标准构件”组成，而这些构建对一个特定领域是通用的，因此可以根据每一个标准构件的出现次数来估计规模。\n4.4.5 综合讨论 先采用分解方法，将项目分解到某个层次上，然后再用对比分析方法和经验方法。\n4.5 工作量估算 4.5.1 COCOMO 方法 构造性成本模型（COCOMO）是一种精确，易于使用的基于模型的成本估算方法。分为三个层次：\n基本层次（Basic COCOMO） 根据软件规模估算开发成本和时间，仅考虑项目规模。\n中级层次（Intermediate COCOMO） 在基本模型基础上，加入影响开发的15个成本驱动因素（如团队经验、产品复杂度等）。\n高级层次（Detailed COCOMO） 在中级模型的基础上，进一步细化到各个开发阶段，并对每阶段的成本驱动因素分别加权分析。\nCOCOMO 模型中的基本量 在 COCOMO 模型中，使用的基本量有下：\nKLOC：代码规模（以千行源代码计）\nEffort（人月）：开发所需的工作量\nTime（月）：项目开发周期\n项目类型：有机型、有机嵌入型、嵌入型（影响公式参数）\nCOCOMO 模型中的影响因素 在中级和高级模型中，还需要考虑 15 种影响软件工作量的因素。这分为以下几类：\n产品属性\n软件复杂度（Product Complexity）\n可靠性要求（Required Software Reliability）\n数据规模（Database Size）\n程序运行时间限制（Execution Time Constraint）\n主存储器限制（Main Storage Constraint）\n硬件属性\n虚拟机波动性（Virtual Machine Volatility）\n运行时间性能需求（Computer Turnaround Time）\n人员属性\n分析员能力（Analyst Capability）\n程序员能力（Programmer Capability）\n应用经验（Application Experience）\n编程语言和工具经验（Programming Language Experience）\n项目属性\n软件工具使用程度（Use of Software Tools）\n项目开发进度要求（Required Development Schedule）\n团队协作程度（Team Cohesion）\n4.5.2 多变量模型 根据历史的软件项目数据推导出来的一种动态模型。\n4.5.3 基于用例的工作量估计 通过用例描述系统的需求更清楚，可以在功能点和用例之间建立良好的关系。\n4.5.4 IBM RMC 估算方法 采用的是定量影响因子估算方法和自底向上估算方法。\n4.5.5 不同场景的估算方法 在不同阶段使用不同的方法进行估算。\n4.6 资源估算 根据 WBS 进行估算 根据 WBS 的分解结果来估算资源，减少人员的沟通成本和人员之间的依赖性。\n由工作量和开发周期来计算 一个简单的估算公式如下：\n人员数量（人） = 工作量估算（人日）/ 工期估算（日） 资源特征描述 每一类资源都用四个特征来说明：资源描述、可用性说明、需要该资源的时间、该资源被使用的持续时间。\n在项目初期就应该建立资源的可用性，包括人力资源、软硬件和可以复用的构件资源等\n资源分配给任务 在资源分配的时候，要对资源的可支配时间进行充分的考虑。\n项目角色 根据项目的目标可以确定项目管理需要的工作特征和技能，从而确定角色以及责任。\n人员分配 分配时考虑以下问题：\n谁最有能力完成这项任务\n谁愿意来完成这项任务\n谁有时间来完成这项任务\n4.7 工期估算和安排 4.7.1 工期估算方法 常用的方法是专家（经验）估算法，基于历史的数据进行类比。\n当面临高度不确定任务时，采用三点估算法：\n计划时间 =（T乐观 + 4 × T可能 + T悲观）/ 6 实际项目中还需要留出一部分的时间以应对项目风险。\n4.7.2 特殊场景 使用历史数据来估算开发周期，在假定人员能力接近，人多力量大，项目就可以越早完成时，可以利用以下的公式：\n工期估算（日） = 工作量估算（人日）/ 人员数量（人） 4.8 成本估算 4.8.1 成本估算方法 同样可以使用专家评估方法、经验法、比例法和 WBS 方法等。\n在成本估算过程中，要紧密结合项目计划，同时预算要避免过于乐观和过于保守。费时较长的项目中，还应该考虑今后的职工工资结构，设备费用以及管理费用是否有较大变化。\n在有新员工的项目中，还应该考虑学习成本。\n4.8.2 学习曲线 通过缩短学习曲线，可以降低学习成本。\n五、项目进度和成本管理 在全球的软件中只有至多 20% 的软件可以正常交付，其主要原因就是对软件项目进度和成本的管理混乱。\n5.1 标识项目活动 项目活动就是把项目的工作量分解为容易管理的具体任务，而每一项任务都要有明确的时间和资源限制，它是项目进度表编制的基础。\n标识项目活动的方法如下：\n分解项目范围：根据工作分解结构（WBS），将项目目标分解为具体的可交付成果。\n识别可交付成果的工作任务：将每个可交付成果进一步分解为完成它所需的具体任务或活动。\n定义活动：明确每项任务的具体执行步骤，即项目活动，确保每项活动都是可操作的工作单元。\n核对和确认：与项目团队确认活动完整性，确保无遗漏、无重复。\n记录活动清单：将所有活动列入项目活动清单，作为后续进度计划的基础。\n5.2 确定项目活动的次序 5.2.1 项目活动之间的关系 活动间的三种关系 结束————开始（Finish-Start）：活动 A 结束后，活动 B 才开始。\n开始————开始（Start-Start）：活动 A 和活动 B 一起开始，或者是两个互动有重叠时间。\n结束————结束（Finish-Finish）：活动 A 和活动 B 同时结束，或者是两个活动同时结束有重叠时间。\n5.2.2 项目活动排序 前导图法、箭线图法。\n5.3 关键路径分析 5.3.1 关键路径和关键分析 项目网络中时间最长的路径决定着项目的工期时间，这就是关键路径。\n5.3.2 活动缓冲期的计算 任何关键活动的延迟都会导致项目工期的延迟，因此关键活动的缓冲期必须是 0。\n其他的计算方法可以看课本 P132。\n5.3.3 压缩工期 压缩关键路径的工期是指在现有的资源、成本和任务不变的前提下，针对关键路径进行优化，直到关键路径不能再压缩为止。\n5.3.4 准关键活动的标识 缓冲期小于自身周期的 10%，不加关注的话这样的活动缓冲期容易用完；\n活动路径上只有一两个活动是非关键活动，这两个延迟时间超过缓冲期的时候就变成了关键活动；\n一些有依赖关系的活动，由于依赖关系特殊，没法保证它之前的活动 100% 都会完成；\n根据项目关系的识别。\n5.4 网络模型的遍历 正向遍历 按照活动开始和活动结束的顺序进行遍历。\n反向遍历 按照活动结束到活动开始的倒序，对网络中的活动进行遍历。\n5.5 里程碑 5.5.1 里程碑的定义 里程碑指的是进度上的重要检查点，标志着上一段工作的结束和下一段工作的开始。\n5.5.2 建立里程碑的方法 设立合理的里程碑检查点\n设定里程碑的完成目标和验证标准\n确认里程碑的利益相关人\n标识里程碑的进度百分比\n5.6 进度计划编制 5.6.1 制定进度表 制定进度计划，一般需要划分两个阶段：\n在产品需求范围确定之前的初步进度时间表，确定之后的详细时间表。\n应包括的方面（P144）\n5.6.2 进度编制策略（P145） 5.6.3 进度编制方法 PERT 法，甘特图法，表格表示法。\n5.6.4 审查、变更进度表（P150） 5.7 进度和成本控制 5.7.1 影响软件项目的因素（P152） 5.7.2 软件项目进度控制（P154） 5.7.3 挣值管理（P160） ","date":"2025-02-21T10:07:00Z","permalink":"/p/software-project-management-notes/","title":"《软件项目管理》学习笔记"},{"content":"这几天买了一台 Macbook Air M1 玩玩，学校上课的时候要用 OracleXE 11g 数据库，但是软件没有提供 macOS 的版本，几经周折终于折腾成功，在这里写下折腾的过程。\n1.安装 homebrew 一开始在这一步就栽跟头了，macOS 默认用的终端软件是 zsh，但是官方的似乎是给 bash 准备的，安装了之后终端跑不了 brew。因此改为使用下面的命令安装：\n1 /bin/zsh -c \u0026#34;$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)\u0026#34; 安装的时候使用中科大下载源，等待安装完成。\n2.安装 Colima 运行以下命令，安装 colima：\n1 brew install colima qemu 然后运行以下命令，创建一个运行环境：\n1 colima start --memory 4 --arch x86_64 3.安装 Docker 使用 brew 安装。运行以下命令：\n1 brew install docker 等待安装完成。\n4.设置 Docker 容器 先运行以下命令登录 Docker：\n1 docker login 按照提示登录。然后在终端中运行以下命令：\n1 docker run -h \u0026#34;oraclehost\u0026#34; --name \u0026#34;oracle\u0026#34; -d -p 1521:1521 oracleinanutshell/oracle-xe-11g 等待拉取镜像并安装完毕。\n5.进入 Docker 容器，验证是否安装成功 首先运行以下命令，获取容器的 ID：\n1 docker ps 你可以看到类似以下的输出：\n1 2 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES cdd8e2328ef2 oracleinanutshell/oracle-xe-11g \u0026#34;/bin/sh -c \u0026#39;/usr/sb…\u0026#34; About an hour ago Up About an hour 22/tcp, 8080/tcp, 0.0.0.0:1521-\u0026gt;1521/tcp, :::1521-\u0026gt;1521/tcp oracle cdd8e2328ef2就是需要的容器 ID。然后运行以下命令，进入容器内部的命令行，其中cdd8e2328ef2替换为你自己的容器\nID：\n1 docker exec -it cdd8e2328ef2 /bin/bash 然后输入以下命令：\n1 sqlplus system/oracle 如果一切正常，应该可以看到以下输出：\n1 2 3 4 5 SQL*Plus: Release 11.2.0.2.0 Production on Thu Feb 20 09:41:12 2025 Copyright (c) 1982, 2011, Oracle. All rights reserved. Connected to: Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production SQL\u0026gt; 这样就安装好了。\n可能遇到的一些问题 Q1： 使用 brew 安装 docker 后，配置时提示以下错误信息该怎么办？ 1 macos Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? A： 运行以下命令卸载掉 docker：\n1 brew uninstall docker 然后前往 Docker 官网下载安装 Docker Desktop 作为替代。\nQ2： 安装 Docker Desktop 后，终端显示找不到 docker 命令该怎么办？ A： 用 vim 编辑~/.zshrc（如果没有就新建一个），加入以下行保存：\n1 export PATH=\u0026#34;/Applications/Docker.app/Contents/Resources/bin:$PATH\u0026#34; 然后运行以下命令，使更改生效。\n1 source ~/.zshrc Q3： 容器默认的用户名，密码，服务名是什么？端口是多少？ A： 用户名：system；密码：oracle；服务名：XE；端口：1521。\nQ4： 我想使用 Navicat 查看数据库中的内容，连接信息填写无误，但是连接时显示“未加载 Oracle 数据库”，该怎么办？ A： 在访达中右键 Navicat，选择显示简介，勾选使用 Rosetta 运行后保存即可。\nQ5： 使用 sqlplus 登录数据库后，箭头不能被正确识别，按下后显示转义字符 A： 先在容器中运行以下命令安装rlwrap：\n1 2 apt update apt install rlwrap 然后改用以下命令登录：\n1 rlwrap sqlplus system/oracle 一些碎碎念 不要使用网上那个 deepdiver 的镜像，好像因为镜像太老了 docker 根本拉不下来。\n不知道为什么 Docker Desktop 显示不了用上面方法创建好的容器，而且 Docker Desktop 里创建的容器也运行不了，后来发现新的 Docker 环境在 Colima 的文件夹下，那应该就是因为这个 Docker 被装到了 Colima 的环境里导致软件不能直接读取。\n","date":"2025-02-20T17:15:00Z","permalink":"/p/install-oraclexe11g-on-apple-silicon/","title":"在基于 Apple Silicon 处理器的 Mac 安装 OracleXE 数据库"},{"content":"0. 什么是 Google Analytics ？ Google Analytics是谷歌提供的访问分析服务。它可以告知访问自己网站的用户类型，来源和数量。借助这些数据可以帮助改进自己的网站。\n当用户访问网站时，访问请求会被发送到 Google Analytics 的分析服务器，然后统计服务对访问请求进行分析统计，绘制图表便于查看。\n1.什么是访问分析？ 1.1 改进网站时的问题 当公司要求自己自己改进网站访问量，提高营业额时，具体应该怎么做？一般来说可能会有以下的困惑：\n对现有的统计数据无从下手\n不知道如何利用现有的数据进行下一步操作\n准备的分析报告被告知不准确\n现有的改进策略不是很有效，对目标营业额没有信心\n访问分析的领域会有很多术语，可能会很难理解，但是一旦掌握之后，它们就能够有效帮助我们改善网站现状。\n1.2 面对特别困难的目标时 在实际的工作中，可能会遇到以下的问题：\n商品滞销，需要在这个月底把库存全部清空\n希望网站的访问量能增加一倍\n减少顾客电话咨询的次数\n面对这些“看似不可能”的目标时，我们可能会想要直接放弃。但实际上这样做也无法解决问题，我们还是需要一些临时方案来缓解当前的状况。面对这样的问题时，我们应该怎么办？\n1.3 了解网站现状 只是呆板地盯着网页看，是没法获得具体的统计数据的，借助 Google Analytics，我们就能够知道这些数据了。\n了解网站的当前状态和目标 在了解网站的状态前，需要先知道这样的一个公式：\n转化（CV）= 客户数量 × 转化率（CVR） 在这个公式中：\n转化（CV）指用户完成了某个特定目标行为的次数。这个目标行为可以是购买商品、注册账号、填写表单、下载应用等，具体取决于广告主的目标。比如说，自己现在的目标是在网站上出售衣服，那么转化就是指“购买商品”。\n转化率（CVR）指产生转化的访问百分比。比如说，有 100 人访问了网站，有 1 人购买了商品，CVR就是 1% 。\n了解当前情况 想象一下理发店的运营，对于理发店的经理来说，每天来光顾多少人，光顾的都是哪些人，都是十分直观的，因为他可以直接看到哪些顾客来光顾了理发店，对于顾客的访问统计也很方便。但在互联网上，除非我们亲眼看到，不然我们是看不到我们的客户的。不进行访问分析，就像是理发店经理戴着眼罩拒绝看他的顾客们，这样的理发店也肯定会很快就倒闭。\n了解现在的网站情况，可以通过上面的公式，借助上个月的数据来分析。比如说上个月有 200 名访客，有 2 个人购买了商品，那么转化率就是 1% 。\n制定目标 假设现在要求的营业额是 10 万元，每件商品是 500 元，那么达成营业额需要卖出 200 件商品，对应的CV就是 200。\n接下来就是制定合理的 CVR。对于电商行业，1%-3%是比较合理的范围，在这个例子中我们制定 2% 的CVR。通过公式计算出来，我们需要达成 10,000 的访问量，这样就很可能实现我们的营业额目标。有了具体要实现的目标，就不会感到那么迷茫了。\n特别注意的是，CVR 的数值一定不能制定太大，达到 90% 甚至 100% 的 CVR 是不切实际的。\n1.4 访问分析有什么好处？ 为什么要进行访问分析？ 在这些领域中，可能会有这样的问题：\n策划、销售人员\n如何使用现有的访问数据来改进网站？ 人力资源官\n如何知道自己投放广告的效果？ 程序员，网页设计师\n需要借助 PDCA 循环来创造收益，但是不知道如何进行数据分析。 联盟营销商\n想要吸引更多的人访问网站来增加收入。 对于不同的行业，具体的目标可能不同，但最终的努力方向是一致的，也就是想办法创造更多的成果，换句话说也就是重复之前的成功。\n如果不使用访问分析，会发生什么？ 来看一个例子：\n我们发现网站卖出了一件商品，但是这位顾客是从哪里得知的网站？在这期间又有多少人访问了网站？我们不知道为什么商品会被卖出，因此只能够想当然地采取措施。\n不采取访问分析，我们不知道什么样的方法对哪些顾客有效，就算是真的取得了成功也很难再次复现。就算是现在的网站运营得很好，那么在以后的情况里，这种运营策略是否还有效呢？\n我们再来看一个使用了访问分析的例子：\n借助访问分析，我们知道有 180 个会话来自其他网站，10 个会话来自搜索引擎，15 个会话来自微博；网站内主页有 190 个会话，此后这些会话中的 140 个点进了商品页，然后有 50 个会话点进了购买页……\n借助访问分析，你可以发现很多东西，比如说：\n访问网站的人数\n顾客如何访问网站\n网站的行为模式\n借助这些数据，我们可以了解什么方法对特定的群体有效，借此我们就可以探索出可以复制的成功模式。\n一般来说，可以具体考虑以下几种情况：\n数据显示来自新闻网站的流量要比预期的多 -\u0026gt; 增加发送新闻稿的频率\n数据显示搜索流量是由关键字 B 而不是关键字 A 产生的 -\u0026gt; 重点关注关键字 B 的 SEO\n使用访问分析的另一个好处是，可以通过现在的数据判断自己犯了什么错误，了解哪些模式是不好用的。例如：\n点进了网页的用户立马离开了 -\u0026gt; 广告内容和实际内容是否相差太大？ -\u0026gt; 重新审视广告内容\n新用户增长停滞 -\u0026gt; 可以尝试增加有关新用户邀请奖励的内容\n不仅是了解现在的措施对什么样的人有用，了解对什么样的人没用也是改进网站的重要信息。\n返回最初的问题 如果你读到这里，你应该已经明白问题的答案了：访问分析就是为了让重复成功变得容易。找到成功的种子并且培育它们，避免在无效的措施上浪费时间，专注于更有效的方案上，就可以让成功的幼苗茁壮成长。\n1.5 确认网站的目标客户 当接到改进网站的任务时，突然去直接进行访问分析是不理智的。所有的产品和服务都是有营销策略的，我们身边的东西，智能手机，笔记本，巧克力等等，都是有营销策略的。在这里我们了解两个代表性的营销框架：3C 和 4P。\n3C 框架 3C是一个用于分析市场和竞争对手，确定成功因素以及制定产品和服务战略的框架，它包括这些内容：\n市场（Customer）：了解潜在客户（规模、市场需求等）\n竞争对手（Competitor）：了解竞争对手的特征（优势与劣势，产品和服务特点等）\n公司（Company）：分析自己的公司为市场提供的价值，以及相比竞争对手有哪些优势等\n经过 3C 框架的分析之后，也就引出了 KBF 与 KSF 的概念。\nKBF（Key Buying Factors，关键购买因素）：指的是影响消费者做出购买决策的关键要素。例如，在购买一款智能手机时，消费者可能会考虑价格、品牌、性能、相机质量、电池续航、售后服务等。这些因素决定了消费者为何选择某个产品或品牌。\nKSF（Key Success Factors，关键成功因素）：指一个行业或市场中，企业取得竞争优势、获得成功的关键因素。例如，在电子商务行业，关键成功因素可能包括高效的物流体系、优秀的用户体验、强大的供应链管理、品牌影响力等。\n3C 框架和 KBF，KSF 的关系可以概括为一句话：企业（Company）要成功（KSF），必须满足客户（Customer）需求（KBF），同时应对竞争对手（Competitor）带来的挑战。\n4P 框架 4P 框架是一个考虑吸引潜在客户的具体措施的框架。它包含四个方面的内容：产品（Product）、价格（Price）、渠道（Place）和促销（Promotion）。 主要内容如下：。\n| 项目 | 内容 | 举例 |\n|:\u0026mdash;\u0026mdash;\u0026ndash;:|:\u0026mdash;\u0026mdash;\u0026ndash;:|:\u0026mdash;\u0026mdash;\u0026ndash;:|\n| 产品 | 我的产品和服务有什么特点？可以提供什么价值？ | 产品适用于什么样的人，在什么情况下需要它，产品的支持和保障服务|\n| 价格 | 我如何为我的产品和服务定价？ | 成本，市场渗透定价等 |\n| 渠道 | 通过什么路线，什么方式把产品送到顾客手中？ | 邮购，网上销售，零售，批发等 |\n| 促销 | 如何让顾客了解产品的存在和特点 | 电视广告、传单，在线广告等 |\n保持策略与内容的一致性 成功的营销模式应该是像这样的：\n3C -\u003e 4P -\u003e 制作 -\u003e 验证与改进 如果策略分析和实际制作的环节之间脱了节，那么制作出的内容就很难达到预期的效果。例如，面对一款智能手机，策划部门得出的结论是“针对中低端市场和老年群体”，但是这个信息并没有传达给生产部门，最后网页上只是孤零零地写着“XX手机 XX元”的信息，顾客不知道这个手机的主要卖点，滞销也就是不难理解的事情了。这就像是从两个反过来的方向一块推箱子一样，双方都只是在白费力气，最后箱子也没有推动。\n在实际的任务中，往往都是多人合作完成。多人合作可以产生更大的力量，但是需要方向一致，箱子才能够正常被推动。如果在网站制作出来后，觉得网站的内容很不明确，不知道是为谁开设，不知道策划人员想的是什么的时候，那么可能是以下的原因：\nA：产品策略本身就不明确。\n试想一下这样一个场景：有一个人过来对你推销商品，他说：“我不确定这个产品应该卖给谁，但还是请你买下它吧。”你觉得自己会买下这个产品吗？答案显然是否定的。当“目标客户不明确”的时候，没有人会觉得这件商品看起来“很好”。只有自己告诉客户“这是适合你的商品”时，他们才会感兴趣。\n如果策略本身就不明确，那么就有必要重新思考营销策略。首先做出“有些客户似乎有这样的需求”的假设，然后一边进行小规模的营销测试，一边努力巩固战略。\nB：制定了产品策略，但是还没有传达给制作部门。\n如果自己负责策划，就应该持续向制作部门传达营销策略，并且需要经常检查自己的信息是否被正确传达。市场状况是不断变化的，这意味着营销策略也需要不断地变化，因此持续传达营销策略十分重要，只是通过会议或者便签分享不能让制作部门及时了解到策略的变化。\n此外，不断地传达产品策略，也可以帮助制作团队加深对产品策略的理解。在传达产品策略时，我们不仅是要传达计划本身，也要传达计划所蕴含的价值，也就是“我们希望为有这种困扰的人提供这样一种服务，并且传递这样的价值”。通过这样不断的磨合，团队内的人就会形成一个统一的整体，箱子推进的方向一致了，进度也就可以加快许多。\n如果自己负责开发生产，在规划部门没有传达策略的情况下，最好的方法是去询问。搞清楚当前的营销策略，才能够做出符合要求的产品。\n使用 PDCA 计划进行循环改进 有了正确的策略，根据策略创造出了对应的网站，接下来应该做的就是测试和改进。利用 PDCA 计划，可以有条理地列举出自己的改进计划。PDCA 计划包含的主要内容如下：\n| 阶段 | 含义 | 主要内容 |\n|:\u0026mdash;\u0026mdash;\u0026ndash;:|:\u0026mdash;\u0026mdash;\u0026ndash;:|:\u0026mdash;\u0026mdash;\u0026ndash;:|\n| Plan | 制定计划 | 确定目标、分析现状、制定改进方案 |\n| Do | 计划实施 | 按照计划执行，落实改进措施 |\n| Check | 评估效果 | 监测执行结果，分析是否达成目标 |\n| Act | 持续改进 | 总结经验，优化流程，标准化成功做法 |\n在实际制定 PDCA 计划的时候，每个阶段都必须要有具体的目标数字。下面是一个售卖丝瓜络的具体的 PDCA 计划的例子：\n| 阶段 | 主要内容 |\n|:\u0026mdash;\u0026mdash;\u0026ndash;:|:\u0026mdash;\u0026mdash;\u0026ndash;:|\n| Plan | 一个月内达到 10 万营业额，为实现这一目标，每个月的用户数量设置为 10,000，CVR 设置为 2% |\n| Do | 改进网页并添加符合策略的内容，优化 SEO 使该网站成为“丝瓜络”关键词的第一名 |\n| Check | 每周至少使用一次 Google Analytics 检查网站。第一周用户量有 1,500，CVR 提升到 1.2% |\n| Act | 如果有效，则扩展关键词并继续，若无效则尝试其他方案 |\n2. Google Analytics 与 Google Tag Manager 简介 2.1 简单了解 Google Analytics 的界面 下面展示了 Google Analytics 的首页。在首页可以清晰看到最近的访问次数和活跃用户数。\n鼠标移动到左侧，选择报告，就能够看到更多有关的信息，比如用户的访问时间，所在的地理位置等。\n2.2 引入 Google Analytics 只要拥有谷歌账户，就可以免费开始使用 Google Analytics。如果没有自己的网站，也可以使用谷歌官方提供的演示账户来进行学习。\n使用模拟账户的方法 访问这个链接，即可查看演示账户的网站统计数据。\n为自己的网站引入的方法 在使用 Google Analytics 前，需要先创建一个账号。点击这里访问 Google Analytics 的主页，点击开始衡量，按照流程即可创建好账号。\n在创建好 Google Analytics 后，每个被跟踪的网站会获得一个**衡量 ID **，在当前网站的统计主页下，点击左侧的管理 -\u0026gt; 数据修改和手机 -\u0026gt; 数据流，点击右侧自己的网站，即可查看该网站的衡量 ID。\n这个 ID 之后还需要使用，先暂时把它记下来。向下滚动网页，选择配置代码设置，在“您的 Google 代码”下还可以看到一个 GT 开头的代码，这个也要记下来。\n2.2 利用 Google Tag Manager 嵌入统计代码 Google Tag Manager 是一个可以用来集体管理跟踪代码的工具。借助这个工具，我们可以很方便地批量向需要被统计的网站嵌入代码。\n思考一下这样的一个过程，如果有多个网站同时需要访问分析，向每个网页的源代码中一个个加入跟踪代码是很麻烦的事情，不仅难于管理，修改起来也十分麻烦。如果使用了跟踪代码管理器，只需要向网页统一添加管理器的代码即可，加入的跟踪代码交给管理器管理，更方便也更加直观。\n创建 Google Tag Manager 的步骤 点击这里进入 Google Tag Manager 的主页，点击右上角的创建账号。\n根据实际情况填写之后点击创建，并同意条款。\n接下来会弹出安装管理器的页面，根据指引把代码粘贴在网页代码的相应位置。填写之后点击测试，通过就代表添加成功了。\n把 Google Analytics 引入 Google Tag Manager 中 在工作区中，点击左侧的代码，再点击右侧的新建。\n在弹出来的页面中，点击代码配置，在“选择代码类型”的页面中，选择Google Analytics，再选择Google 代码。在对应位置输入上面得到的 GT 为开头的代码，触发器设置保持默认。点击上面的“未命名代码”，把名字修改为 “Google 代码” ，点击保存。这一部分设置好应该是像这样的：\n然后再点击右侧的新建，选择选择Google Analytics，再选择Google Analytics：GA4 事件，下方输入自己得到的衡量 ID，再点击下方的触发条件，点击加号，再选择网页浏览，删除掉原来的初始化触发器。点击左上方修改名字后，点击保存即可。这一部分设置好应该是这样的：\n设置好后返回到工作区，点击右上角的提交，输入版本名称和版本统计后，点击发布。\n等待发布完成后，尝试访问页面，再回到 Google Analytics 主页，可以看到已经开始统计了。\n2.3 把自己的访问排除在外 Google Analytics 会将一切访问都进行统计，这其中也包括自己的访问。为了获得更准确的统计效果，我们需要把自己的访问排除在外。\n查询自己的 IP 地址 访问这个网址，可以很方便地查询自己的 IP 地址。将它记录下来。\n设置过滤规则 在 Google Analytics 的主页中，选择左侧的管理，然后再点击媒体资源设置-\u0026gt;数据收集和修改-\u0026gt;数据流，选择自己的网站。然后点击向下滚动，点击展开-\u0026gt;配置代码设置-\u0026gt;指定内部流量，点击创建。为事件命名，然后下面的匹配类型选择IP 地址等于，后面输入自己的 IP 地址，点击创建。\n之后选择数据收集和修改下方的数据过滤器，点击右侧名为 “Internal Traffic” 的过滤器。\n把过滤器状态改为“有效”。\n点击“保存”，并确认弹出的警告。这样就设置完毕了。\n","date":"2025-02-05T08:31:00Z","permalink":"/p/study-google-analytics-with-wakaba/","title":"《跟 Wabaka 酱一起学 Google Analytics》笔记"},{"content":"自从不怎么打游戏后，我的iPad Pro在我手里就基本处于吃灰的状态了，iPadOS虽然已经更新了很多，但是它还是无法成为我的下一台电脑。这时候就有了买入Windows平板的想法，兜兜转转看中了有arm加持的Surface Pro X。尽管WOA就是个半成品，怎么说也比iPadOS能干的生产力活多不少了。\n外观 第一观感其实觉得，这台换了新模具的Surface和以前的至少在外观上没什么很大的区别，和我以前用过的Surface Go相比也就是更大了一些和更薄了一些，而且背面还进化成了指纹收集器，我这种强迫症得随身带着眼镜布擦一擦。\n新模具在支架底部加上了快拆硬盘的设计，不过我还是挺希望微软能把TF卡槽的选项也加回来，我手头还有好几张TF卡，插在这里面正好。\n正面是一块12.3英寸的触控屏，机子的扬声器被安置在了屏幕正面的两侧，也算是经典设计了，捧在手里的时候不会挡住发声的部分。\n为了给Windows Hello摄像头留位置，正面的上黑边留得比较大，为了好看下黑边和上黑边对称设计。对我倒是无所谓，对于一些追求屏占比的同学来说看起来就有点难受了。摄像头识别很迅速，眼神对准了一秒就能识别出来。\n微软说这块触控屏采用了PixelSense技术，查了一下好像是一个能识别接触屏幕的物体的技术。我感觉没什么用，触控该烂还是烂，屏幕打字还是没安卓苹果那么流畅，拿来玩触屏音游也不可行。\n还有Surface经典的无极支架和磁吸充电器，我一直都很喜欢Surface这个支架，平常搭在桌面上用的话可以很方便地撑开到自己需要的角度，稳定性也很好，这一点比起来iPad Pro要好不少。iPad Pro里只有罗技键盘能够支持无极支架，不管是官方保护套还是妙控键盘都只能支起来特定的角度。\n哦对，新模具还把3.5mm耳机孔取缔了，估计是为了轻薄做的妥协。作为一台Windows 平板，我还是挺希望能保留的。虽然Surface Pro X取消掉了耳机孔，但是Surface Pro 8又加回来了，而Pro 8也只比Pro X厚了2mm而已。后面Surface Pro 9又把耳机孔摘掉了，但是厚度没怎么变化，有点搞不懂微软的脑回路，可能是因为数字版的有风扇所以会厚一点。\n相比前代，Pro X最大的变化应该体现在键盘上，键盘特地为手写笔加了笔槽，不用的时候可以放在笔槽里面收纳。\n这样做其实多少有点强行捆绑销售的意味，如果你只用手写笔，而不想另外花大价钱买官方键盘，那么还得为手写笔买一个充电仓，不过比起七代以前那种磁吸在旁边的方式，不用怎么担心放在包里笔突然掉下来的问题。这里点名批评苹果，所有的官方双面夹都没有给笔槽预留位置，二代笔还能吸在平板上保管，一代笔就没有办法了。想起来以前苹果还给一代笔发售过笔套，和微软笔的充电仓有点异曲同工之妙。\n键盘按键是我喜欢的那种类型，按下来反馈很清脆，拿来码码字很舒服，这篇文章就是全程在这台Surface上写完的。键盘背光也有，就是电脑似乎不能很好地识别暗光环境，想要打开背光的话有的时候还需要摸黑按一下F1自己打开。键盘一共有三档背光，日常用完全足够了。\n日常使用 软件生态适配与日用 现在WOA的生态相比较以前要好了不少，我日常使用的很多软件都已经适配了arm64的版本，在这颗SQ1上跑起来基本没什么压力。\n瓶颈主要是在内存上，8G的内存对于24H2的Windows肯定是不够用了，有一次安装Chrome就出现了非常严重的卡顿（尽管Chrome已经原生适配arm64了）。我平常也会装VS来上课学习.Net，光是打开就要一分多钟了，如果是16G内存的话，spx的性能放在现在是完全够日用的。\n对于非arm原生的软件，24H2微软专门给软件加了新的转译层，x64的软件运行效率改善了不少，x86还是一样捉急。我目前日用的软件中还没适配的有Steam，微信和企业微信，Steam和企业微信跑在x86上，只是勉强能用的水平，Steam打开之后要等三四分钟才能加载完，然后切换新的选项卡又要卡一次，还能看到Steam的信息框一点点加载出来的过程。\n企业微信也是一样的毛病，有一次在我来回切换几次聊天框后直接闪退了。\nx64的微信表现还好，虽然运行效率不如原生arm，至少日用没什么问题的，稳定性也好很多。\n至于一些专业软件，比如VMware虚拟机，还有我们做计网实验要用到的eNSP，是跑不起来的。目前的软件生态也就是只够文字办公和简单娱乐用，arm轻薄本主打的也是这方面的功能。\n游戏我尝试了x86的galgame《爱上火车》和x64音游《SixtarGate STARTRAIL》，前者能跑起来但是人物Live2D的动作比较卡，后者打开会慢一点但是完全能玩，应该说这颗SQ1的GPU性能还是挺不错的。\n手写笔 从新模具开始，Surface的标配笔就全部变成超薄触控笔了，网上这方面的评测挺少的，于是我就把两只笔都买了回来，简单用了一段时间。\n先说共同的结论：这两根笔相比Apple Pencil还有一定距离，相比老款的五代触控笔来说进步非常大。两根笔相比以前都换成了扁平的形状，有点像考试用的涂卡笔，刚上手会感觉有点硌手，多用用就能够适应了，没有想象中的那么不堪。\n延迟方面，微软第一方的软件延迟很低，老款笔迹抖动的毛病也改善了很多，上学期期末周的时候我就用的是spx复习，体验是很棒的那一档，从iPad Pro换下来的我也能很快适应。\n微软目前第一方的书写软件主要有Word，OneNote，微软白板和微软日记，延迟都差不多，日常笔记我更推荐用微软日记，使用习惯和我用过的GoodNotes比较接近。第三方软件我主要测试了三星笔记，延迟比第一方的软件要高不少，仅仅是属于能写的水平，这个估计是三星自己的优化问题，本来他们也没有为其他平板适配的打算，但是三星的功能交互好不少，导入PDF书写的体验比微软家自己的都好，OneNote到现在都没有给arm电脑适配打印到PDF的驱动，直接在软件里导入比例显得很别扭。微软日记导入只能在固定的页面方向，横屏PDF导入页面也还是竖过来的。\n（这里有一个小插曲，spx的官方恢复镜像里，微软白板预装的是x86的而不是arm64的，用的时候特别卡特别卡，需要自己卸载到商店重装一下才能正常用）\n再来说两根笔的区别，一代笔不管是橡皮头还是笔尖，阻尼感都要更强一些，在钢化膜上书写起来都要更靠近真实书写的感觉。二代的顶部按键按起来要更硬一点，个人感觉一代笔按下来的手感更好。二代笔多了一个触控信号的功能，可以通过震动模拟纸书写的感觉，不过Surface Pro X用不了。我个人更喜欢用二代笔，二代笔的笔头更细写起来要舒服很多，侧边按键也被移到了中间，抓起来要舒服一点。两根笔在spx的延迟没什么区别，主要是看着自己的使用习惯买就好。\n续航 都说arm的电脑续航要比x86的好，在我这台spx上并没有得到太好的体验。后台挂着iCloud（x64），微信（x64），QQ，开启能源之星，前台Edge用金山文档写文案，两个半小时电池就从100%降到了35%。这么算下来的话这台电脑在我的使用习惯下，用云文档码字也就是四个小时续航的水平。我这台电脑的电池健康度是89%，结合来看的话续航表现并不能让我满意。用2019年的电脑和现在的轻薄本比续航多少有些不公平，但我觉得arm芯片的表现应该也没有那么差吧，可能是因为这颗SQ1是855超频超上来的，所以功耗有点爆炸。\n但是发热控制的挺好的，就算是没有风扇，平常用摸背面也不会有太明显的发热，不像是Surface Go的那个破奔腾，干点什么就都烫得要命还巨卡。\n充电器自带的是65W的，还自己带了一个A口，实测中午十二点回家冲上，下午两点准备出发的时候完全足够补满电。我这个充电器没充电的时候，插在插座上会有嗡嗡的电流声，给电脑充电的时候没有，不知道是不是个例。\n触控体验 电脑键盘摘下来的时候，系统默认会把任务栏缩小成一个小横条，点一下就会有经过触控优化的任务栏弹出来。\n我不知道微软是出于什么样的心态要把任务栏做成类似于安卓全面屏手势这样的形式，这个小横条的功能也只有点一下弹出来任务栏，更高级的功能它也用不了。想要切换关闭应用，还是要点开任务栏，点按应用图标或者长按应用图标在右键菜单里操作。而且这个触控优化也就是把元素暴力拉大增大点击范围，Edge和Chrome的触控优化也是一样，十分不协调。\n在Windows 11里其实能看到，微软为了适配触控操作付出了很大的努力，比如给Windows资源管理器的顶部和右键菜单专门加了复制粘贴的按钮。\n还有右下角的控制中心，多指手势，新的WinUI3设计也对触控更加友好。老毛病不是一时半会能改掉的，我觉得Windows平板触控最大的问题之一还是在相应十分不迅速，摘下键盘后屏幕键盘要等一两秒才会有相应，输入起来也是一卡一卡的，根本就没有安卓苹果那么迅速。第三方的适配也很重要，就用B站看视频为例，浏览器和客户端都不支持触屏下长按快进，只能自己调倍速，看图片不能用双指手势放大缩小，这些都是需要花时间解决的问题，至少目前不好用。\n小毛病 WOA的小毛病要比x86上的多不少，目前我遇到了这些问题：\n• 更新最新的Web体验包后，小组件没法显示在线信息流，也没法添加新的小组件；\n• 有些时候打字的候选框要拖几秒才会显示出来；\n• 睡眠唤醒十分拖沓，平均都要等十几秒才能亮屏，有时候甚至会直接睡死。插电的时候唤醒反而十分迅速；\n• 睡眠唤醒之后有的任务栏图标偶尔会消失。\n总结 一直在想应该用什么样的词语来形容这台电脑，说它好吧，比起x86电脑它的长处不多，唯一的续航优势也并没有那么突出，而且现在Windows on ARM用起来依然有不少小毛病。但是说它不好吧，电脑日用起来并没有什么问题，随着生态发展支持的原生应用也多了很多，它已经完全可以胜任我日常的大部分任务了，这颗披着855外衣的骁龙SQ1放在Windows上性能也是够用的。也许我们只是需要用更多的时间去等待WOA的适配，但至少目前还没有非买arm不可的理由。\n","date":"2025-01-22T09:14:00Z","permalink":"/p/surface-pro-x-experience/","title":"Surface Pro X 体验"},{"content":" 这篇文章的大部分内容都由ChatGPT辅助完成。\n一、STL概述 1. 什么是STL？ C++ 标准模板库（Standard Template Library，STL）是一套功能强大的 C++ 模板类和函数的集合，它提供了一系列通用的、可复用的算法和数据结构。\n2. STL的组成部分 容器，算法，迭代器。\n3. STL主要使用了什么技术实现？ 模板。\n二、模板与运算符重载 1. 函数模板，类模板 1 2 3 4 5 6 7 8 9 10 11 12 13 // 函数模板 template \u0026lt;typename T\u0026gt; T add(T a, T b) { return a + b; } //类模板 template \u0026lt;typename T\u0026gt; class ClassName { T data; // 使用类型参数 T public: ClassName(T value) : data(value) {} // 构造函数 T getData() const { return data; } // 返回 data }; 2. traits技术 主要思想：提取不同类的共性，以便统一处理。\nTraits 技术的核心思想是通过定义一个特性类（或结构体），在其中对类型的特征进行描述。这些描述可以是类型信息、操作支持情况等，通常通过模板类来实现。通过 traits，可以在编译期获取一个类型的“能力”或“特征”，从而做出不同的决策。\n使用 Traits 技术的示例 假设我们需要定义一个 is_integral trait 来判断一个类型是否为整数类型：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 template \u0026lt;typename T\u0026gt; struct is_integral { static const bool value = false; // 默认不是整数类型 }; // 特化：整数类型的 trait template \u0026lt;\u0026gt; struct is_integral\u0026lt;int\u0026gt; { static const bool value = true; }; template \u0026lt;\u0026gt; struct is_integral\u0026lt;long\u0026gt; { static const bool value = true; }; template \u0026lt;\u0026gt; struct is_integral\u0026lt;short\u0026gt; { static const bool value = true; }; // 使用示例 int main() { std::cout \u0026lt;\u0026lt; \u0026#34;int is integral: \u0026#34; \u0026lt;\u0026lt; is_integral\u0026lt;int\u0026gt;::value \u0026lt;\u0026lt; std::endl; // 输出: 1 std::cout \u0026lt;\u0026lt; \u0026#34;double is integral: \u0026#34; \u0026lt;\u0026lt; is_integral\u0026lt;double\u0026gt;::value \u0026lt;\u0026lt; std::endl; // 输出: 0 return 0; } 在这个例子中：\nis_integral 是一个 trait，用于判断给定类型 T 是否是整数类型。\nis_integral\u0026lt;int\u0026gt;、is_integral\u0026lt;long\u0026gt; 等是类型特化，表示这几种类型是整数类型。\nis_integral\u0026lt;double\u0026gt; 会返回 false，因为 double 不是整数类型。\n3. 运算符重载 成员函数运算符重载（二元运算符重载） 二元运算符（如 +、-、*、/ 等）需要两个操作数。\n当重载为成员函数时，左操作数是调用对象（隐式传递），右操作数通过函数参数传递。\n一个重载的例子：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 // 重载加号运算符 #include \u0026lt;iostream\u0026gt; using namespace std; class Complex { private: double real, imag; public: Complex(double r = 0, double i = 0) : real(r), imag(i) {} // 成员函数重载 + Complex operator+(const Complex \u0026amp;other) const { return Complex(real + other.real, imag + other.imag); } void print() const { cout \u0026lt;\u0026lt; real \u0026lt;\u0026lt; \u0026#34; + \u0026#34; \u0026lt;\u0026lt; imag \u0026lt;\u0026lt; \u0026#34;i\u0026#34; \u0026lt;\u0026lt; endl; } }; int main() { Complex c1(1.0, 2.0), c2(3.0, 4.0); Complex c3 = c1 + c2; // 调用重载的 + 运算符 c3.print(); // 输出: 4 + 6i return 0; } 只能使用成员函数重载的运算符有：=、()、[]、-\u0026gt;。\n友元运算符重载 友元运算符重载是 C++ 中的一种特殊情况，允许我们将运算符函数定义为类的“友元函数”，使其可以访问类的私有成员。友元运算符函数的重载一般用于重载需要操作类成员的运算符，特别是二元运算符，如加法（+）或赋值（=）。\n友元函数并不属于类的一部分，但它能够访问类的私有和保护成员。\n友元运算符重载允许我们在类外部定义运算符的行为，并且能够访问类的私有成员。\n一个重载的例子：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 // 重载 \u0026gt;\u0026gt; 与 \u0026lt;\u0026lt; ，输出复数 #include \u0026lt;iostream\u0026gt; using namespace std; class Complex { private: double real, imag; public: Complex(double r = 0, double i = 0) : real(r), imag(i) {} // 友元运算符重载 friend ostream\u0026amp; operator\u0026lt;\u0026lt;(ostream \u0026amp;out, const Complex \u0026amp;c); friend istream\u0026amp; operator\u0026gt;\u0026gt;(istream \u0026amp;in, Complex \u0026amp;c); }; // 输出流运算符重载 ostream\u0026amp; operator\u0026lt;\u0026lt;(ostream \u0026amp;out, const Complex \u0026amp;c) { out \u0026lt;\u0026lt; c.real \u0026lt;\u0026lt; \u0026#34; + \u0026#34; \u0026lt;\u0026lt; c.imag \u0026lt;\u0026lt; \u0026#34;i\u0026#34;; return out; } // 输入流运算符重载 istream\u0026amp; operator\u0026gt;\u0026gt;(istream \u0026amp;in, Complex \u0026amp;c) { in \u0026gt;\u0026gt; c.real \u0026gt;\u0026gt; c.imag; return in; } int main() { Complex c1(1.0, 2.0); cout \u0026lt;\u0026lt; \u0026#34;Complex number: \u0026#34; \u0026lt;\u0026lt; c1 \u0026lt;\u0026lt; endl; // 调用重载的 \u0026lt;\u0026lt; 运算符 Complex c2; cout \u0026lt;\u0026lt; \u0026#34;Enter a complex number (real and imaginary): \u0026#34;; cin \u0026gt;\u0026gt; c2; // 调用重载的 \u0026gt;\u0026gt; 运算符 cout \u0026lt;\u0026lt; \u0026#34;You entered: \u0026#34; \u0026lt;\u0026lt; c2 \u0026lt;\u0026lt; endl; return 0; } \u0026gt;\u0026gt;和\u0026laquo;只能使用友元函数重载。\n三、迭代器 1. 迭代器分多少种？不同容器对应的迭代器类型？ | 类型 | 单向遍历 | 双向遍历 | 随机访问 | 读写能力 | 示例容器 |\n| \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; | \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash; | \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash; | \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash; | \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash; | \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;- |\n| 输入迭代器 | ✅ | ❌ | ❌ | 只读 | std::istream |\n| 输出迭代器 | ✅ | ❌ | ❌ | 只写 | std::ostream |\n| 前向迭代器 | ✅ | ❌ | ❌ | 读写 | std::forward_list |\n| 双向迭代器 | ✅ | ✅ | ❌ | 读写 | list,set,multiset,map,multimap |\n| 随机访问迭代器 | ✅ | ✅ | ✅ | 读写 | vector,deque |\n队列（queue）和堆栈（stack）本身不提供迭代器，迭代器类型由其封装而成的容器决定。\nbitset容器不提供迭代器，其访问由数组下标完成。\n2.迭代器在STL中起到什么作用？ 迭代器可以提供统一的方法访问容器中的元素。同时，迭代器可以使容器和算法分离，使算法更具有普适性。\n3.迭代器如何访问对应元素及内部变量？ 迭代器通过重载操作符（如 * 和 ++）来访问容器中的元素和实现遍历。\n* 和 -\u0026gt; 用于访问迭代器指向的元素或成员。\n++ 用于移动迭代器。\n随机访问迭代器支持额外的操作，如 +, -, 和 []。\n4.熟练使用迭代器的advance、distance、next函数，为何要使用这些函数？ advance函数 将一个迭代器向前或向后移动指定的距离，支持任意类型的迭代器（前向、双向、随机访问）。\n用法：\n1 2 3 4 5 6 7 8 9 10 11 // 函数用法：advance(迭代器指针,移动距离) #include \u0026lt;vector\u0026gt; #include \u0026lt;iostream\u0026gt; #include \u0026lt;iterator\u0026gt; // for std::advance int main() { std::vector\u0026lt;int\u0026gt; vec = {10, 20, 30, 40, 50}; auto it = vec.begin(); std::advance(it, 3); // 将迭代器向前移动 3 个位置 std::cout \u0026lt;\u0026lt; *it \u0026lt;\u0026lt; std::endl; // 输出: 40 return 0; } distance函数 计算两个迭代器之间的距离（即两个迭代器间元素的数量）。\n用法：\n1 2 3 4 5 6 7 8 9 10 11 // 函数用法：distance(迭代器指针1,迭代器指针2) #include \u0026lt;vector\u0026gt; #include \u0026lt;iostream\u0026gt; #include \u0026lt;iterator\u0026gt; // for std::distance int main() { std::vector\u0026lt;int\u0026gt; vec = {10, 20, 30, 40, 50}; auto it1 = vec.begin(); auto it2 = vec.end(); std::cout \u0026lt;\u0026lt; std::distance(it1, it2) \u0026lt;\u0026lt; std::endl; // 输出: 5 return 0; } next函数 创建一个新的迭代器，指向当前迭代器的指定偏移位置，而不修改原来的迭代器。支持所有的迭代器。\n用法：\n1 2 3 4 5 6 7 8 9 10 11 // 函数用法：next(迭代器指针,偏移量) #include \u0026lt;vector\u0026gt; #include \u0026lt;iostream\u0026gt; #include \u0026lt;iterator\u0026gt; // for std::next int main() { std::vector\u0026lt;int\u0026gt; vec = {10, 20, 30, 40, 50}; auto it = vec.begin(); auto newIt = std::next(it, 2); // 获取当前迭代器之后第 2 个位置的迭代器 std::cout \u0026lt;\u0026lt; *newIt \u0026lt;\u0026lt; std::endl; // 输出: 30 return 0; } 使用函数的原因 简单高效，易于阅读，可以避免出错。\n四、输入输出流 1. 都有哪些流？ | 流类型 | 功能描述 | 常用类 |\n| \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; | \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;- | \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash; |\n| 标准输入输出流 | 用于从控制台输入或输出到控制台。 | std::cin, std::cout, |\n| 文件流 | 用于文件的输入和输出操作。 | std::ifstream, std::ofstream, std::fstream |\n| 字符串流 | 用于基于字符串的输入和输出操作（内存中的流）。 | std::istringstream, std::ostringstream, std::stringstream |\n| 二进制流 | 用于处理二进制数据。 | 通常使用 std::ifstream 和 std::ofstream 配合二进制模式。 |\n2. 状态如何检测？ 使用流的成员函数 std::ios::good(), std::ios::fail() 等可以检测流的状态。常用的状态位如下：\nstd::ios::good()：流状态正常，无错误。\nstd::ios::eof()：到达文件或输入的末尾。\nstd::ios::fail()：输入或输出操作失败。\nstd::ios::bad()：流发生不可恢复的错误。\n示例代码如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include \u0026lt;iostream\u0026gt; #include \u0026lt;fstream\u0026gt; int main() { std::ifstream file(\u0026#34;example.txt\u0026#34;); if (!file.is_open()) { std::cerr \u0026lt;\u0026lt; \u0026#34;File could not be opened!\u0026#34; \u0026lt;\u0026lt; std::endl; return 1; } int number; while (file \u0026gt;\u0026gt; number) { std::cout \u0026lt;\u0026lt; \u0026#34;Read: \u0026#34; \u0026lt;\u0026lt; number \u0026lt;\u0026lt; std::endl; } if (file.eof()) { std::cout \u0026lt;\u0026lt; \u0026#34;End of file reached.\u0026#34; \u0026lt;\u0026lt; std::endl; } else if (file.fail()) { std::cerr \u0026lt;\u0026lt; \u0026#34;Input operation failed!\u0026#34; \u0026lt;\u0026lt; std::endl; } else if (file.bad()) { std::cerr \u0026lt;\u0026lt; \u0026#34;Critical error occurred!\u0026#34; \u0026lt;\u0026lt; std::endl; } file.close(); return 0; } 3.cin.get()与cin.getline()使用 cin.get() 用于从输入流中读取单个字符。 示例代码：\n1 2 3 4 5 6 7 8 9 // 用法：cin.get() include \u0026lt;iostream\u0026gt; int main() { char c; std::cout \u0026lt;\u0026lt; \u0026#34;Enter a character: \u0026#34;; c = std::cin.get(); // 读取单个字符 std::cout \u0026lt;\u0026lt; \u0026#34;You entered: \u0026#34; \u0026lt;\u0026lt; c \u0026lt;\u0026lt; std::endl; return 0; } cin.getline() 用于从输入流中读取一行文本（直到遇到换行符），并丢弃换行符。\n如果输入的字符数超过缓冲区大小，多余的字符会被丢弃。\n示例代码：\n1 2 3 4 5 6 7 8 9 10 // 用法：cin.getline(char类型的数组指针,读取的字符数量) // char类型的数组指针用作缓冲区 #include \u0026lt;iostream\u0026gt; int main() { char buffer[20]; std::cout \u0026lt;\u0026lt; \u0026#34;Enter a line: \u0026#34;; std::cin.getline(buffer, 20); // 读取最多 19 个字符，最后一个留给 \u0026#39;\\0\u0026#39; std::cout \u0026lt;\u0026lt; \u0026#34;You entered: \u0026#34; \u0026lt;\u0026lt; buffer \u0026lt;\u0026lt; std::endl; return 0; } 4. stringstream相关操作举例 实现 int\u0026lt;——\u0026gt;string 转换 示例代码：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include \u0026lt;iostream\u0026gt; #include \u0026lt;sstream\u0026gt; #include \u0026lt;string\u0026gt; int main() { int num = 42; std::string str; // int -\u0026gt; string std::stringstream ss; ss \u0026lt;\u0026lt; num; // 插入num到流中 str = ss.str(); //提取为字符串赋值给str std::cout \u0026lt;\u0026lt; \u0026#34;Integer to string: \u0026#34; \u0026lt;\u0026lt; str \u0026lt;\u0026lt; std::endl; // string -\u0026gt; int ss.clear(); // 清空流 ss.str(\u0026#34;123\u0026#34;); // 设置流的内容为字符串123 ss \u0026gt;\u0026gt; num; // 提取流中数据，转换为整型变量赋值 std::cout \u0026lt;\u0026lt; \u0026#34;String to integer: \u0026#34; \u0026lt;\u0026lt; num \u0026lt;\u0026lt; std::endl; return 0; } 字符串分解 可以将字符串分解为多个部分，例如按空格分隔。\n示例代码：\n1 2 3 4 5 6 7 8 9 10 11 12 #include \u0026lt;iostream\u0026gt; #include \u0026lt;sstream\u0026gt; #include \u0026lt;string\u0026gt; int main() { std::string line = \u0026#34;10 20 30 40\u0026#34;; std::stringstream ss(line); //包装字符串为流 int number; while (ss \u0026gt;\u0026gt; number) { //提取每个数输出 std::cout \u0026lt;\u0026lt; \u0026#34;Extracted number: \u0026#34; \u0026lt;\u0026lt; number \u0026lt;\u0026lt; std::endl; } return 0; } 字符串合并 将多个数据合并为一个字符串。\n示例代码：\n1 2 3 4 5 6 7 8 9 10 #include \u0026lt;iostream\u0026gt; #include \u0026lt;sstream\u0026gt; #include \u0026lt;string\u0026gt; int main() { std::stringstream ss; ss \u0026lt;\u0026lt; \u0026#34;Hello\u0026#34; \u0026lt;\u0026lt; \u0026#34; \u0026#34; \u0026lt;\u0026lt; \u0026#34;World\u0026#34; \u0026lt;\u0026lt; \u0026#34;!\u0026#34; \u0026lt;\u0026lt; \u0026#34; \u0026#34; \u0026lt;\u0026lt; 2024; //插入字符到流中 std::string result = ss.str(); //把流中字符全部输出 std::cout \u0026lt;\u0026lt; \u0026#34;Merged string: \u0026#34; \u0026lt;\u0026lt; result \u0026lt;\u0026lt; std::endl; return 0; } 五、容器 1. 容器的分类 | 容器名称 | 容器概念 | 对应容器 |\n| \u0026mdash;\u0026mdash;\u0026mdash;- | \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash; | \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash; |\n| 序列性容器 | 按照线性排列储存值 | vector,deque,list |\n| 关联式容器 | 根据键值检索数据 | set,multiset,map,multimap |\n| 容器适配器 | 根据前两种容器再封装得到 | stack,queue |\n2. vector,deque vector 初始化 1 2 3 4 5 6 7 8 9 10 11 #include \u0026lt;vector\u0026gt; // 默认构造 std::vector\u0026lt;int\u0026gt; v1; // 指定大小初始化 std::vector\u0026lt;int\u0026gt; v2(10); // 包含10个默认值为0的元素 // 指定大小和初始值 std::vector\u0026lt;int\u0026gt; v3(5, 100); // 包含5个元素，值为100 // 使用初始化列表 std::vector\u0026lt;int\u0026gt; v4 = {1, 2, 3, 4, 5}; // 复制构造 std::vector v5(v4); push_back 向vector末尾添加元素\n1 2 v1.push_back(10); v1.push_back(20); pop_back 删除vector末尾的元素\n1 v1.pop_back(); insert 在指定位置插入元素\n1 2 v1.insert(v1.begin() + 1, 15); // 在第2个位置插入元素15 v1.insert(v1.begin(), 5); // 在第一个位置插入元素5 erase 删除指定位置的元素\n1 v1.erase(v1.begin() + 1); // 删除第2个元素 assign 重新指定vector的大小和元素值\n1 v1.assign(3, 50); // 将v1重置为包含3个元素，每个元素的值为50 迭代器遍历 使用迭代器遍历vector\n1 2 3 for (iterator it = v1.begin(); it != v1.end(); it++) { std::cout \u0026lt;\u0026lt; *it \u0026lt;\u0026lt; \u0026#34; \u0026#34;; } 或者使用范围-based for 循环\n1 2 3 for (const auto\u0026amp; value : v1) { std::cout \u0026lt;\u0026lt; value \u0026lt;\u0026lt; \u0026#34; \u0026#34;; } deque 初始化 1 2 3 4 5 6 7 8 9 10 11 #include \u0026lt;deque\u0026gt; // 默认构造 std::deque\u0026lt;int\u0026gt; dq1; // 指定大小初始化 std::deque\u0026lt;int\u0026gt; dq2(10); // 包含10个默认值为0的元素 // 指定大小和初始值 std::deque\u0026lt;int\u0026gt; dq3(5, 100); // 包含5个元素，每个的值为100 // 使用初始化列表 std::deque\u0026lt;int\u0026gt; dq4 = {1, 2, 3, 4, 5}; // 复制构造 std::deque\u0026lt;int\u0026gt; dq5(dq4); push_back 向deque末尾添加元素\n1 2 dq1.push_back(10); dq1.push_back(20); push_front 向deque前端添加元素\n1 dq1.push_front(5); pop_back 删除deque末尾的元素\n1 dq1.pop_back(); pop_front 删除deque前端的元素\n1 dq1.pop_front(); insert 在指定位置插入元素\n1 2 dq1.insert(dq1.begin() + 1, 15); // 在第2个位置插入元素15 dq1.insert(dq1.begin(), 5); // 在第一个位置插入元素5 erase 删除指定位置的元素\n1 dq1.erase(dq1.begin() + 1); // 删除第2个元素 assign 重新指定deque的大小和元素值\n1 dq1.assign(3, 50); // 将dq1重置为包含3个元素，每个元素的值为50 迭代器遍历 使用迭代器遍历deque\n1 2 3 for (iterator it = dq1.begin(); it != dq1.end(); it++) { std::cout \u0026lt;\u0026lt; *it \u0026lt;\u0026lt; \u0026#34; \u0026#34;; } 3. list 特点 双向列表，使用双向迭代器，不支持随机访问，动态大小\n结构 由一个个节点组成。每个节点包含指向前一个节点的指针和后一个节点的指针。\n操作 初始化 方法与前两个容器类似。\n插入/删除 可以使用push类方法与pop类方法。\n此外还可以使用insert，使用指定位置进行插入：\n1 2 3 iterator it = lst1.begin(); lst1.insert(it, 15); // 在首位置插入15 lst1.insert(++it, 25); // 在第二个位置插入25 使用insert也可以插入多个相同的元素：\n1 2 iterator it = lst1.begin(); lst1.insert(it, 3, 100); // 在首位置插入3个元素，值为100 对于删除操作，还可以使用以下的操作：\nerase：删除指定位置的元素 1 2 iterator it = lst1.begin(); lst1.erase(it); // 删除指向首元素的迭代器位置的元素 erase操作也可以用来删除指定范围的元素：\n1 2 3 iterator it1 = lst1.begin(); iterator it2 = ++lst1.begin(); lst1.erase(it1, it2); // 删除从it1到it2之间的所有元素 remove：删除所有与指定值匹配的元素（注意：remove 只是移除元素，但并不会真正删除，实际删除需要配合 erase 使用） 1 lst1.remove(20); // 删除所有值为20的元素 clear：删除所有元素 1 lst1.clear(); // 清空整个链表 遍历 使用迭代器进行正向遍历：\n1 2 3 for (iteartor it = lst1.begin(); it != lst1.end(); it++) { std::cout \u0026lt;\u0026lt; *it \u0026lt;\u0026lt; \u0026#34; \u0026#34;; // 访问元素 } std::list 支持双向迭代，可以用 --it 实现反向遍历：\n1 2 3 4 for (iteartor it = lst1.end(); it != lst1.begin(); ) { --it; // 向前移动 std::cout \u0026lt;\u0026lt; *it \u0026lt;\u0026lt; \u0026#34; \u0026#34;; } 迭代器的插入与删除 在指定位置插入元素： 1 2 iterator it = lst1.begin(); lst1.insert(it, 100); // 在迭代器位置插入100 删除指定位置的元素： 1 2 iterator it = lst1.begin(); lst1.erase(it); // 删除迭代器指向的元素 4. stack、queue、priority_queue stack 特点 后进先出（LIFO）：std::stack 是一个栈，遵循后进先出（Last In, First Out）原则。即最后压入栈的元素最先被弹出。\n限制访问：std::stack 只允许从栈顶访问元素，并且只能执行 push()、pop() 和 top() 操作。不能像其他容器那样遍历栈中的元素。\n底层容器：std::stack 通常是基于 deque 或 vector 来实现的。\n初始化 1 2 3 4 5 6 7 8 9 10 11 12 13 #include \u0026lt;stack\u0026gt; #include \u0026lt;deque\u0026gt; #include \u0026lt;vector\u0026gt; // 使用默认底层容器 deque std::stack\u0026lt;int\u0026gt; s1; // 空栈 // 使用指定的底层容器 deque std::stack\u0026lt;int, std::deque\u0026lt;int\u0026gt;\u0026gt; s2; // 使用指定的底层容器 vector std::stack\u0026lt;int, std::vector\u0026lt;int\u0026gt;\u0026gt; s3; // 使用初始化列表初始化栈 std::stack\u0026lt;int\u0026gt; s4; s4.push(10); s4.push(20); 常用操作 push(value)：将元素压入栈顶。\npop()：弹出栈顶元素。\ntop()：访问栈顶元素。\nempty()：检查栈是否为空。\nsize()：获取栈中的元素数量。\n1 2 3 4 5 6 std::stack\u0026lt;int\u0026gt; s; s.push(10); s.push(20); std::cout \u0026lt;\u0026lt; s.top() \u0026lt;\u0026lt; std::endl; // 输出 20 s.pop(); std::cout \u0026lt;\u0026lt; s.top() \u0026lt;\u0026lt; std::endl; // 输出 10 适合的底层容器 deque：deque 是 std::stack 的默认底层容器，因为它支持高效的两端插入和删除操作。栈操作只在容器的一端进行，因此 deque 是理想选择。\nvector：vector 也可以作为底层容器，但由于 push_back 和 pop_back 操作的时间复杂度与容器大小有关（可能会触发重新分配内存），因此在某些情况下可能不如 deque 高效。\n遍历 利用vector容器来临时保存：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include \u0026lt;iostream\u0026gt; #include \u0026lt;stack\u0026gt; #include \u0026lt;vector\u0026gt; int main() { std::stack\u0026lt;int\u0026gt; s; s.push(10); s.push(20); s.push(30); // 使用临时容器来遍历栈 std::vector\u0026lt;int\u0026gt; temp; while (!s.empty()) { temp.push_back(s.top()); s.pop(); } // 打印栈的元素（逆序） for (auto val : temp) { std::cout \u0026lt;\u0026lt; val \u0026lt;\u0026lt; \u0026#34; \u0026#34;; } return 0; } queue 特点 先进先出（FIFO）：std::queue 是一个队列，遵循先进先出（First In, First Out）原则。即最早加入队列的元素最先被删除。\n限制访问：std::queue 只允许从队列的前端删除元素（pop()）和从队列的后端添加元素（push()）。不能像其他容器那样直接访问或修改队列中的元素。\n底层容器：std::queue 通常是基于 deque 来实现的，但也可以使用其他容器，如 list。\n初始化 1 2 3 4 5 6 7 8 #include \u0026lt;queue\u0026gt; #include \u0026lt;deque\u0026gt; // 使用默认底层容器 deque std::queue\u0026lt;int\u0026gt; q1; // 空队列 // 使用指定的底层容器 deque std::queue\u0026lt;int, std::deque\u0026lt;int\u0026gt;\u0026gt; q2; // 使用指定的底层容器 list std::queue\u0026lt;int, std::list\u0026lt;int\u0026gt;\u0026gt; q3; 常用操作 push(value)：将元素添加到队列的末尾。\npop()：删除队列的前端元素。\nfront()：访问队列的前端元素。\nback()：访问队列的末端元素。\nempty()：检查队列是否为空。\nsize()：获取队列中的元素数量。\n1 2 3 4 5 6 std::queue\u0026lt;int\u0026gt; q; q.push(10); q.push(20); std::cout \u0026lt;\u0026lt; q.front() \u0026lt;\u0026lt; std::endl; // 输出 10 q.pop(); std::cout \u0026lt;\u0026lt; q.front() \u0026lt;\u0026lt; std::endl; // 输出 20 适合的底层容器 deque：deque 是 std::queue 的默认底层容器，因为它支持高效的两端插入和删除操作，适合队列操作。\nlist：list 也是一个合适的底层容器，因为它在两端插入和删除操作的时间复杂度是 O(1)。\n遍历 利用vector容器进行操作：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include \u0026lt;iostream\u0026gt; #include \u0026lt;queue\u0026gt; #include \u0026lt;vector\u0026gt; int main() { std::queue\u0026lt;int\u0026gt; q; q.push(10); q.push(20); q.push(30); // 使用临时容器来遍历队列 std::vector\u0026lt;int\u0026gt; temp; while (!q.empty()) { temp.push_back(q.front()); q.pop(); } // 打印队列的元素（顺序） for (auto val : temp) { std::cout \u0026lt;\u0026lt; val \u0026lt;\u0026lt; \u0026#34; \u0026#34;; } return 0; } priority_queue 特点 优先级队列：std::priority_queue 是一种特殊类型的队列，其中元素按优先级排序。具有较高优先级的元素总是排在队列前面。\n底层容器：std::priority_queue 是基于堆（通常是二叉堆）实现的，因此它的插入和删除操作的时间复杂度是 O(log n)。\n限制访问：std::priority_queue 只允许访问队列的顶部元素（即最大或最小元素，取决于排序规则），无法直接访问其他元素。\n初始化 1 2 3 4 5 6 7 8 9 10 11 12 13 #include \u0026lt;queue\u0026gt; #include \u0026lt;vector\u0026gt; // 使用默认底层容器 vector，按默认的降序排序（最大堆） std::priority_queue\u0026lt;int\u0026gt; pq1; // 使用指定的底层容器 vector，按自定义的升序排序（最小堆） std::priority_queue\u0026lt;int, std::vector\u0026lt;int\u0026gt;, std::greater\u0026lt;int\u0026gt;\u0026gt; pq2; // 使用自定义比较器 struct Compare { bool operator()(int a, int b) { return a \u0026gt; b; // 升序排列 } }; std::priority_queue\u0026lt;int, std::vector\u0026lt;int\u0026gt;, Compare\u0026gt; pq3; 常用操作 push(value)：将元素插入到优先级队列中。\npop()：删除队列顶部的元素。\ntop()：访问队列顶部的元素（优先级最高的元素）。\nempty()：检查队列是否为空。\nsize()：获取队列中的元素数量。\n1 2 3 4 5 6 7 std::priority_queue\u0026lt;int\u0026gt; pq; pq.push(10); pq.push(20); pq.push(5); std::cout \u0026lt;\u0026lt; pq.top() \u0026lt;\u0026lt; std::endl; // 输出 20 pq.pop(); std::cout \u0026lt;\u0026lt; pq.top() \u0026lt;\u0026lt; std::endl; // 输出 10 适合的底层容器 vector：std::priority_queue 默认使用 vector 作为底层容器，基于堆实现。vector 提供了良好的内存布局和随机访问特性。\ndeque：deque 也可以作为底层容器，但通常情况下，vector 会是更高效的选择。\n遍历 利用vector容器进行操作：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include \u0026lt;iostream\u0026gt; #include \u0026lt;queue\u0026gt; #include \u0026lt;vector\u0026gt; int main() { std::priority_queue\u0026lt;int\u0026gt; pq; pq.push(10); pq.push(20); pq.push(5); // 使用临时容器来遍历 priority_queue std::vector\u0026lt;int\u0026gt; temp; while (!pq.empty()) { temp.push_back(pq.top()); pq.pop(); } // 打印队列的元素（优先级顺序） for (auto val : temp) { std::cout \u0026lt;\u0026lt; val \u0026lt;\u0026lt; \u0026#34; \u0026#34;; } return 0; } 5. bitset容器 bitset是一个存放二进制数的容器。\n初始化 默认初始化（所有位为0）：\n1 std::bitset\u0026lt;8\u0026gt; b; // 默认所有位为0 使用整数初始化： 你可以使用整数（例如 unsigned int）来初始化 bitset，这将根据整数的二进制表示来设置对应的位。\n1 std::bitset\u0026lt;8\u0026gt; b(42); // b = 00101010 (二进制表示42) 使用字符串初始化： 你也可以使用一个二进制字符串（必须只包含 0 和 1）来初始化 bitset，并且字符串的长度必须不超过 bitset 的大小。\n1 std::bitset\u0026lt;8\u0026gt; b(\u0026#34;10101010\u0026#34;); // b = 10101010 使用部分初始化： 如果初始化的字符串长度小于 bitset 的大小，剩余的位将被设置为 0。\n1 std::bitset\u0026lt;8\u0026gt; b(\u0026#34;101\u0026#34;); // b = 00000101 to_string的使用 to_string() 函数将 bitset 转换为一个包含二进制表示的字符串。该字符串由字符 '0' 和 '1' 构成，表示 bitset 中的位。\n示例： 1 2 3 4 5 6 7 8 9 #include \u0026lt;iostream\u0026gt; #include \u0026lt;bitset\u0026gt; #include \u0026lt;string\u0026gt; int main() { std::bitset\u0026lt;8\u0026gt; b(42); // b = 00101010 (二进制表示42) std::string binaryString = b.to_string(); std::cout \u0026lt;\u0026lt; \u0026#34;Binary string: \u0026#34; \u0026lt;\u0026lt; binaryString \u0026lt;\u0026lt; std::endl; // 输出: 00101010 return 0; } to_ulong的使用 to_ulong() 函数将 bitset 转换为 unsigned long 类型的数值。它返回 bitset 对应的整数值。如果 bitset 的大小超过了 unsigned long 能表示的最大位数，使用 to_ulong() 会抛出一个 std::overflow_error 异常。\n示例： 1 2 3 4 5 6 7 8 #include \u0026lt;iostream\u0026gt; #include \u0026lt;bitset\u0026gt; int main() { std::bitset\u0026lt;8\u0026gt; b(42); // b = 00101010 (二进制表示42) unsigned long num = b.to_ulong(); std::cout \u0026lt;\u0026lt; \u0026#34;Unsigned long: \u0026#34; \u0026lt;\u0026lt; num \u0026lt;\u0026lt; std::endl; // 输出: 42 return 0; } 6. 关联性容器 数据结构特点，迭代器 使用平衡二叉树。对于set，multiset，map，multimap，使用双向迭代器。\n集合，映射容器的相同与区别 set与multiset 二者默认都按照升序排列。\n在初始化时，可以通过构造函数的参数来更改排列方式：\n1 2 3 4 5 6 7 8 // set 默认升序排序 set\u0026lt;int\u0026gt; s = {3, 1, 4, 2}; for (int x : s) cout \u0026lt;\u0026lt; x \u0026lt;\u0026lt; \u0026#34; \u0026#34;; // 输出：1 2 3 4 cout \u0026lt;\u0026lt; endl; // 自定义降序排序 set\u0026lt;int, greater\u0026lt;int\u0026gt;\u0026gt; s_desc = {3, 1, 4, 2}; for (int x : s_desc) cout \u0026lt;\u0026lt; x \u0026lt;\u0026lt; \u0026#34; \u0026#34;; // 输出：4 3 2 1 cout \u0026lt;\u0026lt; endl; 前者不允许重复值，后者允许。\nmap与multimap 二者默认按照升序排列。在初始化时，也可以利用类似于set的方法进行自定义排序。\n前者为一对一排列，后者为一对多排列。前者不允许重复值，后者允许。\n初始化方法 set与multiset 二者初始化方法类似。以set举例：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include \u0026lt;iostream\u0026gt; #include \u0026lt;set\u0026gt; using namespace std; int main() { // 1. 空初始化 set\u0026lt;int\u0026gt; s; // 2. 列表初始化 set\u0026lt;int\u0026gt; s1 = {3, 1, 4, 2}; // 3. 复制初始化 set\u0026lt;int\u0026gt; s2(s1); // 复制另一个 set // 4. 通过范围初始化 int arr[] = {5, 6, 7}; set\u0026lt;int\u0026gt; s3(arr, arr + 3); // 5. 自定义比较器初始化（降序） set\u0026lt;int, greater\u0026lt;int\u0026gt;\u0026gt; s4 = {3, 1, 4, 2}; // 输出 for (int x : s4) cout \u0026lt;\u0026lt; x \u0026lt;\u0026lt; \u0026#34; \u0026#34;; // 输出：4 3 2 1 return 0; } map与multimap 二者初始化方法类似。以map举例：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include \u0026lt;iostream\u0026gt; #include \u0026lt;map\u0026gt; using namespace std; int main() { // 1. 空初始化 map\u0026lt;int, string\u0026gt; m; // 2. 列表初始化 map\u0026lt;int, string\u0026gt; m1 = {{1, \u0026#34;one\u0026#34;}, {2, \u0026#34;two\u0026#34;}, {3, \u0026#34;three\u0026#34;}}; // 3. 复制初始化 map\u0026lt;int, string\u0026gt; m2(m1); // 复制另一个 map // 4. 通过范围初始化 pair\u0026lt;int, string\u0026gt; arr[] = {{4, \u0026#34;four\u0026#34;}, {5, \u0026#34;five\u0026#34;}}; map\u0026lt;int, string\u0026gt; m3(arr, arr + 2); // 5. 自定义比较器初始化（降序） map\u0026lt;int, string, greater\u0026lt;int\u0026gt;\u0026gt; m4 = {{1, \u0026#34;one\u0026#34;}, {2, \u0026#34;two\u0026#34;}}; // 输出 for (auto \u0026amp;p : m4) cout \u0026lt;\u0026lt; p.first \u0026lt;\u0026lt; \u0026#34;: \u0026#34; \u0026lt;\u0026lt; p.second \u0026lt;\u0026lt; endl; // 输出：2: two 1: one return 0; } 添加新数据 set与multiset 使用 insert() 方法\nset/multiset 都可以使用 insert() 方法添加新元素。\n返回值：\n对于 set：返回 pair\u0026lt;iterator, bool\u0026gt;，bool 表示是否插入成功。\n对于 multiset：返回插入的元素位置的迭代器。\n通过范围插入\n可以使用 insert 将其他容器或范围的元素插入到当前容器中。 示例代码：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include \u0026lt;iostream\u0026gt; #include \u0026lt;set\u0026gt; using namespace std; int main() { // 初始化 set 和 multiset set\u0026lt;int\u0026gt; s = {1, 2, 3}; multiset\u0026lt;int\u0026gt; ms = {1, 2, 3}; // 添加新元素到 set auto result = s.insert(4); // 插入元素 4 cout \u0026lt;\u0026lt; \u0026#34;Insert 4 into set: \u0026#34; \u0026lt;\u0026lt; (result.second ? \u0026#34;Success\u0026#34; : \u0026#34;Failure\u0026#34;) \u0026lt;\u0026lt; endl; // 输出：Success result = s.insert(2); // 插入元素 2（已存在） cout \u0026lt;\u0026lt; \u0026#34;Insert 2 into set: \u0026#34; \u0026lt;\u0026lt; (result.second ? \u0026#34;Success\u0026#34; : \u0026#34;Failure\u0026#34;) \u0026lt;\u0026lt; endl; // 输出：Failure // 添加新元素到 multiset ms.insert(4); // 插入元素 4 ms.insert(2); // 插入重复的元素 2 // 输出 set 和 multiset 的元素 cout \u0026lt;\u0026lt; \u0026#34;Set elements: \u0026#34;; for (int x : s) cout \u0026lt;\u0026lt; x \u0026lt;\u0026lt; \u0026#34; \u0026#34;; // 输出：1 2 3 4 cout \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;Multiset elements: \u0026#34;; for (int x : ms) cout \u0026lt;\u0026lt; x \u0026lt;\u0026lt; \u0026#34; \u0026#34;; // 输出：1 2 2 3 4 cout \u0026lt;\u0026lt; endl; return 0; } map与multimap 使用 insert() 方法\n插入一个 pair 类型的键值对。\n返回值：\n对于 map：返回 pair\u0026lt;iterator, bool\u0026gt;，bool 表示插入是否成功。\n对于 multimap：返回迭代器，指向插入元素的位置。\n通过 [] 运算符添加数据（仅限 map）\nmap 可以使用 [] 直接插入键值对。\n如果键已存在，会更新对应的值；如果键不存在，会创建新的键值对。\n示例代码：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include \u0026lt;iostream\u0026gt; #include \u0026lt;map\u0026gt; using namespace std; int main() { // 初始化 map 和 multimap map\u0026lt;int, string\u0026gt; m = {{1, \u0026#34;one\u0026#34;}, {2, \u0026#34;two\u0026#34;}}; multimap\u0026lt;int, string\u0026gt; mm = {{1, \u0026#34;one\u0026#34;}, {2, \u0026#34;two\u0026#34;}}; // 添加新元素到 map auto result = m.insert({3, \u0026#34;three\u0026#34;}); // 插入键值对 {3, \u0026#34;three\u0026#34;} cout \u0026lt;\u0026lt; \u0026#34;Insert {3, \u0026#39;three\u0026#39;} into map: \u0026#34; \u0026lt;\u0026lt; (result.second ? \u0026#34;Success\u0026#34; : \u0026#34;Failure\u0026#34;) \u0026lt;\u0026lt; endl; // 输出：Success result = m.insert({2, \u0026#34;duplicate\u0026#34;}); // 插入已存在的键 2 cout \u0026lt;\u0026lt; \u0026#34;Insert {2, \u0026#39;duplicate\u0026#39;} into map: \u0026#34; \u0026lt;\u0026lt; (result.second ? \u0026#34;Success\u0026#34; : \u0026#34;Failure\u0026#34;) \u0026lt;\u0026lt; endl; // 输出：Failure // 使用 [] 添加或更新元素（仅限 map） m[4] = \u0026#34;four\u0026#34;; // 添加键值对 {4, \u0026#34;four\u0026#34;} m[2] = \u0026#34;updated\u0026#34;; // 更新键 2 的值 // 添加新元素到 multimap mm.insert({3, \u0026#34;three\u0026#34;}); // 插入键值对 {3, \u0026#34;three\u0026#34;} mm.insert({2, \u0026#34;duplicate\u0026#34;}); // 插入重复的键 2 // 输出 map 和 multimap 的元素 cout \u0026lt;\u0026lt; \u0026#34;Map elements: \u0026#34; \u0026lt;\u0026lt; endl; for (auto \u0026amp;p : m) { cout \u0026lt;\u0026lt; p.first \u0026lt;\u0026lt; \u0026#34;: \u0026#34; \u0026lt;\u0026lt; p.second \u0026lt;\u0026lt; endl; // 输出键值对 } cout \u0026lt;\u0026lt; \u0026#34;Multimap elements: \u0026#34; \u0026lt;\u0026lt; endl; for (auto \u0026amp;p : mm) { cout \u0026lt;\u0026lt; p.first \u0026lt;\u0026lt; \u0026#34;: \u0026#34; \u0026lt;\u0026lt; p.second \u0026lt;\u0026lt; endl; // 输出键值对 } return 0; } lower_bound和upper_bound的使用 lower_bound(key)：返回第一个 不小于（大于或等于）键 key 的迭代器。\nupper_bound(key)：返回第一个 大于键 key 的迭代器。\n示例代码：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include \u0026lt;iostream\u0026gt; #include \u0026lt;set\u0026gt; #include \u0026lt;map\u0026gt; using namespace std; int main() { //set 示例 set\u0026lt;int\u0026gt; s = {1, 2, 4, 5}; auto lb = s.lower_bound(3); // 找到第一个 \u0026gt;= 3 的元素 auto ub = s.upper_bound(3); // 找到第一个 \u0026gt; 3 的元素 cout \u0026lt;\u0026lt; \u0026#34;lower_bound(3): \u0026#34; \u0026lt;\u0026lt; (lb != s.end() ? to_string(*lb) : \u0026#34;not found\u0026#34;) \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;upper_bound(3): \u0026#34; \u0026lt;\u0026lt; (ub != s.end() ? to_string(*ub) : \u0026#34;not found\u0026#34;) \u0026lt;\u0026lt; endl; // map 示例 // 在 map 中，主要是和键值对里的 key 比较 map\u0026lt;int, string\u0026gt; m = {{1, \u0026#34;one\u0026#34;}, {2, \u0026#34;two\u0026#34;}, {4, \u0026#34;four\u0026#34;}, {5, \u0026#34;five\u0026#34;}}; auto mlb = m.lower_bound(3); auto mub = m.upper_bound(3); cout \u0026lt;\u0026lt; \u0026#34;map lower_bound(3): \u0026#34; \u0026lt;\u0026lt; (mlb != m.end() ? to_string(mlb-\u0026gt;first) : \u0026#34;not found\u0026#34;) \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;map upper_bound(3): \u0026#34; \u0026lt;\u0026lt; (mub != m.end() ? to_string(mub-\u0026gt;first) : \u0026#34;not found\u0026#34;) \u0026lt;\u0026lt; endl; return 0; } 关联性容器的遍历 set与multiset 1 2 3 4 5 6 7 8 9 10 11 #include \u0026lt;iostream\u0026gt; #include \u0026lt;set\u0026gt; int main() { std::set\u0026lt;int\u0026gt; mySet = {1, 2, 3, 4, 5}; // 使用迭代器遍历 for (auto it = mySet.begin(); it != mySet.end(); ++it) { std::cout \u0026lt;\u0026lt; *it \u0026lt;\u0026lt; \u0026#34; \u0026#34;; } std::cout \u0026lt;\u0026lt; std::endl; return 0; } map与multimap 1 2 3 4 5 6 7 8 9 10 #include \u0026lt;iostream\u0026gt; #include \u0026lt;map\u0026gt; int main() { std::map\u0026lt;int, std::string\u0026gt; myMap = {{1, \u0026#34;one\u0026#34;}, {2, \u0026#34;two\u0026#34;}, {3, \u0026#34;three\u0026#34;}}; // 使用迭代器遍历 for (auto it = myMap.begin(); it != myMap.end(); ++it) { std::cout \u0026lt;\u0026lt; it-\u0026gt;first \u0026lt;\u0026lt; \u0026#34;: \u0026#34; \u0026lt;\u0026lt; it-\u0026gt;second \u0026lt;\u0026lt; std::endl; } return 0; } 7. string容器 初始化方法 使用字符常量初始化 1 std::string str = \u0026#34;Hello, World!\u0026#34;; 这里 str 被初始化为一个包含 \u0026quot;Hello, World!\u0026quot; 的字符串。\n使用字符数组初始化 1 2 const char* arr = \u0026#34;Hello, World!\u0026#34;; std::string str(arr); 这种方式将一个 const char* 类型的 C 风格字符串转换为 std::string。\n指定长度初始化 1 std::string str(10, \u0026#39;a\u0026#39;); // 创建一个包含 10 个字符 \u0026#39;a\u0026#39; 的字符串 这会创建一个长度为 10 的字符串，所有字符都是 'a'。\n使用子字符串初始化 1 2 3 std::string str = \u0026#34;Hello, World!\u0026#34;; std::string subStr(str, 7, 5); // 从位置 7 开始，提取 5 个字符 // subStr 将是 \u0026#34;World\u0026#34; 这种方法可以从现有字符串中提取一个子字符串。\n拷贝构造初始化 1 2 std::string str1 = \u0026#34;Hello\u0026#34;; std::string str2 = str1; // 通过拷贝构造函数初始化 通过拷贝另一个字符串初始化新的字符串。\n常见操作 insert 操作 insert 用于在指定位置插入字符或字符串。\n1 2 3 std::string str = \u0026#34;Hello, World!\u0026#34;; str.insert(5, \u0026#34; Beautiful\u0026#34;); // 在索引 5 位置插入 \u0026#34; Beautiful\u0026#34; std::cout \u0026lt;\u0026lt; str \u0026lt;\u0026lt; std::endl; // 输出 \u0026#34;Hello Beautiful, World!\u0026#34; 说明：\ninsert(pos, str)：在 pos 位置插入字符串 str。\ninsert(pos, n, ch)：在 pos 位置插入 n 个字符 ch。\nreplace 操作 replace 用于替换指定位置的子字符串。\n1 2 3 std::string str = \u0026#34;Hello, World!\u0026#34;; str.replace(7, 5, \u0026#34;C++\u0026#34;); // 从位置 7 开始替换 5 个字符，替换成 \u0026#34;C++\u0026#34; std::cout \u0026lt;\u0026lt; str \u0026lt;\u0026lt; std::endl; // 输出 \u0026#34;Hello, C++!\u0026#34; 说明：\nreplace(pos, len, str)：从 pos 位置开始，替换 len 长度的字符为 str。 erase 操作 erase 用于删除指定位置的字符或子字符串。\n1 2 3 std::string str = \u0026#34;Hello, World!\u0026#34;; str.erase(5, 7); // 从位置 5 开始删除 7 个字符 std::cout \u0026lt;\u0026lt; str \u0026lt;\u0026lt; std::endl; // 输出 \u0026#34;Hello\u0026#34; 说明：\nerase(pos, len)：从 pos 位置开始删除 len 长度的字符。\nerase(pos)：删除从 pos 开始直到字符串末尾的所有字符。\nstring 与 stringstream的结合 类型转换、字符串解析和数字字符串化等。\n六、函数对象 1. 函数对象的概念 概念 **函数对象（Function Object）**是一个重载了operator()运算符的类或结构体的实例。\n当我们对函数对象使用()操作时，实际上调用的是该对象的operator()函数。\n比如这个例子：\n1 2 3 4 5 6 7 8 class MyFunctor { public: int operator()(int x, int y) { return x + y; } // 重载()运算符 }; MyFunctor func; int result = func(3, 5); // 等价于调用 func.operator()(3, 5)，result == 8 优点 灵活性高\n函数对象比普通函数更灵活，可以通过成员变量保存状态。\n效率更高\n编译器可以内联函数对象的调用，从而减少函数调用的开销。\n便于与算法结合\n函数对象可以作为参数传递给标准库算法，增强算法的通用性和可配置性。\n2. 函数对象的分类 | 分类 | 定义 | 示例 |\n| \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;- | \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash; | \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; |\n| 发生器 | 无参数，生成值 | RandomGenerator |\n| 一元函数 | 接受一个参数，返回一个值 | Square |\n| 一元判定函数 | 接受一个参数，返回布尔值 | IsOdd |\n| 二元函数 | 接受两个参数，返回一个值 | Add |\n| 二元判定函数 | 接受两个参数，返回布尔值 | Greater |\n3.函数对象与容器结合使用举例 使用函数对象对容器排序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; #include \u0026lt;algorithm\u0026gt; using namespace std; class Descending { public: bool operator()(int x, int y) const { return x \u0026gt; y; // 降序排序规则 } }; int main() { vector\u0026lt;int\u0026gt; vec = {3, 1, 4, 1, 5, 9}; // 使用函数对象 Descending 排序 sort(vec.begin(), vec.end(), Descending()); for (vector\u0026lt;int\u0026gt;::iterator it = vec.begin(); it != vec.end(); ++it) { cout \u0026lt;\u0026lt; *it \u0026lt;\u0026lt; \u0026#34; \u0026#34;; // 输出：9 5 4 3 1 1 } return 0; } 用函数对象筛选容器元素 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; #include \u0026lt;algorithm\u0026gt; using namespace std; class IsEven { public: bool operator()(int x) const { return x % 2 == 0; // 判断是否为偶数 } }; int main() { vector\u0026lt;int\u0026gt; vec = {3, 1, 4, 1, 5, 9, 2, 6, 5}; // 移除所有偶数 vector\u0026lt;int\u0026gt;::iterator new_end = remove_if(vec.begin(), vec.end(), IsEven()); vec.erase(new_end, vec.end()); for (vector\u0026lt;int\u0026gt;::iterator it = vec.begin(); it != vec.end(); ++it) { cout \u0026lt;\u0026lt; *it \u0026lt;\u0026lt; \u0026#34; \u0026#34;; // 输出：3 1 1 5 9 5 } return 0; } 4.函数对象与算法结合使用举例 结合 std::transform 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; #include \u0026lt;algorithm\u0026gt; using namespace std; class Square { public: int operator()(int x) const { return x * x; // 返回平方 } }; int main() { vector\u0026lt;int\u0026gt; vec = {1, 2, 3, 4}; vector\u0026lt;int\u0026gt; result(vec.size()); // 使用函数对象 Square 对每个元素求平方 transform(vec.begin(), vec.end(), result.begin(), Square()); for (vector\u0026lt;int\u0026gt;::iterator it = result.begin(); it != result.end(); ++it) { cout \u0026lt;\u0026lt; *it \u0026lt;\u0026lt; \u0026#34; \u0026#34;; // 输出：1 4 9 16 } return 0; } 结合 std::accumulate 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; #include \u0026lt;numeric\u0026gt; using namespace std; class Add { public: int operator()(int x, int y) const { return x + y; // 执行加法操作 } }; int main() { vector\u0026lt;int\u0026gt; vec = {1, 2, 3, 4}; // 使用函数对象 Add 计算累加和 int sum = accumulate(vec.begin(), vec.end(), 0, Add()); cout \u0026lt;\u0026lt; \u0026#34;Sum: \u0026#34; \u0026lt;\u0026lt; sum \u0026lt;\u0026lt; endl; // 输出：Sum: 10 return 0; } 5.系统函数对象 算术运算函数对象 plus\u0026lt;T\u0026gt;：加法\nminus\u0026lt;T\u0026gt;：减法\nmultiplies\u0026lt;T\u0026gt;：乘法\ndivides\u0026lt;T\u0026gt;：除法\nmodulus\u0026lt;T\u0026gt;：取模\nnegate\u0026lt;T\u0026gt;：取反\n关系运算函数对象 greater\u0026lt;T\u0026gt;：大于\nless\u0026lt;T\u0026gt;：小于\ngreater_equal\u0026lt;T\u0026gt;：大于等于\nless_equal\u0026lt;T\u0026gt;：小于等于\nequal_to\u0026lt;T\u0026gt;：等于\nnot_equal_to\u0026lt;T\u0026gt;：不等于\n逻辑运算函数对象 logical_and\u0026lt;T\u0026gt;：逻辑与\nlogical_or\u0026lt;T\u0026gt;：逻辑或\nlogical_not\u0026lt;T\u0026gt;：逻辑非\n七、算法 1. 常用算法举例 count_if 功能：统计容器中满足特定条件的元素个数。\n函数原型：\n1 2 3 template \u0026lt;class InputIterator, class Predicate\u0026gt; typename iterator_traits\u0026lt;InputIterator\u0026gt;::difference_type count_if(InputIterator first, InputIterator last, Predicate pred); first, last: 范围的起止迭代器。\npred: 条件谓词。\n示例：统计偶数个数\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; #include \u0026lt;algorithm\u0026gt; using namespace std; class IsEven { public: bool operator()(int x) const { return x % 2 == 0; } }; int main() { vector\u0026lt;int\u0026gt; vec = {1, 2, 3, 4, 5, 6}; // 使用 count_if 统计偶数个数 int even_count = count_if(vec.begin(), vec.end(), IsEven()); cout \u0026lt;\u0026lt; \u0026#34;Even numbers: \u0026#34; \u0026lt;\u0026lt; even_count \u0026lt;\u0026lt; endl; // 输出：Even numbers: 3 return 0; } count 功能：统计容器中与指定值相等的元素个数。\n函数原型：\n1 2 3 template \u0026lt;class InputIterator, class T\u0026gt; typename iterator_traits\u0026lt;InputIterator\u0026gt;::difference_type count(InputIterator first, InputIterator last, const T \u0026amp;value); first, last: 范围的起止迭代器。\nvalue: 要统计的目标值。\n示例：统计数字 3 的出现次数\n1 2 3 4 5 6 7 8 9 10 11 #include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; #include \u0026lt;algorithm\u0026gt; using namespace std; int main() { vector\u0026lt;int\u0026gt; vec = {1, 3, 3, 4, 5, 3}; // 使用 count 统计 3 的个数 int count_three = count(vec.begin(), vec.end(), 3); cout \u0026lt;\u0026lt; \u0026#34;Number of 3s: \u0026#34; \u0026lt;\u0026lt; count_three \u0026lt;\u0026lt; endl; // 输出：Number of 3s: 3 return 0; } find 功能：在容器范围中查找第一个与指定值相等的元素，返回迭代器。\n函数原型：\n1 2 template \u0026lt;class InputIterator, class T\u0026gt; InputIterator find(InputIterator first, InputIterator last, const T \u0026amp;value); first, last: 范围的起止迭代器。\nvalue: 要查找的目标值。\n示例：查找第一个值为 4 的位置\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; #include \u0026lt;algorithm\u0026gt; using namespace std; int main() { vector\u0026lt;int\u0026gt; vec = {1, 2, 3, 4, 5}; // 使用 find 查找值为 4 的元素 vector\u0026lt;int\u0026gt;::iterator it = find(vec.begin(), vec.end(), 4); if (it != vec.end()) { cout \u0026lt;\u0026lt; \u0026#34;Found 4 at position: \u0026#34; \u0026lt;\u0026lt; distance(vec.begin(), it) \u0026lt;\u0026lt; endl; // 输出：Found 4 at position: 3 } else { cout \u0026lt;\u0026lt; \u0026#34;4 not found\u0026#34; \u0026lt;\u0026lt; endl; } return 0; } find_end 功能：查找容器范围内最后一个子序列的位置。\n函数原型：\n1 2 template \u0026lt;class ForwardIterator1, class ForwardIterator2\u0026gt; ForwardIterator1 find_end(ForwardIterator1 first1, ForwardIterator1 last1, ForwardIterator2 first2, ForwardIterator2 last2); first1, last1: 容器范围。\nfirst2, last2: 子序列范围。\n示例：查找子序列 {3, 4} 的最后出现位置：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; #include \u0026lt;algorithm\u0026gt; using namespace std; int main() { vector\u0026lt;int\u0026gt; vec = {1, 2, 3, 4, 3, 4, 5}; vector\u0026lt;int\u0026gt; sub = {3, 4}; // 使用 find_end 查找子序列的最后位置 vector\u0026lt;int\u0026gt;::iterator it = find_end(vec.begin(), vec.end(), sub.begin(), sub.end()); if (it != vec.end()) { cout \u0026lt;\u0026lt; \u0026#34;Last occurrence starts at position: \u0026#34; \u0026lt;\u0026lt; distance(vec.begin(), it) \u0026lt;\u0026lt; endl; // 输出：Last occurrence starts at position: 4 } else { cout \u0026lt;\u0026lt; \u0026#34;Subsequence not found\u0026#34; \u0026lt;\u0026lt; endl; } return 0; } adjacent_find 功能：查找范围中相邻两个相等元素的第一个位置，或满足特定条件的相邻元素。\n函数原型：\n1 2 3 4 template \u0026lt;class ForwardIterator\u0026gt; ForwardIterator adjacent_find(ForwardIterator first, ForwardIterator last); template \u0026lt;class ForwardIterator, class BinaryPredicate\u0026gt; ForwardIterator adjacent_find(ForwardIterator first, ForwardIterator last,BinaryPredicate pred); pred: 二元谓词，用于自定义相邻元素匹配条件。 查找相邻相等元素：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; #include \u0026lt;algorithm\u0026gt; using namespace std; int main() { vector\u0026lt;int\u0026gt; vec = {1, 2, 2, 3, 4}; // 使用 adjacent_find 查找相邻相等元素 vector\u0026lt;int\u0026gt;::iterator it = adjacent_find(vec.begin(), vec.end()); if (it != vec.end()) { cout \u0026lt;\u0026lt; \u0026#34;Adjacent equal elements found at position: \u0026#34; \u0026lt;\u0026lt; distance(vec.begin(), it) \u0026lt;\u0026lt; endl; // 输出：Adjacent equal elements found at position: 1 } else { cout \u0026lt;\u0026lt; \u0026#34;No adjacent equal elements\u0026#34; \u0026lt;\u0026lt; endl; } return 0; } 示例 2：查找相邻满足条件的元素：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; #include \u0026lt;algorithm\u0026gt; using namespace std; // 自定义二元谓词：相邻元素差值大于 2 // 使用自定义函数对象 class DiffGreaterThanTwo { public: bool operator()(int x, int y) const { return abs(x - y) \u0026gt; 2; // 绝对值大于 2 } }; int main() { vector\u0026lt;int\u0026gt; vec = {1, 3, 6, 7}; // 使用 adjacent_find 查找相邻差值大于 2 的元素 vector\u0026lt;int\u0026gt;::iterator it = adjacent_find(vec.begin(), vec.end(), DiffGreaterThanTwo()); if (it != vec.end()) { cout \u0026lt;\u0026lt; \u0026#34;Adjacent elements with difference \u0026gt; 2 found at position: \u0026#34; \u0026lt;\u0026lt; distance(vec.begin(), it) \u0026lt;\u0026lt; endl; // 输出：Adjacent elements with difference \u0026gt; 2 found at position: 1 } else { cout \u0026lt;\u0026lt; \u0026#34;No such adjacent elements\u0026#34; \u0026lt;\u0026lt; endl; } return 0; } 2. 使用copy算法实现容器遍历输出 代码示例：\n1 2 3 4 5 6 7 8 9 10 11 12 13 #include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; #include \u0026lt;algorithm\u0026gt; #include \u0026lt;iterator\u0026gt; using namespace std; int main() { // 定义一个容器 vector\u0026lt;int\u0026gt; vec = {1, 2, 3, 4, 5}; // 使用 std::copy 和 std::ostream_iterator 遍历输出 cout \u0026lt;\u0026lt; \u0026#34;Container elements: \u0026#34;; copy(vec.begin(), vec.end(), ostream_iterator\u0026lt;int\u0026gt;(cout, \u0026#34; \u0026#34;)); cout \u0026lt;\u0026lt; endl; return 0; ","date":"2024-12-27T20:52:00Z","permalink":"/p/cppstl-review/","title":"C++ STL 期末复习"},{"content":"好像目前没怎么看到这个机子装黑果的教程，于是就打算写一篇作为参考了\n安装 Windows 系统 我这台机子不知道为什么，用原版镜像重装Win11 后，Fn+Q 的热键驱动打不上去，没法开启野兽模式，所以改用联想的 OEM 镜像。下面的创建教程应该适合所有的联想电脑。\n想要下载 OEM 镜像需要先创建恢复介质的订单。国内的联想站不提供这个机子的镜像，需要到香港的站点申请。打开申请的网站，输入机器编号，点击“送出”。\n在下一个页面中，选择恢复镜像的语言并同意条款，点击“下一步”，按照指引登录账号进行操作。登录账号最好使用邮箱进行登录，方便后续接受恢复订单的信息。\n中间可能会要求更改网站语言，更改语言后重新按照上面的步骤来就可以了。登录账号后会要求输入个人信息，填写后点击“Submit”。\n看到像是下图这样的页面，订单就创建好了。\n此时检查你注册账号使用的邮箱，会发现多出来一个订单信息的邮件，按照说明点击蓝色的“RDDS”下载创建工具。\n下载创建工具后，按照创建工具里面的指引，创建恢复盘。提前准备好一个32G以上的U盘插入电脑，里面的内容在做启动盘时都会被删除。工具使用前必须要登录联想账号，一定要登录刚刚创建订单使用的联想账号，工具只有在识别到账号下的订单后才可以继续使用。\n创建完成后，确保U盘插在电脑上，设置从U盘启动，启动恢复程序。中间会重启几次，耐心等待一会。Pro 13的出厂镜像是Windows 10 2004，恢复好后可以用原版镜像升级到Windows 11。\n安装黑苹果 我这里安装的是Sonoma 14.2.1，更低版本的系统教程通用。\n先简单说一下安装黑苹果的原理，黑苹果安装的关键在于引导文件（也就是EFI），有了对应的EFI之后才可以正常引导系统进行安装。对于小新Pro 13 2020，可以使用这个项目下的引导文件，具体的操作步骤之后会讲。\n小新Pro 13安装黑苹果前需要刷破解BIOS并且进行相关设置，详情请参考这篇教程。\n除了上面教程提到的设置之外，在BIOS中还需要另外修改以下三项（此处参考这篇教程）：\nSecurity选项卡下：\nIntel Platform Trust Technology = Disable\nIntel SGX Control = Disable 【建议】\nSecure Boot = Disable\n1、准备安装U盘 下载好黑苹果的系统镜像，版本自选，黑苹果镜像分为两种，以dmg结尾的可引导镜像和以iso结尾的不可引导镜像，确保下载的是以dmg结尾的这种。\n准备好一个32G以上大小的U盘，用balenaEtcher把镜像写入U盘。写入的时候同样会清除U盘里面的数据，记得提前备份。\n如果使用balenaEtcher时，多次尝试仍然写入失败，可以改用TransMac制作启动盘。使用TransMac制作启动盘的方法可以参考这篇教程。\n打开这个仓库，点击绿色的Code标签，点击Download ZIP把整个仓库的文件下载下来。解压出来EFI文件夹下的EFI_OC文件夹，这个就是我们一会要用到的引导文件。\n打开DiskGenius，点击左侧U盘下的EFI分区，删掉EFI目录下的所有文件，然后把刚刚下好的EFI文件替换进去。\n2、开始安装 用Windows自带的磁盘管理新分出来一个分区，创建好后格式化为NTFS分区。\nU盘插入电脑，重启电脑设置从U盘启动，使用左右方向键选择“Install macOS Sonoma”，回车启动。\n在安装页面中，先选择“磁盘工具”，点击右上角的“分区”按钮。可以看到两个分区。\n选择刚刚新创建的分区，格式选择 APFS，点击“应用”。\n退出磁盘工具，点击“安装 macOS”，选择刚刚的分区，按照指引安装操作。\n安装过程中会重启，第一次重启过后，需要再从 U 盘引导 macOS，可以看到 OC 的引导界面多了一个启动项，这个就是安装在本地的 macOS。选择这个启动项启动，后面安装时还会重启几次，每次重启都需要从 U 盘引导系统，选择本地 macOS 的启动项，直到看到设置向导为止。此时你会发现还没有网络，不要着急，先不联网把设置向导走完，驱动下一步再补。\n3、补全驱动，设置本地启动项 现在我们是通过U 盘来启动 macOS，这一步我们需要把黑苹果的引导安装到本地。由于驱动包含在 EFI 内，所以驱动补全也可以同时完成。EFI可以参考前述项目内的配置，配置文件下载可以点击此处。\n打开DiskGenius，左侧点击SYSTEM_DRV分区，打开EFI文件夹，把压缩包内的OC文件夹复制进去，然后点开OC文件夹，把我改好的config.plist文件覆盖进去。\n然后点击上方的“工具”——“设置UEFI BIOS启动项”，点击左侧的“添加”，文件选择OC文件夹下的OpenCore.efi，然后右侧启动项名称改为“OpenCore”，点击“保存当前启动项设置”，点击左侧的“上移”，把OpenCore放在第一位。点击“取消”退出。\n重启电脑，此时就可以进入OpenCore的引导界面了，选择macOS启动，也可以看到网卡已经能用了。这个配置文件已经注入了三码，iCloud也可以正常使用。\n到这里所有的工作就都完成了，可以开始体验黑苹果啦~\n","date":"2024-07-27T19:09:00Z","permalink":"/p/install-windows-and-macos-on-xiaoxinpro13/","title":"联想小新 Pro 13 2020 款 Windows 系统+黑苹果安装记录"},{"content":" 突然就想做一个SDVX的手台玩了，第一次搞这些踩了不少坑，就在这里简单写一下自己的制作经验。\n在开始制作之前，请确认你自己有足够的耐心和一定的动手能力，一般来说我更建议买现成的商业手台，虽然要比自己做贵一些，但是更方便，体验也更好一些。\n在制作过程中，我参考了以下项目和文章，在这里对各位大佬表示感谢：\n简单自制SDVX手台（控制器）\nlizardbeans/sdvx-diy\nFreezeRasis/sdvx-diy-pcb\nSirius-RX/SDVX-SP_Con\n并且特别感谢@Huang_xuan 帮助修改固件和组装过程的各种答疑解惑！\n一、材料准备 我做手台用到的材料如下：\n1.开发板：用的是这个仓库里的方案，需要下载嘉立创EDA，然后把仓库里的json文件导入后下单，具体使用方法仓库里面写的已经很清楚了。嘉立创每个月可以免费下单两次PCB，在下单平台里领券之后再确认订单。开发板需要买Pro Micro，芯片是ATMEGA32U4。\n2.外壳：直接去淘宝一家名叫“多美歌外壳”做的雪弗板，他们那里有现成的方案，不要图案的话可以便宜20块。\n多美歌的板子厚度是10mm，虽然会有点厚，但是在不需要加装亚克力板的情况下，实测对安装没什么影响。或者你也可以用外壳的CAD文件自己定制。CAD文件你可以在这个仓库里找到：\n3.按键：用的是淘宝“喵呜手台店”的按键，优点就是便宜而且自带弹簧。\n体验想要更好的话可以换星韵家的（淘宝“星韵工作室”）或者三和按键（特别贵，但据说手感也非常好），弹簧也需要自己另外买。\n4.微动：霍尼韦尔V15S05-1EZ050-K01，一共需要买7个，想要体验更好就上欧姆龙的。\n5.旋转编码器和旋钮：需要买磁电式NPN的，工作电压5-24V，脉冲600ppr，AB两相，需要买两个。\n旋钮就选一般的全铝旋钮就可以。规格直径30mm高25mm，也是买两个。\n6.其他的一些小零件：\n（1）M5*25 自攻螺丝 19个，用来固定外壳；\n（2）M3*16 平头螺丝6个，用来固定旋转编码器；\n（3）5V蓝色迷你LED灯5个，红色2个，红色的需要安装到FX-L和FX-R两个按键上，蓝色的安装到其他按键上，如果你不需要灯效就不需要买；\n（4）带灯按键微动线7条，用来连接按键到板子上，淘宝搜对应关键词就能找到了；\n（5）XH2.54 公头单头端子线 2条，用来连接旋转编码器到板子上\n（6）XH2.54直针座 4P 9个，焊接到板子上需要；\n（7）Type-C/MicroUSB延长线，具体看你买的板子用的接口；\n（8）防滑脚垫4个（可选）；\n7.需要的工具：\n（1）焊接相关的东西（烙铁，锡丝，松香等等）；\n（2）一把十字头螺丝刀；\n（3）一套六角扳手；\n（4）一把剪线钳；\n（5）一把热熔胶枪，用于固定PCB板；\n二、组装过程 1.焊接 分别需要焊接板子和旋转编码器的线头。板子的左上角已经给开发板预留了位置，把Pro Micro接口朝外放在左上角的位置，然后转到PCB板的背面，把开发板的针脚焊接在板子上。剩余的接口都是给端子座预留了，在每个位置上用类似的方法焊接一个上去。焊接的教程可以看这个视频。\n接下来是线头的焊接。按照PCB板的引脚定义，从左到右的顺序应该为：5V，OUTA，OUTB，GND。旋转编码器上已经写清楚了不同颜色的线对应的功能，把他们按照这个顺序焊接到端子线上，焊接好后记得分别缠上一圈电工胶带。焊接线头可以参考这个教程。\n2.固件上传 需要下载Arduino IDE，把焊接好后的板子连接到电脑上，点击“Tools”选项卡,“Board\u0026quot;一栏选择“Arduino Leonardo”，端口选择你板子的端口。\n在这个仓库里下载固件，双击leovx.ino在Arduino中打开，然后点击左上方的右箭头键，把固件上传到开发板。\n3.组装 (1)按键部分\n一个按键一般会有这几部分：按键本体，套在按键外面的壳子，微动支架，以及固定壳子的螺母（不知道怎么叫准确，姑且先这么叫吧）。\n先把按键扣在雪弗板上对应的位置，\n然后再把壳子扣在按键的背面，\n把螺母拧上去拧紧，\n按键部分就组装完成了。\n把微动固定在微动支架上，支架上有两个凸出来的地方，和微动是对应的，按照这个地方安装微动。\n把灯固定在微动支架的下端，注意灯上长的一端是正极，这个一会接线的时候需要注意。把安装好的微动支架拧到按键背面，这样按键部分就安装好了。其他的按键也用类似的办法装上去；\n(2)旋转编码器部分\n旋转编码器上有6个螺丝孔，把其中的任意三个对准雪弗板正面预留的空位上，然后用M3*16的螺丝拧上去。\n你可以看到旋转编码器会露出一截轴，用六角螺丝刀，把旋钮固定到轴上面。\n(3)接线部分\nPCB板上已经写好了每个插口对应的部分，前面已经焊接好了旋转编码器的线，直接插到对应位置就可以了。对于按键部分，我简单做了一个图展示怎么连接：\nPS：微动支架上不严格区分正负极。也就是说只要确保把PCB板上负责正负极的两个引脚接到支架两端就可以了。\n(4)外壳部分\n板子上已经给了组装时候需要用到的孔位了，先把板子按照形状拼好，然后把M5*25的自攻螺丝放到孔里，直接使劲拧进去就行，注意区分预留USB孔位的那一面，不要把螺丝拧到这里。把脚垫贴在手台底部的四个角。\n(5)其他部分\n用热熔胶枪，把板子固定到背面FX-L和FX-R两个按键中间的位置。然后把延长线一端接到板子上，另一端从壳子上预留的孔位绕出来。先暂时不要把正面合盖。\n4.软件测试 在spice里绑定对应的键位，并测试功能是否正常，注意旋钮需要另外在“Analogs”下绑定。如果发现旋钮左右旋转方向是反过来的，需要勾选“Invert Axis”选项。灵敏度推荐调整为0.25。\n如果你发现在这个菜单下，旋钮高速旋转时并不能被正确识别，请不要慌张，进游戏后在服务菜单里再测试一下，只要游戏里旋转正常就没什么问题。\n测试没有问题之后就可以合盖安装了。\n至此，你已经做好了一个可以正常游玩的SDVX手台，可以愉快玩耍啦~\n","date":"2024-05-19T16:36:00Z","permalink":"/p/sdvx-controller-diy/","title":"SDVX 手台自制教程"},{"content":" 这几天试着往电脑上安装FydeOS，不知道为什么FydeOS自带的rEFInd安装不上去，就试着自己搞了一下，在此整理了自己配置的步骤。\nrEFInd是一个UI更为现代化的启动引导器，在Linux/Mac下，rEFInd可以使用自带的脚本安装，Windows则需要自己配置。以下内容适用于使用EFI启动的Windows平台。\nSurface设备以及部分电脑配置rEFInd时，可能需要关闭安全启动（Secure Boot）。\n1.安装rEFInd 从官网下载rEFInd的相关文件，得到一个zip结尾的压缩文件。打开压缩文件，将里面的refind文件夹复制出来。\n打开DiskGenius，在左侧的窗口里展开SYSTEM分区，双击EFI文件夹，然后把刚刚解压出来的refind文件夹拖到右侧的窗口里，把文件夹复制到分区里。\n然后点击上方的工具选项卡，点击设置UEFI BIOS启动项，在弹出来的窗口中点击添加，然后在新窗口中分别打开EFI，refind文件夹，双击refind_x64.efi，软件会提示“设置完成”，列表中会多一个名叫“New Boot Entry”的启动项。把这个启动项改名为“rEFInd”，点击保存当前启动项设置，然后再把它移动到第一位。\n到这里，rEFInd的安装就安装完毕了。此时重启电脑，应该可以看到rEFInd的启动界面。\n2.配置rEFInd rEFInd的默认主题不是很好看，启动项也可能会有一些缺失，因此我们还需要做一些配置来舒适使用。在rEFInd的文件目录下可以找到一个名叫redind.conf-sample的文件，这个就是配置文件模板。把这个文件复制一份，重命名为refind.conf，然后用文本编辑器打开。这个文件里，我们一般需要配置这几项：\n基础配置 1 2 3 4 5 6 7 8 # Timeout in seconds for the main menu screen. Setting the timeout to 0 # disables automatic booting (i.e., no timeout). Setting it to -1 causes # an immediate boot to the default OS *UNLESS* a keypress is in the buffer # when rEFInd launches, in which case that keypress is interpreted as a # shortcut key. If no matching shortcut is found, rEFInd displays its # menu with no timeout. # timeout 20 这一项配置等待时间，默认是20秒，建议改为5秒。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # Set the screen\u0026#39;s video resolution. Pass this option one of the following: # * two integer values, corresponding to the X and Y resolutions # * one integer value, corresponding to a GOP (UEFI) video mode # * the string \u0026#34;max\u0026#34;, which sets the maximum available resolution # Note that not all resolutions are supported. On UEFI systems, passing # an incorrect value results in a message being shown on the screen to # that effect, along with a list of supported modes. On EFI 1.x systems # (e.g., Macintoshes), setting an incorrect mode silently fails. On both # types of systems, setting an incorrect resolution results in the default # resolution being used. A resolution of 1024x768 usually works, but higher # values often don\u0026#39;t. # Default is \u0026#34;0 0\u0026#34; (use the system default resolution, usually 800x600). # #resolution 1024 768 #resolution 1440 900 #resolution 3 #resolution max 这一项设置分辨率，一般的格式为resolution 横边像素 竖边像素。建议直接设置为max，显示为支持的最大分辨率。配置时需要删掉resolution前的井号。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # Enable touch screen support. If active, this feature enables use of # touch screen controls (as on tablets). Note, however, that not all # tablets\u0026#39; EFIs provide the necessary underlying support, so this # feature may not work for you. If it does work, you should be able # to launch an OS or tool by touching it. In a submenu, touching # anywhere launches the currently-selection item; there is, at present, # no way to select a specific submenu item. This feature is mutually # exclusive with the enable_mouse feature. If both are uncommented, # the one read most recently takes precedence. # #enable_touch # Enable mouse support. If active, this feature enables use of the # computer\u0026#39;s mouse. Note, however, that not all computers\u0026#39; EFIs # provide the necessary underlying support, so this feature may not # work for you. If it does work, you should be able to launch an # OS or tool by clicking it with the mouse pointer. This feature # is mutually exclusive with the enable_touch feature. If both # are uncommented, the one read most recently takes precedence. # #enable_mouse 这两项分别配置触控支持和鼠标支持，建议开启，删掉末尾行前的井号即可。\n启动项配置 一般来说，rEFInd会自动搜索和添加启动项，如果没有对应的启动项，则需要我们自行配置。常用的配置参数一般如下：\n1 2 3 4 menuentry \u0026#34;启动项名称\u0026#34; { loader 启动文件目录 icon 图标目录 } 举个例子，如果要配置Windows启动项，那么配置参数应该写成这样：\n1 2 3 4 menuentry \u0026#34;Windows\u0026#34; { icon /EFI/refind/icons/os_win.png loader /EFI/Microsoft/Boot/bootmgfw.efi } 主题配置 rEFInd的默认界面确实有点不好看，不过我们可以自己安装主题来美化。这个页面下整理了很多rEFInd的主题，要使用的话直接下载整个仓库的文件就可以。在refind的目录下新建一个名叫themes的文件夹，然后把主题文件夹复制进去，再在配置文件末尾添加这样一行：\n1 include themes/主题文件夹的名称/theme.conf 这样就配置好了。\n隐藏多余的启动项 这一步需要在默认主题下进行。\n有些启动项可能会重复，在启动界面按delete键即可隐藏对应的启动项。\n到这里rEFInd的安装和基本配置就完毕了。rEFInd的玩法远远不止如此，想要了解更多的话，可以参阅官方文档。\n","date":"2024-04-29T14:55:00Z","permalink":"/p/reifnd-configuration/","title":"rEFInd 安装及简单配置指南"},{"content":"今天翻译课讲茅盾的翻译思想，里面提到了这样的一个例子，原文是这样的：\nWhat a piece of work is man! says the poet. Yes: but what a blunderer! Here is the highest miracle of organization yet attained by life, the most intensely alive thing that exists, the most conscious of all the organisms; and yet, how wretched are his brains!\n茅盾的译文是怎么样的，课上讲的已经记不清了，网络上也已经不可考，线下搜的时候知道这个选段是出自于萧伯纳的《人与超人》。单拿出来这段话其实是有点云里雾里的，现在就结合萧伯纳的思想和原文内容做一点翻译讲解。\n在这里我们需要先了解萧伯纳的“生命力”思想。萧伯纳认为，人是依靠“生命力”来生存和进化的，而这个生命力是主观的，需要靠自己来掌控。如果说得更为通俗一点就是，如果把整个人类的命运比作一艘船，那么这艘船并非是随波逐流的，而是依靠人类自己来掌舵寻找方向。而在这里，掌舵的人就是“超人”，他们是参透了生命的意义，有能力引领人类走向正确道路的人，有点类似于我们今天的精英主义。\n另一个是故事发生的背景，选段是在主人公的梦中（由于主人公的故事对于理解选段意义不大，因此在这里略过不讲），选自一个西班牙传说 《塞维拉的荡子与石像之客》 ，在这个传说中，主人公唐璜是一个浪荡的花花公子，他到处追求女性，最后同安娜恋爱，发生暧昧关系，安娜的父亲对此愤愤不平，他要与唐璜决斗，最后被唐璜击败。安娜为父亲立雕像而警示自己的愚蠢行为，但唐璜毫无悔恨之意，甚至为了展现胆量邀请雕像共进晚餐。午夜时分，雕像应约而至，唐璜反而大为惊恐。雕像要求唐璜改过自新，但唐璜拒绝，最后唐璜被拉入地狱。选段即来自在地狱，唐璜，安娜，雕像和魔鬼路西法的对话。\n在萧伯纳的笔下，天堂的人们各司其职，整日冥想，过着平和的生活，但是地狱却不是如此，人们的意识游离，居无定所，也没有余地去进行思考。安娜想要前往天堂，魔鬼嗤之以鼻，认为人类是有自己的意识，但是他们崇尚的不是生而是死。比起对于生的渴望，人类更喜欢用死说话，以此来展现自己的“生命力”。在魔鬼看来，人类发明纺织机这些便利的机器只是展现了他们的懒惰，而剩余的部分都被他们拿来用作战争和决斗。所以最好的方法还是顺其自然，在地狱什么都不用想最好。但唐璜是坚决反对的，他认为魔鬼的话不假，但魔鬼所说的随波逐流对人类是很不适用的，人类和动物不同的是人类拥有理智，这证明他们拥有着更高的追求。人向死而生不错，但向死而生也就意味着他们拥有理想，理想会指引人类走上正确的道路，而不是漫无目的地遵从简单的自然规律。不难看出，唐璜的这番话其实就是萧伯纳本人思想的体现，也就是说人类的生命力是由自己掌控的。\n说到这里，再看回原文，选段的地方来自于雕像的发言，雕像认为人类是精巧不错，但是头脑却不怎么够用，不愿面对现实，魔鬼接过话茬，批评人类总是崇尚死而不是生。结合上面的背景，想要进行理解这段话就不难了。\n贴一个我自己的翻译，参考了一个译本：\n诗人称赞道，人类是多么灵巧！是啊，可人类又是多么的浮躁！人类是生命的组织中最精妙的存在，是最生机勃勃的存在，生物中最有意识的存在，可他们的脑子却又是多么的差劲！\n","date":"2023-11-09T21:23:00Z","permalink":"/p/thoughts-in-translation/","title":"翻译思考：萧伯纳《人与超人》选段"},{"content":"一、Git是什么？ 1.Git能解决的事情 Git是版本管理系统。它具有以下功能：\n更方便地记录文件状态。如果使用手动备份，如果某个时间的备份丢失了，那么就没法返回到这个时间点对应的状态了，并且手动备份管理起来也很麻烦。如果使用Git，不仅只需要一个工作文件夹，也可以很方便地返回各个修改点的文件状态。\n可以多人协作修改文件。一般情况下，多人协作修改文件时，只能口头询问然后手动修改，使用Git，可以自动汇总多人的修改。\n能记录修改的原因。手动修改文件时，只能询问修改人，而Git可以进行记录，需要的时候查一下历史记录就可以了。\n2.作为交流场所的Github和Bitbucket Github和Bitbucket是常用的代码托管服务。除了托管服务之外，他们还提供互动交流的功能。这个教程中，我们重点介绍Github。你可以点击这里来访问Github。\n二、一个人独自使用Git 1.安装可以简单使用Git的工具 在本教程中，我们用SourceTree来进行教学。相比较直接使用命令行进行操作，SourceTree的图形化界面更方便新手的操作。你可以点击这里下载。\n安装教程什么的就不需要说明了吧？;)\n2.创建仓库 仓库，顾名思义，就是存放各种东西的地方，它可以保存过去文件的各种状态。我们接下来的各种操作，都要在仓库之间操作。我们先从创建一个本地仓库开始。\n先在本地创建一个文件夹，我们这里把它命名为“sample”。接下来我们把这个文件夹初始化为仓库，打开SourceTree，点击菜单栏下方的“Create”，点击“浏览”，选择刚刚的文件夹。然后，在第二行输入仓库的名字，点击“创建”按钮。如果提示路径重复，则点击“是”进行覆盖。这样，我们就创建好了一个空仓库。\n如何确认仓库是否已经初始化了呢？我们先在Windows打开“显示隐藏文件”这个选项，然后查看刚刚的文件夹，目录下是否有一个叫 .git的文件夹。如果有的话，那么就证明已经创建好了。在 .git这个文件夹里，存储着过去的文件与目录的状态。\n3.提交 我们先想象一个准备什锦烧原料的过程，在这个过程中，用相机记录下来各个过程。在上面的过程可以形容如下：\n（1）把原料放在碗里，按照比例混合好；\n（2）把装着混合好的原料的碗放上摄影台；\n（3）拍摄此刻原料的状态。\n在这个过程中，我们一共经历了三个状态：混合原料（操作），放上摄影台（暂存），拍摄照片（提交）。而Git中提交代码的操作就是这样的，先对源代码进行操作，将操作存入暂存区后再进行提交。\nSourceTree可以察觉仓库内的任何变化，点击左侧“WORKSPACE”下的History按钮，我们可以很方便地查看仓库中的操作记录。\n在SourceTree中，上述操作过程是这样完成的：\n编辑好代码之后，先点击“暂存所有”，将文件保存到暂存区。暂存好的文件会转移到上方“已暂存文件”一栏中。\n暂存完毕后点击左上角的“提交”，然后在界面最下方的文字框内输入对这次提交内容的说明，编辑好后点击右下角的“提交”按钮，等待提交完成即可。\n提交内容的说明是很重要的，因为Git可以记录提交的时间，提交者和内容，但并不能自动记录提交的原因，因此这部分必须要认真如实填写，方便后续的维护。\n每一次的“提交”操作都相当于是创建了一个版本，也就是添加了一条版本管理的历史记录。像刚刚这样重复操作，我们就可以记录下操作的历史。\n想撤回某些文件的暂存时，只需要按住Ctrl键，在“已暂存文件”一栏下选择需要取消的文件，然后点击“取消选定暂存”即可。如果用刚刚什锦烧的例子说明，取消暂存就相当于是把原料碗从摄影台拿下来，等原料调好了再放上去拍照。\n对于想要分开提交多个文件，或者没有提交时想要有所变更的情况下，都需要用到暂存区域。\n4.用checkout移动提交 继续刚刚什锦烧的例子。我们做什锦烧需要的原料有：什锦烧粉，鸡蛋，卷心菜等等。现在我们按照以下的顺序，把每个放入原料的过程拍了照：\n（1）放入什锦烧粉，水和鸡蛋；\n（2）放入卷心菜；\n（3）放入可乐。\n等等……可乐好像放错了。不过没关系，我们已经给每个状态拍了一张照，我们只需要找到放入可乐之前的状态，按照这个状态回溯到对应的时间点就可以了。\n在Git中，利用 checkout可以将文件更改为某个指定的版本。就像刚刚拍照片一样，找到对应的文件版本，我们就可以进行操作了。\n在SourceTree中的操作如下：\n在左端的“History”选项卡，找到需要回溯的提交记录，双击后在弹出的确认对话框中点击“确定”即可。\n就算是移动到了过去的提交，最新的提交记录也还是存在的，使用的方法类似。\n你可能注意到了，每一个提交都有一个对应的字符串，比如 b57e271430990131a7c0dae4c7e14aaee4b804c0。这是根据每一次提交生成的识别码，每一个提交都有一个独一无二的识别码。通过这串识别码，我们就可以辨别出来是哪个提交了。\n为什么不用001，002这样的数字来作为识别码呢？如果在同一时刻两个人提交了操作，那么就没法分辨是谁修改的了。使用这样的40位识别码，就可以有效避免这个问题。\n三、多人协作使用Git Git的一大优势就是在于它是分散式的分布管理系统。集中式的分布管理系统由操作者共同使用一个仓库，使用的时候必须联网。而分散式的分布管理系统中，对于一个仓库，每位开发者都可以在本地保存仓库内容，在适当的时刻提交到中央仓库就可以了。\n分散式管理系统有以下优点：\n就算是离线状态，也可以正常工作\n可以在不影响主仓库的条件下，自己修改代码调试\n紧急情况下，可以很快从别人的仓库进行复原\n因此，Git更适合多人协作使用，在这一节中我们做较为详细的介绍。\n1.创建Github账号 我们前面已经提到过，Github是常用的托管服务。在Github中，可以免费创建仓库用于代码托管。\n打开GitHub，点击右上角的 Sign Up，按照指引进行注册。注册时需要准备一个可以使用的邮箱账号。\n最近Github强制开启了两步验证，可能还需要在手机上准备一个生成校验码的APP，这里推荐Microsoft Authenticator或Google Authenticator。\nGitHub在国内访问较慢，如果上网姿势正确的话对访问会有帮助。\n2.复制仓库 为了方便使用，我们把仓库先进行复制（Fork），复制到自己的账号内，然后再进行克隆（Clone） 操作，将仓库下载到本地。\n复制的步骤 打开一个仓库（在这本书中以这个仓库作为例子），点开后点击右上角的“Fork”，然后在新页面中点击“Create Fork”，等待几秒钟后便可复制完成。如果你成功复制了仓库，那么仓库名最开始的部分会变成你的Github用户名。\nSourceTree中，克隆的步骤 先在仓库页面，点击“Clone or download”，复制显示出来的网址。然后回到SourceTree，点击文件-克隆/新建，在“源路径/URL”一栏中填入刚刚得到的网址，最后点击“克隆”即可。\n3.创建分支 在Git的官方解释中，分支的本质是指向提交对象的可变指针。\n为了方便理解，对于分支做一些形象的说明。假设我们现在要做一份寿司，那我们可以把这个过程分成两条线，一条是准备寿司用的米饭，一条是准备做寿司用的生鱼片，准备两种原料的过程是互不冲突的，这就是分支的理念。通过分支，可以很方便地进行并行工作，同时也不会相互干扰。\n那什么是指针呢？指针，简单而言，就是指向“当前”。比如在准备米饭的分支里，是这样的结构：\n开始——禾苗——稻穗——米饭\n那么指针就指向的是当前这个分支下最新的节点。在上面的例子中，指针指向的是“米饭”。\n确认目前所在的分支 在SourceTree中，目前所在的分支，会在左侧的“分支”选项卡下以黑色的粗体标出。\n在Github中，创建仓库时会自动创立一个名字叫 main的默认分支，就像是河流的干流。在早些创建的仓库上，默认分支名字是 master。\n分支的移动 利用 checkout命令，也可以进行分支的移动。比如，我们现在处于米分支，那么我们就可以通过 checkout，移动到鱼分支来对其进行操作。不仅如此，利用 checkout，还可以移动到分支下任意的一次提交。在SourceTree中，想要移动分支，只需要在左侧的“分支”选项卡下，双击需要移动到的分支即可。\n创立分支并提交 在SourceTree上的操作步骤如下：\n点击顶部菜单栏下方的“分支”，在“新分支”选项卡下输入分支的名字，点击“创建分支”按钮。\n创建好并修改代码后，进行暂存和提交操作，即可完成对对应分支下的提交。\n4.合并分支 我们已经创建了一个分支，当我们想把当前分支的更改合并（merge）到主分支里，又需要怎么做呢？\n还是以寿司的例子讲解，我们现在已经准备好了米饭和鱼肉，现在我们打算把两个合并起来，做成寿司。我们需要先移动到米分支上，再读取鱼分支进行合并。简单来说，合并分支的操作就是：\n1.切换到主分支；\n2.读取需要合并的分支，进行操作。\n在SourceTree中，合并分支的步骤如下：\n1.双击 main（或 master）分支，切换到主分支；\n2.右键需要合并的分支，点击“合并 xxx分支至当前分支（xxx是你需要合并的分支的名字）”，等待片刻后即可合并完毕。\n什么时候需要合并分支呢？比如说，bug修复的分支已经完成了自己的任务，那么就可以把bug修复后的代码合并到主分支上。添加新功能时，也可以这样合并到主分支上。\n冲突 在合并的时候有这样一种情况，当同一行代码在同一时间被不同的人修改时，Git这个时候就不知道应该合并哪个代码，这样的情况就叫“冲突”。\n在SourceTree中，如果遇到冲突，提交的时候会遇到这样的提示：\n解决冲突 对于出现冲突的文件，SourceTree会对应地用感叹号标出来：\n打开对应的文件，可以看到Git已经标注出来了对应的冲突点：\n修改冲突的地方后，再次进行暂存和提交即可。\n修改冲突后进行提交，SourceTree会自动生成解决冲突的提交注释，当然也可以根据需要自己修改提交注释。\n5.推送 刚刚我们对分支的操作，都还在本地上，我们需要通过推送（push），来把代码推送到远程仓库。还记得在第二节我们复制了一个仓库吗？这个就是我们要推送代码过去的目标仓库。\n在SourceTree中，想要推送代码的话，点击上方的“推送”按钮，在需要推送的分支前勾选“是否推送”的复选框，点击“推送”按钮即可。\n推送过程中可能会弹出Github的登录窗口要求验证。\n6.拉取 多人协作中，往往会出现我们需要获取其他编辑者的更改的情况，这个时候就需要用到拉取（pull）。\n假设他人已经提交了代码，但是我们还没有同步，那么在SourceTree中的操作如下：\n点击最上方的“拉取”，在“要拉取的远端分支”一栏，确认拉取的分支是从主分支到主分支，核对好后点击“确定”，稍等片刻拉取就完毕了。检查提交记录，应该也可以看到对应的更改了。\n7.拉取请求与合并 假设我们已经修改好了代码，想要把更改上传到主仓库，这时就需要用到拉取请求（pull requests）。\n明明是我们推送代码，为什么要写成是“拉取请求”而不是“推送请求”呢？其实这个说法是从对方的角度来说的，从对方来看，我们的需求是需要主仓库从我们的仓库“拉取”代码，因此是“‘拉取’请求”。\n为什么我们不能直接把代码上传上去，还需要得到“允许”才可以呢？如果没有审核环节的话，上传的代码得不到检查，就很容易产生漏洞，或者是功能上的不匹配。而经过负责人的检查后，代码确保没有问题才会被合并，这样更不容易出问题。\n简单来说，拉取请求与合并的步骤如下：\n复刻-\u0026gt;克隆-\u0026gt;创建自己进行操作的分支，提交-\u0026gt;推送-\u0026gt;Github上生成拉取请求-\u0026gt;被合并。\n在SourceTree中的操作步骤如下：\n1.按照我们之前的步骤，复制仓库，创建分支，编辑文件，然后进行提交和推送。\n2.打开浏览器，来到我们复制的仓库主页。点击仓库名下的“branches”，弹出的页面会显示目前仓库下的所有分支。\n3.在“Active branches”下找到想要进行拉取请求的分支，点击右边的“New pull requests”，确认需要合并的分支正确后，填写说明点击“Create pull request”提交请求。\n这是提交拉取请求的步骤。如果是仓库管理者，需要进入“Pull request”选项卡查看拉取请求。确认无误后，点击“Merge pull request”即可完成合并。\n当然，提交的请求也可能会被驳回。这个时候只需要在同一个分支上，按照注释修改代码，再提交和推送，就可以简单地更新拉取请求的内容了。\n8.忽略无须进行版本管理的文件 在Git仓库中，有些文件是不需要进行版本管理的，比如系统自动生成的文件，缓存或者容量过大的文件等。在Git中，可以通过在仓库内建立名叫 .gitignore的文件，指定不需要版本管理的文件。\n.gitignore文件的书写规则如下：\n备注：在这一行的开头加 #，例如：\n1 # 这是一条备注，它大概有这么长 忽略特定文件，输入对应的文件名即可：\n1 2 # 忽略名叫filename.txt的文件 filename.txt 忽略特定扩展名的文件：\n1 2 # 忽略扩展名为txt的文件 *.txt 不忽略某个文件：\n1 2 # 不忽略filename.txt !filename.txt 忽略特定目录：\n1 2 # 忽略folder文件夹下的所有文件，对于仓库内所有叫folder的文件夹都有效 folder/ 忽略根目录下的子目录内的文件：\n1 2 # 忽略根目录的folder子目录下的所有文件，仅对根目录符合要求的文件夹有效 /folder/ 更多的 .gitignore编写规则，可参考这个网站。\n在SourceTree中，向 .gitignore写入忽略规则的步骤如下：\n（1）点击右上角的设置，在“仓库设置”对话框中点击“高级”选项卡，再点击“仓库指定忽略列表”中的“编辑”选项卡；\n（2）弹出编辑界面，按照上述规则按需配置；\n（3）保存，暂存并提交。\n如果想要忽略已经提交的文件，在SourceTree中需要以下操作：\n（1）在“文件状态”选项卡中，点击右侧上方的下拉选项卡，选择“所有文件”；\n（2）在“未暂存文件”一栏中，选择想要停止追踪的文件，右键点击“停止追踪”；\n（3）提交此次更改，随后向 .gitignore中写入忽略信息，再次提交。\n9.如何高效使用分支 我们可以通过分支创建不同的工作流程，而正因如此，如何高效使用分支成为了一个课题。在这里介绍一种推荐的使用规则——GitHub Flow。你可以点击这里来了解相关的信息。\n对于GitHub Flow，它的分支模型可以分为两部分：\n（1）main分支：此分支上的代码需要保持随时可发布的状态，原则上不在此进行操作；\n（2）topic分支：用于对项目的开发，比如增加新功能，修复程序错误等。开发完毕后，再向main分支发送拉取请求进行合并。topic分支的名字最好直观易懂。\nGitHub Flow的使用规则如下：\n（1）main分支必须任何时刻都可以公开发布；\n（2）需要进行新功能开发时，需要新建分支，分支名字可以直接反映新功能；\n（3）定期推送分支；\n（4）如果想要获得反馈建议，或者已经开发完毕需要合并到主分支，就发送拉取请求；\n（5）合并的代码必须经过审计后通过；\n（6）合并分支后，可以直接发布。\n遵守上述规则，只需要浏览分支就可以知道目前在进行什么开发了。而代码经过审计，也可以保证代码的质量和稳定性。\n四、实用Git~这些时候，该怎么办呢？ 1.回到过去，创建新分支，修改内容 依旧是设想一下做什锦烧的场景。我们已经现在有了三个时间点（提交）：\n（1）放入水，鸡蛋，什锦烧粉；\n（2）放入卷心菜；\n（3）放入鸡肉。\n如果我们想要另外做一份猪肉什锦烧，就需要回到放入卷心菜这一步，更改配料进行制作。用Git的语言表述这个过程，就是想要从过去的某一次提交开始，新建分支进行开发。这时候应该怎么操作呢？我们只需要先移动到需要对应时间点的提交，然后从这里新建分支即可。\n在SourceTree中的操作如下：\n1.左侧点击“History”选项卡，双击想要返回到的提交，在弹出的对话框选择确定；\n2.点击最上面的“分支”选项卡，在“新分支”选项卡下，填写新分支信息，创建分支；\n3.创建完毕。在此状态下提交，就可以实现需要的效果了。\n2.撤销过去的提交（revert） 假设我们现在有这样一条提交记录：\nA-B-C-D（D为最新提交）\n那么是否可以撤销一个过去时间的提交（比如提交B）呢？这时候就需要用到revert。revert撤销的原理并不是简单粗暴地删除这次提交，而是通过提交一次与这次提交完全相反的内容来实现撤销更改的效果。\n在SourceTree中，想要进行revert，只需要右键点击对应的提交，选择“回滚提交”即可。\n当变更的内容与指定的内容在同一行时会产生冲突，使用前面介绍的方法解决冲突即可。\n3.提交历史合并为一条直线（rebase） 本节使用的图片来自知乎，你可以点击前面的蓝色字体进行访问。图片版权归原作者所有。\n我们前面已经介绍了用merge合并分支。思考这样的分支：\n对于两条分支，如果用merge进行合并，那么合并后会变成这样的结构：\n可以看出来，使用merge合并，提交的历史记录依然保持在原先的分流状态。在这里我们介绍一种新的合并方式：rebase。如果使用rebase来进行合并，那么就会变成这样：\n容易看出，rebase把之前的提交记录重新整理为了一条直线。在一些情况下，利用rebase进行操作，可以使提交记录更为清晰易懂。\nrebase修改的只是提交记录。也就是说，对于同一个合并操作，使用两种方法合并后的代码是一致的。\n但是，使用rebase也要小心，因为rebase会把所有的历史提交重新生成新的提交，在这之中提交的id值也会改变。如果把远程仓库已有的分支进行rebase，那么如果有的使用者想要提交新的代码，就会遇到很大的麻烦（因为提交记录不匹配）。所以，在熟练掌握rebase前，尽量先使用merge进行合并操作。\n在Sourcetree中，想要进行rebase，只需要先双击移动要进行rebase的分支，然后在需要rebase的目标分支上，右键选择“将当前变更变基到xxx”即可（xxx是目标分支的分支名）。\n如果在rebase的过程中发生冲突，先点击上方菜单栏中的“操作”按钮，选择“解决冲突”。按照之前介绍的方法解决冲突后，再选择“继续变基”，即可完成rebase的操作。\n4.合并多个提交（squash） 当对一个地方修改很多次后，提交历史会变得比较繁琐，如果能把对这一个地方的提交合并在一起进行记录，就会变得简洁很多了。这个时候就可以用到squash。\n要注意的是，由于squash的操作会改变提交记录，所以必须确认需要合并的提交记录只在本地。合并已经在远程仓库的提交记录容易造成混乱。\nsquash有“压缩，挤压”的意思，这样想可以方便理解一些。\n在Sourcetree中的操作方法如下：\n1.先确认需要合并的多个提交记录，找到在这群提交记录之前离得最近的一个提交记录，右键选择“交互式变基commit id的子提交”（commit id是进行右键操作的对应提交的id）；\n2.在弹出窗口勾选想要进行合并的提交，点击“用此前的squash”按钮，然后点击“编辑信息”按钮，修改提交注释，全部完成后，点击“确定”进行提交，合并完成。\n如果编辑过程中出现混乱，可以点击“重置”按钮从头开始操作。\nsquash中解决冲突的操作与rebase相同，这里不再赘述。\n5.拉取是怎么实现的？ 获取远程仓库的最新状态（fetch） 我们前面已经介绍了通过拉取功能来同步本地仓库与远程仓库的提交，现在我们来详细解释一下这个过程。\n到目前为止，我们认为的拉取操作是这样的：\n远程分支-\u0026gt;本地分支\n但其实这个过程是这样的：\n远程分支-\u0026gt;远程跟踪分支-\u0026gt;本地分支\n我们注意到，中间其实是有一个叫做“远程本地分支”的本地仓库充当“中介”的角色。远程跟踪分支是远程分支在本地的镜像，它是处于只读状态的。\n我们把“远程分支-\u0026gt;远程跟踪分支”的这个过程叫做fetch。由此我们可以得出，拉取（pull）的实现，其实是同步（fetch）与合并（merge）两个过程的结合。\n如果再详细介绍一下上述过程，那么就应该是这样的：\n在一般情况下，三个仓库的进度是相同的。现在，远程仓库多出了一个提交记录，那么Git会先把这个提交记录同步到远程跟踪分支上，也就是fetch。这个时候，远程的提交记录暂时还没有合并到我们的本地仓库上，我们目前只是知道了远程仓库多出来了一条这样的提交。如果我们确认要把这个提交合并到本地仓库，那么再使用merge。如果是拉取的话，那么两个过程就是先后同时进行的。\n简单来说，拉取的命令并不是简单地把提交直接同步到本地仓库上，而是先把提交同步到一个跟踪远程分支的本地仓库里，再把提交同步到我们本地的工作仓库上。如果我们只是想要查看远程代码的开发进度，而暂时不想进行合并，那么就可以使用fetch命令进行操作。\n想要在Sourcetree中使用fetch，只需要点击上方的“获取”按钮，再点击“确定”即可。如果想要进行合并，只需要在最新的提交上右键，点击“合并”即可。\n6.删除不需要的远程分支 对于已经合并的分支，可以直接删除，提高使用效率。\n一定要认真考虑整个团队的开发进度和运营方式后再决定是否要删除分支！\n在Sourcetree中，想要删除分支的步骤如下：\n（1）打开左侧菜单栏的“远程”选项卡，点开“origin”，在其下方找到需要删除的分支，点击删除“origin/分支名”，在弹出后的提示框确认，此时完成了远程跟踪分支下的分支删除。推送之后，远程仓库的分支也会被删除。\n（2）在左侧的“分支”选项卡下，进行同样的操作。此时完成对本地仓库的分支删除。\n如果其他成员在远程仓库删除了分支，我们也想删除对应的分支时，需要先点击“获取”，在新的对话框中取消勾选“删掉所有远端现已不存在的跟踪分支（tracking）“，然后点击确定，先将更改反映到远程跟踪分支上。此后再按照前面的介绍，删除本地的对应分支就可以了。\n如果直接右击“远端”选项卡下的“origin”，然后再点击“删除origin”，这样会直接删除掉整个远程仓库！除非你知道你在干什么，否则一定不要这样做！\n7.修改最近的提交注释（amend） 如果注释中出现错误已经提交，但是还没有推送到远程仓库的时候，可以进行修改。修改仅限于最近的一次提交。\n在Sourcetree中的修改方法如下：\n在编写注释信息的文本框上方，点击“提交选项”的下拉框，选择“修改最后一次提交”，弹出对话框确认，修改之后再提交即可。\n8.暂存未提交的内容（stash） 如果正在编辑某个内容，而此时又需要放下现在的工作去修复另外的问题，这个时候就可以用stash，将现在的工作内容暂存起来，等到之后再取出来继续工作。\n在Sourcetree中，想要进行stash，只需要先在提交记录中选择“未提交的更改”，在弹出的对话框中取好名字，确认即可。想要恢复的话，需要点击左侧的“贮藏”按钮，右键下方刚刚贮藏的内容，点击“应用贮藏区……”，确认即可。\n9.从其他分支上获取特定提交（cherry-pick） 如果在进行提交时搞错了分支，这个时候可以使用cherry-pick解决。它可以把任意的提交复制到当前分支。\n“cherry-pick”是一句英文俚语，意思是“只挑熟的樱桃摘取”。考虑一个这样的分支：\nmain: A-B-D-E\nvice: A-C\n利用cherry-pick，可以把主分支的提交E移动到vice分支上，也就是变成这样：\nvice: A-C-E\u0026rsquo;\n要特别指出的是，cherry-pick的作用只是“复制”而不是“移动”，原提交依然在对应分支上。\n在Sourcetree上，进行cherry-pick，只需要先移动到想要进行新增提交的分支，然后右键想要获取的提交，选择“遴选”，在弹出的确认框中确认即可。\n10.错误地把HEAD直接指向了某次提交 我们先来解释一下什么是HEAD。在Git中，HEAD是一个指针，它指向主分支的最新提交。但是有的时候，因为一些原因，我们可能会移动到了某次提交，这个时候HEAD就指向了某一次提交，HEAD就和主分支分离开了，这就是detached head。在这种情况下，我们可以进行提交，但是此时的提交是不属于任何分支的（因为这次的提交已经和主分支分离开了，并不会保存到主分支上），如果我们再次移动到主分支，这些提交就会丢失。\n对于detached head状态，想要解决也很简单，在刚刚的提交的基础上创建一个新分支就好了。当然，因为这种状态下，提交不会被保存，所以也可以在这种状态下进行一些实验性操作。\n11.在Github Pages上发布网页 在Github上，如果仓库放的是网页源文件，可以很方便地通过Github进行网页的发布，并且不需要服务器。操作的步骤如下：\n（1）打开需要发布的仓库的页面，点击上方的Settings；\n（2）在左侧选择Pages，随后Source选择Deploy from a branch，下方的Branch选择想要发布页面的分支，然后点击Save；\n（3）输入网址（https://username.github.io/repository，其中username是用户的Github用户名，repository是发布网站的仓库名字），即可访问。\n","date":"2023-10-22T22:11:00Z","permalink":"/p/study-git-with-wakaba/","title":"《跟 Wakaba 酱一起学 Git 使用》笔记"},{"content":"一、策划 1.明确网站制作的目的 制作网站并不是目的，而是达成目的的一种手段。\n如果用售卖巧克力来举例子，不仅可以通过制作网站介绍产品，也可通过在社交媒体宣传或投放广告的方式进行。有了目的再制作网站，效果就会好不少。\n2.明确目标用户 也就是说，此网站为谁设计，提供什么。\n可以从“6W1H”的角度进行思考。\n在思考时，要注意目标用户的描述不能过于模糊。比如对象是“20岁的女性”的描述就过于泛化，应该再多想一些具体的特征，比如说对方的兴趣爱好，烦恼，性格等等。\n3.制作架构图 目标用户访问网站是为了寻找特定的信息或者体验内容，但网站如果没有合理的分类就麻烦了。所以我们需要架构图，来为用户提供合适的切入点，改善使用体验。\n用超市来举例的话，如果我们通过商品类型进行规划，比如果蔬，肉类等，这样就更为直观，但是只用价格区间分类就没有那么直观了。\n制作架构图时，可以先想一下这个网站的人的需求，再制作对应的切入点。举例来说，目标用户想要知道某个电影的演员，那么就可以从演员阵容作为切入点。\n特别注意，必须是要从访问者的视角多加考虑，提出的需求也必须具有代表性。\n其次还要确定顺序，主要是区分信息的优先级。\n4.制定计划 使用WBS（工作分解结构）来制定，可以使开发计划更加明朗。\n明确任务时并不能想到什么写什么，而是按照从上到下的顺序依次列举下来，以减少遗漏。\nGantter（甘特图）是很好用的制作工具，可以利用Google Drive将其关联使用。\n二、设计 1.先画线框图 其实也就是确定好网站的大体布局。线框图就是把网站的页面布局画下来的图，不考虑配色，装饰等UI设计问题，只是确定把什么元素放在什么位置上。\n如果一开始就考虑视觉化的设计，那么很有可能会干扰想让用户真正看到的功能。所以，在制定线框图的时候，要以专注于网站目的为目标进行。\n利用Mockplus，Sketch，Axure等工具，可以很方便地制作线框图。\n2.用图片编辑软件进行设计 设计的原则：简单易用，风格恰当。\n让网站简单易用的方法 （1）对元素进行适当分组，把相同的元素排布得更紧凑一些、，不同的元素间距放大一些；\n（2）明确父子结构，建立清晰的逻辑关系；\n（3）尊重主流的习惯，比如把返回键放在左边，前进键放在右边；\n（4）设置导航栏，提供清晰的网页定位。\n如何展现风格呢？ 用饮料举例子来解释一下什么是风格。碳酸饮料的包装让人感到清爽，橙汁包装盒能让人感到充满汁水的水果，如果全部用相同的透明饮料瓶装着，就没有想让人喝一口或者购买的欲望了。不同的饮料包装让消费者产生对应的感觉，这就可以认为是一种“风格”。\n基调与风格可以用以下两部确定：\n提炼商品的特征、列出想让用户拥有的印象。\n三、HTML 1.网页在互联网上可见的底层逻辑 大体可以概括为，设备先向网络寻找拥有所需数据的Web服务器，然后发送获取数据的请求，最后Web服务器再把对应的数据发回来，这样网页就显示出来了。\nWeb服务器可以在任何地方，也许某一台服务器就在身边。\n2.HTML是什么？ HTML，全名Hyper Text Markup Language，中文名超文本标记语言。在浏览器中右键点击“查看网页源代码”，就可以看到HTML的代码。\nHTML由 Tim Berners-Lee 和 Daniel W. Connolly 创立，起初是为了解决资料和人数太多难以寻找的问题。HTML可以便捷地整理信息，同时具有跳转功能，这样管理资料就方便很多了。\nHTML能做什么事呢？ （1）文章结构化。比如标题部分，我们可以用 \u0026lt;h1\u0026gt;\u0026lt;/h1\u0026gt;这样的标记区分，对于不同类型的内容做上标记，就可以把网页做出来了。\n但是，要注意标记要适当。HTML的目的就是向阅读文章的人和程序正确地传递文章内容。使用正确的标记，对于网页阅读和搜索引擎收录网页都很有好处。\n如果我们知道HTML最初的作用是整理机构论文的话，理解这里会相对容易一些。\n（2）超链接。直观地说就是，点击链接就可以在网页之间灵活地跳转。\n真的是很棒的功能！\n3.迎接HTML的准备工作 只需要利用记事本，就可以编写HTML代码，效果使用浏览器查看即可。\n为了方便编辑，可以选用一些专为编程设计的编辑器，比如Atom和Visual Studio Code，浏览器可以选用Chrome，Microsoft Edge和Firefox等。\n4.HTML的基本结构 以一个最简单的HTML文件举例子：\n1 2 3 4 5 6 7 8 9 10 \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;!--声明这是一个HTML文件--\u0026gt; \u0026lt;html lang=\u0026#34;zh\u0026#34;\u0026gt; \u0026lt;!--制定HTML元素和语言。所有的元素都在HTML元素之内--\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;utf-8\u0026#34;\u0026gt; \u0026lt;!--指定使用的字体编码--\u0026gt; \u0026lt;title\u0026gt;标题\u0026lt;/title\u0026gt; \u0026lt;!--显示在浏览器的标签或标题栏--\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;!--head元素，记录页面标题与相关联的外部文件--\u0026gt; \u0026lt;body\u0026gt; 文字和图片 \u0026lt;/body\u0026gt; \u0026lt;!--body元素，包括网页内容--\u0026gt; \u0026lt;/html\u0026gt; 在写HTML代码的时候，一定要注意使用正确的格式，养成换行和缩进的习惯。HTML的缩进使用两个空格比较合适。\n你也许注意到了，在上面的代码中使用了类似 \u0026lt;!--内容--\u0026gt;这样的代码格式。这样的代码被称为注释，它不会显示在网页中，主要用来代码的可读性，使代码易于维护。在制作的过程中，合理使用注释很有必要。\n在写注释的过程中，要特别注意连字符的个数。使用连字符过多，会使浏览器错误地判断注释代码的结束位置，导致显示效果异常。\n5.制作标题和段落 基本格式：\n标题：\u0026lt;h1\u0026gt;大标题\u0026lt;/h1\u0026gt;，一共有 \u0026lt;h1\u0026gt;到 \u0026lt;h6\u0026gt;六种层次的标题格式。\n段落：\u0026lt;p\u0026gt;文章内容\u0026lt;/p\u0026gt;\n换行：\u0026lt;br\u0026gt;\nh=heading（标题），p=paragraph（段落），br=line break（换行）。\n当效果不符合预期的时候\u0026hellip;\u0026hellip; 主要从是否错误使用了半角/全角符号，是否是保存后再打开网页检查效果的，浏览器打开的和修改的是否是同一个文件三个角度检查。\n一行代码中各部分的名称 请看以下代码：\n\u0026lt;h1 class=\u0026quot;ranking\u0026quot;\u0026gt;本周人气T恤\u0026lt;/h1\u0026gt;\n（1）标签：\u0026lt;\u0026gt;括起来的部分；\n（2）属性：为标签添加的附加信息，没有或者有很多个都可以。上面的 class就是属性；\n（3）属性值：上面的 \u0026quot;ranking\u0026quot;部分；\n（4）元素：标签和标签内的内容一起被称为元素。上面整行代码都是元素。\n特别地，没有开始和结束标签的被称为空元素，比如 \u0026lt;br\u0026gt;。\n段落中使用文字样式 在HTML中，可以使用以下标签，给文字增加显示效果：\n\u0026lt;strong\u0026gt;：加粗\n\u0026lt;b\u0026gt;： 程度弱一点的加粗\n\u0026lt;em\u0026gt;：斜体\n\u0026lt;i\u0026gt;：程度弱一点的斜体\n\u0026lt;sub\u0026gt;：下标\n\u0026lt;sup\u0026gt;：上标\n\u0026lt;del\u0026gt;：删除线\n\u0026lt;ins\u0026gt;：下划线\n6.用列表制作导航栏 使用ul（unordered list）元素，ol(ordered list)元素和li（list）元素。\n使用方法如下：\n1 2 3 4 \u0026lt;ul\u0026gt; \u0026lt;li\u0026gt;主页\u0026lt;/li\u0026gt; \u0026lt;li\u0026gt;关于\u0026lt;/li\u0026gt; \u0026lt;/ul\u0026gt; \u0026lt;ul\u0026gt;和 \u0026lt;ol\u0026gt;的区别在于，显示标题的时候是否会加上顺序。\n上面的代码，用ul元素是这样的效果：\n主页 关于 用ol元素则是这样的：\n主页 关于 而从上面的代码，我们能看出来，ul元素包着li元素，我们说两种元素之间包含着父子关系。从 \u0026lt;li\u0026gt;的角度看，\u0026lt;ul\u0026gt;是父元素，反过来以 \u0026lt;ul\u0026gt;的角度看，\u0026lt;li\u0026gt;是子元素。\n父子关系的思考方式在学习CSS时很有用。\n7.添加超链接 使用a（anchor）元素。 使用例如下：\n\u0026lt;a href=\u0026quot;index.html\u0026quot;\u0026gt;TOP\u0026lt;/a\u0026gt; \u0026lt;!--href后面表示网页的路径--\u0026gt;\n相对路径和绝对路径 相对路径以当前的位置为起点，说明目标文件的位置；绝对路径则是完整描述文件位置，不论在哪个网页都显示一致。\n举个例子，如果用两种方法描述一个人的住址，相对路径就是“这个公寓同一层的XXX”，绝对路径就是“中国XX省XX市XX区X街道X公寓的XXX”。\n使用相对路径，可以减小网站迁移时更改代码的工作量。\n8.插入图片 使用img（image）元素。 请看下面的代码：\n\u0026lt;img src=\u0026quot;图片地址\u0026quot; alt=\u0026quot;替代文本\u0026quot;\u0026gt;\nsrc属性指定图片的路径，相对绝对的都可以；alt属性则是指定在图片无法正常加载时显示的替代性文本。\n9.划分区域 划分区域对于后续的页面布局很有好处。一般来说，网页可以分为三个基本区域：header元素，main元素和footer元素。\nheader元素 主要负责定义网页的头部内容，具体包含网页的标题，商标，导航和搜索框。 使用例如下：\n\u0026lt;header\u0026gt;头部内容\u0026lt;/header\u0026gt;\n注意和head元素区分开哦，两个不一样的。\nmain元素 负责定义主内容的范围，一个页面只能使用一次。 使用例如下：\n\u0026lt;main\u0026gt;主内容\u0026lt;/main\u0026gt;\nfooter元素 顾名思义，就是定义页脚的部分。主要包括网站运营者的联系方式，版权声明和相关链接等。 使用例如下：\n\u0026lt;footer\u0026gt;页脚部分\u0026lt;/footer\u0026gt;\n除了这三个元素之外，还有一些其他的用来定义区域的元素：\nnav元素 主要定义导航栏的范围。 使用例如下：\n\u0026lt;nav\u0026gt;导航栏\u0026lt;/nav\u0026gt;\nsection元素 用来定义大纲，也就是文件的层次结构，类似于一本书的章节和段落。被section元素包围表示这个范围写的内容与这个标题相关。\n所以，section元素包围的部分必须得有标题。\narticle元素 用于定义独立内容范围。被article元素包围的内容表示其独立于页面其他部分，像是商品介绍，公司简介以及单个的博客文章都比较适合使用。\ndiv元素 用于内容的模块化，表示内容属于一个组。\n10.命名元素 命名了元素后，CSS和JavaScript就能通过名字指定元素来美化网页了。我们使用id元素和class元素进行标记。\nid元素在同一网页中只能使用一次，class元素可以多次使用。 如果只是用来调整外观，那么一般使用class元素就可以了。\n11.制作表格 使用table元素。先把想做的表格整体用 \u0026lt;table\u0026gt;\u0026lt;/table\u0026gt;包围起来，然后把每一行用\u0026quot;\u0026lt;tr\u0026gt;包围起来，最后把单元格的部分用 \u0026lt;th\u0026gt;或 \u0026lt;td\u0026gt;一个个围起来。\n从单元格的内容来看，标题使用 \u0026lt;th\u0026gt;元素进行标记，数据使用 \u0026lt;td\u0026gt;进行标记。表格的排列方式为横向并列。\n另外需要注意的是，使用 \u0026lt;table\u0026gt;时需要设置一下border属性，比如：\n1 \u0026lt;table border=\u0026#34;1\u0026#34;\u0026gt; 12.表单的制作方法 表单的自动发送功能仅靠HTML和CSS是无法实现的。在这里我们先介绍如何使用HTML写简单的表单框架。 表单使用form元素进行标记。\n而在表单中，主要使用以下几种元素：\nname属性。 使用方法为 name=\u0026quot;example\u0026quot;。\naction属性。 指定数据发送地址的URL。使用方法为 action=\u0026quot;example.html\u0026quot;。\nmethod属性。 指定数据传送方式，使用方法与上面两种类似。分为get与post两种传输方式。\n两种传输方式的区别请见下表。\n| 属性值 | 功能 |\n| :\u0026mdash;-: | :\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;: |\n| get | 把数据加载URL的后面进行传输，可以被第三方看到 |\n| post | 传输数据不在浏览器上呈现，适用于传输邮箱，密码等保密信息 |\ninput属性。 用于单行输入。input元素中主要使用以下几种属性：\ntype属性\n指定输入数据的类型，使用方法参照name属性的指定方法。属性值主要有以下几种：\n| 属性值 | 输入形式的类型 |\n| :\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash;: | :\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash;: |\n| text | 单行文本输入框 |\n| password | 密码输入框，输入的信息会自动变为圆点显示 |\n| checkbox | 复选框 |\n| radio | 单选框 |\n| submit | 发送按钮 |\n| hidden | 隐藏的输入框 |\n| email tel url | 电子邮箱，电话与网址。具有校验功能，如果输入格式不正确会进行提示 |\nvalue属性\n对于不同的输入类型，value属性所指内容也不同。主要有以下几种：\n| 输入类型 | 属性值所指内容 |\n| :\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;-: | :\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash;: |\n| 文本输入框 | 输入框内的初始值 |\n| 复选框 单选按钮 | 传输给服务器的值 |\n| 按钮 | 按钮上的文字 |\nname属性\n定义表单中每个输入框的名字，便于服务器正确匹配数据。\nplaceholder属性\n指定文本框内的提示文字。\nchecked属性\n此属性不具有属性值，用来预先选定复选框或单选按钮。\nrequired属性\n定义必填项。若未完成必填项就点击发送键，数据不会被发送，并且网页会有相应提示。无属性值。\ntextarea元素。 用于多行输入。主要使用以下几种属性：\ncols属性\n规定一行的文字数。\nrows属性\n规定文本输入框的行数。文本框高度也会随着规定的行数发生改变。\nrequired属性\n无属性值，定义必填项。\n当用HTML设计网页时，我们需要思考文章的正确结构。由此就引出了类别与内容模型的概念。\n现在举一个例子，水的类别是“液体”，水杯的类别是“容器”，内容模型是“液体”。我们很自然地可以想到，一般情况下都是杯子里盛水，而不是杯子浸泡在水里。\n把这个关系用HTML表示出来就是这样：\n1 2 3 \u0026lt;水杯\u0026gt; \u0026lt;水\u0026gt;放在杯子里的水\u0026lt;/水\u0026gt; \u0026lt;/水杯\u0026gt; 把这种情况推广到HTML的表示方法中，就可以直观地理解这两个概念。\n四、CSS 1.什么是CSS？ CSS=Cascading Style Sheets，即层叠样式表。如果说HTML是网页的基本结构，那么CSS的作用就是为网页锦上添花，使网页看起来更加生动。\n在最早的一开始，HTML同时担任着结构和美化的任务。但是这样导致结构和美化的部分杂糅，使源代码很难读懂。于是CSS应运而生。本文以CSS3标准进行介绍。\nCSS的语法 首先，使用CSS是不需要声明的，它也不一定非得和HTML5绑定使用。 使用CSS的格式如下：\n1 2 /* 选择器 {属性:值;} */ p{color:red;} 这个格式代表把选择器指定的元素的指定属性改为指定的值。上方的代码表示把元素p的字体颜色改为红色。\n你也许注意到了，上方的代码使用 /* */这样的格式来进行注释。这是CSS中写注释的方式，和在HTML5中不太一样。\n2.层叠 层叠就像是一层层连起来的瀑布，当一个元素上应用多种样式的时候，层叠可以安排样式的先后顺序，优化显示效果。\n举个例子，我们要实现一个白底黑字的效果，那么HTML和CSS都是这么写的：\n1 2 3 4 \u0026lt;!--HTML--\u0026gt; \u0026lt;div\u0026gt; \u0026lt;p\u0026gt;Cascading\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; 1 2 3 4 5 6 7 8 /* CSS */ \u0026lt;div\u0026gt; { background-color: black; /* 背景色 白色 */ color: white; /* 文字颜色 白色 */ } p { font-size: large; /* 字号 大 */ } 也许你会疑惑，p元素中只指定了文字的大小，颜色真的会变白色吗？CSS中存在一个叫样式继承的机制，子元素会继承父元素的样式，这样我们就没必要对元素一个个设置了。\n思考这样一个问题：如果在上述代码中，我们先指定p元素是白色，后指定p元素是黑色，结果会是什么样子？答案是黑色，因为CSS中，当选择器处于同一优先级的时候，后设置的样式优先。\n3.CSS的引入方法 一共有3种。分别是：\n行内样式：直接写入标签内\n内部样式：直接写入head元素中\n外部样式：CSS文件附在外面\n具体的样子如下：\n行内样式：\n1 2 \u0026lt;!--行内样式，使用style元素进行引入--\u0026gt; \u0026lt;p style type=\u0026#34;color: red\u0026#34;;\u0026gt; Example\u0026lt;/p\u0026gt; 这样子使用会导致网页源代码的结构混乱，请尽量避免使用这样的方法。\n内部样式：\n1 2 3 4 5 6 7 8 9 10 11 12 \u0026lt;!--内部样式--\u0026gt; \u0026lt;head\u0026gt; ······ \u0026lt;style type=\u0026#34;text/css\u0026#34;\u0026gt; p { color: red; } \u0026lt;/style\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p\u0026gt;Example\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; 相较于第一种方案，这一种在一定程度上实现了HTML和CSS的分离，但是如果使用的样式过多，依然会造成可维护性的下降。\n外部样式：\nHTML 文件：\n1 2 3 4 5 6 7 8 \u0026lt;!--外部样式--\u0026gt; \u0026lt;head\u0026gt; ······ \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;css/common.css\u0026#34;\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p\u0026gt;Example\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; CSS文件：\n1 2 @charset \u0026#34;UTF-8\u0026#34;; p {color: red;} 在上面的例子中，我们先额外创立了一个CSS文件，写入了我们需要的样式。在额外创立CSS文件时，一定要注意先使用 @charset来指定编码方式。随后，我们在HTML文件中引用了这个CSS文件。其中，rel=\u0026quot;stylesheet\u0026quot;指的是“附上于此处关联的样式表”，而 href=\u0026quot;css/common.css\u0026quot;指定了样式表的位置。\n为什么要尽量使用外部样式呢？思考这样一个问题：\n假设你现在有一个网页，然后这个网页有50个HTML文件组成。现在我们想改掉同一个地方的样式，那么你需要：\nA.行内/内部样式，修改50个HTML文件；\nB.外部样式，修改1个对应的CSS文件。\n这样就体现出来外部样式的好处了。当需要修改的时候，我们可以减少修改量，便于维护网页。\n4.尝试导入字体吧！ 一般情况下，浏览器只能显示进入用户终端的字体。而在线字体可以使用服务器上的字体文件，不受用户端的限制，显示相同的字体。\n本节中，我们使用谷歌字体。谷歌字体的种类很全，并且使用简单，而完全免费。\n（1）打开谷歌字体网站 （需要正确的上网姿势） ，在搜索框中需要你想要的字体。本节中我们以Montserrat字体为例。\n（2）点开字体页面后往下滑，选择 Select Regular 400，在右侧的 Use on the web选项卡下，点击 \u0026lt;link\u0026gt;选项卡，复制 \u0026lt;link href...\u0026gt;开头的代码，放到 head元素内。\n1 2 3 4 \u0026lt;head\u0026gt; \u0026lt;!--省略--\u0026gt; \u0026lt;link href=\u0026#34;https://fonts.googleapis.com/css2?family=Montserrat:wght@100\u0026amp;display=swap\u0026#34; rel=\u0026#34;stylesheet\u0026#34;\u0026gt; \u0026lt;/head\u0026gt; （3）在想要使用的元素中设定 font-family即可。\n1 2 3 4 5 6 7 header nav ul li a{ color: #FFF; font-family: \u0026#34;Montserrat\u0026#34;,sans-serif; /* 字体设置 */ /* 这句话表示，在这个元素中优先使用Montserrat字体，如果不能正常调用的话，就用sans-serif。 */ font-size: 16px; padding: 0 1em; } 5.改变文字的大小与颜色 请看以下的例子：\n1 2 3 4 .price { color: #C00; font-size: 2em; } 让我们来解释一下代码的含义：\n（1）控制字号的是 font-size属性，示例代码表示的是，将字号设置为原来的2倍。\n在CSS中，要接触很多和大小有关的单位。具体的单位可以参见下表：\n| 单位 | 说明 | 示例 |\n| :\u0026ndash;: | :\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash;: | :\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;-: |\n| px | 字体的显示像素，具体大小为以设定值像素长为边的正方形 | div {width: 100px; } /* 宽度为100像素 */ |\n| % | 相对于父元素初始值的百分比 | div {width: 80%; } /* 宽度为父元素的80% */ |\n| em | 相对于页面原本的字号，这部分的放大倍数 | 上部分的示例代码 |\n（2）color调整的是文字的颜色。填入后面的颜色可以使用以下的两种表示方法：\n16进制数，比如 #1f1e33。\n10进制数，比如 rgb(114,51,4)。\n在这里简单解释一下RGB颜色的表示法。显示器组成的颜色，是由红色，蓝色和绿色三原色组成的。因此我们可以通过指定三种颜色的浓度，来混合出不同的颜色。\n对于三种颜色的浓度，取值范围在[0,255]，0到255从浅到深变化。十进制就是直接把三个值表示出来，十六进制就是先把每个值换算为十六进制的形式，再合并在一起表示。\n6.选择器的特指度 CSS中还有优先级关系？ 我们在前面说到，CSS中是有优先级关系的。在这一节中，我们对这里进行更为深入的解释。\n顾名思义，优先级就是面对不同样式时，优先使用高优先级的样式。接下来一一介绍各个选择器，为了方便描述，从有高到低的优先级介绍。\n（1）!important——强势国王！ !important就和国王一样，拥有最高的优先级。但也正因如此，请尽量避免过多使用此选择器，以免在覆盖样式的时候出现大麻烦。\n使用的一个例子如下：\n1 2 .price {color: red !important;} .price {color: blue;} 如果没有 !important，按照“先来后到”的原则，应该是蓝色的样式先使用。但是加了这个选择器后就发生了变化，红色的样式优先级最高，会被最优先地使用。\n可能有的同学不能理解，既然 !important下的样式可以被无条件使用，那么岂不是非常方便，为什么不能过多使用呢？\n设想一下，如果一个王国内有好几个国王，那么国王之间同时下达指令，肯定会造成冲突和混乱。而且，能覆盖国王意见的也只有国王，如果我们想要这么做，只有另外造出来一个国王了，而这样子也肯定会产生大麻烦的。\n（2）内联（行内）样式——内务大臣 内联样式就是直接把样式设置写入HTML的 style属性中的一种方式。我们在前面已经介绍过了，为了方便修改，最好不要使用这种样式。\n以下是内联样式的一个例子：\n1 \u0026lt;span style=\u0026#34;color: red;\u0026#34;\u0026gt;2,980\u0026lt;/span\u0026gt;日元（含税） （3）id选择器——孤傲公主 人（?）如其名，id选择器就和自尊心很强的公主一样，不允许城堡内再有同名的公主。也就是说，在一个页面内同名的id属性仅可使用一次。\nid选择器的标记是 #，在 #后标注目标元素的id属性。比如这样：\n1 #sale {color:blue;} 在这里使用id选择器更改外观只是为了演示使用，实际使用应该尽量避免这种用法，避免样式覆盖带来麻烦。\n（4）类选择器、属性选择器，伪类——三类将军 类选择器是通过 class属性来选择元素的选择器。类选择题的标记是 .，在这之后加需要修改的元素的 class属性值。比如这样：\n1 .price {color: red;} 属性选择器是根据属性，以及对应属性中是否有特定内容来进行样式的修改。比如：\n1 2 /* href的属性值中有\u0026#34;Web\u0026#34;元素的文字颜色设置为红色 */ a[href*=\u0026#34;web\u0026#34;]{ color: red;} 伪类用来设定某个元素在特定条件下时的样式规则，比如在链接被点击前后时设置不同的颜色。使用例子如下：\n1 2 a: hover { color: red;} /* 伪类的设定需要一个冒号 */ /* 光标放在链接上时，文字变为红色 */ 伪类名字中虽然有“类”，但是和类选择器没关系哦。\n（5）元素选择器、伪元素——二类士兵 元素选择题就是通过元素的种类来设定样式规则的一种选择器。比如：\n1 span { color:red; } 伪元素用来修改HTML中没有被标记的内容的样式设置，比如改变某个元素第一个字的颜色。使用例子如下：\n1 2 3 p::after { /* 伪元素的设定需要两个冒号 */ content:\u0026#34;example\u0026#34;; } /* 在p元素对应的内容后添加字样 */ （6）通配选择器——万能女仆 通配选择器虽然优先级最低，但是可以影响整个页面的全部元素。*是使用通配选择器的标志。比如：\n1 2 3 4 * { /* 重置默认留白 */ margain: 0; padding: 0; } 由于每个浏览器都有一套默认的CSS样式规则，而且这些样式规则的属性值不完全相同，因此可以通过使用通配选择器，来将页面外观统一重置，这样做也被称为CSS重置。\n不过，这样做虽然很方便，但是也有以下缺点：\n默认样式规则的好处没有了\n被重置的部分还需要重新写入，时间成本增加，CSS也会臃肿\n所以，现在大家更多的还是“只重置需要的元素”，而不是一锅端。\n强度数值化 关于选择器的强度（优先级），也可以通过特指度来判断。我们前面用拟人化的形象来比喻各种选择器，在这里同样可以使用。\n思考一下，如果有以下两个选择器：\n#id p\np.class::after\n哪一个选择器更强？\n让我们来具体分析一下。如果用前面的知识表示，那么 #id p就是一位公主加一位士兵，而 p.class::after就是一位将军加一位士兵，而公主比士兵有更高的权力（也就是优先级），所以 #id p的优先级更高。\n从上面的例子我们可以看出，通过特指度判断优先级的时候，按照“大臣，公主，将军，士兵”的顺序依次判断，先判断这一阶层的人数，人数多的直接胜出，如果这一阶层人数相同，则顺延到下一阶层进行判断，直至分出胜负为止。!important是最高优先级的（国王），不需要比较就可以无条件胜出。\n如果我们按照上面介绍的顺序，把每个阶层的人数标出来，那么上述两个选择器的强度可以表示为 0.1.0.1与 0.0.1.2。这就是特指度。同样地，!important没有优先级，可以无条件胜出。\n是不是已经晕了？没关系，你可以点击这里，用这个网址计算特指度。\n7.内边距与外边距的区别 在网页制作中，会有“在一定元素间设置空白”的需要，这时候就要用到 padding和 margin。两个都可以设置空白，但是两个的含义并不一样。前者是边内侧的空白（内边距），后者是边外侧的空白（外边距）。\n是不是感觉理解起来有点费劲？我们用一个图表示一下：\n就像我们看到的，网页中的图像都是被这三层包围着，从内到外分别是 padding，border（我图里面拼错了，请自动忽视），margin。这三者总称为CSS的框模型（Box Model）。从观感来说，padding元素设置后画面会“变胖”，而 margin是扩大了“禁止入内的范围”。\npadding和 margin的设置方法有很多种：\n上下左右统一设置 直接指定两个元素的值即可，单位可以参考这一章第5节的介绍。值可以单一设置，也可以同时设置多个值，规则如下：\n| 代码 | 含义 |\n| :\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash;: | :\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash;: |\n| padding: 10px; | 上下左右各10px |\n| padding: 10px 20px; | 上下各10px，左右各20px |\n| padding: 10px 20px 30px; | 上10px，左右各20px，下30px |\n| padding: 10px 20px 30px 40px; | 从上面开始顺时针设置，上10px，右20px，下30px，左40px |\n上下左右分开设置 格式是 padding-xxx，xxx处填入你想要设置的地方的值。上方是 top，下方是 bottom，左侧是 left，右侧是 right。比如：\n1 padding-left: 10px; /* 设置左侧内边距为10像素 */ margin的作用是扩大禁止进入的范围，但是在元素纵向排列时，相邻部分的 margin会重叠。\n举个例子，现在有两个纵向排列的元素，两个的 margin值都是60px。如果正常计算，当他们重叠后，间隔应该是120px，但实际上，重叠后他们的间隔依然是60px。\n如果在实际开发的过程中搞不懂哪里是 margin，可以使用浏览器的开发者工具来确定。\n8.用浮动把元素围起来 使用 float属性，可以让元素自动地向左或者向右靠拢，这时后面的元素将会按照顺序围上来。\nfloat属性一共可以设置三个值：left，right与 none。效果分别是向左浮动，向右浮动，无浮动。\n我们用数字大概表示一下效果：\nleft： 1 2 3 4 5\n6 7 8\nright： 5 4 3 2 1\nㅤ 8 7 6\nnone： 1\n2\n3\n\u0026hellip;\n如何解除浮动？ 如果不结束浮动的话，那么后面的元素就会一个接一个跟上来。对于这种情况，我们使用 clear属性，在对应的地方结束浮动。先给需要结束的地方赋予 clear属性，再在css中进行选择，设定 clear的值。比如这样：\nHTML文件：\n1 2 3 4 5 6 7 8 9 \u0026lt;div class=\u0026#34;box float\u0026#34;\u0026gt;1\u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;box float\u0026#34;\u0026gt;2\u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;box float\u0026#34;\u0026gt;3\u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;box float\u0026#34;\u0026gt;4\u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;box float\u0026#34;\u0026gt;5\u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;box float\u0026#34;\u0026gt;6\u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;box float\u0026#34;\u0026gt;7\u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;box float\u0026#34;\u0026gt;8\u0026lt;/div\u0026gt; \u0026lt;p class=\u0026#34;clear\u0026#34;\u0026gt;float练习中, 我想解除包围状态！\u0026lt;/p\u0026gt; \u0026lt;!--在这里设置clear属性--\u0026gt; CSS文件：\n1 2 3 .clear{ clear:both; } /* 选择clear属性，并设定对应的值 */ 这样，从文字处我们就可以断开浮动了。\n9.适配智能手机的方法 由于智能手机和电脑的布局很不一样，因此我们有必要为手机做一些适配，让访问者得到更友好的体验。为此，“响应式Web设计” 的概念应运而生。\n响应式网站设计的理念是：页面的设计与开发应当根据用户行为以及设备环境（系统平台、屏幕尺寸、屏幕定向等）进行相应的响应和调整。 使用响应式布局的优点如下：\n（1）节省HTML文件的运营成本\n在之前，如果想要适配不同的设备，需要单独适配HTML文件，这样的话维护一个网页就必须维护两个或者更多的HTML文件，很费时。如果采用响应式设计，适配的工作交给CSS，那么只需要更新一个HTML文件就可以了。\n（2）可以统一链接\n如果网页是分开部署的，那么对于不同类型的设备，就需要使用不同的链接指向对应的网页，有的时候用户不清楚指向的平台，那么就会导致混乱，响应式布局统一了网页，就可以很好地解决这个问题。\n但是，响应式布局也有以下缺点：\n（1）网站的加载时间变长\n因为不同平台使用的是同一网页，因此对于智能手机专门适配的网页，加载图片的时间会变长。\n（2）页面布局的自由度降低\n响应式Web设计的HTML代码是同一套，区分由CSS完成，有的时候网页布局不会很完美。\n如何改变外观呢？ 通过判断条件。比如这样：\n1 @media screen and (max-width: 599px) /* 宽度在599px以下，使用智能手机的布局 */ 什么是乱码？\n简单说，乱码就是机器判断字符编码规则失败的一种情形。\n什么是字符编码呢？在这之前我们得知道，计算机一开始是只能显示英文和阿拉伯数字的，要想显示中文或者其他语言，我们就得用英文和数字进行表示。\n比如说，对于“爱”这个中文汉字，对于GB2312-80标准来说，它用 B0AE来表示，而UTF-8中，它用 E788B1表示。所以，当浏览器从前一种标准“翻译”到另一种标准的时候，就会因为标准不统一造成翻译的偏差，从而导致乱码。\n如果在制作网页的过程中遇到了乱码，请确保HTML文件本身和 head元素中的指定一致。在HTML5中，推荐使用UTF-8，这是全世界通用的统一编码。\n五、JavaScript 1.JavaScript是什么？ 当需要实现一些动态效果，比如图片自动切换，光标放到某个位置后自动出现目录，这个时候就需要JavaScript。如果说把用HTML+CSS的网页说成是绘本，那么JavaScript就可以让绘本动起来。\nJavaScript是一种编程语言。也就是说，JavaScript可以运行“程序”。作为比较，HTML和CSS只是“所见即所得”的变成语言，它们“展现”写下来的东西，而不能做到“实现”。\n什么是程序呢？简单来说，程序就是完成一件事情的过程。比如说我们现在要做一道菜，那么做菜的程序可以分为这几步：买菜，洗菜，切菜，炒菜。对于计算机的程序也是一样的，比如说放映幻灯片的时候，程序可以是这样，先展示图片A，两秒后展示图片B，再过三秒后展示图片C……\n对于JavaScript来说，它可以做到一些基于时间或者用户动作的交互，就像上面提到的，当访问者把鼠标移动到某个地方的时候，利用JavaScript可以做出相应的回应。\nJavaScript的主要功能如下：\n计算\n鼠标点击或悬停等事件交互的触发\nHTML的改写、添加、删除\n样式表的获取、设定等\nJavaScript属于脚本语言，也就是说JavaScript写完就可以直接运行，不需要编译。编译形象地说，就是把写下来的代码“翻译”成计算机可以识别的语言的过程，像是C语言，Java这些都是编程语言。\nJavaScript和Java没有任何关系。\n编写JavaScript 编写JavaScript同样只需要文本编辑器和浏览器。由于JavaScript的语法较为复杂，我们这里介绍一个简单的例子——用报警的形式显示“test”。\n1 2 3 4 5 \u0026lt;script type=\u0026#34;text/javascript\u0026#34;\u0026gt; \u0026lt;!-- alert(\u0026#34;test\u0026#34;); --\u0026gt; \u0026lt;/script\u0026gt; 你可以点击这个按钮查看效果：\nJavaScript也可以像CSS那样外部引用。JavaScript文件的后缀名是 .js，只要写入源代码再在HTML的 head元素中中标记引用即可。就像这样：\n1 \u0026lt;script type=\u0026#34;text/javascript\u0026#34; src=\u0026#34;sample.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; 2.jQuery是什么？ jQuery将JavaScript的一些代码进行了模块化，这样就能用更方便的方法使用JavaScript。\njQuery的使用方法与CSS类似，使用选择器来选择元素，就像这样：\n1 2 $(\u0026#34;选择器\u0026#34;).方式{参数}; /* 表示把选择器选择的元素以某种方式设定参数 */ 举一个简单的例子，我们想要把元素p下的文字设置为红色，那么用jQuery就可以写为：\n1 $(\u0026#34;p\u0026#34;).css(\u0026#34;color\u0026#34;,\u0026#34;red\u0026#34;); 其中“方式”这里写的是 css()，意思就是通过CSS这种方式进行颜色的设置。\n3.使用jQuery的插件 jQuery可以通过引入插件来实现很多功能，这些插件既可以自己开发，也可以直接使用他人已经开发好的。在这一节，我们通过Lightbox插件，来实现“点击图片展开”的功能。\n使用插件的步骤 要想使用插件，我们需要经历以下几步：\n引入jQuery 我们需要先让jQuery能够被浏览器读取。有这样两种方法：\n调用服务器上的jQuery\n在服务器的 head元素中，加入如下代码：\n1 \u0026lt;script src=\u0026#34;http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.0.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; 从官网下载jQuery，再手动导入\n下载好jQuery之后，使用类似于在线导入的方式，在head元素中加入如下代码：\n1 \u0026lt;script src=\u0026#34;jquery.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; 其中src后面写入的是本地下载的jQuery目录中，此脚本对应的地址。\n下载 Lightbox 在 [LightBox 的 Github Release 页][https://github.com/lokesh/lightbox2/releases/tag/v2.11.4]下载，会得到一个压缩包。解压缩里面的文件到一个目录下，我们需要使用以下文件：\ncss/lightbox.css\njs/lightbox.js\nimages 下的所有文件\n把它们复制到自己网站目录中对应的位置。\n在自己的网页中引入 Lightbox 在head元素中加入以下代码：\n1 \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;css/lightbox.css\u0026#34;\u0026gt; 在body元素结束前的位置加入以下代码：\n1 \u0026lt;script src=\u0026#34;js/lightbox.js\u0026#34;\u0026lt;/script\u0026gt; 这样就可以使用 Lightbox 了。\n设置适配 Lightbox 的图片 Lightbox 中可以设置的属性如下：\ndata-lightbox属性\n如果对一组图片设置相同的 data-lightbox 属性值，则这一组图片会被图集化，其中一张图片被点开后，它们之间可以实现 “上一张” “下一张” 的切换效果。\ndata-title属性\ndata-title属性可以为弹出的图片添加说明。\n许可证 在很多时候，我们会去使用网上的免费素材。但是很多素材虽然免费，却不是完全能自由使用的。对于大部分免费素材，都有对应使用的许可证。目前常见的许可证有这些：\nMIT 许可证 只需要保证自负责任，再发布时保持原版权声明，就可以自由地使用，包括复制、修改、再发布、用于商业用途、售卖等等。\nGPL 许可证 GPL 许可证除了要求自负责任，再发布时保持原版权声明，还要求源代码公开，禁止闭源衍生，同时使用了 GPL 许可证的代码，也必须继承 GPL 许可证。GPL 许可证也允许商业使用。\nCC 许可协议 主要针对文章、音乐、图片、视频等创作内容。CC 许可证的基本要素有以下四点：\n| 元素 | 符号 | 含义 |\n|\u0026mdash;\u0026mdash;|\u0026mdash;\u0026mdash;|\u0026mdash;\u0026mdash;|\n| 署名 | BY | 必须注明原作者 |\n| 非商业性使用 | NC | 不能用于商业用途 |\n| 禁止演绎 | ND | 不能修改、改编、二次创作 |\n| 相同方式共享 | SA | 修改后必须使用相同的 CC 许可发布 |\nCreative Commons 组织提供了 6 种常见的 CC 许可协议，每种组合不同元素：\n| 许可类型 | 许可标识 | 主要特点 |\n|\u0026mdash;\u0026mdash;\u0026mdash;-|\u0026mdash;\u0026mdash;\u0026mdash;-|\u0026mdash;\u0026mdash;\u0026mdash;-|\n| CC BY | | 允许自由使用、修改、分发，但必须署名 |\n| CC BY-SA | | 允许修改、分发，但必须署名，并且必须用相同的 CC 许可共享 |\n| CC BY-ND | | 允许分发，但禁止修改 |\n| CC BY-NC | | 允许修改、分发，但不能用于商业用途 |\n| CC BY-NC-SA | | 允许修改、分发，但不能用于商业用途，且必须使用相同的 CC 许可 |\n| CC BY-NC-ND | | 最严格，只能免费分享原始作品，不能修改，也不能用于商业用途 |\n为了尊重他人的劳动成果，在使用他人的免费素材时，请一定要遵循其对应的许可证要求。\n六、PHP 简介 1. PHP 的特点和可以实现的功能 PHP 是专门用于 Web 开发的脚本语言。PHP 运行在服务器上，是服务器端的脚本。\nPHP 可以实现的功能 PHP 擅长制作动态网页。动态网页是指内容可变的网页，页面内容由服务器端的程序（如 PHP、Java、Python、Node.js）动态生成，通常与数据库交互，根据用户请求显示不同的内容。相应地还有静态网页，是指内容固定的网页，用户每次访问时看到的内容都是相同的。它的页面不依赖服务器端的数据库或后台逻辑来生成内容。\nPHP 擅长链接数据库。数据库是存放整理好的数据的地方，PHP 可以利用 SQL 语句从数据库存取数据，再利用各种程序语句，就可以把数据输出到网页。\nPHP 可以直接嵌入 HTML 中。PHP 可以和 HTML 配套使用，只需要在 HTML 中引入即可。\n2. 产品经理也需要了解 Web 的开发知识 互联网产品经理需要和程序员组成一个项目组，才能一起推进项目的进展。但是如果认为“这些工作程序员都会去做的”，而把所有技术相关的工作全部扔给程序员，就会造成程序员的负担过重，项目开发的进度也会被延迟。具备以下素养的产品经理，能够更好地开展工作：\n了解一定的编程知识，和程序员讨论时，可以想出更好的方案\n编写 HTML 和 CSS 时，有意识地思考“怎么样才能方便地让程序员嵌入程序”\n有能力的事情自己做（比如简单的 JavaScript）\n","date":"2023-09-01T00:00:00Z","permalink":"/p/study-web-with-wakaba/","title":"《跟 Wakaba 酱一起学网站制作》笔记"},{"content":" 家里有一个办宽带送的电视盒子，但是1+8，安卓4.4的配置已经是太太太老了，还预装了一堆IPTV的东西拖累运行速度，况且安卓4.4也不支持adb激活冰箱，于是就只能通过root来解决这个问题了。不过网上的教程都需要通过拆机接TTL线来搞，实在是有点麻烦，考虑到拆机核心思路也就是拿到shell的root权限，如果我们能在系统中直接拿到root权限就好办了。安卓4.4时代正是各种一键root工具开花的时候，因此上面这种思路就变得好实现了。\n免责声明：本文只提供搞机的思路，由此带来的损失本人不承担任何责任！\n需要的工具你可以在这里进行下载。\n开启adb调试 下载STB_ConfigTool_V7.6.7_Bin_Site.rar，解压其中的bin文件夹（密码：qizhi），双击StbCfgTool.exe。打开后先点击Character Info，右边会出来一段字符，复制下来，然后点击Next，把刚刚得到的这串字符复制进去，点下一步，就可以进到主页面了。\nWindows 10以上的系统会出现界面显示不全的现象，不需要去理会。\n将你的机顶盒连接到无线网络，注意必须和电脑在同一个局域网内（也就是连接到一个路由器的网上）。在配置工具内在ipv4一栏输入你机顶盒的IP地址，点击连接，等待连接成功后再点打开 ADB。如果在等待一段时间后，按钮变为关闭 ADB，那么这一步的设置就完成了。\n下载adb工具包并解压，在工具包的目录下打开cmd。为了方便描述，在这里假定盒子的IP地址为192.168.0.5。\n打开cmd后，输入以下命令远程连接盒子：\n1 adb connect 192.168.0.5 如果出现connected to 192.168.0.5:5555就代表这一步完成了。为了避免不必要的麻烦，我们先看看能不能直接获取到adb shell的root权限。往里面输入\n1 adb shell 此时前面应该会变成shell@orange:/ $这样的字样。输入su，观察$是否会变成#，同时前面应该会变成root@orange:/ #这样的字。如果可以的话，那么直接就可以卸载掉不必要的软件了，如果报错的话，那么我们就要手动进行root权限的接管。\n获取root权限 下载360cjroot-7.0.7.apk，回到刚才的cmd窗口。先输入exit退出shell模式，然后我们用adb把apk安装到盒子上。为了方便描述，在这里我们假定安装包路径为D:\\360root.apk。\n向cmd里输入\n1 adb install D:\\360root.apk 等待安装完成。安装完成后，转到电视盒子插上鼠标，打开360root，点击一键ROOT，等待root完毕。\n等待过程中很可能会出现电视盒子卡死，或者提示root失败等，多尝试几次再确认是否可行。\nroot完毕之后，重复前面的步骤，确认是否可以取得shell的root权限。可行之后转到下一步。\n如果盒子弹出了给予名字为Shell的APP请求root权限的对话框，请选择允许。\n接管root权限 下载SuperSU.zip并解压，回到我们刚刚已经获取了权限的shell中。\n先用mkdir命令创立一个目录，我们要把这个目录作为临时中转站：\n1 2 cd /sdcard/ mkdir SuperSUTemp 随后，我们运行两次exit命令退出shell模式。就像这样：\n1 2 3 root@orange:/ # exit shell@orange:/ $ exit C:\\Users\\Rinko24\u0026gt; 在这里我们假定你把SuperSU的文件复制出来的目录是D:\\SuperSU。运行以下命令，把相关文件复制到临时目录里：\n1 2 3 4 5 6 7 8 9 10 11 12 13 adb push D:\\SuperSU\\su /sdcard/SuperSUTemp/ adb push D:\\SuperSU\\su /sdcard/SuperSUTemp/ adb push D:\\SuperSU\\supolicy /sdcard/SuperSUTemp/ adb push D:\\SuperSU\\supolicy /sdcard/SuperSUTemp/ adb push D:\\SuperSU\\chattr /sdcard/SuperSUTemp/ adb push D:\\SuperSU\\chattr /sdcard/SuperSUTemp/ adb push D:\\SuperSU\\suinit /sdcard/SuperSUTemp/ adb push D:\\SuperSU\\suinit /sdcard/SuperSUTemp/ adb push D:\\SuperSU\\sukernel /sdcard/SuperSUTemp/ adb push D:\\SuperSU\\sukernel /sdcard/SuperSUTemp/ adb push D:\\SuperSU\\daemonsu /sdcard/SuperSUTemp/ adb push D:\\SuperSU\\daemonsu /sdcard/SuperSUTemp/ adb push D:\\SuperSU\\libsupol.so /sdcard/SuperSUTemp/ 然后进入到root权限的shell中。先把system分区挂载为可读写：\n1 mount -o remount,rw /system 随后，移动临时目录中的文件到system分区下对应的文件夹：\n1 2 3 4 5 6 7 8 9 10 11 12 13 cp /sdcard/SuperSUTemp/su /system/bin/ cp /sdcard/SuperSUTemp/su /system/xbin/ cp /sdcard/SuperSUTemp/supolicy /system/bin/ cp /sdcard/SuperSUTemp/supolicy /system/xbin/ cp /sdcard/SuperSUTemp/chattr /system/bin/ cp /sdcard/SuperSUTemp/chattr /system/xbin/ cp /sdcard/SuperSUTemp/suinit /system/bin/ cp /sdcard/SuperSUTemp/suinit /system/xbin/ cp /sdcard/SuperSUTemp/sukernel /system/bin/ cp /sdcard/SuperSUTemp/sukernel /system/xbin/ cp /sdcard/SuperSUTemp/daemonsu /system/bin/ cp /sdcard/SuperSUTemp/daemonsu /system/xbin/ cp /sdcard/SuperSUTemp/libsupol.so /system/lib/ 为这些文件添加权限：\n1 2 3 4 5 6 7 8 9 10 11 12 13 chmod 4755 /system/bin/su chmod 4755 /system/xbin/su chmod 4755 /system/bin/supolicy chmod 4755 /system/xbin/supolicy chmod 4755 /system/bin/chattr chmod 4755 /system/xbin/chattr chmod 4755 /system/bin/suinit chmod 4755 /system/xbin/suinit chmod 4755 /system/bin/sukernel chmod 4755 /system/xbin/sukernel chmod 4755 /system/bin/daemonsu chmod 4755 /system/xbin/daemonsu chmod 0644 /system/lib/libsupol.so 随后设置su文件的自启选项：\n1 2 echo \u0026#34;/system/xbin/daemonsu --auto-daemon \u0026amp;\u0026#34; \u0026gt;\u0026gt; /system/bin/init.zte.post_boot.sh /system/xbin/daemonsu --auto-daemon 现在我们已经都设置好了。接下来卸载掉360超级root:\n1 pm uninstall --user 0 com.qihoo.permmgr 然后关闭掉盒子电视自带的安装限制，并设置自动启动：\n1 2 3 4 5 6 setprop config.Android.AppInstallCtrl 3 settings put secure install_non_market_apps 1 settings put global install_non_market_apps 1 echo \u0026#34;setprop config.Android.AppInstallCtrl 3\u0026#34; \u0026gt;\u0026gt;/system/bin/init.zte.post_boot.sh echo \u0026#34;settings put secure install_non_market_apps 1\u0026#34; \u0026gt;\u0026gt;/system/bin/init.zte.post_boot.sh echo \u0026#34;settings put global install_non_market_apps 1\u0026#34; \u0026gt;\u0026gt;/system/bin/init.zte.post_boot.sh 连续执行两次exit退出shell，然后安装SuperSU：\n1 adb install D:\\SuperSU\\SuperSU.apk 接着转到盒子，把你的盒子网络连接到可以科学上网的环境，打开SuperSU，不出意外会提示你更新二进制文件，点击更新，按照程序的提示进行操作，更新完毕后就可以正常授权了。\n接下来的事情就可以自己选择了，卸载预装，安装当贝桌面这些都可以。预装软件建议通过AirFrozen进行冻结，不需要自己去卸载预装，以免不必要的麻烦。\n参考资料：\n中兴现场配置工具STB_ConfigTool_V7.6.7_Bin_Site\n大江苏b860av1.1无UBOOT 文件救砖成功新的分享。\nB860AV1.1-T优化第二弹:获取 root 权限\n","date":"2023-08-02T17:50:00Z","permalink":"/p/get-root-on-zte-tvbox/","title":"中兴 B860AV1.1 免拆机 SuperSU 接管 root 教程"},{"content":"4月\n12日\n——坏人\n19日\n——两边一样长\n23日\n——全部\n25日\n——知道是错误还行动\n——知道是正确而行动\n27日\n——四色定理\n30日\n——人物\n——才能\n——优秀的人\n——————————\n5月\n7日\n——命运之女\n10日\n——平安时代\n11日（考试）\n——源义经\n——源赖朝\n——源赖朝赢了\n——弱者\n12日（考试）\n——认知\n——两个都是\n13日（考试）\n——恶魔辞典\n——致命女郎\n16日\n——安慰剂效应\n19日\n——葛饰北斋\n21日\n——1:1.414\n23日\n——一同\n——感觉\n——共感觉\n26日\n——柯南·道尔\n31日\n——约翰·席尔瓦\n——————————\n6月\n4日\n——晕轮效应\n7日\n——脚的数量\n8日\n——20世纪\n13日\n——绿色\n15日\n——硬币\n20日\n——智慧型手机\n23日\n——教皇\n27日\n——狗\n29日\n——金\n——————————\n7月\n1日\n——蛮头\n4日\n——尤利乌斯与奥古都斯\n7日\n——银河\n——素面\n9日\n——三角形\n11日\n——长期记忆\n——近于无限的量\n——绝不会遗忘\n12日\n——偷窃\n13日（考试）\n——64度\n——诸葛亮\n——人头\n——人头的替代品\n14日（考试）\n——勘察加拟石蟹·石蟹科\n——经济混乱\n15日（考试）\n——下猫下狗\n——鬼的肠子\n——————————\n9月\n3日\n——繁荣\n6日\n——停表错觉\n14日\n——租赁店\n17日\n——有能力的老鹰都会藏起自己的爪子\n21日\n——正中间\n24日\n——白色20片，黑色12片\n28日\n——Phantom\n——Vibration\n——Syndrome\n29日\n——宫内厅\n——————————\n10月\n3日\n——星星\n6日\n——约瑟夫·吉约丹\n11日\n——波巴\n17日（考试）\n——32面\n——以前只有一种颜色\n——黑白的\n18日（考试）\n——夏尔·亨利·桑松\n——因为是世袭制\n19日（考试）\n——强制劳动\n——3只蜜蜂在欧洲大陆\n22日\n——15\n24日\n——主观认定\n——————————\n11月\n2日\n——江户时代\n4日\n——剑\n8日\n——几岁都可以\n10日\n——因为很难看到它的眼睛\n12日\n——因为声音是合成的\n14日\n——因为海拔很高\n15日\n——斩首示众\n18日\n——蜗牛线\n————————————\n12月\n20日（考试）\n——D\n——10亿以上\n——斩首并游街示众\n——是一场表演\n21日（考试）\n——红桃\n——旁听\n22日（考试）\n——日本\n——英国的无畏舰\n——————————\n1月\n11日\n——八百万\n14日\n——岩手县\n18日\n——准备就绪\n21日\n——蛇\n25日\n——我的手机\n——用光\n——我的手机没电了\n27日\n——可以传给朋友的朋友的朋友\n","date":"2023-07-30T17:28:00Z","permalink":"/p/p5r-answer/","title":"P5R 课堂回答答案"},{"content":" 这篇文章中的部分内容可能已经过时。\n编译前的准备 1.你的电脑最好有一颗强劲的CPU\n性能差一点也没关系，只是时间会长一点，这方面没有严格的要求。\n2.你的电脑需要16GB以上的运行内存\n如果你的电脑且运行内存为16GB，请最好通过未安装桌面环境的系统操作来提高编译成功率。\n如果你的电脑运行着Windows，且运行内存为16GB，则不推荐通过安装虚拟机来进行编译操作。\n3.你的电脑需要350GB以上的硬盘空间\n如果你需要进行多次编译，则最好预留500GB以上的硬盘空间。\n4.你的电脑需要运行Ubuntu 20.04（wsl，虚拟机，直接运行的都可以）\n5.你的电脑具有良好的网络环境\n建议租一个云服务器，会更方便一些。\n以下命令均需要在终端下运行。\n编译教程 这里以编译Lineage OS 19.1为例：\n一、准备编译环境 1.更新软件源 1 sudo apt-get update \u0026amp;\u0026amp; apt-get upgrade -y 2.安装依赖 1 sudo apt-get install bc bison build-essential ccache curl flex g++-multilib gcc-multilib git gnupg gperf imagemagick lib32ncurses5-dev lib32readline-dev lib32z1-dev libelf-dev liblz4-tool libncurses5 libncurses5-dev libsdl1.2-dev libssl-dev libwxgtk3.0-gtk3-dev libxml2 libxml2-utils lzop pngcrush rsync schedtool squashfs-tools xsltproc zip zlib1g-dev 3.设置git相关信息 1 2 git config --global user.name \u0026#34;在此处替换成你的名字\u0026#34; git config --global user.email \u0026#34;在此处替换成你的邮箱\u0026#34; 4.安装repo 1 2 3 4 mkdir -p ~/.bin PATH=\u0026#34;${HOME}/.bin:${PATH}\u0026#34; curl https://storage.googleapis.com/git-repo-downloads/repo \u0026gt; ~/.bin/repo chmod a+rx ~/.bin/repo 二、拉取相关代码 1.创立工作区和工作文件夹 1 2 screen -S sync mkdir LineageOS \u0026amp;\u0026amp; cd LineageOS 2.拉取Lineage OS源码 1 2 repo init -u https://github.com/LineageOS/android.git -b lineage-19.1 repo sync 如果硬盘空间比较紧张，可以通过在第一行命令后加上 --depth=1来节省空间。\n同时，在第一行命令输入完成后，你可以通过把镜像源换成清华镜像站来加快同步速度：\n1 git config --global url.https://mirrors.bfsu.edu.cn/git/AOSP/.insteadof https://android.googlesource.com 3.同步设备相关代码 1 2 3 4 5 git clone https://github.com/wbs306/device_xiaomi_mt6895-common.git device/xiaomi/mt6895-common git clone -b lineage-19.1-oss https://github.com/YuKongA/device_xiaomi_rubens.git device/xiaomi/rubens git clone https://github.com/PixelExperience/device_mediatek_sepolicy_vndr device/mediatek/sepolicy_vndr git clone https://github.com/YuKongA/vendor_xiaomi_rubens.git vendor/xiaomi/rubens git clone https://github.com/wbs306/kernel_xiaomi_mt6895.git kernel/xiaomi/mt6895 4.修补相关补丁 1 2 3 4 5 6 cd ~/LineageOS/frameworks/opt/telephony git fetch https://gerrit.pixelexperience.org/frameworks_opt_telephony refs/changes/76/15476/1 \u0026amp;\u0026amp; git cherry-pick FETCH_HEAD cd ~/LineageOS/frameworks/opt/net/ims git fetch https://gerrit.pixelexperience.org/frameworks_opt_net_ims refs/changes/18/15318/1 \u0026amp;\u0026amp; git cherry-pick FETCH_HEAD git fetch https://gerrit.pixelexperience.org/frameworks_opt_net_ims refs/changes/17/15317/1 \u0026amp;\u0026amp; git cherry-pick FETCH_HEAD cd ~/LineageOS 如果你编译的是像Pixel Experience这些已经合并补丁的ROM，则不需要进行这一步。\n5.修补必要的文件 从13.0.22的官方包里提取vendor.img，odm.img，然后把这两个文件上传到工作目录里的device/xiaomi/rubens/prebuilts文件夹里。\n6.开始编译 1 2 3 source build/envsetup.sh lunch lineage_rubens-userdebug mka bacon 此时按下Ctrl+A+D可以脱离工作区，你也可以随时通过screen -R sync来查看编译进度。\n当编译完成时，在工作目录下的out/target/product/rubens找到编译完成的包以及md5校验文件。可以通过TWRP来刷入刷机包。\n如果不进行第五步，可以通过以下命令只编译system分区与product分区：\n1 2 3 source build/envsetup.sh lunch lineage_rubens-userdebug mka systemimage \u0026amp;\u0026amp; mka productimage \u0026amp;\u0026amp; cd$OUT \u0026amp;\u0026amp; zip dsu.zip system.img product.img 这样下来你会得到一个dsu.zip，里面有两个img文件，你可以通过DSU Sideloader来直接加载系统，也可以直接用fastboot刷入对应分区。\n","date":"2022-08-30T00:00:00Z","permalink":"/p/make-lineage-os-for-rubens/","title":"Lineage OS for Redmi K50 编译指南"}]