C++ 学习笔记 1.0 : 基本语言(变量和基本类型,标准库类型,表达式,语句,函数,标准IO库)

%%% 注意 %%%

  • 写函数时要充分考虑到 极端 的情况
  • 要注意函数中的 异常处理
  • 注意 函数的返回值的类型、参数的类型

1. 将负数赋值为unsigned类型的变量在C++中是合法的。

2. float和double的区别:

  •   float占一个字(4字节),double占二个字(64字节)
  •   float只能保证6位有效数字,double型至少可以保证10为有效数字。
  •   double在提高了精度的同时保持了和float持平的计算代价。

3 自增(++i)、自减(--i)时避免使用后置操作符:

  •   尽量使用前自增(减)操作:
  •     原因:前置操作需要做的工作更少:只需加1后返回加1后的结果即可。    、
  •        后置操作则必须先保存操作数原来的值,以便返回未加1之间的值作为操作的结果
  •     只有在必要的时候才使用后置操作符。
  •   *iter++ 等价于:*(iter++) 等价于:*iter;++iter;//因为后自增操作的优先级高于解引用操作。!!!

4. C++中的 箭头(->)操作:

  •   箭头(->) : 在点操作符后使用的解引用操作
  •   Object item;
  •   Object *sp = &item;
  •   (*sp).function(item2); // OK 对应指向对象的指针,在使用点操作之前需要对该指针进行解引用操作。
  •   *sp.function(item2); // Error : sp has no member named function.
  •   sp -> function(item2); // OK, 等价于 (*sp).function(item2);

5. cout 与 ?:输出表达式与条件操作符:

  • 在输出表达式中,如果不严格使用圆括号将条件操作符括起来,将会得到意外的结果:
  • cout << (i < j ? i : j); // OK 输出结果符合预期:输出 i、j 中最大的那个。
  • cout << (i < j) : i : j; // OK 能正常运行,但是输出结果不符合预期:输出结果为0或1.
    • 等效于:cout << (i < j); // 将根据 i、j 的大小关系输出0或1;
    • cout ? i : j; // 测试cout的值,然后选择 i 或 j 。
  • cout << i < j : i : j ; // Error : campares cout to int 不能通过运行。

6. sizeof操作符

  • sizeof 操作符的作用是:返回一个对象或类型名的长度。
  • 返回类型为: size_t 类型。
  • 将 sizeof 用在 表达式 上,将获得该表达式的结果的类型长度。(但是并没有计算表达式的值)
    • classA item, *p; // 下面三种操作的作用相同:求得classA的类型长度(需要持有一个该类型的对象)
    • sizeof(classA); // 结果为classA
    • sizeof item; // 结果为item的类型大小,即:sizeof(classA);
    • sizeof *p; // 返回p指向的类型大小, 即:sizeof(classA);
  • 将 sizeof 用于表达式 expr 时,并没有计算表达式 expr 的值,
  • 特别是在 sizeof *p 中,指针 p 可以持有一个无效的地址,因为不需要对 p 做解引用操作,结果只跟 p 指向的对象类型有关。
  • 可以使用 sizeof 操作快速计算一个数组的元素个数:
    • int sz = sizeof(array ) / sizeof(*array);

7. 数组名一般作为该数组的第一个元素的指针:p156

  • 不将数组转换为指针的例外情况:
  • 将数组用于 取地址(&)操作符的操作数 或 sizeof 操作符的操作数时,或者用作对数组的引用进行初始化时,不会将数组转换为指针。

8. 指针:

  • 指向任意数据类型的指针都可转换为 void* 类型;
  • 整型数值常量 0 可转换为任意指针类型。
  • 算术值和 指针值 都可以转换为 bool 类型:如果指针或算术值为0,则其bool值为false,其他值则为true。

9. 显示转换(强制类型转换)

  • 强制类型转换(C++ primer P158)
  • static_cast: 编译器执行的任何类型的转换都可以由static_cast显示完成。(P159)
  • dynamic_cast: 支持运行时识别指针或引用所指向的对象。(P159)
  • const_cast: 用来转换掉表达式的 cast 性质。(P159)
  • reinterpret_cast: 为操作数的位模式提供较低层次的重新解释。(P159)
  • 强制类型转换关闭或挂起了正常的类型检查,强烈建议程序员避免使用强制类型转换。

