0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

几个实用的C语言知识点

硬件攻城狮 来源:硬件攻城狮 2023-02-15 10:25 次阅读

一、变量与值得比较

1、布尔变量与零值的比较

不可将布尔变量直接与 TRUE、 FALSE或者 1、 0进行比较 。据布尔类型的语义,零值为“ 假”(记为 FALSE),任何非零值都是“ 真”(记为TRUE)。

TRUE的值究竟是什么并没有统一的标准。例如 Visual C++ 将 TRUE定义为 1, 而 Visual Basic则将 TRUE定义为-1 。

假设布尔变量名字为 flag,它与零值比较的标准 if语句如下:

if (flag) // 表示flag为真
if (!flag) // 表示flag为假

其它的用法都属于不良风格,例如:

if (flag == TRUE)
if (flag == 1 )
if (flag == FALSE)
if (flag == 0)

2、整形变量与零值的比较

应当将整型变量用“ ==” 或“ !=” 直接与 0比较 。假设整型变量的名字为 value,它与零值比较的标准 if语句如下:

if (value == 0)
if (value != 0)

不可模仿布尔变量的风格而写成:

if (value) // 会让人误解 value是布尔变量
if (!value)

3、浮点变量与零值的比较

不可将浮点变量用“ ==” 或“ !=” 与任何数字比较 。千万要留意, 无论是 float还是 double类型的变量, 都有精度限制。

所以一定要避免将浮点变量用“ ==” 或“ !=” 与数字比较,应该设法转化成“ >=” 或“ <=” 形式。假设浮点变量的名字为 x,应当 将:

if (x == 0.0) // 隐含错误的比

转化为:

if ((x>=-EPSINON) && (x<=EPSINON))

其中 EPSINON是允许的误差(即精度) 。

4、指针变量与零值的比较

应当将指针变量用“ ==” 或“ !=” 与 NULL比较 。指针变量的零值是“ 空”(记为 NULL)。

尽管 NULL 的值与 0相同,但是两者意义不同。假设指针变量的名字为 p,它与零值比较的标准 if语句如下:

if (p == NULL) // p与 NULL显式比较,强调 p是指针变量
if (p != NULL)

不要写成:

if (p == 0) // 容易让人误解 p是整型变量
if (p != 0)

或者:

if (p) // 容易让人误解p是布尔变量
if (!p)

二、变量及基本运算

1、整型数

如果我们确定整数非负,就应该使用unsigned int而不是int。

有些处理器处理无符号unsigned 整形数的效率远远高于有符号signed整形数(这是一种很好的做法,也有利于代码具体类型的自解释)。

因此,在一个紧密循环中,声明一个int整形变量的最好方法是:

registerunsignedint variable_name;

记住,整形in的运算速度高浮点型float,并且可以被处理器直接完成运算,而不需要借助于FPU(浮点运算单元)或者浮点型运算库。

尽管这不保证编译器一定会使用到寄存器存储变量,也不能保证处理器处理能更高效处理unsigned整型,但这对于所有的编译器是通用的。

例如在一个计算包中,如果需要结果精确到小数点后两位,我们可以将其乘以100,然后尽可能晚的把它转换为浮点型数字。

2、除法和取余数

在标准处理器中,对于分子和分母,一个32位的除法需要使用20至140次循环操作。

除法函数消耗的时间包括一个常量时间加上每一位除法消耗的时间。

Time (numerator / denominator) = C0 + C1* log2 (numerator / denominator)
     = C0 + C1 * (log2 (numerator) - log2 (denominator)).

对于ARM处理器,这个版本需要20+4.3N次循环。这是一个消耗很大的操作,应该尽可能的避免执行。

有时,可以通过乘法表达式来替代除法。例如,假如我们知道b是正数并且b*c是个整数,那么(a/b)>c可以改写为a>(c*b)

如果确定操作数是无符号unsigned的,使用无符号unsigned除法更好一些,因为它比有符号signed除法效率高。

3、取模的一种替代方法

我们使用取余数操作符来提供算数取模。但有时可以结合使用if语句进行取模操作。考虑如下两个例子:

uint modulo_func1 (uint count)
{
   return (++count % 60);
}

uint modulo_func2 (uint count)
{
   if (++count >= 60)
      count = 0;
   return (count);
}

优先使用if语句,而不是取余数运算符,因为if语句的执行速度更快。这里注意新版本函数只有在我们知道输入的count结余0至59时在能正确的工作。

4、使用数组下标

如果你想给一个变量设置一个代表某种意思的字符值,你可能会这样做:

switch ( queue )
{
case0 :   letter = 'W';
   break;
case1 :   letter = 'S';
   break;
case2 :   letter = 'U';
   break;
}

或者这样做:

if ( queue == 0 )
  letter = 'W';
elseif ( queue == 1 )
  letter = 'S';
else
  letter = 'U';

一种更简洁、更快的方法是使用数组下标获取字符数组的值。如下:

