5.1 循环

5.2 基本运算符

5.2.1 赋值运算符 =

  • bmw = 2002意思是把值 2002 赋给变量 bmw
  • 左值、可修改的左值、数据对象、右值 P106
  • C语言可以多重赋值,方向为从右向左,其他语言大概率不支持
  • 例如cheeta = tarzan = jane = 68

5.2.2 ~ 5.2.6 运算符 + - * /

  • 一元运算符与二元运算符,+ - 同时可以做一元运算符,表示正负rocky=-12;smoky=-rocky。+ 做一元运算符只能用于 dozen = +12;
  • C语言原生没有幂函数,可以用乘法表示

  • 整数截断:对于整数除法“4/3”,答案不会包含小数,会直接把小数部分给舍弃,称为截断;浮点数之间的除法会保留小数;对于混合使用浮点数和整数的运算,即混合类型,会将整数转化为浮点数在进行计算,一般情况下需要尽量避免

  • 负数的整数之间的除法,例如当答案为“-3.8”,截断为 “-3”,称之为趋零截断

5.2.7 运算优先级

乘除法优先级大于加减,当全部优先级相同时从左往右进行,有括号先括号 对于上面未规定的运算顺序,例如y = 6 * 12 + 5 * 20;,前后两个乘法运算的优先级相同,但中间多了一个优先级不同的运算,不符合上面的第二条。标准中对该运算的顺序没有规定,由不同的硬件来决定

5.3 其他运算符

C语言运算符有40多个,比较常用的还有四个

5.3.1 sizeof 运算符和 size_t 类型

size_t 类型本质上是 unsigned int 或 unsigned long 的别名,在C语言头文件中使用 typedef 定义,具体哪种类型,由系统来定,转换说明使用%zd

sizeof 的运算对象为具体的数据对象或者类型,如果运算对象是类型,则必须有括号,具体数据对象可以没有括号,但是最好加上括号。

sizeof 运算结果是对象在内存中占用的字节数,对字符串,结果是占用的所有内存的大小而不是字符串的实际长度

5.3.2 求模运算符 %

求模运算符只能用于整数,即求余数。

求模运算更多用于判断语句中,每值某个数的整数倍则如何的判断

  • 负数求模:趋零截断的原则下用 a % b = a - (a/b)*b计算

  • 几个例子:11 / 5 = 2,11 % 5 = 1;11 / -5 = -2,11 % -5 = 1;-11 / -5 = 2,-11 % -5 = -1;-11 / 5 = -2,-11 % 5 = -1

5.3.3 递增运算符 ++

递增运算符会将其运算对象递增一,有两种模式:前缀模式、后缀模式。顾名思义,放在变量的前面或者后面 递增运算的一个极大的优势是可以将代码写的更加优美,例如

1
2
3
4
5
shoe = 2.0;
while (++shoe < 18.5)
{
    foot = SCALE * shoe + ADJUST;
}

即把变量递增过程放入循环条件中

这种方式将判断循环的两个条件:循环条件以及参数改变 放在了一个地方,避免忘记

但一定程度上降低了程序可读性

前缀模式与后缀模式的区别

示例:

1
2
3
int a=1,b=1;
a11=a++;
_11b=++b;

结果是a11 = 1 ; _11b = 2 相同点是对应的变量值,a 和 b 都递增了,区别是在表达式中使用的 a++ 没有改变,仍然是 1, 而 ++b 则表示已经递增过的值。 由此,在判断表达式中如果使用递增符号,则需要注意,例如a++ < 9++a < 9循环次数相差 1

由于不同的递增方式会对代码产生不同的影响,因此最明智的方式是不在表达式中使用

例如,使用++i;b = i;而不是b = ++i;

5.3.4 递减运算符:–

与递增运算类似

5.3.5 优先级

递增递减运算符优先级仅次于括号

(x*y)++ 的使用是无效的,++仅仅会作用于可修改的左值

5.3.6 不要自作聪明

一次性使用太多递增运算符,容易导致自己糊涂以及发生错误 C语言中并没有对不同项的运算规定顺序 例如:

1
printf("%d %d\n",num,num*num++);

可以先计算前面的num 再计算num++,得到(5,25),也可以先计算num++,再计算前面的num (6,30)。也有可能先计算中间num,再计算num++,最后计算最前面的num,有(6,25).

1
ans = num/2 + 5*(1 + num++)
1
y=n++ + n++

也都有可能出现问题,应当予以避免 于是,规定以下情况不使用递加或递减,可以使程序更好运行

  • 变量出现在一个函数的多个参数中
  • 变量多次出现在一个表达式中

5.4 表达式和语句

5.4.1 表达式

表达式由运算符和运算对象组成

