-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfeed.json
1250 lines (1250 loc) · 968 KB
/
feed.json
1
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
{
"version": "https://jsonfeed.org/version/1.1",
"title": "Leonhardt's Blog",
"home_page_url": "https://kigane.github.io/",
"feed_url": "https://kigane.github.io/feed.json",
"description": "想,都是问题。做,才是答案。",
"author": {
"name": "Leonhardt"
},
"items": [
{
"title": "IoC(Inversion of Control)",
"url": "https://kigane.github.io/blog/IoCandDI/",
"id": "https://kigane.github.io/blog/IoCandDI/",
"content_html": "<h2 id=\"ioc-inversion-of-control\"> IoC(Inversion of Control)</h2>\n<ul>\n<li>control:类的非主要责任</li>\n<li>dependency:A类的任务没有B类就完成不了,即A依赖B。</li>\n</ul>\n<h2 id=\"dip-definition\"> DIP Definition</h2>\n<ul>\n<li>High-level modules should not depend on low-level modules. Both should depend on the abstraction.</li>\n<li>Abstractions should not depend on details. Details should depend on abstractions.</li>\n<li>一句话:加接口。</li>\n<li>面向接口编程</li>\n</ul>\n<h2 id=\"di\"> DI</h2>\n<ul>\n<li>Constructor Injection:通过构造函数把实现接口的具体类的对象传给A</li>\n<li>Property Injection:用Setter传</li>\n<li>Method Injection:在创建一个接口,专门用来把具体类的对象传给A。(不用构造函数,而是用接口函数)</li>\n</ul>\n<h2 id=\"ioc-container\"> IOC Container</h2>\n<ul>\n<li>Register: The container must know which dependency to instantiate when it encounters a particular type. This process is called registration. Basically, it must include some way to register type-mapping.</li>\n<li>Resolve: When using the IoC container, we don't need to create objects manually. The container does it for us. This is called resolution. The container must include some methods to resolve the specified type; the container creates an object of the specified type, injects the required dependencies if any and returns the object.</li>\n<li>Dispose: The container must manage the lifetime of the dependent objects. Most IoC containers include different lifetimemanagers to manage an object's lifecycle and dispose it.</li>\n</ul>\n<p>简单的IoC容器,内部保存一个字典,以类型为key,实例为value。包含一个Register()方法和Get()方法。</p>\n",
"date_published": "2022-02-19T05:19:13.000Z",
"date_modified": "2022-02-19T05:19:13.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "Blender Basic",
"url": "https://kigane.github.io/blog/blender/",
"id": "https://kigane.github.io/blog/blender/",
"content_html": "<h2 id=\"基础操作\"> 基础操作</h2>\n<table>\n<thead>\n<tr>\n<th>操作</th>\n<th>快捷键</th>\n<th>说明</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>移动</td>\n<td>g</td>\n<td>按下g后再按x,y,z可固定移动轴为global,再按一次相同键,使用local,再按一次,取消固定。</td>\n</tr>\n<tr>\n<td>旋转</td>\n<td>r</td>\n<td>按下r后再按x,y,z可固定旋转轴为global,再按一次相同键,使用local,再按一次,取消固定。</td>\n</tr>\n<tr>\n<td>缩放</td>\n<td>s</td>\n<td>按下s后再按x,y,z可固定缩放轴为global,再按一次相同键,使用local,再按一次,取消固定。</td>\n</tr>\n<tr>\n<td>移动视角</td>\n<td>鼠标中键</td>\n<td>或右上的Gizmo。不按shift,相机始终看向当前屏幕中心点。按shift可上下左右平移相机。</td>\n</tr>\n<tr>\n<td>调节视角远近</td>\n<td>鼠标滚轮</td>\n<td></td>\n</tr>\n<tr>\n<td>视角对齐到轴</td>\n<td>1,3,7,9</td>\n<td>front, right, top, bottom</td>\n</tr>\n<tr>\n<td>微调视角</td>\n<td>2,4,6,8</td>\n<td></td>\n</tr>\n<tr>\n<td>相机视角</td>\n<td>0</td>\n<td></td>\n</tr>\n<tr>\n<td>侧边栏</td>\n<td>n</td>\n<td>物体的位置,旋转,缩放参数</td>\n</tr>\n<tr>\n<td>在对象模式和编辑模式间切换</td>\n<td>tab</td>\n<td>ctrl+tab呼出轮盘</td>\n</tr>\n<tr>\n<td>比例编辑</td>\n<td>o</td>\n<td>需要先选定一个顶点</td>\n</tr>\n</tbody>\n</table>\n",
"date_published": "2022-02-04T00:00:00.000Z",
"date_modified": "2022-02-19T05:19:13.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": [
"GameEngine"
]
},
{
"title": "我的博客",
"url": "https://kigane.github.io/blog/",
"id": "https://kigane.github.io/blog/",
"content_html": "<h2 id=\"名言警句\"> 名言警句</h2>\n<blockquote>\n<p>不积跬步无以至千里</p>\n</blockquote>\n<blockquote>\n<p>Keep It Simple, Stupid.</p>\n</blockquote>\n<blockquote>\n<p>快速推进,逐步优化</p>\n</blockquote>\n<blockquote>\n<p>Make it work. Make it right. Make it fast.</p>\n</blockquote>\n<blockquote>\n<p>前途很远,也很暗。\n然而不要怕,不怕的人的面前才有路。</p>\n</blockquote>\n<blockquote>\n<p>I want to create a collection of games during my career, so that when I am on my deathbed I can look back and see that I created all these wonderful things that brought people joy.</p>\n</blockquote>\n<blockquote>\n<p>Less interests, more interest.</p>\n</blockquote>\n<blockquote>\n<p>想,都是问题。做,才是答案。</p>\n</blockquote>\n<blockquote>\n<p>形而上者谓之道,形而下者谓之器,能舍器而直接近道者,几稀!</p>\n</blockquote>\n<blockquote>\n<p>比较的关键不在于相似特征的多少,而在于特征是不是揭示了事物的本质</p>\n</blockquote>\n<blockquote>\n<p>Experience is what you get when you didn't get what you wanted.</p>\n</blockquote>\n<blockquote>\n<p>"luck" is where preparation meets opportunity.</p>\n</blockquote>\n<blockquote>\n<p>Perfection is finally attained not when there is no longer anything to add, but when there is no\nlonger anything to take away.</p>\n</blockquote>\n<blockquote>\n<p>As I Began to Love Myself</p>\n<p>当我真正开始爱自己</p>\n<p>As I began to love myself I found that anguish and emotional suffering are only warning signs that I was living against my own truth. Today, I know, this is AUTHENTICITY.</p>\n<p>当我真正开始爱自己,我才意识到,所有的痛苦和情感的折磨,仅仅是在提醒,我的生活背离了真实的自己。今日,我明白,这叫做「真实」。</p>\n<p>As I began to love myself I understood how much it can offend somebody as I try to force my desires on this person, even though I knew the time was not right and the person was not ready for it, and even though this person was me. Today I call it RESPECT.</p>\n<p>当我真正开始爱自己,我才懂得,当我将自己的意愿强加于人时,是多么的冒犯无礼,即使那时我知晓时机尚未成熟,那个人也并未做好准备,即便那个人就是我自己。今日我明白这叫做「尊重」。</p>\n<p>As I began to love myself I stopped craving for a different life, and I could see that everything that surrounded me was inviting me to grow. Today I call it MATURITY.</p>\n<p>当我开始真正爱自己,我不再苛求不一样的人生,我明白任何发生在我身边的事,都是对我成长的邀约。而今,我称之为「成熟」。</p>\n<p>As I began to love myself I understood that at any circumstance, I am in the right place at the right time, and everything happens at the exactly right moment, so I could be calm. Today I call it SELF-CONFIDENCE.</p>\n<p>当我开始真正爱自己,我才明白在任何情况下,我其实一直处于恰好的时机、恰好的地方,而每件事的发生也都恰如其分。由此,我得以平静。今日,我称其为「自信」。</p>\n<p>As I began to love myself I quit stealing my own time, and I stopped designing huge projects for the future. Today, I only do what brings me joy and happiness, things I love to do and that make my heart cheer, and I do them in my own way and in my own rhythm. Today I call it SIMPLICITY.</p>\n<p>当我开始真正爱自己,我不再挥霍自己生命和时间,不再去擘画波澜壮阔的未来。今天的我,只做能为我带来喜悦和幸福的事,做那些我所热爱,且能鼓舞我心的事情,用我特有的方式,按照我自己的节奏。今日我知晓这叫做「简单」。</p>\n<p>As I began to love myself I freed myself of anything that is no good for my health – food, people, things, situations, and everything that drew me down and away from myself. At first I called this attitude a healthy egoism. Today I know it is LOVE OF ONESELF.</p>\n<p>当我开始真正爱自己,我开始远离所有不健康的东西,不论是饮食、人物、事情亦或环境,远离拖累我、让我背离了真实自我的一切。从前的我称呼这为“有益健康的利己主义”。今天我知道,这其实是「自爱」。</p>\n<p>As I began to love myself I quit trying to always be right, and ever since I was wrong less of the time. Today I discovered that is MODESTY.</p>\n<p>当我开始真正爱自己,我不再总想做到永远正确,从那时起,我犯错的时候反而更少了。现在我发现那就是「谦逊」。</p>\n<p>As I began to love myself I refused to go on living in the past and worry about the future. Now, I only live for the moment, where EVERYTHING is happening. Today I live each day, day by day, and I call it FULFILLMENT.</p>\n<p>当我开始真正爱自己,我不再继续沉溺于过去,也不再为明天而忧虑。如今,我只活在这一切正在发生的当下。今天,我活在此时此地,如此日复一日,而我称这为「圆满」。</p>\n<p>As I began to love myself I recognized that my mind can disturb me and it can make me sick. But As I connected it to my heart, my mind became a valuable ally. Today I call this connection WISDOM OF THE HEART.</p>\n<p>当我真正开始爱自己,我明白,我的思虑扰乱了自我,使我变得了无生趣。但当我与本心相连,心灵的力量就成为了我坚实的后盾。今日我称这种连结为「心灵的智慧」。</p>\n<p>We no longer need to fear arguments, confrontations or any kind of problems with ourselves or others. Even stars collide, and out of their crashing new worlds are born. Today I know THAT IS LIFE!</p>\n<p>我们无需再害怕生命中的争论、冲突,或是任何出现在与我们自己或与其他人之间的问题。因为即使是星星也会有碰撞在一起的时候,而从它们的撞击中,新的世界将会诞生。今天我知道,这,就是「生命」!</p>\n</blockquote>\n",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2022-02-19T05:19:13.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "Docker入门",
"url": "https://kigane.github.io/blog/docker/",
"id": "https://kigane.github.io/blog/docker/",
"content_html": "<h2 id=\"基本命令\"> 基本命令</h2>\n<ul>\n<li><code>docker pull image</code> 从Docker仓库中拉取镜像。</li>\n<li><code>docker run image</code> 创建一个新的容器, 启动镜像。如果镜像不存在,则会尝试先从Docker仓库中拉取。\n<ul>\n<li>-d 后台运行</li>\n<li>-p 81:80 将主机的81端口和Docker Container的80端口绑定</li>\n<li>--name custonName</li>\n</ul>\n</li>\n<li><code>docker start <the-container-id></code> 启动容器</li>\n<li><code>docker stop <the-container-id></code> 关闭容器</li>\n<li><code>docker ps</code> 显示当前运行的容器。\n<ul>\n<li>-a 所有容器,包括已停止的。</li>\n</ul>\n</li>\n<li><code>docker log name</code> 显示日志</li>\n<li><code>docker exec -it name|<the-container-id> /bin/bash</code> 打开容器的交互式终端。可用于Debug环境。\n<ul>\n<li>-it 打开交互式终端</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"创建镜像\"> 创建镜像</h2>\n<ul>\n<li><code>docker build -t getting-started .</code> 创建一个镜像\n<ul>\n<li>-t 镜像名</li>\n<li>. dockerfile所在文件夹</li>\n</ul>\n</li>\n<li><code>docker rm <the-container-id></code> 删除镜像</li>\n</ul>\n<h2 id=\"上传镜像\"> 上传镜像</h2>\n<ul>\n<li><code>docker login -u YOUR-USER-NAME</code> 登录</li>\n<li><code>docker tag image YOUR-USER-NAME/image_new_name</code> 给镜像标tag。</li>\n<li><code>docker push YOUR-USER-NAME/image_new_name</code> 上传镜像</li>\n</ul>\n<h2 id=\"数据持久化\"> 数据持久化</h2>\n<p>容器的文件系统:每个容器有独立的文件系统,在一个容器中创建,更新,删除文件不会影响到其他容器。</p>\n<p>Container Volumes:容器的文件系统都是独立的,在容器被删除时,其文件系统中的文件也不会保留。此外,容器也不能共享文件。Volumes提供了将容器中的文件路径和主机连接起来的能力。Volume可以看作一个存数据的空间,其在主机中的具体位置由Docker管理,用户只要记住Volume的名字即可使用。</p>\n<p><code>docker volume create todo-db</code> 创建Volume\n<code>docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started</code> 在创建容器时用-v选项指定Volume在容器中挂载的位置</p>\n<p>Volumes有两种主要类型,一种named volume主要用于存储数据,不需要关注数据具体存在哪里。一种bind mount,用于热更新,增加数据。</p>\n<p><code>docker run -dp 3000:3000 -w /app -v "$(pwd):/app" node:12-alpine sh -c "yarn install && yarn run dev</code>\n-w /app 指定app在容器中的工作目录\n-v "$(pwd):/app" 前面是主机目录,后面是容器目录。将主机目录挂载到容器目录上。注意,主机目录应该这样写"/c/Docker/app",即在c盘的Docker/app文件夹。\nsh -c "yarn install && yarn run dev 安装依赖并启动nodemon监视源码,如有改动,立即更新。</p>\n<h2 id=\"多容器应用\"> 多容器应用</h2>\n<ul>\n<li>\n<p>In general, each container should do one thing and do it well.</p>\n</li>\n<li>\n<p>If two containers are on the same network, they can talk to each other. If they aren't, they can't.</p>\n</li>\n<li>\n<p><code>docker network create todo-app</code> 创建网络</p>\n</li>\n<li>\n<p><code>docker run -d --network todo-app --network-alias mysql -v todo-mysql-data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=secret -e MYSQL_DATABASE=todos mysql:5.7</code></p>\n<ul>\n<li>--network-alias 定义容器在网络中的别名,可以当做IP地址使用。</li>\n<li>-v todo-mysql-data:/var/lib/mysql 我们没有手动创建todo-mysql-data Volume,这里docker知道你要用named volume,会自动帮你创建。</li>\n<li>-e 设置环境变量</li>\n</ul>\n</li>\n<li>\n<p><code>docker exec -it <mysql-container-id> mysql -p</code> 进入数据库,输入<code>SHOW DATABASES;</code>应该能看到todos数据库。</p>\n</li>\n<li>\n<p><a href=\"https://github.com/nicolaka/netshoot\" target=\"_blank\" rel=\"noopener noreferrer\">nicolaka/netshoot</a> container:包含很多处理网络相关事务的工具。</p>\n</li>\n<li>\n<p><code>docker run -it --network todo-app nicolaka/netshoot</code> 启动工具。在工具命令行中输入dig mysql,徐结果如下。其中ANSWER SECTION中表示mysql对应的IP为172.23.0.2。mysql不是有效的主机名,而是我们用--network-alias定义的别名。在容器网络中可以直接用这个别名代替IP地址。</p>\n</li>\n</ul>\n<div><pre><code>; <<>> DiG 9.14.1 <<>> mysql\n;; global options: +cmd\n;; Got answer:\n;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32162\n;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0\n\n;; QUESTION SECTION:\n;mysql. IN A\n\n;; ANSWER SECTION:\nmysql. 600 IN A 172.23.0.2\n\n;; Query time: 0 msec\n;; SERVER: 127.0.0.11#53(127.0.0.11)\n;; WHEN: Tue Oct 01 23:47:24 UTC 2019\n;; MSG SIZE rcvd: 44\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br></div></div><ul>\n<li>\n<p><code>docker run -dp 3000:3000 -w /app -v "$(pwd):/app" --network todo-app -e MYSQL_HOST=mysql -e MYSQL_USER=root -e MYSQL_PASSWORD=secret -e MYSQL_DB=todos node:12-alpine sh -c "yarn install && yarn run dev"</code> 启动app并设置环境变量以连接到mysql。</p>\n</li>\n<li>\n<p><code>docker exec -it <mysql-container-id> mysql -p todos</code> 进入todos数据库,执行select * from todo_items查看添加的todo条目。</p>\n</li>\n</ul>\n<h2 id=\"使用docker-compose简化多容器应用\"> 使用Docker Compose简化多容器应用</h2>\n<p>上一节创建一个多容器app步骤较繁琐。使用Docker Compose可以简化。(如果是在Linux上首先需要安装Docker Compose)</p>\n<ol>\n<li>在项目根目录中新建docker-compose.yml文件</li>\n<li>使用配置文件定义服务,以实现和上一节同样的效果</li>\n<li>运行:<code>docker-compose up -d</code></li>\n<li>查看日志: <code>docker-compose logs -f</code></li>\n<li>关闭: <code>docker-compose up</code></li>\n</ol>\n<div><pre><code><span>version</span><span>:</span> <span>\"3.8\"</span> <span># schema version</span>\n<span>services</span><span>:</span>\n <span>app</span><span>:</span> <span># 服务名会自动作为--network-alias</span>\n <span>image</span><span>:</span> node<span>:</span>12<span>-</span>alpine <span># 要运行的镜像</span>\n <span>command</span><span>:</span> sh <span>-</span>c \"yarn install <span>&&</span> yarn run dev\" <span># 容器创建后要执行的命令</span>\n <span>ports</span><span>:</span> <span># 端口映射 -p</span>\n <span>-</span> 3000<span>:</span><span>3000</span>\n <span>working_dir</span><span>:</span> /app <span># 指定容器工作目录 -w</span>\n <span>volumes</span><span>:</span> <span># volume映射 -v</span>\n <span>-</span> ./<span>:</span>/app\n <span>environment</span><span>:</span> <span># 环境变量 -e</span>\n <span>MYSQL_HOST</span><span>:</span> mysql\n <span>MYSQL_USER</span><span>:</span> root\n <span>MYSQL_PASSWORD</span><span>:</span> secret\n <span>MYSQL_DB</span><span>:</span> todos\n \n <span>mysql</span><span>:</span>\n <span>image</span><span>:</span> <span>'mysql:5.7'</span>\n <span>volumes</span><span>:</span>\n <span>-</span> todo<span>-</span>mysql<span>-</span>data<span>:</span>/var/lib/mysql\n <span>environment</span><span>:</span>\n <span>-</span> MYSQL_ROOT_PASSWORD=secret\n <span>-</span> MYSQL_DATABASE=todos\n\n<span>volumes</span><span>:</span> <span># 创建 named volume</span>\n <span>todo-mysql-data</span><span>:</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br><span>25</span><br><span>26</span><br></div></div><p>在Docker Desktop中查看,这个app显示为app(项目所在文件夹的名字)。app下的容器名为<code><project-name>_<service-name>_<replica-number></code>。</p>\n<h2 id=\"创建镜像的最佳实践\"> 创建镜像的最佳实践</h2>\n<h3 id=\"layer-caching\"> layer caching</h3>\n<ul>\n<li>dockerfile的每一行为一层(layer),一但某层需要重新执行,则其下所有层都会重新执行。</li>\n<li>dockerfile缓存机制</li>\n</ul>\n<div><pre><code>FROM node<span>:</span>12<span>-</span>alpine\nWORKDIR /app\nCOPY . .\nRUN yarn install <span>-</span><span>-</span>production\nCMD <span>[</span><span>\"node\"</span><span>,</span> <span>\"src/index.js\"</span><span>]</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br></div></div><p>每次重新创建镜像的时候,都会从头开始构建:新建app目录,将原镜像的文件复制过来,执行yarn install安装依赖,最后执行node src/index.js运行服务。问题在于:parent镜像执行过yarn install后,工作目录中会多一个npm_modules的文件夹,里面是yarn安装的各种依赖,通常比较大,而且内容主要是数量极多的小文件。复制操作极为耗时。因此,最佳实践如下</p>\n<div><pre><code>FROM node<span>:</span>12<span>-</span>alpine\nWORKDIR /app\nCOPY package.json yarn.lock ./\nRUN yarn install <span>-</span><span>-</span>production\nCOPY . .\nCMD <span>[</span><span>\"node\"</span><span>,</span> <span>\"src/index.js\"</span><span>]</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br></div></div><p>修改dockfile后,再在工作目录新建一个文件<code>.dockerignore</code>,内容为<code>npm_modules</code>,意思是不将npm_modules文件夹放到构建上下文(build context)中。此时重新创建镜像时:先复制<code>package.json</code>(要安装的依赖)和<code>yarn.lock</code>(已安装的依赖)两个文件,再执行yarn install,此时如果前两个文件内容未变,yarn不会重新安装依赖。接着执行COPY将其他文件复制过来,<code>npm_modules</code>直接使用缓存。</p>\n",
"date_published": "2022-04-01T00:00:00.000Z",
"date_modified": "2022-04-02T08:34:13.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": [
"Tool"
]
},
{
"title": "文件操作",
"url": "https://kigane.github.io/blog/file_io/",
"id": "https://kigane.github.io/blog/file_io/",
"content_html": "<h2 id=\"os\"> os</h2>\n<table>\n<thead>\n<tr>\n<th>方法</th>\n<th>作用</th>\n<th>补充说明</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>os.chdir(path)</td>\n<td>修改当前工作目录</td>\n<td>会改变getcwd()的结果</td>\n</tr>\n<tr>\n<td>os.getcwd()</td>\n<td>获取当前工作目录</td>\n<td></td>\n</tr>\n<tr>\n<td>os.mkdirs(path)</td>\n<td>创建目录,可以直接创建不存在的父目录再创建子目录,如果目录已存在则抛出错误</td>\n<td>可以使用exist_ok=True参数,使在创建已存在的目录时不抛出错误。</td>\n</tr>\n<tr>\n<td>os.removedirs(path)</td>\n<td>删除指定目录,可以是子目录,如果不存在也不会出现错误</td>\n<td></td>\n</tr>\n<tr>\n<td>os.rename(old, new)</td>\n<td>重命名文件</td>\n<td>可以是文件或目录。对于目录,如果old不存在,也会创建一个new。对于文件,如果old不存则抛出错误</td>\n</tr>\n<tr>\n<td>os.stat(path)</td>\n<td>查询文件meta数据</td>\n<td>st_size(Byte), 时间都是秒数,需要用datatime转换为可读的形式</td>\n</tr>\n<tr>\n<td>os.walk(path)</td>\n<td>递归遍历指定路径的所有子文件和目录</td>\n<td>返回一个三元组的Iterator,(dirpath:str, dirnames:list, filenames:list)分别代表(正在遍历的目录,该目录下的子目录,该目录下的文件)。DFS先序遍历。</td>\n</tr>\n<tr>\n<td>os.environ</td>\n<td>返回一个环境变量字典</td>\n<td>os.environ.get(env_name)获取指定环境变量</td>\n</tr>\n<tr>\n<td>os.path.join(patha, pathb)</td>\n<td>拼接路径</td>\n<td></td>\n</tr>\n<tr>\n<td>os.path.basename(path)</td>\n<td>文件名</td>\n<td></td>\n</tr>\n<tr>\n<td>os.path.dirname(path)</td>\n<td>文件所在的目录名</td>\n<td></td>\n</tr>\n<tr>\n<td>os.path.split(path)</td>\n<td>返回(文件所在的目录名, 文件名)</td>\n<td>实际是找到最后一个分割符,将path分为两部分放入元组</td>\n</tr>\n<tr>\n<td>os.path.splitext(path)</td>\n<td>返回(文件名, 扩展名)</td>\n<td>实际是找到最后一个".",将path分为两部分,扩展名包含"."</td>\n</tr>\n<tr>\n<td>os.path.exists(path)</td>\n<td>确认文件或目录是否存在</td>\n<td></td>\n</tr>\n<tr>\n<td>os.path.isdir(path)</td>\n<td>确认path是否为目录</td>\n<td></td>\n</tr>\n<tr>\n<td>os.path.isfile(path)</td>\n<td>确认path是否为文件</td>\n<td></td>\n</tr>\n</tbody>\n</table>\n<h2 id=\"shutil\"> shutil</h2>\n<table>\n<thead>\n<tr>\n<th>方法</th>\n<th>作用</th>\n<th>补充说明</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>shutil.copyfile(src, dst)</td>\n<td>复制文件</td>\n<td>dst不能是目录</td>\n</tr>\n<tr>\n<td>shutil.copy(src, dst)</td>\n<td>复制文件</td>\n<td>dst可以是目录,此时会将src文件放在dst目录下。dst不存在会创建一个文件</td>\n</tr>\n<tr>\n<td>shutil.move(src, dst)</td>\n<td>递归地移动文件或目录</td>\n<td>dst不存在会创建一个文件</td>\n</tr>\n<tr>\n<td>shutil.copytree(src, dst)</td>\n<td>复制目录中的所有内容到目标目录中</td>\n<td>src/xx -> dst/xx,dst不存在会创建</td>\n</tr>\n<tr>\n<td>shutil.make_archive(basename, format, root)</td>\n<td>将root目录中的所有内容添加到压缩文件中</td>\n<td>format通常设为"zip"</td>\n</tr>\n<tr>\n<td>shutil.unpack_archive(archive_file)</td>\n<td>解压</td>\n<td></td>\n</tr>\n</tbody>\n</table>\n",
"date_published": "2021-12-16T00:00:00.000Z",
"date_modified": "2022-02-19T05:19:13.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": [
"Python"
]
},
{
"title": "直方图均衡化",
"url": "https://kigane.github.io/blog/equalize_histogram/",
"id": "https://kigane.github.io/blog/equalize_histogram/",
"content_html": "<h2 id=\"总览\"> 总览</h2>\n<ul>\n<li>提高全局对比度,在图像灰度集中于某个小区域时效果尤为明显</li>\n<li>直方图均衡化将集中在一起的灰度像素值打散到全灰度级上来获得<strong>对比度的增强</strong></li>\n<li>适合处理曝光不足的照片和骨骼的x-ray图像,热力图,卫星图</li>\n<li>是可逆的,需要知道直方图均衡化函数</li>\n<li>一个缺点是对背景噪音和有用的信号一视同仁</li>\n<li>会使图像产生不真实感</li>\n<li>会丢失一些细节,因此不用于分析环节,仅用于显示。</li>\n<li>对低color depth的图像,会进一步降低其color depth</li>\n<li>改进,在提高对比度的同时不改变灰度的均值和避免细节上的损失\n<ul>\n<li>adaptive histogram equalization</li>\n<li>contrast limiting adaptive histogram equalization (CLAHE)</li>\n<li>multipeak histogram equalization (MPHE)</li>\n<li>multipurpose beta optimized bihistogram equalization (MBOBHE)</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"具体算法\"> 具体算法</h2>\n<ul>\n<li>计算图像的hist直方图,即每个灰度有多少像素</li>\n<li>计算hist的cumsum(),即cdf</li>\n<li>用cdf当权值在目标RANGE内进行插值。\n<ul>\n<li><span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:1em;vertical-align:-0.25em;\"></span><span>h</span><span>(</span><span style=\"margin-right:0.03588em;\">v</span><span>)</span><span style=\"margin-right:0.2778em;\"></span><span>=</span><span style=\"margin-right:0.2778em;\"></span></span><span><span style=\"height:1.4551em;vertical-align:-0.4451em;\"></span><span>ro</span><span>u</span><span>n</span><span>d</span><span>(</span><span><span></span><span><span><span><span style=\"height:1.01em;\"><span style=\"top:-2.655em;\"><span style=\"height:3em;\"></span><span><span><span style=\"margin-right:0.08125em;\">H</span><span>×</span><span style=\"margin-right:0.13889em;\">W</span><span>−</span><span><span><span style=\"margin-right:0.07778em;\">cdf</span></span><span><span><span><span style=\"height:0.3281em;\"><span style=\"top:-2.357em;margin-right:0.0714em;\"><span style=\"height:2.5em;\"></span><span><span><span>min</span></span></span></span></span><span></span></span><span><span style=\"height:0.143em;\"><span></span></span></span></span></span></span></span></span></span><span style=\"top:-3.23em;\"><span style=\"height:3em;\"></span><span style=\"border-bottom-width:0.04em;\"></span></span><span style=\"top:-3.485em;\"><span style=\"height:3em;\"></span><span><span><span><span style=\"margin-right:0.07778em;\">cdf</span></span><span>(</span><span style=\"margin-right:0.03588em;\">v</span><span>)</span><span>−</span><span><span><span style=\"margin-right:0.07778em;\">cdf</span></span><span><span><span><span style=\"height:0.3281em;\"><span style=\"top:-2.357em;margin-right:0.0714em;\"><span style=\"height:2.5em;\"></span><span><span><span>min</span></span></span></span></span><span></span></span><span><span style=\"height:0.143em;\"><span></span></span></span></span></span></span></span></span></span></span><span></span></span><span><span style=\"height:0.4451em;\"><span></span></span></span></span></span><span></span></span><span style=\"margin-right:0.2222em;\"></span><span>×</span><span style=\"margin-right:0.2222em;\"></span></span><span><span style=\"height:1em;vertical-align:-0.25em;\"></span><span>(</span><span>L</span><span style=\"margin-right:0.2222em;\"></span><span>−</span><span style=\"margin-right:0.2222em;\"></span></span><span><span style=\"height:1em;vertical-align:-0.25em;\"></span><span>1</span><span>))</span></span></span></span></li>\n<li>cdf<sub>min</sub>代表灰度最低像素的个数</li>\n<li>HxW为图像像素总数</li>\n<li>L为灰度级,通常为256</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"python实现\"> python实现</h2>\n<div><pre><code>TODO\n</code></pre>\n<div><span>1</span><br></div></div>",
"date_published": "2021-12-05T00:00:00.000Z",
"date_modified": "2022-04-02T08:34:13.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": [
"CV"
]
},
{
"title": "Hazel",
"url": "https://kigane.github.io/blog/hazel/",
"id": "https://kigane.github.io/blog/hazel/",
"content_html": "<h2 id=\"eventsystem\"> EventSystem</h2>\n<ul>\n<li>EntryPoint.h -- Game Main Loop\n<ul>\n<li>app = CreateApplicaiton();</li>\n<li>app->run();</li>\n<li>delete app;</li>\n</ul>\n</li>\n<li>Application.h\n<ul>\n<li>Application(name)\n<ul>\n<li>创建窗口,设置回调</li>\n<li>渲染初始化</li>\n<li>创建ImGui层</li>\n</ul>\n</li>\n<li>OnEvent(e)\n<ul>\n<li>Dispatch Event=>function</li>\n<li>Layer->OnEvent(e) 传播机制</li>\n</ul>\n</li>\n<li>Run() 更新Layers和Window</li>\n</ul>\n</li>\n<li>Event.h\n<ul>\n<li>创建事件 Event</li>\n<li>分发事件 EventDispatcher</li>\n<li>具体事件继承Event</li>\n</ul>\n</li>\n</ul>\n<p>一个窗口事件发生,有glfwWindow捕获,产生glfw事件。通过在创建窗口时,设置的各种事件回调函数(glfwSetWindowSizeCallback(window,callback)),将glfw事件转化为Hazel事件,并用Application::OnEvent回调函数处理。</p>\n<h2 id=\"precompiled-header\"> precompiled header</h2>\n<p>当项目中cpp文件多了以后,每个cpp文件编译时都需要解析其include的头文件,其中有些头文件可能被多个cpp文件所引用,但每个编译单元(cpp文件)都要独立解析各自的头文件,效率比较低。<br>\n因此,对于这些常用的,通常不会有修改的头文件(例如,iostream,vector,string....),可以预先编译好,变成一个二进制文件。在后续cpp文件编译,链接时,可以直接查找这个编译好的二进制文件,效率高。</p>\n<p>怎么做?</p>\n<ul>\n<li>建立一个pch.h头文件,将需要预编译的头文件包含进来</li>\n<li>MSVC需要建立一个pch.cpp,内容仅需include "pch.h"即可。</li>\n<li>VS2019相关设置\n<ul>\n<li>C/C++->precompiled headers->Precompiled Header 设为 Use</li>\n<li>C/C++->precompiled headers->Precompiled Header File 设为 pch.h</li>\n</ul>\n</li>\n<li>premake设置(proejct下)\n<ul>\n<li>pchheader "pch.h"</li>\n<li>pchsource "src/pch.cpp"</li>\n</ul>\n</li>\n</ul>\n<p>PS: 如果pch.h中的头文件有任何一个改动,整个pch.h都需要重新编译。所以,不要将可能经常改动的头文件放入预编译头。</p>\n<h2 id=\"layers\"> Layers</h2>\n<ul>\n<li>Layer & LayerStack</li>\n<li>LayerStack是Layer的wrapper(一个vector<Layer>),主要用于设定渲染顺序,以及事件的传播顺序(和渲染顺序相反),将不同Layer的渲染和事件处理隔离开。</li>\n<li>OnUpdate() 在每一帧渲染时调用</li>\n<li>OnEvent() 在反向传播事件</li>\n</ul>\n",
"date_published": "2021-12-13T00:00:00.000Z",
"date_modified": "2021-12-13T15:53:11.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": [
"C/C++"
]
},
{
"title": "游戏引擎总览",
"url": "https://kigane.github.io/blog/game-engine-overview/",
"id": "https://kigane.github.io/blog/game-engine-overview/",
"content_html": "<h2 id=\"典型的游戏团队结构\"> 典型的游戏团队结构</h2>\n<ul>\n<li>engineers\n<ul>\n<li>runtime programmer: engine&game</li>\n<li>tools programmer: offline tools for whole team</li>\n</ul>\n</li>\n<li>artists\n<ul>\n<li>Concept artists: 概念设计</li>\n<li>3D modelers: 建模师\n<ul>\n<li>foreground modelers: 人物,武器,交通工具等</li>\n<li>background modelers: 静态背景几何体,如高楼,桥等</li>\n</ul>\n</li>\n<li>Texture artists: 纹理艺术家</li>\n<li>Lighting artists: 光照艺术家,调整光照以最大化艺术和情感上的冲击</li>\n<li>Animators: 动画师</li>\n<li>Motion capture actors: 动捕演员,提供粗略的运动数据供动画师整理和调整</li>\n<li>Sound designers: 将音效和音乐混入游戏</li>\n<li>Voice actors: 角色声优</li>\n<li>composers: 作曲家,负责配乐</li>\n</ul>\n</li>\n<li>game designers: gameplay 设计师,负责用户体验部分\n<ul>\n<li>宏观层面: 故事走向,人物和关卡的安排,玩家的宏观的目标和终点</li>\n<li>微观层面: 场景设计,关卡设计,谜题设计</li>\n<li>writers: 编剧</li>\n</ul>\n</li>\n<li>producers: 在不同工作室中的职能不尽相同\n<ul>\n<li>有些负责日程安排和人力资源管理</li>\n<li>有些负责游戏设计</li>\n<li>也可能会负责团队之间(如开发部门和商业部门)的联系</li>\n<li>小工作室可能不需要</li>\n</ul>\n</li>\n<li>management&support staff\n<ul>\n<li>执行管理</li>\n<li>市场</li>\n<li>行政</li>\n<li>IT</li>\n</ul>\n</li>\n<li>Publisher and Studio\n<ul>\n<li>发行商:负责游戏的营销,生产和发行,如EA,Nitendo, Sony等</li>\n<li>游戏工作室\n<ul>\n<li>独立的,不依附于发行商,做好游戏后交给发行商发行,价格详谈。</li>\n<li>依附于发行商的</li>\n<li>第一方的:为特定平台开发游戏,如Naughty Dog 是 Sony 的第一方工作室。</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"什么是游戏\"> 什么是游戏?</h2>\n<p>Koster断言,游戏“乐趣”的核心在于“学习(learning)”和“掌握(mastering)”,就像笑话只有在你“懂了(get-it)”的时候才有趣。</p>\n<h3 id=\"soft-real-time-interactive-agent-based-computer-simulations\"> soft real-time interactive agent-based computer simulations.</h3>\n<ul>\n<li>simulations: 对现实世界(即使是想象中的现实)的近似和简化。</li>\n<li>agent-based: 有大量不同的实体(entity/agent)交互</li>\n<li>interactive -- interactive temporal simulations: 交互式时间模拟。游戏状态随着时间的变化而不断改变,并对无法预测的输入作出响应。</li>\n<li>real-time: 所有实时系统的核心都是"deadline"。例如\n<ul>\n<li>大多数游戏渲染的帧率为30/60帧每秒。(NTSC指定的显示器刷新率为29.97帧每秒)</li>\n<li>电影的帧率至少为24帧每秒。(使用3:2 pulldown,即让两个电影帧,一帧占3个视频帧,另一帧占2个视频帧)</li>\n<li>物理模拟要保持稳定,一秒要计算120次。</li>\n<li>角色AI至少一秒运行一次</li>\n<li>音频也要每1/60 s调用一次以保持audio buffer充满,防止听觉故障。</li>\n</ul>\n</li>\n<li>soft: 表示即使错过了deadline也不会带来灾难性的后果。与之相反的是hard real-time system ,如直升机的航空电子系统,核电站的控制杆系统。一旦错过deadline,操作者可能会死。</li>\n</ul>\n<h3 id=\"游戏中的数学模型\"> 游戏中的数学模型</h3>\n<p>分析型:有闭式公式。可以求得任意时刻的状态。只能解决一小部分问题。<br>\n数值型:通过不断求导等方法,估计离散点的数值。</p>\n<p>通常在一个游戏循环中,每次迭代都有机会进行物理系统,AI,游戏逻辑的计算,最后根据计算结果进行渲染。</p>\n<h2 id=\"什么是游戏引擎\"> 什么是游戏引擎?</h2>\n<p><img src=\"/assets/img/game-engine-reusability-gamut.png\" alt=\""game-engine-reusability-gamut"\" /></p>\n<p>游戏引擎与游戏的区别在于数据驱动的架构。固定的部分越多,就越难修改为其他的游戏。就越接近游戏而非游戏引擎。\n另外,一个游戏引擎或中间件越泛用,对于特定的游戏或平台,其最优性就越低。这很容易理解,每个软件在设计的时候都需要权衡(trade-off),权衡的基准就是软件将会如何被使用或将在哪个平台运行。\n即使现在游戏引擎越来越强大,一般性与最优性的权衡仍然存在。所以顶尖的游戏工作室都会使用自研引擎。</p>\n<h2 id=\"不同类型游戏的游戏引擎的区别\"> 不同类型游戏的游戏引擎的区别</h2>\n<h3 id=\"fps\"> FPS</h3>\n<p>通常FPS游戏目标是给玩家沉浸在充满细节,高度写实的世界中的感觉。是所有游戏类型中技术挑战最大的。毫不意外的是,游戏行业的大型技术创新都是出于FPS游戏。</p>\n<p>FPS游戏需要的典型技术有</p>\n<ul>\n<li>高效渲染大型3D虚拟世界</li>\n<li>响应式的镜头控制和瞄准机制</li>\n<li>高保真的人物肢体动画和武器动画</li>\n<li>大量可持有的武器</li>\n<li>人物移动和碰撞模型(可能会产生漂浮感)</li>\n<li>NPC的AI和动画</li>\n<li>小规模多人对战,死亡竞技模式</li>\n</ul>\n<p>FPS游戏中的渲染技术需要高度优化且根据所处环境仔细调整。例如室内和室外环境,渲染技术大不相同,室外渲染优化通常需要遮挡剔除,离线游戏世界区域化(即手动或自动指定从源区域可以看到哪些目标区域)。</p>\n<p>当然,为了沉浸感,需要的远不止高质量的图像优化技术,角色动画,音效,音乐,刚体物理,游戏运镜等等都要使用非常前沿的技术。</p>\n<h3 id=\"平台游戏和其他第三人称游戏\"> 平台游戏和其他第三人称游戏</h3>\n<p>与FPS相比,更重视人物整体的动画,而非漂浮的手(洗手液战神Eason🤣)。主角也通常是更偏卡通风格,分辨率不用太高类人型角色,有丰富的动作和动画。</p>\n<p>需要的典型技术有</p>\n<ul>\n<li>移动的平台,梯子,绳子,齿轮和其他有趣的移动方式</li>\n<li>带有谜题性质的环境元素</li>\n<li>相机跟随玩家,且视角可变</li>\n<li>复杂的相机碰撞系统,保证视点不会卡进背景几何体或动态前景物体。</li>\n</ul>\n<h3 id=\"格斗游戏\"> 格斗游戏</h3>\n<p>技术要点</p>\n<ul>\n<li>丰富的战斗动画</li>\n<li>精确的击打检测(hit detection)</li>\n<li>检测用户输入的复杂按键</li>\n<li>相对静态的背景人群</li>\n</ul>\n<p>可以用到更高技术水平的特色有</p>\n<ul>\n<li>高清人物</li>\n<li>真实的皮肤渲染,表面散射和流汗效果</li>\n<li>photo-realistic lighting 和粒子效果</li>\n<li>高保真角色动画</li>\n<li>基于物理的衣服效果,头发模拟</li>\n</ul>\n<h3 id=\"竞速游戏\"> 竞速游戏</h3>\n<p>典型技术</p>\n<ul>\n<li>大量用于处理远景的"技巧",如用远处的山,树用二维卡片代替</li>\n<li>赛道通常会分解为二维的区域(sector)以帮助决定可见区域和AI的自动寻路</li>\n<li>相机通常处在载具后或驾驶座</li>\n<li>当赛道进入非常狭小的空间时,需要作出很大的努力来保证相机不与背景几何体碰撞</li>\n</ul>\n<h3 id=\"策略游戏\"> 策略游戏</h3>\n<p>通常视角固定,对与渲染优化是个好消息。世界通常是基于网格构建的。</p>\n<p>典型技术</p>\n<ul>\n<li>同屏可显示大量单位,从而分辨率会相当低</li>\n<li>游戏设计和游玩的画布通常是Height-field terrain 😕</li>\n<li>玩家通常可以在领土中建造建筑和军队</li>\n<li>单位的选取方式要多样</li>\n</ul>\n<h3 id=\"mmog\"> MMOG</h3>\n<p>服务器。</p>\n<h3 id=\"virtualreality-augumentedr-mixedr\"> VirtualReality,AugumentedR,MixedR</h3>\n<p>VR的难点</p>\n<ul>\n<li>立体渲染,同一帧画面要渲染两次(左右眼)</li>\n<li>需要高帧率,至少90帧,否则会引发人体不适</li>\n<li>导航问题,在虚拟现实世界中如何移动?原地踏步?WSAD?手柄?</li>\n</ul>\n<p>其他略</p>\n<h2 id=\"有名的游戏引擎\"> 有名的游戏引擎</h2>\n<p>Quake/Quake2\n<a href=\"https://github.com/id-Software/Quake-2\" target=\"_blank\" rel=\"noopener noreferrer\">Quake2 源码</a><br>\n架构合理且干净,但可能有点过时,且几乎是用纯C语言写的。\n如果有Quake2游戏,非常推荐下载源码来跑跑,设置断点来分析引擎如何工作。</p>\n<p>Unreal Engine\n开放,开源。不多说。</p>\n<p>Source Engine\nHalf-Life系列使用的引擎。</p>\n<p>DICE的Frostbite引擎\n质量效应,星球大战:前线2,龙腾世纪,极品飞车(Need for Speed)系列</p>\n<p>Rockstar Advanced Game Engine(RAGE)\n给他爱系列。荒野大镖客系列。</p>\n<p>CryEngine\nFar Cry等</p>\n<p>Sony的PhyreEngine</p>\n<p>微软的 XNA Game Studio,2014年没了</p>\n<p>Unity\nUnity的设计初衷是简化开发和跨平台。\n代表作杀出重围,空洞骑士,茶杯头。</p>\n<h2 id=\"开源引擎列表\"> 开源引擎列表</h2>\n<ul>\n<li><a href=\"https://github.com/id-Software/Quake-2\" target=\"_blank\" rel=\"noopener noreferrer\">Quake2</a></li>\n<li><a href=\"https://www.ogre3d.org/\" target=\"_blank\" rel=\"noopener noreferrer\">OGRE 一个架构好,易学易用的3D渲染引擎</a></li>\n<li><a href=\"https://www.panda3d.org/\" target=\"_blank\" rel=\"noopener noreferrer\">Panda3D a script-based engine,设计用于快速方便的制作3D游戏原型和虚拟世界</a></li>\n<li>Crystal Space is a game engine with an extensible modular architecture.</li>\n</ul>\n<h2 id=\"运行时引擎架构\"> 运行时引擎架构</h2>\n<p>游戏引擎是个巨大的软件系统。通常是一层层(layers)构建的,且上层依赖于下层。</p>\n<p><img src=\"/assets/img/game-engine-archtecture.png\" alt=\"game-engine-archtecture\" /></p>\n<h3 id=\"目标硬件\"> 目标硬件</h3>\n<p>Windows,Linux,MacOS-PC,移动平台,PSV,PS4,PS5,Switch,Wii,NDS,XBOX等等。</p>\n<h3 id=\"设备驱动\"> 设备驱动</h3>\n<p>管理硬件资源并为操作系统和引擎上层屏蔽无数的硬件设备细节。</p>\n<h3 id=\"操作系统\"> 操作系统</h3>\n<p>操作系统上通常运行着多个程序(分时抢占式多任务),这意味着PC游戏永远无法假设它能完全控制硬件,故必须保证它能与系统中的其他程序一起“play nice”。\n现在的游戏机上也有操作系统了。开发逐渐趋近于PC。</p>\n<h3 id=\"第三方sdk-software-development-kits-和中间件-middleware\"> 第三方SDK(software development kits)和中间件(Middleware)</h3>\n<p>大多数引擎都利用了大量第三方SDK和中间件,SDK提供的功能或类接口经常被叫做API。</p>\n<h4 id=\"数据结构和算法\"> 数据结构和算法</h4>\n<p>任何软件的开发都离不开容器和算法。常用的第三方库有</p>\n<ul>\n<li><a href=\"https://www.boost.org/\" target=\"_blank\" rel=\"noopener noreferrer\">Boost</a></li>\n<li>Folly:扩展C++标准库和Boost,注重最大化代码性能</li>\n<li>Loki:强大的泛型模板编程库</li>\n</ul>\n<h4 id=\"图形\"> 图形</h4>\n<ul>\n<li>OpenGL</li>\n<li>DirectX</li>\n<li>Vulkan</li>\n<li>libgcm</li>\n<li>...</li>\n</ul>\n<h4 id=\"碰撞和物理\"> 碰撞和物理</h4>\n<ul>\n<li>Havok</li>\n<li>PhysX</li>\n<li>ODE(Open Dynamic Engine)</li>\n</ul>\n<h4 id=\"角色动画\"> 角色动画</h4>\n<ul>\n<li>Granny:作者认为设计最好,API也最符合逻辑的库。处理速度也快。</li>\n<li>Havok Animation</li>\n<li>OrbisAnim</li>\n<li>Endorphin and Euphoria:使用真实的人类移动生物力学模型来模拟人物移动</li>\n</ul>\n<h3 id=\"平台无关层\"> 平台无关层</h3>\n<p>将更底层的API封装一层成为你的游戏引擎更上层使用的API。以屏蔽平台底层的差异。另外,以后要将某个功能的实现库换了,如物理/碰撞检测的库,上层的代码也不用动。</p>\n<h3 id=\"核心系统\"> 核心系统</h3>\n<p>游戏引擎是一个巨大且复杂的C++应用,需要一系列有用的软件工具,我们将其分类为核心系统(core system)。</p>\n<p>典型的核心系统层见架构图。</p>\n<p>核心系统层通常提供的工具,例如</p>\n<ul>\n<li>断言(Assertion):用于检查代码的逻辑错误和违反程序员意图的行为。通常不会出现在最终的游戏产品中。</li>\n<li>内存管理(Memeory management):几乎每个游戏引擎都会实现自身的内存分配系统以保证高速内存分配和限制内存碎片的副作用。</li>\n<li>数学库:通常需要提供高效的向量和矩阵运算,四元数旋转,三角学,直线的集合操作,光线,球体等,齿轮操作(spline manipulation),数值计算,解方程组等等。</li>\n<li>自定义数据结构和算法:主要是为了最小化或消除动态内存分配并保证在目标平台上的最优运行时性能。</li>\n</ul>\n<h3 id=\"资源管理器\"> 资源管理器</h3>\n<p>为获取所有类型的游戏资源(game assets)和游戏入数据提供统一的接口。有些引擎以高度中心化和一致的方式管理资源,如Unreal的package,OGRE和ResourceManager类。其他一些引擎采取一种临时的方式 ,将资源的处理留给游戏程序员,让程序员可以直接访问硬盘上的原文件或压缩包中的文件,如Quake的PAK文件。典型结构见架构图。</p>\n<h3 id=\"渲染引擎\"> 渲染引擎</h3>\n<p>游戏引擎中最大,最复杂的组件。渲染器的架构方法多种多样,且目前没有统一的标准。</p>\n<h4 id=\"底层渲染器-low-level-renderer\"> 底层渲染器(low-level renderer)</h4>\n<p>包含所有引擎的所有原始渲染能力。在这一层,设计目标主要是尽可能快地渲染一系列几何图元,不考虑其在场景中是否可见。这个组件可以细分为大量子组件,例如</p>\n<ul>\n<li>图形设备接口(Graphics Device Interface):图形SDK,如DirectX,OpenGL,Vulkan等的使用都需要大量的代码,包括初始化,资源获取和绑定等等。典型的用于处理这些的组件称为GDI(每个引擎的术语可能不同)。</li>\n<li>其他:光照,纹理,视口,几何图元的提交,材质,相机等等。</li>\n</ul>\n<h4 id=\"剔除优化-culling-optimization-场景图-scene-graph\"> 剔除优化(Culling Optimization)/场景图(Scene Graph)</h4>\n<p>用于根据某种可见性判断标准来限制需要渲染的图元数量。对于小型的世界,最简单的frustum cull,即将相机视锥外的图形剔除。对大型的世界,需要更高级的数据结构spatial subdivision来提高渲染效率,通过spatial subdivision可以非常快速的确定潜在可见物体集(the Potentila Visiable Set of Object, PVS)。</p>\n",
"image": "https://kigane.github.io/assets/img/game-engine-reusability-gamut.png",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "katex 常用写法",
"url": "https://kigane.github.io/blog/katex-cheatsheet/",
"id": "https://kigane.github.io/blog/katex-cheatsheet/",
"content_html": "<h2 id=\"常用符号\"> 常用符号</h2>\n<table>\n<thead>\n<tr>\n<th style=\"text-align:center\">符号</th>\n<th style=\"text-align:center\">写法</th>\n<th style=\"text-align:center\">符号</th>\n<th style=\"text-align:center\">写法</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style=\"text-align:center\"><span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:0.8889em;vertical-align:-0.1944em;\"></span><span>ϕ</span></span></span></span></td>\n<td style=\"text-align:center\">\\phi</td>\n<td style=\"text-align:center\"><span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:0.6833em;\"></span><span>Φ</span></span></span></span></td>\n<td style=\"text-align:center\">\\Phi</td>\n</tr>\n<tr>\n<td style=\"text-align:center\"><span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:0.4306em;\"></span><span style=\"margin-right:0.03588em;\">ω</span></span></span></span></td>\n<td style=\"text-align:center\">\\omega</td>\n<td style=\"text-align:center\"><span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:0.6833em;\"></span><span>Ω</span></span></span></span></td>\n<td style=\"text-align:center\">\\Omega</td>\n</tr>\n</tbody>\n</table>\n<h2 id=\"环境\"> 环境</h2>\n<p><img src=\"/assets/img/katex-environments.png\" alt=\"环境\" /></p>\n<p>多行公式在vuepress中无效,使用aligned环境替代。</p>\n",
"image": "https://kigane.github.io/assets/img/katex-environments.png",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "PR剪辑",
"url": "https://kigane.github.io/blog/premiere/",
"id": "https://kigane.github.io/blog/premiere/",
"content_html": "<h2 id=\"基础操作\"> 基础操作</h2>\n<table>\n<thead>\n<tr>\n<th>操作</th>\n<th>快捷键</th>\n<th>说明</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>新建文本图层</td>\n<td>ctrl+T</td>\n<td></td>\n</tr>\n<tr>\n<td>不启用</td>\n<td>shitf+E</td>\n<td></td>\n</tr>\n<tr>\n<td>打标志</td>\n<td>M</td>\n<td>选中素材就在素材上打,不选中则在时间轴上打</td>\n</tr>\n<tr>\n<td>标记一段时间</td>\n<td>Alt+拖动标志</td>\n<td></td>\n</tr>\n<tr>\n<td>移动图层</td>\n<td>Alt+up/down</td>\n<td></td>\n</tr>\n<tr>\n<td>播放速度</td>\n<td>j,k,l</td>\n<td>J后退(连按速率加倍)、K暂停播放,L前进(连按速率加倍), J+K 减速后退, K+L减速前进</td>\n</tr>\n<tr>\n<td>音频波峰放大</td>\n<td>Alt+滚轮</td>\n<td>鼠标悬停于音频轨道(左侧控制栏) 用Alt+滚轮可放大,缩小</td>\n</tr>\n<tr>\n<td>逐帧微调</td>\n<td>left,right</td>\n<td>按住Shift则一次5帧</td>\n</tr>\n<tr>\n<td>删除素材和间隙</td>\n<td>Shift+del</td>\n<td></td>\n</tr>\n<tr>\n<td>相机视角</td>\n<td>0</td>\n<td></td>\n</tr>\n</tbody>\n</table>\n",
"date_published": "2022-02-04T00:00:00.000Z",
"date_modified": "2022-02-19T05:19:13.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": [
"Blog"
]
},
{
"title": "随机数",
"url": "https://kigane.github.io/blog/random/",
"id": "https://kigane.github.io/blog/random/",
"content_html": "<h2 id=\"固定种子\"> 固定种子</h2>\n<p>np.random.seed(seed)</p>\n<h2 id=\"均匀分布的随机数\"> 均匀分布的随机数</h2>\n<h3 id=\"_0-1-内\"> [0, 1)内</h3>\n<div><pre><code><span># random.rand(d0, d1, ..., dn)</span>\nnp<span>.</span>random<span>.</span>rand<span>(</span><span>)</span> <span># 返回单个随机数</span>\nnp<span>.</span>random<span>.</span>rand<span>(</span><span>3</span><span>,</span> <span>2</span><span>)</span> <span># 返回(3, 2)的随机数数组</span>\n<span>(</span>b <span>-</span> a<span>)</span> <span>*</span> np<span>.</span>random<span>.</span>rand<span>(</span><span>)</span> <span>+</span> a <span># [a, b)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br></div></div><h3 id=\"指定范围内的离散的\"> 指定范围内的离散的</h3>\n<div><pre><code><span># random.randint(low, high=None, size=None, dtype=int)</span>\nnp<span>.</span>random<span>.</span>randint<span>(</span><span>1</span><span>,</span> <span>7</span><span>,</span> <span>(</span><span>6</span><span>,</span><span>)</span><span>)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br></div></div><h2 id=\"高斯分布的随机数\"> 高斯分布的随机数</h2>\n<div><pre><code><span># random.randn(d0, d1, ..., dn)</span>\nnp<span>.</span>random<span>.</span>randn<span>(</span><span>)</span>\nnp<span>.</span>random<span>.</span>randn<span>(</span><span>3</span><span>,</span> <span>2</span><span>)</span>\nmu <span>+</span> sigma <span>*</span> np<span>.</span>random<span>.</span>randn<span>(</span><span>3</span><span>,</span> <span>2</span><span>)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br></div></div><h2 id=\"其他分布\"> 其他分布</h2>\n<p>np.random.xx</p>\n<ul>\n<li>chisquare</li>\n<li>gamma</li>\n<li></li>\n</ul>\n",
"date_published": "2021-12-05T00:00:00.000Z",
"date_modified": "2021-12-13T15:53:11.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": [
"blog"
]
},
{
"title": "日语基础语法",
"url": "https://kigane.github.io/blog/japanese/",
"id": "https://kigane.github.io/blog/japanese/",
"content_html": "<h2 id=\"基本语法\"> 基本语法</h2>\n<h3 id=\"状态表示\"> 状态表示</h3>\n<ul>\n<li>某物的状态,使用「だ」,このDIOだ</li>\n<li>否定状态,使用「じゃない」</li>\n<li>曾经是,过去状态,使用「だった」</li>\n<li>否定过去形,使用 「じゃなかった」</li>\n</ul>\n<h3 id=\"助词\"> 助词</h3>\n<ul>\n<li>主题助词,写作「は」,读作wa。指出了所谈论的对象,也就是句子的主题。</li>\n<li>包含主题助词,「も」。就是一个带了「也」意思的主题助词。需要注意一致性。如俺は学生,JoJoも学生。不一致则可以用一个新句子,俺は学生,DIOは吸血鬼。</li>\n<li>识别助词/主语助词,。表明了说话的人想要认清楚的东西。</li>\n</ul>\n<div><p>辨析</p>\n<p>私は学生 (至于)我,是一个学生。<br>\n私が学生 我是(你要找的那个)学生。\n主题助词可以翻译成「对于,至于」(as for; about),把识别助词可以翻译成「那个」(the one; the thing)。</p>\n</div>\n<h3 id=\"形容词\"> 形容词</h3>\n<p>形容词可以直接放在名词前面,也可以像连接名词那样用助词来连接名词。形容词可以分为な形容词和い形容词两类。</p>\n<ul>\n<li>な形容词:和名词的活用规则是一样的,主要区别在于用な形容词修饰名词的时候中间要加一个「な」\n<ul>\n<li>静か 「しず・か」 安静</li>\n<li>きれい=「綺麗」 漂亮;干净</li>\n<li>好き 喜欢(招人喜欢的)</li>\n</ul>\n</li>\n</ul>\n<div><p>な形容词的活用</p>\n<ol>\n<li>魚が好き<strong>な</strong>人</li>\n<li>魚が好き<strong>じゃない</strong>人</li>\n<li>魚が好き<strong>だった</strong>人</li>\n<li>魚が好き<strong>じゃなかった</strong>人</li>\n</ol>\n</div>\n<ul>\n<li>い形容词:所有い形容词都是以平假名「い」结尾的。活用的时候改变「い」而汉字不动。\n<ul>\n<li>高い 高</li>\n<li>おいしい 好吃</li>\n<li>いい 好</li>\n</ul>\n</li>\n</ul>\n<div><p>以い结尾的な形容词和い形容词区分</p>\n<p>以い结尾的な形容词通常写成汉字,い在汉字中。以独立的い结尾的形容词,通常就是い形容词。<br>\n例外:「嫌い」是な形容词</p>\n</div>\n<div><p>い形容词的活用</p>\n<p>正常活用</p>\n<ol>\n<li>高<strong>い</strong>ビル</li>\n<li>高<strong>くない</strong>ビル</li>\n<li>高<strong>かった</strong>ビル</li>\n<li>高<strong>くなかった</strong>ビル</li>\n</ol>\n<p>特例:いい和かっこいい<br>\n<strong>「いい」</strong> 是从 **良い「よい」**演变而来。「いい」写成汉字时,又读成「よい」。活用时,非过去肯定型不变,其他都以「よい」为基础变化。</p>\n<ol>\n<li>いい</li>\n<li>よくない</li>\n<li>よかった</li>\n<li>よくなかった</li>\n</ol>\n</div>\n<h3 id=\"动词\"> 动词</h3>\n<p>日语里面的动词总是放在句尾。除了两个特例之外,所有动词可分类为る动词(一段动词)或者う动词(五段动词)。所有る动词以「る」结尾,而う动词则以包括「る」在内的多种う元音结尾。所以,一个不是以「る」结尾的动词肯定是う动词。对于以「る」结尾的动词,如果在「る」音之前的假名包含a、u或者o元音,那它就是う动词,而前面是i或者e元音的,那它大多数情况下就是る动词。</p>\n<div><p>特例</p>\n<p>「する」和「来る」既不是る动词也不是う动词。被称为サ变动词和カ变动词。</p>\n</div>\n<h3 id=\"动词的否定\"> 动词的否定</h3>\n<ul>\n<li>对る动词:把「る」去掉换成「ない」。例:食べる + ない = 食べない</li>\n<li>对以「う」结尾的う动词: 把「う」换成「わ」,再加上「ない」。例:買う + わ + ない = 買わない</li>\n<li>其他う动词:把末假名换成同行的あ段假名,再加上「ない」。例:待つ + た = 待たない</li>\n<li>例外:\n<ul>\n<li>する → しない</li>\n<li>くる → こない</li>\n<li>ある → ない</li>\n</ul>\n</li>\n</ul>\n<div><p>存在</p>\n<ul>\n<li>ある 无生命的东西存在</li>\n<li>いる 有生命的东西存在</li>\n</ul>\n</div>\n<h3 id=\"动词的过去形\"> 动词的过去形</h3>\n<ul>\n<li>る动词的过去形,把「る」变成「た」。</li>\n<li>う动词的过去形\n<img src=\"/assets/img/jp_verb_past.png\" alt=\"past\" /></li>\n</ul>\n<div><p>将动词活用为过去否定式</p>\n<p>将动词活用为否定式,然后把结尾的「い」换成「かった」<br>\n示例</p>\n<ol>\n<li>捨てる → 捨てない → 捨てなかった</li>\n<li>行く → 行かない → 行かなかった</li>\n</ol>\n</div>\n",
"image": "https://kigane.github.io/assets/img/jp_verb_past.png",
"date_published": "2022-01-16T00:00:00.000Z",
"date_modified": "2022-02-19T05:19:13.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": [
"Japanese"
]
},
{
"title": "黑魂复刻",
"url": "https://kigane.github.io/blog/unity/",
"id": "https://kigane.github.io/blog/unity/",
"content_html": "<h2 id=\"玩家输入\"> 玩家输入</h2>\n<h3 id=\"统一输入\"> 统一输入</h3>\n<p>为了统一键盘和手柄等输入,抽象出一个信号概念。</p>\n<ul>\n<li>Dup 上下信号</li>\n<li>Dright 左右信号</li>\n</ul>\n<h3 id=\"衰减\"> 衰减</h3>\n<p>Mathf.SmoothDamp(float current, float target, ref float currentVelocity, float smoothTime, float maxSpeed = Mathf.Infinity, float deltaTime = Time.deltaTime)</p>\n<ul>\n<li>current: 平滑的当前值</li>\n<li>target: 目标值</li>\n<li>smoothTime: 经过多长时间达到目标值</li>\n<li>currentVelocity:变化的速度,一阶导数</li>\n</ul>\n<h3 id=\"使能标志\"> 使能标志</h3>\n<p>控制模块的激活与禁用。在脚本内用一个bool变量控制。</p>\n<h3 id=\"_1d-blend-tree\"> 1D Blend Tree</h3>\n<ul>\n<li>在Animator中右键create->from blend tree,创建一个blend tree,双击进入。</li>\n<li>默认有一个参数,控制在两个动画之间的平滑变化。</li>\n<li>在Motion List中加入要混合的两个动画</li>\n<li>threshold表示变化的起止位置</li>\n<li>threshold旁边的参数为每个动画的播放速度</li>\n</ul>\n<h3 id=\"连接玩家输入和动画控制器\"> 连接玩家输入和动画控制器</h3>\n<p>anim.SetFloat("Forward", pi.Dup);</p>\n<h3 id=\"移动\"> 移动</h3>\n<ul>\n<li>使用Dup和Dright的和组成移动的方向movingVec,并用此值修改模型的朝向。</li>\n<li>使用Dup和Dright的和模控制移动速度。一个问题:斜向速度更快</li>\n<li>用rigid.velocity控制移动时,要注意movingVec没有y轴分量,需要使用原来的值。</li>\n<li>rigidbody要在FixedUpdate中更新</li>\n</ul>\n<h3 id=\"解决trigger累积的问题\"> 解决trigger累积的问题</h3>\n<p>在Animator中使用trigger会有一个问题,如果触发两次以上的SetTrigger,会消耗一个并累积一个,结果造成第二次过渡。</p>\n<p>解决方法是在状态上添加一个脚本,在OnStateEnter和OnStateExit上清除累积的trigger。</p>\n<p>为了尽量将功能放在顶级游戏对象的组件中,使用SendMessage将具体的处理逻辑延迟到接受Message的方法中。</p>\n<div><p>提示</p>\n<p>SendMessage():只发送到所在的GameObject\nSendMessageUpwards():发送到所在的GameObject和其所有的父对象。</p>\n</div>\n<h2 id=\"动画\"> 动画</h2>\n<ul>\n<li>make transition到子状态机,进入子状态机后,会转到子状态机的Entry。</li>\n<li>make transition到子状态机内的某个状态,则会从子状态机的(Up)XXLayer状态转过去。</li>\n<li>make transition到子状态机内的exit,意味着退出子状态机,转到上一层状态机的Entry。(可以减少一条线哦)</li>\n</ul>\n<h2 id=\"模型出现章鱼一样的情况\"> 模型出现章鱼一样的情况</h2>\n<ul>\n<li>avatar不对</li>\n<li>unity没有识别出模型中的一些额外的骨骼。解决方法是,将额外的骨骼塞到同级的骨骼下。</li>\n</ul>\n",
"date_published": "2022-01-24T00:00:00.000Z",
"date_modified": "2022-02-19T05:19:13.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": [
"GameEngine"
]
},
{
"title": "视觉计算基础-预备知识",
"url": "https://kigane.github.io/blog/visual_computing_intro/",
"id": "https://kigane.github.io/blog/visual_computing_intro/",
"content_html": "<h2 id=\"数据\"> 数据</h2>\n<h3 id=\"离散化\"> 离散化</h3>\n<ul>\n<li>样本:指一个连续函数f(t)在指定的t处的函数值(可以是标量或向量)</li>\n<li>采样过程:从一个连续信号中提取一个或多个样本的过程。将f(t)缩成一个离散函数<span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:1.2079em;vertical-align:-0.25em;\"></span><span><span><span><span style=\"height:0.9579em;\"><span style=\"top:-3em;\"><span style=\"height:3em;\"></span><span style=\"margin-right:0.10764em;\">f</span></span><span style=\"top:-3.2634em;\"><span style=\"height:3em;\"></span><span style=\"left:-0.0833em;\"><span>^</span></span></span></span><span></span></span><span><span style=\"height:0.1944em;\"><span></span></span></span></span></span><span>(</span><span>t</span><span>)</span></span></span></span></li>\n<li>均匀采样:自变量取值间距相等</li>\n<li>采样密度:自变量取值间距</li>\n<li>重建(Reconstruction):从离散函数<span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:1.2079em;vertical-align:-0.25em;\"></span><span><span><span><span style=\"height:0.9579em;\"><span style=\"top:-3em;\"><span style=\"height:3em;\"></span><span style=\"margin-right:0.10764em;\">f</span></span><span style=\"top:-3.2634em;\"><span style=\"height:3em;\"></span><span style=\"left:-0.0833em;\"><span>^</span></span></span></span><span></span></span><span><span style=\"height:0.1944em;\"><span></span></span></span></span></span><span>(</span><span>t</span><span>)</span></span></span></span>恢复f(t)</li>\n<li>混叠效应(Aliasing):重建得到的函数并不是原函数,这个错误函数称为别名(Alias),因为它是冒名顶替者。</li>\n<li>奈奎斯特采样率(Nyquist Sampling Rate):对频率为f的正弦或余弦波,要以两倍的频率即2f进行采样才能保证正确的重建。</li>\n<li>分解(Decomposition):傅里叶分解。=>一个普通型号的充分采样频率为其最高频率正弦或余弦分量的频率的两倍。</li>\n</ul>\n<h3 id=\"量化\"> 量化</h3>\n<ul>\n<li>量化(Quantization):一个范围内的模拟信号值被赋予同一个数字值。</li>\n<li>量化误差:信号的原始值和量化值之间的差异。</li>\n<li>均匀步长离散化:等间距地选取离散值,该间距即步长,每一个连续值被量化为与其最近的离散值。因此,最大量化误差等于步长的一半。</li>\n<li>史蒂芬幂定律(Steven's Power Law):对于输入I,其感知P满足方程<span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:0.6833em;\"></span><span style=\"margin-right:0.13889em;\">P</span><span style=\"margin-right:0.2778em;\"></span><span>∝</span><span style=\"margin-right:0.2778em;\"></span></span><span><span style=\"height:0.6833em;\"></span><span><span style=\"margin-right:0.07847em;\">I</span><span><span><span><span style=\"height:0.6644em;\"><span style=\"top:-3.063em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span style=\"margin-right:0.05556em;\">γ</span></span></span></span></span></span></span></span></span></span></span>\n<ul>\n<li>人类的感知大多数是亚线性的,即<span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:0.7335em;vertical-align:-0.1944em;\"></span><span style=\"margin-right:0.05556em;\">γ</span><span style=\"margin-right:0.2778em;\"></span><span><</span><span style=\"margin-right:0.2778em;\"></span></span><span><span style=\"height:0.6444em;\"></span><span>1</span></span></span></span>。</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"表示\"> 表示</h3>\n<ul>\n<li>\n<p>一般用<code>A(t)</code>表示模拟信号,用<code>A[t]</code>表示量化的信号。</p>\n</li>\n<li>\n<p>频域表示:根据采样频率确定一组基础信号频率,原信号可以表示成一组相对于这些基础信号的系数。</p>\n</li>\n<li>\n<p>网格(Mesh):一组几何实体的集合,可以表示另一个几何实体。</p>\n<ul>\n<li>三角网格:使用三角形定义一个三维物体</li>\n<li>基元(Primitives)构成网格的实体(如线,三角形,四边形)</li>\n</ul>\n</li>\n<li>\n<p>三角网格表示:包含两部分</p>\n<ul>\n<li>用三维坐标表示的一组顶点,定义了网格的几何属性</li>\n<li>三个顶点的编号表示一组三角形,定义了网格的拓扑属性。拓扑指在数据的几何属性变化过程中保持不变的关系。</li>\n</ul>\n</li>\n<li>\n<p>网格的属性</p>\n<ul>\n<li>流形:即封闭网格,每条边恰有两个入射三角形的网格。</li>\n<li>有边界的流形:每条边有一个或两个入射三角形的网格。</li>\n<li>非流形:边可以有超过两个入射三角形的网格。</li>\n<li>欧拉示性数(Euler Characteristic)😒 e=V-E+F $。其中V是顶点数,E是边数,F是面片数。拓扑属性。</li>\n<li>亏格(Genus):定义为网格的环柄数。</li>\n<li>有向性:如果从网格的正面可以走到其背面,这样的网格是无向的。如莫比乌斯带。</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"噪声\"> 噪声</h3>\n<p>数据中随机位置处增加的随机值。</p>\n<ul>\n<li>随机噪音:在数据中的某些位置增加一些小的随机值。可用低通滤波去除。</li>\n<li>异常值。如椒盐噪音:传感器失效导致某些像素全黑或全白。可用中值滤波或秩统计滤波器去除。</li>\n<li>一些噪音在空域看起来是随机的,但在频域中却可以隔离为少数几个频率。可用频域中的陷波滤波器去除。</li>\n</ul>\n<h2 id=\"技术\"> 技术</h2>\n",
"date_published": "2022-02-21T00:00:00.000Z",
"date_modified": "2022-04-02T08:34:13.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": [
"CV"
]
},
{
"title": "VuePress 默认主题设置",
"url": "https://kigane.github.io/guide/configuration/",
"id": "https://kigane.github.io/guide/configuration/",
"content_html": "<h2 id=\"frontmatter\"> frontmatter</h2>\n<p>必须在每个 .md 文件开头,通常以 yaml 格式表示。</p>\n<div><pre><code><span>---</span>\n<span>title</span><span>:</span> hello vuepress\n<span>lang</span><span>:</span> zh<span>-</span>CN\n\n<span>sidebarDepth</span><span>:</span> <span>2</span> \n<span>navbar</span><span>:</span> <span>false</span> \n<span>sidebar</span><span>:</span> <span>false</span>\n<span>search</span><span>:</span> <span>false</span>\n<span>tags</span><span>:</span>\n <span>-</span> configuration\n <span>-</span> theme\n <span>-</span> indexing\n<span>prev</span><span>:</span> ./some<span>-</span>other<span>-</span>page\n<span>next</span><span>:</span> <span>false</span>\n<span>pageClass</span><span>:</span> custom<span>-</span>page<span>-</span>class\n<span>layout</span><span>:</span> SpecialLayout\n<span>---</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br></div></div><ul>\n<li>title 会自动作为 sidebar 的一级链接标题(优先级低于在 config.js 中配置)。</li>\n<li>sidebarDepth 见下文内嵌链接</li>\n<li>navbar 用于在本页面关闭导航栏</li>\n<li>sidebar 用于在本页面关闭侧边栏</li>\n<li>search 用于在本页面关闭搜索框</li>\n<li>tag 用于搜索(内置的搜索只搜索文章的 h2, h3 和 frontmatter 中设置的 tags)</li>\n<li>prev 文章底部,上一篇文章的链接</li>\n<li>next 文章底部,下一篇文章的链接</li>\n<li>pageClass 为这一页自定义一个 class,用于在 .vuepress/styles/index.styl 添加自定义 css</li>\n<li>layout 通常每个 .md 文件内容都是在容器 <code><div class="page"></code> 中渲染的,包含侧边栏,上一篇,下一篇链接等。使用这个选项,可以设置使用特定的 vue 组件渲染该 .md 文件。(.vuepress/components/SpecialLayout.vue)</li>\n</ul>\n<h2 id=\"navbar\"> navbar</h2>\n<p>外部链接自动获得两个属性 target="_blank" & rel="noopener noreferrer"。你也可以自定义。</p>\n<ul>\n<li>text 是显示的标题</li>\n<li>ariaLabel 不知道有什么用</li>\n<li>link 当然就是链接啦</li>\n<li>items 用于套娃,最多两层(第一层有下拉菜单,第二层之间有分割线)。</li>\n</ul>\n<div><div><br><br><br><br><br><br><div> </div><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br></div><pre><code>themeConfig<span>:</span> <span>{</span>\n logo<span>:</span> <span>'/assets/img/ChernoLogo.png'</span><span>,</span> <span>// 左上角的图标</span>\n <span>// 导航栏</span>\n nav<span>:</span> <span>[</span>\n <span>{</span> text<span>:</span> <span>'Home'</span><span>,</span> link<span>:</span> <span>'/'</span> <span>}</span><span>,</span>\n <span>{</span> text<span>:</span> <span>'Guide'</span><span>,</span> link<span>:</span> <span>'/guide/'</span> <span>}</span><span>,</span>\n <span>{</span> text<span>:</span> <span>'External'</span><span>,</span> link<span>:</span> <span>'https://google.com'</span> <span>,</span> target<span>:</span><span>'_self'</span><span>,</span> rel<span>:</span><span>false</span> <span>}</span><span>,</span>\n <span>{</span>\n text<span>:</span> <span>'Languages'</span><span>,</span>\n ariaLabel<span>:</span> <span>'Language Menu'</span><span>,</span>\n items<span>:</span> <span>[</span> <span>// 还可以继续套娃</span>\n <span>{</span>\n text<span>:</span> <span>'Chinese'</span><span>,</span>\n ariaLabel<span>:</span> <span>'Chinese Menu'</span><span>,</span> <span>// 没啥用啊,这属性</span>\n items<span>:</span> <span>[</span>\n <span>{</span> text<span>:</span> <span>'ah'</span><span>,</span> link<span>:</span> <span>'https://google.com'</span> <span>}</span><span>,</span>\n <span>{</span> text<span>:</span> <span>'nj'</span><span>,</span> link<span>:</span> <span>'https://google.com'</span> <span>}</span><span>,</span>\n <span>]</span>\n <span>}</span><span>,</span>\n <span>{</span>\n text<span>:</span> <span>'English'</span><span>,</span>\n ariaLabel<span>:</span> <span>'English Menu'</span><span>,</span> <span>// 没啥用啊,这属性</span>\n items<span>:</span> <span>[</span>\n <span>{</span> text<span>:</span> <span>'sf'</span><span>,</span> link<span>:</span> <span>'https://google.com'</span> <span>}</span><span>,</span>\n <span>{</span> text<span>:</span> <span>'ny'</span><span>,</span> link<span>:</span> <span>'https://google.com'</span> <span>}</span><span>,</span>\n <span>]</span>\n <span>}</span><span>,</span>\n <span>]</span>\n <span>}</span>\n <span>]</span><span>,</span>\n\n</code></pre><div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br><span>25</span><br><span>26</span><br><span>27</span><br><span>28</span><br><span>29</span><br><span>30</span><br><span>31</span><br></div></div><h2 id=\"sidebar\"> sidebar</h2>\n<div><pre><code><span>// .vuepress/config.js</span>\nmodule<span>.</span>exports <span>=</span> <span>{</span>\n themeConfig<span>:</span> <span>{</span>\n sidebar<span>:</span> <span>[</span>\n <span>'/'</span><span>,</span>\n <span>'/page-a'</span><span>,</span>\n <span>[</span><span>'/page-b'</span><span>,</span> <span>'显示指定链接标题'</span><span>]</span>\n <span>]</span>\n <span>}</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br></div></div><p>指定路径时,可以忽略.md后缀,以'path/'结尾的路径,对应的内容为 path/README.md。如果需要显示指定标题,则如例子中的第三行。</p>\n<h3 id=\"内嵌链接\"> 内嵌链接</h3>\n<p>默认抓取 Markdown 文档的所有二级标题作为当前链接的子链接。对应的设置为 themeConfig.sidebarDepth = 1。</p>\n<p>sidebarDepth = 0 表示没有子链接。\nsidebarDepth = 2 会抓取h2,h3作为子链接。最大层次就是2。效果见本文。</p>\n<p>这个设置除了在配置中改,也可以在 .md 的 frontmatter 部分改。</p>\n<div><pre><code><span>---</span>\n<span>sidebarDepth</span><span>:</span> <span>0</span>\n<span>---</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br></div></div><h3 id=\"展开所有子链接\"> 展开所有子链接</h3>\n<div><pre><code><span>// .vuepress/config.js</span>\nmodule<span>.</span>exports <span>=</span> <span>{</span>\n themeConfig<span>:</span> <span>{</span>\n displayAllHeaders<span>:</span> <span>true</span> <span>// Default: false</span>\n <span>}</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br></div></div><h3 id=\"导航栏分组\"> 导航栏分组</h3>\n<p>顾名思义</p>\n<div><pre><code>sidebar<span>:</span> <span>[</span>\n <span>{</span>\n title<span>:</span> <span>'Group1'</span><span>,</span> <span>// 必须要有分组名</span>\n collapsable<span>:</span> <span>false</span><span>,</span> <span>// 默认是折叠,设为 true 表示直接展开</span>\n children<span>:</span> <span>[</span><span>'/blog/'</span><span>,</span> <span>'/blog/test'</span><span>]</span> <span>// 有多个分组成员</span>\n <span>}</span><span>,</span>\n <span>{</span> <span>// 只有一个分组成员</span>\n title<span>:</span> <span>'Group2'</span><span>,</span>\n path<span>:</span> <span>'/guide/'</span><span>,</span>\n <span>}</span><span>,</span>\n <span>'/'</span><span>,</span>\n <span>]</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br></div></div><h3 id=\"多重侧边栏\"> 多重侧边栏</h3>\n<p>不同的分区可以对应不同的侧边导航栏。<br>\n<strong>fallback 必须放在最后!</strong><br>\nauto 的意思是这一个 md 自动生成一个它独占的侧边栏。</p>\n<div><div><br><br><br><br><br><br><br><br><br><br><br><br><br><div> </div><br><br><br><br><br><br><br><br><br></div><pre><code>sidebar<span>:</span> <span>{</span>\n <span>'/foo/'</span><span>:</span> <span>[</span>\n <span>''</span><span>,</span> <span>/* /foo/ */</span>\n <span>'one'</span><span>,</span> <span>/* /foo/one.html */</span>\n <span>'two'</span> <span>/* /foo/two.html */</span>\n <span>]</span><span>,</span>\n\n <span>'/bar/'</span><span>:</span> <span>[</span>\n <span>''</span><span>,</span> <span>/* /bar/ */</span>\n <span>'three'</span><span>,</span> <span>/* /bar/three.html */</span>\n <span>'four'</span> <span>/* /bar/four.html */</span>\n <span>]</span><span>,</span>\n\n <span>'/baz/'</span><span>:</span> <span>'auto'</span><span>,</span> <span>/* automatically generate single-page sidebars */</span>\n\n <span>// fallback</span>\n <span>'/'</span><span>:</span> <span>[</span>\n <span>''</span><span>,</span> <span>/* / */</span>\n <span>'contact'</span><span>,</span> <span>/* /contact.html */</span>\n <span>'about'</span> <span>/* /about.html */</span>\n <span>]</span>\n <span>}</span>\n</code></pre><div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br></div></div><h2 id=\"code-groups\"> code groups</h2>\n<p>CodeGroupItem之间需要空一行</p>\n<div><pre><code><span><span><span><</span>CodeGroup</span><span>></span></span>\n<span><span><span><</span>CodeGroupItem</span> <span>title</span><span><span>=</span><span>\"</span>YARN<span>\"</span></span><span>></span></span>\n\\`\\`\\`bash\nyarn create vuepress-site [optionalDirectoryName]\n\\`\\`\\`\n<span><span><span></</span>CodeGroupItem</span><span>></span></span>\n\n<span><span><span><</span>CodeGroupItem</span> <span>title</span><span><span>=</span><span>\"</span>NPM<span>\"</span></span><span>></span></span>\n\\`\\`\\`bash\nnpx create-vuepress-site [optionalDirectoryName]\n\\`\\`\\`\n<span><span><span></</span>CodeGroupItem</span><span>></span></span>\n<span><span><span></</span>CodeGroup</span><span>></span></span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br></div></div><p>效果如下</p>\n<CodeGroup>\n<CodeGroupItem title=\"YARN\">\n<div><pre><code><span>yarn</span> create vuepress-site <span>[</span>optionalDirectoryName<span>]</span>\n</code></pre>\n<div><span>1</span><br></div></div></CodeGroupItem>\n<CodeGroupItem title=\"NPM\">\n<div><pre><code>npx create-vuepress-site <span>[</span>optionalDirectoryName<span>]</span>\n</code></pre>\n<div><span>1</span><br></div></div></CodeGroupItem>\n</CodeGroup>",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "组件禁用",
"url": "https://kigane.github.io/guide/disable/",
"id": "https://kigane.github.io/guide/disable/",
"summary": "<p>你可以通过设置页面的 Frontmatter,在页面禁用一些功能。</p>\n",
"content_html": "<p>你可以通过设置页面的 Frontmatter,在页面禁用一些功能。</p>\n\n<p>本页面应当禁用了:</p>\n<ul>\n<li>导航栏</li>\n<li>侧边栏</li>\n<li>路径导航</li>\n<li>页面信息</li>\n<li>贡献者</li>\n<li>编辑此页链接</li>\n<li>更新时间</li>\n<li>上一篇/下一篇 链接</li>\n<li>评论</li>\n<li>页脚</li>\n<li>返回顶部按钮</li>\n</ul>\n",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": [
"使用指南"
]
},
{
"title": "密码加密的文章",
"url": "https://kigane.github.io/guide/encrypt/",
"id": "https://kigane.github.io/guide/encrypt/",
"content_html": "<h1 id=\"密码加密的文章\"> 密码加密的文章</h1>\n<p>实际的文章内容。</p>\n<p>段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字。</p>\n<p>段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字。</p>\n",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": [
"使用指南"
]
},
{
"title": "Markdown 增强",
"url": "https://kigane.github.io/guide/markdown/",
"id": "https://kigane.github.io/guide/markdown/",
"summary": "<p><code>vuepress-theme-hope</code> 通过内置 <a href=\"https://vuepress-theme-hope.github.io/md-enhance\" target=\"_blank\" rel=\"noopener noreferrer\">md-enhance</a>,在 Markdown 中启用了更多的语法与新功能。</p>\n",
"content_html": "<p><code>vuepress-theme-hope</code> 通过内置 <a href=\"https://vuepress-theme-hope.github.io/md-enhance\" target=\"_blank\" rel=\"noopener noreferrer\">md-enhance</a>,在 Markdown 中启用了更多的语法与新功能。</p>\n\n<h2 id=\"一键启用\"> 一键启用</h2>\n<p>你可以设置 <code>themeconfig.mdEnhance.enableAll</code> 启用 <a href=\"https://vuepress-theme-hope.github.io/md-enhance\" target=\"_blank\" rel=\"noopener noreferrer\">md-enhance</a> 插件的所有功能。</p>\n<div><div><br><br><div> </div><div> </div><div> </div><br><br><br></div><pre><code>module<span>.</span>exports <span>=</span> <span>{</span>\n themeConfig<span>:</span> <span>{</span>\n mdEnhance<span>:</span> <span>{</span>\n enableAll<span>:</span> <span>true</span><span>,</span>\n <span>}</span><span>,</span>\n <span>}</span><span>,</span>\n<span>}</span><span>;</span>\n</code></pre><div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br></div></div><h2 id=\"新增的更多语法\"> 新增的更多语法</h2>\n<h3 id=\"上下角标\"> 上下角标</h3>\n<p>19<sup>th</sup> H<sub>2</sub>O</p>\n<details><summary>代码</summary>\n<div><pre><code>19^th^ H<span><span>~</span><span>2</span><span>~</span></span>O\n</code></pre>\n<div><span>1</span><br></div></div></details>\n<ul>\n<li><a href=\"https://vuepress-theme-hope.github.io/zh/guide/markdown/sup-sub/\" target=\"_blank\" rel=\"noopener noreferrer\">点击查看</a></li>\n</ul>\n<h3 id=\"自定义对齐\"> 自定义对齐</h3>\n<p>::: center</p>\n<p>我是居中的</p>\n<p>:::</p>\n<p>::: right</p>\n<p>我在右对齐</p>\n<p>:::</p>\n<details><summary>代码</summary>\n<div><pre><code>::: center\n\n我是居中的\n\n:::\n\n::: right\n\n我在右对齐\n\n:::\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br></div></div></details>\n<ul>\n<li><a href=\"https://vuepress-theme-hope.github.io/zh/guide/markdown/align/\" target=\"_blank\" rel=\"noopener noreferrer\">点击查看</a></li>\n</ul>\n<h3 id=\"脚注\"> 脚注</h3>\n<p>此文字有脚注<sup></sup>.</p>\n<details><summary>代码</summary>\n<div><pre><code>此文字有脚注[^first].\n\n<span><span>[</span><span>^first</span><span>]</span><span>:</span> 这是脚注内容</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br></div></div></details>\n<ul>\n<li><a href=\"https://vuepress-theme-hope.github.io/zh/guide/markdown/footnote/\" target=\"_blank\" rel=\"noopener noreferrer\">点击查看</a></li>\n</ul>\n<h3 id=\"标记\"> 标记</h3>\n<p>你可以标记 ==重要的内容== 。</p>\n<details><summary>代码</summary>\n<div><pre><code>你可以标记 ==重要的内容== 。\n</code></pre>\n<div><span>1</span><br></div></div></details>\n<ul>\n<li><a href=\"https://vuepress-theme-hope.github.io/zh/guide/markdown/mark/\" target=\"_blank\" rel=\"noopener noreferrer\">点击查看</a></li>\n</ul>\n<h3 id=\"任务列表\"> 任务列表</h3>\n<ul>\n<li>[x] 计划 1</li>\n<li>[ ] 计划 2</li>\n</ul>\n<details><summary>Code</summary>\n<div><pre><code><span>-</span> [x] 计划 1\n<span>-</span> [ ] 计划 2\n</code></pre>\n<div><span>1</span><br><span>2</span><br></div></div></details>\n<ul>\n<li><a href=\"https://vuepress-theme-hope.github.io/guide/markdown/tasklist/\" target=\"_blank\" rel=\"noopener noreferrer\">点击查看</a></li>\n</ul>\n<h3 id=\"流程图\"> 流程图</h3>\n<i>Not supported content</i><details><summary>代码</summary>\n<div><pre><code><span><span>```</span><span>flow</span>\n<span>cond=>condition: Process?\nprocess=>operation: Process\ne=>end: End\n\ncond(yes)->process->e\ncond(no)->e</span>\n<span>```</span></span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br></div></div></details>\n<ul>\n<li><a href=\"https://vuepress-theme-hope.github.io/zh/guide/markdown/flowchart/\" target=\"_blank\" rel=\"noopener noreferrer\">点击查看</a></li>\n</ul>\n<h2 id=\"mermaid\"> Mermaid</h2>\n<Mermaid id=\"mermaid-64a57026\" data-code=\"sequenceDiagram%0A%20%20%20%20Alice%20-%3E%3E%20Bob%3A%20Hello%20Bob%2C%20how%20are%20you%3F%0A%20%20%20%20Bob--%3E%3EJohn%3A%20How%20about%20you%20John%3F%0A%20%20%20%20Bob--x%20Alice%3A%20I%20am%20good%20thanks!%0A%20%20%20%20Bob-x%20John%3A%20I%20am%20good%20thanks!%0A%20%20%20%20Note%20right%20of%20John%3A%20Bob%20thinks%20a%20long%3Cbr%2F%3Elong%20time%2C%20so%20long%3Cbr%2F%3Ethat%20the%20text%20does%3Cbr%2F%3Enot%20fit%20on%20a%20row.%0A%0A%20%20%20%20Bob--%3EAlice%3A%20Checking%20with%20John...%0A%20%20%20%20Alice-%3EJohn%3A%20Yes...%20John%2C%20how%20are%20you%3F%0A\"></Mermaid><details><summary>代码</summary>\n<div><pre><code>```sequence\nAlice ->> Bob: Hello Bob, how are you?\nBob-->>John: How about you John?\nBob--x Alice: I am good thanks!\nBob-x John: I am good thanks!\nNote right of John: Bob thinks a long<span><span><span><</span>br</span><span>/></span></span>long time, so long<span><span><span><</span>br</span><span>/></span></span>that the text does<span><span><span><</span>br</span><span>/></span></span>not fit on a row.\n\nBob-->Alice: Checking with John...\nAlice->John: Yes... John, how are you?\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br></div></div><div><pre><code>\n</code></pre>\n<div><span>1</span><br></div></div></details>\n<ul>\n<li><a href=\"https://vuepress-theme-hope.github.io/zh/guide/markdown/mermaid/\" target=\"_blank\" rel=\"noopener noreferrer\">点击查看</a></li>\n</ul>\n<h3 id=\"tex-语法\"> Tex 语法</h3>\n<p class='katex-block'><span><span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:2.4em;vertical-align:-0.95em;\"></span><span><span></span><span><span><span><span style=\"height:1.3714em;\"><span style=\"top:-2.314em;\"><span style=\"height:3em;\"></span><span><span style=\"margin-right:0.05556em;\">∂</span><span><span style=\"margin-right:0.03588em;\">ω</span><span><span><span><span style=\"height:0.5904em;\"><span style=\"top:-2.989em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span style=\"margin-right:0.02778em;\">r</span></span></span></span></span></span></span></span></span></span><span style=\"top:-3.23em;\"><span style=\"height:3em;\"></span><span style=\"border-bottom-width:0.04em;\"></span></span><span style=\"top:-3.677em;\"><span style=\"height:3em;\"></span><span><span><span style=\"margin-right:0.05556em;\">∂</span><span><span><span><span style=\"height:0.6644em;\"><span style=\"top:-3.063em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span style=\"margin-right:0.02778em;\">r</span></span></span></span></span></span></span></span></span></span></span><span></span></span><span><span style=\"height:0.686em;\"><span></span></span></span></span></span><span></span></span><span style=\"margin-right:0.1667em;\"></span><span><span style=\"top:0em;\"><span>(</span></span><span><span></span><span><span><span><span style=\"height:1.3414em;\"><span style=\"top:-2.314em;\"><span style=\"height:3em;\"></span><span><span style=\"margin-right:0.03588em;\">ω</span></span></span><span style=\"top:-3.23em;\"><span style=\"height:3em;\"></span><span style=\"border-bottom-width:0.04em;\"></span></span><span style=\"top:-3.677em;\"><span style=\"height:3em;\"></span><span><span><span style=\"margin-right:0.03588em;\">y</span><span><span><span><span style=\"height:0.6644em;\"><span style=\"top:-3.063em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span><span style=\"margin-right:0.03588em;\">ω</span></span></span></span></span></span></span></span></span></span></span></span><span></span></span><span><span style=\"height:0.686em;\"><span></span></span></span></span></span><span></span></span><span style=\"top:0em;\"><span>)</span></span></span><span style=\"margin-right:0.2778em;\"></span><span>=</span><span style=\"margin-right:0.2778em;\"></span></span><span><span style=\"height:3.0277em;vertical-align:-1.2777em;\"></span><span><span style=\"top:0em;\"><span>(</span></span><span><span></span><span><span><span><span style=\"height:1.3414em;\"><span style=\"top:-2.314em;\"><span style=\"height:3em;\"></span><span><span style=\"margin-right:0.03588em;\">ω</span></span></span><span style=\"top:-3.23em;\"><span style=\"height:3em;\"></span><span style=\"border-bottom-width:0.04em;\"></span></span><span style=\"top:-3.677em;\"><span style=\"height:3em;\"></span><span><span><span style=\"margin-right:0.03588em;\">y</span><span><span><span><span style=\"height:0.6644em;\"><span style=\"top:-3.063em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span><span style=\"margin-right:0.03588em;\">ω</span></span></span></span></span></span></span></span></span></span></span></span><span></span></span><span><span style=\"height:0.686em;\"><span></span></span></span></span></span><span></span></span><span style=\"top:0em;\"><span>)</span></span></span><span style=\"margin-right:0.1667em;\"></span><span><span style=\"top:0em;\"><span>{</span></span><span>(</span><span>lo<span style=\"margin-right:0.01389em;\">g</span></span><span style=\"margin-right:0.1667em;\"></span><span style=\"margin-right:0.03588em;\">y</span><span><span>)</span><span><span><span><span style=\"height:0.7144em;\"><span style=\"top:-3.113em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span style=\"margin-right:0.02778em;\">r</span></span></span></span></span></span></span></span><span style=\"margin-right:0.2222em;\"></span><span>+</span><span style=\"margin-right:0.2222em;\"></span><span><span><span><span style=\"height:1.6514em;\"><span style=\"top:-1.8723em;margin-left:0em;\"><span style=\"height:3.05em;\"></span><span><span><span>i</span><span>=</span><span>1</span></span></span></span><span style=\"top:-3.05em;\"><span style=\"height:3.05em;\"></span><span><span>∑</span></span></span><span style=\"top:-4.3em;margin-left:0em;\"><span style=\"height:3.05em;\"></span><span><span style=\"margin-right:0.02778em;\">r</span></span></span></span><span></span></span><span><span style=\"height:1.2777em;\"><span></span></span></span></span></span><span style=\"margin-right:0.1667em;\"></span><span><span></span><span><span><span><span style=\"height:1.5017em;\"><span style=\"top:-2.314em;\"><span style=\"height:3em;\"></span><span><span><span style=\"margin-right:0.03588em;\">ω</span><span><span><span><span style=\"height:0.7507em;\"><span style=\"top:-2.989em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span>i</span></span></span></span></span></span></span></span></span></span><span style=\"top:-3.23em;\"><span style=\"height:3em;\"></span><span style=\"border-bottom-width:0.04em;\"></span></span><span style=\"top:-3.677em;\"><span style=\"height:3em;\"></span><span><span>(</span><span>−</span><span>1</span><span><span>)</span><span><span><span><span style=\"height:0.8247em;\"><span style=\"top:-3.063em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span>i</span></span></span></span></span></span></span></span><span style=\"margin-right:0.02778em;\">r</span><span style=\"margin-right:0.1667em;\"></span><span>⋯</span><span style=\"margin-right:0.1667em;\"></span><span>(</span><span style=\"margin-right:0.02778em;\">r</span><span style=\"margin-right:0.2222em;\"></span><span>−</span><span style=\"margin-right:0.2222em;\"></span><span>i</span><span style=\"margin-right:0.2222em;\"></span><span>+</span><span style=\"margin-right:0.2222em;\"></span><span>1</span><span>)</span><span>(</span><span>lo<span style=\"margin-right:0.01389em;\">g</span></span><span style=\"margin-right:0.1667em;\"></span><span style=\"margin-right:0.03588em;\">y</span><span><span>)</span><span><span><span><span style=\"height:0.8247em;\"><span style=\"top:-3.063em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span><span style=\"margin-right:0.02778em;\">r</span><span>−</span><span>i</span></span></span></span></span></span></span></span></span></span></span></span><span></span></span><span><span style=\"height:0.686em;\"><span></span></span></span></span></span><span></span></span><span style=\"top:0em;\"><span>}</span></span></span></span></span></span></span></p>\n<details><summary>代码</summary>\n<div><pre><code>$$\n\\frac {\\partial^r} {\\partial \\omega^r} \\left(\\frac {y^{\\omega}} {\\omega}\\right)\n= \\left(\\frac {y^{\\omega}} {\\omega}\\right) \\left\\{(\\log y)^r + \\sum_{i=1}^r \\frac {(-1)^i r \\cdots (r-i+1) (\\log y)^{r-i}} {\\omega^i} \\right\\}\n$$\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br></div></div></details>\n<ul>\n<li><a href=\"https://vuepress-theme-hope.github.io/zh/guide/markdown/tex/\" target=\"_blank\" rel=\"noopener noreferrer\">点击查看</a></li>\n</ul>\n<h3 id=\"代码案例\"> 代码案例</h3>\n\n <div\n id=\"code-demo-5ac6bc9f\"\n \n \n data-title=\"%E4%B8%80%E4%B8%AA%E6%99%AE%E9%80%9A%20Demo\"\n\n data-code=\"%7B%22html%22%3A%22%3Ch1%3EMr.Hope%3C%2Fh1%3E%5Cn%3Cp%3E%3Cspan%20id%3D%5C%22very%5C%22%3E%E5%8D%81%E5%88%86%3C%2Fspan%3E%20%E5%B8%85%3C%2Fp%3E%5Cn%22%2C%22js%22%3A%22document.querySelector(%5C%22%23very%5C%22).addEventListener(%5C%22click%5C%22%2C%20()%20%3D%3E%20%7B%5Cn%20%20alert(%5C%22%E5%8D%81%E5%88%86%E5%B8%85%5C%22)%3B%5Cn%7D)%3B%5Cn%22%2C%22css%22%3A%22span%20%7B%5Cn%20%20color%3A%20red%3B%5Cn%7D%5Cn%22%7D\"\n >\n \n <div>\n <div>\n<div><pre><code><span><span><span><</span>h1</span><span>></span></span>Mr.Hope<span><span><span></</span>h1</span><span>></span></span>\n<span><span><span><</span>p</span><span>></span></span><span><span><span><</span>span</span> <span>id</span><span><span>=</span><span>\"</span>very<span>\"</span></span><span>></span></span>十分<span><span><span></</span>span</span><span>></span></span> 帅<span><span><span></</span>p</span><span>></span></span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br></div></div><div><pre><code>document<span>.</span><span>querySelector</span><span>(</span><span>\"#very\"</span><span>)</span><span>.</span><span>addEventListener</span><span>(</span><span>\"click\"</span><span>,</span> <span>(</span><span>)</span> <span>=></span> <span>{</span>\n <span>alert</span><span>(</span><span>\"十分帅\"</span><span>)</span><span>;</span>\n<span>}</span><span>)</span><span>;</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br></div></div><div><pre><code><span>span</span> <span>{</span>\n <span>color</span><span>:</span> red<span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br></div></div>\n </div>\n </div>\n \n </div>\n<details><summary>代码</summary>\n<div><pre><code>::: demo 一个普通 Demo\n\n<span><span>```</span><span>html</span>\n<span><span><span><span><</span>h1</span><span>></span></span>Mr.Hope<span><span><span></</span>h1</span><span>></span></span>\n<span><span><span><</span>p</span><span>></span></span><span><span><span><</span>span</span> <span>id</span><span><span>=</span><span>\"</span>very<span>\"</span></span><span>></span></span>十分<span><span><span></</span>span</span><span>></span></span> 帅<span><span><span></</span>p</span><span>></span></span></span>\n<span>```</span></span>\n\n<span><span>```</span><span>js</span>\n<span>document<span>.</span><span>querySelector</span><span>(</span><span>\"#very\"</span><span>)</span><span>.</span><span>addEventListener</span><span>(</span><span>\"click\"</span><span>,</span> <span>(</span><span>)</span> <span>=></span> <span>{</span>\n <span>alert</span><span>(</span><span>\"十分帅\"</span><span>)</span><span>;</span>\n<span>}</span><span>)</span><span>;</span></span>\n<span>```</span></span>\n\n<span><span>```</span><span>css</span>\n<span><span>span</span> <span>{</span>\n <span>color</span><span>:</span> red<span>;</span>\n<span>}</span></span>\n<span>```</span></span>\n\n:::\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br></div></div></details>\n\n <div\n id=\"code-demo-693e1878\"\n \n data-type=\"react\"\n data-title=\"%E4%B8%80%E4%B8%AA%20React%20Demo\"\n\n data-code=\"%7B%22js%22%3A%22export%20default%20class%20App%20extends%20React.Component%20%7B%5Cn%20%20constructor(props)%20%7B%5Cn%20%20%20%20super(props)%3B%5Cn%20%20%20%20this.state%20%3D%20%7B%20message%3A%20%5C%22%E5%8D%81%E5%88%86%E5%B8%85%5C%22%20%7D%3B%5Cn%20%20%7D%5Cn%20%20render()%20%7B%5Cn%20%20%20%20return%20(%5Cn%20%20%20%20%20%20%3Cdiv%20className%3D%5C%22box-react%5C%22%3E%5Cn%20%20%20%20%20%20%20%20Mr.Hope%20%3Cspan%3E%7Bthis.state.message%7D%3C%2Fspan%3E%5Cn%20%20%20%20%20%20%3C%2Fdiv%3E%5Cn%20%20%20%20)%3B%5Cn%20%20%7D%5Cn%7D%5Cn%22%2C%22css%22%3A%22.box-react%20span%20%7B%5Cn%20%20color%3A%20red%3B%5Cn%7D%5Cn%22%7D\"\n >\n \n <div>\n <div>\n<div><pre><code><span>export</span> <span>default</span> <span>class</span> <span>App</span> <span>extends</span> <span>React<span>.</span>Component</span> <span>{</span>\n <span>constructor</span><span>(</span><span>props</span><span>)</span> <span>{</span>\n <span>super</span><span>(</span>props<span>)</span><span>;</span>\n <span>this</span><span>.</span>state <span>=</span> <span>{</span> message<span>:</span> <span>\"十分帅\"</span> <span>}</span><span>;</span>\n <span>}</span>\n <span>render</span><span>(</span><span>)</span> <span>{</span>\n <span>return</span> <span>(</span>\n <span><</span>div className<span>=</span><span>\"box-react\"</span><span>></span>\n Mr<span>.</span>Hope <span><</span>span<span>></span><span>{</span><span>this</span><span>.</span>state<span>.</span>message<span>}</span><span><</span><span>/</span>span<span>></span>\n <span><</span><span>/</span>div<span>></span>\n <span>)</span><span>;</span>\n <span>}</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br></div></div><div><pre><code><span>.box-react span</span> <span>{</span>\n <span>color</span><span>:</span> red<span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br></div></div>\n </div>\n </div>\n \n </div>\n<details><summary>代码</summary>\n<div><pre><code>::: demo [react] 一个 React Demo\n\n<span><span>```</span><span>js</span>\n<span><span>export</span> <span>default</span> <span>class</span> <span>App</span> <span>extends</span> <span>React<span>.</span>Component</span> <span>{</span>\n <span>constructor</span><span>(</span><span>props</span><span>)</span> <span>{</span>\n <span>super</span><span>(</span>props<span>)</span><span>;</span>\n <span>this</span><span>.</span>state <span>=</span> <span>{</span> message<span>:</span> <span>\"十分帅\"</span> <span>}</span><span>;</span>\n <span>}</span>\n <span>render</span><span>(</span><span>)</span> <span>{</span>\n <span>return</span> <span>(</span>\n <span><</span>div className<span>=</span><span>\"box-react\"</span><span>></span>\n Mr<span>.</span>Hope <span><</span>span<span>></span><span>{</span><span>this</span><span>.</span>state<span>.</span>message<span>}</span><span><</span><span>/</span>span<span>></span>\n <span><</span><span>/</span>div<span>></span>\n <span>)</span><span>;</span>\n <span>}</span>\n<span>}</span></span>\n<span>```</span></span>\n\n<span><span>```</span><span>css</span>\n<span><span>.box-react span</span> <span>{</span>\n <span>color</span><span>:</span> red<span>;</span>\n<span>}</span></span>\n<span>```</span></span>\n\n:::\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br><span>25</span><br></div></div></details>\n\n <div\n id=\"code-demo-2c0b9cdb\"\n \n data-type=\"vue\"\n data-title=\"%E4%B8%80%E4%B8%AA%20Vue%20Demo\"\n\n data-code=\"%7B%22vue%22%3A%22%3Ctemplate%3E%5Cn%20%20%3Cdiv%20class%3D%5C%22box%5C%22%3E%5Cn%20%20%20%20Mr.Hope%20%3Cspan%3E%7B%7B%20message%20%7D%7D%3C%2Fspan%3E%5Cn%20%20%3C%2Fdiv%3E%5Cn%3C%2Ftemplate%3E%5Cn%3Cscript%3E%5Cnexport%20default%20%7B%5Cn%20%20data%3A%20()%20%3D%3E%20(%7B%20message%3A%20%5C%22%E5%8D%81%E5%88%86%E5%B8%85%5C%22%20%7D)%2C%5Cn%7D%3B%5Cn%3C%2Fscript%3E%5Cn%3Cstyle%3E%5Cn.box%20span%20%7B%5Cn%20%20color%3A%20red%3B%5Cn%7D%5Cn%3C%2Fstyle%3E%5Cn%22%7D\"\n >\n \n <div>\n <div>\n<div><pre><code><span><span><span><</span>template</span><span>></span></span>\n <span><span><span><</span>div</span> <span>class</span><span><span>=</span><span>\"</span>box<span>\"</span></span><span>></span></span>\n Mr.Hope <span><span><span><</span>span</span><span>></span></span>{{ message }}<span><span><span></</span>span</span><span>></span></span>\n <span><span><span></</span>div</span><span>></span></span>\n<span><span><span></</span>template</span><span>></span></span>\n<span><span><span><</span>script</span><span>></span></span><span><span>\n<span>export</span> <span>default</span> <span>{</span>\n <span>data</span><span>:</span> <span>(</span><span>)</span> <span>=></span> <span>(</span><span>{</span> message<span>:</span> <span>\"十分帅\"</span> <span>}</span><span>)</span><span>,</span>\n<span>}</span><span>;</span>\n</span></span><span><span><span></</span>script</span><span>></span></span>\n<span><span><span><</span>style</span><span>></span></span><span><span>\n<span>.box span</span> <span>{</span>\n <span>color</span><span>:</span> red<span>;</span>\n<span>}</span>\n</span></span><span><span><span></</span>style</span><span>></span></span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br></div></div>\n </div>\n </div>\n \n </div>\n<details><summary>代码</summary>\n<div><pre><code>::: demo [vue] 一个 Vue Demo\n\n<span><span>```</span><span>vue</span>\n<span><template>\n <div>\n Mr.Hope <span>{{ message }}</span>\n </div>\n</template>\n<script>\nexport default {\n data: () => ({ message: \"十分帅\" }),\n};\n</script>\n<style>\n.box span {\n color: red;\n}\n</style></span>\n<span>```</span></span>\n\n:::\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br></div></div></details>\n\n <div\n id=\"code-demo-2869b0e2\"\n \n \n data-title=\"%E4%B8%80%E4%B8%AA%E6%99%AE%E9%80%9A%20Demo\"\n\n data-code=\"%7B%22md%22%3A%22%23%20%E6%A0%87%E9%A2%98%5Cn%5Cn%E5%8D%81%E5%88%86%E5%B8%85%5Cn%22%2C%22ts%22%3A%22const%20message%3A%20string%20%3D%20%5C%22Mr.Hope%5C%22%3B%5Cn%5Cndocument.querySelector(%5C%22h1%5C%22).innerHTML%20%3D%20message%3B%5Cn%22%2C%22scss%22%3A%22h1%20%7B%5Cn%20%20font-style%3A%20italic%3B%5Cn%5Cn%20%20%2B%20p%20%7B%5Cn%20%20%20%20color%3A%20red%3B%5Cn%20%20%7D%5Cn%7D%5Cn%22%7D\"\n >\n \n <div>\n <div>\n<div><pre><code><span><span>#</span> 标题</span>\n\n十分帅\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br></div></div><div><pre><code><span>const</span> message<span>:</span> <span>string</span> <span>=</span> <span>\"Mr.Hope\"</span><span>;</span>\n\ndocument<span>.</span><span>querySelector</span><span>(</span><span>\"h1\"</span><span>)</span><span>.</span>innerHTML <span>=</span> message<span>;</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br></div></div><div><pre><code><span>h1 </span><span>{</span>\n <span>font-style</span><span>:</span> italic<span>;</span>\n\n <span>+ p </span><span>{</span>\n <span>color</span><span>:</span> red<span>;</span>\n <span>}</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br></div></div>\n </div>\n </div>\n \n </div>\n<details><summary>代码</summary>\n<div><pre><code>::: demo 一个普通 Demo\n\n<span><span>```</span><span>md</span>\n<span><span><span>#</span> 标题</span>\n\n十分帅</span>\n<span>```</span></span>\n\n<span><span>```</span><span>ts</span>\n<span><span>const</span> message<span>:</span> <span>string</span> <span>=</span> <span>\"Mr.Hope\"</span><span>;</span>\n\ndocument<span>.</span><span>querySelector</span><span>(</span><span>\"h1\"</span><span>)</span><span>.</span>innerHTML <span>=</span> message<span>;</span></span>\n<span>```</span></span>\n\n<span><span>```</span><span>scss</span>\n<span><span>h1 </span><span>{</span>\n <span>font-style</span><span>:</span> italic<span>;</span>\n\n <span>+ p </span><span>{</span>\n <span>color</span><span>:</span> red<span>;</span>\n <span>}</span>\n<span>}</span></span>\n<span>```</span></span>\n\n:::\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br><span>25</span><br></div></div></details>\n<ul>\n<li><a href=\"https://vuepress-theme-hope.github.io/zh/guide/markdown/demo/\" target=\"_blank\" rel=\"noopener noreferrer\">点击查看</a></li>\n</ul>\n<h3 id=\"幻灯片\"> 幻灯片</h3>\n<i>Not supported content</i><details><summary>代码</summary>\n<div><pre><code>@slidestart\n\n<span><span>##</span> 幻灯片 1</span>\n\n一个有文字和 <span>[<span>链接</span>](<span>https://mrhope.site</span>)</span> 的段落\n\n<span>---</span>\n\n<span><span>##</span> 幻灯片 2</span>\n\n<span>-</span> 列表 1\n<span>-</span> 列表 2\n\n<span>---</span>\n\n<span><span>##</span> 幻灯片 3.1</span>\n\n<span><span>```</span><span>js</span>\n<span><span>const</span> a <span>=</span> <span>1</span><span>;</span></span>\n<span>```</span></span>\n\n--\n\n<span><span>##</span> 幻灯片 3.2</span>\n\n$$\nJ(\\theta_0,\\theta_1) = \\sum_{i=0}\n$$\n\n@slideend\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br><span>25</span><br><span>26</span><br><span>27</span><br><span>28</span><br><span>29</span><br><span>30</span><br></div></div></details>\n<ul>\n<li><a href=\"https://vuepress-theme-hope.github.io/zh/guide/markdown/presentation/\" target=\"_blank\" rel=\"noopener noreferrer\">点击查看</a></li>\n</ul>\n<h2 id=\"其他语法\"> 其他语法</h2>\n<div><p>自定义标题</p>\n<p>信息容器</p>\n</div>\n<div><p>自定义标题</p>\n<p>提示容器</p>\n</div>\n<div><p>自定义标题</p>\n<p>警告容器</p>\n</div>\n<div><p>自定义标题</p>\n<p>危险容器</p>\n</div>\n<details><summary>自定义标题</summary>\n<p>详情容器</p>\n</details>\n<details><summary>代码</summary>\n<div><pre><code>::: info 自定义标题\n\n信息容器\n\n:::\n\n::: tip 自定义标题\n\n提示容器\n\n:::\n\n::: warning 自定义标题\n\n警告容器\n\n:::\n\n::: danger 自定义标题\n\n危险容器\n\n:::\n\n::: details 自定义标题\n\n详情容器\n\n:::\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br><span>25</span><br><span>26</span><br><span>27</span><br><span>28</span><br><span>29</span><br></div></div></details>\n<hr>\n<section>\n<ol>\n<li id=\"footnote1\"><p>这是脚注内容 </p>\n</li>\n</ol>\n</section>\n",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-12-13T15:53:11.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": [
"使用指南"
]
},
{
"title": "页面配置",
"url": "https://kigane.github.io/guide/page/",
"id": "https://kigane.github.io/guide/page/",
"content_html": "<h2 id=\"页面信息\"> 页面信息</h2>\n<p>你可以在 Markdown 的 Frontmatter 中设置页面信息。</p>\n<ul>\n<li>\n<p>作者设置为 Ms.Hope。</p>\n</li>\n<li>\n<p>写作时间应为 2020 年 1 月 1 日</p>\n</li>\n<li>\n<p>分类为 “使用指南”</p>\n</li>\n<li>\n<p>标签为 “页面配置” 和 “使用指南”</p>\n</li>\n</ul>\n<h2 id=\"页面内容\"> 页面内容</h2>\n<p>你可以自由在这里书写你的 Markdown。</p>\n<div><p>Tips</p>\n<ul>\n<li>\n<p>Markdown 文件夹的图片请使用相对链接 <code>./</code> 进行引用。</p>\n</li>\n<li>\n<p><code>.vuepress/public</code> 文件夹的图片,请使用绝对链接 <code>/</code> 进行引用</p>\n</li>\n</ul>\n</div>\n<p>主题包含了一个自定义徽章章可以使用:</p>\n<blockquote>\n<p>文字结尾应该有深蓝色的 徽章文字 徽章。 <i>Not supported content</i></p>\n</blockquote>\n<h2 id=\"页面结构\"> 页面结构</h2>\n<p>此页面应当包含:</p>\n<ul>\n<li>返回顶部按钮</li>\n<li>路径导航</li>\n<li>评论</li>\n<li>页脚</li>\n</ul>\n<h2 id=\"slide\"> slide</h2>\n<i>Not supported content</i>",
"date_published": "2020-01-01T00:00:00.000Z",
"date_modified": "2021-12-13T15:53:11.000Z",
"authors": [
{
"name": "Ms.Hope"
}
],
"tags": [
"guide"
]
},
{
"title": "Hello Vuepress",
"url": "https://kigane.github.io/guide/",
"id": "https://kigane.github.io/guide/",
"content_html": "<h2 id=\"遇到的问题\"> 遇到的问题</h2>\n<p>在根目录的readme中设置footer内容时使用双引号,会出现YAML Exception。项目无法正常编译。</p>\n<h2 id=\"math\"> math</h2>\n<p>安装 markdown-it-katex 插件</p>\n<div><pre><code><span>yarn</span> <span>add</span> markdown-it-katex -D\n</code></pre>\n<div><span>1</span><br></div></div><p>再设置</p>\n<div><pre><code><span>// .vuepress/config.js</span>\nmodule<span>.</span>exports <span>=</span> <span>{</span>\n markdown<span>:</span> <span>{</span>\n <span>// 开启代码块行号</span>\n lineNumbers<span>:</span> <span>true</span><span>,</span>\n <span>// 选择目录层级</span>\n toc<span>:</span> <span>{</span> includeLevel<span>:</span> <span>[</span><span>1</span><span>,</span> <span>2</span><span>,</span> <span>3</span><span>]</span> <span>}</span><span>,</span>\n <span>// 加载插件</span>\n <span>extendMarkdown</span><span>:</span> <span>md</span> <span>=></span> <span>{</span>\n <span>// use more markdown-it plugins!</span>\n md<span>.</span><span>use</span><span>(</span><span>require</span><span>(</span><span>'markdown-it-katex'</span><span>)</span><span>)</span>\n <span>}</span>\n <span>}</span><span>,</span>\n <span>// 要使 markdown-it-katex 插件生效,还需要设置在每个 html 的 <head> 部分,加上一些依赖的引用。</span>\n head<span>:</span> <span>[</span>\n <span>[</span><span>'link'</span><span>,</span> <span>{</span>\n rel<span>:</span> <span>'stylesheet'</span><span>,</span>\n href<span>:</span> <span>'https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.7.1/katex.min.css'</span>\n <span>}</span><span>]</span><span>,</span>\n <span>[</span><span>'link'</span><span>,</span> <span>{</span>\n rel<span>:</span> <span>\"stylesheet\"</span><span>,</span>\n href<span>:</span> <span>\"https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/2.10.0/github-markdown.min.css\"</span>\n <span>}</span><span>]</span><span>,</span>\n <span>]</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br><span>25</span><br></div></div><p>效果如下:<br>\n<span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:1.1901em;vertical-align:-0.345em;\"></span><span><span></span><span><span><span><span style=\"height:0.8451em;\"><span style=\"top:-2.655em;\"><span style=\"height:3em;\"></span><span><span><span>n</span></span></span></span><span style=\"top:-3.23em;\"><span style=\"height:3em;\"></span><span style=\"border-bottom-width:0.04em;\"></span></span><span style=\"top:-3.394em;\"><span style=\"height:3em;\"></span><span><span><span>1</span></span></span></span></span><span></span></span><span><span style=\"height:0.345em;\"><span></span></span></span></span></span><span></span></span><span style=\"margin-right:0.2778em;\"></span><span>=</span><span style=\"margin-right:0.2778em;\"></span></span><span><span style=\"height:1.0622em;vertical-align:-0.2481em;\"></span><span><span>n</span><span><span><span><span style=\"height:0.8141em;\"><span style=\"top:-2.4519em;margin-left:0em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span>1</span></span></span><span style=\"top:-3.063em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span>2</span></span></span></span><span></span></span><span><span style=\"height:0.2481em;\"><span></span></span></span></span></span></span></span></span></span></p>\n<p class='katex-block'><span><span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:2.4637em;vertical-align:-0.9819em;\"></span><span><span><span style=\"width:0.5em;\"></span><span><span><span><span style=\"height:1.4819em;\"><span style=\"top:-3.4819em;\"><span style=\"height:3.4819em;\"></span><span><span><span><span style=\"top:0em;\"><span>[</span></span><span><span><span style=\"width:0.5em;\"></span><span><span><span><span style=\"height:1.45em;\"><span style=\"top:-3.61em;\"><span style=\"height:3em;\"></span><span><span>cos</span><span style=\"margin-right:0.1667em;\"></span><span>ϕ</span></span></span><span style=\"top:-2.41em;\"><span style=\"height:3em;\"></span><span><span>−</span><span style=\"margin-right:0.1667em;\"></span><span>sin</span><span style=\"margin-right:0.1667em;\"></span><span>ϕ</span></span></span></span><span></span></span><span><span style=\"height:0.95em;\"><span></span></span></span></span></span><span style=\"width:0.5em;\"></span><span style=\"width:0.5em;\"></span><span><span><span><span style=\"height:1.45em;\"><span style=\"top:-3.61em;\"><span style=\"height:3em;\"></span><span><span>sin</span><span style=\"margin-right:0.1667em;\"></span><span>ϕ</span></span></span><span style=\"top:-2.41em;\"><span style=\"height:3em;\"></span><span><span>cos</span><span style=\"margin-right:0.1667em;\"></span><span>ϕ</span></span></span></span><span></span></span><span><span style=\"height:0.95em;\"><span></span></span></span></span></span><span style=\"width:0.5em;\"></span></span></span><span style=\"top:0em;\"><span>]</span></span></span><span style=\"margin-right:0.1667em;\"></span><span><span style=\"top:0em;\"><span>[</span></span><span><span><span style=\"width:0.5em;\"></span><span><span><span><span style=\"height:1.45em;\"><span style=\"top:-3.61em;\"><span style=\"height:3em;\"></span><span><span><span>λ</span><span><span><span><span style=\"height:0.3011em;\"><span style=\"top:-2.55em;margin-left:0em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span><span>1</span></span></span></span></span><span></span></span><span><span style=\"height:0.15em;\"><span></span></span></span></span></span></span></span></span><span style=\"top:-2.41em;\"><span style=\"height:3em;\"></span><span><span>0</span></span></span></span><span></span></span><span><span style=\"height:0.95em;\"><span></span></span></span></span></span><span style=\"width:0.5em;\"></span><span style=\"width:0.5em;\"></span><span><span><span><span style=\"height:1.45em;\"><span style=\"top:-3.61em;\"><span style=\"height:3em;\"></span><span><span>0</span></span></span><span style=\"top:-2.41em;\"><span style=\"height:3em;\"></span><span><span><span>λ</span><span><span><span><span style=\"height:0.3011em;\"><span style=\"top:-2.55em;margin-left:0em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span><span>2</span></span></span></span></span><span></span></span><span><span style=\"height:0.15em;\"><span></span></span></span></span></span></span></span></span></span><span></span></span><span><span style=\"height:0.95em;\"><span></span></span></span></span></span><span style=\"width:0.5em;\"></span></span></span><span style=\"top:0em;\"><span>]</span></span></span><span style=\"margin-right:0.1667em;\"></span><span><span style=\"top:0em;\"><span>[</span></span><span><span><span style=\"width:0.5em;\"></span><span><span><span><span style=\"height:1.45em;\"><span style=\"top:-3.61em;\"><span style=\"height:3em;\"></span><span><span>cos</span><span style=\"margin-right:0.1667em;\"></span><span>ϕ</span></span></span><span style=\"top:-2.41em;\"><span style=\"height:3em;\"></span><span><span>sin</span><span style=\"margin-right:0.1667em;\"></span><span>ϕ</span></span></span></span><span></span></span><span><span style=\"height:0.95em;\"><span></span></span></span></span></span><span style=\"width:0.5em;\"></span><span style=\"width:0.5em;\"></span><span><span><span><span style=\"height:1.45em;\"><span style=\"top:-3.61em;\"><span style=\"height:3em;\"></span><span><span>−</span><span style=\"margin-right:0.1667em;\"></span><span>sin</span><span style=\"margin-right:0.1667em;\"></span><span>ϕ</span></span></span><span style=\"top:-2.41em;\"><span style=\"height:3em;\"></span><span><span>cos</span><span style=\"margin-right:0.1667em;\"></span><span>ϕ</span></span></span></span><span></span></span><span><span style=\"height:0.95em;\"><span></span></span></span></span></span><span style=\"width:0.5em;\"></span></span></span><span style=\"top:0em;\"><span>]</span></span></span><span style=\"margin-right:0.2778em;\"></span><span>=</span></span><span><span><span style=\"top:0em;\"><span>[</span></span><span><span><span style=\"width:0.5em;\"></span><span><span><span><span style=\"height:1.4819em;\"><span style=\"top:-3.61em;\"><span style=\"height:3em;\"></span><span><span><span>λ</span><span><span><span><span style=\"height:0.3011em;\"><span style=\"top:-2.55em;margin-left:0em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span><span>1</span></span></span></span></span><span></span></span><span><span style=\"height:0.15em;\"><span></span></span></span></span></span></span><span style=\"margin-right:0.1667em;\"></span><span><span>cos</span><span><span><span><span style=\"height:0.8141em;\"><span style=\"top:-3.063em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span><span>2</span></span></span></span></span></span></span></span></span><span style=\"margin-right:0.1667em;\"></span><span>ϕ</span><span style=\"margin-right:0.2222em;\"></span><span>+</span><span style=\"margin-right:0.2222em;\"></span><span><span>λ</span><span><span><span><span style=\"height:0.3011em;\"><span style=\"top:-2.55em;margin-left:0em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span><span>2</span></span></span></span></span><span></span></span><span><span style=\"height:0.15em;\"><span></span></span></span></span></span></span><span style=\"margin-right:0.1667em;\"></span><span><span>sin</span><span><span><span><span style=\"height:0.8719em;\"><span style=\"top:-3.1208em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span><span>2</span></span></span></span></span></span></span></span></span><span style=\"margin-right:0.1667em;\"></span><span>ϕ</span></span></span><span style=\"top:-2.3781em;\"><span style=\"height:3em;\"></span><span><span><span style=\"top:0em;\">(</span><span><span>λ</span><span><span><span><span style=\"height:0.3011em;\"><span style=\"top:-2.55em;margin-left:0em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span><span>2</span></span></span></span></span><span></span></span><span><span style=\"height:0.15em;\"><span></span></span></span></span></span></span><span style=\"margin-right:0.2222em;\"></span><span>−</span><span style=\"margin-right:0.2222em;\"></span><span><span>λ</span><span><span><span><span style=\"height:0.3011em;\"><span style=\"top:-2.55em;margin-left:0em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span><span>1</span></span></span></span></span><span></span></span><span><span style=\"height:0.15em;\"><span></span></span></span></span></span></span><span style=\"top:0em;\">)</span></span><span style=\"margin-right:0.1667em;\"></span><span>cos</span><span style=\"margin-right:0.1667em;\"></span><span>ϕ</span><span style=\"margin-right:0.1667em;\"></span><span>sin</span><span style=\"margin-right:0.1667em;\"></span><span>ϕ</span></span></span></span><span></span></span><span><span style=\"height:0.9819em;\"><span></span></span></span></span></span><span style=\"width:0.5em;\"></span><span style=\"width:0.5em;\"></span><span><span><span><span style=\"height:1.4819em;\"><span style=\"top:-3.61em;\"><span style=\"height:3em;\"></span><span><span><span style=\"top:0em;\">(</span><span><span>λ</span><span><span><span><span style=\"height:0.3011em;\"><span style=\"top:-2.55em;margin-left:0em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span><span>2</span></span></span></span></span><span></span></span><span><span style=\"height:0.15em;\"><span></span></span></span></span></span></span><span style=\"margin-right:0.2222em;\"></span><span>−</span><span style=\"margin-right:0.2222em;\"></span><span><span>λ</span><span><span><span><span style=\"height:0.3011em;\"><span style=\"top:-2.55em;margin-left:0em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span><span>1</span></span></span></span></span><span></span></span><span><span style=\"height:0.15em;\"><span></span></span></span></span></span></span><span style=\"top:0em;\">)</span></span><span style=\"margin-right:0.1667em;\"></span><span>cos</span><span style=\"margin-right:0.1667em;\"></span><span>ϕ</span><span style=\"margin-right:0.1667em;\"></span><span>sin</span><span style=\"margin-right:0.1667em;\"></span><span>ϕ</span></span></span><span style=\"top:-2.3781em;\"><span style=\"height:3em;\"></span><span><span><span>λ</span><span><span><span><span style=\"height:0.3011em;\"><span style=\"top:-2.55em;margin-left:0em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span><span>2</span></span></span></span></span><span></span></span><span><span style=\"height:0.15em;\"><span></span></span></span></span></span></span><span style=\"margin-right:0.1667em;\"></span><span><span>cos</span><span><span><span><span style=\"height:0.8141em;\"><span style=\"top:-3.063em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span><span>2</span></span></span></span></span></span></span></span></span><span style=\"margin-right:0.1667em;\"></span><span>ϕ</span><span style=\"margin-right:0.2222em;\"></span><span>+</span><span style=\"margin-right:0.2222em;\"></span><span><span>λ</span><span><span><span><span style=\"height:0.3011em;\"><span style=\"top:-2.55em;margin-left:0em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span><span>1</span></span></span></span></span><span></span></span><span><span style=\"height:0.15em;\"><span></span></span></span></span></span></span><span style=\"margin-right:0.1667em;\"></span><span><span>sin</span><span><span><span><span style=\"height:0.8719em;\"><span style=\"top:-3.1208em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span><span>2</span></span></span></span></span></span></span></span></span><span style=\"margin-right:0.1667em;\"></span><span>ϕ</span></span></span></span><span></span></span><span><span style=\"height:0.9819em;\"><span></span></span></span></span></span><span style=\"width:0.5em;\"></span></span></span><span style=\"top:0em;\"><span>]</span></span></span></span></span></span></span><span></span></span><span><span style=\"height:0.9819em;\"><span></span></span></span></span></span><span style=\"width:0.5em;\"></span></span></span></span></span></span></span></p>\n<h2 id=\"git-hub-tables\"> git hub tables</h2>\n<table>\n<thead>\n<tr>\n<th>Tables</th>\n<th style=\"text-align:center\">Are</th>\n<th style=\"text-align:right\">Cool</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>col 3 is</td>\n<td style=\"text-align:center\">right-aligned</td>\n<td style=\"text-align:right\">$1600</td>\n</tr>\n<tr>\n<td>col 2 is</td>\n<td style=\"text-align:center\">centered</td>\n<td style=\"text-align:right\">$12</td>\n</tr>\n<tr>\n<td>zebra stripes</td>\n<td style=\"text-align:center\">are neat</td>\n<td style=\"text-align:right\">$1</td>\n</tr>\n</tbody>\n</table>\n<h2 id=\"container\"> container</h2>\n<div><p>Tips</p>\n<p>this is tips</p>\n</div>\n<div><p>Note</p>\n<p>this is warning</p>\n</div>\n<div><p>STOP</p>\n<p>this is danger warning</p>\n</div>\n<details><summary>custom title</summary>\n<p>more details here</p>\n</details>\n<h2 id=\"语法高亮\"> 语法高亮</h2>\n<p>代码块内某些行高亮。{}里面行号之间不能有空格。</p>\n<div><div><br><div> </div><br><br><br><div> </div><br><br></div><pre><code><span><span>#</span><span>version</span> <span><span>330</span> core</span></span>\n<span>(</span><span>layout</span> location <span>=</span> <span>0</span><span>)</span> <span>in</span> <span>vec3</span> a_Pos\n\n<span>void</span> <span>main</span><span>(</span><span>)</span>\n<span>{</span>\n gl_Position <span>=</span> a_Pos<span>;</span>\n<span>}</span>\n</code></pre><div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br></div></div><h2 id=\"部署\"> 部署</h2>\n<p>按文档来,没成功。git push -f xxxxx master 会在github库中新建一个master分支,而且有时候推送失败,原因不明。<br>\n因为我安装了 wsl2,执行bash命令会进入Ubantu18.04,也导致shell脚本出错。<br>\n最终,解决方法如下:</p>\n<p>在package.json中添加npm命令,用于执行shell脚本。</p>\n<div><div><br><br><br><div> </div><br><br></div><pre><code><span>\"scripts\"</span><span>:</span> <span>{</span>\n <span>\"docs:dev\"</span><span>:</span> <span>\"vuepress dev docs\"</span><span>,</span>\n <span>\"docs:build\"</span><span>:</span> <span>\"vuepress build docs\"</span><span>,</span>\n <span>\"docs:deploy\"</span><span>:</span> <span>\"deploy.sh\"</span>\n <span>}</span><span>,</span>\n</code></pre><div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br></div></div><p>手动clone GithubPage关联库 https://github.com/username/username.github.io.git 到本地,并放在目录project/docs/.vuepress/下。(和生成的dist目录同级)</p>\n<p>deploy.sh内容为</p>\n<div><pre><code><span>#!/usr/bin/env sh</span>\n\n<span># abort on errors</span>\n<span>set</span> -e\n\n<span># build</span>\n<span>npm</span> run docs:build\n\n<span># navigate into the build output directory</span>\n<span>cd</span> docs/.vuepress/dist\n\n<span># 将一个目录下的一些文件移动到另一个目录下</span>\n<span>cp</span> -R * <span>..</span>/username.github.io\n\n<span>cd</span> <span>..</span>/username.github.io\n\n<span>git</span> <span>add</span> -A\n<span>git</span> commit -m <span>'deploy'</span>\n<span>git</span> push\n\n<span>cd</span> -\n\n<span># 执行完不立即退出shell,便于查看错误信息。</span>\n<span>read</span> -n <span>1</span> -p <span>'Press any key to continue...'</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br></div></div><p>deploy.sh 实际上完成了三件事</p>\n<ol>\n<li>执行 npm run docs:build 生成要发布的内容。(重复生成时 dist 文件夹会先被清空,再生成新文件。)</li>\n<li>将 dist 文件夹中的所有文件都复制到 username.githbu.io 文件夹中</li>\n<li>在 username.githbu.io 文件夹中(GithubPage关联的库),提交修改并推送到远程库</li>\n</ol>\n<h2 id=\"全局computed\"> 全局Computed</h2>\n<p>因为 ketex 公式分隔符是 'dollar', 和 vue 提供的全局变量前缀冲突了。所以一页只能有一个全局变量保持正常。<br>\nsite = {{$site}}</p>\n",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-12-13T15:53:11.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "Intro Page",
"url": "https://kigane.github.io/intro/",
"id": "https://kigane.github.io/intro/",
"content_html": "<h1 id=\"intro-page\"> Intro Page</h1>\n<p>Place your introducation and profile here.</p>\n",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "这主题废弃了标题?",
"url": "https://kigane.github.io/home/",
"id": "https://kigane.github.io/home/",
"content_html": "<p>This is an example of a normal homepage. You can place your main content here.</p>\n<p>To use this layout, you need to set <code>home: true</code> in the page front matter.</p>\n<p>For related descriptions of configuration items, please see <a href=\"https://vuepress-theme-hope.github.io/guide/layout/home/\" target=\"_blank\" rel=\"noopener noreferrer\">Project HomePage Layout Config</a>.</p>\n",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2022-04-02T08:34:13.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "Custom Layout",
"url": "https://kigane.github.io/layout/",
"id": "https://kigane.github.io/layout/",
"content_html": "<p>You can use slots with markdown and component support to custom page layout.</p>\n<div><p>Note</p>\n<p>This is just a demo, you should add styles according to your own needs.</p>\n\n\n</div>\n<template #page-top><p>Page top content</p>\n</template><template #page-bottom><p>Page bottom content</p>\n</template><template #content-top><p>Content top content</p>\n</template><template #content-bottom><p>Content bottom content</p>\n</template><template #navbar-start><p>Navbar start content</p>\n</template><template #navbar-center><p>Navbar center content</p>\n</template><template #navbar-end><p>Navbar end content</p>\n</template><template #sidebar-top><p>Sidebar top content</p>\n</template><template #sidebar-center><p>Sidebar center content</p>\n</template><template #sidebar-bottom><p>Sidebar bottom content</p>\n</template><p>For details, see <a href=\"https://vuepress-theme-hope.github.io/guide/layout/custom/\" target=\"_blank\" rel=\"noopener noreferrer\">Custom layout</a>.</p>\n",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "UML类图简介",
"url": "https://kigane.github.io/note/algorithm/",
"id": "https://kigane.github.io/note/algorithm/",
"content_html": "<h2 id=\"类的表示\"> 类的表示</h2>\n<ul>\n<li>类名\n<ul>\n<li><code><<Interface>></code></li>\n<li><code><<abstract>></code></li>\n<li><code><<Service>></code></li>\n<li><code><<enumeration>></code></li>\n</ul>\n</li>\n<li>数据: 保护级别+类型+变量名\n<ul>\n<li>保护级别:public +, protected #, private -, package/internal ~</li>\n</ul>\n</li>\n<li>方法: 保护级别+方法名(参数)+返回值</li>\n</ul>\n<Mermaid id=\"mermaid-382ee1ab\" data-code=\"classDiagram%0A%20%20%20%20Duck%0A%0A%20%20%20%20class%20Duck%20%7B%0A%20%20%20%20%20%20%20%20%3C%3CInterface%3E%3E%0A%20%20%20%20%20%20%20%20-%20int%20weight%0A%20%20%20%20%20%20%20%20%2B%20Quark()%20void%0A%20%20%20%20%7D%0A\"></Mermaid><h2 id=\"关系\"> 关系</h2>\n<h3 id=\"泛化关系-generalization\"> 泛化关系(generalization)</h3>\n<ul>\n<li>类的继承结构表现在UML中为:泛化(generalize)与实现(realize)</li>\n<li>继承关系为 is-a 的关系,两个对象之间如果可以用 is-a 来表示,就是继承关系</li>\n<li>最终代码中,泛化关系表现为继承非抽象类</li>\n<li>用实线+三角箭头表示,mermaid.js中用<code><|--</code>表示</li>\n</ul>\n<h3 id=\"实现关系-realize\"> 实现关系(realize)</h3>\n<ul>\n<li>最终代码中,实现关系表现为继承抽象类</li>\n<li>用虚线+三角箭头表示,mermaid.js中用<code>..|></code>表示</li>\n</ul>\n<h3 id=\"聚合关系-aggregation\"> 聚合关系(aggregation)</h3>\n<ul>\n<li>用于表示实体对象之间的关系,表示整体由部分构成的语义</li>\n<li>与组合关系不同的是,整体和部分不是强依赖的,即使整体不存在了,部分仍然存在。如打工人和公司</li>\n<li>用实线+空心菱形箭头表示,mermaid.js中用<code>o--</code>表示</li>\n</ul>\n<h3 id=\"组合关系-composition\"> 组合关系(composition)</h3>\n<ul>\n<li>用于表示实体对象之间的关系,表示整体由部分构成的语义</li>\n<li>整体和部分是强依赖的,整体不存在了,部分也就不存在了。如公司和部门</li>\n<li>用实线+实心菱形箭头表示,mermaid.js中用<code>*--</code>表示</li>\n</ul>\n<h3 id=\"关联关系-association\"> 关联关系(association)</h3>\n<ul>\n<li>它一般用来定义对象之间静态的、天然的结构</li>\n<li>关联关系默认不强调方向,表示对象间相互知道</li>\n<li>用一条直线表示,mermaid.js中用<code>--</code>表示,强调方向用<code>--></code>表示</li>\n<li>在最终代码中,关联对象通常是以成员变量的形式实现的</li>\n</ul>\n<h3 id=\"依赖关系-dependency\"> 依赖关系(dependency)</h3>\n<ul>\n<li>描述一个对象在运行期间会用到另一个对象的关系</li>\n<li>与关联关系不同的是,它是一种临时性的关系,通常在运行期间产生,并且随着运行时的变化; 依赖关系也可能发生变化</li>\n<li>双向依赖是一种非常糟糕的结构,我们总是应该保持单向依赖,杜绝双向依赖的产生</li>\n<li>用虚线+箭头表示,mermaid.js中用<code>..></code>表示</li>\n<li>在最终代码中,依赖关系体现为类构造方法及类方法的传入参数,箭头的指向为调用关系</li>\n</ul>\n",
"date_published": "2022-02-06T00:00:00.000Z",
"date_modified": "2022-02-19T05:19:13.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": [
"SoftwareEngineering"
]
},
{
"title": "观察者模式--Observer",
"url": "https://kigane.github.io/note/algorithm/observer/",
"id": "https://kigane.github.io/note/algorithm/observer/",
"content_html": "<h2 id=\"定义\"> 定义</h2>\n<p>定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式。</p>\n<h2 id=\"理解\"> 理解</h2>\n<p>被观察者实现IObservable接口,包含基本的Add(),Remove(),Notify()方法用于注册观察者,移除观察者,和在状态发生变化时,通知观察者。Notify()实际上是调用了观察者的Update()方法。观察者IObserver接口中只要一个方法Update(),用于响应被观察者发生的变化。</p>\n<p>一个比较好的实践为,在IObserver的具体实现类中,保存一个IObservable的引用。</p>\n<h2 id=\"类图\"> 类图</h2>\n<Mermaid id=\"mermaid-382ee14d\" data-code=\"classDiagram%0A%20%20%20%20direction%20BT%0A%20%20%20%20IObservable%20--%3E%20%22many%22%20IObserver%0A%20%20%20%20ConcreteObservable%20..%7C%3E%20IObservable%0A%20%20%20%20ConcreteObserver%20..%7C%3E%20IObserver%0A%20%20%20%20ConcreteObserver%20--%3E%20ConcreteObservable%0A%0A%20%20%20%20class%20IObservable%20%7B%0A%20%20%20%20%20%20%20%20%3C%3Cinterface%3E%3E%0A%20%20%20%20%20%20%20%20%2B%20AddObserver()%20void%0A%20%20%20%20%20%20%20%20%2B%20RemoveObserver()%20void%0A%20%20%20%20%20%20%20%20%2B%20Notify()%20void%0A%20%20%20%20%7D%0A%0A%20%20%20%20class%20IObserver%20%7B%0A%20%20%20%20%20%20%20%20%3C%3Cinterface%3E%3E%0A%20%20%20%20%20%20%20%20%2B%20Update()%20void%0A%20%20%20%20%7D%0A%0A%20%20%20%20class%20ConcreteObservable%20%7B%0A%20%20%20%20%20%20%20%20-%20Set~T~%20observers%0A%0A%20%20%20%20%20%20%20%20%2B%20AddObserver()%20void%0A%20%20%20%20%20%20%20%20%2B%20RemoveObserver()%20void%0A%20%20%20%20%20%20%20%20%2B%20Notify()%20void%0A%20%20%20%20%7D%0A%0A%20%20%20%20class%20ConcreteObserver%20%7B%0A%20%20%20%20%20%20%20%20-%20IObservable%20ob%0A%0A%20%20%20%20%20%20%20%20%2B%20Update()%20void%0A%20%20%20%20%7D%0A%0A\"></Mermaid>",
"date_published": "2022-02-07T00:00:00.000Z",
"date_modified": "2022-02-19T05:19:13.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": [
"SoftwareEngineering"
]
},
{
"title": "策略模式--Strategy",
"url": "https://kigane.github.io/note/algorithm/strategy/",
"id": "https://kigane.github.io/note/algorithm/strategy/",
"content_html": "<h2 id=\"定义\"> 定义</h2>\n<p>定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。</p>\n<h2 id=\"理解\"> 理解</h2>\n<p>继承是个强大的技巧,但很多情况下并不需要使用继承,或者继承不够好。<br>\n<strong>继承只能在不同类层级之间共享</strong>,子类在父类的基础上修改。而没有办法在相同层级进行共享。<br>\n例如,同意父类的6个子类,都有一个方法各不相同。但又有一个方法是3个子类一组,组间不同,组内相同的。继承方法无法共享,只能将相同的代码重复3遍。</p>\n<p>策略模式通过多态机制,可以很好的实现这种<strong>同级共享</strong>。具体来说,<strong>将方法抽象为接口,不同的实现封装为不同的实体类</strong>。原来拥有方法的类,现在持有抽象的接口,通过构造函数或Setter方法传入不同的实体类就可以让该类表现出不同的行为。</p>\n<h2 id=\"类图\"> 类图</h2>\n<Mermaid id=\"mermaid-382ee14d\" data-code=\"classDiagram%0A%20%20%20%20Client%20--%3E%20IStrategy%0A%20%20%20%20IStrategy%20%3C%7C..%20CommonStrategy%0A%20%20%20%20IStrategy%20%3C%7C..%20AmazingStrategy%0A%0A%20%20%20%20class%20Client%7B%0A%20%20%20%20%20%20%20%20-%20IStrategy%20strategy%0A%20%20%20%20%20%20%20%20%2B%20Strategy()%20void%0A%20%20%20%20%20%20%20%20%2B%20SetStrategy(IStrategy%20s)%20void%0A%20%20%20%20%7D%0A%0A%20%20%20%20class%20IStrategy%7B%0A%20%20%20%20%20%20%20%20%3C%3CInterface%3E%3E%0A%20%20%20%20%20%20%20%20%2B%20Strategy()%20void%0A%20%20%20%20%7D%0A%0A%20%20%20%20class%20CommonStrategy%7B%0A%20%20%20%20%20%20%20%20%2B%20Strategy()%20void%0A%20%20%20%20%7D%0A%0A%20%20%20%20class%20AmazingStrategy%7B%0A%20%20%20%20%20%20%20%20%2B%20Strategy()%20void%0A%20%20%20%20%7D%0A\"></Mermaid>",
"date_published": "2022-02-06T00:00:00.000Z",
"date_modified": "2022-02-19T05:19:13.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": [
"SoftwareEngineering"
]
},
{
"title": "C++ 小知识",
"url": "https://kigane.github.io/note/cpp/",
"id": "https://kigane.github.io/note/cpp/",
"content_html": "<h2 id=\"lvalue-rvalue\"> lvalue & rvalue</h2>\n<p>简单来说</p>\n<ul>\n<li>左值(lvalue):是变量,占用某一块确定的内存。</li>\n<li>右值(rvalue):是字面量或临时量。</li>\n</ul>\n<div><pre><code><span>int</span> i <span>=</span> <span>10</span><span>;</span> <span>// i 是左值, 10 是右值(字面量) </span>\n<span>int</span> a <span>=</span> i<span>;</span> <span>// a, i 都是左值</span>\n<span>int</span> b <span>=</span> a <span>+</span> i<span>;</span> <span>// b 是左值,a+i 是右值(临时量)</span>\n\n<span>int</span> <span>GetInt</span><span>(</span><span>)</span><span>{</span> <span>int</span> a <span>=</span> <span>7</span><span>;</span><span>return</span> a<span>;</span><span>}</span> <span>// 返回值是右值</span>\n<span>int</span><span>&</span> <span>GetInt</span><span>(</span><span>int</span><span>&</span> a<span>)</span><span>{</span> a <span>=</span> <span>7</span><span>;</span><span>return</span> a<span>;</span><span>}</span> <span>// 返回值是左值</span>\n\n<span>// 有一些方法如下</span>\n<span>void</span> <span>GetVal1</span><span>(</span><span>int</span> a<span>)</span><span>{</span>cout <span><<</span> a<span>;</span><span>}</span>\n<span>void</span> <span>GetVal2</span><span>(</span><span>int</span><span>&</span> a<span>)</span><span>{</span>cout <span><<</span> a<span>;</span><span>}</span>\n<span>void</span> <span>GetVal3</span><span>(</span><span>const</span> <span>int</span><span>&</span> a<span>)</span><span>{</span>cout <span><<</span> a<span>;</span><span>}</span>\n<span>void</span> <span>GetVal4</span><span>(</span><span>int</span><span>&&</span> a<span>)</span><span>{</span>cout <span><<</span> a<span>;</span><span>}</span>\n\n<span>GetVal1</span><span>(</span>a<span>+</span>i<span>)</span><span>;</span> <span>// 正常</span>\n<span>GetVal2</span><span>(</span>a<span>+</span>i<span>)</span><span>;</span> <span>// 编译错误,非常量引用的初始值必须为左值</span>\n<span>GetVal3</span><span>(</span>a<span>+</span>i<span>)</span><span>;</span> <span>// 正常。常量引用的初始值可以是左值,也可以是右值 ==> 所以字符串参数常常声明为常量引用。</span>\n<span>GetVal4</span><span>(</span>a<span>+</span>i<span>)</span><span>;</span> <span>// 正常。&& 表示右值引用。</span>\n<span>GetVal4</span><span>(</span>b<span>)</span><span>;</span> <span>// 编译错误,无法将右值引用绑定到左值。</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br></div></div><p>有啥好处呢?好处在于可以通过重载函数,区分出传入的参数是不是临时的,因此可以做一些特殊处理。</p>\n<div><pre><code><span>// 可以接受左值和右值</span>\n<span>void</span> <span>PrintName</span><span>(</span><span>const</span> string<span>&</span> name<span>)</span><span>{</span>cout <span><<</span> <span>\"[lvalue]\"</span> <span><<</span> name<span>;</span><span>}</span>\n<span>// 对于右值,有特殊重载</span>\n<span>void</span> <span>PrintName</span><span>(</span>string<span>&&</span> name<span>)</span><span>{</span>cout <span><<</span> <span>\"[rvalue]\"</span> <span><<</span> name<span>;</span><span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br></div></div><h2 id=\"移动语义-move-semantics\"> 移动语义(move semantics)</h2>\n<p>先看一个例子</p>\n<div><pre><code><span>class</span> <span>String</span>\n<span>{</span>\n<span>public</span><span>:</span>\n <span>String</span><span>(</span><span>)</span> <span>=</span> <span>default</span><span>;</span>\n <span>String</span><span>(</span><span>const</span> <span>char</span><span>*</span> str<span>)</span>\n <span>{</span>\n <span>printf</span><span>(</span><span>\"Created!\\n\"</span><span>)</span><span>;</span>\n m_Size <span>=</span> <span>strlen</span><span>(</span>str<span>)</span><span>;</span>\n m_Data <span>=</span> <span>new</span> <span>char</span><span>[</span>m_Size<span>]</span><span>;</span>\n <span>memcpy</span><span>(</span>m_Data<span>,</span> str<span>,</span> m_Size<span>)</span><span>;</span>\n <span>}</span>\n\n <span>String</span><span>(</span><span>const</span> String<span>&</span> other<span>)</span>\n <span>{</span>\n <span>printf</span><span>(</span><span>\"Copied!\\n\"</span><span>)</span><span>;</span>\n m_Size <span>=</span> other<span>.</span>m_Size<span>;</span>\n m_Data <span>=</span> <span>new</span> <span>char</span><span>[</span>m_Size<span>]</span><span>;</span>\n <span>memcpy</span><span>(</span>m_Data<span>,</span> other<span>.</span>m_Data<span>,</span> m_Size<span>)</span><span>;</span>\n <span>}</span>\n\n <span>~</span><span>String</span><span>(</span><span>)</span>\n <span>{</span>\n <span>printf</span><span>(</span><span>\"Deleted!\\n\"</span><span>)</span><span>;</span>\n <span>delete</span> m_Data<span>;</span>\n <span>}</span> \n\n <span>void</span> <span>Print</span><span>(</span><span>)</span>\n <span>{</span>\n <span>for</span> <span>(</span><span>int</span> i <span>=</span> <span>0</span><span>;</span> i <span><</span> m_Size<span>;</span> <span>++</span>i<span>)</span>\n <span>printf</span><span>(</span><span>\"%c\"</span><span>,</span> m_Data<span>[</span>i<span>]</span><span>)</span><span>;</span>\n <span>printf</span><span>(</span><span>\"\\n\"</span><span>)</span><span>;</span>\n <span>}</span>\n<span>private</span><span>:</span>\n <span>int</span> m_Size<span>;</span>\n <span>char</span><span>*</span> m_Data<span>;</span>\n<span>}</span><span>;</span>\n\n<span>class</span> <span>Entity</span>\n<span>{</span>\n<span>public</span><span>:</span>\n <span>Entity</span><span>(</span><span>)</span> <span>=</span> <span>default</span><span>;</span>\n <span>Entity</span><span>(</span><span>const</span> String<span>&</span> name<span>)</span> <span>:</span> <span>m_Name</span><span>(</span>name<span>)</span> <span>{</span><span>}</span>\n <span>void</span> <span>PrintName</span><span>(</span><span>)</span><span>{</span> m_Name<span>.</span><span>Print</span><span>(</span><span>)</span><span>;</span> <span>}</span>\n<span>private</span><span>:</span>\n String m_Name<span>;</span>\n<span>}</span><span>;</span>\n\n<span>int</span> <span>main</span><span>(</span><span>)</span> <span>{</span>\n Entity <span>entity</span><span>(</span><span>\"Sekiro\"</span><span>)</span><span>;</span>\n entity<span>.</span><span>PrintName</span><span>(</span><span>)</span><span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br><span>25</span><br><span>26</span><br><span>27</span><br><span>28</span><br><span>29</span><br><span>30</span><br><span>31</span><br><span>32</span><br><span>33</span><br><span>34</span><br><span>35</span><br><span>36</span><br><span>37</span><br><span>38</span><br><span>39</span><br><span>40</span><br><span>41</span><br><span>42</span><br><span>43</span><br><span>44</span><br><span>45</span><br><span>46</span><br><span>47</span><br><span>48</span><br><span>49</span><br><span>50</span><br><span>51</span><br></div></div><p>执行结果为:</p>\n<div><pre><code>Created!\nCopied!\nDeleted!\nSekiro\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br></div></div><p>这意味着,在Entity(String name)的初始化列表中,为右值"Sekiro"调用String的相应构造函数,再调用String的拷贝构造函数,将其值传入Entity.m_Name,最后销毁保存"Sekiro"的临时对象。\n一个简单的赋值操作,竟调用了两次构造函数,在堆上分配了两次内存。问题很大🤔。</p>\n<p>解决方法是,对于传入的右值做特殊处理,在构造出来临时对象后,直接赋值,不要再调用拷贝构造函数。</p>\n<div><pre><code><span>// 在String中添加</span>\n<span>String</span><span>(</span>String<span>&&</span> other<span>)</span> <span>noexcept</span>\n<span>{</span>\n <span>printf</span><span>(</span><span>\"Moved!\\n\"</span><span>)</span><span>;</span>\n m_Size <span>=</span> other<span>.</span>m_Size<span>;</span>\n m_Data <span>=</span> other<span>.</span>m_Data<span>;</span>\n\n <span>// 临时变量会被销毁,所以需要防止析构时删除数据</span>\n other<span>.</span>m_Data <span>=</span> <span>nullptr</span><span>;</span>\n other<span>.</span>m_Size <span>=</span> <span>0</span><span>;</span>\n<span>}</span>\n\n<span>// 在 Entity 中修改 std::move(name) 相当于 (String&&) name</span>\n<span>Entity</span><span>(</span><span>const</span> String<span>&</span> name<span>)</span> <span>:</span> <span>m_Name</span><span>(</span>std<span>::</span><span>move</span><span>(</span>name<span>)</span><span>)</span><span>{</span><span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br></div></div><p>现在执行结果为</p>\n<div><pre><code>Created!\nMoved!\nDeleted!\nSekiro\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br></div></div><p>节省了一次堆内存分配。</p>\n<h2 id=\"inline\"> inline</h2>\n<p>直接在类中声明并实现的方法是隐式的inline函数,不需要再加inline前缀。</p>\n<h2 id=\"constexpr\"> constexpr</h2>\n<p>指明变量或函数的值可以出现在常量表达式中。通过添加constexpr来声明的变量或函数,可以在编译期进行计算。</p>\n<ul>\n<li>literal type: 标量类型,引用类型,以及前两种的数组类型。(C++20还可能有更多)</li>\n<li>constexpr 变量,函数返回值和参数类型必须是literal type。</li>\n<li>inline是将函数体直接搬过来,减少函数调用的开销。constexpr是直接将编译器可计算的结果算出来,来减少调用,执行函数的开销。</li>\n</ul>\n<div><pre><code><span><span>#</span><span>include</span><span><iostream></span></span>\n<span>using</span> <span>namespace</span> std<span>;</span>\n \n<span>constexpr</span> <span>long</span> <span>int</span> <span>fib</span><span>(</span><span>int</span> n<span>)</span>\n<span>{</span>\n <span>return</span> <span>(</span>n <span><=</span> <span>1</span><span>)</span><span>?</span> n <span>:</span> <span>fib</span><span>(</span>n<span>-</span><span>1</span><span>)</span> <span>+</span> <span>fib</span><span>(</span>n<span>-</span><span>2</span><span>)</span><span>;</span>\n<span>}</span>\n \n<span>int</span> <span>main</span> <span>(</span><span>)</span>\n<span>{</span>\n <span>// value of res is computed at compile time. 执行的非常快。</span>\n <span>const</span> <span>long</span> <span>int</span> res <span>=</span> <span>fib</span><span>(</span><span>30</span><span>)</span><span>;</span> \n cout <span><<</span> res<span>;</span>\n <span>return</span> <span>0</span><span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br></div></div><h2 id=\"可变参数\"> 可变参数</h2>\n<ul>\n<li>在 stdarg.h 中</li>\n<li>va_list 为存储可变参数的类型。即function中的 <code>...</code></li>\n<li>va_start() 宏。有两个参数,第一个为va_list类型的变量,第二个为可变参数<code>...</code>的前一个参数。用于初始化可变参数列表。</li>\n<li>va_arg() 宏。有两个参数,第一个为va_list类型的变量,第二个为用于接受可变参数的类型。用于获取可变参数列表中的下一个参数。(PS:printf的format string就是用于确定va_arg需要处理的变量类型。)</li>\n<li>va_end() 宏。一个参数,为va_list类型的变量。用于清理va_list</li>\n</ul>\n<p>示例:</p>\n<div><pre><code><span><span>#</span><span>include</span> <span><stdarg.h></span></span>\n<span>double</span> <span>avg</span><span>(</span><span>int</span> num<span>,</span> <span>.</span><span>.</span><span>.</span><span>)</span>\n<span>{</span>\n va_list args<span>;</span>\n <span>double</span> sum <span>=</span> <span>0</span><span>;</span>\n <span>va_start</span><span>(</span>args<span>,</span> num<span>)</span><span>;</span>\n <span>for</span> <span>(</span><span>int</span> i <span>=</span> <span>0</span><span>;</span> i <span><</span> num<span>;</span> <span>++</span>i<span>)</span>\n <span>{</span>\n sum <span>+=</span> <span>va_arg</span><span>(</span>args<span>,</span> <span>double</span><span>)</span><span>;</span>\n <span>}</span>\n <span>va_end</span><span>(</span>args<span>)</span><span>;</span>\n <span>return</span> sum <span>/</span> num<span>;</span>\n<span>}</span>\n\n<span>int</span> <span>main</span><span>(</span><span>int</span> argc<span>,</span> <span>const</span> <span>char</span><span>*</span> argv<span>[</span><span>]</span><span>)</span>\n<span>{</span>\n <span>printf</span><span>(</span><span>\"average = %4.2f\\n\"</span><span>,</span> <span>avg</span><span>(</span><span>3</span><span>,</span> <span>1.2</span><span>,</span> <span>3.4</span><span>,</span> <span>5.6</span><span>)</span><span>)</span><span>;</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br></div></div><p>PS: float在通过<code>...</code>传递时,会被提升为double。如果用float接收,程序会abort。</p>\n",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "C语言",
"url": "https://kigane.github.io/note/cpp/c/",
"id": "https://kigane.github.io/note/cpp/c/",
"content_html": "<h2 id=\"union和匿名结构\"> Union和匿名结构</h2>\n<p>Union: 定义类似struct,但内存分配方式不一样。struct会为所有成员分配内存空间,union是所有成员共用一个内存空间,大小以最大的成员为准。\n匿名:通常嵌套于union或strcut中,可以用外层类型的变量名直接访问匿名的结构成员。union中的匿名比较有用,因为可以将一系列成员包裹起来以获取更大的内存空间。</p>\n<div><pre><code><span><span>#</span><span>include</span> <span><stdio.h></span></span>\n<span><span>#</span><span>include</span> <span><stdint.h></span></span>\n\n<span>typedef</span> <span>union</span> \n<span>{</span>\n <span>union</span>\n <span>{</span>\n <span>uint32_t</span> _32<span>;</span>\n <span>uint16_t</span> _16<span>;</span>\n <span>uint8_t</span> _8<span>[</span><span>2</span><span>]</span><span>;</span>\n <span>}</span> gpr<span>[</span><span>8</span><span>]</span><span>;</span> <span>// 32B</span>\n\n <span>struct</span>\n <span>{</span>\n <span>uint32_t</span> eax<span>,</span> ecx<span>,</span> edx<span>,</span> ebx<span>,</span> esp<span>,</span> ebp<span>,</span> esi<span>,</span> edi<span>;</span>\n <span>uint64_t</span> pc<span>;</span>\n <span>}</span><span>;</span> <span>// 40B</span>\n<span>}</span> CPU<span>;</span>\n\n\n<span>int</span> <span>main</span><span>(</span><span>int</span> argc<span>,</span> <span>char</span> <span>const</span> <span>*</span>argv<span>[</span><span>]</span><span>)</span>\n<span>{</span>\n CPU cpu<span>;</span>\n cpu<span>.</span>gpr<span>[</span><span>0</span><span>]</span><span>.</span>_32 <span>=</span> <span>666</span><span>;</span>\n <span>printf</span><span>(</span><span>\"CPU size: %d\\n\"</span><span>,</span> <span>sizeof</span><span>(</span>CPU<span>)</span><span>)</span><span>;</span> <span>// 40</span>\n <span>printf</span><span>(</span><span>\"cpu.eax: %d\\n\"</span><span>,</span> cpu<span>.</span>eax<span>)</span><span>;</span> <span>// 666</span>\n <span>return</span> <span>0</span><span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br><span>25</span><br><span>26</span><br><span>27</span><br><span>28</span><br></div></div><h3 id=\"bitfield\"> bitfield</h3>\n<p>只能在struct或union中声明。</p>\n<div><pre><code><span>struct</span> <span>{</span>\n <span>/* field 4 bits wide */</span>\n <span>unsigned</span> field1 <span>:</span><span>4</span><span>;</span>\n <span>/*\n * unnamed 3 bit field\n * unnamed fields allow for padding\n */</span>\n <span>unsigned</span> <span>:</span><span>3</span><span>;</span>\n <span>/*\n * one-bit field\n * can only be 0 or -1 in two's complement!\n */</span>\n <span>signed</span> field2 <span>:</span><span>1</span><span>;</span>\n <span>/* align next field on a storage unit */</span>\n <span>unsigned</span> <span>:</span><span>0</span><span>;</span>\n <span>unsigned</span> field3 <span>:</span><span>6</span><span>;</span>\n<span>}</span>full_of_fields<span>;</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br></div></div><ul>\n<li>1bit的有符号bitfield只能表示0和-1.</li>\n<li>bitfield可以用const,volatile修饰</li>\n<li>C不能保证机器内的字段的排序。标准没有定义,取决于机器和编译器的实现。</li>\n<li>要强制对准到存储单元边界,请在想要对齐的字段之前使用零宽度字段。</li>\n<li>gcc可能是从LSB开始分配的。</li>\n</ul>\n<h2 id=\"getopt-int-argc-char-const-argv-const-char-optstring\"> getopt(int argc, char* const argv[], const char* optstring)</h2>\n<p>选项处理过程</p>\n<div><pre><code><span>int</span> optind<span>;</span> <span>// argv中下一个要处理的元素的索引(初始为 1)</span>\n<span>int</span> opterr<span>;</span> <span>// 是否输出错误信息</span>\n<span>int</span> optopt<span>;</span> <span>// 导致错误的选项字符</span>\n<span>char</span><span>*</span> optarg<span>;</span>\n\n<span>struct</span> <span>option</span> <span>{</span>\n <span>const</span> <span>char</span> <span>*</span>name<span>;</span> <span>// 长选项(--option)的名称</span>\n <span>int</span> has_arg<span>;</span> <span>// 0:无参数 1:有参数 2:参数可选</span>\n <span>int</span> <span>*</span>flag<span>;</span> <span>// flag为NULL:getopt_long()返回val。否则getopt_long()返回0,flag指向的变量值为val(如果该option用户设置了的话)</span>\n <span>int</span> val<span>;</span> <span>// 返回值或放入flag</span>\n<span>}</span><span>;</span> <span>// 结构体数组的最后一个元素必须设为0</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br></div></div><p>如果getopt()找到下一个选项字符,就返回那个字符,更新optind和一个static变量nextchar</p>\n<p>optstring包含了所有合法的选项,如果字符后面跟着一个<code>:</code>,说明需要参数,两个说明是可选参数。<br>\n如果选项中包含文本(-oarg, o 是选项,arg是文本),则文本会保存在optarg中其他情况下,optarg设为0.\nGNU扩展,optstring中的<code>W:</code>表示将<code>-W foo</code>看作<code>--foo</code>看待。</p>\n<p>默认情况下,getopt()按输入顺序排列argv的内容。所以,所有的非选项参数放在最后。\noptstring的首字符决定使用的模式</p>\n<ul>\n<li><code>+</code> 遇到任何一个非选项参数就终止选项处理</li>\n<li><code>-</code> 强制将所有非选项参数看作选项的参数\n<code>--</code> 在任何模式下都可以强行终止选项处理过程。</li>\n</ul>\n<p>Errors</p>\n<ul>\n<li>处理的选项不再optstring中</li>\n<li>设置为有参数的选项没有检测到参数\n处理</li>\n<li>默认输出错误信息,将导致错误的选项字符放在optopt中,函数返回 ?</li>\n<li>如果调用者将opterr设为0,则不会输出错误信息,函数仍返回 ?</li>\n<li>如果optstring的第一个字符(不包括描述模式的<code>+,-</code>)为<code>:</code>。函数同样不输出错误信息,并且在设为有参数的选项没有参数时返回值变为<code>:</code>,从而可以区别出这种情况。</li>\n</ul>\n<p>getopt()返回值</p>\n<ul>\n<li>如果所有的命令行选项都处理完了,则返回 -1.</li>\n<li>如果遇到没在optstring中指定的字符,返回 ?</li>\n<li>如果遇到选项丢失参数的情况,看optstring中的第一个字符,如果是<code>:</code> 则返回 <code>:</code>,否则返回 ?</li>\n</ul>\n<p><code>int getopt_long(int argc, char* const argv[], const char* optstring, const struct option* longopts, int* longindex)</code></p>\n<p>getopt_long(),getopt_long_only()</p>\n<ul>\n<li>当识别到短选项时,也返回值</li>\n<li>长选项,见flag注释。</li>\n<li>Error和-1和getopt()一样</li>\n<li>识别到模糊匹配和冗余参数会返回 ?</li>\n<li>longindex为longopts的索引,如果不为NULL的话</li>\n</ul>\n<h2 id=\"readline\"> readline</h2>\n<p><code>char* readline(const char* prompt)</code><br>\nGNU的命令行编辑接口。readline会从终端读入一行并返回,其参数prompt为命令行开头的提示符(例如gdb中的"(gdb)")。其返回值不包括换行符,仅保留输入的文本。另外返回值使用malloc分配内存的,调用者需要手动free。</p>\n<h2 id=\"str系列\"> str系列</h2>\n<p>strtok</p>\n<ul>\n<li><code>char *strtok(char *str, const char *delim)</code>: 使用了static buffer,不是线程安全的。</li>\n<li><code>char *strtok_r(char *str, const char *delim, char **saveptr)</code>: 多线程安全</li>\n<li>这个函数用于将字符串分解为一系列非空token(子串)。第一次调用strtok时str是需要被分解的字符串,之后处理相同字符串时str必须为NULL。delim参数为用于分解的分隔符,可以指定多个字符为同一级分隔符。</li>\n<li>strtok内部有一个指针,该指针决定了每个token的起点。第一次调用时指向str的第一个字符,第二次调用时指向第一个分割符后第一个不是分割符的字符,如果找不到这样的字符,strtok返回NULL。</li>\n<li>每个token的终点为分隔符(会被替换为'\\0')或null('\\0')。</li>\n<li>strtok的返回值要么是非空字符串,要么是NULL。(例如,'aaa;bbb'分隔符为';',顺序调用strtok的返回值为 'aaa', 'bbb', NULL)</li>\n</ul>\n<div><pre><code><span><span>#</span><span>include</span> <span><stdio.h></span></span>\n<span><span>#</span><span>include</span> <span><stdlib.h></span></span>\n<span><span>#</span><span>include</span> <span><string.h></span></span>\n\n<span>int</span> <span>main</span><span>(</span><span>int</span> argc<span>,</span> <span>char</span> <span>const</span> <span>*</span>argv<span>[</span><span>]</span><span>)</span>\n<span>{</span>\n <span>char</span> <span>*</span>str1<span>,</span> <span>*</span>str2<span>,</span> <span>*</span>token<span>,</span> <span>*</span>subtoken<span>;</span>\n <span>char</span> <span>*</span>savaptr1<span>,</span> <span>*</span>saveptr2<span>;</span>\n <span>int</span> j<span>;</span>\n <span>if</span> <span>(</span>argc <span>!=</span> <span>4</span><span>)</span>\n <span>{</span>\n <span>fprintf</span><span>(</span><span>stderr</span><span>,</span> <span>\"Usage: %s string delim subdelim\\n\"</span><span>,</span> argv<span>[</span><span>0</span><span>]</span><span>)</span><span>;</span>\n <span>exit</span><span>(</span>EXIT_FAILURE<span>)</span><span>;</span>\n <span>}</span>\n\n <span>for</span> <span>(</span>j <span>=</span> <span>1</span><span>,</span> str1 <span>=</span> argv<span>[</span><span>1</span><span>]</span><span>;</span> <span>;</span>j<span>++</span><span>,</span> str1 <span>=</span> <span>NULL</span><span>)</span>\n <span>{</span>\n token <span>=</span> <span>strtok_r</span><span>(</span>str1<span>,</span> argv<span>[</span><span>2</span><span>]</span><span>,</span> <span>&</span>savaptr1<span>)</span><span>;</span>\n <span>printf</span><span>(</span><span>\"saveptr1 %d %s\\n\"</span><span>,</span> j<span>,</span> savaptr1<span>)</span><span>;</span> <span>// 剩余未处理的部分</span>\n <span>if</span> <span>(</span>token <span>==</span> <span>NULL</span><span>)</span>\n <span>break</span><span>;</span>\n <span>printf</span><span>(</span><span>\"%d: %s\\n\"</span><span>,</span> j<span>,</span> token<span>)</span><span>;</span>\n\n <span>for</span> <span>(</span>str2 <span>=</span> token<span>;</span> <span>;</span> str2 <span>=</span> <span>NULL</span><span>)</span>\n <span>{</span>\n subtoken <span>=</span> <span>strtok_r</span><span>(</span>str2<span>,</span> argv<span>[</span><span>3</span><span>]</span><span>,</span> <span>&</span>saveptr2<span>)</span><span>;</span>\n <span>if</span> <span>(</span>subtoken <span>==</span> <span>NULL</span><span>)</span>\n <span>break</span><span>;</span>\n <span>printf</span><span>(</span><span>\" --> %s\\n\"</span><span>,</span> subtoken<span>)</span><span>;</span>\n <span>}</span>\n <span>}</span>\n\n <span>exit</span><span>(</span>EXIT_SUCCESS<span>)</span><span>;</span>\n <span>return</span> <span>0</span><span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br><span>25</span><br><span>26</span><br><span>27</span><br><span>28</span><br><span>29</span><br><span>30</span><br><span>31</span><br><span>32</span><br><span>33</span><br><span>34</span><br><span>35</span><br></div></div><ul>\n<li><code>size_t strlen(const char *s)</code>:返回字符串的长度,即string中有多少byte。不包含'\\0'。</li>\n<li><code>size_t strnlen(const char *s, size_t max)</code>:返回字符串的长度,但不会超过最大值。</li>\n<li><code>int strcmp(const char *s1, const char *s2)</code>: 比较两个字符串是否相等。逐字符比较,如果第一个不相等的字符s1 < s2,则返回负值,反之返回正值。都相等则返回0。</li>\n<li><code>int strncmp(const char *s1, const char *s2, size_t n)</code>:比较两个字符串是否相等,但只比前n个。</li>\n<li><code>int strcoll(const char *s1, const char *s2)</code>:比较两个字符串是否相等,基于程序当前的locale设置解释的字符串,locale的值为环境变量LC_COLLATE。</li>\n<li><code>char *strcpy(char *dest,const char *src)</code>: 复制字符串,包含最后的'\\0'。dest需要足够大。返回dest指针。</li>\n<li><code>char *strncpy(char *dest,const char *src, size_t n)</code>:将src的前n个字符复制到dest。注意如果前n个字符中没有'\\0',则dest也不会添加,导致dest没有正常的终止符。如果n>strlen(src)+1,剩下的字符会用'\\0'补全。</li>\n<li><code>char *strcat(char *dest, const char *src)</code>: 将src(不包括'\\0')连接到dest后,dest的'\\0'会被覆盖,且会在整个字符串后添加一个'\\0'。dest要足够大。</li>\n<li><code>char *strncat(char *dest, const char *src, size_t n)</code>:类似strcat。但最多使用src中的n个字符。另外,src可以没有'\\0'。</li>\n</ul>\n<h2 id=\"scanf系列-printf系列\"> scanf系列&printf系列</h2>\n<ul>\n<li>\n<p><code>int scanf(const char *format, ...)</code></p>\n</li>\n<li>\n<p><code>int fscanf(FILE *stream, const char *format, ...)</code></p>\n</li>\n<li>\n<p><code>int sscanf(const char *str, const char *format, ...)</code></p>\n</li>\n<li>\n<p><code>sprintf, snprintf, fprintf</code></p>\n</li>\n<li>\n<p><code>int vscanf(const char *format, va_list ap)</code></p>\n</li>\n<li>\n<p><code>int vfscanf(FILE *stream, const char *format, va_list ap)</code></p>\n</li>\n<li>\n<p><code>int vsscanf(const char *str, const char *format, va_list ap)</code></p>\n</li>\n</ul>\n<p>format可能包含转换规约(%m.nf),转换的结果被储存在format后的指针中,指针的类型要可format中的转换规约一致。如果format中的%数量多于指针参数的数量,则结果未定义。反之,则多余的指针参数会被忽略。</p>\n<ul>\n<li>scanf从stdin读取输入。</li>\n<li>fscanf从指定的文件流stream中读取输入。</li>\n<li>sscanf从字符串str中读取输入。</li>\n<li>加v的读取变量参数列表(a variable arguemnt list)</li>\n</ul>\n<p>返回值</p>\n<ul>\n<li>成功:返回匹配的转换数</li>\n<li>失败:EOF</li>\n</ul>\n<p>PS:format可以是变量而非双引号字符串。例如:打印程序的命令行参数。</p>\n<div><pre><code><span>while</span> <span>(</span><span>--</span>argc <span>></span> <span>0</span><span>)</span>\n <span>printf</span><span>(</span><span>(</span>argc <span>></span> <span>1</span><span>)</span> <span>?</span> <span>\"%s \"</span> <span>:</span> <span>\"%s\"</span><span>,</span> <span>*</span><span>++</span>argv<span>)</span><span>;</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br></div></div><h3 id=\"转换规约\"> 转换规约</h3>\n<p>格式:<code>%#.#char</code> 其中第一个#为maximum field width。即最多匹配|输出的字符数。.#表示保留的小数点位数。char为转换说明符。\nprintf的格式串中</p>\n<ul>\n<li>\\NNN 最多3八进制数字,表示对应的ASCII字符</li>\n<li>\\xHH 最多两个十六进制数字,表示对应的ASCII字符</li>\n<li>\\uHHHH Unicode字符</li>\n<li>\\UHHHHHHHH Unicode字符</li>\n</ul>\n<p>输入</p>\n<ul>\n<li>%: 匹配%。</li>\n<li>d: 匹配有符号十进制整数,对应的指针类型必须为int*。</li>\n<li>i: 匹配有符号整数,对应的指针类型必须为int*。用于scanf,如果输入的整数以0x开头,则以16进制读,以0开头,则以8进制读,否则以十进制读。</li>\n<li>o: 匹配无符号八进制整数。对应的指针类型必须为unsigned int*。</li>\n<li>u: 匹配无符号十进制整数。对应的指针类型必须为unsigned int*。</li>\n<li>x|X: 匹配无符号十六进制整数。对应的指针类型必须为unsigned int*。</li>\n<li>f | e | E | g | a: 匹配浮点数。对应的指针类型必须为float*。</li>\n<li>s: 匹配一连串字符,到空白符或字符数组满了(要留一个位置给自动加上的'\\0')为止。对应的指针类型必须为char数组。</li>\n<li>%.5s: 表示从给定字符串中最多读5个字符。</li>\n<li>c: 匹配一个字符,不会加上'\\0'。对应的指针类型必须为char*。</li>\n<li>[char set]: 匹配指定字符集中的字符。对应的指针类型必须为char*。特殊字符<code>]</code>,要匹配它,必须将它放在<code>[</code>后,要排除它,则需放在<code>^</code>后。 <code>-</code>必须放在字符集的开头或结尾。</li>\n<li>p: 匹配指针。对应的指针类型必须为void**。</li>\n<li>n: 没看懂</li>\n</ul>\n<p>指针类型修改符</p>\n<ul>\n<li>\n<p>h: 针对diouxXn,将指针类型改为short int 或 unsigned short int。</p>\n</li>\n<li>\n<p>hh: signed char, unsigned char</p>\n</li>\n<li>\n<p>j: intmax_t,uintmax_t</p>\n</li>\n<li>\n<p>t: ptrdiff_t</p>\n</li>\n<li>\n<p>z: size_t</p>\n</li>\n<li>\n<p>l: signed long int, unsigned long int; 针对f,则指针类型变为double; 针对c,s,相关的指针类型被认为是wide charater?</p>\n</li>\n<li>\n<p>L: signed long long int, unsigned long long int; long long double</p>\n</li>\n<li>\n<p>m: %ms, %#mc 为输入字符串分配一块内存,指针类型改为char**。</p>\n</li>\n</ul>\n<p>输出\n<a href=\"https://alvinalexander.com/programming/printf-format-cheat-sheet/\" target=\"_blank\" rel=\"noopener noreferrer\">一些使用示例</a></p>\n<p>// FLag characters</p>\n<ul>\n<li>#: #必须紧跟在%后面,用于o,总是显示0####。用于x,总是显示0x####。用于[aAeEfFgG],总是会显示小数点。注意:0,0x是被算进field width的。</li>\n<li>0: 不足的位在左边用0补足。如果指定了浮点数的精度,则被忽略。</li>\n<li>-: 在maximum field width的左边界对齐。</li>\n<li>' ': 空格。有符号正数最前面放一个空格。</li>\n<li>+: 有符号正数前面加+。</li>\n</ul>\n<p>// Sing Unix Specification</p>\n<ul>\n<li>\n<p>': 十进制数以千为单位加逗号。</p>\n</li>\n<li>\n<p>I: idu, 使用本地对应的字符输出。</p>\n</li>\n<li>\n<p>d,i:十进制有符号</p>\n</li>\n<li>\n<p>o,u,x,X</p>\n</li>\n<li>\n<p>e,E: [-]d.ddde[+-]dd</p>\n</li>\n<li>\n<p>f,F: 常见小数形式</p>\n</li>\n<li>\n<p>g,G: 看情况转换为f,e,F,G中的一种</p>\n</li>\n<li>\n<p>a,A: 十六进制的小数形式0xh.hhhp[+-]</p>\n</li>\n<li>\n<p>c</p>\n</li>\n<li>\n<p>s</p>\n</li>\n<li>\n<p>p</p>\n</li>\n<li>\n<p>n</p>\n</li>\n<li>\n<p>%</p>\n</li>\n</ul>\n<h3 id=\"号\"> *号</h3>\n<div><pre><code><span>printf</span><span>(</span><span>\"%*d\"</span><span>,</span> <span>9</span><span>,</span> <span>123</span><span>)</span><span>;</span> <span>// ' 1223'</span>\n</code></pre>\n<div><span>1</span><br></div></div><p><code>*</code>在格式串中表示filed witdh和precision这两个部分使用下一个参数表示。<code>*</code>本身所在的<code>%</code>对应的参数则延后一位。<br>\n在Single UNIX Specification中还有另一种等价表示。</p>\n<div><pre><code><span>printf</span><span>(</span><span>\"%2$*1$d\"</span><span>,</span> <span>9</span><span>,</span> <span>123</span><span>)</span><span>;</span>\n</code></pre>\n<div><span>1</span><br></div></div><p>其中<code>%2$</code>代替了<code>%</code>,<code>*2$</code>代替了<code>*</code>,<code>#$</code>代表使用对应的参数位置。值得一提的是,这种写法可以通过<code>#$</code>使用同一个变量。可惜,C99中没有这个功能。</p>\n<h2 id=\"regex系列\"> regex系列</h2>\n<div><pre><code><span><span>#</span><span>include</span> <span><sys/types.h></span></span>\n<span><span>#</span><span>include</span> <span><regex.h></span></span>\n\n<span>int</span> <span>regcomp</span><span>(</span><span>regex_t</span> <span>*</span>preg<span>,</span> <span>const</span> <span>char</span> <span>*</span>regex<span>,</span> <span>int</span> cflags<span>)</span><span>;</span>\n<span>int</span> <span>regexec</span><span>(</span><span>const</span> <span>regex_t</span> <span>*</span>preg<span>,</span> <span>const</span> <span>char</span> <span>*</span>string<span>,</span> <span>size_t</span> nmatch<span>,</span>\n <span>regmatch_t</span> pmatch<span>[</span><span>]</span><span>,</span> <span>int</span> eflags<span>)</span><span>;</span>\n<span>size_t</span> <span>regerror</span><span>(</span><span>int</span> errcode<span>,</span> <span>regex_t</span> <span>*</span>preg<span>,</span> <span>char</span> <span>*</span>errbuf<span>,</span> <span>size_t</span> errbuf_size<span>)</span><span>;</span>\n<span>void</span> <span>regfree</span><span>(</span><span>regex_t</span> <span>*</span>preg<span>)</span><span>;</span>\n\n<span>typedef</span> <span>struct</span> <span>{</span>\n <span>regoff_t</span> rm_so<span>;</span> <span>// 匹配的子串开头位置</span>\n <span>regoff_t</span> rm_eo<span>;</span> <span>// 匹配子串的结尾位置,相对于子串开头。</span>\n<span>}</span> <span>regmatch_t</span><span>;</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br></div></div><p>regcomp()用于将正则表达式编译为适用于后续regexec()搜索的形式。</p>\n<ul>\n<li>preg 指针指向pattern buffer的储存区域</li>\n<li>regex 指针指向c风格字符串(the null-terminated string)</li>\n<li>cflags 用于决定编译的类型\n<ul>\n<li>REG_EXTENDED:使用POSIX扩展的正则表达式语法</li>\n<li>REG_ICASE: 忽略大小写</li>\n<li>REG_NOSUB: 不比报告匹配的位置。regexec()中的nmatch, pmatch参数会被忽略。</li>\n<li>REG_NEWLINE: <code>*</code>不会匹配 newline, <code>[^...]</code> 即使不包含newline,也不会匹配newline。紧跟在newline后<code>^</code>匹配空字符。newline前的<code>$</code>也立即匹配空字符。</li>\n</ul>\n</li>\n<li>eflags\n<ul>\n<li>REG_NOTBOL: <code>^</code>总是匹配失败。常用于一大堆字符串被拆分为多个子串分别传入regexec()处理。</li>\n<li>REG_NOTEOL: <code>$</code>总是匹配失败。</li>\n</ul>\n</li>\n<li>nmatch 匹配几个</li>\n<li>pmatch 匹配结果</li>\n<li>regcomp()返回0表示成功编译,其他为错误码。regexec返回0表示匹配成功,REG_NOMATCH表示匹配失败,其他为错误码。</li>\n</ul>\n<p>所有的正则表达式的搜索都必须使用编译过的pattern buffer。因此regexec的preg参数必须是经过regcomp编译过的。\nregerror()用于将regcomp()和regexec()的错误码转换为错误信息,存储在errbuf中。<br>\nregfree()释放预编译的pattern buffer的内存。</p>\n<h2 id=\"system\"> system</h2>\n<p><code>int system(const char *command);</code> 在 stdlib.h 中</p>\n<p>使用fork(2)创建子进程使用execl(3)执行command: execl("/bin/sh", "sh", "-c", command, (char *)NULL);</p>\n<h2 id=\"popen-pclose\"> popen/pclose</h2>\n<div><pre><code><span><span>#</span><span>include</span> <span><stdio.h></span></span>\nFILE <span>*</span><span>popen</span><span>(</span><span>const</span> <span>char</span> <span>*</span>command<span>,</span> <span>const</span> <span>char</span> <span>*</span>type<span>)</span><span>;</span>\n<span>int</span> <span>pclose</span><span>(</span>FILE <span>*</span>stream<span>)</span><span>;</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br></div></div><ul>\n<li>popen() 通过创建一个管道打开一个进程。因为管道是单向的,所以type只能是r或w。</li>\n<li>popen() 的返回值是一个标准I/O流,必须用pclose()关闭。</li>\n<li>popen() type=w时,向返回的流中写相当于向command的标准输入写。type=r时,从流中读,相当于从command的标准输出中读。</li>\n</ul>\n",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "随机数",
"url": "https://kigane.github.io/note/cpp/cpp-random/",
"id": "https://kigane.github.io/note/cpp/cpp-random/",
"content_html": "<h2 id=\"线性同余法\"> 线性同余法</h2>\n<div><pre><code><span>unsigned</span> <span>int</span> <span>PRNG</span><span>(</span><span>)</span> <span>// pseudo-random number generator</span>\n<span>{</span>\n <span>static</span> <span>unsigned</span> <span>int</span> seed<span>{</span><span>5323</span><span>}</span><span>;</span>\n seed <span>=</span> <span>8253729</span> <span>*</span> seed <span>+</span> <span>2396403</span><span>;</span>\n <span>return</span> seed <span>%</span> <span>32768</span><span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br></div></div><p>原理大致如上。这样简单的随机数不够"好"。</p>\n<h2 id=\"c-标准库中的随机数生成\"> C 标准库中的随机数生成</h2>\n<p>原理就是线性同余法,不同编译器实现略有不同,且大多有缺陷。仅供学习使用。</p>\n<div><pre><code><span><span>#</span><span>include</span> <span><cstdlib></span> <span>// for std::rand() and std::srand()</span></span>\n<span><span>#</span><span>include</span> <span><ctime></span> <span>// for std::time()</span></span>\n\n<span>void</span> <span>PrintNumbersWithRand</span><span>(</span><span>)</span>\n<span>{</span>\n std<span>::</span><span>srand</span><span>(</span><span><span>static_cast</span><span><span><</span><span>unsigned</span> <span>int</span><span>></span></span></span><span>(</span>std<span>::</span><span>time</span><span>(</span><span>nullptr</span><span>)</span><span>)</span><span>)</span><span>;</span>\n <span>// 因为某些编译器中rand算法的缺陷,导致第一个随机数大概率相同,所以这里先调用一次去掉第一个</span>\n std<span>::</span><span>rand</span><span>(</span><span>)</span><span>;</span>\n\n <span>// 打印 100 个随机数</span>\n <span>for</span> <span>(</span><span>int</span> count<span>{</span><span>1</span><span>}</span><span>;</span> count <span><=</span> <span>100</span><span>;</span> <span>++</span>count<span>)</span>\n <span>{</span>\n std<span>::</span>cout <span><<</span> std<span>::</span><span>rand</span><span>(</span><span>)</span> <span><<</span> <span>'\\t'</span><span>;</span>\n\n <span>if</span> <span>(</span>count <span>%</span> <span>5</span> <span>==</span> <span>0</span><span>)</span>\n std<span>::</span>cout <span><<</span> <span>'\\n'</span><span>;</span>\n <span>}</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br></div></div><h2 id=\"一个好的伪随机数生成器-prng-的标准\"> 一个好的伪随机数生成器(PRNG)的标准</h2>\n<ul>\n<li>应该以大致相同的概率生成每个数字(生成六个数字5,6,7,8,9的概率大致相等),即数字分布均匀性。</li>\n<li>生成序列中的下一个数字应该是不可预测。(num = num + 1)</li>\n<li>生成的数字序列应该有良好的空间分布(生成0-9之间的随机数,不能只生成5,6,7,8,9,生成的数落在大,中,小范围内的概率也应大致相当),即空间分布均匀性。</li>\n<li>所有的PRNG都有周期性。通常,周期越大越好。</li>\n</ul>\n<h2 id=\"更好的随机数生成器\"> 更好的随机数生成器</h2>\n<p>使用梅森旋转演算法(Mersenne Twister)。在C++11中引入。</p>\n<div><pre><code><span><span>#</span><span>include</span> <span><iostream></span></span>\n<span><span>#</span><span>include</span> <span><random></span> <span>// for std::mt19937</span></span>\n<span><span>#</span><span>include</span> <span><ctime></span> <span>// for std::time</span></span>\n \n<span>int</span> <span>main</span><span>(</span><span>)</span>\n<span>{</span>\n\t<span>// 初始化 mersenne twister,将时间作为 seed。</span>\n\tstd<span>::</span>mt19937 mersenne<span>{</span> <span><span>static_cast</span><span><span><</span>std<span>::</span>mt19937<span>::</span>result_type<span>></span></span></span><span>(</span>std<span>::</span><span>time</span><span>(</span><span>nullptr</span><span>)</span><span>)</span> <span>}</span><span>;</span>\n \n\t<span>// 生成 1-6 之间随机整数的PRNG</span>\n\tstd<span>::</span>uniform_int_distribution die<span>{</span> <span>1</span><span>,</span> <span>6</span> <span>}</span><span>;</span> <span>// C++17</span>\n\t<span>// std::uniform_int_distribution<> die{ 1, 6 }; // C++11</span>\n \n\t<span>for</span> <span>(</span><span>int</span> count<span>{</span> <span>1</span> <span>}</span><span>;</span> count <span><=</span> <span>48</span><span>;</span> <span>++</span>count<span>)</span>\n\t<span>{</span>\n\t\tstd<span>::</span>cout <span><<</span> <span>die</span><span>(</span>mersenne<span>)</span> <span><<</span> <span>'\\t'</span><span>;</span>\n\n\t\t<span>if</span> <span>(</span>count <span>%</span> <span>6</span> <span>==</span> <span>0</span><span>)</span>\n\t\t\tstd<span>::</span>cout <span><<</span> <span>'\\n'</span><span>;</span>\n\t<span>}</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br></div></div><h2 id=\"effolkronium-s-random-library\"> Effolkronium’s random library.</h2>\n<p>一个随机数库(纯头文件)。提供了便于使用的API。</p>\n<div><pre><code><span><span>#</span><span>include</span> <span><iostream></span></span>\n<span><span>#</span><span>include</span> <span>\"random.hpp\"</span></span>\n \n<span>// get base random alias which is auto seeded and has static API and internal state</span>\n<span>using</span> Random <span>=</span> effolkronium<span>::</span>random_static<span>;</span>\n \n<span>int</span> <span>main</span><span>(</span><span>)</span>\n<span>{</span>\n\tstd<span>::</span>cout <span><<</span> <span>Random</span><span>::</span><span>get</span><span>(</span><span>1</span><span>,</span> <span>6</span><span>)</span> <span><<</span> <span>'\\n'</span><span>;</span>\n\tstd<span>::</span>cout <span><<</span> <span>Random</span><span>::</span><span>get</span><span>(</span><span>1</span><span>,</span> <span>10</span><span>)</span> <span><<</span> <span>'\\n'</span><span>;</span>\n <span>// decltype(val) is long double</span>\n\t<span>auto</span> val <span>=</span> <span>Random</span><span>::</span><span>get</span><span>(</span><span>1.l</span><span>,</span> <span>-</span><span>1.l</span><span>)</span>\n\t<span>return</span> <span>0</span><span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br></div></div>",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "C/C++ 文件 IO",
"url": "https://kigane.github.io/note/cpp/file-io/",
"id": "https://kigane.github.io/note/cpp/file-io/",
"content_html": "<h2 id=\"c\"> C</h2>\n<h3 id=\"读写\"> 读写</h3>\n<p>C提供两种访问文件的途径:二进制模式和文本模式。二进制模式中打开的文件一个字节都不会变,而文本模式中打开的文件中的行末尾和文件末尾会被映射为C模式(行尾:\\n, 文件末尾: EOF)。</p>\n<p>模式串</p>\n<ul>\n<li>r 读</li>\n<li>w 写。如果文件不存在,会创建新文件。如果文件存在,会先删除文件内容。</li>\n<li>a 写,添加。如果文件不存在,会创建新文件。</li>\n<li><code>+</code> 后缀,表示以更新模式打开,可读可写。但有一些规则\n<ul>\n<li>从读模式转换到写模式前,需要先调用一个文件定位函数,或遇到EOF</li>\n<li>从写模式转换到读模式前,必须先调用fflush函数</li>\n</ul>\n</li>\n<li>b 后缀,表示以二进制模式打开</li>\n<li>x 后缀,C11</li>\n</ul>\n<p>stdio.h头文件中包含stdin,stdout,stderr三个标准流文件的文件指针。</p>\n<ul>\n<li>FILE *fopen(const char *filename, const char *mode);</li>\n<li>int fclose(FILE *stream);</li>\n<li>FILE *freopen(const char *filename, const char *mode, FILE *stream); // 将stream流重定向到文件。常用:freopen("foo", "w", stdout);</li>\n</ul>\n<hr>\n<ul>\n<li>int putc(int c, FILE *stream); // 向stream写入一个字符c,用int而非char是因为EOF是一个负的整数常量。</li>\n<li>int fputc(int c, FILE *stream); // 功能同putc。但putc是宏,fputc是函数。通常putc更快。</li>\n<li>int putchar(int c); // 将一个字符放入stdout</li>\n<li>int fputs(const char *s, FILE *stream); // 向stream写入一行字符c。不会自动加换行符</li>\n<li>int puts(const char *s); // 将一行字符放入stdout。会自动加换行符。</li>\n</ul>\n<hr>\n<ul>\n<li>int getc(FILE *stream); // 从stream读入一个字符。宏</li>\n<li>int fgetc(FILE *stream); // 从stream读入一个字符。函数</li>\n<li>int getchar(void) // 从stdin读入一个字符</li>\n<li>int ungetc(int c, FILE *stream); // 将一个字符放回stream。只有第一次调用保证会成功。</li>\n<li>char *fgets(char *s, int n, FILE *stream); // 从stream读入一行字符c。最多读取n-1个。不会丢弃换行符,但可能没读到。</li>\n<li>char *gets(char *s); // 从stdin读入一行字符。自动丢弃换行符。\n注意:以上get函数在遇到问题时行为一样。遇到文件末尾,返回EOF,并设置流的文件末尾指示器。遇到读错误,返回EOF,并设置流的错误指示器。可用<code>int feof(FILE *fp),int ferror(FILE *fp)</code>函数区分这两种情况。</li>\n</ul>\n<hr>\n<p>主要用于二进制流</p>\n<ul>\n<li>size_t fwrite(const void *ptr, size_t size, size_t elnum, FILE *stream); // 把内存中的数组复制给流。ptr为数组地址,size为数组元素的大小(B),elnum为数组元素数量。stream为写入的流。返回实际写入的<strong>元素</strong>数量</li>\n<li>size_t fread(void *ptr, size_t size, size_t elnum, FILE *stream); // 读入。返回值为实际读入的元素数量,应等于elnum</li>\n<li>不一定非得是数组,结构体也行。</li>\n<li>示例1: fwrite(arr, sizeof(a[0]), sizeof(a)/sizeof(a[0]), fp); // 写数组</li>\n<li>示例2: fwrite(&s, sizeof(s), 1, fp); // 写结构体</li>\n</ul>\n<hr>\n<p>文件定位(适合二进制流)</p>\n<ul>\n<li>\n<p>int fseek(FILE *stream, long int offset, int whence);</p>\n<ul>\n<li>whence SEEK_SET, SEEK_CUR, SEEK_END 代表新位置的计算起点。</li>\n<li>offset 可以为负。和whence共同确定pos。</li>\n<li>移动到文件开头 fseek(fp, 0L, SEEK_SET)</li>\n<li>移动到文件末尾 fseek(fp, 0L, SEEK_END)</li>\n<li>往回移动10个字节 fseek(fp, -10L, SEEK_CUR)</li>\n</ul>\n</li>\n<li>\n<p>long int ftell(FILE *stream); // 返回当前pos。错误时,返回-1L,错误码在errno中。0表示文件起始,其他整数表示当前pos所在的字节数。</p>\n</li>\n<li>\n<p>void rewind(FILE *stream); // 把pos设为0,且会清除fp的错误指示器。 类似fseek(fp, 0L, SEEK_SET)</p>\n</li>\n<li>\n<p>int fgetpos(FILE *stream, fpos_t *pos); // 用于超大文件。fpos_t是一个结构,可储存超过long int可表示的数的位置。</p>\n</li>\n<li>\n<p>int fsetpos(FILE *stream, const fpos_t *pos);</p>\n</li>\n</ul>\n<h3 id=\"临时文件\"> 临时文件</h3>\n<ul>\n<li>FILE *tmpfile(void); // "wb+"</li>\n<li>char *tmpnam(char *s); // 写入内容后才生成\n<ul>\n<li>参数为NULL时,会自动生成一个文件名,将文件名存储在静态变量中,并返回其指针。</li>\n<li>否则,会把文件名复制到程序员提供的字符数组中。L_tmpnam宏为临时文件名的字符数组长度。</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"文件缓冲\"> 文件缓冲</h3>\n<ul>\n<li>int fflush(FILE *stream); // 强制刷新</li>\n<li>void setbuf(FILE *stream, char *buf); // 等价于(void) setvbuf(stream, NULL, _IONBF, 0) 或 (void) setvbuf(stream, buf, _IOFBF, BUFSIZ)</li>\n<li>int setvbuf(FILE *stream, char *buf, int mode, size_t size);\n<ul>\n<li>缓冲mode有三种\n<ul>\n<li>_IOFBF 满缓冲 缓冲区满时刷新。默认设置</li>\n<li>_IOLBF 行缓冲 没读/写一行就刷新</li>\n<li>_IONBF 无缓冲 没有缓冲区</li>\n</ul>\n</li>\n<li>buf 是缓冲区。为NULL时,setvbuf会创建一个size大小的缓冲区。</li>\n<li>size 是缓冲区的大小(Byte)</li>\n</ul>\n</li>\n</ul>\n<p>通常,缓冲是后台发生的,向文件写数据时,数据先放入缓冲区。当缓冲区满或关闭文件时,缓冲区会自动flush(即将数据写入磁盘)。</p>\n<h3 id=\"其他文件操作\"> 其他文件操作</h3>\n<ul>\n<li>int remove(const char *filename);</li>\n<li>int rename(const char *old, const char *new);</li>\n</ul>\n",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "宏",
"url": "https://kigane.github.io/note/cpp/macro/",
"id": "https://kigane.github.io/note/cpp/macro/",
"content_html": "<h2 id=\"预定义的宏\"> 预定义的宏</h2>\n<p>注意:不同的编译器宏的名称可能不同。</p>\n<ul>\n<li><code>__func__</code> 所在函数名</li>\n<li><code>__FILE__</code> 所在文件名</li>\n<li><code>__LINE__</code> 所在行数</li>\n<li><code>__DATE__</code> 当前源文件的编译时间</li>\n<li>``</li>\n</ul>\n<p><a href=\"https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros?view=msvc-160\" target=\"_blank\" rel=\"noopener noreferrer\">MSVC</a></p>\n<h2 id=\"操作符\"> #操作符</h2>\n<ul>\n<li>#x 用来把参数转换成字符串</li>\n<li>a##b 将两个单独的符号合并成一个,且该符号必须是有效的。</li>\n<li><code>## __VA_ARGS__</code> 这里##的作用在于,当可变参数的个数为0时,这里的##起到把前面多余的","去掉的作用,否则会编译出错</li>\n</ul>\n<div><pre><code><span><span>#</span><span>define</span> <span>__T</span><span><span>(</span>x<span>)</span> L </span><span>##</span> <span>x </span><span>// 如果x是字符串 \"abc\" 则__T(x) 相当于 L\"abc\"</span></span>\n\n<span><span>#</span><span>define</span> <span>paster</span><span><span>(</span> n <span>)</span> <span>printf_s</span><span>(</span> </span><span>\"token\"</span> <span><span><span>#</span><span>n</span> </span></span><span>\" = %d\"</span><span><span>,</span> token</span><span>##</span><span>n <span>)</span></span></span>\n<span>int</span> token9 <span>=</span> <span>9</span><span>;</span>\n<span>int</span> <span>main</span><span>(</span><span>)</span>\n<span>{</span>\n <span>paster</span><span>(</span><span>9</span><span>)</span><span>;</span> <span>// 输出 token9 = 9</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br></div></div><h2 id=\"多行宏\"> 多行宏</h2>\n<p>对于复杂的多行宏。推荐的写法为</p>\n<div><pre><code><span><span>#</span><span>define</span> <span>some_marco</span><span><span>(</span><span>)</span></span><span>\\</span>\n <span><span>do</span> <span>{</span></span><span>\\</span>\n <span>contents</span><span>\\</span>\n <span><span>}</span> <span>while</span> <span>(</span><span>0</span><span>)</span></span></span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br></div></div><p><a href=\"https://stackoverflow.com/questions/1067226/c-multi-line-macro-do-while0-vs-scope-block\" target=\"_blank\" rel=\"noopener noreferrer\">解释</a></p>\n",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "异常控制流",
"url": "https://kigane.github.io/note/cs/ECF/",
"id": "https://kigane.github.io/note/cs/ECF/",
"content_html": "<h2 id=\"异常\"> 异常</h2>\n<h3 id=\"异常和异常处理\"> 异常和异常处理</h3>\n<ul>\n<li>异常(exception):就是控制流中的突变,用来相应处理器状态中的某些变化。</li>\n<li>事件(event):即状态变化。在处理器中,状态被编码为不同的位和信号。</li>\n<li>异常号(exception number):系统为每种类型的异常分配的唯一的非负整数标识。</li>\n<li>异常表(exception table):当处理器检测到事件发生时,会通过该表调用相应的异常处理程序。其起始地址位于一个特殊的CPU寄存器--异常表基址寄存器。在系统启动时,由操作系统分配和初始化。</li>\n<li>在异常处理程序完成后,根据事件的类型,处理程序的处理方式不同。</li>\n</ul>\n<h3 id=\"异常的类别\"> 异常的类别</h3>\n<ul>\n<li>中断(interrupt):是由硬件造成的,即来自处理器外部的I/O设备的信号的结果。因此,是异步的。从中断返回时,总是返回到下一条指令。(过程:执行<span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:0.8333em;vertical-align:-0.15em;\"></span><span><span style=\"margin-right:0.07847em;\">I</span><span><span><span><span style=\"height:0.1514em;\"><span style=\"top:-2.55em;margin-left:-0.0785em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span><span>c</span><span>u</span><span style=\"margin-right:0.02778em;\">rr</span></span></span></span></span><span></span></span><span><span style=\"height:0.15em;\"><span></span></span></span></span></span></span></span></span></span>,发生中断,执行完<span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:0.8333em;vertical-align:-0.15em;\"></span><span><span style=\"margin-right:0.07847em;\">I</span><span><span><span><span style=\"height:0.1514em;\"><span style=\"top:-2.55em;margin-left:-0.0785em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span><span>c</span><span>u</span><span style=\"margin-right:0.02778em;\">rr</span></span></span></span></span><span></span></span><span><span style=\"height:0.15em;\"><span></span></span></span></span></span></span></span></span></span>,调用中断处理程序,处理程序返回到下一条指令<span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:0.8333em;vertical-align:-0.15em;\"></span><span><span style=\"margin-right:0.07847em;\">I</span><span><span><span><span style=\"height:0.2806em;\"><span style=\"top:-2.55em;margin-left:-0.0785em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span><span>n</span><span>e</span><span>x</span><span>t</span></span></span></span></span><span></span></span><span><span style=\"height:0.15em;\"><span></span></span></span></span></span></span></span></span></span>)</li>\n<li>陷阱(trap):是有意的异常。用于为用户程序和内核之间提供系统调用,让用户程序可以向内核请求服务。具体而言,用户执行<code>syscall n</code>指令,会触发一个trap,trap的处理程序解析参数,并调用适当的内核程序。另外:系统调用运行于内核模式。</li>\n<li>故障(fault):由可能能被错误处理程序修正的错误引起。如果故障处理程序能修正(如缺页异常),则返回<span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:0.8333em;vertical-align:-0.15em;\"></span><span><span style=\"margin-right:0.07847em;\">I</span><span><span><span><span style=\"height:0.1514em;\"><span style=\"top:-2.55em;margin-left:-0.0785em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span><span>c</span><span>u</span><span style=\"margin-right:0.02778em;\">rr</span></span></span></span></span><span></span></span><span><span style=\"height:0.15em;\"><span></span></span></span></span></span></span></span></span></span>并重新执行这个引起异常的指令,如果无法修正(如除零,段错误),则调用内核的abort例程,终止该程序。</li>\n<li>终止(abort):由不可恢复的致命错误引起,通常是硬件错误。调用内核的abort例程,终止该程序。</li>\n</ul>\n<details><summary>Linux异常和系统调用</summary>\n<ul>\n<li>除法错误(0):除零或除法溢出时发生。Unix不会从除法错误中恢复,而是会终止程序。Linux shell会报告"Floating exception"。</li>\n<li>一般保护故障(13):原因很多,通常是因为程序引用的未定义的虚拟内存区域或试图写一个只读的文本段。Linux不会恢复这类故障,会终止程序并报告"Segmentation fault"。</li>\n<li>缺页异常://TODO</li>\n</ul>\n<p>系统调用:syscall</p>\n<ul>\n<li>系统调用时,%rax保存系统调用号,%rdi,%rsi,%rdx,%r10,%r8,%r9顺序包含最多6个参数。系统调用返回时,%rcx,%r11会被破坏,%rax包含返回值。返回值为负(-4095~-1),说明发生了错误,会设置相应的errno。</li>\n<li>常用系统调用\n<ul>\n<li>0 read 读文件</li>\n<li>1 write 写文件</li>\n<li>2 open 打开文件</li>\n<li>3 close 关闭文件</li>\n<li>4 stat 获取文件信息</li>\n<li>9 mmap 将内存页映射到文件</li>\n<li>12 brk 重置堆顶</li>\n<li>32 dup2 复制文件描述符</li>\n<li>33 pause 挂起进程,直到有信号到达</li>\n<li>37 alarm 设置定时器,时间到了发送一个alarm信号给进程,默认行为会终止进程</li>\n<li>39 getpid 获取进程ID</li>\n<li>57 fork 创建进程</li>\n<li>59 execve 执行一个程序</li>\n<li>60 _exit 终止进程</li>\n<li>61 wait4 等待一个进程终止</li>\n<li>62 kill 向一个进程发送一个信号</li>\n</ul>\n</li>\n</ul>\n</details>\n<h2 id=\"进程\"> 进程</h2>\n<ul>\n<li>经典定义:一个执行中的程序的实例。</li>\n<li>上下文(context):程序正确运行所需的状态。包括内存中程序的的代码和数据,栈,GPRs,PC,环境变量以及打开的文件描述符的集合。</li>\n<li>进程为应用程序提供的关键抽象:\n<ul>\n<li>一个独立的逻辑控制流</li>\n<li>一个私有的地址空间</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"逻辑控制流\"> 逻辑控制流</h3>\n<p>即执行程序的PC值的序列。\n<img src=\"/assets/img/csapp-ecf-01.png\" alt=\"ECF01\" /><br>\n关键点:进程是轮流使用处理器的,每个进程执行其流的一部分,然后被preempted(暂时挂起),然后轮到其他进程。</p>\n<h3 id=\"并发\"> 并发</h3>\n<ul>\n<li>一个逻辑流的执行在<strong>时间上</strong>与另一个流重叠,称为并发流(concurrent flow),这两个流被称为并发的执行。</li>\n<li>并发(concurrency):多个流并发的执行。</li>\n<li>多任务(multitask):一个进程和其他进程轮流运行。</li>\n<li>时间片(time slice):一个进程执行其逻辑控制流的一部分的每一时间段。</li>\n<li>并行流(parallel flow):两个流并发地运行在不同的处理器或计算机上。并行流是并发流的真子集。</li>\n</ul>\n<h3 id=\"私有地址空间\"> 私有地址空间</h3>\n<p><img src=\"/assets/img/csapp-ecf-02.png\" alt=\"ECF02\" /><br>\n进程为每个程序提供私有地址空间。通用结构如图。</p>\n<h3 id=\"用户模式和内核模式\"> 用户模式和内核模式</h3>\n<p>用户模式是处理器提供的一种机制,限制应用可以执行的指令和可访问的地址空间范围,即不能使用特权指令和访问地址空间中的内核区。处理器通过某状态寄存器中的一个模式位(mode bit)确定进程处于用户模式还是内核模式。</p>\n<ul>\n<li>特权指令(privileged instruction):如停止处理器,改变模式位,发起I/O操作。</li>\n<li>用户只能通过系统调用接口间接访问内核的代码和数据。任何直接访问的尝试都会导致abort。</li>\n<li>进程从用户模式切换到内核模式的唯一方式是使用interrupt,fault,trap的异常处理程序。异常发生时,会调用异常处理程序,异常处理程序运行于内核模式中。异常处理程序返回后,就切换回用户模式。</li>\n<li>Linux中\n<ul>\n<li>/proc文件系统提供许多用户可以访问的内核数据结构。如/proc/cpuinfo,/proc/:pid/maps(进程使用的内存段)</li>\n<li>/sys文件系统提供关于系统总线和设备的低层信息。</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"上下文切换\"> 上下文切换</h3>\n<ul>\n<li>进程上下文\n<ul>\n<li>用户级上下文:程序块,数据块,用户堆栈</li>\n<li>系统级上下文:\n<ul>\n<li>进程标识信息: pid</li>\n<li>进程现场信息:寄存器内容(GPRs,浮点寄存器,某些状态寄存器)</li>\n<li>进程控制信息:PC,某些状态寄存器</li>\n<li>内核:内核栈和内核数据结构,包括页表,进程表,文件表。</li>\n</ul>\n</li>\n</ul>\n</li>\n<li>上下文切换\n<ul>\n<li>保存当前进程上下文</li>\n<li>恢复被切换到的进程的上下文</li>\n<li>将控制转移给新进程</li>\n</ul>\n</li>\n<li>上下文切换时机\n<ul>\n<li>内核执行系统调用时。系统调用发起I/O请求,或使用sleep系统调用等等。</li>\n<li>系统周期性中断。所有系统都有某种产生周期性定时器中断的机制,通常为1或10ms。每次发生定时器中断,内核就能判定出当前进程已经运行了足够长的时间,并切换到另一个进程。</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"系统调用错误处理\"> 系统调用错误处理</h2>\n<p>Unix系统级函数出错时,会返回-1,并设置全局整数变量errno来表示什么出错了。<code>strerrno(errno)</code>返回错误的具体描述。</p>\n<div><pre><code><span>void</span> <span>unix_error</span><span>(</span><span>char</span> <span>*</span>msg<span>)</span> <span>{</span>\n <span>fprintf</span><span>(</span><span>stderr</span><span>,</span> <span>\"%s, %s\\n\"</span><span>,</span> msg<span>,</span> <span>strerr</span><span>(</span>errno<span>)</span><span>)</span><span>;</span>\n <span>exit</span><span>(</span><span>0</span><span>)</span><span>;</span>\n<span>}</span>\n\n<span>// fork 的错误包装函数</span>\n<span>pid_t</span> <span>Fork</span><span>(</span><span>void</span><span>)</span> <span>{</span>\n <span>pid_t</span> pid<span>;</span>\n <span>if</span> <span>(</span>pid <span>=</span> <span>fork</span><span>(</span><span>)</span> <span><</span> <span>0</span><span>)</span>\n <span>unix_error</span><span>(</span><span>\"Fork error\"</span><span>)</span><span>;</span>\n <span>return</span> pid<span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br></div></div><h2 id=\"进程控制\"> 进程控制</h2>\n<ul>\n<li>每个进程都有一个唯一的正整数进程 ID(PID)。</li>\n<li>每个进程都只属于一个进程组,进程组也是用一个正整数 ID 来标识的。默认的,子进程和父进程同属于一个进程组。</li>\n<li>进程的三种状态\n<ul>\n<li>运行:进程正在 CPU 上执行,或在等待被执行且最终会被调度。</li>\n<li>停止:进程被挂起(suspended),不会被调度。\n<ul>\n<li>会导致停止的信号:SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU</li>\n<li>进程再次运行信号:SIGCONT</li>\n</ul>\n</li>\n<li>终止:进程永远停止。进程终止有三种原因\n<ul>\n<li>收到一个默认行为是终止进程的信号</li>\n<li>从主程序返回</li>\n<li>调用exit()函数</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n<div><pre><code><span><span>#</span><span>include</span> <span><sys/types.h></span></span>\n<span><span>#</span><span>include</span> <span><unistd.h></span></span>\n<span>pid_t</span> <span>getpid</span><span>(</span><span>void</span><span>)</span><span>;</span> <span>// 返回调用进程的PID</span>\n<span>pid_t</span> <span>getppid</span><span>(</span><span>void</span><span>)</span><span>;</span> <span>// 返回调用进程的父进程的PID</span>\n<span>pid_t</span> <span>getpgrp</span><span>(</span><span>void</span><span>)</span><span>;</span> <span>// 返回调用进程的进程组ID</span>\n<span>int</span> <span>setpgid</span><span>(</span><span>pid_t</span> pid<span>,</span> <span>pid_t</span> pgid<span>)</span><span>;</span> <span>// 将pid进程的进程组改为pgid。成功返回0,否则返回-1。</span>\n<span>// setpgid(0,0)表示用当前进程的pid为名创建进程组,并将当前进程加入该进程组。</span>\n<span>pid_t</span> <span>fork</span><span>(</span><span>void</span><span>)</span><span>;</span> <span>// 创建子进程,子进程返回0,父进程返回子进程的PID。</span>\n<span>pid_t</span> <span>waitpid</span><span>(</span><span>pid_t</span> pid<span>,</span> <span>int</span> <span>*</span>statusp<span>,</span> <span>int</span> options<span>)</span><span>;</span> <span>// 等待pid子进程停止或终止</span>\n<span>pid_t</span> <span>wait</span><span>(</span><span>int</span> <span>*</span>status<span>)</span><span>;</span> <span>// 等价于 waitpid(-1, &status, 0)</span>\n<span>unsigned</span> <span>int</span> <span>sleep</span><span>(</span><span>unsigned</span> <span>int</span> secs<span>)</span><span>;</span> <span>// 将进程挂起(suspend)指定秒数</span>\n<span>int</span> <span>pause</span><span>(</span><span>void</span><span>)</span><span>;</span> <span>// 将进程挂起,直到该进程收到一个信号</span>\n<span>// 在当前进程的上下文中加载并运行一个新程序</span>\n<span>int</span> <span>execve</span><span>(</span><span>const</span> <span>char</span> <span>*</span>filename<span>,</span> <span>const</span> <span>char</span> <span>*</span>argv<span>[</span><span>]</span><span>,</span> <span>const</span> <span>char</span> <span>*</span>envp<span>[</span><span>]</span><span>)</span><span>;</span> \n\n<span><span>#</span><span>include</span> <span><stdlib.h></span></span>\n<span>void</span> <span>exit</span><span>(</span><span>int</span> status<span>)</span><span>;</span> <span>// 以status为退出状态终止进程</span>\n<span>char</span> <span>*</span><span>getenv</span><span>(</span><span>const</span> <span>char</span> <span>*</span>name<span>)</span><span>;</span> <span>// 返回指定的环境变量</span>\n<span>int</span> <span>setenv</span><span>(</span><span>const</span> <span>char</span> <span>*</span>name<span>,</span> <span>const</span> <span>char</span> <span>*</span>newvalue<span>,</span> <span>int</span> overwrite<span>)</span><span>;</span> <span>// 设置环境变量</span>\n<span>void</span> <span>unsetenv</span><span>(</span><span>const</span> <span>char</span> <span>*</span>name<span>)</span><span>;</span> <span>// 删除环境变量</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br></div></div><h3 id=\"fork\"> fork()</h3>\n<ul>\n<li>fork()函数调用一次,返回两次。在子进程中返回0,在父进程中返回子进程的 PID。</li>\n<li>fork()函数创建的子进程和所在的进程是并发运行的独立进程。内核能以任意方式交替执行它们的逻辑控制流中的指令。</li>\n<li>fork()函数创建的子进程拥有相同但独立的地址空间。</li>\n<li>fork()函数创建的子进程和所在进程共享文件。</li>\n<li>进程图:每个顶点对应一条程序语句,有向边 a->b 表示语句 a 在语句 b 之前执行。在 fork 处分为两支。</li>\n</ul>\n<details><summary>例题</summary>\n<div><pre><code><span>int</span> <span>main</span><span>(</span><span>)</span> <span>{</span>\n <span>int</span> x <span>=</span> <span>1</span><span>;</span>\n <span>if</span> <span>(</span><span>Fork</span><span>(</span><span>)</span> <span>==</span> <span>0</span><span>)</span>\n <span>printf</span><span>(</span><span>\"p1: x=%d\\n\"</span><span>,</span> <span>++</span>x<span>)</span><span>;</span>\n <span>printf</span><span>(</span><span>\"p2: x=%d\\n\"</span><span>,</span> <span>--</span>x<span>)</span><span>;</span>\n <span>exit</span><span>(</span><span>0</span><span>)</span><span>;</span>\n<span>}</span>\n<span>// 其输出为:</span>\n<span>// 子进程:p1: x=2 p2: x=1</span>\n<span>// 父进程:p2: x=0 </span>\n<span>// 012的出现顺序不定(除了2一定在1前)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br></div></div></details>\n<h3 id=\"waitpid-回收子进程\"> waitpid()--回收子进程</h3>\n<ul>\n<li>进程的回收(reaped):当一个进程由于某种原因终止时,内核并不是立即把它从系统中清除。相反,进程被保持在一种已终止的状态中,直到被它的父进程回收。当父进程回收已终止的子进程时,内核将子进程的退出状态传递给父进程,然后抛弃已终止的进程,从此时开始,该进程就不存在了。</li>\n<li>僵死进程(zombie):终止了,但还没被回收的进程。会消耗内存资源。</li>\n<li>如果一个父进程终止了,内核会安排 init 进程成为它的子进程的养父。init 进程的 PID 为 1,是在系统启动时由内核创建的,它不会终止,是所有进程的祖先。如果父进程没有回收它的僵死子进程就终止了,那么内核会安排 init 进程去回收它们。</li>\n</ul>\n<p>waitpid函数</p>\n<ul>\n<li>通常行为:waitpid挂起调用进程的执行,直到它的等待集合(wait set)中的一个子进程终止。如果等待集合中的一个进程,在waitpid调用时就已经终止了,则立即返回。返回子进程的PID(此时子进程已被回收)。</li>\n<li>参数 pid:指定等待集合\n<ul>\n<li>pid > 0: 等待集合就是pid指定的单独的子进程。</li>\n<li>pid = -1:等待集合就是父进程的所有子进程。</li>\n<li>pid = 0:等待集合就是在父进程所在进程组的所有子进程。</li>\n<li>pid < 0: 等待集合就是在|pid|指定的进程组中的所有子进程。</li>\n</ul>\n</li>\n<li>参数 options:修改 waitpid 的默认行为\n<ul>\n<li>0:默认值。通常行为。</li>\n<li>WNOHANG:如果等待集合中的一个进程,在 waitpid 调用时就已经终止了,则立即返回。如果等待集合中任何子进程都没有终止,也立即返回,返回值为 0。</li>\n<li>WUNTRACED:不仅在终止时返回,停止时也返回。(停止的进程不会被回收)</li>\n<li>WCONTINUED:不仅在终止时返回,在一个停止的程序继续运行时也返回。</li>\n<li>选项可以组合</li>\n</ul>\n</li>\n<li>statusp参数指向子进程的退出状态信息status。\n<ul>\n<li><code><sys/wait.h></code>中定义了一些宏,用于解释status。</li>\n<li>WIFEXITED(status):如果于进程通过调用 exit 或者返回正常终止,就返回真。</li>\n<li>WEXITSTATUS(status):返回一个正常终止的子进程的退出状态。只有在 WIFEXITED() 返回为真时,才会定义这个状态。</li>\n<li>WIFSIGNALED(status):如果子进程是因为一个未被捕获的信号终止的,. 那么就返回真。</li>\n<li>WTERMSIG(status):返回导致子进程终止的信号的编号。只有在 WIFSIGNALED() 返回为真时,才定义这个状态。</li>\n<li>WIFSTOPPED(status):如果引起返回的子进程当前是停止的,那么就返回真。</li>\n<li>WSTOPSIG(status):返回引起子进程停止的信号的编号。只有在 WIFSTOPPED() 返回为真时,才定义这个状态。</li>\n<li>WIFCONTINUED(status):如果子进程收到 SIGCONT 信号重新启动,则返回真。</li>\n</ul>\n</li>\n<li>错误条件\n<ul>\n<li>如果调用进程没有子进程(没创建或者已经回收完了),则 waitpid 返回 -1,并设置 errno 为 ECHILD。</li>\n<li>如果 waitpid 函数被一个信号中断,则返回 -1,并设置 errno 为 EINTR。</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"execve函数\"> execve函数</h3>\n<ul>\n<li>execve 函数在当前进程上下文中加载并运行一个新程序,并会<strong>覆盖</strong>当前进程的地址空间,不会创建新进程,而是继承原进程的PID和已打开文件列表。</li>\n<li>execve 参数列表中,filename 为要加载的可执行目标文件, argv,envp 为传递给 main 函数的参数。</li>\n<li>execve 加载 filename 后,调用_start()启动代码,启动代码设置栈,并将控制传递给新程序的 main 函数。</li>\n<li>main 函数的原型 <code>int main(int argc, char *argv[], char *envp[]);</code>\n<ul>\n<li>argc 为 argv 数组中非空指针的数量</li>\n<li>agrv 数组中 <code>argv[0]</code> 为可执行目标文件名,之后为执行时所带的参数列表。</li>\n<li>envp 数组保存环境变量字符串(内容为"name=value"的键值对)。</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"信号\"> 信号</h2>\n<ul>\n<li>一种比硬件异常,上下文切换更高层的软件形式异常,它允许进程和内核中断其他进程。</li>\n<li>一个信号就是一个消息,它通知进程系统中发生了一个某类型的事件。</li>\n<li>某种信号类型对应每种系统事件。</li>\n</ul>\n<table>\n<thead>\n<tr>\n<th>序号</th>\n<th>名称</th>\n<th>默认行为</th>\n<th>相应事件</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>1</td>\n<td>SIGHUP</td>\n<td>终止</td>\n<td>终端线挂断</td>\n</tr>\n<tr>\n<td>2</td>\n<td>SIGINT</td>\n<td>终止</td>\n<td>来自键盘的中断</td>\n</tr>\n<tr>\n<td>3</td>\n<td>SIGQUIT</td>\n<td>终止</td>\n<td>来自键盘的退出</td>\n</tr>\n<tr>\n<td>4</td>\n<td>SIGILL</td>\n<td>终止</td>\n<td>非法指令</td>\n</tr>\n<tr>\n<td>5</td>\n<td>SIGTRAP</td>\n<td>终止并转储内存</td>\n<td>跟踪陷阱</td>\n</tr>\n<tr>\n<td>6</td>\n<td>SIGABRT</td>\n<td>终止并转储内存</td>\n<td>来自 abort 函数的终止信号</td>\n</tr>\n<tr>\n<td>7</td>\n<td>SIGBUS</td>\n<td>终止</td>\n<td>总线错误</td>\n</tr>\n<tr>\n<td>8</td>\n<td>SIGFPE</td>\n<td>终止并转储内存</td>\n<td>浮点异常</td>\n</tr>\n<tr>\n<td>9</td>\n<td>SIGKILL</td>\n<td>终止</td>\n<td>杀死程序</td>\n</tr>\n<tr>\n<td>10</td>\n<td>SIGUSR1</td>\n<td>终止</td>\n<td>用户定义的信号 1</td>\n</tr>\n<tr>\n<td>11</td>\n<td>SIGSEGV</td>\n<td>终止并转储内存</td>\n<td>无效的内存引用(段故障)</td>\n</tr>\n<tr>\n<td>12</td>\n<td>SIGUSR2</td>\n<td>终止</td>\n<td>用户定义的信号 2</td>\n</tr>\n<tr>\n<td>13</td>\n<td>SIGPIPE</td>\n<td>终止</td>\n<td>向一个没有读用户的管道做写操作</td>\n</tr>\n<tr>\n<td>14</td>\n<td>SIGALRM</td>\n<td>终止</td>\n<td>来自 alarm 函数的定时器信号</td>\n</tr>\n<tr>\n<td>15</td>\n<td>SIGTERM</td>\n<td>终止</td>\n<td>软件终止信号</td>\n</tr>\n<tr>\n<td>16</td>\n<td>SIGSTKFLT</td>\n<td>终止</td>\n<td>协处理器上的栈故障</td>\n</tr>\n<tr>\n<td>17</td>\n<td>SIGCHLD</td>\n<td>忽略</td>\n<td>一个子进程停止或者终止</td>\n</tr>\n<tr>\n<td>18</td>\n<td>SIGCONT</td>\n<td>忽略</td>\n<td>继续进程如果该进程停止</td>\n</tr>\n<tr>\n<td>19</td>\n<td>SIGSTOP</td>\n<td>停止直到下一个SIGCONT</td>\n<td>不是来自终端的停止信号</td>\n</tr>\n<tr>\n<td>20</td>\n<td>SIGTSTP</td>\n<td>停止直到下一个SIGCONT</td>\n<td>来自终端的停止信号</td>\n</tr>\n<tr>\n<td>21</td>\n<td>SIGTTIN</td>\n<td>停止直到下一个SIGCONT</td>\n<td>后台进程从终端读</td>\n</tr>\n<tr>\n<td>22</td>\n<td>SIGTTOU</td>\n<td>停止直到下一个SIGCONT</td>\n<td>后台进程向终端写</td>\n</tr>\n<tr>\n<td>23</td>\n<td>SIGURG</td>\n<td>忽略</td>\n<td>套接字上的紧急情况</td>\n</tr>\n<tr>\n<td>24</td>\n<td>SIGXCPU</td>\n<td>终止</td>\n<td>CPU 时间限制超出</td>\n</tr>\n<tr>\n<td>25</td>\n<td>SIGXFSZ</td>\n<td>终止</td>\n<td>文件大小限制超出</td>\n</tr>\n<tr>\n<td>26</td>\n<td>SIGVTALRM</td>\n<td>终止</td>\n<td>虚拟定时器期满</td>\n</tr>\n<tr>\n<td>27</td>\n<td>SIGPROF</td>\n<td>终止</td>\n<td>剖析定时器期满</td>\n</tr>\n<tr>\n<td>28</td>\n<td>SIGWINCH</td>\n<td>忽略</td>\n<td>窗口大小变化</td>\n</tr>\n<tr>\n<td>29</td>\n<td>SIGIO</td>\n<td>终止</td>\n<td>在某个描述符上可执行 I/O 操作</td>\n</tr>\n<tr>\n<td>30</td>\n<td>SIGPWR</td>\n<td>终止</td>\n<td>电源故障</td>\n</tr>\n</tbody>\n</table>\n<h3 id=\"信号术语\"> 信号术语</h3>\n<ul>\n<li>发送信号:内核通过<strong>更新目的进程上下文中的某个状态</strong>,发送一个信号给目的进程。需要发送信号的两种原因\n<ul>\n<li>内核检测到某个系统事件,如除零错误或子进程终止。</li>\n<li>一个进程调用了 kill 函数。</li>\n</ul>\n</li>\n<li>接收信号:当目的进程被内核强迫以某种方式对信号作出反应时,它就接收了信号。有三种反应\n<ul>\n<li>忽略信号</li>\n<li>终止进程</li>\n<li>捕获信号:即调用信号处理程序。</li>\n</ul>\n</li>\n<li>待处理信号(pending signal):发出了,但没被处理的信号。在任意时刻,对一个进程而言,每个信号类型最多有一个待处理信号,后续发到该进程的同类型信号会被丢弃。也就是说,一个进程,最多接收一个信号(正在处理)并有一个同类型的待处理信号。</li>\n<li>阻塞:对于信号 k,进程 p 第一次收到信号 k 时,p 接收 k,并作出合理反应。在此期间,第二次收到信号 k 时,k 就处于待处理状态,此时就说信号 k 被隐式阻塞了。如果继续收到信号 k,直接丢弃。</li>\n<li>内核为每个进程维护两个信号集合\n<ul>\n<li>在 pending 位向量中维护待处理信号集合(被隐式阻塞的信号集合)</li>\n<li>在 blocked 位向量中维护被阻塞的信号集合(用sigprocmask()函数阻塞的)</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"发送信号\"> 发送信号</h3>\n<ul>\n<li>/bin/kill 程序:kill -sigid pid。具体见man kill。</li>\n<li>键盘:shell 中使用 job 表示进程。\n<ul>\n<li>Ctrl+C:内核发送一个 SIGINTR 信号到前台进程组中的每个进程。默认结果为终止前台进程。</li>\n<li>Ctrl+Z:内核发送一个 SIGTSTP 信号到前台进程组中的每个进程。默认结果为停止前台进程。</li>\n</ul>\n</li>\n<li>kill() 函数</li>\n<li>alarm() 函数,向进程自己发送 SIGALRM 信号。</li>\n</ul>\n<div><pre><code><span><span>#</span><span>include</span> <span><sys/types.h></span></span>\n<span><span>#</span><span>include</span> <span><signal.h></span></span>\n<span>int</span> <span>kill</span><span>(</span><span>pid_t</span> pid<span>,</span> <span>int</span> sig<span>)</span><span>;</span> <span>// 向pid进程发送sig信号</span>\n<span>unsigned</span> <span>int</span> <span>alarm</span><span>(</span><span>unsigned</span> <span>int</span> secs<span>)</span><span>;</span> <span>// 在secs后发送一个 SIGALRM 信号给自己。</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br></div></div><h3 id=\"接收信号\"> 接收信号</h3>\n<p>当内核将进程 p 从内核模式切换到用户模式时,它会检查进程 p 的为被阻塞的待处理信号的集合(pending & ~blocked)。如果集合为空,内核将控制直接传给 p 的逻辑控制流中的下一条指令。如果集合非空,则选择某个信号 k (通常是最小的),并且强制 p 接收信号 k。引起相应的行为(终止,终止+转储内存(Core dump),停止,执行信号处理程序)。</p>\n<p>进程可以通过 signal 函数修改和信号相关联的默认行为,除了 SIGSTOP 和 SIGKILL。</p>\n<div><pre><code><span><span>#</span><span>include</span> <span><signal.h></span></span>\n<span>typedef</span> <span>void</span> <span>(</span><span>*</span><span>sighandler_t</span><span>)</span><span>(</span><span>int</span><span>)</span><span>;</span>\n<span>sighandler_t</span> <span>signal</span><span>(</span><span>int</span> signum<span>,</span> <span>sighandler_t</span> handler<span>)</span><span>;</span> <span>// 设置 signum 信号的信号处理程序为 handler。</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br></div></div><p>其中 handler 可以是:</p>\n<ul>\n<li>SIG_IGN:忽略信号</li>\n<li>SIG_DFL:信号的默认行为</li>\n<li>用户自定义函数。这个函数称为信号处理程序。调用信号处理程序称为<strong>捕获信号</strong>,执行信号处理程序称为<strong>处理信号</strong>。</li>\n<li>信号处理程序可以被其他信号处理程序中断。</li>\n</ul>\n<h3 id=\"信号的阻塞和解除\"> 信号的阻塞和解除</h3>\n<ul>\n<li>隐式阻塞机制:内核默认阻塞任何当前处理程序正在处理的信号类型的待处理信号。</li>\n<li>显示阻塞机制:使用 sigprocmask 函数设置。</li>\n</ul>\n<div><pre><code><span><span>#</span><span>include</span> <span><signal.h></span></span>\n<span>int</span> <span>sigprocmask</span><span>(</span><span>int</span> how<span>,</span> <span>const</span> <span>sigset_t</span> <span>*</span>set<span>,</span> <span>sigset_t</span> <span>*</span>oldset<span>)</span><span>;</span> <span>// 设置blocked位向量,阻塞信号。</span>\n<span>int</span> <span>sigempty</span><span>(</span><span>sigset_t</span> <span>*</span>set<span>)</span><span>;</span> <span>// 初始化 set 为空集合</span>\n<span>int</span> <span>sigfillset</span><span>(</span><span>sigset_t</span> <span>*</span>set<span>)</span><span>;</span> <span>// 初始化 set 为选择所有信号的集合</span>\n<span>int</span> <span>sigaddset</span><span>(</span><span>sigset_t</span> <span>*</span>set<span>,</span> <span>int</span> signum<span>)</span><span>;</span> <span>// 将 signum 添加到 set</span>\n<span>int</span> <span>sigdelset</span><span>(</span><span>sigset_t</span> <span>*</span>set<span>,</span> <span>int</span> signum<span>)</span><span>;</span> <span>// 从 set 中删除 signum</span>\n<span>int</span> <span>sigismember</span><span>(</span><span>sigset_t</span> <span>*</span>set<span>,</span> <span>int</span> signum<span>)</span><span>;</span> <span>// 判断 signum 是不是 set 的成员</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br></div></div><p>其中,how</p>\n<ul>\n<li>SIG_BLOCK: 将 set 中的信号添加到 blocked 中(blocked = blocked | set)</li>\n<li>SIG_UNBLOCK: blocked = blocked & ~set</li>\n<li>SIG_SETMASK:blocked = set</li>\n</ul>\n<h3 id=\"编写信号处理程序\"> 编写信号处理程序</h3>\n<p>信号处理是 Linux 系统编程最棘手的一个问题。因为</p>\n<ul>\n<li>处理程序和主程序并发运行,共享同样的全局变量</li>\n<li>如何及何时接收信号的规则常常违反人的直觉</li>\n<li>不同系统有不同的信号处理语义</li>\n</ul>\n<p>//TODO</p>\n<ul>\n<li>信号时不排队的,每种信号最多一个正在处理,一个待处理,其余都是直接丢弃。因此,关于信号的一个关键思想是:如果存在一个未处理的信号,就表明至少有一个信号到达了。</li>\n<li>ps命令输出中的<code><defunct></code>表明进程为僵死进程。</li>\n</ul>\n<h2 id=\"非本地跳转\"> 非本地跳转</h2>\n<p>另一种用户级异常控制流形式。通过 setjump 和 longjump 函数提供。</p>\n<div><pre><code><span><span>#</span><span>include</span> <span><setjmp.h></span></span>\n<span>int</span> <span>setjmp</span><span>(</span>jmp_buf env<span>)</span><span>;</span> <span>// 在 env 缓冲区中保存当前调用环境,供longjmp使用,返回0。</span>\n<span>void</span> <span>longjmp</span><span>(</span>jmp_buf env<span>,</span> <span>int</span> retval<span>)</span><span>;</span> <span>// 从 env 缓冲区中恢复调用环境,然后触发一个最近一次初始化 env 的 setjmp 调用的返回。然后 setjmp 返回,返回值为非零的 retval。</span>\n<span>// 下面这两个是可被信号处理函数使用的版本。(但都不是异步信号安全的函数)</span>\n<span>int</span> <span>sigsetjump</span><span>(</span>sigjmp_buf env<span>,</span> <span>int</span> savesigs<span>)</span><span>;</span> \n<span>void</span> <span>siglongjmp</span><span>(</span>sigjmp_buf env<span>,</span> <span>int</span> retval<span>)</span><span>;</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br></div></div><p>longjmp不返回,而是直接跳转到最近的setjmp,让setjmp再次返回。</p>\n<p>非本地跳转的一个重要应用就是允许从一个深层嵌套的函数调用中立即返回,通常是由检测到某个错误情况引起的。</p>\n<details><summary>示例</summary>\n<div><pre><code><span><span>#</span><span>include</span> <span>\"csapp.h\"</span></span>\n\njmp_buf buf<span>;</span>\n\n<span>int</span> error1 <span>=</span> <span>0</span><span>;</span>\n<span>int</span> error2 <span>=</span> <span>1</span><span>;</span>\n\n<span>void</span> <span>foo</span><span>(</span><span>void</span><span>)</span><span>,</span> <span>bar</span><span>(</span><span>void</span><span>)</span><span>;</span>\n\n<span>int</span> <span>main</span><span>(</span><span>)</span>\n<span>{</span>\n <span>switch</span> <span>(</span><span>setjmp</span><span>(</span>buf<span>)</span><span>)</span> <span>{</span>\n <span>case</span> <span>0</span><span>:</span>\n <span>foo</span><span>(</span><span>)</span><span>;</span>\n <span>break</span><span>;</span>\n <span>case</span> <span>1</span><span>:</span>\n <span>printf</span><span>(</span><span>\"Detected an error1 condition in foo\\n\"</span><span>)</span><span>;</span>\n <span>break</span><span>;</span>\n <span>case</span> <span>2</span><span>:</span>\n <span>printf</span><span>(</span><span>\"Detected an error2 condition in foo\\n\"</span><span>)</span><span>;</span>\n <span>break</span><span>;</span>\n <span>default</span><span>:</span>\n <span>printf</span><span>(</span><span>\"Unknown error condition in foo\\n\"</span><span>)</span><span>;</span>\n <span>}</span>\n <span>exit</span><span>(</span><span>0</span><span>)</span><span>;</span>\n<span>}</span>\n\n<span>/* Deeply nested function foo */</span>\n<span>void</span> <span>foo</span><span>(</span><span>void</span><span>)</span>\n<span>{</span>\n <span>if</span> <span>(</span>error1<span>)</span>\n <span>longjmp</span><span>(</span>buf<span>,</span> <span>1</span><span>)</span><span>;</span>\n <span>bar</span><span>(</span><span>)</span><span>;</span>\n<span>}</span>\n\n<span>void</span> <span>bar</span><span>(</span><span>void</span><span>)</span>\n<span>{</span>\n <span>if</span> <span>(</span>error2<span>)</span>\n <span>longjmp</span><span>(</span>buf<span>,</span> <span>2</span><span>)</span><span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br><span>25</span><br><span>26</span><br><span>27</span><br><span>28</span><br><span>29</span><br><span>30</span><br><span>31</span><br><span>32</span><br><span>33</span><br><span>34</span><br><span>35</span><br><span>36</span><br><span>37</span><br><span>38</span><br><span>39</span><br><span>40</span><br></div></div></details>\n<p>非本地跳转的另一个重要应用是使一个信号处理程序分支到一个特殊的代码位置,而不是返回到被信号到达中断了的指令的位置。</p>\n<details><summary>示例</summary>\n<div><pre><code><span><span>#</span><span>include</span> <span>\"csapp.h\"</span></span>\n\nsigjmp_buf buf<span>;</span>\n\n<span>void</span> <span>handler</span><span>(</span><span>int</span> sig<span>)</span>\n<span>{</span>\n <span>siglongjmp</span><span>(</span>buf<span>,</span> <span>1</span><span>)</span><span>;</span>\n<span>}</span>\n\n<span>int</span> <span>main</span><span>(</span><span>)</span>\n<span>{</span>\n <span>if</span> <span>(</span><span>!</span><span>sigsetjmp</span><span>(</span>buf<span>,</span> <span>1</span><span>)</span><span>)</span> <span>{</span>\n <span>Signal</span><span>(</span>SIGINT<span>,</span> handler<span>)</span><span>;</span>\n <span>Sio_puts</span><span>(</span><span>\"starting\\n\"</span><span>)</span><span>;</span>\n <span>}</span>\n <span>else</span>\n <span>Sio_puts</span><span>(</span><span>\"restarting\\n\"</span><span>)</span><span>;</span>\n\n <span>while</span> <span>(</span><span>1</span><span>)</span> <span>{</span>\n <span>Sleep</span><span>(</span><span>1</span><span>)</span><span>;</span>\n <span>Sio_puts</span><span>(</span><span>\"processing...\\n\"</span><span>)</span><span>;</span>\n <span>}</span>\n <span>exit</span><span>(</span><span>0</span><span>)</span><span>;</span> <span>/* Control never reaches here */</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br></div></div></details>\n<h2 id=\"操作进程的工具\"> 操作进程的工具</h2>\n<ul>\n<li>STRACE</li>\n<li>PS</li>\n<li>TOP</li>\n<li>PMAP</li>\n</ul>\n",
"image": "https://kigane.github.io/assets/img/csapp-ecf-01.png",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2022-02-19T05:19:13.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "Window 小知识",
"url": "https://kigane.github.io/note/cs/",
"id": "https://kigane.github.io/note/cs/",
"content_html": "<h2 id=\"bat\"> bat</h2>\n<h3 id=\"文件复制-robocopy\"> 文件复制-robocopy</h3>\n<p>语法:<code>robocopy <source> <destination> [<file>[ ...]] [<options>]</code><br>\nsource和destination都是目录,如果要复制文件,用file参数指定</p>\n<p>常用选项:</p>\n<ul>\n<li>/s 复制子目录。默认不包含空目录。</li>\n<li>/e 复制子目录。默认包含空目录。</li>\n<li>/mov 移动文件,复制后删除原文件</li>\n<li>/move 移动文件和目录,复制后删除原文件</li>\n<li>/create Creates a directory tree and zero-length files only.</li>\n</ul>\n<p><a href=\"https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/robocopy\" target=\"_blank\" rel=\"noopener noreferrer\">详细文档</a></p>\n<h2 id=\"日期与时间\"> 日期与时间</h2>\n<ul>\n<li><code>%DATE%</code> 当前本地日期</li>\n<li><code>%TIME%</code> 当前本地时间</li>\n<li><code>%DATE:~-4,4%</code> 当前本地日期字符串的最后4个字符</li>\n<li><code>%TIME:~0,2%</code> 当前本地时间字符串前两个字符</li>\n</ul>\n",
"date_published": "2022-03-29T00:00:00.000Z",
"date_modified": "2022-04-02T08:34:13.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": [
"Window"
]
},
{
"title": "虚拟内存",
"url": "https://kigane.github.io/note/cs/VM/",
"id": "https://kigane.github.io/note/cs/VM/",
"content_html": "<p>虚拟内存提供三个重要能力</p>\n<ul>\n<li>将主存看作磁盘的高速缓存,在主存中只保留活动区域,并根据需要在主存和磁盘间传送数据,高效地使用了主存</li>\n<li>为每个进程提供了一致的地址空间,简化了内存管理</li>\n<li>也保护了每个进程的地址空间不被其他进程破坏</li>\n</ul>\n<h2 id=\"物理和虚拟寻址\"> 物理和虚拟寻址</h2>\n<p>物理地址(Physical Address, PA):计算机的主存被组织成一个由M个连续的字节大小的单元组成的数组。每字节都有一个唯一的物理地址。<br>\n虚拟寻址(Virtual Address, VA):CPU生成一个虚拟地址来访问主存,这个虚拟地址在被送到内存之前先转换为适当的物理地址。在CPU芯片上由叫内存管理单元(Memory Management Unit, MMU)的硬件负责地址翻译,即将VA转换为PA。MMU通过到主存中查询页表来动态翻译VA。</p>\n<h2 id=\"地址空间\"> 地址空间</h2>\n<p>地址空间是一个非负整数地址的有序集合,为了简化讨论总是使用线性地址空间,即整数是连续的。<br>\n虚拟地址空间的大小用位表示,n位地址空间,表示有<span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:0.6833em;\"></span><span style=\"margin-right:0.10903em;\">N</span><span style=\"margin-right:0.2778em;\"></span><span>=</span><span style=\"margin-right:0.2778em;\"></span></span><span><span style=\"height:0.6644em;\"></span><span><span>2</span><span><span><span><span style=\"height:0.6644em;\"><span style=\"top:-3.063em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span>n</span></span></span></span></span></span></span></span></span></span></span>个地址。物理地址空间对应于物理内存的M个字节。M不要求是2的幂,但我们通常这样假设。<br>\n虚拟内存的基本思想是:允许每个数据对象(字节)有多个独立的地址,其中每个地址来自不同的地址空间。</p>\n<h2 id=\"虚拟内存的结构\"> 虚拟内存的结构</h2>\n<p>概念上,虚拟内存是一个由存放在磁盘上的N个连续字节组成的数组。磁盘上的数据被分割成块,作为在磁盘和主存之间的传送单元。VM系统将虚拟内存分割成的块称为虚拟页(Virtual Page, VP),物理内存的块称为物理页(Physical Page, PP)或页帧(page frame)(大小为<span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:0.6833em;\"></span><span style=\"margin-right:0.13889em;\">P</span><span style=\"margin-right:0.2778em;\"></span><span>=</span><span style=\"margin-right:0.2778em;\"></span></span><span><span style=\"height:0.6644em;\"></span><span><span>2</span><span><span><span><span style=\"height:0.6644em;\"><span style=\"top:-3.063em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span>p</span></span></span></span></span></span></span></span></span></span></span>字节)。</p>\n<p><img src=\"/assets/csapp/VP-PP.png\" alt=\"VP-PP\" /></p>\n<h3 id=\"dram缓存\"> DRAM缓存</h3>\n<ul>\n<li>术语DRAM缓存表示VM系统的缓存,即用主存缓存VP。</li>\n<li>相应的SRAM缓存表示CPU和主存之间的L1,L2,L3高速缓存。</li>\n</ul>\n<p>磁盘比DRAM慢大约100000多倍,不命中的开销巨大。从磁盘读取第一个字节的开销也很大。因此</p>\n<ul>\n<li>虚拟页往往很大,通常为4KB~2MB</li>\n<li>DRAM缓存是全相联的</li>\n<li>替换策略更复杂,精密</li>\n<li>使用写回,而非直写</li>\n</ul>\n<h3 id=\"页表-page-table\"> 页表(Page Table)</h3>\n<p>建立虚拟页到物理页的映射</p>\n<ul>\n<li>页表是一个存放在物理内存中的数据结构,是一个页表条目(Page Table Entry, PTE)的数组,建立虚拟页到物理页的映射。虚拟地址空间中的每个页在页表中一个<strong>固定偏移量</strong>处都有一个PTE。</li>\n<li>MMU中的地址翻译硬件每次将VA转换为PA时,都会去读页表。</li>\n<li>OS负责维护页表的内容,以及在磁盘和DRAM之间来回传送页。\n<img src=\"/assets/csapp/PageTable.png\" alt=\"PageTable\" />\nPS:有效位为1表示对应的VP已经装入物理内存了,地址为内存地址。有效位为0表示要么VP还未分配,要么地址指向VP在磁盘上的位置。</li>\n</ul>\n<h2 id=\"命中与缺页\"> 命中与缺页</h2>\n<p>当CPU想要读取VM系统中的一个字节(如VP2)时,发送一个VA到MMU的地址翻译硬件,地址翻译硬件根据VA的前n-p位VPN(Virtual Page Number)定位页表中相应的PTE,然后查看PTE的有效位</p>\n<ul>\n<li>\n<p>如果为1,说明VP2已缓存在内存中了。PTE中的地址就是PA的PPN(Physics Page Number)。取出来和VPO(Virtual Page Offset)构成实际物理地址。</p>\n</li>\n<li>\n<p>如果为0,称为缺页(page fault)。引发一个缺页异常。缺页异常调用内核中的缺页异常处理程序,该程序选择一个牺牲页,设为VP4(如果VP4已经被修改了,则内核将VP4复制回磁盘),内核修改相应PTE,以表示VP4已不再主存中。接下来,内核从磁盘复制VP2到原VP4所在的主存位置,更新相应PTE后返回。异常处理程序返回时,会重新启动导致缺页的指令,此时必定命中。</p>\n</li>\n<li>\n<p>页面调度(Paging):在磁盘和内存之间传送页的活动。从磁盘到DRAM称换入,反之称换出。</p>\n</li>\n<li>\n<p>页面分配:分配一个新的VP,如调用malloc,使PTE中含null的空洞页实际占有物理空间。</p>\n</li>\n</ul>\n<h2 id=\"内存管理\"> 内存管理</h2>\n<p>OS为每个进程提供了一个独立的页表,因而也就是一个独立的虚拟地址空间。</p>\n<p>好处</p>\n<ul>\n<li>简化链接:每个进程的内存格式都类似。对于64位地址空间,代码段总是从VA=0x400000开始。数据段跟在代码段之后,中间有一段符合要求的对齐空白。栈占据用户进程地址空间最高的部分,并向下生长。链接时,一直使用VA,实际运行时再映射为PA。</li>\n<li>简化加载:有了虚拟内存后,加载文件时,只需要为文件分配虚拟页,即将其PTE有效位设为0,并指向目标文件的适当位置。也就是说,实际没有任何复制动作,实际要用时再按需调度。</li>\n<li>简化共享:OS可将不同进程的页表中适当的VP映射到相同的PP,从而实现多进程共享相应代码。</li>\n<li>简化内存分配:用malloc要求额外的堆空间时,在VM中分配的连续的VP,实际PP可以不连续。</li>\n</ul>\n<h2 id=\"内存保护\"> 内存保护</h2>\n<p>OS需要某种手段来控制对内存系统的访问。如</p>\n<ul>\n<li>不允许用户进程修改其只读代码段</li>\n<li>不允许用户进程修改内核中的代码和数据结构</li>\n<li>不允许用户进程读或写其他进程的私有内存</li>\n<li>不允许用户进程修改任何与其他进程共享的虚拟页面(除非所有共享者都允许它这么做)</li>\n</ul>\n<p>实现很容易,只需在PTE中添加几个许可位。例如</p>\n<ul>\n<li>SUP:进程需要在内核(超级用户)模式下才能访问该页</li>\n<li>READ:读权限</li>\n<li>WRITE:写权限</li>\n<li>……</li>\n</ul>\n<p>保护违例:如果一条指令违反了这些许可条件,则CPU触发一个保护故障,将控制传递给一个内核中的异常处理程序。Linux shell 一般称这种异常为“段错误(segmentation fault)”</p>\n<h2 id=\"地址翻译\"> 地址翻译</h2>\n<p>TLB, Translation Lookaside Buffer,简称快表</p>\n<p>符号</p>\n<ul>\n<li><span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:0.6833em;\"></span><span style=\"margin-right:0.10903em;\">N</span><span style=\"margin-right:0.2778em;\"></span><span>=</span><span style=\"margin-right:0.2778em;\"></span></span><span><span style=\"height:0.6644em;\"></span><span><span>2</span><span><span><span><span style=\"height:0.6644em;\"><span style=\"top:-3.063em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span>n</span></span></span></span></span></span></span></span></span></span></span>:虚拟地址空间中的地址数量</li>\n<li><span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:0.6833em;\"></span><span style=\"margin-right:0.10903em;\">M</span><span style=\"margin-right:0.2778em;\"></span><span>=</span><span style=\"margin-right:0.2778em;\"></span></span><span><span style=\"height:0.6644em;\"></span><span><span>2</span><span><span><span><span style=\"height:0.6644em;\"><span style=\"top:-3.063em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span>m</span></span></span></span></span></span></span></span></span></span></span>:物理地址空间中的地址数量</li>\n<li><span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:0.6833em;\"></span><span style=\"margin-right:0.13889em;\">P</span><span style=\"margin-right:0.2778em;\"></span><span>=</span><span style=\"margin-right:0.2778em;\"></span></span><span><span style=\"height:0.6644em;\"></span><span><span>2</span><span><span><span><span style=\"height:0.6644em;\"><span style=\"top:-3.063em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span>p</span></span></span></span></span></span></span></span></span></span></span>:页大小(字节)</li>\n</ul>\n<hr>\n<ul>\n<li>VPN:虚拟页号</li>\n<li>VPO:虚拟页面偏移量</li>\n<li>TLBT: TLB标记</li>\n<li>TLBI:TLB索引</li>\n</ul>\n<hr>\n<ul>\n<li>PPO:物理页面偏移量,和VPO一样</li>\n<li>PPN:物理页号</li>\n<li>CO:缓存块内字节偏移量</li>\n<li>CI: cache索引</li>\n<li>CT: cache标记</li>\n</ul>\n<p><img src=\"/assets/csapp/AddrTranslate.png\" alt=\"地址翻译\" /></p>\n<ul>\n<li>CPU中的一个控制寄存器,页表基址寄存器(PTBR)指向当前页表基址。</li>\n<li>虚拟地址=VPN(n-p位)+VPO(p位)</li>\n<li>VPN和页表中的PTE一一对应。</li>\n<li>物理地址=PPN+VPO</li>\n</ul>\n<p>页面命中时CPU执行步骤:</p>\n<ol>\n<li>处理器生成一个虚拟地址,并把它传送给 MMU。</li>\n<li>MMU 生成 PTE 地址,并从高速缓存/主存请求得到它。</li>\n<li>高速缓存/主存向 MMU 返回 PTE。</li>\n<li>MMU 构造物理地址,并把它传送给高速缓存/主存。</li>\n<li>高速缓存/主存返回所请求的数据字给处理器。</li>\n</ol>\n<p><img src=\"/assets/csapp/FindAddr.png\" alt=\"寻址\" /></p>\n<p>缺页时:</p>\n<ul>\n<li>第 1 步到第 3 步:和命中时的第 1 步到第 3 步相同。</li>\n<li>第 4 步:PTE 中的有效位是零,所以 MMU 触发了一次异常,传递 CPU 中的控制到操作系统内核中的缺页异常处理程序。</li>\n<li>第 5 步:缺页处理程序确定出物理内存中的牺牲页,如果这个页面已经被修改了,则把它换出到磁盘。</li>\n<li>第 6 步:缺页处理程序页面调入新的页面,并更新内存中的 PTE。</li>\n<li>第 7 步:缺页处理程序返回到原来的进程,再次执行导致缺页的指令。CPU 将引起缺页的虚拟地址重新发送给 MMU。因为虚拟页面现在缓存在物理内存中,所以就会命中,在 MMU 执行了图 9-13b 中的步骤之后,主存就会将所请求字返回给处理器。</li>\n</ul>\n<h3 id=\"结合高速缓存和虚拟内存\"> 结合高速缓存和虚拟内存</h3>\n<p>因为MMU在CPU内,翻译地址时需要到主存中查页表,因此缓存可以发挥作用,缓存PTE就像其他数据字一样。</p>\n<h3 id=\"使用tlb加速地址翻译\"> 使用TLB加速地址翻译</h3>\n<p>为了减少到主存或缓存中查询页表的次数,在MMU中包括了一个关于PTE的小的缓存,称为翻译后备缓冲器(TLB),即快表。<br>\nTLB 是一个小的、虚拟寻址的缓存,其中每一行都保存着一个由单个 PTE 组成的块。</p>\n<p><img src=\"/assets/csapp/tlbpte.png\" alt=\"TLB\" /></p>\n<p>CPU访存时,地址中虚页号被分成tag+index,tag用于和TLB页表项中的tag比较,index用于定位需要比较的表项。</p>\n<ul>\n<li>TLB全相联时,没有index,只有Tag,虚页号需与每个Tag比较;</li>\n<li>TLB组相联时,则虚页号高位为Tag,低位为index,用作组索引</li>\n</ul>\n<p><img src=\"/assets/csapp/FindAddrTlb.png\" alt=\"寻址-tlb\" /></p>\n<h3 id=\"多级页表\"> 多级页表</h3>\n<p>如果我们有一个 32 位的地址空间、4KB 的页面和 4B 的 PTE,那么即使应用所引用的只是虚拟地址空间中很小的一部分,也总是需要一个 4MB 的页表驻留在内存中。对于地址空间为 64 位的系统来说,问题更大。所以需要压缩页表,常用方法是使用层次结构的页表。<br>\n假设 32 位虚拟地址空间被分为 4KB 的页,而每个页表条目都是 4 字节。还假设在这一时刻,虚拟地址空间有如下形式:内存的前 2K 个页面分配给了代码和数据,接下来的 6K 个页面还未分配,再接下来的 1023 个页面也未分配,接下来的 1 个页面分配给了用户栈。\n<img src=\"/assets/csapp/MultiLayerPT.png\" alt=\"多级页表\" /></p>\n<p>一级页表中的每个 PTE 负责映射虚拟地址空间中一个 4MB 的片(chunk),这里每一片都是由 1024 个连续的页面组成的。假设地址空间是 4GB,1024 个 PTE 已经足够覆盖整个空间了。如果片 i 中的每个页面都未被分配,那么一级 PTE i 就为空。如果在片 i 中至少有一个页是分配了的,那么一级 PTE i 就指向一个二级页表的基址。二级页表中的每个 PTE 都负责映射一个 4KB 的虚拟内存页面,就像我们查看只有一级的页表一样。<br>\n注意,使用 4 字节的 PTE,每个一级和二级页表都是 4KB 字节,这刚好和一个页面的大小是一样的。</p>\n<p>这种方法从两个方面减少了内存要求。</p>\n<ol>\n<li>如果一级页表中的一个 PTE 是空的,那么相应的二级页表就根本不会存在。这代表着一种巨大的潜在节约,因为对于一个典型的程序,4GB 的虚拟地址空间的大部分都会是未分配的。</li>\n<li>只有一级页表才需要总是在主存中;虚拟内存系统可以在需要时创建、页面调入或调出二级页表,这就减少了主存的压力;只有最经常使用的二级页表才需要缓存在主存中。</li>\n</ol>\n<p>使用 k 级页表层次结构的地址翻译。虚拟地址被划分成为 k 个 VPN 和 1 个 VPO。每个 VPN i 都是一个到第 i 级页表的索引。第 j 级页表中的每个 PTE都指向第 j+1 级的某个页表的基址。</p>\n<p>为了确定PPN,MMU必须访问k个PTE。看上去消耗很大,实际上TLB可以将不同层次上页表的PTE缓存起来,代价并没有大很多。</p>\n<p><img src=\"/assets/csapp/MultiPageTable.png\" alt=\"多级页表\" /></p>\n<h2 id=\"linux虚拟内存系统\"> Linux虚拟内存系统</h2>\n<p>Linux为每个进程维护一个单独的虚拟地址空间。形式如图。\n<img src=\"/assets/csapp/LinuxVMSystem.png\" alt=\"Linux虚拟内存系统\" /></p>\n<h3 id=\"linux虚拟内存区域\"> Linux虚拟内存区域</h3>\n<p>Linux将虚拟内存组织成一些区域(area,也叫做段,segment)的集合。一个段就是已分配的虚拟内存的连续片(chunk),并以某种形式相关联。每个虚拟页都保存在某个段中,如果引用的虚拟页不属于某个段,会引发段错误。<br>\n以段的形式组织内存,好处是:允许虚拟地址空间有间隙,因此内存无需记录那些不存在的虚拟页。</p>\n<h3 id=\"虚拟内存区域的内核数据结构\"> 虚拟内存区域的内核数据结构</h3>\n<ul>\n<li>内核为每个进程维护一个单独的任务结构(task_struct)。</li>\n<li>task_struct中一个条目指向一个描述虚拟内存当前状态的数据结构(mm_struct)。</li>\n<li>mm_struct中有两个重要字段:\n<ul>\n<li>pgd:指向<strong>第一级页表的基址</strong>。当内核运行该程序是,将pdg放到<strong>CR3控制寄存器</strong>中。</li>\n<li>mmap:指向一个区域结构(vm_area_structs)的链表。\n<ul>\n<li>每个vm_area_structs都描述了当前虚拟地址空间中的一个区域,包含:</li>\n<li>vm_start:指向区域的起始地址。</li>\n<li>vm_end:指向区域的结束地址。</li>\n<li>vm_prot:描述该区域包含的所有页的读写许可权限。</li>\n<li>vm_flags:描述一些其他信息,如该区域内的页面是与其他进程共享的,还是私有的。</li>\n<li>vm_next:指向下一个区域结构。</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n<p><img src=\"/assets/csapp/LinuxSegment.png\" alt=\"Linux虚拟内存段\" /></p>\n<h3 id=\"linux缺页处理异常\"> Linux缺页处理异常</h3>\n<p>设MMU在试图翻译某个VA时,触发了一个缺页。导致控制转到内核的缺页处理程序,随后执行:</p>\n<ol>\n<li>VA是否合法?也就是说,VA在某个区域结构定义的区域内吗?为了作出判断,需要将VA和链表中所有区域结构的vm_start,vm_end作比较。如果不合法,触发一个段错误,并终止进程。因为一个进程可以创建任意数量的新虚拟内存区域,所以这种顺序搜索效率不够。因而实际中,有优化。</li>\n<li>试图进行的内存访问是否合法?即进程的访问权限对不对?如果访问不合法,那么缺页处理程序会触发一个保护异常,从而终止进程。</li>\n<li>如果确实是对合法VA的合法操作。则选择牺牲页,如果有修改,则多一步换出操作,否则换入新的页面并更新页表。<br>\n当缺页处理程序返回时,CPU重新启动引起缺页的指令,该指令就能正常执行了。</li>\n</ol>\n<h2 id=\"内存映射\"> 内存映射</h2>\n<p>Linux 通过将一个虚拟内存区域与一个磁盘上的<strong>对象</strong>(object)关联起来,以初始化这个虚拟内存区域的内容,这个过程称为<strong>内存映射</strong>(memory mapping),虚拟内存区域可以映射到两种类型的对象上:</p>\n<ol>\n<li>**Linux 文件系统中的普通文件:**一个段可以映射到一个普通磁盘文件的连续部分,例如一个可执行目标文件。文件区(section)被分成页大小的片,每一片包含一个虚拟页面的初始内容。因为按需进行页面调度,所以这些虚拟页面没有实际交换进入物理内存,直到 CPU 第一次引用到页面。如果段比文件区要大,那么就用零来填充这个段的余下部分。</li>\n<li><strong>匿名文件:<strong>一个段也可以映射到一个匿名文件,匿名文件是由内核创建的,包含的全是二进制零。CPU 第一次引用这样一个段内的虚拟页面时,内核就在物理内存中找到一个合适的牺牲页面,如果该页面被修改过,就将这个页面换出来,用二进制零覆盖牺牲页面并更新页表,将这个页面标记为是驻留在内存中的。注意在磁盘和内存之间并没有实际的数据传送。因为这个原因,映射到匿名文件的段中的页面也叫做</strong>demand-zero page</strong>。</li>\n</ol>\n<p>无论在哪种情况中,一旦一个虚拟页面被初始化了,它就在一个由内核维护的专门的<strong>交换文件</strong>(swap file)之间换来换去。交换文件也叫做<strong>交换空间</strong>(swap space)或者<strong>交换区域</strong>(swap area)。需要意识到的很重要的一点是,在任何时刻,交换空间都限制着当前运行着的进程能够分配的虚拟页面的总数。</p>\n<h3 id=\"再看共享对象\"> 再看共享对象</h3>\n<p>一个对象可以被映射到虚拟内存的一个区域,要么作为共享对象,要么作为私有对象。</p>\n<ul>\n<li>如果一个进程将一个共享对象映射到它的虚拟地址空间的一个区域内,那么这个进程对这个区域的任何写操作,对于其它也把这个共享对象映射到虚拟内存中的进程而言也是可见的。而且,这些变化也会反映在磁盘上的原始对象中。</li>\n<li>对一个映射到私有对象的区域做的改变,对于其他进程来说是不可见的,并且进程对这个区域所做的任何写操作都不会反映在磁盘上的对象中。一个映射到共享对象的虚拟内存区域叫做<strong>共享区域</strong>。类似地,也有<strong>私有区域</strong>。</li>\n</ul>\n<p><img src=\"/assets/csapp/gsxd1.png\" alt=\"共享\" /></p>\n<p>因为每个对象都有一个唯一的文件名,内核可以迅速地判定进程 1 已经映射了这个对象,而且可以使进程 2 中的页表条目指向相应的物理页面。关键点在于即使对象被映射到了多个共享区域,物理内存中也只需要存放共享对象的一个副本。为了方便,我们将物理页面显示为连续的,但是在一般情况下当然不是这样的。</p>\n<p><img src=\"/assets/csapp/gsxd2.png\" alt=\"私有\" /></p>\n<p>私有对象使用一种叫做写时复制(copy-on-write)的技术映射到虚拟内存中。一个私有对象开始时共享对象的一样,在物理内存中只有私有对象的一份副本,但相应私有区域的页表条目都被标记为只读,并且区域结构的标记(vm_flags)为私有的、写时复制。当没有进程试图写它自己的私有区域时,它们就共享物理内存中对象的一个单独副本。然而,只要有一个进程试图写私有区域内的某个页面,那么这个写操作就会触发一个保护故障。当故障处理程序注意到保护异常是由于进程试图写一个标记为私有的、写时复制的区域中的一个页面而引起的时候,它就会在物理内存中创建这个页面的一个新副本,更新页表条目指向这个新的副本,然后恢复这个页面的可写权限。当故障处理程序返回时,CPU 重新执行这个写操作,现在在新创建的页面上这个写操作就可以正常执行了。</p>\n<p>通过尽可能延迟私有对象的拷贝操作,copy-on-write充分地使用了稀有的物理内存。</p>\n<h3 id=\"再看fork函数\"> 再看fork函数</h3>\n<p>当 fork 函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的 PID。为了给这个新进程创建虚拟内存,它复制当前进程的 mm_struct、区域结构和页表。并将两个进程中的每个页面都标记为只读,每个区域结构都标记为私有的、写时复制。</p>\n<p>当 fork 在新进程中返回时,新进程的虚拟内存和调用 fork 时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念。</p>\n<h3 id=\"再看-execve-函数\"> 再看 execve 函数</h3>\n<p><code>execve("a.out", NULL, NULL);</code><br>\nexecve 函数在当前进程中加载并运行包含在可执行目标文件 a.out 中的程序,用 a.out 程序替代当前程序。加载并运行 a.out 需要以下几个步骤:</p>\n<ol>\n<li>删除已存在的用户区域。删除当前进程虚拟地址的用户部分中的已存在的区域结构。</li>\n<li>映射私有区域。为新程序的text、data、bss 和栈区域创建新的区域结构。所有这些新的区域标记为私有的、写时复制的。代码和data区域被映射为 a.out 文件中的.text 和.data 区。<strong>bss 区域映射到匿名文件,其大小包含在 a.out 中。栈和堆区域也映射到匿名文件,初始长度为零。</strong>(C/C++的未初始化static变量,全局变量都是在.bss节,因此都会初始化为0。堆栈虽然会初始化为零,但堆栈都是动态的,其地址可能被复用,导致下一次分配到的地址可能存在之前留下的脏数据,因此,未初始化的堆栈变量的值无法确定)</li>\n<li>映射共享区域。如果 a.out 程序与共享对象链接,比如标准 C 库 libc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。</li>\n<li>设置程序计数器(PC)。execve 做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。</li>\n</ol>\n<p>下一次调度到这个进程时,它将从这个入口点开始执行。Linux 将根据需要换入代码和数据页面。</p>\n<p><img src=\"/assets/csapp/ExecveLoad.png\" alt=\"execve加载\" /></p>\n<h3 id=\"使用mmap函数的用户级内存映射\"> 使用mmap函数的用户级内存映射</h3>\n<p>Linux进程可以使用mmap函数来创建新的虚拟内存区域,并将对象映射到这些区域。</p>\n<div><pre><code><span><span>#</span><span>include</span> <span><unistd.h></span></span>\n<span><span>#</span><span>include</span> <span><sys/mman.h></span></span>\n\n<span>void</span> <span>*</span><span>mmap</span><span>(</span><span>void</span> <span>*</span>start<span>,</span> <span>size_t</span> length<span>,</span> <span>int</span> prot<span>,</span> <span>int</span> flags<span>,</span>\n <span>int</span> fd<span>,</span> <span>off_t</span> offset<span>)</span><span>;</span>\n\n<span>// 返回:若成功时则为指向映射区域的指针,若出错则为 MAP_FAILED(-1)。</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br></div></div><p>mmap 函数要求内核创建一个新的虚拟内存区域,最好是从地址 start 开始的一个区域,并将文件描述符 fd (用于打开文件的open函数的返回值)指定的对象的一个连续的片(chunk)映射到这个新的区域。连续的对象片大小为 length 字节,从距文件开始处偏移量为 offset 字节的地方开始。start 地址仅仅是一个暗示而不是命令,通常设为 NULL。\n<img src=\"/assets/csapp/mmap.png\" alt=\"mmap函数\" /></p>\n<p>参数 prot 包含描述新映射的虚拟内存区域的访问权限位(即在相应区域结构中的 vm_prot 位)。</p>\n<ul>\n<li>PROT_EXEC:这个区域内的页面由可以被 CPU 执行的指令组成。</li>\n<li>PROT_READ:这个区域内的页面可读。</li>\n<li>PROT_WRITE:这个区域内的页面可写。</li>\n<li>PROT_NONE:这个区域内的页面不能被访问。</li>\n</ul>\n<p>参数 flags 由描述被映射对象类型的位组成。</p>\n<ul>\n<li>MAP_ANON 标记位,表示被映射的对象是一个匿名对象,而相应的虚拟页面是请求二进制零的</li>\n<li>MAP_PRIVATE 表示是一个私有的、写时复制的对象</li>\n<li>MAP_SHARED 表示是一个共享对象。</li>\n</ul>\n<p>munmap 函数删除虚拟内存的区域:</p>\n<div><pre><code><span><span>#</span><span>include</span> <span><unistd.h></span></span>\n<span><span>#</span><span>include</span> <span><sys/mman.h></span></span>\n\n<span>int</span> <span>munmap</span><span>(</span><span>void</span> <span>*</span>start<span>,</span> <span>size_t</span> length<span>)</span><span>;</span>\n\n<span>// 返回:若成功则为 0,若出错则为 -1。</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br></div></div><p>munmap 函数删除从虚拟地址 start 开始的,由接下来 length 字节组成的区域。接下来对已删除区域的引用会导致段错误。</p>\n<details><summary>更多信息</summary>\n<div><pre><code><span><span>#</span><span>include</span> <span>\"csapp.h\"</span></span>\n\n<span>/*\n* mmapcopy - uses mmap to copy file fd to stdout\n*/</span>\n<span>void</span> <span>mmapcopy</span><span>(</span><span>int</span> fd<span>,</span> <span>int</span> size<span>)</span>\n<span>{</span>\n <span>char</span> <span>*</span>bufp<span>;</span> <span>/* ptr to memory-mapped VM area */</span>\n\n bufp <span>=</span> <span>mmap</span><span>(</span><span>NULL</span><span>,</span> size<span>,</span> PROT_READ<span>,</span> MAP_PRIVATE<span>,</span> fd<span>,</span> <span>0</span><span>)</span><span>;</span>\n <span>Write</span><span>(</span><span>1</span><span>,</span> bufp<span>,</span> size<span>)</span><span>;</span>\n <span>return</span><span>;</span>\n<span>}</span>\n\n<span>/* mmapcopy driver */</span>\n<span>int</span> <span>main</span><span>(</span><span>int</span> argc<span>,</span> <span>char</span> <span>*</span><span>*</span>argv<span>)</span>\n<span>{</span>\n <span>struct</span> <span>stat</span> stat<span>;</span>\n <span>int</span> fd<span>;</span>\n\n <span>/* Check for required command-line argument */</span>\n <span>if</span> <span>(</span>argc <span>!=</span> <span>2</span><span>)</span> <span>{</span>\n <span>printf</span><span>(</span><span>\"usage: %s <filename>\\n\"</span><span>,</span> argv<span>[</span><span>0</span><span>]</span><span>)</span><span>;</span>\n <span>exit</span><span>(</span><span>0</span><span>)</span><span>;</span>\n <span>}</span>\n\n <span>/* Copy the input argument to stdout */</span>\n fd <span>=</span> <span>Open</span><span>(</span>argv<span>[</span><span>1</span><span>]</span><span>,</span> O_RDONLY<span>,</span> <span>0</span><span>)</span><span>;</span>\n <span>fstat</span><span>(</span>fd<span>,</span> <span>&</span>stat<span>)</span><span>;</span>\n <span>mmapcopy</span><span>(</span>fd<span>,</span> stat<span>.</span>st_size<span>)</span><span>;</span>\n <span>exit</span><span>(</span><span>0</span><span>)</span><span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br><span>25</span><br><span>26</span><br><span>27</span><br><span>28</span><br><span>29</span><br><span>30</span><br><span>31</span><br><span>32</span><br></div></div></details>\n<h2 id=\"动态内存分配\"> 动态内存分配</h2>\n<p>动态内存分配器维护着一个进程的虚拟内存区域--堆</p>\n<ul>\n<li>堆紧接在未初始化的数据区域(bss)后开始,并向高地址增长。</li>\n<li>内核为每个进程维护一个变量 brk(读做 “break”),指向堆的顶部。</li>\n<li>分配器将堆视为一组不同大小的块(block)的集合来维护。每个块就是一个连续的虚拟内存片(chunk),要么是已分配的,要么是空闲的。</li>\n</ul>\n<p>分配器风格</p>\n<ul>\n<li>显式分配器(explicit allocator),要求应用显式地释放任何已分配的块。malloc/free, new/delete。</li>\n<li>隐式分配器(implicit allocator),要求分配器检测一个已分配块何时不再被程序所使用,并负责释放这个块。隐式分配器也叫做垃圾收集器(garbage collector),而自动释放未使用的已分配的块的过程叫做垃圾收集(garbage collection)</li>\n</ul>\n<h3 id=\"malloc和free函数\"> malloc和free函数</h3>\n<div><pre><code><span><span>#</span><span>include</span> <span><stdlib.h></span></span>\n<span>void</span> <span>*</span><span>malloc</span><span>(</span><span>size_t</span> size<span>)</span><span>;</span>\n<span>void</span> <span>*</span><span>calloc</span><span>(</span><span>size_t</span> size<span>)</span><span>;</span>\n<span>void</span> <span>free</span><span>(</span><span>void</span> <span>*</span>ptr<span>)</span><span>;</span>\n\n<span><span>#</span><span>include</span> <span><unistd.h></span></span>\n<span>void</span> <span>*</span><span>sbrk</span><span>(</span><span>intptr_t</span> incr<span>)</span><span>;</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br></div></div><ul>\n<li>malloc函数返回一个指针,指向大小至少为size字节的内存块。在32位模式中,malloc返回的块地址总是8的倍数。64位模式下,总是16的倍数。</li>\n<li>如果malloc遇到问题则返回NULL并设置errno。</li>\n<li>malloc不初始化它返回的内存。要初始化,使用calloc。</li>\n<li>sbrk函数通过将内核的brk指针增加incr来扩展和收缩堆(incr为负值)。成功返回brk的旧值,否则返回-1,并设errno为ENOMEM。</li>\n<li>sbrk(0)返回当前brk的值。</li>\n<li>free的参数必须是已分配块的起始位置。否则free的行为未定义。</li>\n</ul>\n<div><p>峰值利用率</p>\n<p>为程序的聚集有效载荷(每个块的有效载荷的和)占当前堆大小的比例,在分配器分配释放内存的序列中达到的峰值。</p>\n</div>\n<h3 id=\"碎片\"> 碎片</h3>\n<ul>\n<li>内部碎片:已分配块比有效载荷大的部分。</li>\n<li>外部碎片:是当空闲内存合计起来足够满足一个分配请求,但是没有一个单独的空闲块足够大可以来处理这个请求时发生的。\n<ul>\n<li>外部碎片不仅取决于以前请求的模式和分配器的实现方式,还取决于将来请求的模式。例如,现在内存中只有6个字的空闲,但这六个字是分散的,有两个3字的空闲块组成的。那么,对于一个6字的请求,就存在两个碎片,对于一个3字的请求,就不存在碎片。</li>\n<li>因为外部碎片难以量化且不可能预测,所以分配器通常釆用启发式策略来试图维持少量的大空闲块,而不是维持大量的小空闲块。</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"分配器的实现问题\"> 分配器的实现问题</h3>\n<p>最简单的分配器会把堆组织成一个大的字节数组,还有一个指针 p,初始指向这个数组的第一个字节。为了分配 size 个字节,malloc 将 p 的当前值保存在栈里,将 p 增加 size,并将 p 的旧值返回到调用函数。free 只是简单地返回到调用函数,而不做其他任何事情。</p>\n<p>这个简单的分配器是设计中的一种极端情况。因为每个 malloc 和 free 只执行很少量的指令,吞吐率会极好。然而,因为分配器从不重复使用任何块,内存利用率将极差。一个实际的分配器要在吞吐率和利用率之间把握好平衡,就必须考虑以下几个问题:</p>\n<ul>\n<li>空闲块组织:我们如何记录空闲块?(核心)</li>\n<li>放置:我们如何选择一个合适的空闲块来放置一个新分配的块?</li>\n<li>分割:在将一个新分配的块放置到某个空闲块之后,我们如何处理这个空闲块中的剩余部分?</li>\n<li>合并:我们如何处理一个刚刚被释放的块?</li>\n</ul>\n<h3 id=\"隐式空闲链表\"> 隐式空闲链表</h3>\n<p>任何实际的分配器都需要一些数据结构,允许它来区别块边界,以及区别已分配块和空闲块。大多数分配器将这些信息嵌入块本身。\n<img src=\"/assets/csapp/StackBlock.png\" alt=\"堆块的格式\" /></p>\n<ul>\n<li>头部编码了这个块的大小(包括头部和所有的填充),以及这个块是已分配的还是空闲的。</li>\n<li>有效载荷的大小为malloc的参数。malloc返回的指针,指向的就是有效载荷的首地址。</li>\n<li>填充可能是分配器策略的一部分,用来对付外部碎片。或者也需要用它来满足对齐要求。</li>\n</ul>\n<div><p>注意</p>\n<p>因为对齐约束条件,块大小就总是 8 的倍数,也就是说块大小的最低 3 位总是零。因此,内存大小只要用到其 29 个高位,剩余的 3 位来编码其他信息。我们用其中的最低位(称已分配位)来指明这个块是已分配的还是空闲的。</p>\n</div>\n<p>将堆组织为一个连续的已分配块和空闲块的序列,称为隐式空闲链表,因为空闲块是通过头部中的大小字段隐含地连接着的。\n<img src=\"/assets/csapp/ImplicitList.png\" alt=\"隐式空闲列表\" />\n注意:结束块需要某种特殊标记,在这个示例中,就是一个设置了已分配位而大小为零的终止头部(terminating header)。</p>\n<p>系统对齐要求和分配器对块格式的选择会对分配器上的最小块大小有强制的要求。</p>\n<h4 id=\"放置\"> 放置</h4>\n<p>当一个应用请求一个k字节的块时,分配器搜索空闲链表,查找一个足够大可以放置所请求块的空闲块。搜索的方式由放置策略确定。</p>\n<ul>\n<li>首次适配:从头开始搜索空闲链表,选择第一个合适的空闲块。</li>\n<li>下一次适配:从上一次查询结束的地方开始搜索空闲链表,选择第一个合适的空闲块。</li>\n<li>最佳适配:检查每个空闲块,选择适合所需请求大小的最小空闲块。</li>\n</ul>\n<h4 id=\"分割\"> 分割</h4>\n<p>一旦分配器找到一个匹配的空闲块,它就必须做另一个策略决定,那就是分配这个空闲块中多少空间。一个选择是用整个空闲块。虽然这种方式简单而快捷,但是主要的缺点就是它会造成内部碎片。分配器通常会选择将这个空闲块分割为两部分。第一部分变成分配块,而剩下的变成一个新的空闲块。</p>\n<div><p>注意</p>\n<p>如果分配器不能为请求块找到合适的空闲块将发生什么呢?</p>\n<ul>\n<li>合并那些在内存中物理上相邻的空闲块来创建一些更大的空闲块</li>\n<li>调用 sbrk 函数,向内核请求额外的堆内存。分配器将额外的内存转化成一个大的空闲块,将这个块插入到空闲链表中,然后将被请求的块放置在这个新的空闲块中。</li>\n</ul>\n</div>\n<h4 id=\"合并\"> 合并</h4>\n<p>当分配器释放一个已分配块时,可能有其他空闲块与这个新释放的空闲块相邻。这些邻接的空闲块可能引起一种现象,叫做假碎片(fault fragmentation),就是有许多可用的空闲块被切割成为小的、无法使用的空闲块。为了解决假碎片问题,任何实际的分配器都必须合并相邻的空闲块,这个过程称为合并(coalescing)。</p>\n<p>合并策略</p>\n<ul>\n<li>立即合并(immediate coalescing):在每次一个块被释放时,就合并所有的相邻块。</li>\n<li>推迟合并(deferred coalescing):等到某个稍晚的时候再合并空闲块。例如,某个分配请求失败时,扫描整个堆,合并所有的空闲块。</li>\n<li>立即合并简单明了,可以在常数时间内执行完成,但是对于某些请求模式,这种方式会产生一种形式的抖动,块会反复地合并,然后马上分割。快速的分配器通常会选择某种形式的推迟合并。</li>\n</ul>\n<h4 id=\"带边界标记的合并\"> 带边界标记的合并</h4>\n<p>设想要释放的块为当前块。那么,合并下一个空闲块很简单而且高效。<strong>当前块的头部指针+头部大小=下一块的头部指针</strong>,可以检查这个指针以判断下一个块是否是空闲的。如果是,就将它的大小简单地加到当前块头部的大小上,这两个块在常数时间内被合并。但是,对于前一块,因为不知道块大小,所以无法找到其头部指针,因而无法判断其是否空闲,也无从合并。</p>\n<p>Knuth提出了一种解决方法,叫<strong>边界标记(boundary tag)</strong>。<br>\n在每个块的结尾处添加一个脚部(footer,边界标记),实际上就是头部的一个副本。这样,因为头部指针大小是固定的,从当前块的头部指针上移一个指针就能找到上一块的脚步,从而确定其是否空闲,块大小为多少。</p>\n<p>使用边界标记合并共有4种情况:</p>\n<ol>\n<li>前面的块和后面的块都是已分配的。</li>\n<li>前面的块是已分配的,后面的块是空闲的。</li>\n<li>前面的块是空闲的,而后面的块是已分配的。</li>\n<li>前面的和后面的块都是空闲的。\n<img src=\"/assets/csapp/BoundaryTag.png\" alt=\"边界标记\" /></li>\n</ol>\n<p>边界标记的概念是简单优雅的,它对许多不同类型的分配器和空闲链表组织都是通用的。然而,它也存在一个潜在的缺陷。它要求每个块都保持一个头部和一个脚部,在应用程序操作许多个小块时,会产生显著的内存开销。</p>\n<div><p>边界标记的优化方法</p>\n<p>考虑合并的四种情况,只有在前面的块是空闲时(情况3,4),才会需要用到它的脚部。因此,可以将表明前面块是已分配还是空闲的标志位,放在当前块头部的3个低位中(最低位是当前块的标志,次低位是前一块的标志),这样已分配的块就不要footer了。但注意,空闲块仍然需要footer。<br>\n例如,如果当前块的次低位为1,即前一块是已分配的,那么合并时用不到前一块,自然不需要前一块的footer。如果次低位是0,即前一块是未分配的,可以通过空闲块的footer找到其头部,将前一块和当前块合并。至于后面的块,本来就用不到footer,用头部即可。</p>\n</div>\n<h3 id=\"显式空闲链表\"> 显式空闲链表</h3>\n<p>隐式空闲链表,块分配与堆块的总数呈线性关系(每次分配块,都要从头开始扫描整个堆),所以对于通用的分配器,隐式空闲链表是不适合的。一种更好的方法是将空闲块组织为某种形式的显式数据结构。另外,根据定义,程序不需要一个空闲块的主体,所以实现这个数据结构的指针可以存放在这些空闲块的主体里面。</p>\n<p><img src=\"/assets/csapp/ExplicitList.png\" alt=\"显示空闲链表\" /></p>\n<ul>\n<li>使用双向链表而不是隐式空闲链表,使首次适配的分配时间从块总数的线性时间减少到了空闲块数量的线性时间。</li>\n<li>释放时间取决于链表中块的排序策略。\n<ul>\n<li>用后进先出的顺序维护链表,将新释放的块放置在链表的开始处。释放与合并可以在常数时间内完成。</li>\n<li>按照地址顺序来维护链表,其中链表中每个块的地址都小于它后继的地址。在这种情况下,释放一个块需要线性时间的搜索来定位合适的前驱。但首次适配的内存利用率更高。</li>\n</ul>\n</li>\n</ul>\n<p>一般而言,显式链表的缺点是空闲块必须足够大,以包含所有需要的指针,以及头部和可能的脚部。这就导致了更大的最小块大小,也潜在地提高了内部碎片的程度。</p>\n<h3 id=\"分离的空闲链表\"> 分离的空闲链表</h3>\n<p>分离存储(segregated storage),就是维护多个空闲链表,其中每个链表中的块有大致相等的大小。一般的思路是将所有可能的块大小分成一些等价类,也叫做大小类(size class,例如{17~32}表示大小为17到32字之内的内存都属于这一类)。<br>\n分配器维护着一个空闲链表数组,每个大小类一个空闲链表,按照大小的升序排列。当分配器需要一个大小为 n 的块时,它就搜索相应的空闲链表。如果不能找到合适的块与之匹配,它就搜索下一个链表,以此类推。</p>\n<h4 id=\"简单分离存储\"> 简单分离存储</h4>\n<ul>\n<li>每个大小类的空闲链表包含大小相等的块,每个块的大小就是这个大小类中最大元素的大小。例如{17~32}中就只有大小为32字的块。</li>\n<li>空闲块是不会分割以满足分配请求的。</li>\n<li>如果链表为空,分配器就向操作系统请求一个固定大小的额外内存片(通常是页大小的整数倍),将这个片<strong>分成大小相等的块,并将这些块链接起来形成新的空闲链表</strong>。</li>\n<li>要释放一个块,分配器只要简单地将这个块插入到相应的空闲链表的前部。因为不分割,所以也不会有合并。</li>\n</ul>\n<hr>\n<ul>\n<li>优点:\n<ul>\n<li>分配和释放块都很快,常数时间。</li>\n<li>每个片中都是大小相等的块,不分割,不合并,内存开销少(不需要头部,脚部,单向链表只要一个指针)。</li>\n<li>最小块大小为一个字,即succ指针</li>\n</ul>\n</li>\n<li>缺点:\n<ul>\n<li>非常容易造成内部和外部碎片。</li>\n</ul>\n</li>\n</ul>\n<h4 id=\"分离适配\"> 分离适配</h4>\n<p>每个链表包含潜在的大小不同的块,这些块的大小是大小类的成员。</p>\n<ul>\n<li>为了分配一个块,必须确定请求的大小类,并且对适当的空闲链表做首次适配,査找一个合适的块。</li>\n<li>如果找到了一个,那么就(可选地)分割它,并将剩余的部分插入到适当的空闲链表中。如果找不到,就搜索下一个更大的大小类的空闲链表。如此重复,直到找到一个合适的块。</li>\n<li>如果空闲链表中没有合适的块,那么就向操作系统请求额外的堆内存,从这个新的堆内存中<strong>分配出一个块</strong>,将剩余部分放置在适当的大小类中。</li>\n<li>要释放一个块,执行合并,并将结果放置到相应的空闲链表中。</li>\n</ul>\n<p>分离适配方法是一种常见的选择,C 标准库中提供的 GNU malloc 包就是釆用的这种方法,因为这种方法既快速,对内存的使用也很有效率。</p>\n<h4 id=\"伙伴系统\"> 伙伴系统</h4>\n<p>伙伴系统(buddy system)是分离适配的一种特例,其中每个大小类都是 2 的幂。基本的思路是假设一个堆的大小为<span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:0.6644em;\"></span><span><span>2</span><span><span><span><span style=\"height:0.6644em;\"><span style=\"top:-3.063em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span>m</span></span></span></span></span></span></span></span></span></span></span>个字,我们为每个块大小<span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:0.8491em;\"></span><span><span>2</span><span><span><span><span style=\"height:0.8491em;\"><span style=\"top:-3.063em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span style=\"margin-right:0.03148em;\">k</span></span></span></span></span></span></span></span></span></span></span>维护一个分离空闲链表,其中<span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:0.7804em;vertical-align:-0.136em;\"></span><span>0</span><span style=\"margin-right:0.2778em;\"></span><span>≤</span><span style=\"margin-right:0.2778em;\"></span></span><span><span style=\"height:0.8304em;vertical-align:-0.136em;\"></span><span style=\"margin-right:0.03148em;\">k</span><span style=\"margin-right:0.2778em;\"></span><span>≤</span><span style=\"margin-right:0.2778em;\"></span></span><span><span style=\"height:0.4306em;\"></span><span>m</span></span></span></span>。请求块大小向上舍入到最接近的 2 的幂。最开始时,只有一个大小为 <span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:0.6644em;\"></span><span><span>2</span><span><span><span><span style=\"height:0.6644em;\"><span style=\"top:-3.063em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span>m</span></span></span></span></span></span></span></span></span></span></span> 个字的空闲块。</p>\n<p>为了分配一个大小为<span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:0.8491em;\"></span><span><span>2</span><span><span><span><span style=\"height:0.8491em;\"><span style=\"top:-3.063em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span style=\"margin-right:0.03148em;\">k</span></span></span></span></span></span></span></span></span></span></span>的块,先找到第一个可用的大小足够的块。如果大小正好,结束。如果找到的块大了,则递归地二分割这个块,直到大小正好,剩下的半块(也叫做伙伴)被放置在相应的空闲链表中。为了释放一个大小为<span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:0.8491em;\"></span><span><span>2</span><span><span><span><span style=\"height:0.8491em;\"><span style=\"top:-3.063em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span style=\"margin-right:0.03148em;\">k</span></span></span></span></span></span></span></span></span></span></span>的块,只需要合并空闲的伙伴。当遇到一个已分配的伙伴时,就停止合并。</p>\n<ul>\n<li>关键事实:给定地址和块的大小,很容易计算出它的伙伴的地址。</li>\n<li>主要优点:快速搜索和快速合并。</li>\n<li>主要缺点:要求块大小为 2 的幂可能导致显著的内部碎片。</li>\n</ul>\n<h2 id=\"垃圾收集\"> 垃圾收集</h2>\n<p>垃圾收集器(garbage collector)是一种动态内存分配器,它自动释放程序不再需要的已分配块。这些块被称为垃圾(garbage)。自动回收堆存储的过程叫做垃圾收集(garbage collection)。</p>\n<h3 id=\"基本知识\"> 基本知识</h3>\n<p>垃圾收集器将内存视为一张有向可达图(reachability graph),其形式如图所示。\n<img src=\"/assets/csapp/GarbageCollection.png\" alt=\"垃圾收集\" /><br>\n该图的节点被分成一组根节点(root node)和一组堆节点(heap node)。</p>\n<ul>\n<li>每个堆节点对应于堆中的一个已分配块。</li>\n<li>有向边 p→q 意味着块 p 中的某个位置指向块 q 中的某个位置。</li>\n<li>根节点对应于本身不再堆中,但包含指向堆的指针的变量。</li>\n<li>p是可达的:存在一条从任意根节点出发并到达 p 的有向路径</li>\n</ul>\n<p>Java 的垃圾收集器,对应用如何创建和使用指针有很严格的控制,能够维护可达图的一种精确的表示,因此也就能够回收所有垃圾。然而,诸如 C 和 C++ 这样的语言的收集器通常不能维持可达图的精确表示。这样的收集器也叫做保守的垃圾收集器(conservative garbage collector)。即每个可达块都被正确地标记为可达了,而一些不可达节点却可能被错误地标记为可达。</p>\n<p>向malloc包中加入保守的GC后,malloc的流程为</p>\n<ol>\n<li>向malloc请求空间,找到了,返回。</li>\n<li>找不到合适的空闲块,则调用GC,希望回收一些垃圾到空闲链表。</li>\n<li>GC返回,malloc重试。成功找到,返回。</li>\n<li>还是失败,向OS要求额外的内存。成功,返回指针,否则返回NULL。</li>\n</ol>\n<h3 id=\"mark-sweep垃圾收集器\"> Mark&Sweep垃圾收集器</h3>\n<p>Mark&Sweep 垃圾收集器由标记(mark)阶段和清除(sweep)阶段组成,标记阶段标记出根节点的所有可达的和已分配的后继,而后面的清除阶段释放每个未被标记的已分配块。块头部中空闲的低位中的一位通常用来表示这个块是否被标记了。</p>\n<p>描述所需函数</p>\n<ul>\n<li>ptr 定义为 typedef void* ptr:</li>\n<li>ptr isPtr (ptr p)。如果 p 指向一个已分配块中的某个字,那么就返回一个指向这个块的起始位置的指针 b。否则返回 NULL。</li>\n<li>int blockMarked(ptr b)。如果块 b 是已标记的,那么就返回 true。</li>\n<li>int blockAllocated (ptr b)。如果块 b 是已分配的,那么就返回 true。</li>\n<li>void markBlock (ptr b)。标记块 b。</li>\n<li>int length (b)。返回块 b 的以字为单位的长度(不包括头部)。</li>\n<li>void unmarkBlock (ptr b)。将块 b 的状态由已标记的改为未标记的。</li>\n<li>ptr nextBlock (ptr b)。返回堆中块 b 的后继。</li>\n</ul>\n<div><pre><code><span>void</span> <span>mark</span><span>(</span>ptr p<span>)</span> <span>{</span>\n <span>if</span> <span>(</span><span>(</span>b <span>=</span> <span>isPtr</span><span>(</span>p<span>)</span><span>)</span> <span>==</span> <span>NULL</span><span>)</span>\n <span>return</span><span>;</span>\n <span>if</span> <span>(</span><span>blockMarked</span><span>(</span>b<span>)</span><span>)</span>\n <span>return</span><span>;</span>\n <span>markBlock</span><span>(</span>b<span>)</span><span>;</span>\n len <span>=</span> <span>length</span><span>(</span>b<span>)</span><span>;</span>\n <span>for</span> <span>(</span>i <span>=</span> <span>0</span><span>;</span> i <span><</span> len<span>;</span> i<span>++</span><span>)</span>\n <span>mark</span><span>(</span>b<span>[</span>i<span>]</span><span>)</span><span>;</span>\n <span>return</span><span>;</span>\n<span>}</span>\n\n<span>void</span> <span>sweep</span><span>(</span>ptr b<span>,</span> ptr end<span>)</span> <span>{</span>\n <span>while</span> <span>(</span>b <span><</span> end<span>)</span> <span>{</span>\n <span>if</span> <span>(</span><span>blockMarked</span><span>(</span>b<span>)</span><span>)</span>\n <span>unmarkBlock</span><span>(</span>b<span>)</span><span>;</span>\n <span>else</span> <span>if</span> <span>(</span><span>blockAllocated</span><span>(</span>b<span>)</span><span>)</span>\n <span>free</span><span>(</span>b<span>)</span><span>;</span>\n b <span>=</span> <span>nextBlock</span><span>(</span>b<span>)</span><span>;</span>\n <span>}</span>\n <span>return</span><span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br></div></div><ul>\n<li>标记阶段为每个根节点调用一次 mark 函数。</li>\n<li>清理阶段循环遍历堆上的每个块,为每个调用 sweep 函数。直到某次循环没有清理任何块。</li>\n</ul>\n<hr>\n<p>c语言的isPtr函数存在的问题</p>\n<ul>\n<li>C 不会用任何类型信息来标记内存位置。因此,对 isPtr 没有一种明显的方式来判断它的输入参数 p 是不是一个指针。这导致了C程序的Mark&Sweep收集器必须是保守的。</li>\n<li>即使 p 是一个指针,isPtr 也没有明显的方式来判断 p 是否指向一个已分配块的有效载荷中的某个位置。解决方法是将已分配块集合维护成一棵平衡二叉树,这棵树保持着这样一个属性:左子树中的所有块都放在较小的地址处,而右子树中的所有块都放在较大的地址处。每个已分配块的头部里有两个附加字段(left 和 right)。每个字段指向某个已分配块的头部。isPtr(ptr p) 函数用树来执行对已分配块的二分查找。在每一步中,它依赖于块头部中的大小字段来判断 p 是否落在这个块的范围之内。\n<img src=\"/assets/csapp/GCBST.png\" alt=\"垃圾收集平衡二叉树\" /></li>\n</ul>\n",
"image": "https://kigane.github.io/assets/csapp/VP-PP.png",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "编译器相关",
"url": "https://kigane.github.io/note/cs/compiler/",
"id": "https://kigane.github.io/note/cs/compiler/",
"content_html": "<h2 id=\"gcc\"> gcc</h2>\n<p>gcc在编译代码时可以做出优化,通过选项-On(n=0,1,2...)确定优化等级。优化后的代码在debug时,打印某些变量时会出现<code><optimized out></code>。解决方法就是关闭优化选项。或者,如果你只关心某个变量的话,可以将其用<code>volatile</code>关键字修饰。</p>\n<ul>\n<li>-E 预编译 .c->.i 对应cpp,即c预处理器,主要是在扩展include和宏。</li>\n<li>-S 编译为汇编代码 .i|.c->.s 对应cc1,即编译器,产生汇编代码。</li>\n<li>-c 汇编 .s->.o 对应as,即汇编器,产生可重定位目标文件,所有的指令都以二进制表示。</li>\n<li>-Idir 头文件搜索路径</li>\n<li>-Ldir 将dir指定为搜索库(-l指定的)的路径</li>\n<li>-llib 链接时,搜索liblib.a|liblib.so。如果有同名的静态库和动态库,则无-static时取动态库,有则取静态库。</li>\n<li>-O 开启优化选项。Og, O1, O2等开启了不同的优化选项,从性能考虑,O1,O2优化效果较好,但产生的代码变化会很大,很难理解源码和机器代码的关系,所以,学习时通常使用Og选项,有一定的优化,但源码的整体结构可以很好保存。</li>\n<li>-fno-stack-protector 关闭栈溢出检测</li>\n</ul>\n<hr>\n<ul>\n<li>-Wall 启用对某些用户可能会认为有问题(通常容易避免)的结构的警告。</li>\n<li>-Werror 让所有的warning变成error。</li>\n<li>-ggdb 产生给gdb用的调试信息。这意味着使用最有表达性的格式。(DWARF,stabs,如果两个都不支持则使用本地格式)</li>\n</ul>\n<h2 id=\"attribute\"> attribute</h2>\n<ul>\n<li>__attribute__((unused)) 如果变量声明了,但没有被引用过,也不弹出警告</li>\n<li>__attribute__((used)) 用于static函数或变量,告诉编译器,即使没被引用过也要在obj文件中保留(链接器删除未使用的section时,不会删除有该声明的函数或变量)。</li>\n</ul>\n<h2 id=\"工具\"> 工具</h2>\n<h3 id=\"反汇编器-objdump\"> 反汇编器 objdump</h3>\n<ul>\n<li>objdump -d main.o</li>\n</ul>\n",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "gdb",
"url": "https://kigane.github.io/note/cs/gdb-cheatsheet/",
"id": "https://kigane.github.io/note/cs/gdb-cheatsheet/",
"content_html": "<h2 id=\"gdb-cheatsheet\"> gdb cheatsheet</h2>\n<p><a href=\"https://sourceware.org/gdb/onlinedocs/gdb/index.html#SEC_Contents\" target=\"_blank\" rel=\"noopener noreferrer\">gdb 文档</a></p>\n<ul>\n<li>-g: 生成debugging symbols,使得调试更高效。-ggdb,包含gdb规定的符号。</li>\n</ul>\n<p>gdb [--args] program [args]进入debug模式</p>\n<ul>\n<li>set args <args></li>\n<li>run: 执行程序</li>\n<li>kill: 关闭执行的程序</li>\n<li>help</li>\n</ul>\n<p>检查栈</p>\n<ul>\n<li>backtrace/where | bt: 查看调用栈</li>\n<li>backtrace/where full: 查看调用栈,也打印栈帧中的变量</li>\n<li>frame frame#: 查看指定帧(backtrace列出的信息开头的数字)</li>\n</ul>\n<p>变量和内存</p>\n<ul>\n<li>x/nfu addr: examine addr。查看指定地址的值\n<ul>\n<li>n: 要打印的unit数量</li>\n<li>f: format</li>\n<li>u: unit (b:Byte 1B, h:Half-word 2B, w:Word 4B, g:Giant word 8B)</li>\n</ul>\n</li>\n<li>p <what>: 查看变量的值</li>\n<li>p arr[n]@m: 查看数组arr从n开始数,共m个元素</li>\n<li>print/format <what>: 查看变量的值</li>\n<li>display/format <what>: 在每个step指令后打印变量的值</li>\n<li>undisplay display#: 不再监视</li>\n<li>enable/disable display#</li>\n<li><what>\n<ul>\n<li>expressions</li>\n<li>filename::variable_name</li>\n<li>function::variable_name</li>\n<li>{type}address : 在address处的内容,解释为C的type类型。</li>\n<li><span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:0.854em;vertical-align:-0.1944em;\"></span><span>re</span><span style=\"margin-right:0.03588em;\">g</span><span>i</span><span>s</span><span>t</span><span style=\"margin-right:0.02778em;\">er</span><span style=\"margin-right:0.2778em;\"></span><span>:</span><span style=\"margin-right:0.2778em;\"></span></span><span><span style=\"height:0.6833em;\"></span><span>有名字的寄存器,如</span></span></span></span>esp--栈指针,<span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:0.8889em;vertical-align:-0.1944em;\"></span><span>e</span><span>b</span><span>p</span><span style=\"margin-right:0.2222em;\"></span><span>−</span><span style=\"margin-right:0.2222em;\"></span></span><span><span style=\"height:0.7667em;vertical-align:-0.0833em;\"></span><span>−</span><span>帧基址,</span></span></span></span>eip--指令指针</li>\n</ul>\n</li>\n<li>format\n<ul>\n<li>a: 指针</li>\n<li>c: 读为int,打印为char</li>\n<li>d: 有符号int</li>\n<li>f: 浮点数</li>\n<li>o: 八进制int</li>\n<li>s: Try to treat as C string</li>\n<li>t: 二进制int</li>\n<li>u: 无符号int</li>\n<li>x: 十六进制int</li>\n</ul>\n</li>\n</ul>\n<p>查看源码</p>\n<ul>\n<li>list line_number: 查看101行附件10行</li>\n<li>list from,to: 查看从from到to行</li>\n<li>list -: 查看前10行</li>\n<li>list <where></li>\n</ul>\n<p>断点</p>\n<ul>\n<li>break where: 在指定位置设置一个断点(会输出断点所在具体位置,注意到断点有序号,Breakpoint 1 at 0x29fa0: file main.cc, line 52.)</li>\n<li>where\n<ul>\n<li>function_name</li>\n<li>line_number: 当前文件的某行</li>\n<li>file:line_number</li>\n</ul>\n</li>\n<li>delete breakpoint#: 删除指定断点</li>\n<li>enable breakpoint</li>\n<li>disable breakpoint</li>\n<li>clear: 删除所有断点</li>\n<li>condition 1 var==666: 仅当变量var的值为666时,才触发断点1。</li>\n<li>break/watch <where> if <condition></li>\n<li>watch where // breakpoint 和 watchpoint 共用编号。</li>\n<li>watchpoint 有两种实现方式,具体取决于你的系统。软件实现方式为在每一步执行后都测试一次expr的值,会很慢。硬件方式更快。</li>\n<li>watch [-l|-location] expr [thread thread-id] [mask maskvalue] 为expr设置一个监视点。如果expr的值改变了,则停止执行。\n<ul>\n<li>thread thread-id 指定某个线程,只有该线程修改expr的值时才停止执行。(仅限硬件实现)</li>\n<li>-l 告诉gdb,如果expr的结果是个地址,则查看该地址的内容。如果结果不是地址,则输出一个错误。</li>\n<li>mask maskvalue 默认开启-l。maskvalue用于同时查看多数地址。 watch foo mask 0xffffff00?</li>\n</ul>\n</li>\n<li>rwatch expr 在expr被程序读取时break</li>\n<li>awatch expr 在expr被读取或被修改时都break</li>\n<li>delete/enable/disable watchpoint#</li>\n</ul>\n<p>步进</p>\n<ul>\n<li>gdb的一个有用的特性:如果直接按回车,会重复执行上一个命令</li>\n<li>step: 到下一行。如果有函数会进入函数</li>\n<li>next: 到下一行。不会进入函数</li>\n<li>finish: 将当前函数执行完</li>\n<li>continue | cont: 继续正常执行</li>\n<li>quit: 退出</li>\n</ul>\n<p>反向执行</p>\n<ul>\n<li>record 反向执行的前置条件</li>\n<li>reverse-step</li>\n<li>reverse-next</li>\n<li>reverse-continue</li>\n<li>reverse-finish 回到函数执行前</li>\n<li>set exec-direction reverse 所有next,step...全变为逆向操作</li>\n<li>set exec-direction forward 恢复正常</li>\n</ul>\n<p>信息</p>\n<ul>\n<li>info args: 当前栈帧的函数参数</li>\n<li>info breakpoints/watchpoints</li>\n<li>info locals: 当前栈帧的本地变量</li>\n<li>info sharedlibrary: 列出已加载的动态库</li>\n<li>info signals: 列出所有信号和他们当前是如何处理的</li>\n<li>info threads</li>\n<li>show directories: 列出GDB搜索涉及到的源文件</li>\n<li>show listsize</li>\n<li>whatis variable_name: 输出变量的类型</li>\n</ul>\n<h2 id=\"gdb-tui\"> gdb-tui</h2>\n<p>标志</p>\n<ul>\n<li>\n<p><code>></code> 当前执行的行</p>\n</li>\n<li>\n<p>断点表示 [b|B][+|-]</p>\n<ul>\n<li><code>b</code> 表示还没到的断点</li>\n<li><code>B</code> 表示至少到过一次的断点</li>\n<li><code>+</code> 表示 enabled</li>\n<li><code>-</code> 表示 disabled</li>\n</ul>\n</li>\n<li>\n<p>C-x a/A/C-a: 进入或离开TUI模式</p>\n</li>\n<li>\n<p>C-x 1: 保留一个窗口</p>\n</li>\n<li>\n<p>C-x 2: 保留两个窗口</p>\n</li>\n<li>\n<p>C-x o: 切换当前活动的窗口</p>\n</li>\n<li>\n<p>C-x s: 切换到TUI的单键模式</p>\n</li>\n<li>\n<p>PgUp,PgDn,上下左右可用于操作活动窗口。</p>\n</li>\n<li>\n<p>当焦点不在cmd窗口时,C-p相当于Up,C-b相当于Left,C-f相当于Right,C-n相当于Down</p>\n</li>\n<li>\n<p>C-l: 刷新窗口</p>\n</li>\n</ul>\n<p>单键模式</p>\n<ul>\n<li>s: step</li>\n<li>i: step Into</li>\n<li>n: next</li>\n<li>o: step Over</li>\n<li>r: run</li>\n<li>c: continue</li>\n<li>f: finish</li>\n<li>d: down 在frame stack中向下一层</li>\n<li>u: up 在frame stack中向上一层</li>\n<li>q: 退出单键模式</li>\n<li>v: info local</li>\n<li>w: where</li>\n</ul>\n<p>TUI命令</p>\n<ul>\n<li>\n<p>tui enable: 进入上次使用的tui窗口模式,或默认窗口模式。</p>\n</li>\n<li>\n<p>tui disable</p>\n</li>\n<li>\n<p>tui new-layout name window weight [window weight ...]: 新建一个名为name的TUI窗口布局。</p>\n<ul>\n<li>可以使用layout name使用该布局</li>\n<li>window 有四种 src,asm,regs,cmd</li>\n<li>weight 是权重,用于确定每个窗口占屏幕的比例。status的权重应总是设为0</li>\n<li>默认是从上到下分割。加-horizontal可改为从左到右分割。</li>\n<li>示例1:(gdb) tui new-layout example src 1 regs 1 status 0 cmd 1</li>\n<li>示例2:(gdb) tui new-layout example {-horizontal src 1 asm 1} 2 status 0 cmd 1</li>\n<li>9.2版本还没有实装</li>\n</ul>\n</li>\n<li>\n<p>layout name:使用内置布局或新建的布局</p>\n<ul>\n<li>prev: 前一个</li>\n<li>next: 后一个</li>\n<li>src: 显示源码和命令窗口</li>\n<li>asm:显示汇编和命令窗口</li>\n<li>split:显示源码,汇编和命令窗口</li>\n<li>regs:显示寄存器窗口。如果在src模式则上为寄存器,中为源码。如果在split和asm模式,则上为寄存器,中为汇编。</li>\n</ul>\n</li>\n<li>\n<p>focus name: 改变当前活动窗口。</p>\n</li>\n<li>\n<p>refresh: 刷新窗口</p>\n</li>\n<li>\n<p>winheight name +count: 改变窗口大小</p>\n</li>\n<li>\n<p>winheight name -count: 改变窗口大小</p>\n</li>\n<li>\n<p>tui reg group:显示不同的寄存器分组</p>\n<ul>\n<li>general: 通用寄存器</li>\n<li>float: 浮点数寄存器</li>\n<li>system: 系统寄存器</li>\n<li>vector: 向量寄存器</li>\n<li>all</li>\n</ul>\n</li>\n<li>\n<p>set tui compact-source [on|off]:设置源码的行号和代码间的距离。on-仅一个空格,off-一个tab。</p>\n</li>\n</ul>\n<h2 id=\"小技巧\"> 小技巧</h2>\n<ul>\n<li>对一个大数组,p arr[n]@m,查看数组arr从n开始数,共m个元素</li>\n<li>gdb 命令输出在src模式下可见部分太小了。可以使用\n<ul>\n<li>set trace-commands on</li>\n<li>set logging on</li>\n<li>cd where/gdb/is/running</li>\n<li>tail -f -n 30 gdb.txt</li>\n<li>less +F gdb.txt 可以代替tail。 ctrl+c,退出等待输入模式,进入普通less模式,shift+f进入。</li>\n<li>进入TUI模式后,日志会停止,这是个gdb bug。先set logging off,再set logging on即可正常显示。</li>\n</ul>\n</li>\n</ul>\n",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "git",
"url": "https://kigane.github.io/note/cs/git/",
"id": "https://kigane.github.io/note/cs/git/",
"content_html": "<h2 id=\"资源\"> 资源</h2>\n<p><a href=\"https://learngitbranching.js.org/?locale=zh_CN\" target=\"_blank\" rel=\"noopener noreferrer\">交互式Git入门网站 强烈推荐!!!</a><br>\n<a href=\"https://git-scm.com/book/zh/v2\" target=\"_blank\" rel=\"noopener noreferrer\">Pro Git 中文版</a><br>\n<a href=\"https://ohshitgit.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Oh Shit, Git!?! 简短的介绍了如何从Git错误中恢复</a></p>\n<p><a href=\"https://eagain.net/articles/git-for-computer-scientists/\" target=\"_blank\" rel=\"noopener noreferrer\">Git 的数据模型</a><br>\n<a href=\"https://jwiegley.github.io/git-from-the-bottom-up/1-Repository/1-directory-content-tracking.html\" target=\"_blank\" rel=\"noopener noreferrer\">Git from the Bottom Up</a></p>\n<h2 id=\"git-的数据模型\"> Git 的数据模型</h2>\n<h3 id=\"快照\"> 快照</h3>\n<p>在Git的术语里,文件被称作Blob对象,也就是一组数据。目录则被称之为“树”,它将名字与 Blob 对象或树对象进行映射(使得目录中可以包含其他目录)。快照则是被追踪的最顶层的树。</p>\n<div><pre><code>&lt;root> (tree)\n|\n+- foo (tree)\n| |\n| + bar.txt (blob, contents = "hello world")\n|\n+- baz.txt (blob, contents = "git is wonderful")\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br></div></div><p>这个顶层的树包含了两个元素,一个名为 “foo” 的树,以及一个 blob 对象 “baz.txt”。</p>\n<h3 id=\"历史记录\"> 历史记录</h3>\n<p>在 Git 中,历史记录是一个由快照组成的有向无环图。这代表 Git 中的每个快照都有一系列的“父辈”,也就是其之前的一系列快照。注意,快照具有多个“父辈”而非一个,因为某个快照可能由多个父辈而来。例如,经过合并后的两条分支。</p>\n<p>在 Git 中,这些快照被称为“提交”。</p>\n<h3 id=\"数据模型的伪码表示\"> 数据模型的伪码表示</h3>\n<div><pre><code><span>//</span> 文件就是一组数据\n<span>type</span> blob <span>=</span> array<span><</span>byte<span>></span>\n\n<span>//</span> 一个包含文件和目录的目录\n<span>type</span> tree <span>=</span> <span>map</span><span><</span>string<span>,</span> tree <span>|</span> blob<span>></span>\n\n<span>//</span> 每个提交都包含一个父辈,元数据和顶层树\n<span>type</span> commit <span>=</span> struct <span>{</span>\n parent<span>:</span> array<span><</span>commit<span>></span>\n author<span>:</span> string\n message<span>:</span> string\n snapshot<span>:</span> tree\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br></div></div><h3 id=\"对象和内存寻址\"> 对象和内存寻址</h3>\n<div><pre><code><span>//</span> git中的对象类型\n<span>type</span> <span>object</span> <span>=</span> blob <span>|</span> tree <span>|</span> commit\n\nobjects <span>=</span> <span>map</span><span><</span>string<span>,</span> <span>object</span><span>></span>\n\n<span>//</span> Git 在储存数据时,所有的对象都会基于它们的 SHA<span>-</span><span>1</span> 哈希 进行寻址\n<span>def</span> <span>store</span><span>(</span><span>object</span><span>)</span><span>:</span>\n <span>id</span> <span>=</span> sha1<span>(</span><span>object</span><span>)</span>\n objects<span>[</span><span>id</span><span>]</span> <span>=</span> <span>object</span>\n\n<span>def</span> <span>load</span><span>(</span><span>id</span><span>)</span><span>:</span>\n <span>return</span> objects<span>[</span><span>id</span><span>]</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br></div></div><h3 id=\"引用\"> 引用</h3>\n<p>现在,所有的快照都可以通过它们的 SHA-1 哈希值来标记了。但这也太不方便了,谁也记不住一串 40 位的十六进制字符。(Git对哈希的处理很智能。你只需要提供能够唯一标识提交记录的前几个字符即可。但还是很不方便)</p>\n<p>针对这一问题,Git 的解决方法是给这些哈希值赋予人类可读的名字,也就是引用(references)。<strong>引用是指向提交的指针</strong>。与对象不同的是,它是可变的(引用可以被更新,指向新的提交)。例如,master 引用通常会指向主分支的最新一次提交。</p>\n<div><pre><code>references <span>=</span> <span>map</span><span><</span>string<span>,</span> string<span>></span>\n\n<span>def</span> <span>update_reference</span><span>(</span>name<span>,</span> <span>id</span><span>)</span><span>:</span>\n references<span>[</span>name<span>]</span> <span>=</span> <span>id</span>\n\n<span>def</span> <span>read_reference</span><span>(</span>name<span>)</span><span>:</span>\n <span>return</span> references<span>[</span>name<span>]</span>\n\n<span>def</span> <span>load_reference</span><span>(</span>name_or_id<span>)</span><span>:</span>\n <span>if</span> name_or_id <span>in</span> references<span>:</span>\n <span>return</span> load<span>(</span>references<span>[</span>name_or_id<span>]</span><span>)</span>\n <span>else</span><span>:</span>\n <span>return</span> load<span>(</span>name_or_id<span>)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br></div></div><p>通常情况下,我们会想要知道“我们当前所在位置”,并将其标记下来。这样当我们创建新的快照的时候,我们就可以知道它的相对位置(如何设置它的“父辈”)。在 Git 中,我们当前的位置有一个特殊的索引,它就是 “HEAD”。</p>\n<p>如果想看 HEAD 指向,可以通过 cat .git/HEAD 查看, 如果 HEAD 指向的是一个引用,还可以用 git symbolic-ref HEAD 查看它的指向。</p>\n<h3 id=\"仓库\"> 仓库</h3>\n<p>Git仓库就是对象(文件,目录,提交)和引用(分支,HEAD, hash, tag)。</p>\n<p>在硬盘上,Git仅存储对象和引用:因为其数据模型仅包含这些东西。所有的 git 命令都对应着对提交树的操作,例如增加对象,增加或删除引用。</p>\n<h2 id=\"相对引用\"> 相对引用</h2>\n<ul>\n<li>使用 <code>ref^</code> 向上移动1个提交记录(ref^^,2个)</li>\n<li>使用 <code>ref~<num></code> 向上移动多个提交记录,如 ~3(1也可以省略)</li>\n<li>应用:移动分支,使用-f选项让分支指向另一个提交。例如:<code>git branch -f main HEAD~3</code></li>\n<li><code>^<num></code>: 如果父节点不止一个,ref^就指向产生该节点(在这里merge的)的父节点,<code>ref^2</code>就指向另一个节点</li>\n<li>链式移动: <code>git checkout ~^2~3</code></li>\n</ul>\n<h2 id=\"暂存区\"> 暂存区</h2>\n<p>Git 中还包括一个和数据模型完全不相关的概念,但它确是创建提交的接口的一部分。允许您指定下次快照中要包括那些改动。</p>\n<p>使用场景:您开发了两个独立的特性,然后希望创建两个独立的提交,其中第一个提交仅包含第一个特性,而第二个提交仅包含第二个特性。</p>\n<h2 id=\"tag\"> tag</h2>\n<p><strong>永远指向某个提交记录的标识</strong>。通常软件发布新的大版本,或者是修正一些重要的Bug或是增加了某些新特性这种重要的修改可以添加一个tag。</p>\n<p>tag可以(在某种程度上 —— 因为标签可以被删除后重新在另外一个位置创建同名的标签)永久地将某个特定的提交命名为里程碑,然后就可以像分支一样引用了。</p>\n<p>更难得的是,它们并不会随着新的提交而移动。你也不能检出到某个标签上面进行修改提交,它就像是提交树上的一个锚点,标识了某个特定的位置。</p>\n<div><pre><code><span>git</span> tag tagName refspec <span># 在refspec提交上建立一个标签tagName</span>\n<span>git</span> tag tagName <span># 在HEAD指向的提交上建立一个标签</span>\n<span>git</span> describe refspec <span># 结果格式为tagName_distance_ghash,tagName是离refspec最近的tag的名称,distance是refspec离tag的距离,hash是refspec hash值的前几位。</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br></div></div><h2 id=\"cheatsheet\"> cheatsheet</h2>\n<p>Git中“refspec” 是一个自造的词,意思是Git能识别的位置(比如分支foo或者HEAD~1)。如果需要refspec参数缺省为HEAD。</p>\n<p>基础</p>\n<ul>\n<li>git help <command>: 获取 git 命令的帮助信息</li>\n<li>git init: 创建一个新的 git 仓库,其数据会存放在一个名为 .git 的目录下</li>\n<li>git status: 显示当前的仓库状态</li>\n<li>git add <filename>: 添加文件到暂存区</li>\n<li>git commit: 创建一个新的提交</li>\n<li>git commit --allow-empty: 创建一个新的提交,允许没有修改</li>\n<li>git log: 显示历史日志</li>\n<li>git log --author who: 显示who提交的历史日志</li>\n<li>git log --all --graph --decorate: 可视化历史记录(有向无环图)</li>\n<li>git diff <filename>: 显示与暂存区文件的差异</li>\n<li>git diff <revision> <filename>: 显示某个文件两个版本之间的差异</li>\n<li>git checkout <revision>: 更新 HEAD 和目前的分支</li>\n</ul>\n<p>分支和合并</p>\n<ul>\n<li>git branch: 显示分支</li>\n<li>git branch <name>: 创建分支</li>\n<li>git checkout -b <name>: 创建分支并切换到该分支</li>\n<li>git merge refspec: 合并到当前分支</li>\n<li>git mergetool: 使用工具来处理合并冲突</li>\n<li>git rebase: 将一系列补丁变基(rebase)为新的基线</li>\n</ul>\n<p>远端操作</p>\n<ul>\n<li>git remote: 列出远端</li>\n<li>git remote add <name> <url>: 添加一个远端</li>\n<li>git push <remote> <local branch>:<remote branch>: 将对象传送至远端并更新远端引用</li>\n<li>git branch --set-upstream-to=<remote>/<remote branch>: 创建本地和远端分支的关联关系</li>\n<li>git fetch: 从远端获取对象/索引</li>\n<li>git pull: 相当于 git fetch; git merge</li>\n<li>git clone: 从远端下载仓库</li>\n</ul>\n<p>撤销</p>\n<ul>\n<li>git commit --amend: 编辑提交的内容或信息。在提交树中HEAD所指节点的父节点下生成一个新的commit节点,并指向新的commit节点,原节点的子节点不会保留。所以最好只对叶节点使用 --amend.</li>\n<li>git reset HEAD~1: 撤销最近一次commit,在reset后所做的变更还在,但是处于未加入暂存区状态。适合在本地修改。</li>\n<li>git revert refspec: 会commit一次,将最近一次commit的修改撤销。适合多人协作时,撤销已push的修改。</li>\n<li>git checkout -- <file>: 丢弃修改</li>\n</ul>\n<p>Git 高级操作</p>\n<ul>\n<li>git config: Git 是一个 高度可定制的 工具</li>\n<li>git clone --depth=1: 浅克隆(shallow clone),不包括完整的版本历史信息</li>\n<li>git add -p: 交互式暂存</li>\n<li>git rebase to [from]: from不写默认为HEAD所指节点。将从from节点和to节点的最近公共父节点(不包括该父节点)到from结点本身的所有节点按顺序复制到to节点下,HEAD将指向被复制的from节点,另外from节点(路径上的其他节点的引用不会动)上的引用(分支,tag等)也会指向新的from节点。用于将from分支上的工作转移到to分支上。PS:如果from为to的祖先节点,则rebase只是将from的引用移动到to上。</li>\n<li>git rebase -i ref~n: 交互式变基(经典左闭右开/下闭上开)</li>\n<li>git cherry-pick refspec1 refspec2 ...:可以将提交树上任何其他地方的提交记录取过来追加到HEAD上(不能是HEAD上游的提交)</li>\n<li>git blame: 查看最后修改某行的人</li>\n<li>git stash: 暂时移除工作目录下的修改内容</li>\n<li>git bisect: 通过二分查找搜索历史记录</li>\n<li>git describe refspec: 结果格式为tagName_distance_ghash,tagName是离refspec最近的tag的名称,distance是refspec离tag的距离,hash是refspec hash值的前几位。</li>\n<li>.gitignore: 指定 故意不追踪的文件</li>\n</ul>\n<h3 id=\"git-stash\"> git stash</h3>\n<p><code>git stash</code>命令会暂时缓存任何你未提交的修改,包括暂存区的和未暂存的(没有通过<code>git add</code>命令添加到暂存区的,包括新add的增删文件,修改add过的文件),不包括未追踪的文件。stash就相当于将之前的修改都隐藏了起来,之后的操作不会受到之前修改的限制。\n注意:stash只保留在本地库中,不会被推送到远程库。</p>\n<div><pre><code><span># 做一些修改1</span>\n<span>git</span> stash\n<span># 再做一些修改2</span>\n<span>git</span> stash\n<span># 再再做一些修改3,并提交 假设三次修改的是不同的文件</span>\n<span># some changes</span>\n<span>git</span> commit\n<span># 不想要修改2了</span>\n<span>git</span> stash pop\n<span># 继续从修改1开始</span>\n<span>git</span> stash apply\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br></div></div><p>注意:git stash apply会将修改应用到所有分支。比如,在test分支修改了一个文件,stash以后再apply回来,这样master分支中也会有一个未提交的修改。</p>\n<h2 id=\"远程仓库\"> 远程仓库</h2>\n<h3 id=\"git-clone\"> git clone</h3>\n<p>git clone somerepo后,本地就有了somerepo的完整提交树,并且多了origin/master,origin/dev....等多了origin/前缀的远程分支(另有origin/HEAD->origin/main说明远程分支的HEAD指向哪里)。</p>\n<p>远程分支反映了远程仓库(在你上次和它通信时)的状态。</p>\n<p>远程分支有一个特别的属性,在你检出时自动进入分离HEAD状态(不是HEAD指向的分支上有*,而是HEAD独立显示出来)。Git这么做是因为不能直接在这些分支上进行操作, 你必须在本地完成你的工作, (更新了远程分支之后)再用远程分享你的工作成果。</p>\n<h3 id=\"git-fetch\"> git fetch</h3>\n<p>完成了仅有的但是很重要的两步:</p>\n<ol>\n<li>从远程仓库下载本地仓库中缺失的提交记录</li>\n<li>更新远程分支指针(如 o/main)</li>\n</ol>\n<p>实际上将本地仓库中的远程分支更新成了远程仓库相应分支最新的状态。但并不会改变你本地仓库的状态。它不会更新你的 main 分支,也不会修改你磁盘上的文件。</p>\n<p>要实际更新,需要使用git merge将远程和本地分支合并。(git cherry-pick, git rebase也可以)</p>\n<h3 id=\"git-pull\"> git pull</h3>\n<p>实际等同于 git fetch + git merge。\n用法参考<code>git push</code>/<code>git fetch</code></p>\n<h3 id=\"git-push\"> git push</h3>\n<p><code>git push remoteRepo place</code> (remoteRepo 通常为 origin)<br>\n<code>git push remoteRepo <source>:<remote branch></code> (source是refspec,如果remote branch是远程仓库中的分支名(没有origin/前缀)且不存在会自动创建新的)\n<code>git push remoteRepo :foo</code> (会删除远程的foo分支)\n将本地提交同步到远程,并将本地仓库的远程分支也更新好。</p>\n<p>PS:git fetch 语法类似,只是方向相反罢了。</p>\n<h3 id=\"最常见团队合作工作流\"> 最常见团队合作工作流</h3>\n<p>从某个commit之后,你和同事分别修改各自的代码,现在你的同事已经将他的代码同步到远程库了,你的代码该如何提交。(假设没有冲突)。</p>\n<p>方案一:git fetch; git rebase origin/main; git push\n方案二: git fetch; git merge origin/main; git push\n方案一简化版:git pull --rebase; git push\n方案二简化版: git pull; git push</p>\n<h3 id=\"远程服务器拒绝-remote-rejected\"> 远程服务器拒绝!(Remote Rejected)</h3>\n<p>如果你是在一个大的合作团队中工作, 很可能是main被锁定了, 需要一些Pull Request流程来合并修改。如果你直接提交(commit)到本地main, 然后试图推送(push)修改, 你将会收到这样类似的信息:</p>\n<blockquote>\n<p>! [远程服务器拒绝] main -> main (TF402455: 不允许推送(push)这个分支; 你必须使用pull request来更新这个分支.)</p>\n</blockquote>\n<p>远程服务器拒绝直接推送(push)提交到main, 因为策略配置要求 pull requests 来提交更新.</p>\n<p>你应该按照流程,新建一个分支, 推送(push)这个分支并申请pull request,但是你忘记并直接提交给了main.现在你卡住并且无法推送你的更新.</p>\n<p>解决办法\n新建一个分支feature, 推送到远程服务器. 然后reset你的main分支和远程服务器保持一致, 否则下次你pull并且他人的提交和你冲突的时候就会有问题.</p>\n<div><pre><code><span>git</span> reset --hard o/main <span># 重置索引和工作树。 自o/main以来的工作树中跟踪文件的任何更改都被丢弃。</span>\n<span>git</span> checkout -b feature rawmainSHA <span># reset不会删除已有的commit。所以仍能checkout到你修改过commit,同时新建分支。</span>\n<span>git</span> push origin feature <span># 将你的修改(现在在feature中)同步到远端的feature分支</span>\n\n<span># 我认为更好的方法</span>\n<span>git</span> branch feature\n<span>git</span> push origin feature\n<span>git</span> reset --hard origin/main\n<span>git</span> checkout feature\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br></div></div><p>然后再申请pull request。</p>\n<h3 id=\"合并特性分支\"> 合并特性分支</h3>\n<p>有些开发人员只在 main 上做 push、pull —— 这样的话 main 总是最新的,始终与远程分支 (o/main) 保持一致。</p>\n<p>这个工作流,分两个步骤:</p>\n<ol>\n<li>将特性分支集成到 main 上</li>\n<li>推送并更新远程分支 git pull --rebase; git push</li>\n</ol>\n<h3 id=\"rebase-和-merge\"> rebase 和 merge</h3>\n<p>在开发社区里,有许多关于 merge 与 rebase 的讨论。以下是关于 rebase 的优缺点:</p>\n<ul>\n<li>优点: Rebase 使你的提交树变得很干净, 所有的提交都在一条线上</li>\n<li>缺点: Rebase 修改了提交树的历史。比如, 提交 C1 可以被 rebase 到 C3 之后。这看起来 C1 中的工作是在 C3 之后进行的,但实际上是在 C3 之前。</li>\n</ul>\n<p>一些开发人员喜欢保留提交历史,因此更偏爱 merge。而其他人可能更喜欢干净的提交树,于是偏爱 rebase。</p>\n<h3 id=\"远程跟踪\"> 远程跟踪</h3>\n<p>直接了当地讲,main 和 o/main 的关联关系就是由分支的“remote tracking”属性决定的。main 被设定为跟踪 o/main —— 这意味着为 main 分支指定了推送的目的地以及拉取后合并的目标。</p>\n<p>当你克隆时, Git 会为远程仓库中的每个分支在本地仓库中创建一个远程分支(比如 o/main)。然后再创建一个跟踪远程仓库中活动分支的本地分支,默认情况下这个本地分支会被命名为 main。\n这也解释了为什么会在克隆的时候会看到下面的输出:</p>\n<blockquote>\n<p>local branch "main" set to track remote branch "o/main"</p>\n</blockquote>\n<p>自己指定本地分支和远程分支的映射</p>\n<ol>\n<li>git checkout -b notmain origin/main</li>\n<li>git branch -u origin/main foo # foo不写就默认使用当前分支</li>\n</ol>\n<p>之后commit,push这个notmain分支到远程库,实际就会更新远程库的main分支。</p>\n",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "ICS-PA心得",
"url": "https://kigane.github.io/note/cs/ics-pa/",
"id": "https://kigane.github.io/note/cs/ics-pa/",
"content_html": "<h2 id=\"pa1\"> PA1</h2>\n<h3 id=\"nemu准备\"> NEMU准备</h3>\n<div><pre><code>nemu\n├── include # 存放全局使用的头文件\n│ ├── common<span>.</span>h # 公用的头文件\n│ ├── cpu\n│ │ ├── decode<span>.</span>h # 译码相关\n│ │ └── exec<span>.</span>h # 执行相关\n│ ├── debug<span>.</span>h # 一些方便调试用的宏\n│ ├── device # 设备相关\n│ ├── isa # <span>ISA</span>相关\n│ ├── isa<span>.</span>h # <span>ISA</span>相关\n│ ├── macro<span>.</span>h # 一些方便的宏定义\n│ ├── memory # 访问内存相关\n│ ├── monitor\n│ │ ├── log<span>.</span>h # 日志文件相关\n│ │ └── monitor<span>.</span>h\n│ └── rtl\n│ ├── pesudo<span>.</span>h # <span>RTL</span>伪指令\n│ └── rtl<span>.</span>h # <span>RTL</span>指令相关定义\n├── Makefile # 指示<span>NEMU</span>的编译和链接\n├── Makefile<span>.</span>git # git版本控制相关\n├── runall<span>.</span>sh # 一键测试脚本\n└── src # 源文件\n ├── device # 设备相关\n ├── engine\n │ └── interpreter # 解释器的实现\n ├── isa # <span>ISA</span>相关的实现\n │ ├── mips32\n │ ├── riscv32\n │ ├── riscv64\n │ └── x86\n ├── main<span>.</span>c # 你知道的<span>...</span>\n ├── memory\n │ └── paddr<span>.</span>c # 物理内存访问\n └── monitor\n ├── cpu<span>-</span>exec<span>.</span>c # 指令执行的主循环\n ├── debug # 简易调试器相关\n │ ├── expr<span>.</span>c # 表达式求值的实现\n │ ├── log<span>.</span>c # 日志文件相关\n │ ├── ui<span>.</span>c # 用户界面相关\n │ └── watchpoint<span>.</span>c # 监视点的实现\n └── monitor<span>.</span>c\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br><span>25</span><br><span>26</span><br><span>27</span><br><span>28</span><br><span>29</span><br><span>30</span><br><span>31</span><br><span>32</span><br><span>33</span><br><span>34</span><br><span>35</span><br><span>36</span><br><span>37</span><br><span>38</span><br><span>39</span><br><span>40</span><br><span>41</span><br></div></div><ul>\n<li>make ISA=$ISA run</li>\n<li>在cmd_c()函数中, 调用cpu_exec()的时候传入了参数-1,但参数类型是uint64_t,因此实际的值为<span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:0.8974em;vertical-align:-0.0833em;\"></span><span><span>2</span><span><span><span><span style=\"height:0.8141em;\"><span style=\"top:-3.063em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span><span>64</span></span></span></span></span></span></span></span></span><span style=\"margin-right:0.2222em;\"></span><span>−</span><span style=\"margin-right:0.2222em;\"></span></span><span><span style=\"height:0.6444em;\"></span><span>1</span></span></span></span>,即0xffffffffffffffff。</li>\n<li>三个对调试有用的宏(在nemu/include/debug.h中定义)\n<ul>\n<li>Log()是printf()的升级版, 专门用来输出调试信息, 同时还会输出使用Log()所在的源文件, 行号和函数. 当输出的调试信息过多的时候, 可以很方便地定位到代码中的相关位置</li>\n<li>Assert()是assert()的升级版, 当测试条件为假时, 在assertion fail之前可以输出一些信息</li>\n<li>panic()用于输出信息并结束程序, 相当于无条件的assertion fail</li>\n</ul>\n</li>\n<li>内存通过在nemu/src/memory/paddr.c中定义的大数组pmem来模拟. 在客户程序运行的过程中, 总是使用vaddr_read()和vaddr_write() (在nemu/include/memory/vaddr.h中定义)来访问模拟的内存. vaddr, paddr分别代表虚拟地址和物理地址.</li>\n<li>"单元"是指有独立含义的子串, 它们正式的称呼叫token.</li>\n<li>\\33 即 ESC。</li>\n<li>makefile中使用awk,因为makefile中<code>$</code>会被特殊对待,所以awk的program text中的<code>$</code>要用<code>$$</code>表示。注意awk的action要写在<code>{}</code>中。</li>\n<li>fopen文件路径,在nemu目录下make run时,当前目录就是nemu。</li>\n<li>超级经典错误,if (tokens[p].type = '*')。。。。少写个等号</li>\n<li>strncpy(dest, src, n) 如果n个字符中没有'\\0',dest中不会主动加。如果dest中原来字符数大于n,则复制后要使src==dest,需要手动添加'\\0'(dest[n] = '\\0')</li>\n</ul>\n<div><pre><code><span><</span>expr<span>></span> <span>:</span><span>:</span><span>=</span> <span><</span>number<span>></span> # 一个数是表达式\n <span>|</span> <span>\"(\"</span> <span><</span>expr<span>></span> <span>\")\"</span> # 在表达式两边加个括号也是表达式\n <span>|</span> <span><</span>expr<span>></span> <span>\"+\"</span> <span><</span>expr<span>></span> # 两个表达式相加也是表达式\n <span>|</span> <span><</span>expr<span>></span> <span>\"-\"</span> <span><</span>expr<span>></span> # 接下来你全懂了\n <span>|</span> <span><</span>expr<span>></span> <span>\"*\"</span> <span><</span>expr<span>></span>\n <span>|</span> <span><</span>expr<span>></span> <span>\"/\"</span> <span><</span>expr<span>></span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br></div></div><h3 id=\"如何调试\"> 如何调试</h3>\n<p>一些软件工程相关的概念:</p>\n<ul>\n<li>Fault: 实现错误的代码, 例如if (p = NULL)</li>\n<li>Error: 程序执行时不符合预期的状态, 例如p被错误地赋值成NULL</li>\n<li>Failure: 能直接观测到的错误, 例如程序触发了段错误</li>\n</ul>\n<p>调试其实就是从观测到的failure一步一步回溯寻找fault的过程, 找到了fault之后, 我们就很快知道应该如何修改错误的代码了. 但从上面的例子也可以看出, 调试之所以不容易, 恰恰是因为:</p>\n<ul>\n<li>fault不一定马上触发error</li>\n<li>触发了error也不一定马上转变成可观测的failure</li>\n<li>error会像滚雪球一般越积越多, 当我们观测到failure的时候, 其实已经距离fault非常遥远了</li>\n</ul>\n<p>理解了这些原因之后, 我们就可以制定相应的策略了:</p>\n<ul>\n<li>尽可能把fault转变成error. 这其实就是测试做的事情, 所以我们在上一节中加入了表达式生成器的内容, 来帮助大家进行测试, 后面的实验内容也会提供丰富的测试用例. 但并不是有了测试用例就能把所有fault都转变成error了, 因为这取决于测试的覆盖度. 要设计出一套全覆盖的测试并不是一件简单的事情, 越是复杂的系统, 全覆盖的测试就越难设计. 但是, 如何提高测试的覆盖度, 是学术界一直以来都在关注的问题.</li>\n<li>尽早观测到error的存在. 观测到error的时机直接决定了调试的难度: 如果等到触发failure的时候才发现error的存在, 调试就会比较困难; 但如果能在error刚刚触发的时候就观测到它, 调试难度也就大大降低了. 事实上, 你已经见识过一些有用的工具了:\n<ul>\n<li>-Wall, -Werror: 在编译时刻把潜在的fault直接转变成failure. 这种工具的作用很有限, 只能寻找一些在编译时刻也觉得可疑的fault, 例如if (p = NULL). 不过随着编译器版本的增强, 编译器也能发现代码中的一些未定义行为. 这些都是免费的午餐, 不吃就真的白白浪费了.</li>\n<li>assert(): 在运行时刻把error直接转变成failure. assert()是一个很简单却又非常强大的工具, 只要在代码中定义好程序应该满足的特征, 就一定能在运行时刻将不满足这些特征的error拦截下来. 例如链表的实现, 我们只需要在代码中插入一些很简单的assert()(例如指针解引用时不为空), 就能够几乎告别段错误. 但是, 编写这些assert()其实需要我们对程序的行为有一定的了解, 同时在程序特征不易表达的时候, assert()的作用也较为有限.</li>\n<li>printf(): 通过输出的方式观察潜在的error. 这是用于回溯fault时最常用的工具, 用于观测程序中的变量是否进入了错误的状态. 在NEMU中我们提供了输出更多调试信息的宏Log(), 它实际上封装了printf()的功能. 但由于printf()需要根据输出的结果人工判断是否正确, 在便利程度上相对于assert()的自动判断就逊色了不少.</li>\n<li>GDB: 随时随地观测程序的任何状态. 调试器是最强大的工具, 但你需要在程序行为的茫茫大海中观测那些可疑的状态, 因此使用起来的代价也是最大的.</li>\n</ul>\n</li>\n</ul>\n<p>建议:</p>\n<ul>\n<li>总是使用-Wall和-Werror</li>\n<li>尽可能多地在代码中插入assert()</li>\n<li>assert()无法捕捉到error时, 通过printf()输出可疑的变量, 期望能观测到error</li>\n<li>printf()不易观测error时, 通过GDB理解程序的精确行为</li>\n</ul>\n<h3 id=\"一条指令在nemu中的执行过程\"> 一条指令在NEMU中的执行过程</h3>\n<p>事实上, 一个字节最多只能区分256种不同的指令形式. 当指令形式的数目大于256时, 我们需要使用另外的方法来识别它们. x86中有主要有两种方法来解决这个问题(在PA2中你都会遇到这两种情况):</p>\n<ul>\n<li>一种方法是使用转义码(escape code), x86中有一个2字节转义码 0x0f, 当指令opcode的第一个字节是0x0f时, 表示需要再读入一个字节才能决定具体的指令形式(部分条件跳转指令就属于这种情况). 后来随着各种SSE指令集的加入, 使用2字节转义码也不足以表示所有的指令形式了, x86在2字节转义码的基础上又引入了3字节转义码, 当指令opcode的前两个字节是0x0f和0x38时, 表示需要再读入一个字节才能决定具体的指令形式.</li>\n<li>另一种方法是使用ModR/M字节中的扩展opcode域来对opcode的长度进行扩充. 有些时候, 读入一个字节也还不能完全确定具体的指令形式, 这时候需要读入紧跟在opcode后面的ModR/M字节, 把其中的reg/opcode域当做opcode的一部分来解释, 才能决定具体的指令形式. x86把这些指令划分成不同的指令组(instruction group), 在同一个指令组中的指令需要通过ModR/M字节中的扩展opcode域来区分.</li>\n</ul>\n<p>最一般的寻址格式是<br>\ndisplacement(R[base_reg], R[index_reg], scale_factor)<br>\n相应内存地址的计算方式为<br>\naddr = R[base_reg] + R[index_reg] * scale_factor + displacement<br>\n其它寻址格式都可以看作这种一般格式的特例</p>\n<p>在NEMU中, RTL寄存器只有以下这些</p>\n<ul>\n<li>不同ISA的通用寄存器(在nemu/include/isa/$ISA.h中定义)</li>\n<li>id_src, id_src2和id_dest中的操作数内容val(在nemu/include/cpu/decode.h中定义).</li>\n<li>临时寄存器s0, s1, s2和t0(在nemu/include/rtl/rtl.h中定义)</li>\n<li>零寄存器rz(在nemu/src/monitor/cpu-exec.c中定义), 它的值总是0</li>\n<li>x86的ISA相关译码信息中的内存基地址mbr</li>\n</ul>\n<p>RTL基本指令包括(我们使用了一些简单的正则表达式记号):</p>\n<ul>\n<li>寄存器-寄存器类型和寄存器-立即数类型的基本算术/逻辑运算, 包括rtl_(add|sub|and|or|xor|shl|shr|sar|setrelop)i?, 它们的定义用到了nemu/src/engine/interpreter/c_op.h中的C语言运算和interpret_relop()函数</li>\n<li>寄存器-寄存器类型的乘除法运算, 包括rtl_i?(mul_[lo|hi]|div_[q|r]),</li>\n<li>被除数为64位的除法运算rtl_i?div64_[q|r]</li>\n<li>guest内存访问rtl_lm, rtl_lms和rtl_sm</li>\n<li>host内存访问rtl_host_lm和rtl_host_sm</li>\n<li>跳转, 包括直接跳转rtl_j, 间接跳转rtl_jr和条件跳转rtl_jrelop</li>\n<li>终止程序rtl_exit(在nemu/src/monitor/cpu-exec.c中定义)</li>\n</ul>\n<p>小型调用约定:</p>\n<ul>\n<li>实现RTL伪指令的时候, 尽可能不使用dest之外的寄存器存放中间结果. 由于dest最后会被写入新值, 其旧值肯定要被覆盖, 自然也可以安全地作为RTL伪指令的临时寄存器.</li>\n<li>实在需要使用临时寄存器的时候, 按照以下约定来使用:</li>\n<li>t0, t1, ... - 只能在RTL伪指令的实现过程中存放中间结果</li>\n<li>s0, s1, ... - 只能在译码辅助函数和执行辅助函数的实现过程中存放中间结果</li>\n</ul>\n<p>RTL寄存器的生存期</p>\n<p>任何指令都可以分解为以下4中操作</p>\n<ul>\n<li>读取某一主存单元的内容,并将其装入某个寄存器(取指, 取数)</li>\n<li>把一个数据从某个寄存器存入给定的主存单元中(存结果)</li>\n<li>把一个数据从某寄存器送到另一寄存器或者ALU(取数,存结果)</li>\n<li>进行算术或逻辑运算(PC+”1”,计算地址,运算)</li>\n</ul>\n",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "riscv",
"url": "https://kigane.github.io/note/cs/isa-riscv/",
"id": "https://kigane.github.io/note/cs/isa-riscv/",
"content_html": "",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "i386",
"url": "https://kigane.github.io/note/cs/isa-i386/",
"id": "https://kigane.github.io/note/cs/isa-i386/",
"content_html": "<h2 id=\"基本编程模型\"> 基本编程模型</h2>\n<h3 id=\"内存组织和分段-segmentation\"> 内存组织和分段(Segmentation)</h3>\n<p>物理内存由8-bit的字节组成的序列构成。每个字节有一个唯一的地址,范围<span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:1.0641em;vertical-align:-0.25em;\"></span><span>[</span><span>0</span><span>,</span><span style=\"margin-right:0.1667em;\"></span><span><span>2</span><span><span><span><span style=\"height:0.8141em;\"><span style=\"top:-3.063em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span><span>32</span></span></span></span></span></span></span></span></span><span style=\"margin-right:0.2222em;\"></span><span>−</span><span style=\"margin-right:0.2222em;\"></span></span><span><span style=\"height:1em;vertical-align:-0.25em;\"></span><span>1</span><span>]</span></span></span></span>,共4GB。</p>\n<p>内存组织模型有两种</p>\n<ul>\n<li>flat address space 由大小为4GB的单个数组组成</li>\n<li>segmented address space 由16383(<span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:0.8974em;vertical-align:-0.0833em;\"></span><span><span>2</span><span><span><span><span style=\"height:0.8141em;\"><span style=\"top:-3.063em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span><span>14</span></span></span></span></span></span></span></span></span><span style=\"margin-right:0.2222em;\"></span><span>−</span><span style=\"margin-right:0.2222em;\"></span></span><span><span style=\"height:0.6444em;\"></span><span>1</span></span></span></span>)个线性地址空间集合组成,每个地址空间最多4GB。\n<ul>\n<li>内存空间最大为64TB(<span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:0.8141em;\"></span><span><span>2</span><span><span><span><span style=\"height:0.8141em;\"><span style=\"top:-3.063em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span><span>46</span></span></span></span></span></span></span></span></span></span></span></span>B)。称为逻辑地址空间</li>\n<li>可以看作最多16383个一维子空间的集合,每个子空间就是一个Segment。子空间的地址是连续的。</li>\n<li>逻辑地址空间的指针由两个部分组成\n<ul>\n<li>16bits的segment selector,用于确定Segment</li>\n<li>32bits的offset,用于确定Segment内的字节。</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n<p>详见第5章。</p>\n<h3 id=\"数据类型\"> 数据类型</h3>\n<p>操作数(Operand)的基础数据类型</p>\n<ul>\n<li>Byte 8bits</li>\n<li>Word 16bits</li>\n<li>DoubleWord 32bits</li>\n</ul>\n<p>注意:Word的地址不必对齐到偶数地址,DoubleWord的地址不必对齐到能被4整除的地址。这样数据结构的弹性最大,内存利用率最高。但使用32-bits的总线传输效率会较低。为了更好的传输性能,数据结构应该要设计为尽量对齐。</p>\n<hr>\n<p>操作数(Operand)的基础数据类型只有三种,但根据使用指令的不同,可以识别出额外的基础数据类型</p>\n<ul>\n<li>Integer: 用二进制补码表示。最高有效位(MSB)为符号位。</li>\n<li>Ordinal: 用二进制原码表示。无符号位。</li>\n<li>Near Pointer: 32-bit 逻辑地址。用于flat model或Segment内部。</li>\n<li>Far Pointer: 48-bit 逻辑地址。仅用于segmented model。</li>\n<li>String: Byte,Word,DoubleWord的连续序列。</li>\n<li>Bit field: bit的连续序列。最大32位。起始bit任意。</li>\n<li>Bit string: bit的连续序列。起始bit任意。最大<span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:0.8974em;vertical-align:-0.0833em;\"></span><span><span>2</span><span><span><span><span style=\"height:0.8141em;\"><span style=\"top:-3.063em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span><span>32</span></span></span></span></span></span></span></span></span><span style=\"margin-right:0.2222em;\"></span><span>−</span><span style=\"margin-right:0.2222em;\"></span></span><span><span style=\"height:0.6444em;\"></span><span>1</span></span></span></span>bits。</li>\n<li>BCD: 每个字节表示一个十进制数字,0-3这4个bit表示一个数字(只有0-9有意义)。4-7个4个bit在乘除中必须为0,在加减中可以是任意值。</li>\n<li>Packed BCD:每个字节表示两个十进制数字。每半个字节表示一个数字。</li>\n</ul>\n<h2 id=\"寄存器\"> 寄存器</h2>\n<p>80386共有16个寄存器,可分为3类</p>\n<ul>\n<li>通用寄存器(General registers) 8个32-bit寄存器,主要用于保存操作数</li>\n<li>段寄存器(Segment registers) 这6个寄存器决定了在任一时刻,那个segment是可寻址的。</li>\n<li>状态和指令寄存器(Status and instruction registers) 用于记录和改变处理器的状态。</li>\n</ul>\n<h3 id=\"通用寄存器\"> 通用寄存器</h3>\n<p><img src=\"/assets/img/general-registers.png\" alt=\"通用寄存器\" /></p>\n<p>其中</p>\n<ul>\n<li>EAX, EDX, ECX, EBX, EBP, ESI, EDI, ESP是32位寄存器;</li>\n<li>AX, DX, CX, BX, BP, SI, DI, SP是16位寄存器;</li>\n<li>AL, DL, CL, BL, AH, DH, CH, BH是8位寄存器.</li>\n</ul>\n<p>某些指令会使用特定的寄存器,如:双精度浮点乘除运算,I/O,字符串指令,循环,栈操作等等。</p>\n<h3 id=\"段寄存器\"> 段寄存器</h3>\n<p><img src=\"/assets/img/other-registers.png\" alt=\"其他寄存器\" />\n完整的程序通常由许多不同的模块组成,每个模块包括指令和数据。 但是,在程序执行期间的任一时刻,实际使用到的仅为程序模块的小子集。 80386架构通过提供直接访问当前模块环境的指令和数据的机制来利用这一点,必要时可以访问额外的段。</p>\n<p>执行中的程序可以在任一时刻立即访问六个内存段。这六个内存段分别用寄存器CS,SS,DS,ES,FS,GS标识,每个段寄存器唯一确定一个特定的程序段,并可以高速访问。</p>\n<ul>\n<li>CS(Code Segment) 对应保存当前执行的指令序列所在的段。CS的值只能通过控制转移指令(e.g JMP,CALL),中断或异常隐式改变。</li>\n<li>SS(Stack Segment) 子程序调用,参数,过程激活记录通常会在栈上分配内存。所有的栈操作都会使用SS来定位栈。不像CS, SS寄存器可以显式加载,因此程序员可以动态定义栈。</li>\n<li>DS,ES,FS和GS寄存器指定四个数据段,每个数据段都可由当前执行的程序寻址。对四个单独的数据区域的可访问性有助于程序有效地访问不同类型的数据结构。有些程序可能需要访问不止4个数据段,此时需要在程序执行期间改变这4个寄存器的值。这需要程序在访问特定数据时,需要先加载正确的段寄存器。</li>\n</ul>\n<p>处理器将基址与由段寄存器选择的段地址相关联。即先通过段寄存器找到相应的段,再用32-bit基址在段内寻址。</p>\n<h4 id=\"栈的实现\"> 栈的实现</h4>\n<p>栈操作相关寄存器</p>\n<ul>\n<li>SS 一个系统可以有多个栈,每个栈大小最多可达4GB。由SS直接寻址的栈,通常被称为“当前”栈。SS自动用于所有栈操作,由处理器处理。</li>\n<li>ESP ESP始终指向栈的栈顶(栈是从高地址向低地址扩展的,所以栈顶的地址最小)。ESP被PUSH,POP,子程序调用和返回,中断隐式引用。</li>\n<li>EBP EBP指向当前过程确定的栈帧的基址(栈帧的最高地址,不会随栈的变换而改变)。因此适用于访问栈中的元素。</li>\n</ul>\n<h4 id=\"标志寄存器-eflags\"> 标志寄存器--EFLAGS</h4>\n<p><img src=\"/assets/img/EFLAGS.png\" alt=\"EFLAGS\" />\n标志可以分为3组</p>\n<ul>\n<li>Status flags:用于让某个指令的结果影响下一个指令。算数指令使用前6个\n<ul>\n<li>CF(Carry Flag): 有进位或借位发生是设置,否则清除。</li>\n<li>PF(Parity Flag): 如果结果的低8bit有偶数个1,则设置,否则清除。</li>\n<li>AF(Adjust Flag): 用于BCD数据运算。如果AL的低4位有进位或借位则设置,否则清除。</li>\n<li>ZF(Zoro Flag): 如果结果为0则设置,否则清除。</li>\n<li>SF(Sign Flag): 结果的最高有效位。</li>\n<li>OF(Overflow Flag): 溢出标志。(<span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:0.8333em;vertical-align:-0.15em;\"></span><span><span style=\"margin-right:0.07153em;\">C</span><span><span><span><span style=\"height:0.1514em;\"><span style=\"top:-2.55em;margin-left:-0.0715em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span>n</span></span></span></span><span></span></span><span><span style=\"height:0.15em;\"><span></span></span></span></span></span></span><span style=\"margin-right:0.2222em;\"></span><span>⊕</span><span style=\"margin-right:0.2222em;\"></span></span><span><span style=\"height:0.8917em;vertical-align:-0.2083em;\"></span><span><span style=\"margin-right:0.07153em;\">C</span><span><span><span><span style=\"height:0.3011em;\"><span style=\"top:-2.55em;margin-left:-0.0715em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span><span>n</span><span>−</span><span>1</span></span></span></span></span><span></span></span><span><span style=\"height:0.2083em;\"><span></span></span></span></span></span></span><span>,</span><span style=\"margin-right:0.1667em;\"></span><span><span style=\"margin-right:0.07153em;\">C</span><span><span><span><span style=\"height:0.1514em;\"><span style=\"top:-2.55em;margin-left:-0.0715em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span>n</span></span></span></span><span></span></span><span><span style=\"height:0.15em;\"><span></span></span></span></span></span></span></span></span></span>是最高位是否溢出,<span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:0.8917em;vertical-align:-0.2083em;\"></span><span><span style=\"margin-right:0.07153em;\">C</span><span><span><span><span style=\"height:0.3011em;\"><span style=\"top:-2.55em;margin-left:-0.0715em;margin-right:0.05em;\"><span style=\"height:2.7em;\"></span><span><span><span>n</span><span>−</span><span>1</span></span></span></span></span><span></span></span><span><span style=\"height:0.2083em;\"><span></span></span></span></span></span></span></span></span></span>是次高位是否溢出)</li>\n<li>TF(Trap Flag): 陷阱标志。Debug时用到。</li>\n</ul>\n</li>\n<li>Control flags:\n<ul>\n<li>DF(Direction Flag): 当字符串指令自减时设置(即从高地址向低地址处理字符串时),反之清除。</li>\n</ul>\n</li>\n<li>System flags:</li>\n</ul>\n<hr>\n<h4 id=\"指令指针\"> 指令指针</h4>\n<p>程序计数器(PC) -- EIP (Extended Instruction Pointer) 指向要执行的指令</p>\n<h3 id=\"指令格式\"> 指令格式</h3>\n<p>指令=操作+操作数类型&操作数位置</p>\n<p><strong>指令中常见元素</strong></p>\n<ul>\n<li>Prefix 在指令之前的1-2个字节,修改指令的操作。\n<ul>\n<li>Segment override 显式指定指定应该使用的段寄存器,覆盖默认的。</li>\n<li>Address size 产生16或32位地址</li>\n<li>Operand size 产生16或32位操作数</li>\n<li>Repeat 字符串指令使用该前缀,让该指令作用于string的每个元素。</li>\n</ul>\n</li>\n<li>Opcode 操作码。确定指令的操作</li>\n<li>Register specifier 一条指令可以指定1-2个寄存器操作数。寄存器指示符要么和Opcode在一个字节(+rb,+rw...),要么和Address mode在一个字节。</li>\n<li>Addressing-mode specifier 决定操作数是寄存器还是内存位置。如果是内存位置,决定是否使用位移,基址寄存器,索引寄存器和缩放因子。</li>\n<li>SIB byte (scale, index, base) 当addressing-mode specifier暗示要使用索引寄存器来计算操作数的地址时,指令中会包含一个SIB字节来编码基址寄存器,索引寄存器和缩放因子。</li>\n<li>Displacement 当addressing-mode specifier暗示要使用位移来计算操作数的地址时,位移会编码在指令中。位移是一个<strong>有符号整数</strong>,通常8-bit就足够,按需要可以扩展到16-bit,32-bit。</li>\n<li>Immediate operand 立即数,就是指令的直接操作数。可以是8,16,32位。为了以防万一有8bit操作数和32/16-bit操作数合并的操作,通常会自动扩展。</li>\n</ul>\n<h3 id=\"操作数选择-operand-selection\"> 操作数选择 Operand Selection</h3>\n<p>操作数是指令操纵的数据,一个指令可以有0或多个操作数。操作数可以位于</p>\n<ul>\n<li>指令本身(立即数)</li>\n<li>寄存器</li>\n<li>内存</li>\n<li>I/O端口</li>\n</ul>\n<p>对于有操作数的指令,操作数可以是隐式的,显式的,或者既有隐式操作数又有显式操作数。例如</p>\n<ul>\n<li>AAM(Ascii Adjust for mutiplication) 作用于AX寄存器。隐式,</li>\n<li>XCHG EAX,EBX 显式</li>\n<li>PUSH COUNTER 显式+隐式 内存变量COUNTER拷贝到栈中</li>\n</ul>\n<hr>\n<p><strong>大多数指令有隐式操作数。所有的算数操作数都会更新EFLAGS。</strong></p>\n<hr>\n<p>指令可以显示引用1-2个操作数。有两个操作数的指令,其结果一定会覆盖其中一个操作数,称为目标操作数(dest),另一个则称源操作数(src)。有两个显式操作数的指令,一个操作数为内存(M)或寄存器(R),另一个操作数一定是寄存器或立即数(Immediate)允许的操作有</p>\n<ul>\n<li>R-to-R</li>\n<li>R-to-M</li>\n<li>M-to-R</li>\n<li>I-to-R</li>\n<li>I-to-M</li>\n</ul>\n<p>M-M模式在字符串处理指令中存在,但两个操作数都是隐式表示的。Push,Pop也有点特殊。</p>\n<p>I: 指令中的有符号数字字面量\nR: 对应各个寄存器</p>\n<p>Memory Operand<br>\n数据操纵指令在处理内存操作数时,必须指明包含该操作数的段(segment)和操作数在段中的位置(offset)。然而,为了速度和指令编码的简练,将段选择器(segment selector)放在高速的段寄存器中。因此数据操纵指令只需要指明offset即可。<br>\n内存寻址</p>\n<ul>\n<li>大多数数据操纵指令访问内存时都显式包含一个指示寻址方法的字节modR/M字节。通常跟在opcode后。如果操作数为内存地址,则最终地址LA = SR + (B,I,s,D的各种组合),其中(B:基址寄存器,I:索引寄存器,s:缩放因子,D:位移)。当用到索引寄存器的时候,modR/M字节后会跟着一个字节,指示使用的索引寄存器和缩放因子。</li>\n<li>某些特殊的寻址\n<ul>\n<li>LA = SR + D 某些MOV指令,隐式使用EAX,offset写在指令中。</li>\n<li>字符串操作\n<ul>\n<li>使用 DS:ESI 寻址,如MOVS, CMPS, OUTS, LODS, SCAS</li>\n<li>使用 ES:EDI 寻址,如MOVS, CMPS, INS, STOS</li>\n</ul>\n</li>\n<li>栈操作,使用 SS:ESP 寻址。 如PUSH, POP, PUSHA, PUSHAD, POPA, POPAD, PUSHF, PUSHFD, POPF, POPFD, CALL, RET, IRET, IRETD以及异常和中断。</li>\n</ul>\n</li>\n</ul>\n<h4 id=\"默认的段寄存器选择规则\"> 默认的段寄存器选择规则</h4>\n<table>\n<thead>\n<tr>\n<th>内存引用</th>\n<th>段寄存器</th>\n<th>规则</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>指令</td>\n<td>CS</td>\n<td>在预取(prefetch)指令时自动选择</td>\n</tr>\n<tr>\n<td>栈</td>\n<td>SS</td>\n<td>任何栈push,pop操作。以EBP/ESP为基址的寻址</td>\n</tr>\n<tr>\n<td>Local Data</td>\n<td>DS</td>\n<td>其他</td>\n</tr>\n<tr>\n<td>Dest Strings</td>\n<td>ES</td>\n<td>字符串指令的 dest</td>\n</tr>\n</tbody>\n</table>\n<h4 id=\"有效地址计算\"> 有效地址计算</h4>\n<p><img src=\"/assets/img/effective-address.png\" alt=\"有效地址计算\" /></p>\n<p>对以modR/M定义的内存操作数,在目标段内的有效地址由3个部分组成</p>\n<ul>\n<li>指令中的位移元素。因为是编码在指令中,所以适合地址的固定部分\n<ul>\n<li>简单标量操作数的位置</li>\n<li>静态(static)分配的数组的开头</li>\n<li>record中某个item的offset</li>\n</ul>\n</li>\n<li>基址寄存器</li>\n<li>变址寄存器(可能会乘以缩放因子2, 4, 或 8,上图错了)\n<ul>\n<li>基址+变址适合动态决定的地址</li>\n<li>栈中的局部变量和过程参数</li>\n<li>records[i]</li>\n<li>二维数组中的一维数组的起始</li>\n<li>动态分配的数组地址</li>\n</ul>\n</li>\n<li>注意,ESP不能作为变址寄存器。EBP,ESP作为基址寄存器时,默认的段寄存器为SS。</li>\n<li>当数组元素大小为2,4,8时,由于scaling的存在,索引比较高效(不用分开移动或多次使用指令)。</li>\n</ul>\n<p>常用的寻址组合,段寄存器省略</p>\n<ul>\n<li>A = D 位移本身就是地址。通常用于static分配内存的操作数。</li>\n<li>A = B 寄存器中的值就是地址。某些变量。</li>\n<li>A = B + D\n<ul>\n<li>当索引的static array元素大小不是2,4,8B时。D为目标元素的offset。</li>\n<li>访问records数组中的某个item。B为某个record,D为item的offset</li>\n</ul>\n</li>\n<li>A = Ixs + D 当索引的static array元素大小是2,4,8B时。D为数组首地址。</li>\n<li>A = B + I + D\n<ul>\n<li>二维数组,D为m[0][0],</li>\n<li>访问records数组中的某个item。B为records[0],D为item的offset</li>\n</ul>\n</li>\n<li>A = B + Ixs+ D 最一般的寻址格式\n<ul>\n<li>在汇编代码中写作 displacement(R[base_reg], R[index_reg], scale_factor)</li>\n<li>其它寻址格式都可以看作这种一般格式的特例</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"中断和异常-interrupts-and-exceptions\"> 中断和异常(Interrupts and Exceptions)</h3>\n<p>80386有两个机制来打断程序执行</p>\n<ul>\n<li>异常:是对在程序执行期间CPU检测到的特定条件作出回应的同步事件(synchronous event)。</li>\n<li>中断:是外部设备需要注意时触发的异步事件(asynchronous event)</li>\n</ul>\n<p>异常和中断很类似,都会引起处理器暂时暂停(suspend)当前程序的执行,并执行另一个优先级更高的程序。它们的主要区别在于引发原因。异常总是可重现的,只要以同样的程序和数据再次执行。而中断通常独立于当前执行的程序。</p>\n<p>中断详见第9章。应用程序员通常不处理中断,只处理异常。系统程序员需要定义应用程序和异常机制之间的接口,并提供给应用程序员。</p>\n<p>//TODO</p>\n<p>Reserved Exceptions and Interrupts</p>\n<table>\n<thead>\n<tr>\n<th>Vector Number</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>Divide Error</td>\n</tr>\n<tr>\n<td>1</td>\n<td>Debug Exceptions</td>\n</tr>\n<tr>\n<td>2</td>\n<td>NMI Interrupt</td>\n</tr>\n<tr>\n<td>3</td>\n<td>Breakpoint</td>\n</tr>\n<tr>\n<td>4</td>\n<td>INTO Detected Overflow</td>\n</tr>\n<tr>\n<td>5</td>\n<td>BOUND Range Exceeded</td>\n</tr>\n<tr>\n<td>6</td>\n<td>Invalid Opcode</td>\n</tr>\n<tr>\n<td>7</td>\n<td>Coprocessor Not Available</td>\n</tr>\n<tr>\n<td>8</td>\n<td>Double Exception</td>\n</tr>\n<tr>\n<td>9</td>\n<td>Coprocessor Segment Overrun</td>\n</tr>\n<tr>\n<td>10</td>\n<td>Invalid Task State Segment</td>\n</tr>\n<tr>\n<td>11</td>\n<td>Segment Not Present</td>\n</tr>\n<tr>\n<td>12</td>\n<td>Stack Fault</td>\n</tr>\n<tr>\n<td>13</td>\n<td>General Protection</td>\n</tr>\n<tr>\n<td>14</td>\n<td>Page Fault</td>\n</tr>\n<tr>\n<td>15</td>\n<td>(reserved)</td>\n</tr>\n<tr>\n<td>16</td>\n<td>Coprocessor Error</td>\n</tr>\n<tr>\n<td>17-32</td>\n<td>(reserved)</td>\n</tr>\n</tbody>\n</table>\n<h2 id=\"应用指令集\"> 应用指令集</h2>\n<h3 id=\"数据移动指令\"> 数据移动指令</h3>\n<h4 id=\"通用数据移动指令\"> 通用数据移动指令</h4>\n<ul>\n<li>MOV (move)</li>\n<li>XCHG (exchange) 交换两个操作数。不需要临时变量,可代替3条MOV指令。在实现semaphores(信号量)及类似的进程同步数据结构时很有用。如果有一个操作数是M,则XCHG自动激活LOCK信号。</li>\n</ul>\n<h4 id=\"栈操作指令\"> 栈操作指令</h4>\n<ul>\n<li>PUSH 降低ESP,再将源操作数放到ESP指的位置。通常用于过程调用时保存参数或在栈上保存临时变量。</li>\n<li>PUSHA (Push All Registers) 将八个通用寄存器的值存到栈中。顺序为EAX,EDX,ECX,EBX,OLD ESP,EBP,ESI,EDI。可以简化过程调用时保存寄存器的值操作。</li>\n<li>POP 将栈中保存的值放到目标操作数,再提高ESP。</li>\n<li>POPA 将栈中的个通用寄存器的值取出,并用这些值重设寄存器。但忽略OLD ESP。</li>\n</ul>\n<h4 id=\"类型转换指令\"> 类型转换指令</h4>\n<ul>\n<li>CWD (Convert Word to Doubleword)</li>\n<li>CDQ (Convert Doubleword to Quad-Word)</li>\n<li>CBW (Convert Byte to Word)</li>\n<li>CWDE (Convert Word to Doubleword Extended)</li>\n<li>只能操作EAX中的数据。可用于被除之前被除数的扩展。都是有符号扩展。</li>\n</ul>\n<hr>\n<ul>\n<li>MOVSX (Move with Sign Extension)</li>\n<li>MOVZX (Move with Zero Extension)</li>\n<li>可使用任何寄存器。另一个操作可以为M或R。</li>\n</ul>\n<h3 id=\"二元算术指令\"> 二元算术指令</h3>\n<p>会影响CF,SF,OF,ZF四个flag。</p>\n<h4 id=\"加减指令\"> 加减指令</h4>\n<ul>\n<li>ADD (Add Integers) 如果溢出,设置CF=1</li>\n<li>ADC (Add Integers with Carry) 如果CF=1,再+1。否则同ADD</li>\n<li>INC (Increment) +1。不影响CF</li>\n<li>SUB (Subtract Integers) 如果借位(borrow),设置CF=1</li>\n<li>SBB (Subtract Integers with Borrow) 如果CF=1,再-1。否则同SUB</li>\n<li>DEC (Decrement) -1。不影响CF</li>\n</ul>\n<h4 id=\"比较和取负指令\"> 比较和取负指令</h4>\n<ul>\n<li>CMP (Compare) 用dest-src,更新OF, SF, ZF, AF, PF, 和 CF 标志,但不会改变dest和src的值。</li>\n<li>NEG (Negate) 0-dest</li>\n</ul>\n<h4 id=\"乘法指令\"> 乘法指令</h4>\n<ul>\n<li>MUL (Unsigned Integer Multiply) 源操作数和累加器(EAX/AX/AL)相乘,结果大小为操作数的两倍。如果结果的上半部分不为0,则将CF,OF设为1,否则设为0。</li>\n<li>IMUL (Signed Integer Multiply)\n<ul>\n<li>单操作数 和MUL一样,另一个乘数隐式为EAX/AX/AL。取决于操作数的大小。</li>\n<li>双操作数 其中一个操作数一定在通用寄存器中,结果也放在通用寄存器中。</li>\n<li>三操作数 一个必定是有符号立即数,一个在寄存器或内存中,剩下一个通常是通用寄存器,存储乘积。</li>\n<li>结果大小为操作数的两倍。如果结果的上半部分不为0,则将CF,OF设为1,否则设为0。</li>\n<li>双,三操作数的形式结果会被截断(truncate)到源操作数的长度。也因此兼容无符号数的乘,因为乘积的低半部分是相同的。</li>\n</ul>\n</li>\n</ul>\n<h4 id=\"除法指令\"> 除法指令</h4>\n<p>如果除数为0或商超过EAX/AX/AL的表示范围,则触发一个异常(interrupt zero)<br>\nDIV (Unsigned Integer Divide) 被除数隐式存储在EAX中。商不为整数时向0舍入。\nIDIV (Signed Integer Divide) 同DIV。余数的符号同被除数。</p>\n<table>\n<thead>\n<tr>\n<th>Divisor(SRC 除数)</th>\n<th>Dividend(被除数)</th>\n<th>Quotient(商)</th>\n<th>Remainder (余数)</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Byte</td>\n<td>AX</td>\n<td>AL</td>\n<td>AH</td>\n</tr>\n<tr>\n<td>Word</td>\n<td>DX:AX</td>\n<td>AX</td>\n<td>DX</td>\n</tr>\n<tr>\n<td>Doubleword</td>\n<td>EDX:EAX</td>\n<td>EAX</td>\n<td>EDX</td>\n</tr>\n</tbody>\n</table>\n<h3 id=\"十进制算数指令\"> 十进制算数指令</h3>\n<h4 id=\"packed-bcd指令\"> packed BCD指令</h4>\n<ul>\n<li>DAA (Decimal Adjust after Addition)</li>\n<li>DAS (Decimal Adjust after Subtraction)</li>\n</ul>\n<h4 id=\"unpacked-bcd指令\"> unpacked BCD指令</h4>\n<ul>\n<li>AAA (ASCII Adjust after Addition)</li>\n<li>AAS (ASCII Adjust after Subtraction)</li>\n<li>AAM (ASCII Adjust after Multiplication)</li>\n<li>AAD (ASCII Adjust before Division)</li>\n</ul>\n<h3 id=\"逻辑指令\"> 逻辑指令</h3>\n<h4 id=\"布尔操作指令\"> 布尔操作指令</h4>\n<ul>\n<li>NOT 对一个R或M中的操作数取反码。不影响flags。</li>\n<li>AND/OR/XOR 操作数可以是R-R,R-M,I-R/M。会将OF,CF置零,AF的行为未定义,更新SF,ZF,PF。</li>\n</ul>\n<h4 id=\"位测试和修改指令\"> 位测试和修改指令</h4>\n<p>首先将选中的bit的值符给CF,再将新值放到该bit上。</p>\n<table>\n<thead>\n<tr>\n<th>Instruction</th>\n<th>Effect on CF</th>\n<th>Effect on Selected Bit</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Bit (Bit Test)</td>\n<td>CF ← BIT</td>\n<td>(none)</td>\n</tr>\n<tr>\n<td>BTS (Bit Test and Set)</td>\n<td>CF ← BIT</td>\n<td>BIT ← 1</td>\n</tr>\n<tr>\n<td>BTR (Bit Test and Reset)</td>\n<td>CF ← BIT</td>\n<td>BIT ← 0</td>\n</tr>\n<tr>\n<td>BTC (Bit Test and Complement)</td>\n<td>CF ← BIT</td>\n<td>BIT ← NOT(BIT)</td>\n</tr>\n</tbody>\n</table>\n<h4 id=\"位扫描指令\"> 位扫描指令</h4>\n<ul>\n<li>BSF (Bit Scan Forward) 从0-7/15/31扫描。保存第一个set bit(即1,unset bit就是0)的索引位置。如果全为0,则set ZF。反之清除。另外,如果被扫描的操作数全为0,则结果未定义。</li>\n<li>BSR (Bit Scan Reverse) 反过来扫描。</li>\n</ul>\n<h4 id=\"shift-rotate指令\"> shift&rotate指令</h4>\n<h3 id=\"控制转移指令\"> 控制转移指令</h3>\n<h4 id=\"无条件转移指令\"> 无条件转移指令</h4>\n<ul>\n<li>JMP (Jump)\n<ul>\n<li>直接跳转 操作数写在指令中,且为有符号的立即数,表示相对位移(单位为Byte,Word,或DoubleWord)。计算的结果更新EIP。</li>\n<li>间接跳转 通过R或M指定应该执行的下一条指令的绝对地址。</li>\n</ul>\n</li>\n<li>CALL 激活另一个过程(其地址可以在某个通用寄存器中,也可以是编码在指令中的内存地址),在栈中保存CALL指令的下一条指令的地址(即当前EIP的值),供RET使用。</li>\n<li>RET (Return From Procedure) 从栈中恢复CALL保存的EIP的值。</li>\n<li>IRET (Return From Interrupt) 将控制返回到被中断过程。在恢复EIP之外,还会恢复中断机制存储在栈中的flags。</li>\n</ul>\n<h4 id=\"条件转移指令\"> 条件转移指令</h4>\n<p>Unsigned Conditional Transfers</p>\n<table>\n<thead>\n<tr>\n<th>Mnemonic</th>\n<th>Condition Tested</th>\n<th>"Jump If..."</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>JA/JNBE</td>\n<td>(CF or ZF) = 0</td>\n<td>above/not below nor equal</td>\n</tr>\n<tr>\n<td>JAE/JNB</td>\n<td>CF = 0 above or</td>\n<td>equal/not below</td>\n</tr>\n<tr>\n<td>JB/JNAE</td>\n<td>CF = 1 below/not</td>\n<td>above nor equal</td>\n</tr>\n<tr>\n<td>JBE/JNA</td>\n<td>(CF or ZF) = 1</td>\n<td>below or equal/not above</td>\n</tr>\n<tr>\n<td>JC</td>\n<td>CF = 1</td>\n<td>carry</td>\n</tr>\n<tr>\n<td>JE/JZ</td>\n<td>ZF = 1</td>\n<td>equal/zero</td>\n</tr>\n<tr>\n<td>JNC</td>\n<td>CF = 0</td>\n<td>not carry</td>\n</tr>\n<tr>\n<td>JNE/JNZ</td>\n<td>ZF = 0</td>\n<td>not equal/not zero</td>\n</tr>\n<tr>\n<td>JNP/JPO</td>\n<td>PF = 0</td>\n<td>not parity/parity odd</td>\n</tr>\n<tr>\n<td>JP/JPE</td>\n<td>PF = 1</td>\n<td>parity/parity even</td>\n</tr>\n</tbody>\n</table>\n<hr>\n<p>Signed Conditional Transfers</p>\n<table>\n<thead>\n<tr>\n<th>Mnemonic</th>\n<th>Condition Tested</th>\n<th>"Jump If..."</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>JG/JNLE</td>\n<td>((SF xor OF) or ZF) = 0</td>\n<td>greater/not less nor equal</td>\n</tr>\n<tr>\n<td>JGE/JNL</td>\n<td>(SF xor OF) = 0</td>\n<td>greater or equal/not less</td>\n</tr>\n<tr>\n<td>JL/JNGE</td>\n<td>(SF xor OF) = 1</td>\n<td>less/not greater nor equal</td>\n</tr>\n<tr>\n<td>JLE/JNG</td>\n<td>((SF xor OF) or ZF) = 1</td>\n<td>less or equal/not greater</td>\n</tr>\n<tr>\n<td>JNO</td>\n<td>OF = 0 not</td>\n<td>overflow</td>\n</tr>\n<tr>\n<td>JNS</td>\n<td>SF = 0 not</td>\n<td>sign (positive, including 0)</td>\n</tr>\n<tr>\n<td>JO</td>\n<td>OF = 1</td>\n<td>overflow</td>\n</tr>\n<tr>\n<td>JS</td>\n<td>SF = 1</td>\n<td>sign (negative)</td>\n</tr>\n</tbody>\n</table>\n<h3 id=\"杂项指令\"> 杂项指令</h3>\n<ul>\n<li>LEA (Load Effective Address) LEA DEST SRC。将SRC(一定是M)的地址存放到DEST(一定是R)中。例如,LEA EBX, EBCDIC_TABLE意为将EBCDIC_TABLE的首地址存放到EBX中。</li>\n<li>NOP (No Operation) 用于内存对齐。</li>\n<li>XLAT (Translate) 将AL中的一个字节替换成用户指定的表中的一个字节。当执行XLAT时,EBX是表的首地址,AL中的值为表的索引。执行XLAT后,AL的内容变为表中指定位置的内容,EBX的值不变。因为AL只有8-bit,所以表最多256B。</li>\n</ul>\n<h2 id=\"_80386指令集说明\"> 80386指令集说明</h2>\n<h3 id=\"操作数和地址的大小-operand-size-address-size\"> 操作数和地址的大小(Operand-Size, Address-Size)</h3>\n<ul>\n<li>default: 指令中的D-bit,为0表示16-bit,为1表示32-bit。</li>\n<li>指令前缀: Operand-Size对应66H,Address-Size对应67H。其效果为对当前D-bit取反。(例如,有66H,D为0,则有效操作数大小为32-bit)</li>\n<li>栈的Address-Size: 在SS中的数据段的B-bit。为0表示16-bit,即使用SP,为1表示32-bit,即使用ESP。</li>\n<li>通常32-bit机器默认D-bit为1,指令如果出现66H前缀,表示要改变操作数大小为16-bit。</li>\n</ul>\n<h3 id=\"指令格式-2\"> 指令格式</h3>\n<p><img src=\"/assets/img/80386-instruction-format.png\" alt=\"指令格式\" /></p>\n<p>可用的指令前缀(H表示该数为16进制)</p>\n<ul>\n<li>F3H REP prefix (used only with string instructions)</li>\n<li>F3H REPE/REPZ prefix (used only with string instructions)</li>\n<li>F2H REPNE/REPNZ prefix (used only with string instructions)</li>\n<li>F0H LOCK prefix</li>\n</ul>\n<hr>\n<p>段覆盖指令前缀</p>\n<ul>\n<li>2EH CS segment override prefix</li>\n<li>36H SS segment override prefix</li>\n<li>3EH DS segment override prefix</li>\n<li>26H ES segment override prefix</li>\n<li>64H FS segment override prefix</li>\n<li>65H GS segment override prefix</li>\n<li>66H Operand-size override</li>\n<li>67H Address-size override</li>\n</ul>\n<h4 id=\"modr-m-and-sib-bytes\"> ModR/M and SIB Bytes</h4>\n<p><img src=\"/assets/img/modrm-sib.png\" alt=\"ModR/M&SIB\" />\nx86通过ModR/M字节来指示内存操作数</p>\n<ul>\n<li>mod(MSB的两位) 和r/m组合为32种可能的值,包括8个寄存器,24种寻址模式(indexing mode)\n<ul>\n<li>32位寻址</li>\n<li>00 通常代表地址值在寄存器内</li>\n<li>01 通常代表地址值在寄存器内,还要加上modR/M字节后跟的8-bit位移</li>\n<li>10 通常代表地址值在寄存器内,还要加上modR/M字节后跟的32-bit位移</li>\n<li>11 取寄存器内的值</li>\n</ul>\n</li>\n<li>reg/op(mod后三位) 取决于指令的opcode,通常代表寄存器编号,在某些指令中代表opcode信息。</li>\n<li>r/m(LSB的三位) 可能代表操作数使用的寄存器,看和mod的组合。</li>\n</ul>\n<p>SIB字节在r/m为100时使用。加不加位移,位移几位看mod字段。</p>\n<ul>\n<li>ss 比例因子。00:1B, 01:2B, 10:4B, 11:8B。</li>\n<li>index 索引寄存器使用的通用寄存器编号</li>\n<li>base 基址寄存器使用的通用寄存器编号</li>\n</ul>\n<h4 id=\"如何阅读指令格式说明页\"> 如何阅读指令格式说明页</h4>\n<ul>\n<li>i386手册中的汇编语言格式都是Intel格式, 而objdump的默认格式是AT&T格式, 两者的源操作数和目的操作数位置不一样, 千万不要把它们混淆了! 否则你将会陷入难以理解的bug中.</li>\n<li>opcode\n<ul>\n<li>/digit(0-7) ModR/M字节只使用r/m域,reg/opcode字段包含的数字为指令操作码的扩展。对于含有/digit记号的指令形式, 需要通过指令本身的opcode和ModR/M中的扩展opcode共同决定指令的形式, 例如80 /0表示add指令的一种形式, 而80 /5则表示sub指令的一种形式, 只看opcode的首字节80不能区分它们.</li>\n<li>/r 表示opcode后跟一个ModR/M字节,reg/opcode字段包含的数字为通用寄存器的编码。</li>\n<li>cb, cw, cd, cp 操作码后的1-byte(cb),2-byte(cw),4-byte(cd),6-byte(cp)用于指定code offset(相对段寄存器的偏移)或是段寄存器的新值。例如CALL指令的一种</li>\n<li>ib, iw, id 操作码,modR/M或SIB后的1-byte(ib),2-byte(iw),4-byte(id)是指令的立即数。是否有符号有操作码决定</li>\n<li>+rb, +rw, +rd分别表示8位, 16位, 32位通用寄存器的编码. 和ModR/M中的reg域不一样的是, 这三种记号表示直接将通用寄存器的编号按数值加到opcode中 (也可以看成通用寄存器的编码嵌在opcode的低三位), 因此识别指令的时候可以通过opcode的低三位确定一个寄存器操作数.</li>\n</ul>\n</li>\n<li>instruction\n<ul>\n<li>以下表示操作数的类型</li>\n<li>rel8 相对地址,范围从包含指令末尾的之前的128个字符到指令结束后的127个字符</li>\n<li>rel16,rel32 在同一个Segment内的相对地址。operand-size分别为16-bit和32-bit</li>\n<li>ptr16:16, ptr16:32 FARPOINTER。略。</li>\n<li>r8 AL, CL, DL, BL, AH, CH, DH, BH中的一个</li>\n<li>r16 AX, CX, DX, BX, SP, BP, SI, DI中的一个</li>\n<li>r32 EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI中的一个</li>\n<li>imm8 8位有符号立即数。和word,DoubleWord的操作数同时使用时,会进行有符号扩展。</li>\n<li>imm16 16位有符号立即数。</li>\n<li>imm32 32位有符号立即数。</li>\n<li>r/m8|16|32 存储在寄存器或内存中的1B|2B|4B操作数</li>\n<li>m8|16|32 内存中的操作数</li>\n<li>m16:16, M16:32 FARPOINTER内存操作数。略。</li>\n<li>m16 & 32, m16 & 16, m32 & 32 内存数据对</li>\n<li>moffs8, moffs16, moffs32 某些MOV变体使用的内存偏移(相对于当前Segment的基),这些变体不会用到modR/M字节</li>\n<li>Sreg 段寄存器。ES=0, CS=1, SS=2, DS=3, FS=4, and GS=5.</li>\n</ul>\n</li>\n<li>clock 指令执行需要的时钟周期数\n<ul>\n<li>n 重复次数</li>\n<li>m 执行的下一个指令中的组件数量</li>\n<li>pm 保护模式</li>\n</ul>\n</li>\n<li>description\n<ul>\n<li>有两个操作数时,通常SRC在右,DEST在左。</li>\n<li><code>"(*"和"*)"</code>之间是注释</li>\n<li>寄存器的名字代表寄存器的内容。加了<code>[]</code>后,表示寄存器中内容是地址,该地址的内容为<code>[register]</code>代表的内容。</li>\n<li><code>[]</code> 就是取地址的内容。地址是相对于段基址的地址。</li>\n<li>A := B 将B的值赋予A</li>\n<li>OperandSize 代表指令中的 operand-size attribute。AddressSize,StackAddrSize类似</li>\n<li>eSP 代表ESP或SP,取决于当前栈中的B-bit。</li>\n</ul>\n</li>\n<li>opcode map\n<ul>\n<li>操作数类型可以用Zz表示,Z表示寻址方式,z表示操作数长度</li>\n<li>A 直接寻址。没有modR/M字节,操作数直接编码在指令中</li>\n<li>E opcode后面紧接着一个modR/M字节,指定了操作数。操作数要么是通用寄存器,要么是内存地址。如果是内存地址</li>\n<li>G modR/M字节的reg字段选择一个通用寄存器</li>\n<li>F EFLAGS</li>\n<li>I 立即数。其值编码在指令的下一个字节</li>\n<li>J 指令包含一个相对EIP的相对位移(relative offset)</li>\n<li>O 指令没有modR/M字节,操作数的偏移量被编码为单词或双字</li>\n<li>以下为操作数类型标识符,和operand size attribute无关的前面加-。</li>\n<li>a 两个单字/双字操作数,在内存中</li>\n<li>c byte/word</li>\n<li>p 32/48位指针</li>\n<li>s Six-byte pseudo-descriptor</li>\n<li>v 单字/双字</li>\n<li>-b byte</li>\n<li>-w word</li>\n<li>-d double-word</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"指令\"> 指令</h3>\n<div><pre><code><span>// PUSH</span>\n<span>IF</span> StackAddrSize <span>=</span> <span>16</span>\n<span>THEN</span>\n <span>IF</span> OperandSize <span>=</span> <span>16</span> <span>THEN</span>\n <span>SP</span> ← <span>SP</span> <span>-</span> <span>2</span><span>;</span>\n <span>(</span><span>SS</span><span>:</span><span>SP</span><span>)</span> <span>←</span> <span>(</span><span>SOURCE</span><span>)</span><span>;</span> \n <span>ELSE</span>\n <span>SP</span> ← <span>SP</span> <span>-</span> <span>4</span><span>;</span>\n <span>(</span><span>SS</span><span>:</span><span>SP</span><span>)</span> <span>←</span> <span>(</span><span>SOURCE</span><span>)</span><span>;</span>\n <span>FI</span><span>;</span>\n<span>ELSE</span>\n <span>IF</span> OperandSize <span>=</span> <span>16</span>\n <span>THEN</span>\n <span>ESP</span> ← <span>ESP</span> <span>-</span> <span>2</span><span>;</span>\n <span>(</span><span>SS</span><span>:</span><span>ESP</span><span>)</span> <span>←</span> <span>(</span><span>SOURCE</span><span>)</span><span>;</span>\n <span>ELSE</span>\n <span>ESP</span> ← <span>ESP</span> <span>-</span> <span>4</span><span>;</span>\n <span>(</span><span>SS</span><span>:</span><span>ESP</span><span>)</span> <span>←</span> <span>(</span><span>SOURCE</span><span>)</span><span>;</span>\n <span>FI</span><span>;</span>\n<span>FI</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br></div></div><ul>\n<li>e8 cw CALL rel16 首先PUSH(IP),在EIP=EIP+rel16&0x0000ffff</li>\n<li>68 PUSH imm</li>\n</ul>\n<h2 id=\"intel-和-at-t-格式汇编的语法差异\"> Intel 和 AT&T 格式汇编的语法差异</h2>\n<table>\n<thead>\n<tr>\n<th>type</th>\n<th>Intel</th>\n<th>AT&T</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Comments</td>\n<td>;</td>\n<td>//</td>\n</tr>\n<tr>\n<td>Instructions</td>\n<td>Untagged add</td>\n<td>Tagged with operand sizes: addq</td>\n</tr>\n<tr>\n<td>Registers</td>\n<td>eax, ebx, etc.</td>\n<td>%eax,%ebx, etc.</td>\n</tr>\n<tr>\n<td>Immediates</td>\n<td>0x100</td>\n<td>$0x100</td>\n</tr>\n<tr>\n<td>Indirect</td>\n<td>[eax]</td>\n<td>(%eax)</td>\n</tr>\n<tr>\n<td>General indirect</td>\n<td>[base + reg + reg * scale + displacement]</td>\n<td>displacement(reg, reg, scale)</td>\n</tr>\n</tbody>\n</table>\n<p>AT&T and Intel syntax use the opposite order for source and destination operands. Intel add eax, 4 is addl $4, %eax. The source, dest convention is maintained for compatibility with previous Unix assemblers. Note that instructions with more than one source operand, such as the enter instruction, do not have reversed order.</p>\n",
"image": "https://kigane.github.io/assets/img/general-registers.png",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "Linux - Ubantu",
"url": "https://kigane.github.io/note/cs/linux/",
"id": "https://kigane.github.io/note/cs/linux/",
"content_html": "<h2 id=\"安装\"> 安装</h2>\n<p><a href=\"https://nju-projectn.github.io/ics-pa-gitbook/ics2020/0.1.html\" target=\"_blank\" rel=\"noopener noreferrer\">NJU PA0</a><br>\n分区:此电脑->管理->磁盘管理,选择磁盘,右键->压缩卷,分配适当空间即可,不用新建简单卷。</p>\n<h2 id=\"设置\"> 设置</h2>\n<ul>\n<li>sudo apt install gnome-tweaks</li>\n<li>sudo apt remove gnome-shell-extension-ubantu-dock</li>\n<li>sudo dpkg -i package_file.deb</li>\n</ul>\n<h2 id=\"关于键位\"> 关于键位</h2>\n<ul>\n<li>C == ctrl, S == shift, M == meta | alt | esc</li>\n<li>ESC == <code>C-[</code></li>\n<li>Up == <code>C-p</code></li>\n<li>Down == <code>C-n</code></li>\n<li>Left == <code>C-b</code></li>\n<li>Right == <code>C-f</code></li>\n</ul>\n<p>在vim等应用中<code>C-s</code>会导致无法交互,据说是和software flow control有关。按<code>C-q</code>可以退出这种状态。</p>\n<h2 id=\"ansi转义码\"> ANSI转义码</h2>\n<ul>\n<li>CSI 控制序列起始符 <code>ESC[</code></li>\n<li>SGR 修改接下来的字符外观 <code>CSI n m</code> n为字符外观选项,可以控制字符颜色,斜体,下划线,闪烁等外观。m为SGR终止符。<code>CSI n m</code>相当于<code>CSI 0 m</code>。可以使用多个选项,中间用<code>;</code>隔开即可。如<code>\\033[4;31m</code>,为红色带下划线。</li>\n<li><code>ESC</code> = <code>\\033</code> = <code>\\e</code>(echo -e时)</li>\n</ul>\n<table>\n<thead>\n<tr>\n<th>n</th>\n<th>Name</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>Reset or normal, All attributes off</td>\n</tr>\n<tr>\n<td>1</td>\n<td>Bold or increased intensity</td>\n</tr>\n<tr>\n<td>2</td>\n<td>Faint, decreased intensity, or dim</td>\n</tr>\n<tr>\n<td>3</td>\n<td>Italic</td>\n</tr>\n<tr>\n<td>4</td>\n<td>Underline</td>\n</tr>\n<tr>\n<td>5</td>\n<td>Slow blink</td>\n</tr>\n<tr>\n<td>9</td>\n<td>Crossed-out, or strike</td>\n</tr>\n<tr>\n<td>10</td>\n<td>Primary (default) font</td>\n</tr>\n<tr>\n<td>11–19</td>\n<td>Alternative font</td>\n</tr>\n<tr>\n<td>30–37</td>\n<td>Set foreground color</td>\n</tr>\n<tr>\n<td>38</td>\n<td>Set foreground color, Next arguments are 5;n or 2;r;g;b</td>\n</tr>\n<tr>\n<td>39</td>\n<td>Default foreground color</td>\n</tr>\n<tr>\n<td>40–47</td>\n<td>Set background color</td>\n</tr>\n<tr>\n<td>48</td>\n<td>Set background color, Next arguments are 5;n or 2;r;g;b</td>\n</tr>\n<tr>\n<td>49</td>\n<td>Default background color</td>\n</tr>\n</tbody>\n</table>\n<h3 id=\"_8-bit颜色\"> 8-bit颜色</h3>\n<ul>\n<li>ESC[38;5;⟨n⟩m 设置字体颜色</li>\n<li>ESC[48;5;⟨n⟩m 设置背景颜色</li>\n<li>0- 7: standard colors (as in ESC [ 30–37 m)</li>\n<li>8- 15: high intensity colors (as in ESC [ 90–97 m)</li>\n<li>16-231: 6 × 6 × 6 cube (216 colors): 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5)</li>\n<li>232-255: grayscale from black to white in 24 steps</li>\n</ul>\n<h3 id=\"_24-bit颜色-rgb\"> 24-bit颜色(RGB)</h3>\n<ul>\n<li>ESC[38;2;⟨r⟩;⟨g⟩;⟨b⟩m 设置RGB字体颜色</li>\n<li>ESC[48;2;⟨r⟩;⟨g⟩;⟨b⟩m 设置RGB背景颜色</li>\n</ul>\n<h2 id=\"unix哲学\"> Unix哲学:</h2>\n<ul>\n<li>每个程序只做一件事, 但做到极致</li>\n<li>用程序之间的相互协作来解决复杂问题</li>\n<li>每个程序都采用文本作为输入和输出, 这会使程序更易于使用</li>\n</ul>\n<h2 id=\"linux命令\"> Linux命令</h2>\n<div><pre><code><span>df</span> -h <span># 查看硬盘空间使用情况</span>\n<span>su</span> - <span># 切换到root用户</span>\nadduser username <span>sudo</span> <span># 将用户分到sudo组</span>\nhostnamectl <span># 显示主机信息 在/etc/hostname /etc/hosts 中修改主机名</span>\nlscpu <span># 查看cpu信息</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br></div></div><p>文件管理 - cd, pwd, mkdir, rmdir, ls, cp, rm, mv, tar<br>\n文件检索 - cat, more, less, head, tail, file, find<br>\n输入输出控制 - 重定向, 管道, tee, xargs<br>\n文本处理 - vim, grep, awk, sed, sort, wc, uniq, cut, tr<br>\n系统监控 - jobs, ps, top, kill, free, demsg, lsof<br>\n具体见Shell页</p>\n<h3 id=\"别名\"> 别名</h3>\n<ul>\n<li><code>alias</code> 列出已定义的别名</li>\n<li><code>alias shortname="your custom cmd"</code> 定义临时别名</li>\n<li><code>unalias shortname</code> 取消</li>\n<li><code>unalias -a</code> 全部取消</li>\n<li>在~/.bashrc中定义,并 source ~/.bashrc。定义永久别名</li>\n</ul>\n<p>PS:alias不支持参数,所以要使用参数的话,可以在.bashrc中定义shell函数。</p>\n<h3 id=\"特别\"> 特别</h3>\n<div><pre><code><span>sudo</span> <span>dmesg</span> <span># 输出OS的启动日志</span>\n</code></pre>\n<div><span>1</span><br></div></div><h2 id=\"环境变量\"> 环境变量</h2>\n<ul>\n<li>临时定义:<code>export name=value</code> -- 只对当前shell及其子shell有效</li>\n<li>永久定义,使用配置文件\n<ul>\n<li>用户: <code>~/.bash_profile</code></li>\n<li>系统: <code>/etc/profile</code></li>\n</ul>\n</li>\n</ul>\n<p>其他相关命令</p>\n<ul>\n<li><code>env</code>: 查看所有环境变量</li>\n<li><code>set</code>: 显示本地定义的shell变量,包括在.bashrc中定义的函数</li>\n<li><code>unset</code>: 清除环境变量 unset HELLO</li>\n<li><code>readonly</code>: 设置只读环境变量 readonly HELLO</li>\n</ul>\n<h2 id=\"工具\"> 工具</h2>\n<h3 id=\"strace\"> strace</h3>\n<p>strace 的作用就是跟踪程序在运行时所做的每个系统调用,然后将跟踪结果显示在屏幕上供你查看。</p>\n<ul>\n<li>-f 跟踪所有 fork 的子进程</li>\n<li>-t 报告每次调用的时间,</li>\n<li>-e trace=open,close,read,write 只跟踪对这些系统调用的调用,并忽略所有其他调用</li>\n</ul>\n<h3 id=\"tmux\"> tmux</h3>\n<p><code>sudo apt-get install tmux</code></p>\n<ul>\n<li>获取所有命令列表: C-b ?</li>\n<li>创建Session: tmux new -s session_name</li>\n<li>离开Session: C-b d(程序仍会继续运行)</li>\n<li>回到Session: tmux ls; tmux attach-session -t 0</li>\n<li>结束Session: tmux kill-session -t session_name</li>\n<li>列出所有Session: C-b s</li>\n<li>重命名当前Session: C-b $</li>\n</ul>\n<p>管理Tmux窗口</p>\n<ul>\n<li><code>C-b c</code> 创建新窗口</li>\n<li><code>C-b w</code> 列出所有窗口,从中选一个作为当前窗口</li>\n<li><code>C-b 0-9</code> 切换到窗口0-9</li>\n<li><code>C-b p | n</code> 切换到前|后一个窗口</li>\n<li><code>C-b ,</code> 重命名当前窗口</li>\n</ul>\n<p>pane</p>\n<ul>\n<li><code>C-b %</code> 分割为左右两个pane</li>\n<li><code>C-b "</code> 分割为上下两个pane</li>\n<li><code>C-b o</code> 跳转到下一个pane</li>\n<li><code>C-b h</code> 跳转到左边的pane。同理 j,k,l.</li>\n<li><code>C-b q</code> 显示pane数量</li>\n<li><code>C-b ;</code> 在当前和上一个pane间反复横跳</li>\n<li><code>C-b x</code> 关闭当前pane</li>\n<li><code>C-b {</code> 将当前pane移动到前一个pane位置</li>\n<li><code>C-b }</code> 将当前pane移动到后一个pane位置</li>\n<li><code>C-b C-o</code> 将所有窗口顺时针旋转</li>\n<li><code>C-b M-o</code> 将所有窗口逆时针旋转</li>\n<li><code>C-b !</code> move the current pane into a new separate window (‘break pane’)</li>\n</ul>\n<p>命令行</p>\n<ul>\n<li><code>C-b [</code> 进入scroll模式,q退出</li>\n<li><code>C-b PgUp</code> 进入scroll模式,并向上翻一页</li>\n</ul>\n<p>调整窗口大小</p>\n<ul>\n<li><code>C-b :</code> 会在底部显示命令行\n<ul>\n<li>输入 resize-pana -Direction #</li>\n<li>Direction: U, D, L, R</li>\n<li>#:数字,当前窗口向某方向上调整的尺寸。</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"tree\"> tree</h3>\n<p><code>sudo apt-get install tree</code></p>\n<ul>\n<li>tree dir: 以树状形式展示文件夹内容</li>\n<li>tree: 相当于<code>tree .</code></li>\n<li>-d: 只显示dir</li>\n<li>-f: 显示每个文件的完整路径</li>\n<li>-i: 不显示缩进</li>\n<li>-L level: 设置最大显示深度</li>\n<li>-P pattern: 只显示pattern匹配的文件(如果要显示.xx,需要加上-a)</li>\n<li>-I pattern: 不显示pattern匹配的文件</li>\n<li>--ignore-case</li>\n<li>--filelimit #: 不展开包含#个文件以上的文件夹</li>\n</ul>\n<p>文件显示选项</p>\n<ul>\n<li>-Q: 文件名加上双引号</li>\n<li>-u: 显示用户名</li>\n<li>-g: 显示用户组名</li>\n<li>-D: 显示最后修改日期</li>\n<li>-s: 显示文件大小(byte)</li>\n<li>-h: 显示文件大小(更人性化)</li>\n<li>-F: 显示文件类型(/:dir, =:socket fils, *:可执行文件, |:FIFO, >:doors)</li>\n</ul>\n<p>排序</p>\n<ul>\n<li>-t: 最后修改时间</li>\n<li>-U: 不排序</li>\n<li>-r: 逆序</li>\n<li>--dirsfirst: 文件夹放前面</li>\n<li>--sort=size (ctime, mtime, version)</li>\n</ul>\n<p>输出模式</p>\n<ul>\n<li>-X: xml</li>\n<li>-J: json</li>\n<li>-H baseHREF: html</li>\n</ul>\n<h2 id=\"makefile\"> makefile</h2>\n<p><a href=\"https://www.gnu.org/software/make/manual/html_node/index.html#SEC_Contents\" target=\"_blank\" rel=\"noopener noreferrer\">GNU make 手册</a></p>\n<p>echoing:回显。通常make会将先执行的命令打印出来再执行该命令。使用-s选项关闭所有命令的回显。-n选项只打印命令,不执行。\n在命令行开头加上<code>@</code>可关闭该命令的回显(-n仍会打印出来)。</p>\n<p>在命令行开头加上<code>-</code>表示如果该命令执行发生错误就忽略它。</p>\n<h3 id=\"基本规则\"> 基本规则</h3>\n<div><pre><code><span>target</span><span>:</span> file1 file2\n gcc -o a.out file1 file2\n</code></pre>\n<div><span>1</span><br><span>2</span><br></div></div><ul>\n<li>#代表注释</li>\n<li>命令行前必须有<tab></li>\n<li>target和依赖文件之间以 <code>:</code> 隔开</li>\n<li>make target : 会执行相应命令行</li>\n<li>make: 会默认执行第一个target</li>\n</ul>\n<div><pre><code>LIBS <span>=</span> -lm\nOBJ <span>=</span> file1 file2\n<span>target</span><span>:</span> <span>$</span><span>{</span>OBJ<span>}</span>\n gcc -o <span>$@</span> <span>$</span><span>{</span>OBJ<span>}</span> <span>$</span><span>{</span>LIBS<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br></div></div><p>makefile的变量</p>\n<ul>\n<li>变量从行开头开始写</li>\n<li>= 附近可以有空格</li>\n<li>习惯上,用大写字母</li>\n<li><span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:1em;vertical-align:-0.25em;\"></span><span>(</span><span style=\"margin-right:0.03588em;\">v</span><span>a</span><span style=\"margin-right:0.02778em;\">r</span><span>)</span><span>或</span></span></span></span>{var}取得变量的值</li>\n<li>可以使用shell的环境变量\n<ul>\n<li>在make target命令行中指定的环境变量优先考虑</li>\n<li>makefile内部定义的环境变量次之</li>\n<li>shell的环境变量最后</li>\n</ul>\n</li>\n<li><code>$@</code> 代表 target 位置的内容</li>\n<li><code>$xx</code> 称为<a href=\"https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html\" target=\"_blank\" rel=\"noopener noreferrer\">automatic variables</a></li>\n</ul>\n<h3 id=\"设置变量-变量展开\"> 设置变量&变量展开</h3>\n<p>make中变量展开方式有两种</p>\n<ul>\n<li>递归展开: 使用 <code>=</code>或<code>define\\n....\\n endef</code>。 类似引用。</li>\n<li>简单展开:使用 <code>:=</code> 或 <code>::=</code>。逐行处理。</li>\n<li>变量还有一种条件设置方式:使用<code>?=</code>。即如果该变量已经定义了,不会再次定义覆盖以前的。</li>\n</ul>\n<div><pre><code>foo <span>=</span> <span>$</span><span>(</span>bar<span>)</span>\nbar <span>=</span> <span>$</span><span>(</span>ugh<span>)</span>\nugh <span>=</span> Huh?\n\nx <span>:=</span> abc\ny <span>:=</span> <span>$</span><span>(</span>x<span>)</span> efg\nx <span>:=</span> later\nx <span>?=</span> condition\n\n<span>recursively</span><span>:</span>\n echo <span>$</span><span>(</span>foo<span>)</span> <span># Huh? </span>\n<span>simply</span><span>:</span>\n echo <span>$</span><span>(</span>y<span>)</span> <span># abc efg</span>\n<span>condition</span><span>:</span>\n echo <span>$</span><span>(</span>x<span>)</span> <span># later</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br></div></div><h3 id=\"判断\"> 判断</h3>\n<div><pre><code><span>ifeq</span> <span>(</span>lhs, rhs<span>)</span> <span># ifneq(l, r)</span>\n<span># 如果lhs=rhs,则执行</span>\n<span>else</span> <span>ifeq</span><span>(</span>l, r<span>)</span>\n<span># 如果l=r,则执行</span>\n<span>else</span>\n<span># 其他情况</span>\n<span>endif</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br></div></div><h3 id=\"include\"> include</h3>\n<ul>\n<li><code>include filenames</code>: 在make读到这一行的时候暂停,去读filenames,读完继续。如果指定的filename不存在,则会先输出警告信息,在make remade之后如果还不存在,则报错(fatal)。文件搜寻顺序为当前文件夹,prefix/include (通常是/usr/local/include) /usr/gnu/include, /usr/local/include, /usr/include。</li>\n<li><code>-include filenames</code>: 如果filename不存在,则忽略它。</li>\n</ul>\n<h3 id=\"函数\"> 函数</h3>\n<p>函数调用语法</p>\n<div><pre><code><span>$</span><span>(</span>function arg0, arg1, ...<span>)</span>\n</code></pre>\n<div><span>1</span><br></div></div><h4 id=\"文本处理函数\"> 文本处理函数</h4>\n<ul>\n<li>subst函数: $(subst from, to , text)</li>\n<li>patsubst函数: $(patsubst pattern, replacement, text),注意这里的<code>%</code>相当于正则表达式中的通配符<code>*</code>。</li>\n<li>strip函数: $(strip string),将字符串开头和结尾的空白字符删除,字符串中词与词之间的空格只保留一个。</li>\n<li>findstring函数: $(findstring find, in),如果in中有find则返回find本身,否则返回空。</li>\n<li>filter函数: $(filter pattern..., text),将text中每个词和各个pattern匹配,返回所有匹配任一pattern成功的词。</li>\n<li>filter-out函数: 返回匹配不成功的。</li>\n<li>sort函数: $(sort list),将list中的单词按字典序返回,并且会去重。</li>\n<li>word函数: $(word n, text),将text中的第n个词返回。n大于text中词数则返回空。</li>\n<li>wordlist函数: $(wordlist s, e, text),返回从s到e个单词。闭区间。</li>\n<li>words函数: $(words text),返回text中单词个数。</li>\n<li>firstword函数: $(firstwords text),返回text中第一个单词。</li>\n<li>lastword函数: $(lastwords text),返回text中最后一个单词。</li>\n</ul>\n<div><pre><code>comma<span>:=</span> ,\nempty<span>:=</span>\nspace<span>:=</span> <span>$</span><span>(</span>empty<span>)</span> <span>$</span><span>(</span>empty<span>)</span>\nfoo<span>:=</span> a b c\nbar<span>:=</span> <span>$</span><span>(</span><span>subst</span> <span>$</span><span>(</span>space<span>)</span>,<span>$</span><span>(</span>comma<span>)</span>,<span>$</span><span>(</span>foo<span>)</span><span>)</span> <span># bar is now ‘a,b,c’</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br></div></div><h4 id=\"文件名相关函数\"> 文件名相关函数</h4>\n<div><pre><code><span>$</span><span>(</span><span>dir</span> src/foo.c hacks<span>)</span> <span># src/ ./</span>\n<span>$</span><span>(</span><span>notdir</span> src/foo.c hacks<span>)</span> <span># foo.c hacks</span>\n<span>$</span><span>(</span><span>suffix</span> src/foo.c src-1.0/bar.c hacks<span>)</span> <span># .c .c</span>\n<span>$</span><span>(</span><span>basename</span> src/foo.c src-1.0/bar hacks<span>)</span> <span># src/foo src-1.0/bar hacks</span>\n<span>$</span><span>(</span><span>addsuffix</span> .c,foo bar<span>)</span> <span># foo.c bar.c</span>\n<span>$</span><span>(</span>addprefix src/,foo bar<span>)</span> <span># src/foo src/bar</span>\n<span>$</span><span>(</span><span>join</span> a b,.c .o<span>)</span> <span># a.c b.o // join list1,list2 将list1和list中的单词一一对应的连接。</span>\n<span>$</span><span>(</span><span>wildcard</span> pattern<span>)</span> <span># 输出当前目录下匹配的文件名。</span>\n<span>$</span><span>(</span><span>realpath</span> names…<span>)</span> <span># 输出name文件的绝对路径,name必须是存在的文件或文件名</span>\n<span>$</span><span>(</span><span>abspath</span> names…<span>)</span> <span># 输出name文件的绝对路径,name可以包含通配符</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br></div></div><h4 id=\"wildcard\"> wildcard</h4>\n<p><code>$(wildcard pattern...)</code>: 某些地方不能使用<code>%</code>通配符时,就需要使用该函数。输出当前目录下匹配的文件名。</p>\n<div><pre><code>objects <span>:=</span> <span>$</span><span>(</span><span>patsubst</span> %.c,%.o,<span>$</span><span>(</span><span>wildcard</span> *.c<span>)</span><span>)</span>\n\n<span>foo</span> <span>:</span> <span>$</span><span>(</span>objects<span>)</span>\n cc -o foo <span>$</span><span>(</span>objects<span>)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br></div></div><h4 id=\"call\"> call</h4>\n<p><code>$(call variable, param1, param2)</code>,其中variable是makefile中的变量,variable的值中的<span><span><i>Not supported content</i></span><span aria-hidden=\"true\"><span><span style=\"height:1em;vertical-align:-0.25em;\"></span><span>(</span><span>0</span><span>)</span><span>代表自身的名字,</span></span></span></span>(1)代表param1,$(2)代表param2,以此类推。</p>\n<div><pre><code>reverse <span>=</span> <span>$</span><span>(</span>2<span>)</span> <span>$</span><span>(</span>1<span>)</span>\n\nfoo <span>=</span> <span>$</span><span>(</span><span>call</span> reverse,a,b<span>)</span> <span># foo = b a</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br></div></div><h4 id=\"shell\"> shell</h4>\n<p><code>$(shell shellcmd)</code>: 创建shell执行shellcmd,返回shellcmd的输出。返回状态存储在<code>.SHELLSTATUS</code>变量中</p>\n<div><pre><code>contents <span>:=</span> <span>$</span><span>(</span><span>shell</span> cat foo<span>)</span>\n</code></pre>\n<div><span>1</span><br></div></div><h2 id=\"ssh\"> ssh</h2>\n<div><pre><code><span>sudo</span> <span>apt</span> <span>install</span> openssh-client <span># 只连接别的机器</span>\n<span>sudo</span> <span>apt</span> <span>install</span> openssh-server <span># 本机作为服务器</span>\n/etc/init.d/ssh start <span># 手动启动server</span>\n/etc/init.d/ssh stop <span># 停止server</span>\n<span>vim</span> /etc/ssh/sshd_config <span># 配置文件,可修改端口号</span>\n/etc/init.d/ssh restart <span># 修改后需要重启server</span>\n<span>ssh</span> -p <span>22</span> user@ip <span># 登录另一台开了openssh-server的机器</span>\n<span>hostname</span> -I <span># 查看本机ip地址</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br></div></div><p><a href=\"https://www.netsarang.com/zh/free-for-home-school/\" target=\"_blank\" rel=\"noopener noreferrer\">xshell 免费版</a></p>\n<h2 id=\"任务管理\"> 任务管理</h2>\n<ul>\n<li>将任务放到后台执行 <code>cmd &</code> 会显示任务的序号和相关的PID</li>\n<li>为了不让后台任务的输出影响前台,可以重定向流</li>\n<li>将当前任务放到后台暂停 <code>C-z</code></li>\n<li>查看当前任务状态 <code>jobs</code>\n<ul>\n<li>-l 列出任务序号,PID,任务状态,命令串</li>\n<li>-r 仅列出后台run的任务</li>\n<li>-s 仅列出后台stop的任务</li>\n<li><code>+</code> 表示最近被放到后台的任务。 <code>-</code> 表示最近第二个被放到后台的任务</li>\n</ul>\n</li>\n<li>将后台任务拿到前台处理 <code>fg [[%]jobnumber]</code>\n<ul>\n<li>不加jobnumber,默认将最近被放到后台的任务拿出来</li>\n<li><code>fg -</code>,默认将最近第二个被放到后台的任务拿出来</li>\n<li><code>fg %1</code> 指定将任务序号为1的任务拿出来</li>\n</ul>\n</li>\n<li>让任务在后台的状态变成运行中 <code>bg [[%]jobnumber]</code></li>\n<li>管理后台任务 <code>kill -signal %jobnumber</code>\n<ul>\n<li>signal\n<ul>\n<li>-1: 重新读取一次参数的配置文件</li>\n<li>-2: 相当于<code>C-c</code></li>\n<li>-9: 立刻强制删除一个任务</li>\n<li>-15: 正常中止一个任务。默认值。</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"进程管理\"> 进程管理</h2>\n<ul>\n<li><code>ps -l</code> 查看当前bash的相关进程\n<ul>\n<li>F: 进程标识(process flag),说明进程的权限\n<ul>\n<li>4 root权限</li>\n<li>1 仅fork,没有exec</li>\n</ul>\n</li>\n<li>S: 进程状态\n<ul>\n<li>R: Running</li>\n<li>S: Sleep</li>\n<li>D: 不可被唤醒的睡眠状态,如等待I/O</li>\n<li>T: 停止状态(stop)</li>\n<li>Z: Zombie,僵尸状态,进程已经被中止但无法移出内存</li>\n</ul>\n</li>\n<li>UID: 进程所属用户的UID</li>\n<li>PID,PPID: 进程号,父进程的进程号</li>\n<li>C: CPU使用率(%)</li>\n<li>PRI: Priority 进程优先级</li>\n<li>NI: Nice 进程优先级相关</li>\n<li>ADDR: 进程在内存的那个部分。running进程显示 <code>-</code></li>\n<li>SZ: 进程使用的内存大小</li>\n<li>WCHAN: 进程是否在运行 <code>-</code>表示运行中</li>\n<li>TTY: 登录者的终端位置</li>\n<li>TIME: 进程实际使用的CPU运行时间</li>\n<li>CMD: 触发进程的命令</li>\n</ul>\n</li>\n<li><code>ps aux</code> 查看系统所有进程\n<ul>\n<li>VSZ: 进程使用的虚拟内存量(kB)</li>\n<li>RSS: 进程使用的固定内存量(kB)</li>\n<li>STAT: 同S</li>\n</ul>\n</li>\n<li><code>top</code> 动态查看进程的状态\n<ul>\n<li>-d n 指定更新状态间隔的秒数</li>\n<li>-p PID 查看指定进程。要看多个进程,重复使用多次-p选项即可。</li>\n<li>top 按键命令\n<ul>\n<li>? 帮助</li>\n<li>P 以CPU使用率排序</li>\n<li>M 以内存使用率排序</li>\n<li>N 以PID排序</li>\n<li>T 以进程使用的CPU时间排序</li>\n<li>q 退出</li>\n<li>k 给某个PID一个signal</li>\n<li>r 给某个PID一个新nice值</li>\n</ul>\n</li>\n</ul>\n</li>\n<li><code>kill -signal PID</code> 同jobs</li>\n</ul>\n",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2022-02-19T05:19:13.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "正则表达式",
"url": "https://kigane.github.io/note/cs/regex/",
"id": "https://kigane.github.io/note/cs/regex/",
"content_html": "<h2 id=\"学习网站推荐\"> 学习网站推荐</h2>\n<p><a href=\"https://regexone.com/lesson/introduction_abcs\" target=\"_blank\" rel=\"noopener noreferrer\">regexone.com 一个交互式的入门网站</a>\n<a href=\"https://github.com/ziishaned/learn-regex/blob/master/translations/README-cn.md\" target=\"_blank\" rel=\"noopener noreferrer\">learn-regex</a></p>\n<h2 id=\"基础\"> 基础</h2>\n<table>\n<thead>\n<tr>\n<th>元字符</th>\n<th>描述</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>.</td>\n<td>match-any-charcater</td>\n</tr>\n<tr>\n<td>[ ]</td>\n<td>匹配方括号内的任意一个字符</td>\n</tr>\n<tr>\n<td>[^ ]</td>\n<td>匹配除了方括号里的任意一个字符</td>\n</tr>\n<tr>\n<td>*</td>\n<td>匹配>=0个重复的在*号之前的字符</td>\n</tr>\n<tr>\n<td>+</td>\n<td>匹配>=1个重复的+号前的字符</td>\n</tr>\n<tr>\n<td>?</td>\n<td>标记?之前的字符为可有可无的</td>\n</tr>\n<tr>\n<td>{n,m}</td>\n<td>匹配num个大括号之前的字符或字符集 (n <= num <= m)</td>\n</tr>\n<tr>\n<td>(xyz)</td>\n<td>子模式,匹配与 xyz 完全相等的字符串</td>\n</tr>\n<tr>\n<td>|</td>\n<td>或运算符,匹配符号前或后的字符</td>\n</tr>\n<tr>\n<td>\\</td>\n<td>转义字符,用于匹配一些保留的字符 [ ] ( ) { } . * + ? ^ $ \\ |</td>\n</tr>\n<tr>\n<td>^</td>\n<td>match-beginning-of-line</td>\n</tr>\n<tr>\n<td>$</td>\n<td>match-end-of-line</td>\n</tr>\n</tbody>\n</table>\n<table>\n<thead>\n<tr>\n<th>简写</th>\n<th style=\"text-align:center\">描述</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>.</td>\n<td style=\"text-align:center\">除换行符外的所有字符</td>\n</tr>\n<tr>\n<td>\\w</td>\n<td style=\"text-align:center\">匹配所有字母数字,等同于 [a-zA-Z0-9_]</td>\n</tr>\n<tr>\n<td>\\W</td>\n<td style=\"text-align:center\">匹配所有非字母数字,即符号,等同于: [^\\w]</td>\n</tr>\n<tr>\n<td>\\d</td>\n<td style=\"text-align:center\">匹配数字: [0-9]</td>\n</tr>\n<tr>\n<td>\\D</td>\n<td style=\"text-align:center\">匹配非数字: [^\\d]</td>\n</tr>\n<tr>\n<td>\\s</td>\n<td style=\"text-align:center\">匹配所有空格字符,等同于: [\\t\\n\\f\\r\\p{Z}]</td>\n</tr>\n<tr>\n<td>\\S</td>\n<td style=\"text-align:center\">匹配所有非空格字符: [^\\s]</td>\n</tr>\n<tr>\n<td>\\f</td>\n<td style=\"text-align:center\">匹配一个换页符</td>\n</tr>\n<tr>\n<td>\\n</td>\n<td style=\"text-align:center\">匹配一个换行符</td>\n</tr>\n<tr>\n<td>\\r</td>\n<td style=\"text-align:center\">匹配一个回车符</td>\n</tr>\n<tr>\n<td>\\t</td>\n<td style=\"text-align:center\">匹配一个制表符</td>\n</tr>\n<tr>\n<td>\\v</td>\n<td style=\"text-align:center\">匹配一个垂直制表符</td>\n</tr>\n<tr>\n<td>\\p</td>\n<td style=\"text-align:center\">匹配 CR/LF(等同于 \\r\\n),用来匹配 DOS 行终止符</td>\n</tr>\n</tbody>\n</table>\n<h2 id=\"标志\"> 标志</h2>\n<p>使用标志:<code>/RegEx/flags</code></p>\n<table>\n<thead>\n<tr>\n<th>标志</th>\n<th style=\"text-align:center\">描述</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>i</td>\n<td style=\"text-align:center\">忽略大小写</td>\n</tr>\n<tr>\n<td>g</td>\n<td style=\"text-align:center\">全局搜索(不仅仅返回第一个匹配的,而是返回全部)</td>\n</tr>\n<tr>\n<td>m</td>\n<td style=\"text-align:center\">多行修饰符:锚点元字符 ^ $ 工作范围在每行的起始</td>\n</tr>\n</tbody>\n</table>\n<p>(^,$) 用于检查格式是否是在待检测字符串的开头或结尾。但我们如果想要它在每行的开头和结尾生效,我们需要用到多行修饰符 m。</p>\n<h2 id=\"前后预查\"> 前后预查</h2>\n<p>用于判断所匹配的格式是否在另一个确定的格式(通常会用小括号包裹)之前或之后,匹配结果不包含该确定格式(仅作为约束)。</p>\n<table>\n<thead>\n<tr>\n<th>符号</th>\n<th style=\"text-align:left\">描述</th>\n<th style=\"text-align:left\">示例</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>?=</td>\n<td style=\"text-align:left\">正先行断言-存在(后面一定有)</td>\n<td style=\"text-align:left\">"(T</td>\n</tr>\n<tr>\n<td>?!</td>\n<td style=\"text-align:left\">负先行断言-排除(后面一定没有)</td>\n<td style=\"text-align:left\">"(T</td>\n</tr>\n<tr>\n<td>?<=</td>\n<td style=\"text-align:left\">正后发断言-存在(前面一定有)</td>\n<td style=\"text-align:left\">"(?<=(T</td>\n</tr>\n<tr>\n<td>?<!</td>\n<td style=\"text-align:left\">负后发断言-排除(前面一定没有)</td>\n<td style=\"text-align:left\">"(?<!(T</td>\n</tr>\n</tbody>\n</table>\n<h2 id=\"贪婪匹配和惰性匹配\"> 贪婪匹配和惰性匹配</h2>\n<p>正则表达式默认采用贪婪匹配模式(主要针对量词*,+,{l,},{,h},{l,h}),在该模式下意味着会匹配尽可能长的子串。我们可以使用 ?(紧跟在量词后面) 将贪婪匹配模式转化为惰性匹配模式。</p>\n<p>"/(.*at)/" => <strong>The fat cat sat on the mat</strong>.</p>\n<p>"/(.*?at)/" => <strong>The fat</strong> cat sat on the mat.</p>\n",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "shell",
"url": "https://kigane.github.io/note/cs/shell-cheatsheet/",
"id": "https://kigane.github.io/note/cs/shell-cheatsheet/",
"content_html": "<h2 id=\"shell-是什么\"> shell 是什么</h2>\n<p>核心功能:允许你执行程序,输入并获取某种半结构化的输出。</p>\n<h2 id=\"shell-基础\"> shell 基础</h2>\n<div><pre><code>hostname:~$ <span># 主机名:当前目录 $显示当前并非root用户</span>\nhostname:~$ <span>echo</span> hello\n</code></pre>\n<div><span>1</span><br><span>2</span><br></div></div><p>shell 基于空格分割命令并进行解析,执行第一个单词代表的程序,后续单词将作为程序访问的参数</p>\n<h3 id=\"环境变量\"> 环境变量</h3>\n<p>shell去哪里找需要执行的程序呢?shell是一个编程环境,有变量,条件,循环和函数,在shell中执行命令就是在执行一段shell可以理解的代码。如果执行的命令不是shell的关键字,shell就会咨询<strong>环境变量$PATH</strong>,其中,不同的路径由":"分割。</p>\n<p>当然,不用$PATH,直接给出执行程序的路径也可以。</p>\n<div><pre><code><span>echo</span> <span>$PATH</span>\n/bin/echo <span>$PATH</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br></div></div><h3 id=\"ls-l-chmod\"> ls -l & chmod</h3>\n<div><pre><code><span>#权限 TODO TODO TODO 文件大小(B) 最后修改时间 文件名</span>\n-rw-r--r-- <span>1</span> user user <span>220</span> May <span>25</span> 02:38 .bash_logout\n-rw-r--r-- <span>1</span> user user <span>3771</span> May <span>25</span> 02:38 .bashrc\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br></div></div><p>权限由10个字符表示:</p>\n<ul>\n<li>第一个:d 表示这是一个目录</li>\n<li>后面每3个一组: 表示文件所有者(u,user),用户组(g,group),其他所有人(o,other)所具有的权限</li>\n<li>\n<ul>\n<li>表示相应无权限</li>\n</ul>\n</li>\n<li>-rwx 分别表示<strong>读,写,执行</strong>权限</li>\n</ul>\n<p><code>chmod 0777 FILE</code>: 表示将FILE权限设为-rwxrwxrwx。第一个参数表示文件类型。如果不是4个数,会在前面补0,如4实际为0004。\n<code>chmod u+x FILE</code>: 表示为用户添加FILE执行权限。[ugoa][+-=][rwx]</p>\n<h3 id=\"在程序间创建连接-重定向\"> 在程序间创建连接--重定向</h3>\n<p>shell中程序有两个主要的流:</p>\n<ul>\n<li>输入流:键盘 '/dev/stdin'</li>\n<li>输出流:显示器 '/dev/stdout', '/dev/stderr'</li>\n<li>空:忽略输出 /dev/null</li>\n</ul>\n<p>当然,可以重定向</p>\n<div><pre><code><span>echo</span> hello <span>></span> hello.txt <span># hello</span>\n<span>cat</span> <span><</span> hello.txt <span>></span> hello2.txt <span># hello2.txt 中内容: hello</span>\n<span>echo</span> world <span>>></span> hello.txt <span># hello\\nworld</span>\n<span># echo \\ world >> hello.txt # hello\\n world 空格需要转义</span>\n<span># echo ' world' >> hello.txt # hello\\n world 或用引号(单,双都可)包裹</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br></div></div><p>>, < 用于重定向流,>>用于追加内容。</p>\n<p>管道(pipes):"|" 操作符,用于将一个程序的输出和另一个程序的输入连接起来。</p>\n<h3 id=\"root\"> root</h3>\n<p>sudo 可以让用户以root的身份执行紧跟着的程序。</p>\n<p>只有根用户才能做的操作:向<code>sysfs</code>文件写入内容,该文件暴露了一些内核参数,所以用户可以在运行时配置系统内核。系统被挂载在<code>/sys</code>下。</p>\n<h2 id=\"shell脚本\"> shell脚本</h2>\n<ul>\n<li>变量赋值:<code>foo=bar</code>,<strong>不能有空格</strong></li>\n<li>访问变量:<code>$foo</code></li>\n<li>字符串:单引号表示原义字符串,其中的变量不会被转义。而双引号中变量会被转义。</li>\n</ul>\n<h3 id=\"bash函数\"> bash函数</h3>\n<div><pre><code><span>mcd</span><span>(</span><span>)</span><span>{</span>\n <span>mkdir</span> -p <span>\"<span>$1</span>\"</span>\n <span>cd</span> <span>\"<span>$1</span>\"</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br></div></div><p>bash使用很多特殊变量表示参数</p>\n<table>\n<thead>\n<tr>\n<th style=\"text-align:center\">符号</th>\n<th style=\"text-align:left\">含义</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style=\"text-align:center\">$0</td>\n<td style=\"text-align:left\">脚本名</td>\n</tr>\n<tr>\n<td style=\"text-align:center\">$1~$9</td>\n<td style=\"text-align:left\">第n个参数</td>\n</tr>\n<tr>\n<td style=\"text-align:center\">$@</td>\n<td style=\"text-align:left\">所有参数</td>\n</tr>\n<tr>\n<td style=\"text-align:center\">$#</td>\n<td style=\"text-align:left\">参数个数</td>\n</tr>\n<tr>\n<td style=\"text-align:center\">$?</td>\n<td style=\"text-align:left\">前一个命令的返回值</td>\n</tr>\n<tr>\n<td style=\"text-align:center\">$$</td>\n<td style=\"text-align:left\">当前脚本的进程识别码</td>\n</tr>\n<tr>\n<td style=\"text-align:center\">!!</td>\n<td style=\"text-align:left\">完整的上一条命令,包括参数</td>\n</tr>\n<tr>\n<td style=\"text-align:center\">$_</td>\n<td style=\"text-align:left\">上一条命令的最后一个参数</td>\n</tr>\n</tbody>\n</table>\n<p>命令通常使用<code>STDOUT</code>返回输出值,使用<code>STDERR</code>返回错误码。返回值0表示正常执行,其他都表示有错误发生。可以搭配短路运算符(<code>&&</code>,<code>||</code>)进行条件判断。</p>\n<ul>\n<li>同一行的多个命令可以用 ; 分隔</li>\n<li>命令替换 (command substitution):以变量的形式获取一个命令的输出。通过 $(CMD) 这样的方式来执行CMD这个命令时,它的输出结果会替换掉$(CMD)。例如,如果执行 for file in $(ls) ,shell首先将调用ls ,然后遍历得到的这些返回值。</li>\n</ul>\n<p>示例</p>\n<div><pre><code><span>#!/bin/bash</span>\n\n<span>echo</span> <span>\"Starting program at <span><span>$(</span><span>date</span><span>)</span></span>\"</span> <span># date会被替换成日期和时间</span>\n\n<span>echo</span> <span>\"Running program <span>$0</span> with <span>$#</span> arguments with pid <span>$$</span>\"</span>\n\n<span>for</span> <span>file</span> <span>in</span> <span>\"<span>$@</span>\"</span><span>;</span> <span>do</span>\n <span>grep</span> foobar <span>\"<span>$file</span>\"</span> <span>></span> /dev/null <span><span>2</span>></span> /dev/null\n <span># 如果模式没有找到,则grep退出状态为 1</span>\n <span># 我们将标准输出流和标准错误流重定向到Null,因为我们并不关心这些信息</span>\n <span>if</span> <span>[</span><span>[</span> <span>$?</span> -ne <span>0</span> <span>]</span><span>]</span><span>;</span> <span>then</span> <span># [[ 中的命令必须前后空一格 ]]</span>\n <span>echo</span> <span>\"File <span>$file</span> does not have any foobar, adding one\"</span>\n <span>echo</span> <span>\"# foobar\"</span> <span>>></span> <span>\"<span>$file</span>\"</span>\n <span>fi</span>\n<span>done</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br></div></div><p>在bash中进行比较时,尽量使用双方括号 [[ ]] 而不是单方括号 [ ],这样会降低犯错的几率,尽管这样并不能兼容 sh。</p>\n<p>在shebang行(脚本第一行)中使用env命令(!/usr/bin/env bash)是一种好的实践,env会用PATH环境变量来进行定位,从而提高脚本的可移植性。</p>\n<ul>\n<li>函数只能用与shell使用相同的语言,脚本可以使用任意语言。因此在脚本中包含 shebang 是很重要的。</li>\n<li>函数仅在定义时被加载,脚本会在每次被执行时加载。这让函数的加载比脚本略快一些,但每次修改函数定义,都要重新加载一次。</li>\n<li>函数会在当前的shell环境中执行,脚本会在单独的进程中执行。因此,函数可以对环境变量进行更改,比如改变当前工作目录,脚本则不行。脚本需要使用export将环境变量导出,并将值传递给环境变量。</li>\n</ul>\n<h3 id=\"文件描述符\"> 文件描述符</h3>\n<ul>\n<li>0 STDIN 标准输入 键盘 '/dev/stdin'</li>\n<li>1 STDOUT 标准输出 显示器 '/dev/stdout', '/dev/stderr'</li>\n<li>2 STDERR 标准错误</li>\n<li>空:忽略输出 /dev/null</li>\n</ul>\n<p>当文件描述符(0,1,2)与重定向符号"<, >"组合之后,就可以重新定向输入,输出,及错误。</p>\n<ul>\n<li><code>command 2>file1</code> 命令执行的错误信息保存到了file1文件中。显示屏只是显示正确的信息。</li>\n<li><code>command 1>file1 2>file2</code> 命令执行后,没有显示。因为正确输出到file1,错误定向到file2</li>\n<li><code>command &>file1</code> 命令执行后,输出和错误都定向到file1中\nPS:2>file 可以放在命令的前面,也可以放在后面。效果一样。</li>\n</ul>\n<p>在shell脚本中,进行流的重定向</p>\n<ul>\n<li>exec 1> file1</li>\n<li>exec 2> file2</li>\n<li>exec 0< file0</li>\n</ul>\n<p>指定命令的输出传到STDERR指定的文件: <code>echo "some output" >&2</code></p>\n<h2 id=\"shell工具\"> shell工具</h2>\n<h3 id=\"查看程序执行时间-time\"> 查看程序执行时间--time</h3>\n<h3 id=\"查找文件-find\"> 查找文件--find</h3>\n<p>find [-H] [-L] [-P] [-D debugopts] [-Olevel] [starting-point...] [expression]</p>\n<p>find会搜索以每个给出的starting-point为根的文件树,找出匹配expression的文件。默认的starting-point为<code>.</code>。</p>\n<p>选项</p>\n<ul>\n<li>-P 将symbolic links视为文件,不搜索其指向的文件。这是默认行为</li>\n<li>-L 搜索symbolic links指向的文件</li>\n<li>-H 不搜索symbolic links,除了在处理命令行参数的时候。(应该说的是最后的expression)</li>\n<li>以上三个只能选一个。同时出现,只有最后一个生效。</li>\n</ul>\n<hr>\n<ul>\n<li>-D 打印find命令的诊断信息</li>\n</ul>\n<hr>\n<ul>\n<li>-Olevel 优化等级\n<ul>\n<li>0,1 默认等级。expressions重新排序,-name,-regex测试首先进行</li>\n<li>2 -type,-xtype首先执行。</li>\n<li>3 所有cost-based查询优化符都会启用。如果有必要的话,测试的顺序会以代价小的优先。</li>\n</ul>\n</li>\n</ul>\n<p>表达式--描述如何匹配文件和如何处理匹配的文件</p>\n<ul>\n<li>组成\n<ul>\n<li>Tests 返回T/F,通常以文件的属性为判断基础。</li>\n<li>Actions 会产生其他影响。也会返回T/F,但以action的成功与否为基础。</li>\n<li>Global options 总返回true。会影响所有Tests和Actions的行为。</li>\n<li>Positional options 总返回true。只影响紧随其后的Tests或Actions。</li>\n<li>Operators expression中其他东西的连接符。默认为 <code>-a</code> 即 <code>AND</code></li>\n</ul>\n</li>\n<li>Positional options\n<ul>\n<li>-daystart 时间度量从今天0:00开始,而非24小时前。</li>\n<li>-regextype type 改变正则表达式的语法。具体请 -regextype help</li>\n<li>-warn,-nowarn 打开或关闭警告信息</li>\n</ul>\n</li>\n<li>Global options\n<ul>\n<li>-d/-depth 先处理每个目录的内容,再处理目录本身</li>\n<li>-maxdepth levels 目录树的最大展开深度。0表示只处理starting-point本身这个根节点。</li>\n<li>-mindepth levels 目录树的最小展开深度。1表示只不处理starting-point本身这个根节点。</li>\n</ul>\n</li>\n<li>Tests\n<ul>\n<li>数字参数 +n 大于n, -n 小于n, n 正好n。</li>\n</ul>\n<hr>\n<ul>\n<li>-xmin n 文件最后一次x是n分钟以前吗?</li>\n<li>-xnewer reference 文件比指定文件的x更新吗?</li>\n<li>-xtime n 文件最后一次x是n*24小时以内吗?(+1表示48小时以内,以次类推)</li>\n<li>其中x可为\n<ul>\n<li>a access,表示文件的上次使用时间。</li>\n<li>c change,表示文件状态的上次修改时间。</li>\n<li>m modify,表示文件数据的上次修改时间。没有mnewer,因为-newer的默认行为就是这个。</li>\n</ul>\n</li>\n<li>-newerXY reference 如果文件的X时间比Y时间新,则返回true。X,Y可以是\n<ul>\n<li>a accsess time</li>\n<li>B birthtime</li>\n<li>c inode status change time</li>\n<li>m modification time</li>\n<li>t reference直接解释为时间。</li>\n</ul>\n</li>\n<li>-used n 文件上次status改变后到最后access时间过了n天</li>\n</ul>\n<hr>\n<ul>\n<li>-empty 空文件或空文件夹</li>\n<li>-executable 当前用户可执行的文件</li>\n<li>-gid n 文件的数字组(group)ID为n</li>\n<li>-group gname 文件属于特定组</li>\n<li>-uid n</li>\n<li>-user uname</li>\n<li>-ilname,-iname,-ipath,-iregex 忽略大小写版本</li>\n<li>-inum n 文件有n个inode</li>\n<li>-links n 文件有n个hard link</li>\n<li>-lname pattern 文件是symbolic link且内容和pattern匹配。(如果使用的-L选项,则总返回false)</li>\n<li>-name pattern 匹配文件名(只会用文件名取匹配,不包含任何<code>/</code>)。元字符<code>*</code> <code>?</code> <code>[]</code>会匹配以<code>.</code> 开头的文件。要忽略以<code>.</code>开头的文件和文件夹,使用-prune选项。</li>\n<li>-path pattern 匹配完整的文件名(包含以某个starting point开始的路径,例如 ./src/hello.c)。注意:文件夹的末尾不会加<code>/</code>。</li>\n<li>-wholename pattern 匹配完整的文件名(绝对路径)。</li>\n<li>-regex pattern 匹配完整的文件名。(必须匹配整个完整的文件名,例如'./fubar3' 必须用'.*bar.'匹配)</li>\n<li>-perm mode 所有的权限匹配。mode 0xxx,参考chmod</li>\n<li>-perm -mode 所有的权限都有。 -[ugoa][rwx]</li>\n<li>-perm /mode 有其中一个权限</li>\n<li>-readable 当前用户可读</li>\n<li>-writable 当前用户可写</li>\n<li>-size n[cwbkMG] 使用n单位的空间的文件,会舍入。\n<ul>\n<li>b 表示512B为一个单位</li>\n<li>c bytes</li>\n<li>w two-byte word</li>\n<li>k KiB, 1024B</li>\n<li>M MiB, 1024k</li>\n<li>G GiB, 1024M</li>\n</ul>\n</li>\n<li>-type c 类型c包括\n<ul>\n<li>b block(buffered) special</li>\n<li>c character(unbuffered) special</li>\n<li>d 目录</li>\n<li>p named pipe(FIFO)</li>\n<li>f 常规文件</li>\n<li>l symbolic link</li>\n<li>s socket</li>\n<li>D door</li>\n</ul>\n</li>\n<li>-xtype 对symbolic link类型的文件,xtype会检查该link文件,而type不会。</li>\n</ul>\n</li>\n<li>Actions\n<ul>\n<li>-delete 删除文件,成功时返回true。</li>\n<li>-exec cmd; 执行cmd;,当cmd返回状态0时返回true。cmd会为每个匹配的文件执行一次。cmd会在执行find命令的起始目录执行。<code>{}</code>表示当前匹配的文件,<code>;</code>表示命令结束。</li>\n<li>-exec cmd {} + <code>-exec</code>的变体,执行cmd,所有匹配的文件都用空格连接后作为参数放在cmd后。</li>\n<li>-execdir cmd ;</li>\n<li>-execdir cmd {} + cmd会在匹配文件所在目录执行</li>\n<li>-ok cmd ;</li>\n<li>-okdir cmd ; 执行命令时会弹出提示框,问你是否执行。</li>\n<li>-ls 以ls -dils 的格式输出当前文件。</li>\n<li>-fls file 将-ls的输出输出到file文件中。 如果file不存在,创建一个新的,否则,覆盖(truncated)。</li>\n<li>-print 将完整文件名输出,以newline隔开。默认选项</li>\n<li>-print0 将完整文件名输出,以'\\0'隔开。</li>\n<li>-printf format\n<ul>\n<li>%p 文件名 '%h/%f'</li>\n<li>%P 不包含starting-point的文件名</li>\n<li>%f 纯文件名</li>\n<li>%F 文件使用的文件系统</li>\n<li>%h 文件所在的文件夹名</li>\n<li>%y 文件的类型</li>\n<li>%i 文件的十进制inode数</li>\n<li>%k 文件大小kB</li>\n<li>%m 文件的八进制权限bit</li>\n<li>%M 文件的权限符号表示</li>\n<li>%s 文件大小B</li>\n<li>%t/%a/%c/%Tk/%Ak/%Ck 文件时间,k为时间的表示方式。</li>\n<li>%d 文件在目录树中的深度,0表示根节点starting-point。</li>\n</ul>\n</li>\n<li>-fprint/-fprint0/-fprintf file [format]</li>\n<li>-prune 如果文件是目录,不要进入。如果使用了-depth,-delete,则无效。</li>\n<li>-quit 立即退出。</li>\n</ul>\n</li>\n<li>Operators 按优先级降序\n<ul>\n<li>( expr ) 因为shell中括号有特殊含义,最好这么写'(expr)'</li>\n<li>! expr</li>\n<li>expr1 [-a] expr2 -a等于AND</li>\n<li>expr1 -o expr2 -o等于OR</li>\n<li>expr1, expr2 两个表达式都会被评估,但最后一个表达式的返回值成为整个列表的返回值。可以用于搜索一些不同类型的目标,但只遍历一次。</li>\n<li>注意-a 比 -o 优先级高,意味着什么。举例来说,find . -name afile -o -name bfile -print 不会输出afile。</li>\n</ul>\n</li>\n</ul>\n<p>例如</p>\n<div><pre><code><span># 查找所有名称为src的文件夹</span>\n<span>find</span> <span>.</span> -name src -type d\n<span># 查找所有文件夹路径中包含test的python文件</span>\n<span>find</span> <span>.</span> -path <span>'*/test/*.py'</span> -type f\n<span># 查找前一天修改的所有文件</span>\n<span>find</span> <span>.</span> -mtime -1\n<span># 查找所有大小在500k至10M的tar.gz文件</span>\n<span>find</span> <span>.</span> -size +500k -size -10M -name <span>'*.tar.gz'</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br></div></div><p>除了列出所寻找的文件之外,find还能对所有查找到的文件进行操作。这能极大地简化一些单调的任务。</p>\n<div><pre><code><span># 删除全部扩展名为.tmp 的文件</span>\n<span>find</span> <span>.</span> -name <span>'*.tmp'</span> -exec <span>rm</span> <span>{</span><span>}</span> <span>\\</span><span>;</span>\n<span># 查找全部的 PNG 文件并将其转换为 JPG</span>\n<span>find</span> <span>.</span> -name <span>'*.png'</span> -exec convert <span>{</span><span>}</span> <span>{</span><span>}</span>.jpg <span>\\</span><span>;</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br></div></div><h3 id=\"xargs\"> xargs</h3>\n<p>xargs [opts] [cmd [initial-args]]</p>\n<p>xargs从标准输入读取items(以blank或newline为分隔符划分,空行直接忽略),建立并执行cmd命令。initial-args默认是用item填,直到达到系统定义的命令行限制。这样,cmd的总调用数会相对变少,性能表现好些。另外,xargs默认会将blank和newline特殊处理,所以,包含blank和newline的文件名不会被正确处理,需要使用-0选项。</p>\n<p>选项</p>\n<ul>\n<li>-t, --verbose 在执行cmd前,将其用STDERR输出</li>\n<li>-p, --interactive 执行每个命令前让用户决定是否执行</li>\n<li>-a file, --arg-file=file 从file读取输入,而非STDIN。如果使用了这个选项,则STDIN在执行cmd时不会变,否则会被重定向到/dev/null。</li>\n<li>-r 如果标准输入为空,则不要执行任何cmd。xargs默认会至少执行一次</li>\n</ul>\n<hr>\n<ul>\n<li>-L max-lines 每个cmd使用的非空输入最多为max-line行。</li>\n<li>-n max-args, --max-args=max-args 每个cmd最多使用max-args的参数。如果在达到max-args之前,size超了,则停。</li>\n<li>-s max-chars, --max-chars=max-chars 限制每个cmd最多使用的字符数,包括cmd和initail-args和最后的'\\0'。最多不能超过系统命令行限制。</li>\n<li>-x, --exit 如果size超了,直接退出</li>\n</ul>\n<hr>\n<ul>\n<li>--show-limits 显示os的命令行长度限制。 最好这么用:<code>xargs --show-limits 0< /dev/null</code></li>\n<li>-P max-procs, --max-procs=max-proc 多线程执行。默认为1,设为0表示使用尽可能多的线程。</li>\n<li>-0, --null items会以'\\0'结束,而非空格。所有字符都取字面量。通常和find -print0连用。</li>\n<li>-d delim, --delimiter=delim 输入item解析终止符。仅支持单个字符。</li>\n<li>-E eof-str 如果在输入的某一行出现了eof-str,则其余的输入被忽略。</li>\n</ul>\n<div><pre><code><span>find</span> /tmp -name core -type f -print <span>|</span> <span>xargs</span> /bin/rm -f\n<span>find</span> /tmp -name core -type f -print0 <span>|</span> <span>xargs</span> -0 /bin/rm -f\n</code></pre>\n<div><span>1</span><br><span>2</span><br></div></div><h3 id=\"wc-代码统计\"> wc--代码统计</h3>\n<p>print newline, word, and byte counts for each file.</p>\n<p>用法</p>\n<ul>\n<li>wc [OPTION] ... [FILE] ...</li>\n<li>wc [OPTION] ... --file0-from=F</li>\n<li>如果没有指定FILE或FILE=-,则以标准输入为输入。</li>\n</ul>\n<p>选项</p>\n<ul>\n<li>统计信息显示的相对顺序:newline, word, character, byte, maximum line length</li>\n<li>-c, --bytes</li>\n<li>-m, --chars</li>\n<li>-l, --lines</li>\n<li>--files0-from=F 用C风格字符串指定的所有输入文件</li>\n<li>-L, --max-line-length</li>\n<li>-w, --words</li>\n</ul>\n<h3 id=\"查找代码-grep\"> 查找代码--grep</h3>\n<p>grep 有很多选项,如 -C :获取查找结果的上下文(Context);-v 将对结果进行反选(Invert),也就是输出不匹配的结果。例如, grep -C 5 会输出匹配结果前后五行。当需要搜索大量文件的时候,使用 -R 会递归地进入子目录并搜索所有的文本文件。</p>\n<h3 id=\"sed-流编辑器\"> sed--流编辑器</h3>\n<p>sed [opts] [script] [input-file]</p>\n<ul>\n<li>-n 使用安静模式,不会总显示stdin的所有行,只显示被sed处理的行。</li>\n<li>-f scriptfile 读取文件中的操作脚本,并执行</li>\n<li>-r 使用扩展的正则表达式语法</li>\n<li>-i 直接修改文件内容</li>\n<li>script\n<ul>\n<li>#a content: 在#行,后新增一行,内容为content</li>\n<li>#i content: 在#行,前新增一行,内容为content</li>\n<li>l,hc content: 将l行到h行的内容替换为content</li>\n<li>l,hd: 删除l行到h行。$表示最后一行</li>\n<li>l,hp: 打印l行到h行。</li>\n<li>[l,h]s/old/new/g: 查找并替换</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"awk-行文本处理\"> awk--行文本处理</h3>\n<p>常见用法: awk 'program text' file, 从file中读取输入,每一行都用'program text'处理。</p>\n<ul>\n<li>awk每次处理一行,最小处理单位为字段(field, 以空格分隔)</li>\n<li>变量\n<ul>\n<li>$0 整行</li>\n<li>$# 第#个字段</li>\n<li>NF 一行的字段总数</li>\n<li>NR 当前awk处理的是第几行</li>\n<li>FS 目前的分隔符,默认为空格</li>\n</ul>\n</li>\n<li>逻辑运算 >,<,>=, <=, ==, !=</li>\n</ul>\n<hr>\n<p>mawk [-F separator] [-v var=value] [--] 'program text' [file...]<br>\nmawk [-F separator] [-v var=value] [-f program-file] [--] [file...]</p>\n<ul>\n<li>-F 指定分隔符</li>\n<li>-v 预先设置变量</li>\n<li>-f 从指定的文件中读取awk程序</li>\n<li>file 输入,未指定则从STDIN中读取</li>\n</ul>\n<hr>\n<p>AWK语言</p>\n<ul>\n<li>程序结构: pattern{action}的序列\n<ul>\n<li>pattern可以是\n<ul>\n<li>BEGIN 在处理第一行之前顺序执行所有的 BEGIN 后的action。</li>\n<li>END 在处理最后一行之后顺序执行所有的 END 后的action。</li>\n<li>expr</li>\n<li>expr1, expr2</li>\n</ul>\n</li>\n<li>语句以<code>;</code>或newline结束。</li>\n<li>控制流\n<ul>\n<li>if (expr) statement</li>\n<li>if (expr) statement else statement</li>\n<li>while (expr) statement</li>\n<li>do statement while (expr)</li>\n<li>for (opt_expr; opt_expr; opt_expr) statement</li>\n<li>for (var in array) statement</li>\n<li>continue</li>\n<li>break</li>\n</ul>\n</li>\n</ul>\n</li>\n<li>数据类型:numeric和string。\n<ul>\n<li>所有numeric都是以浮点数为内部表示和运算。true == 1.0</li>\n<li>string常量以双引号表示</li>\n</ul>\n</li>\n<li>正则表达式\n<ul>\n<li>expr ~ /regex/ 如果expr匹配regex,则返回1.</li>\n<li>expr !~ /regex/ 如果expr不匹配regex,则返回1.</li>\n<li>/r/ {action} 相当于 $0 ~ /r/ {action}, 当行匹配r时,才执行action</li>\n</ul>\n</li>\n<li>内置变量\n<ul>\n<li>$0 整行</li>\n<li>$# 第#个字段</li>\n<li>NF 第一行的字段总数</li>\n<li>NR 当前awk处理的是第几行</li>\n<li>FS 目前的field分隔符,默认为空格</li>\n<li>RS record分隔符,默认为'\\n'</li>\n<li>OFS,ORS 输出时的相应分隔符</li>\n<li>ARGC</li>\n<li>ARGV</li>\n<li>FILENAME 输入文件的文件名</li>\n</ul>\n</li>\n<li>内置函数\n<ul>\n<li>数学函数\n<ul>\n<li>atan2(y, x)</li>\n<li>cos(x)</li>\n<li>sin(x)</li>\n<li>exp(x)</li>\n<li>int(x) 向0舍入</li>\n<li>log(x) 自然对数</li>\n<li>rand()</li>\n<li>srand(seed)</li>\n<li>sqrt(x)</li>\n</ul>\n</li>\n<li>mktime(format)</li>\n<li>String\n<ul>\n<li>index(s, t) t在s中的起始位置</li>\n<li>length(s)</li>\n<li>match(s, r) 返回s中第一个r的位置</li>\n<li>split(s, A, r) 用r分割s为一系列field,并以数组形式存储在A中。如果r省略,默认为FS。</li>\n<li>sprintf(format, expr-list) 返回一个格式串</li>\n<li>sub(r, s, t) 替换一次 将t中的r替换成s。 t缺省为$0</li>\n<li>gsub(r, s, t) 全部替换</li>\n<li>substr(s, i, n) 返回s第i个字符后的n个字符,n省略,则返回i后所有字符。(包含第i个)</li>\n<li>tolower(s) 将s中全部字符换为小写</li>\n<li>toupper(s)</li>\n</ul>\n</li>\n</ul>\n</li>\n<li>输出\n<ul>\n<li>print 将$0输出</li>\n<li>print expr1,expr2,... 输出 expr1 OFS expr2 OFS...ORS</li>\n<li>printf format, expr-list</li>\n<li>getline 略</li>\n</ul>\n</li>\n</ul>\n<hr>\n<p>示例</p>\n<ol>\n<li>模拟cat {print}</li>\n<li>模拟wc {chars += length($0) + 1 # add 1 for the '\\n'\nwords += NF}\nEND {print NR, words, chars}</li>\n<li>排序文件</li>\n</ol>\n<div><pre><code>{line[NR] = $0 ""} # 确保每一行都是string类型\nEND {\n isort(line, NR)\n for (i =1; i <=NR; i++) print line[i] \n}\n \nfunction isort(A, n, i, j, hold)\n{\n for (i = 2; i <= n; i++)\n {\n hold = A[j = i]\n while (A[j-1] > hold)\n { j--; A[j+1] = A[j]}\n A[j] = hold\n }\n}\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br></div></div><h3 id=\"nl-加行号显示\"> nl--加行号显示</h3>\n<p><code>nl [opts] [file]</code> file为<code>-</code>或未指定时,从STDIN读入。</p>\n<h3 id=\"文件夹导航\"> 文件夹导航</h3>\n<p>fasd和autojump这两个工具来查找最常用或最近使用的文件和目录。</p>\n<p>Fasd 基于frecency对文件和文件排序,也就是说它会同时针对频率(frequency )和时效( recency)进行排序。默认情况下,fasd使用命令 z 帮助我们快速切换到最常访问的目录。例如, 如果您经常访问/home/user/files/cool_project 目录,那么可以直接使用 z cool 跳转到该目录。对于 autojump,则使用j cool代替即可。</p>\n<h2 id=\"cheatsheet\"> cheatsheet</h2>\n<table>\n<thead>\n<tr>\n<th style=\"text-align:center\">命令</th>\n<th style=\"text-align:left\">作用</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style=\"text-align:center\">man</td>\n<td style=\"text-align:left\">用户手册</td>\n</tr>\n<tr>\n<td style=\"text-align:center\">cd</td>\n<td style=\"text-align:left\">切换目录</td>\n</tr>\n<tr>\n<td style=\"text-align:center\">pwd</td>\n<td style=\"text-align:left\">当前工作目录</td>\n</tr>\n<tr>\n<td style=\"text-align:center\">which</td>\n<td style=\"text-align:left\">确定某个程序名代表的是哪个具体程序</td>\n</tr>\n<tr>\n<td style=\"text-align:center\">ls</td>\n<td style=\"text-align:left\">显示目录包含的文件</td>\n</tr>\n<tr>\n<td style=\"text-align:center\">mv</td>\n<td style=\"text-align:left\">移动或重命名文件</td>\n</tr>\n<tr>\n<td style=\"text-align:center\">cp</td>\n<td style=\"text-align:left\">复制文件</td>\n</tr>\n<tr>\n<td style=\"text-align:center\">rm</td>\n<td style=\"text-align:left\">删除文件</td>\n</tr>\n<tr>\n<td style=\"text-align:center\">mkdir</td>\n<td style=\"text-align:left\">新建文件夹</td>\n</tr>\n<tr>\n<td style=\"text-align:center\">touch</td>\n<td style=\"text-align:left\">创建文件或修改文件时间</td>\n</tr>\n<tr>\n<td style=\"text-align:center\">chmod [mode] FILE</td>\n<td style=\"text-align:left\">修改文件权限</td>\n</tr>\n<tr>\n<td style=\"text-align:center\">grep [pattern] FILE</td>\n<td style=\"text-align:left\">打印pattern匹配成功的行</td>\n</tr>\n<tr>\n<td style=\"text-align:center\"></td>\n<td style=\"text-align:left\"></td>\n</tr>\n<tr>\n<td style=\"text-align:center\"></td>\n<td style=\"text-align:left\"></td>\n</tr>\n</tbody>\n</table>\n",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2022-04-02T08:34:13.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "vim",
"url": "https://kigane.github.io/note/cs/vim/",
"id": "https://kigane.github.io/note/cs/vim/",
"content_html": "<h2 id=\"vim基础\"> Vim基础</h2>\n<h3 id=\"资料\"> 资料</h3>\n<p><a href=\"https://missing-semester-cn.github.io/2020/editors/\" target=\"_blank\" rel=\"noopener noreferrer\">学习Vim</a><br>\nvimtutor : Vim安装时自带的教程<br>\n<a href=\"https://vim-adventures.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Vim Adventures</a><br>\n<a href=\"https://vimways.org/2019/\" target=\"_blank\" rel=\"noopener noreferrer\">Vim 小技巧</a></p>\n<p><img src=\"/assets/img/vim-keyboard.png\" alt=\"Vim 键盘图\" /></p>\n<h3 id=\"改键\"> 改键</h3>\n<p>ubantu</p>\n<ul>\n<li>setxkbmap -option caps:escape capslock映射为esc</li>\n<li>setxkbmap -option ctrl:nocaps capslock映射为ctrl</li>\n</ul>\n<h3 id=\"模式\"> 模式</h3>\n<p>Vim的设计以大多数时间都花在阅读、浏览和进行少量编辑改动为基础,因此它具有多种操作模式:</p>\n<ul>\n<li>正常模式:在文件中四处移动光标进行修改</li>\n<li>插入模式:插入文本</li>\n<li>替换模式:替换文本</li>\n<li>可视化(一般,行,块)模式:选中文本块</li>\n<li>命令模式:用于执行命令</li>\n</ul>\n<p>在不同的操作模式下,键盘敲击的含义也不同。</p>\n<p>按下 <code>ESC</code> 从任何其他模式返回正常模式。 在正常模式,键入 <code>i</code> 进入插入 模式, <code>R</code> 进入替换模式, <code>v</code> 进入可视(一般)模式, <code>V</code> 进入可视(行)模式, <code>Ctrl-V</code>, 有时也写作 <code>^V</code>进入可视(块)模式, <code>:</code> 进入命令模式。</p>\n<p>Vim 最重要的设计思想是 Vim 的界面本身是一个程序语言。键入操作本身是命令, 这些命令可以组合使用。这使得移动和编辑更加高效,特别是一旦形成肌肉记忆。</p>\n<h3 id=\"缓存-标签页-窗口\"> 缓存,标签页,窗口</h3>\n<p>Vim 会维护一系列打开的文件,称为“缓存”。一个 Vim 会话包含一系列标签页,每个标签页包含一系列窗口(分隔面板)。每个窗口显示一个缓存(可以是同一个缓存)。</p>\n<p>Vim 默认打开一个标签页,这个标签也包含一个窗口。</p>\n<h3 id=\"基操\"> 基操</h3>\n<ul>\n<li><code>vim $(fzf)</code></li>\n<li><code>:q</code>退出(关闭窗口)</li>\n<li><code>:w</code>保存当前文件(写)</li>\n<li><code>:w FILE</code> 另存为</li>\n<li><code>:wq</code>保存然后退出</li>\n<li><code>:e {文件名}</code> 打开要编辑的文件</li>\n<li><code>:ls</code>显示打开的缓存</li>\n<li><code>:help {标题}</code> 打开帮助文档\n<ul>\n<li><code>:help :w</code>打开 <code>:w</code>命令的帮助文档</li>\n<li><code>:help w</code>打开 <code>w</code> 移动的帮助文档</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"移动\"> 移动</h3>\n<p>正常模式下\n在 Vim 里面移动也被称为 “名词”, 因为它们指向文字块。</p>\n<ul>\n<li>基本移动: hjkl (左, 下, 上, 右)</li>\n<li>单词word: w (下一个词的首字母), b (当前词首), e (当前词尾) ge(上一个单词词尾)</li>\n<li>广义单词WORD: W, B, E, gE (广义单词为空格之间的所有字符,单词为数字字母下划线的序列)</li>\n<li>段落: <code>{</code>, <code>}</code>。段落是由空行隔开的代码段。</li>\n<li>行: 0 (行初), ^ | _ (第一个非空格字符), $ (行尾)</li>\n<li>屏幕: H (屏幕首行), M (屏幕中间), L (屏幕底部)</li>\n<li>文件: gg (文件头), G (文件尾)</li>\n<li>行数: <code>:{num}<CR></code> 或者 <code>{num}G</code> 转到第num行</li>\n<li>ctrl+f 向下翻页</li>\n<li>ctrl+b 向上翻页</li>\n<li>ctrl+d 向下翻半页</li>\n<li>ctrl+u 向上翻半页</li>\n<li><strong>杂项: % (找到配对,比如各种括号)</strong></li>\n<li>查找: f{字符}, t{字符}, F{字符}, T{字符}\n<ul>\n<li>查找(find)/到(until) 向前/向后 在本行的{字符}</li>\n<li><code>,</code> / <code>;</code> 用于导航匹配</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"编辑\"> 编辑</h3>\n<p>Vim 的编辑命令也被称为 “动词”, 因为动词可以施动于名词。</p>\n<ul>\n<li>O / o 在之上/之下插入行,并进入编辑模式</li>\n<li>r{char}: 替换一个字符</li>\n<li>R: 进入替换模式</li>\n<li>d{移动命令} 删除 {移动命令}\n<ul>\n<li>例如, dw 删除词, d$ 删除到行尾, d0 删除到行头。</li>\n</ul>\n</li>\n<li>c{移动命令} 改变 {移动命令}\n<ul>\n<li>例如, cw 改变词 相当于 d{移动命令} 再 i</li>\n</ul>\n</li>\n<li>cc 删除该行并在该行进入插入模式</li>\n<li>x 删除字符(等同于 dl)</li>\n<li>s 替换字符(等同于 xi)</li>\n<li>可视化模式 + 操作\n<ul>\n<li>选中文字, d 删除 或者 c 改变</li>\n</ul>\n</li>\n<li>u 撤销, Ctrl-r 重做</li>\n<li>y 复制 (复制大段需要进入VISUAL模式)</li>\n<li>yy 复制一行</li>\n<li>p 粘贴 (也可将刚刚删除的内容(储存在Vim寄存器中)粘贴到光标后)</li>\n</ul>\n<h3 id=\"计数\"> 计数</h3>\n<p>你可以用一个计数来结合“名词”和“动词”,这会执行指定操作若干次。</p>\n<ul>\n<li>3w 向前移动三个词</li>\n<li>5j 向下移动5行</li>\n<li>7dw 删除7个词</li>\n</ul>\n<h3 id=\"搜索\"> 搜索</h3>\n<ul>\n<li>搜索: <code>/{正则表达式}</code>, <code>n</code> / <code>N</code> 用于导航匹配,向后/向前\n<ul>\n<li>set hls(earch) 设置搜索高亮</li>\n<li>:noh 清楚高亮显示</li>\n</ul>\n</li>\n<li>:grep 系统的grep</li>\n<li>:vimgrep pattern path 在目录中搜索pattern。<code>**</code>表示递归搜索。 <code>**/*.c</code>搜索具体文件类型。\n<ul>\n<li>执行后,vim会跳转到第一个匹配项处</li>\n<li>:cn, :cp 逐个浏览匹配项</li>\n<li>:copen 显示匹配列表</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"修饰语\"> 修饰语</h3>\n<p>你可以用修饰语改变“名词”的意义。修饰语有 i,表示“内部”或者“在内“,和 a, 表示”周围“。</p>\n<ul>\n<li>ci( 改变当前括号内的内容</li>\n<li>ci[ 改变当前方括号内的内容</li>\n<li>da' 删除一个单引号字符串, 包括周围的单引号</li>\n</ul>\n<h3 id=\"扩展vim\"> 扩展Vim</h3>\n<p>Vim 有很多扩展插件。从 Vim 8.0 开始,你可以使用内置的插件管理系统。只需要创建一个 ~/.vim/pack/vendor/start/ 的文件夹,然后把插件放到这里。</p>\n<p><a href=\"https://vimawesome.com/\" target=\"_blank\" rel=\"noopener noreferrer\">vimawesome</a></p>\n<h2 id=\"practice-vim\"> practice vim</h2>\n<ul>\n<li>\n<p><code>.</code> 用于重复一次修改。(从进入插入模式开始到按ESC退出为止的所有操作)。</p>\n</li>\n<li>\n<p><code>u</code> 撤销一次修改。输入间隔的停顿时间较长时,就退出吧。可以控制撤销粒度。</p>\n</li>\n<li>\n<p>在插入模式中使用Up,Down等光标键,将会产生一个新的撤销块。可以认为是先退回普通模式,在执行jklh。也会影响<code>.</code>命令。</p>\n</li>\n<li>\n<p><code>f{char}</code> 用于在一行内查找下一个指定字符,并立即将光标移动到那里。</p>\n</li>\n<li>\n<p><code>;</code> 会重复向后查找上次f命令查找的字符</p>\n</li>\n<li>\n<p><code>,</code> 和<code>;</code>方向相反</p>\n</li>\n<li>\n<p>j,k,0,$用于操作实际行,但加上g前缀后操作屏幕行。</p>\n</li>\n<li>\n<p><code>:s/target/replacement</code> 使用<code>&</code>重复。</p>\n</li>\n<li>\n<p><code>*</code> 查找当前光标下的单词。光标会跳到下一个匹配项。</p>\n</li>\n<li>\n<p><code><C-h></code> 删除前一个字符,同BS</p>\n</li>\n<li>\n<p><code><C-w></code> 删除前一个单词</p>\n</li>\n<li>\n<p><code><C-u></code> 删除至行首。这三个命令比较通用。</p>\n</li>\n<li>\n<p><code><C-o></code> 进入插入-普通模式,执行一个命令后返回插入模式。</p>\n</li>\n<li>\n<p><code><C-r>=</code> 使用表达式寄存器计算一个表达式,并插入结果。</p>\n</li>\n<li>\n<p><code><C-v>{123}</code> 插入三位数ASCII码对应的字符</p>\n</li>\n<li>\n<p><code><C-v>u{1234}</code> 插入Unicode码对应的字符</p>\n</li>\n<li>\n<p><code><C-k>{char1}{char2}</code> 插入组合字符表示的单个字符</p>\n</li>\n<li>\n<p><code>ga</code> 查看光标所在字符的 ASCII码,Unicode码,字符组合(digr)</p>\n</li>\n<li>\n<p><code><C-g></code> 在可视模式和选择模式中切换</p>\n</li>\n<li>\n<p><code>o</code> 在可视模式中可以切换固定的端点。</p>\n</li>\n<li>\n<p><code>gv</code> 重新选择上次的高亮选区</p>\n</li>\n<li>\n<p><code>:xx</code> 由于历史原因,被称为Ex命令</p>\n</li>\n<li>\n<p>[range]格式为{start},{end}。</p>\n<ul>\n<li><code>.</code>表示当前行</li>\n<li><code>%</code>表示所有行</li>\n<li><code>'<,'></code>表示当前高亮选区</li>\n<li>start,end也可以是模式。如<code>:/<html>/,/<\\/html>/p</code>打印html标签所在行及之间的所有行。</li>\n<li>start,end也可以加上偏移。如<code>:/<html>/+1,/<\\/html>/-1p</code>打印html标签之间的所有行(不包括所在行)。</li>\n<li>0 表示文件第一行上方的虚拟行</li>\n</ul>\n</li>\n<li>\n<p>:{num} num被解析为地址,会跳转到指定行。</p>\n</li>\n<li>\n<p>:[range]normal {commands} 对指定范围内的所有行执行相同的命令</p>\n</li>\n<li>\n<p>:copy | :co | :t 复制指定行并粘贴到当前行下方。不会使用寄存器。</p>\n</li>\n<li>\n<p><code>@:</code> 重复上次Ex命令</p>\n</li>\n<li>\n<p><code>q:</code> 打开命令行窗口,记录了Ex命令历史,可以轻松重复以前的Ex命令</p>\n</li>\n<li>\n<p>:shell 打开shell。exit回到vim。</p>\n</li>\n<li>\n<p><code>z<CR></code> 将光标行放到屏幕顶端</p>\n</li>\n<li>\n<p><code>zz</code> 将光标行放到屏幕中间</p>\n</li>\n</ul>\n<h3 id=\"text-object\"> Text Object</h3>\n<ul>\n<li>aw: a word</li>\n<li>iw: inner word</li>\n<li>aW: a WORD</li>\n<li>as: a setence</li>\n<li>is</li>\n<li>ap: a paragraph</li>\n<li>ip</li>\n<li><code>a[ | a]</code> a [] block, <code>"[content]"->"[content]"</code></li>\n<li><code>i[ | i]</code> inner [] block, <code>"[content]"->"content"</code></li>\n<li>a,i作为前缀省略</li>\n<li><code>(,),b</code></li>\n<li><code><,></code></li>\n<li><code>t</code> tag block <code><a>content</a></code></li>\n<li><code>{,},B</code></li>\n<li>"</li>\n<li>'</li>\n<li>`</li>\n</ul>\n<h2 id=\"vim进阶\"> Vim进阶</h2>\n<h3 id=\"vim生成的文件\"> vim生成的文件</h3>\n<ul>\n<li>backup 是普通的加~备份.</li>\n<li>undofile 加后缀un~。用于在下次打开文件时也可以执行撤销命令撤销上次的修改。</li>\n<li>writebackup 是防止灾难时的加~备份, 特点是文件正常写入之后就会自动删除. 也就是说如果你的文件有正常写入(不正常情况通常发生在磁盘快要满的时候), 你是很难见到这个文件出现的.</li>\n<li>swapfile 也是防止灾难, 不过是缓冲区的备份, 也就是你正在编辑的内容. 如果你在编辑的时候电脑断电或者Vim发生异常退出, 而你没有保存, 你可以从.file.txt.swp里恢复这个文件.</li>\n</ul>\n<p>禁止备份功能<br>\n在~/.vimrc中添加</p>\n<div><pre><code>set nobackup\nset noundofile\nset nowritebackup\nset noswapfile\n\" 备份到某个目录 // 表示会保存目录信息到文件名中\nset directory=$HOME/.vim/tmp//\nset backupdir=$HOME/.vim/tmp//\nset undodir=$HOME/.vim/tmp//\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br></div></div><h3 id=\"搜索和替换\"> 搜索和替换</h3>\n<ul>\n<li><code>:s/foo/bar/g</code> 当前行的foo替换成bar</li>\n<li><code>:%s/foo/bar/gc</code> 所有行的foo替换成bar,且需要一个个确认(<code>c</code>)</li>\n<li><code>:5,12s/foo/bar/g</code> 5-12行的foo替换成bar</li>\n</ul>\n<h3 id=\"宏\"> 宏</h3>\n<ul>\n<li>q{字符} 来开始在寄存器{字符}中录制宏</li>\n<li>q停止录制</li>\n<li>@{字符} 重放宏</li>\n<li>宏的执行遇错误会停止</li>\n<li>{计数}@{字符}执行一个宏{计数}次</li>\n</ul>\n<h3 id=\"在vim中执行外部命令\"> 在Vim中执行外部命令</h3>\n<p><code>:!external command<CR></code>: <code>!</code>和回车之间的都是被当做外部命令执行。</p>\n<h3 id=\"保存选中部分-取回内容\"> 保存选中部分,取回内容</h3>\n<p>按v进入可视化模式,移动以选择内容。然后<code>:w FILE</code>即可。<br>\n注意按<code>:</code>后底部出现了<code>:'<,'></code>。</p>\n<p><code>:r FILE</code> 将FILE内容放入光标下一行<br>\n<code>:r !cmd</code> 将cmd的输出放入光标下一行</p>\n<h3 id=\"设置\"> 设置</h3>\n<p><code>:set xxx</code></p>\n<ul>\n<li>ic : 搜索时忽略大小写</li>\n<li>hls : 高亮匹配的字符</li>\n<li>noxxx : 取消xxx设置</li>\n</ul>\n<h3 id=\"自动补全\"> 自动补全</h3>\n<p>Vim命令也有自动补全,<code>tab</code>补全唯一的一个,<code>ctrl-d</code>显示所有候选。</p>\n<h3 id=\"代码折叠\"> 代码折叠</h3>\n<ul>\n<li>在.vimrc中设置 set foldmethod=syntax|indent</li>\n<li>zc 折叠</li>\n<li>zo 展开</li>\n<li>za 折叠展开切换</li>\n<li>zR 打开所有折叠</li>\n<li>zM 关闭所有折叠</li>\n</ul>\n<h3 id=\"命令重映射\"> 命令重映射</h3>\n<ul>\n<li>:map 递归映射</li>\n<li>:unmap 取消映射</li>\n<li>:mapclear 清除所有映射</li>\n<li>:noremap 非递归映射</li>\n<li>前缀\n<ul>\n<li>nore: 非递归</li>\n<li>n: 在普通模式下生效</li>\n<li>v | x: VISUAL模式</li>\n<li>i: INSERT模式</li>\n<li>c: 命令模式</li>\n</ul>\n</li>\n<li>键表\n<ul>\n<li><code><k0></code> - <code><k9></code> 小键盘 0 到 9</li>\n<li><code><S-...></code> Shift+键</li>\n<li><code><C-...></code> Control+键</li>\n<li><code><M-...></code> Alt+键 或 meta+键</li>\n<li><code><A-...></code> 同 <code><M-...></code></li>\n<li><code><Esc></code> Escape</li>\n<li><code><Up></code> 光标上移键</li>\n<li><code><Space></code> 空格</li>\n<li><code><Tab></code> Tab</li>\n<li><code><CR></code> 等于<code><Enter></code></li>\n<li><code><f1>~<f12></code></li>\n<li><code><home> <insert> <del> <end></code></li>\n<li><code><nop></code> 无操作</li>\n</ul>\n</li>\n</ul>\n<div><pre><code>\" 在插入模式下加入一对引号或括号\ninoremap ' ''<span><span><span><</span>esc</span><span>></span></span>i\ninoremap \" \"\"<span><span><span><</span>esc</span><span>></span></span>i\ninoremap ( ()<span><span><span><</span>esc</span><span>></span></span>i\ninoremap [ []<span><span><span><</span>esc</span><span>></span></span>i\ninoremap { {}<span><span><span><</span>esc</span><span>></span></span>i\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br></div></div><h3 id=\"先导键-leader-key\"> 先导键(leader key)</h3>\n<p>本质是用户或插件自定义的快捷键的命令空间。默认的先导键为<code>\\</code>。</p>\n<div><pre><code>\" 需要放在.vimrc的顶部\n\" let mapleader = ','\nlet mapleader = \"\\<span><span><span><</span>space</span><span>></span></span>\" \" mapleader变量中不含特殊字符,所以转义字符是必要的\n\" 使用先导键\nnnoremap <span><span><span><</span>leader</span><span>></span></span>w :w<span><span><span><</span>cr</span><span>></span></span> \" 用leader-w保存文件 \n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br></div></div><h3 id=\"vim寄存器\"> vim寄存器</h3>\n<p>使用<code>"</code>访问寄存器</p>\n<ul>\n<li>无名寄存器 复制粘贴默认使用的寄存器。可用<code>"</code>访问</li>\n<li>a~z寄存器 用于手动复制数据。例如,将一个单词复制到a寄存器,<code>"ayw</code>,粘贴<code>"ap</code></li>\n<li>0~9寄存器 最近10次删除的历史记录</li>\n<li>只读寄存器\n<ul>\n<li><code>%</code> 当前文件名</li>\n<li><code>#</code> 文件所在目录</li>\n<li><code>.</code> 最后插入的文本</li>\n<li><code>:</code> 最后执行的命令</li>\n</ul>\n</li>\n<li><code>*</code> 系统的主粘贴板</li>\n<li><code>+</code> Linux中<code><C-c|v></code>使用的粘贴板</li>\n<li>使用:reg rid 访问寄存器内容</li>\n</ul>\n<h2 id=\"多文件-多窗口\"> 多文件,多窗口</h2>\n<ul>\n<li>:help window-moving</li>\n<li>:help window-resize</li>\n<li>:ls 显示所有buffer。 %当前焦点所在的文件。#轮换文件,按<code>C-^</code>可以切换到该文件。a活动窗口。</li>\n<li>:w! 将磁盘中的文件读入缓冲区。即回滚所有修改。</li>\n<li>:q! 不修改,关闭所有窗口。</li>\n<li><code><C-w><C-w></code> 按住ctrl,连续按ww可以循环切换窗口。</li>\n<li>:pwd 查看vim当前工作目录。默认是在shell中打开vim时,shell的工作目录。</li>\n<li>:lcd {path} 设置当前窗口的本地工作目录</li>\n<li>:windo lcd {path} 为所有窗口设置本地工作目录</li>\n<li>% 在文件路径中表示当前缓冲区的完整文件路径。按tab展开。</li>\n<li>%:h 当前缓冲区的完整文件路径,去除了文件名。</li>\n<li>重映射 <code>cnoremap <expr>%% getcmdtype() == ':' ? expand('%:h').'/' : '%%'</code></li>\n</ul>\n<p>在不同文件buffer间切换</p>\n<ul>\n<li>:bn 下一个文件buffer</li>\n<li>:bp 上一个文件buffer</li>\n<li>:ls 输出buffer列表 %表示当前窗口的缓冲区,a表示活动的缓冲区(可见)。</li>\n<li>:b[num] 切换到第num个buffer</li>\n<li>:b{bufname} 使用文件名切换</li>\n<li>:bd 删除缓冲区</li>\n<li>:e file 打开文件</li>\n<li>:jumps 显示vim的跳转列表。\n<ul>\n<li><code><C-o></code> 在跳转列表中后退</li>\n<li><code><C-i></code> 前进</li>\n</ul>\n</li>\n<li>:changes 显示vim的修改记录表。\n<ul>\n<li>g; 跳转到上一次修改的地方</li>\n<li>g, 下</li>\n</ul>\n</li>\n<li>:find 在当前path下查找文件或文件夹。不支持模糊。不加**则不会进入子文件夹</li>\n<li>set path+=...</li>\n<li>:set path? 查看path的值</li>\n<li>gf 跳转到光标下的文件</li>\n</ul>\n<p>用 <code>:sp</code> / <code>:vsp | :vs</code> 来分割窗口<br>\n同一个缓存可以在多个窗口中显示。</p>\n<p>切换</p>\n<ul>\n<li><code><C-W> j</code> 下一个</li>\n<li><code><C-W> k</code> 上一个</li>\n<li><code><C-W> h</code> 左边一个</li>\n<li><code><C-W> l</code> 右边一个</li>\n<li><code><C-W> w</code> | <code><C-W> <C-w></code> 一个一个遍历窗口</li>\n<li><code><C-W> o</code> 只保留当前窗口</li>\n<li><code>:close</code> 关闭当前窗口,不会退出Vim</li>\n</ul>\n<p>标签页</p>\n<ul>\n<li>:tabnew [file] 打开新标签页</li>\n<li>:tabedit [file] 打开新标签页</li>\n<li>gt | :tabnext</li>\n<li>gT | :tabprevious</li>\n<li>Ngt 跳转到标签页N</li>\n<li>:tabclose</li>\n<li>:tabmove N 将当前标签页移动到第N个标签页之后</li>\n<li>:tabonly</li>\n<li><code><C-w> T</code> 将当前窗口移动到一个新标签页</li>\n</ul>\n<p>调整窗口大小</p>\n<ul>\n<li><code>[num]<C-W> +</code> 当前窗口增加一行</li>\n<li><code>[num]<C-W> -</code> 当前窗口减少一行</li>\n<li>:resize +n|-n 简写:res。垂直。</li>\n<li>:vertical resize +n|-n 简写:vert res。水平</li>\n<li><code><C-W> =</code> 将所有窗口宽高恢复一致。</li>\n</ul>\n<h2 id=\"会话-session\"> 会话(session)</h2>\n<ul>\n<li>保存 <code>:mksession ~/.vim/sessions/xxx.vim</code> | <code>:mks ~/.vim/sessions/xxx.vim</code></li>\n<li>恢复 <code>vim -S ~/.vim/sessions/xxx.vim</code></li>\n</ul>\n<h2 id=\"插件\"> 插件</h2>\n<p>:so % 在vim中直接source当前文件</p>\n<h3 id=\"vim-plug\"> vim-plug</h3>\n<ul>\n<li>下载文件 <code>curl -fLo ~/.vim/autoload/plug.vim --create-dirs https://raw.github.com/junegunn/vim-plug/master/plug.vim</code></li>\n<li>vim-plug初始化 在.vimrc中添加两个call。</li>\n<li>插件管理\n<ul>\n<li>添加插件 <code>Plug <username>/<repo></code></li>\n<li>:PlugInstall 安装添加的插件</li>\n<li>:PlugUpdate 更新插件</li>\n<li>:PlugUpgrade 更新vim-plug。需要source一下。</li>\n<li>:PlugClean 移除不用的插件</li>\n</ul>\n</li>\n<li>插件延迟加载\n<ul>\n<li>Plug 'scrooloose/nerdtree', {'on', : 'NERDTreeToggle'} 在执行'NERDTreeToggle'命令时再加载</li>\n<li>Plug 'junegunn/goyo.vim', {'for', : 'markdown'} 按文件类型加载</li>\n</ul>\n</li>\n</ul>\n<div><pre><code>call plug<span>#begin()</span>\nPlug <span>'mileszs/ack.vim'</span>\nPlug <span>'easymotion/vim-easymotion.vim'</span>\ncall plug<span>#end()</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br></div></div><h3 id=\"ctags\"> ctags</h3>\n<p><code>sudo apt-get install ctags</code></p>\n<h4 id=\"生成tags文件\"> 生成tags文件</h4>\n<p>在源码根目录下</p>\n<div><pre><code>ctags -R --exclude<span>=</span>.git --exclude<span>=</span>vendor/* --<span>..</span>.\n</code></pre>\n<div><span>1</span><br></div></div><p>这样就会生成一个tags文件,ctags需要这个文件实现跳转。<br>\n常用的选项可以写在 ~/.ctags 文件中</p>\n<div><pre><code>-R\n--exclude<span>=</span>.git\n--exclude<span>=</span>vendor/*\n<span>..</span>.\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br></div></div><p>自动生成ctags</p>\n<div><pre><code>autocmd BufWritePost *.c *.h *.cpp silent! !ctags -R &\n</code></pre>\n<div><span>1</span><br></div></div><h4 id=\"how-to-use\"> How to use</h4>\n<ul>\n<li>找一个指定的tag,并用Vim打开其定义位置,在shell中使用 <code>vim -t <tag></code>,也可以使用正则</li>\n<li>在Vim中\n<ul>\n<li><code>ctrl+]</code>(有多个tag时,会选一个直接进入) 或 <code>g]</code>(有多个会显示一个列表) 或 <code>:ta[g] ctrl+rw</code> 跳转到tag</li>\n<li><code>:ts tag_name</code> 列出所有tag_name匹配的tag</li>\n<li><code>:pts tag_name</code> <code>:ts</code>功能,但有预览</li>\n<li><code>ctrl+w}</code> 或 <code>:ptag ctrl+rw</code> 预览tag</li>\n<li><code>ctrl+wz</code> 或 <code>:pc</code> 关闭上面打开的预览</li>\n<li><code>:tn</code> 或 <code>:tp</code> 在多个匹配的tag上跳转</li>\n<li><code>ctrl+t</code> 向tag栈底跳一格(相当于返回上一个tag)</li>\n<li><code>:tags</code> 显示tag栈,当前活动的有 <code>></code> 标记</li>\n</ul>\n</li>\n<li>使用正则\n<ul>\n<li><code>:tag main</code> 直接跳转到 tag "main"</li>\n<li><code>:tag /^get</code> 直接跳转到以"get"开头的 tag</li>\n<li><code>:tag /Final$</code> 直接跳转到以"Final"结尾的 tag</li>\n<li><code>:tag /norm</code> 列出包含"norm"的 tag</li>\n<li><code>:tag /Final$\\C</code> 列出以"Final"结尾的 tag\nPS:记得要保持tags索引文件最新</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"youcompleteme\"> YouCompleteMe</h3>\n<p>需要安装cmake和llvm。因为该插件需要编译代码。 <code>sudo apt-get install cmake llvm</code></p>\n<div><pre><code>\" .vimrc中配置vim-plug\nlet g:plug_timeout = 300 \" 为插件增加超时时间\nPlug 'Valloric/YouCompleteMe', {'do': './install.py'}\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br></div></div><p>:source ~/.vimrc | PlugInstall</p>\n<h3 id=\"ack\"> ack</h3>\n<p>mileszs/ack.vim</p>\n<ul>\n<li>:Ack [opts] {pattern} [{dirs}]</li>\n<li>--cc为c,--rr为r。ack规定语言必须大于一个字符。</li>\n<li>在搜索结果中\n<ul>\n<li>? 命令帮助</li>\n<li>o 同 enter</li>\n<li>O 打开文件并关闭搜索窗口</li>\n<li>go 打开文件但焦点保持在搜索结果窗口</li>\n<li>q 退出</li>\n<li>v,h,t,V,H,T 小写打开并切换到,大写打开但焦点保持在搜索结果窗口</li>\n<li>gv 在右边打开一个窗口,焦点保持在搜索结果窗口</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"ctrlp\"> ctrlp</h3>\n<p>完整路径模糊查找。可查文件,buffer,最近使用文件(MRU), tag等等。</p>\n<ul>\n<li>c-p 打开查找面板</li>\n<li>c-d 选择文件查找|路径查找</li>\n<li>c-r 开关正则匹配</li>\n<li>c-f, c-b 在file查找 ,buffer查找 ,mru查找中切换。</li>\n<li>c-j, c-k 在查找结果中上下移动</li>\n<li>c-t 在新tab页中打开</li>\n<li>c-x,c-v 在新窗口中打开。(x-上下, v-左右)</li>\n<li>c-z 标记多个文件,c-o打开这些文件</li>\n<li>c-y 新建文件如果目录不存在,则创建</li>\n</ul>\n<h3 id=\"easy-motion\"> easy-motion</h3>\n<p>easymotion/vim-easymotion</p>\n<ul>\n<li>\\w 向后跳转到单词头</li>\n<li>\\b 向前跳转到单词头</li>\n<li>\\s 双向跳转到指定字符</li>\n<li>\\j 向下跳转到行首</li>\n<li>\\k 向上跳转到行首</li>\n</ul>\n<h3 id=\"vim-bookmark\"> vim-bookmark</h3>\n<table>\n<thead>\n<tr>\n<th>Action</th>\n<th>Shortcut</th>\n<th>Command</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>在当前行添加/删除书签</td>\n<td>mm</td>\n<td>:BookmarkToggle</td>\n</tr>\n<tr>\n<td>Add/edit/remove当前行的书签名</td>\n<td>mi</td>\n<td>:BookmarkAnnotate name</td>\n</tr>\n<tr>\n<td>显示所有书签</td>\n<td>ma</td>\n<td>:BookmarkShowAll</td>\n</tr>\n<tr>\n<td>清除当前buffer的所有书签</td>\n<td>mc</td>\n<td>:BookmarkClear</td>\n</tr>\n<tr>\n<td>清除所有buffer的所有书签</td>\n<td>mx</td>\n<td>:BookmarkClearAll</td>\n</tr>\n<tr>\n<td>下一个书签</td>\n<td>mn</td>\n<td>:BookmarkNext</td>\n</tr>\n<tr>\n<td>上一个书签</td>\n<td>mp</td>\n<td>:BookmarkPrev</td>\n</tr>\n<tr>\n<td>将当前行的书签向上移动n行</td>\n<td>nmkk</td>\n<td>:BookmarkMoveUp n</td>\n</tr>\n<tr>\n<td>将当前行的书签向下移动n行</td>\n<td>nmjj</td>\n<td>:BookmarkMoveDown n</td>\n</tr>\n<tr>\n<td>将当前行的书签移动到第n行</td>\n<td>nmg</td>\n<td>:BookmarkMoveToLine n</td>\n</tr>\n<tr>\n<td>将书签保存到文件中</td>\n<td></td>\n<td>:BookmarkSave FILE</td>\n</tr>\n<tr>\n<td>从文件中加载书签</td>\n<td></td>\n<td>:BookmarkLoad FILE</td>\n</tr>\n</tbody>\n</table>\n<h3 id=\"snippet\"> snippet</h3>\n<ul>\n<li>:UltiSnippetsEdit 编辑当前类型文件的snippets</li>\n<li>格式</li>\n</ul>\n<h2 id=\"vim-script\"> vim-script</h2>\n<h3 id=\"语法\"> 语法</h3>\n<ul>\n<li>set 为Vim内部选项赋值</li>\n<li>let 对非Vim内部变量</li>\n<li>没有bool类型,1为真,0为假</li>\n<li>作用域前缀\n<ul>\n<li>g: 全局作用域。默认</li>\n<li>v: Vim所定义的全局作用域</li>\n<li>I: 局部作用域</li>\n<li>b,w,t: 当前缓冲区,窗口,标签页</li>\n<li>s: :source'd执行的Vim脚本中的局部文件作用域</li>\n<li>a: 函数的参数</li>\n</ul>\n</li>\n<li>echom 输出,可以用:message查看输出历史</li>\n<li>条件表达式\n<ul>\n<li>if expr</li>\n<li>else if expr</li>\n<li>else</li>\n<li>endif</li>\n<li>(expr ? true : false)</li>\n</ul>\n</li>\n<li>文本比较\n<ul>\n<li>== 文本比较</li>\n<li>=~ 正则匹配</li>\n<li>!~ 正则不匹配</li>\n<li>后缀?|#表示忽略|考虑大小写</li>\n<li>无后缀是否忽略取决于Vim的内置选项ignorecase</li>\n</ul>\n</li>\n<li>函数调用:如果是单独调用,必须在前面加个call。在表达式中,则不必。</li>\n<li>list:类似python的list。add,insert,remove,sort,extend,index,empty,len,count</li>\n<li>dict: 代码跨多行时,要在行尾加<code>\\</code>. :help dict</li>\n<li>循环\n<ul>\n<li>for expr in exprs</li>\n<li>endfor</li>\n<li>while expr</li>\n<li>endwhile</li>\n</ul>\n</li>\n<li>函数\n<ul>\n<li>function! Funcname() 函数名首字母必须大写。!防止多次定义</li>\n<li>endfunction</li>\n</ul>\n</li>\n<li>和Vim交互\n<ul>\n<li>execute 将参数解析为Vim命令并执行</li>\n<li>normal 执行按键序列</li>\n<li>silent 隐藏其他命令的输出</li>\n<li>has 检查Vim是否支持某个功能 :help feature-list</li>\n<li>confirm</li>\n<li>input</li>\n</ul>\n</li>\n<li>:help eval</li>\n</ul>\n",
"image": "https://kigane.github.io/assets/img/vim-keyboard.png",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": [
"使用指南"
]
},
{
"title": "JS 小知识",
"url": "https://kigane.github.io/note/js/",
"id": "https://kigane.github.io/note/js/",
"content_html": "<h2 id=\"箭头函数\"> 箭头函数</h2>\n<p>类似 lambda 表达式。</p>\n<div><pre><code><span>(</span><span>params</span><span>)</span><span>=></span><span>{</span>\n <span>// body...</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br></div></div><p>需要注意的是,箭头函数没有 this, 如果在箭头函数内使用了 this,会导致 this 作为变量一直向上级词法作用域查找,直至找到为止。这经常会导致错误。</p>\n<h2 id=\"提升-hoisting\"> 提升(Hoisting)</h2>\n<p>JavaScript引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升(hoisting)。函数声明也一样。类似 C++ 中 static 的工作方式。</p>\n<h2 id=\"in\"> in</h2>\n<ul>\n<li>在单独使用时,in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中</li>\n<li>在使用for-in循环时,返回的是所有能够通过对象访问的、可枚举的(enumerated)属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性。屏蔽了原型中不可枚举属性(即将<code>[[Enumerable]]</code>标记为false的属性)的实例属性也会在for-in循环中返回,因为根据规定,所有开发人员定义的属性都是可枚举的。</li>\n</ul>\n<h2 id=\"exports-module-exports\"> exports&module.exports</h2>\n<p>模块是自包含的功能单元,可以在项目中共享和重复使用。\n由于JavaScript最初没有模块的概念,随着时间的推移出现了各种竞争格式。Node.js中使用的是CommonJS格式。其他格式(<a href=\"https://www.jvandemo.com/a-10-minute-primer-to-javascript-modules-module-formats-module-loaders-and-module-bundlers/\" target=\"_blank\" rel=\"noopener noreferrer\">可以参考这里</a>)。</p>\n<p>Node.js 内置了许多模块,例如</p>\n<div><pre><code><span>const</span> fs <span>=</span> <span>require</span><span>(</span><span>'fs'</span><span>)</span><span>;</span>\n<span>const</span> folderPath <span>=</span> <span>'./'</span><span>;</span>\n\nfs<span>.</span><span>readdir</span><span>(</span>folderPath<span>,</span> <span>(</span><span>err<span>,</span> files</span><span>)</span> <span>=></span> <span>{</span>\n files<span>.</span><span>forEach</span><span>(</span><span>file</span> <span>=></span> <span>{</span>\n console<span>.</span><span>log</span><span>(</span>file<span>)</span><span>;</span>\n <span>}</span><span>)</span><span>;</span>\n<span>}</span><span>)</span><span>;</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br></div></div><p>更多<a href=\"https://www.w3schools.com/nodejs/ref_modules.asp\" target=\"_blank\" rel=\"noopener noreferrer\">可以参考这里</a></p>\n<h3 id=\"创建和导出模块\"> 创建和导出模块</h3>\n<div><pre><code><span>// in ./user.js</span>\n<span>const</span> <span>getName</span> <span>=</span> <span>(</span><span>)</span> <span>=></span> <span>{</span>\n <span>return</span> <span>'Zrt'</span><span>;</span>\n<span>}</span><span>;</span>\n\nexports<span>.</span>getName <span>=</span> getName<span>;</span>\n\n<span>// in ./index.js</span>\n<span>const</span> user <span>=</span> <span>require</span><span>(</span><span>'./user'</span><span>)</span><span>;</span> <span>// .js 很显然,可以省略</span>\nconsole<span>.</span><span>log</span><span>(</span><span><span>`</span><span>User: </span><span><span>${</span>user<span>.</span><span>getName</span><span>(</span><span>)</span><span>}</span></span><span>`</span></span><span>)</span><span>;</span> <span>// User: Zrt</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br></div></div><h3 id=\"导出多个方法和值\"> 导出多个方法和值</h3>\n<div><pre><code><span>// in ./user.js</span>\n<span>const</span> <span>getName</span> <span>=</span> <span>(</span><span>)</span> <span>=></span> <span>{</span>\n <span>return</span> <span>'Zrt'</span><span>;</span>\n<span>}</span><span>;</span>\n\n<span>const</span> <span>getLocation</span> <span>=</span> <span>(</span><span>)</span> <span>=></span> <span>{</span>\n <span>return</span> <span>'dzxx'</span><span>;</span>\n<span>}</span><span>;</span>\n\n<span>const</span> dateOfBirth <span>=</span> <span>'9.9.1999'</span><span>;</span>\n\nexports<span>.</span>getName <span>=</span> getName<span>;</span>\nexports<span>.</span>getLocation <span>=</span> getLocation<span>;</span>\nexports<span>.</span>dob <span>=</span> dateOfBirth<span>;</span>\n\n<span>// in ./index.js</span>\n<span>const</span> user <span>=</span> <span>require</span><span>(</span><span>'./user'</span><span>)</span><span>;</span> <span>// .js 很显然,可以省略</span>\nconsole<span>.</span><span>log</span><span>(</span>\n <span><span>`</span><span><span>${</span>user<span>.</span><span>getName</span><span>(</span><span>)</span><span>}</span></span><span> lives in </span><span><span>${</span>user<span>.</span><span>getLocation</span><span>(</span><span>)</span><span>}</span></span><span> and was born on </span><span><span>${</span>user<span>.</span>dob<span>}</span></span><span>.</span><span>`</span></span>\n<span>)</span><span>;</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br></div></div><h3 id=\"语法变体\"> 语法变体</h3>\n<div><pre><code><span>// ./user.js</span>\nexports<span>.</span><span>getName</span> <span>=</span> <span>(</span><span>)</span> <span>=></span> <span>{</span>\n <span>return</span> <span>'Jim'</span><span>;</span>\n<span>}</span><span>;</span>\n\nexports<span>.</span><span>getLocation</span> <span>=</span> <span>(</span><span>)</span> <span>=></span> <span>{</span>\n <span>return</span> <span>'Munich'</span><span>;</span>\n<span>}</span><span>;</span>\n\nexports<span>.</span>dob <span>=</span> <span>'12.01.1982'</span><span>;</span>\n\n<span>// ./index.js</span>\n<span>// ES6 语法 Destructuring Assignment</span>\n<span>const</span> <span>{</span> getName<span>,</span> dob <span>}</span> <span>=</span> <span>require</span><span>(</span><span>'./user'</span><span>)</span><span>;</span> \nconsole<span>.</span><span>log</span><span>(</span>\n <span><span>`</span><span><span>${</span><span>getName</span><span>(</span><span>)</span><span>}</span></span><span> was born on </span><span><span>${</span>dob<span>}</span></span><span>.</span><span>`</span></span>\n<span>)</span><span>;</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br></div></div><h3 id=\"导出默认值\"> 导出默认值</h3>\n<p>如果一个模块只想导出一个东西,module.exports更常用。</p>\n<div><pre><code><span>// ./user.js</span>\n<span>class</span> <span>User</span> <span>{</span>\n <span>constructor</span><span>(</span><span>name<span>,</span> age<span>,</span> email</span><span>)</span> <span>{</span>\n <span>this</span><span>.</span>name <span>=</span> name<span>;</span>\n <span>this</span><span>.</span>age <span>=</span> age<span>;</span>\n <span>this</span><span>.</span>email <span>=</span> email<span>;</span>\n <span>}</span>\n\n <span>getUserStats</span><span>(</span><span>)</span> <span>{</span>\n <span>return</span> <span><span>`</span><span>\n Name: </span><span><span>${</span><span>this</span><span>.</span>name<span>}</span></span><span>\n Age: </span><span><span>${</span><span>this</span><span>.</span>age<span>}</span></span><span>\n Email: </span><span><span>${</span><span>this</span><span>.</span>email<span>}</span></span><span>\n </span><span>`</span></span><span>;</span>\n <span>}</span>\n<span>}</span>\n\nmodule<span>.</span>exports <span>=</span> User<span>;</span>\n\n<span>// ./index.js</span>\n<span>const</span> User <span>=</span> <span>require</span><span>(</span><span>'./user'</span><span>)</span><span>;</span>\n<span>const</span> jim <span>=</span> <span>new</span> <span>User</span><span>(</span><span>'Jim'</span><span>,</span> <span>37</span><span>,</span> <span>'[email protected]'</span><span>)</span><span>;</span>\n\nconsole<span>.</span><span>log</span><span>(</span>jim<span>.</span><span>getUserStats</span><span>(</span><span>)</span><span>)</span><span>;</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br></div></div><h3 id=\"console-log-module\"> console.log(module)</h3>\n<p>console.log(module);结果可以看到,exports属性。</p>\n<div><pre><code>Module <span>{</span>\n id<span>:</span> <span>'.'</span><span>,</span>\n path<span>:</span> <span>'f:\\\\workspace\\\\MyBlogs\\\\docs\\\\guide'</span><span>,</span>\n exports<span>:</span> <span>{</span><span>}</span><span>,</span>\n parent<span>:</span> <span>null</span><span>,</span>\n filename<span>:</span> <span>'f:\\\\workspace\\\\MyBlogs\\\\docs\\\\guide\\\\index.js'</span><span>,</span>\n loaded<span>:</span> <span>false</span><span>,</span>\n children<span>:</span> <span>[</span>\n Module <span>{</span>\n id<span>:</span> <span>'f:\\\\workspace\\\\MyBlogs\\\\docs\\\\guide\\\\test.js'</span><span>,</span>\n path<span>:</span> <span>'f:\\\\workspace\\\\MyBlogs\\\\docs\\\\guide'</span><span>,</span>\n exports<span>:</span> <span>[</span>Object<span>]</span><span>,</span>\n parent<span>:</span> <span>[</span>Circular<span>]</span><span>,</span>\n filename<span>:</span> <span>'f:\\\\workspace\\\\MyBlogs\\\\docs\\\\guide\\\\test.js'</span><span>,</span>\n loaded<span>:</span> <span>true</span><span>,</span>\n children<span>:</span> <span>[</span><span>]</span><span>,</span>\n paths<span>:</span> <span>[</span>Array<span>]</span>\n <span>}</span>\n <span>]</span><span>,</span>\n paths<span>:</span> <span>[</span>\n <span>'f:\\\\workspace\\\\MyBlogs\\\\docs\\\\guide\\\\node_modules'</span><span>,</span>\n <span>'f:\\\\workspace\\\\MyBlogs\\\\docs\\\\node_modules'</span><span>,</span>\n <span>'f:\\\\workspace\\\\MyBlogs\\\\node_modules'</span><span>,</span>\n <span>'f:\\\\workspace\\\\node_modules'</span><span>,</span>\n <span>'f:\\\\node_modules'</span>\n <span>]</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br><span>25</span><br><span>26</span><br><span>27</span><br></div></div><h2 id=\"三个点\"> 三个点(<code>...</code>)</h2>\n<p>js中的<code>...</code>有两种意思。<br>\n在函数参数列表中--剩余参数</p>\n<div><pre><code><span>function</span> <span>myFunc</span><span>(</span><span>x<span>,</span> y<span>,</span> <span>...</span>args</span><span>)</span>\n<span>{</span>\n console<span>.</span><span>log</span><span>(</span>x<span>)</span><span>;</span>\n console<span>.</span><span>log</span><span>(</span>y<span>)</span><span>;</span>\n console<span>.</span><span>log</span><span>(</span>args<span>)</span><span>;</span>\n<span>}</span>\n\n<span>myFunc</span><span>(</span><span>1</span><span>,</span> <span>2</span><span>,</span> <span>3</span><span>,</span> <span>4</span><span>,</span> <span>5</span><span>,</span> <span>7</span><span>)</span><span>;</span> <span>// output: 1, 2, [3, 4, 5, 6]</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br></div></div><p>spread operator -- 展开操作符<br>\n展开可迭代对象的元素</p>\n<div><pre><code><span>const</span> featured <span>=</span> <span>[</span><span>'Deep Dish'</span><span>,</span> <span>'Pepperoni'</span><span>,</span> <span>'Hawaiian'</span><span>]</span><span>;</span>\n<span>const</span> specialty <span>=</span> <span>[</span><span>'Meatzza'</span><span>,</span> <span>'Spicy Mama'</span><span>,</span> <span>'Margherita'</span><span>]</span><span>;</span>\n\n<span>const</span> pizzas <span>=</span> <span>[</span><span>...</span>featured<span>,</span> <span>'veg pizza'</span><span>,</span> <span>...</span>specialty<span>]</span><span>;</span>\n\nconsole<span>.</span><span>log</span><span>(</span>pizzas<span>)</span><span>;</span> <span>// 'Deep Dish', 'Pepperoni', 'Hawaiian', 'veg pizza', 'Meatzza', 'Spicy Mama', 'Margherita'</span>\n\n<span>var</span> obj1 <span>=</span> <span>{</span> foo<span>:</span> <span>'bar'</span><span>,</span> x<span>:</span> <span>42</span> <span>}</span><span>;</span>\n<span>var</span> obj2 <span>=</span> <span>{</span> foo<span>:</span> <span>'baz'</span><span>,</span> y<span>:</span> <span>13</span> <span>}</span><span>;</span>\n\n<span>var</span> clonedObj <span>=</span> <span>{</span> <span>...</span>obj1 <span>}</span><span>;</span>\n<span>// Object { foo: \"bar\", x: 42 }</span>\n\n<span>var</span> mergedObj <span>=</span> <span>{</span> <span>...</span>obj1<span>,</span> <span>...</span>obj2 <span>}</span><span>;</span>\n<span>// Object { foo: \"baz\", x: 42, y: 13 }</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br></div></div><h2 id=\"caller-callee\"> caller & callee</h2>\n<ul>\n<li>JS中函数是对象,函数名是指针,函数参数用arguments数组保存。</li>\n<li>arguments.callee是一个指向拥有该arguments对象的函数的指针。通常用于解耦函数和函数名。</li>\n<li>functionName.caller中保存着调用当前函数的函数的引用,如果是在全局作用域中调用,则为null。</li>\n</ul>\n<h2 id=\"apply-call-bind-this\"> apply & call & bind & this</h2>\n<ul>\n<li>this引用的是函数执行时的环境对象(全局对象window,或某个函数对象)。使用var声明的变量会被添加到最近的环境中。不加var声明的变量会被添加到全局环境中。</li>\n<li>apply和call可以用于传递参数,可以固定部分参数。但真正强大的地方是能通<strong>第一个参数</strong>修改this对象绑定,扩充函数的作用域,这种扩充作用域的方式,不需要对象和方法有任何耦合关系。</li>\n<li>bind会创建一个函数实例,其this值会被绑定到传给bind的参数。</li>\n</ul>\n<div><pre><code><span>// 传参数</span>\n<span>function</span> <span>sum</span><span>(</span><span>num1<span>,</span> num2</span><span>)</span> <span>{</span>\n <span>return</span> num1 <span>+</span> num2<span>;</span>\n<span>}</span>\n\n<span>function</span> <span>callSum1</span><span>(</span><span>num1<span>,</span> num2</span><span>)</span> <span>{</span>\n <span>return</span> <span>sum</span><span>.</span><span>apply</span><span>(</span><span>this</span><span>,</span> arguments<span>)</span>\n<span>}</span>\n\n<span>function</span> <span>callSum2</span><span>(</span><span>num1</span><span>)</span> <span>{</span>\n <span>return</span> <span>sum</span><span>.</span><span>apply</span><span>(</span><span>this</span><span>,</span> <span>[</span>num1<span>,</span> <span>5</span><span>]</span><span>)</span>\n<span>}</span>\n\n<span>function</span> <span>callSum3</span><span>(</span><span>num1<span>,</span> num2</span><span>)</span> <span>{</span>\n <span>return</span> <span>sum</span><span>.</span><span>call</span><span>(</span><span>this</span><span>,</span> num1<span>,</span> num2<span>)</span>\n<span>}</span>\n\n<span>var</span> bindSum <span>=</span> <span>sum</span><span>.</span><span>bind</span><span>(</span><span>this</span><span>)</span><span>;</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br></div></div><p>修改绑定</p>\n<div><pre><code><span>var</span> o <span>=</span> <span>{</span> color<span>:</span> <span>'blue'</span> <span>}</span>\n<span>var</span> color <span>=</span> <span>'red'</span>\n\n<span>function</span> <span>sayColor</span><span>(</span><span>)</span><span>{</span>\n <span>alert</span><span>(</span><span>this</span><span>.</span>color<span>)</span> <span>// red</span>\n<span>}</span>\n\n<span>var</span> objColor <span>=</span> <span>sayColor</span><span>.</span><span>bind</span><span>(</span>o<span>)</span>\n<span>objColor</span><span>(</span><span>)</span> <span>// blue</span>\n\n<span>sayColor</span><span>.</span><span>apply</span><span>(</span>o<span>)</span> <span>// blue</span>\n<span>sayColor</span><span>.</span><span>call</span><span>(</span>o<span>)</span> <span>// blue</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br></div></div><h2 id=\"正则\"> 正则</h2>\n<p>语法如下</p>\n<div><pre><code><span>var</span> text <span>=</span> <span>\"mom and dad and baby\"</span>\n<span>var</span> pattern <span>=</span> <span><span>/</span><span>mom( and dad( and baby)?)?</span><span>/</span><span>gi</span></span> <span>// /pattern/flags。产生一个RegEx对象。</span>\n<span>var</span> matches <span>=</span> pattern<span>.</span><span>exec</span><span>(</span>text<span>)</span> <span>// pattern.exec(text) === text.match(pattern)</span>\nconsole<span>.</span><span>log</span><span>(</span>matches<span>.</span>index<span>)</span> <span>// 匹配项位置:0</span>\nconsole<span>.</span><span>log</span><span>(</span>matches<span>.</span>input<span>)</span> <span>// 输入字符串:mom and dad and baby</span>\nconsole<span>.</span><span>log</span><span>(</span>matches<span>[</span><span>0</span><span>]</span><span>)</span> <span>// 模式匹配到的子串</span>\nconsole<span>.</span><span>log</span><span>(</span>matches<span>[</span><span>1</span><span>]</span><span>)</span> <span>// 捕获组1 (捕获组,即正则中括号内的部分,如果没匹配上,则为undefined)</span>\nconsole<span>.</span><span>log</span><span>(</span>matches<span>[</span><span>2</span><span>]</span><span>)</span> <span>// 捕获组2 </span>\n<span>// 如果没有捕获组,则matches只有第一项</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br></div></div><h2 id=\"json\"> JSON</h2>\n<ul>\n<li>JSON.stringify(obj, filter, indentOption):把JavaScript对象序列化为JSON字符串。只有数据,不包括方法。\n<ul>\n<li>filter可以是数组,包含要序列化的属性,不再其内的属性序列化时被忽略。</li>\n<li>filter也可以是函数<code>function(key, value) {}</code>,将在每个键值对上调用。通常会用一个switch语句来分别决定如何处理不同的属性。函数的返回值为序列化时使用的value值。</li>\n<li>indentOption指定json文件的缩进格式(数字表示以几个空格为缩进)。</li>\n</ul>\n</li>\n<li>JSON.parse(jsonText, filter):把JSON字符串解析为原生JavaScript值。</li>\n</ul>\n<p>假设把一个对象传入JSON.stringify(),序列化该对象的顺序如下</p>\n<ol>\n<li>如果对象存在toJSON()方法而且能通过它取得有效的值,则调用该方法。否则,返回对象本身。</li>\n<li>如果提供了第二个参数,应用这个函数过滤器。传入函数过滤器的值第(1)步返回的值。</li>\n<li>对第(2)步返回的每个值进行相应的序列化。</li>\n<li>如果提供了第三个参数,执行相应的格式化。</li>\n</ol>\n<h2 id=\"proxy对象\"> Proxy对象</h2>\n<p>可以创建另一个对象的代理。拦截并重新定义对象的基础操作,如读写。使用方法和对象一样。\n两个参数</p>\n<ul>\n<li>target: 目标对象</li>\n<li>handler: 一个对象,定义哪些操作会被拦截,如何重定义被拦截的操作。</li>\n</ul>\n<p>PS:Reflect对象可用于指定对象原来的操作。</p>\n<h3 id=\"基础示例1\"> 基础示例1</h3>\n<div><pre><code><span>const</span> target <span>=</span> <span>{</span>\n message1<span>:</span> <span>\"hello\"</span><span>,</span>\n message2<span>:</span> <span>\"everyone\"</span>\n<span>}</span><span>;</span>\n\n<span>const</span> handler <span>=</span> <span>{</span>\n <span>get</span><span>:</span> <span>function</span><span>(</span><span>target<span>,</span> prop<span>,</span> receiver</span><span>)</span> <span>{</span>\n <span>// prop:正要get的属性, receiver:代理对象或继承自代理的对象</span>\n <span>return</span> <span>\"world\"</span><span>;</span>\n <span>}</span>\n<span>}</span><span>;</span> <span>// 这样设置handler之后,无论获取什么属性,都只会返回\"world\"</span>\n\n<span>const</span> proxy <span>=</span> <span>new</span> <span>Proxy</span><span>(</span>target<span>,</span> handler<span>)</span><span>;</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br></div></div><h3 id=\"基础示例2\"> 基础示例2</h3>\n<div><pre><code><span>const</span> target <span>=</span> <span>{</span>\n message1<span>:</span> <span>\"hello\"</span><span>,</span>\n message2<span>:</span> <span>\"everyone\"</span>\n<span>}</span><span>;</span>\n\n<span>const</span> handler <span>=</span> <span>{</span>\n <span>get</span><span>:</span> <span>function</span> <span>(</span><span>target<span>,</span> prop<span>,</span> receiver</span><span>)</span> <span>{</span>\n <span>if</span> <span>(</span>prop <span>===</span> <span>\"message2\"</span><span>)</span> <span>{</span> <span>// 只特殊处理message2属性</span>\n <span>return</span> <span>\"world\"</span><span>;</span>\n <span>}</span>\n <span>return</span> Reflect<span>.</span><span>get</span><span>(</span><span>...</span>arguments<span>)</span><span>;</span> <span>// 其他属性按原对象来</span>\n <span>}</span><span>,</span>\n<span>}</span><span>;</span>\n\n<span>const</span> proxy <span>=</span> <span>new</span> <span>Proxy</span><span>(</span>target<span>,</span> handler<span>)</span><span>;</span>\n\nconsole<span>.</span><span>log</span><span>(</span>proxy<span>.</span>message1<span>)</span><span>;</span> <span>// hello</span>\nconsole<span>.</span><span>log</span><span>(</span>proxy<span>.</span>message2<span>)</span><span>;</span> <span>// world</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br></div></div><h3 id=\"数组示例\"> 数组示例</h3>\n<div><pre><code><span>let</span> products <span>=</span> <span>new</span> <span>Proxy</span><span>(</span><span>[</span>\n <span>{</span> name<span>:</span> <span>'Firefox'</span><span>,</span> type<span>:</span> <span>'browser'</span> <span>}</span><span>,</span>\n <span>{</span> name<span>:</span> <span>'SeaMonkey'</span><span>,</span> type<span>:</span> <span>'browser'</span> <span>}</span><span>,</span>\n <span>{</span> name<span>:</span> <span>'Thunderbird'</span><span>,</span> type<span>:</span> <span>'mailer'</span> <span>}</span>\n<span>]</span><span>,</span>\n<span>{</span>\n <span>get</span><span>:</span> <span>function</span><span>(</span><span>obj<span>,</span> prop</span><span>)</span> <span>{</span>\n <span>// The default behavior to return the value; prop is usually an integer</span>\n <span>if</span> <span>(</span>prop <span>in</span> obj<span>)</span> <span>{</span>\n <span>return</span> obj<span>[</span>prop<span>]</span><span>;</span> <span>// products[0]</span>\n <span>}</span>\n\n <span>// Get the number of products; an alias of products.length</span>\n <span>if</span> <span>(</span>prop <span>===</span> <span>'number'</span><span>)</span> <span>{</span>\n <span>return</span> obj<span>.</span>length<span>;</span> <span>// products.number</span>\n <span>}</span>\n\n <span>let</span> result<span>,</span> types <span>=</span> <span>{</span><span>}</span><span>;</span>\n\n <span>for</span> <span>(</span><span>let</span> product <span>of</span> obj<span>)</span> <span>{</span> <span>// products[name]</span>\n <span>if</span> <span>(</span>product<span>.</span>name <span>===</span> prop<span>)</span> <span>{</span>\n result <span>=</span> product<span>;</span>\n <span>}</span>\n <span>if</span> <span>(</span>types<span>[</span>product<span>.</span>type<span>]</span><span>)</span> <span>{</span> <span>// 根据type分类</span>\n types<span>[</span>product<span>.</span>type<span>]</span><span>.</span><span>push</span><span>(</span>product<span>)</span><span>;</span>\n <span>}</span> <span>else</span> <span>{</span>\n types<span>[</span>product<span>.</span>type<span>]</span> <span>=</span> <span>[</span>product<span>]</span><span>;</span>\n <span>}</span>\n <span>}</span>\n\n <span>// Get a product by name</span>\n <span>if</span> <span>(</span>result<span>)</span> <span>{</span>\n <span>return</span> result<span>;</span>\n <span>}</span>\n\n <span>// Get products by type</span>\n <span>if</span> <span>(</span>prop <span>in</span> types<span>)</span> <span>{</span>\n <span>return</span> types<span>[</span>prop<span>]</span><span>;</span>\n <span>}</span>\n\n <span>// Get product types</span>\n <span>if</span> <span>(</span>prop <span>===</span> <span>'types'</span><span>)</span> <span>{</span>\n <span>return</span> Object<span>.</span><span>keys</span><span>(</span>types<span>)</span><span>;</span>\n <span>}</span>\n\n <span>return</span> <span>undefined</span><span>;</span>\n <span>}</span>\n<span>}</span><span>)</span><span>;</span>\n\nconsole<span>.</span><span>log</span><span>(</span>products<span>[</span><span>0</span><span>]</span><span>)</span><span>;</span> <span>// { name: 'Firefox', type: 'browser' }</span>\nconsole<span>.</span><span>log</span><span>(</span>products<span>.</span>number<span>)</span><span>;</span> <span>// 3</span>\nconsole<span>.</span><span>log</span><span>(</span>products<span>[</span><span>'Firefox'</span><span>]</span><span>)</span><span>;</span> <span>// { name: 'Firefox', type: 'browser' }</span>\nconsole<span>.</span><span>log</span><span>(</span>products<span>[</span><span>'Chrome'</span><span>]</span><span>)</span><span>;</span> <span>// undefined</span>\nconsole<span>.</span><span>log</span><span>(</span>products<span>.</span>browser<span>)</span><span>;</span> <span>// [{ name: 'Firefox', type: 'browser' }, { name: 'SeaMonkey', type: 'browser' }]</span>\nconsole<span>.</span><span>log</span><span>(</span>products<span>.</span>types<span>)</span><span>;</span> <span>// ['browser', 'mailer']</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br><span>25</span><br><span>26</span><br><span>27</span><br><span>28</span><br><span>29</span><br><span>30</span><br><span>31</span><br><span>32</span><br><span>33</span><br><span>34</span><br><span>35</span><br><span>36</span><br><span>37</span><br><span>38</span><br><span>39</span><br><span>40</span><br><span>41</span><br><span>42</span><br><span>43</span><br><span>44</span><br><span>45</span><br><span>46</span><br><span>47</span><br><span>48</span><br><span>49</span><br><span>50</span><br><span>51</span><br><span>52</span><br><span>53</span><br><span>54</span><br><span>55</span><br></div></div>",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "CSS 高级话题",
"url": "https://kigane.github.io/note/js/css-advance/",
"id": "https://kigane.github.io/note/js/css-advance/",
"content_html": "<h2 id=\"背景-阴影和混合模式\"> 背景,阴影和混合模式</h2>\n<p>backgrounp属性是以下8个属性的简写</p>\n<ul>\n<li>background-image——指定一个文件或者生成的颜色渐变作为背景图片。\n<ul>\n<li>可以是一个图片url路径,如url(bg.png)</li>\n<li>也可以是一个渐变函数,如linear-gradient(to-right, white, blue)</li>\n</ul>\n</li>\n<li>background-position——设置背景图片的初始位置。</li>\n<li>background-size——指定元素内背景图片的渲染尺寸。</li>\n<li>background-repeat——决定在需要填充整个元素时,是否平铺图片。</li>\n<li>background-origin——决定背景相对于元素的边框盒、内边距框盒(初始值)或内容盒子来定位。</li>\n<li>background-clip——指定背景是否应该填充边框盒(初始值)、内边距框盒或内容盒子。</li>\n<li>background-attachment——指定背景图片是随着元素上下滚动(初始值),还是固定在视口区域。注意,使用fixed值会对页面性能产生负面影响。</li>\n<li>background-color——指定纯色背景,渲染到背景图片下方。</li>\n</ul>\n<h3 id=\"渐变\"> 渐变</h3>\n<h4 id=\"linear-gradient\"> linear-gradient</h4>\n<p>linear-gradient函数使用三个参数来定义行为:角度、起始颜色和终止颜色。例如角度值是to right,意思是渐变从元素的左侧开始(起始颜色),平滑过渡到右侧(终止颜色)。</p>\n<hr>\n<p>指定渐变的角度</p>\n<ul>\n<li>to right,to top,to bottom,to bottomright等</li>\n<li>使用更确切的单位,更精确地控制角度。值0deg代表垂直向上,更大的值会沿着顺时针变化\n<ul>\n<li>deg——角度</li>\n<li>rad——弧度</li>\n<li>turn——代表环绕圆周的圈数。可以使用小数来表示不足一圈,比如0.25turn相当于90deg。</li>\n<li>grad——百分度(gradian)。一个完整的圆是400grad,100grad相当于90deg。</li>\n</ul>\n</li>\n</ul>\n<hr>\n<p>指定渐变的颜色节点</p>\n<ul>\n<li>一个渐变可以接受任意数量的颜色节点,节点之间通过逗号分隔。渐变会自动均匀地平铺这些颜色节点。也可以在渐变函数中为每个颜色节点明确指定位置。例如linear-gradient(90deg, red 0%, white 50%, blue 100%)</li>\n<li>条纹效果:linear-gradient(90deg, red 40%, white 40%, white 60%, blue 60%)。如果在同一个位置设置两个颜色节点,那么渐变会直接从一个颜色变换到另一个,而不是平滑过渡。因为第一个颜色节点是红色,在40%的位置,所以渐变从左侧边缘一直到40%是纯红色,最后一个颜色节点是蓝色,也是在60%的位置,这样就会直接变换成蓝色,然后一直到右侧边缘是蓝色。</li>\n<li>repeating-linear-gradient(-45deg, #57b, #57b 10px, #148 10px, #148 20px)。条纹进度条。</li>\n</ul>\n<h4 id=\"radial-gradient\"> radial-gradient</h4>\n<p>另一类渐变是径向渐变。线性渐变是从元素的一端开始,沿着直线过渡到另一端,而径向渐变不同,默认情况下,渐变在元素中是从中心开始,平滑过渡到边缘。渐变整体呈椭圆形,跟随元素大小进行变化。</p>\n<p>跟线性渐变一样,径向渐变同样支持颜色节点。你可以提供多个节点,使用百分比或者长度单位指定节点位置。你也可以把径向渐变设置为圆形而非椭圆,甚至可以指定渐变中心点的位置。repeating-radial-gradient()函数可以重复生成图样,形成同心圆环。</p>\n<p><img src=\"/assets/img/radial-gradient.png\" alt=\"radial-gradient\" /></p>\n<h3 id=\"阴影\"> 阴影</h3>\n<p>有两个属性可以创建阴影,box-shadow可以为元素盒子生成阴影,text-shadow可以为渲染后的文字生成阴影。</p>\n<p>box-shadow: x y blur-radius expand-radius shodow-color</p>\n<ul>\n<li>x 水平偏移</li>\n<li>y 垂直偏移</li>\n<li>blur-radius 用来控制阴影边缘模糊区域的大小,可以为阴影生成一个更柔和、有点透明的边缘。</li>\n<li>expand-radius 用来控制阴影的大小,设置为正值可以使阴影全方位变大,设为负值则会变小。</li>\n<li>shodow-color 阴影颜色</li>\n<li>inset 关键字,让阴影出现在元素内部</li>\n<li>可以同时设置多个阴影,用逗号分隔即可</li>\n<li>文本阴影的语法也基本上完全一样:水平偏移量、垂直偏移量、模糊半径(可选)和颜色。但文本阴影不支持inset关键字和扩展半径值。</li>\n</ul>\n<div><pre><code><span>.button:active</span> <span>{</span>\n <span>box-shadow</span><span>:</span> inset 0 0 0.5em #124<span>,</span> inset 0 0.5em 1em <span>rgba</span><span>(</span>0<span>,</span>0<span>,</span>0<span>,</span>0.4<span>)</span><span>;</span>\n <span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br></div></div><h3 id=\"混合模式\"> 混合模式</h3>\n<p>大部分情况下,不论是使用真正的图片还是渐变,元素一般只会使用一张背景图片。但某些情况下你可能想要使用两张或者更多的背景图片,CSS是支持这么做的。使用多个背景图片时,列表中排在前面的图片会渲染到排序靠后的图片上面。一般是希望第二张图片也可以透视显示。这时就可以使用混合模式(blend mode)。<br>\n<img src=\"/assets/img/blend-mode.png\" alt=\"blend\" /></p>\n<div><pre><code><span><span><span><</span>!doctype</span><span>></span></span>\n<span><span><span><</span>div</span> <span>class</span><span><span>=</span><span>\"</span>blend<span>\"</span></span><span>></span></span><span><span><span></</span>div</span><span>></span></span>\n\n<span><span><span><</span>style</span><span>></span></span><span><span>\n<span>.blend</span> <span>{</span>\n <span>min-height</span><span>:</span> 400px<span>;</span>\n <span>background-image</span><span>:</span> <span><span>url</span><span>(</span>images/bear.jpg<span>)</span></span><span>,</span> <span><span>url</span><span>(</span>images/bear.jpg<span>)</span></span><span>;</span>\n <span>background-size</span><span>:</span> cover<span>;</span>\n <span>background-repeat</span><span>:</span> no-repeat<span>;</span>\n <span>background-position</span><span>:</span> -30vw<span>,</span> 30vw<span>;</span>\n <span>background-blend-mode</span><span>:</span> multiply<span>;</span>\n<span>}</span>\n</span></span><span><span><span></</span>style</span><span>></span></span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br></div></div><h2 id=\"对比-颜色和间距\"> 对比,颜色和间距</h2>\n<h3 id=\"对比\"> 对比</h3>\n<p>对比是设计中的一种手段,通过突出某物来达到吸引注意力的目的。我们的眼睛和思维天生对模式比较敏感,一旦某种东西破坏了模式的整体效果,我们就自然而然地注意到了它。<br>\n若要起到对比的效果,页面必须先建立模式,就如同必须先有了规矩才能打破规矩。<br>\n推进样式代码复用,就可以确保网站中使用一致的模式。建立统一的模式,然后打破模式,突出网页上最重要的部分,这是专业设计师的一个核心思路。<br>\nCTA,即Call To Action的缩写,是一种针对核心元素的营销手段,希望引导用户使用。</p>\n<h3 id=\"颜色\"> 颜色</h3>\n<p>调色板:一般会有一种主色,其他颜色基于主色。其他颜色一般是同一色系不同明暗度的颜色,还有一些补充颜色。大部分调色板也会有黑色和白色,以及少量的灰色。因为这些颜色会在CSS中多次重复出现,所以将它们指定为变量可以节省很多时间</p>\n<p>颜色表示法:</p>\n<ul>\n<li>十六进制表示法:#fffffff,#000</li>\n<li>rgb(), rgba():描述红、绿、蓝彩色值的颜色表示法,使用十进制而非十六进制。取值范围为0-255。</li>\n<li>hsl(), hsla():是一种更适合人类读取的颜色表示法。需要3个参数。\n<ul>\n<li>第一个参数表示色相,是一个0~359的整数值。这代表色相环上的360度,从红色(0)、黄色(60)、绿色(120)、青色(180)、蓝色(240)、洋红色(300)依次过渡,最后回到红色。</li>\n<li>第二个参数表示饱和度,是一个代表色彩强度的百分数,100%的时候颜色最鲜艳,0%就意味着没有彩色,只是一片灰色。</li>\n<li>第三个参数表示明度,也是百分数,代表颜色有多亮(或者多暗)。大部分鲜艳的颜色是使用50%的明度值。明度值设置得越高,颜色越浅,100%就是纯白色;设置得越低,颜色越暗,0%就是黑色。</li>\n</ul>\n</li>\n</ul>\n<hr>\n<p>为某种颜色寻找一个搭配的颜色,最简单的方式是找到它的补色(complement)。补色位于色相环的对侧位置,蓝色的补色是黄色;绿色的补色是紫色;红色的补色是青色。使用HSL颜色值时,计算补色非常简单,为色相值加上或者减去180即可。</p>\n<p>检查背景和文本的对比度是否符合要求。<a href=\"https://contrast-ratio.com/\" target=\"_blank\" rel=\"noopener noreferrer\">CSS colorcontrast checker: contrast-ratio</a></p>\n<h3 id=\"间距\"> 间距</h3>\n<p>这部分工作里的大多数内容可以简单归结为正确设置元素的外边距。一般从最容易的地方开始,哪怕后面可能需要再做一些调整。<br>\n我们需要思考两件事情,一个是是否需要使用相对单位,另一个是行高如何影响垂直间距。</p>\n<p>使用绝对单位会比较容易,但是使用相对单位会有很多好处,不论em还是rem(更有弹性,响应式)。</p>\n<p>在盒模型中,元素的内容盒子为内边距所环绕,然后是边框,最后是外边距。但是对于段落和标题这样的元素,内容盒子并不是只有显示出来的文字区域,元素的行高决定了内容盒子最终的高度,这要超出字符的顶部和底部。\n<img src=\"/assets/img/line-height.png\" alt=\"行高\" /><br>\n网页里的行高设为1.4。只有一行文字的元素,内容盒子的高度就是1.4em,文字在内部垂直居中。字号是16px,内容盒子的最终高度就是22.4px。额外的6.4px平均分配到文字的上方和下方。</p>\n<p>如果为行内元素添加内边距,元素本身会变高,却不会增加文本行的高度。文本行的高度只由行高来决定。要解决这个问题,就需要增加每项的行高。如果一个元素是弹性子元素(或者行内块级元素),为了容纳它,其所在的行会随之增高。</p>\n<p><strong>牢记行高可以影响垂直间距</strong>。</p>\n<h2 id=\"排版\"> 排版</h2>\n<h3 id=\"web字体\"> Web字体</h3>\n<p>通过在线服务使用Web字体是最简单也最普遍的方式。谷歌字体有很多高质量并且开源的字体(还免费),建议使用。<br>\n选中字体后,复制link标签并添加到页面的head里,这样就为页面添加了一个包含字体描述的样式表。谷歌通过样式表为页面配置好了Web字体需要的设置,然后就可以在自己的样式表中随意使用Web字体了。需要使用font-family属性来指定字体。</p>\n<div><p>关于serif</p>\n<p>Serif——字母笔画末端的小线条或者“爪状”装饰。包含serif的字体就被称为serif字体(例如Times New Roman)。如果没有serif,那就是sans-serif字体(例如Helvetica)。</p>\n</div>\n<div><p>字型和字体</p>\n<p>字型(typeface)和字体(font)这两个术语经常被混为一谈。字型通常是指字体(比如Roboto)的整个家族,一般由同一个设计师创造。一种字型可能会存在多种变体和字重(比如细体、粗体、斜体、压缩,等等),这些变体的每一种可称之为一种字体(font)。</p>\n</div>\n<h3 id=\"调整字距\"> 调整字距</h3>\n<p>line-height和letter-spacing,这两个属性可以控制文本行之间的距离(垂直方向)和字符之间的距离(水平方向)。</p>\n<ul>\n<li>line-height属性的初始值是关键字normal,大约等于1.2,但是在大部分情况下,这个值太小了。对于正文主体来说,介于1.4和1.6之间的值比较理想。</li>\n<li>letter-spacing通常从0.01em开始测试,直到你满意为止。</li>\n</ul>\n<div><p>Tips</p>\n<p>文字行越长,行高应该设置得越大。这样读者的眼睛扫到下一行的时候才更容易,不会注意力分散。理想情况下,每行文字的长度应该控制在45~75个字符,一般认为这样的长度最利于阅读。</p>\n</div>\n<p>在设计领域,文本行之间的距离称为行距(leading,与bedding有点谐音),来源于印刷版每行文字之间添加的一条条的引导线(lead)。字符之间的距离称之为字距(tracking)。行高一般使用“点”作单位来描述,比如18pt,代表的是一行文字的高度加上它与下一行文字之间的距离。这实际上与CSS的line-height一样。你必须首先把它转化为跟字体一样使用像素单位,然后再计算出无单位数字。把pt值乘以1.333,就可以把pt转化为px(因为每英寸是96px,并且每英寸等于72pt, 96/72=1.333),即18pt×1.333=24px。然后除以字号,得到无单位的行高值,即24px/16px=1.5。字距通常会给定一个字数,比如100。因为这个数字表示1em的千分之一,所以除以1000就可以转化成em单位,即100/1000=0.1em。</p>\n<p>对于正文主体来讲,调整间距是为了使阅读体验效果更佳,但对于标题和其他内容很少的元素(比如按钮)来讲,影响不大。这时候间距可调整范围大大增加,就可以多多发挥创意了。也可以使用负数设置字符间距,让字符更紧凑。</p>\n<h2 id=\"过渡\"> 过渡</h2>\n<h3 id=\"从这边到那边\"> 从这边到那边</h3>\n<p>过渡是通过一系列transition-*属性来实现的。如果某个元素设置了过渡,那么当它的属性值发生变化时,并不是直接变成新值,而是使用过渡效果。</p>\n<details><summary>Code</summary>\n<p><button>Hover over me</button>\n<style>\nbutton {\nbackground-color: hsl(180, 50%, 50%);\nborder: 0;\ncolor: white;\nfont-size: 1rem;\npadding: .3em 1em;\ntransition-property: all; /* 所有属性变化都使用过渡效果 <em>/\ntransition-duration: 0.5s; /</em> 过渡时间 */\n}\nbutton:hover {\nbackground-color: hsl(0, 50%, 50%);\nborder-radius: 1em;\n}\n</style></p>\n</details>\n<ul>\n<li>transition-property:指定哪些属性使用过渡。</li>\n<li>transition-duration:代表过渡到最终值之前需要多长时间</li>\n<li>元素属性任何时候发生变化都会触发过渡:可以是状态改变的时候,比如:hover;也可以是JavaScript导致变化的时候,比如添加或者移除类影响了元素的样式。</li>\n<li>过渡属性要设置在一个始终指向需要过渡的元素的选择器。因为虽然其他属性发生着变化,但你肯定不想过渡属性本身发生变化。</li>\n<li>简写属性transition: transition:property duration timing-function delay\n<ul>\n<li>timing-function 定时函数,用来控制属性的中间值如何计算,实际上控制的是过渡过程中变化率如何加速或者减速。定时函数可以是一个关键字值,比如linear或者ease-in,也可以是自定义函数。</li>\n<li>delay 延迟时间</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"定时函数\"> 定时函数</h3>\n<p>过渡让某个属性从一个值“移动”到另一个值,定时函数用来说明如何移动。</p>\n<ul>\n<li>linear:值以固定的速度改变</li>\n<li>ease-in:变化速度开始时慢,然后一直加速,直到过渡完成</li>\n<li>ease-out:是减速,开始时快速变化,结束时比较慢</li>\n</ul>\n<p>定时函数是基于数学定义的贝塞尔曲线(Bézier curve)。浏览器使用贝塞尔曲线作为随时间变化的函数,来计算某个属性的值。\n<img src=\"/assets/img/transition-timing-func.png\" alt=\"定时函数\" /><br>\n这些贝塞尔曲线都是从左下方开始,持续延伸到右上方。时间是从左向右递进的,曲线代表某个值在到达最终值的过程中是如何变化的。</p>\n<div><p>设置定时函数</p>\n<p>打开开发者工具并检查样式面板中的定时函数,旁边有一个小小的标志符号。点击标志符号会打开一个弹窗,可以在弹窗中修改定时函数的曲线。</p>\n</div>\n<div><p>过渡时间</p>\n<p>按照经验来讲,大部分的过渡持续时间应该处于200~500ms。时间如果再长,用户就会感觉页面变得卡慢,页面响应让他们产生了无谓的等待,尤其是面对那些经常或者重复使用的过渡特效时。<br>\n对于鼠标悬停、淡入淡出和轻微缩放特效,应该使用较快的过渡速度。一般要控制在300ms以下,有时候甚至可能要低到100ms。对于那些包含较大移动或者复杂定时函数的过渡,比如弹跳特效,要使用较长的300~500ms的持续时间。</p>\n</div>\n<div><p>选择定时函数</p>\n<p>可以在下列场景中分别使用这三种函数。</p>\n<ul>\n<li>线性——颜色变化和淡出、淡入效果。</li>\n<li>减速——用户发起的变化。用户点击按钮或者划过元素的时候,使用ease-out或者类似曲线。这样用户就可以看到快速发生的反馈,及时响应输入,然后元素慢慢过渡到最终状态。</li>\n<li>加速——系统发起的变化。当内容加载完成或者超时事件触发的时候,使用ease-in或者类似曲线。这样元素就可以慢慢引起用户注意,然后速度越来越快直到完成最终变化。</li>\n</ul>\n</div>\n<hr>\n<p>另一种定时函数--阶跃steps()<br>\n阶跃函数需要两个参数:阶跃次数和一个用来表示每次变化发生在阶跃的开始还是结束的关键词(start或者end)<br>\n<img src=\"/assets/img/step-func.png\" alt=\"阶跃函数\" /><br>\n第二个参数的默认值是end,所以steps(3)等于steps(3, end)。</p>\n<h3 id=\"创建下拉菜单并添加过渡效果\"> 创建下拉菜单并添加过渡效果</h3>\n<p>使用透明度的过渡为下拉菜单的打开和闭合添加淡入淡出特效。</p>\n<div><pre><code><span>.dropdown__drawer</span> <span>{</span>\n <span>position</span><span>:</span> absolute<span>;</span>\n <span>background-color</span><span>:</span> white<span>;</span>\n <span>width</span><span>:</span> 10em<span>;</span>\n <span>opacity</span><span>:</span> 0<span>;</span>\n <span>visibility</span><span>:</span> hidden<span>;</span>\n <span>transition</span><span>:</span> opacity 0.2s linear<span>,</span> <span>/* 透明度过渡效果--淡出 */</span>\n visibility 0s linear 0.2s<span>;</span> <span>/* 淡出时,可见性在透明度过渡完成时再变化 */</span>\n<span>}</span>\n<span>.dropdown.is-open .dropdown__drawer</span> <span>{</span>\n <span>opacity</span><span>:</span> 1<span>;</span>\n <span>visibility</span><span>:</span> visible<span>;</span>\n <span>transition-delay</span><span>:</span> 0s<span>;</span> <span>/* 菜单淡入时,可见性要立即变化,否则看不到淡入效果 */</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br></div></div><p>visibility属性可以从页面上移除某个元素,有点类似于display属性,分别设置visible和hidden即可。但跟display不同的是,visibility可以支持动画。为它设置过渡不会使其逐渐消失,但transition-delay可以生效,而在display属性上是不生效的。<br>\n为某个元素设置visibility: hidden可以从可见页面中移除该元素,但不会从文档流中移除它,这就意味着该元素仍然占位。其他元素会继续围绕该元素的位置布局,在页面上保留一个空白区域。在我们的例子中,不会影响到菜单,因为我们同时也设置了绝对定位。</p>\n<hr>\n<p>实现自动高度</p>\n<div><pre><code><span>.dropdown__drawer</span> <span>{</span>\n <span>position</span><span>:</span> absolute<span>;</span>\n <span>background-color</span><span>:</span> white<span>;</span>\n <span>width</span><span>:</span> 10em<span>;</span>\n <span>height</span><span>:</span> 0<span>;</span>\n <span>overflow</span><span>:</span> hidden<span>;</span>\n <span>transition</span><span>:</span> height 0.3s ease-out<span>;</span>\n<span>}</span>\n<span>.dropdown.is-open .dropdown__drawer</span> <span>{</span>\n <span>height</span><span>:</span> auto<span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br></div></div><p>很遗憾,这种纯css写法不会奏效。因为过渡不支持auto,必须指定确定的高度值。但在css中没办法知道具体的高度值。因为只有当内容在浏览器中渲染完成之后才能确定高度,所以需要使用JavaScript来获取。<br>\n页面加载完成后,访问DOM元素的scrollHeight属性,就可以获取到高度值。</p>\n<div><pre><code><span>(</span><span>function</span> <span>(</span><span>)</span> <span>{</span>\n <span>var</span> toggle <span>=</span> document<span>.</span><span>getElementsByClassName</span><span>(</span><span>'dropdown__toggle'</span><span>)</span><span>[</span><span>0</span><span>]</span><span>;</span>\n <span>var</span> dropdown <span>=</span> toggle<span>.</span>parentElement<span>;</span>\n <span>var</span> drawer <span>=</span> document<span>.</span><span>getElementsByClassName</span><span>(</span><span>'dropdown__drawer'</span><span>)</span><span>[</span><span>0</span><span>]</span><span>;</span>\n <span>var</span> height <span>=</span> drawer<span>.</span>scrollHeight<span>;</span> <span>// 元素的高度值</span>\n\n toggle<span>.</span><span>addEventListener</span><span>(</span><span>'click'</span><span>,</span> <span>function</span> <span>(</span><span>e</span><span>)</span> <span>{</span>\n e<span>.</span><span>preventDefault</span><span>(</span><span>)</span><span>;</span>\n dropdown<span>.</span>classList<span>.</span><span>toggle</span><span>(</span><span>'is-open'</span><span>)</span><span>;</span>\n <span>if</span> <span>(</span>dropdown<span>.</span>classList<span>.</span><span>contains</span><span>(</span><span>'is-open'</span><span>)</span><span>)</span> <span>{</span>\n drawer<span>.</span>style<span>.</span><span>setProperty</span><span>(</span><span>'height'</span><span>,</span> height <span>+</span> <span>'px'</span><span>)</span><span>;</span>\n <span>}</span> <span>else</span> <span>{</span>\n drawer<span>.</span>style<span>.</span><span>setProperty</span><span>(</span><span>'height'</span><span>,</span> <span>'0'</span><span>)</span><span>;</span>\n <span>}</span>\n <span>}</span><span>)</span><span>;</span>\n<span>}</span><span>(</span><span>)</span><span>)</span><span>;</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br></div></div><h2 id=\"变换\"> 变换</h2>\n<p>transform属性,它可以用来改变页面元素的形状和位置,其中包括二维或者三维的旋转、缩放和倾斜。用法:<code>transform:rotata(90deg)</code>\n<img src=\"/assets/img/transformation.png\" alt=\"变换\" /><br>\n使用变换的时候要注意一件事情,虽然元素可能会被移动到页面上的新位置,但它不会脱离文档流。你可以在屏幕范围内以各种方式平移元素,其初始位置不会被其他元素占用。当旋转某元素的时候,它的一角可能会移出屏幕边缘,同样也可能会遮住旁边其他元素的部分内容。</p>\n<div><p>注意</p>\n<p>变换不能作用在span或者a这样的行内元素上。若确实要变换此类元素,要么改变元素的display属性,替换掉inline(比如inline-block),要么把元素改为弹性子元素或者网格项目(为父元素应用display: flex或者display: grid)。</p>\n</div>\n<ul>\n<li>变换是围绕基点(point of origin)发生的。基点是旋转的轴心,也是缩放或者倾斜开始的地方。这就意味着元素的基点是固定在某个位置上,元素的剩余部分围绕基点变换(但translate()是个例外,因为平移过程中元素整体移动)。</li>\n<li>默认情况下,基点就是元素的中心,但可以通过transform-origin:x y属性改变基点位置。\n<ul>\n<li>常用值:left, right, top, bottom, center</li>\n<li>基点也可以指定为百分比,从元素左上角开始测量。</li>\n</ul>\n</li>\n<li>可以对transform属性指定多个值,用空格隔开。变换的每个值从右向左按顺序执行,类似矩阵变换。</li>\n</ul>\n<h3 id=\"图标放大效果\"> 图标放大效果</h3>\n<p>图标设置过宽度和高度,因此我们可以通过增大这些属性来放大它。但会重新计算文档流时,导致其周围的一些元素跟着移动。如果改用变换,那周围的元素不会受到影响。</p>\n<div><pre><code><span>.nav-links__icon</span> <span>{</span>\n <span>transition</span><span>:</span> transform 0.2s ease-out<span>;</span>\n<span>}</span>\n\n<span>.nav-links a:hover > .nav-links__icon,\n.nav-links a:focus > .nav-links__icon</span> <span>{</span>\n <span>transform</span><span>:</span> <span>scale</span><span>(</span>1.3<span>)</span><span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br></div></div><h3 id=\"飞入效果\"> 飞入效果</h3>\n<p>菜单的标签没有必要一直保持可见状态。默认情况下可以把它们隐藏,只在相应位置保留图标,告诉用户菜单的位置。当用户移动鼠标到菜单或者导航元素上时,再把标签以淡入的方式展示出来。</p>\n<div><pre><code><span>.nav-links</span> <span>{</span>\n <span>display</span><span>:</span> block<span>;</span>\n <span>padding</span><span>:</span> 1em<span>;</span>\n <span>margin-bottom</span><span>:</span> 0<span>;</span>\n<span>}</span>\n<span>.nav-links > li + li</span> <span>{</span>\n <span>margin-left</span><span>:</span> 0<span>;</span>\n<span>}</span>\n\n<span>.nav-links__label</span> <span>{</span>\n <span>display</span><span>:</span> inline-block<span>;</span>\n <span>margin-left</span><span>:</span> 1em<span>;</span> <span>/* 为过渡效果预留的空间 */</span>\n <span>padding-right</span><span>:</span> 1em<span>;</span>\n <span>opacity</span><span>:</span> 0<span>;</span>\n <span>transform</span><span>:</span> <span>translate</span><span>(</span>-1em<span>)</span><span>;</span>\n <span>transition</span><span>:</span> transform 0.4s <span>cubic-bezier</span><span>(</span>0.2<span>,</span> 0.9<span>,</span> 0.3<span>,</span> 1.3<span>)</span><span>,</span> <span>/* */</span>\n opacity 0.4s linear<span>;</span> <span>/* 背景淡入淡出 */</span>\n<span>}</span>\n<span>.nav-links:hover .nav-links__label,\n.nav-links a:focus > .nav-links__label</span> <span>{</span>\n <span>opacity</span><span>:</span> 1<span>;</span>\n <span>transform</span><span>:</span> <span>translate</span><span>(</span>0<span>)</span><span>;</span>\n<span>}</span>\n\n<span>.nav-links__icon</span> <span>{</span>\n <span>transition</span><span>:</span> transform 0.2s ease-out<span>;</span>\n<span>}</span>\n\n<span>.nav-links a:hover > .nav-links__icon,\n.nav-links a:focus > .nav-links__icon</span> <span>{</span>\n <span>transform</span><span>:</span> <span>scale</span><span>(</span>1.3<span>)</span><span>;</span>\n<span>}</span>\n\n<span>/* 为每个菜单项设置不同的延迟时间。这样就可以使每段动画交错飞入显示\n,不再一次性全部展示出来,就像翻滚的“波浪” */</span>\n<span>.nav-links > li:nth-child(2) .nav-links__label</span> <span>{</span>\n <span>transition-delay</span><span>:</span> 0.1s<span>;</span>\n<span>}</span>\n<span>.nav-links > li:nth-child(3) .nav-links__label</span> <span>{</span>\n <span>transition-delay</span><span>:</span> 0.2s<span>;</span>\n<span>}</span>\n<span>.nav-links > li:nth-child(4) .nav-links__label</span> <span>{</span>\n <span>transition-delay</span><span>:</span> 0.3s<span>;</span>\n<span>}</span>\n<span>.nav-links > li:nth-child(5) .nav-links__label</span> <span>{</span>\n <span>transition-delay</span><span>:</span> 0.4s<span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br><span>25</span><br><span>26</span><br><span>27</span><br><span>28</span><br><span>29</span><br><span>30</span><br><span>31</span><br><span>32</span><br><span>33</span><br><span>34</span><br><span>35</span><br><span>36</span><br><span>37</span><br><span>38</span><br><span>39</span><br><span>40</span><br><span>41</span><br><span>42</span><br><span>43</span><br><span>44</span><br><span>45</span><br><span>46</span><br><span>47</span><br></div></div><h3 id=\"_3d变换\"> 3D变换</h3>\n<p>旋转和平移都可以在三个维度上实现:X轴、Y轴和Z轴。</p>\n<div><pre><code><span>.a</span> <span>{</span>\n <span>transform</span><span>:</span> <span>translate</span><span>(</span>15px<span>,</span> 50px<span>)</span><span>;</span> <span>/* 等价于transform: translateX(15px) translateY(50px); */</span>\n <span>transform</span><span>:</span> <span>rotate</span><span>(</span>30deg<span>)</span><span>;</span> <span>/* 等价于transform: rotateZ(30deg); */</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br></div></div><h4 id=\"透视距离\"> 透视距离</h4>\n<p>为页面添加3D变换之前,我们需要先确定一件事情,即透视距离(perspective)。变换后的元素一起构成了一个3D场景。接着浏览器会计算这个3D场景的2D图像,并渲染到屏幕上。透视距离类似相机到近平面的距离。</p>\n<p>可以通过两种方式指定透视距离:使用perspective()变换或者使用perspective属性。</p>\n<div><pre><code><span><span><span><</span>div</span> <span>class</span><span><span>=</span><span>\"</span>container<span>\"</span></span><span>></span></span>\n <span><span><span><</span>div</span> <span>class</span><span><span>=</span><span>\"</span>row<span>\"</span></span><span>></span></span>\n <span><span><span><</span>div</span> <span>class</span><span><span>=</span><span>\"</span>box<span>\"</span></span><span>></span></span>One<span><span><span></</span>div</span><span>></span></span>\n <span><span><span><</span>div</span> <span>class</span><span><span>=</span><span>\"</span>box<span>\"</span></span><span>></span></span>Two<span><span><span></</span>div</span><span>></span></span>\n <span><span><span><</span>div</span> <span>class</span><span><span>=</span><span>\"</span>box<span>\"</span></span><span>></span></span>Three<span><span><span></</span>div</span><span>></span></span>\n <span><span><span><</span>div</span> <span>class</span><span><span>=</span><span>\"</span>box<span>\"</span></span><span>></span></span>Four<span><span><span></</span>div</span><span>></span></span>\n <span><span><span></</span>div</span><span>></span></span>\n<span><span><span></</span>div</span><span>></span></span>\n<span><span><span><</span>style</span><span>></span></span><span><span>\n <span>.row</span> <span>{</span>\n <span>display</span><span>:</span> flex<span>;</span>\n <span>justify-content</span><span>:</span> center<span>;</span>\n <span>/* 给父容器设置透视距离,则所有子容器可看作一个整体,效果和单独设置不同 */</span>\n <span>/* perspective: 200px; .box的transform只设置rotateX */</span>\n <span>}</span>\n\n <span>.box</span> <span>{</span>\n <span>box-sizing</span><span>:</span> border-box<span>;</span>\n <span>width</span><span>:</span> 150px<span>;</span>\n <span>margin</span><span>:</span> 0 2em<span>;</span>\n <span>padding</span><span>:</span> 60px 0<span>;</span>\n <span>text-align</span><span>:</span> center<span>;</span>\n <span>background-color</span><span>:</span> <span>hsl</span><span>(</span>150<span>,</span> 50%<span>,</span> 40%<span>)</span><span>;</span>\n <span>transform</span><span>:</span> <span>perspective</span><span>(</span>200px<span>)</span> <span>rotateX</span><span>(</span>30deg<span>)</span><span>;</span>\n <span>}</span>\n</span></span><span><span><span></</span>style</span><span>></span></span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br><span>25</span><br><span>26</span><br></div></div><div><p>注意</p>\n<ul>\n<li>添加透视距离是3D变换中非常重要的部分。如果不设置透视距离,离得远的元素不会显得变小,离得近的元素也不会显得变大。</li>\n<li>透视距离小,3D效果就会比较强。透视距离大,3D效果就会比较弱。</li>\n</ul>\n</div>\n<h4 id=\"其他3d属性\"> 其他3D属性</h4>\n<ul>\n<li>perspective-origin:默认情况下,透视距离的渲染是假设观察者(或者镜头)位于元素中心的正前方。perspective-origin属性可以上下、左右移动镜头的位置。</li>\n<li>backface-visibility:如果你使用rotateX()或者rotateY()旋转元素超过90度,就会发现一些有趣的事情:元素的“脸”不再直接朝向你。它的“脸”转向别的地方,你会看到元素的背面。默认情况下背面是可见的,但我们可以为元素设置backface-visibility:hidden来改变它。添加这条声明之后,元素只有在正面朝向观察者的时候才可见,朝向别处的时候不可见。\n<ul>\n<li>针对这项技术,一个可能的应用场景是把两个元素背靠背放在一起,就像卡片的两面。卡片的正面展示出来,背面隐藏。然后我们可以旋转它们的容器元素,使这两个元素都翻转过来,这样正面隐藏背面显现。</li>\n</ul>\n</li>\n<li>transform-style:preserve-3d -- 假设对容器设置了透视距离,接下来对容器内的元素进行3D变换。容器元素渲染时,实际上会被绘制成2D场景,就像是3D对象的一张照片。这看起来没什么问题,因为元素最终就是要渲染到2D屏幕上的。如果接下来我们对容器元素进行3D旋转,就有问题了。这是因为实际上没有对整个场景进行旋转,只是旋转3D场景的2D照片。透视距离全都错了,场景中的立体感也被破坏了。这时transform-style:preserve-3d就有作用了。\n<ul>\n<li>参考<a href=\"https://davidwalsh.name/css-cube\" target=\"_blank\" rel=\"noopener noreferrer\">css cube</a></li>\n</ul>\n</li>\n</ul>\n<h2 id=\"动画\"> 动画</h2>\n<p>CSS中的动画包括两部分:用来定义动画的@keyframes规则和为元素添加动画的animation属性。</p>\n<h3 id=\"关键帧\"> 关键帧</h3>\n<p>关键帧(keyframe)是指动画过程中某个特定时刻。我们定义一些关键帧,浏览器负责填充或者插入这些关键帧之间的帧图像。</p>\n<div><pre><code><span><span><span><</span>div</span> <span>class</span><span><span>=</span><span>\"</span>box<span>\"</span></span><span>></span></span><span><span><span></</span>div</span><span>></span></span>\n<span><span><span><</span>style</span><span>></span></span><span><span>\n<span>.box</span> <span>{</span>\n <span>width</span><span>:</span> 100px<span>;</span>\n <span>height</span><span>:</span> 100px<span>;</span>\n <span>background-color</span><span>:</span> green<span>;</span>\n <span>animation</span><span>:</span> over-and-back 1.5s linear 3<span>;</span>\n<span>}</span>\n\n<span><span>@keyframes</span> over-and-back</span> <span>{</span>\n<span>0%</span> <span>{</span>\n <span>background-color</span><span>:</span> <span>hsl</span><span>(</span>0<span>,</span> 50%<span>,</span> 50%<span>)</span><span>;</span>\n <span>transform</span><span>:</span> <span>translate</span><span>(</span>0<span>)</span><span>;</span>\n<span>}</span>\n\n<span>50%</span> <span>{</span>\n <span>transform</span><span>:</span> <span>translate</span><span>(</span>50px<span>)</span><span>;</span>\n<span>}</span>\n\n<span>100%</span> <span>{</span>\n <span>background-color</span><span>:</span> <span>hsl</span><span>(</span>270<span>,</span> 50%<span>,</span> 90%<span>)</span><span>;</span>\n <span>transform</span><span>:</span> <span>translate</span><span>(</span>0<span>)</span><span>;</span>\n<span>}</span>\n<span>}</span>\n</span></span><span><span><span></</span>style</span><span>></span></span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br><span>25</span><br></div></div><p>animation是简写属性。</p>\n<ul>\n<li>animation-name——代表动画名称,来自@keyframes规则定义。</li>\n<li>animation-duration——代表动画持续时间</li>\n<li>animation-timing-function——代表定时函数</li>\n<li>animation-iteration-count——代表动画重复的次数。初始值默认是1。</li>\n</ul>\n<div><p>注意</p>\n<ol>\n<li>颜色从0%的红色平滑过渡到100%的淡紫色,但是接下来动画重复的时候立即变回红色。如果你打算重复某个动画并希望整体衔接流畅,需要确保结束值和初始值相匹配。</li>\n<li>最后一次重复动画结束后,背景颜色变为绿色,即原样式规则中指定的值。但注意动画持续过程中,这句样式声明被@keyframes中的规则覆盖了。如果出现样式层叠,那么动画中设置的规则比其他声明拥有更高的优先级。</li>\n<li>优先级规则:用户代理样式 < 作者自定义样式 < @keyframe中定义的样式 < !important</li>\n</ol>\n</div>\n<h3 id=\"动画延迟和填充模式\"> 动画延迟和填充模式</h3>\n<p>可以使用animation-delay属性推迟动画开始的时间,该属性行为和transition-delay类似。</p>\n<div><pre><code><span><span><span><</span>style</span><span>></span></span><span><span>\n<span>.flyin-grid</span> <span>{</span>\n <span>margin</span><span>:</span> 0 1rem<span>;</span>\n <span>perspective</span><span>:</span> 500px<span>;</span> <span>/* 添加透视距离 */</span>\n<span>}</span>\n\n<span>.flyin-grid__item</span> <span>{</span>\n <span>animation</span><span>:</span> fly-in 600ms ease-in<span>;</span> <span>/* 添加动画 */</span>\n <span>/* animation-fill-mode: backwards; 加上这一句,动画前的元素的初始位置就对了 */</span>\n<span>}</span>\n<span>/* 添加动画延迟 */</span>\n<span>.flyin-grid__item:nth-child(2)</span> <span>{</span>\n <span>animation-delay</span><span>:</span> 0.15s<span>;</span>\n<span>}</span>\n<span>.flyin-grid__item:nth-child(3)</span> <span>{</span>\n <span>animation-delay</span><span>:</span> 0.3s<span>;</span>\n<span>}</span>\n<span>.flyin-grid__item:nth-child(4)</span> <span>{</span>\n <span>animation-delay</span><span>:</span> 0.45s<span>;</span>\n<span>}</span>\n\n\n<span><span>@media</span> <span>(</span><span>min-width</span><span>:</span> 30em<span>)</span></span> <span>{</span>\n<span>.flyin-grid</span> <span>{</span>\n <span>display</span><span>:</span> flex<span>;</span>\n <span>flex-wrap</span><span>:</span> wrap<span>;</span>\n <span>margin</span><span>:</span> 0 5rem<span>;</span>\n<span>}</span>\n\n<span>.flyin-grid__item</span> <span>{</span>\n <span>flex</span><span>:</span> 1 1 300px<span>;</span>\n <span>margin-left</span><span>:</span> 0.5em<span>;</span>\n <span>margin-right</span><span>:</span> 0.5em<span>;</span>\n <span>max-width</span><span>:</span> 600px<span>;</span>\n<span>}</span>\n\n<span><span>@supports</span> <span>(</span><span>display</span><span>:</span> grid<span>)</span></span> <span>{</span> <span>/* 查询浏览器是否支持grid属性,如果支持,则下面样式生效 */</span>\n <span>.flyin-grid</span> <span>{</span>\n <span>display</span><span>:</span> grid<span>;</span>\n <span>/* 尽可能多的生成网格轨道,每个轨道至少300px,如果元素较少,会将非空轨道扩展以填满空白 */</span>\n <span>grid-template-columns</span><span>:</span> <span>repeat</span><span>(</span>auto-fit<span>,</span> <span>minmax</span><span>(</span>300px<span>,</span> 1fr<span>)</span><span>)</span><span>;</span>\n <span>grid-gap</span><span>:</span> 2em<span>;</span> <span>/* 网格元素间距 */</span>\n <span>}</span>\n\n <span>.flyin-grid__item</span> <span>{</span>\n <span>max-width</span><span>:</span> initial<span>;</span> <span>/* div 的 max-width 初始值为 none,即没有限制*/</span>\n <span>margin</span><span>:</span> 0<span>;</span>\n <span>}</span>\n<span>}</span>\n<span>}</span>\n\n<span>.card</span> <span>{</span>\n <span>margin-bottom</span><span>:</span> 1em<span>;</span>\n <span>padding</span><span>:</span> 0.5em<span>;</span>\n <span>background-color</span><span>:</span> white<span>;</span>\n <span>color</span><span>:</span> <span>hsl</span><span>(</span>210<span>,</span> 15%<span>,</span> 20%<span>)</span><span>;</span>\n <span>box-shadow</span><span>:</span> 0.2em 0.5em 1em <span>rgba</span><span>(</span>0<span>,</span> 0<span>,</span> 0<span>,</span> 0.3<span>)</span><span>;</span>\n<span>}</span>\n<span>.card > img</span> <span>{</span>\n <span>width</span><span>:</span> 100%<span>;</span>\n<span>}</span>\n\n<span><span>@keyframes</span> fly-in</span> <span>{</span>\n <span>0%</span> <span>{</span>\n <span>transform</span><span>:</span> <span>translateZ</span><span>(</span>-800px<span>)</span> <span>rotateY</span><span>(</span>90deg<span>)</span><span>;</span>\n <span>opacity</span><span>:</span> 0<span>;</span>\n <span>}</span>\n <span>56%</span> <span>{</span>\n <span>transform</span><span>:</span> <span>translateZ</span><span>(</span>-160px<span>)</span> <span>rotateY</span><span>(</span>87deg<span>)</span><span>;</span>\n <span>opacity</span><span>:</span> 1<span>;</span>\n <span>}</span>\n <span>100%</span> <span>{</span>\n <span>transform</span><span>:</span> <span>translateZ</span><span>(</span>0<span>)</span> <span>rotateY</span><span>(</span>0<span>)</span><span>;</span>\n <span>}</span>\n<span>}</span>\n</span></span><span><span><span></</span>style</span><span>></span></span>\n\n<span><span><span><</span>main</span> <span>class</span><span><span>=</span><span>\"</span>flyin-grid<span>\"</span></span><span>></span></span>\n <span><span><span><</span>div</span> <span>class</span><span><span>=</span><span>\"</span>flyin-grid__item card<span>\"</span></span><span>></span></span>\n <span><span><span><</span>img</span> <span>src</span><span><span>=</span><span>\"</span>images/chicken1.jpg<span>\"</span></span> <span>alt</span><span><span>=</span><span>\"</span>a chicken<span>\"</span></span><span>/></span></span>\n <span><span><span><</span>h4</span><span>></span></span>Mrs. Featherstone<span><span><span></</span>h4</span><span>></span></span>\n <span><span><span><</span>p</span><span>></span></span>\n She may be a bit frumpy, but Mrs Featherstone gets\n the job done. She lays her largish cream-colored\n eggs on a daily basis. She is gregarious to a fault.\n <span><span><span></</span>p</span><span>></span></span>\n <span><span><span><</span>p</span><span>></span></span>This Austra White is our most prolific producer.<span><span><span></</span>p</span><span>></span></span>\n <span><span><span></</span>div</span><span>></span></span>\n <span><span><span><</span>div</span> <span>class</span><span><span>=</span><span>\"</span>flyin-grid__item card<span>\"</span></span><span>></span></span>\n <span><span><span><</span>img</span> <span>src</span><span><span>=</span><span>\"</span>images/chicken2.jpg<span>\"</span></span> <span>alt</span><span><span>=</span><span>\"</span>a chicken<span>\"</span></span><span>/></span></span>\n <span><span><span><</span>h4</span><span>></span></span>Hen Solo<span><span><span></</span>h4</span><span>></span></span>\n <span><span><span><</span>p</span><span>></span></span>\n Though the most recent addition to our flock, Hen\n Solo is a fast favorite among our laying brood. She\n is a sassy and suspicious hen; we frequently have to\n follow her to find where she has hidden her loot from\n the other hens.\n <span><span><span></</span>p</span><span>></span></span>\n <span><span><span><</span>p</span><span>></span></span>This Snowy Easter Egger lays in delicate shades of\n blue and green. A full dozen of her eggs costs an\n additional $2.<span><span><span></</span>p</span><span>></span></span>\n <span><span><span></</span>div</span><span>></span></span>\n <span><span><span><</span>div</span> <span>class</span><span><span>=</span><span>\"</span>flyin-grid__item card<span>\"</span></span><span>></span></span>\n <span><span><span><</span>img</span> <span>src</span><span><span>=</span><span>\"</span>images/chicken3.jpg<span>\"</span></span> <span>alt</span><span><span>=</span><span>\"</span>a chicken<span>\"</span></span><span>/></span></span>\n <span><span><span><</span>h4</span><span>></span></span>Cluck Norris<span><span><span></</span>h4</span><span>></span></span>\n <span><span><span><</span>p</span><span>></span></span>\n Every brood has its brawler. Cluck Norris is our\n feistiest hen, frequently picking fights with other\n hens about laying territory and foraging space. Her\n sister hens continue to hope that she will follow the\n steps of her namesake (eventually) and focus the her\n strength of will for good.\n <span><span><span></</span>p</span><span>></span></span>\n <span><span><span><</span>p</span><span>></span></span>This Buff Chantecler is as robust and hardy as her\n Canadian forebears, laying through the coldest parts\n of the winter.<span><span><span></</span>p</span><span>></span></span>\n <span><span><span></</span>div</span><span>></span></span>\n<span><span><span></</span>main</span><span>></span></span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br><span>25</span><br><span>26</span><br><span>27</span><br><span>28</span><br><span>29</span><br><span>30</span><br><span>31</span><br><span>32</span><br><span>33</span><br><span>34</span><br><span>35</span><br><span>36</span><br><span>37</span><br><span>38</span><br><span>39</span><br><span>40</span><br><span>41</span><br><span>42</span><br><span>43</span><br><span>44</span><br><span>45</span><br><span>46</span><br><span>47</span><br><span>48</span><br><span>49</span><br><span>50</span><br><span>51</span><br><span>52</span><br><span>53</span><br><span>54</span><br><span>55</span><br><span>56</span><br><span>57</span><br><span>58</span><br><span>59</span><br><span>60</span><br><span>61</span><br><span>62</span><br><span>63</span><br><span>64</span><br><span>65</span><br><span>66</span><br><span>67</span><br><span>68</span><br><span>69</span><br><span>70</span><br><span>71</span><br><span>72</span><br><span>73</span><br><span>74</span><br><span>75</span><br><span>76</span><br><span>77</span><br><span>78</span><br><span>79</span><br><span>80</span><br><span>81</span><br><span>82</span><br><span>83</span><br><span>84</span><br><span>85</span><br><span>86</span><br><span>87</span><br><span>88</span><br><span>89</span><br><span>90</span><br><span>91</span><br><span>92</span><br><span>93</span><br><span>94</span><br><span>95</span><br><span>96</span><br><span>97</span><br><span>98</span><br><span>99</span><br><span>100</span><br><span>101</span><br><span>102</span><br><span>103</span><br><span>104</span><br><span>105</span><br><span>106</span><br><span>107</span><br><span>108</span><br><span>109</span><br><span>110</span><br><span>111</span><br><span>112</span><br><span>113</span><br><span>114</span><br><span>115</span><br><span>116</span><br><span>117</span><br><span>118</span><br></div></div><p>以上代码有一个问题:后面的元素在动画还没开始播放的时候就出现在了最终位置,开始播放时才转到动画初始位置。</p>\n<p>使用animation-fill-mode可以在动画播放前或播放后应用动画样式。</p>\n<ul>\n<li>初始值是none--意思是动画执行前或执行后动画样式都不会应用到元素上。</li>\n<li>backwards--在动画执行之前,浏览器就会取出动画中第一帧的值,并把它们应用在元素上。</li>\n<li>forwards--在动画播放完成后仍然应用最后一帧的值。</li>\n<li>both--会同时向前和向后填充。<br>\n为页面添加后向填充模式可以修复动画开始时的元素跳动。</li>\n</ul>\n<h3 id=\"用动画传递意图\"> 用动画传递意图</h3>\n<p>好的动画不是最后才加上的,而是融入到了开发过程中。它们向用户传达页面上某些事物的特殊含义。<br>\n动画可以向用户表明按钮被点击了或者消息被接收了。如果你曾经提交过表单,回想一下是否经常记不清自己点没点过注册按钮,就知道这有多重要了。</p>\n<p><a href=\"https://animista.net/play/basic\" target=\"_blank\" rel=\"noopener noreferrer\">CSS Animista</a></p>\n",
"image": "https://kigane.github.io/assets/img/radial-gradient.png",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "CSS布局",
"url": "https://kigane.github.io/note/js/css-layout/",
"id": "https://kigane.github.io/note/js/css-layout/",
"content_html": "<h2 id=\"浮动布局\"> 浮动布局</h2>\n<p>要实现将图片移动到网页一侧,并且让文字围绕图片的效果,浮动是唯一的方法。这也是设计浮动的初衷。</p>\n<h3 id=\"双容器模式\"> 双容器模式</h3>\n<p>通过将内容放置到两个嵌套的容器中,然后给内层的容器设置外边距,让它在外层容器中居中。</p>\n<details><summary>示例代码</summary>\n<p>双容器外层为body,内层为.container</p>\n<div><pre><code><span><span><!</span><span>doctype</span> <span>html</span><span>></span></span>\n<span><span><span><</span>head</span><span>></span></span>\n <span><span><span><</span>style</span><span>></span></span><span><span>\n <span>/* 全局设置为border-box */</span>\n <span>:root</span> <span>{</span>\n <span>box-sizing</span><span>:</span> border-box<span>;</span>\n <span>}</span>\n\n <span>*,\n ::before,\n ::after</span> <span>{</span>\n <span>box-sizing</span><span>:</span> inherit<span>;</span>\n <span>}</span>\n\n <span>body</span> <span>{</span>\n <span>background-color</span><span>:</span> #eee<span>;</span>\n <span>font-family</span><span>:</span> Helvetica<span>,</span> Arial<span>,</span> sans-serif<span>;</span>\n <span>}</span>\n\n <span>/* 猫头鹰选择器 */</span>\n <span>body * + *</span> <span>{</span>\n <span>margin-top</span><span>:</span> 1.5em<span>;</span>\n <span>}</span>\n\n <span>header</span> <span>{</span>\n <span>padding</span><span>:</span> 1em 1.5em<span>;</span>\n <span>color</span><span>:</span> #fff<span>;</span>\n <span>background-color</span><span>:</span> #0072b0<span>;</span>\n <span>border-radius</span><span>:</span> .5em<span>;</span>\n <span>margin-bottom</span><span>:</span> 1.5em<span>;</span>\n <span>}</span>\n\n <span>.main</span> <span>{</span>\n <span>padding</span><span>:</span> 0 1.5em<span>;</span>\n <span>background-color</span><span>:</span> #fff<span>;</span>\n <span>border-radius</span><span>:</span> .5em<span>;</span>\n <span>}</span>\n\n <span>.container</span> <span>{</span>\n <span>max-width</span><span>:</span> 1080px<span>;</span>\n <span>margin</span><span>:</span> 0 auto<span>;</span>\n <span>}</span>\n\n </span></span><span><span><span></</span>style</span><span>></span></span>\n<span><span><span></</span>head</span><span>></span></span>\n\n<span><span><span><</span>body</span><span>></span></span>\n <span><span><span><</span>div</span> <span>class</span><span><span>=</span><span>\"</span>container<span>\"</span></span><span>></span></span>\n <span><span><span><</span>header</span><span>></span></span>\n <span><span><span><</span>h1</span><span>></span></span>Franklin Running Club<span><span><span></</span>h1</span><span>></span></span>\n <span><span><span></</span>header</span><span>></span></span>\n\n <span><span><span><</span>main</span> <span>class</span><span><span>=</span><span>\"</span>main clearfix<span>\"</span></span><span>></span></span>\n <span><span><span><</span>h2</span><span>></span></span>Running tips<span><span><span></</span>h2</span><span>></span></span>\n\n <span><span><span><</span>div</span><span>></span></span>\n <span><span><span><</span>div</span> <span>class</span><span><span>=</span><span>\"</span>media<span>\"</span></span><span>></span></span>\n <span><span><span><</span>img</span> <span>class</span><span><span>=</span><span>\"</span>media-image<span>\"</span></span> <span>src</span><span><span>=</span><span>\"</span>shoes.png<span>\"</span></span><span>></span></span>\n <span><span><span><</span>div</span> <span>class</span><span><span>=</span><span>\"</span>media-body<span>\"</span></span><span>></span></span>\n <span><span><span><</span>h4</span><span>></span></span>Strength<span><span><span></</span>h4</span><span>></span></span>\n <span><span><span><</span>p</span><span>></span></span>\n Strength training is an important part of\n injury prevention. Focus on your core<span title=\"—\">&mdash;</span>\n especially your abs and glutes.\n <span><span><span></</span>p</span><span>></span></span>\n <span><span><span></</span>div</span><span>></span></span>\n <span><span><span></</span>div</span><span>></span></span>\n\n <span><span><span><</span>div</span> <span>class</span><span><span>=</span><span>\"</span>media<span>\"</span></span><span>></span></span>\n <span><span><span><</span>img</span> <span>class</span><span><span>=</span><span>\"</span>media-image<span>\"</span></span> <span>src</span><span><span>=</span><span>\"</span>runner.png<span>\"</span></span><span>></span></span>\n <span><span><span><</span>div</span> <span>class</span><span><span>=</span><span>\"</span>media-body<span>\"</span></span><span>></span></span>\n <span><span><span><</span>h4</span><span>></span></span>Cadence<span><span><span></</span>h4</span><span>></span></span>\n <span><span><span><</span>p</span><span>></span></span>\n Check your stride turnover. The most efficient\n runners take about 180 steps per minute.\n <span><span><span></</span>p</span><span>></span></span>\n <span><span><span></</span>div</span><span>></span></span>\n <span><span><span></</span>div</span><span>></span></span>\n\n <span><span><span><</span>div</span> <span>class</span><span><span>=</span><span>\"</span>media<span>\"</span></span><span>></span></span>\n <span><span><span><</span>img</span> <span>class</span><span><span>=</span><span>\"</span>media-image<span>\"</span></span> <span>src</span><span><span>=</span><span>\"</span>runner.png<span>\"</span></span><span>></span></span>\n <span><span><span><</span>div</span> <span>class</span><span><span>=</span><span>\"</span>media-body<span>\"</span></span><span>></span></span>\n <span><span><span><</span>h4</span><span>></span></span>Change it up<span><span><span></</span>h4</span><span>></span></span>\n <span><span><span><</span>p</span><span>></span></span>\n Don't run the same every time you hit the\n road. Vary your pace, and vary the distance\n of your runs.\n <span><span><span></</span>p</span><span>></span></span>\n <span><span><span></</span>div</span><span>></span></span>\n <span><span><span></</span>div</span><span>></span></span>\n\n <span><span><span><</span>div</span> <span>class</span><span><span>=</span><span>\"</span>media<span>\"</span></span><span>></span></span>\n <span><span><span><</span>img</span> <span>class</span><span><span>=</span><span>\"</span>media-image<span>\"</span></span> <span>src</span><span><span>=</span><span>\"</span>shoes.png<span>\"</span></span><span>></span></span>\n <span><span><span><</span>div</span> <span>class</span><span><span>=</span><span>\"</span>media-body<span>\"</span></span><span>></span></span>\n <span><span><span><</span>h4</span><span>></span></span>Focus on form<span><span><span></</span>h4</span><span>></span></span>\n <span><span><span><</span>p</span><span>></span></span>\n Run tall but relaxed. Your feet should hit\n the ground beneath your hips, not out in\n front of you.\n <span><span><span></</span>p</span><span>></span></span>\n <span><span><span></</span>div</span><span>></span></span>\n <span><span><span></</span>div</span><span>></span></span>\n\n <span><span><span></</span>div</span><span>></span></span>\n <span><span><span></</span>main</span><span>></span></span>\n <span><span><span></</span>div</span><span>></span></span>\n<span><span><span></</span>body</span><span>></span></span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br><span>25</span><br><span>26</span><br><span>27</span><br><span>28</span><br><span>29</span><br><span>30</span><br><span>31</span><br><span>32</span><br><span>33</span><br><span>34</span><br><span>35</span><br><span>36</span><br><span>37</span><br><span>38</span><br><span>39</span><br><span>40</span><br><span>41</span><br><span>42</span><br><span>43</span><br><span>44</span><br><span>45</span><br><span>46</span><br><span>47</span><br><span>48</span><br><span>49</span><br><span>50</span><br><span>51</span><br><span>52</span><br><span>53</span><br><span>54</span><br><span>55</span><br><span>56</span><br><span>57</span><br><span>58</span><br><span>59</span><br><span>60</span><br><span>61</span><br><span>62</span><br><span>63</span><br><span>64</span><br><span>65</span><br><span>66</span><br><span>67</span><br><span>68</span><br><span>69</span><br><span>70</span><br><span>71</span><br><span>72</span><br><span>73</span><br><span>74</span><br><span>75</span><br><span>76</span><br><span>77</span><br><span>78</span><br><span>79</span><br><span>80</span><br><span>81</span><br><span>82</span><br><span>83</span><br><span>84</span><br><span>85</span><br><span>86</span><br><span>87</span><br><span>88</span><br><span>89</span><br><span>90</span><br><span>91</span><br><span>92</span><br><span>93</span><br><span>94</span><br><span>95</span><br><span>96</span><br><span>97</span><br><span>98</span><br><span>99</span><br><span>100</span><br><span>101</span><br><span>102</span><br><span>103</span><br><span>104</span><br><span>105</span><br><span>106</span><br><span>107</span><br></div></div></details>\n<h3 id=\"容器折叠\"> 容器折叠</h3>\n<p>浮动元素不同于普通文档流的元素,它们的高度不会加到父元素上。这会造成容器折叠问题(容器内所有元素都浮动了,容器的高度就撑不开)。</p>\n<p>解决方案1<br>\n因为浮动元素的外边距不会折叠到清除浮动容器的外部,非浮动元素的外边距则会正常折叠。所以h2和.container的外边距折叠了,h2的内容紧贴在容器顶部。</p>\n<div><pre><code><span>.clearfix::after</span> <span>{</span>\n <span>display</span><span>:</span> block<span>;</span> <span>/* clear只对块级元素有效。 */</span>\n <span>content</span><span>:</span> <span>\" \"</span><span>;</span> <span>/* 设置content让伪元素出现在文档中 */</span>\n <span>clear</span><span>:</span> both<span>;</span> <span>/* clear: both声明让该元素移动到浮动元素的下面,而不是侧面。clear的值还可以设置为left或者right,这样只会相应地清除向左或者向右浮动的元素。因为空div本身没有浮动,所以容器就会扩展,直到包含它,因此也会包含该div上面的浮动元素。 */</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br></div></div><p>解决方案2<br>\n使用display: table能够包含外边距,是因为利用了CSS的一些特性。创建一个display: table元素,也就在元素内隐式创建了一个表格行和一个单元格。外边距无法通过单元格元素折叠,从而所有子元素的外边距都会包含在容器的顶部和底部之间。</p>\n<div><pre><code><span>.clearfix::before,\n.clearfix::after</span> <span>{</span>\n <span>display</span><span>:</span> table<span>;</span>\n <span>content</span><span>:</span> <span>\" \"</span><span>;</span>\n<span>}</span>\n<span>.clearfix::after</span> <span>{</span>\n <span>clear</span><span>:</span> both<span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br></div></div><h3 id=\"浮动陷阱\"> 浮动陷阱</h3>\n<p>第一个元素下有很大一片空白。\n<img src=\"/assets/img/float-trap.png\" alt=\"浮动陷阱\" /></p>\n<p>要想修复这个问题很简单:清除第三个浮动元素上面的浮动。更通用的做法是,清除每行的第一个元素上面的浮动。</p>\n<div><pre><code><span>.media</span> <span>{</span>\n <span>float</span><span>:</span> left<span>;</span>\n <span>margin</span><span>:</span> 0 1.5em 1.5em 0<span>;</span> <span>/* 猫头鹰选择器会导致第一个元素没有margin-top,从而第一行顶端对不齐。故重设 */</span>\n <span>width</span><span>:</span> <span>calc</span><span>(</span>50% - 1.5em<span>)</span><span>;</span> <span>/* 为margin留出空间 */</span>\n <span>padding</span><span>:</span> 1.5em<span>;</span>\n <span>background-color</span><span>:</span> #eee<span>;</span>\n <span>border-radius</span><span>:</span> 0.5em<span>;</span>\n<span>}</span>\n\n<span>/* 清除每行的第一个元素上面的浮动 */</span>\n<span>.media:nth-child(odd)</span> <span>{</span>\n <span>clear</span><span>:</span> left<span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br></div></div><h3 id=\"媒体对象\"> 媒体对象</h3>\n<p>图片在一侧,一段文字出现在图片的旁边。这是一种很典型的网页布局,称为“媒体对象”。<br>\n块级格式化上下文(block formatting context, BFC)<br>\nBFC是网页的一块区域,元素基于这块区域布局。虽然BFC本身是环绕文档流的一部分,但它将内部的内容与外部的上下文隔离开。这种隔离为创建BFC的元素做出了以下3件事情。</p>\n<ol>\n<li>包含了内部所有元素的上下外边距。它们不会跟BFC外面的元素产生外边距折叠。</li>\n<li>包含了内部所有的浮动元素。</li>\n<li>不会跟BFC外面的浮动元素重叠。</li>\n</ol>\n<hr>\n<p>给元素添加以下的任意属性值都会创建BFC。</p>\n<ul>\n<li>float: left或right,不为none即可。</li>\n<li>overflow:hidden、auto或scroll,不为visible即可。最简单,推荐使用。</li>\n<li>display:inline-block、table-cell、table-caption、flex、inline-flex、grid或inline-grid。拥有这些属性的元素称为块级容器。</li>\n<li>position:absolute或position: fixed。</li>\n</ul>\n<h3 id=\"网格系统\"> 网格系统</h3>\n<p>大部分流行的CSS框架包含了自己的网格系统。它们的实现细节各不相同,但是设计思想相同:在一个行容器里放置一个或多个列容器。列容器的类决定每列的宽度。</p>\n<ul>\n<li>通常网格系统的每行被划分为特定数量的列,一般是12个,但也可以是其他数。每行子元素的宽度可能等于1~12个列的宽度。</li>\n<li>行元素负责清除浮动,设置负外边距调整列头尾对齐,等等。</li>\n<li>列元素都浮动,用百分比宽度+不同类控制列宽(<code>.column-n{width:xx%}</code>)。column-</li>\n</ul>\n<h2 id=\"flexbox\"> Flexbox</h2>\n<ul>\n<li>给元素添加display: flex,该元素变成了一个弹性容器(flex container),它的直接子元素变成了弹性子元素(flex item)。</li>\n<li>弹性子元素默认是在同一行按照从左到右的顺序并排排列。</li>\n<li>弹性容器像块元素一样填满可用宽度,但是弹性子元素不一定填满其弹性容器的宽度。</li>\n<li>弹性子元素高度相等,该高度由它们的内容决定。</li>\n<li>子元素按照主轴线排列,主轴的方向为主起点(左)到主终点(右)。垂直于主轴的是副轴。方向从副起点(上)到副终点(下)。</li>\n<li>Flexbox允许使用margin: auto来填充弹性子元素之间所有的可用空间</li>\n<li>flex属性控制弹性子元素在主轴方向上的大小,在这里指的元素的宽度。flex属性是三个不同大小属性的简写:flex-grow、flex-shrink和flex-basis。\n<ul>\n<li>flex-basis:默认值为0%,即不占任何宽度。flex-basis定义了元素大小的基准值,即一个初始的“主尺寸”。flex-basis属性可以设置为任意的width值,包括px、em、百分比。它的初始值是auto,此时浏览器会检查元素是否设置了width属性值。如果有,则使用width的值作为flex-basis的值;如果没有,则用元素内容自身的大小。如果flex-basis的值不是auto, width属性会被忽略。</li>\n<li>flex-grow:默认值为1。每个弹性子元素的flex-basis值计算出来后,它们(加上子元素之间的外边距)加起来会占据一定的宽度。加起来的宽度不一定正好填满弹性容器的宽度,可能会有留白。多出来的留白(或剩余宽度)会按照flex-grow(增长因子)的值分配给每个弹性子元素,flex-grow的值为<strong>非负整数</strong>。如果一个弹性子元素的flex-grow值为0,那么它的宽度不会超过flex-basis的值;如果某个弹性子元素的增长因子非0,那么这些元素会增长到所有的剩余空间被分配(按给定的权重分配)完,也就意味着弹性子元素会填满容器的宽度。</li>\n<li>flex-shrink:默认值为1。lex-shrink属性与flex-grow遵循相似的原则。计算出弹性子元素的初始主尺寸后,它们的累加值可能会超出弹性容器的可用宽度。如果不用flex-shrink,就会导致溢出。每个子元素的flex-shrink值代表了它是否应该收缩以防止溢出。如果某个子元素为flex-shrink: 0,则不会收缩;如果值大于0,则会收缩至不再溢出。按照flex-shrink值的比例,值越大的元素收缩得越多。</li>\n<li>PS:当flex-basis为0%时,内边距会改变弹性子元素的初始主宽度计算的方式。\n<img src=\"/assets/img/flux-layout-example.png\" alt=\"examples\" /></li>\n</ul>\n</li>\n<li>Flexbox的另一个重要功能是能用弹性容器的flex-direction属性切换主副轴方向。它的初始值(row)控制子元素按从左到右的方向排列;指定flex-direction: column能控制弹性子元素沿垂直方向排列(从上到下)。Flexbox还支持row-reverse让元素从右到左排列,column-reverse让元素从下到上排列</li>\n</ul>\n<h3 id=\"input\"> input</h3>\n<ul>\n<li>input元素可以是文本和密码输入框以及很多类似的HTML5输入框,比如数字、邮箱、日期输入框。它还可以是看起来完全不一样的输入元素,即单选按钮和复选框。<code>input:not([type=checkbox]):not([type=radio])</code>可以排除单选按钮和复选框。</li>\n<li>对input设置了display: block,让它们单独占据一行,还要将其宽度设置为100%。通常情况下,块级元素会自动填满可用宽度,但是input比较特殊,其宽度由size属性决定,而它表示不出滚动条的情况下大致能容纳的字符数量。如果不指定的话,该属性就会恢复为默认值。</li>\n</ul>\n<h3 id=\"更多属性\"> 更多属性</h3>\n<p>flex-container\n<img src=\"/assets/img/flex-more1.png\" alt=\"flex-container\" />\nflex-item\n<img src=\"/assets/img/flex-more2.png\" alt=\"flex-item\" /></p>\n<h2 id=\"网格布局\"> 网格布局</h2>\n<ul>\n<li>跟Flexbox类似,网格布局也是作用于两级的DOM结构。设置为display: grid的元素成为一个网格容器(grid container)。它的子元素则变成网格元素(griditems)。容器会表现得像一个块级元素,100%填充可用宽度。</li>\n<li>容器的grid-template-columns和grid-template-rows。这两个属性定义了网格每行每列的大小。新单位fr,代表每一列(或每一行)的分数单位(fraction unit)。这个单位跟Flexbox中flex-grow因子的表现一样。</li>\n<li>容器的grid-gap属性定义了每个网格单元之间的间距。也可以用两个值分别指定垂直和水平方向的间距(比如grid-gap: 0.5em 1em)。</li>\n</ul>\n<hr>\n<ul>\n<li>网格的组成部分\n<ul>\n<li>网格线(grid line)——网格线构成了网格的框架。一条网格线可以水平或垂直,也可以位于一行或一列的任意一侧。如果指定了grid-gap的话,它就位于网格线上。</li>\n<li>网格轨道(grid track)——一个网格轨道是两条相邻网格线之间的空间。网格有水平轨道(行)和垂直轨道(列)。</li>\n<li>网格单元(grid cell)——网格上的单个空间,水平和垂直的网格轨道交叉重叠的部分。</li>\n<li>网格区域(grid area)——网格上的矩形区域,由一个到多个网格单元组成。该区域位于两条垂直网格线和两条水平网格线之间。</li>\n<li>repeat(num, pat1, ...):将pat重复num次。类似宏。例如,repeat(4, auto) === auto auto auto auto。</li>\n</ul>\n</li>\n<li>网格线编号从左上角为1开始递增,负数则指向从右下角开始的位置。</li>\n<li>网格元素定位:\n<ul>\n<li>grid-column, grid-row:两个属性,有四个值,代表四个网格线,切出的封闭矩形即为元素占据的区域。例如:grid-column:1/3; grid-row:2/4;在九宫格中代表左下四个格子。</li>\n<li>span n 表示扩展至n格。例如,grid-row:span 1 表示在水平网格轨道只占一格。</li>\n</ul>\n</li>\n<li>另一种定位方式--使用命名的网格区域\n<ul>\n<li>grid-template-areas属性:值为一系列加引号字符串,每一个字符串代表网格的一行,字符串内用空格区分每一列。在CSS中画一个可视化的网格形象。句点,代表留空。必须每个网格都要命名。</li>\n<li>例如<code>"title title"\\n "nav nav"\\n "main aside1"\\n "main aside2"\\n</code></li>\n</ul>\n</li>\n<li>和Flex的对比:\n<ul>\n<li>Flexbox本质上是一维的,而网格是二维的。</li>\n<li>Flexbox是以内容为切入点由内向外工作的,而网格是以布局为切入点从外向内工作的。用网格给网页的主区域定位是因为我们希望内容能限制在它所在的网格内,但是对于网页上的其他元素,比如导航菜单,则允许内容对布局有更大的影响。也就是说,文字多的元素可以宽一些,文字少的元素则可以窄一些。同时这还是一个水平(一维)布局。因此,用Flexbox来处理这些元素更合适。</li>\n</ul>\n</li>\n<li>当设计要求元素在两个维度上都对齐时,使用网格。当只关心一维的元素排列时,使用Flexbox。这意味着网格更适合用于整体的网页布局,而Flexbox更适合对网格区域内的特定元素布局。</li>\n</ul>\n<hr>\n<ul>\n<li>\n<p>隐式网格(implicit grid)。使用grid-template-*属性定义网格轨道时,创建的是显式网格(explicit grid),但如果网格元素放在声明的网格轨道之外,就会创建隐式轨道以扩展网格,直到包含该元素。</p>\n</li>\n<li>\n<p>隐式网格轨道默认大小为auto,也就是它们会扩展到能容纳网格元素内容。可以给网格容器设置grid-auto-columns和grid-auto-rows,为隐式网格轨道指定一个大小(比如,grid-auto-columns: 1fr)。</p>\n</li>\n<li>\n<p>有时候我们不想给一个网格轨道设置固定尺寸,但是又希望限制它的最小值和最大值。这时候需要用到minmax()函数。它指定两个值:最小尺寸和最大尺寸。浏览器会确保网格轨道的大小介于这两者之间。(如果最大尺寸小于最小尺寸,最大尺寸就会被忽略。)通过指定minmax(200px, 1fr),浏览器确保了所有的轨道至少宽200px。</p>\n</li>\n<li>\n<p>repeat()函数里的auto-fill关键字是一个特殊值。设置了之后,只要网格放得下,浏览器就会尽可能多地生成轨道,并且不会跟指定大小(minmax()值)的限制产生冲突。</p>\n</li>\n<li>\n<p>auto-fill和minmax(200px, 1fr)加在一起,就会让网格在可用的空间内尽可能多地产生网格列,并且每个列的宽度不会小于200px。因为所有轨道的大小上限都为1fr(最大值),所以所有的网格轨道都等宽。</p>\n</li>\n<li>\n<p>如果网格元素不够填满所有网格轨道,auto-fill就会导致一些空的网格轨道。如果不希望出现空的网格轨道,可以使用auto-fit关键字代替auto-fill。它会让非空的网格轨道扩展,填满可用空间。</p>\n</li>\n</ul>\n<hr>\n<ul>\n<li>当不指定网格上元素的位置时,元素会按照其布局算法自动放置。\n<ul>\n<li>默认情况下,布局算法会按元素在标记中的顺序将其逐列逐行摆放。</li>\n<li>grid-auto-flow:row 如果一行放不下,算法会将它移动到下一行,寻找足够大的空间容纳它。</li>\n<li>grid-auto-flow:column 如果一列放不下,算法会将它移动到下一列,寻找足够大的空间容纳它。</li>\n<li>还可以额外加一个关键字dense(比如,grid-auto-flow: column dense)。它让算法紧凑地填满网格里的空白,尽管这会改变某些网格元素的顺序。</li>\n</ul>\n</li>\n<li>默认情况下,每个网格元素都会扩展并填满整个网格区域,但是子元素不会,因此网格区域可能会出现多余的高度。一个简单的解决办法是用Flexbox。给图片标签加上flex-grow,强制拉伸图片填充空白区域。但是拉伸图片并不可取,因为这会改变图片的宽高比,导致图片变形。好在CSS为控制这一行为提供了一个特殊属性object-fit。默认情况下,一个img的object-fit属性值为fill,也就是说整个图片会缩放,以填满img元素。你也可以设置其他值改变默认行为。\n<ul>\n<li>cover:扩展图片,让它填满盒子(导致图片一部分被裁剪)。</li>\n<li>contain:缩放图片,让它完整地填充盒子(导致盒子里出现空白)。\n<img src=\"/assets/img/img-object-fit.png\" alt=\"图片缩放\" /></li>\n</ul>\n</li>\n</ul>\n<hr>\n<p>对齐</p>\n<ul>\n<li>CSS给网格布局提供了三个调整属性:justify-content、justify-items、justify-self。这些属性控制了网格元素在水平方向上的位置。</li>\n<li>还有三个对齐属性:align-content、align-items、align-self。这些属性控制网格元素在垂直方向上的位置。</li>\n</ul>\n<h3 id=\"特性查询\"> 特性查询</h3>\n<p><code>@supports (display:grid) {...}</code></p>\n<ul>\n<li>@supports规则后面跟着一个小括号包围的声明。如果浏览器理解这个声明,它就会使用大括号里面的所有样式规则。如果它不理解小括号里的声明,就不会使用这些样式规则。</li>\n<li>@supports not(declaration)</li>\n<li>@supports (declaration) or (declaration)</li>\n<li>@supports (declaration) and (declaration)</li>\n</ul>\n<h2 id=\"定位\"> 定位</h2>\n<p>position属性的初始值是static。前面的章节里用的都是这个静态定位。如果把它改成其他值,我们就说元素就被定位了。而如果元素使用了静态定位,那么就说它未被定位。</p>\n<p>前面介绍的布局方法是用各种操作来控制文档流的行为。定位则不同:它将元素彻底从文档流中移走。它允许你将元素放在屏幕的任意位置。还可以将一个元素放在另一个元素的前面或后面,彼此重叠。</p>\n<h3 id=\"固定定位\"> 固定定位</h3>\n<p>给一个元素设置position: fixed就能将元素放在视口的任意位置。<br>\n这需要搭配四种属性一起使用:top、right、bottom和left。这些属性的值决定了固定定位的元素与浏览器视口边缘的距离。比如,top: 3em表示元素的上边缘距离视口顶部3em。<br>\n设置这四个值还隐式地定义了元素的宽高。比如指定left: 2em; right: 2em表示元素的左边缘距离视口左边2em,右边缘距离视口右边2em。因此元素的宽度等于视口总宽度减去4em。top、bottom和视口高度也是这样的关系。</p>\n<details><summary>模态框背景</summary>\n<p>用隐式定义的宽高拉伸空div,再设置背景色。</p>\n<div><pre><code><span><span><span><</span>div</span> <span>class</span><span><span>=</span><span>\"</span>modal-backdrop<span>\"</span></span><span>></span></span><span><span><span></</span>div</span><span>></span></span>\n<span><span><span><</span>style</span><span>></span></span><span><span>\n<span>.modal-backdrop</span> <span>{</span>\n <span>position</span><span>:</span> fixed<span>;</span>\n <span>top</span><span>:</span> 0<span>;</span>\n <span>right</span><span>:</span> 0<span>;</span>\n <span>bottom</span><span>:</span> 0<span>;</span>\n <span>left</span><span>:</span> 0<span>;</span>\n <span>background-color</span><span>:</span> <span>rgba</span><span>(</span>0<span>,</span> 0<span>,</span> 0<span>,</span> 0.5<span>)</span><span>;</span>\n<span>}</span>\n</span></span><span><span><span></</span>style</span><span>></span></span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br></div></div></details>\n<h3 id=\"绝对定位\"> 绝对定位</h3>\n<ul>\n<li>固定定位让元素相对视口定位,此时视口被称作元素的包含块(containingblock)。绝对定位的行为也是如此,只是它的包含块不一样。绝对定位不是相对视口,而是相对最近的<strong>祖先定位元素</strong>。</li>\n<li>如果祖先元素都没有定位,那么绝对定位的元素会基于初始包含块(initial containing block)来定位。初始包含块跟视口一样大,固定在网页的顶部。</li>\n</ul>\n<details><summary>关闭按钮</summary>\n<p>对于这种Close按钮,用户通常期望看到一个类似于x的图形化显示,如何做到?<br>\n你可能首先想到将按钮里的文字close换成x,但是这会导致可访问性的问题:辅助的屏幕阅读器会读按钮里的文字。因此要给这个按钮一些有意义的提示。在使用CSS之前,HTML本身必须有意义。<br>\n相反,你可以用CSS隐藏close,并显示x。总共需要两步。</p>\n<ol>\n<li>将按钮的文字挤到外面,并隐藏溢出内容。 text-indent:xxem+overflow:hidden</li>\n<li>将按钮的::after伪元素的content属性设置为x,并让伪元素绝对定位到按钮中间。<br>\nPS:乘法符号的Unicode字符更对称,也更好看。HTML字符&times;可以显示为这个字符,但在CSS的content属性里,必须写成转义的Unicode数字:\\00D7。</li>\n</ol>\n</details>\n<h3 id=\"相对定位\"> 相对定位</h3>\n<p>相对定位将元素从初始位置移走,但是其他元素不受影响(好像被移走的元素还在原来的位置一样)。<br>\n跟固定或者绝对定位不一样,不能用top、right、bottom和left改变相对定位元素的大小。这些值只能让元素在上、下、左、右方向移动。可以用top或者bottom,但它们不能一起用(bottom会被忽略)。同理,可以用left或right,但它们也不能一起用(right会被忽略)。<br>\n可以用这些属性调整相对元素的位置,把它挤到某个位置,但这只是相对定位的一个冷门用法。更常见的用法是使用position: relative给它里面的绝对定位元素创建一个包含块。</p>\n<h3 id=\"层叠上下文和z-index\"> 层叠上下文和z-index</h3>\n<p>浏览器将HTML解析为DOM的同时还创建了另一个树形结构,叫作渲染树(render tree)。它代表了每个元素的视觉样式和位置。同时还决定浏览器绘制元素的顺序。</p>\n<ul>\n<li>顺序很重要,因为如果元素刚好重叠,后绘制的元素就会出现在先绘制的元素前面。</li>\n<li>通常情况下(使用定位之前),元素在HTML里出现的顺序决定了绘制的顺序。</li>\n<li>定位元素时,这种行为会改变。浏览器会先绘制所有非定位的元素,然后绘制定位元素。默认情况下,所有的定位元素会出现在非定位元素前面。</li>\n<li>通常情况下,模态框要放在网页内容的最后,body关闭标签之前。以防止模态框被其他定位元素遮挡。</li>\n<li>z-index属性的值可以是任意整数(正负都行)。z表示的是笛卡儿x-y-z坐标系里的深度方向。拥有较高z-index的元素出现在拥有较低z-index的元素前面。拥有负数z-index的元素出现在静态元素后面。\n<ul>\n<li>z-index只在定位元素上生效,不能用它控制静态元素。</li>\n<li>给一个定位元素加上z-index可以创建层叠上下文。</li>\n</ul>\n</li>\n<li>层叠上下文之外的元素无法叠放在层叠上下文内的两个元素之间。例如a有层叠上下文,本身z-index为2,其内有z-index为100的元素,b元素将a完全遮住,z-index为4。渲染出的结果只能看到b,a内的任何元素都会为遮挡。(可以这么理解:2.100 < 4)</li>\n<li>所有层叠上下文内的元素会按照以下顺序,从后到前叠放\n<ul>\n<li>层叠上下文的根</li>\n<li>z-index为负的定位元素(及其子元素)</li>\n<li>非定位元素</li>\n<li>z-index为auto的定位元素(及其子元素)</li>\n<li>z-index为正的定位元素(及其子元素)</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"粘性定位\"> 粘性定位</h3>\n<p>它是相对定位和固定定位的结合体:正常情况下,元素会随着页面滚动,当到达屏幕的特定位置时,如果用户继续滚动,它就会“锁定”在这个位置。最常见的用例是侧边栏导航。</p>\n<div><pre><code><span><span><span><</span>aside</span> <span>class</span><span><span>=</span><span>\"</span>col-sidebar<span>\"</span></span><span>></span></span>\n <span><span><span><</span>div</span> <span>class</span><span><span>=</span><span>\"</span>affix<span>\"</span></span><span>></span></span>\n <span><span><span><</span>ul</span> <span>class</span><span><span>=</span><span>\"</span>submenu<span>\"</span></span><span>></span></span>\n <span><span><span><</span>li</span><span>></span></span><span><span><span><</span>a</span> <span>href</span><span><span>=</span><span>\"</span>/<span>\"</span></span><span>></span></span>Home<span><span><span></</span>a</span><span>></span></span><span><span><span></</span>li</span><span>></span></span>\n <span><span><span><</span>li</span><span>></span></span><span><span><span><</span>a</span> <span>href</span><span><span>=</span><span>\"</span>/coffees<span>\"</span></span><span>></span></span>Coffees<span><span><span></</span>a</span><span>></span></span><span><span><span></</span>li</span><span>></span></span>\n <span><span><span><</span>li</span><span>></span></span><span><span><span><</span>a</span> <span>href</span><span><span>=</span><span>\"</span>/brewers<span>\"</span></span><span>></span></span>Brewers<span><span><span></</span>a</span><span>></span></span><span><span><span></</span>li</span><span>></span></span>\n <span><span><span><</span>li</span><span>></span></span><span><span><span><</span>a</span> <span>href</span><span><span>=</span><span>\"</span>/specials<span>\"</span></span><span>></span></span>Specials<span><span><span></</span>a</span><span>></span></span><span><span><span></</span>li</span><span>></span></span>\n <span><span><span><</span>li</span><span>></span></span><span><span><span><</span>a</span> <span>href</span><span><span>=</span><span>\"</span>/about<span>\"</span></span><span>></span></span>About us<span><span><span></</span>a</span><span>></span></span><span><span><span></</span>li</span><span>></span></span>\n <span><span><span></</span>ul</span><span>></span></span>\n <span><span><span></</span>div</span><span>></span></span>\n<span><span><span></</span>aside</span><span>></span></span>\n<span><span><span><</span>style</span><span>></span></span><span><span>\n<span>.affix</span> <span>{</span>\n <span>position</span><span>:</span> sticky<span>;</span>\n <span>top</span><span>:</span> 1em<span>;</span>\n<span>}</span>\n</span></span><span><span><span></</span>style</span><span>></span></span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br></div></div><p>top值设置了元素最终固定的位置:距离视口的顶部1em。<br>\n粘性元素永远不会超出父元素的范围,所以本例中affix不会超出col-sidebar的范围。当滚动页面的时候,col-sidebar会一直正常滚动,但是affix会在滚动到特定位置时停下来。如果继续滚动得足够远,粘性元素还会恢复滚动。这种情况只在父元素的底边到达粘性元素的底边时发生。<br>\n注意,只有当父元素的高度大于粘性元素时才会让粘性元素固定</p>\n",
"image": "https://kigane.github.io/assets/img/float-trap.png",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "CSS 基础",
"url": "https://kigane.github.io/note/js/css-basic/",
"id": "https://kigane.github.io/note/js/css-basic/",
"content_html": "<h2 id=\"基础\"> 基础</h2>\n<h3 id=\"术语\"> 术语</h3>\n<ul>\n<li>声明:CSS中的一行,由一个属性和一个值组成。例如 color:black。</li>\n<li>声明块:包含在大括号内的一组声明。</li>\n<li>规则集:选择器 + 声明块</li>\n<li>@规则:用“@”符号开头的语法。比如@import规则或者@media查询。</li>\n<li>层叠值:作为层叠结果,应用到一个元素上的特定属性的值。</li>\n<li>计算值:浏览器根据相对单位的值计算出的绝对值。</li>\n<li>声明值:声明中的值</li>\n<li>视口:浏览器窗口里网页可见部分的边框区域。它不包括浏览器的地址栏、工具栏、状态栏。</li>\n<li>*:通用选择器,选中页面上所有元素</li>\n<li>普通文档流:指网页元素的默认布局行为。行内元素跟随文字的方向从左到右排列,当到达容器边缘时会换行。块级元素会占据完整的一行,前后都有换行。</li>\n</ul>\n<h3 id=\"选择器\"> 选择器</h3>\n<p>基础选择器</p>\n<ul>\n<li>tagname 标签选择器。优先级001</li>\n<li>.class 类选择器。优先级010</li>\n<li>#id ID选择器。优先级100</li>\n<li><code>*</code> 通用选择器。优先级000</li>\n</ul>\n<hr>\n<p>组合选择器</p>\n<ul>\n<li>a>b 子组合器。a的直接后代b</li>\n<li>a+b 相邻兄弟组合器。紧跟在a后面的兄弟元素b。</li>\n<li>a~b 通用兄弟组合器。所有在a后面的兄弟元素b。</li>\n<li>a b 选择a的后代中的任何匹配b选择器的元素。</li>\n<li>ab 选择所有既符合a,又符合b的元素。复合选择器。多个基础选择器可以连起来(不使用空格或者其他组合器)组成一个复合选择器。复合选择器选中的元素将匹配其全部基础选择器。例如.dropdown.is-active只能匹配同时有这两个类的元素。</li>\n</ul>\n<hr>\n<p>伪类选择器<br>\n用于选中处于某个特定状态的元素。这种状态可能是由于用户交互,也可能是由于元素相对于其父级或兄弟元素的位置。伪类选择器始终以一个冒号(:)开始。优先级等于一个类选择器(0,1,0)。</p>\n<ul>\n<li>:first-child——匹配的元素是其父元素的第一个子元素。</li>\n<li>:last-child——匹配的元素是其父元素的最后一个子元素。</li>\n<li>:only-child——匹配的元素是其父元素的唯一一个子元素(没有兄弟元素)。</li>\n<li>:nth-child(an+b)——匹配的元素在兄弟元素中间有特定的位置。公式an+b里面的a和b是整数,n取所有自然数,包括0。</li>\n<li>:nth-last-child(an+b)——类似于:nth-child(),但不是从第一个元素往后数,而是从最后一个元素往前数。</li>\n<li>:first-of-type——类似于:first-child,但不是根据在全部子元素中的位置查找元素,而是根据拥有相同标签名的子元素中的数字顺序查找第一个元素。</li>\n<li>:last-of-type——匹配每种类型的最后一个子元素。</li>\n<li>:only-of-type——该选择器匹配的元素是满足该类型的唯一一个子元素。</li>\n<li>:nth-of-type(an+b)——根据目标元素在特定类型下的数字顺序以及特定公式选择元素,类似于:nth-child。</li>\n<li>:nth-last-of-type(an+b)——根据元素类型以及特定公式选择元素,从其中最后一个元素往前算,类似于:nth-last-child。</li>\n<li><code>:not(<selector>)</code>——匹配的元素不匹配括号内的选择器。括号内的选择器必须是基础选择器,它只能指定元素本身,无法用于排除祖先元素,同时不允许包含另一个排除选择器。</li>\n<li>:empty——匹配的元素必须没有子元素。注意,如果元素包含空格就无法由该选择器匹配,因为空格在DOM中属于文本节点。</li>\n<li>:focus——匹配通过鼠标点击、触摸屏幕或者按Tab键导航而获得焦点的元素。</li>\n<li>:hover——匹配鼠标指针正悬停在其上方的元素。</li>\n<li>:root——匹配文档根元素。</li>\n</ul>\n<hr>\n<p>伪元素选择器</p>\n<ul>\n<li>::before——创建一个伪元素,使其成为匹配元素的第一个子元素。该元素默认是行内元素,可用于插入文字、图片或其他形状。必须指定content属性才能让元素出现,例如:.menu::before。</li>\n<li>::after——创建一个伪元素,使其成为匹配元素的最后一个子元素。类似::before。</li>\n<li>::first-letter——用于指定匹配元素的第一个文本字符的样式,例如:h2::first-letter。</li>\n<li>::first-line——用于指定匹配元素的第一行文本的样式。</li>\n<li>::selection——用于指定用户使用鼠标高亮选择的任意文本的样式。通常用于改变选中文本的background-color。只有少数属性可以使用,包括color、background-color、cursor、text-decoration。</li>\n</ul>\n<hr>\n<p>属性选择器<br>\n用于根据HTML属性匹配元素。其优先级与一个类选择器(0,1,0)相等。</p>\n<ul>\n<li><code>[attr]</code>——匹配的元素拥有指定属性attr,无论属性值是什么</li>\n<li><code>[attr="value"]</code>——匹配的元素拥有指定属性attr,且属性值等于指定的字符串值</li>\n<li><code>[attr^="value"]</code>——“开头”属性选择器。该选择器匹配的元素拥有指定属性attr,且属性值的开头是指定的字符串值,例如:<code>a[href^="https"]</code>。</li>\n<li><code>[attr$="value"]</code>——“结尾”属性选择器。该选择器匹配的元素拥有指定属性attr,且属性值的结尾是指定的字符串值,例如:<code>a[href$= ".pdf"]</code>。</li>\n<li><code>[attr*="value"]</code>——“包含”属性选择器。该选择器匹配的元素拥有指定属性attr,且属性值包含指定的字符串值,例如:<code>[class*="sprite-"]</code>。</li>\n<li><code>[attr~="value"]</code>——“空格分隔的列表”属性选择器。该选择器匹配的元素拥有指定属性attr,且属性值是一个空格分隔的值列表,列表中的某个值等于指定的字符串值</li>\n<li><code>[attr|="value"]</code>——匹配的元素拥有指定属性attr,且属性值要么等于指定的字符串值,要么以该字符串开头且紧跟着一个连字符。适用于语言属性,因为该属性有时候会指定一种语言的子集(比如墨西哥西班牙语,es-MX,或者普通的西班牙语,es),例如:<code>[lang|="es"]</code>。</li>\n</ul>\n<h3 id=\"浏览器的开发者工具\"> 浏览器的开发者工具</h3>\n<h4 id=\"样式\"> 样式</h4>\n<ul>\n<li>element.style 为行内样式</li>\n<li>靠近顶部的样式会覆盖下面的样式。</li>\n<li>被覆盖的样式上划了删除线。</li>\n<li>右侧显示了每个规则集的样式表和在源代码中行号。</li>\n<li>顶部的筛选框可以选择特定的声明,同时隐藏其他声明。</li>\n<li>按住shift,再点击颜色前的方框,可以改变颜色的表示法</li>\n</ul>\n<h4 id=\"element\"> Element</h4>\n<ul>\n<li>选择指定元素,右键菜单中可以设置强制元素进入:active,:hover等状态。</li>\n</ul>\n<h3 id=\"层叠\"> 层叠</h3>\n<p>当声明冲突时,层叠会依据三种条件解决冲突。</p>\n<ol>\n<li>样式表的来源:样式是从哪里来的,有三级,浏览器默认样式(也称用户代理样式),作者自定义样式(即开发者写的CSS),作者的!important样式。后面的会覆盖前面的。</li>\n<li>选择器优先级:行内样式 > ID选择器 > 类选择器 > 标签选择器。选择器最高优先级相同时,比其数量,数量多的优先级高。\n<ul>\n<li>伪类选择器(如:hover)和属性选择器(如[type="input"])与一个类选择器的优先级相同。通用选择器(*)和组合器(>、+、~)对优先级没有影响。</li>\n</ul>\n</li>\n<li>源码顺序:样式在样式表里的声明顺序。晚出现的优先级更高。</li>\n</ol>\n<h3 id=\"继承-inherit-属性值\"> 继承(inherit 属性值)</h3>\n<ul>\n<li>不是所有的属性都能被继承。默认情况下,只有特定的一些属性能被继承,通常是我们希望被继承的那些。</li>\n<li>文本相关的属性:color、font、font-family、font-size、font-weight、font-variant、font-style、line-height、letter-spacing、text-align、text-indent、text-transform、white-space以及word-spacing。</li>\n<li>列表属性:list-style、list-style-type、list-style-position以及list-style-image。</li>\n<li>表格的边框属性border-collapse和border-spacing也能被继承。</li>\n</ul>\n<p>PS: initial属性,即CSS属性的初始值。可用于撤销某元素的样式。</p>\n<h3 id=\"简写属性\"> 简写属性</h3>\n<p>简写属性是用于同时给多个属性赋值的属性。例如</p>\n<ul>\n<li>font:它指定了font-style、font-weight、font-size、font-height以及font-family。</li>\n<li>background:它指定了background-color、background-image、background-size、background-repeat、background-position、background-origin、background-chip以及background-attachment。</li>\n<li>border是border-width、border-style以及border-color的简写属性</li>\n<li>border-width是上、右、下、左四个边框宽度的简写属性。</li>\n</ul>\n<p>大多数简写属性可以省略一些值,只指定我们关注的值。但被省略的值会被隐式地设置为初始值。<br>\n通常元素的四个方向相关的属性声明顺序为上右下左,即顺时针方向。如果声明结束时四个属性值还剩有没指定的,则没有指定的一边会取其对边的值。指定三个值时,左边和右边都会使用第二个值。指定两个值时,上边和下边会使用第一个值,左边和右边使用第二个值。如果只指定一个值,那么四个方向都会使用这个值。</p>\n<p>还有一些属性只支持最多指定两个值,这些属性包括background-position、box-shadow、text-shadow(虽然严格来讲它们并不是简写属性)。其声明顺序为先x,后y。即先水平方向,再垂直方向。</p>\n<h2 id=\"相对单位\"> 相对单位</h2>\n<h3 id=\"em-rem\"> em & rem</h3>\n<ul>\n<li>em\n<ul>\n<li>对于font-size属性,em是根据继承的字号来计算的,即父元素的font-size。</li>\n<li>对于非font-size属性,em是根据元素的font-size属性计算的。</li>\n<li>em的复杂之处就在于同时用它指定一个元素的字号和其他属性。这时,浏览器必须先计算字号,然后使用这个计算值去算出其余的属性值。这两类属性可以拥有一样的声明值,但是计算值不一样。</li>\n<li>em的好处。可以定义一个元素的大小,然后只需要改变字号就能整体缩放元素。padding、height、width、border-radius等属性用em很方便。</li>\n<li>对于嵌套结构,如果都使用em单位,则字体大小会逐级放大或缩小,要避免此情况,应使用rem单位。</li>\n</ul>\n</li>\n<li>rem\n<ul>\n<li>根据根元素的 font-size 属性计算</li>\n<li>html是根元素。根节点有一个伪类选择器:root。</li>\n<li>对大多数浏览器来说,默认的字号为16px。准确地说,medium关键字的值是16px。</li>\n</ul>\n</li>\n<li>百分数 基数为父元素的 px 值</li>\n</ul>\n<p>tips:拿不准的时候,用rem设置字号,用px设置边框,用em设置其他大部分属性。</p>\n<h3 id=\"媒体查询\"> 媒体查询</h3>\n<div><pre><code><span>:root</span> <span>{</span>\n <span>font-size</span><span>:</span> 0.75em<span>;</span>\n<span>}</span>\n\n<span>/* 作用于宽度 > 800px的屏幕,生效时,因源码顺序,会覆盖前面的样式 */</span>\n<span><span>@media</span> <span>(</span><span>min-width</span><span>:</span> 800px<span>)</span></span> <span>{</span>\n <span>:root</span> <span>{</span>\n <span>font-size</span><span>:</span> 0.875em<span>;</span>\n <span>}</span>\n<span>}</span>\n\n<span>/* 作用于宽度 > 1200px的屏幕,生效时,因源码顺序,会覆盖前面的样式 */</span>\n<span><span>@media</span> <span>(</span><span>min-width</span><span>:</span> 1200px<span>)</span></span> <span>{</span>\n <span>:root</span> <span>{</span>\n <span>font-size</span><span>:</span> 1em<span>;</span>\n <span>}</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br></div></div><h3 id=\"视口单位\"> 视口单位</h3>\n<p>视口——浏览器窗口里网页可见部分的边框区域。它不包括浏览器的地址栏、工具栏、状态栏。</p>\n<ul>\n<li>vh:视口高度的1/100。</li>\n<li>vw:视口宽度的1/100。</li>\n<li>vmin:视口宽、高中较小的一方的1/100。</li>\n<li>vmax:视口宽、高中较大的一方的1/100。</li>\n</ul>\n<p>相对视口单位有一个不起眼的用途,就是设置字号,这样做的好处在于元素能够在这两种大小之间平滑地过渡,即不会在某个断点突然改变。当视口大小改变时,元素会逐渐过渡。但只使用vw或vh,字号会比较大,所以通常会结合em一起使用。<br>\ncalc()函数可以对两个及其以上的值进行基本运算。当要结合不同单位的值时,calc()特别实用。它支持四则运算。加号和减号两边必须有空白,如calc(.5em + 1vh)。</p>\n<h3 id=\"无单位的数值\"> 无单位的数值</h3>\n<p>有些属性允许无单位的值(即一个不指定单位的数)。</p>\n<ul>\n<li>支持这种值的属性包括line-height、z-index、font-weight(700等于bold,400等于normal,等等)。</li>\n<li>任何长度单位都可以用无单位的值0,因为这些情况下单位不影响计算值,0px、0%、0em均相等。</li>\n<li>警告:一个无单位的0只能用于长度值和百分比,比如内边距、边框和宽度等,而不能用于角度值,比如度,或者时间相关的值,比如秒。</li>\n</ul>\n<p>继承的怪异行为</p>\n<ul>\n<li>当一个元素的值定义为长度时,子元素会继承它的计算值。当使用em等单位定义行高时,它们的值是计算值,传递到了任何继承子元素上。如果子元素有不同的字号,并且继承了line-height属性,就会造成意想不到的结果,比如文字重叠。</li>\n<li>使用无单位的数值时,继承的是声明值,即在每个继承子元素上会重新算它的计算值。这样得到的结果几乎总是我们想要的。我们可以用一个无单位的数值给body设置行高,之后就不用修改了,除非有些地方想要不一样的行高。</li>\n</ul>\n<h3 id=\"自定义属性-css变量\"> 自定义属性(css变量)</h3>\n<div><pre><code><span>:root</span> <span>{</span>\n <span>--main-color</span><span>:</span> white<span>;</span> <span>/* 变量声明,无实际效果 */</span>\n <span>--main-bg</span><span>:</span> #fff<span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br></div></div><ul>\n<li>变量必须在一个声明块内声明。</li>\n<li>变量名前面必须有两个连字符(--),用来跟CSS属性区分,剩下的部分可以随意命名。</li>\n<li>变量声明本身什么也没做,我们使用时才能看到效果。</li>\n<li>使用变量需使用var()函数,其第二个参数,指定了备用值。如果第一个参数指定的变量未定义,那么就会使用第二个值。</li>\n<li>注意:如果var()函数算出来的是一个非法值,对应的属性就会设置为其初始值。比如,如果在padding: var(--brand-color)中的变量算出来是一个颜色,它就是一个非法的内边距值。这种情况下,内边距会设置为0。</li>\n<li><strong>自定义属性的声明能够层叠和继承</strong>,可以在多个选择器中定义相同的变量,则该变量在网页的不同地方有不同的值。</li>\n</ul>\n<p>用JS访问和修改自定义属性。</p>\n<div><pre><code><span><span><span><</span>script</span> <span>type</span><span><span>=</span><span>\"</span>text/javascript<span>\"</span></span><span>></span></span><span><span>\n <span>let</span> rootElement <span>=</span> document<span>.</span>documentElement<span>;</span>\n <span>let</span> styles <span>=</span> <span>getComputedStyle</span><span>(</span>rootElement<span>)</span><span>;</span>\n <span>let</span> mainColor <span>=</span> styles<span>.</span><span>getPropertyValue</span><span>(</span><span>'--main-bg'</span><span>)</span><span>;</span>\n console<span>.</span><span>log</span><span>(</span><span>String</span><span>(</span>mainColor<span>)</span><span>.</span><span>trim</span><span>(</span><span>)</span><span>)</span><span>;</span>\n\n rootElement <span>=</span> document<span>.</span>documentElement<span>;</span>\n rootElement<span>.</span>style<span>.</span><span>setProperty</span><span>(</span><span>'--main-bg'</span><span>,</span> <span>'#cdf'</span><span>)</span><span>;</span>\n</span></span><span><span><span></</span>script</span><span>></span></span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br></div></div><h2 id=\"盒模型\"> 盒模型</h2>\n<p><img src=\"/assets/img/box-model.png\" alt=\"box-model\" />\n当给一个元素设置宽或高的时候,指定的是内容的宽或高,所有内边距、边框、外边距都是追加到该宽度上的。</p>\n<h3 id=\"元素宽度问题\"> 元素宽度问题</h3>\n<p>在CSS中可以使用box-sizing属性调整盒模型的行为。</p>\n<ul>\n<li>box-sizing的默认值为content-box,这意味任何指定的宽或高都只会设置内容盒子的大小。</li>\n<li>box-sizing设置为border-box后,height和width属性会设置内容、内边距以及边框的大小总和。</li>\n</ul>\n<p>全局修改盒模型为border-box(已是普遍做法了)</p>\n<div><pre><code><span>:root</span> <span>{</span>\n <span>box-sizing</span><span>:</span> border-box<span>;</span>\n<span>}</span>\n\n<span>*,\n::before,\n::after</span> <span>{</span>\n <span>box-sizing</span><span>:</span> inherit<span>;</span> <span>/* 盒模型通常不会被继承,但是使用inherit关键字可以强制继承。 */</span>\n<span>}</span>\n\n<span>.third-party-component</span> <span>{</span>\n <span>box-sizing</span><span>:</span> content-box<span>;</span> <span>/* 如果带样式的第三方组件使用的是默认的盒模型,则恢复 */</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br></div></div><p>给列之间加上间隙</p>\n<div><pre><code><span>.main</span> <span>{</span>\n <span>float</span><span>:</span> left<span>;</span>\n <span>width</span><span>:</span> 70%<span>;</span>\n <span>background-color</span><span>:</span> #fff<span>;</span>\n <span>border-radius</span><span>:</span> .5em<span>;</span>\n<span>}</span>\n\n<span>.sidebar</span> <span>{</span>\n <span>float</span><span>:</span> left<span>;</span>\n <span>width</span><span>:</span> <span>calc</span><span>(</span>30% - 1.5em<span>)</span><span>;</span> <span>/* 从宽度中分1.5em给外边距以形成间隙 */</span>\n <span>margin-left</span><span>:</span> 1.5em<span>;</span>\n <span>padding</span><span>:</span> 1.5em<span>;</span>\n <span>background-color</span><span>:</span> #fff<span>;</span>\n <span>border-radius</span><span>:</span> .5em<span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br></div></div><h3 id=\"元素高度问题\"> 元素高度问题</h3>\n<p>容器的高度由内容天然地决定,而不是容器自己决定。当明确设置一个元素的高度时,内容可能会溢出容器。当内容在限定区域放不下,渲染到父元素外面时,就会溢出。<br>\n用overflow属性可以控制溢出内容的行为,该属性支持以下4个值。</p>\n<ul>\n<li>visible(默认值)——所有内容可见,即使溢出容器边缘。</li>\n<li>hidden——溢出容器内边距边缘的内容被裁剪,无法看见。</li>\n<li>scroll——容器出现滚动条,用户可以通过滚动查看剩余内容。在一些操作系统上,即使所有内容都可见,也会出现水平和垂直两种滚动条。不过,在这种情况下,滚动条不可滚动。</li>\n<li>auto——只有内容溢出时容器才会出现滚动条。</li>\n<li>可以用overflow-x属性单独控制水平方向的溢出,或者用overflow-y控制垂直方向溢出。这些属性支持overflow的所有值,然而同时给x和y指定不同的值,往往会产生难以预料的结果。</li>\n</ul>\n<h4 id=\"等高列\"> 等高列</h4>\n<p>任意一列的内容增加,两列的高度都会增加,同时保持底部对齐。<br>\n现代浏览器支持了CSS表格,可以轻松实现等高列,比如IE8+支持display: table,IE10+支持弹性盒子或者Flexbox,都默认支持等高列。</p>\n<p>方案一:\n用CSS表格布局替代浮动布局。给容器设置display: table,给每一列设置display:table-cell。</p>\n<ol>\n<li>不像block的元素,默认情况下,显示为table的元素宽度不会扩展到100%,因此需要明确指定宽度(width:100%)。</li>\n<li>table-cell元素的外边距失效。</li>\n<li>可以用表格元素的border-spacing属性来定义单元格的间距。该属性接受两个长度值:水平间距和垂直间距。但这会产生一个特殊的副作用,间距也会作用于表格的外边缘,导致水平方向无法对齐。</li>\n<li>可使用负外边距解决,正的外边距会将容器的边缘往里推,而负的外边距则会将边缘往外拉。在表格容器外面包一个元素,将其左右外边距设置为−1.5em,从而抵消表格容器外侧1.5em的border-spacing。</li>\n</ol>\n<p>方案二:\n给容器设置display: flex,它就变成了一个弹性容器(flex container),子元素默认等高。你可以给子元素设置宽度和外边距,尽管加起来可能超过100%, Flexbox也能妥善处理。</p>\n<h4 id=\"min-height-max-height\"> min-height, max-height</h4>\n<ul>\n<li>min-height指定一个最小高度。元素至少等于你指定的高度,如果内容太多,浏览器就会允许元素自己扩展高度,以免内容溢出。</li>\n<li>max-heght允许元素自然地增高到一个特定界限。如果到达这个界限,元素就不再增高,内容会溢出。</li>\n<li>min-width, max-width同理</li>\n</ul>\n<h4 id=\"垂直居中内容\"> 垂直居中内容</h4>\n<p>关于vertical-align:该声明只会影响行内元素或者table-cell元素。</p>\n<ul>\n<li>对于行内元素,它控制着该元素跟同一行内其他元素之间的对齐关系。比如,可以用它控制一个行内的图片跟相邻的文字对齐。</li>\n<li>对于显示为table-cell的元素,vertical-align控制了内容在单元格内的对齐。如果使用CSS表格布局,则可以用vertical-align来实现垂直居中。</li>\n</ul>\n<p>在容器里让内容居中最好的方式是根据特定场景考虑不同因素。做出判断前,先逐个询问自己以下几个问题,直到找到合适的解决办法。</p>\n<ul>\n<li>可以用一个自然高度的容器吗?给容器加上相等的上下内边距让内容居中。</li>\n<li>容器需要指定高度或者避免使用内边距吗?对容器使用display: table-cell和vertical-align: middle。</li>\n<li>可以用Flexbox吗? 如果不需要支持IE9,可以用Flexbox居中内容。</li>\n<li>容器里面的内容只有一行文字吗?设置一个大的行高,让它等于理想的容器高度。这样会让容器高度扩展到能够容纳行高。如果内容不是行内元素,可以设置为inline-block。</li>\n<li>容器和内容的高度都知道吗?将内容绝对定位。</li>\n<li>不知道内部元素的高度?用绝对定位结合变形(transform)。</li>\n<li>还不确定的话,参考howtocenterincss网站。</li>\n</ul>\n<h3 id=\"负外边距\"> 负外边距</h3>\n<p>不同于内边距和边框宽度,外边距可以设置为负值。负外边距有一些特殊用途,比如让元素重叠或者拉伸到比容器还宽。\n<img src=\"/assets/img/minus-margin.png\" alt=\"minus-margin\" />\n负外边距的具体行为取决于设置在元素的哪边,如图所示。如果设置左边或顶部的负外边距,元素就会相应地向左或向上移动,导致元素与它前面的元素重叠,如果设置右边或者底部的负外边距,并不会移动元素,而是将它后面的元素拉过来。给元素底部加上负外边距并不等同于给它下面的元素顶部加上负外边距。</p>\n<h3 id=\"外边距折叠\"> 外边距折叠</h3>\n<p>当顶部和/或底部的外边距相邻时,就会重叠,产生单个外边距。这种现象被称作折叠。折叠外边距的大小等于相邻外边距中的最大值。即使两个元素不是相邻的兄弟节点也会产生外边距折叠。在没有其他CSS的影响下,所有相邻的顶部和底部外边距都会折叠。<br>\nPS:只有上下外边距会产生折叠,左右外边距不会折叠。折叠外边距就像“个人空间”。如果在公交车站站着两个人,他们每个人都认为较为舒适的个人空间应为3英尺,那么他们就会乐意间隔3英尺,而不必间隔6英尺才让双方满意。</p>\n<h3 id=\"容器内堆叠元素的间距\"> 容器内堆叠元素的间距</h3>\n<p>容器的内边距和内容的外边距之间的相互作用处理起来很棘手。</p>\n<p>例如:容器有1.5em的padding,想将容器内的多个块级元素隔开一个间隙,使用margin-top:.5em。元素之间确实隔开了,但容器顶部1.5em的padding加上第一个元素的.5em margin会导致容器顶部空间过大。</p>\n<p>解决方法为:使用兄弟选择器。.button-link+.button-link,这不会选择第一个子元素。<br>\n更通用的方法为:猫头鹰选择器。<code>*+*</code>。这会选中页面上有着相同父级的非第一个子元素。</p>\n<h2 id=\"渲染\"> 渲染</h2>\n<p>浏览器计算好了页面上哪些样式应用于哪些元素上之后,需要把这些样式转化成屏幕上的像素,这个过程叫作渲染(rending)。渲染可以分为三个阶段:布局、绘制和合成。</p>\n<h3 id=\"布局\"> 布局</h3>\n<p>在第一个阶段布局中,浏览器需要计算每个元素将在屏幕上占多大空间。因为文档流的工作方式,所以一个元素的大小和位置可以影响页面上无数其他元素的大小和位置。</p>\n<p>任何时候改变一个元素的宽度或高度,或者调整位置属性(比如top或者left),元素的布局都会重新计算。如果使用JavaScript在DOM中插入或者移除元素,也会重新计算布局。一旦布局发生改变,浏览器就必须 <strong>重排(reflow)</strong> 页面,重新计算所有其他被移动或者缩放的元素的布局。</p>\n<h3 id=\"绘制\"> 绘制</h3>\n<p>布局之后是绘制。这个过程就是填充像素:描绘文本,着色图片、边框和阴影。这不会真正显示在屏幕上,而是在内存中绘制。页面各部分生成了很多的<strong>图层(layers)</strong>。</p>\n<p>如果改变某个元素的背景颜色,就必须<strong>重新绘制</strong>它。但因为更改背景颜色不会影响到页面上任何元素的位置和大小,所以这种变化不需要重新计算布局。</p>\n<p>某些条件下,页面元素会被提取到自己的图层。这时候,它会从页面的其他图层中独立出来单独绘制。浏览器把这个图层发送到计算机的GPU进行绘制,而不是像主图层那样使用主CPU绘制。这就是硬件加速(hardware acceleration)。</p>\n<h3 id=\"合成\"> 合成</h3>\n<p>在合成(composite)阶段,浏览器收集所有绘制完成的图层,并把它们提取为最终显示在屏幕上的图像。合成过程需要按照特定顺序进行,以确保图层出现重叠时,正确的图层显示在其他图层之上。</p>\n<p><strong>opacity和transform</strong>这两个属性如果发生改变,需要的渲染时间就会非常少。当我们修改元素的这两个属性之一时,浏览器就会把元素提升到其自己的绘制图层并使用GPU加速。因为元素存在于自己的图层,所以整个图像变化过程中主图层将不会发生变化,也无须重复的重绘。</p>\n<p>因此,处理过渡或者动画的时候,尽量只改变transform和opacity属性。如果有需要,可以修改那些只导致重绘而不会重新布局的属性。只有在没有其他替代方案的时候,再去修改那些影响布局的属性。</p>\n<h2 id=\"css属性建议书写顺序\"> css属性建议书写顺序</h2>\n<table>\n<thead>\n<tr>\n<th>显示属性</th>\n<th>自身属性</th>\n<th>文本属性和其他修饰</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>display</td>\n<td>width</td>\n<td>font</td>\n</tr>\n<tr>\n<td>visibility</td>\n<td>height</td>\n<td>text-align</td>\n</tr>\n<tr>\n<td>position</td>\n<td>margin</td>\n<td>text-decoration</td>\n</tr>\n<tr>\n<td>float</td>\n<td>padding</td>\n<td>vertical-align</td>\n</tr>\n<tr>\n<td>clear</td>\n<td>border</td>\n<td>white-space</td>\n</tr>\n<tr>\n<td>list-style</td>\n<td>overflow</td>\n<td>color</td>\n</tr>\n<tr>\n<td>top</td>\n<td>min-width</td>\n<td>background</td>\n</tr>\n</tbody>\n</table>\n<p>从左到右。</p>\n",
"image": "https://kigane.github.io/assets/img/box-model.png",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "CSS 模块化",
"url": "https://kigane.github.io/note/js/css-modular/",
"id": "https://kigane.github.io/note/js/css-modular/",
"content_html": "<h2 id=\"模块化css\"> 模块化CSS</h2>\n<p>是指把页面分割成不同的组成部分,这些组成部分可以在多种上下文中重复使用,并且互相之间没有依赖关系。最终目的是,当我们修改其中一部分CSS时,不会对其他部分产生意料之外的影响。</p>\n<h3 id=\"基础样式\"> 基础样式</h3>\n<p>每个样式表的开头都要写一些给整个页面使用的通用规则,模块化CSS也不例外。这些规则通常被称为基础样式,其他的样式是构建在这些基础样式之上的。</p>\n<details><summary>https://necolas.github.io/normalize.css/</summary>\n<div><pre><code><span>/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */</span>\n\n<span>/* Document\n ========================================================================== */</span>\n\n<span>/**\n * 1. Correct the line height in all browsers.\n * 2. Prevent adjustments of font size after orientation changes in iOS.\n */</span>\n\n<span>html</span> <span>{</span>\n <span>line-height</span><span>:</span> 1.15<span>;</span> <span>/* 1 */</span>\n <span>-webkit-text-size-adjust</span><span>:</span> 100%<span>;</span> <span>/* 2 */</span>\n<span>}</span>\n\n<span>/* Sections\n ========================================================================== */</span>\n\n<span>/**\n * Remove the margin in all browsers.\n */</span>\n\n<span>body</span> <span>{</span>\n <span>margin</span><span>:</span> 0<span>;</span>\n<span>}</span>\n\n<span>/**\n * Render the `main` element consistently in IE.\n */</span>\n\n<span>main</span> <span>{</span>\n <span>display</span><span>:</span> block<span>;</span>\n<span>}</span>\n\n<span>/**\n * Correct the font size and margin on `h1` elements within `section` and\n * `article` contexts in Chrome, Firefox, and Safari.\n */</span>\n\n<span>h1</span> <span>{</span>\n <span>font-size</span><span>:</span> 2em<span>;</span>\n <span>margin</span><span>:</span> 0.67em 0<span>;</span>\n<span>}</span>\n\n<span>/* Grouping content\n ========================================================================== */</span>\n\n<span>/**\n * 1. Add the correct box sizing in Firefox.\n * 2. Show the overflow in Edge and IE.\n */</span>\n\n<span>hr</span> <span>{</span>\n <span>box-sizing</span><span>:</span> content-box<span>;</span> <span>/* 1 */</span>\n <span>height</span><span>:</span> 0<span>;</span> <span>/* 1 */</span>\n <span>overflow</span><span>:</span> visible<span>;</span> <span>/* 2 */</span>\n<span>}</span>\n\n<span>/**\n * 1. Correct the inheritance and scaling of font size in all browsers.\n * 2. Correct the odd `em` font sizing in all browsers.\n */</span>\n\n<span>pre</span> <span>{</span>\n <span>font-family</span><span>:</span> monospace<span>,</span> monospace<span>;</span> <span>/* 1 */</span>\n <span>font-size</span><span>:</span> 1em<span>;</span> <span>/* 2 */</span>\n<span>}</span>\n\n<span>/* Text-level semantics\n ========================================================================== */</span>\n\n<span>/**\n * Remove the gray background on active links in IE 10.\n */</span>\n\n<span>a</span> <span>{</span>\n <span>background-color</span><span>:</span> transparent<span>;</span>\n<span>}</span>\n\n<span>/**\n * 1. Remove the bottom border in Chrome 57-\n * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.\n */</span>\n\n<span>abbr[title]</span> <span>{</span>\n <span>border-bottom</span><span>:</span> none<span>;</span> <span>/* 1 */</span>\n <span>text-decoration</span><span>:</span> underline<span>;</span> <span>/* 2 */</span>\n <span>text-decoration</span><span>:</span> underline dotted<span>;</span> <span>/* 2 */</span>\n<span>}</span>\n\n<span>/**\n * Add the correct font weight in Chrome, Edge, and Safari.\n */</span>\n\n<span>b,\nstrong</span> <span>{</span>\n <span>font-weight</span><span>:</span> bolder<span>;</span>\n<span>}</span>\n\n<span>/**\n * 1. Correct the inheritance and scaling of font size in all browsers.\n * 2. Correct the odd `em` font sizing in all browsers.\n */</span>\n\n<span>code,\nkbd,\nsamp</span> <span>{</span>\n <span>font-family</span><span>:</span> monospace<span>,</span> monospace<span>;</span> <span>/* 1 */</span>\n <span>font-size</span><span>:</span> 1em<span>;</span> <span>/* 2 */</span>\n<span>}</span>\n\n<span>/**\n * Add the correct font size in all browsers.\n */</span>\n\n<span>small</span> <span>{</span>\n <span>font-size</span><span>:</span> 80%<span>;</span>\n<span>}</span>\n\n<span>/**\n * Prevent `sub` and `sup` elements from affecting the line height in\n * all browsers.\n */</span>\n\n<span>sub,\nsup</span> <span>{</span>\n <span>font-size</span><span>:</span> 75%<span>;</span>\n <span>line-height</span><span>:</span> 0<span>;</span>\n <span>position</span><span>:</span> relative<span>;</span>\n <span>vertical-align</span><span>:</span> baseline<span>;</span>\n<span>}</span>\n\n<span>sub</span> <span>{</span>\n <span>bottom</span><span>:</span> -0.25em<span>;</span>\n<span>}</span>\n\n<span>sup</span> <span>{</span>\n <span>top</span><span>:</span> -0.5em<span>;</span>\n<span>}</span>\n\n<span>/* Embedded content\n ========================================================================== */</span>\n\n<span>/**\n * Remove the border on images inside links in IE 10.\n */</span>\n\n<span>img</span> <span>{</span>\n <span>border-style</span><span>:</span> none<span>;</span>\n<span>}</span>\n\n<span>/* Forms\n ========================================================================== */</span>\n\n<span>/**\n * 1. Change the font styles in all browsers.\n * 2. Remove the margin in Firefox and Safari.\n */</span>\n\n<span>button,\ninput,\noptgroup,\nselect,\ntextarea</span> <span>{</span>\n <span>font-family</span><span>:</span> inherit<span>;</span> <span>/* 1 */</span>\n <span>font-size</span><span>:</span> 100%<span>;</span> <span>/* 1 */</span>\n <span>line-height</span><span>:</span> 1.15<span>;</span> <span>/* 1 */</span>\n <span>margin</span><span>:</span> 0<span>;</span> <span>/* 2 */</span>\n<span>}</span>\n\n<span>/**\n * Show the overflow in IE.\n * 1. Show the overflow in Edge.\n */</span>\n\n<span>button,\ninput</span> <span>{</span> <span>/* 1 */</span>\n <span>overflow</span><span>:</span> visible<span>;</span>\n<span>}</span>\n\n<span>/**\n * Remove the inheritance of text transform in Edge, Firefox, and IE.\n * 1. Remove the inheritance of text transform in Firefox.\n */</span>\n\n<span>button,\nselect</span> <span>{</span> <span>/* 1 */</span>\n <span>text-transform</span><span>:</span> none<span>;</span>\n<span>}</span>\n\n<span>/**\n * Correct the inability to style clickable types in iOS and Safari.\n */</span>\n\n<span>button,\n[type=\"button\"],\n[type=\"reset\"],\n[type=\"submit\"]</span> <span>{</span>\n <span>-webkit-appearance</span><span>:</span> button<span>;</span>\n<span>}</span>\n\n<span>/**\n * Remove the inner border and padding in Firefox.\n */</span>\n\n<span>button::-moz-focus-inner,\n[type=\"button\"]::-moz-focus-inner,\n[type=\"reset\"]::-moz-focus-inner,\n[type=\"submit\"]::-moz-focus-inner</span> <span>{</span>\n <span>border-style</span><span>:</span> none<span>;</span>\n <span>padding</span><span>:</span> 0<span>;</span>\n<span>}</span>\n\n<span>/**\n * Restore the focus styles unset by the previous rule.\n */</span>\n\n<span>button:-moz-focusring,\n[type=\"button\"]:-moz-focusring,\n[type=\"reset\"]:-moz-focusring,\n[type=\"submit\"]:-moz-focusring</span> <span>{</span>\n <span>outline</span><span>:</span> 1px dotted ButtonText<span>;</span>\n<span>}</span>\n\n<span>/**\n * Correct the padding in Firefox.\n */</span>\n\n<span>fieldset</span> <span>{</span>\n <span>padding</span><span>:</span> 0.35em 0.75em 0.625em<span>;</span>\n<span>}</span>\n\n<span>/**\n * 1. Correct the text wrapping in Edge and IE.\n * 2. Correct the color inheritance from `fieldset` elements in IE.\n * 3. Remove the padding so developers are not caught out when they zero out\n * `fieldset` elements in all browsers.\n */</span>\n\n<span>legend</span> <span>{</span>\n <span>box-sizing</span><span>:</span> border-box<span>;</span> <span>/* 1 */</span>\n <span>color</span><span>:</span> inherit<span>;</span> <span>/* 2 */</span>\n <span>display</span><span>:</span> table<span>;</span> <span>/* 1 */</span>\n <span>max-width</span><span>:</span> 100%<span>;</span> <span>/* 1 */</span>\n <span>padding</span><span>:</span> 0<span>;</span> <span>/* 3 */</span>\n <span>white-space</span><span>:</span> normal<span>;</span> <span>/* 1 */</span>\n<span>}</span>\n\n<span>/**\n * Add the correct vertical alignment in Chrome, Firefox, and Opera.\n */</span>\n\n<span>progress</span> <span>{</span>\n <span>vertical-align</span><span>:</span> baseline<span>;</span>\n<span>}</span>\n\n<span>/**\n * Remove the default vertical scrollbar in IE 10+.\n */</span>\n\n<span>textarea</span> <span>{</span>\n <span>overflow</span><span>:</span> auto<span>;</span>\n<span>}</span>\n\n<span>/**\n * 1. Add the correct box sizing in IE 10.\n * 2. Remove the padding in IE 10.\n */</span>\n\n<span>[type=\"checkbox\"],\n[type=\"radio\"]</span> <span>{</span>\n <span>box-sizing</span><span>:</span> border-box<span>;</span> <span>/* 1 */</span>\n <span>padding</span><span>:</span> 0<span>;</span> <span>/* 2 */</span>\n<span>}</span>\n\n<span>/**\n * Correct the cursor style of increment and decrement buttons in Chrome.\n */</span>\n\n<span>[type=\"number\"]::-webkit-inner-spin-button,\n[type=\"number\"]::-webkit-outer-spin-button</span> <span>{</span>\n <span>height</span><span>:</span> auto<span>;</span>\n<span>}</span>\n\n<span>/**\n * 1. Correct the odd appearance in Chrome and Safari.\n * 2. Correct the outline style in Safari.\n */</span>\n\n<span>[type=\"search\"]</span> <span>{</span>\n <span>-webkit-appearance</span><span>:</span> textfield<span>;</span> <span>/* 1 */</span>\n <span>outline-offset</span><span>:</span> -2px<span>;</span> <span>/* 2 */</span>\n<span>}</span>\n\n<span>/**\n * Remove the inner padding in Chrome and Safari on macOS.\n */</span>\n\n<span>[type=\"search\"]::-webkit-search-decoration</span> <span>{</span>\n <span>-webkit-appearance</span><span>:</span> none<span>;</span>\n<span>}</span>\n\n<span>/**\n * 1. Correct the inability to style clickable types in iOS and Safari.\n * 2. Change font properties to `inherit` in Safari.\n */</span>\n\n<span>::-webkit-file-upload-button</span> <span>{</span>\n <span>-webkit-appearance</span><span>:</span> button<span>;</span> <span>/* 1 */</span>\n <span>font</span><span>:</span> inherit<span>;</span> <span>/* 2 */</span>\n<span>}</span>\n\n<span>/* Interactive\n ========================================================================== */</span>\n\n<span>/*\n * Add the correct display in Edge, IE 10+, and Firefox.\n */</span>\n\n<span>details</span> <span>{</span>\n <span>display</span><span>:</span> block<span>;</span>\n<span>}</span>\n\n<span>/*\n * Add the correct display in all browsers.\n */</span>\n\n<span>summary</span> <span>{</span>\n <span>display</span><span>:</span> list-item<span>;</span>\n<span>}</span>\n\n<span>/* Misc\n ========================================================================== */</span>\n\n<span>/**\n * Add the correct display in IE 10+.\n */</span>\n\n<span>template</span> <span>{</span>\n <span>display</span><span>:</span> none<span>;</span>\n<span>}</span>\n\n<span>/**\n * Add the correct display in IE 10.\n */</span>\n\n<span>[hidden]</span> <span>{</span>\n <span>display</span><span>:</span> none<span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br><span>25</span><br><span>26</span><br><span>27</span><br><span>28</span><br><span>29</span><br><span>30</span><br><span>31</span><br><span>32</span><br><span>33</span><br><span>34</span><br><span>35</span><br><span>36</span><br><span>37</span><br><span>38</span><br><span>39</span><br><span>40</span><br><span>41</span><br><span>42</span><br><span>43</span><br><span>44</span><br><span>45</span><br><span>46</span><br><span>47</span><br><span>48</span><br><span>49</span><br><span>50</span><br><span>51</span><br><span>52</span><br><span>53</span><br><span>54</span><br><span>55</span><br><span>56</span><br><span>57</span><br><span>58</span><br><span>59</span><br><span>60</span><br><span>61</span><br><span>62</span><br><span>63</span><br><span>64</span><br><span>65</span><br><span>66</span><br><span>67</span><br><span>68</span><br><span>69</span><br><span>70</span><br><span>71</span><br><span>72</span><br><span>73</span><br><span>74</span><br><span>75</span><br><span>76</span><br><span>77</span><br><span>78</span><br><span>79</span><br><span>80</span><br><span>81</span><br><span>82</span><br><span>83</span><br><span>84</span><br><span>85</span><br><span>86</span><br><span>87</span><br><span>88</span><br><span>89</span><br><span>90</span><br><span>91</span><br><span>92</span><br><span>93</span><br><span>94</span><br><span>95</span><br><span>96</span><br><span>97</span><br><span>98</span><br><span>99</span><br><span>100</span><br><span>101</span><br><span>102</span><br><span>103</span><br><span>104</span><br><span>105</span><br><span>106</span><br><span>107</span><br><span>108</span><br><span>109</span><br><span>110</span><br><span>111</span><br><span>112</span><br><span>113</span><br><span>114</span><br><span>115</span><br><span>116</span><br><span>117</span><br><span>118</span><br><span>119</span><br><span>120</span><br><span>121</span><br><span>122</span><br><span>123</span><br><span>124</span><br><span>125</span><br><span>126</span><br><span>127</span><br><span>128</span><br><span>129</span><br><span>130</span><br><span>131</span><br><span>132</span><br><span>133</span><br><span>134</span><br><span>135</span><br><span>136</span><br><span>137</span><br><span>138</span><br><span>139</span><br><span>140</span><br><span>141</span><br><span>142</span><br><span>143</span><br><span>144</span><br><span>145</span><br><span>146</span><br><span>147</span><br><span>148</span><br><span>149</span><br><span>150</span><br><span>151</span><br><span>152</span><br><span>153</span><br><span>154</span><br><span>155</span><br><span>156</span><br><span>157</span><br><span>158</span><br><span>159</span><br><span>160</span><br><span>161</span><br><span>162</span><br><span>163</span><br><span>164</span><br><span>165</span><br><span>166</span><br><span>167</span><br><span>168</span><br><span>169</span><br><span>170</span><br><span>171</span><br><span>172</span><br><span>173</span><br><span>174</span><br><span>175</span><br><span>176</span><br><span>177</span><br><span>178</span><br><span>179</span><br><span>180</span><br><span>181</span><br><span>182</span><br><span>183</span><br><span>184</span><br><span>185</span><br><span>186</span><br><span>187</span><br><span>188</span><br><span>189</span><br><span>190</span><br><span>191</span><br><span>192</span><br><span>193</span><br><span>194</span><br><span>195</span><br><span>196</span><br><span>197</span><br><span>198</span><br><span>199</span><br><span>200</span><br><span>201</span><br><span>202</span><br><span>203</span><br><span>204</span><br><span>205</span><br><span>206</span><br><span>207</span><br><span>208</span><br><span>209</span><br><span>210</span><br><span>211</span><br><span>212</span><br><span>213</span><br><span>214</span><br><span>215</span><br><span>216</span><br><span>217</span><br><span>218</span><br><span>219</span><br><span>220</span><br><span>221</span><br><span>222</span><br><span>223</span><br><span>224</span><br><span>225</span><br><span>226</span><br><span>227</span><br><span>228</span><br><span>229</span><br><span>230</span><br><span>231</span><br><span>232</span><br><span>233</span><br><span>234</span><br><span>235</span><br><span>236</span><br><span>237</span><br><span>238</span><br><span>239</span><br><span>240</span><br><span>241</span><br><span>242</span><br><span>243</span><br><span>244</span><br><span>245</span><br><span>246</span><br><span>247</span><br><span>248</span><br><span>249</span><br><span>250</span><br><span>251</span><br><span>252</span><br><span>253</span><br><span>254</span><br><span>255</span><br><span>256</span><br><span>257</span><br><span>258</span><br><span>259</span><br><span>260</span><br><span>261</span><br><span>262</span><br><span>263</span><br><span>264</span><br><span>265</span><br><span>266</span><br><span>267</span><br><span>268</span><br><span>269</span><br><span>270</span><br><span>271</span><br><span>272</span><br><span>273</span><br><span>274</span><br><span>275</span><br><span>276</span><br><span>277</span><br><span>278</span><br><span>279</span><br><span>280</span><br><span>281</span><br><span>282</span><br><span>283</span><br><span>284</span><br><span>285</span><br><span>286</span><br><span>287</span><br><span>288</span><br><span>289</span><br><span>290</span><br><span>291</span><br><span>292</span><br><span>293</span><br><span>294</span><br><span>295</span><br><span>296</span><br><span>297</span><br><span>298</span><br><span>299</span><br><span>300</span><br><span>301</span><br><span>302</span><br><span>303</span><br><span>304</span><br><span>305</span><br><span>306</span><br><span>307</span><br><span>308</span><br><span>309</span><br><span>310</span><br><span>311</span><br><span>312</span><br><span>313</span><br><span>314</span><br><span>315</span><br><span>316</span><br><span>317</span><br><span>318</span><br><span>319</span><br><span>320</span><br><span>321</span><br><span>322</span><br><span>323</span><br><span>324</span><br><span>325</span><br><span>326</span><br><span>327</span><br><span>328</span><br><span>329</span><br><span>330</span><br><span>331</span><br><span>332</span><br><span>333</span><br><span>334</span><br><span>335</span><br><span>336</span><br><span>337</span><br><span>338</span><br><span>339</span><br><span>340</span><br><span>341</span><br><span>342</span><br><span>343</span><br><span>344</span><br><span>345</span><br><span>346</span><br><span>347</span><br><span>348</span><br><span>349</span><br></div></div></details>\n<h3 id=\"简单的模块组织规则\"> 简单的模块组织规则</h3>\n<ul>\n<li>\n<p>模块的选择器由单个类名构成,这非常重要。这样通过给元素添加类名,就可以把这些样式复用到很多场景,比如针对表单输入给用户反馈,提供醒目的帮助文字,或者提醒用户注意免责声明条款等。使用相同的组件,就能产生一套风格一致的UI。</p>\n</li>\n<li>\n<p>要把一个模块所有的代码集中放在同一个地方,这样一个接一个的模块就会组成我们最终的样式表。</p>\n</li>\n<li>\n<p>通过定义一个以模块名称开头的新类名来创建一个修饰符(也叫变体类)。常用的写法是使用两个连字符来表示修饰符,比如message--error。双连字符的写法很容易区分哪部分是模块名称,哪部分是修饰符。</p>\n</li>\n<li>\n<p>当模块需要有不同的外观或者表现的时候,就创建一个可以直接应用到指定元素的修饰符类。比如,写.dropdown--dark,而不是写成page-header.dropdown。通过这种方式,模块本身,并且只能是它本身,可以决定自己的样式表现。其他模块不能进入别的模块内部去修改它。这样一来,深色下拉列表并没有绑定到深层嵌套的HTML结构上,也就可以在页面上需要的地方随意使用。</p>\n</li>\n<li>\n<p>对于多元素模块,如媒体对象。给主容器添加media类名来匹配模块名称。对于容器内的图片和正文,可以使用类名media__image和media__body。这些类名以模块名称开头,后跟双下划线,然后是子元素的名称。</p>\n</li>\n<li>\n<p>模块封装的一个非常重要的原则--单一职责原则(Single Responsibility Principle)。有一条经验:“如果你不得不使用并(或者和)这个词来表述模块的职责,那你可能正在描述多项职责。” 尽可能让一个模块只负责一项职责。</p>\n</li>\n<li>\n<p>应该尽量让需要定位的元素关联到同一个模块内的其他元素。只有这样,我们把模块放在另一个有定位的容器里的时候,才不会弄乱样式。</p>\n</li>\n<li>\n<p>状态类(state class)代表着模块在当前状态下的表现。通常在模块里使用JavaScript动态地添加或移除它。按照惯例,状态类一般以is-或者has-开头。这样状态类的目的就会比较明显,它们表示模块当前状态下的一些特征或者即将发生的变化。状态类的代码要和模块的其他代码放在一起。使用JavaScript动态更改模块表现的时候,要使用状态类去触发改变。</p>\n</li>\n</ul>\n<h3 id=\"预处理器\"> 预处理器</h3>\n<p>所有的预处理器(比如Sass或者LESS)都提供了把分散的CSS文件合并成一个文件的功能。我们可以用多个文件和多个目录来组织样式,最后提供一个文件给浏览器。这样可以减少浏览器发起的网络请求数,开发者也可以把代码文件拆分成易于维护的大小。\n//TODO</p>\n<h3 id=\"工具类\"> 工具类</h3>\n<p>有时候,我们需要用一个类来对元素做一件简单明确的事,比如让文字居中、让元素左浮动,或者清除浮动。这样的类被称为工具类(utility class)。工具类是唯一应该使用important注释的地方。事实上,工具类应该优先使用它。这样的话,不管在哪里用到工具类,都可以生效。</p>\n<p>工具类的作用立竿见影。在页面上做点小事儿的时候不需要创建一个完整的模块,这种情况下可以用一个工具类来实现。但是不要滥用工具类。对于大部分网站,最多十几个工具类就够用了。</p>\n<details><summary>示例</summary>\n<div><pre><code><span>.text-center</span> <span>{</span>\n <span>text-align</span><span>:</span> center <span>!important</span><span>;</span>\n<span>}</span>\n\n<span>.float-left</span> <span>{</span>\n <span>float</span><span>:</span> left<span>;</span>\n<span>}</span>\n\n<span>.clearfix::before,\n.clearfix::after</span> <span>{</span>\n <span>content</span><span>:</span> <span>\" \"</span><span>;</span>\n <span>display</span><span>:</span> table<span>;</span>\n<span>}</span>\n\n<span>.clearfix::after</span> <span>{</span>\n <span>clear</span><span>:</span> both<span>;</span>\n<span>}</span>\n\n<span>.hidden</span> <span>{</span>\n <span>display</span><span>:</span> none <span>!important</span><span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br></div></div></details>\n<h2 id=\"模式库\"> 模式库</h2>\n<p>把模块清单整合成一组文档,在大型项目中已经成为通用做法。这组文档被称为模式库(pattern library)或者样式指南(style guide)。模式库不是网站或者应用程序的一部分,它是单独的一组HTML页面,用来展示每个CSS模块。模式库是你和你的团队在建站的时候使用的一个开发工具。</p>\n<h3 id=\"kss\"> KSS</h3>\n<p>一个模式库构建工具。</p>\n<ol>\n<li>创建项目文件夹</li>\n<li>npm init -y 初始化项目</li>\n<li>npm install --save-dev kss 安装KSS</li>\n<li>配置KSS配置文件</li>\n</ol>\n<ul>\n<li>新建kss-config.json</li>\n<li>在package.json中添加一条构建命令 <code>"build": "kss --config kss-config.json"</code></li>\n</ul>\n<ol start=\"5\">\n<li>编写KSS文档</li>\n<li>npm run build 构建KSS模式库</li>\n</ol>\n<details><summary>kss-config.json</summary>\n<div><pre><code><span>{</span>\n <span>\"title\"</span><span>:</span> <span>\"My pattern library\"</span><span>,</span>\n <span>\"source\"</span><span>:</span> <span>[</span>\n <span>\"./css\"</span> <span>// KSS将扫描的CSS源文件路径</span>\n <span>// 如果使用了预处理器,比如SASS或者Less,源文件目录应该指向SASS或者Less文件,</span>\n <span>// 但是css字段应该指向编译生成的CSS样式表。</span>\n <span>]</span><span>,</span>\n <span>\"destination\"</span><span>:</span> <span>\"docs/\"</span><span>,</span> <span>// 生成的模式库文件路径</span>\n <span>\"css\"</span><span>:</span> <span>[</span>\n <span>\"../css/styles.css\"</span> <span>// 相对于destination路径的css文件路径</span>\n <span>]</span><span>,</span>\n <span>\"js\"</span><span>:</span> <span>[</span>\n <span>\"../js/docs.js\"</span> <span>// 相对于destination路径的js文件路径</span>\n <span>]</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br></div></div></details>\n<h3 id=\"编写kss文档\"> 编写KSS文档</h3>\n<p>KSS会按照特定的方式在样式表中搜寻注释。注释中包含了标题(通常是模块名称)、描述信息、示例HTML代码和用来表示该模块在目录中位置的Styleguide注释。这几个部分之间通过空白行彼此间隔,便于KSS区分它们。严格来讲,只有最后的Styleguide注释是KSS必需的,如果没有这一行,KSS就会忽略整个注释块。</p>\n<div><pre><code><span>/*\nDropdown // 文档标题\n\n// 一些描述,使用Markdown语法\nA dropdown menu. Clicking the toggle button opens\nand closes the drawer.\n\nUse JavaScript to toggle the `is-open` class in\norder to open and close the dropdown.\n\nMarkup: // 用来举例说明模块的用法。KSS把这些HTML转化成模式库,以便读者预览效果,复制粘贴。\n<div>\n <button>Open menu</button>\n <div>\n Drawer contents\n </div>\n</div>\n\nStyleguide Dropdown // 在KSS目录中出现的名字,最多可有三级,写法为 one.two.three。可用于分组\n*/</span>\n<span>.dropdown</span> <span>{</span>\n ...\n<span>}</span>\n...\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br></div></div><div><p>注意</p>\n<p>要牢记HTML片段中不能有空白行,因为对KSS来讲,空白行就意味着markup这部分结束了。<br>\nKSS生成新页面的时候不会主动删除旧页面。如果重命名或者移动源码中的一部分文档,docs目录下的相应文件还在原地,与新文件共存。</p>\n</div>\n<hr>\n<p>KSS提供了阐述多重变体的方法,可以在模式库里把每个都渲染出来。<br>\n<code>{{modifier_class}}</code>注释指明修饰符类所属的位置。Markup后面一段则为可用修饰符列表。</p>\n<div><pre><code><span>/*\nButtons\n\nButtons are available in a number of sizes and\ncolors. You may mix and match any size with any\ncolor.\n\nMarkup:\n<button>\n click here\n</button>\n\n.button--success - A green success button\n.button--danger - A red danger button\n.button--small - A small button\n.button--large - A large button\n\nStyleguide Buttons\n*/</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br></div></div><hr>\n<p>概览文件<br>\n模式库的主页。在css目录下面新建一个名为homepage.md的文件。这是一个markdown格式的文件,用来整体介绍模式库。</p>\n<div><p>注意</p>\n<p>你可能会注意到目录中Overview链接仍然不能工作,因为现在是在磁盘上直接打开模式库文件,KSS把Overview链接指向了./而不是index.html。要解决这个问题,我们需要通过HTTP服务器访问模式库,这样./在浏览器里会链接到index.html。</p>\n</div>\n<hr>\n<p>JS<br>\n有些模块需要JavaScript配合一起工作。这时候,要为页面添加一些简单的JavaScript来演示模块的行为。没必要在模式库里引入一个功能齐全的JavaScript库。大多数情况下,切换不同的状态类就够了。<br>\nKSS会把js数组里列出的所有脚本文件都添加到页面上。我们可以把代码写到这些脚本文件中,为模块提供最基本的功能。</p>\n<div><pre><code><span>(</span><span>function</span> <span>(</span><span>)</span> <span>{</span>\n <span>var</span> dropdowns <span>=</span> document<span>.</span><span>querySelectorAll</span><span>(</span><span>'.dropdown__toggle'</span><span>)</span><span>;</span>\n <span>Array</span><span>.</span>prototype<span>.</span><span>forEach</span><span>.</span><span>call</span><span>(</span>dropdowns<span>,</span> <span>function</span><span>(</span><span>dropdown</span><span>)</span> <span>{</span>\n dropdown<span>.</span><span>addEventListener</span><span>(</span><span>'click'</span><span>,</span> <span>function</span> <span>(</span><span>event</span><span>)</span> <span>{</span>\n event<span>.</span>target<span>.</span>parentNode<span>.</span>classList<span>.</span><span>toggle</span><span>(</span><span>'is-open'</span><span>)</span><span>;</span>\n <span>}</span><span>)</span><span>;</span>\n <span>}</span><span>)</span><span>;</span>\n<span>}</span><span>(</span><span>)</span><span>)</span><span>;</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br></div></div>",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "CSS 预处理器",
"url": "https://kigane.github.io/note/js/css-sass/",
"id": "https://kigane.github.io/note/js/css-sass/",
"content_html": "<h2 id=\"sass\"> Sass</h2>\n<p>预处理器的工作原理是把我们写的源文件转译成输出文件,即常规CSS样式表。</p>\n<h3 id=\"安装\"> 安装</h3>\n<ol>\n<li>新建项目目录并进入</li>\n<li>npm init -y 初始化一个新的npm项目,创建package.json文件。</li>\n<li>npm install --save-dev node-sass 安装node-sass包,并把它作为开发依赖写入package.json。在Windows系统中,需要先安装node-gyp包。(-g全局安装)</li>\n</ol>\n<div><p>选择语法</p>\n<p>Sass支持两种语法:Sass和SCSS。它们的语言特性一样,但Sass语法去掉了所有的大括号和分号,严格使用缩进来表示代码结构,写法类似python。SCSS语法使用大括号和分号,更像常规CSS。</p>\n<p>SCSS文件使用.scss扩展名,Sass文件使用.sass扩展名。</p>\n</div>\n<h3 id=\"运行\"> 运行</h3>\n<ol>\n<li>在项目目录新建两个子文件夹,分别叫作sass和build。源文件放在sass文件夹,Sass会使用这些文件来生成CSS文件,并放到build文件夹。</li>\n<li>编辑package.json文件,在脚本中添加一条命令<code>"sass": "sass sass/index.scss build/styles.css"</code>。这样就定义了一条sass命令,运行的时候会把sass/index.scss编译成build/styles.css这个新文件。</li>\n<li>写代码,npm run sass,得到styles.css</li>\n</ol>\n<h3 id=\"核心特性\"> 核心特性</h3>\n<p>源scss</p>\n<div><pre><code><span><span>$brand-blue</span></span><span>:</span> #0086b3<span>;</span>\n\n<span>a:link </span><span>{</span>\n <span>color</span><span>:</span> <span>$brand-blue</span><span>;</span>\n<span>}</span>\n\n<span>.page-heading </span><span>{</span>\n <span>font-size</span><span>:</span> 1.6rem<span>;</span>\n <span>color</span><span>:</span> <span>$brand-blue</span><span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br></div></div><p>处理后生成的css</p>\n<div><pre><code><span>a:link</span> <span>{</span>\n <span>color</span><span>:</span> #0086b3<span>;</span>\n<span>}</span>\n\n<span>.page-heading</span> <span>{</span>\n <span>font-size</span><span>:</span> 1.6rem<span>;</span>\n <span>color</span><span>:</span> #0086b3<span>;</span>\n<span>}</span><span>/*# sourceMappingURL=index.css.map */</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br></div></div><div><p>源映射</p>\n<p>源映射:一个特殊文件(.map),计算机可以用它来追踪生成后的代码(在我们这里是CSS)中每一行对应的源代码中的那一行(Sass)。这个映射文件可以用在一些调试器中,包括浏览器的开发者工具。</p>\n</div>\n<h4 id=\"行内计算\"> 行内计算</h4>\n<p>Sass支持使用+、-、*、/和%进行行内计算。这样我们就可以从一个初始值获得多个值。这个特性在两个值相关但不同的时候特别有用。</p>\n<h4 id=\"嵌套选择器\"> 嵌套选择器</h4>\n<p>Sass允许在代码块内嵌套选择器。你可以使用嵌套把有关联的代码分到一组。Sass会把外层声明块的选择器与嵌套选择器合并。默认情况下,外层的选择器会自动添加到编译代码的每个选择器前面,拼接的位置还会插入一个空格。要修改默认操作,可以使用&符号代表外层选择器想要插入的位置。</p>\n<div><pre><code><span>.site-nav </span><span>{</span>\n <span>display</span><span>:</span> flex<span>;</span>\n\n <span>> li </span><span>{</span>\n <span>margin-top</span><span>:</span> 0<span>;</span>\n\n <span><span>&</span>.is-active </span><span>{</span>\n <span>display</span><span>:</span> block<span>;</span>\n <span>}</span>\n <span>}</span>\n<span>}</span>\n\n<span>/* 也可以在声明块内嵌套媒体查询 */</span>\n<span>html </span><span>{</span>\n <span>font-size</span><span>:</span> 1rem<span>;</span>\n\n <span><span>@media</span> <span>(</span><span>min-width</span><span>:</span> 45em<span>)</span></span> <span>{</span>\n <span>font-size</span><span>:</span> 1.25rem<span>;</span>\n <span>}</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br></div></div><p>编译后</p>\n<div><pre><code><span>.site-nav</span> <span>{</span>\n <span>display</span><span>:</span> flex<span>;</span>\n<span>}</span>\n<span>.site-nav > li</span> <span>{</span>\n <span>margin-top</span><span>:</span> 0<span>;</span>\n<span>}</span>\n<span>.site-nav > li.is-active</span> <span>{</span>\n <span>display</span><span>:</span> block<span>;</span>\n<span>}</span>\n\n<span>html</span> <span>{</span>\n <span>font-size</span><span>:</span> 1rem<span>;</span>\n<span>}</span>\n<span><span>@media</span> <span>(</span><span>min-width</span><span>:</span> 45em<span>)</span></span> <span>{</span>\n <span>html</span> <span>{</span>\n <span>font-size</span><span>:</span> 1.25rem<span>;</span>\n <span>}</span>\n<span>}</span><span>/*# sourceMappingURL=index.css.map */</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br></div></div><h4 id=\"局部文件-import\"> 局部文件--@import</h4>\n<p>局部文件可以允许你把样式分割成多个独立的文件,Sass会把这些文件拼接在一起生成一个文件。使用局部文件可以按照自己的想法随意组织文件,但最终只提供给浏览器一个文件,这样可以减少网络请求的数量。</p>\n<p>将样式文件拆分为一系列局部文件,在index.scss中使用<code>@import "path"</code>引入,运行Sass的时候,局部文件会被编译,然后插入到@import规则指定的地方。相当于C的#include。</p>\n<h4 id=\"混入-mixin\"> 混入(mixin)</h4>\n<p>混入是一小段CSS代码块,可以在样式表任意地方复用。使用@mixin规则来定义,使用@include规则来调用。例如</p>\n<div><pre><code><span>@mixin</span> <span>clrfix </span><span>{</span>\n <span><span>&</span>::before </span><span>{</span>\n <span>display</span><span>:</span> table<span>;</span>\n <span>content</span><span>:</span> <span>\" \"</span><span>;</span>\n <span>}</span>\n\n <span><span>&</span>::after </span><span>{</span>\n <span>clear</span><span>:</span> both<span>;</span>\n <span>}</span>\n<span>}</span>\n\n<span>.media </span><span>{</span>\n <span>@include</span> clrfix<span>;</span>\n <span>background-color</span><span>:</span> #eee<span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br></div></div><p>编译后</p>\n<div><pre><code><span>.media</span> <span>{</span>\n <span>background-color</span><span>:</span> #eee<span>;</span>\n<span>}</span>\n<span>.media::before</span> <span>{</span>\n <span>display</span><span>:</span> table<span>;</span>\n <span>content</span><span>:</span> <span>\" \"</span><span>;</span>\n<span>}</span>\n<span>.media::after</span> <span>{</span>\n <span>clear</span><span>:</span> both<span>;</span>\n<span>}</span><span>/*# sourceMappingURL=index.css.map */</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br></div></div><hr>\n<p>还可以定义带参数的混入,就像平时编程中使用的函数一样。</p>\n<div><pre><code><span>@mixin</span> <span>alert-color</span><span>(</span><span>$color</span><span>,</span> <span>$bg-color</span><span>)</span> <span>{</span>\n <span>color</span><span>:</span> <span>$color</span><span>;</span>\n <span>background-color</span><span>:</span> <span>$bg-color</span><span>;</span>\n<span>}</span>\n\n<span>.media-info </span><span>{</span>\n <span>@include</span> <span>alert-color</span><span>(</span>blue<span>,</span> lightblue<span>)</span><span>;</span>\n<span>}</span>\n<span>.media-info </span><span>{</span>\n <span>@include</span> <span>alert-color</span><span>(</span>red<span>,</span> pink<span>)</span><span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br></div></div><p>编译后</p>\n<div><pre><code><span>.media-info</span> <span>{</span>\n <span>color</span><span>:</span> blue<span>;</span>\n <span>background-color</span><span>:</span> lightblue<span>;</span>\n<span>}</span>\n\n<span>.media-info</span> <span>{</span>\n <span>color</span><span>:</span> red<span>;</span>\n <span>background-color</span><span>:</span> pink<span>;</span>\n<span>}</span><span>/*# sourceMappingURL=index.css.map */</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br></div></div><h4 id=\"扩展-extend\"> 扩展(extend)</h4>\n<p>sass还支持@extend规则。和mixin类似,但编译方式有所不同。对于扩展,sass不会多次复制相同的声明,而是把选择器组合在一起,这样它们就会包含同样的规则。</p>\n<div><pre><code><span>.message </span><span>{</span>\n <span>padding</span><span>:</span> .3em .5em<span>;</span>\n <span>border-radius</span><span>:</span> .5em<span>;</span>\n<span>}</span>\n\n<span>.message-info </span><span>{</span>\n <span>@extend</span> .message<span>;</span>\n <span>color</span><span>:</span> blue<span>;</span>\n <span>background-color</span><span>:</span> lightblue<span>;</span>\n<span>}</span>\n\n<span>.message-danger </span><span>{</span>\n <span>@extend</span> .message<span>;</span>\n <span>color</span><span>:</span> red<span>;</span>\n <span>background-color</span><span>:</span> pink<span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br></div></div><p>编译后</p>\n<div><pre><code><span>.message, .message-danger, .message-info</span> <span>{</span>\n <span>padding</span><span>:</span> 0.3em 0.5em<span>;</span>\n <span>border-radius</span><span>:</span> 0.5em<span>;</span>\n<span>}</span>\n\n<span>.message-info</span> <span>{</span>\n <span>color</span><span>:</span> blue<span>;</span>\n <span>background-color</span><span>:</span> lightblue<span>;</span>\n<span>}</span>\n\n<span>.message-danger</span> <span>{</span>\n <span>color</span><span>:</span> red<span>;</span>\n <span>background-color</span><span>:</span> pink<span>;</span>\n<span>}</span><span>/*# sourceMappingURL=index.css.map */</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br></div></div><p>Sass复制了.message-info和.message-danger选择器,并上移到第一个规则集。这样做的好处是标记只需要引用一个类,无须两个都引入,从<code><divclass="message message-info"></code>变成了<code><div class="message-info"></code>。</p>\n<div><p>提示</p>\n<p>@extend的输出长度通常会比mixin短一些。这是显而易见的,也很容易想到@extend更好一些,因为它最终输出的样式表更小(因此网络传输速度更快)。但也要知道mixin产生的大量重复代码,使用gzip可以压缩得比较小。只要你的服务器使用gzip压缩处理过所有的网络传输(当然,也应该这么做),增加的这些重复代码通常会比预期小得多。</p>\n</div>\n<h4 id=\"颜色处理\"> 颜色处理</h4>\n<p>Sass还有个不错的特性,它有一堆处理颜色的函数。<a href=\"http://jackiebalzer.com/color\" target=\"_blank\" rel=\"noopener noreferrer\">参考:AVisual Guide to Sass & Compass Color Functions</a></p>\n<h4 id=\"循环\"> 循环</h4>\n<p>使用<code>@for $var from start through/to end {}</code></p>\n<ul>\n<li>through 包括 end</li>\n<li>to 不包括 end</li>\n</ul>\n<div><pre><code><span><span>$base-color</span></span><span>:</span> #036<span>;</span>\n\n<span>@for</span> <span>$i</span> <span>from</span> 1 <span>through</span> <span>3 </span><span>{</span>\n <span>ul</span><span>:</span><span>nth-child</span><span>(</span>3n <span>+</span> <span>#{$i}</span><span>)</span> <span>{</span>\n <span>background-color</span><span>:</span> <span>lighten</span><span>(</span><span>$base-color</span><span>,</span> <span>$i</span> <span>*</span> 5%<span>)</span><span>;</span>\n <span>}</span>\n<span>}</span>\n\n<span>@for</span> <span>$i</span> <span>from</span> <span>1 to 3 </span><span>{</span>\n <span>ol</span><span>:</span><span>nth-child</span><span>(</span>3n <span>+</span> <span>#{$i}</span><span>)</span> <span>{</span>\n <span>background-color</span><span>:</span> <span>lighten</span><span>(</span><span>$base-color</span><span>,</span> <span>$i</span> <span>*</span> 5%<span>)</span><span>;</span>\n <span>}</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br></div></div><p>编译后</p>\n<div><pre><code><span>ul:nth-child(3n+1)</span> <span>{</span>\n <span>background-color</span><span>:</span> #004080<span>;</span>\n<span>}</span>\n\n<span>ul:nth-child(3n+2)</span> <span>{</span>\n <span>background-color</span><span>:</span> #004d99<span>;</span>\n<span>}</span>\n\n<span>ul:nth-child(3n+3)</span> <span>{</span>\n <span>background-color</span><span>:</span> #0059b3<span>;</span>\n<span>}</span>\n\n<span>ol:nth-child(3n+1)</span> <span>{</span>\n <span>background-color</span><span>:</span> #004080<span>;</span>\n<span>}</span>\n\n<span>ol:nth-child(3n+2)</span> <span>{</span>\n <span>background-color</span><span>:</span> #004d99<span>;</span>\n<span>}</span><span>/*# sourceMappingURL=index.css.map */</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br></div></div><div><p>提示</p>\n<p>sass中的<code>#{expr}</code>称为Interpolation,用于将sass中的expr值计算出来,嵌入CSS块中。通常用于属性,选择器,@规则,还有例子中的nth-child参数部分等等。而在属性值用<code>#{expr}</code>,等于没用。</p>\n</div>\n<div><pre><code><span>@mixin</span> <span>corner-icon</span><span>(</span><span>$name</span><span>,</span> <span>$top-or-bottom</span><span>,</span> <span>$left-or-right</span><span>)</span> <span>{</span>\n <span>.icon-<span>#{$name}</span> </span><span>{</span>\n <span>background-image</span><span>:</span> <span>url</span><span>(</span><span>\"/icons/#{$name}.svg\"</span><span>)</span><span>;</span>\n <span>position</span><span>:</span> absolute<span>;</span>\n <span><span>#{$top-or-bottom}</span></span><span>:</span> 0<span>;</span>\n <span><span>#{$left-or-right}</span></span><span>:</span> 0<span>;</span>\n <span>}</span>\n<span>}</span>\n\n<span>@include</span> <span>corner-icon</span><span>(</span><span>\"mail\"</span><span>,</span> top<span>,</span> left<span>)</span><span>;</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br></div></div>",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "css 小技巧",
"url": "https://kigane.github.io/note/js/css-tricks/",
"id": "https://kigane.github.io/note/js/css-tricks/",
"content_html": "<h2 id=\"背景与边框\"> 背景与边框</h2>\n<h3 id=\"透明边框\"> 透明边框</h3>\n<p>元素的背景默认会占据border+padding+content区域。即background-clip: border-box。<br>\n要实现元素背景外有一圈透明边框,需要修改background-clip为padding-box。则背景只占据padding+content区域。再将边框设为透明即可。</p>\n<h3 id=\"多重边框\"> 多重边框</h3>\n<p>box-shadow:一个正值的扩张半径加上两个为零的偏移量以及为零的模糊值,得到的“投影”其实就像一道实线边框。并且,它支持逗号分隔语法,我们可以创建任意数量的投影。</p>\n<div><p>注意</p>\n<ol>\n<li>box-shadow不会影响布局。所以,需要通过margin或padding模拟边框占据的空间。</li>\n<li>box-shadow不会响应鼠标事件,这可以通过使用内圈边框(加 inset 关键字)解决。</li>\n</ol>\n</div>\n<hr>\n<p>outline:如果只需要两层边框,那就可以先设置一层常规边框,再加上outline(描边)属性来产生外层的边框。相比于box-shadow只能产生实现边框,outline和border一样,有更多边框样式。<br>\noutline-offset:控制outline和border的间距,可以取负值,实现简单的缝边效果。如果负值和border的宽度相同,则两个边框会重合,outline在上。</p>\n<div><p>注意</p>\n<p>outline不一定会贴合border-radius属性产生的圆角,因此如果元素是圆角的,它的描边可能还是直角的。这种行为被CSS工作组认为是一个bug,因此未来可能会改为贴合border-radius圆角。</p>\n</div>\n<h3 id=\"边框内圆角\"> 边框内圆角</h3>\n<p>使用两个容器,外层设置背景+padding,内层设置border-radius即可。</p>\n<h3 id=\"条纹背景\"> 条纹背景</h3>\n<p>backgound-img: linear-gradient(...)</p>\n<ul>\n<li>如果多个色标具有相同的位置,它们会产生一个无限小的过渡区域,过渡的起止色分别是第一个和最后一个指定值。从效果上看,颜色会在那个位置突然变化,而不是一个平滑的渐变过程。</li>\n<li>如果某个色标的位置值比整个列表中在它之前的色标的位置值都要小,则该色标的位置值会被设置为它前面所有色标位置值的最大值。</li>\n</ul>\n<div><pre><code><span>backgound-img</span><span>:</span> <span>linear-gradient</span><span>(</span>to right<span>,</span> black 30%<span>,</span> white 0<span>,</span> white 70%<span>,</span> black 0<span>)</span> <span>/* 黑白黑 */</span>\n</code></pre>\n<div><span>1</span><br></div></div><hr>\n<p>斜向条纹</p>\n<div><pre><code><span>background-image</span><span>:</span> <span>repeating-linear-gradient</span><span>(</span>-45deg<span>,</span> black<span>,</span> black 10px<span>,</span> azure 0<span>,</span> azure 20px<span>)</span><span>;</span>\n<span>/*\n相当于\nlinear-gradient(-45deg, \nblack, black 10px, azure 0, azure 20px\nblack 20px, black 30px, azure 0, azure 40px\nblack 40px, black 50px, azure 0, azure 60px\n...)\n*/</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br></div></div><h2 id=\"布局\"> 布局</h2>\n<p>https://1linelayouts.glitch.me/</p>\n<h3 id=\"紧贴底部的页脚\"> 紧贴底部的页脚</h3>\n<p>想要的效果是,页头和页脚的高度由其内部因素来决定,而内容区块的高度应该可以自动伸展并占满所有的可用空间。</p>\n<div><pre><code><span>/*\n<div>\n <header><h1>Site Name</h1></header>\n <main>\n <p>haha There should have some content</p>\n <p>haha There should have some content</p>\n <p>haha There should have some content</p>\n </main>\n <footer>\n <p>copyright &copy; 2021</p>\n </footer>\n</div>\n*/</span>\n\n<span>.sf-container</span> <span>{</span>\n <span>display</span><span>:</span> flex<span>;</span>\n <span>flex-direction</span><span>:</span> column<span>;</span>\n <span>min-height</span><span>:</span> 98vh<span>;</span>\n<span>}</span>\n\n<span>main</span> <span>{</span>\n <span>flex</span><span>:</span> 1<span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br></div></div><h2 id=\"oneline-css\"> oneline css</h2>\n<h3 id=\"水平-垂直居中\"> 水平,垂直居中</h3>\n<div><pre><code><span>.parent</span> <span>{</span>\n <span>display</span><span>:</span> grid<span>;</span>\n <span>place-items</span><span>:</span>center<span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br></div></div><h3 id=\"the-deconstructed-pancake\"> The Deconstructed Pancake</h3>\n<div><pre><code><span>.parent</span> <span>{</span>\n <span>display</span><span>:</span> flex<span>;</span>\n <span>flex-wrap</span><span>:</span> wrap<span>;</span>\n <span>justify-content</span><span>:</span> center<span>;</span>\n<span>}</span>\n\n<span>.box</span> <span>{</span>\n <span>flex</span><span>:</span> 1 1 150px<span>;</span> <span>/* Stretching: */</span>\n <span>/* flex: 0 1 150px; No stretching: */</span>\n <span>margin</span><span>:</span> 5px<span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br></div></div><h3 id=\"sidebar\"> sidebar</h3>\n<p>左侧边栏最小150px,最大25%。其余空间由内容占据。</p>\n<div><pre><code><span>.parent</span> <span>{</span>\n <span>display</span><span>:</span> grid<span>;</span>\n <span>grid-template-columns</span><span>:</span> <span>minmax</span><span>(</span>150px<span>,</span> 25%<span>)</span> 1fr<span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br></div></div><h3 id=\"pancake-stack\"> Pancake Stack</h3>\n<p>display:grid中的auto表示元素扩展到能容纳网格元素内容。<br>\nfr类似flex中的增长因子。</p>\n<div><pre><code><span>.parent</span> <span>{</span>\n <span>display</span><span>:</span> grid<span>;</span>\n <span>grid-template-rows</span><span>:</span> auto 1fr auto<span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br></div></div><h3 id=\"经典圣杯布局\"> 经典圣杯布局</h3>\n<div><pre><code><span>.parent</span> <span>{</span>\n <span>display</span><span>:</span> grid<span>;</span>\n <span>grid-template</span><span>:</span> auto 1fr auto / auto 1fr auto<span>;</span>\n<span>}</span>\n \n<span>header</span> <span>{</span>\n <span>padding</span><span>:</span> 2rem<span>;</span>\n <span>grid-column</span><span>:</span> 1 / 4<span>;</span>\n<span>}</span>\n\n<span>.left-side</span> <span>{</span>\n <span>grid-column</span><span>:</span> 1 / 2<span>;</span>\n<span>}</span>\n\n<span>main</span> <span>{</span>\n <span>grid-column</span><span>:</span> 2 / 3<span>;</span>\n<span>}</span>\n\n<span>.right-side</span> <span>{</span>\n <span>grid-column</span><span>:</span> 3 / 4<span>;</span>\n<span>}</span>\n\n<span>footer</span> <span>{</span>\n <span>grid-column</span><span>:</span> 1 / 4<span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br><span>25</span><br></div></div><h3 id=\"_12-span-网格布局\"> 12-span(网格布局)</h3>\n<div><pre><code><span>.parent</span> <span>{</span>\n <span>display</span><span>:</span> grid<span>;</span>\n <span>grid-template-columns</span><span>:</span> <span>repeat</span><span>(</span>12<span>,</span> 1fr<span>)</span><span>;</span>\n<span>}</span>\n \n<span>.span-12</span> <span>{</span>\n <span>grid-column</span><span>:</span> 1 / span 12<span>;</span>\n<span>}</span>\n\n<span>.span-6</span> <span>{</span>\n <span>grid-column</span><span>:</span> 1 / span 6<span>;</span>\n<span>}</span>\n\n<span>.span-4</span> <span>{</span>\n <span>grid-column</span><span>:</span> 4 / span 4<span>;</span>\n<span>}</span>\n\n<span>.span-2</span> <span>{</span>\n <span>grid-column</span><span>:</span> 3 / span 2<span>;</span>\n<span>}</span>\n\n<span>/* centering text */</span>\n<span>.section</span> <span>{</span>\n <span>display</span><span>:</span> grid<span>;</span>\n <span>place-items</span><span>:</span> center<span>;</span>\n <span>text-align</span><span>:</span> center\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br><span>25</span><br><span>26</span><br><span>27</span><br></div></div><h3 id=\"ram\"> RAM</h3>\n<ul>\n<li>auto-fill 会尽可能多的产生网格列,因此,和repeat,minmax合用时,当容器特别宽时,可能会有多余的轨道空出来。</li>\n<li>auto-fit 同auto-fill,但当容器特别宽时已有元素或轨道会扩展,直到占满容器宽度。</li>\n<li>当容器宽度较小时,两者表现相同。</li>\n</ul>\n<div><pre><code><span>.parent</span> <span>{</span>\n <span>display</span><span>:</span> grid<span>;</span>\n <span>grid-gap</span><span>:</span> 1rem<span>;</span>\n <span>grid-template-columns</span><span>:</span> <span>repeat</span><span>(</span>auto-fit<span>,</span> <span>minmax</span><span>(</span>150px<span>,</span> 1fr<span>)</span><span>)</span><span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br></div></div><h2 id=\"回到顶部\"> 回到顶部</h2>\n<ul>\n<li>利用a标签href为#时会自动会到顶部的特性。</li>\n<li>css中设置 <code>* { scroll-behavior:smooth }</code></li>\n</ul>\n",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "JS 基础",
"url": "https://kigane.github.io/note/js/js-base/",
"id": "https://kigane.github.io/note/js/js-base/",
"content_html": "<h2 id=\"变量\"> 变量</h2>\n<p>按照ECMA-262的定义,JavaScript的变量只是在特定时间用于保存特定值的一个名字而已。</p>\n<h3 id=\"变量基础\"> 变量基础</h3>\n<h4 id=\"类型\"> 类型</h4>\n<p>ECMAScript变量可能包含两种不同数据类型的值:基本类型值和引用类型值。</p>\n<ul>\n<li>基本类型值指的是简单的数据段:包括 Undefined、Null、Boolean、Number和String 这五种。</li>\n<li>引用类型值指那些可能由多个值构成的对象。引用类型的值是保存在内存中的对象。与其他语言不同,JavaScript不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。(创建的对象只不给你直接访问的方式,对对象的所有的操作都是通过指针进行的)</li>\n</ul>\n<h4 id=\"值传递\"> 值传递</h4>\n<p>JavaScript中,变量的赋值,函数的传参都只有一种方式--按值传递。基本类型不必多说。引用类型的“值传递”实际上类似于C/C++中的按指针传递,就是将指针的值复制一份。</p>\n<h4 id=\"类型检测\"> 类型检测</h4>\n<p>typeof操作符是确定一个变量是字符串、数值、布尔值,还是undefined的最佳工具。对于对象,返回Object。对于函数(实现了<code>[[Call]]</code>方法的对象),返回Function。</p>\n<p>instanceof操作符。用法为:<code>result = variable instanceof Constructor</code>。\n如果变量是给定引用类型的实例,就根据它的原型链来识别,即检查variable的原型链中是否存在Constructor.prototype,有则返回true。</p>\n<p>根据规定,所有引用类型的值都是Object的实例。因此,在检测一个引用类型值和Object构造函数时,instanceof操作符始终会返回true。当然,如果使用instanceof操作符检测基本类型的值,则该操作符始终会返回false,因为基本类型不是对象。</p>\n<h3 id=\"执行环境及作用域\"> 执行环境及作用域</h3>\n<p>执行环境(execution context,也称“环境”)定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。虽然我们编写的代码无法访问这个对象,但解析器在处理数据时会在后台使用它。</p>\n<p>全局执行环境是最外围的一个执行环境。根据ECMAScript实现所在的宿主环境不同,表示执行环境的对象也不一样。</p>\n<ul>\n<li>在Web浏览器中,全局执行环境被认为是window对象。</li>\n<li>每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。</li>\n</ul>\n<p>当代码在一个环境中执行时,会创建变量对象的一个作用域链。用于保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其<strong>活动对象</strong>作为变量对象。活动对象在最开始时只包含一个变量,即arguments对象(这个对象在全局环境中是不存在的)。作用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,一直延续到全局执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。</p>\n<p>标识符解析是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始,然后逐级地向后回溯,直至找到标识符为止(找不到就报错)。\n内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数(作用域链是个单向链表🤔)。\n<strong>函数参数也被当作变量来对待</strong>,因此其访问规则与执行环境中的其他变量相同。</p>\n<h3 id=\"垃圾收集\"> 垃圾收集</h3>\n<p>JavaScript具有自动垃圾收集机制,也就是说,执行环境会负责管理代码执行过程中使用的内存。主要有两种策略</p>\n<h4 id=\"标记清除-mark-and-sweep\"> 标记清除(mark-and-sweep)</h4>\n<p>垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记。而在此之后仍有标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾收集器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。</p>\n<h4 id=\"引用计数-reference-counting\"> 引用计数(reference counting)</h4>\n<p>释放那些引用次数为零的值所占用的内存。需要特别小心“循环引用”问题。</p>\n<h4 id=\"性能\"> 性能</h4>\n<p>垃圾收集器是周期性运行的,而且如果为变量分配的内存数量很可观,那么回收工作量也是相当大的。\n垃圾收集器是根据内存分配量运行的,具体一点说就是256个变量、4096个对象(或数组)字面量和数组元素(slot)或者64KB的字符串。达到上述任何一个<strong>临界值</strong>,垃圾收集器就会运行。\n如果垃圾收集例程回收的内存分配量低于15%,则变量、字面量和(或)数组元素的临界值就会加倍。如果例程回收了85%的内存分配量,则将各种临界值重置回默认值。(比固定临界值性能好很多)</p>\n<h4 id=\"内存管理\"> 内存管理</h4>\n<p>一旦数据不再有用,最好通过将其值设置为null来释放其引用——这个做法叫做解除引用(dereferencing)。这一做法适用于大多数全局变量和全局对象的属性。局部变量会在它们离开执行环境时自动被解除引用</p>\n<h2 id=\"函数\"> 函数</h2>\n<p>函数声明</p>\n<div><pre><code><span>function</span> <span>funcName</span><span>(</span><span>args</span><span>)</span><span>{</span>\n <span>// function body</span>\n<span>}</span><span>;</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br></div></div><p>函数表达式</p>\n<div><pre><code><span>var</span> <span>func1</span> <span>=</span> <span>function</span><span>(</span><span>args</span><span>)</span><span>{</span>\n <span>// function body</span>\n<span>}</span><span>;</span> <span>// 匿名函数 </span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br></div></div><p>一个典型错误</p>\n<div><pre><code><span>if</span> <span>(</span>condition<span>)</span>\n<span>{</span>\n <span>function</span> <span>sayHi</span><span>(</span><span>)</span><span>{</span><span>alert</span><span>(</span><span>\"Hi\"</span><span>)</span><span>;</span><span>}</span><span>;</span>\n<span>}</span>\n<span>else</span>\n<span>{</span>\n <span>function</span> <span>sayHi</span><span>(</span><span>)</span><span>{</span><span>alert</span><span>(</span><span>\"Hello\"</span><span>)</span><span>;</span><span>}</span><span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br></div></div><p>表面上看,以上代码表示在condition为true时,使用一个sayHi()的定义;否则,就使用另一个定义。实际上,这在ECMAScript中属于无效语法,JavaScript引擎会尝试修正错误,将其转换为合理的状态。但问题是浏览器尝试修正错误的做法并不一致。大多数浏览器会返回第二个声明,忽略condition; Firefox会在condition为true时返回第一个声明。这里用函数表达式就没问题了</p>\n<div><pre><code><span>var</span> func<span>;</span>\n<span>if</span> <span>(</span>condition<span>)</span>\n<span>{</span>\n <span>func</span> <span>=</span> <span>function</span><span>(</span><span>)</span><span>{</span><span>alert</span><span>(</span><span>\"Hi\"</span><span>)</span><span>;</span><span>}</span><span>;</span>\n<span>}</span>\n<span>else</span>\n<span>{</span>\n <span>func</span> <span>=</span> <span>function</span><span>(</span><span>)</span><span>{</span><span>alert</span><span>(</span><span>\"Hello\"</span><span>)</span><span>;</span><span>}</span><span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br></div></div><h3 id=\"递归\"> 递归</h3>\n<p>经典例子+经典错误</p>\n<div><pre><code><span>function</span> <span>factorial</span><span>(</span><span>num</span><span>)</span>\n<span>{</span>\n <span>if</span> <span>(</span>num <span><=</span> <span>1</span><span>)</span><span>{</span><span>return</span> num<span>;</span><span>}</span>\n <span>else</span><span>{</span> <span>return</span> num <span>*</span> <span>factorial</span><span>(</span>num<span>-</span><span>1</span><span>)</span><span>;</span><span>}</span>\n <span>// return num * arguments.callee(num - 1) 即可。</span>\n<span>}</span>\n\n<span>var</span> <span>F</span> <span>=</span> factorial<span>;</span>\nfactorial <span>=</span> <span>null</span><span>;</span>\n<span>F</span><span>(</span><span>5</span><span>)</span><span>;</span> <span>// TypeError: factorial is not a function</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br></div></div><p>因为,F(5)内部调用的还是factorial(4),但factorial已经被设为null了。所以在编写递归函数时,应该尽可能使用arguments.callee(arguments.callee是一个指向正在执行的函数的指针)。</p>\n<p>但在严格模式下,不能通过脚本访问arguments.callee,访问这个属性会导致错误。不过,可以使用命名函数表达式来达成相同的结果</p>\n<div><pre><code><span>var</span> factorial <span>=</span> <span>(</span><span>function</span> <span>f</span><span>(</span><span>num</span><span>)</span><span>{</span>\n <span>if</span> <span>(</span>num <span><=</span> <span>1</span><span>)</span><span>{</span><span>return</span> num<span>;</span><span>}</span>\n <span>else</span><span>{</span> <span>return</span> num <span>*</span> <span>f</span><span>(</span>num<span>-</span><span>1</span><span>)</span><span>;</span><span>}</span>\n <span>}</span><span>)</span><span>;</span>\n<span>var</span> <span>F</span> <span>=</span> factorial<span>;</span>\nfactorial <span>=</span> <span>null</span><span>;</span>\n<span>F</span><span>(</span><span>5</span><span>)</span><span>;</span> <span>// 120</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br></div></div><h3 id=\"闭包\"> 闭包</h3>\n<p><strong>闭包是指有权访问另一个函数作用域中的变量的函数</strong>。创建闭包的常见方式,就是在一个函数内部创建另一个函数。</p>\n<p>当某个函数被调用时,会创建一个执行环境(execution context)及相应的作用域链。然后,使用arguments和其他命名参数的值来初始化函数的活动对象(activation object)。但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,……直至作为作用域链终点的全局执行环境。</p>\n<p>在另一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域链中。<strong>当外部函数返回后,其执行环境的作用域链会被销毁,但它的活动对象仍然会留在内存中</strong>。</p>\n<p>由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多,我们建议读者只在绝对必要时再考虑使用闭包。虽然像V8等优化后的JavaScript引擎会尝试回收被闭包占用的内存,但请大家还是要慎重使用闭包。</p>\n<h4 id=\"经典错误\"> 经典错误</h4>\n<div><pre><code><span>function</span> <span>createFunc</span><span>(</span><span>)</span><span>{</span>\n <span>var</span> result <span>=</span> <span>new</span> <span>Array</span><span>(</span><span>)</span><span>;</span>\n <span>for</span> <span>(</span><span>var</span> i <span>=</span> <span>0</span><span>;</span> i <span><</span> <span>10</span><span>;</span> i<span>++</span><span>)</span>\n <span>{</span>\n result<span>[</span>i<span>]</span> <span>=</span> <span>function</span><span>(</span><span>)</span><span>{</span><span>return</span> i<span>}</span><span>;</span>\n <span>}</span>\n <span>return</span> result<span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br></div></div><p>表面上看,似乎每个函数都应该返自己的索引值,即位置0的函数返回0,位置1的函数返回1,以此类推。但实际上,每个函数都返回10。因为每个函数的作用域链中都保存着createFunctions()函数的活动对象,所以它们引用的都是同一个变量i。\n闭包只能取得包含函数中任何变量的最后一个值。</p>\n<h4 id=\"this-对象\"> this 对象</h4>\n<p>this对象是在运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象。不过,匿名函数的执行环境具有全局性,因此其this对象通常指向window。(当然,可以用call, apply修改this对象)</p>\n<p><strong>每个函数在被调用时都会自动取得两个特殊变量:this和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。</strong></p>\n<div><pre><code><span>var</span> name <span>=</span> <span>\"The Window\"</span><span>;</span>\n<span>var</span> object <span>=</span> <span>{</span>\n name<span>:</span> <span>\"the object\"</span><span>,</span>\n <span>getName</span><span>:</span> <span>function</span><span>(</span><span>)</span><span>{</span>\n <span>return</span> <span>this</span><span>.</span>name<span>;</span>\n <span>}</span>\n<span>}</span><span>;</span>\nobject<span>.</span><span>getName</span><span>(</span><span>)</span> <span>// \"the object\" </span>\n\n<span>var</span> object1 <span>=</span> <span>{</span>\n name<span>:</span> <span>\"the object1\"</span><span>,</span>\n <span>getName</span><span>:</span> <span>function</span><span>(</span><span>)</span><span>{</span>\n <span>return</span> <span>function</span><span>(</span><span>)</span><span>{</span>\n <span>return</span> <span>this</span><span>.</span>name<span>;</span>\n <span>}</span><span>;</span>\n <span>}</span>\n<span>}</span><span>;</span>\nobject1<span>.</span><span>getName</span><span>(</span><span>)</span><span>(</span><span>)</span> <span>// \"The Window\"</span>\n\n<span>var</span> object2 <span>=</span> <span>{</span>\n name<span>:</span> <span>\"the object2\"</span><span>,</span>\n <span>getName</span><span>:</span> <span>function</span><span>(</span><span>)</span><span>{</span>\n <span>var</span> that <span>=</span> <span>this</span><span>;</span> <span>// 使用闭包保存了this</span>\n <span>return</span> <span>function</span><span>(</span><span>)</span><span>{</span>\n <span>return</span> that<span>.</span>name<span>;</span>\n <span>}</span><span>;</span>\n <span>}</span>\n<span>}</span><span>;</span>\nobject2<span>.</span><span>getName</span><span>(</span><span>)</span><span>(</span><span>)</span> <span>// \"the object2\" </span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br><span>25</span><br><span>26</span><br><span>27</span><br><span>28</span><br><span>29</span><br></div></div><h4 id=\"闭包引起的内存泄露\"> 闭包引起的内存泄露</h4>\n<div><pre><code><span>function</span> <span>assignHandler</span><span>(</span><span>)</span><span>{</span>\n <span>var</span> elem <span>=</span> document<span>.</span><span>getElementById</span><span>(</span><span>\"some-id\"</span><span>)</span><span>;</span>\n elem<span>.</span><span>onclick</span> <span>=</span> <span>function</span><span>(</span><span>)</span><span>{</span>\n <span>alert</span><span>(</span>elem<span>.</span>id<span>)</span><span>;</span>\n <span>}</span><span>;</span> <span>// elem-->DOM元素, DOM元素的onclick是指向闭包,闭包中又有elem。因此产生了循环引用。</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br></div></div><p>解决方法是</p>\n<div><pre><code><span>function</span> <span>assignHandler</span><span>(</span><span>)</span><span>{</span>\n <span>var</span> elem <span>=</span> document<span>.</span><span>getElementById</span><span>(</span><span>\"some-id\"</span><span>)</span><span>;</span>\n <span>var</span> id <span>=</span> elem<span>.</span>id<span>;</span>\n elem<span>.</span><span>onclick</span> <span>=</span> <span>function</span><span>(</span><span>)</span><span>{</span>\n <span>alert</span><span>(</span>id<span>)</span><span>;</span>\n <span>}</span><span>;</span> \n elem <span>=</span> <span>null</span><span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br></div></div><h3 id=\"模仿块级作用域\"> 模仿块级作用域</h3>\n<p>用作块级作用域(通常称为私有作用域)的匿名函数的语法如下:</p>\n<div><pre><code><span>(</span><span>function</span><span>(</span><span>)</span><span>{</span>\n <span>// 这里是块级作用域</span>\n<span>}</span><span>)</span><span>(</span><span>)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br></div></div><p>JavaScript将function关键字当作一个函数声明的开始,而函数声明后面不能跟圆括号(不能这样<code>function(){}()</code>)。然而,函数表达式的后面可以跟圆括号。要将函数声明转换成函数表达式,只要像上面这样给它加上一对圆括号即可。</p>\n<p>这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。一般来说,我们都应该尽量少向全局作用域中添加变量和函数。在一个由很多开发人员共同参与的大型应用程序中,过多的全局变量和函数很容易导致命名冲突。而通过创建私有作用域,每个开发人员既可以使用自己的变量,又不必担心搞乱全局作用域。例如</p>\n<div><pre><code><span>(</span><span>function</span><span>(</span><span>)</span><span>{</span>\n <span>var</span> now <span>=</span> <span>new</span> <span>Date</span><span>(</span><span>)</span><span>;</span>\n <span>if</span> <span>(</span>now<span>.</span><span>getMonth</span><span>(</span><span>)</span> <span>==</span> <span>0</span> <span>&&</span> now<span>.</span><span>getDate</span><span>(</span><span>)</span> <span>==</span> <span>1</span><span>)</span><span>{</span>\n <span>alert</span><span>(</span><span>\"Happy New Year!\"</span><span>)</span><span>;</span>\n <span>}</span>\n<span>}</span><span>)</span><span>(</span><span>)</span><span>;</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br></div></div><h3 id=\"私有变量和单例模式\"> 私有变量和单例模式</h3>\n<p>严格来讲,JavaScript中没有私有成员的概念;所有对象属性都是公有的。不过,倒是有一个私有变量的概念。任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量。</p>\n<p>// TODO</p>\n<h3 id=\"scoped\"> <code>[[scoped]]</code></h3>\n<p><code>[[scoped]]</code>是Chorme开发者工具内部使用的私有属性,显示了函数可访问的变量(即函数的作用域中的变量)。</p>\n<div><pre><code><span>function</span> <span>a</span><span>(</span><span>)</span> <span>{</span>\n <span>var</span> foo <span>=</span> <span>'foo'</span><span>;</span>\n <span>var</span> obj <span>=</span> <span>{</span>\n <span>bar</span><span>:</span> <span>function</span> <span>(</span><span>)</span> <span>{</span>\n <span>return</span> foo<span>;</span>\n <span>}</span>\n <span>}</span><span>;</span>\n console<span>.</span><span>log</span><span>(</span>obj<span>)</span><span>;</span>\n<span>}</span>\n<span>a</span><span>(</span><span>)</span><span>;</span>\n\n<span>// obj.bar</span>\n<span>// [[Scopes]]: Scopes[2]</span>\n<span>// 0: Closure (a)</span>\n<span>// foo: \"foo\"</span>\n<span>// 1: Global</span>\n<span>// (all global variables)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br></div></div>",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "Vue.js",
"url": "https://kigane.github.io/note/js/vue/",
"id": "https://kigane.github.io/note/js/vue/",
"content_html": "<h2 id=\"基础\"> 基础</h2>\n<h2 id=\"深入\"> 深入</h2>\n<h3 id=\"data\"> data</h3>\n<p>vue组件中data中定义的数组,对象,实际类型是Proxy,都使用了代理。</p>\n<h3 id=\"全局变量\"> 全局变量</h3>\n<div><pre><code><span>const</span> app <span>=</span> <span>createApp</span><span>(</span>App<span>)</span>\napp<span>.</span>config<span>.</span>globalProperties<span>.</span>$foo <span>=</span> <span>'bar'</span><span>;</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br></div></div><p>或使用provide/inject<br>\n父组件中用provide选项提供数据,子组件中用inject选项使用该数据。</p>\n<div><pre><code><span>/*\nRoot\n└─ TodoList\n ├─ TodoItem\n └─ TodoListFooter\n ├─ ClearTodosButton\n └─ TodoListStatistics\n*/</span>\n\n<span>const</span> app <span>=</span> Vue<span>.</span><span>createApp</span><span>(</span><span>{</span><span>}</span><span>)</span>\n\napp<span>.</span><span>component</span><span>(</span><span>'todo-list'</span><span>,</span> <span>{</span>\n <span>data</span><span>(</span><span>)</span> <span>{</span>\n <span>return</span> <span>{</span>\n todos<span>:</span> <span>[</span><span>'Feed a cat'</span><span>,</span> <span>'Buy tickets'</span><span>]</span>\n <span>}</span>\n <span>}</span><span>,</span>\n <span>provide</span><span>(</span><span>)</span> <span>{</span>\n <span>return</span> <span>{</span>\n user<span>:</span> <span>'John Doe'</span>\n <span>}</span>\n <span>}</span><span>,</span>\n template<span>:</span> <span><span>`</span><span>\n <div>\n {{ todos.length }}\n <!-- rest of the template -->\n </div>\n </span><span>`</span></span>\n<span>}</span><span>)</span>\n\napp<span>.</span><span>component</span><span>(</span><span>'todo-list-statistics'</span><span>,</span> <span>{</span>\n inject<span>:</span> <span>[</span><span>'user'</span><span>]</span><span>,</span>\n <span>created</span><span>(</span><span>)</span> <span>{</span>\n console<span>.</span><span>log</span><span>(</span><span><span>`</span><span>Injected property: </span><span><span>${</span><span>this</span><span>.</span>user<span>}</span></span><span>`</span></span><span>)</span> <span>// > Injected property: John Doe</span>\n <span>}</span>\n<span>}</span><span>)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br><span>25</span><br><span>26</span><br><span>27</span><br><span>28</span><br><span>29</span><br><span>30</span><br><span>31</span><br><span>32</span><br><span>33</span><br><span>34</span><br><span>35</span><br><span>36</span><br></div></div><h3 id=\"router-link\"> router-link</h3>\n<ul>\n<li>当router-link的目标路径可以和当前路径匹配(例如,当前/foo/bar,可以匹配/,/foo,/foo/bar三个目标路径)时,该router-link标签会自动添加类.router-link-active</li>\n<li>当router-link的目标路径可以和当前路径完全匹配(一模一样)时,该router-link标签会自动添加类.router-link-exact-active</li>\n<li>可以在router-link标签上加exact属性,这样只有在完全匹配时才会添加类.router-link-active。通常会用于to='/'的router-link</li>\n<li><code><router-link to="/foo/bar"></router-link></code> 普通的router-link写法</li>\n<li><code><router-link :to="{name = 'Foo', params = {id : 'bar'}}"></router-link></code> 转到已命名的路由的router-link写法</li>\n<li>另一种动态链接写法</li>\n</ul>\n<div><pre><code><span><span><span><</span>router-link</span> <span>:to</span><span><span>=</span><span>\"</span>`/foo/${id}`<span>\"</span></span><span>></span></span><span><span><span></</span>router-link</span><span>></span></span> \n</code></pre>\n<div><span>1</span><br></div></div><h3 id=\"nested-router-view\"> nested router-view</h3>\n<p>Home组件中有一个router-view,这个router-view对应的组件中还有一个router-view。则内层router-view可以声明如下。</p>\n<div><pre><code><span>{</span>\n path<span>:</span> <span>'/home'</span><span>,</span>\n name<span>:</span> <span>'Home'</span><span>,</span>\n <span>component</span><span>:</span> <span>(</span><span>)</span> <span>=></span> <span>import</span><span>(</span><span>'@/views/Home.vue'</span><span>)</span><span>,</span>\n children<span>:</span> <span>[</span>\n <span>{</span>\n path<span>:</span> <span>':id'</span><span>,</span> <span>// 匹配/home/:id</span>\n name<span>:</span> <span>'Main'</span><span>,</span>\n <span>component</span><span>:</span> <span>(</span><span>)</span> <span>=></span> <span>import</span><span>(</span><span>'@/views/Main.vue'</span><span>)</span><span>,</span>\n props<span>:</span> <span>true</span> <span>// 让Home组件中的router-link中的路径参数可以传入Main组件,用Main的props接收。</span>\n <span>}</span><span>,</span>\n <span>]</span><span>,</span>\n <span>}</span><span>,</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br></div></div><h3 id=\"named-router-view\"> named router-view</h3>\n<p>普通的<code><router-view /></code>默认使用routes中的component指定的组件。如果一个页面要有多个router-view,每个router-view对应不同的组件,则需要为router-view指定name。</p>\n<div><pre><code><span><</span>router<span>-</span>view <span>/</span><span>></span>\n<span><</span>router<span>-</span>view name<span>=</span><span>'a'</span> <span>/</span><span>></span>\n<span><</span>router<span>-</span>view name<span>=</span><span>'b'</span> <span>/</span><span>></span>\n\n<span>// 在routes中,要为每个router-view指定好组件。</span>\n<span>{</span>\n path<span>:</span> <span>'xxx'</span><span>,</span>\n name<span>:</span> <span>'yyy'</span><span>,</span>\n component<span>:</span> <span>{</span>\n <span>default</span><span>:</span> ComponentDefault<span>,</span>\n a<span>:</span> ComponentA<span>,</span>\n b<span>:</span> ComponentB<span>,</span>\n <span>}</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br></div></div><h3 id=\"route路径参数-动态链接\"> route路径参数(动态链接)</h3>\n<div><pre><code><span>const</span> router <span>=</span> <span>new</span> <span>VueRouter</span><span>(</span><span>{</span>\n routes<span>:</span> <span>[</span>\n <span>// dynamic segments start with a colon</span>\n <span>{</span> path<span>:</span> <span>'/user/:id'</span><span>,</span> component<span>:</span> User<span>,</span> props<span>:</span> <span>true</span> <span>}</span>\n <span>]</span>\n<span>}</span><span>)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br></div></div><p>path中的:id称为dynamic segment。如果path匹配到了,则可以通过$route.params.id访问匹配到的id值。<br>\n如果路由对象的props设为true,则可由component的props属性接收路径参数。</p>\n<h3 id=\"如何获取事件的event对象\"> 如何获取事件的event对象</h3>\n<p><code>@event="onEvent($event)</code>,<code>$event</code>为原生DOM的事件对象。</p>\n",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "python",
"url": "https://kigane.github.io/note/python/",
"id": "https://kigane.github.io/note/python/",
"content_html": "<h2 id=\"import\"> import</h2>\n<h3 id=\"module\"> module</h3>\n<ul>\n<li>dir(): 无参数,查看全局命名空间的变量</li>\n<li>dir(module): 查看指定模块的变量</li>\n<li>一个.py文件可看作一个模块\n<ul>\n<li>import module (as alias) 引入module,可使用module命名空间及其内的变量</li>\n<li>from module import var 将var从module中导入全局命名空间</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"package\"> package</h3>\n<ul>\n<li>一个目录,包含目录和子模块。</li>\n<li>创建一个package需要在目录下创建一个<code>__init__.py</code></li>\n<li>需要在<code>__init__.py</code>中引入子模块,否则import package时空有命名空间,而无内容</li>\n</ul>\n<h3 id=\"模块查找顺序\"> 模块查找顺序</h3>\n<ul>\n<li>当前工作目录(cwd)</li>\n<li>python标准库, PYTHONPATH环境变量指定位置</li>\n<li>sys.path</li>\n</ul>\n<div><p>提示</p>\n<p>通过os.environ字典可以查看当前环境变量</p>\n</div>\n<h3 id=\"只导入一次\"> 只导入一次</h3>\n<p>导入模块时,如果模块中有可执行代码,则会执行。(<code>if __name__ == '__main__'</code>就是用于防止意外执行不希望执行的代码)。<br>\n重复导入相同模块,什么也不会发生。</p>\n<h2 id=\"解包-自动\"> 解包(自动)</h2>\n<ul>\n<li>等号右边的序列可以直接解包为单个元素,只要在左边分配同样数量的变量。</li>\n<li>数量必须相同,否则会报错</li>\n<li>可以部分解包,即在等号左边某个变量前加<code>*</code>号,则其会接受所有为未分配的值,并形成一个数组。(序列元素数量必须多于分配的变量数)。例如 <code>a, *b, c = [1, 2, 3, 4, 5]</code>,则有<code>b=[2,3,4]</code></li>\n</ul>\n<h2 id=\"函数\"> 函数</h2>\n<h3 id=\"参数\"> 参数</h3>\n<ul>\n<li><code>def add(*args)</code>: 这里<code>*</code>作用是将所有剩下的未解析参数打包到一个元组中,赋给args。调用时可以用<code>*args</code>的到解包后的值。</li>\n<li><code>def init(**kwargs)</code>: 这里<code>**</code>作用是将所有剩下未解析的关键字参数打包成字典,赋给kwargs。</li>\n<li><code>def say(a, b, *, key=val...)</code>: 这里<code>*</code>是标识符,左边是位置参数,右边是关键字参数。</li>\n<li>参数类型的声明顺序\n<ul>\n<li>必须参数</li>\n<li>可选参数(提供默认值的参数)</li>\n<li><code>*args</code></li>\n<li><code>**kwargs</code></li>\n</ul>\n</li>\n</ul>\n<h3 id=\"函数注解\"> 函数注解</h3>\n<ul>\n<li>python中对参数类型(参数后的冒号部分)和返回值类型(函数括号后,行尾冒号前的<code>->xx</code>部分)的描述方法</li>\n<li><code>def prepend_rows(row:list, prefix:str) -> list:</code></li>\n<li>这些部分属于注解而非表达式,所以内容是无限制的。</li>\n</ul>\n<h3 id=\"lambda\"> lambda</h3>\n<ul>\n<li><code>f = lambda x: x*x</code></li>\n<li>语法: <code>lambda params:body</code>, <code>lambda: body</code></li>\n<li>body必须是返回一个值的单个表达式</li>\n</ul>\n<h2 id=\"类\"> 类</h2>\n<h3 id=\"基础\"> 基础</h3>\n<ul>\n<li>python类声明的主体是一个代码块,可以包含任何有效的python代码</li>\n<li>在类声明中声明的变量会成为类对象的一个属性(即,类会创建一个新的命名空间记录这些变量)</li>\n<li>super(Class, instance, ...) 使用instance的MRO,从Class位置开始向上找。</li>\n</ul>\n<h3 id=\"动态加载\"> 动态加载</h3>\n<ul>\n<li>python所有的类都是type()的子类</li>\n<li>type()用于实例化类的三部分信息:\n<ul>\n<li>类名称</li>\n<li>基类</li>\n<li>命名空间字典,在执行类的主体时被填充</li>\n</ul>\n</li>\n<li>type()称为元类</li>\n</ul>\n<div><pre><code><span>class</span> <span>A</span><span>(</span><span>int</span><span>)</span><span>:</span>\n spam <span>=</span> <span>'eggs'</span>\n\n<span># 等价于</span>\n<span># 第一个A是为了创建类, 第二个A是用于将类名绑定到命名空间,两者可以不同(实际用时也确实是不同的)</span>\nA <span>=</span> <span>type</span><span>(</span><span>'A'</span><span>,</span> <span>(</span><span>int</span><span>,</span><span>)</span><span>,</span> <span>{</span><span>'spam'</span><span>:</span> <span>'eggs'</span><span>}</span><span>)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br></div></div><h4 id=\"自定义元类\"> 自定义元类</h4>\n<div><pre><code><span># 通过继承type自定义元类</span>\n<span>class</span> <span>SimpleMeta</span><span>(</span><span>type</span><span>)</span><span>:</span>\n <span>def</span> <span>__init__</span><span>(</span>cls<span>,</span> name<span>,</span> bases<span>,</span> attrs<span>)</span><span>:</span>\n <span>print</span><span>(</span>name<span>)</span>\n <span>super</span><span>(</span>SimpleMeta<span>,</span> cls<span>)</span><span>.</span>__init__<span>(</span>name<span>,</span> bases<span>,</span> attrs<span>)</span>\n\n<span># 使用元类,效果是:python会自动将类定义传递给元类进行处理</span>\n<span>class</span> <span>Example</span><span>(</span>metaclass<span>=</span>SimpleMeta<span>)</span><span>:</span>\n <span>pass</span> <span># 效果和标准类型一样</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br></div></div><h3 id=\"成员变量\"> 成员变量</h3>\n<ul>\n<li>getattr() 用名称检索成员变量 访问类中不存在的成员变量时会触发<code>__getattr__</code>(d['attr'])</li>\n<li>setattr() 用名称设置成员变量 设置类成员时触发<code>__setattr__</code>(d.attr = 'attr')</li>\n<li>delattr() 用名称删除成员变量 删除类成员时触发<code>__delattr__</code>(del d.attr)</li>\n<li><code>__str__</code> toString方法</li>\n<li><code>__repr__</code> 在解释器中单独引用对象时触发</li>\n</ul>\n<h2 id=\"打包到pip\"> 打包到pip</h2>\n<ul>\n<li>新建文件夹</li>\n<li>将package放进去(包含__init__.py)</li>\n<li>在根目录新建setup.py</li>\n<li>在根目录执行\n<ul>\n<li>python setup.py bdist_wheel 打包成wheel</li>\n<li>python setup.py sdist 打包成tar</li>\n</ul>\n</li>\n<li>在打包好的文件目录下pip install 打包好的文件名</li>\n<li>pip uninstall setup.name 即可卸载</li>\n</ul>\n<div><pre><code><span>from</span> setuptools <span>import</span> setup\n<span>from</span> setuptools <span>import</span> find_packages\n\nsetup<span>(</span>name<span>=</span><span>'MySheet'</span><span>,</span>\n version<span>=</span><span>'0.1'</span><span>,</span>\n author<span>=</span><span>'hwk'</span><span>,</span>\n author_email<span>=</span><span>'[email protected]'</span><span>,</span>\n url<span>=</span><span>'https://hello.com'</span><span>,</span>\n packages<span>=</span>find_packages<span>(</span><span>)</span><span>,</span>\n zip_safe<span>=</span><span>False</span><span>,</span>\n<span>)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br></div></div><h2 id=\"anaconda\"> anaconda</h2>\n<h3 id=\"常用命令\"> 常用命令</h3>\n<ul>\n<li>conda list: 查看安装了哪些包。</li>\n<li>conda env list: 查看当前存在哪些虚拟环境</li>\n<li>conda create -n your_env_name python=X.X: 创建python版本为X.X、名字为your_env_name的虚拟环境。your_env_name文件可以在Anaconda安装目录envs文件夹下找到。</li>\n<li>activate your_env_name: Windows下激活虚拟环境</li>\n<li>deactivate 退出</li>\n<li>Linux下,激活,退出都在开头加一个source</li>\n<li>conda remove -n your_env_name --all: 移除虚拟环境</li>\n<li>conda install package_name: 在当前环境中安装包</li>\n<li>conda install -n your_env_name package_name: 在指定环境中安装包</li>\n<li>update, remove语法和install相同</li>\n<li>conda search pname: 模糊搜索package</li>\n<li>conda search --full-name pname: 全名查找</li>\n<li>conda env export > environment.yml: 导出环境配置</li>\n<li>conda env create -f environment.yml: 导入环境配置</li>\n</ul>\n<h2 id=\"tqdm\"> tqdm</h2>\n<p>用在被遍历的可迭代对象上,自动产生进度条。</p>\n<div><pre><code><span>from</span> tqdm <span>import</span> tqdm\n<span>from</span> tqdm<span>.</span>notebook <span>import</span> tqdm_notebook\n<span>import</span> time\n<span>for</span> i <span>in</span> tqdm<span>(</span><span>range</span><span>(</span><span>20</span><span>)</span><span>,</span> desc<span>=</span><span>'Process Bar'</span><span>,</span> postfix<span>=</span><span>\"Postfix loss=xx\"</span><span>)</span><span>:</span>\n time<span>.</span>sleep<span>(</span><span>0.5</span><span>)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br></div></div>",
"date_published": "2022-04-02T08:34:13.000Z",
"date_modified": "2022-04-02T08:34:13.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": [
"Python"
]
},
{
"title": "Python数据类",
"url": "https://kigane.github.io/note/python/dataclass/",
"id": "https://kigane.github.io/note/python/dataclass/",
"content_html": "<h2 id=\"dataclasses\"> dataclasses</h2>\n<p>数据类,即一种主要用于存放数据的类。如果要手动写的话,需要在<code>__init__</code>方法中写一大段样板代码,并且可能需要重写<code>__eq__</code>,<code>__repr__</code>等方法。这些代码都高度重复,手写又很麻烦。Python从3.7开始内置了dataclasses模块,用于解决这个问题。</p>\n<h3 id=\"基本用法\"> 基本用法</h3>\n<div><pre><code><span>from</span> dataclasses <span>import</span> asdict<span>,</span> astuple<span>,</span> dataclass<span>,</span> field\n<span>import</span> inspect\n\n<span>@dataclass</span><span>(</span><span>)</span>\n<span>class</span> <span>Book</span><span>(</span><span>)</span><span>:</span>\n <span>id</span><span>:</span> <span>int</span> <span>=</span> <span>0</span>\n name<span>:</span> <span>str</span> <span>=</span> <span>'no name'</span>\n isbn<span>:</span> <span>str</span>\n references<span>:</span> <span>list</span> <span>=</span> field<span>(</span>default_factory<span>=</span><span>list</span><span>)</span>\n\nb <span>=</span> Book<span>(</span><span>0</span><span>,</span> <span>'hello'</span><span>,</span> <span>'123-2341-5345'</span><span>)</span>\n<span>print</span><span>(</span>inspect<span>.</span>getmembers<span>(</span>Book<span>,</span> inspect<span>.</span>isfunction<span>)</span><span>)</span>\n<span>print</span><span>(</span>astuple<span>(</span>b<span>)</span><span>)</span>\n<span>print</span><span>(</span>asdict<span>(</span>b<span>)</span><span>)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br></div></div><p>定义一个Book数据类,有三个属性,其中两个有默认值。用inspect模块查看Book类,可知其自动添加了<code>__init__</code>,<code>__eq__</code>,<code>__repr__</code>三个方法。通过dataclasses模块提供的astuple,asdict方法可以很轻松的将Book对象转换为其值的元组或字典。</p>\n<h3 id=\"冻结实例\"> 冻结实例</h3>\n<p>在@dataclass()装饰器参数中指定frozen=True即可。这样实例在创建后就不可更改,若有更改则会引发FrozenInstanceError。</p>\n<div><pre><code><span>@dataclass</span><span>(</span>frozen<span>=</span><span>True</span><span>)</span>\n<span>class</span> <span>Person</span><span>:</span>\n first_name<span>:</span> <span>str</span> <span>=</span> <span>\"Ahmed\"</span>\n last_name<span>:</span> <span>str</span> <span>=</span> <span>\"Besbes\"</span>\n age<span>:</span> <span>int</span> <span>=</span> <span>30</span>\n job<span>:</span> <span>str</span> <span>=</span> <span>\"Data Scientist\"</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br></div></div><h3 id=\"计算属性\"> 计算属性</h3>\n<p>有时候需要的某些属性是依赖于其他属性定义的。这时可以使用<code>field()</code>和定义<code>__post_init__()</code>函数处理这些属性。<br>\nfield中</p>\n<ul>\n<li>init=False 表示初始化类实例时不需要传入该参数</li>\n<li>repr=True 表示在<code>__repr__</code>中显示该属性。为False则不显示。</li>\n</ul>\n<div><pre><code><span>from</span> dataclasses <span>import</span> dataclass<span>,</span> field\n\n<span>@dataclass</span>\n<span>class</span> <span>Person</span><span>:</span>\n first_name<span>:</span> <span>str</span> <span>=</span> <span>\"Ahmed\"</span>\n last_name<span>:</span> <span>str</span> <span>=</span> <span>\"Besbes\"</span>\n age<span>:</span> <span>int</span> <span>=</span> <span>30</span>\n job<span>:</span> <span>str</span> <span>=</span> <span>\"Data Scientist\"</span>\n full_name<span>:</span> <span>str</span> <span>=</span> field<span>(</span>init<span>=</span><span>False</span><span>,</span> <span>repr</span><span>=</span><span>True</span><span>)</span>\n <span>def</span> <span>__post_init__</span><span>(</span>self<span>)</span><span>:</span>\n self<span>.</span>full_name <span>=</span> self<span>.</span>first_name <span>+</span> <span>\" \"</span> <span>+</span> self<span>.</span>last_name\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br></div></div><h3 id=\"实例排序\"> 实例排序</h3>\n<p>dataclasses默认没有添加<code>__gt__</code>,<code>__ge__</code>等比较方法的。在@dataclass()装饰器参数中指定order=True后,默认的比较方法是:按照属性在类中的定义顺序逐一比较。</p>\n<h2 id=\"pydantic\"> pydantic</h2>\n<p>作用和dataclasses类似。功能更强大,但需要安装pydantic包。</p>\n<h3 id=\"基础使用\"> 基础使用</h3>\n<div><pre><code><span>import</span> pydantic\n<span>from</span> typing <span>import</span> Optional\n\n<span>class</span> <span>Book</span><span>(</span>pydantic<span>.</span>BaseModel<span>)</span><span>:</span>\n title<span>:</span> <span>str</span>\n author<span>:</span> <span>str</span>\n publisher<span>:</span> <span>str</span>\n price<span>:</span> <span>float</span>\n isbn_10<span>:</span> Optional<span>[</span><span>str</span><span>]</span>\n isbn_13<span>:</span> Optional<span>[</span><span>str</span><span>]</span>\n subtitle<span>:</span> Optional<span>[</span><span>str</span><span>]</span>\n author2<span>:</span> Optional<span>[</span>Author<span>]</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br></div></div><h3 id=\"属性检查\"> 属性检查</h3>\n<p>定义一个方法,有两个参数,一个类,一个属性值。用<code>@classmethod</code>修饰,再用<code>@pydantic.validator("isbn_10")</code>指定要检查的属性,</p>\n<div><pre><code><span>class</span> <span>ISBN10FormatError</span><span>(</span>Exception<span>)</span><span>:</span>\n <span>def</span> <span>__init__</span><span>(</span>self<span>,</span> value<span>:</span> <span>str</span><span>,</span> message<span>:</span> <span>str</span><span>)</span> <span>-</span><span>></span> <span>None</span><span>:</span>\n self<span>.</span>value <span>=</span> value\n self<span>.</span>message <span>=</span> message\n <span>super</span><span>(</span><span>)</span><span>.</span>__init__<span>(</span>message<span>)</span>\n\n\n<span>class</span> <span>Book</span><span>(</span>pydantic<span>.</span>BaseModel<span>)</span><span>:</span>\n <span>.</span><span>.</span><span>.</span> <span># 同上</span>\n\n <span>@pydantic<span>.</span>validator</span><span>(</span><span>\"isbn_10\"</span><span>)</span>\n <span>@classmethod</span>\n <span>def</span> <span>isbn_10_valid</span><span>(</span>cls<span>,</span> value<span>)</span> <span>-</span><span>></span> <span>None</span><span>:</span>\n <span>\"\"\"Validator to check whether ISBN10 is valid\"\"\"</span>\n chars <span>=</span> <span>[</span>c <span>for</span> c <span>in</span> value <span>if</span> c <span>in</span> <span>\"0123456789Xx\"</span><span>]</span>\n <span>if</span> <span>len</span><span>(</span>chars<span>)</span> <span>!=</span> <span>10</span><span>:</span>\n <span>raise</span> ISBN10FormatError<span>(</span>\n value<span>=</span>value<span>,</span> message<span>=</span><span>\"ISBN10 should be 10 digits.\"</span><span>)</span>\n\n <span>def</span> <span>char_to_int</span><span>(</span>char<span>:</span> <span>str</span><span>)</span> <span>-</span><span>></span> <span>int</span><span>:</span>\n <span>if</span> char <span>in</span> <span>\"Xx\"</span><span>:</span>\n <span>return</span> <span>10</span>\n <span>return</span> <span>int</span><span>(</span>char<span>)</span>\n\n <span>if</span> <span>sum</span><span>(</span><span>(</span><span>10</span> <span>-</span> i<span>)</span> <span>*</span> char_to_int<span>(</span>x<span>)</span> <span>for</span> i<span>,</span> x <span>in</span> <span>enumerate</span><span>(</span>chars<span>)</span><span>)</span> <span>%</span> <span>11</span> <span>!=</span> <span>0</span><span>:</span>\n <span>raise</span> ISBN10FormatError<span>(</span>\n value<span>=</span>value<span>,</span> message<span>=</span><span>\"ISBN10 digit sum should be divisible by 11.\"</span>\n <span>)</span>\n <span>return</span> value\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br><span>25</span><br><span>26</span><br><span>27</span><br><span>28</span><br><span>29</span><br></div></div><h3 id=\"模型检查\"> 模型检查</h3>\n<div><pre><code><span>class</span> <span>ISBNMissingError</span><span>(</span>Exception<span>)</span><span>:</span>\n <span>def</span> <span>__init__</span><span>(</span>self<span>,</span> title<span>:</span> <span>str</span><span>,</span> message<span>:</span> <span>str</span><span>)</span> <span>-</span><span>></span> <span>None</span><span>:</span>\n self<span>.</span>title <span>=</span> title\n self<span>.</span>message <span>=</span> message\n <span>super</span><span>(</span><span>)</span><span>.</span>__init__<span>(</span>message<span>)</span>\n\n<span>class</span> <span>Book</span><span>(</span>pydantic<span>.</span>BaseModel<span>)</span><span>:</span>\n <span>.</span><span>.</span><span>.</span> <span># 同上</span>\n <span>@pydantic<span>.</span>root_validator</span><span>(</span>pre<span>=</span><span>True</span><span>)</span>\n <span>@classmethod</span>\n <span>def</span> <span>check_isbn_10_or_13</span><span>(</span>cls<span>,</span> values<span>)</span><span>:</span>\n <span>\"\"\"Make sure there is either an isbn_10 or isbn_13 value defined\"\"\"</span>\n <span>if</span> <span>\"isbn_10\"</span> <span>not</span> <span>in</span> values <span>and</span> <span>\"isbn_13\"</span> <span>not</span> <span>in</span> values<span>:</span>\n <span>raise</span> ISBNMissingError<span>(</span>\n title<span>=</span>values<span>[</span><span>\"title\"</span><span>]</span><span>,</span>\n message<span>=</span><span>\"Document should have either an ISBN10 or ISBN13\"</span><span>,</span>\n <span>)</span>\n <span>return</span> values\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br></div></div><h3 id=\"设置\"> 设置</h3>\n<p>在pydantic数据类内定义Config类,在Config类中使用pydantic设置。</p>\n<div><pre><code> <span>class</span> <span>Config</span><span>:</span>\n <span>\"\"\"Pydantic config class\"\"\"</span>\n allow_mutation <span>=</span> <span>False</span> <span># 让实例不可变</span>\n anystr_lower <span>=</span> <span>True</span> <span># 将所有str保存为str.lower()</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br></div></div><h3 id=\"将pydantic数据对象转为python字典对象\"> 将pydantic数据对象转为python字典对象</h3>\n<div><pre><code><span>print</span><span>(</span>books<span>[</span><span>0</span><span>]</span><span>.</span><span>dict</span><span>(</span>exclude<span>=</span><span>{</span><span>\"price\"</span><span>}</span><span>)</span><span>)</span> <span># 转为字典时不包括price属性</span>\n<span>print</span><span>(</span>books<span>[</span><span>0</span><span>]</span><span>.</span><span>dict</span><span>(</span>include<span>=</span><span>{</span><span>\"price\"</span><span>}</span><span>)</span><span>)</span> <span># 转为字典时只包括price属性</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br></div></div>",
"date_published": "2022-04-05T00:00:00.000Z",
"date_modified": "2022-04-20T00:32:32.305Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": [
"Python"
]
},
{
"title": "资源与工具",
"url": "https://kigane.github.io/note/js/resources/",
"id": "https://kigane.github.io/note/js/resources/",
"content_html": "<h2 id=\"谷歌字体\"> 谷歌字体</h2>\n<p><a href=\"https://fonts.google.com/\" target=\"_blank\" rel=\"noopener noreferrer\">谷歌字体网站</a><br>\n选中字体后,点击select this style。在右边弹窗中复制<code><lilnk></code>部分到html中,复制<code>@import</code>部分到css中。要使用字体时,复制下方的CSS rules即可指定字体。</p>\n<h2 id=\"font-awesome\"> font-awesome</h2>\n<p><a href=\"https://fontawesome.com/v5/cheatsheet\" target=\"_blank\" rel=\"noopener noreferrer\">FontAwesome</a><br>\n在index.html的head最后添加: <code><script src="https://kit.fontawesome.com/b9d02be592.js" crossorigin="anonymous"></script></code></p>\n<h2 id=\"serve\"> serve</h2>\n<p>npm i -g serve<br>\nserve -s /path/to/dist</p>\n<h2 id=\"json-serve\"> json-serve</h2>\n<p>npm i -g json-server<br>\njson-server --watch db.json --port 5000</p>\n",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "DL常用操作",
"url": "https://kigane.github.io/note/python/dl_operations/",
"id": "https://kigane.github.io/note/python/dl_operations/",
"content_html": "<h2 id=\"dataloader-dataset和sampler\"> Dataloader, Dataset和Sampler</h2>\n<p>先简单介绍一下Dataloader, Dataset和Sampler:</p>\n<ul>\n<li>Dataset即数据集,是实际数据存储的地方,可以简单的视为一个列表,列表中的元素为<code>(X, y)</code></li>\n<li>Sampler用于产生索引</li>\n<li>Dataloader使用Sampler产生的索引逐批量的读取Dataset中的数据。通常我们就是在每个产生的小批量上进行预测,计算损失,准确率,执行梯度下降。</li>\n</ul>\n<h3 id=\"数据采样\"> 数据采样</h3>\n<p><code>torch.multinomial(input, num_samples, replacement=False) → LongTensor</code></p>\n<ul>\n<li>replacement=False时,为不放回抽样。replacement=True时,为有放回抽样。</li>\n<li>num_samples为需要抽样出的样本数。显然,replacement=False时,num_samples不能超过对应的输入元素个数。</li>\n<li>当input为一维时,返回值为长为num_samples的一维张量。</li>\n<li>当input为二维时,返回值为<code>input.shape[0]</code>个num_samples长的二维张量。</li>\n<li>input的元素必须为float类型</li>\n<li>input的值的<strong>相对大小</strong>就代表了其索引被抽取的概率。可以理解为其内部会自动归一化。不要求input的值或其每一行的值总和为1,但其值必须非负,非inf,和大于零。</li>\n<li>返回值为LongTensor,每个元素为抽取的样本在input相应行的索引值。</li>\n</ul>\n<h3 id=\"dataloader\"> Dataloader</h3>\n<p><code>torch.utils.data.DataLoader(dataset, batch_size=1, shuffle=False, sampler=None, batch_sampler=None, num_workers=0, collate_fn=None, pin_memory=False, drop_last=False)</code></p>\n<ul>\n<li>dataset, batch_size</li>\n<li>shuffle为False,使用SequentialSampler,为True则使用RandomSampler</li>\n<li>sampler可以自定义,但要注意此时不可有shuffle参数。</li>\n<li>batch_sampler作用是将sampler产生的索引列表根据batch_size分组。</li>\n<li>collate_fn作用是将一个batch的样本合并为一个张量。即<code>[(X1, y1), ....] ==> ([X1, ...], [y1, ...])</code></li>\n<li>pin_memory: //TODO</li>\n<li>drop_last表示当数据集中最后一点数据凑不够一个batch时,是直接丢弃,还是就将剩下的数据算作一个batch。</li>\n<li>num_workers表示使用多少线程加载数据。Windows上有bug,只能单线程。</li>\n</ul>\n<div><p>Dataloader的核心代码</p>\n<div><pre><code><span># 老版本</span>\n<span>def</span> <span>__next__</span><span>(</span>self<span>)</span><span>:</span>\n <span>if</span> self<span>.</span>num_workers <span>==</span> <span>0</span><span>:</span> \n indices <span>=</span> <span>next</span><span>(</span>self<span>.</span>sample_iter<span>)</span> <span># 用Sampler确定索引</span>\n batch <span>=</span> self<span>.</span>collate_fn<span>(</span><span>[</span>self<span>.</span>dataset<span>[</span>i<span>]</span> <span>for</span> i <span>in</span> indices<span>]</span><span>)</span> <span># 从Dataset获取每个数据后合并</span>\n <span>if</span> self<span>.</span>pin_memory<span>:</span>\n batch <span>=</span> _utils<span>.</span>pin_memory<span>.</span>pin_memory_batch<span>(</span>batch<span>)</span>\n <span>return</span> batch\n\n<span># 新版本 更复杂了,但基本逻辑还是一样</span>\n<span>def</span> <span>__next__</span><span>(</span>self<span>)</span> <span>-</span><span>></span> Any<span>:</span>\n <span>.</span><span>.</span><span>.</span>\n data <span>=</span> self<span>.</span>_next_data<span>(</span><span>)</span>\n <span>.</span><span>.</span><span>.</span>\n\n<span>def</span> <span>_next_data</span><span>(</span>self<span>)</span><span>:</span> <span># 位于_SingleProcessDataLoaderIter下</span>\n index <span>=</span> self<span>.</span>_next_index<span>(</span><span>)</span> <span># may raise StopIteration</span>\n data <span>=</span> self<span>.</span>_dataset_fetcher<span>.</span>fetch<span>(</span>index<span>)</span> <span># may raise StopIteration</span>\n <span>if</span> self<span>.</span>_pin_memory<span>:</span>\n data <span>=</span> _utils<span>.</span>pin_memory<span>.</span>pin_memory<span>(</span>data<span>)</span>\n <span>return</span> data\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br></div></div></div>\n<h3 id=\"sampler\"> Sampler</h3>\n<p>Sampler的核心方法是<code>__iter__</code></p>\n<ul>\n<li>RandomSampler: shuffle为True时,replacement为False。\n<ul>\n<li>data_source: 数据集</li>\n<li>num_samples: 指定采样的数量,默认是所有。</li>\n<li>replacement: 默认为False,使用<code>randperm(n)</code>。若为True,则表示可以重复采样,使用<code>randint(n)</code>。</li>\n</ul>\n</li>\n<li>SubsetRandomSampler: 用于划分训练集和测试集\n<ul>\n<li>indices: 直接传入需要的索引。在迭代时顺序会被打乱<code>(self.indices[i] for i in torch.randperm(len(self.indices))</code></li>\n</ul>\n</li>\n<li>WeightedRandomSampler: 加权采样,用于处理类别不平衡问题。\n<ul>\n<li>weights: 权重张量。相对更大的值代表的索引更容易被选中。</li>\n<li>num_samples: 指定采样的数量</li>\n<li>replacement: 默认为False,使用<code>randperm(n)</code>。若为True,则表示可以重复采样,使用<code>randint(n)</code>。</li>\n</ul>\n</li>\n</ul>\n<div><p>SubsetRandomSampler使用示例</p>\n<div><pre><code>n_train <span>=</span> <span>len</span><span>(</span>train_dataset<span>)</span>\nsplit <span>=</span> n_train <span>//</span> <span>3</span>\nindices <span>=</span> <span>list</span><span>(</span><span>range</span><span>(</span>n_train<span>)</span><span>)</span>\nrandom<span>.</span>shuffle<span>(</span>indices<span>)</span>\ntrain_sampler <span>=</span> torch<span>.</span>utils<span>.</span>data<span>.</span>sampler<span>.</span>SubsetRandomSampler<span>(</span>indices<span>[</span>split<span>:</span><span>]</span><span>)</span>\nvalid_sampler <span>=</span> torch<span>.</span>utils<span>.</span>data<span>.</span>sampler<span>.</span>SubsetRandomSampler<span>(</span>indices<span>[</span><span>:</span>split<span>]</span><span>)</span>\ntrain_loader <span>=</span> DataLoader<span>(</span><span>.</span><span>.</span><span>.</span><span>,</span> sampler<span>=</span>train_sampler<span>,</span> <span>.</span><span>.</span><span>.</span><span>)</span>\nvalid_loader <span>=</span> DataLoader<span>(</span><span>.</span><span>.</span><span>.</span><span>,</span> sampler<span>=</span>valid_sampler<span>,</span> <span>.</span><span>.</span><span>.</span><span>)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br></div></div></div>\n<div><p>WeightedRandomSampler使用示例</p>\n<div><pre><code><span># 有4类,样本数分别为</span>\nclass_counts <span>=</span> <span>[</span><span>10</span><span>,</span> <span>20</span><span>,</span> <span>30</span><span>,</span> <span>40</span><span>]</span>\nclass_weights <span>=</span> <span>[</span><span>1</span><span>/</span>c <span>for</span> c <span>in</span> class_counts<span>]</span>\nsampler_weights <span>=</span> <span>[</span><span>]</span> <span># 计算每一个样本被采样到的概率</span>\n<span>for</span> w<span>,</span> c <span>in</span> <span>zip</span><span>(</span>class_weights<span>,</span> class_counts<span>)</span><span>:</span>\n sampler_weights <span>+=</span> <span>[</span>w<span>]</span> <span>*</span> c\n<span># 归一化后,更好理解sampler_weights的每一个值,代表一个样本被采样到的概率。</span>\n<span># sampler_weights = [x/sum(sampler_weights) for x in sampler_weights] </span>\nsampler <span>=</span> torch<span>.</span>utils<span>.</span>data<span>.</span>sampler<span>.</span>WeightedRandomSampler<span>(</span>sampler_weights<span>,</span> num_samples<span>=</span><span>len</span><span>(</span>sampler_weights<span>)</span><span>,</span> replacement<span>=</span><span>True</span><span>)</span>\nloader <span>=</span> DataLoader<span>(</span><span>.</span><span>.</span><span>.</span><span>,</span> sampler<span>=</span>sampler<span>,</span> <span>.</span><span>.</span><span>.</span><span>)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br></div></div></div>\n<p>BatchSampler: 将sampler产生的索引列表根据batch_size分组</p>\n<div><pre><code><span>def</span> <span>__iter__</span><span>(</span>self<span>)</span> <span>-</span><span>></span> Iterator<span>[</span>List<span>[</span><span>int</span><span>]</span><span>]</span><span>:</span>\n batch <span>=</span> <span>[</span><span>]</span>\n <span>for</span> idx <span>in</span> self<span>.</span>sampler<span>:</span>\n batch<span>.</span>append<span>(</span>idx<span>)</span>\n <span>if</span> <span>len</span><span>(</span>batch<span>)</span> <span>==</span> self<span>.</span>batch_size<span>:</span>\n <span>yield</span> batch\n batch <span>=</span> <span>[</span><span>]</span>\n <span>if</span> <span>len</span><span>(</span>batch<span>)</span> <span>></span> <span>0</span> <span>and</span> <span>not</span> self<span>.</span>drop_last<span>:</span>\n <span>yield</span> batch\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br></div></div><h3 id=\"另一种数据集划分方法random-split\"> 另一种数据集划分方法random_split</h3>\n<p><code>torch.utils.data.random_split(dataset, lengths)</code></p>\n<ul>\n<li>dataset 要划分的数据集</li>\n<li>lengths 需要划分出的数据集的长度列表</li>\n</ul>\n<div><pre><code>random_split<span>(</span><span>range</span><span>(</span><span>10</span><span>)</span><span>,</span> <span>[</span><span>3</span><span>,</span> <span>7</span><span>]</span><span>)</span>\n</code></pre>\n<div><span>1</span><br></div></div><h2 id=\"范数与标准化\"> 范数与标准化</h2>\n<CodeGroup>\n<CodeGroupItem title=\"pytorch\">\n<div><pre><code><span># torch.nn.functional.normalize(input, p=2.0, dim=1, eps=1e-12, out=None)</span>\n<span># 用input的dim维的向量的L~p~范数标准化input, v=v/||v||~p~</span>\nx <span>=</span> torch<span>.</span>tensor<span>(</span><span>[</span><span>1.0</span><span>,</span> <span>2.0</span><span>,</span> <span>2.0</span><span>]</span><span>,</span> dtype<span>=</span>torch<span>.</span><span>float</span><span>)</span>\nF<span>.</span>normalize<span>(</span>x<span>,</span> dim<span>=</span><span>0</span><span>)</span> <span># 输出:tensor([0.3333, 0.6667, 0.6667])</span>\n<span># torch.norm(input, p='fro', dim=None, keepdim=False, out=None, dtype=None)`</span>\n<span># p=(int, float, inf, -inf, 'fro', 'nuc'), fro即F范数,相当于L~2~范数</span>\ntorch<span>.</span>norm<span>(</span>x<span>)</span> <span># 输出:tensor([0.3333, 0.6667, 0.6667])</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br></div></div></CodeGroupItem>\n<CodeGroupItem title=\"numpy\">\n<div><pre><code><span># np.linalg.norm(x, ord=None, axis=None, keepdims=False):求L~p~范数。ord指定p</span>\nx <span>=</span> np<span>.</span>array<span>(</span><span>[</span><span>1.0</span><span>,</span> <span>2.0</span><span>,</span> <span>2.0</span><span>]</span><span>)</span>\nx_norm <span>=</span> np<span>.</span>linalg<span>.</span>norm<span>(</span>x<span>,</span> <span>ord</span><span>=</span><span>2</span><span>)</span>\n<span># 手动标准化</span>\nx_normed <span>=</span> x <span>/</span> x_norm\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br></div></div></CodeGroupItem>\n</CodeGroup>\n<h2 id=\"scipy随机变量\"> scipy随机变量</h2>\n<p>scipy的stats模块中整合了大量连续分布和离散分布的随机变量对象(RVs)。连续分布的RV包含以下方法</p>\n<ul>\n<li>rvs: Random Variates</li>\n<li>pdf: Probability Density Function</li>\n<li>cdf: Cumulative Distribution Function</li>\n<li>sf: Survival Function (1-CDF)</li>\n<li>ppf: Percent Point Function (Inverse of CDF)</li>\n<li>isf: Inverse Survival Function (Inverse of SF)</li>\n<li>stats: Return mean, variance, (Fisher’s) skew, or (Fisher’s) kurtosis</li>\n<li>moment: non-central moments of the distribution</li>\n<li>这些方法都有默认的关键词参数loc=mean, scale=std。norm默认为标准正态分布。</li>\n</ul>\n<div><pre><code><span>from</span> scipy <span>import</span> stats\n<span># pdf和cdf就是统计中的概率密度函数和累积密度函数</span>\n<span># RV的方法都可以输入一个向量,返回一个每个元素都用相应函数处理过的向量。</span>\n<span># 以正态分布为例</span>\nstats<span>.</span>norm<span>.</span>pdf<span>(</span><span>0</span><span>)</span> <span># 0.3989</span>\nstats<span>.</span>norm<span>.</span>cdf<span>(</span><span>0</span><span>)</span> <span># 0.5</span>\nstats<span>.</span>norm<span>.</span>cdf<span>(</span><span>[</span><span>-</span><span>1.0</span><span>,</span> <span>0</span><span>,</span> <span>1.0</span><span>]</span><span>)</span> <span># array([0.15865525, 0.5, 0.84134475])</span>\nstats<span>.</span>norm<span>.</span>mean<span>(</span><span>)</span>\nstats<span>.</span>norm<span>.</span>std<span>(</span><span>)</span>\nstats<span>.</span>norm<span>.</span>var<span>(</span><span>)</span>\nstats<span>.</span>norm<span>.</span>stats<span>(</span>moments<span>=</span><span>\"mvsk\"</span><span>)</span> <span># 返回均值,方差,偏度系数,峰度系数</span>\n<span># ppf是cdf的反函数。percent = cdf(x) ==> x = ppf(percent)</span>\nstats<span>.</span>norm<span>.</span>ppf<span>(</span><span>0.5</span><span>)</span>\nnorm<span>.</span>rvs<span>(</span>size<span>=</span><span>3</span><span>)</span> <span># 从正态分布中产生3个随机数</span>\n<span># 冻结分布 Freezing Distribution</span>\nnorm_rv <span>=</span> stats<span>.</span>norm<span>(</span>loc<span>=</span><span>5</span><span>,</span> scale<span>=</span><span>2</span><span>)</span> <span># norm_rv和stats.norm用法相同,但默认参数不同</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br></div></div><h2 id=\"截断分布-scipy-stats-truncnorm\"> 截断分布-scipy.stats.truncnorm</h2>\n<p>将标准正态分布截断到<code>[a, b]</code><br>\n移动和缩放分布:truncnorm.pdf(x, a, b, loc, scale) 等价于 truncnorm.pdf(y, a, b) / scale,其中 y = (x - loc) / scale</p>\n<p>要想将自定参数的正态分布截断到指定<code>[ma,mb]</code>,则需要重新计算形状参数 a, b = (ma - mean)/std, (mb - mean)/std。</p>\n<div><pre><code>mean<span>,</span> std <span>=</span> <span>2</span><span>,</span> <span>4</span>\nma<span>,</span> mb <span>=</span> <span>0</span><span>,</span> <span>4</span>\na<span>,</span> b <span>=</span> <span>(</span>ma <span>-</span> mean<span>)</span><span>/</span>std<span>,</span> <span>(</span>mb <span>-</span> mean<span>)</span><span>/</span>std\nx <span>=</span> np<span>.</span>linspace<span>(</span><span>-</span><span>5</span><span>,</span> <span>5</span><span>,</span> num<span>=</span><span>1000</span><span>)</span>\npdf <span>=</span> stats<span>.</span>truncnorm<span>.</span>pdf<span>(</span>x<span>,</span> a<span>,</span> b<span>,</span> mean<span>,</span> std<span>)</span> <span># 下图一</span>\n<span># pdf = stats.truncnorm.pdf(x, a, b) # 下图二</span>\nfig <span>=</span> sns<span>.</span>lineplot<span>(</span>x<span>=</span>x<span>,</span> y<span>=</span>pdf<span>)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br></div></div><p><img src=\"/assets/img/fig_truncnorm_1.png\" alt=\"调整过\" />\n<img src=\"/assets/img/fig_truncnorm_2.png\" alt=\"未调整\" /></p>\n<h2 id=\"谱归一化\"> 谱归一化</h2>\n<p><a href=\"https://arxiv.org/abs/1802.05957\" target=\"_blank\" rel=\"noopener noreferrer\">SNGAN</a>: 在GAN的Discriminator中使用,标准化Discriminator的权重参数,使其满足1-Lipschitz约束。</p>\n<CodeGroup>\n<CodeGroupItem title=\"python\">\n<div><pre><code><span>import</span> torch\n<span>import</span> torch<span>.</span>nn<span>.</span>functional <span>as</span> F\n\n<span>def</span> <span>spectral_norm</span><span>(</span>W<span>,</span> iteration<span>=</span><span>1</span><span>)</span><span>:</span>\n <span>\"\"\"谱归一化, 用于让 Discriminator 服从 1-Lipschitz 约束。\n 对权重矩阵 W,计算出其矩阵二范数 ||W||_2,并将其归一化为 W = W / ||W||_2\n\n Args:\n W (tensor): weights\n iteration (int, optional): num of iterations. Defaults to 1.\n \"\"\"</span>\n h<span>,</span> w <span>=</span> W<span>.</span>shape\n <span># 初始化</span>\n u <span>=</span> torch<span>.</span>normal<span>(</span><span>0</span><span>,</span> <span>1</span><span>,</span> size<span>=</span><span>(</span><span>1</span><span>,</span> h<span>)</span><span>)</span><span>.</span>squeeze<span>(</span><span>)</span>\n v <span>=</span> torch<span>.</span>normal<span>(</span><span>0</span><span>,</span> <span>1</span><span>,</span> size<span>=</span><span>(</span><span>1</span><span>,</span> w<span>)</span><span>)</span><span>.</span>squeeze<span>(</span><span>)</span>\n \n <span># power iteration</span>\n <span>for</span> _ <span>in</span> <span>range</span><span>(</span>iteration<span>)</span><span>:</span>\n u <span>=</span> F<span>.</span>normalize<span>(</span>torch<span>.</span>mv<span>(</span>W<span>,</span> v<span>)</span><span>,</span> dim<span>=</span><span>0</span><span>)</span>\n v <span>=</span> F<span>.</span>normalize<span>(</span>torch<span>.</span>mv<span>(</span>W<span>.</span>T<span>,</span> u<span>)</span><span>,</span> dim<span>=</span><span>0</span><span>)</span>\n \n <span># ||W||_2的近似解</span>\n sigma <span>=</span> torch<span>.</span>dot<span>(</span>u<span>,</span> torch<span>.</span>mv<span>(</span>W<span>,</span> v<span>)</span><span>)</span>\n\n <span>return</span> W <span>/</span> sigma\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br><span>24</span><br><span>25</span><br></div></div></CodeGroupItem>\n<CodeGroupItem title=\"pytorch\">\n<div><pre><code><span>import</span> torch\n<span>from</span> torch<span>.</span>nn<span>.</span>utils <span>import</span> spectral_norm <span># 未来会被下面的实现取代</span>\n<span># from torch.nn.utils.parametrizations import spectral_norm</span>\n\n<span># spectral_norm(module, name='weight', n_power_iterations=1, eps=1e-12, dim=None)</span>\nsnm <span>=</span> spectral_norm<span>(</span>nn<span>.</span>Linear<span>(</span><span>20</span><span>,</span> <span>40</span><span>)</span><span>)</span>\ntorch<span>.</span>linalg<span>.</span>matrix_norm<span>(</span>snm<span>.</span>weight<span>,</span> <span>2</span><span>)</span> <span># output: 1.0</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br></div></div></CodeGroupItem>\n</CodeGroup>\n",
"image": "https://kigane.github.io/assets/img/fig_truncnorm_1.png",
"date_published": "2022-03-28T00:00:00.000Z",
"date_modified": "2022-04-02T08:34:13.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": [
"Python"
]
},
{
"title": "Effective Python",
"url": "https://kigane.github.io/note/python/effective_python/",
"id": "https://kigane.github.io/note/python/effective_python/",
"content_html": "<h2 id=\"用-property替代getter-setter写法\"> 用@property替代getter, setter写法</h2>\n<ul>\n<li>用@property修饰的方法可以当做属性使用,被修饰的方法可看作属性的getter方法。</li>\n<li>用@attr.setter修饰的方法为属性的setter方法。</li>\n</ul>\n<div><pre><code><span>class</span> <span>A</span><span>:</span>\n\n <span>def</span> <span>__init__</span><span>(</span>self<span>)</span> <span>-</span><span>></span> <span>None</span><span>:</span>\n self<span>.</span>_grade <span>=</span> <span>0</span>\n\n <span>@property</span>\n <span>def</span> <span>grade</span><span>(</span>self<span>)</span><span>:</span>\n <span>print</span><span>(</span><span>'getter triggered'</span><span>)</span>\n <span>return</span> self<span>.</span>_grade\n \n <span>@grade<span>.</span>setter</span>\n <span>def</span> <span>grade</span><span>(</span>self<span>,</span> val<span>)</span><span>:</span>\n self<span>.</span>_grade <span>=</span> val\n <span>print</span><span>(</span><span>'setter triggered'</span><span>)</span>\n \n<span>if</span> __name__ <span>==</span> <span>'__main__'</span><span>:</span>\n a <span>=</span> A<span>(</span><span>)</span>\n a<span>.</span>grade <span>=</span> <span>5</span>\n <span>print</span><span>(</span>a<span>.</span>grade<span>)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br></div></div><h2 id=\"字典转对象\"> 字典转对象</h2>\n<p>python读取json等数据时,会返回字典。因为python的字典不能用点操作符来访问,只能用<code>[key]</code>来访问,如果key是字符串,又嵌套了很多层,取内层值的时候很麻烦。因此希望将字典转换为对象,使用点操作符来访问。</p>\n<div><pre><code><span>class</span> <span>DictObj</span><span>(</span><span>)</span><span>:</span>\n\n <span>def</span> <span>__init__</span><span>(</span>self<span>,</span> in_dict<span>)</span><span>:</span>\n <span>for</span> k<span>,</span> v <span>in</span> in_dict<span>.</span>items<span>(</span><span>)</span><span>:</span>\n <span>if</span> <span>isinstance</span><span>(</span>v<span>,</span> <span>(</span><span>list</span><span>,</span> <span>tuple</span><span>)</span><span>)</span><span>:</span>\n <span>setattr</span><span>(</span>self<span>,</span> k<span>,</span> <span>[</span>DictObj<span>(</span>x<span>)</span> <span>if</span> <span>isinstance</span><span>(</span>x<span>,</span> <span>dict</span><span>)</span> <span>else</span> x <span>for</span> x <span>in</span> v<span>]</span><span>)</span>\n <span>else</span><span>:</span>\n <span>setattr</span><span>(</span>self<span>,</span> k<span>,</span> DictObj<span>(</span>v<span>)</span> <span>if</span> <span>isinstance</span><span>(</span>v<span>,</span> <span>dict</span><span>)</span> <span>else</span> v<span>)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br></div></div>",
"date_published": "2021-12-16T00:00:00.000Z",
"date_modified": "2022-04-02T08:34:13.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": [
"Python"
]
},
{
"title": "构建图片数据集",
"url": "https://kigane.github.io/note/python/image_folder/",
"id": "https://kigane.github.io/note/python/image_folder/",
"content_html": "<h2 id=\"imagefolder\"> ImageFolder</h2>\n<p><code>torchvision.datasets.ImageFolder(root, transform = None, loader=<function default_loader>)</code></p>\n<ul>\n<li>root: 数据集的根目录</li>\n<li>transform: 图像的预处理操作</li>\n<li>loader: 图像加载函数,默认使用Pillow。给定路径,返回图片。</li>\n</ul>\n<p>一个通用的图片数据加载器,要求数据集以以下方式组织</p>\n<div><pre><code>directory/\n├── class_x\n│ ├── xxx.ext\n│ ├── xxy.ext\n│ └── ...\n│ └── xxz.ext\n└── class_y\n ├── 123.ext\n ├── nsdf3.ext\n └── ...\n └── asd932_.ext\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br></div></div><p>即,给定的根目录下,每一个文件夹中只保存一个类别的数据。文件夹的名称会作为类别的名称,保存在dataset.classes中,类别名到类别编码的映射保存在dataset.class_to_idx中(编码默认按字典序生成)。dataset.imgs即其内部保存的数据列表,其元素为<code>(img_path, class_num)</code>元组。访问<code>dataset[0]</code>,返回的是<code>(PIL.Image.Image, class_num)</code>元组(没有使用任何transform)。</p>\n<p>支持的图片格式有<code>('.jpg', '.jpeg', '.png', '.ppm', '.bmp', '.pgm', '.tif', '.tiff', '.webp')</code>。</p>\n",
"date_published": "2022-04-03T00:00:00.000Z",
"date_modified": "2022-04-20T00:32:32.310Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": [
"Python"
]
},
{
"title": "BOM&DOM&Event",
"url": "https://kigane.github.io/note/js/js-bom-dom/",
"id": "https://kigane.github.io/note/js/js-bom-dom/",
"content_html": "<h2 id=\"bom\"> BOM</h2>\n<ul>\n<li>window\n<ul>\n<li>window对象是JS的全局对象,所有全局作用域的变量和函数,都会变成window对象的属性和方法。</li>\n<li>全局变量和定义在window对象上的属性间的唯一差别是,全局变量不能delete,而window对象上的属性可以。</li>\n<li>访问未声明的全局变量会抛出错误,而访问未声明的window对象属性会返回undefined。</li>\n<li>页面中每个frame都有自己的window对象,每个window对象都有一个name属性,<code>window.frames[name]</code>可以访问指定frame</li>\n<li>window.top对象始终指向最外层的frame,即浏览器窗口。</li>\n<li>window.parent指向外层的frame</li>\n<li>window.self === window</li>\n<li>window.open(url, windowOrFrameName, features, replaceInHistory) 在windowOrFrameName上打开新的url,如果windowOrFrameName不存在,用指定的features创建新窗口。被open的窗口的window.opener指向原窗口,原窗口对被open的窗口一无所知。</li>\n<li>对话框:alert, confirm, prompt</li>\n</ul>\n</li>\n<li>location\n<ul>\n<li>window.location === document.location</li>\n<li>location.href 完整的url。</li>\n<li>location.host hostname:port</li>\n<li>location.hostname</li>\n<li>location.port</li>\n<li>location.pathname</li>\n<li>location.search 以?开头的查询字符串</li>\n<li>location.hash url中#+后面部分。除了该属性,修改其他属性都会导致浏览器重新加载。</li>\n<li>location.replace(url) 加载新url,原url不会添加到历史记录中。</li>\n<li>location.reload(bImpose) 重新加载(false: 可能从缓存中加载,true: 从服务器重新加载)</li>\n</ul>\n</li>\n<li>navigator\n<ul>\n<li>navigator.cookieEnabled</li>\n<li>navigator.userAgent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36 Edg/93.0.961.38"</li>\n</ul>\n</li>\n<li>history\n<ul>\n<li>history.back() 回退一页</li>\n<li>history.forward() 前进一页</li>\n<li>history.go(num) num>0时前进,反之后退。</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"间歇调用和超时调用\"> 间歇调用和超时调用</h3>\n<ul>\n<li>setTimeout(CallBackFn, time-ms) 经过time-ms后将当前任务添加到任务队列中。如果队列为空,则立即执行,否则,等待。</li>\n<li>setInterval(CallBackFn, time-ms)</li>\n<li>两个函数都会返回一个id标识符,可以调用相应的clearTimeout(id), clearInterval(id)取消调用。</li>\n<li>两个函数都是在全局作用域中执行的。无论在哪里调用。</li>\n<li>通常很少用setInterval,因为后一个间歇调用可能会在前一个间歇调用结束前启动。</li>\n</ul>\n<div><pre><code><span>var</span> num <span>=</span> <span>0</span>\n<span>var</span> max <span>=</span> <span>10</span>\n\n<span>function</span> <span>incrementNum</span><span>(</span><span>)</span> <span>{</span>\n num<span>++</span>\n <span>if</span> <span>(</span>num <span><</span> max<span>)</span> <span>{</span>\n <span>setTimeout</span><span>(</span>incrementNum<span>,</span> <span>500</span><span>)</span>\n <span>}</span> <span>else</span> <span>{</span>\n <span>alert</span><span>(</span><span>\"Done\"</span><span>)</span>\n <span>}</span>\n<span>}</span>\n\n<span>setTimeout</span><span>(</span>incrementNum<span>,</span> <span>500</span><span>)</span>\n\n<span>// 相当于setInterval(incrementNum, 500)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br></div></div><h2 id=\"event\"> Event</h2>\n<h3 id=\"事件流\"> 事件流</h3>\n<p>事件流描述的是从页面接受事件的顺序。</p>\n<ul>\n<li>事件冒泡:事件开始时由嵌套最深的元素接收,然后逐级向上传播。</li>\n<li>事件捕获:事件开始时由window对象接收,然后逐级向下传播到嵌套最深的元素。</li>\n<li>DOM2级事件:规定事件流包括三个阶段\n<ul>\n<li>事件捕获阶段 1</li>\n<li>处于目标阶段 2</li>\n<li>事件冒泡阶段 3</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"事件处理程序\"> 事件处理程序</h3>\n<p>即响应某个事件的函数。</p>\n<div><pre><code><span><span><span><</span>input</span> <span>type</span><span><span>=</span><span>\"</span>Button<span>\"</span></span> <span>value</span><span><span>=</span><span>\"</span>Click Me<span>\"</span></span> <span><span>onclick</span><span><span>=</span><span>\"</span><span><span>showMessage</span><span>(</span><span>)</span></span><span>\"</span></span></span><span>/></span></span>\n</code></pre>\n<div><span>1</span><br></div></div><ul>\n<li>这里showMessage为事件处理程序调用的函数。在该函数内部,this为事件的目标元素(本例中为input)。</li>\n<li>在这个函数作用域内,可以像访问局部变量一样访问document和元素本身的成员。如果当前元素是一个表单元素(在form内),则作用域中还会包含form元素。</li>\n<li>在html中指定事件处理程序可能会有时差问题,即html元素渲染好了,但showMessage函数还不可用,即JS还没执行到。</li>\n<li>DOM0级事件处理程序:用JS选中DOM对象中的指定元素,再将一个函数赋给事件处理程序(例如,el.onclick)。</li>\n<li>DOM2级事件处理程序:使用\n<ul>\n<li>addEventListener(eventName, EventCallBackFn, isCapture)</li>\n<li>removeEventListener(eventName, EventCallBackFn, isCapture)</li>\n<li>isCapture为true,表示在捕获阶段处理事件,为false表示在冒泡阶段处理事件。</li>\n<li>remove时的参数必须和add时的参数一模一样。如果add时使用的是匿名函数,则无法remove。</li>\n<li>这种方式可以为同一个事件设置多个处理函数。按照添加顺序触发。</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"事件对象\"> 事件对象</h3>\n<p>浏览器会将event对象传入事件处理程序中。</p>\n<table>\n<thead>\n<tr>\n<th>属性/方法</th>\n<th>类型</th>\n<th>只读</th>\n<th>说明</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>type</td>\n<td>String</td>\n<td>Y</td>\n<td>事件的类型</td>\n</tr>\n<tr>\n<td>target</td>\n<td>Element</td>\n<td>Y</td>\n<td>事件的目标。(最上层元素?)</td>\n</tr>\n<tr>\n<td>currentTarget</td>\n<td>Element</td>\n<td>Y</td>\n<td>当前正在处理事件的元素。事件处理程序中的this。</td>\n</tr>\n<tr>\n<td>eventPhase</td>\n<td>Integer</td>\n<td>Y</td>\n<td>表明事件处理程序的阶段</td>\n</tr>\n<tr>\n<td>bubbles</td>\n<td>Boolean</td>\n<td>Y</td>\n<td>表明事件是否冒泡</td>\n</tr>\n<tr>\n<td>detail</td>\n<td>Integer</td>\n<td>Y</td>\n<td>事件相关细节信息</td>\n</tr>\n<tr>\n<td>cancelable</td>\n<td>Boolean</td>\n<td>Y</td>\n<td>表明是否可以取消事件的默认行为</td>\n</tr>\n<tr>\n<td>preventDefault()</td>\n<td>Function</td>\n<td>Y</td>\n<td>如果cancelable为true,取消事件的默认行为。</td>\n</tr>\n<tr>\n<td>defaultPrevented</td>\n<td>Boolean</td>\n<td>Y</td>\n<td>为true,说明已经调用了preventDefault()</td>\n</tr>\n<tr>\n<td>stopImmediatePropagation()</td>\n<td>Function</td>\n<td>Y</td>\n<td>取消事件进一步捕获或冒泡,同时阻止任何事件被调用。</td>\n</tr>\n<tr>\n<td>stopPropagation()</td>\n<td>Function</td>\n<td>Y</td>\n<td>如果bubbles为true,取消事件进一步捕获或冒泡。</td>\n</tr>\n<tr>\n<td>trusted</td>\n<td>Boolean</td>\n<td>Y</td>\n<td>为true表明是浏览器生成的,为false表示由JS生成的</td>\n</tr>\n</tbody>\n</table>\n<h3 id=\"事件类型\"> 事件类型</h3>\n<h4 id=\"ui事件\"> UI事件</h4>\n<ul>\n<li>load\n<ul>\n<li>当页面完全加载后(包括所有图像、JavaScript文件、CSS文件等外部资源),就会触发window上面的load事件。</li>\n<li>EventUtil.addHandler(window, "load", function(e) {...})。建议使用。</li>\n<li>或在body元素中添加onload属性。</li>\n<li>img元素也有load事件,在图片加载完成时触发。</li>\n</ul>\n</li>\n<li>unload:在文档被完全卸载后触发。只要用户从一个页面切换到另一个页面,就会发生unload事件。而利用这个事件最多的情况是清除引用,以避免内存泄漏。</li>\n<li>select:当用户选择文本框(<code><input></code>或<code><texterea></code>)中的一或多个字符时触发。</li>\n<li>resize:当窗口或框架的大小变化时在window或框架上面触发。</li>\n<li>scroll:当用户滚动带滚动条的元素中的内容时,在该元素上面触发。</li>\n<li>resize和scroll触发频率很高。注意代码的简洁。</li>\n</ul>\n<h4 id=\"焦点事件\"> 焦点事件</h4>\n<ul>\n<li>blur:在元素失去焦点时触发。这个事件不会冒泡。</li>\n<li>focus:在元素获得焦点时触发。不冒泡</li>\n<li>focusin:冒泡版focus</li>\n<li>focusout:冒泡版blur</li>\n</ul>\n<h4 id=\"鼠标和滚轮事件\"> 鼠标和滚轮事件</h4>\n<ul>\n<li>click:在用户单击主鼠标按钮或者按下回车键时触发。</li>\n<li>dblclick:在用户双击主鼠标按钮时触发。</li>\n<li>mousedown:在用户按下了任意鼠标按钮时触发。不能通过键盘触发这个事件。</li>\n<li>mouseup:在用户释放鼠标按钮时触发。\n<ul>\n<li>event.button:0表示主鼠标按钮,1表示中间的鼠标滚轮按钮,2表示次鼠标按钮。</li>\n</ul>\n</li>\n<li>mousemove:当鼠标指针在元素内部移动时重复地触发。不能通过键盘触发这个事件。</li>\n<li>mouseout:在鼠标指针位于一个元素上方,然后用户将其移入另一个元素时触发。从A元素移动到B元素上,会触发A的mouseout,B的mouseover,A的relatedTarget是B,B的relatedTarget是A。</li>\n<li>mouseover:在鼠标指针位于一个元素外部,然后用户将其首次移入另一个元素边界之内时触发。</li>\n<li>事件触发顺序:mousedown->mouseup->click->mousedown->mouseup->click->dbclick</li>\n<li>mousewheel:滚动鼠标滚轮时触发。与mousewheel事件对应的event对象除包含鼠标事件的所有标准信息外,还包含一个特殊的wheelDelta属性。当用户向前滚动鼠标滚轮时,wheelDelta是120的倍数;当用户向后滚动鼠标滚轮时,wheelDelta是-120的倍数。</li>\n</ul>\n<hr>\n<ul>\n<li>视口坐标位置:事件对象的clientX和clientY属性,表示事件发生时鼠标指针在视口中的水平和垂直坐标。</li>\n<li>页面坐标位置:事件对象的pageX和pageY属性,能告诉你事件是在页面中的什么位置发生的。</li>\n<li>屏幕坐标位置:事件对象的screenX和screenY属性,表示事件发生时鼠标指针相对于整个屏幕的水平和垂直坐标。</li>\n<li>修饰键:事件对象的shiftKey、ctrlKey、altKey和metaKey属性,如果相应的键被按下了,则值为true。</li>\n</ul>\n<h4 id=\"键盘和文本事件\"> 键盘和文本事件</h4>\n<ul>\n<li>keydown:当用户按下键盘上的任意键时触发,而且如果按住不放的话,会重复触发此事件。</li>\n<li>keypress:当用户按下键盘上的字符键时触发,而且如果按住不放的话,会重复触发此事件。按下Esc键也会触发这个事件。</li>\n<li>keyup:当用户释放键盘上的键时触发。</li>\n<li>textInput:这个事件是对keypress的补充,用意是在将文本显示给用户之前更容易拦截文本。在文本插入文本框之前会触发textInput事件。\n<ul>\n<li>任何可以获得焦点的元素都可以触发keypress事件,但只有可编辑区域才能触发textInput事件。</li>\n<li>textInput事件只会在用户按下能够输入实际字符的键时才会被触发,而keypress事件则在按下那些能够影响文本显示的键时也会触发(例如退格键)。</li>\n<li>event.inputMethod\n<ul>\n<li>0,表示浏览器不确定是怎么输入的。</li>\n<li>1,表示是使用键盘输入的。</li>\n<li>2,表示文本是粘贴进来的。</li>\n<li>3,表示文本是拖放进来的。</li>\n<li>4,表示文本是使用IME输入的。</li>\n<li>5,表示文本是通过在表单中选择某一项输入的。</li>\n<li>6,表示文本是通过手写输入的(比如使用手写笔)。</li>\n<li>7,表示文本是通过语音输入的。</li>\n<li>8,表示文本是通过几种方法组合输入的。</li>\n<li>9,表示文本是通过脚本输入的。</li>\n</ul>\n</li>\n</ul>\n</li>\n<li>键盘事件与鼠标事件一样,都支持相同的修改键。而且,键盘事件的事件对象中也有shiftKey、ctrlKey、altKey和metaKey属性。</li>\n</ul>\n<p><img src=\"/assets/img/js-keycode.png\" alt=\"KeyCode\" />\nkeyCode: 即相应字符对应的ASCII码</p>\n<h4 id=\"变动事件\"> 变动事件</h4>\n<ul>\n<li>DOMSubtreeModified:在DOM结构中发生任何变化时触发。这个事件在其他任何事件触发后都会触发。</li>\n<li>DOMNodeInserted:在一个节点作为子节点被插入到另一个节点中时触发。</li>\n<li>DOMNodeRemoved:在节点从其父节点中被移除时触发。</li>\n<li>DOMNodeInsertedIntoDocument:在一个节点被直接插入文档或通过子树间接插入文档之后触发。这个事件在DOMNodeInserted之后触发。</li>\n<li>DOMNodeRemovedFromDocument:在一个节点被直接从文档中移除或通过子树间接从文档中移除之前触发。这个事件在DOMNodeRemoved之后触发。</li>\n<li>DOMAttrModified:在特性被修改之后触发。</li>\n<li>DOMCharacterDataModified:在文本节点的值发生变化时触发。</li>\n</ul>\n<h4 id=\"html5事件\"> HTML5事件</h4>\n<ul>\n<li>contextmenu事件:右键单击时触发,使用event.preventDefalut()取消默认菜单。因为contextmenu事件属于鼠标事件,所以其事件对象中包含与光标位置有关的所有属性。通常使用contextmenu事件来显示自定义的上下文菜单,而使用onclick事件处理程序来隐藏该菜单。</li>\n<li>DOMContentLoaded事件:在形成完整的DOM树之后就会触发,不理会图像、JavaScript文件、CSS文件或其他资源是否已经下载完毕。</li>\n<li>hashchange事件:HTML5新增了hashchange事件,以便在URL的参数列表(及URL中“#”号后面的所有字符串)发生变化时通知开发人员。之所以新增这个事件,是因为在Ajax应用中,开发人员经常要利用URL参数列表来保存状态或导航信息。</li>\n</ul>\n<h3 id=\"内存和性能\"> 内存和性能</h3>\n<p>在JavaScript中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能。导致这一问题的原因是多方面的。</p>\n<ol>\n<li>每个函数都是对象,都会占用内存;内存中的对象越多,性能就越差。</li>\n<li>必须事先指定所有事件处理程序而导致的DOM访问次数,会延迟整个页面的交互就绪时间。</li>\n</ol>\n<h4 id=\"事件委托\"> 事件委托</h4>\n<p>click、mousedown、mouseup、keydown、keyup和keypress事件适合采用事件委托技术。利用了事件冒泡,在事件冒泡到较高层次时统一处理,这样只指定一个事件处理程序,就可以管理某一类型的所有事件。</p>\n<div><pre><code><span>var</span> list <span>=</span> document<span>.</span><span>getElementById</span><span>(</span><span>\"#app\"</span><span>)</span>\n\nEventUtil<span>.</span><span>addHandler</span><span>(</span>list<span>,</span> <span>\"click\"</span><span>,</span> <span>function</span><span>(</span><span>e</span><span>)</span><span>{</span>\n event <span>=</span> EventUtil<span>.</span><span>getEvent</span><span>(</span>event<span>)</span><span>;</span>\n <span>var</span> target <span>=</span> EventUtil<span>.</span><span>getTarget</span><span>(</span>event<span>)</span>\n\n <span>switch</span> <span>(</span>target<span>.</span>id<span>)</span> <span>{</span>\n <span>case</span><span>:</span> <span>\"aBtn\"</span><span>:</span>\n document<span>.</span>title <span>=</span> <span>\"I changed the document's title\"</span>\n <span>break</span>\n <span>case</span> <span>\"bBtn\"</span><span>:</span>\n location<span>.</span>href <span>=</span> <span>\"http://www.baidu.com\"</span>\n <span>break</span>\n <span>case</span><span>:</span> <span>\"cBtn\"</span><span>:</span>\n <span>alert</span><span>(</span><span>\"Hi\"</span><span>)</span>\n <span>break</span><span>;</span>\n <span>}</span>\n<span>}</span><span>)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br></div></div><h4 id=\"移除事件处理程序\"> 移除事件处理程序</h4>\n<p>内存中留有那些过时不用的“空事件处理程序”(dangling event handler),也是造成Web应用程序内存与性能问题的主要原因。</p>\n<p>在两种情况下,可能会造成上述问题。</p>\n<ol>\n<li>从文档中移除带有事件处理程序的元素。这可能是通过纯粹的DOM操作,例如使用removeChild()和replaceChild()方法,但更多地是发生在使用innerHTML替换页面中某一部分的时候。如果带有事件处理程序的元素被innerHTML删除了,那么原来添加到元素中的事件处理程序极有可能无法被当作垃圾回收。(元素移除了,但事件处理程序还和元素保持着引用关系,浏览器很可能无法处理,而将元素和对事件处理程序的引用都保存在内存中,因而无法通过GC回收)</li>\n<li>卸载页面的时候。如果在页面被卸载之前没有清理干净事件处理程序,那它们就会滞留在内存中。每次加载完页面再卸载页面时(可能是在两个页面间来回切换,也可以是单击了“刷新”按钮),内存中滞留的对象数目就会增加,因为事件处理程序占用的内存并没有被释放。一般来说,最好的做法是在页面卸载之前,先通过onunload事件处理程序移除所有事件处理程序。在此,事件委托技术再次表现出它的优势——需要跟踪的事件处理程序越少,移除它们就越容易。</li>\n</ol>\n",
"image": "https://kigane.github.io/assets/img/js-keycode.png",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "Python文件操作",
"url": "https://kigane.github.io/note/python/file_operations/",
"id": "https://kigane.github.io/note/python/file_operations/",
"content_html": "<h2 id=\"os-walk\"> os.walk()</h2>\n<p><code>os.walk(top[, topdown=True])</code>: 从top目录开始,先序遍历所有子目录</p>\n<div><pre><code><span>for</span> root<span>,</span> dirs<span>,</span> files <span>in</span> os<span>.</span>walk<span>(</span>path<span>)</span><span>:</span>\n <span>print</span><span>(</span>root<span>)</span> <span># str</span>\n <span>print</span><span>(</span>dirs<span>)</span> <span># list</span>\n <span>print</span><span>(</span>files<span>)</span> <span># list</span>\n <span>print</span><span>(</span><span>'\\n'</span><span>)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br></div></div><h2 id=\"pathlib-os-os-path\"> pathlib & os & os.path</h2>\n<p>path.xxx表示实例方法,Path.xxx表示类方法(@classmethod)。</p>\n<h3 id=\"文件操作\"> 文件操作</h3>\n<table>\n<thead>\n<tr>\n<th>os & os.path</th>\n<th>pathlib</th>\n<th>作用</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>os.chmod(mode)</td>\n<td>path.chmod(mode)</td>\n<td>改变文件模式和权限</td>\n</tr>\n<tr>\n<td>os.mkdir(path)</td>\n<td>path.mkdir(path, parents=False)</td>\n<td>创建一个名为 path 的目录,如果中间目录不存在会报错</td>\n</tr>\n<tr>\n<td>os.makedirs(path, exist_ok=False)</td>\n<td>path.mkdir(path, parents=True, exist_ok=False)</td>\n<td>如果 exist_ok 为 false(默认),则在目标已存在的情况下抛出 FileExistsError。为 True 则忽略该异常。</td>\n</tr>\n<tr>\n<td>os.rename(src, dst)</td>\n<td>path.rename(target)</td>\n<td>重命名。如果dst已存在,则失败。</td>\n</tr>\n<tr>\n<td>os.replace(src, dst)</td>\n<td>path.replace(target)</td>\n<td>重命名。但dst是文件,且存在时,会直接替换。target无论是文件或目录都会直接替换。</td>\n</tr>\n<tr>\n<td>os.rmdir(path)</td>\n<td>path.rmdir()</td>\n<td>移除此目录。此目录必须存在且为空。</td>\n</tr>\n<tr>\n<td>os.remove(path)</td>\n<td>无对应方法</td>\n<td>移除此文件。不能是目录。</td>\n</tr>\n</tbody>\n</table>\n<h3 id=\"路径操作\"> 路径操作</h3>\n<table>\n<thead>\n<tr>\n<th>os & os.path</th>\n<th>pathlib</th>\n<th>作用</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>os.stat(path)</td>\n<td>path.stat()</td>\n<td>检查文件属性</td>\n</tr>\n<tr>\n<td>os.path.abspath(path)</td>\n<td>path.resolve()</td>\n<td>返回path的标准化的绝对路径</td>\n</tr>\n<tr>\n<td>os.getcwd()</td>\n<td>Path.cwd()</td>\n<td>返回当前工作目录路径</td>\n</tr>\n<tr>\n<td>os.path.exists(path)</td>\n<td>path.exists()</td>\n<td>检测文件或目录是否存在</td>\n</tr>\n<tr>\n<td>os.listdir(path='.')</td>\n<td>path.iterdir()</td>\n<td>列出path的所有子文件和子目录</td>\n</tr>\n<tr>\n<td>os.path.isdir(path)</td>\n<td>path.is_dir()</td>\n<td>检测path是否为目录</td>\n</tr>\n<tr>\n<td>os.path.isfile(path)</td>\n<td>path.is_file()</td>\n<td>检测path是否为文件</td>\n</tr>\n<tr>\n<td>os.path.join(path, *paths)</td>\n<td>path.joinpath(*other)</td>\n<td>拼接路径</td>\n</tr>\n<tr>\n<td>os.path.basename(path)</td>\n<td>path.name</td>\n<td>返回path的基本名称。这是将path传入函数split()之后,返回的一对值中的第二个元素</td>\n</tr>\n<tr>\n<td>os.path.dirname(path)</td>\n<td>path.parent</td>\n<td>返回path的目录名称。这是将path传入函数split()之后,返回的一对值中的第一个元素</td>\n</tr>\n<tr>\n<td>os.path.splitext(path)</td>\n<td>path.suffix</td>\n<td>将path拆分为(root, ext)对使得root + ext == path,并且ext为空或以句点打头并最多只包含一个句点。path.suffix == ext</td>\n</tr>\n</tbody>\n</table>\n<div><p>os.path.split(path)</p>\n<p>将path拆分为一对,即(head,tail),其中,tail是路径的最后一部分,而head里是除最后部分外的所有内容。tail部分不会包含斜杠,如果path以斜杠结尾,则tail将为空。如果path中没有斜杠,head将为空。如果path为空,则head和tail均为空。head末尾的斜杠会被去掉,除非它是根目录(即它仅包含一个或多个斜杠)。在所有情况下,join(head,tail)指向的位置都与path相同(但字符串可能不同)。另head==dirname()和tail==basename()。</p>\n</div>\n<h2 id=\"shutil\"> shutil</h2>\n<h3 id=\"高阶文件操作\"> 高阶文件操作</h3>\n<ul>\n<li>shutil.copyfile(src, dst): 复制文件src到文件dst。</li>\n<li>shutil.copy(src, dst) & shutil.copy2(src, dst: 文件src拷贝到文件或目录dst。</li>\n<li>shutil.copytree(src, dst, ignore=None, copy_function=copy2, dirs_exist_ok=False): 将以src为根起点的整个目录树拷贝到名为dst的目录并返回目标目录。dirs_exist_ok指明是否要在dst或任何丢失的父目录已存在的情况下引发异常。</li>\n<li>shutil.rmtree(path): 删除一个完整的目录树;path 必须指向一个目录。</li>\n<li>shutil.move(src, dst, copy_function=copy2): 递归地将一个文件或目录 (src) 移至另一位置 (dst) 并返回目标位置。</li>\n</ul>\n<h3 id=\"压缩操作\"> 压缩操作</h3>\n<ul>\n<li>shutil.make_archive(base_name, format[, root_dir[, base_dir]])\n<ul>\n<li>base_name: 要创建的文件名称,包括路径,去除任何特定格式的扩展名。</li>\n<li>format: 归档格式。\n<ul>\n<li>"zip" (如果 zlib 模块可用)</li>\n<li>"tar", "gztar" (如果 zlib 模块可用)</li>\n<li>"bztar" (如果 bz2 模块可用)</li>\n<li>"xztar" (如果 lzma 模块可用)</li>\n</ul>\n</li>\n<li>root_dir: 归档文件的根目录,归档中的所有路径都将是它的相对路径。相当于先os.chdir(root_dir)。</li>\n<li>base_dir: 要执行归档的起始目录。也就是说base_dir将成为归档中所有文件和目录共有的路径前缀。base_dir必须相对于root_dir给出。</li>\n<li>root_dir 和 base_dir 默认均为当前目录。</li>\n<li>这个函数不是线程安全的</li>\n</ul>\n</li>\n<li>shutil.unpack_archive(filename[, extract_dir[, format]])\n<ul>\n<li>解包一个归档文件。filename是归档文件的完整路径。</li>\n<li>extract_dir是归档文件解包的目标目录名称。 如果未提供,则将使用当前工作目录。</li>\n</ul>\n</li>\n<li>shutil.get_archive_formats(): 返回支持的归档格式列表。</li>\n</ul>\n",
"date_published": "2022-03-30T00:00:00.000Z",
"date_modified": "2022-04-02T08:34:13.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": [
"Python"
]
},
{
"title": "PyQt5 Image Viewer示例",
"url": "https://kigane.github.io/note/python/image_viewer/",
"id": "https://kigane.github.io/note/python/image_viewer/",
"content_html": "<h2 id=\"用label显示图片\"> 用Label显示图片</h2>\n<div><pre><code>self<span>.</span>imageLabel <span>=</span> QLabel<span>(</span><span>)</span>\n<span># 定义组件调色板中的刷子用于呈现背景</span>\nself<span>.</span>imageLabel<span>.</span>setBackgroundRole<span>(</span>QPalette<span>.</span>Base<span>)</span>\nself<span>.</span>imageLabel<span>.</span>setSizePolicy<span>(</span>QSizePolicy<span>.</span>Ignored<span>,</span> QSizePolicy<span>.</span>Ignored<span>)</span>\n<span># 将缩放PIXMAP以填充可用空间。</span>\nself<span>.</span>imageLabel<span>.</span>setScaledContents<span>(</span><span>True</span><span>)</span>\n\n<span># QLabel的pixmap属性用于设置图像</span>\nimage <span>=</span> QImage<span>(</span>fileName<span>)</span>\nself<span>.</span>imageLabel<span>.</span>setPixmap<span>(</span>QPixmap<span>.</span>fromImage<span>(</span>image<span>)</span><span>)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br></div></div><ul>\n<li>QSizePolicy::Fixed:固定尺寸不能变</li>\n<li>QSizePolicy::Minimum:可以放大</li>\n<li>QSizePolicy::Maximum:可以缩小</li>\n<li>QSizePolicy::Preferred:可以放大也可以缩小</li>\n<li>QSizePolicy::Expanding:自动放大和缩小</li>\n<li>QSizePolicy::MinimumExpanding:至少为给定尺寸,可以自动放大</li>\n<li>QSizePolicy::Ignored:忽略给定尺寸。组件的大小不受限制。</li>\n</ul>\n<h2 id=\"打开文件-qfiledialog\"> 打开文件--QFileDialog</h2>\n<h3 id=\"static-成员\"> static 成员</h3>\n<ul>\n<li>getExistingDirectory() -> dirpath</li>\n<li>getExistingDirectoryUrl() -> QUrl('file:///F:/xxx/xx')</li>\n<li>getOpenFileName() -> filepath, filter_str</li>\n<li>getOpenFileNames()-> filepath_list, filter_str</li>\n<li>getOpenFileUrl()</li>\n<li>getOpenFileUrls()</li>\n<li>getSaveFileName() -> filepath, filter_str 只返回一个用户输入的文件名,并没有真的创建文件</li>\n<li>getSaveFileUrl()</li>\n<li>参数说明\n<ul>\n<li>caption='Dialog Title'</li>\n<li>directory='F:/' 初始路径</li>\n<li><code>filter='Images (*.png *.xpm *.jpg);;Text files (*.txt);;All Files (*)'</code> 过滤器。多个过滤器用两个分号隔开。</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"signals\"> signals</h3>\n<ul>\n<li>currentChanged(QString): 当前文件改变了。参数为新文件名。</li>\n<li>currentUrlChanged(QUrl):当前文件改变了。参数为新文件URL。</li>\n<li>directoryEntered(QString):用户进入某个目录</li>\n<li>directoryUrlEntered(QUrl)</li>\n<li>fileSelected(QString):选择文件变化时。参数为选中的文件。</li>\n<li>filterSelected(QString)</li>\n<li>urlSelected(QUrl)</li>\n<li><code>urlsSelected(QList<QUrl>)</code></li>\n<li>filesSelected(QStringList):用户选择过滤器时。</li>\n</ul>\n<h2 id=\"scrollarea\"> ScrollArea</h2>\n<div><pre><code><span># 检查Action是否被勾选</span>\nfitToWindow <span>=</span> self<span>.</span>fitToWindowAct<span>.</span>isChecked<span>(</span><span>)</span> \n<span># WidgetResizable为false,scrollArea拥有Widget的大小</span>\n<span># 为True,则scrollArea会调整Widget大小以尽量避免滚动条,或更充分利用空间。</span>\nself<span>.</span>scrollArea<span>.</span>setWidgetResizable<span>(</span>fitToWindow<span>)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br></div></div><ul>\n<li>self.scrollArea.horizontalScrollBar() 获取水平滚动条</li>\n<li>self.scrollArea.verticalScrollBar()</li>\n<li>scrollBar.value()</li>\n<li>scrollBar.setValue() 设置滚动条的位置</li>\n</ul>\n<h2 id=\"qpainter\"> QPainter</h2>\n<div><pre><code>self<span>.</span>printer <span>=</span> QPrinter<span>(</span><span>)</span>\n\ndialog <span>=</span> QPrintDialog<span>(</span>self<span>.</span>printer<span>,</span> self<span>)</span>\n<span>if</span> dialog<span>.</span>exec_<span>(</span><span>)</span><span>:</span>\n painter <span>=</span> QPainter<span>(</span>self<span>.</span>printer<span>)</span>\n rect <span>=</span> painter<span>.</span>viewport<span>(</span><span>)</span>\n size <span>=</span> self<span>.</span>imageLabel<span>.</span>pixmap<span>(</span><span>)</span><span>.</span>size<span>(</span><span>)</span>\n size<span>.</span>scale<span>(</span>rect<span>.</span>size<span>(</span><span>)</span><span>,</span> Qt<span>.</span>KeepAspectRatio<span>)</span>\n painter<span>.</span>setViewport<span>(</span>rect<span>.</span>x<span>(</span><span>)</span><span>,</span> rect<span>.</span>y<span>(</span><span>)</span><span>,</span>\n size<span>.</span>width<span>(</span><span>)</span><span>,</span> size<span>.</span>height<span>(</span><span>)</span><span>)</span>\n painter<span>.</span>setWindow<span>(</span>self<span>.</span>imageLabel<span>.</span>pixmap<span>(</span><span>)</span><span>.</span>rect<span>(</span><span>)</span><span>)</span>\n painter<span>.</span>drawPixmap<span>(</span><span>0</span><span>,</span> <span>0</span><span>,</span> self<span>.</span>imageLabel<span>.</span>pixmap<span>(</span><span>)</span><span>)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br></div></div><h2 id=\"代码\"> 代码</h2>\n<p><a href=\"https://gist.github.com/acbetter/32c575803ec361c3e82064e60db4e3e0\" target=\"_blank\" rel=\"noopener noreferrer\">查看完整代码</a></p>\n",
"date_published": "2021-12-15T00:00:00.000Z",
"date_modified": "2022-04-02T08:34:13.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": [
"Python"
]
},
{
"title": "用Python制作gif图",
"url": "https://kigane.github.io/note/python/make_gif/",
"id": "https://kigane.github.io/note/python/make_gif/",
"content_html": "<h2 id=\"准备\"> 准备</h2>\n<p>安装 imageio 和 <a href=\"https://github.com/LucaCappelletti94/pygifsicle\" target=\"_blank\" rel=\"noopener noreferrer\">pygifsicle</a> 库。</p>\n<div><pre><code>pip install imageio\npip install pygifsicle\n</code></pre>\n<div><span>1</span><br><span>2</span><br></div></div><p>pygifsicle 只是 gifsicle 的Python接口,因此还需要安装 gifsicle 本体。</p>\n<ul>\n<li>Windows系统需要下载二进制程序,然后将 gifsicle.exe 所在文件夹设为 path 环境变量。若执行<code>gifsicle --version</code>成功,则说明安装好了。</li>\n<li>Linux系统执行<code>sudo apt-get install gifsicle</code>即可。</li>\n</ul>\n<h2 id=\"代码\"> 代码</h2>\n<div><pre><code><span>import</span> imageio <span>as</span> iio\n<span>from</span> pygifsicle <span>import</span> optimize\n<span>from</span> pathlib <span>import</span> Path\n\n\n<span>if</span> __name__ <span>==</span> <span>'__main__'</span><span>:</span>\n img_dir <span>=</span> <span>'path/to/your/img_folder'</span>\n path <span>=</span> Path<span>(</span>img_dir<span>)</span>\n gif_imgs <span>=</span> <span>[</span>iio<span>.</span>imread<span>(</span>img<span>)</span> <span>for</span> img <span>in</span> path<span>.</span>iterdir<span>(</span><span>)</span><span>]</span> <span># 读取文件夹中的所有图片</span>\n iio<span>.</span>mimsave<span>(</span><span>'hello.gif'</span><span>,</span> gif_imgs<span>,</span> <span>format</span><span>=</span><span>'GIF'</span><span>,</span> duration<span>=</span><span>0.5</span><span>)</span> <span># 将图片制作为 gif</span>\n optimize<span>(</span><span>'hello.gif'</span><span>)</span> <span># 优化 gif 文件体积。覆盖原文件</span>\n optimize<span>(</span><span>'hello.gif'</span><span>,</span> <span>'new_hello.git'</span><span>)</span> <span># 创建一个新文件</span>\n <span>print</span><span>(</span><span>\"finished\"</span><span>)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br></div></div>",
"date_published": "2022-03-30T00:00:00.000Z",
"date_modified": "2022-04-02T08:34:13.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": [
"Python"
]
},
{
"title": "PyQt5记录",
"url": "https://kigane.github.io/note/python/pyqt5/",
"id": "https://kigane.github.io/note/python/pyqt5/",
"content_html": "<h2 id=\"qtdesigner\"> QtDesigner</h2>\n<ul>\n<li>设计界面,产生一个xxx.ui文件</li>\n<li><code>pyuic5 -x -o yyy.py xxx.ui</code>\n<ul>\n<li>-o后面为输出文件名</li>\n<li>-x表示在py文件中生成测试代码</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"正确使用ui文件\"> 正确使用ui文件</h2>\n<ul>\n<li>使用类继承,而非直接在生成的py文件上修改。</li>\n<li>或直接用uic模块得到需要的类</li>\n</ul>\n<CodeGroup>\n<CodeGroupItem title=\"编译后导入\">\n<div><pre><code><span># 引入生成的类</span>\n<span>from</span> ui<span>.</span>login <span>import</span> Ui_Form\n<span>from</span> PyQt5 <span>import</span> QtWidgets <span>as</span> qtw\n\n<span># 通过继承取得类属性</span>\n<span>class</span> <span>LoginWindow</span><span>(</span>qtw<span>.</span>QWidget<span>,</span> Ui_Form<span>)</span><span>:</span>\n <span>def</span> <span>__init__</span><span>(</span>self<span>)</span><span>:</span>\n <span>super</span><span>(</span>LoginWindow<span>,</span> self<span>)</span><span>.</span>__init__<span>(</span><span>)</span>\n <span># 调用类中定义的方法,准备好 ui</span>\n self<span>.</span>setupUi<span>(</span>self<span>)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br></div></div></CodeGroupItem>\n<CodeGroupItem title=\"使用uic模块\">\n<div><pre><code><span>from</span> PyQt5 <span>import</span> uic\nUi_Form<span>,</span> BaseClass <span>=</span> uic<span>.</span>loadUiType<span>(</span><span>'login.ui'</span><span>)</span>\n\n<span># 通过继承取得类属性</span>\n<span>class</span> <span>LoginWindow</span><span>(</span>BaseClass<span>,</span> Ui_Form<span>)</span><span>:</span>\n <span>def</span> <span>__init__</span><span>(</span>self<span>)</span><span>:</span>\n <span>super</span><span>(</span>LoginWindow<span>,</span> self<span>)</span><span>.</span>__init__<span>(</span><span>)</span>\n <span># 调用类中定义的方法,准备好 ui</span>\n self<span>.</span>setupUi<span>(</span>self<span>)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br></div></div></CodeGroupItem>\n</CodeGroup>\n<p>注意到,生成的py文件中,UI类是object的子类,而非QWiget的子类。这是出于在大型项目中,经常会修改界面的考虑,通过提供setupUi方法实现界面外观,易于修改(Mixin)。但缺点是继承后,不能直观地看到有哪些属性,属性的类型也被隐藏了,编程会变得不方便。</p>\n<h2 id=\"基本的qt程序\"> 基本的Qt程序</h2>\n<div><pre><code><span>import</span> sys\n<span>from</span> PyQt5 <span>import</span> QtWidgets <span>as</span> qtw\n<span>from</span> PyQt5 <span>import</span> QtCore <span>as</span> qtc\n<span>from</span> PyQt5 <span>import</span> QtGui <span>as</span> qtg\n\n<span>class</span> <span>MainWindow</span><span>(</span>qtw<span>.</span>QWidget<span>)</span><span>:</span>\n\n <span>def</span> <span>__init__</span><span>(</span>self<span>,</span> <span>*</span>args<span>,</span> <span>**</span>kwargs<span>)</span><span>:</span>\n <span>super</span><span>(</span><span>)</span><span>.</span>__init__<span>(</span><span>*</span>args<span>,</span> <span>**</span>kwargs<span>)</span>\n <span># Your code will go here</span>\n\n <span># Your code ends here</span>\n self<span>.</span>show<span>(</span><span>)</span>\n\n\n<span>if</span> __name__ <span>==</span> <span>'__main__'</span><span>:</span>\n <span># 接受命令行参数</span>\n app <span>=</span> qtw<span>.</span>QApplication<span>(</span>sys<span>.</span>argv<span>)</span> \n w <span>=</span> MainWindow<span>(</span><span>)</span>\n <span># 给程序一个退出状态</span>\n sys<span>.</span>exit<span>(</span>app<span>.</span>exec_<span>(</span><span>)</span><span>)</span> \n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br></div></div><h2 id=\"基本布局\"> 基本布局</h2>\n<ul>\n<li>qtw.QHBoxLayout()\n<ul>\n<li>layout.addWidget(widget)</li>\n<li>所有组件横着放</li>\n</ul>\n</li>\n<li>qtw.VHBoxLayout()\n<ul>\n<li>layout.addWidget(widget)</li>\n<li>所有组件竖着放</li>\n</ul>\n</li>\n<li>qtw.GridLayout()\n<ul>\n<li>layout.addWidget(widget, row, col)</li>\n<li>把组件放入相应网格中</li>\n</ul>\n</li>\n<li>qtw.FormLayout()\n<ul>\n<li>layout.addRow('widget_label', widget)</li>\n<li>会产生适合不同平台的表格</li>\n<li>要构造表格组件时,推荐用该布局</li>\n</ul>\n</li>\n<li>布局可以嵌套,即上面的widget也可以是layout。</li>\n<li>也可以用一个QWidget作为布局组件\n<ul>\n<li>w.setLayout(qtw.QHBoxLayout())</li>\n<li>w.layout().addWidget(widget)</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"signal-slot\"> Signal & Slot</h2>\n<h3 id=\"基本用法\"> 基本用法</h3>\n<p>signal是Qt组件的属性,可以和slot连接,当signal触发时,执行slot。slot可以是任何python callable。</p>\n<p>Qt组件有一些定义好的signal和slot。具体看文档。定义好的信号,可以在组件的构造函数中用关键词参数指定,效果和手动连接相同,写法更简单。</p>\n<h3 id=\"signal可以携带数据\"> signal可以携带数据</h3>\n<ul>\n<li>例如textChanged signal连接的slot的第一个参数是当前文本。</li>\n<li>如果信号携带了数据,可用装饰器<code>@qtc.pyqtSlot(str)</code>声明,这会提升一点速度,用于多线程时更安全,此外可以让你确定参数类型,如果在装饰器中声明的参数类型和信号实际携带的类型不符,则会报错。</li>\n</ul>\n<h3 id=\"自定义signal\"> 自定义signal</h3>\n<div><pre><code>my_signal <span>=</span> qtc<span>.</span>pyqtSignal<span>(</span><span>str</span><span>)</span> <span># 携带一个字符串参数的信号</span>\nmy_signal<span>.</span>emit<span>(</span><span>'some content'</span><span>)</span> <span># 在某个地方触发信号,并传入数据</span>\nmy_signal<span>.</span>connect<span>(</span>some_callable<span>)</span> <span># 连接信号与槽</span>\n\n<span>def</span> <span>some_callable</span><span>(</span><span>str</span><span>)</span><span>:</span>\n <span>pass</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br></div></div><p>自定义信号+携带数据=不同窗口之间传递数据</p>\n<h2 id=\"qwidget\"> QWidget</h2>\n<h3 id=\"signals\"> signals</h3>\n<ul>\n<li>customContextMenuRequested(QPoint)</li>\n<li>windowIconChanged(QIcon)</li>\n<li>windowTitleChanged(QString)</li>\n</ul>\n<h3 id=\"slots\"> slots</h3>\n<ul>\n<li>close()</li>\n<li>hide() == setVisible(false)</li>\n<li>lower() 将Widget放到父Widget堆栈的底部</li>\n<li>raise() 将Widget放到父Widget堆栈的顶部</li>\n<li>repaint() 可能会导致无限递归。除了做动画,其他情况update()更好。</li>\n<li>setDisabled(bool) 禁止输入</li>\n<li>setEnabled(bool)</li>\n<li>setFocus() 获得焦点</li>\n<li>setHidden(bool) = setVisible(!bool).</li>\n<li>setStyleSheet(QString)</li>\n<li>setVisible(bool)</li>\n<li>setWindowModified(bool) 设置窗口有没有未保存的变化</li>\n<li>setWindowTitle(QString)</li>\n<li>show()</li>\n<li>showFullScreen()</li>\n<li>showMaximized()</li>\n<li>showMinimized()</li>\n<li>showNormal()</li>\n<li>update()</li>\n<li>adjustSize() 调整到初始设定的大小</li>\n</ul>\n<h2 id=\"qmainwindow\"> QMainWindow</h2>\n<ul>\n<li>Central Widget\n<ul>\n<li>选择一个组件</li>\n</ul>\n</li>\n<li>MenuBar\n<ul>\n<li>QMenuBar(): menu = self.menuBar()</li>\n<li>QMenu('name'): file_menu = menu.addMenu('File')</li>\n<li>QAction('name', callable, shortcut): save_act = file_menu.addAction('Save', self.save)\n<ul>\n<li>第二个参数triggered=xx,设置和triggered信号绑定的槽</li>\n<li>第三个参数shortcut=xx,设置快捷键,预定义在qtg.QKeySequence中,或直接用字符串'Ctrl+t'</li>\n<li>enabled=bool 初始是否可用</li>\n<li>checkable=bool 是否可打√</li>\n<li>act.setEnabled()</li>\n<li>act.isChecked()</li>\n</ul>\n</li>\n</ul>\n</li>\n<li>StatusBar\n<ul>\n<li>self.statusBar().showMessage(msg, last_time)</li>\n<li>last_time单位ms</li>\n</ul>\n</li>\n<li>ToolBar\n<ul>\n<li>edit_toolbar = self.addToolBar('Edit') 增加一个工具栏</li>\n<li>edit_toolbar.addAction(qtg.QIcon(qtg.QPixmap('action.svg')), 'action_name', callable)</li>\n<li>edit_toolbar.addAction('copy', self.textedit.copy)</li>\n<li>有图标的时候不显示文字,只有未设置图标或图标缺失时显示文字</li>\n</ul>\n</li>\n<li>Dockable widget\n<ul>\n<li>search_dock = qtw.QDockWidget('Search')</li>\n<li>self.addDockWidget(qtc.Qt.RightDockWidgetArea,search_dock) 添加到主窗口并设置停靠位置</li>\n<li>search_dock.setFeatures(\nqtw.QDockWidget.DockWidgetClosable |\nqtw.QDockWidget.DockWidgetMovable |\nqtw.QDockWidget.DockWidgetFloatable\n) 设置特性</li>\n<li>search_dock.setWidget(widget) 在停靠窗口中添加自定义组件</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"图片和图标\"> 图片和图标</h2>\n<h3 id=\"qpixmap\"> QPixmap</h3>\n<p>用于在屏幕上展示图像</p>\n<ul>\n<li>size()-> QSize, width(), height(), rect()->QRect(x,y,w,h):左上+宽高</li>\n<li>hasAlphaChannel()</li>\n<li>depth()->bits per pixel,bpp</li>\n<li>toImage() -> QImage</li>\n<li>fromImage()</li>\n<li>copy(), scaled(size, aspect_ratio_mode), scaledToWidth(), scaledToHeight()</li>\n<li>transformed(QTransform, mode)\n<ul>\n<li>Qtransfrom(m11, m12, m21, m22, dx=0, dy=0)</li>\n<li>mode有两个,默认的0是快速变换,1是使用了双线性插值的变换</li>\n</ul>\n</li>\n<li>scroll(dx,dy,(x,y,w,h)) 复制图像的部分并移动dx,dy。原图仍在,并会被遮挡。</li>\n</ul>\n<h3 id=\"qbitmap\"> QBitmap</h3>\n<p>单bit特化的QPixmap</p>\n<h3 id=\"qimage\"> QImage</h3>\n<p>对I/O有和像素级处理有优化。</p>\n<ul>\n<li>读写\n<ul>\n<li>构造函数</li>\n<li>load()</li>\n<li>loadFromData()</li>\n<li>static fromData()</li>\n<li>save()</li>\n</ul>\n</li>\n<li>几何信息\n<ul>\n<li>size(), width(), height(), dotsPerMeterX(), dotsPerMeterY(), rect()</li>\n<li>valid() 判断给定坐标是否在图像rect范围内</li>\n<li>offset(), setOffset() 通过相对于其他图像定位时,设置图像相对于其他图像偏移的像素数。</li>\n</ul>\n</li>\n<li>颜色\n<ul>\n<li>pixel(pos) 返回某像素点的QRgb值</li>\n<li>pixelIndex(pos) + color() = pixel(pos)</li>\n<li>colorCount(), colorTable() 图像用到的颜色列表</li>\n<li>isGrayscale()</li>\n</ul>\n</li>\n<li>文本\n<ul>\n<li>text(key)</li>\n<li>setText(key, text)</li>\n<li>textKeys() 获取图像的text key</li>\n</ul>\n</li>\n<li>低级信息\n<ul>\n<li>depth() 每像素占用比特数</li>\n<li>bitPlaneCount() 每像素实际使用的比特数</li>\n<li>format()</li>\n<li>bytesPerLine()</li>\n<li>sizeInBytes()</li>\n</ul>\n</li>\n</ul>\n<p>用numpy的2d数组构造QImage</p>\n<ul>\n<li>numpy数组必须是np.uint8类型</li>\n<li>QImage构造时需要指定为相应类型如QImage.Format_RGB888</li>\n</ul>\n<div><pre><code>img <span>=</span> np<span>.</span>zeros<span>(</span><span>(</span><span>500</span><span>,</span> <span>500</span><span>,</span> <span>3</span><span>)</span><span>,</span> dtype<span>=</span>np<span>.</span>uint8<span>)</span>\nimg<span>[</span><span>.</span><span>.</span><span>.</span><span>,</span> <span>0</span><span>]</span> <span>=</span> <span>233</span>\nw<span>,</span> h <span>=</span> img<span>.</span>shape<span>[</span><span>:</span><span>2</span><span>]</span>\nimage <span>=</span> QImage<span>(</span>img<span>.</span>data<span>,</span> w<span>,</span> h<span>,</span> QImage<span>.</span>Format_RGB888<span>)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br></div></div><h3 id=\"qpicture\"> QPicture</h3>\n<p>一种绘制设备,可记录和重放QPainter命令</p>\n<h3 id=\"qicon-qpixmap\"> QIcon(QPixmap)</h3>\n<p>图标</p>\n<h2 id=\"文件位置处理\"> 文件位置处理</h2>\n<div><pre><code><span>import</span> os\nBASE_DIR <span>=</span> os<span>.</span>path<span>.</span>dirname<span>(</span>__file__<span>)</span>\nimg_path <span>=</span> os<span>.</span>path<span>.</span>join<span>(</span>BASE_DIR<span>,</span> path<span>)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br></div></div><h2 id=\"qss\"> QSS</h2>\n",
"date_published": "2021-12-14T00:00:00.000Z",
"date_modified": "2022-04-02T08:34:13.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": [
"Python"
]
},
{
"title": "JS 对象",
"url": "https://kigane.github.io/note/js/js-object/",
"id": "https://kigane.github.io/note/js/js-object/",
"content_html": "<p>ECMA-262把对象定义为:“无序属性的集合,其属性可以包含基本值,对象或函数。”<br>\n简单地说,对象就是一个散列表,包含一系列 key-value 对,其中值可以是数据或函数。</p>\n<h2 id=\"理解对象\"> 理解对象</h2>\n<p>ECMAScript 中有两类属性(property):数据属性和访问器属性。<br>\n属性的特征由 ECMA 定义的内部特性(attribute)描述。</p>\n<h3 id=\"数据属性的特性\"> 数据属性的特性</h3>\n<ol>\n<li><code>[[Configurable]]</code>:表示\n<ul>\n<li>能否通过 delete 删除属性从而可以重新定义属性</li>\n<li>能否修改属性的特性</li>\n<li>能否把属性修改为访问器属性</li>\n</ul>\n</li>\n<li><code>[[Enumerable]]</code>:表示能否通过 for-in 循环返回属性。</li>\n<li><code>[[Writable]]</code>:表示能否修改属性的值。</li>\n<li><code>[[Value]]</code>:保存这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。默认值为 undefined。<br>\n对于使用字面量直接在对象上定义的属性(<code>var obj = {name: 'jack'}</code>),<code>[[Configurable]]</code>,<code>[[Enumerable]]</code>,<code>[[Writable]]</code>默认为 true,<code>[[Value]]</code>为设置的值。</li>\n</ol>\n<h3 id=\"访问器属性的特性\"> 访问器属性的特性</h3>\n<p>访问器属性不包含数据值;它们包含一对 getter 和 setter 函数(都非必需)。在读取访问器属性时,会调用 getter 函数,这个函数负责返回有效的值;在写入访问器属性时,会调用 setter 函数并传入新值,这个函数负责决定如何处理数据。访问器属性必须用Object.defineProperty()来定义。\n访问器属性有如下4个特性。</p>\n<ol>\n<li><code>[[Configurable]]</code>:表示\n<ul>\n<li>能否通过 delete 删除属性从而可以重新定义属性</li>\n<li>能否修改属性的特性</li>\n<li>能否把属性修改为数据属性</li>\n</ul>\n</li>\n<li><code>[[Enumerable]]</code>:表示能否通过 for-in 循环返回属性。</li>\n<li><code>[[Get]]</code>:在读取属性时调用的函数。默认值为undefined。</li>\n<li><code>[[Set]]</code>:在写入属性时调用的函数。默认值为undefined。\n访问器属性不能直接定义,必须使用 Object.defineProperty() 来定义。</li>\n</ol>\n<h3 id=\"object-defineproperty\"> Object.defineProperty()</h3>\n<p>要修改属性默认的特性,必须使用 ECMAScript 5 的 Object.defineProperty() 方法。这个方法接收三个参数:属性所在的对象、属性的名字和一个描述符对象。其中,描述符(descriptor)对象的属性必须是:configurable、enumerable、writable&value或者get&set。设置其中的一或多个值,可以修改对应的特性值。</p>\n<p>设置访问器属性时,可以同时指定getter和setter。只指定getter意味着属性是不能写,尝试写入属性会被忽略。在严格模式下,尝试写入只指定了getter函数的属性会抛出错误。类似地,只指定setter函数的属性也不能读,否则在非严格模式下会返回undefined,而在严格模式下会抛出错误。</p>\n<div><p>注意</p>\n<p>在调用 Object.defineProperty() 方法时,如果不指定,configurable、enumerable 和 writable 特性的默认值都是 false。而且,一旦把属性定义为不可配置的,就不能再把它变回可配置了,因为它已经不可配置了🤣。通常不用特意将configurable设为true,因为很少会有修改特性的需求。</p>\n</div>\n<p>ECMAScript 5又定义了一个Object.defineProperties()方法。利用这个方法可以通过描述符一次定义多个属性。这个方法接收两个对象参数:第一个参数是要添加和修改其属性的对象,第二个参数是一个对象,其属性与第一个对象中要添加或修改的属性一一对应。</p>\n<div><pre><code><span>var</span> book <span>=</span> <span>{</span><span>}</span>\n\nObject<span>.</span><span>defineProperties</span><span>(</span>book<span>,</span> <span>{</span>\n _year<span>:</span> <span>{</span>\n writable<span>:</span> <span>true</span><span>,</span>\n value<span>:</span> <span>2000</span>\n <span>}</span><span>,</span>\n edition<span>:</span> <span>{</span>\n writable<span>:</span> <span>true</span><span>,</span>\n value<span>:</span> <span>0</span>\n <span>}</span><span>,</span>\n year<span>:</span> <span>{</span>\n <span>get</span><span>:</span> <span>function</span><span>(</span><span>)</span> <span>{</span>\n <span>return</span> <span>this</span><span>.</span>_year\n <span>}</span><span>,</span>\n <span>set</span><span>:</span> <span>function</span><span>(</span><span>y</span><span>)</span> <span>{</span>\n <span>this</span><span>.</span>_year <span>=</span> year\n <span>this</span><span>.</span>edition<span>++</span>\n <span>}</span>\n <span>}</span>\n<span>}</span><span>)</span>\n\n<span>// 对book用for-in循环,啥也打印不出来。</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br><span>19</span><br><span>20</span><br><span>21</span><br><span>22</span><br><span>23</span><br></div></div><p>使用ECMAScript 5的Object.getOwnPropertyDescriptor()方法,可以取得给定属性的描述符。这个方法接收两个参数:属性所在的对象和要读取其描述符的属性名称。返回值是一个对象,如果是访问器属性,这个对象的属性有configurable、enumerable、get和set;如果是数据属性,这个对象的属性有configurable、enumerable、writable和value。<br>\nObject.getOwnPropertyDescriptors(obj),取得obj的所有属性描述符。</p>\n<h2 id=\"创建对象\"> 创建对象</h2>\n<p>Object构造函数或对象字面量都可以用来创建单个对象,但这些方式有个明显的缺点:会产生大量的重复代码。</p>\n<div><pre><code><span>var</span> p1 <span>=</span> <span>{</span>\n name<span>:</span> <span>\"wyz\"</span><span>,</span>\n age<span>:</span> <span>21</span><span>,</span>\n <span>sayHi</span><span>:</span> <span>function</span><span>(</span><span>)</span><span>{</span><span>alert</span><span>(</span><span>\"says Hi\"</span><span>)</span><span>}</span>\n<span>}</span><span>;</span>\n<span>var</span> p2 <span>=</span> <span>{</span>\n name<span>:</span> <span>\"gtl\"</span><span>,</span>\n age<span>:</span> <span>22</span><span>,</span>\n <span>sayHi</span><span>:</span> <span>function</span><span>(</span><span>)</span><span>{</span><span>alert</span><span>(</span><span>\"says Hi\"</span><span>)</span><span>}</span>\n<span>}</span><span>;</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br></div></div><h3 id=\"工厂模式\"> 工厂模式</h3>\n<div><pre><code><span>function</span> <span>createPerson</span><span>(</span><span>name<span>,</span> age</span><span>)</span>\n<span>{</span>\n <span>var</span> o <span>=</span> <span>new</span> <span>Object</span><span>(</span><span>)</span><span>;</span>\n o<span>.</span>name <span>=</span> name<span>;</span>\n o<span>.</span>age <span>=</span> age<span>;</span>\n o<span>.</span><span>sayHi</span> <span>=</span> <span>function</span><span>(</span><span>)</span><span>{</span><span>alert</span><span>(</span><span>\"says Hi\"</span><span>)</span><span>}</span><span>;</span>\n <span>return</span> o<span>;</span>\n<span>}</span>\n\n<span>var</span> p1 <span>=</span> <span>createPerson</span><span>(</span><span>\"wyz\"</span><span>,</span> <span>21</span><span>)</span><span>;</span> \n<span>var</span> p2 <span>=</span> <span>createPerson</span><span>(</span><span>\"gtl\"</span><span>,</span> <span>22</span><span>)</span><span>;</span> \n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br></div></div><p>工厂模式虽然解决了创建多个相似对象代码重复的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。</p>\n<h3 id=\"构造函数模式\"> 构造函数模式</h3>\n<div><pre><code><span>function</span> <span>Person</span><span>(</span><span>name<span>,</span> age</span><span>)</span>\n<span>{</span>\n <span>this</span><span>.</span>name <span>=</span> name<span>;</span>\n <span>this</span><span>.</span>age <span>=</span> age<span>;</span>\n <span>this</span><span>.</span><span>sayHi</span> <span>=</span> <span>function</span><span>(</span><span>)</span><span>{</span><span>alert</span><span>(</span><span>\"says Hi\"</span><span>)</span><span>}</span><span>;</span>\n<span>}</span>\n\n<span>var</span> p1 <span>=</span> <span>new</span> <span>Person</span><span>(</span><span>\"wyz\"</span><span>,</span> <span>21</span><span>)</span><span>;</span>\n<span>var</span> p2 <span>=</span> <span>new</span> <span>Person</span><span>(</span><span>\"gtl\"</span><span>,</span> <span>22</span><span>)</span><span>;</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br></div></div><p>注意到构造函数 Person() 的特点</p>\n<ul>\n<li>没有显式地创建对象</li>\n<li>直接将属性和方法赋给了this对象</li>\n<li>没有return语句</li>\n</ul>\n<p>要创建Person的新实例,必须使用new操作符。以这种方式调用构造函数实际上会经历以下4个步骤</p>\n<ol>\n<li>创建一个新对象;</li>\n<li>将构造函数的作用域赋给新对象(因此this就指向了这个新对象)🤔 ;</li>\n<li>执行构造函数中的代码(为这个新对象添加属性);</li>\n<li>返回新对象。</li>\n</ol>\n<p>使用构造函数的主要问题,就是每个方法都要在每个实例上重新创建一遍。</p>\n<div><p>提示</p>\n<p>p1和p2都有一个名为sayHi()的方法,但那两个方法不是同一个Function的实例。即 p1.sayHi != p2.sayHi</p>\n</div>\n<h3 id=\"原型模式\"> 原型模式</h3>\n<p>我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的<strong>所有实例共享</strong>的属性和方法。</p>\n<div><pre><code><span>function</span> <span>Person</span><span>(</span><span>)</span><span>{</span><span>}</span>\n<span>Person</span><span>.</span>prototype<span>.</span>name <span>=</span> <span>\"wyz\"</span><span>;</span>\n<span>Person</span><span>.</span>prototype<span>.</span>age <span>=</span> <span>21</span><span>;</span>\n<span>Person</span><span>.</span>prototype<span>.</span><span>sayHi</span> <span>=</span> <span>function</span><span>(</span><span>)</span><span>{</span><span>alert</span><span>(</span><span>this</span><span>.</span>name<span>)</span><span>;</span><span>}</span><span>;</span>\n\n<span>var</span> p1 <span>=</span> <span>new</span> <span>Person</span><span>(</span><span>)</span><span>;</span>\np1<span>.</span><span>sayHi</span><span>(</span><span>)</span><span>;</span> <span>// \"wyz\"</span>\n<span>var</span> p2 <span>=</span> <span>new</span> <span>Person</span><span>(</span><span>)</span><span>;</span>\np2<span>.</span>name <span>=</span> <span>\"gtl\"</span><span>;</span>\np2<span>.</span><span>sayHi</span><span>(</span><span>)</span><span>;</span> <span>// \"gtl\"</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br></div></div><div><p>提示</p>\n<p>理解ECMAScript中原型对象的性质:</p>\n<ol>\n<li>无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为<strong>该函数创建一个prototype属性</strong>,这个属性指向(可以理解为指针😏)函数的原型对象。所有原型对象都会默认获得一个constructor属性,这个属性是一个指向prototype属性所属函数的指针(Person.prototype.constructor == Person)。而通过这个构造函数对象,我们还可继续为原型对象添加其他属性和方法。创建了自定义的构造函数之后,其原型对象默认只会取得constructor属性;至于其他方法,则都是从Object继承而来的。</li>\n<li>当调用构造函数创建一个新实例后,该<strong>实例的内部将包含一个指针,指向构造函数的原型对象</strong>(p1.__proto__ == Person.prototype)。ECMA-262第5版中管这个指针叫<code>[[Prototype]]</code>。虽然在脚本中没有标准的方式访问<code>[[Prototype]]</code>,但Firefox、Safari和Chrome在每个对象上都支持一个属性__proto__;而在其他实现中,这个属性对脚本则是完全不可见的。</li>\n<li>ECMAScript 5增加了一个新方法,叫Object.getPrototypeOf(),在所有支持的实现中,这个方法返回<code>[[Prototype]]</code>的值。😂</li>\n</ol>\n</div>\n<h4 id=\"更简单的原型语法\"> 更简单的原型语法</h4>\n<div><pre><code><span>function</span> <span>Person</span><span>(</span><span>)</span><span>{</span><span>}</span><span>;</span>\n<span>var</span> p1 <span>=</span> <span>Person</span><span>(</span><span>)</span><span>;</span> <span>// 把这一行移动到 Person.prototype 赋值后就没问题了</span>\n<span>Person</span><span>.</span>prototype <span>=</span> <span>{</span>、\n <span>// constructor: Person, // 最好也加上这行</span>\n name<span>:</span> <span>\"wyz\"</span><span>,</span>\n age<span>:</span> <span>21</span><span>,</span>\n <span>sayHi</span><span>:</span> <span>function</span><span>(</span><span>)</span><span>{</span><span>alert</span><span>(</span><span>this</span><span>.</span>name<span>)</span><span>;</span><span>}</span>\n<span>}</span>\np1<span>.</span><span>sayHi</span><span>(</span><span>)</span><span>;</span> <span>// error p1.__proto__ 指向的老对象没有 sayHi 函数</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br></div></div><p>此时,instanceof操作符会返回正确的结果。</p>\n<div><p>特别小心</p>\n<p>因为 Person.prototype 和 p1.__proto__ 都是指针。经过这样"简单"的赋值之后, Person.prototype 指向新的对象,而 p1.__proto__ 仍然指向原来的原型对象💀。并且,新的对象的 constructor 等于 Object 而不是 Person 了,而且 constructor 的特性 <code>[[enumarable]]</code> 也从默认的 false 变成了 true。</p>\n</div>\n<h4 id=\"查找对象属性\"> 查找对象属性</h4>\n<p>每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索首先从对象实例本身开始。如果在实例中找到了具有给定名字的属性,则返回该属性的值;如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这个属性,则返回该属性的值。<br>\n也就是说,如果我们在实例中添加了一个属性,而该属性与实例原型中的一个属性同名,那我们就在实例中创建该属性,该属性将会屏蔽原型中的那个属性。(就近原则😏)</p>\n<div><p>提示</p>\n<p>使用hasOwnProperty()方法(从Object继承来的)可以检测一个属性是存在于实例中(返回true),还是存在于原型中。(加了Own的都只能操作实例本身的属性😄)</p>\n</div>\n<p>要取得对象上所有可枚举的实例属性,可以使用ECMAScript 5的Object.keys()方法。\n如果你想要得到所有实例属性,无论它是否可枚举,都可以使用Object.getOwnPropertyNames()方法。</p>\n<h4 id=\"原型的动态性\"> 原型的动态性</h4>\n<p>由于在原型中查找值的过程是一次搜索,因此我们对原型对象所做的任何修改都能够立即从实例上反映出来——即使是先创建了实例后修改原型也照样如此</p>\n<h4 id=\"原型模式的缺点\"> 原型模式的缺点</h4>\n<p>原型模式的最大问题是由其共享的本性所导致的。\n原型中所有属性是被很多实例共享的,这种共享对于函数非常合适。对于那些包含基本值的属性倒也说得过去,毕竟,通过在实例上添加一个同名属性,可以隐藏原型中的对应属性。然而,对于包含引用类型值的属性来说,问题就比较突出了。</p>\n<div><pre><code><span>function</span> <span>Person</span><span>(</span><span>)</span><span>{</span><span>}</span><span>;</span>\n<span>Person</span><span>.</span>prototype <span>=</span> <span>{</span>\n name<span>:</span> <span>\"wyz\"</span><span>,</span>\n age<span>:</span> <span>21</span><span>,</span>\n friends<span>:</span> <span>[</span><span>\"cl\"</span><span>,</span> <span>\"gtl\"</span><span>]</span><span>,</span>\n <span>sayHi</span><span>:</span> <span>function</span><span>(</span><span>)</span><span>{</span><span>alert</span><span>(</span><span>this</span><span>.</span>name<span>)</span><span>;</span><span>}</span>\n<span>}</span>\n<span>var</span> p1 <span>=</span> <span>new</span> <span>Person</span><span>(</span><span>)</span><span>;</span>\np1<span>.</span>friends<span>.</span><span>push</span><span>(</span><span>\"jiege\"</span><span>)</span><span>;</span>\n<span>var</span> p2 <span>=</span> <span>new</span> <span>Person</span><span>(</span><span>)</span><span>;</span>\n<span>alert</span><span>(</span>p2<span>.</span>friends<span>)</span><span>;</span> <span>// \"cl\", \"glt\", \"jiege\"</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br></div></div><h3 id=\"组合使用构造函数模式和原型模式\"> 组合使用构造函数模式和原型模式</h3>\n<p>创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。\n这样,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。另外,这种混成模式还支持向构造函数传递参数;可谓是集两种模式之长。👏</p>\n<div><pre><code><span>function</span> <span>Person</span><span>(</span><span>name<span>,</span> age</span><span>)</span>\n<span>{</span>\n <span>this</span><span>.</span>name <span>=</span> name<span>;</span>\n <span>this</span><span>.</span>age <span>=</span> age<span>;</span>\n<span>}</span>\n\n<span>Person</span><span>.</span>prototype <span>=</span> <span>{</span>\n constructor<span>:</span> Person<span>,</span>\n <span>sayHi</span><span>:</span> <span>function</span><span>(</span><span>)</span> <span>{</span>\n <span>alert</span><span>(</span><span>this</span><span>.</span>name<span>)</span><span>;</span>\n <span>}</span>\n<span>}</span>\n\n<span>var</span> p1 <span>=</span> <span>new</span> <span>Person</span><span>(</span><span>\"wyz\"</span><span>,</span> <span>21</span><span>)</span><span>;</span>\n<span>var</span> p2 <span>=</span> <span>new</span> <span>Person</span><span>(</span><span>\"gtl\"</span><span>,</span> <span>22</span><span>)</span><span>;</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br></div></div><h3 id=\"动态原型模式\"> 动态原型模式</h3>\n<div><pre><code><span>function</span> <span>Person</span><span>(</span><span>name<span>,</span> age</span><span>)</span>\n<span>{</span>\n <span>this</span><span>.</span>name <span>=</span> name<span>;</span>\n <span>this</span><span>.</span>age <span>=</span> age<span>;</span>\n\n <span>if</span> <span>(</span><span>typeof</span> <span>this</span><span>.</span>sayHi <span>!=</span> <span>\"function\"</span><span>)</span>\n <span>{</span>\n <span>Person</span><span>.</span>prototype<span>.</span><span>sayHi</span> <span>=</span> <span>function</span><span>(</span><span>)</span><span>{</span><span>alert</span><span>(</span><span>\"says Hi\"</span><span>)</span><span>}</span><span>;</span>\n <span>}</span>\n <span>...</span>\n<span>}</span>\n\n<span>var</span> p1 <span>=</span> <span>new</span> <span>Person</span><span>(</span><span>\"wyz\"</span><span>,</span> <span>21</span><span>)</span><span>;</span>\n<span>var</span> p2 <span>=</span> <span>new</span> <span>Person</span><span>(</span><span>\"gtl\"</span><span>,</span> <span>22</span><span>)</span><span>;</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br></div></div><p>if语句检查的可以是初始化之后应该存在的任何属性或方法——不必用一大堆if语句检查每个属性和每个方法;只要检查其中一个即可。</p>\n<h3 id=\"寄生构造函数\"> 寄生构造函数</h3>\n<p>这种模式的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象</p>\n<div><pre><code><span>function</span> <span>Person</span><span>(</span><span>name<span>,</span> age</span><span>)</span>\n<span>{</span>\n <span>var</span> o <span>=</span> <span>new</span> <span>Object</span><span>(</span><span>)</span><span>;</span> <span>// 得到扩充版Object对象(或Array对象等)</span>\n o<span>.</span>name <span>=</span> name<span>;</span>\n o<span>.</span>age <span>=</span> age<span>;</span>\n o<span>.</span><span>sayHi</span> <span>=</span> <span>function</span><span>(</span><span>)</span><span>{</span><span>alert</span><span>(</span><span>\"says Hi\"</span><span>)</span><span>}</span><span>;</span>\n <span>return</span> o<span>;</span>\n<span>}</span>\n\n<span>var</span> p1 <span>=</span> <span>new</span> <span>Person</span><span>(</span><span>\"wyz\"</span><span>,</span> <span>21</span><span>)</span><span>;</span>\n<span>var</span> p2 <span>=</span> <span>new</span> <span>Person</span><span>(</span><span>\"gtl\"</span><span>,</span> <span>22</span><span>)</span><span>;</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br></div></div><p>除了使用new操作符并把使用的包装函数叫做构造函数之外,这个模式跟工厂模式其实是一模一样的。</p>\n<p>这个模式可以在特殊的情况下用来为对象创建构造函数。假设我们想创建一个具有额外方法的特殊数组。由于不能直接修改Array构造函数,因此可以使用这个模式。</p>\n<p>关于寄生构造函数模式,有一点需要说明:首先,返回的对象与构造函数或者与构造函数的原型属性之间没有关系;也就是说,构造函数返回的对象与在构造函数外部创建的对象没有什么不同。为此,不能依赖instanceof操作符来确定对象类型。</p>\n<h3 id=\"稳妥构造函数模式\"> 稳妥构造函数模式</h3>\n<p>所谓稳妥对象,指的是没有公共属性,而且其方法也不引用this的对象。稳妥对象最适合在一些安全的环境中(这些环境中会禁止使用this和new),或者在防止数据被其他应用程序(如Mashup程序)改动时使用。与寄生构造函数模式类似,区别在于实例不用new操作符构造(因此方法不引用this对象),而且只定义方法,不定义属性。</p>\n<div><pre><code><span>function</span> <span>Person</span><span>(</span><span>name<span>,</span> age</span><span>)</span>\n<span>{</span>\n <span>var</span> o <span>=</span> <span>new</span> <span>Object</span><span>(</span><span>)</span><span>;</span>\n <span>// 这里可以定义私有变量和函数。私有变量很安全,只有私有函数能访问。</span>\n o<span>.</span><span>sayHi</span> <span>=</span> <span>function</span><span>(</span><span>)</span><span>{</span><span>alert</span><span>(</span>name<span>)</span><span>}</span><span>;</span>\n <span>return</span> o<span>;</span>\n<span>}</span>\n\n<span>var</span> p1 <span>=</span> <span>Person</span><span>(</span><span>\"wyz\"</span><span>,</span> <span>21</span><span>)</span><span>;</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br></div></div><p>变量p1中保存的是一个稳妥对象,而除了调用sayHi()方法外,没有别的方式可以访问其数据成员。即使有其他代码会给这个对象添加方法或数据成员,但也不可能有别的办法访问传入到构造函数中的原始数据。</p>\n<p>与寄生构造函数模式类似,使用稳妥构造函数模式创建的对象与构造函数之间也没有什么关系,因此instanceof操作符对这种对象也没有意义。</p>\n<h2 id=\"继承\"> 继承</h2>\n<h3 id=\"原型链\"> 原型链</h3>\n<p>ECMAScript中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。\n基本模式如下</p>\n<div><div><br><br><br><br><br><br><br><div> </div><br><br><br><br><br><br><br></div><pre><code><span>function</span> <span>BaseClass</span><span>(</span><span>)</span><span>{</span><span>this</span><span>.</span>baseProp <span>=</span> <span>\"base\"</span><span>;</span><span>}</span>\n<span>BaseClass</span><span>.</span>prototype<span>.</span><span>baseFunc</span> <span>=</span> <span>function</span><span>(</span><span>)</span><span>{</span>\n <span>return</span> <span>this</span><span>.</span>baseProp<span>;</span>\n<span>}</span><span>;</span>\n\n<span>function</span> <span>DerivedClass</span><span>(</span><span>)</span><span>{</span><span>this</span><span>.</span>derivedProp <span>=</span> <span>\"derived\"</span><span>;</span><span>}</span><span>;</span>\n<span>// 继承了BaseClass</span>\n<span>DerivedClass</span><span>.</span>prototype <span>=</span> <span>new</span> <span>BaseClass</span><span>(</span><span>)</span><span>;</span> <span>// 替换原型必须在添加方法前,否则白加。</span>\n<span>DerivedClass</span><span>.</span>prototype<span>.</span><span>derivedFunc</span> <span>=</span> <span>function</span><span>(</span><span>)</span><span>{</span>\n <span>return</span> <span>this</span><span>.</span>derivedProp<span>;</span>\n<span>}</span><span>;</span>\n\n<span>var</span> instance <span>=</span> <span>new</span> <span>DerivedClass</span><span>(</span><span>)</span><span>;</span>\nconsole<span>.</span><span>log</span><span>(</span>instance<span>.</span><span>baseFunc</span><span>(</span><span>)</span><span>)</span><span>;</span> <span>// \"base\"</span>\n</code></pre><div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br></div></div><p>本例的原型链大概长这样😆\ninstance.__proto__-->DerivedClass.prototype(即new BaseClass())-->BaseClass.prototype-->Object.prototype</p>\n<p>原型链的第一个问题来自包含引用类型值的原型。包含引用类型值的原型属性会被所有实例共享;而这也正是为什么要在构造函数中,而不是在原型对象中定义属性的原因。</p>\n<p>原型链的第二个问题是:在创建子类型的实例时,不能向超类型的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。</p>\n<h3 id=\"借用构造函数-constructor-stealing\"> 借用构造函数(constructor stealing)</h3>\n<p>这种技术的基本思想相当简单,即在子类型构造函数的内部调用超类型构造函数。</p>\n<div><div><br><br><br><br><br><br><div> </div><br><br><br><br><br><br><br></div><pre><code><span>function</span> <span>Base</span><span>(</span><span>)</span><span>{</span>\n <span>this</span><span>.</span>colors <span>=</span> <span>[</span><span>\"red\"</span><span>,</span> <span>\"green\"</span><span>,</span> <span>\"blue\"</span><span>]</span><span>;</span>\n<span>}</span><span>;</span>\n\n<span>function</span> <span>Derived</span><span>(</span><span>)</span><span>{</span>\n <span>// 继承。用 apply(this)也行</span>\n <span>Base</span><span>.</span><span>call</span><span>(</span><span>this</span><span>)</span><span>;</span>\n<span>}</span><span>;</span>\n<span>var</span> instance1 <span>=</span> <span>new</span> <span>Derived</span><span>(</span><span>)</span><span>;</span>\ninstance1<span>.</span>colors<span>.</span><span>push</span><span>(</span><span>\"black\"</span><span>)</span><span>;</span>\nconsole<span>.</span><span>log</span><span>(</span>instance1<span>.</span>colors<span>)</span><span>;</span><span>// 多一个 \"black\"</span>\n<span>var</span> instance2 <span>=</span> <span>new</span> <span>Derived</span><span>(</span><span>)</span><span>;</span>\nconsole<span>.</span><span>log</span><span>(</span>instance1<span>.</span>colors<span>)</span><span>;</span>\n</code></pre><div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br></div></div><p>这样一来,就会在新SubType对象上执行SuperType()函数中定义的所有对象初始化代码。结果,SubType的每个实例就都会具有自己的colors属性的副本了。</p>\n<p>相对于原型链而言,借用构造函数有一个很大的优势,即可以在子类型构造函数中向超类型构造函数传递参数。</p>\n<div><div><br><br><br><br><br><br><div> </div><br><br><br></div><pre><code><span>function</span> <span>Base</span><span>(</span><span>name</span><span>)</span><span>{</span>\n <span>this</span><span>.</span>name <span>=</span> name<span>;</span>\n<span>}</span><span>;</span>\n\n<span>function</span> <span>Derived</span><span>(</span><span>)</span><span>{</span>\n <span>// 继承。用 apply(this)也行</span>\n <span>Base</span><span>.</span><span>call</span><span>(</span><span>this</span><span>,</span> <span>\"wyz\"</span><span>)</span><span>;</span>\n <span>this</span><span>.</span>age <span>=</span> age<span>;</span>\n<span>}</span><span>;</span>\n</code></pre><div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br></div></div><p>这还不够。</p>\n<h3 id=\"组合继承\"> 组合继承</h3>\n<div><pre><code><span>function</span> <span>Base</span><span>(</span><span>name</span><span>)</span><span>{</span>\n <span>this</span><span>.</span>name <span>=</span> name<span>;</span>\n <span>this</span><span>.</span>colors <span>=</span> <span>[</span><span>\"red\"</span><span>,</span> <span>\"green\"</span><span>,</span> <span>\"blue\"</span><span>]</span><span>;</span>\n<span>}</span><span>;</span>\n<span>Base</span><span>.</span>prototype<span>.</span><span>sayHi</span> <span>=</span> <span>function</span><span>(</span><span>)</span><span>{</span><span>alert</span><span>(</span><span>this</span><span>.</span>name<span>)</span><span>;</span><span>}</span><span>;</span>\n\n<span>function</span> <span>Derived</span><span>(</span><span>name<span>,</span> age</span><span>)</span><span>{</span>\n <span>// 继承属性</span>\n <span>Base</span><span>.</span><span>call</span><span>(</span><span>this</span><span>,</span> name<span>)</span><span>;</span>\n <span>this</span><span>.</span>age <span>=</span> age<span>;</span>\n<span>}</span><span>;</span>\n<span>// 继承方法</span>\n<span>Derived</span><span>.</span>prototype <span>=</span> <span>new</span> <span>Base</span><span>(</span><span>)</span><span>;</span>\n<span>Derived</span><span>.</span>prototype<span>.</span>constructor <span>=</span> Derived<span>;</span>\n<span>// 子类定义新方法</span>\n<span>Derived</span><span>.</span>prototype<span>.</span><span>printAge</span> <span>=</span> <span>function</span><span>(</span><span>)</span><span>{</span>\n console<span>.</span><span>log</span><span>(</span><span>this</span><span>.</span>age<span>;</span><span>)</span><span>;</span>\n<span>}</span><span>;</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br></div></div><p>使用借用构造函数继承属性,使用原型链继承方法。👏👏👏</p>\n<h3 id=\"原型式继承\"> 原型式继承</h3>\n<div><pre><code><span>function</span> <span>object</span><span>(</span><span>o</span><span>)</span>\n<span>{</span>\n <span>function</span> <span>F</span><span>(</span><span>)</span><span>{</span><span>}</span><span>;</span>\n <span>F</span><span>.</span>prototype <span>=</span> o<span>;</span>\n <span>return</span> <span>new</span> <span>F</span><span>(</span><span>)</span><span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br></div></div><p>在object()函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。从本质上讲,object()对传入其中的对象执行了一次浅复制。\n克罗克福德主张的这种原型式继承,要求你必须有一个对象可以作为另一个对象的基础。如果有这么一个对象的话,可以把它传递给object()函数,然后再根据具体需求对得到的对象加以修改即可。</p>\n<p>ECMAScript 5通过新增Object.create()方法规范化了原型式继承。这个方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。在传入一个参数的情况下,Object.create()与object()方法的行为相同。</p>\n<p>在只想<strong>让一个对象与另一个对象保持类似</strong>的情况下,原型式继承是完全可以胜任的。不过别忘了,包含引用类型值的属性始终都会共享相应的值,就像使用原型模式一样。</p>\n<h3 id=\"寄生式继承\"> 寄生式继承</h3>\n<div><pre><code><span>function</span> <span>another</span><span>(</span><span>original</span><span>)</span>\n<span>{</span>\n clone <span>=</span> <span>object</span><span>(</span>original<span>)</span><span>;</span>\n clone<span>.</span><span>sayHi</span> <span>=</span> <span>function</span><span>(</span><span>)</span><span>{</span><span>alert</span><span>(</span><span>\"Hi\"</span><span>)</span><span>}</span><span>;</span>\n <span>return</span> clone<span>;</span>\n<span>}</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br></div></div><p>another() 返回的对象原型是original,并添加了额外的sayHi()函数。其实就是把添加函数的行为硬编码到函数中去了。</p>\n<p>在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。前面示范继承模式时使用的object()函数不是必需的;任何能够返回新对象的函数都适用于此模式。</p>\n<p>使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率;这一点与构造函数模式类似。</p>\n<h3 id=\"寄生组合式继承\"> 寄生组合式继承</h3>\n<p>前面说过,组合继承是JavaScript最常用的继承模式;不过,它也有自己的不足。<strong>组合继承最大的问题就是无论什么情况下,都会调用两次超类型构造函数</strong>:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。没错,子类型最终会包含超类型对象的全部实例属性,但我们不得不在调用子类型构造函数时重写这些属性</p>\n<p>好在我们已经找到了解决这个问题方法——寄生组合式继承。所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背后的基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。寄生组合式继承的基本模式如下所示。</p>\n<div><pre><code><span>function</span> <span>inheritPrototype</span><span>(</span><span>Derived<span>,</span> Base</span><span>)</span><span>{</span>\n <span>var</span> prototype <span>=</span> <span>object</span><span>(</span><span>Base</span><span>.</span>prototype<span>)</span><span>;</span> <span>// 创建对象</span>\n prototype<span>.</span>constructor <span>=</span> Derived<span>;</span> <span>//增强对象</span>\n <span>Derived</span><span>.</span>prototype <span>=</span> prototype<span>;</span> <span>// 指定对象</span>\n<span>}</span><span>;</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br></div></div><p>应用方法</p>\n<div><div><br><br><br><br><br><br><br><br><br><br><br><div> </div><br><br><br><br><br><br><br></div><pre><code><span>function</span> <span>Base</span><span>(</span><span>name</span><span>)</span><span>{</span>\n <span>this</span><span>.</span>name <span>=</span> name<span>;</span>\n <span>this</span><span>.</span>colors <span>=</span> <span>[</span><span>\"red\"</span><span>,</span> <span>\"green\"</span><span>,</span> <span>\"blue\"</span><span>]</span><span>;</span>\n<span>}</span><span>;</span>\n<span>Base</span><span>.</span>prototype<span>.</span><span>sayHi</span> <span>=</span> <span>function</span><span>(</span><span>)</span><span>{</span><span>alert</span><span>(</span><span>this</span><span>.</span>name<span>)</span><span>;</span><span>}</span><span>;</span>\n\n<span>function</span> <span>Derived</span><span>(</span><span>name<span>,</span> age</span><span>)</span><span>{</span>\n <span>Base</span><span>.</span><span>call</span><span>(</span><span>this</span><span>,</span> name<span>)</span><span>;</span>\n <span>this</span><span>.</span>age <span>=</span> age<span>;</span>\n<span>}</span><span>;</span>\n<span>// 继承方法</span>\n<span>inheritPrototype</span><span>(</span>Derived<span>,</span> Base<span>)</span><span>;</span> <span>// 这里省了一个 new Base()</span>\n<span>// 子类定义新方法</span>\n<span>Derived</span><span>.</span>prototype<span>.</span><span>printAge</span> <span>=</span> <span>function</span><span>(</span><span>)</span><span>{</span>\n console<span>.</span><span>log</span><span>(</span><span>this</span><span>.</span>age<span>;</span><span>)</span><span>;</span>\n<span>}</span><span>;</span>\n\nconsole<span>.</span><span>log</span><span>(</span><span>Derived</span><span>.</span>prototype<span>.</span>__proto__ <span>==</span> <span>Base</span><span>.</span>prototype<span>)</span><span>// true</span>\n</code></pre><div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br><span>11</span><br><span>12</span><br><span>13</span><br><span>14</span><br><span>15</span><br><span>16</span><br><span>17</span><br><span>18</span><br></div></div><p>这个例子的高效率体现在它只调用了一次SuperType构造函数,并且因此避免了在SubType.prototype上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用instanceof和isPrototypeOf()。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。</p>\n",
"date_published": "2021-10-03T02:18:05.000Z",
"date_modified": "2021-10-03T02:18:05.000Z",
"authors": [
{
"name": "Leonhardt"
}
],
"tags": []
},
{
"title": "numpy用法",
"url": "https://kigane.github.io/note/python/numpy/",
"id": "https://kigane.github.io/note/python/numpy/",
"content_html": "<h2 id=\"索引\"> 索引</h2>\n<h3 id=\"采样\"> 采样</h3>\n<p>x是一维数组,<code>x[i]</code>,为x的第i个元素,np支持数组索引,<code>x[[i, j, k]]</code>,得到<code>[x[i], x[j], x[k]]</code>。如果<code>x[]</code>中传入的数组大于一维,得到的结果类似于将数组flatten后作为索引,得到的结果再reshape成索引数组的形状。</p>\n<div><pre><code>x <span>=</span> np<span>.</span>arange<span>(</span><span>12</span><span>)</span>\ny <span>=</span> np<span>.</span>array<span>(</span><span>[</span><span>[</span><span>1</span><span>,</span> <span>2</span><span>,</span> <span>3</span><span>,</span> <span>4</span><span>]</span><span>,</span> <span>[</span><span>2</span><span>,</span> <span>3</span><span>,</span> <span>4</span><span>,</span> <span>7</span><span>]</span><span>]</span><span>)</span>\nx<span>[</span>y<span>]</span> <span># y必须是np数组才行</span>\n<span># array([[1, 2, 3, 4],</span>\n<span># [2, 3, 4, 7]])</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br></div></div><h2 id=\"np-clip\"> np.clip</h2>\n<div><pre><code><span># numpy.clip(a, a_min, a_max, out=None, **kwargs)</span>\na <span>=</span> np<span>.</span>arange<span>(</span><span>5</span><span>)</span>\nnp<span>.</span>clip<span>(</span>a<span>,</span> <span>1</span><span>,</span> <span>3</span><span>)</span> <span># [1,1,2,3,3]</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br></div></div><h2 id=\"np-random-shuffle\"> np.random.shuffle</h2>\n<p>原地打乱数组元素的顺序</p>\n<div><pre><code>x <span>=</span> np<span>.</span>arange<span>(</span><span>10</span><span>)</span>\nnp<span>.</span>random<span>.</span>shuffle<span>(</span>x<span>)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br></div></div><h2 id=\"np-cumsum\"> np.cumsum</h2>\n<p>计算列表元素的累积和</p>\n<div><pre><code>a <span>=</span> np<span>.</span>array<span>(</span><span>[</span><span>[</span><span>1</span><span>,</span><span>2</span><span>,</span><span>3</span><span>]</span><span>,</span> <span>[</span><span>4</span><span>,</span><span>5</span><span>,</span><span>6</span><span>]</span><span>]</span><span>)</span>\n<span># 未指定axis则会先flatten,再计算cumsum</span>\nnp<span>.</span>cumsum<span>(</span>a<span>)</span> <span># 1, 3, 6, 10, 15, 21</span>\n<span># 沿指定轴计算 </span>\nnp<span>.</span>cumsum<span>(</span>a<span>,</span>axis<span>=</span><span>0</span><span>)</span> <span># [[1, 2, 3], [5, 7, 9]]</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br></div></div><h2 id=\"np-diff\"> np.diff</h2>\n<p>计算列表相邻元素之间的差值</p>\n<div><pre><code><span># numpy.diff(a, n=1, axis=-1, prepend=<no value>, append=<no value>)</span>\nx <span>=</span> np<span>.</span>array<span>(</span><span>[</span><span>1</span><span>,</span> <span>2</span><span>,</span> <span>4</span><span>,</span> <span>7</span><span>,</span> <span>0</span><span>]</span><span>)</span>\nnp<span>.</span>diff<span>(</span>x<span>)</span> <span># [1, 2, 3, -7]</span>\n<span># n表示递归地做n次diff</span>\nnp<span>.</span>diff<span>(</span>x<span>,</span> n<span>=</span><span>2</span><span>)</span> <span># [1, 1, -10]</span>\n<span># prepend=1表示在a的最后一维的所有列表前添加一个1,再计算cumsum。append同理</span>\nnp<span>.</span>diff<span>(</span>x<span>,</span> prepend<span>=</span><span>1</span><span>)</span> <span># [0, 1, 2, 3, -7]</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br></div></div><h2 id=\"np-ma-masked-where\"> np.ma.masked_where</h2>\n<p>得到列表中满足条件的元素被设为True的掩码</p>\n<div><pre><code><span># ma.masked_where(condition, a, copy=True)</span>\na <span>=</span> np<span>.</span>arange<span>(</span><span>4</span><span>)</span>\nc <span>=</span> ma<span>.</span>masked_where<span>(</span>a <span><=</span> <span>2</span><span>,</span> a<span>)</span>\n<span># c: masked_array(data=[--, --, --, 3],</span>\n<span># mask=[ True, True, True, False],</span>\n<span># fill_value=999999)</span>\nc<span>[</span><span>0</span><span>]</span><span>=</span><span>99</span>\n<span># masked_array(data=[99, --, --, 3],</span>\n<span># mask=[False, True, True, False],</span>\n<span># fill_value=999999)</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br><span>4</span><br><span>5</span><br><span>6</span><br><span>7</span><br><span>8</span><br><span>9</span><br><span>10</span><br></div></div><ul>\n<li>masked_equal(a, value, copy=True)</li>\n<li>masked_less_equal(a, value, copy=True)</li>\n<li>masked_inside(a, v1, v2, copy=True)</li>\n<li>ma.masked_invalid(a, copy=True)</li>\n<li>...</li>\n</ul>\n<p>填充被masked的位置,使用np.ma.filled</p>\n<div><pre><code>x <span>=</span> np<span>.</span>ma<span>.</span>array<span>(</span><span>[</span><span>1</span><span>,</span><span>2</span><span>,</span><span>3</span><span>,</span><span>4</span><span>,</span><span>5</span><span>]</span><span>,</span> mask<span>=</span><span>[</span><span>0</span><span>,</span><span>0</span><span>,</span><span>1</span><span>,</span><span>0</span><span>,</span><span>1</span><span>]</span><span>,</span> fill_value<span>=</span><span>-</span><span>999</span><span>)</span>\nx<span>.</span>filled<span>(</span><span>)</span>\n<span># array([ 1, 2, -999, 4, -999])</span>\n</code></pre>\n<div><span>1</span><br><span>2</span><br><span>3</span><br></div></div><h2 id=\"np-histogram\"> np.histogram</h2>\n<p><code>hist, bin_edge = numpy.histogram(a, bins=10, range=None, weights=None, density=None)</code></p>\n<ul>\n<li>a: 列表</li>\n<li>bins: 划分出的区间数</li>\n<li>range: 总区间的范围</li>\n<li>weights: 和a大小相同,决定a中每个元素在bin中的计数次数。</li>\n<li>density: 默认为False,对数据进行计数。若为True,则计算概率密度。即有<code>np.sum(hist * np.diff(bin_edges)) == 1</code></li>\n</ul>\n",
"date_published": "2021-12-05T00:00:00.000Z",
"date_modified": "2022-04-02T08:34:13.000Z",
"authors": [
{
"name": "Leonhardt"