目录
第1章 Linux操作系统基础 …… 1
1.1 GNU简介 …… 2
GNU是GNU's Not Unix的递归缩写,是一个由Richard Stallman发起的自由软件项目,旨在创建一个完全自由的类Unix操作系统。GNU项目的目标是开发一套完整的自由软件系统,包括编译器、编辑器、操作系统内核等。
1.1.1 GNU的历史
- 1983年,Richard Stallman发起GNU项目
- 1985年,成立自由软件基金会(FSF)
- 1989年,发布GNU通用公共许可证(GPL)
1.1.2 GNU的核心组件
- GCC:GNU编译器集合,支持多种编程语言
- GNU Emacs:功能强大的文本编辑器
- GNU Bash:命令行解释器
- GNU Coreutils:核心工具集,包括ls、cp、mv等命令
1.2 Linux简介 …… 2
Linux是一种自由和开放源码的类Unix操作系统,由Linus Torvalds于1991年创建。Linux内核是Linux操作系统的核心,负责管理硬件资源和提供系统服务。
1.2.1 Linux的历史
- 1991年,Linus Torvalds发布Linux 0.01版本
- 1993年,Linux 1.0版本发布
- 1994年,Linux内核采用GPL许可证
- 2001年,Linux 2.4内核发布
- 2003年,Linux 2.6内核发布
- 2011年,Linux 3.0内核发布
- 2015年,Linux 4.0内核发布
- 2020年,Linux 5.0内核发布
1.2.2 Linux的特点
- 自由开源:遵循GPL许可证,允许自由修改和分发
- 多用户:支持多个用户同时使用系统
- 多任务:支持多个程序同时运行
- 稳定性:系统稳定可靠,适合长时间运行
- 安全性:内置多种安全机制
- 可移植性:支持多种硬件平台
1.2.3 Linux的发行版
- Ubuntu:基于Debian,用户友好
- CentOS:基于Red Hat,适合服务器
- Fedora:由Red Hat赞助,更新频繁
- Debian:以稳定性著称
- Arch Linux:滚动更新,适合高级用户
- openSUSE:由Novell赞助,企业级支持
1.3 Shell命令概述 …… 4
Shell是用户与Linux内核之间的接口,是一种命令解释器,负责执行用户输入的命令。在Linux系统中,常用的Shell包括Bash、Zsh、Fish等。
1.3.1 Shell的基本概念
- 命令:用户输入的指令,如ls、cd等
- 选项:修改命令行为的参数,通常以-开头,如-l、-a等
- 参数:命令的操作对象,如文件名、目录名等
- 管道:将一个命令的输出作为另一个命令的输入,使用|符号
- 重定向:改变命令的输入/输出方向,使用<、>、>>等符号
1.3.2 Shell的基本操作
- 命令补全:按下Tab键自动补全命令或文件名
- 命令历史:使用上下箭头键查看历史命令
- 命令别名:使用alias命令创建命令别名
- 后台执行:在命令末尾添加&符号,使命令在后台执行
1.3.3 环境变量
- 查看环境变量:
echo $变量名 - 设置环境变量:
export 变量名=值 - 常用环境变量:
PATH:命令搜索路径HOME:用户主目录SHELL:当前使用的ShellUSER:当前用户名PWD:当前工作目录
1.4 文件与目录操作 …… 7
文件和目录是Linux系统中最基本的存储单位,掌握文件和目录操作命令是使用Linux的基础。
1.4.1 目录操作命令
-
pwd(Print Working Directory):显示当前工作目录
pwd -
cd(Change Directory):切换目录
cd /path/to/directory # 切换到指定目录 cd .. # 切换到上级目录 cd ~ # 切换到用户主目录 cd - # 切换到上一个工作目录 -
mkdir:创建目录
mkdir directory # 创建单个目录 mkdir -p dir1/dir2 # 创建嵌套目录 -
rmdir:删除空目录
rmdir directory # 删除空目录
1.4.2 文件操作命令
-
ls(list):列出文件和目录
ls # 列出当前目录中的文件和目录 ls -l # 详细列出,包括权限、大小、修改时间等 ls -a # 显示所有文件,包括隐藏文件 ls -la # 详细显示所有文件 -
cp:复制文件或目录
cp source destination # 复制文件 cp -r source_dir destination_dir # 复制目录 -
mv:移动或重命名文件或目录
mv old_name new_name # 重命名文件或目录 mv file directory # 移动文件到目录 -
rm:删除文件或目录
rm file # 删除文件 rm -r directory # 删除目录及其内容 rm -f file # 强制删除文件 rm -rf directory # 强制删除目录及其内容 -
touch:创建空文件或更新文件时间戳
touch file # 创建空文件 -
cat(catenate):查看文件内容
cat file # 查看文件内容 cat file1 file2 > file3 # 合并文件内容 -
more:分页查看文件内容
more file # 分页查看文件内容 -
less:交互式分页查看文件内容
less file # 交互式分页查看文件内容 -
head:查看文件开头部分
head file # 查看文件前10行 head -n 5 file # 查看文件前5行 -
tail:查看文件结尾部分
tail file # 查看文件后10行 tail -n 5 file # 查看文件后5行 tail -f file # 实时查看文件内容
1.4.3 文件权限
-
chmod:修改文件权限
chmod 755 file # 设置文件权限为rwxr-xr-x chmod u+x file # 给文件所有者添加执行权限 chmod g-w file # 移除文件所属组的写权限 -
chown:修改文件所有者
chown user file # 修改文件所有者为user chown user:group file # 修改文件所有者和所属组 -
chgrp:修改文件所属组
chgrp group file # 修改文件所属组为group
1.5 系统运行常用命令 …… 19
系统运行命令用于管理Linux系统的运行状态,包括进程管理、系统信息查看等。
1.5.1 进程管理命令
-
ps(Process Status):查看进程状态
ps # 查看当前终端的进程 ps aux # 查看所有进程 ps -ef # 查看所有进程,显示父进程ID -
top(Table Of Processes):实时查看进程状态
top # 实时查看进程状态 -
kill:终止进程
kill PID # 终止指定PID的进程 kill -9 PID # 强制终止指定PID的进程 -
pkill:根据进程名终止进程
pkill process_name # 终止指定名称的进程 -
**pgrep(process + grep
• grep 全称:Global Regular Expression Print)**:根据进程名查找进程
bash pgrep process_name # 查找指定名称的进程
1.5.2 系统信息命令
-
uname:查看系统信息
uname -a # 查看所有系统信息 uname -r # 查看内核版本 -
hostname:查看或设置主机名
hostname # 查看主机名 hostname new_name # 设置主机名 -
uptime():查看系统运行时间
uptime # 查看系统运行时间和负载 -
free:查看内存使用情况
free # 查看内存使用情况 free -h # 以人类可读的格式查看内存使用情况 -
df(Disk Free):查看磁盘使用情况
df # 查看磁盘使用情况 df -h # 以人类可读的格式查看磁盘使用情况 -
du(Disk Usage):查看目录或文件大小
du # 查看当前目录下各文件和目录的大小 du -h # 以人类可读的格式查看 du -s # 查看总大小
1.5.3 系统服务管理
-
systemctl(system + control):管理系统服务(systemd系统)
systemctl status service_name # 查看服务状态 systemctl start service_name # 启动服务 systemctl stop service_name # 停止服务 systemctl restart service_name # 重启服务 systemctl enable service_name # 设置服务开机自启 systemctl disable service_name # 禁止服务开机自启 -
service:管理系统服务(SysV系统)
service service_name status # 查看服务状态 service service_name start # 启动服务 service service_name stop # 停止服务 service service_name restart # 重启服务
1.6 查找操作命令 …… 22
查找命令用于在系统中查找文件、目录或内容。
1.6.1 文件查找命令
-
find:根据条件查找文件
find /path -name "*.txt" # 查找指定路径下所有.txt文件 find /path -type f # 查找指定路径下所有普通文件 find /path -type d # 查找指定路径下所有目录 find /path -size +1M # 查找指定路径下大小大于1MB的文件 find /path -mtime -7 # 查找指定路径下7天内修改的文件 -
locate:快速查找文件(基于数据库)
locate file_name # 查找文件 updatedb # 更新数据库
1.6.2 内容查找命令
-
grep:在文件中查找指定内容
grep "pattern" file # 在文件中查找指定模式 grep -r "pattern" /path # 递归查找指定路径下的文件 grep -i "pattern" file # 忽略大小写查找 grep -n "pattern" file # 显示行号 -
egrep(Extended Global Regular Expression Print):使用扩展正则表达式查找
egrep "pattern1|pattern2" file # 查找多个模式 -
fgrep(Fixed Global Regular Expression Print):固定字符串查找,不解析正则表达式
fgrep "pattern" file # 固定字符串查找
1.7 其他常用命令 …… 23
1.7.1 网络命令
-
ifconfig(Interface Configuration):查看网络接口信息
ifconfig # 查看网络接口信息 -
ip:查看和配置网络接口
ip addr # 查看网络接口地址 ip route # 查看路由表 -
ping:测试网络连接
ping hostname # 测试与主机的连接 -
netstat(Network Statistics):查看网络状态
netstat -tuln # 查看所有监听端口 -
ss(Socket Statistics):查看网络状态(替代netstat)
ss -tuln # 查看所有监听端口 -
curl(Client URL):发送HTTP请求
curl http://example.com # 发送GET请求 curl -X POST -d "data" http://example.com # 发送POST请求 -
wget(World Wide Web Get):下载文件
wget http://example.com/file # 下载文件
1.7.2 压缩和解压缩命令
-
tar:打包和压缩文件
tar -cvf archive.tar files # 打包文件 tar -xvf archive.tar # 解包文件 tar -czvf archive.tar.gz files # 打包并压缩为gzip格式 tar -xzvf archive.tar.gz # 解压gzip格式 tar -cjvf archive.tar.bz2 files # 打包并压缩为bzip2格式 tar -xjvf archive.tar.bz2 # 解压bzip2格式 -
gzip:压缩文件
gzip file # 压缩文件 gunzip file.gz # 解压文件 -
zip:压缩文件
zip archive.zip files # 压缩文件 unzip archive.zip # 解压文件
1.7.3 文本处理命令
-
sed:流编辑器
sed 's/old/new/g' file # 替换文件中的文本 sed -i 's/old/new/g' file # 直接修改文件 -
awk:文本处理工具
awk '{print $1}' file # 打印文件的第一列 awk '$3 > 100 {print $0}' file # 打印第三列大于100的行 -
cut:剪切文件内容
cut -d ':' -f 1 /etc/passwd # 以:为分隔符,提取第一列 -
sort:排序文件内容
sort file # 排序文件 sort -n file # 按数字排序 sort -r file # 反向排序 -
uniq:去重
uniq file # 去除连续重复行 sort file | uniq # 去除所有重复行 -
wc:统计文件内容
wc -l file # 统计行数 wc -w file # 统计单词数 wc -c file # 统计字节数
1.8 Linux应用软件包管理 …… 37
Linux系统使用软件包管理器来管理软件的安装、更新和卸载。不同的Linux发行版使用不同的软件包管理器。
1.8.1 Debian/Ubuntu系列(apt)
-
apt-get:管理软件包
sudo apt-get update # 更新软件包列表 sudo apt-get upgrade # 升级已安装的软件包 sudo apt-get install package_name # 安装软件包 sudo apt-get remove package_name # 卸载软件包 sudo apt-get purge package_name # 卸载软件包并删除配置文件 sudo apt-get autoremove # 自动删除不再需要的依赖包 -
apt:apt-get的改进版本
sudo apt update # 更新软件包列表 sudo apt upgrade # 升级已安装的软件包 sudo apt install package_name # 安装软件包 sudo apt remove package_name # 卸载软件包 sudo apt purge package_name # 卸载软件包并删除配置文件 sudo apt autoremove # 自动删除不再需要的依赖包
1.8.2 Red Hat/CentOS系列(yum/dnf)
-
yum:管理软件包(CentOS 7及以下)
sudo yum update # 更新软件包 sudo yum install package_name # 安装软件包 sudo yum remove package_name # 卸载软件包 sudo yum search package_name # 搜索软件包 -
dnf:管理软件包(CentOS 8及以上)
sudo dnf update # 更新软件包 sudo dnf install package_name # 安装软件包 sudo dnf remove package_name # 卸载软件包 sudo dnf search package_name # 搜索软件包
1.8.3 Arch Linux系列(pacman)
- pacman:管理软件包
sudo pacman -Sy # 更新软件包数据库 sudo pacman -Syu # 更新系统 sudo pacman -S package_name # 安装软件包 sudo pacman -R package_name # 卸载软件包 sudo pacman -Rs package_name # 卸载软件包及其依赖
1.8.4 源码安装
有些软件可能没有提供预编译的软件包,需要从源码编译安装。
# 1. 下载源码包
wget http://example.com/software.tar.gz
# 2. 解压源码包
tar -xzvf software.tar.gz
# 3. 进入源码目录
cd software
# 4. 配置
./configure
# 5. 编译
make
# 6. 安装
sudo make install
1.9 项目实训:Linux基本命令 …… 39
1.9.1 实训目标
- 掌握Linux基本命令的使用
- 熟悉文件和目录操作
- 了解系统运行管理
- 学会使用软件包管理器
1.9.2 实训内容
-
文件和目录操作
- 创建目录结构:
mkdir -p project/src project/include project/lib - 创建文件:
touch project/src/main.c project/include/header.h - 复制文件:
cp project/src/main.c project/src/main_backup.c - 移动文件:
mv project/src/main_backup.c project/ - 删除文件:
rm project/main_backup.c
- 创建目录结构:
-
文件内容操作
- 查看文件内容:
cat project/src/main.c - 编辑文件:使用vim或nano编辑文件
- 搜索内容:
grep "main" project/src/main.c
- 查看文件内容:
-
系统信息查看
- 查看系统版本:
uname -a - 查看内存使用情况:
free -h - 查看磁盘使用情况:
df -h - 查看进程状态:
ps aux | head -10
- 查看系统版本:
-
软件包管理
- 更新软件包列表:
sudo apt update(Ubuntu/Debian)或sudo yum update(CentOS) - 安装软件包:
sudo apt install tree(Ubuntu/Debian)或sudo yum install tree(CentOS) - 查看已安装的软件包:
dpkg -l(Ubuntu/Debian)或rpm -qa(CentOS)
- 更新软件包列表:
1.9.3 实训要求
- 完成所有实训内容
- 记录每个命令的执行结果
- 撰写实训报告,总结Linux基本命令的使用方法
1.10 本章小结 …… 41
本章介绍了Linux操作系统的基础知识,包括:
- GNU简介:GNU项目的历史和核心组件
- Linux简介:Linux的历史、特点和发行版
- Shell命令概述:Shell的基本概念和操作
- 文件与目录操作:目录操作、文件操作和文件权限
- 系统运行常用命令:进程管理、系统信息和系统服务管理
- 查找操作命令:文件查找和内容查找
- 其他常用命令:网络命令、压缩和解压缩命令、文本处理命令
- Linux应用软件包管理:不同发行版的软件包管理器
- 项目实训:Linux基本命令的使用
通过本章的学习,读者应该能够掌握Linux的基本操作,为后续的编程学习打下基础。
第2章 常用的C语言库函数 …… 44
2.1 字符和字符串操作函数 …… 45
2.1.1 字符操作函数(<ctype.h>)
这类函数主要用于判断字符类型(如字母、数字、空格)或转换字符大小写,参数通常是int类型(实际传入char,会被提升为int)。
| 函数原型 | 功能说明 | 示例 |
|---|---|---|
int isalpha(int c) | 判断字符是否为字母(a-z/A-Z),是则返回非 0 | isalpha('A') → 非0 |
int isdigit(int c) | 判断字符是否为数字(0-9),是则返回非 0 | isdigit('5') → 非0 |
int isalnum(int c) | 判断字符是否为字母或数字,是则返回非 0 | isalnum('8') → 非0 |
int isspace(int c) | 判断字符是否为空白符(空格、换行、制表符) | isspace('\n') → 非0 |
int isupper(int c) | 判断字符是否为大写字母,是则返回非 0 | isupper('B') → 非0 |
int islower(int c) | 判断字符是否为小写字母,是则返回非 0 | islower('b') → 非0 |
int toupper(int c) | 将小写字母转为大写,非小写字母返回原值 | toupper('a') → 'A' |
int tolower(int c) | 将大写字母转为小写,非大写字母返回原值 | tolower('A') → 'a' |
示例代码:
#include <stdio.h>
#include <ctype.h>
int main() {
char ch = '7';
if (isdigit(ch)) {
printf("%c 是数字\n", ch); // 输出:7 是数字
}
ch = 'm';
printf("转换为大写:%c\n", toupper(ch)); // 输出:转换为大写:M
return 0;
}
2.1.2 字符串操作函数(<string.h>)
这类函数是字符串处理的核心,涵盖长度计算、拷贝、拼接、比较、查找等功能,使用时务必注意字符串以 \0 结尾,避免越界。
1. 基础操作(长度、拷贝、拼接)
| 函数原型 | 功能说明 |
|---|---|
size_t strlen(const char *s) | 计算字符串长度(不包含\0) |
char *strcpy(char *dest, const char *src) | 将src字符串拷贝到dest(包含\0),注意 dest 空间足够 |
char *strncpy(char *dest, const char *src, size_t n) | 拷贝src的前n个字符到dest,更安全(避免越界) |
char *strcat(char *dest, const char *src) | 将src拼接到dest末尾(覆盖dest的\0),注意 dest 空间足够 |
char *strncat(char *dest, const char *src, size_t n) | 拼接src的前n个字符到dest末尾,更安全 |
示例代码:
#include <stdio.h>
#include <string.h>
int main() {
char str1[20] = "Hello";
char str2[] = " World";
// 字符串长度
printf("str1长度:%zu\n", strlen(str1)); // 输出:5(不含\0)
// 字符串拼接
strcat(str1, str2);
printf("拼接后:%s\n", str1); // 输出:Hello World
// 字符串拷贝(注意str3空间足够)
char str3[20];
strcpy(str3, str1);
printf("拷贝后:%s\n", str3); // 输出:Hello World
return 0;
}
2. 字符串比较
| 函数原型 | 功能说明 |
|---|---|
int strcmp(const char *s1, const char *s2) | 比较s1和s2的 ASCII 值:- 相等返回 0- s1<s2 返回负数- s1>s2 返回正数 |
int strncmp(const char *s1, const char *s2, size_t n) | 仅比较前n个字符,规则同strcmp |
示例代码:
#include <stdio.h>
#include <string.h>
int main() {
char *s1 = "apple";
char *s2 = "banana";
char *s3 = "apple";
int res1 = strcmp(s1, s2); // s1 < s2,返回负数
int res2 = strcmp(s1, s3); // 相等,返回0
printf("s1 vs s2:%d\n", res1); // 输出:-1(具体值依编译器)
printf("s1 vs s3:%d\n", res2); // 输出:0
return 0;
}
3. 字符串查找
| 函数原型 | 功能说明 |
|---|---|
char *strchr(const char *s, int c) | 在s中查找字符c第一次出现的位置,返回指向该位置的指针,找不到返回NULL |
char *strrchr(const char *s, int c) | 在s中查找字符c最后一次出现的位置,返回指针,找不到返回NULL |
char *strstr(const char *haystack, const char *needle) | 在haystack中查找子串needle第一次出现的位置,找不到返回NULL |
示例代码:
#include <stdio.h>
#include <string.h>
int main() {
char *str = "hello world";
// 查找字符 'o' 第一次出现的位置
char *pos1 = strchr(str, 'o');
if (pos1) {
printf("'o'第一次出现的位置:%ld\n", pos1 - str); // 输出:4
}
// 查找子串 "world"
char *pos2 = strstr(str, "world");
if (pos2) {
printf("子串位置:%ld,子串内容:%s\n", pos2 - str, pos2);
// 输出:子串位置:6,子串内容:world
}
return 0;
}
2.1.3 注意事项
- 使用
<string.h>的函数时,目标数组必须有足够的空间,否则会导致内存越界(缓冲区溢出),推荐使用strncpy/strncat替代strcpy/strcat。 - 字符串操作函数的参数如果是字符串常量(如
"abc"),不能修改(如strcpy("abc", "def")会崩溃)。 strlen返回size_t类型(无符号整数),避免用它做减法(如strlen(s1) - strlen(s2)可能得到非预期值)。
总结
- 字符操作函数(
<ctype.h>):核心是判断字符类型(isalpha/isdigit)和转换大小写(toupper/tolower)。 - 字符串操作函数(
<string.h>):核心包括长度计算(strlen)、拷贝 / 拼接(strcpy/strcat,优先用strncpy/strncat)、比较(strcmp)、查找(strchr/strstr)。 - 使用字符串函数时,务必保证目标缓冲区空间足够,避免内存越界问题。
2.2 内存管理函数 …… 55
2.2.1 内存分配函数
stdlib.h头文件中的函数:malloc(size_t size):分配指定大小的内存calloc(size_t nmemb, size_t size):分配指定数量和大小的内存,并初始化为0realloc(void *ptr, size_t size):重新分配内存free(void *ptr):释放内存
2.2.2 内存分配注意事项
- 内存分配失败时,函数返回NULL
- 分配的内存使用完毕后必须释放,否则会导致内存泄漏
- 不要释放已经释放的内存,否则会导致错误
- 不要在释放内存后继续使用该内存,否则会导致悬空指针
2.2.3 内存分配示例
// 分配一个整型数组
int *arr = (int *)malloc(10 * sizeof(int));
if (arr == NULL) {
printf("内存分配失败\n");
return 1;
}
// 使用数组
for (int i = 0; i < 10; i++) {
arr[i] = i;
}
// 重新分配内存
arr = (int *)realloc(arr, 20 * sizeof(int));
if (arr == NULL) {
printf("内存分配失败\n");
return 1;
}
// 释放内存
free(arr);
arr = NULL; // 避免悬空指针
2.3 日期与时间函数 …… 63
2.3.1 时间类型
time_t:时间戳类型,通常是一个整数,表示从1970年1月1日00:00:00 UTC到当前时间的秒数struct tm:分解时间结构,包含年、月、日、时、分、秒等信息
2.3.2 时间函数
time.h头文件中的函数:time(time_t *t):获取当前时间戳localtime(const time_t *timep):将时间戳转换为本地时间结构gmtime(const time_t *timep):将时间戳转换为UTC时间结构mktime(struct tm *tm):将时间结构转换为时间戳asctime(const struct tm *tm):将时间结构转换为字符串ctime(const time_t *timep):将时间戳转换为字符串difftime(time_t time1, time_t time0):计算两个时间戳之间的差值strftime(char *s, size_t max, const char *format, const struct tm *tm):格式化时间为字符串
2.3.3 时间函数示例
// 获取当前时间戳
time_t now = time(NULL);
printf("当前时间戳:%ld\n", now);
// 转换为本地时间结构
struct tm *local_time = localtime(&now);
printf("本地时间:%s", asctime(local_time));
// 格式化时间
char buffer[100];
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", local_time);
printf("格式化时间:%s\n", buffer);
2.4 随机函数 …… 65
2.4.1 随机数生成函数
stdlib.h头文件中的函数:rand(void):生成0到RAND_MAX之间的随机整数srand(unsigned int seed):设置随机数种子
2.4.2 随机数生成注意事项
- 如果不设置种子,rand()会使用默认种子,每次运行程序生成的随机数序列相同
- 通常使用时间作为种子,确保每次运行生成不同的随机数序列
2.4.3 随机函数示例
// 设置种子
srand(time(NULL));
// 生成0-99之间的随机数
int random_num = rand() % 100;
printf("随机数:%d\n", random_num);
// 生成指定范围的随机数
int min = 10, max = 20;
int range_random = min + rand() % (max - min + 1);
printf("10-20之间的随机数:%d\n", range_random);
2.5 项目实训:C语言常用库函数调用 …… 68
2.5.1 实训目标
- 掌握C语言常用库函数的使用
- 熟悉字符和字符串操作
- 了解内存管理函数的使用
- 掌握日期与时间函数的使用
- 熟悉随机函数的使用
2.5.2 实训内容
-
字符处理
- 编写程序,输入一个字符,判断其类型(字母、数字、空白字符等)
- 编写程序,将字符串转换为大写或小写
-
字符串操作
- 编写程序,计算字符串长度
- 编写程序,复制和拼接字符串
- 编写程序,比较两个字符串
- 编写程序,查找子字符串
-
内存管理
- 编写程序,使用malloc分配内存,存储一个整型数组
- 编写程序,使用realloc调整内存大小
- 编写程序,释放分配的内存
-
日期与时间
- 编写程序,获取当前时间并显示
- 编写程序,格式化时间为指定格式
- 编写程序,计算两个时间之间的差值
-
随机数生成
- 编写程序,生成指定范围的随机数
- 编写程序,模拟掷骰子
2.5.3 实训要求
- 完成所有实训内容
- 编写完整的C程序
- 测试程序的正确性
- 撰写实训报告
2.6 本章小结 …… 72
本章介绍了C语言常用的库函数,包括:
- 字符和字符串操作函数:字符处理、字符串操作、字符串转换
- 内存管理函数:内存分配、释放和注意事项
- 日期与时间函数:时间类型、时间函数和示例
- 随机函数:随机数生成和种子设置
- 项目实训:C语言常用库函数调用
通过本章的学习,读者应该能够熟练使用C语言的常用库函数,为后续的编程学习打下基础。
第3章 编程环境 …… 74
3.1 概述 …… 75
编程环境是指用于编写、编译、调试和运行程序的工具集合。在Linux环境下,常用的编程工具包括:
- 文本编辑器:VIM、Emacs、VS Code等
- 编译器:GCC、Clang等
- 调试器:GDB等
- 工程管理工具:make等
选择合适的编程环境可以提高开发效率,本章将介绍Linux下常用的编程工具。
3.2 VIM编辑器 …… 75
3.2.1 VIM简介
VIM是Vi Improved的缩写,是一款功能强大的文本编辑器,广泛应用于Linux和Unix系统中。VIM具有以下特点:
- 模式化编辑
- 强大的命令集
- 可扩展性
- 跨平台支持
3.2.2 VIM的模式
- 普通模式:默认模式,用于导航和执行命令
- 插入模式:用于输入文本
- 可视模式:用于选择文本
- 命令行模式:用于执行命令
3.2.3 VIM的基本操作
- 进入VIM:
vim filename - 进入插入模式:
i(在光标前插入)、a(在光标后插入)、o(在光标下一行插入) - 退出插入模式:
Esc - 保存文件:
:w - 退出VIM:
:q - 保存并退出:
:wq或ZZ - 强制退出:
:q!
3.2.4 VIM的常用命令
-
导航命令:
h:向左移动j:向下移动k:向上移动l:向右移动w:移动到下一个单词开头b:移动到上一个单词开头0:移动到行首$:移动到行尾gg:移动到文件开头G:移动到文件结尾
-
删除命令:
x:删除光标所在字符dd:删除当前行dw:删除当前单词d$:删除从光标到行尾的内容
-
复制和粘贴命令:
yy:复制当前行yw:复制当前单词p:粘贴到光标后P:粘贴到光标前
-
搜索和替换命令:
/pattern:向下搜索pattern?pattern:向上搜索patternn:继续搜索下一个N:继续搜索上一个:%s/old/new/g:全局替换
3.2.5 VIM的配置
VIM的配置文件是~/.vimrc,可以在其中设置各种选项,例如:
" 设置行号
set number
" 设置自动缩进
set autoindent
" 设置Tab键宽度
set tabstop=4
set shiftwidth=4
" 设置搜索忽略大小写
set ignorecase
" 设置高亮搜索
set hlsearch
3.3 GCC编译器 …… 79
3.3.1 GCC简介
GCC(GNU Compiler Collection)是一套功能强大的编译器集合,支持多种编程语言,包括C、C++、Objective-C、Fortran、Ada等。GCC是Linux下最常用的编译器。
3.3.2 GCC的基本使用
- 编译单个文件:
gcc source.c -o output - 编译多个文件:
gcc file1.c file2.c -o output - 指定警告级别:
gcc -Wall source.c -o output - 指定优化级别:
gcc -O2 source.c -o output - 生成调试信息:
gcc -g source.c -o output
3.3.3 GCC的编译过程
- 预处理:处理头文件和宏定义,生成
.i文件gcc -E source.c -o source.i
- 编译:将预处理后的文件编译为汇编代码,生成
.s文件gcc -S source.i -o source.s
- 汇编:将汇编代码转换为目标代码,生成
.o文件gcc -c source.s -o source.o
- 链接:将目标代码与库文件链接,生成可执行文件
gcc source.o -o output
3.3.4 GCC的常用选项
-c:只编译不链接,生成目标文件-o:指定输出文件-Wall:开启所有警告-Werror:将警告视为错误-g:生成调试信息-O0、-O1、-O2、-O3:优化级别-I:指定头文件搜索路径-L:指定库文件搜索路径-l:指定要链接的库
3.4 GDB程序调试器 …… 84
3.4.1 GDB简介
GDB(GNU Debugger)是一款功能强大的程序调试器,可以用于调试C、C++等语言的程序。GDB可以帮助开发者定位程序中的错误。
3.4.2 GDB的基本使用
- 启动GDB:
gdb program - 运行程序:
run或r - 设置断点:
break filename:line或b filename:line - 查看断点:
info breakpoints - 删除断点:
delete breakpoint_number或d breakpoint_number - 继续执行:
continue或c - 单步执行:
step或s(进入函数)、next或n(不进入函数) - 查看变量:
print variable或p variable - 查看内存:
x/[format] address - 查看堆栈:
backtrace或bt - 退出GDB:
quit或q
3.4.3 GDB的高级功能
- 条件断点:
break filename:line if condition - 观察点:
watch variable - 命令断点:
break filename:line command - 线程调试:
info threads、thread thread_number
3.5 make工程管理器 …… 87
3.5.1 make简介
make是一款工程管理工具,用于自动化编译过程。通过编写makefile文件,可以指定文件之间的依赖关系和编译规则,使编译过程更加高效。
3.5.2 makefile的基本结构
makefile由一系列规则组成,每个规则的格式如下:
target: dependencies
commands
- target:目标文件
- dependencies:依赖文件
- commands:编译命令(必须以Tab开头)
3.5.3 makefile的变量
- 内置变量:
$@:目标文件$^:所有依赖文件$<:第一个依赖文件
- 自定义变量:
CC = gcc CFLAGS = -Wall -g
3.5.4 makefile的示例
CC = gcc
CFLAGS = -Wall -g
TARGET = program
SRCS = main.c utils.c
OBJS = $(SRCS:.c=.o)
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) $(OBJS) -o $(TARGET)
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(OBJS) $(TARGET)
3.5.5 make的常用命令
- 执行make:
make - 指定目标:
make target - 清理生成的文件:
make clean - 查看执行过程:
make -n - 强制重新编译:
make -B
3.6 项目实训:makefile的编写 …… 93
3.6.1 实训目标
- 掌握makefile的编写方法
- 熟悉make的基本使用
- 了解工程管理的基本概念
3.6.2 实训内容
-
创建项目结构
- 创建项目目录:
mkdir -p project/src project/include project/lib - 创建源文件:
project/src/main.c、project/src/utils.c - 创建头文件:
project/include/utils.h
- 创建项目目录:
-
编写源代码
-
project/include/utils.h:#ifndef UTILS_H #define UTILS_H int add(int a, int b); int subtract(int a, int b); #endif -
project/src/utils.c:#include "utils.h" int add(int a, int b) { return a + b; } int subtract(int a, int b) { return a - b; } -
project/src/main.c:#include <stdio.h> #include "utils.h" int main() { int a = 10, b = 5; printf("%d + %d = %d\n", a, b, add(a, b)); printf("%d - %d = %d\n", a, b, subtract(a, b)); return 0; }
-
-
编写makefile
-
创建
project/Makefile:CC = gcc CFLAGS = -Wall -g INCLUDE = -Iinclude TARGET = bin/program SRCS = src/main.c src/utils.c OBJS = $(SRCS:.c=.o) all: $(TARGET) $(TARGET): $(OBJS) mkdir -p bin $(CC) $(CFLAGS) $(OBJS) -o $(TARGET) %.o: %.c $(CC) $(CFLAGS) $(INCLUDE) -c $< -o $@ clean: rm -f $(OBJS) $(TARGET) rm -rf bin
-
-
编译和运行
- 进入项目目录:
cd project - 编译项目:
make - 运行程序:
./bin/program - 清理项目:
make clean
- 进入项目目录:
3.6.3 实训要求
- 完成所有实训内容
- 编写完整的makefile
- 测试程序的正确性
- 撰写实训报告
3.7 本章小结 …… 96
本章介绍了Linux下的编程环境,包括:
- 概述:编程环境的基本组成
- VIM编辑器:模式化编辑、基本操作、常用命令和配置
- GCC编译器:基本使用、编译过程和常用选项
- GDB程序调试器:基本使用和高级功能
- make工程管理器:makefile的编写、变量和示例
- 项目实训:makefile的编写
通过本章的学习,读者应该能够熟练使用Linux下的编程工具,为后续的编程学习打下基础。
第4章 文件操作 …… 98
4.1 文件系统 …… 99
4.1.1 文件系统的概念
文件系统是操作系统用于管理和存储文件的一种机制,它负责文件的组织、存储、检索和保护。在Linux系统中,文件系统采用树形结构,从根目录(/)开始,向下延伸出各种子目录和文件。
4.1.2 Linux文件系统类型
- ext2/ext3/ext4:Linux系统的传统文件系统
- XFS:高性能文件系统,适用于大型文件
- Btrfs:现代文件系统,支持快照、压缩等功能
- FAT32/NTFS:Windows文件系统,Linux也支持
- tmpfs:临时文件系统,存储在内存中
4.1.3 文件系统的基本概念
- 文件:存储数据的基本单位
- 目录:存储文件和子目录的容器
- inode:存储文件的元数据,如权限、大小、修改时间等
- block:存储文件数据的物理块
- 硬链接:指向同一个inode的多个文件名
- 符号链接:指向另一个文件的路径的特殊文件
4.1.4 文件系统的操作命令
df:查看文件系统使用情况du:查看目录或文件的大小mount:挂载文件系统umount:卸载文件系统fsck:检查和修复文件系统
4.2 文件描述符的I/O操作 …… 100
4.2.1 文件描述符
文件描述符是一个非负整数,用于标识一个打开的文件。在Linux系统中,每个进程都有一个文件描述符表,用于管理该进程打开的文件。
4.2.2 常用的文件描述符
- 0:标准输入(stdin)
- 1:标准输出(stdout)
- 2:标准错误(stderr)
4.2.3 文件操作函数
-
打开文件:
open(const char *pathname, int flags, mode_t mode)flags:打开方式,如O_RDONLY(只读)、O_WRONLY(只写)、O_RDWR(读写)、O_CREAT(创建)、O_TRUNC(截断)等mode:文件权限,如0644(所有者读写,其他用户只读)
-
关闭文件:
close(int fd) -
读取文件:
read(int fd, void *buf, size_t count)- 返回值:实际读取的字节数,0表示文件结束,-1表示出错
-
写入文件:
write(int fd, const void *buf, size_t count)- 返回值:实际写入的字节数,-1表示出错
-
定位文件:
lseek(int fd, off_t offset, int whence)whence:定位基准,如SEEK_SET(文件开头)、SEEK_CUR(当前位置)、SEEK_END(文件结尾)
-
文件状态:
fstat(int fd, struct stat *statbuf)- 获取文件的元数据
4.2.4 文件操作示例
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
int fd;
char buffer[1024];
ssize_t nread, nwrite;
// 打开文件
fd = open("test.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
perror("open");
exit(1);
}
// 写入文件
const char *text = "Hello, World!\n";
nwrite = write(fd, text, strlen(text));
if (nwrite == -1) {
perror("write");
close(fd);
exit(1);
}
printf("写入了 %zd 字节\n", nwrite);
// 定位到文件开头
lseek(fd, 0, SEEK_SET);
// 读取文件
nread = read(fd, buffer, sizeof(buffer));
if (nread == -1) {
perror("read");
close(fd);
exit(1);
}
printf("读取了 %zd 字节:%s", nread, buffer);
// 关闭文件
close(fd);
return 0;
}
4.2.5 文件权限
- 文件权限由9个字符表示,分为3组,每组3个字符:
- 第一组:所有者权限
- 第二组:所属组权限
- 第三组:其他用户权限
- 每个字符的含义:
- r:读权限
- w:写权限
- x:执行权限
- -:无权限
- 权限的数字表示:
- r = 4
- w = 2
- x = 1
- 例如:0644 表示 rw-r--r--
4.3 项目实训:日志管理功能 …… 109
4.3.1 实训目标
- 掌握文件描述符的I/O操作
- 熟悉文件的打开、读写和关闭操作
- 了解日志管理的基本概念
- 学会编写简单的日志管理功能
4.3.2 实训内容
-
日志管理功能设计
- 日志文件的创建和打开
- 日志的写入(包括时间戳、日志级别、日志内容)
- 日志文件的关闭
-
编写日志管理函数
log_open(const char *filename):打开日志文件log_write(int level, const char *format, ...):写入日志log_close():关闭日志文件
-
日志级别定义
- 0:DEBUG
- 1:INFO
- 2:WARN
- 3:ERROR
- 4:FATAL
-
编写测试程序
- 测试不同级别的日志写入
- 验证日志文件的内容
4.3.3 实训代码
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdarg.h>
#include <time.h>
#define DEBUG 0
#define INFO 1
#define WARN 2
#define ERROR 3
#define FATAL 4
static int log_fd = -1;
const char *level_str[] = {
"DEBUG",
"INFO",
"WARN",
"ERROR",
"FATAL"
};
int log_open(const char *filename) {
log_fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0644);
if (log_fd == -1) {
perror("open");
return -1;
}
return 0;
}
void log_write(int level, const char *format, ...) {
if (log_fd == -1) {
return;
}
// 获取当前时间
time_t now = time(NULL);
struct tm *tm_now = localtime(&now);
char time_buf[64];
strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm_now);
// 格式化日志头部
char log_buf[1024];
int n = snprintf(log_buf, sizeof(log_buf), "[%s] [%s] ", time_buf, level_str[level]);
// 格式化日志内容
va_list args;
va_start(args, format);
vsnprintf(log_buf + n, sizeof(log_buf) - n, format, args);
va_end(args);
// 写入日志
write(log_fd, log_buf, strlen(log_buf));
write(log_fd, "\n", 1);
}
void log_close() {
if (log_fd != -1) {
close(log_fd);
log_fd = -1;
}
}
int main() {
// 打开日志文件
if (log_open("app.log") == -1) {
return 1;
}
// 写入不同级别的日志
log_write(DEBUG, "这是一条调试日志");
log_write(INFO, "这是一条信息日志");
log_write(WARN, "这是一条警告日志");
log_write(ERROR, "这是一条错误日志");
log_write(FATAL, "这是一条致命错误日志");
// 关闭日志文件
log_close();
return 0;
}
4.3.4 实训要求
- 完成所有实训内容
- 编写完整的C程序
- 测试程序的正确性
- 查看日志文件的内容
- 撰写实训报告
第5章 标准I/O库 …… 111
5.1 标准流的I/O操作 …… 114
5.1.1 标准流的概念
标准I/O库是C语言提供的一组用于输入输出操作的函数,它是对底层I/O操作的封装,提供了更高级、更方便的接口。标准I/O库使用流(stream)作为操作的对象。
5.1.2 标准流
在C语言中,有三个标准流:
- stdin:标准输入流,通常对应键盘输入
- stdout:标准输出流,通常对应屏幕输出
- stderr:标准错误流,通常对应屏幕输出
这些标准流在程序启动时自动打开,不需要手动打开。
5.1.3 标准流的操作函数
- 输入函数:
scanf(),getchar(),gets()等 - 输出函数:
printf(),putchar(),puts()等
5.2 流的打开和关闭 …… 114
5.2.1 文件流
文件流是标准I/O库中用于操作文件的对象,用 FILE 类型表示。
5.2.2 打开文件
fopen(const char *pathname, const char *mode):打开文件并返回文件流指针mode:打开模式,如"r"(只读)、"w"(只写,创建或截断)、"a"(追加)、"r+"(读写)、"w+"(读写,创建或截断)、"a+"(读写,追加)等
5.2.3 关闭文件
fclose(FILE *stream):关闭文件流- 关闭文件时会刷新缓冲区
5.2.4 打开和关闭文件的示例
#include <stdio.h>
int main() {
FILE *fp;
// 打开文件
fp = fopen("test.txt", "w");
if (fp == NULL) {
perror("fopen");
return 1;
}
// 写入数据
fprintf(fp, "Hello, World!\n");
// 关闭文件
fclose(fp);
return 0;
}
5.3 缓冲区的操作 …… 115
5.3.1 缓冲区的概念
缓冲区是标准I/O库为了提高I/O效率而设置的一块内存区域,用于暂时存储数据。当缓冲区满或手动刷新时,数据才会被写入实际的文件或设备。
5.3.2 缓冲区的类型
- 全缓冲:当缓冲区满时才进行I/O操作,通常用于磁盘文件
- 行缓冲:当遇到换行符时进行I/O操作,通常用于终端
- 无缓冲:立即进行I/O操作,通常用于标准错误流
5.3.3 缓冲区的操作函数
fflush(FILE *stream):刷新缓冲区,将缓冲区中的数据写入实际的文件或设备setbuf(FILE *stream, char *buf):设置缓冲区setvbuf(FILE *stream, char *buf, int mode, size_t size):设置缓冲区的模式和大小mode:_IOFBF(全缓冲)、_IOLBF(行缓冲)、_IONBF(无缓冲)
5.3.4 缓冲区操作示例
#include <stdio.h>
int main() {
FILE *fp;
char buffer[1024];
// 打开文件
fp = fopen("test.txt", "w");
if (fp == NULL) {
perror("fopen");
return 1;
}
// 设置缓冲区
setbuf(fp, buffer);
// 写入数据
fprintf(fp, "Hello, ");
fprintf(fp, "World!\n");
// 刷新缓冲区
fflush(fp);
// 关闭文件
fclose(fp);
return 0;
}
5.4 直接输入/输出 …… 118
5.4.1 直接输入/输出函数
-
fread(void *ptr, size_t size, size_t nmemb, FILE *stream):从文件流中读取数据ptr:存储数据的指针size:每个数据项的大小nmemb:数据项的数量- 返回值:实际读取的数据项数量
-
fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream):向文件流中写入数据ptr:数据的指针size:每个数据项的大小nmemb:数据项的数量- 返回值:实际写入的数据项数量
5.4.2 直接输入/输出示例
#include <stdio.h>
struct Student {
char name[50];
int age;
float score;
};
int main() {
FILE *fp;
struct Student students[2] = {
{"Alice", 18, 95.5},
{"Bob", 19, 88.0}
};
struct Student read_students[2];
// 写入数据
fp = fopen("students.dat", "wb");
if (fp == NULL) {
perror("fopen");
return 1;
}
fwrite(students, sizeof(struct Student), 2, fp);
fclose(fp);
// 读取数据
fp = fopen("students.dat", "rb");
if (fp == NULL) {
perror("fopen");
return 1;
}
fread(read_students, sizeof(struct Student), 2, fp);
fclose(fp);
// 输出数据
for (int i = 0; i < 2; i++) {
printf("Name: %s, Age: %d, Score: %.1f\n",
read_students[i].name, read_students[i].age, read_students[i].score);
}
return 0;
}
5.5 格式化输入/输出 …… 119
5.5.1 格式化输出函数
printf(const char *format, ...):向标准输出流输出格式化数据fprintf(FILE *stream, const char *format, ...):向指定文件流输出格式化数据sprintf(char *str, const char *format, ...):向字符串输出格式化数据snprintf(char *str, size_t size, const char *format, ...):向字符串输出指定长度的格式化数据
5.5.2 格式化输入函数
scanf(const char *format, ...):从标准输入流读取格式化数据fscanf(FILE *stream, const char *format, ...):从指定文件流读取格式化数据sscanf(const char *str, const char *format, ...):从字符串读取格式化数据
5.5.3 格式化输入/输出示例
#include <stdio.h>
int main() {
FILE *fp;
int a, b;
float c;
// 写入格式化数据
fp = fopen("data.txt", "w");
if (fp == NULL) {
perror("fopen");
return 1;
}
fprintf(fp, "%d %d %.2f\n", 10, 20, 3.14);
fclose(fp);
// 读取格式化数据
fp = fopen("data.txt", "r");
if (fp == NULL) {
perror("fopen");
return 1;
}
fscanf(fp, "%d %d %f", &a, &b, &c);
fclose(fp);
// 输出数据
printf("a = %d, b = %d, c = %.2f\n", a, b, c);
return 0;
}
5.6 基于字符和行的输入/输出 …… 121
5.6.1 基于字符的输入/输出函数
-
fgetc(FILE *stream):从文件流中读取一个字符- 返回值:读取的字符,若到达文件结尾或出错则返回EOF
-
fputc(int c, FILE *stream):向文件流中写入一个字符- 返回值:写入的字符,若出错则返回EOF
-
getchar(void):从标准输入流中读取一个字符- 相当于
fgetc(stdin)
- 相当于
-
putchar(int c):向标准输出流中写入一个字符- 相当于
fputc(c, stdout)
- 相当于
5.6.2 基于行的输入/输出函数
-
fgets(char *s, int size, FILE *stream):从文件流中读取一行数据s:存储数据的缓冲区size:缓冲区的大小- 返回值:成功返回s,若到达文件结尾或出错则返回NULL
-
fputs(const char *s, FILE *stream):向文件流中写入一行数据- 返回值:成功返回非负值,若出错则返回EOF
-
gets(char *s):从标准输入流中读取一行数据(已废弃)- 相当于
fgets(s, INT_MAX, stdin)
- 相当于
-
puts(const char *s):向标准输出流中写入一行数据- 相当于
fputs(s, stdout)后跟putchar('\n')
- 相当于
5.6.3 基于字符和行的输入/输出示例
#include <stdio.h>
int main() {
FILE *fp;
char buffer[1024];
int ch;
// 写入字符
fp = fopen("test.txt", "w");
if (fp == NULL) {
perror("fopen");
return 1;
}
fputc('H', fp);
fputc('e', fp);
fputc('l', fp);
fputc('l', fp);
fputc('o', fp);
fputc('\n', fp);
fclose(fp);
// 读取字符
fp = fopen("test.txt", "r");
if (fp == NULL) {
perror("fopen");
return 1;
}
while ((ch = fgetc(fp)) != EOF) {
putchar(ch);
}
fclose(fp);
// 写入行
fp = fopen("test.txt", "a");
if (fp == NULL) {
perror("fopen");
return 1;
}
fputs("World!\n", fp);
fclose(fp);
// 读取行
fp = fopen("test.txt", "r");
if (fp == NULL) {
perror("fopen");
return 1;
}
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
fputs(buffer, stdout);
}
fclose(fp);
return 0;
}
5.7 项目实训:出错管理功能 …… 124
5.7.1 实训目标
- 掌握标准I/O库的使用
- 熟悉文件的打开、读写和关闭操作
- 了解出错管理的基本概念
- 学会编写简单的出错管理功能
5.7.2 实训内容
-
出错管理功能设计
- 错误信息的定义和存储
- 错误的记录和报告
- 错误处理策略
-
编写出错管理函数
error_init():初始化错误管理模块error_set(int code, const char *message):设置错误信息error_get(int *code, char *message, size_t size):获取错误信息error_print():打印错误信息
-
错误码定义
- 0:成功
- 1:文件打开失败
- 2:文件读取失败
- 3:文件写入失败
- 4:内存分配失败
-
编写测试程序
- 测试文件操作中的错误处理
- 验证错误信息的正确性
5.7.3 实训代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_ERROR_MSG 256
static int error_code = 0;
static char error_message[MAX_ERROR_MSG];
void error_init() {
error_code = 0;
error_message[0] = '\0';
}
void error_set(int code, const char *message) {
error_code = code;
strncpy(error_message, message, MAX_ERROR_MSG - 1);
error_message[MAX_ERROR_MSG - 1] = '\0';
}
void error_get(int *code, char *message, size_t size) {
if (code != NULL) {
*code = error_code;
}
if (message != NULL && size > 0) {
strncpy(message, error_message, size - 1);
message[size - 1] = '\0';
}
}
void error_print() {
if (error_code != 0) {
fprintf(stderr, "Error %d: %s\n", error_code, error_message);
}
}
FILE *safe_fopen(const char *pathname, const char *mode) {
FILE *fp = fopen(pathname, mode);
if (fp == NULL) {
char msg[MAX_ERROR_MSG];
snprintf(msg, sizeof(msg), "Failed to open file: %s", pathname);
error_set(1, msg);
}
return fp;
}
size_t safe_fread(void *ptr, size_t size, size_t nmemb, FILE *stream) {
size_t n = fread(ptr, size, nmemb, stream);
if (n != nmemb) {
error_set(2, "Failed to read file");
}
return n;
}
size_t safe_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) {
size_t n = fwrite(ptr, size, nmemb, stream);
if (n != nmemb) {
error_set(3, "Failed to write file");
}
return n;
}
void *safe_malloc(size_t size) {
void *ptr = malloc(size);
if (ptr == NULL) {
error_set(4, "Failed to allocate memory");
}
return ptr;
}
int main() {
FILE *fp;
char *buffer;
size_t n;
// 初始化错误管理
error_init();
// 测试文件打开失败
fp = safe_fopen("nonexistent.txt", "r");
if (fp == NULL) {
error_print();
error_init();
}
// 测试文件写入
fp = safe_fopen("test.txt", "w");
if (fp == NULL) {
error_print();
return 1;
}
const char *text = "Hello, World!\n";
safe_fwrite(text, 1, strlen(text), fp);
if (error_code != 0) {
error_print();
fclose(fp);
return 1;
}
fclose(fp);
// 测试文件读取
fp = safe_fopen("test.txt", "r");
if (fp == NULL) {
error_print();
return 1;
}
// 获取文件大小
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
fseek(fp, 0, SEEK_SET);
// 分配内存
buffer = safe_malloc(size + 1);
if (buffer == NULL) {
error_print();
fclose(fp);
return 1;
}
// 读取文件
n = safe_fread(buffer, 1, size, fp);
if (error_code != 0) {
error_print();
free(buffer);
fclose(fp);
return 1;
}
buffer[n] = '\0';
// 输出内容
printf("File content: %s", buffer);
// 清理资源
free(buffer);
fclose(fp);
return 0;
}
5.7.4 实训要求
- 完成所有实训内容
- 编写完整的C程序
- 测试程序的正确性
- 验证错误处理的有效性
- 撰写实训报告
5.8 本章小结 …… 125
本章介绍了C语言的标准I/O库,包括:
- 标准流的I/O操作:标准流的概念和操作函数
- 流的打开和关闭:文件流的打开和关闭操作
- 缓冲区的操作:缓冲区的概念、类型和操作函数
- 直接输入/输出:fread和fwrite函数的使用
- 格式化输入/输出:printf、scanf等函数的使用
- 基于字符和行的输入/输出:fgetc、fputc、fgets、fputs等函数的使用
- 项目实训:出错管理功能
通过本章的学习,读者应该能够熟练使用标准I/O库进行文件操作,为后续的编程学习打下基础。
第6章 进程控制 …… 125
6.1 进程概述 …… 125
6.1.1 进程的概念
进程是程序的一次执行过程,是操作系统进行资源分配和调度的基本单位。每个进程都有自己的内存空间、文件描述符、环境变量等资源。
6.1.2 进程的状态
- 运行状态(Running):进程正在CPU上执行
- 就绪状态(Ready):进程已经准备好执行,等待CPU分配
- 阻塞状态(Blocked):进程等待某个事件的发生,如I/O操作完成
- 终止状态(Terminated):进程已经结束执行
6.1.3 进程的标识符
- 进程ID(PID):每个进程都有一个唯一的ID
- 父进程ID(PPID):创建该进程的进程的ID
- 组ID(GID):进程所属的组的ID
6.1.4 进程的层次结构
在Linux系统中,进程形成一个树状结构,init进程(PID为1)是所有进程的祖先。
6.2 进程控制 …… 131
6.2.1 进程创建
-
fork():创建一个新进程- 返回值:在父进程中返回子进程的PID,在子进程中返回0,出错返回-1
- 子进程是父进程的一个副本,继承父进程的内存空间、文件描述符等
-
vfork():创建一个新进程,与fork()类似,但有一些差异- 子进程共享父进程的内存空间
- 父进程会被阻塞,直到子进程执行exec()或exit()
6.2.2 进程执行
exec系列函数:在当前进程中执行一个新程序execl(const char *path, const char *arg, ...)execle(const char *path, const char *arg, ..., char *const envp[])execlp(const char *file, const char *arg, ...)execv(const char *path, char *const argv[])execve(const char *path, char *const argv[], char *const envp[])execvp(const char *file, char *const argv[])
6.2.3 进程等待
-
wait(int *status):等待子进程结束- 返回值:结束的子进程的PID,出错返回-1
status:存储子进程的退出状态
-
waitpid(pid_t pid, int *status, int options):等待指定的子进程结束pid:要等待的子进程的PID,-1表示等待任意子进程options:选项,如WNOHANG(非阻塞)、WUNTRACED(跟踪停止的进程)
6.2.4 进程终止
-
exit(int status):终止进程status:退出状态,0表示正常终止,非0表示异常终止
-
_exit(int status):终止进程,不执行清理操作 -
abort():异常终止进程,生成SIGABRT信号
6.2.5 进程控制示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
pid_t pid;
int status;
// 创建子进程
pid = fork();
if (pid == -1) {
perror("fork");
exit(1);
}
if (pid == 0) {
// 子进程
printf("子进程 PID: %d\n", getpid());
printf("子进程 PPID: %d\n", getppid());
// 执行新程序
execl("/bin/ls", "ls", "-l", NULL);
// 如果exec失败
perror("execl");
exit(1);
} else {
// 父进程
printf("父进程 PID: %d\n", getpid());
printf("父进程 PPID: %d\n", getppid());
printf("子进程 PID: %d\n", pid);
// 等待子进程结束
wait(&status);
printf("子进程结束,退出状态: %d\n", WEXITSTATUS(status));
}
return 0;
}
6.2.6 进程的环境变量
getenv(const char *name):获取环境变量的值setenv(const char *name, const char *value, int overwrite):设置环境变量unsetenv(const char *name):删除环境变量environ:全局变量,指向环境变量数组
6.3 项目实训:进程的实现 …… 139
6.3.1 实训目标
- 掌握进程的创建和管理
- 熟悉fork、exec、wait等函数的使用
- 了解进程的生命周期
- 学会编写简单的进程管理程序
6.3.2 实训内容
-
创建子进程
- 使用fork()创建子进程
- 区分父进程和子进程
- 查看进程的PID和PPID
-
执行新程序
- 使用exec系列函数执行新程序
- 处理exec失败的情况
-
进程等待
- 使用wait()或waitpid()等待子进程结束
- 获取子进程的退出状态
-
进程管理
- 编写一个简单的进程管理程序,创建多个子进程并等待它们结束
6.3.3 实训代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
pid_t pid;
int status;
int i;
// 创建3个子进程
for (i = 0; i < 3; i++) {
pid = fork();
if (pid == -1) {
perror("fork");
exit(1);
}
if (pid == 0) {
// 子进程
printf("子进程 %d PID: %d\n", i+1, getpid());
// 模拟不同的执行时间
sleep(i+1);
// 退出状态为i+1
exit(i+1);
}
}
// 父进程等待所有子进程结束
for (i = 0; i < 3; i++) {
pid = wait(&status);
if (pid == -1) {
perror("wait");
exit(1);
}
printf("子进程 %d 结束,退出状态: %d\n", pid, WEXITSTATUS(status));
}
printf("所有子进程都已结束\n");
return 0;
}
6.3.4 实训要求
- 完成所有实训内容
- 编写完整的C程序
- 测试程序的正确性
- 观察进程的创建和结束过程
- 撰写实训报告
6.4 本章小结 …… 140
本章介绍了进程控制的相关知识,包括:
- 进程概述:进程的概念、状态、标识符和层次结构
- 进程控制:进程的创建、执行、等待和终止
- 项目实训:进程的实现
通过本章的学习,读者应该能够掌握进程的基本概念和控制方法,为后续的线程控制和进程间通信学习打下基础。
第7章 线程控制 …… 142
7.1 线程控制 …… 145
7.1.1 线程的概念
线程是进程内的一个执行单元,是CPU调度的基本单位。一个进程可以包含多个线程,这些线程共享进程的内存空间、文件描述符等资源,但每个线程有自己的栈空间和程序计数器。
7.1.2 线程的特点
- 轻量级:线程的创建和切换比进程快
- 共享资源:线程共享进程的资源,减少了资源开销
- 并发执行:多个线程可以并发执行,提高程序的执行效率
- 独立性:每个线程有自己的执行路径和状态
7.1.3 线程的创建
pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg):创建一个新线程thread:存储新线程的IDattr:线程属性,NULL表示使用默认属性start_routine:线程的入口函数arg:传递给线程入口函数的参数- 返回值:成功返回0,失败返回错误码
7.1.4 线程的终止
-
pthread_exit(void *retval):终止当前线程retval:线程的退出状态
-
pthread_cancel(pthread_t thread):取消指定的线程thread:要取消的线程的ID
7.1.5 线程的等待
- **
pthread_join(pthread_t thread, void **retval)**:等待指定的线程结束thread:要等待的线程的IDretval:存储线程的退出状态- 返回值:成功返回0,失败返回错误码
7.1.6 线程的属性
pthread_attr_init(pthread_attr_t *attr):初始化线程属性pthread_attr_destroy(pthread_attr_t *attr):销毁线程属性pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate):设置线程的分离状态detachstate:PTHREAD_CREATE_DETACHED(分离状态)或 PTHREAD_CREATE_JOINABLE(可连接状态)
7.1.7 线程同步
-
互斥锁(Mutex):
pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr):初始化互斥锁pthread_mutex_lock(pthread_mutex_t *mutex):获取互斥锁pthread_mutex_unlock(pthread_mutex_t *mutex):释放互斥锁pthread_mutex_destroy(pthread_mutex_t *mutex):销毁互斥锁
-
条件变量(Condition Variable):
pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr):初始化条件变量pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex):等待条件变量pthread_cond_signal(pthread_cond_t *cond):唤醒一个等待条件变量的线程pthread_cond_broadcast(pthread_cond_t *cond):唤醒所有等待条件变量的线程pthread_cond_destroy(pthread_cond_t *cond):销毁条件变量
-
信号量(Semaphore):
sem_init(sem_t *sem, int pshared, unsigned int value):初始化信号量sem_wait(sem_t *sem):获取信号量sem_post(sem_t *sem):释放信号量sem_destroy(sem_t *sem):销毁信号量
7.1.8 线程控制示例
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NUM_THREADS 5
void *thread_function(void *arg) {
int thread_id = *((int *)arg);
printf("线程 %d 开始执行\n", thread_id);
// 模拟工作
sleep(1);
printf("线程 %d 执行完毕\n", thread_id);
pthread_exit(NULL);
}
int main() {
pthread_t threads[NUM_THREADS];
int thread_ids[NUM_THREADS];
int i, ret;
// 创建线程
for (i = 0; i < NUM_THREADS; i++) {
thread_ids[i] = i;
ret = pthread_create(&threads[i], NULL, thread_function, &thread_ids[i]);
if (ret != 0) {
fprintf(stderr, "创建线程失败: %d\n", ret);
exit(1);
}
printf("创建线程 %d\n", i);
}
// 等待线程结束
for (i = 0; i < NUM_THREADS; i++) {
ret = pthread_join(threads[i], NULL);
if (ret != 0) {
fprintf(stderr, "等待线程失败: %d\n", ret);
exit(1);
}
printf("线程 %d 已结束\n", i);
}
printf("所有线程都已结束\n");
return 0;
}
7.1.9 线程同步示例
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#define NUM_THREADS 2
#define MAX_COUNT 10
int count = 0;
pthread_mutex_t mutex;
void *increment_function(void *arg) {
int i;
for (i = 0; i < MAX_COUNT; i++) {
pthread_mutex_lock(&mutex);
count++;
printf("线程 %d: count = %d\n", *((int *)arg), count);
pthread_mutex_unlock(&mutex);
sleep(0.1);
}
pthread_exit(NULL);
}
int main() {
pthread_t threads[NUM_THREADS];
int thread_ids[NUM_THREADS];
int i, ret;
// 初始化互斥锁
ret = pthread_mutex_init(&mutex, NULL);
if (ret != 0) {
fprintf(stderr, "初始化互斥锁失败: %d\n", ret);
exit(1);
}
// 创建线程
for (i = 0; i < NUM_THREADS; i++) {
thread_ids[i] = i;
ret = pthread_create(&threads[i], NULL, increment_function, &thread_ids[i]);
if (ret != 0) {
fprintf(stderr, "创建线程失败: %d\n", ret);
exit(1);
}
}
// 等待线程结束
for (i = 0; i < NUM_THREADS; i++) {
ret = pthread_join(threads[i], NULL);
if (ret != 0) {
fprintf(stderr, "等待线程失败: %d\n", ret);
exit(1);
}
}
// 销毁互斥锁
pthread_mutex_destroy(&mutex);
printf("最终 count = %d\n", count);
return 0;
}
7.2 项目实训:线程的实现 …… 154
7.2.1 实训目标
- 掌握线程的创建和管理
- 熟悉线程同步机制
- 了解线程的生命周期
- 学会编写简单的多线程程序
7.2.2 实训内容
-
创建线程
- 使用pthread_create()创建多个线程
- 为每个线程传递参数
- 区分主线程和子线程
-
线程同步
- 使用互斥锁保护共享资源
- 使用条件变量实现线程间通信
- 使用信号量控制线程的执行顺序
-
线程管理
- 使用pthread_join()等待线程结束
- 使用pthread_exit()终止线程
- 设置线程的属性
-
多线程应用
- 编写一个多线程程序,模拟生产者-消费者问题
- 编写一个多线程程序,计算数组的和
7.2.3 实训代码
示例1:生产者-消费者问题
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#define BUFFER_SIZE 5
#define MAX_ITEMS 10
int buffer[BUFFER_SIZE];
int in = 0;
int out = 0;
pthread_mutex_t mutex;
pthread_cond_t not_full;
pthread_cond_t not_empty;
void *producer(void *arg) {
int i, item;
for (i = 0; i < MAX_ITEMS; i++) {
item = i;
pthread_mutex_lock(&mutex);
while ((in + 1) % BUFFER_SIZE == out) {
pthread_cond_wait(¬_full, &mutex);
}
buffer[in] = item;
in = (in + 1) % BUFFER_SIZE;
printf("生产者生产: %d\n", item);
pthread_cond_signal(¬_empty);
pthread_mutex_unlock(&mutex);
sleep(1);
}
pthread_exit(NULL);
}
void *consumer(void *arg) {
int i, item;
for (i = 0; i < MAX_ITEMS; i++) {
pthread_mutex_lock(&mutex);
while (in == out) {
pthread_cond_wait(¬_empty, &mutex);
}
item = buffer[out];
out = (out + 1) % BUFFER_SIZE;
printf("消费者消费: %d\n", item);
pthread_cond_signal(¬_full);
pthread_mutex_unlock(&mutex);
sleep(2);
}
pthread_exit(NULL);
}
int main() {
pthread_t producer_thread, consumer_thread;
int ret;
// 初始化互斥锁和条件变量
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(¬_full, NULL);
pthread_cond_init(¬_empty, NULL);
// 创建生产者和消费者线程
ret = pthread_create(&producer_thread, NULL, producer, NULL);
if (ret != 0) {
fprintf(stderr, "创建生产者线程失败: %d\n", ret);
exit(1);
}
ret = pthread_create(&consumer_thread, NULL, consumer, NULL);
if (ret != 0) {
fprintf(stderr, "创建消费者线程失败: %d\n", ret);
exit(1);
}
// 等待线程结束
pthread_join(producer_thread, NULL);
pthread_join(consumer_thread, NULL);
// 销毁互斥锁和条件变量
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(¬_full);
pthread_cond_destroy(¬_empty);
return 0;
}
示例2:计算数组的和
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NUM_THREADS 4
#define ARRAY_SIZE 1000
int array[ARRAY_SIZE];
long sum = 0;
pthread_mutex_t mutex;
void *sum_function(void *arg) {
int thread_id = *((int *)arg);
int start = thread_id * (ARRAY_SIZE / NUM_THREADS);
int end = (thread_id + 1) * (ARRAY_SIZE / NUM_THREADS);
long local_sum = 0;
int i;
for (i = start; i < end; i++) {
local_sum += array[i];
}
pthread_mutex_lock(&mutex);
sum += local_sum;
pthread_mutex_unlock(&mutex);
printf("线程 %d 计算的和: %ld\n", thread_id, local_sum);
pthread_exit(NULL);
}
int main() {
pthread_t threads[NUM_THREADS];
int thread_ids[NUM_THREADS];
int i, ret;
// 初始化数组
for (i = 0; i < ARRAY_SIZE; i++) {
array[i] = i + 1;
}
// 初始化互斥锁
pthread_mutex_init(&mutex, NULL);
// 创建线程
for (i = 0; i < NUM_THREADS; i++) {
thread_ids[i] = i;
ret = pthread_create(&threads[i], NULL, sum_function, &thread_ids[i]);
if (ret != 0) {
fprintf(stderr, "创建线程失败: %d\n", ret);
exit(1);
}
}
// 等待线程结束
for (i = 0; i < NUM_THREADS; i++) {
ret = pthread_join(threads[i], NULL);
if (ret != 0) {
fprintf(stderr, "等待线程失败: %d\n", ret);
exit(1);
}
}
// 销毁互斥锁
pthread_mutex_destroy(&mutex);
printf("数组的总和: %ld\n", sum);
printf("预期的总和: %ld\n", (long)ARRAY_SIZE * (ARRAY_SIZE + 1) / 2);
return 0;
}
7.2.4 实训要求
- 完成所有实训内容
- 编写完整的C程序
- 测试程序的正确性
- 观察线程的执行过程
- 撰写实训报告
7.3 本章小结 …… 155
本章介绍了线程控制的相关知识,包括:
- 线程的概念和特点
- 线程的创建、终止和等待
- 线程的属性设置
- 线程同步机制:互斥锁、条件变量、信号量
- 项目实训:线程的实现
通过本章的学习,读者应该能够掌握线程的基本概念和控制方法,为后续的进程间通信学习打下基础。
第8章 进程间通信 …… 156
8.1 概述 …… 157
8.1.1 进程间通信的概念
进程间通信(IPC,Inter-Process Communication)是指不同进程之间传递信息的机制。由于进程的地址空间是相互独立的,进程之间不能直接访问对方的内存,因此需要使用专门的IPC机制来实现进程间的通信。
8.1.2 进程间通信的方式
在Linux系统中,常用的IPC机制包括:
- 共享内存:多个进程共享同一块内存区域
- 信号量:用于同步进程的执行
- 管道:用于进程间的单向通信
- 命名管道:用于无亲缘关系进程间的通信
- 消息队列:用于进程间传递消息
- 信号:用于进程间的异步通信
- 套接字:用于网络通信
8.2 共享内存 …… 157
8.2.1 共享内存的概念
共享内存是一种高效的IPC机制,它允许多个进程共享同一块内存区域,进程可以直接读写该内存区域,而不需要进行数据拷贝。
8.2.2 共享内存的操作函数
-
shmget(key_t key, size_t size, int shmflg):创建或获取共享内存key:共享内存的键值size:共享内存的大小shmflg:标志位,如IPC_CREAT(创建)、IPC_EXCL(排他)、权限位等- 返回值:共享内存的ID,出错返回-1
-
shmat(int shmid, const void *shmaddr, int shmflg):将共享内存附加到进程的地址空间shmid:共享内存的IDshmaddr:附加的地址,NULL表示由系统自动分配shmflg:标志位,如SHM_RDONLY(只读)- 返回值:附加的内存地址,出错返回-1
-
shmdt(const void *shmaddr):将共享内存从进程的地址空间分离shmaddr:附加的内存地址- 返回值:成功返回0,出错返回-1
-
shmctl(int shmid, int cmd, struct shmid_ds *buf):控制共享内存shmid:共享内存的IDcmd:命令,如IPC_STAT(获取状态)、IPC_SET(设置状态)、IPC_RMID(删除)buf:状态缓冲区- 返回值:成功返回0,出错返回-1
8.2.3 共享内存的示例
// 写入进程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define SHM_SIZE 1024
#define SHM_KEY 1234
int main() {
int shmid;
char *shmaddr;
// 创建共享内存
shmid = shmget(SHM_KEY, SHM_SIZE, IPC_CREAT | 0666);
if (shmid == -1) {
perror("shmget");
exit(1);
}
// 附加共享内存
shmaddr = (char *)shmat(shmid, NULL, 0);
if (shmaddr == (char *)-1) {
perror("shmat");
exit(1);
}
// 写入数据
strcpy(shmaddr, "Hello, Shared Memory!");
printf("写入共享内存: %s\n", shmaddr);
// 等待读取
printf("按任意键继续...\n");
getchar();
// 分离共享内存
shmdt(shmaddr);
// 删除共享内存
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
// 读取进程
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define SHM_SIZE 1024
#define SHM_KEY 1234
int main() {
int shmid;
char *shmaddr;
// 获取共享内存
shmid = shmget(SHM_KEY, SHM_SIZE, 0666);
if (shmid == -1) {
perror("shmget");
exit(1);
}
// 附加共享内存
shmaddr = (char *)shmat(shmid, NULL, 0);
if (shmaddr == (char *)-1) {
perror("shmat");
exit(1);
}
// 读取数据
printf("读取共享内存: %s\n", shmaddr);
// 分离共享内存
shmdt(shmaddr);
return 0;
}
8.3 信号量 …… 162
8.3.1 信号量的概念
信号量是一种用于同步进程执行的IPC机制,它可以用来保护共享资源,防止多个进程同时访问共享资源。
8.3.2 信号量的操作函数
-
semget(key_t key, int nsems, int semflg):创建或获取信号量集key:信号量集的键值nsems:信号量的数量semflg:标志位,如IPC_CREAT(创建)、IPC_EXCL(排他)、权限位等- 返回值:信号量集的ID,出错返回-1
-
semop(int semid, struct sembuf *sops, size_t nsops):执行信号量操作semid:信号量集的IDsops:信号量操作数组nsops:操作的数量- 返回值:成功返回0,出错返回-1
-
semctl(int semid, int semnum, int cmd, ...):控制信号量semid:信号量集的IDsemnum:信号量的编号cmd:命令,如SETVAL(设置值)、GETVAL(获取值)、IPC_RMID(删除)- 返回值:根据命令不同而不同,出错返回-1
8.3.3 信号量的示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/wait.h>
#define SEM_KEY 1234
// 信号量操作函数
void sem_wait(int semid) {
struct sembuf sops;
sops.sem_num = 0;
sops.sem_op = -1;
sops.sem_flg = 0;
semop(semid, &sops, 1);
}
void sem_post(int semid) {
struct sembuf sops;
sops.sem_num = 0;
sops.sem_op = 1;
sops.sem_flg = 0;
semop(semid, &sops, 1);
}
int main() {
int semid;
pid_t pid;
// 创建信号量
semid = semget(SEM_KEY, 1, IPC_CREAT | 0666);
if (semid == -1) {
perror("semget");
exit(1);
}
// 初始化信号量为1
semctl(semid, 0, SETVAL, 1);
// 创建子进程
pid = fork();
if (pid == -1) {
perror("fork");
exit(1);
}
if (pid == 0) {
// 子进程
printf("子进程等待信号量...\n");
sem_wait(semid);
printf("子进程获取信号量\n");
sleep(2);
printf("子进程释放信号量\n");
sem_post(semid);
exit(0);
} else {
// 父进程
sleep(1);
printf("父进程等待信号量...\n");
sem_wait(semid);
printf("父进程获取信号量\n");
sleep(2);
printf("父进程释放信号量\n");
sem_post(semid);
wait(NULL);
}
// 删除信号量
semctl(semid, 0, IPC_RMID);
return 0;
}
8.4 管道通信 …… 168
8.4.1 管道的概念
管道是一种用于进程间单向通信的IPC机制,它只能在有亲缘关系的进程之间使用(如父进程和子进程)。
8.4.2 管道的操作函数
pipe(int pipefd[2]):创建管道pipefd:管道的文件描述符数组,pipefd[0]用于读取,pipefd[1]用于写入- 返回值:成功返回0,出错返回-1
8.4.3 管道的示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main() {
int pipefd[2];
pid_t pid;
char buffer[1024];
// 创建管道
if (pipe(pipefd) == -1) {
perror("pipe");
exit(1);
}
// 创建子进程
pid = fork();
if (pid == -1) {
perror("fork");
exit(1);
}
if (pid == 0) {
// 子进程:读取管道
close(pipefd[1]); // 关闭写入端
read(pipefd[0], buffer, sizeof(buffer));
printf("子进程读取到: %s\n", buffer);
close(pipefd[0]); // 关闭读取端
exit(0);
} else {
// 父进程:写入管道
close(pipefd[0]); // 关闭读取端
const char *message = "Hello, Pipe!";
write(pipefd[1], message, strlen(message) + 1);
printf("父进程写入: %s\n", message);
close(pipefd[1]); // 关闭写入端
wait(NULL);
}
return 0;
}
8.5 命名管道 …… 171
8.5.1 命名管道的概念
命名管道(FIFO)是一种特殊的文件,它可以在无亲缘关系的进程之间使用,通过文件系统路径来标识。
8.5.2 命名管道的操作函数
mkfifo(const char *pathname, mode_t mode):创建命名管道pathname:命名管道的路径mode:权限位- 返回值:成功返回0,出错返回-1
8.5.3 命名管道的示例
// 写入进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#define FIFO_PATH "/tmp/myfifo"
int main() {
int fd;
const char *message = "Hello, Named Pipe!";
// 创建命名管道
mkfifo(FIFO_PATH, 0666);
// 打开命名管道
fd = open(FIFO_PATH, O_WRONLY);
if (fd == -1) {
perror("open");
exit(1);
}
// 写入数据
write(fd, message, strlen(message) + 1);
printf("写入命名管道: %s\n", message);
// 关闭命名管道
close(fd);
return 0;
}
// 读取进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#define FIFO_PATH "/tmp/myfifo"
int main() {
int fd;
char buffer[1024];
// 打开命名管道
fd = open(FIFO_PATH, O_RDONLY);
if (fd == -1) {
perror("open");
exit(1);
}
// 读取数据
read(fd, buffer, sizeof(buffer));
printf("读取命名管道: %s\n", buffer);
// 关闭命名管道
close(fd);
// 删除命名管道
unlink(FIFO_PATH);
return 0;
}
8.6 消息队列 …… 176
8.6.1 消息队列的概念
消息队列是一种用于进程间传递消息的IPC机制,它可以存储消息,进程可以从消息队列中读取消息。
8.6.2 消息队列的操作函数
-
msgget(key_t key, int msgflg):创建或获取消息队列key:消息队列的键值msgflg:标志位,如IPC_CREAT(创建)、IPC_EXCL(排他)、权限位等- 返回值:消息队列的ID,出错返回-1
-
msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg):发送消息msqid:消息队列的IDmsgp:消息指针msgsz:消息大小msgflg:标志位,如IPC_NOWAIT(非阻塞)- 返回值:成功返回0,出错返回-1
-
msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg):接收消息msqid:消息队列的IDmsgp:消息指针msgsz:消息大小msgtyp:消息类型msgflg:标志位,如IPC_NOWAIT(非阻塞)、MSG_NOERROR(截断消息)- 返回值:成功返回接收到的消息大小,出错返回-1
-
msgctl(int msqid, int cmd, struct msqid_ds *buf):控制消息队列msqid:消息队列的IDcmd:命令,如IPC_STAT(获取状态)、IPC_SET(设置状态)、IPC_RMID(删除)buf:状态缓冲区- 返回值:成功返回0,出错返回-1
8.6.3 消息队列的示例
// 发送进程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MSG_KEY 1234
struct message {
long mtype;
char mtext[1024];
};
int main() {
int msqid;
struct message msg;
// 创建消息队列
msqid = msgget(MSG_KEY, IPC_CREAT | 0666);
if (msqid == -1) {
perror("msgget");
exit(1);
}
// 设置消息类型和内容
msg.mtype = 1;
strcpy(msg.mtext, "Hello, Message Queue!");
// 发送消息
msgsnd(msqid, &msg, strlen(msg.mtext) + 1, 0);
printf("发送消息: %s\n", msg.mtext);
// 删除消息队列
msgctl(msqid, IPC_RMID, NULL);
return 0;
}
// 接收进程
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MSG_KEY 1234
struct message {
long mtype;
char mtext[1024];
};
int main() {
int msqid;
struct message msg;
// 获取消息队列
msqid = msgget(MSG_KEY, 0666);
if (msqid == -1) {
perror("msgget");
exit(1);
}
// 接收消息
msgrcv(msqid, &msg, sizeof(msg.mtext), 1, 0);
printf("接收消息: %s\n", msg.mtext);
return 0;
}
8.7 项目实训:进程之间通信功能的实现 …… 183
8.7.1 实训目标
- 掌握进程间通信的基本概念
- 熟悉各种IPC机制的使用
- 学会编写简单的进程间通信程序
- 了解进程间通信的应用场景
8.7.2 实训内容
-
共享内存通信
- 编写两个进程,使用共享内存传递数据
- 一个进程写入数据,另一个进程读取数据
-
信号量同步
- 编写两个进程,使用信号量同步它们的执行
- 实现生产者-消费者模式
-
管道通信
- 编写父进程和子进程,使用管道传递数据
-
命名管道通信
- 编写两个无亲缘关系的进程,使用命名管道传递数据
-
消息队列通信
- 编写两个进程,使用消息队列传递消息
8.7.3 实训代码
示例:使用共享内存和信号量实现生产者-消费者模式
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/wait.h>
#define SHM_KEY 1234
#define SEM_KEY 5678
#define SHM_SIZE 1024
// 信号量操作函数
void sem_wait(int semid) {
struct sembuf sops;
sops.sem_num = 0;
sops.sem_op = -1;
sops.sem_flg = 0;
semop(semid, &sops, 1);
}
void sem_post(int semid) {
struct sembuf sops;
sops.sem_num = 0;
sops.sem_op = 1;
sops.sem_flg = 0;
semop(semid, &sops, 1);
}
int main() {
int shmid, semid;
char *shmaddr;
pid_t pid;
// 创建共享内存
shmid = shmget(SHM_KEY, SHM_SIZE, IPC_CREAT | 0666);
if (shmid == -1) {
perror("shmget");
exit(1);
}
// 附加共享内存
shmaddr = (char *)shmat(shmid, NULL, 0);
if (shmaddr == (char *)-1) {
perror("shmat");
exit(1);
}
// 创建信号量
semid = semget(SEM_KEY, 1, IPC_CREAT | 0666);
if (semid == -1) {
perror("semget");
exit(1);
}
// 初始化信号量为1
semctl(semid, 0, SETVAL, 1);
// 创建子进程
pid = fork();
if (pid == -1) {
perror("fork");
exit(1);
}
if (pid == 0) {
// 子进程:消费者
while (1) {
sem_wait(semid);
if (strlen(shmaddr) > 0) {
printf("消费者读取: %s\n", shmaddr);
shmaddr[0] = '\0';
}
sem_post(semid);
sleep(1);
}
} else {
// 父进程:生产者
char message[1024];
while (1) {
printf("请输入消息(输入exit退出): ");
fgets(message, sizeof(message), stdin);
message[strlen(message) - 1] = '\0'; // 去除换行符
if (strcmp(message, "exit") == 0) {
break;
}
sem_wait(semid);
strcpy(shmaddr, message);
printf("生产者写入: %s\n", message);
sem_post(semid);
}
// 等待子进程结束
kill(pid, SIGTERM);
wait(NULL);
// 清理资源
shmdt(shmaddr);
shmctl(shmid, IPC_RMID, NULL);
semctl(semid, 0, IPC_RMID);
}
return 0;
}
8.7.4 实训要求
- 完成所有实训内容
- 编写完整的C程序
- 测试程序的正确性
- 观察进程间通信的过程
- 撰写实训报告
8.8 本章小结 …… 184
本章介绍了进程间通信的相关知识,包括:
- 概述:进程间通信的概念和方式
- 共享内存:高效的内存共享机制
- 信号量:用于同步进程的执行
- 管道通信:有亲缘关系进程间的单向通信
- 命名管道:无亲缘关系进程间的通信
- 消息队列:用于传递消息的机制
- 项目实训:进程之间通信功能的实现
通过本章的学习,读者应该能够掌握各种IPC机制的使用方法,为后续的网络编程学习打下基础。
第9章 信号及信号处理 …… 186
9.1 信号及其使用 …… 187
9.1.1 信号的概念
信号是一种用于进程间通信的机制,它是由操作系统或其他进程发送给目标进程的一种异步通知,用于通知目标进程发生了某个事件。
9.1.2 常用的信号
- SIGINT (2):中断信号,通常由Ctrl+C产生
- SIGTERM (15):终止信号,默认的终止进程信号
- SIGKILL (9):强制终止信号,无法被捕获或忽略
- SIGSEGV (11):段错误信号,当进程访问无效内存时产生
- SIGFPE (8):浮点异常信号,当发生浮点运算错误时产生
- SIGALRM (14):闹钟信号,由alarm()函数设置
- SIGCHLD (17):子进程状态改变信号,当子进程终止或停止时产生
- SIGUSR1 (10) 和 SIGUSR2 (12):用户自定义信号
9.1.3 信号的默认处理方式
- Term:终止进程
- Ign:忽略信号
- Core:终止进程并生成core文件
- Stop:停止进程
- Cont:继续进程
9.1.4 信号的发送
-
kill(pid_t pid, int sig):向指定进程发送信号pid:进程ID,-1表示向所有进程发送sig:信号编号- 返回值:成功返回0,出错返回-1
-
raise(int sig):向当前进程发送信号sig:信号编号- 返回值:成功返回0,出错返回-1
-
alarm(unsigned int seconds):设置闹钟,在指定秒数后发送SIGALRM信号seconds:秒数- 返回值:之前设置的闹钟剩余秒数
-
pause(void):暂停进程,直到收到信号- 返回值:-1,并设置errno为EINTR
9.2 信号处理 …… 189
9.2.1 信号处理函数
-
signal(int signum, sighandler_t handler):设置信号处理函数signum:信号编号handler:信号处理函数,或SIG_DFL(默认处理)、SIG_IGN(忽略)- 返回值:之前的信号处理函数
-
sigaction(int signum, const struct sigaction *act, struct sigaction *oldact):设置信号处理函数(更灵活)signum:信号编号act:新的信号处理配置oldact:保存旧的信号处理配置- 返回值:成功返回0,出错返回-1
9.2.2 信号处理函数的编写
信号处理函数的原型为:
void handler(int signum);
signum:接收到的信号编号
9.2.3 信号集操作
sigemptyset(sigset_t *set):初始化信号集为空sigfillset(sigset_t *set):初始化信号集为包含所有信号sigaddset(sigset_t *set, int signum):向信号集添加信号sigdelset(sigset_t *set, int signum):从信号集删除信号sigismember(const sigset_t *set, int signum):检查信号是否在信号集中
9.2.4 信号屏蔽
-
sigprocmask(int how, const sigset_t *set, sigset_t *oldset):设置进程的信号屏蔽字how:SIG_BLOCK(添加屏蔽)、SIG_UNBLOCK(解除屏蔽)、SIG_SETMASK(设置屏蔽)set:要屏蔽的信号集oldset:保存旧的信号屏蔽字- 返回值:成功返回0,出错返回-1
-
sigsuspend(const sigset_t *mask):临时替换信号屏蔽字并暂停进程mask:临时的信号屏蔽字- 返回值:-1,并设置errno为EINTR
9.2.5 信号处理示例
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void sigint_handler(int signum) {
printf("接收到SIGINT信号 (%d)\n", signum);
printf("正在清理资源...\n");
sleep(1);
printf("资源清理完成,程序退出\n");
exit(0);
}
int main() {
// 设置SIGINT信号的处理函数
signal(SIGINT, sigint_handler);
printf("程序运行中,按Ctrl+C发送SIGINT信号\n");
printf("等待信号...\n");
// 无限循环
while (1) {
sleep(1);
}
return 0;
}
9.3 项目实训:信号的处理 …… 194
9.3.1 实训目标
- 掌握信号的基本概念
- 熟悉常用信号的使用
- 学会编写信号处理函数
- 了解信号屏蔽和信号集的操作
9.3.2 实训内容
-
信号处理函数的编写
- 编写SIGINT信号的处理函数
- 编写SIGTERM信号的处理函数
-
信号发送
- 使用kill命令发送信号
- 使用kill()函数发送信号
-
信号屏蔽
- 使用sigprocmask()函数屏蔽信号
- 使用sigsuspend()函数临时解除屏蔽
-
闹钟信号
- 使用alarm()函数设置闹钟
- 编写SIGALRM信号的处理函数
9.3.3 实训代码
示例1:信号处理函数
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void sigint_handler(int signum) {
printf("接收到SIGINT信号 (%d)\n", signum);
printf("正在退出...\n");
exit(0);
}
void sigterm_handler(int signum) {
printf("接收到SIGTERM信号 (%d)\n", signum);
printf("正在退出...\n");
exit(0);
}
int main() {
// 设置信号处理函数
signal(SIGINT, sigint_handler);
signal(SIGTERM, sigterm_handler);
printf("程序运行中...\n");
printf("使用 'kill %d' 发送SIGTERM信号\n", getpid());
printf("按Ctrl+C发送SIGINT信号\n");
// 无限循环
while (1) {
printf("运行中...\n");
sleep(1);
}
return 0;
}
示例2:闹钟信号
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
int count = 0;
void alarm_handler(int signum) {
printf("接收到SIGALRM信号 (%d)\n", signum);
count++;
if (count < 5) {
// 重新设置闹钟
alarm(1);
} else {
printf("闹钟响了5次,程序退出\n");
exit(0);
}
}
int main() {
// 设置SIGALRM信号的处理函数
signal(SIGALRM, alarm_handler);
// 设置闹钟,1秒后发送SIGALRM信号
alarm(1);
printf("程序运行中,等待闹钟信号...\n");
// 无限循环
while (1) {
pause(); // 暂停进程,直到收到信号
}
return 0;
}
9.3.4 实训要求
- 完成所有实训内容
- 编写完整的C程序
- 测试程序的正确性
- 观察信号的处理过程
- 撰写实训报告
9.4 本章小结 …… 196
本章介绍了信号及信号处理的相关知识,包括:
- 信号的概念和常用信号
- 信号的默认处理方式
- 信号的发送和接收
- 信号处理函数的编写
- 信号集操作和信号屏蔽
- 项目实训:信号的处理
通过本章的学习,读者应该能够掌握信号的基本概念和处理方法,为后续的网络编程学习打下基础。
第10章 网络编程 …… 197
10.1 网络编程的基本概念 …… 198
10.1.1 网络协议
网络协议是计算机网络中通信双方必须遵守的规则和约定,它规定了数据的格式、传输方式、错误处理等。常用的网络协议包括:
- TCP/IP:传输控制协议/网际协议,是互联网的基础协议
- HTTP:超文本传输协议,用于Web通信
- FTP:文件传输协议,用于文件传输
- SMTP:简单邮件传输协议,用于邮件发送
- DNS:域名系统,用于域名解析
10.1.2 网络模型
- OSI七层模型:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层
- TCP/IP四层模型:网络接口层、网络层、传输层、应用层
10.1.3 IP地址和端口
- IP地址:用于标识网络中的设备,IPv4地址为32位,IPv6地址为128位
- 端口:用于标识设备上的进程,范围为0-65535,其中0-1023为知名端口
10.1.4 套接字
套接字(Socket)是网络通信的端点,是应用程序与网络协议栈之间的接口。套接字分为:
- 流套接字(SOCK_STREAM):基于TCP协议,提供可靠的、面向连接的通信
- 数据报套接字(SOCK_DGRAM):基于UDP协议,提供不可靠的、无连接的通信
10.2 网络编程基础 …… 199
10.2.1 套接字函数
-
socket(int domain, int type, int protocol):创建套接字domain:地址族,如AF_INET(IPv4)、AF_INET6(IPv6)type:套接字类型,如SOCK_STREAM(TCP)、SOCK_DGRAM(UDP)protocol:协议,通常为0,表示使用默认协议- 返回值:成功返回套接字文件描述符,出错返回-1
-
bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen):绑定套接字到地址sockfd:套接字文件描述符addr:地址结构体addrlen:地址结构体的长度- 返回值:成功返回0,出错返回-1
-
listen(int sockfd, int backlog):设置套接字为监听状态sockfd:套接字文件描述符backlog:等待连接的最大队列长度- 返回值:成功返回0,出错返回-1
-
accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen):接受连接sockfd:监听套接字文件描述符addr:存储客户端地址addrlen:地址结构体的长度- 返回值:成功返回新的套接字文件描述符,出错返回-1
-
connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen):连接到服务器sockfd:套接字文件描述符addr:服务器地址addrlen:地址结构体的长度- 返回值:成功返回0,出错返回-1
-
send(int sockfd, const void *buf, size_t len, int flags):发送数据sockfd:套接字文件描述符buf:数据缓冲区len:数据长度flags:标志,通常为0- 返回值:成功返回发送的字节数,出错返回-1
-
recv(int sockfd, void *buf, size_t len, int flags):接收数据sockfd:套接字文件描述符buf:数据缓冲区len:缓冲区长度flags:标志,通常为0- 返回值:成功返回接收的字节数,0表示连接关闭,出错返回-1
-
close(int sockfd):关闭套接字sockfd:套接字文件描述符- 返回值:成功返回0,出错返回-1
10.2.2 地址结构体
-
IPv4地址结构体:
struct sockaddr_in { sa_family_t sin_family; // 地址族(AF_INET) in_port_t sin_port; // 端口号(网络字节序) struct in_addr sin_addr; // IP地址 }; -
通用地址结构体:
struct sockaddr { sa_family_t sa_family; // 地址族 char sa_data[14]; // 地址数据 };
10.2.3 字节序转换
htons(uint16_t hostshort):主机字节序转网络字节序(16位)ntohs(uint16_t netshort):网络字节序转主机字节序(16位)htonl(uint32_t hostlong):主机字节序转网络字节序(32位)ntohl(uint32_t netlong):网络字节序转主机字节序(32位)
10.2.4 IP地址转换
inet_addr(const char *cp):字符串转IPv4地址(网络字节序)inet_ntoa(struct in_addr in):IPv4地址转字符串inet_pton(int af, const char *src, void *dst):字符串转IP地址(支持IPv4和IPv6)inet_ntop(int af, const void *src, char *dst, socklen_t size):IP地址转字符串(支持IPv4和IPv6)
10.3 TCP通信编程 …… 203
10.3.1 TCP的特点
- 面向连接:通信前需要建立连接
- 可靠传输:使用确认机制和重传机制
- 流量控制:使用滑动窗口机制
- 拥塞控制:适应网络状况
10.3.2 TCP服务器的实现步骤
- 创建套接字
- 绑定地址
- 设置监听状态
- 接受连接
- 收发数据
- 关闭连接
10.3.3 TCP客户端的实现步骤
- 创建套接字
- 连接到服务器
- 收发数据
- 关闭连接
10.3.4 TCP服务器示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[BUFFER_SIZE] = {0};
char *hello = "Hello from server";
// 创建套接字
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 设置套接字选项
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
// 绑定地址
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 设置监听状态
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
// 接受连接
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
// 接收数据
read(new_socket, buffer, BUFFER_SIZE);
printf("客户端消息: %s\n", buffer);
// 发送数据
send(new_socket, hello, strlen(hello), 0);
printf("已发送消息\n");
// 关闭连接
close(new_socket);
close(server_fd);
return 0;
}
10.3.5 TCP客户端示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int sock = 0;
struct sockaddr_in serv_addr;
char buffer[BUFFER_SIZE] = {0};
char *hello = "Hello from client";
// 创建套接字
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
printf("套接字创建失败\n");
return -1;
}
// 设置服务器地址
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
// 转换IP地址
if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr)<=0) {
printf("无效的地址\n");
return -1;
}
// 连接到服务器
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
printf("连接失败\n");
return -1;
}
// 发送数据
send(sock, hello, strlen(hello), 0);
printf("已发送消息\n");
// 接收数据
read(sock, buffer, BUFFER_SIZE);
printf("服务器消息: %s\n", buffer);
// 关闭连接
close(sock);
return 0;
}
10.4 UDP通信编程 …… 211
10.4.1 UDP的特点
- 无连接:通信前不需要建立连接
- 不可靠传输:不保证数据的可靠交付
- 面向报文:以报文为单位传输数据
- 速度快:没有确认和重传机制
10.4.2 UDP服务器的实现步骤
- 创建套接字
- 绑定地址
- 收发数据
- 关闭套接字
10.4.3 UDP客户端的实现步骤
- 创建套接字
- 收发数据
- 关闭套接字
10.4.4 UDP服务器示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int sockfd;
struct sockaddr_in servaddr, cliaddr;
char buffer[BUFFER_SIZE];
int len, n;
// 创建套接字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// 清空地址结构体
memset(&servaddr, 0, sizeof(servaddr));
memset(&cliaddr, 0, sizeof(cliaddr));
// 设置服务器地址
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = INADDR_ANY;
servaddr.sin_port = htons(PORT);
// 绑定地址
if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
len = sizeof(cliaddr);
// 接收数据
n = recvfrom(sockfd, (char *)buffer, BUFFER_SIZE, MSG_WAITALL, (struct sockaddr *)&cliaddr, &len);
buffer[n] = '\0';
printf("客户端消息: %s\n", buffer);
// 发送数据
sendto(sockfd, "Hello from server", strlen("Hello from server"), MSG_CONFIRM, (const struct sockaddr *)&cliaddr, len);
printf("已发送消息\n");
// 关闭套接字
close(sockfd);
return 0;
}
10.4.5 UDP客户端示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int sockfd;
struct sockaddr_in servaddr;
char buffer[BUFFER_SIZE];
int n, len;
// 创建套接字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// 清空地址结构体
memset(&servaddr, 0, sizeof(servaddr));
// 设置服务器地址
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(PORT);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
// 发送数据
sendto(sockfd, "Hello from client", strlen("Hello from client"), MSG_CONFIRM, (const struct sockaddr *)&servaddr, sizeof(servaddr));
printf("已发送消息\n");
// 接收数据
len = sizeof(servaddr);
n = recvfrom(sockfd, (char *)buffer, BUFFER_SIZE, MSG_WAITALL, (struct sockaddr *)&servaddr, &len);
buffer[n] = '\0';
printf("服务器消息: %s\n", buffer);
// 关闭套接字
close(sockfd);
return 0;
}
10.5 项目实训:局域网文件下载 …… 216
10.5.1 实训目标
- 掌握网络编程的基本概念
- 熟悉TCP通信编程
- 学会编写文件传输程序
- 了解局域网通信的实现
10.5.2 实训内容
-
服务器端
- 创建TCP服务器
- 接收客户端的文件请求
- 读取文件并发送给客户端
-
客户端
- 创建TCP客户端
- 连接到服务器
- 发送文件请求
- 接收文件并保存
10.5.3 实训代码
服务器端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/stat.h>
#include <fcntl.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[BUFFER_SIZE] = {0};
int file_fd;
ssize_t bytes_read, bytes_sent;
// 创建套接字
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 设置套接字选项
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
// 绑定地址
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 设置监听状态
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
printf("服务器启动,等待客户端连接...\n");
// 接受连接
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
// 接收文件名
read(new_socket, buffer, BUFFER_SIZE);
printf("客户端请求文件: %s\n", buffer);
// 打开文件
file_fd = open(buffer, O_RDONLY);
if (file_fd < 0) {
perror("open");
send(new_socket, "文件不存在", strlen("文件不存在"), 0);
close(new_socket);
close(server_fd);
return 1;
}
// 发送文件内容
while ((bytes_read = read(file_fd, buffer, BUFFER_SIZE)) > 0) {
bytes_sent = send(new_socket, buffer, bytes_read, 0);
if (bytes_sent < 0) {
perror("send");
break;
}
}
// 关闭文件和连接
close(file_fd);
close(new_socket);
close(server_fd);
printf("文件传输完成\n");
return 0;
}
客户端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int sock = 0;
struct sockaddr_in serv_addr;
char buffer[BUFFER_SIZE] = {0};
char filename[100];
int file_fd;
ssize_t bytes_read, bytes_written;
// 创建套接字
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
printf("套接字创建失败\n");
return -1;
}
// 设置服务器地址
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
// 转换IP地址
if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr)<=0) {
printf("无效的地址\n");
return -1;
}
// 连接到服务器
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
printf("连接失败\n");
return -1;
}
// 输入文件名
printf("请输入要下载的文件名: ");
scanf("%s", filename);
// 发送文件名
send(sock, filename, strlen(filename), 0);
printf("请求文件: %s\n", filename);
// 打开文件
file_fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (file_fd < 0) {
perror("open");
close(sock);
return -1;
}
// 接收文件内容
while ((bytes_read = read(sock, buffer, BUFFER_SIZE)) > 0) {
bytes_written = write(file_fd, buffer, bytes_read);
if (bytes_written < 0) {
perror("write");
break;
}
}
// 关闭文件和连接
close(file_fd);
close(sock);
printf("文件下载完成\n");
return 0;
}
10.5.4 实训要求
- 完成所有实训内容
- 编写完整的C程序
- 测试程序的正确性
- 观察文件传输的过程
- 撰写实训报告
10.6 本章小结 …… 221
本章介绍了网络编程的相关知识,包括:
- 网络编程的基本概念:网络协议、网络模型、IP地址和端口、套接字
- 网络编程基础:套接字函数、地址结构体、字节序转换、IP地址转换
- TCP通信编程:TCP的特点、服务器和客户端的实现
- UDP通信编程:UDP的特点、服务器和客户端的实现
- 项目实训:局域网文件下载
通过本章的学习,读者应该能够掌握网络编程的基本概念和实现方法,为后续的网络应用开发打下基础。
第11章 GTK+图形界面编程 …… 222
11.1 Linux图形界面开发 …… 223
11.1.1 Linux图形界面概述
Linux系统的图形界面是基于X Window System(X11)构建的,它提供了一个基础的图形窗口系统,允许应用程序创建和管理窗口、处理用户输入等。
11.1.2 常见的Linux桌面环境
- GNOME:使用GTK+作为工具包
- KDE:使用Qt作为工具包
- Xfce:轻量级桌面环境,使用GTK+
- LXDE:轻量级桌面环境,使用GTK+
11.1.3 GTK+简介
GTK+(GIMP Toolkit)是一个跨平台的图形用户界面工具包,最初为GIMP(GNU Image Manipulation Program)开发,现在被广泛应用于Linux桌面环境和应用程序。
11.2 GTK+程序结构 …… 224
11.2.1 GTK+程序的基本结构
- 初始化GTK+
- 创建主窗口
- 创建和添加构件
- 连接信号和回调函数
- 显示窗口
- 进入主事件循环
11.2.2 GTK+程序示例
#include <gtk/gtk.h>
// 回调函数
void on_button_clicked(GtkWidget *widget, gpointer data) {
g_print("按钮被点击了!\n");
}
int main(int argc, char *argv[]) {
GtkWidget *window;
GtkWidget *button;
GtkWidget *box;
// 初始化GTK+
gtk_init(&argc, &argv);
// 创建主窗口
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), "GTK+示例");
gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
// 创建垂直布局盒
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
gtk_container_add(GTK_CONTAINER(window), box);
// 创建按钮
button = gtk_button_new_with_label("点击我");
g_signal_connect(button, "clicked", G_CALLBACK(on_button_clicked), NULL);
gtk_box_pack_start(GTK_BOX(box), button, TRUE, TRUE, 0);
// 显示所有构件
gtk_widget_show_all(window);
// 进入主事件循环
gtk_main();
return 0;
}
11.2.3 编译和运行GTK+程序
编译命令:
gcc `pkg-config --cflags gtk+-3.0` -o hello hello.c `pkg-config --libs gtk+-3.0`
运行命令:
./hello
11.3 基本构件 …… 226
11.3.1 窗口(Window)
-
gtk_window_new(GtkWindowType type):创建窗口type:GTK_WINDOW_TOPLEVEL(顶级窗口)或 GTK_WINDOW_POPUP(弹出窗口)
-
常用属性:
- title:窗口标题
- default-width:默认宽度
- default-height:默认高度
- resizable:是否可调整大小
-
常用信号:
- destroy:窗口被销毁时触发
- delete-event:窗口关闭按钮被点击时触发
11.3.2 按钮(Button)
-
gtk_button_new():创建按钮 -
gtk_button_new_with_label(const gchar *label):创建带标签的按钮 -
gtk_button_new_with_mnemonic(const gchar *label):创建带助记符的按钮 -
常用信号:
- clicked:按钮被点击时触发
11.3.3 标签(Label)
gtk_label_new(const gchar *str):创建标签gtk_label_set_text(GtkLabel *label, const gchar *str):设置标签文本gtk_label_set_markup(GtkLabel *label, const gchar *str):设置带标记的文本
11.3.4 输入框(Entry)
-
gtk_entry_new():创建输入框 -
gtk_entry_set_text(GtkEntry *entry, const gchar *text):设置输入框文本 -
gtk_entry_get_text(GtkEntry *entry):获取输入框文本 -
常用信号:
- changed:输入框内容改变时触发
- activate:按下Enter键时触发
11.3.5 布局构件
- Box:水平或垂直布局
gtk_box_new(GtkOrientation orientation, gint spacing)
- Grid:网格布局
gtk_grid_new()
- Frame:带边框的容器
gtk_frame_new(const gchar *label)
- Notebook:标签页容器
gtk_notebook_new()
11.5 信号与事件 …… 237
11.5.1 信号的概念
信号是GTK+中用于处理用户交互的机制,当用户执行某个操作(如点击按钮)时,会触发相应的信号,程序可以通过连接回调函数来响应这些信号。
11.5.2 信号连接
g_signal_connect(gpointer instance, const gchar *signal_name, GCallback c_handler, gpointer data):连接信号和回调函数instance:发出信号的构件signal_name:信号名称c_handler:回调函数data:传递给回调函数的数据
11.5.3 回调函数
回调函数的一般形式:
void callback_function(GtkWidget *widget, gpointer data) {
// 处理信号
}
11.5.4 信号与事件示例
#include <gtk/gtk.h>
// 按钮点击回调函数
void on_button_clicked(GtkWidget *widget, gpointer data) {
GtkEntry *entry = GTK_ENTRY(data);
const gchar *text = gtk_entry_get_text(entry);
g_print("输入的文本:%s\n", text);
}
// 窗口销毁回调函数
void on_window_destroy(GtkWidget *widget, gpointer data) {
gtk_main_quit();
}
int main(int argc, char *argv[]) {
GtkWidget *window;
GtkWidget *box;
GtkWidget *entry;
GtkWidget *button;
// 初始化GTK+
gtk_init(&argc, &argv);
// 创建窗口
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), "信号与事件示例");
gtk_window_set_default_size(GTK_WINDOW(window), 300, 100);
g_signal_connect(window, "destroy", G_CALLBACK(on_window_destroy), NULL);
// 创建布局
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
gtk_container_add(GTK_CONTAINER(window), box);
// 创建输入框
entry = gtk_entry_new();
gtk_box_pack_start(GTK_BOX(box), entry, TRUE, TRUE, 0);
// 创建按钮
button = gtk_button_new_with_label("获取文本");
g_signal_connect(button, "clicked", G_CALLBACK(on_button_clicked), entry);
gtk_box_pack_start(GTK_BOX(box), button, TRUE, TRUE, 0);
// 显示所有构件
gtk_widget_show_all(window);
// 进入主事件循环
gtk_main();
return 0;
}
11.6 常用构件 …… 239
11.6.1 复选框(CheckButton)
gtk_check_button_new():创建复选框gtk_check_button_new_with_label(const gchar *label):创建带标签的复选框gtk_toggle_button_get_active(GtkToggleButton *toggle_button):获取复选框状态gtk_toggle_button_set_active(GtkToggleButton *toggle_button, gboolean is_active):设置复选框状态
11.6.2 单选按钮(RadioButton)
gtk_radio_button_new(GSList *group):创建单选按钮gtk_radio_button_new_with_label(GSList *group, const gchar *label):创建带标签的单选按钮gtk_radio_button_get_group(GtkRadioButton *radio_button):获取单选按钮组gtk_radio_button_join_group(GtkRadioButton *radio_button, GtkRadioButton *group_source):将单选按钮加入组
11.6.3 组合框(ComboBox)
gtk_combo_box_text_new():创建文本组合框gtk_combo_box_text_append_text(GtkComboBoxText *combo_box, const gchar *text):添加选项gtk_combo_box_text_get_active_text(GtkComboBoxText *combo_box):获取当前选中的文本
11.6.4 进度条(ProgressBar)
gtk_progress_bar_new():创建进度条gtk_progress_bar_set_fraction(GtkProgressBar *progress_bar, gdouble fraction):设置进度(0.0-1.0)gtk_progress_bar_pulse(GtkProgressBar *progress_bar):脉冲模式
11.6.5 滚动窗口(ScrolledWindow)
gtk_scrolled_window_new(GtkAdjustment *hadjustment, GtkAdjustment *vadjustment):创建滚动窗口gtk_scrolled_window_set_policy(GtkScrolledWindow *scrolled_window, GtkPolicyType hscrollbar_policy, GtkPolicyType vscrollbar_policy):设置滚动条策略
11.7 项目实训:贪吃蛇游戏 …… 243
11.7.1 实训目标
- 掌握GTK+图形界面编程
- 熟悉GTK+的基本构件和信号处理
- 学会编写简单的图形界面游戏
- 了解游戏开发的基本思路
11.7.2 实训内容
-
游戏设计
- 游戏界面:包含游戏区域、分数显示、控制按钮
- 游戏逻辑:蛇的移动、食物的生成、碰撞检测、得分计算
-
游戏实现
- 创建主窗口
- 绘制游戏区域
- 实现蛇的移动控制
- 实现食物的生成和碰撞检测
- 实现得分系统
11.7.3 实训代码
#include <gtk/gtk.h>
#include <stdlib.h>
#include <time.h>
#define WIDTH 400
#define HEIGHT 400
#define GRID_SIZE 20
#define MAX_LENGTH 100
typedef struct {
int x;
int y;
} Position;
typedef struct {
Position body[MAX_LENGTH];
int length;
int direction;
Position food;
int score;
gboolean running;
guint timer;
} SnakeGame;
SnakeGame game;
GtkWidget *drawing_area;
GtkWidget *score_label;
// 方向常量
#define UP 0
#define DOWN 1
#define LEFT 2
#define RIGHT 3
// 初始化游戏
void init_game() {
// 初始化蛇
game.length = 3;
game.body[0].x = WIDTH / 2;
game.body[0].y = HEIGHT / 2;
game.body[1].x = game.body[0].x - GRID_SIZE;
game.body[1].y = game.body[0].y;
game.body[2].x = game.body[1].x - GRID_SIZE;
game.body[2].y = game.body[1].y;
game.direction = RIGHT;
game.score = 0;
game.running = TRUE;
// 生成食物
srand(time(NULL));
game.food.x = (rand() % (WIDTH / GRID_SIZE)) * GRID_SIZE;
game.food.y = (rand() % (HEIGHT / GRID_SIZE)) * GRID_SIZE;
}
// 绘制游戏
gboolean draw_game(GtkWidget *widget, cairo_t *cr, gpointer data) {
int i;
// 绘制背景
cairo_set_source_rgb(cr, 0.9, 0.9, 0.9);
cairo_rectangle(cr, 0, 0, WIDTH, HEIGHT);
cairo_fill(cr);
// 绘制蛇
cairo_set_source_rgb(cr, 0.2, 0.6, 0.2);
for (i = 0; i < game.length; i++) {
cairo_rectangle(cr, game.body[i].x, game.body[i].y, GRID_SIZE - 1, GRID_SIZE - 1);
cairo_fill(cr);
}
// 绘制食物
cairo_set_source_rgb(cr, 0.8, 0.2, 0.2);
cairo_rectangle(cr, game.food.x, game.food.y, GRID_SIZE - 1, GRID_SIZE - 1);
cairo_fill(cr);
return FALSE;
}
// 移动蛇
void move_snake() {
int i;
Position head;
// 计算新头部位置
head = game.body[0];
switch (game.direction) {
case UP:
head.y -= GRID_SIZE;
break;
case DOWN:
head.y += GRID_SIZE;
break;
case LEFT:
head.x -= GRID_SIZE;
break;
case RIGHT:
head.x += GRID_SIZE;
break;
}
// 检查边界碰撞
if (head.x < 0 || head.x >= WIDTH || head.y < 0 || head.y >= HEIGHT) {
game.running = FALSE;
return;
}
// 检查自身碰撞
for (i = 1; i < game.length; i++) {
if (head.x == game.body[i].x && head.y == game.body[i].y) {
game.running = FALSE;
return;
}
}
// 检查食物碰撞
if (head.x == game.food.x && head.y == game.food.y) {
// 增加长度
if (game.length < MAX_LENGTH) {
game.length++;
}
// 增加分数
game.score += 10;
// 更新分数显示
char score_text[50];
sprintf(score_text, "分数: %d", game.score);
gtk_label_set_text(GTK_LABEL(score_label), score_text);
// 生成新食物
game.food.x = (rand() % (WIDTH / GRID_SIZE)) * GRID_SIZE;
game.food.y = (rand() % (HEIGHT / GRID_SIZE)) * GRID_SIZE;
}
// 移动身体
for (i = game.length - 1; i > 0; i--) {
game.body[i] = game.body[i - 1];
}
// 设置新头部
game.body[0] = head;
}
// 定时器回调
gboolean on_timer(gpointer data) {
if (game.running) {
move_snake();
gtk_widget_queue_draw(drawing_area);
return TRUE;
} else {
// 游戏结束
GtkWidget *dialog;
char message[100];
sprintf(message, "游戏结束!最终分数: %d", game.score);
dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_OK, "%s", message);
gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
return FALSE;
}
}
// 键盘事件处理
gboolean on_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) {
switch (event->keyval) {
case GDK_KEY_Up:
if (game.direction != DOWN) {
game.direction = UP;
}
break;
case GDK_KEY_Down:
if (game.direction != UP) {
game.direction = DOWN;
}
break;
case GDK_KEY_Left:
if (game.direction != RIGHT) {
game.direction = LEFT;
}
break;
case GDK_KEY_Right:
if (game.direction != LEFT) {
game.direction = RIGHT;
}
break;
}
return FALSE;
}
// 开始游戏
void on_start_game(GtkWidget *widget, gpointer data) {
// 取消之前的定时器
if (game.timer > 0) {
g_source_remove(game.timer);
}
// 初始化游戏
init_game();
// 更新分数显示
char score_text[50];
sprintf(score_text, "分数: %d", game.score);
gtk_label_set_text(GTK_LABEL(score_label), score_text);
// 启动定时器
game.timer = g_timeout_add(150, on_timer, NULL);
// 重绘
gtk_widget_queue_draw(drawing_area);
}
int main(int argc, char *argv[]) {
GtkWidget *window;
GtkWidget *vbox;
GtkWidget *hbox;
GtkWidget *start_button;
// 初始化GTK+
gtk_init(&argc, &argv);
// 创建主窗口
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), "贪吃蛇游戏");
gtk_window_set_default_size(GTK_WINDOW(window), WIDTH, HEIGHT + 50);
g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
// 创建垂直布局
vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
gtk_container_add(GTK_CONTAINER(window), vbox);
// 创建绘制区域
drawing_area = gtk_drawing_area_new();
gtk_widget_set_size_request(drawing_area, WIDTH, HEIGHT);
g_signal_connect(drawing_area, "draw", G_CALLBACK(draw_game), NULL);
gtk_box_pack_start(GTK_BOX(vbox), drawing_area, TRUE, TRUE, 0);
// 创建水平布局
hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10);
gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
// 创建分数标签
score_label = gtk_label_new("分数: 0");
gtk_box_pack_start(GTK_BOX(hbox), score_label, TRUE, TRUE, 0);
// 创建开始按钮
start_button = gtk_button_new_with_label("开始游戏");
g_signal_connect(start_button, "clicked", G_CALLBACK(on_start_game), NULL);
gtk_box_pack_start(GTK_BOX(hbox), start_button, FALSE, FALSE, 0);
// 连接键盘事件
gtk_widget_add_events(window, GDK_KEY_PRESS_MASK);
g_signal_connect(window, "key-press-event", G_CALLBACK(on_key_press), NULL);
// 初始化游戏
init_game();
// 显示所有构件
gtk_widget_show_all(window);
// 进入主事件循环
gtk_main();
return 0;
}
11.7.4 实训要求
- 完成所有实训内容
- 编写完整的C程序
- 测试程序的正确性
- 观察游戏的运行过程
- 撰写实训报告
11.8 本章小结 …… 249
本章介绍了GTK+图形界面编程的相关知识,包括:
- Linux图形界面开发:Linux图形界面概述、常见的桌面环境、GTK+简介
- GTK+程序结构:基本结构、示例代码、编译和运行
- 基本构件:窗口、按钮、标签、输入框、布局构件
- 信号与事件:信号的概念、信号连接、回调函数
- 常用构件:复选框、单选按钮、组合框、进度条、滚动窗口
- 项目实训:贪吃蛇游戏
通过本章的学习,读者应该能够掌握GTK+图形界面编程的基本概念和实现方法,为后续的图形界面应用开发打下基础。
结语
呕心沥血 , 啊啊啊 , 我要歇一会了 !😱😱😱😱😱😱