lufayang
2025-02-10 83fff50ad8ebbe352606ce5179d9b82978273dcd
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
结构体
 
1、概念:结构体是由一系列具有相同类型或不同类型的数据构成的数据集合,它允许将不同类型的数据组合在一起,形成一个新的、更复杂的数据类型。可以把结构体看作是一个 “数据容器”,用于存储多个相关的数据项,这些数据项被称为结构体的成员
2、字节对齐:
 
(1)、结构体各成员的起始位置相对于结构体变量的起始位置的偏移量应该是这种类型的倍数。
 
(2)、结构体变量占总字节数应该是结构体各成员最大类型所占字节数的倍数。
 
2、读入或输出数据时,不能整体的读入或者整体输出,只能逐个结构体成员读入或输出。
 
3、相同类型的结构体变量能够相互赋值。     
 
c++中类和结构体的区别
 
默认访问权限不同
 
1.类:默认私有
 
2.结构体:默认共有
 
### this指针
 
**this指针的本质是指针常量,this指针的指向是不可以修改的,但是指向的内容的值是可以修改的**
 
**实际是:Person * const this;**
 
类内的非静态成员函数只会诞生一份函数实例,也就是说多个同类型类对象共用一份代码,怎么区分哪些用户调用呢,就是用this指针
 
1.解决名称冲突
 
​    形参和成员变量同名时,用this区分
 
2.返回对象本身用*this
 
​    使用链式调用返回值必须是对象本身
 
​    cout就属于链式编程,返回的对象就是ostream,可以无限追加调用
 
​    返回对象类型必须是类的引用类型,不能返回类类型,因为使用函数返回类的话实际返回的是一个临时副本p',然后p'链式调用返回p''
===========================================
    1、    柔性数组:
    如:
    struct Test
    {
        int type;
        int len;
        char d[0];  // 柔性数组,长度为0的数组,只能位于结构体最后一个成员,所以每个结构体最多只能有一个;柔性数组能表示任意长度的内容,也可以叫做不定长数组,含有柔性数组的结构体,也可以叫做不定长结构体。
    };
    
    由于数组长度为0,所以不占结构体内存,其内存在结构体的末尾连续。
    
    因为柔性数组在结构体末尾连续,所以访问数组时,可以使用结构体对象直接访问,不需要做其他修正操作。
    
    内存模型如下:
    --------------
    |int|int| ...[d数组内容,不占结构体内存]
    --------------
    sizeof(Test) -> 8
    -------------------------------
    
    2、另一种表示结构体内容长度可变的操作:
        指针,成员变量为指针的方式
        
    如:
        struct TestNext
        {
            int type;
            int len;
            char *a;  
            char *b; // 若使用指针来指向不定长的内容,可以有多个指针,且位置可以在结构体任意位置;唯一的缺点是由于指向的是一块内存地址,在不同进程/系统里,内存地址会有变化,当传输给另一端之后,需要做地址重定向
        };
        
        -------------TestNext内存模型如下:32位系统下
        |int|int|char*|char*|...a内存块...|...b内存块...|
         4    4   4     4     后面跟着2个指针对应的内存块
        -----------------
        TestNext * test = (TestNext*)buffer;
        重定向修正:
            第一个char* 的指向:这个内存块的首地址+结构体大小,及a的地址为  a = test+sizeof(TestNext);
            
            第二个指向是: b = a + strlen(a)+1;
        
        
    -----------------------------------
        内容不定长结构体使用场景:
        
        每次要往结构体里面存放的内容长度是变化的,同时又不想浪费额外的内存空间时,就可使用;
        比如:一些查询结果的存储,会因为查询条件不同,而使得结果的内容长度不同,这个时候使用不定长结构体非常合适。
        
        若内容是整体变化的,那就可以视为一个整体,通过自定义结构体来把整体表示出来,使用结构体类型的柔性数组即可表示。
        
        若内容有多个变化,且不能视为一个整体,每一个变化都可以使用指针来表示,这样的话使用多个指针来表示不定长内容即可。
        
    --------------------------------
        代码验证:
        
        结构体声明:
        struct Test
        {
            int type;
            int len;
            char data[0];  // char 数组,也可以换成自定义类型的数组
        };
        
        使用时,想把这个字符串"mayibase is very good"放到结构体中
        
        char mayi[] = "mayibase is very good";
        int len = sizeof(Test)+sizeof(mayi);
        Test *t = (Test*)malloc(len);
        t->type = 100;
        t->len = len;
        strcpy(t->data,mayi);
        
        // 接着就可以把这个结构体发送给另一端了
        
        -------------------------------------
        
        另一种带多个指针的:
        
        struct TestNext
        {
            int type;
            int len;
            char *a;   // 这些指针,也可以缓存自定义类型的指针
            char *b; 
        };
        // 要求携带\0
        char first[] = "mayibase is very good";
        char second[] = "0701class is very good";
        int total_len = sizeof(TestNext)+strlen(first)+1+strlen(second)+1;
        TestNext *tn = (TestNext*)malloc(total_len); // 一开始开辟足够大小的内存
        tn->type = 100;
        tn->len = total_len;
        memcpy((char*)(tn)+sizeof(TestNext),first,sizeof(first));
        memcpy((char*)(tn)+sizeof(TestNext)+sizeof(first),second,sizeof(second));
        tn->a = first; // 发送端给指针意义不大,在另一端会失效
        tn->b = second;
        
2、## 引用
 
 
数组名与指针对应是好事吗?确实是一件好事。将数组地址作为参数可以节省复制整个数组所需的时间和内存。如果数组很大,则使用拷贝的系统开销将非常大;程序不仅需要更多的计算机内存,还需要花费时间来复制大块的数据。另一方面,使用原始数据增加了破坏数据的风险。
 
 
 
​    在C++中,如果将类的对象作为参数传递给函数时,通常会使用引用,这样可以避免对象的拷贝开销,特别是对于大型对象而言。当你将一个类对象通过引用传递给函数时,即使类的成员变量是私有的,函数仍然可以访问和操作这些私有变量。这是因为私有变量的访问控制只是在类外起作用,类内部的成员函数(包括成员函数作为参数的函数)可以自由访问类的私有成员
 
### 引用和指针和值变量的区别
 
1.必须在声明引用时将其初始化,不像指针那样先声明再赋值
 
2.引用初始化时右边不能是表达式,值变量可以
 
### 函数形参使用引用时创建临时变量的情况
 
1.老版本C++比如实参和形参引用的值类型不同时,会通过临时变量接收强制转化后的值进行交换
 
2.我遇到的VS中则是直接通过函数重载形参类型实现了结果
 
```cpp
void swap(int &a,int &b) {
    int tmp;
    tmp = a;
    a = b;
    b = tmp;
}
int main()
{
    long long a = 3;//形参int 实参long long,函数根据实参自动重载了
    long long b = 5;
    swap(a, b);
    cout << "a = " << a << endl;
    cout << "size of a = " << sizeof(a) << endl;
    return 0;
}
```
 
### 引用的实质
 
1. 所以,引用变量在功能上等于一个指针常量,即一旦指向某一个单元就不能在指向别处。
 
2. 在底层,引用变量由指针按照指针常量的方式实现。
 
3. 既然引用占内存,为什么我却无法获得引用的地址啊。
 
   1. 之所以不能获取引用的地址,是因为编译器进行了内部转换。
 
      ```c++
      int a = 99;
      int &r = a;
      r = 18;
      cout<<&r<<endl;
      ```
 
      编译时会被转换成如下的形式:
 
      ```cpp
      int a = 99;
      int *r = &a;
      *r = 18;
      cout<<r<<endl;
      ```
 
      使用&r取地址时,编译器会对代码进行隐式的转换,使得代码输出的是 r 的内容(a 的地址),而不是 r 的地址,这就是为什么获取不到引用变量的地址的原因。也就是说,不是变量 r 不占用内存,而是编译器不让获取它的地址。
 
int a = 10;
 
int &ref = a;
 
编译器看到引用,实际上会转换成 int* const ref = &a;
 
*ref = *a
 
加const修饰指针变成指针常量,既保证了别名不能再修改指向别处
 
 
 
**const S &a1 = a**  常量引用,等价于 const S* const a1 = a  const即修饰了指针使引用不能指别的,又修饰S使指向的值也不能修改
 
 
 
void func(int& ref){
 
​    ref = 1000//ref是引用,转化为*ref = 1000
 
}
 
**使用方式:**
 
int a = 10
 
int &b = a;    使用&符,变量类型得一致
 
**注意事项:**
 
1.引用必须初始化
 
int &b;错误    
 
2.引用在初始化后不可以更改
 
**引用做函数的返回值时:**
 
1.不要返回局部变量的引用(但是可以返回静态局部变量的引用)
 
2.函数的调用可以作为左值(且只有返回类型时引用类型的函数可以作为左值)
 
```cpp
int& test02(){
    static int a = 20;
    return a;
}
 
int main(){
    int &ref = test02();
    test02() = 1000;//test02()返回的就是a,ref就是a的别名
}
```
 
### 对常量使用引用
 
int &ref = 10;//这个不可以,引用本身需要合法的内存空间,而10在常量文本区,加入const修饰就可以了
 
const int &ref = 10;//这个可以,这行代码执行时被编译器修改为了int tmp = 10;const int &ref = tmp;虽然最后tmp变量名会消失,但是别名一直指在开辟的那个空间中。
 
ref = 20;//错误。因为加入const修饰之后,该指针变为只读,不可再修改了
优点:1.避免对象拷贝,提高性能 2。返回引用支持链式调用 3.代码简洁 4通过基类的引用调用派生类的虚函数,实现运行时的多态。
缺点:1.引用必须初始化 2.不支持空引用 3.调试难度大,难以直观看出关联对象 4.相对功能受限,引用不支持多级引用。
左值引用&和右值引用&&的区别:
1.左值(lvalue):左值是一个表示对象身份的表达式,它通常对应于内存中具有确定地址的对象。左值可以出现在赋值语句的左边或右边,例如变量名、数组元素、对象的成员等。
右值(rvalue):右值是一个不表示对象身份的表达式,它通常是临时对象、字面量或表达式的求值结果。右值只能出现在赋值语句的右边,不能出现在左边。
左值引用(&):左值引用是对左值的引用,它必须绑定到一个左值上。
右值引用(&&):右值引用是对右值的引用,它主要用于绑定临时对象,允许对临时对象进行移动操作,避免不必要的拷贝。
2.语法差异:int &&rref = 20;  // 右值引用,绑定到右值 20
           int &lref = num;  // 左值引用,绑定到左值 num
3.绑定规则差异:左值引用:通常只能绑定到左值,不能直接绑定到右值。但 const 左值引用是个例外,它可以绑定到右值。const int &clref = 20;右值引用:只能绑定到右值,不能绑定到左值。但可以通过 std::move 将左值转换为右值后再绑定;int &&rref3 = std::move(num);
4.使用场景差异:左值引用:函数参数传递:避免对象的拷贝,提高性能。同时可以在函数内部修改实参的值。函数返回值:返回对象的引用,允许在函数调用处直接对返回的对象进行修改。  右值引用:移动语义:通过右值引用实现移动构造函数和移动赋值运算符,避免对临时对象进行不必要的拷贝,提高性能。
 
------------------------------------------------------------------------------------------------
3、const
   作用: 保证数据的安全性和不可修改性,同时提高代码的可读性和可维护性      
   在不用位置的作用:
1.使用 const 修饰基本数据类型  ,将变量定义为常量
2.const 也可用于修饰字符串,使其成为只读字符串。
3.const int* ptr = &num1;//指针指向的内容不能改,常量指针
4. int* const ptr = &num;//指针常量,指针本身的值不变
5. const int* const ptr = &num;//指向常量的常量指针,都不变
6.使用 const 修饰引用可以创建常量引用,常量引用不能修改所引用对象的值,但可以引用常量或临时对象。
7.void printValue(const int& val)//函数内部不能修改参数的值
8.printString("World");//const 引用作为函数参数可以接收常量对象或临时对象
9. int getValue() const {},常成员函数,不能修改对象的非静态成员变量
10.const int getValue() const {}//使用 const 修饰函数返回值可以防止调用者修改返回的对象或引用。
 
   Const和define的区别:
1.语法和类型:
     const:const 是 C++ 中的一个关键字,用于声明常量。它具有明确的类型,编译器会对其进行类型检查。#define:#define 是 C 和 C++ 中的预处理指令,用于进行文本替换。它没有类型的概念,只是简单地将宏名替换为对应的文本。
2  作用域:
const:const 常量的作用域遵循正常的变量作用域规则。在不同的作用域中定义的 const 常量是相互独立的。#define:#define 宏的作用域从定义处开始,直到文件结束或者遇到 #undef 指令取消定义。宏没有局部作用域的概念,可能会导致命名冲突。
3.内存分配:const常量会分配,#define不会,只是进行宏替换。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
4.指针:
   智能指针(一种用于管理动态分配内存的工具,它可以自动处理内存的分配和释放,)
  std::unique_ptr 独占所有权,同一时间只有一个对象,不允许拷贝构造和拷贝赋值,通过move(转让所有权)
  std::shared_ptr 共享所有权,引用计数
  weak_ptr 配合shared_ptr使用 ,用于解决 std::shared_ptr 可能出现的循环引用问题。lock() 方法获取一个 std::shared_ptr,从而访问对象。
引用的区别:1.类型&引用名=变量名;定义时必须初始化,之后不能引用其他的变量,指针是一个变量,储存变量的内存地址,定义是可以不初始化,不初始化,指针的值是不确定的值。2.内存占用(引用本身不占内存,但是指针需要) 3.引用不能为空4 引用在使用时不需要进行解引用操作。
优点:1.直接访问内存 2.实现数据结构 3.避免数据复制,提高性能 4.和硬件交互
缺点:1.内存管理复杂 2.容易出错(越界等)
安全性:1.空指针(是否有效) 2 悬空指针问题 3.指针越界
智能指针:避免内存泄漏和悬空指针
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 
5 数组
内存的连续性:占用内存中一段连续的地址
数组的遍历:1.下标访问 2.指针遍历 3.范围for循环
和指针的区别:   1.  数组是数据结构,指针是变量,保存的地址
2. 数组下标访问,指针用解引用
3. 动态数组的大小可以在运行时确定,一般在堆上分配
 
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 
 
6.链表
概念:线性数据结构,指针域和数据域组成,内存是是不连续的。
增加:创建新节点,建立连接
struct ListNode {
    int val;
    ListNode* next;
    ListNode(int x) : val(x), next(nullptr) {}
};
ListNode* new_node = new ListNode(val);
new_node->next = head;//头插
删除:删除的节点用临时变量保存,先断开连接,再删除
    if (!head) {
        return nullptr;
    }
    ListNode* temp = head;
    head = head->next;
    delete temp;
return head;
双向链表:前指针域、后指针域、以及数据域
 
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 
7.内存区域:
      1,栈区:存放函数的形参和局部变量。
    2,堆区:用malloc函数分配的空间,分配到堆区,程序员必须通过free函数来释放。
         否则内存泄漏。malloc的函数原型:
         void * malloc(int Size);
         作用:在堆上分配Size字节的内存空间。
         返回值:返回分配的内存空间的首地址。
   3,常量文本区:常量文本区的内容不能被改变,否则崩溃。
 4,全局静态区:函数外定义的变量,叫全局变量,分配到全局静态区。
            初始化的全局变量分配到data段,未初始化的全局变量分配到bss段。
5.程序代码区:保存程序每条指令的。
堆栈的区别:1.内存分配方式,栈内存系统自动释放和分配,堆手动分配和释放 2.栈的内存有限,在编译的时候确定,堆相对较大。3.数据共享性,栈定义的变量在当前的函数类使用,堆可以在不同的函数之间共享
 
Static的作用:1.修饰局部变量时,局部变量被初始化一次2.修饰全局变量和函数时,限制在当前文件的范围内使用 3.修饰类的成员变量,表示属于类,所有对象共享这个变量,在类外初始化 4.修饰类的成员函数时, 该函数属于类本身,不依赖于类的对象。静态成员函数只能访问静态成员变量和其他静态成员函数,不能访问非静态成员。
 
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 
8  malloc free new delete 的区别:
1.Malloc 、free通常在c语言使用,new delete 在c++使用
2.Malloc 只是简单分配一块内存,new和delete会调用析构和构造函数
3.类型安全性:malloc 返回 void* 类型的指针,需要进行显式的类型转换才能赋值给其他类型的指针,new 运算符会根据所分配对象的类型自动返回相应类型的指针,无需进行显式的类型转换。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 
9 类
默认构造: 默认无参和带默认形参值的构造函数。
           MyClass(int x, int y = 2, int z = 3)//形参具有默认值,需要在最右侧
 
拷贝构造函数:拷贝构造函数的主要作用是在创建对象时,将一个已有对象的数据成员的值复制到新对象中,从而实现对象的复制。ClassName(const ClassName& other) {
        // 拷贝成员变量
}
 调用场景:1. 用一个对象初始化另一个对象 2. 对象作为函数参数按值传递 3. 函数返回对象
  浅拷贝和深拷贝:默认的拷贝构造函数是浅拷贝,只会复制值,遇到指针时会出现悬空指针的问题。 自定义拷贝构造函数,分配新的内存,将原对象指针的内容拷贝到新的内存。需注意同时也要重载赋值运算函数
虚析构函数:虚析构函数是将析构函数声明为 virtual 的析构函数。当基类的析构函数被声明为虚析构函数时,通过基类指针删除派生类对象时,会先调用派生类的析构函数,再调用基类的析构函数。
实例化的三种方式:1.MyClass obj;//栈实例化 2.Circle* circlePtr = new Circle(2.0);//堆上实例化对象 3.Point p2(p1);//拷贝实例化
三大权限:public (类外可调用) protected(它介于 public 和 private 之间,主要用于在继承体系中让派生类能够访问基类的某些成员,同时又对外部代码隐藏这些成员。)       private(只能类使用,派生类无法使用)
隐式转换:1.当类具有一个单参数的构造函数(或者除了第一个参数外其余参数都有默认值)时,编译器可以使用这个构造函数将该参数类型的对象隐式转换为类的对象。
 
      2. 类型转换运算符引发的隐式转换可以通过定义类型转换运算符,使类的对象能够隐式转换为其他类型。
 
