抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

C++补完计划

关键字decltype(c++11)

关键字decltype(x)用于自动检测x的类型,并作为关键字。使用方法如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
int x;	
decltype(x) y; // 声明y,其中y的类型取决于x
decltype(x+y) xpy;
// 格式如下
decltype(expression) var; // expression可以是函数调用
```


#### 另一种函数声明语法(c++11后置返回类型)

考虑下面的情形:

``` c++
template<classs T1, class T2>
?type? gf(T1 x, T2 y){
...
return x+y;
}
那函数的返回类型是什么呢?
```

显然只用decltype是解决不了问题了。使用新增的语法可编写成这样:

``` c++
template<classs T1, class T2>
auto gf(T1 x, T2 y) -> decltype(x+y)
{
...
return x+y;
}
```


### 说明符和限定符

- auto
- register
- static
- extern
- 指出是对外部变量(相对当前代码块的外部)的引用
- thread\_local(C++11)
- mutable
- 即使结构变量为constmutable的成员也是可以被修改的
- volatile
- 表明,即使程序代码没有对内存单元进行修改,其值也可能发生改变


### 使用new运算符初始化

如果要为内置的标量类型分配存储空间并初始化,可以在类型名后面加上初始化值,并将其用括号括起:

``` c++
double* pd = new double (99.99) // *pd=99.99
```

要初始化常规结构或数组,需要使用打括号

``` c++
struct where{double x; double y; double z;}
where* one = new where{2.3, 3.2, 6.4}
int* ar = new int[4]{2, 3, 3, 4}
```

#### 初始化结构体

```
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
ListNode* ls=new ListNode(0);
// ls->val=0;ls->next=NULL;
```


#### 定位new运算符

通常,new负责在堆中找到一个足以满足要求的内存块。new也可以指定要使用的位置:

``` c++
char buffer[20];
int p1 = new (buffer) int; //在buffer中找空间
```


### 名称空间

当项目很大的时候可能会发出重名的现象,这时可以使用名称空间进行区分。

C++新增了这样一种功能,即通过定义一种新的声明区域来创建命名的名称空间:

``` c++
namespace Jack{
double pail;
void fetch();
}
```

using声明将特定的名称添加到它所属的声明区域中:

``` c++
chat fetch;
int main(){
using Jack::fetch();
double fetch; // 失败,因为已经声明了fetch
cin >> fetch; // Jack的fetch
cing >> ::fetch; // 全局的fetch
}
```

using声明使一个名称可用,而using编译指令使所有的名称都可用。using编译指令使用`using namespace`关键字。

`
using namespace Jack;
`

可以将名称空间声明进行嵌套

``` c++
namespace Bob{
namespace Bill{
int age;
}
string name;
}
```

也可以在名称空间中使用using编译指令和using声明:

```
namespace myth{
using Jack::fetch;
using namespace Bob;
}
```


## 对象和类

### 构造函数和析构函数

类需要构造函数来创建类对象,不能像下面的那样初始化对象,因为数据部分的访问状态是私有的,这意味着程序不能直接访问数据乘员。

``` c
Person one = {"Bob", 23}; //编译错误
```

- 使用构造函数的两种方法:
- `Person one = Person("Bob", 23);`
- `Person one("Bob", 23);`
- 将构造函数与new一起使用
- `Person *one = new Person("Bob", 23);`

- 析构函数
- `~Person(){}`
- 构造函数创建对象后,程序负责跟踪该对象,直到过期。过期时自动调用析构函数
- 一般用于删除分配了的资源
- 默认构造函数
- `Person(){}`
- 列表初始化
- C++11中,可将列表初始化语法用于类中(构造函数)
- `Person one = {"Bob", 23};`
- `Person one{"Bob", 23};`
- `Person one{};` 调用默认构造函数
- 不同于`Person one()`; 这是一个返回Person的函数
- const成员函数
- `const Person one("Bob", 23);`
`one.show()`,这样是不行的因为不能保证show方法不会修改对象
- 除非show方法为:`void show() const;`,这就是声明const成员函数的方法
- 因此只要类方法不修改调用对象,就应该将其声明为const
- 函数前const`const int func();`表示返回值为const
- 函数后const`int func() const;`表示不能修改class的成员


## 类和对象

### 使用类

#### 重载运算符

- 格式:`operator op(argument-list)`
- 如`operator +()、operator *()`
- 不能是一个虚构的符号

假设有个一个Bob类,并为它定义了一个`operator +()`成员函数,以重载+运算符。ABC都是Bob的对象。便可以编写如下等式:
- `C = A + B`
- 编译器发现操作数是Bob对象,因此使用相应的运算符函数代替上述运算符
- `C = A.operator+(B)`
- 这说明了运算符的原理

重载限制:
- 重载的运算符必须至少有一个操作数是用户自定义的类型,防止用户为标准类型重载运算符
- 使用运算符不能违反运算符原来的语句规则
- 不能修改运算符的优先级
- 不能创造新的运算符

在区分++运算符的前缀版本和后缀版本,C++将operator++作为前缀版本,将operator++(int)作为后缀版本;其中的参数永远不会被用到,所以不指定名称


### 友元

C++提供形式的访问权限,:友元。友元有3中:
- 友元函数
- 友元类
- 友元成员函数


#### 友元成员函数

友元成员函数可以访问类内的私有成员,通过让函数成为类的友元,可以赋予该函数与类的成员函数相同的访问权限。

考虑下面的情形:设A、B都是Bob类的对象,并且Bob类重载了\*运算符。则`A = B * 2.5`可行,但`A = 2.5 * B`就会出现问题。原因在于,类内对运算符重载,隐式调用对象`A = B.operator*(2.5)`,而换位置后`2.5`对象并没有重载运算符。那难道要重载`double`类的运算符吗?那将会造成很大混乱。

还有种重载运算符的方法:使用非成员函数。如`Time operator*(double m, const Time& t)`运算符左边对应第一个参数,右边对应第二个参数。

那如果Time类中有私有数据呢,非成员函数怎么访问?这时就需要友元了。

- 创建友元
- 将原型放在类声明中,并加上`friend`关键字
- 该原型意味着下面两点
- 函数是在类中声明的,但不是类成员函数,因此不能用成员运算符来调用
- 不是成员函数,但数据的访问权限相同
- 编写函数定义
- 因为不是成员函数,所以不能用`Class::`限定符,就像声明普通函数一样即可


### 静态成员

静态类成员有一个特点:无论创建多少对象,程序都只创建一个静态类变量副本。即类的所有对象共享同一个静态成员。

不能在类声明中初始化静态成员变量,因为声明描述如何分配内存,但不执行分配。需要在声明之外使用单独的语句进行初始化, **因为静态类是单独储存的,而不是对象的组成部分** 。


### 特殊成员函数

- C++自动提供下面这些成员函数(如果没有定义)
- 默认构造函数
- 默认析构函数
- 复制构造函数
- 它接受一个指向类对象的常量引用作为参数`Class_name(const Class_name&);`
- 何时调用
- 用类对象生成另一个对象`Time a(b);`
- `Time a = b;`
- 复制是按值复制的,也就是说a、b是同一个东西,因为传入的是引用。这样释放空间时可能会遇到问题。
- 赋值运算函数
- 地址运算函数


### 类继承

从一个类派生出另一个类时,原始类成为基类,继承类称为派生类。

- 派生一个类:`class Son: public Base`,将Son类声明为从Base类派生而来
- 冒号指出Son的基类是Base
- pulic指出Base是一个公有基类,称为公有派生。
- 基类的公有成员将成为派生类的公有成员,基类的私有部分也成为派生类的一部分,但只能通过基类的公有和保护方法访问
- 派生类不能直接访问基类私有成员,必须通过基类的方法进行访问
- private指出Base是一个私有基类
- 基类的成员在派生类中中都(公有、保护私有成员)为私有
- 保护继承
- 基类的公有、保护私有成员在派生类中中都为保护成员


- 创建派生对象时,程序先创建基类对象。使用下列语法指定创建基类的构造函数。
- `Son::Son(int age, string name, int Sex): Base(int age){}`
- 其中`:Base(int age)`是成员初始化列表。参数从派生类构造函数传入基类构造函数
- 也可对派生类的成员使用成员初始化列表语法:
- `Son::Son(int age, string name, int Sex): Base(int age), age(age){}`
- 相当于`Son::Son(int age, string name, int Sex): Base(int age){ this.age = age;}`

- 派生类和基类之间的特殊关系
- 派生类对象可以使用基类的公有方法
- 基类指针可以不进行显示类型转换的情况下指向派生类对象
- 基类范围更广,派生类可以是基类。但是基类不能是派生类,因为派生类更具体
- 基类引用可以不进行显示类型转换的情况下引用派生类对象
- 虽然基类指针或引用只能调用基类方法

- 多态:方法的行为取决于调用该方法的对象
- 两种机制实现多态
- 在派生类中重新定义
- 使用虚方法
- 其他容易理解,这里特别介绍虚方法,它将决定指针或引用调用那种方法
- 如果没有使用虚方法关键字virtual,程序将根据引用类型或指针类型选择方法
- 如果使用虚方法关键字virtual,程序将根据引用或指针指向的对象的类型选择方法
- 即"虚方法将看到指针或引用的本质"

- 将源代码中的函数调用解释为执行特定的函数代码块被称为函数名联编(binding)
- 在编译过程中进行联编被称为静态联编或早联编
- 在程序运行时进行联编称为动态联编或晚联编(如多态)

- 访问控制:protected
- protectedprivate类似,区别之在基类和派生类之间才会表现出来
- 对外界来说protected成员的行为与private相似;但对于派生类来说protected成员的行为与公有成员相似

- **抽象基类(abstract base class, ABC)**
- 将类的共性抽象出来,放到一个ABC类中,然后再用ABC类派生有这些共性的类
- 这么做便可以使用基类指针(配合虚函数)数组同时管理多个类的对象了
- C++通过使用纯虚函数提供未实现的函数。纯虚函数声明的结尾处为=0,原型中的=0是虚函数成为纯虚函数:`virtual void func(int arg) = 0;`
- 当类声明中包含纯虚函数时,不能创建该类的对象,只能作为基类。成为真正的ABC至少包含一个纯虚函数
- C++允许在基类中定义(只是不能创建对象)


## C++中代码重用

- 包含对象成员的类
- has-a
- 有一些类为表示类中的组建提供了方便,用这些类作为成员以就不需要重复的定义
- 如`valarray<T>`类,类似`vector<T>`,但valarray提供的算数支持更多,如max、sum。如果类的成员需要算数数组就可用valarray,而不用重新定义数组和sum、max等类方法,因为valarray对象成员包含了这些方法

- 使用using重新定义访问权限
- 使用保护派生或私有派生时,要让基类的方法在派生类外面可用的一种方法是将函数调用包装在另一个函数调用中,即使用using来指出派生类可以使用特定的基类成员

- 多重继承`class Son: public Dad, private Mom{...};`
- **虚基类**:使得从多个类(它们的基类相同)派生出的对象只继承一个基类对象,而不是重复的两个
- `class Singer: virtual public Worker{...};`
- `class Waiter: public virtual Worker{...};`
- `class SingerWaiter: public Singer, public Waiter{...};`,SingerWaiter对象将只包含worker对象的副本,而不是引入各自(Singer、Waiter)的Worker对象副本

- 类也有模板类、同样的类成员、友元也是,定义方法同模板函数
- 有模板类就会有隐式实例化、显式实例化和显式具体化,同模板函数


## 友元、异常和其他

### 友元

友元一般用于类的扩展接口中,类并非只能拥有友元函数,也可以将类作为友元。

#### 友元类

如电视机和遥控器,它们两并不是is-a或has-a的关系,但是遥控器能够改变电视机的状态,这表明应将遥控作为电视机的一个友元类。电视机的面板也提供了换台、修改音量等功能。遥控器的功能和电视机内置的功能相同,但是遥控器可以任意的选择频道以及其他许多功能。使遥控成为电视的友元类,这样遥控就能访问电视的私有数据了。


### 嵌套类

C++中可以将类声明放在另一个类中。在另一个类中声明的类称为嵌套类,它通过提供新的类型类作用域来避免名称混乱。包含类的成员函数可以使用和创建被嵌套类的对象;而仅当声明位于公有部分,才能在包含类的外部使用嵌套类,而且必须使用作用域解析运算符(class::class::func)

对类进行嵌套通常是为了帮助实现另一个类,避免名称冲突。


### 异常

- try{}
- 花括号表明要注意这些代码引发的异常
- throw Object
- 抛出异常,回到try的位置,并跳过try。通常抛出类类型,类似返回语句return
- 类似return是原理,使用栈解退来跳转到try
- catch(Type){}
- catch关键字表示异常捕获,括号中指出捕获的类型,并接受异常抛出的对象,花括号指出采取的措施
- exception类
- exception类位于exception头文件中,可以把它用作其他异常的基类


### RTTI

RTTI是运行阶段类型识别(Runtime Type Identification)的简称。

- C++有3个支持的RTTI元素
- dynamic\_cast:如果可能的话,将使用一个指向基类的指针来生成一个指向派生类的指针;否则返回空指针
- typeid:返回一个指出对象的类型的值
- type\_info:存储了有关特定类型的信息
- 只能将RTTI用于包含虚函数的类层次结构,原因在与只有对于这种层次结构,才应该将派生类对象的地址赋给基类指针
- 因为如果使用虚方法关键字virtual,程序将根据引用或指针指向的对象的类型选择方法,就算使用基类指针接收,也会选择引用或指针指向的对象方法


- dynamic\_cast:
- `dynamic_cast<Type *>(ps)`,ps指向一个对象
- 如果ps的类型可以安全的转换为`Type*`,运算符返回对象的地址;否则返回空指针
- 因此可以安全地`Base* pb = dynamic_cast<Base *>(ps)`
- typeid和type\_info
- `typeid`运算符接收类名或者一个对象,返回一个对type\_info的的引用
- type\_info是在头文件typeinfo中定义的一个类。type\_info重载了==和!=运算符,以便可以对类进行对比
- `typeid(Base) == typeid(*pg)`如果pg是空指针将发生异常,异常类型是exception类派生来的
- type\_info的实现随厂商而异,但包含一个`name()`成员,该函数返回一个随实现而异的字符串,通常是类的名称


## 标准模板库

### string

#### string类输入

- 对于类,有3中输入方式。`char str[100]`
- `cin >> str`,读一个词
- `cin.getline(str, 100);`,读一行,去掉`\n`,有一个可选参数用来指定使用哪个字符来确定输入边界
- `cin.get(info, 100)`,读一行,保留`\n`
- 对于string对象,有两种方式
- `cin >> str`,读一个词
- `getline(cin, stuff)`,读一行,去掉`\n`,有一个可选参数用来指定使用哪个字符来确定输入边界


### 智能指针模板类

智能指针是行为类似指针的 **类对象** ,这种对象还有其他功能。看下面代码:

``` c
void func(string& str){
string* ps = new string(str);
if(hasException){
throw exception();
}
...
str = ps;
return;
}
```

注意到这段代码分配了堆中的内存,但是没有释放,从而导致内存泄漏。
- 那在结尾加delete不就行了?
- 这是中可能忘记的解决方案,存在隐患
- 就算加了delete如果出现异常,delete将不被执行,也导致内存泄漏
- 因此如果它是一个对象,那在它过期的时候析构函数删除指向的内存就好了。由此得到了使用智能指针的解决方案


#### 智能指针的使用

`auto_ptr`,`unique_ptr`和`shared_ptr`都定义了类似指针的对象,可以将new获得(直接或间接)的地址赋给这种对象。当智能指针过期时,其析构函数将使用delete来释放内存。要创建智能指针对象,必须引入头文件memory。
- 语法:`auto_ptr<Type> pointer(new Type)`(其他两种相同)
- 所有智能指针类都有一个explicit构造函数,该构造函数将指针作为参数。因此不需要自动将指针转换为智能指针对象


#### 智能指针的注意事项

``` c
auto_ptr<string> ps (new string("hello"));
auto_ptr<string> pd;
pd = ps;
```

如果ps和pd是常规指针,那这两个指针指向同一个string对象。这是不能接受的,因为程序将试图将同一个对象删除两次:ps过期时删除,pd过期时删除。要避免这个问题有如下方法:
- 定义赋值运算符
- 使之执行深复制。这样两个指针指向不同的对象
- 建立所有权(ownership)概念
- 对于特定的对象,只能有一个智能指针可拥有它,这样只有拥有对象的指针能够删除该对象
- **auto\_ptr和unique\_ptr就是使用这种策略** ,但是`unique_ptr`的策略更加严格
- 当所有权转移,将不能通过原来的指针进行访问
- `auto_ptr`编译时不会检测所有权是否转移
- 使用`unique_ptr`,将在编译阶段检测到所有权转移错误,因此`unique_ptr`比较安全
- 创建只能更高的指针,跟踪引用特定对象的智能指针
- 如,赋值时计数加1,过期时计数减1.当最后一个指针过期(计数减为0)时才调用delete,这是share\_ptr采用的策略


### 标准模板库(Standard Template Library)

STL容器是同质的,即存储的值的类型相同。


#### 模板类vector

vector类提供了与前面将的valarray和Array类似的操作,即可以创造vector对象,将一个vector对象赋值给另一个vector对象,可以随机访问。


#### 可对vector执行的操作

所有STL容器都提供了一些基本方法,如:`size(), swap(), begin()`等。其中`begin(), end()`返回第一个元素的迭代器和返回超过容量尾的迭代器。

迭代器是一个广义的指针。事实上它可以是指针,也可以是一个可对其执行类似指针操作(如解除引用`operator*()`和递增`operator++()`)的对象。通过将指针广义化为迭代器,让STL能够为各种不同的容器类提供统一的接口。每个容器类都定义了一个合适的迭代器,该迭代器的类型是一个名为`iterator`的typedef,其作用域是整个类。如

``` c
//声明一个迭代器变量
vector<int>::iterator pd;
vector<int> scores;
pd = scores.begin();
//或者auto pd = scores.begin();
*pd = 2;
++pd;
//如上,迭代器的行为就像指针
```

- `erase(iterator1, iterator2)`方法删除矢量中给定区间[iterator1, iterator2)的元素
- 它接受两个迭代器参数
- `insert()`与erase相反
- 它接受三个迭代器参数
- 第一个参数指定了新元素的插入位置,第二个和第三个迭代器定义了被插入区间,该区间通常来自另一个容器对象
- 拥有超尾元素使得在尾部附加元素非常简单


#### 对vector可执行的其他操作

STL从更广泛的角度定义了非成员函数来执行这些操作,即不是为每个容器类定义find()成员函数,而是定义了一个适用于所有容器类的非成员函数。这种设计省去了大量重复的工作。

下面是3个代表性的STL函数:
- `for_each()`
- 接受3个参数,前两个是定义容器区间的迭代器,最后一个是指向函数的指针,被指向的函数应用于区间内的每个元素。被指向的函数不能修改容器元素的值
- `random_shuffle()`
- 接受两个指定区间的迭代器参数,并随机排序该区间中的元素
- 该函数要求容器类支持随机访问
- `sort()`
- 有两个格式:
- 第一个版本接受两个定义区间的迭代器参数,并使用储存在容器中的类型元素定义的<运算符,对区间中的元素进行操作,如果元素是用户定义的对象,要使用sort必须定义能够处理该类型的opertator<()函数
- 另一种格式的sort接受3个参数,前两个是指定区间的迭代器,最后一个是要使用的函数,而不是用opertator<()。返回值可转换为boolfalse表示两个参数的顺序不正确
- 该函数要求容器类支持随机访问


### 泛型编程

STL是一种泛型编程,泛型编程关注的是算法。如模板能够按泛型定义函数或类,高效地代码复用。


#### 迭代器

模板使得算法独立于储存的数据类型,而迭代器使算法独立于使用的容器类型。如果泛型编程想要使用同一个find函数处理数组、链表或其他容器类型那么就需要一个东西来对容器中的值进行通用表示,迭代器正是这样的通用表示。

每个容器类(vectorlist等)定义了相应的迭代器类型。
- 每个容器都有`begin()`和`end()`方法,分别返回第一个容器和超尾位置的迭代器
- 每个容器都实现了\*和++等方法,从而可以对迭代器进行操作


#### 迭代器的类型

- 输入迭代器
- 这里的"输入"是从程序的角度来说的,即容器的信息被视为输入
- 输入迭代器可以被程序用来读取容器的信息,但不能修改容器的值
- 支持++运算符来访问所有元素
- 输入迭代器是单向的、可递增的、不能倒退的
- 输出迭代器
- 这里的"输出"指用于信息从程序传输到容器
- 输出迭代器是单通行的、只写的
- 正向迭代器
- 只能使用++运算符遍历容器
- 总是按一定的顺序
- 递增后仍可以对前面的迭代器解除引用,并可的到相同的值
- 可读可写也可只读`int *p; const int* p`
- 双向迭代器
- 具有正向迭代器的所有特性,同时支持两种增减运算符
- 随机访问迭代器
- 具有双向迭代器的所以特性,同时支持随机访问


## 输入输出和文件

### 流和缓冲区

输入流需要两个连接,每端各一个。文件端(文件端可以是文件也可以是设备)连接提供流的来源,程序端连接将流的流出部分转储到程序中。同样,输出的管理包括将输出流连接到程序以及将输出目标与流关联。

缓冲区是用作中介的内存块,它是将信息从设备传输到程序或从程序传输给设备的临时储存工具。使用缓冲区可以高效地处理输入和输出。缓冲区帮助匹配两种不同的信息传输速率。C++程序通常在用户按下回车键时刷新入缓冲区,对键盘输入进行缓冲可以让用户将输入传输给程序之前返回并更正。


### 流、缓冲区和iostream文件

iostream文件中包含一些专门设计来实现、管理流和缓冲区的类。

- stream类
- 为缓冲区提供了内存,并提供了用于填充缓冲区、访问缓冲区内容、刷新缓冲区和管理缓冲区内存的类方法
- ios\_base类
- 表示流的一般特征,如是否可读取、是二进制流还是文本流等
- ios类基于ios\_base,其中包括了一个指向streambuf对象的指针成员
- ostream类
- 从ios类派生而来的,提供了输出方法
- istream类
- 从ios类派生而来,提供了输入方法
- 这些工具必须使用适当的类对象,如ostream对象cout。创建这样的对象将打开一个流,自动创建缓冲区,并将其与流关联起来。

iostream库管理了很多细节。如在程序中包含iostream文件将自动创建8个流对象(4个用于窄字符流、4个用于宽字符流)
- cin
- 标准输入流,默认情况下这个流被关联到标准输入设备
- wcin对象与此类似,但处理的是wchar\_t类型
- cout
- 标准输出流,默认情况下这个流被关联到标准输出设备
- wout对象与此类似,但处理的是wchar\_t类型
- cerr
- 标准错误流,可用于显示错误消息,默认情况下这个流被关联到标准输出设备
- 这个流没有缓冲,直接被发送到屏幕
- werr对象与此类似,但处理的是wchar\_t类型
- clog
- 也是标准错误流,可用于显示错误消息,默认情况下这个流被关联到标准输出设备
- wlog对象与此类似,但处理的是wchar\_t类型
- 对象代表流意味着什么?
- 当iostream文件为程序声明一个cout对象时,该对象将包含储存了与输出有关的信息和数据成员,如显示数据时使用的字段宽度、小数位数、显示整数时采用的计数方法以及描述用来处理输出流缓冲区的streambuf对象的地址
- `cout << "hello";`这个语句通过将指向的streambuf对象将字符串"hello"中的字符放到cout管理的缓冲区中


- 重定向
- 标准输入和输出流通常连接着键盘和屏幕,重定向使得能够改变标准输入和标准输出。


### 使用cout进行输出

C++将输出看做字节流,但在程序中,很多数据被组织成比字节流更大的单位,如,int类型可能有16位或32位的二进制值表示。但在将字节流发送给屏幕时,希望每个字节表示一个字符,如int类型的12可能是32位表示的,但在屏幕上要求3用一个字节表示,2用一个字节表示。

因此ostream类最重要的任务之一是将数值类型转换为文本形式表示的字符流。


- 重载的\<\<运算符
- C/C++中`<<`运算符默认含义是按位左移,但ostream类重载了`<<`运算符,将其重载为输出。`<<`运算符被重载使之能识别C++中所有的基本类型。
- 其他ostream方法
- `put()`方法原型:`ostream& put(char)`
- `cout.put('W');`,cout是调用方法的对象,put是类成员函数
- 返回一个指向调用对象的引用,因此可以用它将拼接输出
- `write()`原型:`basic_ostream<charT,traits>& write(const char_type* s, streamsize n);`
- write方法显示整个字符串
- 第一个参数提供了要显示的字符串的地址,第二个参数指出要显示多少个字符
- write方法不会在遇到空字符时停止打印,而是打印指定数目的字符,即使超出了字符串的边界!


### 刷新缓冲区

ostream类对cout对象处理的输出进行缓冲,所以输出不会立即发送到目标地址,而是被储存在缓冲区中,直至缓冲区填满。然后刷新(flush)缓冲区,把内容发送出去,并清空缓冲区。

- 在屏幕输出时,程序不必等到缓冲区填满。如
- 将换行号发送到缓冲区后将刷新缓冲区。
- 多数C++实现都会在输入即将发生时刷新缓冲区,即使没有换行符
- 如果实现不能在所希望时刷新输出,可以使用两个控制符中的一个来强制进行刷新
- 控制符flush刷新缓冲区:`cout << "hello" << flush;`
- 控制符endl刷新缓冲区,并插入一个换行符:`cout << "hello" << endl;`
- 实际上控制符也是函数,可以直接调用`flush(cout);`,ostream重载了`<<`,使得`cout << flush;`可行。


### 用cout进行格式化

ostream类是从ios类派生而来的,而ios类从ios\_base类派生而来。ios\_base类存储了描述格式状态的信息。通过使用ios\_base的成员函数,可以控制字段宽度和小数位数。由于ios\_base类是ostream的间接基类,因此可将其方法用于ostream对象,如cout

- 调整进制,**设置持续到将格式状态设置为其他选项为止**,控制符不是成员函数,但也可`cout << hex;`调用
- `hex()`,设置后程序以十六进制形式打印整数值
- `oct()`,设置后程序以八进制形式打印整数值
- 调整字段宽度
- `width` **成员函数** 将长度不同的数字放到宽度相同的字段中
- `width()`方法只影响将显示的下一个项目,然后字段宽度将恢复为默认值
- `cout.width(12);cout<<12<<"#";`只有12被放宽到12个字符,并右对齐,后面的将不受影响
- `int width()`,返回宽度当前值
- `int width(int i)`,将字段宽度设置为i个,并返回以前的字段宽度值
- 如果试图在宽度为2的字段中显示一个7位值,C++将增宽字段,以容纳该数据
- 填充字符,默认情况下cout使用空格填充字段中未被使用的部分
- `fill()` **成员函数** 来改变填充的字符,如`cout.fill('*')`。新的填充字符将一直有效直到设置新的填充字符
- 设置浮点数的显示精度有效位数
- `precision()` **成员函数** 改变精度。如`cout.precision(2);`。精度设置一直有效直到被重新设置
- 打印末尾的0和小数点
- iostream没有提供专门用于这项功能的函数,但ios\_base类提供了一个`serf()`函数,能够控制多种格式化特性。这个类还定义了多个常量,可以用作改函数的参数
- cout显示末尾小数点`cout.setf(ios_base::showpoint);`
- 这会显示小数后的0,如精度为62会显示为2.00000

`setf()`除了可以控制小数点的显示还有几个格式选项。ios\_base类有一个受保护的数据成员,其中的各位(这里称之为标记)分别控制着格式化的各个方面。打开一个标记称为设置标记(或位),并意味着相应的位被设置为1.

hex、dec、oct等控制符实际上就是通过控制技术系统的3个标记位实现的。`setf()`函数提供了另一种调整标记位的途径。`setf()`函数有两个原型
- `fmtflags setf(fmtflags);`,其中哦fmtflags是bitmask类型的别名(typedef),用于储存格式标记。
- 这个版本的setf用来设置单个控制位的格式信息,参数fmtflags是一个值,指出要设置哪一位。返回值是类型为fmtflags的数字,指出标记以前的设置
- ios\_base类定义了代表位值的常量:
- `ios\_base::boolalpha`,输入和输出的bool值,可以为truefalse
- `ios\_base::showbase`,对于输出,使用C++基数前缀(0, 0x等)
- `ios\_base::showpoint`,显示末尾的小数点
- `ios\_base::uppercase`,对于十六进制输出,使用大写字母,E表示法
- `ios\_base::showpos`,在正数前面加上+。十进制才有效,因为十六进制八进制都被视为无符号
- 修改将一直有效直到被覆盖为止
- `fmtflags setf(fmtflags, fmtflags);`
- 函数的这种格式用于设置有多位控制的格式选项
- 第一个参数包含了所需设置的fmtflags值,第二个参数指出要清理第一个参数中的哪些位
- 如,第3位为1表示以10为基数,第4位为1表示以8为基数,第5位为1表示以16为基数,如果要输出原来以10为基数,要设置为以16为基数,则要将第5位设置为1,和将第3位设置为0:这叫做清除位
- ios\_base类为此定义了常量
- `setf(ios_base::dec, ios_base::basefield)`,使用10为基数
- `setf(ios_base::oct, ios_base::basefield)`,使用8为基数
- `setf(ios_base::hex, ios_base::basefield)`,使用16为基数
- `setf(ios_base::fixed, ios_base::floatfield)`,使用定点计数法
- `setf(ios_base::scientific, ios_base::floatfield)`,使用科学计数法
- `setf(ios_base::left, ios_base::adjustfield)`,使用左对齐
- `setf(ios_base::right, ios_base::adjustfield)`,使用右对齐
- `setf(ios_base::internal, ios_base::adjustfield)`,符号或基数前缀左对齐,值右对齐
- 第二个参数清理一批相关位,然后第一个参数将其中1位设置为1
- 调用setf的效果可通过`unsetf()`消除
- 原型为:`void unsetf(fmtflags mask);`,mask中位设置为1,将使对应的位被复位

使用setf不是进行格式化、对用户友好的方法,C++提供了过个控制符,如hex、dex等。如:使用下列方式打开左对齐和定点选项`cout << left << fixed;`


### 使用cin进行输入

istream类重载了抽取运算符`>>`,使之能识别基本类型。重载的原型为:`istream& operator>>(Type&)`参数和返回都是引用。引用参数意味着会修改参数本身;每个抽取运算符都返回调用对象本身,这使得能够将输入拼接。

可以将hex、oct和dec控制符与cin一起使用,来指定将整数输入解释为十六进制、八进制还是十进制格式。


#### cin>>如何检查输入

抽取运算符跳过空白(空格、换行、制表符),直到遇到非空白字符。抽取运算符将读取指定类型是数据,即从非空白字符开始,到与指定类型不匹配的第一个字符之间的全部内容。不匹配的内容将留在流中,下一个cin语句将从这里开始读取。

如果istream对象的错误状态被设置,ifwhile将判定该对象为false


#### 流状态

cincout对象包含一个描述流状态的数据成员。流状态由3个ios\_base元素组成:eofbit、badbit、failbit,每个元素都是一位,可以是10。当全部3个状态都设置为0,则一切顺利。

- 设置状态
- `clear(eofbit)`方法将状态设置为它的参数(eofbit),剩下的两个状态位置被清除
- `clear()`将清除全部3个状态位
- `setstate()`方法只影响其参数中已设置的位,如`setstate(eofbit)`如果已经设置了failbit,则仍将被设置
- I/O异常
- 可以通过`exceptions()`方法来控制异常如何被处理
- `exceptions()`方法返回一个位字段,它包含3个位,分别对应于eofbit、failbit、badbit
- 修改流状态后`clear()`方法将当前的流状态与`exceptions()`返回的值进行比较。如果当前状态中有"期望"的状态,则clear引发ios\_base::failure异常,就可以通过`catch(ios_base::failure)`捕获
- 位运算符OR使得能够指定多位:`cin.exceptions(badbit|eofbit);`
- 流状态的影响
- 只有流状态良好这个语句才会返回true`while(cin>>input)`,但这么做有个 **严重的后果** :流将对后面的输入或输出关闭,直到位被清除
- 如果希望程序在流状态被设置后能够读取后面的输入,就必须将流状态设置为良好(clear)和将引发错误的字符清空(`while(!isspace(cin.get()))`或`while(cin.get()!='\n')`)


#### 其他istream类方法

- 单字符输入
- `get()`方法读取下一个输入字符,**即使该字符是空白**,而抽取运算符`>>`不会读取空白
- `get(char& ch)`将输入字符赋给参数,返回调用它的istream对象的引用,即cin
- `get(void)`将输入字符转换为整型并返回
- 到达文件尾后,`cin.get(void)`将返回EOF,可用这点来做条件控制

| 特征 | `cin.get(ch)` | `ch = cin.get()` |
|--------------------------|-----------------------|------------------|
| 传输输入字符的方法 | 赋值给参数ch | 返回值赋值给ch |
| 字符输入时函数的返回值 | 指向istream对象的引用 | 字符编码(int值) |
| 达到文件尾时函数的返回值 | 转换为false | EOF |

如果希望跳过空白,使用抽取运算符`>>`更方便。`get(void)`的主要优点是它和C语言中的`getchar`函数及其相似,简单替换即可从C转换为C++。

- getline()、get()
- `istream& get(char*, int, char);`
- `istream& get(char*, int);`
- `istream& getline(char*, int, char);`
- `istream& getline(char*, int);`
- 第一个参数是输入字符串的内存单元地址,第二个参数是最大字符数加1,多一位用来存结尾空字符,第3个参数指定分界符,默认以换行符分界
- get和getline之间的主要区别是,get将分界符留在流中,而getline抽取并丢弃分界符
- `istream& ignore(int = 1, int = EOF);`
- 一个参数指定要读取的最大字符数,另一个参数用作分界符
- 将读取并丢弃最大指定数量个字符或直到到达第一个分界符

- 意外字符串输入
- get和getline的某些输入形式将影响流状态
- 如遇到文件尾将设置eofbit,遇到流被破坏将设置badbit
- 空行并不会导致getline设置failbit,因为它抽取换行符

#### 其他istream方法

其他istream方法包括read()、peek()、gcount()和putback()

- `read(char*, int)`
- read函数读取指定数目的字节,并将它们存储在指定的位置中
- read不会在输入后加上空值字符,因此 **不能将输入转换为字符串**
- `peek()`
- peek函数返回输入中的下一个字符,但不从输入流中抽取出来,即单纯的查看下一个字符
- `gcount()`
- gcount方法返回最后一个非格式化抽取方法读取字符
- 所谓格式化是:抽取运算符对输入进行格式化,使之与特定的数据类型匹配
- `istream& putback(char)`
- putback将一个字符插入到输入字符串中,被插入的字符串将是下一条输入语句读取的地一个字符(即插入到首部)


### 文件输入和输出

#### 简单文件的I/O

要让程序写入文件,必须:
- 1. 创建一个ostream对象来管理输出流
- 2. 将该对象与特定的文件关联起来
- 3. 以使用cout的方式使用该对象,唯一的区别是输出将进入文件,而不是屏幕

``` c
// 1. 包含头文件fstream,该文件包含iostream文件
#include<fstream>
// 2.声明一个ofstream对象
ofstream fout;
// 3. 将这个对象与特定的文件关联起来
fout.open("hello.txt");
// 或使用构造函数
ofstream fout("hello.txt");
// 4. 以使用cout的方式使用fout
fout << "hello world";
```

由于ostream是ofstream的基类,因此可以使用所有的ostream方法,包括各种插入运算符定义、格式化方法和控制符。每创建一个对象,程序将为这个对象创建一个缓冲区。

用上面这种方式打开文件进行输出时,如果没有这样的文件,将创建一个新的文件;如果有这样的文件,则打开并清空文件,输出将进入这个空文件中。

读取文件与写入文件类似:
- 1. 创建一个ifstream对象来管理输入流
- 2. 将该对象与特定的文件关联起来
- 3. 以使用cin的方式使用该对象

当输入输出流对象过期时,到文件的连接将自动关闭。也可以使用`close()`方法来显式地关闭文件的连接。

关闭这样的连接不会删除流,而只是断开流到文件的连接。流管理装置仍被保留。如:fin对象与它管理的输入缓冲区仍然存在。因此可以将流重新连接到同一个或另一个文件。


#### 流状态检查

C++文件流从ios\_base类继承了一个流状态成员。该成员存储了指出流状态的信息。当然还继承了报告流状态的方法,如`fin.is_open()`

由于ifstream和istream对象一样,被放在需要bool类型的地方时,将被转换为bool值。如`if(fin)`


#### 命令行处理

C++有一种让在命令环境中运行的程序能够访问命令行参数的机制,方法是使用main函数的参数:`int main(int argc, char* argv[])`,其中argc为参数的个数,包括命令本身,argv是参数数组argv[0]指向命令行的第一个字符串(命令本身),以此类推。


#### 文件模式

文件模式描述的是文件将被如何使用:读、写、追加等。将流与文件关联时,都可以提供指定文件模式作为第二个参数,如:

``` c
ifstream fin1("hello", mod1);
ifstream fin2;
fin2.open("hello", mod2);
```

ios\_base类定义了一个openmode类型,用于表示模式。可以选择iso\_base类中定义的多个常量来指定模式。

| 常量 | 含义 |
|---------------------|--------------------------------|
| `ios\_base::in` | 打开文件,以便读取 |
| `ios\_base::cout` | 打开文件,以便写入 |
| `ios\_base::ate` | 打开文件,并移动到文件尾 |
| `ios\_base::app` | 追加到文件尾 |
| `ios\_base::trunc` | 如果文件存在,则截短文件(清空) |
| `ios\_base::binary` | 二进制文件 |

ifstream的open方法和构造函数默认用`ios_base::in`打开文件;ofstream默认用`ios_base::out|ios_base::trunc`打开文件。使用运算符OR将两个位值合并成一个可用于设置两个位的值。

`ios_base::trunc`标记意味着打开已有的文件,以接受程序的输入时将被截短,即其以前的内容将被删除。

C++语句`ifstream fin(filename, c++mode);`就像使用了的C的`fopen()`函数一样:`fopen(filename, cmode)`。其中c++mode是一个openmode值,如ios\_base::in;而cmode是相对应的C模式字样,如"r"

| C++模式 | C模式 | 含义 |
|-----------------------------------------------------|----------|------------------------------------------|
| `ios_base::in` | "r" | 打开以读取 |
| `ios_base::out` | "w" | 等价于`ios_base::out \| ios_base::trunc` |
| `ios_base::out \| ios_base::trunc` | "w" | |
| `ios_base::out \| ios_base::app` | "a" | 打开写入,追加 |
| `ios_base::out \| ios_base::out` | "r+" | 打开以读写,在文件允许的位置写入 |
| `ios_base::out \| ios_base::out \| ios_base::trunc` | "w+" | 打开以读写,截短 |
| `c++mode \| ios_base::binary` | "cmodeb" | 以C\+\+mode和二进制模式打开 |
| `c++mode \| ios_base::ate` | "cmode" | |

`ios_base::ate`和`ios_base::app`区别在于:app模式只是将数据添加到文件尾,而ate模式将指针放在文件尾巴


#### 二进制文件

文本格式指的是将所有内容都储存位文本,这需要将浮点数的计算机内部表示转换位字符格式。对于字符来说,二进制表示与文本表示是一样的,都是ASCII码,对于数字二进制表示和文本表示就有很大区别。


## C++新标准

### Lambda函数

名称lambda来自lambda calculus,一种定义和应用函数的数学系统。这个系统让你能够使用匿名函数。在C++11中,对于接受函数指针或函数符的函数,可以使用匿名函数定义作为其参数。

看下面一个lambda函数:

``` c
[](int x){return x%3==0;}

