C 基础语法 - C教程

C 基础语法

C 语言是一种通用的编程语言,广泛应用于系统编程、嵌入式开发和高性能计算等领域。

C 语言具有高效、灵活、可移植性强等特点,是许多其他编程语言(如 C++、Java、Go)的基础,至今仍是操作系统、驱动程序和底层库开发的主流选择。

在 C 语言中,令牌(Token)是程序的基本组成单位,编译器通过对源代码进行词法分析,将代码分解成一个个的令牌。令牌是编译器能识别的最小有意义单元,整个 C 程序都由这些令牌构成。

C 语言的令牌主要包括以下几种类型:

  • 关键字(Keywords):语言预留的具有特殊含义的单词,如 intifreturn
  • 标识符(Identifiers):程序员自定义的名称,用于变量、函数、数组等。
  • 常量(Constants):程序中固定不变的值,如整数 42、浮点数 3.14
  • 字符串字面量(String Literals):由双引号括起来的字符序列,如 "Hello"
  • 运算符(Operators):用于执行运算的符号,如 +==&&
  • 分隔符(Separators):用于分隔语句和代码块的符号,如 ;{}

C 程序的基本结构

这是一个简单的 C 语言程序,可以输出 "Hello, World!":

示例代码

#include <stdio.h>

int main() {
    printf("Hello, World!\n");
    return 0;
}

以上代码组成结构如下:

  • 预处理器指令:如 #include#define,在编译前由预处理器处理。
  • 主函数:每个 C 程序都有且只有一个 main() 函数,是程序的执行入口。
  • 变量声明:声明程序中使用的变量,C89 要求声明必须在代码块最开头。
  • 函数定义:定义程序中使用的函数,描述函数的具体实现逻辑。

更复杂一点的 C 程序结构说明(后面章节会对每个知识点展开说明):

示例代码

#include <stdio.h>   // 头文件包含

#define PI 3.14159    // 宏定义

// 函数声明
int add(int a, int b);

int main() {         // 主函数
    // 变量声明
    int num1, num2, sum;

    // 用户输入
    printf("Enter two integers: ");
    scanf("%d %d", &num1, &num2);

    // 函数调用
    sum = add(num1, num2);

    // 输出结果
    printf("Sum: %d\n", sum);

    return 0;        // 返回 0 表示程序成功执行
}

// 函数定义
int add(int a, int b) {
    return a + b;
}

头文件包含

  • 头文件通常在程序的开头使用 #include 指令包含。头文件提供了函数和库的声明,如标准输入输出库 <stdio.h>、标准库 <stdlib.h> 等。它们定义了函数、宏、常量等使程序能够使用预定义的库函数。
  • 系统头文件使用尖括号 <>,自定义头文件使用双引号 "",两者的搜索路径不同。
  • 示例:#include <stdio.h>(系统头文件)、#include "myheader.h"(自定义头文件)

宏定义

  • 宏是通过 #define 指令定义的符号常量或代码片段。宏在编译前由预处理器进行文本替换,不占用运行时内存,常用于定义常量或简单的内联代码逻辑。
  • 宏没有类型检查,使用时需谨慎;对于有类型要求的常量,推荐使用 const 变量替代。
  • 示例:#define PI 3.14159#define MAX(a, b) ((a) > (b) ? (a) : (b))

函数声明

  • 在 C 语言中,函数的声明(也称原型)必须在函数定义或调用之前出现。声明提供了函数的返回类型、函数名和参数列表,以便编译器在调用点进行类型检查。
  • 函数声明可以放在头文件中,供多个源文件共享使用。
  • 示例:int add(int a, int b);

主函数

  • main() 函数是 C 程序的入口点,每个 C 程序都必须包含一个 main() 函数。程序从 main() 开始执行,返回值通常为 0 表示程序成功执行,非零值表示出现了错误。
  • main() 函数也可以接收命令行参数:int main(int argc, char *argv[]),其中 argc 是参数个数,argv 是参数字符串数组。
  • 示例:int main() { ... }

