xuexiziliao

C语言程序设计基础教程——从零开始学编程

14 阅读 2026-06-02
内容简介

全面介绍C语言编程基础,涵盖数据类型、运算符、控制结构、函数、数组、指针、结构体等核心内容,配合大量编程实例与练习题,适合编程初学者。

C语言程序设计基础教程——从零开始学编程

本教程全面介绍C语言编程基础,涵盖数据类型、运算符、控制结构、函数、数组、指针、结构体等核心内容,配合大量编程实例与练习题,适合编程初学者。


第一章 C语言概述

1.1 C语言的发展历史与特点

C语言诞生于1972年,由Dennis Ritchie在贝尔实验室开发,最初用于编写UNIX操作系统。经过数十年发展,C语言已成为世界上使用最广泛的编程语言之一。

C语言的主要特点:

  • 简洁紧凑:仅有32个关键字(C89标准),语法精炼
  • 高效灵活:接近硬件底层,执行效率高,可直接操作内存
  • 可移植性强:标准C程序几乎可以在任何平台上编译运行
  • 结构化语言:支持模块化程序设计,便于代码组织
  • 丰富的运算符:提供40多种运算符,功能强大

C语言标准的演进:

  • K&R C(1978年):最初的非正式标准
  • ANSI C / C89(1989年):第一个正式标准
  • C99(1999年):增加了变长数组、//注释等特性
  • C11(2011年):增加了多线程支持、泛型选择等

1.2 开发环境搭建

学习C语言需要一个编译器和一个代码编辑器。常用的选择包括:

编译器:

  • GCC(GNU Compiler Collection):Linux/Mac下的标准选择
  • MinGW:Windows下的GCC移植版本
  • MSVC:微软的C/C++编译器(Visual Studio自带)
  • Clang:LLVM项目的编译器,错误提示友好

集成开发环境(IDE):

  • Visual Studio Code + C/C++扩展
  • Code::Blocks
  • Dev-C++(适合初学者)
  • Visual Studio(Windows)

1.3 第一个C程序

#include <stdio.h>

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

逐行解析:

#include <stdio.h>:预处理指令,将标准输入输出头文件的内容包含到源文件中。stdio.h提供了printfscanf等函数的声明。

int main(void):主函数,每个C程序必须有且只有一个main函数。int表示函数返回整数值,void表示不接受参数。

printf("Hello, World!\n");:调用标准库函数printf输出字符串。\n是转义字符,表示换行。每条语句以分号结尾。

return 0;:向操作系统返回状态码0,表示程序正常结束。

程序编译运行过程:

源代码(.c) → 预处理 → 编译 → 汇编 → 链接 → 可执行文件

在命令行中:

gcc hello.c -o hello    # 编译
./hello                  # 运行

例 1.1 编写程序,输出自己的姓名和年龄。

#include <stdio.h>

int main(void)
{
    printf("姓名:张三\n");
    printf("年龄:20\n");
    printf("专业:计算机科学\n");
    return 0;
}

输出:

姓名:张三
年龄:20
专业:计算机科学

练习题 1.1

题 1 编写程序,输出以下图案:

*
***
*****
*******
*****
***
*

答案:

#include <stdio.h>
int main(void)
{
    printf("*\n***\n*****\n*******\n*****\n***\n*\n");
    return 0;
}

题 2 #include <stdio.h>中,#include的作用是什么?stdio代表什么?

答案: #include是预处理指令,用于将指定头文件的内容插入到源代码中。stdio是"standard input/output"的缩写,即标准输入输出库。


第二章 数据类型与运算符

2.1 基本数据类型

C语言提供了四种基本数据类型:

类型 关键字 字节数(典型) 取值范围(典型)
整型 int 4 \(-2^{31}\) ~ \(2^{31}-1\)
单精度浮点 float 4 约6-7位有效数字
双精度浮点 double 8 约15-16位有效数字
字符型 char 1 \(-128\) ~ \(127\)\(0\) ~ \(255\)

整型的修饰符:

  • short int(短整型):通常2字节
  • long int(长整型):至少4字节
  • unsigned int(无符号整型):非负整数

sizeof运算符:

sizeof用于计算数据类型或变量所占的字节数。

#include <stdio.h>

int main(void)
{
    printf("int: %zu bytes\n", sizeof(int));
    printf("float: %zu bytes\n", sizeof(float));
    printf("double: %zu bytes\n", sizeof(double));
    printf("char: %zu bytes\n", sizeof(char));
    return 0;
}

