嵌入式Linux C语言程序设计八

函数

8.1 函数基础

函数就是一个能完成特定功能的代码模块,其程序独立,可以给函数传递参数,也可以得到返回值.

8.1.1 函数定义和声明

  1. 函数定义
    函数是一个完成特定功能的代码模块,通常有参数,也可以没有参数;通常要求有返回值,也可以是空值.
<数据类型> <函数名称>(<形式参数说明>)
{
    /***
        语句序列;
                
    */
    return[(<表达式>)];
     
}

函数名称是一个标识符,要求符合标识符的命名规则.
数据类型是整个函数返回值的类型.这里可以包括存储类型说明符,数据类型说明符以及时间域说明符.如果函数不需要有返回值时,函数类型说明符可以写为void.
参数说明就是参数列表,可以是任意类型的变量,积压参数之间用逗号分隔.在进行函数调用时,调用函数将赋予这些形式参数实际的值.

函数名后有一对花括号,"{}"的内容称为函数体.函数体中有若干条语句(大小或等于0个),实现特定的功能.注意:在函数体中,表达式语句是使用的变量必须事先已有说明,否则不能使用.

return [(表达式)] 语句中表达式的值,要求和函数名前的数据类型保持一致,如果数据类型为void 可以省略,也可以写成"return;"

void fun()
{
    printf("Welcome!\n");
}
void fun(int a,char *name)
{
    printf("%d:Welcome %s\n",a,name);
}
  1. 函数声明
    在前面已经介绍了函数的定义.“定义"是对bbi数功能的确立,包括函数名,函数返回值类型,参数列表,函数体,是一个完整,独立的函数.在这里所说的"函数声明”,是为了把函数名,返回值类型以及形参类型,个数和顺序通知编译系统,以便在调用该函数时,编译系统时行对照检查,包括函数名是否正确,传递参数的类型,个数是否与形参一致.如若出现不对应情况,编译会有语法错误.
    了解了函数声明的作用后,就不难理解,在编程的过程中,可以简单地照写已定义的函数的首部,再加一个分号,就成为了对函数的"声明".在函数声明中也可以不写参数名,而是写参数的类型.在C语言中,函数声明称为函数原型(function prototype),用函数原型ANSI C的一个重要特点,它的作用主要是利用它在程序的编译阶段对调用函数的合法性进行全面检查.

在C语言中,用衣可以通过两种方法来进行函数声明.

  1. 如果函数调用前,没有对函数作声明,且同一源文件的前出现了该函数的定义,那么编译器就会记住它的参数数量和类型以及函数的返回值类型,即把它作为函数的声明,并将函数返回值的类型默认为int型.
  2. 如果在同一源文件的前面没有该函数定义,是需要提供该函数的函数原型.用户定义的函数原型通常可以一写在头文件中,通过头文件引用的方式来声明.
    函数原型的一般形式如下.
  • 函数类型 函数名(参数类型1,参数类型2,…);
  • 函数类型 函数名(参数类型1 参数名1,参数类型2 参数2,…);

实际上,如果在调用函数之前没有对函数时行声明,则编译系统会把第一次遇到的该函数形式(函数定义或函数调用) 作为函数的声明,并将函数返回值的类型默认为int型.

对于会局变量的声明可以加上extern标识,同样对于函数的声明,也可以使用extern.如果函数的声明可以加上extern标识.如果函数的声明中带有关键字extern,是告诉编译器这个函数是在别的源文件里定义的.还有一个关键字static,若全局变量前有static修饰,则变量只能在当前文件中被使用,同样若bbi数的声明前有static限制,是告诉编译器,该函数只能在当前文 件中被调用,外部文件不能调用.

8.1.2 函数的调用,参数传递和返回值

  1. 函数的调用
    函数的使用也叫函数的调用
    函数名称([参数])
#include <stdio.h>
void fun()
{
    printf("Welcome!\n");
}

int main()
{
    fun();
    return 0;
}

被调函数必须是已经声明了的函数,或者被调用函数的位置位于调用函数之前.

  1. 函数的参数传递
    在c语言中,传递参数主要有3种方式,
    (1)基本类型,