变量声明

  • 在 C 程序中,所有变量必须在使用前声明其类型。变量可以在 main() 函数中声明(局部变量),也可以在函数外部全局声明(全局变量)。
  • 声明变量时可同时进行初始化,未初始化的局部变量包含不确定值(垃圾值),而全局变量和静态变量会被自动初始化为 0
  • 示例:int count = 0;float price = 9.99;char grade = 'A';

语句和表达式

  • 语句是 C 程序的基本执行单元,通常是函数调用、赋值、控制语句(如 iffor 等)或表达式。表达式是由变量、操作符和常量组成的代码片段,能够计算出一个值。
  • C 语言中,赋值本身也是一个表达式,具有值(即被赋的值),因此可以写出 a = b = c = 0; 这样的链式赋值。
  • 示例:
    printf("Enter two integers: ");
    
    sum = add(num1, num2);

控制流语句

  • 控制流语句用于控制程序执行的顺序,包括条件判断(if/elseswitch)和循环(forwhiledo-while)。还有跳转语句 breakcontinuegotoreturn
  • 合理使用控制流语句是写出逻辑清晰程序的关键,应避免滥用 goto 以保持代码可读性。
  • 示例:
    if (num1 > num2) {
    
        printf("num1 is greater than num2");
    
    }

函数定义

  • 函数定义包含完整的函数体,描述了函数的具体实现。一个函数由返回类型、函数名、参数列表和函数体组成。良好的函数设计应遵循"单一职责"原则,每个函数只做一件事。
  • C 语言的函数不支持重载,每个函数名在同一作用域中必须唯一。函数可以递归调用自身。
  • 示例:
    int add(int a, int b) {
    
        return a + b;
    
    }

返回语句

  • return 语句用于终止函数的执行,并将控制权连同返回值一起交还给调用者。void 类型的函数可以使用不带值的 return; 提前退出,也可以省略 return 语句。
  • main() 函数的返回值会传递给操作系统,0 通常表示成功,可使用标准宏 EXIT_SUCCESSEXIT_FAILURE(定义在 <stdlib.h>)。
  • 示例:return 0;return EXIT_SUCCESS;

分隔符

分隔符用于分隔语句、表达式和代码块,帮助编译器理解程序的结构,常见的分隔符包括:

  • 逗号 ,:用于分隔变量声明、函数参数或初始化列表中的多个元素。
  • 分号 ;:用于结束一条语句,是 C 语言中最常见的分隔符。
  • 括号
    • 圆括号 ():用于函数调用的参数列表、控制流语句的条件,以及强制表达式求值顺序。
    • 花括号 {}:用于定义函数体、控制流代码块,以及复合字面量与数组/结构体的初始化。
    • 方括号 []:用于数组的声明和元素访问(下标运算)。

在 C 程序中,分号 ; 是语句结束符,每个语句必须以分号结束,它表明一个逻辑实体的结束。缺少分号是初学者最常见的语法错误之一。

例如,下面是两个不同的语句:


printf("Hello, World! \n");

return 0;

一个单独的分号也可以作为一个空语句,表示什么都不做,常用于某些循环结构中:


while (condition);  // 空循环体,等待条件变化

注释

C 语言有两种注释方式:


// 单行注释

// 开始的单行注释(C99 引入),这种注释从 // 起到行尾结束,可以单独占一行,也可以放在代码行的末尾。


/* 单行注释 */

/* 

 多行注释

 多行注释

 多行注释

 */

/* */ 这种格式的注释可以单行或多行,适合用于函数说明、模块头部等较长的注释场景。

示例代码

// 这是单行注释

/*
这是多行注释
它可以跨越多行
*/


int main() {
    // 打印一条消息
    printf("Hello, World!\n");
    return 0;
}

使用注释时需注意以下几点:

  • 不能在注释内嵌套注释(/* /* */ */ 是非法的)。
  • 注释不能出现在字符串字面量或字符常量内部。
  • 注释是写给人看的,应简洁明了,重点解释"为什么"而非"做了什么",代码本身应该能表达"做什么"。
  • 良好的注释习惯是专业编程的重要组成部分,有助于代码维护和团队协作。

标识符

