文档开发者中心
我的

总结 | C 基本语法(三)

一、为什么要用函数


使用函数可使程序清晰、精炼、简单、灵活。


函数就是功能。每一个函数用来实现一个特定的功能。函数名应反映其代表的功能。


在设计较大程序时,往往把它分为若干个程序模块,每一个模块包括一个或多个函数,每个函数实现一个特定的功能。


一个C程序可由一个主函数和若干个其他函数构成。由主函数调用其他函数,其他函数也可以互相调用。

说明:


(1) 一个C程序由一个或多个程序模块组成,每一个程序模块作为一个源程序文件。较大的程序,可分别放在若干个源文件中。这样便于分别编写和编译,提高调试效率。一个源程序文件可以为多个C程序共用。


(2) 一个源程序文件由一个或多个函数以及其他有关内容(如指令、数据声明与定义等)组成。一个源程序文件是一个编译单位,在程序编译时是以源程序文件为单位进行编译的,而不是以函数为单位进行编译的。


(3) C程序的执行是从main函数开始的,如果在main函数中调用其他函数,在调用后流程返回到main函数,在main函数中结束整个程序的运行。


(4) 所有函数都是平行的,即在定义函数时是分别进行的,是互相独立的。一个函数并不从属于另一个函数,即函数不能嵌套定义。函数间可以互相调用,但不能调用main函数。main函数是被操作系统调用的。


(5) 从用户使用的角度看,函数有两种。库函数,它是由系统提供的,用户不必自己定义,可直接使用它们。应该说明,不同的C语言编译系统提供的库函数的数量和功能会有一些不同,当然许多基本的函数是共同的。用户自己定义的函数。它是用以解决用户专门需要的函数。


(6) 从函数的形式看,函数分两类。

无参函数:在调用无参函数时,主调函数不向被调用函数传递数据。
有参函数:在调用函数时,主调函数在调用被调用函数时,通过参数向被调用函数传递数据。


二、定义函数


1、为什么要定义函数


C语言要求,在程序中用到的所有函数,必须“先定义,后使用”。


定义函数应包括以下几个内容:


(1) 指定函数的名字,以便以后按名调用。


(2) 指定函数的类型,即函数返回值的类型。


(3) 指定函数的参数的名字和类型,以便在调用函数时向它们传递数据。对无参函数不需要这项。


(4) 指定函数应当完成什么操作,也就是函数是做什么的,即函数的功能。这是最重要的,是在函数体中解决的。


2、定义函数的方法


①定义无参函数
函数名后面括号内的void表示“空”,即函数没有参数。
函数体包括声明部分和语句部分。

在定义函数时要用“类型标识符”(即类型名)指定函数值的类型,即指定函数带回来的值的类型。


②定义有参函数

形式参数表列的格式 :数据类型 变量1,数据类型 变量2,…,数据类型 变量n


③定义空函数

函数体为空,什么也不做。


三、调用函数


1、函数调用的形式


一般形式为:函数名(实参表列)


按函数调用在程序中出现的形式和位置来分,可以有以下三种函数调用方式:
(1). 函数调用语句:把函数调用单独作为一个语句。如printf_star(); 这时不要求函数带回值,只要求函数完成一定的操作。
(2). 函数表达式:函数调用出现在另一个表达式中,如c=max(a,b); 这时要求函数带回一个确定的值以参加表达式的运算。

(3). 函数参数:函数调用作为另一个函数调用时的实参。如m=max(a,max(b,c));,又如:printf (″%d″, max (a,b))。


2、函数调用时的数据传递


① 形式参数和实际参数

在调用有参函数时,主调函数和被调用函数之间有数据传递关系。
在定义函数时函数名后面括号中的变量名称为“形式参数”(简称“形参”)或“虚拟参数”。
在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”(简称“实参”)。
实际参数可以是常量、变量或表达式,但要求它们有确定的值。

实参与形参的类型应相同或赋值兼容。赋值兼容是指实参与形参类型不同时能按不同类型数值的赋值规则进行转换。


② 实参和形参间的数据传递


3、函数调用的过程


(1) 在定义函数中指定的形参,在未出现函数调用时,它们并不占内存中的存储单元。在发生函数调用时,函数的形参才被临时分配内存单元。


(2) 将实参的值传递给对应形参。


(3) 在执行函数期间,由于形参已经有值,就可以利用形参进行有关的运算。