staticchar *classes="WSU";
letter = classes[queue];

5、使用别名

考虑如下的例子:

void func1( int *data )
{
    int i;
 
    for(i=0; i<10; i++)
    {
          anyfunc( *data, i);
    }
}

尽管*data的值可能从未被改变,但编译器并不知道anyfunc函数不会修改它,所以程序必须在每次使用它的时候从内存中读取它。如果我们知道变量的值不会被改变,那么就应该使用如下的编码:

void func1( int *data )
{
    int i;
    int localdata;
 
    localdata = *data;
    for(i=0; i<10; i++)
    {
          anyfunc ( localdata, i);
    }
}

这为编译器优化代码提供了条件。

6、局部变量的类型

我们应该尽可能的不使用char和short类型的局部变量。对于char和short类型,编译器需要在每次赋值的时候将局部变量减少到8或者16位。

这对于有符号变量称之为有符号扩展,对于无符号变量称之为零扩展。这些扩展可以通过寄存器左移24或者16位,然后根据有无符号标志右移相同的位数实现,这会消耗两次计算机指令操作(无符号char类型的零扩展仅需要消耗一次计算机指令)。

可以通过使用int和unsigned int类型的局部变量来避免这样的移位操作。这对于先加载数据到局部变量,然后处理局部变量数据值这样的操作非常重要。无论输入输出数据是8位或者16位,将它们考虑为32位是值得的。

考虑下面的三个函数:

int wordinc (int a)
{
   return a + 1;
}
short shortinc (short a)
{
    return a + 1;
}
char charinc (char a)
{
    return a + 1;
}

尽管结果均相同,但是第一个程序片段运行速度高于后两者。

三、循环语句

1、多重循环

在多重循环中, 如果有可能, 应当将最长的循环放在最内层, 最短的循环放在最外层,以减少 CPU 跨切循环层的次数。例如示例 4-4(b)的效率比示例4-4(a)的高 :

3e64f6c4-ac8d-11ed-bfe3-dac502259ad0.png

2、循环体内的判断

如果循环体内存在逻辑判断, 并且循环次数很大, 宜将逻辑判断移到循环体的外面。

示例 4-4(c)的程序比示例 4-4(d)多执行了 N-1次逻辑判断。并且由于前者老要进行逻辑判断,打断了循环“ 流水线” 作业,使得编译器不能对循环进行优化处理, 降低了效率。

如果N非常大, 最好采用示例 4-4(d)的写法, 可以提高效率。如果 N非常小,两者效率差别并不明显,采用示例 4-4(c)的写法比较好, 因为程序更加简洁。

3e7a89a8-ac8d-11ed-bfe3-dac502259ad0.png

3、for 语句的循环控制变量

不可在 for 循环体内修改循环变量,防止 for 循环失去控制 。建议 for语句的循环控制变量的取值采用“ 半开半闭区间” 写法。

示例 4-5(a)中的 x值属于半开半闭区间“ 0 =< x < N”,起点到终点的间隔为 N,循环次数为 N。

示例 4-5(b)中的 x值属于闭区间“ 0 =< x <= N-1”,起点到终点的间隔为 N-1,循环次数为 N。

相比之下,示例 4-5(a)的写法更加直观,尽管两者的功能是相同的 。

3e979cb4-ac8d-11ed-bfe3-dac502259ad0.png

4、更快的for()循环

这是一个简单而高效的概念。通常,我们编写for循环代码如下:

for( i=0;  i<10;  i++){ ... }

i从0循环到9。如果我们不介意循环计数的顺序,我们可以这样写:

for( i=10; i--; ) { ... }

这样快的原因是因为它能更快的处理i的值–测试条件是:i是非零的吗?如果这样,递减i的值。对于上面的代码,处理器需要计算“计算i减去10,其值非负吗?

如果非负,i递增并继续”。简单的循环却有很大的不同。这样,i从9递减到0,这样的循环执行速度更快。

这里的语法有点奇怪,但确实合法的。循环中的第三条语句是可选的(无限循环可以写为for(;;))。如下代码拥有同样的效果:

for(i=10; i; i--){}

或者更进一步的:

for(i=10; i!=0; i--){}

这里我们需要记住的是循环必须终止于0(因此,如果在50到80之间循环,这不会起作用),并且循环计数器是递减的。使用递增循环计数器的代码不享有这种优化。

四、指针

我们应该尽可能的使用引用值的方式传递结构数据,也就是说使用指针,否则传递的数据会被拷贝到栈中,从而降低程序的性能。

函数通过参数接受结构数据的指针,如果我们确定不改变数据的值,我们需要将指针指向的内容定义为常量。例如:

void print_data_of_a_structure ( const Thestruct  *data_pointer)
{
    ...printf contents of the structure...
}

这个示例告诉编译器函数不会改变外部参数的值(使用const修饰),并且不用在每次访问时都进行读取。

同时,确保编译器限制任何对只读结构的修改操作从而给予结构数据额外的保护。

