第2章 C语言概述
2.1 数据类型
数据类型是一个值的集合以及定义在这个值集上的一组操作。变量是用来存储值的所在处,它们有名字和数据类型,变量的数据类型决定了如何将代表这些值的位存储到计算机的内存中。在声明变量时也可以指定它的数据类型。所有的变量都具有数据类型,以决定其能够存储哪种数据。
C 语言的 C99 标准中,数据类型可以分为基本数据类型、构造类型、指针类型和其他类型。其中,基本数据类型包括整型、浮点型、字符型和枚举型;构造类型包括数组类型、结构类型和联合类型;其他类型包括空类型和布尔类型。数据类型的分类如图2-1所示。
图2-1 C99标准中的数据类型分类
2.1.1 基本数据类型
基本数据类型包括整型、浮点型、字符型和枚举型,它们有着不同的精度和广度,可以根据自己的需要选择合适的类型。
1. 整型
整型在C语言中是用来表示整数的,主要用关键字int表示。为了满足不同的需要,整型按照可表示的范围分为以下几类:整型(或者叫一般整型)、短整型、长整型。短整型用来表示较小范围的数,长整型用来表示较大范围的数。这就好比要装水得有瓢、水桶或缸一样,都是用来装水的,但是分别用于不同的用途。对于不同的整型,在 C 语言中使用如下不同的符号表示。
整型:使用int关键字表示。
短整型:使用short int关键字表示,简写为short。
长整型:使用long int关键字表示,简写为long。
整数分为正整数、负整数和零。有时需要表示的整数既可以是正的也可以是负的,有时只需要表示正的整数。就像我们表示人民币的时候用的都是正数或者零元,表示温度的时候既可以是负一度,也可以是正一度。为了满足上面的需求,又可以将整型分为有符号的和无符号的。有符号整型可以表示正整数、零和负整数,使用 signed 符号表示;无符号整型只可以表示正整数和零,使用unsigned符号表示。
因此,3种整型分别和有符号、无符号组合,总共可分为6种,如表2-1所示。
表2-1 整型的分类
从表 2-1 中可以看出长整型和整型是一样的,其实对于不同的系统,这个范围是不一样的,长整型会比整型的范围大,但是现在一般系统都把它们定义为一样的。
2. 浮点型
浮点型在 C 语言中主要用来表示小数,分为单精度浮点类型和双精度浮点类型。它们的区别仅仅是表示的精度不一样,双精度的表示精度会更细些,并且表示的范围也会更大。单精度浮点类型使用 float 关键字表示;双精度浮点类型使用 double float 表示,简写为double。浮点型没有有符号和无符号的区别,都是有符号的。下面列表加以比较,如表 2-2所示。
表2-2 浮点型的分类
单精度浮点型占 4 字节(32 位)内存空间,只能提供 7 位有效数字。双精度浮点型占8字节(64位)内存空间,可提供16位有效数字。
3. 字符型
字符类型用于存储字符,如英文字母或标点,用关键字 char 表示。严格来说,字符类型其实也是整数类型,因为字符类型存储的实际上是整数,而不是字符。计算机使用特定的整数编码来表示特定的字符。我们普遍使用的编码是 ASCII(American Standard Code For Information Interchange,美国信息交换标准编码)。例如,ASCII 使用 65 来代表大写字母A,因此存储字母A实际上存储的是整数65。
ASCII的范围是0~127,因而7位就足以表示全部的ASCII。字符类型一般占用8位内存单元,表示ASCII绰绰有余。许多字符集超出了8位所能表示的范围(如汉字字符集),使用这种字符集作为基本字符集的系统中,字符类型可能是16位的,甚至可能是32位的。总之,C语言保证字符类型占用空间的大小足以存储系统所用的基本字符集的编码。C语言定义1字节的位数为char的位数,所以1字节可能是16位,也可能是32位,而不仅仅限于8位。
4. 枚举型
在实际问题中,有些变量的取值被限定在一个有限的范围内。例如,一个星期只有 7天,一年只有 12 个月,一个班每周只有 6 门课程,等等。如果把这些量说明为整型、字符型或其他类型显然是不妥当的。为此,C语言提供了一种称为“枚举”的类型。在“枚举”类型的定义中列举出所有可能的取值,被说明为该“枚举”类型的变量取值不能超过定义的范围。应该说明的是,枚举类型是一种基本数据类型,而不是一种构造类型,因为它不能再分解为任何基本类型。
枚举类型定义的一般形式为:
enum 枚举名 { 枚举值表 };
在枚举值表中应罗列出所有可用值。这些值也称为枚举元素。例如:
enum weekday { sun, mou, tue, wed, thu, fri, sat };
该枚举名为weekday,枚举值共有7个,即一周中的7天。凡被说明为weekday类型变量的取值只能是7天中的某一天。
枚举值是常量,不是变量,不能在程序中用赋值语句再对它赋值。例如,对枚举weekday 的元素再进行以下赋值:sun=5,mon=2,sun=mon,这类做法都是错误的。枚举元素本身由系统定义了一个表示序号的数值,从 0 开始顺序定义为 0,1,2,…。如在weekday中,sun的值为0,mon的值为1,…,sat的值为6。
只能把枚举值赋予枚举变量,不能把元素的数值直接赋予枚举变量。如“enum weekday a=sum,b=mon;”是正确的,而“enum weekday a=0, b=1;”是错误的。如果一定要把数值赋予枚举变量,则必须用强制类型转换,如“enum weekday a=(enum weekday)2;”,其意义是将顺序号为2的枚举元素赋予枚举变量a,相当于“enum weekday a=tue;”。还应该说明的是枚举元素不是字符常量也不是字符串常量,使用时不要加单、双引号。
2.1.2 指针类型
指针是 C 语言中的一个重要角色,可以说是 C 语言的精华,它的概念非常复杂,使用也非常灵活,是C语言中最大的难点之一。
所谓变量的指针,实际上是指变量的地址。变量的地址虽然在形式上类似于整数,但在概念上却不同于以前介绍过的整数,它属于一种新的数据类型,即指针类型。在 C 语言中,一般用“指针”来指明一个表达式“&x”的类型,而用“地址”作为它的值,也就是说,若 x 为一个整型变量,则表达式“&x”的类型是指向整型的指针,而它的值是变量 x的地址。同样,若 d 为一个 double 类型的变量,则“&d”的类型是指向 double 类型的指针,而“&d”的值是 double 变量 d 的地址。所以,指针和地址用来叙述一个对象的两个方面。虽然“&x”、“&d”的值分别是整型变量 x 和 double 变量 d 的地址,但“&x”、“&d”的类型是不同的,一个是指向整型变量x的指针,而另一个是指向double变量d的指针。
指针变量的定义用下面的形式:
类型标志符 *标志符;
例如:
int *pointer_1;
上述语句定义了一个指针变量pointer_1,它指向整型变量。
如果还定义了一个整型变量 i,则可以用复制语句将一个指针变量指向一个整型变量的地址。例如:
pointer_1=&i;
其中,“&”是C语言中的取址符,即“&i”将返回变量i的地址。
这里只做简要的介绍,如果想要更深层次地了解指针,可以查阅有关方面的书籍。最后,以一个程序来结束指针的学习。
/* pointer.c */ #include <stdio.h> int main(void) { int a,b; int *pointer_1,*pointer_2; a = 10; b = 100; pointer_1 = &a; pointer_2 = &b; printf("a = %d\nb = %d\n", a, b); printf("&a = %p\n&b = %p\n", pointer_1, pointer_2); printf("*pointer_1 = %d\n*pointer_2 = %d\n", *pointer_1, *pointer_2); return 0; }
程序运行的结果为:
a = 10 b = 100 &a = 0xbfc0de8c &b = 0xbfc0de88 *pointer_1 = 10 *pointer_2 = 100
2.1.3 构造类型
构造类型是指由若干个相关的数据组合在一起形成的一种复杂数据类型,构造数据类型的各个成分数据可以是基本数据类型的,也可以是别的构造类型的。按构造方式和构造要求区分,构造类型主要有数组类型、结构类型和共用类型。数组类型是由相同类型的数据组成的;结构类型可以由不同类型的数据组成;当不同数据类型不会同时使用时,为节约内存,让不同数据占用同一区域,这就是共用类型。
1. 数组类型
数组是有序数据的集合,数组中的每一个元素都属于同一数据类型,用一个统一的数组名和下标来唯一地确定数组中的元素。
(1)一维数组
一维数组的定义格式如下:
类型说明符 数组名[常量表达式] …;
例如:
int a[5];
数组名为a,且有5个元素,下标为0~4(注意不是从1开始,故最后一个元素是a[4]而不是a[5]),即a[0]~a[4]。
注意,常量表达式不能是变量。
一维数组的初始化列举如下:
int a[5] = {0, 1, 2, 3, 4};
则 a[0]=0,a[1]=1,a[2]=2,a[3]=3,a[4]=4。如果花括号中值的个数少于 5,则后面的数组元素都将初始化为0。
一维数组的引用:只能逐个引用数组元素而不能引用整个数组。
数组名[下标]
下面简单用代码来演示一下数组的定义与引用。
#include <stdio.h> int main(void) { int i,a[5]; int b[5]={5,6,7,8,9}; for(i=0; i<5; i++) a[i] = i; for(i=4; i>=0; i--) { printf("a[%d] = %d\t", i, a[i]); printf("b[%d] = %d\n", i, b[i]); } return 0; }
运行结果是:
a[4]=4 b[4]=9 a[3]=3 b[3]=8 a[2]=2 b[2]=7 a[1]=1 b[1]=6 a[0]=0 b[0]=5
(2)二维数组
二维数组的定义格式如下:
类型说明符 数组名[常量表达式1][常量表达式2] … ;
其中,常量表达式1表示第一维下标的长度,常量表达式2表示第二维下标的长度。
例如:
int a[3][4];
定义了一个三行四列的数组,数组名为 a,其下标变量的类型为整型。该数组的下标变量共有3×4个,即:
a[0][0],a[0][1],a[0][2],a[0][3] a[1][0],a[1][1],a[1][2],a[1][3] a[2][0],a[2][1],a[2][2],a[2][3]
二维数组的初始化也是在类型说明时给各下标变量赋以初值。二维数组可按行分段赋值,也可按行连续赋值。例如,对数组a[5][3]:
按行分段赋值可写为:
static int a[5][3]={ {80,75,92},{61,65,71},{59,63,70},{85,87,90},{76,77,85} };
按行连续赋值可写为:
static int a[5][3]={ 80,75,92,61,65,71,59,63,70,85,87,90,76,77,85 };
这两种赋初值的结果是等价的。
对于二维数组初始化赋值,还有以下说明。
可以只对部分元素赋初值,未赋初值的元素自动取0值。例如:
static int a[3][3]={{1},{2},{3}};
是对每一行的第一列元素赋值,未赋值的元素取 0 值。赋值后各元素的值为:1、0、0、2、0、0、3、0、0。
static int a [3][3]={{0,1},{0,0,2},{3}};
赋值后的元素值为:0、1、0、0、0、2、3、0、0。
若对全部元素赋初值,则第一维的长度可以不给出。例如:
static int a[3][3]={1,2,3,4,5,6,7,8,9};
可以写为:
static int a[][3]={1,2,3,4,5,6,7,8,9};
例如,要在屏幕上直接输出如下数据,可以使用二维数组。
1 2 2 2 2 3 1 2 2 2 3 3 1 2 2 3 3 3 1 2 3 3 3 3 1
程序实例如下:
#include <stdio.h> void main(void) { int i,j; int a[5][5]; for(i = 0; i < 5; i++) { for(j = 0; j < 5; j++) { if(i == j) a[i][j] = 1; else if(i < j) a[i][j] = 2; else a[i][j] = 3; } } for(i = 0; i < 5; i++) { for(j = 0; j<5; j++) printf("%d ", a[i][j]); printf("\n"); } }
2. 结构体类型
在实际问题中,一组数据往往具有不同的数据类型。例如,在学生登记表中,姓名应为字符型,学号可为整型或字符型,年龄应为整型,性别应为字符型,成绩可为整型或实型。显然不能用一个数组来存放这一组数据。而数组中各元素的类型和长度都必须一致,以便于编译系统处理。为了解决这个问题,C语言中给出了另一种构造数据类型——结构体。它相当于其他高级语言中的记录。
下面是定义一个结构体类型的一般形式:
struct 结构体名 { 成员列表 }变量名列表;
对各成员都应进行类型说明,例如:
类型标志符 成员名;
下面是一个具体实例:
struct student { int num; char name[20]; char sex; int age; float score; char addr[30]; } student1={"33", "zhanwei", "m", "22", "75", "32#307"};
该实例定义了一个struct student类型的变量student1,它包括num、name、sex、age、score、addr六个不同类型的数据项,各项初值分别为33、zhanwei、m、22、75、32#307。
3. 共用体类型
所谓共用体(或称联合体)类型是指将不同的数据项组织成一个整体,它们在内存中占用同一段存储单元,这样在某些特殊的应用场合有利于节省存储空间。
共用体类型变量的定义形式如下:
union 共用体名 { 成员列表 }变量列表;
例如:
union data { int i; char ch; float f; }a, b, c;
上面先定义了一个共用体类型,然后定义了a、b、c三个共用体变量。变量a的成员引用方式为:a.i,a.ch,a.f。需要注意的是,不能同时引用这三个成员,在某一时刻,只能引用成员之一。
2.1.4 其他类型
1. 空类型
空类型常应用于指针和函数。
(1)void * ——空缺具体类型的指针
int *p = NULL; void *q = NULL; q=p; //right p=q; //error
例如,p是指向int类型的指针,q是指向空类型(void)的指针。可以把p赋值给q,让q指向int类型,因为q的属性是可以指向任意类型的;但是不可以把q赋值给p,让p指向任意类型,因为 p 的属性规定其只能指向 int 类型。所以,void 不能定义非指针变量(如果定义,则不知道分配多大的空间给变量),然而 void* 能直接定义指针变量(因为指针变量所占空间大小是固定的,如果定义,则知道分配多大的空间给指针变量)。
(2)voidfunc(void) ——无返回值/无参数的函数类型
voidfunc(void) { function_body return; }
对于不需要参数或返回值的函数,可以在需要填写函数参数或返回值的地方用空类型(void)表示“没有参数”或“没有返回值”,当然也可以不填,这样编译器会将其等价为空类型(void)来处理。尽管是无返回值的函数,但最好还是在函数体内结尾处使用 return 语句,以保证函数调用后能有效返回。
2. 布尔类型
C语言将非零整数都认为是“真”,将零认为是“假”,布尔类型则只有“真”、“假”两个值。在表达式中,布尔量也作为整数处理,整数也可以出现在布尔表达式中。布尔类型用关键字bool(或_Bool)表示,要用到头文件stdbool.h。
下面的代码演示了布尔类型的定义与引用方法。
#include <stdio.h> #include <stdbool.h> void main(void) { bool flag=true; if (flag) printf("真:flag = %d\n", flag); else printf("假:flag = %d\n", flag); return; }
运行结果如下:
真:flag = 1