第五章 函数
关键考点总览
跳转到:关键考点总览- 函数的定义格式(返回类型、函数名、形参表、函数体)
- 函数的声明与调用机制
- 参数传递:值传递机制
- 数组作为函数参数的特殊性(形参数组会调整为指针,传递的是首元素地址)
- 变量作用域:局部变量与全局变量
- 变量存储类别:auto、register、extern、static
- 重载函数的定义与匹配规则
- 函数模板的定义与使用
- 递归函数的设计(递归出口、递归调用)
- 默认参数与内联函数
学习目标(必考重点)
跳转到:学习目标(必考重点)以下内容来自学习目标页面,是本章考核重点
- 掌握函数的定义、声明、调用 [来源:第5章 p7,学习目标]
- 理解内存层面工作机制 [来源:第5章 p7,学习目标]
- 理解变量作用域 [来源:第5章 p7,学习目标]
- 会读代码,给出执行结果 [来源:第5章 p7,学习目标]
- 理解重载函数的使用 [来源:第5章 p7,学习目标]
- 会递归调用函数 [来源:第5章 p7,学习目标]
详细知识点
跳转到:详细知识点5.1 函数定义
跳转到:5.1 函数定义定义/语法
跳转到:定义/语法-
函数定义:完成某一特定功能的程序模块 [来源:第5章 p9]
-
函数定义格式:
返回类型 函数名(形式参数表) {变量定义部分语句部分}[来源:第5章 p10,函数定义格式]
-
return语句格式:
return 表达式; // 或 return (表达式);return; // 不带返回值,只能用于void函数[来源:第5章 p10]
规则/规范
跳转到:规则/规范-
C/C++中不允许在一个函数内部定义另外一个函数(函数不能嵌套定义) [来源:第5章 p10,Notes]
-
返回类型指出函数返回值的类型,返回值必须是返回类型(或能够自动类型转换成返回类型) [来源:第5章 p10]
-
函数可以没有返回值,此时返回类型用
void[来源:第5章 p10] -
return语句可以用在函数内任何需要返回的地方,遇到return语句表示函数执行结束 [来源:第5章 p10,Notes]
代码模式
跳转到:代码模式-
无参数、无返回值:
void printTriangle() {// 打印等腰三角形}[来源:第5章 p11]
-
有参数、无返回值:
void printTriangle(int numofline) {// 根据输入调整三角形尺寸}[来源:第5章 p11]
-
有参数、有返回值(阶乘):
int fact(int n) {if (n < 0) return 0; // 非法输入int s = 1;for (int i = 1; i <= n; ++i)s *= i;return s;}[来源:第5章 p12]
-
返回表达式结果(判断闰年):
bool IsLeapYear(int year) {return ((year%4==0)&&(year%100!=0)) || (year%400==0);}[来源:第5章 p13]
易错点与辨析
跳转到:易错点与辨析-
⚠️
return的功能是在不同程序模块之间传递信息,而cout的功能是向硬件输出设备传递信息 [来源:第5章 p12,Notes] -
⚠️ 阶乘计算注意运算溢出:13! 超出 int 范围 [来源:第5章 p12,Notes]
-
⚠️ 即使函数有返回结果,
func();可以独立存在,不用涉及赋值 [来源:第5章 p12,Notes]
5.2 函数的使用
跳转到:5.2 函数的使用5.2.1 函数调用
跳转到:5.2.1 函数调用定义/语法
跳转到:定义/语法-
函数调用形式:
函数名(实际参数表)[来源:第5章 p16] -
实际参数:带入本次函数调用的输入。通常要求个数与顺序匹配;类型需满足形参要求(可发生隐式类型转换/提升;若存在重载,由重载决议选择“最佳匹配”)。 [来源:第5章 p16]
规则/规范
跳转到:规则/规范-
参数传递:将实际参数赋值给形式参数的过程 [来源:第5章 p16]
-
值传递:实参的值被拷贝并赋值给形参变量,函数中对形参的修改不影响实参 [来源:第5章 p16]
-
函数调用属于后缀表达式(与
[]、.、->、后置++/--同级),优先级很高,可以作为表达式的一部分 [来源:第5章 p16] -
函数可以嵌套调用(使用过程中,不同函数间传递的是计算好的数值,而不是函数本身) [来源:第5章 p16,Notes]
代码模式
跳转到:代码模式p(5) + p(4) + p(7) // 计算 5! + 4! + 7!printstar(p(4)); // 嵌套调用[来源:第5章 p16]
易错点与辨析
跳转到:易错点与辨析- ⚠️ 函数调用中各实参的求值顺序不作保证(不同编译器/优化可能不同;C++17 起各实参彼此“有序但顺序未指定”)。因此应避免在实参中写依赖求值顺序的副作用表达式(例如在不同实参里同时读写同一变量)。 [来源:第5章 p16,Notes]
5.2.2 函数声明
跳转到:5.2.2 函数声明定义/语法
跳转到:定义/语法-
函数声明格式:
返回类型 函数名(形式参数说明);[来源:第5章 p17] -
形式参数说明只需指出参数个数和类型,可以不给名字 [来源:第5章 p17]
规则/规范
跳转到:规则/规范-
方法一:定义在调用之前(不需要声明) [来源:第5章 p17]
-
方法二:函数原型声明(定义在调用之后时使用) [来源:第5章 p17]
-
函数声明与定义要保持一致:返回类型、函数名、参数个数、类型和顺序必须相同 [来源:第5章 p17]
-
声明需要分号结尾,定义不用 [来源:第5章 p17,Notes]
代码模式
跳转到:代码模式// 声明int max(int a, int b);
// 调用cout << max(x + 5, y - 3);
// 定义int max(int a, int b) { if (a > b) return(a); else return(b);}[来源:第5章 p18]
函数定义与声明的区别
跳转到:函数定义与声明的区别| 比较项 | 声明 | 定义 |
|---|---|---|
| 形式 | 类型、函数名、输入 | 类型、函数名、输入、执行内容 |
| 功能 | 告诉编译器去寻找该函数的定义 | 告诉计算机该函数要执行什么 |
[来源:第5章 p19]
5.2.3 函数调用过程
跳转到:5.2.3 函数调用过程执行逻辑
跳转到:执行逻辑函数调用的执行步骤:
- 在调用函数(caller)中计算被调用函数(callee)每个实际参数值
- 用实际参数值初始化形式参数
- 依次执行函数体的每个语句,直到遇见某个return语句或函数体结束
- 计算 return 表达式的值,并将其作为该次函数调用表达式的结果(很多情况下可发生拷贝消除/RVO/NRVO,不一定产生可观察的“临时对象”)
- 返回到调用点,用该返回结果继续完成外层表达式/赋值等后续计算
[来源:第5章 p20]
易错点与辨析
跳转到:易错点与辨析- ⚠️ 自动存储期的局部变量在函数返回时结束生命周期;返回值由调用点接收(可能经过拷贝消除优化)。切勿返回局部变量的引用/指针。 [来源:第5章 p20,Notes]
5.2.4 默认参数
跳转到:5.2.4 默认参数定义/语法
跳转到:定义/语法- C++在定义或声明函数时可以为一个或多个形参指定默认值 [来源:第5章 p22]
void print(int value, int base = 10);[来源:第5章 p22]
规则/规范
跳转到:规则/规范-
有声明,在声明中给出;没有声明,在定义中给出;不能重复定义 [来源:第5章 p22]
-
缺省参数无论有几个,都必须放在参数列表的最后 [来源:第5章 p22]
-
在函数调用时,若某个参数省略,则其后的参数皆应省略而取其缺省值 [来源:第5章 p22]
-
同一函数在不同源文件(不同翻译单元)中的声明,默认参数应保持一致;否则会导致不同调用点使用不同默认值,接口行为不一致(C++20 起对此有更明确的约束)。 [来源:第5章 p23]
代码模式
跳转到:代码模式// file1.cpp - 默认基数是10void print(int value=1, int base=10);
// file2.cpp - 默认基数是2void print(int value=1, int base=2);[来源:第5章 p23]
5.2.5 内联函数
跳转到:5.2.5 内联函数定义/语法
跳转到:定义/语法- 内联函数的定义:在函数头部前加保留词
inline[来源:第5章 p24]
inline float cube(float s) { return s*s*s;}[来源:第5章 p24]
规则/规范
跳转到:规则/规范-
inline最初用于建议编译器进行“内联展开”,但是否展开由编译器决定;更重要的语义是允许该函数在多个翻译单元中出现相同定义(常用于头文件)。 [来源:第5章 p24] -
对于比较短小而又多次调用的函数,可以定义为内联函数 [来源:第5章 p24]
-
inline函数的定义需在使用它的翻译单元可见(通常放在头文件);在同一源文件中不要求写在调用语句之前。 [来源:第5章 p24] -
定义一个函数为内联函数对于编译器只是一个建议(编译器可以选择不内联) [来源:第5章 p24]
5.2.6 数组作为函数参数(重要)
跳转到:5.2.6 数组作为函数参数(重要)定义/语法
跳转到:定义/语法int average(int array[5]) { int i, sum = 0; for (i = 0; i < 5; ++i) sum += array[i]; return sum / 5;}[来源:第5章 p25]
规则/规范
跳转到:规则/规范-
数组名在大多数表达式中会发生数组到指针的衰变,转换为指向首元素的指针
&a[0];但在sizeof、取地址&a、decltype(a)等场景不会衰变。 [来源:第5章 p28] -
按照值传递:调用时
score会衰变为指向首元素的指针并按值传递给形参array(其类型实际被调整为int*),因此二者指向同一段数组存储。 [来源:第5章 p28] -
在函数形参中写
T a[5]/T a[]等价于写T* a:数组形参会被调整为指针形参,因此维度不会成为形参类型的一部分。 [来源:第5章 p28] -
形参
array指向实参数组的同一片内存:在函数内通过array[i]修改元素会影响实参数组。 [来源:第5章 p28] -
若形参写成
T*/T[],传递的只有首元素地址,不携带数组长度;如需长度可另传num,或改用T(&)[N]/std::array/std::span等保留尺寸信息。 [来源:第5章 p28] -
需要额外用一个整型参数来提供数组规模 [来源:第5章 p28]
代码模式
跳转到:代码模式// 正确写法:额外传递数组大小int average(int array[], int num) { int i, sum = 0; for (i = 0; i < num; ++i) sum += array[i]; return sum / num;}
// 调用average(score, 10);[来源:第5章 p30]
易错点与辨析
跳转到:易错点与辨析- ⚠️ 数组参数传递的是地址,函数内对数组元素的修改会影响实际数组!
int average(int array[5]) { // ... array[3] = 90; // 这会修改原数组! return sum / 5;}
int main() { int score[5] = {90,65,72,83,55}; average(score); cout << score[3]; // 输出 90,不是原来的 83}[来源:第5章 p27]
- ⚠️
strcpy()等 C 字符串函数依赖结尾的\0判断停止,因此不需要把长度作为参数传入;但目标数组必须足够大,否则会发生缓冲区溢出。实务中更推荐使用std::string等更安全的方案。 [来源:第5章 p30,Notes]
5.3 变量作用域与存储类别
跳转到:5.3 变量作用域与存储类别5.3.1 变量的作用域
跳转到:5.3.1 变量的作用域定义/语法
跳转到:定义/语法-
作用域:变量名能被引用的程序范围 [来源:第5章 p32]
-
局部变量:函数内声明的变量以及函数的形式参数,作用域局部于函数内部;任何复合语句块内声明的变量,作用域局部于该语句块内部 [来源:第5章 p32]
-
全局变量(外部变量):声明/定义在所有函数之外的变量;其名字的作用域从声明处开始到本翻译单元结束(其他源文件如需使用,通常通过
extern声明)。 [来源:第5章 p32]
规则/规范
跳转到:规则/规范-
当碰到一个变量名,优先用当前作用域的定义,没有再找外部作用域(如果存在的话),最后找全局定义 [来源:第5章 p32]
-
不同作用域中可以存在同名的不同变量 [来源:第5章 p33]
执行逻辑
跳转到:执行逻辑编译器如何确定一个名字引用的是哪个变量:
- 编译器保存一个所有全局声明的标识符表
- 编译器为每个作用域都保存一个局部声明的标识符表
- 当碰到一个标识符时,先查当前作用域的标识符表,再查外部作用域表(如果存在的话),最后查全局标识符表
[来源:第5章 p33]
例题结论
跳转到:例题结论【执行结果分析】Q1(p34)
int p = 1, q = 5, r = 3; // 全局变量void f1() { int p = 3, r = 2; q = p+q+r; } // 使用局部p,r,全局qvoid f2() { p = p+q+r; } // 全部使用全局变量void f3() { int q; r = 2*r; q = r+p; } // 局部q,全局r,p执行结果:
f3: p,q,r = 1,7,6after f3: p,q,r = 1,5,6(f3的q是局部的)f1: p,q,r = 3,10,2after f1: p,q,r = 1,10,6(f1的p,r是局部的,q是全局的)f2: p,q,r = 17,10,6after f2: p,q,r = 17,10,6
[来源:第5章 p34,Notes]
【执行结果分析】Q2(p35)
int x = 1; // 全局int f() { int y = -2; cout << "x in f is " << x << endl; // 输出全局x=1 if (x) { int x; // if块内的局部x for (x=0; x<3; x++) cout << "Current x and y are " << x << " and " << y << endl; } cout << "x in f is " << x << endl; // 仍是全局x=1 return 0;}int main() { int x = 12; // main的局部x f(); cout << "x in main is " << x << endl; // main的x=12}输出:
x in f is 1Current x and y are 0 and -2Current x and y are 1 and -2Current x and y are 2 and -2x in f is 1x in main is 12[来源:第5章 p35,Notes]
5.3.2 变量的存储类别
跳转到:5.3.2 变量的存储类别定义/语法
跳转到:定义/语法- 完整的变量定义格式:
存储类别 数据类型 变量名表;[来源:第5章 p36]
-(传统教材表述)常见的存储类别/说明符包括:
auto(自动变量;C++11 起不再作为存储类,改为类型推导关键字)register(寄存器变量;C++17 起已移除,不建议使用)extern(外部链接声明,用于跨源文件引用同一实体)static(静态存储期;在命名空间作用域也可赋予内部链接)
-(补充)现代 C++ 还提供 thread_local(线程存储期)。
[来源:第5章 p36]
(1)自动变量 auto
跳转到:(1)自动变量 auto-
局部变量(函数内或块内定义的变量)默认是自动变量 [来源:第5章 p37]
-
函数(语句块)被执行时,系统自动为自动变量分配空间,而函数返回(语句块执行结束)时,系统自动回收自动变量空间 [来源:第5章 p37]
int max(int x, int y) { int rst; // 等价于 auto int rst; rst = x>y?x:y; return rst;}[来源:第5章 p37]
- ⚠️ 在C++11标准中,auto已经不再作为存储类型标识符使用,现在是一个类型指示符,指导编译器推测变量的数据类型 [来源:第5章 p37,Notes]
(2)寄存器变量 register
跳转到:(2)寄存器变量 register-
register的本意是向编译器建议“频繁使用的局部变量可优先放入寄存器”;现代编译器通常会自行完成寄存器分配,该关键字往往无实际收益,且在 C++17 起已被移除(不建议使用)。 [来源:第5章 p38] -
只有局部变量可以声明为寄存器变量 [来源:第5章 p38]
-
register声明只是给编译器的一种建议,编译器可以不理会而当作一般的自动变量 [来源:第5章 p38]
register int x;[来源:第5章 p38]
(3)外部变量 extern
跳转到:(3)外部变量 extern-
extern用于声明具有外部链接的变量/函数(通常表示“在别处定义”),常用于跨源文件共享同一个全局变量;extern声明本身通常不分配存储(除非带初始化)。 [来源:第5章 p39] -
全局变量的定义只有一次,但可以在不同的源文件中用extern声明 [来源:第5章 p39]
-
两种使用情况:
- 变量在其他文件中定义,需要在当前文件中使用
- 变量在同一文件中后面定义,需要在前面使用
extern int x; // 外部变量声明int main() { f(); cout << "in main(): x= " << x << endl;}
// file2.cppint x; // 全局变量的定义void f() { cout << "in f(): x= " << x << endl;}[来源:第5章 p39]
(4)静态变量 static
跳转到:(4)静态变量 static-
static可以用于全局变量也可以用于局部变量 [来源:第5章 p40]
-
static用于全局变量:使该名字具有内部链接(internal linkage),只能在本翻译单元内被引用;其他源文件无法通过
extern引用同一个名字来访问。 [来源:第5章 p40] -
static用于局部变量:该局部变量将不会因为函数执行结束而消亡,下一次调用也不会自动分配新的空间,而一直使用同一空间;虽然局部静态变量在函数调用结束后仍然存在,但其他函数不能引用它 [来源:第5章 p40]
-
static变量在整个程序退出时才消亡 [来源:第5章 p40]
-
函数也可以用
static声明为具有内部链接的函数(传统称“静态函数”),只能在本翻译单元内被引用。 [来源:第5章 p40] -
【整理自 PPT】静态变量会被系统自动初始化,如果没有人为初始化,默认初始化值是0 [来源:第5章 p40,Notes]
例题结论
跳转到:例题结论【执行结果分析】Q1(p41)
int f(int a) { int b = 0; static int c = 3; // 静态局部变量,只初始化一次 b = b + 1; c = c + 1; return (a + b + c);}// C++中 main 必须返回 int,此示例不标准void main() { int a = 2, i; for (i = 0; i < 3; ++i) cout << f(a) << ' ';}输出:7 8 9
分析:
- 第1次调用:b=1, c=4, return 2+1+4=7
- 第2次调用:b=1, c=5, return 2+1+5=8
- 第3次调用:b=1, c=6, return 2+1+6=9
[来源:第5章 p41,Notes]
【执行结果分析】Q2(p41)
int x = 2; // 全局void a() { int x = 25; cout << "x in a is " << x << endl; }void b() { static int x = 50; cout << "x in b is " << x++ << endl; }void c() { x *= 10; cout << "x in c is " << x << endl; }void d(int x) { cout << "x in d is " << ++x << endl; }
int main() { int x = 6; cout << "x in main is " << x << endl; a(); b(); c(); d(x); x++; a(); b(); c(); d(x); cout << "x in main is " << x << endl; return 0;}输出:
x in main is 6x in a is 25x in b is 50x in c is 20x in d is 7x in a is 25x in b is 51x in c is 200x in d is 8x in main is 7分析:
- a():使用局部x=25
- b():静态局部x,保持累加
- c():使用全局x
- d():使用参数x的副本
[来源:第5章 p41,Notes]
5.4 重载函数
跳转到:5.4 重载函数定义/语法
跳转到:定义/语法- 函数重载:可以定义同名但参数不同的多个函数 [来源:第5章 p43]
int f(int a1, int a2);int f(double a1, double a2, double a3);int f(int a1, int a2, double a3, double a4);[来源:第5章 p43]
规则/规范
跳转到:规则/规范-
在传统的C语言中,不允许定义多个同名函数 [来源:第5章 p43]
-
C++中在调用时,程序会根据调用时给出的实参表决定具体使用哪种函数定义 [来源:第5章 p43]
执行逻辑
跳转到:执行逻辑-
编译器会给所有重名定义的函数各自取一个内部名称,加以区别 [来源:第5章 p43,Notes]
-
调用时先确定可行函数:实参个数与形参匹配(必要时可用默认参数补齐),且每个实参都能通过隐式转换序列匹配到对应形参。 [来源:第5章 p43,Notes]
-
在可行函数中比较隐式转换序列的优劣:通常 精确匹配 优于 提升(promotion),优于 标准转换(conversion);若无唯一最佳匹配则报二义性。默认参数主要影响“是否可行”,不直接决定“最优”。 [来源:第5章 p43,Notes]
5.5 函数模板
跳转到:5.5 函数模板定义/语法
跳转到:定义/语法-
函数模板:实现类型的参数化(泛型化),即把函数中某些形参的类型定义成参数,称为模板参数 [来源:第5章 p45]
-
在函数调用时,编译器根据实际参数的类型确定模板参数的值,生成不同的模板函数 [来源:第5章 p45]
-
函数模板定义格式:
template<模板参数类型表>返回类型 函数名(形式参数表) {// 函数体}[来源:第5章 p46]
-
模板参数既可以是类型参数(用
class/typename声明,二者等价),也可以是非类型参数(如int N)或模板模板参数等。 [来源:第5章 p46]
代码模式
跳转到:代码模式template<class T1>T1 max(T1 a, T1 b) { return a > b ? a : b;}[来源:第5章 p46]
规则/规范
跳转到:规则/规范- 通常每个模板参数都需要出现在函数形式参数表,否则调用时需要显式指定模板实参 [来源:第5章 p47]
max(3, 5); // T1 <- intmax('d', '4'); // T1 <- char[来源:第5章 p47]
- 当模板参数未在形参表中使用时,需要显式指定类型:
[来源:第5章 p47]template <class T1, class T2, class T3>T3 calc(T1 x, T2 y) {return x + y;}calc<int, char, int>(5, 'a'); // return 102calc<int, char, char>(5, 'a'); // return 'f'
5.6 递归函数
跳转到:5.6 递归函数定义/语法
跳转到:定义/语法-
递归:用了自己。直接或间接地调用了自己的函数叫做递归函数 [来源:第5章 p49]
-
如果原始问题可以分为多个子问题,每个子问题与原问题相比只是问题规模变小了,则可以用递归函数来解 [来源:第5章 p49]
规则/规范
跳转到:规则/规范-
递归需要最终终止于某个递归出口 [来源:第5章 p49]
-
递归出口没有设计好会导致:像死循环一样停不下来,更糟糕会内存溢出,系统死机 [来源:第5章 p49,Notes]
-
递归是包含分支路径的,要确保分支之间彼此互斥,但又能完全包含问题所涉及的集合 [来源:第5章 p49,Notes]
代码模式
跳转到:代码模式阶乘的递归实现:
long fact(int n) { if (n == 0) return 1; // 递归出口 else return n * fact(n-1); // 递归调用}[来源:第5章 p50]
斐波那契数的递归实现:
int fib(int n) { if (n < 3) return 1; // 递归出口 return fib(n-1) + fib(n-2); // 递归调用}[来源:第5章 p53]
汉诺塔的递归实现:
void Hanoi(int n, char start, char finish, char temp) { if (n == 1) cout << start << "->" << finish << '\t'; else { Hanoi(n-1, start, temp, finish); cout << start << "->" << finish << '\t'; Hanoi(n-1, temp, finish, start); }}[来源:第5章 p59]
易错点与辨析
跳转到:易错点与辨析-
⚠️ 用递归调用求斐波纳契数产生大量重复计算,非常低效 [来源:第5章 p54]
-
⚠️ 用循环实现的函数没有重复计算,效率很高:
int loopfib(int n) {int curr = 1, prev = 1, tmp;for (int i = 0; i < n-2; i++) {tmp = curr;curr = curr + prev;prev = tmp;}return curr;}[来源:第5章 p55]
-
【整理自 PPT】理论上来讲,所有递归算法都可以用循环实现,但一般情况下需要依赖一些特殊的数据结构(如堆栈)才能实现 [来源:第5章 p55,Notes]
总结
跳转到:总结-
函数可以将一段完成独立功能的程序封装起来。通过函数名就可执行这一段功能。使用函数可以将程序模块化 [来源:第5章 p60]
-
C++的程序是由一组函数组成。每个程序必须有一个名为main的函数,它对应于一般的程序设计语言中主程序 [来源:第5章 p60]
-
函数也可以调用自己,这样的函数称为递归函数 [来源:第5章 p60]
习题与答案
跳转到:习题与答案习题1
跳转到:习题1编写一个函数模板,判断两个一维数组是否相同。模板参数是数组类型。
答案提示:typeid(a).name() 可以获取类型名称
[来源:第5章 p61,Notes]
习题2
跳转到:习题2编写一个函数 int count(),使得第一次调用时返回1,第二次调用时返回2,即返回当前调用次数。
答案提示:使用静态局部变量 [来源:第5章 p61,Notes]
int count() { static int c = 0; return ++c;}【整理自 PPT】(由静态局部变量特性推得)
习题3
跳转到:习题3设计一个基于分治法的函数,找出一组整型数中的最大值。
答案提示:递归,两两比较 [来源:第5章 p61,Notes]
核心考点速记表
跳转到:核心考点速记表| 考点 | 要点 |
|---|---|
| 函数定义格式 | 返回类型 + 函数名 + (形参表) + {函数体} |
| 值传递 | 形参是实参的副本,修改形参不影响实参 |
| 数组参数 | 传递的是地址,修改会影响原数组 |
| 函数声明 | 可省略形参名,只保留类型 |
| 默认参数 | 必须放在参数列表最后,省略从右向左 |
| 作用域查找顺序 | 当前作用域 → 外层作用域 → 全局 |
| static局部变量 | 不随函数结束消亡,保持上次值 |
| static全局变量 | 作用域限制在本文件 |
| extern | 声明其他文件中定义的全局变量 |
| 函数重载 | 同名不同参(个数/类型不同) |
| 函数模板 | template<class T> 实现泛型 |
| 递归要点 | 必须有递归出口,避免无限递归 |