#include <stdio.h>
void exchange(int a ,int b)
{
    int t ;
    printf("&a=%p %b=%p\n",&a,&b);
    t = a;
    a = b ;
    b = t;
    printf("a = %d b = %d\n",a,b);
}

int main()
{
    int m = 10,n = 20;
    exchage(m,n);
    printf("&m = %p,&n=%p\n",&m,&n);
    printf("m = %d,n=%d\n",m,n);
    return 0;
}

(2)指针参数

#include <stdio.h>
void exchange(int *p,int *q)
{
    int t;
    printf("&p = %p,&q=%p p = %p q = %p\n",&p,&q,p,q);
    t = *p;
    *p = *q;
    *q = t;
    
    printf("*p=%d *q=%d\n\n",*p,*q);
}

int main()
{
    int m = 10,n = 20;
    
    exchange(&m,&n);
    printf("&m=%p &n=%p\n",&m,&n);
    printf("m=%d n=%d\n",m,n);
    
    return 0;
}

参数的值复制到函数中无论基本类型,还是指针地址.

(3)全局参数
可以直接大函数里使用.

int n 
double factorial();
int main()
{
    double s= 0;
    printf("input:");
    scanf("%d",&n);
    
    s = factorial();
    printf("%e\n",s);
    
    return 0;
}

double factorial()
{
    double ret = 1;
    int i ;
    for(i = 1;i<=n;i++)
    {
        ret*=i;
    }
    
    return ret;
}

  1. 函数的返回值
    函数的返回值
    (1) 函数的返回值只能通过 return 语句返回主调函数,return 语句的一般形式为
    return 表达式

或者

return (表达式)

该语句的功能是计算表达式的值,并返回给主调函数.在函数中允许有多个return 语句,但每次调用只能有一个return 语句被执行,因此只能返回一个值.

(2)函数返回值的类型和函数定义中函数的类型保持一致.如果两者不一致,则以函数定义中的类型为准,自动进行类型转换.
(3)如函数返回值为整型,在函数定义时可以省去类型说明.
(4)没有返回值的函数,可以明确定义为空型类型,类型说明符为void.

#include<stdio.h>
int fun(int n);

int main()
{
    int sum = 0,n;
    printf("input:");
    sum = scanf("%d",&n);
    sum = fun(n);
    printf("1+2+...+%d\n",n,sum);
    return 0;
}

int fun(int n )
{
    int i,sum = 0;
    for(i=1;i<=n;i++)
    {
        sum +=i;
    }
    
    return sum;
}

8.1.3 函数和数组

1.传递数组
当参数是组组形式时,基本质上也是一个指针

#include<stdio.h>
int test_array(int a[],int n,int *p)
{
    int i,sum = 0;
    *p = 0;
    for(i = 0;i<n;i++)
    {
        sum +=a[i];
        if(a[i]%2)
        {
            (*p)++;
        }
    }   
    return sum;
}

int main()
{
    int a[] = {9,12,2,3,29,31,40,80},n;
    int sum = 0,odd = 0;
    n = sizeof(a)/sizeof(int);
    sum = test_array(a,n,&odd);
    
    printf("sum=%d odd numbers count = %d\n",sum,odd);
    
    return 0;
    
}

当参数是数组形式时,本质是同级别的指针.

二级数组

#include <stdio.h>

int test_array(int a[],int n,int *p)
{
    int i,sum = 0;
    *p = 0;
    for(i = 0;i<n;i++)
    {
        sum +=a[i];
        if(a[i]%2)
        {
            (*p)++;
        }
    }   
    return sum;
}

int main()
{
    int a[2][3] = {{9,12,2},{3,29,31}},n
    int sum = 0,odd=0;
    n = sizeof(a)/sizeof(int);
    
    sum = test_array(a[0],n,&odd);
    
    printf("sum=%d odd numbers count = %d\n",sum,odd);
    
    return 0;
}

按行列处理二维数组

#include <stdio.h>
int test_array(int n,int m,int a[][m],int *p)
{
    int i,j,sum = 0;
    *p=0;
    for(i = 0;i<n;i++)
    {
        for(j=0;j<m;j++)
        {
            sum+= a[i][j];
            if(a[i][j]%2)
            {
                (*p)++;
            }
        }
    }
    return sum;
}

