
🔥铅笔小新z:个人主页
🎬博客专栏:C语言
💫滴水不绝,可穿石;步履不休,能至渊。

前言
大家好!今天这篇博客是为 零基础 的小伙伴准备的 C 语言入门教程。我们将从:"C语言常见概念"和"C语言数据类型和变量" ,给大家做一个详细的、通俗易懂的总结。
第一部分:C语言常见概念
1. C语言是什么?
人和人之间交流用的是自然语言,比如汉语、英语、日语。
那么人和计算机怎么交流呢?答案是使用计算机语言。
C 语言就是众多计算机语言中的一种,和它同类的还有 C++、Java、Go、Python 等。我们通过用这些语言编写程序,给计算机下达指令,让计算机按照我们的要求去工作。
简单理解: C 语言就像一门"外语",我们用这门"外语"告诉计算机该做什么。
2. C语言的历史和辉煌
C 语言最初是作为 Unix 操作系统 的开发工具而发明的。它诞生于上世纪 70 年代,虽然年纪很大了,但直到今天,它依然是全世界最流行的编程语言之一。
想知道 C 语言有多火?可以去看看 TIOBE 编程语言排行榜:https://www.tiobe.com/tiobe-index/
C 语言影响了很多后来的编程语言(C++、Java、C#、Python 等),所以学好 C 语言,后面学其他语言会轻松很多!
3. 编译器的选择 ------ VS2022
3.1 编译和链接
C 语言是一门 编译型 计算机语言。我们写的 C 语言源代码是文本文件(后缀名为 .c),文本文件本身是无法直接执行的。必须经过两个步骤:
- 编译 ------ 编译器把
.c源文件翻译成.obj目标文件 - 链接 ------ 链接器把多个
.obj目标文件和库文件链接在一起,生成最终的.exe可执行文件
流程如下:
test.c ──┐
add.c ──┤──> 编译器(cl.exe) ──> test.obj + add.obj ──> 链接器(link.exe) ──> xxx.exe
xxx.c ──┘
补充说明:
- 每个
.c源文件会单独 经过编译器处理,生成对应的.obj目标文件 - 多个目标文件和库文件再经过链接器处理,最终生成
.exe可执行文件 - 这就是在 Windows 电脑上 C 语言程序变成可执行文件的全过程
3.2 常见编译器对比
C 语言的编译器有很多种:
| 编译器 | 特点 |
|---|---|
| MSVC | Visual Studio 自带的编译器,功能强大 |
| Clang | XCode 集成的编译器(苹果电脑使用) |
| GCC | Linux 系统上常用的编译器,开源免费 |
除了编译器本身,还有集成开发环境(IDE),它把代码编辑器、编译器、调试器、图形界面等工具集成在一起,方便我们开发:
| IDE | 集成的编译器 | 特点 |
|---|---|---|
| VS2022 | MSVC | 安装简单,无需额外配置,界面中文,初学者友好 |
| XCode | Clang | 苹果电脑专用 |
| CodeBlocks | GCC | 需要自己配置环境,不太推荐新手 |
| DevC++ | GCC | 小巧但过于简单,不适合养成良好代码风格 |
| Clion | 可配置 | 收费工具 |
推荐: 初学者选择 VS2022 社区版(免费!安装方便!中文界面!工作中也常见!)
VS2022 安装教程:https://www.bilibili.com/video/BV11R4y1s7jz/
3.3 VS2022 的优缺点
优点:
- 主流的集成开发环境,企业中使用普遍
- 包含了编辑器 + 编译器 + 调试器,功能强大
- 安装即可使用,基本不用额外配置环境
- 默认界面是中文的,对初学者友好
缺点:
- 功能丰富导致安装包较大,占用磁盘空间较多
4. VS项目、源文件和头文件
在 VS 上写代码,需要先创建一个项目(就像写作文要先新建一个 Word 文档一样)。
在项目中可以添加两种文件:
- 源文件 :以
.c为后缀,里面放的是我们写的 C 代码 - 头文件 :以
.h为后缀,里面放的是函数声明、宏定义等(后文会用到)
5. 第一个C语言程序
来看我们的第一个 C 语言程序:
c
#include <stdio.h> // 引入标准输入输出库,printf函数就在这个库里
int main() // main是程序的入口函数,程序从这里开始执行
{
printf("hello C\n"); // printf是打印函数,在屏幕上显示文字
return 0; // 程序执行完毕,返回0表示正常结束
}
在 VS2022 上运行代码的快捷键: Ctrl + F5
6. main函数 ------ 程序的入口
重点来了!每个 C 语言程序,不管有多少行代码,都是从 main 函数开始执行的。
关于 main 函数,需要记住三个要点:
main函数是程序的入口 ------ 程序启动后第一个执行的就是它main函数有且仅有一个 ------ 一个程序只能有一个入口- 即使项目中有多个
.c文件,也只能有一个main函数
main 前面的 int 表示 main 函数执行结束后要返回一个整数值。所以在 main 的最后要写 return 0;,表示"一切正常,程序结束"。
新手第一次写代码常见错误:
main写成了mian(拼写错误)main后面的()漏掉了- 代码中使用了中文符号(比如中文括号
()或中文分号;) - 语句末尾忘记写
;分号
7. printf 和库函数
7.1 printf 的基本使用
printf 是一个库函数,它的作用是在屏幕上打印信息。
c
printf("hello C\n"); // 打印字符串"hello C",\n 表示换行
printf 也可以打印其他类型的数据:
c
int n = 100;
printf("%d\n", n); // %d 是占位符,表示这里要放一个整数
printf("%c\n", 'q'); // %c 是占位符,表示这里要放一个字符
printf("%lf\n", 3.14); // %lf 是占位符,表示这里要放一个双精度浮点数
这里的 %d、%c、%lf 等叫做占位符,它们会被后面的值替换掉。
7.2 什么是库函数?
库函数就是 C 语言标准规定好的一组函数,由不同的编译器厂商根据标准实现,提供给程序员使用。这些函数组成了一个标准库。
使用库函数时需要包含对应的头文件,比如:
printf函数需要包含#include <stdio.h>strlen函数需要包含#include <string.h>
8. 关键字介绍
关键字(也叫保留字)是 C 语言中预留的、有特殊意义的单词。比如 int、if、return 等。
规则:
- 关键字都有特殊的意义,是保留给 C 语言使用的
- 程序员自己在起名字(标识符)的时候,不能和关键字重复
- 关键字也不能自己创建
C 语言共有 32 个关键字:
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
在 C99 标准中又加入了
inline、restrict、_Bool、_Complex、_Imaginary等关键字
9. 字符和ASCII编码
在键盘上敲出的各种符号,比如 a、q、@、# 等,都叫做字符 。在 C 语言中,字符用单引号 括起来,例如:'a'、'b'、'@'。
计算机里所有的数据都是以二进制(0 和 1)存储的,那字符在内存中怎么存呢?
为了解决这个问题,美国国家标准学会(ANSI)制定了一个标准 ------ ASCII 编码。C 语言中的字符就遵循 ASCII 编码。
我们需要记住几组特殊的 ASCII 码值:
| 字符范围 | ASCII 码值范围 |
|---|---|
'A' ~ 'Z' 大写字母 |
65 ~ 90 |
'a' ~ 'z' 小写字母 |
97 ~ 122 |
'0' ~ '9' 数字字符 |
48 ~ 57 |
换行符 '\n' |
10 |
小技巧: 同一个字母的大小写 ASCII 码值相差 32。比如 'A' 是 65,'a' 是 97,差 32。
不可打印字符: ASCII 码值 0~31 这 32 个字符无法在屏幕上显示,称为不可打印字符。
演示代码:
c
#include <stdio.h>
int main()
{
printf("%c\n", 'Q'); // %c 打印字符 Q
printf("%c\n", 81); // 81 是字符 Q 的 ASCII 码值,也能打印出 Q
return 0;
}
再来一个打印所有可打印字符的代码(32~127 是标准 ASCII 中可打印的部分):
c
#include <stdio.h>
int main()
{
int i = 0;
for (i = 32; i <= 127; i++) // 从32循环到127
{
printf("%c ", i); // 打印ASCII码对应的字符
if (i % 16 == 15) // 每打印16个字符换一行
printf("\n");
}
return 0;
}
10. 字符串和 \0
10.1 字符串的表示
字符串就是用双引号 括起来的一串字符,比如 "abcdef"。
打印字符串可以使用 %s 占位符:
c
#include <stdio.h>
int main()
{
printf("%s\n", "hello C"); // %s 打印字符串
printf("hello c"); // 也可以直接打印
return 0;
}
10.2 字符串的结束标志 \0
C 语言中有一个很重要的概念:字符串末尾隐藏着一个 \0 字符,它是字符串的结束标志。
比如字符串 "abcdef",我们表面上看到了 6 个字符 a b c d e f,但实际上末尾还藏着一个 \0(占一个字符位置)。
当 printf 打印字符串或 strlen 计算字符串长度时,遇到 \0 就自动停止了。
来验证一下:
c
#include <stdio.h>
int main()
{
// arr1 数组中只放了 3 个字符:a, b, c ------ 没有 \0
char arr1[] = {'a', 'b', 'c'};
// arr2 数组中存放的是字符串 "abc" ------ 末尾自动有 \0
char arr2[] = "abc";
printf("%s\n", arr1); // 打印完 a b c 后还会继续打印随机值(因为没有 \0 停止)
printf("%s\n", arr2); // 打印完 abc 遇到 \0 就正常停止
return 0;
}
如果在 arr1 末尾手动加一个 '\0':
c
#include <stdio.h>
int main()
{
char arr1[] = {'a', 'b', 'c', '\0'}; // 手动加上 \0
char arr2[] = "abc"; // 自动有 \0
printf("%s\n", arr1); // 打印 abc,遇到 \0 停止
printf("%s\n", arr2); // 打印 abc,遇到 \0 停止
printf("%s\n", "abc\0def"); // 只打印 abc,遇到 \0 就停了
return 0;
}
结论: \0 虽然看不见,但它非常重要,是字符串的"终止信号"!
11. 转义字符
转义字符,顾名思义,就是转变原来意思的字符。
比如字符 n,本来的意思就是字母 n,但如果前面加上一个反斜杠变成 \n,意思就变了 ------ \n 表示换行。
c
#include <stdio.h>
int main()
{
printf("abcndef"); // 普通打印,n就是字母n
return 0;
}
// 输出: abcndef
c
#include <stdio.h>
int main()
{
printf("abc\ndef"); // \n 表示换行
return 0;
}
// 输出:
// abc
// def
C 语言中常见的转义字符:
| 转义字符 | 含义 | 说明 |
|---|---|---|
\' |
单引号 | 用于表示字符常量 ' |
\" |
双引号 | 用于表示字符串内部的 " |
\\ |
反斜杠 | 表示一个真正的 \ |
\n |
换行符 | 光标移到下一行开头 |
\r |
回车符 | 光标移到同一行开头 |
\t |
制表符 | 跳到下一个制表位(通常是4或8的倍数) |
\b |
退格符 | 光标回退一个字符 |
\a |
警报 | 终端发出警报声或闪烁 |
\0 |
空字符 | 字符串结束标志,ASCII值为0 |
\ddd |
八进制表示的字符 | ddd 是 1~3 个八进制数字,如 \130 表示 'X' |
\xdd |
十六进制表示的字符 | dd 是 2 个十六进制数字,如 \x30 表示 '0' |
演示代码:
c
#include <stdio.h>
int main()
{
printf("%c\n", '\''); // 打印单引号 '
printf("%s\n", "\""); // 打印双引号 "
printf("c:\\test\\code\\test.c\n"); // 打印文件路径
printf("\a"); // 让电脑"嘀"一声
printf("%c\n", '\130'); // 130是8进制=88(十进制),对应ASCII码88是字符'X'
printf("%c\n", '\x30'); // x30是16进制=48(十进制),对应ASCII码48是字符'0'
return 0;
}
12. 语句和语句分类
C 语言的代码由一条一条的语句构成。C 语言中的语句分为五类:
12.1 空语句
一个单独的分号就是空语句,表示这里需要一条语句但什么都不做:
c
#include <stdio.h>
int main()
{
; // 空语句:什么都不做
return 0;
}
12.2 表达式语句
表达式后边加上分号就是表达式语句:
c
#include <stdio.h>
int main()
{
int a = 20;
int b = 0;
b = a + 5; // 表达式语句:先计算 a+5,再把结果赋给 b
return 0;
}
12.3 函数调用语句
调用函数时加上分号就是函数调用语句:
c
#include <stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
printf("hehe\n"); // 函数调用语句:调用 printf 打印
int ret = Add(2, 3); // 函数调用语句:调用 Add 函数求和
return 0;
}
12.4 复合语句
成对的大括号 { } 中的代码构成一个代码块,叫复合语句:
c
#include <stdio.h>
void print(int arr[], int sz) // 函数的大括号内的代码就是复合语句
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
int i = 0;
int arr[10] = {0};
for (i = 0; i < 10; i++) // for 循环体的大括号内也是复合语句
{
arr[i] = 10 - i;
printf("%d\n", arr[i]);
}
return 0;
}
12.5 控制语句
控制语句用于控制程序的执行流程。C 语言支持三种结构:
- 顺序结构 ------ 代码从上到下依次执行
- 选择结构 ------ 根据条件判断执行哪段代码(
if、switch) - 循环结构 ------ 重复执行某段代码(
while、for、do while)
控制语句分为三类:
- 条件判断语句(分支语句):
if、switch - 循环执行语句:
do while、while、for - 转向语句:
break、goto、continue、return
这些内容会在后面的课程中详细介绍。
13. 注释
13.1 注释是什么?
注释是对代码的说明文字,编译器会忽略注释,所以注释对实际代码的执行没有影响。
写注释是为了让程序员(包括未来的自己)能看懂代码。好的注释能帮助理解代码,但也不要过度注释。
13.2 注释的两种形式
形式一:/* */ 风格(C89 标准支持)
c
/* 这是一行注释 */
/*
这是多行注释
可以写多行内容
*/
还可以插在行内:
c
int fopen(char* s /* file name */, int mode); // 在行内注释参数的含义
但要注意:
- 一定不能忘记写结束符
*/,否则注释会一直延续下去 - 不支持嵌套注释(
/*遇到第一个*/就认为结束了)
形式二:// 风格(C99 标准新增)
c
// 这是一行注释
int x = 1; // 这也是注释(放在行尾)
这种注释只能写一行,从 // 到行尾都是注释。
特别注意: 注释不能放在双引号里面!双引号里面的 // 和 /* */ 都会成为字符串的一部分,失去注释作用。
c
printf("// hello /* world */ "); // 这里面的 // 和 /* */ 都是普通字符
13.3 注释的替换
编译时,注释会被替换成一个空格。所以:
c
min/* 这里是注释 */Value
// 编译后会变成: min Value(中间多了一个空格)
// 而不是: minValue
第二部分:C语言数据类型和变量
1. 数据类型介绍
C 语言提供了丰富的数据类型来描述生活中的各种数据:
- 整型类型 ------ 描述整数(如 100, -50, 0)
- 字符类型 ------ 描述字符(如 'a', 'b', '@')
- 浮点类型 ------ 描述小数(如 3.14, -0.5)
所谓"类型",本质上是相似数据的共同特征描述。编译器只有知道了数据的类型,才知道怎么操作这些数据。
1.1 字符型
c
char // 字符类型,用于存储单个字符
[signed] char // 有符号的字符类型(可正可负)
unsigned char // 无符号的字符类型(只有非负值)
1.2 整型
c
// 短整型
short [int] // 相当于 short int,可简写为 short
[signed] short [int] // 有符号短整型
unsigned short [int] // 无符号短整型
// 整型
int // 最常用的整数类型
[signed] int // 有符号整型(int 默认就是 signed)
unsigned int // 无符号整型
// 长整型
long [int] // 比 int 更长的整数类型
[signed] long [int] // 有符号长整型
unsigned long [int] // 无符号长整型
// 更长的整型(C99 标准引入)
long long [int] // 比 long 更长的整数类型
[signed] long long [int] // 有符号长长整型
unsigned long long [int] // 无符号长长整型
[ ]中的内容表示可以省略,比如short int可以简写为short。
1.3 浮点型
c
float // 单精度浮点型(精度较低,占4字节)
double // 双精度浮点型(精度较高,占8字节)
long double // 更高精度的浮点型
1.4 布尔类型(C99 引入)
布尔类型专门用来表示真 和假。
在 C 语言中:
- 0 表示假(false)
- 非零值 表示真(true)
C99 标准引入了 _Bool 类型:
c
#include <stdbool.h> // 使用布尔类型需要包含这个头文件
int main()
{
_Bool flag = true; // 或者直接用 bool flag = true;
if (flag) // 如果 flag 为真
{
printf("i like C\n");
}
return 0;
}
为了方便,
stdbool.h头文件把_Bool定义为bool,把0定义为false,把1定义为true。
1.5 各种数据类型的长度
不同的数据类型占用的内存大小不同,用 sizeof 操作符可以测量。
1.5.1 sizeof 操作符
sizeof 是一个关键字(也是操作符),用来计算类型或变量的长度,单位是字节。
c
sizeof(类型) // 计算类型的长度
sizeof 表达式 // 计算表达式的长度(如果是变量,括号可省略)
注意:
sizeof后面的表达式是不真实参与运算的,它只根据表达式的类型来算出大小sizeof的计算结果是size_t类型(一种无符号整数类型)- 打印
size_t类型使用%zd
示例代码:
c
#include <stdio.h>
int main()
{
int a = 10;
printf("%zd\n", sizeof(a)); // 计算变量a的长度
printf("%zd\n", sizeof a); // 变量名可以省略括号
printf("%zd\n", sizeof(int)); // 计算int类型的长度
printf("%zd\n", sizeof(3 + 3.5)); // 计算表达式的长度
return 0;
}
1.5.2 各类型的具体长度
在 VS2022 X64 配置下,各数据类型的长度如下:
c
#include <stdio.h>
int main()
{
printf("%zd\n", sizeof(char)); // 输出: 1
printf("%zd\n", sizeof(_Bool)); // 输出: 1
printf("%zd\n", sizeof(short)); // 输出: 2
printf("%zd\n", sizeof(int)); // 输出: 4
printf("%zd\n", sizeof(long)); // 输出: 4
printf("%zd\n", sizeof(long long)); // 输出: 8
printf("%zd\n", sizeof(float)); // 输出: 4
printf("%zd\n", sizeof(double)); // 输出: 8
printf("%zd\n", sizeof(long double)); // 输出: 8
return 0;
}
总结各类型占用字节数:
| 数据类型 | 字节数 | 说明 |
|---|---|---|
char |
1 | 字符型 |
_Bool |
1 | 布尔型 |
short |
2 | 短整型 |
int |
4 | 整型 |
long |
4 | 长整型(在64位Windows上为4字节) |
long long |
8 | 长长整型 |
float |
4 | 单精度浮点型 |
double |
8 | 双精度浮点型 |
long double |
8 | 更高精度浮点型 |
1.5.3 sizeof 中的表达式不计算
来看一个有趣的例子:
c
#include <stdio.h>
int main()
{
short s = 2;
int b = 10;
printf("%d\n", sizeof(s = b + 1)); // sizeof 只关心s的类型(short=2字节)
printf("s = %d\n", s); // s 还是 2,因为表达式没执行!
return 0;
}
为什么? 因为 sizeof 在编译期间 就已经根据表达式的类型确定了大小。s = b + 1 这个表达式虽然写在 sizeof 里,但它根本不会在运行时执行 !所以 s 的值仍然是 2。
2. signed 和 unsigned
C 语言使用 signed 和 unsigned 关键字来修饰字符型和整型:
signed------ 带有正负号,可以表示负数unsigned------ 不带有正负号,只能表示零和正数
c
signed int a; // 等同于 int a;(int 默认就是 signed)
unsigned int a; // 无符号整型,只能存非负数
unsigned 的好处: 同样长度的内存,能表示的最大正数值增大了一倍。
比如 short 在内存中占 2 字节(16 位):
signed short:取值范围 -32768 ~ 32767,最大 32767unsigned short:取值范围 0 ~ 65535,最大 65535
再看一下 limits.h 头文件中定义的常量:
c
#define SHRT_MIN (-32768) // short 最小值
#define SHRT_MAX 32767 // short 最大值
#define USHRT_MAX 0xffff // unsigned short 最大值
#define INT_MIN (-2147483647 - 1) // int 最小值
#define INT_MAX 2147483647 // int 最大值
字符类型 char 也可以设置 signed 和 unsigned:
c
signed char c; // 范围: -128 ~ 127
unsigned char c; // 范围: 0 ~ 255
特别注意: C 语言规定 char 类型默认是否带正负号由系统决定 。也就是说,char 不一定等同于 signed char,它可能是 signed 也可能是 unsigned。这一点与 int 不同(int 就是等同于 signed int)。
3. 数据类型的取值范围
不同的类型有不同的取值范围。为什么 C 语言要提供这么多种整型(short、int、long、long long)呢?
答案很简单:让我们在合适的场景选择合适的大小 。如果知道一个数不会很大,用 short 可以节省内存;如果不知道,用 int 最保险。
要查看当前系统上的极限值:
- 整型的取值范围 :定义在
limits.h头文件中 - 浮点型的取值范围 :定义在
float.h头文件中
常用常量:
| 常量 | 含义 |
|---|---|
SCHAR_MIN / SCHAR_MAX |
signed char 的最小/最大值 |
SHRT_MIN / SHRT_MAX |
short 的最小/最大值 |
INT_MIN / INT_MAX |
int 的最小/最大值 |
LONG_MIN / LONG_MAX |
long 的最小/最大值 |
LLONG_MIN / LLONG_MAX |
long long 的最小/最大值 |
UCHAR_MAX |
unsigned char 的最大值 |
USHRT_MAX |
unsigned short 的最大值 |
UINT_MAX |
unsigned int 的最大值 |
ULONG_MAX |
unsigned long 的最大值 |
ULLONG_MAX |
unsigned long long 的最大值 |
为了保证代码的可移植性,在需要知道某种整数类型的极限值时,应该尽量使用这些常量,而不是自己写死数字。
4. 变量
4.1 变量的创建
类型是用来创建变量 的。什么是变量?经常变化的值称为变量,不变的值称为常量。
变量创建的语法:
数据类型 变量名;
c
int age; // 创建一个整型变量,名字叫 age
char ch; // 创建一个字符变量,名字叫 ch
double weight; // 创建一个浮点型变量,名字叫 weight
创建变量时就给一个初始值,叫做初始化:
c
int age = 18; // 初始化 age 为 18
char ch = 'w'; // 初始化 ch 为字符 'w'
double weight = 48.0; // 初始化 weight 为 48.0
unsigned int height = 100; // 初始化 height 为 100(无符号整型)
4.2 变量的分类
变量分为两大类:
(1)全局变量: 在大括号 { } 外部定义的变量
c
#include <stdio.h>
int global = 2023; // 全局变量:定义在 main 函数的外面
int main()
{
int local = 2018; // 局部变量:定义在 main 函数的里面
printf("%d\n", local);
printf("%d\n", global);
return 0;
}
- 全局变量的作用范围更广,整个工程中都能使用
- 局部变量的作用范围比较局限,只能在它所在的局部范围内使用
(2)局部变量和全局变量同名时: 局部变量优先使用
c
#include <stdio.h>
int n = 1000; // 全局变量 n
int main()
{
int n = 10; // 局部变量 n(和全局变量同名)
printf("%d\n", n); // 输出的是 10,而不是 1000!
return 0;
}
因为当局部变量和全局变量同名时,局部变量优先。
(3)变量在内存中的存储区域:
学习 C/C++ 时,我们会关注内存中的三个区域:
| 区域 | 存放的内容 |
|---|---|
| 栈区 | 局部变量 |
| 静态区 | 全局变量 |
| 堆区 | 动态内存管理(后面会学) |
内存区域的划分其实更加细致,以后学到操作系统时会详细介绍。
5. 算术操作符:+ - * / %
写代码时一定会涉及计算。C 语言提供了一组算术操作符(也叫算术运算符):
| 操作符 | 名称 | 例子 |
|---|---|---|
+ |
加 | 5 + 3 结果是 8 |
- |
减 | 5 - 3 结果是 2 |
* |
乘 | 5 * 3 结果是 15 |
/ |
除 | 6 / 4 结果是 1(整数除法) |
% |
取模(求余) | 6 % 4 结果是 2 |
这些操作符都是双目操作符,因为它们有两个操作数(一个在左边,一个在右边)。
5.1 + 和 -
加法和减法:
c
#include <stdio.h>
int main()
{
int x = 4 + 22; // x = 26
int y = 61 - 23; // y = 38
printf("%d\n", x);
printf("%d\n", y);
return 0;
}
5.2 * 乘法
c
#include <stdio.h>
int main()
{
int num = 5;
printf("%d\n", num * num); // 输出: 25
return 0;
}
5.3 / 除法 ------ 重点!
不同点来了! 除法 / 在 C 语言中有两种行为:
情况一:整数除法(两端都是整数)
c
#include <stdio.h>
int main()
{
float x = 6 / 4; // 整数除法!6/4 = 1(丢弃小数部分)
int y = 6 / 4; // 整数除法!结果是 1
printf("%f\n", x); // 输出: 1.000000
printf("%d\n", y); // 输出: 1
return 0;
}
情况二:浮点数除法(至少一端是小数)
c
#include <stdio.h>
int main()
{
float x = 6.0 / 4; // 浮点数除法!6.0/4 = 1.5
printf("%f\n", x); // 输出: 1.500000
return 0;
}
注意! 整数除法的结果就是整数,小数部分会被直接丢弃,而不是四舍五入!
再看一个新手常犯的错误:
c
#include <stdio.h>
int main()
{
int score = 5;
score = (score / 20) * 100; // score/20 = 5/20 = 0(整数除法!)
// 0 * 100 = 0
return 0;
}
// 你期望 score 是 25,但实际上 score 是 0!
修改方法:把 20 改成 20.0
c
#include <stdio.h>
int main()
{
int score = 5;
score = (score / 20.0) * 100; // 浮点数除法!score/20.0 = 0.25
return 0;
}
// 这样 score 才会得到 25
5.4 % 取模(求余)
% 操作符返回两个整数相除的余数 ,只能用于整数,不能用于浮点数!
c
#include <stdio.h>
int main()
{
int x = 6 % 4; // 6 ÷ 4 = 1 余 2,所以结果是 2
return 0;
}
负数取模时的规则:结果的正负号由第一个运算数决定:
c
#include <stdio.h>
int main()
{
printf("%d\n", 11 % -5); // 结果是 1(正)
printf("%d\n", -11 % -5); // 结果是 -1(负)
printf("%d\n", -11 % 5); // 结果是 -1(负)
return 0;
}
6. 赋值操作符:= 和复合赋值
6.1 赋值操作符 =
创建变量时给一个值叫初始化 ,创建好变量后再给一个值叫赋值:
c
int a = 100; // 初始化
a = 200; // 赋值(使用 = 操作符)
连续赋值(从右向左依次赋值):
c
int a = 3;
int b = 5;
int c = 0;
c = b = a + 3; // 连续赋值:先把 a+3=6 赋给 b,再把 b 的值赋给 c
虽然 C 语言支持连续赋值,但建议拆开来写,代码更清晰,调试也方便:
c
int a = 3;
int b = 5;
int c = 0;
b = a + 3; // b = 6
c = b; // c = 6
6.2 复合赋值符
在写代码时,经常需要对一个数进行自增、自减操作:
c
int a = 10;
a = a + 3; // a 增加 3
a = a - 2; // a 减少 2
C 语言提供了更方便的写法------复合赋值符:
c
int a = 10;
a += 3; // 相当于 a = a + 3
a -= 2; // 相当于 a = a - 2
常见的复合赋值符:
| 复合赋值符 | 等价于 |
|---|---|
a += b |
a = a + b |
a -= b |
a = a - b |
a *= b |
a = a * b |
a /= b |
a = a / b |
a %= b |
a = a % b |
还有一些复合赋值符(如
>>=、<<=、&=、|=、^=)属于位操作,后面会学到。
7. 单目操作符:++ -- + -
前面介绍的都是双目操作符(有两个操作数),C 语言中还有一些只有一个操作数的操作符,叫单目操作符。
7.1 ++ 和 --
前置 ++
c
int a = 10;
int b = ++a; // ++ 放在 a 的前面,叫前置++
printf("a=%d b=%d\n", a, b); // 输出: a=11 b=11
口诀:先 +1,后使用
a 原来是 10,先 +1 变成 11,再把 11 赋给 b,所以 a 和 b 都是 11。
相当于:
c
int a = 10;
a = a + 1; // a 先变成 11
b = a; // 再把 11 赋给 b
后置 ++
c
int a = 10;
int b = a++; // ++ 放在 a 的后面,叫后置++
printf("a=%d b=%d\n", a, b); // 输出: a=11 b=10
口诀:先使用,后 +1
a 原来是 10,先把 10 赋给 b,然后 a 再 +1 变成 11。所以 a 是 11,b 是 10。
相当于:
c
int a = 10;
int b = a; // 先把 a 的值给 b
a = a + 1; // a 再自增
前置 --
和前置 ++ 同理,只是把加 1 换成了减 1:
c
int a = 10;
int b = --a; // 先 -1,后使用
printf("a=%d b=%d\n", a, b); // 输出: a=9 b=9
后置 --
和后置 ++ 同理,只是把加 1 换成了减 1:
c
int a = 10;
int b = a--; // 先使用,后 -1
printf("a=%d b=%d\n", a, b); // 输出: a=9 b=10
7.2 + 和 - 正负号
这里的 + 是正号,- 是负号,都是单目操作符:
c
int a = +10; // + 对数值没有影响,可省略
int a = 10; // 等价于上面
int b = -a; // - 会改变值的正负号:a=10 时 b=-10
int c = -10; // c = -10
int d = -(-10); // d = 10(负负得正)
8. 强制类型转换
强制类型转换的语法很简单:
c
(类型)
示例:
c
int a = 3.14; // 警告!3.14 是 double,a 是 int,类型不匹配
int a = (int)3.14; // 强制转换:把 3.14 转成 int(只取整数部分,a = 3)
注意: 强制类型转换只取整数部分,不进行四舍五入。
俗话说"强扭的瓜不甜",强制类型转换是万不得已时才使用的方法。如果不需要强制转换就能实现代码,自然更好。
9. scanf 和 printf 介绍
9.1 printf ------ 输出
9.1.1 基本用法
printf() 的作用是将文本输出到屏幕。名字里的 f 代表 format(格式化),表示可以定制输出格式。
c
#include <stdio.h>
int main()
{
printf("Hello World"); // 在屏幕上输出 Hello World
return 0;
}
注意: printf() 不会在行尾自动添加换行符,光标会停留在输出结束的位置。
如果要换行,需要手动添加 \n:
c
#include <stdio.h>
int main()
{
printf("Hello World\n"); // 输出后换行
return 0;
}
多行输出:
c
#include <stdio.h>
int main()
{
printf("Hello\nWorld\n"); // 两行输出
return 0;
}
9.1.2 占位符
占位符的意思是:这个位置可以先占着,后面用具体的值替换。
c
#include <stdio.h>
int main()
{
printf("There are %d apples\n", 3); // %d 被 3 替换
return 0;
}
// 输出: There are 3 apples
%d 表示这里要代入一个整数,%s 表示代入字符串:
c
#include <stdio.h>
int main()
{
printf("%s will come tonight\n", "zhangsan");
return 0;
}
// 输出: zhangsan will come tonight
多个占位符:
c
#include <stdio.h>
int main()
{
printf("%s says it is %d o'clock\n", "lisi", 21);
return 0;
}
// 输出: lisi says it is 21 o'clock
规则: 有 n 个占位符,printf() 就应该有 n + 1 个参数(第一个参数是格式字符串)。如果参数个数少于占位符,可能会输出内存中的随机值!
9.1.3 常用占位符列表
| 占位符 | 对应类型 | 说明 |
|---|---|---|
%c |
char | 字符 |
%d |
int | 十进制整数 |
%i |
int | 整数,基本等同 %d |
%f |
float | 单精度浮点数 |
%lf |
double | 双精度浮点数 |
%Lf |
long double | 高精度浮点数 |
%s |
char* | 字符串 |
%p |
void* | 指针(地址) |
%u |
unsigned int | 无符号整数 |
%hd |
short | 短整型 |
%ld |
long | 长整型 |
%lld |
long long | 长长整型 |
%lu |
unsigned long | 无符号长整型 |
%llu |
unsigned long long | 无符号长长整型 |
%zd |
size_t | sizeof 的返回类型 |
%x |
int | 十六进制整数 |
%o |
int | 八进制整数 |
%e / %E |
double | 科学计数法 |
%% |
--- | 输出一个百分号 % |
9.1.4 输出格式定制
(1)限定宽度
c
#include <stdio.h>
int main()
{
printf("%5d\n", 123); // %5d 表示至少占5位,不足前面补空格
return 0;
}
// 输出: " 123"(前面有两个空格,总共占5位)
默认是右对齐 ,如果要左对齐 ,加 - 号:
c
#include <stdio.h>
int main()
{
printf("%-5d\n", 123); // %-5d 表示左对齐
return 0;
}
// 输出: "123 "(后面有两个空格)
(2)总是显示正负号
默认情况下,正数不显示 + 号,负数显示 - 号。如果想让正数也显示 + 号:
c
#include <stdio.h>
int main()
{
printf("%+d\n", 12); // 输出: +12
printf("%+d\n", -12); // 输出: -12
return 0;
}
(3)限定小数位数
c
#include <stdio.h>
int main()
{
printf("Number is %.2f\n", 0.5); // %.2f 保留两位小数
return 0;
}
// 输出: Number is 0.50
可以和限定宽度结合使用:
c
#include <stdio.h>
int main()
{
printf("%6.2f\n", 0.5); // 总宽度6位,小数2位
return 0;
}
// 输出: " 0.50"(前面有两个空格)
宽度和小数位数也可以用 * 代替,通过参数传入:
c
#include <stdio.h>
int main()
{
printf("%*.*f\n", 6, 2, 0.5); // 等同于 %6.2f
return 0;
}
(4)输出部分字符串
c
#include <stdio.h>
int main()
{
printf("%.5s\n", "hello world"); // %.5s 只输出前5个字符
return 0;
}
// 输出: hello
9.2 scanf ------ 输入
9.2.1 基本用法
有了变量,我们需要给变量输入值 ,这时就要用到 scanf 函数。
c
#include <stdio.h>
int main()
{
int score = 0; // 先初始化
printf("请输入成绩:"); // 提示用户输入
scanf("%d", &score); // 用户输入整数,存入 score 变量
printf("成绩是:%d\n", score); // 输出用户输入的值
return 0;
}
注意:
scanf的第一个参数是格式字符串,里面放占位符,告诉编译器如何解读用户的输入scanf的第二个参数前面要加&(取地址符号),因为scanf需要知道变量的地址才能把值存进去- 变量前面必须加
&(指针变量除外,比如字符串变量就不需要加&)
一次读取多个变量:
c
scanf("%d%d%f%f", &i, &j, &x, &y);
// 用户输入: 1 -20 3.4 -4.0e3
// 把 1 给 i,-20 给 j,3.4 给 x,-4.0e3 给 y
输入规则:
scanf处理数值占位符时,会自动过滤空白字符(空格、制表符、换行符)- 用户输入的数据之间有一个或多个空格不影响解读
- 用户按回车把输入分成几行也不影响解读
工作原理: 用户的输入先放入缓存 ,按下回车键后,scanf 按照占位符对缓存进行解读。解读时会从上一次停止的地方继续读取。
c
#include <stdio.h>
int main()
{
int x;
float y;
// 用户输入: " -13.45e12# 0"
scanf("%d", &x); // %d 忽略空格,读到 -13 停止(因为 . 不是整数有效字符)
printf("%d\n", x); // 输出: -13
scanf("%f", &y); // 从上一次停的地方继续,读到 .45e12
printf("%f\n", y); // 输出: -13.450000
return 0;
}
上面的代码也可以合并写:
c
#include <stdio.h>
int main()
{
int x;
float y;
// 用户输入: " -13.45e12# 0"
scanf("%d%f", &x, &y);
return 0;
}
9.2.2 scanf 的返回值
scanf() 的返回值是一个整数,表示成功读取的变量个数。
- 如果匹配失败或没有读取任何项,返回 0
- 如果在成功读取前遇到读取错误或文件结尾,返回 EOF(即 -1)
- EOF 是 End Of File(文件结束标志)的缩写
c
#include <stdio.h>
int main()
{
int a = 0;
int b = 0;
float f = 0.0f;
int r = scanf("%d %d %f", &a, &b, &f); // r 记录成功读取的个数
printf("a=%d b=%d f=%f\n", a, b, f);
printf("r = %d\n", r);
return 0;
}
在 VS 环境中,按 3 次
Ctrl+Z可以结束输入。
9.2.3 scanf 的占位符
与 printf 类似,scanf 的常用占位符:
| 占位符 | 说明 |
|---|---|
%c |
字符(不会忽略空白字符) |
%d |
整数 |
%f |
float 类型浮点数 |
%lf |
double 类型浮点数 |
%Lf |
long double 类型浮点数 |
%s |
字符串(遇到空白字符停止) |
%[] |
指定一组匹配字符,如 %[0-9] |
关于 %c 的特殊性: 除了 %c 以外,所有占位符都会自动忽略起首的空白字符 。%c 不会忽略空白字符,总是返回当前第一个字符。
如果要强制跳过空白字符读取字符:
c
scanf(" %c", &ch); // %c 前面加一个空格,表示跳过空白
关于 %s 的注意事项:
%s从第一个非空白字符开始读,遇到空白字符停止%s不会包含空白字符,所以无法读多个单词%s会在字符串末尾自动存储\0- 不会检测字符串是否超过了数组长度,可能导致数组溢出!
为了防止数组溢出,应该指定最大读取长度:
c
#include <stdio.h>
int main()
{
char name[11]; // 字符数组,长度为11
scanf("%10s", name); // %10s 最多读取10个字符,后面的丢弃
return 0;
}
9.2.4 赋值忽略符 *
有时候,用户的输入可能不符合预定的格式。比如我们期望用户输入 2020-01-01,但用户可能输入 2020/01/01。
c
#include <stdio.h>
int main()
{
int year = 0;
int month = 0;
int day = 0;
scanf("%d-%d-%d", &year, &month, &day); // 只能用 - 分隔
printf("%d %d %d\n", year, month, day);
return 0;
}
如果用户输入 2020/01/01,解析就会失败。解决办法是使用赋值忽略符 *:
c
#include <stdio.h>
int main()
{
int year = 0;
int month = 0;
int day = 0;
// %*c 会匹配一个字符但丢弃它(不赋值给任何变量)
scanf("%d%*c%d%*c%d", &year, &month, &day);
return 0;
}
这样无论用户用 - 还是 / 还是其他分隔符,%*c 都会读取一个字符并丢弃,我们只关心数字本身。
总结
到这里,我们已经把 C 语言第一讲和第二讲的知识点全部学完了!来回顾一下我们都学了什么:
一:C语言常见概念
- C 语言是什么 ------ 一门计算机语言
- C 语言的历史 ------ 用于开发 Unix 系统
- 编译器和 IDE 的选择 ------ 推荐 VS2022
- 源文件(.c)和头文件(.h)
- 第一个 C 程序 ------
#include <stdio.h>+main函数 printf和库函数- 32 个关键字
- ASCII 编码
- 字符串和
\0 - 转义字符
- 语句分类
- 注释
二:C语言数据类型和变量
- 数据类型:char、short、int、long、long long、float、double、_Bool
- signed 和 unsigned
- 数据类型长度(sizeof)
- 变量的创建和分类(全局 vs 局部)
- 算术操作符:
+ - * / % - 赋值操作符:
=和+=等复合赋值 - 单目操作符:
++ -- + - - 强制类型转换
printf占位符和格式控制scanf输入
这些都是 C 语言最最基础的知识,就像学汉字要先学笔画一样。把这些基础打牢,后面学函数、数组、指针就会轻松很多!
如果有不懂的地方,建议动手把代码敲一遍,编程是一门动手实践的技能,光看不练是学不会的。加油!🎉
