多模块程序
gcc -o program main.c
对单个代码文件进行编译,生成可执行文件program
,并且通过./program
运行编译生成的程序
我们对每一个模块会首先编译出每个模块对应的*.o
目标代码文件(relocatable object file)
gcc -c -o set.o set.c
会将我们的一个set.c
文件编译成一个set.o
的目标代码文件。请注意,这里的-c
表示生成目标代码文件。-o
与之前单文件的时候一样,在它之后我们会写明被生成的文件的名称。
#include"abc/set.h"复制路径 set.h----函数定义 set.c----函数执行内容
gcc -o program main.o set.o others.o
将目标代码文件set.o
和others.o
与main.o
在链接在一起,并且输出了 可执行文件(excutable file)program
。我们依然可以通过./program
运行编译生成的程序。
预处理指令#ifndef xxx
和#endif
,它们成对出现且#ifndef
在前,作用是如果这时并未已定义xxx
宏,则这对#ifndef xxx
, #endif
之间的内容有效。(其中xxx
可以替换为任意宏名)
这个宏的名字形式为工程名_路径名_文件名_H_
。只运用一次
#ifndef xxx
#define xxx
typedef enum Status { Success, Fail };
typedef struct {
char *name;
int age;
} People;
Status go_to_Jisuanke(People);
#endif
文件操作
声明一个文件指针。
FILE *fp;
这时候,如果我们希望对一个文件进行操作,我们需要先使用
fp = fopen(文件名, 访问模式);
将文件指针和文件关联起来,其中第一个参数是一个字符串,对应了我们希望访问的文件路径。第二个参数是访问模式,它可以是表示只读模式的"r"
,也可以是表示只写模式的"w"
,还可以是在文件末尾追加的"a"
。
通过fgetc(fp);
获得当前指针之后位置的一个字符了,每获得一个字符,指针会向后移动一个字符(如果到达文件尾部则会返回EOF
)。
我们这时通过fputc('c', fp);
的方式将字符'c'
写入到fp
关联的文件内了。
void filecopy(FILE *in_fp, FILE *out_fp) {
char ch;//赋值右值判断是否为EOF
while ((ch = fgetc(in_fp)) != EOF) {//输入结束时都会有EOF即是-1,ctrl+Z;ctrl+c终止信号,kill -9 SIGSTOP;
fputc(ch, out_fp);
}
}
这个函数接收的两个参数都是文件指针。这个函数会通过一个可读模式的文件指针逐字符地读取,并且通过一个可写模式的文件指针逐字符地将所有字符写出,从而起到复制文件内容的作用。
通过 fgetc(stdin);
获得来自标准输入的字符,也可以通过 fputc(ch, stdout);
或 fputc(ch, stderr);
将变量 ch
中的字符输出到标准输出或标准错误中的。
使用 fscanf
从文件指针in_fp
进行读取时,使用fprintf
向文件指针out_fp,
可以写成:
fscanf(in_fp, "%c", &a); fprintf(out_fp, "%c", a);freopen("abc.in","r",stdin)标准输入abc.in以读的形式==FILE *fp;
而如果我们写
fscanf(stdin, "%c", &a); fprintf(stdout, "%c", a);
等价于
scanf("%c", &a); printf("%c", a);
文件需要fclose(fp)关闭; fflush(fp)---刷新缓冲区;关闭io同步
Makefile
每一条规则的命令前,必须要有一个制表符\t
。
目标: 依赖1 依赖2 ...
命令
例如,我们可以写一条规则:
array.o: array.c array.h gcc -c -o array.o array.c
表示生成的文件是目标代码文件array.o
,它依赖于array.c
和array.h
。当我们在命令行中执行make array.o
时,根据这一规则,如果array.o
不存在或者array.c
与array.h
至少之一比array.o
更新,就会执行gcc -c -o array.o array.c
。
我们把上述代码保存为Makefile
,与array.c
和array.h
放在同一目录,在那个目录里执行make array.o
就能看到效果。注意:Makefile
里的除当前目录隐藏文件外的第一个目标会成为运行make
不指定目标时的默认目标。
.o
文件是对象文件的扩展名。对象文件是源代码文件(例如 .c
文件)经过编译器编译后生成的二进制文件。
-
中间表示:
.o
文件是编译过程的中间产物。当一个 C 源文件被编译但还没有被链接时,编译器会生成一个或多个.o
文件。 -
包含机器代码:
.o
文件包含了从源代码翻译过来的机器代码。这是程序在计算机上实际执行的代码。 -
符号表:除了机器代码,
.o
文件还包含一个符号表。这个符号表列出了所有在该对象文件中定义的符号(例如函数或变量)以及引用的但未在该对象文件中定义的符号。 -
重定位信息:对象文件还包含了重定位信息。这是因为在这个阶段,编译器还不知道每个函数或变量在最终的可执行文件中的确切位置。链接器使用这些重定位信息来调整对象文件中的地址引用,使其指向正确的位置。
-
静态库的组成部分:多个
.o
文件可以被归档为一个静态库文件(.a
文件,例如在 Unix-like 系统中)。当其他程序链接到这个静态库时,只有实际被引用的.o
文件中的代码会被包含到最终的可执行文件中。 -
链接过程:为了生成一个可执行的程序或共享库,链接器将多个
.o
文件链接在一起。链接器会解析所有的符号引用,确保每个符号都能找到对应的定义,并组合所有的.o
文件生成一个单一的输出文件。
将.o
为后缀的目标代码文件和可执行的程序文件删除,完全从头进行编译。那么我们可以写一条clean
规则,例如:
clean:
rm -f array.o main.o main
rm
命令表示删除文件,-f
表示强制,因此rm -f array.o main.o main
按照预期,当我们执行make clean
就可以删除array.o
、main.o
和main
了。事实真的这样吗?
细心的同学已经发现,这时如果已经存在clean
文件,rm
命令就不会执行了。为了解决这个问题,我们通过一个特殊的方法告诉make
这个名为clean
的规则在clean
存在的时候仍然有效。
.PHONY: clean
clean:
rm -f array.o main.o main
.PHONY
用于声明一些伪目标,伪目标与普通的目标的主要区别是伪目标不会被检查是否存在于文件系统中而默认不存在且不会应用默认规则生成它。
# 井号开头的行是一个注释,用注释说明了我们定义的Makefile变量的用途
# 设置 C 语言的编译器,CC变量定义了编译器,CFLAGS变量标记了编译参数,MAINOBJS变量记录了main依赖的目标文件。定义的变量可以直接通过$(变量名)进行使用。
CC = gcc
# -g 增加调试信息
# -Wall 打开大部分警告信息
CFLAGS = -g -Wall
# 整理一下 main 依赖哪些目标文件
MAINOBJS = main.o array.o
.PHONY: clean
main: $(MAINOBJS)
$(CC) $(CFLAGS) -o main $(MAINOBJS)
array.o: array.c array.h
$(CC) $(CFLAGS) -c -o array.o array.c
main.o: main.c array.h
$(CC) $(CFLAGS) -c -o main.o main.c
clean:
rm -f $(MAINOBJS) main
自制简易 OJ
研究真理可以有三个目的:当我们探索时,就要发现到真理;当我们找到时,就要证明真理;当我们审查时,就要把它同谬误区别开来。——帕斯卡(法国)
在本节中我们首先要探索一个错误程序的错误原因并予以修正,但不要求对其正确性进行数学证明,然后要编写一个 OJ 核心,审查其正确性。
完整目标
首先用gdb
调试并修正一个关于判断三角形的程序。然后编写一个简单的 OJ 核心判断这个程序或者其它一个仅操作于标准输入输出的程序是否正确。
给出的结构
Makefile
可以运行make
进行自测。src
编写和编译代码的目录,本节要求的所有任务都在这里完成。本节中,在终端里我们可以输入pushd src
并按回车进入这个目录,之后可以输入popd
并按回车退回进入之前的目录。Makefile
给出的正确的用于编译 OJ 的 Makefile。angle.c
这是需要被调试的错误地判断三角形的程序代码。oj.c
在这里编写 OJ 的主程序。run.c
提供函数void run(const char *program, const char *file_in, const char *file_out)
,其中第一个参数表示要运行的程序的路径是program
,第二个参数表示把路径为file_in
的文件的内容作为program
的标准输入内容,第三个参数表示program
的标准输出会输出到路径为file_out
的文件。run.h
这是run.c
对应的头文件。
srctestbyscript
(若存在) 运行自测用的目录,不必阅读和修改其中内容。usertest.sh
与自测相关的文件,不必阅读和修改其中内容。
第一部分
先修正angle
的编译和链接错误。(提示:只是两处简单的笔误)通过gdb
来调试,把angle.c
修正为下述题目的正确代码:
-
判断三角形
输入三个小于一万的正整数表示三角形的三个边长。
若不存在这样的三角形,输出
It's not a triangle
若存在这样一个锐角三角形,输出
It's an acute triangle
若存在这样一个钝角三角形,输出
It's an obtuse triangle
若存在这样一个直角三角形,输出
It's a right triangle
-
样例输入
5 4 3
-
样例输出
It's a right triangle
第二部分
测试./program
输入为in.txt
,输出到out.txt
,正确答案是right.txt
的情况下的正确性。如果正确,输出Accept
,否则输出Wrong Answer
。(只需要测试一次)
注意:一个程序是正确的当且仅当它的输出与标准输出完全一致或它的输出比标准输出的结尾多或少一个换行符('\n'
)。
第一部分
gdb调试 命令行如下:
cd scr
gcc -g -o angle angle.c
调整后代码
#include <stdio.h>
void swap(int *p, int *q)
{
int temp;
temp = *p;
*p = *q;
*q = temp;
}
int main(void)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
if (a > c) {
swap(&a, &c);
}
if (b > c) {
swap(&b, &c);
}
if (a + b <= c) {
puts("It's not a triangle");
} else if (a * a + b * b < c * c) {
puts("It's an obtuse triangle");
} else if (a * a + b * b > c * c) {
puts("It's an acute triangle");
} else {
puts("It's a right triangle");
}
return 0;
}
第二部分
在scr中的oj代码中运行
#include <stdio.h>
#include<math.h>
#include "run.h"
int main()
{
const char *name_program = "./program";
const char *name_in = "./in.txt";
const char *name_out = "./out.txt";
const char *name_right = "./right.txt";
run(name_program, name_in, name_out);
FILE *fpright = fopen(name_right, "r");
FILE *fpout = fopen(name_out, "r");
//该段很重要
int len1,len2;
fseek(fpright,0,SEEK_END);
fseek(fpout,0,SEEK_END);
len1 = ftell(fpright);
len2 = ftell(fpout);
if (abs(len1 - len2) > 1) {
fclose(fpright);
fclose(fpout);
printf("Wrong Answer");
return 0;
}
fseek(fpright,0,SEEK_SET);
fseek(fpout,0,SEEK_SET);
int right_ch, out_ch;
while (1) {
right_ch = getc(fpright);
out_ch = getc(fpout);
if (right_ch == EOF || out_ch == EOF) {
break;
}
if (right_ch != out_ch) {
printf("Wrong Answer");
fclose(fpright);
fclose(fpout);
return 0;
}
}
if ((right_ch == EOF && out_ch == '\n') || (right_ch == '\n' && out_ch == EOF) || (right_ch == EOF && out_ch == EOF) ) {
printf("Accept");
} else {
printf("Wrong Answer");
}
fclose(fpout);
fclose(fpright);
return 0;
}
make 判断答案是否正确
bash实现
#!/bin/bash//chmod 755 a.sh 可执行 ./a.sh执行
read -p "请输入学号: " student_id
read -p "请输入老师公布的6位数字密码: " password
# 生成哈希
combined_str="$student_id$password"
hashed_value=$(echo -n $combined_str | md5sum | awk '{print $1}')//awk取前面的数字
# 创建文件并存入哈希
today=$(date +"%Y-%m-%d")
filename="$today.txt"
echo $hashed_value > $filename//把哈希结果输入到该文件
# Git操作
read -p "请输入你的git分支名: " branch_name
git checkout $branch_name
git pull origin $branch_name
git add $filename
git commit -m "签到 $filename"
git push origin $branch_name
echo "签到完成!"
./checkin.sh
签到脚本
import hashlib
import os
import datetime
def generate_md5(data)://获取哈希值
m = hashlib.md5()
m.update(data.encode('utf-8'))
return m.hexdigest()
def create_and_save_file(filename, content):
with open(filename, 'w') as f://f=open('a.txt','r')类似
f.write(content)
def git_operations(branch_name, filename):
os.system('git checkout {}'.format(branch_name))//执行系统命令,关机shutdown
os.system('git pull origin {}'.format(branch_name))
os.system('git add {}'.format(filename))
os.system('git commit -m "签到 {}"'.format(filename))
os.system('git push origin {}'.format(branch_name))
if __name__ == "__main__"://看是否是主函数(有无都可)
# 用户信息
student_id = input("请输入学号: ").strip()
password = input("请输入老师公布的6位数字密码: ").strip()
# 生成哈希
combined_str = student_id + password
hashed_value = generate_md5(combined_str)
# 创建文件并存入哈希
today = datetime.date.today().strftime('%Y-%m-%d')
filename = "{}.txt".format(today)
create_and_save_file(filename, hashed_value)
# Git操作
branch_name = input("请输入你的git分支名: ").strip()
git_operations(branch_name, filename)
print("签到完成!")
签到统计脚本
import os
import sys
import subprocess
import hashlib
import re
def compute_md5(student_id, sign_code):
"""Compute md5 hash of student_id concatenated with sign_code."""
combined_string = f"{student_id}{sign_code}"
return hashlib.md5(combined_string.encode('utf-8')).hexdigest()
def get_all_branches(repo_path):
"""Return a list of all branches in the repo."""
cmd = ["git", "-C", repo_path, "branch", "-r"]//真则表达式提取数字,*多个字母
branches = subprocess.check_output(cmd).decode('utf-8').split()
# Filter out the remote name (e.g., origin/) from branch names
branches = [branch.split('/')[-1] for branch in branches if not branch.startswith('origin/HEAD')]
return branches
def check_attendance_for_date(repo_path, date, sign_code):
"""Check attendance for all branches on a given date."""
branches = get_all_branches(repo_path)
results = {}
for branch in branches:
# Extract student ID from the branch name (assuming the branch name starts with student ID)
try:
student_id = re.match(r'\d+', branch).group()
except:
continue
# Switch to the branch
subprocess.check_output(['git', '-C', repo_path, 'checkout', branch])
try:
subprocess.check_output(['git', '-C', repo_path, 'pull'])//更新
except:
continue
file_name = os.path.join(repo_path, date)
try:
with open(file_name, 'r') as file://获取文件名
try:
content = file.read().strip()//去换行等
except:
results[branch] = 'Encoding Error'
continue
expected_md5 = compute_md5(student_id, sign_code)
if content == expected_md5:
results[branch] = "Present"
else:
results[branch] = "Incorrect Code"
except FileNotFoundError:
results[branch] = "Absent"
# Switch back to the original branch
subprocess.check_output(['git', '-C', repo_path, 'checkout', '-'])
return results
if __name__ == "__main__":
if len(sys.argv) != 3:
print("Usage: python3 check_all_attendance.py <date> <sign_code>")
sys.exit(1)
repo_path = '.'
date = sys.argv[1]
sign_code = sys.argv[2]
attendance_results = check_attendance_for_date(repo_path, date, sign_code)
for branch, status in attendance_results.items():
print(f"Branch: {branch}, Status: {status}")
vscode,atom,sublime文本编辑器建议使用