显示调用:使用 explicit 禁止隐式转换
静态属性:静态属性属于类本身,所有类的对象共享同一个静态属性。
静态属性一般在类的外部进行初始化,格式为 DataType ClassName::staticVariable = initialValue;。
整数类型或枚举类型的常量静态属性可以在类的内部进行初始化。
 
静态方法:不依赖对象,只能访问静态成员
 
This 指针 、final、delete:
This指针:在 C++ 中,this 指针是一个隐含于每一个非静态成员函数中的特殊指针。它指向调用该成员函数的对象,通过 this 指针可以访问调用对象的成员变量和成员函数,从而区分成员变量和局部变量。
Final: 一是用于修饰虚函数,表示该虚函数不能在派生类中被重写;二是用于修饰类,表示该类不能被继承。 virtual void func() final {
        std::cout << "Base::func()" << std::endl;
}//修饰虚函数
class Base final {...}//修饰类
Delete: 一是用于释放使用 new 运算符动态分配的内存,避免内存泄漏;二是用于禁用特定的函数,如默认构造函数、拷贝构造函数等。
      // 禁用默认构造函数
MyClass() = delete;
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 
10 继承
   继承方式可以是 public(公有继承)、private(私有继承)或 protected(保护继承),默认的继承方式是 private。
   公有继承(public):父类的 public 成员在子类中仍然是 public 的,父类的 protected 成员在子类中仍然是 protected 的,父类的 private 成员在子类中不可直接访问。
私有继承(private):父类的所有成员在子类中都变成 private 的,子类的子类无法再访问这些成员。
保护继承(protected):父类的 public 和 protected 成员在子类中都变成 protected 的,父类的 private 成员在子类中不可直接访问。
实例化过程:
构造函数:创建子类对象时,会先调用父类的构造函数,再调用子类的构造函数。
析构函数:销毁子类对象时,会先调用子类的析构函数,再调用父类的析构函数
多继承:先声明的先继承
虚函数:虚函数的主要作用是实现运行时多态性。用virtual修饰
虚函数表:每个有虚函数的类都一个虚函数表;在创建包含虚函数的类的对象时,对象的内存布局中会包含一个虚表指针,该指针会在对象构造时被初始化为指向该类的虚函数表。
静态多态:函数重载
定义:编译时多态是指在编译阶段就确定了要调用的函数或操作,主要通过函数重载和运算符重载来实现。
优点:编译时多态的执行效率高,因为函数调用在编译阶段就已经确定,没有额外的运行时开销。
缺点:缺乏灵活性,一旦编译完成,调用的函数就固定了。
动态多态:重写
定义:运行时多态是指在运行阶段才确定要调用的函数或操作,主要通过继承和虚函数(在 C++ 中)、抽象类和接口(在 Java 中)来实现。
优点:运行时多态提供了更高的灵活性,允许程序在运行时根据对象的实际类型来动态地选择要调用的函数。
缺点:由于需要在运行时进行函数调用的动态绑定,会带来一定的性能开销。
纯虚函数:virtual void pureVirtualFunction() = 0;
      基类不实现:纯虚函数在基类中只有声明,没有函数体,即基类不会为纯虚函数提供具体的代码实现。
      派生类实现:包含纯虚函数的类是抽象类,不能实例化对象。派生类必须实现基类中的所有纯虚函数,否则派生类也会成为抽象类,同样不能实例化。
抽象类(包括纯虚函数)的两种方式:
1.使用纯虚函数
 2.使用包含未实现接口的类,接口类中只包含纯虚函数,派生类需要实现这些接口。
 
虚继承:解决菱形继承带来的冗余和二义性问题
       class 派生类名 : virtual 继承方式 基类名 {
    // 派生类的成员
};
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 
11.模板
   11.1 函数模板
定义:
template <class 类型参数1,...,class 类型参数2>
类型 函数名(形参列表)
{
    函数体
}
class可以typename关键字代替,作用相同,无区别;
<>括号中的参数称为模板形参;
将函数模板中的模板形参实例化的参数称为模板实参 ;
用模板实参实例化的函数称为模板函数;
模板形参需要调用该模板函数时提供的模板实参来初始化模板形参,一旦编译器确定了实际的模板实参类型就称他实例化了函数模板的一个实例。
不能省略类型参数的场景:template <class T>  T add(T a,Tb)
1.从模板函数实参获取的信息有矛盾//add<double>(2.3,3)
2.需要获得特定类型的返回值,不管参数的类型// add<int>(1.3,7.8)
3.虚拟类型参数没有出现在模板函数的形参中//
    
  4.函数模板含有常规形参
 
 
11.2类模板
定义:
template <class 类型参数1,…,类型参数n>
class 类名
{
        类体
};
模板类
在定义了类模板后,可以根据需要生成相应的模板类。
利用模板类创建对象的格式如下:
Array<double>  array;
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
12、
## STL
 
### STL的诞生
 
- 长久以来,软件界一直希望建立一种可重复利用的东西
- C++的面向对象和泛型编程思想,目的就是复用性的提升。
- 大多情况下,数据结构和算法都未能有一套标准,导致被迫从事大量重复工作
- 为了建立数据结构和算法的一套标准,诞生了STL
 
### STL基本概念
 
- STL(Standard Template Library,**标准模板库**)
- STL从广义上分为:**容器(container) 算法(algorithm)迭代器(iterator)**
- **容器**和**算法**之间通过**迭代器**进行无缝连接。
- STL几乎所有的代码都采用了**模板类或者模板函数**
 
###  STL六大组件
 
STL大体分为六大组件,分别是:**容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器**
 
**容器:**各种数据结构,如vector、list、deque、set、map等,用来存放数据
 
**算法:**各种常用的算法,如sort、find、copy、for_each等
 
**迭代器:**扮演了容器与算法之间的胶合剂。
 
**仿函数:**行为类似函数,可作为算法的某种策略。
 
**适配器:**一种用来修饰容器或者仿的数或选代器接口的东西
 
**空间配置器:**负责空间的配置与管理
 
### STL中容器、算法、迭代器
 
**容器:**置物之所也
STL容器就是将运用最广泛的一些数据结构实现出来
 
常用的数据结构:数组,链表,树,栈,队列,集合, 映射表 等
 
这些容器分为**序列式容器和关联式容器**两种:
 
**序列式容器:**强调值的排序,序列式容器中的每个元素均有固定的位置
 
**关联式容器:**二叉树结构,各元素之间没有严格的物理上的顺序关系
 
**算法:**问题之解法也
有限的步骤,解决逻辑或数学上的问题,这一门学科我们叫做算法(Algorithms)
 
1、非可变序列算法:指不直接修改其所操作的容器的算法。
 
2、可变序列算法:指可以修改其所操作的容器的算法。
 
3、排序算法:包括对序列进行排序和合并的算法,搜索算法以及有序序列上的集合操作。
 
4、数值算法:对容器内容进行数值计算
 
 
 
**迭代器:**容器和算法之间粘合剂
提供一种方法,使之能够依序寻访某个容晶所含的各个元素,而又无需暴露该容器的内部表示方式
 
每个容器都有自己专属的选代器
 
迭代器使用非常**类似于指针**,初学阶段我们可以先理解选代器为指针
 
1、正向迭代器:容器类名::iterator 迭代器名;
 
2、常量正向迭代器:容器类名::const_iterator 迭代器名;
 
3、反向迭代器:容器类名::reverse_iterator 迭代器名;
 
4、常量反向迭代器:容器类名::const_reverse_iterator 迭代器名;12.  STL
12.1 string容器
   遍历的三种方式:for循环、范围遍历、迭代器遍历:for(string:: reverse_itreator........)
 
