【C语言深度解剖】第一章:关键字

C语言标准定义的32个关键字

关键字 意义
auto 声明自动变量,缺省时编译器一般默认为 auto
int 声明整型变量
double 声明双精度变量
long 声明长整型变量
char 声明字符型变量
float 声明浮点型变量
short 声明短整型变量
signed 声明有符号类型变量
unsigned 声明无符号类型变量
struct 声明结构体变量
union 声明联合数据类型
enum 声明枚举类型
static 声明静态变量
switch 用于开关语句
case 开关语句分支
default 开关语句中的“其他”分支
break 跳出当前循环
register 声明寄存器变量
const 声明只读变量
volatile 说明变量在程序执行中可被隐含地改变
typedef 用以给数据类型取别名(当然还有其他作用)
extern 声明变量是在其他文件正声明(也可以看做是引用变量)
return 子程序返回语句(可以带参数,也可不带参数)
void 声明函数无返回值或无参数,声明空类型指针
continue 结束当前循环,开始下一轮循环
do 循环语句的循环体
while 循环语句的循环条件
if 条件语句
else 条件语句否定分支(与 if 连用)
for 一种循环语句(可意会不可言传)
goto 无条件跳转语句
sizeof 计算对象所占内存空间大小

定义与声明:

定义

定义就是(编译器)创建一个对象,为这个对象分配一块内存并给它取上一个名字,这个名字就是我们经常所说的变量名或对象名。但注意,这个名字一旦和这块内存匹配起来(可以想象是这个名字嫁给了这块空间,没有要彩礼啊。_),它们就同生共死,终生不离不弃。并且这块内存的位置也不能被改变。

声明

  • 告诉编译器,这个名字已经匹配到一块内存上了,下面的代码用到变量或对象是在别的地方定义的,声明可以出现多次。
  • 告诉编译器,这个名字我先预定了,别的地方再也不能用它来作为变量名或对象名。比如你在图书馆自习室的某个座位上放了一本书,表明这个座位已经有人预订,别人再也不允许使用这个座位。

定义声明最重要的区别:定义创建了对象并为这个对象分配了内存,声明没有分配内存

1.1 最宽恒大量的关键字 ----auto

auto:编译器在缺省情况下,所有变都是 auto 的

1.2 最快的关键字 ---- register

register:请求编译器尽可能的将变量存在 CPU 内部寄存器中而不是通过内存寻址访问以提高效率,注意是尽可能,不是绝对

寄存器就是一块一块小的存储空间,只不过存取速度比内存快。数据从内存里拿出来先放到寄存器中,然后CPU从寄存器里读取数据来进行处理,处理完成后同样把数据通过寄存器存放到内存里,CPU不直接和内存打交到。

【注意】:

  • register 变量必须是能被 CPU 寄存器所接受的类型,意味着 register 变量必须是一个单个的值。
  • 且其长度应小于或等于整型的长度
  • 而且 register 变量可能不存放在内存中,所以不能用取址运算符“&”来获取 register 变量的地址

1.3 最名不符实的关键字 ----static

第一个作用修饰变量

被修饰的变量又分为局部变量全局变量,但修饰后的它们都存在内存的静态区

原本的局部变量存放在栈中,全局变量存放在静态数据区中。

  • 静态全局变量
    • 作用域仅限于变量被定义的文件中,从定义之处开始,到文件结尾处结束
    • 定义之处前面想要使用就得在前面再加 extern
  • 静态局部变量
    • 在函数体里面定义的,就只能在这个函数里用了,同一个文档中的其他函数也用不了
    • 由于被 static 修饰的变量总是存在内存的静态区,所以即使这个函数运行结束,这个静态变量的值还是不会被销毁,函数下次使用时仍然能用到这个值

第二个作用修饰函数,函数前加 static 使得函数成为静态函数

  • 函数的作用域仅局限于本文件(所以又称内部函数)。
  • 不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名

