【C语言】04-函数

一、函数的分类

前面已经说过,C语言中的函数就是面向对象中的"方法",C语言的函数可以大概分为3类:

1.主函数,也就是main函数。每个程序中只能有一个、也必须有一个主函数。无论主函数写在什么位置,C程序总是从主函数开始执行

2.开发人员自定义的函数,可有可无,数目不限

3.C语言提供的库函数,例如stdio.h中的输出函数printf()和输入函数scanf()

二、函数的声明和定义

虽说C中的函数类似于Java中的方法,但在使用上还是有区别的。

1.在Java中,每个方法的定义顺序没有限制,在前面定义的方法内部可以调用后面定义的方法

1 public void test() {
2      int c = sum(1, 4);
3  }
4
5  public int sum(int a, int b) {
6      return a + b;
7  }

第1行定义的test方法可以调用在第5行定义的sum方法

2.在标准C语言中,函数的定义顺序是有讲究的,默认情况下,只有后面定义的函数才可以调用前面定义过的函数

1 int sum(int a, int b) {
2      return a + b;
3  }
4
5  int main()
6  {
7      int c = sum(1, 4);
8      return 0;
9  }

第5行定义的main函数调用了第1行的sum函数,这是合法的。如果调换下sum函数和main函数的顺序,在标准的C编译器环境下是不合法的(不过在Xcode中只是警告,Xcode中用的是GCC编译器)

3.如果想把其他函数的定义写在main函数后面,而且main函数能正常调用这些函数,那就必须在main函数前面作一下函数的声明

 1 // 只是做个函数声明,并不用实现
 2  int sum(int a, int b);
 3
 4  int main()
 5  {
 6      int c = sum(1, 4);
 7      return 0;
 8  }
 9
10  // 函数的定义(实现)
11  int sum(int a, int b) {
12      return a + b;
13  }

我们在第2行做了sum函数的声明,然后在第6行(main函数中)就可以正常调用sum函数了。

函数的声明格式:

返回值类型  函数名 (参数1, 参数2, ...)

可以省略参数名称,比如上面的sum函数声明可以写成这样:

int sum(int, int);

只要你在main函数前面声明过一个函数,main函数就知道这个函数的存在,就可以调用这个函数。究竟这个函数是做什么用,还要看函数的定义。如果只有函数的声明,而没有函数的定义,那么程序将会在链接时出错。

4.在大型的C程序中,为了分模块进行开发,一般会将函数的声明和定义(即实现)分别放在2个文件中,函数声明放在.h头文件中,函数定义放在.c源文件中

下面我们将sum函数的声明和定义分别放在sum.h和sum.c中

sum.h文件

sum.c文件

然后在main.c中包含sum.h即可使用sum函数

其实sum.h和sum.c的文件名不一样要相同,可以随便写,只要文件名是合法的

运行步骤分析:

1> 在编译之前,预编译器会将sum.h文件中的内容拷贝到main.c中

2> 接着编译main.c和sum.c两个源文件,生成目标文件main.obj和sum.obj,这2个文件是不能被单独执行的,原因很简单:

* sum.obj中不存在main函数,肯定不可以被执行

* main.obj中虽然有main函数,但是它在main函数中调用了一个sum函数,而sum函数的定义却存在于sum.obj中,因此main.obj依赖于sum.obj

3> 把main.obj、sum.obj链接在一起,生成可执行文件

4> 运行程序

说到这里,有人可能有疑惑:可不可以在main.c中包含sum.c文件,不要sum.h文件了?

大家都知道#include的功能是拷贝内容,因此上面的代码等效于:

这么一看,语法上是绝对没有问题的,但是绝对运行不起来,在链接时会出错。原因:编译器会编译所有的.c源文件,这里包括main.c、sum.c,编译成功后生成sum.obj、main.obj文件,当链接这两个文件时链接器会发现sum.obj和main.obj里面都有sum函数的定义,于是报"标识符重复"的错误。

有人可能觉得分出sum.h和sum.c文件的这种做法好傻B,好端端多出2个文件,你把所有的东西都写到main.c不就可以了么?

  • 没错,整个C程序的代码是可以都写在main.c中。但是,如果项目做得很大,你可以想象得到,main.c这个文件会有多么庞大,会严重降低开发和调试效率。
  • 要想出色地完成一个大项目,需要一个团队的合作,不是一个人就可以搞的定的。如果把所有的代码都写在main.c中,那就导致代码冲突,因为整个团队的开发人员都在修改main.c文件,张三修改的代码很有可能会抹掉李四之前添加的代码。
  • 正常的模式应该是这样:假设张三负责编写main函数,李四负责编写一系列的自定义函数,张三需要用到李四编写的某个函数,怎么办呢?李四可以将所有的函数声明在一个.h文件中,比如lisi.h,然后张三在他自己的代码中包含lisi.h文件,接着就可以调用lisi.h中声明的函数了,而李四呢,可以独立地在另外一个文件中(比如lisi.c)编写函数的定义,实现那些在lisi.h中声明的函数。这样子,张三和李四就可以相互协作、不会冲突。

三、函数的形参和实参

在定义函数时,函数名后面的()中定义的变量称为形式参数(形参);在调用函数时传入的值称为实际参数(实参)。

// b是test函数的形参(形式参数)
void test(int b) {
    b = 9; // 改变了形参b的值
}

int main()
{
    int a = 10;
    printf("函数调用前的a:%d\n", a);

    test(a); // a是test函数的实参(实际参数)

    printf("函数调用后的a:%d", a);
    return 0;
}

