2013年(大三)暑假C语言学习笔记

1 关于数值

1)例如,在宏定义时写上

#define SECONDS_PER_YEAR     (60*60*24*365)UL
编译器会在预编译时计算出常熟表达式的值,这样比直接写出计算结果要好,因为思路在,别人可根据你的思路看懂程序代码,也方便修改。

2)关于字面值常量。(60*60*24*365)是一个常量,在后面加上一个UL表示这是一个无符号长整型类型的数。如果不加上UL,则默认为缺省的int。由于字面值常量有特定的数据类型,所以在进行混合运算时就按照运算规则的算术转换来进行。此外,由于字符常量'a',其实就是一个int值97,实质上也是一个int型,所以当sizeof('a')时实际上会求出sizeof(int)。注意:sizeof()求值,只根据该数据类型来求,而不管该值的内容。



2  关于回调函数。。。。,主要是实现多态性,所以要求调用函数的形参和回调函数的形参一般都为void *类型,由于形参为void *类型,因此编译时不会出现错误,而且这样可以在回调函数中进行强制类型转换,达到多态通用的目的。注意的是: 回调函数要自己根据不同类型的形参实现。

3 实现一个状态机,以适应键盘扫描的各种情况等等。(至于预编译条件,可以参考一下uC-OS源码中...)




4  strcmp源码中有:
int __cdecl strcmp (
        const char * src,
        const char * dst
        )
{
        int ret = 0 ;
                            //思考:为什么是要强制转换为 unsigned char *?
        while( ! (ret =  *(unsigned char *)src - *(unsigned char *)dst) && *dst)
                ++src, ++dst;

        if ( ret < 0 )
                ret = -1 ;
        else if ( ret > 0 )
                ret = 1 ;

        return( ret );
}

在参考网友总结的观点:1)适应字符集扩展     2)算数运算时会发生整型提升。从char 改为unsigned char 相当与扩大了字符值的范围(字符值不可能小于0,因此从0-255计算的有效范围比较大),有助于解决char情况时的符号溢出问题。 3)程序的可移植性——不同的编译器有可能将缺省的char类型编译为unsigned char。

按照目前的总结理解:1)和2)的说法比较有说服力。

在我进行调试的时候,写了如下一段代码:
char  src = 128;
char  dst = 129;
int ret1;
int ret2;
int ret3;
int ret4;


ret1 = (char)src - (char)dst;
ret2 = (unsigned char)src - (unsigned char)dst;
ret3 = (unsigned char)src - (char)dst;
ret4 = (char)src - (unsigned char)dst;


printf("(char)src - (char)dst is: %d\n",ret1);
printf("(unsigned char)src - (unsigned char)dst is: %d\n",ret2);
printf("(unsigned char)src - (char)dst is: %d\n",ret3);
printf("(char)src - (unsigned char)dst is: %d\n",ret4);

最后ret1 = -1,ret2 = -1, ret3 = -255,ret4 = -257;

说明,当变量定义声明为char类型时,若对该src变量赋值128,则src由于为char类型,范围-128~+127,此时src在计算机的补码表示形式为1000 0000,尽管赋值为128,但表示时按照char类型理解,其值为-128(一切数值最终都以补码形式存储在内存中,类型只是辅助计算机理解的一种形式)。但如果按照unsigned char理解,src则为128;同理,dst 赋值为129,则129的补码为:1000 0001,所以如果按照char 类型理解,其值为-127,如果理解为unsigned char 则为129;

总结:计算机中一切数值都是以补码的形式存储的,因此在需要进行计算时,需要告诉计算机这个补码的具体理解方法。例如,数据的类型和数据理解。比如对于一段数据:1110 1100 1111 0000 为两个字节,但如果告诉计算机仅需要计算第一个字节,因此可以强制转换为char 或者unsigned char,又由于补码形式容易产生歧义(既可以表现为有符号数和无符号数),因此又要声明为到底是具体的 char 还是unsigned char,这样计算机才能正确的运算。而程序员需要做的就是:仔细观察数值和数据类型的关系,注意控制不同的类型转换是否会出现溢出等情况。例如将129赋给char dst,则dst的补码数据为1000 0001,由于是char类型,因此计算机将该段数据按照char的思维(最高位1为符号位 )去理解为-127。这样,当运算时,src - dst 就等于(-128 )- (-127) = -1。

另外,可以用圆周角这一比喻来理解补码:360 为满 ,说-30` 其实就是 330 `,则 补码 一共256为满,-127的补码 实际就是 129的原码。

