C语言编程10大规范

本文是大厂c代码规范,有点长,有时间可以学习下。
1 代码总体原则
1、清晰第一
清晰性是易于维护、易于重构的程序必需具备的特征。代码首先是给人读的,好的代码应当可以像文章一样发声朗诵出来。
目前软件维护期成本占整个生命周期成本的40%~90%。根据业界经验,维护期变更代码的成本,小型系统是开发期的5倍,大型系统(100万行代码以上)可以达到100倍。业界的调查指出,开发组平均大约一半的人力用于弥补过去的错误,而不是添加新的功能来帮助公司提高竞争力。
一般情况下,代码的可阅读性高于性能,只有确定性能是瓶颈时,才应该主动优化。
2、简洁为美
简洁就是易于理解并且易于实现。代码越长越难以看懂,也就越容易在修改时引入错误。写的代码越多,意味着出错的地方越多,也就意味着代码的可靠性越低。因此,我们提倡大家通过编写简洁明了的代码来提升代码可靠性。
废弃的代码(没有被调用的函数和全局变量)要及时清除,重复代码应该尽可能提炼成函数。
3、选择合适的风格,与代码原有风格保持一致
产品所有人共同分享同一种风格所带来的好处,远远超出为了统一而付出的代价。在公司已有编码规范的指导下,审慎地编排代码以使代码尽可能清晰,是一项非常重要的技能。如果重构/ / 修改其他风格的代码时,比较明智的做法是根据 现有 代码 的 现有风格继续编写代码,或者使用格式转换工具进行转换成公司内部风格。
2、头文件
对于c语言来说,头文件的设计体现了大部分的系统设计。不合理的头文件布局是编译时间过长的根因,不合理的头文件实际上反映了不合理的设计。
1、头文件中适合放置接口的声明,不适合放置实现
头文件是模块(module)或单元(unit)的对外接口。头文件中应放置对外部的声明,如对外提供的函数声明、宏定义、类型定义等。
要求:
内部使用的函数(相当于类的私有方法)声明不应放在头文件中。
内部使用的宏、枚举、结构定义不应放入头文件中。
变量定义不应放在头文件中,应放在.c文件中。
变量的声明尽量不要放在头文件中,亦即尽量不要使用全局变量作为接口。变量是模块或单元的内部实现细节,不应通过在头文件中声明的方式直接暴露给外部,应通过函数接口的方式进行对外暴露。即使必须使用全局变量,也只应当在.c中定义全局变量,在.h中仅声明变量为全局的。
2、头文件应当职责单一,切忌依赖复杂
头文件过于复杂,依赖过于复杂是导致编译时间过长的主要原因。很多现有代码中头文件过大,职责过多,再加上循环依赖的问题,可能导致为了在.c中使用一个宏,而包含十几个头文件。
错误示例:某平台定义word类型的头文件:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

typedef unsigned short word;

