07函数
一、上节作业(重点)
1.
输入一个整型数,存入变量i,通过子函数change把主函数的变量i除2,然后打印i,例如如果输入的为10,打印出5,如果输入的为7,打印出3
#include <stdio.h>
void change(int *j)
{
*j=*j/2;
}
int main() {
int i;
scanf("%d",&i);
change(&i);
printf("%d",i);
return 0;
}
2. 输入一个整型数,然后申请对应大小空间内存,然后读取一个字符串(测试用例的字符串中含有空格),字符串的输入长度小于最初输入的整型数大小,最后输出输入的字符串即可(无需考虑输入的字符串过长,超过了内存大小);
#include <stdio.h>
#include <stdlib.h>
int main() {
//读取申请空间的大小
int i;
scanf("%d",&i);
//读取字符串并清除标准输入缓冲区中的换行
char c;
scanf("%c",&c);
//申请i个字节大小的空间,强制类型转换char*
char *p;
p=(char*)malloc(i);
fgets(p,i,stdin);//等价于gets(p);gets被去掉是因为不安全,会访问越界
puts(p);
return 0;
}
二、函数的声明与定义
1.函数的声明与定义
-
函数的调用:由主函数调用其他函数,其他函数可以相互调用。同一个函数可以被一个或多个函数调用任意次。
-
函数的声明:指把 函数的名字、函数类型及形参的类型、个数和顺序通知编译系统,以便在调用该函数时编译系统能正确识别函数并检查调用是否合法。如果不在头文件中对使用的函数进行声明,那么在编译时会出现警告。
-
函数的定义:指对函数功能的确立,包括指定 函数名、函数值类型、形参及其类型、函数体等,是一个完整的、独立的函数单位。
-
隐式声明:C语言中有几种声明的类型名可以省略,如函数如果不显示地声明返回值的类型,那么它默认返回整型;使用旧风格声明函数的形式参数时,如果省略参数的类型,那么编译器默认其为整型。隐式声明不是好习惯,显示声明能够清楚地表达意图。
-
例子
#include <stdio.h>
void print_message();//增加该声明即可解决
int main() {
print_message();
return 0;
}
void print_message()//函数定义
{
printf("Hello, World!\n");
}
-
结果
warning: conflicting types for 'print_message'//未声明print_message,则返回类型未知
Hello, World!//能正常出结果,但是在消息中有警告
-
CLion新建C源文件或头文件方法
![](https://img-blog.csdnimg.cn/direct/bacd2a6ad1c74360b4f24b2341653c03.png)
-
#include <stdio.h>:引用的是标准库中的头文件
-
#include "func.h":引用的是当前目录下自定义的头文件
-
例子
func.h //存放公共头文件+声明函数
#ifndef INC_1_FUNCTION_FUNC_H
#define INC_1_FUNCTION_FUNC_H
#include <stdio.h>
void print_message();//声明函数
int print_star(int i);//声明函数
#endif //INC_1_FUNCTION_FUNC_H
func.c //定义函数
#include "func.h"
void print_message()
{
printf("Hello, World!\n");
print_star(3);
}
int print_star(int i)
{
printf("**********************\n");
printf("printstar %d\n",i);
return i+3;
}
main.c
#include "func.h"
int main() {
int a=10;
a=print_star(a);
print_message();
print_star(a);
return 0;
}
-
结果
**********************
printstar 10
Hello, World!
**********************
printstar 3
**********************
printstar 13
-
CLion快捷点:ctrl+鼠标左键,可以跳转到对应函数查看源码
2.C语言编译和执行的特点
-
分别编写、分别编译,进而提高调试效率:一个C程序由一个或多个程序模块组成,每个程序模块作为一个源程序文件,对于较大的程序,通常将长须内容分别放在若干源文件中,再由若干源文件组成一个C程序。
-
一个源程序文件是一个编译单位:在程序编译时是以源程序文件为单位而不是以函数为单位进行编译。一个源程序文件由一个或多个函数及其他有关内容组成(如命令行、数据定义等)。
-
C程序的执行是从main函数开始的:如果main函数中调用其他函数,那么在调用后会返回到main函数中,在main函数中结束整个程序的运行。
-
所有函数都是平行的:在定义函数时是分别进行的,并且是相互独立的,一个函数并不从属于另一个函数,即函数不能嵌套定义。函数间可以相互调用(嵌套调用),但不能调用main函数,main函数是由系统调用的。
3.函数的分类与调用
-
用户角度分类:标准函数与自定义函数。
-
标准函数:即库函数,由系统提供,用户不必自己定义,可以直接使用。如printf、scanf等,不同的C系统提供的库函数的数量和功能会有一些不同。
-
自定义函数:用户自己定义的函数,用以解决用户的专门需要。
-
函数形式分类:无参函数与有参函数。
-
无参函数:一般用来执行指定的一组操作,在调用无参函数时,主调函数不向被调用函数传递数据。
类型标识符 函数名()
{
声明部分
语句部分
}
-
有参函数:主调函数在调用函数时,通过参数向被调用函数传递数据。
类型标识符 函数名(形式参数表列)
{
声明部分
语句部分
}
三、函数的递归调用
-
把函数自身调用自身的操作,成为递归函数。递归函数一定要有结束条件,否则会产生死循环。
-
递归的核心是找公式。
-
例子(求阶乘)
#include <stdio.h>
int f(int i)
{
if(i==1)
{
return 1;
}
return i*f(i-1);
}
int main() {
int i,res;
scanf("%d",&i);
res=f(i);
printf("f(%d)=%d\n",i,res);
return 0;
}
-
结果
4
f(4)=24
-
使用递归在解决一些问题时,可以让问题变得简单,降低变成的难度。
-
例子(上台阶,一次只能上1个台阶或2个台阶,上n个台阶有几种方法)
#include <stdio.h>
int step(int n)
{
if(n==1 || n==2)
{
return n;
}
return step(n-1)+step(n-2);
}
int main() {
int n,res;
scanf("%d",&n);
res=step(n);
printf("step(%d)=%d\n",n,res);
return 0;
}
-
结果
4
step(4)=5
四、局部变量与全局变量
1.全局变量解析
-
在不同函数之间传递数据时,可以使用的方法有:
(1)参数:通过形式参数和实际参数。
(2)返回值:用return语句返回计算结果。
(3)全局变量:外部变量。
-
例子
#include <stdio.h>
int i=10;//全局变量
void print(int a)
{
printf("print i=%d\n",i);
}
int main() {
printf("main i=%d\n",i);
i=5;//修改了全局变量
print(i);
return 0;
}
-
结果
main i=10
print i=5
-
例子
#include <stdio.h>
int i=10;
void print(int a)
{
printf("print i=%d\n",i);
}
int main() {
printf("main i=%d\n",i);
int i=5;//局部变量和全局变量重名
print(i);
return 0;
}
-
结果
main i=10
print i=10
-
原因:全局变量i存储在 数据段,main函数和print函数都是可见的。全局变量不会因为某个函数执行结束而消失,在整个进程的执行过程中始终有效。在函数内定义的变量都称为局部变量,局部变量存储在自己的函数对应的 栈空间内,函数执行结束后,函数内的局部变量所分配的空间将会得到释放。如果局部变量与全局变量重名,那么将采取就近原则,即实际获取和修改的值是局部变量的值。
![](https://img-blog.csdnimg.cn/direct/fd30e07bdfdc4ed49a8ece43de983186.png)
-
例子
#include <stdio.h>
int i=10;
void print(int i)
{
printf("print i=%d\n",i);
}
int main() {
printf("main i=%d\n",i);
int i=5;
print(i);
return 0;
}
-
结果
main i=10
print i=5
-
原因:形参类似于局部变量,也遵循就近原则。定义函数中指定的形参,如果没有函数调用,那么它并不占用内存中的存储单元,只有在发生函数调用时,函数printt中的形参才被分配内存单元。在调用结束后,形参所占的内存单元也会被释放。
-
实参可以是常量、变量或表达式,但要求它有确定的值。
-
在被定义的函数中,必须指定形参的类型。如果实参列表中包含多个实参,那么各参数间用逗号隔开,实参与形参的个数应相等,类型应匹配,且实参与形参应按顺序对应,一一传递数据。
-
实参与形参的类型应相同或赋值应兼容。
-
实参向形参的数据传递是单向值传递,只能由实参传递给形参,而不能由形参传回给实参,在调用函数时,给形参分配存储单元,并将实参对应的值传递给形参,调用结束后,形参单元被释放,实参单元仍保留并维持原值。
-
形参相当于局部变量,因此不能再定义局部变量与形参同名,否则会造成编译不不通。
2.局部变量与全局变量
-
在一个函数内部定义的变量称为内部变量,只在本函数范围内有效,即只有在本函数内才能使用这些变量,也称局部变量。
-
主函数中定义的变量只在主函数中有效,而不因为在主函数中定义而在整个文件或程序中有效,主函数也不能使用其他函数中定义的变量。
-
不同函数中可以使用相同名字的变量,代表不同的对象,互不干扰。
-
形式参数也是局部变量。
-
在一个函数内部,可以在复合语句中定义变量,这些变量只在本负责语句中有效,这种复合语句也称“分程序”或“程序块”。
-
例子
#include <stdio.h>
int i=10;
void print(int i)
{
printf("print i=%d\n",i);
}
int main() {
{
int j=10;
}
printf("main i=%d\n",i);
printf("main j=%d\n",j);
int i=5;
print(i);
return 0;
}
-
结果
error: 'j' undeclared (first use in this function)
-
原因:局部变量在其最近的花括号内有效,若离开花括号,则在其下面使用该变量会造成编译不通。
-
for循环的小括号内定义的int i,在离开for循环后,是不可以再次使用的。
-
例子
#include <stdio.h>
int i=10;
void print(int i)
{
printf("print i=%d\n",i);
}
int main() {
printf("main i=%d\n",i);
for(int k=0;k<-1;)
{
}
printf("main k=%d\n",k);
int i=5;
print(i);
return 0;
}
-
结果
error: 'k' undeclared (first use in this function)
-
函数之外定义的变量称为外部变量,外部变量可以为本文件中的其他函数共用,它的 有效范围是从定义变量的位置开始到本源文件结束,所以也称为全程变量。
-
全局变量在程序的全部执行过程中都占存储单元,而不是仅在需要时才开辟单元。
-
使用全局变量过多会降低程序的清晰性,在各个函数执行时都可能改变外部变量的值,程序容易出错,因此要有限制地使用全局变量。
-
因为函数在执行时依赖于其所在的外部变量,如果将一个函数移到另一个文件中,那么还要将有关的外部变量及其值一起移过去。然而,如果外部变量与其他文件的变量同名,那么就会出现问题,会降低程序的可靠性和通用性。
-
C语言一般要求把程序中的函数做成一个封闭体,除可以通过“实参→形参”的渠道与外界发生联系外,没有其他渠道。