贰(几个关键字、几个运算符、隐式转换/溢出、表达式求值的顺序、左值和右值)

最后更新于:2022-04-01 20:29:59

**贰** **几个关键字** sizeof 1、sizeof是关键字而不是函数。 例:int i=3;   sizeof(i) ;  与  sizeof i ; 是完全等同。 sizeof在计算变量所占空间大小时,括号可以省略,而计算类型大小时不能省略。(所以我们常在其后加上括号) 2、sizeof的作用域是紧跟它后面的一个变量或类型。 故:sizeof(int)*p;//p为指针,则此表达式会报错。此表达式的意思不是计算 *p强制转换成int类型之后值的存储大小,而意为sizeof(int)的值4 然后是*p的值 这两个值中间没有操作符故会报错。同理:sizeof i*i ;//int i=3 此表达式值为12 1、 ★sizeof的值在编译时就已经确定,故在程序执行时不会执行sizeof括号内的表达式。 ~~~ #include int main(void) { int i; i = 10; printf("sizeof(i++)is: %d\n",sizeof(i++)); printf("i :%d\n",i); return 0; }//最后打印i的值为10,因为程序不执行sizeof里的表达式i++ ~~~ switch、case组合 注意: 1、每个case语句的结尾不要忘了加break,若是为了实现某功能故意不加,应该注释清楚 2、最后必须使用default分支。即使程序真的不需要default处理,也应该保留语句 default :   break; 可以避免让人误认为你忘了default处理。 2、 case关键字后面只能是整型或字符型常量(小整型)或常量表达式。 3、 ★switch块中的case是一个标签,switch根据值来选择执行哪个case标签的语句。故编译器只对case和default标签里的语句编译,而写在而在case块之前的语句则忽视掉。 ~~~ #include int main(void) { inta=1; switch(a) { intb=20; //此语句不被编译,C编译器会产生警告(C++编译器会报错) case1: printf("bis %d\n",b); //故此输出随机值 break; default: printf("bis %d\n",b); break; } return 0; } ~~~ void void的字面意思是“空类型”,void * 则为“空指针类型”任何类型的指针都可以直接赋值给它,无需进行强制类型转换。但:void * 不可以不经强制类型转换地赋值给其它类型的指针。因为“空类型”可以包容“有类型”,而有类型则不能包容“空类型”。 如果函数的参数可以是任意类型的指针,那么应声明其参数为void * void不能代表一个真实的变量。(因为定义变量时必须分配内存空间,定义void类型编译器不知道分配多少空间)故:void a ;//错误 void只是为了一种抽象的需要。 const 在C语言中,const修饰的值是只读变量,其值在编译的时候不能被使用,因为编译器在编译的时候不知道其存储的内容。 例:const intmax=100 ;  int Array[max] ; 在C编译环境下,编译器会报错。因为在C中,const修饰的仍然是变量,只不过是只读属性罢了。 而在C++中,const则修饰的值是常量,完全可以取代宏定义,其效果和宏定义相同。故上面的代码在C++中是合法的。 volatile volatile修饰的量就是很容易变化,不稳定的量,它可能被其它线程,操作系统,硬件等等在未知的时间改变,所以它被存储在内存中,每次取用它的时候都只能在内存中去读取,它不能被编译器优化放在内部寄存器中。 ★const和volatile在类型声明中的位置 两者的规则一样。以const为例: 类型声明中const用来修饰一个常量,我们一般这样使用: ①const在前面 const int  ;           //int是const const char* ;         //char是const char* const ;         //*(指针)是const const char* const ; //char和*都是const 【const所修饰的类型是 它后面的那一个】 ②const在后面( 与上面的声明对等) int const ;                     //int是const char const* ;         //char是const char* const ;         //*(指针)是const char const* const ; //char和*都是const **建议const写在后面:** A. const所修饰的类型是正好在它前面的那一个。 B. 我们很多时候会用到typedef的类型别名定义。比如typedef char* pchar,如果用const来修饰的话,当const在前面的时候,就是const pchar,你会以为它就是const char* ,但是你错了,它的真实含义是char* const。是不是让你大吃一惊!但如果你采用const在后面的写法,意义就怎么也不会变,不信你试试! ***几个运算符*** 逻辑运算符:**(一定要注意表达式的顺序)** && ||  有“短路”现象    (有好处也有坏处) **/*利用短路现象*/** 例:if( x>=0&& x5 ? b-6: c/2  ; //可读作“a是不是大于5?如果是,就执行b-6,否则执行c/2” ~~~ if(a>5) b[2*c+d(e/5)]= 3 ; else b[2*c+d(e/5)]= -20 ; //用条件运算符可写作:b[2*c+d(e/5)]= a>5 ? 3 : -20 ; ~~~ 【在某些复杂的表达式写相似的两次时,用条件运算符更简洁,易修改会产生较小的目标代码, 可以简化表达式】 逗号运算符: 【**可用于循环判断中(多用于while),使多次重复的语句只写一次**】 ~~~ a=get_value(); count_value(a); while(a>0) { ………. a=get_value(); count_value(a); } ~~~ 则可简化为: while(a=get_value() , count_value(a) , a>0){……} (这些表达式自左向右逐个求值,整个逗号表达式的值就是最后那个表达式的值) ***隐式转换 / 溢出*** 例1:char  ch ;        While( (ch=getchar()) != EOF ) …..//错误! 注意:**getchar()返回一个整型值而不是字符值!** 若把getchar返回值存储于ch中,将导致它被截断!然后这个被截断的值被提升为整形并与EOF比较,循环会出错。 【用整形来定义一个字符变量更好!**字符就是一个小整数**】 sizeof(‘a’) ; 的值为4。 ~~~ char a,b,c ; a = b+c ; //b+c的值超过或等于128就会出错。 ~~~ 注意:C的整型算数运算总是至少以缺省整型类型的精度来进行的。为了获得这个精度,表达式中的字符型和短整型操作数在使用之前被转换为普通整型。即:**整型提升** (上面b和c的值被提升为普通整型,然后再执行加法运算。加法运算的结果被截断再存储于a中) 【所以:**最好把字符型定义为int 尤其是涉及运算时**】 //WHY?在整数运算时,操作数是放到两个寄存器中进行的(32位计算机寄存器是32位 故字符型变量被提升为整型,计算的结果又被传回到内存中的字符型存储位置中故被截断) ~~~ int a=5000 ; int b=25 ; long c=a*b ; ~~~ 表达式a*b以整型进行计算,若在16位的计算机上,这个乘法可能会产生溢出! 故应该显式转换: longc=(long)a*b ; 【在进行算术运算时一定要警惕乘法加法的可能溢出,尤其**注意赋值号两边的数据类型保持一致**】 在计算类似a*b/c 这样的表达式的时候,一些中间计算结果可能会溢出。 【**对于算术运算可能的溢出一定要保持警惕!!!**】 ***对无符号类型的建议:*** 1、尽量不要在你的代码中使用无符号类型,以免增加不必要的复杂性。 2、尽量使用像int那样的有符号类型,这样在涉及升级混合类型的复杂细节时,不必担心边界情况。 3、只有在使用位段和二进制掩码时,才可以使用无符号数。 ***表达式求值的顺序*** ***两个相邻操作符的执行顺序由它们的优先级决定。如果它们的优先级相同,它们的执行顺序由它们的结合性决定。(即从左还是从右运算),此外编译器可自由决定顺序对表达式求值! ~~~ a*b + c*d +e*f ; //可能按下列顺序运行: c*d e*f a*b (a*b)+(c*d) (a*b)+(c*d)+(e*f) //加法运算的结合性要求两个加法运算按先左后右执行,但对表达式剩余部分执行顺序没有限制。 没有规则要求所有的乘法首先进行,没有规定其谁先执行。 ~~~ 【**优先级只对相邻的操作符的执行顺序起作用**】 【在C语言的众多运算符中,**ANSI标准只规定了4种运算符的操作数的求值顺序: &&  || 逗号运算符 和 ?:**】 警示:f( ) + g( )+ h( ) ; //应该避免! 如果函数执行有副作用,不同的结合顺序会产生不同的结果! 故 最好使用临时变量,让每个函数调用都在单独的语句中进行。 ~~~ tmp = f( ) ; tmp += g( ) ; tmp += h( ) ; //良好的编程风格 ~~~ 【**避免编写 结果依赖于求值顺序的表达式**】 例:a[i] = i++;  //应该避免! //i在同一表达式的其它地方被引用无从判断该应用是旧值还是新值。 【**确保一个表达式只修改一个对象,如果要修改多个对象,要确保修改的对象互不相同**!(在一个表达式中,一个对象只能修改一次)】 ~~~ printf(“%d%d”,f1(), f2() ) ; //应该避免! //若函数f2( )的结果依赖于函数f1( ) 则printf的结果是不确定的! //用逗号分隔的函数参数不是逗号运算符。 ~~~ 【**函数调用的参数的求值顺序是不确定的! 不要让有副作用的函数作为参数!**】 ***左值和右值*** 左值就是那些能够出现在赋值号左边的东西,右值就是那些可以出现在赋值号右边的东西。 【编译原理:编译器会把赋值号左边的部分解释成一个存储位置,把赋值号右边的部分解释成一个值。在编译过程中,编译器会维护一张变量表,其中是我们定义过的变量名及其在内存中分配的地址。 当编译器遇到一个变量名,若此变量名在赋值号的左边,则编译器查变量表得到其在内存中的地址;若此变量名在赋值号的右边,则编译器先查得其在内存中地址,再把地址中的内容提取出来。(不准确,但初学者可以这么理解)】 **变量作为右值时,编译器只是取变量的值。** **左值标志了一个特定的位置来存储结果。** 例: a =b+25  ; // 正确        b+25 = a ;// 错误 当计算机计算b+25时,它的结果只是一个映像(暂存寄存器中),不在内存区中,我们无法得到它的地址,故这个表达式不是一个左值。 同理:*cp + 1 也不可做左值。(因为其值不存储于内存区中的一个特定的位置) 而字符串字面量虽然存储于内存区中,但其存储于内存的无名常量区,这个位置不是由我们设定的特定位置,而是由编译器随机分配的,故其也不能作为左值。 **表达式也可以是左值。** 例:int  a[30] ; ……….a[b+10]=0 ; // 正确 下标引用实际是一个操作符,故左边实际上是个表达式,但它是一个合法的左值,因为它标志了一个特定的位置。 同理: int  a, *p ; ……..p=&a; *p=20 ; // 正确 (含操作符的就是表达式。 sizeof(int) ; 也是表达式) 【**左值意味着一个位置,而右值意味着一个值(左值就是存储右值的位置)**】
';