C语言基础(函数)

devtools/2025/3/21 19:32:29/

函数的概述

  • 函数:实现一定功能的,独立的代码模块。对于函数的使用,一定是先定义,后使用
  • 使用函数的优势:
        ① 我们可以通过函数提供功能给别人使用。当然我们也可以使用别人提供的函数,减少代码
        量。
        ②借助函数可以减少重复性的代码。
        ③ 实现结构化(模块化) 程序设计思想。
        关于 结构化设计思想 :将大型的任务功能划分为相互独立的小型的任务模块来设计。
  • 函数是C语言程序的基本组成单元:
        C语言程序是由一个(必然是 main 函数)或多个函数组成。

函数的分类

  • 从函数实现的角度:
                库函数:C语言标准库实现的并提供使用的函数,比如说: scanf() printf() fgets()
fputs() strlen()
                自定义函数:需要程序员自行实现,开发中大部分函数都是自定义函数。
  • 从函数形式的角度:
                无参函数:函数调用时,无需传递,可有可无返回值,举例:show_all();
                有参函数:函数调用时,需要参数传递数据,经常需要配套返回值来使用,举例:
                printf("%d\n",12);
  • 从函数调用的角度:
                主调函数:主动去调用其他函数的函数。(main函数只能作为主调函数)
                被调函数:被其他函数调用的函数。

举例:

// 主调函数
int main()
{
// 被调函数
printf("hello world!\n");
}

很多时候,尤其是对于自定义函数来说,一个函数有可能既是主调函数,又是被调函数。

函数的定义

定义

语法:

[返回类型] 函数名([形参列表])    -- 函数头 | 函数首部
{函数体语句;                 -- 函数体,整个{}包裹的内容包括返回值都属于函数体,{}不能省略
}

函数头:

  • 返回类型:函数返回值的类型

  • 函数名:函数的名称,遵循标识符命名(不能以数字开头,只能包含字母、数字、下划线,建议:小写+下划线命名,举例:show_all())

  • 形参列表:用于接收主调函数传递的数据,如果有多个参数,使用“,”分隔,且每一个形参都需要指明类型。

小贴士:
形参列表(被调函数):主调函数给被调函数传递数据,主调函数 → 被调函数
返回类型(被调函数):被调函数给主调函数返回数据,被调函数 → 主调函数

说明:

  • 函数的返回类型:就是返回值的类型,两个类型可以不同,但是必须能够进行转换。举例:
double fun_a() // 函数的返回类型是double
{return 12; // 函数的返回值是int
}
以上代码可以理解为:将 int 类型的 12 赋值给一个 double 类型的匿名变量( int → double ),此
时属于隐式转换。
int fun_a() // 函数的返回类型是double
{return 12.5; // 函数的返回值是int
}
  • C语言中还可以定义无类型(void类型)的函数,这种函数不返回函数值(没有返回值),只 是完成某种功能,举例:
void test()// 此时这个函数,没有返回值,也就是它的返回值是 return;
{printf("hello\n");
}
// 下面写法等价于上面写法
void test()
{return; // 一般,这个return是省略不写的。
}
  • C语言中,函数的返回类型标识符是可以省略的,如果省略,默认返回int,举例:
// 写法1:main的返回类型是int类型,默认的返回值是0,等价于 写法2
main()
{...
}
// 写法2:main的返回类型是int类型,返回值是0
int main()
{return 0;// 0:逻辑真,-1:逻辑假,支持非0表示
}
  • 函数中返回语句的形式为 return(表达式) 或者 return 表达式
// 写法1
int main()
{return(0);
}
// 写法2 完全等价于写法1
int main()
{return 0;
}
  • 如果 参数列表 中有多个形式参数,则它们之间要用“,”分隔;即使它们的类型相同,在形式参数 中只能逐个进行说明,举例:
// 正确示例
int avg(int x, int y, int z)
{...
}
// 错误示例
int avg(int x, y, z)
{...
}
  • 如果 形参列表 中没有参数,我们可以不写,也可以用void标识,举例:
// 写法1 推荐
int main()
{...
}
// 写法2
int main(void)
{...
}