使用[]代替了函数名;没有声明函数返回类型,返回类型相当于使用了decltype推断得到。当lambda表达式完全由一条返回语句组成时,自动类型推断才管用,否则需要使用返回类型后置语法

1
[](double x)->double{int y=x; return x-y;}

lambda函数的好处:定义位于使用的地方附近,不需要翻阅多页寻找函数定义。

lambda函数可以访问作用域内任何动态变量,要捕获要使用的动态变量,可将其名称放在中括号内。如果只指定了变量名如[z]则按值访问变量,如果使用[&z]则按引用访问变量。[&]让你以引用方式访问所有变量,[=]以值方式。[&, z]以值访问z,以引用访问其他变量。

包装器

C++提供了多个包装器。这些对象用于给其他编程提供更一致或更合适的接口。

模板是比较低效的,如ef(q)ef可以是函数名、函数指针、函数对象或有名称的lambda表达式。这些都是可调用的类型,对于模板,可能重复实例化同一个函数,因此导致了模板的低效。

包装器function让你能够防止这样的事情发生。模板function是在头文件functional中声明的,它从调用特征的角度定义了一个对象,可用于包装调用特征相同的函数指针、函数对象或lambda表达式。如下面的声明创建一个function对象,她接受一个char参数和int参数,并返回double值:std::function<double(char, int)> fdci;。可以将接受一个char参数和一个int参数,返回double值的函数指针、函数对象、lambda表达式赋给它。这样对应特征相同的函数指针、函数对象、lambda表达式在使用模板时唯一实例化一次。

