== 等于 系 双目同上a==b c=(a+b)>=(a-b)c=0;
a+=a>=ba=3;
a-=a==-b+1a=2(见exa4_2)
若在一个表达式中含有赋值号,常称为赋值表达式;含有逻辑运算符,常称为逻辑表达式;含有关系运算符,常称为关系表达式.
式中的a,b很多情况是表达式;
逻 逻辑运算的返回值如同普通值一样
可以参与后继操作.如:
a=2,b=-2;
c=!
bc=0;c=(a>0)&&(b>0)c=0;
辑 c=a>=0||b>0c=1;c=(a>=0)&&b
c=1;c=(a<=0)||!
bc=0(见exa4_2)
&取址 单目 无 &aa的存储地址 地址是一个特殊类型的数据类型
此外,教材上把强制类型转换、取数据类型存储长度函数sizeof()也列入单目运算类型.
2.运算优先级
①运算的优先级:
多种运算符同时出现于一个表达式时,准确判断其有限序是获得准确结果的前提.
运算优先级见教材P.369P.369.记忆规律可循一般的主导思路:
找寻目标对目标作处理(单目运算)目标参与(算术)运算(先乘除余,后加
减)对结果进行比较(关系运算)对多个关系做逻辑判定(逻辑运算)输
出最终结果(赋值).
②同优先级处理:
在一个表达式中,若出现同一优先级运算符,则按结合方向决定运算的先后次序.所谓结合方向是指表达式向哪个方向去结合运算符,自左向右结合是从右边起,表达式去与其右边最近的运算符结合.结合方向可按下述方法记忆:
除单目运算、赋值运算和三项结合运算外,都是自左向右结合,而单目和赋值自右向左结合是我们的自然习惯.如
c=a+b+c(左向右结合) c=(a+b)+c);
x=5>2>7>8(左向右结合) x=((5>2)>7)>8x=(1>7)>8x=0>8,最后x为0;
③对前置、后置自增自减运算,其固定法则部分,不受运算优先级的干扰.如
(1)a=4,b=2;c=++a%b--;
按运算优先级,自减运算‘--’应在取余运算‘%’之前,但按后置运算法则,实际上运算次序仍然是:
a自增a=a+1成为5a%b得1b自减成为1把a%b的结果1赋予c.
(2)a=13,b=7;c=(a--)%(b--);
按运算优先级,先对()内运算,结果似乎应该是c=(12)%(6),即以0赋予c,但实际上因为()内是a--和b--,运算()时按后置自减的法则,应该先运算(),结果分别返回13和7,然后a,b再分别‘--’成为12和6,而运算‘%’对两个()内返回值进行,即13%7得6,因此赋值予c的是6.
(3)a=13,b=5;c=++(a%b--);c1=(++a%b)--;
与例1)相比,c把‘++’放到了()外,c1把‘--’放到了()外,但表达式含义及结果与1)大相径庭.因为这一来,运算‘++’和‘--’的对象不再是a,b而是()内的返回值,因此
第一个表达式结果是:
a%b返回3b--,b成为4++3得4赋予c,所以最终:
a=13,b=4,c=4;
第二个表达式结果是:
++a,a成为1414%4返出22--成为11赋予c1,所以最终:
a=14,b=4,c1=1.
可惜上述分析仅是理论上的,C语言的编译系统还不足以分析这种略微复杂一点的表达式,干脆给出一个出错信息,认为语法有错误!
也就是说,如果您想得到上述结果,只能分为两个表达式,不能一步得到.(见exa4_4)
④运算与输出序的关系.在以前讲输出项即输出目标,总认为它是变量、常量、函数,其实输出目标可以是一个表达式的返回值,此时输出项就成为一个表达式了.如:
putchar(‘c’-32);(输出‘C’);printf(“%d”,x>=0?
x:
-x); (输出x).
用格式化输出函数printf(),一次可以输出多项,当输出项是一些表达式时,就会发生问题:
这些表达式先计算哪一个?
例如:
inti=0;printf(“%d,%d,%d”,i,i++,i++);
两个‘i++’先计算哪一个?
C语言规定:
逆序计算,顺序输出.也就是说,表达式从最后一个开始依次向前计算;到第一个计算结束,再顺序依次从第一个输出,因此上面语句输出结果是:
2,1,0_.注意这里仍然遵守后置运算的规则,先把末个i=0送到输出栈,再在i的存储元中自增,改变i成为1;然后把中间i=1送到输出栈,再在i的存储元中自增,改变i成为2;最后把第一个i=2送到输出栈,再在i的存储元中自增,改变i成为3.所有输出项计算完毕,依次从输出栈取出2,1,0予以输出.(见exa4_5)
⑤几点注意:
(1)若运算属于优先的,理论上不必再加(),但为了增强程序的可读性,最好还是加这些“不必要”的圆括弧,或者分列成几个运算式.如
x=2,y=3;z=3-1>=x+1<=y+2; (*)
按优先级:
z=(3-1)>=(x+1)<=(y+2),
按结合方向:
z=((3-1)>=(x+1))<=(y+2)z=(2>=3)<=5z=1
因此.(*)式是合法的,且按算法优先级有一个唯一的计算顺序,但这种写法显然增加了阅读的困难.
(2)不要滥用优先级或连续赋值,尽量加一些不必要的括号(),以免引起误解.例如
1)x=i+++j;
可解释为x=i+(++j),也可解释为x=(i++)+j;实际上C语言在编译时,自左向右结合尽量多的运算符成为一个运算符,因此后一种解释是正确的.
2)x=i+++++j;
解释就更多了,按上面编译处理原则,似乎应解释为x=((i++)++)+j,实际上不能通过编译,认为它是一个错误的表达式.
3)i=(k=j+1)+(j=5);
按运算优先级和结合方向规定,能得到一个唯一的结果:
k=j+1得到第一个返回值j=5得到第二个返回值相加两个返回值后赋予i.但由于编译系统差异,实际可能会先执行j=5,再执行k=j+1而使结果与预期的不同.因此在“结合方向”问题上不能绝对化.
类似的例子还有运算‘&&’与‘||’,它们优先级相同,结合方向自左向右,但有时是‘&&’优先于‘||’.因此在同优先级情况,比较可靠的还是分列或加括号.
(3)C语言对表达式处理有一个优化原则:
当一个表达式已经能得到确定的值,就终止计算.如:
x=y=0;经表达式 ++x||++y&&++z;计算后的x,y,z的值应该是1,1,1,但实际上是1,0,0.原因是当++x返回1时,根据逻辑运算‘||’的运算法则,此表达式已经能得到确定的值,因此计算终止,下面的++y,++z不再执行而保留了原来的值.(见exa4_6)
3.字符串运算
字符串运算常称串操作,其运算对象不是字符串中的个别字符,而是整个串.
对字符串运算主要调用系统提供的一批函数,这些函数的原型说明都在string.h中,因此程序中应包含头文件#include.
①字符串求长函数strlen():
使用格式:
strlen(字符串地址);
功能:
返回从指定地址起到‘\0’止所含的字符个数(不包括‘\0’本身).若地址是存储字符串的首地址,则返回整个字符串所含的字符个数,即字符串的长度.
例如charch[20]=“Goodmorning”;
strlen(ch) 返回12; strlen(&ch[4]) 返回8.
②字符串连接函数:
strcat()
使用格式:
strcat(字符串1的地址1,字符串2的地址2);
功能:
把地址2起直到‘\0’为止的字符串2,连接到字符串1的后面去,成为一个字符串,并返回地址1(直观图象见下).
例:
charstr1[50]=“Good”,str2[50]=“morning”;
strcat(str1,str2);str1[]中存储了字符串“Goodmorning”,返回地址str1;
若改为strcat(&str1[2],&str2[2]);str1[]中存储了字符串“Goodrning”,返回地址&str1[2].
注意:
1)字符串2可以是一个字符串常量,例如:
strcat(str1,“Goodmorning”);
字符串1在理论上也可以是字符串常量,如strcat(“Ilove”,“you”);但在实际上若强行如此,可能会破坏第二个字符串,因为字符串常量存储地址与存放于一维char型数组中的字符串的地址的段不同,字符串常量之间没有空余字节.
2)被连接的字符串后应该有富裕字节,使能容下连接后所成较长的字符串,否则与1)同样的问题也会发生,例如charstr1[7]=“Ilove”,其后无富裕字节,在其后连接另一个字符串“you”时,也可能破坏后继数据.
③字符串比较函数strcmp():
使用格式:
strcmp(字符串1地址,字符串2地址);
功能:
从指定的两个地址起,逐字节比较,直至
两个字符串都读到了‘\0’,返回0 两个字符串相同;
串1的字符>串2的字符,返回正数 串1>串2;
串1的字符<串2的字符,返回负数 串1<串2.
简言之,比较结果返回0表示两个字符串相同;返回正数表示字符串1>字符串2;返回负数表示字符串1<字符串2.
例如,charch1[10]=“abcd”,ch2[10]=“abcd”,ch3[10]=“abcd”,ch4[10]=“abcD”,
ch5[10]=“Abcd”,
strcmp(ch1,ch2)返回0;strcmp(ch1,ch3)返回负数;strcmp(ch3,ch4)返回正数;
strcmp(ch1,ch5)返回正数;strcmp((ch1[1],&ch5[1])返回0.
④字符串复制函数strcpy():
使用格式:
strcpy(地址1,地址2);
功能:
把地址2开始的字符串(从地址2起到‘\0’止的字符串),赋值到地址1去,成为一个相同的新字符串,并返回地址1.如
charch1[20],ch2[20]=“Googmorning”;
strcpy(ch1,ch2);ch1[]中存放了字符串“Goodmorning”;
strcpy(ch1,&ch2[2]); ch1[]中存放了字符串“odmorning”.
注意:
1)地址2开始的字符串可以是一个字符串常量,如
strcpy(ch1,“Goodmorning”);ch1[]中也存放了字符串“Goodmorning”.
2)以前曾强调字符串不能整体赋值,例如ch1[]=“Goodmorning”是错误语法.strcpy()函数的功能,相当于赋字符串予一个一维char型数组,只要strcpy(数组名,字符串常量).
⑤字符匹配函数strchr():
使用格式:
strchr(地址,字符变量或字符常量);
功能:
从指定的地址起,逐个字符与变量中的字符或字符常量对照,直到发现字符相同,返回自该字符在字符串中的地址;或已经扫描到‘\0’还没有发现相同字符,则返回“空”(即NULL).
例如charstr[20]=“Googmorning”,ch=‘n’;
strchr(str,ch)返回&str[8]; strchr(str,‘n’);结果相同;
strchr(&str[6],ch);结果也相同;若strchr(&str[1],‘G’);则返回NULL.
⑥子串匹配函数 strstr():
所谓子串匹配是指在一个字符串1中,是否含有另一个字符串2,若含有,则称字符串1能匹配字符串2,否则成为不能匹配.
使用格式:
strstr(地址1,地址2);
功能:
在以地址1起到‘\0’止的字符串1中,匹配地址2起的字符串2,若能够匹配,则返回字符串1中的匹配起始地址,否则返回NULL(‘空’).
例如:
charstr1[20]=“GoodBye”;str2[10]=“Good”;str3[10]=“Bye”;
strstr(str1,str2);返回str1(即&str1[0]);
strstr(str1,str3);返回&str1[5];
strstr(str1,“good”);返回NULL;
strstr(&str1[1],“Good”);也返回NULL.(见exa4_7)
⑦数字字符串转化为数的转换函数:
以下三个函数可以把一个数字字符串转化为相应的数,如“123”转化为123.这三个函数的原型说明在stdlib.h,因此要使用它们,必须包含头文件#include.
1)短整型转化函数atoi():
使用格式:
atoi(地址);
功能:
把指定地址起直到‘\0’止的、不含小数点的数字字符串,转化为相应的数,并予以返回.“地址”也可以是一个整数数字字符串常量.如
atoi(“123”); 返回整数123;
charstr[5]=“0124”;
atoi(str);返回124; atoi(&str[1]);也返回124;
atoi(&str[2]);则返回24; stoi(&str[3]);返回4.
注意若字符串中含有除+、-号外非数码字符,或转化结果超出短整型数的范围(-3276832767)则返回不正确的结果.
2)长整型转化函数atol():
使用格式及功能与atoi()相同,区别仅在于返回的是长整型数,且转化成数的范围扩大为长整型数的范围.
3)实型转换函数atof():
使用格式及功能与atoi()相同,区别仅在于数字字符串中可以含有数码、+,-号及小数点,返回的是单精度实型数(float型).
⑧数转化为数字字符串的转换函数:
有若干个函数能实现转换功能,仅介绍一个gcvt().
使用格式:
gcvt(doublevalue,intndigit,地址);
功能:
把数value转化为长度是ndigit个字节的相应字符串,存放在指定的地址中,并返回此地址.
如:
doublea=10.12;charbuff[20];
gcvt(a,5,buff);则在buff[]中即存放了字符串“10.12”.
value也可以是直接常量,如gcvt(10.12,5,buff);
结构体型变量可以含有不同数据类型的成员,在输入数据中,当由数值型成员输入转为字符型或字符串成员输入时,常常因为键盘缓冲区残留的回车符而造成误输入.为避免这种失误,常用的技巧之一是,在输入数值型成员时也输入为字符串,再应用上述转换函数转换为需要的数据类型赋予数值型成员.因为输入字符串能吸收回车符转化为‘\0’,这样就可不必顾虑残留回车符问题.例如:
(见exa4_8)
#include
#include
#defineMAX50/*定义宏MAX*/
structstudent/*定义结构体数据类型structsutdent,表示一个学生记录*/
{
unsignedlongnum;/*学号*/
charname[10],sex,;/*姓名、性别*/
floatscore; /*课程的成绩*/
};
/*-----------------------------*/
main()
{
structstudentst[MAX]; /*数组st[]存放一个班级学生的记录*/
intn=0,i,k=0;
/*n为实际输入记录的人数,i,k为计数器*/
charch,temp[20];/*ch用来接受用户的应答输入字符,temp[]临时存放字符串*/
do/*do...while(条件);是循环语句,在条件满足时,重复执行其间的所有语句*/
{
clrscr();/*每一个记录输入之前清屏*/
printf("\n\n"); /*空二行*/
printf("输入第%d个记录的全部数据:
\n",n+1);/*提示输入第几个记录*/
printf("学号:
");/*下一行行首空二个字符后,提示输入学号*/
gets(temp); /*自动换行*/
st[n].num=atol(temp);
printf("姓名:
");
gets(st[n].name); /*自动换行,键盘缓冲区是空的*/
printf("性别(男:
m;女:
w):
");
gets(temp);
st[n].sex=temp[0];
printf("输入课程的成绩:
");
gets(temp);
st[n].score=atof(temp);
n=n+1; /*一个记录输入完毕,输入记录个数增加1*/
printf(“\n还要继续输入吗(y/n)?
");
/*隔开一行后提示选择后继操作及应答格式*/
gets(temp);ch=temp[0];
}while(ch=='Y'||ch=='y');
......
}
本章作业:
一、P.663.9,3.10,3.12
二、补充:
输入三个字符串,按有“小”到“大”的顺序输出;然后把这三个字符串连接起
来予以输出;再把第三个字符串的后半部分复制到连接后字符串的一半的地方,输
出结果;最后,找出第三步复制后与第三个字符串的后半部分的匹配起始点.
三、职工工资表的结构为
structwork
{
charname[10],salory[10],depart[3];
}
其中name[]为姓名,salory[]为工资(含有二位小数),depart[]为部门号.部门号
从“01”到“99”.所有职工的工资记录存放在数组structworks[200]中,实际职
工人数不足200人,以姓名是空字符串为结束标记.试编一个函数
voidaverage_salory(structworks[])
输出各部门职工的平均工资和全体职工的总平均工资.显示形式为:
部门号..,职工平均工资=.....(换行);
最后输出所有姓王和姓张的职工的混合平均工资.