10. new 和 delete

  • new 表达式返回指向新创建对象的指针,我们通过该指针访问此对象。
    • int *pi = new int[20];
  • delete 用来显式的将该对象占用的内存返回给自由存储区。
    • delete [] pi;
  • new 和 delete 应成对出现。
  • 不应对不是有new申请的空间进行delete,尽管有时可以通过编译器。
  • 可以对指针值为 0 的指针施加 delete 操作, 但是没有什么意义。
  • 注意:
    • 在 delete 之后,应重设指针的值:因为删除指针后,该指针变成悬垂指针,
    • 应在 delete 指针之后,立即将该指针置 0 ,这样就非常清楚地表明指针不再指向任何对象。

11. 动态内存分配的三种常见错误:

  • 删除(delete)指向动态分配内存的指针失效:因此将无法将该块内存返还给自由存储区,删除动态分配内存失败称为:“内存泄露”。
  • 读写已经删除的对象:如果删除指针所指向的对象之后,将指针置为 0 值,则比较容易检测出次来错误。
  • 对同一个内存空间使用两次delete表达式:
    • 发生在:当两个指针指向同一个动态创建的对象时,
    • 在其中一个指针上做 delete 运算时,将该对象的内存空间返还给自由存储区。
    • 再第二个指针上做 delete 运算时,此时自由存储区可能会被破坏。

12. switch 内部变量的定义:p176

  • 在 switch 结构中,只能在它的最后一个 case 标号 或 default 标号后面定义变量:
    • case true:
    • string s = f(a); // Error:由于此处定义了 s ,那么后面的 case 中都可以使用此 s ,若从后面是 case 开始执行的话,就会造成使用未定义的变量 s。
    • break:
  • 若一定要为某个特殊的 case 定义变量,则可以引用 块语句 ,在该块语句中定义变量,从而保证这个变量在使用前被定义和初始化。
    • case true:
    • {
    • string s = f(a); // OK, s 的声明周期只在这个块内,不会出现使用未定义的变量的情况。
    • }
  • switch 中 case 标号必须是 const 常量表达式。

13. for 语句头中的多个定义:p181

  • 可在 for 语句头中定义多个局部变量:
    • for(int i = 0, j = 0; i < 10; i++) {} // OK,此处就在 for 的语句头定义的两个变量 i, j .
    • 虽然可以定义多个对象,但是不管怎么样,该处只能出现一个语句,因此所有的对象必须具有相同的一般类型。
    • for(int i = 0, string s = "string"; i < 10; i++) {} // Error: 该处楚翔多个语句,多个变量不具有相同的一般类型。

14. do while 语句:交互性 p182

  • do while 相对于 while 具有较强的 交互性:
    • 它能够保证循环体至少执行一次。
    • 程序员可以利用该次的执行,输出给用户一些提示性信息,以介绍while循环的功能。
  • 与 while 语句不同,do while 语句总是以分号结束:
    • int a = 0;
    • do {
    • // int a = 0; // Error:不能在此处定义,在此处定义的变量将不能在while中作为条件变量。
    • a++;
    • } while (a != 10) ;
    • 由于在 do 后的块内定义的变量只在该块内有效,则 while 中将不能使用该变量,只有将该变量定义在 do 之前,才能在 while 中使用该变量。

15. break 语句 p183

  • break 语句只能出现在循环或 switch 结构中,或者出现在嵌套于循环或 switch 结构中的语句里。
  • 对于 if 语句,只有当它嵌套在 switch 或循环里面时,才能使用 break 。

16. continue语句 p184

  • continue 语句只能出现在 for 、 while 、 或者do while 循环中,包括嵌套在这些循环内部的块语句中。