可变参数模板

创建可变参数模板需要理解以下几点:

  • 模板参数包
  • 函数参数包
  • 展开参数包
  • 递归

模板参数包

C++11提供了一个用省略号表示的元运算符,让你能够声明表示模板参数包的标识符。

1
2
3
4
template<class... Args>
void show(Args... args){
...
}

其中Args是一个模板参数包,而args是一个函数参数包。可将这些参数包的名称指定位任何符合C++标识符规则的名称。

展开参数包

索引功能在这里不适用。相反可以将省略号放在函数参数包名的右边,将函数参数包展开。如

1
2
3
4
template<class... Args>
void show(Args... args){
show(args...);
}

相当于把所有参数又传给了show,如此下去合时是头?需要使用递归

在可变参数模板函数中使用递归

这里的技巧在于将函数模板头改成如下:

1
2
3
4
template<class T, class... Args>
void show(T value, Args... args){
show(args...);
}

这样每次T都会取走第一个参数,而arg取走其余参数,每次递归就会少一个参数,直到递归结束。

使用

Map

unordered_map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
unordered_map<Type, Type> Hash;	
//索引、添加、修改
Hash[key];
Hash[1] = 2; //添加
Hash[2] = 3; //修改
//计数
Hash.count(key);
//删除
Hash.erase(key);
```

## Queue

### 优先队列

`priority_queue`模板有三个参数,第一个参数是储存对象的类型,第二个参数是存储元素的底层容器,第三个参数是一个决定元素顺序的断言。其中第二参数默认`vector`,第三参数默认`<` **大的元素排在前面** 。

```cpp
template <typename T, typename Container=std::vector<T>, typename Compare=std::less<T>> class priority_queue

