这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » 软件与操作系统 » 【学习会】一起学习C语言之重温C语言

共22条 2/3 1 2 3 跳转至
院士
2018-05-25 17:22:39     打赏
11楼
第七章、基本数据类型

这一章主要还是讲概念,重知识讲解,平时对这些概念基本忽略,而是直接使用的多。

本章讲了进制。十进制,十六进制,二进制,八进制,这些都是数字,其值是相等的,不会因为进制的改变,其实际的数量而改变。它的作用为我们展示与应用数据提供了方便,所以这里举几个典型的应用示例:

有一个2字节长的数,其高8位代表小时数,而低8位代表分钟数,此时为409分,我们就可以轻松表示为0x0409,如果使用十进制1033,我想你肯定很难看出来是几点几分吧。

数据基本类型,在嵌入式编程环境下,主要还是区分整形与字符型,很少使用浮点类型。它们之间的联系非常紧密,因为全部需要存储空间,所以全部可以用存储空间来解释。先说说字符型,

字符型,主要针对ASCII码使用,表示字符,存储空间为1字节,值域范围0~255。我们常用的printf函数参数就是char型。特别说明以下三个标志性字符:

‘0’ = 0x30;

‘A’ = 0x41;

‘a’ = 0x61;

这三个是基准,剩下的字符可以依据这三个推测出来。

整型,该数据类型还有short int, int , long int等,依编译器的不同而不同,字长也是依据编译器而定。总之,2字节长的整形值域范围就 0~655354字节长的,请大家自行计算。

对于整型来说,在平时设计时最关心的还是溢出、有无符号两件事。

溢出:在计算过程中,16位长度还是很容易溢出的,例如对两个整型数据相乘,一不小心就溢出了。还有就是移位操作,大家自行在平时的设计中多留意,我也会在后面的实验设计中再详细讲解。

有无符号:有符号类型,将直接影响了值域范围,另外,有符号类型的数在做移位操作时要多注意。

浮点类型,多数编译器使用4字节长度来存储,与整型数据按位存储不同,浮点数将字长区分为整数区、指数区、小数区等,具体的划分我觉得编译器自己按标准执行吧,这么多年也没有关心与操作过。

本章还讲了优先级的概念,在运算与操作过程中,优先级的概念非常重要。概念非常重要,识别起来要对照那个大表,我表示我平时不会对照表来写代码,比如 *p->head[5] 这个表达式是什么意思?如果写成 *p->head[5])是不是清晰很多,优先级是不是一目了然了。所以,关于优先级的问题,自己在平时写编程时多用几个括号即可完美解决,放弃那个大表吧~~



院士
2018-05-27 18:10:09     打赏
12楼

第十二章、结构体(附枚举类型)


本章是非常重要的一章,结构体的概念与实际意义与C++里类的概念有几分相近,都是是一类相关联数据的集合。用示例来解释会较为清晰,以串口参数为例,串口的配置有许多参数,但基本包括以下4个:1)串口波特率;2)串口停止位;3)串口传送位数;4)奇偶校验位。我们在配置过程如果声明四个变量,在初始化时依次调用——这个思路肯定没有问题,程序也是能成功初始化并跑通。只是这并不符合编程习惯,当然也因为我们有更好的——结构体变量。

结构的声明需要使用struct关键字,示例如下:

struct _tUartParam
{
      uint16_t band;
      uint8_t stop;
      uint8_t frameBits;
      uint8_t check;
};
struct tUartParam uart1;

上面的示例,我们就声明了变量uart1。结构体的变量初始化过程就是对结构体成员每个变量都进行初始化,现在uart1使用波特率96001位停止位,无奇偶校验位,8位字长,示例如下:

Uart1.band = 9600;
Uart1.stop = 1;
Uart1.frameBits = 8;
Uart1.check = 0;

结构体的引用也挺简单的,就是使用“.“号,像上面的示例一样。对于结构体的指针变量,还有另外一种常用的引用方式”->“箭头方式,例如:

struct tUartParam *pUartParam;
pUartParam = &Uart1;
pUartParam->band = 9600;
pUartParam->stop = 1;
……

这一章还讲了typedef关键字,这个也是平时常用的关键字之一。其作用就是声明一个我们自己定义名字的类型,如:

Typedef BYTE char;

这样就声明了一个BYTE类型等同于char型,我们在使用时更加明晰,增强代码的可读性,从而减少误操作的可能性。

还有一个类型“枚举类型”,它只是声明了类型的值域必须为声明的值,不能为其余值,否则会报错。这个典型应用就是有限状态机编程里的状态机的状态值。比如我们生活中最常见的星期表示方法,因为只可能是这七个值,不会出现星期八的情况,因此,枚举值可以完美处理这样的需求。

声明方式:

enum eWeek {SUN, MON, TUE, WED, THUR, FRI, STA};

枚举类型的变量,编译器最后仍然是处理成为整数变量,换句话说,可以对枚举类型的变量进行运算操作,但是我们如果没有特殊要求不要这样设计,建议仅使用赋值语句来实现变量的变更。



院士
2018-05-28 22:56:30     打赏
13楼
第三章、分支结构程序


今天学习程序流的控制方式,主要有if语句,switch语句,while语句,for语句。在本章里,我们要学习前两个,先来第一个if语句。

If语句,判断语句,像它的英语意思一样。一个完整的if语句如下所示:

If(表达式)
{
  执行语句0;
}
Else
{
  执行语句1;
}

表达式为真是则执行语句0,为假时,执行语句1 。这个并不难理解,平时使用时要注意以下几点:

表达式为真,如果表达式为值,则真为非0,而0为假。例如,空指针的值为0,即为假条件。

对于假条件,在没有执行语句1时,其是可以省略的,即只有if语句,而没有else语句,这个我们在平时的源代码阅读中经常看到。

If语句也是可以嵌套的,在if判断成功后再判断if的条件。其实可以一直判断很多条语句,就像switch语句那样。

Switch语句,在分支结构另一个典型语句设计,这个我们将在下一章的学习中进行了解。



院士
2018-05-31 00:00:46     打赏
14楼

第四章、程序的循环控制

我们在讲述第四章之前,我们先将上一章遗漏的switch语句搞定。

对于switch语句,分支结构语句,其部署的方式如下所示:


Switch(变量值)
{
Case 常量0:
{
  语句0;
}
Case 常量1:
{
  语句1;
}
Default:
{
  语句N;
}
};

其实本质上,switch语句,就像书中所言,switch语句可以用if来改写,只是这一步编译器帮我们做了而已。

Switch语句在应用中所占比例相当高,主要由于其清晰的程序设计格式,尤其在有限状态机的应用上面,二者在一起就像是郭靖与黄蓉。

这里有一个小小的提示,在使用switch时,一定要将出现概率大的常量放在前面。

讲完分支结构,我们再来看看循环语句。Do{语句段}while(表达式);或是while(表达式){};或for(前执行语句0;表达式0;后执行语句1)这三种方式。从上面的文字与程序格式,我们可以得出来循环中有几个要素:循环的初始条件;循环主体;循环终止条件;随着学习的不断深入,对于元素可有可无,也促成了一些专有应用程序。

我们先来说说Do{语句段}while(表达式);它的程序结构里没有初始值,如果要有需要初始化变量,则初始值需要放在循环语句外。“Do”即做为循环起点的标识,然后执行语句段,再执行while语句内的表达式,即判断循环结束。

        这里一定要说明一下,使用do{}while();语句至少要执行一次循环体,即如下的示例代码情况下,仍然会执行一次。

do
{
执行语句0;
}while(0);


    while(表达式)语句则是要先判断表达式的真假,如我们常用的无限循环语句while(1)语句。因为表达式一直为真,因此,其主体会一直循环执行下去。

    for语句在自增运算的时候,也是非常方便的——我觉得主要因为其代码的清晰性,从实现的角度,能用while语句实现的,肯定使用for语句也一定可以。

比如我们最常用的批量赋值语句:

for(i = 0; i < 10; i++)
{
  a[i] = i;
}




院士
2018-06-02 23:53:08     打赏
15楼
第六章、函数

函数,在我们的数学课程学习中已经学习与使用过无数次了。比如y = f(x)=kx+b 这样的一次函数,我们输入变量x,通过函数f(x)的计算能得出y的值。在C语言里,函数的作用与其是一样的,只是各个部分的名称略有不同:C语言里将y的值称为“返回值”;将f()称为函数名,并且要自己来定义,只要不重复即可。将变量x称为“形参”;当然,非常重要的 kx+b,函数体内的语句是需要我们自己编写。

当不需要返回值时,我们使用void来替代,称为无返回值函数(部分编程语言里称为过程)

当不需要传入参数时,我们也使用void来替代,称无参数函数,只是机械的执行内部语句。

对于形参,我们可以传入值,也可以传入地址。前者对于传入的变量不会改变其值,而后者,可以修改传入的变量,即实参的值。

本章还讲了变量的作用域,有函数内部的作用域,本文件的作用域,整个程序的作用域。这个道理很简单,在函数内部声明的变量,其作用域就在本函数内部,而声明在函数外的变量而使用了static关键字进行限定,则只限于本文件内部,否则,即为全局变量。

注意:我们在编写程序的时候,多少也要有些行规,像I,j,k这类的变量,只用于函数内部的局部变量,且仅用于计数使用;而全局变量在定义时,其要以gXX开头,这样不仅方便自己阅读代码,也方便别人阅读,万一人家看出程序的bug呢!

