文章目录
前言
本手册是在学习过C语言的情况下,复习C语言使用的。如果帮助到大家,实属荣幸,如果有不足之处也可以在评论中提出来,互相学习互相增长,谢谢。
1、预处理器
预处理器其实是一个小软件,它可以在编译前处理C程序。而预处理器的行为都是有预处理指令控制的,我们前面接触到的头文件“#include……”就是一种预处理指令。
1.1-预处理指令
指令都是以“#”开头的,在指令中可以随意添加空格,但在“#”的开头不允许有空白字符。常见的预处理指令一共有三种,分别是:宏定义、文件包含、条件编译,对于“#include”文件包含我们就不赘述了,主要介绍后两种。
1.2-宏定义
宏定义就是以“#define”开头的预处理指令,整体格式为:#define 宏定义标识符 替换列表
这样,当代码中出现了宏定义标识符,那么它就会被替换列表里的内容替换。比如:#define N 5,在代码编译过程中,所有的”N”都会被替换成5。这样使用的好处是使程序更容易读懂,因为某些数的含义可以被描述出来,还更容易维护,只要修改替换列表,那么大批的宏定义标识符都会被修改。
1.2.1-带参宏
上面介绍的宏定义被叫做简单宏,在简单宏的基础上写上参数,这样就可以替换更多的内容。
带参宏的格式通常为:“#define 宏定义标识符(参数列表) 替换列表
比如:#define MAX(x,y) ((x)>(y)?(x):(y)) ,这样,代码中的三目运算符就可以使用MAX(x,y)替换了,就变得通俗易懂,也比较方便。
这里我们还需要注意圆括号的使用,在宏定义中,每个参数和运算符都必须在括号内。如果不写,很难保证编译器会准确识别出我们想要替换的内容。
1.3-条件编译
条件编译指的是,编译器会根据预处理器所执行的测试结果来包含或排除程序的片段。
1.2.1-#if与#endif
如果了解过shell脚本语言,那么我们可以把这两种条件编译指令与shell脚本中的if语句共同理解,如果出现了#if,就一定会有#endif来结尾,以包含或排除一个片段。如果没有接触过shell脚本,那就把它当成if语句理解,只不过需要加上#endif来确定范围,而不是通过”{ }“。语法格式如下:
#if 条件……
…………
…………
#endif
满足条件,就会执行中间的语句。比如:
#define X 1
#if X
printf("Hello!\n");
#endif
在C语言中,表示对错只有两种情况,非0的数表示true,0表示false,要记住。(面试小考点哦!)
在上例中,X值为1,表示正确,那么就会执行printf,相反,如果为0,就不会执行。
1.2.2-defined运算符
defined的作用就是检测宏定义表示符是否被定义,如果被定义则表示正确,相反表示错误。
#if define(X)
printf("no X!\n");
#endif
其实,我们还可以写成
#ifdef X/#ifndef 标识符
………………
………………
#endif
#ifdef就是将defined运算符与普通的#if结合起来,效果就是二者功能的结合体。而#ifndef相当于把#if与!defined(标识符)结合起来用于判断该标识符是否没被定义。
2、运算符
C语言中最基本的结构就是表达式,表达式是表示如何计算值的一个公式,最简单的表达式就是变量和常量。而运算符则是构建表达式的基本工具,其包含了算术运算符、关系运算符、逻辑运算符、位运算符。
2.1-算术运算符
除了包含基础数学中的“+”、“-”、“ * ”、“/”,之外还有“%”、“,”、“=”、“+=”、“-=”、“ * =”、“/=”、“%=”、“++”、“–”。
2.1.1-普通算术运算符
加减乘除我们就不介绍了,与基础数学中的用法一样。
“%”——模运算符,也称取余/求余运算符,是一个双元运算符。与除法类似但也有所不同,结果为两元相除所得的余数。
int a = 5 , b = 3;
printf("a %% b = %d\n",a%b);
运行结果为:
“,”——逗号运算符,用于计算多个表达式,总体从左至右依次计算,结果为最后一个表达式的值。
int a = 5 , b = 3 , c = 0;
printf("the res is %d\n",(c = a + b ,a-b,c+b));
运行结果为:
“=”——赋值运算符,即与将表达式的右操作数赋给左操作数。比如,在上两个例子中的变量初始化就用到了赋值运算符。
int a = 5 , b = 3;
c = a + b;
2.1.2-复合算术运算符
“++”——自增运算符,见名知意,若对某个变量使用该运算符,会使变量自身增加1。
“–”——自减运算符(两个减号连着写),见名知意,若对某个变量使用该运算符,会使变量自身减少1。
对于自增自减运算符我们需要注意的是,其要影响的目标变量与该运算符之间的位置关系是可能影响结果的。
当我们单独使用时:
a++;
++a;
--b;
b--;
根据自增自减运算符的语法规则,最终如果输出a和b的值,其变化都是a增加了2,b减少了2。
但当我们与其他语句共同使用时:
int a = 5 , b = 3;
printf("src a is %d b is %d\n",a,b);
printf("change after ");
printf("a is %d b is %d\n",a++,++b);
printf("now ");
printf("a is %d b is %d\n",a,b);
其结果为:
不难看出,当变量名在自增/自减运算符前面,调用该表达式会先使用该变量,再自增/自减,当变量名在自增/自减运算符后面,调用该表达式会先自增/自减,再使用该变量。
2.1.3-复合赋值运算符
“+=”、“-=”、“ * =”、“/=”、“%=”都属于复合赋值运算符,其最终效果都是赋值,过程不同罢了。其操作为将表达式左操作数与右操作数进行算术运算并将结果赋给左操作数。比如,如果是“a+=b”,就相当于“ a = a + b”,依此类推。
int a = 5 , b = 3 , c = 4;
a+=b;
printf("a = %d\n",a);
c%=b;
printf("c = %d\n",c);
运行结果为:
2.2-关系运算符
见名知意,确定两个变量之间的关系的运算符,包含了基础数学中的“>”、“>=”、“<”、“<=”,同时还有“= =”、“!=”。
“>”、“>=”、“<”、“<=”即为大于、大于等于、小于、小于等于,只不过写法上略有不同。
“==”表示C语言中的判断等于,即判断表达式左右操作数是否相等。
“!=”表示C语言中的不等于,即判断表达式左右操作数是否不等。
2.3-逻辑运算符
逻辑运算符包含了逻辑与、逻辑或、逻辑非。
“&&”——逻辑与,当运算符的左操作数和右操作数同时表示正确时,整个表达式表示正确,否则,整个表达式表示错误。需要注意的是,当可以判断左操作数表示错误的时候,就不会再去判断右操作数,也就是不会执行右操作数/表达式。
int a = 4 , b = 5;
(a>b) && (a<b);//整个表达式表示错误
“||”——逻辑或,当运算符的左操作数和右操作数同时表示错误时,整个表达式表示错误,否则,整个表达式表示正确。同上,当可以判断左操作数表示正确的时候,就不会再去判断右操作数,也就是不会执行右操作数/表达式。
int a = 4 , b = 5;
(a>b) || (a<b);//整个表达式表示正确
“! ”——逻辑非,是一个单元运算符,会将表达式的结果变为与原来相反的结果。
int a = 4 , b = 5;
!(a<b);//整个表达式表示错误
特别提醒:使用关系运算符和逻辑运算符的表达式称为逻辑表达式,其结果只有true和false两种情况。
2.4-位运算符
在计算机中,任何操作数都是以二进制表示的,这就诞生了一种通过操作其某位上的数字的运算符,位运算符。位运算符包含了移位运算符、按位运算符。
2.4.1-进制转换
由于牵扯到二进制数,我们这里介绍一下进制转换。实际上,我们常接触的进制数有二进制、八进制、十进制、十六进制。
二进制:全部由0和1组成,位数依据实际情况而定。
八进制:由0~7之间的数组成,首位为数字0,其余位任意。
十进制:我们正常使用数都是十进制数。
十六进制:由0~15之间的数组成,10至15分别用A-F表示。
转换过程:
二进制→十进制:从最右端往左,第一位表示的值为2的当前位数-1次幂乘上该位的值,依此类推,最后求和。如:
二进制→八进制:从最右端往左,每三位一组,不够三位在每组的左端补0凑满三位。每组按照十进制转换并依次排列即可。如:
二进制→十六进制:从最右端往左,每四位一组,不够四位在每组的左端补0凑满四位。每组按照十进制转换并依次排列即可。如:
八进制→二进制:其实懂得了二进制转八进制,那么八进制转二进制就很容易了。比如033,抛去首位0,3的二进制数为0011,那么最总结果就是11011。
八进制→十进制:方法与二进制转十进制相同,只不过从2的次幂变成了8的次幂,这里就不过多介绍了。
八进制→十六进制:先将八进制转换成二进制,再将二进制转换成十六进制即可。
十进制→二进制:十进制转换成二进制用到了一种列式除法,如下:
方法就是对一个十进制数除2,将整数商写道下面,余数写到侧面,依此类推,不断除2直至商为0,然后从0向上把每次的余数按顺序写下来就得到了这个十进制的二进制形式,若没位数要求,第一个0可省。
十进制→八进制:方法与十进制转二进制相同,只不过将除数换成了8,其余步骤一样,就不再介绍了。
十进制→十六进制:先将十进制转换成二进制,再将二进制转换成十六进制即可。
十六进制→二进制:根据二进制转换成十六进制的方法,我们可以得出,十六进制数的每一位都由四位二进制数表示,那么反过来就是写出每一位数的二进制形式,然后按顺序写出来就是结果,这里就不再过多介绍了。
十六进制→八进制:先将十六进制转换成二进制,再将二进制转换成八进制即可。
十六进制→十进制:先将十六进制转换成二进制,再将二进制转换成十进制即可。
2.4.2-移位运算符
“<<”——左移位运算符,为二元运算符,即得到 / 将左操作数对应的二进制数 / 向左移动 / 右操作数个 / 位,并在左操作数对应的二进制的最右端用“0”补齐最左端溢出的位 / 后的结果。语言表达,理解起来比较匮乏,直接看例子。(这里的“/”只表示短句,无其他含义)
为简化理解,我们采用16位无符号数进行测试。
int a = 5 , b = 3 , c = 2;
b = a<<c;
printf("b is %d\n",b);
运行结果为:
根据运算符的含义,我们先将a转换成二进制数,5的二进制(16位,无符号)表示为“0000000000000101”,左移位运算符的右操作数为c,表示2,所以向左移两位,变成“00000000000101”,再在最右端用0,补上溢出的两位,结果为“0000000000010100”,再转换成十进制就是20。
“>>”——右移位运算符,如果理解了左移,那么右移就很容易了。对于无符号或非负的数,跟左移类似,只不过变成了右移,然后在最左端补0。如果是负数,根据实际情况,在左端补0或为保留符号位补1。
int a = 5 , b = 3 , c = 2;
b = a>>c;
printf("b is %d\n",b);
运行结果为:
2.4.3-按位运算符
“~”——按位取反运算符,是一元运算符,即将操作数对应的二进制数的每一位取反(0变1,1变0)即可。
“&”——按位与运算符,是二元运算符,将左右操作数的二进制数每位对应,如果对应位上的两数全为1则结果为1,其余情况结果为0。
“^”——按位异或运算符,是二元运算符,将左右操作数的二进制数每位对应,如果对应位上的两数相同则结果为0,不同结果为1。
“|”——按位或运算符,是二元运算符,将左右操作数的二进制数每位对应,如果对应位上的两数有一位是1则结果为1,否则结果为0。
“&”、“^”、“|”都可以与赋值运算符结合,效果同上,最终再加上赋值功能。
2.5-sizeof()运算符
表面上长得像一个函数,实际上是一种操作符,与“++”、“–”同等。功能就像他的名字一样,得到变量或数据类型所占内存空间的大小,以字节为单位。若为字符串测量,包含了‘\0’。比如
int a = 5;
char x[] = "mo";
printf("int size is %d\na size is %d\nx size is %d\n",sizeof(int),sizeof(a),sizeof(x));
运行结果为:
2.6-优先级
就像加减乘除先算乘除一样,每个运算符都有自己的优先级。在C语言中的大致顺序为:算术运算符>移位运算符>条件运算符>按位运算符>赋值。具体如下:(由于*会影响整体格式,所以这里用图片)
2.7-三元运算符
是一种特殊的运算符,本质是一种条件表达式,由于是唯一一个需要三个操作数的运算符,所以得此名。格式如下:
//表达式1?表达式2:表达式3
int a = 5 , b = 3 , c = 0;
printf("res is %d\n",a>b?a:b);
那么它的作用可以概括为,当表达式1的结果为真,执行表达式2,否则执行表达式3。