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社区

更多推荐