类型转换:

  • 隐式转换:运算时自动进行,小类型向大类型转换
    • 规则:char → int → long → float → double
  • 显式转换(强制类型转换):使用(类型名)语法
    • 例:(int)3.7的结果为3,直接截断小数部分

例 2.1 分析以下程序的输出:

#include <stdio.h>

int main(void)
{
    int a = 5;
    double b = 2.5;
    double result = a / b;
    printf("result = %f\n", result);

    int c = 7, d = 2;
    printf("c / d = %d\n", c / d);
    printf("(double)c / d = %f\n", (double)c / d);
    return 0;
}

输出:

result = 2.000000
c / d = 3
(double)c / d = 3.500000

分析: a / b中,a被隐式转换为double,结果为2.0c / d是整数除法,结果为3(double)c / dc强制转换为double后除法变为浮点除法。

2.2 常量与变量

变量的声明与初始化:

int age = 20;           // 声明并初始化
double score;           // 先声明
score = 95.5;           // 后赋值
char grade = 'A';       // 字符型变量

const常量:

const double PI = 3.14159265358979;
const int MAX_SIZE = 100;
// PI = 3.14;  // 错误!const常量不可修改

#define宏常量:

#define PI 3.14159265358979
#define MAX_SIZE 100

const#define的区别:

  • const定义的常量有类型,编译器会进行类型检查
  • #define是简单的文本替换,没有类型信息
  • const常量有作用域限制,#define从定义处到文件末尾有效

2.3 运算符

算术运算符:

运算符 含义 示例
+ 加法 3 + 25
- 减法 3 - 21
* 乘法 3 * 26
/ 除法 7 / 23(整数除法)
% 取模 7 % 21
++ 自增 a++(后置)、++a(前置)
-- 自减 a--(后置)、--a(前置)

前置与后置自增的区别:

int a = 5;
int b = a++;  // b = 5, a = 6(先使用后加)
int c = ++a;  // c = 7, a = 7(先加后使用)

关系运算符: ><>=<===!=

逻辑运算符: &&(与)、||(或)、!(非)

短路求值:

  • a && b:若a为假,则不计算b
  • a || b:若a为真,则不计算b

位运算符: &(按位与)、|(按位或)、^(按位异或)、~(取反)、<<(左移)、>>(右移)

例 2.2 不使用临时变量,交换两个整数的值。

#include <stdio.h>

int main(void)
{
    int a = 10, b = 20;
    printf("交换前:a = %d, b = %d\n", a, b);

    a = a ^ b;
    b = a ^ b;
    a = a ^ b;

    printf("交换后:a = %d, b = %d\n", a, b);
    return 0;
}

原理: 利用异或运算的性质:\(a \oplus a = 0\)\(a \oplus 0 = a\),异或满足交换律和结合律。

运算符优先级(从高到低,摘取常用):

优先级 运算符
1(最高) () [] -> .
2 ! ~ ++ -- (type) sizeof * &
3 * / %
4 + -
5 << >>
6 < <= > >=
7 == !=
8-10 & ^ |
11-12 && ||
13 ?:
14 = += -=
15(最低) ,

练习题 2.1

题 1 写出以下代码的输出:

int x = 3;
printf("%d %d %d\n", x++, x, ++x);

答案: 输出结果是未定义行为(UB)。在同一个序列点内多次修改同一变量是C语言标准未定义的,不同编译器可能产生不同结果。应避免这种写法。

题 2 用位运算判断一个整数是否为偶数。

答案:

int is_even = !(n & 1);  // 最低位为0则为偶数

题 3 计算表达式 2 + 3 > 4 && !(5 % 2 == 0) 的值。

答案: 2 + 3 = 55 > 41(真),5 % 2 = 11 == 00!0 = 11 && 1 = 1。结果为1


第三章 控制结构

3.1 顺序结构

顺序结构是最基本的程序结构,语句按照从上到下的顺序依次执行。

例 3.1 输入圆的半径,计算面积和周长。

#include <stdio.h>
#define PI 3.14159

int main(void)
{
    double r, area, circumference;
    printf("请输入圆的半径:");
    scanf("%lf", &r);

    area = PI * r * r;
    circumference = 2 * PI * r;

    printf("面积 = %.2f\n", area);
    printf("周长 = %.2f\n", circumference);
    return 0;
}

