运算符的优先级
运算符 |
结合性 |
() [] -> . |
从左至右 |
++ -- + - ! ~ (类型) * & sizeof |
从右至左 |
* / % |
从左至右 |
+ - |
从左至右 |
<< >> |
从左至右 |
< <= > >= |
从左至右 |
== != |
从左至右 |
& |
从左至右 |
^ |
从左至右 |
| |
从左至右 |
&& |
从左至右 |
|| |
从左至右 |
?: |
从左至右 |
= += -= *= /= %= &= ^= |= <<= >>= |
从右至左 |
, |
从左至右 |
字符串常量与字符数组
从技术角度看,字符串常量就是字符数组。字符串的内部表示使用一个空字符 作为串的结尾,因此,存储字符串的物理存储单元数比在括在双引号中的字符数多一个。这种表示方法也说明,C语言对字符串的长度没有限制,但程序必须扫描完整个字符串后才能确定字符串的长度。标准库函数strlen(s)可以返回字符串s的长度,但长度不包括末尾的 。
字符串
char *str = "abcd";//可以将字符串赋给一个 char * 类型的变量
printf("%c ",*str);//a
printf("%s ",str);//abcd
下面结果一样:
char ca[] = "abcd";//也可以将字符串赋给一个字符数组
printf("%c ", *ca);//a
printf("%s ", ca);//abcd
printf("%d ", sizeof(ca));//5
C语言中的字符串是用空字符 结束的字符数组。字符串是用指向字符串中第一个字符的指针访问的。字符串的值是其第一个字符的地址。因此,C语言中的字符串就是一个指针,事实上,字符串就是指向其第一个字符的指针。
在用字符串直接量初始化char *类型的变量时,某些编译器可能会把该字符串放在不能修改该字符串的某个存储单元中,所以,如果希望能够修改字符串直接量,应该把该字符串存储在某个字符数组中。
上面还可以这样写:char ca[] = { 'a', 'b', 'c', 'd', ' ' };
char *s 可以接收的数据类型有如下这些:字符串常量(char *s="str",实质上是将字符数组的第一个元素地址赋值给了变量s)、字符数组、字符指针。
输出一个字符串时使用 printf("%s ", s),第二个参数不是 *s(C语言没有提供将整个字符串作为一个整体进行处理的运算符),如果输出的是字符串,则直接是字符串地址即可。也就是说,字符串可以通过一个指向其第一个元素的指针来访问。
char str1[]="str1";
char *str2="str2";
//直接使用数组首地址或字符指针
printf("%s ",str1);
printf("%s ",str2);
在函数定义中,形式参数 char s[] 与 char *s 是等价的
可用一个字符串直接初始化一个字符数组:char string [] ="first";,编译器根据字符串的长度确定数组的大小。特别要注意的是:字符串“first”除了包含五个字符外,还包含一个结束该字符串的特别的字符,该字符表示“空字符”,因此,数组string实际上有6个元素,空字符用字符常量 表示,C语言中的所有字符串都是用该字符结束的。在声明一个字符串的字符数组时(指定数组大小时),数组的大小应该能够足以容纳字符串中的字符和结束该字符串的空字符。也可以在初始化值列表中用单个字符常量初始化字符数组:char string[] = { 'f', 'i', 'r', 's', 't', ' ' };,这与前面的定义等价。又因为字符串实际上是字符数组,所以可以直接使用数组下标来访问字符串中的单个字符。还可以用scanf函数和转换说明符 %s 把字符直接输入到字符数组中(字符串末会有 结束字符):
char string2 [20];
scanf("%s",string2);
数组名是数组的起始地址,因此&是不需要的。也可以用print函数和转换说明符 %s 输出代表字符串的字符数组。下面的语句打印了数组string2:printf("%s",string2);,它和scanf函数一样,printf函数也不关心字符数组的大小,它打印字符串的字符直到遇到终止字符串的空字符为止。
枚举常量
enum boolean{NO, YES=1}
在没有说明的情况下,enum类型中第一个枚举名的值为0,第二个为1,依次类推。如果只指定了部分枚举名的值,那么指定值的枚举名的值将依着最后一个指定值值向后递增。
C程序存储空间布局
C程序一直由下列部分组成:
1)正文段——CPU执行的机器指令部分;一个程序只有一个副本;只读,防止程序由于意外事故而修改自身指令;
2)初始化数据段(数据段)——在程序中所有赋了初值的全局变量,存放在这里。
3)非初始化数据段(bss段)——在程序中没有初始化的全局变量;内核将此段初始化为0。
4)栈——自动变量以及每次函数调用时所需要保存的信息(返回地址;环境信息)。
5)堆——动态存储分。int c* = (int *)malloc(sizeof(int));分配的空间就存储在堆里。
|-----------|
| |
|-----------|
| 栈 |
|-----------|
| | |
| |/ |
| |
| |
| /| |
| | |
|-----------|
| 堆 |
|-----------|
| 未初始化 |
|-----------|
| 初始化 |
|-----------|
| 正文段 |
|-----------|
四种存储类别
实际上,程序中的每一个标识符有以下几种属性:存储类别、存储期、作用域、连接(linkage).
auto register extern static
标识符的存储类别有助于确定其存储期、作用域和连接
存储期:标识符在内存中的存在期
作用域:可引用标识符的区域
连接:对于有多个源文件的程序,该标识符是只被当前源文件识别,还是因为用了合适的声明而被所有源文件识别。
四种存储类别说明符有两种存储期:自动存储期和静态存储期。
自动存储期变量在进入声明该变量的程序块时建立,退出该程序块时撤销。只有变量才有自动存储期。函数的局部变量(参数与函数体中声明的变量)通常具有自动存储期。
关键字auto明确地声明了具有自动存储期的变量,auto float x,y;因为局部变量在默认情况下具有自动存储期,所以很少使用关键字auto。
具有自动存储期的变量简单地称为自动变量。
因为局部变量在只在需要的时候存在,所以自动存储是节省内存的一种方法。
在自动变量声明前使用存储类型说明符register可建议编译器把该变量装载到计算机的一个调整硬件寄存器中。使用频繁的变量可以使用。register int counter=1;
编译器也可能会忽略register声明,例如,可能没有足够数目的寄存器可供编译器使用。
关键字register只能和具有自动存储期的变量一起使用。
register声明通常是不必要的,当今的优化编译器能够识别频繁使用的变量,并能够在不需要程序员作出register声明的情况下把这些变量放到寄存器中。
关键字extern和static用来声明具有静态存储期的变量和函数的标识符。具有静态存储期的标识符从程序开始执行起就一直存在。对于具有静态存储期的变量而言,存储空间是程序开始执行时一次性分配和初始化,对于函数而言,函数名从程序开始执行时就存在。
有两种类型的标识符具有静态存储期:外部标识符(如全局变量和函数名)和static声明的局部变量。
默认情况下,全局变量和函数名具有存储类别extern。
全局变量在整个程序执行期间都保持其值。
全局变量和函数可被在其声明或定义之后的该文件中的所有函数中引用,这是使用函数原型的原因之一。
一个标识符只能使用一种存储类别。
static
1. 全局静态变量
在全局变量之前加上关键字static,全局变量就被定义成为一个全局静态变量。
1)内存中的位置:静态存储区(静态存储区在整个程序运行期间都存在)。全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式
2)初始化:未经初始化的全局静态变量会被程序自动初始化为0
3)作用域:静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效(准确地讲从定义之处开始到文件结尾),在同一源程序的其它源文件中不能使用它。非静态全局变量的作用域是整个源程序(多个源文件可以共同使用)。
static对全局变量的修饰,可以认为是限制了只能是本文件引用此变量,并没有改变它的存储位置。
定义全局静态变量的好处:
<1>不会被其他文件所访问,修改
<2>其他文件中可以使用相同名字的变量,不会发生冲突。
2. 局部静态变量
在局部变量之前加上关键字static,局部变量就被定义成为一个局部静态变量。
1)内存中的位置:静态存储区
2)初始化:对基本类型的静态局部变量若在说明时未赋以初值,则系统也会自动赋予0值。而对自动变量不赋初值,则其值是不定的
3)作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域随之结束。
注:当static用来修饰局部变量的时候,它就改变了局部变量的存储位置,从原来的栈中存放改为静态存储区。但是局部静态变量在离开作用域之后,并没有被销毁,而是仍然驻留在内存当中,直到程序结束。退出该函数后,尽管该变量还继续存在,但不能使用它。
当static用来修饰全局变量的时候,它就改变了全局变量的作用域(在声明他的文件之外是不可见的),但是没有改变它的存放位置,还是在静态存储区中。
static对栈变量的修饰,可以认为栈变量的生命周期延长到程序执行结束时。一般来说,栈变量的生命周期由OS管理,在退栈的过程中,栈变量的生命也就结束了。但加入static修饰之后,变量已经不在存储在栈中,而是和全局变量一起存储。同时,离开定义它的函数后不能使用,但如再次调用定义它的函数时,它又可继续使用,而且保存了前次被调用后留下的值
另外,静态变量(非全局与全局)只能使用常量表达式初始化,因为它们是在编译时计算其值的。
3. 静态函数
在函数的返回类型前加上关键字static,函数就被定义成为静态函数。
函数的定义和声明默认情况下是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。
定义静态函数的好处:
<1> 其他文件中可以定义相同名字的函数,不会发生冲突
<2> 静态函数不能被其他文件所用。
存储说明符auto,register,extern,static,对应两种存储期:自动存储期和静态存储期。
auto和register对应自动存储期。具有自动存储期的变量在进入声明该变量的程序块时被建立,它在该程序块活动时存在,退出该程序块时撤销。
关键字extern和static用来说明具有静态存储期的变量和函数。用static声明的局部变量具有静态存储持续期(static storage duration),或静态范围(static extent)。虽然他的值在函数调用之间保持有效,但是其名字的可视性仍限制在其局部域内。静态局部对象在程序执行到该对象的声明处时被首次初始化。
static对函数的修饰与对全局变量的修饰相似,只能被本文件中的函数调用,而不能被同一程序其它文件中的函数调用。
C语言中使用静态函数的好处:
- 静态函数会被自动分配在一个一直使用的存储区,直到退出应用程序实例,避免了调用函数时压栈出栈,速度快很多。
- 关键字“static”,译成中文就是“静态的”,所以内部函数又称静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件。使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名,因为同名也没有关系。
const
任何变量的声明都可以使用const限定符限定,该限定符指定变量的值不能被修改。对数组而言,const限定符指定数组所有元素的值都不能被修改(数组类型变量本身也不能被修改,即不能改变它的指向,这与使用final定义的数组的Java是一样的,但内容不能修改这与Java又不一样)。但是,如果const限定符修饰数组参数时,函数中仍然不能修改数组元素的值,但在函数中能让数组重新指向另外的数组,但调用者未改变(因为你在函数中改变了它的指向,当然不会修改原来的内容),如下面程序:
void val(constint a[]) {//这里的 const int a[]相当于const int * a
int b[] = { 1 };
printf("%d ", a[0]);//输出:2
/*
* 可以修改其指向。数组在作为参数传递时,实质上传递的
* 是第一个元素的地址,这里的参数声明好比为 int *a,但
* 如果不是做为参数这样声明时,则是不能够再次让数组变量
* 指向另外一个新的数组
*/
a = b;
// 编译出错:assignment of read-only location '*a'
// a[0] = 3;
printf("%d ", a[0]);//输出:1
}
int main(void) {
int a[] = { 2 };//这里 int a[] 相当于 int * const a
val(a);
printf("%d ", a[0]);//值还是2,没有被修改
int b[] = { 1 };
//! a = b;//不能使用数组变量指向新的数组,但在参数声明时可以
return 1;
}
数组名实际上是一个指向非常量的常量指针。
当遇到形如一维数组 int b[] 的函数参数时,编译器把该参数转换为指针形式 int * b,这两种形式是可以互换的。
int *const cpi=&i,不能修改常量指针cpi的值,该指针总是指向同一位置,但它所指之处的值可以改变。
const int *pci(或者 int const *pci),pci的类型是“指向const int的指针”,pci本身可以被修改以指向另一个地方,但它所指之处的值不能通过pci来改变。
intconst a;
constint a;
上面两个语句等价。
指向常量的常量指针具有最少访问权:
int main(int argc, char * argv[]) {
int x = 5, y;
//指向常量的常量指针
constint * const ptr = &x;
//下面两行编译出错
//!*ptr = 7;
//!ptr = &7;
return 0;
}
指向非常量数据的非常量指针(int * ptr)
指向非常量数据的常量指针(int * const ptr)
指向常量数据的非常量指针(constint * ptr 或int const * ptr)
指向常量数据的常量指针(constint * const ptr 或intconst * const ptr)
在使用函数之前检查一下函数原型,确定该函数是否能够修改传给它的值。
sizeof
sizeof运算符在用于数组名时以整数形式返回该数组所占用的字节数。
sizeof运算符可用于任何变量名、变量类型和常量。
register
register声明告诉编译器,它所声明的变量在程序中使用频率较高,会将它们存放在寄存器中,这样可以使用程序更小,执行速度更快。
register声明只适用于自动变量以及函数的形式参数。
不能对register变量进行取地址操作&
位移规则
左移时右边补0
unsigned类型的无符号值进行右移位时,左边空出的部分将用0填补;当对signed类型的带符号值进行右移时,某些机器对左边空出的位用符号位填补(即“算术移位”),而另一些机器对左边空出的部分用0填补(即“逻辑移位”)。
函数默认返回值
如果函数定义中省略了返回值类型,则默认为int类型。