static的历史:起初,在 C 中引入关键字 static 是为了表示退出一个块后仍然存在的局部变量。随后,static 在 C 中有了第二种含义:用来表示不能被其它文件访问的全局变量和函数

1.4 基本数据类型 ----short、int、long、char、float、double

【C语言深度解剖】第一章:关键字_第1张图片

1.4.1 数据类型与”模子“

数据类型类似于模子,在内存中直接卡出相应大小的内存来进行后续数据的存储。

在 32 位的系统上:

  • short 咔出来的内存大小是 2 个 byte

  • int 咔出来的内存大小是4个byte

  • long 咔出来的内存大小是4个byte

  • float 咔出来的内存大小是4 个 byte

  • double 咔出来的内存大小是 8 个 byte

  • char 咔出来的内存大小是 1 个 byte

【注意】这里指一般情况,可能不同的平台还会有所不同,具体平台可以用 sizeof 关键字测试一下。

1.4.2 变量的命名规则

【规则 1-1】命名应当直观且可以拼读,可望文知意,便于记忆和阅读。标识符最好采用英文单词或其组合,不允许使用拼音

【规则 1-2】命名的长度应当符合“min-length && max-information”原则。标识符的长度一般不要过长,较长的单词可通过去掉“元音”形成缩写。英文词尽量不缩写,特别是非常用专业名词,如果有缩写,在同一系统中对同一单词必须使用相同的表示法,并且注明其意思。

【规则 1-3】当标识符由多个词组成时,每个词的第一个字母大写,其余全部小写

【规则 1-4】尽量避免名字中出现数字编号

【规则 1-5】对在多个文件之间共同使用的全局变量或函数要加范围限定符(建议使用模块名的缩写作为范围限定符,如图形界面GUI_)。

【规则 1-6】标识符名分为两部分:规范标识符前缀(后缀) + 含义标识 ;非全局变量可以不使用范围限定符前缀。

【C语言深度解剖】第一章:关键字_第2张图片

作用域前缀

【C语言深度解剖】第一章:关键字_第3张图片

数据类型前缀

【C语言深度解剖】第一章:关键字_第4张图片

含义标识规则

变量含义标识:目标词 + 动词(的过去分词)+ [状语]+[目的地]

【C语言深度解剖】第一章:关键字_第5张图片

函数含义标识:动词(一般现时)+目标词+[状语]+[目的地]

【C语言深度解剖】第一章:关键字_第6张图片

【规则 1-7】程序中不得出现仅靠大小写区分的相似的标识符

【规则 1-8】一个函数名禁止被用于其它之处

【规则 1-9】所有宏定义、枚举常数、只读变量全用大写字母命名,用下划线分割单词。

【规则 1-10】局部变量中可采用通用的命名方式,仅限于 n、i、j 等作为循环变量使用。

【规则 1-11】结构体定义时需要有明确的结构体名

【规则 1-12】定义变量的同时千万千万别忘了初始化。定义变量时编译器并不一定清空了这块内存,它的值可能是无效的数据。

【规则 1-13】不同类型数据之间的运算要注意精度扩展问题,一般低精度数据将向高精度数据扩展。由于表达式的运算结果的类型根据操作数的类型决定的,所以当运算表达式操作数的类型和将要赋值的目标变量类型不一致时,操作数要先强制转换为目标变量数据类型。

【规则 1-14】禁止使用八进制的常数(八进制整常数必须以0开头,即以0作为八进制数的前缀。数码取值为0~7)和八进制的转义字符。在计算机中,任何以0开头的数字都被认为是八进制格式的数,例如052是十进制42。

【规则 1-15】char有三种不同的类型:单纯的char、signed char、unsigned char。其中signed char、unsigned char这两个用来声明数值的,单纯的char是真正的字符类型,声明字符的。一元负号运算(-)不能使用在unsigned表达式内

【规则 1-16】所有无符号型常量都应该带有字母U后缀

【规则 1-17】使用浮点数应遵循已定义好的浮点数标准。

【规则 1-18】代码的缩进一般为4个字符不要使用Tab键,因为不同的编译器Tab键的定义不同。