scanf函数说明:

  • %lf:读取double类型(注意float%f
  • &r:取变量r的地址(指针概念,后面详细讲解)
  • 输入多个值用空格或回车分隔

3.2 选择结构

if-else语句

if (条件1) {
    // 条件1为真时执行
} else if (条件2) {
    // 条件2为真时执行
} else {
    // 以上条件都不满足时执行
}

例 3.2 输入成绩,输出等级(A: 90-100, B: 80-89, C: 70-79, D: 60-69, F: 0-59)。

#include <stdio.h>

int main(void)
{
    int score;
    printf("请输入成绩:");
    scanf("%d", &score);

    if (score < 0 || score > 100) {
        printf("输入错误!\n");
    } else if (score >= 90) {
        printf("等级:A\n");
    } else if (score >= 80) {
        printf("等级:B\n");
    } else if (score >= 70) {
        printf("等级:C\n");
    } else if (score >= 60) {
        printf("等级:D\n");
    } else {
        printf("等级:F\n");
    }
    return 0;
}

switch-case语句

switch (表达式) {
    case 常量1:
        // 语句
        break;
    case 常量2:
        // 语句
        break;
    default:
        // 默认语句
}

例 3.3 输入数字1-7,输出对应的星期几。

#include <stdio.h>

int main(void)
{
    int day;
    printf("请输入1-7:");
    scanf("%d", &day);

    switch (day) {
        case 1: printf("星期一\n"); break;
        case 2: printf("星期二\n"); break;
        case 3: printf("星期三\n"); break;
        case 4: printf("星期四\n"); break;
        case 5: printf("星期五\n"); break;
        case 6: printf("星期六\n"); break;
        case 7: printf("星期日\n"); break;
        default: printf("输入错误!\n");
    }
    return 0;
}

注意: switchbreak的作用是跳出switch语句。如果省略break,会继续执行下一个case(称为"穿透")。

条件运算符

max = (a > b) ? a : b;  // 等价于 if-else 的简写

3.3 循环结构

for循环

for (初始化; 条件; 更新) {
    // 循环体
}

例 3.4 计算 \(1 + 2 + 3 + \cdots + 100\)

#include <stdio.h>

int main(void)
{
    int sum = 0;
    for (int i = 1; i <= 100; i++) {
        sum += i;
    }
    printf("sum = %d\n", sum);  // 输出 5050
    return 0;
}

while循环

while (条件) {
    // 循环体
}

例 3.5 输入一个正整数,计算其各位数字之和。

#include <stdio.h>

int main(void)
{
    int n, sum = 0;
    printf("请输入正整数:");
    scanf("%d", &n);

    while (n > 0) {
        sum += n % 10;  // 取最后一位
        n /= 10;        // 去掉最后一位
    }
    printf("各位数字之和 = %d\n", sum);
    return 0;
}

do-while循环

do {
    // 循环体(至少执行一次)
} while (条件);

例 3.6 使用do-while实现猜数字游戏。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void)
{
    srand(time(NULL));
    int target = rand() % 100 + 1;
    int guess, count = 0;

    do {
        printf("请猜一个1-100的数字:");
        scanf("%d", &guess);
        count++;

        if (guess > target) {
            printf("太大了!\n");
        } else if (guess < target) {
            printf("太小了!\n");
        } else {
            printf("恭喜你猜对了!用了%d次\n", count);
        }
    } while (guess != target);

    return 0;
}

break与continue

  • break:立即跳出当前循环(只跳出一层)
  • continue:跳过本次循环的剩余部分,进入下一次循环

例 3.7 输出100以内所有能被3整除但不能被5整除的数。

#include <stdio.h>

int main(void)
{
    for (int i = 1; i <= 100; i++) {
        if (i % 3 != 0) continue;   // 不能被3整除则跳过
        if (i % 5 == 0) continue;   // 能被5整除则跳过
        printf("%d ", i);
    }
    printf("\n");
    return 0;
}

嵌套循环

例 3.8 打印九九乘法表。

#include <stdio.h>

int main(void)
{
    for (int i = 1; i <= 9; i++) {
        for (int j = 1; j <= i; j++) {
            printf("%d×%d=%-4d", j, i, i * j);
        }
        printf("\n");
    }
    return 0;
}

输出:

1×1=1
1×2=2   2×2=4
1×3=3   2×3=6   3×3=9
...