形参和实参

函数定义时指定的参数,形参是用来接收数据的,函数定义时,系统不会为形参申请内存,只有当
函数调用时,系统才会为形参申请内存。主要用于存储实际参数,并且当函数返回时(执行
return),系统会自动回收为形参申请的内存资源。

实参(实际参数)

定义

  • 函数调用时主调函数传递的数据参数(字面量、符号常量、表达式...,只要有确定的值),实参 就是传递的数据。
  • 实参和形参必须类型相同,如果不同时,按赋值规定进行类型转换,比如隐式转换。
  • C语言中,参数传递必须遵循 单向值传递 (通过实参给形参赋值),实参只是将自身的值传递 给了形参,而不是实参本身。形参的值的改变不会影响实参。
int fun(int n) // n 是形参
{printf("%d\n",n); // n = 10 n = 12
}
int main()
{
int a = 10; // a = 10
fun(a); // a 是实参,此时传递的数据是 10 实参是变量
fun(12); // 字面量12就是实参,此时传递的数据是 12 实参是常量
fun(a + 12); // 此时传递的数据是 22 实参是表达式
  • 实参与形参在内存中占据不同的内存空间。

函数的返回值

  • 若不需要返回值,函数可以没有return语句。
// 如果返回类型是void,return关键字可以省略
void fun1
{
}
// 这种写法,return关键字也可以省略,但是此时默认返回是 return 0
int fun2()
{
}
// 这种写法,return关键字也可以省略,但是此时默认返回是 return 0
fun3() // 如果不写返回类型,默认返回int
{
}
  • 一个函数中可以有多个return语句,但任一时刻只有一个return语句被执行
int eq(int num)
{if (num % 2 == 0){printf("%d是偶数\n",num);return 0;// 第一次出现}printf("%d是奇数\n",num);return 0; //第二次出现
}
  • 返回值类型一般情况下要和函数中return语句返回的数据类型一致,如果不一致,以函数定义 时指定的返回值类型为标准。

函数的调用

调用的方式

①函数语句

test();                //对于无返回值的函数,直接调用
int res=max(2,4);     //对于有返回值的函数,一般需要在主调函数中接收被调函数的返回值

在一个函数中调用另一个函数具备以下条件: ①被调用的函数必须是己经定义的函数。

②若使用库函数,应在本文件开头用#include包含其对应的头文件。

③若使用自定义函数,自定义函数又在主调函数的后面,则应在主调函数中对被调函数进行声明。声明的作用是把函数名、函数参数的个数和类型等信息通知编译系统,以便于在遇到函数时,编译系统能正确识别函数,并检查函数调用的 合法性。

函数的声明

函数调用时,往往要遵循先定义后使用,,但如果我们对函数的调用操作出现在函数定义之前,则需要对函数进行声明。

定义:

完整的函数分为三部分:

  • 函数声明
int max(int x,int y);   //指明参数名称
int max(int,int);       //省略参数名称

函数声明如果是在同一个文件,一定要定义在文件中所有函数定义的最前面。如果有对应的山文件,可以将函数的 声明抽取到h中。

  • 函数定义
int max(int x,int y,double z)
{return x >y x : y > z (int) ? y : (int) z;
}

函数定义的时候,不能省略参数的数据类型,参数个数,参数名称,位置要和函数声明完全一致。

  • 函数调用
int main()
{printf("%d\n",max(4,5,6));
}

函数声明的作用:

  • 是把函数名、函数参数的个数、函数参数的类型和返回类型等信息通知给编译系统,以便于在遇到函数调用时,编译系统能正确识别函数,并检查函数调用的合法性(C语言规范)。

使用

错误演示:被调函数写在主调函数之后

//主调函数
int main()
{printf("%d\n",add(12,13));  //编译会报错
}
//被调函数
int add(int x,int y)
{return x y;
}

注意:如果函数的调用比较简单,并且被调函数写在主调函数之前,此时是可以省略函数声明的。

正确演示:被调函数和主调函数无法区分前后,需要增加被调函数的函数声明

//函数声明
//int add(int x, int y);
int add(int, int);//主调函数
int main()
{printf("%d\n",add(12,13));  //编译会报错
}//被调函数
int add(int x,int y)
{return x y;
}

注意:如果涉及函数的相互嵌套调用,或者复杂嵌套调用,此时是无法区分函数的前后的,这就需要函数声明。

声明的方式:

  • 函数头加上分号

int add(int a,int b);
  • 函数头加上分号,可省略形参名称,但不能省略参数类型

 int add(int,int);

函数的嵌套调用

函数不允许嵌套定义,但允许嵌套调用

  • 正确:函数嵌套调用:
void a(){..}
void b()
{a(0;
}
  • 错误:函数嵌套定义
void a()
{void b(){}
}

嵌套调用:在被调函数内又主动去调用其他函数,这样的函数调用形式,称之为嵌套调用。

函数的递归调用

递归调用的含义:在一个函数中,直接或间接调用了函数自身,就称之为函数的递归调用。本质上还是函数的嵌套调用。

递归调用的本质

是一种循环结构,它不同于我们之前学的while、for、do.while这样的循环结构,这些循环结构是借助于循环变量;而递归调用时利用函数自身实现循环结构,如果不加以控制,很容易产生死循环。

递归调用的注意事项

①递归调用必须要有出口,一定要想办法终止递归(否则就会产生死循环)

②对终止条件的判断一定要放在函数递归之前(先判断,再执行)

③进行函数的递归调用。

④函数递归的同时一定要将函数调用向出口逼近。

数组做函数参数

定义

当用数组做函数的实参时,则形参应该也要用数组或者指针变量来接收(函数实参是数组,形参一
定是数组或者指着),注意的是,此次传递的并不代表传递了数组中所有的元素数据,而是传递了
第一个元素的内存地址(数组首地址),形参接收到这个地址后,则形参和实参就代表了同一个内
存空间,则形参的数据修改会改变实参。这种数据传递方式称之为地址传递

 如果用数组作为函数的形参,那么我们提供另一个形参表示数组的元素个数。原因是数组形参代
表的仅仅是实际数组的首地址。也就是说形参只获取到了实参数组的第一个元素的地址,并不确定
传递了多少个数据。所以提供另一个形参表示数组元素的容量,可以防止在被调函数对实际数组访
问时产生的下标越界。

但有一个例外,如果是用字符数组做形参,且实参数组中存放的是字符串数据(形参是字符数组,
实参是字符串常量)。则不用表示数组元素个数的形参,原因是字符串本身会添加自动结束标志
\n ,举例
#include <stdio.h>
// 定义一个函数,传递一个字符串
void fun(char arr[])
{char c;int i = 0;while((c = arr[i]) != '\0'){printf("%c",c);i++;}printf("\n");
}
void main()
{
fun("hello");
}

变量的作用域

变量的作用域

概念:变量的作用范围,也就是说变量在什么范围有效。

变量的分类

根据变量的作用域不同,变量可以分为:

全局变量

举例:
 

int num1; // 定义在所有函数之外的变量,称之为全局变量,num1能被fun1,fun2,main共同访问
void fun1(){}
int num2; // 定义在所有函数之外的变量,称之为全局变量,num2能被fun2,main共同访问
void fun2(){}
void main(){}
int num3; // 定义在所有函数之外的变量,称之为全局变量,num3不能被任何函数访问

局部变量:

 使用全局变量的优缺点:

优点:

1. 利用全局变量可以实现一个函数对外输出的多个结果数据。
2. 利用全局变量可以减少函数形参的个数,从而降低内存消耗,以及因为形参传递带来的时间消
耗。
缺点:
1. 全局变量在程序的整个运行期间,始终占据内存空间,会引起资源消耗。
2. 过多的全局变量会引起程序的混乱,操作程序结果错误。
3. 降低程序的通用性,特别是当我们进行函数移植时,不仅仅要移植函数,还要考虑全局变量。
4. 违反了 高内聚,低耦合 的程序设计原则

总结:我们发现弊大于利,建议尽量减少对全局变量的使用,函数之间要产生联系,仅通过实参
+ 形参的方式产生联系。

作用域举例:

注意:

如果全局变量(外部变量)和局部变量同名,程序执行的时候,就近原则(区分作用域)

int a = 10;// 全局变量
int main()
{int a = 20;// 局部变量printf("%d\n",a); // 20 就近原则for (int a = 0; a < 5; a++) // 这里的a是块作用域,一旦出了for循环,就不能再访问{printf("a=%d ",a); // 0 1 2 3 4 就近原则}printf("%d\n",a); // 20
}

变量的生命周期

定义:

概念:变量在程序运行中的存在时间(内存申请到内存释放的时间)。
根据变量存在的时间不同,变量可分为静态存储方式和动态存储方式。

变量的存储类型
变量的完整定义格式:[存储类型] 数据类型 变量列表;

http://www.ppmy.cn/devtools/168546.html

相关文章

《模型思维》第二十章 “空间竞争模型与享受竞争模型” 总结

《模型思维》第二十章 “空间竞争模型与享受竞争模型” 的核心内容总结&#xff0c;结合斯科特佩奇的核心观点与逻辑框架&#xff1a; 1. 两类模型的本质与核心思想 &#xff08;1&#xff09;空间竞争模型&#xff08;Spatial Competition&#xff09; 定义&#xff1a;基于地…

算法每日一练 (14)

&#x1f4a2;欢迎来到张胤尘的技术站 &#x1f4a5;技术如江河&#xff0c;汇聚众志成。代码似星辰&#xff0c;照亮行征程。开源精神长&#xff0c;传承永不忘。携手共前行&#xff0c;未来更辉煌&#x1f4a5; 文章目录 算法每日一练 (14)斐波那契数题目描述解题思路解题代码…

AI大模型:(一)1.大模型的发展与局限

说起AI大模型不得不说下机器学习的发展史&#xff0c;机器学习包括传统机器学习、深度学习&#xff0c;而大模型&#xff08;Large Models&#xff09;属于机器学习中的深度学习&#xff08;Deep Learning&#xff09;领域&#xff0c;具体来说&#xff0c;它们通常基于神经网络…

解决MySQL 5.6升5.7之后,非空有默认值的字段,报cannot be null Column ‘xxx‘的问题

背景 项目需要升级MySQL版本&#xff0c;打算5.6升5.7&#xff0c;升级后发现原本正常的部分SQL&#xff0c;不能正常工作。 字段定义为 created_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP在之前的使用中&#xff0c;有的同事代码不规范&#xff0c;向此字段插入时…

LeetCode 1105. 填充书架

LeetCode 1105. 填充书架 题目描述 给定一个数组 books&#xff0c;其中 books[i] [thicknessi, heighti] 表示第 i 本书的厚度和高度。你也会得到一个整数 shelfWidth&#xff0c;表示书架的总宽度。 按顺序将这些书摆放到总宽度为 shelfWidth 的书架上。你可以先选择几本…

如何在 Github 上获得 1000 star?

作为程序员&#xff0c;Github 是第一个绕不开的网站。我们每天都在上面享受着开源带来的便利&#xff0c;我相信很多同学也想自己做一个开源项目&#xff0c;从而获得大家的关注。然而&#xff0c;理想很丰满&#xff0c;现实却是开发了很久的项目仍然无人问津。 最近&#x…

在使用mybatis时遇到枚举的相关问题和解决

目录 1.前言 2.问题解决 2.1 在父依赖中添加版本管理 2.2 微服务中引入依赖 2.3 在application.yaml中进行配置 2.4 在枚举中添加注解 1.前言 今天在使用mybatis的时候&#xff0c;如下SQL查询遇到了报错(其中status为枚举类型&#xff0c;数据库中存的为整形)&#xf…

排序算法-选择排序

选择排序的思路 基本思路步骤&#xff1a; 遍历数组&#xff1a; 从数组的起始位置开始&#xff0c;将第一个元素视为当前最小&#xff08;或最大&#xff09;的元素。 找到最小&#xff08;或最大&#xff09;元素&#xff1a; 遍历未排序的部分&#xff0c;找到比当前最小&a…