温故而知新(一)

时间:2022-12-27 14:28:48

        以下知识点是笔者在学习过程中,对感到薄弱或有价值的地方,进行的记录和总结,希望对读者有所帮助。由于时间久远,所以无法一一列举各知识点的出处,主要摘录自互联网及一些杂志期刊和专业书籍,如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++’更少,效率更高。)