目录
一、初探程序中的函数二、深入浅出函数调用三、函数定义细节剖析四、函数参数深度剖析五、编写函数对数组排序六、变量的作用域与生命期(上)七、变量的作用域与生命期(下)八、函数专题练习九、递归函数简介一、初探程序中的函数
函数的概念
函数是具有特定功能的程序部件(可当黑盒使用)函数有明确的使用方式(固定输入对应固定输出)函数在程序中可重复使用(程序中的工具)函数的类型
数据处理(数据→数据)通过某种规则将 x处理成 y,如: y = 2x +1
过程定义(数据→功能)(根据数据)执行一系列动作,进而完成某种功能,如:屏幕打印
C语言中函数的组成部分
函数名:函数的唯一标识
函数参数定义:数据输入(数据→数据,数据→动作)
函数返回类型:
数据输出(数据→数据)无返回值(数据→动作)广义函数示例:
返回类型 函数名(参数1,参数2)
{
程序语句1;
程序语句2;
......;
程序语句n;
}
C语言中的函数示例
函数的调用
通过函数名调用已经定义好的函数函数调用时需要依次指定函数参数的具体值函数调用的结果(返回值)可保存在同类型的变量中下面看一段函数调用的代码:
#includeint func_demo( int x ) { int y = 0; y = 2 * x - 1; return y; } int main() { int r1 = func_demo(1); int r2 = func_demo(5); int r3 = func_demo(10); printf("r1 = %d\n", r1); printf("r2 = %d\n", r2); printf("r3 = %d\n", r3); return 0; }
下面为输出结果:
下面再看一段编写函数计算累加和的代码:
#includeint sum (int n) { int r = 0; int i = 0; for(i=1; i<=n; i++) { r += i; } return r; } int main() { int o[10] = {10, 20, 30, 40, 50, 100}; int r[10]; int i = 0; for(i=0; i<10; i++) { r[i] = sum(o[i]); } for(i=0; i<10; i++) { printf("sum(%d) = %d\n", o[i], r[i]); } return 0; }
下面为输出结果:
采用数组可以便捷的求出从1加到指定的数。
小结
函数是具有特定功能的程序部件函数由函数名,参数,返回类型以及函数体组成通过函数名调用已经定义好的函数,并同时传入参数值函数的本质就是可重复利用的代码段二、深入浅出函数调用
再论C语言程序的入口
一般情况下,C语言程序从main()开始执行深入理解main()
main() 是应用程序与操作系统的一个“约定”当操作系统运行应用程序时,首先调用的就是 main() 函数应用程序必须运行于操作系统,接受操作系统管理应用程序的运行
应用程序运行流程
下面看一段代码,实际感受一下吧:
#include#include int main() { printf("Hello World!\n"); system("pause"); return 0; }
没错,就是这个简单的不能再简单的代码,打开Test.exe,得到下图:
下面来证明一下 返回值 0 是返回给操作系统,首先打开命令提示符,如下:
然后在命令提示符上切换到这个目录,采用 cd 命令,如下:
然后运行 Test.exe,如下:
按下回车,输入echo %errorlevel%
,如下:
可以看到输出 0 ,这是 main 函数中的返回值。如果把 return 0那里换成 return 666,那么运行 echo %errorlevel% 会输出 666。这就说明返回值成功返回给操作系统。
核心本质
C程序由一系列不同功能的函数构成函数之间通过相互调用组合“小功能”构成“大功能”整个C程序的功能由函数的组合调用完成工具包的本质
工具包就是函数集,包含了一系列定义好的函数#include 语句用于声明需要使用工具包中的函数工具包中的函数由其它开发者通过C语言编写也可根据项目需要自行编写私有工具包小结
main() 是C程序中的入口函数(第一个被调用的函数)main() 函数被操作系统调用(返回值也传给操作系统)工具包的本质是一个函数集合可以根据需要自行定义工具包(函数集)三、函数定义细节剖析
函数定义与函数调用
函数在被调用前必须完整定义
函数可以先被声明,再被定义
声明时,必须给出函数三要素(函数名,参数列表,返回类型)定义时,必须完整给出函数体定义特殊的基础类型
C语言中存在空类型(void),这种类型表示“空”void 不能用于定义具体变量(没有任何数据属于空类型)void 常用于函数定义,表示无返回值或无参数void 深入理解
void 是基础类型,但不是基础数据类型,无法定义变量void可用于函数参数,表示函数无参数void 可用于函数返回类型,表示函数无返回值所以说,下面程序的写法就是错误的。
#includevoid demo(void i) { return i; } int main() { void v; void x = v; demo(x); return 0; }
注意事项
C语言中的函数如果确定不需要参数,那么用 void 定义参数,而不是不写参数。
#includevoid f( ) { printf("void f() \n"); } void g(void) { printf("void g() \n"); } int main() { f(); f(1, 2); g(); // g(1); // ERROR return 0; }
下面为输出结果:
可以看出,f 函数的输入参数是没有限制的,而g 函数没有输入参数,这点要特别注意。
关于函数返回
return 语句直接返回主调函数,后续代码不再执行
对于无返回值函数
return 可以直接使用,无需跟上返回值当函数体中没有 return 语句时,最后一条语句执行后自动返回对于有返回值的函数
return 必须跟上一个合法返回值,所有执行分支都必须显示返回值return 语句必须出现在函数体中,并且必须被执行小结
函数可以先被声明,再被定义(调用前必须给出完整定义)C语言中存在空类型(void) ,这种类型表示“空”void是基础类型,但不是基础数据类型return语句直接返回主调函数,后续代码不再执行函数中的所有执行分支必须存在return语句四、函数参数深度剖析
深入函数参数
函数参数在函数定义时并没有确定的值(形参)函数参数的具体值在函数调用时指定(实参)函数参数的本质是变量函数调用时指定的实参用于对形参进行初始化,初始化之后形参在函数内部等同于普通变量。下面看一段代码:
#includeint test(int n); int main() { int i = 3; int j = test(i); printf("i = %d, j = %d\n", i, j); return 0; } int test(int n) { n = n * 10; return n; }
下面为输出结果:
特殊的数组参数
可以在定义函数时使用数组形参(如: int f( int a[5] );)数组形参需要使用同类型数组作为实参在C语言中,数组作为函数参数传递时大小信息丢失在函数内部修改数组形参,将影响数组实参注意事项
数组形参已经发生退化,不包含数组大小信息示例:void func (int a[ ])
等价于void func (int a[1])
等价于void func (int a[10])
等价于void func (int a[100])
下面看一段代码,加深印象:
#includevoid demo(int a[3]) { a[0] = 50; } int sum(int a[], int len) { int ret = 0; int i = 0; while( i < len ) { ret += a[i]; i++; } return ret; } int main() { int arr1[5] = {0, 1, 2, 3, 4}; // arr1[0] -> 0 int arr2[10] = {0, 10, 20, 30, 40}; // arr2[0] -> 0 demo(arr1); demo(arr2); printf("arr1[0] = %d\n", arr1[0]); printf("arr2[0] = %d\n", arr2[0]); printf("sum(arr1) = %d\n", sum(arr1, 5)); printf("sum(arr2) = %d\n", sum(arr2, 10)); return 0; }
下面为输出结果:
这里注意一下这句话:在函数内部修改数组形参,将影响数组实参,所以当调用 demo 函数后,实参的 arr1[0] 和 arr2[0] 都变成了50。
小结
函数定义时参数没有具体值,函数调用时指定参数初始值函数参数在函数内部等同于普通变量在C语言中,数组作为函数参数传递时大小信息丢失在函数内部修改数组形参,将影响数组实参在C语言中,数组作为函数参数传递时大小信息丢失在函数内部修改数组形参,将影响数组实参五、编写函数对数组排序
排序的一般定义
排序是计算机内经常进行的一种操作,其目的是将一组“无序”的数据元素调整为“有序”的数据元素。排序中的关键操作
比较
任意两个数据元素通过比较操作确定先后次序交换
数据元素之间需要交换才能得到预期结果核心思想
每次(例如第i次,i = O,1,..., n-2) 从后面 n-i 个待排的数据元素中选出最小元素,作为第 i 个元素。解决方案
编写函数 int Min(int a[], int b, int e) 选择最小元素
功能定义:在数组 a 的 b ...e ] 范围寻找最小元素返回值:最小元素在数组中的下标循环遍历数组,将每次找到的最小元素交换就位
下面看一下示例代码:
#includeint Min(int a[], int b, int e) { int r = b; int i = 0; for(i=b; i<=e; i++) if( a[r] > a[i] ) r = i; return r; } void Sort(int a[], int n) { int i = 0; int j = 0; int k = 0; for(i=0; i 下面为输出结果:
小结
排序是将一组“无序”的数据调整为“有序”的数据排序中的关键操作为比较和交换排序时每次选择未排序数据中的最小值,并交换就位每次选择交换使得数据逐渐有序,最终完全有序六、变量的作用域与生命期(上)
C语言中变量的分类
局部变量
函数内部定义的变量(隶属于当前函数)只能在当前函数中访问使用全局变量
全局范围内的变量(不特定隶属于任意一个函数)可以在任意函数中访问使用同名变量的问题
不同函数中的局部变量可以同名(不会产生冲突)全局变量不能同名(会产生命名冲突)当局部变量和全局变量同名时,优先使用局部变量同名变量规则
存在多个同名变量时,优先使用最近定义的变量。下面看一段代码,感受一下:
#includeint var = 100; // 全局变量 void f(int var) // var <==> 局部变量 { var++; printf("var = %d\n", var); } int main() { int var = 10; // 局部变量 f(var); // f(10); printf("var = %d\n", var); // var = 10; return 0; } 下面为输出结果:
变量的作用域
变量的作用域指的是变量定义后的可访问范围不同变量的作用域可以有重叠 不同名变量在重叠作用域内可分别访问在重叠作用域内,只可访问最近定义的同名变量局部变量的作用域
代码块:从 { 开始到 } 结束的一段代码变量只能定义在代码块的开始处,即: { 之后,执行语句之前变量的作用域从定义开始到当前代码块结束当变量的作用域结束后,变量不可用 (无法直接访问)全局变量的作用域
全局作用域:可在程序的各个角落访问并使用文件作用域:只能在当前代码文件中访问并使用全局作用域:可在程序的各个角落访问并使用一文件作用域:只能在当前代码文件中访问并使用工程开发中,全局变量通常以 g_ 作为前缀命名(工程约定)下面看一段代码,感受一下:
#includeint var = 100; // 全局变量 int main() { int var = 10; // 局部变量 { int var = 1; // 局部变量 printf("var = %d\n", var); } printf("var = %d\n", var); // var = 10; return 0; } 下面为输出结果:
注意:存在多个同名变量时,优先使用最近定义的变量
小结
局部变量只能在当前函数中使用,全局变量可在任何地方使用当局部变量和全局变量同名时,优先使用局部变量变量的作用域可重叠,内层作用域覆盖外层作用域离开作用域后变量不可访问(无法继续使用)七、变量的作用域与生命期(下)
不同变量的物理存储区域
在现代计算机系统中,物理内存被分为不同区域区域不同,用途不同,不同种类的变量位于不同区域 全局数据区:存放全局变量,静态变量栈空间:存放函数参数,局部变量堆空间:用于动态创建变量生命期:变量从创建到销毁的时间(即:合法可用的时间)
不同变量的生命期
全局数据区中的变量
程序开始运行时创建,程序结束时被销毁,整个程序运行期合法可用栈空间中的变量
进入作用域时创建,离开作用域时销毁(自动销毁)局部变量在函数调用返回后销毁
下面看一段代码,感受一下变量生命期:
#includeint var = 1; void func() { printf("var = %d\n", var); } int main() { int var = 2; int i = 0; for(i=0; i<5; i++) { int var = 4; var += i; printf("var = %d\n", var); } func(); printf("var = %d\n", var); return 0; } 下面为输出结果:
这个例子充分展示了变量的生命周期,值得仔细体会。
作用域与生命期无本质联系
作用域规则是语法层面对变量是否可访问的规定
生命期是二进制层面上变量存在于内存中的时间
可能的情况
作用域外无法访问的变量,可能在其生命期中(静态局部变量)作用域内可访问的变量,可能已经被销毁(堆变量)生命期中的变量,可能无法访问(文件作用域全局变量)静态变量
static 是C语言中的关键字static 修饰的局部变量创建于全局数据区(拥有程序生命期)static 修饰的全局变量只有文件作用域(文件之外无法访问)static 局部变量只会初始化一次,作用域与普通变量无异变量的生命期由变量存储位置决定
static 将变量存储于全局数据区,默认初始化为0auto 将变量存储于栈空间,默认初始化为随机值register 将变量存储于寄存器,默认初始化为随机值不同类型变量示例
#includeint g_var = 1; static int g_sVar = 2; int main() { static int s_var = 3; auto int v = 4; register int rv = 5; printf("g_var = %d\n", g_var); printf("g_sVar = %d\n", g_sVar); printf("s_var = %d\n", s_var); printf("v = %d\n", v); printf("rv = %d\n", rv); return 0; } 下面为输出结果:
下面看一段代码,感受一下 static 关键词:
#includeint global; int func(int x) { static int s_var; // 全局数据区中的变量,默认初始化为 0 // 并且,只做一次初始化 s_var += x; return s_var; } int main() { int i = 0; for(i=1; i<=5; i++) { printf("func(%d) = %d\n", i, func(i)); } printf("func(0) = %d\n", func(0)); printf("global = %d\n", global); return 0; } 下面为输出结果:
这里注意:全局数据区中的变量,默认初始化为 0 ,并且,只做一次初始化
小结
变量生命期指变量合法可用的时间生命期是变量存在于内存中的时间作用域与生命期无本质联系作用域和生命期用于判断变量是否可访问
static auto(默认) register 局部变量 全局数据区 栈空间 寄存器(可能) 全局变量 全局数据区 --- --- 八、函数专题练习
题目:编写函数,将字符串转换为整型数
函数原型:
int str2int(char s[]);
参数:可以代表整型数的字符串
返回值:整型值
注意事项:
整型数可以存在符号位,如: "-12345"字符串本身可能不是一个合法整型数,如:"123xyz45"算法流程
上代码:
#includeint getNumber(char c) { int ret = -1; if( ("0" <= c) && (c <= "9") ) ret = c - "0"; return ret; } int str2int(char str[]) { int ret = 0; int sign = 0; int i = 0; if( getNumber(str[0]) != -1 ) { sign = 1; i = 0; } else if( str[0] == "+" ) { sign = 1; i = 1; } else if( str[0] == "-" ) { sign = -1; i = 1; } while( sign && str[i] ) { int n = getNumber(str[i]); if( n != -1 ) ret = ret * 10 + n; else break; i++; } ret = sign * ret; return ret; } int main() { printf("%d\n", str2int("123")); printf("%d\n", str2int("-12345")); printf("%d\n", str2int("567xyz89")); printf("%d\n", str2int("abc")); printf("%d\n", str2int("-xyz")); return 0; } 下面为输出结果:
九、递归函数简介
在程序设计中,将函数自调用称为递归调用
递归是一种数学上分而自治的思想
将原问题分解为规模较小的问题进行处理问题的分解是有限的(递归不能无限进行)递归模型的一般表示法
递归在程序设计中的应用
递归函数
函数体中存在自我调用的函数递归函数必须有递归出口(边界条件)函数的无限递归将导致程序崩溃递归思想的应用:
自然数列求和:sum( n ) = 1 +2 +3 + ... + n
斐波拉契数列:1,1,2,3,5,8,13,21,...
上代码:
#includeint sum(int n) { int ret = 0; if( n == 1 ) ret = 1; else ret = n + sum(n-1); return ret; } int fac(int n) { int ret = 0; if( n == 1 ) ret = 1; else if( n == 2 ) ret = 1; else if( n >= 3 ) ret = fac(n-1) + fac(n-2); else ret = -1; return ret; } int main() { int i = 0; printf("sum(1) = %d\n", sum(1)); printf("sum(10) = %d\n", sum(10)); printf("sum(100) = %d\n", sum(100)); for(i=1; i<=10; i++) { printf("%d, ", fac(i)); } printf("\n"); return 0; } 下面为输出结果:
小结
递归是一种数学上分而自治的思想递归程序中的表现为函数自调用递归解法必须要有边界条件,否则无解编写递归函数时不要陷入函数的执行细节到此这篇关于C语言从零探索函数的知识的文章就介绍到这了,更多相关C语言函数内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
上一篇:Python绘制简单散点图的方法
下一篇:Java中如何保证缓存一致性问题
X 关闭
X 关闭
- 15G资费不大降!三大运营商谁提供的5G网速最快?中国信通院给出答案
- 2联想拯救者Y70发布最新预告:售价2970元起 迄今最便宜的骁龙8+旗舰
- 3亚马逊开始大规模推广掌纹支付技术 顾客可使用“挥手付”结账
- 4现代和起亚上半年出口20万辆新能源汽车同比增长30.6%
- 5如何让居民5分钟使用到各种设施?沙特“线性城市”来了
- 6AMD实现连续8个季度的增长 季度营收首次突破60亿美元利润更是翻倍
- 7转转集团发布2022年二季度手机行情报告:二手市场“飘香”
- 8充电宝100Wh等于多少毫安?铁路旅客禁止、限制携带和托运物品目录
- 9好消息!京东与腾讯续签三年战略合作协议 加强技术创新与供应链服务
- 10名创优品拟通过香港IPO全球发售4100万股 全球发售所得款项有什么用处?