前言
- 本系列C语言教程将补充C语言的基础部分,适合具有一定语言基础的朋友进行复习。
- 第一期: 【C语言简明教程】(一):数据类型,表达式与控制结构-CSDN博客
- 第二期: 【C语言简明教程提纲】(二):函数你值得拥有-CSDN博客
- 以下是本系列的目录
- 字符串使用(本节)
- 编译预处理(本节)
- 数组与函数
- 数组定义和引用
- 指针定义和使用
- 指针与数组、与字符串、与函数
- 结构体定义、结构体数组、结构体指针
- 文件定义和操作。
- 常用算法
- 关于数值和指针两个难点,可以参考之前写的文章,浅显易懂,适合初学者的朋友
0 前置知识-进制转换
0-1 进制表
- 在 C 语言中,字符串的 八进制转义字符 和 十六进制转义字符 本质上是 ASCII 数值表示,因此需要简单了解进制转换。
0-2 十六进制 → 十进制(手算方法)
- 十六进制的基数是 16 。
0~9和a~f - 计算方法: 每一位 × 16 的幂 每一位 × 16 的幂 每一位×16的幂
- 例如: 0 x 41 0x41 0x41
- 计算就是
c
4 × 16¹ + 1 × 16⁰
= 64 + 1
= 65
0-3 八进制 → 十进制(手算方法)
- 同样的道理,八进制基数是 8 。
0~7 - 计算方法: 每一位 × 8 的幂 每一位 × 8 的幂 每一位×8的幂
- 例如: 141 ( 八进制 ) 141(八进制) 141(八进制)
- 计算就是:
c
1 × 8² + 4 × 8¹ + 1 × 8⁰
= 64 + 32 + 1
= 97
1 字符串使用