1.5 最冤枉的关键字 ----sizeof

1.5.1,常年被人误认为函数

int i=0;
A),sizeof(int); B)sizeof(i); C)sizeof int; D)sizeof i;

A,B: 值为 4。
c:    sizeof int 表示什么啊?int 前面加一个关键字?类型扩展?明显不正确,我们可以在 int 前加 unsignedconst 等关键字但不能加 sizeof。
D:	  结果也为 4
  • sizeof 在计算变量所占空间大小时,括号可以省略,而计算类型(模子)大小时不能省略
  • sizeof 是在编译时求值(sizeof 操作符里面不要有其他运算,否则不会达到预期的目的),所以sizeof(i=1234);这样的代码不被允许,因为i的值还是0,并没有被赋值为1234。
  • 在C99中,计算柔性数组所占空间大小时,sizeof 是在运行时求值,此为特例

1.5.2 sizeof(int)*p 表示什么意思?

首先计算int型所占字节数,然后再乘以p。

1.6 signed、unsigned 关键字

计算机底层只知道0、1,是如何区分正负呢:把基本数据类型的最高位腾出来,用来存储符号

  • 1:负号,值为除最高位以外的剩余的值填上这个“-”号
  • 0:正号,值为除最高位以外的剩余的值

缺省下,编译器默认数据为signed类型(char类型数据除外)

int main()
{
    signed char a[1000];
    int i;
    for(i=0; i<1000; i++)
    {
   		a[i] = -1-i;
    }
    printf("%d",strlen(a));
    return 0;
}
答案:255

计算机系统,数值一律按照补码存储

  • 正数:与原码相同。
  • 负数:符号位不变,其它部分取反再加1。

通过补码运算,可以将符号位和其他位统一处理;减法可以按照加法运算;若最高位有进位则进位被舍弃。

a[0]	-1 -> 0xff		1111 1111
a[1]	-2 -> 0xfe		1111 1110
a[2]	-3 -> 0xfd		1111 1101
	 	  ...
a[127]	-128 -> 0x80 	1000 0000
a[128]	-129 -> 0x7f 	1 0111 1111(需要九位才可以存储,去掉最高位,剩下8)    
a[128]	-130 -> 0x7e 	1 0111 1110(需要九位才可以存储,去掉最高位,剩下8)    
    	  ...
a[255]	-256 -> 0x0 	1 0000 0000(需要九位才可以存储,去掉最高位,剩下8)    
a[256]	-257 -> 0xff 	10 1111 1111(需要十位才可以存储,去掉最高两位,剩下8)	->	轮回了
    
所以a[0]-a[254]里面值都不为0,a[255]0,strlen碰到‘\0’结束。

1.7 if、else组合

1.7.1 bool变量与”零值“比较

FLASE的值在编译器里定义为0,但TRUE的值可以是任何非零的值,由于值不确定不能进行比较

bool bTestFlag = FALSE
//以下两种可以
if!bTestFlag){	}
if(bTestFlag){		}

1.7.2 float 变量和”零值“比较

float 和 double 类型的数据都有精度限制,直接拿来与0.0比较是不对的。“float.h” 提供了DBL_EPSILON、FLT_EPSILON、LDBL_EPSILON定义好的精度,如果一个数落在了[0.0 - EPSILON , 0.0 + EPSILON]这个闭区间,我们认为在某个精度内它的值与零值相等,否则不相等。(将EPSILON替换成上面三个则分别代表float、double、long double三种精度)

bool fTest() {
	float a = 0.0;
	if ((a >= -FLT_EPSILON) && (a <= FLT_EPSILON)) {
		return true;
	}
	else 
        return false;
}

int main() {
	printf("%d", fTest());
	return 10;
}

输出:1

1.7.3 指针变量和”零值“比较

int * p = null; //定义指针要初始化
//以下两种可以
if(NULL == p);	if(NULL != P);

这种写法有些奇怪,但是成功避免的“=” 和 “==”编译器检查不出的问题,所以以后要这样写。

