以下知识点是笔者在学习过程中,对感到薄弱或有价值的地方,进行的记录和总结,希望对读者有所帮助。由于时间久远,所以无法一一列举各知识点的出处,主要摘录自互联网及一些杂志期刊和专业书籍,如C专家编程,程序员面试宝典,深入理解计算机系统,敏捷开发修炼之道,C和指针等等。在此对原著作者表示感谢。由于知识点是笔者在阅读完整资料时抄录和总结的,可能有断章取义之嫌,如有不当的地方还请指出。另外读者也可以自行阅读以上书籍,你将会有很多收获。
0. Blame doesn’t fix bugs。
1. 两句心灵鸡汤,这两句不知何时开始存在于我的备忘录里。“Les Brown说你不需要很出色才能起步,但是你必须起步才能变得很出色。”所以我开始写博客来见证自己的成长。“亚里士多德说能容纳自己并不接受的想法,表明你的头脑足够有学识。”所以我很乐于与别人讨论,从而丰富自我。
2. 位域(bit field)
概念:表示定义的数据所占用的不是整数字节(如char是1字节,short是2字节等等),而是按位(bit)分配的,例如:
struct x { int a:6; int b:2; };
其中a占6位,b占2位,两者合起来占8位,就是1字节。
3. 数字滤波器(digital filter)的设计与应用
使用Matlab中的FDAtool进行FIR等滤波器的设计,设计完成后得到滤波器的系数输出。由于Matlab输出的都是浮点数,而单片机中浮点数运算的速率明显低于整形数,所以单片机编程时将这些浮点数转化成整形数,即将所有系数扩大相同的倍数进行滤波,最后将滤波的结果同时缩小相同的倍数。得到滤波系数后,可以通过卷积操作对原始波形进行滤波。
4. 如何进行程序分析
从函数模块的功能结构图或数据流图进行分析。
5. sizeof与strlen的深入理解
sizeof操作符的结果类型是size_t,它在头文件中typedef为unsigned int类型。该类型保证能容纳实现所建立的最大对象的字节大小。sizeof是运算符,strlen是函数。sizeof返回一个对象或类型所占的内存字节数。数组作为参数传给函数时传的是指针而不是数组,传递的是数组的首地址,如fun(char [8]),fun(char [ ]),都等价于fun(char *)。所以数组做sizeof的参数不退化,传递给strlen就退化为指针。unsigned影响的只是最高位bit的意义(正/负),数据长度不会改变,所以sizeof(unsigned int) == sizeof(signed int)。只要是指针,大小一般都是4,如sizeof(string*)== 4。
6. 数据结构是彼此具有一定关系的数据元素的集合。简单地说,数据结构中的数据是指特性相同的数据元素的集合,结构就是指数据元素之间存在的一种或多种特定关系。
7. 针对=和==之间的问题,通过良好的代码习惯可以避免,如利用if( 0 == nValue ){ … }。换句话说,就是将0和nValue的位置交换,此时如果你再写出if( 0 = value )这样的代码,编译器会直接了当地提示发生了错误,因为常数不能作为左值来使用,即不能给常量赋值。
8. 诡异的表达式,评估求值的顺序问题如
int i = 2016; printf(“Theresults are: %d,%d”,i,i+1);
函数参数的评估求值没有固定顺序,所以printf( )函数输出结果可能是2016,2017,也可能是2017,2017。表达式计算顺序是一个很繁琐但是很有必要的话题,所以针对操作符优先级,建议多写几个符号,把你的意图表达地更清晰。注意函数参数和操作数的评估求值问题,小心其陷阱,让你的表达式不依赖于计算顺序。
9. 结构体
可以先声明结构体类型,再定义该类型的变量,如
struct Student { … }; //注意加分号 struct Student stu1,stu2;
也可以在声明类型的同时定义变量,如
struct Student { … }stu1,stu2;
还可以利用typedef定义,如
typedef struct { … }Student; Student stu1, stu2;
结构体指针就是指向结构体变量的指针,一个结构体变量的起始地址就是这个结构体的指针。
struct Student *pt; //pt可以指向struct Student类型的变量或数组元素。所以可以定义
struct Student stu1;
struct Student *pt;
pt =& stu1;
如果pt指向一个结构体变量stu1,以下3种方法等价:
stu1.num == (*pt).num == pt->num (num为此结构体中的一个成员变量)
10. 一个32位(数据位,指一个机器周期时钟脉冲能处理的字长)的机器的指针是多少位呢?指针是多少位只要看地址总线的位数进行了。80386以后的机子都是32位的地址总线。所以指针的位数就是4字节。使用未初始化的局部指针是件很危险的事,所以在使用局部指针变量时一定要及时将其初始化。对于全局变量,在声明的同时编译器会悄悄完成对变量的初始化。
11. 在C语言中,所有非数组形式的数据实参均以传值形式调用(对实参做一份拷贝并传递给调用的函数,函数不能修改作为实参的实际变量的值,而只能修改传递给它的那份拷贝)。以下示例程序可能有助于你对上述说法的理解:
#include <stdio.h> void swap1(int p, int q) { inttemp; temp = p; p = q; q = temp; } void swap2(int *p, int *q) { int*temp; *temp = *p; *p = *q; *q = *temp; } void swap3(int *p, int *q) { int*temp; temp = p; p = q; q = temp; } void swap4(int *p, int *q) { inttemp; temp = *p; *p = *q; *q = temp; } void swap5(int &p, int &q) { inttemp; temp = p; p = q; q = temp; } int main( ) { int a = 1,b = 2; //swap1(a,b); //swap2(&a,&b); //swap3(&a,&b); //swap4(&a,&b); //swap5(a,b); printf(“a = %d, b = %d”, a, b); return0; }
分别运行以上5个子程序,理论上会发现只有swap4函数和swap5可以实现a,b值的互换。实际上笔者在vs2015平台上运行时却出现许多问题,如编译器报告了swap2函数使用了未初始化的局部指针变量temp,而函数swap5则出现了许多错误导致程序无法运行,可能是开发环境的差异,希望了解的朋友出来说说。另外读者需要注意的是数据可以使用传址调用,只要在它前面加上取址操作符‘&’,这样传递给函数的是实参的地址而不是实参的拷贝。
12. 防止重复包含头文件
如果某个头文件被包含了两次,在编译整个工程时,编译器会提示错误。为了避免该情况,C/C++有两种处理方式,一种是#ifndef ,另一种是#pragma once 方式,后者不受标准支持,兼容性不好,前者编译器每次都判定文件是否重复定义,编译时间会变长。
13. 字节对齐
现代计算机内存空间都是按照字节来划分的,从理论上来讲,对变量的访问可以从任何地址开始;但在实际情况中,为了提升存取效率,各类型数据需要按照一定的规则在空间上排列,这使得对某些特定类型的数据只能从某些特定地址开始存取,以空间换取时间,这就是字节对齐。结构体默认的字节对齐一般满足三个准则:结构体变量的首地址能够被其最宽基本类型成员的大小整除;结构体每个成员相对于结构体首地址的偏移量(offset)都是成员自身大小的整数倍,如有需要,编译器会在成员之间加上填充字节(internal adding);结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节(trailing padding)。了解结构体中元素的对齐规则,合理地为结构体元素进行布局,这样不仅可以有效节约空间,还可以提高元素的存取效率。在标准C中,强制类型转换可能导致内存扩张与截断,所以尽量将强制转型减到最小。
14. 对于整形和长整型的操作,前缀操作的性能区别通常是可以忽略的。对于用户自定义类型,优先使用前缀操作符。因为与后缀操作符相比,前缀操作符因为无须构造临时对象而更具性能优势。(对此知识点笔者当时没有记录更多的解释,不过根据现在的理解,可能就好比‘++i’与‘i++’。在循环判断等情况下,可以优先使用‘++i’。因为在汇编语言层次上可以发现,‘++i’无需构造临时变量,所以转化成的汇编语句比‘i++’更少,效率更高。)