C语言-函数

知识点大纲:

目录

1.函数的概念:

2.库函数 

标准库函数

库函数的特点

库函数的使用

3.自定义函数

自定义函数的结构

4.形参和实参

形参

实参

实参和形参的关系

5.return语句

 基本概念

 用法示例

返回整数

6.数组做函数参数

基本概念

7.嵌套调用和链式访问

嵌套调用

例子:组装饮料

链式访问

例子:火车车厢

8.函数的声明和定义

函数的声明

函数声明的结构

函数的定义

函数定义的结构

 声明和定义的区别

为什么需要函数声明

总结

 static和extern关键字

修饰局域变量:

修饰全局变量

extern 关键字

static 关键字

修饰函数

static 关键字


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 提供了用于控制台和文件输入/输出的函数,如 printfscanffopenfclosefgetsfprintf 等。
  • 字符串库string.h 提供了操作字符串的函数,如 strlenstrcpystrcatstrcmp 等。
  • 数学库math.h 提供了基本数学运算的函数,如 sincossqrtpow 等。
  • 内存管理库stdlib.h 提供了动态内存分配和转换的函数,如 malloccallocreallocfreeatoiatof 等。

注 :这些库函数C标准未将其实现,只是提供了该函数应该有的功能。具体的实现是编译器的生产商的进行实现的,不同编译器下的库函数实现不同,我们无需了解其底层如何实现,会熟练使用即可。

这里提供库函数的学习网站,可通过该网站,查询到你所想使用的库函数: 

 https://cplusplus.com/

库函数的特点

  • 预编译:库函数已经编译成目标代码,并链接到程序中。这使得库函数执行效率更高。
  • 通用性:标准库函数是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;
}

在上面的代码中,abadd 函数的形参。当调用 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;
}

在上面的代码中,xy 是传递给 add 函数的实参。在函数调用过程中,这些实参的值被赋予形参 ab。 

实参和形参的关系

当调用函数时,实参的值被传递给形参。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.嵌套调用和链式访问

嵌套调用

嵌套调用类似于在日常生活中完成一系列步骤,每个步骤都建立在之前的步骤基础之上。这种方式要求在一个操作中完成多个任务。

例子:组装饮料

想象一下,你要制作一杯冰镇柠檬水。为了完成这项任务,你需要一系列步骤:

  1. 准备柠檬。
  2. 切片挤汁。
  3. 添加糖和水。
  4. 加冰块。

在编程中,如果我们将每个步骤看作一个函数,嵌套调用就是在一个步骤中调用另一个步骤。例如:

  • 切片挤汁:这个步骤本身需要先切片,然后挤出柠檬汁。
  • 制作饮料:在制作饮料的过程中,你需要先挤出柠檬汁,然后添加其他成分。

在代码中,嵌套调用可能是这样的:

#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 中访问。这可以帮助创建私有的函数,防止命名冲突。

以上就是本次的所有内容,如果对你有所帮助,请留下你的赞~

  • 25
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值