算术运算符 + - * / %

除了%操作符,其余几个操作符都是既适用于浮点类型有适用于整数类型。

当/操作符的两个操作数都是整数时,它执行整除运算,在其他情况下则执行浮点数除法,整除运算的任意操作符为负值,运算结果又编译器定义。整数类型进行 “/” 运算商也是整数,截断小数部分

%为取模操作符,它接受两个整型操作数,把左操作数除以右操作数,它的返回值是余数而不是商

应尽量避免让负数参与/ 和 %运算,结果肯能是不确定的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>

void div(unsigned int i, unsigned int j)
{
        if (i == ((i/j)*j))
        {
                printf ("%u is divisible by %u\n", i, j);
        }
}
void div_mod(unsigned int i, unsigned j)
{
        if (i == (((i/j)*j) + i%j))
        {
                printf ("%u is divisible by %u\n", i, j);
        }
}
int main()
{
        div(100, 10);
        div(100, 11);
        div_mod(100, 10);
        div_mod(100, 11);
}

运行结果:

1
2
3
100 is divisible by 10
100 is divisible by 10
100 is divisible by 11

逻辑操作符 && || !

使用&&或||连接的表达式从左到右求值,已知结果则立即停止。

注意!与~操作符的区别:!是逻辑取反,~是按位取反

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <stdio.h>

int main()
{
        int a;
        int i;

        for (a=0, i=0; a<=1&&!i++; a++)
        {
                a++;
        }

        printf("a = %d x = %d\n", a, i);
}

运行结果:

1
a = 2 x = 1

位操作符 & | ^ ~ « »

&可用于屏蔽(mask off)位,|可用于设置(turn on)位

value = value & ~(1«bit_number)或者 value &= ~(1«bit_number)

value = value | 1«bit_number或者 value |= 1«bit_number

~操作符可用于定义机器字长全F

通过以下两种方式定义机器字长全F

ULONG g_ulA = (ULONG)-1L

ULONG g_ulA = ~0UL

« 左移n位,等效于 *2^n(不溢出情况下)

>> 右移n位,等效于/ 2^n

左移右移两个操作数必须都是整型类型

右移位操作存在一个左移位操作不曾面临的问题:从左边移入新位时,可以选择两种方案:

一种是逻辑移位,左边移入的位用0填充;

另一种是算术移位,左边移入的位有原先该值的符号位决定,符号位为1则移入的位均为1,符号位为0则移入的位均为0,这样能够保证原数的正负形式不变。

算术左移和逻辑左移是相同的,它们只在右移时不同,而且只有当操作数是负值时才不一样。

注:标准说明无符号类型值的执行的所有移位操作都是逻辑移位,但对于有符号值,到底是采用逻辑移位还是算术移位取决于编译器。因此,一个程序如果使用了有符号数的右移位操作,它就是不可移植的。

&在特定情况下可以代替%进行取余运算

除数为2^n时取余可用&代替,且效率更高

以下为判断整除的例子

除数任意

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
BOOL_T IsDivisible_A(IN UINT uiX, IN UINT uiY)
{
    BOOL_T bDiv = BOOL_FALSE;

    if (0 == (uiX%uiY))
    {
        bDiv = BOOL_TRUE;
    }

    return bDiv;   
}
BOOL_T IsDivisible_B(IN UINT uiX, IN UINT uiY)
{
    BOOL_T bDiv = BOOL_FALSE;

    if (uiX == ((uiX/uiY)*uiY))
    {
        bDiv = BOOL_TRUE;
    }

    return bDiv;   
}

除数为2^n

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
BOOL_T IsDivisible_8_A(IN UINT uiX)
{
    BOOL_T bDiv = BOOL_FALSE;

    if (0 == (uiX&0x7U))
    {
        bDiv = BOOL_TRUE;
    }

    return bDiv;   
}
BOOL_T IsDivisible_8_B(IN UINT uiX)
{
    BOOL_T bDiv = BOOL_FALSE;

    if (uiX == ((uiX>> 3UL)<<3UL))
    {
        bDiv = BOOL_TRUE;
    }

    return bDiv;   
}

对数据进行截取、移位、组合,就可以进行字节序转换

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include <stdio.h>

#define Conversion_4Byte(x ,y) \
        y =     \
        ((x & 0xFF000000U) >> 24)| \
        ((x & 0x00FF0000U) >> 8) | \
        ((x & 0xFF00U)  << 8)  | \
        ((x & 0xFFU)    << 24)
int main()
{
        unsigned int x = 0xAABBCCDD;
        unsigned int y = 0;

        Conversion_4Byte(x, y);

        printf("%#X \n%#X\n", x, y);

}

运行结果:

1
2
0XAABBCCDD
0XDDCCBBAA

位运算符常见用法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#define BIT_SET(f, b)   ((f) |= (b))
#define BIT_RESET(f, b) ((f) &= ~(b))
#define BIT_TEST(f, b)  (((f)&(b)) != 0)
#define BIT_MATCH(f, b) (((f)&(b)) == (b))

typedef enum tagEvent
{
    EVENT_X = 0,
    EVENT_Y,
    EVENT_Z
}

#define EVENT_MASK(enEvt)   ((UINT)(1UL << (ULONG)(UINT)(enEvt)))

