第3章 数据和C¶
本页最后更新于 ,内容可能过时
变量声明与初始化¶
示例
#include <stdio.h>
int main(void)
{
int hogs = 21;
int cows = 32, goats = 14;
printf("%d %d %d\n", hogs, cows, goats);
return 0;
}
输出:
说明
- 声明为变量分配存储空间,并登记名称与类型
- 一条声明可列出多个同类型变量,变量名之间用逗号分隔
- 未初始化的变量保留内存中原有的位组合,不能假定为
0 int dogs, cats = 94;只初始化cats,dogs仍无初值
常见错误
- 声明末尾漏写分号
- 把
int dogs, cats = 94;理解为dogs也初始化为94 - 在同一条声明中混用已初始化和未初始化的变量
基本数据类型关键字¶
说明
- C 的基本数据类型由 11 个关键字组成,按存储方式分为整数类型与浮点类型两族
表3-1 基本数据类型关键字
| 关键字 | 类型 |
|---|---|
int |
基本整数类型 |
short |
较小的整数 |
long |
较大的整数 |
unsigned |
无符号修饰 |
signed |
显式声明有符号 |
char |
字符与小整数 |
float |
单精度浮点数 |
double |
双精度浮点数 |
_Bool |
布尔值,C99 |
_Complex |
复数,C99 |
_Imaginary |
虚数,C99 |
long long由两个long拼成,不是独立关键字unsigned与signed是修饰符,不能单独作为类型使用_Bool_Complex_Imaginary是 C99 新增的关键字
常见错误
- 把
long long当作一个关键字书写 - 在 C89 编译器上使用
//注释与_Bool
整数类型与取值范围¶
示例
#include <stdio.h>
int main(void)
{
int a = 2147483647;
unsigned int b = 4294967295u;
printf("%d\n", a);
printf("%u\n", b);
return 0;
}
输出:
表3-2 整数类型 (典型 64 位 Linux 系统)
| 类型 | 字节 | 取值范围 |
|---|---|---|
signed char |
1 | -128 到 127 |
unsigned char |
1 | 0 到 255 |
short |
2 | -32768 到 32767 |
unsigned short |
2 | 0 到 65535 |
int |
4 | -2147483648 到 2147483647 |
unsigned int |
4 | 0 到 4294967295 |
long |
8 | -9223372036854775808 到 9223372036854775807 |
long long |
8 | 与 long 相同 |
unsigned long long |
8 | 0 到 18446744073709551615 |
说明
- C 标准只规定最小宽度:
short至少 16 位,long至少 32 位 long long至少 64 位,由 C99 引入int通常等于机器字长,16 位机上为 2 字节- Windows 的
long为 4 字节,Linux 与 macOS 上为 8 字节 - 存储以二进制补码表示,有符号类型最高位为符号位
整型常量¶
示例
#include <stdio.h>
int main(void)
{
int a = 42;
int b = 052;
int c = 0x2a;
printf("%d %d %d\n", a, b, c);
return 0;
}
输出:
说明
0前缀表示八进制,0x或0X前缀表示十六进制- 三种写法只在源码中不同,存储时都换成二进制
- 无后缀整型常量按
int,unsigned int,long,unsigned long
的顺序取第一个能容纳该值的类型 - 十六进制 1 位对应二进制 4 位,便于观察位组合
常见错误
- 在八进制常量中写
8或9,例如018 - 把
010当作十进制 10,实际为 8
打印整数¶
示例
#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);
return 0;
}
输出:
说明
%d是转换说明,指明按有符号十进制打印一个int值- 转换说明的个数与待打印值的个数须一致
printf()参数个数可变,编译器不检查个数与类型是否匹配- 格式化字符串中的每个
%d按顺序与后面的实参对应
常见错误
- 遗漏实参,例如
printf("%d %d\n", ten);,缺少的值取自内存中的任意内容 - 把
printf()的检查指望于编译器,多数编译器只给警告
打印不同进制¶
语法形式
示例
#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;
}
输出:
说明
- 同一个值可用不同转换说明显示,存储不变
#修饰符只控制前缀的显示,不改变数值%X输出大写字母,前缀也随之变成0X
常见错误
- 认为
%o会把十进制数换算成八进制存储 - 需要前缀时漏写
#
整数类型修饰¶
示例
#include <stdio.h>
int main(void)
{
unsigned int s_count = 10u;
unsigned long headcount = 3000000000ul;
signed short int yesvotes = -5;
printf("%u %lu %hd\n", s_count, headcount, yesvotes);
return 0;
}
输出:
说明
shortlonglong long后面的int可省略short的宽度不大于int,long不小于int,long long不小于longunsigned去掉符号位,范围平移到0到 \(2^n-1\)signed只用于强调有符号,不改变类型- 计数类变量优先用
unsigned,需要更大范围时依次考虑longlong long
常见错误
- 写出
unsigned signed int这类互斥修饰 - 认为
unsigned只是去掉负号,忽略范围上限翻倍
整数常量后缀¶
示例
#include <stdio.h>
int main(void)
{
long a = 7L;
long long b = 3LL;
unsigned long c = 10UL;
printf("%ld %lld %lu\n", a, b, c);
return 0;
}
输出:
说明
- 后缀字母大小写均可,可组合
L优于l,小写l与数字 1 容易混淆- 后缀决定常量的类型,不改变它的值
- 默认无后缀的十进制常量是
int,超出范围时依次升级
常见错误
- 用小写
l结尾,阅读时误认为1 - 认为加上
L后数值变大
整数溢出¶
示例
#include <stdio.h>
int main(void)
{
int i = 2147483647;
unsigned int j = 4294967295u;
printf("%d %d %d\n", i, i + 1, i + 2);
printf("%u %u %u\n", j, j + 1, j + 2);
return 0;
}
输出:
说明
- 无符号整数按模 \(2^n\) 回绕,超过最大值后回到
0 - 有符号整数溢出属于未定义行为,C 标准不规定结果
- 常见的实现结果是最高位翻转,得到负数,但不能依赖
- 溢出时系统不报告错误,需程序自行检查范围
常见错误
- 认为溢出会终止程序或给出提示
- 依赖有符号溢出的具体结果编写逻辑
转换说明与整数宽度¶
示例
#include <stdio.h>
int main(void)
{
unsigned int un = 3000000000;
short end = 200;
long big = 65537;
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);
return 0;
}
输出:
说明
%u打印unsigned int,%ld打印long%hd打印short,h前缀只读取低 16 位short在传参时自动提升为int,所以%d也能打印小值- 同样的位组合按有符号或无符号解释,显示结果不同
- 常量后缀可大写或小写,转换说明中的长度修饰符用小写
- 例如
%ld不写成%LD,而%X表示大写的十六进制数字
常见错误
- 用
%d打印超出int范围的unsigned值 - 用
%hd打印long,只输出低 16 位
字符类型与字符常量¶
示例
#include <stdio.h>
int main(void)
{
char grade = 'A';
char code = 65;
printf("%c %d\n", grade, grade);
printf("%c\n", code);
return 0;
}
输出:
说明
char存储的是字符的整数编码,本质是 1 字节整数- 字符常量用单引号括起来,编译器把它换成对应编码值
- 字符常量本身是
int类型,赋值给char时发生收窄 - 用编码
65赋值可行,但只在 ASCII 系统上成立,宜写'A' - 多字符常量如
'FATE'把 4 个编码压进一个int,赋给char后只剩最后一个字符
常见错误
char c = "A";双引号是字符串char c = A;没有引号,A被当作变量名- 认为
'4'等于数值4,实际是编码52
转义序列¶
示例
#include <stdio.h>
int main(void)
{
char beep = '\a';
char nl = '\n';
printf("Gramps sez, \"a \\ is a backslash.\"\n");
printf("%c", beep);
printf("done%c", nl);
return 0;
}
输出:
表3-3 转义序列
| 转义序列 | 含义 |
|---|---|
\a |
警报,响铃 |
\b |
退格 |
\f |
换页 |
\n |
换行 |
\r |
回车,回到行首 |
\t |
水平制表符 |
\v |
垂直制表符 |
\\ |
反斜杠 |
\' |
单引号 |
\" |
双引号 |
\? |
问号 |
\ooo |
1 到 3 位八进制编码 |
\xhh |
1 到 3 位十六进制编码 |
说明
\ooo与\xhh以八进制或十六进制编码表示字符- 编码值取决于系统的字符集,常见系统为 ASCII
- 八进制写法可省略前导
0,'\07'与'\7'均有效 - 转义序列写在字符串中时不必再加单引号
- 优先用
'\f'这类字符写法,比'\014'可读且可移植 \r回到行首,后续输出会覆盖本行已有字符
常见错误
- 打印反斜杠时只写
\,被当作续行或非法转义 - 在双引号字符串里放未转义的
"
打印字符¶
示例
#include <stdio.h>
int main(void)
{
char ch = 'C';
printf("The code for %c is %d.\n", ch, ch);
return 0;
}
输出:
常见错误
- 用
%d打印字符而期望看到字母 - 用
%c打印字符串,只输出首字符
布尔类型¶
示例
#include <stdio.h>
#include <stdbool.h>
int main(void)
{
_Bool on = 1;
_Bool off = 0;
bool ok = true;
printf("%d %d %d\n", on, off, ok);
printf("%zu\n", sizeof(_Bool));
return 0;
}
输出:
说明
_Bool由 C99 引入,只能存0或1- 赋非零值给
_Bool时,结果统一为1 - 包含
stdbool.h后可用booltruefalse _Bool是整数类型,可用%d打印sizeof(_Bool)为 1 字节
[教材原写法: _Bool 原则上仅占用 1 位存储空间
- 本笔记: _Bool 占 1 字节
- 依据: C 标准要求对象至少占 1 个字节,GCC 与 MSVC 上 sizeof(_Bool) 均为 1]
常见错误
- 未包含
stdbool.h就使用bool - 期望
printf直接输出true与false
可移植整数类型¶
示例
#include <stdio.h>
#include <inttypes.h>
int main(void)
{
int32_t me32 = 45933945;
printf("me32 = %" PRId32 "\n", me32);
printf("sizeof = %zu\n", sizeof(int32_t));
return 0;
}
输出:
说明
int8_tint16_tint32_tint64_t为精确宽度类型,宽度固定int_least8_t保证至少 8 位,取满足条件的最小类型int_fast8_t取运算最快的类型,可能更宽intmax_t与uintmax_t为系统支持的最大整数类型PRId32等宏展开为对应的转换字符,与int32_t匹配- 相邻的字符串字面量会自动拼接,所以
"%" PRId32可用
常见错误
- 用
%d打印int64_t,宽度不匹配 - 把
PRId32写在引号内部,宏不会被展开
浮点类型¶
示例
#include <stdio.h>
int main(void)
{
float f = 3.14f;
double d = 3.141592653589793;
printf("%.2f\n", f);
printf("%.15f\n", d);
return 0;
}
输出:
表3-4 浮点类型
| 类型 | 典型字节数 | 有效数字 | 范围 |
|---|---|---|---|
float |
4 | 至少 6 位 | 约 \(10^{-37}\) 到 \(10^{37}\) |
double |
8 | 至少 10 位,通常 15 到 16 位 | 约 \(10^{-308}\) 到 \(10^{308}\) |
long double |
8 或 16 | 不少于 double |
不少于 double |
说明
- 浮点数由符号,尾数,指数三部分分开存储
- 尾数决定精度,指数决定范围
float的 32 位中通常 8 位存指数,24 位存尾数与符号double的位数多用于尾数,因而精度更高- C 只保证
long double的精度不低于double
常见错误
- 认为位数多就一定表示更大的范围,实际可能只是尾数更宽
- 在同一程序中混用大量
float与double,造成隐式转换
浮点常量¶
示例
#include <stdio.h>
int main(void)
{
float a = 2.3f;
double b = 1.23e5;
long double c = 54.3L;
double d = 0xa.1fp10;
printf("%.1f\n", a);
printf("%.0f\n", b);
printf("%.1f\n", d);
return 0;
}
输出:
说明
- 小数点与指数部分至少有一个,不能都省略
.24e16.8E-5都是有效的浮点常量- 无后缀的浮点常量是
double 0xa.1fp10是 C99 的十六进制写法,p10表示乘以 \(2^{10}\)- 该值等于 \((10+1/16+15/256)\times 1024=10364.0\)
L后缀优于l,避免与数字 1 混淆
常见错误
- 在常量中间加空格,如
1.56 E+12 - 写成
e21,e前至少要有一个数字,如1e21
打印浮点值¶
语法形式
示例
#include <stdio.h>
int main(void)
{
float aboat = 32000.0;
double abet = 2.14e9;
printf("%f can be written %e\n", aboat, aboat);
printf("%f can be written %e\n", abet, abet);
return 0;
}
输出:
说明
%f与%e默认显示 6 位小数float在传参时自动提升为double,所以%f可打印floatlong double要用%Lf%Le%La%a以十六进制尾数与二进制指数显示,需编译器支持 C99%.2f中的.2指定小数点后两位
常见错误
- 用
%d打印浮点数 - 用
%f打印long double,宽度不匹配
浮点溢出与下溢¶
示例
#include <stdio.h>
int main(void)
{
float toobig = 3.4E38f * 100.0f;
float tiny = 1.17549435e-38f / 2.0f;
printf("%e\n", toobig);
printf("%e\n", tiny);
return 0;
}
输出:
说明
- 结果超出类型上限称为上溢,C 规定此时得到无穷大
printf把无穷大显示为inf或infinity- 结果小于最小正规数时,指数已无法再减,只能右移尾数
- 右移损失末尾有效位,这种值称为低于正常值,即下溢
- 再小下去所有位变为
0,结果变成零 - 对无效运算,如
asin()的参数大于 1,结果为NaN
常见错误
- 认为上溢会让程序终止
- 把下溢得到的小数当作精确结果继续运算
浮点舍入误差¶
示例
#include <stdio.h>
int main(void)
{
float a, b;
b = 2.0e20f + 1.0f;
a = b - 2.0e20f;
printf("%f\n", a);
return 0;
}
输出:
说明
float只有 6 到 7 位有效数字2.0e20加 1 需要第 21 位有效数字,超出存储能力- 加 1 的结果与
2.0e20相同,相减后得0 - 换成
2.0e4时,加 1 只改动第 5 位,结果正确 - 浮点运算结果与系统,编译器,优化级别相关
常见错误
- 用
==比较两个浮点数 - 在累加大量小数时使用
float - 认为浮点数能精确表示所有十进制小数
复数与虚数类型¶
示例
#include <stdio.h>
#include <complex.h>
int main(void)
{
double complex z = 1.0 + 2.0 * I;
printf("%.1f %.1f\n", creal(z), cimag(z));
return 0;
}
输出:
说明
- 复数类型由两个浮点数组成,分别存实部与虚部
- 包含
complex.h后可用complex代替_Complex I表示虚数单位,即 \(\sqrt{-1}\)creal()取实部,cimag()取虚部- 虚数类型
float _Imaginary等在 C11 中为可选项
常见错误
- 未包含
complex.h就使用complex与I - 用
%f打印复数整体,应分别打印实部与虚部
类型转换与截断¶
示例
#include <stdio.h>
int main(void)
{
int cost = 12.99;
float pi = 3.1415926536;
char ch = 'FATE';
printf("%d %.6f %c\n", cost, pi, ch);
return 0;
}
输出:
说明
- 浮点值赋给整数变量时直接丢弃小数部分,不四舍五入
double赋给float时保留前 6 到 7 位有效数字,其余舍去- 多字符常量压缩成
int,赋给char时只留最低 8 位 - 编译器一般给警告,不阻止转换
- 表达式中的混合类型按整型提升与类型等级自动转换
常见错误
- 认为
int cost = 12.99;得到13 - 在需要精度的场合用
float代替double
sizeof 与类型大小¶
示例
#include <stdio.h>
int main(void)
{
printf("Type int has a size of %zd bytes.\n", sizeof(int));
printf("Type char has a size of %zd bytes.\n", sizeof(char));
printf("Type long has a size of %zd bytes.\n", sizeof(long));
printf("Type long long has a size of %zd bytes.\n",
sizeof(long long));
printf("Type double has a size of %zd bytes.\n", sizeof(double));
printf("Type long double has a size of %zd bytes.\n",
sizeof(long double));
return 0;
}
输出 (MinGW,Windows)
说明
sizeof是运算符,不是函数,以字节为单位返回大小- 返回值类型为
size_t,C99 起用%zd打印 - 参数为类型名时必须加括号,为变量时括号可省略
char的大小恒为 1 字节,1 字节的位数由实现决定- 其余类型的大小依系统而异,同一类型在 32 位与 64 位系统上可能不同
常见错误
- 用
%d打印sizeof的结果 - 把书上的字节数当作标准规定,跨平台移植时出问题
转换说明与参数匹配¶
示例
#include <stdio.h>
int main(void)
{
int cats = 2;
int cans = 3;
printf("%d cats ate %d cans of tuna\n", cats, cans);
return 0;
}
输出:
说明
- 格式串中的转换说明按顺序与后面的实参一一对应
printf与scanf的参数个数可变,编译器不检查个数与类型- 转换说明过多或过少时行为未定义,结果随平台而异
- 一个字符串字面量无论多长都只算一个参数
- 数量与类型都由程序员保证
常见错误
- 遗漏实参,打印出内存中的任意值
- 用
%d打印float,值不会被自动转换 - 误以为编译器会拦截这类错误
scanf 读取数据¶
示例
说明
&取得变量的地址,告诉scanf把值写到何处%d读int,%f读float,%c读单个字符%lf用于读double,打印时仍用%f- 输入时按 Enter 提交,换行符留在缓冲区
- 读数失败时不改变变量,返回值小于参数个数
常见错误
- 漏写
&,程序运行时崩溃 - 用
%f读double变量 - 用
%c读字符时,读到的却是上一次留下的换行符
复习题¶
(1)指出下面各种数据使用的合适数据类型 (有些可使用多种数据类型)
a. East Simpleton 的人口
b. DVD 影碟的价格
c. 本章出现次数最多的字母
d. 本章出现次数最多的字母次数
答案
a. int,也可用 short 或 unsigned short,人口是整数
b. float,也可用 double,价格通常不是整数
c. char
d. int,也可用 unsigned
(2)在什么情况下要用 long 类型的变量代替 int 类型的变量
答案
一是要表示的数超出 int 的范围
二是需要一种在所有系统上都保证至少 32 位的类型,以提高可移植性
(3)使用哪些可移植的数据类型可以获得 32 位有符号整数,选择的理由是什么
答案
要正好 32 位,用 int32_t
要可储存至少 32 位的最小类型,用 int_least32_t
要 32 位整数的最快运算,用 int_fast32_t
(4)指出下列常量的类型和含义 (如果有的话)
a. '\b'
b. 1066
c. 99.44
d. 0XAA
e. 2.0e30
答案
a. 字符常量,存储为 int,表示退格
b. int 常量
c. double 常量
d. int 常量,十六进制写法,值为 170
e. double 常量
[教材原写法: d 为 unsigned int 类型常量
本笔记: d 为 int 类型常量
依据: 无后缀的十六进制常量按 int,unsigned int,long 的顺序取首个可容纳的类型,170 在 int 范围内]
(5)Dottie Cawm 编写了一个程序,请找出程序中的错误
答案
第 1 行改为 #include <stdio.h>
第 2 行改为 int main(void)
第 3 行把 ( 改为 {
第 4 行把 g 与 h 之间的分号改为逗号
第 7 行 e21 前应至少有一个数字,如 1.0e21
第 9 行把 ) 改为 }
另缺 rate 的赋值,h 未被使用,结果未打印,末尾缺 return
参考版本
(6)写出下列常量在声明中使用的数据类型和在 printf() 中对应的转换说明
答案
| 常量 | 类型 | 转换说明 |
|---|---|---|
12 |
int |
%d |
0X3 |
int |
%#X |
'C' |
char,实际为 int |
%c |
2.34E07 |
double |
%e |
'\040' |
char,实际为 int |
%c |
7.0 |
double |
%f |
6L |
long |
%ld |
6.0f |
float |
%f |
0x5.b6p12 |
double |
%a |
[教材原写法: 0X3 为 unsigned int,0x5.b6p12 为 float
本笔记: 0X3 为 int,0x5.b6p12 为 double
依据: 无后缀整型常量先取 int,装不下才升为 unsigned int; 十六进制浮点常量无后缀时为 double,已用 gcc 的 _Generic 实测]
(7)写出下列常量在声明中使用的数据类型和在 printf() 中对应的转换说明,假设 int 为 16 位
答案
| 常量 | 类型 | 转换说明 |
|---|---|---|
012 |
int |
%#o |
2.9e05L |
long double |
%Le |
's' |
char,实际为 int |
%c |
100000 |
long |
%ld |
'\n' |
char,实际为 int |
%c |
20.0f |
float |
%f |
0x44 |
int |
%#x |
-40 |
int |
%d |
[教材原写法: 012 与 0x44 为 unsigned int
本笔记: 两者均为 int,值分别为 10 与 68,都在 16 位 int 范围内
依据: 八进制与十六进制常量按 int,unsigned int,long 的顺序取首个可容纳的类型]
(8)假设程序的开头有下列声明,把下面 printf() 语句中的转换字符补充完整
printf("The odds against the %__ were %__ to 1.\n", imate, shot);
printf("A score of %__ is not an %__ grade.\n", log, grade);
答案
第一句填 %d 与 %ld
第二句填 %f 与 %c
(9)假设 ch 是 char 类型的变量,分别使用转义序列,十进制值,八进制字符常量和十六进制字符常量把回车字符赋给 ch
答案
ch = '\r';
ch = 13;
ch = '\015';
ch = '\xd';
(10)修正下面的程序 (/ 表示除以)
void main(int) / this program is perfect /
{
cows, legs integer;
printf("How many cow legs did you count?\n);
scanf("%c", legs);
cows = legs / 4;
printf("That implies there are %f cows.\n", cows)
}
答案
最前面补 #include <stdio.h>
第 1 行用 /* */ 或 // 包围注释
第 3 行改为 int cows, legs;
第 4 行补上结尾的 \n");
第 5 行把 %c 改为 %d,把 legs 改为 &legs
第 7 行把 %f 改为 %d
末尾补 return 0;
修改后的版本
(11)指出下列转义序列的含义
a. \n
b. \\
c. \"
d. \t
答案
a. 换行字符
b. 反斜杠字符
c. 双引号字符
d. 水平制表字符
编程练习¶
编程练习
-
通过试验观察系统如何处理整数上溢,浮点数上溢和浮点数下溢,编写带有这类问题的程序
-
编写一个程序,提示输入一个 ASCII 码值 (如 66),然后打印输入的字符
-
编写一个程序,发出一声警报,然后打印下面的文本
- 编写一个程序,读取一个浮点数,先打印成小数点形式,再打印成指数形式
然后如果系统支持,再打印成p记数法
输出格式如下
Enter a floating-point value: 64.25
fixed-point notation: 64.250000
exponential notation: 6.425000e+01
p notation: 0x1.01p+6
-
一年大约有 \(3.156\times10^7\) 秒
编写一个程序,提示用户输入年龄,然后显示该年龄对应的秒数 -
1 个水分子的质量约为 \(3.0\times10^{-23}\) 克
1 夸脱水约为 950 克
编写一个程序,提示用户输入水的夸脱数,并显示水分子的数量 -
1 英寸相当于 2.54 厘米
编写一个程序,提示用户输入身高 (英寸),然后以厘米为单位显示身高 -
在美国的体积测量系统中,1 品脱等于 2 杯,1 杯等于 8 盎司
1 盎司等于 2 大汤勺,1 大汤勺等于 3 茶勺
编写一个程序,提示用户输入杯数,并以品脱,盎司,汤勺,茶勺为单位显示等价容量
并说明该程序为何使用浮点类型比整数类型更合适


