结对编程
一.梗概
本项目由开发者2352316与2352318通过结对编程协作完成,实现了一套基于网络通信的四则运算教学辅助系统。该系统采用双端架构设计,包含教师控制端与学生答题端两大功能模块,支持多客户端并发处理机制。在教师端功能实现方面,系统提供智能化参数配置:支持固定运算符模式与动态运算符模式两种出题策略。动态模式下可自定义运算符数量的上下限区间,系统将基于该区间自动生成随机数量的运算符组合试题。教师端同时集成自动化批改功能,可实时接收学生端提交的答案并进行智能校验。学生端采用轻量化设计,专注于题目呈现与答案采集功能。系统通过TCP/IP协议实现师生端的实时数据交互,学生在本地完成作答后,为了保证数据安全答案将传输给教师端进行批改。经批改后的结果通过专用通道回传至对应学生终端,形成完整的教学闭环。本系统采用分布式架构设计,具备良好的横向扩展能力,可支持多学生端并行接入。通过模块化编程实现了核心算法与界面逻辑的解耦,在确保运算准确性的同时,提升了系统的可维护性与可扩展性。
二.算法设计思路
2352316 练习题生成算法
我主要负责生成算式 ,我在设计生成算式的算法时分成了三步
第一步,生成。使用random库中uniform_int_distribution动态生成0-100的随机数和四则运算符,确保生成出来的算式长度可变。
第二步,检验。按数学规则优先处理乘除。若遇到除法,强制调整除数确保整除,避免无效操作,并将中间结果缓存。除结果确定后,按顺序处理加减法,计算最终答案。若结果超出1-1000的范围,则重新生成整个表达式。
第三步,拼接并返回。将验证后的数字和运算符按顺序拼接为字符串,保证表达式与计算结果严格一致。
通过分阶段计算模拟运算优先级、动态修正除数和结果范围控制,确保最后生成的结果符合要求。
2352328 数据传输算法
系统架构采用C/S结构服务端生成题目并验证答案,客户端接收题目并提交答案。网络通信基于Winsock2 封装WinSockTcp类,包括Startup,CreateSocket, Bind,Listen,Accept,Connect,Send
,Recv,Close,Shutdown,CleanUp这些成员函数,通过它们处理TCP通信。
服务器端即教师端,教师设置生成题目的格式,其中多线程处理,使用accept接受客户端连接,为每个客户端创建独立线程
使用线程池vector <std::thread> 管理并发连接。线程函数studentTransform实现题目的交互。
客户端即学生端,循环接收题目,答题并接收答题反馈。
三.程序代码
练习题生成代码:
CreateArithmeticProblems.h
点击查看代码
#pragma once
#include <utility>
#include <string>
#include <random>
class CreateArithmeticProblems
{
private:
static std::default_random_engine random_engine;
static std::uniform_int_distribution<int> operator_distribution;
static std::uniform_int_distribution<int> number_distribution;
CreateArithmeticProblems() = default;
public:
static std::pair<std::string, int> create(int size = 3);
};
CreateArithmeticProblems.cpp
点击查看代码
#include "CreateArithmeticProblems.h"
#include <vector>
std::default_random_engine CreateArithmeticProblems::random_engine(time(NULL));
std::uniform_int_distribution<int> CreateArithmeticProblems::operator_distribution(1, 4);
std::uniform_int_distribution<int> CreateArithmeticProblems::number_distribution(0, 100);
std::pair<std::string, int> CreateArithmeticProblems::create(int size)
{
int answer = 0;std::string arithmetic = "";
std::vector<char> operators;
std::vector<int> numbers;
do
{
operators.clear();
numbers.clear();
numbers.push_back(number_distribution(random_engine));
for (int i = 1; i < size; i++)
{
switch (operator_distribution(random_engine))
{
case 1:
operators.push_back('+'); break;
case 2:
operators.push_back('-'); break;
case 3:
operators.push_back('*'); break;
case 4:
operators.push_back('/'); break;
}
numbers.push_back(number_distribution(random_engine));
}
std::vector<int> reaining_numbers;
std::vector<char> secondary_operator;
int current_number = numbers[0];
for (int i = 0; i < operators.size(); i++)
{
const char& current_operator = operators[i];
const int& next_number = numbers[i + 1];
if (current_operator == '*' || current_operator == '/')
{
if (current_operator == '*')
{
current_number *= next_number;
}
else
{
while (next_number == 0 || current_number % next_number != 0)
{
const_cast<int&>(next_number) = number_distribution(random_engine);
}
current_number /= next_number;
}
}
else
{
reaining_numbers.push_back(current_number);
secondary_operator.push_back(current_operator);
current_number = next_number;
}
}
reaining_numbers.push_back(current_number);
answer = reaining_numbers[0];
for (int i = 0; i < secondary_operator.size(); i++)
{
if (secondary_operator[i] == '+') {
answer += reaining_numbers[i + 1];
}
else {
answer -= reaining_numbers[i + 1];
}
}
} while (answer <= 1 || answer >= 1000);
for (int i = 0; i < size - 1; i++)
{
arithmetic += std::to_string(numbers[i]) + operators[i];
}
arithmetic += std::to_string(numbers[size - 1]) + '=';
return std::pair<std::string, int>(arithmetic, answer);
}
本类写成了仿函数的形式,将构造函数设置为私有属性,以确保不会生成实例,同时,将所有的数据成员和方法设置为静态。在使用方面,只需要调用其中唯一的一个公有属性函数,就可以生成一个四则运算公式,其余全过程都在类的内部进行处理。
WinSockTcp.h
点击查看代码
#pragma once
#include <winsock2.h>
#include<iostream>
#include<vector>
#include<string>
#pragma comment(lib, "ws2_32.lib")
class WinSockTcp
{
private:
SOCKET sock = INVALID_SOCKET;
public:
WinSockTcp();
~WinSockTcp();
bool Startup(WSAData& wsaData);
bool CreateSocket();
bool Bind(std::string& ip, unsigned short port);
bool Listen(int count = 5);
bool Accept(sockaddr_in* addr, WinSockTcp* ts);
bool Connect(std::string& ip, unsigned short port);
bool Send(const std::string data);
bool Recv(std::string& data);
void Close();
void Shutdown();
static void CleanUp();
};
WinSockTcp.cpp
点击查看代码
#pragma once
#include "WinSockTcp.h"
#pragma warning(disable : 4996)
WinSockTcp::WinSockTcp() {}
WinSockTcp::~WinSockTcp() {
if (sock != INVALID_SOCKET) {
closesocket(sock);
}
}
bool WinSockTcp::Startup(WSAData& wsaData)
{
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
std::cout << "WSAStartup failed" << WSAGetLastError() << std::endl;
return false;
}
return true;
}
bool WinSockTcp::CreateSocket()
{
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock == INVALID_SOCKET) {
std::cout << "Socket creation failed" << WSAGetLastError() << std::endl;
return false;
}
return true;
}
bool WinSockTcp::Bind(std::string& ip, unsigned short port)
{
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.S_un.S_addr = inet_addr(ip.c_str());
if (bind(sock, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
std::cout << "Bind failed" << WSAGetLastError() << std::endl;
return false;
}
return true;
}
bool WinSockTcp::Listen(int count)
{
if (listen(sock, count) == SOCKET_ERROR) {
std::cout << "Listen failed" << WSAGetLastError() << std::endl;
return false;
}
return true;
}
bool WinSockTcp::Accept(sockaddr_in* addr, WinSockTcp* ts)
{
int addrlen = sizeof(sockaddr_in);
ts->sock = accept(sock, (sockaddr*)addr, &addrlen);
if (ts->sock == INVALID_SOCKET) {
std::cout << "Accept failed" << WSAGetLastError()<< std::endl;
return false;
}
return true;
}
bool WinSockTcp::Connect(std::string& ip, unsigned short port)
{
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
if (connect(sock, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
std::cout << "Connect failed" << WSAGetLastError() << std::endl;
return false;
}
return true;
}
bool WinSockTcp::Send(const std::string data)
{
int total = 0;
while (total < data.size())
{
int sent = send(sock, data.c_str() + total, data.size() - total, 0);
if (sent == SOCKET_ERROR) {
std::cout << "Send failed" << WSAGetLastError() << std::endl;
return false;
}
total += sent;
}
return true;
}
bool WinSockTcp::Recv(std::string& data)
{
char buffer[1024];
int received = recv(sock, buffer, sizeof(buffer), 0);
if (received == SOCKET_ERROR) {
std::cout << "Recv failed" << WSAGetLastError() << std::endl;
return false;
}
data.assign(buffer, received);
return true;
}
void WinSockTcp::Close() {
if (sock != INVALID_SOCKET) {
closesocket(sock);
sock = INVALID_SOCKET;
}
}
void WinSockTcp::Shutdown() {
if (sock != INVALID_SOCKET) {
shutdown(sock, SD_BOTH);
sock = INVALID_SOCKET;
}
}
void WinSockTcp::CleanUp() {
WSACleanup();
}
该文件封装了多个函数来实现TCP通信
Startup:初始化Winsock
CreateSocket:创建socket
Bind:绑定地址
Listen:监听连接
Accept:接收连接
Connect:连接服务器
Send:发送数据
Recv:接收数据
Close:释放Socket资源
Shutdown:通知不再接收、发送
servers.cpp
点击查看代码
#pragma once
#include"WinSockTcp.h"
#include"CreateArithmeticProblems.h"
#include<thread>
#include<array>
#include<utility>
const std::string right = "回答正确";
const std::string wrong = "回答错误,正确答案是:";
void studentTransform(const std::array<std::pair<std::string, int>, 300>& problems, WinSockTcp& socket)
{
int count = 0;
while (count < 300)
{
if (!socket.Send(problems[count].first))
{
std::cerr << "发送失败" << std::endl;
break;
}
std::string answer;
if (!socket.Recv(answer)) {
std::cerr << "连接断开" << std::endl;
break;
}
if (answer == std::to_string(problems[count].second))
{
if (!socket.Send(right))
{
std::cerr << "发送失败" << std::endl;
break;
}
}
else
{
if (!socket.Send(wrong + std::to_string(problems[count].second)))
{
std::cerr << "发送失败" << std::endl;
break;
}
}
count++;
}
}
int main() {
WinSockTcp ts;
WSADATA wsadata;
std::string ip = "127.0.0.1";
ts.Startup(wsadata);
ts.CreateSocket();
ts.Bind(ip, 8080);
ts.Listen(30);
std::vector<std::thread> threads;
std::vector<WinSockTcp> students(30);
std::array<std::pair<std::string, int>, 300> problems;
std::cout << "请选择题目输出格式 0-固定运算符 1-变化运算符" << std::endl;
int choice;
std::cin >> choice;
if (choice == 0)
{
std::cout << "输入运算符个数:";
int size;
std::cin >> size;
for (int i = 0; i < 300; i++)
{
problems[i] = CreateArithmeticProblems::create(size);
}
}
else
{
std::cout << "输入运算符个数下限:";
int down_range;
std::cin >> down_range;
std::cout << "输入运算符个数上限:";
int up_range;
std::cin >> up_range;
std::default_random_engine random_engine(time(NULL));
std::uniform_int_distribution<int> operator_distribution(down_range, up_range);
for (int i = 0; i < 300; i++)
{
int size = operator_distribution(random_engine);
problems[i] = CreateArithmeticProblems::create(size);
}
}
int i = 0;
while (true) {
sockaddr_in addr;
if (ts.Accept(&addr, &students[i]))
{
threads.emplace_back(studentTransform, std::ref(problems), std::ref(students[i]));
i++;
}
}
ts.Close();
WinSockTcp::CleanUp();
}
这是教师端接收学生端传来的答案并进行批改,批改后将批改结果反馈给学生端
studentTransform:实现交互的主要函数
client.cpp
点击查看代码
#include "WinSockTcp.h"
int main() {
WinSockTcp client;
WSADATA wsadata;
string ip = "127.0.0.1";
if (!client.Startup(wsadata)) {
cerr << "WSAStartup失败!" << endl;
return -1;
}
if (!client.CreateSocket()) {
cerr << "创建Socket失败!" << endl;
return -1;
}
if (!client.Connect(ip, 8080)) {
cerr << "连接服务器失败!" << endl;
return -1;
}
cout << "已连接到服务器!" << endl;
int num = 1;
while (num <= 300)
{
string question, answer;
if (!client.Recv(question)) {
cerr << "连接断开" << endl;
break;
}
cout << "第" << num << "题:" << question << "\n请输入答案:";
num++;
cin >> answer;
if (!client.Send(answer)) {
cerr << "发送失败" << endl;
break;
}
if (!client.Recv(question)) {
cerr << "连接断开" << endl;
break;
}
cout << question << endl;
}
client.Close();
WinSockTcp::CleanUp();
return 0;
}
这是学生端,接收教师端传来的题目,输入答案后接收教师端传来的结果反馈
四.运算结果
当老师设置变化运算符,并指定好运算符数量的上下限,两个学生端对教师段进行交互时,程序运行结果如图
当老师设置固定运算符,并指定好运算符数量,两个学生端对教师段进行交互时,程序运行结果如图
五.作业体会
2352328:在结对编程实践中,我深刻感受到这种协作模式所激发的知识共振效应。作为团队中经验较浅的一方,整个合作过程宛如一场手把手的实战教学——当搭档以"领航员"身份剖析系统架构设计思维时,我得以窥见隐藏在代码背后的工程哲学;而当他切换为"驾驶员"示范具体模块实现时,诸如函数封装的艺术、API的合理调用方式等细节技巧,又如同活水般注入我的知识体系。这种立体化的知识传递远比文档学习更富穿透力,推动我的编程思维实现了从"实现功能"到"构建系统"的跃迁。
更令我惊喜的是双重视角的交叉审视带来的质量提升。当我们共同推敲某个复杂逻辑时,我的基础性疑问常能触发他对设计合理性的重新审视,而他老道的调试经验又能快速定位我忽视的边界条件。这种实时纠偏机制使得代码漏洞无处遁形这种动态的知识对流,不仅加速了我的技术成长,更在团队中形成了知识共享的良性循环。
2352316:在结对编程实践中,我觉得一人coding,另一人审核代码并无必要。实际上,只要将接口设计好,双方就可以并行开发。无需交叉审核代码实现细节,而是通过精心设计的模块接口进行协同,这种模式类似于开发者调用标准模板库时仅需关注接口规范而非底层实现。
在这一次结对编程过程中,我彻底理解了高内聚,低耦合的好处。当接口设计达到高内聚度与低耦合度的理想状态时,协作双方能真正实现并行开发效率的最大化——每个开发者都可视为独立封装的功能模块,通过标准化接口进行高效交互。