为了方便编译器分配内存,C89规定变量必须定义在函数开始的位置,在定义变量之前不能有其他表达式。C99取消了这个限制。
变量的默认初始值:
无论是强制类型转换还是自动类型转换,都是为了本次运算而进行的临时性转换,转换的结果也会保存到临时的内存空间,不会改变数据本来的类型或者值。
缓冲区(buffer)也叫缓存(cache),是内存空间的一部分,计算机在内存中预留了一定的存储空间,用来暂时保存输入或者输出的数据。
缓冲区是为了让低速的输入输出设备和高速的用户程序能够协调工作,并降低输入输出设备的读写次数。
缓冲区可以分为输入缓冲区和输出缓冲区。也可以分为全缓冲区、行缓冲区、不带缓冲。
全缓冲区:当缓冲区被填满以后才会进行真正的输入输出操作,缓冲区的大小都有限制,数据量达到最大时就清空缓冲区。在实际的开发过程中,将数据写入文件以后,打开文件并不能立即看到内容,只有清空缓冲区、关闭文件或者关闭程序后才能在文件中看到内容。
行缓冲区:当输入或者输出的过程中遇到换行符时,才执行真正的输入输出操作。典型代表就是键盘和显示器。
不带缓冲:不带缓冲区,数据就没地方缓存,必须立即进行输入输出。
缓冲区的刷新规则:
对于输出操作,清空缓存区会使得缓冲区的所有数据立即显示在屏幕上;对于输入操作,清空缓冲区就是丢弃残余字符。
fflush(stdout)是一个专门用来清空缓冲区的函数,stdout是标准输出设备。
#include <srdio.h>
int fflush(FILE *stream)
调用fflush会将缓冲区中的内容写入到stream所指向的文件中去,若stream为NULL,则会将所有打开的文件进行数据更新。
fflush(stdin):刷新缓冲区,将缓冲区中的数据清空并丢弃。
fflush(stdout):刷新缓冲区,将缓冲区内的数据输出到设备。
fflush(stdin)不常用,可以使用while(getchar() != '/n')来代替
使用**getchar()**清空缓冲区:每次从缓冲区中读取一个字符,while(getchar() != '/n')
。
使用**scanf()**清空缓冲区:使用正则表达式,可以读取所有的字符。scanf(“%*[^\n]”);scanf(“%*c”);
第一个 scanf() 将逐个读取缓冲区中\n
之前的其它字符,% 后面的 * 表示将读取的这些字符丢弃,遇到\n
字符时便停止读取。
%[xxx]
,[]包围起来的是需要读取的字符集合,遇到[]之外的字符就结束。为了简化制度集合的写法,也支持使用-
连字符表示一个范围的字符,如 %[a-z]、%[0-9]。不匹配某些字符的方式就是在前面加上^,如%[^\n]
表示匹配除换行符以外的所有字符。%*d
表示读取一个整数并丢弃,%*[a-z]
表示读取小写字母并丢弃,%*[^\n]
表示将换行符以外的字符全部丢弃。#error 指令用于在编译期间产生错误信息,阻止程序的编译。
#
是预处理作用。#
是字符串运算符,会把宏调用时的实参转化为字符串。##
是连接符号,功能是带参数的宏定义中将两个子串联起来,从而形成一个新的子串。##
在可变参数宏中的作用,例如printf(fmt,##arg)
,##
的作用是当可变参数为空或者被忽略的时候,使预处理器去掉前面的逗号。指针变量可以指向计算机的任何一块内存,不管该内存有没有被分配,也不管该内存有没有使用权限。
数组和指针不等价,大多数情况下数组名可以当做指针使用。比如在求数组长度的时候,必须使用数组名,而不能使用指针。
int *p
,指针的数据类型就是int*
。C语言规定,对于一个符号的定义,编译器总是从它的名字开始读取,然后按照优先级顺序一次解析。所以,编译器是从名字开始读取的,而不是从开头或者结尾开始的。
int (*(*(*pfunc)(int *))[5])(int *)
表示pfunc是一个函数指针,该函数的返回值是一个指针,它指向一个指针数组,指针数组中的指针指向原型为int func(int *)
的函数。
[]
()
.
->
-
(type)
++
--
*
&
!
~
sizeof()
/
*
%
+
-
<<
>>
>
>=
<
<=
==
!=
&
^
|
&&
||
?:
=
/=
*=
%=
+=
-=
<<=
>>=
&=
^=
|=
,
确定大小端的方式:
union duan{
int a;
char b;
};
int is_little_endian(void)
{
union duan u1;
u1.a = 0x12345678;
if(u1.b == 0x12)
return FAUSE;
else if(u1.b == 0x78)
return TRUE;
}
int is_big_endian(void)
{
union duan u1;
u1.a = 0x12345678;
if(u1.b == 0x12)
return TRUE;
else if(u1.b == 0x78)
return FAUSE;
}
在定义结构体时,可以指定某个成员变量占用的二进制位数。使用位域定义的格式为类型说明符 位域名:位域长度
。位域只能是int、unsigned int、signed int类型。
位域的宽度不能超过所依附的数据类型的长度。位域技术就是在成员变量占用的内存中选出一部分位宽来存储数据。
struct abc{
unsigned int a: 10;
unsigned int b: 10;
unsigned int c: 10;
};
struct k{
int a:1;
int :2;/*无名位域,不能使用*/
int b:3;
int c:2;
}
想要分配一个不定长度的数组,可以使用零长度数组,即柔性数组(flexible array),如下所示:
struct line{
int length;
char contents[0];
};
零长度数组是存在于结构体内部的,但是不占用结构体的size。可以简单地理解为一个没有内容的占位标志,直到给结构体分配内存,这个占位标志才变成一个有长度的数组。
为什么不直接使用指针而是使用零长度数组来表示?因为想要给结构体内的数据分配一个连续的内存。
const修饰指针的时候,const离变量名近就是用来修饰指针变量,离变量名远就是用来修饰指针指向的数据。
const通常用在函数形参中,如果形参是一个指针,为了防止在函数内部修饰指针指向的数据,就可以使用const来限制。
可以使用struct = {xx,xx,xx}的方式,结构体成员变量赋值完后用逗号而不是分号,初始化语句的元素是以固定的顺序出现的,和被初始化的数组或者结构体中的元素顺序一样。
使用在成员变量前面加上小数点,可以不用按照成员顺序赋值。struct = {.member1 = xx, .member2 = xx, .member3 = xx}
在内嵌汇编中,可以将c语言表达式指定为汇编指令的操作数,不用管如何将c语言表达式的值读入到哪个寄存器,以及如何将计算结果返回,只需要告诉c语言表达式和汇编指定操作数之间的关系。
c语言内嵌汇编举例为:__asm__ __volatile__("hlt");
__asm__
表示后面的代码为内嵌汇编,asm
是__asm__
的别名。__volatile__
表示编译器不要优化代码,后面的指令保持原样。volatile
是__volatile
的别名。"hlt"
是汇编指令。内嵌汇编举例:__asm_- __volatile__("mov %1, %0" : "=r" (result) : "m" (input));
"move %1, %0"
是指令模板;"%0"
和"%1"
代表指令的操作数,称为占位符,内嵌汇编靠它们将c语言表达式与指令操作数对应起来。result
和input
是c语言表达式,即指令模板后面括号里面的是c语言表达式。按照出现的顺序分别和指令操作数"%0"
和"%1"
对应。操作数最多有10个,按照c语言表达式的出现顺序,分别和"%0"
…"%9"
对应。"=r"
和"m"
是对操作数的限制和要求,"="
表示"result"
是输出操作数。c语言内嵌汇编语法:__asm__(汇编语句模板:输出部分:输入部分:破坏描述部分)
汇编语句模板:由汇编语句组成,语句之间使用;
、\n
、\n\t
来分开。指令中的操作数可以使用站位符来引用c语言变量,操作数站位符最多10个。指令中使用占位符表示的操作数,被视为long
型,当把操作数当做字节来使用的时候,默认是操作低字节,可以显式的指定低字节或者高字节。方法是在%
和序号之间插入l
或者h
用来表示低字节或者高字节,如%h1
。
输出部分:输出部分描述输出操作数,不同的操作数描述符之间用逗号隔开,每个操作数由限定字符串和c语言变量组成。每个输出操作数的限制字符串必须包含=
用来表示输出操作数。
输入部分:输入部分描述输入操作数,不同的操作数描述符之间使用逗号隔开,每个操作数描述符由限定字符串和c语言表达式或者c语言变量组成。
破坏描述部分:破坏描述符用于通知编译器,我们使用了哪些寄存器或者内存,由逗号隔开的字符串组成。
因篇幅问题不能全部显示,请点此查看更多更全内容