20191218 2021-2022-diocs-Linux C语言编程基础(必做)
一、任务详情
- 基于Ubuntu或OpenEuler完成下面的任务(OpenEuler有加分)
- 选择教材第二章的一节进行编程基础练习(2.10,2.11,2.12,2.13,2.14任选一个)
- 建立自己的项目目录,包含自己学号信息(如20190100linkedlist),构建项目结构(src, include, bin, lib, docs, test...),然后把相应代码和文档放置到正确位置,用tree命令查看项目结构,提交截图(5分)
- 进行gcc相关练习(ESc, iso, -I等)提交相关截图(5分)
- 进行静态库,动态库制作和调用练习,提交相关截图(5分)
- 进行gdb相关练习,至少包含四种断点的设置,提交相关截图(10分)
- 编写makefile(5分)
二、实践过程
所有实践内容均在OpenEuler下完成,相应代码已上传至码云:第三周代码。
-
习题2.11
实现代码
#include <stdio.h>
#include <stdlib.h>
#define TElemType int
//初始化队头和队尾指针开始时都为0
int front=0,rear=0;
typedef struct BiTNode{
TElemType data;//数据域
struct BiTNode *lchild,*rchild;//左右孩子指针
}BiTNode,*BiTree;
void CreateBiTree(BiTree *T){
*T=(BiTNode*)malloc(sizeof(BiTNode));
(*T)->data=1;
(*T)->lchild=(BiTNode*)malloc(sizeof(BiTNode));
(*T)->rchild=(BiTNode*)malloc(sizeof(BiTNode));
(*T)->lchild->data=2;
(*T)->lchild->lchild=(BiTNode*)malloc(sizeof(BiTNode));
(*T)->lchild->rchild=(BiTNode*)malloc(sizeof(BiTNode));
(*T)->lchild->rchild->data=NULL;
(*T)->lchild->rchild->lchild=NULL;
(*T)->lchild->rchild->rchild=NULL;
(*T)->rchild->data=3;
(*T)->rchild->lchild=(BiTNode*)malloc(sizeof(BiTNode));
(*T)->rchild->lchild->data=6;
(*T)->rchild->lchild->lchild=NULL;
(*T)->rchild->lchild->rchild=NULL;
(*T)->rchild->rchild=(BiTNode*)malloc(sizeof(BiTNode));
(*T)->rchild->rchild->data=7;
(*T)->rchild->rchild->lchild=NULL;
(*T)->rchild->rchild->rchild=NULL;
(*T)->lchild->lchild->data=4;
(*T)->lchild->lchild->lchild=NULL;
(*T)->lchild->lchild->rchild=NULL;
}
//入队函数
void EnQueue(BiTree *a,BiTree node){
a[rear++]=node;
}
//出队函数
BiTNode* DeQueue(BiTNode** a){
return a[front++];
}
//输出函数
void displayNode(BiTree node){
if (node->data == 0)
printf("- ");
else printf("%d ",node->data);
}
int main() {
BiTree tree;
//初始化二叉树
CreateBiTree(&tree);
BiTNode * p;
//采用顺序队列,初始化创建队列数组
BiTree a[20];
//根结点入队
EnQueue(a, tree);
//当队头和队尾相等时,表示队列为空
while(front<rear) {
//队头结点出队
p=DeQueue(a);
displayNode(p);
//将队头结点的左右孩子依次入队
if (p->lchild!=NULL) {
EnQueue(a, p->lchild);
}
if (p->rchild!=NULL) {
EnQueue(a, p->rchild);
}
}
return 0;
}
其中,对教材上所提供的代码作出的主要修改在如下部分,添加了一个节点是否为空的判断
void displayNode(BiTree node){
if (node->data == 0)
printf("- ");
else printf("%d ",node->data);
在OpenEuler下运行结果,实现题目要求。
-
gcc相关练习
同时复习C语言文件操作,实现功能为文本文件和二进制文件的转换。
分为三个文件,BinIO.c中存放main函数,BinIO_func.c中存放其余被调用函数,BinIO.h中存放所需头文件以及结构体定义。
BinIO.c
#include "BinIO.h"
/**
*代码实例是将文本文件写入二进制文件,然后从二进制文件中读取,再写入到
*文本文件中去
*/
int main(int argc,char *argv[]){
if(argc !=4 ){
printf("缺少参数\n");
exit(EXIT_FAILURE);
}
text_to_bin(argv);
bin_to_text(argv);
return 0;
}
BinIO_func.c
#include "BinIO.h"
void text_to_bin();
void bin_to_text();
void text_to_bin(char *argv[]){
FILE *source_file_pointer;
FILE *des_file_pointer;
Stu stu = {1,"demo",0,0,0};
source_file_pointer = fopen(argv[1],"r");
if(source_file_pointer == NULL){
printf("open text source file failed\n");
exit(EXIT_FAILURE);
}
des_file_pointer = fopen(argv[2],"wb");
if(des_file_pointer == NULL){
printf("open bin source file failed\n");
exit(EXIT_FAILURE);
}
while(fscanf(source_file_pointer,"%d %s %d %d %d",&stu.xh,stu.name,&stu.math_score,&stu.english_score,&stu.chinese_score) != EOF ){
fwrite(&stu,sizeof(stu),1,des_file_pointer);
}
int source_close_result = fclose(source_file_pointer);
if(source_close_result == EOF){
printf("close source file failed\n");
exit(EXIT_FAILURE);
}else{
printf("close source file success\n");
}
int des_close_result = fclose(des_file_pointer);
if(des_close_result == EOF){
printf("close des file failed\n");
exit(EXIT_FAILURE);
}else{
printf("close des file success\n");
}
}
void bin_to_text(char *argv[]){
FILE *bin_source_file_pointer;
FILE *text_des_file_pointer;
Stu stu = {1,"z",0,0,0};
bin_source_file_pointer = fopen(argv[2],"rb");
if(bin_source_file_pointer == NULL){
printf("open bin_source_file failed\n");
//perror(argv[2]);
exit(EXIT_FAILURE);
}else{
printf("open bin_source_file success\n");
}
text_des_file_pointer = fopen(argv[3],"w");
if(text_des_file_pointer == NULL){
printf("open text des file failed\n");
perror(argv[3]);
exit(EXIT_FAILURE);
}else{
printf("open text_des file success\n");
}
while(fread(&stu,sizeof(stu),1,bin_source_file_pointer)){
fprintf(text_des_file_pointer,"%d %s %d %d %d\n",stu.xh,stu.name,stu.math_score,stu.english_score,stu.chinese_score);
}
int bin_source_file_close_result = fclose(bin_source_file_pointer);
if(bin_source_file_close_result == EOF){
printf("close bin_source_close_file error\n");
exit(EXIT_FAILURE);
}else{
printf("close bin_source_close_file success\n");
}
int text_des_file_close_result = fclose(text_des_file_pointer);
if(text_des_file_close_result == EOF){
printf("close text_des_file error\n");
exit(EXIT_FAILURE);
}else{
printf("close text_des_file success\n");
}
}
BinIO.h
#ifndef _BINIO_H_
#define _BINIO_H
#include<stdio.h>
#include<stdlib.h>
typedef struct student{
int xh;
char name[20];
int math_score;
int english_score;
int chinese_score;
}Stu;
void text_to_bin();
void bin_to_text();
#endif
实践过程
在直接编译时一定注意每次都不能忘掉-Iinclude
,以确定头文件所在位置
编译好后tree下的项目结构图
首次运行时发现文件内容并没有被改变,检查问题
在进行编程时,我本来是将这几部分合在一起的并且能够正常运行。为了练习gcc的使用(多文件链接)才将一个文件拆开,可是真正运行时发现程序根本没有办法终止,而是一直在往temp这个二进制文件中写入数据。我特意在较为熟悉的Windows下尝试运行,发现
程序一直在执行文本文件向二进制文件的写入操作,可以看到new1.txt文件甚至已经到了8GB!受时间关系此问题还没解决,在此做一个占位。
于是我又尝试了另一C程序的编译运行
用C语言大数的阶乘(数组实现)
实现代码
#include <stdio.h>
int a[10000];
int main()
{
int n, digit = 1, temp, i, j, carry;
scanf("%d", &n);
a[0] = 1;
for (i = 2; i <= n; i++)
{
carry = 0;
for (j = 1; j <= digit; j++)
{
temp = a[j-1] * i + carry;
a[j-1] = temp % 10;
carry = temp / 10;
}
while (carry)
{
a[++digit - 1] = carry % 10;
carry = carry / 10;
}
}
for (j = digit; j >= 1; j--)
printf("%d", a[j-1]);
return 0;
}
- 静态库、动态库相关练习
-
制作静态库和动态库
- 静态库
注意在第一次制作静态库时加上-Iinclude链接头文件,生成.a静态库之后可以不需要再加,直接编译即可。 - 动态库
注意每次都要加-Iinclude。
- 静态库
-
myod(选做)项目中的练习
tree下的结构图
用静态库运行myod结果
可以看到实现了od -tx -tc XXX
的要求 -
动态库
上面这张截图中我忘记添加-o参数指定输出路径,在当前目录下生成了a.out,下图是修改后的。
下图是在动态库下的运行结果
- gdb的练习
首先编译生成可执行文件(这里的test.c的功能是:从键盘读入一个整数,判断其是否是回文数和素数)。
gcc -g src/test.c src/test_fucnc.c -o resource/test
其中-g选项告诉gcc在编译程序时加入调试信息。
在其中遇到的问题
开始以为是由于调用了math.h库,而本目录和include目录中都不包含的这个,需要重新写头文件并放在include目录下
然而并不是这个问题,查阅资料发现在Linux系统下,C源文件若调用了math库里的函数,则编译时要加上-lm,表示链接到math库。
问题成功解决
接下来
使用gdb resource/test
可参考博客:https://blog.csdn.net/weixin_33881050/article/details/92279415
然后你就会看到屏幕出现许多信息,是一些关于gdb的版本信息说明之类内容的,但是它对调试程序没用,可以加上-q参数。
下面是一些常用的GDB调试命令:
(gdb)help:查看命令帮助,具体命令查询在gdb中输入help + 命令,简写h
(gdb)run:重新开始运行文件(run-text:加载文本文件,run-bin:加载二进制文件),简写r
(gdb)start:单步执行,运行程序,停在第一执行语句
(gdb)list:查看原代码(list-n,从第n行开始查看代码。list+ 函数名:查看具体函数),简写l
(gdb)set:设置变量的值
(gdb)next:单步调试(逐过程,函数直接执行),简写n
(gdb)step:单步调试(逐语句:跳入自定义函数内部执行),简写s
(gdb)backtrace:查看函数的调用的栈帧和层级关系,简写bt
(gdb)frame:切换函数的栈帧,简写f
(gdb)info:查看函数内部局部变量的数值,简写i
(gdb)finish:结束当前函数,返回到函数调用点
(gdb)continue:继续运行,简写c
(gdb)print:打印值及地址,简写p
(gdb)quit:退出gdb,简写q
(gdb)break+num:在第num行设置断点,简写b
(gdb)info breakpoints:查看当前设置的所有断点
(gdb)delete breakpoints num:删除第num个断点,简写d
(gdb)display:追踪查看具体变量值
(gdb)undisplay:取消追踪观察变量
(gdb)watch:被设置观察点的变量发生修改时,打印显示
(gdb)i watch:显示观察点
(gdb)enable breakpoints:启用断点
(gdb)disable breakpoints:禁用断点
(gdb)x:查看内存x/20xw 显示20个单元,16进制,4字节每单元
(gdb)run argv[1] argv[2]:调试时命令行传参
练习过程截图
-
设置断点
包括函数断点、临时断点、行断点、条件断点
-
跟踪调试
- 单步跟踪
- 下一步调试
- 单步跟踪
-
Makefile的练习
makefile在之前已经完成过,有关的练习见myod选做项目博客:20191218 2021-2022-diocs-MyOD