1
一、语句概述二,C++中特有的输入输出流三、逻辑判断
2
一、语句概述
C/C++ 程序设计提供了三种基本的流程控制结构,第一为顺序结构即语句从前往后从上往下顺序地执行,第二种为选择结构,再一种为循环结构。
选择结构根据给定的条件进行判断,由判断的结果确定执行两支或多路分支中的一个程序段。再复杂的程序也是通过程序各种语句的组合实现的,
其组合的结果可视为一个模块,独立的模块构成函数。
3
名称由定义、声明或说明语句引入。名称遵循标识符的命名规定。定义对象索引类类型名称,函数调用索引函数名称,表达式中的变量索引变量名称。在名称的索引点,编译器向上或向前搜寻名称是否已经定义或说明。名称必须首先有效说明,然后才能正确索引。
在函数中引入的名称为局部名称,在全局范围引入的名称为全局名称。
局部范围是函数体界定的范围,全局范围是类声明外和函数定义外的范围。
局部名称只在当前函数体中索引,全局名称可在其后的函数体中或源文件中索引。函数名具有全局作用范围。
4
术语定义、声明或说明都引入名称,它们之间区别是细微的。定义指变量或函数代码的唯一内存分配。声明用于指对类或结构或联合的描述,一般不占有内存空间。名称的唯一性通过定义或声明来限定。变量名引用名在其作用范围是唯一的。数据类型声明语句引入的名称在其作用范围也是唯一的。函数名在重载的概念上是唯一的。
说明在一个程序段引入一个名称,通知编译器该名称在别处已经定义或声明过。同一说明可以散见于多处,常见的说明是函数原型说明、外部连接说明与类名的前置说明。
5
C/C++ 的程序是由语句构成的,语句一般以分号作为语句分隔和语句的结尾标志,复合语句块则以一对花括号定界。分号本身又构成空语句,语句以分号,;”作为结束。
语句用于为变量分配内存、顺序求值、分支选择和循环迭代控制等的操作运算。
表达式语句、函数调用语句、空语句、复合语句和流程控制语句等属于运行时动态执行的指令,仅放置于函数体中,称为执行语句。
6
非执行语句是为函数体中的动态代码运行做准备的辅助性说明语句。 C++语句的布置次序相当灵活,执行语句和非执行语句的分界线不明确,甚至可以相互穿插。
但从语句块的局部看执行语句前总是安排相应的非执行语句或先辅助性说明语句后执行语句。
变量定义语句介于声明语句和执行语句之间,变量定义语句为变量分配内存。引用声明语句建立独立变量的等价别名,变量的内存和引用的名在编译或连接阶段备案,变量的值和引用关联的过程可在编译阶段静态完成也可动态进行。
7
C/C++语言中的语句主要有下面几种形式,源程序的安排上大抵上根据下面次序分布:
1,辅助性说明语句 (引用,class和模板为 C++所独有 )
函数原型说明语句。 例如,long f(long); int g();
外部连接说明语句。 例如,extern int x; extern
int& r;
typedef类型声明语句。 例如,typedef int BOOL;
前置说明语句 (C++概念 )。 例如,struct SStruct;
class CClass;
类模板声明语句(仅在全局范围,C++独有)
数据类型声明语句
8
数据类型声明语句是由 struct,class和 union以及
enum引入的数据描述,其后引入的类型名如 CClass,
SStruct,UUnion,EEnum具有全局作用域,如果相应的声明放置在全局范围。以分号结束的数据类型声明语句 (类类型声明语句一般放在全局范围 )如下:
class CClass {long f();float m_f;};
//类类型声明语句,CClass是类类型名
struct SStruct{int n; int m;};
//结构类型声明语句,SStruct是结构名
union UUnion { double d; char c;};
//联合类型声明语句,UUnion是联合名
enum EEnum { e1,e2,e3};
//枚举类型声明语句,EEnum是枚举名
9
2、变量、结构变量和对象定义语句。 例如:
int i=0,j,sum; SStruct a;CClass obj;
3、执行语句(仅放置在函数体中)
表达式语句。 例如:
Lvalue ++;-- Lvalue;Lvalue =e;Lvalue +=e;
函数调用语句。 例如:
printf("输入两个整数 \t"); scanf("%d,%d",&i,&j);
在表达式之后加一个分号,;” 就构成表达式语句,函数调用后加一个分号构成函数调用语句。非 void型的函数调用归于表达式的范畴。表达式在分号前完成所有副作用,表达式语句应改变内存状态才有意义。例如,3*x; (int)x+x;a*b;
(3*4,x<<5);是无意义的表达式语句。
10
空语句只有一个分号的语句,称为空语句。空语句是无表达式的语句,它在语法上要求一条语句但无需求值时发生作用。
空语句用于循环语句中占据一个语句的位置。 例如,
for (;*dest=*source; dest++,source++ ); //空语句
流程控制语句
复合语句 (块 )
复合语句由括在花括号 { }中的 0个或多个语句构成。一个复合语句可用于任何需要语句的地方,复合语句通常称为程序块。复合语句的语法格式为:
11
{ 变量 j定义语句; //例如,long j;
//其后程序段识别同层定义的局部变量 j
语句序列;
.....,
//前面部分的同层程序段不识别其后定义的局部变量 k
变量 k定义语句; //例如,double k;
语句序列;
}
语句序列是指若干有序排列的语句。函数体就是由一对花括号 { }包含的语句序列构成。在程序中单一语句可以出现的位置,复合语句也能出现。复合语句常用于选择语句或循环语句中。在复合语句内引入的变量或名称其可见性局限于花括号 { }之内的后续部分。
12
二,C++中特有的输入输出流
C++ 支持面向对象的编程,因此为输入输出提供了一个面向对象的方法,这就是标准输出流对象 cout,以及标准输入流对象 cin。
标准输出流对象 cout实现一般的屏幕输出,其使用格式为:
cout<<表达式 1<<表达式 2;
“<<”是左移运算符。这里又称为插入符。可多次出现,
其间跟随表达式。标准输入流对象 cin则用于键盘输入,格式为:
cin>>左值表达式 1>>左值表达式 2;
13
“>>”是右移运算符,此处亦称为提取符。提取符之后是左值表达式,最简单的左值表达式是变量。 例如:
double x,y;
cin>>x>>y;
此时要求从键盘上输入两个 double变量值,用空格分隔。如键入,
4.1 5.2
则变量 x=4.1,y=5.2
语句,cout<<e;”可理解为:将表达式 e的内容插入到输出流对象 cout中,数据信息是往左移动的;语句 "cin>>x;“
可以理解为:从输入流对象 cin中提取值,送往目标变量 x,
数据信息是往右移动的。
14
[例 ] 返回 double&引用的函数 f1,返回 int&引用的函数 f2和输入输出流
#include<stdio.h>
#include<iostream.h>
double d=1; int n=2;
double& f1() { printf ("f1();\t"); return d;}
int& f2() { printf ("f2();\t"); return n; }
void main (void)
{ cout<<f1()<<"___"<<f2()<<endl;
cin>>f1()>>f2();
cout<<d<<"___"<<n<<endl;
scanf ("%lf %d",&f1(),&f2());
printf ("%f%s%d\n",d,"__",n);
}
15
返回引用的函数 f1和 f2与相关的全局变量 d和 n在函数调用的结果上是等效的,但函数在获得结果之前进行了另外的操作运算。流对象输入方式 cin>>f1()>>f2()是右移运算符 >>
函数重载。右移运算符的结合律从左到右,因此流对象输入方式可以理解为,(cin>>f1()) >>f2()
但语言没有规定基本表达式的初始化求值次序,编译器可以先求 f1的值,再求 f2的值。也可以先求 f2,再求 f1。 初值出来后再根据从左到右的次序,进行输入流信息的处理。
从上面的结果可以看出,微软采用的策略是参照 scanf
流文件的次序进行函数调用,即从右到左完成函数调用的计算。同样的分析适应 [cout<<f1()<<"___"<<f2()<<endl;]。
16
输入输出处理过程不是 C/C++语言本身的一部分。格式化输入输出函数 printf,scanf和输入输出流对象 cin,cout
在文本编程模式下可完成同样的输入输出功能,两相比较各有千秋,各有不足。但流对象 cin,cout隐含着几种变动。
1,首先这里面牵涉的一个最重要的变化是 main函数作为 C环境下应用程序的入口函数由于全局对象的引入而退住第二线。
C++编译器允许程序在 main函数之前进行必要的初始化工作。在,#include<iostream.h>”预处理语言中所含的
<iostream.h>头文件中就定义了全局对象即标准输出流对象
cout,标准输入流对象 cin。
17
2,另外一个重要的变动是 C++ 语言中引进了其它高级语言中形参变量的引用调用机制,该机制使得被调函数的形参可以直接改变主控函数相应实参变量,而不必借用取地址运算符 &和访问指针运算。下面比较一番两者在输入输出上的差异:
printf ("%f%s%d\n",d,"__",n);
scanf("%lf %d",&f1(),&f2());
int j,k;
scanf ("%d,%d",&j,&k);
cout<<d<<"___"<<n<<endl;
cin>>f1()>>f2();
int j,k;
cin>>j>>k;
18
3,输出流对象 cout取代了标准输出函数 printf,左移运算符,<<”取代了函数调用运算符,()”,输入流对象 cin代替标准输入函数 scanf,右移运算符,>>”代替函数调用运算符
"()"。
左移运算符,<<”等代替函数调用运算符,()”的现象称为运算符重载,运算符重载是函数调用的代理形式,是 C++简化对象运算的语法手段。
语句,cin>>j>>k;,将两个右移运算符串联起来,从左到右进行了两次函数调用。在函数的返回类型上 C++进行了扩张:函数的返回结果是一个可以连续运行的左值表达式。
19
4,printf,scanf函数需要格式控制如 %f%s%d匹配不同的数据以完成数据转换,而 cin,cout通过函数重载机制自动鉴别不同数据的类型,简化了数据的格式转换过程。在简单的格式转换中采用 cin,cout是方便的,但复杂的格式控制
printf,scanf提供紧凑简洁的代码。
在 scanf函数中变量 j,k前的取地址运算符 &到了输入流
cin中消失不见了,这样输入数据流通存在两种几可互换的语法形式:指针的显式传值和变量的隐含传址。
控制符或操作算子 endl与’ \n?一样起换行的作用。
Endl
表示 end line。
20
三、逻辑判断分支选择结构根据逻辑判断的真伪进行,关系表达式和逻辑表达式构成逻辑判断分支的开关条件,假结果或零结果对其后的分支构成屏蔽的短路效果。
关系和逻辑运算符两个操作数的类型可以不同,操作数可以是字符、整数、浮点或相同属性的指针类型,它们是可以转换为 bool类型的数据。
关系和逻辑运算在整数和浮点型操作数上执行常用的算术转换。
21
1,关系运算符关系运算符有六个分为两组,运算符 <,>,>=,<=比运算
==,!=具有更高的优先级。
关系表达式是关系运算符 <,>,>=,<=,==,!= 连接的表达式,关系表达式的语法格式为:
操作数 1 运算符 操作数 2
< 小于关系 e1<e2
> 大于关系 e1>e2
<= 小于等于 e1<=e2
>= 大于等于 e1>=e2
,
== 等于关系 e1==e2
!= 不等于关系 e1!=e2?,
22
双目关系运算符将其第一个操作数与第二个操作数进行比较以测试指定关系的有效性或真假性。如果测试的关系成立或为真,则该关系表达式的结果为 1,如果测试的关系不成立或为假,则该关系表达式的结果为 0。 例如 表达式:
3<5,6>5,1<=1.1,2!=1
的结果为,1。
关系运算符具有左结合性。这样 a>b>c 等价于 (a>b)>c
23
[例 ] 关系以及逻辑运算的结果和类型
# include<stdio.h>
void main()
{ printf ("%d,%d,%d\n",4>3,1.2==true,sizeof(1==true));
printf ("%d,%d,%d\n",4>=4.01,1.2!=1.2,sizeof(1.2!=1.2));
printf ("%d,%d,%d\n",4.8>3>2,4>3.4>2.8-4,5+4<3+7);
double x,y;
printf ("%3.1f,%d\n",x=(4.8>3)+1.6,(y=4.8)>3+1.6);
x=3+1.6;
printf ("%d,%d\n",sizeof(y>x),sizeof(4.8>4.8));
printf("%d,%d\n",sizeof(y||x),sizeof(4.8&&4.8));
}
说明:关系表达式和逻辑表达式的结果 (0或 1)在 C语言中为 int型的右值,在 C++视为 bool型的右值。
24
2.逻辑运算符逻辑运算用于组合操作关系表达式形成多个逻辑条件。
逻辑表达式的结果为 0 或 1。其结果的类型为 bool型 (在 C语言中为 int型 )的右值表达式。逻辑运算符有三个:
a,逻辑非运算符!,
b,逻辑与运算符 &&,
c,逻辑或运算符 || 。
优先级上逻辑非运算符高于算术运算符,算术运算符高于关系运算符,关系运算符高于逻辑与运算符,逻辑与运算符高于逻辑或运算符。逻辑或运算符高于赋值运算符。
25
!a>b*c 等价于 (!a)> (b*c)
a+b>b*c 等价于 (a+b)>(b*c)
a>b&&b>c 等价于 (a>b)&&(b>c)
a||b&&c 等价于 a||(b&&c)
a=b>c 等价于 a= (b>c)
a==b+!c 等价于 a==(b+(!c))
1) 逻辑非运算符的语法格式为,!表达式 !e
表达式为算术或指针类型。如果表达式为非 0值,则逻辑非运算的结果为 0;仅当操作数为 0时结果为 1。对一个表达式
e,单目表达式 !e等价于表达式 e==0。
printf ("%d,%d,%d\n",!4,4==0,sizeof(!4));
printf ("%d,%d,%d\n",!0,!0.12,!(3>4));
优先级逻辑非运算符 !e 高算术运算符关系运算符逻辑运算符 && ||
赋值运算符 低
26
2)逻辑与运算符逻辑与运算符在两个操作数均为非 0时返回值 1;否则返回 0 。逻辑与具有从左到右的结合性即逻辑与表达式严格的从左到右的次序执行。逻辑与表达式的语法格式为:
操作数 1&&操作数 2 e1&&e2
逻辑与表达式的第一个操作数 e1首先求值,且完成所有的副作用。第二个操作数 e2仅在第一个操作数求值为真或非
0 时才继续进行计算求值。
逻辑与运算是短路运算,只要求得表达式的结果为 0,则判断结束。逻辑与短路运算对于表达式 e1==0串起来的式子
e1&&e2,e2表达式的求值不被执行。
27
[例 ] 逻辑与短路特点显示
#include<stdio.h>
int f(int i) { printf ("f (%d); ",i); return i; }
void main(void)
{ int k=f (0)&&f(1); printf ("%d\n",k);
k=f (1)&&f (0); printf ("%d\n",k);
k=f (2)&&f (1)&&f(0); printf ("%d\n",k);
int n=0; k=0;
printf ("%d,%d,%d\n",k,n,k++&&n++);
n=0,k=0;
printf ("%d,%d,%d\n",k,n,n++&&k++);
}
28
3) 逻辑或运算符逻辑或运算符在两个操作数均为 0时返回值 0;否则返回 1.
逻辑或具有从左到右的结合性即逻辑或表达式严格的从左到右的次序执行。逻辑或表达式的语法格式为:
操作数 1||操作数 2 e1||e2
首先计算逻辑或表达式的第一个操作数,且完成所有的副作用。第二个操作数仅在第一个操作数求值为 0 时才继续进行计算求值。
这种计算对于表达式嵌套的情形可消除后面操作数在逻辑或表达式为真时不必要的求值。
29
逻辑或运算是短路运算,只要求得表达式的结果为 1 或非 0,则判断结束。逻辑或短路运算对于表达式 e1!=0串起来的式子 e1||e2,e2表达式的求值不被执行。在求值的副作用上表达式 e1||e2与表达式 e2||e1并不完全等价。 例如:
k++||n++ 不同于 n++||k++
逻辑与的优先级高于逻辑或。逻辑与又称为逻辑乘,逻辑或又称为逻辑加。
30
[例 ] 逻辑或短路特点显示
#include<stdio.h>
int f(int i) { printf ("f(%d);",i);return i; }
void main (void)
{ int k=f(1)||f(0); printf ("%d\n",k);
k=f(0)||f(1); printf ("%d\n",k);
k=f(0)||f(0)||f(1);printf ("%d\n",k);
int n=1; k=1;
printf ("%d,%d,%d\n",k,n,k++||n++);
n=1,k=1;
printf ("%d,%d,%d\n",k,n,n++||k++);
}
31
[例 ] 逻辑与组合逻辑或时的短路特点
#include<stdio.h>
double f (double x){printf ("f(%0.1f);\t",x);return x;}
void main (void)
{ int k;
k=f(0)||f(1)&&f(2); printf ("%d\n",k);
k=f(2)||f(1)&&f(0); printf ("%d\n",k);
k=f(2)&&f(1)||f(0); printf ("%d\n",k);
k=f(0)&&f(1)||f(2); printf ("%d\n",k);
}
32
上面表达式中逻辑与的优先级高于逻辑或的优先级字面上理解为:
f(0)||f(1)&&f(2)? f(0)||(f(1)&&f(2)),
f(2)||f(1)&&f(0)? f(2)||(f(1)&&f(0))
f(2)&&f(1)||f(0)? f(2)&&(f(1))||f(0),
f(0)&&f(1)||f(2)? f(0)&&(f(1)||f(2))
C/C++语言对于逻辑或和逻辑与运算规定计算次序严格的从左到右,另加上短路的运算机制。这样输出上面的结果。
e1&&e2和 e2&&e1运算以及 e1||e2和 e2||e1运算的效果具有不同的性质,在组合逻辑或和逻辑与的判断表达式时,
应注意这一短路现象,忽略这一点容易导致程序的漏洞。
33
四、选择语句在 C/C++中选择语句常用于构成条件分支,选择语句主要 有 两种形式:一种是 if语句,另一种是 switch语句,形成顺序开关分支多路选择。
1.单路分支 if 语句在 C/C++语言中 if关键字提供了两种基本的控制结构形式。
值得特别强调地说只有两种原始的独立的 if控制结构形式,其余的形式都是这两种形式的嵌套与灵活的组合。
if 语句圆括号中的表达式可为算术或指针类型,通常是关系或逻辑表达式,称为条件表达式。
34
后续语句复合语句
if
非零零表达式if (表达式 )
{ 语句序列 ;}
后续语句;
(1) 语法格式 (2) 程序流程图图 if (表达式)语句语法格式与流程
35
上面的 if 结构称为单路分支选择控制结构。该 if语句的执行过程为,if语句首先对条件表达式进行求值计算且完成所有的副作用。若结果为非零值,则执行 if 下的单条语句或花括号包含的复合语句,否则不执行单条语句或复合语句。
当语句序列只有一条语句时花括号通常省略不写。
条件表达式将非 0 值作为真,将 0 值作为假。
if 条件表达式的影响范围为最外层花括号包括的语句序列或紧随其后的单条语句。而后续语句是不为 if 选择语句所控制的随后的语句,通常是流程的必经点。
可视,if(表达式) {语句序列 ;}”或,if(表达式)语句 ;”为一条独立的语句。
36
[例 ] 求三个数的极大值与极小值,
#include<stdio.h>
void main (void)
{ int min,max,x,y,z;
scanf ("%d,%d,%d",&x,&y,&z);
min=max=z;
if (x <min) min=x; if(x>max) max=x;
if (y <min) min=y; if(y>max) max=y;
//交互运行的情况如下,
printf ("min=%d,max=%d\n",min,max);
//3,2,7?
} //min=2,max=7
37
[例 ] 对三个数从小到大排序对两个数排序非常简单,就是交换排序。对于两个数 a,b,
如果 b<a则将其次序交换,其代码为三个赋值语句,t=a; a=b;
b=t;
对于任意次序的三个数 a,b,c 排序的问题,可以归于两个数的排序问题,其思路是选出三个数中的两个数,小的前移大的后移,排序时可以存在各种不同的路由判断。下面的函数
f 先对后面两个变量进行比较。第一次比较的结果较大的变量在第三个位置。第二次用第一次比较后大的结果和第一个变量比,比较之后最大的放置到第三个位置。第三次对前面两个位置的变量比,比较之后最小的放置在前面第一个位置。
38
#include<stdio.h>
void f (int a,int b,int c)
{ int t;
if (c<b) {t=b; b=c; c=t;}
if (c<a) t=a,a=c,c=t;
if (b<a) {t=a; a=b; b=t;}
printf ("%d,%d,%d\t",a,b,c);
}
void main (void) { f(1,0,2),f(9,7,4); }
39
2.双路分支 if~else语句
if~else语句结构称为双路分支选择控制结构,在两种不同的动作中做出选择即二者选一。
if~else语句的执行过程为:
if语句对表达式进行求值计算,若结果为非零值,则执行
if下的复合语句 1,否则执行复合语句 2。两个语句中仅执行一个语句。复合语句仅只有一条语句的时候花括号可以省略。
if(e)中的表达式 e非零分支影响范围是复合语句 1,else
分支即 e为零的影响范围是复合语句 2。整个,if (表达式 )语句
1;else 语句 2; "语句可视为一条独立的语句。
40
注意:
因为语句是表达式跟分号结尾构成的,而这里的语法格式中特地又添写了一个分号,只是强调分号的重要性,if 条件表达式严格精炼地语法格式应写成:
"if(表达式)语句 1 else 语句 2 "

