c/c++ 谭浩强第四章总结 函数的相关知识点 利用函数完成指定功能

一 什么是函数

一个函数就是一个功能

一个程序只能有一个main函数 程序是从main函数开始执行的 且main函数是由系统调用的

所有函数都是平行的 定义函数时是相互独立的 函数不能嵌套定义 也不能把函数定义写在主函数中

其他函数不可调用main函数( 有点废话的感觉了)

如果main函数的位置在其他两个函数之前 在main函数调用该两个函数前 必须对它们进行声明(后面会有详细介绍)

上面说到如果使用自己定义的函数 在程序中要对函数进行声明 那么对库函数要不要声明呢? 同样需要 但是不用用户自己声明 因为在有关的头文件中已包含了声明的内容(如#include<iostream>) 这就是在调用库函数时必须用#include指令包含对应的头文件的原因

二 定义函数的一般形式

没什么好说的 写一句话吧 定义无参函数时函数首部的括号内可以不写void 如 void printstar() 但是专业人员一般不省略括号内的void

三 函数参数和函数的值

在定义函数时函数名后面括号里的变量名称为形式参数

在主调函数中调用一个函数时 函数名后面括号中的参数(可以是一个表达式)称为实际参数

关于形参与实参

定义函数时指定的形参 在未出现函数调用时 他们并不占内存中的存储单元 因此称他们是形式参数或虚拟参数 表示他们并不是实际存在的数据 只有在发生函数调用时 函数的形参才被分配内存单元 以便接收从实参传过来的数据 在调用结束后 形参所占内存单元也被释放

实参和形参之间的传递是值传递 即单向传递 只有实参传给形参 不由形参传回实参  注意实参和形参所占的是不同的单元 所以形参改变不影响实参  由此联想到后面的数组传递  数组名即首元素地址  而以数组作为实参传递时 实参和形参占用同一段内存单元

其实这句话并不是很严谨 在第六章中说到 其实以地址和数据传输的都是值传递 为单向传递 而以引用的形式传输的方式称为址传递 形参的改变直接影响实参

关于函数的返回值

函数的返回值是必须明确的   而当函数的返回值的类型和函数中return语句中表达式的值不一样时        以函数返回值类型为准  对于数值型数据 可以自动进行类型转换

四 函数的调用

如果是无参函数  函数的参数列表可以省略 但是函数名后面的括号不可省略 如 printstar();   

如果是 func( i , ++i );  实参的值受c++系统的影响 大多数是自右向左的顺序求值的

函数调用的方式

可以单独作为一个语句  也可以出现在一个表达式中  和其他类型数据进行加减乘除  还可以作为其他函数的参数

函数的声明

当主调函数在被调用函数之前时  需要对被调用函数进行声明  目的是通知编译系统这个函数的存在  让编译系统对于函数声明和函数调用时  函数的各个参数进行检查  是否正确   头文件的使用就是为了对库函数进行声明   

函数的定义和声明   

定义是对函数功能的确立  声明是通知编译系统 以便在对包含函数调用的语句进行编译时据此进行对照检查   声明就是对已定义函数的首部加一个分号就好   声明时函数参数名可以省略 

如果多个函数要用到函数a 则可以把函数a的声明放在所有函数外部这就是对函数的外部声明 在各个主调函数中就不必对所调用函数再做声明

五 函数的嵌套调用

没什么可说的 就是一个函数中调用另一个函数而已

六 函数的递归调用

这一部分我觉得太挺难的  需要做一些题来领悟

递归调用就是一个函数在函数体内调用本身    这样的话很容易造成无休止的自我调用  所以函数递归调用中很重要的一点就是终止标志  即函数递归调用 包括递归公式 和递归结束条件(边界条件)

七  内置函数

这个也没什么可说的  内置函数就是在函数声明及定义  或者只在函数的声明时  函数首行的左端加一个inline即可 适用于规模小且使用频繁的函数 因为在规模很小的情况下 函数调用的时间开销可能大于执行函数本身的时间 所以置为内置函数  可大大减少时间

这个是建议性的而不是指令性的  还有一点 如果函数是递归函数或者包含循环 则不可定为内置函数

有一个之前不知道的点就是  在调用函数的过程中 要记下当时执行的指令的地址 还要记下当时有关的信息  以便函数调用之后继续执行  流程返回到先前记下的地址处 还要根据记下的信息恢复现场 这些都要花费时间

八  函数的重载

引:  有时我们要实现的函数是同一类的功能 只是有些细节不同  那么程序编写者就要分别编写出功能类似而名字不同的函数 这是不方便的 所以就有了重载函数 

总而言之  就是用同一个函数名字去定义多个函数  即对一个函数名赋予新的含义  达到一名多用的效果 所谓重载 就是一物多用  

那么  函数名字都相同  系统怎么判定该调用哪个函数呢?  编译系统会根据实参的类型找到与之对应的函数 然后调用该函数 

重载函数并不要求函数体相同  

重载函数的参数个数 参数类型或参数顺序三者中必须至少有一种不同  函数返回值可以相同可以不同  但是不能只有函数返回值不同 也就是函数返回值不可作为函数重载的依据  因为这样编译系统就无法判定该调用哪个函数了  很容易理解 

九  函数模板

引: 函数重载可以实现一名多用  将实现相同或类似功能的函数用同一个函数名来定义 但是仍然要定义多个每一个函数  在某种情况下也不方便   比如三种不同返回值的max函数用来比较三种不同类型的数据   这样定义三个函数就太不方便了   所以有了函数模板

所谓函数模板就是建立一个通用函数 其函数类型和形参类型不具体指定 用一个虚拟的类型来代替 这个通用函数就是函数模板  

凡是函数体相同的函数都可以用这个模板代替   

就不举例了 实在不想打了  看书吧  谭浩强p103

格式:  template <  typename T  >

把这行写在定义函数模板的上一行 而参数列表中的类型和函数返回值就用T来表示   而在调用函数时  系统自动将你用的实参类型代替这个T    

函数模板只适用于函数体相同 函数参数个数相同而类型不同的情况  讲道理目前来看还用的不多  其实这些在工作之后工程规模很大时才会用到 

十  有默认参数的函数

当用同样的实参多次调用同一个函数时 可以给形参一个默认值  这样形参就不必一定要从实参处取值了   如  int max(int a,int b=3);    调用时 你可以写max(5)  也可以写max(5,4)  b对应的实参处有值就用实参的  没值就用形参默认的参数   

注意  指定默认值的参数必须放在参数表列的最右端  否则无法判断

对于有默认参数的函数的声明  如果函数的定义在函数调用前 则在定义处给出默认值即可  若在之后 则在函数声明处 给出默认值   在定义时可以不给默认值 也就是  必须在函数调用之前将默认值的信息通知编译系统  因为编译是从上往下的 若不通知编译系统 则在编译到函数调用时 就会认为实参个数与形参个数不匹配而出错 

若定义和声明都给 且不同  则有的编译系统会给出“默认指定参数值不同”的报错信息   有的会以先遇到的为准 

一个函数不建议又做重载函数  又做有默认参数的函数 容易出现二义性 但是这个不是必然的 如果调用的很明确也不会出现错误

函数重载 函数模板 有默认参数函数 内置函数是c++中新增的 在编写较大程序时 会用到他们

十一  局部变量与全局变量

局部变量

每一个变量都有其有效的作用范围 这就是变量的作用域  在作用域以外是不能访问这些变量的

在一个函数内部定义的变量就是局部变量  它只在本函数范围内有效  在函数以外是不能使用这些变量的  比如 函数的形参  函数内部定义的变量 还有在复合语句中定义的变量 都是局部变量  (复合语句中定义的变量只在复合语句范围内有效)

全局变量 

全局变量的有效范围是从定义变量的位置开始到本源文件结束 

全局变量的作用是增加了各函数间数据联系的渠道  不必要时不建议使用全局变量 诸多弊端见谭浩强p108,109  此处不写了

注  当函数外部有全局变量a  函数内部定义局部变量a  则在局部变量的作用范围内 全局变量被屏蔽 

变量的有效范围称为作用域    有 文件作用域  函数作用域  块作用域  函数原型作用域 文件作用域是全局的 其他三者是局部的  其他的像函数 数组 结构体 类等都有作用域 概念与变量作用域类似

十二  变量的存储类别

严格来讲 其实这里才是我最想写的部分 

上面11中说到了作用域  还有一个属性就是存储期 存储期指的是变量在内存中的作用周期  这是从变量值存在的时间角度来分析的 存储期分为静态存储期和动态存储期 这是由变量的静态存储方式和动态存储方式决定的   

静态存储方式 : 程序运行期间  系统对变量分配固定的存储空间    动态存储方式 :系统为变量动态地分配空间  

存储空间分为3部分   程序区  静态存储区   动态存储区   

比如 全局变量存放在静态存储区 程序开始时给全局变量分配存储单元  程序执行完毕就释放这些空间 程序执行过程中  他们占有固定的存储单元  而不是动态地进行分配和释放 

动态存储区:1 函数的形式参数  在调用函数时 动态地给形参分配存储空间  2 函数中定义的变量 (未加static声明的局部变量   如果加上static就是静态存储了  也就是函数结束时 此变量并不会释放 当程序结束时才会释放)  3  函数调用时现场保护和返回地址等  这一部分在内置函数中有提到 当时声明内置函数的原因就是调用函数的过程中因为要有这些步骤 所以当函数规模很小且使用频率很高时 声明为内置函数会增加运行速度

c++变量除了有数据类型之外  还有存储类别的属性 存储类别指的是数据在内存中的存储方式  存储方式分为静态存储和动态存储两大类  存储类别4种  自动的(auto)存储类别  静态的(static)存储类别 寄存器的(register)存储类别 外部的(extern)存储类别 根据变量的存储类别可以知道变量的作用域和存储期

1   自动变量

函数中的局部变脸 如果不用关键字static加以声明 编译系统对他们是动态地分配空间的  如函数的形参和函数中定义的变量及复合语句中定义的变量都属于此类  这些数据存储在动态存储区中 函数调用结束或复合语句结束时就自动释放这些空间  因此这些局部变量称为自动变量

自动变量用关键字auto作为存储类别的声明  且定义自动变量时  关键字auto可以省略不写 也就是说其实如果你不特意声明为static或者register类型的变量  而声明为 int a =10; 则这种的都默认为自动变量  系统把它默认为自动存储类别 属于动态存储方式  (auto和int的位置随意)

2  用static声明静态局部变量

如果希望函数中的局部变量在函数调用结束后不消失而保留原值  其占有的存储单元不释放 在下一次调用函数时 该变量保留上一次函数调用结束时的值 则应该指定该局部变量为静态局部变量  静态局部变量在函数调用结束后 并不释放 

对于静态局部变量的说明: 

1  静态局部变量在静态存储区内分配内存单元 在程序整个运行过程中都不释放 而自动变量(即动态局部变量)属于动态存储类别 存储在动态存储区 函数调用结束后即释放 

2  静态局部变量是在编译时赋初值的  只赋值一次  在程序运行时他已有了初值 以后每次调用函数时 不再赋初值而是保留上一次函数调用结束时的值  而对于自动变量赋初值 不是在编译时进行的 而是在函数调用时进行 每调用一次函数就重新给自动变量赋一次初值 相当于执行一次赋值语句

3  如果定义局部变量时不赋初值 对于静态局部变量 编译时自动赋初值0(对于数值型变量) 或者空字符(对于字符型变量)  而对于自动变量来说 如果不赋初值 则它的值是一个不确定的值 这是因为每次函数调用结束后存储单元已释放下次调用时另分配存储单元  而所分配的单元中的值是不确定的

4  虽然静态局部变量在函数调用结束后仍然存在 但其他函数是不能引用它的 也就是说在其他函数中它仍是不可见的

和全局变量一样 静态局部变量也是不建议过多使用 

3   用register声明寄存器变量

如果一个变量使用频繁 (例如在一个函数中要执行10000次循环 每次都要引用某局部变量)则为存取变量的值要花不少时间 为了提高执行效率 c++允许把局部变量的值放在cpu的寄存器中  需要时直接从寄存器中取出参与运算 不必再到内存中去存取 因为对寄存器的存取速度远高于对内存的存取速度 这种变量称为寄存器变量

这个和内联函数一样 即使你声明某变量为寄存器变量 也是建议性的而不是指令性的  如今优化的编译系统能够自动识别使用频繁的变量 从而自动地把变量放在寄存器中  实际使用中不必用register声明变量 读者对他有一定了解即可

4  用extern声明外部变量 

4.1 在文件内用extern声明全局变量 

如果一个外部变量不在文件开头定义 则它的有效作用范围只限于定义的位置到文件终止得位置  若在此之前需要引用此外部变量 则只需在使用处用extern声明此外部变量即可  表示该变量是一个将在下面定义的全局变量  有了此声明 就可以从声明的位置起 合法地引用该全局变量  这种声明称为提前引用声明 

为了避免进行这种意义不大的操作  一般都把全局变量的定义放在所有需要引用它的函数之前 这样就可以避免在函数中多加一个extern声明了

4.2  在多文件的程序中声明外部变量 

一个c++程序可以由一个或多个源程序文件组成  对于只有一个源文件组成时  使用外部变量的方法前面已经介绍   如果程序由多个源程序文件组成 在一个文件中想引用另一个文件中已定义的外部变量(全局变量)  怎么办呢?

如果说两个文件都要用到一个外部变量num  这时不可以在两个文件中分别定义一个num变量 这样在程序连接时会出现“重复定义”的错误  正确做法为:在一个文件中定义此外部变量num  在另一个文件中用extern作外部变量声明 即 extern int num;

如果你做了extern声明 编译系统由此知道了此num变量是一个在别处定义的外部变量   他先在本文件中找有没有外部变量num  如果有 就把此变量的作用域扩展到本行开始  如果本文件中没有此外部变量  则在程序连接时从其他文件中找有无外部变量num  如果有 就把另一文件中定义的外部变量num的作用域扩展到此文件  此文件就可以合法地引用该外部变量num了

注意  extern只用作变量声明 而不是变量定义  它只是对一个已定义的外部变量做声明  以扩展其作用域  只要看到extern 就是用作声明的

注意  extern扩展全局变量的作用域  需要慎重  因为在执行一个文件中的函数时 可能会改变此全局变量的值 从而影响到另一文件中函数的执行结果  

5  用static声明静态外部变量

有时 在程序设计中希望某外部变量只限于在本文件中使用 而不能被其他文件引用 这时可以在定义外部变量时加一个static声明  

其实这个和extern是完全相反的  extern在声明外部变量时  如果在a文件中声明num  则可以扩展b文件中的全局变量num的作用域  而static是用来防止这种情况发生的 

这种加上static声明 只能用于本文件的外部变量(全局变量)称为静态外部变量  这个和静态局部变量是不同的 两者也没有什么联系  

这个在工作时 工程项目很大时很有用  这时需要若干人分别完成各个模块 各人可以独立地在其设计的文件中使用相同名字的全局变量而互不相干 只需要在每个文件中的全局变量前加一个static声明即可 这就为程序的模块化 通用性提供了方便 

不要误以为用static声明的外部变量才采用静态存储方式(放在静态存储区)  而不加static声明的是动态存储(放在动态存储区)  这样是不对的  实际上两种形式的外部变量(加static声明的和不加static声明的)都用静态存储方式 只是加的不可以被其他源文件引用 而不加的可以被其他源文件引用而已  且二者都是在编译时分配内存的

-------------------------------------------------------------------------------------------------------------------

其实在谭浩强这本书紧随其后有个变量属性小结  写的很概括很清晰  此处就不写了  见p115下

十四  关于变量的声明和定义

首先 对于函数 声明和定义的区别是很明显的 函数的声明是函数的原型 函数的定义是函数功能的确立

对于变量而言 声明和定义稍显复杂了一点  

变量的声明有两种情况  第一种是需要建立存储空间的  如 int a =10; 第二种是不需要建立存储空间的 如 extern int a ; 前者称为定义性声明 后者称为引用性声明     广义地说 声明包括定义  因为第一种也可以说声明了一个变量a  也可以说定义了一个变量a

所以 为了方便叙述  把建立存储空间的声明称为定义  把不需要建立存储空间的声明称为声明           显然这里的声明是相对狭义的 不包括定义性声明

而对于外部变量的声明和定义是不同的  外部变量的定义只能有一次 而声明可以有很多次 且函数初值的设定只能在定义的时候设定 定义是要创建存储空间的 而声明只是告诉编译系统 其他地方有这么一个变量存在(本源文件内或其他源文件内)

用static声明一个变量作用有两个: 1  对于局部变量用static声明时  作用是是该变量在函数调用结束后不释放 下一次调用时仍保留上一次调用结束时的值  整个程序执行期间始终存在 使其存储期为程序的全过程    2  全局变量用static声明 则该变量的作用域只限于本文件模块(即被声明的文件中)

注意  用auto register static 声明变量时 是在定义的基础上加上这些关键字  不能单独使用                 如 static a =10; 是不允许的

十五 内部函数和外部函数

说实话 这一部分对于理解函数原型是很有帮助的 

现在很纠结 想直接复制书上的原话 但是这样写文章的本意就消失了  但是确实每一句都很有用 那这里就言简意赅地说的简短一点吧(主要含义)

函数本质上是全局的 因为一个函数要被其他函数调用 但是也可以指定函数只能被本文件调用 而不能被其他文件调用  (其实这里和静态全局变量道理一样 只是一个针对变量 一个针对函数)根据函数能否被其他源文件调用 分为内部函数和外部函数

内部函数

如果一个函数只能被本文件中其他函数所调用,它称为内部函数。  定义内部函数时 在函数首部加上一个static即可  如 static int max(int a,int b)   {...} 

作用:  使用内部函数可以让函数只局限所在文件  如果在不同文件中有相同名字的函数 可以互不干扰 通常把只能由一个文件所用的函数和外部变量存放在一个文件中

外部函数

在定义函数时,如果在函数首部的最左端冠以关键字extern(可省略) ,则表示此函数是外部函数,可供其他文件调用。

关键字extern可以省略  其实大部分函数都是外部函数  只要你不加以static声明  也就是说 在不同文件中  如果在a文件中定义了函数 int max(int a,int b) 则在b文件中也可以使用此函数  只要加上一个声明即可  即 extern int max(int ,int );  且extern可以省略  就当作是本文件中定义的函数 来声明这个函数即可  这就是函数原型 的真正含义    用函数原型可以把函数的作用域扩展到定义该函数的文件之外 函数原型通知系统: 该函数在本文件中稍后定义 或在另一个文件中定义

而对于系统函数(库函数)也是一样的道理  只不过当你使用一些常用函数时 比如sin函数 不需要用户在自己的文件中重新定义  只需要加上该函数的原型即可 而sin函数的原型就在math头文件中包括着(sin函数的主体并不在math头文件中 而是存放在数学函数库中)所以你只需要#include<math>即可  这样你就可以在文件中合法地调用各种数学库函数了
  

十六  头文件

许多程序都要使用系统提供的库函数,而C++又规定在调用函数前必须对被调用的函数作原型声明,如果由用户来完成这些工作,是非常麻烦和枯燥的,而且容易遗漏和出错。现在,库函数的开发者把这些信息写在一个文件中,用户只须将该文件“包含”进来即可(如调用数学函数的,应包含cmath头文件),这就大大简化了程序,写一行#include指令的作用相当于写几十行,几百行甚至更多行的内容。这种常用在文件头部的被包含的文件称为“标题文件”或“头部文件”(“头文件”)。(复制的)

头文件中一般包含以下几类内容

1  对类型的声明  包括第七章的自定义类型和第八章的对类的声明  

2  函数声明  应特别注意的是 函数的定义不放在头文件中 而是放在函数库中或单独编译成目标文件 在编译连接阶段与用户文件连接组成可执行文件

3  内联函数定义 因为内联函数的代码是要插入用户程序中的 因此它应当与调用它的语句在同一文件中 而不能分别放在不同文件中

4 宏定义 #define N 100 

5  全局变量的定义 

6  外部变量的声明  extern int a;

7   还可以根据需要包含其他头文件

由于有了#include指令 就可以把不同文件组合在一起 形成一个文件 所以说头文件是不同源文件之间的接口

十六  关于c++标准库和头文件的形式

如 c++中引用头文件格式为 #include<cmath>  而c语言传统的为 #include<math.h>(为了兼容)  建议使用第一种    如果用户自己编写头文件 可以加.h后缀 便于区分头文件是自己编写的还是别人提供的

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值