这个头文件不但定义了基本数据类型word,还包含了stdio.h syslib.h等等不常用的头文件。如果工程中有10000个源文件,而其中100个源文件使用了stdio.h的printf,由于上述头文件的职责过于庞大,而word又是每一个文件必须包含的,从而导致stdio.h/syslib.h等可能被不必要的展开了9900次,大大增加了工程的编译时间。
3、头文件应向稳定的方向包含
头文件的包含关系是一种依赖,一般来说,应当让不稳定的模块依赖稳定的模块,从而当不稳定的模块发生变化时,不会影响(编译)稳定的模块。
就我们的产品来说,依赖的方向应该是:产品依赖于平台,平台依赖于标准库。某产品线平台的代码中已经包含了产品的头文件,导致平台无法单独编译、发布和测试,是一个非常糟糕的反例。除了不稳定的模块依赖于稳定的模块外,更好的方式是两个模块共同依赖于接口,这样任何一个模块的内部实现更改都不需要重新编译另外一个模块。在这里,我们假设接口本身是最稳定的。
4、每一个 .c 文件应有一个同名 .h 文件,用于声明需要对外公开的接口
如果一个.c文件不需要对外公布任何接口,则其就不应当存在,除非它是程序的入口,如main函数所在的文件。
现有某些产品中,习惯一个.c文件对应两个头文件,一个用于存放对外公开的接口,一个用于存放内部需要用到的定义、声明等,以控制.c文件的代码行数。编者不提倡这种风格。这种风格的根源在于源文件过大,应首先考虑拆分.c文件,使之不至于太大。另外,一旦把私有定义、声明放到独立的头文件中,就无法从技术上避免别人include之,难以保证这些定义最后真的只是私有的。
5、禁止头文件循环依赖
头文件循环依赖,指a.h包含b.h,b.h包含c.h,c.h包含a.h之类导致任何一个头文件修改,都导致所有包含了a.h/b.h/c.h的代码全部重新编译一遍。而如果是单向依赖,如a.h包含b.h,b.h包含c.h,而c.h不包含任何头文件,则修改a.h不会导致包含了b.h/c.h的源代码重新编译。
6、  .c/.h文件禁止包含用不到的头文件
很多系统中头文件包含关系复杂,开发人员为了省事起见,可能不会去一一钻研,直接包含一切想到的头文件,甚至有些产品干脆发布了一个god.h,其中包含了所有头文件,然后发布给各个项目组使用,这种只图一时省事的做法,导致整个系统的编译时间进一步恶化,并对后来人的维护造成了巨大的麻烦。
7、  头文件应当自包含
简单的说,自包含就是任意一个头文件均可独立编译。如果一个文件包含某个头文件,还要包含另外一个头文件才能工作的话,就会增加交流障碍,给这个头文件的用户增添不必要的负担。
示例:如果a.h不是自包含的,需要包含b.h才能编译,会带来的危害:每个使用a.h头文件的.c文件,为了让引入的a.h的内容编译通过,都要包含额外的头文件b.h。额外的头文件b.h必须在a.h之前进行包含,这在包含顺序上产生了依赖。
注意:该规则需要与“.c/.h文件禁止包含用不到的头文件”规则一起使用,不能为了让a.h自包含,而在a.h中包含不必要的头文件。a.h要刚刚可以自包含,不能在a.h中多包含任何满足自包含之外的其他头文件。
8、总是编写内部 #include 保护符( #define  保护)
多次包含一个头文件可以通过认真的设计来避免。如果不能做到这一点,就需要采取阻止头文件内容被包含多于一次的机制。通常的手段是为每个文件配置一个宏,当头文件第一次被包含时就定义这个宏,并在头文件被再次包含时使用它以排除文件内容。所有头文件都应当使用#define 防止头文件被多重包含,命名格式为filename_h,为了保证唯一性,更好的命名是projectname_path_filename_h。
注:没有在宏最前面加上单下划线_,是因为一般以单下划线_和双下划线__开头的标识符为ansic等使用,在有些静态检查工具中,若全局可见的标识符以_开头会给出告警。
定义包含保护符时,应该遵守如下规则:
保护符使用唯一名称;
不要在受保护部分的前后放置代码或者注释。
正确示例:假定vos工程的timer模块的timer.h,其目录为vos/include/timer/timer.h,应按如下方式保护:
#ifndef vos_include_timer_timer_h
#define vos_include_timer_timer_h
...
#endif
也可以使用如下简单方式保护:
#ifndef timer_h
#define timer_h
...
#endif
例外情况:头文件的版权声明部分以及头文件的整体注释部分(如阐述此头文件的开发背景、使用注意事项等)可以放在保护符(#ifndef xx_h)前面。
9、禁止在头文件中定义变量
在头文件中定义变量,将会由于头文件被其他.c文件包含而导致变量重复定义。
10、只能通过包含头文件的方式使用其他 .c 提供的接口,禁止在.c 中通过 extern 的方式使用外部函数接口、变量
若a.c使用了b.c定义的foo()函数,则应当在b.h中声明extern int foo(int input);并在a.c中通过#include 来使用foo。禁止通过在a.c中直接写extern int foo(int input);来使用foo,后面这种写法容易在foo改变时可能导致声明和定义不一致。
11、禁止在 extern c 中包含头文件
在extern c中包含头文件,会导致extern c嵌套,visual studio对extern c嵌套层次有限制,嵌套层次太多会编译错误。
在extern c中包含头文件,可能会导致被包含头文件的原有意图遭到破坏。
错误示例:
extern “c”
{
#include “xxx.h”
...
}
正确示例:
#include “xxx.h”
extern “c”
{
...

12、一个模块通常包含多个 .c 文件,建议放在同一个目录下,目录名即为模块名。为方便外部使用者,建议每一个模块提供一个 .h ,文件名为目录名
需要注意的是,这个.h并不是简单的包含所有内部的.h,它是为了模块使用者的方便,对外整体提供的模块接口。以google test(简称gtest)为例,gtest作为一个整体对外提供c++单元测试框架,其1.5版本的gtest工程下有6个源文件和12个头文件。但是它对外只提供一个gtest.h,只要包含gtest.h即可使用gtest提供的所有对外提供的功能,使用者不必关系gtest内部各个文件的关系,即使以后gtest的内部实现改变了,比如把一个源文件c拆成两个源文件,使用者也不必关心,甚至如果对外功能不变,连重新编译都不需要。对于有些模块,其内部功能相对松散,可能并不一定需要提供这个.h,而是直接提供各个子模块或者.c的头文件。
比如产品普遍使用的vos,作为一个大模块,其内部有很多子模块,他们之间的关系相对比较松散,就不适合提供一个vos.h。而vos的子模块,如memory(仅作举例说明,与实际情况可能有所出入),其内部实现高度内聚,虽然其内部实现可能有多个.c和.h,但是对外只需要提供一个memory.h声明接口。
13、如果一个模块包含多个子模块,则建议每一个子模块提供一个对外的 .h,文件名为子模块名
降低接口使用者的编写难度
14、头文件不要使用非习惯用法的扩展名,如 .inc
目前很多产品中使用了.inc作为头文件扩展名,这不符合c语言的习惯用法。在使用.inc作为头文件扩展名的产品,习惯上用于标识此头文件为私有头文件。但是从产品的实际代码来看,这一条并没有被遵守,一个.inc文件被多个.c包含比比皆是。
除此之外,使用.inc还导致source insight、visual stduio等ide工具无法识别其为头文件,导致很多功能不可用,如“跳转到变量定义处”。虽然可以通过配置,强迫ide识别.inc为头文件,但是有些软件无法配置,如visual assist只能识别.h而无法通过配置识别.inc。
15、同一产品统一包含头文件排列方式
常见的包含头文件排列方式:功能块排序、文件名升序、稳定度排序。
正确示例1:以升序方式排列头文件可以避免头文件被重复包含:
#include
#include
#include
#include
#include
正确示例2:以稳定度排序,建议将不稳定的头文件放在前面,如把产品的头文件放在平台的头文件前面:
#include
#include
相对来说,product.h修改的较为频繁,如果有错误,不必编译platform.h就可以发现product.h的错误,可以部分减少编译时间。
3 函数
函数设计的精髓:编写整洁函数,同时把代码有效组织起来。
整洁函数要求:代码简单直接、不隐藏设计者的意图、用干净利落的抽象和直截了当的控制语句将函数有机组织起来。
代码的有效组织包括:逻辑层组织和物理层组织两个方面。逻辑层,主要是把不同功能的函数通过某种联系组织起来,主要关注模块间的接口,也就是模块的架构。物理层,无论使用什么样的目录或者名字空间等,需要把函数用一种标准的方法组织起来。例如:设计良好的目录结构、函数名字、文件组织等,这样可以方便查找。
1、一个函数仅完成一件功能
一个函数实现多个功能给开发、使用、维护都带来很大的困难。
将没有关联或者关联很弱的语句放到同一函数中,会导致函数职责不明确,难以理解,难以测试和改动。
2、重复代码应该尽可能提炼成函数
重复代码提炼成函数可以带来维护成本的降低。
重复代码是我司不良代码最典型的特征之一。在“代码能用就不改”的指导原则之下,大量的烟囱式设计及其实现充斥着各产品代码之中。新需求增加带来的代码拷贝和修改,随着时间的迁移,产品中堆砌着许多类似或者重复的代码。
项目组应当使用代码重复度检查工具,在持续集成环境中持续检查代码重复度指标变化趋势,并对新增重复代码及时重构。当一段代码重复两次时,即应考虑消除重复,当代码重复超过三次时,应当立刻着手消除重复。
3、避免函数过长,新增函数不超过 50 行 (非空非注释行)
过长的函数往往意味着函数功能不单一,过于复杂。
函数的有效代码行数,即nbnc(非空非注释行)应当在[1,50]区间。
例外:某些实现算法的函数,由于算法的聚合性与功能的全面性,可能会超过50行。
延伸阅读材料:业界普遍认为一个函数的代码行不要超过一个屏幕,避免来回翻页影响阅读;一般的代码度量工具建议都对此进行检查,例如logiscope的函数度量:number of statement (函数中的可执行语句数)建议不超过20行,qa c建议一个函数中的所有行数(包括注释和空白行)不超过50行。
4、避免函数的代码块嵌套过深,新增函数的代码块嵌套不超过4层
函数的代码块嵌套深度指的是函数中的代码控制块(例如:if、for、while、switch等)之间互相包含的深度。每级嵌套都会增加阅读代码时的脑力消耗,因为需要在脑子里维护一个“栈”(比如,进入条件语句、进入循环„„)。应该做进一步的功能分解,从而避免使代码的阅读者一次记住太多的上下文。优秀代码参考值:[1, 4]。
错误示例:代码嵌套深度为5层:
void serial (void)
{
    if (!received)
    {
        tmocount = 0;
         switch (buff)
        {
            case aisgflg:
                if ((tibuff.count > 3)&& ((tibuff.buff[0] == 0xff) || (tibuf.buff[0] == curpa.addr)))
                {
                    flg7e = false;
                    received = true;
                }
                else
                {
                    tibuff.count = 0;
                    flg7d = false;
                    flg7e = true;
                }
                break;
            default:
                break;
        }
    }
}
5、 可重入函数应避免使用共享变量;若需要使用,则应通过互斥手段(关中断、信号量)对其加以保护
可重入函数是指可能被多个任务并发调用的函数。在多任务操作系统中,函数具有可重入性是多个任务可以共用此函数的必要条件。共享变量指的全局变量和static变量。编写c语言的可重入函数时,不应使用static局部变量,否则必须经过特殊处理,才能使函数具有可重入性。
示例:函数square_exam返回g_exam平方值。那么如下函数不具有可重入性。
int g_exam;
unsigned int example( int para )
{
    unsigned int temp;
    g_exam = para; // (**)
    temp = square_exam ( );
    return temp;
}
此函数若被多个线程调用的话,其结果可能是未知的,因为当(**)语句刚执行完后,另外一个使用本函数的线程可能正好被激活,那么当新激活的线程执行到此函数时,将使g_exam赋于另一个不同的para值,所以当控制重新回到“temp =square_exam ( )”后,计算出的temp很可能不是预想中的结果。此函数应如下改进。
int g_exam;
unsigned int example( int para )
{
    unsigned int temp;
    [申请信号量操作] // 若申请不到“信号量”,说明另外的进程正处于
    g_exam = para; //给g_exam赋值并计算其平方过程中(即正在使用此
    temp = square_exam( ); // 信号),本进程必须等待其释放信号后,才可继
    [释放信号量操作] // 续执行。其它线程必须等待本线程释放信号量后
    // 才能再使用本信号。
    return temp;
}
6、对参数的合法性检查,由调用者负责还是由接口函数负责,应在项目组/模块内应统一规定。缺省由调用者负责。
对于模块间接口函数的参数的合法性检查这一问题,往往有两个极端现象,即:要么是调用者和被调用者对参数均不作合法性检查,结果就遗漏了合法性检查这一必要的处理过程,造成问题隐患;要么就是调用者和被调用者均对参数进行合法性检查,这种情况虽不会造成问题,但产生了冗余代码,降低了效率。
7、对函数的错误返回码要全面处理
一个函数(标准库中的函数/第三方库函数/用户定义的函数)能够提供一些指示错误发生的方法。这可以通过使用错误标记、特殊的返回数据或者其他手段,不管什么时候函数提供了这样的机制,调用程序应该在函数返回时立刻检查错误指示。
8、设计高扇入,合理扇出(小于7)的函数
扇出是指一个函数直接调用(控制)其它函数的数目,而扇入是指有多少上级函数调用它。
扇出过大,表明函数过分复杂,需要控制和协调过多的下级函数;而扇出过小,例如:总是1,表明函数的调用层次可能过多,这样不利于程序阅读和函数结构的分析,并且程序运行时会对系统资源如堆栈空间等造成压力。通常函数比较合理的扇出(调度函数除外)通常是3~5。
扇出太大,一般是由于缺乏中间层次,可适当增加中间层次的函数。扇出太小,可把下级函数进一步分解多个函数,或合并到上级函数中。当然分解或合并函数时,不能改变要实现的功能,也不能违背函数间的独立性。扇入越大,表明使用此函数的上级函数越多,这样的函数使用效率高,但不能违背函数间的独立性而单纯地追求高扇入。公共模块中的函数及底层函数应该有较高的扇入。
较良好的软件结构通常是顶层函数的扇出较高,中层函数的扇出较少,而底层函数则扇入到公共模块中。
9、废弃代码(没有被调用的函数和变量) ) 要及时清除
程序中的废弃代码不仅占用额外的空间,而且还常常影响程序的功能与性能,很可能给程序的测试、维护等造成不必要的麻烦。
10、函数不变参数使用const
不变的值更易于理解/跟踪和分析,把const作为默认选项,在编译时会对其进行检查,使代码更牢固/更安全。
正确示例:c99标准 7.21.4.4 中strncmp 的例子,不变参数声明为const。
int strncmp(const char *s1, const char *s2, register size_t n)
{
    register unsigned char u1, u2;
    while (n-- > 0)
    {
        u1 = (unsigned char) *s1++;
        u2 = (unsigned char) *s2++;
        if (u1 != u2)
        {
            return u1 - u2;
        }
        if (u1 == '')
        {
            return 0;
        }
    }
    return 0;
}
11、函数应避免使用全局变量、静态局部变量和 i/o 操作,不可避免的地方应集中使用
带有内部“存储器”的函数的功能可能是不可预测的,因为它的输出可能取决于内部存储器(如某标记)的状态。这样的函数既不易于理解又不利于测试和维护。在c语言中,函数的static局部变量是函数的内部存储器,有可能使函数的功能不可预测。
错误示例:如下函数,其返回值(即功能)是不可预测的。
unsigned int integer_sum( unsigned int base )
{
    unsigned int index;
    static unsigned int sum = 0;// 注意,是static类型的。
    // 若改为auto类型,则函数即变为可预测。
    for (index = 1; index (b) ? (a) : (b))
int max_func(int a, int b) {
    return ((a) > (b) ? (a) : (b));
}
int testfunc()
{
    unsigned int a = 1;
    int b = -1;
    printf(macro: max of a and b is: %d , max_macro(++a, b));
    printf(func : max of a and b is: %d , max_func(a, b));
    return 0;
}
上面宏代码调用中,由于宏缺乏类型检查,a和b的比较变成无符号数的比较,结果是a 10))
    {
        printf(a: %d , a);
    }
    printf(b: %d , b);
}
作用函数参数来使用,参数的压栈顺序不同可能导致结果未知。
4、用括号明确表达式的操作顺序,避免过分依赖默认优先级
使用括号强调所使用的操作符,防止因默认的优先级与设计思想不符而导致程序出错;同时使得代码更为清晰可读,然而过多的括号会分散代码使其降低了可读性。
5、赋值操作符不能使用在产生布尔值的表达式上
示例:
x = y;
if (x != 0)
{
    foo ();
}
不能写成:
if (( x = y ) != 0)
{
    foo ();
}
或者更坏的:
if (x = y)
{
    foo ();
}
8 注释
 1、优秀的代码可 以自我解释,不通过注释即可轻易读懂