17. C++参数传递 p199

  • 每次调用函数时,都会重新创建该函数的所有的形参,此时所传递的实参将会初始化对应的形参。
  • 形参的初始化与变量的初始化一样:
    • 如果形参具有非引用类型,则 复制 实参的值。
      • 缺点:
        • 不能在函数中修改实参的值。
        • 当需要以大型实参作为实参传递时,复制对象所付出的时间和存储代价比较高。
        • 有些对象没法实现复制。(类类型)
    • 如果形参为引用类型,则它只是实参的别名。
      • 在C++中 使用 引用 来传递实参比使用 指针 来传递实参更安全。
  • 指针形参:如果需要保护指针指向的值,则形参需定义为指向 const 对象的指针:
    • 一方面,我们可以为 const 形参传入“const”对象实参,例const int *类型
    • 另一方面,我我们也可以为 “const形参” 传入 “非const” 对象的实参。例:int *类型。
    • 原因:可以将指向 “const” 对象的指针初始化为指向 “非const” 对象,但不可以让指向 “非const” 对象的指针指向 “const” 对象。
  • Const 形参 (非引用)(情况和 “指针形参相反”):
    • 在调用函数时,如果函数使用 “非引用(指针形参属于引用类型)的非 const 形参”:
      • 既可以给该函数传递 “Const实参”。
      • 也可以给该函数传递 “非 Const 实参”。
      • (因为该初始化是 非引用形参 ,属于初始化复制了初始化的值,所以可以用 “Const” 对象初始化“非Const”对象,反之亦然)
      • 另一原因:兼容C语言,在C语言中,具有 Const形参 或 非Const形参 的函数并无区别。
  • Const引用(见18)

18. Const引用 与 非Const引用:p205

  • 应该将不需要修改的引用形参定义为 Const引用
  • 普通的 “非Const引用形参” 在使用时不太灵活,这样的形参既不能用 Const 对象初始化,也不能用字面值或产生右值的表达式实参初始化(会造成编译错误)。
  • 鉴于上述原因,应谨慎使用 “非Const引用形参”。
  • Const引用:
    • 使用 “Const引用” 避免复制,使用引用形参都可以避免复制
    • 将不修改的相应实参的形参定义为Const引用。

19. 指向指针的引用(int *&v1 ) p205

  • int *&v1:的定义应从右至左理解:
    • v1是一个“引用”,与指向 “int” 型对象的指针相关联,
    • 也就是说,v1 是指向一个 int型 指针的引用
    • v1 只是传递进 函数 的任意指针的别名(还是一个指针)

20. Vector 和其它容器类型的形参 p206

  • 通常,函数不应该有 Vector或其它标准库容器类型的形参。
  • 若函数使用 “普通的非引用的Vector形参”,在调用函数时,会复制 Vector 中的每一个元素(代价大,不推荐)
  • 为避免复制 Vector 元素,应考虑将形参声明为引用类型。
    • 但是更好的做法是:通过传递 “指向容器中需要处理的元素的迭代器” 来传递容器。
    • 这样,直接对迭代器进行 “解引用” 操作,即可处理容器内的元素。
    • 例:
    • void print(vector<int> :: const_iterator beg, vector<int> :: const_iterator end) //当不需要改变vector内的元素时,应声明为const类型
    • {
    • while(beg != end)
    • {
    • cout << *beg++;
    • if(beg != end) cout << ' ';
    • }
    • cout << endl;
    • }

