-
Notifications
You must be signed in to change notification settings - Fork 605
/
C_primer_note
9594 lines (6855 loc) · 441 KB
/
C_primer_note
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
# 《C++ Primer中文版(第5版)》笔记
<!-- GFM-TOC -->
![Cover](Images/Cover.png)
## 目录
[第1章 开始](#1)
### 第I部分 C++基础
[第2章 变量和基本类型](#第2章)
[第3章 字符串、向量和数组](#第3章)
[第4章 表达式](#第4章)
[第5章 语句](第5章)
[第6章 函数](第6章)
[第7章 类](第7章)
### 第II部分 C++标准库
[第8章 IO库](第8章)
[第9章 顺序容器](第9章)
[第10章 泛型算法](第10章)
[第11章 关联容器](第11章)
[第12章 动态内存](第12章)
### 第III部分 类设计者的工具
[第13章 拷贝控制](第13章)
[第14章 操作重载与类型转换](第14章)
[第15章 面向对象程序设计](第15章)
[第16章 模板与泛型编程](第16章)
### 第IV部分 高级主题
[第17章 标准库特殊设施](第17章)
[第18章 用于大型程序的工具](第18章)
[第19章 特殊工具与技术](第19章)
<!-- GFM-TOC -->
# 第1章 开始
最简单的`main`函数:
```c++
int main()
{
return 0;
}
```
C++包含两种注释,注释界定符`/**/`通常用于多行注释,而双斜杠`//`通常用于单行或半行注释。
```c++
#include <iostream>
/*
* Simple main function:
* Read two numbers and write their sum
*/
int main()
{
int sum = 0, val = 1;
// keep executing the while as long as val is less than or equal to 10
while (val <= 10)
{
sum += val; // assigns sum + val to sum
++val; // add 1 to val
}
std::cout << "Sum of 1 to 10 inclusive is "<< sum << std::endl;
return 0;
}
```
# 第2章 变量和基本类型
## 基本内置类型(Primitive Built-in Types)
### 算数类型(Arithmetic Types)
算数类型分为两类:整型(integral type)、浮点型(floating-point type)。
![2-1](Images/2-1.png)
`bool`类型的取值是`true`或`false`。
一个`char`的大小和一个机器字节一样,确保可以存放机器基本字符集中任意字符对应的数字值。`wchar_t`确保可以存放机器最大扩展字符集中的任意一个字符。
在整型类型大小方面,C++规定`short` ≤ `int` ≤ `long` ≤ `long long`(`long long`是C++11定义的类型)。
浮点型可表示单精度(single-precision)、双精度(double-precision)和扩展精度(extended-precision)值,分别对应`float`、`double`和`long double`类型。
除去布尔型和扩展字符型,其他整型可以分为带符号(signed)和无符号(unsigned)两种。带符号类型可以表示正数、负数和0,无符号类型只能表示大于等于0的数值。类型`int`、`short`、`long`和`long long`都是带符号的,在类型名前面添加`unsigned`可以得到对应的无符号类型,如`unsigned int`。
字符型分为`char`、`signed char`和`unsigned char`三种,但是表现形式只有带符号和无符号两种。类型`char`和`signed char`并不一样, `char`的具体形式由编译器(compiler)决定。
如何选择算数类型:
- <font color='red'>当明确知晓数值不可能为负时,应该使用无符号类型。</font>
- <font color='red'>使用`int`执行整数运算</font>,如果数值超过了`int`的表示范围,应该使用`long long`类型。
- 在算数表达式中不要使用`char`和`bool`类型。<font color='red'>如果需要使用一个不大的整数,应该明确指定它的类型是`signed char`还是`unsigned char`。</font>
- 执行浮点数运算时<font color='red'>建议使用`double`类型。</font>
### 类型转换(Type Conversions)
进行类型转换时,类型所能表示的<font color='red'>值的范围决定了转换的过程</font>。
- 把非布尔类型的算术值<font color='red'>赋给布尔类型时</font>,初始值为0则结果为`false`,否则结果为`true`。
- 把布尔值<font color='red'>赋给非布尔类型时</font>,初始值为`false`则结果为0,初始值为`true`则结果为1。
- 把浮点数赋给整数类型时,进行近似处理,结果值仅保留浮点数中的整数部分。
- 把整数值赋给浮点类型时,小数部分记为0。如果该整数所占的空间超过了浮点类型的容量,精度可能有损失。
- <font color='red'>赋给无符号类型一个超出它表示范围的值时</font>,结果是初始值对无符号类型表示数值总数(8比特大小的`unsigned char`能表示的数值总数是256)<font color='red'>取模后的余数。</font>
- 赋给<font color='red'>带符号类型一个超出它表示范围的值时,结果是未定义的</font>(undefined)。
<font color='red'>避免无法预知和依赖于实现环境的行为。</font>
<font color='red'>无符号数不会小于0</font>这一事实关系到循环的写法。
```C++
// WRONG: u can never be less than 0; the condition will always succeed
for (unsigned u = 10; u >= 0; --u)
std::cout << u << std::endl;
```
当*u*等于0时,*--u*的结果将会是4294967295。<font color='red'>一种解决办法是用`while`语句来代替`for`语句,前者可以在输出变量前先减去1。</font>
```c++
unsigned u = 11; // start the loop one past the first element we want to print
while (u > 0)
{
--u; // decrement first, so that the last iteration will print 0
std::cout << u << std::endl;
}
```
<font color='red'>不要混用带符号类型和无符号类型。</font>
### 字面值常量(Literals)
以`0`开头的整数代表八进制(octal)数,以`0x`或`0X`开头的整数代表十六进制(hexadecimal)数。在C++14中,`0b`或`0B`开头的整数代表二进制(binary)数。
整型字面值具体的数据类型由它的值和符号决定。
C++14新增了单引号`'`形式的数字分隔符。<font color='red'>数字分隔符不会影响数字的值,但可以通过分隔符将数字分组,使数值读写更容易。</font>
```c++
// 按照书写形式,每3位分为一组
std::cout << 0B1'101; // 输出"13"
std::cout << 1'100'000; // 输出"1100000"
```
浮点型字面值默认是一个`double`。
由单引号括起来的一个字符称为`char`型字面值,双引号括起来的零个或多个字符称为字符串字面值。
<font color='red'>字符串字面值的类型是由常量字符构成的数组(array)</font>。编译器在每个字符串的<font color='red'>结尾处添加一个空字符</font>`'\0'`,因此字符串字面值的实际长度要比它的内容多一位。
转义序列:
| 含义 | 转义字符 |
| :-------------: | :------: |
| newline | `\n` |
| horizontal tab | `\t` |
| alert (bell) | `\a` |
| vertical tab | `\v` |
| backspace | `\b` |
| double quote | `\"` |
| backslash | `\\` |
| question mark | `\?` |
| single quote | `\'` |
| carriage return | `\r` |
| formfeed | `\f` |
```c++
std::cout << '\n'; // prints a newline
std::cout << "\tHi!\n"; // prints a tab followd by "Hi!" and a newline
```
泛化转义序列的形式是`\x`后紧跟1个或多个十六进制数字,或者`\`后紧跟1个、2个或3个八进制数字,其中数字部分表示字符对应的数值。如果`\`后面跟着的八进制数字超过3个,则只有前3个数字与`\`构成转义序列。相反,`\x`要用到后面跟着的所有数字。
```c++
std::cout << "Hi \x4dO\115!\n"; // prints Hi MOM! followed by a newline
std::cout << '\115' << '\n'; // prints M followed by a newline
```
<font color='red'>添加特定的前缀和后缀</font>,可以改变整型、浮点型和字符型字面值的默认类型。
![2-2](Images/2-2.png)
使用一个长整型字面值时,最好使用<font color='red'>大写字母</font>`L`进行标记,小写字母`l`和数字`1`容易混淆。
## 变量(Variables)
### 变量定义(Variable Definitions)
变量定义的基本形式:类型说明符(type specifier)后紧跟由一个或多个变量名组成的列表,其中变量名以逗号分隔,最后以分号结束。定义时可以为一个或多个变量赋初始值(初始化,initialization)。
初始化不等于赋值(assignment)。<font color='red'>初始化的含义是创建变量时赋予其一个初始值</font>,而<font color='red'>赋值的含义是把对象的当前值擦除,再用一个新值来替代。</font>
<font color='red'>用花括号初始化变量称为列表初始化</font>(list initialization)。当用于内置类型的变量时,如果使用了列表初始化并<font color='red'>且初始值存在丢失信息的风险</font>,则编译器会报错。
```c++
long double ld = 3.1415926536;
int a{ld}, b = {ld}; // error: narrowing conversion required
int c(ld), d = ld; // ok: but value will be truncated
```
如果定义变量时未指定初值,则变量被默认初始化(default initialized)。
对于<font color='red'>内置类型,定义于任何函数体之外的变量被初始化为0,函数体内部的变量将不被初始化</font>(uninitialized)。
定义于<font color='red'>函数体内</font>的内置类型对象如果<font color='red'>没有初始化,则其值未定义</font>,使用该类值是一种错误的编程行为且很难调试。类的对象如果没有显式初始化,则其值由类确定。
<font color='red'>建议初始化每一个内置类型的变量。</font>
### 变量声明和定义的关系(Variable Declarations and Definitions)
<font color='red'>声明(declaration)使得名字为程序所知</font>。一个文件如果想使用其他地方定义的名字,则必须先包含对那个名字的声明。
<font color='red'>定义(definition)负责创建与名字相关联的实体</font>。
<font color='red'>如果想声明一个变量而不定义它,就在变量名前添加关键字`extern`,并且不要显式地初始化变量。</font>
```c++
extern int i; // declares but does not define i
int j; // declares and defines j
```
`extern`语句如果包含了初始值就不再是声明了,而变成了定义。
<font color='red'>变量能且只能被定义一次,但是可以被声明多次。</font>
如果要在<font color='red'>多个文件中使用同一个变量,就必须将声明和定义分开</font>。此时变量的定义必须出现且只能出现在一个文件中,其他使用该变量的文件必须对其进行声明,但绝对不能重复定义。
### 标识符(Identifiers)
C++的标识符由字母、数字和下划线组成,其中必须以字母或下划线开头。标识符的长度没有限制,但是对大小写字母敏感。C++为标准库保留了一些名字。用户自定义的标识符<font color='red'>不能连续出现两个下划线</font>,<font color='red'>也不能以下划线紧连大写字母开头</font>。此外,定义在函数体外的标识符不能以下划线开头。
![2-3](Images/2-3.png)
### 名字的作用域(Scope of a Name)
<font color='red'>定义在函数体之外的名字拥有全局作用域</font>(global scope)。声明之后,该名字在整个程序范围内都可使用。
<font color='red'>最好在第一次使用变量时再去定义它。</font>这样做更容易找到变量的定义位置,并且也可以赋给它一个比较合理的初始值。
作用域中一旦声明了某个名字,在它所嵌套着的所有作用域中都能访问该名字。同时,允许在内层作用域中重新定义外层作用域已有的名字,<font color='red'>此时内层作用域中新定义的名字将屏蔽外层作用域的名字</font>。
可以用作用域操作符`::`来覆盖默认的作用域规则。因为全局作用域本身并没有名字,<font color='red'>所以当作用域操作符的左侧为空时,会向全局作用域发出请求获取作用域操作符右侧名字对应的变量。</font>
```c++
#include <iostream>
// Program for illustration purposes only: It is bad style for a function
// to use a global variable and also define a local variable with the same name
int reused = 42; // reused has global scope
int main()
{
int unique = 0; // unique has block scope
// output #1: uses global reused; prints 42 0
std::cout << reused << " " << unique << std::endl;
int reused = 0; // new, local object named reused hides global reused
// output #2: uses local reused; prints 0 0
std::cout << reused << " " << unique << std::endl;
// output #3: explicitly requests the global reused; prints 42 0
std::cout << ::reused << " " << unique << std::endl;
return 0;
}
```
如果函数有可能用到某个全局变量,则不宜再定义一个同名的局部变量。
## 复合类型(Compound Type)
### 引用(References)
引用为对象起了另外一个名字,引用类型引用(refers to)另外一种类型。通过将声明符写成`&d`的形式来定义引用类型,其中*d*是变量名称。
```c++
int ival = 1024;
int &refVal = ival; // refVal refers to (is another name for) ival
int &refVal2; // error: a reference must be initialized
```
<font color='red'>定义引用时,程序把引用和它的初始值绑定(bind)在一起</font>,而不是将初始值拷贝给引用。一旦初始化完成,将无法再令引用重新绑定到另一个对象,因此引用必须初始化。
<font color='red'>引用不是对象</font>,它只是为一个已经存在的对象<font color='red'>所起的另外一个名字</font>。
声明语句中引用的类型实际上被用于指定它所绑定的对象类型。大部分情况下,引用的类型要和与之绑定的对象严格匹配。
<font color='red'>引用只能绑定在对象上</font>,不能与字面值或某个表达式的计算结果绑定在一起。
### 指针(Pointer)
与引用类似,<font color='red'>指针也实现了对其他对象的间接访问。</font>
- <font color='red'>指针本身就是一个对象,允许对指针赋值和拷贝</font>,而且在生命周期内它可以先后指向不同的对象。
- <font color='red'>指针无须在定义时赋初值。</font>和其他内置类型一样,在块作用域内定义的指针如果没有被初始化,也将拥有一个不确定的值。
通过将声明符写成`*d`的形式来定义指针类型,其中*d*是变量名称。<font color='red'>如果在一条语句中定义了多个指针变量,则每个量前都必须有符号`*`。</font>
```c++
int *ip1, *ip2; // both ip1 and ip2 are pointers to int
double dp, *dp2; // dp2 is a pointer to double; dp is a double
```
<font color='red'>指针存放某个对象的地址</font>,要想获取对象的地址,需要使用<font color='red'>取地址符`&`</font>。
```c++
int ival = 42;
int *p = &ival; // p holds the address of ival; p is a pointer to ival
```
<font color='red'>因为引用不是对象</font>,没有实际地址,所以<font color='red'>不能定义指向引用的指针。</font>
声明语句中指针的类型实际上被用于指定它所指向的对象类型。大部分情况下,指针的类型要和它指向的对象严格匹配。
指针的值(即地址)应属于下列状态之一:
- <font color='red'>指向一个对象</font>。
- 指向紧邻对象所占空间的<font color='red'>下一个位置</font>。
- <font color='red'>空指针</font>,即指针没有指向任何对象。
- <font color='red'>无效指针</font>,即上述情况之外的其他值。
试图拷贝或以其他方式访问<font color='red'>无效指针的值都会引发错误</font>。
如果<font color='red'>指针指向一个对象</font>,可以使用解引用(dereference)符`*`来访问该对象。
```c++
int ival = 42;
int *p = &ival; // p holds the address of ival; p is a pointer to ival
cout << *p; // * yields the object to which p points; prints 42
```
<font color='red'>给解引用的结果赋值就是给指针所指向的对象赋值。</font>
<font color='red'>解引用操作仅适用于那些确实指向了某个对象的有效指针。</font>
空指针(null pointer)不指向任何对象,在试图使用一个指针前代码可以先检查它是否为空。得到空指针<font color='red'>最直接的办法是用字面值`nullptr`来初始化指针</font>。
旧版本程序通常使用`NULL`(预处理变量,定义于头文件*cstdlib*中,值为0)给指针赋值,但在C++11中,最好使用`nullptr`初始化空指针。
```c++
int *p1 = nullptr; // equivalent to int *p1 = 0;
int *p2 = 0; // directly initializes p2 from the literal constant 0
// must #include cstdlib
int *p3 = NULL; // equivalent to int *p3 = 0;
```
建议初始化所有指针。
<font color='red'>`void*`是一种特殊的指针类型,可以存放任意对象的地址,但不能直接操作`void*`指针所指的对象。</font>
### 理解复合类型的声明(Understanding Compound Type Declarations)
<font color='red'>指向指针的指针</font>(Pointers to Pointers):
```c++
int ival = 1024;
int *pi = &ival; // pi points to an int
int **ppi = π // ppi points to a pointer to an int
```
![2-4](Images/2-4.png)
<font color='red'>指向指针的引用</font>(References to Pointers):
```C++
int i = 42;
int *p; // p is a pointer to int
int *&r = p; // r is a reference to the pointer p
r = &i; // r refers to a pointer; assigning &i to r makes p point to i
*r = 0; // dereferencing r yields i, the object to which p points; changes i to 0
```
面对一条比较复杂的指针或引用的声明语句时,<font color='red'>从右向左阅读有助于弄清它的真实含义。 </font>
## const限定符(Const Qualifier)
在变量类型前添加关键字`const`可以创建值不能被改变的对象。<font color='red'>`const`变量必须被初始化。</font>
```c++
const int bufSize = 512; // input buffer size
bufSize = 512; // error: attempt to write to const object
```
<font color='red'>默认情况下,`const`对象被设定成仅在文件内有效。</font>当多个文件中出现了同名的`const`变量时,其实等同于在不同文件中分别定义了独立的变量。
如果想在<font color='red'>多个文件间共享`const`对象</font>:
- 若`const`对象的值在编译时已经确定,则应该<font color='red'>定义在头文件中</font>。其他源文件包含该头文件时,不会产生重复定义错误。
- <font color='red'>若`const`对象的值直到运行时才能确定,则应该在头文件中声明,在源文件中定义。</font>此时`const`变量的声明和定义前都应该添加`extern`关键字。
```c++
// file_1.cc defines and initializes a const that is accessible to other files
extern const int bufSize = fcn();
// file_1.h
extern const int bufSize; // same bufSize as defined in file_1.cc
```
### const的引用(References to const)
把引用绑定在`const`对象上即为对常量的引用(reference to const)。<font color='red'>对常量的引用不能被用作修改它所绑定的对象。</font>
```c++
const int ci = 1024;
const int &r1 = ci; // ok: both reference and underlying object are const
r1 = 42; // error: r1 is a reference to const
int &r2 = ci; // error: non const reference to a const object
```
大部分情况下,引用的类型要和与之绑定的对象严格匹配。但是有两个例外:
- <font color='red'>初始化常量引用时允许用任意表达式作为初始值</font>,只要该表达式的结果能转换成引用的类型即可。
```c++
int i = 42;
const int &r1 = i; // we can bind a const int& to a plain int object
const int &r2 = 42; // ok: r1 is a reference to const
const int &r3 = r1 * 2; // ok: r3 is a reference to const
int &r4 = r * 2; // error: r4 is a plain, non const reference
```
- <font color='red'>允许为一个常量引用绑定非常量的对象、字面值或者一般表达式。</font>
```c++
double dval = 3.14;
const int &ri = dval;
```
### 指针和const(Pointers and const)
<font color='red'>指向常量的指针(pointer to const)不能用于修改其所指向的对象。常量对象的地址只能使用指向常量的指针来存放,但是指向常量的指针可以指向一个非常量对象。</font>
```c++
const double pi = 3.14; // pi is const; its value may not be changed
double *ptr = π // error: ptr is a plain pointer
const double *cptr = π // ok: cptr may point to a double that is const
*cptr = 42; // error: cannot assign to *cptr
double dval = 3.14; // dval is a double; its value can be changed
cptr = &dval; // ok: but can't change dval through cptr
```
定义语句中把`*`放在`const`之前用来说明<font color='red'>指针本身是一个常量</font>,常量指针(const pointer)<font color='red'>必须初始化</font>。
```c++
int errNumb = 0;
int *const curErr = &errNumb; // curErr will always point to errNumb
const double pi = 3.14159;
const double *const pip = π // pip is a const pointer to a const object
```
<font color='red'>指针本身是常量并不代表不能通过指针修改其所指向的对象的值</font>,能否这样做完全依赖于其指向对象的类型。
### 顶层const(Top-Level const)
<font color='red'>顶层`const`表示指针本身是个常量,底层`const`(low-level const)表示指针所指的对象是一个常量。</font>指针类型既可以是顶层`const`也可以是底层`const`。
```c++
int i = 0;
int *const p1 = &i; // we can't change the value of p1; const is top-level
const int ci = 42; // we cannot change ci; const is top-level
const int *p2 = &ci; // we can change p2; const is low-level
const int *const p3 = p2; // right-most const is top-level, left-most is not
const int &r = ci; // const in reference types is always low-level
```
<font color='red'>当执行拷贝操作时</font>,常量是顶层`const`还是底层`const`区别明显:
- <font color='red'>顶层`const`没有影响。</font>拷贝操作不会改变被拷贝对象的值,因此拷入和拷出的对象是否是常量无关紧要。
```c++
i = ci; // ok: copying the value of ci; top-level const in ci is ignored
p2 = p3; // ok: pointed-to type matches; top-level const in p3 is ignored
```
- <font color='red'>拷入和拷出的对象必须具有相同的底层`const`资格。</font>或者两个对象的数据类型可以相互转换。一般来说,<font color='red'>非常量可以转换成常量</font>,反之则不行。
```c++
int *p = p3; // error: p3 has a low-level const but p doesn't
p2 = p3; // ok: p2 has the same low-level const qualification as p3
p2 = &i; // ok: we can convert int* to const int*
int &r = ci; // error: can't bind an ordinary int& to a const int object
const int &r2 = i; // ok: can bind const int& to plain int
```
### constexpr和常量表达式(constexpr and Constant Expressions)
<font color='red'>常量表达式</font>(constant expressions)<font color='red'>指值不会改变并且在编译过程就能得到计算结果的表达式。</font>
一个对象是否为常量表达式由它的<font color='red'>数据类型和初始值共同决定</font>。
```c++
const int max_files = 20; // max_files is a constant expression
const int limit = max_files + 1; // limit is a constant expression
int staff_size = 27; // staff_size is not a constant expression
const int sz = get_size(); // sz is not a constant expression
```
C++11允许将变量声明为`constexpr`类型以便由编译器来<font color='red'>验证变量的值是否是一个常量表达式</font>。
```c++
constexpr int mf = 20; // 20 is a constant expression
constexpr int limit = mf + 1; // mf + 1 is a constant expression
constexpr int sz = size(); // ok only if size is a constexpr function
```
指针和引用都能定义成`constexpr`,但是初始值受到严格限制。<font color='red'>`constexpr`指针的初始值必须是0、`nullptr`或者是存储在某个固定地址中的对象。</font>
函数体内定义的普通变量一般并非存放在固定地址中,因此`constexpr`指针不能指向这样的变量。相反,<font color='red'>函数体外定义的变量地址固定不变</font>,可以用来初始化`constexpr`指针。
在`constexpr`声明中如果定义了一个指针,限定符`constexpr`仅对指针本身有效,与指针所指的对象无关。<font color='red'>`constexpr`把它所定义的对象置为了顶层`const`</font>。
```c++
constexpr int *p = nullptr; // p是指向int的const指针
constexpr int i = 0;
constexpr const int *cp = &i; // cp是指向const int的const指针
```
<font color='red'>`const`和`constexpr`限定的值都是常量。但`constexpr`对象的值必须在编译期间确定,而`const`对象的值可以延迟到运行期间确定</font>。
<font color='red'>建议使用`constexpr`修饰表示数组大小的对象</font>,因为数组的大小必须在编译期间确定且不能改变。
## 处理类型(Dealing with Types)
### 类型别名(Type Aliases)
类型别名是某种类型的同义词,传统方法是使用<font color='red'>关键字`typedef`定义类型别名。</font>
```c++
typedef double wages; // wages is a synonym for double
typedef wages base, *p; // base is a synonym for double, p for double*
```
C++11使用<font color='red'>关键字`using`进行别名声明</font>(alias declaration),作用是把等号左侧的名字规定成等号右侧类型的别名。
```c++
using SI = Sales_item; // SI is a synonym for Sales_item
```
### auto类型说明符(The auto Type Specifier)
C++11新增`auto`类型说明符,能让编译器自动分析表达式所属的类型。<font color='red'>`auto`定义的变量必须有初始值。</font>
```c++
// the type of item is deduced from the type of the result of adding val1 and val2
auto item = val1 + val2; // item initialized to the result of val1 + val2
```
编译器推断出来的<font color='red'>`auto`类型有时和初始值的类型并不完全一样。</font>
- <font color='red'>当引用被用作初始值时,编译器以引用对象的类型作为`auto`的类型。</font>
```c++
int i = 0, &r = i;
auto a = r; // a is an int (r is an alias for i, which has type int)
```
- <font color='red'>`auto`一般会忽略顶层`const`。 </font>
```c++
const int ci = i, &cr = ci;
auto b = ci; // b is an int (top-level const in ci is dropped)
auto c = cr; // c is an int (cr is an alias for ci whose const is top-level)
auto d = &i; // d is an int*(& of an int object is int*)
auto e = &ci; // e is const int*(& of a const object is low-level const)
```
如果<font color='red'>希望推断出的`auto`类型是一个顶层`const`,需要显式指定`const auto`</font>。
```C++
const auto f = ci; // deduced type of ci is int; f has type const int
```
设置类型为`auto`的引用时,原来的初始化规则仍然适用,初始值中的顶层常量属性仍然保留。
```c++
auto &g = ci; // g is a const int& that is bound to ci
auto &h = 42; // error: we can't bind a plain reference to a literal
const auto &j = 42; // ok: we can bind a const reference to a literal
```
### decltype类型指示符(The decltype Type Specifier)
C++11新增`decltype`类型指示符,<font color='red'>作用是选择并返回操作数的数据类型</font>,此过程中编译器不实际计算表达式的值。
```c++
decltype(f()) sum = x; // sum has whatever type f returns
```
`decltype`处理顶层`const`和引用的方式与`auto`有些不同,<font color='red'>如果`decltype`使用的表达式是一个变量,则`decltype`返回该变量的类型(包括顶层`const`和引用)。</font>
```c++
const int ci = 0, &cj = ci;
decltype(ci) x = 0; // x has type const int
decltype(cj) y = x; // y has type const int& and is bound to x
decltype(cj) z; // error: z is a reference and must be initialized
```
如果`decltype`使用的表达式不是一个变量,则`decltype`返回表达式结果对应的类型。如果表达式的内容是解引用操作,则`decltype`将得到引用类型。如果`decltype`使用的是一个不加括号的变量,则得到的结果就是该变量的类型;如果给变量加上了一层或多层括号,则`decltype`会得到引用类型,因为变量是一种可以作为赋值语句左值的特殊表达式。
<font color='red'>`decltype((var))`的结果永远是引用,而`decltype(var)`的结果只有当*var*本身是一个引用时才会是引用。</font>
## 自定义数据结构(Defining Our Own Data Structures)
C++11规定可以为类的数据成员(data member)提供一个类内初始值(in-class initializer)。创建对象时,类内初始值将用于初始化数据成员,没有初始值的成员将被默认初始化。
<font color='red'>类内初始值不能使用圆括号。</font>
<font color='red'>类定义的最后应该加上分号。</font>
头文件一旦改变,相关的源文件必须重新编译以获取更新之后的声明。
头文件保护符(header guard)依赖于预处理变量(preprocessor variable)。预处理变量有两种状态:已定义和未定义。`#define`指令把一个名字设定为预处理变量。`#ifdef`指令当且仅当变量已定义时为真,`#ifndef`指令当且仅当变量未定义时为真。一旦检查结果为真,则执行后续操作直至遇到`#endif`指令为止。
```c++
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include <string>
struct Sales_data
{
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
#endif
```
在高级版本的IDE环境中,可以直接使用`#pragma once`命令来防止头文件的重复包含。
<font color='red'>预处理变量无视C++语言中关于作用域的规则。</font>
整个程序中的预处理变量,包括头文件保护符必须唯一。预处理变量的名字一般均为大写。
<font color='red'>头文件即使目前还没有被包含在任何其他头文件中,也应该设置保护符。</font>
# 第3章 字符串、向量和数组
## 命名空间的using声明(Namespace using Declarations)
使用`using`声明后就无须再通过专门的前缀去获取所需的名字了。
```c++
using std::cout;
```
程序中使用的每个名字都需要用独立的`using`声明引入。
头文件中通常不应该包含`using`声明。
## 标准库类型string(Library string Type)
<font color='red'>标准库类型`string`表示可变长的字符序列</font>,定义在头文件*string*中。
### 定义和初始化string对象(Defining and Initializing strings)
初始化`string`的方式:
![3-1](Images/3-1.png)
<font color='red'>如果使用等号初始化一个变量,实际上执行的是拷贝初始化</font>(copy initialization),编译器把等号右侧的初始值拷贝到新创建的对象中去。<font color='red'>如果不使用等号,则执行的是直接初始化</font>(direct initializati<font color='red'></font>on)。
### string对象上的操作(Operations on strings)
`string`的操作:
![3-2](Images/3-2.png)
在执行读取操作时,`string`对象会自动忽略开头的空白(空格符、换行符、制表符等)并从第一个真正的字符开始读取,直到遇见下一处空白为止。
使用`getline`函数可以读取一整行字符。<font color='red'>该函数只要遇到换行符就结束读取并返回结果,如果输入的开始就是一个换行符,则得到空`string`。</font>触发`getline`函数返回的那个换行符实际上被丢弃掉了,得到的`string`对象中并不包含该换行符。
`size`函数返回`string`对象的长度,返回值是`string::size_type`类型,这是一种无符号类型。要使用`size_type`,必须先指定它是由哪种类型定义的。
<font color='red'>如果一个表达式中已经有了`size`函数就不要再使用`int`了,</font>这样可以避免混用`int`和`unsigned int`可能带来的问题。
当把`string`对象和字符字面值及字符串字面值混合在一条语句中使用时,<font color='red'>必须确保每个加法运算符两侧的运算对象中至少有一个是`string`。</font>
```c++
string s4 = s1 + ", "; // ok: adding a string and a literal
string s5 = "hello" + ", "; // error: no string operand
string s6 = s1 + ", " + "world"; // ok: each + has a string operand
```
<font color='red'>为了与C兼容,C++语言中的字符串字面值并不是标准库`string`的对象。</font>
### 处理string对象中的字符(Dealing with the Characters in a string)
头文件*cctype*中的字符操作函数:
![3-3](Images/3-3.png)
<font color='red'>建议使用C++版本的C标准库头文件。</font>C语言中名称为*name.h*的头文件,在C++中则被命名为*cname*。
C++11提供了范围`for`(range for)语句,可以遍历给定序列中的每个元素并执行某种操作。
```c++
for (declaration : expression)
statement
```
*expression*部分是一个对象,用于表示一个序列。*declaration*部分负责定义一个变量,该变量被用于访问序列中的基础元素。<font color='red'>每次迭代,*declaration*部分的变量都会被初始化为*expression*部分的下一个元素值。</font>
```c++
string str("some string");
// print the characters in str one character to a line
for (auto c : str) // for every char in str
cout << c << endl; // print the current character followed by a newline
```
如果想在范围`for`语句中<font color='red'>改变</font>`string`对象中字符的值,必须把<font color='red'>循环变量定义成引用类型</font>。
下标运算符接收的输入参数是`string::size_type`类型的值,表示要访问字符的位置,返回值是该位置上字符的引用。
下标数值从0记起,范围是0至*size - 1*。<font color='red'>使用超出范围的下标将引发不可预知的后果。</font>
C++标准并不要求标准库检测下标是否合法。编程时可以把<font color='red'>下标的类型定义为相应的</font>`size_type`,这是一种无符号数,可以确保下标不会小于0,此时代码只需要保证下标小于`size`的值就可以了。<font color='red'>另一种确保下标合法的有效手段就是使用范围`for`语句。</font>
## 标准库类型vector(Library vector Type)
标准库类型`vector`表示对象的集合,也叫做容器(container),定义在头文件*vector*中。`vector`中所有对象的类型都相同,每个对象都有一个索引与之对应并用于访问该对象。
`vector`<font color='red'>是模板(template)而非类型</font>,由`vector`生成的类型必须包含`vector`中元素的类型,如`vector<int>`。
因为引用不是对象,所以不存在包含引用的`vector`。
在早期的C++标准中,如果`vector`的元素还是`vector`,定义时必须在外层`vector`对象的右尖括号和其元素类型之间添加一个空格,如`vector<vector<int> >`。但是在C++11标准中,可以直接写成`vector<vector<int>>`,不需要添加空格。
### 定义和初始化vector对象(Defining and Initializing vectors)
初始化`vector`对象的方法:
![3-4](Images/3-4.png)
<font color='red'>初始化`vector`对象时如果使用圆括号,可以说提供的值是用来构造(construct)`vector`对象的;如果使用的是花括号,则是在列表初始化(list initialize)该`vector`对象。</font>
可以只提供`vector`对象容纳的元素数量而省略初始值,此时会创建一个值初始化(value-initialized)的元素初值,并把它赋给容器中的所有元素。这个初值由`vector`对象中的元素类型决定。
### 向vector对象中添加元素(Adding Elements to a vector)
`push_back`函数可以把一个值添加到`vector`的尾端。
```c++
vector<int> v2; // empty vector
for (int i = 0; i != 100; ++i)
v2.push_back(i); // append sequential integers to v2
// at end of loop v2 has 100 elements, values 0 . . . 99
```
<font color='red'>范围`for`语句体内不应该改变其所遍历序列的大小。</font>
### 其他vector操作(Other vector Operations)
`vector`支持的操作:
![3-5](Images/3-5.png)
`size`函数返回`vector`对象中元素的个数,返回值是由`vector`定义的`size_type`类型。<font color='red'>`vector`对象的类型包含其中元素的类型。</font>
```c++
vector<int>::size_type // ok
vector::size_type // error
```
<font color='red'>`vector`和`string`对象的下标运算符只能用来访问已经存在的元素,而不能用来添加元素。 </font>
```c++
vector<int> ivec; // empty vector
for (decltype(ivec.size()) ix = 0; ix != 10; ++ix)
{
ivec[ix] = ix; // disaster: ivec has no elements
ivec.push_back(ix); // ok: adds a new element with value ix
}
```
## 迭代器介绍(Introducing Iterators)
迭代器的作用和下标类似,但是更加通用。<font color='red'>所有标准库容器都可以使用迭代器</font>,但是其中只有少数几种同时支持下标运算符。
### 使用迭代器(Using Iterators)
定义了迭代器的类型都拥有`begin`和`end`两个成员函数。`begin`函数返回指向第一个元素的迭代器,`end`函数返回指向容器“尾元素的下一位置(one past the end)”的迭代器,通常被称作尾后迭代器(off-the-end iterator)或者简称为尾迭代器(end iterator)。尾后迭代器仅是个标记,表示程序已经处理完了容器中的所有元素。迭代器一般为`iterator`类型。
```c++
// b denotes the first element and e denotes one past the last element in ivec
auto b = ivec.begin(), e = ivec.end(); // b and e have the same type
```
<font color='red'>如果容器为空</font>,则`begin`和`end`返回的是<font color='red'>同一个迭代器,都是尾后迭代器。</font>
标准容器迭代器的运算符:
![3-6](Images/3-6.png)
<font color='red'>因为`end`返回的迭代器</font>并不实际指向某个元素,<font color='red'>所以不能对其进行递增或者解引用的操作</font>。
在`for`或者其他循环语句的判断条件中,最好使用`!=`而不是`<`。<font color='red'>所有标准库容器的迭代器都定义了`==`和`!=`,</font>但是只有其中少数同时定义了`<`运算符。
<font color='red'>如果`vector`或`string`对象是常量,则只能使用`const_iterator`迭代器,该迭代器只能读元素,不能写元素。</font>
<font color='red'>`begin`和`end`返回的迭代器具体类型由对象是否是常量决定</font>,如果对象是常量,则返回`const_iterator`;如果对象不是常量,则返回`iterator`。
```c++
vector<int> v;
const vector<int> cv;
auto it1 = v.begin(); // it1 has type vector<int>::iterator
auto it2 = cv.begin(); // it2 has type vector<int>::const_iterator
```
<font color='red'>C++11新增了`cbegin`和`cend`函数</font>,不论`vector`或`string`对象是否为常量,都返回`const_iterator`迭代器。
<font color='red'>任何可能改变容器对象容量的操作,都会使该对象的迭代器失效。</font>
### 迭代器运算(Iterator Arithmetic)
`vector`和`string`迭代器支持的操作:
![3-7](Images/3-7.png)
`difference_type`类型用来表示<font color='red'>两个迭代器间的距离,这是一种带符号整数类型。</font>
## 数组(Arrays)
数组类似`vector`,但数组的大小确定不变,不能随意向数组中添加元素。
如果不清楚元素的确切个数,应该使用`vector`。
### 定义和初始化内置数组(Defining and Initializing Built-in Arrays)
数组是一种复合类型,声明形式为`a[d]`,其中*a*是数组名称,*d*是数组维度(dimension)。维度必须是一个常量表达式。
<font color='red'>默认情况下,数组的元素被默认初始化。</font>
<font color='red'>定义数组的时候必须指定数组的类型,不允许用`auto`关键字由初始值列表推断类型。</font>
如果定义数组时提供了元素的初始化列表,则允许省略数组维度,编译器会根据初始值的数量计算维度。但如果显式指明了维度,那么初始值的数量不能超过指定的大小。如果维度比初始值的数量大,则用提供的值初始化数组中靠前的元素,剩下的元素被默认初始化。
```c++
const unsigned sz = 3;
int ia1[sz] = {0,1,2}; // array of three ints with values 0, 1, 2
int a2[] = {0, 1, 2}; // an array of dimension 3
int a3[5] = {0, 1, 2}; // equivalent to a3[] = {0, 1, 2, 0, 0}
string a4[3] = {"hi", "bye"}; // same as a4[] = {"hi", "bye", ""}
int a5[2] = {0,1,2}; // error: too many initializers
```
<font color='red'>可以用字符串字面值初始化字符数组,但字符串字面值结尾处的空字符也会一起被拷贝到字符数组中。 </font>
```c++
char a1[] = {'C', '+', '+'}; // list initialization, no null
char a2[] = {'C', '+', '+', '\0'}; // list initialization, explicit null
char a3[] = "C++"; // null terminator added automatically
const char a4[6] = "Daniel"; // error: no space for the null!
```
<font color='red'>不能用一个数组初始化或直接赋值给另一个数组。</font>
从数组的名字开始<font color='red'>由内向外阅读</font>有助于理解复杂数组声明的含义。
```c++
int *ptrs[10]; // ptrs is an array of ten pointers to int
int &refs[10] = /* ? */; // error: no arrays of references
int (*Parray)[10] = &arr; // Parray points to an array of ten ints
int (&arrRef)[10] = arr; // arrRef refers to an array of ten ints
```
### 访问数组元素(Accessing the Elements of an Array)
数组下标通常被定义成`size_t`类型,这是一种机器相关的无符号类型,可以表示内存中任意对象的大小。<font color='red'>`size_t`定义在头文件*cstddef*中</font>。
<font color='red'>大多数常见的安全问题都源于缓冲区溢出错误。</font>当数组或其他类似数据结构的下标越界并试图访问非法内存区域时,就会产生此类错误。
### 指针和数组(Pointers and Arrays)
<font color='red'>在大多数表达式中,使用数组类型的对象其实是在使用一个指向该数组首元素的指针。</font>
```c++
string nums[] = {"one", "two", "three"}; // array of strings
string *p = &nums[0]; // p points to the first element in nums
string *p2 = nums; // equivalent to p2 = &nums[0]
```
一维数组寻址公式:
![3-8](Images/3-8.png)
<font color='red'>当使用数组作为一个`auto`变量的初始值时,推断得到的类型是指针而非数组。但`decltype`关键字不会发生这种转换,直接返回数组类型。 </font>
```c++
int ia[] = {0,1,2,3,4,5,6,7,8,9}; // ia is an array of ten ints
auto ia2(ia); // ia2 is an int* that points to the first element in ia
ia2 = 42; // error: ia2 is a pointer, and we can't assign an int to a pointer
auto ia2(&ia[0]); // now it's clear that ia2 has type int*
// ia3 is an array of ten ints
decltype(ia) ia3 = {0,1,2,3,4,5,6,7,8,9};
ia3 = p; // error: can't assign an int* to an array
ia3[4] = i; // ok: assigns the value of i to an element in ia3
```
C++11在头文件*iterator*中定义了两个名为`begin`和`end`的函数,功能与容器中的两个同名成员函数类似,参数是一个数组。
```c++
int ia[] = {0,1,2,3,4,5,6,7,8,9}; // ia is an array of ten ints
int *beg = begin(ia); // pointer to the first element in ia
int *last = end(ia); // pointer one past the last element in ia
```
两个指针相减的结果类型是`ptrdiff_t`,这是一种定义在头文件*cstddef*中的带符号类型。
<font color='red'>标准库类型限定使用的下标必须是无符号类型,而内置的下标运算无此要求。</font>
### C风格字符串(C-Style Character Strings)
C风格字符串是将字符串存放在字符数组中,并以空字符结束(null terminated)。这不是一种类型,而是一种为了表达和使用字符串而形成的书写方法。
C++标准支持C风格字符串,但是最好不要在C++程序中使用它们。<font color='red'>对大多数程序来说,使用标准库`string`要比使用C风格字符串更加安全和高效。</font>
C风格字符串的函数:
![3-9](Images/3-9.png)
C风格字符串函数不负责验证其参数的正确性,<font color='red'>传入此类函数的指针必须指向以空字符作为结尾的数组。</font>
### 与旧代码的接口(Interfacing to Older Code)
<font color='red'>任何出现字符串字面值的地方都可以用以空字符结束的字符数组来代替:
</font>
- 允许使用以<font color='red'>空字符结束的字符数组</font>来初始化`string`对象或为`string`对象赋值。
- 在`string`对象的<font color='red'>加法运算中</font>,允许使用以空字符结束的字符数组作为其中一个运算对象(不能两个运算对象都是)。
- <font color='red'>在`string`对象的复合赋值运算中,允许使用以空字符结束的字符数组作为右侧运算对象。
</font>
不能用`string`对象直接初始化指向字符的指针。为了实现该功能,`string`提供了一个名为`c_str`的成员函数,返回`const char*`类型的指针,指向一个以空字符结束的字符数组,数组的数据和`string`对象一样。
```c++
string s("Hello World"); // s holds Hello World
char *str = s; // error: can't initialize a char* from a string
const char *str = s.c_str(); // ok
```
<font color='red'>针对`string`对象的后续操作有可能会让`c_str`函数之前返回的数组失去作用,如果程序想一直都能使用其返回的数组,最好将该数组重新拷贝一份。</font>
<font color='red'>可以使用数组来初始化`vector`对象,但是需要指明要拷贝区域的首元素地址和尾后地址。
</font>
```c++
int int_arr[] = {0, 1, 2, 3, 4, 5};
// ivec has six elements; each is a copy of the corresponding element in int_arr
vector<int> ivec(begin(int_arr), end(int_arr));
```
<font color='red'>在新版本的C++程序中应该尽量使用`vector`、`string`和迭代器</font>,避免使用内置数组、C风格字符串和指针。
## 多维数组(Multidimensional Arrays)
C++中的多维数组其实就是数组的数组。当一个数组的元素仍然是数组时,通常需要用两个维度定义它:一个维度表示数组本身的大小,另一个维度表示其元素(也是数组)的大小。通常把二维数组的第一个维度称作行,第二个维度称作列。
多维数组初始化的几种方式:
```c++
int ia[3][4] =
{ // three elements; each element is an array of size 4
{0, 1, 2, 3}, // initializers for the row indexed by 0
{4, 5, 6, 7}, // initializers for the row indexed by 1
{8, 9, 10, 11} // initializers for the row indexed by 2
};
// equivalent initialization without the optional nested braces for each row
int ib[3][4] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
// explicitly initialize only element 0 in each row
int ic[3][4] = {{ 0 }, { 4 }, { 8 }};
// explicitly initialize row 0; the remaining elements are value initialized
int id[3][4] = {0, 3, 6, 9};
```
<font color='red'>可以使用下标访问多维数组的元素,数组的每个维度对应一个下标运算符。如果表达式中下标运算符的数量和数组维度一样多,则表达式的结果是给定类型的元素。如果下标运算符数量比数组维度小,则表达式的结果是给定索引处的一个内层数组。 </font>
```c++
// assigns the first element of arr to the last element in the last row of ia
ia[2][3] = arr[0][0][0];
int (&row)[4] = ia[1]; // binds row to the second four-element array in ia
```
多维数组寻址公式:
![3-10](Images/3-10.png)
<font color='red'>使用范围`for`语句处理多维数组时,为了避免数组被自动转换成指针,语句中的外层循环控制变量必须声明成引用类型。 </font>