C++阅读笔记
在阅读C++经典著作的同时,总结一些心得和应该注意的地方。有《C++语言程序设计(梁勇)》,《effective c++》,《more effective c++》,《c++ primer》等。
笔记内有很对重复的点,我个人觉得这样更有利于学习和记忆。忘不了过往的人才会对过去认真。在一次次的冥想中,一张张挥之不去的画面在脑海中轮播,于是乎那些你在意的节点最终会变得刻骨铭心。
- 不要在循环继续条件中使用浮点数的相等性判断。有些数的浮点值表示其近似值,而不是精确值。
- 生成随机数,引头文件<ctime>
srand(time(0));
int num=rand();//0~RAND_MAX
- void函数是不需要return语句的,但是可以在void函数中使用void语句结束函数,返回调用者,语法如下:
return;
- 有时候需要在不正常的情况下终止程序。这个功能可以通过调用exit(int)函数,在cstdlib头文件中。可以通过传递任何一个整数来调用这个函数的显示程序中的错误。
- 重载函数必须有不同的参数列表。你不能依据不同的返回值类型重载函数。
- 如果函数的参数中,有的设置缺省值,有的没有,那么带缺省值的参数应该放在参数列表的末尾。
- 内联函数机制对于短函数而言是值得使用的,但并不适合在程序中多次被调用的长函数。在此情况下使用内联函数会急剧增加可执行代码的长度。
- 对于一个变量,以全局变量的形式声明一次,然后就可以在所有函数中使用它,而无须重新声明,这看上去挺有吸引力。但是,这是一种不好的编程习惯。由于所有函数都可以改变全局变量的值,这可能会导致难以调试的错误。应尽量避免使用全局变量。当常量永远不会改变时,使用全局变量是没问题的。
- 当使用引用传值的时候,实际参数必须是一个变量。当使用值传递的时候传入的参数可以是一个数值,一个变量或者是一个表达式,甚至是另一个函数的返回值。
- 可以指定一个常量引用参数,以防止变量的值被意外修改。如const int& num;
- 对于string类型的对象引用传递会比值传递更有效,因为对象可能占据大量内存。然后对于int和double类型,区别是微不住道的。
- C++要求在数组声明中数组大小必须是常量表达式。例如,下面代码是非法的:
Int size=4;
Double mylist[size];
但是,如果如下面的代码使用一个常量SIZE作为数组的大小,就是合法的:
Const int size=4;
Double mylist[size];
- 使用数组初始化语句,只能在一条语句中完成数组声明和初始化,将两者分开会导致一个语法错误。如下面语句是错误的:
Double mylist[4];
Mylist={1.1,1.3,3.2,2.5};
- 函数中传递一个数组意味着数组的起始地址被传递给形参,属于传递共享,也就是函数中的数组和传递给函数的数组是同一个。可以在函数参数中定义const数组参数防止数组在函数中被修改。如果在函数f1中定义了一个const变量,而且这个参数被传递给另一个函数f2,那么f2中对应的参数必须声明为const类型,确保一致性。
- C++不允许返回数组,但可以通过向函数传递两个数组来解决这个限制。
- size_t是一个C++类型。对于大多数编译器,它和unsigned int相同。
- 关于C字符串处理函数,除了转换函数atoi、atof、atol定义在cstdlib头文件下,其他的都定义在ctring头文件下:
|
函数 |
描述 |
|
size_t strlen(char s[]) |
返回字符串长度,即在空终结符之前的字符个数 |
|
strcpy(char s1[],const char s2[]) |
将字符串s2复制到s1中 |
|
strcpy(char s1[],const char s2[],size_t n) |
将字符串s2中前n个符号复制到s1中 |
|
strcat(char s1[],const char s2[]) |
将字符串s2拼接到s1之后 |
|
strcat(char s1[],const char s2[],size_t n) |
将字符串s2前n个符号拼接到s1之后 |
|
strcmp(char s1[],const char s2[]) |
通过字符的数值码对比,判断s1大于、等于或小于s2,分别返回一个大于0的数、0、小于0的数。 |
|
strcmp(char s1[],const char s2[],size_t n) |
类似于strcmp,但是指定了比较s1和s2中的n个字符 |
|
int atoi(char s[]) |
返回字符串对应的int型值 |
|
double atof(char s[]) |
返回字符串对应的double型值 |
|
long atol(char s[]) |
返回字符串对应的long型值 |
|
void itoa(int value,char s[],int radix) |
获得一个字符串的整数值,基于一个指定的集数 |
- 可以用二维数组作为函数的参数,但C++要求在函数参数类型声明中,指明列的大小。
- C++库中的类名都是小写形式,这样易于和我们自定义的类相区分。
- 为了避免多次包含错误,通常采用下面的模板和符号命名惯例:
#ifndef ClassName_H
#define ClassName_H
A class header for the class named ClassName
#endif
- 如果一个函数是在类定义内实现的,那么它就自动成为一个内联函数。这也被称为“内联定义”。
- 把数字转换成字符串:
<sstream>头文件中的stringstream类提供接口可使我们类似处理输入/输出流一样来处理字符串。一个应用就是把数字转换成字符串。
stringstream ss;
ss<<3.1415;
string s=ss.str();
- 应使用ClassName::functionName(arguments)调用静态函数,以及ClassName::staticVariable访问静态变量,这会提高程序的可读性,可以容易辨别出类中的静态函数和静态数据。
- 方法中的string类型的参数一般按引用方式传递。如string& newname,这可以避免编译器进行对象拷贝,从而提高效率。如果引用参数被限定为const可以避免参数被无意修改。在传递对象时,应该总优先使用按引用传递的方式。
- C++中string类中封装了字符数组,提供了诸多函数应用于处理字符串,如 append、assign、at、clear、erase、empty、length、c_str、compare、substr、find、insert、replace等。
- 用cin读取以空格结尾的字符串,用getline(cin,s,delimiterCharacter)读取以特定分隔符结尾的字符串。
- 可以通过传值和传引用两种方式给函数传递对象做参数,使用传引用方式性能更好。
- 一般以P开头来命名指针
- 可以使用typedef关键字来定义同义类型,语法如下:
typedef existingType newType;
- 函数min_element、max_element、sort、random_shuffle和find都可以应用在数组上。在头文件<algorithm>
- 使用new操作符分配的内存是持久存在的,直到它被显示释放或者程序退出
- C++中,局部变量在栈中分配空间,而由new操作符分配的内存空间则出自于称为自由存储区(freestore)或者堆(heap)的内存区域。
- delete只能用于指向new操作符创建的内存指针,否则会导致不可预料的问题。例如,下面的代码就是错误的,因为p指向的内存并不是有new创建的。
int x=10;
int* p=&x;
delete p;
- 当类中包含指针数据域,而该指针指向动态分配的内存时,应使用自定义的析构函数。不然,使用该类的程序将会有内存泄露。
- 自定义的拷贝构造函数不会影响缺省的赋值运算符=的逐项拷贝行为。
- 使用<typename T>或<class T>指定类型参数都是可以的。使用<typename T>更好些,因为它含义更明确,而<class T>有可能与类声明混淆。
- 有时,一个模板函数可能有多个类型参数,在此情况下,应该讲将所有参数都放在尖括号内,以逗号隔开,如<typename T1,typename T2,typename T3>。
- 当设计一个通用的函数时,最好先设计一个非通用的版本,测试完毕后,再将其转化为通用版本。
- 通常,将类的定义和实现分隔在两个文件中。但是,对于模板类,将定义和实现放在一起更为安全,因为某些编译器不支持将两者分离。
- C++允许为模板类中的类型参数指定一个默认类型(default type)。例如,可以将int 类型赋予通用类stack中的类型参数T,作为缺省类型,如下所示:
template<typename T=int>
class Stack{
…….
};
- 在下面语句中两个“>”之间有个空格
vector<vector<int> >matrix(4);//four rows
如果没有空格,一些编译器可能会报错。
- struct成员默认访问方式是public,而 class默认访问方式是private!
- exit函数终止程序执行会调用析构函数 ,abort函数终止程序不会调用析构函数!
- 静态局部变量直到程序终止时才退出!
- 通过public 函数返回 private成员的引用有可能会破坏类的封装 ,造成外部变量可以改变类私有成员值!
- 常量对象只能调用常量成员函数,常量成员函数可以有非常量版本重载!
- 常量数据成员只能在定义时初始化或者在构造函数里用成员初始化值来初始化 ,不能用赋值语句来初始化!
- 要在析构函数里面使用delete来释放使用 new申请的内存空间!
- 编写析构函数来释放类中成员所申请的内存空间和使用深拷贝函数是好的编程习惯!
- operator++()是相当与++d,operator++(int) 是相当于 d++ !
- 如果父类中函数是虚拟函数 ,那么在每个子类中显式声明虚拟函数是好的编程习惯!
- 如果类作为基类,其析构函数要声明为虚析构函数 ,这样派生类才可以调用自己的析构函数!
- 一个new 就对应一个 delete是好的编程习惯!
54. istream输入read 是从文件中读取 ,ostream输出write 是写到文件中去!
55. istream是seekg 重置文件指针位置 ,ostream是seekp, 文件用file.eof()来判断是否读取到文件尾部!
56. assert(条件), 当满足条件时就不 assert,不满足条件才会assert!
57. 对于不需要修改的变量,使用常量引用来传递参数可以既保证安全性又保证效率!
58. 在循环之前调用srand(time(0)),然后在循环中调用 rand()函数可以使得每次随机的结果都不相同!
59. 一维数组形参如下:int a[],二维数组形参如下 :int a[][SIZE],第一个[] 是空的,后面必须非空!
60. #define A 10 后面没有分号!
61. 在局部函数中用new创建的变量是存在内存中的,即便局部函数执行完毕内存变量仍然存在,但是指向它的指针有可能是局部变量,则需要在局部函数结束前调用 delete释放内存空间,以免内存泄漏
62. 数组声明最好用 T *a 来声明 ,这样不容易出错,创建对象最好用 new 而不是直接创建!
63. 定义一个类时,析构函数(释放资源)和拷贝构造函数(深拷贝)最好显示定义!
64. C/C++语言输入结束符是ctrl+z(windows下 )!
65. C语言的printf(“%.9lf”,a); 比c++的 setprecision(10)来的更加精确,C++有时自动舍入精度!
66. 理清状况,逻辑严谨,变量初始化、是还是否、边界判断判断正确!
67. 要使得栈中的基本元素是模板类类型,必须要定义模板类的一些函数:默认构造函数,友员输出函数等!
68. 用单件模式和全局函数替代全局变量,以便于拓展和维护!
69. 用const char *ptr 表示ptr指向的变量为常量。 char* const ptr表明ptr 本身为常量是好的编程习惯!
70. const int *i = &a, 只是代表不能通过 i指针来修改,但是可以通过其它途径来修改 a,例如a=3 !
71. 灵活熟练运用语言,如 (a<b?a:b<c?b:c) = val();这句话就将3 个if语句合并成一句话!
72. 桥接模式bridge 可以实现接口与实现分离 ,这样可以减少修改类时需要修改的类的范围!
73. 优先级:i++ 高于 ++i ,注意,具体运算符优先级看下面的运算符优先级表!
74. 递归算法符合大致算法思想就行了 ,不必深究思考,合理就可以了!
75. 在C++ 中使用0来对指针初始化可以保证数字字面常量 0可以转换成任何一种指针类型对应的空指针!
76. 非const 对象可以调用 const和非const 函数,但是 const对象只能调用const函数!
77. 可以使用非const 类型变量赋给 const类型参数,不能用const类型变量赋值给非const类型参数!
78. 类重载二元运算符时,要么作为类的只带一个参数的内部函数 ,要么作为类中带两个参数的友元函数!
79. 内建的operator-> 是二元的 ,而重载的operator-> 是一元的 ,如果重载了operator-> 则先调用重载的 ,直到重复调用到内建的operator->才终止!
80. 尽量使用STL 是好的编程习惯!
81.++i返回的是内存变量,可以作为左值也可以作为右值 ,而i++ 返回的是字面量 ,不占内存, 不能作为左值右值!
82. for循环中使用continue 不会出现死循环 ,但是while 中使用continue容易出现死循环 ,因为可能i 没有自加!
83. 取最大优先分析原则会取最长的类型 ,所以要这么义:list<vector<string> > lovos;右边两个>> 之间必须有空格 ,如果没有则编译器可能解释成 >>右移操作符!
84. 对于平凡整型常量,可以用枚举量来表示!
85. 派生类赋值给基类是不要使用对象继续赋值 ,而是使用指针或引用以避免发生截切!
86. 不使用 void * 为类型转换的中介类型是良好的编程习惯 ,因为void * 会抹除指针的类型信息!
87. 使用引用类型或标准库组件以避免引入多级指针的复杂性是较佳的设计!
88. 在类对象中尽量避免使用二级指针 ,尤其是在基类指向派生类对象的指针时尤其要注意!
89. 在C++ 类中尽量不要重载类型转换 operator函数, 而是使用有明确含义的如 toInt(),toChar()等函数替代!
90. C++中有时在类构造函数前加上 explicit关键字来禁止隐式类型转换可以减少很多错误!
91. 多用引用传递参数,用值传递时会有大量的复制操作 ,效率很低, 多用引用是好的编程习惯!
92. 函数对象就是重载了operator()的 class对象, 使用函数对象生成匿名临时对象是好的编程习惯!
93. 使用dynamic_cast<>,static_cast<>,const_cast<>,reinterpret_cast<> !
94. 在派生类构造函数中尽量多使用成员初始化列表 ,静态数据成员和数组不允许在成员初始化表中进行初始化 ,可以在函数体中进行初始化!
95. 在类中涉及到指针和引用等 ,最好显示撰写深拷贝构造函数和赋值运算符重载 operator=(),以免出现错误!
96. 子类在继承父类时如果重写父类的虚函数或者非虚函数 ,要保证在子类中重写的函数访问修饰符和父类结合继承访问后得到的访问修饰符结果一致!
97. 在写拷贝构造函数和重写赋值运算符函数时必须把类中所有变量都用 '='号显示写全了,不要写一部分 ,如果你没写, 则编译器只会调用默认构造函数进行变量初始化 ,而不会默认用'='号!
98. 尽量不要在类中使用静态变量 ,永远不要做运行期静态初始化!
99. 对class 对象或者有可能是 class对象的实体进行直接初始化而不是用赋值语句初始化 ,如:N n(0)!
100. 搞清楚复制初始化和赋值初始化的不同 ,复制初始化是调用拷贝构造函数实现 ,而赋值是重载'='实现!
101. 可以通过在private 区域声明拷贝构造函数来禁止类的复制初始化!
102. 对象作为参数尽量使用引用传递 ,其次考虑指针,尽量不要用值传递 ,如果不想修改对象可以加 const!
103. 抛出匿名临时异常对象,并以引用的方式捕获它们 ,如需重新抛出,则抛出既有的异常对象 ,而非处理原始的异常之后 ,抛出一个新的异常对象!
104.不要返回函数作用域内所分配的存储的引用(返回指针或直接传值可以) ,会造成内存泄漏!
105.使用类的构造函数和析构函数来封装一些成对的操作如加锁和解锁等 ,这样在函数执行开始时创建类对象 ,使用A a; 而不适A a();或 A()来创建, 在函数结束前会自动删除改对象 ,也就实现了开始时调用加锁操作 ,结束时调用解锁操作,俗称 RAII(资源获取即初始化)!
106.复制auto_ptr 模板实例化对象将指向的对象的控制权转移 ,然后把复制源设为空!
107.永远不要把auto_ptr 实例化的类型作为 STL容器的基本类型!
108.不要将auto_ptr 对象作为函数参数按值传递 ,会清空原有对象,造成不可预知的错误!
109.不要在虚函数中提供参数的默认初始化物!
110.访问层级(public protected private) 对重写虚函数没有任何影响 ,即使public 继承,你也可以将继承过来的本来应该是 public的函数写到private 作用域中 ,完全可以!
111.派生类中对基类中的非虚函数的重写或重载其实是遮掩 ,会将基类中同名的所有虚函数和非虚函数全部遮掩 ,使之无效, 这时想要构成重载 ,必须使用using A::fun() 函数来导入基类中 fun函数!
112.重载必须在同一个类作用域下才构成重载 ,否则只是遮掩!
113.重载虚函数要么对基类中的每一个都重写要么一个都不要重写 ,或者使用using A::fun() !
114.C++中尽量避免使用指针,而是使用引用!
115.复制操作不经由模板成员函数来实现 ,必须显示给出复制操作的实现!
116.用户自定义版本的后置式形式自增自减运算符应该返回常量 ,因为后置后置不能作为左值!
117.前置式总是优于后置式,即 ++i总是优于i++ !
118.非成员友员允许在第一个参数上施行类型转换 ,而成员函数不能在第一个参数上施行类型转换!
119.在进行指针重新复制时一定要注意是否可能造成内存泄漏 ,是否要先释放当前指针所指内存空间!
120.mutable变量可以在const 函数中被修改 ,其效果好于const_cast<>, 所以尽量使用 mutable!
121.类重载运算符时有时必须参数和函数都是 const,这样保证在其他函数调用时即使传递常量参数也没问题!
122.变量传递给常量参数可以 ,但是常量传递给变量参数却不行!
123.多使用虚函数实现不同的功能 ,把基类尽量细分成更小的基类!
124.使用动态绑定而不是条件式的分派实现类型相关的需求!
125.如果某个类型可能成为基类 ,则一开始就把它写成抽象基类!
126.以public 方式继承的基类一般应该是抽象的 ,应该集中精力于接口继承而不是代码复用!
127.接口类就是没有数据成员 ,析构函数声明为虚函数,普通成员函数皆为纯虚函数 ,不声明构造函数的类型!
128.要么public, 要么友元函数 ,其它如protected 继承后的函数交叉调用可能会出现问题!
129.尽量不使用class 数组,多用 STL容器, 如vector,而且容器类型是指针而不是对象 ,如vector<B *>!
130.wget w1 = w2;调用拷贝构造函数,而 wget w1;w1 = w2;调用的是类赋值运算符!
131.类参数传值传递是调用拷贝构造函数 ,多用const 引用!
132.enum{star=5};这里定义的star 可以当作常量字面量来用 ,和#define star 5 效果是一样的!
133.用const,enum,inline 来代替#define,使用模板 inline函数代替宏!
134.单纯常量,最好用 const和enum 代替#define,对于形似函数的宏 ,最好使用inline 函数来替代!
135.const 出现在*左边则所指的是常量 ,如果const 出现在*右边 ,则指针是常量!
136.STL中const vector<int>::iterator 相当于T*const,vector<int>::const_iterator相当于const T* !
137.函数的const 版本可以和原版本重载 ,用非const 函数调用 const函数可以减少代码重复!
138.使用函数返回静态对象将 non-local static对象转化为locat static 对象!
139.引用的赋值相当于把等号右边别名所指的对象值替换了等号左边别名所指对象的值,还是 2个对象!
140.可以通过声明纯虚析构函数来定义一个抽象类 ,这时纯虚析构函数必须给出定义才行!
141.析构函数绝对不要吐出异常 ,即使出现异常也要在析构函数内部处理 ,要么结束要么吞下!
142.不要在构造函数和析构函数中调用 virtual函数, 不会下降到 derived class类别, 用向上传递参数替换!
143.令赋值操作符返回一个reference to *this !
144.派生类中的拷贝构造函数和赋值符号不会自动拷贝或赋值基类部分 ,所以要显示调用基类拷贝或赋值函数!
145.在释放内存空间时先把原指针所指内存区域备份 ,再删除或指向新的内存空间 ,再delete 原来备份的区域!
146.多使用tr1::shared_ptr 和auto_ptr智能指针来自动释放内存空间!
147.类中尽量不要定义类型转换符号 ,即使定义也没有返回类型 ,因为类型转换符本身已经规定了返回类型!
148.shared_ptr对象如果有多个指向一个指针 ,则只有第一个需要使用指针初始化 ,其他的要么用拷贝构造函数 ,要么用赋值运算符初始化 ,指针初始化只能用一次!
149.对于内置类型、STL和函数对象 ,传值比较高效,但是对于其它类型 ,传递常量引用比较高效!
150.引用和指针一样支持多态性 ,因为引用的内部就是通过指针实现的!
151.尽量用non-member 和non-friend函数替换 member函数!
152.如果一个函数和类相关,但不是成员函数 ,那么该函数也不一定非得是友元函数 ,如果函数能通过类的公共接口完成功能 ,则完全不必声明为友元函数 ,但该函数返回值一般是const!
153.构造函数如果不是explicit则可以进行隐式类型转换 ,不管构造函数有几个参数 ,但前提是如果你给的实际参数个数小于构造函数需求参数个数 ,你得保证构造函数有默认构造参数!
154.一个类内对象内部函数可以访问对象自己的私有成员变量 ,但是如果有一个同类的另一个对象作为函数的参数 ,则该内部函数也可以访问另一个对象的私有成员变量!
155.尽量往后定义变量的出现 ,最好能等到变量不仅需要被使用而且初值也确定时再定义!
156.尽量避免使用转型,即使必须使用也得使用 C++特定的如static_cast 等!
157.虚析构函数都是public,没有其它的像 private和protected 类型的!
158.根据实际需要选准模式,是 is-a 还是has-a,private 继承和has-a类似 ,但是一般都是会选择has-a!
159.因为private 继承不是 is-a关系, 所以不能满足基类指针指向派生类的情况 ,当然不能使用多态性,只能看作是和 has-a一样的情况了!
160.virtual函数即使是private 函数且被 private继承, 虽然对于派生类不可访问 ,但派生类还是可以继承基类 virtual函数并重写的,并无影响!
161.指针可以改变其指向的对象 ,但是引用不行,引用一旦赋值后不能改变!
162.重载某个操作符时应该返回引用而不是指针!
163.隐式转换最多只能转换一次 ,超过一次就被禁止!
164.前缀返回引用,后缀返回 const value,后缀通过前缀实现,前缀效率比后缀高!
165.在析构函数中释放资源,尽量使用引用来捕捉异常 ,而不用传值或指针!
166.模板和异常规格不要混合使用 ,异常规格是一个应该被审慎使用的特性!
167.STL中对于iterator 尽量运用 (*iterator).first来取值, 而不是用 ->来取值!
168.C++禁止为非常量引用创建临时变量 ,所以非常量引用传递参数时不会发生隐式类型转换!
169.常量引用是会创建临时变量的 ,这时会发生隐式类型转换!
170.每一个重载运算符必须自带至少一个用户自定义类型的参数!
171.操作符 -> 优先级高于 * !
172.每种类有一个vtbl,每个对象有一个 vptr,RTTI是在vtbl 中增加一项 ,所以只会增加类大小,而不会增加对象大小!
173.析构函数必须被定义!
174.auto_ptr对象最好使用const引用传递 ,而不要用值传递!
175.编译器只能默认帮你进行一次类型转换 ,超过一次就不行!
176.对灵巧指针绝对不要提供一个转换到原始指针类型的转换操作符!
177.当使用类型转换太麻烦时 ,不妨就使用模板吧!
178.一定要为含有指针的类提供深拷贝构造函数 ,以免在以后的运用中出现错误!
179.构造函数和析构函数可以被子类继承 ,但如果析构函数是虚函数 ,继承的子类的析构函数也默认是虚函数 ,但你最好加上virtual增加可读性!
180.cout<<setiosflags(ios::fixed)<<setprecision(n)<<s1<<endl; 用来设置 C++中输出小数点后位数!
181.使用extern “C” 可以将函数变成 c函数风格, 即obj文件中不能进行名变换!
182.将十进制化为二进制或判断能否被 2整除或者除以2可以使用右移操作和 &1操作!
183.C++和C语言中,把整数赋值给字符变量,是将整数当作ASCII码赋给字符变量,将字符变量赋值给整数也是将字符变量对应的ASCII码赋给整数!
184.C++中STL容器的工作方式是对内建类型是位拷贝,自定义对象是存入对象的拷贝,使用insert或是push_back时都是使用拷贝,如果创建对象时是在堆中,则传指针给STL,如果在栈中,则传递对象本身!
185.C++中使用STL容器时,若对象拷贝动作较多,基类又要记录派生类,则存放指针,若是基本类型或者很少拷贝,则存放对象!
186. 可以使用如下语法打开一个输入流:
ofstream output(“data.txt”);
等价于:
ofstream output;
output.open(“data.txt”);
如果一个文件已经存在,文件的内容将被清除,系统不会给出任何警告信息。
187. 在调用open函数后,立即使用函数fail()来进行检测。如果fail()返回true,则表示文件不存在。
ifstream input;
input.open(“data.txt”);
if(input.fail()){ //文件不存在
}
eof()可以用来检测文件是否读到文件尾,读完返回true。
188.由于标准C++中,传递给输入流构造函数或者open函数的文件名必须是C字符串,需要使用string类的c_str()函数进行转换,把string对象转换为C字符串。
189.函数getline可以用来读入包含空格的字符串,函数get/put可用来读单字符,getline()函数语法如下:
getline(ifstream& input,int string s,char delimitchar);
使用流提取运算符(>>)读取数据存在一个问题,其算法认为所有数据都是以空格符分隔的。
190. fstream可以创建既能输入又能输出的文件对象。为了用fstream对象打开一个文件,必须指定文件打开模式,告知C++要如何使用文件。
|
模式 |
描述 |
|
ios::in |
打开一个文件用于输入 |
|
ios::out |
打开一个文件用于输出 |
|
ios::app |
所有输出数据追加于文件末尾 |
|
ios::ate |
打开一个输出文件。如果文件已经存在,移动到文件末尾。数据可写入文件的任何位置。 |
|
ios::trunc |
如果文件已经存在,丢弃文件内容。(这实际上是ios::out的缺省方式) |
|
ios::binary |
打开一个文件用于二进制文件的输入输出 |
一些文件模式也可用于ifstream和oftream对象。例如,用ofstream对象打开一个文件时,可以使用ios::app模式,这样就可以向文件附加数据。但是对于一致性和简明性考虑,最好只对fstream对象使用文件模式。
可以用“|”运算符组合使用多个模式。例如,为了打开一个名为city.txt的输出文件用于附加数据,可以使用如下语句:
fstream stream;
stream.open(“data.txt”,ios::out | ios::app);
191. 计算机本身是不区分二进制文件和文本文件的。所有文件实际上都存储为二进制格式,因此所有文件本质上都可以说是二进制文件。文本I/O是建立在二进制I/O基础上的,在这之上提供了一层字符编码/解码的抽象。
二进制I/O不需要任何转换。如果采用二进制I/O方式向文件中写入一个数值,那么内存中存储的值会被原样复制到文件中。为了在C++中进行二进制I/O,必须以二进制方式ios::binary打开文件。缺省的情况下,文件是以位文本方式打开的。
为了读/写二进制文件,必须对流对象使用read和write函数。
192.常需要向文件中写入非字符数据,C++提供了reinterpret_cast运算符来实现此目的。此运算符可以将一个指针类型转换为与其不相关的指针类型,他只是简单地进行了指针值的二进制复制,并不改变指针指向的数据。语法如下:
reinterpret_cast<datatype*>(address)
其中address是输出数据(基本类型、数组和对象)的起始地址,dataType是希望转化出的数据类型。
193.随机访问文件时,可用函数seekg()和seekp()移动文件指针到任意位置。函数seekp(”seek put”)用于输出流,seekg(“seek get”)用于输入流。两个函数都各有两个版本——一个参数的版本和两个参数的版本。一个参数版本,参数指出绝对位置,例如:seekg(0)将文件指针移动到文件开始的位置。
两个参数的版本,第一个参数是长整型,指出偏移量,第二个参数称为定位基址(seek base)指出偏移量相对于哪个位置。
表1定位基址
|
定位基址 |
描述 |
|
ios::beg |
偏移量相对于文件开始位置 |
|
ios::end |
偏移量相对于文件结尾位置 |
|
ios::cur |
偏移量相对于文件指针当前位置 |
表2 seekp和seekg举例
|
语句 |
描述 |
|
seekg(100,ios::beg) |
将文件指针移动到从文件开始第100个字节处 |
|
seekg(-100,ios::end) |
将文件指针移动到从文件末尾向后100个字节处 |
|
seekp(42,ios::cur) |
将文件指针从当前位置向前移动42个字节 |
|
seekp(-42,ios::cur) |
将文件指针从当前位置向后移动42个字节 |
|
seekp(100) |
将文件指针移动到文件第100个字节处 |
194.更新二进制文件,可用组合模式ios::in|ios::out|ios::binary
195.C++定义了运算符的优先级和结合律。运算符重载不能改变运算符的优先级和结合律。
196.大多数运算符都是二元运算符,一少部分是一元运算符。运算符重载不能改变运算符操作的运算对象数组。
197.我们一般把返回引用的运算符函数称为左值运算符,例如+=、-=、*=都是左值运算符。
198.可以通过定义一个友元函数或者友元类,使得它能够访问其他类中的私有成员。
199.一个类可以定义转换函数实现对象到基本数据类型值得转换,或定义一个转换构造函数实现基本数据类型值到对象的转换。但是在一个类中两者不能同时存在。如果两者都定义了,编译器将报一个二义性错误。
200.当程序中需要一个基类对象时,向其提供一个派生类对象是允许的。这种特性使得一个函数可以适用于较大范围的对象实参,变得更通用。我们称之为泛型程序设计。
201.派生类的构造函数在执行其自身代码之前首先调用它的基类的构造函数。派生类的析构函数首先执行其自身代码,然后自动调用其基类的析构函数。
202.如果考虑一个类可能被继承,最好为它设计一个无参的构造函数以避免编程错误。
203.如果基类有一个自定义的拷贝构造函数和赋值操作,应该在派生类中自定义这些来保证基类中数据被正确拷贝。假设Child类从Parent类派生,Child类中的拷贝构造函数代码通常看起来像这样:
Child::Child(const Child& object):Parent(object){}
Child类中的赋值操作代码通常看起来像这样:
Child& Child::operator=(const Child& object){
Parent::operator(object);
}
当派生类的析构函数被调用时,它自动调用基类的析构函数。派生类的析构函数只需要销毁在派生类中动态创建的内存。
204.dynamic_cast只能在多态类型的指针或引用上使用,也就是说,该类型必须包含虚函数。dynamic_cast可以在运行时检查强制转换是否成功。static_cast则在编译时起作用。
205.多个不同类型的异常类可以可以派生自同一基类。如果catch模块被设计为捕获一个基类的异常对象,那么它就能捕获所有派生类的异常对象了。
206.异常处理机制是针对意外情况的,不要用它来处理能用if语句解决的简单逻辑错误。
207.所有的递归函数都有如下特性:
a.函数都是用if-else或switch语句实现,来处理不同的情况。
b.各种不同情况中包括一个或多个基本情况(最简单的情况),用于停止递归。
c.每次递归调用会对原问题进行规约,使其逐步地逼近某种基本情况,直到转化为该基本情况为止。
208.如果你很在意程序的性能,应该避免使用递归,因为递归会比迭代消耗更多的时间和内存。
当一个递归函数在返回递归调用后没有待执行的操作时,这种递归称为尾递归。尾递归是可取的,因为当最后一次递归调用结束时,函数就结束了。所以没有必要存储在堆栈中的中间调用。有些编译器可以优化尾递归来减少堆栈空间。

浙公网安备 33010602011771号