(4) 通过return语句将函数值带回到主调函数。应当注意返回值的类型与函数类型一致。如果函数不需要返回值,则不需要return语句。这时函数的类型应定义为void类型。


(5) 调用结束,形参单元被释放。注意: 实参单元仍保留并维持原值,没有改变。如果在执行一个被调用函数时,形参的值发生改变,不会改变主调函数的实参的值。因为实参与形参是两个不同的存储单元。


注意:实参向形参的数据传递是“值传递”,单向传递,只能由实参传给形参,而不能由形参传给实参。实参和形参在内存中占有不同的存储单元,实参无法得到形参的值。


4、函数的返回值


通常,希望通过函数调用使主调函数能得到一个确定的值,这就是函数值(函数的返回值)。


(1) 函数的返回值是通过函数中的return语句获得的。一个函数中可以有一个以上的return语句,执行到哪一个return语句,哪一个return语句就起作用。return语句后面的括号可以不要,如“return z;”与“return(z);”等价。return后面的值可以是一个表达式。


(2) 函数值的类型。函数值的类型在定义函数时指定。


(3) 在定义函数时指定的函数类型一般应该和return语句中的表达式类型一致。 如果函数值的类型和return语句中表达式的值不一致,则以函数类型为准。对数值型数据,可以自动进行类型转换。即函数类型决定返回值的类型。


(4) 对于不带回值的函数,应当用定义函数为“void类型”(或称“空类型”)。这样,系统就保证不使函数带回任何值,即禁止在调用函数中使用被调用函数的返回值。此时在函数体中不得出现return语句。


四、对被调用函数的声明和函数原型


在一个函数中调用另一个函数(即被调用函数)需要具备如下条件:


(1) 首先被调用的函数必须是已经定义的函数(是库函数或用户自己定义的函数)。


(2) 如果使用库函数,应该在本文件开头用#include指令将调用有关库函数时所需用到的信息“包含”到本文件中来。


(3) 如果使用用户自己定义的函数,而该函数的位置在调用它的函数(即主调函数)的后面(在同一个文件中),应该在主调函数中对被调用的函数作声明(declaration)。声明的作用是把函数名、函数参数的个数和参数类型等信息通知编译系统,以便在遇到函数调用时,编译系统能正确识别函数并检查调用是否合法。


函数声明的一般形式有两种:

函数类型 函数名(参数类型1 参数名1, 参数类型2 参数名2, …, 参数类型n 参数名n);

函数类型 函数名(参数类型1, 参数类型2, …, 参数类型n);

在函数声明中的形参名可以省写,而只写形参的类型。


如果已在文件的开头(在所有函数之前),已对本文件中所调用的函数进行了声明,则在各函数中不必对其所调用的函数再作声明。


注意:对函数的“定义”和“声明”不是同一回事。函数的定义是指对函数功能的确立,包括指定函数名、函数值类型、形参及其类型以及函数体等,它是一个完整的、独立的函数单位。而函数的声明的作用则是把函数的名字、函数类型以及形参的类型、个数和顺序通知编译系统,以便在调用该函数时系统按此进行对照检查,它不包含函数体。


五、函数的嵌套调用


C语言的函数定义是互相平行、独立的,也就是说,在定义函数时,一个函数内不能再定义另一个函数,即不能嵌套定义,但可以嵌套调用函数,即在调用一个函数的过程中,又调用另一个函数。

六、函数的递归调用


在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用。


程序中不应出现无终止的递归调用,而只应出现有限次数的、有终止的递归调用,这可以用if语句来控制,只有在某一条件成立时才继续执行递归调用;否则就不再继续。


七、数组作为函数参数


1、数组元素作为函数实参


数组元素可以用作函数实参,但是不能用作形参。因为形参是在函数被调用时临时分配存储单元的,不可能为一个数组元素单独分配存储单元(数组是一个整体,在内存中占连续的一段存储单元)。在用数组元素作函数实参时,把实参的值传给形参,是“值传递”方式。数据传递的方向是从实参传到形参,单向传递。

2、一维数组名作函数参数


数组名也可以做函数参数(包括实参和形参),应当注意的是:用数组元素做实参时,向形参变量传递的是数组元素的值,而用数组名做函数实参时,向形参(数组名或指针变量)传递的是数组首元素的地址。