"if(表达式)语句 "
41
if(表达式 )
{复合语句 1;}
else
{复合语句 2;}
后续语句;
If ~ else
后续语句复合语句 1
复合语句 2
零非零表达式
(1) 语法格式 (2) 程序流程图图 [ if(表达式)语句 1; else 语句 2; ]语法格式与流程
42
考虑到分号本身是一个空语句,如下的语句在程序中是正确的格式:
if(e) ; statement; // if(e) ; 相当于 e;
“if(e) ;,视为一条语句,if(e)的控制范围是其后紧跟的语句,此时其后紧跟的语句是一条空语句,;”,语句
statement则不受 if(Expre)的影响,即程序无条件执行语句
statement。
下面的语句比较两数的是否相等:
if(x==y) cout<<"x equal y"<<endl;
else cout<<"x not equal y"<<endl;
if语句可独立运行,else则必须与 if成对使用,有一个
else则必须相应地匹配一个 if。
43
3.if~else语句或 if语句的嵌套
if~else语句或 if语句允许嵌套使用,例如,if(表达式 )
语句 ;“中的语句可以是另一个” if(表达式 )语句 1;else语句 2; "。
if(e1)
if(e2) 语句 s1;
else 语句 s2;
,if(e2) s1; else s2;,语句视为 if条件表达式下的内嵌语句。首先执行表达式 e1 的计算,结果为真则执行表达式
e2的计算,表达式 e1结果为假则不执行表达式 e2的计算,
更不执行语句 s1或语句 s2。语句 s1在表达式 e1与表达式 e2
同时为真时得以执行; 语句 s2在表达式 e1为真与表达式 e2
为假时得以执行。
44
花括号用来控制 if结构的条件之影响范围。 花括号括起来的语句在 C/C++语言中对应一个独立的程序块,因此通过加上花括号可以对 if条件的影响范围施加灵活的控制,如:
if(e1)
{ if(e2) s1; }
else
s2;
花括号强制性地限制 if(e2)的作用范围局限于花括号控制的程序块,这是值得强调的一个重要编程性质。
此时 else语句与第一个 if语句匹配。
45
“if(e1) { if(e2) s1;} else s2;,
视为 if~else双路分支选择结构,其 if分支下内嵌 if语句。
表达式 e1首先完成求值计算,结果为假则执行 s2语句。
表达式 e1结果为真,则接着进行表达式 e2的求值计算,
e2结果为真执行 s1语句。
值得注意 e1结果为真 e2结果为假则 s1语句与 s2语句都不执行。
46
[例 ] if表达式的影响区域与求三个数的极大值
long amax (long x,long y,long z)
{ long max=x;
if (y>max) max=y;
if (z>max) max=z;
return max;
}
long bmax (long x,long y,long z)
{ long max=x;
if (z>y) { if (z>x) max=z; }
else if (y>x) max=y;
return max ;
}
47
long errmax (long x,long y,long z)
{ long max=x;
if(z>y) if(z>x) max=z;
else if(y>x) max=y;
return max;
}
#include<iostream.h>
void main (void)
{ cout<<amax(5,7,4)<<endl; //输出 7
cout<<bmax(5,6,4)<<endl; //输出 6
cout<<errmax(5,6,4)<<endl; //输出 5
cout<<errmax(4,6,5)<<endl; //输出 4
}
48
上面 amax函数求极大值以 max为核心,if语句的思路清晰嵌套层次少,程序优雅流畅,bmax函数采用 if ~else语句求若干数中的极大值没有抓住问题的关键程序略嫌不美。
errmax函数求极大值时去掉 bmax函数 if(z>y)后的花括号,程序不存在语法错误,但逻辑含义是不同的。对于 z<y例如 x<z<y或 z < x <y的情形语句序列,
max = x; if (z>y) if (z>x) max = z;
else if (y>x) max=y;
执行的结果是 max=x而不是期望的结果 max=y。 因此花括号限制 if 表达式的影响范围于其内是一个重要的编程特点。
49
[例 ] 符号函数的多种实现对于上面的函数有几种 C函数实现形式,
int f1(int x) int f2(int x)
{ { int y;
if (x<0) return -1; if (x<0) y= -1;
if (x==0) return 0; if (x==0) y= 0;
if (x>0) return 1; if (x>0) y= 1;
} return y;
}
y=f(x)=
-1 x<0
0 x=0
1 x>0
50
int f3 (int x) int f4(int x)
{ int y; { int y;
if(x<0) y= -1; if (x<=0)
else if (x==0) y=0;
if(x==0) y=0; else y=-1;
else y= 1; else y= 1;
return y; return y;
} }
51
同一个三叉分支函数可以对应不同的流程实现,f1 函数直接根据数学上函数的定义进行程序设计,这是直扑问题的解题思路。 f2函数引入临时变量转递计算结果,节奏略嫌缓慢。
函数 f1 和 f2 中的 if 语句的关系是平行的关系,平行的关系导致判断独立进行,但容易卷入多余的逻辑判断。函数
f3 和 f4 是 if~else 嵌套关系,两者功能是等价的。
嵌套关系构成路由机制,先执行前面的逻辑判断,且最多仅进入其中的一个分支语句。
52
4.if~else if~else语句前节例题中 f3 函数里 if~else 语句 else分支下是另一个
if~else 语句。 如果 if~else 语句的嵌套全部发生在外层
if~else语句 else分支下,就得到常用的 if~else if~else语句。
该类型的语句在多个分支中仅执行表达式为非零的那个
if下的语句,如果所有表达式都为零,则执行最后一个 else
下的语句。
if ~ else if~else选择控制结构是 if ~ else 双路选择控制结构的常用的派生形式,分支的层层嵌套全发生在 else分支下,整个 if ~else if~else选择控制结构可视为一条语句,常用于多分支的控制处理中。
53
但由于前面的 if 语句中的条件表达式事先完成求值判断计算,对于后面的条件表达式构成屏蔽的短路效果;因此在构成程序的路由机制时,注意 if ~ else if~else选择控制结构多层嵌套的本质特点,务必仔细安排表达式的先后次序,将多发的重要的事件安排在较外层的 if 分支下,避免函数调用型条件表达式的副作用。
if ~ else if~else控制结构路由的特点是前面 if下的条件为真,后面的分支被跳过;分支越靠前,分支下的代码越被优先处理。这有利于信息轻重缓急的有次序过滤。这一特点是 switch语句不具备的。
语句 [if(e1||e2||e3)s1;]可以等价地展开为 [if(e1)s1;else
if(e2)s1; else if(e3) s1;]。
54
if(表达式 1)
{复合语句 1;}
else if(表达式 2)
{复合语句 2;}
......
else if(表达式 n)
{复合语句 n;}
else
{复合语句 m;}
后续语句;
if~else
if~else
后续语句复合语句 m
复合语句 n
复合语句 1
复合语句 2
零非零表达式 1
零非零表达式 2
零非零表达式 3
(1) 语法格式 (2) 程序流程图 if ~ else if~else 程序流程条件分支
55
关于 if 和 if~else结构,下面是值得注意的几点:
if语句下复合语句涉及到的花括号 {}的后面不要加分号
";",分号 ";"本身是一条空语句,花括号 {}的后面加分号 ";“
以后会构成另外的逻辑语义解释甚至语法错误。
在 if 语句的嵌套结构中,else 总是与距离其最近的未配对的 if匹配,一个 else匹配一个 if。多层嵌套的情况下,从最内层一一配对。
else只能伴生在 if语句的条件判断下,if语句则可以独立运行。介入花括号可以对 if语句的条件表达式的影响范围人为地重新定界,有助于灵活地控制程序的流程与走向。
56
在 if语句中不要将关系运算符 ==与赋值符 =混淆。赋值运算符 = 导致左操作数的更新,且表达式的结果可拥有非零的真值,关系运算符 ==则仅进行逻辑判断而无此副作用。
例如:
if(k=5) i=6; 与 if(k==5) i=6;
具有不同的计算结果。
前者导致整型变量 k再定义为 5,i再定义为 6,后者则仅当原先 k为 5时 i才再定义 6。
if(表达式 )等价于 if(表达式 !=0)或 if(e)等价于 if(e!=0),
酌情选取两者之一。
57
5.条件运算符
C/C++有一个唯一的三目运算符即条件运算符,?:”。
条件运算符的优先级别高于赋值运算符与逗号运算符。条件运算符与三个操作数一起构成条件表达式,其语法格式为:
表达式 1? 操作数 2,操作数 3 e1? e2:e3
条件表达式的运算流程根据下面的规则确定:
a,首先执行第一个表达式,完成所有的副作用。
b,如果第一个表达式求值为真 (一个非 0值 ),第二个操作数求值。
c,如果第一个表达式求值为假 ( 一个 0值 ),第三个操作数求值。
58
条件表达式的结果是第二个操作数或第三个操作数的值。在条件表达式中后两个操作数只有一个求值。对于表达式嵌套的情况,后面操作数中仅只有一个求值。
第一个表达式为算术或指针类型,条件表达式结果的类型取决于第二及第三个操作数转换的共同类型,以下规则用于第二及第三个操作数:
a,如果 e2,e3是同类型的,则结果是那个类型的。
例如,e2,e3是 int型,则表达式是 int型。
在 C++中如果第二和第三个操作数是同类型的左值,则表达式的结果是左值表达式。 在 C中条件表达式的结果不是左值。
59
b,如果 e2,e3是算术类型,则执行常用的算术转换以将它们转换为共同的类型。例如 e2是 short型,e3是 double
型,则表达式的结果是 double型,即 sizeof(e1>e2:e3)=8。
c,如果 e2或 e3作为 void类型的函数调用,则运算的结果为无值返回。
d,允许 e2,e3为结构变量或对象,此时 e2,e3必须是可以转换为相同数据类型的表达式,这种转换可以通过类型转换函数进行,否则是错误的。
例如对于结构声明和定义语句 [struct Data {int x;}
s={1};]和整型变量定义 [int e =1;]
表达式 e =e1? e2:e3相当于:
if(e1!=0) e= e2;
else e= e3;
60
第三个操作数可以是另外的条件表达式,形成条件表达式的嵌套。条件运算符是右结合的。结合性用于确定嵌套的操作数绑定的紧密程度,与其间操作数的先后运算次序没有直接的关系。
编译器对条件表达式从左到右扫描,寻找三个操作数的匹配。问号,?”之前的表达式是整数 (含 bool型 )、浮点或指针类型,冒号 ":"两侧的类型给出结果的类型。
表达式 e =e1? e2:e3?e4:e5根据右结合性处理为 e =e1?
e2:(e3?e4:e5),效果上相当于,
if(e1) e= e2;
else if(e3) e= e4;
else e= e5;
61
而表达式 e =(e1? e2:e3)?e4:e5类似于左结合性的理解,在效果上相当于,
if(e1) if(e2) e= e4;
else e= e5;
else if(e3) e= e4;
else e= e5;
条件运算符常用来计算最大值和最小值、或绝对值,
如:
max=a>b? a,b;
min=a<b?a,b;
abs= x>= 0? x,-x;
62
前节的符号函数可用下面条件表达式的嵌套表示,
如:
signx= x>0? 1,x<0?,-1:0;
x为正数,结果为 1,x为负数,结果为 -1,x为零,结果为 0。
如果 a,b同为算术型的左值变量,则下面的表达式得到 a
与 b的最大值减去最小值,接着直接除以 480。运算的结果依然是左值表达式。
( a>b? a,b -= a<b?a,b ) /=480;
三目运算符表达式 e1?e2:e3本质上是语句 "if(e1)
e=e2;else e=e3;“的一个洗练描述,仅在简单场所替代双路分支语句,其中 e表示该表达式的结果。
63
五,switch语句语法格式
switch语句构成的多路顺序分支选择是一种简捷的结构控制形式。 switch语句的语法格式如下所示,方括号包括的内容表示可以省略的项目,break关键字用于退出 switch控制体。 break 关键字仅用于终止当前的 switch控制体即控制跳转到紧随其后的后续语句。
switch语句的表达式要求是整型 (含字符型或枚举型 )不能是浮点型数据。整型常数表达式为编译阶段可以静态求值的整数构成的表达式,相当于 case语句的入口。最后一个分支可省略 break语句。可用 goto 语句把控制转向嵌套结构的外层的标号处。
64
switch(表达式)
{ case 整型常数表达式 k,[语句序列 k; break;]
case 整型常数表达式 j,语句序列 j; [break;]
case 整型常数表达式 i,语句序列 i; [return语句 ;]
......
case 整型常数表达式 n:
语句序列 n; [ goto AfterSwitch; ]
[ default,语句序列 m; break;]
}
[AfterSwitch,]
后续语句;
switch语句的语法格式
65
switch~case开关分支的 执行流程 是:
switch语句根据表达式中的值进行分支选择,首先计算出表达式的值,然后用其结果值与各个 case后的整型常数表达式的值进行比较判断,测试是否相等,只要找到相等的匹配,就立即跳转到与之匹配的语句序列中进行计算。
如果在各整型常数表达式中没有搜索到相等的匹配,则执行 default下的语句序列或跳过 switch~case控制体若此时程序没提供 default语句。
66
在一个给定的 switch语句中,在 case语句中的整型常数表达式的值不能彼此相同,default也仅能出现一次。
流程一经进入 case分支中,就顺序往下执行分支中的语句,直到遇到某一分支的 break 或 goto语句或 return语句,
此时退出该分支;否则,继续往下执行。
每一个分支的先后次序不影响优化搜寻的匹配结果。
在刻意省略 break的情况下,程序的运算状况差异很大。
67
在 case分支下的语句序列中可以引入局部变量,但不能引入局部对象。 switch的内部语句块新定义的变量须在某一可路过的 case分支中。
而 if语句下的复合语句中可以引入局部变量和局部对象。 在每一分支中引入 break语句是稳健的作风。
,AfterSwitch:”是与 goto语句匹配的标号。
当 switch的复合语句只有一条语句时,可以写为:
switch (e) 整型常量表达式 1,语句 1;
例如,对于 [intz=2;]下面的两条语句时等价的:
1,if(z!=1) printf ("%d \n",z);
2,switch (z) case 2,printf ("%d \n",z);
68
[例 ] switch分支求自然数的和 sum=1+2+...+n=n*(n+1)/2
#include<stdio.h>
long Sumof1toN(int n);
void main(void)
{ printf("1+2=%d\n",Sumof1toN(2));
printf("Sumof 1 to 100=%d\n",Sumof1toN(100));
}
long Sumof1toN(int n)
{ long sum=0;
switch (n)
{ case 0,return 0;
case 2+1,sum+=3;
69
case 2,sum+=2;
case '1'-48,sum+=1;break;
default,sum=n*(n+1)/2; break;
}
return sum;
}
注意,[case?1?]等价于 [case 49],因为‘ 1?等于 49,
应避免此类隐含的标号重名的错误。
上面分支属于下落式结构,下落式的特点是 case分支之间无 break语句打断,分支语句的次序不能自由变动。而下面的函数分支由于每一个 case之下布置跳出语句,因而次序可以任意,因此是好的编程风格。
70