1.7.4 else与哪个if配对

else始终与同一括号内最近的未匹配的if语句结合。

1.7.5 if语句后面的分号

if(NULL != p) ;
	fun();
等价于			
if(NULL != p)
{
    ;
}
fun();
会把分号解析成两条语句
    
真正使用空语句时候建议写法:
if(NULL != p) NULL;
fun();

【规则 1-19】由于if语句总是需要判断,所以先处理正常情况,再处理异常情况也就是说把正常情况的处理放在 if 后面,而不要放在 else 后面。

【规则 1-20】确保if和else子句没有弄反

【规则 1-21】赋值运算符不能使用在产生布尔值的表达式上。

if((x = y) != 0)	//Boolean by context
    
应修改为
x = y;
if(x != 0)
{
    foo;
}

【规则1-22】所有的 if - else if 结构应该由else子句结束,最后的else语句的要求是保护性编程。else语句或者要执行适当的动作,或者要包含适当的注释以说明为何没有执行动作,与switch语句中要求有一个default子句是一致的

if(x < 0)
{
    log_error(3);
    x = 0;
}
else if(y < 0)
{
    x = 3;
}
else	//this else clause is required,even if the programer expects this will never be reached
{
    	//no charge in value of x
}

1.8 switch、case组合

if、else 一般表示两个分支或者是嵌套比较少量的分支,如果分支比较多还是使用switch、case。

//基本格式
switch(variable)
{
    case	Valuel:
        //program code
        break;
    case	Value2:
    	//program code
        break;
    case	Value3:
    	//program code
        break;
   
        ...
        
    default:
        break;
}

【规则1-23】每个case语句的结尾绝对不要忘记加break,否则将会导致多个分支重叠。

【规则1-24】最后必须使用default分支,即使程序真的不需要也要保留下面语句:

defaultbreak;

【规则1-25】switch case组合中,禁止使用return语句

【规则1-26】switch表达式不应是有效的布尔值。

1.8.1 case关键字后面的值有什么要求吗

case后面只能是整型字符型的常量或者常量表达式(因为字符型数据在内存中储存的是它的ASCII码值)

1.8.2 case语句的排列顺序

【规则1-27】按照字母或者数字顺序排列各条case语句,A-B-C;1-2-3。

【规则1-28】按照正常情况放前面,异常情况放后面的顺序排列,同时都需要做好注释。

【规则1-29】按照执行频率排列。最常执行的放在前面,不常执行的放到后面。最常执行的可能是调试单步执行最多的代码,方便寻找。

1.8.3 case语句的其他注意事项

【规则1-30】简化每种情况的操作,如果某个case需要多个代码执行某个操作,可以把操作写成一个或几个子程序在case里进行调用。

【规则1-31】default子句只用于检查真正的默认情况,认真地把每一种情况都用case语句完成。

1.9 do、while、for关键字

C语言中循环语句有3种:while循环、do-while循环、for循环

  • while 循环:先判断 while 后面括号里的值,如果为真则执行其后面的代码;否则不执行。while(1)表示死循环。
  • do-while 循环:先执行 do 后面的代码,然后再判断 while 后面括号里的值,如果为真, 循环开始;否则,循环不开始。
  • for 循环:for 循环可以很容易的控制循环次数,多用于事先知道循环次数的情况下。

1.9.1 break与continue的区别

break:中止本层循环。

continue:终止本次(本轮)循环,进入下一次循环。

循环计数器(循环变量)与循环次数控制调剂必须是相同数据类型

循环调剂表达如果需要访问数组的元素,需要同时判断对数组的访问是否越界,否则很容易出现死循环。

char var1[MAX]
for(i = 0; i<MAX && var1[i]!=0; i++)
{
    //Even if 0 are not set in the var1 array,there is no risk of accessing out-side the array range
}

在循环体内,相等操作(== , !=)不能用于循环计数变量,因为当循环计数变量不是加减1时,会导致死循环。所以使用<=,>=,<,>进行代替