练习题 3.1

题 1 判断一个正整数是否为素数。

答案:

#include <stdio.h>
#include <math.h>

int main(void)
{
    int n, is_prime = 1;
    printf("请输入正整数:");
    scanf("%d", &n);

    if (n < 2) {
        is_prime = 0;
    } else {
        for (int i = 2; i <= sqrt(n); i++) {
            if (n % i == 0) {
                is_prime = 0;
                break;
            }
        }
    }

    if (is_prime)
        printf("%d 是素数\n", n);
    else
        printf("%d 不是素数\n", n);
    return 0;
}

题 2 计算 \(S = 1 - \dfrac{1}{2} + \dfrac{1}{3} - \dfrac{1}{4} + \cdots + \dfrac{1}{99} - \dfrac{1}{100}\)

答案:

double sum = 0;
for (int i = 1; i <= 100; i++) {
    if (i % 2 == 1)
        sum += 1.0 / i;
    else
        sum -= 1.0 / i;
}
printf("S = %f\n", sum);  // 约 0.688172

题 3 打印以下图案(5行):

    *
   ***
  *****
 *******
*********

答案:

for (int i = 1; i <= 5; i++) {
    for (int j = 1; j <= 5 - i; j++) printf(" ");
    for (int j = 1; j <= 2 * i - 1; j++) printf("*");
    printf("\n");
}

第四章 函数

4.1 函数的定义与调用

函数是C语言程序的基本构建块,用于将复杂问题分解为可管理的小模块。

返回类型 函数名(参数列表)
{
    函数体;
    return 返回值;  // void函数可省略
}

例 4.1 编写函数计算两个整数的最大值。

#include <stdio.h>

int max(int a, int b)
{
    return (a > b) ? a : b;
}

int main(void)
{
    int x = 10, y = 20;
    printf("max(%d, %d) = %d\n", x, y, max(x, y));
    return 0;
}

4.2 参数传递(值传递)

C语言中函数参数传递是值传递,即形参是实参的副本,函数内对形参的修改不会影响实参。

例 4.2 值传递的演示。

#include <stdio.h>

void swap_wrong(int a, int b)
{
    int temp = a;
    a = b;
    b = temp;
    printf("函数内:a = %d, b = %d\n", a, b);
}

int main(void)
{
    int x = 10, y = 20;
    swap_wrong(x, y);
    printf("函数外:x = %d, y = %d\n", x, y);
    return 0;
}

输出:

函数内:a = 20, b = 10
函数外:x = 10, y = 20

可以看到,函数内的交换操作没有影响main中的变量。要实现真正的交换,需要使用指针(第六章讲解)。

4.3 函数的返回值

  • return语句用于从函数返回值并结束函数执行
  • 一个函数可以有多个return语句(如if-else分支中各一个)
  • void函数可以使用return;提前结束,或省略return

4.4 递归函数

递归是函数直接或间接调用自身的技术。递归必须有递归条件(继续递归)和基准条件(停止递归)。

例 4.3 用递归计算 \(n!\)

#include <stdio.h>

long long factorial(int n)
{
    if (n <= 1) return 1;       // 基准条件
    return n * factorial(n - 1); // 递归调用
}

int main(void)
{
    for (int i = 0; i <= 10; i++) {
        printf("%d! = %lld\n", i, factorial(i));
    }
    return 0;
}

递归执行过程(以 \(3!\) 为例):

factorial(3) = 3 * factorial(2)
             = 3 * 2 * factorial(1)
             = 3 * 2 * 1
             = 6

例 4.4 用递归求斐波那契数列第 \(n\) 项。

#include <stdio.h>

int fibonacci(int n)
{
    if (n <= 0) return 0;
    if (n == 1) return 1;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

int main(void)
{
    for (int i = 0; i < 10; i++) {
        printf("F(%d) = %d\n", i, fibonacci(i));
    }
    return 0;
}

注意: 递归版本的斐波那契效率较低(指数级时间复杂度),因为存在大量重复计算。实际应用中常用迭代法或带记忆化的递归。

4.5 变量的作用域与生命周期

作用域: 变量的有效使用范围。

  • 局部变量:在函数或代码块内定义,只在该范围内有效
  • 全局变量:在函数外定义,从定义处到文件末尾都有效

生命周期: 变量存在的时间段。

  • 自动变量auto):函数调用时创建,函数返回时销毁
  • 静态局部变量static):程序运行期间一直存在,但只在定义它的函数内可见
  • 全局变量:程序运行期间一直存在