迭代器{正向{常量:cbegin(),cend()   非常量:begin()  end()
      反向{常量:crbegin(),crend()   非常量:rbegin()  rend()
    s1.erase(it,it+3);  删除三个元素
S1.erase(1,1)//从下标为1开始删,删除n个,默认删完
类型转换:
          Stoi(“18”)
          Stoi(“18.3”)
          To_string(2)
          To_string(23.3)
12.2 list
    使用erase时,注意迭代器失效
    For( auto it=l.begin();it!=l.end();)
{
If(*it==val){
It=l.erase(it);}
Else{
It++;}}
 
12.3 map容器
 
增加的方式:  map[100]=”xuac,c232”  //没有就会新建
              Map.insert(makpair(“xuac”,”cdah”);//存在会失败
 
 
12.4  vector、stack、queue、deque、set
 
12.5 算法:冒泡
     排序:快排、红黑树
     查找:二分查找
12.6 迭代器的种类
12.6.1输入迭代器
 输入迭代器是最基本的迭代器类型,它只能单方向向前移动,并且只能读取所指向的元素一次。也就是说,对同一个元素不能进行多次读取操作。
支持的操作包括:解引用(*)、前置和后置自增(++)、相等和不相等比较(==、!=)。
//std::find 函数就可以使用输入迭代器。
//auto it = std::find(vec.begin(), vec.end(), 3);  // vec.begin() 返回的是输入迭代器
12.6.2 输出迭代器
  出迭代器也是单方向向前移动,但它的主要作用是向所指向的位置写入元素,并且同样只能对每个位置进行一次写入操作。
支持的操作有:解引用赋值(*it = value)、前置和后置自增(++)。
常用于单遍写入算法,例如 std::copy 函数在将元素复制到目标容器时就可以使用输出迭代器。
   
 
   12.6.3 前向迭代器
    前向迭代器继承了输入迭代器和输出迭代器的功能,它可以单方向向前移动,并且可以多次访问同一个元素。
支持输入迭代器和输出迭代器的所有操作,同时允许对同一个元素进行多次读写。
  //适用于需要多次遍历容器中元素的算法,例如某些链表操作。
 
   12.6.4 双向迭代器
双向迭代器在向前迭代器的基础上增加了向后移动的能力,它可以在容器中双向移动。
除了支持前向迭代器的所有操作外,还支持前置和后置自减(--)操作。
//适合需要双向遍历容器的算法,例如 std::reverse 函数就需要双向迭代器。
 
  12.6.5 随机访问迭代器
     随机访问迭代器是功能最强大的迭代器类型,它继承了双向迭代器的所有功能,并且支持随机访问元素。
支持的操作包括:算术运算(+、-)、复合赋值(+=、-=)、下标访问([])以及比较大小(<、>、<=、>=)。
//适用于需要快速随机访问容器元素的算法,例如 std::sort 函数通常要求使用随机访问迭代器。
  
自带find函数的容器:
  1.std::set 和 std::multiset
特点:这两个容器基于红黑树实现,存储的元素会自动排序。std::set 中元素唯一,std::multiset 允许元素重复。它们的 find 函数可以在o(logn)  的时间复杂度内查找指定元素。
  2. std::map 和 std::multimap
特点:同样基于红黑树实现,存储键值对。std::map 中键是唯一的,std::multimap 允许键重复。它们的 find 函数根据键来查找元素,时间复杂度也是 o(logn)。
3. std::unordered_set 和 std::unordered_multiset
特点:这两个容器基于哈希表实现,不保证元素的顺序。std::unordered_set 中元素唯一,std::unordered_multiset 允许元素重复。它们的 find 函数平均时间复杂度为o(1) ,最坏情况下为 o(n)。
4. std::unordered_map 和 std::unordered_multimap
特点:基于哈希表存储键值对,不保证元素顺序。std::unordered_map 中键唯一,std::unordered_multimap 允许键重复。find 函数根据键查找元素,平均时间复杂度为 o(1),最坏为o(n) 。
 
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
13.智能指针
1.可以作为容器的元素,但是shared_ptr当容器的元素时,需要使用移动语义
2.设计一个智能指针
 
## 智能指针
 
```
智能指针是一个类,它封装了一个原始的C++指针,以管理所指对象的生命期。解决动态内存自动释放问题。
 
头文件:#include <memory>
```
 
![img](./Image-1730682583556-1.png)
 
### unique_ptr实现方式
 
```
#include <iostream>
#include <memory>
#include <string>
using namespace std;
class Test {
public:
       Test() {
              cout << "Test()" << endl;
       }
       ~Test() {
              cout << "~Test()" << endl;
       }
       void Output() const {
              cout << "Output()" << endl;
       }
};
template <class T>        //类模板
class AutoPtr         //智能指针类
{
public:
       AutoPtr(T* ptr) {            //有参构造
              this->ptr = ptr;
       }
       ~AutoPtr() {          //析构
              if (ptr != nullptr) delete ptr;
       }
       T& operator *() {            //重载指针运算符,使智能指针与普通指针作用一样
              return *ptr;
       }
       T* get() {                   //获取ptr指针,即原始指针
              return ptr;
       }
       T* operator->() {            //重载指向运算符,使智能指针可以访问被管对象的成员函数和成员变量
              return ptr;
       }
private:
       AutoPtr(const AutoPtr&) = delete;          //删除拷贝构造
       AutoPtr& operator=(const AutoPtr&) = delete;              //删除赋值运算符重载
private:
       T* ptr;       //智能指针对象,ptr为被管对象
};
int main()
{                                     //p1智能指针对象new一个Test类对象,Test类对象为被管对象
       AutoPtr<Test> p1(new Test);    //智能指针对象访问成员函数用 . ,被管对象访问成员函数用 ->
       p1.get()->Output();
       p1->Output();
       return 0;
}
```
 
### unique_ptr(独占,一个智能指针指向一个被管对象)
 
```
#include <iostream>
#include <memory>
#include <string>
using namespace std;
int main()
{
    unique_ptr<int> p1(new int);
    *p1 = 10;
    cout << *p1 << endl;
    unique_ptr<string> s1(new string);
    *s1 = "hello world!";
    cout << *s1 << endl;
    cout << s1->size() << endl;
    return 0;
}
```
 
### shared_ptr(共享,多个智能指针指向同一个被管对象)
 
```
shared_ptr允许多个shared_ptr对象指向同一个被管理对象, shared_ptr是通过引用计数来管理对象的,拷贝一个shared_ptr,计数器都会递增,离开作用域时,计数器会递减,如果计数器变为0,它就会自动释放自己所管理的对象。
 
#include <iostream>
#include <string>
#include <memory>
using namespace std;
int main()
{
shared_ptr<int> p1(new int);
shared_ptr<int> p2(p1);
*p1 = 10;
cout << *p1 << endl;
cout << *p2 << endl;
cout << p1.use_count() << endl;
return 0;
}
------------------------------------
class Test {
public:
       Test() {
              cout << "Test()" << endl;
       }
       ~Test() {
              cout << "~Test()" << endl;
       }
       void Output() const {
              cout << "Output()" << endl;
       }
};
int main() {
       shared_ptr<Test> p1(new Test);
       shared_ptr<Test> p2 = p1;
       shared_ptr<Test> p3;
       p3 = p1;
       cout << p1.use_count() << endl;
       return 0;
}
```
 
### weak_ptr
 
```
weak_ptr不能独立使用,必配合shared_ptr使用, weak_ptr是为了解决shared_ptr的引用成环问题。
weak_ptr只进行浅拷贝,引用计数不会发生改变。
 
#include <iostream>
#include <memory>
using namespace std;
class B;
class A
{
public:
A() { cout << "A::A() called" << endl; }
~A() { cout << "A::~A() called" << endl; }
shared_ptr<B> p;                            //解决引用成环问题,将shared_ptr换成weak_ptr
};
--------------------------------------
class B
{
public:
B() { cout << "B::B() called" << endl; }
~B() { cout << "B::~B() called" << endl; }
shared_ptr<A> p;                          //解决引用成环问题,将shared_ptr换成weak_ptr
};
--------------------------------------------
int main()
{
shared_ptr<A> pA(new A);
shared_ptr<B> pB(new B);
//下面两句导致了A与B的循环引用,结果就是A和B对象都不会析构
pA->p = pB;
pB->p = pA;
cout << "pA use_count: " << pA.use_count() << endl;
cout << "pB use_count: " << pB.use_count() << endl;
return 0;
}
```
 
```
解决方法:
#include <iostream>
#include <memory>
using namespace std;
class B;
class A
{
public:
    A() { cout << "A::A() called" << endl; }
    ~A() { cout << "A::~A() called" << endl; }
    weak_ptr<B> p;
};
------------------------------
class B
{
public:
    B() { cout << "B::B() called" << endl; }
    ~B() { cout << "B::~B() called" << endl; }
    weak_ptr<A> p; //!
};
------------------------------
int main()
{
    shared_ptr<A> pA(new A);
    shared_ptr<B> pB(new B);     pA->p = pB;
    pB->p = pA;
    cout << "pA use_count: " << pA.use_count() << endl;
    cout << "pB use_count: " << pB.use_count() << endl;
    return 0;
}
```
 
```
常规使用:
#include <iostream>
#include <memory>
using namespace std;
int main()
{
    shared_ptr<int> p1(new int);
    weak_ptr<int> w1(p1);
    *p1 = 10;
    shared_ptr<int> p2 = w1.lock();
    if (p2) {
        cout << *p2 << endl;
    }
    return 0;
}
 
```
 
### 实现一个智能指针
 
```
#include <iostream>
#include <string>
using namespace std;
class Test{
public:
Test() {
    cout << "Test()" << endl;
}
~Test() {
cout << "~Test()" << endl;
}
void Output() const {
cout << "Output()" << endl;
}
};
-----------------------------------
template <class T>
class AutoPtr
{
public:
AutoPtr(T* ptr) {
    this->ptr = ptr;
}
~AutoPtr() {
    if (ptr != nullptr) delete ptr;
}
T& operator *() {
    return *ptr;
}
T* get() {
    return ptr;
}
T* operator->() {
    return ptr;
}
------------------------------
private:
AutoPtr(const AutoPtr&) = delete;
AutoPtr& operator=(const AutoPtr&) = delete;
private:
T* ptr;
};
-------------------------------------------
int main()
{
AutoPtr<Test> p1(new Test);
p1->Output();
return 0;
}
```
 
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
14、
### 1. `try...catch`语句
`try...catch` 是C++中用于捕获和处理异常的基本机制。程序中可能抛出异常的代码块放在 `try` 块中,而用于处理这些异常的代码则放在对应的 `catch` 块中。
 
#### 语法结构
```cpp
try {
    // 可能抛出异常的代码
    // ...
    throw exception_object; // 抛出异常对象
}
catch (ExceptionType1 e1) {
    // 处理 ExceptionType1 类型的异常
}
catch (ExceptionType2 e2) {
    // 处理 ExceptionType2 类型的异常
}
// 可以有更多的 catch 块
catch (...) {
    // 处理其他未明确捕获的异常
}
```
 
#### 示例代码
```cpp
#include <iostream>
 
double divide(double a, double b) {
    if (b == 0) {
        throw std::runtime_error("除数不能为零");
    }
    return a / b;
}
 
int main() {
    double num1 = 10.0, num2 = 0.0;
    try {
        double result = divide(num1, num2);
        std::cout << "结果: " << result << std::endl;
    }
    catch (const std::runtime_error& e) {
        std::cerr << "异常: " << e.what() << std::endl;
    }
    return 0;
}
```
在上述代码中,`divide` 函数在除数为零时抛出 `std::runtime_error` 异常,`main` 函数中的 `try` 块调用 `divide` 函数,`catch` 块捕获并处理该异常。
 
### 2. 异常检测工具
#### Valgrind
- **功能**:Valgrind是一个强大的内存调试和性能分析工具套件,其中的 `Memcheck` 工具可以检测程序中的内存错误,包括内存泄漏、使用未初始化的内存、越界访问等。同时,它也能帮助发现一些可能导致异常的内存问题。
- **使用示例**:假设编译生成的可执行文件名为 `my_program`,在终端中运行以下命令:
```bash
valgrind --leak-check=full ./my_program
```
这会让 Valgrind 对 `my_program` 进行全面的内存检查,并输出详细的内存错误和泄漏信息。
 
#### AddressSanitizer(ASan)
- **功能**:AddressSanitizer 是一个快速的内存错误检测工具,集成在 GCC 和 Clang 编译器中。它可以检测堆、栈和全局内存的越界访问、使用已释放的内存等问题,能有效帮助发现可能引发异常的内存错误。
- **使用示例**:编译时添加相应的编译选项,以 GCC 为例:
```bash
g++ -fsanitize=address -g your_program.cpp -o your_program
```
然后运行生成的可执行文件,若存在内存错误,ASan 会输出详细的错误信息。
 
### 3. 内存泄漏检测工具
#### Valgrind(Memcheck)
- **原理**:Memcheck 会跟踪程序中所有的内存分配和释放操作,在程序结束时检查是否存在已分配但未释放的内存块。
- **示例输出分析**:当运行 `valgrind --leak-check=full ./my_program` 后,如果存在内存泄漏,Valgrind 会输出类似如下信息:
```plaintext
==1234== LEAK SUMMARY:
==1234==    definitely lost: 4 bytes in 1 blocks
==1234==    indirectly lost: 0 bytes in 0 blocks
==1234==      possibly lost: 0 bytes in 0 blocks
==1234==    still reachable: 0 bytes in 0 blocks
==1234==         suppressed: 0 bytes in 0 blocks
```
这表明程序中存在 4 字节的确定内存泄漏。
 
#### LeakSanitizer(LSan)
- **功能**:LeakSanitizer 是一个内存泄漏检测工具,同样集成在 GCC 和 Clang 编译器中。它能在程序结束时自动检测并报告内存泄漏情况。
- **使用示例**:编译时添加相应的编译选项,以 Clang 为例:
```bash
clang++ -fsanitize=leak -g your_program.cpp -o your_program
```
运行程序后,若存在内存泄漏,LSan 会输出详细的泄漏信息。
 
### 4. 安全函数
#### `strcpy_s` 和 `strncpy_s`
- **作用**:在C++中,传统的 `strcpy` 和 `strncpy` 函数存在缓冲区溢出的风险,而 `strcpy_s` 和 `strncpy_s` 是更安全的替代函数。`strcpy_s` 用于将一个字符串复制到另一个字符串缓冲区,同时会检查目标缓冲区的大小,避免溢出;`strncpy_s` 则在复制指定长度的字符时也会进行类似的检查。
- **示例代码**:
```cpp
#include <iostream>
#include <cstring>
 
int main() {
    char dest[10];
    const char* src = "Hello";
    errno_t result = strcpy_s(dest, sizeof(dest), src);
    if (result == 0) {
        std::cout << "复制成功: " << dest << std::endl;
    } else {
        std::cerr << "复制失败" << std::endl;
    }
    return 0;
}
```
 
#### `sprintf_s`
- **作用**:`sprintf` 函数在格式化字符串并输出到缓冲区时可能会导致缓冲区溢出,`sprintf_s` 则是其安全版本。它会检查目标缓冲区的大小,确保不会发生溢出。
- **示例代码**:
```cpp
#include <iostream>
#include <cstdio>
 
int main() {
    char buffer[20];
    int num = 123;
    errno_t result = sprintf_s(buffer, sizeof(buffer), "数字是: %d", num);
    if (result > 0) {
        std::cout << "格式化成功: " << buffer << std::endl;
    } else {
        std::cerr << "格式化失败" << std::endl;
    }
    return 0;
}
```
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
15、
### 单例模式
单例模式确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。
 
#### 饿汉式单例模式
- **特点**:在程序启动时就创建单例实例,线程安全,无需考虑多线程并发问题,但可能会导致资源浪费,因为无论是否使用该实例,它都会被创建。
- **示例代码**:
```cpp
#include <iostream>
 
class SingletonHungry {
private:
    // 构造函数私有化,防止外部创建对象
    SingletonHungry() {}
    // 静态成员变量,在类加载时就创建实例
    static SingletonHungry instance;
 
public:
    // 提供全局访问点
    static SingletonHungry& getInstance() {
        return instance;
    }
 
    void doSomething() {
        std::cout << "饿汉式单例模式执行操作" << std::endl;
    }
};
 
// 初始化静态成员变量
SingletonHungry SingletonHungry::instance;
 
int main() {
    SingletonHungry& singleton = SingletonHungry::getInstance();
    singleton.doSomething();
    return 0;
}
```
 
#### 懒汉式单例模式
- **特点**:在第一次使用时才创建单例实例,避免了资源浪费,但在多线程环境下需要考虑线程安全问题。
- **示例代码(非线程安全)**:
```cpp
#include <iostream>
 
class SingletonLazy {
private:
    // 构造函数私有化
    SingletonLazy() {}
    // 静态成员指针,初始化为 nullptr
    static SingletonLazy* instance;
 
public:
    // 提供全局访问点
    static SingletonLazy* getInstance() {
        if (instance == nullptr) {
            instance = new SingletonLazy();
        }
        return instance;
    }
 
    void doSomething() {
        std::cout << "懒汉式单例模式执行操作" << std::endl;
    }
};
 
// 初始化静态成员指针
SingletonLazy* SingletonLazy::instance = nullptr;
 
int main() {
    SingletonLazy* singleton = SingletonLazy::getInstance();
    singleton->doSomething();
    return 0;
}
```
- **示例代码(线程安全,使用双重检查锁定)**:
```cpp
#include <iostream>
#include <mutex>
 
class SingletonLazyThreadSafe {
private:
    // 构造函数私有化
    SingletonLazyThreadSafe() {}
    // 静态成员指针,初始化为 nullptr
    static SingletonLazyThreadSafe* instance;
    // 互斥锁,用于线程同步
    static std::mutex mtx;
 
public:
    // 提供全局访问点
    static SingletonLazyThreadSafe* getInstance() {
        if (instance == nullptr) {
            std::lock_guard<std::mutex> lock(mtx);
            if (instance == nullptr) {
                instance = new SingletonLazyThreadSafe();
            }
        }
        return instance;
    }
 
    void doSomething() {
        std::cout << "线程安全的懒汉式单例模式执行操作" << std::endl;
    }
};
 
// 初始化静态成员指针和互斥锁
SingletonLazyThreadSafe* SingletonLazyThreadSafe::instance = nullptr;
std::mutex SingletonLazyThreadSafe::mtx;
 
int main() {
    SingletonLazyThreadSafe* singleton = SingletonLazyThreadSafe::getInstance();
    singleton->doSomething();
    return 0;
}
```
 
### 工厂模式
 
#### 简单工厂模式
- **特点**:定义一个工厂类,根据传入的参数决定创建哪种具体产品类的实例。它将对象的创建和使用分离,但不符合开闭原则(对扩展开放,对修改关闭),因为添加新的产品类时需要修改工厂类的代码。
- **示例代码**:
```cpp
#include <iostream>
 
// 抽象产品类
class Product {
public:
    virtual void use() = 0;
    virtual ~Product() {}
};
 
// 具体产品类 A
class ConcreteProductA : public Product {
public:
    void use() override {
        std::cout << "使用产品 A" << std::endl;
    }
};
 
// 具体产品类 B
class ConcreteProductB : public Product {
public:
    void use() override {
        std::cout << "使用产品 B" << std::endl;
    }
};
 
// 简单工厂类
class SimpleFactory {
public:
    static Product* createProduct(const std::string& type) {
        if (type == "A") {
            return new ConcreteProductA();
        } else if (type == "B") {
            return new ConcreteProductB();
        }
        return nullptr;
    }
};
 
int main() {
    Product* productA = SimpleFactory::createProduct("A");
    if (productA) {
        productA->use();
        delete productA;
    }
 
    Product* productB = SimpleFactory::createProduct("B");
    if (productB) {
        productB->use();
        delete productB;
    }
 
    return 0;
}
```
 
#### 工厂方法模式
- **特点**:定义一个创建对象的抽象方法,让子类决定实例化哪个具体产品类。它符合开闭原则,添加新的产品类时只需创建对应的具体工厂子类,而无需修改现有代码。
- **示例代码**:
```cpp
#include <iostream>
 
// 抽象产品类
class Product {
public:
    virtual void use() = 0;
    virtual ~Product() {}
};
 
// 具体产品类 A
class ConcreteProductA : public Product {
public:
    void use() override {
        std::cout << "使用产品 A" << std::endl;
    }
};
 
// 具体产品类 B
class ConcreteProductB : public Product {
public:
    void use() override {
        std::cout << "使用产品 B" << std::endl;
    }
};
 
// 抽象工厂类
class Factory {
public:
    virtual Product* createProduct() = 0;
    virtual ~Factory() {}
};
 
// 具体工厂类 A
class ConcreteFactoryA : public Factory {
public:
    Product* createProduct() override {
        return new ConcreteProductA();
    }
};
 
// 具体工厂类 B
class ConcreteFactoryB : public Factory {
public:
    Product* createProduct() override {
        return new ConcreteProductB();
    }
};
 
int main() {
    Factory* factoryA = new ConcreteFactoryA();
    Product* productA = factoryA->createProduct();
    if (productA) {
        productA->use();
        delete productA;
    }
    delete factoryA;
 
    Factory* factoryB = new ConcreteFactoryB();
    Product* productB = factoryB->createProduct();
    if (productB) {
        productB->use();
        delete productB;
    }
    delete factoryB;
 
    return 0;
}
```
 
#### 抽象工厂模式
- **特点**:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。它可以创建多个不同类型的产品,适用于创建产品族的场景。
- **示例代码**:
```cpp
#include <iostream>
 
// 抽象产品类 A
class AbstractProductA {
public:
    virtual void useA() = 0;
    virtual ~AbstractProductA() {}
};
 
// 具体产品类 A1
class ConcreteProductA1 : public AbstractProductA {
public:
    void useA() override {
        std::cout << "使用产品 A1" << std::endl;
    }
};
 
// 具体产品类 A2
class ConcreteProductA2 : public AbstractProductA {
public:
    void useA() override {
        std::cout << "使用产品 A2" << std::endl;
    }
};
 
// 抽象产品类 B
class AbstractProductB {
public:
    virtual void useB() = 0;
    virtual ~AbstractProductB() {}
};
 
// 具体产品类 B1
class ConcreteProductB1 : public AbstractProductB {
public:
    void useB() override {
        std::cout << "使用产品 B1" << std::endl;
    }
};
 
// 具体产品类 B2
class ConcreteProductB2 : public AbstractProductB {
public:
    void useB() override {
        std::cout << "使用产品 B2" << std::endl;
    }
};
 
// 抽象工厂类
class AbstractFactory {
public:
    virtual AbstractProductA* createProductA() = 0;
    virtual AbstractProductB* createProductB() = 0;
    virtual ~AbstractFactory() {}
};
 
// 具体工厂类 1
class ConcreteFactory1 : public AbstractFactory {
public:
    AbstractProductA* createProductA() override {
        return new ConcreteProductA1();
    }
    AbstractProductB* createProductB() override {
        return new ConcreteProductB1();
    }
};
 
// 具体工厂类 2
class ConcreteFactory2 : public AbstractFactory {
public:
    AbstractProductA* createProductA() override {
        return new ConcreteProductA2();
    }
    AbstractProductB* createProductB() override {
        return new ConcreteProductB2();
    }
};
 
int main() {
    AbstractFactory* factory1 = new ConcreteFactory1();
    AbstractProductA* productA1 = factory1->createProductA();
    AbstractProductB* productB1 = factory1->createProductB();
    if (productA1) {
        productA1->useA();
        delete productA1;
    }
    if (productB1) {
        productB1->useB();
        delete productB1;
    }
    delete factory1;
 
    AbstractFactory* factory2 = new ConcreteFactory2();
    AbstractProductA* productA2 = factory2->createProductA();
    AbstractProductB* productB2 = factory2->createProductB();
    if (productA2) {
        productA2->useA();
        delete productA2;
    }
    if (productB2) {
        productB2->useB();
        delete productB2;
    }
    delete factory2;
 
    return 0;
}
```
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
16、
### Boost多线程特性
 
#### 概述
Boost.Thread库为C++提供了跨平台的多线程支持,它封装了底层操作系统的线程机制,使得在不同操作系统上编写多线程程序变得更加简单和统一。
 
#### 示例代码
以下示例展示了如何使用Boost.Thread创建和管理线程:
```cpp
#include <iostream>
#include <boost/thread.hpp>
 
// 线程函数
void threadFunction() {
    for (int i = 0; i < 5; ++i) {
        std::cout << "子线程: " << i << std::endl;
        boost::this_thread::sleep(boost::posix_time::seconds(1));
    }
}
 
int main() {
    // 创建线程对象
    boost::thread t(threadFunction);
 
    // 主线程执行的代码
    for (int i = 0; i < 3; ++i) {
        std::cout << "主线程: " << i << std::endl;
        boost::this_thread::sleep(boost::posix_time::seconds(1));
    }
 
    // 等待子线程结束
    t.join();
 
    std::cout << "主线程结束" << std::endl;
    return 0;
}
```
 
#### 代码解释
1. **线程函数**:`threadFunction` 是一个普通的函数,它将在新线程中执行。在这个函数中,我们使用 `boost::this_thread::sleep` 函数让线程休眠1秒。
2. **创建线程**:通过 `boost::thread t(threadFunction);` 创建一个新线程,并将 `threadFunction` 作为线程的入口点。
3. **主线程执行**:主线程继续执行自己的代码,同样使用 `boost::this_thread::sleep` 函数让线程休眠1秒。
4. **等待线程结束**:使用 `t.join()` 函数等待子线程执行完毕,确保主线程在子线程结束后才退出。
 
### Boost Socket特性
 
#### 概述
Boost.Asio库是Boost中用于网络编程的库,提供了异步I/O操作的支持,包括Socket编程。它可以用于创建TCP、UDP等各种类型的网络应用程序。
 
#### TCP服务器示例代码
```cpp
#include <iostream>
#include <boost/asio.hpp>
 
using boost::asio::ip::tcp;
 
// 处理客户端连接的函数
void handleConnection(tcp::socket socket) {
    try {
        boost::asio::streambuf buffer;
        boost::asio::read_until(socket, buffer, '\n');
 
        std::istream is(&buffer);
        std::string line;
        std::getline(is, line);
 
        std::cout << "接收到客户端消息: " << line << std::endl;
 
        std::string response = "服务器已收到消息: " + line + "\n";
        boost::asio::write(socket, boost::asio::buffer(response));
    }
    catch (std::exception& e) {
        std::cerr << "异常: " << e.what() << std::endl;
    }
}
 
int main() {
    try {
        boost::asio::io_context io_context;
        tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), 12345));
 
        while (true) {
            tcp::socket socket(io_context);
            acceptor.accept(socket);
            handleConnection(std::move(socket));
        }
    }
    catch (std::exception& e) {
        std::cerr << "异常: " << e.what() << std::endl;
    }
 
    return 0;
}
```
 
#### TCP客户端示例代码
```cpp
#include <iostream>
#include <boost/asio.hpp>
 
using boost::asio::ip::tcp;
 
int main() {
    try {
        boost::asio::io_context io_context;
        tcp::socket socket(io_context);
        tcp::resolver resolver(io_context);
        boost::asio::connect(socket, resolver.resolve("127.0.0.1", "12345"));
 
        std::string message = "Hello, Server!\n";
        boost::asio::write(socket, boost::asio::buffer(message));
 
        boost::asio::streambuf buffer;
        boost::asio::read_until(socket, buffer, '\n');
 
        std::istream is(&buffer);
        std::string line;
        std::getline(is, line);
 
        std::cout << "收到服务器响应: " << line << std::endl;
    }
    catch (std::exception& e) {
        std::cerr << "异常: " << e.what() << std::endl;
    }
 
    return 0;
}
```
 
#### 代码解释
 
##### 服务器端
1. **创建 `io_context`**:`boost::asio::io_context` 是Boost.Asio库的核心对象,用于管理所有的异步操作。
2. **创建 `acceptor`**:`tcp::acceptor` 用于监听指定端口的连接请求。
3. **接受连接**:使用 `acceptor.accept(socket)` 接受客户端的连接请求,并将连接的套接字传递给 `handleConnection` 函数处理。
4. **处理连接**:`handleConnection` 函数负责读取客户端发送的消息,并向客户端发送响应。
 
##### 客户端
1. **创建 `io_context` 和 `socket`**:同样需要创建 `io_context` 和 `tcp::socket` 对象。
2. **解析服务器地址**:使用 `tcp::resolver` 解析服务器的地址和端口。
3. **连接服务器**:使用 `boost::asio::connect` 函数连接到服务器。
4. **发送和接收消息**:使用 `boost::asio::write` 函数向服务器发送消息,使用 `boost::asio::read_until` 函数读取服务器的响应。 
 
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
17、
    线程间的通信
        1、全局变量
        2、信号槽
        3、消息队列
        4、管道[匿名管道、命名管道]
        
    进程间的通信
        前提条件:
            进程间的内存是相互独立的,每个进程都是独立的内存空间
        通信方式:
            1、共享内存--记得上锁排队处理--最快
            2、消息队列
            3、管道 
            4、socket--tcp、udp协议--跨平台、跨电脑
            5、剪切板
            6、D-BUS:是一种信号槽的扩展
    
    Qt:
        类:<QProcess>
        API:start("名字或完整路径"),还可以带启动参数来启动
    
    
    死锁问题
        多线程--访问同一个资源[临界资源]--争夺资源、脏数据--上锁:互斥锁、信号量、条件变量等--互斥的情况下--可能产生死锁
        
        死锁现象:
            1、一把互斥锁,连续2次lock或者异常结束,都会导致死锁
            2、两把互斥锁,两条线程,各自锁上之后,还想拿多方的锁,就会导致相互等待的死锁;
        
        
        死锁条件:4个必要条件
            1、互斥条件
            2、请求保持
            3、不可剥夺
            4、循环等待
        必须要满足4个条件才会导致死锁。
        
        死锁解决方案:
            破坏形成死锁的任意条件即可,如:撤销线程、剥夺资源、超时等待等
            
        预防死锁:
            1、银行家算法--做资源的合理分配
            2、异常死锁的预防:使用守卫锁或者唯一锁,来自动上锁、解锁。
            
    
    任务:
        1、进程间的通信方式:共享内存的示例,把示例代码完善并测试通过
            提示:需要开2个进程来测试
            
        2、设计模式的掌握:单例模式、工厂模式、观察者模式的原理及实现代码;
        
        
 
 
=========================================================================================================================================================================================================================
18、            心跳检测:
    作用:为了对长连接进行保活,并且能去除无用连接
          还能做到断线重连--针对客户端
 
    tcp本身是有一个keep-alive保活状态,默认是2个小时
    由于默认的时间太久,满足不了我们的业务需求,所以要重设一个心跳检测。
    
    --------------------------
    要实现心跳检测,需要在服务端和客户端进行相应的处理:
    要在30秒内进行保活检测;
    服务端:
        容器:map<套接字,阈值>   map<SOCKET,int>   // 阈值 -- 临界值
        然后使用线程定时轮询容器进行相应的处理;
        对容器进行增删改查的操作:
        增:accept 成功之后
        删:阈值为0时,先关闭套接字,再删除键值对
        改:遍历时的递减;收到正常包时重置阈值
        查:定时遍历,需要单独起一条线程来遍历map
        
        
    
    客户端:
        起一条线程来定时发心跳包;
        设定一个阈值来判断服务器是否回响应,若发现服务器一直不响应,需要断线重连;
    ============================================================================================================================
    dyc-------------------------------------
    --------------------------------------------
    en  
    时的 
    --------------
    通知所有在线的客户端
    前提是客户端在线
    --------------------
    PC机器上
    ------------------时的  连的时长连接
    【描述界面效果】
    自己做的
    -------------------
    一直在维护更新 有商用的
    ----------------------
    【刚刚听错了】
    -----------------------
    【按剧本来】
    -----------------------
    没啥问题
    -----------------
    【吹起来】
    qt 信号槽
    mvd
    socket 
    ------------------
    qt 多线程 
    自动 默认 会自动切换连接,单线 -- 直接,多线程--队列 
    直接  单线程 
    队列  多线程 异步
    阻塞队列 多线程 阻塞 
    唯一  防止重复连接
         就是防止多次连接的时候,只生效一个连接
         如果使用其他的方式,连接几次就会生效几次
    ---------------------
    事件循环
    
    一般先到父部件的事件过滤器
    再到子部件的 event 函数 
    再到子部件的 具体的事件处理函数
     如果对事件进行忽略,再返回到父部件的具体事件处理
    --------------------
    mvd 模型视图
    
    一般数据量大的时候 使用 
    特别是使用二维表格来展示数据的时候使用
    说具体字母
    M 模型 存储数据
    V 视图 显示数据
    D 代理 提供编辑功能
    mv关联使用 setModel()函数 
    设置一些项的值  setItem()
    ----------------------------------
    tcp协议
    类:QTcpSocket 客户端类
        APi: connectToHost 连接服务器
              判断连接是否成功 
              发送 write 函数 
              收 read 或者 readAll
              需要有个接受信号 readyRead
              这个信号和自定义槽关联,槽里面收包
              ---------------------
        【老大 深呼吸 慢慢控制你的脑子 你现在脑子空白了】
    -----------------------------
    只要是开发相关的 都行 
    C++ Qt 都行 
    --------------------
    我擅长 C++ qt 
    ----------------------------
    网络 界面 都行 
    嗯  擅长 QWidget 
    QML用得少一些 【好好念字母 别着急】
    --------------------
    啥问题 
    Git  命令行
    --------------------------说啊
    代码为主  有时候也用qt 设计师来辅助
    
    有时候一些底层大的底板  界面相对简单的
    使用设计师工具来处理
    复杂的使用纯代码来实现
    ------------------------------
    就是我来负责的---【设计师工具 就是Qt Desinger】
    -------------------
----------------------------------------------------------------------------------------------------------------------------------------------------------
19、
### 文件流操作
C++ 提供了 `fstream` 库来进行文件的读写操作,它包含了三个主要的类:`ifstream`(用于读取文件)、`ofstream`(用于写入文件)和 `fstream`(可同时进行读写操作)。
 
#### 示例代码:文件读写
```cpp
#include <iostream>
#include <fstream>
#include <string>
 
int main() {
    // 写入文件
    std::ofstream outFile("example.txt");
    if (outFile.is_open()) {
        outFile << "Hello, File!" << std::endl;
        outFile << "This is a test." << std::endl;
        outFile.close();
    } else {
        std::cerr << "无法打开文件进行写入。" << std::endl;
        return 1;
    }
 
    // 读取文件
    std::ifstream inFile("example.txt");
    if (inFile.is_open()) {
        std::string line;
        while (std::getline(inFile, line)) {
            std::cout << line << std::endl;
        }
        inFile.close();
    } else {
        std::cerr << "无法打开文件进行读取。" << std::endl;
        return 1;
    }
 
    return 0;
}
```
 
#### 代码解释
- `std::ofstream outFile("example.txt");`:创建一个输出文件流对象 `outFile`,并打开 `example.txt` 文件用于写入。
- `outFile << "Hello, File!" << std::endl;`:向文件中写入数据。
- `std::ifstream inFile("example.txt");`:创建一个输入文件流对象 `inFile`,并打开 `example.txt` 文件用于读取。
- `std::getline(inFile, line)`:逐行读取文件内容。
 
### 日志操作
日志操作是记录程序运行过程中重要信息的一种方式,有助于调试和监控程序。可以通过自定义类来实现简单的日志功能。
 
#### 示例代码:简单日志类
```cpp
#include <iostream>
#include <fstream>
#include <ctime>
#include <string>
 
class Logger {
public:
    Logger(const std::string& filename) : logFile(filename, std::ios::app) {
        if (!logFile.is_open()) {
            std::cerr << "无法打开日志文件。" << std::endl;
        }
    }
 
    ~Logger() {
        if (logFile.is_open()) {
            logFile.close();
        }
    }
 
    void log(const std::string& message) {
        if (logFile.is_open()) {
            std::time_t now = std::time(nullptr);
            char* dt = std::ctime(&now);
            logFile << "[" << dt << "] " << message << std::endl;
        }
    }
 
private:
    std::ofstream logFile;
};
 
int main() {
    Logger logger("app.log");
    logger.log("程序启动");
    logger.log("执行某个操作");
    logger.log("程序结束");
    return 0;
}
```
 
#### 代码解释
- `Logger(const std::string& filename)`:构造函数,打开指定的日志文件并以追加模式写入。
- `void log(const std::string& message)`:记录日志信息,同时添加当前时间戳。
- `~Logger()`:析构函数,关闭日志文件。
 
### XML 文件操作
可以使用第三方库如 `pugixml` 来进行 XML 文件的读写操作。
 
#### 示例代码:使用 `pugixml` 读写 XML 文件
```cpp
#include <iostream>
#include "pugixml.hpp"
 
int main() {
    // 创建 XML 文档
    pugi::xml_document doc;
    pugi::xml_node root = doc.append_child("root");
    pugi::xml_node child = root.append_child("child");
    child.append_attribute("name") = "example";
    child.text() = "Hello, XML!";
 
    // 保存 XML 文档到文件
    if (doc.save_file("example.xml")) {
        std::cout << "XML 文件保存成功。" << std::endl;
    } else {
        std::cerr << "XML 文件保存失败。" << std::endl;
        return 1;
    }
 
    // 读取 XML 文件
    pugi::xml_document readDoc;
    if (readDoc.load_file("example.xml")) {
        pugi::xml_node readRoot = readDoc.child("root");
        pugi::xml_node readChild = readRoot.child("child");
        std::cout << "属性值: " << readChild.attribute("name").value() << std::endl;
        std::cout << "文本内容: " << readChild.text().get() << std::endl;
    } else {
        std::cerr << "XML 文件读取失败。" << std::endl;
        return 1;
    }
 
    return 0;
}
```
 
#### 代码解释
- `pugi::xml_document doc;`:创建一个 XML 文档对象。
- `pugi::xml_node root = doc.append_child("root");`:添加根节点。
- `doc.save_file("example.xml");`:将 XML 文档保存到文件。
- `readDoc.load_file("example.xml");`:从文件中加载 XML 文档。
 
### JSON 数据处理
可以使用第三方库如 `nlohmann/json` 来进行 JSON 数据的处理。
 
#### 示例代码:使用 `nlohmann/json` 处理 JSON 数据
```cpp
#include <iostream>
#include <fstream>
#include "json.hpp"
 
using json = nlohmann::json;
 
int main() {
    // 创建 JSON 对象
    json j;
    j["name"] = "John";
    j["age"] = 30;
    j["hobbies"] = {"reading", "swimming", "running"};
 
    // 将 JSON 对象写入文件
    std::ofstream outJsonFile("example.json");
    if (outJsonFile.is_open()) {
        outJsonFile << j.dump(4);
        outJsonFile.close();
    } else {
        std::cerr << "无法打开文件进行 JSON 写入。" << std::endl;
        return 1;
    }
 
    // 从文件中读取 JSON 数据
    std::ifstream inJsonFile("example.json");
    if (inJsonFile.is_open()) {
        json readJ;
        inJsonFile >> readJ;
        std::cout << "姓名: " << readJ["name"] << std::endl;
        std::cout << "年龄: " << readJ["age"] << std::endl;
        std::cout << "爱好: ";
        for (const auto& hobby : readJ["hobbies"]) {
            std::cout << hobby << " ";
        }
        std::cout << std::endl;
        inJsonFile.close();
    } else {
        std::cerr << "无法打开文件进行 JSON 读取。" << std::endl;
        return 1;
    }
 
    return 0;
}
```
 
#### 代码解释
- `json j;`:创建一个 JSON 对象。
- `j["name"] = "John";`:向 JSON 对象中添加键值对。
- `outJsonFile << j.dump(4);`:将 JSON 对象以缩进 4 个空格的格式写入文件。
- `inJsonFile >> readJ;`:从文件中读取 JSON 数据到 `readJ` 对象中。         
----------------------------------------------------------------------------------------------------------------------------------------------------------        
20、
### 学生类的单元测试
 
```cpp
#include <iostream>
#include <string>
#include "gtest/gtest.h"
 
// 学生类定义
class Student {
public:
    Student(const std::string& name, char gender, int age)
        : name(name), gender(gender), age(age) {}
 
    std::string getName() const {
        return name;
    }
 
    char getGender() const {
        return gender;
    }
 
    int getAge() const {
        return age;
    }
 
private:
    std::string name;
    char gender;
    int age;
};
 
// 测试用例:测试 Student 类的构造函数和成员访问函数
TEST(StudentTest, ConstructorAndGetters) {
    Student student("Alice", 'F', 18);
    EXPECT_EQ(student.getName(), "Alice");
    EXPECT_EQ(student.getGender(), 'F');
    EXPECT_EQ(student.getAge(), 18);
}
```
 
### 联系人类的单元测试
 
```cpp
#include <iostream>
#include <string>
#include "gtest/gtest.h"
 
// 联系人类定义
class Contact {
public:
    Contact(const std::string& name, char gender, const std::string& phoneNumber)
        : name(name), gender(gender), phoneNumber(phoneNumber) {}
 
    std::string getName() const {
        return name;
    }
 
    char getGender() const {
        return gender;
    }
 
    std::string getPhoneNumber() const {
        return phoneNumber;
    }
 
private:
    std::string name;
    char gender;
    std::string phoneNumber;
};
 
// 测试用例:测试 Contact 类的构造函数和成员访问函数
TEST(ContactTest, ConstructorAndGetters) {
    Contact contact("Bob", 'M', "1234567890");
    EXPECT_EQ(contact.getName(), "Bob");
    EXPECT_EQ(contact.getGender(), 'M');
    EXPECT_EQ(contact.getPhoneNumber(), "1234567890");
}
 
// 主函数,运行所有测试
int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}
```
 
### 代码解释
1. **包含头文件**:引入所需的头文件,其中 `gtest/gtest.h` 是 Google Test 的主要头文件。
2. **定义类**:这里重新定义了要测试的 `Student` 类和 `Contact` 类,实际项目中,这些类可能在其他头文件中定义。
3. **编写测试用例**:
    - `TEST(StudentTest, ConstructorAndGetters)`:定义了 `Student` 类的一个测试用例,测试构造函数和成员访问函数。
    - `TEST(ContactTest, ConstructorAndGetters)`:为 `Contact` 类编写了类似的测试用例。
4. **测试框架初始化和运行**:在 `main` 函数中,调用 `::testing::InitGoogleTest` 初始化 Google Test 框架,然后通过 `RUN_ALL_TESTS` 运行所有测试用例。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
21、
## QT 环境安装
 
Qt5.9.4
 
Qt:基于C++可跨平台的App和UI开发框架。
 
App:Application,应用程序。
 
UI:User Interface 用户界面。
 
GUI:Graphics User interface 图形用户界面。
 
平台:Windows、Ubuntu、CentOS、Android、IOS、鸿蒙、UOS(统信)、Deepin、塞班
 
跨平台:可在不同平台上直接运行的方式。
 
两种跨平台方式:
 
1、程序级:编译好的程序可直接在不同的平台上运行,需安装解释器来支持。如:Java、Python、JS等。
 
2、源码级:一份代码,无需修改,可直接在不同平台上编译、运行,需编译环境或交叉编译来支持。如:C、C++等。
 
速度比较:源码级快、程序级慢。
IDE(集成开发环境):Qt Creator 4.5.0
 
VS 2015助手:Qt VST
 
工具-->扩展与更新-->联机-->搜索"Qt"-->下载Qt VST-->安装Qt VST-->完成后重启VS2015-->配置Qt版本(msvc2015_64)
 
安装包下载地址: https://download.qt.io/official_releases/qt/
 
工程目录:QtProject
 
**创建工程不能带中文路径。**
 
类名格式:TestMainWindow
 
添加控件:ui文件-->拖拽,双击ui文件进入设计师界面
 
解决按钮文字显示不全
 
- 方法1、使用布局自适应大小
- 方法2、在主函数设置DPI属性
  - **QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);** 
 
自适应大小:栅格布局+间隔。
 
### 驼峰命名法
 
大驼峰:类名、结构体,例如MyClass
 
小驼峰:变量名、函数名,例如myClass
 
凹驼峰:文件、工程,例如my_class
 
### 常用英文
 
GUI 即图形用户界面(Graphical User Interface)
 
## QT基本概念
 
### Qt是什么?
 
Qt是一个基于C++,可跨平台的APP和UI开发框架
 
平台:各类系统
 
跨平台:可在不同平台上直接运行的方式有两种跨的方式
 
1. **程序级:**编译好的程序可直接在不同平台上运行,需安装解释器(虚拟机)来支持
2. **源码级:**一份代码,无需修改,可直接在不同平台上编译运行,需编译环境或交叉编译来支持
 
### Qt版本介绍
 
Qt安装包:
qt-opensource-windows-x86-mingw492-5.6.1-1.exe
 
- opensource表示开源版本
- windows-x86表示Windows 32位平台
- mingw表示使用MinGW编译器
- 5.6.1-1是当前版本号
 
**MinGW**即**Minimalist GNU For Windows**,是将GNU开发工具移植到Win32平台下的产物,是一套Windows上的GNU工具集。用其开发的程序不需要额外的第三方DLL支持就可以直接在Windows下运行。
 
 
 
### Qt分辨率自适应
 
方法1:设置本地电脑分辨率
 
方法2:UI模块中自适应
 
方法3:
 
```c++
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
```
 
### C++和Qt的区别
 
输出:
 
C++:头文件\<iostream>    和cout<<
 
Qt:头文件\<QDebug> 和qDebug()<<
 
**Qt中使用cout会在程序执行后才输出,不友好**
 
**qDebug()会自动换行,多段数据一起输入不需要输入“ ”空格隔开,会自动分隔**
 
 
 
### Qt主函数参数
 
主函数参数在项目--->Command line arguments中添加,**参数以空格分隔**
 
```cpp
#include "testmainwindow.h"
#include <QApplication>
#include <QDebug>
 
int main(int argc, char *argv[])
{
    qDebug()<<argc;
    for(int i=0;i<argc;i++)
    {
        qDebug()<<argv[i];
    }
 
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QApplication a(argc, argv);
    TestMainWindow w;
    w.show();
 
    return a.exec();
}
```
 
 
 
### Qt工具
 
**1.Qt助手:**文档阅读、发布,一般想给项目生成帮助文档:先对源码加注解
 
**2.Qt语言家:**用于国际化的翻译……多语言文字的切换
 
**3.Qt设计师:**用于拖拽式的可视化界面设计
 
[Qt分级:入门级(拖拽)->高级(纯代码)->大师级(自定义控件)]
 
## QT常用控件
 
explicit关键字:显式的,防止隐式转换。
 
```
class A
{
    int a;
public:
    explicit A(int x){
        a = x;
    }
};
int main(){
    A a1(5);        //显式调用有参构造
    A a2 = A(5);        //显式调用构造 A(int x)
    A a3 = 5;            //隐式转换,一旦加了explicit 关键字修饰有参构造,隐式转换则失败
}
```
 
**控件类型**
 
Layouts:布局
 
- vertical Layout:垂直布局
- Horizontal layout:水平布局
- Grid Layout:网络布局
- Form Layout:表单布局
 
Spacers:间隔器
 
- Horizontal Spacer:水平间隔器
- Vertical Spacer:垂直间隔器
 
Buttons:按钮
 
- Push Button:按键
- Tool Button:工具按钮
- Radio Button:收音机按钮
- Check Box:复选框
- Command Link Button:命令链接按钮
- Dialog Button Box:对话按钮框
 
Item Views (Model-Based):项目视图(基于模型)
 
- List View:列表视图
- Tree View:树视图
- Table View:表格视图
- Column View:列视图
 
Item Widgets (Item-Based):项目Widget(基于项目)
 
- List Widget:列表小部件
- Tree Widget:树状插件
- Table Widget:表格Widget
 
Containers:容器/集装箱
 
- Group Box:群组框
- Scroll Area:滚动区域
- Tool Box:工具箱
- Tab Widget:标签组件
- Stacked Widget:堆叠部件
- Frame:框架
- Widget:部件
- MDI Area:MDI区域
- Dock Widget:停靠小部件
- QAxWidget:
 
Input Widgets:输入小部件
 
- Combo Box:组合框
- Font Combo Box:字体组合框
- Line Edit:行编辑框
- Text Edit:文本编辑框
- Plain Text Edit:纯文本编辑
- Spin Box:旋转盒
- Double Spin Box:双旋转盒
- Time Edit:时间编辑
- Date Edit:日期编辑
- Date/Time Edit:日期/时间编辑
- Dial:拨号
- Horizontal Scroll Bar:水平滚动条
- Vertical Scroll Bar:垂直滚动条
- Horizontal Slider:
- Vertical Slider:
- Key Sequence Edit:密钥序列编辑
 
Display Widgets:显示小部件
 
- Label:标签
  - 设置文本:对象.setText("文本");
  - 取文本:对象.text() --> 返回QString类型的文本;
- Text Browser:文本浏览器
  - 可用追加的方式添加文本:对象.append("");
- Graphics View:图形视图
- Calender Widget:日历
- LCD Number:液晶显示器编号
- Progress Bar:进度条
- Horizontal Line:水平线
- Vertical Line:垂直线
- OpenGL Widget
- QQuikWidget 
 
**控件属性**
 
先把空间放到ui界面上,在构造中:
 
- 读:ui->控件对象->text     直接就是属性名或函数名,或在text前(加一些前缀配合什么样的属性要前缀)
- 写:ui->控件对象->set属性名(参数列表)     修改属性内容 
 
**复选框:**
 
- ui->控件对象->is属性名     返回值为bool
- ui->控件对象->has属性名     返回值为bool
- ui->控件对象->属性名     返回值为bool
 
**行编辑框:**
 
- ui->控件对象->text()  读
- ui->控件对象->setText(字符串)  写
 
**控件具体属性**
 
**通用属性**
 
QObject:对象;
 
- objectName:对象名称;
 
QWidget:部件;
 
- enabled:启用;
- geometry:几何形状;
- sizePolicy:尺寸策略;
- minimumSize:最小尺寸;
- maximumSize:最大尺寸;
- sizelncrement:尺寸增量;
- baseSize:基本尺寸;
- palette:调色板;
- font:字体;
- cursor:光标;
- mouseTracking:鼠标跟踪;
- tabletTracking:平板跟踪;
- focusPolicy:焦点策略;
- contextMenuPolicy:上下文菜单策略;
- acceptDrops:接受拖放;
- toolTip:工具提示;
- toolTipDuration:工具提示持续时间;
- statusTip:状态提示;
- whatsThis:这是什么;
- accessibleName:可访问名称;
- accessibleDescription:可访问描述;
- layoutDirection:布局方向;
- autoFillBackground:自动填充背景;
- styleSheet:样式表;
- locale:区域设置;
- inputMethodHints:输入法提示
 
**独有属性**
 
PushButton
 
- QAbstractButton:抽象按钮;
  - text:文本;
  - icon:图标;
  - iconSize:图标大小;
  - shortcut:快捷键;
  - checkable:可复选;
  - checked:已选中;
  - autoRepeat:自动重复;
  - autoExclusive:自动排他;
  - autoRepeatDelay:自动重复延迟;
  - autoRepeatInterval:自动重复间隔;
- QPushButton:按钮;
  - autoDefault:自动默认;
  - default:默认;
  - flat:扁平。
 
Label
 
- QFrame:框架;
  - frameShape:框架形状;
  - frameShadow:框架阴影;
  - lineWidth:线宽;
  - midLineWidth:中线宽;
- QLabel:标签;
  - text:文本;
  - textFormat:文本格式;
  - pixmap:像素图;
  - scaledContents:缩放内容;
  - alignment:对齐方式;
  - wordWrap:自动换行;
  - margin:边距;
  - indent:缩进;
  - openExternallinks:打开外部链接;
  - textlnteractionFlags:文本交互标志;
  - buddy:伙伴。
- API
  - 读
    - text():获取文本内容
    - textFormat(): 用于获取文本的格式类型(如纯文本、富文本)。
    - textInteractionFlags(): 用于获取文本的交互标志(如是否允许点击链接)。
  - 写
  - 
    - setTextFormat(参数);
      - Qt::AutoText:自动确定文本格式(通常为纯文本)。
      - Qt::PlainText:纯文本格式,不支持 HTML 格式。
      - Qt::RichText:富文本格式,支持 HTML 和样式。
      - Qt::MarkdownText:Markdown 格式(从 Qt 5.14 开始支持)。
 
LineEdit
 
- QLineEdit:行编辑框;
  - inputMask:输入掩码;
  - text:文本内容;
  - maxLength:最大长度;
  - frame:边框;
  - echoMode:回显模式;
  - cursorPosition:光标位置;
  - alignment:对齐方式;
  - dragEnabled:启用拖动;
  - readOnly:只读;
  - placeholderText:占位符文本;
  - cursorMoveStyle:光标移动风格;
  - clearButtonEnabled:启用清除按钮。
 
https://blog.csdn.net/GG_Bruse/article/details/136634146
 
```
https://blog.csdn.net/Miwll/article/details/141201383?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-0-141201383-blog-137563865.235^v43^pc_blog_bottom_relevance_base7&spm=1001.2101.3001.4242.1&utm_relevant_index=1
```
 
https://blog.csdn.net/GG_Bruse/article/details/136634146
 
### PushButton
 
类继承关系:QObject->QWidget->QAbstractButton->QPushbutton
 
**geometry:**几何形状
 
**sizePolicy:**尺寸策略
 
在Qt中,sizePolicy是每个Qt widgets(包括QPushButton)的一个属性,它决定了当窗口大小改变或者父容器大小改变时,控件(如QPushButton)应该如何调整自身的大小。对于sizePolicy的水平策略(Horizontal Policy),具体来说,它可以影响按钮在水平方向上的尺寸管理行为,主要包括以下几个方面:
 
- **Fixed**:表示按钮应该保持固定的宽度,无论窗口大小如何变化,按钮都不会随之拉伸或收缩。
- **Minimum**:控件至少需要保持指定的最小宽度,但允许在空间充足的情况下扩展到大于这个最小值。
- **Maximum**:控件的最大宽度不会超过指定值,但在空间不足时仍然可以缩小到更小。
- **Preferred**(也称为MinimumExpanding):控件有一个理想的首选宽度,通常是由控件内容决定的。在布局中,如果空间足够,控件将尝试扩展到这个宽度,但如果其他控件也需要空间,它可能会比首选宽度小。同时,它也会保持一个最小宽度。
- **Expanding**(也称为Ignored):控件在布局中尽可能占据所有可用的空间。如果布局的所有控件都设置了水平扩张策略,它们将会平等地分配额外的空间。
 
**palette:**调色板
 
**font:**字体
 
**cursor:**光标,游标
 
**mouseTracking:**鼠标追踪
 
```
mouseTracking属性用于保存是否启用鼠标跟踪,缺省情况是不启用的。没启用的情况下,对应部件只接收在鼠标移动同时至少一个鼠标按键按下时的鼠标移动事件,启用鼠标跟踪的情况下,任何鼠标移动事件部件都会接收。
 
部件方法hasMouseTracking()用于返回当前是否启用鼠标跟踪,setMouseTracking(bool enable)用于设置是否启用鼠标跟踪。
 
与鼠标跟踪相关的函数主要是mouseMoveEvent()。
```
 
**tabletTracking:**平板触控板/笔追踪
 
```
tabletTracking属性保存是否启用部件的平板跟踪,缺省是不起用的。没有启用平板跟踪的情况下,部件仅接收触控笔与平板接触或至少有个触控笔按键按下时的触控笔移动事件。
如果部件启用了平板跟踪功能,部件能接收触控笔靠近但未真正接触平板时的触控笔移动事件,这可以用于监视操作位置以及部件的辅助操作功能(如旋转和倾斜),并为图形界面提供这些操作的信息接口。
 
部件方法hasTabletTracking()用于返回当前是否启用平板跟踪,setTabletTracking(bool enable)用于设置是否启用平板跟踪。
```
 
**focus policy:**焦点策略
 
- **NoFocus:**不接受键盘焦点。这个属性通常用于那些不需要与用户交互的控件。
- **TabFocus:**通过 Tab 键获得焦点。这个属性用于需要通过 Tab 键进行导航的控件。
- **ClickFocus:** 通过鼠标点击获得焦点。
- **StrongFocus:**通过 Tab 键和鼠标点击都可以获得焦点。
- **WheelFocus:** 通过鼠标滚轮也可以获得焦点。
 
​       属性              应用场景              适用控件示例
Qt::NoFocus    不需要用户交互的控件    QLabel, QFrame
Qt::TabFocus    表单填写、设置选项    QLineEdit, QSpinBox
Qt::ClickFocus    图像编辑、拖拽操作    QGraphicsView
Qt::StrongFocus    综合应用,如文本编辑器    QTextEdit
Qt::WheelFocus    需要通过滚轮进行精细调整的控件    QSlider
 
 
 
**ContextMenuPolicy:**上下文菜单策略
 
ContextMenuPolicy 是 [Qt](https://so.csdn.net/so/search?q=Qt&spm=1001.2101.3001.7020) 框架中 QWidget 类的一个枚举类型,它定义了如何响应和处理鼠标右键点击事件,即上下文菜单的策略。上下文菜单是一种临时出现的菜单,通常出现在用户执行特定操作(如右键点击)时,提供与当前上下文相关的操作选项。
 
- NoContextMenu:这个策略表示禁用上下文菜单。当设置为这个值时,即使用户右键点击控件,也不会弹出菜单。这可以用于那些不需要额外菜单选项的控件。widget->setContextMenuPolicy(Qt::NoContextMenu);
 
 
 
- DefaultContextMenu:这个策略表示使用默认的上下文菜单行为。当用户右键点击控件时,Qt 会自动弹出一个包含 actions() 返回的所有动作(QAction 对象)的菜单。这是大多数控件的默认行为。widget->setContextMenuPolicy(Qt::DefaultContextMenu);
 
 
 
- CustomContextMenu:当你想要自定义上下文菜单时,应该使用这个策略。设置为 Qt::CustomContextMenu 后,你需要重写控件的 contextMenuEvent(QContextMenuEvent *event) 事件处理函数来创建和显示自己的菜单。
 
  ```cpp
  widget->setContextMenuPolicy(Qt::CustomContextMenu);
   
  // 在控件的类中重写 contextMenuEvent 函数
  void MyClass::contextMenuEvent(QContextMenuEvent *event) 
  {
      QMenu menu(this);
      // 添加自定义的动作到菜单
      QAction *action1 = menu.addAction("Custom Action 1");
      QAction *action2 = menu.addAction("Custom Action 2");
      // 显示菜单
      menu.exec();
  }
  ```
 
 
 
- **ActionsContextMenu:**这个策略是 Qt::DefaultContextMenu 的一个特例,它只显示与当前控件相关联的 QAction 对象。这意味着,只有那些通过 addAction 方法添加到控件中的动作才会出现在上下文菜单中。这通常用于那些具有内置动作的控件,例如按钮或工具栏。
 
  widget->setContextMenuPolicy(Qt::ActionsContextMenu);
 
  使用这个策略时,你不需要重写 contextMenuEvent 函数,因为 Qt 会自动为你创建一个包含所有相关动作的菜单。
 
 
 
- **PreventContextMenu:**这个策略用于完全阻止上下文菜单的显示。即使用户执行了通常会引发上下文菜单的操作(如右键点击),也不会有任何菜单弹出。这个选项可以用来确保某些控件不接受任何上下文菜单交互。
 
  widget->setContextMenuPolicy(Qt::PreventContextMenu);
 
  这个选项对于那些不应该有任何上下文菜单的控件非常有用,例如,一个简单的标签或静态文本控件。
 
  
 
  
 
     在设计用户界面时,选择合适的 ContextMenuPolicy 对于提供良好的用户体验至关重要。你需要根据控件的功能和预期的用户交互来决定使用哪种策略。例如,对于包含多个操作的复杂控件,Qt::DefaultContextMenu 或 Qt::ActionsContextMenu 可能是合适的选择。对于那些不需要任何上下文菜单的控件,Qt::NoContextMenu 或 Qt::PreventContextMenu 可以确保用户不会期望进行不相关的操作。对于需要完全自定义上下文菜单的控件,Qt::CustomContextMenu 提供了最大的灵活性。
 
 
 
**acceptDrops:**接收拖放
 
**ToolTip:**工具提示
 
toolTip属性设置部件的toolTip提示信息,toolTip提示信息在鼠标放到控件上会浮动出一个小框显示提示信息。默认情况下,仅显示活动窗口子部件的toolTip,可以通过在窗口对象设置Qt.WA_AlwaysShowToolTips属性来改变,但不能是需要显示tooTip的部件对象中设置。
 
如果要控制tooTip显示的行为(如控制显示位置),可以重写部件的event()方法捕获事件类型为QEvent.ToolTip的事件。
 
缺省值为空,可以通过toolTip()和setToolTip( str)来读取和设置toolTip。
 
**ToolTipDuring:**工具提示
 
toolTipDuration 属性控制toolTip显示的时长,单位是毫秒,如果设置为-1,则显示时长根据toolTip内容的长度来计算。
toolTipDuration 缺省值为-1,可以通过toolTipDuration ()和settoolTipDuration ( int msec)来读取和设置toolTipDuration 。
 
**statusTip:**状态提示
 
statusTip属性保存statusTip提示信息,statusTip提示信息在鼠标放到控件上时在窗口的状态栏显示提示信息,如果窗口无状态栏则不显示。
 
statusTip属性 缺省值为空字符串,可以通过statusTip()和setstatusTip ( str)来读取和设置statusTip。
 
**whatsThis:**whatsThis属性保存部件的帮助信息。whatsThis的帮助信息一般在部件获得焦点后按Shift+F1弹出显示,如果这个快捷键被别的功能占用,则whatsThis的帮助信息可能无法展示。有些对话窗提供一个带问号的按钮可以点击显示whatsThis的帮助信息。
 
whatsThis属性 缺省值为空字符串,可以通过whatsThis()和setwhatsThis ( str)来读取和设置whatsThis。
 
**accessibleName:**辅助阅读中显示的部件的名称。对于大多数控件,不需要设置此属性。当控件不提供任何文本时,设置此属性非常重要。默认为一个空字符串。
 
**accessibleDescription:**控件的描述。描述应该提供相对详细的控件信息。默认为一个空字符串。
 
**autoFillBackground :** 是否自动填充控件背景。默认为false。
 
 
 
**QAbstractButton**
 
**shortcut:**快捷键
 
### Layout布局对象
 
```cpp
#include<QApplication>
#include<QLabel>
#include<QLineEdit>
#include<QPushButton>
#include<QHBoxLayout>//水平布局
#include<QVBoxLayout>//垂直布局
#include<QWidget>//窗口,QApplication是程序
 
int main(int argc,char *argv[]){
 
QApplication app(argc,argv);//程序对象
 
QLabel *openLabel = new QLabel;
QLable *infoLabel = new QLabel;
    
QLineEdit *cmdLineEdit = new QLineEdit;
QPushButton *commitButton = new PushButton;
QPushButton *cancelButton = new PushButton;
QPushButton *browseButton = new PushButton;
    
infoLabel->setText("inputsmd:");
openLabel->setText("open");
commitButton->setText("commit");
cancelButton->setText("cancel");
browseButton->setText("browse");
    
QHBoxLayout *cmdLayout =new QHBoxLayout;//水平布局对象
cmdLayout->addWidget(openLabel);
cmdLayout->addWidget(cmdLineEdit);
    
QHBoxLayout *buttonLayout = new QHBoxLayout;
buttonLayout->addWidget(commitButton);
butfonLayout->addWidget(cancelButton);
buttonLayout->addWidget(browseButton);
    
QVBoxLayout *mainLayout =new QVBoxLayout//垂直布局对象
mainLayout->addwidget(infoLabel);//布局对象中添加控件对象使用的addwidget
mainLayout->addLayout(cmdLayout);//布局对象中添加布局对象使用的addLayout
mainLayout->addLayout(buttonLayout);
    
QWidget w;//创建空白窗口对象,注意这里不是指针,要用.调用成员函数
w.setLayout(mainLayout);//窗口对象中添加布局对象
w.show();    
 
return app.exec();//返回程序执行,死循环,让程序一直处于打开状态
}
```
 
1.打开QT MinGW 窗口
 
cd到 cpp文件目录
 
d:  //先进入所在磁盘,再cd到目录
 
2.修改环境变量
 
path中加入:D:\Qt\Qt5.9.4\5.9.4\mingw53_32\bin
 
3.qmake(cpp文件名) -project
 
生成qmake.pro工程文件,在其中添加
 
QT += widgets gui
 
 
 
 
 
## 工程结构
 
**.xml文件**
 
- 可扩展标记语言,用用尖括号包住的名为标签名,以树形结点的方式存在,并在尖括号内加上键值对(属性)来使标签节点的属性更丰富。
- C++用三方库:tinyxml2开源库解析xml。
 
### 工程文件
 
xx.pro: 工程项目配置文件,也是启动文件
 
xx.pro.user:工程在当前系统用户下的记录文件,会自动生成 
 
​                         主要记录工程的生成目录,编译器信息等,若将源码打包其他人,要删掉该文件
 
xx.ui:工程可视化界面设计的文件,记录拖拽生成的界面效果
 
​            是由Qt Designer【QT设计师工具】产生的
 
​             编译时会自动的生成目录中产生一个相应的的c++头文件:ui.xx.h
 
​             build-工程名-编译器-版本
 
​           【xml文件】可扩展标识语言
 
​             用尖括号包住的标签名,以树形节点的方式存在
 
​            并在尖括号内加上键值(属性)对来使得标签节点的属性更丰富
 
```
 <a>内容</a>
 <a><b>xx</b></a>[可嵌套]
c++用三方库
tinyxmlz开源库解析xml
```
 
### 生成目标的目录[生成目录]
 
一般和源码目录同级
 
点击按钮执行事件流程:
 
- 1、鼠标(硬件)点击按钮
- 2、消息循环捕捉点击事件(系统中)
- 3、通过消息坐标遍历屏幕上的顶层窗口,发送消息给窗口句柄,得到程序进程(窗口句柄:系统给每个窗口分配的唯一ID)
- 4、Qt程序中进行事件循环:a.exec(),捕捉到对应事件后分发给对应部件
- 5、鼠标点击事件发送信号,执行与之关联的槽
 
**build-工程名-编译器-版本**
 
build-helloworld-Desktop_Qt_5_6_1_MinGW_32bit-Debug
 
Qt Creator将项目源文件和编译生成的文件进行了分类存放。
 
helloworld文件夹中是项目源文件,而现在这个文件夹存放的是编译后生成的文件。进入该文件夹可以看到这里有3个Makefile文件和一个ui_hellodialog.h文件,还有两个目录debug和release。
 
现在release文件夹是空的,进入debug文件夹,这里有3个.o文件和一个.cpp文件,它们是编译时生成的中间文件,可以不必管它,而剩下的一个helloworld.exe文件便是生成的可执行文件。
 
 testmainwindow.h 头文件
 
```
#ifndef TESTMAINWINDOW_H
#define TESTMAINWINDOW_H
#include <QMainWindow
namespace Ui {
class TestMainWindow;
}
//下面同名类负责UI功能的实现
class TestMainWindow : public QMainWindow
{
    Q_OBJECT
public:
    explicit TestMainWindow(QWidget *parent = 0);   //explicit:显式的,防止隐式转换
    ~TestMainWindow();
private slots:
    void on_pushButton_clicked();   //按钮的功能槽函数,点击会触发槽函数
private:
    Ui::TestMainWindow *ui;     //ui指针代表设计界面的对象
    int cnt;
};
#endif // TESTMAINWINDOW_H
```
 
main.cpp源文件
 
```
#include "testmainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QApplication a(argc, argv);
    TestMainWindow w;
    w.show();
    return a.exec();        //启动事件循环,捕捉并处理事件,将其分发给对应部件
}
```
 
testmainwindow.cpp源文件
 
```
#include "testmainwindow.h"
#include "ui_testmainwindow.h"
#include <QDebug>
TestMainWindow::TestMainWindow(QWidget *parent) :
    QMainWindow(parent),        //委托构造
    ui(new Ui::TestMainWindow)
{
    ui->setupUi(this);      //ui指针里面的控件初始化(界面上的控件初始化)
}
TestMainWindow::~TestMainWindow()
{
    delete ui;
}
void TestMainWindow::on_pushButton_clicked()        //按钮点击事件
{
    cnt++;        //记录点击次数
    ui->label->setText(QString::number(cnt));        //设置文本,即更改文本内容  QString::number():数字转为QString类型字符串
    qDebug()<<ui->label->text();        //取文本内容    
}
```
 
**添加并使用资源**
 
添加资源:右击工程名-->添加新文件-->Qt-->Qt Resource File -->改名--> 完成-->添加前缀-->添加文件-->保存
 
使用资源:
 
- 1、通过属性选择。
- 2、在样式中使用,用的是资源路径。
- 3、在代码添加资源路径,使用。
  - 使用方式:ui->pushButton->setStyleSheet();
 
**鼠标点击并移动窗口**
 
头文件:
 
```
<QMouseEvent>
 
<QPoint>
```
 
方法一:
 
```
void WorkMainWindow::mousePressEvent(QMouseEvent *event)    //获取偏移量,鼠标点击事件
{
    m_offect = event->globalPos() - this->pos();        //m_offect为QPoint类型成员变量
}
 
void WorkMainWindow::mouseMoveEvent(QMouseEvent *event)     //鼠标移动事件
{
   this->move(event->globalPos() - m_offect);
}
```
 
event->globalPos()  获取鼠标在屏幕中的全局位置。
 
this->pos()  获取当前窗口的位置。
 
方法二:
 
```
//定义鼠标三种状态方法 
protected: 
//鼠标按下 
void mousePressEvent(QMouseEvent *e); 
//鼠标移动 
void mouseMoveEvent(QMouseEvent *e); 
//鼠标释放 
void mouseReleaseEvent(QMouseEvent *e);
private:
QPoint last;
 
//获取鼠标点定位窗体位置 
void MainWindow::mousePressEvent(QMouseEvent *e) 
    last = e->globalPos(); 
void MainWindow::mouseMoveEvent(QMouseEvent *e) 
    int dx = e->globalX() - last.x(); 
    int dy = e->globalY() - last.y(); 
    last = e->globalPos(); 
    move(x()+dx,y()+dy); 
void MainWindow::mouseReleaseEvent(QMouseEvent *e) 
    int dx = e->globalX() - last.x(); 
    int dy = e->globalY() - last.y(); 
    move(x()+dx, y()+dy); 
}
```
 
setWindowFlag(Qt::FramelessWindowHint);  删除窗口顶部
 
## QT默认源码
 
### QT头文件 mainWindow.h 
 
```
#ifndef TESTMAINWINDOW_H
#define TESTMAINWINDOW_H
 
#include <QMainWindow>
 
namespace Ui {//避免同名冲突,使用命名空间
class TestMainWindow;//负责界面相关的事情
}
 
//同名类负责UI对应功能的实现
class TestMainWindow : public QMainWindow
{
    Q_OBJECT//有了这个宏,才能使用信号与槽函数
 
public:
    static int a;
    explicit TestMainWindow(QWidget *parent = 0);//显示关键字,防止隐式转换
    ~TestMainWindow();
 
private slots:
    //按钮的功能槽函数
    void on_pushButton_clicked();
 
private:
    //ui指针代表着设计界面的对象
    Ui::TestMainWindow *ui;
};
 
#endif // TESTMAINWINDOW_H
 
```
 
```
namespace Ui {
class TestMainWindow;//负责界面相关的事情
}
```
 
定义了命名空间Ui,并在其中前置声明了TestMainWindow类,这个类就是在testmainwindow.h文件中看到的那个类。因为它与新定义的类同名,所以使用了Ui命名空间。而前置声明是为了加快编译速度,也可以避免在一个头文件中随意包含其他头文件而产生错误。因为这里只使用了该类对象的指针,如:
 
```
private:
    //ui指针代表着设计界面的对象
    Ui::TestMainWindow *ui;
};
```
 
**定义了该类对象的指针,这并不需要该类的完整定义,所以可以使用前置声明(后续在cpp文件中加入ui_xxx.h的头文件声明)。**这样就不用在这里添加ui_hellodialog.h的头文件包含,而可以将其放到hellodialog.cpp文件中进行。
 
```
class TestMainWindow : public QMainWindow
{
    Q_OBJECT//有了这个宏,才能使用信号与槽函数
```
 
定义类,并指定继承自QMainWindow
 
定义了Q_OBJECT宏,扩展了普通C++类的功能,比如下一章要讲的信号和槽功能。必须在类定义的最开始处来定义这个宏
 
```
explicit TestMainWindow(QWidget *parent = 0);//显示关键字,防止隐式转换
```
 
是显式构造函数,参数是用来指定父窗口的,默认是没有父窗口的。
 
### QT main.cpp文件
 
```
explicit:显示的,防止隐式转换
class A{
    int a;
public:
    explicit A(int x){
        a=x;
    }
};
int main(int argc, char *argv[])
{
 
    A a1(5);//显示调用有参构造
    A a2=A(5);//显示调用构造:A(int x)
    A a3=5;  //隐式转换,一旦加了explicit,关键字修饰有参构造,隐式转换就会失败
    return 0;
}
```
 
```
标签:label
 
api:设置文本:对象.setText(“文本”)
 
​         取文本:对象.Text()->返回QString类型的文本
 
ui界面控件:通过ui指针找到
 
​            数字:0~N,每点一次值加一,并在标签上显示最新值
 
​            数字转字符串:QString::number(数字)
```
 
**创建应用程序对象**:
 
- `QApplication`对象负责管理应用程序的生命周期和全局状态。
- 它处理应用程序的初始化、事件循环和资源管理。例如,它负责接收和分发操作系统发送给应用程序的事件,如鼠标点击、键盘输入等。
- **QApplication类对象**,用于管理应用程序的资源,任何一个Qt GUI程序都要有一个QApplication对象。因为Qt程序可以接收命令行参数,所以它需要argc和argv两个参数。
 
**初始化和显示界面**:
 
- 在`main`函数中,通常会创建界面类的实例,并调用其`show`方法来显示界面。
 
- 新建了一个QLabel对象,并将QDialog对象作为参数,表明了对话框是它的父窗口,也就是说这个标签放在对话框窗口中。
- 在默认情况下,新建的可视部件对象都是不可见的,要使用show()函数让它们显示出来。
 
**进入事件循环**:
 
- `main`函数通常会调用`QApplication`对象的`exec`方法,进入应用程序的事件循环。
- 事件循环负责不断地接收和处理事件,直到应用程序退出。在事件循环中,当用户与界面进行交互时,相应的信号和槽会被触发,执行相应的业务逻辑。
 
- QApplication对象进入事件循环,**这样当Qt应用程序在运行时便可以接收产生的事件**,例如单击和键盘按下等事件
 
**清理资源和退出应用程序**:
 
- 当应用程序退出时,`main`函数负责清理资源,如释放内存、关闭文件等。
- 通常,在`main`函数的最后,会返回一个退出码,以指示应用程序的退出状态
 
### QT mainWindow.cpp文件
 
```
#include "testmainwindow.h"
#include "ui_testmainwindow.h"
#include <QDebug>
 
TestMainWindow::TestMainWindow(QWidget *parent) :
    //委托构造
    QMainWindow(parent),
    //开辟新空间给Ui指针赋值
    ui(new Ui::TestMainWindow)
{
    //给界面控件/Ui指针对象初始化,开辟内存
    ui->setupUi(this);//初始化,类中的指针挨个新建
}
TestMainWindow::~TestMainWindow()
{
    //析构堆内存
    delete ui;
}
 
int TestMainWindow::a = 100;
 
//槽函数
void TestMainWindow::on_pushButton_clicked()
{
    qDebug()<<"100";
    //通过点击按钮
    qDebug()<<ui->label->text();
 
    static int num;
    ui->label->setText(QString::number(a));
    a++;
}
 
```
 
第2行添加了ui头文件,因为在TestMainWindow.h文件中只是使用了前置声明,所以头文件在这里添加。
 
第7行设置setupUi()函数的参数为this,表示为现在这个类所代表的对话框创建界面。
 
### 在Qt Creator中使用.ui界面文件 ui_xxx.h
 
ui界面生成的控件是xxx.ui格式的XML文件
 
需要通过编译.ui文件:
 
​     打开命令提示符程序,然后输入:
​     cd C:\Qt\helloworld_2命令进入helloworld_2文件夹中。再**使用uic编译工具,从ui文件生成头文件**。具体命令是:
​     **uic –o ui_hellodialog.h hellodialog.ui** 
​      就像前面看到的那样,ui文件生成的默认头文件的名称是“ui_”加ui文件的名称。这时在helloworld_2目录中已经生成了相应的头文件。
 
本地磁盘的项目目录的build-helloworld-Desktop_Qt_5_6_1_MinGW_32bit-Debug目录中,就可以看到由.ui文件生成的ui_hellodialog.h头文件。
 
或是在源文件的构造函数的初始化列表中对Ui::类名使用F2进入
 
ui头文件中包含了对所有设计界面的控件的初始化定义和重命名,最后使用connectSlotsByName进行关联
 
```
QMetaObject::connectSlotsByName(ConnectionMainWindow);
```
 
## qt窗口拖拽事件
 
### QT系统两大循环
 
**1.系统的消息循环:**会通过循环执行的消息队列接收输入设备(鼠标、键盘点击)发来的消息,进行对应的中断事件
 
**2.Qt程序的事件循环:**程序通过事件循环,捕捉对应事件,分发给对应部件,从而**执行与之关联的槽函数**
 
### 鼠标点击并移动窗口的问题
 
 
 
### 窗口拖拽实现
 
头文件构造函数中加入坐标成员变量和鼠标点击、鼠标拖拽事件
 
```cpp
public:
    explicit Login(QWidget *parent = 0);
    ~Login();
 
    QPoint m_offset;
    void mousePressEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent *event);
```
 
源文件中通过计算窗口左上角坐标和鼠标间偏移量来实现移动
 
qt2022使用globalPosition().toPoint()获取当前鼠标坐标
 
```cpp
void Login::mousePressEvent(QMouseEvent *event)
{
    //求坐标偏移量
    m_offset = event->globalPos() - this->pos();
}
 
void Login::mouseMoveEvent(QMouseEvent *event)
{
    //移动窗口,窗口新坐标记录为当前鼠标松开位置-移动的偏移量
    this->move(event->globalPos() - m_offset);
}
```
 
方法3:定义QPoint last; last记录鼠标点击窗口时的窗口坐标
 
```cpp
//获取鼠标点定位窗体位置
void MainWindow::mousePressEvent(QMouseEvent *event)
{
    last = event->globalPos();
}
 
void MainWindow::mouseMoveEvent(QMouseEvent *event)
{
    int dx = event->globalX() - last.x();
    int dy = event->globalY() - last.y();
 
    last = event->globalPos();
    move(x()+dx,y()+dy);
}
 
void MainWindow::mouseReleaseEvent(QMouseEvent *event)
{
    //dx,dy是偏移量
    int dx = event->globalX() - last.x();
    int dy = event->globalY() - last.y();
    move(x()+dx,y()+dy);
}
```
 
## 对象模型
 
信号本质则为事件。槽的本质是对信号响应的函数
 
信号呈现方式就是函数(某个事件产生,Qt框架机制会调用对应的信号函数,通知使用者)。
 
- 1、所谓信号槽(观察者模式)。
- 2、当某一个事件发生之后,则会发出一个信号(signal)。
- 3、处理信号和自己的一函数称为槽(slot)绑定来处理这个处理这个信号。当信号发出时,被连接的槽函数会自动被回调操作。槽函数可以跟一个信号关联,当信号被发射后,**关联的槽函数自动执行。**
- 4、connect():参数1信号发送者;参数2信号;参数3信号接收者;参数4槽函数(接收到信号之后执行的函数)。
 
### 信号:是一个函数
 
声明:signals
 
作用:作为触发者
 
```
signals:
        void 信号名(形参列表);  //只声明,不实现
使用:emit 信号名(实参);
```
 
类中声明一个信号,例如:
 
signals:
 
  void dlgReturn(int);          // 自定义的信号,只声明,不实现
 
- 声明一个信号要使用signals关键字。
- 在signals前面不能使用public、private和protected等限定符,因为只有定义该信号的类及其子类才可以发射该信号。
- 信号只用声明,不需要也不能对它进行定义实现。
- 信号没有返回值,只能是void类型的。
- 只有QObject类及其子类派生的类才能使用信号和槽机制,使用信号和槽,还必须在类声明的最开始处添加Q_OBJECT宏。
 
**发射信号**
 
例如:
 
void MyDialog::on_pushButton_clicked()  // 确定按钮
 
{
 
 int value = ui->spinBox->value();  // 获取输入的数值
 
 emit dlgReturn(value);        // 发射信号
 
 close();               // 关闭对话框
 
}
 
当单击确定按钮时,便获取spinBox部件中的数值,然后使用自定义的信号将其作为参数发射出去。发射一个信号要使用emit关键字,例如程序中发射了dlgReturn()信号。
 
信号作用:作为触发者
 
### 槽:成员函数
 
声明:slots
 
作用:作为功能执行者
 
```
private slots:
        void 槽名(形参列表);  //有声明,有实现
        {//功能}
```
 
声明:
 
private slots:
 
 void showValue(int value);
 
实现:
 
void Widget::showValue(int value)     // 自定义槽
 
{
 
 ui->label->setText(tr("获取的值是:%1").arg(value));
 
}
 
作用:作为功能执行者。
 
- 声明一个槽需要使用slots关键字。
- 一个槽可以是private、public或者protected类型的,槽也可以被声明为虚函数,这与普通的成员函数是一样的,也可以像调用一个普通函数一样来调用槽。
- 槽的最大特点就是可以和信号关联。
 
### 信号槽:实现对象间的通信
 
信号和槽用于两个对象之间的通信,信号和槽机制是Qt的核心特征,也是Qt不同于其他开发框架的最突出的特征。
 
- 在Qt中,使用了信号和槽来进行对象间的通信。
- 当一个特殊的事情发生时便可以发射一个信号,比如按钮被单击;而槽就是一个函数,它在信号发射后被调用,来响应这个信号。
- 在Qt的部件类中已经定义了一些信号和槽,但是更多的做法是子类化这个部件,然后添加自己的信号和槽来实现想要的功能。
 
**调用顺序:**
 
1、在Qt4版本里面,当信号被触发时,会依次随机调用,即本次信号会把与信号关联的所有的槽函数都调用一遍,但顺序是随机的,可用STL模板库的map(有序存储)、set(有序存储)容器存储。
 
2、在Qt5版本里面,则与关联信号的顺序有关,调用顺序与关联顺序一致。可用STL模板库的vector、list、deque、unordered_map(无序存储)、unordered_set(无序存储)容器存储。
 
### 信号槽的关联函数:
 
**connect(信号发送者、信号、信号接收者、槽、连接方式)**
 
函数原型:
 
bool QObject::connect ( const QObject * sender, const char * signal, const QObject * receiver, const char * method, Qt::ConnectionType type = Qt::AutoConnection )
 
- 它的第一个参数为发送信号的对象,例如这里的dlg;
- 第二个参数是要发送的信号,这里是SIGNAL(dlgReturn(int));
- 第三个参数是接收信号的对象,这里是this,表明是本部件,即Widget,当这个参数为this时,也可以将这个参数省略掉,因为connect()函数还有另外一个重载形式,该参数默认为this;
- 第四个参数是要执行的槽,这里是SLOT(showValue(int))。
- 对于信号和槽,必须使用SIGNAL()和SLOT()宏,它们可以将其参数转化为const char* 类型。connect()函数的返回值为bool类型,当关联成功时返回true。
- 信号和槽的参数只能有类型,不能有变量,例如写成SLOT(showValue(int value))是不对的。对于信号和槽的参数问题,基本原则是信号中的参数类型要和槽中的参数类型相对应,而且信号中的参数可以多于槽中的参数,但是不能反过来,如果信号中有多余的参数,那么它们将被忽略。
 
信号发送者是一个指针,拥有信号;信号接收者是一个指针,拥有槽;
 
连接方式:
 
- 自动连接(Qt::AutoConnection):默认的,单线程切换到直接连接,多线程切换到队列连接。
- 直接连接(Qt::DirectConnection):单线程,同步,阻塞。
- 队列连接(Qt::QueuedConnection):(单)多线程,异步,非阻塞。
- 阻塞队列连接(Qt::BlockingQueuedConnection):只能多线程,同步,阻塞;若单线程,死锁。
- 唯一连接(Qt::UniqueConnection):防止重复连接(其他效果同自动连接)。
 
信号发送者和接收者在同一线程内为单线程。
 
信号发送者和接收者不在同一线程内为多线程。
 
同步:排队执行。
 
异步:同时执行。
 
STL库:vector 5    list 5    deque 5    map 4    set 4
 
![img](./Image.png)
 
### 关联函数原型:
 
connect写法:
 
1.宏的写法:SIGNAL,SLOT,connect(信号发送者,SIGNAL(信号名(参类型)),信号接收者,SLOT(槽名(参类型)));
 
```
头文件类中声明:
signals:
    void mySignal();        //信号声明
private slots:
    void mySlot();        //槽声明
    void on_pushButton_clicked();
 
源文件:
SignalSlotMainWindow::SignalSlotMainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::SignalSlotMainWindow)
{
    ui->setupUi(this);
    connect(this,SIGNAL(mySignal()),this,SLOT(mySlot()));    //信号槽连接
         //信号发送者为指针类型,拥有信号,信号接收者为指针类型,拥有槽函数
}
SignalSlotMainWindow::~SignalSlotMainWindow()
{
    delete ui;
}
void SignalSlotMainWindow::mySlot()        //自定义槽函数
{
     qDebug()<<100; 
}
void SignalSlotMainWindow::on_pushButton_clicked()    //默认槽函数,点击事件
{
     emit mySignal();    //发射信号
}
```
 
2.一般的函数指针,前提是信号槽无重载;
 
connect(信号发送者,&类名::信号名,信号接收者,&类名::槽名);
 
```
connect(d,&D::ShowSignal,this,&WorkMainWindow::ShowSlot);
```
 
3.若有重载,使用QOverload加载函数指针;
 
connect(信号发送者,QOverlaod<形参>::of(&类名::信号名),信号接收者,QOverlaod<形参列表>::of(&类名::槽名));
 
```
connect(d,QOverload<>::of(&D::ShowSignal),this,QOverload<>::of(&WorkMainWindow::ShowSlot));
```
 
4.若槽功能简单,则可使lambda表达式取代槽;
 
connect(信号发送者,QOverlaod<形参列表>::of(&类名::类名),信号接收者,【=/&/变量名】(形参列表){...});
 
```
connect(d,QOverload<>::of(&D::ShowSignal),this,[=](){
        a->show();
        b->show();
        c->show();
        this->show();});
```
 
**信号与槽实现步骤**
 
- 1、有信号-->找地方发射信号
- 2、有槽-->实现功能。
  - **注意:信号与槽在一开始无任何关系。**
- 3、关联信号槽-->关联之后,产生关系。
  - 使用connect()函数进行关联,宏的写法:SIGNAL/SLOT,信号发射后就会执行槽函数。
 
**使用信号槽的条件**
 
1、类得继承 QObject 类(直接或间接)
 
2、类首行得使用宏:Q_OBJECT
 
3、才可以添加自定义信号、槽。
 
**注册元类型**
 
当信号槽的参数为自定义类型时,需要注册元类型才能安全使用,特别是在多线程的情况下:
 
API:qRegisterMataType<类型>("类型");
 
***自动关联(可做了解):**
 
**格式:**
 
void on_对象名_信号名(形参列表……);  //槽函数
 
**条件:**
 
- 给对象注册名字:对象.setObjectName("对象");
  - 示例:pushButton = new QPushButton(centralWidget);
  - pushButton->setObjectName(QStringLiteral("pushButton"));
- 调用:connectSlotsByName(对象名)
  - 示例:QMetaObject::connectSlotsByName(ConnectionMainWindow);
 
**结论:**
 
1、QObject类是所有Qt类的基类
 
2、QWidget类是所有可视界面类的基类
 
**总结:**
 
**UI相关:**
 
1、先绘制效果界面。
 
2、想办法在主界面显示出所有界面。
 
**功能相关:**
 
搞清楚谁有信号谁有槽,想清楚在哪里连接信号槽。
 
一般在主界面关联(标准说法:在拥有信号/槽的对象的类中关联)。
 
 
 
对象树(多叉树):类似C++里智能指针的效果,防止Qt的堆内存泄漏。
 
**字符串截取**
 
QString str = "abcdefg";
 
str.left(长度) 从0开始的长度
 
str.mid(下标,长度) 从下标开始,去m个字符
 
str.right(长度) 从末尾开始,向前取长度个字符
 
**<Qtimer> 信号:timeout()-->关联槽**
 
API函数:
 
- start()/start(毫秒) 启动
- stop() 停止
- 设定周期 setInterval(毫秒)
 
**字符数值转换**
 
字符串转数值:str.toInt();  //str为QString类型
 
数值转字符串:QString::number(数字)
 
### 信号与槽的重复连接问题
 
**一、写在点击槽函数内导致重复关联的原因**
 
1. 多次调用问题:
   - 当把信号与槽的`connect`关联函数写在按钮点击的槽函数(如`on_pushButton_clicked`)中时,每次点击按钮,这个槽函数都会被执行一次。
   - 每次执行都会再次调用`connect`,从而导致信号和同一个槽不断地重复关联。
   - 例如,第一次点击按钮,建立了一个关联;第二次点击,又建立了一个相同的关联,以此类推。
 
**二、写在构造函数中不会重复关联的原因**
 
1. 一次性建立连接:
   - 构造函数在对象创建时只被调用一次。
   - 把信号与槽的关联放在构造函数中,在对象初始化时就建立了连接,并且这个过程只发生一次。
   - 后续无论进行多少次按钮点击或其他操作,都不会再次建立相同的连接,因为连接在一开始就已经确定并且不会被重复执行。
 
综上所述,为了避免重复关联,通常建议在构造函数或其他合适的初始化位置进行信号与槽的连接,而不是在可能被多次调用的槽函数内部进行连接操作。
 
### 使用信号槽注意事项:
 
1.类继承QObject类[直接或间接]
 
2.类首行使用宏:Q_OBJECT
 
3.添加自定义信号,槽
 
```
当信号槽的参数为自定义类型时,需要做一些特殊处理才能安全使用,特别是在多线程的情况下;
特殊处理就是:对自定义类型进行元类型注册;
可以直接使用API来注册:如下
 qRegisterMetaType<MyClass>(“MyClass”)
若要使用到引用类型,也需要对引用注册
qRegisterMetaType<MyClass>(“MyClass&”)
```
 
qRegisterMetaType<类型>(“类型”)
 
结论:
 
1.QObject类是所有qt类的基类
 
2.QWidget类是所有可视化界面类的基类
 
### qt三大核心
 
1.信号槽
 
2.元对象系统
 
3.事件模型
 
```
自动关联:
void on_对象名_信号名(形参列表);  //槽函数
```
 
MFC:微软基础类库,也可以在vs下绘制ui界面【vs内量】
 
|      | 平台                    | 跨平台 | 通信方式 | 源码 | 循环     |
| ---- | ----------------------- | ------ | -------- | ---- | -------- |
| MFC  | Windows                 | 否     | 回调机制 | 闭源 | 消息循环 |
| Qt   | Linux,Windows,Android | 是     | 信号槽   | 开源 | 事件循环 |
 
## sender函数
 
**信号槽传送数据的两种方式**:
 
1.信号中带参数
 
2.sender函数+属性系统
 
**sender函数作用:**返回信号发送者的指针
 
**sender的使用场景:**可以在槽中拿到对象指针,配合属性系统,可**实现对象间通信**
 
QSignalMapper
 
API:
 
- mapping(参数)
- sethapping(参数,参数)
 
```
    connect(m_second,SIGNAL(secSignal()),this,SLOT(showtext()));    连接信号槽
 
void PropertyMainWindow::showtext()
{
    QString str = sender()->property("textData").toString();    //通过信号发送者的对象指针获取textData属性内容,转为字符串型
    ui->label->setText(str);    //修改label的text属性为str
}
```
 
## 属性对应API
 
```
先把控件放到ui界面
 
在构造中ui->控件对象->读->1.属性名当函数名
                       2.加前缀配合使用
                   写->set属性名(参数列表)
标签,按钮,行编辑框 text->text()
                          setText()
内容:数值,文本,下拉选择
读:属性名()
写:set属性名()
 
复选框
is属性名--bool
has属性名--bool
属性名--bool
```
 
## 属性系统
 
1.作用
 
- 1、可配合sender()函数实现对象间的通信。
- 2、可用来掌握控件背后的实现原理。
 
2.添加属性的方式
 
1、宏:Q_PROPERTY
 
2、API:
 
对象.setProperty()  添加/修改
 
对象.property()    读取属性
 
**宏的方式**
 
Q_PROPERTY(类型 属性名 READ 函数名 WRITE 函数名 NOTIFY 信号名……);
 
类型:需添加对应的私有成员存属性内容(同类型)
 
属性名:当字符串用
 
函数名:对私有成员变量读写
 
信号名:写的时候发射信号通知变更了,变更时携带参数,传递变更的内容。
 
用map容器存放名值对,存在则覆盖;不存在则新增。
 
## 对象树:
 
**本质:**多叉树。利用栈的结构特点去自动销毁父节点对其孩子列表中的对象也一起被销毁
 
**作用:**
 
- 防止内存泄漏,与智能指针异曲同工。
- 若对象有界面,还可以控制界面显示层次。(子部件在父部件上显示)
  - 注意:这里的父子是非继承关系,只是树上的节点关系。
 
**添加对象到树的方式:**
 
1、实例化时,参数传父节点指针
 
```
A *a1 = new A(this);      //this作为a1的父节点指针
```
 
2、使用API来指定父节点指针:对象.setParent(父节点指针);
 
```
A *a2 = new A;
a2->setParent(this);        //this作为a2的父节点指针
```
 
**新增C++类如何添加到对象树:**
 
新添C++类,首先**继承QPushButton**这个有可视化界面的父类
 
然后在构造函数的定义中**添加 QWidget类的指针**
 
```cpp
#ifndef MYBUTTON_H
#define MYBUTTON_H
#include <QPushButton>
 
class MyButton : public QPushButton
{
public:
    MyButton(QWidget *p = 0);
    ~MyButton();
};
 
#endif // MYBUTTON_H
 
```
 
接着去cpp源文件中使用委托构造在类的构造函数的初始化列表中完成初始化
 
```cpp
#include "mybutton.h"
#include <QDebug>
MyButton::MyButton(QWidget *p)
    :QPushButton(p)//委托构造,QWidget父类是有界面的基类
{
 
}
 
MyButton::~MyButton()
{
    qDebug()<<"delete MyButton";
}
 
```
 
如此一来,新的C++类MyButton就可以通过主函数new出来,添加到对象树中进行统一管理了:
 
```
 
 
 
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
22、
 
### 1. `auto` 关键字
`auto` 关键字用于自动推导变量的类型,在编译时根据初始化表达式自动确定变量的类型,从而简化代码编写,尤其适用于类型名较长或复杂的情况。
 
#### 示例代码
```cpp
#include <iostream>
#include <vector>
 
int main() {
    // 自动推导为 int 类型
    auto num = 10; 
    // 自动推导为 std::vector<int>::iterator 类型
    std::vector<int> vec = {1, 2, 3};
    auto it = vec.begin(); 
 
    std::cout << "num: " << num << std::endl;
    for (; it != vec.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;
 
    return 0;
}
```
 
### 2. `nullptr` 关键字
`nullptr` 是 C++11 引入的一个新的空指针字面量,用于表示空指针。它可以隐式转换为任何指针类型,解决了 `NULL` 在某些情况下的二义性问题。
 
#### 示例代码
```cpp
#include <iostream>
 
void func(int* ptr) {
    if (ptr == nullptr) {
        std::cout << "传入的是一个空指针" << std::endl;
    } else {
        std::cout << "传入的不是空指针" << std::endl;
    }
}
 
int main() {
    int* ptr = nullptr;
    func(ptr);
 
    return 0;
}
```
 
 
### 3. `tuple` 元组
`std::tuple` 是一个固定大小的、可以存储不同类型元素的容器。它可以将多个不同类型的值组合成一个对象,方便数据的传递和处理。
 
#### 示例代码
```cpp
#include <iostream>
#include <tuple>
 
int main() {
    // 创建一个包含 int、double 和 std::string 类型元素的元组
    auto myTuple = std::make_tuple(10, 3.14, "Hello");
 
    // 获取元组中的元素
    int num = std::get<0>(myTuple);
    double d = std::get<1>(myTuple);
    std::string str = std::get<2>(myTuple);
 
    std::cout << "num: " << num << ", d: " << d << ", str: " << str << std::endl;
 
    return 0;
}
```
 
 
### 4. 范围 `for` 循环
范围 `for` 循环提供了一种更简洁的方式来遍历容器或数组中的元素,无需手动管理迭代器或索引。
 
#### 示例代码
```cpp
#include <iostream>
#include <vector>
 
int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
 
    // 范围 for 循环遍历 vector
    for (auto element : vec) {
        std::cout << element << " ";
    }
    std::cout << std::endl;
 
    return 0;
}
```
 
 
### 5. 匿名函数 `lambda`
`lambda` 表达式是一种匿名函数,允许在需要函数对象的地方直接定义一个简短的函数,无需显式定义一个命名的函数或函数对象类。
 
#### 示例代码
```cpp
#include <iostream>
#include <vector>
#include <algorithm>
 
int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
 
    // 使用 lambda 表达式对 vector 中的每个元素进行平方操作
    std::for_each(vec.begin(), vec.end(), [](int& num) {
        num = num * num;
    });
 
    // 输出处理后的 vector
    for (auto element : vec) {
        std::cout << element << " ";
    }
    std::cout << std::endl;
 
    return 0;
}
```    
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
23、
 
### G++ 编译器的使用
 
#### `-o` 和 `-c` 选项的区别
- **`-c` 选项**:只进行编译和汇编操作,不进行链接。它会将每个源文件(`.cpp` 文件)分别编译成对应的目标文件(`.o` 文件),但不会生成可执行文件。例如:
```bash
g++ -c main.cpp utils.cpp
```
上述命令会生成 `main.o` 和 `utils.o` 两个目标文件。
- **`-o` 选项**:用于指定输出文件的名称。可以用于生成可执行文件,也可以用于生成目标文件。例如:
```bash
g++ main.cpp utils.cpp -o my_program
```
此命令会将 `main.cpp` 和 `utils.cpp` 编译、链接成一个名为 `my_program` 的可执行文件。
 
#### `-g` 选项的目的
`-g` 选项用于在编译时生成调试信息,这些信息会被嵌入到生成的目标文件或可执行文件中。调试信息包含了源代码的行号、变量名、函数名等,使得调试器(如 GDB)能够将可执行文件中的指令与源代码中的行对应起来,方便进行调试。示例命令如下:
```bash
g++ -g main.cpp -o my_program
```
 
### GDB 调试器的使用
 
#### 逐语句(Step)和逐过程(Next)执行
- **逐语句执行(Step)**:使用 `step` 或 `s` 命令,它会进入函数内部,逐行执行代码,包括函数调用语句也会进入函数体内部执行。例如,在 GDB 中启动调试后:
```plaintext
(gdb) s
```
- **逐过程执行(Next)**:使用 `next` 或 `n` 命令,它会执行一行代码,但不会进入函数内部,而是将函数调用当作一条语句执行完毕。例如:
```plaintext
(gdb) n
```
 
#### 给函数设置断点
使用 `break` 或 `b` 命令,后面跟上函数名即可在该函数的入口处设置断点。例如,要在 `main` 函数处设置断点:
```plaintext
(gdb) b main
```
 
#### 给最后一行设置断点
首先需要知道源文件的名称和总行数。可以使用 `break` 命令,后面跟上源文件名和行号。假设源文件名为 `main.cpp`,可以通过以下命令查看总行数:
```bash
wc -l main.cpp
```
假设总行数为 100,那么在最后一行设置断点的命令为:
```plaintext
(gdb) b main.cpp:100
```
 
#### 查看所有断点
使用 `info breakpoints` 或 `info b` 命令可以查看当前设置的所有断点信息,包括断点编号、位置、是否启用等。例如:
```plaintext
(gdb) info b
```
 
### Makefile 的编写与执行
 
#### 执行一个 Makefile
在包含 Makefile 的目录下,直接在终端输入 `make` 命令即可执行 Makefile 中的规则。如果 Makefile 的文件名不是默认的 `Makefile` 或 `makefile`,可以使用 `-f` 选项指定 Makefile 的文件名。例如:
```bash
make -f my_makefile
```
 
#### 编写一个 Makefile
一个简单的 Makefile 通常包含目标(target)、依赖(dependency)和命令(command)三部分。以下是一个简单的示例,用于编译一个包含 `main.cpp` 和 `utils.cpp` 的 C++ 项目:
```makefile
# 编译器和编译选项
CXX = g++
CXXFLAGS = -Wall -g
 
# 目标文件列表
OBJS = main.o utils.o
 
# 最终可执行文件
TARGET = my_program
 
# 生成可执行文件的规则
$(TARGET): $(OBJS)
    $(CXX) $(CXXFLAGS) -o $(TARGET) $(OBJS)
 
# 生成 main.o 的规则
main.o: main.cpp utils.h
    $(CXX) $(CXXFLAGS) -c main.cpp
 
# 生成 utils.o 的规则
utils.o: utils.cpp utils.h
    $(CXX) $(CXXFLAGS) -c utils.cpp
 
# 清理规则
clean:
    rm -f $(OBJS) $(TARGET)
```
上述 Makefile 的解释如下:
- `CXX`:指定编译器为 `g++`。
- `CXXFLAGS`:指定编译选项,`-Wall` 表示显示所有警告信息,`-g` 表示生成调试信息。
- `OBJS`:列出所有的目标文件。
- `TARGET`:指定最终生成的可执行文件的名称。
- `$(TARGET): $(OBJS)`:定义生成可执行文件的规则,依赖于所有的目标文件。
- `main.o: main.cpp utils.h` 和 `utils.o: utils.cpp utils.h`:分别定义生成 `main.o` 和 `utils.o` 的规则,依赖于对应的源文件和头文件。
- `clean`:定义清理规则,用于删除生成的目标文件和可执行文件。
24、
 
### 连接并操作 MySQL 数据库
 
#### 1. 使用 MySQL API
MySQL API 是 MySQL 官方提供的 C/C++ 接口,能让你直接与 MySQL 数据库进行交互。
 
**示例代码**
```cpp
#include <iostream>
#include <mysql/mysql.h>
 
int main() {
    // 初始化 MySQL 连接
    MYSQL *conn = mysql_init(nullptr);
    if (conn == nullptr) {
        std::cerr << "初始化 MySQL 连接失败: " << mysql_error(conn) << std::endl;
        return 1;
    }
 
    // 连接到 MySQL 数据库
    if (mysql_real_connect(conn, "localhost", "user", "password", "database_name", 0, nullptr, 0) == nullptr) {
        std::cerr << "连接到 MySQL 数据库失败: " << mysql_error(conn) << std::endl;
        mysql_close(conn);
        return 1;
    }
 
    // 增删改查操作示例
 
    // 插入数据
    std::string insertQuery = "INSERT INTO users (name, age) VALUES ('John', 25)";
    if (mysql_query(conn, insertQuery.c_str()) != 0) {
        std::cerr << "插入数据失败: " << mysql_error(conn) << std::endl;
    }
 
    // 查询数据
    std::string selectQuery = "SELECT * FROM users";
    if (mysql_query(conn, selectQuery.c_str()) == 0) {
        MYSQL_RES *result = mysql_store_result(conn);
        if (result != nullptr) {
            MYSQL_ROW row;
            while ((row = mysql_fetch_row(result))) {
                for (int i = 0; i < mysql_num_fields(result); ++i) {
                    std::cout << (row[i] ? row[i] : "NULL") << " ";
                }
                std::cout << std::endl;
            }
            mysql_free_result(result);
        }
    } else {
        std::cerr << "查询数据失败: " << mysql_error(conn) << std::endl;
    }
 
    // 更新数据
    std::string updateQuery = "UPDATE users SET age = 26 WHERE name = 'John'";
    if (mysql_query(conn, updateQuery.c_str()) != 0) {
        std::cerr << "更新数据失败: " << mysql_error(conn) << std::endl;
    }
 
    // 删除数据
    std::string deleteQuery = "DELETE FROM users WHERE name = 'John'";
    if (mysql_query(conn, deleteQuery.c_str()) != 0) {
        std::cerr << "删除数据失败: " << mysql_error(conn) << std::endl;
    }
 
    // 调用存储过程
    std::string callProcedureQuery = "CALL your_stored_procedure()";
    if (mysql_query(conn, callProcedureQuery.c_str()) != 0) {
        std::cerr << "调用存储过程失败: " << mysql_error(conn) << std::endl;
    }
 
    // 关闭 MySQL 连接
    mysql_close(conn);
    return 0;
}
```
**代码解释**
- `mysql_init`:初始化 MySQL 连接。
- `mysql_real_connect`:连接到指定的 MySQL 数据库。
- `mysql_query`:执行 SQL 语句。
- `mysql_store_result`:获取查询结果集。
- `mysql_fetch_row`:逐行获取结果集中的数据。
- `mysql_free_result`:释放结果集占用的内存。
- `mysql_close`:关闭 MySQL 连接。
 
#### 2. 使用 ADO(ActiveX Data Objects)
ADO 是微软提供的一种数据访问技术,可用于连接多种数据库,包括 MySQL。不过在 Linux 环境下使用 ADO 不太方便,一般在 Windows 环境中结合 Visual Studio 开发。以下是一个简单示例:
```cpp
#include <iostream>
#import "C:\Program Files\Common Files\System\ado\msado15.dll" no_namespace rename("EOF", "EndOfFile")
 
int main() {
    CoInitialize(nullptr);
    _ConnectionPtr conn(__uuidof(Connection));
    _RecordsetPtr rs(__uuidof(Recordset));
 
    try {
        // 连接到 MySQL 数据库
        conn->Open("Provider=MSDASQL;Driver={MySQL ODBC 8.0 Unicode Driver};Server=localhost;Database=database_name;User=user;Password=password;", "", "", adConnectUnspecified);
 
        // 插入数据
        conn->Execute("INSERT INTO users (name, age) VALUES ('John', 25)", nullptr, adCmdText);
 
        // 查询数据
        rs = conn->Execute("SELECT * FROM users", nullptr, adCmdText);
        while (!rs->EndOfFile) {
            std::cout << (const char*)_bstr_t(rs->Fields->GetItem("name")->Value) << " "
                      << (long)rs->Fields->GetItem("age")->Value << std::endl;
            rs->MoveNext();
        }
 
        // 更新数据
        conn->Execute("UPDATE users SET age = 26 WHERE name = 'John'", nullptr, adCmdText);
 
        // 删除数据
        conn->Execute("DELETE FROM users WHERE name = 'John'", nullptr, adCmdText);
 
        // 调用存储过程
        conn->Execute("EXEC your_stored_procedure", nullptr, adCmdText);
 
        // 关闭连接
        rs->Close();
        conn->Close();
    } catch (_com_error& e) {
        std::cerr << "错误信息: " << (const char*)e.Description() << std::endl;
    }
 
    CoUninitialize();
    return 0;
}
```
**代码解释**
- `CoInitialize`:初始化 COM 库。
- `_ConnectionPtr` 和 `_RecordsetPtr`:用于创建连接对象和记录集对象。
- `conn->Open`:打开数据库连接。
- `conn->Execute`:执行 SQL 语句。
- `CoUninitialize`:释放 COM 库资源。
 
### 连接并操作 Oracle 数据库
 
#### 1. 使用 OCI(Oracle Call Interface)
OCI 是 Oracle 提供的底层 C/C++ 接口,用于与 Oracle 数据库进行交互。
 
**示例代码**
```cpp
#include <iostream>
#include <oci.h>
 
int main() {
    OCIEnv *envhp;
    OCIError *errhp;
    OCISvcCtx *svchp;
    OCIStmt *stmthp;
    OCIDefine *defnp;
    OCIBind *bndp;
    sword status;
 
    // 初始化 OCI 环境
    status = OCIEnvCreate(&envhp, OCI_DEFAULT, nullptr, nullptr, nullptr, nullptr, 0, nullptr);
    if (status != OCI_SUCCESS) {
        std::cerr << "初始化 OCI 环境失败" << std::endl;
        return 1;
    }
 
    // 创建错误句柄
    status = OCIHandleAlloc(envhp, (dvoid **)&errhp, OCI_HTYPE_ERROR, 0, nullptr);
    if (status != OCI_SUCCESS) {
        std::cerr << "创建错误句柄失败" << std::endl;
        OCIHandleFree(envhp, OCI_HTYPE_ENV);
        return 1;
    }
 
    // 连接到 Oracle 数据库
    status = OCILogon(envhp, errhp, &svchp, (text *)"user/password@database", strlen("user/password@database"));
    if (status != OCI_SUCCESS) {
        std::cerr << "连接到 Oracle 数据库失败" << std::endl;
        OCIHandleFree(errhp, OCI_HTYPE_ERROR);
        OCIHandleFree(envhp, OCI_HTYPE_ENV);
        return 1;
    }
 
    // 插入数据
    status = OCIHandleAlloc(envhp, (dvoid **)&stmthp, OCI_HTYPE_STMT, 0, nullptr);
    if (status != OCI_SUCCESS) {
        std::cerr << "创建语句句柄失败" << std::endl;
        OCILogoff(svchp, errhp);
        OCIHandleFree(errhp, OCI_HTYPE_ERROR);
        OCIHandleFree(envhp, OCI_HTYPE_ENV);
        return 1;
    }
    status = OCIStmtPrepare(stmthp, errhp, (text *)"INSERT INTO users (name, age) VALUES (:1, :2)", strlen("INSERT INTO users (name, age) VALUES (:1, :2)"), OCI_NTV_SYNTAX, OCI_DEFAULT);
    if (status != OCI_SUCCESS) {
        std::cerr << "准备插入语句失败" << std::endl;
        OCIHandleFree(stmthp, OCI_HTYPE_STMT);
        OCILogoff(svchp, errhp);
        OCIHandleFree(errhp, OCI_HTYPE_ERROR);
        OCIHandleFree(envhp, OCI_HTYPE_ENV);
        return 1;
    }
    // 绑定参数
    char name[] = "John";
    int age = 25;
    status = OCIBindByName(stmthp, &bndp, errhp, (text *)":1", -1, name, sizeof(name), SQLT_STR, nullptr, nullptr, nullptr, 0, nullptr, OCI_DEFAULT);
    if (status != OCI_SUCCESS) {
        std::cerr << "绑定参数 1 失败" << std::endl;
        OCIHandleFree(stmthp, OCI_HTYPE_STMT);
        OCILogoff(svchp, errhp);
        OCIHandleFree(errhp, OCI_HTYPE_ERROR);
        OCIHandleFree(envhp, OCI_HTYPE_ENV);
        return 1;
    }
    status = OCIBindByName(stmthp, &bndp, errhp, (text *)":2", -1, &age, sizeof(age), SQLT_INT, nullptr, nullptr, nullptr, 0, nullptr, OCI_DEFAULT);
    if (status != OCI_SUCCESS) {
        std::cerr << "绑定参数 2 失败" << std::endl;
        OCIHandleFree(stmthp, OCI_HTYPE_STMT);
        OCILogoff(svchp, errhp);
        OCIHandleFree(errhp, OCI_HTYPE_ERROR);
        OCIHandleFree(envhp, OCI_HTYPE_ENV);
        return 1;
    }
    status = OCIStmtExecute(svchp, stmthp, errhp, 1, 0, nullptr, nullptr, OCI_DEFAULT);
    if (status != OCI_SUCCESS) {
        std::cerr << "执行插入语句失败" << std::endl;
        OCIHandleFree(stmthp, OCI_HTYPE_STMT);
        OCILogoff(svchp, errhp);
        OCIHandleFree(errhp, OCI_HTYPE_ERROR);
        OCIHandleFree(envhp, OCI_HTYPE_ENV);
        return 1;
    }
 
    // 查询数据
    status = OCIStmtPrepare(stmthp, errhp, (text *)"SELECT * FROM users", strlen("SELECT * FROM users"), OCI_NTV_SYNTAX, OCI_DEFAULT);
    if (status != OCI_SUCCESS) {
        std::cerr << "准备查询语句失败" << std::endl;
        OCIHandleFree(stmthp, OCI_HTYPE_STMT);
        OCILogoff(svchp, errhp);
        OCIHandleFree(errhp, OCI_HTYPE_ERROR);
        OCIHandleFree(envhp, OCI_HTYPE_ENV);
        return 1;
    }
    status = OCIStmtExecute(svchp, stmthp, errhp, 0, 0, nullptr, nullptr, OCI_DEFAULT);
    if (status != OCI_SUCCESS) {
        std::cerr << "执行查询语句失败" << std::endl;
        OCIHandleFree(stmthp, OCI_HTYPE_STMT);
        OCILogoff(svchp, errhp);
        OCIHandleFree(errhp, OCI_HTYPE_ERROR);
        OCIHandleFree(envhp, OCI_HTYPE_ENV);
        return 1;
    }
    // 定义列
    char resultName[50];
    int resultAge;
    status = OCIDefineByPos(stmthp, &defnp, errhp, 1, resultName, sizeof(resultName), SQLT_STR, nullptr, nullptr, nullptr, OCI_DEFAULT);
    if (status != OCI_SUCCESS) {
        std::cerr << "定义列 1 失败" << std::endl;
        OCIHandleFree(stmthp, OCI_HTYPE_STMT);
        OCILogoff(svchp, errhp);
        OCIHandleFree(errhp, OCI_HTYPE_ERROR);
        OCIHandleFree(envhp, OCI_HTYPE_ENV);
        return 1;
    }
    status = OCIDefineByPos(stmthp, &defnp, errhp, 2, &resultAge, sizeof(resultAge), SQLT_INT, nullptr, nullptr, nullptr, OCI_DEFAULT);
    if (status != OCI_SUCCESS) {
        std::cerr << "定义列 2 失败" << std::endl;
        OCIHandleFree(stmthp, OCI_HTYPE_STMT);
        OCILogoff(svchp, errhp);
        OCIHandleFree(errhp, OCI_HTYPE_ERROR);
        OCIHandleFree(envhp, OCI_HTYPE_ENV);
        return 1;
    }
    while ((status = OCIStmtFetch2(stmthp, errhp, 1, OCI_FETCH_NEXT, 0, OCI_DEFAULT)) == OCI_SUCCESS) {
        std::cout << resultName << " " << resultAge << std::endl;
    }
 
    // 更新数据、删除数据和调用存储过程的操作与插入、查询类似,这里省略
 
    // 释放资源
    OCIHandleFree(stmthp, OCI_HTYPE_STMT);
    OCILogoff(svchp, errhp);
    OCIHandleFree(errhp, OCI_HTYPE_ERROR);
    OCIHandleFree(envhp, OCI_HTYPE_ENV);
 
    return 0;
}
```
**代码解释**
- `OCIEnvCreate`:初始化 OCI 环境。
- `OCIHandleAlloc`:分配各种 OCI 句柄。
- `OCILogon`:连接到 Oracle 数据库。
- `OCIStmtPrepare`:准备 SQL 语句。
- `OCIBindByName`:绑定 SQL 语句中的参数。
- `OCIStmtExecute`:执行 SQL 语句。
- `OCIDefineByPos`:定义查询结果集中的列。
- `OCIStmtFetch2`:获取查询结果集中的数据。
- `OCILogoff`:断开与 Oracle 数据库的连接。
- `OCIHandleFree`:释放 OCI 句柄占用的资源。
 
#### 2. 使用 ADO 连接 Oracle
和连接 MySQL 类似,在 Windows 环境下可以使用 ADO 连接 Oracle 数据库,示例代码如下:
```cpp
#include <iostream>
#import "C:\Program Files\Common Files\System\ado\msado15.dll" no_namespace rename("EOF", "EndOfFile")
 
int main() {
    CoInitialize(nullptr);
    _ConnectionPtr conn(__uuidof(Connection));
    _RecordsetPtr rs(__uuidof(Recordset));
 
    try {
        // 连接到 Oracle 数据库
        conn->Open("Provider=OraOLEDB.Oracle;Data Source=database_name;User ID=user;Password=password;", "", "", adConnectUnspecified);
 
        // 插入数据
        conn->Execute("INSERT INTO users (name, age) VALUES ('John', 25)", nullptr, adCmdText);
 
        // 查询数据
        rs = conn->Execute("SELECT * FROM users", nullptr, adCmdText);
        while (!rs->EndOfFile) {
            std::cout << (const char*)_bstr_t(rs->Fields->GetItem("name")->Value) << " "
                      << (long)rs->Fields->GetItem("age")->Value << std::endl;
            rs->MoveNext();
        }
 
        // 更新数据
        conn->Execute("UPDATE users SET age = 26 WHERE name = 'John'", nullptr, adCmdText);
 
        // 删除数据
        conn->Execute("DELETE FROM users WHERE name = 'John'", nullptr, adCmdText);
 
        // 调用存储过程
        conn->Execute("EXEC your_stored_procedure", nullptr, adCmdText);
 
        // 关闭连接
        rs->Close();
        conn->Close();
    } catch (_com_error& e) {
        std::cerr << "错误信息: " << (const char*)e.Description() << std::endl;
    }
 
    CoUninitialize();
    return 0;
}
```
**代码解释**和使用 ADO 连接 MySQL 类似,主要区别在于连接字符串的不同。