int main()
{
    int a[2][3] = {{9,12,2},{3,29,31}},n,m;
    int sum = 0,odd = 0;
    n = sizeof(a)/sizeof(a[0]);
    m = sizeof(a[0])/sizeof(int);
    sum = test_array(n,m,a,&odd);
    
    printf("sum = %d,odd numbers count = %d\n",sum,odd);
    
    return 0;
}
  1. 传递指针参数
    int test_array(int *a,int n int *p)
    若需要给子函数传递二维数组,应该写成下面的形式
    int test_array(int n,int m,int (*a)[m],int *p)
    int (*a)[m]是数组指针,指向一个有m个元素的数组

8.1.4 main函数的参数

int main(int argc,char* argv[])

int main(int argc,char **argv)

#include<stdio.h>
int main(int argc,char *argv[])
{
    int i;
    printf("argc=%d\n",argc);
    for(i = 0;i<argc;i++)
    {
        printf("argv[%d]=%s\n",i,argv[i]);
    }
    return 0;
}

8.2指针函数

8.2.1 指针函数的定义和使用.

返回指针的函数叫指针函数

#include <stdio.h>
#include <string.h>
char* mystring(void)
{
    char str[20] = {0};//局部变量,函数节结就清除
    strcpy(str,"Welcome");
    return str;
}

int main()
{
    printf("%s\n",mystring());
    return 0;
}
#include <stdio.h>
#include <string.h>
char* mystring(void)
{
    static char str[20] = {0};//局部变量,函数节结就清除
    strcpy(str,"Welcome");
    return str;
}

int main()
{
    printf("%s\n",mystring());
    return 0;
}

8.3 函数

8.3.1 函数指针的声明

函数指针是专门用来存放函数地址的指针.函数地址是一个函数的入口地址,函数名代表了函数的入口址.
当一个函数指针指向一个函数,就呆以通过这个指针来调用函数,可以将 函数作为参数传递验函数指针.
函数指针变量说明一般形式如下:
<数据类型> (*<函数指针名称>)(<参数说明列表>)
(*<函数指针名称>)中, *说明为指针,()不可缺省,表明为指向指针

#include <stdio.h>
int test(int a,int b,int(*pFunc)(int,int));
int plus(int a,int b);
int minus(int ,int);

int mai()
{
    int x = 5,y = 8;
    int(*pFunc)(int,int);
    pFunc = plus;
    printf("%d\n",(*pFunc)(x,y));
    
    pFunc = minus;
    printf("%d\n",(*pFunc)(x,y));
    
    printf("%d\n",test(15,5,plus));
    printf("%d\n",test(15,5,minus));
    
    return 0;
    
}
int plus(int a,int b)
{
    return (a+b);
}

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

int test(int a,int b,int(*pFunc)(int,int))
{
    return ((*pFunc)(a,b));
}

8.3.2 定义函数指针类型

有时为了书写方便,可以声明一个函数指针数据类型.
<数据类型> (*<函数指针数组名称>[大小])(<参数说明列表>)

#include <stdio.h>
int plus(int,int);
int minus(int,int);

int main()
{
   int (*pFunc[2](int,int));
   int i;
   pFunc[0] = plus;
   pFunc[1]=minus;
   for(int = 0 ;i<2;i++)
   {
       printf("%d\n"(*pFunc[i])(15,85));
  }
  return 0;
}

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

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

8.3.4 函数指针程序举例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int fun_int(const void *p,const void *q);
int fun_float(const void *p,const void *q);
int fun_string(const void *p,const void *q);

int main()
{
        int a[] = {98,1,24,15,78,29,36,10,91},i;
        float b[] = {34,213,92.123,14,91.231,55,60.345,120.1};
        char *c[] = {"hello","about","who","apple","banana","a","zoo"};

        qsort(a,sizeof(a)/sizeof(int),sizeof(int),fun_int);
        for(i = 0;i<sizeof(a)/sizeof(int);i++)
        {
                printf("%d",a[i]);
        }
        printf("\n");

        qsort(b,sizeof(a)/sizeof(float),sizeof(float),fun_float);

                for(i = 0;i<sizeof(b)/sizeof(float);i++)
                {
                        printf("%f",b[i]);
                }
        printf("\n");

        qsort(c,sizeof(c)/sizeof(char *),sizeof(char *),fun_string);
        for(i=0;i<sizeof(c)/sizeof(char*);i++)
        {
                printf("%s",c[i]);
        }
        printf("\n");

        return 0;
}