例 4.5 static局部变量的使用。

#include <stdio.h>

void counter(void)
{
    static int count = 0;  // 只初始化一次
    count++;
    printf("函数被调用了 %d 次\n", count);
}

int main(void)
{
    for (int i = 0; i < 5; i++) {
        counter();
    }
    return 0;
}

输出:

函数被调用了 1 次
函数被调用了 2 次
函数被调用了 3 次
函数被调用了 4 次
函数被调用了 5 次

练习题 4.1

题 1 编写递归函数,计算 \(x^n\)\(x\) 为实数,\(n\) 为非负整数)。

答案:

double power(double x, int n)
{
    if (n == 0) return 1.0;
    if (n % 2 == 0) {
        double half = power(x, n / 2);
        return half * half;
    }
    return x * power(x, n - 1);
}

时间复杂度:\(O(\log n)\)

题 2 编写函数,判断一个正整数是否为回文数(如121、12321)。

答案:

int is_palindrome(int n)
{
    if (n < 0) return 0;
    int original = n, reversed = 0;
    while (n > 0) {
        reversed = reversed * 10 + n % 10;
        n /= 10;
    }
    return original == reversed;
}

题 3 编写函数,用辗转相除法求两个正整数的最大公约数。

答案:

int gcd(int a, int b)
{
    while (b != 0) {
        int temp = b;
        b = a % b;
        a = temp;
    }
    return a;
}

第五章 数组

5.1 一维数组

数组是相同类型元素的有序集合。

声明与初始化:

int arr[5];                      // 声明,未初始化(值不确定)
int arr2[5] = {1, 2, 3, 4, 5};   // 完全初始化
int arr3[5] = {1, 2};            // 部分初始化,其余为0
int arr4[] = {1, 2, 3};          // 编译器自动推断大小为3

遍历:

#include <stdio.h>

int main(void)
{
    int arr[] = {10, 20, 30, 40, 50};
    int n = sizeof(arr) / sizeof(arr[0]);  // 计算数组元素个数

    for (int i = 0; i < n; i++) {
        printf("arr[%d] = %d\n", i, arr[i]);
    }
    return 0;
}

例 5.1 输入10个整数,找出最大值和最小值。

#include <stdio.h>

int main(void)
{
    int arr[10];
    printf("请输入10个整数:\n");
    for (int i = 0; i < 10; i++) {
        scanf("%d", &arr[i]);
    }

    int max = arr[0], min = arr[0];
    for (int i = 1; i < 10; i++) {
        if (arr[i] > max) max = arr[i];
        if (arr[i] < min) min = arr[i];
    }

    printf("最大值:%d\n", max);
    printf("最小值:%d\n", min);
    return 0;
}

例 5.2 冒泡排序。

#include <stdio.h>

void bubble_sort(int arr[], int n)
{
    for (int i = 0; i < n - 1; i++) {
        int swapped = 0;
        for (int j = 0; j < n - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
                swapped = 1;
            }
        }
        if (!swapped) break;  // 优化:若无交换则已排好序
    }
}

int main(void)
{
    int arr[] = {64, 34, 25, 12, 22, 11, 90};
    int n = sizeof(arr) / sizeof(arr[0]);

    bubble_sort(arr, n);

    printf("排序结果:");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    return 0;
}

5.2 二维数组

int matrix[3][4];  // 3行4列的二维数组
int matrix2[2][3] = {{1, 2, 3}, {4, 5, 6}};

例 5.3 矩阵转置。

#include <stdio.h>

int main(void)
{
    int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
    int b[3][2];

    // 转置
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            b[j][i] = a[i][j];
        }
    }

    printf("转置矩阵:\n");
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 2; j++) {
            printf("%d ", b[i][j]);
        }
        printf("\n");
    }
    return 0;
}

5.3 字符数组与字符串

在C语言中,字符串以字符数组形式存储,末尾有一个\0(空字符)作为结束标志。

char str1[] = "Hello";           // 自动添加\0,大小为6
char str2[] = {'H', 'e', 'l', 'l', 'o', '\0'};  // 等价写法
char str3[10] = "Hello";         // 前5个字符+1个\0,其余为\0

