第三章 数据和C

GitHub地址,欢迎 star

3.1 示例程序

本章仍以示例程序开始。如果发现有不熟悉的地方,没关系,我们稍后会详细介绍。

#include <stdio.h>

int main(void) {
    float weight; /* 你的体重 */
    float value; /* 相等重量的白金价值 */
    
    printf("Are you worth your weight in platinum?\n");
    printf("Let's check it out.\n");
    printf("Please enter your weight in pounds: ");
    /* 获取用户的输入 */
    scanf("%f", &weight);
    /* 假设白金的价格是每盎司$1700 */
    /* 14.5833 用于把英镑常衡盎司转换为金衡盎司 */
    value = 1700.0 * weight *14.5833;
    printf("Your weight in platinum is worth $%.2f.\n", value);
    printf("You are easily worth that! aif platinum prices drop,\n");
    printf("eat more to maintain your value.\n");
    
    return 0;
}

注意:“enter your weight“ 的意思是让你输入完体重后,按下ENTERRETURN键。按下ENTER键是告知计算机,你已经完成输入数据。该程序需要你输入一个数字,而不是单词。如果输入字母而不是数字,会导致程序出问题。下面是程序的输出示例:
Are you worth your weight in platinum?
Let’s check it out.
Please enter your weight in pounds: 156
Your weight in platinum is worth $3867491.25.
You are easily worth that! aif platinum prices drop,
eat more to maintain your value.

3.1.1 程序中的新元素

  • 注意,代码中使用了一种新的变量声明。浮点数类型(float)变量。
  • 为了打印新类型的变量,在 printf() 中使用 %f 来处理浮点值。%.2f 中的 .2 用于精确控制输出,指定输出的浮点数只显示小数点后面两位。
  • scanf() 函数用于读取键盘的输入。%f 说明 scanf() 要读取用户从键盘输入的浮点数,&weight 告诉 scanf() 把输入的值赋给名为 weight 的变量。scanf() 函数使用 & 符号表明找到 weight 变量的地点(内存地址)。
  • 本程序最突出的新特点是它的交互性。计算机向用户询问信息,然后用户输入数字。与非交互式程序相比,交互式程序用起来更有趣。更重要的是,交互式使得程序更加灵活。

3.2 变量与常量数据

在程序的指导下,计算机可以做许多事情。要完成这些任务,程序需要使用数据,即承载信息的数字和字符。有些数据类型在程序使用之前已经预先设定好了,在整个程序的运行过程中没有变化,这些称为常量(constant)。其他数据类型在程序运行期间可能会改变或被赋值,这些称为变量(variable)

3.3 数据:数据类型关键字

不仅变量和常量不同,不同的数据类型之间也有差异。一些数据类型表示数字,一些数据类型表示字母(更普遍地说是字符)。
在C语言中,用 int 关键字来表示基本的整数类型。char 类型也可以表示较小的整数。float、double 和 long double 表示带小数点的数。_Bool 类型表示布尔值(true 和 false),_Complex 和 _Imageinary 分别表示复数和虚数。

位、字节和字

位、字节和字是描述计算机数据单元或存储单元的术语。这里主要指存储单元。
最小的存储单元是位(bit),可以储存 0 或 1(或者说,位用于设置“开“或“关“)。虽然 1 位储存的信息有限,但是计算机中位的数量十分庞大。位是计算机内存的基本构建块。
字节(byte)是常用的计算机存储单位。对于几乎所有的机器,1 字节均为 8 位。这是字节的标准定义,至少在衡量存储单位时是这样。既然 1 位可以表示 0 或 1,那么 8 位字节就有 256 种可能的 0、1 的组合。通过二进制编码,便可表示 0~255 的整数或一组字符。
字(word)是设计计算机时给定的自然存储单位。对于 8 位的微型计算机,1 个字长只有 8 位。从那之后,个人计算机字长增至 16 位、32位,直到目前的64位。计算机的字长越大,其数据转移越快,允许的内存访问也更多。

3.3.1 整数

和数学一样,整数是没有小数部分的数。计算机以二进制数字储存整数。

3.3.2 浮点数