1-1 字符串定义
- 在 C 语言中并没有专门的
string类型,字符串本质上是字符数组 ,并且以\0(空字符)作为结束标志。
c
char str[] = "hello";
//等价写法
char str[] = {'h','e','l','l','o','\0'};
- 在内存中实际存储为:
c
h e l l o \0
\0非常重要,它表示 字符串结束,许多字符串函数都是依靠它来判断字符串长度的。
c
sizeof(str)==6
1-2 自定义字符串长度
- 字符串长度可以通过遍历
\0计算。
c
#include <stdio.h>
unsigned int myStrlen(char str[])
{
unsigned int i = 0;
while (str[i] != '\0')
{
i++;
}
return i;
}
int main()
{
char str[] = "hello";
printf("字符串的长度为%u", myStrlen(str));
return 0;
}
- 当然我们平常肯定用内置的函数
1-3 常用字符串函数
- C 语言在 string.h 中提供了许多字符串处理函数。
c
#include <string.h>
- 常用函数如下:
| 函数 | 作用 |
|---|---|
strlen(str) |
计算字符串长度 |
strcpy(a,b) |
字符串复制 |
strcat(a,b) |
字符串连接 |
strcmp(a,b) |
字符串比较 |
strlen:计算字符串长度(不计算 \0)
c
char str[] = "hello";
printf("%d", strlen(str));
sizeof(str) //-> 包含 '\0' 的数组总大小
strlen(str) //-> 不包括 '\0' 的字符长度
strcpy:字符串复制。
c
char a[20];
char b[] = "hello";
strcpy(a, b);
strcat:字符串连接.
c
char a[20] = "hello ";
char b[] = "world";
strcat(a, b);
strcmp:字符串比较
c
printf("%d", strcmp("abc","abd"));
| 返回值 | 含义 |
|---|---|
| 0 | 两字符串相等 |
| >0 | a > b |
| <0 | a < b |
1-4 字符串数组
- 可以定义字符串数组存储多个字符串。
c
char str[3][10] = {
"apple",
"banana",
"orange"
};
2 转义字符
2-1 定义
- 在字符串中,有些字符 无法直接通过键盘输入 ,或者具有 特殊含义 ,这时就需要使用 转义字符(Escape Character)。
- 转义字符以 反斜杠
\开头。例如:
c
printf("hello\nworld");
- 其中的
\n表示换行符。
2-2 常见转义字符
| 转义字符 | 含义 |
|---|---|
\n |
换行 |
\t |
水平制表符(Tab) |
\\ |
输出反斜杠 \ |
\" |
输出双引号 " |
\' |
输出单引号 ' |
\0 |
字符串结束符 |
- 举例:
c
printf("hello\nworld\n");
printf("hello\tworld\n");
printf("\\n 表示换行\n");
- 输出:
c
hello
world
hello world
\n 表示换行
2-3 八进制转义字符
- 转义字符还可以表示 ASCII 码字符。格式如下:
c
\ooo
- 其中
ooo为 1~3 位八进制数 - 举例:
c
printf("\141\n");
- 输出结果为:
a,因为141(八进制) = 97(十进制),而ASCII 码97对应字符为a
2-4 十六进制转义字符
- 格式如下:
c
\xhh
- 其中:
hh为十六进制数
- 举例:
c
printf("\x61\n");
- 输出结果为:
a,因为61(十六进制)= 97(十进制)
2-5 复杂例子
- 我们来看一个稍复杂的例子:
c
char s[] = "a\128b\\\tcd\xdg\n";
printf("%d", strlen(s));
- 展开分析为:
c
a
\12 //(八进制转义字符只能使用数字 0~7)
8
b
\ //(//转义为一个/)
\t
c
d
\xd //十六进制转义字符只能使用0~9 A~F a~f
g
\n
- 故答案为
11
3 字符串输入
- 在 C 语言中,字符串本质是字符数组 ,所以输入字符串的方式和数组类似。常用方法主要有:
scanfgets(不推荐)fgets(安全推荐)
3-1 使用 scanf 输入字符串
c
#include <stdio.h>
int main()
{
char str[20];
printf("请输入字符串:");
scanf("%s", str);
printf("你输入的字符串是:%s\n", str);
return 0;
}
- 注意事项:
%s会自动在字符串末尾加上\0- 遇到空格就结束输入
3-2 使用 gets(不推荐)
c
char str[20];
gets(str);
- 特点:
- 可以读取空格
- 遇到换行符
\n就停止读取 - 不安全:无法限制输入长度,容易导致缓冲区溢出
gets函数已经被弃用
3-3 使用 fgets(安全推荐)
c
#include <stdio.h>
int main()
{
char str[20];
printf("请输入字符串:");
fgets(str, sizeof(str), stdin);
printf("你输入的字符串是:%s", str);
return 0;
}
-
说明:
- 第一个参数是存储字符串的数组
- 第二个参数是 数组大小
- 第三个参数是输入流,一般为
stdin - 遇到换行符
\n就停止读取 - 会把换行符
\n一起读入,如果不想要可以用:
c
str[strcspn(str, "\n")] = '\0';
4 字符串与字符数组
4-1 定义区别
- 字符数组:
c
char str1[] = {'h','e','l','l','o','\0'};
- 字符串字面量指针:
c
char *str2 = "hello";
| 特性 | char str[] |
char *str |
|---|---|---|
| 存储位置 | 数组在栈(局部变量)或全局区 | 字符串字面量在只读区 |
| 是否可修改 | 可修改(如 str[0]='H') |
不可修改(修改会导致未定义行为) |
| 长度计算 | 可用 sizeof(str) 得到数组大小 |
sizeof(str) 得到指针大小,需要 strlen(str) |
4-2与函数结合
- 数组名作为实参传递:
c
void printArray(char arr[]) { ... }
printArray(str1); // 传递的是首元素地址
- 修改函数内部的内容会影响原数组:
c
void modify(char arr[]) {
arr[0] = 'H';
}
modify(str1); // str1[0]变为'H'
- 字符串字面量指针传入函数:
c
void modify(char *s) {
s[0] = 'H'; // 未定义行为,通常会崩溃
}
modify(str2);
5 编译预处理

