变量
C语⾔中把经常变化的值称为变量,不变的值称为常量。
变量命名的⼀般规则:
• 只能由字⺟(包括⼤写和⼩写)、数字和下划线(`_`)组成。
• 不能以数字开头。 • ⻓度不能超过63个字符。
• 变量名中区分⼤⼩写的。
• 变量名不能使⽤关键字。
变量的分类
• 全局变量:在⼤括号外部定义的变量就是全局变量 全局变量的使⽤范围更⼴,整个⼯程中想使⽤,都是有办法使⽤的。
• 局部变量:在⼤括号内部定义的变量就是局部变量 局部变量的使⽤范围是⽐较局限,只能在⾃⼰所在的局部范围内使⽤的。
变量的存储
内存中的三个区域:栈区、堆区、静态区
1. 栈区(Stack)
-
用途:存储局部变量、函数参数、函数返回地址等。
-
管理方式:由编译器自动分配和释放,遵循后进先出(LIFO)原则。
-
特点:
-
高效:分配和释放仅通过移动栈指针完成,速度快。
-
有限大小:栈空间通常较小(默认几MB,取决于系统),溢出会导致
Stack Overflow
错误。 -
生命周期:变量的生命周期与函数调用周期一致,函数结束时自动释放。
-
void func() { int x = 10; // x存储在栈区 char buffer[1024]; // 数组也在栈区 } // 函数结束,x和buffer自动释放
2. 堆区(Heap)
-
用途:动态分配的内存(如
malloc
、new
等)。 -
管理方式:由程序员手动分配和释放(或由垃圾回收机制管理,如Java/Python)。
-
特点:
-
灵活:空间远大于栈(受限于系统可用内存),可动态调整大小。
-
手动管理:需显式释放(C/C++中
free
/delete
),否则会导致内存泄漏。 -
碎片化:频繁分配/释放可能产生内存碎片。
-
-
int *p = (int*)malloc(100 * sizeof(int)); // 在堆区分配100个int
free(p); // 必须手动释放
3. 静态区(Static/Global Storage)
-
用途:存储全局变量、
static
变量、常量(如字符串常量)。 -
管理方式:在程序编译时分配,生命周期持续到程序结束。
-
细分区域:
-
.data段:已初始化的全局变量和静态变量。
-
.bss段:未初始化的全局变量和静态变量(程序启动时自动清零)。
-
.rodata段:只读数据(如字符串常量)。
-
-
特点:
-
持久性:变量在整个程序运行期间存在。
-
线程安全:全局变量需注意多线程同步问题。
-
int global_var = 1; // 已初始化全局变量(.data段)
static int static_var; // 未初始化静态变量(.bss段)
const char* str = "Hello"; // 字符串常量(.rodata段)
对比总结
特性 | 栈区 | 堆区 | 静态区 |
---|---|---|---|
管理方式 | 编译器自动管理 | 程序员手动管理 | 编译器/系统管理 |
生命周期 | 函数调用结束即释放 | 直到显式释放或程序结束 | 整个程序运行期间 |
大小限制 | 较小(MB级) | 大(GB级,受系统限制) | 编译时确定 |
访问速度 | 极快(CPU缓存友好) | 较慢(需指针间接访问) | 快(地址固定) |
典型数据 | 局部变量、函数参数 | 动态分配的对象、数组 | 全局变量、静态变量 |
注意
-
栈溢出:递归过深或局部变量过大(如大数组)会触发栈溢出。
-
内存泄漏:堆区分配后未释放会导致内存耗尽。
-
线程安全:静态区的全局变量在多线程中需加锁保护。
运算符
类别 | 操作符 | 名称 | 作用 | 示例 |
---|---|---|---|---|
算术操作符 | + | 加法 | 计算两个数的和 | a + b |
- | 减法 | 计算两个数的差 | a - b | |
* | 乘法 | 计算两个数的积 | a * b | |
/ | 除法 | 计算两个数的商(整数除法会截断小数部分) | a / b | |
% | 取模(求余) | 计算两个数相除的余数(仅适用于整数) | a % b | |
关系操作符 | == | 等于 | 判断两个值是否相等 | a == b |
!= | 不等于 | 判断两个值是否不相等 | a != b | |
> | 大于 | 判断左边的值是否大于右边的值 | a > b | |
< | 小于 | 判断左边的值是否小于右边的值 | a < b | |
>= | 大于等于 | 判断左边的值是否大于或等于右边的值 | a >= b | |
<= | 小于等于 | 判断左边的值是否小于或等于右边的值 | a <= b | |
逻辑操作符 | && | 逻辑与 | 如果两个操作数都为真(非零),则结果为真 | a && b |
|| | 逻辑或 | 如果任意一个操作数为真(非零),则结果为真 | a||b | |
! | 逻辑非 | 对操作数取反(真变假,假变真) | !a | |
位操作符 | & | 按位与 | 对两个数的二进制位进行与运算 | a & b |
| | 按位或 | 对两个数的二进制位进行或运算 | a|b | |
^ | 按位异或 | 对两个数的二进制位进行异或运算(相同为0,不同为1) | a ^ b | |
~ | 按位取反 | 对一个数的二进制位取反(0变1,1变0) | ~a | |
<< | 左移 | 将二进制位向左移动指定位数,低位补0 | a << 2 | |
>> | 右移 | 将二进制位向右移动指定位数(逻辑右移补0,算术右移补符号位) | a >> 2 | |
赋值操作符 | = | 赋值 | 将右边的值赋给左边的变量 | a = 10 |
+= | 加后赋值 | a += b 等价于 a = a + b | a += 5 | |
-= | 减后赋值 | a -= b 等价于 a = a - b | a -= 3 | |
*= | 乘后赋值 | a *= b 等价于 a = a * b | a *= 2 | |
/= | 除后赋值 | a /= b 等价于 a = a / b | a /= 4 | |
%= | 取模后赋值 | a %= b 等价于 a = a % b | a %= 3 | |
&= | 按位与后赋值 | a &= b 等价于 a = a & b | a &= 0xF | |
|= | 按位或后赋值 | a|= b等价于 a = a|b | a|=0x1 | |
^= | 按位异或后赋值 | a ^= b 等价于 a = a ^ b | a ^= 0xFF | |
<<= | 左移后赋值 | a <<= b 等价于 a = a << b | a <<= 1 | |
>>= | 右移后赋值 | a >>= b 等价于 a = a >> b | a >>= 2 | |
递增/递减 | ++ | 递增 | 变量值加1(a++ 后缀,++a 前缀) | a++ 或 ++a |
-- | 递减 | 变量值减1(a-- 后缀,--a 前缀) | a-- 或 --a | |
条件操作符 | ?: | 三元条件运算符 | 条件 ? 表达式1 : 表达式2 ,如果条件为真返回表达式1,否则返回表达式2 | a > b ? a : b |
逗号操作符 | , | 逗号运算符 | 从左到右计算多个表达式,返回最后一个表达式的值 | a = (b=3, b+2) → a=5 |
指针操作符 | * | 解引用 | 获取指针指向的值 | int *p; *p = 10; |
& | 取地址 | 获取变量的内存地址 | int a; int *p = &a; | |
结构体/联合体 | . | 成员访问(对象) | 访问结构体或联合体的成员 | struct.x |
-> | 成员访问(指针) | 通过指针访问结构体或联合体的成员 | p->x | |
sizeof | sizeof | 计算大小 | 返回变量或类型的大小(字节数) | sizeof(int) |
类型转换 | (type) | 强制类型转换 | 将变量或值转换为指定类型 | (float) a |
下标操作符 | [] | 数组下标 | 访问数组元素 | arr[3] |
关系表达式通常返回 0 或 1 ,表⽰真假。C语⾔中, 0 表⽰假,所有⾮零值表⽰真。
正号(+)和负号(-)运算符
-
+
作为单目运算符时对数值没有影响,可以省略 -
-
作为单目运算符时会反转操作数的符号:-
正数变负数
-
负数变正数
-
-
强制类型转换
用途
-
将较大类型转换为较小类型(可能丢失数据)
-
将浮点数转换为整数(截断小数部分)
-
在不同指针类型之间转换
-
避免编译器警告
占位符列举
C语言输入/输出占位符总表
1. 标准输出占位符(printf
家族)
占位符 | 类型 | 修饰符示例 | 说明 | 示例代码 |
---|---|---|---|---|
%d | int | %5d | 有符号十进制整数(左补空格) | printf("%5d", 42); → ∙∙42 |
%i | int | %+i | 同 %d ,可识别八/十六进制输入 | printf("%i", 012); → 10 |
%u | unsigned int | %08u | 无符号十进制(补零) | printf("%08u", 42); → 00000042 |
%o | unsigned int | %#o | 无符号八进制(# 加前缀 0 ) | printf("%#o", 8); → 010 |
%x | unsigned int | %#x | 无符号十六进制小写(0x 前缀) | printf("%#x", 255); → 0xff |
%X | unsigned int | %04X | 无符号十六进制大写 | printf("%04X", 255); → 00FF |
%f | float /double | %.2f | 十进制浮点数(保留2位小数) | printf("%.2f", 3.1415); → 3.14 |
%e | float /double | %.3e | 科学计数法小写(e ) | printf("%e", 1000.0); → 1.000000e+03 |
%E | float /double | %10.2E | 科学计数法大写(E ) | printf("%E", 0.001); → 1.00E-03 |
%g | float /double | %g | 自动选择 %f 或 %e (更简洁):6个有效数字的浮点数。整数部分⼀旦超过6位,就会⾃动转为科学计数法,指数部分的 e 为⼩写 | printf("%g", 0.0001); → 0.0001 |
%G | float /double | %G | 自动选择 %f 或 %E。 等同于 %g ,唯⼀的区别是指数部分的 E 为⼤写。 | printf("%G", 1.0e5); → 1E+05 |
%c | char | %c | 单个字符 | printf("%c", 'A'); → A |
%s | char* (字符串) | %10s | 字符串(右对齐) | printf("%10s", "Hi"); → ∙∙∙∙∙∙∙∙Hi |
%p | void* (指针) | %p | 指针地址(十六进制) | printf("%p", &num); → 0x7ffd... |
%n | int* | %n | 记录已输出的字符数 | int cnt; printf("Hi%n", &cnt); → cnt=2 |
%% | 无 | %% | 输出 % 符号 | printf("%%"); → % |
%a | ⼗六进制浮点数,字⺟输出为⼩写 | |||
%A | ⼗六进制浮点数,字⺟输出为⼤写 |
最⼩宽度和⼩数位数这两个限定值,都可以⽤ * 代替,通过 printf() 的参数传⼊, %*.*f 的两个星号通过 printf() 的两个参数 6 和 2 传⼊
#include <stdio.h>
int main()
{
printf("%*.*f\n", 6, 2, 0.5);
return 0;
}
//
等同于printf("%6.2f\n", 0.5);
如果只想输出开头的部分,可以⽤ 的⻓度,其中 [m] 代表⼀个数字,表⽰所要输出的⻓度。
//
输出
hello
#include <stdio.h>
int main()
{
printf("%.5s\n", "hello world");
return 0;
}
2. 标准输入占位符(scanf
家族)
占位符 | 类型 | 修饰符示例 | 说明 | 示例代码 |
---|---|---|---|---|
%d | int* | %d | 有符号十进制整数 | scanf("%d", &num); |
%i | int* | %i | 可识别八进制(0 )、十六进制(0x )输入 | scanf("%i", &num); (输入 012 → 10 ) |
%u | unsigned int* | %u | 无符号十进制整数 | scanf("%u", &unum); |
%o | unsigned int* | %o | 无符号八进制 | scanf("%o", &unum); |
%x | unsigned int* | %x | 无符号十六进制 | scanf("%x", &unum); |
%f | float* | %f | 十进制浮点数(float ) | scanf("%f", &fval); |
%lf | double* | %lf | 十进制浮点数(double ) | scanf("%lf", &dval); |
%Lf | long double 类型浮点数 | |||
%e /%E | float* /double* | %e | 科学计数法输入(同 %f /%lf ) | scanf("%e", &fval); |
%c | char* | %c | 单个字符(包括空格/换行) | scanf("%c", &ch); |
%s | char[] (字符串) | %10s | 字符串(遇空格停止,需指定长度防溢出) | scanf("%9s", str); (缓冲区大小为10) |
%p | void** | %p | 指针地址 | scanf("%p", &ptr); |
%[] | char[] | %[a-z] | 自定义字符集(如只读小写字母) | scanf("%[A-Z]", str); |
%n | int* | %n | 记录已读取的字符数 | scanf("H |
scanf() 处理数值占位符时,会⾃动过滤空⽩字符,包括空格、制表符、换⾏符等。所以,⽤⼾输⼊的数据之间,有⼀个或多个空格不影响 scanf() 解读数据。另外,⽤⼾使⽤回⻋ 键,将输⼊分成⼏⾏,也不影响解读
scanf() 的返回值是⼀个整数,表⽰成功读取的变量个数,如果没有读取任何项,或者匹配失败,则返回 0 。 如果在成功读取任何数据之前,发⽣了读取错误或者遇到读取到⽂件结尾,则返回常量EOF(-1)。
注意:
- %c 不忽略空⽩字符,总是返 回当前第⼀个字符,⽆论该字符是否为空格。
- 如果要强制跳过字符前的空⽩字符,可以写成 scanf(" %c", &ch) ,即 ⽰跳过零个或多个空⽩字符。
- 特别说⼀下占位符 %s ,它其实不能简单地等同于字符串。它的规则是,从当前第⼀个⾮空⽩ 字符开始读起,直到遇到空⽩字符(即空格、换⾏符、制表符等)为⽌。 因为 %s 不会包含空⽩字符,所以⽆法⽤来读取多个单词,除⾮多个 %s ⼀起使⽤。另外, scanf() 遇到 %s 占位符,会在字符串变量末尾存储⼀个空字符 \0 。
- scanf() 将字符串读⼊字符数组时,不会检测字符串是否超过了数组⻓度。所以,储存字符串时, 很可能会超过数组的边界,导致预想不到的结果。为了防⽌这种情况,使⽤ 读⼊字符串的最⻓⻓度,即写成 %[m]s ,其中的 %s 占位符时,应该指定 [m] 是⼀个整数,表⽰读取字符串的最⼤⻓度,后 ⾯的字符将被丢弃。
#include <stdio.h>
int main()
{
char name[11];
scanf("%10s", name);
return 0;
}
更安全的写法:
scanf("%19s", arr); // 限制最多读取19个字符
下面这种写法允许读取空格
-
更安全的做法是指定最大读取长度:
%19[^\n]
(留1字节给空终止符)即:scanf("%19[^\n]", arr); // 读取最多19个字符,直到遇到换行符
3. 长度修饰符(通用)
修饰符 | 适用类型 | 示例 | 说明 |
---|---|---|---|
h | short | %hd (short int ) | 短整数 |
l | long | %ld (long int ) | 长整数 |
ll | long long | %lld | 长长整数(C99) |
L | long double | %Lf | 长双精度浮点数 |
z | size_t | %zu | 无符号大小类型(C99) |
t | ptrdiff_t | %td | 指针差值类型(C99) |
赋值忽略符
scanf() 提供了⼀个赋值忽略符(assignmentsuppressioncharacter) * 。 只要把 * 加在任何占位符的百分号后⾯,该占位符就不会返回值,解析后将被丢弃。
赋值忽略符是一个星号(*
),放在%
和格式说明符之间,例如:%*d
。
当使用这个修饰符时:
-
scanf()
会按照指定的格式读取输入 -
但不会将读取的值赋给任何变量
-
相当于"读取并丢弃"这部分输入
跳过不需要的数据
int year, month, day;
// 输入格式为 "YYYY-MM-DD"
scanf("%d%*c%d%*c%d", &year, &month, &day);
这里%*c会读取但跳过中间的连字符-。
跳过特定字段
int id;
char name[50];
// 输入为 "123:John Doe:42" 但我们不需要年龄
scanf("%d:%[^:]:%*d", &id, name);
这里%*d会读取但跳过最后的年龄值42。