目录
前言
标准库:C语言的百宝箱
头文件:库函数的藏宝图
实例分析:计算平方根的sqrt函数
功能描述
头文件包含的重要性
库函数文档的一般格式
自定义函数:释放你的编程创造力
函数的语法形式
函数的比喻
函数的举例
简化函数定义
形参和实参
形参
实参
实参和形参的关系
return 语句
数组做函数参数
嵌套调⽤和链式访问
嵌套调⽤
链式访问
函数的声明和定义
单个⽂件
多个文件
static 和 extern
static 修饰局部变量:
结论
static 修饰全局变量
static 修饰函数
前言
在数学的世界里,我们早已邂逅了函数这一概念,例如那条简洁的直线方程 y=kx+b,其中 k 和 b 是常数。只要你给出一个 x 值,它就能准确无误地返回对应的 y 值。这一概念在 C 语言的编程领域同样适用,函数(有时也被称作子程序)在这里扮演着至关重要的角色。在 C 语言中,函数是一段执行特定任务的代码,它遵循特定的编写规则和调用方式。
想象一下,一个复杂的软件程序,就像一座由无数块乐高积木搭建的城堡。每一块积木,就好比一个函数,负责完成一个具体的小任务。通过这种方式,庞大的计算任务被巧妙地分解成一系列较小、更易于管理的函数,每个函数都专注于自己的职责。这种模块化的方法不仅让程序更加清晰,也极大地提高了开发效率,因为一旦编写完成,这些函数就可以在不同的程序中重复使用。
在 C 语言的浩瀚星河中,我们主要会遇到两类函数:
-
库函数:这些是编程世界中已经预先构建好的“工具箱”,由经验丰富的开发者们精心打造,供我们直接使用。它们就像是我们编程工具箱中的瑞士军刀,包含了各种常用的功能,如输入输出、数学计算等。
-
自定义函数:这些则是我们根据特定需求亲自编写的函数。它们就像是我们为解决特定问题而定制的工具,提供了更高的灵活性和针对性。
标准库:C语言的百宝箱
C语言本身定义了一套严谨的语法规则,但它并不直接提供具体的功能实现——这便是标准库大显身手的地方。ANSI C国际标准为常用的功能设定了标准,而各编译器厂商则根据这些标准,提供了丰富的函数实现,这些预定义的函数就是我们所说的库函数。
想象一下,
printf
和scanf
这些我们耳熟能详的函数,就像是程序员的工具箱中现成的锤子和锯子,我们无需从零开始打造这些工具,只需了解如何使用它们,就能直接投入到编程的创作中。这不仅极大地提升了开发效率,也因为这些库函数经过了严格的测试和优化,其质量和执行效率都得到了保证。头文件:库函数的藏宝图
在C语言的宝库中,库函数被精心分类并存储在不同的头文件中,每个头文件都像是一张藏宝图,指引我们找到所需的函数和类型定义。无论是处理数学运算的
<math.h>
,还是操作字符串的<string.h>
,或是处理日期和时间的<time.h>
,这些头文件中都包含了丰富的函数和类型声明。点击这里,你将找到一份完整的C标准库头文件列表,它涵盖了从基础数学运算到复杂的多线程处理,无所不包。
学习库函数不必急于一时,我们可以一步步来,逐个掌握。随着对这些头文件的深入了解,你会发现,它们不仅是代码的组成部分,更是提升编程技艺的宝贵资源。
- C/C++官方文档:提供了全面的库函数参考,是学习和查阅库函数的权威资源。
- C标准库头文件
- cplusplus.com:提供了C语言库的详细描述和示例,适合快速查找和学习。
- C library - C++ Reference
实例分析:计算平方根的sqrt
函数
让我们以sqrt
函数为例,探讨如何使用库函数。sqrt
函数用于计算一个数的平方根,它定义在<math.h>
头文件中。
#include <stdio.h>
#include <math.h>int main() {double d = 16.0; // 待计算的数double r = sqrt(d); // 调用sqrt函数计算平方根printf("The square root of %lf is %lf\n", d, r);return 0;
}
在这个例子中:
sqrt
是函数名,表示“平方根”。d
是函数的参数,表示调用sqrt
函数时需要传递的值,这里是一个double
类型的值。
功能描述
sqrt
函数的主要功能是计算一个数的平方根。它接受一个double
类型的参数,并返回该参数的平方根。
头文件包含的重要性
使用库函数时,必须包含对应的头文件。这是因为库函数的声明和相关类型定义都在这些头文件中。例如,要使用
sqrt
函数,你需要在代码中包含<math.h>
头文件。如果不包含,编译器将无法识别该函数,可能会导致编译错误。
库函数文档的一般格式
1. 函数原型2. 函数功能介绍3. 参数和返回类型说明4. 代码举例5. 代码输出6. 相关知识链接
自定义函数:释放你的编程创造力
在掌握了库函数的使用之后,我们将注意力转向自定义函数——这是编程中更具创造性的部分。自定义函数不仅重要,而且它们让程序员能够根据自己的需求设计和实现特定的功能。
函数的语法形式
ret_type fun_name(parameter_list) {// 函数体
}
ret_type
:指定函数返回值的数据类型。如果函数不返回任何值,可以使用void
。fun_name
:函数的名称,它应该反映函数的功能,以便于理解和使用。parameter_list
:函数的参数列表,用于传递输入值给函数。参数可以有零个或多个,每个参数都需要指明数据类型和名称。- 花括号
{}
内的部分称为函数体,这里包含了完成特定任务的代码。
函数的比喻
我们可以将函数比作一个小型的加工厂,工厂需要输入原材料,经过加工后产出产品。类似地,函数通常接收一些输入值(参数),在函数体内进行处理,最终产生输出(返回值)。
函数的举例
让我们通过一个简单的例子来演示如何编写一个自定义函数。假设我们需要编写一个加法函数,用于计算两个整型变量的和。
#include <stdio.h>// 自定义加法函数
int Add(int x, int y) {int z = 0;z = x+y;return z; // 直接返回计算结果
}int main() {int a, b;// 输入printf("Enter two integers: ");scanf("%d %d", &a, &b);// 调用加法函数并输出结果int sum = Add(a, b);printf("The sum is: %d\n", sum);return 0;
}
在这个例子中,我们定义了一个名为
Add
的函数,它接收两个整型参数x
和y
,并返回它们的和。在main
函数中,我们从用户那里获取两个整数,调用Add
函数计算它们的和,并将结果打印出来。
简化函数定义
实际上,Add
函数可以进一步简化,直接在函数体内返回计算结果
int Add(int x, int y) {return x + y;
}
函数的参数部分需要交代清楚:参数个数,每个参数的类型是啥,形参的名字叫啥。上⾯只是⼀个例⼦,未来我们是根据实际需要来设计函数,函数名、参数、返回类型都是可以灵活变化的。
形参和实参
形参
在上⾯代码中,第2⾏定义函数的时候,在函数名 Add 后的括号中写的 x 和 y ,称为形式参数,简 称形参。
#include <stdio.h>
int Add(int x, int y)
{int z = 0;z = x+y;return z;
}int main()
{int a = 0;int b = 0;//输⼊scanf("%d %d", &a, &b);//调⽤加法函数,完成a和b的相加//求和的结果放在r中int r = Add(a, b);//输出printf("%d\n", r);return 0;
}
实参
实参和形参的关系
#include <stdio.h>
int Add(int x, int y)
{int z = 0;z = x + y;return z;
}int main()
{int a = 0;int b = 0;//输⼊scanf("%d %d", &a, &b);//调⽤加法函数,完成a和b的相加//求和的结果放在r中int r = Add(a, b);//输出printf("%d\n", r);return 0;
}
return 语句
return后边可以是⼀个数值,也可以是⼀个表达式,如果是表达式则先执⾏表达式,再返回表达式的结果。 return后边也可以什么都没有,直接写 return; 这种写法适合函数返回类型是void的情况。 return返回的值和函数返回类型不⼀致,系统会⾃动将返回的值隐式转换为函数的返回类型。 return语句执⾏后,函数就彻底返回,后边的代码不再执⾏。 如果函数中存在if等分⽀的语句,则要保证每种情况下都有return返回,否则会出现编译错误。
数组做函数参数
#include <stdio.h>
void set_arr(int arr2[10], int sz2)
{int i = 0;for (i = 0; i < sz2; i++){arr2[i] = -1;}
}void print_arr(int arr2[], int sz2)
{int i = 0;for (i = 0; i < sz2; i++){printf("%d ", arr2[i]);}printf("\n");
}int main()
{int arr[] = {1,2,3,4,5,6,7,8,9,10};set_arr();//设置数组内容为-1print_arr();//打印数组内容return 0;
}
#include <stdio.h>
int main()
{int arr[] = {1,2,3,4,5,6,7,8,9,10};int sz = sizeof(arr)/sizeof(arr[0]);set_arr(arr, sz);//设置数组内容为-1print_arr(arr, sz);//打印数组内容return 0;
}
- 函数的形式参数要和函数的实参个数匹配
- 函数的实参是数组,形参也是可以写成数组形式的
- 形参如果是⼀维数组,数组⼤⼩可以省略不写
- 形参如果是⼆维数组,⾏可以省略,但是列不能省略
- 数组传参,形参是不会创建新的数组的
- 形参操作的数组和实参的数组是同⼀个数组
嵌套调⽤和链式访问
嵌套调⽤
int is_leap_year(int y)
{if(((y%4==0)&&(y%100!=0))||(y%400==0))return 1;elsereturn 0;
}
int get_days_of_month(int y, int m)
{int days[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};int day = days[m];if (is_leap_year(y) && m == 2)day += 1;return day;
}int main()
{int y = 0;int m = 0;scanf("%d %d", &y, &m);int d = get_days_of_month(y, m);printf("%d\n", d);return 0;
}
链式访问
#include <stdio.h>
int main()
{int len = strlen("abcdef");//1.strlen求⼀个字符串的⻓度printf("%d\n", len);//2.打印⻓度 return 0;
}
#include <stdio.h>
int main()
{printf("%d\n", strlen("abcdef"));//链式访问return 0;
}
#include <stdio.h>
2 int main()
3 {
4 printf("%d", printf("%d", printf("%d", 43)));
5 return 0;
6 }
int printf ( const char * format, ... );
函数的声明和定义
单个⽂件
#include <stido.h>
//判断⼀年是不是闰年
int is_leap_year(int y)
{if(((y%4==0)&&(y%100!=0)) || (y%400==0))return 1;elsereturn 0;
}int main()
{int y = 0;scanf("%d", &y);int r = is_leap_year(y);if(r == 1)printf("闰年\n");elseprintf("⾮闰年\n");return 0;
}
#include <stido.h>
int main()
{int y = 0;scanf("%d", &y);int r = is_leap_year(y);if(r == 1)printf("闰年\n");elseprintf("⾮闰年\n");return 0;
}
//判断⼀年是不是闰年
int is_leap_year(int y){if(((y%4==0)&&(y%100!=0)) || (y%400==0))return 1;elsereturn 0;
}
#include <stido.h>
int is_leap_year(int y);//函数声明
int main()
{int y = 0;scanf("%d", &y);int r = is_leap_year(y);if(r == 1)printf("闰年\n");elseprintf("⾮闰年\n");return 0;
}
//判断⼀年是不是闰年
int is_leap_year(int y){if(((y%4==0)&&(y%100!=0)) || (y%400==0))return 1;elsereturn 0;
}
多个文件
//函数的定义
int Add1(int x, int y)
{return x + y;
}
#pragma once
//函数的声明
int Add(int x, int y);
#include <stdio.h>
#include "add1.h"
int main()
{int a = 10;int b = 20;//函数调⽤int c = Add1(a, b);printf("%d\n", c);return 0;
}
有了函数声明和函数定义的理解,我们写代码就更加⽅便了。
static 和 extern
static 修饰局部变量:
//代码1
#include <stdio.h>
void test()
{int i = 0;i++;printf("%d ", i);
}
int main()
{int i = 0;for(i=0; i<5; i++){test();}return 0}
//代码2
#include <stdio.h>
void test()
{//static修饰局部变量static int i = 0;i++;printf("%d ", i);
}
int main()
{int i = 0;for(i=0; i<5; i++){test();}return 0;
}
对⽐代码1和代码2的效果,理解 static 修饰局部变量的意义。代码1的test函数中的局部变量i是每次进⼊test函数先创建变量(⽣命周期开始)并赋值为0,然后++,再打印,出函数的时候变量⽣命周期将要结束(释放内存)。代码2中,我们从输出结果来看,i的值有累加的效果,其实 test函数中的i创建好后,出函数的时候是不会销毁的,重新进⼊函数也就不会重新创建变量,直接上次累积的数值继续计算。
结论
static修饰局部变量改变了变量的⽣命周期,⽣命周期改变的本质是改变了变量的存储类型,本来⼀个局部变量是存储在内存的栈区的,但是被 static 修饰后存储到了静态区。存储在静态区的变量和全局变量是⼀样的,⽣命周期就和程序的⽣命周期⼀样了,只有程序结束,变量才销毁,内存才回收。但是作⽤域不变的。
static 修饰全局变量
int g_val = 2018;
test.c
#include <stdio.h>
extern int g_val;
int main()
{printf("%d\n", g_val);return 0;
}
代码2
add.c
static int g_val = 2018;
test.c
#include <stdio.h>
extern int g_val;
int main()
{printf("%d\n", g_val);return 0;
}
结论:⼀个全局变量被static修饰,使得这个全局变量只能在本源⽂件内使⽤,不能在其他源⽂件内使⽤。本质原因是全局变量默认是具有外部链接属性的,在外部的⽂件中想使⽤,只要适当的声明就可以使⽤;但是全局变量被 static 修饰之后,外部链接属性就变成了内部链接属性,只能在⾃⼰所在的源⽂件内部使⽤了,其他源⽂件,即使声明了,也是⽆法正常使⽤的。使⽤建议:如果⼀个全局变量,只想在所在的源⽂件内部使⽤,不想被其他⽂件发现,就可以使⽤static修饰。
static 修饰函数
代码1
add.c
int Add(int x, int y)
{return x+y;
}
test.c
#include <stdio.h>
extern int Add(int x, int y);
int main()
{printf("%d\n", Add(2, 3));return 0;
}
代码2
add.c
static int Add(int x, int y)
{return x+y;
}
test.c
#include <stdio.h>
extern int Add(int x, int y);
int main()
{printf("%d\n", Add(2, 3));return 0;
}
代码1是能够正常运⾏的,但是代码2就出现了链接错误。修饰全局变量是⼀模⼀样的,⼀个函数在整个⼯程都可以使⽤,被static修饰后,只能在本⽂件内部使⽤,其他⽂件⽆法正常的链接使⽤了。本质是因为函数默认是具有外部链接属性,具有外部链接属性,使得函数在整个⼯程中只要适当的声明就可以被使⽤。但是被 static 修饰后变成了内部链接属性,使得函数只能在⾃⼰所在源⽂件内部使⽤。使⽤建议:⼀个函数只想在所在的源⽂件内部使⽤,不想被其他源⽂件使⽤,就可以使⽤ static 修饰。