为什么要以补码的形式存储?是为了消除二义性。

终极的问题:为什么计算机要分不同的数据类型???这样做好处?有了数据类型后运算不是更加麻烦了吗?


答:1)计算机由于要对内存进行管理,如果一段数据在内存中所占的大小和地址都知道的话,这样计算机处理起来就非常快,(同时由于都是二进制表示,一比特一比特的数据排列)否则会造成资源浪费(不知道该分配多少空间给该段数据)。
有了类型,鸟笼里的是鸟,狗窝里的是狗,如果没有,你永远不知道冰箱里的是不是大象

2)有了数据类型后,只要遵循一定的规则则可以进行运算,对计算机来讲并不麻烦,反而效率更高。(麻烦之处在于不符合人类的思维方式,所以才觉得难以理解)




5 用状态机的思想写键盘扫描程序,做到实时响应,不浪费CPU资源。

详细参考马潮老师的《AVR单片机嵌入式系统原理与应用实践》  马潮老师AVR新书《AVR单片机嵌入式系统原理与应用实践》.pdf

简述一下状态机的思想,其实和我之前做过的一个毕业设计《语音复读机》的思想有点相似,可以横向对比参照加深理解。就是将一个模块要完成的工作分成几个不同状态,通过程序处理,让计算机快速知道当前的执行状态,从而省掉不必要的延时,提高CPU资源利用率,从这点看,和一般的嵌入式系统的任务挂起时任务切换有一定相似之处。


6   进入主程序后,对单片机的RAM和各个子模块状态进行一次清零初始化,这样可以避免程序跑飞时单片机系统瘫痪。此外,如果结合软件复位,看门狗和定时器以及EEPROM等技术,甚至可以让单片机系统在最少的时间内以最低的成本自动恢复运行,这在一些需要计算时间的比赛中尤其重要。(因此,必须设置一个自动循环的,即使跑飞或者掉电也可以复用的系统。)

2013/7/20:18:20     To-do list:

已经保证了Timer.c中定时器初始化和设置定时时间程序的健壮性(通过判断宏定义是否打开_EN来在代码中加入返回值,这样在主程序中可以通过调试手段,例如——蜂鸣器提示,确保模块的正常使用),接下来应该保证键盘扫描的健壮性,另外,还有初始化等一些接口函数应该要与其他子模块保持一致。 键盘扫描_状态机__7_20_健壮性.rar


7  关于枚举 enum的使用和总结:

定义方法:
    enum workday{monday,tuesday,wednsday,thursday,friday};

这样,默认monday是常量0, tuesday是常量1等等。。。一经定义下来,就不可以修改。又如
enum workday{monday = 2,tuesday,wednsday,thursday,friday};
以后每个枚举量加1.

可以理解成int const 的值,相比#define ,enum可以自动赋值给该枚举量。

至于枚举变量,则定义为enum workday work;这样,work就只能用 枚举量中的其中某一个值给它赋值。

但要注意,不同编译器对枚举变量的赋值和形参检查的要求不一样。
在VC6.0中,若赋值超出enum枚举的范围,编译时不会出现警告也不会出错。
Keil C编译时会出现警告。
C++编译器中会提示出错。(同时当形参不是enum类型时也会出错)

为了兼容和程序设计的方便性,当需要用到有一定范围的常量值(尤其是函数的形参)时,采用enum编写程序,这样当出错时编译器会给出提示,这样能最大保证程序的健壮性。

8 关于PID控制算法的结果如何用于控制输出PWM?其对应关系如何理解?
答:
首先假设你是采样值直接做pid(换算成温度也可以),得到的结果用v_pid表示
之后我的做法是设置两个阈值H和L,可以做以下判断:
v_pid>H       占空比=100%
v_pid<L       占空比=0%
L<v_pid<H   占空比=(v_pid-L)/(H-L)*100%
追问
那么这两个阈值H和L要怎么确定呢?
回答
一般可以凭经验,我是这样做的:
可以假如认为温度误差在20deg时应该连续通电(占空比为100%),误差<5deg时可以先不通电(占空比为0%),那H可以设置为温度误差在20deg且只考虑比例环节时pid的值(Kp*20deg或Kp*20deg对应的采样值),L就是温度误差在5deg且只考虑比例环节时pid的值。也可以先估计一个值,再在试验中不断修改。祝你成功










#define ARRAY_ELEMENT_NUM(array) ((sizeof(array))/(sizeof(array[0])))