3、多维数组名作函数参数


可以用多维数组名作为函数的实参和形参,在被调用函数中对形参数组定义时可以指定每一维的大小,也可以省略第一维的大小说明。


在定义二维数组时,必须指定列数(即一行中包含几个元素) ,由于形参数组与实参数组类型相同,所以它们是由具有相同长度的一维数组所组成的。不能只指定第1维(行数)而省略第2维(列数)。在第2维大小相同的前提下,形参数组的第1维可以与实参数组不同。例如,实参数组定义为int score[5][10];而形参数组定义为int array[ ][10];或int array[8][10];均可以。这时形参数组和实参数组都是由相同类型和大小的一维数组组成的。

八、局部变量和全局变量


1、局部变量


定义变量可能有3种情况:
(1) 在函数的开头定义;
(2) 在函数内的复合语句内定义;

(3) 在函数的外部定义。


在一个函数内部定义的变量只在本函数范围内有效,也就是说只有在本函数内才能引用它们,在此函数以外是不能使用这些变量的。在复合语句内定义的变量只在本复合语句范围内有效,只有在本复合语句内才能引用它们,在该复合语句以外是不能使用这些变量的,以上这些称为“局部变量”。

(1) 主函数中定义的变量也只在主函数中有效。主函数也不能使用其他函数中定义的变量。
(2) 不同函数中可以使用同名的变量,它们代表不同的对象,互不干扰。
(3) 形式参数也是局部变量。只在定义它的函数中有效。其他函数中不能直接引用形参。

(4) 在一个函数内部,可以在复合语句中定义变量,这些变量只在本复合语句中有效,这种复合语句也称为“分程序”或“程序块”。


2、全局变量


程序的编译单位是源程序文件,一个源文件可以包含一个或若干个函数。在函数内定义的变量是局部变量,而在函数之外定义的变量称为外部变量,外部变量是全局变量(也称全程变量)。全局变量可以为本文件中其他函数所共用。它的有效范围为从定义变量的位置开始到本源文件结束。


注意:


在函数内定义的变量是局部变量,在函数外定义的变量是全局变量。


设置全局变量的作用是增加了函数间数据联系的渠道。由于同一文件中的所有函数都能引用全局变量的值,因此如果在一个函数中改变了全局变量的值,就能影响到其他函数中全局变量的值。相当于各个函数间有直接的传递通道。由于函数的调用只能带回一个函数返回值,因此有时可以利用全局变量来增加函数间的联系渠道,通过函数调用能得到一个以上的值。


九、变量的存储方式和生存期


1、动态存储方式与静态存储方式


从变量值存在的时间(即生存期)来观察,有的变量在程序运行的整个过程都是存在的,而有的变量则是在调用其所在的函数时才临时分配存储单元,而在函数调用结束后该存储单元就马上释放了,变量不存在了。 也就是说,变量的存储有两种不同的方式: 静态存储方式和动态存储方式。


静态存储方式是指在程序运行期间由系统分配固定的存储空间的方式。


动态存储方式则是在程序运行期间根据需要进行动态的分配存储空间的方式。


数据分别存放在静态存储区和动态存储区中。全局变量全部存放在静态存储区中,在程序开始执行时给全局变量分配存储区,程序执行完毕就释放。在程序执行过程中它们占据固定的存储单元,而不是动态地进行分配和释放。


在动态存储区中存放以下数据:
① 函数形式参数。在调用函数时给形参分配存储空间。
② 函数中定义的没有用关键字static声明的变量,即自动变量。

③ 函数调用时的现场保护和返回地址等。


2、局部变量的存储类别


在C语言中,每一个变量和函数都有两个属性: 数据类型和数据的存储类别。 存储类别指的是数据在内存中存储的方式(如静态存储和动态存储)。


在定义和声明变量和函数时,一般应同时指定其数据类型和存储类别,也可以采用默认方式指定(即如果用户不指定,系统会隐含地指定为某一种存储类别)。


C的存储类别包括4种: 自动的(auto)、静态的(statis)、寄存器的(register)、外部的(extern)。根据变量的存储类别,可以知道变量的作用域和生存期。


① 自动变量(auto变量)

