C语言之函数

函数

  • 一个c语言项目只能有一个main函数

函数是什么?

  • 函数是一段可重复使用的代码块,用于执行特定的任务。它接受输入参数,执行一系列操作,并返回一个值。
  • 在C语言中,函数由函数头和函数体组成。函数头包含函数的返回类型、函数名和参数列表。函数体包含函数的具体实现代码。
  • 通过调用函数,可以在程序中多次使用相同的代码逻辑,提高代码的可读性和可维护性。

函数分类

  • 分类:
    • 库函数
    • 自定义函数

库函数

  • 库函数是由C语言提供的预定义函数,可以直接在程序中使用。
  • 这些函数通常包含在标准库中,如stdio.h、stdlib.h等。
  • 库函数提供了各种常用的功能,如输入输出、内存分配、字符串处理等。
  • 库函数参考网站

C语言中常用的库函数包括:

  • 输入输出函数(IO函数):printf、scanf、fprintf、fscanf等
  • 字符串处理函数:strlen、strcpy、strcat、strcmp等
  • 数学函数:sqrt、sin、cos、pow等
  • 内存管理函数:malloc、free、calloc、realloc等
  • 文件操作函数:fopen、fclose、fread、fwrite等
  • 时间和日期函数:time、localtime、strftime等

自定义函数

  • 自定义函数是由程序员根据需要自行编写的函数,它们可以根据特定的需求实现特定的功能。

  • 自定义函数可以在程序中多次调用,以提高代码的可重用性和可维护性。自定义函数的定义和实现由程序员完成,可以根据需要定义函数的返回类型、函数名和参数列表,并在函数体中编写具体的实现代码。

  • 自定义函数的步骤

    1. 在函数的上方声明函数原型(可选):函数原型包括函数的返回类型、函数名和参数列表。
    2. 在合适的位置定义函数:函数定义包括函数的返回类型、函数名和参数列表,以及函数体中的具体实现代码。
    3. 在需要使用函数的地方进行函数调用:通过函数名和参数列表调用函数,将控制权转移到函数体中执行相应的操作。
//
// 函数原型(可选)
int add(int a, int b);

// 函数定义
int add(int a, int b) {
    int sum = a + b;
    return sum;
}

int main() {
    int result = add(3, 5);  // 函数调用
    printf("The sum is: %d", result);
    return 0;
}

函数参数

  • 在C语言中,函数参数传递是按值传递的。这意味着在函数调用时,实参的值会被复制到形参中,形参在函数内部进行操作,但不会影响到原始的实参。
#include<stdio.h>

// 函数原型(可选)
void swap(int a, int b);

// 函数定义
void swap(int a, int b) {
    int temp = 0 ;
    temp = a ;
    a = b;
    b = temp;
}


int main() {
	int n1 = 1, n2 = 2;
	printf("交换前: n1=%d,n2=%d\n", n1,n2);
    swap(n1, n2);  // 函数调用
//swap函数接受两个整数参数a和b,但是在函数内部进行交换操作并不会影响到main函数中的n1和n2变量。
//这是因为swap函数中的a和b是n1和n2的临时拷贝,对它们的修改不会影响到原始的实参。
    printf("交换后: n1=%d,n2=%d\n", n1,n2);
    
    return 0;
}
  • 可以在函数内部通过指针修改实参的值
void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
    //在C语言中,int类型的变量可以接收指针是因为指针可以存储内存地址,而int类型的变量可以存储整数值。指针变量可以指向任何类型的数据,包括整数类型。
}

函数调用

  • 在C语言中,函数调用指的是在程序中使用函数名称和参数列表来执行函数体中的代码。
  • 函数调用允许程序在不重复编写代码的情况下多次执行相同的代码。

函数调用的一般形式如下:

return_value = function_name(argument1, argument2, ...);

其中,return_value 是函数的返回值,function_name 是要调用的函数的名称,argument1, argument2, ... 是传递给函数的参数列表。

在进行函数调用时,程序会跳转到函数体的起始位置,并执行其中的代码。在函数执行完毕后,程序将返回到函数调用的位置,并将函数的返回值赋给 return_value

