跳到主要内容

基础语法

本章介绍 C 语言的基础语法,包括变量、数据类型、运算符和表达式。这些是编写任何 C 程序的基础。

程序结构

一个标准的 C 程序由预处理指令、函数定义和语句组成。让我们从一个简单的程序开始:

#include <stdio.h>          // 预处理指令:包含标准输入输出头文件

int main(void) { // main 函数:程序入口
printf("Hello, C!\n"); // 函数调用:输出字符串
return 0; // 返回语句:表示程序正常结束
}

程序执行的流程:

  1. 预处理器处理 #include 指令,将头文件内容插入
  2. 程序从 main 函数开始执行
  3. printf 函数输出字符串到控制台
  4. return 0 表示程序正常结束

main 函数是每个 C 程序的入口点,操作系统调用它来启动程序。返回值 0 表示成功,非零值表示出错。

注释

注释用于解释代码,编译器会忽略注释内容。C 语言支持两种注释方式:

/*
* 多行注释
* 可以跨越多行
* 用于详细的说明
*/

// 单行注释,从 // 到行末的内容都是注释

int main(void) {
// 这是一个单行注释
printf("Hello\n"); // 行末注释

/*
这是多行注释
可以写很多内容
*/
return 0;
}

注释的最佳实践:

  • 解释为什么这样做,而不是做了什么
  • 保持注释与代码同步更新
  • 避免无意义的注释

变量

变量是存储数据的命名内存位置。使用变量前必须先声明。

变量声明与初始化

int age;           // 声明一个整型变量
age = 25; // 赋值

int score = 100; // 声明并初始化

int a, b, c; // 同时声明多个变量
int x = 1, y = 2; // 声明并初始化多个变量

C99 标准之后,可以在代码块的任何位置声明变量,但建议在使用前就近声明。

变量命名规则

  • 只能包含字母、数字和下划线
  • 必须以字母或下划线开头
  • 不能使用关键字(如 intifwhile 等)
  • 区分大小写(ageAge 是不同的变量)
int player_score;   // 合法:下划线分隔
int PlayerScore; // 合法:驼峰命名
int _count; // 合法:下划线开头
int MAX_SIZE; // 合法:常量风格

// int 2nd_place; // 非法:数字开头
// int my-var; // 非法:包含连字符
// int int; // 非法:使用关键字

命名建议:

  • 使用有意义的名称,描述变量的用途
  • 保持命名风格一致
  • 常量使用全大写字母
  • 变量名使用小写字母,单词间用下划线分隔

数据类型

C 语言是强类型语言,每个变量都有确定的数据类型。数据类型决定了变量占用的内存大小和可以存储的值的范围。

整数类型

整数类型用于存储不带小数的数值:

类型存储大小值范围
char1 字节-128 到 127 或 0 到 255
short2 字节-32,768 到 32,767
int4 字节-2,147,483,648 到 2,147,483,647
long4 或 8 字节至少 -2,147,483,648 到 2,147,483,647
long long8 字节-9,223,372,036,854,775,808 到 9,223,372,036,854,775,807

可以使用 sizeof 运算符查看类型或变量的大小:

#include <stdio.h>

int main(void) {
printf("char: %zu 字节\n", sizeof(char));
printf("short: %zu 字节\n", sizeof(short));
printf("int: %zu 字节\n", sizeof(int));
printf("long: %zu 字节\n", sizeof(long));
printf("long long: %zu 字节\n", sizeof(long long));
return 0;
}

有符号与无符号

整数类型默认是有符号的,可以存储正数和负数。使用 unsigned 关键字声明无符号类型,只能存储非负数,但正数范围扩大一倍:

int signed_num = -100;        // 有符号整数
unsigned int unsigned_num = 100; // 无符号整数

unsigned short us = 65535; // 无符号短整型
unsigned long ul = 4000000000UL; // 无符号长整型

无符号类型的值范围:

类型值范围
unsigned char0 到 255
unsigned short0 到 65,535
unsigned int0 到 4,294,967,295
unsigned long long0 到 18,446,744,073,709,551,615

浮点类型

浮点类型用于存储带小数的数值:

类型存储大小有效数字值范围
float4 字节6-7 位约 ±3.4E38
double8 字节15-16 位约 ±1.7E308
long double10-16 字节18-19 位或更多取决于实现
float pi = 3.14159f;          // 单精度,f 后缀
double e = 2.718281828; // 双精度,默认类型
long double ld = 3.14159265358979L; // 扩展精度,L 后缀

