C语言易错点(二)
目录
一、两个转义字符
二、除法和取模操作符
三、大小端字节序
四、printf的传参
五、位段
六、枚举
八、预处理、编译、链接
九、写一个宏,交换一个数二进制位的奇偶位
十、offsetof宏的实现——计算某结构体相对于首地址的偏移量
十一、C语言头文件中的 ifndef/define/endif 的作用?
十二、动态内存错误
一、两个转义字符
1、/060,‘/’跟三位数字表示将这个数字转为八进制数字,其对应ASCII码表中对应的字符
2、/b,表示退格;/n,表示换行
二、除法和取模操作符
1、除法操作符:两个操作数必须至少有一个是浮点数,才会执行小数除法
2、取模操作符:两个操作数必须是整数;对于负数的取模运算,结果的正负取决于第一个操作数的正负
三、大小端字节序
1、定义:指的是数据在电脑上存储的字节顺序,大端字节序存储是低字节序存在高位,小端字节序存储是低字节序存在地位
2、如何判断自己的机器是大端还是小端
——有两种 方法,一种是类型转换的方法,另一种是采用联合体(因为共用一个空间)
union
{short i;char a;
}m;int main()
{m.a = 1;if(i == 1){printf("当前机器是小端字节序存储");}else{printf("当前机器是大端字节序存储"); }return 0;
}
int main()
{int a = 1;//补码是0x 00 00 00 01,小端的话存储的是 01 00 00 00//如果转化为字符类型的话,就会拿第一个字节,即01if((char)a == 1){printf("当前机器是小端字节序存储");}else{printf("当前机器是大端字节序存储");}return 0;
}
四、printf的传参
1、printf是可变参数的函数,因为我们什么样的数据都可以打印,所以在接收的时候,只会接收四个字节或者是八个字节,对于像char、short这样大小小于四个字节的,也按四个字节接收;而对于像long long、float、double这样的类型会按八位接收(注意float会转换为double再接收)
2、对于读取而言,%llx(long long)、%lld(long long)等整型方式和%f、%lf等浮点型方式会按八位读取
理解这一点可以完成一下题目:
unsigned char a = 200;unsigned char b = 100;unsigned char c = 0;c = a + b;printf(“%d %d”, a+b,c);
五、位段
1、如下即是位段的基本用法,不过需要注意,冒号后的数字单位是bit,而不是byte,即位段指定了用几个比特位来存储一个数据;另外值得注意的是,数据的大小不能超过一个字节的大小,否则编译器会报错。例如以下结构体中,Env_Alarm_ID和Para1共用一个字节,然后是state独享一个字节,最后avail使用一个新字节的一个比特位——据此,整个结构体的大小就是三个字节
2、位段存在巨大的跨平台问题,实际使用不广,但是在网络域名等处应用广泛
3、截断问题:如果我们令Para1为5,那么他的二进制是101,但是他只有两个bit为来存储,所以截断为00,另外这里要注意不要和大小端字节序弄混:正像名字一样,大小端是字节之间的顺序问题,位段这里都是在一个字节的内部,按照从右到左的顺序一次存储
struct _Record_Struct
{unsigned char Env_Alarm_ID : 4;unsigned char Para1 : 2;unsigned char state;unsigned char avail : 1;
}*Env_Alarm_Record;
六、枚举
枚举类型可以作为函数参数传递;枚举值只能是整型,枚举类型在内存中以整数的形式存储;从枚举的第一个第一个成员开始,第一个是0,后面依次加1(没有特别赋值的话)
七、文件的读写
1、fopen,打开模式是“r”时,若文件不存在,会打开文件失败,若是“w”模式,若文件不存在,会创建新文件,打开成功,所以检查fopen的返回值可以判断文件是否打开成功,这对于“r”模式更为重要
2、f系列的函数都是针对所有流,但是getchar只是针对标准输入流stdin
3、对于文件是否正常结束读取,feof函数会检测是否遇到EOF而结束,fgec检测他的返回值是否是EOF,fgets检测他的返回值是否是NULL;
八、预处理、编译、链接
预处理只会处理#开头的语句,编译阶段只校验语法,链接时才会去找实体
预处理:相当于根据预处理指令组装新的C/C++程序。经过预处理,会产生一个没有头文件(都已经被展开了)、宏定义(都已经替换了),没有条件编译指令(该屏蔽的都屏蔽掉了),没有特殊符号的输出文件,这个文件的含义同原本的文件无异,只是内容上有所不同。
编译:将预处理完的文件逐一进行一系列词法分析、语法分析、语义分析及优化后,产生相应的汇编代码文件。编译是针对单个文件编译的,只校验本文件的语法是否有问题,不负责寻找实体。
链接:通过链接器将一个个目标文件(或许还会有库文件)链接在一起生成一个完整的可执行程序。 链接程序的主要工作就是将有关的目标文件彼此相连接,也就是将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。在此过程中会发现被调用的函数未被定义。需要注意的是,链接阶段只会链接调用了的函数/全局变量,如果存在一个不存在实体的声明(函数声明、全局变量的外部声明),但没有被调用,依然是可以正常编译执行的。
九、写一个宏,交换一个数二进制位的奇偶位
#define Swap(n) ((((n) & 0x55555555) << 1) | (((n) & 0xaaaaaaaa) >> 1))
十、offsetof宏的实现——计算某结构体相对于首地址的偏移量
//复制一个结构体,该结构体的首地址在0地址处,因此这个结构体的相应成员变量的地址就是偏移量
#define My_offsetof(structtype, membername) (size_t)&((structtype*)0->membername)
十一、C语言头文件中的 ifndef/define/endif 的作用?
防止头文件被重复引用
十二、动态内存错误
对已经free的内存进行释放、对申请的空间越界访问、对非动态内存进行释放都是常见的动态内存释放,但是使用函数式传NULL指针并不是动态内存错误,事实上,连报错都不会有