C语言程序设计基础


选择语句

计算机学院    杨已彪

yangyibiao@nju.edu.cn

提纲

逻辑表达式

if 语句

switch 语句


选择语句

到目前为止, 我们已经讲完了返回语句和表达式语句.

C 的其他大部分语句:

  • 选择语句: ifswitch

  • 循环语句: while, dofor

  • 跳转语句: break, continuegoto(return也属于这一类)

  • 其他语句: 复合语句 和 空语句(Null)


逻辑表达式

C 的一些语句需要测试一个表达式的值, 看它是真还是假
例如, 一个if语句可能需要测试表达式i < j真值则表示i小于j
在许多编程语言中, 诸如i < j之类的表达式会有一个特殊的 布尔逻辑 类型
C 中, 如i < j的比较会产生一个整数: 0 ()或 1 ()

10 < 11
11 < 10
1 < 2.5
5.6 < 4

关系运算符

C 的关系运算符:

符号 含义
< 小于
‘>’ 大于
<= 小于或等于
>= 大于或等于

这些运算符在表达式中使用时会产生 0 ()或 1 ()

关系运算符可用于比较整数和浮点数, 允许不同类型的混合运算


关系运算符

关系运算符的优先级 低于 算术运算符

i + j < k – 1

表示

(i + j) < (k - 1)

关系运算符是左结合的


关系运算符

表达方式 i < j < k 是合法的, 但并不是测试 j 的大小是否在 ik 之间

因为 < 运算符是左结合的, 所以这个表达式等价于

(i < j) < k
  • i < j 产生的 10 随后将与k进行比较

  • 并非测试j是否位于ik之间

正确的表达式是 i < j && j < k


判等运算符

C 提供了两个判等运算符:

符号 含义
== 等于
!= 不等于

判等运算符也是左结合的, 产生 0 ()或 1 ()作为其结果.

判等运算符优先级 低于 关系运算符, 因此表达式:

i < j == j < k
等价于
(i < j) == (j < k)


逻辑运算符

利用逻辑运算符, 可以从简单逻辑表达式构建复杂逻辑表达式:

符号 含义
! 逻辑非
&& 逻辑与
|| 逻辑或

!运算符是一元的, 而&&||是二元的

逻辑运算符产生 01 作为其结果

逻辑运算符将任何非零操作数视为真值, 零值操作数视为假值


逻辑运算符

逻辑运算符的操作:

  • 如果 表达式 的值为 0, 则 !表达式 的值为1

  • 如果 表达式1表达式2 值都不为零, 则 表达式1 && 表达式2 的值为 1

  • 如果 表达式1表达式2 中任意一个不为零, 则 表达式1 || 表达式2 的值为 1

  • 其他情况下, 这些运算符产生的值都为 0


逻辑运算符

&&|| 会进行 “短路” 计算: 先计算左操作数, 然后是右操作数

若表达式的值可 单独 从左操作数推导出来, 则不计算右操作数

例子: (i != 0) && (j / i > 0)

  • 先计算(i != 0) 如果i不等于 0, 则计算(j / i > 0)的值

  • i0, 则整个表达式必定为假, 因此无需计算(j / i > 0)

  • 如果没有短路计算, 就会发生除零


逻辑运算符

因为 &&|| 两种运算符的短路特性, 逻辑表达式中的副作用可能不会产生

例子: i > 0 && ++j > 0

  • 如果i > 0为假, 则不会计算++j > 0, j也就不会自增

  • 可以将条件表达式修改为 ++j > 0 && i > 0, 更好的方法是j先自增


逻辑运算符

!运算符与一元加号和减号运算符具有相同的优先级

&&||的优先级低于关系运算符和等式运算符

例子: i < j && k == m

表示: (i < j) && (k == m)

  • !运算符是右结合的
  • &&||是左结合的

if语句

if 语句形式:

[if语句]if (表达式) 语句

判定表达式的值是真还是假, 不为零, 则执行语句, 如:

if (line_num == MAX_LINES)
  line_num = 0;

表达式两边的圆括号是必须的,它是if语句的组成部分


if语句

==(判等)和=(赋值)混淆是常见的 C 语言编程错误

if (i == 0)/* 检查i是否等于0 */

if (i = 0)/* 将0赋值给i, 然后检查结果是否为非零 */

if语句

通常, if语句中的表达式检查变量是否在某个值范围内

判定i是否满足 0i<n0 \leq i < n:

if (0 <= i && i < n) …

要判定相反的条件(i超出范围):

if (i < 0 || i >= n) …


复合语句

if语句模板中, 需注意statement(语句)是单数, 而不是复数:

if (表达式) 语句

要使if语句控制两条或更多条语句, 要使用 复合语句

[复合语句]{ 多条语句 }

在一组语句周围放置大括号会强制编译器将其视为单个语句


复合语句

例子:

{ line_num = 0; page_num++; }

复合语句通常放在多行中, 每行一条语句:

{
  line_num = 0;
  page_num++;
}

每个内部语句仍然以分号结尾, 但复合语句本身没有分号


复合语句

if语句中使用的复合语句示例:

if (line_num == MAX_LINES) {
  line_num = 0;
  page_num++;
}

复合语句在循环和其他C语法要求单个语句的地方也很常见


else子句

if语句可能有else子句:

if (表达式) 语句 else 语句

如果表达式的值为0, 则执行else后面的语句

例子:

if (i > j)
  max = i;
else
  max = j;

else子句

if语句包含else子句时, else应该放在哪里?

许多 C 程序员将它与语句开头的if对齐

当内部语句很短, ifelse可放在同一行:

if (i > j) max = i;
else max = j;

else子句

if语句嵌套在其他if语句中也很普遍:

if (i > j)
  if (i > k)
    max = i;
  else 
    max = k;
else
  if (j > k)
    max = j;
  else 
    max = k;

else与匹配的if对齐可以使嵌套层次更易辨别


else子句

为避免混淆, 最好添加大括号:

if (i > j) {
  if (i > k) 
    max = i;
  else 
    max = k;
} else {
  if (j > k) 
    max = j;
  else 
    max = k;
}

else子句

if语句中尽可能多地使用大括号:

if (i > j) {
  if (i > k) {
    max = i;
  } else {
    max = k;
  }
} else {
  if (j > k) {
    max = j;
  } else {
    max = k;
  }
}

else子句

使用大括号的优点(即使在不需要时):

  • 使程序更易于修改, 可以轻松地将更多语句添加到任何ifelse子句中

  • 避免使用ifelse子句时忘记使用大括号而导致的错误


级联式if语句

“级联” if语句通常是判定一系列条件的最佳方式, 一旦其中一个条件为真就停止

例子:

if (n < 0)
  printf("n is less than 0\n");
else
  if (n == 0)
    printf("n is equal to 0\n");
  else
    printf("n is greater than 0\n");

级联式if语句

尽管第二个if语句嵌套在第一个中, 但 C 程序员通常不会对它进行缩进

相反, 他们将每个else与原始if对齐:

if (n < 0)
  printf("n is less than 0\n");
else if (n == 0)
  printf("n is equal to 0\n");
else
  printf("n is greater than 0\n"); 

级联式if语句

这种布局避免了判定数量很多时过度缩进的问题:

if (表达式)
  语句
else if (表达式)
  语句
…
else if (表达式)
  语句
else
  语句

程序: 计算经纪人佣金

当通过经纪人出售或购买股票时, 经纪人的佣金通常取决于所交易股票的价值

假设经纪人收取下表中的金额:

交易规模 佣金率
低于$2,500 $30 + 1.7%
$2,500–$6,250 $56 + 0.66%
$6,250–$20,000 $76 + 0.34%
$20,000–$50,000 $100 + 0.22%
$50,000–$500,000 $155 + 0.11%
超过$500,000 $255 + 0.09%

最低收费为 $39


程序: 计算经纪人佣金

broker.c程序要求用户输入交易金额, 然后显示佣金金额:

Enter value of trade: 30000
Commission: $166.00

该程序的核心是一个级联的if语句, 用于判定交易属于哪个范围


程序: 计算经纪人佣金

broker.c

/* Calculates a broker's commission */
 
#include <stdio.h>
 
int main(void)
{
  float commission, value;
 
  printf("Enter value of trade: ");
  scanf("%f", &value);
 
  if (value < 2500.00f)
    commission = 30.00f + .017f * value;
  else if (value < 6250.00f)
    commission = 56.00f + .0066f * value;
  else if (value < 20000.00f)
    commission = 76.00f + .0034f * value;
  else if (value < 50000.00f)
    commission = 100.00f + .0022f * value;
  else if (value < 500000.00f)
    commission = 155.00f + .0011f * value;
  else
    commission = 255.00f + .0009f * value;
  
  if (commission < 39.00f)
    commission = 39.00f;
 
  printf("Commission: $%.2f\n", commission);
 
  return 0;
}

"悬空else"的问题

if 语句嵌套时, 千万当心出现"悬空else"问题

if (y != 0)
  if (x != 0)
    result = x / y;
else
  printf("Error: y is equal to 0\n");

缩进暗示else子句属于外部if语句

