数据
基本数据类型
C语言包含的基本数据类型:整型、浮点型、指针、聚合类型。
整型
整型包含:字符型、短整型、整型、长整型,它们都分为有符号和无符号两个版本。
标准规定:长整型至少与整型一样长,整型至少与短整型一样长。
limit.h
中说明了不同整型的特点:最小值、最大值。缺省的
char
类型,在不同的编译器下解释不同,既可以被解释为unsigned char
,也可以被解释为signed char
,只有程序中使用char
类型的值是unsigned char
和signed char
的交集时,程序才可以移植。
字面值
字面值是一种实体,指定了自身的值,并且不允许发生改变。
数据的表示分为十进制、八进制(数字前面加0)、十六进制(数字前面加0x)。
整数字面值添加后缀
L
或者l
,整数将解释为long
类型。整数字面值添加后缀
U
或者u
,整数将解释为unsigned
类型。在一个多字节的字符常量前面加
L
,它就是宽字符常量。
枚举类型
枚举类型就是指它的值为符号常量,而不是字面值常量。
枚举的符号名都是整型,默认第一个符号名是0。
只对部分符号名赋值是合法的,如果没有显示指定符号名的值,默认它比前面一个符号名大1。
浮点型
浮点型包含:
float
、double
、long double
。标准规定:
long double
至少和double
一样长,double
至少和float
一样长。float.h
中定义了float
、double
、long double
等的最大值、最小值。浮点数字面值缺省情况下都是
double
类型,字面值后缀L
或者l
表示它是一个long double
,后缀F
或f
表示一个float
。
基本声明
声明整型变量时,如果已经有了一个其他的说明符,关键字int
可以省略,例如下面的定义都是等效的:
不省略int | 省略int |
---|---|
signed short int | signed short |
unsigned short int | unsigned short |
signed int | signed |
unsigned short int | unsigned short |
signed long int | signed long |
unsigned long int | unsigned long |
typedef
typedef
可以为各种数据类型定义新名字。
使用typedef
而不能使用#define
来创建新的类型名,因为后者不能正确的处理指针:
#define d_ptr_to_char char*
d_ptr_to_char a,b; // a是一个字符指针,b是一个字符
常量
常量的值不允许被修改,常量使用const
关键字修饰。
常量初始化的两种方式:
定义时直接赋给初值。
const
修饰函数参数,则在函数调用时得到实参的值。
const与指针
int const *pi; // pi是指针常量,可以修改指针的值,但是不能通过指针修改指针指向的值。
int *const pi; // pi是常量指针,此时指针是常量,它的值不允许修改,但是可以通过指针修改其所指向的值。
int const *const pi; // 指针本身和指针指向的值都是常量。
作用域
标识符的作用域是指程序中标识符可以被使用的区域。
标识符声明的位置决定了它的作用域。
编译器可以确认四种作用域:文件作用域、函数作用域、代码块作用域、原型作用域。
代码块作用域
一对花括号括起来的语句称为一个代码块,任何在代码块开始位置声明的标识符都是具有代码块作用域。
代码块嵌套时,内层代码块如果存在和外层代码块一样的标识符,内层的标识符将隐藏外层的标识符。
K&R C
中规定函数形参开始于形参的声明处,位于函数体之外,如果函数体内部声明了与形参同名的局部标识符,那么形参就会被隐藏,如此一来,形参便无法被函数的任何部分访问。ANSI C
为了避免这种错误的可能性,它把形参的作用域设置为函数最外层的作用域(也就是整个函数体),这样,声明于函数最外层作用域的局部变量无法和形参同名,因为它们的作用域相同。
文件作用域
在代码块之外声明的标识符都具有文件作用域。
文件作用域表示从它们声明之处开始,直到它所在的源文件末尾都是可以访问的。
在头文件中编写,并且
#include
包含到其它文件中的声明,就好像它们直接写在那些文件中一样,它们的作用域不局限在头文件的末尾。
原型作用域
原型作用域只适用于函数原型中声明的参数名。
函数原型中,参数的名字是非必须的。
唯一可能与原型作用域发生标识符冲突的就是在同一个原型中不止一次的使用同一个名字。
函数作用域
函数作用域只适用于语句标签,语句标签用于
go to
语句。一个函数的所有标签必须唯一。
链接属性
标识符的链接属性决定如何处理在不同文件中出现的标识符。
链接属性有三种:
external
(外部)、intenal
(内部)、none
(无)。没有链接属性的标识符总是被当作单独的个体,也就是说该标识符的多个声明被当作独立的不同实体。
属于内部链接属性的标识符在同一个源文件内的所有声明都指向一个实体,但是位于不同源文件的多个声明则分属于不同的实体。
外部链接属性的标识符,无论声明多少次、位于几个源文件中都表示同一个实体。
extern
和static
两个关键字可以用于设定标识符的链接属性。当没有这两个关键字时,默认的链接属性与标识符的作用域相关。
typedef char *a;
int b;
int c(int d)
{
int e;
int f(int g);
...
}
b
、c
、f
有external
链接属性,f在本代码中被调用,定义在其他源文件或者库中,所以也是external
属性。其他的标识符都是none
属性。
static
关键字可以把一个默认为external
属性的标识符改为internal
,如上例中,可以把b
、c
的链接属性改为internal
,使其在其他源文件中不可见:
static int b;
static int c(int d);
extern
关键中可以把none
属性改为external
属性:
// linkage_test1.c
#include<stdio.h>
extern int a; // 可选,因为默认就是external,但是应该写上,增加程序可读性
int main() {
printf("a = %d\n", a);
extern int b; // 必需,默认为none
printf("b = %d\n", b);
}
当extern
关键字用于源文件中一个标识符的一次声明时,它指定该标识符具有external
链接属性,但是,如果它用于该标识符的第2次或者以后的声明时,它并不会更改由第一次声明所指定的链接属性。如下例所示:
static int i;
int func()
{
extern int i; //i的链接属性仍然为static
}
存储类型
变量的存储类型是指存储变量值的内存类型。变量的存储类型决定变量何时创建、何时销毁以及它的值将保持多久。
有三个地方可以用于存储变量:普通内存、运行时堆栈、硬件寄存器。
普通内存变量
凡是在任何代码块之外声明的变量总是存储与静态内存中,这类变量成为静态变量。
可以通过
static
关键字将一个代码块内部变量由堆栈类型变为静态类型。静态类型的标识符存在ELF文件的
.data
(已初始化)或者.bss
段(未初始化,默认值为0)。这些变量在程序未运行之前(通过内存加载)已经存在。
堆栈变量
在代码块内部声明的变量的默认存储类型是自动的(
automatic
),可以使用关键字auto指定,但它极少使用,因为完全没必要。自动变量存在堆栈中。
硬件寄存器变量
可以通过关键字
register
来指定,提示程序运行时用硬件寄存器来存储该变量。编译器可能会忽略
register
关键字,采用自己具有的一套优化方法。
初始化
变量的初始化
静态变量如果不显示初始化,则默认初始化为0。
自动变量可以通过任何合法表达式初始化,因为它是在运行时创建,如果不初始化,其默认值为垃圾。