浮点数与数学中的实数差不多。注意,在一个值后面加上一个小数点,该值就成为一个浮点值。所以,7 是整数,7.00 是浮点数。显然,书写浮点数有多种形式。
这里关键有理解浮点数和整数的储存方案不同。计算机把浮点数分成小数部分和指数部分来表示,而且分开储存这两部分。因此,虽然 7.00 和 7 在数值上相同,但是它们的储存方式不同。在十进制下,可以把 7.0写成0.7E1。这里,0.7 是小数部分,1 是指数部分。现在,我们着重讲解这两种类型的实际区别。

  • 整数没有小数部分,浮点数有小数部分。
  • 浮点数可以表示的范围比整数大。
  • 对于一些算术运算(如,两个很大的数相减),浮点数损失的精度更多。
  • 浮点数通常只是实际值的近似值。例如,7.0 可能被储存为浮点值 6.9999。
  • 过去,浮点运算比整数运算慢。不过,现在许多CPU都包含浮点处理器,缩小来速度上的差距。

3.4 C语言基本数据类型

3.4.1 int 类型

int 类型是有符号整型,即 int 类型的值必须是整数,可以是正整数、负整数或零。

1、声明 int 变量

int erns;
int hogs, cows, goats;

2、初始化变量
初始化(initialize) 变量就是为变量赋一个初始值。

int hogs = 21;
int cows = 32, goats = 14;
int dogs, cats = 94; /* 有效,但是这种格式很糟糕 */

3、int 类型常量
上面示例中出现的整数(21、32、14和94)都是整型常量或整型字面量。C语言把不含小数点和指数的数作为整数。

4、打印 int 值
可以使用 printf() 函数打印 int 类型的值。%d 指明了在一行中打印整数的位置。%d 称为转换说明,它指定了 printf() 应使用什么格式来显示一个值。格式化字符中的每个 %d 都与待打印变量列表中相应的 int 值匹配。这个值可以是 int 类型的变量、int 类型的常量或其他任何值为 int 类型的表达式。作为程序员,要确保转换说明的数量与待打印值的数量相同,编译器不会捕捉这类型的错误。

#include <stdio.h>

int main(void) {
    int ten = 10;
    int two = 2;
    
    printf("Doing it right: ");
    printf("%d minus %d is %d\n", ten, 2, ten - two);
    printf("Doing it wrong: ");
    printf("%d minus %d is %d\n", ten); // 遗漏 2 个参数
    
    return 0;
}

编译并运行该程序,输出如下:
Doing it right: 10 minus 2 is 8
Doing it wrong: 10 minus 1027 is -11861900
在第一行输出中,第1个 %d 对应 int 类型变量 ten;第2个 %d 对应 int 类型常量2;第3个 %d 对应 int 类型表达式 ten - two 的值。在第二行输出中,第1个 %d 对应 ten 的值,但是由于没有给后两个 %d 提供任何值,所以打印出的值是内存中的任意值。

5、八进制和十六进制
通常,C语言都假定整型常量是十进制。然而,许多程序员很喜欢使用八进制和十六进制数。因为 8 和 16 都是 2 的幂,而 10 却不是。显然,八进制和十六进制记数系统在表达与计算机相关的值时很方便。例如,十进制数 65536 用十六进制表示正好是 10000。另外,十六进制数的每一位的数恰好由 4 位二进制数表示。例如,十六进制数 3 是 0011,十六进制数 5 是 0101。因此,十六进制数 35 的位组合(bit pattern)是 00110101,十六进制数 53 的位组合是 01010011。这种对应关系使得十六进制和二进制的转换非常方便。在C语言中,用特定的前缀表示使用哪种进制。0x 或 0X 前缀表示十六进制值,所以十进制数 16 表示成十六进制是 0x10 或 0X10。与此类似, 0 前缀表示八进制。例如,十进制数 16 表示成八进制是 020。
要清楚,使用不同的进制数是为了方便,不会影响数被储存的方式。因为计算机内部都以二进制进行编码。

6、显示八进制和十六进制
在C程序中,既可以使用和显示不同进制的数。不同的进制要使用不同的转换说明。以十进制显示数字,使用 %d ;以八进制显示数字,使用 %o ;以十六进制显示数字,使用 %x 。另外,要显示各进制数的前缀0、0x、和0X,必须分别使用 %#o%#x%#X。如下演示重新:

#include <stdio.h>

int main(void) {
    int x = 100;
    
    printf("dec = %d; octal = %o; hex = %x\n", x, x, x);
    printf("dec = %d; octal = %#o; hex = %#x\n", x, x, x);
    return 0;
}

编译并运行该程序,输出如下:
dec = 100; octal = 144; hex = 64
dec = 100; octal = 0144; hex = 0x64
该程序以3中不同记数系统显示同一个值。printf() 函数做了相应的转换。注意,如果要在八进制和十六进制值前显示 00x 前缀,要分别在转换说明中加入 #

