类型、运算符和表达式
文章目录
变量和常量是程序处理的两种基本数据类型,声明语句说明变量的名字及类型,也可指定变量初值,运算符指定要进行的操作。
1 变量名
变量名由字母、数字及下划线 “_” 组成,且第一个字符必须为字母或下划线。
2 数据类型及长度
C 中的类型可分为以下几种:基本类型,枚举类型、void类型和派生类型。
2.1 基本类型
算术类型,包括整数类型和浮点类型。
类型 | 大小 | 值范围 |
---|---|---|
char | 1字节 | -128~127 |
short | 2字节 | -32768~32767 |
int | 2字节或4字节 | -2147483648~2147483647 |
long | 4字节 | -2147483648~2147483647 |
float | 4字节 | 6位小数 |
double | 8字节 | 15位小数 |
long double | 16字节 | 19位小数 |
2.2 枚举类型
算术类型,被用来定义在程序中只能赋予其一定的离散整数值的变量。
2.3 派生类型
包括指针类型、数组类型、结构类型、共用体类型和函数类型。
2.4 void类型
通常用于以下三种情况:
函数返回为空:函数没有返回值。
函数参数为空:函数不接受参数。
指针指向 void:类型为 void * 的指针代表对象的地址,而不是类型,可转换为任何数据类型。
3 常量
3.1 整数常量
-
字符常量括在单引号中。
-
int类型无后缀,long类型常量后缀为l或L,无符号常量后缀为u或U。
-
八进制整型常量前缀为0,十进制整型常量不带前缀,十六进制整型常量前缀为0x或0X。
3.2 浮点数常量
包含一个小数点或一个指数,float类型常量以字母f或F结尾,double类型常量没有后缀,long double类型常量以字母l或L结尾。
4 运算符
4.1 算术运算符
二元算术运算符包括:+、-、*、/、%(取模运算符)。整数除法会截断结果中的小数部分。取模运算符不能应用于float或double类型。
利用取模运算符可以限制数的范围,例如 a%10 的值只能在 0~9 之间,因为大于等于10的部分都会被整除。
4.2 关系和逻辑运算符
关系运算符:> >= < <= == !=
关系运算符的优先级比算术运算符低。
逻辑运算符:&& || !
由 && 和 || 连接的表达式按从左到右的顺序进行求值。
逻辑非运算符 ! 的作用是将非0操作数转换为0,将操作数0转换为1。if(!valid)
与if(valid == 0)
等价,都是当valid为0是返回真。
4.3 类型转换
当一个运算符的几个操作数类型不同时,就需要通过一些规则把它们转换为某种共同的类型。一般来说,自动转换是指把”较窄“操作数转换为”较宽“操作数,并且不丢失信息的转换。
/* 将字符转换为小写形式 */
int lower(int c){
if(c >= 'A' && c <= 'Z')
return c + 'a' - 'A';
else
return c;
}
若没有unsigned类型的操作数,则遵循以下转换规则:
- 若其中一个操作数的类型为long double,则将另一操作数转化为此类型。
- 若其中一个操作数类型为double,则将另一操作数转换为此类型。
- 若其中一个操作数类型为float,则将另一操作数转换为此类型。
- 将char与short类型操作数转换为int类型。
- 若其中一个操作数类型为long,则将另一操作数转换为此类型。
注意:表达式中的float类型操作数不会自动转换为double类型,主要为了节省存储空间和机器执行时间(双精度算术运算非常费时)。
当表达式包含unsigned类型操作数时,转换规则复杂一些,因为带符号值与无符号值间的比较运算与机器相关,取决于机器中不同整数类型的大小。
强制类型转换:使用一个一元运算符强制进行显示类型转换。可以理解为表达式首先被赋值给类型名指定的类型的某个变量,然后再用该变量替换整条语句。
(类型名) 表达式
unsigned long int next = 1;
/* 返回取值在0~32767之间的伪随机数 */
int rand(void){
next = next * 1103515245 + 12345;
return (unsigned int)(next / 65536) % 32768;
}
/* 为rand函数设置种子数 */
void srand(unsigned int seed){
next = seed;
}
rand() % 10; //产生0~9的随机数
rand() % 51 + 13; //产生13~63的随机数
4.4 自增、自减运算符
这两个运算符既可以用作前缀运算符,也可用作后缀运算符。表达式++n先将n的值递增1,然后再使用n的值;而表达式n++则先使用n的值,然后再将n的值递增1。对于使用变量n的值的上下文来说,++n和n++效果是不同的。自增与自减运算符只能作用于变量,表达式 (i+j)++ 是非法的。
/* 从字符串s中删除字符c */
void squeeze(char s[], char c){
int i, j;
for(i = j = 0; s[i] != '\0'; i++)
if(s[i] != c)
s[j++] = s[i];
s[j] = '\0';
}
4.5 按位运算符
C语言提供了6个位操作运算符,这些运算符只能作用于整数:
& 按位与(AND)
| 按位或(OR)
^ 按位异或(XOR)
<< 左移
-
>> 右移
- 按位求反(一元运算符)
掩码:某些位为1,某些位为0的整数,用&和掩码可以屏蔽整数的某些位,从而达到查看某些位的效果。
/* &用于屏蔽某些二进制位(置0) */
n = n & 0177; //0177即001111111,屏蔽n的前两位
/* |用于将某些二进制位置1 */
n = n | 0600; //将n的两位置1
/* ^用于将某些二进制位转置(若位为1则置0,为0则置1 */
n = n ^ 0600; //任一位与0异或都等于它本身,故将n的前2位转置
/* <<与>>用于将左操作数左移或右移,移动的位数由右操作数指定 */
n<< 2; //将n的值左移2位,右边空出的2位用0填补,等价于n*4
/* ~用于求整数的二进制反码,即二进制位的1变为0,0变为1 */
n = n & ~0600; //将n的前两位置0
/*
返回x中从第p位开始的n位
x >> (p+1-n)将从第p位开始的n位移到最右端
~0的所有位都为1,~0 << n将~0左移n位,最右边的n位用0填补,再用~取反
~(~0 << n)获得了最右边n位都为1的掩码
*/
unsigned getbits(unsigned x, int p, int n){
return (x >> (p+1-n)) & ~(~0 << n);
}
注意:上述位操作中并没有指定n的机器字长,因为系统会自动补0,若n为16位则在0600左边补7个0,而不要采用0000600的形式,这样不利于移植。
4.6 赋值运算符与表达式
在赋值表达式中,若表达式左边的变量重复出现在表达式的右边,可将表达式缩写,如:
i = i + 2
可缩写为i += 2
,其中+=
运算符称为赋值运算符。
大多数二元运算符(op)都有相应的赋值运算符(op=),如 + - * / << >> & ^ | 等。
若exp1和exp2是表达式,则exp1 op= exp2
等价于exp1 = (exp1) op (exp2)
。
/* 统计x中位为1的位数 */
int bitcount(unsigned x){
int i;
for(i = 0; x != 0; x >>= 1)
if(x & 01)
i++;
return i;
}
4.7 条件表达式
条件表达式可以改写为使用三元运算符 ? : 的表达式。
if(exp1) exp2;
else exp3;
/* 先计算exp1,若其值为真则计算exp2的值,否则计算exp3的值 */
exp1 ? exp2 : exp3;
4.8 运算符优先级
类型 | 运算符 | 结合性 |
---|---|---|
后缀 | () [] -> . ++ – | 从左到右 |
一元 | + - ! ~ ++ – (type) * & sizeof | 从右到左 |
* / % | 从左到右 | |
+ - | ||
<< >> | ||
< <= > >= | ||
== != | ||
& | ||
^ | ||
| | ||
&& | ||
|| | ||
三元 | ? : | 从右到左 |
= += ~= *= /= %= &= | 从右到左 | |
^= |= <<= >>= | 从左到右 | |
, |
: | 从右到左 |
| | = += ~= *= /= %= &= | 从右到左 |
| | ^= |= <<= >>= | 从左到右 |
| | , | |