1 C++中理解“初始化不是赋值”是必要的。初始化指创建变量并给它赋初始值,而赋值则是擦除对象的当前值并用新值替代。
2
3 C++支持两种初始化变量的形式:赋值初始化(int val = 1024),直接初始化(int val(1024))
4
5 在文件作用域内,非const变量默认为extern,要使const变量能过在其它文件中访问,必须显示地指定它为extern
6 当我们在头文件中定义了const变量后,每个包含该头文件的源文件都有了自己的const变量,其名称和值都是相同的.
7
8 非const引用只能绑定到与该引用同类型的变量。const引用则可以绑定到不同但相关的类型的对象或绑定到右值。
9
10 class template(类模板):一个可创建许多潜在类类型的蓝图。使用类模板时,必须给出实际的类型和值。例如,vettor类型是保存给定类型对象的模板。创建一个vector对象时,必须指出这个vector对象所保存的元素的类型。vector<int>保存int的对象,而vector<string>则保存string对象。
11
12 container(容器):一种类型,其对象保存一组给定类型的对象的集合.
13
14 :: operator:作用域操作符。::操作符在其左操作数的作用域内找到其右操作数的名字。用于访问某个命名空间的名字,例如std::cout,表明名字cout来自命名空间std。同样地,可用来从某个类取名字,如string::size_type,表明size_type是由string类定义的.
15
16 指针和引用间的两个重要区别:(1)引用总是指向某个对象:定义引用时没有初始化时错误的.(2)给引用赋值修改的时该引用锁关
17 联的对象的值,而并不是使引用和另外一个对象关联。
18
19 指向const对象的指针:const double *ptr; ptr是一个指向double类型const对象的指针.
20 const指针:int *const ptr = NULL; ptr是指向int型对象的const指针
21 指向const对象的const指针:const double *const ptr = &val; ptr是一个const指针,指向double类型的const对象
22
23
24 typedef string *pstring;
25 const pstring cstr; 此处const修饰的是cstr,是把cstr定义为指向string类型对象的const指针。
26 等价于:string *const cstr;
27
28 如果我们在自由存储区中创建的数组存储了内置类型的const对象,则必须为这个数组提供初始化:
29 错误:const int *pci_bad = new const int[100];
30 正确:const int *pci_ok = new const int[100]();
31
32 (1)如果函数使用了非引用的非const型参,则既可以给该函数传递const实参也可传递非const的实参。
33 (2)如果函数使用了非引用的const类型,则传递给该函数的实参可以时const对象,也可以是非const对象。
34 尽管函数的型参时const,但是编译器却将fcn的定义视为其型参被声明为普通的int型:
35 void fcn(const int i);
36 void fcn(int i);//error: redefines fcn(int)
37 (3)非const引用的型参不能通过const对象进行调用,非const引用的型参只能与完全相同类型的非const对象关联.
38 应将不修改相应实参的型参定义为const引用。如果将这样的型参定义为非const引用,则毫无必要的限制了该函数的使用.
39 应该将不需要修改的引用型参定义为const引用。普通的非const引用型参在使用时不太灵活。这样的型参既不能用const对象初>始化,也不能用字面值或产生右值的表达式实参初始化.
40
41 泛型算法中,所谓“泛型(generic)“指的是两个方面:这些算法可作用于各种不同的容器类型,而这些容器类型又可以容纳多种不
42 同类型的元素.
43
44 容器元素必须满足以下两个约束:
45 (1)元素类型必须支持赋值运算
46 (2)元素类型的对象必须可以复制
47
48 list<string>::iterator iter; iter的声明使用了作用域操作符,以此表明此时所使用的符号::右边的类型名字是在符号::左边指
49 定容器的作用域内定义的。其效果是将iter声明为iterator类型,而iterator是存放string类型元素的list类的成员.
50
51 适配器(adaptor)是标准库中通用的概念,包括容器适配器、迭代器适配器和函数适配器。本质上,适配器是使一事物的行为类似
52 于另一事物的行为的一种机制。容器适配器让一种已经存在的容器类型采用另一种不同的抽象类型的工作方式实现
53
54 每个类可以没有成员,也可以定义多个成员,成员可以是数据、函数和类型别名
55
56 如果类的实现速度太慢或给类的使用者加上负担,则必然引起使用者的关注。
57
58 使用类型别名来简化类:
59 除了定义数据和函数成员外,类还可以定义自己的局部类型名字。如果为std::string::size_type提供一个类型别名,那么Screen>类将是一个更好的抽象:
60 class Screen
61 {
62 public:
63 typedef std::string::size_type index;
64 private:
65 std::string contents;
66 index cursor;
67 index height, width;
68 };
69 类所定义的类型名遵循任何其它成员的标准访问控制。将index的定义放在类的public部分,是因为希望用户使用这个名字。Screen的使用者不必了解string实现的底层细节。定义index来隐藏Screen的实现细节。将这个类型设计为public,就允许用户使用这个名
70 字.
71
72 不能从const成员函数返回指向类对象的普通引用。const成员函数只能返回*this作为一个const引用.
73
74 不能从const成员函数返回指向类对象的普通引用。const成员函数只能返回*this作为一个const引用.
75
76 基于成员函数是否为const,可以重载一个成员函数
77
78 区分构造函数中的初始化成员列表与函数体中的赋值行为,比如类中有const类型的成员,或者引用类型的成员
79 一般而言,类的static成员,像普通数据成员一样,不能在类的定义体中初始化,这个对则的一个例外是,只要初始化式是一个常>量表达式,整型const static数据成员就可以在类的定义体中进行初始化. const static数据成员在类的定义体中初始化时,该数>据成员仍必须在类的定义体之外进行定义.
80
81 重载运算符的指导原则:
82 1、赋值(=)、下表([])、调用(())和成员访问箭头(->)等操作符必须定义为成员,将这些操作符定义为非成员函数将在编>译时标记为错误
83 2、向赋值一样,复合赋值操作符通常应定义为类的成员。与赋值不同的是,不一定非得这样做,如果定义非成员符合赋值操作符,
84 不会编译错误
85 3、改变对象状态或与给定类型紧密联系的其他一些操作符,如自增、自减和解引用,通常定义为类成员
86 4、对称的操作符,如算术操作符、相等操作符、关系操作符和位操作符,最好定义为普通非成员函数
87
88 派生类中虚函数的声明必须与基类中的定义方式完全匹配,但是有一个例外:返回对基类型的引用(或指针)的虚函数。派生类中>的虚函数可以返回派生类的引用或指针
89
90
91
92 对于只有一个参数的构造函数可能造成隐式类类型转换,有时我们不想出现该情形:
93 class Sales_item
94 {
95 public:
96 Sales_item(const std::string &book = " "):
97 isbn(book), units_sold(0), revenue(0.0){}
98
99 Sales_item(std::istream &is);
100 };
101
102 string null_book = "9-99";
103 item.same_isbn(null_book);
104
105 这段程序使用一个string类型对象作为实参传给Sales_item的same_isbn函数。该函数期待一个Sales_item对象作为实参。编译器使
106 用接受一个string的Sales_item构造函数从null_book生成一个新的Sales_item对象。新生成的(临时的)Sales_item被传递给same_isbn
107
108 更成问题的是从istream到Sales_item的转换:
109 item.same_isbn(cin)
110 这段代码将cin隐式转换为Sales_item。这个转换执行接受一个istream的Sales_item构造函数。该构造函数通过读标准输入来创建>一个(临时的)Sales_item对象。然后该对象被传递给same_isbn. 这个Sales_item对象是一个临时对象。一旦same_isbn结束,就>不能在访问它。实际上,我们构建了一个在测试完成后被丢弃的对象。这个行为几乎肯定是一个错误.
111
112 解决的办法:
113 通过将构造函数声明为explict,来防止在需要隐式转换的上下文中使用构造函数:
114 class Sales_item
115 {
116 public:
117 explict Sales_item(const std::string &book = " "):
118 isbn(book), units_sold(0), revenue(0.0){}
119 explict Sales_item(std::istream &is);
120 };
121 exlict关键字只能用于类内部的构造函数的声明上。
122 此时item.same_isbn(null_book)//error
123 item.same_isbn(Sales_item(null_book))//right
124
125
126 函数对象
127 可以为类类型的对象重载函数调用操作符,一般为表示操作的类重载调用运算符。例:
128 struct absInt
129 {
130 int operator() (int val)
131 {
132 return val < 0 ? -val : val;
133 }
134 };
135
136 int i = -42;
137 absInt absObj;
138 unsigned int ui = absObj(i);
139 尽管absObj是一个对象而不是函数,我们仍然可以“调用”该对象,效果是运行由absObj对象定义的重载调用操作符,该操作符接受>一个int值并返回它的绝对值.
140 定义了调用操作符的类,其对象常称为函数对象(function object),即它们是行为类似函数的对象.
141
142 转换函数一般不应该改变被转换的对象。因此,转换操作符通常应被定义为const成员
143
144 关于继承中的拷贝构造函数:
145 如果派生类定义了自己的拷贝构造函数,该拷贝构造函数一般应显式的使用基类拷贝构造函数初始化对象的基类部分:
146 class Base{};
147 class Derived: public Base
148 {
149 public:
150 Derived(const Derived& d):Base(d)
151 {
152 }
153 };
154 初始话构造函数Base(d)将派生类对象d转换为它的基类部分的引用,并调用基类拷贝构造函数。如果生类了基类初始化函数,如下>代码:
155 Derived(const Derived& d){}
156 效果是运行Base的默认构造函数初始化对象的基类部分。假定Derived成员的初始化从d拷贝对应的成员,则新构造的对象将具有奇>怪的配置:它的Base部分将保持默认值,而它的Derived成员是另一对象的副本.
157
158 关于派生类中的赋值操作符:
159 赋值操作符通常与拷贝构造函数类似:如果派生类定义了自己的赋值操作符,则该操作符必须对基类部分进行显示的调用。
160 Derived& Derived::operator= (const Derived &rhs)
161 {
162 if(this != &rhs)
163 {
164 Base::operator= (rhs);
165 }
166 return *this;
167 }
168
169 编译器将对象的类型视为在构造或析构期间发生了变化。在基类构造函数或析构函数中,将派生类对象当作基类类型对象对待。如>果在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本.
170
171 所谓泛型编程就是以独立与任何特定类型的方式编写代码。使用泛型程序时,我们需要提供具体程序实例所操作的类型或值。模板>是泛型编程的基础。使用模板时可以无须了解模板的定义。
172
173 模板:非类型形参????
174
175 template < class Parm, class U >
176 Pram fcn(Parm *array, U value)
177 {
178 typename Parm::size_type *p;//ok: declares p to be a pointer
179 }
180 通过在成员前加上关键字typename作为前缀,可以告诉编译器将成员当作类型。通过编写typename Parm::size_type指出绑定到Parm的类型的size_type成员是类型的名字.
181
182 模板编译模型:要编译使用自己的模板类和函数模板的代码,必须查阅编译器的用户指南,看看编译器怎样处理实例化
183 包含编译模型、分别编译模型
184
185 通过运行时识别(RTTI),程序能够使用基类的指针或引用来检索这些指针或引用所指对象的实际派生类型。
186 通过下面的两个操作符提供RTTI:
187 (1)typeid操作符,返回指针或引用所指对象的实际类型
188 (2)dynamic_cast操作符,将基类类型的指针或引用安全的转换为派生类的指针或引用
189 typeid操作符的结果是名为type_info的标准库类型的对象引用,要使用type_info类,必须包含库头文件typeinfo
190
191
192 大量的类并不需要可拷贝,也不需要一个拷贝的构造函数或赋值操作。不幸的是,如果你不主动声明它们,编译器会为你自动生成>,而且是public的。可以在类的private中添加空的拷贝构造函数和赋值操作,只有声明,没有定义。由于这些空声明为private,>当其它代码试图使用它们的时候,编译器将报错。为了方便,可以定义宏DISALLOW_COPY_AND_ASSIGN
193 #define DISALLOW_COPY_AND_ASSIGN(TypeName) \
194 TypeName(const TypeName&); \
195 void operator=(const TypeName&)
196
197 真正需要用到多重实现继承的时候非常少,只有当最多一个基类中含有实现,其他基类都是以Interface为后缀的纯接口类时才会使
198 用多重继承。一般除了第一个外都是纯接口时才能使用多重继承。为了确保它们的纯接口,这些类必须以Interface为后缀
199
200 接口是指满足特定条件的类,这些类以 Interface 为后缀(非必需)。
201 定义:当一个类满足以下要求时,称之为纯接口:
202 1) 只有纯虚函数("=0")和静态函数(下文提到的析构函数除外);
203 2) 没有非静态数据成员;
204 3) 没有定义任何构造函数。如果有,也不含参数,并且为 protected;
205 4) 如果是子类,也只能继承满足上述条件并以 Interface 为后缀的类。
206 接口类不能被直接实例化,因为它声明了纯虚函数。为确保接口类的所有实现可被正确销毁,必
207 须为之声明虚析构函数(作为第 1 条规则的例外,析构函数不能是纯虚函数)
208
209
210 声明次序:
211 在类中使用特定的声明次序:public在private之前,成员函数在数据成员前.
212 定义次序如下:public:、protected:、private:,如果那一块没有,直接忽略即可。
213 每一块中,声明次序一般如下:
214 1) typedefs 和 enums;
215 2) 常量;
216 3) 构造函数;
217 4) 析构函数;
218 5) 成员函数,含静态成员函数;
219 6) 数据成员,含静态数据成员。
220 宏 DISALLOW_COPY_AND_ASSIGN 置于 private:块之后,作为类的最后部分。
221
222
223 长函数有时是恰当的,因此对于函数长度并没有严格限制。如果函数超过 40 行,可以考虑在不 影响程序结构的情况下将其分割一下
224 。 即使一个长函数现在工作的非常好,一旦有人对其修改,有可能出现新的问题,甚至导致难以发现的 bugs。使函数尽量短小、简单
225 ,便于他人阅读和修改代码。在处理代码时,你可能会发现复杂的长函数,不要害怕修改现有代码:如果证实这些代码使用、调试困难,或者你需要使用其中的一小块,考虑将其分割为更加短小、易于管理的若干函数。
226
227 不要在构造函数中做太多逻辑相关的初始化.
228
229 组合>实现继承>接口继承>私有继承,子类重载的虚函数也要声明 virtual 关键字,虽然编译器允许不这样做;
230
231 不要使用流,除非是日志接口需要,使用 printf 之类的代替。使用流还有很多利弊,代码一致性胜过一切,不要在代码中使用流。
232
233
234 使用 C++风格而不要使用 C 风格类型转换。
235 1) static_cast:和C 风格转换相似可做值的强制转换, 或指针的父类到子类的明确的向上转换 ;
236 2) const_cast:移除 const 属性;
237 3) reinterpret_cast:指针类型和整型或其他指针间不安全的相互转换,仅在你对所做的一切了然于心时使用;
238 4) dynamic_cast:除测试外不要使用,除单元测试外,如果你需要在运行时确定类型信息,说明设计有缺陷(参考RTTI)。
239
240 C语言宏的高级应用
241 关于Debian开发学习日记
242
243
244 1. 通用命名规则(General Naming Rules)
245 函数命名、变量命名、文件命名应具有描述性,不要过度缩写,类型和变量应该是名词,函数名 可以用“命令性”动词。
246
247 如何命名:
248 尽可能给出描述性名称,不要节约空间,让别人很快理解你的代码更重要,好的命名选择:
249 int num_errors; // Good.
250 int num_completed_connections; // Good.
251 丑陋的命名使用模糊的缩写或随意的字符:
252 int n; // Bad - meaningless.
253 int nerr; // Bad - ambiguous abbreviation.
254 int n_comp_conns; // Bad - ambiguous abbreviation.
255 类型和变量名一般为名词:如 FileOpener、num_errors。
256 函数名通常是指令性的, OpenFile()、set_num_errors(),如访问函数需要描述的更细致,要与其访问的变量相吻合。
257
258 缩写:
259 除非放到项目外也非常明了,否则不要使用缩写,例如:
260
261 类型命名:
262 类型命名每个单词以大写字母开头,不包含下划线:MyExcitingClass、MyExcitingEnum。所有类型命名——类、结构体、类型定义
263 (typedef)、枚举——使用相同约定
264
265 变量命名:
266 变量名一律小写,单词间以下划线相连,类的成员变量以下划线结尾,如my_exciting_local_variable、my_exciting_member_variable_。
267
268 全局变量:
269 对全局变量没有特别要求,少用就好,可以以g_或其他易与局部变量区分的标志为前缀。
270
271 所有编译时常量(无论是局部的、全局的还是类中的)和其他变量保持些许区别,k 后接大写字母开头的单词:
272 const int kDaysInAWeek = 7;
273
274 枚举值应全部大写,单词间以下划线相连: MY_EXCITING_ENUM_VALUE。
275
276 普通函数:
277 函数名以大写字母开头,每个单词首字母大写,没有下划线:
278 AddTableEntry()
279 DeleteUrl()
280 存取函数:
281 存取函数要与存取的变量名匹配,这儿摘录一个拥有实例变量
282 num_entries_的类:
283 class MyClass {
284 public:
285 ...
286 int num_entries() const { return num_entries_; }
287 void set_num_entries(int num_entries) {
288 num_entries_ = num_entries;
289 }
290 private:
291 int num_entries_;
292 };
293 其他短小的内联函数名也可以使用小写字母,例如,在循环中调用这样的函数甚至都不需要缓存其值,小写命名就可以接受。
294
295 类型和变量命名意义明确要比通过注释解释模糊的命名好得多。
296
297 在每一个文件开头加入版权公告,然后是文件内容描述。
298 如果你对其他人创建的文件做了重大修改,将你的信息添加到作者信息里,这样当其他人对该文件有疑问时可以知道该联系谁。
299
300 通常,.h文件要对所声明的类的功能和用法作简单说明,.cc文件包含了更多的实现细节或算法讨论,如果你感觉这些实现细节或算法>讨论对于阅读有帮助,可以把.cc 中的注释放到.h 中,并在.cc 中指出文档在.h 中。
301
302 注意永远不要用自然语言翻译代码作为注释,要假设读你代码的人 C++比你强
303
304 TODO 注释(TODO Comments)
305 对那些临时的、短期的解决方案,或已经够好但并不完美的代码使用 TODO 注释。
306 这样的注释使用全大写的字符串TODO,后面括号(parentheses)里加上你的大名、邮件地 址等,还可以加上冒号(colon):目的是>可以根据统一的 TODO 格式进行查找:
307 // TODO(kl@gmail.com): Use a "*" here for concatenation operator.
308 // TODO(Zeke) change this to use relations.
309 如果加上是为了在“将来某一天做某事”,可以加上一个特定的时间("Fix by November 2005")或事件("Remove this code when all clients can handle XML responses.") 。
310 很不错,有时候,注释确实是为了标记一些未完成的或完成的不尽如人意的地方, 这样一搜索,就知道还有哪些活要干,日志都省
311 了
312
313 函数声明与定义 ( Function Declarations and Definitions)
314 1) 返回值总是和函数名在同一行;
315 2) 左圆括号(open parenthesis)总是和函数名在同一行;
316 3) 函数名和左圆括号间没有空格;
317 4) 圆括号与参数间没有空格;
318 5) 左大括号(open curly brace)总在最后一个参数同一行的末尾处;
319 6) 右大括号(close curly brace)总是单独位于函数最后一行;
320 7) 右圆括号(close parenthesis)和左大括号间总是有一个空格;
321 8) 函数声明和实现处的所有形参名称必须保持一致;
322 9) 所有形参应尽可能对齐;
323 如果函数为 const 的,关键字 const 应与最后一个参数位于同一行。
324 注意所有情况下 if 和左圆括号间有个空格,右圆括号和左大括号(如果使用的话)间也要有个空格:
325 if (condition) { // Good - proper space after IF and before {.
326
327 空循环体应使用{}或 continue,而不是一个简单的分号:
328 while (condition) {
329 // Repeat test until it returns false.
330 }
331 for (int i = 0; i < kSomeNumber; ++i) {}
332 // Good - empty body.
333 while (condition) continue; // Good - continue indicates no logic.
334 while (condition); // Bad - looks like part of do/while loop.
335
336 return 表达式中不要使用圆括号。
337 预处理指令不要缩进,从行首开始。
338 即使预处理指令位于缩进代码块中,指令也应从行首开始。
339
340 /*******************************************************************************
C++函数重定义、重载、覆盖
1.覆盖(override):
父类与子类之间的多态性。子类重写了与父类中有相同名称和参数的虚函数。
1)被覆盖的函数不能是static的(因为static函数中没有this指针,static的关键作用在于说明作用域)。必须是virtual的(即函数在最原始的基类中被声明为virtual).
2)覆盖函数的返回类型(基本数据类型必须相同,否则可以不同),函数名称和参数列表(即相同的函数原型)
3)覆盖函数的访问修饰符可以不同。尽管virtual是private的,派生类中重写为public,protected也是可以的
2.重载(overload):
指函数名相同,但是它的参数列表个数或顺序,类型不同。但是不能靠返回类型来判断。
3.重定义(redefining):
(1)基类中的函数指明为虚函数,派生类也有相同的函数名,但是参数类型不同,此时视为重定义。
(2)基类中的函数不是虚函数,派生类也有相同的函数名,无论参数类型是否相同,均视为重定义。
4、基类中定义了虚函数,当派生类中出现相同的函数名,若形参完全相同,则视为override,此时
(1)若基类中的返回类型为基本数据类型,则派生类中的返回类型也必须相同,否则编译报错;
(2)若基类中的返回类型为一个类的引用或指针,则派生类中的返回类型必须与父类中返回类型相同或时返回类型的派生类的类型。
(注:《More C++ idioms》p_144
C++ allows the flexibility in types where the return type of the over-ridden function can be a derived type of that of the function in the base class. This language feature is known as co-variant return types.
)
5、对于redefining,如果想要在继续保留基类中的相同函数名的功能,那么需要显式的使用using进行声明。