3.4.2 其他整数类型

初学C语言时,int 类型应该能满足大多数程序的整数类型需求。尽管如此,还应该了解以下整型的其他形式。
C语言提供3个附属关键字修饰基本整数类型:short、long和unsigned。应记住以下几点:

  • short int 类型(或者简称为 short)占用的存储空间可能比 int 类型少,常用于较小数值的场合以节省空间。与 int 类似,short 是有符号类型。
  • long int 或 long 占用的存储空间可能比 int 多,适用于较大数值的场合。与 int 类似,long 是有符号类型。
  • long long int 或 long long占用的储存空间可能比 long 多,使用于更大数值的场合。该类型至少占 64 位。与 int 类似,long long 是有符号类型。
  • unsigned int 或 unsigned 只用于非负数的场合。这种类型与有符号类型表示的范围不同。例如,16 位unsigned int 允许的取值范围是 0 ~ 65535,而不是 -32768 ~ 32768。用于表示正负号的位现在用于表示另一个二进制位,所以无符号整数可以表示更大的数。
  • C90 标准中,添加了 unsigned long int 或 unsigned long 和 unsigned short int 或 unsigned short 类型。C99 标准有添加了 unsigned long long int 或 unsigned long long。
  • 在任何有符号类型前面添加关键字 signed,可强调使用有符号类型的意图。例如,short、short int、signed short、signed short int 都表示同一种类型。

1、声明其他整数类型
其他整数类型的声明方式与 int 类型相同,下面列出了一些例子。不是所有的C编译器都能识别最后3条声明,最后一个例子所有的类型是 C99 标准新增的。

long int estine;
long johns;
short int erns;
short ribs;
unsigned int s_count;
unsigned players;
unsigned long headcount;
unsigned short yesvotes;
long long ago;

2、使用多种整数类型的原因
为什么说 short 类型 “可能“ 比 int 类型占用的空间少,long 类型 “可能“ 比 int 类型占用的空间多?因为 C 语言只规定了 short 占用的存储空间不能多于 int,long 占用的存储空间不能少于 int。这用规定是为了适应不同的机器。例如,过去的一台运行 Windows 3 的机器上,int 类型和 short 类型都占 16 位,long 类型占 32位。后来,Windows 和 苹果 系统都是用 16 位储存 short 类型,32 位储存 int 类型和 long 类型(使用32位可以表示的整数数值超过20亿)。现在,计算机普遍使用 64 位处理器,为了储存 64 位的整数,才引入了 long long 类型。
int 类型那么多,应该如何选择?首先,考虑 unsigned 类型。这种类型的数常用于计数,因为计数不用负数。而且,unsigned 类型可以表示更大的正数。

**3、long 常量和 long long 常量
通常,程序代码中使用的数字都被储存位 int 类型。如果使用 1000000 这样的大数字,超出了 int 类型能表示的范围,编译器会将其视为 long int 类型。如超出了 int 类型能表示的范围,编译器会将其视为unsigned long 类型。如果还不够大,编译器则将其视为 long long 或 unsigned long long 类型。

整数溢出
如果整数超出了相应的取值范围会怎样?下面分别将有符号类型和无符号类型的整数设置为比最大值略大,看看会发生什么( printf() 函数使用 %u 说明显示 unsigned int 类型的值)。

#include <stdio.h>
int main(void){
   int i = 2147483647;
   unsigned int j = 4294967295;
   printf("%d %d %d\n", x, x + 1, x + 2);
   printf("%u %u %u\n", j, j + 1, j + 2);
   return 0;
}

在我们的系统输出的结果是:
2147483647 -2147483648 -2147483647
4294967295 0 1
可以把无符号整数 j 看作是汽车的里程表。当达到它能表示的最大值时,会重新从起点开始。整数 i 也是类似的情况。他们主要的区别是,在超过最大值时,unsigned int 类型的变量 j 从 0 开始;而 int 类型的变量 i 则从 -2147483648 开始。注意,当 i 超出(溢出)其相应类型所能表示的最大值时,系统并未通知用户。因此,在编程时必须自己注意这类问题。
溢出行为是为定义的行为,C标准并未定义有符号类型的溢出规则。以上描述的溢出行为比较有代表性,但是也可能会出现其他情况。

4、打印 short、long、long long 和 unsigned 类型

打印 unsigned int 类型的值,使用 %u 转换说明;打印 long 类型的值,使用 %ld 转换说明。如果系统中 int 和 long 的大小相同,使用 %d 就行。在 x 和 o 前面可以使用 l 前缀,%lx 表示以十六进制格式打印 long 类型整数,%lo 表示以八进制格式打印 long 类型整数。注意,虽然C允许使用大写或小写的常量后缀,但是在转换说明中只能使用小写。

