-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathC++.txt
2211 lines (1740 loc) · 101 KB
/
C++.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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
From C++ Primer Plus
不包含17章
2021.11.7
cin.get()语句读取下一次击键
类,函数和变量是C++编译器的标准组件,它们都被放置在名称空间std中
这意味着iostream中定义的cout变量实际上是std::cout
仅当头文件没有扩展名h时情况如此
使用iostream.h时并非如此
using namespace std;
该编译指令使std名称空间中的所有名称都可用
更好的方法是只使所需的名称可用,using声明如下
using std::cout;
using std::endl;
using std::cin;
从而可以使用cin和cout而不必加上std::前缀
std::cout << "Come up and C++ me some time.";
<<表示该语句将把这个字符串发送给cout,该符号指出了信息流动的路径
cout是一个预定义的对象,知道如何显示字符,字符串和数字等
<<将其右侧的信息插入到流中
cout << endl;
endl表示重起一行,称为控制符,在iostream中定义,位于名称空间std中
可以使用\n换行,差别是endl确保程序刷新输出,而使用\n不能保证
cin为流入程序的对象,cin用>>运算符从输入流中抽取字符,在运算符右侧提供一个变量以接受抽取的信息
cout是一个ostream类对象,cin是一个istream类对象
C++初始化语法
int newinit(12);
int hamburgers = {24};
int emus{7};
下面两种将把变量初始化为0
int rocs={};
int psychics{};
用大括号初始化称为列表初始化,列表初始化禁止缩窄转换
缩窄转换
从浮点数转换为整数
从取值范围大的浮点数转换为取值范围小的浮点数(在编译期可以计算并且不会溢出的表达式除外)
从整数转换为浮点数(在编译期可以计算并且转换之后值不变的表达式除外)
从取值范围大的整数转换为取值范围小的整数(在编译期可以计算并且不会溢出的表达式除外)
hex,oct控制符,修改cout显示整数的方式
cout << std::hex
改为十六进制显示
如果使用了using编译指令,hex便不能作为变量名
成员函数cout.put()用于代替<<显示字符,因为在C++的Realease2.0之前,C++将字符常量储存为int,cout << 'M' 显示的是M的ASCII码,在之后的版本里,C++将字符常量存储为char,cout可以正确地处理字符常量了
通用字符名
\u后接8个十六进制位,\U后接16个十六进制位,表示ISO 10646码点
char默认情况下既不是有符号也不是无符号,取决于实现
因此可以使用signed char和unsigned char显式设置
wchar_t宽字符类型可以表示扩展字符集,cin和cout将输入输出看作char流,因此要使用wcin和wcout处理wchar_t流
前缀L指示宽字符常量和宽字符串
wchar_t bob = L'P';
wcout << L"tall";
C++使用前缀u表示char16_t类型,前缀U表示char32_t类型的字符常量和字符串常量
char16_t和\u00F6形式匹配,char32_t和\U0000222B形式匹配
char16_t ch1 = u'q';
C++中可以用const值声明数组长度
const值未初始化将是不确定的,且无法修改
在初始化中,使用auto而不指定变量类型,将把变量类型设置成与初始值相同
auto x = 3.25
cin.getline()读取一行输入直到换行符,第一个参数是数组名,第二个参数是包括空字符在内的要读取的字符数,并丢弃换行符
cin.get()读取一行输入直到换行符,第一个参数是数组名,第二个参数是包括空字符在内的要读取的字符数,但把换行符留在输入队列
连续调用cin.get()时,如果不借助帮助,cin.get()将不能跨过此换行符
但cin.get()不接受参数时读取下一个字符,因此可以用它处理换行符
cin.get(name, ArSize).get()
cin.get(name, ArSize)返回一个cin对象,该对象随后被用来调用cin()
同样,可以使用下面拼接方式把输入中连续两行读入两个数组中
cin.getline(name, ArSize).getline(name2, ArSize)
如果使用get(),通过查看下一个输入字符是否是换行符
可以看出是已读取了整行还是数组已被填满
getline()使用起来简单一些,但get()使得检査错误更简单些
如果get()读取到空行,将设置失效位,阻断接下来的输入,可以用下面的命令恢复
cin.clear();
如果输入字符串比指定的长,getline()会设置失效位并关闭输入
表达式cin >> year返回cin对象,因此可以
(cin >> year).get(ch);
可以使用C风格字符串初始化string对象
可以使用cin和cout操作string对象
可以使用数组表示法访问string对象中的字符
char str[] = {"abc"};
char str[] {"abc"};
string str = {"abc"};
string str {"abc"};
可以将一个string对象赋给一个string对象,而显然数组不可以
使用+运算符合并两个string对象,+=将字符串附加到string对象的末尾
string类位于string头文件中,cstring头文件提供了C风格字符串函数
int len1 = str1.size()
int len2 = strlen(str2)
strlen()读取到第一个空字符为止,因此对未初始化的字符数组使用将得到不确定的值
未初始化的string对象的长度为0
istream类中没有处理string对象的类方法,因此使用函数getline()
getline(cin, str);
对字符串字面量,使用前缀L、u、U创建为wchar_t、char16_t、char32_t型
使用前缀u8创建为UTF-8字符串
在raw字符串中,字符串表示的就是自己,\n不表示换行符
原始字符串不使用"界定字符串的首尾,使用"( 和 )"作为定界符
使用前缀R表示原始字符串
cout << R"(abc)" << '\n'
输入原始字符串时,按回车键不仅会移到下一行,还将在字符串中添加回车符
如果要在原始字符串中包含)",原始字符串允许在"和(之间包含其它字符,并在)和"之间直接也如此做,以此界定字符串的首尾
cout << R"+*("()")+*" << endl;
结果为
"()"
自定义定界符时,可以添加任意数量的基本字符,但空格、左右括号、斜杠和控制字符如换行符除外
R和u、U等其他字符串前缀可结合使用,顺序没有影响
在C++中,声明结构变量时允许省略关键字struct
struct inflatable{
char name[20];
float volume;
};
inflatable hat;
结构声明位于外部可以被其后面的任何函数使用,内部声明只能被该声明所属的函数使用
外部变量由所有函数共享,不提倡使用外部变量,但提倡使用外部结构声明和外部声明符号常量
结构初始化的=同样是可选的,大括号内未包含任何东西,各成员将被设置为0
同样,不允许缩窄转换
结构可以将string对象作为成员,但一定要让结构定义能够访问名称空间std
为此,可以将using指令移到结构定义之前,也可以将name的定义声明为std::string
struct inflatable{
std::string name;
};
可以同时完成结构的定义和创建结构变量,只需将变量名放在结束花括号之后
struct perks{
int key_number;
}mr_smith, ms_jones;
可以初始化以这种方式创建的变量
struct perks{
int key_number;
}mr_smith = {7};
可以声明没有名称的结构类型
struct{
int x;
}position;
这将创建一个名为position的结构变量,但以后无法创建这种类型的变量
C++结构除了成员变量之外,还可以有成员函数,即方法
结构中的位字段
字段的类型应为整型或枚举型,接下来是冒号,冒号后为使用的位数
可以使用没有名称的字段提供间距
struct torgle_register{
unsigned int SN : 4;
unsigned int : 4;
bool goodIn : 1;
}
可以像通常那样初始化位字段,可以使用标准的结构表示法访问位字段
共用体也可以省略union
enum spectrum{red, orange, yellow};
让spectrum成为新类型的名称,像struct变量被称为结构一样
red, orange, yellow作为符号常量,对应整数值0 1 2
使用枚举名声明这种类型的变量
spectrum band;
只能将定义枚举时使用的枚举量赋给枚举变量
枚举只定义了赋值运算符,没有算术运算
不能将整数值赋给枚举变量
red是spectrum类型
可以将int值强制类型转换为枚举量
band = spectrum(3);
但必须在枚举量的取值范围内,否则为未定义
band = spectrum(40003); //未定义
可以省略枚举类型的名称
enum{red, oragne, yellow};
枚举量的取值范围
上限为大于枚举量最大值的最小的2的幂减1,如枚举量101,上限为127
如果枚举量最小值不小于0,则下限为0
如果枚举量最小值小于0,下限和上限计算相同,不过要加上负号
如枚举量-6,下限为-7
指针不是整型,不能将整数赋给指针,但可以使用强制类型转换如此做
int* p;
p = 0xB8000000 //不行
p = (int*)0xB8000000 //可以
new运算符
int* pn = new int;
new分配内存,返回一个地址
变量的值存储在栈stack中,而new从堆heap或者自由存储区free store中分配内存
delete运算符
和new配对使用,释放new申请的内存,作用于指针
delete pn;
如果delete已经释放的内存,结果是未定义的
不能使用delete释放声明变量获得的内存
new创建动态数组
int* psome = new int[10];
delete释放动态数组
delete[] psome;
如果使用new[]为一个数组分配内存,则使用delete[]
如果使用new为一个实体(single entity)分配内存,使用delete
对NULL使用delete是可以的,但什么也不会发生
不能使用sizeof确定动态数组的字节数
对数组名使用sizeof得到的是数组长度,对指针使用得到的是指针的长度,即使指针指向一个数组
对数组名使用&运算符,得到的是整个数组的地址
int a[10];
cout << a << endl;
cout << &a << endl;
这两个值相同,但a+1将把地址增加4,而&a+1会把地址增加40
&a的类型是int(*)[10],它指向包含10个元素的int数组
可以声明这样的指针
int (*pa)[10] = &a;
*pa与a等价,(*pa)[0]为数组a的第一个元素
自动变量存储在stack中,stack是后进先出的
heap是C语言和操作系统的术语
heap是操作系统所维护的一块特殊内存,它提供了动态分配的功能,当运行程序调用malloc()时从中分配,调用free()时把内存交还
free store是C++中通过new与delete动态分配和释放对象的抽象概念
大多数时候,编译器使用malloc()和free()完成new和delete的工作
因此free store有时和heap是重叠的
auto可以推断出正确的类型
const antarctiac_years_end ** ppa = arp;
auto ppb = arp;
模板类vector
位于头文件vector中,且包含于名称空间std中
vector<int> vi; //创建一个大小为0的int数组
int n = 5;
vector<double> vd[n]; //创建一个大小为n的double数组
vi是一个vector<int>对象,vd[i]是一个vector<double>对象
模板类array
位于头文件array中,且包含于名称空间std中
array对象的长度是固定的,使用stack而不是free store
array<int, 5> ai; //创建一个大小为5的int数组
array<double, 4> ad = {1.2, 2.1, 3.1, 1.3};
和数组一样,大小不能是变量
数组、vector对象和array对象均可以用标准数组表示法访问各个元素
vector对象存储在free store中,而数组和array对象存储在stack中
可以将array对象赋给另一个array对象,前提是大小相等
int a1[4];
a1[-2] = 0; //等价于*(a1-2) = 0;
C++不检查越界错误,对于array对象,可以继续编写不安全的代码,或者使用成员函数at()
array<int, 4> a2;
a2.at(1) = 0; //a2[1] = 0
使用at()时,将在运行期间捕获非法索引,并默认中断程序
成员函数begin()和end()可以确定边界
for的测试表达式结果将被强制转换为bool型
cout在显示bool值之前转换为int,但cout.setf(ios::boolalpha)调用设置了一个标记,标记命令cout显示true和false
for的规则为
for(for-init-statement condition; expression)
因为statement有自己的分号,这时可以在初始化部分声明变量而无需借助"声明语句表达式"的概念
完整表达式是一个序列点
序列点的概念难以用于讨论多线程,故C++11使用术语"顺序"
string对象不使用空字符来标识字符串的末尾
clock()返回程序执行后所用的系统时间,在ctime头文件中
CLOCKS_PER_SEC等于每秒包含的系统时间单位数
类型为clock_t
for遍历数组或容器类
double prices[5] = {4.9, 10.3, 6.8, 7.4};
for(double x : prices)
cout << x << std::endl
如果要修改数组的元素,需要使用不同的语法
for(double &x : prices)
x = x*0.8;
&表明x是一个引用变量
可以直接使用复合字面量
for(int x : {1, 2, 3, 4, 5})
cout << x;
用cin读取char值时,cin将忽略空格和换行符
cin.get(ch)
要修改ch的值,只需要函数将参数声明为引用类型
当cin出现在需要bool值的地方时,将调用一个转换函数将cin对象转换成bool值,如果最后一次读取成功,则转换的bool值为true,否则为false
while(cin)
由于cin.get(char)的返回值为cin,可以精简为
while(cin.get(ch))
类似getchar(),不接受参数的cin.get()返回输入中的下一个字符
ch = cin.get();
同样,cout.put()的工作方式类似putchar()
cout.put(ch);
遇到EOF时,cin.get()将返回EOF,EOF在iostream中定义,通常为-1
cin.get(char)在到达EOF时,不会将一个值赋给ch
cin无法识别枚举类型,因此要求输入一个整数,在switch语句中将枚举量提升为int再进行比较
同样,在while测试条件中也是如此
在使用cin读取到char数组时,将在遇到空白时自动在末尾添加空字符
使用cin.getline(word, num)时,将换行符丢弃,自动在末尾添加空字符,输入队列的下一个字符是下一行的第一个字符
ostream.setf()方法可以设置各种格式化状态
文件输出需要头文件fstream,内含用于处理输出的ofstream类和ofstream对象,同样,在名称空间std中
要将ofstream对象和文件关联,方法之一是使用open()方法,结束后应使用方法close()将文件关闭
虽然iostream提供了预先定义好的名为cout的ostream对象,但需要自己声明一个ofstream对象并命名,将其与文件关联起来
ofstream outFile;
outFile.open("fish.txt");
当声明了一个ofstream对象并将其同文件关联起来后,可以对其使用所有用于cout的操作和方法
outFile << 15;
outFile << "abc" << endl;
默认情况下,open()将把原文件的长度截为0
fstream类成员函数open()的原型
void open(const char* filename, int mode, int access);
mode是要打开文件的方式
access是打开文件的属性
打开文件的方式在类ios(是所有流式I/O类的基类)中定义.
常用的值如下:
ios::app: 以追加的方式打开文件
ios::ate: 文件打开后定位到文件尾,ios:app就包含有此属性
ios::binary: 以二进制方式打开文件,缺省的方式是文本方式。两种方式的区别见前文
ios::in: 文件以输入方式打开(文件数据输入到内存)
ios::out: 文件以输出方式打开(内存数据输出到文件)
ios::nocreate: 不建立文件,所以文件不存在时打开失败
ios::noreplace:不覆盖文件,所以打开文件时如果文件存在失败
ios::trunc: 如果文件存在,把文件长度设为0
可以用或把以上属性连接起来,如ios::out | ios::binary
ifstream和ofstream类似,必须自己声明一个ifstream对象,并与文件关联
可以使用get()方法读取一个字符或者使用getline()方法读取一行字符
当ifstream对象本身被用作测试条件时,如果最后一个读取操作成功,将被转换为true
如果试图打开一个不存在的文件用于输入,将导致后面使用ifstream对象进行输入时失败
检查文件是否被成功打开可以使用方法is_open()
if(!inFile.is_open())
exit(EXIT_FAILURE);
函数exit()在cstdlib中定义
如果最后一次读取数据时遇到了EOF,eof()方法将返回true
如果遇到了EOF或者类型不匹配,fail()方法将返回true
如果文件受损或硬件故障,bad()方法将返回true
在没有任何错误时,good()方法返回true
inFile >> value的结果为inFile,在需要一个bool值的情况下,inFile的结果为inFile.good()
因此可以写作
while(inFile >> value){}
返回值不能是数组,但可以是对象,因此可以将数组作为结构或对象的组成部分来返回
C++在不指定参数列表时应使用省略号...
void say(...);
一般仅当与接受可变参数的C函数如printf()交互时才需要这样做
argument表示实参,parameter表示形参
可以用数组的首地址和尾地址标识数组长度,即指定元素区间
first, end分别为a, a+sizeof(&a)时,注意end指针指向数组最后一个元素的后一个位置
字符串地址是首字符的地址
带array模板类参数的函数原型
void show(std::array<double, 4> da);
void fill(std::array<double,4> *pa);
在C中,main()可以调用自己,但在C++中不允许
函数名即函数的地址
声明函数指针应包括函数类型和函数参数类型
double pam(int);
double (*pf)(int);
pf是一个函数指针,指向一个double类型,且有一个int参数的函数
用*pf替换pam,*pf是一个函数
在调用函数时,函数指针pf和(*pf)等价
double pam(int);
pf = pam; //因此应该像使用pam那样使用pf
double (*pf)(int); //因此应该像使用pam那样使用(*pf)
在复杂的函数指针声明中可使用自动类型推断
auto p1 = f1;
然而,auto只能用于单值初始化,不能用于初始化列表
const double * (*pa[3])(const double *, int) = {f1, f2, f3};
但声明数组pa后,可以声明同样类型的数组
auto pb = pa;
且pa、pb均为指向函数指针的指针
可以继续声明指向函数指针数组的指针
auto pc = &pa;
pc指向一个数组,数组里有3个函数指针,每个函数指针指向一个返回值为const double *,参数为const double *和int的函数
可以写出自己的声明
const double *(*(*pd)[3])(const double *, int) = &pa
函数返回第一个参数
既然pd指向数组,那么*pd就是数组,(*pd)[i]是数组中的元素,即函数指针
函数调用如下
(*pd)[i](av, 3);
(*(*pd)[i])(av, 3);
返回的指针指向的值如下
*(*pd)[i](av, 3)
*(*(*pd)[i])(av, 3)
内联函数
在函数声明和定义前加上关键字inline
执行内联函数时,编译器使用函数代码替换了函数调用,提高运行速度,但需要更多的内存
内联函数通常省略原型,前置函数定义
请求将函数作为内联函数时,编译器不一定会满足该要求
内联函数不能递归
内联函数往往只有一行
inline double square(double x){ return x * x; }
引用是已定义的变量的别名,引用变量是一种复合类型
将引用变量作为参数,函数将使用原始数据而不是其副本
int rats;
int& rodents = rats;
int&是指向int的引用
引用必须在声明时初始化,不能先声明再赋值,更接近const指针
int& rodents = rats;
int* const pr = &rats;
rodents相当于*pr
在函数形参中使用引用,实质是在函数调用时使用实参初始化形参,而形参是一个引用变量,从而,在这个函数中形参成为了实参的一个别名
常量引用
int cube(const int& a);
如果试图改变实参的值,将生成错误消息
然而,如果参数是基本数据类型,一般来说应该采用按值传递的方式
在传递的参数比较大的时候,如结构和类,传递const引用会很有用
引用是一个变量的别名,因此如果采用按引用传递参数的方式,不能像下面那样
cube(15);
cube(x+15);
这相当于
int& a = 15;
int& a = x+15;
但不总是这样,当引用参数是const时,情况有所不同
如果实参与引用参数不匹配,但引用参数是const,且是以下两种情况,C++将生成临时变量
1. 实参的类型正确,但不是左值
2. 实参的类型不正确,但可以转换为正确的类型
左值是可被引用的数据对象,如变量,数组元素,结构成员,引用,以及解引用的指针都是左值
非左值包括字面常量,用引号括起的字符串字面量除外,因为它们由其地址表示
1的情况和下面类似
int edge = 5;
int& edg = edge+1;
不能为edge+1创建引用,edge+1不是一个变量
2的情况和下面类似
long edge = 5L;
int& edg = edge;
不能为edge创建引用,因为类型不正确,int引用不能指向long
在以上情况下,编译器将生成一个临时匿名变量,并让edg指向它,这些临时变量只在函数调用期间存在
void swapr(int& a, int& b){
int c = a;
a = b;
b = c;
}
long m = 1, n = 2;
swapr(a, b);
由于类型不匹配,编译器将创建两个临时的int变量,并初始化为1和2,然后交换这两个临时变量,而m和n保持不变
对于const引用,由于在函数调用期间禁止改变形参,如果只是为了在函数中使用传递的值而非修改它们,临时变量不会造成不利影响
也就是说,对于形参为const引用的函数,如果实参不匹配,其行为类似于按值传递
&&右值引用
double&& rref = std::sqrt(36.00);
double j = 15.0;
double&& jrref = 2.0*j+18.5;
ref的值为6.0,jref的值为48.5
返回引用
int& accumulate(int& a, int b);
accumulate(accumulate(team, one), two);
等价于
accumulate(team, one);
accumulate(team, two);
返回引用还可以将值赋给函数调用,因为函数的返回值是一个引用
accumulate(team, three) = four;
相当于
accumulate(team, three);
team = four;
第二条语句消除了第一条语句所做的工作
返回值时将把值复制到一个临时位置,再进行赋值
double a = sqrt(16.0);
返回引用时将直接把team赋值给dup,效率更高
dup = accumulate(team, five);
返回引用时避免返回指向临时变量的引用,正如避免返回指向临时变量的指针
const free_throws & clone(free_throws& ft){
free_throws* pt = new free_throws;
*pt = ft;
return *pt;
}
关于返回引用的函数,从表面上看应该返回new创建的新变量,但函数声明表明,实际返回的是这个结构*pt的引用
该函数隐藏了对new的调用,注意不要忘记使用delete释放内存
将const用于引用返回类型,避免要使用引用返回值的时候不慎给函数调用赋值
通常将类对象的引用传递给函数
ostream类是基类,ofstream类是派生类
基类引用可以指向派生类对象,无需进行强制类型转换
一个效果是,定义一个接受基类引用参数的函数,调用该函数时,可以将派生类对象作为参数
参数类型为ostream&的函数可以接受ostream对象或ofstream对象作为参数
默认参数
char* left(const char* str, int n = 1);
n的默认值为1
必须从右向左添加默认值,如果要为某个参数设置默认值,必须为它右边的所有参数提供默认值
调用函数时,实参从左到右给形参初始化,不能跳过任何参数,如
beeps = harpo(3, ,8);
这并不能使第二个参数为默认值,调用是无效的
注意,函数定义不变,仅有函数原型指定了默认值
函数重载
函数的特征标相同,即函数参数的数目和对应类型相同,且参数的排列顺序相同,参数名无关紧要
使用重载函数时应使用正确的参数,如果参数类型不对,C++会尝试使用强制类型转换进行匹配,但这要求只有唯一的"合适的"函数原型
如果参数通过强制类型转换可以与多个函数原型相匹配,这种函数调用将被视为错误
有一些看上去不一样的特征标不能共存,如
double cube(double x);
double cube(double& x);
当调用cube(x)时,参数x与两个原型都匹配,编译器无法确定应使用哪个原型
因此,在检查特征标时,类型引用和类型本身被视为同一个特征标
匹配函数时,编译器会根据const变量做出选择
void dribble(char* bits);
void dribble(const char* cbits); //重载
函数重载的关键是特征标而非函数原型
long gronk(int n, float m);
double gronk(int n,float m);
调用函数时编译器无法确定应使用哪个原型
当重载函数使用引用参数时,调用函数将调用最匹配的版本
void stove(double& r1);
void stove(const double& r2);
void stove(double &&r3);
double x = 1.0;
const double y = 2.0;
stove(x); //r1
stove(y); //r2
stove(x+y); //r3
由于const左值引用可以接受右值参数,在没有第三个版本的情况下,stove(x+y)调用第二个版本
有时可以用默认参数代替两个重载函数,但当参数的类型不同时,默认参数便不管用了
名称修饰,即编译器内部跟踪重载函数时,给各重载函数名加密
函数模板
template <typename AnyType>
void Swap(AnyType &a, AnyType &b)
{
AnyType temp;
temp = a;
a = b;
b = temp;
}
第一行表明要建立一个模板,并将类型命名为AnyType
这里关键字template和typename是必须的,除非可以使用class代替typename,另外,必须使用尖括号
类型名可以任意选择
余下的代码描述了交换两个AnyType值的算法
模板不创建函数,只是告诉编译器如何定义函数
在C++98之前,使用class代替尚未添加的关键字typename
template <class AnyType>
模板函数的原型
template <typename T>
void Swap(T& a, T& b);
更常见的情形是将原型放在头文件中
也许不是所有的类型都使用相同的算法,可以像重载函数一样重载模板,被重载的模板的函数特征标必须不同
并非所有的模板参数都必须是模板参数类型,也可以是具体类型
template <typename T>
void Swap(T& a, T& b);
template <typename T>
void Swap(T* a, T* b, int n); //模板重载
模板函数假定可执行哪些操作,但地址值相乘或者结构体比较大小是不成立的,一种方式是运算符重载,另一种方式是为特定类型提供具体化的模板定义
显式具体化
template <>
void Swap<job>(job&, job&);
Swap<job>中,<job>是可选的,即可以写成
template <>
void Swap(job&, job&);
对于任一函数名,可以有非模板函数,模板函数,显式具体化模板函数及它们的重载
显式具体化原型和定义应以template<>开头,并通过名称指出类型
非模板函数优先级高于具体化,具体化优先级高于模板
显式实例化
可以直接命令编译器创建特定的实例,如Swap<int>,而不需要等待程序调用Swap(),语法如下,用<>指示类型,并加上关键字template
template void Swap<int>(int, int);
注意显式具体化和显式实例化的区别,显式具体化在template后加上<>
显式具体化的意思是,不要使用Swap()模板生成函数定义,而应使用专为int显式定义的函数定义
显式实例化的意思是,使用Swap()模板生成一个使用int类型的实例
不允许在同一个文件或转换单元中使用同一类型的显式实例和显式具体化
函数调用创建显式实例化
template <class T>
T Add(T a, T b) {return a + b};
int m =6;
double x = 10.2;
cout << Add<double>(x, m) << endl;
由于m和x的类型不同,函数调用Add(x, m)与模板不匹配,这里强制为double类型实例化,并将m强制类型转换为double类型,以便与Add<double>(double, double)匹配
对Swap()做类似的处理并不管用,因为虽然Swap<double>(x, m)生成了一个显式实例化,形参类型为double&,不能指向int变量m
重载解析
当遇到函数调用时,编译器进行下面三步操作
1. 创建候选函数列表,表中包含名称相同的函数和模板函数
2. 创建可行函数列表,表中都是参数数目正确的函数,其中有实参类型与形参类型完全匹配的情况,也包括隐式转换序列,如使用float实参的函数调用可以将参数转换为double,从而与double形参匹配
3. 确定最佳的可行函数,如果有,则使用它,否则该函数调用出错
在确定可行函数时,最佳到最差的顺序如下
完全匹配,此时常规函数优先于模板
提升转换,如char和short自动转换为int,float自动转换为double
标准转换,如int转换为char,long转换为double
用户自定义转换,如类声明中定义的转换
完全匹配允许某些无关紧要的转换,如int实参与int&形参完全匹配,反之也成立,此外还包括函数名与函数指针,只要它们的返回类型和参数列表相同,就是匹配的
Type类型的实参与const Type,volatile Type类型的形参都是完全匹配的
Type*类型的实参与const Type*,volatile Type*类型的形参都是完全匹配的
通常,有两个函数完全匹配是错误,但有两个例外
一种情况是,是非const指针和非const引用优先与非const参数匹配,尽管存在可以完全匹配的const参数,然而,只限于指针和引用,对于其他变量而言,出现const和非const两种完全匹配的函数是二义性错误
另一种情况是,非模板函数与模板函数都完全匹配,使用非模板函数
在模板函数之间,显式具体化模板优先于隐式具体化模板
同为显式具体化模板或隐式具体化模板,其中较具体的优先,这里的具体指编译器推断使用哪种类型是执行的转换最少,如
template<class Type>void recycle(Type t);
template<class Type>void recycle(Type* t);
struct blot ink = {25, "spots"};
recycle(&ink);
函数调用与这两个模板都匹配,第一个模板将Type解释为blot*,实例为recycle<blot*>(blot*),第二个模板将Type解释为blot,实例为recycle<blot>(blot*),这两个均为可行函数,此时,后者被认为是更具体的,因为后者的参数指出Type是一个指针,可以直接用blot标识Type,前者则必须将Type解释为指向blot的指针
也就是说,第二个模板中,Type已经被具体化为指针,因此说它更具体
同样,对于一个数组类型,如果假定了数组元素的类型,则被认为是更具体的
当使用函数调用进行模板的显式实例化时,尽管有匹配的非模板函数,仍然选择模板函数
cout << lesser<>(m, n) << endl;
上面lesser<>(m, n)中的<>指出,编译器应选择模板函数
带多个参数的函数的匹配,一个函数要比其他函数都更合适,要求其所有参数的匹配程度都必须不比其他函数差,同时至少有一个参数的匹配程度比其他函数都高
在C++98中,编写模板函数时,并非总能知道应在声明中使用哪种类型,原因之一是可能存在的强制类型转换
C++11的decltype关键字
int x;
decltype(x) y; //使y的类型与x相同
给decltype提供的参数可以是表达式
decltype(x+y) xpy = x+y;
实际上decltype要复杂些,为确定类型,编译器必须遍历一个核对表,简化如下
decltype(expression) var;
如果expression是一个不被括号括起的标识符,则var的类型与该标识符的类型相同,包括const等限定符
如果expression是一个函数调用,则var的类型与函数的返回类型相同,但并不会实际调用函数
如果expression是一个用括号括起的左值,则var为指向其类型的引用
如果前面的条件都不满足,则var的类型与expression的类型相同
C++11后置返回类型
decltype很好用,但无法解决函数返回值的问题,由于参数列表还未给出(在后面),无法使用decltype指定函数返回类型
double h(int x, float y);
使用后置返回类型可以写成这样
auto h(int x, float y) -> double;
这种语法也可用于函数定义
auto h(int x, float y) -> double
{/* function body */};
->double称为后置返回类型,auto为占位符
现在可以使用decltype指定函数返回类型
template<class T1, classT2>
auto gt(T1 x, T2 y) -> decltype(x+y)
{
...
return x+y;
}
不应将函数定义和变量声明放在头文件,头文件通常包含函数原型,#define或const定义的符号常量,结构声明,类声明,模板声明和内联函数
C++中将const用于外部变量,将设置该变量的链接性为内部链接
但可以用extern显式覆盖掉const对该变量的链接性的设置
extern const int states = 50;
必须同时在原型和函数定义中使用static,使函数的链接性为内部的
一个函数的所有内联定义必须都相同
如果用到C库函数,可以用函数原型指出要链接的函数
extern "C" void spiff(int);
下面两个则分别通过默认和显式指出使用C++语言链接性
extern void spiff(int);
extern "C++" void spiff(int);
new失败时引发异常std::bad_alloc
void* operator new(std::size_t)
是new调用的分配函数
void* operator new[](std::size_t)
是new[]调用的分配函数
void operator delete(void *)
是delete调用的释放函数
void operator delete[](void *)
是delete[]调用的释放函数
这些函数位于全局名称空间,std::size_t是一个typedef,对应于合适的整型
如
int* p = new int;
被转换为
int* p = new(sizeof(int));
而
int* pa = new int[40];
被转换为
int* pa = new(40 * sizeof(int));
这些函数是可替换的,可以定义作用域为类的替换函数,以满足该类的内存分配需求,在代码中使用new运算符时将调用自定义的new()函数
定位new特性,要包含头文件new
定位new除了需要指定参数外,句法与常规new运算符相同
char buffer1[50];
char buffer2[500];
p1 = new(buffer1) chaff; //chaff是一个struct
p2 = new(buffer2) int[20];
上述代码从buffer1中分配空间给chaff,从buffer2中分配空间给int数组
p3 = new(buffer1) chaff;
重复使用定位new分配同一个地址时,new不跟踪哪些单元已经被使用,例如p3和p1的地址是相同的,程序员应自己计算偏移量
另外,delete只能用于常规new分配的堆内存,上述的buffer1和buffer2位于静态内存或栈中,使用delete将引发运行阶段错误
默认定位new返回传递给new的地址,并强制转换为void*
标准定位new调用一个接收两个参数的new()函数
int* p1 = new int; //调用new(sizeof(int))
int* p2 = new(buffer) int; //调用new(sizeof(int), buffer)
int* p3 = new(buffer) int[40]; //调用new(40 * sizeof(int), buffer)
定位new函数不可替换,但可重载,至少需要两个参数,第一个总是std::size_t
名称空间可以是全局的或位于另一个名称空间中,但不能位于代码块中
默认情况下名称空间中声明的名称是外部链接的,除非它引用了常量
全局名称空间对应于文件级声明区域,全局变量位于全局名称空间中
namespace Jill{
char* goose(const char*);
}
上述语句将名称goose添加到Jill中
namespace Jack{
void fetch();
}
上述语句将名称fetch添加到Jack中,可以在该文件后面或者另一个文件中再次使用Jack名称空间提供fetch()函数的代码
作用域解析运算符::可以访问名称空间中的名称
未被装饰的名称称为未限定的名称
using声明和using编译指令可以简化名称空间中名称的使用
using声明使特定的标识符可用,using编译指令使整个名称空间可用
using Jill::goose;
using声明将特定的名称添加到声明所属的声明区域中,如下,main()中的using声明Jill::fetch将fetch添加到main()定义的声明区域中
char fetch;
int main(){
using Jill:fetch;
double fetch; //错误
std::cin >> fetch; //Jill::fetch
std::cin >> ::fetch; //global fetch
}
using namespace Jill;
将Jill中所有的名称添加到编译指令所属的声明区域中,但如果该声明区域存在名称空间内的名称,将隐藏名称空间名,就像局部变量隐藏同名的全局变量一样
嵌套名称空间
可以在名称空间里用using编译指令和using声明
将using编译指令用于外层名称空间,相当于同时用于内层的所有名称空间
可以给名称空间创建别名
namespace my_very_favorite_things{...};
namespace mvft = my_very_favorite_things;
可以用这种技术简化嵌套名称空间
namespace MEF = myth::elements::fire;
using MEF::flame;
省略名称空间的名称可以创建未命名的名称空间
namespace{
int ice;
int bandycoot;
}
这就像后面跟着using编译指令一样,即未命名名称空间的潜在作用域为从声明点到声明区域末尾(即文件末尾),但由于没有名称,无法在其他声明区域使用using指令或声明,这提供了内部链接的静态变量的替代品
定义位于类声明中的函数都自动成为内联函数
在类声明之外使用inline限定符可以定义内联成员函数
内联函数要求在每个使用它们的文件中都对其进行定义
构造函数和类同名
Stock first();
调用默认构造函数时,不要写圆括号,那会被认为是一个函数原型
Stock second;
Stock third(1, 2, 3);
可以用重载或默认参数的方式提供默认构造函数,只有当不存在非默认构造函数时,编译器才会提供默认构造函数
析构函数为类名前加~,且不带参数,析构函数的原型必须如下
~Stock();
构造函数和析构函数没有声明类型,没有返回值
要让方法无法修改对象,在方法的声明和定义后面加上const
void show() const;
void stock::show() const{}
这实际上是将this指针限定为const,从而不能使用this来修改对象的值
列表初始化
Stock first = {1, 2, 3};
Stock second{1, 2, 3};
Stock *p = new Stock{1, 2, 3};
如果构造函数只有一个参数,可以如下初始化
Stock first = Stock(1);
Stock second(2);
Stock third = 3;
如果构造函数使用了new,则必须提供使用delete的析构函数
对象数组的构造函数初始化
Stock stocks[3] = {
Stock(1, 2),
Stock(2, 3),
Stock(1, 3),
};
如果类包括多个构造函数,可以对不同的对象使用不同的构造函数
Stock stocks[5] = {
Stock(1, 2, 3),
Stock(2, 3),
Stock(),
};
只初始化了部分元素,剩下的2个元素将使用默认构造函数初始化
声明类只是描述了对象的形式,并没有创建对象,因此在类中没有存储值的空间,12不能被存储
class Stock{
private:
const int Months = 12;
double costs[Months];
}
C++11提供了成员初始化,但不适用于以上的数组声明
解决方法一是声明一个枚举,类声明中声明的枚举的作用域为整个类,可以用枚举作为整型常量作为类的符号名称
class Stock{
private:
enum {Months = 12};
double costs[Months];
}
这种方式声明枚举并不会创建类数据成员,即对象中不包含枚举,故不需要提供枚举名
ios_base类中应用了此种方法,fixed就是ios_base类中定义的枚举量
使用关键字static可以在类声明中创建常量
class Stock{
private:
static const int Months = 12;
double costs[Months];
}
Months将与其他静态变量存储在一起,而不是存储在对象中,该常量被所有对象共享
作用域内枚举
enum class egg{small, medium, large, jumbo};
可以用struct代替class
需要用枚举名限定枚举量
egg choice = egg::large;
常规枚举可以在表达式中隐式转换为整型,但作用域内枚举不行
常规枚举的底层整型是什么取决于实现,但作用域内枚举的底层类型为int
但可以重新指定为其他的类型,语法如下
enum class : short egg{small, medium};
:short将底层类型指定为short
运算符重载
Time operator+(Time b);
可以像调用普通方法那样调用operator+()
Time a, b;
a.operator+(b);
也可以用运算符表示法
a+b;
这种情况下,运算符左边的对象为调用对象,右边的对象被作为参数传递
运算符重载的限制
运算符重载要求至少有一个操作数是自定义类型的类对象
运算符重载后的操作数个数必须相同
不能创建新运算符
运算符重载后的结合性不变
运算符重载后的优先级不变
运算符重载函数不能有默认参数
不能重载下列运算符
sizeof运算符 sizeof()
成员运算符 .
成员指针运算符 .*
作用域解析运算符 ::
条件运算符 ?:
RTTI运算符 typeid
强制类型转换运算符 const_cast dynamic_cast reinterpret_cast static_cast
以下运算符只能通过成员函数重载
赋值运算符
函数调用运算符 ()
下标运算符 []
指针访问运算符 ->
//todo
C++17的std::invoke()
友元函数
将函数原型放在类声明中,并加上关键字friend
friend Time operator*(double m, Time& t);
友元函数不是类成员函数,不能通过成员运算符调用,也不要使用Time::限定符,但它与成员函数的访问权限相同
函数定义中不要使用friend
Time operator*(double m, Time& t){
Time result;
result.hours = t.hours;
return result;
}
通过成员函数,下面的语句
A = 2.75*B;
转换为
A = operator(2.75, B);
通过类成员函数重载运算符时,类成员函数只有一个参数,因为this指针是隐藏的"第一个参数"(实际上存放在ECX寄存器中,为最后一个入栈的参数)
重载<<
使用友元函数如下
void operator<<(ostream& os, const Time& t){
os << t.hours << " hours, " << t.minutes << " minutes";
}
这样可以使用下面的语句
cout << trip;
调用cout << trip应使用cout对象本身,而不是拷贝,因此函数按引用传递参数,os将成为cout的别名