1.9.2 循环语句的注意点

【建议1-32】多层循环中,长循环放最内层最短的循环放最外层,减少CPU跨切循环的次数,提高效率。

【建议1-33】建议for语句的控制循环变量的取值采用“半开半闭区间的写法”,更加直观

for(n = 0; n < 10 ;n++)

【规则1-34】不能在for循环体内修改循环变量,防止循环失控。

【规则1-35】循环嵌套控制在3层内,多余的写成子函数。

【规则1-36】for语句的控制表达式不能包含任何浮点类型的对象

1.10 goto关键字

主张禁用goto关键字的使用,有可能会导致跳过程序初始化的操作。

1.11 void关键字

void真正发挥的作用在于:对函数返回的限定对函数参数的限定

void * 类型,任何类型的指针都可以直接赋值给它,无需进行强制类型转换。但不代表 void * 也可以无需进行强制类型转换赋值给其他的指针。

“空类型”可以包含“有类型”,“有类型“则不能包含”空类型“

void *p1
int *p2
p2 = p1;
//报错,提示不能将 void * 转换为 int *
int function(void){}

1.11.1 void 修饰函数返回值和参数

【规则1-37】如果函数没有返回值,应声明其为void类型。凡不加返回值类型限定的函数默认作为返回整型值处理。在编写C程序时,任何函数都必须一个不漏的指定其类型。

【规则1-38】如果函数无参数,应将其参数声明为void,否则别的函数在调用的时候传入参数后有些编译器不报错则会出现意外情况(我的编译器报错会)。

1.11.2 void指针

【规则1-39】ANSI标准,不能对void指针进行算法操作进行算法操作的指针必须是确定知道其指向数据类型大小的,也就是说必须知道内存目的地址的确切值。但是GNU不这么认为,它指定void *与char *一致。

void *pvoid
pvoid ++;				//ANSI:错误		GNU正确
pvoid += 1;				//ANSI:错误		GNU正确

(char *)pvoid ++;		//ANSI:正确		GNU正确
(char *)pvoid += 1;		//ANSI:错误		GNU正确

【规则1-40】如果函数的**参数可以是任意类型指针,应声明其参数为 void ***,任何类型的指针都可以传入其中(类似于泛型概念)。

void * memcpy(void *dest, const void *src, size_t len);
void * memset(void *buff, int c, size_t num);
//这样任何类型的指针都可以传入memcpy和memset中,这样体现了内存操作函数的意义。

*有趣的是memcpy memset的返回值也是void 类型

1.11.3 void不能代表一个真实变量

【规则1-41】void不能代表一个真实的变量,因为定义变量时必须分配内存空间。void体现了一种抽象,正如不能给抽象基类定义一个实例一样。T

void a;						//错误
function(void a);			//错误

1.12 return关键字

return用来中止一个函数并返回其后面跟着的值。

return(Val)//此括号可以省略,但是一般不省略。

【规则1-42】return语句不可返回指向”栈内存“的”指针“,因为该内存在函数体结束时被自动销毁

1.13 const关键字也许该被替换成readonly

const是constant的缩写,是恒定不变的意思,也翻译为常量、常数等,精确来说是只读变量,值在编译的时候不能被使用。其推出的初始目的在于取代预编译指令。

1.13.1 const修饰的只读变量

const修饰的只读变量必须在定义的同时初始化,具有不可变性。

1.13.2 节省空间,避免不必要的内存分配,同时提高效率

编译器通常不为普通const只读变量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的值,提高了效率:

#define M 3			//宏常量
 const int N=5;		//此时并未将N放入内存中
 ......
 int i=N;			//此时为N分配内存,以后不再分配!
 int I=M;			//预编译期间进行宏替换,分配内存
 int j=N;			//没有内存分配
 int J=M;			//再进行宏替换,又一次分配内存!

const定义的只读变量从汇编角度看,只是给出了对应的内存地址,所以在内存中只有一份备份(全局只读变量,存在静态区),在编译的时候确定其值具有特定类型