注意:文章内也提到,全局变量与函数内的局部变量可以一致,实参与形参也可以一致。但大家在编程的时候,千万不要这样做,时间一久,自己都忘了。属于自己给自己埋雷了。

函数的声明里,文章写了,要在函数定义前进行声明,如果不声明,也是可以的,只要函数的定义在被调用之前即可。像IAR嵌入式开发环境下,可以通过选项强制必须有函数的声明。

关于头文件与C文件,我觉得两者的区别主要是内容的不同,是我们自行约定的。对于编译器来讲,它不管你是什么,它只认文本文件,但我们约定要将全局声明放在头文件内,以方便全局声明与调用,而函数实现,一定要放在C文件里。如果放在头文件里,其实也是可以的,只是在多文件调用时,编译器会产生多个实现的报错。(当然,只调用一次,则不会产生错误。)

关于查找,文章中提到的哨兵查找的实现挺棒的,只是在嵌入式编程里,像类似的查找基本都是const表,因此,无法在最后一位写入查找的值。如果我们的查找方式是在内存里,这个查找算法在随机数排序方式下还是不错的。

函数的应用非常有学问,像文章提到的“通用性”,这个就需要编程者要有较高的抽象能力与强大的数学总结归纳能力,善于发现内部相通的地方。总之,学习是循序渐进,切不可急于求成,让浮躁加身。



院士
2018-06-03 10:30:47     打赏
16楼
第八章、动手编写各种函数


动手编写函数这一章讲得我们如何将上面几章所学知识贯穿于我们的程序中。

宏定义,我们多用于对常量的标识,将一个平凡的数字赋予清晰易懂的注释。而宏定义函数,可是有爱有恨。一方面,宏定义函数简单;另一方面,宏定义函数也有诸多副作用。在宏定义后,编译器是通过字符串替换的方式达到我们的设计要求,如果没有编译错误,肯定万事大吉;要是有错误,这个错误的定位,编译器多半会是较大偏差,给我们挖bug带来相当大的不便。

这个不便还存在于单步调试过程,如果宏定义函数只有一个语句,这个问题还较小,如果是多条语句,将无法查看此时的变量实时值,无法进行单步暂停与分析,鉴于上述几个副作用,除非原子操作,否则我是不会使用这个宏定义函数。

宏定义对于常量来说尽量书写格式统一,字节对齐,使我们的代码整洁清晰。

对于排序算法,本章仅讲了经典的冒泡算法,这个非常实用。

递归函数,看似非常好的解决斐波那切函数,我个人觉得没有应用的意义。在本人有限的编程的经验里,没有使用过递归函数解决的问题,不过,我以后也不打算使用。

缓冲与重定向,这两个的出现比较突兀,也很难理解。缓冲的作用,适用于不同处理速度的两个模块之间数据交换时使用,可以增大数据处理的稳定性。重定向,可以理解接口的再次封装,之前指向的串口通讯,现在重定向指向了USB通讯。

对于转义字符,一定要看一下,并亲自操作。这个东西只有用到的时候才知道其具体的意义。

今天就写到这里吧~~



院士
2018-06-08 00:01:34     打赏
17楼

测试题一:

解答如下:

#include <stdio.h>

/**
  * @brief   打印星符金自塔
  * @param
  * @retval
  * @date   2018-06-07
  * @note   家庭作业第一题
  */

char str0[] = "    *\n   **\n  ***\n ****\n";

int main()
{
  printf("%s", str0);
  while(1)
  {
  	;
  }
}


rst01.jpg


院士
2018-06-08 00:12:09     打赏
18楼

测试题二,解答如下:

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



#define NODE_SHAPE	('*')
#define BUF_LENGTH	(10)

char StrBuf[BUF_LENGTH + 1] = {0, };
const char strPrompt[] = "please input the numbers of floor\n";

/**
  * @brief   打印星符金自塔
  * @param
  * @retval
  * @date   2018-06-07
  * @note   家庭作业第二题
  */

void MakeShape(char *pBuf, uint8_t len)
{
	memset(pBuf, ' ', BUF_LENGTH - 1);
	uint8_t pos;
	if(len >= BUF_LENGTH)
	{
		pos = BUF_LENGTH - 1;
	}
	else
	{
		pos = len;
	}

	pBuf += (BUF_LENGTH - 1);
	while(pos-- > 0)
	{
		*pBuf-- = NODE_SHAPE;
	}
	pBuf[BUF_LENGTH] = '\0';
}

/**
  * @brief   依输入数量打印星符金自塔
  * @param
  * @retval
  * @date   2018-06-07
  * @note   家庭作业第二题
  */
