C/C++ Linux 开发教程(GCC + CMake) / 02 — 基本类型、运算符与表达式
基本类型、运算符与表达式
1. 基本数据类型
C 语言提供了一组基本数据类型,用于存储不同范围和精度的数值。
1.1 整型家族
| 类型 | 关键字 | 典型大小 | 范围(典型) |
|---|---|---|---|
| 短整型 | short | 2 字节 | -32,768 ~ 32,767 |
| 整型 | int | 4 字节 | -2,147,483,648 ~ 2,147,483,647 |
| 长整型 | long | 4 或 8 字节 | 取决于平台 |
| 长长整型 | long long | 8 字节 | -9.2×10¹⁸ ~ 9.2×10¹⁸ |
1.2 浮点型家族
| 类型 | 关键字 | 典型大小 | 精度 |
|---|---|---|---|
| 单精度 | float | 4 字节 | 约 6~7 位有效数字 |
| 双精度 | double | 8 字节 | 约 15~16 位有效数字 |
| 扩展精度 | long double | 12 或 16 字节 | 平台相关 |
1.3 字符型
| 类型 | 关键字 | 典型大小 | 说明 |
|---|---|---|---|
| 字符 | char | 1 字节 | 存储 ASCII 字符或小整数 |
| 宽字符 | wchar_t | 2 或 4 字节 | 存储 Unicode 字符 |
💡 提示:
char本质上是整数类型,'A'的值就是 65。
1.4 完整示例
#include <stdio.h>
int main(void)
{
char c = 'A';
short s = 100;
int i = 42;
long l = 100000L;
long long ll = 9876543210LL;
float f = 3.14f;
double d = 3.141592653589793;
long double ld = 3.141592653589793238L;
printf("char: '%c' = %d, size = %zu\n", c, c, sizeof(char));
printf("short: %hd, size = %zu\n", s, sizeof(short));
printf("int: %d, size = %zu\n", i, sizeof(int));
printf("long: %ldL, size = %zu\n", l, sizeof(long));
printf("long long: %lldLL, size = %zu\n", ll, sizeof(long long));
printf("float: %f, size = %zu\n", f, sizeof(float));
printf("double: %lf, size = %zu\n", d, sizeof(double));
printf("long double: %LfL, size = %zu\n", ld, sizeof(long double));
return 0;
}
编译运行:
gcc -Wall -std=c17 -o types types.c && ./types
2. sizeof 运算符
sizeof 返回类型或变量占用的字节数,是编译期常量。
#include <stdio.h>
int main(void)
{
printf("sizeof(char) = %zu\n", sizeof(char));
printf("sizeof(int) = %zu\n", sizeof(int));
printf("sizeof(long) = %zu\n", sizeof(long));
printf("sizeof(long long) = %zu\n", sizeof(long long));
printf("sizeof(float) = %zu\n", sizeof(float));
printf("sizeof(double) = %zu\n", sizeof(double));
printf("sizeof(void *) = %zu\n", sizeof(void *));
int arr[10];
printf("sizeof(arr) = %zu\n", sizeof(arr)); // 10 * sizeof(int)
printf("sizeof(arr)/sizeof(arr[0]) = %zu\n", sizeof(arr) / sizeof(arr[0])); // 元素个数
return 0;
}
⚠️ 注意: 数组传入函数后会退化为指针,sizeof 返回的是指针大小而非数组大小。
| 表达式 | 32 位平台 | 64 位平台 |
|---|---|---|
sizeof(int) | 4 | 4 |
sizeof(long) | 4 | 8 (Linux), 4 (Windows) |
sizeof(void *) | 4 | 8 |
sizeof(size_t) | 4 | 8 |
3. 有符号与无符号
#include <stdio.h>
int main(void)
{
signed int a = -10; // 有符号,可为负
unsigned int b = 10u; // 无符号,仅非负
printf("signed: %d\n", a);
printf("unsigned: %u\n", b);
// 无符号与有符号混用的陷阱
if (a < b) {
printf("a < b\n"); // 实际上 -10 > 10(无符号比较)!
} else {
printf("a >= b\n"); // 这行会被执行
}
return 0;
}
⚠️ 注意: 当 signed 和 unsigned 混合运算时,signed 会被隐式转换为 unsigned。-10 转为无符号后变成一个极大的正数(4294967286),因此 -10 < 10 的比较结果为假。
💡 提示: 使用
-Wall -Wextra编译时,GCC 会对有符号/无符号混用发出警告。
4. 类型转换
4.1 隐式转换
编译器自动完成的类型转换,遵循"小类型向大类型提升"规则:
char → short → int → long → long long → float → double → long double
int a = 5;
double b = 2.5;
double result = a + b; // int 隐式提升为 double
4.2 显式转换 (Casting)
#include <stdio.h>
int main(void)
{
double pi = 3.14159;
int truncated = (int)pi; // 截断小数部分,结果为 3
printf("pi = %f\n", pi);
printf("(int)pi = %d\n", truncated);
// 整数除法的陷阱
int x = 7, y = 2;
printf("7 / 2 = %d\n", x / y); // 3(整数除法)
printf("7 / 2 = %f\n", (double)x / y); // 3.500000
return 0;
}
⚠️ 注意: (int)3.9 的结果是 3,不是四舍五入。需要四舍五入请使用 round() 函数。
5. 运算符
5.1 算术运算符
| 运算符 | 说明 | 示例 | 结果 |
|---|---|---|---|
+ | 加法 | 5 + 3 | 8 |
- | 减法 | 5 - 3 | 2 |
* | 乘法 | 5 * 3 | 15 |
/ | 除法 | 7 / 2 | 3(整数) |
% | 取模 | 7 % 2 | 1 |
++ | 自增 | i++ / ++i | — |
-- | 自减 | i-- / --i | — |
#include <stdio.h>
int main(void)
{
int i = 5;
printf("i++ = %d, i = %d\n", i++, i); // 先用后加: 5, 6
i = 5;
printf("++i = %d, i = %d\n", ++i, i); // 先加后用: 6, 6
return 0;
}
💡 提示: 尽量避免在复杂表达式中使用
++/--,单独使用最安全。
5.2 关系运算符
| 运算符 | 含义 | 示例 |
|---|---|---|
== | 等于 | a == b |
!= | 不等于 | a != b |
< | 小于 | a < b |
> | 大于 | a > b |
<= | 小于等于 | a <= b |
>= | 大于等于 | a >= b |
⚠️ 注意: = 是赋值,== 是比较。if (a = 5) 永远为真!使用 -Wall 时 GCC 会对此发出警告。
5.3 逻辑运算符
| 运算符 | 含义 | 示例 | 短路行为 |
|---|---|---|---|
&& | 逻辑与 | a && b | 左边为假,右边不求值 |
|| | 逻辑或 | a || b | 左边为真,右边不求值 |
! | 逻辑非 | !a | — |
#include <stdio.h>
int main(void)
{
int x = 0;
// 短路求值:x != 0 为假,10/x 不会被执行
if (x != 0 && 10 / x > 2) {
printf("条件成立\n");
} else {
printf("x 为零或条件不成立,安全!\n");
}
return 0;
}
💡 提示: 利用短路求值可以安全地检查空指针:
if (ptr != NULL && ptr->value > 0)。
5.4 位运算符
| 运算符 | 含义 | 示例 | 说明 |
|---|---|---|---|
& | 按位与 | 0xFF & 0x0F → 0x0F | 两个位都为 1 时结果为 1 |
| | 按位或 | 0xF0 | 0x0F → 0xFF | 任一位为 1 时结果为 1 |
^ | 按位异或 | 0xFF ^ 0x0F → 0xF0 | 两位不同则为 1 |
~ | 按位取反 | ~0x00 → 0xFF | 所有位反转 |
<< | 左移 | 1 << 3 → 8 | 左边丢弃,右边补 0 |
>> | 右移 | 8 >> 2 → 2 | 右边丢弃,左边补符号位 |
#include <stdio.h>
void print_binary(unsigned char val)
{
for (int i = 7; i >= 0; i--) {
printf("%d", (val >> i) & 1);
}
}
int main(void)
{
unsigned char a = 0xA5; // 10100101
unsigned char b = 0x0F; // 00001111
printf("a = "); print_binary(a); printf("\n");
printf("b = "); print_binary(b); printf("\n");
printf("a & b = "); print_binary(a & b); printf("\n");
printf("a | b = "); print_binary(a | b); printf("\n");
printf("a ^ b = "); print_binary(a ^ b); printf("\n");
return 0;
}
实际场景:位运算常用于标志位(flags)管理:
#define FLAG_READ (1 << 0) // 0001
#define FLAG_WRITE (1 << 1) // 0010
#define FLAG_EXEC (1 << 2) // 0100
unsigned int perms = FLAG_READ | FLAG_WRITE; // 设置读写权限
perms |= FLAG_EXEC; // 添加执行权限
perms &= ~FLAG_WRITE; // 移除写权限
if (perms & FLAG_READ) {
printf("可读\n");
}
6. 运算符优先级表
| 优先级 | 运算符 | 结合性 |
|---|---|---|
| 1(最高) | () [] -> . | 左到右 |
| 2 | ! ~ ++ -- (type) * & sizeof | 右到左 |
| 3 | * / % | 左到右 |
| 4 | + - | 左到右 |
| 5 | << >> | 左到右 |
| 6 | < <= > >= | 左到右 |
| 7 | == != | 左到右 |
| 8 | & | 左到右 |
| 9 | ^ | 左到右 |
| 10 | | | 左到右 |
| 11 | && | 左到右 |
| 12 | || | 左到右 |
| 13 | ?: | 右到左 |
| 14(最低) | = += -= 等 | 右到左 |
💡 提示: 记不住优先级?用括号
()明确意图,既避免错误又提高可读性。
7. 字面量
// 整型字面量
int decimal = 42; // 十进制
int octal = 052; // 八进制(前缀 0)
int hex = 0x2A; // 十六进制(前缀 0x)
int binary = 0b101010; // 二进制(C23,GCC 也支持)
long big = 1000000L; // long 后缀
unsigned long long ull = 18446744073709551615ULL;
// 浮点字面量
float f = 3.14f; // float 后缀
double d = 3.14; // 默认 double
long double ld = 3.14L; // long double 后缀
double sci = 1.5e10; // 科学计数法
// 字符字面量
char c = 'A';
char newline = '\n'; // 转义字符
char hex_char = '\x41'; // 十六进制 ASCII
// 字符串字面量
const char *str = "Hello, World!";
// 转义字符表
// \n 换行 \t 制表符 \\ 反斜杠 \' 单引号 \" 双引号
// \0 空字符 \r 回车 \a 响铃 \b 退格
8. const 常量
#include <stdio.h>
#define MAX_SIZE 100 // 宏常量(预处理期替换)
const int MIN_SIZE = 10; // const 常量(编译期检查)
int main(void)
{
const double PI = 3.14159265358979;
// PI = 3.14; // 错误!const 变量不可修改
const int *p1 = &MIN_SIZE; // 指向 const int 的指针
int *const p2 = NULL; // const 指针,指向 int
const int *const p3 = &MIN_SIZE; // const 指针,指向 const int
printf("MAX_SIZE = %d\n", MAX_SIZE);
printf("MIN_SIZE = %d\n", MIN_SIZE);
printf("PI = %f\n", PI);
return 0;
}
| 声明 | 含义 |
|---|---|
const int *p | 指针指向的值不可修改 (*p 不可赋值) |
int *const p | 指针本身不可修改 (p 不可重新指向) |
const int *const p | 两者都不可修改 |
9. typedef 类型别名
#include <stdio.h>
#include <stdint.h>
typedef unsigned int uint;
typedef struct Point {
double x;
double y;
} Point;
typedef int (*MathFunc)(int, int); // 函数指针类型别名
int add(int a, int b) { return a + b; }
int main(void)
{
uint count = 42;
Point p = {1.0, 2.0};
MathFunc op = add;
printf("count = %u\n", count);
printf("point = (%.1f, %.1f)\n", p.x, p.y);
printf("add(3, 5) = %d\n", op(3, 5));
// 使用 stdint.h 中的定宽整型
int32_t val = -100;
uint32_t uval = 100;
printf("int32_t: %d, uint32_t: %u\n", val, uval);
return 0;
}
💡 提示: 使用
<stdint.h>中的int32_t、uint64_t等定宽整型,可以避免int/long大小因平台而异的问题。
10. ASCII 与 Unicode
ASCII 码表(常用部分)
| 字符 | 十进制 | 十六进制 | 说明 |
|---|---|---|---|
'0'~'9' | 48~57 | 0x30~0x39 | 数字字符 |
'A'~'Z' | 65~90 | 0x41~0x5A | 大写字母 |
'a'~'z' | 97~122 | 0x61~0x7A | 小写字母 |
' ' | 32 | 0x20 | 空格 |
'\n' | 10 | 0x0A | 换行 |
'\0' | 0 | 0x00 | 空字符(字符串终止符) |
#include <stdio.h>
int main(void)
{
// 大小写转换
char lower = 'a';
char upper = lower - 32; // 'A'
printf("%c -> %c\n", lower, upper);
// 字符与数字互转
char digit_char = '7';
int digit = digit_char - '0'; // 7
printf("'%c' -> %d\n", digit_char, digit);
// 打印 ASCII 表
for (int i = 32; i < 127; i++) {
printf("%3d: '%c' ", i, i);
if ((i - 31) % 8 == 0) printf("\n");
}
return 0;
}
11. 常见陷阱
| 陷阱 | 示例 | 说明 |
|---|---|---|
| 整数除法截断 | 7 / 2 → 3 | 需要浮点结果时用 (double)7 / 2 |
| 整数溢出 | INT_MAX + 1 | 有符号整数溢出是未定义行为(UB) |
| 浮点精度 | 0.1 + 0.2 != 0.3 | 浮点数无法精确表示所有小数 |
| 字符与整数混淆 | char c = 256 | 超出范围会截断 |
sizeof 与数组退化 | 函数内 sizeof(arr) | 返回指针大小而非数组大小 |
#include <stdio.h>
#include <limits.h>
int main(void)
{
// 浮点精度问题
double a = 0.1 + 0.2;
printf("0.1 + 0.2 = %.20f\n", a); // 不是精确的 0.3
printf("0.1 + 0.2 == 0.3? %s\n", a == 0.3 ? "yes" : "no");
// 整数溢出
int max = INT_MAX;
printf("INT_MAX = %d\n", max);
// printf("%d\n", max + 1); // 未定义行为!
return 0;
}
12. 实际场景:温度转换程序
#include <stdio.h>
int main(void)
{
double celsius, fahrenheit;
printf("请输入摄氏温度: ");
if (scanf("%lf", &celsius) != 1) {
fprintf(stderr, "输入错误!\n");
return 1;
}
fahrenheit = celsius * 9.0 / 5.0 + 32.0;
printf("%.1f°C = %.1f°F\n", celsius, fahrenheit);
return 0;
}
编译运行:
gcc -Wall -std=c17 -o temp temp.c && echo "36.5" | ./temp
输出:
请输入摄氏温度: 36.5°C = 97.7°F