然而, 根据C 语言的规则, else子句实际上属于尚未与else配对的最近的if语句


"悬空else"的问题

正确缩进的版本如下:

if (y != 0)
  if (x != 0)
    result = x / y;
  else
    printf("Error: y is equal to 0\n");

"悬空else"的问题

为了使else子句成为外部if语句的一部分, 可以将内部if语句用大括号括起来:

if (y != 0) {
  if (x != 0)
    result = x / y;
} else
  printf("Error: y is equal to 0\n");

if语句中使用大括号可以避免这一问题


条件表达式

C 的条件运算符允许表达式根据条件的值产生两个值之一

条件运算符 由两个符号(?:)组成, 它们必须一起使用:

[条件表达式]   表达式1 ? 表达式2 : 表达式3

  • 操作数可以是任何类型

  • 结果表达式被称为条件表达式


条件表达式

条件运算符需要三个操作数, 因此通常被称为三元运算符

条件表达式表达式1 ? 表达式2 : 表达式3读作如果表达式1成立, 那么表达式2, 否则表达式3.

表达式分阶段计算:

  • 首先计算表达式1, 如果它的值不为零, 则计算表达式2, 它的值是整个条件表达式的值

  • 如果表达式1的值为零, 那么表达式3的值就是整个条件表达式的值


条件表达式

例子:

int i, j, k;

i = 1;
j = 2;
k = i > j ? i : j;          /* k is now 2 */
k = (i >= 0 ? i : 0) + j;   /* k is now 3 */

括号是必需的, 因为条件运算符的优先级低于到目前为止讨论的其他运算符的优先级, 赋值运算符除外


条件表达式

条件表达式往往会使程序更短但更难理解, 因此最好谨慎使用它们

条件表达式常用于返回语句:

return i > j ? i : j;

条件表达式

printf函数调用有时可以从条件表达式中受益, 我们可以将

if (i > j)
  printf("%d\n", i);
else
  printf("%d\n", j);

简单地写成:

printf("%d\n", i > j ? i : j);

条件表达式在某些类型的宏定义中也很常见


C89中的布尔值

多年来, C 语言缺乏合适的布尔类型, C89 标准中也没有定义

一种解决的方法是声明一个int变量, 然后将其赋值为 0 或 1:

int flag;
 
flag = 0;
…
flag = 1;

虽然这个方案有效, 但它对程序的可读性贡献不大


C89中的布尔值

为了使程序更易于理解, C89的程序员用TRUE和FALSE等名称定义宏:

#define TRUE 1
#define FALSE 0

对标志flag的赋值现在看起来更加自然:

flag = FALSE;
…
flag = TRUE;

C89中的布尔值

要判定标志flag是否为真, 我们可以写

if (flag == TRUE)

或者直接

if (flag)

后一种形式更简洁, 如果flag的值不是 0 或 1, 它也可以正常工作


C89中的布尔值

要测试标志flag是否为假, 我们可以写

if (flag == FALSE)

或者

if (!flag)

C89中的布尔值

将这个想法更进一步, 我们甚至可以定义一个可以用作类型的宏:

#define BOOL int

声明布尔变量时, BOOL可以代替int:

BOOL flag;

这样就很清楚看出flag不是一个普通的整数变量, 而是代表一个布尔变量


C89中的布尔值

C99 提供了Bool类型

可以通过这样写来声明布尔变量

_Bool flag;

_Bool是整数类型, 所以_Bool变量实际上只是变相的整数变量

然而, 与普通整数变量不同的是, _Bool变量只能分配 01

尝试将非零值存储到_Bool变量中会导致变量被赋值为 1:

flag = 5; /* flag is assigned 1 */


C99中的布尔值

_Bool变量执行算术运算是合法的, 但不建议

打印_Bool变量也是合法的(显示 0 或 1)

当然, 可以在if语句中测试_Bool变量:

if (flag)   /* tests whether flag is 1 */

C99中的布尔值

C99<stdbool.h>头文件可以更轻松地处理布尔值

它定义了一个宏bool, 它代表_Bool

如果包含<stdbool.h>, 我们可以写

bool flag; /* same as _Bool flag; */

<stdbool.h>还提供了名为truefalse的宏, 它们分别代表 10, 因此可以这样写:

flag = false;
…
flag = true;

switch语句

级联if语句用于将表达式与一系列值进行比较:

if (grade == 4)
  printf("Excellent");
else if (grade == 3)
  printf("Good");
else if (grade == 2)
  printf("Average");
else if (grade == 1)
  printf("Poor");
else if (grade == 0)
  printf("Failing");
else
  printf("Illegal grade"); 

switch语句