浮点数的注意事项:

  • 浮点数运算可能存在精度误差
  • 不要直接比较两个浮点数是否相等
  • double 是浮点运算的默认类型

字符类型

char 类型用于存储单个字符,实际上存储的是字符的 ASCII 码值:

char ch = 'A';        // 字符常量,单引号
char digit = '7'; // 字符 '7',不是数字 7
char newline = '\n'; // 转义字符

printf("字符: %c, ASCII码: %d\n", ch, ch); // 输出: 字符: A, ASCII码: 65

常用转义字符:

转义字符含义
\n换行
\t水平制表符
\\反斜杠
\'单引号
\"双引号
\0空字符(字符串结束符)

布尔类型

C99 标准引入了 _Bool 类型和 <stdbool.h> 头文件:

#include <stdbool.h>

bool is_valid = true; // 真
bool is_empty = false; // 假

在 C99 之前,使用整数表示布尔值:0 表示假,非零表示真。

类型限定符

const 限定符表示变量的值不能被修改:

const int MAX_SIZE = 100;   // 常量,必须初始化
const double PI = 3.14159; // 常量

// MAX_SIZE = 200; // 错误:不能修改常量

volatile 限定符告诉编译器该变量可能被意外修改,不要优化:

volatile int hardware_register;  // 硬件寄存器

运算符

运算符用于执行各种操作,如算术运算、比较、逻辑运算等。

算术运算符

int a = 10, b = 3;

int sum = a + b; // 加法: 13
int diff = a - b; // 减法: 7
int product = a * b; // 乘法: 30
int quotient = a / b; // 整数除法: 3(截断小数部分)
int remainder = a % b; // 取模: 1

double x = 10.0, y = 3.0;
double result = x / y; // 浮点除法: 3.333...

整数除法会截断小数部分,如果需要精确结果,至少有一个操作数应为浮点数。

自增与自减运算符

int i = 5;

i++; // 后置自增,i 变为 6
++i; // 前置自增,i 变为 7
i--; // 后置自减,i 变为 6
--i; // 前置自减,i 变为 5

// 前置与后置的区别
int a = 5;
int b = a++; // b = 5, a = 6(先赋值,后自增)
int c = ++a; // c = 7, a = 7(先自增,后赋值)

关系运算符

关系运算符用于比较两个值,返回 1(真)或 0(假):

int a = 10, b = 5;

int r1 = (a == b); // 等于: 0(假)
int r2 = (a != b); // 不等于: 1(真)
int r3 = (a > b); // 大于: 1(真)
int r4 = (a < b); // 小于: 0(假)
int r5 = (a >= b); // 大于等于: 1(真)
int r6 = (a <= b); // 小于等于: 0(假)

注意区分 =(赋值)和 ==(相等比较)。

逻辑运算符

int a = 1, b = 0;

int r1 = (a && b); // 逻辑与: 0(假)
int r2 = (a || b); // 逻辑或: 1(真)
int r3 = !a; // 逻辑非: 0(假)
int r4 = !b; // 逻辑非: 1(真)

逻辑运算符的短路求值:

int a = 0;
// 如果第一个操作数为假,&& 后面的表达式不会执行
if (a != 0 && 10 / a > 1) { // 不会发生除零错误
// ...
}

位运算符

位运算符直接操作整数的二进制位:

unsigned int a = 0b1100;  // 12
unsigned int b = 0b1010; // 10

unsigned int r1 = a & b; // 按位与: 0b1000 (8)
unsigned int r2 = a | b; // 按位或: 0b1110 (14)
unsigned int r3 = a ^ b; // 按位异或: 0b0110 (6)
unsigned int r4 = ~a; // 按位取反
unsigned int r5 = a << 2; // 左移: 0b110000 (48)
unsigned int r6 = a >> 2; // 右移: 0b11 (3)

位运算的常见应用:

// 设置某一位
flags |= (1 << 3);

// 清除某一位
flags &= ~(1 << 3);

// 切换某一位
flags ^= (1 << 3);

// 检查某一位
if (flags & (1 << 3)) {
// 第 3 位为 1
}

赋值运算符

int a = 10;

a += 5; // 等价于 a = a + 5,结果: 15
a -= 3; // 等价于 a = a - 3,结果: 12
a *= 2; // 等价于 a = a * 2,结果: 24
a /= 4; // 等价于 a = a / 4,结果: 6
a %= 4; // 等价于 a = a % 4,结果: 2

a &= 0x0F; // 等价于 a = a & 0x0F
a |= 0x01; // 等价于 a = a | 0x01

