本文是《C语言期末速成》系列第 2 篇。第 1 篇讲了程序的骨架,这一篇往里面填充”砖块”——程序操作的数据长什么样:怎么给变量起名字(标识符)、数据有哪些进制写法、整数/实数/字符怎么区分、字符为什么能当整数用。


上一篇C语言期末速成 1:程序的骨架

考点清单

  • 标识符只能由字母、数字、下划线组成,首字符不能是数字
  • 标识符分关键字、预定义标识符、用户标识符三类;maindefinescanfprintfIf(大写 I)都不是关键字
  • C 语言中整数用**八进制(0 开头)、十六进制(0x 开头)**书写,没有二进制字面量,但运行时全部转成二进制处理
  • 实型(小数)合法写法:1..1;科学计数法要求 e 前 e 后必须有数,e 后必须是整数
  • 常用类型字节数:char 1 字节、int 4 字节、double 8 字节
  • ASCII:'0'=48'A'=65'a'=97;大小写字母相差 32
  • 转义字符:\0\n\'\"\\,以及八进制 '\141'、十六进制 '\x6d'

一、标识符:给程序里的东西起名字

变量、函数、数组都要名字,这个名字就是标识符

1.1 合法规则(必考)

字母、数字、下划线组成,且第一个字符必须是字母或下划线

