牛骨文教育服务平台(让学习变的简单)

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

几个关键字

sizeof

1、sizeof是关键字而不是函数。

例:int i=3;   sizeof(i) ;  与  sizeof i ; 是完全等同。

sizeof在计算变量所占空间大小时,括号可以省略,而计算类型大小时不能省略。(所以我们常在其后加上括号)

2、sizeof的作用域是紧跟它后面的一个变量或类型。

故:sizeof(int)p;//p为指针,则此表达式会报错。此表达式的意思不是计算 p强制转换成int类型之后值的存储大小,而意为sizeof(int)的值4 然后是p的值 这两个值中间没有操作符故会报错。同理:sizeof ii ;//int i=3 此表达式值为12

1、 ★sizeof的值在编译时就已经确定,故在程序执行时不会执行sizeof括号内的表达式。

#include <stdio.h>
int main(void) 
{
    int i;
    i = 10;
    printf("sizeof(i++)is: %d
",sizeof(i++));
    printf("i :%d
",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 <stdio.h>
int main(void) 
{     
    inta=1;     
    switch(a)     
    {  
        intb=20;    //此语句不被编译,C编译器会产生警告(C++编译器会报错)
        case1:
            printf("bis %d
",b); //故此输出随机值
            break;
        default:
            printf("bis %d
",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&& x<MAX && array[x]==0 ) ……….//可用

(首先检查x值是否在数组下标范围内。如果不是,代码中的下标引用表达式便忽略。此可防止下标值错误导致引用失败)

例:if(p==NULL|| *p==’’) ………….//可用

【总结:使用&&时,把大前提条件放前面,小条件放后面

使用&&时,是第一个表达式成立才继续执行(有点像if-else 故我们常利用&&的短路规则)

使用||时,是第一个表达式不成立才继续执行

条件运算符:(可替换if-else)

例:a>5 ? 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) ; 也是表达式)

左值意味着一个位置,而右值意味着一个值(左值就是存储右值的位置)