标识符是程序中变量、函数、数组、类型等的名字,由程序员自行定义。标识符由字母(大写或小写)、数字和下划线组成,但第一个字符必须是字母或下划线,不能是数字。

一个标识符以字母 A-Z 或 a-z 或下划线 _ 开始,后跟零个或多个字母、下划线和数字(0-9)。

C 标识符内不允许出现标点字符,比如 @、$ 和 %。C 是区分大小写的编程语言。因此,在 C 中,Manpowermanpower 是两个不同的标识符。下面列出几个有效的标识符:


mohd       zara    abc   move_name  a_123

myname50   _temp   j     a23b9      retVal

以下是一些无效的标识符示例及原因:


2abc        // 错误:以数字开头

my-name     // 错误:包含连字符 -

float       // 错误:与关键字重名

my name     // 错误:包含空格

命名建议:标识符应具有描述性,能清晰表达其用途。常见的命名风格有下划线命名法(my_variable)和小驼峰命名法(myVariable),在同一项目中保持一致即可。以双下划线 __ 开头的标识符通常由编译器或标准库保留,应避免在用户代码中使用。

常量

常量是固定值,在程序执行期间不会改变。C 语言中定义常量主要有两种方式:使用 const 关键字或使用 #define 宏定义。

常量可以是整型常量、浮点型常量、字符常量、枚举常量等:


const int MAX = 100;          // 整型常量

const float PI = 3.14159;    // 浮点型常量

const char NEWLINE = '\n';   // 字符常量(转义字符)

字符常量中常用的转义字符包括:

  • '\n':换行
  • '\t':水平制表符
  • '\\':反斜杠
  • '\'':单引号
  • '\0':空字符(字符串结束标志)

使用 const 定义的常量有类型信息,编译器可以进行类型检查,相比 #define 宏更安全,是 C99 及之后推荐的方式。

字符串字面量

字符串字面量是由双引号括起来的字符序列,用于表示文本数据。

字符串字面量在内存中以字符数组的形式存储,末尾会自动添加一个空字符 \0(即 null 终止符),标志字符串的结束。


char greeting[] = "Hello, World!";  // 编译器自动计算长度(包含 \0)

以下是关于字符串字面量的几点重要说明:

  • "Hello" 在内存中实际占 6 个字节:'H'、'e'、'l'、'l'、'o'、'\0'
  • 字符串字面量存储在只读内存区域,不可修改;若需修改,应将其复制到字符数组中。
  • 两个相邻的字符串字面量会被编译器自动拼接:"Hello, " "World!" 等同于 "Hello, World!"
  • 字符 'A'(字符常量)和字符串 "A"(字符串字面量)是不同的,后者包含 'A''\0' 两个字符。

运算符(Operators)

运算符用于执行各种操作,如算术运算、逻辑运算、比较运算等。运算符作用于操作数,形成表达式并产生结果。