函数中的局部变量,如果不专门声明为static(静态)存储类别,都是动态地分配存储空间的,数据存储在动态存储区中。函数中的形参和在函数中定义的局部变量(包括在复合语句中定义的局部变量),都属于此类。在调用该函数时,系统会给这些变量分配存储空间,在函数调用结束时就自动释放这些存储空间。因此这类局部变量称为自动变量。自动变量用关键字auto作存储类别的声明。


② 静态局部变量(static局部变量)
(1) 静态局部变量属于静态存储类别,在静态存储区内分配存储单元。在程序整个运行期间都不释放。而自动变量(即动态局部变量)属于动态存储类别,分配在动态存储区空间而不在静态存储区空间,函数调用结束后即释放。
(2) 对静态局部变量是在编译时赋初值的,即只赋初值一次,在程序运行时它已有初值。以后每次调用函数时不再重新赋初值而只是保留上次函数调用结束时的值。而对自动变量赋初值,不是在编译时进行的,而是在函数调用时进行的,每调用一次函数重新给一次初值,相当于执行一次赋值语句。
(3) 如果在定义局部变量时不赋初值的话,则对静态局部变量来说,编译时自动赋初值0(对数值型变量)或空字符′\0′(对字符变量)。而对自动变量来说,它的值是一个不确定的值。这是由于每次函数调用结束后存储单元已释放,下次调用时又重新另分配存储单元,而所分配的单元中的内容是不可知的。

(4) 虽然静态局部变量在函数调用结束后仍然存在,但其他函数是不能引用它的。因为它是局部变量,只能被本函数引用,而不能被其他函数引用。


③ 寄存器变量(register变量)
一般情况下,变量(包括静态存储方式和动态存储方式)的值是存放在内存中的。当程序中用到哪一个变量的值时,由控制器发出指令将内存中该变量的值送到运算器中。 经过运算器进行运算,如果需要存数,再从运算器将数据送到内存存放。
注意:3种局部变量的存储位置是不同的:
自动变量存储在动态存储区
静态局部变量存储在静态存储区

寄存器变量存储在CPU中的寄存器中


3、全局变量的存储类别


全局变量都是存放在静态存储区中的。因此它们的生存期是固定的,存在于程序的整个运行过程。


一般来说,外部变量是在函数的外部定义的全局变量,它的作用域是从变量的定义处开始,到本程序文件的末尾。在此作用域内,全局变量可以为程序中各个函数所引用。但有时程序设计人员希望能扩展外部变量的作用域。


① 在一个文件内扩展外部变量的作用域

如果外部变量不在文件的开头定义,其有效的作用范围只限于定义处到文件结束。 在定义点之前的函数不能引用该外部变量。如果由于某种考虑,在定义点之前的函数需要引用该外部变量,则应该在引用之前用关键字extern对该变量作“外部变量声明”,表示把该外部变量的作用域扩展到此位置。有了此声明,就可以从“声明”处起,合法地使用该外部变量。


② 将外部变量的作用域扩展到其他文件
如果一个程序包含两个文件,在两个文件中都要用到同一个外部变量Num,不能分别在两个文件中各自定义一个外部变量Num,否则在进行程序的连接时会出现“重复定义”的错误。
正确的做法是: 在任一个文件中定义外部变量Num,而在另一文件中用extern对Num作“外部变量声明”,即“extern Num; ”。在编译和连接时,系统会由此知道Num有“外部链接”,可以从别处找到已定义的外部变量Num,并将在另一文件中定义的外部变量Num的作用域扩展到本文件,在本文件中可以合法地引用外部变量Num。

extern既可以用来扩展外部变量在本文件中的作用域,又可以使外部变量的作用域从一个文件扩展到程序中的其他文件,系统在编译过程中遇到extern时:先在本文件中找外部变量的定义,如果找到,就在本文件中扩展作用域;如果找不到,就在连接时从其他文件中找外部变量的定义。如果从其他文件中找到了,就将作用域扩展到本文件;如果再找不到,就按出错处理。