在C语言中,可以通过以下步骤来调用函数:

  1. 在函数调用前声明函数原型。函数原型包括函数名称、返回类型和参数类型列表。例如:

    int sum(int a, int b);  // 声明函数原型
    
  2. 定义函数。函数定义包括函数名称、返回类型、参数列表和函数体。例如:

    int sum(int a, int b) {   // 定义函数
        return a + b;//计算参数 `a` 和 `b` 的和,并将结果返回给调用者。
    }
    
  3. 在函数中调用函数。调用函数时,使用函数名称和参数列表。例如:

    int result = sum(3, 4);  // 调用函数
    
  • 在调用函数之前,必须先声明函数原型。函数原型告诉编译器函数名称、返回类型和参数类型,以便正确地生成代码。如果没有正确声明函数原型,编译器可能会发出警告或错误信息。此外,函数名称、参数名称和变量名称必须遵循C语言的[[1 初识c语言#命名规范|命名规则]]。

嵌套调用和链式访问

  • 函数之间可以进行组合,也就是说可以互相调用的

嵌套调用

  • 在C语言中,嵌套调用指的是在一个函数中调用另一个函数,并且被调用的函数本身又调用了其他函数,形成了一个函数的嵌套调用结构。
  • 函数可以嵌套调用,但是不能嵌套定义
#include <stdio.h>

int factorial(int n) {
    if (n == 0) {
        return 1;
    } else {
        return n * factorial(n - 1);
    }
}

int main() {
    int x = 5;
    int result = factorial(x);
    printf("%d! = %d\n", x, result);
    return 0;
}

链式调用

  • 链式访问(也称为“链式调用”)通常指的是使用指针来访问结构体中的成员,并且在一连串的操作中将多个访问操作链接在一起,形成一个链式的访问结构。
  • 链式调用的主要优点是使代码更简洁易懂,减少了临时变量的使用,同时也使代码的可读性更好。
  • 在使用函数链式调用时,应该确保每个函数都返回正确的结果,并且每个函数的参数都正确地传递给下一个函数
#include <stdio.h>

int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

int multiply(int a, int b) {
    return a * b;
}

int main() {
//链式访问
    int result = multiply(subtract(add(2, 3), 1), 4);
    //计算表达式(2+3-1)*4的结果
    printf("%d\n", result);

	printf("%d", printf("%d", printf("%d", 43)));//4321
	//printf的返回值是打印字符的个数
	
    return 0;
}

函数的声明与定义

  • 在C语言中,函数的声明和定义是分离的,需要分别进行。

  • 函数的声明

    • 函数的声明用于告诉编译器该函数的名称、参数类型和返回类型等信息,以便在调用该函数时能够正确地解析函数的名称和参数。
    • 函数的声明一般出现在函数使用之前,需要满足先声明后使用。
    • 函数的声明一般要放在头文件中
  • 函数的定义则包含了函数的实际代码,并实现了函数的功能。

  • 函数的声明和定义必须匹配,包括函数的名称、参数类型和返回类型等信息。否则,程序可能无法编译或运行

  • 实际开发中,常常将函数的声明、定义在不同文件中进行,主要好处有:

    • 模块化:可以将代码分解成多个模块,使代码更易于理解和维护,同时也可以增加代码的可重用性。
    • 编译效率:使编译器在编译时只需要处理需要的文件,而不必处理整个程序,提高编译效率,特别是在大型项目中,可以节省大量的编译时间。
    • 防止重复定义:将函数的声明和定义分离到不同的文件中,可以避免函数的重复定义。
    • 保密性:将代码生成静态库,实现代码保密
//add.h文件
/* 函数声明 */
int add(int a, int b);
//add.c文件
/* 函数定义 */
int add(int a, int b) {
    return a + b;
}
#include <stdio.h>
#include "add.h"

/* 主函数 */
int main() {
    int x = 3, y = 4;
    int result = add(x, y);
    printf("%d + %d = %d\n", x, y, result);
    return 0;
}

  • 函数不写返回值的时候,默认返回值是int
add(int a, int b) {
    return a + b;
}//不推荐
int add(int a, int b) {
    return a + b;
}//推荐

函数递归

  • 函数递归是指一个函数在其自身内部调用自身的过程。换言之,函数递归是通过一个函数不断地调用自身来解决问题的一种方法。

  • 在C语言中,函数递归通常会涉及到一个基本情况和一个递归情况。

    • 基本情况是指一个问题的最简单情况,可以直接被解决
    • 递归情况是指问题可以被分解为更小的子问题,这些子问题可以通过调用相同的函数来解决。
  • 函数递归通常需要注意两个问题:递归深度和递归开销。

    • 递归深度是指递归调用的层数,如果递归深度过深,可能会导致栈溢出等问题。
    • 递归开销是指每次递归调用所需的内存和计算时间,如果递归开销过大,可能会导致程序效率低下。
#include <stdio.h>

int factorial(int n) {
  if (n == 1) { // 基本情况
    return 1;
  } else { // 递归情况
    return n * factorial(n - 1);
  }
}

int main() {
  int n = 5;
  printf("%d! = %d\n", n, factorial(n));
  return 0;
}
  • 递归与迭代的优劣:
    递归的优点:
  1. 代码简洁:递归通常比迭代代码更简洁、易读、易于理解。
  2. 代码可维护性:递归代码更容易维护,因为它们往往是自我包含的,具有良好的模块化和可重用性。
  3. 代码可读性:递归代码往往更具可读性和可理解性,因为它们模拟了问题本身的结构和逻辑。

递归的缺点:

  1. 可能导致栈溢出:递归深度过深时,会导致栈溢出,因为每次递归调用都需要在栈上分配内存。
  2. 性能较差:递归通常比迭代代码运行更慢,因为每次递归调用都需要在栈上分配内存,而且递归调用需要保存现场,恢复现场。

迭代的优点:

  1. 性能更好:迭代通常比递归代码运行更快,因为它们不需要在栈上分配内存,而且迭代代码往往更容易优化。
  2. 更少的内存使用:迭代通常使用更少的内存,因为它们不需要在栈上分配内存。

迭代的缺点:

  1. 代码复杂:迭代代码往往比递归代码更复杂,因为它们需要手动管理迭代变量和状态,而且往往需要使用循环。
  2. 可读性差:迭代代码可能更难理解和维护,因为它们往往需要手动管理迭代变量和状态。

练习

  1. 写一个函数判断一个数是不是素数
#include <stdio.h>

// 判断素数
_Bool isSu(int n) {
    if (n <= 1) {
        return 0;
    }
    
    for (int i = 2; i < n; i++) {
        if (n % i == 0) {
            return 0;
        }
    }
    
    return 1;
}

int main() {
    int n = 0;
    printf("请输入一个大于0的整数:");
    scanf("%d", &n);

    if (isSu(n)) {
        printf("%d是素数\n", n);
    } else {
        printf("%d不是素数\n", n);
    }

    return 0;
}

  1. 写一个函数判断一年是不是闰年
#include <stdio.h>

_Bool isRun(int year) {  
   if (year % 4 == 0 && year % 100!= 0) {  
       return 1;  
   } else if (year % 400 == 0) {  
       return 1;  
   } else {  
       return 0;  
   }  
}

int main() {  
   int year = 0;  
   printf("请输入一年:");  
   scanf("%d", &year);

   if (isRun(year)) {  
       printf("%d是闰年\n", year);  
   } else {  
       printf("%d不是闰年\n", year);  
   }

   return 0;  
}

  1. 写一个函数,实现整形有序数组的二分查找
#include<stdio.h> 
//写一个函数,实现整形有序数组的二分查找

int binarySearch(int arr[], int left, int right, int target) {
    while (left <= right) {
        int mid = left + (right - left) / 2;

        if (arr[mid] == target)
            return mid;

        if (arr[mid] < target)
            left = mid + 1;
        else
            right = mid - 1;
    }

    return -1;
}

int main() {
    int arr[] = {1, 2, 3, 4, 5};
    int target = 3;
    int result = binarySearch(arr, 0, sizeof(arr) / sizeof(arr[0]) - 1, target);
    printf("Element found at index %d\n", result);

    return 0;
}

  1. 写一个函数,每调用一次这个函数,就会将main函数中num(初始为0)的值增加1
void increaseNum(int *num) {
    (*num)++;
}

int main() {
    int num = 0;
    
    // 调用increaseNum函数来增加num的值
    increaseNum(&num);
    
    // 打印增加后的num的值
    printf("增加后的num的值为:%d\n", num);
    
    return 0;
}
  1. 接收一个整形值(无符号),按照顺序打印它的每一位,例如输入:1234,输出:1 2 3 4
#include <stdio.h>

void print_digits(unsigned int n) {
    if (n >= 10) {
        print_digits(n / 10);
    }
    printf("%u ", n % 10);
}

int main() {
    unsigned int num;
    printf("请输入一个无符号整数:");
    scanf("%u", &num);
    printf("该整数的每一位为:");
    print_digits(num);
    printf("\n");
    return 0;
}
  1. 求n的阶乘
#include <stdio.h>

int factorial(int n) {
    if (n == 0) {   // 0 的阶乘为 1
        return 1;
    } else {
        return n * factorial(n - 1);
    }
}

int main() {
    int n;
    printf("请输入一个整数:");
    scanf("%d", &n);
    if (n < 0) {
        printf("错误:输入的数必须为非负整数\n");
        return 1;
    }
    printf("%d 的阶乘为 %d\n", n, factorial(n));
    return 0;
}
posted @ 2023-07-25 21:01  FL不凡  阅读(40)  评论(0编辑  收藏  举报