一、基本数据类型
在C语言中,**仅有4种基本数据类型——整型、浮点型、指针和聚合类型(如数组和结构等)。**所有其他的类型都是从这4种基本类型的某种组合派生而来。
整型分为signed和unsigned,包括char,int,short int,long int。
(当可移植问题比较重要时,字符是否为有符号数就会带来两难的境地。最佳妥协方案就是把存储于char型变量的值限制在signed char和unsigned char的交集内,这可以获得最大程度的可移植性,同时不牺牲效率。并且只有当char型变量显式声明为signed或unsigned时,才对它执行算术运算。)
**signed关键字一般只用于char类型,因为其他整型类型在缺失情况下都是有符号数。**至于char是否为signed则因编译器而异。所以char可能等同于signed char,也可能等同于unsigned char。
**声明为枚举类型的变量实际上是整型。**例如:
enum Jar_Type{CUP, PINT, QUART, HALF_GALLON, GALLON};
浮点型类型有float,double,long double。
浮点数字面值在缺省情况下都是double类型的。
指针只是地址的另一个名字罢了。指针变量就是一个其值为另外一个(一些)内存地址的变量。
指针常量与非指针常量在本质上是不同的,因为编译器负责把变量赋值给计算机内存中的位置,程序员事先无法知道某个特定变量将存储到内存中的哪个位置。因此,你通过操作符获得一个变量的地址而不是直接把它的地址写成字面值常量的形式。因此,把指针常量表达为数值字面值的形式几乎没有用处,所以C语言内部并没有特地定义这个概念(有一个例外,NULL,它可以用零值来表示。)
**C语言存在字符串的概念:它就是一串以NUL字节结尾的零个或多个字符。**字符串通常存储在字符数组中,这也是C语言没有显式的字符串类型的原因。由于NUL字节是用于终结字符串的,所以在字符串内部不能有NUL字节。不过在一般情况下,这个限制并不会造成问题。
例如:
“Hello”,"\aWarning!\a",“Line 1\nLine2”,""
需注意的是,即使是空字符串,依然存在作为终止符的NUL字节。
ANSI C声明如果对一个字符串常量进行修改,其效果是未定义的。因此实践中,请尽量避免这样做,如果需要修改字符串,请把它存储于数组中。
你可以把字符串常量赋值给一个“指向字符的指针”,后者指向这些字符所存储的地址。但是,你不能把字符串常量赋值给一个字符数组,因为字符串常量的直接值是一个指针,而不是这些字符本身。
如果觉得不能赋值或复制字符串显得不方便,标准C函数库提供了一组函数用于操纵字符串。
二、基本生命
C语言中的初始化是,在变量名后面跟一个等号,后面是你想要赋给变量的值。例如:
int j = 15;
为了声明一个数组,在数组名后面要跟一对方括号,方括号里面是一个整数,指定数组中的元素个数。
如果下标值是从那些已知正确的值计算得来,那么久无需检查它的值。如果一个用作下标的值是根据某种方法从用户输入的数据产生而来的,那么在使用它之前必须进行检测,确保它们位于有效的范围内。
声明指针,例如:
int *a;
这条语句表示表达式a产生的结果类型是int。
不允许使用int a;式的指针声明,原因是当时同时为多个指针变量进行声明时,这容易造成误导。正确的多指针变量同时声明形式为:
int *b, *c, *d;
在声明指针变量时也可以为它指定初始值,例如:
int a = 25;
int *ptr = &a;
int b[10];
int *point = b;
int *p = &b[0];
char *message = "Hello world!";
C语言中存在隐式声明:函数如果不显式地声明返回值的类型,则默认认为其返回的是整型。例如:
f(int x)
{
return x+1;
}
依赖隐式声明不是一个好主意,这不同于C++中的auto,因此不要使用。
三、typedef
**C语言支持typedef机制,它允许你为各种数据类型定义新名字。**例如
typedef char *ptr_to_char;
ptr_to_char a;
其表示把ptr_to_char作为指向字符的指针类型的新名字,其声明a是一个指向字符的指针。
使用typedef声明类型可以减少使声明变得又臭又长的危险,尤其是那些复杂的声明(在结构中尤其管用)。而且如果以后要修改程序所使用的一些数据类型时,修改一个typedef声明比修改程序中与这种类型有关的所有变量(和函数)的所有声明要容易的多。
四、常量
ANSI C允许声明常量,例如:
const int a;
由于a的值无法被修改,所以你无法把任何东西赋值给它,因此只能通过初始化来给常量赋值,以用于后续处理。例如:
const int a = 15;
在函数中声明为const的形参在函数被调用时会得到实参的值。
**当涉及指针变量时,情况变得更为有趣,因为有两样东西都有可能成为常量——指针变量和它所指向的实体。**例如:
int const *pci;
其为一个指向整型常量的指针,你可以修改指针的值,但你不能修改它所指向的值。
int * const cpi;
其为一个指向整型的常量指针,此时指针是常量,它的值无法修改,但可以修改它所指向的整型值。
int const * const cpci;
其无论是指针本身还是它所指向的值都是常量,不允许修改。
五、作用域
编译器可以确认4种不同类型的作用域:文件作用域、函数作用域、代码块作用域和原型作用域。标识符声明的位置决定它的作用域。
任何在代码块的开始位置声明的标识符都具有代码块作用域。你应该避免在嵌套的代码块中出现相同的变量名。
任何在所有代码块之外声明的标识符都具有文件作用域,它表示这些标识符从它们的声明之处直到它所在的源文件结尾处都是可以访问的。在文件中定义的函数名也具有文件作用域。
原型作用域只适用于在函数原型中声明的参数名。
函数作用域只适用于语句标签,语句标签用于goto语句。
六、存储类型
关键字register可以用于自动变量(即在代码块内部声明的一般变量,存储于堆栈中)的声明,提示它们应该存储于机器的硬件寄存器而不是内存中,这类变量称为寄存器变量。通常,寄存器变量比存储于内存的变量访问起来效率更高。但是编译器并不一定要理睬register关键字,且如果声明的register变量太多,编译器只选取前几个存储于寄存器中,其余的就按普通自动变量处理。
在有些计算机中,如果把指针声明为寄存器变量,程序的效率将能得到提高,尤其是那些频繁执行间接访问操作的指针。