21. 数组形参 和 数组实参 p206

  • 数组有两个特殊性质:
    • 1. 数组不能复制---无法编写使用数组类型的形参
    • 2. 使用数组名字时,数组名会自动转化为指向其第一个元素的指针---处理数组元素通常只能通过操纵指向数组中元素的指针来处理。
  • 数组形参的定义方式:(以下三种定义是等价的)
    • void print(int *) {} // 推荐用法,直观无歧义,一般使用 “非引用” 类型传递,当不需要修改数组元素时,应定义为 “指向Const对象的指针”
    • void print(int []) {} // 虽然不能直接传递数组,但是函数的形参可以写成数组的形式。
    • void prin(int [10]) {} // 编译器会忽略任何数组形参指定的长度,10 无效。
    • 编译器检查数组形参关联的实参时,他只会检查实参是不是指针、指针的类型和数组元素的类型是否匹配,而不会检查数组的长度。
  • 通过引用传递数组:
    • 如果形参是数组的引用,编译器不会将数组实参转化为指针,而是传递数组的引用本身,这种情况下,数组的大小将会成为形参和实参类型的一部分
    • 编译器在检查形参时也会检查实参的大小是否和形参的大小相同。
    • 只有那些实参的长度和形参的长度相同的实参才能完成正常的调用。
    • 实参的长度和形参的长度不同时,调用会出错。(这种机制另一方面体现了一种安全性)
    • 例:
      • void print(int (&array) [10]) // array两边的括号是必须的,因为下表操作具有更高的优先级,10也是必须的
      • {
      • for(size_t i = 0; i != 10; ++i)
      • {
      • cout << array[i] << endl;
      • }
      • }
  • 多维数组的传递:
    • 和其他数组一样,多维数组以指向 0 号元素的指针的方式传递。
    • 但是由于:多维数组中的元素本身就是数组,除了第一维以为的所有维的长度都是元素类型的一部分,必须明确指定。
    • 即:需要明确指定多维数组中除第一维之外的其他维的长度。
      • 例:(以下两种定义方式等价)
      • void print(int (matrix*)[10], int rowsize) {} // 10 必须指明, matrix为指向含有 10 个 int 型元素的数组的指针----数组指针 指向一维数组的指针,机试该指针指向的数组含有 10 个元素,但是该指针实际上指向的是 ”数组的首元素“,即该指针是指向 1 个元素的指针,而不是指向 10 个元素的指针。 因此,若遇到 int (*p) [n]时,此时的 p 一定指向多维数组。
        • int a[2][10] = {};
        • print(a, 2);
      • void print(int matrix[] [10], int rowsize]) {} // 10 必须指明,实际上形参是一个指针,指向数组中的元素,数组中的每个元素本身就是含有10个int型对象的数组。
        • int a[2][10] = {};
        • print(a, 2);
  • 可以使用类似vector容器的传递方法(传递一个迭代器),来传递指针的”首元素“和”末元素“的指针来访问数组。(安全,不会越界)

22. 指针数组 和 数组指针

  • 数组指针 --- 指向数组的指针(本质是一个指针变量) C语言中专门用来指向二维数组的
    • 定义:int (*p) [n] ; // 由于 "()" 优先级高,p是一个指针,指向一个整形的一维数组,该数组长度 n ,也是 p 的 ”步长“
    • 执行 p + 1 时,p 要跨过 n 个整型数据的长度。
    • 将一个二维数组赋值给一个指针:
      • int a[3][4];
      • int (*p)[4]; // 本语句是定义一个数组指针,指向含有 4 个元素的一维数组。
      • p = a; //将该二维数组的首地址赋给 p,也就是 a[0] 或 &a[0][0]。
      • p++; //该语句执行过后,也就是 p = p + 1; p 跨过行 a[0][] 指向了行 a[1][]。所以 “数组指针” 也称为 “行指针” 。p[i]不是一个元素,而是一行元素。
  • 指针数组 --- 包含元素为指针的数组(本质是一个数组)
    • 定义: int *p[n]; // []的优先级高,先与 p 结合成为一个数组,再由 int* 说明这是一个指针数组,数组元素为指针类型,即包含 n 个指针。
    • 执行 p + 1; 操作时错误的, 执行 p = a; 操作也是错误的,因为 p 只不过是一个 “数组名字”。
    • p[0]、p[1] ... ... p[n-1] 操作是允许的 ,且 p[i] 是指针变量,用来存放变量地址, *p = 5;// 改变 p[0]指向的位置的内容
    • 可以将一个二维数组赋值给一个指针数组:
      • int *p[3];
      • int a[3][4];
      • for(i = 0; i < 3; ++i)
      • {
      • p[i] = a[i]; // 指针数组 p 中存放着三个指针变量 p[0]、p[1]、p[2],a[i] 分别代表 a 中的第 i 行元素的首址。
      • }
  • 数组指针只是一个指针变量,在 C 语言里专门用来指向二维数组的,它只占一个指针的存储空间。
  • 指针数组是多个指针变量,以数组的形式存在于内存当中,占有多个指针的存储空间。
  • 数组指针和指针数组同时指向二维数组时,其引用和数组名引用都是一样的:
    • *(p[i] + j)、*(*(p + i) + j)、(*(p + i))[j]、p[i][j]。都表示他们所指向数组的第 i 行,第 j 列的元素。

