1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
3802
3803
3804
3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
3840
3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
3851
3852
3853
3854
3855
3856
3857
3858
3859
3860
3861
3862
3863
3864
3865
3866
3867
3868
3869
3870
3871
3872
3873
3874
3875
3876
3877
3878
3879
3880
3881
3882
3883
3884
3885
3886
3887
3888
3889
3890
3891
3892
3893
3894
3895
3896
3897
3898
3899
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916
3917
3918
3919
3920
3921
3922
3923
3924
3925
3926
3927
3928
3929
3930
3931
3932
3933
3934
3935
3936
3937
3938
3939
3940
3941
3942
3943
3944
3945
3946
3947
3948
3949
3950
3951
3952
3953
3954
3955
3956
3957
3958
3959
3960
3961
3962
3963
3964
3965
3966
3967
3968
3969
3970
3971
3972
3973
3974
3975
3976
3977
3978
3979
3980
3981
3982
3983
3984
3985
3986
3987
3988
3989
3990
3991
3992
3993
3994
3995
3996
3997
3998
3999
4000
4001
4002
4003
4004
4005
4006
4007
4008
4009
4010
4011
4012
4013
4014
4015
4016
4017
4018
4019
4020
4021
4022
4023
4024
4025
4026
4027
4028
4029
4030
4031
4032
4033
4034
4035
4036
4037
4038
4039
4040
4041
4042
4043
4044
4045
4046
4047
4048
4049
4050
4051
4052
4053
4054
4055
4056
4057
4058
4059
4060
4061
4062
4063
4064
4065
4066
4067
4068
4069
4070
4071
4072
4073
4074
4075
4076
4077
4078
4079
4080
4081
4082
4083
4084
4085
4086
4087
4088
4089
4090
4091
4092
4093
4094
4095
4096
4097
4098
4099
4100
4101
4102
4103
4104
4105
4106
4107
4108
4109
4110
4111
4112
4113
4114
4115
4116
4117
4118
4119
4120
4121
4122
4123
4124
4125
4126
4127
4128
4129
4130
4131
4132
4133
4134
4135
4136
4137
4138
4139
4140
4141
4142
4143
4144
4145
4146
4147
4148
4149
4150
4151
4152
4153
4154
4155
4156
4157
4158
4159
4160
4161
4162
4163
4164
4165
4166
4167
4168
4169
4170
4171
4172
4173
4174
4175
4176
4177
4178
4179
4180
4181
4182
4183
4184
4185
4186
4187
4188
4189
4190
4191
4192
4193
4194
4195
4196
4197
4198
4199
4200
4201
4202
4203
4204
4205
4206
4207
4208
4209
4210
4211
4212
4213
4214
4215
4216
4217
4218
4219
4220
4221
4222
4223
4224
4225
4226
4227
4228
4229
4230
4231
4232
4233
4234
4235
4236
4237
4238
4239
4240
4241
4242
4243
4244
4245
4246
4247
4248
4249
4250
4251
4252
4253
4254
4255
4256
4257
4258
4259
4260
4261
4262
4263
4264
4265
4266
4267
4268
4269
4270
4271
4272
4273
4274
4275
4276
4277
4278
4279
4280
4281
4282
4283
4284
4285
4286
4287
4288
4289
4290
4291
4292
4293
4294
4295
4296
4297
4298
4299
4300
4301
4302
4303
4304
4305
4306
4307
4308
4309
4310
4311
4312
4313
4314
4315
4316
4317
4318
4319
4320
4321
4322
4323
4324
4325
4326
4327
4328
4329
4330
4331
4332
4333
4334
4335
4336
4337
4338
4339
4340
4341
4342
4343
4344
4345
4346
4347
4348
4349
4350
4351
4352
4353
4354
4355
4356
4357
4358
4359
4360
4361
4362
4363
4364
4365
4366
4367
4368
4369
4370
4371
4372
4373
4374
4375
4376
4377
4378
4379
4380
4381
4382
4383
4384
4385
4386
4387
4388
4389
4390
4391
4392
4393
4394
4395
4396
4397
4398
4399
4400
4401
4402
4403
4404
4405
4406
4407
4408
4409
4410
4411
4412
4413
4414
4415
4416
4417
4418
4419
4420
4421
4422
4423
4424
4425
4426
4427
4428
4429
4430
4431
4432
4433
4434
4435
4436
4437
4438
4439
4440
4441
4442
4443
4444
4445
4446
4447
4448
4449
4450
4451
4452
4453
4454
4455
4456
4457
4458
4459
4460
4461
4462
4463
4464
4465
4466
4467
4468
4469
4470
4471
4472
4473
4474
4475
4476
4477
4478
4479
4480
4481
4482
4483
4484
4485
4486
4487
4488
4489
4490
4491
4492
4493
4494
4495
4496
4497
4498
4499
4500
4501
4502
4503
4504
4505
4506
4507
4508
4509
4510
4511
4512
4513
4514
4515
4516
4517
4518
4519
4520
4521
4522
4523
4524
4525
4526
4527
4528
4529
4530
4531
4532
4533
4534
4535
4536
4537
4538
4539
4540
4541
4542
4543
4544
4545
4546
4547
4548
4549
4550
4551
4552
4553
4554
4555
4556
4557
4558
4559
4560
4561
4562
4563
4564
4565
4566
4567
4568
4569
4570
4571
4572
4573
4574
4575
4576
4577
4578
4579
4580
4581
4582
4583
4584
4585
4586
4587
4588
4589
4590
4591
4592
4593
4594
4595
4596
4597
4598
4599
4600
4601
4602
4603
4604
4605
4606
4607
4608
4609
4610
4611
4612
4613
4614
4615
4616
4617
4618
4619
4620
4621
4622
4623
4624
4625
4626
4627
4628
4629
4630
4631
4632
4633
4634
4635
4636
4637
4638
4639
4640
4641
4642
4643
4644
4645
4646
4647
4648
4649
4650
4651
4652
4653
4654
4655
4656
4657
4658
4659
4660
4661
4662
4663
4664
4665
4666
4667
4668
4669
4670
4671
4672
4673
4674
4675
4676
4677
4678
4679
4680
4681
4682
4683
4684
4685
4686
4687
4688
4689
4690
4691
4692
4693
4694
4695
4696
4697
4698
4699
4700
4701
4702
4703
4704
4705
4706
4707
4708
4709
4710
4711
4712
4713
4714
4715
4716
4717
4718
4719
4720
4721
4722
4723
4724
4725
4726
4727
4728
4729
4730
4731
4732
4733
4734
4735
4736
4737
4738
4739
4740
4741
4742
4743
4744
4745
4746
4747
4748
4749
4750
4751
4752
4753
4754
4755
4756
4757
4758
4759
4760
4761
4762
4763
4764
4765
4766
4767
4768
4769
4770
4771
4772
4773
4774
4775
4776
4777
4778
4779
4780
4781
4782
4783
4784
4785
4786
4787
4788
4789
4790
4791
4792
4793
4794
4795
4796
4797
4798
4799
4800
4801
4802
4803
4804
4805
4806
4807
4808
4809
4810
4811
4812
4813
4814
4815
4816
4817
4818
4819
4820
4821
4822
4823
4824
4825
4826
4827
4828
4829
4830
4831
4832
4833
4834
4835
4836
4837
4838
4839
4840
4841
4842
4843
4844
4845
4846
4847
4848
4849
4850
4851
4852
4853
4854
4855
4856
4857
4858
4859
4860
4861
4862
4863
4864
4865
4866
4867
4868
4869
4870
4871
4872
4873
4874
4875
4876
4877
4878
4879
4880
4881
4882
4883
4884
4885
4886
4887
4888
4889
4890
4891
4892
4893
4894
4895
4896
4897
4898
4899
4900
4901
4902
4903
4904
4905
4906
4907
4908
4909
4910
4911
4912
4913
4914
4915
4916
4917
4918
4919
4920
4921
4922
4923
4924
4925
4926
4927
4928
4929
4930
4931
4932
4933
4934
4935
4936
4937
4938
4939
4940
4941
4942
4943
4944
4945
4946
4947
4948
4949
4950
4951
4952
4953
4954
4955
4956
4957
4958
4959
4960
4961
4962
4963
4964
4965
4966
4967
4968
4969
4970
4971
4972
4973
4974
4975
4976
4977
4978
4979
4980
4981
4982
4983
4984
4985
4986
4987
4988
4989
4990
4991
4992
4993
4994
4995
4996
4997
4998
4999
5000
5001
5002
5003
5004
5005
5006
5007
5008
5009
5010
5011
5012
5013
5014
5015
5016
5017
5018
5019
5020
5021
5022
5023
|
\documentclass[nofonts,CJKnormalspaces]{ctexbook}[2009/05/20]
\input{fzfonts.tex} % 方正字体设置
% 要用 Windows 预装字体,给 ctexbook 用 winfonts 选项
% 要用 Adobe 字体,给 ctexbook 用 adobefonts 选项
\usepackage[pdftex]{geometry}
\geometry{left=2.5cm,right=2.5cm,top=3.5cm,bottom=2.5cm}
\usepackage{makeidx}
\makeindex
% 数学相关设置
\usepackage{amsmath}
\usepackage{amssymb}
\usepackage{bm}
\usepackage{upgreek}
\let\leq\leqslant
\let\geq\geqslant
\newcommand\set[1]{\left\{ #1 \right\}}
\newcommand\Set[2]{\left\{\, #1 \, \middle\vert \, #2 \,\right\}}
\newcommand\dif{\mathrm{d}}
\newcommand\diff{\,\dif}
\newcommand\mi{\mathrm{i}}
\usepackage{mflogo}
\usepackage{xltxtra}
\newcommand*\ConTeXt{Con\TeX{}t}
\usepackage{longtable}
\input{asysyntex.tex}
\usepackage{graphicx}
\usepackage{hyperref}
\lstdefinestyle{funclist}{style=lesscolor,
xleftmargin={0pt},
xrightmargin={0pt},
backgroundcolor=}
\newenvironment{funclist}{\trivlist
\parindent=0pt
\item[]
\def\item{\medskip\par\leftskip=0pt}
\def\go{\par\leftskip=4em}}
{\endtrivlist}
\makeatletter
\newenvironment{typelist}{\itemize
\let\old@item\@item
\def\@item[##1]{\expandafter\old@item[\ttfamily\color{type!50!black}##1]}}
{\enditemize}
\makeatother
\CTEXsetup[format={\Large\bfseries}]{section}
% 页眉(无 fancyhdr)字体修正
\makeatletter
\let\old@evenhead\@evenhead
\let\old@oddhead\@oddhead
\def\@evenhead{\normalfont\old@evenhead}
\def\@oddhead{\normalfont\old@oddhead}
\makeatother
\newcommand*\prgname[1]{\textsf{#1}}
\newcommand\transnote[1]{\footnote{#1——译者注}}
\newcommand\asyversion{1.77}
\hypersetup{bookmarksopen=false,
bookmarksnumbered=true,
pdftitle={Asymptote \asyversion{} 手册节译},
pdfauthor={milksea},
pdfsubject={Asymptote 手册},
pdfcreator={XeLaTeX + xeCJK},
pdfkeywords={Asymptote, Manual, 手册, 绘图},
pdfstartview={FitH}
}
\begin{document}
\frontmatter
\begin{titlepage}
\setlength{\parindent}{0pt}
\vspace*{\stretch{1}}
{\Huge\bfseries\prgname{Asymptote}: 矢量绘图语言}
\rule[0pt]{\textwidth}{4pt}
\hfill\asyversion{} 版
\centerline{\includegraphics{logo.pdf}}
\rule[0pt]{\textwidth}{2pt}
\vspace{\stretch{2}}
本文件是 \prgname{Asymptote} \asyversion{} 版的文档。
\url{http://asymptote.sourceforge.net}
版权所有 \copyright{} 2004--9 Andy Hammerlindl, John Bowman, and Tom Prince.
{\leftskip=4em 在 GNU Lesser General Public License(GNU 较宽松公共许可证)的
条款下(见源文件目录顶层的 \prgname{LICENSE} 文件)授予复制、发布和/或修改此
文档的许可。\par}
翻译:milksea\qquad\today
\end{titlepage}
\section*{翻译说明}
本手册根据 \prgname{Asymptote \asyversion} 手册节选翻译。目前节选的部分是原
手册的三、四、五章,包括全部的基本绘图功能说明,但未包括软件配置、与 \LaTeX{}
结合、扩展模块、命令行等相关内容。
手册大部分取原文直译,在不影响意义时偶有变化,或增添少量词语帮助理解。
个别术语夹注原文。增加语法高亮,大部分格式如旧,格式上偶有不妥处迳改,不另
注出。原文明显有误的,更改后用译注说明。
水平所限,本译文错漏难免,有疑义请以原文为准。
\tableofcontents
\mainmatter
\setcounter{chapter}{2}
\chapter{教程}
\label{chap:tutorial}
% 3 Tutorial
\emph{一份由用户编写的 \prgname{Asymptote} 绝佳教程又可得自}\transnote{这个
教程是比较简单的。\prgname{Asymptote} 的开发者之一,法国人 Philippe Ivaldi
\href{http://piprim.tuxfamily.org/asymptote/}{关于 \prgname{Asymptote} 的页面}
(英文)以大量例子的形式,展示了从最简单的点线绘图到复杂的模块设计,这无疑是
目前 \prgname{Asymptote} 方面最为生动翔实学习资料。在中文社区,由用户编写的
\prgname{Asymptote} 教程包括较早莫图(lapackapi@ctex.org)的一个中途已中止的
项目\href{http://gzmfig.googlepages.com/}{《\prgname{Asymptote} 作图指南》},
和 cvgmt@ctex.org 目前正在编写的\href{http://code.google.com/p/asy4cn/}
{一个教程}。中文译介的资料包括本手册和由 goodluck@ctex.org 翻译的
\href{http://bbs.ctex.org/viewthread.php?tid=47344}
{\prgname{Asymptote} 官方 FAQ}。这些中文资料一般都可以在
\href{http://bbs.ctex.org}{\CTeX{} 论坛}找到作者并获得更新和支持。}:
\url{http://www.artofproblemsolving.com/Wiki/index.php/Asymptote:_Basics}
要用 \prgname{Asymptote} 的交互模式绘制一条从坐标 $(0, 0)$ 到坐标
$(100, 100)$ 的直线,在命令提示符处敲:
\lstset{delim=[is][\color{black}]{<@}{@>}}
\begin{lstlisting}
<@asy@>
draw((0,0)--(100,100));
\end{lstlisting}
\lstset{delim=[][]{}}
\begin{center}
\includegraphics{diagonal.pdf}
\end{center}
\index{大点}\index{bp}
\index{点}\index{pt}
\index{英寸}\index{inch}
\index{复数}\index{序对}\index{pair@\inlinecode=pair=}
这里的单位是 \prgname{PostScript}“大点”(big point\transnote{这里大点(big
point)的概念来自 \TeX{}。在 \TeX{} 之外的印刷领域,$1/72$\,inch 就叫点
(point, pt),而在 \TeX{} 中的点要小一些:$1\,\text{pt} = 1/72.27\,\text{inch}$。},
$1\,\text{bp} = 1/72\,\text{inch}$);|--| 意谓用直线段连结。在
\prgname{Asymptote} 坐标中像 |(0,0)| 和 |(1000,100)| 这样的叫做\emph{复数}%
(序对,|pair|)。
\index{quit@\inlinecode=quit=}
\index{自动补全}
\index{交互模式}
这里你可以敲入更进一步的绘图命令,它们将被加到上面展示出的图中,也可敲 |quit|
退出交互模式。你可以在交互模式中使用方向键来编辑以前的行(假定你已经打开了
GNU \prgname{readline} 库的支持)。Tab 键将自动补全无歧义的词;否则,再次按下
tab 将显示可能的选择。对交互模式的进一步的特殊命令在原手册第 9 章 Interactive
Mode\transnote{本译本未包括此章。} 描述。
\index{批处理模式}
在批处理模式下,\prgname{Asymptote} 从一个文件中直接读入命令。要试试这个,把
\lstinputlisting{diagonal.asy}
敲进一个文件,比方说 \prgname{test.asy}。然后用 \prgname{MSDOS} 或
\prgname{UNIX} 命令来执行此文件:
\begin{lstlisting}[language=]
asy -V test
\end{lstlisting}
\index{拖放}
\prgname{Windows} 用户\transnote{原手册误作 \prgname{MSDOS},或欲指
\prgname{Windows} 命令行,但不准确。此更正。}可以将文件拖放到桌面的
\prgname{asy} 图标或是把 \prgname{Asymptote} 作为扩展名为 \prgname{asy} 的文
件的默认应用程序。
\index{-V@\verb=-V=}
\index{-o@\verb=-o=}
\verb=-V= 选项打开一个 \prgname{PostScript} 查看窗口,从而你可以立即查看嵌入
式 \prgname{PostScript} 输出。默认将输出到文件 \prgname{test.eps};输出文件的
前缀可以通过 \verb=-o= 命令行参数改变。
可以用多于两个点画线并创建一个像这个正方形的环形路径:
\lstinputlisting{square.asy}
\begin{center}
\includegraphics{square}
\end{center}
\index{size@\inlinecode=size=}
通常直接以 \prgname{PostScript} 坐标工作是不方便的。下面的例子绘制一个放缩到
宽 $101$\,bp 和高 $101$\,bp 的单位正方形。其输出与前面的例子相同。
\begin{lstlisting}
size(101,101);
draw((0,0)--(1,0)--(1,1)--(0,1)--cycle);
\end{lstlisting}
\index{unitsquare@\inlinecode=unitsquare=}
为方便计,路径 |(0,0)--(1,0)--(1,1)--(0,1)--cycle| 可以用预定义的变量
|unitsquare| 代换,或等价地,|box((0,0),(1,1))|。
\index{pt@\inlinecode=pt=}
\index{cm@\inlinecode=cm=}
\index{mm@\inlinecode=mm=}
\index{inch@\inlinecode=inch=}
\index{inches@\inlinecode=inches=}
\index{size@\inlinecode=size=}
可以按 |pt|($1$\,|pt|${}=1/72.27$\,|inch|)、|cm|、|mm| 或是 |inches|%
\transnote{这里 \inlinecode{inch} 和 \inlinecode{inches} 意义相同,都是
英尺。}来设定尺寸。如果给定尺寸(|size|)参数为 0,则该方向没有约束;总的放缩
将由另一方向决定(见 \pageref{sec:framepicture} 页 \ref{sec:framepicture} 节
|size| 函数说明):
\lstinputlisting{bigsquare.asy}
\begin{center}
\includegraphics{bigsquare.pdf}
\end{center}
\index{unitsize@\inlinecode=unitsize=}
要使用户坐标表示正好 |1cm| 的倍数:
\begin{lstlisting}
unitsize(1cm);
draw(unitsquare);
\end{lstlisting}
还可以设定不同的 $x$ 与 $y$ 单位尺寸:
\begin{lstlisting}
unitsize(1cm,2cm);
draw(unitsquare);
\end{lstlisting}
\index{标签}
\index{label@\inlinecode=label=}
在 \prgname{Asymptote} 中很容易增加标签;用双引号引起的 \LaTeX{} 字符串、坐标
和可选的对齐方向来设定标签:
\lstinputlisting{labelsquare.asy}
\begin{center}
\includegraphics{labelsquare.pdf}
\end{center}
\index{罗盘方向}
\index{E@\inlinecode=E=}
\index{N@\inlinecode=N=}
\index{NE@\inlinecode=NE=}
\index{ENE@\inlinecode=ENE=}
\index{up@\inlinecode=up=}
\index{down@\inlinecode=down=}
\index{right@\inlinecode=right=}
\index{left@\inlinecode=left=}
\prgname{Asymptote} 使用标准的罗盘方向 |E=(1,0)|、|N=(0,1)|、|NE=unit(N+E)|
以及 |ENE=unit(E+NE)| 等等,并在 \prgname{Asymptote} 基本模块 \prgname{plain}
中定义有复数(|pair|) |up|、|down|、|right| 和 |left|。具有名为 |E| 的局部
变量的用户可以通过在前面缀以定义它的模块名来访问罗盘方向 |E|:|plain.E|。
这个例子绘制一条路径近似为 $1/4$ 圆:
\lstinputlisting{quartercircle.asy}
\begin{center}
\includegraphics{quartercircle.pdf}
\end{center}
\index{--@\inlinecode=--=}
\index{..@\inlinecode=..=}
\index{cycle@\inlinecode=cycle=}
\index{unitcircle@\inlinecode=unitcircle=}
\index{环形路径}
一般地,一条路径由一列复数(或其他路径)确定,它们以 |--| 相连来表示直线段或
|..| 表示三次样条。设定终止结点 |cycle| 建立一条平滑连回初始结点的环形路径,
如在这里对单位圆的近似(精确到 $0.06\%$ 以内):
\begin{lstlisting}
path unitcircle=E..N..W..S..cycle;
\end{lstlisting}
\index{三次样条曲线}
\index{结点}
\index{入向切线}
\index{出向切线}
三次样条曲线的每个内结点可以指定一个前缀或后缀的方向 |{dir}|:复数 |dir| 的
方向分别设定该曲线在此结点处,入向或出向切线的方向。外结点仅能在向内一边指定
方向限定。
\index{控制点}
\index{Bézier 曲线}
在带有后控制点 $c_0$ 的结点 $z_0$,与带有前控制点 $c_1$ 的结点 $z_1$ 之间的
一段三次样条曲线,按 Bézier 曲线
\begin{equation*}
(1-t)^3 z_0 + 3t(1-t)^2 c_0 + 3t^2 (1-t) c_1 + t^3 z_1, \qquad 0\leq t\leq 1
\end{equation*}
计算\transnote{本节关于三次 Bézier 样条曲线的讲解是比较难懂的,并且下图中关
于曲线离散生成的知识事实上与绘图无关。关于曲线控制的更清晰详细的介绍可以参考
\MP{} 的手册 \textit{\MP{}: A User's Manual} 第 4 章 Curves,以及关于 \MP{}
与 Con\TeX{}t 的文档 \textit{METAFUN} 的第 1 章。}。
\index{Bézier 曲线!离散生成}
如下图所示,由两个终点 $z_0$ 和 $z_1$ 以及两个控制点 $c_0$ 与 $c_1$ 构造的
三阶中点($m_5$),正是在由四元组 $(z_0, c_0, c_1, z_1)$ 建立的 Bézier 曲线
上对应于 $t = 1/2$ 的点。这就允许人们通过将重新提取的三阶中点用作终点,并
将二阶与一阶中点用作控制点,递归地构造想要的曲线:
\begin{center}
\includegraphics{bezier2.pdf}
\end{center}
这里 $m_0$, $m_1$ 和 $m_2$ 是一阶中点,$m_3$ 与 $m_4$ 是二阶中点,而 $m_5$ 是
三阶中点。于是曲线就通过对 $(z_0, m_0, m_3, m_5)$ 和 $(m_5, m_4, m_2, z_1)$
递归应用此算法构造出来。
事实上,类似的性质对于每小段中在 $[0,1]$ 上的任意分数 $t$ 位置的点都成立,而
不仅仅是中点($t=1/2$)。
\index{Bézier 曲线!的几何性质}
用这种方式构造的 Bézier 曲线具有如下性质:
\begin{itemize}
\item 它完全包含在给定四个点的凸包内。
\item 其起点朝向从第一个端点到第一个控制点而其终点朝向从第二个控制点到
第二个端点。
\end{itemize}
\index{controls@\inlinecode=controls=}
用户可以像这样设定两个结点间的显式控制点:
\begin{lstlisting}
draw((0,0)..controls (0,100) and (100,100)..(100,0));
\end{lstlisting}
然而,通常更为方便的是仅使用 |..| 运算符,它告诉 \prgname{Asymptote} 使用在
Donald Knuth 的专著 \textit{The \MF{}book} 的第 14 章中描述的算法来选择自己的
控制点。用户还可以通过设定方向、张力和卷曲值来定制路向(或路径)。
\index{张力}
张力越强,曲线越直,它也就越接近直线。可以将样条曲线的张力从其默认值 $1$ 变为
任意大于等于 $0.75$ 的实数值(参考 John D.~Hobby, \textit{Discrete and
Computational Geometry 1}, 1986):
\index{tension@\inlinecode=tension=}
\index{tension atleast@\inlinecode=tension atleast=}
\begin{lstlisting}
draw((100,0)..tension 2 ..(100,100)..(0,100));
draw((100,0)..tension 2 and 1 ..(100,100)..(0,100));
draw((100,0)..tension atleast 1 ..(100,100)..(0,100));
\end{lstlisting}
\index{卷曲参数}
卷曲参数设定在路径端点处的弯曲程度($0$ 表示直的;默认值 $1$ 表示近似为圆):
\index{curl@\inlinecode=curl=}
\begin{lstlisting}
draw((100,0){curl 0}..(100,100)..{curl 0}(0,100));
\end{lstlisting}
\index{::@\inlinecode=::=}
\MP{} 的 |...| 路径连接符,在可能时它要求一个限定在由端点和方向定义的三角形内
的无拐点的曲线,它在 \prgname{Asymptote} 中实现为 |.. tension atleast 1 ..|
的方便缩写 |::|(省略号 |...| 在 \prgname{Asymptote} 中用于标示可变长参数;见
\pageref{subsec:restarguments} 页 \ref{subsec:restarguments} 节剩余参数)。
例如,比较
\lstinputlisting{dots.asy}
\begin{center}
\includegraphics{dots.pdf}
\end{center}
与
\lstinputlisting{colons.asy}
\begin{center}
\includegraphics{colons.pdf}
\end{center}
\index{---@\inlinecode=---=}
\index{&@\inlinecode=&=}
|---| 连接符是 |..tension atleast infinity..| 的缩写\transnote{它的效果和通常
的折线连接基本相同,只是在弯折处比完全的折线更为圆滑。},而 |&| 连接符连结两条
路径,并去除第一条路径的第最后一个结点(它一般应该与第二条路径的第一个结点
重合)。
\index{^^@\inlinecode=^^=}
一条相连的 \prgname{Asymptote} 路径,与一条 \prgname{PostScript} 的
\verb=subpath=(子路径)等价。|^^| 二元运算符,它要求画笔从左边路径的终点移动
(而不绘制出来或影响终点的曲率)到右边路径的起点,可以用来将若干
\prgname{Asymptote} 路径聚集到一个 |path[]| 数组中(等价于一条
\prgname{PostScript} 路径):
\lstinputlisting{superpath.asy}
\begin{center}
\includegraphics{superpath.pdf}
\end{center}
\index{evenodd@\inlinecode=evenodd=}
\index{zerowinding@\inlinecode=zerowinding=}
这里 \prgname{PostScript} 奇偶(|evenodd|)填充规制设定仅在两个圆\transnote{%
原文误作单位圆。事实上这里其中一个圆已经放缩过。}之间界定的区域会被填充。在
这个例子中,如果小心地变换路径的方向,可以使用默认的零卷绕数(|zerowinding|)
填充规则得到同样的效果:
\begin{lstlisting}
filldraw(unitcircle^^reverse(g),yellow,black);
\end{lstlisting}
\index{^^@\inlinecode=^^=}
|^^| 运算符被 \prgname{three.asy} 中的 |box(triple, triple)| 函数用于无回溯
步骤地构造立方体 |unitbox| 的边:
\lstinputlisting{cube.asy}
\begin{center}
\includegraphics{cube.pdf}
\end{center}
见原手册 7.25 节 graph 模块\transnote{本译本未包括此节。}(或在线的
\prgname{Asymptote} 画廊和在 \url{http://asymptote.sourceforge.net} 发布的
外部链接)以得到更多例子,包括二维和三维的科学图形。额外的例子则由 Philippe
Ivaldi 在 \url{http://piprim.tuxfamily.org/asymptote/} 发布。
\chapter{绘图命令}
% 4 Drawing commands
\label{chap:drawingcommands}
\prgname{Asymptote} 的全部绘图功能都基于四个基本命令。三个
\prgname{PostScript} 绘图命令 |draw|、|fill| 和 |clip| 按执行顺序将对象加入
图,最近绘制的对象出现在顶层。标注命令 |label| 可用来添加文字标签和外部 EPS
图像,它会显示在 \prgname{PostScript} 对象的上面(鉴于这通常是人们想要的),
而又会以它们执行的相对次序显示。在一幅图(|picture|)上绘制对象后,图可以由
|shipout| 函数输出(见 \pageref{sec:framepicture} 页
\ref{sec:framepicture} 节 |shipout| 的说明)。
\index{layer@\inlinecode=layer=}
\index{层}
如果需要在标签(或逐字的 \TeX 命令,见 \pageref{sec:framepicture} 页
\ref{sec:framepicture} 节 |tex| 的说明)上面绘制 \prgname{PostScript}
对象,可以使用 |layer| 命令开始一个新的 \prgname{PostScript}/\LaTeX{} 层:
\begin{lstlisting}
void layer(picture pic=currentpicture);
\end{lstlisting}
|layer| 函数给人对象绘制的完全控制。层(layer)依次绘制,最近的层出现在顶层。
在每个层中,标签、图像、逐字 \TeX{} 命令总在该层中 \prgname{PostScript} 对象
之后绘制。
尽管下面一些绘图命令有很多选项,它们都有合理的默认值(例如,图参数默认为
|currentpicture|)。
\section{描绘(draw)}
% 4.1 draw
\index{描绘}
\index{draw@\inlinecode=draw=}
\begin{lstlisting}
void draw(picture pic=currentpicture, Label L="", path g,
align align=NoAlign, pen p=currentpen,
arrowbar arrow=None, arrowbar bar=None, margin margin=NoMargin,
Label legend="", marker marker=nomarker);
\end{lstlisting}
在图 |pic| 上使用画笔 |p| 描绘路径 |g|,并带可选的绘制属性(标签 |L|,显式
标签对齐 |align|,箭头与短杠 |arrow| 和 |bar|,边距 |margin|,图例
(|legend|),以及记号 |marker|。只有一个参数,即路径,是必需的。为方便计,
参数 |arrow| 与 |bar| 可按任何次序限定\transnote{即,不论使用的是
\inlinecode{draw(g, BeginBar, EndArrow)} 还是
\inlinecode{draw(g, EndArrow, BeginBar)},得到的图形效果是一样的。}。参数
|legend| 是构造可选图例项时使用的标签。
\index{None@\inlinecode=None=}
\index{BeginBar@\inlinecode=BeginBar=}
\index{EndBar@\inlinecode=EndBar=}
\index{Bars@\inlinecode=Bars=}
\index{barsize@\inlinecode=barsize=}
\index{短杠}
短杠在标明尺寸时很有用。|bar| 可取值 |None|、|BeginBar|、|EndBar|(或等价地
|Bar|)以及 |Bars|(在路径两端绘制短杠)。每个短杠设定(除了 |None|)将接受
一个可选的实数参数以 \prgname{PostScript} 坐标标明短杠的长度。设置的短杠长度
为 |barsize(pen)|。
\index{None@\inlinecode=None=}
\index{Blank@\inlinecode=Blank=}
\index{BeginArrow@\inlinecode=BeginArrow=}
\index{MidArrow@\inlinecode=MidArrow=}
\index{EndArrow@\inlinecode=EndArrow=}
\index{Arrow@\inlinecode=Arrow=}
\index{Arrows@\inlinecode=Arrows=}
\index{arrowhead@\inlinecode=arrowhead=}
\index{Default@\inlinecode=Default=}
\index{SimpleHead@\inlinecode=SimpleHead=}
\index{HookHead@\inlinecode=HookHead=}
\index{TeXHead@\inlinecode=TeXHead=}
\index{FillDraw@\inlinecode=FillDraw=}
\index{Fill@\inlinecode=Fill=}
\index{NoFill@\inlinecode=NoFill=}
\index{UnFill@\inlinecode=UnFill=}
\index{Draw@\inlinecode=Draw=}
\index{arrowsize@\inlinecode=arrowsize=}
\index{BeginArcArrow@\inlinecode=BeginArcArrow=}
\index{EndArcArrow@\inlinecode=EndArcArrow=}
\index{ArcArrow@\inlinecode=ArcArrow=}
\index{MidArcArrow@\inlinecode=MidArcArrow=}
\index{ArcArrows@\inlinecode=ArcArrows=}
\index{箭头}
|arrow| 可取值 |None|、|Blank|(不画出箭头与路径\transnote{利用它可以只画一组
短杠。})、|BeginArrow|、|MidArrow|、|EndArrow|(或等价地 |Arrow|)以及
|Arrows|(在路径的两端都绘制箭头)。除 |None| 和 |Blank| 外的所有箭头设定可以
给以可选的箭尖参数 |arrowhead|(预定义的箭头风格 |Default|、|SimpleHead|、
|HookHead|、|TeXHead| 之一),实数 |size|(\prgname{PostScript} 坐标的箭尖
尺寸),实数 |angle|(箭尖角度,以度计),填充类型 |filltype|(|FillDraw|、
|Fill|、|NoFill|、|UnFill|、|Draw| 之一)以及(除了 |MidArrow| 和 |Arrows|)
表示箭头顶端沿路径放置相对位置的实数 |position|(一个 |arctime|)。用画笔 |p|
绘制时,默认的箭尖尺寸为 |arrowsize(p)|。还有 |size| 与 |angle| 默认值略有
修改的适合曲线的箭头版本\transnote{其中如果使用 \inlinecode{TeXHead},尺寸过
大,而效果不佳。}:|BeginArcArrow|、|EndArcArrow|(或等价地 |ArcArrow|)、
|MidArcArrow| 以及 |ArcArrows|。
\index{NoMargin@\inlinecode=NoMargin=}
\index{BeginMargin@\inlinecode=BeginMargin=}
\index{EndMargin@\inlinecode=EndMargin=}
\index{Margins@\inlinecode=Margins=}
\index{Margin@\inlinecode=Margin=}
\index{labelmargin@\inlinecode=labelmargin=}
\index{BeginPenMargin@\inlinecode=BeginPenMargin=}
\index{EndPenMargin@\inlinecode=EndPenMargin=}
\index{PenMargin@\inlinecode=PenMargin=}
\index{PenMargins@\inlinecode=PenMargins=}
\index{DotMargin@\inlinecode=DotMargin=}
\index{dotfactor@\inlinecode=dotfactor=}
\index{BeginDotMargin@\inlinecode=BeginDotMargin=}
\index{EndDotMargin@\inlinecode=EndDotMargin=}
\index{DotMargins@\inlinecode=DotMargins=}
\index{TrueMargin@\inlinecode=TrueMargin=}
\index{边距}
边距可以用于收缩 |labelmargin(p)| 长的路径可见部分以避免与其他画出的对象
重叠。|margin| 的典型值为 |NoMargin|、|BeginMargin|、|EndMargin|(或等价地
|Margin|)以及 |Margins|(它在路径的两端都留下边距)。可以使用
|Margin(real begin, real end)|,以用于对齐标签的单位 |labelmargin(p)| 的倍数
来分别设定起始和终点的边距大小。此外,|BeginPenMargin|、|EndPenMargin|(或
等价地 |PenMargin|)、|PenMargins|、|PenMargin(real begin, real end)| 以画笔
线宽为单位确定边距,这里考虑在绘制路径或箭头时的画笔线宽。例如,使用
|DotMargin|,即 |PenMargin(-0.5*dotfactor,0.5*dotfactor)| 的缩写,来绘制从
通常的起点恰好到达宽度为 |dotfactor*linewidth(p)| 的终点的边界。修饰符
|BeginDotMargin|、|EndDotMargin| 和 |DotMargins| 与之类似。修饰符
|TrueMargin(real begin, real end)| 允许人们直接用 \prgname{PostScript} 单位
设定边距,而与画笔的线宽无关。
箭头、短杠和边距的用法在示例文件 \prgname{Pythagoras.asy}、%
\prgname{sqrtx01.asy} 和 \prgname{triads.asy} 中展示%
\transnote{\label{foot:pythagoras}这里仅给出 \prgname{Pythagoras.asy} 例子的
效果,代码略:\begin{center}
\includegraphics[height=5\baselineskip]{Pythagoras}
\end{center}}。
\index{图例}
一幅图 |pic| 的图例可以用下列过程对齐装入一帧:
\index{legend@\inlinecode=legend=}
\begin{lstlisting}
frame legend(picture pic=currentpicture, int perline=1,
real xmargin=legendmargin, real ymargin=xmargin,
real linelength=legendlinelength,
real hskip=legendhskip, real vskip=legendvskip,
real maxwidth=0, real maxheight=0,
bool hstretch=false, bool vstretch=false, pen p=currentpen);
\end{lstlisting}
这里 |xmargin| 与 |ymargin| 设定四周 $x$ 与 $y$ 边距,|perline| 设定每行的
条目数(默认为 1;0 表示自动选取此数目),|linelength| 设定路径线的长度,
|hskip| 与 |vskip| 设定行距(按图例条目尺寸的倍数),|maxwidth| 与
|maxheight| 设定可选的最终图例长宽上界(0 表示无限定),|hstretch| 和
|vstretch| 允许图例在水平和竖直方向伸缩,而 |p| 设定用于绘制边框的画笔。图例
的帧于是可以使用 |add| 和 |attach| 添加进来并相对图 |dest| 上一点对齐(参看
\pageref{sec:framepicture} 页 \ref{sec:framepicture} 节 |add| 函数)。
\index{画点}
\index{dotfactor@\inlinecode=dotfactor=}
要绘制一点,只要画出只包含一个单独点的路径。在 |plain| 模块中定义的命令 |dot|
画出一个点,它有等于显式画笔线宽或默认线宽放大 |dotfactor|(默认为 6)倍的
直径,并使用设定的填充类型(见 \pageref{sec:framepicture} 页
\ref{sec:framepicture} 节填充类型的说明):
\index{dot@\inlinecode=dot=}
\begin{lstlisting}
void dot(picture pic=currentpicture, pair z, pen p=currentpen,
filltype filltype=Fill);
void dot(picture pic=currentpicture, Label L, pair z, align align=NoAlign,
string format=defaultformat, pen p=currentpen, filltype filltype=Fill);
void dot(picture pic=currentpicture, Label[] L=new Label[], pair[] z,
align align=NoAlign, string format=defaultformat, pen p=currentpen,
filltype filltype=Fill)
void dot(picture pic=currentpicture, Label L, pen p=currentpen,
filltype filltype=Fill);
\end{lstlisting}
\index{defaultformat@\inlinecode=defaultformat=}
如果给定变量 |Label| 为第二个过程的 |Label| 参数,|format| 参数将用于在格式化
打点位置的字符串(这里默认值 |defaultformat| 为 |"$%.4g$"|)。
\iffalse$\fi % 欺骗编辑器语法高亮
\index{画路径上的点}
第三个过程在复数数组 |z| 的每个点处画一个点。还可以在一条路径的每个结点处打
一个点:
\index{dot@\inlinecode=dot=}
\begin{lstlisting}
void dot(picture pic=currentpicture, Label[] L=new Label[],
path g, align align=RightSide, string format=defaultformat,
pen p=currentpen, filltype filltype=Fill);
\end{lstlisting}
参看原手册 7.25 节 \prgname{graph} 模块和 7.8 节 \prgname{markers} 模块%
\transnote{本译本未包括此部分。}中给路径结点标记的更一般的途径。
要关于用户坐标 |origin| 绘制固定尺寸的对象(以 \prgname{PostScript}
坐标),使用过程
\index{draw@\inlinecode=draw=}
\begin{lstlisting}
void draw(pair origin, picture pic=currentpicture, Label L="", path g,
align align=NoAlign, pen p=currentpen, arrowbar arrow=None,
arrowbar bar=None, margin margin=NoMargin, Label legend="",
marker marker=nomarker);
\end{lstlisting}
\section{填充(fill)}
% 4.2 fill
\label{sec:fill}
\index{填充}
\index{fill@\inlinecode=fill=}
\begin{lstlisting}
void fill(picture pic=currentpicture, path g, pen p=currentpen);
\end{lstlisting}
使用画笔 |p|,在图 |pic| 上填充由环形路径 |g| 限定的区域内部。
\index{filldraw@\inlinecode=filldraw=}
还有一个方便的 |filldraw| 命令,它填充路径并绘制边界线。可以为每个操作分别
设定画笔:
\begin{lstlisting}
void filldraw(picture pic=currentpicture, path g, pen fillpen=currentpen,
pen drawpen=currentpen);
\end{lstlisting}
\index{fill@\inlinecode=fill=}
这个固定尺寸的 |fill| 版本允许人们填充一个关于用户坐标 |origin|,以
\prgname{PostScript} 坐标描述的对象:
\begin{lstlisting}
void fill(pair origin, picture pic=currentpicture, path g, pen p=currentpen);
\end{lstlisting}
这只不过是如下命令的方便缩写:
\begin{lstlisting}
picture opic;
fill(opic,g,p);
add(pic,opic,origin);
\end{lstlisting}
过程
\index{filloutside@\inlinecode=filloutside=}
\begin{lstlisting}
void filloutside(picture pic=currentpicture, path g, pen p=currentpen);
\end{lstlisting}
填充路径 |g| 的区域外部,向外直达图 |pic| 的当前边界。
\index{渐变!格状梯度光滑渐变}
在二维画笔数组 |p| 之上使用填充规则 |fillrule| 的格状梯度光滑渐变,可以由
\index{latticeshade@\inlinecode=latticeshade=}
\begin{lstlisting}
void latticeshade(picture pic=currentpicture, path g, bool stroke=false,
pen fillrule=currentpen, pen[][] p);
\end{lstlisting}
产生。如果 |stroke=true|,填充区域就与由 |draw(pic,g,fillrule+zerowinding)|
绘制的区域相同\transnote{即这里填充的是具有宽度的路径本身。};在这种情况下,
路径 |g| 不必是环形的。|p| 中的画笔必须属于同一颜色空间。可以使用函数
|rgb(pen)| 或 |cmyk(pen)| 来将画笔提升到一个较高的颜色空间,如在示例文件
\prgname{latticeshading.asy} 中展示的那样\transnote{文档此处和实际行为不符,
从早期版本开始,\inlinecode{latticeshade} 就一直会自动进行颜色空间的提升,从
灰阶到 RGB 到 CMYK,因而 \inlinecode{p} 参数不需要手工提升到同一颜色空间。
参看示例文件 \prgname{latticeshading.asy}:\\
\setbox0\vbox{\lstinputlisting{latticeshading.asy}}%
\includegraphics[height=\ht0]{latticeshading.pdf}\box0}。
\index{渐变!轴向梯度光滑渐变}
按线段 |a--b| 方向,从画笔 |pena| 到 |penb| 的轴向梯度光滑渐变可以由下面函数
得到\transnote{这里给一个例子:\\
\setbox0\vbox{\lstinputlisting{axialshade.asy}}%
\includegraphics[height=\ht0]{axialshade.pdf}\box0}:
\index{axialshade@\inlinecode=axialshade=}
\begin{lstlisting}
void axialshade(picture pic=currentpicture, path g, bool stroke=false,
pen pena, pair a,
pen penb, pair b);
\end{lstlisting}
\index{渐变!放射梯度光滑渐变}
从以 |a| 圆心 |ra| 为半径的圆上的画笔 |pena|,到以 |b| 为圆心 |rb| 为半径的
圆上的画笔 |penb| 的放射梯度光滑渐变,也是类似的:
\index{radialshade@\inlinecode=radialshade=}
\begin{lstlisting}
void radialshade(picture pic=currentpicture, path g, bool stroke=false,
pen pena, pair a, real ra,
pen penb, pair b, real rb);
\end{lstlisting}
放射渐变的例子在示例文件 \prgname{shade.asy}、\prgname{ring.asy} 和
\prgname{shadestroke.asy} 中\transnote{仅举 \prgname{shadestroke.asy}
一例:\\
\setbox0\vbox{\lstinputlisting{shadestroke.asy}}%
\includegraphics[height=\ht0]{shadestroke.pdf}\box0}。
\index{渐变!Gouraud 渐变}
在由顶点集 |z| 和边标志 |edges| 定义的三角格点上,使用填充规则 |fillrule| 及
画笔数组 |p| 中的顶点颜色的 Gouraud 渐变,由下面函数实现:
\index{gouraudshade@\inlinecode=gouraudshade=}
\begin{lstlisting}
void gouraudshade(picture pic=currentpicture, path g, bool stroke=false,
pen fillrule=currentpen, pen[] p, pair[] z,
int[] edges);
void gouraudshade(picture pic=currentpicture, path g, bool stroke=false,
pen fillrule=currentpen, pen[] p, int[] edges);
\end{lstlisting}
在第二种形式中,|z| 的元素取为依次路径 |g| 的各结点。|p| 中的画笔必须属于同一
颜色空间。Gouraud 渐变的例子在示例文件 \prgname{Gouraud.asy} 和实体几何模块
\prgname{solids.asy} 中提供\transnote{\inlinecode{p} 中画笔事实上会被自动提升
到同一颜色空间。\prgname{Gouraud.asy} 的效果如下(代码略):\\
\centerline{\includegraphics[scale=0.6]{Gouraud.pdf}}}。用于 Gouraud 渐变的
边标志的文档见
\url{http://partners.adobe.com/public/developer/en/ps/sdk/TN5600.SmoothShading.pdf}。
\index{渐变!张量积渐变}
在由路径数组 |b| 中的长度为 4 的 $n$ 环形路径界定的补片上,使用由 $n\times4$
的画笔数组 |p| 和 $n\times4$ 数组内的中间控制点确定的顶点颜色,使用填充规则
|fillrule| 的张量积渐变,由下面函数实现:
\index{tensorshade@\inlinecode=tensorshade=}
\begin{lstlisting}
void tensorshade(picture pic=currentpicture, path g, bool stroke=false,
pen fillrule=currentpen, pen[][] p, path[] b=g,
pair[][] z=new pair[][]);
\end{lstlisting}
\index{渐变!Coons 渐变}
如果数组 |z| 为空,将使用 Coons 渐变,其中颜色控制点会自动计算。|p| 中画笔
必须属于同一颜色空间。对只有单个补片($n=1$)的情形的更简单的界面也是可用的:
\index{tensorshade@\inlinecode=tensorshade=}
\begin{lstlisting}
void tensorshade(picture pic=currentpicture, path g, bool stroke=false,
pen fillrule=currentpen, pen[] p, path b=g,
pair[] z=new pair[]);
\end{lstlisting}
\index{渐变!填充连续一列路径之间的区域}
还可以使用给定的画笔数组,光滑渐变填充连续一列路径之间的区域:
\index{draw@\inlinecode=draw=}
\begin{lstlisting}
void draw(picture pic=currentpicture, path[] g, pen[] p);
\end{lstlisting}
张量积和 Coons 渐变的例子在示例文件 \prgname{tensor.asy}、
\prgname{Coons.asy}、\prgname{BezierSurface.asy} 和 \prgname{rainbow.asy}
中\transnote{这里仅举出最简单的 Coons 渐变的例子:\\
\setbox0\vbox{\lstinputlisting{Coons.asy}}%
\includegraphics[height=\ht0]{Coons.pdf}\box0}。
\index{渐变!函数式渐变}
用 pdf\LaTeX, \ConTeXt{} 和 pdf\TeX{} 的 \TeX{} 引擎可得到更一般的渐变的可能:
过程
\index{functionshade@\inlinecode=functionshade=}
\begin{lstlisting}
void functionshade(picture pic=currentpicture, path[] g, bool stroke=false,
pen fillrule=currentpen, string shader);
\end{lstlisting}
使用由字符串 |shader| 设定的 \prgname{PostScript} 计算过程,按填充规则
|fillrule|,在图 |pic| 上路径 |g| 内部的部分做渐变填充;该
(\prgname{PostScript})过程取 2 个参数,各自在 $[0,1]$ 取值,并返回
|colors(fillrule).length| 个颜色成分。函数式渐变在例子
\prgname{functionshading.asy} 中示例。
\index{反填充}
下面的过程使用 |evenodd| 剪裁连同 |^^| 运算符来反填充一个区域:
\index{unfill@\inlinecode=unfill=}
\begin{lstlisting}
void unfill(picture pic=currentpicture, path g);
\end{lstlisting}
\section{剪裁(clip)}
% 4.3 clip
\index{剪裁}
\index{clip@\inlinecode=clip=}
\begin{lstlisting}
void clip(picture pic=currentpicture, path g, stroke=false,
pen fillrule=currentpen);
\end{lstlisting}
使用填充规则 |fillrule|(见 \pageref{sec:pens} 页 \ref{sec:pens} 节),把图
|pic| 当前的内容按路径 |g| 界定的区域剪裁。如果 |stroke=true|,剪裁的部分与
|draw(pic,g,fillrule+zerowinding)| 绘制的区域相同;在这种情况下路径 |g| 不必
是环形的。图剪裁的例子,见原手册第 6 章 \LaTeX\ usage 的第一个例子\transnote{
本译本未包含此章。这里另给一个简单的例子:\\
\setbox0\vbox{\lstinputlisting{clippath.asy}}%
\includegraphics[height=\ht0]{clippath.pdf}\box0}。
\section{标注(label)}
% 4.4 label
\label{sec:label}
\index{标注}
\index{label@\inlinecode=label=}
\begin{lstlisting}
void label(picture pic=currentpicture, Label L, pair position,
align align=NoAlign, pen p=nullpen, filltype filltype=NoFill)
\end{lstlisting}
\index{align@\inlinecode=ailgn=}
\index{NoAlign@\inlinecode=NoAlign=}
\index{labelmargin@\inlinecode=labelmargin=}
\index{Align@\inlinecode=Align=}
\index{标签}
\index{对齐}
使用画笔 |p| 在图 |pic| 上绘制标签 |L|。如果 |align| 为 |NoAlign|,标签将在
用户坐标 |position| 居中;否则将以 |align| 方向从 |position| 以
\prgname{PostScript} 坐标偏移 |align*labelmargin(p)| 的距离对齐。常数 |Align|
可以用来按标签的左下角在 |position| 位置对齐。如果 |p| 为 |nullpen|,在标签中
设定的画笔(默认为 |currentpen|)将被使用。标签 |L| 既可以是一个字符串也可以
是通过调用如下函数之一得到的结构体:
\index{Label@\inlinecode=Label=}
\begin{lstlisting}
Label Label(string s="", pair position, align align=NoAlign,
pen p=nullpen, embed embed=Rotate, filltype filltype=NoFill);
Label Label(string s="", align align=NoAlign,
pen p=nullpen, embed embed=Rotate, filltype filltype=NoFill);
Label Label(Label L, pair position, align align=NoAlign,
pen p=nullpen, embed embed=L.embed, filltype filltype=NoFill);
Label Label(Label L, align align=NoAlign,
pen p=nullpen, embed embed=L.embed, filltype filltype=NoFill);
\end{lstlisting}
标签的文字可以通过左乘一个仿射变换(见 \pageref{sec:transforms} 页
\ref{sec:transforms} 节)而被放缩、倾斜、旋转或是平移。例如,%
|rotate(45)*xscale(2)*L| 首先在 $x$ 方向放缩 |L| 而后将其逆时针方向旋转
$45^\circ$。标签的最终位置还可以通过 \prgname{PostScript} 坐标转换平移:%
|shift(10,0)*L|。|embed| 参数决定标签如何随着嵌入(此标签的)图变换:
\begin{funclist}
\index{Shift@\inlinecode=Shift=}
\item |Shift| \go
仅随嵌入它的图平移;
\index{Rotate@\inlinecode=Rotate=}
\item |Rotate| \go
仅随嵌入它的图平移和旋转(默认值);
\index{Rotate@\inlinecode=Rotate=}
\item |Rotate(pair z)| \go
按(随图变换过的)向量 |z| 旋转;
\index{Slant@\inlinecode=Slant=}
\item |Slant| \go
仅随嵌入它的图平移、旋转、倾斜、反射;
\index{Scale@\inlinecode=Scale=}
\item |Scale| \go
随嵌入它的图的平移、旋转、倾斜、反射和放缩;
\end{funclist}
\index{标注路径}
要给一条路径加标签,用
\index{label@\inlinecode=label=}
\begin{lstlisting}
void label(picture pic=currentpicture, Label L, path g, align align=NoAlign,
pen p=nullpen, filltype filltype=NoFill);
\end{lstlisting}
\index{标签位置}
\index{position@\inlinecode=position=}
\index{Relative@\inlinecode=Relative=}
标签默认将位于路径的中点。另一种标签位置(一个在 0 与 |length(g)| 之间的
|arctime| 值,参看 \pageref{sec:pathsandguides} 页 \ref{sec:pathsandguides}
节 |arctime| 函数)可以在构造标签时用一个实数值 |position| 设定。位置
|Relative(real)| 确定一个相对于路径总弧长的位置\transnote{\label{foot:morelabel}
因而除了上面的提及的四种 \inlinecode{Label} 构造函数外,还有两种接受
\inlinecode{position} 类型参数的构造函数:
\begin{lstlisting}^^J
Label Label(string s, string size="", explicit position position,^^J
\ \ \ \ \ \ \ \ \ \ \ \ align align=NoAlign, pen p=nullpen, embed embed=Rotate,^^J
\ \ \ \ \ \ \ \ \ \ \ \ filltype filltype=NoFill);^^J
Label Label(Label L, explicit position position, align align=NoAlign,^^J
\ \ \ \ \ \ \ \ \ \ \ \ pen p=nullpen, embed embed=L.embed, filltype filltype=NoFill);^^J
\end{lstlisting}
其 \inlinecode{position} 参数即可以是一个实数,也可以是一个
\inlinecode{Relative(value)} 值。}。如下的方便缩写形式已经预定义好了:
\index{BeginPoint@\inlinecode=BeginPoint=}
\index{MidPoint@\inlinecode=MidPoint=}
\index{EndPoint@\inlinecode=EndPoint=}
\begin{lstlisting}
position BeginPoint=Relative(0);
position MidPoint=Relative(0.5);
position EndPoint=Relative(1);
\end{lstlisting}
\index{align@\inlinecode=align=}
\index{对齐}
\index{Relative@\inlinecode=Relative=}
路径标签以方向 |align| 对齐,它可由一个绝对罗盘方向(复数 |pair|)或相对于
路径本地方向北极轴测量的方向 |Relative(pair)| 设定\transnote{%
\inlinecode{Relative(N)} 就是路径在此位置的切向。有关这种 \inlinecode{Label}
的 \inlinecode{position} 和 \inlinecode{align} 参数的意义,如图所示:
\begin{center}\includegraphics{labelalign.pdf}\end{center}}。
\index{LeftSide@\inlinecode=LeftSide=}
\index{Center@\inlinecode=Center=}
\index{RightSide@\inlinecode=RightSide=}
为方便计,|LeftSide|、|Center| 和 |RightSide| 分别定义为 |Relative(W)|、%
|Relative((0,0))| 以及 |Relative(E)|。给 |LeftSide|、|Center| 和 |RightSide|
左乘一个实数放缩因子会把标签移得更靠近或更远离路径\transnote{除了上文和脚注
\ref{foot:morelabel} 中的六种 \inlinecode{Label} 构造函数,在 \prgname{plain}
模块中还有一种构造函数,它将标签位置(复数 \inlinecode{pair} 类型)自动转换为
标签的内容:
\begin{lstlisting}^^J
Label Label(pair position, align align=NoAlign, pen p=nullpen,^^J
\ \ \ \ \ \ \ \ \ \ \ \ embed embed=Rotate, filltype filltype=NoFill);^^J
\end{lstlisting}}。
\index{arrowlength@\inlinecode=arrowlength=}
带有从方向 |dir| 来指向 |b| 的长为 |arrowlength| 的固定尺寸箭头的标签,可以由
如下过程产生:
\index{arrow@\inlinecode=arrow=}
\begin{lstlisting}
void arrow(picture pic=currentpicture, Label L="", pair b, pair dir,
real length=arrowlength, align align=NoAlign,
pen p=currentpen, arrowbar arrow=Arrow, margin margin=EndMargin);
\end{lstlisting}
如果没有设定对齐方向(不论是在标签中还是明确以参数给出),可选的标签将使用
边距 |align|,以 |dir| 的方向对齐。
\index{graphic@\inlinecode=graphic=}
函数 |string graphic(string name, string options="")| 返回一个可以用于插入
嵌入式 \prgname{PostScript}(Encapsulated \prgname{PostScript},EPS)文件的
字符串。这里,|name| 是要插入的文件名而 |options| 是一个逗号分隔的参数列表
字符串:包括可选的边框(|bb=llx lly urx ury|)、宽度(|width=value|)、高度
(|height=value|)、旋转(|angle=value|)、放缩(|scale=factor|)、剪裁
(|clip=bool|)以及草稿模式(|draft=bool|)。|layer()| 函数可用来强迫将来的
对象在插入的图像上方绘制:
\begin{lstlisting}
label(graphic("file.eps","width=1cm"),(0,0),NE);
layer();
\end{lstlisting}
\index{baseline@\inlinecode=baseline=}
\index{基线}
|string baseline(string s, string template="\strut")| 函数可用于
扩大标签的边框以匹配给定的模板,从而这些标签的基线将排在同一水平线上。
参见 \prgname{Pythagoras.asy} 的例子\transnote{\prgname{Pythagoras.asy}
的例子见脚注 \ref{foot:pythagoras}。}。
\index{overwrite@\inlinecode=overwrite=}
用 |overwrite| 画笔属性(参看 \pageref{sec:pens} 页 \ref{sec:pens} 节),可以
避免标签互相覆写。
\index{object@\inlinecode=object=}
在 \prgname{plain\_Label.asy} 中定义的结构体 |object|(对象)允许以一致的行为
方式看待标签和帧。用如下过程可以将一组对象打包进一帧内:
\index{pack@\inlinecode=pack=}
\begin{lstlisting}
frame pack(pair align=2S ... object inset[]);
\end{lstlisting}
要绘制或填充围绕一个标签的方盒(或是椭圆或其他路径)并返回界定的对象,使用
如下过程之一:
\index{draw@\inlinecode=draw=}
\begin{lstlisting}
object draw(picture pic=currentpicture, Label L, envelope e,
real xmargin=0, real ymargin=xmargin, pen p=currentpen,
filltype filltype=NoFill, bool above=true);
object draw(picture pic=currentpicture, Label L, envelope e, pair position,
real xmargin=0, real ymargin=xmargin, pen p=currentpen,
filltype filltype=NoFill, bool above=true);
\end{lstlisting}
\index{envelope@\inlinecode=envelope=}
\index{box@\inlinecode=box=}
\index{roundbox@\inlinecode=roundbox=}
\index{ellipse@\inlinecode=ellipse=}
这里 |envelope| 是在 \prgname{plain\_boxes.asy} 中定义的边界绘制过程(参看
\pageref{sec:pathsandguides} 页 \ref{sec:pathsandguides} 节),如 |box|、
|roundbox| 或 |ellipse|。
\index{texpath@\inlinecode=texpath=}
函数 |path[] texpath(Label L)| 返回 \TeX{} 将要为绘制标签 |L| 而填充的路径。
\index{minipage@\inlinecode=minipage=}
函数 |string minipage(string s, width=100pt)| 可用于将字符串 |s| 格式化为一个
宽度为 |width| 的段落。这个例子利用 |minipage|、|clip| 和 |graphic| 来生成一
个 CD 标签:
\begin{center}
\includegraphics{CDlabel.pdf}
\end{center}
\lstinputlisting{CDlabel.asy}
\chapter{编程}
% 5 Programming
\label{chap:programming}
\index{控制结构}
\index{控制流}
这里是对程序设计语言 \prgname{Asymptote} 的一个介绍性的例子,强调其中与
C, C++ 和 Java 相似的控制结构\transnote{这个文档无疑是面向具有 C, C++ 或 Java
等语言编程经验的人的,因而这里关于控制流的说明简略得甚至有些过分,比如没有
给出 \inlinecode{do} 结构的例子。有需要的读者请参考相关语言和程序设计的其他
书籍。}。
\index{注释}
\index{//@\inlinecode=//=}
\index{/* */@\inlinecode=/* */=}
\index{声明变量}
\index{条件测试}
\index{if@\inlinecode=if=}
\index{else@\inlinecode=else=}
\index{循环}
\index{for@\inlinecode=for=}
\begin{lstlisting}
// 这是一行注释。
// 声明:声明 x 是一个 real 变量;
real x;
// 赋值:给 real 变量 x 赋值 1 。
x=1.0;
// 条件:测试 x 是否等于 1 。
if(x == 1.0) {
write("x 等于 1.0");
} else {
write("x 不等于 1.0");
}
// 循环:迭代 10 次
for(int i=0; i < 10; ++i) {
write(i);
}
\end{lstlisting}
\index{while@\inlinecode=while=}
\index{do@\inlinecode=do=}
\index{break@\inlinecode=break=}
\index{continue@\inlinecode=continue=}
正如在 C/C++ 中一样,\prgname{Asymptote} 支持 |while|, |do|, |break| 和
|continue| 语句。Java 风格的迭代遍历数组元素的简便形式也是支持的:
\index{for@\inlinecode=for=}
\index{遍历数组}
\begin{lstlisting}
// 迭代遍历一个数组
int[] array={1,1,2,3,5};
for(int k : array) {
write(k);
}
\end{lstlisting}
此外,它也支持许多上述语言之外的特征。
\section{数据类型}
% 5.1 Data types
\index{数据类型}
\prgname{Asymptote} 支持下列数据类型(除用户自定义类型外):
\begin{typelist}
\index{void@\inlinecode=void=}
\index{空类型}
\item[void] 空(|void|)类型仅用于无参数或无返回值的函数。
\index{bool@\inlinecode=bool=}
\index{布尔类型}
\index{true@\inlinecode=true=}
\index{false@\inlinecode=false=}
\item[bool] 布尔类型只能取值 |true| 或 |false|。例如:
\begin{lstlisting}
bool b = true;
\end{lstlisting}
定义一个布尔变量 |b| 并初始化为值 |true|。如果没有给定初始式:
\begin{lstlisting}
bool b;
\end{lstlisting}
其值假定为 |false|。
\index{bool3@\inlinecode=bool3=}
\index{扩展布尔类型}
\index{true@\inlinecode=true=}
\index{false@\inlinecode=false=}
\index{default@\inlinecode=default=}
\item[bool3] 一种扩展的布尔类型,可以取值 |true|, |default| 或 |false|。
|bool3| 类型可以与 |bool| 类型相互转换。|bool3| 的默认初始式为 |default|。
\index{int@\inlinecode=int=}
\index{整数}
\index{intMin@\inlinecode=intMin=}
\index{intMax@\inlinecode=intMax=}
\item[int] 整数类型;如果没有给定初始式,隐式的值假定为 |0|。整数允许的最小值
为 |intMin|,最大值为 |intMax|。
\index{real@\inlinecode=real=}
\index{实数}
\index{realEpsilon@\inlinecode=realEpsilon=}
\index{realDigits@\inlinecode=realDigits=}
\index{realMin@\inlinecode=realMin=}
\index{realMax@\inlinecode=realMax=}
\item[real] 实数;它将设为计算机结构的本地浮点类型的最大精度。实数的隐式
初始式为 |0.0|。实数具有精度 |realEpsilon|,有效数字为 |realDigits|。最小的
正实数为 |realMin|,而最大的正实数为 |realMax|。
\index{pair@\inlinecode=pair=}
\index{复数}
\index{x@\inlinecode=x=}
\index{y@\inlinecode=y=}
\index{实部}
\index{虚部}
\item[pair] 复数,即,实数构成的有序对 |(x, y)|。复数 |z| 的实部和虚部可读为
|z.x| 和 |z.y|。我们称 |x| 和 |y| 为复数数据元素的虚拟成员;然而,它们不能
直接修改。复数的隐式初始式为 |(0.0,0.0)|。
有许多方法取复数的复共轭:
\begin{lstlisting}
pair z=(3,4);
z=(z.x,-z.y);
z=z.x-I*z.y;
z=conj(z);
\end{lstlisting}
\index{I@\inlinecode=I=}
这里 |I| 是复数 |(0,1)|。复数类型定义了许多内部函数:
\begin{funclist}
\index{conf@\inlinecode=conf=}
\index{复共轭}
\item |pair conf(pair z)| \go
返回 |z| 的复共轭。
\index{length@\inlinecode=length=}
\index{模长}
\item |real length(pair z)| \go
返回参数 |z| 的复数模长 $\vert$|z|$\vert$。例如,
\begin{lstlisting}
pair z=(3,4);
length(z);
\end{lstlisting}
返回结果 5。|abs(pair)| 与 |length(pair)| 同义;
\index{angle@\inlinecode=angle=}
\index{幅角}
\item |real angle(pair z, bool warn=true)| \go
返回 |z| 的幅角,单位为弧度,在区间 $[-\uppi, \uppi]$ 内,而当 |warn| 为
|false| 且 |z|${}={}$|(0,0)| 时返回 |0|(而不是产生错误);
\index{degrees@\inlinecode=degrees=}
\index{幅角}
\item |real degrees(pair z, bool warn=true)| \go
返回 |z| 的幅角,单位为度,在区间 $[0,360)$ 内,而当 |warn| 为 |false| 且
|z|${}={}$|(0,0)| 时返回 |0|(而不是产生错误);
\index{unit@\inlinecode=unit=}
\index{单位向量}
\item |pair unit(pair z)| \go
返回以复数 |z| 同方向的单位向量;
\index{expi@\inlinecode=expi=}
\index{单位向量}
\item |pair expi(real angle)| \go
返回以弧度角 |angle| 为方向的单位向量;
\index{dir@\inlinecode=dir=}
\index{单位向量}
\item |pair dir(real degrees)| \go
返回以角度 |degrees| 为方向的单位向量;
\index{xpart@\inlinecode=xpart=}
\index{x 部分@$x$ 部分}
\index{实部}
\item |real xpart(pair z)| \go
返回 |z.x|;
\index{ypart@\inlinecode=ypart=}
\index{y 部分@$y$ 部分}
\index{虚部}
\item |real ypart(pair z)| \go
返回 |z.y|;
\index{realmult@\inlinecode=realmult=}
\index{逐元素乘积}
\item |pair realmult(pair z, pair w)| \go
返回逐元素乘积 |(z.x*w.x,z.y*w.y)|;
\index{dot@\inlinecode=dot=}
\index{点积}
\item |real dot(pair z, pair w)| \go
返回点积 |z.x*w.x+z.y*w.y|;
\index{minbound@\inlinecode=minbound=}
\item |pair minbound(pair z, pair w)| \go
返回 |(min(z.x,w.x),min(z.y,w.y))|;
\index{maxbound@\inlinecode=maxbound=}
\item |pair maxbound(pair z, pair w)| \go
返回 |(max(z.x,w.x),max(z.y,w.y))|。
\end{funclist}
\index{triple@\inlinecode=triple=}
\index{三元组}
\index{x@\inlinecode=x=}
\index{y@\inlinecode=y=}
\index{z@\inlinecode=z=}
\item[triple] 有序实数三元组(向量)|(x,y,z)| 用于三维作图。三元组 |v| 的对应
组成部分分别读为 |v.x|, |v.y| 和 |v.z|。隐式初始式为 |(0.0,0.0,0.0)|。
这里是三元组的内部函数:
\begin{funclist}
\index{length@\inlinecode=length=}
\index{模长}
\item |real length(triple v)| \go
返回向量 |v| 的长度 $\vert$|v|$\vert$。|abs(triple)| 与 |length(triple)| 同
义;
\index{polar@\inlinecode=polar=}
\index{余纬角}
\item |real polar(triple v, bool warn=true)| \go
返回 |v| 的余纬角,从 $z$ 轴计,单位为弧度,而当 |warn| 为 |false| 且
|v|${}={}$|O| 时\transnote{即 \inlinecode{v} 的所有坐标均为 0。
\inlinecode{O} 不是内部的常量,而是在 \prgname{three} 模块中定义的变量
\inlinecode{(0,0,0)}。后同。}返回 |0|(而不是产生错误);
\index{azimuth@\inlinecode=azimuth=}
\index{经度角}
\item |real azimuth(triple v, bool warn=true)| \go
返回 |v| 的经度角,从 $x$ 轴计,单位为弧度,而当 |warn| 为 |false| 且
|v.x|${}={}$|v.y|${}={}$|0| 时返回 |0|(而不是产生错误);
\index{colatitude@\inlinecode=colatitude=}
\index{余纬角}
\item |real colatitude(triple v, bool warn=true)| \go
返回 |v| 的余纬角,从 $z$ 轴计,单位为度,而当 |warn| 为 |false| 且
|v|${}={}$|O| 时返回 |0|(而不是产生错误);
\index{latitude@\inlinecode=latitude=}
\index{纬度角}
\item |real latitude(triple v, bool warn=true)| \go
返回 |v| 的纬度角,从 $xy$ 平面计,单位为度,而当 |warn| 为 |false| 且
|v|${}={}$|O| 时返回 |0|(而不是产生错误);
\index{longitude@\inlinecode=longitude=}
\index{经度角}
\item |real longitude(triple v, bool warn=true)| \go
返回 |v| 的经度角,从 $x$ 轴计,单位为度,而当 |warn| 为 |false| 且
|v.x|${}={}$|v.y|${}={}$|0| 则返回 |0|(而非产生错误);
\index{unit@\inlinecode=unit=}
\index{单位向量}
\item |triple unit(triple v)| \go
返回与三元组(向量)|v| 同方向的单位向量;
\index{expi@\inlinecode=expi=}
\index{单位向量}
\item |triple expi(real polar, real azimuth)| \go
返回方向为 |(polar,azimuth)|(单位为弧度)的单位向量;
\index{dir@\inlinecode=dir=}
\index{单位向量}
\item |triple dir(real colatitude, real longitude)| \go
返回方向为 |(colatitude,longitude)|(单位为度)的单位向量;
\index{xpart@\inlinecode=xpart=}
\index{x 部分@$x$ 部分}
\item |real xpart(triple v)| \go
返回 |v.x|;
\index{ypart@\inlinecode=ypart=}
\index{y 部分@$y$ 部分}
\item |real ypart(triple v)| \go
返回 |v.y|;
\index{zpart@\inlinecode=zpart=}
\index{z 部分@$z$ 部分}
\item |real zpart(triple v)| \go
返回 |v.z|;
\index{realmult@\inlinecode=realmult=}
\index{逐元素乘积}
\item |triple realmult(triple u, triple v)| \go
返回逐元素乘积\transnote{这个函数在原手册中缺失,这里根据源代码加上。}
|(u.x*v.x, u.y*v.y, u.z*v.z)|;
\index{dot@\inlinecode=dot=}
\index{点积}
\item |real dot(triple u, triple v)| \go
返回点积 |u.x*v.x+u.y*v.y+u.z*v.z|;
\index{cross@\inlinecode=cross=}
\index{叉积}
\item |triple cross(triple u, triple v)| \go
返回叉积 |(u.y*v.z-u.z*v.y,u.z*v.x-u.x*v.z,u.x*v.y-v.x*u.y)|;
\index{minbound@\inlinecode=minbound=}
\item |triple minbound(triple u, triple v)| \go
返回 |(min(u.x,v.x),min(u.y,v.y),min(u.z,v.z))|;
\index{maxbound@\inlinecode=maxbound=}
\item |triple maxbound(triple u, triple v)| \go
返回 |(max(u.x,v.x),max(u.y,v.y),max(u.z,v.z))|。
\end{funclist}
\index{string@\inlinecode=string=}
\index{字符串}
\item[string] 字符串,它使用 STL 的 \verb=string= 类实现。
\index{""@\inlinecode=""=}
\index{双引号}
\index{引号}
\index{映射}
用双引号(|"|)界定的字符串受限于下列映射,以允许在 \TeX{} 中双引号的使用(如
为了使用 \prgname{bable} 包,见文档 7.21 节 \prgname{babel}\transnote{本译本
不包括此部分。}):
\begin{itemize}
\item |\"| 映射到 |"|
\item |\\| 映射到 |\\|
\end{itemize}
\index{单引号}
\index{'@\inlinecode='=}
用单引号(|'|)界定的字符串具有与 ANSI C 相同的字符映射:
\index{映射}
\begin{itemize}
\item |\'| 映射到 |'|
\item |\"| 映射到 |"|
\item |\?| 映射到 |?|
\item |\\| 映射到反斜线
\item |\a| 映射到响铃
\item |\b| 映射到退格
\item |\f| 映射到换页符
\item |\n| 映射到换行符
\item |\r| 映射到回车符
\item |\t| 映射到制表符
\item |\v| 映射到竖直制表符
\item |\0|--|\377| 映射到对应的八进制字节
\item |\x0|--|xFF| 映射到对应的十六进制字节\transnote{在这里十六进制的字母
须大写,而 C 语言的转义符可用小写。另外,\prgname{Asymptote} 并没有像 C 中
那样八进制和十六进制整数类型的字面量(如 \inlinecode{0177}、
\inlinecode{0x2F})。}
\end{itemize}
\index{字符串!空串}
\index{字符串!拼接}
字符串的隐式初始式为空串 |""|。字符串可以用运算符 |+| 拼接。在下列字符串函数
中,位置 |0| 表示字符串的开始:
\begin{funclist}
\index{length@\inlinecode=length=}
\item |int length(string s)| \go
返回字符串 |s| 的长度;
\index{find@\inlinecode=find=}
\item |int find(string s, string t, int pos=0)| \go
返回字符串 |t| 在字符串 |s| 在位置 |pos| 处或之后,第一次出现的位置,或是
在 |t| 不是 |s| 的子串时返回 |-1|;
\index{rfind@\inlinecode=rfind=}
\item |int rfind(string s, string t, int pos=-1)| \go
返回字符串 |t| 在字符串 |s| 在位置 |pos| 处或之前(若 |pos|${}={}$|-1|,则
在字符串 |s| 末尾),最后一次出现的位置,或是在 |t| 不是 |s| 的子串时返回
|-1|;
\index{insert@\inlinecode=insert=}
\item |string insert(string s, int pos, string t)| \go
返回将字符串 |t| 插入 |s| 位置 |pos| 处构成的字符串;
\index{erase@\inlinecode=erase=}
\item |string erase(string s, int pos, int n)| \go
返回将字符串 |s| 在位置 |pos| 处删去长度为 |n| 的串(如果 |n|${}={}$|-1|,
删至字符串 |s| 末尾)构成的字符串;
\index{substr@\inlinecode=substr=}
\item |string substr(string s, int pos, int n=-1)| \go
返回从位置 |pos| 开始长度为 |n|(如果 |n|${}={}$|-1|,直至字符串 |s| 的
末尾)的子串;
\index{reverse@\inlinecode=reverse=}
\item |string reverse(string s)| \go
返回由字符串 |s| 翻转构成的字符串;
\index{replace@\inlinecode=replace=}
\item |string replace(string s, string before, string after)| \go
返回将字符串 |s| 中所有串 |before| 的出现改为串 |after| 得到的字符串;
\index{replace@\inlinecode=replace=}
\item |string replace(string s, string[][] table)| \go
返回将字符串 |s| 中将数组 |table| 中字符串对 |{before,after}| 中的串
|before| 的所有出现转换为对应的串 |after| 构成的字符串;
\index{split@\inlinecode=split=}
\item |string[] split(string s, string delimiter)| \go
返回一个字符串数组,包含将 |s| 用 |delimiter| 定界分割而成的子串;
\index{format@\inlinecode=format=}
\item |string format(string s, int n)| \go
返回使用当前区域设置(locale),将 |n| 用 C 风格格式化串格式化得到的字符
串\transnote{例如,\inlinecode{format("\%04d", 3)} 得到字符串
\inlinecode{"0003"}。由于 \prgname{Asymptote} 中只有一种整数类型,所以表示
整数长度的修饰符 \inlinecode{h}、\inlinecode{l}、\inlinecode{ll} 是无用的。
可用的格式符主要为 \inlinecode{\%d}(或 \inlinecode{\%i},十进制整数)、
\inlinecode{\%o}(八进制)、\inlinecode{\%x}(十六进制小写)、
\inlinecode{\%X}(十六进制大写)、\inlinecode{\%u}(无符号十进制整数),
字母前可用最小宽度、前导零、\inlinecode{\#} 和正负号修饰。这里文档未提及的
\inlinecode{\#} 修饰符表示输出八进制和十六进制数时同时输出 \inlinecode{0}
或 \inlinecode{0x}(\inlinecode{0X})前缀,正号前缀表示对正数也总输出正号
(负数不变),负号前缀表示左对齐(在限定最小输出宽度时有用)。当然,这里不
支持 \inlinecode{\%*d} 这种具有不同函数原型的复杂语法(其功能可以用
\inlinecode{format("\%"+string(4)+"d", 3)} 这种用法代替)。};
\index{format@\inlinecode=format=}
\item |string format(string s=defaultformat, real x, string locale="")| \go
返回使用区域设置 |locale|(或当前区域设置,如果设定为空串),按照 C 函数
|fprintf| 的行为方式用 C 风格格式化串格式化 |x| 得到的字符串,除了只允许
一个数据域时,末尾的零默认会被删去(除非设定了 |#|),并且(若格式串设定
了数学模式)\TeX{} 将用于排版科学
计数法\transnote{\prgname{Asymptote} 中数字没有千分位,区域设置通常主要会
影响小数点(有些区域设置使用逗号),不过译者在 \prgname{Windows} 版本下没能
成功改变这个设置。表示双精度的 \inlinecode{L} 是无用的。可用的格式化符主要
为 \inlinecode{\%f}(纯小数形式)、\inlinecode{\%e}(科学计数法)、
\inlinecode{\%g}(自动选取纯小数和科学计数法中相对较简短的形式),字符前
可用最小宽度、精度、前导零、\inlinecode{\#} 和正负号(作用与整数
\inlinecode{format(string, int)} 的参数相同)来修饰。注意从 1.77 版开始引入
的对数学模式的判断并不准确,函数只是简单地搜索格式串中是否有字符
\texttt{\$}。使用格式串修饰符的例子如:
\inlinecode{format("\%010.5f", pi)} 得到字符串 \inlinecode{0003.14159},
\inlinecode{format("\%+.3g", 0.00001234)} 得到字符串
\inlinecode{+1.23e-05},而 \inlinecode{format("\$\%+.3g\$", 0.00001234)} 则
得到 \inlinecode{\$+1.23\\!\\times\\!10\^\{-5\}\$}。};
\index{hex@\inlinecode=hex=}
\item |int hex(string s)| \go
将十六进制字符串 |s| 转换为一个整数。
\index{string@\inlinecode=string=}
\item |string string(real x, int digits=realDigits)| \go
使用精度 |digits| 和 C 区域设置(locale)将 |x| 转换为字符串;
\index{locale@\inlinecode=locale=}
\item |string locale(string s="")| \go
设定区域设置为给定的字符串(如果此字符串非空),并返回当前的区域设置;
\index{time@\inlinecode=time=}
\item |string time(string format="%a %b %d %T %Z %Y")| \go
返回用 ANSI C 过程 \prgname{strftime},依字符串 |format| 和当前区域设置
格式化的当前时间\transnote{如下日期格式串的格式化字符表格参考 K\&R 和 C99
标准中的描述,并据实际测试和后面文档的说明略有改动:\par
\begin{tabular}{llll}
\hline
\inlinecode{\%a} & 缩写的星期名。 &
\inlinecode{\%A} & 全写的星期名。 \\
\inlinecode{\%b} & 缩写的月份名。 &
\inlinecode{\%B} & 全写的月份名。 \\
\inlinecode{\%c} & 本地(当前区域设置)的日期与时间表示。 &
\inlinecode{\%C} & 年份的世纪部分(年份除以 100 并取整的结果)。 \\
\inlinecode{\%d} & 一月内的日期(01--31)。 &
\inlinecode{\%D} & 等价于 \inlinecode{\%m/\%d/\%y}。 \\
\inlinecode{\%e} & 一月内的日期(1--31);一位数前面用空格补齐。 &
\inlinecode{\%F} & 等价于 \inlinecode{\%Y-\%m-\%d}(此为 ISO 8601 日期格式)。 \\
\inlinecode{\%g} & ISO 8601 基于星期的年份的后两位(00--99)。 &
\inlinecode{\%G} & 基于星期的年份。 \\
\inlinecode{\%h} & 等价于 \inlinecode{\%b}。 &
\inlinecode{\%H} & 小时(24 小时制)(00--23)。 \\
\inlinecode{\%I} & 小时(12 小时制)(01--12)。 &
\inlinecode{\%j} & 一年内的天数(001--366)。 \\
\inlinecode{\%m} & 月份(01--12)。 &
\inlinecode{\%M} & 分钟(00--59)。 \\
\inlinecode{\%n} & 换行符。 &
\inlinecode{\%p} & AM 或 PM 的本地等价物。 \\
\inlinecode{\%r} & 本地的 12 小时制时间。 &
\inlinecode{\%R} & 等价于 \inlinecode{\%H:\%M}。 \\
\inlinecode{\%S} & 秒(00--60)。 &
\inlinecode{\%t} & 水平制表符。 \\
\inlinecode{\%T} & 等价于 \inlinecode{\%H:\%M:\%S}(此为 ISO 8601 时间格式)。 &
\inlinecode{\%u} & 星期(1--7),其中周一是 1。 \\
\inlinecode{\%U} & 一年内的星期数(周日是每星期的第一天)(00--53)。 &
\inlinecode{\%w} & 星期(0--6,周日是 0)。 \\
\inlinecode{\%W} & 一年内的星期数(周一是每星期的第一天)(00--53)。 &
\inlinecode{\%V} & ISO 8601 星期数(01--53)。 \\
\inlinecode{\%x} & 本地的日期表示。 &
\inlinecode{\%X} & 本地的时间表示。 \\
\inlinecode{\%y} & 不含世纪的年(00--99)。 &
\inlinecode{\%Y} & 含世纪的年。 \\
\inlinecode{\%z} & \parbox[t]{24em}{当前时间与 UTC(协调世界时)的差距,用 ISO
8601 格式表示。例如 \inlinecode{"+0800"} 表示比格林威治西部的 UTC 时间大
(早)8 小时 0 分。若时区不确定则没有字符。} &
\inlinecode{\%\%} & 符号 \inlinecode{\%} 本身。 \\
\hline
\end{tabular}\par
以上只是简略解释,此外还有一些复合形式。更多详情请参看相关手册。}。因而
\begin{lstlisting}
time();
time("%a %b %d %H:%M:%S %Z %Y");
\end{lstlisting}
是返回以 \prgname{Unix date} 命令默认格式化的当前时间的等价的方法;
\index{seconds@\inlinecode=seconds=}
\item |int seconds(string t="", string format="")| \go
返回新纪元(UTC 1970 年 1 月 1 日星期四,00:00:00)之后到某一时间的秒数。
此时间由 ANSI C 过程 \prgname{strptime} 使用当前区域设置按字符串 |format|
确定,或在 |t| 为空串时取当前时间。注意 POSIX \prgname{strptime} 说明的 |"%Z"|
扩展在目前的 GNU C 库中被忽略。如果产生错误,返回值 |-1|。这里是一些例子:
\begin{lstlisting}
seconds("Mar 02 11:12:36 AM PST 2007","%b %d %r PST %Y");
seconds(time("%b %d %r %z %Y"),"%b %d %r %z %Y");
seconds(time("%b %d %r %Z %Y"),"%b %d %r "+time("%Z")+" %Y");
1+(seconds()-seconds("Jan 1","%b %d"))/(24*60*60);
\end{lstlisting}
最后一个例子返回从元旦开始,今天的日期序数\transnote{相当于
\inlinecode{(int)time("\%j")}。}。
\index{time@\inlinecode=time=}
\item |string time(int seconds, string format="%a %b %d %T %Z %Y")| \go
返回对应于新纪元(UTC 1970 年 1 月 1 日星期四,00:00:00)之后 |seconds| 秒
的时间,由 ANSI C 过程 \prgname{strftime} 使用当前区域设置按字符串 |format|
格式化。例如,要返回对应于 24 小时之前的日期:
\begin{lstlisting}
time(seconds()-24*60*60);
\end{lstlisting}
\index{abort@\inlinecode=abort=}
\item |void abort(string s)| \go
中止执行(在批处理模式时返回一个非零值);如果字符串 |s| 非空,则会打印由
源文件名、行号和 |s| 构成的诊断信息。
\index{exit@\inlinecode=exit=}
\item |void exit()| \go
在批处理模式以错误返回值零退出\transnote{程序退出时返回值为零即为无错误。}。
\index{sleep@\inlinecode=sleep=}
\item |void sleep(int seconds)| \go
暂停给定的秒数。
\index{usleep@\inlinecode=usleep=}
\item |void usleep(int microseconds)| \go
暂停给定的毫秒数。
\index{beep@\inlinecode=beep=}
\item |void beep()| \go
在控制台产生一声蜂鸣(哔声)。
\end{funclist}
\end{typelist}
\index{typedef@\inlinecode=typedef=}
像在 C/C++ 中一样,复合类型可以用 |typedef| 缩写\transnote{但
\inlinecode{typedef} 定义的类型缩写对函数类型有一定限制:它不能用于
\inlinecode{new} 之后构成匿名函数,其中也不能有函数的默认参数(注意默认参数
是在函数定义时求值的)。}(见 \pageref{sec:functions} 页 \ref{sec:functions}
节的例子)。
\section{路径(path)与路向(guide)}
% 5.2 Paths and guides
\label{sec:pathsandguides}
\begin{typelist}
\index{path@\inlinecode=path=}
\index{路径}
\item[path] 解析为确定路径的三次样条。路径的隐式的初始式为 |nullpath|。
\index{圆}
\index{circle@\inlinecode=circle=}
\index{unitcircle@\inlinecode=unitcircle=}
例如,过程 |circle(pair c, real r)|,返回一条近似为原点在 |c| 半径为 |r| 的
圆的 Bézier 曲线,它基于 |unitcircle|(见 \pageref{chap:tutorial} 页第%
\ref{chap:tutorial}章\transnote{\inlinecode{unitcircle} 的定义为
\begin{lstlisting}^^J
path unitcircle=E..N..W..S..cycle;^^J
\end{lstlisting}
即使用 4 个节点的闭三次样条曲线近似表示的单位圆路径,起点在
\inlinecode{(1,0)} 点,方向为逆时针方向。}):
\begin{lstlisting}
path circle(pair c, real r)
{
return shift(c)*scale(r)*unitcircle;
}
\end{lstlisting}
\index{Circle@\inlinecode=Circle=}
如果需要高精度,可以用模块 \prgname{graph.asy} 中定义的 |Circle| 过程来产生
真圆\transnote{即返回结点数为 \inlinecode{nCircle} 的路径来逼近圆。在
\prgname{graph} 模块中,\inlinecode{nCircle} 的默认值是 $400$,实用上这个数值
太大了——原来四个结点的曲线仅和精确的圆相差不足 $0.06\%$,太多的结点在输出
设备上体现不出来,却要大大增加绘制、填充等路径操作的计算量。因此译者建议实用
中先重定义 \inlinecode{nCircle} 为较小的数值。}:
\index{Circle@\inlinecode=Circle=}
\begin{lstlisting}
import graph;
path Circle(pair c, real r, int n=nCircle);
\end{lstlisting}
\index{圆弧}
\index{arc@\inlinecode=arc=}
与 |circle| 一致,圆心为 |c| 半径为 |r|,从 |angle1| 到 |angle2| 度,在
|angle2|${}\geq{}$|angle1| 时逆时针绘制的圆弧,可以这样构造:
\index{arc@\inlinecode=arc=}
\begin{lstlisting}
path arc(pair c, real r, real angle1, real angle2);
\end{lstlisting}
也可以明确指定方向:
\begin{lstlisting}
path arc(pair c, real r, real angle1, real angle2, bool direction);
\end{lstlisting}
\index{逆时针}
\index{顺时针}
\index{CCW@\inlinecode=CCW=}
\index{CW@\inlinecode=CW=}
这里方向 |direction| 可以指定为 |CCW|(counter-clockwise,逆时针)或 |CW|%
(clockwise,顺时针)。为方便计,从复数(点)|z1| 到 |z2|,圆心为 |c| 的圆弧
(假定\transnote{从 \prgname{plain} 模块源码来看,这个函数实际得到的是圆心在
\inlinecode{c},起点在 \inlinecode{z1},圆心到终点的方向为
\inlinecode{z2}${}-{}$\inlinecode{c} 的圆弧。即半径仅由 \inlinecode{c} 和
\inlinecode{z1} 确定。}
$\vert$|z2|${}-{}$|c|$\vert=\vert$|z1|${}-{}$|c|$\vert$)也可以这样构造:
\begin{lstlisting}
path arc(pair c, explicit pair z1, explicit pair z2,
bool direction=CCW)
\end{lstlisting}
如果需要高精度,可以用模块 \prgname{graph.asy} 中定义的过程来产生 |n| 个结点
的 Bézier 曲线真圆弧:
\index{Arc@\inlinecode=Arc=}
\begin{lstlisting}
import graph;
path Arc(pair c, real r, real angle1, real angle2, bool direction,
int n=nCircle);
path Arc(pair c, real r, real angle1, real angle2, int n=nCircle);
path Arc(pair c, explicit pair z1, explicit pair z2,
bool direction=CCW, int n=nCircle);
\end{lstlisting}
\index{椭圆}
椭圆可以用此过程绘制:
\index{ellipse@\inlinecode=ellipse=}
\begin{lstlisting}
path ellipse(pair c, real a, real b)
{
return shift(c)*scale(a,b)*unitcircle;
}
\end{lstlisting}
这个例子说明了在手册第\ref{chap:tutorial}章\nameref{chap:tutorial}讨论的全部
五种路向连接:
\lstinputlisting{join.asy}
\begin{center}
\includegraphics{join.pdf}
\end{center}
这里是一些对路径有用的函数:
\begin{funclist}
\index{length@\inlinecode=length=}
\item |int length(path p);| \go
这是路径 |p| 的(直线或三次曲线)段数。如果 |p| 是环形,这就与 |p| 的结点
数相同。
\index{size@\inlinecode=size=}
\item |int size(path p);| \go
这是路径 |p| 的结点数。如果 |p| 是环形,这就与 |length(p)| 相同。
\index{cyclic@\inlinecode=cyclic=}
\item |bool cyclic(path p);| \go
返回 |true| 当且仅当路径 |p| 为环形\transnote{即封闭路径。}。
\index{straight@\inlinecode=straight=}
\item |bool straight(path p, int i);| \go
返回 |true| 当且仅当路径 |p| 在结点 |i| 和结点 |i+1| 之间的线段是直的。
\index{piecewisestraight@\inlinecode=piecewisestraight=}
\item |bool piecewisestraight(path p);| \go
返回 |true| 当且仅当路径 |p| 是分段直的(即为折线)。
\index{point@\inlinecode=point=}
\item |pair point(path p, int t);| \go
如果 |p| 是环形,返回结点 |t| mod |length(p)| 的坐标。否则,返回结点 |t|
的坐标,除非 |t|${}<0$(此时返回 |point(0)|)或 |t|${}>{}$|length(p)|(此时
返回 |point(length(p))|)。
\index{point@\inlinecode=point=}
\item |pair point(path p, real t);| \go
这将返回结点 |floor(t)| 与 |floor(t)+1| 之间对应于三次样条参数 |t-floor(t)|
的点的坐标(参看手册第\ref{chap:tutorial}章\nameref{chap:tutorial} Bézier
曲线部分)。如果 |t| 在 $[0,{}$|length(p)|$]$ 的范围之外,则在 |p| 是环形的
情况下首先模 |length(p)| 约化或在 |p| 非环形的情况下转化为 |p| 的对应终点。
\index{dir@\inlinecode=dir=}
\item |pair dir(path p, int t, int sign=0, bool normalize=true);| \go
如果 |sign|${}<0$,则返回路径 |p| 在结点 |t| 处的入向切线方向(作为一个
复数);如果 |sign|${}>0$,则返回出向切线方向。如果 |sign|${}=0$,则返回
两个方向的平均值。
\index{dir@\inlinecode=dir=}
\item |pair dir(path p, real t, bool normalize=true);| \go
返回路径 |p| 在结点 |floor(t)| 和 |floor(t)+1| 之间对应于三次样条参数
|t-floor(t)| 的点的切线方向(参看手册第\ref{chap:tutorial}章%
\nameref{chap:tutorial},Bézier 曲线部分)。
\index{accel@\inlinecode=accel=}
\item |pair accel(path p, int t, int sign=0);| \go
如果 |sign|${}<0$,则返回路径 |p| 在结点 |t| 处的入向加速度(作为一个
复数);如果 |sign|${}>0$,则返回出向加速度。如果 |sign|${}=0$,则返回
两个加速度的平均值。
\index{accel@\inlinecode=accel=}
\item |pair accel(path p, real t);| \go
返回路径 |p| 在点 |t| 处的加速度。
\index{radius@\inlinecode=radius=}
\item |pair radius(path p, real t);| \go
返回路径 |p| 在点 |t| 处的半径\transnote{指曲率半径,即密切圆的半径。}。
\index{precontrol@\inlinecode=precontrol=}
\item |pair precontrol(path p, int t);| \go
返回 |p| 在结点 |t| 处的前控制点。
\index{precontrol@\inlinecode=precontrol=}
\item |pair precontrol(path p, real t);| \go
返回 |p| 在参数 |t| 处的有效前控制点。
\index{postcontrol@\inlinecode=postcontrol=}
\item |pair postcontrol(path p, int t);| \go
返回 |p| 在结点 |t| 处的后控制点。
\index{postcontrol@\inlinecode=postcontrol=}
\item |pair postcontrol(path p, real t);| \go
返回 |p| 在结点 |t| 处的有效后控制点。
\index{arclength@\inlinecode=arclength=}
\item |real arclength(path p);| \go
返回路径 |p| 表示的分段线性或三次曲线的长度(按用户坐标)。
\index{arctime@\inlinecode=arctime=}
\item |real arctime(path p, real L);| \go
返回路径的“时刻\transnote{指曲线的时间参数。}”,一个在 0 和路径长度之间,
在 |point(path p, real t)| 意义下的实数,在此时刻累积弧长(从路径开始处
度量)等于 |L|。
\index{dirtime@\inlinecode=dirtime=}
\item |real dirtime(path p, pair z);| \go
返回第一个“时刻”,它是在 0 和路径长度之间,在 |point(path, real)| 意义下
的实数,在此时刻路径的切线具有复数 |z| 的方向,否则若没有这样的时刻则返回
|-1|。
\index{reltime@\inlinecode=reltime=}
\item |real reltime(path p, real l);| \go
返回路径 |p| 在其弧长的相对分数 |l| 处的时刻。
\index{relpoint@\inlinecode=relpoint=}
\item |pair relpoint(path p, real l);| \go
返回路径 |p| 在其弧长的相对分数 |l| 处的点。
\index{midpoint@\inlinecode=midpoint=}
\item |pair midpoint(path p);| \go
返回路径 |p| 在其一半弧长处的点。
\index{reverse@\inlinecode=reverse=}
\item |path reverse(path p);| \go
返回一条沿 |p| 反向行进的路径。
\index{subpath@\inlinecode=subpath=}
\item |path subpath(path p, int a, int b);| \go
返回 |p| 从结点 |a| 到结点 |b| 行进的子路径。如果 |a|${}<{}$|b|,则子路径的
方向会反向。
\index{subpath@\inlinecode=subpath=}
\item |path subpath(path p, real a, real b);| \go
返回 |p| 从路径时刻 |a| 到路径时刻 |b| 行进的子路径。如果 |a|${}<{}$|b|,则
子路径的方向会反向。
\index{intersect@\inlinecode=intersect=}
\item |real[] intersect(path p, path q, real fuzz=-1);| \go
如果 |p| 与 |q| 至少有一个交点,则返回一个长度为 2 的实数数组,它包含表示
对于这样一个交点(交点按 \textit{The \MF{}book} 137 页描述的算法选择%
\transnote{此算法可简单描述如下:设 \inlinecode{p} 与 \inlinecode{q} 的长度
分别为 $m$ 和 $n$(从而两路径分别有 $m$ 段和 $n$ 段),先按字典序找出最小的
相交的段(即相交曲线段对中 \inlinecode{p} 中序号最小段和与之相交的
\inlinecode{q} 的段中序号最小的段),设此段中某交点时刻 $(u, v)$ 按二进制
小数表示分别为 $(.t_1 t_2 \ldots)_2$ 和 $(.u_1 u_2 \ldots)_2$,选取使得
“交错二进制”表示 $(.t_1 u_1 t_2 u_2 \ldots)_2$ 最小的时刻对 $(u, v)$。
不难看出,这种算法的就是简单的一个二重循环找交点再进行排序的算法,实现上较
简易。}),在 |point(path p, real t)| 意义下,分别沿 |p| 和 |q| 的路径
时刻。计算以 |fuzz| 确定的绝对误差执行,或者如果 |fuzz|${}<0$,则达到机器
精度。如果路径不相交,返回一个长度为 0 的实数数组。
\index{intersections@\inlinecode=intersections=}
\item |real[][] intersections(path p, path q, real fuzz=-1);| \go
以排序的(参看 \pageref{sec:arrays} 页 \ref{sec:arrays} 节排序的介绍),
长度为 2 的实数数组的数组,返回路径 |p| 和 |q| 的所有(除非有无穷多个)相交
时刻。计算以 |fuzz| 确定的绝对误差进行,或者如果 |fuzz|${}<0$,则达到机器
精度。
\index{intersections@\inlinecode=intersections=}
\item |real[] intersections(path p, explicit pair a, explicit pair b, real fuzz=-1);| \go
以已排序数组返回路径 |p| 与通过 |a|、|b| 两点的(无穷)直线的所有(除非有
无穷多个)相交时刻。返回的相交时刻保证在绝对误差 |fuzz| 以内正确,或者,
如果 |fuzz|${}<0$,则达到机器精度。
\index{times@\inlinecode=times=}
\item |real[] times(path p, real x)| \go
返回路径 |p| 与通过 |(x,0)| 点的竖直直线的所有相交时刻。
\index{times@\inlinecode=times=}
\item |real[] times(path p, explicit pair z)| \go
返回路径 |p| 与通过 |(0,z.y)| 点的水平直线的所有相交时刻。
\index{mintimes@\inlinecode=mintimes=}
\item |real[] mintimes(path p)| \go
返回长度为 2,包含路径 |p| 分别达到其最小水平与竖直范围的时刻的数组。
\index{maxtimes@\inlinecode=maxtimes=}
\item |real[] maxtimes(path p)| \go
返回长度为 2,包含路径 |p| 分别达到其最大水平与竖直范围的时刻的数组。
\index{intersectionpoint@\inlinecode=intersectionpoint=}
\item |pair intersectionpoint(path p, path q, real fuzz=-1);| \go
返回交点 |point(p,intersect(p,q,fuzz)[0])|。
\index{intersectionpoints@\inlinecode=intersectionpoints=}
\item |pair[] intersectionpoints(path p, path q, real fuzz=-1);| \go
返回包含路径 |p| 与 |q| 所有交点的数组。
\index{extension@\inlinecode=extension=}
\item |pair extension(pair P, pair Q, pair p, pair q);| \go
返回线段 |P--Q| 与 |p--q| 延长线的交点,否则,如果两直线平行,返回
|(infinity,infinity)|。
\index{slice@\inlinecode=slice=}
\item |slice cut(path p, path knife, int n);| \go
以结构体 |slice|:
\begin{lstlisting}
struct slice {
path before,after;
}
\end{lstlisting}
返回路径 |p| 与路径 |knife| 第 |n| 次相交前后的路径 |p| 的划分(如果未找到
交点,整个路径将被认为是在相交之“前”)。参数 |n| 会与相交次数取模。
\index{firstcut@\inlinecode=firstcut=}
\item |slice firstcut(path p, path knife);| \go
等价于 |cut(p,knife,0)|;注意 |firstcut.after| 起到 \MP{} |cutbefore| 命令
的作用。
\index{lastcut@\inlinecode=lastcut=}
\item |slice lastcut(path p, path knife);| \go
等价于 |cut(p,knife,-1)|;注意 |lastcut.before| 起到 \MP{} |cutafter| 命令
的作用。
\index{buildcycle@\inlinecode=buildcycle=}
\item |path buildcycle(... path[] p);| \go
依照 \MP{} |buildcycle| 命令的行为\transnote{在 \MP{} 中的行为方式是:对于
路径 $p_1$, $p_2$, \ldots, $p_k$,在每个 $p_i$ 和 $p_{i+1}$ 之间选取对
$p_i$ 尽可能晚而对 $p_{i+1}$ 尽可能早的交点,将其沿路径连成封闭环形。参看
\textit{\MP{}: A User's Manual} 中 9.1 节 Building Cycles。},返回围绕由
两个或更多相交的一列路径连续地界定的区域得到的路径。
\index{min@\inlinecode=min=}
\item |pair min(path p);| \go
返回路径 |p| 的路径边框的 $(\text{左}, \text{下})$ 角复数。
\index{max@\inlinecode=max=}
\item |pair max(path p);| \go
返回路径 |p| 的路径边框的 $(\text{右}, \text{上})$ 角复数。
\index{windingnumber@\inlinecode=windingnumber=}
\item |int windingnumber(path p, pair z);| \go
返回环形路径 |p| 关于点 |z| 的卷绕数\transnote{这是一个拓扑不变量,如图:
\\\centerline{\includegraphics[width=0.8\textwidth]{windingnumber.pdf}}\\
用数学公式表示,$\text{卷绕数} = \tilde\theta(t_0) - \tilde\theta(0) =
\frac{1}{2\uppi\mi} \oint_{C} \frac{\dif z}{z-a}$,其中 $C\colon z = z(t)$,
($t\in[0,t_0]$) 为复平面上的闭曲线,$z-a$ 的极坐标表示为 $(\tilde{r}(t),
\tilde\theta(t))$。}。若路径以逆时针方向环绕 |z|,则卷绕数是正数。如果 |z|
在路径 |p| 上,则返回常数 |undefined|(定义为最大的奇数)。
\index{interior@\inlinecode=interior=}
\item |bool interior(int windingnumber, pen fillrule);| \go
据填充规则 |fillrule|,当 |windingnumber| 对应一个内点时,返回 |true|%
\transnote{这是一个简单的函数,当 \inlinecode{fillrule} 为
\inlinecode{zerowinding} 时,返回值等价于 \inlinecode{windingnumber != 0};
当 \inlinecode{fillrule} 为 \inlinecode{evenodd} 时,返回值等价于
\inlinecode{windingnumber\%2 != 0} 即 \inlinecode{windingnumber} 是奇数。}。
\index{inside@\inlinecode=inside=}
\item |bool inside(path p, pair z, pen fillrule=currentpen);| \go
返回 |true| 当且仅当点 |z| 在环形路径 |p| 据填充规则 |fillrule|(见
\pageref{sec:pens} 页 \ref{sec:pens} 节)界定的区域内部或边界上。
\index{inside@\inlinecode=inside=}
\item |int inside(path p, path q, pen fillrule=currentpen);| \go
如果环形路径 |p| 按填充规则 |fillrule|(见 \pageref{sec:pens} 页
\ref{sec:pens} 节)严格包含 |q| 则返回 |1|,如果路径 |q| 严格包含 |p| 则
返回 |-1|,否则返回 |0|。
\index{inside@\inlinecode=inside=}
\item |pair inside(path p, pen fillrule=currentpen);| \go
返回按填充规则 |fillrule|(见 \pageref{sec:pens} 页 \ref{sec:pens} 节)在
一条环形路径 |p| 严格内部的任意一点。
\index{strokepath@\inlinecode=strokepath=}
\item |path[] strokepath(path g, pen p=currentpen);| \go
返回 \prgname{PostScript} 用画笔 |p| 绘制路径 |g| 时将会填充的路径数组。
\end{funclist}
\index{guide@\inlinecode=guide=}
\index{路向}
\index{nullpath@\inlinecode=nullpath=}
\item[guide] 未解析的三次样条(一列三次样条结点与控制点)。路向(|guide|)的
隐式初始式为 |nullpath|;这对在循环中建立路向有用。
路向与路径类似,只是三次样条的计算被延迟到绘制时(当它解析为一条路径时);这
就允许具有自由终点情形的两条路向光滑地相连。下面例子中的实线是作为路向逐次
建立的,但仅在绘制时解析;而虚线则在每次迭代时,在整个结点集(用红色表示)
尚未都知道时就逐次解析:
\lstinputlisting{mexicanhat.asy}
\begin{center}
\includegraphics{mexicanhat.pdf}
\end{center}
我们指出路向和路径的使用中的一个有效区别:
\begin{lstlisting}
guide g;
for(int i=0; i < 10; ++i)
g=g--(i,i);
path p=g;
\end{lstlisting}
以线性时间运行,而
\begin{lstlisting}
path p;
for(int i=0; i < 10; ++i)
p=p--(i,i);
\end{lstlisting}
以平方时间运行,因为在每步迭代中整个路径直到那一点都被复制。
下列过程可用于在不将路向实际解析为确定的路径的情况下检查路向的个别元素(除了
内部环形,它们会被解析):
\begin{funclist}
\index{size@\inlinecode=size=}
\item |int size(guide g);| \go
与 |size(path p)| 类似。
\index{length@\inlinecode=length=}
\item |int length(guide g);| \go
与 |length(path p)| 类似。
\index{cyclic@\inlinecode=cyclic=}
\item |bool cyclic(path p);| \go
与 |cyclic(path p)| 类似。
\index{point@\inlinecode=point=}
\item |pair point(guide g, int t);| \go
与 |point(path p, int t)| 类似。
\index{reverse@\inlinecode=reverse=}
\item |guide reverse(guide g);| \go
与 |reverse(path p)| 类似。如果 |g| 是环形的而且也包含次级环,它首先会被
解析为一条路径,然后反向。如果 |g| 不是环形的但包含内部环,只有内部环先会被
解决。如果没有内部环,路向会反向但不会解析为路径。
\index{dirSpecifier@\inlinecode=dirSpecifier=}
\item |pair[] dirSpecifier(guide g, int i);| \go
返回一个长度为 2 的复数数组,包含对路向 |g| 在结点 |i| 和 |i+1| 之间的段的
出向(在元素 0)和入向(在元素 1)的方向限定子\transnote{在形如
\inlinecode{z1 \{d1\} .. \{d2\} z2} 的路向中,出向和入向的方向限定子分别为
\inlinecode{d1} 和 \inlinecode{d2},模长会被约化为 1。}(或 |(0,0)|,如果
没有限定)。
\index{controlSpecifier@\inlinecode=controlSpecifier=}
\item |pair[] controlSpecifier(guide g, int i);| \go
如果路向 |g| 在结点 |i| 和 |i+1| 之间的段有明确的出向和入向控制点%
\transnote{在形如 \inlinecode{z1 .. controls c1 and c2 .. z2} 的路向中,
出向和入向的控制点分别为 \inlinecode{c1} 和 \inlinecode{c2}。路向
\inlinecode{z1 .. controls c .. z2} 等价于
\inlinecode{z1 .. controls c and c .. z2}。},它们会被分别以一个两元素的
数组的元素 0 和 1 返回。否则,会返回一个空白数组。
\index{tensionSpecifier@\inlinecode=tensionSpecifier=}
\item |tensionSpecifier tensionSpecifier(guide g, int i);| \go
返回路向 |g| 在结点 |i| 和 |i+1| 之间的段的张力限定子。|tensionSpecifier|
类型的个别组成部分可以按虚拟成员 |in|、|out| 和 |atLeast| 访问%
\transnote{在形如 \inlinecode{z1 .. tension t1 and t2 .. z2} 的路向中,成员
\inlinecode{in}、\inlinecode{out} 分别为实数 \inlinecode{t1}、%
\inlinecode{t2},\inlinecode{atLeast} 为 \inlinecode{false};在形如
\inlinecode{z1 .. tension atleast t1 and t2 .. z2} 的路向中,%
\inlinecode{atLeast} 的值为 \inlinecode{true}。路向
\inlinecode{z1 .. tension t .. z2} 等价于
\inlinecode{z1 .. tension t and t .. z2}。对于
\inlinecode{tensionSpecifier} 类型的变量 \inlinecode{tens},可以使用
\inlinecode{z1 .. tens .. z2} 的语法构造路向。}。
\index{curlSpecifier@\inlinecode=curlSpecifier=}
\item |real[] curlSpecifier(guide g);| \go
返回一个数组,它包含路向 |g| 的起点卷曲限定子(在元素 0 中)和终点卷曲限定
子(在元素 1 中)\transnote{在形如
\inlinecode{z1 \{curl c1\} .. \{curl c2\} z2} 的路向中,起、终点限定子分别
为 \inlinecode{c1} 和 \inlinecode{c2}}。
\end{funclist}
作为技术细节我们指出,给 |nullpath| 的方向限定子将改变另一边的结点:路向
\begin{lstlisting}
a..{up}nullpath..b;
c..nullpath{up}..d;
e..{up}nullpath{down}..f;
\end{lstlisting}
分别等价于
\begin{lstlisting}
a..nullpath..{up}b;
c{up}..nullpath..d;
e{down}..nullpath..{up}f;
\end{lstlisting}
\end{typelist}
\section{画笔}
% 5.3 Pens
\label{sec:pens}
\index{画笔}
\index{pen@\inlinecode=pen=}
\index{currentpen@\inlinecode=currentpen=}
\index{defaultpen@\inlinecode=defaultpen=}
在 \prgname{Asymptote} 中,画笔为四个基本绘图命令提供了上下文(见
\pageref{chap:drawingcommands} 页第\ref{chap:drawingcommands}章)。它们用于
限定如下的绘图属性:颜色、线型、线宽、线端、线连接、填充规则、文字对齐、
字体、字体大小、图案、覆写模式以及笔尖的书写变换。绘图过程默认使用的画笔叫做
|currentpen|。它提供了与 \MP{} 命令 \verb=pickup= 相当的功能。画笔的隐式初始
式为 |defaultpen|。
\index{+@\inlinecode=+=}
\index{画笔!相加}
\index{*@\inlinecode=*=}
\index{画笔!数乘}
画笔可以用非结合的二元运算符 |+| 相加。这将加起两个画笔的颜色。右端画笔的所有
其他非默认属性将覆盖左端的画笔。因而,比如说,可以用 |dashed+red+green| 或
|red+green+dashed| 或 |red+dashed+green| 来得到黄色虚线笔。二元运算符 |*| 可
用于以一个实数放缩画笔的颜色,直至一个或多个颜色分量等于 1 而饱和%
\transnote{通常而言,使用加号和乘号调色并不直观而且易错(如导致颜色分量大于
1)。除了使用预定义颜色以及在颜色空间中直接计算各分量外,比较方便自然的方式是
使用 \ref{subsec:arthlogoper} 节介绍的线性内插函数
\inlinecode{interp(T a, T b, real t)}
进行混色,而不必关心具体的分量(译者曾为此功能写过一个运算符重载的混色模块,
语法接近 \LaTeX 的 \prgname{xcolor} 宏包)。}。
\begin{itemize}
\index{画笔!颜色}
\item 颜色用下列颜色空间之一限定:
\begin{funclist}
\index{灰阶颜色}
\index{gray@\inlinecode=gray=}
\item |pen gray(real g);| \go
产生灰阶颜色,其中亮度 |g| 在区间 $[0,1]$ 内,$0.0$ 表示黑色而 $1.0$ 表示
白色。
\index{RGB 颜色}
\index{rgb@\inlinecode=rgb=}
\item |pen rgb(real r, real g, real b);| \go
产生 RGB 颜色\transnote{红(red)、绿(green)、蓝(blue)颜色,这三种颜色
是色光三原色,计算机屏幕输出即以这种颜色空间为基础。},其中红、绿、蓝每个的
亮度都在区间 $[0,1]$ 内。
\index{CMYK 颜色}
\index{cmyk@\inlinecode=cmyk=}
\item |pen cmyk(real c, real m, real y, real k);| \go
产生 CMYK 颜色\transnote{青(cyan)、品红(magenta)、黄(yellow)、黑
(black)颜色,这是印刷工业常用的标准颜色空间。},其中青、品红、黄、黑每个
的亮度都在区间 $[0,1]$ 内。
\index{invisible@\inlinecode=invisible=}
\item |pen invisible;| \go
这是使用不可见墨水书写的特殊画笔,但就像什么东西被画了一样调节边框(类似
\TeX{} 中的 \verb=\phantom= 命令)。函数 |bool invisible(pen)| 可用来测试
一支画笔是否不可见。
\end{funclist}
\index{defaultpen@\inlinecode=defaultpen=}
\index{colorspace@\inlinecode=colorspace=}
\index{颜色空间}
默认的颜色是 |black|(黑色);这个可以使用过程 |defaultpen(pen)| 来改变。函数
|colorspace(pen p)| 以字符串返回画笔 |p| 的颜色空间(|"gray"|、|"rgb"|、
|"cmyk"| 或 |""|)。
\index{colors@\inlinecode=colors=}
\index{gray@\inlinecode=gray=}
\index{rgb@\inlinecode=rgb=}
\index{cmyk@\inlinecode=cmyk=}
\index{colorless@\inlinecode=colorless=}
函数 |real[] colors(pen)| 返回画笔的各颜色分量。函数 |pen gray(pen)|、
|pen rgb(pen)| 以及 |pen cmyk(pen)| 返回分别将其参数转化为对应颜色空间的得到
的新画笔。函数 |colorless(pen=currentpen)| 返回其参数的复本,而剥离其颜色属性
(以避免混色)。
使用如下过程,可以将 6 字符的 RGB 十六进制串转换为画笔:
\index{rgb@\inlinecode=rgb=}
\begin{lstlisting}
pen rgb(string s);
\end{lstlisting}
在 \prgname{plain} 模块中预定义了各种渐变与混合色的颜色名,它们基于灰阶基本色
|black|(黑) 与 |white|(白),RGB 基本色 |red|(红)、|green|(绿)、
|blue|(蓝),以及 RGB 次级基本色 |cyan|(青)、|magenta|(品红)、
|yellow|(黄),还有 CMYK 基本色 |Cyan|、|Magenta|、|Yellow|、|Black|:
\begin{center}
\index{palered@\inlinecode=palered=}
\index{颜色!palered@\inlinecode=palered=}
\index{lightred@\inlinecode=lightred=}
\index{颜色!lightred@\inlinecode=lightred=}
\index{mediumred@\inlinecode=mediumred=}
\index{颜色!mediumred@\inlinecode=mediumred=}
\index{red@\inlinecode=red=}
\index{颜色!red@\inlinecode=red=}
\index{heavyred@\inlinecode=heavyred=}
\index{颜色!heavyred@\inlinecode=heavyred=}
\index{brown@\inlinecode=brown=}
\index{颜色!brown@\inlinecode=brown=}
\index{darkbrown@\inlinecode=darkbrown=}
\index{颜色!darkbrown@\inlinecode=darkbrown=}
\index{palegreen@\inlinecode=palegreen=}
\index{颜色!palegreen@\inlinecode=palegreen=}
\index{lightgreen@\inlinecode=lightgreen=}
\index{颜色!lightgreen@\inlinecode=lightgreen=}
\index{mediumgreen@\inlinecode=mediumgreen=}
\index{颜色!mediumgreen@\inlinecode=mediumgreen=}
\index{green@\inlinecode=green=}
\index{颜色!green@\inlinecode=green=}
\index{heavygreen@\inlinecode=heavygreen=}
\index{颜色!heavygreen@\inlinecode=heavygreen=}
\index{deepgreen@\inlinecode=deepgreen=}
\index{颜色!deepgreen@\inlinecode=deepgreen=}
\index{darkgreen@\inlinecode=darkgreen=}
\index{颜色!darkgreen@\inlinecode=darkgreen=}
\index{paleblue@\inlinecode=paleblue=}
\index{颜色!paleblue@\inlinecode=paleblue=}
\index{lightblue@\inlinecode=lightblue=}
\index{颜色!lightblue@\inlinecode=lightblue=}
\index{mediumblue@\inlinecode=mediumblue=}
\index{颜色!mediumblue@\inlinecode=mediumblue=}
\index{blue@\inlinecode=blue=}
\index{颜色!blue@\inlinecode=blue=}
\index{heavyblue@\inlinecode=heavyblue=}
\index{颜色!heavyblue@\inlinecode=heavyblue=}
\index{deepblue@\inlinecode=deepblue=}
\index{颜色!deepblue@\inlinecode=deepblue=}
\index{darkblue@\inlinecode=darkblue=}
\index{颜色!darkblue@\inlinecode=darkblue=}
\index{palecyan@\inlinecode=palecyan=}
\index{颜色!palecyan@\inlinecode=palecyan=}
\index{lightcyan@\inlinecode=lightcyan=}
\index{颜色!lightcyan@\inlinecode=lightcyan=}
\index{mediumcyan@\inlinecode=mediumcyan=}
\index{颜色!mediumcyan@\inlinecode=mediumcyan=}
\index{cyan@\inlinecode=cyan=}
\index{颜色!cyan@\inlinecode=cyan=}
\index{heavycyan@\inlinecode=heavycyan=}
\index{颜色!heavycyan@\inlinecode=heavycyan=}
\index{deepcyan@\inlinecode=deepcyan=}
\index{颜色!deepcyan@\inlinecode=deepcyan=}
\index{darkcyan@\inlinecode=darkcyan=}
\index{颜色!darkcyan@\inlinecode=darkcyan=}
\index{pink@\inlinecode=pink=}
\index{颜色!pink@\inlinecode=pink=}
\index{lightmagenta@\inlinecode=lightmagenta=}
\index{颜色!lightmagenta@\inlinecode=lightmagenta=}
\index{mediummagenta@\inlinecode=mediummagenta=}
\index{颜色!mediummagenta@\inlinecode=mediummagenta=}
\index{magenta@\inlinecode=magenta=}
\index{颜色!magenta@\inlinecode=magenta=}
\index{heavymagenta@\inlinecode=heavymagenta=}
\index{颜色!heavymagenta@\inlinecode=heavymagenta=}
\index{deepmagenta@\inlinecode=deepmagenta=}
\index{颜色!deepmagenta@\inlinecode=deepmagenta=}
\index{darkmagenta@\inlinecode=darkmagenta=}
\index{颜色!darkmagenta@\inlinecode=darkmagenta=}
\index{paleyellow@\inlinecode=paleyellow=}
\index{颜色!paleyellow@\inlinecode=paleyellow=}
\index{lightyellow@\inlinecode=lightyellow=}
\index{颜色!lightyellow@\inlinecode=lightyellow=}
\index{mediumyellow@\inlinecode=mediumyellow=}
\index{颜色!mediumyellow@\inlinecode=mediumyellow=}
\index{yellow@\inlinecode=yellow=}
\index{颜色!yellow@\inlinecode=yellow=}
\index{lightolive@\inlinecode=lightolive=}
\index{颜色!lightolive@\inlinecode=lightolive=}
\index{olive@\inlinecode=olive=}
\index{颜色!olive@\inlinecode=olive=}
\index{darkolive@\inlinecode=darkolive=}
\index{颜色!darkolive@\inlinecode=darkolive=}
\index{palegray@\inlinecode=palegray=}
\index{颜色!palegray@\inlinecode=palegray=}
\index{lightgray@\inlinecode=lightgray=}
\index{颜色!lightgray@\inlinecode=lightgray=}
\index{mediumgray@\inlinecode=mediumgray=}
\index{颜色!mediumgray@\inlinecode=mediumgray=}
\index{gray@\inlinecode=gray=}
\index{颜色!gray@\inlinecode=gray=}
\index{heavygray@\inlinecode=heavygray=}
\index{颜色!heavygray@\inlinecode=heavygray=}
\index{deepgray@\inlinecode=deepgray=}
\index{颜色!deepgray@\inlinecode=deepgray=}
\index{darkgray@\inlinecode=darkgray=}
\index{颜色!darkgray@\inlinecode=darkgray=}
\index{black@\inlinecode=black=}
\index{颜色!black@\inlinecode=black=}
\index{white@\inlinecode=white=}
\index{颜色!white@\inlinecode=white=}
\index{orange@\inlinecode=orange=}
\index{颜色!orange@\inlinecode=orange=}
\index{fuchsia@\inlinecode=fuchsia=}
\index{颜色!fuchsia@\inlinecode=fuchsia=}
\index{chartreuse@\inlinecode=chartreuse=}
\index{颜色!chartreuse@\inlinecode=chartreuse=}
\index{springgreen@\inlinecode=springgreen=}
\index{颜色!springgreen@\inlinecode=springgreen=}
\index{purple@\inlinecode=purple=}
\index{颜色!purple@\inlinecode=purple=}
\index{royalblue@\inlinecode=royalblue=}
\index{颜色!royalblue@\inlinecode=royalblue=}
\index{Cyan@\inlinecode=Cyan=}
\index{颜色!Cyan@\inlinecode=Cyan=}
\index{Magenta@\inlinecode=Magenta=}
\index{颜色!Magenta@\inlinecode=Magenta=}
\index{Yellow@\inlinecode=Yellow=}
\index{颜色!Yellow@\inlinecode=Yellow=}
\index{Black@\inlinecode=Black=}
\index{颜色!Black@\inlinecode=Black=}
\includegraphics{colors.pdf}
\end{center}
\index{x11colors@\prgname{x11colors}}
标准的 140 种 RGB \prgname{X11} 颜色可以使用如下命令导入:
\begin{lstlisting}
import x11colors;
\end{lstlisting}
\index{texcolors@\prgname{texcolors}}
而标准的 68 种 CMYK \TeX{} 颜色可以使用如下命令导入:
\begin{lstlisting}
import texcolors;
\end{lstlisting}
注意两个标准重复定义的一些颜色(如 |Green|)实际并不一致。
\index{asycolors.sty@\prgname{asycolors.sty}}
\prgname{Asymptote} 还附有一个 \prgname{asycolors.sty} 的 \LaTeX{} 宏包,定义
了 \LaTeX{} CMYK 版本的 \prgname{Asymptote} 预定义颜色,从而使其可以直接用于
\LaTeX{} 字符串中。通常,这些颜色通过画笔参数传递给 \LaTeX{};然而,要仅对
字符串的一部分改变颜色,比如说在幻灯演示中(参看原手册 7.17 节 slide 部分%
\transnote{本译本未包括此部分。},可能需要直接向 \LaTeX{} 设置颜色。这个文件
可以用 \prgname{Asymptote} 命令传递给 \LaTeX{}:
\begin{lstlisting}
usepackage("asycolors");
\end{lstlisting}
\index{hsv@\inlinecode=hsv=}
定义于 \prgname{plain\_pens.asy} 的结构体 |hsv| 可以用于在 HSV(Hue
Saturation Value,色调饱和度明度)和 RGB 空间相互转换,其中色调 |h| 是一个在
$[0,360)$ 内的角度而饱和度 |s| 和明度 |v| 在 $[0,1]$ 内:
\begin{lstlisting}
pen p=hsv(180,0.5,0.75);
write(p); // ([default], red=0.375, green=0.75, blue=0.75)
hsv q=p;
write(q.h,q.s,q.v); // 180 0.5 0.75
\end{lstlisting}
\index{画笔!线型}
\index{linetype@\inlinecode=linetype=}
\item 线型用函数
|pen linetype(string s, real offset=0, bool scale=true, bool adjust=true)|
确定,其中 |s| 是一个由空格分隔的若干整数或实数组成的字符串。可选参数
|offset| 限定在模式的何处开始。第一个数字限定在画笔落下时画多远(如果
|scale| 为 |true|,以画笔线宽为单位;否则使用 \prgname{PostScript} 单位),
第二个数字限定在画笔抬起时画多远,以此类推。如果 |adjust| 为 |true|,这此
间距将自动由 \prgname{Asymptote} 调整以适合路径的弧长。这里是一些预定义的
线型:
\index{solid@\inlinecode=solid=}
\index{dotted@\inlinecode=dotted=}
\index{dashed@\inlinecode=dashed=}
\index{longdashed@\inlinecode=longdashed=}
\index{dashdotted@\inlinecode=dashdotted=}
\index{longdashdotted@\inlinecode=longdashdotted=}
\index{Dotted@\inlinecode=Dotted=}
\begin{lstlisting}
pen solid=linetype("");
pen dotted=linetype("0 4");
pen dashed=linetype("8 8");
pen longdashed=linetype("24 8");
pen dashdotted=linetype("8 8 0 8");
pen longdashdotted=linetype("24 8 0 8");
pen Dotted=dotted+1.0;
pen Dotted(pen p=currentpen) {return dotted+2*linewidth(p);}
\end{lstlisting}
\begin{center}
\includegraphics{linetype.pdf}
\end{center}
默认的线型是 |solid|;这可以用 |defaultpen(pen)| 改变。
\index{画笔!线宽}
\index{linewidth@\inlinecode=linewidth=}
\item 画笔的线宽使用 |pen linewidth(real)| 按 \prgname{PostScript} 单位来
限定\transnote{在 \prgname{Asymptote} 中,用 \prgname{PostScript} 单位表示
的长度是最终输出图形中的绝对长度,不会被放缩。}。默认的线宽为 $0.5$\,dp;
这个值可以用 |defaultpen(pen)| 来修改。画笔的线宽可由
|real linewidth(pen p=currentpen)| 返回。为方便计,在模块 \prgname{plain}
中我们定义了
\begin{lstlisting}
static void defaultpen(real w) {defaultpen(linewidth(w));}
static pen operator +(pen p, real w) {return p+linewidth(w);}
static pen operator +(real w, pen p) {return linewidth(w)+p;}
\end{lstlisting}
从而可以像这样设置线宽:
\begin{lstlisting}
defaultpen(2);
pen p=red+0.5;
\end{lstlisting}
\index{画笔!线端}
\index{linecap@\inlinecode=linecap=}
\item 特定 \prgname{PostScript} 线端的画笔可以通过以一个整数参数调用
|linecap| 返回\transnote{线端(line cap)指曲线端点的形状,
\prgname{PostScript} 三种线端有方形、圆形和外扩方形,如下图所示:
\begin{center}\includegraphics{linecap.pdf}\end{center}}:
\index{squarecap@\inlinecode=squarecap=}
\index{roundcap@\inlinecode=roundcap=}
\index{extendcap@\inlinecode=extendcap=}
\begin{lstlisting}
pen squarecap=linecap(0);
pen roundcap=linecap(1);
pen extendcap=linecap(2);
\end{lstlisting}
默认的线端为 |roundcap|,可以用 |defaultpen(pen)| 改变。画笔的线端由
|int linecap(pen p=currentpen)| 返回。
\index{画笔!连接样式}
\index{linejoin@\inlinecode=linejoin=}
\item 特定 \prgname{PostScript} 连接样式的画笔可以以一个整数参数调用
|linejoin| 返回\transnote{\prgname{PostScript} 三种折线的连接样式为尖角
连接、圆角连接和斜角连接:\begin{center}\includegraphics{linejoin.pdf}\end{center}}:
\index{miterjoin@\inlinecode=miterjoin=}
\index{roundjoin@\inlinecode=roundjoin=}
\index{beveljoin@\inlinecode=beveljoin=}
\begin{lstlisting}
pen miterjoin=linejoin(0);
pen roundjoin=linejoin(1);
pen beveljoin=linejoin(2);
\end{lstlisting}
默认的连接样式为 |roundjoin|,可以用 |defaultpen(pen)| 改变。画笔的连接样式
由 |int linejoin(pen p=currentpen)| 返回。
\index{画笔!尖角限量}
\index{miterlimit@\inlinecode=miterlimit=}
\item 有一定 \prgname{PostScript} 尖角限量(miter limit)的画笔由
|miterlimit(real)| 的调用返回。默认的尖角限量,|10.0|,可以用
|defaultpen(pen)| 改变。画笔的尖角限量由 |real miterlimit(pen p=currentpen)|
返回。
\index{画笔!填充规则}
\index{fillrule@\inlinecode=fillrule=}
\item 特定 \prgname{PostScript} 填充规则的画笔可以以一个整数参数调用
|fillrule| 返回:
\index{zerowinding@\inlinecode=zerowinding=}
\index{evenodd@\inlinecode=evenodd=}
\begin{lstlisting}
pen zerowinding=fillrule(0);
pen evenodd=fillrule(1);
\end{lstlisting}
填充规则确定如何判定是否在一个路径或路径数组内部的算法,它仅影响 |clip|、
|fill| 和 |inside| 函数。对于 |zerowinding| 填充规则,如果路径与水平线
|z--z+infinity| 向上的相交次数减去向下的相交次数等于零,则点 |z| 在路径限界
的区域内部。对 |evenodd| 填充规则,如果上述相交的总数为偶数,则认为 |z| 在
区域外面。默认的填充规则为 |zerowinding|,可以用 |defaultpen(pen)| 改变。
画笔的填充规则由 |int fillrule(pen p=currentpen)| 返回。
\index{画笔!文字对齐}
\index{basealign@\inlinecode=basealign=}
\item 特定文字对齐设置的画笔能以一个整数参数调用 |basealign| 返回:
\index{nobasealign@\inlinecode=nobasealign=}
\index{basealign@\inlinecode=basealign=}
\begin{lstlisting}
pen nobasealign=basealign(0);
pen basealign=basealign(1);
\end{lstlisting}
默认设置为 |nobasealign|,会令标签对齐过程使用整个标签的边框进行对齐,它可
用 |defaultpen(pen)| 改变。相反,|baseline| 要求考虑 \TeX{} 基线。
画笔的基线对齐设置由 |int basealign(pen p=currentpen)| 返回。
\index{画笔!字体大小}
\index{fontsize@\inlinecode=fontsize=}
\item 字体大小用函数 |pen fontsize(real size, real lineskip=1.2*size)| 按
\TeX{} 点($1\,\text{pt}=1/72.27 \,\text{英寸}$)确定。默认的字体大小为
$12$\,pt,可以用 |defaultpen(pen)| 改变。非标准的字体大小需要在文件开头插入
\index{fontsize.asy@\prgname{fontsize.asy}}
\begin{lstlisting}
import fontsize;
\end{lstlisting}
(这需要 \prgname{fix-cm} 宏包,它可从
\url{http://www.ctan.org/tex-archive/help/Catalogue/entries/fix-cm} 获得,
也包含在最新的 \LaTeX{} 发行版中)。画笔的字体大小与行间距可以分别用过程
|real fontsize(pen p=currentpen)| 与 |real lineskip(pen p=currentpen)| 进行
检查。
\index{画笔!字体}
\index{LaTeX NFSS 字体@\LaTeX\ NFSS 字体}
\item 使用特定 \LaTeX\ NFSS 字体的画笔可调用函数
\index{font@\inlinecode=font=}
\begin{lstlisting}
pen font(string encoding, string family, string series, string shape)
\end{lstlisting}
返回。默认设置为 |font("OT1","cmr","m","n")|,对应 12\,pt Computer Modern
Roman 字体;它可用 |defaultpen(pen)| 改变。画笔的字体设置由
|string font(pen p=currentpen)| 返回。国际标准化字符集支持由
\prgname{unicode} 包提供(见原手册 7.19 节 \prgname{unicode} 部分%
\transnote{此译本未包括此部分。})。
\index{TeX 字体@\TeX{} 字体}
\index{font@\inlinecode=font=}
另外,可以使用函数 |pen font(string name)| 选择固定大小的 \TeX{} 字体(此时
|fontsize| 不起作用),如 |"cmr12"|($12$\,pt Computer Modern Roman)或
|pcrr|(Courier)。还可以给定可选的大小参数来将字体放缩到要求的大小:
|pen font(string name, real size)|。
\index{字体命令}
\index{fontcommand@\inlinecode=fontcommand=}
非标准的字体命令可以用 |pen fontcommand(string)| 生成。
\index{PostScript 字体@\prgname{PostScript} 字体}
下面提供了标准 \prgname{PostScript} 字体的方便界面:
\index{AvantGarde@\inlinecode=AvantGarde=}
\index{Bookman@\inlinecode=Bookman=}
\index{Courier@\inlinecode=Courier=}
\index{Helvetica@\inlinecode=Helvetica=}
\index{NewCenturySchoolBook@\inlinecode=NewCenturySchoolBook=}
\index{Palatino@\inlinecode=Palatino=}
\index{TimesRoman@\inlinecode=TimesRoman=}
\index{ZapfChancery@\inlinecode=ZapfChancery=}
\index{Symbol@\inlinecode=Symbol=}
\index{ZapfDingbats@\inlinecode=ZapfDingbats=}
\begin{lstlisting}
pen AvantGarde(string series="m", string shape="n");
pen Bookman(string series="m", string shape="n");
pen Courier(string series="m", string shape="n");
pen Helvetica(string series="m", string shape="n");
pen NewCenturySchoolBook(string series="m", string shape="n");
pen Palatino(string series="m", string shape="n");
pen TimesRoman(string series="m", string shape="n");
pen ZapfChancery(string series="m", string shape="n");
pen Symbol(string series="m", string shape="n");
pen ZapfDingbats(string series="m", string shape="n");
\end{lstlisting}
\index{画笔!透明度}
\item 画笔的透明度可以用此命令改变:
\index{opacity@\inlinecode=opacity=}
\begin{lstlisting}
pen opacity(real opacity=1, string blend="Compatible");
\end{lstlisting}
不透明度(opacity)可以从 |0|(完全透明)到默认值 |1|(不透明)变动,而
|blend| 确定下列前景-背景混合操作之一:
\begin{lstlisting}
"Compatible","Normal","Multiply","Screen","Overlay","SoftLight",
"HardLight","ColorDodge","ColorBurn","Darken","Lighten","Difference",
"Exclusion","Hue","Saturation","Color","Luminosity"
\end{lstlisting}
它们在
\url{http://partners.adobe.com/public/developer/en/pdf/PDFReference16.pdf}
中描述。
由于 \prgname{PostScript} 不支持透明,这个特性仅对 \verb=-f pdf= 输出格式
选项有效。可以从 PDF 结果文件通过 \prgname{ImageMagick convert} 程序生成。
标签永远以 |opacity| 值 |1| 绘制。透明填充的一个简单例子在示例文件
\prgname{transparency.asy} 中给出\transnote{效果如下:\\
\setbox0\vbox{\lstinputlisting{transparency.asy}}%
\includegraphics[height=\ht0]{transparency.pdf}\box0}。
\index{画笔!铺砌图案}
\item |picture| 中的 \prgname{PostScript} 命令可以用于创建一个以字符串 |name|
标识的铺砌图案,通过将其加在全局 \prgname{PostScript} 帧 |currentpatterns|
上来影响 |fill| 和 |draw| 操作,外带可选的左下边界 |lb| 和右上边界 |rt|。
\index{add@\inlinecode=add=}
\begin{lstlisting}
import patterns;
void add(string name, picture pic, pair lb=0, pair rt=0);
\end{lstlisting}
\index{pattern@\inlinecode=pattern=}
要使用图案 |name| 进行 |fill| 或 |draw|,使用画笔 |pattern("name")|。例如,
矩形铺砌可以使用定义于 \prgname{patternss.asy} 的过程
\index{tile@\inlinecode=tile=}
\index{checker@\inlinecode=checker=}
\index{brick@\inlinecode=brick=}
\begin{lstlisting}
picture tile(real Hx=5mm, real Hy=0, pen p=currentpen,
filltype filltype=NoFill);
picture checker(real Hx=5mm, real Hy=0, pen p=currentpen);
picture brick(real Hx=5mm, real Hy=0, pen p=currentpen);
\end{lstlisting}
来构造:
\lstinputlisting{tile.asy}
\begin{center}
\includegraphics{tile.pdf}
\end{center}
\index{hatch@\inlinecode=hatch=}
\index{crosshatch@\inlinecode=crosshatch=}
阴影线图案可以通过过程
|picture hatch(real H=5mm, pair dir=NE, pen p=currentpen)|、
|picture crosshatch(real H=5mm, pen p=currentpen)| 生成:
\lstinputlisting{hatch.asy}
\begin{center}
\includegraphics{hatch.pdf}
\end{center}
你可能需要在你的 \prgname{PostScript} 预览器中关闭锯齿以正确显示。自定义
图案可以按照 \prgname{patterns.asy} 的例子简单地构造。铺砌的图案甚至可以
包含渐变(见 \pageref{sec:fill} 页 \ref{sec:fill} 关于渐变填充的部分),
下面是示例\transnote{原手册中有说明:“因为并非所有打印机支持
\prgname{PostScript 3},(示例图形)不包括在手册中。”现在译本把它加进
正文。}:
\lstinputlisting{shadedtiling.asy}
\begin{center}
\includegraphics{shadedtiling.pdf}
\end{center}
\index{画笔!笔尖}
\index{makepen@\inlinecode=makepen=}
\index{nib@\inlinecode=nib=}
\item 可以使用 |pen makepen(path)| 来以任意多边形路径确定自定义的笔尖;此路径
表示用以绘制包含单个点的路径的记号。笔尖路径可以用 |path nib(pen)| 从一个
画笔中恢复出来。不像 \MP{} 中那样,路径不必是凸的:
\lstinputlisting{makepen.asy}
\begin{center}
\includegraphics{makepen.pdf}
\end{center}
值 |nullpath| 表示一个圆形笔尖(默认值);椭圆形画笔可以仅仅通过给画笔乘以
一个变换得到:|yscale(2)*currentpen|。
\index{画笔!覆写}
\index{overwrite@\inlinecode=overwrite=}
\item 使用画笔属性 |overwrite|,可以避免标签互相覆写,它取单独一个参数:
\begin{funclist}
\index{Allow@\inlinecode=Allow=}
\item |Allow| \go
允许标签互相覆写。这是默认行为(除非用 |defaultpen(pen)| 废除)。
\index{Suppress@\inlinecode=Suppress=}
\item |Suppress| \go
禁止,对每个将覆写另一个标签的标签,连带一个警告。
\index{SuppressQuiet@\inlinecode=SuppressQuiet=}
\item |SuppressQuiet| \go
禁止,对每个将覆写另一个标签的标签,没有警告。
\index{Move@\inlinecode=Move=}
\item |Move| \go
把将要覆写另一个标签的标签移走并发出警告。由于此调整是在最终输出阶段(以
\prgname{PostScript} 坐标),可能结果会得到比要求的更大一些的图形。
\index{MoveQuiet@\inlinecode=MoveQuiet=}
\item |MoveQuiet| \go
把将要覆写另一个标签的标签移走而不发出警告。由于此调整是在最终输出阶段(以
\prgname{PostScript} 坐标),可能结果会得到比要求的更大一些的图形。
\end{funclist}
\end{itemize}
过程 |defaultpen()| 返回当前默认画笔属性。调用过程 |resetdefaultpen()| 可重设
所有画笔默认属性为初始值。
\section{变换}
% 5.4 Transforms
\label{sec:transforms}
\index{仿射变换}\index{变换}
\prgname{Asymptote} 广泛利用仿射变换。复数 |(x,y)| 由变换
|t=(t.x,t.y,t.xx,t.xy,t.yx,t.yy)| 变换到
\lstinline[stringstyle={\color{black}}]=(x',y')=,其中
\begin{lstlisting}[stringstyle={\color{black}}]
x' = t.x + t.xx * x + t.xy * y
y' = t.y + t.yx * x + t.yy * y
\end{lstlisting}
\lstset{}
这等价于 \prgname{PostScript} 变换 |[t.xx t.yx t.xy t.yy t.x t.y]|。
\index{transform@\inlinecode=transform=}
\index{inverse@\inlinecode=inverse=}
\index{逆变换}
\index{变换!逆}
\index{^@\inlinecode=^=}
\index{*@\inlinecode=*=}
\index{变换!复合}
变换可以用左乘(通过二元运算符 |*|)应用于复数、路向、路径、画笔、字符串、
变换、帧与图(见 \pageref{sec:pathsandguides} 页 \ref{sec:pathsandguides} 节
圆的例子)。变换可以互相复合并通过函数 |transform inverse(transform t)| 取
逆;它们也可以用 |^| 运算符进行任意整数次幂\transnote{包括负整数和零。变换的
$n$ 次幂($n>0$)表示 $n$ 个自身复合;变换的 $-n$ 次幂表示其逆变换的 $n$ 次
幂;变换的零次幂就是恒同变换。}。
内建的变换有:
\begin{funclist}
\index{变换!恒同变换}
\index{identity@\inlinecode=identity=}
\item |transform identity();| \go
恒同变换;
\index{变换!平移}
\index{shift@\inlinecode=shift=}
\item |transform shift(pair z);| \go
按复数 |z| 平移;
\index{变换!平移}
\index{shift@\inlinecode=shift=}
\item |transform shift(real x, real y);| \go
按复数 |(x,y)| 平移;
\index{变换!放缩}
\index{xscale@\inlinecode=xscale=}
\item |transform xscale(real x);| \go
在 $x$ 方向按 |x| 放缩;
\index{变换!放缩}
\index{yscale@\inlinecode=yscale=}
\item |transform yscale(real y);| \go
在 $y$ 方向按 |y| 放缩;
\index{变换!放缩}
\index{scale@\inlinecode=scale=}
\item |transform scale(real s);| \go
在 $x$ 与 $y$ 方向都按 |s| 放缩;
\index{变换!放缩}
\index{scale@\inlinecode=scale=}
\item |transform scale(real x, real y);| \go
在 $x$ 方向按 |x| 而在 $y$ 方向按 |y| 放缩;
\index{变换!倾斜}
\index{变换!错切}
\index{slant@\inlinecode=slant=}
\item |transform slant(real s);| \go
映射 $(x,y) \mapsto (x+{}$|s|${}\cdot y, y)$;
\index{变换!旋转}
\index{rotate@\inlinecode=rotate=}
\item |transform rotate(real angle, pair z=(0,0));| \go
关于 |z| 旋转 |angle| 度;
\index{变换!反射}
\index{reflect@\inlinecode=reflect=}
\item |transform reflect(pair a, pair b);| \go
关于直线 |a--b| 反射。
\end{funclist}
\index{shiftless@\inlinecode=shiftless=}
变换的隐式初始式是 |identity()|。过程 |shift(transform t)| 和
|shiftless(transform t)| 分别返回变换 |(t.x,t.y,0,0,0,0)| 和
|(0,0,t.xx,t.xy,t.yx,t.yy)|\transnote{变换除了可以相乘、取幂,也可以相加,
变换相加就是对应平移向量和矩阵相加。\par
\prgname{Asymptote} 并没有提供直接控制变换各分量的方式,可以用名字访问
\inlinecode{xx}、\inlinecode{xy} 等成员,但它们是只读的。不过,通过变换的
运算,可以使用表达式
\begin{lstlisting}^^J
shift(x,y) * (rotate(90)*scale(yx,-xy) + scale(xx,yy))^^J
// (x, y, xx, xy, yx, yy)^^J
\end{lstlisting}
直接以矩阵元素值来表示任意的仿射变换。}。
\section{帧(frame)与图(picture)}
% 5.5 Frames and pictures
\label{sec:framepicture}
\begin{typelist}
\index{frame@\inlinecode=frame=}
\index{帧}
\item[frame]
\index{newframe@\inlinecode=newframe=}
\index{empty@\inlinecode=empty=}
\index{erase@\inlinecode=erase=}
\index{min@\inlinecode=min=}
\index{max@\inlinecode=max=}
帧(|frame|)是在 \prgname{PostScript} 坐标中绘画的画布。偶尔需要为了构造延时
作图过程而直接在帧上工作,通常在图(|picture|)上工作更为方便。帧的隐式初始式
为 |newframe|。仅当帧 |f| 是空的时,函数 |bool empty(frame f)| 返回 |true|。
一帧可以用 |erase(frame)| 过程擦除。函数 |pair min(frame)| 和
|pair max(frame)| 分别返回帧边框的 $(\text{左},\text{下})$ 和 $(\text{右},
\text{上})$ 坐标。用命令
\index{add@\inlinecode=add=}
\begin{lstlisting}
void add(frame dest, frame src);
\end{lstlisting}
可以将帧 |src| 的内容附加在帧 |dest| 的后面,或用命令
\index{prepend@\inlinecode=prepend=}
\begin{lstlisting}
void prepend(frame dest, frame src);
\end{lstlisting}
添加在前面\transnote{附加在后面的帧在较上的位置(较晚绘制),添加在前面的帧
在较下的位置(较早绘制)。}。
\index{align@\inlinecode=align=}
按照与 |label| 使用 |align| 参数(参看 \pageref{sec:label} 页 \ref{sec:label}
节标签部分)类似的方式,把帧 |f| 按方向 |align| 对齐得到的帧,可由
\begin{lstlisting}
frame align(frame f, pair align);
\end{lstlisting}
返回。
\index{envelope@\inlinecode=envelope=}
为描画或填充围绕标签或帧的盒形或椭圆并返回边界路径,使用如下预定义的
|envelope| 过程之一\transnote{这几个过程使用对应的画笔、填充类型等在帧
\inlinecode{f} 中绘制对应的形状(其边界由 \inlinecode{f} 原有的内容和标签
\inlinecode{L} 决定),并返回此形状的边界路径。}:
\index{box@\inlinecode=box=}
\index{roundbox@\inlinecode=roundbox=}
\index{ellipse@\inlinecode=ellipse=}
\begin{lstlisting}
path box(frame f, Label L="", real xmargin=0,
real ymargin=xmargin, pen p=currentpen,
filltype filltype=NoFill, bool above=true);
path roundbox(frame f, Label L="", real xmargin=0,
real ymargin=xmargin, pen p=currentpen,
filltype filltype=NoFill, bool above=true);
path ellipse(frame f, Label L="", real xmargin=0,
real ymargin=xmargin, pen p=currentpen,
filltype filltype=NoFill, bool above=true);
\end{lstlisting}
\index{picture@\inlinecode=picture=}
\index{图}
\item[picture]
图是在模块 \prgname{plain} 中定义的高级结构(参见 \pageref{sec:structures} 页
\ref{sec:structures} 节结构体部分),提供以用户坐标绘图的画布。默认的图叫做
|currentpicture|。新的图可以这样创建:
\begin{lstlisting}
picture pic;
\end{lstlisting}
匿名图可以用表达式 |new picture| 创建。
\index{size@\inlinecode=size=}
|size| 过程限定要求的图的尺寸:
\begin{lstlisting}
void size(picture pic=currentpicture, real x, real y=x,
bool keepAspect=Aspect);
\end{lstlisting}
如果 |x| 与 |y| 的大小都为 |0|,用户坐标将被解释为 \prgname{PostScript}
坐标。在这种情况下,把 |pic| 映射到最终的输出帧的变换为 |identity()|。
如果 |x| 或 |y| 恰有一个为 |0|,该方向则不施加限制;它将与另一方向一致地
放缩。
\index{Aspect@\inlinecode=Aspect=}
如果 |keepAspect| 设为 |Aspect| 或 |true|,该图将按原比例放缩,并保持最终宽度
不大于 |x| 而最终高度不大于 |y|。
\index{IgnoreAspect@\inlinecode=IgnoreAspect=}
如果 |keepAspect| 设为 |IgnoreAspect| 或 |false|,该图将以两个方向放缩以使
最终宽度为 |x| 而最终高度为 |y|。
要令图 |pic| 的用户坐标表示在 $x$ 方向的 |x| 单位倍和 $y$ 方向的 |y| 单位倍,
使用
\index{unitsize@\inlinecode=unitsize=}
\begin{lstlisting}
void unitsize(picture pic=currentpicture, real x, real y=x);
\end{lstlisting}
如果非零,这里 |x| 与 |y| 值将覆盖图 |pic| 的对应尺寸参数。
过程
\index{size@\inlinecode=size=}
\begin{lstlisting}
void size(picture pic=currentpicture, real xsize, real ysize,
pair min, pair max);
\end{lstlisting}
强迫最终的图放缩为把用户坐标 |box(min,max)| 映射到宽 |xsize| 高 |ysize| 的
区域(当这些参数非零时)。
此外,调用过程
\index{fixedscaling@\inlinecode=fixedscaling=}
\begin{lstlisting}
transform fixedscaling(picture pic=currentpicture, pair min,
pair max, pen p=nullpen, bool warn=false);
\end{lstlisting}
将使图 |pic| 使用一个固定的放缩把 |box(min,max)| 中的用户坐标映射为(已经限定
的)图尺寸,其中考虑画笔 |p| 的宽度。如果最终的图超出限定的尺寸将发出警告。
调用函数 |shipout|,可以将图 |pic| 装入帧,并使用图像格式 |format| 输出到文件
\verb=prefix.fotmat=:
\index{shipout@\inlinecode=shipout=}
\index{outprefix@\inlinecode=outprefix=}
\begin{lstlisting}
void shipout(string prefix=defaultfilename, picture pic=currentpicture,
orientation orientation=orientation,
string format="", bool wait=false, bool view=true,
string options="", string script="",
projection P=currentprojection);
\end{lstlisting}
默认的输出格式为 \prgname{PostScript},可用命令行选项 \verb=-f= 或
\verb=-tex= 改变。参数 |options|, |script| 与 |projection| 仅与 3D 图相关。如
果 |defaultfilename| 为空串,则将使用前缀 |outprefix()|。
如果之前没有执行过 |shipout()| 命令,在文件末尾将会隐式地加上一条 |shipout()|
命令。
\index{orientation@\inlinecode=orientation=}
\index{Portrait@\inlinecode=Portrait=}
\index{Landscape@\inlinecode=Landscape=}
\index{UpsideDown@\inlinecode=UpsideDown=}
默认的页面方向为 |Portrait|(纵向);这可以通过改变变量 |orientation| 修改。
要以横向模式输出,仅仅需要设置 |orientation=Landscape| 或发出指令
\begin{lstlisting}
shipout(Landscape);
\end{lstlisting}
要旋转页面 $-90^\circ$,使用方向 |Seascape|。要旋转页面 $180^\circ$,使用方向
|UpsideDown|(上下颠倒)。
可以调用函数明确将图 |pic| 装入一帧:
\index{fit@\inlinecode=fit=}
\index{calculateTransform@\inlinecode=calculateTransform=}
\begin{lstlisting}
frame pic.fit(real xsize=pic.xsize, real ysize=pic.ysize,
bool keepAspect=pic.keepAspect);
\end{lstlisting}
默认的尺寸和宽高比设置是给 |size| 命令的值(默认分别为 |0|, |0| 和 |true|)。
当前将用于把图 |pic| 装入一帧的变换可通过成员函数
|pic.calculateTransform()| 返回。
在某些只能对 |pic| 的近似尺寸做出估计的情形(例如 2D 图表),图装入过程
\index{scale@\inlinecode=scale=}
\begin{lstlisting}
frame pic.scale(real xsize=this.xsize, real ysize=this.ysize,
bool keepAspect=this.keepAspect);
\end{lstlisting}
(它将放缩结果帧,包括标注和固定尺寸的对象)将强制完全遵从给定的尺寸设定,但
通常应该并不需要。
要在图外一定边距画出边框,使用函数
\index{bbox@\inlinecode=bbox=}
\begin{lstlisting}
frame bbox(picture pic=currentpicture, real xmargin=0,
real ymargin=xmargin, pen p=currentpen,
filltype filltype=NoFill);
\end{lstlisting}
\index{filltype@\inlinecode=filltype=}
将图装入帧。这里 |filltype| 限定为如下填充类型之一:
\begin{funclist}
\index{FillDraw@\inlinecode=FillDraw=}
\item |FillDraw| \go
填充内部并绘出边界。
\index{FillDraw@\inlinecode=FillDraw=}
\item
\begin{lstlisting}[style=funclist]
FillDraw(real xmargin=0, real ymargin=xmargin, pen fillpen=nullpen,
pen drawpen=nullpen);
\end{lstlisting} \go
如果 |fillpen| 为 |nullpen|,则用正在绘图的画笔填充;否则用画笔 |fillpen|
填充;如果 |drawpen| 为 |nullpen|,则用 |fillpen| 填充;否则用 |drawpen|
填充。可以设定可选的边距 |xmargin| 和 |ymargin|。
\index{Fill@\inlinecode=Fill=}
\item |Fill| \go
填充内部。
\index{Fill@\inlinecode=Fill=}
\item |Fill(real xmargin=0, real ymargin=xmargin, pen p=nullpen)| \go
如果 |p| 为 |nullpen|,则用正在绘图的画笔填充;否则用画笔 |p| 填充。
可以设定可选的边距 |xmargin| 和 |ymargin|。
\index{NoFill@\inlinecode=NoFill=}
\item |NoFill| \go
不填充.
\index{Draw@\inlinecode=Draw=}
\item |Draw| \go
仅绘制边界。
\index{Draw@\inlinecode=Draw=}
\item |Draw(real xmargin=0, real ymargin=xmargin, pen p=nullpen)| \go
如果 |p| 是 |nullpen|,用正在绘图的画笔绘制边界;否则用画笔 |p| 绘制。
可以设定可选的边距 |xmargin| 和 |ymargin|。
\index{UnFill@\inlinecode=UnFill=}
\item |UnFill| \go
剪裁区域\transnote{与 \inlinecode{clip} 命令的结果不同,\inlinecode{UnFill}
选项把区域内部剪掉,保留外部,而 \inlinecode{clip} 命令则保留被剪裁路径的
内部。}。
\index{UnFill@\inlinecode=UnFill=}
\item |UnFill(real xmargin=0, real ymargin=xmargin)| \go
以四周边距 |xmargin| 和 |ymargin| 剪裁区域。
\index{RadialShade@\inlinecode=RadialShade=}
\item |RadialShade(pen penc, pen penr)| \go
从边框中心的 |penc| 到边界的 |penr| 放射状渐变填充。
\end{funclist}
例如,要为一图画出有 $0.25$\,cm 边距的边框并输出结果的帧,使用命令:
\begin{lstlisting}
shipout(bbox(0.25cm));
\end{lstlisting}
用函数 |bbox(p,Fill)| 可以用画笔 |p| 的背景色将图装入帧。
函数
\index{min@\inlinecode=min=}
\index{max@\inlinecode=max=}
\index{size@\inlinecode=size=}
\begin{lstlisting}
pair min(picture pic, user=false);
pair max(picture pic, user=false);
pair size(picture pic, user=false);
\end{lstlisting}
计算出如果图 |pic| 现在使用其默认尺寸设定装入一帧,它将具有的
\prgname{PostScript} 边界。如果 |user| 为 |false|,则返回值为
\prgname{PostScript} 坐标,否则以用户坐标返回。
函数
\index{point@\inlinecode=point=}
\begin{lstlisting}
pair point(picture pic=currentpicture, pair dir, bool user=true);
\end{lstlisting}
是一种方便的方式确定相对图 |pic| 中心按方向 |dir| 得到的边框上的点,忽略固定
尺寸对象(如标签和箭头)的贡献。如果 |user| 为 |false|,则返回值为
\prgname{PostScript} 坐标,否则以用户坐标返回。
函数
\index{truepoint@\inlinecode=truepoint=}
\begin{lstlisting}
pair truepoint(picture pic=currentpicture, pair dir, bool user=true);
\end{lstlisting}
与 |point| 相同,只是它也计入固定尺寸的对象,使用如果图 |pic| 现在用其默认
尺寸设定装入一帧将会具有的放缩变换。如果 |user| 为 |false|,则返回值为
\prgname{PostScript} 坐标,否则以用户坐标返回。
有时在分开的图上绘制对象使用 |add| 函数将图互相加入是有用的:
\index{add@\inlinecode=add=}
\begin{lstlisting}
void add(picture src, bool group=true,
filltype filltype=NoFill, bool above=true);
void add(picture dest, picture src, bool group=true,
filltype filltype=NoFill, bool above=true);
\end{lstlisting}
第一个例子把 |src| 加入 |currentpicture|;第二个把 |src| 加入 |dest|。|group|
选项设定是否在图形用户界面 \prgname{xasy} 中要把 |src| 所有的元素视为单个实体
(见原手册第 10 章 GUI\transnote{本译本未包括此章。}),|filltype| 要求可选的
背景填充或剪裁,而 |above| 设定是否把 |src| 加到已有对象的上面还是下面。
同样还有过程将一个以 \prgname{PostScript} 坐标限定的图或帧 |src|,按照用户
坐标 |position| 加入到另一图 |dest|(或 |currentpicture|)中。
\index{add@\inlinecode=add=}
\begin{lstlisting}
void add(picture src, pair position, bool group=true,
filltype filltype=NoFill, bool above=true);
void add(picture dest, picture src, pair position,
bool group=true, filltype filltype=NoFill, bool above=true);
void add(picture dest=currentpicture, frame src, pair position=0,
bool group=true, filltype filltype=NoFill, bool above=true);
void add(picture dest=currentpicture, frame src, pair position,
pair align, bool group=true, filltype filltype=NoFill,
bool above=true);
\end{lstlisting}
最后一种形式\transnote{原文误作三种。文档这一部分多少与实际源代码不符合的,
且为了易读性,有一些函数形式没有文档,有时令人
相当莫名其妙。请读者在实际中遇到问题时自行查看 \prgname{plain\_picture} 中的
代码。}中的可选参数 |align| 确定用于对齐帧的方向,行为与 |label| 的参数
|align| 类似(参看 \pageref{sec:label} 页 \ref{sec:label} 节标签)。然而,
一个关键区别是当 |align| 未设定时,标签是居中的,而帧与图则以 |position| 为
原点对齐。帧对齐的例子可以在原手册 7.25 节 \prgname{graph} 模块的 errorbars
和 image 例子\transnote{本译本未包括此节。}。如果想对齐三个或更多子图,每次将
它们分为两组:
\lstinputlisting{subpictures.asy}
\begin{center}
\includegraphics{subpictures.pdf}
\end{center}
\index{attach@\inlinecode=attach=}
此外,也可以使用 |attach| 自动增加图 |dest| 的尺寸以适应按用户坐标 |position|
加入的帧 |src|:
\begin{lstlisting}
void attach(picture dest=currentpicture, frame src,
pair position=0, bool group=true,
filltype filltype=NoFill, bool above=true);
void attach(picture dest=currentpicture, frame src,
pair position, pair align, bool group=true,
filltype filltype=NoFill, bool above=true);
\end{lstlisting}
\index{erase@\inlinecode=erase=}
要清除一幅图的内容(但不清除尺寸设定),使用函数
\begin{lstlisting}
void erase(picture pic=currentpicture);
\end{lstlisting}
\index{currentpicture@\inlinecode=currentpicture=}
\index{currentpen@\inlinecode=currentpen=}
\index{currentprojection@\inlinecode=currentprojection=}
\index{save@\inlinecode=save=}
要保存 |currentpicture|、|currentpen| 和 |currentprojection| 的快照,使用函数
|save()|。
\index{restore@\inlinecode=restore=}
要恢复 |currentpicture|、|currentpen| 和 |currentprojection| 的快照,使用函数
|restore()|。\transnote{\inlinecode{save()} 函数和 \inlinecode{restore} 函数
维护一个栈,可以嵌套使用。}
许多关于图和帧的进一步的操作由基本模块 \prgname{plain} 提供\transnote{然而
手册中并没有给出更多的说明,因而可能需要用户自己阅读 \prgname{plain} 模块的
相关代码。}。
使用如下过程之一,可以向一幅图中逐字插入 \prgname{PostScript} 命令:
\index{postscript@\inlinecode=postscript=}
\begin{lstlisting}
void postscript(picture pic=currentpicture, string s);
void postscript(picture pic=currentpicture, string s, pair min,
pair max)
\end{lstlisting}
这里 |min| 与 |max| 可以用于明确指定相关 \prgname{PostScript} 代码导致的
边界。
逐字的 \TeX{} 命令可以使用如下函数之一插入到中间 \LaTeX{} 输出文件中:
\index{tex@\inlinecode=tex=}
\begin{lstlisting}
void tex(picture pic=currentpicture, string s);
void tex(picture pic=currentpicture, string s, pair min, pair max);
\end{lstlisting}
这里 |min| 与 |max| 可以用于明确指定相关 \TeX{} 代码导致的边界。
要在 \TeX{} 导言区(在顶层模块的剩余部分有效\transnote{意谓在导言区的命令是
全局有效的,能影响所有的标签,包括此函数执行前的标签。})发出一个全局的
\TeX{} 命令(如一个 \TeX{} 宏定义),使用:
\index{texpreamble@\inlinecode=texpreamble=}
\begin{lstlisting}
void texpreamble(string s);
\end{lstlisting}
可以重设 \TeX{} 环境为初始状态,清除所有的宏定义,使用函数\transnote{此函数的
作用也是全局的,可以影响执行此函数之前的标签。}
\index{texreset@\inlinecode=texreset=}
\begin{lstlisting}
void texreset();
\end{lstlisting}
过程
\index{usepackage@\inlinecode=usepackage=}
\begin{lstlisting}
void usepackage(string s, string options="");
\end{lstlisting}
提供了
\begin{lstlisting}
texpreamble("\usepackage["+options+"]{"+s+"}");
\end{lstlisting}
的一个方便的缩写形式,可以用于导入 \LaTeX{} 宏包。
\end{typelist}
\section{文件}
% 5.6 Files
\index{文件}
\index{file@\inlinecode=file=}
\index{CSV@\inlinecode=CSV=}
\prgname{Asymptote} 能读写文本文件(包括逗号分隔值文件 CSV)和可移植 XDR
(External Data Representation 外部数据表示)二进制文件。
\index{input@\inlinecode=input=}
一个输入文件必须首先由 |input(string name, bool check=true, string comment="#")|
打开;于是读文件由赋值完成:
\begin{lstlisting}
file fin=input("test.txt");
real a=fin;
\end{lstlisting}
\index{error@\inlinecode=error=}
如果可选的布尔参数 |check| 为 |false|,则不会检查文件是否存在。如果文件不存在
或不可读,函数 |bool error(file)| 将返回 |true|。字符串 |comment| 的第一个
字符指定一个注释字符。如果这个字符在数据文件中出现,此行的剩余部分会被忽略。
当读字符串时,一个注释字符后紧跟另一个注释字符会被看作一个注释字符的字面量。
\index{-globalwrite@\verb=-glo=}
\index{-nosafe@\verb=-nosafe=}
\index{cd@\inlinecode=cd=}
如果 \verb=-globalwrite=(或 \verb=-nosafe=)选项可用,则可以利用函数
|string cd(string s)| 改变当前工作目录为串 |s|,此函数返回新的工作目录。如果
|string s| 为空,路径将被重设为程序启动时的值。
\index{getc@\inlinecode=getc=}
当读入复数(序对)时,括号对是可选的。字符串也通过赋值读入,一起读入字符直到但不包括
换行符。此外,\prgname{Asymptote} 提供函数 |string getc(file)| 读入下一个字符(它把注释
字符当做普通字符)并将其作为一个字符串返回。
\index{output@\inlinecode=output=}
一个名为 |name| 的文件可以用
\begin{lstlisting}
file output(string name, bool update=false);
\end{lstlisting}
\index{-globalwrite@\verb=-glo=}
\index{-nosafe@\verb=-nosafe=}
打开用作输出文件。如果 |update=false|,则文件中已有的全部数据将被清除,并且
只能对文件做写操作。如果 |update=true|,则所有已有的数据都会被保留,读写位置
会设为文件末尾,此时读写操作都可以使用。出于安全原因,只有在 |-globalwrite|
(或 |-nosafe|)命令行选项被设置时,才允许向当前目录以外的文件中写入。
\index{stdin@\inlinecode=stdin=}
\index{stdout@\inlinecode=stdout=}
\index{null@\inlinecode=null=}
有两个特殊的文件:|stdin|(标准输入),它从键盘读入;以及 |stdout|(标准
输出),它向终端输出。文件的隐式初始式为 |null|。
内部类型 |T| 的数据可以通过调用下列函数写入输出文件:
\index{write@\inlinecode=write=}
\begin{lstlisting}
write(string s="", T x, suffix suffix=endl ... T[]);
write(file file, string s="", T x, suffix suffix=none ... T[]);
write(file file=stdout, string s="", explicit T[] x ... T[][]);
write(file file=stdout, T[][]);
write(file file=stdout, T[][][]);
write(suffix suffix=endl);
write(file file, suffix suffix=none);
\end{lstlisting}
\index{none@\inlinecode=none=}
\index{flush@\inlinecode=flush=}
\index{endl@\inlinecode=endl=}
\index{newl@\inlinecode=newl=}
\index{tab@\inlinecode=tab=}
\index{comma@\inlinecode=comma=}
如果 |file| 未被指定,则 |stdout| 将被使用并且默认用换行符结束。如果指定,则
可选的标识字符串 |s| 则会在数据 |x| 之前写入。在写入一维数组时,可以列出任意
数量的数据值。参数 |suffix| 可以是下面几个中的一个:|none|(什么也不做),
|flush|(刷新:立即输出缓存中的数据),|endl|(用换行符结束并刷新),|newl|
(用换行符结束),|tab|(用制表位结束)或是 |comma|(用逗号结束)。这里有一些
数据输出的简单例子:
\begin{lstlisting}
file fout=output("test.txt");
write(fout,1); // 写入 "1"
write(fout); // 写入新的一行
write(fout,"List: ",1,2,3); // 写入 "List: 1 2 3"
\end{lstlisting}
\index{xinput@\inlinecode=xinput=}
\index{xoutput@\inlinecode=xoutput=}
\index{XDR}
\index{binput@\inlinecode=binput=}
\index{boutput@\inlinecode=boutput=}
\index{single@\inlinecode=single=}
一个文件也可以用 |xinput| 或 |xoutput| 而不是 |input| 或 |output| 打开,用来
以 Sun Microsystem 公司的 XDR(External Data Representation 外部数据表示)可
移植二进制格式(它在所有 \prgname{UNIX} 平台都可用)读写双精度(64 位)实数值
和单精度(32 位)整数值。一个文件同样可以由 |binput| 或 |boutput| 打开用来读
写在本地机器的(不可移植)二进制格式双精度值。函数 |file single(file,0.0)| 可
被用来设置一个文件读入单精度实数 XDR 或二进制值;调用
|file single(file,0.0,false)| 又将其设回读入双精度值。函数
|file single(file,0)| 和 |file single(file,0,false)| 可用于改变默认的整数精度
(为单精度)。函数 |file single(file)| 和 |file single(file,false)| 可用于同
时设置实数和整数二者的精度。
\index{eof@\inlinecode=eof=}
\index{eol@\inlinecode=eol=}
\index{error@\inlinecode=error=}
\index{flush@\inlinecode=flush=}
\index{clear@\inlinecode=clear=}
\index{close@\inlinecode=close=}
\index{precision@\inlinecode=precision=}
\index{tell@\inlinecode=tell=}
\index{seek@\inlinecode=seek=}
\index{seekeof@\inlinecode=seekeof=}
可以用布尔值函数 |eof(file)| 测试一个文件的末尾,用 |eol(file)| 测试行末尾,
而用 |error(file)| 测试 I/O 错误。可以用 |flush(file)| 函数刷新输出缓存,用
|clear(file)| 清除原有的 I/O 错误,而用 |close(file)| 关闭文件。给定 |digits|
非零,函数 |int precision(file file=stdout, int digits=0)| 设置文件 |file| 的
输出精度为 |digits| 位数,并返回原来的精度设置。函数 |int tell(file)| 返回
一个文件相对开头的相对位置。过程 |seek(file file, int pos)| 可用来改变这个
位置,而负的位置 |pos| 将被解释为相对文件末尾的位置。例如,可以用命令
|seek(file,0)| 倒回文件开头,用 |seek(file,-1)| 到达文件的最后一个字符处。
命令 |seekeof(file)| 设置位置为文件的末尾。
\index{scroll@\inlinecode=scroll=}
\index{settings.scroll@\inlinecode=settings.scroll=}
设定 |settings.scroll=n| 为一个正整数 |n| 会要求每向 |stdout| 输出 |n| 行就
停顿一下。而后可以按下回车键继续下面的 |n| 行输出,按 |s| 键回车后会使后面的
滚动不再停顿,按 |q| 键回车后跳出当前输出操作。如果 |n| 是负数,输出会每页
滚动一次(即,每比当前屏幕的行数少一行时滚动)。默认值,|settings.scroll=0|,
设定连续滚动。
在 |plain| 模块中定义的过程
\index{getstring@\inlinecode=getstring=}
\index{getint@\inlinecode=getint=}
\index{getreal@\inlinecode=getreal=}
\index{getpair@\inlinecode=getpair=}
\index{gettriple@\inlinecode=gettriple=}
\begin{lstlisting}
string getstring(string name="", string default="", string prompt="",
bool store=true);
int getint(string name="", int default=0, string prompt="",
bool store=true);
real getreal(string name="", real default=0, string prompt="",
bool store=true);
pair getpair(string name="", pair default=0, string prompt="",
bool store=true);
triple gettriple(string name="", triple default=(0,0,0), string prompt="",
bool store=true);
\end{lstlisting}
\index{历史记录}
可以用来显示提示符并从 |stdin| 中读入值,它使用 GNU \prgname{readline} 库。如果
|store=true|,名字 |name| 的值的历史记录会被存在文件 |".asy_history_"|%
${}+{}$|name| 中(见手册的历史记录一节\transnote{这个节译本并不包括此节。请
参看原英文版手册 9 Interactive mode 一章的相关内容。})。
历史记录中最近的值将提供随后执行的默认值。默认值(初始为 |default|)在
|prompt| 之后显示。这些函数都基于内部过程
\index{readline@\inlinecode=readline=}
\index{saveline@\inlinecode=saveline=}
\begin{lstlisting}
string readline(string prompt="", string name="", bool tabcompletion=false);
void saveline(string name, string value, bool store=true);
\end{lstlisting}
这里,|readline| 使用按照 |prompt| 格式化的初始值提示用户输入,而 |saveline|
用于把字符串 |value| 保存在一个名为 |name| 的本地历史记录,可选地把本地历史
记录保存在一个文件 |".asy_history_"|${}+{}$|name| 中。
\index{history@\inlinecode=history=}
过程 |history(string name, int n=1)| 可以用来查找最近为字符串 |name| 输入的第
|n| 个值(或直到 |historylines| 的所有值,如果 |n=0|)。过程
|history(int n=0)| 返回交互历史。例如,
\begin{lstlisting}
write(output("transcript.asy"),history());
\end{lstlisting}
将交互历史输出到文件 |transcript.asy|。
\index{delete@\inlinecode=delete=}
\index{rename@\inlinecode=rename=}
\index{-globalwrite@\verb=-glo=}
\index{-nosafe@\verb=-nosafe=}
函数 |int delete(string s)| 删去文件名为字符串的 |s| 文件。除非
|-globalwrite|(或 |-nosafe|)选项可用,否则此文件必须存在于当前目录。函数
|int rename(string from, string to)| 可以用于把文件 |from| 改名为文件 |to|。
除非 |-globalwrite|(或 |-nosafe|)选项可用,否则此操作也限定在当前目录。函数
\index{convert@\inlinecode=convert=}
\index{animate@\inlinecode=animate=}
\begin{lstlisting}
int convert(string args="", string file="", string format="");
int animate(string args="", string file="", string format="");
\end{lstlisting}
\index{system@\inlinecode=system=}
\index{safe@\verb=safe=}
分别调用 \prgname{ImageMagick} 命令 \prgname{convert} 和 \prgname{animate},
附带参数 |args| 以及由字符串 |file| 和 |format| 构成的文件名\transnote{这可能
是不安全的,因为不能保证这两个命令是不是来自 \prgname{ImageMagick} 以及它们会
做什么。特别地,\prgname{Windows} 系统把 FAT 硬盘格式转换为 NTFS 硬盘格式的
命令就是 \prgname{convert}。}。如果设置 \verb=safe= 为假,则函数
|int system(string s)| 可以用来执行任意的系统命令 |s|。
\section{变量初始式}
% 5.7 Variable initializers
\label{sec:initializers}
% 按:initializer 译为“初始式”,与《C++ 程序设计语言》中裘宗燕教授的译法一致。
\index{初始式}
一个变量可以在声明时赋值,就像 |int x=3;|,其中变量 |x| 被赋以值 |3|。与字面
常量如 |3| 一样,任意表达式都可以用于初始式,如 |real x=2*sin(pi/2);|。
一个变量在初始式被求值之前不会加入名字空间之中,因此举例而言,在
\begin{lstlisting}
int x=2;
int x=5*x;
\end{lstlisting}
中,第二行初始式中的 |x| 表示的是在第一行声明的变量 |x|。于是第二行声明了一个
变量 |x| 遮蔽了原来的 |x| 并将其初始化为值 |10|。
大多数类型的变量声明时可以没有明确的初始式,并且它们会被该类型的默认初始式
初始化:
\begin{itemize}
\index{0}
\index{O@\inlinecode=O=}
\item 数值类型 |int|, |real| 和 |pair| 的变量都初始化为零;类型 |triple| 的
变量初始化为 |O=(0,0,0)|。
\index{false@\inlinecode=false=}
\item |bool| 变量初始化为 |false|。
\index{空串}
\item |string| 变量初始化为空串。
\index{恒同变换}
\item |transform| 变量初始化为恒同变换。
\index{nullpath@\inlinecode=nullpath=}
\item |path| 和 |guide| 变量初始化为 |nullpath|。
\index{默认画笔}
\item |pen| 变量初始化为默认画笔。
\index{空帧}\index{空图}
\item |frame| 和 |picture| 变量分别初始化为空的帧与图。
\index{null@\inlinecode=null=}
\item |file| 变量初始化为 |null|。
\end{itemize}
\index{code@\inlinecode=code=}
用户定义的数组、结构体和函数类型的默认初始式分别在对应的章节说明。一些类型,
如 |code|,没有默认的初始式。当一个这种类型的变量被引入,用户必须明确用一个值
对其进行初始化。
\index{operator init@\inlinecode=operator init=}
\index{init@\inlinecode=init=}
任意类型 |T| 的默认初始式可能通过定义函数 |T operator init()| 来重新声明。
例如,|int| 变量通常被初始化为零,但在
\begin{lstlisting}
int operator init() {
return 3;
}
int y;
\end{lstlisting}
中,变量 |y| 初始化为 |3|。这只是一个用于说明的例子;并不推荐重新声明内部类型
的初始式。|operator init| 典型的用法是定义用户定义类型的有意义的默认值。
\section{结构体}
% 5.8 Structures
\label{sec:structures}
\index{结构体}
\index{struct@\inlinecode=struct=}
\index{public@\inlinecode=public=}
\index{restricted@\inlinecode=restricted=}
\index{private@\inlinecode=private=}
\index{this@\inlinecode=this=}
用户可以定义自己的数据类型如结构体,连同用户定义的算子,就像在 C++ 中一样。
默认地,结构体的成员是 |public|(公有的;可以在代码的任何地方读和更改),但
也可选择声明为 |restricted|(受限的;可以在任何地方读,但只能在定义它的结构体
内部修改)或是 |private|(私有的;仅能在结构体内部读写)。在一个结构体的定义
中,关键字 |this| 可用于在一个表达式中引用包围它的结构体。在结构体顶层作用域
的任何代码都会在初始化时执行。
\index{引用}
\index{结构体!引用}
变量保存的是结构体的引用\transnote{请读者注意,这与 C/C++ 有重要的不同。}。就
是说,例如\transnote{一个语法细节:结构体的定义后不需要再加分号,这与 C/C++
不同。}:
\begin{lstlisting}
struct T {
int x;
}
T foo=new T;
T bar=foo;
bar.x=5;
\end{lstlisting}
变量 |foo| 保存一个到结构体 |T| 的实例的引用。当 |bar| 被赋值为 |foo| 时,它
也保存一个和 |foo| 相同的引用。赋值 |bar.x=5| 改变那个实例中 |x| 域的值,因而
|foo.x| 将也等于 |5|。
表达式 |new T| 创建结构体 |T| 的一个新的实例并返回到该实例的引用。在建立新
实例时,在记录定义的体中的所有代码都会被执行。例如
\begin{lstlisting}
int Tcount=0;
struct T {
int x;
++Tcount;
}
T foo=new T;
\end{lstlisting}
\index{new@\inlinecode=new=}
这里,表达式 |new T| 将会产生这个类的一个新的实例,但同时也会使 |Tcount|
增长,因此它会保有实例产生数目的轨迹\transnote{然而这个用法并不很好。如果真要
记录实例创建的数目,应该将 \inlinecode{Tcount} 设置为类 \inlinecode{T} 的静态
成员。参看 \pageref{sec:static} 页 \ref{sec:static} 节。}。
\index{null@\inlinecode=null=}
\index{空引用}
\index{结构体!空引用}
表达式 |null| 可以转换为任何结构体类型的空引用,即一个不指向任何实际结构体
实例的引用。试图使用一个空引用的域将导致错误。
\index{alias@\inlinecode=alias=}
\index{==@\inlinecode/==/}
\index{!=@\inlinecode/!=/}
\index{深比较}
函数 |bool alias(T,T)| 检查两个结构体是否引用结构体的同一个实例(或同时为
|null|)。例如,在本节开头的示例代码中,|alias(foo,bar)| 将返回真,但
|alias(foo,new T)| 将返回假,因为 |new T| 创建结构体 |T| 的一个新的实例。布尔
运算符 |==| 和 |!=| 默认分别等价于 |alias| 和 |!alias|,但对特定的类型它们
可以被改写(例如做深比较\transnote{指按结构体的嵌套结构递归进行的,逐个域的
比较。相关的概念还有深复制。在后面章节数组的讨论中,也有类似的深比较和
深复制概念。})。
在定义结构体 |T| 之后,一个类型为 |T| 的变量默认会被初始化为一个新的实例
(|new T|)。然而,在结构体的定义中,类型为 |T| 的变量默认会被初始化为
|null|。这个特殊的行为可以避免在下面这种代码中建立一个新实例时的无穷递归:
\begin{lstlisting}
struct tree {
int value;
tree left;
tree right;
}
\end{lstlisting}
这里是一个简单的例子,解释结构体的使用:
\begin{lstlisting}
struct S {
real a=1;
real f(real a) {return a+this.a;}
}
S s; // 用 new S 初始化 s
write(s.f(2)); // 输出 3
S operator + (S s1, S s2)
{
S result;
result.a=s1.a+s2.a;
return result;
}
write((s+s).f(0)); // 输出 2
\end{lstlisting}
有构造一个结构体的新实例的函数通常很方便。比如我们有一个 |Person| 结构体:
\begin{lstlisting}
struct Person {
string firstname;
string lastname;
}
Person joe=new Person;
joe.firstname="Joe";
joe.lastname="Jones";
\end{lstlisting}
创建一个新的“人”(Person)是件繁琐的事;要用三行来建立一个新的实例并初始化
它的域(尽管这比在实际生活中建立一个新的人花的力气还要少得多)。
\index{构造函数}
我们可以通过定义一个构造函数 |Person(string,string)| 来简化这件工作:
\begin{lstlisting}
struct Person {
string firstname;
string lastname;
static Person Person(string firstname, string lastname) {
Person p=new Person;
p.firstname=firstname;
p.lastname=lastname;
return p;
}
}
Person joe=Person.Person("Joe", "Jones");
\end{lstlisting}
现在比以前建立一个新的实例要容易了,但我们还得用限定过的名字 |Person.Person|
引用构造函数。如果紧接着结构体的定义后增加一行
\begin{lstlisting}
from Person unravel Person;
\end{lstlisting}
则构造函数就可以不加限定地使用:|Person joe=Person("Joe", "Jones");|。
现在构造函数是容易用了,可定义起来却成了件麻烦事儿。如果你要写大量的构造
函数,你就会发现每次你都得重复大量的代码。幸运的是,你友好的邻居 Asymptote 的
开发者们设计出了一种方法能自动化完成大部分的过程。
如果,在结构体的(定义)体中,Asymptote 遇到形如 |void operator init(args)|
的函数定义,则会隐式地定义一个以 |args| 为函数的构造函数来初始化一个结构体的
新实例。即,它实质上定义了如下的构造函数(假定结构体名叫 |Foo|):
\lstset{emph=args,emphstyle={\slshape}}
\begin{lstlisting}
static Foo Foo(args) {
Foo instance=new Foo;
instance.operator init(args);
return instance;
}
\end{lstlisting}
\lstset{emph=}
这个构造函数也会被隐式地复制到结构体定义结束后的外围作用域中,因而它可以随后
就不加限定地以结构体的名字使用。我们 |Person| 的例子从而可以实现为:
\begin{lstlisting}
struct Person {
string firstname;
string lastname;
void operator init(string firstname, string lastname) {
this.firstname=firstname;
this.lastname=lastname;
}
}
Person joe=Person("Joe", "Jones");
\end{lstlisting}
\index{operator init@\inlinecode=operator init=}
\index{init@\inlinecode=init=}
|operator init| 隐式定义构造函数的用法不应与它定义变量的默认值的用法相混淆
(见 \pageref{sec:initializers} 页 \ref{sec:initializers} 节)。确实,在前
一种情况,|operator init| 的返回值类型必须是 |void|,而第二种情形它必须是变量
的(非 |void|)类型。
\index{cputime@\inlinecode=cputime=}
\index{parent.user@\inlinecode=parent.user=}
\index{parent.system@\inlinecode=parent.system=}
\index{child.user@\inlinecode=child.user=}
\index{child.system@\inlinecode=child.system=}
\index{change.user@\inlinecode=change.user=}
\index{change.system@\inlinecode=change.system=}
函数 |cputime()| 返回一个结构体 |cputime|,它保存不断增长的 CPU 时间,分为域
|parent.user|, |parent.system|, |child.user| 和 |child.system|。为了方便,
增加的域 |change.user| 和 |change.system| 显示对应的总父进程和子进程 CPU 时间
自上次调用 |cputime()| 的改变量。函数
\index{write@\inlinecode=write=}
\begin{lstlisting}
void write(file file=stdout, string s="", cputime c,
string format=cputimeformat, suffix suffix=none);
\end{lstlisting}
显示增加的用户 CPU 时间(后跟“u”)、增加的系统 CPU 时间(后跟“s”)、总的
用户 CPU 时间(后跟“U”)以及总的系统 CPU 时间(后跟“S”)。
\index{类型转换}
\index{继承}
\index{虚函数}
很像 C++ 中那样,类型转换(见 \pageref{sec:casts} 页 \ref{sec:casts} 节)提供
了结构体\transnote{即 C++ 中的类。}继承和虚函数的一个优雅的实现\transnote{这
只是对于 C++ 继承和多态性的一种模拟,然而这或许并不能算是如何优雅的实现。
对这里列出的简单示例,确实可以进行一些改进:在 \inlinecode{child} 定义
中的 \inlinecode{parent} 声明后面加一句 \inlinecode{unravel parent;} 可以免去
在引用基类成员 \inlinecode{parent.x} 时的限定符 \inlinecode{parent}。而对于虚
函数的定义,或许 \inlinecode{_f(parent obj=this, int)} 比
\inlinecode{virtual(int)} 的写法更好,因为虚函数可能不只一个,并且很可能会
用到 \inlinecode{this} 变量引用。}:
\begin{lstlisting}
struct parent {
real x=1;
void virtual(int) {write (0);}
void f() {virtual(1);}
}
void write(parent p) {write(p.x);}
struct child {
parent parent;
real y=2;
void virtual(int x) {write (x);}
parent.virtual=virtual;
void f()=parent.f;
}
parent operator cast(child child) {return child.parent;}
parent p;
child c;
write(c); // 输出 1;
p.f(); // 输出 0;
c.f(); // 输出 1;
write(c.parent.x); // 输出 1;
write(c.y); // 输出 2;
\end{lstlisting}
更多结构体的例子,见 \prgname{Asymptote} 基础模块 \prgname{plain} 中的
|Legend| 和 |picture|。
\section{运算符}
% 5.9 Operators
\index{运算符!算术和逻辑运算符}
\subsection{算术和逻辑运算符}
% 5.9.1 Arithmetic & logical operators
\label{subsec:arthlogoper}
\index{quotient@\inlinecode=quotient=}
\prgname{Asymptote} 使用标准二元算术运算符。然而,当一个整数被另一个整数所除
时,两个参数都会在相除之前被转换为实数值并返回一个实数商(鉴于这通常是人们想
要的)。函数 |int quotient(int x, int y)| 返回小于等于 |x/y| 的最大整数。在
所有其他情况下,两个运算对象会被提升为相同的类型,此类型也将成为结果的类型:
\index{+@\inlinecode=+=}
\index{-@\inlinecode=-=}
\index{*@\inlinecode=*=}
\index{/@\inlinecode=/=}
\index{%@\inlinecode=%=}
\index{^@\inlinecode=^=}
\index{**@\inlinecode=**=}
\begin{longtable}[l]{l@{\hspace*{4em}}p{\dimexpr\textwidth-5em}}
|+| & 加法 \\
|-| & 减法 \\
|*| & 乘法 \\
|/| & 除法 \\
|%| &
模(求余数);结果总与除数的符号相同。特别地,对所有整数 \inlinecode|p| 和
非零整数 \inlinecode|q| 得到结果 \inlinecode|q*quotient(p,q)+p%q == p|。 \\
|^| &
乘方;如果指数(第二个参数)是一个整数型,则会使用递归乘法;否则,会使用
对数和指数计算(\inlinecode|**| 与 \inlinecode|^| 同义)。
\end{longtable}
通常的布尔运算符同样有定义\transnote{这里的布尔运算符都只能用于
\inlinecode{bool} 类型量,不包括按位计算。而且与 C/C++ 不同,其他类型的量不能
依是否为 0 值自动转化为 \inlinecode{bool} 类型的量。因而 \inlinecode{3 && 5}
这种写法是非法的,必须写成 \inlinecode{3!=0 && 5!=0}。}:
\index{==@\inlinecode/==/}
\index{!=@\inlinecode/!=/}
\index{<@\inlinecode/</}
\index{<=@\inlinecode/<=/}
\index{>=@\inlinecode/>=/}
\index{>@\inlinecode/>/}
\index{&&@\inlinecode=&&=}
\index{&@\inlinecode=&=}
\index{"|"|@\inlinecode="|"|=}
\index{"|@\inlinecode="|=}
\index{^@\inlinecode=^=}
\index{"!@\inlinecode="!=}
\begin{longtable}[l]{l@{\hspace*{4em}}p{\dimexpr\textwidth-4.5em}}
|==| & 等于 \\
|!=| & 不等于 \\
|<| & 小于 \\
|<=| & 小于等于 \\
|>=| & 大于等于 \\
|>| & 大于 \\
|&&| & 与(右边参数为条件求值\transnote{指短路求值。仅当左边参数求值为真时才
对右边求值。或运算类似,仅当左边参数求值为假时才对右边求值。}) \\
|&| & 与 \\
\inlinecode=||= & 或(右边参数为条件求值) \\
\inlinecode=|= & 或 \\
|^| & 异或 \\
|!| & 非
\end{longtable}
\index{? :@\inlinecode=? :=}
\prgname{Asymptote} 同样支持类似 C 的条件语法:
\begin{lstlisting}
bool positive=(pi >= 0) ? true : false;
\end{lstlisting}
\index{interp@\inlinecode=interp=}
函数 |T interp(T a, T b, real t)| 对所有非整数的内部数值类型 |T| 返回
|(1-t)*a+t*b|。如果 |a| 和 |b| 是画笔(|pen|),它们会首先被提升到相同的颜色
空间。
\index{AND@\inlinecode=AND=}
\index{OR@\inlinecode=OR=}
\index{XOR@\inlinecode=XOR=}
\index{NOT@\inlinecode=NOT=}
\index{按位运算}
\prgname{Asymptote} 同样定义了按位函数 |int AND(int,int)|, |int OR(int,int)|,
|int XOR(int,int)| 以及 |int NOT(int)|。
\index{运算符!前缀运算符}
\index{运算符!自(赋值)运算符}
\subsection{自(赋值)与前缀运算符}
% 5.9.2 Self & prefix operators
\index{+=@\inlinecode/+=/}
\index{-=@\inlinecode/-=/}
\index{*=@\inlinecode/*=/}
\index{/=@\inlinecode+/=+}
\index{%=@\inlinecode/%=/}
\index{^=@\inlinecode/^=/}
\index{++@\inlinecode=++=}
\index{--@\inlinecode=--=}
与在 C 中一样,每个运算符 |+|, |-|, |*|, |/|, |%|
和 |^| 都可以作为自(赋值)运算符使用。前缀运算符 |++|(加一)与 |--|(减一)
也有定义。例如,
\begin{lstlisting}
int i=1;
i += 2;
int j=++i;
\end{lstlisting}
就等价于代码
\begin{lstlisting}
int i=1;
i=i+2;
int j=i=i+1;
\end{lstlisting}
然而,后缀运算符如 |i++| 和 |i--| 并未定义(由于 |--| 路径连接运算符产生的
固有歧义性)。只在罕见的实例中 |i++| 和 |i--| 才真正需要,它们可以分别替换为
表达式 |(++i-1)| 和 |(--i+1)|。
\index{运算符!用户自定义运算符}
\subsection{用户自定义运算符}
% 5.9.3 User-defined operators
\index{operator@\inlinecode=operator=}
\index{运算符!重载}
下列的符号可以与 |operator| 一起为结构体或内部类型定义或重定义运算符:
\index{-@\inlinecode=-=}
\index{+@\inlinecode=+=}
\index{*@\inlinecode=*=}
\index{/@\inlinecode=/=}
\index{%@\inlinecode=%=}
\index{^@\inlinecode=^=}
\index{**@\inlinecode=**=}
\index{"!@\inlinecode="!=}
\index{<@\inlinecode/</}
\index{>@\inlinecode/>/}
\index{==@\inlinecode/==/}
\index{<=@\inlinecode/<=/}
\index{>=@\inlinecode/>=/}
\index{&@\inlinecode=&=}
\index{"|@\inlinecode="|=}
\index{^^@\inlinecode=^^=}
\index{..@\inlinecode=..=}
\index{::@\inlinecode=::=}
\index{--@\inlinecode=--=}
\index{---@\inlinecode=---=}
\index{++@\inlinecode=++=}
\index{<<@\inlinecode=<<=}
\index{>>@\inlinecode=>>=}
\index{$@\inlinecode=$=}
\index{$$@\inlinecode=$$=}
\index{"@@\inlinecode="@=}
\index{"@"@@\inlinecode="@"@=}
\begin{lstlisting}
- + * / % ^ ! < > == != <= >= & | ^^ .. :: -- --- ++
<< >> $ $$ @ @@
\end{lstlisting}
第二行的运算符拥有比布尔运算符 |<|, |>|, |<=| 和 |>=| 高一级的优先级%
\transnote{手册这一节对于运算符及其自定义的介绍还是有些过于简略了,比如所有
运算符的优先级和结合性都没有详细说明,事实上它们大致与 C/C++ 中相同。第二行的
运算符对内部类型都没有定义,据实验,它们都是左结合的普通二元运算符。还有需要
说明的是,前置的 \inlinecode{++}, \inlinecode{--} 运算符的运算对象必须是一个
左值,这会在重载时带来一些限制。C/C++ 中常见的逗号运算符、关于指针的运算符、
\inlinecode{sizeof} 等都不存在。赋值、条件、圆点、指标 \inlinecode{[]}、括号
等也都不能重载。重载 \inlinecode{**} 与重载 \inlinecode{^} 效果相同。}。
路向运算符如 |..| 可以重载,比如说,写一个用户自定义函数从给定路向产生一条
新的路向:
\begin{lstlisting}
guide dots(... guide[] g)=operator ..;
guide operator ..(... guide[] g) {
guide G;
if(g.length > 0) {
write(g[0]);
G=g[0];
}
for(int i=1; i < g.length; ++i) {
write(g[i]);
write();
G=dots(G,g[i]);
}
return G;
}
guide g=(0,0){up}..{SW}(100,100){NE}..{curl 3}(50,50)..(10,10);
write("g=",g);
\end{lstlisting}
\section{隐式放缩}
% 5.10 Implicit scaling
\index{隐式放缩}
如果一个数值字面量在特定类型的表达式前面,则它们会相乘:
\begin{lstlisting}
int x=2;
real y=2.0;
real cm=72/2.540005;
write(3x);
write(2.5x);
write(3y);
write(-1.602e-19 y);
write(0.5(x,y));
write(2x^2);
write(3x+2y);
write(3(x+2y));
write(3sin(x));
write(3(sin(x))^2);
write(10cm);
\end{lstlisting}
这会产生输出
\begin{lstlisting}
6
5
6
-3.204e-19
(1,1)
8
10
18
2.72789228047704
2.48046543129542
283.464008929116
\end{lstlisting}
\section{函数}
% 5.11 Functions
\label{sec:functions}
\index{函数}
\prgname{Asymptote} 的函数被看作是带有标识的变量(非函数变量有空的标识)。
允许有名字相同的变量,只要它们有不同的标识。
\index{函数!参数传值}
\index{函数!参数传引用}
函数的参数是传值的。要以传引用方式传递参数,只不过需要把它用一个结构体包装
起来(见 \pageref{sec:structures} 页 \ref{sec:structures} 节)。
这里是 \prgname{Asymptote} 函数的一些重要特性:
\begin{enumerate}
% 1
\item 带标识的变量(函数)和不带标识的变量(非函数变量)是不同的:
\begin{lstlisting}
int x, x();
x=5;
x=new int() {return 17;};
x=x(); // 调用 x() 并将结果 17 存入标量 x
\end{lstlisting}
% 2
\item 允许使用传统函数的定义:
\begin{lstlisting}
int sqr(int x)
{
return x*x;
}
sqr=null; // 然而这个函数仍然只是一个变量。
\end{lstlisting}
% 3
\item 类型转换可用于消解歧义:
\begin{lstlisting}
int a, a(), b, b(); // 合法:创建四个变量
a=b; // 不合法:赋值有歧义
a=(int) b; // 合法:消解歧义
(int) (a=b); // 合法:消解歧义
(int) a=b; // 不合法:类型转换表达式不能为左值
int c();
c=a; // 合法:只有一种可能的赋值
\end{lstlisting}
% 4
\index{函数!匿名函数}
\index{函数!高阶函数}
\index{匿名函数}
\index{高阶函数}
\item 同样允许匿名(又称“高阶”)函数:
\begin{lstlisting}
typedef int intop(int);
intop adder(int m)
{
return new int(int n) {return m+n;};
}
intop addby7=adder(7);
write(addby7(1)); // 写入 8
\end{lstlisting}
% 5
\item 可以通过赋值为另一个(匿名的或命名的)函数来重定义一个函数 |f|,即便对
那些以前声明过的函数的调用。然而,如果 |f| 被新的函数定义重载,先前的调用仍会
访问 |f| 原来的版本,如下例所示\transnote{这两句话有些拗口。注意这里有一个
区别,即原来只是\emph{声明}而没有\emph{定义}(而且没有通过自定义
\inlinecode{operator init()} 而初始化,从而初值为 \inlinecode{null})的
函数 \inlinecode{f},如果被其他函数 \inlinecode{g} 的定义调用,则给
\inlinecode{f} 合适的定义后,\inlinecode{g} 的定义也会跟着变化;但如果原来
\inlinecode{f} 已经有\emph{定义},则重定义 \inlinecode{f} 并不会改变
\inlinecode{g} 的定义,正如例子中展示的那样。这儿的例子只展示了后一种情况,
前一种情况例子可以参看下一个条目。}:
\begin{lstlisting}
void f() {
write("hi");
}
void g() {
f();
}
g(); // 写入 "hi"
f=new void() {write("bye");};
g(); // 写入 "bye"
void f() {write("overloaded");};
f(); // 写入 "overloaded"
g(); // 写入 "bye"
\end{lstlisting}
% 6
\item 匿名函数可以用于重定义一个已经声明(并隐式初始化为空函数(|null|))但
尚未明确定义的函数变量:
\begin{lstlisting}
void f(bool b);
void g(bool b) {
if(b) f(b);
else write(b);
}
f=new void(bool b) {
write(b);
g(false);
};
g(true); // 写入 true, 然后写入 false.
\end{lstlisting}
\end{enumerate}
\prgname{Asymptote} 是我们已知唯一的把函数看做变量,但又通过基于标识的变量而
允许重载的语言。
\index{函数!递归}
允许函数递归调用自身。像在 C++ 中一样,无穷嵌套的递归将导致栈溢出(报告为一个
段错误,除非一个完整工作着的 GNU \prgname{libsigsegv} 库(如 2.4 或更新)在
配置的时候已经安装)。
\subsection{默认参数}
% 5.11.1 Default arguments
\prgname{Asymptote} 支持比 C++ 更为灵活的默认函数参数机制:它们可以在函数原型
的任何位置出现。由于特定的数据类型被隐式转换为更复杂的类型(见
\pageref{sec:casts} 页 \ref{sec:casts} 节),经常可以通过将函数参数从最简单到
最复杂排列来避免歧义性。例如,给定
\begin{lstlisting}
real f(int a=1, real b=0) {return a+b;}
\end{lstlisting}
则 |f(1)| 返回 1.0,但 |f(1.0)| 返回 2.0。
默认参数的值在被调函数定义的环境中对给定 \prgname{Asymptote} 表达式求值决定。
\subsection{具名参数}
% 5.11.2 Named arguments
有时很难记住函数声明中参数出现的顺序。具名(带关键字的)参数使调用多参数函数
更容易了。不像在 C 和 C++ 语言里那样,在函数参数中的赋值被解释为在函数标识中
同名参数的赋值,\emph{而不在局部作用域中}。命令行选项 |-d| 可用于检查
\prgname{Asymptote} 代码中可能把具名参数误拼为一个局部赋值的情况。
当参数与标识匹配时,首先所有的关键字相匹配,而后不具名的参数再如常与未匹配的
形参相匹配。例如,
\begin{lstlisting}
int f(int x, int y) {
return 10x+y;
}
write(f(4,x=3));
\end{lstlisting}
输出 34,因为在我们试图给无名参数 |4| 匹配时,|x| 已经匹配过了,因而它与
下一项 |y| 来匹配。
对于想要在函数参数中进行局部变量赋值的罕见情形(通常\emph{不是}一种好的编程
实践),仅仅需要把赋值放进括号里。例如,给定在前面例子中 |f| 的定义,
\begin{lstlisting}
int x;
write(f(4,(x=3)));
\end{lstlisting}
就等价于语句
\begin{lstlisting}
int x;
x=3;
write(f(4,3));
\end{lstlisting}
而输出 43。
作为一个技术细节,我们指出,鉴于在同一个作用域中允许有同名但有不同标识的
变量,代码
\begin{lstlisting}
int f(int x, int x()) {
return x+x();
}
int seven() {return 7;}
\end{lstlisting}
在 \prgname{Asymptote} 中是合法的,而 |f(2,seven)| 返回 9。一个具名参数匹配
第一个同名的未匹配的形参,于是 |f(x=2,x=seven)| 就是等价的调用,然而
|f(x=seven,2)| 则不是,因为第一个参数与第一个形参匹配,而 |int()| 不能隐式地
转换为 |int|。默认参数不会影响一个具名参数匹配的形式参数,于是如果 |f| 被定义
为
\begin{lstlisting}
int f(int x=3, int x()) {
return x+x();
}
\end{lstlisting}
则 |f(x=seven)| 将是非法的,即使 |f(seven)| 显然是允许的。
\subsection{剩余参数}
\label{subsec:restarguments}
% 5.11.3 Rest arguments
剩余参数允许人们写使用可变个数参数的函数:
\begin{lstlisting}
// 此函数将其参数求和
int sum(... int[] nums) {
int total=0;
for(int i=0; i < nums.length; ++i)
total += nums[i];
return total;
}
sum(1,2,3,4); // 返回 10
sum(); // 返回 0
// 此函数用第一个参数减去后面的参数
int subtract(int start ... int[] subs) {
for(int i=0; i < subs.length; ++i)
start -= subs[i];
return start;
}
subtract(10,1,2); // 返回 7
subtract(10); // 返回 10
subtract(); // 非法
\end{lstlisting}
把一个参数放进一个剩余表称作\emph{打包}。可以为剩余参数给定一个明确的参数表,
从而 |subtract| 也可以选择实现为
\begin{lstlisting}
int subtract(int start ... int[] subs) {
return start - sum(... subs);
}
\end{lstlisting}
甚至可以把剩余参数与普通参数组合使用:
\begin{lstlisting}
sum(1,2,3 ... new int[] {4,5,6}); // 返回 21
\end{lstlisting}
它建立一个六个元素的数组,以 |nums| 传递给 |sum|。相反的操作,\emph{解包},
却是不允许的:
\begin{lstlisting}
subtract(... new int[] {10, 1, 2});
\end{lstlisting}
是非法的,因为开头的形参不匹配。
如果没有参数被打包,一个长度为零的数组(与 |null| 相反\transnote{长度为零的
数组(空白数组 empty array)和空数组(null array)是不同的概念,见
\pageref{sec:arrays} 页 \ref{sec:arrays} 节的解释。事实上,长度为零的数组是已
分配空间的数组,而空数组 \inlinecode{null} 未分配空间,意义相当于 C/C++ 中的
空指针。})会约束到剩余参数上。注意剩余参数的默认参数会被忽略而且剩余参数不会
约束到一个关键字上。
在 \prgname{Asymptote} 中重载的解决与 C++ 中使用的函数匹配规则类似。每个参数
匹配会被给以一个得分。精确匹配的得分比需要类型转换的得分好,而形参的匹配(不
考虑类型转换)又比把一个参数打包进剩余数组的得分好。如果有唯一的最大候选,它
就会被选取;否则,就会有歧义性错误。
\begin{lstlisting}
int f(path g);
int f(guide g);
f((0,0)--(100,100)); // 匹配第二个;参数是一个 guide
int g(int x, real y);
int g(real x, int x);
g(3,4); // 歧义;第一个候选的第一个参数更好,
// 但第二个候选的第二个参数更好
int h(... int[] rest);
int h(real x ... int[] rest);
h(1,2); // 第二个定义匹配,尽管有一个类型转换,
// 因为类型转换比打包更好
int i(int x ... int[] rest);
int i(real x, real y ... int[] rest);
i(3,4); // 歧义;第一个候选的第一个参数更好,
// 但第二个候选的第二个参数更好
\end{lstlisting}
\subsection{数学函数}
% 5.11.4 Mathematical functions
\index{libm 过程@\prgname{libm} 过程}
\index{sin@\inlinecode=sin=}
\index{cos@\inlinecode=cos=}
\index{tan@\inlinecode=tan=}
\index{asin@\inlinecode=asin=}
\index{acos@\inlinecode=acos=}
\index{atan@\inlinecode=atan=}
\index{exp@\inlinecode=exp=}
\index{log@\inlinecode=log=}
\index{pow10@\inlinecode=pow10=}
\index{log10@\inlinecode=log10=}
\index{sinh@\inlinecode=sinh=}
\index{cosh@\inlinecode=cosh=}
\index{tanh@\inlinecode=tanh=}
\index{asinh@\inlinecode=asinh=}
\index{acosh@\inlinecode=acosh=}
\index{atanh@\inlinecode=atanh=}
\index{sqrt@\inlinecode=sqrt=}
\index{cbrt@\inlinecode=cbrt=}
\index{fabs@\inlinecode=fabs=}
\index{expm1@\inlinecode=expm1=}
\index{log1p@\inlinecode=log1p=}
\index{identity@\inlinecode=identity=}
\index{J@\inlinecode=J=}
\index{Y@\inlinecode=Y=}
\index{gamma@\inlinecode=gamma=}
\index{erf@\inlinecode=erf=}
\index{erfc@\inlinecode=erfc=}
\index{atan2@\inlinecode=atan2=}
\index{hypot@\inlinecode=hypot=}
\index{fmod@\inlinecode=fmod=}
\index{remainder@\inlinecode=remainder=}
\prgname{Asymptote} 有标准 \prgname{libm} 数学 |real(real)| 型函数的内部
版本:|sin|, |cos|, |tan|, |asin|, |acos|, |atan|,
|exp|, |log|, |pow10|, |log10|, |sinh|, |cosh|, |tanh|, |asinh|, |acosh|,
|atanh|, |sqrt|, |cbrt|, |fabs|, |expm1|, |log1p| 以及恒等函数 |identity|\transnote{
三角函数 \inlinecode{sin} 正弦,\inlinecode{cos} 余弦,\inlinecode{tan} 正切
以弧度为单位;反三角函数\inlinecode{asin} 反正弦,\inlinecode{acos}
反余弦,\inlinecode{atan} 反正切返回弧度值;指数 \inlinecode{exp},对数
\inlinecode{log} 以 $\mathrm{e}$ 为底;指数 \inlinecode{pow10},对数
\inlinecode{log10} 以 $10$ 为底;双曲(反)三角函数
\inlinecode{sinh} 双曲正弦,\inlinecode{cosh} 双曲余弦,\inlinecode{tanh}
双曲正切,\inlinecode{asinh} 双曲反正弦,\inlinecode{acosh} 双曲反余弦,
\inlinecode{atanh} 双曲反正切;\inlinecode{sqrt} 平方根;\inlinecode{cbrt}
立方根;\inlinecode{fabs} 实数绝对值;
\inlinecode{expm1} 为 $x \mapsto \exp x - 1$(对接近零的参数也精确);
\inlinecode{log1p} 为 $x \mapsto \log(1+x)$(对接近零的参数也精确);
恒等函数 \inlinecode{identity} 与产生单位方阵的函数同名。}。
\prgname{Asymptote} 同样定义了 |n| 阶 Bessel 函数:
第一类 |J(int n, real)| 以及第二类 |Y(int n, real)|,还有 $\Gamma$ 函数
|gamma|,误差函数 |erf|,以及互补误差函数 |erfc|。还包括标准的
|real(real, real)| 型函数 |atan2|, |hypot|, |fmod|, |remainder|\transnote{
函数 \inlinecode{atan2(real y, real x)} 计算 \inlinecode{y/x} 的反正切;函数
\inlinecode{hypot(real x, real y)} 计算三角形斜边长 $\sqrt{x^2+y^2}$;
\inlinecode{fmod} 和 \inlinecode{remainder} 都是实数的求余函数,前者符号与
被除数相同,后者取对称模。}。
函数 |degrees(real radians)| 和 |radians(real degrees)| 可用来在弧度和度之间
转换。函数 |Degrees(real radians)| 返回在区间 $[0,360)$ 中的角度度数。为方便
计,\prgname{Asymptote} 定义了使用度而非弧度的标准三角函数变体 |Sin|, |Cos|,
|Tan|, |aSin|, |aCos| 和 |aTan|。我们同样定义了 |sqrt|, |sin|, |cos|, |exp|,
|log| 和 |gamma| 的复数版本。
函数 |floor|, |ceil| 和 |round|\transnote{\inlinecode{floor} 是向下取整
$\lfloor x \rfloor$,\inlinecode{ceil} 是向上取整 $\lceil x \rceil$,而
\inlinecode{round} 是四舍五入取整。}与它们通常的定义不同,它返回一个整数值而
非实数值(鉴于这是通常人们想要的)。函数 |Floor|, |Ceil| 和 |Round| 分别与
前面的类似,只是如果结果不能被转化为一个合法的整数值,它们会对正参数返回
|intMax| 而对负参数返回 |intMin|,而不会产生整数溢出。我们同样定义了函数
|sgn|,它以一个整数($-1$, $0$ 或 $1$)返回实数参数的符号。
有 |abs(int)| 函数,还有 |abs(real)| 函数(等价于 |fabs(real)|),以及
|abs(pair)| 函数(等价于 |length(pair)|)。
随机数可由 |srand(int)| 设定种子并由 |int rand()| 产生,它返回一个 $0$ 到
整数 |randMax| 的整数。函数 |unitrand()| 返回一个 $[0,1]$ 区间均匀分布的随机
数。Gauss 分布的随机数生成器 |Gaussrand| 以及一集统计过程,包括 |histogram|%
(直方图),由基础模块文件 \prgname{stats.asy} 提供\transnote{这里的介绍十分
简略,有需要的用户应去参考 \prgname{stats.asy} 的源文件以获得详细的函数参数
形式。}。还定义了函数\transnote{这两个函数都返回 \inlinecode{int} 类型。}%
|factorial(int n)|,返回 $n!$,以及 |choose(int n, int k)|,返回
$n!/(k!(n-k))!$。
\bgroup\sloppy
如果配置加上了 GNU 科学计算库(GNU Scientific Library, GSL),它在
\url{http://www.gnu.org/software/gsl/} 可得到,则 \prgname{Asymptote} 会包括
一个内部模块 \prgname{gsl} 定义了 Airy 函数 |Ai(real)|, |Bi(real)|,
|Ai_deriv(real)|, |Bi_deriv(real)|, |zero_Ai(int)|, |zero_Bi(int)|,
|zero_Ai_deriv(int)|, |zero_Bi_deriv(int)|,Bessel 函数 |I(int, real)|,
|K(int, real)|, |j(int, real)|, |y(int, real)|, |i_scaled(int, real)|,
|k_scaled(int, real)|, |J(real, real)|, |Y(real, real)|, |I(real, real)|,
|K(real, real)| , |zero_J(real, int)|,椭圆函数 |F(real, real)|,
|E(real, real)| 和 |P(real, real)|,指数/三角积分 |Ei|, |Si| 和 |Ci|,
Legendre 多项式 |Pl(int, real)|,以及 Riemann $\zeta$ 函数 |zeta(real)|。
例如,要计算 $1.0$ 的正弦积分 |Si|:
\begin{lstlisting}
import gsl;
write(Si(1.0));
\end{lstlisting}
\egroup
\prgname{Asymptote} 同样提供了一些一般目的的数值过程:
\begin{funclist}
\item
\begin{lstlisting}[style=funclist]
real newton(int iterations=100, real f(real), real fprime(real), real x,
bool verbose=false);
\end{lstlisting}
\go 给定导数 |fprime| 和初值 |x|,使用 Newton-Raphson 迭代求解实值可微函数
|f| 的根。如果 |verbose=true|,每次迭代的诊断值将打印出来。如果在最大允许循环
次数(|iterations|)之后迭代失败,将返回 |realMax|。
\item
\begin{lstlisting}[style=funclist]
real newton(int iterations=100, real f(real), real fprime(real), real x1,
real x2, bool verbose=false);
\end{lstlisting}
\go 给定导数 |fprime|,使用包围的 Newton-Raphson 二分法(bracketed
Newton-Raphson bisection)在区间 $[$|x1|, |x2|$]$(在上面 |f| 的端点值有相反
的符号)求解实值可微函数 |f| 的根。如果 |verbose=true|,每次迭代的诊断值将
打印出来。如果在最大允许循环次数(|iterations|)之后迭代失败,将返回
|realMax|。
\item
|real simpson(real f(real), real a, real b, real acc=realEpsilon, real dxmax=b-a)|
\go 返回使用 Simpson 自适应法计算的 |f| 从 |a| 到 |b| 的积分。
\end{funclist}
\section{数组}
% 5.12 Arrays
\label{sec:arrays}
\index{数组}
\index{[]@\inlinecode=[]=}
在内部或用户自定义的类型后加上 |[]| 就得到数组。数组 |A| 的元素 |i| 可用
|A[i]| 访问。默认情况下,试图使用负指标对一个数组元素进行访问或赋值将产生
错误。以超出数组长度的指标读数组元素同样会产生错误;然而,对超出数组长度处的
元素赋值会导致数组变长以容纳新元素。可以用一个整数数组 |B| 来作为数组 |A| 的
指标:数组 |A[B]| 由数组 |A| 以 |B| 中各元素为指标构成。对整个数组元素迭代,
有一种方便的 Java 风格的简化形式;见第 \pageref{chap:programming} 页数组迭代
部分\transnote{指 \inlinecode|for (elem : array) \{ statements \}| 的格式。在
本章开头。}。
声明
\begin{lstlisting}
real[] A;
\end{lstlisting}
\index{数组!空白数组(empty array)}
\index{数组!空数组(null array)}
\index{空白数组(empty array)}
\index{空数组(null array)}
\index{null@\inlinecode=null=}
初始化 |A| 为一个空白(长度为零的)数组(empty array)。空白数组应与空数组
(null array,或译“无效数组”更直白)区别。如果我们说
\begin{lstlisting}
real[] A=null;
\end{lstlisting}
则 |A| 完全不能被解引用(空数组根本没有长度并且不能从中读取或是向其元素
赋值)。
数组可以像这样明确地初始化:
\begin{lstlisting}
real[] A={0,1,2};
\end{lstlisting}
\index{浅复制}
\index{深复制}
在 \prgname{Asymptote} 中数组的赋值进行的是浅复制:只有指针被复制过来(如果
一个复本被修改,则另一个也同样会被修改)。后面列出的 |copy| 函数提供了数组的
深复制。
每个类型为 |T[]| 的数组 |A| 都有虚成员
\begin{itemize}
\index{length@\inlinecode=length=}
\item |int length|,
\index{cyclic@\inlinecode=cyclic=}
\item |void cyclic(bool b)|,
\index{cyclicflag@\inlinecode=cyclicflag=}
\item |bool cyclicflag|,
\index{keys@\inlinecode=keys=}
\item |int[] keys|,
\index{push@\inlinecode=push=}
\item |T push(T x)|,
\index{append@\inlinecode=append=}
\item |void append(T[] a)|,
\index{pop@\inlinecode=pop=}
\item |T pop()|,
\index{insert@\inlinecode=insert=}
\item |void insert(int i ... T[] x)|,
\index{delete@\inlinecode=delete=}
\item |void delete(int i, int j=i)|,
\item |void delete()|,以及
\index{initialized@\inlinecode=initialized=}
\item |bool initialized(int n)|。
\end{itemize}
成员 |A.length| 计算数组的长度。设置 |A.cyclic(true)| 表示指标应该约化为对
当前数组长度取模的结果。从一个非空的循环(cyclic)数组中读写不会造成指标越界
错误或是数组长度变化。成员 |A.cyclicflag| 返回对于 |cyclic| 标记的当前设置。
成员 |A.keys| 求出一个整数数组,它包含原数组中已初始化的项的指标,按升序
排列。所以,对一个长度为 |n| 并且所有项均初始化的数组,|A.keys| 将求得从 |0|
到 |n-1| 的闭区间整数数组。每当 |A.keys| 求值时,都会求出一个新的键值(key)
数组。
函数 |A.push| 和 |A.append| 把参数添加到数组的末尾,而
|A.insert(int i ... T[] x)| 把 |x| 插入数组指标 |i| 的位置。为方便 |A.push|
返回压入的项目。函数 |A.pop()| 弹出反返回最后一个元素,而
|A.delete(int i, int j=i)| 删除指标在 $[$|i|,~|j|$]$ 范围内的元素,并把指标
较大的元素都移回前面。如果不给参数,则 |A.delete()| 提供了一种方便的方式删去
|A| 的所有元素。过程 |A.initialized(int n)| 可用于检查指标 |n| 处元素是否已
初始化。如同所有 \prgname{Asymptote} 函数一样,|cyclic|, |push|, |append|,
|pop|, |insert|, |delete| 和 |initialized| 可以从数组“脱下”并作用于它们
自身。例如,
\begin{lstlisting}
int[] A={1};
A.push(2); // A 现在包含 {1,2}
A.append(A); // A 现在包含 {1,2,1,2}
int f(int)=A.push;
f(3); // A 现在包含 {1,2,1,2,3}
int g()=A.pop;
write(g()); // 输出 3
A.delete(0); // A 现在包含 {2,1,2}
A.delete(0,1); // A 现在包含 {2}
A.insert(1,3); // A 现在包含 {2,3}
A.insert(1 ... A); // A 现在包含 {2,2,3,3}
A.insert(2,4,5); // A 现在包含 {2,2,4,5,3,3}
\end{lstlisting}
\index{[]@\inlinecode=[]=}
|[]| 后缀同样可以出现在变量名的后面;有时这对声明同一类型的一列变量和数组比较
方便:
\begin{lstlisting}
real a,A[];
\end{lstlisting}
这声明了 |a| 为 |real| 并隐式地声明 |A| 为类型 |real[]|。
在下列的内部数组函数中,|T| 表示一个一般的类型。注意内部函数 |alias|,
|array|, |copy|, |concat|, |sequence|, |map| 和 |transpose|,依赖于函数 |T|,
仅在初次声明一个类型为 |T[]| 的变量后才有定义。
\begin{funclist}
\index{new@\inlinecode=new=}
\item |new T[]| \go
返回类型为 |T[]| 的新的空白数组。
\item |new T[] {list}| \go
返回一个类型为 |T[]| 的新数组,由 |list|(一个逗号分隔的元素列表)初始化。
\item |new T[n]| \go
返回一个新的 |n| 元数组。这 |n| 个数组元素不会被初始化,除非它们本身就是
数组(在这种情况它们每个都被初始化为空白数组)。
\index{array@\inlinecode=array=}
\item |T[] array(int n, T value, int depth=intMax)| \go
返回一个由 |value| 的 |n| 份复本组成的数组。默认情况下,如果 |value| 本身是
一个数组,将对新数组的每个项做深复制。如果指明了 |depth|,这个深复制仅会
递归指定的层数。
\index{sequence@\inlinecode=sequence=}
\item |int[] sequence(int n)| \go
如果 |n >= 1| 则返回数组 |{0,1,...,n-1}|(否则返回一个空数组)。
\item |int[] sequence(int n, int m)| \go
如果 |m >= n| 则返回数组 |{n,n+1,...,m}|(否则返回一个空数组)。
\item |T[] sequence(T f(int), int n)| \go
给定函数 |T f(int)| 和整数 |int n|,如果 |n >= 1| 则返回序列
|{f(i) : i=0,1,...,n-1}|(否则返回一个空数组)。
\index{map@\inlinecode=map=}
\item |T[] map(T f(T), T[] a)| \go
返回数组,它包含把函数 |f| 应用于数组 |a| 的每个元素的结果。这等价于\\
|sequence(new T(int i) {return f(a[i]);}, a.length)|。
\index{reverse@\inlinecode=reverse=}
\item |int[] reverse(int n)| \go
如果 |n >= 1| 则返回数组 |{n-1,n-2,...,0}|(否则返回一个空数组)。
\index{complement@\inlinecode=complement=}
\item |int[] complement(int[] a, int n)| \go
返回整数数组 |a| 对 |{0,1,2,...,n-1}| 的补集,于是
|b[complement(a,b.length)]| 就得到补集 |b[a]|。
\index{uniform@\inlinecode=uniform=}
\item |real[] uniform(real a, real b, int n)| \go
如果 |n >= 1| 则返回 $[$|a|,~|b|$]$ 的一个均匀划分,分为 |n| 段(否则返回
一个空数组)\transnote{这个函数返回的是一个长为 \inlinecode|n+1| 的分点
数组。}。
\index{find@\inlinecode=find=}
\item |int find(bool[], int n=1)| \go
返回第 |n| 个 |true| 值的指标,若找不到则返回 |-1|。如果 |n| 是负数,则从
数组末尾开始倒着找第 |-n| 个值。
\index{search@\inlinecode=search=}
\item |int search(T[] a, T key)| \go
对于内部有序类型 |T|,在 |n| 元已排序的数组 |a| 中搜索一个区间包含 |key|,
如果 |key| 比第一个元素还小则返回 |-1|,如果大于等于最后一个元素则返回
|n-1|,否则返回的指标对应区间的左(较小)端点。
\index{copy@\inlinecode=copy=}
\item |T[] copy(T[] a)| \go
返回数组 |a| 的一个深复制。
\index{copy@\inlinecode=copy=}
\item |T[][] copy(T[][] a)| \go
返回数组 |a| 的一个深复制。
\index{copy@\inlinecode=copy=}
\item |T[][][] copy(T[][][] a)| \go
返回数组 |a| 的一个深复制。
\index{concat@\inlinecode=concat=}
\item |T[] concat(... T[][] a)| \go
返回一个由给定参数拼接构成的新数组。
\index{alias@\inlinecode=alias=}
\item |bool alias(T[] a, T[] b)| \go
如果数组 |a| 和 |b| 是相同的\transnote{这里两个数组相同指它们的指针相同。}%
则返回 |true|。
\index{sort@\inlinecode=sort=}
\item |T[] sort(T[] a)| \go
对内部有序类型 |T|,返回 |a| 的增序排列的复本\transnote{也就是说,
\inlinecode{a} 本身并不被修改。}。
\index{sort@\inlinecode=sort=}
\item |T[][] sort(T[][] a)| \go
对内部有序类型 |T|,返回 |a| 的复本,其各行按第一列排序,若相等则按下一列
排序。例如:
\begin{lstlisting}
string[][] a={{"bob","9"},{"alice","5"},{"pete","7"},{"alice","4"}};
// 行排序(按第 0 列,使用第 1 列打破平衡):
write(sort(a));
\end{lstlisting}
产生
\begin{lstlisting}
alice 4
alice 5
bob 9
pete 7
\end{lstlisting}
\index{sort@\inlinecode=sort=}
\item |T[] sort(T[] a, bool compare(T i, T j))| \go
返回 |a| 的稳定递增排序复本,次序按如果 |compare(i,j)| 为真则元素 |i| 先于
元素 |j| 定义。
\index{transpose@\inlinecode=transpose=}
\item |T[][] transpose(T[][] a)| \go
返回 |a| 的转置。
\index{transpose@\inlinecode=transpose=}
\item |T[][][] transpose(T[][][] a, int[] perm)| \go
返回 |a| 的 3 维转置,它由 |new int[]{0,1,2}| 的排列 |perm| 作用于每项的
指标决定\transnote{在数学上,这就是说把对称群 $S_3$ 的一个元素(置换)
$\sigma$ 作用于 $\Set{a_{i_1,i_2,i_3}}{0\leq i_1, i_2, i_3 < n}$
每个元素下标的下标得到的结果
$\Set{a_{i_{\sigma(1)}, i_{\sigma(2)}, i_{\sigma(3)}}}
{0\leq i_1, i_2, i_3 < n, \sigma \in S_3}$。}。
\index{sum@\inlinecode=sum=}
\item |T sum(T[] a)| \go
对算术类型 |T|,返回 |a| 的和。在 |T| 是 |bool| 时,将返回 |a| 中为真的元素
个数。
\item
\index{min@\inlinecode=min=}
\begin{lstlisting}[style=funclist]
T min(T[] a)
T min(T[][] a)
T min(T[][][] a)
\end{lstlisting} \go
对内部有序类型 |T|,返回 |a| 中最小元素。
\item
\index{max@\inlinecode=max=}
\begin{lstlisting}[style=funclist]
T max(T[] a)
T max(T[][] a)
T max(T[][][] a)
\end{lstlisting} \go
对内部有序类型 |T|,返回 |a| 中最大元素。
\index{min@\inlinecode=min=}
\item |T[] min(T[] a, T[] b)| \go
对内部有序类型 |T|,并且 |a| 和 |b| 长度相同,返回一个由 |a| 与 |b| 中对应
元素较小值构成的数组。
\index{max@\inlinecode=max=}
\item |T[] max(T[] a, T[] b)| \go
对内部有序类型 |T|,并且 |a| 和 |b| 长度相同,返回一个由 |a| 与 |b| 中对应
元素较大值构成的数组。
\index{pairs@\inlinecode=pairs=}
\item |pair[] pairs(real[] x, real[] y)| \go
对长度相同的数组 |x| 和 |y|,返回复数(序对 |pair|)数组\\
|sequence(new pair(int i) {return (x[i],y[i]);}, x.length)|。
\index{fft@\inlinecode=fft=}
\item |pair[] fft(pair[] a, int sign=1)| \go
返回 |a| 的快速 Fourier 变换(如果可选的 \prgname{FFTW} 包安装了的话),
使用给定的符号 |sign|\transnote{当 \inlinecode{sign} 为 $-1$ 时给出离散
Fourier 变换,当 \inlinecode{sign} 为 $+1$ 时给出离散 Fourier 逆变换。}。
这是一个简单的例子:
\begin{lstlisting}
int n=4;
pair[] f=sequence(n);
write(f);
pair[] g=fft(f,-1);
write();
write(g);
f=fft(g,1);
write();
write(f/n);
\end{lstlisting}
\index{dot@\inlinecode=dot=}
\item |real dot(real[] a, real[] b)| \go
返回向量 |a| 和 |b| 的点积。
\index{tridiagonal@\inlinecode=tridiagonal=}
\item |real[] tridiagonal(real[] a, real[] b, real[] c, real[] f)| \go
求解周期三对角问题 $L\bm x = \bm f$ 并返回解 $\bm x$,其中 $\bm f$ 为 $n$
维向量,而 $L$ 为 $n\times n$ 矩阵
\begin{equation*}
\begin{pmatrix}
b_0 & c_0 & & & a_0 \\
a_1 & b_1 & c_1 \\
& a_2 & b_2 & c_2 \\
& & \ddots & \ddots & \ddots \\
c_{n-1} & & & a_{n-1} & b_{n-1} \\
\end{pmatrix}
\end{equation*}
对 Dirichlet 边界条件(在此记为 $u_{-1}$ 和 $u_{n}$),用
$f_0 - a_0 u_{-1}$ 和 $f_{n-1} - c_{n-1} u_n$ 替换 $f_0$,然后设
$a_0 = c_{n-1} = 0$。\transnote{公式中的 $a$, $b$, $c$, $f$ 分别对应数组
\inlinecode{a}, \inlinecode{b}, \inlinecode{c}, \inlinecode{f}。这里为方便
公式书写改为数学记法。}
\index{solve@\inlinecode=solve=}
\item |real[] solve(real[][] a, real[] b, bool warn=true)| \go
利用 LU 分解求解线性方程组 |a|$\bm x = {}$|b|,并返回解 $x$,其中 |a| 是
一个 $n\times n$ 矩阵而 |b| 为长度为 $n$ 的数组。例如:
\begin{lstlisting}
import math;
real[][] a={{1,-2,3,0},{4,-5,6,2},{-7,-8,10,5},{1,50,1,-2}};
real[] b={7,19,33,3};
real[] x=solve(a,b);
write(a); write();
write(b); write();
write(x); write();
write(a*x);
\end{lstlisting}
如果 $a$ 是奇异矩阵而 |warn| 为 |false|,返回一个空白矩阵。如果矩阵 |a| 是
对角阵,过程 |tridiagonal| 提供了一种更为有效的算法(见前面 |tridiagonal|
的解释)。
\index{solve@\inlinecode=solve=}
\item |real[][] solve(real[][] a, real[][] b, bool warn=true)| \go
求解线性方程组 |a|$x = {}$|b| 并返回解 $x$,其中 |a| 是一个 $n\times n$
矩阵而 |b| 是一个 $n\times m$ 矩阵。如果 $a$ 是奇异矩阵而 |warn| 为
|false|,返回一个空白矩阵。
\index{identity@\inlinecode=identity=}
\item |real[][] identity(int n)| \go
返回 $n\times n$ 单位矩阵。
\index{diagonal@\inlinecode=diagonal=}
\item |real[][] diagonal(... real[] a)| \go
返回以 |a| 为对角线元素的对角矩阵。
\index{inverse@\inlinecode=inverse=}
\item |real[][] inverse(real[][] a)| \go
返回方阵 |a| 的逆。
\index{quadraticroots@\inlinecode=quadraticroots=}
\item |real[] quadraticroots(real a, real b, real c)| \go
这是一个数值鲁棒的求解器,返回二次方程 $ax^2+bx+c=0$ 的实根,以升序排列。
重根会分别列出。
\index{quadraticroots@\inlinecode=quadraticroots=}
\item |pair[] quadraticroots(explicit pair a, explicit pair b, explicit pair c)| \go
这是一个数值鲁棒的求解器,返回二次方程 $ax^2+bx+c=0$ 的两个复根。
\index{cubicroots@\inlinecode=cubicroots=}
\item |real[] cubicroots(real a, real b, real c, real d)| \go
这是一个数值鲁棒的求解器,返回三次方程 $ax^3+bx^2+cx+d=0$ 的实根。重根会
分别列出。
\end{funclist}
\prgname{Asymptote} 包含完整的一集向量化的数组算术(包括自赋值)命令与逻辑
操作。为了计算速度需要,这些逐元素的命令由 C++ 代码实现。给定
\begin{lstlisting}
real[] a={1,2};
real[] b={3,2};
\end{lstlisting}
则 |a == b| 与 |a >= 2| 都求值得到向量 |{false, true}|。要测试 |a| 和 |b| 的
所有部分是否全都一致,可使用布尔函数 |all(a == b)|。同样可以使用条件表达式如
|(a >= 2) ? a : b|,它返回数组 |{3,2}|,或写 |(a >= 2) ? a : null|,它返回
数组 |{2}|。
所有标志为 |real(real)| 的标准内部 \prgname{libm} 函数同样可以使用实数数组
作为参数,效果如果隐式地调用了 |map|。
与其他内部类型一样,基本数据类型的数组可由赋值读入。在这个例子中,代码
\begin{lstlisting}
file fin=input("test.txt");
real[] A=fin;
\end{lstlisting}
\index{line@\inlinecode=line=}
向 |A| 内读入实数值,直至达到文件末尾(或发生 I/O 错误)。如果用 |line(file)|
设置了行模式,则读入将改在达到行末时停止(行模式可以用 |line(file,false)|
清除):
\begin{lstlisting}
file fin=word(line(input("test.txt")));
real[] A=fin;
\end{lstlisting}
\index{csv@\inlinecode=csv=}
另一个有用的模式是逗号分隔值模式,由 |csv(file)| 设置而由 |csv(file,false)|
清除,它使得读入以逗号为界定符:
\begin{lstlisting}
file fin=csv(input("test.txt"));
real[] A=fin;
\end{lstlisting}
\index{dimension@\inlinecode=dimension=}
要限定读入值的个数,使用 |dimension(file,int)| 函数:
\begin{lstlisting}
file fin=input("test.txt");
real[] A=dimension(fin,10);
\end{lstlisting}
这会向 |A| 中读入 10 个值,除非先遇到文件末尾(或在行模式中遇到行末尾)。试图
超出文件末尾读入将产生一个运行时错误信息。指定值为 0 的整数界限等价于在前面的
例子中读入直至遇到文件末尾(或在行模式中遇到行末尾)。
基本数据类型的二维和三维的数组可以像这样读入:
\begin{lstlisting}
file fin=input("test.txt");
real[][] A=dimension(fin,2,3);
real[][][] B=dimension(fin,2,3,4);
\end{lstlisting}
同样地,一个零值的整数界限意味着没有限制。
\index{read1@\inlinecode=read1=}
\index{read2@\inlinecode=read2=}
\index{read3@\inlinecode=read3=}
有时数组维数与数据一起作为整数域保存在数组开头。这种数组可以用函数 |read1|,
|read2| 和 |read3| 分别读入:
\begin{lstlisting}
file fin=input("test.txt");
real[] A=read1(fin);
real[][] B=read2(fin);
real[][][] C=read3(fin);
\end{lstlisting}
\index{write@\inlinecode=write=}
基本数据类型的一、二、三维数组可以用函数 |write(file,T[])|,
|write(file,T[][])|, |write(file,T[][][])| 分别输出。
\subsection{切割}
% 5.12.1 Slices
\index{切割(slice)}
\index{数组!切割(slice)}
\prgname{Asymptote} 允许使用一种类似 Python 的语法以切割(slice)来表示数组的
一个片断。如果 |A| 是一个数组,表达式 |A[m:n]| 返回一个新数组,它由 |A| 中
指标从 |m| 开始直到但不包括 |n| 的元素构成。例如,
\begin{lstlisting}
int[] x={0,1,2,3,4,5,6,7,8,9};
int[] y=x[2:6]; // y={2,3,4,5};
int[] z=x[5:10]; // z={5,6,7,8,9};
\end{lstlisting}
如果省略左边的指标,则取为 |0|。如果省略右边的指标则取为数组长度。如果二者都
省略了,则切割从数组开头直到末尾,产生原数组的一个非循环的深复本。例如:
\begin{lstlisting}
int[] x={0,1,2,3,4,5,6,7,8,9};
int[] y=x[:4]; // y={0,1,2,3}
int[] z=x[5:]; // z={5,6,7,8,9}
int[] w=x[:]; // w={0,1,2,3,4,5,6,7,8,9}, 与数组 x 相区别
\end{lstlisting}
如果 |A| 是一个非循环数组,则对任一指标使用负数值都是非法的。然而,如果指标
超过数组长度,它们会被斯文地截短到此长度。
对于循环数组,切割 |A[m:n]| 仍然由集合 $[$|m|,~|n|$)$ 中的指标的单元构成,但
现在允许负数值和超过数组长度的值。指标只不过是绕回。例如:
\begin{lstlisting}
int[] x={0,1,2,3,4,5,6,7,8,9};
x.cyclic(true);
int[] y=x[8:15]; // y={8,9,0,1,2,3,4}.
int[] z=x[-5:5]; // z={5,6,7,8,9,0,1,2,3,4}
int[] w=x[-3:17]; // w={7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6}
\end{lstlisting}
注意对循环数组,有可能在一个切割中多次包含原数组中相同元素。不论原数组如何,
由切割产生的数组总是非循环的。
如果一个切割的左指标和右指标是相同的,则结果是一个空白数组。如果被切割的数组
是空白的,则结果是空白数组。任何左指标比右指标大的切割都会导致一个错误。
切割也可以被赋值,改变原数组的值。如果用来给切割赋值的数组长度与切割本身的
长度不同,元素会被插入或从(原)数组中删除以适应(用来赋值的)数组。举例
来说:
\begin{lstlisting}
string[] toppings={"mayo", "salt", "ham", "lettuce"};
toppings[0:2]=new string[] {"mustard", "pepper"};
// 现在 toppings={"mustard", "pepper", "ham", "lettuce"}
toppings[2:3]=new string[] {"turkey", "bacon" };
// 现在 toppings={"mustard", "pepper", "turkey", "bacon", "lettuce"}
toppings[0:3]=new string[] {"tomato"};
// 现在 toppings={"tomato", "bacon", "lettuce"}
\end{lstlisting}
如果将一个数组赋给它自身的切割,则原数组的复本会被赋给此切割。亦即,像
|x[m:n]=x| 的代码等价于 |x[m:n]=copy(x)|。可以使用简化形式 |x[m:m]=y| 用来在
数组 |x| 中恰在 |x[m]| 之前的位置处插入数组 |y| 的内容。
对循环数组,如果一个切割指向的单元到达数组的末尾然后又继续指向数组开头的
单元,则此切割是桥接的。例如,如果 |A| 是一个长度为 10 的循环数组,|A[8:12]|,
|A[-3:1]| 和 |A[5:25]| 是桥接切割,而 |A[3:7]|, |A[7:10]|, |A[-3:0]| 与
|A[103:107]| 就不是。桥接切割只能在切割元素个数与待赋值元素个数严格相等时才能
被赋值。否则的话,就没有一种明确的方法决定哪个新条目应该是 |A[0]| 从而报告
错误。非桥接的切割可以用任意长度的数组赋值。
对循环数组 |A|,形如 |A[A.length:A.length]| 的表达式等价于表达式 |A[0:0]|,
于是向此切割赋值将会在数组的开头将值插入。|A.append()| 可用于向数组的末尾插入
值。
向有任何重复单元的循环数组的切割赋值都是非法的。
\section{类型转换}
% 5.13 Casts
\label{sec:casts}
\index{类型转换}
\index{explicit@\inlinecode=explicit=}
\prgname{Asymptote} 隐式地转换 |int| 到 |real|,|int| 到 |pair|,|real| 到
|pair|,|pair| 到 |path|,|pair| 到 |guide|,|path| 到 |guide|,|guide| 到
|path|,|real| 到 |pen|,|pair[]| 到 |guide[]|,|pair[]| 到 |path[]|,|path|
到 |path[]| 以及 |guide| 到 |path[]|,加上在 \prgname{three.asy} 中定义的多种
三维类型转换。在赋值时以及在由可行的函数标识尝试匹配函数调用时,隐式类型转换
会自动尝试。在函数标识中,通过单独声明参数 |explicit| 可以阻止隐式类型转换,
比如说在下例中防止一个歧义的函数调用,函数将输出 0:
\begin{lstlisting}
int f(pair a) {return 0;}
int f(explicit real x) {return 1;}
write(f(0));
\end{lstlisting}
其他转换,比如说 |real| 到 |int| 或者 |real| 到 |string|,需要显式的类型
转换:
\begin{lstlisting}
int i=(int) 2.5;
string s=(string) 2.5;
real[] a={2.5,-3.5};
int[] b=(int []) a;
write(stdout,b); // 输出 2,-3
\end{lstlisting}
\index{cast@\inlinecode=cast=}
\index{operator cast@\inlinecode=operator cast=}
使用 |operator cast| 同样可以向用户自定义类型转换:
\begin{lstlisting}
struct rpair {
real radius;
real angle;
}
pair operator cast(rpair x) {
return (x.radius*cos(x.angle),x.radius*sin(x.angle));
}
rpair x;
x.radius=1;
x.angle=pi/6;
write(x); // 输出 (0.866025403784439,0.5)
\end{lstlisting}
在定义新的类型转换操作时必须要小心。假设在一些代码中需要把所有的整数表示为
100 的倍数。要把它们转换为实数,应该先要把它们乘以 100。然而,直接的实现
\begin{lstlisting}
real operator cast(int x) {return x*100;}
\end{lstlisting}
等价于一个无穷递归,因为结果 |x*100| 本身需要从整数转换为实数。作为替代,我们
要用从 |int| 到 |real| 的标准转换:
\begin{lstlisting}
real convert(int x) {return x*100;}
real operator cast(int x)=convert;
\end{lstlisting}
\index{ecast@\inlinecode=ecast=}
\index{operator ecast@\inlinecode=operator ecast=}
显式类型转换用 |operator ecast| 类似地实现。
\section{导入}
% 5.14 Import
\index{导入}
尽管 \prgname{Asymptote} 默认提供了许多特征,但一些应用仍需要外部
\prgname{Asymptote} 模块包含的特殊特征。例如,这几行
\index{access@\inlinecode=access=}
\begin{lstlisting}
access graph;
graph.axes();
\end{lstlisting}
在二维图表中绘制 $x$ 与 $y$ 坐标轴。这里,指令在诸模块的一个全局字典中查找
名为 |graph| 的模块,并将其置入一个名为 |graph| 的新变量。模块就是一个
结构体,我们可以像通常在一个结构体中一样引用它的域。
经常,我们想要不必指定模块名就使用模块的函数。代码
\index{from@\inlinecode=from=}
\begin{lstlisting}
from graph access axes;
\end{lstlisting}
把 |graph| 的 |axes| 域加进本地名字空间,从而,就可以写 |axes()|。如果给定的
名字是重载的,所有这个名字的类型和变量都会加入。要加入多于一个名字,只要使用
逗号分隔的列表:
\begin{lstlisting}
from graph access axes, xaxis, yaxis;
\end{lstlisting}
通配符可以用来将模块中所有非私有(non-private)的域和类型加入本地名字空间:
\begin{lstlisting}
from graph access *;
\end{lstlisting}
\index{unravel@\inlinecode=unravel=}
类似地,可以使用关键字 |unravel| 将一个结构体的非私有的域和类型加入本地名字
空间:
\begin{lstlisting}
struct matrix {
real a,b,c,d;
}
real det(matrix m) {
unravel m;
return a*d-b*c;
}
\end{lstlisting}
命令
\index{import@\inlinecode=import=}
\begin{lstlisting}
import graph;
\end{lstlisting}
是下面命令的一个方便的缩写形式:
\begin{lstlisting}
access graph;
unravel graph;
\end{lstlisting}
即,|import graph| 首先将一个模块载入一个叫做 |graph| 的结构体,然后将其
非私有的域和类型加入本地名字空间。这样的话,如果一个成员变量(或函数)被一个
本地变量(或相同标识的函数)覆盖,原有的仍然可以通过用模块名限定来访问。
通配符导入在大多数情况下都工作良好,但通常并不知道一个模块的所有内部类型和
变量,它们也可能由于模块作者增加或改变了模块的特征而改变。照此说来,在
\prgname{Asymptote} 文件开头加上 |import| 命令应审慎,以便导入的名字不会遮蔽
其他导入的名字,这取决于它们引入的次序,并且如果引入的函数与后来定义的本地
函数同名,还可能造成重载解析的问题。
\index{as@\inlinecode=as=}
要在将模块或域加入本地环境时对其重命名,使用 |as|:
\begin{lstlisting}
access graph as graph2d;
from graph access xaxis as xline, yaxis as yline;
\end{lstlisting}
命令
\begin{lstlisting}
import graph as graph2d;
\end{lstlisting}
是下面命令的一种方便的缩写形式:
\begin{lstlisting}
access graph as graph2d;
unravel graph2d;
\end{lstlisting}
除了几个内部模块,如 \prgname{settings},所有的模块都实现为
\prgname{Asymptote} 文件。当查找一个尚未加载的模块时,\prgname{Asymptote}
搜索标准路径(见 2.5 节搜索路径\transnote{此节译本不包括此节。})以匹配文件。
对应此名字的文件被读入而其中的代码作为定义模块的结构体的体被解释。
如果文件名包含非字母数字的字符,用引号把它括起来:
\begin{lstlisting}
access "/usr/local/share/asymptote/graph.asy" as graph;
from "/usr/local/share/asymptote/graph.asy" access axes;
import "/usr/local/share/asymptote/graph.asy" as graph;
\end{lstlisting}
模块导入自身(或互相循环导入)是错误。导入的模块名必须在编译时已知。
然而,你可以这样在运行时导入一个由字符串 |s| 确定的 \prgname{Asymptote}
模块:
\index{eval@\inlinecode=eval=}
\begin{lstlisting}
eval("import "+s,true);
\end{lstlisting}
要有条件地运行一个 asy 文件数组,用
\index{asy@\inlinecode=asy=}
\begin{lstlisting}
void asy(string format, bool overwrite ... string[] s);
\end{lstlisting}
使用输出文件格式 |format|,只有在 |overwrite| 为 |true| 或输出文件不存在时,
文件才会被处理。
可以求值一个包含在字符串 |s| 中的 \prgname{Asymptote} 表达式(然而没有任何
返回值),使用:
\index{eval@\inlinecode=eval=}
\begin{lstlisting}
void eval(string s, bool embedded=false);
\end{lstlisting}
不需要在字符串末尾以分号结束。如果 |embedded| 为 |true|,字符串会在当前环境的
顶层求值。如果 |embedded| 为 |false|(默认值),字符串会在一个不确定的环境
求值,共享相同的 \prgname{settings} 模块(见第八章相关内容\transnote{此节译本
不包括此节。})。
可以使用下列命令求值任意 \prgname{Asymptote} 代码(可以包含非转义的引号):
\index{eval@\inlinecode=eval=}
\begin{lstlisting}
void eval(code s, bool embedded=false);
\end{lstlisting}
\index{code@\inlinecode=code=}
\index{quote@\inlinecode=quote=}
这里 |code| 是一种特殊类型,像这样以 |quote {}| 包装起 \prgname{Asymptote}
代码来使用:
\begin{lstlisting}
real a=1;
code s=quote {
write(a);
};
eval(s,true); // 输出 1
\end{lstlisting}
要逐字地包含一个文件 \prgname{graph} 的内容(就好像文件是内容被插入到那个点
中),使用下列形式中的一种:
\index{include@\inlinecode=include=}
\begin{lstlisting}
include graph;
include "/usr/local/share/asymptote/graph.asy";
\end{lstlisting}
要列出在一个以字符串 |s| 的内容为名字的模块中定义的全部全局函数和变量,用函数
\index{list@\inlinecode=list=}
\begin{lstlisting}
void list(string s, bool imports=false);
\end{lstlisting}
如果 |imports| 为 |true|,导入的全局函数和变量也会被列出。
\section{静态(static)}
% 5.15 Static
\label{sec:static}
\index{静态(static)}
\index{static@\inlinecode=static=}
静态限定符将变量的内存地址分配在一个较高的外围环境。
对于一个函数体,变量在函数定义的块中分配;于是在代码
\begin{lstlisting}
struct s {
int count() {
static int c=0;
++c;
return c;
}
}
\end{lstlisting}
中对每个 |s| 的对象都只有一个变量 |c| 的实例(而非每次调用 |count| 就有一个
实例)。
类似地,在
\begin{lstlisting}
int factorial(int n) {
int helper(int k) {
static int x=1;
x *= k;
return k == 1 ? x : helper(k-1);
}
return helper(n);
}
\end{lstlisting}
中对 |factorial| 的每次调用(而非对 |helper| 的每次调用)有一个 |x| 的实例,
因而这是一个正确,然而丑陋的,阶乘的实现。
类似地,一个在结构体中声明的静态变量会在结构体定义的块中分配。所以,
\begin{lstlisting}
struct A {
struct B {
static pair z;
}
}
\end{lstlisting}
对创建的每个类型 |A| 的对象创建一个对象 |z|。
在这个例子中,
\begin{lstlisting}
int pow(int n, int k) {
struct A {
static int x=1;
void helper() {
x *= n;
}
}
for(int i=0; i < k; ++i) {
A a;
a.helper();
}
return A.x;
}
\end{lstlisting}
对 |pow| 的每次调用有一个 |x| 的实例,因此这是指数函数的一个丑陋的实现。
循环结构在每次迭代时分配一个新的框架。这就是为什么高阶函数可以引用循环中特定
迭代的变量:
\begin{lstlisting}
void f();
for(int i=0; i < 10; ++i) {
int x=i;
if(x==5) {
f=new void () { write(x); }
}
}
f();
\end{lstlisting}
这里,循环的每次迭代都有自己的变量 |x|,所以 |f()| 将写入 |5|。如果循环中的
变量声明为静态的,它将在外围函数或结构体定义的地方分配(就好像它是在循环外面
声明为 |static|)。例如,在
\begin{lstlisting}
void f() {
static int x;
for(int i=0; i < 10; ++i) {
static int y;
}
}
\end{lstlisting}
中,|x| 与 |y| 二者将会在同一个地方分配,这也正是 |f| 分配的地方。
语句也可以声明为静态的,此时它们会在外围函数或结构体定义的地方执行。不在函数
或结构体定义内部包围的声明或语句已经在顶层了,因而静态限定符就失去意义了。这
种情况下会给出一个警告。
鉴于结构体可以有静态域,对一个限定的名字,这个限定符是一个变量还是一个类型就
并不总是清晰的了。例如,在
\begin{lstlisting}
struct A {
static int x;
}
pair A;
int y=A.x;
\end{lstlisting}
中,|A.x| 中的 |A| 是指结构体还是指复数(序对)变量呢?在 \prgname{Asymptote}
中的惯例是,如果同名的限定符中有一个非函数的变量,这个限定符指那个变量,而非
类型。这与那个变量事实上究竟有什么域无关。
\phantomsection
\addcontentsline{toc}{chapter}{\indexname}
\printindex
\end{document}
% vim:tw=77:
|