#define给出的是立即数,所以在内存中有若干备份,在预编译阶段进行替换,且没有类型

1.13.3 const修饰

  • 一般变量:修饰符const可以用在类型说明符前,也可以用在类型说明符后。

    int const i=2;const int i=2;
    
  • 数组:定义或说明一个只读数组可采用如下格式:

    int const a[5]={1, 2, 3, 4, 5};const int a[5]={1, 2, 3, 4, 5};
    
  • 指针:离谁近修饰谁:

    const int *p;		 // p 可变,p指向的对象不可变			->*p
    int const *p;		 // p 可变,p指向的对象不可变			->*p
    int *const p;		 // p不可变,p指向的对象可变			->p
    const int *const p;  // 指针 p 和 p 指向的对象都不可变		->p
    
  • 函数参数:当不希望这个参数值在函数体内被意外改变时使用:

    //告诉编译器*p在函数体中不能改变
    void Fun(const int *p);
    
  • 函数返回值:修饰函数返回值,返回值不可改变。

  • 另一链接文件引用const只读变量

    extern const int i;		 //正确的声明
    extern const int j = 10; //错误的声明,成了定义
    

1.14 最易变的关键字----volatile

volatile是异变的、不稳定的意思,用它修饰的变量表示可以被某些编译器未知的因素更改,比如操作系统、硬件或者其他线程等。遇到这个关键字的变量,编译器对访问该变量的代码不再进行优化。

int i=10;
int j = i;//(1)语句
int k = i;//(2)语句
//在(1)、(2)两条语句中,i没有被用作左值。在(1)语句时从内存中取出i的值赋给j之后,这个值并没有被丢掉,而是在(2)语句时继续用这个值给k赋值。

volatile int i=10;
int j = i;//(3)语句
int k = i;//(4)语句
//volatile 关键字告诉编译器 i是随时可能发生变化的,因而编译器生成的汇编代码会重新从i的地址处读取数据放在k中。如果一个端口数据或者是多个线程的共享数据就不会出错。

const volatile int i = 10:这行代码没有问题,**“volatile”的含义并非是“non-const”,volatile和const并不矛盾,只是控制的范围不一样,一个在程序本身之外,另一个是程序本身。**这个表明当前值i在当前程序的上下文中不会被修改,且可能会被外部的其余进程或系统进行修改。

const表示常量语义(运行时):被const修饰的对象在所在的作用域无法进行修改操作,编译器对于试图直接修改const对象的表达式会产生编译错误。

volatile表示“易变的”:即在运行期对象可能在当前程序上下文的控制流以外被修改(例如多线程中被其它线程修改;对象所在的存储器可能被多个硬件设备随机修改等情况)

1.15 最会带帽子的关键字——extern

extern可以置于变量或函数前,表明变量或函数的定义在别的文件中,不是不文定义的。链接器在其他模块中解析/绑定此标识符。

1.16 struct关键字

struct将一些相关联的数据打包成一个整体,方便使用。

1.16.1 空结构体有多大

结构体所占的内存大小是其成员所占内存之和。

typedef struct st_type_b
{
}type_b;
printf("%d\r\n",sizeof(type_b));

上述所述情况值为多少呢?答案为1,编译器认为任何一种数据类型都有其大小,非空结构体类型数据最少需要占一个字节的空间,而空结构体类型数据总不能比最小的非空结构体类型数据所占的空间大吧。编译器理所当然的认为你构造一个结构体数据类型是用来打包一些数据成员的,而最小的数据成员需要1个byte,编译器为每个结构体类型数据至少预留1个字节的空间。所以,空结构体的大小就定为1个字节。

1.16.2 柔性数组

结构体中的最后一个元素允许是位置大小的数组,这就叫做柔性数组,但结构中的柔性数组成员前面必须至少有一个其他成员。柔性数组成员允许结构中包含一个大小可变的数组。sizeof返回的这种结构大小不包含柔性数组的内存(它属于编外人员不占用结构体的编制,只是在使用的时候需要把它当作结构体的一个成员)。包含柔性数组成员的结构用malloc()函数进行内存的动态分配。