利用sizeof运算符求数组大小的时候,出现了如下警告:
warning C198: sizeof returns zero
经谷歌后,发现是Keil编译器(不知道其他编译器是否)不支持这样求数组大小,也即当extern 声明数组时,由于是extern array[],因此在另一个文件中使用 ARRAY_ELEMENT_NUM(array)时便会出现问题。


10    今天在对电机进行编程控制时,发现一个很好的编程思想,可能是对C++中的对象了解得更深刻了,也能抽象出其中的共同点。

下面是我对电机编程时的几点理解:

1) 把小车抽象理解成一个对象对其进行类的设计,小车的控制方法函数就是一个个成员函数。
对于小车来说,控制方法主要有  前进、后退    左转    右转    停止等五个操作,写成函数即可。考虑到《开放——封闭原则》,对其进行封装成static静态函数,再定义一个函数指针数组(使用了typedef 函数指针的技巧——同时加深理解了typedef关键字的用法。)也称为转移表对其进行操作,在主函数中定义一个下标值变量作为电机的指示状态,这样只要根据下标就可以对小车进行控制。

(待完成)——进一步对类对象思想的深入,可以定义一个结构体,函数成员包含一个函数指针数组,以及数据成员(用于下标)。

这样只需要再定义一个结构体变量就可以对小车进行操作了。


11 关于51单片机的中断的几点归纳: http://blog.sina.com.cn/s/blog_4a3946360100pbll.html

1)中断嵌套:普通51只有2级中断——高优先级,低优先级。中断嵌套只能发生在高优先级打断低优先级的情况。
2)在执行中断时的情况,分为几种:①低优先级中断正在进行时(假设是外部中断0),如果发生同优先级的另一个中断(假设是外部中断1),那么外部中断0不会被打断进入嵌套中断中,因为它们两个是同一个优先级。 但是在执行完外部中断0(RETI指令返回)后,会紧接着进入外部中断1的中断服务程序。因为在中断服务程序中,只有RETI返回后外部中断的寄存器IT0/IT1位才会硬件自动清零。所有的中断如此,当中断发生时,该中断响应的IE寄存器位会被置1,这样CPU在执行指令时会不断地按照一定次序轮流查询IE寄存器的中断标志位,一旦发现有中断标志位置1,就进入该中断服务程序。所以只要寄存器没被清零(串口中断标志位RI,TI由软件清零,定时器标志位TF1,TF0在CPU进入中断后(注意,应该是一进入中断就马上清零,这样才能解释当前的中断A执行完后马上响应下一个同级的中断B,因为中断B申请中断时还没能进入中断,但中断B的寄存器标志位没被清零) 由硬件自动清零)中断不会丢失,②如果外部中断0(低优先级)中断服务程序正在执行,如果这时发生③④
中断响应的条件:
我们人可以响应外界的事件,是因为我们有多种“传感器“――眼、耳可以接受不同的信息,计算机是如何做到这点的呢?MCS51工作时, 在每个机器周期中都会去查询一下各个中断标记,看他们是否是“1“ ,如果是1,就说明有中断请求了,所以所谓中断,其实也是查询,不过是每个周期都查一下而已。这要换成人来说,就相当于你在看书的时候,每一秒钟都会抬起头来看一看,查问一下,是不是有人按门铃,是否有电话。。。。


12 关于回调函数:
分层设计有关
抽象有关
接口与实现分开有关
松耦合原则有关





13  符号位扩展

#include <stdio.h>
int main()
{
        int a = 225;//十六进制e1
        char *b = (char *)&a;
        printf("%x\n",*b);
        return 0;
}
为什么输出是 ffffffe1
如果把a换成90(十六进制是5A)
输出是5a
我想问的是e1前面的ffffff是怎么回事,为什么5a前面就没有呢?

答案:由于225的16进制为0xe1,当其转换成char时(或者以char格式显示),相当于0xe1的最高位为符号位1,并且最高位为符号位。为了兼容显示(16位或者32位),故在其前面加上补1作为符号位的扩展。而当a=90时,0x5A,最高位为符号位0,符号位扩展也是在前面加0,但由于前面补0显示不符合人的思维,所以显示时把0舍去。



14 上述补充一点:
int a =0x77665544;
int *pi =&a;
char *pc = (char *)&a;

那么在对*pc取值的时候,将会是第一个字节0x44(为什么不是0x77,具体和大小端有关)而不是0x77665544,只是pc指针的寻址方式改变。这一点《程序员宝典》一书为错误的。因为其举的例子a=0xFFFFFFF7其实是符号位扩展了。



  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值