5-1 定义
- 编译预处理 (Preprocessing)是 C 语言编译的第一步,在正式编译前会进行:
- 宏定义展开
- 头文件包含
- 条件编译
- 行号信息处理
- 预处理由 预处理器(cpp) 完成,指令以
#开头。
5-2 常用预处理指令
| 指令 | 作用 | 示例 |
|---|---|---|
#define |
定义宏 | #define PI 3.14 |
#undef |
取消宏定义 | #undef PI |
#include |
引入头文件 | #include <stdio.h> |
#if / #ifdef / #ifndef / #else / #elif / #endif |
条件编译 | 见示例 |
#error |
报错信息 | #error "必须定义宏" |
#pragma |
特定编译器指令 | #pragma once |
5-3 宏函数
- 宏函数 (Macro Function)其实是 通过
#define定义的带参数的宏 ,它本质上并不是函数,而是 在预处理阶段进行文本替换。 - 语法:
c
#define 宏名(参数1, 参数2, ...) 替换内容
- 例子:
c
#define SQUARE(x) ((x)*(x))
- 这里
SQUARE(x)是宏函数 (x)*(x))防止宏被放到更复杂表达式中时优先级错误。- 宏展开时会直接替换:
c
int a = 5;
int b = SQUARE(a); // 编译前会变成 ((a)*(a))
| 特性 | 说明 |
|---|---|
| 预处理展开 | 宏函数在编译前被展开为文本,不会生成函数调用 |
| 无类型检查 | 宏参数不会检查类型,容易出错 |
| 效率高 | 没有函数调用开销,但可能导致代码膨胀 |
| 可能产生副作用 | 如果参数中有表达式,可能被重复计算 |
- 总之这东西一般不太可能会用((了解为主))
| 特性 | 宏函数 | 普通函数 |
|---|---|---|
| 调用开销 | 无 | 有函数调用开销 |
| 类型检查 | 无 | 有类型检查 |
| 编译阶段 | 预处理阶段 | 编译/链接阶段 |
| 表达式副作用 | 可能重复计算 | 安全,不重复 |
5-3-1 宏函数题目(为什么会考这种啊喂)
- 定义一个宏,用于判断所给出年份是否喂闰年
c
#define LEAP_YEAR(y) ( (((y)%100 != 0) && ((y)%4 == 0)) || ((y)%400 == 0) )
5-4 条件编译
- C 语言提供几个常用的条件编译指令:
| 指令 | 作用 |
|---|---|
#if 常量表达式 |
如果表达式非零,编译该代码块 |
#ifdef 宏名 |
如果宏已定义,编译该代码块 |
#ifndef 宏名 |
如果宏未定义,编译该代码块 |
#elif 常量表达式 |
#if 的 else-if 分支 |
#else |
前面的条件都不满足时编译该代码块 |
#endif |
结束条件编译块 |
5-4-1ifdef
c
#include <stdio.h>
#define DEBUG
int main() {
#ifdef DEBUG
printf("调试模式开启\n");
#endif
printf("程序运行\n");
return 0;
}
- 输出:
c
调试模式开启
程序运行
- 如果注释掉
#define DEBUG,就不会输出"调试模式开启"。
5-4-2 ifndef
#ifdef/#ifndef仅检查宏是否定义,不考虑宏值
c
#include <stdio.h>
#ifndef VERSION
#define VERSION 1
#endif
int main() {
printf("版本号: %d\n", VERSION);
return 0;
}
- 如果
VERSION未定义,则定义为 1。 - 如果已经定义,则保留原定义。
5-4-3 #if / #elif / #else / #endif
#if/#elif需要常量表达式,不可以直接写变量。
c
#include <stdio.h>
#define PLATFORM 2
int main() {
#if PLATFORM == 1
printf("Windows 平台\n");
#elif PLATFORM == 2
printf("Linux 平台\n");
#else
printf("其他平台\n");
#endif
return 0;
}
5-5 文件包含
- 系统头文件:
c
#include <stdio.h>
- 用户自定义头文件:
c
#include "myheader.h"
- 区别:
< >:系统目录查找" ":先当前目录,再系统目录查找
5-6 常用头文件
| 头文件 | 主要内容 |
|---|---|
<stdio.h> |
输入输出函数,如 printf(), scanf(), fgets(), fputs() |
<stdlib.h> |
常用工具函数,如 malloc(), free(), exit(), atoi(), rand() |
<string.h> |
字符串处理函数,如 strlen(), strcpy(), strcat(), strcmp(), memset(),memcpy(), memcmp() |
<math.h> |
数学函数,如 sqrt(), pow(), sin(), cos(), fabs() |
<ctype.h> |
字符处理函数,如 isdigit(), isalpha(), toupper(), tolower() |
<time.h> |
时间函数,如 time(), clock(), difftime(), strftime() |
<limits.h> |
定义各种数据类型的边界值,如 INT_MAX, CHAR_MIN |
<float.h> |
定义浮点数相关的边界,如 FLT_MAX, DBL_MIN |
<stdbool.h> |
布尔类型支持:true 和 false |
<assert.h> |
断言宏 assert(),用于调试 |
小结
- 本节主要介绍了 C 语言的 字符串使用 、转义字符 、字符串输入与数组区别 ,以及 编译预处理相关内容 ,包括 宏定义、宏函数、条件编译和头文件包含。
- 下一节我们将从
结构体开始 - 如有错误,欢迎指出!