字符串处理函数(需#include <string.h>):

函数 功能 示例
strlen(s) 返回字符串长度(不含\0) strlen("Hello")5
strcpy(dest, src) 复制字符串 strcpy(dest, "Hello")
strcat(dest, src) 拼接字符串 strcat(dest, " World")
strcmp(s1, s2) 比较字符串 strcmp("abc", "abd") → 负数
strncpy(dest, src, n) 复制前n个字符 安全版本

例 5.4 编写函数统计字符串中大写字母、小写字母、数字和其他字符的个数。

#include <stdio.h>
#include <string.h>

void count_chars(const char *str, int *upper, int *lower, int *digit, int *other)
{
    *upper = *lower = *digit = *other = 0;
    for (int i = 0; str[i] != '\0'; i++) {
        if (str[i] >= 'A' && str[i] <= 'Z')
            (*upper)++;
        else if (str[i] >= 'a' && str[i] <= 'z')
            (*lower)++;
        else if (str[i] >= '0' && str[i] <= '9')
            (*digit)++;
        else
            (*other)++;
    }
}

int main(void)
{
    char str[100];
    int u, l, d, o;
    printf("请输入字符串:");
    fgets(str, sizeof(str), stdin);

    count_chars(str, &u, &l, &d, &o);
    printf("大写:%d, 小写:%d, 数字:%d, 其他:%d\n", u, l, d, o);
    return 0;
}

练习题 5.1

题 1 编写函数,将一个整数数组中的元素逆序存放。

答案:

void reverse(int arr[], int n)
{
    for (int i = 0; i < n / 2; i++) {
        int temp = arr[i];
        arr[i] = arr[n - 1 - i];
        arr[n - 1 - i] = temp;
    }
}

题 2 编写函数,删除字符串中所有的空格。

答案:

void remove_spaces(char *str)
{
    int j = 0;
    for (int i = 0; str[i] != '\0'; i++) {
        if (str[i] != ' ') {
            str[j++] = str[i];
        }
    }
    str[j] = '\0';
}

题 3 编写函数,在有序数组中使用二分查找查找指定元素。

答案:

int binary_search(int arr[], int n, int target)
{
    int left = 0, right = n - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (arr[mid] == target) return mid;
        else if (arr[mid] < target) left = mid + 1;
        else right = mid - 1;
    }
    return -1;  // 未找到
}

第六章 指针

6.1 指针的概念

指针是C语言最重要也最难以掌握的概念之一。指针变量存储的是另一个变量的内存地址。

基本概念:

  • 地址:每个变量在内存中都有一个唯一的地址
  • 指针变量:存储内存地址的变量
  • 取地址运算符&:获取变量的地址
  • 解引用运算符*:通过地址访问对应的值
#include <stdio.h>

int main(void)
{
    int a = 42;
    int *p = &a;  // p存储a的地址

    printf("a的值:%d\n", a);
    printf("a的地址:%p\n", (void *)&a);
    printf("p的值(即a的地址):%p\n", (void *)p);
    printf("p指向的值(即*p):%d\n", *p);

    *p = 100;  // 通过指针修改a的值
    printf("修改后a的值:%d\n", a);
    return 0;
}

指针的大小: 在32位系统中,所有指针占4字节;在64位系统中,所有指针占8字节。与指针指向的数据类型无关。

6.2 指针与数组

数组名就是指向首元素的指针(常量指针)。

int arr[] = {10, 20, 30, 40, 50};
int *p = arr;  // 等价于 int *p = &arr[0];

指针运算:

  • p + i:指向arr[i]的地址
  • *(p + i):等价于arr[i]
  • p++:指针移动到下一个元素

例 6.1 用指针遍历数组。

#include <stdio.h>

int main(void)
{
    int arr[] = {1, 2, 3, 4, 5};
    int n = sizeof(arr) / sizeof(arr[0]);
    int *p = arr;

    for (int i = 0; i < n; i++) {
        printf("arr[%d] = %d, *(p+%d) = %d\n", i, arr[i], i, *(p + i));
    }
    return 0;
}

6.3 指针与函数

指针作为函数参数(实现"引用传递"):

例 6.2 用指针实现真正的交换函数。

#include <stdio.h>

void swap(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main(void)
{
    int x = 10, y = 20;
    printf("交换前:x = %d, y = %d\n", x, y);
    swap(&x, &y);
    printf("交换后:x = %d, y = %d\n", x, y);
    return 0;
}

输出:

交换前:x = 10, y = 20
交换后:x = 20, y = 10

函数指针:

#include <stdio.h>

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }

int main(void)
{
    int (*operation)(int, int);  // 函数指针声明
    operation = add;
    printf("add(3, 2) = %d\n", operation(3, 2));
    operation = sub;
    printf("sub(3, 2) = %d\n", operation(3, 2));
    return 0;
}

6.4 动态内存分配

C语言提供了在运行时分配和释放内存的函数(需#include <stdlib.h>)。

函数 功能
malloc(size) 分配size字节的内存,不初始化
calloc(n, size) 分配n*size字节的内存,初始化为0
realloc(ptr, size) 重新调整已分配内存的大小
free(ptr) 释放之前分配的内存

例 6.3 动态创建数组。

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int n;
    printf("请输入数组大小:");
    scanf("%d", &n);

    int *arr = (int *)malloc(n * sizeof(int));
    if (arr == NULL) {
        printf("内存分配失败!\n");
        return 1;
    }

    for (int i = 0; i < n; i++) {
        arr[i] = i * i;
    }

    for (int i = 0; i < n; i++) {
        printf("arr[%d] = %d\n", i, arr[i]);
    }

    free(arr);  // 释放内存
    arr = NULL; // 避免悬空指针
    return 0;
}

内存泄漏: 分配了内存但忘记释放,导致程序占用的内存不断增加。这是C程序中常见的bug。


练习题 6.1

题 1 编写函数,用指针实现字符串反转。

答案:

void str_reverse(char *str)
{
    char *left = str;
    char *right = str;
    while (*right != '\0') right++;  // 找到末尾
    right--;  // 指向最后一个字符

    while (left < right) {
        char temp = *left;
        *left = *right;
        *right = temp;
        left++;
        right--;
    }
}

题 2 编写函数,使用动态内存分配创建二维数组。

答案:

int **create_2d_array(int rows, int cols)
{
    int **arr = (int **)malloc(rows * sizeof(int *));
    for (int i = 0; i < rows; i++) {
        arr[i] = (int *)calloc(cols, sizeof(int));
    }
    return arr;
}

void free_2d_array(int **arr, int rows)
{
    for (int i = 0; i < rows; i++) {
        free(arr[i]);
    }
    free(arr);
}

题 3 malloccalloc有什么区别?

答案: malloc分配指定字节数的内存,不初始化(内容为随机值)。calloc分配指定数量×大小的内存,并将所有字节初始化为0。calloc的参数是两个(数量和单个大小),malloc的参数是一个(总字节数)。


第七章 结构体与联合体

7.1 结构体的定义与使用

结构体允许将不同类型的数据组合在一起,形成自定义的数据类型。

struct Student {
    char name[50];
    int age;
    double gpa;
};

声明和初始化:

struct Student s1 = {"张三", 20, 3.8};
struct Student s2;
strcpy(s2.name, "李四");
s2.age = 21;
s2.gpa = 3.5;

使用typedef简化:

typedef struct {
    char name[50];
    int age;
    double gpa;
} Student;

Student s1 = {"张三", 20, 3.8};  // 不需要写struct

7.2 结构体数组

例 7.1 定义学生结构体数组,按成绩排序。

#include <stdio.h>
#include <string.h>

typedef struct {
    char name[50];
    int age;
    double gpa;
} Student;