如果是基本数据类型作为函数的形参,那是简单的值传递,将实参a的值赋值给了形参b,相当于

int a = 10;
int b = a;
b = 9;

a和b是分别有着不同内存地址的2个变量,因此改变了形参b的值,并不会影响实参a的值。

上述代码的输出结果为:

时间: 2025-01-02 03:04:25

【C语言】04-函数的相关文章

C语言04函数与递归

1 函数的定义 1.1 问题 自定义两个简单的函数,使用return返回数据. 1.2 步骤 实现此案例需要按照如下步骤进行. 步骤一:函数的定义 代码如下所示: #include <stdio.h> int getInt() { return 10; } double getDouble() { return 5.5; } int main() { int n; n = getInt(); printf ("%d\n", n); double d; d = getDoub

Android C语言_init函数和constructor属性及.init/.init_array节探索

本篇文章主要介绍了"Android C语言_init函数和constructor属性及.init/.init_array节探索",主要涉及到Android C语言_init函数和constructor属性及.init/.init_array节探索方面的内容,对于Android C语言_init函数和constructor属性及.init/.init_array节探索感兴趣的同学可以参考一下. 了解C语言的程序猿都知道有两种方法可以让一部分代码在so或可执行文件被加载的时候先于其它任何函

黑 马 程 序 员_视频学习总结&lt;C语言&gt;----04 预处理指令

---------------------- ASP.Net+Unity开发..Net培训.期待与您交流! ---------------------- 一.预处理指令简介 1.C语言在对源程序进行编译之前,会先对一些特殊的预处理指令作解释(比如之前使用的#include文件包含指令),产生一个新的源程序(这个过程称为编译预处理),之后再进行通常的编译 2.为了区分预处理指令和一般的C语句,所有预处理指令都以符号"#"开头,并且结尾不用分号 3.预处理指令可以出现在程序的任何位置,它的

[C/C++基础] C语言常用函数sprintf和snprintf的使用方法

Sprintf 函数声明:int sprintf(char *buffer, const char *format [, argument1, argument2, -]) 用途:将一段数据写入以地址buffer开始的字符串缓冲区 所属库文件: <stdio.h> 参数:(1)buffer,将要写入数据的起始地址:(2)format,写入数据的格式:(3)argument:要写的数据,可以是任何格式的. 返回值:实际写入的字符串长度 说明:此函数需要注意缓冲区buffer溢出,要为写入的arg

C语言strlen()函数用法

C语言strlen()函数:返回字符串的长度 头文件:#include <string.h> strlen()函数用来计算字符串的长度,其原型为:unsigned int strlen (char *s);  s为指定的字符串 eg: #include<stdio.h> #include<string.h> int main() { char *str1 = "http://see.xidian.edu.cn/cpp/u/shipin/"; char

C语言_函数

函数 1.函数构成 int main(int argc, const char * argv[]) { // insert code here... return 0; } 返回值类型  函数名(函数参数) { 函数体语句: return 返回值: } 2.函数分类 1)标准函数 由官方或第三方库提供的函数,可以直接调用,无需实现: 2)自定义函数 由程序员自己实现的函数: 3.编写函数的步骤 1)声明函数 写在头文件 2)实现函数 写在原文件 3)调用函数 C阶段,一般在主函数调用 [注意事项

C语言学习-函数和递归函数

C源程序是由函数组成的,有且只有一个主函数(main()函数). 一.函数 1.自定义函数的书写格式: 返回值类型 函数名(参数类型 形式参数1,参数类型 形式参数2,…) { 函数体 }例如: 1 include <stdio.h> 2 void test(); 3 4 int main() { 5 test(); 6 return 0; 7 } 8 9 void test() { 10 11 printf("hello world!"); 12 13 } 2.定义函数的

C语言基础 函数--思想的体现

1.C语言由函数组成,main函数又称主函数,是程序的入口. 1.1函数定义:    1.1.1    格式:        返回值类型 函数名(形式参数列表)        {            函数体        }     1.1.2    函数名:  不允许相同 1.1.3    参数注意点:        1.形式参数: 定义函数时函数名后面括号中的参数,简称形参        2.实际参数: 调用函数时传入的具体数据,简称实参        3.实参个数必须等于形参个数    

C语言中函数和指针的参数传递

最近写二叉树的数据结构实验,想用一个没有返回值的函数来创建一个树,发现这个树就是建立不起来,那么我就用这个例子讨论一下c语言中指针作为形参的函数中传递中隐藏的东西. 大家知道C++中有引用的概念,两个数据引用同一个数据,那么更改任意的一个都相当于更改了本体,那么另一个数据所对应的值也会改变,可是C中是没有这个概念的.所以就产生了一些东西.和我们本来想的有差别. 一.明确C语言中函数的入口: C语言中函数的形参负责接收外部数据,那么数据究竟怎么进入函数的呢,其实我们在函数体内操作的形参只是传递进来

从头开始-05.C语言中函数

函数: 完成特定功能代码段 特点:函数只有调用的时候才会执行 定义格式:返回值类型 函数名称(形参类型 形参名称,...){ 语句; ...  } 函数参数 形式参数:在定义函数的时候,函数名后面小括号中的参数 实际参数:在函数调用的时候,函数名后面小括号中的参数 注意点: 形参与实参必须一一对应 实参与形参的类型必须一致 基本数据类型作为函数参数传递是值传递 组作为函数参数传递是地址传递 指针作为函数参数传递是地址传递 结构体作为函数参数传递是值传递 返回值: return 作用: 1.结束函