int fun_int(const void *p,const void *q) 
{
        return (*(int const *)p-*(int const*)q);
}

int fun_float(const void *p,const void *q) 
{
        float t = *(float const *)p - *(float const *)q;

        if(t>1e-6)
        {
                return 1;    
        }
        else if(t<-1e-6)
        {
                return -1; 
        }
        }
        else
        {
                return 0;
        }
}

int fun_string(const void *p,const void *q)
{
        return strcmp((const char *)p,(const char *)q);
}

8.4 递归函数

8.4.1 递归函数的定义

所谓递归函数是指一个函数的函数体中直接调用或间接调用了该函数自身的函数.

  1. 递推阶段
  2. 回归阶段
#include <stdio.h>
double factorial(int);

int main()
{
    double r;
    r = factorial(5);
    printf("5!=5lf\n",r);
    return 0;
}

double factorial(int n)
{
    if(n<=1)
    {
        return 1;
    }
    return (n*factorial(n-1));
}

8.4.2 函数调用机制说明

任何函数之间不能嵌套定义,调用函数与被调用函数之间相互独立(彼此可以调用).发生函数调用时,被调函数保护了调用函数的运行环境和返回地址 .使得调用函数的状态可以在被调函数运行返回后完全恢复,而且该状态与被调函数无关.
被调函数运行的代码虽是同一个函数的代码体,但由于调用点,调用时状态,返回点的不同,可以看作是函数的一个副本,一调用函数的代码无关,所以函数的代码是独立的,被调函数运行的栈空间,所以与调用函数之间的数据也是无关的,函数之间靠参数传递和返回值来联系,函数看作为黑盒

8.4.3 递归调用的形式.

递归调用有直接递归调用和间接递归调用两种形式.
直接递归即在函数中出现调用函数本身.

#include <stdio.h>

long fib(int n)
{
    if(n == 0||n==1)
    {
        return 1;
    }
    else
    {
        return (fib(n-1)+fib(b-2));
    }
}

int main()
{
    int i;
    for(i = 0;i<8;i++)
    {
        printf("%ld ",fib(i));
    }
    printf("\n");
    return 0;
}

8.4.4 递归的条件

一个问题能否用递归实现,看其是否具有以下特点

  1. 须有完成函数任务的语句.
  2. 一个确定是否能避免递归调用的测试.
  3. 一个递归调用语句
  4. 先测试,后递归调用.
#include <stdio.h>

void count(int val)
{
    if(val>1)
    {
        count(val-1);
    }
    printf("OK:%d\n",val);
}

int main()
{
    int n = 10;
    count(n);
    return 0;
}

8.5 回调函数

8.5.1 回调函数的定义

回调函数就是一个通过函数指针调用的函数,如果你把函数的指针(地址) 作为参数传递给另一个函数,当这个指针被用来调用其他指向的函数时,我们就说这是回调函数.

/*<数据类型> <数据名称> (<参数说明列表>)
{
  语名序列  
}
*/

8.5.2 回调函数实现机制

  1. 定义一个回函数
  2. 提供一个函数实现的一方在初始化的时候将回调函数指针注册给调用者.
  3. 当特定的事件或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理.

8.5.3 自定义回调函数

方式一:通过命名方式

#include <stdio.h>
typedef int(*CallBackFun(char *p));
int fun(char *p)
{
    printf("fun %s\n",p);
    return 0;
}

int Call(CallBackFun pCallBack,char *p)
{
    printf("call %s\n",p);
    pCallBack(p);
    return 0;
}

int main(int argc,const char *argv[])
{
    char *p = "hello";
    call(fun,p);
    return 0;
}

方式二:直接通过函数指针

#include <stdio.h>

int fun(char *p)
{
    printf("fun %s\n",p);
    return 0;
}

int call(int (*ptr)(char *p),char *p)
{
    printf("call\n",p);
    (*ptr)(p);
}