C语言中有多种 printf() 格式,对于 short 类型,可以使用 h 前缀。%hd 表示以十进制显示 short 类型的整数,%ho 表示以八进制显示 short 类型的整数。h 和 l 前缀都可以和 u 一起使用,用于表示无符号类型。例如,%lu 表示打印 unsigned long 类型的值。对于支持 long long 类型的系统,%lld 和 %llu 分别表示符号和无符号类型。

#include <stdio.h>

int main(void) {
    unsigned int un = 3000000000;
    short end = 200;
    long big = 65537;
    long long verybig = 12345678908642;
    
    printf("un = %u and not %d\n", un , un);
    printf("end = %hd and %d\n", end, end);
    printf("big = %ld and not %hd\n", big, big);
    printf("very = %lld and not %ld\n", verybig, verybig);
    return 0;
}

在特定的系统中输出如下(输出的结果可能不同):
un = 3000000000 and not -1294967296
end = 200 and 200
big = 65537 and not 1
very = 12345678908642 and not 12345678908642

提示 匹配 printf() 说明符的类型
在使用 printf()函数时,切记检查每个待打印值都有对应的转换说明,还有检查转换说明的类型是否与待打印值的类型相匹配。

3.4.3 使用字符;char 类型

char 类型用于储存字符,但是从技术层面看,char 是整数类型。因为 char 类型实际上储存的是整数而不是字符。计算机使用数字编码来处理字符,计时特定的整数表示待定的字符。
C语言把 1 字节定义为 char 类型占用的位(bit)数,因此无论是 16 位还是 32 位系统,都可以使用 char 类型。

1、声明 char 类型变量
char 类型变量的声明方式与其他类型变量的声明方式相同。下面是一些例子:

char response;
char itable, latan;

2、字符常量和初始化
在C语言中,用单引号括起来的单个字符被称为字符常量( character constant )。单引号必不可少。

char broiled; /* 声明一个 char 类型的变量 */
broiled = 'T'; /* 为其赋值,正确 */
broiled = T; /* 错误!此时 T 是一个变量 */
broiled = "T"; /* 错误!此时 "T" 是一个字符串 */

实际上,字符是以数值形式储存的,所以也可使用数字代码值来赋值:
char grade = 65; /* 对于 ASCII,这样做没问题,但这是一种不好的编程风格 */
在本例中,虽然 65 是 int 类型,但是它也在 char 表示的范围内,所以将其赋值给 grade 没问题。但是由于,65 是字母 A 对应的 ASCII 码,一次本例实际上是把 A 赋给了 grade。注意,这样做的前提是系统使用 ASCII 码。其实,用 ‘A’ 代替 65 才是比较妥当的做法,这样在任何系统中都不会出现问题。

3、非打印字符
单引号只适用于字符、数字和标点符号,浏览 ASCII 表会发现,有些 ASCII 字符打印不出来。
第1中方法前面介绍过——使用 ASCII 码。例如,蜂鸣字符的 ASCII 值是7,因此可以这样写:char beep = 7;
第2中方法是,使用特殊的符号序列表示一些特殊的字符。这些符号序列叫作转义序列(escape sequence)
使用 ASCII 码时,注意数字和数字字符的区别。例如,字符 4 对应的 ASCII 码是 52。‘4’ 表示字符 4 ,而不是数值 4。
关于转义序列,读者可能有下面 3 个问题。

  • 例子printf("Gramps sez, \"a \\ is a backslsah.\"\n");,为何没有用单引号把转义序列括起来?无论是普通字符还是转义序列,只要是双引号括起来的字符集合,就无需应单引号括起来。双引号中的字符集合叫作字符串。注意,该例中的其他字符(G、r、a、m、p、s等)都没有用单引号括起来。与此类似,printf("Hello!\007\n");将打印Hello! 并发出一声蜂鸣,而printf("Hello!7\n");则打印Hello!7 。不是转义序列中的数字将作为普通字符被打印出来。
  • 何时使用 ASCII 码,为何要写成 ‘\032’ 而不是 032 ?首先,’\032’ 能更清晰地表达程序员使用字符编码的意图。其次,类似 \032 这样的转义序列可以嵌入C的字符串中,如printf("Hello!\007\n");中就嵌入了 \007。