一些表达式由子表达式组成,a*(b+c/d)/20中,c/d可以认为是子表达式

每个表达式都有一个值,包括赋值和比较。q = 2*5,作为一个表达式时,值为10,等于赋值的值(参考连续赋值)。不等式a>2,当为真时,值为1,否则为0。

不建议使用但是不错的表达式:6 + (c = 3 + 8),值为17,后面视为子表达式

5.4.2 语句

一条语句表示一条完整的计算机命令,大部分语句以分号结尾.legs=4不是语句,可能是语句的一部分,而legs=4;表示语句。

1
2
3
;
8;
3 + 4;

上面的几条也都是语句,但是在程序中什么都不做,没有意义

声明不是表达式语句

  • 表达式语句由:赋值表达式语句和函数表达式语句

  • 迭代语句(while语句没有括号时)

  • 复合语句

副作用(side effect)

C语言中,每个语句的主要目的是求值,比如a=100; printf("sdfasg"),每个语句的目的(C语言角度)都是求值,比如上面的第一条语句返回的值是 100,第二条语句返回打印的字符数。

而其产生的其他效果,比如给 a 赋予了一个值,以及打印了一些字符,便被称为副作用。

序列点(sequence point)

序列点是程序执行点,所有的副作用都应该在该点之前完成。通常来说一个分号是一个序列点。C标准中还规定了一个完整表达式的结束也是一个序列点。

完整表达式:一个表达式语句不是另一个表达式的子表达式时,包含循环条件和表达式语句

1
2
while(a++<10)
    printf("%d\n",a);

该语句中,在a++<10之后虽然没有分号,但是已经构成了一个完整表达式,所以后面打印的值应当是递增之后的。

5.4.3 复合语句(compund statement)

复合语句是用花括号括起来的一条或多条语句,也称为块(block)

对于一些函数,循环来说,例如while,复合语句的效果类似于一条语句。

5.5 类型转换

  1. 表达式中,short 和 char (无论有无符号)会被转换成 int 。如果 short 和 int 一样大(某些系统中是这样的),unsigned short 会被转换成 unsigned int。该转换称为升级(promotion)

  2. 涉及到两种不同的类型,会被转换成两者的高级别形式。比如 int 和 long int 相加,都转换成 long int 再相加

  3. 类型的级别从高到底为:long double、double、float、unsigned long long、long long、unsigned long、long、unsigned int、int。

    上面没有short和char,是因为已经被升级了

    当long 和 int 相同,unsigned int 比 long 级别高

  4. 赋值表达式中,右值的计算结果会被转换成左值的类型,该过程可能出现**降级(demotion)**或升级

  5. 函数作为参数传递时,char 、short 被转换成 int,float 被转换成 double。

通常类型升级不会出现太多问题,但是降级会,因为较低的类型放不下整个数字

4中转换成目标类型的过程中:

  1. 目标是无符号整型,待赋值的值是整数。忽略额外位数,比如8位的unsigned char 有8位,整数对256求模
  2. 目标是有符号整型,待赋为整数,结果需进一步讨论
  3. 目标是整型,待赋为浮点数,该行为是未定义的(这好像和下面相悖

把浮点数转换成整数类型,会发生趋零截断

5.5.1 强制类型转换运算符

上面讨论的类型转换都是自动完成的,然而有时候需要精确的类型转换,此时需要**强制类型转换(cast)**例如

1
2
mice = 1.6 + 1.7
mice = (int)1.6 +(int)1.7

在对应的值前加上括号和类型,但是由于趋零截断,上面两个计算的结果是不同的。

一般不应该混合使用类型,很多语言甚至不允许

5.6 带参数的函数

构造带参数的函数时,在其函数头的括号中加上所包含的参数的声明,例如void pound(int n),不带参数的函数该部分是void。

通过声明参数,会创建一个形参(术语:parameter),形式参数(formal argument、formal parameter)。

而当使用该函数,pound(m)中,m是实际使用的参数,称为实参(argument),实际参数(actual argument、actural parameter)。

示例:

 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 pound(int n);          //函数声明
int main(void)
{
    int times = 5;
    char ch = '!';
    float f = 6.0f;

    pound(times);           //int类型参数   
    pound(ch);              // 等价于pound((int)ch),类型转换
    pound(f);               //同上

    return 0;
}
void pound(int n)
{
    while (n-->0)
    {
        printf("#");
    }
    printf("\n");
}

在函数头中声明的变量名是函数私有的,仅仅在声明的函数中使用,对函数之外函数比如主函数,没有影响

上面使用 pound(ch) 时,由于pound函数的参数类型是 int ,与 char 不匹配,故根据前面的转换方法进行