typedef struct st_type
{
	int i;
	int a[];
}type_a;
void main() {
	type_a * p = (type_a*)malloc(sizeof(type_a) + 100 * sizeof(int));
	printf("%d\r\n", sizeof(*p));
	free(p);
	return ;
}
输出:4

1.17 union关键字

union 维护足够的空间来置放多个数据成员中的“一种”,在union中所有的数据成员共用一个空间同一时间只能储存其中一个数据成员,所有的数据成员具有相同的起始地址

union StateMachine
 {
    char character;
	int number;
	char *str;
	double exp;
 };

一个union只配置一个足够大的空间以来容纳最大长度的数据成员,例如上述最大长度为double,所以StateMachine的空间大小是double类型。

1.17.1 大小端模式对union类型数据的影响

大端模式(Big_endian):字数据的高字节存储在低地址中,而字数据的低字节则存放在高地址中。

小端模式(Little_endian):字数据的高字节存储在高地址中,而字数据的低字节则存放在低地址中。

union型的成员的存取都是 相对于该联合体基地址的偏移量为0处开始,也就是联合体的访问不论对哪个变量的存取都是从union的首地址位置开始。某些系统可能同时支持两种存储模式,你可以用硬件跳线或在选项中设置其存储模式。

1.17.2 如何用程序确认当前系统的存储模式

int checkSystem( )
 {
     union check
     {
         int i;
         char ch;
     }c;
     c.i = 1;
     return (c.ch ==1);
 }

【C语言深度解剖】第一章:关键字_第7张图片

变量i占4个字节,但只有一个字节的值为1,另外三个字节的值都为0。如果取出低地址上的值为0,毫无疑问,这是大端模式;如果取出低地址上的值为1,毫无疑问,这是小端模式

【规则1-43】使用位域的时候需要提前用代码check当前系统的模式(大端或是小端),使用位域特别注意对其方式是LSB或是MSB

MSB LSB:起始地址为最高位, 最后地址为最低位。

LSB MSB:起始地址为最低位,最后地址为最高位。

一个芯片的管脚中,对于一个多比特的信号,比如32根的地址线,从低开始按0到31编个号。MSB就是31,LSB就是0。那么如果标记为:ADDR[31:0]就是MSB first的方式,如果标记为ADDR[0:31]就是LSB first的方式。

位域主要有两种用法

  • 访问较大数据类型中个别bit,或者一组bit
  • 将标志位或者其他短长度的数据压缩防止节省空间

位域特点

  • 位域从字的第一个位开始
  • 位域不能与整数的边界重叠
  • 可以给未命名的域声明大小,这种提供填充值
  • 字段中可以有未使用的位
  • 不能使用位域变量地址,不能用scanf,可以用中间变量赋值方法
  • 位域不能数组化
  • 位域必须进行赋值,且在大小范围内
  • 数据类型为signed的则其位域不能少于两位(一位符号)

建议申明结构体来存放位域,在同一结构体中不要包含其他数据类型

struct message
{
    signed int little: 4;
    unsigned int x_set: 1;
    unsigned int y_set: 1;
}message_chunk;

1.18 enum关键字

1.18.1 枚举类型的使用方法

enum enum_type_name
{
    ENUM_CONST_1,
    ENUM_CONST_2,
    ...
    ENUM_CONST_n
} enum_variable_name;

enum_type_name是自定义的一种数据类型名,enum_variable_name为enum_type_name的一个变量,也是枚举变量。enum_type_name是对一个变量取值范围的限定,如果赋给该类型变量的值不在列表中,则会报错或者警告。ENUM_CONST_1,ENUM_CONST_2…这些成员都是常量,可以给这些常量赋值,如果不赋值则会从被赋初值的那个常量开始依次加1,如果都没有赋值,它们的值从0开始依次递增1。