C 语言中的运算符种类繁多,常见的包括:

  • 算术运算符+, -, *, /, %(其中 / 对整数做整除,% 取余数)
  • 关系运算符==, !=, >, <, >=, <=(返回 1 表示真,0 表示假)
  • 逻辑运算符&&(与), ||(或), !(非),支持短路求值
  • 位运算符&(按位与), |(按位或), ^(按位异或), ~(按位取反), <<(左移), >>(右移)
  • 赋值运算符=, +=, -=, *=, /=, %=(复合赋值,如 a += b 等价于 a = a + b
  • 自增/自减运算符++(自增), --(自减),分前缀(先运算再取值)和后缀(先取值再运算)两种形式
  • 其他运算符sizeof(计算类型或变量字节大小), ?:(三目运算符), &(取地址), *(解引用), ->(指针访问成员), .(直接访问成员)

int a = 5, b = 10;

int sum = a + b;           // 算术运算符 +,sum = 15

int isEqual = (a == b);   // 关系运算符 ==,isEqual = 0(假)

int result = a &lt;&lt; 1;      // 位运算左移,result = 10(相当于 a * 2)

a++;                       // 后缀自增,a 变为 6

int max = (a > b) ? a : b; // 三目运算符,max = b = 10

运算符具有优先级和结合性:优先级决定运算顺序,结合性决定同级运算符的运算方向。当不确定优先级时,建议使用圆括号明确分组,以提高代码可读性。

关键字

下表列出了 C 中的保留字。这些保留字不能作为常量名、变量名或其他标识符名称,它们在 C 语言中具有特定的语法含义。

关键字 说明
auto声明自动变量(局部变量默认存储类型,现代 C 中很少显式使用)
break跳出当前循环或 switch 语句
caseswitch 语句中的分支标签
char声明字符型变量或函数返回值类型(通常占 1 字节)
const定义只读常量,被 const 修饰的变量其值不能再被改变
continue结束本次循环迭代,直接开始下一轮循环
defaultswitch 语句中的默认分支(所有 case 均不匹配时执行)
dodo-while 循环的循环体,循环体至少执行一次
double声明双精度浮点型变量或函数返回值类型(通常占 8 字节)
else条件语句的否定分支(与 if 连用)
enum声明枚举类型,用于定义一组命名的整型常量
extern声明变量或函数在其它文件或本文件的其他位置定义
float声明单精度浮点型变量或函数返回值类型(通常占 4 字节)
forfor 循环语句,适合已知循环次数的场景
goto无条件跳转语句(应谨慎使用,可能降低代码可读性)
if条件判断语句
int声明整型变量或函数返回值类型(通常占 4 字节)
long声明长整型变量或函数返回值类型
register建议编译器将变量存入寄存器以提高访问速度(现代编译器通常自动优化)
return函数返回语句,可带返回值,也可不带(void 函数)
short声明短整型变量或函数返回值类型(通常占 2 字节)
signed声明有符号类型变量(可表示负数,整型默认有符号)
sizeof计算数据类型或变量所占的字节数(编译期运算符)
static声明静态变量(局部静态变量生命周期延续到程序结束;全局静态变量限制为文件作用域)
struct声明结构体类型,将不同类型的数据组合成一个整体
switch多分支选择语句,根据表达式的值跳转到对应 case
typedef为已有数据类型定义新的别名,提高代码可读性
unsigned声明无符号类型变量(只能表示非负数,范围更大)
union声明共用体类型,所有成员共享同一块内存空间
void声明函数无返回值或无参数;也用于声明无类型指针 void*
volatile声明变量可能被程序外部因素(如硬件或中断)改变,禁止编译器优化该变量的读写
whilewhile 循环语句,适合循环次数未知但终止条件明确的场景

C99 新增关键字

_Bool _Complex _Imaginary inline restrict

C99 新增的关键字主要用于:布尔类型支持(_Bool)、复数支持(_Complex_Imaginary)、内联函数优化(inline)、指针别名限制优化(restrict)。

C11 新增关键字

_Alignas _Alignof _Atomic _Generic _Noreturn
_Static_assert _Thread_local      

C11 新增的关键字主要用于:内存对齐控制(_Alignas_Alignof)、原子操作与多线程支持(_Atomic_Thread_local)、泛型选择(_Generic)、静态断言(_Static_assert)和标记无返回值函数(_Noreturn)。


C 中的空格

只包含空格的行,被称为空白行,可能带有注释,C 编译器会完全忽略它。

在 C 中,空格用于描述空白符(空格键)、制表符(Tab)、换行符和注释。空格分隔语句的各个部分,让编译器能识别语句中的某个元素(比如 int)在哪里结束,下一个元素在哪里开始。因此,在下面的语句中:


int age;

intage 之间必须至少有一个空格字符(通常是一个空白符),这样编译器才能够区分它们。另一方面,在下面的语句中:


fruit = apples + oranges;   // 获取水果的总数

fruit=,或者 =apples 之间的空格字符不是必需的,但是为了增强可读性,您可以根据需要适当增加一些空格。

良好的空格和缩进习惯是高质量代码的重要组成部分,建议遵循以下规范:

  • 使用一致的缩进(通常是 4 个空格或 1 个制表符)来表示代码块层级。
  • 在运算符两侧添加空格(如 a + b),使表达式更易读。
  • 在逗号后添加空格(如 int a, b, c;),区分多个元素。
  • 适当使用空行分隔逻辑相关的代码段,提高整体可读性。