五、懒检测开发

if(a>10 && b=4)这样的语句中,确保AND表达式的第一部分最可能较快的给出结果(或者最早、最快计算),这样第二部分便有可能不需要执行。

六、用switch()函数替代if…else…

对于涉及if…else…else…这样的多条件判断,例如:

if( val == 1)
    dostuff1();
elseif (val == 2)
    dostuff2();
elseif (val == 3)
    dostuff3();

使用switch可能更快:

switch( val )
{
    case1: dostuff1(); break;

    case2: dostuff2(); break;

    case3: dostuff3(); break;
}

在if()语句中,如果最后一条语句命中,之前的条件都需要被测试执行一次。switch允许我们不做额外的测试。如果必须使用if…else…语句,将最可能执行的放在最前面。

审核编辑 :李倩


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • C语言
    +关注

    关注

    180

    文章

    7604

    浏览量

    136710
  • 编译器
    +关注

    关注

    1

    文章

    1624

    浏览量

    49109
  • 变量
    +关注

    关注

    0

    文章

    613

    浏览量

    28361

原文标题:几个实用的 C 语言知识点

文章出处:【微信号:mcu168,微信公众号:硬件攻城狮】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    一些小众的C语言知识点

    一些小众的C语言知识点,可能会比较有趣或者怪异,分享给大家看一下。
    发表于 09-26 10:16 659次阅读

    嵌入式C语言知识点总结

    怎么才能做好嵌入式开发?学好C语言吧!今天就来推荐一篇大佬写的嵌入式C语言知识点总结。
    发表于 09-27 09:53 1172次阅读

    C语言链表知识点(2)

    C语言链表知识点(2)
    发表于 08-22 10:38 325次阅读
    <b class='flag-5'>C</b><b class='flag-5'>语言</b>链表<b class='flag-5'>知识点</b>(2)

    C51单片机及C语言知识点必备秘籍

      电子发烧友网讯:应广大电子发烧友网读者要求,本电子书《C51单片机及C语言知识点必备秘籍》为《单片机关键知识点全攻略》单片机系列教程及《
    发表于 07-30 13:59 9847次阅读

    【信盈达】C语言知识点的总结

    、算法说明:学习单片机C一般只需要前9个知识点即可进行产品开发,但要学习嵌入式C还需要要掌握:指针、结构体、链表、宏定义等知识点。二、单片机C
    发表于 10-08 14:41

    C语言程序小知识点总结

    C语言总结(stm32嵌入式开发)文章目录C语言总结(stm32嵌入式开发)c程序小知识点总结1
    发表于 11-05 07:45

    浅谈几个C语言基础知识点

    文章目录前言一、位操作二、使用步骤前言这里主要是简单的复习一下几个 C 语言基础知识点,引导那些 C
    发表于 12-09 07:14

    C51语言的基础知识点实例讲解

    本文档的主要内容详细介绍的是C51语言的18个基础知识点实例讲解包括了:C51控制语句和C51数组和指针
    发表于 06-04 17:52 12次下载
    <b class='flag-5'>C</b>51<b class='flag-5'>语言</b>的基础<b class='flag-5'>知识点</b>实例讲解

    C语言学习入门知识点/干货

    C语言知识点总结
    的头像 发表于 07-18 17:54 5930次阅读
    <b class='flag-5'>C</b><b class='flag-5'>语言</b>学习入门<b class='flag-5'>知识点</b>/干货

    STM32中重要的C语言知识点总结

    的一些例程中,遇到不懂的C语言知识,再去查相关的知识点,这样印象才会深刻些。 下面就列出了一些STM32中重要的C
    的头像 发表于 04-25 16:42 2797次阅读
    STM32中重要的<b class='flag-5'>C</b><b class='flag-5'>语言</b><b class='flag-5'>知识点</b>总结

    嵌入式C语言知识点总结

    导读:怎么做好嵌入式?相信这个问题无论问谁你都会得到一句学好C语言!今天推荐一篇大佬写的嵌入式C语言知识点总结,非常值得一读。
    的头像 发表于 04-13 11:12 2707次阅读

    C语言C++面试知识点总结

    相对而言,C语言C++相关的面试题比较少见,没有Java方向写的人那么多,这是一篇 C 语言C
    的头像 发表于 05-12 14:59 1456次阅读

    C语言C++面试知识点总结

    相对而言,C语言C++相关的面试题比较少见,没有Java方向写的人那么多,这是一篇 C 语言C
    的头像 发表于 05-13 11:59 1859次阅读

    C语言最重要的知识点

    C语言知识点总结.doc
    发表于 02-16 16:37 9次下载

    C语言基础知识点

    C语言是单片机开发中的必备基础知识,这里就列举部分STM32学习中会遇见的C 语言基础知识点
    的头像 发表于 05-31 09:07 949次阅读
    <b class='flag-5'>C</b><b class='flag-5'>语言</b>基础<b class='flag-5'>知识点</b>