**贰**
**几个关键字**
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 <stdio.h>
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 <stdio.h>
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&& x<MAX && array[x]==0 ) ……….//可用
(首先检查x值是否在数组下标范围内。如果不是,代码中的下标引用表达式便忽略。此可防止下标值错误导致引用失败)
例:if(p==NULL|| *p==’\0’) ………….//可用
【总结:**使用&&时,把大前提条件放前面,小条件放后面**
**使用&&时,是第一个表达式成立才继续执行(有点像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) ; 也是表达式)
【**左值意味着一个位置,而右值意味着一个值(左值就是存储右值的位置)**】