23. 引用返回左值 p215

  • 返回引用的函数返回一个左值,这样的函数可用于任何要求使用左值的地方。
  • 例:
    • char &get_val(string &str, string :: size_type ix)
    • {
    • return str[ix];
    • }
    • int main()
    • {
    • string s("a value");
    • cout << s << endl; // 输出“a value”
    • get_val(s, 0) = 'A' ; // 改变 s 中的内容,令 s[0] = 'A'
    • cout << s << endl; // 输出“A value”
    • }
  • 如果不希望引用的返回值被修改,返回值类型英爱声明为 Const: const char &get_val()......

24. 默认实参 p218

  • C++中可以为函数提供默认实参,如果函数中有一个形参具有默认实参,那么,它后面所有的形参都必须有默认实参。
  • 调用包含默认是惨的函数时,可以为该形参提供实参,也可以不提供。
  • 在一个文件中,只能为一个形参指定默认实参一次。
  • 通常,应在函数声明中指定默认实参,并将该声明放在合适的头文件中,在函数的定义处就不需要添加默认实参了。
  • 如果放在函数的定义文件中,则只有在函数的定义文件内调用该函数,默认实参才有效。
    • 例:
      • 文件xxxx.h: int f(int a, int b = 10); //默认实参在这里说明
      • 文件xxxx.cpp: int classA::f(int a, int b) {......} // 此处不能再添加默认实参了否则会引发编译错误。

25. 静态局部变量 p220

  • 一个变量如果位于函数的作用域内,但生命周期能够跨越这个函数的多次调用,这样的对象应定义为 static (静态的)
  • static 局部变量 确保 不迟于执行流程第一次经过该对象的定义语句时进行初始化
  • 这种对象一旦被创建,在程序结束前都不会被注销。
  • 当定义静态局部对象的函数结束时,静态局部对象不会被撤销。
  • 在该函数的多次调用的过程中,静态局部对象会持续存在并保持它的值。
    • 例:在第一次调用 count 函数之前,ctr 就已创建并赋予初值0;
      • size_t count()
      • {
      • static size_t ctr = 0; // ctr的值在整个程序的执行结束前都有效。
      • return ++ctr;
      • }
      • int main()
      • {
      • for(size_t i = 0; i != 10; ++i)
      • cout << count() << endl;
      • return 0;
      • }

26. 内联函数 p221

  • 内联函数通过在函数声明时添加关键字 “inline” ,并且在函数声明中直接给出函数的定义。
  • 不能将内联函数的 “声明” 和 “定义” 分开放在两个文件中,那样会造成编译错误。
  • 在类的内部(声明部分)“定义” 的函数默认作为 “内联函数” 处理。
  • 在 内联函数 中不能含有 递归、循环语句,同时内联函数是否会被直接展开不是绝对的,由编译器根据情况决定。
  • 内联函数应该在头文件中 “定义” !
      • 例:class A
      • {
      • public: // public 部分定义的成员可被使用该类型的所有代码访问
      • // 内敛函数,在此处同时给出声明和定义。
      • inline const string &shorterString(const string &str1, const string &str2)
      • {
      • return str1.size() < str2.size() ? str1 : str2 ; // 内联函数里面最好不要包括递归和循环语句。
      • }
      • protecter: // 在 private 部分定义的成员可被本类的其他成员访问。但该类的对象不能访问
      • private: // 用于继承
      • } ; //要注意 class 结尾处的 “分号(;)”
      • int main()
      • {
      • A object;
      • string a , b = "shorter", c = "long string";
      • a = object.shorterString(b, c); // a 的结果为 b 和 c 中比较短的 string 即为 b 。
      • }