switch语句是另一种选择:

switch (grade) {
  case 4:  printf("Excellent");
           break;
  case 3:  printf("Good");
           break;
  case 2:  printf("Average");
           break;
  case 1:  printf("Poor");
           break;
  case 0:  printf("Failing");
           break;
  default: printf("Illegal grade");
           break;
}

switch语句

switch语句可能比级联if语句更容易阅读

switch语句通常比if语句执行更快

switch语句最常见的形式:

switch (表达式) {
  case 常量表达式: 语句
  …
  case 常量表达式: 语句
  default: 语句
}

switch语句

单词switch后面必须跟一个括号中的整数表达式——控制表达式

字符在 C 中被视为整数, 因此可以在switch语句中进行判定

但是, 浮点数和字符串不符合条件


switch语句

每个case都以下面的标签形式开头

case 常量表达式:

常量表达式与普通表达式很相似, 只是它不能包含变量或函数调用

  • 5是一个常数表达式, 5 + 10也是一个常数表达式, 但n + 10不是常量表达式(除非n是表示常量的宏)

case 标签中的常量表达式必须计算为整数(可接受字符)


switch语句

在每个案例标签之后是任意数量的语句

语句周围不需要大括号

每组中的最后一条语句通常是break


switch语句

不允许有重复的case标签

案例的顺序无关紧要, 默认case不需要排在最后

一组语句之前可以有几个 case 标签:

switch (grade) {
  case 4:
  case 3:
  case 2:
  case 1:  printf("Passing");
           break;
  case 0:  printf("Failing");
           break;
  default: printf("Illegal grade");
           break;
}

switch语句

为了节省空间, 可以将多个case标签放在同一行:

switch (grade) {
  case 4: case 3: case 2: case 1:
    printf("Passing");
    break;
  case 0: 
    printf("Failing");
    break;
  default: 
    printf("Illegal grade");
    break;
}

如果缺少default, 且控制表达式的值与任何case都不匹配, 则控制传递到switch之后的下一条语句


break语句的作用

执行break语句会导致程序从switch语句中 中断

switch 之后的下一条语句继续执行

switch语句实际上是 计算跳转 的一种形式

  • 计算控制表达式时, 控制跳转到与switch表达式的值匹配的 case 标签

  • case 标签是一个标记, 表示switch中的一个位置


break语句的作用

没有 break(或其他跳转语句), 控制将流入下一个 case

例子:

switch (grade) {
  case 4:  printf("Excellent");
  case 3:  printf("Good");
  case 2:  printf("Average");
  case 1:  printf("Poor");
  case 0:  printf("Failing");
  default: printf("Illegal grade");
}

如果grade的值为 3, 则打印的消息为
GoodAveragePoorFailingIllegal grade


break语句的作用

省略break有时是故意的, 但通常是因为疏忽

明确指出故意省略break语句是个好的习惯:

switch (grade) {
  case 4: case 3: case 2: case 1:
    num_passing++;
    /* FALL THROUGH */
  case 0: total_grades++;
    break;
}

尽管最后一个 case 永远不需要break语句, 但包含一个break可以避免在将来添加 case 时出错


程序: 以法律形式打印日期

合同和其他法律文件通常按以下方式注明日期:

Dated this __________ day of __________ , 20__ .

date.c程序将以这种形式显示日期:

Enter date (mm/dd/yy): 7/19/14
Dated this 19th day of July, 2014.

该程序使用switch语句将"th"(或"st""nd""rd")添加到日期, 并将月份打印为单词而不是数字


程序: 以法律形式打印日期

date.c

/* Prints a date in legal form */

#include <stdio.h>
 
int main(void)
{
  int month, day, year;
 
  printf("Enter date (mm/dd/yy): ");
  scanf("%d /%d /%d", &month, &day, &year);
 
  printf("Dated this %d", day);
  switch (day) {
    case 1: case 21: case 31:
      printf("st"); break;
    case 2: case 22:
      printf("nd"); break;
    case 3: case 23:
      printf("rd"); break;
    default: printf("th"); break;
  }
  printf(" day of ");
  
  switch (month) {
    case 1:  printf("January");   break;
    case 2:  printf("February");  break;
    case 3:  printf("March");     break;
    case 4:  printf("April");     break;
    case 5:  printf("May");       break;
    case 6:  printf("June");      break;
    case 7:  printf("July");      break;
    case 8:  printf("August");    break;
    case 9:  printf("September"); break;
    case 10: printf("October");   break;
    case 11: printf("November");  break;
    case 12: printf("December");  break;
  }
 
  printf(", 20%.2d.\n", year);
  return 0;
}