int main(int argc,const char *argv[])
{
    char *p = "hello";
    call(fun,p);
    return 0;
}

8.5.4 嵌入式开发中常见的回调函数

在嵌入式开发中常见的回调函数
(1) 信号注册函数(通过命名方式)
typedef void(*sighandler_t)(int)
sighandler_t signal(int signum,sighandler_t handler)

(2) 线程创建函数(直接通过函数指针)

#include <pthread_h>
int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine)(void *),void *arg);

8.6 attribute 机制价绍

GNU C 的一大特色就是__attribute__机制. __attribute__可以设函数属性(function attribute),变量属性(variable attribute)和类型属性(type attribute).
__attribute__书写特征是__attribute__前后都有两个下划线,并且后面会紧跟一对圆括号,括号里面是相应的__attribute__参数.
attribute__语法格式为__attribute((attribute-list)).

  1. 函数属性(function attribute)
    函数属性可以帮助开发者把一些特性添加到函数声明中,从而可以使编译器在错误检查方面的功能更强大.__attribute__机制也很容易同非GNU应用程序做到兼容.GNU C需要使用 -Wall编译选项来激活该功能,这是控制警告信息的一个很好的方法.下面介绍几个常见的属性参数.
    (1)attribute format
    该属性可以给被声明的函数上加上类似printf或scanf的特征.它可以使编译器检查函数声明和函数实际调用参数之间的格式化字符串是否匹配.该功能十分有用,尤其是处理一些很难发现的bug.
    format 的语法格式为format(archetype,string-index,first-to-check).
    format 属性告诉编译器,按照printf,scanf,strtime或strifmon的参数列表格式规则对该函数的参数进行检查."archetype"指定是哪种风格;"string-index"指定是哪种风格;"string-index"指定传入函数的第几个参数是格式化字符串;“first-to-check” 指定从函数的第几个参数开始按上述规则进行检查.
    __attribute__((format(printf,m,n))
    __attribute__((format(scanf,m,n)))
    1. m:是第几个参数为格式化字符串(format string).
    2. n:是参数集合中的第一即参数"…"里第一个参数在函数参数总排在第几.
/*m = 1;n=2*/
extern void myprint(const char *format,...) __attribute__((format(printf,1,2)));
/* m =2;n=3*/


(2) attribute noreturn
该属性通知编译器函数从不返回值,当遇到类似函数需要返回值而不可能运行到返回值处就已经退回出来的情况,该属性可以避免出现错误信息.

extern void exit(int) __attribute__((noreturn));
extern void abort(void) __attribute__((noreturn));
extern void myexit();

int test(int n)
{
    if(n>0)
    {
        myexit();
        /*程序不可能到达这里*/
    }
    else
    {
        return 0;
    }
}

编译会出warning

extern void myexit();
//改为
extern void myexit() __attribute__((noreturn));

(3) attribute const
该属性只能用于还有数值类型参数的函数上.当重复调用带有数值参数的函数时,由于返回值是相同的,所以此时编译器可以进行优化处理,除第一次需要运算外,其他只需要返回第一次的结果就可以,进而可提高效训练场.该属性主要适用于没有静态状(static state)和副作用的一些函数,并且返回值仅仅依赖输入的参数.

下面例子

extern int square(int n) __attribute__((const));
...
for(i = 0;i<100;i++)
{
    total +=square(5)+i;
}

通过添加"attribute((const))"声明,编译器只调用了函数一次,以后只是直接得到了相同的一个返回值.
事实上,const参数不能用在带有指针类型参数的函数中,因为该属性不但影响函数的参数值,同样也影响到了参数指向的数据.它可能会对代码本身产生严重甚至是不可恢复的后果.
并且,带有该属性的函数不能有任何副作用或是靜态的状态,所以类似getchar或time函数是不适合使用该属性的.

(4) -finstrument-functions
该参数可以使程序在编译时在函数的入口和出口处生instrumentatio n调用.恰好在函数入口之后并恰好在函数出口之前,将使用当函数的地址和调用地址来调用下面的profiling函数(在一些平台上,builtin_return address 不能在超过当前函数范围之外正常工作,所以调用地址信息可能对profiling函数是无效的.)

void __cyg_profile_func_enter(void *this_fn,void *call_site);
void __cyg_profile_func_exit(void *this_fn,void *call_site);

其中,第一个参数this_fn是当前函数起始地址.可在符号表中长到;第二叁数call_site是指调用处地址.

instrumentation 也可用于在其他函数中展开的内联函数.从概念上来说,profiling调用将指出在哪里进入和退出内联.这就意未着这种函数必须具有可寻址形式,如果函数包含内联,而所有使用到该函数的程序都 要把该内联展开,这会额地增加代码长度.如果要在C语言代码中使用extern inline声明,必须提供这种函数的可寻址形式.

可对函数指定no_instrument_function 属性就不会进行instrument ation 操作.例如,可以在以下情况下使用no_instrument_function属性:上面列出的profiling函数,高优级的中断例 程以及任何不能保证profiling正常调用的函数.

如果使用了 -finstrument-functions,将在绝大数用户编译的函数入口和出口点调用,profiling函数.使用该属性,将不进行instrumentation操作.

(5)constructor/destructor
若函数被设定为constructor属性,则该函数会在main函数执行之前被自动执行.类似地,若函数被设定为destructor属性,则该函数会在main函执行之后或者exit被调用后被自动执行.拥有此类属性的函数经常隐式地用程序的初始化数据方面.

(6)同时使用多个属性
可以同在同一个函数声明里使用多个 attribute,并且实际应用中这种情况是十分常见的,使用方式上,可以选两个单独的__attribute__,或者把它们写在一超,可以参考下面的例子.

/*把类似printf的消息传递给stderr并退出*/
extern void die(const char *format,...)
__attribute__((noreturn))
__attribute__((format(printf,1,2)));
extern void die(const char *format,...)
__attribute__((noreturn,format(printf,1,2)));

如果带有该属性的自定义函数追加到库的头文件里,那么所有调用该函数的程序都要做相应的检查.

(7)和非GNU编译器的兼容性.

更多的属性
http://gcc.gnu.org/onlinedocs

  1. 变量属性(variable attribute)
    (1) aligned(alignment)
    该属性规定变量或结构体成员的最小的对齐格式,以字节为单位.
    int x __attribute__((aligned(16))) = 0;

(2) packed
使用该属性可以使得变量或者结构成员使用最的对齐方式,即对变量是一字节对齐,对域(field)是位对齐

struct test
{
    char a;
    int x[2] __attribute__((packed));
}
  1. 类型属性(type attribute)
    关键字__attirbute__也可以对结构体(struct)或者共用体(union)进行属性设置.大致有6个参数可以被设定,即aligned,packed\transparent_union, unused,deprecated,may_alias.

(1) aligned(alignment)
该属性设定一个指定大小的对齐格式(以字节为单位),例如:

struct S {short f[3];}__attribute__((aligned(8)));
typedef int more_aligned_int __attribute__((aligned(8)));

该声明将强制编译器确保(尽它所能)变量类型为 struct S 或者 more_aligned_int 的变量在分配空间时采用8字节对齐方式.

(2)packed
使用该属性对struct 或者union类型进行定义,设定其类型的第一个变量的内存约束,当用在enum类型型定义时,暗示了应该使用最小完整的类型(it indicates that the smallest integeral type should be used )

struct my_unpacked_struct
{
    char c;
    int i;
};

struct my_packed_struct
{
    char c;
    int i;
    struct my_unpacked_struct s;
} __attribute__ ((__packed__))
  1. 变量属性与类型属性举例
    下面的例子中使用__attribute__属性定义了一些结构体及其变量,并给出了输出结果和对结果的分析.
struct p
{
    int a;
    char b;
    char c;
}__attribute__((aligned(4))) pp;

struct q
{
    int a;
    char b;
    struct p qn;
    char c;
}__attribute__((aligned(8))) qq;

int main()
{
    printf("sizeof(int)=%d,sizeof(short)=%d,sizeof(char)=%d\n",sizeof(int),sizeof(short),sizeof(char));
    printf("pp=%d,qq=%d\n",sizeof(pp),sizeof(qq));
    
   return  0;
}
Logo

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

更多推荐