1 引言:从Hello World开始
学习任何编程语言,通常都是从"Hello, World!"程序开始的。下面是一个最简单的C语言程序:

#include <stdio.h>

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


这个程序虽然只有几行代码,却包含了C语言程序的所有核心构成要素:

预处理指令(#include <stdio.h>)

函数(main函数)

函数调用(printf)

语句(printf("Hello, World!\n");和return 0;)

代码块(由{ }括起来的部分)

本章将逐一剖析这些要素。

2 预处理指令
2.1 预处理指令的基本概念
在上一章中,我们已经了解到C程序的编译过程包含预处理阶段。预处理指令就是在这一阶段被处理的特殊命令,它们以井号#开头,通常放在程序的开头。

预处理指令不是C语言本身的组成部分,而是指导预处理器如何对源代码进行加工的指令。它们在编译之前被处理,处理结果才进入后续的编译阶段。

2.2 #include指令
#include是C语言中最常用的预处理指令,它的作用是将指定文件的内容插入到当前文件的#include指令所在位置。

2.2.1 两种包含形式
#include指令有两种使用形式:

形式一:尖括号形式

#include <文件名>


形式二:双引号形式

#include "文件名"


这两种形式的区别在于搜索头文件的路径不同:

形式 搜索路径 典型用途
#include <文件名> 在系统指定的标准头文件目录中搜索 包含标准库头文件(如stdio.h
#include "文件名" 先在当前源文件所在目录搜索,找不到再到系统目录搜索 包含用户自定义的头文件


2.2.2 头文件的作用
头文件(通常以.h为扩展名)主要包含以下内容:

函数声明:告诉编译器某个函数的存在及其参数类型、返回类型

宏定义:使用#define定义的常量或宏

类型定义:如struct、union、enum等自定义类型的定义

全局变量声明:使用extern声明的外部变量

以stdio.h为例,它包含了标准输入输出库函数的声明,如printf、scanf、getchar、putchar等。

2.2.3 头文件包含的实质
假设有一个头文件myheader.h内容如下:

/* myheader.h */
#define PI 3.14159
int add(int a, int b);


当我们在源文件中写:

#include "myheader.h"


预处理后,#include "myheader.h"这行会被替换为myheader.h的全部内容,相当于:

/* myheader.h的内容开始 */
#define PI 3.14159
int add(int a, int b);
/* myheader.h的内容结束 */


2.2.4 避免重复包含
当头文件被多次包含时,可能会导致重复定义错误。常用的解决方法是使用条件编译指令:

c
/* myheader.h */
#ifndef MYHEADER_H
#define MYHEADER_H

#define PI 3.14159
int add(int a, int b);

#endif


这种模式被称为包含守卫(Include Guard),它确保头文件的内容只被处理一次。

2.3 其他常用预处理指令
除了#include,C语言还有其他重要的预处理指令,下表简要列举:

指令 用途 示例
#define 定义宏或常量 #define MAX 100
#undef 取消宏定义 #undef MAX
#if 条件编译 #if MAX > 50
#ifdef 如果宏已定义 #ifdef DEBUG
#ifndef 如果宏未定义 #ifndef HEADER_H
#else #if配合 #else
#elif 否则如果 #elif MAX == 100
#endif 结束条件编译 #endif
#error 输出错误信息 #error "Version mismatch"
#pragma 编译器特定指令 #pragma once


这些指令将在后续章节中详细介绍。

3 main函数的本质
3.1 main函数的定义形式
main函数是C程序的入口点——程序从main函数开始执行。ANSI标准规定了两种主要的main函数定义形式:

形式一:无参数形式

int main(void)
{
    /* 函数体 */
    return 0;
}


形式二:带参数形式

int main(int argc, char *argv[])
{
    /* 函数体 */
    return 0;
}


其中:

argc(argument count):命令行参数的个数

argv(argument vector):指向命令行参数字符串的指针数组

3.2 main函数的返回类型
main函数的返回类型是int,表示它返回一个整数值给操作系统。

3.2.1 返回值的作用
main函数的返回值用于向操作系统报告程序的执行状态:

返回0:通常表示程序成功执行

返回非0:通常表示程序执行出错,具体数值可表示不同错误类型

在批处理脚本或Shell脚本中,可以通过检查程序的返回值来判断执行结果。

3.2.2 返回值的使用示例

#include <stdio.h>

int main(void)
{
    int num;
    printf("请输入一个正数:");
    scanf("%d", &num);
    
    if (num <= 0) {
        printf("错误:输入的不是正数\n");
        return 1;  /* 返回1表示错误 */
    }
    
    printf("您输入的是:%d\n", num);
    return 0;  /* 返回0表示成功 */
}


在Linux/Unix系统中,可以通过echo $?查看上一个程序的返回值:

bash
$ ./a.out
请输入一个正数:5
您输入的是:5
$ echo $?
0
$ ./a.out
请输入一个正数:-3
错误:输入的不是正数
$ echo $?
1
3.3 main函数的参数
3.3.1 参数的含义
带参数的main函数允许程序接收命令行传递的参数:

#include <stdio.h>

int main(int argc, char *argv[])
{
    int i;
    printf("参数个数:%d\n", argc);
    
    for (i = 0; i < argc; i++) {
        printf("argv[%d] = %s\n", i, argv[i]);
    }
    
    return 0;
}


3.3.2 参数传递示例
假设将上述程序编译为test,在命令行执行:

$ ./test hello world 123


输出:

text
参数个数:4
argv[0] = ./test
argv[1] = hello
argv[2] = world
argv[3] = 123
重要说明:

argv[0]通常是程序本身的名称

argv[argc]是一个空指针(NULL)

参数之间以空白字符(空格、制表符等)分隔

如果参数本身包含空格,需要用引号括起来

3.4 main函数的调用者
一个常见的问题是:main函数由谁调用?

答案是:main函数由操作系统调用。当程序被执行时,操作系统负责加载程序到内存,然后调用main函数,程序开始运行。当main函数返回时,控制权交还给操作系统。

3.5 标准允许的其他main形式
虽然标准规定了上述两种形式,但某些编译器可能允许其他变体:

/* 省略返回类型(不推荐) */
main()  /* 默认返回int */
{
    /* ... */
}

/* void返回类型(不符合标准) */
void main()  /* 某些系统允许,但不推荐 */
{
    /* ... */
}


为了代码的可移植性,始终使用标准规定的形式:int main(void)或int main(int argc, char *argv[])。

4 标准输入输出库函数
4.1 标准库函数的概念
C语言本身只提供了核心语言特性(如数据类型、运算符、控制结构等),而输入输出操作并不属于语言的核心部分。C语言通过标准库(Standard Library)提供了丰富的函数,用于执行各种常见任务。

标准库函数是一组预先编写好的函数,它们被编译成目标代码,存放在库文件中。当我们使用#include指令包含相应的头文件后,就可以在程序中调用这些函数。

4.2 标准输入输出库
标准输入输出库(Standard Input Output Library)是C语言最常用的库,由头文件stdio.h声明。它提供了与输入输出相关的函数、宏和类型定义。

4.2.1 标准输入输出的概念
在C程序中,有三个预定义的标准流(Standard Streams):

流名称 设备 默认关联 用途
stdin 标准输入 键盘 从键盘读取数据
stdout 标准输出 屏幕 向屏幕输出数据
stderr 标准错误 屏幕 向屏幕输出错误信息


这些流在程序启动时自动打开,在程序结束时自动关闭。

4.3 printf函数详解
printf(print formatted)是C语言中最常用的输出函数,用于将格式化的数据输出到stdout。

4.3.1 函数原型

int printf(const char *format, ...);


参数:

format:格式控制字符串,指定输出的格式

...:可变参数列表,表示要输出的数据

返回值:

成功:返回输出的字符数

失败:返回负数

4.3.2 格式控制字符串
格式控制字符串包含两种成分:

普通字符:原样输出

格式转换说明符:以%开头,用于指定如何输出对应的参数

常用的格式转换说明符:

说明符 含义 示例
%d 输出十进制整数 printf("%d", 123);
%i 输出十进制整数(与%d同) printf("%i", 123);
%u 输出无符号十进制整数 printf("%u", 123u);
%o 输出八进制整数 printf("%o", 123);
%x 输出十六进制整数(小写) printf("%x", 123);
%X 输出十六进制整数(大写) printf("%X", 123);
%f 输出浮点数(小数形式) printf("%f", 3.14159);
%e 输出浮点数(科学计数法,e小写) printf("%e", 3.14159);
%E 输出浮点数(科学计数法,E大写) printf("%E", 3.14159);
%g 自动选择%f%e中较短的 printf("%g", 3.14159);
%c 输出单个字符 printf("%c", 'A');
%s 输出字符串 printf("%s", "Hello");
%p 输出指针地址 printf("%p", &a);
%% 输出百分号%本身 printf("%%");


4.3.3 格式修饰符
在%和转换字符之间可以插入格式修饰符,控制输出的格式:

修饰符 含义 示例
- 左对齐 %-10d
+ 显示正负号 %+d
空格 正数显示空格,负数显示负号 % d
0 用0填充宽度 %05d
数字 最小字段宽度 %10d
.数字 精度(浮点数小数点后位数,字符串最大长度) %.2f%.5s
h 短整数(short) %hd
l 长整数(long) %ld
ll 长长整数(long long) %lld


4.3.4 printf使用示例

#include <stdio.h>

int main(void)
{
    int i = 123;
    float f = 3.14159;
    char c = 'A';
    char str[] = "Hello";
    
    /* 基本用法 */
    printf("整数:%d\n", i);
    printf("浮点数:%f\n", f);
    printf("字符:%c\n", c);
    printf("字符串:%s\n", str);
    
    /* 宽度和精度控制 */
    printf("宽度10:%10d\n", i);      /* 右对齐,宽度10 */
    printf("左对齐:%-10d结束\n", i);  /* 左对齐,宽度10 */
    printf("精度.2:%.2f\n", f);       /* 保留两位小数 */
    printf("补零:%05d\n", i);         /* 宽度5,不足补零 */
    
    /* 多种格式输出同一个数 */
    int n = 255;
    printf("十进制:%d\n", n);
    printf("八进制:%o\n", n);
    printf("十六进制:%x\n", n);
    printf("十六进制大写:%X\n", n);
    
    return 0;
}


输出:

整数:123
浮点数:3.141590
字符:A
字符串:Hello
宽度10:       123
左对齐:123       结束
精度.2:3.14
补零:00123
十进制:255
八进制:377
十六进制:ff
十六进制大写:FF
4.3.5 转义字符
在字符串中,反斜杠\开始一个转义序列,表示特殊字符:

转义序列 含义
\n 换行
\t 水平制表符(Tab)
\b 退格
\r 回车
\f 换页
\a 响铃
\\ 反斜杠本身
\' 单引号
\" 双引号
\0 空字符(字符串结束标志)


4.4 scanf函数详解
scanf(scan formatted)是C语言中用于从stdin读取格式化输入的函数。

4.4.1 函数原型

int scanf(const char *format, ...);


参数:

format:格式控制字符串,指定输入的格式

...:可变参数列表,必须是变量的地址(指针)

返回值:

成功:返回成功匹配并赋值的输入项个数

失败或遇到文件结束:返回EOF

4.4.2 与printf的重要区别
scanf的参数必须是变量的地址,而不是变量本身:

int num;
scanf("%d", &num);  /* 正确:&num是num的地址 */
scanf("%d", num);   /* 错误:num是变量的值,不是地址 */


4.4.3 scanf使用示例

#include <stdio.h>

int main(void)
{
    int i;
    float f;
    char c;
    char str[100];  /* 字符数组,用于存储字符串 */
    
    /* 读取整数 */
    printf("请输入一个整数:");
    scanf("%d", &i);
    printf("您输入了:%d\n", i);
    
    /* 读取浮点数 */
    printf("请输入一个浮点数:");
    scanf("%f", &f);
    printf("您输入了:%f\n", f);
    
    /* 读取字符 */
    printf("请输入一个字符:");
    scanf(" %c", &c);  /* 注意%c前的空格,用于消耗上一输入的换行符 */
    printf("您输入了:%c\n", c);
    
    /* 读取字符串(不用加&,数组名本身就是地址) */
    printf("请输入一个字符串:");
    scanf("%s", str);  /* 字符串输入遇到空白字符停止 */
    printf("您输入了:%s\n", str);
    
    return 0;
}


4.4.4 scanf的常见问题与注意事项
1. 缓冲区中的换行符问题

#include <stdio.h>

int main(void)
{
    int i;
    char c;
    
    printf("请输入一个整数:");
    scanf("%d", &i);
    
    printf("请输入一个字符:");
    scanf("%c", &c);  /* 问题:会读取上一行输入末尾的换行符 */
    
    printf("整数:%d,字符:%c\n", i, c);
    return 0;
}


输入:

text
123
A
输出:

text
请输入一个整数:123
请输入一个字符:整数:123,字符:
字符c读取的是换行符,而不是'A'。

解决方法:在%c前加空格,告诉scanf跳过空白字符:

scanf(" %c", &c);  /* 空格会跳过所有空白字符(空格、制表符、换行符) */


2. 输入格式必须严格匹配

c
scanf(" %c", &c);  /* 空格会跳过所有空白字符(空格、制表符、换行符) */


如果用户输入123 456(没有逗号),则只有a被赋值,b保持原值(未定义)。

3. 字符串输入的限制

%s在遇到空白字符(空格、制表符、换行符)时会停止读取。如果要读取包含空格的整行,应使用fgets函数。

4. 检查输入是否成功

利用scanf的返回值可以检查输入是否成功:

int num;
if (scanf("%d", &num) != 1) {
    printf("输入错误,请输入整数\n");
    /* 处理错误 */
}


4.5 字符输入输出函数
除了格式化的printf和scanf,stdio.h还提供了字符级的输入输出函数。

4.5.1 getchar和putchar
getchar:从stdin读取一个字符

int getchar(void);


返回值:读取的字符(作为int返回),出错或文件结束时返回EOF

注意:返回类型是int而不是char,这是为了能够返回EOF(通常是-1)

putchar:向stdout输出一个字符

int putchar(int c);


参数:要输出的字符

返回值:成功返回输出的字符,失败返回EOF

使用示例:

#include <stdio.h>

int main(void)
{
    int ch;  /* 注意:用int类型接收getchar的返回值 */
    
    printf("请输入一些字符(输入Ctrl+D结束):\n");
    
    while ((ch = getchar()) != EOF) {
        putchar(ch);  /* 回显输入的字符 */
    }
    
    printf("\n程序结束\n");
    return 0;
}


这个程序会逐个读取字符并输出,直到遇到文件结束标志(Linux:Ctrl+D,Windows:Ctrl+Z)。

4.5.2 gets和puts
gets:从stdin读取一行字符串(危险!不要使用)

char *gets(char *str);

gets函数在C11标准中已被正式移除,因为它无法限制读取的字符数,容易导致缓冲区溢出。

puts:向stdout输出字符串,并自动添加换行符

int puts(const char *str);


使用示例:

#include <stdio.h>

int main(void)
{
    char str[100];
    
    printf("请输入一行文字:");
    fgets(str, sizeof(str), stdin);  /* 安全的替代:fgets */
    
    printf("您输入的是:");
    puts(str);  /* puts会自动添加换行符 */
    
    return 0;
}


5 语句与代码块
5.1 语句的概念
语句(Statement)是C程序中的可执行单元。每条语句都完成某种操作,并以分号;结尾。

C语言中的语句可以分为以下几类:

5.1.1 表达式语句
任何表达式后跟分号就构成表达式语句:

x = y + z;      /* 赋值表达式语句 */
i++;             /* 自增表达式语句 */
printf("Hi");    /* 函数调用表达式语句 */
3 + 5;           /* 合法但无意义的语句,计算结果被丢弃 */


5.1.2 控制语句
控制语句控制程序的执行流程,包括:

条件判断语句:if、switch

循环执行语句:for、while、do-while

转向语句:break、continue、goto、return

这些将在后续章节详细讲解。

5.1.3 空语句
只有一个分号的语句称为空语句:

;  /* 空语句,什么也不做 */


空语句在某些语法要求有语句但无需操作的地方有用,例如无限循环:

while (1)
    ;  /* 空循环体 */


5.2 代码块的概念
代码块(Block),也称为复合语句(Compound Statement),是由一对花括号{ }括起来的零条或多条语句的集合。

5.2.1 代码块的基本形式

{
    语句1;
    语句2;
    /* ... */
    语句n;
}


5.2.2 代码块的语法作用
在语法上,代码块被视为一条语句,因此可以出现在任何单条语句可以出现的位置。例如,在if语句中:

if (条件) {
    语句1;
    语句2;
    语句3;
}


如果不使用代码块,if后面只能跟一条语句:

if (条件)
    语句1;  /* if只控制这一条语句 */
语句2;       /* 这条语句不在if控制范围内,总会执行 */


5.2.3 代码块与作用域
代码块还定义了变量的作用域(Scope)。在代码块内部声明的变量,其作用域仅限于该代码块内部:

#include <stdio.h>

int main(void)
{
    int a = 10;  /* 外层变量 */
    
    {
        int b = 20;  /* 内层变量,作用域仅限于这个代码块 */
        printf("块内:a = %d, b = %d\n", a, b);
    }
    
    printf("块外:a = %d\n", a);
    /* printf("块外:b = %d\n", b); */  /* 错误:b在此处未定义 */
    
    return 0;
}


代码块可以嵌套,内层代码块可以访问外层代码块的变量,但外层不能访问内层的变量。

5.2.4 代码块与函数体
函数的函数体本身就是一个代码块:

int max(int x, int y)
{  /* 代码块开始 */
    int result;
    if (x > y)
        result = x;
    else
        result = y;
    return result;
}  /* 代码块结束 */


5.3 语句与代码块的关系总结

特性 简单语句 代码块
构成 表达式 + 分号 花括号 { } 括起的语句序列
结尾 必须有分号 花括号后不用分号
是否引入作用域
包含关系 不能包含其他语句 可以包含多个语句和其他代码块


6 综合案例分析
让我们综合本章所学,分析一个稍微复杂的程序:

/* 文件名:calculator.c */
#include <stdio.h>   /* 预处理指令:包含标准输入输出库 */

int main(void)       /* 主函数,程序入口 */
{                    /* 代码块开始 */
    int num1, num2;      /* 变量声明 */
    int sum;             /* 变量声明 */
    char operator;       /* 运算符 */
    int result;          /* 计算结果 */
    
    /* 输入部分 */
    printf("简单计算器\n");
    printf("请输入第一个整数:");
    scanf("%d", &num1);      /* 输入语句 */
    
    printf("请输入运算符(+、-、*、/):");
    scanf(" %c", &operator); /* 注意空格 */
    
    printf("请输入第二个整数:");
    scanf("%d", &num2);
    
    /* 计算部分 - 使用if语句控制流程 */
    if (operator == '+') {
        result = num1 + num2;        /* 表达式语句 */
    }
    else if (operator == '-') {
        result = num1 - num2;
    }
    else if (operator == '*') {
        result = num1 * num2;
    }
    else if (operator == '/') {
        if (num2 != 0) {              /* 嵌套的if语句 */
            result = num1 / num2;
        } else {
            printf("错误:除数不能为0\n");
            return 1;                  /* 返回错误代码 */
        }
    }
    else {
        printf("错误:不支持的运算符\n");
        return 1;                      /* 返回错误代码 */
    }
    
    /* 输出部分 */
    printf("%d %c %d = %d\n", num1, operator, num2, result);
    
    return 0;  /* 返回0表示成功 */
}              /* 代码块结束 */


这个程序包含了本章讨论的所有要素:

预处理指令:#include <stdio.h>

main函数:程序入口,带返回值

变量声明:int num1, num2;等

输入输出语句:printf和scanf函数调用

控制语句:if-else条件判断

表达式语句:result = num1 + num2;等

代码块:函数体、if语句体都用{}括起

return语句:返回状态值

7 常见错误与注意事项
7.1 忘记包含必要的头文件

/* 错误示例 */
int main(void)
{
    printf("Hello\n");  /* 错误:printf未声明 */
    return 0;
}


解决方法:在文件开头添加#include <stdio.h>

7.2 main函数格式错误

/* 错误示例 */
void main()  /* 不符合标准 */
{
    /* ... */
}

/* 正确示例 */
int main(void)  /* 符合标准 */
{
    /* ... */
    return 0;
}


7.3 忘记添加分号

#include <stdio.h>

int main(void)
{
    int a = 10  /* 错误:缺少分号 */
    printf("%d\n", a);
    return 0;
}

7.4 代码块匹配错误

#include <stdio.h>

int main(void)
{
    if (1) {
        printf("条件成立\n");
    /* 缺少右花括号 */
    return 0;
}


7.5 scanf缺少取地址符

#include <stdio.h>

int main(void)
{
    int num;
    scanf("%d", num);  /* 错误:应该是 &num */
    printf("%d\n", num);
    return 0;
}


7.6 格式说明符与数据类型不匹配

#include <stdio.h>

int main(void)
{
    int num = 100;
    printf("%f\n", num);  /* 错误:%f用于float/double,但传入int */
    return 0;
}


8 本章小结
本章系统分析了C程序的基本结构,主要包括以下内容:

1. 预处理指令

#include用于包含头文件,有尖括号(系统头文件)和双引号(用户头文件)两种形式

预处理指令在编译前被处理,不属于C语言本身的语法

2. main函数

程序的入口点,由操作系统调用

标准形式:int main(void)和int main(int argc, char *argv[])

返回值向操作系统报告执行状态(0表示成功)

3. 标准输入输出库函数

printf:格式化输出,支持丰富的格式控制符和修饰符

scanf:格式化输入,注意参数必须为变量地址

getchar/putchar:字符级输入输出

转义字符:表示特殊字符(如\n换行)

4. 语句与代码块

语句:以分号结尾的可执行单元,包括表达式语句、控制语句、空语句

代码块:花括号括起的语句集合,在语法上视为一条语句,并引入变量作用域

理解这些基本构成要素,是阅读和编写C程序的基础。在后续的章节中,我们将在此基础上深入学习数据类型、运算符、控制结构等更复杂的内容。

 

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