#define EVENT_ON(uiCur, enEvt)   (BIT_SET(uiCur, EVENT_MASK(enEvt)))
#define EVENT_OFF(uiCur, enEvt)  (BIT_RESET(uiCur, EVENT_MASK(enEvt)))
#define EVENT_TEST(uiCur, enEvt) (BIT_TEST(uiCur, EVENT_MASK(enEvt)))

#define BIT_SET(f, b)   ((f) |= (b))
#define BIT_RESET(f, b) ((f) &= ~(b))
#define BIT_TEST(f, b)  (((f)&(b)) != 0)
#define BIT_MATCH(f, b) (((f)&(b)) == (b))

#define STATUS_X    0x01U
#define STATUS_Y    0x02U
#define STATUS_Z    0x04U

#define STATUS_ALL  (STATUS_X | STATUS_Y | STATUS_Z)

#define STATUS_ISVALID(uiStatus)    (BIT_TEST(uiStatus, STATUS_ALL) && \
                                     !BIT_TEST(uiStatus, ~STATUS_ALL))

整理常量后缀

1
2
3
4
5
6
7
类型         后缀
int          默认
unsigned int     u/U
long         l/L
unsigned long     ul/UL
long long       ll
unsigned long long  ull/ULL

为常量写适当的后缀,可增加代码可读性,也能预防由于转换引起的微妙问题

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <stdio.h>

int main()
{
        printf("the default size %d\n", sizeof(0));
        printf("the postfix U size %d\n", sizeof(0U));
        printf("the postfix L size %d\n", sizeof(0L));
        printf("the postfix UL size %d\n", sizeof(0UL));
        printf("the postfix LL size %d\n", sizeof(0LL));
        printf("the postfix ULL size %d\n", sizeof(0ULL));
}

32bitCPU运行结果:

1
2
3
4
5
6
the default size 4
the postfix U size 4
the postfix L size 4
the postfix UL size 4
the postfix LL size 8
the postfix ULL size 8
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <stdio.h>
#define USHORT  unsigned int
#define UINT    unsigned int
#define ULONG   unsigned long
#define UINT64  unsigned long long
int main()
{
        USHORT usA = (USHORT)-1;
        UINT   uiB = (UINT)-1;
        ULONG  ulC = (ULONG)-1;
        UINT64 uiD = (UINT64)-1;

        printf("%hx\n%x\n%lx\n%llx\n", usA, uiB, ulC, uiD);
}

运行结果:

1
2
3
4
ffff
ffffffff
ffffffff
ffffffffffffffff

整型提升(intergral promotion)

C语言整型算术运算总是至少以缺省整型的精度来进行的。为了获得这个精度,表达式中的字符型和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。

如下面的表达式求值中:

char a, b, c;

a = b + c;

b和c的值被提升为普通整型,然后再执行加法运算。加法运算的结果将被截短,然后再存储于a中。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <stdio.h>
int main()
{
        unsigned short usData = 0;
        if (~usData > (unsigned short)0)
        {
                printf ("TRUE\n");
        }
        else
        {
                printf("FALSE\n");
        }
}

运行结果:

1
FALSE

“~“会对操作数做“整型提升”,USHORT 提升为INT变为有符号类型,取反后为四字节全F(-1<0)

不要让UCHAR , USHORT,直接进行“~”, « »也有类似问题

算术类型转换

当运算符的操作数类型不一致时,运算前会将操作数转换为同一类型

原则:

1、长度不同,往更大长度的类型转换

2、长度相同,往无符号类型转换

long double > double > float > unsigned long > long > unsigned int > int

下面这个代码包含一个潜在的问题

1
2
3
int a = 5000;
int b = 25;
long c = a * b;

表达是a*b是以整型进行计算,在32位整数的机器上,这段代码运行起来毫无问题,但在16位整数的机器上,这个乘法运算会产生溢出,这样c就会被初始化为错误的值。

解决方案是在执行乘法运算之前把其中一个(或两个)操作数转换为长整型

long c = (long)a * b

扩展

从一个较小的数据类型转换到一个较大的类型

要将一个无符号数转换为一个更大的数据类型,只要简单地在表示的开头添加0。这种运算被称为零扩展(zero extension)

要将一个二进制补码数字转换为一个更大的数据类型,规则是执行一个符号扩展(sign extension),在表示中添加最高有效位的值。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <stdio.h>

int main()
{
        int val = -12345;
        short sx = val;
        unsigned short usx = sx;
        int x = sx;
        unsigned int ux = sx;

        printf("%10d %#10hx\n", sx, sx);
        printf("%10u %#10hx\n", usx, usx);
        printf("%10d %#10x\n", x, x);
        printf("%10u %#10x\n", usx, usx);
}

运行结果:

1
2
3
4
  -12345   0xcfc7
   53191   0xcfc7
  -12345 0xffffcfc7
   53191   0xcfc7

操作符的属性

复杂表达式的求值顺序有3个因素决定的:操作符的优先级、操作符的结合型以及操作符是否有控制执行的顺序。

两个相邻的操作符哪个先执行取决于它们的优先级,如果两者的优先级相同,那么它们的执行顺序由它们的结合性决定。简单地说,结合性就是一串操作符是从左向右依次执行还是从右向左逐个执行。最后,有4个操作符(&& || ?: ,)它们可以对整个表达式的求值顺序加以控制。

C语言操作符优先级

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <stdio.h>
int main()
{
        int a = 20;
        if ( 1<= a <= 10)
        {
                printf("In Range\n");
        }
        else
        {
                printf("Out of Range\n");
        }
}

运行结果

1
In Range