1. 概述

函数是一个有特定功能、代码独立的代码块;

1.1 声明

声明形式如下:

数据类型        函数名称        (形参说明)函数体  

如:int abs  ( int a ,int b ) {  函数体  };

① 数据类型是函数返回值的数据类型,若无返回值,须将数据类型声明为 void ;

② 形参可以有多个,多个形参之间用 逗号 隔开;也可以没有参数,即无参函数;

③ 函数体即 “ { 代码块 } ”,表示函数执行功能的代码;

<以下代码是函数的简单使用演示:

1.2 说明与使用

函数必须先说明再使用;

函数的说明:数据类型  函数名 ( <形参> );

        如 double Power( double a , int b ) 

        其中,数据类型不能缺省,形参名可以缺省:  double Power( double  , int ) 

函数的使用:也称函数的调用—— 函数名 ( <实际参数> );

        有参函数可以单独成为一个语句,也可以作为一个运算量出现在表达式中;

        但无参函数只能单独成为一个语句;

<如下,是求 X 的 n 次幂 的代码演示:注意函数 先声明再使用

若函数实现在 main 函数后面,则须先说明一下;

<一般,函数采用 说明——调用——实现 的流程,即以下代码所示:

当然,也可以解释为什么需要引入头文件: #include <stdio.h>

头文件中有许多函数声明,比如 scanf ( )  ,printf ( );

2. 函数传参

传参方式有三种:

全局变量(几乎不用),复制传递(又叫做值传递),地址传递(又叫做指针传递);

2.1 全局变量

声明在函数外部(包括 main 函数外面)的变量就是全局变量;

但是,一经定义后就会在程序的任何地方看见,一经修改,其值就会受到影响

一般不使用;

< 如下代码所示,定义全局变量,

2.2 复制传递(值传递)

复制传递拷贝的是实参的值,因此过程中不会改变实参的值

因为,形参是新开辟的存储空间,在函数中改变形参的值,不会影响到实参

< 以下代码演示了值传递不能改变实参的值;

2.3 地址传递(指针传递)

又称为指针传递,被调用函数对形参的操作(即实参地址)将直接影响实参的值;

只需要明白形参声明为指针,实参传递的是地址即可;

2.4 练习

编写一个函数,统计字符串中小写字母个数,并将其转换为大写字母;

  

3. 数组传参

数组传参略有区别,单独学习;

数组传参也有三种方式:全局变量,值传递,地址传递(又叫做指针传递);

值传递与指针传递传递:实参传递的都是数组名

3.1 值传递

实参为数组名,形参为数组名(本质是一个指针变量

<下面代码展示通过函数求一维数组中元素的和:

第九行可以深刻理解数组值传递中形参的本质是指针变量;

<那如何修改呢:增加一个形参用于记录数组元素个数

3.2 地址传递

地址传递的形参是指针变量;

<下面是通过地址传递方式演示的代码:

一维数组应该传递 数组名、元素个数

字符串数组只需传递数组名即可,因为遍历时可根据 “ \0 ” 判断; 

4. 指针函数

见名知意——即指针类型的函数,函数返回值为地址;

下面看一个经典的指针函数问题:

<以下代码是错误的:自定义函数中的 str 是局部变量,只能在该函数中使用

因此不能作为返回值,返回到 main 函数中;

若需要改正,则返回 全局变量的地址 / 字符串常量的地址 / 静态变量的地址 即可;

 4.1 练习一

编写函数,用于删除字符串中的空字符;

< 如下代码所示:

       双指针协作p 负责遍历源字符串,s 负责构建无空格的新字符串。

       原地修改:函数直接在原字符串内存上操作,无需额外空间。

       返回原地址:通过保存初始地址(r),确保返回的是修改后的字符串起始位置。

      最终结果:所有空格被删除,新字符串为 "helloworldD"s 指向最后一个字符 'D' 的下一位,添加 '\0'

       局部变量地址r 是 getString 函数内的局部变量,但返回它指向的地址是安全的。这是因为指针本身是局部的,但它指向的内存地址是调用者提供的外部数组main 函数中的 ch 数组)。函数只是操作并返回这个外部内存的地址,并没有返回局部变量本身。

        辨别:如果返回的是局部变量的值(如 int a = 10; return a;)或局部数组的地址(如 char arr[10]; return arr;),这是危险的,因为局部变量在函数返回后会被销毁。但在代码中,r 指向的是调用者传递的外部内存,因此是安全的。

 本题较难,如有疑问可借助 Ai 工具解答;

