知识点大纲:
目录
1.函数的概念:
在C语言中,函数(function)是一个独立的代码块,用于执行特定的任务或计算操作。它是C语言模块化和代码复用的核心概念之一。通过使用函数,程序员可以将代码划分成较小的、更易于管理的部分,提高代码的可读性、可维护性和可重用性。
理解:我们可将函数看成一个加工厂,这个工厂接收原材料后,形成产品。其中,原材料就可以看成c语言代码中的变量或常量等一系列可以操作的值,形成的产品就是我们需要这个函数所需要做的事情。
代码示例:
int add(int a,int b)
{
return a+b;//这个函数的结果就是得到a+b的值,
//相当于工厂生产出的产品
}
int main()
{
int a=10;
int b=20;
int c=add(a,b);//add就是函数
return 0;
}
上述代码中的细节:在后面会继续讨论,我们现在了解函数的基本用处即可。
C语言中一般会有两类函数:
- 库函数
- 自定义函数
2.库函数
在C语言中,库函数(Library Functions)是预先编写和编译的函数集合,提供了用于执行各种常见任务的标准化代码片段。库函数的使用使开发者无需从头编写这些常见操作,从而提高了开发效率和代码可读性。库函数通常被打包成库,并通过包含头文件进行访问。
标准库函数
C标准库提供了一组用于执行基本操作的库函数,包括输入/输出、字符串处理、数学运算、内存管理等。这些函数可以在所有遵循C标准的编译器上使用。
- 输入/输出库:
stdio.h
提供了用于控制台和文件输入/输出的函数,如printf
、scanf
、fopen
、fclose
、fgets
、fprintf
等。 - 字符串库:
string.h
提供了操作字符串的函数,如strlen
、strcpy
、strcat
、strcmp
等。 - 数学库:
math.h
提供了基本数学运算的函数,如sin
、cos
、sqrt
、pow
等。 - 内存管理库:
stdlib.h
提供了动态内存分配和转换的函数,如malloc
、calloc
、realloc
、free
、atoi
、atof
等。
注 :这些库函数C标准未将其实现,只是提供了该函数应该有的功能。具体的实现是编译器的生产商的进行实现的,不同编译器下的库函数实现不同,我们无需了解其底层如何实现,会熟练使用即可。
这里提供库函数的学习网站,可通过该网站,查询到你所想使用的库函数:
库函数的特点
- 预编译:库函数已经编译成目标代码,并链接到程序中。这使得库函数执行效率更高。
- 通用性:标准库函数是C标准的一部分,在不同平台上具有一致的行为。
- 抽象:库函数隐藏了底层实现细节,提供了简洁的接口。
- 模块化:通过将常用功能封装在库函数中,简化了代码开发和维护。
库函数的使用
库函数使用前,都要包含该库函数所在的头文件中。
其实我们在刚开始学习C语言时,就接触过库函数,如下面这段代码:
#include <stdio.h>
int main()
{
printf("hello world");
return 0;
}
其中printf就是库函数,该函数被包含在 stdio.h该头文件中 。
3.自定义函数
在C语言中,自定义函数是由开发者自己编写的函数,用于执行特定的操作或逻辑。自定义函数可以帮助代码模块化、提高代码复用性、简化复杂性,并增强代码的可维护性和可读性。
自定义函数的结构
一个自定义函数的基本结构包括以下部分:
- 返回类型:指定函数返回的值的类型。如果函数不返回值,则使用
void
。 - 函数名:函数的标识符,遵循C语言的命名规则。
- 参数列表:列出函数接受的参数及其类型。如果没有参数,则留空或使用
void
。 - 函数体:由大括号
{}
包围的代码块,包含了函数的操作和逻辑。
现在我们自定义一个用来进行加法运算的函数。
图示:
下面,我们在实现一个打印hello world的函数,该函数无返回值和参数。
#include <stdio.h>//为了使用printf库函数
void Print()
{
printf("hello world\n");
}
int main()
{
Print();
return 0;
}
如果需要打印“hello world”则直接调用该函数即可。减少了代码的冗余。
4.形参和实参
在C语言中,函数的实参(Actual Parameters)和形参(Formal Parameters)是与函数参数相关的重要概念。它们涉及到函数的调用和传递数据的方法。
形参
形参是指在函数定义中用来接收参数的变量。这些变量在函数的作用域内有效,它们作为函数的输入,赋值时来自于调用函数时的实参。形参定义了函数需要的参数类型和名称。
#include <stdio.h>
// 函数的形参 a 和 b
int add(int a, int b)
{
return a + b;
}
在上面的代码中,a
和 b
是 add
函数的形参。当调用 add
函数时,实际传递的参数将赋值给形参。
实参
实参是指在调用函数时传递给函数的实际值或变量。实参可以是直接的常量值、变量、表达式,或地址(当使用指针时)。
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int main()
{
int x = 3;
int y = 4;
// 函数的实参是 x 和 y
int result = add(x, y);
printf("The sum is: %d\n", result);
return 0;
}
在上面的代码中,x
和 y
是传递给 add
函数的实参。在函数调用过程中,这些实参的值被赋予形参 a
和 b
。
实参和形参的关系
当调用函数时,实参的值被传递给形参。C语言中参数传递的方式主要有两种:
- 按值传递:函数接收实参的副本,形参的改变不会影响实参的值。通常情况下,C语言使用按值传递。
- 按址传递:通过传递指针,形参可以直接操作实参的地址,从而改变实参的值。
按值传递,我们通过编译器的调试的监视窗口,进行更加直观的理解:
想了解值传递和 址传递的区别,我们首先要了解什么时地址。&x表示得到x的地址。
我们可以将地址比作一个宿舍的门牌号,其中宿舍里面的我们,就是内存中所存储的东西。
地址就是用来标识每个不同的内存的。通过调试代码我们可以发现。a,b与x,y的内存时互相独立的,互不干扰,所以改变x不会改变y。
代码实列:
#include <stdio.h>
void swap(int x, int y)//该函数用于交换两个变量的值
{
int temp = 0;
temp = x;
x = y;
y = temp;
}
int main()
{
int a = 10;
int b = 20;
swap(a, b);
printf("%d %d", a, b);
return 0;
}
通过运行改代码,结果并未发生a,b变量的交换 ,因为x,y和a,b是不同的。我们可以理解为,形参是实参的一份临时拷贝.
址传递概念:(如果未学习指针,该内容建议学习完基础的指针内容在进行学习)
当使用地址传递时,函数的形参接收的是一个指针,指针指向实参的内存地址。通过解引用指针,函数可以直接修改实参的值。因此,任何对形参的修改都会反映在实参上。这种方式在需要函数修改调用者传递的变量时非常有用。
址传递示例:
#include <stdio.h>
void changeReference(int *x) { // 形参是一个指针
*x = 10; // 通过解引用修改实参的值
}
int main() {
int a = 5;
changeReference(&a); // 传递 a 的地址
printf("a is: %d\n", a); // a 被改变了,变成 10
return 0;
}
在这个例子中,changeReference
函数接收一个指向 a
的指针,并通过解引用修改 a
的值。这是通过指针实现按引用传递的方式。
5.return语句
基本概念
- 返回值:
return
语句可以返回一个值,这个值的类型由函数的返回类型决定。如果函数的返回类型是int
,则return
应返回一个整数;如果返回类型是void
,则return
不返回值。 - 提前结束:
return
语句可以提前结束函数的执行,无论它在函数体的哪个位置出现。一旦return
被执行,函数就会立即退出。
用法示例
返回整数
#include <stdio.h>
// 返回两个整数的和
int add(int a, int b) {
return a + b; // 返回值
}
int main() {
int result = add(3, 4); // 调用返回值的函数
printf("Result: %d\n", result);
return 0; // 结束程序
}
在这个例子中,add
函数通过 return
语句返回两个整数的和。main
函数使用返回值来执行进一步操作。
提前退出
#include <stdio.h>
// 检查数字是否为正数
void checkPositive(int num) {
if (num <= 0) {
printf("Not positive.\n");
return; // 提前退出函数
}
printf("Is positive.\n");
}
int main() {
checkPositive(5); // 输出 "Is positive."
checkPositive(-3); // 输出 "Not positive."
return 0;
}
在这个例子中,checkPositive
函数根据输入的数字是否为正数来决定是否提前退出。使用 return
语句可以避免执行不必要的代码。
6.数组做函数参数
数组名在做函数参数时,数组名代表该数组首元素的地址。这句话将是理解下面内容的关键所在
基本概念
当将数组作为参数传递给函数时,实际上是将数组的首地址传递给函数。这是C语言中实现按引用传递的一种方式,因此数组作为参数传递有以下特性:
- 原始数组可被修改:因为函数获得了数组的内存地址,因此在函数内对数组进行修改会影响到原始数组。
- 传递额外的参数来指示数组大小:因为数组作为参数传递时只传递首地址,函数无法知道数组的大小,所以通常需要额外传递数组的大小。
将数组作为参数的示例
#include <stdio.h>
// 打印数组内容的函数
void printArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
// 修改数组内容的函数
void modifyArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
arr[i] = arr[i] * 2; // 将数组每个元素乘以2
}
}
int main() {
int array[5] = {1, 2, 3, 4, 5}; // 定义一个数组
printf("Original array: ");
printArray(array, 5); // 将数组传递给函数
modifyArray(array, 5); // 修改数组
printf("Modified array: ");
printArray(array, 5); // 查看修改后的数组
return 0;
}
在这个示例中,我们定义了两个函数,一个用于打印数组,一个用于修改数组。通过将数组和大小作为参数传递,函数可以正确访问和修改数组。
7.嵌套调用和链式访问
嵌套调用
嵌套调用类似于在日常生活中完成一系列步骤,每个步骤都建立在之前的步骤基础之上。这种方式要求在一个操作中完成多个任务。
例子:组装饮料
想象一下,你要制作一杯冰镇柠檬水。为了完成这项任务,你需要一系列步骤:
- 准备柠檬。
- 切片挤汁。
- 添加糖和水。
- 加冰块。
在编程中,如果我们将每个步骤看作一个函数,嵌套调用就是在一个步骤中调用另一个步骤。例如:
- 切片挤汁:这个步骤本身需要先切片,然后挤出柠檬汁。
- 制作饮料:在制作饮料的过程中,你需要先挤出柠檬汁,然后添加其他成分。
在代码中,嵌套调用可能是这样的:
#include <stdio.h>
// 计算数组的平均值
double average(int arr[], int size) {
int sum = 0;
for (int i = 0; i < size; i++) {
sum += arr[i];
}
return (double)sum / size; // 返回平均值
}
// 计算数组的方差
double variance(int arr[], int size) {
double avg = average(arr, size); // 嵌套调用计算平均值
double var = 0;
for (int i = 0; i < size; i++) {
var += (arr[i] - avg) * (arr[i] - avg); // 方差计算
}
return var / size; // 返回方差
}
int main() {
int arr[] = {1, 2, 3, 4, 5};
int size = sizeof(arr) / sizeof(arr[0]);
double var = variance(arr, size); // 使用嵌套调用计算方差
printf("Variance: %.2f\n", var);
return 0;
}
在这个示例中,我们定义了两个自定义函数:average
计算数组的平均值,variance
计算数组的方差。通过嵌套调用,variance
使用 average
的结果来进一步计算方差。
链式访问
链式访问类似于在现实世界中连续操作对象。它是将一系列操作按顺序链接在一起,每个操作都依赖于上一个操作的结果。就是将一个函数的返回值做为另外一个函数的参数,像链条一样将函数串起来就是函数的链式访问。
例子:火车车厢
想象一列火车,每个车厢连接到另一个车厢。当你进入第一节车厢后,你可以通过车厢间的连接通道进入下一节车厢,然后继续向前移动。这种连接关系让你可以连续访问所有车厢。
在编程中,链式访问类似于这样的行为。你通过一个对象访问另一个对象,或者通过连续调用来完成一系列操作。例如,在字符串处理十分常见。
#include <stdio.h>
#include <math.h>
// 链式访问的例子:计算一个表达式
int main() {
double x = 2.0;
double result = sqrt(pow(x, 2)); // 链式访问
//sqrt函数使用了pow函数的返回值
printf("Result: %f\n", result);
return 0;
}
这个列子中,库函数sqrt使用pow函数的返回值,这就构成了一个链式访问。
8.函数的声明和定义
函数的声明
函数的声明(Function Declaration),也称为函数原型(Function Prototype),用于告诉编译器一个函数的名称、返回类型、参数类型和顺序。这允许编译器在编译过程中验证函数调用是否符合预期,但函数声明不包含函数的具体实现。函数声明通常用于在代码的顶部或头文件中,以便让其他部分的代码知道这个函数的存在。
函数声明的结构
- 返回类型:函数返回的值的类型。如果函数没有返回值,使用
void
。 - 函数名:函数的标识符。
- 参数列表:指定函数接受的参数类型和顺序,可以包括参数名称,也可以省略。
- 分号:函数声明以分号结束,因为它只是一个声明,而不是完整的函数体。
// 函数声明
int add(int a, int b); // 声明返回两个整数和的函数
void printMessage(); // 声明一个没有返回值且无参数的函数
函数的定义
函数的定义(Function Definition)提供了函数的具体实现。它包括函数的返回类型、名称、参数列表和函数体。函数体包含了函数的实际代码,描述了函数的操作。
函数定义的结构
- 返回类型:与声明一致,表明函数的返回值类型。
- 函数名:与声明一致。
- 参数列表:与声明一致,通常包含参数名称。
- 函数体:用大括号
{}
包围,包含了函数的实际操作和逻辑。
// 函数定义
int add(int a, int b) {
return a + b; // 返回两个整数的和
}
void printMessage() {
printf("Hello, world!\n");
}
声明和定义的区别
- 函数声明:提供了函数的签名,告诉编译器这个函数的存在和如何调用它。声明不包含函数的实际实现。
- 函数定义:提供了函数的完整实现,包括函数体,是函数实际工作的地方。
为什么需要函数声明
C语言中的编译器通常是单次扫描的,它从上到下依次编译代码。函数声明确保在调用函数之前,编译器已经知道该函数的签名。如果没有函数声明,编译器可能会因为找不到函数而产生错误。
函数声明通常用于以下情况:
- 头文件:在头文件中声明函数,使得不同文件中的代码可以引用同一个函数。
- 前向声明:在函数调用前声明函数,以确保编译器知道函数的存在。
总结
- 函数声明 用于告诉编译器一个函数的名称、返回类型、参数类型和顺序,但不包含函数的具体实现。
- 函数定义 提供了函数的完整实现,包括函数体和实际操作。
static和extern关键字
static三个作用:
- 修饰局域变量
- 修饰全局变量
- 修饰函数
修饰局域变量:
改变该局域变量的声明周期但不改变其作用域。
代码示例:
#include <stdio.h>
void Print()
{
int i = 0;
i++;
printf("%d\n", i);
}
int main()
{
Print();
Print();
Print();
return 0;
}
该代码的输出结果:
1
1
1
加上static的代码:
#include <stdio.h>
void Print()
{
static int i = 0;
i++;
printf("%d\n", i);
}
int main()
{
Print();
Print();
Print();
return 0;
}
结果:
1
2
3
在没有static修饰的变量i时,i的生命周期 随着调用函数的结束而结束。但是加上static后改变其生命周期。本质上时因为,改变i的存储位置,一般局域变量我们存储与栈区,随着被static修饰后,该变量存储与静态区,其中全局变量也是存储与静态区中,此时静态变量的生命周期变得和全局变量的周期一致。
修饰全局变量
全局变量在文件内或者整个程序内共享,修饰全局变量可以控制它们的可见性、生命周期和链接属性。
extern
关键字
extern
用于声明一个全局变量,而不定义它。通常用于在一个文件中引用另一个文件中定义的全局变量。
// file1.c
int globalVar = 42; // 定义全局变量
// file2.c
extern int globalVar; // 声明全局变量
在这个例子中,file1.c
中定义了一个全局变量,而 file2.c
中使用 extern
声明它。这种方式允许在不同文件间共享全局变量。
//在file2.c中贼可以使用globalVar
#include <stdio.h>
extern int globalVar;
int main()
{
printf("%d",globalVar);
return 0;
}
static
关键字
static
关键字用于限制全局变量的可见性,使其只在当前文件中可见。
// file1.c
static int privateVar = 42; // 仅在 file1.c 中可见
// file2.c
// extern int privateVar; // 这将导致编译错误,因为 privateVar 在 file2.c 中不可见
在这个例子中,privateVar
是一个静态全局变量,它只能在 file1.c
文件中访问。static
关键字可以防止全局变量的命名冲突。
修饰函数
修饰函数通常用于控制函数的可见性、链接属性,以及对编译器提供优化提示。
static
关键字
当用于函数时,static
关键字限制函数的可见性,使其只能在当前文件中使用。
// file1.c
static void hiddenFunction() {
printf("This function is private to file1.c.\n");
}
// file2.c
// void hiddenFunction(); // 这将导致编译错误,因为函数在 file2.c 中不可见
在这个例子中,hiddenFunction
只能在 file1.c
中使用,而不能在 file2.c
中访问。这可以帮助创建私有的函数,防止命名冲突。
以上就是本次的所有内容,如果对你有所帮助,请留下你的赞~