27. 类的 Const 成员函数 p224

  • 每一个成员函数(除了 static 成员函数外)都有一个额外的、隐含的形参 this 。--- 类似python中的函数在声明时提供this形参,调用时不提供this形参而直接使用。
  • Const 成员函数该变了隐含的 this 形参的类型,使得 this 形参将是一个指向 “本类对象的Const object*类型的指针”
  • Const 成员函数的结果 --- Const成员函数不能修改调用该函数的对象。
  • 注意:
    • Const 对象指向 Const对象的指针或引用 只能 用于调用其 Const 成员函数。---- 对象:类的实例化
    • 使用 Const对象、指向 Const 对象的指针或引用来调用 非 Const 成员函数 是错误的
  • 不能在成员函数的形参表中显式地使用 this 指针,但是可以在成员函数的函数体内 显式地 使用 this 指针。
  • 成员函数的声明若为 Const 成员函数,那么在函数的定义的时候 形参列表的后面也必须有 Const。
  • 例:double sale(const sale &rhs) const; // 形参列表实际包含两个形参:this,const sale &rhs 。
  • 类的成员函数可以访问类的 Private 成员,但是类的对象无法访问类的 Private 成员。

28. 构造函数:p225

  • 合成的默认构造函数
    • 一般适用于:仅包含类类型(string,vector等)的成员的类。
  • 自定义默认构造函数
    • 一般将构造函数定义为 Public 的,如果定义为 private 的,则不能定义(实例化并初始化)类的对象,因为类的对象无法访问类的 private 成员。
    • 对于含有内置类型(int,float等)或复合类型(struct、union等)成员的类,应定义他们自己的默认构造函数初始化这些成员。
    • 构造函数初始化列表:className(arg1,arg2):item1(arg1), item2(arg2) {}

29. 重载函数 p228

  • 具有相同的名字而形参表不同的函数,称为重载函数。
  • 如果两个函数的返回类型形参表完全相同,则将第二个函数声明视为第一个的重复声明
  • 如果两个函数的形参表完全相同,但是返回类型 不同,则第二个声明是错误的。--- 函数不能仅仅基于不同的返回类型而实现重载
  • 重载函数都应在同一个作用域中声明(处于同一层次)----如果局部的声明一个函数,则该函数将屏蔽而不是重载在外层作用域中声明的同名函数。
  • 重载和 Const 形参:
    • 仅当形参是引用或指针时,形参是否为 const 才有影响,当形参是非引用或指针时,形参的初始化方式为复制,const 形参和非 Const 形参无区别。
    • 可基于 “引用形参” 是指向 Const 对象还是非 Const对象 来实现重载。
      • record lookup(account &)
      • record lookup(const account &) // 重载
      • const account a(0); account b;
      • lookup(a); // 调用 lookup(const account &)
      • lookup(b); // 调用 lookup(account &)
      • 如果形参是普通的引用(非Const引用),则不能将 Const 对象传递给这个形参。
      • 如果传递了 Const 对象,则只有带 Const 引用形参的版本才是该调用的可行函数。
      • 如果传递的是“非Const对象”,则上述任意一种函数皆可行,非Const 对象既可用于初始化 Const 引用,也可用于初始化 非Const 引用。
      • 但是将 Const 引用初始化为 非Const对象,需要通过转换来实现,而非 Const 形参的初始化则是精确版本。
    • 但是不可基于 “指针本身是否是 Const 的” 来实现重载:
      • f(int *);
      • f(int *const); //二次声明而非重载
    • 可基于 “指针形参” 指向的对象是 Const对象还是非 Const 对象 来实现重载。
    • 当形参以副本传递时,不能基于形参是否为 Const 来实现重载。

30. 指向函数的指针 p237

  • 函数的类型:由函数的返回值和形参列表确定,而与函数名无关。
  • bool (*pf) (const string&, const string&); //将 pf 声明为指向函数的指针,它所指向的函数带有两个 const string & 类型的形参和 bool 类型的返回值。pf 两侧的括号是必须的
  • 可使用 typedef 简化函数指针的定义:
    • typedef bool (*cmpFcn) (const string&, cosnt string &); // cmpFcn 成为了一种指向函数的指针类型的名字(而不是变量名字,就像 int 一样是类型的名字)
    • cmpFcn 类型为指针类型:指向返回 bool 类型并带有两个 const string 引用形参的函数的指针。
    • 可以直接使用 cmpFcn 来定义这样的类型的变量:cmpFcn pf1 = 0; // 定义了一个 cmpFcn 类型的指针,该指针不指向任何函数(0)
    • 若有:bool len(const sting&, const string&);
    • cmpFcn pf2 = len;
    • pf1 = len;
    • pf2 = pf1;
    • cmpFcn pf1 = len; 等价于: cmpFcn pf2 = &len;
    • len("this", "is");pf1("string", "is"); (*pf1)("this", "is");

