-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsitemap.xml
967 lines (939 loc) · 165 KB
/
sitemap.xml
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
<search>
<entry>
<title>[Node.js] Node.js技术栈(一)JavaScript基础</title>
<url>https://litmingc.github.io/post/nodejs/route-js/</url>
<categories>
<category>JavaScript</category><category>Node.js</category>
</categories>
<tags>
<tag>笔记</tag><tag>Node.js学习路线</tag>
</tags>
<content type="html"> 内容摘录自:
https://www.w3school.com.cn/js/index.asp
基础 一个简单的js实例 js简介 js由浏览器执行js语句 js语句结束需要; 大小写敏感 使用Unicode字符集 导入js代码 &lt;script&gt;标签 外部脚本 1 &lt;script src=&#34;xxx.js&#34;&gt;&lt;/script&gt; 外部脚本的好处:
已经缓存的脚本不需要再加载,可以加速。
js输出 输出方案:
window.alert()写入警告框 document.write()写入html 先获取DOM元素(例如,document.getElementById(id)),再使用innerHTML写入HTML元素 console.log()写入浏览器控制台 值的类型 js有两种类型:
混合值,字面量 变量值,变量 1 2 // x是变量值,12.3是混合值 var x = 12.3; 变量 使用var声明变量
重复声明不会丢失变量的值: 1 2 var x = &#34;value&#34;; var x; x值仍为&quot;value&quot;。
向未声明的变量赋值,它会自动成为全局变量;在“严格模式”中不会自动创建全局变量。 js变量必须以唯一的名称标识
构造变量名称的通用规则:
必须以字母、_、$开头 运算 +
数字与数字是求和运算,有字符串参与时则是级联运算。 字符串与字符串相加: 1 2 3 txt1 = &#34;Bill&#34;; txt2 = &#34;Gates&#34;; txt3 = txt1 + &#34; &#34; + txt2; 字符串与数字相加: 1 2 3 x = 7 + 8; y = &#34;7&#34; + 8; z = &#34;Hello&#34; + 7; x是一个数字;
y、z是字符串。
A?B:C三元运算符 **幂运算 ===值相等并且类型相等;!==值不相等或类型不相等 JavaScript运算符优先级 好长的表,不抄了,还是放个链接吧! JavaScript运算符优先级 数据类型 字符串、数值、布尔值、数组、对象
JavaScript是动态类型,相同变量可作不同类型。
……
字符串 单引号、双引号都可以 数值number js只有一种数值 科学计数法var x=123e-5 ……
typeof运算符,返回类型
1 typeof &#34;&#34; // 返回&#34;string&#34; 对于复杂类型:
typeof 运算符可返回以下两种类型之一:
function object typeof 运算符把对象、数组或 null 返回 object。 typeof 运算符不会把函数返回 object。 undefined
没有值的变量,其值为undefined,其类型也是undefined
null是一个对象类型,值是null
undefined与null的值相等,类型不相同
1 2 null === undefined // false null == undefined // true 字符串 常用的字符串方法: 方法 描述 indexOf(&quot;&quot;,20) &quot;asddsa&quot;.indexOf(&quot;ab&quot;,2),从索引2开始检索,返回字符串首次出现的索引,即&quot;ab&quot;中a的索引位置 lastIndexOf() 返回最后一次出现的索引 search() 类似indexOf,但是indexOf无法使用正则 slice(x,y) 提取索引x位置到y位置的字符串,索引可以取负数表示倒数,第二个参数默认为最大索引值 substring() 类似slice,但是不接受负数参数 substr(x,n) 类似slice,但是第二个参数表示子串长度 replace(&quot;oldstr&quot;, &quot;newstr&quot;) 默认只替换首个匹配,可以使用正则(不带引号) toUpperCae()、toLowerCase() 大小写转换 concat() &quot;Hello&quot;.concat(&quot; &quot;,&quot;World!&quot;)的返回值等同于&quot;Hello&quot; + &quot; &quot; + &quot;World!&quot;;不改变原字符串的值 trim() 删除两端的空白符 charAt(x) 返回指定位置的字符 charCodeAt(x) 返回unicode值 split(&quot;,&quot;) 把字符串分割成数组;split(&quot;,&quot;)将把字符串分割成单个字符数组 注意 :+符号为级联运算,但是其它运算符(/、-、*)会尝试将字符串转为数字,进行数值运算。
1 2 3 4 5 6 var x = &#34;100&#34;; var y = &#34;10&#34;; var z = x / y; // z 将是 10 var z = x * y; // z 将是 1000 var z = x - y; // z 将是 90 var z = x + y; // z 不会是 110(而是 10010) 数字 JavaScript 只有一种数值类型。 书写数值时带不带小数点均可,还可以使用科学计数法。
JavaScript 数值始终是 64 位的浮点数。
精度:
整数(不使用指数或科学计数法)会被精确到 15 位:
1 2 var x = 999999999999999; // x 将是 999999999999999 var y = 9999999999999999; // y 将是 10000000000000000 小数的最大数是 17 位,但是浮点的算数并不总是 100% 精准:
1 2 3 4 var x = 0.2 + 0.1; // x 将是 0.30000000000000004 // 使用乘除法有助于解决上面的问题: var x = (0.2 * 10 + 0.1 * 10) / 10; // x 将是 0.3 使用+时,注意是否有字符串
注意下列情况:
1 2 3 4 var x = 10; var y = 20; var z = &#34;30&#34;; var result = x + y + z; result的结果是&quot;3030&quot;。因为,JavaScript 从左向右进行编译;x和y都是数,10+20将被相加;z是字符串,30+&quot;30&quot;被级联。
进制
JavaScript 会把前缀为0x的数值常量解释为十六进制。
一些 JavaScript 版本会把带有前导零的数解释为八进制(var a=07;)。
使用 toString() 方法把数输出为十六进制、八进制或二进制:
1 2 3 4 var myNumber = 128; myNumber.toString(16); // 返回 80 myNumber.toString(8); // 返回 200 myNumber.toString(2); // 返回 10000000 数值可以是对象,但是不建议使用数值对象。
1 2 3 4 5 var x = 123; var y = new Number(123); // typeof x 返回 number // typeof y 返回 object 不适当地使用数值对象,在使用 === 相等运算符时,看似相等的数可能实际不相等,因为 === 运算符需要类型和值同时相等:
1 2 3 4 var x = 500; var y = new Number(500); // (x === y) 为 false,因为 x 和 y 的类型不同 // (x == y) 为 true,因为 x 和 y 有相等的值 而且,JavaScript 对象无法进行比较:
1 2 3 var x = new Number(500); var y = new Number(500); // (x == y) 为 false,因为对象无法比较 JavaScript 数值方法
一些特殊的数:
NaN非数值 1 2 3 var x = 100 / &#34;Apple&#34;; // x 将是 NaN isNaN(x); // 返回 true,因为 x 不是数 var x = 100 / &#34;10&#34;; // x 将是 10 注意:NaN是数,typeof NaN返回 number
Infinity(或-Infinity)表示JavaScript在计算数时超出最大可能数范围时返回的值
除以 0(零)也会生成 Infinity 。 数组 1 2 3 4 5 6 7 8 var cars = [ &#34;Saab&#34;, &#34;Volvo&#34;, &#34;BMW&#34; ]; var cars = new Array(&#34;Saab&#34;, &#34;Volvo&#34;, &#34;BMW&#34;); // 以上两个例子效果完全一样。无需使用 new Array()。 // 出于简洁、可读性和执行速度的考虑,请使用第一种方法(数组文本方法)。 数组是一种特殊类型的对象。在 JavaScript 中对数组使用 typeof 运算符会返回 &ldquo;object&rdquo; 如何识别数组: 1 2 3 4 5 6 7 8 9 10 11 // ECMAScript 5 的方法 Array.isArray() Array.isArray(fruits); // 返回 true // 创建自己的 isArray() 函数以解决此问题 function isArray(x) { return x.constructor.toString().indexOf(&#34;Array&#34;) &gt; -1; } //假如对象由给定的构造器创建,则 instanceof 运算符返回 true var fruits = [&#34;Banana&#34;, &#34;Orange&#34;, &#34;Apple&#34;, &#34;Mango&#34;]; fruits instanceof Array // 返回 true 数组的增删方法 数组的迭代方法 Array.reduce() 1 2 3 4 5 6 var numbers1 = [45, 4, 9, 16, 25]; var sum = numbers1.reduce(myFunction); function myFunction(total, value, index, array) { return total + value; } 其中,total的初始值为numbers1[0],之后total为函数返回值;遍历数组可以看作从numbers1[1]开始遍历求和。
流程控制 switch switch case 使用严格比较(===) JavaScript 提升(Hoisting) 在 JavaScript 中,可以在使用变量之后对其进行声明。
换句话说,可以在声明变量之前使用它。
1 2 3 4 5 6 x = 5; // 把 5 赋值给 x elem = document.getElementById(&#34;demo&#34;); // 查找元素 elem.innerHTML = x; // 在元素中显示 x var x; // 声明 x 但是,严格模式中 在不声明对象的情况下使用对象也是不允许的。
JavaScript 初始化不会被提升:JavaScript 只提升声明,而非初始化。
1 2 3 4 var x = 5; // 初始化 x elem = document.getElementById(&#34;demo&#34;); // 查找元素 elem.innerHTML = x + &#34; &#34; + y; // 显示 &#34;5 undefined&#34; var y = 7; // 初始化 y This 实例:
1 2 3 4 5 6 7 8 var person = { firstName: &#34;Bill&#34;, lastName : &#34;Gates&#34;, id : 678, fullName : function() { return this.firstName + &#34; &#34; + this.lastName; } }; this 是什么? JavaScript this 关键词指的是它所属的对象。
它拥有不同的值,具体取决于它的使用位置:
在方法中,this 指的是所有者对象。 单独的情况下,this 指的是全局对象。
在函数中,this 指的是全局对象[object Window]。 在函数中,严格模式下,this 是 undefined。 在事件中,this 指的是接收事件的元 素。
像 call() 和 apply() 这样的方法可以将 this 引用到任何对象。 函数 函数定义
1 2 3 function name(参数 1, 参数 2, 参数 3) { 要执行的代码 } 函数调用
函数返回
1 2 3 4 5 function toCelsius(fahrenheit) { return (5/9) * (fahrenheit-32); } document.getElementById(&#34;demo&#34;).innerHTML = toCelsius; toCelsius将返回函数定义,demo代表的标签内容将是toCelsius的代码。
对象 所有 JavaScript 值,除了原始值(string、number、boolean、null、undefined),都是对象。
对象的定义 使用对象文字定义和创建单个对象: 1 var car = {type:&#34;porsche&#34;, model:&#34;911&#34;, color:&#34;white&#34;}; js对象是 被命名值 的容器,以键值对的形式书写其 属性 与 属性值。
于简易性、可读性和执行速度的考虑,对象文字是比较好的创建方法。
通过关键词 new 定义和创建单个对象 1 2 3 var person = new Object(); person.firstName = &#34;Bill&#34;; person.lastName = &#34;Gates&#34;; 定义对象构造器,然后创建构造类型的对象 对象属性 遍历对象属性: 1 2 3 4 5 6 7 var txt = &#34;&#34;; var person = {fname:&#34;Bill&#34;, lname:&#34;Gates&#34;, age:62}; var x; for (x in person) { txt += person[x] + &#34; &#34;; } // txt 的值为 &#34;Bill Gates 62&#34; 对象访问器(Getter/Setter) Getter 使用关键字get定义Getter:
1 2 3 4 5 6 7 8 9 10 11 12 13 // 创建对象: var person = { firstName: &#34;Bill&#34;, lastName : &#34;Gates&#34;, language : &#34;en&#34;, // 本例使用 lang 属性来获取 language 属性的值 get lang() { return this.language; } }; // 使用 getter 来显示来自对象的数据: document.getElementById(&#34;demo&#34;).innerHTML = person.lang; 使用函数实现相似效果:
1 2 3 4 5 6 7 8 9 10 var person = { firstName: &#34;Bill&#34;, lastName : &#34;Gates&#34;, fullName : function() { return this.firstName + &#34; &#34; + this.lastName; } }; // 使用方法来显示来自对象的数据: document.getElementById(&#34;demo&#34;).innerHTML = person.fullName(); 注意访问形式不同,一种是方法调用,一种是访问属性的形式。
Setter 1 2 3 4 5 6 7 8 9 10 11 12 13 14 var person = { firstName: &#34;Bill&#34;, lastName : &#34;Gates&#34;, language : &#34;&#34;, set lang(lang) { this.language = lang; } }; // 使用 setter 来设置对象属性: person.lang = &#34;en&#34;; // 显示来自对象的数据: document.getElementById(&#34;demo&#34;).innerHTML = person.language; 对象构造器 实例:
1 2 3 4 5 6 7 8 function Person(first, last, age, eye) { this.firstName = first; this.lastName = last; this.age = age; this.eyeColor = eye; } var myFather = new Person(&#34;Bill&#34;, &#34;Gates&#34;, 62, &#34;blue&#34;); 原型 所有 JavaScript 对象都从 原型 继承属性和方法。
原型继承 日期对象继承自 Date.prototype。数组对象继承自 Array.prototype。Person 对象继承自 Person.prototype。
Object.prototype 位于原型继承链的顶端:
日期对象、数组对象和 Person 对象都继承自 Object.prototype。
使用 prototype 属性 向对象添加属性和方法 1 2 3 4 5 6 7 8 9 10 function Person(first, last, age, eyecolor) { this.firstName = first; this.lastName = last; this.age = age; this.eyeColor = eyecolor; } Person.prototype.nationality = &#34;English&#34;; Person.prototype.name = function() { return this.firstName + &#34; &#34; + this.lastName; }; 请只修改您自己的原型。绝不要修改标准 JavaScript 对象的原型。
正则 实例:
1 var patt = /w3school/i; 例子解释:
/w3school/i 是一个正则表达式。
w3school 是模式(pattern)(在搜索中使用)。
i 是修饰符(把搜索修改为大小写不敏感)。
正则那么多,网上搜一搜。
元字符 字符 描述 \ 将下一个字符标记为一个特殊字符、或一个原义字符、或一个 向后引用、或一个八进制转义符。例如,&lsquo;n&rsquo; 匹配字符 &ldquo;n&rdquo;。'\n' 匹配一个换行符。序列 &lsquo;\&rsquo; 匹配 &ldquo;&quot; 而 &ldquo;(&rdquo; 则匹配 &ldquo;(&quot;。 ^ 匹配输入字符串的开始位置。如果设置了 RegExp 对象的 Multiline 属性,^ 也匹配 &lsquo;\n&rsquo; 或 &lsquo;\r&rsquo; 之后的位置。 $ 匹配输入字符串的结束位置。如果设置了RegExp 对象的 Multiline 属性,$ 也匹配 &lsquo;\n&rsquo; 或 &lsquo;\r&rsquo; 之前的位置。 * 匹配前面的子表达式零次或多次。例如,zo* 能匹配 &ldquo;z&rdquo; 以及 &ldquo;zoo&rdquo;。* 等价于{0,}。 + 匹配前面的子表达式一次或多次。例如,&lsquo;zo+&rsquo; 能匹配 &ldquo;zo&rdquo; 以及 &ldquo;zoo&rdquo;,但不能匹配 &ldquo;z&rdquo;。+ 等价于 {1,}。 ? 匹配前面的子表达式零次或一次。例如,&ldquo;do(es)?&rdquo; 可以匹配 &ldquo;do&rdquo; 或 &ldquo;does&rdquo; 。? 等价于 {0,1}。 {n} n 是一个非负整数。匹配确定的 n 次。例如,&lsquo;o{2}&rsquo; 不能匹配 &ldquo;Bob&rdquo; 中的 &lsquo;o&rsquo;,但是能匹配 &ldquo;food&rdquo; 中的两个 o。 {n,} n 是一个非负整数。至少匹配n 次。例如,&lsquo;o{2,}&rsquo; 不能匹配 &ldquo;Bob&rdquo; 中的 &lsquo;o&rsquo;,但能匹配 &ldquo;foooood&rdquo; 中的所有 o。&lsquo;o{1,}&rsquo; 等价于 &lsquo;o+'。&lsquo;o{0,}&rsquo; 则等价于 &lsquo;o*'。 {n,m} m 和 n 均为非负整数,其中n &lt;= m。最少匹配 n 次且最多匹配 m 次。例如,&ldquo;o{1,3}&rdquo; 将匹配 &ldquo;fooooood&rdquo; 中的前三个 o。&lsquo;o{0,1}&rsquo; 等价于 &lsquo;o?'。请注意在逗号和两个数之间不能有空格。 ? 当该字符紧跟在任何一个其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串 &ldquo;oooo&rdquo;,&lsquo;o+?&rsquo; 将匹配单个 &ldquo;o&rdquo;,而 &lsquo;o+&rsquo; 将匹配所有 &lsquo;o&rsquo;。 . 匹配除换行符(\n、\r)之外的任何单个字符。要匹配包括 &lsquo;\n&rsquo; 在内的任何字符,请使用像&rdquo;(. (pattern) 匹配 pattern 并获取这一匹配。所获取的匹配可以从产生的 Matches 集合得到,在VBScript 中使用 SubMatches 集合,在JScript 中则使用 0…&rdquo; role=&ldquo;presentation&rdquo; style=&ldquo;position: relative;&quot;&gt;0…0…0…9 属性。要匹配圆括号字符,请使用 &lsquo;(&rsquo; 或 &lsquo;)'。 (?:pattern) 匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用 &ldquo;或&rdquo; 字符 ( (?=pattern) 正向肯定预查(look ahead positive assert),在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,&ldquo;Windows(?=95 (?!pattern) 正向否定预查(negative assert),在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如&quot;Windows(?!95 (?&lt;=pattern) 反向(look behind)肯定预查,与正向肯定预查类似,只是方向相反。例如,&quot;(?&lt;=95 (?&lt;!pattern) 反向否定预查,与正向否定预查类似,只是方向相反。例如&rdquo;(?&lt;!95 x y [xyz] 字符集合。匹配所包含的任意一个字符。例如, &lsquo;[abc]&rsquo; 可以匹配 &ldquo;plain&rdquo; 中的 &lsquo;a&rsquo;。 [^xyz] 负值字符集合。匹配未包含的任意字符。例如, &lsquo;[^abc]&rsquo; 可以匹配 &ldquo;plain&rdquo; 中的&rsquo;p&rsquo;、&lsquo;l&rsquo;、&lsquo;i&rsquo;、&lsquo;n&rsquo;。 [a-z] 字符范围。匹配指定范围内的任意字符。例如,'[a-z]&rsquo; 可以匹配 &lsquo;a&rsquo; 到 &lsquo;z&rsquo; 范围内的任意小写字母字符。 [^a-z] 负值字符范围。匹配任何不在指定范围内的任意字符。例如,'[^a-z]&rsquo; 可以匹配任何不在 &lsquo;a&rsquo; 到 &lsquo;z&rsquo; 范围内的任意字符。 \b 匹配一个单词边界,也就是指单词和空格间的位置。例如, &lsquo;er\b&rsquo; 可以匹配&quot;never&rdquo; 中的 &lsquo;er&rsquo;,但不能匹配 &ldquo;verb&rdquo; 中的 &lsquo;er&rsquo;。 \B 匹配非单词边界。&lsquo;er\B&rsquo; 能匹配 &ldquo;verb&rdquo; 中的 &lsquo;er&rsquo;,但不能匹配 &ldquo;never&rdquo; 中的 &lsquo;er&rsquo;。 \cx 匹配由 x 指明的控制字符。例如, \cM 匹配一个 Control-M 或回车符。x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 &lsquo;c&rsquo; 字符。 \d 匹配一个数字字符。等价于 [0-9]。 \D 匹配一个非数字字符。等价于 [^0-9]。 \f 匹配一个换页符。等价于 \x0c 和 \cL。 \n 匹配一个换行符。等价于 \x0a 和 \cJ。 \r 匹配一个回车符。等价于 \x0d 和 \cM。 \s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r \v]。 \S 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。 \t 匹配一个制表符。等价于 \x09 和 \cI。 \v 匹配一个垂直制表符。等价于 \x0b 和 \cK。 \w 匹配字母、数字、下划线。等价于&rsquo;[A-Za-z0-9_]'。 \W 匹配非字母、数字、下划线。等价于 &lsquo;[^A-Za-z0-9_]'。 \xn 匹配 n,其中 n 为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,'\x41&rsquo; 匹配 &ldquo;A&rdquo;。'\x041' 则等价于 &lsquo;\x04&rsquo; &amp; &ldquo;1&rdquo;。正则表达式中可以使用 ASCII 编码。 \num 匹配 num,其中 num 是一个正整数。对所获取的匹配的引用。例如,'(.)\1' 匹配两个连续的相同字符。 \n 标识一个八进制转义值或一个向后引用。如果 \n 之前至少 n 个获取的子表达式,则 n 为向后引用。否则,如果 n 为八进制数字 (0-7),则 n 为一个八进制转义值。 \nm 标识一个八进制转义值或一个向后引用。如果 \nm 之前至少有 nm 个获得子表达式,则 nm 为向后引用。如果 \nm 之前至少有 n 个获取,则 n 为一个后跟文字 m 的向后引用。如果前面的条件都不满足,若 n 和 m 均为八进制数字 (0-7),则 \nm 将匹配八进制转义值 nm。 \nml 如果 n 为八进制数字 (0-3),且 m 和 l 均为八进制数字 (0-7),则匹配八进制转义值 nml。 \un 匹配 n,其中 n 是一个用四个十六进制数字表示的 Unicode 字符。例如, \u00A9 匹配版权符号 (?)。 </content>
</entry>
<entry>
<title>[Tips]Rime Settings</title>
<url>https://litmingc.github.io/post/tips/rime-settings/</url>
<categories>
<category>Tips</category>
</categories>
<tags>
</tags>
<content type="html"> rime输入法的设置的备忘录。
文档备忘:
致第一次安装RIME的你
全局配置:
在用户目录修改文件default.custom.yaml,添加配置项:
patch:&quot;ascii_composer/switch_key/Shift_L&quot;: commit_code 自定义符号:
使用朙月拼音.简化字的情况下,在用户目录新建文件luna_pinyin_simp.custom.yaml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 patch:&#34;menu/page_size&#34;: 5# 自定义符号映射punctuator/full_shape:&#34;\\&#34;:&#34;\\&#34;punctuator/half_shape:&#34;\\&#34;:&#34;\\&#34;punctuator/half_shape:&#34;`&#34;:&#34;`&#34;# 配置符号表、匹配的表达式punctuator/import_preset:mysymbolsrecognizer/patterns/punct:&#34;^i([0-9]0?|[A-Za-z]+)$&#34; 注意,要使用特殊符号还需要mysymbols.yaml文件。 在用户目录新建文件mysymbols.yaml: mysymbols.yaml文件的内容 复制了程序目录下的symbols.yaml的内容,将symbols部分的\改成i(i取决于recognizer/patterns/punct:的设置,可以自行选择其它字符)。
示例:
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 patch:punctuator/import_preset:symbolsrecognizer/patterns/punct:&#39;^!([0-9]0?|[A-Za-z]+)$&#39;punctuator:full_shape:&#39; &#39; :{commit:&#39; &#39;}half_shape:&#39;,&#39; :{commit:, }symbols:&#34;i0&#34;: [&#34;〇&#34;,&#34;零&#34;,&#34;₀&#34;,&#34;⁰&#34;,&#34;⓪&#34;,&#34;⓿&#34;,&#34;0&#34;]&#34;i1&#34;: [&#34;一&#34;,&#34;壹&#34;,&#34;₁&#34;,&#34;¹&#34;,&#34;Ⅰ&#34;,&#34;ⅰ&#34;,&#34;①&#34;,&#34;➀&#34;,&#34;❶&#34;,&#34;➊&#34;,&#34;⓵&#34;,&#34;⑴&#34;,&#34;⒈&#34;,&#34;1&#34;,&#34;㊀&#34;,&#34;㈠&#34;,&#34;弌&#34;,&#34;壱&#34;,&#34;幺&#34;,&#34;㆒&#34;]&#34;i10&#34;: [&#34;十&#34;,&#34;拾&#34;,&#34;₁₀&#34;,&#34;¹⁰&#34;,&#34;Ⅹ&#34;,&#34;ⅹ&#34;,&#34;⑩&#34;,&#34;➉&#34;,&#34;❿&#34;,&#34;➓&#34;,&#34;⓾&#34;,&#34;⑽&#34;,&#34;⒑&#34;,&#34;10&#34;,&#34;㊉&#34;,&#34;㈩&#34;,&#34;什&#34;]&#34;i2&#34;: [&#34;二&#34;,&#34;貳&#34;,&#34;₂&#34;,&#34;²&#34;,&#34;Ⅱ&#34;,&#34;ⅱ&#34;,&#34;②&#34;,&#34;➁&#34;,&#34;❷&#34;,&#34;➋&#34;,&#34;⓶&#34;,&#34;⑵&#34;,&#34;⒉&#34;,&#34;2&#34;,&#34;㊁&#34;,&#34;㈡&#34;,&#34;弍&#34;,&#34;弐&#34;,&#34;貮&#34;,&#34;㒃&#34;,&#34;㒳&#34;,&#34;兩&#34;,&#34;倆&#34;,&#34;㆓&#34;]&#34;izmq&#34;: [&#34;ⓐ&#34;,&#34;Ⓐ&#34;,&#34;ⓑ&#34;,&#34;Ⓑ&#34;,&#34;ⓒ&#34;,&#34;Ⓒ&#34;,&#34;ⓓ&#34;,&#34;Ⓓ&#34;,&#34;ⓔ&#34;,&#34;Ⓔ&#34;,&#34;ⓕ&#34;,&#34;Ⓕ&#34;,&#34;ⓖ&#34;,&#34;Ⓖ&#34;,&#34;ⓗ&#34;,&#34;Ⓗ&#34;,&#34;ⓘ&#34;,&#34;Ⓘ&#34;,&#34;ⓙ&#34;,&#34;Ⓙ&#34;,&#34;ⓚ&#34;,&#34;Ⓚ&#34;,&#34;ⓛ&#34;,&#34;Ⓛ&#34;,&#34;ⓜ&#34;,&#34;Ⓜ&#34;,&#34;ⓝ&#34;,&#34;Ⓝ&#34;,&#34;ⓞ&#34;,&#34;Ⓞ&#34;,&#34;ⓟ&#34;,&#34;Ⓟ&#34;,&#34;ⓠ&#34;,&#34;Ⓠ&#34;,&#34;ⓡ&#34;,&#34;Ⓡ&#34;,&#34;ⓢ&#34;,&#34;Ⓢ&#34;,&#34;ⓣ&#34;,&#34;Ⓣ&#34;,&#34;ⓤ&#34;,&#34;Ⓤ&#34;,&#34;ⓥ&#34;,&#34;Ⓥ&#34;,&#34;ⓦ&#34;,&#34;Ⓦ&#34;,&#34;ⓧ&#34;,&#34;Ⓧ&#34;,&#34;ⓨ&#34;,&#34;Ⓨ&#34;,&#34;ⓩ&#34;,&#34;Ⓩ&#34;]&#34;izy&#34;: [&#34;ㄅ&#34;,&#34;ㄆ&#34;,&#34;ㄇ&#34;,&#34;ㄈ&#34;,&#34;ㄉ&#34;,&#34;ㄊ&#34;,&#34;ㄋ&#34;,&#34;ㄌ&#34;,&#34;ㄍ&#34;,&#34;ㄎ&#34;,&#34;ㄏ&#34;,&#34;ㄐ&#34;,&#34;ㄑ&#34;,&#34;ㄒ&#34;,&#34;ㄓ&#34;,&#34;ㄔ&#34;,&#34;ㄕ&#34;,&#34;ㄖ&#34;,&#34;ㄗ&#34;,&#34;ㄘ&#34;,&#34;ㄙ&#34;,&#34;ㄧ&#34;,&#34;ㄨ&#34;,&#34;ㄩ&#34;,&#34;ㄚ&#34;,&#34;ㄛ&#34;,&#34;ㄜ&#34;,&#34;ㄝ&#34;,&#34;ㄞ&#34;,&#34;ㄟ&#34;,&#34;ㄠ&#34;,&#34;ㄡ&#34;,&#34;ㄢ&#34;,&#34;ㄣ&#34;,&#34;ㄤ&#34;,&#34;ㄥ&#34;,&#34;ㄦ&#34;,&#34;ㄪ&#34;,&#34;ㄫ&#34;,&#34;ㄬ&#34;,&#34;ㄭ&#34;,&#34;ㆠ&#34;,&#34;ㆡ&#34;,&#34;ㆢ&#34;,&#34;ㆣ&#34;,&#34;ㆤ&#34;,&#34;ㆥ&#34;,&#34;ㆦ&#34;,&#34;ㆧ&#34;,&#34;ㆨ&#34;,&#34;ㆩ&#34;,&#34;ㆪ&#34;,&#34;ㆫ&#34;,&#34;ㆬ&#34;,&#34;ㆭ&#34;,&#34;ㆮ&#34;,&#34;ㆯ&#34;,&#34;ㆰ&#34;,&#34;ㆱ&#34;,&#34;ㆲ&#34;,&#34;ㆳ&#34;,&#34;ㆴ&#34;,&#34;ㆵ&#34;,&#34;ㆶ&#34;,&#34;ㆷ&#34;]
输入法样式的配置 在用户目录修改文件weasel.custom.yaml,添加配置项:
patch:&quot;style/font_point&quot;: 12&quot;style/horizontal&quot;: true # 水平显示候选字 拓展词库:
访问rime-aca/dictionaries,参看readme.md文件,将*.dict.yaml文件放到用户目录中,同时参照*.custom.yaml文件修改对应的输入方案。
自然码双拼:
访问rime/rime-double-pinyin,下载得到一堆*.schema.yaml文件,将自己需要的文件复制到用户目录中,重新部署后,使用输入发设定勾选需要的输入方案。
用户配置备份:
在installation.yaml文件中添加配置项:
1 2 # Windowssync_dir:&#39;F:\BackupDocuments\RimeSettings&#39; 重新部署后,使用用户资料同步勾选需要的输入方案。
注意:路径中不要出现中文。
</content>
</entry>
<entry>
<title>[C&C++]Cmake</title>
<url>https://litmingc.github.io/post/cc++/cmake/</url>
<categories>
<category>C&C++</category>
</categories>
<tags>
<tag>翻译</tag><tag>笔记</tag>
</tags>
<content type="html"> 本文为阅读官方教程CMake Tutorial的笔记。
Step1:hello world 新建一个project01目录,并添加一个tutorial.cpp文件(其中输出hello world)。
在项目目录下创建一个CMakeLists.txt文件。
文件内容如下:
1 2 3 4 5 6 7 8 # 指定cmake最小版本 cmake_minimum_required(VERSION 3.10)# 设置项目的名称,没有其他设置的话,编译完成的可执行文件就是这个名称 project(project001)# 添加可执行文件,windows下,会编译生成Tutorial.exe add_executable(project01 tutorial.cpp) 可执行文件的名字与项目的名字并没有关系,不一定要设置成一样的。
添加cmake的头文件,添加版本号
修改CMakeLists.txt文件,添加版本号的信息 1 project(project001 VERSION 0.1) 添加tutorial.h.in文件,编写相关的宏定义 1 2 3 // the configured options and settings for Tutorial #define Tutorial_VERSION_MAJOR @project001_VERSION_MAJOR@ #define Tutorial_VERSION_MINOR @project001_VERSION_MINOR@ 需要注意:
@project001_VERSION_MAJOR@的写法,其中包含了项目名称,不同项目名需要对应修改。
修改CMakeLists.txt文件,添加头文件的配置项,使cmake根据XX.h.in文件生成对应的XX.h文件 1 2 3 4 5 configure_file(tutorial.h.in tutorial.h)add_executable(project01 tutorial.cpp)target_include_directories(project01 PUBLIC &#34;${PROJECT_BINARY_DIR}&#34; ) configure_file指定了生成的头文件的名字;
target_include_directories指定头文件所在目录,其中第一个参数是目标文件,此处与add_executable一致,与项目名无关。因此,target_include_directories需要写在add_executable后面。
cmake构建之后可以在项目中检查生成的头文件,@project001_VERSION_MAJOR@被做了相应的替换:
1 2 3 // the configured options and settings for Tutorial #define Tutorial_VERSION_MAJOR 0 #define Tutorial_VERSION_MINOR 1 修改main函数,使用宏,输出cmake中定义的版本号信息 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include &lt;iostream&gt;#include &#34;tutorial.h&#34; int main(int argc, char* argv[]) { if (argc &lt; 2) { // report version std::cout &lt;&lt; argv[0] &lt;&lt; &#34; Version &#34; &lt;&lt; Tutorial_VERSION_MAJOR &lt;&lt; &#34;.&#34; &lt;&lt; Tutorial_VERSION_MINOR &lt;&lt; std::endl; std::cout &lt;&lt; &#34;Usage: &#34; &lt;&lt; argv[0] &lt;&lt; &#34; number&#34; &lt;&lt; std::endl; return 1; } return 0; } 指定c++标准
当需要使用c++新的特性是,可以在cmake中指定:
1 2 3 # specify the C++ standard set(CMAKE_CXX_STANDARD 11)set(CMAKE_CXX_STANDARD_REQUIRED True) 编译项目
新建一个build目录,在build目录下,执行命令:
1 2 cmake ../ cmake --build ./ 其中cmake ../指定的是CMakeLists.txt文件的位置,并在执行目录生成项目文件以及编译过程中需要的配置文件,其中有CMakeCache.txt等文件。
cmake --build ./指定的就是CMakeCache.txt的目录。
Step2:添加库文件 新建子目录MathFunctions,构造我们的mysqrt函数,并在main函数中调用。
新建文件MathFunctions.h、mysqrt.cpp,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // mysqrt.cpp #include &lt;iostream&gt; // a hack square root calculation using simple operations double mysqrt(double x) { if (x &lt;= 0) { return 0; } double result = x; // do ten iterations for (int i = 0; i &lt; 10; ++i) { if (result &lt;= 0) { result = 0.1; } double delta = x - (result * result); result = result + 0.5 * delta / result; std::cout &lt;&lt; &#34;Computing sqrt of &#34; &lt;&lt; x &lt;&lt; &#34; to be &#34; &lt;&lt; result &lt;&lt; std::endl; } return result; } 1 2 // mysqrt.cpp double mysqrt(double x); 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 // main函数 #include &lt;iostream&gt;#include &lt;string&gt;#include &#34;tutorial.h&#34;#include &#34;MathFunctions.h&#34; int main(int argc, char* argv[]) { if (argc &lt; 2) { // report version std::cout &lt;&lt; argv[0] &lt;&lt; &#34; Version &#34; &lt;&lt; Tutorial_VERSION_MAJOR &lt;&lt; &#34;.&#34; &lt;&lt; Tutorial_VERSION_MINOR &lt;&lt; std::endl; std::cout &lt;&lt; &#34;Usage: &#34; &lt;&lt; argv[0] &lt;&lt; &#34; number&#34; &lt;&lt; std::endl; return 1; } // convert input to double const double inputValue = std::stod(argv[1]); const double outputValue = mysqrt(inputValue); std::cout &lt;&lt; &#34;The square root of &#34; &lt;&lt; inputValue &lt;&lt; &#34; is &#34; &lt;&lt; outputValue &lt;&lt; std::endl; return 0; } 在MathFunctions子目录下,添加CMakeLists.txt文件,添加MathFunctions库。文件内容如下:
1 add_library(MyFunctions mysqrt.cpp) 修改顶层的CMakeLists.txt(项目目录下),为目标文件添加链接:
1 2 3 4 5 6 7 8 add_executable(project01 tutorial.cpp)add_subdirectory(MathFunctions)target_link_libraries(project01 PUBLIC MyFunctions)target_include_directories(project01 PUBLIC &#34;${PROJECT_BINARY_DIR}&#34; &#34;${PROJECT_SOURCE_DIR}/MathFunctions&#34; ) 至此,main中已经可以正常调用mysqrt函数。
下面,添加控制变量,通过cmake的控制变量,在项目中调用不同的函数。
修改顶层的CMakeLists.txt,添加控制变量USE_MYMATH,使得MathFunctions成为可选的库文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 option(USE_MYMATH &#34;Use tutorial provided math implementation&#34; ON)configure_file(tutorial.h.in tutorial.h)if(USE_MYMATH) add_subdirectory(MathFunctions) list(APPEND EXTRA_LIBS MathFunctions) list(APPEND EXTRA_INCLUDES &#34;${PROJECT_SOURCE_DIR}/MathFunctions&#34;)endif()# add the executable add_executable(Tutorial tutorial.cxx)target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS})# add the binary tree to the search path for include files # so that we will find TutorialConfig.h target_include_directories(Tutorial PUBLIC &#34;${PROJECT_BINARY_DIR}&#34; ${EXTRA_INCLUDES} ) 通过option设置变量,之后通过.h.in文件生成的头文件引入宏定义,通过宏修改代码中的相关逻辑。
修改tutorial.h.in,引入USE_MYMATH的定义:
1 2 // tutorial.h.in #cmakedefine USE_MYMATH 类似于#cmakedefine VAR的定义语句将会被替换为#define VAR或者/* #undef VAR /,如上例中USE_MYMATH当设定为ON的时候,#cmakedefine USE_MYMATH变成了#define USE_MYMATH,设定为OFF时,变成了/ #undef USE_MYMATH */;
同理,类似于#cmakedefine01 VAR的定义语句将会被替换为#define VAR 1或#define VAR 0。1
修改mian函数,利用USE_MYMATH控制是否调用自定义的mysqrt函数:
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 #include &lt;math.h&gt;#include &lt;iostream&gt;#include &lt;string&gt;#include &#34;tutorial.h&#34; #ifdef USE_MYMATH #include &#34;MathFunctions.h&#34;#endif int main(int argc, char *argv[]) { if (argc &lt; 2) { // report version std::cout &lt;&lt; argv[0] &lt;&lt; &#34; Version &#34; &lt;&lt; Tutorial_VERSION_MAJOR &lt;&lt; &#34;.&#34; &lt;&lt; Tutorial_VERSION_MINOR &lt;&lt; std::endl; std::cout &lt;&lt; &#34;Usage: &#34; &lt;&lt; argv[0] &lt;&lt; &#34; number&#34; &lt;&lt; std::endl; return 1; } // convert input to double const double inputValue = std::stod(argv[1]); #ifdef USE_MYMATH const double outputValue = mysqrt(inputValue); #else const double outputValue = sqrt(inputValue); #endif std::cout &lt;&lt; &#34;The square root of &#34; &lt;&lt; inputValue &lt;&lt; &#34; is &#34; &lt;&lt; outputValue &lt;&lt; std::endl; return 0; } 测试变量
在build目录下,使用如下命令设置USE_MYMATH,并编译:
1 2 cmake ../ -DUSE_MYMATH=OFF cmake --build ./ 1 2 cmake ../ -DUSE_MYMATH=ON cmake --build ./ Step 2: Adding a Library
Step 2: Adding a Library中有一个问题:
Exercise: Why is it important that we configure TutorialConfig.h.in after the option for USE_MYMATH? What would happen if we inverted the two?
我也不知道答案,感觉没啥问题。删除顶层MakeList.txt中的option()之后,头文件中内容与USE_MYMATH=ON的时候一致。
也许,对于任意变量,默认值就是ON。因此,先处理USE_MYMATH再定义它,可以避免忘记处理而带来难以察觉的错误。 Step3:为library添加Usage Requirement 警告:个人理解,有待验证
没有特别理解这一节的意思,这里对consumers的理解可能有错误。请先读官方文档。 Usage Requirement指的是target_link_libraries()、target_include_directories()等命令中的PUBLIC、PRIVATE、INTERFACE参数。
1 target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS}) 首先,说明三个不同的Usage Requirement的使用场景:
INTERFACE:消费者需要,生产者不需要;
PUBLIC:消费者需要,生产者需要;
PRIVATE:消费者不需要,生产者需要;
其次,什么是消费者、 生产者,什么是需要?
以Step2中的代码为例:
修改MathFunctions目录下的CMakeLists.txt,添加如下命令:
1 2 3 4 add_library(MyFunctions mysqrt.cpp)target_include_directories(MyFunctions INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} ) 这里MyFunctions就是生产者,而上层的project01将是消费者,因为project01使用了MyFunctions。${CMAKE_CURRENT_SOURCE_DIR}这个目录是project01需要的,而MyFunctions不需要配置。
修改顶层的CMakeLists.txt,删除一部分命令:
1if(USE_MYMATH)2add_subdirectory(MathFunctions)3list(APPEND EXTRA_LIBS MyFunctions)4endif()56target_include_directories(project01 PUBLIC 7 &#34;${PROJECT_BINARY_DIR}&#34; 8 )这里Tutorial是生产者,没有消费者。
这两步只是删去了Tutorial的target_include_directories中MathFunctions的目录配置,下放到了下一级(MathFunctions中的cmake文件)里。
总结一下,Usage Requirements控制的是配置项的传递方式。INTERFACE会把配置项向上层(使用者、消费者)传递,而自己不保留配置项;PUBLIC会传递配置,自己也保留配置;PRIVATE不传递配置,配置只对自己生效。
Step4:安装与测试 安装installing 对于MathFunction,我们需要install的是lib文件和头文件,所以在MathFunction/CMakeLists.txt中添加命令:
1 2 install(TARGETS MyFunctions DESTINATION lib)install(FILES MathFunctions.h DESTINATION include) 对于Toturial,在顶层CMakeList.txt中添加命令:
1 2 3 4 install(TARGETS project01 DESTINATION bin)install(FILES &#34;${PROJECT_BINARY_DIR}/tutorial.h&#34; DESTINATION include ) 添加完install的规则之后,先编译项目,之后使用命令cmake --install进行安装:
1 cmake --install . --prefix &#34;/home/myuser/installdir&#34; --prefix用于指定安装目录。执行完命令,在指定的目录下将生成bin、include、lib文件夹,并包含对应的文件。
测试 修改顶层的cmake文件,添加测试命令:
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 enable_testing()# does the application run add_test(NAME Runs COMMAND project01 25)# does the usage message work? add_test(NAME Usage COMMAND project01)set_tests_properties(Usage PROPERTIES PASS_REGULAR_EXPRESSION &#34;Usage:.*number&#34; )# define a function to simplify adding tests function(do_test target arg result) add_test(NAME Comp${arg} COMMAND ${target} ${arg}) set_tests_properties(Comp${arg} PROPERTIES PASS_REGULAR_EXPRESSION ${result} )endfunction(do_test)# do a bunch of result based tests do_test(project01 4 &#34;4 is 2&#34;)do_test(project01 9 &#34;9 is 3&#34;)do_test(project01 5 &#34;5 is 2.236&#34;)do_test(project01 7 &#34;7 is 2.645&#34;)do_test(project01 25 &#34;25 is 5&#34;)do_test(project01 -25 &#34;-25 is [-nan|nan|0]&#34;)do_test(project01 0.0001 &#34;0.0001 is 0.01&#34;) enable_testing()启用测试功能。 add_test()添加测试:
line4添加了一个测试。NAME 后面是定义测试的名字,可以随便起名,但是测试名不可重复,必须是唯一标识一个测试;COMMAND后面接执行的命令以及参数。 set_tests_properties()为测试添加属性:
line8为名为 Usage的测试添加了一条属性PASS_REGULAR_EXPRESSION,以及属性值&quot;Usage:.*number&quot;。从属性的名字来看,功能是匹配正则表达式是否通过,因此对于line7添加的测试,当它的输出可以与&quot;Usage:.*number&quot;匹配时,会显示PASS。 function()以及endfunction()定义了函数:
line13开始函数定义do_test并声明了参数,line14-17是函数内容,line18结束函数定义并命名函数为 do_test,随后的line20则调用了函数 do_test。do_test函数中,添加了名为CompXX系列的测试,并将测试结果与 result参数进行正则表达式的匹配。 重新编译项目文件。
使用命令ctest -N查看将会执行哪些命令,只是列出可以执行的测试项,但不会执行测试。
ctest -VV执行测试,并且输出较为完整的测试信息(Enable more verbose output from tests.)。使用multi-config generators,比如Visual Studio,需要指定configuration,在build目录下使用ctest -C Debug -VV进行测试。
Step5:添加System Introspection 通过检查当前的平台系统,借助之前的可选参数来控制编译的配置项。
假定以下的情形:
我们的mysqrt()函数需要log、exp函数做一些事情,但是不确定系统支不支持,所以需要修改代码以及cmake的配置,以便适应不同情况。
首先,修改mysqrt()函数,添加log相关的操作,并且通过宏定义来判断系统是否支持log等函数。
1#include &lt;iostream&gt;2#include &lt;cmath&gt;3 4// a hack square root calculation using simple operations 5double mysqrt(double x) 6{ 7 if (x &lt;= 0) { 8 return 0; 9 } 10 double result = x; 11 // do ten iterations 12 for (int i = 0; i &lt; 10; ++i) { 13 if (result &lt;= 0) { 14 result = 0.1; 15 } 16 double delta = x - (result * result); 17 result = result + 0.5 * delta / result; 18 std::cout &lt;&lt; &#34;Computing sqrt of &#34; &lt;&lt; x &lt;&lt; &#34; to be &#34; &lt;&lt; result &lt;&lt; std::endl; 19 } 20 21#if defined(HAVE_LOG) &amp;&amp; defined(HAVE_EXP) 22 double result2 = exp(log(x) * 0.5); 23 std::cout &lt;&lt; &#34;Computing sqrt of &#34; &lt;&lt; x &lt;&lt; &#34; to be &#34; &lt;&lt; result 24 &lt;&lt; &#34; using log and exp&#34; &lt;&lt; std::endl; 25#else 26 double result2 = x; 27#endif 28 29 return result2; 30} 然后,借助CheckSymbolExsits模块来进行检查,若不支持则尝试在 m 库中获取这两个函数并链接。
修改MathFunctions/CMakeLists.txt,添加:
1 2 3 4 5 6 7 8 9 10 11 12 13 include(CheckSymbolExists)check_symbol_exists(log &#34;math.h&#34; HAVE_LOG)check_symbol_exists(exp &#34;math.h&#34; HAVE_EXP)if(NOT (HAVE_LOG AND HAVE_EXP)) unset(HAVE_LOG CACHE) unset(HAVE_EXP CACHE) set(CMAKE_REQUIRED_LIBRARIES &#34;m&#34;) check_symbol_exists(log &#34;math.h&#34; HAVE_LOG) check_symbol_exists(exp &#34;math.h&#34; HAVE_EXP) if(HAVE_LOG AND HAVE_EXP) target_link_libraries(MyFunctions PRIVATE m) endif()endif() 如果系统中支持,那么就添加宏定义,使代码生效:
修改MathFunctions/CMakeLists.txt,添加:
1 2 3 4 if(HAVE_LOG AND HAVE_EXP) target_compile_definitions(MathFunctions PRIVATE &#34;HAVE_LOG&#34; &#34;HAVE_EXP&#34;)endif() target_compile_definitions()为target添加编译定义,效果看起来就是mysqrt()中的#if defined(HAVE_LOG) &amp;&amp; defined(HAVE_EXP)结果为true了。这里的target,是由add_executable()oradd_library()给定的。
最后编译看看结果。
Step6:添加Custom Command,生成文件 删除*Step5*所做的修改,这次是新的需求了。
新需求:通过生成新文件向mysqrt传递数据。具体而言,Step6通过一个编译可执行程序MakeTable,并执行,在项目中生成一个Table.h文件。然后,Table.h作为源文件参与 project01 的编译。 添加MathFunctions/MakeTable.cpp,功能是向文件中写入代码:
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 // A simple program that builds a sqrt table #include &lt;cmath&gt;#include &lt;fstream&gt;#include &lt;iostream&gt; int main(int argc, char* argv[]) { // make sure we have enough arguments if (argc &lt; 2) { return 1; } std::ofstream fout(argv[1], std::ios_base::out); const bool fileOpen = fout.is_open(); if (fileOpen) { fout &lt;&lt; &#34;double sqrtTable[] = {&#34; &lt;&lt; std::endl; for (int i = 0; i &lt; 10; ++i) { fout &lt;&lt; sqrt(static_cast&lt;double&gt;(i)) &lt;&lt; &#34;,&#34; &lt;&lt; std::endl; } // close the table with a zero fout &lt;&lt; &#34;0};&#34; &lt;&lt; std::endl; fout.close(); } return fileOpen ? 0 : 1; // return 0 if wrote the file } 在MathFunctions/CMakeLists.txt中添加命令,将MakeTable.cpp编译并运行,并且这个过程是整个project01编译过程的一部分。
1 2 3 4 5 6 add_executable(MakeTable MakeTable.cpp)add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h DEPENDS MakeTable ) 修改MathFunctions/mysqrt.cpp,使用Table.h中定义的double sqrtTable[]变量:
1#include &lt;iostream&gt;2#include &#34;MathFunctions.h&#34;3 4// include the generated table 5#include &#34;Table.h&#34;6 7// a hack square root calculation using simple operations 8double mysqrt(double x) 9{ 10 if (x &lt;= 0) { 11 return 0; 12 } 13 14 // use the table to help find an initial value 15 double result = x; 16 if (x &gt;= 1 &amp;&amp; x &lt; 10) { 17 std::cout &lt;&lt; &#34;Use the table to help find an initial value &#34; &lt;&lt; std::endl; 18 result = sqrtTable[static_cast&lt;int&gt;(x)]; 19 } 20 21 // do ten iterations 22 for (int i = 0; i &lt; 10; ++i) { 23 if (result &lt;= 0) { 24 result = 0.1; 25 } 26 double delta = x - (result * result); 27 result = result + 0.5 * delta / result; 28 std::cout &lt;&lt; &#34;Computing sqrt of &#34; &lt;&lt; x &lt;&lt; &#34; to be &#34; &lt;&lt; result &lt;&lt; std::endl; 29 } 30 31 return result; 32} 此外,修改MathFunctions/CMakeLists.txt使mysqrt.cpp能找到Table.h文件。
1 2 3 4 5 6 add_library(MyFunctions mysqrt.cpp ${CMAKE_CURRENT_BINARY_DIR}/Table.h)target_include_directories(MyFunctions INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} PRIVATE ${CMAKE_CURRENT_BINARY_DIR} ) 编译项目后,可以发现多了一个Table.h,并且mysqrt执行无误。
Step7:打包可执行文件 修改顶层的CMakeLists.txt:
1 2 3 4 include(InstallRequiredSystemLibraries)set(CPACK_PACKAGE_VERSION_MAJOR &#34;${project001_VERSION_MAJOR}&#34;)set(CPACK_PACKAGE_VERSION_MINOR &#34;${project001_VERSION_MINOR}&#34;)include(CPack) 照常编译项目,使用cpack打包:
1 cpack -C Debug -G ZIP 打包后得到一个.zip文件,其中,bin目录下可以找到可执行文件。
Step8:添加Testing Dashboard 略
Step9:选择静态库或动态库 使用BUILD_SHARED_LIBS变量控制add_library()的行为。
主程序链接 MyFunctions 库, MyFunctions 链接 SqrtLibrary 库。
在顶层CMakeLists.txt中添加BUILD_SHARED_LIBS变量,使用option()命令手动控制变量的值为ON或OFF;删除USE_MYMATH变量,将它移动到MathFuntions层次下。
1cmake_minimum_required(VERSION 3.0.0)23project(project001 VERSION 0.1)45# specify the C++ standard 6set(CMAKE_CXX_STANDARD 11)7set(CMAKE_CXX_STANDARD_REQUIRED True)89option(USE_MYMATH &#34;use tutorial provided math implementation?&#34; ON)1011# 设置静态库、动态库编译输出的路径,方便之后运行可执行程序 12set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY &#34;${PROJECT_BINARY_DIR}&#34;)13set(CMAKE_LIBRARY_OUTPUT_DIRECTORY &#34;${PROJECT_BINARY_DIR}&#34;)14set(CMAKE_RUNTIME_OUTPUT_DIRECTORY &#34;${PROJECT_BINARY_DIR}&#34;)1516option(BUILD_SHARED_LIBS &#34;Build using shared libraries&#34; ON)1718configure_file(tutorial.h.in tutorial.h)19# 添加目录 20add_subdirectory(MathFunctions)2122add_executable(project01 tutorial.cpp)23# 链接MyFunctions库 24target_link_libraries(project01 PUBLIC MyFunctions)25target_include_directories(project01 PUBLIC 26 &#34;${PROJECT_BINARY_DIR}&#34; 27 ) 修改MathFunctions/CMakeLists.txt,添加SqrtLibrary库,并且使用 USE_MYMATH变量控制使用不同版本的函数:
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 # add the library that runs add_library(MyFunctions MathFunctions.cpp)# 声明链接到此库需要include的目录,以便检索头文件 target_include_directories(MyFunctions INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} )# 控制变量 option(USE_MYMATH &#34;Use tutorial provided math implementation&#34; ON)if(USE_MYMATH) # 定义宏 target_compile_definitions(MyFunctions PRIVATE &#34;USE_MYMATH&#34;) # 添加可执行程序,用来生成Table.h add_executable(MakeTable MakeTable.cpp) # 使用命令生成Table.h add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h DEPENDS MakeTable ) # 显式添加静态库 add_library(SqrtLibrary STATIC mysqrt.cpp ${CMAKE_CURRENT_BINARY_DIR}/Table.h ) # state that we depend on our binary dir to find Table.h target_include_directories(SqrtLibrary PRIVATE ${CMAKE_CURRENT_BINARY_DIR} ) target_link_libraries(MyFunctions PRIVATE SqrtLibrary)endif()# define the symbol stating we are using the declspec(dllexport) when # building on windows target_compile_definitions(MyFunctions PRIVATE &#34;EXPORTING_MYMATH&#34;)# install rules set(installable_libs MyFunctions)if(TARGET SqrtLibrary) list(APPEND installable_libs SqrtLibrary)endif()install(TARGETS ${installable_libs} DESTINATION lib)install(FILES MathFunctions.h DESTINATION include) 不同于Step2,这里使用target_compile_definitions配合if(),实现与#cmakedefine的相同的效果。
同时,修改MathFunctions目录下的内容,添加相应文件,使用命名空间区分不同库中的sqrt:
添加MathFuntions\MathFuntions.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include &#34;MathFunctions.h&#34; #include &lt;cmath&gt; #ifdef USE_MYMATH #include &#34;mysqrt.h&#34;#endif namespace mathfunctions { double sqrt(double x) { #ifdef USE_MYMATH return detail::mysqrt(x); #else return std::sqrt(x); #endif } } 添加MathFuntions\mysqrt.h:
1 2 3 4 5 6 7 namespace mathfunctions { namespace detail { double mysqrt(double x); } } 修改MathFuntions\mysqrt.cpp,补上命名空间的代码。
修改MathFuntions\MathFuntions.h,添加动态库的声明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #if defined(_WIN32) #if defined(EXPORTING_MYMATH) #define DECLSPEC __declspec(dllexport) #else #define DECLSPEC __declspec(dllimport) #endif #else // non windows #define DECLSPEC #endif namespace mathfunctions { double DECLSPEC sqrt(double x); } 改动之后,相当于由 MyFunctions 根据不同情况选择不同的sqrt(),因此修改tutorial.cpp中的内容,删掉USE_MYMATH相关的控制,使用MathFunctions\MathFunctions.h声明的函数替换掉mysqrt():
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 #include &lt;math.h&gt;#include &lt;iostream&gt;#include &lt;string&gt;#include &#34;tutorial.h&#34; #include &#34;MathFunctions.h&#34; int main(int argc, char *argv[]) { if (argc &lt; 2) { // report version std::cout &lt;&lt; argv[0] &lt;&lt; &#34; Version &#34; &lt;&lt; Tutorial_VERSION_MAJOR &lt;&lt; &#34;.&#34; &lt;&lt; Tutorial_VERSION_MINOR &lt;&lt; std::endl; std::cout &lt;&lt; &#34;Usage: &#34; &lt;&lt; argv[0] &lt;&lt; &#34; number&#34; &lt;&lt; std::endl; return 1; } // convert input to double const double inputValue = std::stod(argv[1]); const double outputValue = mathfunctions::sqrt(inputValue); std::cout &lt;&lt; &#34;The square root of &#34; &lt;&lt; inputValue &lt;&lt; &#34; is &#34; &lt;&lt; outputValue &lt;&lt; std::endl; return 0; } 编译项目,可以得到build\Debug\MyFunctions.dll文件,而 SqrtLibrary 为静态库。
Exercise We modified MathFunctions.h to use dll export defines. Using CMake documentation can you find a helper module to simplify this? Step10:生成表达式 generator expressions interface library
创建一个接口库
1 add_library(&lt;name&gt; INTERFACE [IMPORTED [GLOBAL]]) 这类库有属性,能install(), export 和 imported ,但可能没有build过程。像纯头文件库或完全针对target的设计。2
Step11:Adding Export Configuration 添加导出配置,让其他项目可以使用我们的项目。
首先,更新install(TARGETS),添加 EXPORT 。 EXPORT 关键字的作用是生成并安装一个CMake文件,用以导入安装命令所需的全部 TARGET 。
修改MathFunctions\CMakeLists.txt:
1 2 3 4 install(TARGETS ${installable_libs} DESTINATION lib EXPORT MathFunctionsTargets ) 此外,修改顶层CMakeLists.txt,显式地安装生成的.cmake文件:
1 2 3 4 install(EXPORT MathFunctionsTargets FILE MathFunctionsTargets.cmake DESTINATION lib/cmake/MathFunctions ) 此时,编译并安装项目项目:
1 2 cmake --build ./ --config Release cmake --install . --prefix &#34;../install/&#34; 在../install/lib/cmake/MathFunctions中可以查看到生成的.cmake文件。
官网提到的编译问题,我没有遇到。
其解决方法是,修改target_include_directories(),利用生成器表达式添加适当的INTERFACE路径:
1 2 3 4 5 target_include_directories(MathFunctions INTERFACE $&lt;BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}&gt; $&lt;INSTALL_INTERFACE:include&gt; ) 至此,target信息都已打包完毕。但是,我们仍然需要一些配置,使得CMake 的find_package()指令可以找到已经准备好的项目。
在项目根目录下,添加Config.cmake.in文件:
1 2 @PACKAGE_INIT@ include ( &#34;${CMAKE_CURRENT_LIST_DIR}/MathFunctionsTargets.cmake&#34; 为了正确地配置安装生成的文件,在顶层CMakeLists.txt中添加如下命令:
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 # 前几步已经配置了 install(EXPORT MathFunctionsTargets FILE MathFunctionsTargets.cmake DESTINATION lib/cmake/MathFunctions )include(CMakePackageConfigHelpers)# generate the config file that is includes the exports configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in &#34;${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake&#34; INSTALL_DESTINATION &#34;lib/cmake/example&#34; NO_SET_AND_CHECK_MACRO NO_CHECK_REQUIRED_COMPONENTS_MACRO )# generate the version file for the config file write_basic_package_version_file( &#34;${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake&#34; VERSION &#34;${Tutorial_VERSION_MAJOR}.${Tutorial_VERSION_MINOR}&#34; COMPATIBILITY AnyNewerVersion )# install the configuration file install(FILES ${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake DESTINATION lib/cmake/MathFunctions ) 至此,我们已经为项目生成了可重定位的的CMake配置。此后,只要项目已安装或打包,就可以被使用。
若希望项目可以在构建目录中被使用,则可以在项目根目录下的CMakeLists.txt末尾添加如下指令:
1 2 3 export(EXPORT MathFunctionsTargets FILE &#34;${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsTargets.cmake&#34; ) 此时,在build目录下,生成了MathFunctionsTargets.cmake,允许构建目录中的MathFunctionsConfig.cmake配置供其他项目使用,而无需install。
作者:仙人掌D 链接:https://www.jianshu.com/p/f0f71d36411a&#160;&#x21a9;&#xfe0e;
https://blog.csdn.net/LaineGates/article/details/108242803&#160;&#x21a9;&#xfe0e;
</content>
</entry>
<entry>
<title>[C&C++]Auto关键字</title>
<url>https://litmingc.github.io/post/cc++/base-autotype/</url>
<categories>
<category>C&C++</category>
</categories>
<tags>
</tags>
<content type="html"> auto关键字主要功能为类型推导。
基本用法 1 2 3 4 5 int a=10; auto x=a; // x的类型为int auto *x=&amp;a; // x的类型为int* auto x=&amp;a; // x的类型为int* auto&amp; x=a; // x的类型为int&amp; 去除or保持const语义(volatile相同) 去除const 1 2 3 const int a=10; auto x=a; // x的类型为int,可以修改x的值 此时,x是一个新的int变量。
保持const 1 2 3 const int a=10; auto&amp; x=a; // x的类型是const int&amp;,保持const,不可以修改x 此时,x是a的一个引用。&amp;x与&amp;a的值一样。
对数组做推断 指针类型
1 2 3 4 int a[3] = {1,2,3}; auto x=a; cout &lt;&lt; typeid(x).name(); // 输出类型: int * x为指针类型,指向a的首地址。
数组类型
1 2 3 4 int a[3] = {1,2,3}; auto&amp; x=a; cout &lt;&lt; typeid(x).name(); // 输出类型:int [3] x为数组类型,可以用sizeof()计算数组大小。
不使用auto声明数组的引用 int a[3] = {1,2,3};int (&amp;x)[3] = a; 连续的推导
auto的推导结果应当是前后一致的。 1 2 3 int a=10; auto *x=&amp;a, y=20; // 正确 auto *x=&amp;a, y=3.0; // 编译失败 </content>
</entry>
<entry>
<title>[C&C++]基础语法小记</title>
<url>https://litmingc.github.io/post/cc++/base/</url>
<categories>
<category>C&C++</category>
</categories>
<tags>
</tags>
<content type="html"> 一些语法 const 可以用非常量引用初始化常量引用,反之不行。 宏 宏中的特殊符号#、@、##: 1 2 3 #define Conn(x,y) x##y #define ToChar(x) #@x #define ToString(x) #x ##表示连接,int n = Conn(123,456);结果就是n=123456;。
#@表示给x加上单引号,结果返回是一个const char。
#表示加上双引号。
引用 引用vs指针:
不存在空引用 引用只能初始化为一个对象,不可更改 引用创建时必须初始化 创建引用:
1 2 int i = 17; int&amp; r = i; r 是一个初始化为 i 的整型引用
Lambda 函数与表达式 基础语法:
[capture](parameters)-&gt;return-type{body}
[capture]为捕获列表,可以是lambda函数外面的变量列表,相当于函数调用时的传参。 (parameters)等,和普通函数定义没有不同,分别是参数列表、返回类型和函数体。 函数体是必须的,其它部分可以为空或省略,例如: 1 2 3 auto f = []{return 42;} []mutable{ ++global_x; } // 无返回值 使用捕获列表传参:
捕获列表仅用于局部非static变量 变量捕获类似于参数传递,有值传递、引用传递: 1 2 3 4 5 6 7 8 9 // [capture](parameters)-&gt;return-type{body} [] // 沒有定义任何变量。使用未定义变量会引发错误。 [x, &amp;y] // x以传值方式传入(默认),y以引用方式传入。 [&amp;] // 任何被使用到的外部变量都隐式地以引用方式加以引用。 [=] // 任何被使用到的外部变量都隐式地以传值方式加以引用。 [&amp;, x] // x显式地以传值方式加以引用。其余变量以引用方式加以引用。 [=, &amp;z] // z显式地以引用方式加以引用。其余变量以传值方式加以引用。 [this]() { this-&gt;someFunc(); }(); 值捕获方式中,被捕获变量必须是可拷贝的,且被捕获变量的值是lambda创建时拷贝。
默认情况下,值捕获的变量在lambda中不会改变值。
内联函数 一个 C/C++ 程序的执行过程可以认为是多个函数之间的相互调用过程。
但是,函数调用是有时间和空间开销的。
为了消除函数调用的时空开销,C++ 提供一种提高效率的方法,即在编译时将函数调用处用函数体替换,类似于C语言中的宏展开。
🌰 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include &lt;iostream&gt;using namespace std; //内联函数,交换两个数的值 inline void swap(int *a, int *b){ int temp; temp = *a; *a = *b; *b = temp; } int main(){ int m, n; cin&gt;&gt;m&gt;&gt;n; cout&lt;&lt;m&lt;&lt;&#34;, &#34;&lt;&lt;n&lt;&lt;endl; swap(&amp;m, &amp;n); cout&lt;&lt;m&lt;&lt;&#34;, &#34;&lt;&lt;n&lt;&lt;endl; return 0; } 编译main的时代码替换为:
1 2 3 4 5 6 7 8 9 10 11 int main(){ int m, n; cin&gt;&gt;m&gt;&gt;n; cout&lt;&lt;m&lt;&lt;&#34;, &#34;&lt;&lt;n&lt;&lt;endl; int temp; temp = *(&amp;m); *(&amp;m) = *(&amp;n); *(&amp;n) = temp; cout&lt;&lt;m&lt;&lt;&#34;, &#34;&lt;&lt;n&lt;&lt;endl; return 0; }
面向对象 成员定义
1 2 3 4 5 6 7 8 9 10 11 12 13 class Box{ public: int x; void setX(int tmp); // 声明 } // 定义 void Box::setX(int tmp){ x = tmp; } int main(){ return 0; } 构造函数&amp;析构函数
构造函数:类创建对象时执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Box{ public: int x; Box(int tmp); // 声明 ~Box() } // 定义 Box::Box(int tmp){ x = tmp; } Box::~Box(void){ } int main(){ Box box(3); return 0; } 继承构造函数
1 2 3 4 子类构造函数(...):基类构造函数(...) { } 拷贝构造函数
1 2 3 classname(const classname &amp;obj){ // 构造函数主体 } 实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Box{ public: int x; Box(int tmp); // 声明 Box(const Box &amp;obj); ~Box() } // 定义 Box::Box(int tmp){ x = tmp; } Box::Box(const Box &amp;obj){ } int main(){ Box box(3); return 0; } 友元函数&amp;友元类 友元函数不是类的成员,但是可以访问类的privated、protected成员。
友元函数没有 this 指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Box{ double width; public: friend void printWidth( Box box ); void setWidth( double wid ); }; // 成员函数定义 void Box::setWidth( double wid ){ width = wid; } // 请注意:printWidth() 不是任何类的成员函数 void printWidth( Box box ){ /* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */ cout &lt;&lt; &#34;Width of box : &#34; &lt;&lt; box.width &lt;&lt;endl; } // 程序的主函数 int main( ){ Box box; // 使用成员函数设置宽度 box.setWidth(10.0); // 使用友元函数输出宽度 printWidth( box ); return 0; } 类的静态成员 我们可以使用 static 关键字来把类成员定义为静态的。当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。 静态成员函数即使在类对象不存在的情况下也能被调用。
静态成员函数有一个类范围,他们不能访问类的 this 指针。您可以使用静态成员函数来判断类的某些对象是否已被创建。
多继承 1 2 3 4 class &lt;派生类名&gt;:&lt;继承方式1&gt;&lt;基类名1&gt;,&lt;继承方式2&gt;&lt;基类名2&gt;,… { &lt;派生类类体&gt; }; 继承方式,即访问修饰符:
public继承:基类非私有成员的访问属性不变
protected继承:基类的public成员变成procteced成员
private继承:基类的public、protected成员变成private成员
多态 子类重写父类的函数,不同子类的对象调用各自的函数
虚函数
若不使用虚函数定义父类的函数,子类的同名函数无法生效。因为函数调用在程序编译期间就已经设置好了,又称静态链接、早绑定。
使用virtual修饰函数,在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定。
纯虚函数
1 2 3 4 5 6 7 8 9 10 11 12 class Shape { protected: int width, height; public: Shape( int a=0, int b=0) { width = a; height = b; } // pure virtual function virtual int area() = 0; }; c++接口 c++使用抽象类实现接口
类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。
虚函数未实现时,编译会出错。
泛型编程 c++模板是泛型编程的基础。
函数模板
形如:
template &lt;typename type&gt; ret-type func-name(parameter list){// 函数的主体}例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // function templates #include &lt;iostream&gt;using namespace std; template &lt;class T, class U&gt; bool are_equal (T a, U b) { return (a==b); } int main () { if (are_equal(10,10.0)) cout &lt;&lt; &#34;x and y are equal\n&#34;; else cout &lt;&lt; &#34;x and y are not equal\n&#34;; return 0; } 模板中除了使用T指代某种类型,还可以指定具体的实例作为参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 // template arguments #include &lt;iostream&gt;using namespace std; template &lt;class T, int N&gt; T fixed_multiply (T val) { return val * N; } int main() { std::cout &lt;&lt; fixed_multiply&lt;int,2&gt;(10) &lt;&lt; &#39;\n&#39;; // 20 std::cout &lt;&lt; fixed_multiply&lt;int,3&gt;(10) &lt;&lt; &#39;\n&#39;; // 30 } 类模板 形如:
template &lt;class type&gt; class class-name {}如何理解头文件与cpp文件的关系? 编译过程以C文件为单位,编译C文件的过程中,遇到的头文件被递归展开到C文件中。
使用任何变量、函数之前,必须要有它的声明、定义。声明是为了让编译器知道它的存在,因为编译过程以C文件为单位,所以 声明必须出现在每一个需要使用他的文件中。头文件就是为了方便地插入声明。
</content>
</entry>
<entry>
<title>[Unity]Learning Records (creat with code)</title>
<url>https://litmingc.github.io/post/unity/learning-records/</url>
<categories>
<category>Unity</category><category>学习笔记</category>
</categories>
<tags>
<tag>unity 学习</tag><tag>creat with code</tag><tag>unity 3d</tag>
</tags>
<content type="html"> 本文为跟随unity在B站的直播教学的学习记录。
教程地址
lesson 1 实现了一个小车的前进及转向控制。
提要 素材导入
在assets区域,右击鼠标。选择Import Package &ndash;&gt; Costum Package&hellip;
物体视角调整:
鼠标右键 + w、a、s、d、q、e &mdash;-&gt; wsad是前后左右,qe是升降视角;
选中物体 + Alt + 鼠标左键 &mdash;-&gt; 以选中物体为中心旋转视角;
鼠标放在Scene窗口 + f键 &mdash;-&gt; 聚焦到当前选中物体
脚本作为组件添加到物体上。
物体的Transform编辑方法:
transform.Translate();
transform.Rotate();
输入输出的获取方法:
Input.GetAxis()
设置变量
代码中的public变量可以直接在unity界面修改。
过程 略过相关安装过程。
打开unity hub,新建项目。
模板选择3D,编辑项目名,选择创建地址。
认识界面。
区域1选择各个窗口的布局,区域2为项目的资源窗口。
导入项目资源。 在assets区域,右击鼠标。选择Import Package &ndash;&gt; Costum Package&hellip;,导入官方的资源 prototype1。
导入资源之后,在Assets/Scenes/目录下出现了一个新的场景Prototype 1,把项目原先的空场景删除。
场景中添加GameObject
在hierarchy窗口添加GameObject,可以将Assets/中的对象直接拖放到Scene中,也可以拖到hierarchy窗口。
把小车(Assets/Course Library/Vehicles/Veh_Ute_Red_Z.prefab)和障碍物(Assets/Course Library/Obstacles/Crate_01.prefab)拖到场景中。
GameObject位置的调整
在Inspector窗口中的Transform标签下,Position调整三维位置(这些字,X、Y、Z等等,都是可以拖动的),或者在Scene窗口中拖动相应的箭头。
物体视角调整:
鼠标右键 + w、a、s、d、q、e &mdash;-&gt; wsad是前后左右,qe是升降视角;
选中物体 + Alt + 鼠标左键 &mdash;-&gt; 以选中物体为中心旋转视角;
鼠标放在Scene窗口 + f键 &mdash;-&gt; 聚焦到当前选中物体
摄像机的调整
三维坐标的调整与上一部分相同。
使用Rotation调整物体的旋转,X表示绕着X轴转动。
在如图所示的工具栏中选择相应的工具,在Scene窗口拖动轴线也可以达到相同的效果。
为对象添加脚本
习惯上,先创建Assets/Scripts/目录,在此目录下新建c#脚本PlyerController.cs。选中小车,把脚本PlyerController.cs拖到Inspector窗口中,或者使用Inspector窗口最下面的Add Component功能添加脚本为组件。
之后,双击脚本可打开编辑器编辑脚本代码。
设置编辑器:
菜单栏选择Edit&ndash;&gt;Preferences&ndash;&gt;External Tools,在External Script Editor中选择相应的编辑器。
使用visual studio的话,需要在visual studio安装unity的相应拓展。若不安装拓展,打开代码时会出现不兼容的问题。 使用vscode,也是一样的需要unity的相应插件(C#、Debugger for Unity、Unity Tool、Unity Code Snippets)
写代码了
小车的脚本代码如下:
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 using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerController : MonoBehaviour { //前进速度 public float speed; // 使用public则在unity编辑器中可以直观地设置speed值 //转向速度 public float turnspeed; //获取输入参数 public float verticalInput; public float horizontalInput; // Update is called once per frame void Update() { //获取输入 verticalInput = Input.GetAxis(&#34;Vertical&#34;); // 取值为[-1,1] horizontalInput = Input.GetAxis(&#34;Horizontal&#34;); //前进、后退 transform.Translate(Vector3.forward * Time.deltaTime * speed * verticalInput); //转向 transform.Rotate(Vector3.up,Time.deltaTime * turnspeed * horizontalInput); // 绕着y轴旋转 } } 类中的public参数,在物体的Inspector窗口的脚本组件下可以直接设置它的值。
Input.GetAxis()获取输入,参数类型为string,取值根据unity的设置填写。unity中选择Edit&ndash;&gt;Project Setting&ndash;&gt;Input Manager查看unity中的输入方式的名称。按键未按下时,其值为0;按下时,取值范围为【-1,1】。 Time.deltaTime避免不同平台上时间计量的差异 transform.Translate(),transform.Rotate()控制物体的Transform属性对应的值。 创建另外一个脚本CameraFollow.cs添加到Main Camera中,控制摄像头跟随小车。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 using System.Collections; using System.Collections.Generic; using UnityEngine; public class CameraFollow : MonoBehaviour { public GameObject player; private Vector3 offset = new Vector3(0, 3.02f, -12.39f); // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { transform.position = player.transform.position + offset; } } 设置Transform使得摄像头与小车保持相对位置。
将player设置为小车:
在unity界面中,将小车拖到public GameObject player;的位置,为其设值为小车。
添加物理效果 为障碍物以及小车添加刚体,添加重力。 lesson 2 射击&amp;消除
过程 新建3D项目,导入素材【Prototype_2】
向场景中添加物体,三只动物、一名农夫、一个食物。
调整动物的面向(transform.rotation),面向农夫。
为动物、食物添加碰撞体,为食物添加刚体(实现动物与食物碰撞相消的前提,两者都必须有碰撞体,两者至少有一个有刚体)。同时,调整碰撞体至合适大小,取消食物刚体的重力效果(以防向下掉出地面)。
将动物、食物添加为Prefab(预制建筑、预制体)
新建目录Assets/Prefab,把动物、食物拖到目录中,并选择original prefab。
为物体添加脚本
注意:编辑场景中的物体并不会改变预制体,所以这里需要编辑Asset中的预制体;也可以先编好脚本再添加预制体。总之,预制体和已添加的物体不是同步的。
为食物和动物添加向前移动的脚本,并且,碰撞时销毁物体。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 using System.Collections; using System.Collections.Generic; using UnityEngine; public class MoveForward : MonoBehaviour { public float speed = 40f; // Update is called once per frame void Update() { transform.Translate(Vector3.forward * speed * Time.deltaTime); if(transform.position.z &gt; 40f || transform.position.z &lt; -4f) Destroy(gameObject); } private void OnCollisionEnter(Collision other) { Destroy(gameObject); } } public变量,可在unity为不同的动物、食物设置不同速度;
transform.Translate(Vector3.forward * speed * Time.deltaTime);沿着向前的方向移动,即沿Z轴移动;
if(transform.position.z &gt; 40f || transform.position.z &lt; -4f) Destroy(gameObject);物体移动到上下边界以外的地方时,销毁自身(Z轴方向的边界,即视角的上下边界)。
private void OnCollisionEnter(Collision other){},这个方法发生碰撞时触发,参数other为碰撞的另一方。
为农夫创建移动脚本,并设置发射食物的功能。代码如下:
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 using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerController : MonoBehaviour { public float speed = 10f; private float HorizontalInput; public float HRange=16f; public GameObject ShootObjects; // Update is called once per frame void Update() { // HorizontalInput = Input.GetAxis(&#34;Horizontal&#34;); HorizontalInput = Input.GetAxisRaw(&#34;Horizontal&#34;); transform.Translate(Vector3.right * speed * Time.deltaTime * HorizontalInput); // 限制移动范围 if(transform.position.x &gt; HRange) transform.position = new Vector3(HRange,0,0); if(transform.position.x &lt; -HRange) transform.position = new Vector3(-HRange,0,0); // 空格按键触发 // 射击:生成新物体 if(Input.GetKeyDown(KeyCode.Space)) Instantiate(ShootObjects, transform.position, ShootObjects.transform.rotation); } } Input.GetAxisRaw(&quot;Horizontal&quot;),与Input.GetAxis()不同。GetAxisRaw在按键按下时,取值为{-1,1},它的值直接是1或者-1;而GetAxis则是渐变的,从0慢慢变到1或-1。因此使用GetAxisRaw时,农夫移动的反应更及时。
Instantiate(ShootObjects, transform.position, ShootObjects.transform.rotation);,生成新物体,有多个重载方法。此处表示,在农夫的位置,以ShootObjects本身的面向,生成ShootObjects。
在Unity中,将食物的预制体拖到变量ShootObjects中完成赋值。
随机生成动物
先添加一个空的GameObject:在Hierarchy窗口的空白处右击,选择Create Empty,并命名为随便你(SpawnManager)。
为SpawnManager添加一个同名脚本,用来生成动物。代码如下:
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 using System.Collections; using System.Collections.Generic; using UnityEngine; public class SpawnManager : MonoBehaviour { public GameObject[] animals; private int index=0; //上下左右 public float[] Range={0f,0f,0f,0f}; // Start is called before the first frame update void Start() { InvokeRepeating(nameof(SpawnAnimals), 2, 2); } // Update is called once per frame void Update() { } void SpawnAnimals(){ float xpos = Random.Range(Range[2],Range[3]); index = Random.Range(0,animals.Length); Instantiate(animals[index], new Vector3(xpos, 0f, Range[0]), animals[index].transform.rotation); } } public GameObject[] animals;,设置动物数组,在Unity中拖动动物预制体到变量上设置它的值。由于是数组,先编辑一下它的大小为3,再把不同动物的预制体拖到各个元素上。
void SpawnAnimals()方法,实现随机选择动物种类(index)、随机生成刷新位置(X轴随机,Z轴为上边界,Y轴为地面坐标)。生成的方法和发射物ShootObjects一样,生成的旋转角度(rotation),也就是动物的面向,使用预制体本身的面向。
注意设置好边界数组public float[] Range={0f,0f,0f,0f};,依次为上下左右。
把场景里的杂物清理一下,只留下农夫。可以开始游戏了。
challenge 2 在 lesson 2 的基础上,导入有错误的新场景,将它修改完善。
原效果:
完成效果:
需要修改的内容 人物应当发射狗,随机掉落的物体应当是球。
脚本变量的赋值搞反了。 球落地消失,狗跑远了应当消失,狗、球相遇要消失。
DestroyOutOfBoundsX脚本中的判断条件,以及边界大小做相应的更改。transform.position的数值是以全局的坐标值,个体的坐标轴用于判断个体的面向(前后左右上下,Z轴表示前后)。 球落地要显示“Game Over!”。
实现方式多种多样。
此处借助已存在的private void OnTriggerEnter(){}方法实现触发。
为地面设置碰撞体,并设置Tag为“Finish”。
修改脚本DetectCollisionsX的内容,如下: 1 2 3 4 5 6 7 private void OnTriggerEnter(Collider other) { if(other.CompareTag(&#34;Finish&#34;)) Debug.Log(&#34;Game Over!&#34;); else Destroy(gameObject); } Unity文档中,关于OnTriggerEnter方法,有如下描述:
Note: Both GameObjects must contain a Collider component. One must have Collider.isTrigger enabled, and contain a Rigidbody. If both GameObjects have Collider.isTrigger enabled, no collision happens. The same applies when both GameObjects do not have a Rigidbody component.
触发OnTriggerEnter的条件,需要碰撞的双方具有碰撞体,至少有一方具有刚体且勾选Is Trigger。
启用Is Trigger代表此刚体有形无实,不会碰撞,所以如果双方都启用Is Trigger,那么不会碰撞,但会触发OnTriggerEnter方法。如果双方都没有Is Trigger,且有一方具有刚体,则碰撞。
随机落下不同种类的球体 lesson 3 一个跑酷游戏。
提要 GetCompontes()顺序的问题?
过程 添加人物为player,控制其跳跃;不断生成的障碍物,向player移动,超出视线范围时销毁自身。
效果如下:
需要注意,人物跳跃时,不能触发二段跳。
为障碍物添加碰撞体,为人物添加碰撞体、添加刚体组件使其具有物体特性,使得人物与障碍物可以产生碰撞。
障碍物的生成与移动的代码和lesson1几乎一致,现给出人物脚本如下:
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 using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerController : MonoBehaviour { public float jumpForce; public float gravityModifier; private bool isGround; public bool isGameOver; // 需要public,后面要用 private Rigidbody rb; private Animator anim; // Start is called before the first frame update void Start() { rb = GetComponent&lt;Rigidbody&gt;(); anim = GetComponent&lt;Animator&gt;(); Physics.gravity *= gravityModifier; } // Update is called once per frame void Update() { if(Input.GetKeyDown(KeyCode.Space) &amp;&amp; isGround){ rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse); isGround = false; } } private void OnCollisionEnter(Collision other) { if(other.gameObject.CompareTag(&#34;Ground&#34;)) isGround = true; } } 跳跃的实现方法:
跳跃时对人物添加向上的力。首先获取刚体组件,定义刚体组件为private Rigidbody rb;,获取刚体组件rb = GetComponent&lt;Rigidbody&gt;();。然后,触发按键时施加冲击力rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);(若不使用ForceMode.Impulse冲击,则只有一帧受力,跳不起来)。此外,Physics.gravity *= gravityModifier;修改重力加速度g,影响人物下落速度(参考数值:jumpForce=30;gravityModifier=10)。
为了避免碰撞时,人物倒下、面向改变的问题,在刚体中,锁定其旋转角度。如图:
移动背景,使背景与障碍物保持相对静止。
在背景上,添加脚本(MoveLeft.cs,障碍物也使用这一脚本)控制其移动。与障碍物不同的是,背景是一个重复的画面。因此,背景移动策略是移动半个背景长度时,背景位置复原。
为了避免背景消失,在MoveLeft.cs中,销毁物体前判断一下是否为障碍物。或者,使用另一个脚本(DestroyObstcale.cs)来实现销毁,并添加给障碍物。
为了背景可以复位,新建脚本组件BgRepeat.cs控制其复位,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class BgRepeat : MonoBehaviour { BoxCollider rb; float repeatWidth; // 一半碰撞体长度(X轴) Vector3 startPos; // 记录起始位置 // Start is called before the first frame update void Start() { rb = GetComponent&lt;BoxCollider&gt;(); repeatWidth = rb.size.x / 2f; startPos = transform.position; } // Update is called once per frame void Update() { if(transform.position.x &lt; startPos.x - repeatWidth) transform.SetPositionAndRotation(startPos, transform.rotation); } } 效果如下:
地面是纯色,不需要移动。
为人物添加跑动、跳跃动画。
选中人物,双击Animator组件中的Controller,如图:
在打开的动画界面中,选择Layers&ndash;&gt;Movement。选中“Run_Static”,右击,将其设置为default state(Entry指向它)。如图:
同时,选中Parameters,设置Speed_f的值为1.0(大于0.5)。如图:
此时,运行程序,可以看到人物跑动起来了。(如果人物跑动的速度与环境格格不入,可以适当调整动画播放速度)
为什么设置Speed_f大于0.5? 选中Run_Static指向Walk_Static的状态,可以看到Conditions是Speed_f小于0.5,那么只要Speed_f大于0.5,动画就不会转变为走路。你还可以看一下其他的变化条件,它们之间都是不会冲突的。 查看一下Layers中的Jumping的情况,发现从Run到Running_Jump变化的条件是Jump_trig,且在Parameters中查到Jump_trig是一个触发变量。接下来,使用脚本(PlayController)控制Jump_trig,在跳跃的条件中加入Jump_trig的触发:
1 2 3 4 5 if(Input.GetKeyDown(KeyCode.Space) &amp;&amp; isGround){ rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse); isGround = false; anim.SetTrigger(&#34;Jump_trig&#34;); } 至此,人物跳跃时可以表现出跳跃的动作。但是,在跳跃时,人物的位置会向前进,因此,我们可以选择在刚体中锁定position的x值。
实现效果如图:
人物碰撞到阻碍物时,播放死亡动画,同时环境不再移动,且障碍物不再生成。 播放死亡动画与跳跃异曲同工。先查看其条件,然后使用脚本设置条件(Death_b为true,DeathType_int为1):
PlayerController.cs:
1 2 3 4 5 6 if(other.gameObject.CompareTag(&#34;Obstacle&#34;)){ isGameOver = true; Debug.Log(&#34;GAME OVER!&#34;); anim.SetBool(&#34;Death_b&#34;, true); anim.SetInteger(&#34;DeathType_int&#34;, 1); } MoveLeft.cs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 using System.Collections; using System.Collections.Generic; using UnityEngine; public class MoveLeft : MonoBehaviour { public float speed=5f; private PlayerController playerController; // Start is called before the first frame update void Start() { playerController = FindObjectOfType&lt;PlayerController&gt;(); } // Update is called once per frame void Update() { if(!playerController.isGameOver) transform.Translate(Vector3.left * speed * Time.deltaTime); } } SpawnObstacles.cs:
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 using System.Collections; using System.Collections.Generic; using UnityEngine; public class SpawnObstacles : MonoBehaviour { private PlayerController playerController; public GameObject[] obstacles; int index; public Vector3 spawnPosition; // Start is called before the first frame update void Start() { InvokeRepeating(nameof(Spawn), 2f, 2f); playerController = FindObjectOfType&lt;PlayerController&gt;(); } // Update is called once per frame void Update() { } void Spawn() { if (!playerController.isGameOver) { index = Random.Range(0, obstacles.Length); Instantiate(obstacles[index], spawnPosition, obstacles[index].transform.rotation); } } } 效果如图:
添加粒子特效。 效果如下:
在Assets/Course Library/Particles中有俩个粒子特效,一个是灰尘的粒子,另一个是爆炸的粒子。
首先,把粒子拖到Player上,作为人物的子集:
然后用代码控制粒子的播放。灰尘粒子本身就设置了循环播放,所以要控制其跳跃时停止播放、死亡时不再播放;爆炸粒子不是循环播放的,并且在游戏开始时没有播放,所以用脚本控制其死亡时播放。代码如下,注意在Unity中对粒子变量赋值:
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 using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerController : MonoBehaviour { private Rigidbody rb; private Animator anim; public ParticleSystem dirtParticle, explosionParticle; public float jumpForce; public float gravityModifier; public bool isGround; public bool isGameOver; // Start is called before the first frame update void Start() { rb = GetComponent&lt;Rigidbody&gt;(); anim = GetComponent&lt;Animator&gt;(); Physics.gravity *= gravityModifier; } // Update is called once per frame void Update() { if(Input.GetKeyDown(KeyCode.Space) &amp;&amp; isGround){ rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse); isGround = false; anim.SetTrigger(&#34;Jump_trig&#34;); dirtParticle.Stop(); } } private void OnCollisionEnter(Collision other) { // isGameOver的判断,避免死亡且与地面碰撞时的粒子播放 if(other.gameObject.CompareTag(&#34;Ground&#34;) &amp;&amp; (!isGameOver)){ isGround = true; dirtParticle.Play(); } if(other.gameObject.CompareTag(&#34;Obstacle&#34;)){ isGameOver = true; Debug.Log(&#34;GAME OVER!&#34;); anim.SetBool(&#34;Death_b&#34;, true); anim.SetInteger(&#34;DeathType_int&#34;, 1); explosionParticle.Play(); dirtParticle.Stop(); } } } 添加音效。
在MainCamera中有AudioListener用于收声,相当于你的耳朵。
首先,在MainCamera中添加AudioSource并设置AudioClip作为音源。背景音要设置开始播放,设置循环播放:
其次,在Player中添加AudioSource,但不要设置AudioClip,并且开始时不要播放,不要设置循环播放。在脚本中设置2个AudioClip变量,赋值碰撞以及跳跃的音效,并控制啥时候播放哪个AudioClip:
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 using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerController : MonoBehaviour { private Rigidbody rb; private Animator anim; private AudioSource audioSource; public ParticleSystem dirtParticle, explosionParticle; public AudioClip crashClip, jumpClip; public float jumpForce; public float gravityModifier; public bool isGround; public bool isGameOver; // Start is called before the first frame update void Start() { rb = GetComponent&lt;Rigidbody&gt;(); anim = GetComponent&lt;Animator&gt;(); Physics.gravity *= gravityModifier; audioSource = GetComponent&lt;AudioSource&gt;(); } // Update is called once per frame void Update() { if(Input.GetKeyDown(KeyCode.Space) &amp;&amp; isGround &amp;&amp; (!isGameOver)){ rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse); isGround = false; anim.SetTrigger(&#34;Jump_trig&#34;); dirtParticle.Stop(); audioSource.PlayOneShot(jumpClip, 0.5f); } } private void OnCollisionEnter(Collision other) { // isGameOver的判断,避免死亡且与地面碰撞时的粒子播放 if(other.gameObject.CompareTag(&#34;Ground&#34;) &amp;&amp; (!isGameOver)){ isGround = true; dirtParticle.Play(); } if(other.gameObject.CompareTag(&#34;Obstacle&#34;)){ isGameOver = true; audioSource.PlayOneShot(crashClip, 1f); Debug.Log(&#34;GAME OVER!&#34;); anim.SetBool(&#34;Death_b&#34;, true); anim.SetInteger(&#34;DeathType_int&#34;, 1); explosionParticle.Play(); dirtParticle.Stop(); } } } audioSource.PlayOneShot(jumpClip, 0.5f);表示以0.5的音量播放jumpClip。
</content>
</entry>
<entry>
<title>[Translation]Whoosh 2.7.4 Documentation</title>
<url>https://litmingc.github.io/post/translation/whoosh/</url>
<categories>
<category>Translation</category><category>Python</category>
</categories>
<tags>
<tag>whoosh</tag><tag>文档检索</tag>
</tags>
<content type="html"> 英语小白,【原文地址】。store不知道怎么翻译合适,存储?
Quick start A quick introduction The Index and Schema objects The IndexWriter object The Searcher object Designing a schema About schemas and fields Built-in field types Creating a Schema Modifying the schema after indexing Dynamic fields Advanced schema setup Field boosts Field types Formats Vectors How to index documents Creating an Index object Clearing the index Indexing documents Indexing and storing different values for the same field Finishing adding documents Merging segments Deleting documents Updating documents Incremental indexing Clearing the index How to search The Searcher object Quick start whoosh是一个为文本建立索引并搜索这个索引的方法库和类库。它允许你针对自己的内容开发自定义的搜索引擎。比如,如果你正在建立一个博客写作软件,你就可以使用Whoosh添加搜索方法,让用户搜索博文。
A quick introduction 1 2 3 4 5 6 7 8 9 10 11 12 13 from whoosh.index import create_in from whoosh.fields import * schema = Schema(title=TEXT(stored=True), path=ID(stored=True), content=TEXT) ix = create_in(&#34;indexdir&#34;, schema) writer = ix.writer() writer.add_document(title=u&#34;First document&#34;, path=u&#34;/a&#34;,content=u&#34;This is the first document we&#39;ve added!&#34;) writer.add_document(title=u&#34;Second document&#34;, path=u&#34;/b&#34;,content=u&#34;The second one is even more interesting!&#34;) writer.commit() from whoosh.qparser import QueryParser with ix.searcher() as searcher: query = QueryParser(&#34;content&#34;, ix.schema).parse(&#34;first&#34;) results = searcher.search(query) print(results[0]) 输出:
{&quot;title&quot;: u&quot;First document&quot;, &quot;path&quot;: u&quot;/a&quot;}
The Index and Schema objects 首先,使用whoosh前,你需要有一个索引(index)对象;
其次,使用index前,你必须定义好index的schema(结构模式)。
schema列出了index中的字段(field)。一个字段(field)就是索引中记录的文档的一项信息。field能够被索引(indexed)(可以搜索field)、被存储(stored)(被索引的field的值会和结果一起返回;在标题等字段中,这一点很有用)。
下面的schema示例中有两个字段,“title”和“content”:
1 2 from whoosh.fields import Schema, TEXT schema = Schema(title=TEXT, content=TEXT) 创建index时,你只需要创建一个schema。这个schema和index深度绑定。
创建schema对象时,你使用关键字参数来把字段名和字段类型绑定在一起。字段列表以及他们的类型决定了“哪些会被索引,哪些能被检索”。Whoosh自带了一些非常有用的字段类型,并且你也可以轻易的定义自己的类型。
whoosh.fields.ID
这种类型简单地把整个字段的值索引成一个单元(a single unit,也就是说,它不会把字段的值切分成单独的词语),同时也可以选择存储这个值。这在诸如文件路径、URL、日期以及分类等字段中很有用。 whoosh.fields.STORED
使用这个类型的字段会被储存为文档,但是不会为它建立索引。这个字段类型不会被索引,也不可搜索。它可以用在一些文档信息上,而你只是想把这些文档信息呈现在搜索结果中。 whoosh.fields.KEYWORD
这个类型是为那些用空格和逗号分隔的关键字设计的,可以被索引、被搜索,也可以选择将其存储。为了节省空间,这个类型的字段的搜索并不允许部分匹配(phrase searching)。 whoosh.fields.TEXT
这是用于正文的字段类型。它为文本建立索引(可选择存储与否)并且储存词语的位置用于片言搜索(phrase searching) whoosh.fields.NUMERIC
用于数字,可以存储整型和浮点数。 whoosh.fields.BOOLEAN
用于布尔值(true,false) whoosh.fields.DATETIME
用于datatime对象。更多信息参见Indexing and parsing dates/times whoosh.fields.NGRAM 和 whoosh.fields.NGRAMWORDS
这个类型把字段的文本以及单个的术语打散,将它们组成N元组(N-grams)。更多信息参见Indexing and searching N-grams (如果你不需要向字段类型传递参数,那么只要给出类名就可以了。Whoosh会帮你实例化。)
1 2 3 from whoosh.fields import Schema, STORED, ID, KEYWORD, TEXT schema = Schema(title=TEXT(stored=True), content=TEXT, path=ID(stored=True), tags=KEYWORD, icon=STORED) 更多信息参见Designing a schema
一旦你有了schema,你就可以使用create_in方法创建index:
1 2 3 4 5 6 import os.path from whoosh.index import create_in if not os.path.exists(&#34;index&#34;): os.mkdir(&#34;index&#34;) ix = create_in(&#34;index&#34;, schema) (这一步创建了一个包含索引&quot;index&quot;的存储对象。Storage对象是将被储存的index的中间件。通常,存储对象会是FileStorage对象,将index存储为一组文件,存储到某个文件夹中。)
在创建了一个index之后,你可以使用open_dir方法,很方便的打开它:
1 2 from whoosh.index import open_dir ix = open_dir(&#34;index&#34;) The IndexWriter object 好的,既然我们已经有的index对象了,那现在就可以添加文档了。index对象的writer()方法会返回一个IndexWriter对象,可以用来将文档添加进index。IndexWriter对象的add_document(**kwargs)方法通过传入的关键字参数,将字段名映射到具体的值。
1 2 3 4 5 6 7 8 writer = ix.writer() writer.add_document(title=u&#34;My document&#34;, content=u&#34;This is my document!&#34;, path=u&#34;/a&#34;, tags=u&#34;first short&#34;, icon=u&#34;/icons/star.png&#34;) writer.add_document(title=u&#34;Second try&#34;, content=u&#34;This is the second example.&#34;, path=u&#34;/b&#34;, tags=u&#34;second short&#34;, icon=u&#34;/icons/sheep.png&#34;) writer.add_document(title=u&#34;Third time&#39;s the charm&#34;, content=u&#34;Examples are many.&#34;, path=u&#34;/c&#34;, tags=u&#34;short&#34;, icon=u&#34;/icons/book.png&#34;) writer.commit() 其中,有两点需要注意:
你不一定要为所有的字段赋值。Whoosh根本不在乎。 建立索引的字段的文本参数必须是以unicode编码的字符串。那些只存储不建立索引的字段(STORED类型)可以传入任何编码的对象。 如果你有一个文本字段,既要建立索引,也要被存储,那么你可以为unicode编码值建立索引,然后再用下面展示的技巧存储一个不同的对象(如果必要的话,但是通常没必要。 又 但是,有时候,这真的很有用):
1 writer.add_document(title=u&#34;Title to be indexed&#34;, _stored_title=u&#34;Stored title&#34;) 调用IndexWriter的commit()来提交那些添加到index中的文档:
1 writer.commit() 更多信息参见How to index documents
一旦文档被提交到index中,那你就可以搜索它们了。
The Searcher object 为了检索index,我们需要一个Searcher对象:
1 searcher = ix.searcher() 你通常会想要通过with语句打开检索器(Searcher)。这样,当你使用完检索器,它就会自动关闭了(Searcher对象表示很多打开的文件。所以,如果你没有显式地关闭他们,而且系统收集他们的速度也慢的话,你可能会花光文件句柄)。
1 2 with ix.searcher() as searcher: pass 等价于:
1 2 3 4 5 try: searcher = ix.searcher() pass finally: searcher.close() Searcher对象的search()方法需要一个查询(Query)对象。你可以直接构造query对象,也可以使用query分析器分析query字符串。
比如,下面的查询对象将会匹配在“content”字段中同时包含“apple”和“bear”的文档:
1 2 3 4 # Construct query objects directly from whoosh.query import * myquery = And([Term(&#34;content&#34;, u&#34;apple&#34;), Term(&#34;content&#34;, &#34;bear&#34;)]) 此外,你也可以使用qparser模块中默认的查询分析器去分析查询字符串。QueryParser构造器的第一个参数是默认的搜索字段,它通常传入“文本主体”所在的字段。第二个可选参数是一个schema,用来让分析器理解如何分析字段:
1 2 3 4 5 # Parse a query string from whoosh.qparser import QueryParser parser = QueryParser(&#34;content&#34;, ix.schema) myquery = parser.parse(querystring) 只要你有了Searcher和query对象,你就可以使用Searcher的search()方法来执行查询,获得Results对象:
1 2 3 4 5 &gt;&gt;&gt; results = searcher.search(myquery) &gt;&gt;&gt; print(len(results)) 1 &gt;&gt;&gt; print(results[0]) {&#34;title&#34;: &#34;Second try&#34;, &#34;path&#34;: &#34;/b&#34;, &#34;icon&#34;: &#34;/icons/sheep.png&#34;} 默认的QueryParser实现查询语言的方式和Lucene中的非常相似。它允许使用AND或者OR联系语句,使用NOT排除语句,把语句组合成带括号的子句,进行范围、前缀以及通配符查询,并指定不同字段去搜索。默认情况下,它使用AND组合查询子句(所以,默认情况下,你所指定的全部搜索词必须全在文档中,文档才能被匹配):
1 2 3 4 5 6 7 8 &gt;&gt;&gt; print(parser.parse(u&#34;render shade animate&#34;)) And([Term(&#34;content&#34;, &#34;render&#34;), Term(&#34;content&#34;, &#34;shade&#34;), Term(&#34;content&#34;, &#34;animate&#34;)]) &gt;&gt;&gt; print(parser.parse(u&#34;render OR (title:shade keyword:animate)&#34;)) Or([Term(&#34;content&#34;, &#34;render&#34;), And([Term(&#34;title&#34;, &#34;shade&#34;), Term(&#34;keyword&#34;, &#34;animate&#34;)])]) &gt;&gt;&gt; print(parser.parse(u&#34;rend*&#34;)) Prefix(&#34;content&#34;, &#34;rend&#34;) Whoosh还有额外的功能处理检索结果,比如:
以索引字段的值排序,而不是相关程度 在原文的节选中,高亮搜索字段 基于检索到的前几个文档,扩展搜索选项 分页显示结果(例如,“显示结果 1-20 ,页面1 of 4”)
更多内容参见How to search Designing a schema About schemas and fields schema指定了文档在索引中的字段。
每一篇文档都可以有多个字段,比如title、content、url、date等等。
某些字段可以被索引。某些字段可以和文档一起存储,用于在检索的结果中显示这些字段的值。某些字段既可以被索引,也可以被存储。
schema是文档中可能出现的字段的集合,每一篇文档都可能仅仅使用它的子集。
比如,一个用于索引邮件的schame可能的字段有from_addr、 to_addr、subject、body以及attachments,attachments字段可能以列表形式列出了邮件的附件名。对于没有附件的邮件,你就可以忽略这个字段。
Built-in field types Whoosh提供了一些实用的预设字段类型:
whoosh.fields.TEXT
这个类型用于文本主体。它为文本建立索引,并且记录词语的位置以便短语检索;亦可增选存储起来。
TEXT字段默认使用StandarAnalyzer进行分词。要指定不同的分词器,你可以为字段的构造器使用analyzer参数,比如TEXT(analyzer=analysis.StemmingAnalyzer())。详情见See About analyzers
TEXT字段默认储存词语的位置,用于短语检索。如果你不需要这个功能,你可以关闭它以节省存储空间。使用TEXT(phrase=False)关闭此功能。 TEXT字段默认不存储字段的值。通常,你不会希望在索引中储存文档的正文。因为一般情况下,你可以从检索结果中直接读取或者链接到文档本身,所以无需在索引中存储文本。但是,在某些情况下,储存字段的值会是实用的,比如How to create highlighted search result excerpts。使用TEXT(stored=True)将文本存储到索引中。 whoosh.fields.KEYWORD
这个类型是设计给以空格、逗号分隔的关键词的。此类型可索引,也可存储展示。为了节省空间,它不支持短语检索。
在构造器中使用stored=True存储展示字段的值;使用lowercase=True将在索引建立前自动小写关键字。
默认情况下,它认为关键字是以空格为分割符的,可设置commas=True使用逗号替代空格成为分隔符(注意,此时即允许关键字中含有空格)。
如果你的使用者将使用关键字字段进行检索,请设置scorable=True。 whoosh.fields.ID
ID类型简单地将字段的整个值当作一个单元(不会将字段分隔成一个一个词语)进行索引(and optionally stores)。这个类型不会存储词频,因此它完成得很快,但对于文档相关度的计算就没啥用了。
ID类型一般用于URL、文件路径、日期、分类等字段,这些字段通常需要当成整体来对待,并且一个文档只有一个值。
默认情况下,ID类型也不被存储展示。设置ID(stored=true),使指定的字段可以被存储,用于在检索结果中展示。比如,你可以存储URL的值,以便在检索结果中提供文档的链接。 whoosh.fields.STORED
这个类型用于存储文档,但是不能建立索引也不能检索,主要用在你希望在检索结果中展示,却不必检索得到的文档信息上。 whoosh.fields.NUMERIC
用紧凑、可排序得形式存储int、long、浮点数。 whoosh.fields.DATETIME
用紧凑、可排序得形式存储日期对象。 whoosh.fields.BOOLEAN
用于索引布尔值。用户可以使用yes no true false 1 0 t f检索到这个类型的字段。 whoosh.fields.NGRAM
TBD. Creating a Schema 举个栗子:
1 2 3 4 5 6 7 8 from whoosh.fields import Schema, TEXT, KEYWORD, ID, STORED from whoosh.analysis import StemmingAnalyzer schema = Schema(from_addr=ID(stored=True), to_addr=ID(stored=True), subject=TEXT(stored=True), body=TEXT(analyzer=StemmingAnalyzer()), tags=KEYWORD) 如果不需要为原生的字段类型构造器提供参数,可以省略()(比如,用fieldname=TEXT代替fieldname=TEXT())。whoosh会帮你实例化。
此外,你还可以使用SchemaClass基础类来声明一个Schema:
1 2 3 4 5 6 7 from whoosh.fields import SchemaClass, TEXT, KEYWORD, ID, STORED class MySchema(SchemaClass): path = ID(stored=True) title = TEXT(stored=True) content = TEXT tags = KEYWORD 你可以将上述声明类当参数传给方法create_in()或者create_index(),而不必使用Schema实例。
Modifying the schema after indexing 在已经建立索引之后,你可以使用add_field()和remove_field()增减字段。这两个方法是writer对象的成员方法:
1 2 3 4 writer = ix.writer() writer.add_field(&#34;fieldname&#34;, fields.TEXT(stored=True)) writer.remove_field(&#34;content&#34;) writer.commit() (如果增减字段和添加文档使用的是同一个writer,你必须确保在添加文档前调用add_field()以及remove_field()。)
在index对象中,这些方法使用起来也很方便:
1 ix.add_field(&#34;fieldname&#34;, fields.KEYWORD) 但是,当你这么用的时候,index对象实际上是创建了writer,并调用了对应的函数再提交(commit)的。所以,若是你想增减多个字段,使用writer会更有效率。
在filedb后台,移除一个字段仅仅对schema做了修改,index并不会变小,index有关那个字段的数据仍然存在,除非你commit的时候使用了optimize(优化)。优化可以重构索引,移除相应字段的引用。示例如下:
1 2 3 4 writer = ix.writer() writer.add_field(&#34;uuid&#34;, fields.ID(stored=True)) writer.remove_field(&#34;path&#34;) writer.commit(optimize=True) 因为数据是和字段名一起存储在磁盘上的,所以不要在移除字段且不设置optimize的情况下,添加同名的字段:
1 2 3 4 writer = ix.writer() writer.delete_field(&#34;path&#34;) # Don&#39;t do this!!! writer.add_field(&#34;path&#34;, fields.KEYWORD) (未来,whoosh也许会自动避免这个错误。)
Dynamic fields 动态字段就是把类型与一组字段联系起来,这些字段能够被给定的“通配符”匹配(通配符(glob)指一种命名模板,包含*、?以及[abc]通配符)。
你可以使用带glob关键字的add()方法添加动态字段(设置glob=True:
1 2 3 4 schema = fields.Schema(...) # Any name ending in &#34;_d&#34; will be treated as a stored # DATETIME field schema.add(&#34;*_d&#34;, fields.DATETIME(stored=True), glob=True) 和添加普通的字段一样,你可以使用IndexWriter.add_field()添加动态字段。但是,需要设置glob=True:
1 2 3 writer = ix.writer() writer.add_field(&#34;*_d&#34;, fields.DATETIME(stored=True), glob=True) writer.commit() 移除动态字段时,使用IndexWriter.remove_field(),并给出“统配符字段名”:
1 2 3 writer = ix.writer() writer.remove_field(&#34;*_d&#34;) writer.commit() 下面是一个示例,展示了如何允许文档包含任何以_id结尾的字段名,并指定其为ID类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 schema = fields.Schema(path=fields.ID) schema.add(&#34;*_id&#34;, fields.ID, glob=True) ix = index.create_in(&#34;myindex&#34;, schema) w = ix.writer() w.add_document(path=u&#34;/a&#34;, test_id=u&#34;alfa&#34;) w.add_document(path=u&#34;/b&#34;, class_id=u&#34;MyClass&#34;) # ... w.commit() qp = qparser.QueryParser(&#34;path&#34;, schema=schema) q = qp.parse(u&#34;test_id:alfa&#34;) with ix.searcher() as s: results = s.search(q) Advanced schema setup Field boosts 你可以为字段指定field boost(字段增强器),它可以倍增某个字段中出现的任意term的检索价值。比如下面的示例中,在计算文档与检索条件的相关性的时候,title字段中出现的相关词句(term),分数会是body中的两倍:
1 schema = Schema(title=TEXT(field_boost=2.0), body=TEXT) Field types 前文列出的预设类型都是fields.FieldType的子类。FieldType是一个非常简单的类,它的属性中包含了定义字段行为的信息:
Attribute Type Description format fields.Format Defines what kind of information a field records about each term, and how the information is stored on disk. vector fields.Format Optional: if defined, the format in which to store per-document forward-index information for this field. scorable bool If True, the length of (number of terms in) the field in each document is stored in the index. Slightly misnamed, since field lengths are not required for all scoring. However, field lengths are required to get proper results from BM25F. stored bool If True, the value of this field is stored in the index. unique bool If True, the value of this field may be used to replace documents with the same value when the user calls document_update() on an IndexWriter. 大多数类型的构造器都有一些参数让你能够自定义表中的内容。比如:
FieldType.stored TEXT()构造器就有analyzer关键字参数,这个参数的值会传给form对象。 Formats format对象定义了一个字段所记录的每一个term的信息,以及这些信息是如何存储在磁盘上的。
比如,Existence存储posting: Doc 10 20 30 而,Positions存储posting: Doc Positions &mdash; &mdash; 10 [1,5,23] 20 [45] 30 [7,12] 索引代码将字段的unicode字符串发给字段的Format对象。Format对象调用它的解释器(analyzer)将字符串切分成一些标记,再为每一个标记编码。
Wshoosh会忽略下表中的预设format: 类名 描述 Stored A “null” format for fields that are stored but not indexed. Existence Records only whether a term is in a document or not, i.e. it does not store term frequency. Useful for identifier fields (e.g. path or id) and “tag”-type fields, where the frequency is expected to always be 0 or 1. Frequency Stores the number of times each term appears in each document. Positions Stores the number of times each term appears in each document, and at what positions. 此外,还有一些不常用的format,它们主要是方便一些强者的使用: 类名 描述 DocBoosts Like Existence, but also stores per-document boosts Characters Like Positions, but also stores the start and end character indices of each term PositionBoosts Like Positions, but also stores per-position boosts CharacterBoosts Like Positions, but also stores the start and end character indices of each term and per-position boosts Vectors 主要的index是一个反向索引。它将term映射到其所在的文档。它有时也有助于储存前向索引(亦称term vector),即把文档映射到文档中的term上。
比如,你可以想象一个字段的反向索引: Term Postings apple [(doc=1, freq=2), (doc=2, freq=5), (doc=3, freq=1)] bear [(doc=2, freq=7)] 相应的前向索引,或者说term vector,应该是这样的: Doc Postings 1 [(text=apple, freq=2)] 2 [(text=apple, freq=5), (text=&lsquo;bear&rsquo;, freq=7)] 3 [(text=apple, freq=1)] 如果你为一个Format对象设置了FieldType.vector,那么索引代码就会使用这个Format对象来存储每一个文档中的那些term。目前,Whoosh默认不使用任何term vector,但是对于想要实现自己的字段类型的大神们而言,这个功能是可用的。
How to index documents Creating an Index object 使用index.create_in在指定目录中创建一个index:
1 2 3 4 5 6 7 import os, os.path from whoosh import index if not os.path.exists(&#34;indexdir&#34;): os.mkdir(&#34;indexdir&#34;) ix = index.create_in(&#34;indexdir&#34;, schema) 使用index.open_dir打开指定文件夹存在的index:
1 2 3 import whoosh.index as index ix = index.open_dir(&#34;indexdir&#34;) 这儿还有些快捷函数(convenience functions):
1 2 3 4 5 6 7 8 from whoosh.filedb.filestore import FileStorage storage = FileStorage(&#34;indexdir&#34;) # Create an index ix = storage.create_index(schema) # Open an existing index storage.open_index() 你创建index所用的schame是和index深度捆绑且一起存储的。
你可以在同一个文件中创建多个index,但是需要使用indexname关键字指定:
1 2 3 4 5 6 7 # Using the convenience functions ix = index.create_in(&#34;indexdir&#34;, schema=schema, indexname=&#34;usages&#34;) ix = index.open_dir(&#34;indexdir&#34;, indexname=&#34;usages&#34;) # Using the Storage object ix = storage.create_index(schema, indexname=&#34;usages&#34;) ix = storage.open_index(indexname=&#34;usages&#34;) Clearing the index 调用index.create_in将会清除目录中已存在的index中的内容。
使用index.exists_in可以判断目录中的是否包含有效index:
1 2 exists = index.exists_in(&#34;indexdir&#34;) usages_exists = index.exists_in(&#34;indexdir&#34;, indexname=&#34;usages&#34;) (此外,也可以直接删除index的相关文件。如果目录中仅有一个index,你可以使用shutil.rmtree移除文件夹并重新创建它。)
Indexing documents 一旦创建好Index对象,你就可以使用IndexWriter向index中添加文档了。获取IndexWriter对象的最简单的方式就是调用index.writer():
1 2 ix = index.open_dir(&#34;index&#34;) writer = ix.writer() 创建了writer的时候会锁定index。因此,同一时间,一个线程(或进程)只能存在一个writer:
Note
你需要有意识到,因为打开一个writer的时候会把index锁定,所以在多线程(多进程)的编程环境中,打开writer时可能会抛出异常(whoosh.store.LockError,如果已经有一个writer被打开了)。Whoosh包含了一组实现样例(whoosh.writing.AsyncWriter和whoosh.writing.BufferedWriter),它们可以绕开写入锁。 Note
即便writer已打开且处于commit中,index仍然是可以被读取的。已存在的reader不受影响,新的reader也能正常地打开最新的index。但是,commit完成之后,已存在的reader只能获取先前的index(也就是说,它们不会自动更新最新的提交内容)。 IndexWriter的add_document(**kwargs)方法以参数的形式将字段名映射到值:
1 2 3 4 5 6 7 8 writer = ix.writer() writer.add_document(title=u&#34;My document&#34;, content=u&#34;This is my document!&#34;, path=u&#34;/a&#34;, tags=u&#34;first short&#34;, icon=u&#34;/icons/star.png&#34;) writer.add_document(title=u&#34;Second try&#34;, content=u&#34;This is the second example.&#34;, path=u&#34;/b&#34;, tags=u&#34;second short&#34;, icon=u&#34;/icons/sheep.png&#34;) writer.add_document(title=u&#34;Third time&#39;s the charm&#34;, content=u&#34;Examples are many.&#34;, path=u&#34;/c&#34;, tags=u&#34;short&#34;, icon=u&#34;/icons/book.png&#34;) writer.commit() 建立索引的字段必须赋予unicode编码的值;存储但不索引的字段可以使用任意编码。
Whoosh允许使用同样的值添加文档,这可能会很有用,也有可能令人厌烦:
1 2 writer.add_document(path=u&#34;/a&#34;, title=u&#34;A&#34;, content=u&#34;Hello there&#34;) writer.add_document(path=u&#34;/a&#34;, title=u&#34;A&#34;, content=u&#34;Deja vu!&#34;) 这个例子添加了具有相同path和title的两个文档。
下文的“updating documents”会有关于update_document方法的介绍,它使用“unique”参数来替换旧文档,而不是新增文档。
Indexing and storing different values for the same field 如果你有一个字段既能索引也能存储,那么,必要情况下,你可以索引一个unuicode编码的值并存储一个不同的值(通常这是不必要的,但有时这真的很有用 似曾相识的话 )。使用一个特殊的关键字参数_stored_&lt;fieldname&gt;可以实现这个功能。一般的参数值会被分析并建立索引,而带有“stored”的参数的值会被展示在检索结果中:
1 writer.add_document(title=u&#34;Title to be indexed&#34;, _stored_title=u&#34;Stored title&#34;) Finishing adding documents IndexWriter就像数据库事务一样。你进行一连串的操作,然后一次性提交它们。
调用commit()保存添加到index中的文档:
1 writer.commit() 一旦你的文档在index中,你就可以检索它们了。
如果想要关闭writer而不提交,使用cancel()代替commit():
1 writer.cancel() 始终要记住,如果你要打开一个writer(或者已经打开过writer并且尚未关闭),那么就应当不会有其它线程或者进程会获取相同index的writer或修改该index。一个writer可能会占用多个打开的文件,所以你也应该记住在不需要writer的时候要调用commit()或者cancel()。
Merging segments Whoosh的filedb索引实际上是一个容器,用于一个或多个称为段的子索引。当您将文档添加到索引中时,并不是将新文档整合进现有文档(这操作的代价可能非常昂贵,因为它涉及到在磁盘上使用所有已索引的term),而是在现有段的后面创建一个新段。然后,当你检索索引时,Whoosh将分别搜索两个段并合并结果,使这些段看起来是一个统一的索引。(这个巧妙的设计是抄袭Lucene的。)
所以,相交于每次添加文档都重写整个index,拥有几个搜索段将会使搜索更加高效。但是,在检索某些内容时,多段同时检索可能会变慢,并且段越多检索越慢。因此,whoosh采用了一种算法,在你调用commit()的时候,算法会检查较小的段,将提交的内容合并到小的段中,使得段的个数少些、段的大小大些。
提交时,使用merge=False避免whoosh自动整合段:
1 writer.commit(merge=False) 而,要合并所有段,将index优化为单一的段,请使用optimize=True:
1 writer.commit(optimize=True) 由于优化会重写index中的所有信息,所以在大型index中会很慢。通常,最好还是依靠whoosh的算法,而不是一直使用优化功能。
(Index对象也有一个optmize()方法可以用来优化index(合并为单一段)。它仅仅创建一个writer并调用commit(optimize=True)。)
如果想要更好地控制合并段,你可以制定自己的合并策略,并且在commit()中指定它。参见whoosh.writing模块中NO_MERGE、MERGE_SMALL以及OPTIMIZE的实现。
Deleting documents 你可以使用IndexWriter对象提供的如下方法删除文档,然后调用commit()提交修改。
delete_document(docnum)
通过内部的文档编号删除文档,低级方法。 is_deleted(docnum)
如果指定编号的文档已经被删除,返回True,低级方法?基础方法?基层方法?low-level。 delete_by_term(fieldname, termtext)
删除指定字段名中包含指定内容的文档。通常用于ID或KEYWORD字段。 delete_by_query(query)
删除所有与query匹配的文档 1 2 3 4 # Delete document by its path -- this field must be indexed ix.delete_by_term(&#39;path&#39;, u&#39;/a/b/c&#39;) # Save the deletion to disk ix.commit() 在filedb后台,“删除”文档仅仅是把文档的编号添加到已删文档列表中,这个列表仍然存储在index中。当检索index时,whoosh知道检索结果中不需要返回已经删除的文档。然而,文档的内容依旧储存在index中(,只是关于文档的一些统计信息不再更新),直到合并包含已删文档的段。(因为,立即从index中删除文档会涉及到重写磁盘上的整个index,这会非常低效。)
Updating documents 如果你想替换文档,可以删除旧文档,再添加新的文档。或者,使用IndexWriter.update_document进行这一步操作。
使用update_document,你必须在schema中标记至少一个字段为“unique”。whoosh会使用“unique”字段的内容去寻找需要删除的文档:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from whoosh.fields import Schema, ID, TEXT schema = Schema(path = ID(unique=True), content=TEXT) ix = index.create_in(&#34;index&#34;) writer = ix.writer() writer.add_document(path=u&#34;/a&#34;, content=u&#34;The first document&#34;) writer.add_document(path=u&#34;/b&#34;, content=u&#34;The second document&#34;) writer.commit() writer = ix.writer() # Because &#34;path&#34; is marked as unique, calling update_document with path=&#34;/a&#34; # will delete any existing documents where the &#34;path&#34; field contains &#34;/a&#34;. writer.update_document(path=u&#34;/a&#34;, content=&#34;Replacement for the first document&#34;) writer.commit() “unique”字段必须是被索引的。
如果需要更新的文档不存在,那么update_document的作用将和add_document相似。
“unique”和undate_document仅仅是删除和添加的一个快捷方式。whoosh本身没有unique标志符的概念,当你使用add_document时并不能保证唯一性。
Incremental indexing 索引一个文档集合的编程方式通常有两种:
一种是从头索引所有文档;另一种是只更新改变的文档 (leaving aside web applications where you need to add/update documents according to user actions)。
第一种方式很简单,示例如下:
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 import os.path from whoosh import index from whoosh.fields import Schema, ID, TEXT def clean_index(dirname): # Always create the index from scratch ix = index.create_in(dirname, schema=get_schema()) writer = ix.writer() # Assume we have a function that gathers the filenames of the # documents to be indexed for path in my_docs(): add_doc(writer, path) writer.commit() def get_schema() return Schema(path=ID(unique=True, stored=True), content=TEXT) def add_doc(writer, path): fileobj = open(path, &#34;rb&#34;) content = fileobj.read() fileobj.close() writer.add_document(path=path, content=content) 对于少量的文档,这种方式是足够迅速的。但是,面对大量文档时,你就不想使用这种方式了。
首先,我们需要记录每个文档的最近修改时间,以便在它更改时可以检查到它。简单起见,如下的示例中,我们仅使用mtime:
1 2 3 4 5 6 7 8 9 def get_schema() return Schema(path=ID(unique=True, stored=True), time=STORED, content=TEXT) def add_doc(writer, path): fileobj = open(path, &#34;rb&#34;) content = fileobj.read() fileobj.close() modtime = os.path.getmtime(path) writer.add_document(path=path, content=content, time=modtime) 现在,我们修改之前的脚本,使其既可以选择从头建立索引,也可以增量建立索引:
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 def index_my_docs(dirname, clean=False): if clean: clean_index(dirname) else: incremental_index(dirname) def incremental_index(dirname) ix = index.open_dir(dirname) # The set of all paths in the index indexed_paths = set() # The set of all paths we need to re-index to_index = set() with ix.searcher() as searcher: writer = ix.writer() # Loop over the stored fields in the index for fields in searcher.all_stored_fields(): indexed_path = fields[&#39;path&#39;] indexed_paths.add(indexed_path) if not os.path.exists(indexed_path): # This file was deleted since it was indexed writer.delete_by_term(&#39;path&#39;, indexed_path) else: # Check if this file was changed since it # was indexed indexed_time = fields[&#39;time&#39;] mtime = os.path.getmtime(indexed_path) if mtime &gt; indexed_time: # The file has changed, delete it and add it to the list of # files to reindex writer.delete_by_term(&#39;path&#39;, indexed_path) to_index.add(indexed_path) # Loop over the files in the filesystem # Assume we have a function that gathers the filenames of the # documents to be indexed for path in my_docs(): if path in to_index or path not in indexed_paths: # This is either a file that&#39;s changed, or a new file # that wasn&#39;t indexed before. So index it! add_doc(writer, path) writer.commit() incremental_index方法中:
遍历最近索引的文档路径: 如果文件不存在,则删除index中的相关文档形式。 如果文件存在,但是修改过了,则把它添加到重写列表中。 如果文件存在,无论有没有修改,把它添加到所有的索引路径中。 遍历磁盘上的所有文件路径: 如果文件路径不在索引中,这个文件就是新文件,需要添加到index中。 If a path is not in the set of all indexed paths, the file is new and we need to index it. 如果文件路径在重写列表中,就需要索引它。 If a path is in the set of paths to re-index, we need to index it. 或者,跳过这一步。 Otherwise, we can skip indexing the file. Clearing the index 某些情况下,你需要在不破坏已经存在的reader的前提下,重头建立index:
1 2 3 4 5 6 7 8 9 from whoosh import writing with myindex.writer() as mywriter: # You can optionally add documents to the writer here # e.g. mywriter.add_document(...) # Using mergetype=CLEAR clears all existing segments so the index will # only have any documents you&#39;ve added to this writer mywriter.mergetype = writing.CLEAR 或者,不使用writer上下文管理器,直接调用commit(),像这样:
1 2 3 mywriter = myindex.writer() # ... mywriter.commit(mergetype=writing.CLEAR) Note
如果你不需要担心已经存在的reader,更高效的方式是直接删除index目录中的内容,然后重启。 How to search 若你已经建立了索引,并且添加了文档,那么就可以检索这些文档了。
The Searcher object 通过调用Index对象的searcher()获得whoosh.searching.Searcher对象:
1 searcher = myindex.searcher() </content>
</entry>
<entry>
<title>[Tips]颜色mark</title>
<url>https://litmingc.github.io/post/tips/tips%E9%A2%9C%E8%89%B2mark/</url>
<categories>
</categories>
<tags>
</tags>
<content type="html"> 也不知道哪儿来的颜色名ヾ(。 ̄□ ̄)ツ゜゜゜
玄青 #3d3b50 雪青 #b0a4e2 鸦青 #434a52 鸭卵青 #dfeee7 牙色 #efdeb0 枣红 #883040 章丹 #ee652e 正灰 #94a1a9 中棕灰 #a9987e 纸棕 #bba590 织锦灰 #758a8f 紫藤灰 #847f95 紫水晶 #c2a6cc </content>
</entry>
<entry>
<title>[Tips]算法小抄</title>
<url>https://litmingc.github.io/post/tips/tips%E7%AE%97%E6%B3%95%E5%B0%8F%E6%8A%84/</url>
<categories>
</categories>
<tags>
</tags>
<content type="html"> 欧几里得算法证明(最大公约数)1 \(a\) 整除 \(b\) 表示\(b\%a\)为\(0\); \(a\) 除以 \(b\) 表示 \(b/a\);我总把除和除以搞混。 \(gcd(a,b)\)表示\(a\),\(b\)的最大公约数。 证明:
设\(a\),\(b\) (\(a&gt;b\))的最大公约数为\(c\)。
\(\because\)存在\(q,r,k_1,k_2\) ,使得\(\left\{ \begin{aligned}a=&amp;b*q+r \\ a=&amp;c*k_1 \\ b=&amp;c*k_2 \end{aligned}\right.\) ,
\(\therefore r=a-bq=ck\_1-ck\_2q=c(k\_1-k\_2q)\),
又\(\because b=ck_2\)
\(\therefore\)若\(k\_2\)与\((k\_1-k\_2*q)\)互质,则\(r\)和\(b\)的最大公约数也是\(c\)。
下面证明,\(k_2\)与\((k_1-k_2*q)\)互质:
假设\(k_2\)与\((k_1-k_2*q)\)不互质,即有大于1的公约数\(m\),
则存在\(x\)、\(y\),使得\(\left\{\begin{aligned}k_2=&amp;xm \\ (k_1-k_2*q)=&amp;ym\end{aligned}\right.\),
代入\(a\)、\(b\),得\(\left\{\begin{aligned} a=&amp;b*q+r=xmc*q+ymc=mc(x*q+y)\\ b=&amp;xmc \end{aligned}\right.\), \(a\)、\(b\)最大公约数为\(mc\),矛盾。
所以,\(k_2\)与\((k_1-k_2*q)\)互质,\(c\)为\(b\)、\(r\)的最大公约数。 即,\(gcd(a,b)=gcd(b,r)=gcd(b,a\%b)=c\)
从上述证明中不难发现a、b、r(\(a\%b\))有共同的最大公约数,所以不断取其中最小的两个数相除得到更小的一组数,直到这组数中一个数本身就是最大公约数,算法结束。
https://blog.csdn.net/weixin_43406046/article/details/84197255?depth_1-utm_source=distribute.pc_relevant.none-task&amp;utm_source=distribute.pc_relevant.none-task “欧几里德算法 CSDN”↩︎
</content>
</entry>
<entry>
<title>[Tips]Git使用笔记</title>
<url>https://litmingc.github.io/post/tips/tipsgit%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0/</url>
<categories>
<category>Git</category>
</categories>
<tags>
<tag>学习笔记</tag><tag>Git</tag>
</tags>
<content type="html"> Git语法 子模块 删除子模块 rm -rf 子模块目录 删除子模块目录及源码 vi .gitmodules 删除项目目录下.gitmodules文件中子模块相关条目 vi .git/config 删除配置项中子模块相关条目 rm .git/module/* 删除模块下的子模块目录 Git Hook http://www.worldhello.net/gotgit/08-git-misc/070-hooks-and-templates.html
任意版本库.git/hooks目录下默认都是有一些脚本文件的(需要删除.sample后缀才会执行),当Git执行特定操作时会调用特定的钩子脚本。脚本返回非0值时,对应阶段的git操作将被终止。
案例:
使用commit-msg,在git commit执行时校验 commit message 。
commit-msg脚本包含一个参数.git/COMMIT_EDITMSG,.git/COMMIT_EDITMSG是记录 commit message 的文件路径,因此可以在commit-msg脚本中校验 commit message 。案例脚本如下:
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 echo &#34;&gt;&gt;&gt; 校验 commit message&#34; # feat: 新功能(feature) # fix: 修补bug # docs: 文档(documentation) # style: 格式(不影响代码运行的变动) # refactor: 重构(即不是新增功能,也不是修改bug的代码变动) # test: 增加测试 # chore: 构建过程或辅助工具的变动 # 正则表达式:匹配案例&#34;fix 20210606&#34; mode=&#34;(feat|fix|doc|style|refactor|test|chore) [0-9]{8,}&#34; firstline=`head -1 $1 | grep -E &#34;$mode&#34;` if test &#34;&#34;=&#34;$firstline&#34; then echo &#34;&gt;&gt;&gt; 失败&#34; echo &#34;&gt;&gt;&gt; Input: $firstline&#34; echo &#34;&gt;&gt;&gt; Mode : $mode&#34; echo &#34;&#34; exit 1 else echo &#34;&gt;&gt;&gt; 成功&#34; echo &#34;&#34; fi Git工作流 Gitflow12 Gitflow 工作流是目前非常成熟的一个方案,它定义了一个围绕项目发布的严格分支模型,通过为代码研发、项目发布以及维护分配独立的分支来让项目的迭代过程更加地顺畅,不同于之前的集中式工作流以及功能分支工作流,gitflow 工作流常驻的分支有两个:
主干分支 master 开发分支 dev
此外针对项目研发的各个阶段,设定了特定的分支。 阶段分支常驻:
Production master
master 分支主要跟踪项目正式发布的代码历史;
master 分支上通常会为某次版本发布分配一个标签来记录版本号; 这个分支只能从其他分支合并,不能在这个分支直接修改。一般情况下Master不存在Commit; dev
dev 分支主要跟踪项目代码研发的提交历史,包含所有要发布到下一个Release的代码;
这个主要合并于其他分支,比如Feature分支 研发 feature
开发阶段开启某一个需求时需要从 dev 分支上新建功能分支 feature;在需求开发完成之后需要提交 PR 请求合并进 dev 分支,完成之后即可删除对应的功能分支。 发布 release
Release分支基于Develop分支创建,打完Release分支之后,我们可以在这个Release分支上测试,修改Bug等;
这个分支不做任何功能上的任务;
完成之后将 release 分支再分别合并进 master/dev 分支;
同时,其它开发人员可以基于Develop分支新建Feature (记住:一旦打了Release分支之后不要从Develop分支上合并新的改动到Release分支) 热修复 hotfix
当我们在Production发现新的Bug时候,我们需要创建一个Hotfix, 完成Hotfix后,我们合并回Master和Develop分支,所以Hotfix的改动会进入下一个Release(Tag) 规范git commit https://zhuanlan.zhihu.com/p/316302079&#160;&#x21a9;&#xfe0e;
https://www.jianshu.com/p/41910dc6ef29&#160;&#x21a9;&#xfe0e;
</content>
</entry>
<entry>
<title>[rpi]树莓派使用v2ray</title>
<url>https://litmingc.github.io/post/linux/%E6%A0%91%E8%8E%93%E6%B4%BE%E4%BD%BF%E7%94%A8v2ray/</url>
<categories>
<category>学习笔记</category><category>Linux</category>
</categories>
<tags>
<tag>RaspberryPi</tag><tag>v2ray</tag>
</tags>
<content type="html"> 摘录自链接
安装v2ray GitHub下载相应版本【https://github.com/v2ray/v2ray-core/releases】
树莓派4下载linux-arm.zip,树莓派的官方系统目前(2020.03.12)还是32位。
下载安装脚本
1 wget https://install.direct/go.sh 执行安装脚本
1 sudo bash go.sh --local ./v2ray-linux-arm.zip 配置v2ray
编辑/etc/v2ray/config.json文件进行配置
v2ray的使用
service命令
1 2 3 4 5 service v2ray start //启动v2ray service v2ray status //查看v2ray状态 service v2ray stop //停止v2ray 使用代理 安装proxychains
1 sudo apt install proxychains 配置proxychains
1 sudo vim /etc/proxychains.conf 在proxychains.conf文件末尾修改你的socks5服务地址,可以把sock4删除
[ProxyList]socks5 127.0.0.1 1080此处的配置与v2ray中的配置对应,即port字段:
&quot;inbounds&quot;: [{&quot;tag&quot;: &quot;proxy&quot;,&quot;port&quot;: 1080,&quot;listen&quot;: &quot;0.0.0.0&quot;,&quot;protocol&quot;: &quot;socks&quot;,# ...},] 使用
在命令前加上proxychains( 此方法只能代理TCP链接,我只要TCP就够了 )
1 proxychains curl https://www.google.com </content>
</entry>
<entry>
<title>[linux]目录的含义与相关操作</title>
<url>https://litmingc.github.io/post/linux/%E6%96%87%E4%BB%B6%E5%A4%B9%E7%9A%84%E7%BB%84%E7%BB%87/</url>
<categories>
<category>学习笔记</category><category>Linux</category>
</categories>
<tags>
<tag>目录</tag><tag>文件类型</tag>
</tags>
<content type="html"> 目录的一般含义 摘录自【链接】
一级目录 含义 /bin/ 系统命令,普通用户、root都可以执行。 /boot/ 系统启动目录,如:内核文件、引导程序等。 /dev/ 设备文件 /etc/ 配置文件。默认安装方式下的服务配置文件,如:用户信息、启动脚本、常用服务的配置文件等。 /home/ 存放普通用户的主目录 /lib/ 系统调用的函数库 lost+found 系统意外崩溃或意外关机时的恢复目录,不同分区可能都有此目录 /media/ 挂载目录,建议光盘等媒体设备 /mnt/ 挂载目录,挂载额外设备 /misc/ 挂载目录,建议挂载NFS服务的共享目录。(有的系统无此目录,挂载目录的类别不是必须的) /opt/ 第三方软件安装目录。 /proc/ 虚拟文件系统。保存系统内核进程、外部设备、网络状态等。&lt;br &gt;数据在内存中。因此,在此文件夹中写入数据会使得可用内存越来越小,其它存储在内存中的文件夹同理。 /root/ root的主目录 /run/ 系统启动后的一些运行数据,临时的文件系统,数据不在硬盘中。 /sbin/ 与系统环境设置相关的命令,只有root可以使用这些命令进行环境设置,普通用户可以查看部分命令 /usr/ Unix Software Resource,系统软件资源。 /srv/ 部分系统服务启动后的数据目录 /sys/ 虚拟文件系统,多为内核相关。 /tmp/ 临时目录,所有用户都可以访问和写入。 /var/ 储存动态数据,如:缓存、日志等。 /usr/目录 /usr/bin: 与系统启动无关的系统命令。普通用户和root用户可执行。 /usr/sbin: 存放根文件系统不必要的系统管理命令,如多数服务程序。只有root用户可以使用。 /usr/lib: 应用程序调用的程序库。 /usr/local: 手工安装的软件保存位置。一般建议源码包软件安装在这个位置。 /usr/share: 应用程序的资源文件保存位置,如帮助文档、说明文档和字体目录. /usr/src: 源码包保存位置,手工下载的源码包和内核源码包都可以保存到这里。 /usr/include: C/C++等编程语言的放置目录。 /var/目录 /var/lib: 程序运行中需要调用或改变的数据保存位置。如,MySQL的数据库保存在/var/lib/mysql/目录中。 /var/log: 登陆文件放置的目录,其中所包含比较重要的文件如/var/log/messages, /var/log/wtmp。 /var/run/: 一些服务和程序运行后,其PID保存位置。 /var/www/: 系统的包工具(yum,apt)安装的Apache等程序的网页主目录。 /var/tmp: 一些应用程序在安装或执行时,需要在重启后使用的某些文件,此目录能将该类文件暂时存放起来,完成后再行删除。 部分软件的目录说明 python 摘录自【链接】,如下:
python的可执行文件的目录一般在 /usr/bin下,通过apt-get安装的应用一般会在这个目录
自行安装的一般在/usr/local/bin下
python3.5的自带库目录在
/usr/lib/python3/dist-packages
/usr/lib/python3.5/
python2.7的自带库目录在
/usr/lib/python2.6/dist-packages
/usr/lib/python2.7/
通过pip安装的模块目录在
~/.local/lib/python3.5/site-packages
~/.local/lib/python2.7/site-packages
或者
/usr/local/lib/python2.7/dist-packages
————————————————
版权声明:本文为CSDN博主「sodawaterer」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/sodawaterer/article/details/72795468
文件类型 文件类型的符号标识(ls -l命令下)
- 普通文件 d 目录 b 块设备文件:保存大块数据的设备,比如硬盘。 s 套接字文件:网络数据连接过程中,通过套接字进行数据通信。 p 管道文件:用于解决多个程序同时存取同一个文件所造成的错误。 l 链接文件:快捷方式 c 字符设备文件:键盘、鼠标等。 </content>
</entry>
<entry>
<title>[Tips]Raspberrypi相关</title>
<url>https://litmingc.github.io/post/tips/raspberrypi%E7%9B%B8%E5%85%B3/</url>
<categories>
<category>学习笔记</category><category>Linux</category>
</categories>
<tags>
<tag>挂载硬盘</tag><tag>berryconda</tag><tag>RaspberryPi</tag>
</tags>
<content type="html"> 常用初始化配置 新烧好系统之后,我常做的配置。列出来做个备忘。
修改主机名 1 sudo vim /etc/hostname 修改密码 修改hosts 主要是添加127.0.0.1对前面修改的主机名的映射 1 sudo vim /etc/hosts v2ray配置 aria2配置* samba配置 docker配置* 挂载移动硬盘 挂载移动硬盘 NTFS 安装ntfs-3g 1 sudo apt-get install ntfs-3g 查看设备 1 fdisk -l 准备挂载目录 1 sudo mkdir /mnt/dir 暂时挂载硬盘 1 ntfs-3g /dev/sda1 /mnt/dir 开机自动挂载
修改文件 /etc/fstab
添加如下内容: 1 /dev/sda1 /mnt/dir ntfs utf8,uid=1000,gid=1000,umask=022 0 0 berryconda 环境:raspbian armv7l 问题:网络不佳,使用conda insatll无法下载openssl
解决:
使用wget下载目标 conda insatll --use-local openssl本地文件安装 问题:conda insatll --use-local openssl本地文件安装出错
错误信息:CondaError: EOFError('Compressed file ended before the end-of-stream marker was reached',)
可能的原因:berryconda/pkgs/存在未正确下载的文件,安装时可能并没有用指定本地文件,而是用了缓存的文件?
解决: 删除berryconda/pkgs/目录下的相关文件,再次安装
修改主机名后,sudo有异常提示 环境:Linux raspberrypi 4.19.88-OPENFANS+20191229-v8 aarch64 问题:在cockpit中修改主机名,sudo功能正常,但有提示信息
信息:sudo: unable to resolve host raspberrypi: Name or service not known
解决:
修改/etc/hosts文件,将127.0.0.1映射到当前主机名。主机名所在文件为/etc/hostname。 安装Aria2 环境:Linux raspberrypi 4.19.88-OPENFANS+20191229-v8 aarch64 下载对应的build包
【下载地址】 arm似乎没有,可以使用apt-get install aria2安装,但版本可能不是最新的。 samba安装 1 sudo apt-get install samba samba-common-bin 配置文件
[public]comment = Public Storagepath = /home/piread only = no#任何人都具有了访问修改的权限#因为是公共文件夹,所以给了所有用户全部权限,可以自定义create mask = 0777#新创建文件的默认属性directory mask = 0777#新创建文件夹的默认属性guest ok = yes#默认的访问用户名为guestbrowseable = yes更多配置项1
https://blog.csdn.net/qq_39626154/article/details/85335103&#160;&#x21a9;&#xfe0e;
</content>
</entry>
<entry>
<title>[linux]常见命令</title>
<url>https://litmingc.github.io/post/linux/%E5%B8%B8%E8%A7%81%E5%91%BD%E4%BB%A4/</url>
<categories>
<category>学习笔记</category><category>Linux</category>
</categories>
<tags>
</tags>
<content type="html"> linux使用过程中自己会用到的一些“小”命令
uname
查看系统内核以及系统版本
参数:
-a或&ndash;all 显示全部的信息。 -m或&ndash;machine 显示电脑类型。 -n或-nodename 显示在网络上的主机名称。 -r或&ndash;release 显示操作系统的发行编号。 -s或&ndash;sysname 显示操作系统名称。 -v 显示操作系统的版本。 getconf LONG_BIT
看看系统是32位还是64位
uptime
查看负载信息
free
显示当前系统中内存使用量信息
</content>
</entry>
<entry>
<title>[Tips]Hugo相关</title>
<url>https://litmingc.github.io/post/tips/hugo%E7%9B%B8%E5%85%B3/</url>
<categories>
<category>Hugo</category>
</categories>
<tags>
<tag>MathJax</tag>
</tags>
<content type="html"> 环境:
hugo_extended_0.55.6_Windows-64bit 表达数学公式
hugo主题:Next
方案一: 先上参考:
MathJax Support
在Hugo中使用MathJax
修改公式显示的大小:
方法一:修改上述参考中的style。
方法二:使用如下代码:
1 2 3 4 5 &lt;script type=&#34;text/x-mathjax-config&#34;&gt; MathJax.Hub.Config({ &#34;HTML-CSS&#34;: { scale: 175} }); &lt;/script&gt; 但是,MathJax在渲染的时候,速度有些慢。总要等很久。 又因为听说KaTeX挺快的(用了一下,还真的)。
参考上述改法,KaTeX的配置如下:
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 &lt;link rel=&#34;stylesheet&#34; href=&#34;https://cdn.jsdelivr.net/npm/katex@0.10.0-beta/dist/katex.min.css&#34; integrity=&#34;sha384-9tPv11A+glH/on/wEu99NVwDPwkMQESOocs/ZGXPoIiLE8MU/qkqUcZ3zzL+6DuH&#34; crossorigin=&#34;anonymous&#34;&gt; &lt;script src=&#34;https://cdn.jsdelivr.net/npm/katex@0.10.0-beta/dist/katex.min.js&#34; integrity=&#34;sha384-U8Vrjwb8fuHMt6ewaCy8uqeUXv4oitYACKdB0VziCerzt011iQ/0TqlSlv8MReCm&#34; crossorigin=&#34;anonymous&#34;&gt;&lt;/script&gt; &lt;script src=&#34;https://cdn.jsdelivr.net/npm/katex@0.10.0-beta/dist/contrib/auto-render.min.js&#34; integrity=&#34;sha384-aGfk5kvhIq5x1x5YdvCp4upKZYnA8ckafviDpmWEKp4afOZEqOli7gqSnh8I6enH&#34; crossorigin=&#34;anonymous&#34;&gt;&lt;/script&gt; &lt;script&gt; renderMathInElement(document.body, { delimiters: [ {left: &#34;$$&#34;, right: &#34;$$&#34;, display: true}, {left: &#34;$&#34;, right: &#34;$&#34;, display: false}, ], ignoredTags: [&#39;script&#39;, &#39;noscript&#39;, &#39;style&#39;, &#39;textarea&#39;, &#39;pre&#39;], } ); var all=document.getElementsByClassName(&#39;katex&#39;),i; for(i = 0; i &lt; all.length; i += 1) { var tmp=all[i].parentNode; for(;tmp.nodeName==&#39;SPAN&#39;;tmp=tmp.parentNode); if(tmp.nodeName == &#39;CODE&#39;) tmp.className += &#39; hasKatex&#39;; } &lt;/script&gt; &lt;style&gt; code.hasKatex { font: inherit; font-size: 110%; background: inherit; border: inherit; color: #515151; } &lt;/style&gt; 如同参考教程12所说的一样,公式中使用_等符号会和Markdown冲突造成渲染失败,上述解决方法是在公式外加一对``解决,同时调整公式父层code标签的样式(因为原先的code的样式背景是灰的)。
方案二
上一种方案需要``把数学公式括起来,因而在使用vscode等编辑文档时非常不便于预览。幸运的是,在使用方案一的几天后,我又找到了方案二。
先上参考3
首先,和方案一一样引入KaTeX的代码块。因为不套用code块,也不需要调整code块的样式了,所以直接用官方默认的示例就行了:
1 2 3 4 5 6 7 8 9 10 &lt;link rel=&#34;stylesheet&#34; href=&#34;https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.css&#34; integrity=&#34;sha384-zB1R0rpPzHqg7Kpt0Aljp8JPLqbXI3bhnPWROx27a9N0Ll6ZP/+DiW/UqRcLbRjq&#34; crossorigin=&#34;anonymous&#34;&gt; &lt;script defer src=&#34;https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.js&#34; integrity=&#34;sha384-y23I5Q6l+B6vatafAwxRu/0oK/79VlbSz7Q9aiSZUvyWYIYsd+qj+o24G5ZU2zJz&#34; crossorigin=&#34;anonymous&#34;&gt;&lt;/script&gt; &lt;script defer src=&#34;https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/contrib/auto-render.min.js&#34; integrity=&#34;sha384-kWPLUVMOks5AQFrykwIup5lo0m3iMkkHrD0uJ4H5cjeGihAutqP0yW0J6dpFiVkI&#34; crossorigin=&#34;anonymous&#34;&gt;&lt;/script&gt; &lt;script&gt; document.addEventListener(&#34;DOMContentLoaded&#34;, function() { renderMathInElement(document.body, { // ...options... }); }); &lt;/script&gt; 然后,安装pandoc。
最后,在MarkDown文件的front-matter部分加上markup: &quot;pandoc&quot;即可。
&laquo;&laquo;&laquo;&laquo;&laquo;&laquo;&laquo;&laquo;手工分割线&laquo;&laquo;&laquo;&laquo;&laquo;&laquo;&laquo;&laquo;
这里还有一些问题。其中,无法像方案一那像配置delimiters: 选项。一旦配置了就无法渲染,我猜是不是pandoc和这个冲突? 而且,front-matter部分加上markup: &quot;pandoc&quot;后不能渲染&lt;details&gt;标签了,可能需要pandoc额外的设置吧。
于是,使用方案二就无法用一些html标签了,使用方案一又不便于编辑器预览。但是不用纠结
因为小孩子才做选择,而我可以全都要:
我用了如下的傻瓜方法,通过条件判断,给不同页面添加不同的代码。
内心OS:什么妖魔鬼怪的方法啊!ミ(ノ゜д゜)ノ打你哦
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 {{- if (and .IsPage (eq .Params.markup &#34;pandoc&#34; ) ) -}} &lt;!-- 方案二 --&gt; &lt;link rel=&#34;stylesheet&#34; href=&#34;https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.css&#34; integrity=&#34;sha384-zB1R0rpPzHqg7Kpt0Aljp8JPLqbXI3bhnPWROx27a9N0Ll6ZP/+DiW/UqRcLbRjq&#34; crossorigin=&#34;anonymous&#34;&gt; &lt;script defer src=&#34;https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.js&#34; integrity=&#34;sha384-y23I5Q6l+B6vatafAwxRu/0oK/79VlbSz7Q9aiSZUvyWYIYsd+qj+o24G5ZU2zJz&#34; crossorigin=&#34;anonymous&#34;&gt;&lt;/script&gt; &lt;script defer src=&#34;https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/contrib/auto-render.min.js&#34; integrity=&#34;sha384-kWPLUVMOks5AQFrykwIup5lo0m3iMkkHrD0uJ4H5cjeGihAutqP0yW0J6dpFiVkI&#34; crossorigin=&#34;anonymous&#34;&gt;&lt;/script&gt; &lt;script&gt; document.addEventListener(&#34;DOMContentLoaded&#34;, function() { renderMathInElement(document.body, { // ...options... }); }); &lt;/script&gt; {{- else if eq .Site.Params.Math &#34;katex&#34; -}} &lt;!-- 方案其它 --&gt; &lt;link rel=&#34;stylesheet&#34; href=&#34;https://cdn.jsdelivr.net/npm/katex@0.10.0-beta/dist/katex.min.css&#34; integrity=&#34;sha384-9tPv11A+glH/on/wEu99NVwDPwkMQESOocs/ZGXPoIiLE8MU/qkqUcZ3zzL+6DuH&#34; crossorigin=&#34;anonymous&#34;&gt; &lt;script src=&#34;https://cdn.jsdelivr.net/npm/katex@0.10.0-beta/dist/katex.min.js&#34; integrity=&#34;sha384-U8Vrjwb8fuHMt6ewaCy8uqeUXv4oitYACKdB0VziCerzt011iQ/0TqlSlv8MReCm&#34; crossorigin=&#34;anonymous&#34;&gt;&lt;/script&gt; &lt;script src=&#34;https://cdn.jsdelivr.net/npm/katex@0.10.0-beta/dist/contrib/auto-render.min.js&#34; integrity=&#34;sha384-aGfk5kvhIq5x1x5YdvCp4upKZYnA8ckafviDpmWEKp4afOZEqOli7gqSnh8I6enH&#34; crossorigin=&#34;anonymous&#34;&gt;&lt;/script&gt; &lt;script&gt; renderMathInElement(document.body, { delimiters: [ {left: &#34;$$&#34;, right: &#34;$$&#34;, display: true}, {left: &#34;$&#34;, right: &#34;$&#34;, display: false}, // 注意看这里,pandoc似乎会把$$ 转成\( \) // Next主题中,博文在首页的summary的公式都是\( \)这样的 {left: &#34;\\(&#34;, right: &#34;\\)&#34;, display: false}, ], ignoredTags: [&#39;script&#39;, &#39;noscript&#39;, &#39;style&#39;, &#39;textarea&#39;, &#39;pre&#39;], } ); var all=document.getElementsByClassName(&#39;katex&#39;),i; for(i = 0; i &lt; all.length; i += 1){ /* if(all[i].parentNode.parentNode.nodeName == &#39;CODE&#39;) */ /* all[i].parentNode.parentNode.className += &#39; mathcode&#39;; */ var tmp=all[i].parentNode; for(;tmp.nodeName==&#39;SPAN&#39;;tmp=tmp.parentNode); if(tmp.nodeName == &#39;CODE&#39;) tmp.className += &#39; mathcode&#39;; } &lt;/script&gt; &lt;!-- 下略 --&gt; {{- end -}} 这样的话,在文档中不指名使用pandoc则会使用方案一(需要设置.Site.Params.Math),算是个折中的方法。
修改代码块的样式
主题:pure
修改css文件的&lt;pre&gt;标签的样式,样式参考
使用tab键可能造成奇怪的渲染结局,应使用空格为宜
google analytic4
1 2 3 {{ if not .Site.IsServer }} {{ template &#34;_internal/google_analytics_async.html&#34; . }} {{ end }} 好想用其它主题的shortcode
shortcode. 很强大。这是个ref的例子.
[Neat]({{&lt; ref &ldquo;/blog/neat.md&rdquo; &gt;}})
1 &lt;a href=&#34;/blog/neat&#34;&gt;Neat&lt;/a&gt; https://www.gohugo.org/doc/tutorials/mathjax/ &ldquo;MathJax Support&rdquo;&#160;&#x21a9;&#xfe0e;
https://note.qidong.name/2018/03/hugo-mathjax/&#160;&#x21a9;&#xfe0e;
https://wrong.wang/flight-rules/20181130-%E4%BD%BF%E7%94%A8pandoc%E5%92%8Ckatex%E4%B8%BAhugo%E6%B7%BB%E5%8A%A0latex%E6%94%AF%E6%8C%81/&#160;&#x21a9;&#xfe0e;
https://note.qidong.name/2017/07/05/google-analytics-in-hugo/&#160;&#x21a9;&#xfe0e;
</content>
</entry>
<entry>
<title>[linux]进程管理</title>
<url>https://litmingc.github.io/post/linux/%E8%BF%9B%E7%A8%8B%E7%9B%B8%E5%85%B3/</url>
<categories>
<category>学习笔记</category><category>Linux</category>
</categories>
<tags>
</tags>
<content type="html"> 相关字段的含义 表太长,我折叠了 表头 含义 USER 进程由哪个用户产生 PID 进程id PPID 父进程id %CPU 或者 C 占用cpu %MEM 占用物理内存 VSC 占用虚拟内存的大小,单位KB RSS 占用物理内存的大小,单位KB PRI 进程优先级,数值越小,优先级越高。PRI += nice NI 即nice,用户可修改。 WCHAN 是否在运行,-表示在运行 TTY tty1 ~ tty7 代表本地控制台终端,tty7 是图型终端。
pts/0 ~ 255 代表虚拟终端,一般是远程连接的终端,第一个远程连接占用 pts/0,第二个远程连接占用 pts/1,依次増长。 STAT 或者 S 进程状态。常见的状态有以下几种: 1. -D:不可被唤醒的睡眠状态,通常用于 I/O 情况。 2. -R:该进程正在运行。 3. -S:该进程处于睡眠状态,可被唤醒。 4. -T:停止状态,可能是在后台暂停或进程处于除错状态。 5. -W:内存交互状态(从 2.6 内核开始无效)。 6. -X:死掉的进程(应该不会出现)。 7. -Z:僵尸进程。进程已经中止,但是部分程序还在内存当中。 8. -&lt;:高优先级(以下状态在 BSD 格式中出现)。 9. -N:低优先级。 10. -L:被锁入内存。 11. -s:包含子进程。 12. -l:多线程(小写 L)。
13. -+:位于后台。 TIME 占用cpu的时间 COMMAND 命令 FD 文件描述符 TYPE REG:文件;DIR:目录;CHR:字符;BLK:块设备;UNIX:unix套接字;FIFO:先进先出队列;IPv4等:IP套接字 DEVICE 指定磁盘名称 NODE 索引节点(文件在磁盘中的标识) 进程后台执行 在命令后使用 &amp; 符号
后台运行
1 find / -name abc.jpg &amp; 常见搭配:输出重定向,使用&gt;符号,如下
1 2 3 # 命令 &gt; 目标文件 &amp; # 将命令在后台运行,并将输出导出到目标文件 cat a.txt &gt; 目标文件 &amp; nohup命令
进程脱离终端执行:当前终端退出时,进程不被打断
no hang up,即不挂断,但不是在后台运行,搭配&amp;使用。 1 nohup find / -name abc.jpg &amp; jobs命令
查看当前终端的后台工作
1 jobs [options] options:
-l:列出进程PID号 -n:只显示状态改变的进程 -p:只显示PID -r:筛选running状态的进程 -s:筛选stoppde状态的进程 输出
工作号 +- status commend
+表示新增工作
fg命令
将后台命令恢复到前台
1 fg [[%]工作号] 工作号为jobs输出[]中的数字 默认恢复最新命令,即带+的工作
bg命令
将后台暂停的工作启动
1 bg [[%]工作号] 查看进程信息——ps命令 命令选项 含义 a 显示一个终端的所有进程,除会话引线外 u 显示进程的归属用户及内存的使用情况 x 显示没有控制终端的进程 -l 长格式显示更加详细的信息 -e 显示所有进程 实时监听进程信息——top命令 交互操作
?或者 h :帮助 p :按cpu占用排序 m :按照内存占用排序 t :按TIME排序 k :终止(kill)进程,指定PID,Esc退出 r :重新设置nice值,指定PID,Esc退出 q :退出top 命令选项
-d 秒数:top命令几秒更新一次。默认3秒 -b:使用批处理模式。据说,用于将信息保存输出到文件。 -n 次数:指定top命令的执行次数。 -p 进程PID:查看指定进程。 -s:安全模式。避免在交互过程中出错。 -u 用户名:只监听某个用户的进程。 注:cache 、 buffer的区别部分显示注解:
cache:读取硬盘的数据缓存 buffer:写入硬盘的数据缓存 查看进程树——pstree命令 pstree [option] [PID or username]
option:
-a:显示启动每个进程对应的完整指令 -c:不使用精简法显示进程信息 -n:根据PID排序 -p:显示PID -u:显示用户名 查看文件调用——lsof命令 lsof [options] [filename]
option:
-c 字符串:筛选以此字符串开头的命令 +d 目录:筛选目录 -u 用户名:筛选用户 -p PID:筛选PID 调整进程优先级 进程优先级涉及到的参数问题 PRI:Priority,越小优先级越高 NI:nice PRI(new) = PRI(old) + nice ,
其中:
NI $\epsilon$ [-20,19] 普通用户只能调整自己的进程,只能调高NI值,且调整范围为[0,19] ;root用户不受限 nice命令
nice [-n NI值] commend
renice命令
renice [NI值] PID 与nice不同,可以在命令运行中修改NI值
终止进程 kill命令
1 2 kill [signal] PID kill [-信号编号] PID 信号编号 信号名 含义 1 HUP 9 KILL 杀死进程 15 TERM 正常结束进程,kill默认信号 killall命令
1 killall [options] [signal] 进程名 options:
-i:交互式,询问是否杀死进程 -l:忽略进程名的大小写 pkill命令
1 pkill [signal] [-t tty] [进程名] 重要功能 -t:指定终端
1 2 3 # 例如: # 强制杀死从pts/1虚拟终端登录的进程 pkill -9 -t pts/1 </content>
</entry>
<entry>
<title>[Python]PyQt5笔记</title>
<url>https://litmingc.github.io/post/python/%E7%AC%94%E8%AE%B0pyqt5-notebook/</url>
<categories>
<category>学习笔记</category><category>python</category>
</categories>
<tags>
<tag>PyQt5</tag>
</tags>
<content type="html"> 环境:
python 3.8 PyQt5 5.13.0 QLineEdit 控制输入 正则表达式
1 2 3 4 5 6 Validator = QRegExpValidator(self) # 校验器 # 为校验器配置QRegExp(), 参数r&#34;[0-9]&#34;为正则表达式 Validator.setRegExp(QRegExp(r&#34;[0-9]&#34;)) c = QLineEdit() c.setValidator(Validator) 掩码
掩码对比正则表达式:掩码使用占位符;正则表达式则没有占位。所以,在没有输入的情况下,掩码的光标可以在占下的位置间移动,而且输入方式相当于更新,而不是插入。
1 2 3 c = QLineEdit() c.setInputMask(&#39;0&#39;) # 设置掩码:一位 0-9 的字符 c.setInputMask(&#39;000&#39;) # 设置掩码:三位 0-9 的字符 掩码占位符表
参考网上搜到的表格,但在应用时,原表中“必须输入”以及“不是必需的”的差异未有体现,下表为个人试验得出的意见。
【注】试验时,有效的代码部分仅为上述代码更换掩码而已。
占位字符 含义 A,a 只允许输入【A~Z, a~z】以及汉字 N,n 只允许输入【A~Z, a~z, 0~9】以及汉字 X,x 允许输入各种字符,标点 9,0 只允许输入0-9的数字 D,d 只允许输入1-9的数字 # 只允许输入1-9的数字、加号+、减号- H,h 只允许输入A~F、 a~f、0-9,即十六进制数字 B,b 只允许输入0、1,即二进制数字 &gt; 搭配上述字符,将字符自动变大写,可输入汉字 &lt; 搭配上述字符,将字符自动变小写,可输入汉字 ! 搭配上述字符,从!的位置开始,关闭 &gt; 、&lt;的大小写转换效果。
例如:'&lt;aaa!aaa',前三个字符会转换为小写,后三个字符不会被转换成小写 鼠标事件 判断鼠标按键 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 import sys from PyQt5 import QtGui from PyQt5.QtCore import QEvent, Qt from PyQt5.QtWidgets import QApplication, QLabel, QWidget class FillArea(QWidget): def __init__(self) -&gt; None: super().__init__() self.wordTip = QLabel(&#34;asdasd&#34;, self) self.setGeometry(300, 300, 350, 100) self.show() def initWin(self): pass def mousePressEvent(self, event: QtGui.QMouseEvent) -&gt; None: # 左键按下 if event.buttons() == Qt.LeftButton: self.wordTip.setText (&#34;左&#34;) # 右键按下 elif event.buttons() == Qt.RightButton: self.wordTip.setText (&#34;右&#34;) # 中键按下 elif event.buttons() == Qt.MidButton: self.wordTip.setText (&#34;中&#34;) # 左右键同时按下 elif event.buttons() == Qt.LeftButton | Qt.RightButton: self.wordTip.setText (&#34;左右&#34;) # 左中键同时按下 elif event.buttons() == Qt.LeftButton | Qt.MidButton: self.wordTip.setText (&#34;左中&#34;) # 右中键同时按下 elif event.buttons() == Qt.MidButton | Qt.RightButton: self.wordTip.setText (&#34;右中&#34;) # 左中右键同时按下 elif event.buttons() == Qt.LeftButton | Qt.MidButton | Qt.RightButton: self.wordTip.setText (&#34;左中右&#34;) self.update() if __name__ == &#34;__main__&#34;: app = QApplication(sys.argv) ex = FillArea() sys.exit(app.exec_()) 鼠标位置监控 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 import sys from PyQt5 import QtGui from PyQt5.QtCore import QEvent, Qt from PyQt5.QtWidgets import QApplication, QLabel, QWidget class FillArea(QWidget): def __init__(self) -&gt; None: super().__init__() self.wordTip = QLabel(&#34;asdasd-----------&#34;, self) self.setGeometry(300, 300, 350, 100) # 跟踪鼠标 self.setMouseTracking(True) self.show() def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -&gt; None: # 相对于整个屏幕的的位置 s = a0.screenPos() self.wordTip.setText(&#34;X:{},Y:{}&#34;.format(s.x(),s.y())) self.update() if __name__ == &#34;__main__&#34;: app = QApplication(sys.argv) ex = FillArea() sys.exit(app.exec_()) 即使没有开启鼠标跟踪(setMouseTracking),当在控件内按下鼠标键移动时,会调用鼠标移动事件函数mouseMoveEvent()。
s = event.windowPos():相对于QT窗口的位置。
s = PyQt5.Qt.QCursor.pos():可以获取全局的鼠标位置,相对于整个桌面。需要考虑怎么触发获取。
此外,QCursor还可以用来操纵鼠标。
全屏显示 self.showFullScreen():覆盖全屏,包括任务栏。
透明 self.setWindowOpacity(0.5):透明度
</content>
</entry>
<entry>
<title>QLineEdit</title>
<url>https://litmingc.github.io/post/pyqt5_nb/%E7%AC%94%E8%AE%B0qlineedit/</url>
<categories>
<category>学习笔记</category><category>python</category>
</categories>
<tags>
<tag>PyQt5</tag>
</tags>
<content type="html"> 环境:
python 3.7 PyQt5 5.13.0 QLineEdit 控制输入 正则表达式
1 2 3 4 5 6 Validator = QRegExpValidator(self) # 校验器 # 为校验器配置QRegExp(), 参数r&#34;[0-9]&#34;为正则表达式 Validator.setRegExp(QRegExp(r&#34;[0-9]&#34;)) c = QLineEdit() c.setValidator(Validator) 掩码
掩码对比正则表达式:掩码使用占位符;正则表达式则没有占位。所以,在没有输入的情况下,掩码的光标可以在占下的位置间移动,而且输入方式相当于更新,而不是插入。
1 2 3 c = QLineEdit() c.setInputMask(&#39;0&#39;) # 设置掩码:一位 0-9 的字符 c.setInputMask(&#39;000&#39;) # 设置掩码:三位 0-9 的字符 掩码占位符表
参考网上搜到的表格,但在应用时,原表中“必须输入”以及“不是必需的”的差异未有体现,下表为个人试验得出的意见。
【注】试验时,有效的代码部分仅为上述代码更换掩码而已。
占位字符 含义 A,a 只允许输入【A~Z, a~z】以及汉字 N,n 只允许输入【A~Z, a~z, 0~9】以及汉字 X,x 允许输入各种字符,标点 9,0 只允许输入0-9的数字 D,d 只允许输入1-9的数字 # 只允许输入1-9的数字、加号+、减号- H,h 只允许输入A~F、 a~f、0-9,即十六进制数字 B,b 只允许输入0、1,即二进制数字 &gt; 搭配上述字符,将字符自动变大写,可输入汉字 &lt; 搭配上述字符,将字符自动变小写,可输入汉字 ! 搭配上述字符,从!的位置开始,关闭 &gt; 、&lt;的大小写转换效果。
例如:'&lt;aaa!aaa',前三个字符会转换为小写,后三个字符不会被转换成小写 </content>
</entry>
</search>