拆成两个判断步骤:

  1. 先扫一遍:有没有字母、数字、下划线以外的字符(空格、-$#@ 都是非法的);
  2. 再看开头:如果第一个字符是数字,直接判死刑。
int 3x;    /* 非法:首字符是数字 */
int a-b;   /* 非法:含减号 */
int my name; /* 非法:含空格 */
int 2abc;  /* 非法:以数字开头 */
int my_name; /* 合法 */
int _count;  /* 合法:下划线开头 */
int x2y;   /* 合法:数字不在首位即可 */
int retVal;/* 合法:区分大小写,与 retval 不同 */

注意 int float; 也非法——float关键字,不能作用户标识符。判定一句话标识符是否合法,先扫一遍字符(只要含字母、数字、下划线以外的字符就出局),再看首字符(是数字就出局)。

大小写敏感Abcabc 是两个不同的标识符。这也是下面 If 能当用户标识符的原因。

1.2 三类标识符

类别含义能否作用户标识符
关键字语言本身保留的词,如 intifreturnwhile不能
预定义标识符库函数/库常量,如 printfscanfdefineinclude可以(但不建议)
用户标识符你自己起的名字——
📋 附:C 语言完整关键字(C11 共 44 个)

C11 标准为准,按加入标准的时间分组:

基础关键字

auto break case char const continue default do double else enum extern float for goto if int long register return short signed sizeof static struct switch typedef union unsigned void volatile while inline restrict _Bool _Complex _Imaginary

C11 新增(7 个)

_Alignas _Alignof _Atomic _Generic _Noreturn _Static_assert _Thread_local

C23 新增

alignas alignof bool constexpr false true nullptr static_assert thread_local typeof typeof_unqual _BitInt _Decimal32 _Decimal64 _Decimal128

说明:C17 只是修正版,没有新增关键字;C23 同时移除了 _Imaginary。另外注意 _Bool 在 C23 中已由 bool 替代,_Alignas/_Alignof/_Static_assert/_Thread_local 也都有了不带下划线的正式写法。下划线开头的关键字都是标准库实现者预留的,用户标识符不能以它们为名

必考迷惑点

  • maindefinescanfprintf不是关键字。所以”定义 int main;”语法上允许,虽然没人会这么干。
  • If 不是关键字 if,因为首字母大写,可以做用户标识符——这是考试最爱设的陷阱。
  • 预定义标识符可以被重新定义,只是会把库函数的含义”劫持”掉,一般不会这么写。
/* 判断题 */
/* 1. main 是关键字             → 错 */
/* 2. define 是关键字           → 错 */
/* 3. If 可以作为用户标识符      → 对 */
/* 4. int 是关键字,不可作变量名 → 对 */

二、进制:数在 C 里怎么”写”

2.1 C 语言的字面量进制

C 语言源代码里只有三种进制写法:

进制前缀示例十进制值
十进制1717
八进制001715
十六进制0x0x1117

两个立刻要避开的坑:

  1. 八进制以 0 开头,所以不存在 8 和 9018 是非法字面量,因为八进制逢 8 进 1,一位最大是 7。
  2. 十六进制以 0x(或 0X)开头,0x1FF 表示 15。

为什么 C 没有二进制字面量? 因为二进制写起来太长(十进制的 15 要写成 1111),日常用八/十六进制就够表达。但运行时所有进制都会转换成二进制再参与运算——这解释了为什么位运算要在二进制视角下理解(第 11 章)。

2.2 进制转换:掌握三种方法

方法一:按权展开(任意进制 → 十进制)

每一位乘以它的”位权”,再加起来。八进制转十进制的规则:第 k 位(从 0 开始从右往左数)的权是 8^k。

017  = 1×8^1 + 7×8^0 = 8 + 7 = 15
0x11 = 1×16^1 + 1×16^0 = 16 + 1 = 17
0x2F = 2×16 + 15 = 47

方法二:除基取余(十进制 → 任意进制)

反复除目标基数、记录余数,余数倒着读。以十进制 15 → 二进制为例:

15 ÷ 2 = 7 余 1
 7 ÷ 2 = 3 余 1
 3 ÷ 2 = 1 余 1
 1 ÷ 2 = 0 余 1
余数从下往上读:1111  →  15 = (1111)₂

15 → 八进制:15 ÷ 8 = 1 余 71 ÷ 8 = 0 余 1,倒读得 17,即 017

方法三:一分四/四并一法(二 ↔ 十六快速互转)

因为 4 位二进制正好对应 1 位十六进制,记住每位的权重 8、4、2、1

二进制 1011 → 8+0+2+1 = 11 → 十六进制 B
十六进制 3F → 0011 1111 → 二进制 00111111
二进制00000010010110101111
十六进制025AF

2.3 使用printf 看进制的实际效果

同一个变量,用不同格式符输出,会以不同进制显示(格式符详见第 4 章,这里先感受):

#include <stdio.h>

int main(void) {
    int x = 017;            /* 注意:这是八进制字面量,值是十进制的 15 */
    printf("%d\n", x);      /* 15  (十进制) */
    printf("%o\n", x);      /* 17  (八进制) */
    printf("%#o\n", x);     /* 017 (带前缀的八进制) */
    printf("%x\n", x);      /* f   (十六进制,小写) */
    printf("%#x\n", x);     /* 0xf (带前缀的十六进制) */
    return 0;
}

# 的作用是让输出带上进制前缀,一眼就能看出 0x 还是 0


三、整数与实数:字面量的合法写法

3.1 小数的合法写法

C 语言里,小数点两边如果有一个是 0,可以不写

  • 1.0 可以写成 1.
  • 0.1 可以写成 .1

所以看到 1. 别以为它缺了什么,它是合法的 double 类型小数 1.0。

3.2 科学计数法(指数形式)

合法格式如 2.333e-1,表示 2.333 × 10⁻¹ = 0.2333

考试口诀——e 前 e 后必有数,e 后必为整数

  • e 前面必须有数字(可以是小数,如 2.5e2);
  • e 后面必须有数,且必须是整数(不能是 2e3.5,也不能是 2e);
  • 指数可为负(1e-3)。
2.333e-1  合法  → 0.2333
2.5e2     合法  → 250.0
2e3.5     非法  → 指数不能是小数
2e        非法  → 指数后缺数
.e3       非法  → e 前缺数

3.3 类型与字节数

类型字节数(常见,因编译器而异)说明
char1字符型
short2短整型
int4整型
long/long int4 或 8长整型
float4单精度浮点
double8双精度浮点

修饰符组合:long int x; 表示长整型;unsigned int x; 表示无符号整型——unsigned 的意思就是”这个类型不再保存负数,把省下的状态全用来装更大的正数”。

3.4 signed / unsigned 的范围

char(1 字节 = 8 位)为例:

类型范围
signed char-128 ~ 127(2⁷ 个负数 + 0 + 2⁷-1 个正数)
unsigned char0 ~ 255(全部 2⁸ 种状态都用来表示非负数)

8 个 bit 总共只有 256 种状态。signed 把其中一半分给负数,unsigned 全拿来做正数。所以同样 1 个字节,unsigned char 能装到 255,signed char 最多 127。考试若问”unsigned 比 signed 能表示更大正数”,答案是对。

3.5 void 类型:表示”没有值”

void 意思是”没有值”,它不是一个能装数据的类型,而是用来描述空。三种典型用途:

  1. 函数没有返回值void swap(...) 表示这个函数不返回任何值(第 6 章);
  2. 函数不接收参数int getchar(void) 表示 getchar 不需要参数;
  3. 无类型指针 void*:可以指向任意类型,“不关心里面是什么”的地址。malloc 的返回值就是 void*,所以通常要强转成目标指针类型(第 10 章)。
void nothing(void) {          /* 无参数、无返回值 */
    printf("no value\n");
}
void *p;                      /* 万能指针:能存任何类型的地址 */

不能声明 void x;——void 没有大小,不可能给一个”没有值”的变量分配空间,这种写法是编译错误。


四、字符:披着整数外衣的”1 字节小整数”

4.1 字符与字符串的区别

  • '1'字符,占 1 个字节
  • "1"字符串,占 2 个字节(多出的那 1 字节是字符串结尾的 \0)。
char c = 'A';      /* 1 字节 */
char s[] = "A";    /* 2 字节:'A' 和 '\0' */

4.2 ASCII:字符背后的编号

每个字符在内存里其实就是一个小整数(它的 ASCII 码)。三个必须背的值:

字符ASCII(十进制)
'0'48
'A'65
'a'97

由此推出的规律:

  • 数字字符是连续的:'0''9' 是 4857;
  • 大写字母是连续的:'A''Z' 是 6590;
  • 小写字母是连续的:'a''z' 是 97122;
  • 大小写字母相差 32'A' + 32 = 'a'

所以”大写转小写”就是 ch = ch + 32;,判断”是不是数字字符”就是 ch >= '0' && ch <= '9';——这里的比较实际上是 ASCII 码大小的比较。

4.3 字符能做算术运算

既然字符是整数,就能参与算术:

char c = '0';
printf("%d\n", c);      /* 48:把 ASCII 码当整数打印 */
printf("%d\n", c - '0');/* 0 :'0'-'0'=48-48=0,字符数字转数值数字 */

'0' - 0 = 48 这个式子字面上是”字符 ‘0’ 减去整数 0 等于 48”,本质是用 ‘0’ 的 ASCII 码 48 参与运算

#include <stdio.h>

int main(void) {
    char a = 65;
    printf("%c\n", a);   /* A  :用字符格式解释 65 */
    printf("%d\n", a);   /* 65 :用整数格式解释 65 */
    return 0;
}

同一个变量,%c 把它当字符输出,%d 把它当整数输出——字符型和整型是”近亲”,二者可互转。这是考试超高频知识点。

4.4 转义字符:让字符”换种写法”

有些字符无法直接打出来(换行、回车、制表),有些字符有特殊含义(引号、反斜杠),于是用反斜杠 + 字符的形式转义。

一般转义字符(必背)

转义字符含义
\0空字符(ASCII 0),也是字符串结尾标志
\n换行
\t水平制表(Tab)
\'单引号
\"双引号
\\反斜杠本身

八进制转义字符'\141' 合法,表示八进制数 141 对应的字符(1×64+4×8+1 = 97,正是 'a')。注意:前导 0 不能写,写 '\0141' 是错的。

十六进制转义字符'\x6d' 合法,0x6d = 109,正是 'm'注意:x 必须小写,且前面不能有 0

'\141'  合法   /* 八进制转义,表示 'a' */
'\x6d'  合法   /* 十六进制转义,表示 'm' */
'\0141' 非法   /* 前导 0 不能写 */
'\X6d'  非法   /* x 必须小写 */

考点提醒'\141' 是单个字符(1 字节),而 "abc" 是 4 个字节(3 个字符 + 1 个 \0),别把二者混为一谈。


易错点 / 陷阱

  1. 018 当合法八进制:八进制逢 8 进 1,018 含数字 8,非法。
  2. e 后是小数2e3.5 非法,指数必须是整数;e 前缺数字也非法。
  3. '\0141''\X6d' 当作合法转义:八进制前导 0 不能写,x 必须小写。
  4. '1' 当整数 1'1' 的 ASCII 是 48,不是 1。第 4 章 scanf 也会再考一次。
  5. If 当关键字:首字母大写就不是关键字,可作用户标识符。
  6. main 当关键字main 不是关键字,只是约定的入口函数名。
  7. 0.1 写成 .1 认为非法:小数点一边为 0 时可省,.1 合法。
  8. char a=65 输出 65 当字符:用 %c 输出 65 得到 'A',用 %d 才是 65。

自测小练习

  1. 以下哪些是合法标识符?3x_ax-yIfinta_2
  2. 写出 0170x1F 对应的十进制值。
  3. 把十进制 25 分别写成二进制、八进制、十六进制字面量。
  4. '\141''\x6d' 分别对应哪个字符?它们的 ASCII 码分别是多少?
  5. char c = 'B'; 要输出它的 ASCII 码,用 %c 还是 %d
参考答案
  1. 合法:_aIfa_2;非法:3x(首字符数字)、x-y(含减号)、int(关键字)。
  2. 017 = 1×8+7 = 15;0x1F = 1×16+15 = 31。
  3. 25 = (11001)₂ = (31)₈ = (19)₁₆,C 写法:0x19(八进制字面量写作 031)。
  4. '\141':八进制 141 = 十进制 97 = 'a''\x6d':十六进制 6d = 109 = 'm'
  5. %d%c 输出字符本身(B),%d 输出 ASCII 码(66)。

下一篇C语言期末速成 3:运算符与表达式

评论区