-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcpp-functions.html
751 lines (751 loc) · 241 KB
/
cpp-functions.html
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
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=2"><meta name="theme-color" content="#222"><meta http-equiv="X-UA-COMPATIBLE" content="IE=edge,chrome=1"><meta name="renderer" content="webkit"><link rel="icon" type="image/ico" sizes="32x32" href="/assets/favicon.ico"><link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon.png"><link rel="alternate" href="/rss.xml" title="Jiankychen's Blog" type="application/rss+xml"><link rel="alternate" href="/atom.xml" title="Jiankychen's Blog" type="application/atom+xml"><link rel="alternate" type="application/json" title="Jiankychen's Blog" href="https://jiankychen.github.io/feed.json"><link rel="preconnect" href="https://lf9-cdn-tos.bytecdntp.com"><link rel="preconnect" href="https://at.alicdn.com"><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Mulish:400,400italic,700,700italic%7CFredericka%20the%20Great:400,400italic,700,700italic%7CNoto%20Serif%20JP:400,400italic,700,700italic%7CNoto%20Serif%20SC:400,400italic,700,700italic%7CInconsolata:400,400italic,700,700italic&display=swap&subset=latin,latin-ext" media="none" onload="this.media='all'"><link rel="stylesheet" href="/css/app.css?v=0.4.2"><link rel="modulepreload" href="/js/chunk-7IVLRIQ3.js"><link rel="modulepreload" href="/js/chunk-IXT6LZJL.js"><link rel="modulepreload" href="/js/chunk-PHSEV26P.js"><link rel="modulepreload" href="/js/chunk-XHQGHZCW.js"><link rel="modulepreload" href="/js/comments-TUWNDU5I.js"><link rel="modulepreload" href="/js/post-P6IN2S3Y.js"><link rel="modulepreload" href="/js/quicklink-HAJEHOPK.js"><link rel="modulepreload" href="/js/search-WFXK2K66.js"><link rel="modulepreload" href="/js/siteInit.js"><link rel="stylesheet" href="https://npm.webcache.cn/@waline/[email protected]/dist/waline.css" media="none" onload="this.media='all'"><link rel="preload" href="https://img.timelessq.com/images/2022/07/26/1135e8eb0ca0a462aa1c2f6ecb6a5ae2.jpg" as="image" fetchpriority="high"><link rel="preload" href="https://img.timelessq.com/images/2022/07/26/2aabaeb8aca379b991071d1c41632741.jpg" as="image" fetchpriority="high"><link rel="preload" href="https://i.imgtg.com/2023/03/09/Y0iNK.jpg" as="image" fetchpriority="high"><link rel="preload" href="https://img.timelessq.com/images/2022/07/26/9b626c5ba21d7cb4dbcba2b507688bbb.jpg" as="image" fetchpriority="high"><link rel="preload" href="https://i.imgtg.com/2023/03/09/YS2LU.jpg" as="image" fetchpriority="high"><link rel="preload" href="https://i.imgtg.com/2023/03/09/YQSYM.jpg" as="image" fetchpriority="high"><link rel="canonical" href="https://jiankychen.github.io/cpp-functions"><title>C++ 函数</title><meta name="generator" content="Hexo 7.0.0"></head><body itemscope="" itemtype="http://schema.org/WebPage"><div id="loading"><div class="cat"><div class="body"></div><div class="head"><div class="face"></div></div><div class="foot"><div class="tummy-end"></div><div class="bottom"></div><div class="legs left"></div><div class="legs right"></div></div><div class="paw"><div class="hands left"></div><div class="hands right"></div></div></div></div><div id="container"><header id="header" itemscope="" itemtype="http://schema.org/WPHeader"><div class="inner"><div id="brand"><div class="pjax"><h1 itemprop="name headline">C++ 函数</h1><div class="meta"><span class="item" title="创建时间:2022-09-20 16:40:31"><span class="icon"><i class="ic i-calendar"></i></span><span class="text">发表于</span><time itemprop="dateCreated datePublished" datetime="2022-09-20T16:40:31+08:00">2022-09-20</time></span><span class="item" title="本文字数"><span class="icon"><i class="ic i-pen"></i></span><span class="text">本文字数</span><span>40k</span><span class="text">字</span></span><span class="item" title="阅读时长"><span class="icon"><i class="ic i-clock"></i></span><span class="text">阅读时长</span><span>36 分钟</span></span></div></div></div><nav id="nav"><div class="inner"><div class="toggle"><div class="lines" aria-label="切换导航栏"><span class="line"></span><span class="line"></span><span class="line"></span></div></div><ul class="menu"><li class="item title"><a href="/" rel="start">Jiankychen</a></li></ul><ul class="right" id="rightNav"><li class="item theme"><i class="ic i-sun"></i></li><li class="item search"><i class="ic i-search"></i></li></ul></div></nav></div><div class="pjax" id="imgs"><ul><li class="item" style="background-image: url("https://img.timelessq.com/images/2022/07/26/1135e8eb0ca0a462aa1c2f6ecb6a5ae2.jpg");"></li><li class="item" style="background-image: url("https://img.timelessq.com/images/2022/07/26/2aabaeb8aca379b991071d1c41632741.jpg");"></li><li class="item" style="background-image: url("https://i.imgtg.com/2023/03/09/Y0iNK.jpg");"></li><li class="item" style="background-image: url("https://img.timelessq.com/images/2022/07/26/9b626c5ba21d7cb4dbcba2b507688bbb.jpg");"></li><li class="item" style="background-image: url("https://i.imgtg.com/2023/03/09/YS2LU.jpg");"></li><li class="item" style="background-image: url("https://i.imgtg.com/2023/03/09/YQSYM.jpg");"></li></ul></div></header><div id="waves"><svg class="waves" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 24 150 28" preserveAspectRatio="none" shape-rendering="auto"><defs><path id="gentle-wave" d="M-160 44c30 0 58-18 88-18s 58 18 88 18 58-18 88-18 58 18 88 18 v44h-352z"></path></defs><g class="parallax"><use xlink:href="#gentle-wave" x="48" y="0"></use><use xlink:href="#gentle-wave" x="48" y="3"></use><use xlink:href="#gentle-wave" x="48" y="5"></use><use xlink:href="#gentle-wave" x="48" y="7"></use></g></svg></div><main><div class="inner"><div class="pjax" id="main"><div class="article wrap"><div class="breadcrumb" itemlistelement="" itemscope="" itemtype="https://schema.org/BreadcrumbList"><i class="ic i-home"></i><span><a href="/">首页</a></span><i class="ic i-angle-right"></i><span class="current" itemprop="itemListElement" itemscope="itemscope" itemtype="https://schema.org/ListItem"><a href="/categories/C/" itemprop="item" rel="index" title="分类于C++"><span itemprop="name">C++<meta itemprop="position" content="0"></span></a></span></div><article class="post block" itemscope="itemscope" itemtype="http://schema.org/Article" lang="zh-CN"><link itemprop="mainEntityOfPage" href="https://jiankychen.github.io/cpp-functions.html"><span hidden="hidden" itemprop="author" itemscope="itemscope" itemtype="http://schema.org/Person"><meta itemprop="image" content="/assets/avatar.jpg"><meta itemprop="name" content="Jiankychen"><meta itemprop="description" content="Never put off till tomorrow what you can do today, "></span><span hidden="hidden" itemprop="publisher" itemscope="itemscope" itemtype="http://schema.org/Organization"><meta itemprop="name" content="Jiankychen's Blog"></span><div class="body md" itemprop="articleBody"><h1 id="函数基础"><a class="anchor" href="#函数基础">#</a> 函数基础</h1>
<p>典型的函数(function)定义包括以下部分:</p>
<ul>
<li>返回类型(return type)</li>
<li>函数名字</li>
<li>由 0 个或多个形参(parameter)组成的列表,其中,形参以逗号隔开,形参的列表位于一对圆括号之内</li>
<li>函数体</li>
</ul>
<p>例如:定义一个函数用于求数 n 的阶乘,其中,返回类型为 int 型,函数名字为 fact,形参为 int n,函数体为一对花括号 {} 内的语句</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">int</span> <span class="token function">fact</span><span class="token punctuation">(</span><span class="token keyword">int</span> n<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="2"></td><td><pre> <span class="token keyword">int</span> ret <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="3"></td><td><pre> <span class="token keyword">while</span> <span class="token punctuation">(</span>n <span class="token operator">></span> <span class="token number">1</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="4"></td><td><pre> ret <span class="token operator">*=</span> n<span class="token operator">--</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="5"></td><td><pre> <span class="token keyword">return</span> ret<span class="token punctuation">;</span></pre></td></tr><tr><td data-num="6"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></tbody></table></figure><h2 id="调用函数"><a class="anchor" href="#调用函数">#</a> 调用函数</h2>
<p>通过 <strong>调用运算符</strong>(call operator)来执行函数</p>
<ul>
<li>调用运算符的形式是一对圆括号,它作用于一个表达式,该表达式是函数或者指向函数的指针;圆括号之内是一个用逗号隔开的实参(argument)列表,我们用实参初始化函数的形参</li>
<li>调用表达式的类型就是函数的返回类型</li>
</ul>
<p>函数的调用完成两项工作:</p>
<ul>
<li>(隐式地)定义并用实参初始化函数对应的形参</li>
<li>将控制权转移给被调用函数</li>
</ul>
<p>此时,主调函数(calling function)的执行被暂时中断,被调函数(called function)开始执行</p>
<p>当遇到一条 return 语句时,函数结束执行过程</p>
<p>return 语句也完成两项工作:</p>
<ul>
<li>返回 return 语句中的值(如果有的话)</li>
<li>将控制权从被调函数转移回主调函数</li>
</ul>
<h2 id="形参和实参"><a class="anchor" href="#形参和实参">#</a> 形参和实参</h2>
<p>实参是形参的初始值。第一个实参初始化第一个形参,第二个实参初始化第二个形参,以此类推</p>
<blockquote>
<p>尽管实参与形参存在对应关系,但是并没有规定实参的求值顺序。编译器能以任意可行的顺序对实参求值</p>
</blockquote>
<p>实参数量应与形参数量一致,并且,实参的类型必须与对应的形参类型匹配</p>
<h2 id="函数的形参列表"><a class="anchor" href="#函数的形参列表">#</a> 函数的形参列表</h2>
<p>函数的形参列表可以为空,但是不能省略</p>
<ul>
<li>要想定义一个不带形参的函数,最常用的办法是书写一个空的形参列表</li>
<li>为了与 C 语言兼容,也可以使用关键字 void 表示函数没有形参</li>
</ul>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">void</span> <span class="token function">f1</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span> <span class="token comment">// 隐式地定义空形参列表</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">void</span> <span class="token function">f2</span><span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span> <span class="token comment">// 显式地定义空形参列表</span></pre></td></tr></tbody></table></figure><p>形参列表中的形参通常用逗号隔开,其中,每个形参都是含有一个声明符的声明(即使两个形参的类型一样,也必须把两个类型都写出来)</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">int</span> <span class="token function">f3</span><span class="token punctuation">(</span><span class="token keyword">int</span> v1<span class="token punctuation">,</span> v2<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span> <span class="token comment">// 错误</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">int</span> <span class="token function">f4</span><span class="token punctuation">(</span><span class="token keyword">int</span> v1<span class="token punctuation">,</span> <span class="token keyword">int</span> v2<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span> <span class="token comment">// 正确</span></pre></td></tr></tbody></table></figure><p>任意两个形参都不能同名,而且,函数最外层作用域中的局部变量也与函数形参同名</p>
<p>形参名是可选的。如果函数不会使用到个别形参,通常不命名该形参,以表示其不会在函数体内被使用</p>
<blockquote>
<p>注意:即使某个形参不被函数使用,也必须为它提供一个实参</p>
</blockquote>
<h2 id="函数返回类型"><a class="anchor" href="#函数返回类型">#</a> 函数返回类型</h2>
<blockquote>
<p>大多数类型都能用作函数的返回类型</p>
</blockquote>
<p>一种特殊的返回类型是 void ,它表示函数不返回任何值</p>
<p>函数的返回类型不能是数组类型或函数类型,但可以是指向数组或函数的指针</p>
<h2 id="局部对象"><a class="anchor" href="#局部对象">#</a> 局部对象</h2>
<p>在 C++ 语言中,名字有作用域,对象有生命周期(lifetime)</p>
<ul>
<li>名字的作用域:程序文本的一部分,名字在其中可见</li>
<li>对象的生命周期:程序执行过程中该对象存在的一段时间</li>
</ul>
<p>形参和函数体内部定义的变量统称为局部变量(local variable)。它们对函数而言是 “局部” 的,仅在函数的作用域内可见</p>
<blockquote>
<p>Parameters and variables defined inside a function body are referred to as local variables. They are “local” to that function and hide declarations of the same name made in an outer scope.</p>
</blockquote>
<p>在所有函数体之外定义的对象存在于程序的整个执行过程中。此类对象在程序启动时被创建,直到程序结束才会销毁</p>
<p>局部变量的生命周期依赖于定义的方式。对于普通局部变量对应的对象,当函数的控制路径经过变量定义语句时创建该对象,当到达定义所在的块末尾时销毁它</p>
<h2 id="自动对象"><a class="anchor" href="#自动对象">#</a> 自动对象</h2>
<p>只存在于块执行期间的对象被称为 <strong>自动对象</strong>(automatic object)</p>
<p>当块的执行结束后,块中创建的自动对象的值就变成未定义的了</p>
<p>形参就是一种自动对象:函数开始时为形参申请存储空间,因为形参定义在函数体作用域之内,所以一旦函数终止,形参也就被销毁</p>
<p>自动对象的初始化:</p>
<ul>
<li>形参对应的自动对象:用传递给函数的实参初始化形参对应的自动对象</li>
<li>局部变量对应的自动对象:
<ul>
<li>如果变量定义本身含有初始值,就用这个初始值进行初始化</li>
<li>否则,如果变量定义本身不含初始值,执行默认初始化</li>
</ul>
</li>
</ul>
<blockquote>
<p>内置类型的未初始化局部变量将产生未定义的值</p>
</blockquote>
<h2 id="局部静态对象"><a class="anchor" href="#局部静态对象">#</a> 局部静态对象</h2>
<p>某些时候,有必要令局部变量的生命周期贯穿函数调用及之后的时间。此时,可以将局部变量定义成 <strong>static 类型</strong></p>
<p><strong>局部静态对象</strong>(local static object)在程序第一次经过对象定义语句时进行初始化,并且直到程序终止才被销毁。在此期间,即使对象所在的函数结束执行,也不会对它有影响</p>
<p>如果局部静态变量没有显式的初始值,它将执行值初始化,内置类型的局部静态变量初始化为 0</p>
<h2 id="函数声明"><a class="anchor" href="#函数声明">#</a> 函数声明</h2>
<p>类似于变量,函数只能定义一次,但可以声明多次</p>
<blockquote>
<p>唯一的例外是,如果一个函数永远也不会被我们用到,那么它可以只有声明没有定义</p>
</blockquote>
<p>函数的声明和函数的定义非常类似,唯一的区别是,函数声明无须函数体,用一个分号替代即可</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">// 函数声明</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">void</span> <span class="token function">print</span><span class="token punctuation">(</span>vector<span class="token operator"><</span><span class="token keyword">int</span><span class="token operator">></span><span class="token double-colon punctuation">::</span>const_iterator beg<span class="token punctuation">,</span></pre></td></tr><tr><td data-num="3"></td><td><pre> vector<span class="token operator"><</span><span class="token keyword">int</span><span class="token operator">></span><span class="token double-colon punctuation">::</span>const_iterator end<span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr></tbody></table></figure><p>函数的声明不包含函数体,所以也就无须形参的名字(事实上,在函数的声明中经常省略形参的名字)</p>
<p>函数的三要素(返回类型、函数名、形参类型)描述了函数的接口,说明了调用该函数所需的全部信息</p>
<p>函数声明也称作<strong>函数原型</strong>(function prototype)</p>
<h3 id="在头文件中进行函数声明"><a class="anchor" href="#在头文件中进行函数声明">#</a> 在头文件中进行函数声明</h3>
<p>类似于变量,函数也应该在头文件中声明而在源文件中定义</p>
<blockquote>
<p>允许把函数的声明直接放在使用该函数的源文件中,但是这么做可能会很烦琐而且容易出错。相反,如果把函数声明放在头文件中,就能确保同一函数的所有声明保持一致。而且,一旦我们想改变函数的接口,只需改变一条声明即可</p>
</blockquote>
<p><strong>含有函数声明的头文件应该被包含到定义函数的源文件中</strong></p>
<h3 id="分离式编译"><a class="anchor" href="#分离式编译">#</a> 分离式编译</h3>
<p>为了允许编写程序时按照逻辑关系将其划分开来,C++ 语言支持所谓的分离式编译(separate compilation)</p>
<p>分离式编译允许我们把程序分割到几个文件中去,每个文件独立编译</p>
<p>如果我们修改了其中一个源文件,那么只需重新编译那个改动了的文件</p>
<p>大多数编译器提供了分离式编译每个文件的机制,这一过程通常会产生一个后缀名是 .obj(Windows)或 .o(UNIX)的文件(该文件包含对象代码(object code))</p>
<h1 id="参数传递"><a class="anchor" href="#参数传递">#</a> 参数传递</h1>
<p>每次调用函数时都会重新创建它的形参,并用传入的实参对形参进行初始化</p>
<ul>
<li>
<p>当形参是引用类型时,它将绑定到对应的实参上。此时,我们说它对应的实参被 <strong>引用传递</strong>(passed by reference)或者函数被 <strong>传引用调用</strong>(called by reference)</p>
</li>
<li>
<p>当实参的值被拷贝给形参时,形参和实参是两个相互独立的对象。我们说这样的实参被 <strong>值传递</strong>(passed by value)或者函数被 <strong>传值调用</strong>(called by value)</p>
</li>
</ul>
<h2 id="传值参数"><a class="anchor" href="#传值参数">#</a> 传值参数</h2>
<p>函数对形参做的所有操作都不会影响实参</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">int</span> <span class="token function">fact</span><span class="token punctuation">(</span><span class="token keyword">int</span> val<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="2"></td><td><pre> <span class="token keyword">int</span> ret <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="3"></td><td><pre> <span class="token keyword">while</span> <span class="token punctuation">(</span>val<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="4"></td><td><pre> ret <span class="token operator">*=</span> val<span class="token operator">--</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="5"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="6"></td><td><pre> <span class="token keyword">return</span> ret<span class="token punctuation">;</span></pre></td></tr><tr><td data-num="7"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></tbody></table></figure><h3 id="指针形参"><a class="anchor" href="#指针形参">#</a> 指针形参</h3>
<p>可以将指针作为函数的形参</p>
<ul>
<li>调用函数时,会将指针实参拷贝给形参(形参和实参是两个相互独立的对象)</li>
<li>由于形参和实参具有相同的值,可以通过形参间接地访问实参所指的对象,并且可以可以修改它所指对象的值</li>
<li>改变形参并不会影响实参</li>
</ul>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">// 该函数接受一个指针,然后将指针所指的值置 0</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">void</span> <span class="token function">reset</span><span class="token punctuation">(</span><span class="token keyword">int</span> <span class="token operator">*</span>ip<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// 指针形参</span></pre></td></tr><tr><td data-num="3"></td><td><pre> <span class="token operator">*</span>ip <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token comment">// 改变指针 ip 所指对象的值</span></pre></td></tr><tr><td data-num="4"></td><td><pre> ip <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token comment">// 只改变了 ip 的局部拷贝,实参并未被改变</span></pre></td></tr><tr><td data-num="5"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></tbody></table></figure><blockquote>
<p>熟悉 C 的程序员常常使用指针类型的形参访问函数外部的对象。在 C++ 语言中,建议使用引用类型的形参替代指针</p>
</blockquote>
<h2 id="传引用参数"><a class="anchor" href="#传引用参数">#</a> 传引用参数</h2>
<p>引用形参绑定到对应的实参上</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">// 该函数接受一个 int 对象的引用,然后将对象的值置为 0</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">void</span> <span class="token function">reset</span><span class="token punctuation">(</span><span class="token keyword">int</span> <span class="token operator">&</span>i<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">//i 是传给 reset 函数的对象的另一个名字</span></pre></td></tr><tr><td data-num="3"></td><td><pre> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token comment">// 改变了 i 所引对象的值</span></pre></td></tr><tr><td data-num="4"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></tbody></table></figure><h3 id="使用引用避免拷贝"><a class="anchor" href="#使用引用避免拷贝">#</a> 使用引用避免拷贝</h3>
<p>拷贝大的类类型对象或者容器对象是比较低效的,此时可以通过引用传入参数</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">// 比较两个 string 对象的长度</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">bool</span> <span class="token function">isShorter</span><span class="token punctuation">(</span><span class="token keyword">const</span> string <span class="token operator">&</span>s1<span class="token punctuation">,</span> <span class="token keyword">const</span> string <span class="token operator">&</span>s2<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="3"></td><td><pre> <span class="token keyword">return</span> s1<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator"><</span> s2<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="4"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></tbody></table></figure><p>甚至,有的类类型(包括 IO 类型在内)根本就不支持拷贝操作。此时,函数只能通过引用形参访问该类型的对象</p>
<blockquote>
<p>如果函数无须改变引用形参的值,最好将其声明为常量引用</p>
</blockquote>
<h3 id="使用引用形参返回额外信息"><a class="anchor" href="#使用引用形参返回额外信息">#</a> 使用引用形参返回额外信息</h3>
<p>一个函数只能返回一个值,然而有时函数需要同时返回多个值,因此可以考虑通过引用形参获取多个结果</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">// 返回 s 中 c 第一次出现的位置索引</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token comment">// 引用形参 occurs 负责统计 c 出现的总次数</span></pre></td></tr><tr><td data-num="3"></td><td><pre>string<span class="token double-colon punctuation">::</span>size_type <span class="token function">find_char</span><span class="token punctuation">(</span><span class="token keyword">const</span> string <span class="token operator">&</span>s<span class="token punctuation">,</span> <span class="token keyword">char</span> c<span class="token punctuation">,</span></pre></td></tr><tr><td data-num="4"></td><td><pre> string<span class="token double-colon punctuation">::</span>size_type <span class="token operator">&</span>occurs<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="5"></td><td><pre> <span class="token keyword">auto</span> ret <span class="token operator">=</span> s<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 第一次出现的位置(如果有的话)</span></pre></td></tr><tr><td data-num="6"></td><td><pre> occurs <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token comment">// 出现次数置 0</span></pre></td></tr><tr><td data-num="7"></td><td><pre> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">decltype</span><span class="token punctuation">(</span>ret<span class="token punctuation">)</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">!=</span> s<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token operator">++</span>i<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="8"></td><td><pre> <span class="token keyword">if</span> <span class="token punctuation">(</span>s<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">==</span> c<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="9"></td><td><pre> <span class="token keyword">if</span> <span class="token punctuation">(</span>ret <span class="token operator">==</span> s<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="10"></td><td><pre> ret <span class="token operator">=</span> i<span class="token punctuation">;</span> <span class="token comment">// 记录 c 第一次出现的位置</span></pre></td></tr><tr><td data-num="11"></td><td><pre> <span class="token operator">++</span>occurs<span class="token punctuation">;</span> <span class="token comment">// 更新出现次数</span></pre></td></tr><tr><td data-num="12"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="13"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="14"></td><td><pre> <span class="token keyword">return</span> ret<span class="token punctuation">;</span> <span class="token comment">//c 出现次数通过 occurs 隐式地返回</span></pre></td></tr><tr><td data-num="15"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></tbody></table></figure><h2 id="const-形参和实参"><a class="anchor" href="#const-形参和实参">#</a> const 形参和实参</h2>
<p>在用实参初始化形参时,会忽略掉形参的顶层 const</p>
<p>换而言之,当形参具有顶层 const 时,传给它常量对象或者非常量对象都是可以的(可参考 <strong>const 限定符</strong>)</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">void</span> <span class="token function">fcn</span> <span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token keyword">int</span> i<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="2"></td><td><pre> <span class="token comment">/* fcn 可以读取 i ,但不能向 i 写值 */</span></pre></td></tr><tr><td data-num="3"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></tbody></table></figure><p>调用 fcn 函数时,既可以传入 const int ,也可以传入 int</p>
<p>在 C++ 语言中,允许我们定义若干具有相同名字的函数,不过前提是不同函数的形参列表应该有明显的区别,例如:</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">void</span> <span class="token function">helper</span> <span class="token punctuation">(</span>string <span class="token operator">&</span>s<span class="token punctuation">,</span> <span class="token keyword">int</span> start<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">/* 第一个 helper 函数 */</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">void</span> <span class="token function">helper</span> <span class="token punctuation">(</span><span class="token keyword">int</span> n<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">/* 第二个 helper 函数 */</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></pre></td></tr></tbody></table></figure><p>然而,由于形参的顶层 const 会被忽略,以下两个同名函数不能同时定义</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">void</span> <span class="token function">fcn</span> <span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token keyword">int</span> i<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">/* */</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">void</span> <span class="token function">fcn</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">/* */</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment">// 错误:重复定义了 fcn (int)</span></pre></td></tr></tbody></table></figure><p>因为顶层 const 被忽略掉了,所以在上面的代码中传入两个 fcn 函数的参数可以完全一样,因此第二个 fcn 是错误的</p>
<h3 id="const-与-指针或引用形参"><a class="anchor" href="#const-与-指针或引用形参">#</a> const 与 指针或引用形参</h3>
<p>形参的初始化方式和变量的初始化方式是一样的:</p>
<ul>
<li>可以使用非常量来初始化一个底层 const 对象,但是反过来不行</li>
<li>一个引用必须用同类型的对象初始化</li>
</ul>
<p>变量初始化:</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">42</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">const</span> <span class="token keyword">int</span> <span class="token operator">*</span>cp <span class="token operator">=</span> <span class="token operator">&</span>i<span class="token punctuation">;</span> <span class="token comment">// 正确:但是不能通过 cp 改变 i</span></pre></td></tr><tr><td data-num="3"></td><td><pre><span class="token keyword">const</span> <span class="token keyword">int</span> <span class="token operator">&</span>r <span class="token operator">=</span> i<span class="token punctuation">;</span> <span class="token comment">// 正确:但是不能通过 r 改变 i</span></pre></td></tr><tr><td data-num="4"></td><td><pre><span class="token keyword">const</span> <span class="token keyword">int</span> <span class="token operator">&</span>r2 <span class="token operator">=</span> <span class="token number">42</span><span class="token punctuation">;</span> <span class="token comment">// 正确</span></pre></td></tr><tr><td data-num="5"></td><td><pre><span class="token keyword">int</span> <span class="token operator">*</span>p <span class="token operator">=</span> cp<span class="token punctuation">;</span> <span class="token comment">// 错误:p 的类型和 cp 的类型不匹配(存在通过 p 改变 cp 所指对象的风险)</span></pre></td></tr><tr><td data-num="6"></td><td><pre><span class="token keyword">int</span> <span class="token operator">&</span>r3 <span class="token operator">=</span> r<span class="token punctuation">;</span> <span class="token comment">// 错误:r3 的类型和 r 的类型不匹配(存在通过 r3 改变 r 所引对象的风险)</span></pre></td></tr><tr><td data-num="7"></td><td><pre><span class="token keyword">int</span> <span class="token operator">&</span>r4 <span class="token operator">=</span> <span class="token number">42</span><span class="token punctuation">;</span> <span class="token comment">// 错误:不能用字面值初始化一个非常量引用</span></pre></td></tr></tbody></table></figure><p>参数传递:</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">const</span> <span class="token keyword">int</span> ci <span class="token operator">=</span> i<span class="token punctuation">;</span></pre></td></tr><tr><td data-num="3"></td><td><pre>string<span class="token double-colon punctuation">::</span>size_type ctr <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="4"></td><td><pre><span class="token function">reset</span><span class="token punctuation">(</span><span class="token operator">&</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 调用形参类型是 int * 的 reset 函数</span></pre></td></tr><tr><td data-num="5"></td><td><pre><span class="token function">reset</span><span class="token punctuation">(</span><span class="token operator">&</span>ci<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 错误:不能用指向 const int 对象的指针初始化 int * 形参</span></pre></td></tr><tr><td data-num="6"></td><td><pre><span class="token function">reset</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 调用形参类型是 int & 的 reset 函数</span></pre></td></tr><tr><td data-num="7"></td><td><pre><span class="token function">reset</span><span class="token punctuation">(</span>ci<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 错误:不能将普通引用绑定到 const 对象 ci 上</span></pre></td></tr><tr><td data-num="8"></td><td><pre><span class="token function">reset</span><span class="token punctuation">(</span><span class="token number">42</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 错误:不能将葡萄引用绑定到字面值上</span></pre></td></tr><tr><td data-num="9"></td><td><pre><span class="token function">reset</span><span class="token punctuation">(</span>ctr<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 错误:类型不匹配,ctr 是无符号类型</span></pre></td></tr><tr><td data-num="10"></td><td><pre><span class="token function">find_char</span><span class="token punctuation">(</span><span class="token string">"Hello World!"</span><span class="token punctuation">,</span> <span class="token char">'o'</span><span class="token punctuation">,</span> ctr<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 正确:find_char 的第一个形参是对常量的引用</span></pre></td></tr></tbody></table></figure><p>要想调用引用版本的 reset ,只能使用 int 类型的对象,而不能使用字面值、求值结果为 int 的表达式、需要转换的对象或者 const int 类型的对象。类似的,要想调用指针版本的 reset,只能使用 int* 类型的对象</p>
<p>我们能传递一个字符串字面值作为 find_char 的第一个实参,这是因为该函数的引用形参是常量引用,而 C++ 允许我们用字面值初始化常量引用</p>
<h3 id="尽量使用常量引用"><a class="anchor" href="#尽量使用常量引用">#</a> 尽量使用常量引用</h3>
<p>将函数不会改变的形参定义成(普通的)引用是一种比较常见的错误,这么做会给函数的调用者造成误导,即,函数可以修改它的实参的值</p>
<p>此外,使用引用而非常量引用也会极大地限制函数所能接受的实参类型。例如,我们不能把 const 对象、字面值或者需要类型转换的对象传递给普通的引用形参</p>
<h2 id="数组形参"><a class="anchor" href="#数组形参">#</a> 数组形参</h2>
<p>数组 无法直接通过数组名进行拷贝,因此,无法以值传递的方式使用数组参数</p>
<p>由于 数组(通常)会被转换成指针,当我们为一个函数传递一个数组时,实际上传递的是指向数组首元素的指针</p>
<p>尽管不能以值传递的方式传递数组,但是我们可以把形参写成类似数组的形式:</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">// 尽管表现形式不同,但这三个 print 函数是等价的</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token comment">// 每个函数都有一个 const int* 类型的形参</span></pre></td></tr><tr><td data-num="3"></td><td><pre><span class="token keyword">void</span> <span class="token function">print</span> <span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token keyword">int</span><span class="token operator">*</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="4"></td><td><pre><span class="token keyword">void</span> <span class="token function">print</span> <span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token keyword">int</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token comment">// 函数的意图是作用于一个数组</span></pre></td></tr><tr><td data-num="5"></td><td><pre><span class="token keyword">void</span> <span class="token function">print</span> <span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token keyword">int</span> <span class="token punctuation">[</span><span class="token number">10</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token comment">// 这里的维度表示我们期望的数组维度,实际不一定是这个维度</span></pre></td></tr></tbody></table></figure><p>当编译器处理对 print 函数的调用时,只检查传入的参数是否是 const int* 类型</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> j<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">}</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token function">print</span><span class="token punctuation">(</span><span class="token operator">&</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 正确:&i 的类型是 int*</span></pre></td></tr><tr><td data-num="3"></td><td><pre><span class="token function">print</span><span class="token punctuation">(</span>j<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 正确:j 转换成 int* 并指向 j [0]</span></pre></td></tr></tbody></table></figure><p>如果我们传给 print 函数的是一个数组,则实参自动地转换成指向数组首元素的指针。其中,数组的大小对函数的调用没有影响</p>
<blockquote>
<p>以数组作为形参的函数必须确保使用数组时不会越界</p>
</blockquote>
<h3 id="数组指针形参"><a class="anchor" href="#数组指针形参">#</a> 数组指针形参</h3>
<p>因为数组是以指针的形式传递给函数的,所以一开始函数并不知道数组的确切尺寸,调用者应该为此提供一些额外的信息</p>
<p>管理指针形参有三种常用的技术:</p>
<ul>
<li>使用标记指定数组长度</li>
<li>使用标准库规范</li>
<li>显式传递一个表示数组大小的形参</li>
</ul>
<h4 id="使用标记指定数组长度"><a class="anchor" href="#使用标记指定数组长度">#</a> 使用标记指定数组长度</h4>
<ul>
<li>要求数组本身包含一个结束标记</li>
<li>这种方法适用于那些有明显结束标记且该标记不会与普通数据混淆的情况</li>
<li>典型示例:C 风格字符串,C 风格字符串存储在字符数组中,并且在最后一个字符后面跟着一个空字符</li>
</ul>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">// 遇到空字符停止</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">void</span> <span class="token function">print</span><span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token keyword">char</span> <span class="token operator">*</span>cp<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="3"></td><td><pre> <span class="token keyword">if</span> <span class="token punctuation">(</span>cp<span class="token punctuation">)</span> <span class="token comment">// 若 cp 不是一个空指针</span></pre></td></tr><tr><td data-num="4"></td><td><pre> <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token operator">*</span>cp<span class="token punctuation">)</span> <span class="token comment">// 只要指针所指的字符不是空字符</span></pre></td></tr><tr><td data-num="5"></td><td><pre> cout <span class="token operator"><<</span> <span class="token operator">*</span>cp<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token comment">// 输出当前字符并将指针向前移动一个位置</span></pre></td></tr><tr><td data-num="6"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></tbody></table></figure><h4 id="使用标准库规范"><a class="anchor" href="#使用标准库规范">#</a> 使用标准库规范</h4>
<p>传递指向数组首元素和尾后元素的指针</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">void</span> <span class="token function">print</span><span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token keyword">int</span> <span class="token operator">*</span>beg<span class="token punctuation">,</span> <span class="token keyword">const</span> <span class="token keyword">int</span> <span class="token operator">*</span>end<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// 传入两个指针:一个指向首元素,另一个指向尾元素的下一位置</span></pre></td></tr><tr><td data-num="2"></td><td><pre> <span class="token comment">// 输出 beg 到 end 之间(不含 end )的所有元素</span></pre></td></tr><tr><td data-num="3"></td><td><pre> <span class="token keyword">while</span> <span class="token punctuation">(</span>beg <span class="token operator">!=</span> end<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="4"></td><td><pre> cout <span class="token operator"><<</span> <span class="token operator">*</span>beg<span class="token operator">++</span> <span class="token operator"><<</span> endl<span class="token punctuation">;</span> <span class="token comment">// 输出当前元素并将指针向前移动一个位置</span></pre></td></tr><tr><td data-num="5"></td><td><pre><span class="token punctuation">}</span></pre></td></tr><tr><td data-num="6"></td><td><pre></pre></td></tr><tr><td data-num="7"></td><td><pre><span class="token keyword">int</span> j<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">}</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="8"></td><td><pre><span class="token function">print</span><span class="token punctuation">(</span><span class="token function">begin</span><span class="token punctuation">(</span>j<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">end</span><span class="token punctuation">(</span>j<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//begin 和 end 函数,分别得到指向数组 j 的首元素和尾后元素的指针</span></pre></td></tr></tbody></table></figure><h4 id="显式传递一个表示数组大小的形参"><a class="anchor" href="#显式传递一个表示数组大小的形参">#</a> 显式传递一个表示数组大小的形参</h4>
<p>即,专门定义一个形参表示数组大小</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">//const int ia [] 等价于 const int* ia</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token comment">//size 表示数组的大小,将它显式地传给函数,用于控制对 ia 的访问</span></pre></td></tr><tr><td data-num="3"></td><td><pre><span class="token keyword">void</span> <span class="token function">print</span><span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token keyword">int</span> ia<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> size_t size<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="4"></td><td><pre> <span class="token keyword">for</span> <span class="token punctuation">(</span>size_t i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">!=</span> size<span class="token punctuation">;</span> <span class="token operator">++</span>i<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="5"></td><td><pre> cout <span class="token operator"><<</span> ia<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator"><<</span> endl<span class="token punctuation">;</span></pre></td></tr><tr><td data-num="6"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="7"></td><td><pre><span class="token punctuation">}</span></pre></td></tr><tr><td data-num="8"></td><td><pre></pre></td></tr><tr><td data-num="9"></td><td><pre><span class="token keyword">int</span> j<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment">// 大小为 3 的整型数组</span></pre></td></tr><tr><td data-num="10"></td><td><pre><span class="token function">print</span><span class="token punctuation">(</span>j<span class="token punctuation">,</span> <span class="token function">end</span><span class="token punctuation">(</span>j<span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token function">begin</span><span class="token punctuation">(</span>j<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr></tbody></table></figure><p>正如之前所说,当函数不需要对数组元素执行写操作的时候,数组形参应该是指向 const 的指针(即,常量指针)。只有当函数确实要改变元素值的时候,才把形参定义成指向非常量的指针</p>
<h3 id="数组引用形参"><a class="anchor" href="#数组引用形参">#</a> 数组引用形参</h3>
<p>C++ 允许将变量定义成数组的引用(详见 <strong>数组</strong> ),同样地,也允许将形参定义成数组的引用,此时,引用形参绑定到对应的数组上</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">// 正确:形参是数组的引用,维度是类型的一部分</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">void</span> <span class="token function">print</span><span class="token punctuation">(</span><span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">&</span>arr<span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">10</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="3"></td><td><pre> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">auto</span> elem <span class="token operator">:</span> arr<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="4"></td><td><pre> cout <span class="token operator"><<</span> elem <span class="token operator"><<</span> endl<span class="token punctuation">;</span></pre></td></tr><tr><td data-num="5"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></tbody></table></figure><p>注意, <code>&arr</code> 两端的括号必不可少:</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token function">f</span><span class="token punctuation">(</span><span class="token keyword">int</span> <span class="token operator">&</span>arr <span class="token punctuation">[</span><span class="token number">10</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token comment">// 错误:将 arr 声明成了引用的数组</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token function">f</span><span class="token punctuation">(</span><span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">&</span>arr<span class="token punctuation">)</span> <span class="token punctuation">[</span><span class="token number">10</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token comment">// 正确:arr 是具有 10 个整数的整型数组的引用</span></pre></td></tr></tbody></table></figure><p>数组大小是构成数组类型的一部分,只要不超过维度,就可以在函数体内放心地使用数组</p>
<p>然而,这一用法也在无形中限制了 print 函数的可用性,即,只能将函数作用于大小为 10 的数组:</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> j<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">}</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">int</span> k<span class="token punctuation">[</span><span class="token number">10</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">,</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token number">3</span><span class="token punctuation">,</span><span class="token number">4</span><span class="token punctuation">,</span><span class="token number">5</span><span class="token punctuation">,</span><span class="token number">6</span><span class="token punctuation">,</span><span class="token number">7</span><span class="token punctuation">,</span><span class="token number">8</span><span class="token punctuation">,</span><span class="token number">9</span><span class="token punctuation">}</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="3"></td><td><pre><span class="token function">print</span><span class="token punctuation">(</span><span class="token operator">&</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 错误:实参不是含有 10 个整数的数组</span></pre></td></tr><tr><td data-num="4"></td><td><pre><span class="token function">print</span><span class="token punctuation">(</span>j<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 错误:实参不是含有 10 个整数的数组</span></pre></td></tr><tr><td data-num="5"></td><td><pre><span class="token function">print</span><span class="token punctuation">(</span>k<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 正确:实参是含有 10 个整数的数组</span></pre></td></tr></tbody></table></figure><h3 id="传递多维数组"><a class="anchor" href="#传递多维数组">#</a> 传递多维数组</h3>
<p>将多维数组传递给函数时,真正传递的是指向数组首元素的指针</p>
<p>因为我们处理的是数组的数组,首元素本身就是一个数组,数组第二维(以及后面所有维度)的大小都是数组类型的一部分,不能省略</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">//matrix 指向数组的首元素,该数组的元素是由 10 个整数构成的数组</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">void</span> <span class="token function">print</span><span class="token punctuation">(</span><span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>matrix<span class="token punctuation">)</span> <span class="token punctuation">[</span><span class="token number">10</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token keyword">int</span> rowSize<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span> <span class="token comment">//matrix 是一个指向含有 10 个整数的数组的指针</span></pre></td></tr></tbody></table></figure><p>注意, <code>*matrix</code> 两端的括号必不可少</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">int</span> <span class="token operator">*</span>matrix<span class="token punctuation">[</span><span class="token number">10</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment">// 10 个指针构成的数组</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>matrix<span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">10</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment">// 指向含有 10 个整数的数组的指针</span></pre></td></tr></tbody></table></figure><p>我们也可以使用数组的语法定义函数(此时编译器会一如既往地忽略掉第一个维度,所以最好不要把它包括在形参列表内)</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">//matrix 看似是一个二维数组,实则是指向含有 10 个整数的数组的指针</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">void</span> <span class="token function">print</span><span class="token punctuation">(</span><span class="token keyword">int</span> matrix<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">10</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token keyword">int</span> rowSize<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span></pre></td></tr></tbody></table></figure><h2 id="main-处理命令行选项"><a class="anchor" href="#main-处理命令行选项">#</a> main: 处理命令行选项</h2>
<p>到目前为止,我们定义的 main 函数都只有空形参列表:</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">int</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">}</span></pre></td></tr></tbody></table></figure><p>然而,有时我们确实需要给 main 传递实参。一种常见的情况是,用户通过设置一组选项来确定函数所要执行的操作。例如,假定 main 函数位于可执行文件 prog 之内,我们可以向程序传递下面的选项:</p>
<pre><code>prog -d -o ofile data0
</code></pre>
<p>这些命令行选项通过两个(可选的)形参传递给 main 函数:</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">int</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token keyword">int</span> argc<span class="token punctuation">,</span> <span class="token keyword">char</span> <span class="token operator">*</span>argv<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token punctuation">}</span></pre></td></tr></tbody></table></figure><p>第二个形参 argv 是一个数组,它的元素是指向 C 风格字符串的指针;第一个形参 argc 表示数组中字符串的数量</p>
<p>因为第二个形参是数组,所以 main 函数也可以定义成:</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">int</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token keyword">int</span> argc<span class="token punctuation">,</span> <span class="token keyword">char</span> <span class="token operator">*</span><span class="token operator">*</span>argv<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token comment">//argv 执指向 char*</span></pre></td></tr></tbody></table></figure><p>当实参传给 main 函数之后,argv 的第一个元素指向程序的名字或者一个空字符串,接下来的元素依次传递命令行提供的实参。最后一个指针之后的元素值保证为 0</p>
<p>以上面提供的命令行 <code>prog -d -o ofile data0</code> 为例,argc 等于 5,argv 包含如下 C 风格字符串</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre>argv<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">"prog"</span><span class="token punctuation">;</span> <span class="token comment">// 或者 argv [0] 也可以指向一个空字符串</span></pre></td></tr><tr><td data-num="2"></td><td><pre>argv<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">"-d"</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="3"></td><td><pre>argv<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">"-o"</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="4"></td><td><pre>argv<span class="token punctuation">[</span><span class="token number">3</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">"ofile"</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="5"></td><td><pre>argv<span class="token punctuation">[</span><span class="token number">4</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">"data0"</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="6"></td><td><pre>argv<span class="token punctuation">[</span><span class="token number">5</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span></pre></td></tr></tbody></table></figure><blockquote>
<p>当使用 <code>argv</code> 中的实参时,一定要记得可选的实参从 <code>argv[1]</code> 开始; <code>argv[0]</code> 保存程序的名字,而非用户输入</p>
</blockquote>
<h2 id="含有可变形参的函数"><a class="anchor" href="#含有可变形参的函数">#</a> 含有可变形参的函数</h2>
<p>有时我们无法提前预知应该向函数传递几个实参。例如,我们想要编写代码输出程序产生的错误信息,此时最好用同一个函数实现该项功能,以便对所有错误的处理能够整齐划一。然而,错误信息的种类不同,所以调用错误输出函数时传递的实参也各不相同</p>
<p>为了编写能处理不同数量实参的函数,C++ 11 新标准提供了两种主要的方法:</p>
<ul>
<li>如果所有的实参类型相同,可以传递一个名为 <code>initializer_list</code> 的标准库类型</li>
<li>如果实参的类型不同,我们可以编写一种特殊的函数,也就是所谓的可变参数模板</li>
</ul>
<p>C++ 还有一种特殊的形参类型(即省略符),可以用它传递可变数量的实参(这种功能<strong>一般只用于与 C 函数交互的接口程序</strong>)</p>
<h3 id="initializer_list-形参"><a class="anchor" href="#initializer_list-形参">#</a> initializer_list 形参</h3>
<p>如果函数的实参数量未知、但是全部实参的类型都相同,我们可以使用 initializer_list 类型的形参</p>
<p>initializer_list 是一种标准库类型,用于表示某种特定类型的值的数组</p>
<p>initializer_list 类型定义在同名的头文件中</p>
<p><img loading="lazy" data-src="Cpp6-%E5%87%BD%E6%95%B0/initializer_list.webp" alt=""></p>
<p>和 vector 一样,initializer_list 也是一种模板类型。定义 initializer_list 对象时,必须说明列表中所含元素的类型</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre>initializer_list<span class="token operator"><</span>string<span class="token operator">></span> ls<span class="token punctuation">;</span> <span class="token comment">//initializer_list 的元素类型是 string</span></pre></td></tr><tr><td data-num="2"></td><td><pre>initializer_list<span class="token operator"><</span><span class="token keyword">int</span><span class="token operator">></span> li<span class="token punctuation">;</span> <span class="token comment">//initializer_list 的元素类型是 int</span></pre></td></tr></tbody></table></figure><p>和 vector 不一样的是,initializer_list 对象中的元素永远是常量值,我们无法改变 initializer_list 对象中元素的值</p>
<p>我们使用如下的形式编写输出错误信息的函数,使其可以作用于可变数量的实参:</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">void</span> <span class="token function">error_msg</span><span class="token punctuation">(</span>initializer_list<span class="token operator"><</span>string<span class="token operator">></span> il<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="2"></td><td><pre> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">auto</span> beg <span class="token operator">=</span> il<span class="token punctuation">.</span><span class="token function">begin</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> beg <span class="token operator">!=</span> il<span class="token punctuation">.</span><span class="token function">end</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token operator">++</span>beg<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="3"></td><td><pre> cout <span class="token operator"><<</span> <span class="token operator">*</span>beg <span class="token operator"><<</span> <span class="token string">" "</span> <span class="token punctuation">;</span></pre></td></tr><tr><td data-num="4"></td><td><pre> cout <span class="token operator"><<</span> endl<span class="token punctuation">;</span></pre></td></tr><tr><td data-num="5"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></tbody></table></figure><p>其中,作用于 initializer_list 对象的 begin 和 end 操作类似于 vector 对应的成员</p>
<ul>
<li>begin () 成员提供一个指向列表首元素的指针</li>
<li>end () 成员提供一个指向列表尾后元素的指针</li>
</ul>
<p>initializer_list 包含 begin 和 end 成员,因此可以使用 <strong>范围 for</strong> 循环处理其中的元素</p>
<p>如果想向 initializer_list 形参中传递一个值的序列,则必须把序列放在一对花括号内</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">//expected 和 actual 是 string 对象</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">if</span> <span class="token punctuation">(</span>expected <span class="token operator">!=</span> actual<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="3"></td><td><pre> <span class="token function">error_msg</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token string">"functionX"</span><span class="token punctuation">,</span> expected<span class="token punctuation">,</span> actual<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="4"></td><td><pre><span class="token keyword">else</span></pre></td></tr><tr><td data-num="5"></td><td><pre> <span class="token function">error_msg</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token string">"functionX"</span><span class="token punctuation">,</span> <span class="token string">"okay"</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr></tbody></table></figure><p>含有 initializer_list 形参的函数也可以同时拥有其他形参。例如,调试系统可能有个名为 ErrCode 的类,用来表示不同类型的错误,因此我们可以改写之前的程序,使其包含一个 initializer_list 形参和一个 ErrCode 形参</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">void</span> <span class="token function">error_msg</span><span class="token punctuation">(</span>ErrCode e<span class="token punctuation">,</span> initializer_list<span class="token operator"><</span>string<span class="token operator">></span> il<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="2"></td><td><pre> cout <span class="token operator"><<</span> e<span class="token punctuation">.</span><span class="token function">msg</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator"><<</span> <span class="token string">": "</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="3"></td><td><pre> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token keyword">auto</span> <span class="token operator">&</span>elem <span class="token operator">:</span> il<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="4"></td><td><pre> cout <span class="token operator"><<</span> elem <span class="token operator"><<</span> <span class="token string">" "</span> <span class="token punctuation">;</span></pre></td></tr><tr><td data-num="5"></td><td><pre> cout <span class="token operator"><<</span> endl<span class="token punctuation">;</span></pre></td></tr><tr><td data-num="6"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></tbody></table></figure><p>为了调用这个版本的 error_msg 函数,需要额外传递一个 ErrCode 实参</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">if</span> <span class="token punctuation">(</span>expected <span class="token operator">!=</span> actual<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="2"></td><td><pre> <span class="token function">error_msg</span><span class="token punctuation">(</span><span class="token function">ErrCode</span><span class="token punctuation">(</span><span class="token number">42</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><span class="token string">"functionX"</span><span class="token punctuation">,</span> expected<span class="token punctuation">,</span> actual<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="3"></td><td><pre><span class="token keyword">else</span></pre></td></tr><tr><td data-num="4"></td><td><pre> <span class="token function">error_msg</span><span class="token punctuation">(</span><span class="token function">ErrCode</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><span class="token string">"functionX"</span><span class="token punctuation">,</span> <span class="token string">"okay"</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr></tbody></table></figure><h3 id="省略符形参"><a class="anchor" href="#省略符形参">#</a> 省略符形参</h3>
<p>省略符形参是为了便于 C++ 程序访问某些(使用了名为 varargs 的 C 标准库功能的)C 代码而设置的</p>
<p>通常,省略符形参不应用于其他目的</p>
<blockquote>
<p>省略符形参应该仅仅用于 C 和 C++ 通用的类型<br>
特别应该注意的是,大多数类类型的对象在传递给省略符形参时都无法正确拷贝</p>
</blockquote>
<p><strong>省略符形参只能出现在形参列表的最后一个位置</strong>,它的形式无外乎以下两种:</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">void</span> <span class="token function">foo</span><span class="token punctuation">(</span>parm_list<span class="token punctuation">,</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">void</span> <span class="token function">foo</span><span class="token punctuation">(</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr></tbody></table></figure><p>第一种形式指定了 foo 函数的部分形参的类型,对应于这些形参的实参将会执行正常的类型检查</p>
<p>省略符形参所对应的实参无须类型检查</p>
<p>在第一种形式中,形参声明后面的逗号是可选的</p>
<h1 id="返回类型和-return-语句"><a class="anchor" href="#返回类型和-return-语句">#</a> 返回类型和 return 语句</h1>
<p>return 语句:终止当前正在执行的函数,并将控制权返回到调用该函数的地方</p>
<p>return 语句的两种形式:</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token comment">// 无返回值</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">return</span> expression<span class="token punctuation">;</span> <span class="token comment">// 有返回值</span></pre></td></tr></tbody></table></figure><h2 id="无返回值函数"><a class="anchor" href="#无返回值函数">#</a> 无返回值函数</h2>
<p>没有返回值的 return 语句只能用在返回类型是 void 的函数中</p>
<p>返回 void 的函数并不一定要有 return 语句,因为这类函数最后会隐式地执行 return</p>
<p>一个返回类型是 void 的函数也能使用 return 语句的第二种形式,不过此时 return 语句的 expression 必须是另一个返回 void 的函数</p>
<p>强行令 void 函数返回其他类型的表达式将产生编译错误</p>
<h2 id="有返回值函数"><a class="anchor" href="#有返回值函数">#</a> 有返回值函数</h2>
<p>只要函数的返回类型不是 void ,该函数内的每条 return 语句就必须返回一个值</p>
<p>return 语句返回值的类型必须与函数的返回类型相同,或者能隐式地转换成函数的返回类型</p>
<h3 id="值是如何被返回的"><a class="anchor" href="#值是如何被返回的">#</a> 值是如何被返回的</h3>
<p>返回一个值的方式:返回的值用于初始化调用点的一个临时量,该临时量就是函数调用的结果</p>
<p>如果函数返回的类型是局部变量,则会返回该变量的副本</p>
<p>例如,书写一个返回类型是 string 的函数 make_plural,由于该函数的返回类型是 string ,返回值将被拷贝到调用点。即,该函数将返回 word 的副本或者一个未命名的临时 string 对象</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre>string <span class="token function">make_plural</span><span class="token punctuation">(</span>size_t ctr<span class="token punctuation">,</span> <span class="token keyword">const</span> string <span class="token operator">&</span>word<span class="token punctuation">,</span> <span class="token keyword">const</span> string ending<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="2"></td><td><pre> <span class="token keyword">return</span> <span class="token punctuation">(</span>ctr <span class="token operator">></span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">?</span> word <span class="token operator">+</span> ending <span class="token operator">:</span> word<span class="token punctuation">;</span></pre></td></tr><tr><td data-num="3"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></tbody></table></figure><p>如果函数返回引用类型,则不需要拷贝其所引对象</p>
<p>例如:书写一个函数,其中形参和返回类型都是 const string 的引用,不管是调用函数还是返回结果都不会真正拷贝 string 对象</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">// 挑出两个 string 对象中较短的那个,返回其引用</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">const</span> string <span class="token operator">&</span><span class="token function">shorterString</span><span class="token punctuation">(</span><span class="token keyword">const</span> string <span class="token operator">&</span>s1<span class="token punctuation">,</span> <span class="token keyword">const</span> string <span class="token operator">&</span>s2<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="3"></td><td><pre> <span class="token keyword">return</span> s1<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator"><=</span> s2<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">?</span> s1 <span class="token operator">:</span> s2<span class="token punctuation">;</span></pre></td></tr><tr><td data-num="4"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></tbody></table></figure><h3 id="不要返回局部对象的引用或指针"><a class="anchor" href="#不要返回局部对象的引用或指针">#</a> 不要返回局部对象的引用或指针</h3>
<p>函数完成后,它所占用的存储空间也随之被释放掉,因此,不要返回局部对象的引用或指针</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">const</span> string <span class="token operator">&</span><span class="token function">manip</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// 严重错误:函数试图返回局部对象的引用</span></pre></td></tr><tr><td data-num="2"></td><td><pre> string ret<span class="token punctuation">;</span></pre></td></tr><tr><td data-num="3"></td><td><pre> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>ret<span class="token punctuation">.</span><span class="token function">empty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="4"></td><td><pre> <span class="token keyword">return</span> ret<span class="token punctuation">;</span> <span class="token comment">// 错误:返回局部对象的引用!</span></pre></td></tr><tr><td data-num="5"></td><td><pre> <span class="token keyword">else</span></pre></td></tr><tr><td data-num="6"></td><td><pre> <span class="token keyword">return</span> <span class="token string">"Empty"</span><span class="token punctuation">;</span> <span class="token comment">// 错误:"Empty" 是一个局部临时量</span></pre></td></tr><tr><td data-num="7"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></tbody></table></figure><h3 id="返回类类型的函数和调用运算符"><a class="anchor" href="#返回类类型的函数和调用运算符">#</a> 返回类类型的函数和调用运算符</h3>
<p>和其他运算符一样,调用运算符也有优先级和结合律</p>
<p><strong>调用运算符的优先级 与 点运算符和箭头运算符 相同,并且也符合左结合律</strong></p>
<p>如果函数返回指针、引用或类的对象,我们就能使用函数调用的结果访问结果对象的成员</p>
<p>例如,我们可以通过如下形式得到较短 string 对象的长度:</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">// 调用 string 对象的 size 成员,该 string 对象是由 shorterString 函数返回的</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">auto</span> sz <span class="token operator">=</span> <span class="token function">shorterString</span><span class="token punctuation">(</span>s1<span class="token punctuation">,</span> s2<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr></tbody></table></figure><h3 id="引用返回左值"><a class="anchor" href="#引用返回左值">#</a> 引用返回左值</h3>
<p>函数的返回类型决定函数调用是否是左值:调用一个返回引用的函数将会得到左值,其他返回类型将得到右值</p>
<p>因此,可以像使用其他左值那样来使用返回引用的函数,特别是,可以给返回类型为非常量引用的函数的结果赋值</p>
<p>例如:</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">char</span> <span class="token operator">&</span><span class="token function">get_val</span><span class="token punctuation">(</span>string <span class="token operator">&</span>str<span class="token punctuation">,</span> string<span class="token double-colon punctuation">::</span>size_type ix<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="2"></td><td><pre> <span class="token keyword">return</span> str<span class="token punctuation">[</span>ix<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment">//get_val 假定索引值是有效的</span></pre></td></tr><tr><td data-num="3"></td><td><pre><span class="token punctuation">}</span></pre></td></tr><tr><td data-num="4"></td><td><pre></pre></td></tr><tr><td data-num="5"></td><td><pre><span class="token keyword">int</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="6"></td><td><pre> string <span class="token function">s</span><span class="token punctuation">(</span><span class="token string">"a value"</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="7"></td><td><pre> cout <span class="token operator"><<</span> s <span class="token operator"><<</span> endl<span class="token punctuation">;</span> <span class="token comment">// 输出 a value</span></pre></td></tr><tr><td data-num="8"></td><td><pre> <span class="token function">get_val</span><span class="token punctuation">(</span>s<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token char">'A'</span><span class="token punctuation">;</span> <span class="token comment">// 将 s [0] 的值改为 'A'</span></pre></td></tr><tr><td data-num="9"></td><td><pre> cout <span class="token operator"><<</span> s <span class="token operator"><<</span> endl<span class="token punctuation">;</span> <span class="token comment">// 输出 A value</span></pre></td></tr><tr><td data-num="10"></td><td><pre> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="11"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></tbody></table></figure><p>如果返回类型是常量引用,则不能给调用的结果赋值</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token function">shorterString</span><span class="token punctuation">(</span><span class="token string">"hi"</span><span class="token punctuation">,</span> <span class="token string">"bye"</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token string">"X"</span><span class="token punctuation">;</span> <span class="token comment">// 错误:返回值是个常量</span></pre></td></tr></tbody></table></figure><h3 id="列表初始化返回值"><a class="anchor" href="#列表初始化返回值">#</a> 列表初始化返回值</h3>
<p>C++ 11 标准规定,函数可以返回花括号包围的值的列表,该列表用来初始化函数调用点处的临时量</p>
<ul>
<li>如果列表为空,临时量执行值初始化</li>
<li>否则,返回的值由函数的返回类型决定</li>
</ul>
<p>例如:定义一个返回 vector 对象的函数,其中,vector 对象用来存放表示错误信息的 string 对象</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre>vector<span class="token operator"><</span>string<span class="token operator">></span> <span class="token function">process</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="2"></td><td><pre> <span class="token comment">// . . .</span></pre></td></tr><tr><td data-num="3"></td><td><pre> <span class="token comment">//expected 和 actual 是 string 对象</span></pre></td></tr><tr><td data-num="4"></td><td><pre> <span class="token keyword">if</span> <span class="token punctuation">(</span>expected<span class="token punctuation">.</span><span class="token function">empty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="5"></td><td><pre> <span class="token keyword">return</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment">// 返回一个空 vector 对象</span></pre></td></tr><tr><td data-num="6"></td><td><pre> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>expected <span class="token operator">==</span> actual<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="7"></td><td><pre> <span class="token keyword">return</span> <span class="token punctuation">{</span><span class="token string">"functionX"</span><span class="token punctuation">,</span> <span class="token string">"okay"</span><span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment">// 返回列表初始化的 vector 对象</span></pre></td></tr><tr><td data-num="8"></td><td><pre> <span class="token keyword">else</span></pre></td></tr><tr><td data-num="9"></td><td><pre> <span class="token keyword">return</span> <span class="token punctuation">{</span><span class="token string">"functionX"</span><span class="token punctuation">,</span> expected<span class="token punctuation">,</span> actual<span class="token punctuation">}</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="10"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></tbody></table></figure><p>如果函数返回的是内置类型,则花括号包围的列表最多包含一个值,而且该值所占空间不应该大于目标类型的空间</p>
<p>如果函数返回的是类类型,由类本身来定义如何使用初始值</p>
<h3 id="主函数-main-的返回值"><a class="anchor" href="#主函数-main-的返回值">#</a> 主函数 main 的返回值</h3>
<p>之前介绍过,如果函数的返回类型不是 void ,那么它必须返回一个值。但是这条规则有个例外:允许 main 函数没有 return 语句而直接结束</p>
<p>这是因为,如果控制到达了 main 函数的结尾处而且没有 return 语句,编译器将隐式地插入一条返回 0 的 return 语句</p>
<h3 id="递归"><a class="anchor" href="#递归">#</a> 递归</h3>
<p>如果一个函数调用了它自身,不管这种调用是直接的还是间接的,都称该函数为递归函数(recursive function)</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">// 计算 val 的阶乘</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">int</span> <span class="token function">factorial</span><span class="token punctuation">(</span><span class="token keyword">int</span> val<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="3"></td><td><pre> <span class="token keyword">if</span> <span class="token punctuation">(</span>val <span class="token operator">></span> <span class="token number">1</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="4"></td><td><pre> <span class="token keyword">return</span> <span class="token function">factorial</span><span class="token punctuation">(</span>val<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">*</span> val<span class="token punctuation">;</span></pre></td></tr><tr><td data-num="5"></td><td><pre> <span class="token keyword">return</span> <span class="token number">1</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="6"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></tbody></table></figure><blockquote>
<p>main 函数不能调用它自己</p>
</blockquote>
<h2 id="返回数组指针"><a class="anchor" href="#返回数组指针">#</a> 返回数组指针</h2>
<p>因此数组不能被拷贝,所以函数不能返回数组。但是,函数可以返回数组的指针或者引用</p>
<p>从语法上来说,要想定义一个返回数组的指针或引用的函数比较烦琐,但是有一些方法可以简化这一任务,其中最直接的方法是使用 <strong>类型别名</strong></p>
<p>例如:func 函数返回一个指向包含 10 个整数的数组的指针,其中,arrT 是含有 10 个整数的数组的别名</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">typedef</span> <span class="token keyword">int</span> arrT<span class="token punctuation">[</span><span class="token number">10</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment">//arrT 是一个类型别名,它表示的类型是含有 10 个整数的数组</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">using</span> arrT <span class="token operator">=</span> <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token number">10</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment">//arrT 的等价声明</span></pre></td></tr><tr><td data-num="3"></td><td><pre>arrT<span class="token operator">*</span> <span class="token function">func</span><span class="token punctuation">(</span><span class="token keyword">int</span> i<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//func 返回一个指向含有 10 个整数的数组的指针</span></pre></td></tr></tbody></table></figure><h3 id="声明一个返回数组指针的函数"><a class="anchor" href="#声明一个返回数组指针的函数">#</a> 声明一个返回数组指针的函数</h3>
<p>如果我们想定义一个返回数组指针的函数,并且不希望使用类型别名,则必须满足:</p>
<ul>
<li>数组的维度跟在函数名字之后</li>
<li>函数的形参列表也跟在函数名字后面,并且形参列表应该先于数组的维度</li>
</ul>
<p>因此,返回数组指针的函数形式如下所示:</p>
<pre><code>Type (*function(parameter_list))[dimension]
</code></pre>
<p>其中,Type 表示元素的类型,dimension 表示数组的大小,(*function (parameter_list)) 两端的括号必须存在(如果没有这对括号,函数的返回类型将是指针的数组)</p>
<p>例如:</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span><span class="token function">func</span><span class="token punctuation">(</span><span class="token keyword">int</span> i<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">10</span><span class="token punctuation">]</span><span class="token punctuation">;</span></pre></td></tr></tbody></table></figure><p>可以按照以下的顺序来逐层理解该声明的含义:・</p>
<ul>
<li>func (int i) 表示调用 func 函数时需要一个 int 类型的实参</li>
<li>(*func (int i)) 意味着我们可以对函数调用的结果执行解引用操作</li>
<li>(*func (int i))[10] 表示解引用 func 的调用将得到一个大小为 10 的数组</li>
<li>int (*func (int i))[10] 表示数组中的元素是 int 类型</li>
</ul>
<h3 id="使用尾置返回类型"><a class="anchor" href="#使用尾置返回类型">#</a> 使用尾置返回类型</h3>
<p>在 C++ 11 标准中还有一种可以简化上述 func 声明的方法,就是使用<strong>尾置返回类型</strong>(trailing return type)</p>
<p><strong>任何函数的定义都能使用尾置返回</strong>,但是这种形式对于返回类型比较复杂的函数最有效,比如返回类型是数组的指针或者数组的引用</p>
<p><strong>尾置返回类型跟在形参列表后面,并以一个 <code>-></code> 符号开头</strong>。为了表示函数真正的返回类型跟在形参列表之后,我们在本应该出现返回类型的地方放置一个 <code>auto</code></p>
<p>例如:</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">//func 接受一个 int 类型的实参,返回一个指针,该指针指向含有 10 个整数的数组</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">auto</span> <span class="token function">func</span><span class="token punctuation">(</span><span class="token keyword">int</span> i<span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token operator">*</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">10</span><span class="token punctuation">]</span><span class="token punctuation">;</span></pre></td></tr></tbody></table></figure><p>因为我们把函数的返回类型放在了形参列表之后,所以可以清楚地看到 func 函数返回的是一个指针,并且该指针指向了含有 10 个整数的数组</p>
<h3 id="使用-decltype"><a class="anchor" href="#使用-decltype">#</a> 使用 decltype</h3>
<p>如果我们知道函数返回的指针将指向哪个数组,可以使用 <code>decltype</code> 关键字声明返回类型</p>
<p>例如,下面的函数返回一个指针,该指针根据参数 i 的不同指向两个已知数组中的某一个</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">int</span> odd<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">3</span><span class="token punctuation">,</span><span class="token number">5</span><span class="token punctuation">,</span><span class="token number">7</span><span class="token punctuation">,</span><span class="token number">9</span><span class="token punctuation">}</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">int</span> even<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token number">4</span><span class="token punctuation">,</span><span class="token number">6</span><span class="token punctuation">,</span><span class="token number">8</span><span class="token punctuation">}</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="3"></td><td><pre><span class="token comment">// 返回一个指针,该指针指向含有 5 个整数的数组</span></pre></td></tr><tr><td data-num="4"></td><td><pre><span class="token keyword">decltype</span><span class="token punctuation">(</span>odd<span class="token punctuation">)</span> <span class="token operator">*</span><span class="token function">arrPtr</span><span class="token punctuation">(</span><span class="token keyword">int</span> i<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="5"></td><td><pre> <span class="token keyword">return</span> <span class="token punctuation">(</span>i <span class="token operator">%</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token operator">&</span>odd <span class="token operator">:</span> <span class="token operator">&</span>even<span class="token punctuation">;</span> <span class="token comment">// 返回一个指向数组的指针</span></pre></td></tr><tr><td data-num="6"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></tbody></table></figure><p>arrPtr 使用关键字 <code>decltype</code> 表示它的返回类型是个指针,并且该指针所指对象的类型与 odd 一致。因为 odd 是数组,所以 arrPtr 返回一个指向含有 5 个整数的数组的指针</p>
<p>注意:<strong> <code>decltype</code> 并不负责把数组类型转换成对应的指针</strong>,所以 decltype 的结果是个数组,要想表示 arrPtr 返回指针还必须在函数声明时加一个 <code>*</code> 符号</p>
<h1 id="函数重载"><a class="anchor" href="#函数重载">#</a> 函数重载</h1>
<p>如果同一作用域内的几个函数名字相同但形参列表不同,我们称之为 <strong>重载</strong>(overloaded)<strong>函数</strong></p>
<p>例如:</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">void</span> <span class="token function">print</span><span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token keyword">char</span> <span class="token operator">*</span>cp<span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">void</span> <span class="token function">print</span><span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token keyword">int</span> <span class="token operator">*</span>beg<span class="token punctuation">,</span> <span class="token keyword">const</span> <span class="token keyword">int</span> <span class="token operator">*</span>end<span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="3"></td><td><pre><span class="token keyword">void</span> <span class="token function">print</span><span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token keyword">int</span> ia<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> size_t size<span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr></tbody></table></figure><p>这些函数接受的形参类型不一样,但是执行的操作非常类似。当调用这些函数时,编译器会根据传递的实参类型推断想要的是哪个函数</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">int</span> j<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">,</span><span class="token number">1</span><span class="token punctuation">}</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token function">print</span><span class="token punctuation">(</span><span class="token string">"Hello World"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 调用 print (const char*)</span></pre></td></tr><tr><td data-num="3"></td><td><pre><span class="token function">print</span><span class="token punctuation">(</span>j<span class="token punctuation">,</span> <span class="token function">end</span><span class="token punctuation">(</span>j<span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token function">begin</span><span class="token punctuation">(</span>j<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 调用 print (const int*, size_t)</span></pre></td></tr><tr><td data-num="4"></td><td><pre><span class="token function">print</span><span class="token punctuation">(</span><span class="token function">begin</span><span class="token punctuation">(</span>j<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">end</span><span class="token punctuation">(</span>j<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 调用 print (const int*, const int*)</span></pre></td></tr></tbody></table></figure><blockquote>
<p>main 函数不能重载</p>
</blockquote>
<h2 id="重载函数的定义与调用"><a class="anchor" href="#重载函数的定义与调用">#</a> 重载函数的定义与调用</h2>
<h3 id="定义重载函数"><a class="anchor" href="#定义重载函数">#</a> 定义重载函数</h3>
<p>重载的函数应该在形参数量或形参类型上有所不同</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">// 定义一组函数,分别根据名字、电话、账号等信息查找记录</span></pre></td></tr><tr><td data-num="2"></td><td><pre>Record <span class="token function">lookup</span><span class="token punctuation">(</span><span class="token keyword">const</span> Account<span class="token operator">&</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 根据 Account 查找记录</span></pre></td></tr><tr><td data-num="3"></td><td><pre>Record <span class="token function">lookup</span><span class="token punctuation">(</span><span class="token keyword">const</span> Phone<span class="token operator">&</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 根据 Phone 查找记录</span></pre></td></tr><tr><td data-num="4"></td><td><pre>Record <span class="token function">lookup</span><span class="token punctuation">(</span><span class="token keyword">const</span> Name<span class="token operator">&</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 根据 Name 查找记录</span></pre></td></tr><tr><td data-num="5"></td><td><pre></pre></td></tr><tr><td data-num="6"></td><td><pre>Account acct<span class="token punctuation">;</span></pre></td></tr><tr><td data-num="7"></td><td><pre>Phone phone<span class="token punctuation">;</span></pre></td></tr><tr><td data-num="8"></td><td><pre>Record r1 <span class="token operator">=</span> <span class="token function">lookup</span><span class="token punctuation">(</span>acct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 调用接受 Account 的版本</span></pre></td></tr><tr><td data-num="9"></td><td><pre>Record r2 <span class="token operator">=</span> <span class="token function">lookup</span><span class="token punctuation">(</span>phone<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 调用接受 Phone 的版本</span></pre></td></tr></tbody></table></figure><p>不允许两个函数除了返回类型外其他所有要素都相同:假设有两个函数,它们的形参列表一样但是返回类型不同,则第二个函数的声明是错误的</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre>Record <span class="token function">lookup</span><span class="token punctuation">(</span><span class="token keyword">const</span> Account<span class="token operator">&</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">bool</span> <span class="token function">lookup</span><span class="token punctuation">(</span><span class="token keyword">const</span> Account<span class="token operator">&</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 错误:与上一个函数相比只有返回类型不同</span></pre></td></tr></tbody></table></figure><h3 id="判断两个形参的类型是否相异"><a class="anchor" href="#判断两个形参的类型是否相异">#</a> 判断两个形参的类型是否相异</h3>
<p>有时候两个形参列表看起来不一样,但实际上是相同的</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">// 每一对声明都是同一个函数</span></pre></td></tr><tr><td data-num="2"></td><td><pre>Record <span class="token function">lookup</span><span class="token punctuation">(</span><span class="token keyword">const</span> Account <span class="token operator">&</span>acct<span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="3"></td><td><pre>Record <span class="token function">lookup</span><span class="token punctuation">(</span><span class="token keyword">const</span> Account<span class="token operator">&</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 省略了形参的名字</span></pre></td></tr><tr><td data-num="4"></td><td><pre></pre></td></tr><tr><td data-num="5"></td><td><pre><span class="token keyword">typedef</span> Phone Telno<span class="token punctuation">;</span></pre></td></tr><tr><td data-num="6"></td><td><pre>Record <span class="token function">lookup</span><span class="token punctuation">(</span><span class="token keyword">const</span> Phone<span class="token operator">&</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="7"></td><td><pre>Record <span class="token function">lookup</span><span class="token punctuation">(</span><span class="token keyword">const</span> Telno<span class="token operator">&</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Telno 和 Phone 的类型相同(Telno 是 Phone 的别名)</span></pre></td></tr></tbody></table></figure><p>由于顶层 const 不影响传入函数的对象(详见 <strong>参数传递</strong> ),一个拥有顶层 const 的形参无法和另一个没有顶层 const 的形参区分开来</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre>Record <span class="token function">lookup</span><span class="token punctuation">(</span>Phone<span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="2"></td><td><pre>Record <span class="token function">lookup</span><span class="token punctuation">(</span><span class="token keyword">const</span> Phone<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 重复声明了 Record lookup (Phone)</span></pre></td></tr><tr><td data-num="3"></td><td><pre></pre></td></tr><tr><td data-num="4"></td><td><pre>Record <span class="token function">lookup</span><span class="token punctuation">(</span>Phone<span class="token operator">*</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="5"></td><td><pre>Record <span class="token function">lookup</span><span class="token punctuation">(</span>Phone<span class="token operator">*</span> <span class="token keyword">const</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 重复声明了 Record lookup (Phone*)</span></pre></td></tr></tbody></table></figure><p>如果形参是某种类型的指针或引用,则可区分其指向的是常量对象还是非常量对象(此时考虑的是底层 const ),以实现函数重载</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">// 对于接受引用或指针的函数而言,常量对象与非常量对象对应的形参不同</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token comment">// 定义了 4 个独立的重载函数</span></pre></td></tr><tr><td data-num="3"></td><td><pre>Record <span class="token function">lookup</span><span class="token punctuation">(</span>Account<span class="token operator">&</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 函数作用于 Account 的引用</span></pre></td></tr><tr><td data-num="4"></td><td><pre>Record <span class="token function">lookup</span><span class="token punctuation">(</span><span class="token keyword">const</span> Account<span class="token operator">&</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 新函数,作用于 Account 的常量引用</span></pre></td></tr><tr><td data-num="5"></td><td><pre></pre></td></tr><tr><td data-num="6"></td><td><pre>Record <span class="token function">lookup</span><span class="token punctuation">(</span>Account<span class="token operator">*</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 新函数,作用于 Account 的指针</span></pre></td></tr><tr><td data-num="7"></td><td><pre>Record <span class="token function">lookup</span><span class="token punctuation">(</span><span class="token keyword">const</span> Account<span class="token operator">*</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 新函数,作用于指向 Account 常量的指针</span></pre></td></tr></tbody></table></figure><p>在上面的例子中,编译器可以通过实参是否是常量来推断应该调用哪个函数</p>
<ul>
<li>因为 const 不能转换成其他类型,所以我们只能把 const 对象(或指向 const 的指针)传递给 const 形参</li>
<li>相反地,因为非常量可以转换成 const ,所以上面的 4 个函数都能作用于非常量对象或者指向非常量对象的指针</li>
<li>不过,当我们要传递一个非常量对象或者指向非常量对象的指针时,编译器会优先选用非常量版本的函数</li>
</ul>
<h3 id="建议何时不应该重载函数"><a class="anchor" href="#建议何时不应该重载函数">#</a> 建议:何时不应该重载函数</h3>
<p>尽管函数重载能在一定程度上减轻我们为函数起名字、记名字的负担,但是最好只重载那些确实非常相似的操作</p>
<p>有些情况下,给函数起不同的名字能使得程序更易理解</p>
<p>例如,以下是几个负责移动屏幕光标的函数</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre>Screen<span class="token operator">&</span> <span class="token function">moveHome</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="2"></td><td><pre>Screen<span class="token operator">&</span> <span class="token function">moveAbs</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="3"></td><td><pre>Screen<span class="token operator">&</span> <span class="token function">moveRel</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">,</span> string direction<span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr></tbody></table></figure><p>乍看上去,似乎可以把这组函数统一命名为 move ,从而实现函数的重载。然而,重载之后这些函数失去了名字中本来拥有的信息(因为这些函数移动光标的具体方式各不相同)</p>
<p>一般来说,是否重载函数要看哪个更容易理解</p>
<h3 id="const_cast-和重载"><a class="anchor" href="#const_cast-和重载">#</a> const_cast 和重载</h3>
<p>const_cast 常常用于有函数重载的上下文中</p>
<p>考虑下面的 shorterString 函数,其参数和返回类型都是 const string 的引用,我们可以对两个非常量的 string 实参调用这个函数,但返回的仍然是 const string 的引用</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">// 无论实参是常量还是非常量,返回类型都是 const string &</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">const</span> string <span class="token operator">&</span><span class="token function">shorterString</span><span class="token punctuation">(</span><span class="token keyword">const</span> string <span class="token operator">&</span>s1<span class="token punctuation">,</span> <span class="token keyword">const</span> string <span class="token operator">&</span>s2<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// 形参是 const string & 类型</span></pre></td></tr><tr><td data-num="3"></td><td><pre> <span class="token keyword">return</span> s1<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator"><=</span> s2<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">?</span> s1 <span class="token operator">:</span> s2<span class="token punctuation">;</span></pre></td></tr><tr><td data-num="4"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></tbody></table></figure><p>为了实现 “当实参不是常量时,得到的结果是一个普通的引用,即 string &”,可以使用 const_cast 定义一种新的 shorterString 函数:</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre>string <span class="token operator">&</span><span class="token function">shorterString</span><span class="token punctuation">(</span>string <span class="token operator">&</span>s1<span class="token punctuation">,</span> string <span class="token operator">&</span>s2<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// 形参是 string & 类型</span></pre></td></tr><tr><td data-num="2"></td><td><pre> <span class="token keyword">auto</span> <span class="token operator">&</span>r <span class="token operator">=</span> <span class="token function">shorterString</span><span class="token punctuation">(</span><span class="token generic-function"><span class="token function">const_cast</span><span class="token generic class-name"><span class="token operator"><</span><span class="token keyword">const</span> string<span class="token operator">&</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>s1<span class="token punctuation">)</span><span class="token punctuation">,</span></pre></td></tr><tr><td data-num="3"></td><td><pre> <span class="token generic-function"><span class="token function">const_cast</span><span class="token generic class-name"><span class="token operator"><</span><span class="token keyword">const</span> string<span class="token operator">&</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>s2<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 调用形参为 const string & 类型的 shorterString 函数</span></pre></td></tr><tr><td data-num="4"></td><td><pre> <span class="token keyword">return</span> <span class="token generic-function"><span class="token function">const_cast</span><span class="token generic class-name"><span class="token operator"><</span>string<span class="token operator">&</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>r<span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="5"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></tbody></table></figure><p>在这个版本的函数中,首先将它的实参强制转换成对 const 的引用,然后调用了 shorterString 函数的 const 版本。const 版本返回对 const string 的引用,这个引用事实上绑定在了某个初始的非常量实参上。因此,我们可以再将其转换回一个普通的 string &</p>
<h3 id="调用重载的函数"><a class="anchor" href="#调用重载的函数">#</a> 调用重载的函数</h3>
<p><strong>函数匹配</strong>(function matching):把函数调用与一组重载函数中的某一个关联起来。具体而言,编译器首先将调用的实参与重载集合中每一个函数的形参进行比较,然后根据比较的结果决定到底调用哪个函数</p>
<p>函数匹配也叫做<strong>重载确定</strong>(overload resolution)</p>
<p>调用重载函数时,有三种可能的结果:</p>
<ul>
<li>编译器找到一个与实参<strong>最佳匹配</strong>(best match)的函数,并生成调用该函数的代码</li>
<li>找不到任何一个函数与调用的实参匹配,此时编译器发出<strong>无匹配</strong>(no match)的错误信息</li>
<li>有多于一个函数可以匹配,但是每一个都不是明显的最佳选择。此时也将发生错误,称为<strong>二义性调用</strong>(ambiguous call)</li>
</ul>
<h2 id="重载与作用域"><a class="anchor" href="#重载与作用域">#</a> 重载与作用域</h2>
<blockquote>
<p>一般来说,将函数声明置于局部作用域内不是一个明智的选择。但是为了说明作用域和重载的相互关系,我们将暂时违反这一原则而使用局部函数声明</p>
</blockquote>
<p>如果我们在内层作用域中声明名字,它将隐藏外层作用域中声明的同名实体</p>
<p>因此,在不同的作用域中,无法重载函数名</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre>string <span class="token function">read</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">void</span> <span class="token function">print</span><span class="token punctuation">(</span><span class="token keyword">const</span> string <span class="token operator">&</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="3"></td><td><pre><span class="token keyword">void</span> <span class="token function">print</span><span class="token punctuation">(</span><span class="token keyword">double</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 重载 print 函数</span></pre></td></tr><tr><td data-num="4"></td><td><pre><span class="token keyword">void</span> <span class="token function">fooBar</span><span class="token punctuation">(</span><span class="token keyword">int</span> ival<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="5"></td><td><pre> <span class="token keyword">bool</span> read <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token comment">// 新作用域,隐藏了外层作用域中声明的 read</span></pre></td></tr><tr><td data-num="6"></td><td><pre> string s <span class="token operator">=</span> <span class="token function">read</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 错误:read 是一个布尔对象,而不是函数(因为外层的 read 函数被隐藏了)</span></pre></td></tr><tr><td data-num="7"></td><td><pre> <span class="token comment">// 通常来说,在局部作用域中声明函数不是一个好的选择</span></pre></td></tr><tr><td data-num="8"></td><td><pre> <span class="token keyword">void</span> <span class="token function">print</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 新作用域,隐藏了外层作用域中的 print</span></pre></td></tr><tr><td data-num="9"></td><td><pre> <span class="token function">print</span><span class="token punctuation">(</span><span class="token string">"Value: "</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 错误:print (const string &) 被隐藏掉了</span></pre></td></tr><tr><td data-num="10"></td><td><pre> <span class="token function">print</span><span class="token punctuation">(</span>ival<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 正确:当前 print (int) 可见</span></pre></td></tr><tr><td data-num="11"></td><td><pre> <span class="token function">print</span><span class="token punctuation">(</span><span class="token number">3.14</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 正确:调用 print (int) (外层的 print (double) 被隐藏了)</span></pre></td></tr><tr><td data-num="12"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></tbody></table></figure><p>当我们调用 print 函数时,编译器首先寻找对该函数名的声明,找到的是接受 int 值的那个局部声明。<strong>一旦在当前作用域中找到了所需的名字,编译器就会忽略掉外层作用域中的同名实体</strong>。剩下的工作就是检查函数调用是否有效了</p>
<blockquote>
<p>在 C++ 语言中,名字查找发生在类型检查之前</p>
</blockquote>
<p>第一个调用 print ("Value:") 传入一个字符串字面值,但是当前作用域内 print 函数唯一的声明要求参数是 int 类型,由于字符串字面值无法转换成 int 类型,这个调用是错误的(虽然外层作用域中的 print (const string&) 函数与本次调用匹配,但是它已经被隐藏掉了,根本不会被考虑)</p>
<p>假设我们把 print (int) 和其他 print 函数声明放在同一个作用域中,则它将成为另一种重载形式:</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">void</span> <span class="token function">print</span><span class="token punctuation">(</span><span class="token keyword">const</span> string <span class="token operator">&</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">void</span> <span class="token function">print</span><span class="token punctuation">(</span><span class="token keyword">double</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//print 函数的重载形式</span></pre></td></tr><tr><td data-num="3"></td><td><pre><span class="token keyword">void</span> <span class="token function">print</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//print 函数的另一种重载形式</span></pre></td></tr><tr><td data-num="4"></td><td><pre><span class="token keyword">void</span> <span class="token function">fooBar2</span><span class="token punctuation">(</span><span class="token keyword">int</span> ival<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="5"></td><td><pre> <span class="token function">print</span><span class="token punctuation">(</span><span class="token string">"Value: "</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 调用 print (const string &)</span></pre></td></tr><tr><td data-num="6"></td><td><pre> <span class="token function">print</span><span class="token punctuation">(</span>ival<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 调用 print (int)</span></pre></td></tr><tr><td data-num="7"></td><td><pre> <span class="token function">print</span><span class="token punctuation">(</span><span class="token number">3.14</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 调用 print (double)</span></pre></td></tr><tr><td data-num="8"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></tbody></table></figure><h1 id="特殊用途语言特性"><a class="anchor" href="#特殊用途语言特性">#</a> 特殊用途语言特性</h1>
<p>本节我们介绍函数相关的三种语言特性,这些特性对大多数程序都有用,它们分别是:默认实参、内联函数和 constexpr 函数,以及在程序调试过程中常用的一些功能</p>
<h2 id="默认实参"><a class="anchor" href="#默认实参">#</a> 默认实参</h2>
<p>有些函数的某个参数在大多数(但不是所有)调用中都被赋予了特定的值。此时,我们可以把这个特定值称为函数的<strong>默认实参</strong>(default argument)</p>
<p>默认实参作为形参的初始值出现在形参列表中</p>
<p>例如,我们使用 string 对象表示窗口的内容。一般情况下,我们希望该窗口的高、宽和背景字符都使用默认值,但是同时我们也应该允许用户为这几个参数自由指定与默认值不同的数值。因此,我们为每一个形参都提供了默认实参</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">typedef</span> string<span class="token double-colon punctuation">::</span>size_type sz<span class="token punctuation">;</span></pre></td></tr><tr><td data-num="2"></td><td><pre>string <span class="token function">screen</span><span class="token punctuation">(</span>sz ht <span class="token operator">=</span> <span class="token number">24</span><span class="token punctuation">,</span> sz wid <span class="token operator">=</span> <span class="token number">80</span><span class="token punctuation">,</span> <span class="token keyword">char</span> backgrnd <span class="token operator">=</span> <span class="token char">' '</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr></tbody></table></figure><p>我们可以为一个或多个形参定义默认值,不过需要注意的是,<strong>一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值</strong></p>
<p>调用含有默认实参的函数时,可以包含该实参,也可以省略该实参</p>
<h3 id="使用默认实参调用函数"><a class="anchor" href="#使用默认实参调用函数">#</a> 使用默认实参调用函数</h3>
<p>如果我们想使用默认实参,只要在调用函数的时候省略该实参就可以了</p>
<p>函数调用时实参按其位置解析,默认实参负责填补函数调用缺少的尾部实参(靠右侧位置)</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre>string window<span class="token punctuation">;</span></pre></td></tr><tr><td data-num="2"></td><td><pre>window <span class="token operator">=</span> <span class="token function">screen</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 等价于 screen (24, 80, ' ')</span></pre></td></tr><tr><td data-num="3"></td><td><pre>window <span class="token operator">=</span> <span class="token function">screen</span><span class="token punctuation">(</span><span class="token number">66</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 等价于 screen (66, 80, ' ')</span></pre></td></tr><tr><td data-num="4"></td><td><pre>window <span class="token operator">=</span> <span class="token function">screen</span><span class="token punctuation">(</span><span class="token number">66</span><span class="token punctuation">,</span> <span class="token number">256</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 等价于 screen (66, 256, ' ')</span></pre></td></tr><tr><td data-num="5"></td><td><pre>window <span class="token operator">=</span> <span class="token function">screen</span><span class="token punctuation">(</span><span class="token number">66</span><span class="token punctuation">,</span> <span class="token number">256</span><span class="token punctuation">,</span> <span class="token char">'#'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 等价于 screen (66, 256, '#')</span></pre></td></tr></tbody></table></figure><p>要想覆盖 backgrnd 的默认值,必须为 ht 和 wid 提供实参</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre>window <span class="token operator">=</span> <span class="token function">screen</span><span class="token punctuation">(</span><span class="token punctuation">,</span> <span class="token punctuation">,</span> <span class="token char">'?'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 错误:只能省略尾部的实参</span></pre></td></tr><tr><td data-num="2"></td><td><pre>window <span class="token operator">=</span> <span class="token function">screen</span><span class="token punctuation">(</span><span class="token char">'?'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 调用 screen ('?', 80, ' ')</span></pre></td></tr><tr><td data-num="3"></td><td><pre> <span class="token comment">//char 型的 '?' 可以转换成 string::size_type 类型的值 63</span></pre></td></tr></tbody></table></figure><p>当设计含有默认实参的函数时,需要合理设置形参的顺序,尽量让不怎么使用默认值的形参出现在前面,让那些经常使用默认值的形参出现在后面</p>
<h3 id="默认实参声明"><a class="anchor" href="#默认实参声明">#</a> 默认实参声明</h3>
<p>对于函数的声明来说,通常的习惯是将其放在头文件中,并且一个函数只声明一次,但是多次声明同一个函数也是合法的</p>
<p>不过有一点需要注意,在给定的作用域中一个形参只能被赋予一次默认实参。换句话说,函数的后续声明只能为之前那些没有默认值的形参添加默认实参,而且该形参右侧的所有形参必须都有默认值</p>
<p>假如给定</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">// 表示高度和宽度的形参没有默认值</span></pre></td></tr><tr><td data-num="2"></td><td><pre>string <span class="token function">screen</span><span class="token punctuation">(</span>sz<span class="token punctuation">,</span> sz<span class="token punctuation">,</span> <span class="token keyword">char</span> <span class="token operator">=</span> <span class="token char">' '</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr></tbody></table></figure><p>我们就不能再修改一个已经存在的默认值</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre>string <span class="token function">screen</span><span class="token punctuation">(</span>sz<span class="token punctuation">,</span> sz<span class="token punctuation">,</span> <span class="token keyword">char</span> <span class="token operator">=</span> <span class="token char">'*'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 错误:重复赋予默认实参</span></pre></td></tr></tbody></table></figure><p>但是可以按照如下形式添加默认实参</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre>string <span class="token function">screen</span><span class="token punctuation">(</span>sz <span class="token operator">=</span> <span class="token number">24</span><span class="token punctuation">,</span> sz <span class="token operator">=</span> <span class="token number">80</span><span class="token punctuation">,</span> <span class="token keyword">char</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 正确:添加默认实参</span></pre></td></tr></tbody></table></figure><blockquote>
<p>通常,应该在函数声明中指定默认实参,并将该声明放在合适的头文件中</p>
</blockquote>
<h3 id="默认实参初始值"><a class="anchor" href="#默认实参初始值">#</a> 默认实参初始值</h3>
<p>除了<strong>局部变量不能作为默认实参</strong>外,只要表达式的类型能转换成形参所需的类型,该表达式就能作为默认实参</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">//wd、def 和 ht 的声明必须出现在函数之外</span></pre></td></tr><tr><td data-num="2"></td><td><pre>sz wd <span class="token operator">=</span> <span class="token number">80</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="3"></td><td><pre><span class="token keyword">char</span> def <span class="token operator">=</span> <span class="token char">' '</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="4"></td><td><pre>sz <span class="token function">ht</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="5"></td><td><pre>string <span class="token function">screen</span><span class="token punctuation">(</span>sz <span class="token operator">=</span> <span class="token function">ht</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> sz <span class="token operator">=</span> wd<span class="token punctuation">,</span> <span class="token keyword">char</span> <span class="token operator">=</span> def<span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="6"></td><td><pre></pre></td></tr><tr><td data-num="7"></td><td><pre>string window <span class="token operator">=</span> <span class="token function">screen</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 调用 screen (ht (), 80, ' ')</span></pre></td></tr></tbody></table></figure><p>用作默认实参的名字(例如 ht () )会在函数声明所在的作用域内解析,但是会在函数调用时才求值</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">void</span> <span class="token function">f2</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="2"></td><td><pre> def <span class="token operator">=</span> <span class="token char">'*'</span><span class="token punctuation">;</span> <span class="token comment">// 改变了默认实参的值</span></pre></td></tr><tr><td data-num="3"></td><td><pre> sz wd <span class="token operator">=</span> <span class="token number">100</span><span class="token punctuation">;</span> <span class="token comment">// 隐藏了外层定义的 wd ,但是没有改变默认值</span></pre></td></tr><tr><td data-num="4"></td><td><pre> window <span class="token operator">=</span> <span class="token function">screen</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 调用 screen (ht (), 80, '*')</span></pre></td></tr><tr><td data-num="5"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></tbody></table></figure><p>我们在函数 f2 内部改变了 def 的值,所以对 screen 的调用将会传递这个更新过的值。另一方面,虽然我们的函数还声明了一个局部变量用于隐藏外层的 wd ,但是该局部变量与传递给 screen 的默认实参没有任何关系</p>
<h2 id="内联函数和-constexpr-函数"><a class="anchor" href="#内联函数和-constexpr-函数">#</a> 内联函数和 constexpr 函数</h2>
<p>此前我们编写了一个函数 shroterString ,用于比较两个 string 形参的长度并返回长度较小的 string 的引用。将这种规模较小的操作定义成函数,具有以下优点:</p>
<ul>
<li>便于阅读</li>
<li>可以确保行为的统一</li>
<li>如果我们需要修改计算过程,修改函数要比修改所有等价表达式更容易</li>
<li>可以被其他应用重复利用</li>
</ul>
<p>然而,使用 shorterString 函数也存在一个潜在的缺点:调用函数一般比求等价表达式的值要慢一些</p>
<p>在大多数机器上,一次函数调用其实包含着一系列工作:</p>
<ul>
<li>调用前要先保存寄存器,并在返回时恢复</li>
<li>可能需要拷贝实参</li>
<li>程序转向一个新的位置继续执行</li>
</ul>
<h3 id="内联函数可以避免函数调用的开销"><a class="anchor" href="#内联函数可以避免函数调用的开销">#</a> 内联函数可以避免函数调用的开销</h3>
<p>将函数指定为<strong>内联函数</strong>(inline),通常就是将它在每个调用点上 “内联地” 展开</p>
<p>假设我们把 shorterString 函数定义成内联函数,则如下调用</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre>cout <span class="token operator"><<</span> <span class="token function">shorterString</span><span class="token punctuation">(</span>s1<span class="token punctuation">,</span> s2<span class="token punctuation">)</span> <span class="token operator"><<</span> endl<span class="token punctuation">;</span></pre></td></tr></tbody></table></figure><p>将在编译过程中展开成类似于下面的形式</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre>cout <span class="token operator"><<</span> <span class="token punctuation">(</span>s1<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator"><</span> s2<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">?</span> s1 <span class="token operator">:</span> s2<span class="token punctuation">)</span> <span class="token operator"><<</span> endl<span class="token punctuation">;</span></pre></td></tr></tbody></table></figure><p>从而消除了 shorterString 函数的运行时开销</p>
<p><strong>在函数返回类型的前面加上加上关键字 <code>inline</code> ,即可将其声明为内联函数</strong></p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">// 内联版本:寻找两个 string 对象中较短的那个</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">inline</span> <span class="token keyword">const</span> string <span class="token operator">&</span> <span class="token function">shorterString</span><span class="token punctuation">(</span><span class="token keyword">const</span> string <span class="token operator">&</span>s1<span class="token punctuation">,</span> <span class="token keyword">const</span> string <span class="token operator">&</span>s2<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="3"></td><td><pre> <span class="token keyword">return</span> s1<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator"><=</span> s2<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">?</span> s1 <span class="token operator">:</span> s2<span class="token punctuation">;</span></pre></td></tr><tr><td data-num="4"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></tbody></table></figure><blockquote>
<p>内联说明只是向编译器发出的一个请求,编译器可以选择忽略这个请求</p>
</blockquote>
<p>一般来说,内联机制用于优化规模较小、流程直接、频繁调用的函数</p>
<p>很多编译器都不支持内联递归函数,而且,一个太长的函数也不太可能在调用点 “内联地” 展开</p>
<h3 id="constexpr-函数"><a class="anchor" href="#constexpr-函数">#</a> constexpr 函数</h3>
<p><strong>constexpr 函数</strong>(constexpr function)是指能用于常量表达式的函数</p>
<p>constexpr 函数的定义:</p>
<ul>
<li>函数的返回类型及所有形参的类型都必须是字面值类型</li>
<li>函数体中必须有且只有一条 return 语句</li>
</ul>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">constexpr</span> <span class="token keyword">int</span> <span class="token function">new_sz</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token number">42</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">constexpr</span> <span class="token keyword">int</span> foo <span class="token operator">=</span> <span class="token function">new_sz</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 正确:foo 是一个常量表达式</span></pre></td></tr></tbody></table></figure><p>在上面的代码中,我们把 new_sz 定义成一个无参数的 constexpr 函数,于是,new_sz 函数返回的是常量表达式,因此可以用 new_sz 函数来初始化 constexpr 类型的变量 foo</p>
<p>执行该初始化任务时,编译器把对 constexpr 函数的调用替换成其结果值。为了能在编译过程中随时展开,<strong>constexpr 函数被隐式地指定为内联函数</strong></p>
<blockquote>
<p>constexpr 函数体内也可以包含其他语句,只要这些语句在运行时不执行任何操作就行。例如,constexpr 函数中可以有空语句、类型别名以及 using 声明</p>
</blockquote>
<p>constexpr 函数的返回值可以不是一个常量</p>
<p>例如:定义一个 constexpr 函数 scale 。当我们给 scale 函数传入一个形如字面值 2 的常量表达式时,它的返回类型也是常量表达式,此时,编译器用相应的结果值替换对 scale 函数的调用。相反,如果我们用一个非常量表达式调用 scale 函数,比如 int 类型的对象 i ,则返回值是一个非常量表达式</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">// 如果 arg 是常量表达式,则 scale (arg) 也是常量表达式</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">constexpr</span> size_t <span class="token function">scale</span><span class="token punctuation">(</span>size_t cnt<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">new_sz</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> cnt<span class="token punctuation">;</span> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="3"></td><td><pre></pre></td></tr><tr><td data-num="4"></td><td><pre><span class="token comment">// 当 scale 的实参是常量表达式时,它的返回值也是常量表达式;反之则不是</span></pre></td></tr><tr><td data-num="5"></td><td><pre><span class="token keyword">int</span> arr<span class="token punctuation">[</span><span class="token function">scale</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment">// 正确:scale (2) 是常量表达式</span></pre></td></tr><tr><td data-num="6"></td><td><pre><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span> <span class="token comment">//i 不是常量表达式</span></pre></td></tr><tr><td data-num="7"></td><td><pre><span class="token keyword">int</span> a2<span class="token punctuation">[</span><span class="token function">scale</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment">// 错误:scale (i) 不是常量表达式</span></pre></td></tr></tbody></table></figure><p>当把 scale 函数用在需要常量表达式的上下文中时,编译器会负责检查函数的结果是否符合要求。如果结果恰好不是常量表达式,编译器将发出错误信息</p>
<blockquote>
<p>constexpr 函数不一定返回常量表达式</p>
</blockquote>
<h3 id="把内联函数和-constexpr-函数放在头文件内"><a class="anchor" href="#把内联函数和-constexpr-函数放在头文件内">#</a> 把内联函数和 constexpr 函数放在头文件内</h3>
<p>和其他函数不一样,内联函数和 constexpr 函数可以在程序中多次定义</p>
<p>不过,对于某个给定的内联函数或者 constexpr 函数来说,它的多个定义必须完全一致</p>
<p>因此,内联函数和 constexpr 函数通常定义在头文件中</p>
<h2 id="调试帮助"><a class="anchor" href="#调试帮助">#</a> 调试帮助</h2>
<p>C++ 程序员有时会用到一种类似于 <strong>头文件保护</strong> 的技术,以便有选择地执行调试代码</p>
<p>基本思想:程序可以包含一些用于调试的代码,这些代码只在开发程序时使用。当应用程序编写完成准备发布时,要先屏蔽掉调试代码</p>
<p>这种方法用到两项预处理功能: <code>assert</code> 和 <code>NDEBUG</code></p>
<h3 id="assert-预处理宏"><a class="anchor" href="#assert-预处理宏">#</a> assert 预处理宏</h3>
<p>assert 是一种预处理宏(preprocessor marco)</p>
<p>预处理宏是一个行为类似于内联函数的预处理变量</p>
<p>assert 宏使用一个表达式作为它的条件:</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token function">assert</span><span class="token punctuation">(</span>expr<span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr></tbody></table></figure><p>其首先对 expr 求值,如果表达式为假(即,值为 0),assert 输出信息并终止程序的执行。如果表达式为真(即,值不为 0),assert 什么也不做</p>
<p>assert 宏定义在 <code>cassert</code> 头文件中,即:</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span><span class="token string"><cassert></span></span></pre></td></tr></tbody></table></figure><blockquote>
<p>预处理名字由预处理器而非编译器管理,因此我们可以直接使用预处理名字,即,直接使用 assert 而不是 std::assert,也不需要为 assert 提供 using 声明</p>
</blockquote>
<blockquote>
<p>和预处理变量一样,宏名字在程序内必须唯一。因此,含有 cassert 头文件的程序不能再定义名为 assert 的变量、函数或者其他实体</p>
</blockquote>
<p><strong>assert 宏常用于检查 “不能发生” 的条件</strong></p>
<p>例如,一个对输入文本进行操作的程序,可能要求所有给定单词的长度都大于某个阈值</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token function">assert</span><span class="token punctuation">(</span>word<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">></span> threshold<span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr></tbody></table></figure><h3 id="ndebug-预处理变量"><a class="anchor" href="#ndebug-预处理变量">#</a> NDEBUG 预处理变量</h3>
<p>NDEBUG 是一个预处理变量,表示当前不处于调试状态(not debug),可用于控制 assert 的行为:</p>
<ul>
<li>如果没有定义 NDEBUG ,assert 将执行运行时检查</li>
<li>如果定义了 NDEBUG ,则 assert 什么也不做</li>
</ul>
<p>默认状态下,没有定义 NDEBUG</p>
<p>我们可以使用一个 #define 语句定义 NDEBUG ,从而关闭调试状态</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">NDEBUG</span> <span class="token comment">// 关闭调试状态(必须定义在 cassert 头文件之前)</span></span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string"><cassert></span></span></pre></td></tr><tr><td data-num="3"></td><td><pre></pre></td></tr><tr><td data-num="4"></td><td><pre><span class="token keyword">int</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="5"></td><td><pre> <span class="token keyword">int</span> x <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="6"></td><td><pre> <span class="token function">assert</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="7"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></tbody></table></figure><p>定义 NDEBUG 能避免检查各种条件所需的运行时开销(其实根本就不会执行运行时检查),因此,assert 应该仅用于验证那些确实不可能发生的事情</p>
<blockquote>
<p>我们可以把 assert 当成调试程序的一种辅助手段,但是不能用它替代真正的运行时逻辑检查,也不能替代程序本身应该包含的错误检查</p>
</blockquote>
<p>除了用于 assert 外,也可以使用 NDEBUG 编写自己的条件调试代码(详见 <strong>预处理变量</strong> )</p>
<ul>
<li>如果 NDEBUG 未定义,将执行 #ifndef 和 #endif 之间的代码</li>
<li>如果定义了 NDEBUG ,则会忽略 #ifndef 和 #endif 之间的代码</li>
</ul>
<p>例如:</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">void</span> <span class="token function">print</span><span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token keyword">int</span> ia<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> size_t size<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">ifndef</span> <span class="token expression">NDEBUG</span></span></pre></td></tr><tr><td data-num="3"></td><td><pre><span class="token comment">//_ _func_ _ 是编译器定义的一个局部静态变量,用于存放函数的名字</span></pre></td></tr><tr><td data-num="4"></td><td><pre>cerr <span class="token operator"><<</span> _ _func_ _ <span class="token operator"><<</span> <span class="token string">": array size is "</span> <span class="token operator"><<</span> size <span class="token operator"><<</span> endl<span class="token punctuation">;</span></pre></td></tr><tr><td data-num="5"></td><td><pre><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">endif</span></span></pre></td></tr><tr><td data-num="6"></td><td><pre><span class="token comment">// ...</span></pre></td></tr></tbody></table></figure><p>在这段代码中,我们使用变量 <code>_ _func_ _</code> 输出当前调试的函数的名字(即 "print" )</p>
<p>编译器为每个函数都定义了 <code>_ _func_ _</code> ,它是 <code>const char</code> 的一个静态数组,用于存放函数的名字</p>
<p>除了 C++ 编译器定义的 <code>_ _func_ _</code> 之外,预处理器还定义了另外 4 个用于程序调试的名字:</p>
<ul>
<li><code>_ _FILE_ _</code> :存放文件名的字符串字面值</li>
<li><code>_ _LINE_ _</code> :存放当前行号的整型字面值</li>
<li><code>_ _LINE_ _</code> :存放当前行号的整型字面值</li>
<li><code>_ _DATE_ _</code> :存放文件编译日期的字符串字面值</li>
</ul>
<p>可以使用这些常量在错误消息中提供更多信息,例如:</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">if</span> <span class="token punctuation">(</span>word<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator"><</span> threshold<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="2"></td><td><pre> cerr <span class="token operator"><<</span> <span class="token string">"Error: "</span> <span class="token operator"><<</span> _ _FILE_ _</pre></td></tr><tr><td data-num="3"></td><td><pre> <span class="token operator"><<</span> <span class="token string">" : in function "</span> <span class="token operator"><<</span> _ _func_ _</pre></td></tr><tr><td data-num="4"></td><td><pre> <span class="token operator"><<</span> <span class="token string">" at line "</span> <span class="token operator"><<</span> _ _LINE_ _ <span class="token operator"><<</span> endl</pre></td></tr><tr><td data-num="5"></td><td><pre> <span class="token operator"><<</span> <span class="token string">" Compiled on "</span> <span class="token operator"><<</span> _ _DATE_ _</pre></td></tr><tr><td data-num="6"></td><td><pre> <span class="token operator"><<</span> <span class="token string">" at "</span> <span class="token operator"><<</span> _ _TIME_ _ <span class="token operator"><<</span> endl</pre></td></tr><tr><td data-num="7"></td><td><pre> <span class="token operator"><<</span> <span class="token string">" Word read was \""</span> <span class="token operator"><<</span> word</pre></td></tr><tr><td data-num="8"></td><td><pre> <span class="token operator"><<</span> <span class="token string">"\": Length too short"</span> <span class="token operator"><<</span> endl<span class="token punctuation">;</span></pre></td></tr></tbody></table></figure><p>如果我们给程序提供了一个长度小于 threshold 的 string 对象,将得到下面的错误消息:</p>
<pre><code>Error:wdebug.cc : in function main at line 27
Compiled on Jul 11 2012 at 20:50:03
Word read was "foo": Length too short
</code></pre>
<h1 id="函数匹配"><a class="anchor" href="#函数匹配">#</a> 函数匹配</h1>
<p>当重载函数的形参数量相等、并且某些形参的类型可以由其他类型转换得到时,不太容易确定某次调用应该选用哪个重载函数</p>
<h2 id="函数匹配-2"><a class="anchor" href="#函数匹配-2">#</a> 函数匹配</h2>
<p>函数匹配:从一组重载函数中选取最佳函数的过程</p>
<h3 id="函数匹配的步骤"><a class="anchor" href="#函数匹配的步骤">#</a> 函数匹配的步骤</h3>
<p>选定本次调用对应的重载函数集</p>
<ul>
<li>集合中的函数称为 <strong>候选函数</strong>(candidate function)</li>
<li>候选函数具备两个特征:
<ul>
<li>与被调用的函数同名</li>
<li>其声明在调用点可见</li>
</ul>
</li>
</ul>
<p>考察本次调用提供的实参,从候选函数中选出能被这组实参调用的函数</p>
<ul>
<li>这些新选出的函数称为 <strong>可行函数</strong>(viable function)</li>
<li>可行函数具备两个特征:
<ul>
<li>形参数量与本次调用提供的实参数量相等(如果函数含有默认实参,在调用该函数时,传入的实参数量可能少于它实际使用的实参数量)</li>
<li>每个实参的类型与对应的形参类型相同,或者能转换成形参的类型</li>
</ul>
</li>
<li>如果没找到可行函数,编译器将报告无匹配函数的错误</li>
</ul>
<p>逐一检查函数调用提供的实参,寻找形参类型与实参类型最匹配的那个可行函数(即,寻找<strong>最佳匹配</strong>)</p>
<ul>
<li>如果有且只有一个函数满足下列条件,则匹配成功:
<ul>
<li>该函数每个实参的匹配都不劣于其他可行函数需要的匹配</li>
<li>至少有一个实参的匹配优于其他可行函数提供的匹配</li>
</ul>
</li>
<li>下一节将介绍到:实参类型与形参类型越接近,它们匹配得越好</li>
<li>如果在检查了所有实参之后没有任何一个函数脱颖而出,则该调用是错误的。此时,编译器将报告二义性调用的信息</li>
</ul>
<blockquote>
<p>调用重载函数时应尽量避免强制类型转换,如果在实际应用中确实需要强制类型转换,则说明我们设计的形参列表不合理</p>
</blockquote>
<h3 id="实例分析"><a class="anchor" href="#实例分析">#</a> 实例分析</h3>
<p>以下面这组函数及其调用为例:</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">void</span> <span class="token function">f</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">void</span> <span class="token function">f</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="3"></td><td><pre><span class="token keyword">void</span> <span class="token function">f</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="4"></td><td><pre><span class="token keyword">void</span> <span class="token function">f</span><span class="token punctuation">(</span><span class="token keyword">double</span><span class="token punctuation">,</span> <span class="token keyword">double</span> <span class="token operator">=</span> <span class="token number">3.14</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="5"></td><td><pre></pre></td></tr><tr><td data-num="6"></td><td><pre><span class="token function">f</span><span class="token punctuation">(</span><span class="token number">5.6</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 调用 f (double, double)</span></pre></td></tr></tbody></table></figure><p>第一步:可以确定上述 4 个名为 f 的函数均为候选函数</p>
<p>第二步:可以根据实参的数量排除 void f () 和 void f (int, int) ,选出 void f (int) 和 void f (double, double = 3.14) 两个可行函数</p>
<ul>
<li>排除的两个函数:我们的调用提供了一个实参,而 void f () 不使用形参,void f (int, int) 使用两个形参</li>
<li>可行的两个函数:void f (int) 使用一个 int 形参,而我们调用传入的 double 实参可以转换成形参类型 int ;void f (double, double = 3.14) 本应接受两个 double 实参,但因为它含有一个默认实参,所以可以只用一个实参来调用它</li>
</ul>
<p>第三步:逐一考察实参,寻找最佳匹配</p>
<ul>
<li>调用提供了一个(显式的)double 型实参,如果调用 f (int) ,实参将不得不从 double 转换成 int</li>
<li>相反,可行函数 void (double, double) 则与实参精确匹配</li>
<li><strong>精确匹配比需要类型转换的匹配更好</strong>,因此,编译器把 f (5.6) 解析成对函数 void (double, double) 的调用,并使用默认值填补我们未提供的第二个实参</li>
</ul>
<p>当实参的数量有两个或更多时,函数匹配就比较复杂了</p>
<p>例如,考察形如 f (42, 2.56) 的调用:</p>
<p>第一步:4 个名为 f 的函数均为候选函数</p>
<p>第二步:确定 void f (int, int) 和 void f (double, double) 为可行函数</p>
<p>第三步:逐一考察实参,寻找最佳匹配</p>
<ul>
<li>考虑第一个实参 42 :函数 f (int, int) 能精确匹配,而函数 f (double, double) 需要先将 int 类型实参转换成 double 类型。此时,函数 f (int, int) 优于 函数 f (double, double)</li>
<li>接着考虑第二个实参 2.56 :函数 f (double, double) 能精确匹配,而函数 f (int, int) 须将 2.56 从 double 类型转换成 int 型。此时,函数 f (double, double) 优于 函数 f (int, int)</li>
<li>由于每个可行函数各自在一个实参上实现了更好的匹配,无法从整体上判断孰优孰劣。编译器最终将因为这个调用具有二义性而拒绝其请求</li>
</ul>
<blockquote>
<p>看起来我们似乎可以通过强制类型转换其中的一个实参来实现函数的匹配,但是在设计良好的系统中,不应该对实参进行强制类型转换</p>
</blockquote>
<h2 id="实参类型转换"><a class="anchor" href="#实参类型转换">#</a> 实参类型转换</h2>
<p>为了确定最佳匹配,编译器将实参类型到形参类型的转换划分成几个等级,具体排序如下所示(越靠前的,匹配程度越好):</p>
<ul>
<li>精确匹配,其包括以下情况:
<ul>
<li>实参类型和形参类型相同</li>
<li>实参从数组类型或函数类型转换成对应的指针类型</li>
<li>向实参添加顶层 const 或者从实参中删除顶层 const</li>
</ul>
</li>
<li>通过 const 转换实现的匹配</li>
<li>通过类型提升实现的匹配</li>
<li>通过算术类型转换或指针转换实现的匹配</li>
<li>通过类类型转换实现的匹配</li>
</ul>
<h3 id="需要类型提升和算术类型转换的匹配"><a class="anchor" href="#需要类型提升和算术类型转换的匹配">#</a> 需要类型提升和算术类型转换的匹配</h3>
<p><strong>小整型一般都会提升到 int 类型或更大的整数类型</strong></p>
<p>例如,假设有两个函数,一个接受 int 、另一个接受 short ,则</p>
<ul>
<li>只有当调用提供的是 short 类型的值时才会选择 short 版本的函数</li>
<li>有时候,即使实参是一个很小的整数值,也会直接将它提升成 int 类型,此时使用 short 版本反而会导致类型转换</li>
</ul>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">void</span> <span class="token function">ff</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">void</span> <span class="token function">ff</span><span class="token punctuation">(</span><span class="token keyword">short</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="3"></td><td><pre></pre></td></tr><tr><td data-num="4"></td><td><pre><span class="token function">ff</span><span class="token punctuation">(</span><span class="token char">'a'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//char 提升成 int ,调用 f (int)</span></pre></td></tr></tbody></table></figure><p><strong>所有算术类型转换的级别都一样</strong></p>
<p>例如,从 int 向 unsigned int 的转换并不比从 int 向 double 的转换级别高</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">void</span> <span class="token function">manip</span><span class="token punctuation">(</span><span class="token keyword">long</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">void</span> <span class="token function">manip</span><span class="token punctuation">(</span><span class="token keyword">float</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="3"></td><td><pre></pre></td></tr><tr><td data-num="4"></td><td><pre><span class="token function">manip</span><span class="token punctuation">(</span><span class="token number">3.14</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 错误:二义性调用</span></pre></td></tr></tbody></table></figure><h3 id="函数匹配和-const-实参"><a class="anchor" href="#函数匹配和-const-实参">#</a> 函数匹配和 const 实参</h3>
<p><strong>如果重载函数的区别仅在于函数的引用类型的形参是否为常量引用(或者,指针类型的形参是否为常量指针),编译器将通过实参是否为常量来决定选择哪个函数</strong></p>
<blockquote>
<p>When we call an overloaded function that differs on whether a reference or pointer parameter refers or points to const, the compiler uses the constness of the argument to decide which function to call.</p>
</blockquote>
<p>例如:</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre>Record <span class="token function">lookup</span><span class="token punctuation">(</span>Account<span class="token operator">&</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 函数的参数是 Account 的引用</span></pre></td></tr><tr><td data-num="2"></td><td><pre>Record <span class="token function">lookup</span><span class="token punctuation">(</span><span class="token keyword">const</span> Account<span class="token operator">&</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 函数的参数是 Account 的常量引用</span></pre></td></tr><tr><td data-num="3"></td><td><pre></pre></td></tr><tr><td data-num="4"></td><td><pre><span class="token keyword">const</span> Account a<span class="token punctuation">;</span></pre></td></tr><tr><td data-num="5"></td><td><pre>Account b<span class="token punctuation">;</span></pre></td></tr><tr><td data-num="6"></td><td><pre><span class="token function">lookup</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 调用 lookup (const Account&)</span></pre></td></tr><tr><td data-num="7"></td><td><pre><span class="token function">lookup</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 调用 lookup (Account&)</span></pre></td></tr></tbody></table></figure><p>第一个调用传入的是 const 对象 a :因为不能把普通引用绑定到 const 对象上,唯一可行的函数是 以常量引用作为形参的函数 lookup (const Account&) ,并且调用该函数与实参 a 精确匹配</p>
<p>第二个调用传入的是非常量对象 b :对于该调用而言,两个函数都是可行的,因为 b 既可以用来初始化常量引用也可以用来初始化非常量引用。然而,用非常量对象来初始化常量引用需要类型转换,因此,应该选用非常量版本的函数 lookup (Account&)</p>
<p>指针类型的形参也是类似的:如果两个函数的唯一区别是指针形参指向常量或非常量,编译器通过实参是否为常量来决定选用哪个函数</p>
<ul>
<li>如果实参是指向常量的指针,调用形参为 const* 的函数</li>
<li>如果实参是指向非常量的指针,调用形参是普通指针的函数</li>
</ul>
<h1 id="函数指针"><a class="anchor" href="#函数指针">#</a> 函数指针</h1>
<p>函数指针(function pointer)指向的是函数而非对象</p>
<p>类似于其他指针,函数指针也指向一个特定的类型(即,函数的类型)</p>
<p>函数的类型由它的返回类型和形参类型共同决定,与函数名无关</p>
<blockquote>
<p>A function pointer is just that—a pointer that denotes a function rather than an object. Like any other pointer, a function pointer points to a particular type. A function’s type is determined by its return type and the types of its parameters. The function’s name is not part of its type.</p>
</blockquote>
<p>例如,以下函数的类型是 bool (const string &, const string &)</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">// 比较两个 string 对象的长度</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">bool</span> <span class="token function">lengthCompare</span><span class="token punctuation">(</span><span class="token keyword">const</span> string <span class="token operator">&</span><span class="token punctuation">,</span> <span class="token keyword">const</span> string <span class="token operator">&</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr></tbody></table></figure><p>要想声明一个可以指向该函数的指针,只需要用指针替换函数名即可:</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">//pf 指向一个函数,该函数的参数是两个 const string 的引用,返回值是 bool 类型</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">bool</span> <span class="token punctuation">(</span><span class="token operator">*</span>pf<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">const</span> string <span class="token operator">&</span><span class="token punctuation">,</span> <span class="token keyword">const</span> string <span class="token operator">&</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr></tbody></table></figure><p>针对 bool (*pf)(const string &, const string &) 的分析逻辑:</p>
<ul>
<li>pf 前面有个 * ,表示 pf 是指针</li>
<li>右侧是形参列表,表示 pf 指向的是函数</li>
<li>再观察左侧,发现函数的返回类型是布尔值</li>
<li>因此,pf 就是一个指向函数的指针,其中该函数的参数是两个 const string 的引用,返回值是 bool 类型</li>
</ul>
<blockquote>
<p>*pf 两端的括号必不可少,如果不写这对括号(即: <code>bool *pf(const string &, const string &);</code> ),pf 将是一个返回类型为 bool * 的函数</p>
</blockquote>
<h2 id="使用函数指针"><a class="anchor" href="#使用函数指针">#</a> 使用函数指针</h2>
<p>当我们把函数名作为一个值使用时,该函数自动地转换成指针</p>
<p>例如,按照如下形式我们可以将函数 lengthCompare 的地址赋给 pf</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre>pf <span class="token operator">=</span> lengthCompare<span class="token punctuation">;</span> <span class="token comment">//pf 指向名为 lengthCompare 的函数</span></pre></td></tr><tr><td data-num="2"></td><td><pre>pf <span class="token operator">=</span> <span class="token operator">&</span>lengthCompare<span class="token punctuation">;</span> <span class="token comment">// 等价的赋值语句(取地址符是可选的)</span></pre></td></tr></tbody></table></figure><p>我们可以直接使用指向函数的指针来调用该函数,而无须提前解引用指针</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">bool</span> b1 <span class="token operator">=</span> <span class="token function">pf</span><span class="token punctuation">(</span><span class="token string">"hello"</span><span class="token punctuation">,</span> <span class="token string">"goodbye"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 调用 lengthCompare</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">bool</span> b2 <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token operator">*</span>pf<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token string">"hello"</span><span class="token punctuation">,</span> <span class="token string">"goodbye"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 等价的调用</span></pre></td></tr><tr><td data-num="3"></td><td><pre><span class="token keyword">bool</span> b3 <span class="token operator">=</span> <span class="token function">lengthCompare</span><span class="token punctuation">(</span><span class="token string">"hello"</span><span class="token punctuation">,</span> <span class="token string">"goodbye"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 等价的调用</span></pre></td></tr></tbody></table></figure><p>指向不同函数类型的指针是不可以转换的,但是,我们可以为函数指针赋一个 nullptr 或者值为 0 的整型常量表达式,表示该指针没有指向任何一个函数</p>
<blockquote>
<p>There is no conversion between pointers to one function type and pointers to another function type. However, as usual, we can assign nullptr or a zero-valued integer constant expression to a function pointer to indicate that the pointer does not point to any function.</p>
</blockquote>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">bool</span> <span class="token punctuation">(</span><span class="token operator">*</span>pf<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">const</span> string <span class="token operator">&</span><span class="token punctuation">,</span> <span class="token keyword">const</span> string <span class="token operator">&</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="2"></td><td><pre>string<span class="token double-colon punctuation">::</span>size_type <span class="token function">sumLength</span><span class="token punctuation">(</span><span class="token keyword">const</span> string<span class="token operator">&</span><span class="token punctuation">,</span> <span class="token keyword">const</span> string<span class="token operator">&</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="3"></td><td><pre><span class="token keyword">bool</span> <span class="token function">cstringCompare</span><span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token keyword">char</span><span class="token operator">*</span><span class="token punctuation">,</span> <span class="token keyword">const</span> <span class="token keyword">char</span><span class="token operator">*</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="4"></td><td><pre></pre></td></tr><tr><td data-num="5"></td><td><pre>pf <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token comment">// 正确:pf 不指向任何函数</span></pre></td></tr><tr><td data-num="6"></td><td><pre>pf <span class="token operator">=</span> sumLength<span class="token punctuation">;</span> <span class="token comment">// 错误:返回类型不匹配</span></pre></td></tr><tr><td data-num="7"></td><td><pre></pre></td></tr><tr><td data-num="8"></td><td><pre>pf <span class="token operator">=</span> cstringCompare<span class="token punctuation">;</span> <span class="token comment">// 错误:形参类型不匹配</span></pre></td></tr><tr><td data-num="9"></td><td><pre>pf <span class="token operator">=</span> lengthCompare<span class="token punctuation">;</span> <span class="token comment">// 正确:函数和指针的类型精确匹配</span></pre></td></tr></tbody></table></figure><h2 id="重载函数的指针"><a class="anchor" href="#重载函数的指针">#</a> 重载函数的指针</h2>
<p>如果定义了指向重载函数的指针,编译器通过指针类型决定选用哪个函数,指针类型必须与重载函数中的某一个精确匹配</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">void</span> <span class="token function">ff</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token operator">*</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">void</span> <span class="token function">ff</span><span class="token punctuation">(</span><span class="token keyword">unsigned</span> <span class="token keyword">int</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="3"></td><td><pre></pre></td></tr><tr><td data-num="4"></td><td><pre><span class="token keyword">void</span> <span class="token punctuation">(</span><span class="token operator">*</span>pf1<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">unsigned</span> <span class="token keyword">int</span><span class="token punctuation">)</span> <span class="token operator">=</span> ff<span class="token punctuation">;</span> <span class="token comment">//pf1 指向 ff (unsigned)</span></pre></td></tr><tr><td data-num="5"></td><td><pre></pre></td></tr><tr><td data-num="6"></td><td><pre><span class="token keyword">void</span> <span class="token punctuation">(</span><span class="token operator">*</span>pf2<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span> <span class="token operator">=</span> ff<span class="token punctuation">;</span> <span class="token comment">// 错误:没有任何一个 ff 与该形参列表匹配</span></pre></td></tr><tr><td data-num="7"></td><td><pre><span class="token keyword">double</span> <span class="token punctuation">(</span><span class="token operator">*</span>pf3<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token operator">*</span><span class="token punctuation">)</span> <span class="token operator">=</span> ff<span class="token punctuation">;</span> <span class="token comment">// 错误:ff 和 pf3 的返回类型不匹配</span></pre></td></tr></tbody></table></figure><h2 id="函数指针形参"><a class="anchor" href="#函数指针形参">#</a> 函数指针形参</h2>
<p>类似于数组,我们不能定义函数类型的形参,但是,<strong>可以将指向函数的指针作为形参</strong></p>
<p>我们可以定义一个类似于函数类型的形参,它会自动地转换成指向函数的指针(类似于 <strong>数组形参</strong> )</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">// 第三个形参是函数类型,它会自动地转换成指向函数的指针</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">void</span> <span class="token function">useBigger</span><span class="token punctuation">(</span><span class="token keyword">const</span> string <span class="token operator">&</span>s1<span class="token punctuation">,</span> <span class="token keyword">const</span> string <span class="token operator">&</span>s2<span class="token punctuation">,</span></pre></td></tr><tr><td data-num="3"></td><td><pre> <span class="token keyword">bool</span> <span class="token function">pf</span><span class="token punctuation">(</span><span class="token keyword">const</span> string <span class="token operator">&</span><span class="token punctuation">,</span> <span class="token keyword">const</span> string <span class="token operator">&</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="4"></td><td><pre></pre></td></tr><tr><td data-num="5"></td><td><pre><span class="token comment">// 等价的声明:显式地将形参定义成指向函数的指针</span></pre></td></tr><tr><td data-num="6"></td><td><pre><span class="token keyword">void</span> <span class="token function">useBigger</span><span class="token punctuation">(</span><span class="token keyword">const</span> string <span class="token operator">&</span>s1<span class="token punctuation">,</span> <span class="token keyword">const</span> string <span class="token operator">&</span>s2<span class="token punctuation">,</span></pre></td></tr><tr><td data-num="7"></td><td><pre> <span class="token keyword">bool</span> <span class="token punctuation">(</span><span class="token operator">*</span>pf<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">const</span> string <span class="token operator">&</span><span class="token punctuation">,</span> <span class="token keyword">const</span> string <span class="token operator">&</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr></tbody></table></figure><p>如果我们直接将函数作为实参使用,它会自动转换成指针</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">// 自动将函数 lengthCompare 转换成指向该函数的指针</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token function">useBigger</span><span class="token punctuation">(</span>s1<span class="token punctuation">,</span> s2<span class="token punctuation">,</span> lengthCompare<span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr></tbody></table></figure><p>由于直接将函数指针类型作为形参(例如,useBigger 的声明语句中的第三个形参)会显得冗长,可以使用 类型别名 和 decltype 来简化函数指针的代码</p>
<p>例如:首先使用 typedef 和 decltype 来定义自己的函数类型和函数指针类型</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">// Func 和 Func2 是函数类型</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">typedef</span> <span class="token keyword">bool</span> <span class="token function">Func</span><span class="token punctuation">(</span><span class="token keyword">const</span> string<span class="token operator">&</span><span class="token punctuation">,</span> <span class="token keyword">const</span> string<span class="token operator">&</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 函数类型</span></pre></td></tr><tr><td data-num="3"></td><td><pre><span class="token keyword">typedef</span> <span class="token keyword">decltype</span><span class="token punctuation">(</span>lengthCompare<span class="token punctuation">)</span> Func2<span class="token punctuation">;</span> <span class="token comment">// 函数类型的等价声明</span></pre></td></tr><tr><td data-num="4"></td><td><pre></pre></td></tr><tr><td data-num="5"></td><td><pre><span class="token comment">// FuncP 和 FuncP2 是指向函数的指针</span></pre></td></tr><tr><td data-num="6"></td><td><pre><span class="token keyword">typedef</span> <span class="token keyword">bool</span> <span class="token punctuation">(</span><span class="token operator">*</span>FuncP<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">const</span> string <span class="token operator">&</span><span class="token punctuation">,</span> <span class="token keyword">const</span> string<span class="token operator">&</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 函数指针类型</span></pre></td></tr><tr><td data-num="7"></td><td><pre><span class="token keyword">typedef</span> <span class="token keyword">decltype</span><span class="token punctuation">(</span>lengthCompare<span class="token punctuation">)</span> <span class="token operator">*</span>FuncP2<span class="token punctuation">;</span> <span class="token comment">// 函数指针类型的等价声明</span></pre></td></tr></tbody></table></figure><blockquote>
<p>注意:decltype 返回的是函数类型,不会将函数类型自动转换成指针类型,所以需要再加上 <code>*</code> 才能得到指针</p>
</blockquote>
<p>然后重新声明 useBigger 函数:</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">//useBigger 的等价声明,其中使用了类型别名</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">void</span> <span class="token function">useBigger</span><span class="token punctuation">(</span><span class="token keyword">const</span> string<span class="token operator">&</span><span class="token punctuation">,</span> <span class="token keyword">const</span> string<span class="token operator">&</span><span class="token punctuation">,</span> Func<span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="3"></td><td><pre><span class="token keyword">void</span> <span class="token function">useBigger</span><span class="token punctuation">(</span><span class="token keyword">const</span> string<span class="token operator">&</span><span class="token punctuation">,</span> <span class="token keyword">const</span> string<span class="token operator">&</span><span class="token punctuation">,</span> FuncP2<span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr></tbody></table></figure><p>这两个声明语句声明的是同一个函数,在第一条语句中,编译器自动地将 Func 表示的函数类型转换成指针</p>
<h2 id="返回指向函数的指针"><a class="anchor" href="#返回指向函数的指针">#</a> 返回指向函数的指针</h2>
<p>类似于数组,我们不能返回一个函数,但是,<strong>可以返回指向函数类型的指针</strong></p>
<p>由于编译器不会自动将函数返回类型当成对应的指针类型处理,我们必须显式地将返回类型写成指针形式</p>
<p>声明一个返回函数指针的函数,最直接的办法是(类似于 <strong>返回数组指针</strong> ):</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span><span class="token function">f1</span> <span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token keyword">int</span><span class="token operator">*</span> <span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr></tbody></table></figure><p>按照由内向外的顺序阅读这条声明语句:</p>
<ul>
<li>f1 有形参列表,所以 f1 是一个函数</li>
<li>f1 前面有 * ,所以 f1 返回的是一个指针</li>
<li>进一步观察发现,指针所指向的类型为 int (int*, int) ,其包含形参列表,因此,该指针指向的是一个返回类型为 int 的函数</li>
</ul>
<p>声明一个返回函数指针的函数,最简单的办法是使用类型别名:</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">using</span> F <span class="token operator">=</span> <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token operator">*</span><span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// F 是函数类型</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">using</span> PF <span class="token operator">=</span> <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token operator">*</span><span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token operator">*</span><span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// PF 是函数指针类型</span></pre></td></tr></tbody></table></figure><p>必须时刻注意的是,和函数类型的形参不一样,返回类型不会自动地转换成指针,因此,我们必须显式地将返回类型指定为指针</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre>PF <span class="token function">f1</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 正确:PF 是指向函数的指针,f1 返回指向函数的指针</span></pre></td></tr><tr><td data-num="2"></td><td><pre>F <span class="token function">f1</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 错误:F 是函数类型,f1 不能返回一个函数</span></pre></td></tr><tr><td data-num="3"></td><td><pre>F <span class="token operator">*</span><span class="token function">f1</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 正确:将函数返回类型显式地指定为指向函数的指针</span></pre></td></tr></tbody></table></figure><p>此外,我们也可以使用尾置返回类型的方式来声明一个返回函数指针的函数(可参考 <strong>使用尾置返回类型</strong> ):</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token keyword">auto</span> <span class="token function">f1</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span><span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token operator">*</span><span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr></tbody></table></figure><h2 id="将-auto-和-decltype-用于函数指针类型"><a class="anchor" href="#将-auto-和-decltype-用于函数指针类型">#</a> 将 auto 和 decltype 用于函数指针类型</h2>
<p>如果我们明确知道返回的函数是哪一个,可以使用 decltype 关键字将返回类型声明为 函数指针类型</p>
<p>例如,假定有两个函数,它们的返回类型都是 string::size_type ,并且各有两个 const string& 类型的形参</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre>string<span class="token double-colon punctuation">::</span>size_type <span class="token function">sumLength</span><span class="token punctuation">(</span><span class="token keyword">const</span> string<span class="token operator">&</span><span class="token punctuation">,</span> <span class="token keyword">const</span> string<span class="token operator">&</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr><tr><td data-num="2"></td><td><pre>string<span class="token double-colon punctuation">::</span>size_type <span class="token function">largerLength</span><span class="token punctuation">(</span><span class="token keyword">const</span> string<span class="token operator">&</span><span class="token punctuation">,</span> <span class="token keyword">const</span> string<span class="token operator">&</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr></tbody></table></figure><p>此时,我们可以编写第三个函数,它接受一个 string 类型的参数,返回一个指针,该指针指向前两个函数中的某一个</p>
<figure class="highlight cpp"><figcaption data-lang="C++"></figcaption><table><tbody><tr><td data-num="1"></td><td><pre><span class="token comment">// 根据其形参的取值,getFcn 函数返回指向 sumLength 或者 largerLength 的指针</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">decltype</span><span class="token punctuation">(</span>sumLength<span class="token punctuation">)</span> <span class="token operator">*</span><span class="token function">getFcn</span><span class="token punctuation">(</span><span class="token keyword">const</span> string <span class="token operator">&</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></td></tr></tbody></table></figure><p>注意:<strong> <code>decltype</code> 并不负责把函数类型转换成对应的指针</strong>,所以 decltype 的结果是个函数类型,因此,我们显式地加上 <code>*</code> 以表明我们需要返回指针而不是函数本身</p>
<h1 id="术语表"><a class="anchor" href="#术语表">#</a> 术语表</h1>
<p><strong>二义性调用(ambiguous call)</strong> :是一种编译时发生的错误。造成二义性调用的原因:在函数匹配时,两个或多个函数提供的匹配一样好,编译器找不到唯一的最佳匹配</p>
<p><strong>实参(argument)</strong> :函数调用时提供的值,用于初始化函数形参</p>
<p><strong>Assert</strong> :是一个预处理宏,作用于一条表示条件的表达式</p>
<ul>
<li>当未定义预处理变量 NDEBUG 时,assert 对条件求值</li>
<li>如果 assert 的条件为假,输出一条错误信息并终止当前程序的执行</li>
</ul>
<p><strong>自动对象(automatic object)</strong> :仅存在于函数执行过程中的对象</p>
<ul>
<li>当程序的控制流经过此类对象的定义语句时,创建该对象</li>
<li>当到达了定义所在的块的末尾时,销毁该对象</li>
</ul>
<p><strong>最佳匹配(best match)</strong> :从一组重载函数中为调用选出的一个函数。最佳匹配(如果存在的话)至少在一个实参上比其他所有可行函数更优,同时,在其他实参的匹配上不会更差</p>
<p><strong>传引用调用(call by reference)</strong> :对引用传递的函数的调用</p>
<p><strong>传值调用(call by value)</strong> :对值传递的函数的调用</p>
<p><strong>候选函数(candidate function)</strong> :解析某次函数调用时考虑的一组函数。候选函数的名字应该与函数调用使用的名字一致,并且在调用点候选函数的声明在作用域之内</p>
<p><strong>constexpr</strong> :可以返回常量表达式的函数。一个 constexpr 函数被隐式地声明成内联函数</p>
<p><strong>默认实参(default argument)</strong> :当调用缺少了某个实参时,为该实参指定的默认值</p>
<p><strong>可执行文件(executable file)</strong> ;是操作系统能够执行的文件,包含着与程序有关的代码</p>
<p><strong>函数(function)</strong> :可调用的计算单元</p>
<p><strong>函数体(function body)</strong> :是一个块,用于定义函数所执行的操作</p>
<p><strong>函数匹配(function matching)</strong> :编译器解析重载函数调用的过程,在此过程中,实参与每个重载函数的形参列表逐一比较</p>
<p><strong>函数原型(function prototype)</strong> :函数的声明,包含函数名字、返回类型和形参类型。要想调用某函数,在调用点之前必须声明该函数的原型</p>
<p><strong>隐藏名字(hidden name)</strong> :某个作用域内声明的名字会隐藏掉外层作用域中声明的同名实体</p>
<p><strong>initializer_list</strong> :是一个标准类,表示的是一组花括号包围的类型相同的对象,对象之间以逗号隔开</p>
<p><strong>内联函数(inline function)</strong> :请求编译器在可能的情况下在调用点展开函数。内联函数可以避免常见的函数调用开销</p>
<p><strong>链接(link)</strong> :是一个编译过程,负责把若干对象文件链接起来形成可执行程序</p>
<p><strong>局部静态对象(local static object)</strong> :它的值在函数调用结束后仍然存在。在第一次使用局部静态对象前创建并初始化它,当程序结束时局部静态对象才被销毁</p>
<p><strong>局部变量(local variable)</strong> :定义在块中的变量</p>
<p><strong>无匹配(no match)</strong> :是一种编译时发生的错误,原因是在函数匹配过程中所有函数的形参都不能与调用提供的实参匹配</p>
<p><strong>对象代码(object code)</strong> :编译器将我们的源代码转换成对象代码格式</p>
<p><strong>对象文件(object file)</strong> :编译器根据给定的源文件生成的保存对象代码的文件。一个或多个对象文件经过链接生成可执行文件</p>
<p><strong>对象生命周期(object lifetime)</strong> :每个对象都有相应的生命周期</p>
<ul>
<li>块内定义的非静态对象的生命周期从它的定义开始,到定义所在的块末尾为止</li>
<li>程序启动后创建全局对象</li>
<li>程序控制流经过局部静态对象的定义时创建该局部静态对象</li>
<li>当 main 函数结束时销毁全局对象和局部静态对象</li>
</ul>
<p><strong>重载确定(overload resolution)</strong> :参见函数匹配</p>
<p><strong>重载函数(overloaded function)</strong> :函数名与其他函数相同的函数。多个重载函数必须在形参数量或形参类型上有所区别</p>
<p><strong>形参(parameter)</strong> :在函数的形参列表中声明的局部变量。用实参初始化形参</p>
<p><strong>引用传递(pass by reference)</strong> :描述如何将实参传递给引用类型的形参。引用形参和其他形式的引用工作机理类似,形参被绑定到相应的实参上</p>
<p><strong>值传递(pass by value)</strong> :描述如何将实参传递给非引用类型的形参。非引用类型的形参实际上是相应实参值的一个副本</p>
<p><strong>预处理宏(preprocessor macro)</strong> :类似于内联函数的一种预处理功能。除了 assert 之外,现代 C++ 程序很少再使用预处理宏了</p>
<p><strong>递归循环(recursion loop)</strong> :描述某个递归函数没有终止条件,因而不断调用自身直至耗尽程序栈空间的过程</p>
<p><strong>递归函数(recursive function)</strong> :直接或间接调用自身的函数</p>
<p><strong>返回类型(return type)</strong> :是函数声明的一部分,用于指定函数返回值的类型</p>
<p><strong>分离式编译(separate compilation)</strong> :把一个程序分割成多个独立源文件的能力</p>
<p><strong>尾置返回类型(trailing return type)</strong> :在参数列表后面指定的返回类型</p>
<p><strong>可行函数(viable function)</strong> :是候选函数的子集。可行函数能匹配本次调用,它的形参数量与调用提供的实参数量相等,并且每个实参类型都能转换成相应的形参类型</p>
<p><strong>() 运算符( () operator)</strong> :调用运算符,用于执行某函数。括号前面是函数名或函数指针,括号内是以逗号隔开的实参列表(可能为空)</p>
<p>参考:C++ Primer 中文版(第 5 版)</p>
</div><footer><div class="meta"><span class="item"><span class="icon"><i class="ic i-calendar-check"></i></span><span class="text">更新于</span><time title="修改时间:2024-06-08 23:08:02" itemprop="dateModified" datetime="2024-06-08T23:08:02+08:00">2024-06-08</time></span></div><div id="copyright"><ul><li class="author"><strong>本文作者:</strong>Jiankychen<i class="ic i-at"><em>@</em></i>Jiankychen's Blog</li><li class="link"><strong>本文链接:</strong><a href="https://jiankychen.github.io/cpp-functions.html" title="C++ 函数">https://jiankychen.github.io/cpp-functions.html</a></li><li class="license"><strong>版权声明:</strong>本站所有文章除特别声明外,均采用 <a target="_blank" rel="noopener" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh"><i class="ic i-creative-commons"><em>(CC)</em></i>BY-NC-SA</a> 许可协议。转载请注明出处!</li></ul></div></footer></article></div><div class="post-nav"><div class="item left"><a href="/cpp-statement.html" rel="prev" itemprop="url" data-background-image="https://i.imgtg.com/2023/03/09/Y0hOs.jpg" title="C++ 语句"><span class="type">上一篇</span><span class="category"><i class="ic i-flag"></i>C++</span><h3>C++ 语句</h3></a></div><div class="item right"><a href="/Dijkstra.html" rel="next" itemprop="url" data-background-image="https://i.imgtg.com/2023/03/09/YQSYM.jpg" title="Dijkstra"><span class="type">下一篇</span><span class="category"><i class="ic i-flag"></i>Data Structure</span><h3>Dijkstra</h3></a></div></div><div class="wrap" id="comments"></div></div><div id="sidebar"><div class="inner"><div class="panels"><div class="inner"><div class="contents panel pjax" data-title="文章目录"><ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#%E5%87%BD%E6%95%B0%E5%9F%BA%E7%A1%80"><span class="toc-number">1.</span> <span class="toc-text"> 函数基础</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E8%B0%83%E7%94%A8%E5%87%BD%E6%95%B0"><span class="toc-number">1.1.</span> <span class="toc-text"> 调用函数</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%BD%A2%E5%8F%82%E5%92%8C%E5%AE%9E%E5%8F%82"><span class="toc-number">1.2.</span> <span class="toc-text"> 形参和实参</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%87%BD%E6%95%B0%E7%9A%84%E5%BD%A2%E5%8F%82%E5%88%97%E8%A1%A8"><span class="toc-number">1.3.</span> <span class="toc-text"> 函数的形参列表</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%87%BD%E6%95%B0%E8%BF%94%E5%9B%9E%E7%B1%BB%E5%9E%8B"><span class="toc-number">1.4.</span> <span class="toc-text"> 函数返回类型</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%B1%80%E9%83%A8%E5%AF%B9%E8%B1%A1"><span class="toc-number">1.5.</span> <span class="toc-text"> 局部对象</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E8%87%AA%E5%8A%A8%E5%AF%B9%E8%B1%A1"><span class="toc-number">1.6.</span> <span class="toc-text"> 自动对象</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%B1%80%E9%83%A8%E9%9D%99%E6%80%81%E5%AF%B9%E8%B1%A1"><span class="toc-number">1.7.</span> <span class="toc-text"> 局部静态对象</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%87%BD%E6%95%B0%E5%A3%B0%E6%98%8E"><span class="toc-number">1.8.</span> <span class="toc-text"> 函数声明</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%9C%A8%E5%A4%B4%E6%96%87%E4%BB%B6%E4%B8%AD%E8%BF%9B%E8%A1%8C%E5%87%BD%E6%95%B0%E5%A3%B0%E6%98%8E"><span class="toc-number">1.8.1.</span> <span class="toc-text"> 在头文件中进行函数声明</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%88%86%E7%A6%BB%E5%BC%8F%E7%BC%96%E8%AF%91"><span class="toc-number">1.8.2.</span> <span class="toc-text"> 分离式编译</span></a></li></ol></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E5%8F%82%E6%95%B0%E4%BC%A0%E9%80%92"><span class="toc-number">2.</span> <span class="toc-text"> 参数传递</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%BC%A0%E5%80%BC%E5%8F%82%E6%95%B0"><span class="toc-number">2.1.</span> <span class="toc-text"> 传值参数</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%8C%87%E9%92%88%E5%BD%A2%E5%8F%82"><span class="toc-number">2.1.1.</span> <span class="toc-text"> 指针形参</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%BC%A0%E5%BC%95%E7%94%A8%E5%8F%82%E6%95%B0"><span class="toc-number">2.2.</span> <span class="toc-text"> 传引用参数</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8%E5%BC%95%E7%94%A8%E9%81%BF%E5%85%8D%E6%8B%B7%E8%B4%9D"><span class="toc-number">2.2.1.</span> <span class="toc-text"> 使用引用避免拷贝</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8%E5%BC%95%E7%94%A8%E5%BD%A2%E5%8F%82%E8%BF%94%E5%9B%9E%E9%A2%9D%E5%A4%96%E4%BF%A1%E6%81%AF"><span class="toc-number">2.2.2.</span> <span class="toc-text"> 使用引用形参返回额外信息</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#const-%E5%BD%A2%E5%8F%82%E5%92%8C%E5%AE%9E%E5%8F%82"><span class="toc-number">2.3.</span> <span class="toc-text"> const 形参和实参</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#const-%E4%B8%8E-%E6%8C%87%E9%92%88%E6%88%96%E5%BC%95%E7%94%A8%E5%BD%A2%E5%8F%82"><span class="toc-number">2.3.1.</span> <span class="toc-text"> const 与 指针或引用形参</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%B0%BD%E9%87%8F%E4%BD%BF%E7%94%A8%E5%B8%B8%E9%87%8F%E5%BC%95%E7%94%A8"><span class="toc-number">2.3.2.</span> <span class="toc-text"> 尽量使用常量引用</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%95%B0%E7%BB%84%E5%BD%A2%E5%8F%82"><span class="toc-number">2.4.</span> <span class="toc-text"> 数组形参</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%95%B0%E7%BB%84%E6%8C%87%E9%92%88%E5%BD%A2%E5%8F%82"><span class="toc-number">2.4.1.</span> <span class="toc-text"> 数组指针形参</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8%E6%A0%87%E8%AE%B0%E6%8C%87%E5%AE%9A%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6"><span class="toc-number">2.4.1.1.</span> <span class="toc-text"> 使用标记指定数组长度</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8%E6%A0%87%E5%87%86%E5%BA%93%E8%A7%84%E8%8C%83"><span class="toc-number">2.4.1.2.</span> <span class="toc-text"> 使用标准库规范</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%98%BE%E5%BC%8F%E4%BC%A0%E9%80%92%E4%B8%80%E4%B8%AA%E8%A1%A8%E7%A4%BA%E6%95%B0%E7%BB%84%E5%A4%A7%E5%B0%8F%E7%9A%84%E5%BD%A2%E5%8F%82"><span class="toc-number">2.4.1.3.</span> <span class="toc-text"> 显式传递一个表示数组大小的形参</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%95%B0%E7%BB%84%E5%BC%95%E7%94%A8%E5%BD%A2%E5%8F%82"><span class="toc-number">2.4.2.</span> <span class="toc-text"> 数组引用形参</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BC%A0%E9%80%92%E5%A4%9A%E7%BB%B4%E6%95%B0%E7%BB%84"><span class="toc-number">2.4.3.</span> <span class="toc-text"> 传递多维数组</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#main-%E5%A4%84%E7%90%86%E5%91%BD%E4%BB%A4%E8%A1%8C%E9%80%89%E9%A1%B9"><span class="toc-number">2.5.</span> <span class="toc-text"> main: 处理命令行选项</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%90%AB%E6%9C%89%E5%8F%AF%E5%8F%98%E5%BD%A2%E5%8F%82%E7%9A%84%E5%87%BD%E6%95%B0"><span class="toc-number">2.6.</span> <span class="toc-text"> 含有可变形参的函数</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#initializer_list-%E5%BD%A2%E5%8F%82"><span class="toc-number">2.6.1.</span> <span class="toc-text"> initializer_list 形参</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E7%9C%81%E7%95%A5%E7%AC%A6%E5%BD%A2%E5%8F%82"><span class="toc-number">2.6.2.</span> <span class="toc-text"> 省略符形参</span></a></li></ol></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E8%BF%94%E5%9B%9E%E7%B1%BB%E5%9E%8B%E5%92%8C-return-%E8%AF%AD%E5%8F%A5"><span class="toc-number">3.</span> <span class="toc-text"> 返回类型和 return 语句</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%97%A0%E8%BF%94%E5%9B%9E%E5%80%BC%E5%87%BD%E6%95%B0"><span class="toc-number">3.1.</span> <span class="toc-text"> 无返回值函数</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%9C%89%E8%BF%94%E5%9B%9E%E5%80%BC%E5%87%BD%E6%95%B0"><span class="toc-number">3.2.</span> <span class="toc-text"> 有返回值函数</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%80%BC%E6%98%AF%E5%A6%82%E4%BD%95%E8%A2%AB%E8%BF%94%E5%9B%9E%E7%9A%84"><span class="toc-number">3.2.1.</span> <span class="toc-text"> 值是如何被返回的</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%B8%8D%E8%A6%81%E8%BF%94%E5%9B%9E%E5%B1%80%E9%83%A8%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%BC%95%E7%94%A8%E6%88%96%E6%8C%87%E9%92%88"><span class="toc-number">3.2.2.</span> <span class="toc-text"> 不要返回局部对象的引用或指针</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%BF%94%E5%9B%9E%E7%B1%BB%E7%B1%BB%E5%9E%8B%E7%9A%84%E5%87%BD%E6%95%B0%E5%92%8C%E8%B0%83%E7%94%A8%E8%BF%90%E7%AE%97%E7%AC%A6"><span class="toc-number">3.2.3.</span> <span class="toc-text"> 返回类类型的函数和调用运算符</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%BC%95%E7%94%A8%E8%BF%94%E5%9B%9E%E5%B7%A6%E5%80%BC"><span class="toc-number">3.2.4.</span> <span class="toc-text"> 引用返回左值</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%88%97%E8%A1%A8%E5%88%9D%E5%A7%8B%E5%8C%96%E8%BF%94%E5%9B%9E%E5%80%BC"><span class="toc-number">3.2.5.</span> <span class="toc-text"> 列表初始化返回值</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%B8%BB%E5%87%BD%E6%95%B0-main-%E7%9A%84%E8%BF%94%E5%9B%9E%E5%80%BC"><span class="toc-number">3.2.6.</span> <span class="toc-text"> 主函数 main 的返回值</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E9%80%92%E5%BD%92"><span class="toc-number">3.2.7.</span> <span class="toc-text"> 递归</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E8%BF%94%E5%9B%9E%E6%95%B0%E7%BB%84%E6%8C%87%E9%92%88"><span class="toc-number">3.3.</span> <span class="toc-text"> 返回数组指针</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%A3%B0%E6%98%8E%E4%B8%80%E4%B8%AA%E8%BF%94%E5%9B%9E%E6%95%B0%E7%BB%84%E6%8C%87%E9%92%88%E7%9A%84%E5%87%BD%E6%95%B0"><span class="toc-number">3.3.1.</span> <span class="toc-text"> 声明一个返回数组指针的函数</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8%E5%B0%BE%E7%BD%AE%E8%BF%94%E5%9B%9E%E7%B1%BB%E5%9E%8B"><span class="toc-number">3.3.2.</span> <span class="toc-text"> 使用尾置返回类型</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8-decltype"><span class="toc-number">3.3.3.</span> <span class="toc-text"> 使用 decltype</span></a></li></ol></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E5%87%BD%E6%95%B0%E9%87%8D%E8%BD%BD"><span class="toc-number">4.</span> <span class="toc-text"> 函数重载</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%87%8D%E8%BD%BD%E5%87%BD%E6%95%B0%E7%9A%84%E5%AE%9A%E4%B9%89%E4%B8%8E%E8%B0%83%E7%94%A8"><span class="toc-number">4.1.</span> <span class="toc-text"> 重载函数的定义与调用</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%AE%9A%E4%B9%89%E9%87%8D%E8%BD%BD%E5%87%BD%E6%95%B0"><span class="toc-number">4.1.1.</span> <span class="toc-text"> 定义重载函数</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%88%A4%E6%96%AD%E4%B8%A4%E4%B8%AA%E5%BD%A2%E5%8F%82%E7%9A%84%E7%B1%BB%E5%9E%8B%E6%98%AF%E5%90%A6%E7%9B%B8%E5%BC%82"><span class="toc-number">4.1.2.</span> <span class="toc-text"> 判断两个形参的类型是否相异</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%BB%BA%E8%AE%AE%E4%BD%95%E6%97%B6%E4%B8%8D%E5%BA%94%E8%AF%A5%E9%87%8D%E8%BD%BD%E5%87%BD%E6%95%B0"><span class="toc-number">4.1.3.</span> <span class="toc-text"> 建议:何时不应该重载函数</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#const_cast-%E5%92%8C%E9%87%8D%E8%BD%BD"><span class="toc-number">4.1.4.</span> <span class="toc-text"> const_cast 和重载</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%B0%83%E7%94%A8%E9%87%8D%E8%BD%BD%E7%9A%84%E5%87%BD%E6%95%B0"><span class="toc-number">4.1.5.</span> <span class="toc-text"> 调用重载的函数</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%87%8D%E8%BD%BD%E4%B8%8E%E4%BD%9C%E7%94%A8%E5%9F%9F"><span class="toc-number">4.2.</span> <span class="toc-text"> 重载与作用域</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E7%89%B9%E6%AE%8A%E7%94%A8%E9%80%94%E8%AF%AD%E8%A8%80%E7%89%B9%E6%80%A7"><span class="toc-number">5.</span> <span class="toc-text"> 特殊用途语言特性</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%BB%98%E8%AE%A4%E5%AE%9E%E5%8F%82"><span class="toc-number">5.1.</span> <span class="toc-text"> 默认实参</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8%E9%BB%98%E8%AE%A4%E5%AE%9E%E5%8F%82%E8%B0%83%E7%94%A8%E5%87%BD%E6%95%B0"><span class="toc-number">5.1.1.</span> <span class="toc-text"> 使用默认实参调用函数</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E9%BB%98%E8%AE%A4%E5%AE%9E%E5%8F%82%E5%A3%B0%E6%98%8E"><span class="toc-number">5.1.2.</span> <span class="toc-text"> 默认实参声明</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E9%BB%98%E8%AE%A4%E5%AE%9E%E5%8F%82%E5%88%9D%E5%A7%8B%E5%80%BC"><span class="toc-number">5.1.3.</span> <span class="toc-text"> 默认实参初始值</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%86%85%E8%81%94%E5%87%BD%E6%95%B0%E5%92%8C-constexpr-%E5%87%BD%E6%95%B0"><span class="toc-number">5.2.</span> <span class="toc-text"> 内联函数和 constexpr 函数</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%86%85%E8%81%94%E5%87%BD%E6%95%B0%E5%8F%AF%E4%BB%A5%E9%81%BF%E5%85%8D%E5%87%BD%E6%95%B0%E8%B0%83%E7%94%A8%E7%9A%84%E5%BC%80%E9%94%80"><span class="toc-number">5.2.1.</span> <span class="toc-text"> 内联函数可以避免函数调用的开销</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#constexpr-%E5%87%BD%E6%95%B0"><span class="toc-number">5.2.2.</span> <span class="toc-text"> constexpr 函数</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%8A%8A%E5%86%85%E8%81%94%E5%87%BD%E6%95%B0%E5%92%8C-constexpr-%E5%87%BD%E6%95%B0%E6%94%BE%E5%9C%A8%E5%A4%B4%E6%96%87%E4%BB%B6%E5%86%85"><span class="toc-number">5.2.3.</span> <span class="toc-text"> 把内联函数和 constexpr 函数放在头文件内</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E8%B0%83%E8%AF%95%E5%B8%AE%E5%8A%A9"><span class="toc-number">5.3.</span> <span class="toc-text"> 调试帮助</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#assert-%E9%A2%84%E5%A4%84%E7%90%86%E5%AE%8F"><span class="toc-number">5.3.1.</span> <span class="toc-text"> assert 预处理宏</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#ndebug-%E9%A2%84%E5%A4%84%E7%90%86%E5%8F%98%E9%87%8F"><span class="toc-number">5.3.2.</span> <span class="toc-text"> NDEBUG 预处理变量</span></a></li></ol></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E5%87%BD%E6%95%B0%E5%8C%B9%E9%85%8D"><span class="toc-number">6.</span> <span class="toc-text"> 函数匹配</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%87%BD%E6%95%B0%E5%8C%B9%E9%85%8D-2"><span class="toc-number">6.1.</span> <span class="toc-text"> 函数匹配</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%87%BD%E6%95%B0%E5%8C%B9%E9%85%8D%E7%9A%84%E6%AD%A5%E9%AA%A4"><span class="toc-number">6.1.1.</span> <span class="toc-text"> 函数匹配的步骤</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%AE%9E%E4%BE%8B%E5%88%86%E6%9E%90"><span class="toc-number">6.1.2.</span> <span class="toc-text"> 实例分析</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%AE%9E%E5%8F%82%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2"><span class="toc-number">6.2.</span> <span class="toc-text"> 实参类型转换</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E9%9C%80%E8%A6%81%E7%B1%BB%E5%9E%8B%E6%8F%90%E5%8D%87%E5%92%8C%E7%AE%97%E6%9C%AF%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2%E7%9A%84%E5%8C%B9%E9%85%8D"><span class="toc-number">6.2.1.</span> <span class="toc-text"> 需要类型提升和算术类型转换的匹配</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%87%BD%E6%95%B0%E5%8C%B9%E9%85%8D%E5%92%8C-const-%E5%AE%9E%E5%8F%82"><span class="toc-number">6.2.2.</span> <span class="toc-text"> 函数匹配和 const 实参</span></a></li></ol></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E5%87%BD%E6%95%B0%E6%8C%87%E9%92%88"><span class="toc-number">7.</span> <span class="toc-text"> 函数指针</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8%E5%87%BD%E6%95%B0%E6%8C%87%E9%92%88"><span class="toc-number">7.1.</span> <span class="toc-text"> 使用函数指针</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%87%8D%E8%BD%BD%E5%87%BD%E6%95%B0%E7%9A%84%E6%8C%87%E9%92%88"><span class="toc-number">7.2.</span> <span class="toc-text"> 重载函数的指针</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%87%BD%E6%95%B0%E6%8C%87%E9%92%88%E5%BD%A2%E5%8F%82"><span class="toc-number">7.3.</span> <span class="toc-text"> 函数指针形参</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E8%BF%94%E5%9B%9E%E6%8C%87%E5%90%91%E5%87%BD%E6%95%B0%E7%9A%84%E6%8C%87%E9%92%88"><span class="toc-number">7.4.</span> <span class="toc-text"> 返回指向函数的指针</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%B0%86-auto-%E5%92%8C-decltype-%E7%94%A8%E4%BA%8E%E5%87%BD%E6%95%B0%E6%8C%87%E9%92%88%E7%B1%BB%E5%9E%8B"><span class="toc-number">7.5.</span> <span class="toc-text"> 将 auto 和 decltype 用于函数指针类型</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E6%9C%AF%E8%AF%AD%E8%A1%A8"><span class="toc-number">8.</span> <span class="toc-text"> 术语表</span></a></li></ol></div><div class="related panel pjax" data-title="系列文章"><ul><li><a href="/introduce-cpp.html" rel="bookmark" title="初识 C++">初识 C++</a></li><li><a href="/cpp-variables.html" rel="bookmark" title="C++ 变量和基本类型">C++ 变量和基本类型</a></li><li><a href="/cpp-vectors.html" rel="bookmark" title="C++ 字符串、向量和数组">C++ 字符串、向量和数组</a></li><li><a href="/cpp-experssions.html" rel="bookmark" title="C++ 表达式">C++ 表达式</a></li><li><a href="/cpp-statement.html" rel="bookmark" title="C++ 语句">C++ 语句</a></li><li class="active"><a href="/cpp-functions.html" rel="bookmark" title="C++ 函数">C++ 函数</a></li><li><a href="/cpp-classes.html" rel="bookmark" title="C++ 类">C++ 类</a></li></ul></div><div class="overview panel" data-title="站点概览"><div class="author" itemprop="author" itemscope="itemscope" itemtype="http://schema.org/Person"><img class="image" loading="lazy" decoding="async" itemprop="image" alt="Jiankychen" src="/assets/avatar.webp"><p class="name" itemprop="name">Jiankychen</p><div class="description" itemprop="description"></div></div><nav class="state"><div class="item posts"><a href="/archives/"><span class="count">51</span><span class="name">文章</span></a></div><div class="item categories"><a href="/categories/"><span class="count">8</span><span class="name">分类</span></a></div><div class="item tags"><a href="/tags/"><span class="count">20</span><span class="name">标签</span></a></div></nav><div class="social"><a target="_blank" rel="noopener" href="https://github.com/jiankychen" class="item github" title="https://github.com/jiankychen"><i class="ic i-github"></i></a><a href="mailto:[email protected]" class="item email" title="mailto:[email protected]"><i class="ic i-envelope"></i></a><a target="_blank" rel="noopener" href="https://music.163.com/#/user/home?id=447771275" class="item music" title="https://music.163.com/#/user/home?id=447771275"><i class="ic i-cloud-music"></i></a><a target="_blank" rel="noopener" href="https://www.zhihu.com/people/jiankychen" class="item zhihu" title="https://www.zhihu.com/people/jiankychen"><i class="ic i-zhihu"></i></a></div><div class="menu"><li class="item"><a href="/" rel="section"><i class="ic i-home"></i>首页</a></li><li class="item dropdown"><a href="#" onclick="return false;"><i class="ic i-feather"></i>文章</a><ul class="submenu"><li class="item"><a href="/archives/" rel="section"><i class="ic i-list-alt"></i>归档</a></li><li class="item"><a href="/categories/" rel="section"><i class="ic i-th"></i>分类</a></li><li class="item"><a href="/tags/" rel="section"><i class="ic i-tags"></i>标签</a></li></ul></li><li class="item dropdown"><a href="#" onclick="return false;"><i class="ic i-feather"></i>链接</a><ul class="submenu"><li class="item"><a href="/peers/" rel="section"><i class="ic i-magic"></i>链环</a></li><li class="item"><a href="/friends/" rel="section"><i class="ic i-heart"></i>友链</a></li></ul></li><li class="item dropdown"><a href="#" onclick="return false;"><i class="ic i-stars"></i>关于</a><ul class="submenu"><li class="item"><a href="/owner/" rel="section"><i class="ic i-user"></i>关于博主</a></li><li class="item"><a href="/site/" rel="section"><i class="ic i-paw"></i>关于本站</a></li><li class="item"><a href="/update/" rel="section"><i class="ic i-cloud"></i>更新日志</a></li></ul></li></div></div></div></div><ul id="quick"><li class="prev pjax"><a href="/Dijkstra.html" rel="prev" title="上一篇"><i class="ic i-chevron-left"></i></a></li><li class="up"><i class="ic i-arrow-up"></i></li><li class="down"><i class="ic i-arrow-down"></i></li><li class="next pjax"><a href="/cpp-statement.html" rel="next" title="下一篇"><i class="ic i-chevron-right"></i></a></li><li class="percent"></li></ul></div></div><div class="dimmer"></div></div></main><footer id="footer"><div class="inner"><div class="widgets"><div class="rpost pjax"><h2>随机文章</h2><ul><li class="item"><div class="breadcrumb"><a href="/categories/Data-Structure/" title="分类于Data Structure">Data Structure</a></div><span><a href="/sort-algorithm.html">排序</a></span></li><li class="item"><div class="breadcrumb"><a href="/categories/Tutorial/" title="分类于Tutorial">Tutorial</a></div><span><a href="/vscode-intro.html">vscode 使用</a></span></li><li class="item"><div class="breadcrumb"><a href="/categories/Coding/" title="分类于Coding">Coding</a></div><span><a href="/leetcode-binarysearch.html">LeetCode - 二分查找专题</a></span></li><li class="item"><div class="breadcrumb"><a href="/categories/Python/" title="分类于Python">Python</a></div><span><a href="/python-container.html">Python 数据容器</a></span></li><li class="item"><div class="breadcrumb"><a href="/categories/Coding/" title="分类于Coding">Coding</a></div><span><a href="/leetcode-hashtable.html">LeetCode - 哈希表专题</a></span></li><li class="item"><div class="breadcrumb"><a href="/categories/Coding/" title="分类于Coding">Coding</a></div><span><a href="/leetcode-traverse.html">LeetCode - 回溯专题</a></span></li><li class="item"><div class="breadcrumb"><a href="/categories/C/" title="分类于C++">C++</a></div><span><a href="/cpp-statement.html">C++ 语句</a></span></li><li class="item"><div class="breadcrumb"><a href="/categories/Python/" title="分类于Python">Python</a></div><span><a href="/python-pandas.html">pandas 基础</a></span></li><li class="item"><div class="breadcrumb"><a href="/categories/Python/" title="分类于Python">Python</a></div><span><a href="/python-modules.html">Python 异常、模块、包</a></span></li><li class="item"><div class="breadcrumb"><a href="/categories/Coding/" title="分类于Coding">Coding</a></div><span><a href="/leetcode-list.html">LeetCode - 链表专题</a></span></li></ul></div><div class="rpost pjax"><h2>最新评论</h2><ul class="leancloud-recent-comment" id="new-comment"></ul></div></div><div class="status"><div class="copyright">© 2021 -<span itemprop="copyrightYear">2024</span><span class="with-love"><i class="ic i-sakura rotate"></i></span><span class="author" itemprop="copyrightHolder">Jiankychen @ Jiankychen</span></div><div class="count"><span class="post-meta-item-icon"><i class="ic i-chart-area"></i></span><span title="站点总字数">955k 字</span><span class="post-meta-divider"> | </span><span class="post-meta-item-icon"><i class="ic i-coffee"></i></span><span title="站点阅读时长">14:28</span></div><div class="powered-by">基于 <a target="_blank" rel="noopener" href="https://hexo.io/">Hexo</a> & Theme.<a target="_blank" rel="noopener" href="https://github.com/theme-shoka-x/hexo-theme-shokaX/">ShokaX</a></div></div><script src="https://unpkg.com/[email protected]/bsz.pure.mini.js"></script><div id="busuanzi-wrap"><span class="ic i-eye"></span><span id="busuanzi_container_site_pv">本站总访问量 <span id="busuanzi_value_site_pv"></span> 次</span> | <span class="ic i-user"></span><span id="busuanzi_container_site_uv">本站总访客量 <span id="busuanzi_value_site_uv"></span> 次</span></div></div></footer></div><script data-config="" type="text/javascript">var LOCAL = {
ispost: true,
path: `/cpp-functions`,
favicon: {
show: `Jiankychen`,
hide: `Jiankychen`
},
search: {
placeholder: "文章搜索",
empty: "关于 「 ${query} 」,什么也没搜到",
stats: "${time} ms 内找到 ${hits} 条结果"
},
copy_tex: true,
katex: true,
mermaid: false,
audio: undefined,
fancybox: true,
nocopy: false,
outime: true,
template: `<div class="note warning"><p><span class="label warning">文章时效性提示</span><br>这是一篇发布于 {{publish}} 天前,最后一次更新在 {{updated}} 天前的文章,部分信息可能已经发生改变,请注意甄别。</p></div>`,
quiz: {
choice: `单选题`,
multiple: `多选题`,
true_false: `判断题`,
essay: `问答题`,
gap_fill: `填空题`,
mistake: `错题备注`
},
ignores: [
(uri) => uri.includes('#'),
(uri) => new RegExp(LOCAL.path + '$').test(uri),
[]
]
};
</script><script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-6-M/pace/1.2.4/pace.min.js" async=""></script><script src="https://polyfill.io/v3/polyfill.min.js?features=default,fetch" defer=""></script><script src="/js/siteInit.js?v=0.4.2" type="module" fetchpriority="high" defer=""></script></body></html>