void sort_by_gpa(Student arr[], int n)
{
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - 1 - i; j++) {
            if (arr[j].gpa < arr[j + 1].gpa) {
                Student temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

int main(void)
{
    Student students[] = {
        {"张三", 20, 3.8},
        {"李四", 21, 3.5},
        {"王五", 19, 3.9},
        {"赵六", 22, 3.2}
    };
    int n = sizeof(students) / sizeof(students[0]);

    sort_by_gpa(students, n);

    printf("按GPA排序结果:\n");
    for (int i = 0; i < n; i++) {
        printf("%s (年龄%d): %.1f\n",
               students[i].name, students[i].age, students[i].gpa);
    }
    return 0;
}

7.3 结构体指针

Student s = {"张三", 20, 3.8};
Student *p = &s;

// 以下两种访问方式等价:
printf("%s\n", s.name);
printf("%s\n", p->name);    // -> 是结构体指针专用的成员访问运算符

7.4 联合体与枚举

联合体(union): 所有成员共享同一块内存空间,大小等于最大成员的大小。

union Data {
    int i;
    float f;
    char str[20];
};
// sizeof(union Data) = 20(最大成员的大小)

枚举(enum): 用于定义一组命名的整数常量。

enum Color { RED, GREEN, BLUE };  // RED=0, GREEN=1, BLUE=2
enum Direction { NORTH = 1, SOUTH = -1, EAST = 2, WEST = -2 };

例 7.2 使用枚举表示一周七天。

#include <stdio.h>

typedef enum {
    SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
    THURSDAY, FRIDAY, SATURDAY
} Weekday;

const char *day_name(Weekday day)
{
    const char *names[] = {
        "星期日", "星期一", "星期二", "星期三",
        "星期四", "星期五", "星期六"
    };
    return names[day];
}

int main(void)
{
    for (Weekday d = SUNDAY; d <= SATURDAY; d++) {
        printf("%d: %s\n", d, day_name(d));
    }
    return 0;
}

练习题 7.1

题 1 定义一个表示日期的结构体,编写函数判断该日期是该年的第几天。

答案:

typedef struct {
    int year, month, day;
} Date;

int day_of_year(Date d)
{
    int days_in_month[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    // 判断闰年
    if ((d.year % 4 == 0 && d.year % 100 != 0) || d.year % 400 == 0) {
        days_in_month[2] = 29;
    }
    int total = 0;
    for (int i = 1; i < d.month; i++) {
        total += days_in_month[i];
    }
    return total + d.day;
}

题 2 structunion的主要区别是什么?

答案: struct中每个成员有独立的内存空间,总大小至少为各成员大小之和(考虑对齐)。union中所有成员共享同一块内存,总大小等于最大成员的大小。同一时刻,union只能有效存储一个成员的值。

题 3 使用结构体指针和动态内存分配,创建一个可变长度的学生管理系统(增删查改)。

答案: 这是一个综合性较强的题目,核心思路是使用malloc/realloc动态管理结构体数组,用指针实现各操作函数。建议参考本章代码示例自行实现。


附录:常用标准库函数速查

stdio.h

函数 功能 原型
printf 格式化输出 int printf(const char *format, ...)
scanf 格式化输入 int scanf(const char *format, ...)
fgets 安全读取字符串 char *fgets(char *s, int n, FILE *stream)
fopen 打开文件 FILE *fopen(const char *path, const char *mode)
fclose 关闭文件 int fclose(FILE *stream)

stdlib.h

函数 功能
malloc / free 动态内存分配/释放
atoi / atof 字符串转整数/浮点数
rand / srand 随机数生成
abs 整数绝对值

string.h

函数 功能
strlen 字符串长度
strcpy / strncpy 字符串复制
strcat / strncat 字符串拼接
strcmp / strncmp 字符串比较
strstr 子串查找
memset 内存设置
memcpy 内存复制

math.h

函数 功能
sqrt 平方根
pow 幂运算
sin / cos / tan 三角函数
log / log10 对数
ceil / floor 向上/向下取整
fabs 浮点数绝对值

综合练习题

综合 1 编写程序,实现简单的计算器功能(支持加减乘除,使用函数指针)。

答案要点: 定义四个运算函数,使用函数指针数组存储,根据用户输入的运算符选择对应函数执行。

综合 2 编写程序,从文件中读取学生成绩,计算平均分、最高分、最低分,并将结果写入另一个文件。

答案要点: 使用fopen打开文件,fscanf读取数据,fprintf写入结果,最后fclose关闭文件。

综合 3 编写一个链表的基本实现,包括创建节点、插入、删除、查找、遍历和释放功能。

答案要点: 定义链表节点结构体(包含数据域和指针域),实现各操作函数,注意内存管理(mallocfree的配对使用)。


本章小结

知识点 核心内容
C语言基础 编译过程、程序结构、注释
数据类型 int、float、double、char、类型转换
运算符 算术、关系、逻辑、位运算、优先级
控制结构 if-else、switch、for、while、do-while
函数 定义调用、值传递、递归、作用域
数组 一维、二维、字符数组、字符串函数
指针 概念、数组与指针、函数参数、动态内存
结构体 定义使用、结构体数组、指针、联合体、枚举

本教程共覆盖C语言七大核心模块,建议在学习每个知识点后,动手编写代码验证,通过实践加深理解。编程能力的提升没有捷径,唯有不断练习。

文章声明

本文仅供学习和参考,不构成任何投资建议。如有侵权,请联系删除。

目录