创建一个优先队列

1
2
using namespace std;
priority_queue<string, vector<string>,greater<string>> words1

创建一个储存字符串的优先队列,其底层存储结构是vector,根据>判断顺序。

由于默认顺序是按照<比较,所以也可在结构体中重载<运算符,在实现自定义顺序的目的

1
2
3
4
5
6
7
8
9
struct node{
int dis;
int vi;
bool operator <( const node &x )const
{
return x.dis < dis;
}
};
priority_queue<node> q;

位运算符

运算符 描述
& 如果同时存在于两个操作数中,二进制 AND 运算符复制一位到结果中。
| 如果存在于任一操作数中,二进制 OR 运算符复制一位到结果中。
^ 如果存在于其中一个操作数中但不同时存在于两个操作数中,二进制异或运算符复制一位到结果中。
~ 二进制补码运算符是一元运算符,具有”翻转”位效果,即0变成1,1变成0。
<< 二进制左移运算符。左操作数的值向左移动右操作数指定的位数。
>> 二进制右移运算符。左操作数的值向右移动右操作数指定的位数。

赋值运算符

运算符 描述
<<= 左移且赋值运算符
>>= 右移且赋值运算符
&= 按位与且赋值运算符
^= 按位异或且赋值运算符
|= 按位或且赋值运算符

伪函数Functor

伪函数不是一个函数,而是一个类或结构体。伪函数 必须充值()运算符 ,通过重载()运算符来执行函数。

之所以需要伪函数是因为,在传递函数指针在函数内部调用函数,如果我们需要在函数使用一个全局变量,而不能通过在最外层设置全局变量(可能污染命名空间),那么伪函数是类的属性就起到重要作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Func{
public:
Func(string str) : ss(str){}
void operator() (string str){
cout << str << " " << ss <<endl;
}
private:
string str; // 一旦类构造好,那它就是类的全局属性
}

void realFunc(string str, Func myfunc){
myfunc(str); // 正式调用
}

int main(){
realFunc("hello", Func("ring")); // 传入时构造好
}

评论