31. cout 与 cerr:

  • cout的输出可以重定向到一个文件中,而cerr必须输出在显示器上。

32. 文件的输入和输出 p251

  • fstream 头文件定义了三种支持文件IO的类型:(使用以下三个类型时,需要包含 fstream 头文件)
    • ifstream,由 istream 派生而来,提供 “读文件” 的功能。
    • ofstream,由 ostream 派生而来,提供 “写文件” 的功能。
    • fstream,由 iostream 派生而来,提供 “读写同一个文件” 的功能。
  • fstream 类型除了继承下来的行为外,还定义了两个自己的操作:open 和 close。
  • (1)为 ifstream 或者 ofstream 对象提供文件名作为初始化式,就相当于打开了特定的文件。
    • ifstream infile; // 声明一个 输入文件流
    • ofstream outfile; // 声明一个输出文件流。
    • infile.open("in"); // 在当前目录下打开文件 in
    • outfile.open("out"); // 在当前目录下打开文件 out
  • (2)IO标准库使用 C 风格字符串而不是 C++ string 类型的字符串作为文件名。
    • 一般的做法:将文件名读入 string 对象,而不是 C 风格字符数组,在调用 string 对象的 c_str() 成员获取 C 风格字符串。
  • (3)打开文件后,通常要检查打开是否成功,这是一个好习惯。
    • if (! infile)
    • {
    • cerr << "error : unale to open input file."
    • }
    • 同时也可以通过测试对象: if (outfile) // outfile 是否可以使用 ,当返回 true 时意味着文件已经可以使用。也可以通过 if (!outfile) 处理 outfile 不能用的情况。
  • (4)fstream 对象一旦打开,就保持与指定的文件相关联。
    • 如果想要把 fstream 对象与另一个不同的文件关联,则必须先关闭(close)现在的文件,然后打开(open)另一个文件。
      • ifstream infile("in"); // 打开名为 in 的文件
      • infile.close(); // 关闭当前的文件流
      • infile.clear(); // 清除流的状态
      • infi.open("next"); // 打开新的名为“next”的文件---重新绑定流
  • 如果程序员需要重用文件流读写多个文件,必须在读另一个文件之前调用 clear 清除该流的状态。

33. 字符串流 p257

  • iostream 标准库支持内存中的输入/输出,只要将流与存储在程序内存中的 string 对象捆绑起来即可。
  • 可使用 iostream 输入和输出操作符读写这个 string 对象,标准库定义了三种字符串流
    • istringstream,由 istream 派生而来,提供 读 string 的功能。
    • ostringstream,由 ostream 派生而来,提供 写 string 的功能。
    • stringstream,由 iostream 派生而来,提供 读写 string 的功能。
    • 要使用上述类,必须包含 sstream 头文件。
  • 像 fstream 在 iostream 外定义了 open 和 close 一样,这些类还定义了名为 str 的成员,用来读取或设置 stringstream 对象所操纵的 string 值。
    • stringstream strm; // 创建自由的 stringstream 对象。
    • stringstream strm(s); // 创建存储 s 的副本的 stringstream 对象,其中 s 是 string 类型的对象。
    • strm.str(); // 返回 strm 中存储的 string 类型对象。
    • strm.str(s); // 将 string 类型的 s 复制给 strm ,返回 void 。
  • stringstream 的作用:在多种数据类型之间实现自动格式化:
    • int val1 = 512, val2 = 1024;
    • ostringstream format_message;
    • format_message << "val1: " << val1 << "\n" << "val2: " << val2 << "\n"; // 此时,int型自动转换为等价的可打印的字符串。
    • format_message 包含的内容:val1: 512\nval2: 1024
    • istringstream input_istring(format_message.str());
    • string dump;
    • input_istring >> dump >> val1 >> dump >> val2;
    • cout << val1 << " " << val2 << endl; // prints 512 1024

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值