内容简介
全面介绍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提供了printf、scanf等函数的声明。
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.0。c / d是整数除法,结果为3。(double)c / d将c强制转换为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 + 2 → 5 |
- |
减法 | 3 - 2 → 1 |
* |
乘法 | 3 * 2 → 6 |
/ |
除法 | 7 / 2 → 3(整数除法) |
% |
取模 | 7 % 2 → 1 |
++ |
自增 | a++(后置)、++a(前置) |
-- |
自减 | a--(后置)、--a(前置) |
前置与后置自增的区别:
int a = 5;
int b = a++; // b = 5, a = 6(先使用后加)
int c = ++a; // c = 7, a = 7(先加后使用)
关系运算符: >、<、>=、<=、==、!=
逻辑运算符: &&(与)、||(或)、!(非)
短路求值:
a && b:若a为假,则不计算ba || 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 = 5,5 > 4 为1(真),5 % 2 = 1,1 == 0 为0,!0 = 1,1 && 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;
}
注意: switch中break的作用是跳出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 malloc和calloc有什么区别?
答案: 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 struct和union的主要区别是什么?
答案: 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 编写一个链表的基本实现,包括创建节点、插入、删除、查找、遍历和释放功能。
答案要点: 定义链表节点结构体(包含数据域和指针域),实现各操作函数,注意内存管理(malloc和free的配对使用)。
本章小结
| 知识点 | 核心内容 |
|---|---|
| C语言基础 | 编译过程、程序结构、注释 |
| 数据类型 | int、float、double、char、类型转换 |
| 运算符 | 算术、关系、逻辑、位运算、优先级 |
| 控制结构 | if-else、switch、for、while、do-while |
| 函数 | 定义调用、值传递、递归、作用域 |
| 数组 | 一维、二维、字符数组、字符串函数 |
| 指针 | 概念、数组与指针、函数参数、动态内存 |
| 结构体 | 定义使用、结构体数组、指针、联合体、枚举 |
本教程共覆盖C语言七大核心模块,建议在学习每个知识点后,动手编写代码验证,通过实践加深理解。编程能力的提升没有捷径,唯有不断练习。
文章声明
本文仅供学习和参考,不构成任何投资建议。如有侵权,请联系删除。