优秀的代码不写注释也可轻易读懂,注释无法把糟糕的代码变好,需要很多注释来解释的代码往往存在坏味道,需要重构。
错误示例:注释不能消除代码的坏味道:
/* 判断m是否为素数*/
/* 返回值:: 是素数,: 不是素数*/
int p(int m)
{
    int k = sqrt(m);
    for (int i = 2; i k)
        return 1;
    /* 遍历中没有发现整除的情况,返回*/
    else
        return 0;
}
重构代码后,不需要注释:
int isprimenumber(int num)
{
    int sqrt_of_num = sqrt (num);
    for (int i = 2; i = test_count_threshold))
{
    // process code
}
4、多个短语句(包括赋值语句)不允许写在同一行内 ,即一行只写一条语句
错误示例:
int a = 5; int b= 10; //不好的排版
正确示例:
int a = 5;
int b= 10;
5、if 、 for 、 do 、 while 、 case 、 switch 、 default 等语句独占一行
执行语句必须用缩进风格写,属于if、for、do、while、case、switch、default等下一个缩进级别;
一般写if、for、do、while等语句都会有成对出现的„{}‟,对此有如下建议可以参考:if、for、do、while等语句后的执行语句建议增加成对的“{}”;如果if/else配套语句中有一个分支有“{}”,那么另一个分支即使一行代码也建议增加“{}”;添加“{”的位置可以在if等语句后,也可以独立占下一行;独立占下一行时,可以和if在一个缩进级别,也可以在下一个缩进级别;但是如果if语句很长,或者已经有换行,建议“{”使用独占一行的写法。
6、在两个以上的关键字、变量、常量进行对等操作时,它们之间的操作符之前、之后或者前后要加空格 ;进行非对等操作时,如果是关系密切的立即操作符(如-> > ),后不应加空格
采用这种松散方式编写代码的目的是使代码更加清晰。
在已经非常清晰的语句中没有必要再留空格,如括号内侧(即左括号后面和右括号前面)不需要加空格,多重括号间不必加空格,因为在c语言中括号已经是最清晰的标志了。在长语句中,如果需要加的空格非常多,那么应该保持整体清晰,而在局部不加空格。给操作符留空格时不要连续留两个以上空格。
正确示例:
1、逗号、分号只在后面加空格。
int a, b, c;
2、比较操作符, 赋值操作符=、 +=,算术操作符+、%,逻辑操作符&&、&,位域操作符<= max_time_value)
a = b + c;
a *= 2;
a = b ^ 2;
3、!、~、++、--、&(地址操作符)等单目操作符前后不加空格。
*p = 'a'; // 内容操作*与内容之间
flag = !is_empty; // 非操作!与内容之间
p = &mem; // 地址操作& 与内容之间
i++; 
 4、->、.前后不加空格。
p->id = pid; // ->指针前后不加空格
5、if、for、while、switch等与后面的括号间应加空格,使if等关键字更为突出、明显。
if (a >= b && c > d)
7、注释符(包括/**/、//)与注释内容之间要用一个空格进行分隔
8、源程序中关系较为紧密的代码应尽可能相邻
10 代码编辑编译 
1、使用编译器的最高告警级别,理解所有的告警,通过修改代码而不是降低告警级别来消除所有告警
编译器是你的朋友,如果它发出某个告警,这经常说明你的代码中存在潜在的问题。
2、在产品软件(项目组)中,要统一编译开关、静态检查选项以及相应告警清除策略
如果必须禁用某个告警,应尽可能单独局部禁用,并且编写一个清晰的注释,说明为什么屏蔽。某些语句经编译/静态检查产生告警,但如果你认为它是正确的,那么应通过某种手段去掉告警信息。
4、本地构建工具(如 pc-lint)的配置应该和持续集成的一致
两者一致,避免经过本地构建的代码在持续集成上构建失败
5、 使用版本控制(配置管理)系统,及时签入通过本地构建的代码,确保签入的代码不会影响构建成功
及时签入代码降低集成难度。
6、要小心地使用编辑器提供的块拷贝功能编程


2017全球最佳性能手机,华为荣耀V9是唯一国产手机
谈谈XR关键技术及VR/AR/MR/XR关系
基于3nm支持的A17处理器值得期待吗
无线电波的接收原理
LBB315PA-020位移传感器使用方法
C语言编程10大规范
“柔性时代”来临:柔性可穿戴传感器正向产业化方向发展
七夕礼物怎么选择?推荐这几款运动蓝牙耳机
电动汽车成为新能源的主流的趋势明显
2018年Q2德国线上市场手机品牌排名:华为、荣耀手机突破三星独霸局面
国产车规级MCU和国际厂商的MCU相比差距明显?
理想汽车将在2022年使用NVIDIA Orin系统级芯片
AMETEK Sorensen 发布带有触摸显示屏的SGX系列直流程控电源
日本Sanko推出可进行洗鞋子的迷你洗衣机
探讨赛普拉斯的物联网解决方案
盛弘增强型静止无功发生器ASVG为某探井队电能质量保驾护航
32寸面板涨价,电视面板有望止跌
一经推出就引爆市场,OPPOR11夺得多项排名冠军背后有哪些实力?
MicroLED 在汽车显示器领域前景广阔
食堂农药残留检测仪的功能介绍