4、打印字符
*printf()函数用 %c 指明待打印的字符。一个字符变量实际被储存为 1 字节的整数值。因此,如果用 %d 转换说明打印 char 类型变量的值,打印的是一个整数。而 %c 转换说明告诉printf()*打印该整数值对应的字符。下面例子演示里打印 char 变量的两种方式。

#include <stdio.h>

int main(void) {
    
    char ch;
    
    printf("Please enter a character\n");
    scanf("%c",&ch);
    printf("The code for %c is %d.\n", ch, ch);
    return 0;
}

该程序运行结果如下:
Please enter a character
C
The code for C is 67.
运行该程序时,在输入字母后不要忘记按下EnterReturn键。随后,*scanf()*函数会读取用户输入的字符,& 符号表示输入的字符赋给变量 ch。接着,*printf()*函数打印 ch 的值两次,第1次打印一个字符(对应代码中的 %c),第2次打印一个十进制整数值(对应代码中的 %d)。注意,*printf()*函数中的转换说明决定了数据的显示方式,而不是数据的储存方式。

5、有符号还是无符号
有些C编译器把 char 实现为有符号类型,这意味着 char 可表示的范围是 -128~127。但是有些编译器把 char 实现为无符号类型,那 char 可表示的范围是 0~255。
根据 C90 标准,C语言允许在关键字 char 前面使用 signed 或 unsigned。这样无论编译器默认 char 是什么类型,signed char 表示有符号类型,而 unsigned char 表示无符号类型。

3.4.4 _Boll类型

C99 标准添加了_Bool 类型,用于表示布尔值,即逻辑值 true 和 false。因为C语言用值1表示true,值0表示false,所以 _Bool 类型实际上也是一种整数类型。但原则上它仅占用1位存储空间。

3.4.5 可移植类型:stdint.h 和 inttypes.h

C语言提供了许多有用的整数类型。但是,某些类型名在不同系统中的功能不一样。C99新增了两个头文件 stdint.h 和 inttypes.h,以确保C语言的类型在各系统中的功能相同。

C语言为现有类型创建了更多类型名。这些新的类型名定义在 stdint.h 头文件。例如,int32_t 表示32位的有符号整数类型。在使用32位 int 的系统中国呢,头文件会把 int32_t 作为 int 的别名。这种类型别名称为精确宽度整数类型(exact-width integer type)。但是,计算机的底层系统可能不支持。因此,精确宽度整数是可选项。

C99 和 C11 提供了第2类别名集合。一些类型名保证所表示的类型一定是至少有指定宽度的最小整数类型。这种类型集合被称为最小宽度类型(minimum width type)。例如,int_least8_t 是可容纳8位有符号整数值的类型中宽度最小的类型的一个别名。

3.4.6 float、double 和 long double

1、打印浮点值
printf() 函数使用 %f 转换说明打印十进制计数法的 float 和 double 类型浮点数,用 %e 打印指数记数法的浮点数。如果系统支持十六进制格式的浮点数,可用 a 和 A 分别代替 e 和 E。打印 long double 类型要使用 %Lf、%Le 或 %La 转换说明。

#include <stdio.h>

int main(void) {
    
    float about = 32000.0;
    double abet = 2.14e9;
    long double dip = 5.32e-5;
    
    printf("%f can be written %e\n", about, about);
    printf("And it's %a in hexadecimal, powers of 2 notation\n", about);
    printf("%f can be written %e\n", abet, abet);
    printf("%Lf can be written %Le\n", dip, dip);
    return 0;
}

该程序的输出如下,前提是编译器支持 C99/C11:
32000.000000 can be written 3.200000e+04
And it’s 0x1.f4p+14 in hexadecimal, powers of 2 notation
2140000000.000000 can be written 2.140000e+09
0.000053 can be written 5.320000e-05

2、浮点值的上溢和下溢
当计算导致数值过大,超过当前类型能表达的范围时,就会发生上溢(overflow)
当计算导致数值过小,超过当前类型能表达的范围时,就会发生下溢(underflow)
C语言把损失了类型全精度的浮点值称为*低于正常的(subnormal)浮点值。
还有一个特殊的浮点值
NaN(not a number的缩写),只未定义的值。例如,给 asin() 函数传递一个正弦值,因此传入的参数不能大于 1,如果大于 1 返回的就不是角度值而是 NaN。

3.4.7 复数和虚数类型

3.4.8 其他类型

数组、指针、结构和联合

3.4.9 类型大小

用 sizeof() 函数即可,例如,sizeof(long long).它返回的是 byte 大小

3.5 使用数据类型

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值