int main()
{
	uint8_t i = 0;
	char tempInput;
	printf("%s\n", strPrompt);
	scanf("%d", &tempInput);
	printf("%d floors\n", tempInput);
	for(i = 1; i <= tempInput; i++ )
	{
		MakeShape(&StrBuf[0], i);
		printf("%s\n", StrBuf);
	}
	while(1)
	{
		;
	}
  return 0;
}


rst02.jpg


院士
2018-06-08 00:14:00     打赏
19楼

测试题目三,解答如下:


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

#define BUF_LENGTH	(10)

char StrBuf[BUF_LENGTH + 1] = {0, };
const char strPrompt0[] = "please input the BCD format number:\n";
const char strPrompt1[] = "please input the BIN format number:\n";
const char strPrompt2[] = "the wrong BIN input! NEED 8 bits\n";
char InputBuf[BUF_LENGTH] = {0,};

const char BINarray[10][5] =
{
  {'0', '0', '0', '0', 0},
  {'0', '0', '0', '1', 0},
  {'0', '0', '1', '0', 0},
  {'0', '0', '1', '1', 0},
  {'0', '1', '0', '0', 0},
  {'0', '1', '0', '1', 0},
  {'0', '1', '1', '0', 0},
  {'0', '1', '1', '1', 0},
  {'1', '0', '0', '0', 0},
  {'1', '0', '0', '1', 0},
};

/**
  * @brief  BCD码转二进制
  * @param
  * @retval
  * @date   2018-06-07
  * @note
  */

void BCDtoBIN(char *pBuf, uint8_t val)
{
	uint8_t i;
	int8_t j;
	uint8_t temp;

  memset(pBuf, ' ', BUF_LENGTH - 1);

  for(j = 1; j >= 0; j--)
  {
    temp = (val >> (4 * j)) & 0x0F;
    for(i = 0; i < 4; i++)
    {
      *pBuf++ = BINarray[temp][i];
    }
    *pBuf++ = ' ';
  }
}

/**
  * @brief   二进制转换为BCD码
  * @param
  * @retval
  * @date   2018-06-07
  * @note
  */
int8_t BINtoBCD(char *pBuf, char *pVal)
{
  uint8_t len = 0, res = 0;
  int8_t i, temp, ret = 0;
  char *p = pVal;
  while(*p++)
  {
    len++;
  }

  if(len == 8)
  {
    p = pVal;
    for(i = 3; i >= 0; i--)
    {
      temp = *p++ - '0';
      temp <<= i;
      res += temp;
    }
    *pBuf++ = res + '0';
    p = pVal + 4;
    res = 0;
    for(i = 3; i >= 0; i--)
    {
      temp = *p++ - '0';
      temp <<= i;
      res += temp;
    }
    *pBuf++ = res + '0';
    *pBuf = 0;
    ret = 1;
  }
  else
  {
    ret = -1;
  }
  return (ret);

}

void main()
{
  int8_t err;
  char tempInput;
	printf("%s\n", strPrompt0);
	scanf("%d", &tempInput);
	tempInput = (tempInput / 10) * 16 + tempInput % 10;
	BCDtoBIN(&StrBuf[0], tempInput);
	printf("%s\n", StrBuf);

	printf("%s\n", strPrompt1);
	scanf("%s", &InputBuf);
	printf("%s\n", InputBuf);
	err = BINtoBCD(&StrBuf[0], &InputBuf[0]);
  if(err > 0)
  {
    printf("%s\n", StrBuf);
  }
  else
  {
    printf("%s\n", strPrompt2);
  }
	while(1)
	{
		;
	}
}

rst03.jpg


院士
2018-06-08 00:15:27     打赏
20楼

测试题目四,解答如下:


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

#define BUF_LENGTH  (10)

char StrBuf[BUF_LENGTH + 1] = {0, };
char InputBuf[BUF_LENGTH] = {0,};

#define BCDtoBIN(val, p) (itoa(val, p, 2))
#define BINtoBCD(ptr, r, n) do  \
                      {   \
                        for(n = 3; n >= 0; n--) \
                        {                       \
                          if(*ptr++ == '1')     \
                          {                     \
                            r |= (0x01 << n);   \
                          }                     \
                        }                       \
                      }while(0)

int main()
{
  char temp;
  signed char i;
  char res = 0;
  char BinBuf[8];
  char *p;
  scanf("%x", &temp);
  BCDtoBIN(temp, StrBuf);
  printf("%s\n", StrBuf);
  getchar();
  for(i = 0; i < 8; i++)
  {
    BinBuf[i] = getchar();
  }
  p = BinBuf;
  BINtoBCD(p, res, i);
  res *= 10;

  p += 4;
  BINtoBCD(p, res, i);
  printf("%d\n", res);

  while(1)
  {
    ;
  }

  return 0;
}

大家自行运行吧!

exp05.jpg


共22条 2/3 1 2 3 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]