10.2.3 函数的返回值
函数调用后数能得到一个确定的值,这就是函数的返回值,返回值常常是一个计算的结
果,或是用来作为判断函数执行状态的标记。
函数返回值分为以下三种情况:
(1)无返回值类型:针对函数无返回值或明确不需返回值的情况,使用 void(即空类
型)表示。
(2)有返回值类型:指明具体的类型,比如,int、float、char 等。需要在函数体内与 return
语句搭配使用。
(3)如果返回值类型不是 void,但函数中没有 return 语句,则函数会返回一个不确定
的值。
#include<stdio.h>
//无返回值
void fun01()
{
printf("func1\n");
}
//有明确的返回值
double fun02()
{
return 3.14;
}
//函数返回一个不确定的值
int fun03()
{
10 + 20;
return 10 + 20;
}
//返回类型与return的值的类型不一致,可能导致精确损失
int fun04()
{
return 20.89;
}
int main()
{
fun01:
printf("%f\n", fun02());
printf("%d\n", fun03());
printf("%d\n", fun04());
return 0;
}
3.140000
30
20
10.2.4 函数的参数
函数的参数分为形参与实参:
形参:在定义函数时,函数名后面括号()中声明的变量称为形式参数,简称形参。
实参: 在调用函数时,函数名后面括号()中的使用的常量、变量、表达式称为实
际参数,简称实参。
注意:实参的数量要与形参的数量一致,否则报错。
#include<stdio.h>
//x 和 y即为形参
int func(int x, int y)
{
return x+y;
}
int main()
{
//3和5即实参
int sum = func(3, 5);
printf("%d\n", sum);//8
//如果实参数量如形参不一致,会报错
//func(100,299,300);
//func(100);
return 0;
}
8
10.3 主函数
1)主函数的作用
主函数是程序的入口函数,即所有的程序一定要包含一个主函数,程序总是从这个函数
开始执行,如果没有该函数,程序就无法启动。
主函数中可以调用其它函数,但其它函数不能反过来调用主函数,主函数也不能调用自
己。
2)主函数的返回值
C 语言约定,主函数返回值 0 表示运行成功,如果返回其它非零整数,就表示运行失败。
默认情况下,如果主函数里面省略 return 0 这一行,编译器会自动加上,即 main() 的默认
返回值为 0。但是为了保持统一的代码风格,不建议省略。
3)主函数的参数
主函数的声明中可以带有两个参数,格式如下:
int main(int argc , char*argv[])
{
//函数体
}
其中,形参 argc,全称是 argument count,表示传给程序的参数个数,其值至少是 1;
而 argv,全称是 argument value,argv[]是一个指针数组(12.3.2 小节会具体讲解),我们可
以暂时将 argv 理解为是一个数组,数组的每个元素都是字符串。
这种方式可以通过命令行的方式执行源代码,并接收指定的字符串传给参数 argv。
我们创建一个名为 demo.c 的源文件,代码如下:
#include<stdio.h>
int main(int argc, char* argv[])
{
printf("argc=%d\n", argc);
//函数体
for (int i = 0; i < argc; i++)
{
printf("%s\n", argv[i]);
printf("%s\n", argv[i]);
}
return 0;
}
10.4 函数原型
默认情况下,函数必须先声明,后使用。由于程序总是先运行 main() 函数,导致所有
其它函数都必须在 main() 函数之前声明。
如果想将函数声明写在后面,可以在程序开头处给出函数原型。函数原型,就是提前告
诉编译器,每个函数的返回类型和参数类型。其它信息都不需要,也不用包括函数体,具体
的函数实现可以后面再补上。
#include<stdio.h>
//使用原型函数进行声明
int twice(int);
//主函数
int main()
{
printf("%d\n", twice(100));
return 0;
}
// 函数定义
int twice(int num)
{
return 2*num;
}
200
D:\c-code\3-18\x64\Debug\3-18.exe (进程 32124)已退出,代码为 0 (0x0)。
按任意键关闭此窗口. . .
10.5 作用域
11.5.1 作用域的概念
作用域用于确定在代码中某个标识符(如变量、标识符常量、数组等)的可见性和访问
范围,它决定了在程序的哪些部分可以引用或访问该标识符。
作用域可以分为全局作用域、局部作用域、块级作用域。
同一个作用域中不能声明同名的标识符。
10.5.2 全局作用域
在函数和代码块(分支语句、循环语句等)以外定义的变量、标识符常量、数组等具有
全局作用域,在程序的任何地方都可以被访问,通常称它们为全局变量、全局常量、全局数
组等。
代码示例:
#include<stdio.h>
//全局变量
double money = 1.1;
//全局常量
const double Pi = 3.14;
//全局数组
char msg[] = "hello world";
//全局函数
void func()
{
printf("func函数中使用全局数据:\n");
printf("money=%.2f \n", money);
printf("PI=%.2f \n", Pi);
printf("msg=%s \n", msg);
printf("\n");
money += 100;
}
//主函数
int main()
{
//调用func()
func;
printf("主函数中使用全局数据:\n");
printf("money=%.2f \n", money);
printf("PI=%.2f \n", Pi);
printf("msg=%s \n", msg);
printf("\n");
//调用函数
func();
return 0;
}
主函数中使用全局数据:
money=1.10
PI=3.14
msg=hello worldfunc函数中使用全局数据:
money=1.10
PI=3.14
msg=hello world
对于全局变量,如果没有显式指定初始值,它们将自动初始化为零值。
对于全局数组,如果没有显式初始化它们,它们的元素将自动初始化为零值,字符数组,
将初始化为空字符 \0。
代码示例:
#include <stdio.h>
void add(int a)
{
// 局部变量
int b = 20;
// 局部常量
const double PI = 3.14;
// 局部数组
int nums[] = {10, 20, 30};
printf("(a+b+nums[0])*PI=%f \n", (a + b + nums[0]) * PI);
}
int main()
{
// 调用函数 add
add(100); // (a + b + nums[0]) * PI = 408.200000
// add 函数外部使用局部变量
// printf("%d \n", a); // 报错 'a' undeclared
// printf("%d \n", b); //报错 'b' undeclared
// printf("%f \n", PI); // 报错 'PI' undeclared
// printf("%d \n", nums[0]); // 报错 'nums' undeclared
return 0;
}
输出结果:
如果局部作用域中定义了与全局作用域中同名的标识符,优先使用本作用域中定义的数
据,相关代码示例如下:
#include <stdio.h>
// 全局变量
int a = 100;
int b = 200;
void add()
{
int a = 300;
a += 10;
b += 10;
printf("函数 add 内部: a=%d, b=%d \n", a, b);
}
int main()
{
// 调用函数 add
add(); // 函数 add 内部: a=310, b=210
printf("函数 add 外部: a=%d, b=%d \n", a, b); // 函数 add 外部:a = 100, b = 210
return 0;
}
函数 add 内部: a=310, b=210
函数 add 外部: a=100, b=210
与全局变量和全局数组不同,局部变量和局部数组如果没有显式初始化,它们的初始值
是机器垃圾值,即系统之前分配给这块空间的值。所以不对局部变量、局部数组初始化是不
安全且强烈不建议的。
代码示例:
#include <stdio.h>
int main()
{
// 定义局部变量不进行初始化赋值
int a;
// 定义局部数组不进行初始化
int arr[5];
// 输出局部变量
printf("a=%d \n", a);
// 计算数组长度
int len = sizeof arr / sizeof arr[0];
// 遍历数组 arr
for (int i = 0; i < len; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
a=16
130611760 130611760 130611760 130611760 130611760
10.5.4 块级作用域
块级作用域是 C99 标准引入的概念,在代码块(分支语句、循环语句等)中定义的变
量、标识符常量、数组等具有块级作用域,只有在该代码块内部才能被访问,代码块通常具
有花括号 {} 结构。
这些被称为块级变量、块级常量、块级数组等,同时也可以被称为局部变量、局部常量、
局部数组,且与函数中的局部变量、局部常量、局部数组具有相同的特性。
代码示例:
#include<stdio.h>
int main()
{
//该代码块中具有块级作用域
{
//块级变量
int a = 20;
//块级常量
const double PI = 3.14;
printf("a*PI=%f\n", a * PI);
}
//分支语句体具有块级作用域
if (1)
{
//局部数组
int nums[] = { 10,20,30 };
printf("%d %d %d \n", nums[0], nums[1], nums[2]);
}
//循环体语句中的标记变量i是块级变量
for (int i = 0; i < 5; i++)
{
printf("%d", i);
}
return 0;
}
a*PI=62.800000
10 20 30
01234
10.5.5 作用域和内存

(1)栈区域
局部变量、局部数组等通常存储在栈(Stack)区,这些局部数据的内存分配和释放是
自动管理的,它们的生命周期受限于其定义的函数或块级作用域,当函数返回或块级作用域
结束时,这些变量的内存会被自动释放。
函数每调用一次就创建一次局部数据,调用结束就销毁;下次调用再创建新的局部数据。
(2)全局静态区域
全局变量、全局数组等存储在全局静态区,这些全局的数据在程序的整个生命周期内都
存在,它们在程序启动时被分配,在程序结束时才被释放。
(3)变量生命周期演示
#include<stdio.h>
//定义全局变量
int len=5;
//定义全局数组
int arr[5]={10,20,30,40,50];
//定义函数
void fun(int num)
{
//定义局部变量
int a=250;
printf("%d\n",num+a);
}
//主函数
int main()
{
//调用函数
fun(20);
//遍历数组
for(int i=0, i<5:i++)
{
printf("%d,"arr[i]);
}
printf("\n");
//再次调用函数
fun(60);
return 0;
}
270
10 20 30 40 50
310
内存分析:

10.5.6 作用域练习题
(1)思考:下面的代码输出什么内容?
#include<Stdio.h>
double price = 200.0;
void test01()
{
printf("%.2f\n", price);
}
void test02()
{
price = 250.0;
printf("%.2f\n", price);
}
//main函数
int main()
{
printf("main price=%.2f\n", price);
test01();
test02();
test01();
return 0;
}
答案
main price=200.00
200.00
250.00
250.00
(2)思考:下面的代码输出什么内容?
#include<stdio.h>
int n = 10;
void func1()
{
int n = 20;
printf("func1 n:%d\n", n);
}
void func2()
{
printf("func2 n:%d\n", n);
}
void func3()
{
printf("func3 n:%d\n",n);
}
int main()
{
int n= 0;
func1();
func2();
func3();
{
int n = 40;
printf("block n: %d\n", n);
}
printf("main n:%d\n", n);
return 0;
}
答案:
func1 n:20
func2 n:10
func3 n:10
block n: 40
main n:0
10.6 static关键字
static 关键字可以声明静态变量和静态函数,以控制变量和函数的作用范围和生存周期。
10.6.1 静态局部变量

使用 static 关键字修饰的局部变量,称为静态局部变量,静态局部变量与全局变量一样
存储在内存中的全局静态区。静态局部变量具有如下特点:
(1)静态局部变量只在函数第一次调用时初始化一次并将生命周期延长到整个程序的
执行期间。
(2)静态局部变量如果声明时没有初始赋值,系统会自动初始化为零,同全局变量的
初始化规则一致。
代码示例:
#include <stdio.h>
void fn() {
int n = 10;
int a;
printf("n=%d, a=%d \n", n, a);
n++;
printf("n++=%d \n", n);
printf(" \n");
}
void fn_static() {
static int n = 10;
static int a;
printf("static n=%d, a=%d\n", n, a);
n++;
printf("static n++=%d \n", n);
printf(" \n");
}
int main() {
int a = 100;
fn();
fn_static();
fn();
fn_static();
return 0;
}
n=10, a=16
n++=11
static n=10, a=0
static n++=11
n=10, a=0
n++=11
static n=11, a=0
static n++=12
内存分析

11.6.2 静态全局变量
使用 static 关键字修饰的全局变量称为静态全局变量。
普通全局变量对整个工程可见,其他文件中,使用 extern 外部声明后,可以直接使用。
静态全局变量仅对当前文件可见,其他文件不可访问,其他文件中可以定义与其同名的变量,
两者互不影响。
静态全局变量对于需要编译多个源代码文件的程序,能够有效地降低源文件之间的耦合,
避免不同文件同名变量的冲突。
此外 static 关键字还可以修饰函数(静态函数)、全局数组、全局常量等,功能作用与
静态全局变量一致。
下面我们演示一个静态全局变量的案例,创建两个源文件,分别命名为 file01.c 和
file02.c,源文件代码如下:
【file01.c】
#include <stdio.h>
int num01 = 100;
static int num02 = 200;
const double PI01 = 3.14;
static const double PI02 = 3.15;
char msg01[] = "Hello msg01";
static char msg02[] = "Hello msg02";
void fn01()
{
printf("function fn01 \n");
}
static void fn02()
{
printf("function fn01 \n");
}
【file02c】
#include <stdio.h>
// 外部声明 file02.c 中定义的全局变量
extern int num01;
extern const double PI01;
extern char msg01[];
extern void fn01();
// 外部声明 file02.c 中定义的静态全局变量会报错
extern int num02;
extern const double PI02;
extern char msg02[];
extern void fn02();
int main()
{
// 使用 file02.c 中定义的数据
printf("%d \n", num01);
printf("%f \n", PI01);
printf("%s \n", msg01);
fn01();
printf("\n");
// 报错 无法获取 file01.c 中定义的全局变量
// printf("%d \n", num02);
// printf("%d \n", PI02);
// printf("%s \n", msg02);
// fn02();
return 0;
}
10.7 递归函数
10.7.1 递归函数概念
一个函数在函数体内又调用了本身,我们称为递归调用,这样的函数就是递归函数。
递归函数成功执行需满足以下两个条件:
(1)必须有一个明显的结束条件。
(2)必须有一个趋近于结束条件的趋势。
10.7.1 递归函数园路分析
#include<stdio.h>
void test(int n)
{
printf("%d\n",n);
if (n>1)
{
test(n - 1);
}
printf("%d\n", n);
}
int main()
{
//调用函数
test(3);
return 0;
}
3
2
1
1
2
原理分析:

10.7.3 递归函数案例
(1)案例一
1,1,2,3,5,8,13... 这是一个斐波那契数列,它的规则是第 1 个和第 2 个斐波那
契数是 1,从第 3 个数开始,每个斐波那契数都是前两个数之和。编写程序,输入一个整数,
计算它的斐波那契数?
#include <stdio.h>
int fib(int n)
{
if (n == 1 || n == 2)
{
return 1;
}
else
{
return fib(n - 1) + fib(n - 2);
}
}
int main()
{
// 定义变量,保存整数
int num;
// 获取用户输入
printf("请输入一个整数:");
scanf("%d", &num);
// 计算并输出
printf("%d 的斐波那契数是:%d", num, fib(num));
return 0;
}
(2)案例二
有一堆桃子,猴子第一天吃了其中的一半,并多吃一个。以后每天猴子都吃其中的一半,
然后再多吃一个。当到第十天时,想再吃时(注意:此时还没吃),发现只有 1 个桃子了。
问:最初共多少个桃子?
#include <stdio.h>
int peach(int day)
{
if (day == 10)
{
return 1;
}
else
{
return (peach(day + 1) + 1) * 2;
}
}
int main()
{
printf("第十天桃子的数量: %d \n", peach(10));
printf("第九天桃子的数量: %d \n", peach(9));
printf("第一天桃子的数量: %d \n", peach(1));
return 0;
}
第十天桃子的数量: 1
第九天桃子的数量: 4
第一天桃子的数量: 1534
10.8 常用系统函数
10.8.1 字符串相关函数
(1)<string.h> 头文件中的函数
标准库的头文件 <string.h> 中,有三个常用的字符串函数:

演示:
#include <stdio.h>
#include <string.h> // 该头文件中声明字符串相关的系统函数
int main()
{
// 声明两个字符串
char src[] = "abcdefg", dest[50] = "hello";
// strlen 获取字符串长度
printf("src 长度:%d \n", strlen(src));
printf("dest 长度:%d \n", strlen(dest));
printf("\n");
// 将 “world” 拷贝到 src, 拷贝字符串将原来的内容覆盖
strcpy(src, " world");
printf("src=%s \n", src);
printf("\n");
// strcat 是将 src 字符串的内容连接到 dest
strcat(dest, src);
printf("dest=%s", dest);
return 0;
}
src 长度:7
dest 长度:5
src= world
dest=hello world
(2)<stdio,h>头文件中的函数
标准库的头文件 <stdio.h> 中,也有两个字符串函数:
sprintf(),用于将格式化数据写入字符串。相比于 printf(),多了一个参数,第一个参数
是要写入的字符串,后面参数与 printf() 一致。简单地讲,sprintf() 是将内容写入字符串而
不是输出。
sscanf(),用于从一个字符串中按照指定的格式提取数据。相比于 scanf(),多了一个参
数,第一个参数是要提取数据的字符串,后面参数与 scanf() 一致。简单地讲,sscanf() 是
从字符串中提取数据而不是从用户输入提取数据。
代码示例:
#include <stdio.h>
int main()
{
// ------------- sprintf 的使用 ---------------
int age = 12;
double score = 65.5;
char name[] = "张三";
char outputString[100]; // 字符数组,用于存储格式化后的字符串
// 使用 sprintf 将数据格式化为字符串
sprintf(outputString, "我叫%s,今年%d 岁,成绩%.2f 分", name, age,
score);
// 输出格式化后的字符串
printf("%s\n", outputString);
// ------------- sscanf 的使用 ---------------
char inputString[] = "张三成绩:44,孙悟空成绩:65.5,张飞成绩:89";
float score1, score2, score3;
// 使用 sscanf 从字符串中提取数据
sscanf(inputString, "张三成绩:%f,孙悟空成绩:%f,张飞成绩:%f", &score1,
&score2, &score3);
// 输出提取的数据
printf("score1=%.2f, score2=%.2f, score3=%.2f", score1, score2,
score3);
return 0;
}
我叫张三,今年 12 岁,成绩 65.50 分
score1=44.00, score2=65.50, score3=89.00
10.8.2 日期时间相关函数
日期时间相关函数,在头文件 <time.h> 中。

注意:
time_t 是 C 语言中用于表示时间值的数据类型,它通常是一个整数类型(int 或 long
或 long long),用于存储时间戳。
时间戳是指从 1970 年 1 月 1 日(通常称为 UNIX 纪元)零时零分零秒(UTC 时间)起
至某一特定时刻所经过的秒数。
案例演示:
#include <stdio.h>
#include <time.h>
#include <string.h>
int main()
{
time_t curr_time;
time(&curr_time);
printf("当前时间戳: %lld \n", curr_time);
char time_str[26];
ctime_s(time_str, sizeof(time_str), &curr_time);
printf("当前时间字符串: %s \n", time_str);
return 0;
}
当前时间戳: 1742307714
当前时间字符串: Tue Mar 18 22:21:54 2025
接下来,我们使用 difftime() 函数来统计程序的执行时间,代码如下:
#include <stdio.h>
#include <time.h> // 该头文件中,声明和日期和时间相关的函数
int fn()
{
int sum = 0;
for (int i = 0; i < 10000000; i++)
{
sum = 0;
for (int j = 1; j <= 100; j++)
{
sum += j;
}
}
return sum;
}
int main()
{
time_t start_t, end_t;
time_t diff_t; // 存放时间差
time(&start_t); // 得到起始时间
fn(); // 运行 fn 函数,看看执行花费时间
time(&end_t); // 函数执行结束后得到时间
// 计算两个时间值的差
diff_t = difftime(end_t, start_t);
printf("执行 fn() 函数耗用了 %lld 秒", diff_t);
return 0;
}
执行 fn() 函数耗用了 1 秒
D:\c-code\3-18\x64\Debug\3-18.exe (进程 34640)已退出,代码为 0 (0x0)。
按任意键关闭此窗口. . .
10.8.3 数学相关函数
数学相关函数,在头文件 <math.h> 中。

注意:以上函数的参数与返回值的类型都是 double 类型。
案例演示:
#include <stdio.h>
#include <math.h>
int main()
{
printf("2 的绝对值:%.0f, -12.34 的绝对值:%.2f \n", fabs(2.0), fabs(-
12.34));
printf("10 的平方根:%f \n", sqrt(10));
printf("8 的立方根:%.f \n", cbrt(8));
printf("3 的 12 次方:%.f \n", pow(3, 12));
printf("12.31 向上取整:%.f,-12.31 向上取整:%.f \n", ceil(12.31),
ceil(-12.31));
printf("12.51 向下取整:%.f,-12.51 向下取整:%.f \n", floor(12.51),
floor(-12.51));
printf("12.51 四舍五入:%.f,-12.51 四舍五入:%.f \n", round(12.51),
round(-12.51));
printf("12.51 截断小数:%.f,-12.51 截断小数:%.f \n", trunc(12.51),
trunc(-12.51));
return 0;
}
2 的绝对值:2, -12.34 的绝对值:12.34
10 的平方根:3.162278
8 的立方根:2
3 的 12 次方:531441
12.31 向上取整:13,-12.31 向上取整:-12
12.51 向下取整:12,-12.51 向下取整:-13
12.51 四舍五入:13,-12.51 四舍五入:-13
12.51 截断小数:12,-12.51 截断小数:-12
10.9 课后练习
1)定义函数,实现求两个 double 数字的最大值,并返回。
(2)定义函数,求出三个 int 类型数的和,并返回。
(3)定义函数,判断一个数字是否是质数。
(4)函数可以没有返回值案例,编写一个函数,从终端输入一个整数打印出对应的金
字塔,函数可以传入金字塔层数。

1379

被折叠的 条评论
为什么被折叠?