③ 将外部变量的作用域限制在本文件中
有时在程序设计中希望某些外部变量只限于被本文件引用,而不能被其他文件引用。这时可以在定义外部变量时加一个static声明。
这种加上static声明、只能用于本文件的外部变量称为静态外部变量。在程序设计中,常由若干人分别完成各个模块,各人可以独立地在其设计的文件中使用相同的外部变量名而互不相干。只须在每个文件中定义外部变量时加上static即可。这就为程序的模块化、通用性提供方便。如果已确认其他文件不需要引用本文件的外部变量,就可以对本文件中的外部变量都加上static,成为静态外部变量,以免被其他文件误用。至于在各文件中在函数内定义的局部变量,本来就不能被函数外引用,更不能被其他文件引用,因此是安全的。
声明局部变量的存储类型和声明全局变量的存储类型的含义是不同的。对于局部变量来说,声明存储类型的作用是指定变量存储的区域(静态存储区或动态存储区)以及由此产生的生存期的问题,而对于全局变量来说,由于都是在编译时分配内存的,都存放在静态存储区,声明存储类型的作用是变量作用域的扩展问题。
用static声明一个变量的作用是:
对局部变量用static声明,把它分配在静态存储区,该变量在整个程序执行期间不释放,其所分配的空间始终存在。

对全局变量用static声明,则该变量的作用域只限于本文件模块(即被声明的文件中)。


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


在声明部分出现的变量有两种情况: 一种是需要建立存储空间的(如“int a;”);另一种是不需要建立存储空间的(如“extern a;”)。前者称为定义性声明(defining declaration),或简称定义(definition);后者称为引用性声明(referencing declaration)。一般把建立存储空间的声明称定义,而把不需要建立存储空间的声明称为声明。


外部变量定义和外部变量声明的含义是不同的。外部变量的定义只能有一次,它的位置在所有函数之外。在同一文件中,可以有多次对同一外部变量的声明,它的位置可以在函数之内(哪个函数要用就在哪个函数中声明),也可以在函数之外。系统根据外部变量的定义(而不是根据外部变量的声明)分配存储单元。


对外部变量的初始化只能在“定义”时进行,而不能在“声明”中进行。所谓“声明”,其作用是声明该变量是一个已在其他地方已定义的外部变量,仅仅是为了扩展该变量的作用范围而作的“声明”。


十一、内部函数和外部函数


函数本质上是全局的,因为定义一个函数的目的就是要被另外的函数调用。如果不加声明的话,一个文件中的函数既可以被本文件中其他函数调用,也可以被其他文件中的函数调用。但是,也可以指定某些函数不能被其他文件调用。根据函数能否被其他源文件调用,将函数区分为内部函数和外部函数。


1、内部函数


如果一个函数只能被本文件中其他函数所调用,它称为内部函数。在定义内部函数时,在函数名和函数类型的前面加static,即:static 类型名 函数名(形参表);内部函数又称静态函数,因为它是用static声明的。使用内部函数,可以使函数的作用域只局限于所在文件。这样,在不同的文件中即使有同名的内部函数,也互不干扰,不必担心所用函数是否会与其他文件模块中的函数同名。 通常把只能由本文件使用的函数和外部变量放在文件的开头,前面都冠以static使之局部化,其他文件不能引用。这就提高了程序的可靠性。


2、外部函数


如果在定义函数时,在函数首部的最左端加关键字extern,则此函数是外部函数,可供其他文件调用。
说明:使用extern声明就能够在本文件中调用在其他文件中定义的函数,或者说把该函数的作用域扩展到本文件。extern声明的形式就是在函数原型基础上加关键字extern。

——————————————————

BY:HW-RJ

点赞
收藏
回复
6
分享
举报
浏览1465 发布于2019-11-13 07:01未知归属地
全部评论
最多点赞
最新发布
最早发布

蛮难的样子,我不会


6楼回复于2019-11-14 04:09 未知归属地

这么好的帖子怎能不顶,楼主看到我

4楼回复于2019-11-13 07:04 未知归属地

关联阅读:总结 | C基本语法(二)

3楼回复于2019-11-13 07:03 未知归属地

关联阅读:总结 | C基本语法(一)

2楼回复于2019-11-13 07:03 未知归属地

C基本语法的第三篇来啦

1楼回复于2019-11-13 07:02 未知归属地
写回答
新增插入模板功能
一键使用模板,快速填写内容,轻松发帖~
知道了
  • 为了保障您的信息安全,请勿上传您的敏感个人信息(如您的密码等信息)和您的敏感资产信息(如关键源代码、签名私钥、调试安装包、业务日志等信息),且您需自行承担由此产生的信息泄露等安全风险。
  • 如您发布的内容为转载内容,请注明内容来源。

我要发帖子

了解社区公约,与您携手共创和谐专业的开发者社区。