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; decltype(x+y) xpy;
decltype(expression) var; ```
#### 另一种函数声明语法(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 - 即使结构变量为const,mutable的成员也是可以被修改的 - volatile - 表明,即使程序代码没有对内存单元进行修改,其值也可能发生改变
### 使用new运算符初始化
如果要为内置的标量类型分配存储空间并初始化,可以在类型名后面加上初始化值,并将其用括号括起:
``` c++ double* pd = new double (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);
```
#### 定位new运算符
通常,new负责在堆中找到一个足以满足要求的内存块。new也可以指定要使用的位置:
``` c++ char buffer[20]; int p1 = new (buffer) int; ```
### 名称空间
当项目很大的时候可能会发出重名的现象,这时可以使用名称空间进行区分。
C++新增了这样一种功能,即通过定义一种新的声明区域来创建命名的名称空间:
``` c++ namespace Jack{ double pail; void fetch(); } ```
using声明将特定的名称添加到它所属的声明区域中:
``` c++ chat fetch; int main(){ using Jack::fetch(); double fetch; cin >> fetch; cing >> ::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 +()`成员函数,以重载+运算符。A、B和C都是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 - protected与private类似,区别之在基类和派生类之间才会表现出来 - 对外界来说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();
*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<()。返回值可转换为bool,false表示两个参数的顺序不正确 - 该函数要求容器类支持随机访问
### 泛型编程
STL是一种泛型编程,泛型编程关注的是算法。如模板能够按泛型定义函数或类,高效地代码复用。
#### 迭代器
模板使得算法独立于储存的数据类型,而迭代器使算法独立于使用的容器类型。如果泛型编程想要使用同一个find函数处理数组、链表或其他容器类型那么就需要一个东西来对容器中的值进行通用表示,迭代器正是这样的通用表示。
每个容器类(vector、list等)定义了相应的迭代器类型。 - 每个容器都有`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,如精度为6时2会显示为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值,可以为true或false - `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对象的错误状态被设置,if或while将判定该对象为false。
#### 流状态
cin或cout对象包含一个描述流状态的数据成员。流状态由3个ios\_base元素组成:eofbit、badbit、failbit,每个元素都是一位,可以是1或0。当全部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
#include<fstream>
ofstream fout;
fout.open("hello.txt");
ofstream fout("hello.txt");
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;}
|