源代码如下:

#include <stdio.h>

char * removeSpaces(char * str) {
    char * dest = str;
    char * original = str; // 保存原始地址
    
    while (*str) {
        if (*str != ' ') {
            *dest = *str;
            dest++;
        }
        str++;
    }
    *dest = '\0'; // 添加字符串结束符
    return original; // 返回外部数组的地址
}

int main() {
    char buffer[] = " Hello World "; // 局部数组,但生命周期覆盖main函数
    char * result = removeSpaces(buffer);
    
    printf("处理后的字符串: %s\n", result); // 输出: "HelloWorld"
    printf("原始数组地址: %p\n", (void*)buffer);
    printf("返回的地址: %p\n", (void*)result); // 两者地址相同
    
    return 0;
}

 延申:strcpy 函数返回的是前面字符串的地址;

4.2 练习二

编写函数,用于字符串连接;

4.3 练习三

编写函数,将输入的数字转换为字符串;

<如下代码为将数字转换为字符串,需要注意以下问题:

        第七行:scanf 语句的正确使用:注意占位符为 %d(而非 %p),不需要使用 ‘ \ n ’(格式字符串中的\n会导致 scanf 持续等待输入,直到遇到非空白字符);

        第十八行:i++ 的位置应在循环体最后,否则 s[1] 为空;

        第二十行:注意添加 '\0';

<但上述代码是逆序输出,应将数组逆序输出>

        第二十行字符串末尾添加 ‘\0\',所以交换字符串内容时从后面的 i - 2 开始,如 25 行所示;

5. 递归函数

递归函数即在函数体中调用函数本身的函数;

< 递归函数最为典型的是计算斐波那契数列,即以下代码:

6. 函数指针

函数指针是用来存放函数地址的指针

函数的地址是函数的入口地址,也就是函数名

< 以下代码是函数指针的简单使用:包括函数指针的声明,使用,调用:

6.1 函数指针数组

保存若干个函数名的数组;

<以下代码是函数指针数组的声明,使用;

        19 行:p 作为数组名是常量,不能被赋值。一旦数组被定义,其地址就固定不变了。而普通指针是变量,可以被重新赋值。

总结:

1. 函数一般经理 “声明——调用——实现” ,若函数实现在 main 函数前,无需“ 声明 ”;

2. 函数传参有三种:全局变量,复制传递(值传递),地址传递(指针传递);

3. 复制传递(值传递)本质是拷贝,不会改变实参的数值;地址传递(指针传递)会影响到实参;

3. 整型数组传参至少需要两个形参,一个传递数组名,另一个传递数组个数;字符串数组只需传递字符串首地址即可;

4. 整型数组传参:复制传递(值传递):如 int a[ ],相当于指针函数 int * a ;地址传递(指针传递):int * a;

5. 指针函数是指针类型的函数,返回值是地址,注意局部变量不可以返回;

6. 函数指针( 如 int ( *p )( 形参 ) )是存放函数地址(函数名)的地址变量;

7. 函数指针数组( 如 int ( *p [ 10 ] )( 形参 ) )是存放函数地址(函数名)的地址变量数组,类似于指针数组 int * p [ 10 ];

8. 递归函数与函数指针的使用较困难,不要期望一口气掌握;

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