enum Color
{
    GREEN=1,
    RED,
    BLUE,
    GREEN_RED=10,
    GREEN_BLUE
}ColorVal;
其中:
    GREEN=1
    RED=2
    BLUE=3
    GREEN_RED=10
    GREEN_BLUE =11

枚举类型的成员的值必须是int类型能表述的

1.18.2 枚举与#define宏的区别

  1. #define宏是在预编译阶段进行简单替换,枚举是在编译时候确定其值的。
  2. 调试里可以调试枚举常量不能调试宏常量
  3. 枚举一下定义大量相关常量#define宏一次定义一个

1.19 伟大的缝纫师——typedef关键字

1.19.1 历史的误会——也许应该是typerename

typedef 的真正意思是给一个已经存在的数据类型(注意:是类型不是变量)取一个别名,而非定义一个新的数据类型

在实际项目中,为了方便,可能很多数据类型(尤其是结构体之类的自定义数据类型)需要我们重新取一个适用实际情况的别名

typedef struct student
 {
 //code
 }Stu_st,*Stu_pst;
A),struct student stu1;和 Stu_st stu1;没有区别。
B),struct student *stu2;和 Stu_pst stu2;和 Stu_st * stu2;没有区别。
我们把“struct student { /*code*/}”看成一个整体,typedef 就是给“struct student{/*code*/}”取了个别名叫“Stu_st”;同时给“struct student{ /*code*/} *”取了个别名叫“Stu_pst”
C),const Stu_pst stu3;	->stu3指针
D),Stu_pst const stu4;	->stu4指针
const 放在类型名“int”前后都行;而 const int *p 与 int* const p 则完全不一样。我们看const修饰谁都时候完全可以将数据类型名视而不见,当它不存在。
Stu_pst是“struct student { /*code*/} *”的别名,struct student {/*code*/} *”是一个整体。对于编译器来说,只认为 Stu_pst 是一个类型名,所以在解析的时候很自然的把“Stu_pst”这个数据类型名忽略掉。

【规则1-44】用typedef重命名基本的数据类型,以替换原始的数据类型。

typedef signed char        int8_t;
typedef short              int16_t;
typedef int                int32_t;
typedef long long          int64_t;
typedef unsigned char      uint8_t;
typedef unsigned short     uint16_t;
typedef unsigned int       uint32_t;
typedef unsigned long long uint64_t;

1.19.2 typedef与#define的区别

E):	#define INT32 int
     unsigned INT32 i = 10;
F): typedef int int32;
	 unsigned int32 j = 10//其中E)编译不会出错因为在预编译的时候INT32被替换为int
//其中F)编译出错因为用typedef取的别名不支持这种类型扩展。
G): #define PCHAR char*
	 PCHAR p3,p4;
H): typedef char* pchar;
 	 pchar p1,p2;
//两组代码编译都没有问题,但是,这里的p4却不是指针,仅仅是一个char类型的字符。

的别名, “struct student {/code/} *”是一个整体。对于编译器来说,只认为 Stu_pst 是一个类型名,所以在解析的时候很自然的把“Stu_pst”这个数据类型名忽略掉。


【规则1-44】用typedef重命名基本的数据类型,以替换原始的数据类型。

```c
typedef signed char        int8_t;
typedef short              int16_t;
typedef int                int32_t;
typedef long long          int64_t;
typedef unsigned char      uint8_t;
typedef unsigned short     uint16_t;
typedef unsigned int       uint32_t;
typedef unsigned long long uint64_t;

1.19.2 typedef与#define的区别

E):	#define INT32 int
     unsigned INT32 i = 10;
F): typedef int int32;
	 unsigned int32 j = 10//其中E)编译不会出错因为在预编译的时候INT32被替换为int
//其中F)编译出错因为用typedef取的别名不支持这种类型扩展。
G): #define PCHAR char*
	 PCHAR p3,p4;
H): typedef char* pchar;
 	 pchar p1,p2;
//两组代码编译都没有问题,但是,这里的p4却不是指针,仅仅是一个char类型的字符。

你可能感兴趣的:(专业书籍,c语言)