条件运算符(三目运算符)

条件运算符是 C 语言中唯一的三元运算符:

int a = 10, b = 20;
int max = (a > b) ? a : b; // 如果 a > b,取 a,否则取 b

等价于:

int max;
if (a > b) {
max = a;
} else {
max = b;
}

sizeof 运算符

sizeof 返回类型或变量的大小(字节数):

int a = 10;
printf("int 大小: %zu\n", sizeof(int)); // 类型
printf("a 的大小: %zu\n", sizeof(a)); // 变量
printf("表达式大小: %zu\n", sizeof(a + 1.0)); // 表达式

运算符优先级

当表达式中有多个运算符时,按优先级顺序计算。优先级从高到低:

优先级运算符结合性
1() [] -> .左到右
2! ~ ++ -- + - * & sizeof右到左
3* / %左到右
4+ -左到右
5<< >>左到右
6< <= > >=左到右
7== !=左到右
8&左到右
9^左到右
10|左到右
11&&左到右
12||左到右
13?:右到左
14= += -= *= /= %= &= ^= |= <<= >>=右到左
15,左到右

建议:不确定时使用括号明确优先级。

类型转换

隐式类型转换

编译器自动进行的类型转换:

int a = 10;
double b = 3.5;
double c = a + b; // a 自动转换为 double,结果: 13.5

char ch = 'A';
int code = ch; // char 自动转换为 int

转换规则:较小的类型自动转换为较大的类型。

显式类型转换(强制转换)

使用 (类型) 语法进行强制转换:

int a = 10, b = 3;
double result = (double)a / b; // 结果: 3.333...

double x = 3.7;
int y = (int)x; // 截断,结果: 3

表达式与语句

表达式

表达式是由运算符和操作数组成的式子,可以计算出一个值:

10              // 常量表达式
a // 变量表达式
a + b // 算术表达式
a > b // 关系表达式
a && b // 逻辑表达式
func() // 函数调用表达式

语句

语句是程序执行的基本单位,以分号结尾:

int a = 10;     // 声明语句
a = a + 1; // 赋值语句
printf("hello"); // 函数调用语句
; // 空语句

复合语句(代码块)用花括号括起来:

{
int a = 10;
a = a + 1;
printf("%d\n", a);
}

输入输出基础

printf 输出

printf 用于格式化输出:

int age = 25;
double pi = 3.14159;
char ch = 'A';

printf("年龄: %d\n", age); // 输出整数
printf("圆周率: %.2f\n", pi); // 输出浮点数,保留2位小数
printf("字符: %c\n", ch); // 输出字符
printf("字符串: %s\n", "hello"); // 输出字符串

常用格式说明符:

格式符说明
%d有符号十进制整数
%u无符号十进制整数
%f浮点数
%lfdouble 类型
%c单个字符
%s字符串
%x十六进制(小写)
%X十六进制(大写)
%o八进制
%p指针地址
%%百分号

宽度和精度控制:

printf("%5d\n", 42);      // 宽度5,右对齐: "   42"
printf("%-5d\n", 42); // 宽度5,左对齐: "42 "
printf("%05d\n", 42); // 宽度5,用0填充: "00042"
printf("%.2f\n", 3.14159); // 精度2位: "3.14"
printf("%8.2f\n", 3.14); // 宽度8,精度2: " 3.14"

scanf 输入

scanf 用于读取用户输入:

int age;
printf("请输入年龄: ");
scanf("%d", &age); // 注意 & 取地址符
printf("你的年龄是: %d\n", age);

double price;
printf("请输入价格: ");
scanf("%lf", &price); // double 使用 %lf
printf("价格是: %.2f\n", price);

char name[50];
printf("请输入姓名: ");
scanf("%s", name); // 数组名本身就是地址,不需要 &
printf("你好, %s\n", name);

scanf 返回成功读取的项目数,可以用于错误检查:

int a, b;
int result = scanf("%d %d", &a, &b);
if (result == 2) {
printf("读取成功: %d, %d\n", a, b);
} else {
printf("输入格式错误\n");
}

小结

本章介绍了 C 语言的基础语法:

  • 程序结构:预处理指令、main 函数、语句
  • 变量:声明、初始化、命名规则
  • 数据类型:整数、浮点、字符、布尔类型
  • 运算符:算术、关系、逻辑、位运算、赋值
  • 类型转换:隐式转换和强制转换
  • 基本输入输出:printf 和 scanf

下一章将学习 控制流程,包括条件语句和循环语句。