@@ -276,6 +276,98 @@ i / j; // 错!
276
276
i % j; // 错!
277
277
```
278
278
279
+ ## 求值顺序类
280
+
281
+ ### 同一表达式内,对同一个变量有多个自增/自减运算
282
+
283
+ ``` cpp
284
+ int i = 5 ;
285
+ int j = (++i) + (++i); // j 的值未定义
286
+ ```
287
+
288
+ ``` cpp
289
+ int j = (++i) + i; // j 的值未定义
290
+ ```
291
+
292
+ ``` cpp
293
+ int i1 = 5 ;
294
+ int i2 = 5 ;
295
+ int j = (++i1) + (++i2); // j = 12
296
+ ```
297
+
298
+ ### 内建类型的二元运算符,其左右两个参数求值的顺序是不确定的
299
+
300
+ 在标准看来,+ 运算符两侧是“同时”求值的,即“interleaved”,实际执行顺序并不确定。
301
+
302
+ 对于 a + b,我们不能假定总是左侧表达式 a 先求值。
303
+
304
+ 不过,虽然运算符两个参数的求值顺序“未指定(unspecified)”,但并不是“未定义(undefined)”。
305
+
306
+ > 但左右两侧涉及自增/自减运算符的情况仍然是未定义行为。
307
+
308
+ ``` cpp
309
+ int f1 () {
310
+ printf ("f1\n");
311
+ return 1;
312
+ }
313
+
314
+ int f2 () {
315
+ printf ("f2\n");
316
+ return 2;
317
+ }
318
+
319
+ int j = f1() + f2(); // 可能打印 f1 f2,也可能打印 f2 f1,但 j 最终的结果一定是 3
320
+ ```
321
+
322
+ 未指定和未定义是不同的!有未定义行为的程序是非法(ill-formed)的,但未指定只是会让结果无法确定,但一定能正常运行:要么 f1 先运行,要么 f2 先运行。
323
+
324
+ ### 函数参数求值的顺序是不确定的
325
+
326
+ ``` cpp
327
+ int f1 () {
328
+ printf ("f1\n");
329
+ return 1;
330
+ }
331
+
332
+ int f2 () {
333
+ printf ("f2\n");
334
+ return 2;
335
+ }
336
+
337
+ void foo (int i, int j) {
338
+ printf("%d %d\n", i, j);
339
+ }
340
+
341
+ foo(f1(), f2()); // 可能打印 f1 f2 1 2,也可能打印 f2 f1 1 2
342
+ ```
343
+
344
+ 代码中,f1 和 f2 的求值顺序虽然未指定,但可以保证 foo 函数体一定在执行完毕后才会开始。
345
+
346
+ 同一条语句中所有子表达式的执行顺序就像一颗树,树中两个子节点执行顺序是不确定的;但可以肯定的是,树的子节点一定先于他们的父节点执行。
347
+
348
+ 同样地,这只是未指定(unspecified)行为而不是未定义(undefined)行为,结果必然是 f1 f2 或 f2 f1 两种可能之一,不会让程序出现未定义值的情况。
349
+
350
+ 注意,求值顺序未指定仅限同一语句(“同一行”)内,对于互相独立的多条语句,依然是有强先后顺序的。
351
+
352
+ ```cpp
353
+ int f1() {
354
+ printf("f1\n");
355
+ return 1;
356
+ }
357
+
358
+ int f2() {
359
+ printf("f2\n");
360
+ return 2;
361
+ }
362
+
363
+ void foo(int i, int j) {
364
+ }
365
+
366
+ foo(f1(), f2()); // 可能打印 f1 f2,也可能打印 f2 f1
367
+
368
+ f1(); f2(); // 必然打印 f1 f2
369
+ ```
370
+
279
371
## 函数类
280
372
281
373
### 返回类型不为 void 的函数,必须有 return 语句
@@ -352,6 +444,38 @@ int main() {
352
444
}
353
445
```
354
446
447
+ ### 函数指针被调用时,参数列表或返回值必须匹配
448
+
449
+ ```cpp
450
+ void f1(int *p) {
451
+ printf("f1(%p)", p);
452
+ }
453
+
454
+ void (*fp)(const int *);
455
+ fp = (void (*)(const int *)) f1; // 错误
456
+
457
+ int i;
458
+ fp = (void (*)(const int *)) &i; // 错误
459
+ ```
460
+
461
+ ### 普通函数指针与成员函数指针不能互转
462
+
463
+ ``` cpp
464
+ struct Class {
465
+ void mf() {
466
+ printf("成员函数\n");
467
+ }
468
+ };
469
+
470
+ union {
471
+ void (Class::* member_func)();
472
+ void (* free_func)(Class * );
473
+ } u;
474
+ u.member_func = &Class::mf;
475
+ Class c;
476
+ u.free_func(&c); // 错误
477
+ ```
478
+
355
479
## 生命周期类
356
480
357
481
### 不能读取未初始化的变量
@@ -504,6 +628,16 @@ std::string func() {
504
628
}
505
629
```
506
630
631
+ ### 不能把函数指针转换为普通类型指针解引用
632
+
633
+ ``` cpp
634
+ void func () {}
635
+
636
+ printf ("* func = %d\n", * ((int * )func)); // 错误
637
+ ```
638
+
639
+ > C++ 内存模型是哈佛架构(代码与数据分离),不是冯诺依曼架构(代码也是数据)
640
+
507
641
## 库函数类
508
642
509
643
### ctype.h 中一系列函数的字符参数,必须在 0~127 范围内(即只支持 ASCII 字符)
0 commit comments