Linux-> UDP 编程2

目录

本文说明

一:字典程序的几个问题

1:字典的本质

2:翻译功能的本质

3:让服务端和翻译功能相关联

二:字典类(Dict.hpp)

1:加载词典(Load)

2:翻译单词(Translate)

三:服务端(UdpServer.hpp)

四:Main.cc

五:客户端(UdpClient.cc)

六:日志文件(Log.hpp)

七:makefile

八:运行效果


本文说明

本文旨在实现UDP编程下的字典程序,输入英文返回中文,在其中不大改之前的echo程序的代码,而是让翻译功能和服务端类分离开,形成松耦合的效果~~

其中涉及到的socket接口、echo程序、日志文件都在以下博客中:

socket接口及echo程序:https://blog.csdn.net/shylyly_/article/details/151292001

日志文件的实现:https://blog.csdn.net/shylyly_/article/details/151263351

一:字典程序的几个问题

1:字典的本质

直接导入一个名为Dict.txt的文件即可,文件内容如下,暂且导入这点词汇,作为测试

apple: 苹果
banana: 香蕉
cat: 猫
dog: 狗
book: 书
pen: 笔
happy: 快乐的
sad: 悲伤的
run: 跑
jump: 跳
teacher: 老师
student: 学生
car: 汽车
bus: 公交车
love: 爱
hate: 恨
hello: 你好
goodbye: 再见
summer: 夏天
winter: 冬天

2:翻译功能的本质

既然我们是松耦合,那么我们选择在新文件中创建一个字典类Dict来完成关于字典的操作;字典的操作分为两步:①导入词汇,②翻译

①导入词汇:只需把词汇插入进哈希表unordered_map

②翻译:这样我们的翻译就可以直接使用哈希表unordered_map进行,翻译可以分为两种!第一种是英文作为K值,中文作为V值,使用哈希表自带的[ ]即可形成翻译效果,第二种是通过遍历得到对应的K值的迭代器,然后打印迭代器的second即可

3:让服务端和翻译功能相关联

如果是强耦合,则很简单,我们只需要在服务端类中大改代码,成员变量增加哈希表等,成员函数增加翻译函数等,但是这是不优雅的写法

松耦合让我们拥有了一个独立的字典类,该类中有翻译功能,而我们的服务端类相使用字典类中的翻译函数,只需使用bind实现,一个类绑定另一个类中的函数即可!

bind的讲解博客:https://blog.csdn.net/shylyly_/article/details/151109228

所以我们的服务端类的构造函数的参数就应该新增一个去接受字典类的翻译函数,然后使用bind把服务端类构造时的参数和字典类的翻译函数绑定即可,这样我们的服务端运行起来依旧只需要输入端口号,这才优雅

二:字典类(Dict.hpp)

#pragma once#include <iostream>      //C++头文件
#include <unordered_map> //哈希表
#include <fstream>       //C++的文件操作
#include <string>        //使用string类型
#include "Log.hpp"       //包含日志文件// 字典命名空间
namespace dict_ns
{const std::string defaultpath = "./Dict.txt"; // 字典文件的默认文件路径 为当前路径下的Dict.txt文件const std::string sep = ": ";                 // 字典文件中键值对的分隔符// 字典类class Dict{private:bool Load() // 私有方法:从文件加载字典数据到哈希表{std::ifstream in(_dict_conf_filepath); // 创建输入文件流if (!in.is_open())                     // 检查文件是否成功打开 及其打开失败的处理{LOG(FATAL, "open %s error\n", _dict_conf_filepath.c_str());return false;}std::string line;while (std::getline(in, line)) // 逐行读取文件{if (line.empty()) // 如果读到空行(什么字符都没有的一行)continue;     // 则跳过此空行  回到while里的getline 去读取下一行// 来到这 则代表改行不是空行auto pos = line.find(sep);    // 则需要先找到分隔符的 pos为分隔符的起始位置if (pos == std::string::npos) // 如果没有分隔符 代表格式不对!continue;                 // 则继续continent 回到while里的getline 去读取下一行// 来到这 代表一定有分隔符std::string word = line.substr(0, pos); // 则提取改行中的单词if (word.empty())                       // 如果分隔符前面没有单词continue;                           // 则继续continent 回到while里的getline 去读取下一行// 来到这 代表已经提取到了单词 则下面进行提取到中文std::string han = line.substr(pos + sep.size());              // 提取出中文if (han.empty())                                              // 如果分割符后面没有中文continue;                                                 // 则继续continent 回到while里的getline 去读取下一行LOG(DEBUG, "load info, %s: %s\n", word.c_str(), han.c_str()); // 顺便使用日志 打印一下提取到的单词和中文_dict.insert(std::make_pair(word, han)); // 把提取到的单词和中文插入到哈希表}in.close();                                                   // 关闭文件LOG(DEBUG, "load %s success\n", _dict_conf_filepath.c_str()); // 顺便使用日志 打印加载成功 以及词典的文件名字return true;}public:// 构造函数 内部直接调用加载函数// 构造函数接收一个 path 参数,path参数默认缺省为"./Dict.txt" 也可以自己传参指定路径// 再用 path 的值来初始化成员变量 _dict_conf_filepathDict(const std::string &path = defaultpath) : _dict_conf_filepath(path){Load(); // 调用加载函数}// 翻译函数std::string Translate(const std::string &word, bool &ok) // 第一个参数是单词 第二个是bool值(用来反映翻译是否可靠){ok = true;auto iter = _dict.find(word); // 单词作为哈希表的find接口的参数,去查找是否存在if (iter == _dict.end())      // 如果find的返回值是end(),代表没找到{ok = false; // 则返回的不是翻译 而是提示语句 所以bool值为false 表示返回的值不可靠return "未找到";}// return _dict[word];return iter->second; // 返回v值}// 析构函数~Dict(){}private:std::unordered_map<std::string, std::string> _dict; // 哈希表std::string _dict_conf_filepath;                    // 词典的路径};
}

1:加载词典(Load)

字典类的重点在于如何把文件中每一行的单词和中文加载到哈希表中,我们将次逻辑封装进了Load函数中,代码逻辑如下:

①:创建一个 ifstream(输入文件流)对象 in,并且打开词典文件(路径:_dict_conf_filepath)

std::ifstream in(_dict_conf_filepath); // 创建输入文件流
if (!in.is_open())                     // 检查文件是否成功打开 及其打开失败的处理
{LOG(FATAL, "open %s error\n", _dict_conf_filepath.c_str());return false;
}

②:使用getline函数读取词典文件中的每一行,而每一行不一定都是 apple: 苹果 这样的格式,所以我们需要对不符合要求的行进行跳过,而不符合要求的行有几种情况:

情况1:直接就是空行

情况2:不是空行 但没有分隔符

情况3:有分隔符 但是没有单词

情况4:有分隔符 但是没有中文

遇到以上的4中情况,只需使用关键字continue即可,其会跳过后序代码,来到while中的getline再次取到下一行!因为每次不管是否合规,我们都已经读取到了一整行,所以continue之后,回到最初的while中getline就会读取到下一行

Load函数代码:

bool Load() // 私有方法:从文件加载字典数据到哈希表{std::ifstream in(_dict_conf_filepath); // 创建输入文件流if (!in.is_open())                     // 检查文件是否成功打开 及其打开失败的处理{LOG(FATAL, "open %s error\n", _dict_conf_filepath.c_str());return false;}std::string line;while (std::getline(in, line)) // 逐行读取文件 (std::getline(输入流对象, 字符串变量);){if (line.empty()) // 如果读到空行(什么字符都没有的一行)continue;     // 则跳过此空行  回到while里的getline 去读取下一行// 来到这 则代表改行不是空行auto pos = line.find(sep);    // 则需要先找到分隔符的 pos为分隔符的起始位置if (pos == std::string::npos) // 如果没有分隔符 代表格式不对!continue;                 // 则继续continent 回到while里的getline 去读取下一行// 来到这 代表一定有分隔符std::string word = line.substr(0, pos); // 则提取改行中的单词if (word.empty())                       // 如果分隔符前面没有单词continue;                           // 则继续continent 回到while里的getline 去读取下一行// 来到这 代表已经提取到了单词 则下面进行提取到中文std::string han = line.substr(pos + sep.size());              // 提取出中文if (han.empty())                                              // 如果分割符后面没有中文continue;                                                 // 则继续continent 回到while里的getline 去读取下一行LOG(DEBUG, "load info, %s: %s\n", word.c_str(), han.c_str()); // 顺便使用日志 打印一下提取到的单词和中文_dict.insert(std::make_pair(word, han)); // 把提取到的单词和中文插入到哈希表}in.close();                                                   // 关闭文件LOG(DEBUG, "load %s success\n", _dict_conf_filepath.c_str()); // 顺便使用日志 打印加载成功 以及词典的文件名字return true;}

理解了四种需要continue的情况之后,还需要理解怎么从一个合格的行中,摘取出单词和中文,这就需要使用到string的substr接口了!关键点在于我们知道分隔符是冒号空格,我们已经将其定义成了sep!

所以当我们不是情况1空行的时候,我们首先要做的就是查找到我们的分隔符,如果找不到就是情况2,反之找到了分隔符,我们的find函数就会返回分隔符的第一个字符也就是冒号的下标,所以此时我们就使用substr去摘取单词!代码如下:

std::string word = line.substr(0, pos); // 则提取改行中的单词
if (word.empty())                       // 如果分隔符前面没有单词continue;  

参数0代表从下标为0处开始摘取单词,pos代表摘取的字符的个数,冒号的下标刚好就是单词中字符的格式,所以直接line.substr(0, pos)即可!

再下来就是摘取中文,此时我们还会再使用substr,第一个参数应该是中文的第一个字符的下标,所以我们只需pos(冒号的下标)+分隔符的size即可让pos移动到中文的第一个字符的下标,代码:

// 来到这 代表已经提取到了单词 则下面进行提取到中文
std::string han = line.substr(pos + sep.size());              // 提取出中文
if (han.empty())                                              // 如果分割符后面没有中文continue;       

substr的第二个参数不填,代表sunstr会一直向后取到line这个字符串,也就是我们getline获取到的当前行的末尾,我们无需再传递参数

2:翻译单词(Translate)

而翻译函数就更简单了,我们只需对接收到的参数,也就是一个英文单词,将其作为哈希表的k值即可,返回v值即可达到翻译的效果

而Translate参数中的bool值,只是用来避免我们词典中没有这个单词,返回未找到,但是用户刚好传进来的单词的翻译就是未找到的情况,所以bool值为true代表翻译是可靠的,反之代表翻译不可靠,也就是买找到,打印未找到

当然,你也可以完全不用这个bool值参数,因为我们本来就是翻译一个单词,干脆把没找到翻译的情况,我们打印"该单词未被收录"即可,因为不会有一个单词的翻译会是"该单词未被收录"这么长!

// 翻译函数
std::string Translate(const std::string &word, bool &ok) // 第一个参数是单词 第二个是bool值(用来反映翻译是否可靠)
{ok = true;auto iter = _dict.find(word); // 单词作为哈希表的find接口的参数,去查找是否存在if (iter == _dict.end())      // 如果find的返回值是end(),代表没找到{ok = false; // 则返回的不是翻译 而是提示语句 所以bool值为false 表示返回的值不可靠return "未找到";}// return _dict[word];return iter->second; // 返回v值
}

三:服务端(UdpServer.hpp)

#pragma once#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <functional>
#include "Log.hpp"// 一些发生错误时候 返回的枚举变量
enum
{SOCKET_ERROR = 1,BIND_ERROR,USAGE_ERROR
};// socket接口默认的返回值
const static int defaultfd = -1;// typedef std::function<std::string(const std::string&)> func_t;
using func_t = std::function<std::string(const std::string &, bool &ok)>; // 定义一个函数类型  别名为func_tclass UdpServer
{
public:// 构造函数                                 socket的返回值       端口号         是否运行      翻译函数UdpServer(uint16_t port, func_t func) : _sockfd(defaultfd), _port(port), _isrunning(false), _func(func){}// 析构函数~UdpServer(){}// 初始化服务端void InitServer(){// 1:创建udp socket 套接字_sockfd = socket(AF_INET, SOCK_DGRAM, 0); // AF_INET代表网络通信  SOCK_DGRAM代表数据报 所以就是UDP 第三个参数填写0即可// 创建套接字失败 打印语句提醒if (_sockfd < 0){LOG(FATAL, "socket error, %s, %d\n", strerror(errno), errno);exit(SOCKET_ERROR);}// 创建套接字成功 打印语句提醒LOG(INFO, "socket create success, sockfd: %d\n", _sockfd);// 2.0 填充sockaddr_in结构struct sockaddr_in local; // 网络通信 所以定义struct sockaddr_in类型的变量bzero(&local, sizeof(local)); // 先把结构体情况 好习惯local.sin_family = AF_INET;         // 填写第一个字段 (地址类型)local.sin_port = htons(_port);      // 填写第二个字段PORT (需先转化为网络字节序)local.sin_addr.s_addr = INADDR_ANY; // 填写第三个字段IP (直接填写0即可,INADDR_ANY就是为0的宏)// 2:bind绑定// 我们填充好的local和我们创建的套接字进行绑定(绑定我们接收信息发送信息的端口)int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));// 绑定失败 打印语句提醒if (n < 0){LOG(FATAL, "bind error, %s, %d\n", strerror(errno), errno);exit(BIND_ERROR);}// 绑定成功 打印语句提醒LOG(INFO, "socket bind success\n");}// 启动服务端(不想让网络通信模块和业务模块进行强耦合)void Start(){// 先把bool值置为true 代表服务端在运行_isrunning = true;while (true) // 服务端都是死循环{char request[1024];           // 对方端发来的信息 存储在buffer中struct sockaddr_in peer;      // 对方端的网络属性socklen_t len = sizeof(peer); // 必须初始化成为sizeof(peer) 不能初始化为0// 我们要让server先收数据ssize_t n = recvfrom(_sockfd, request, sizeof(request) - 1, 0, (struct sockaddr *)&peer, &len);if (n > 0){request[n] = 0;                            // 添加字符串终止符  方便得到正确完整的单词bool ok;                                   // 定义bool值std::string response = _func(request, ok); // 将翻译请求,回调出去,在外部进行数据处理// 再把翻译得到的中文,发回给对方sendto(_sockfd, response.c_str(), response.size(), 0, (struct sockaddr *)&peer, len);}}_isrunning = false;}private:int _sockfd;     // socket的返回值 在多个接收中都需要使用uint16_t _port;  // 服务器所用的端口号bool _isrunning; // 反映是否在运行的bool值// 给服务器设定回调,用来执行翻译的函数func_t _func;
};

解释:

服务端和上篇博客的echo程序的服务端大致相同,因为我们是松耦合!将翻译函数全部交给了字典类中的翻译函数,所以服务端只需要额外理解我们如果引入一个承载字典类中的Translate函数的载体就行!

①:我们需要先定义出一个函数类型,再给其起一个别名,代码:

using func_t = std::function<std::string(const std::string &, bool &ok)>; 
// 定义一个函数类型  别名为func_t

这个类型就是我们字典类中的类型,这样才能完美的将字典类的Translate函数bind到func_t上

②:然后我们需要利用定义出的类型去创建一个成员变量,因为只有创建除了成员变量,我们才可以通过构造函数的参数进行绑定!

func_t _func;

③:start中调用_func函数

前面准备工作做好之后,我们需要就是在start函数中进行调用_func函数,也就是我们接收到客户端传来的单词,我们就要把单词作为参数传递进_func函数中,再把_func函数的返回值,也就是翻译得到的中文,发送给客户端即可!

// 我们要让server先收数据
ssize_t n = recvfrom(_sockfd, request, sizeof(request) - 1, 0, (struct sockaddr *)&peer, &len);if (n > 0)
{request[n] = 0;                            // 添加字符串终止符  方便得到正确完整的单词bool ok;                                   // 定义bool值std::string response = _func(request, ok); // 将翻译请求,回调出去,在外部进行数据处理//再把翻译得到的中文,发回给对方sendto(_sockfd, response.c_str(), response.size(), 0, (struct sockaddr *)&peer, len);
}

这样在我们进行bind操作之后,就可以实现松耦合的翻译功能了!

四:Main.cc

#include <iostream>
#include <memory>
#include "Dict.hpp"
#include "UdpServer.hpp"using namespace dict_ns; // 打开字典类的命名空间// 运行服务端的时候,因为IP在服务端文件中给定0了
// 所以只需要使用者只需要给服务端一个PORT即可
void Usage(std::string proc)
{std::cout << "Usage:\n\t" << proc << " local_port\n"<< std::endl;
}int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);exit(USAGE_ERROR);}EnableScreen(); // 表示把日志打印到屏幕上Dict dict; // 定义字典类对象uint16_t port = std::stoi(argv[1]); // 从main的参数列表中获取到PORT//  创建服务端对象 并且使用bind把字典类的成员函数绑定到服务器类的构造参数中std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, std::bind(&Dict::Translate, &dict, std::placeholders::_1, std::placeholders::_2));usvr->InitServer(); // 初始化服务端usvr->Start();      // 启动服务端return 0;
}

解释:

重点就在于bind的使用!首先需要包含字典文件已经打开字典类的命名空间,然后创建一个字典类的对象,这样,我们下一步进行创建服务端对象的时候,就可以进行bind了。bind的语法就不再赘述了,都整理在了博客中:https://blog.csdn.net/shylyly_/article/details/151109228

bind之后,就相当于我们服务端类中的_func函数就拥有了实体了,其才能够真正的实现翻译功能

五:客户端(UdpClient.cc)

#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>// 启动客户端 需要用户输入服务端的IP+PORT
void Usage(std::string proc)
{std::cout << "Usage:\n\t" << proc << " serverip serverport\n"<< std::endl;
}int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(1);}std::string serverip = argv[1];           // 从main的参数列表中获取到服务端IPuint16_t serverport = std::stoi(argv[2]); // 从main的参数列表中获取到服务端PORT// 1. 创建socketint sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){std::cerr << "socket error" << std::endl;}// 2. 无需显式的bind OS会自己做// 用户已经传递服务端的IP+PORT// 所以我们sendto就知道了 给哪个服务端传递信息// 所以需要事先构建好服务端的网络属性struct sockaddr_in server;                            // 定义出struct sockaddr_in的变量memset(&server, 0, sizeof(server));                   // 清零server.sin_family = AF_INET;                          // 地址类型字段的填写server.sin_port = htons(serverport);                  // PORT字段的填写server.sin_addr.s_addr = inet_addr(serverip.c_str()); // IP字段的填写std::string message; // 存放用户输入的信息// 3. 直接通信即可while (true){std::cout << "Please Enter# ";   // 提示用户可以输入你想向服务端发送的信息了std::getline(std::cin, message); // 获取到用户输入的信息 cin到message中// 发送信息sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));// 构建出接收服务端的网络属性结构体struct sockaddr_in peer;socklen_t len = sizeof(peer);char buffer[1024];// 接收信息ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);// 接收成功if (n > 0){buffer[n] = 0;                                       // 添加字符串终止符std::cout << "server echo# " << buffer << std::endl; // 打印服务器回复的信息}}return 0;
}

解释:客户端代码与echo程序一模一样,不再赘述

六:日志文件(Log.hpp)

#pragma once#include <iostream>    //C++必备头文件
#include <cstdio>      //snprintf
#include <string>      //std::string
#include <ctime>       //time
#include <cstdarg>     //va_接口
#include <sys/types.h> //getpid
#include <unistd.h>    //getpid
#include <thread>      //锁
#include <mutex>       //锁
#include <fstream>     //C++的文件操作std::mutex g_mutex;                    // 定义全局互斥锁
bool gIsSave = false;                  // 定义一个bool类型 用来判断打印到屏幕还是保存到文件
const std::string logname = "log.txt"; // 保存日志信息的文件名字// 日志等级
enum Level
{DEBUG = 0,INFO,WARNING,ERROR,FATAL
};// 将日志写进文件的函数
void SaveFile(const std::string &filename, const std::string &message)
{std::ofstream out(filename, std::ios::app);if (!out.is_open()){return;}out << message << std::endl;out.close();
}// 日志等级转字符串--->字符串才能表示等级的意义 0123意义不清晰
std::string LevelToString(int level)
{switch (level){case DEBUG:return "Debug";case INFO:return "Info";case WARNING:return "Warning";case ERROR:return "Error";case FATAL:return "Fatal";default:return "Unknown";}
}// 获取当前时间的字符串
// 时间格式包含多个字符 所以干脆糅合成一个字符串
std::string GetTimeString()
{// 获取当前时间的时间戳(从1970-01-01 00:00:00开始的秒数)time_t curr_time = time(nullptr);// 将时间戳转换为本地时间的tm结构体// tm结构体包含年、月、日、时、分、秒等字段struct tm *format_time = localtime(&curr_time);// 检查时间转换是否成功if (format_time == nullptr)return "None";// 缓冲区用于存储格式化后的时间字符串char time_buffer[1024];// 格式化时间字符串:年-月-日 时:分:秒snprintf(time_buffer, sizeof(time_buffer), "%d-%02d-%02d %02d:%02d:%02d",format_time->tm_year + 1900, // tm_year: 从1900年开始的年数,需要加1900format_time->tm_mon + 1,     // tm_mon: 月份范围0-11,需要加1得到实际月份format_time->tm_mday,        // tm_mday: 月中的日期(1-31)format_time->tm_hour,        // tm_hour: 小时(0-23)format_time->tm_min,         // tm_min: 分钟(0-59)format_time->tm_sec);        // tm_sec: 秒(0-60,60表示闰秒)return time_buffer; // 返回格式化后的时间字符串
}// 日志函数-->打印出日志
// 格式:时间 + 等级 + PID + 文件名 + 代码行数 + 可变参数
void LogMessage(int level, std::string filename, int line, bool issave, const char *format, ...)
{std::string levelstr = LevelToString(level); // 得到等级字符串std::string timestr = GetTimeString();       // 得到时间字符串pid_t selfid = getpid();                     // 得到PID// 使用va_接口+vsnprintf得到用户想要的可变参数的字符串 存储与buffer中char buffer[1024];va_list arg;va_start(arg, format);vsnprintf(buffer, sizeof(buffer), format, arg);va_end(arg);std::lock_guard<std::mutex> lock(g_mutex); // 引入C++的RAII的锁 保护打印功能// 保存格式为时间 + 等级 + PID + 文件名 + 代码行数 + 可变参数 的日志信息 到message中std::string message = "[" + timestr + "]" + "[" + levelstr + "]" +"[" + std::to_string(selfid) + "]" +"[" + filename + "]" + "[" + std::to_string(line) + "] " + buffer;// 打印到屏幕if (!issave){std::cout << message << std::endl;}// 保存进文件else{SaveFile(logname, message);}
}// 宏定义 省略掉__FILE__  和 __LINE__
#define LOG(level, format, ...)                                                \do                                                                         \{                                                                          \LogMessage(level, __FILE__, __LINE__, gIsSave, format, ##__VA_ARGS__); \} while (0)// 用户调用则意味着保存到文件
#define EnableScreen() (gIsSave = false)// 用户调用则意味着打印到屏幕
#define EnableFile() (gIsSave = true)

解释:日志博客:https://blog.csdn.net/shylyly_/article/details/151263351

七:makefile

.PHONY:all
all:udpserver udpclientudpserver:Main.ccg++ -o $@ $^ -std=c++14
udpclient:UdpClient.ccg++ -o $@ $^ -std=c++14
.PHONY:clean
clean:rm -f udpserver udpclient

八:运行效果

解释:

当我们运行服务端的时候,因为我们添加了日志,所以每次我们插入到哈希表中的单词中文,都会被打印出来,而在右侧客户端,我们就实现了翻译的功能

最后我也准备一份更全面的常用单词文件:

apple: 苹果
banana: 香蕉
cat: 猫
dog: 狗
book: 书
pen: 笔
happy: 快乐的
sad: 悲伤的
run: 跑
jump: 跳
teacher: 老师
student: 学生
car: 汽车
bus: 公交车
love: 爱
hate: 恨
hello: 你好
goodbye: 再见
summer: 夏天
winter: 冬天
water: 水
food: 食物
house: 房子
home: 家
family: 家庭
friend: 朋友
money: 钱
time: 时间
work: 工作
school: 学校
one: 一
two: 二
three: 三
four: 四
five: 五
six: 六
seven: 七
eight: 八
nine: 九
ten: 十
red: 红色
blue: 蓝色
green: 绿色
yellow: 黄色
black: 黑色
white: 白色
orange: 橙色
purple: 紫色
pink: 粉色
brown: 棕色
rice: 米饭
noodle: 面条
bread: 面包
meat: 肉
fish: 鱼
chicken: 鸡肉
egg: 鸡蛋
milk: 牛奶
tea: 茶
coffee: 咖啡
tiger: 老虎
lion: 狮子
elephant: 大象
monkey: 猴子
bird: 鸟
horse: 马
cow: 牛
sheep: 羊
pig: 猪
sun: 太阳
moon: 月亮
star: 星星
rain: 雨
snow: 雪
wind: 风
cloud: 云
weather: 天气
hot: 热的
cold: 冷的
head: 头
eye: 眼睛
ear: 耳朵
nose: 鼻子
mouth: 嘴巴
hand: 手
foot: 脚
heart: 心脏
blood: 血液
bone: 骨头
doctor: 医生
nurse: 护士
engineer: 工程师
driver: 司机
cook: 厨师
farmer: 农民
worker: 工人
manager: 经理
artist: 艺术家
singer: 歌手
angry: 生气的
excited: 兴奋的
tired: 疲倦的
scared: 害怕的
surprised: 惊讶的
calm: 平静的
nervous: 紧张的
proud: 自豪的
shy: 害羞的
curious: 好奇的
walk: 走路
sit: 坐
stand: 站
sleep: 睡觉
eat: 吃
drink: 喝
read: 阅读
write: 写
speak: 说话
listen: 听
day: 天
night: 夜晚
morning: 早上
afternoon: 下午
evening: 晚上
week: 周
month: 月
year: 年
today: 今天
tomorrow: 明天
city: 城市
country: 国家
park: 公园
hospital: 医院
restaurant: 餐厅
hotel: 酒店
store: 商店
bank: 银行
station: 车站
airport: 机场
study: 学习
knowledge: 知识
exam: 考试
question: 问题
answer: 答案
class: 班级
lesson: 课程
homework: 作业
test: 测试
degree: 学位
computer: 电脑
phone: 电话
internet: 互联网
music: 音乐
movie: 电影
game: 游戏
sport: 运动
ball: 球
dance: 舞蹈
photo: 照片
father: 父亲
mother: 母亲
son: 儿子
daughter: 女儿
brother: 兄弟
sister: 姐妹
grandfather: 祖父
grandmother: 祖母
uncle: 叔叔
aunt: 阿姨
big: 大的
small: 小的
tall: 高的
short: 矮的
long: 长的
new: 新的
old: 旧的
young: 年轻的
beautiful: 美丽的
ugly: 丑陋的
rich: 富有的
poor: 贫穷的
strong: 强壮的
weak: 虚弱的
fast: 快的
slow: 慢的
easy: 容易的
difficult: 困难的
right: 正确的
wrong: 错误的
true: 真实的
false: 虚假的
open: 打开
close: 关闭
start: 开始
stop: 停止
buy: 买
sell: 卖
give: 给
take: 拿
help: 帮助
wait: 等待
play: 玩
sing: 唱歌
dance: 跳舞
swim: 游泳
fly: 飞
travel: 旅行
meet: 见面
think: 思考
know: 知道
understand: 理解
remember: 记住
forget: 忘记
live: 生活
die: 死亡
birth: 出生
growth: 成长
change: 改变
stay: 停留
leave: 离开
arrive: 到达
depart: 出发
win: 赢
lose: 输
success: 成功
failure: 失败
hope: 希望
dream: 梦想
plan: 计划
goal: 目标
problem: 问题
solution: 解决方案
reason: 原因
result: 结果
cause: 导致
effect: 影响

解释:彩色是因为是关键字

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/97903.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/97903.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

辉视养老方案:重塑老年生活的温馨与安心

在当今社会&#xff0c;随着老龄化进程的加速&#xff0c;如何为老年人提供更加便捷、舒适且安全的养老环境&#xff0c;成为了全社会共同关注的焦点。辉视养老方案应运而生&#xff0c;它以科技为翼&#xff0c;以关爱为心&#xff0c;通过远程探望、客控系统、信息服务、IPTV…

SQuAD:机器阅读理解领域的里程碑数据集

本文由「大千AI助手」原创发布&#xff0c;专注用真话讲AI&#xff0c;回归技术本质。拒绝神话或妖魔化。搜索「大千AI助手」关注我&#xff0c;一起撕掉过度包装&#xff0c;学习真实的AI技术&#xff01; 1 什么是SQuAD&#xff1f; SQuAD&#xff08;Stanford Question Ans…

【vim,Svelte】怎样使用 vim 编辑 Svelte 那些奇奇怪怪名字的文件?

当你要使用 vim&#xff08;或者neovim&#xff09;来编辑 Svelte 下面的文件时&#xff0c;比如这些文件&#xff1a; page.svelte layout.svelte$ vim page.svelte $ vim "page.svelte" $ vim page.svelte $ vim \page.svelte使用上面的命令&#xff0c;你会遇到这…

深入解析 HTTP 状态码

在日常的网络浏览和 Web 开发过程中&#xff0c;我们总会不可避免地遇到各种 HTTP 状态码。比如常见的 “404 Not Found”&#xff0c;它意味着我们所请求的页面不存在&#xff1b;还有 “500 Internal Server Error”&#xff0c;表示服务器端出现了错误。这些由三位数字组成的…

【C++】C++类和对象—(中)

前言&#xff1a;在上一篇类和对象(上)的文章中我们已经带领大家认识了类的概念&#xff0c;定义以及对类和对象的一些基本操作&#xff0c;接下来我们要逐步进入到类和对象(中)的学习。我们将逐步的介绍类和对象的核心——类和对象的六个默认成员函数。(注意&#xff1a;这六个…

使用python-fastApi框架开发一个学校宿舍管理系统-前后端分离项目

今天给大家分享一个我最近做的一个学校宿舍管理系统&#xff0c;python版&#xff0c;这个系统实现的功能有&#xff1a;首页 | 学校管理 | 宿舍楼管理 | 宿舍管理 | 学生管理 | 学生调宿 | 学生退宿 | 报修等级 | 宿舍卫生评分 | 违纪记录 | 管理员管理 。一共有11个菜单。 使…

阻塞 vs 非阻塞:程序等待的两种哲学

当程序需要等待外部操作时&#xff0c;是应该"干等"还是"边等边干"&#xff1f;为什么有些程序会卡住不动&#xff0c;而另一些却能流畅运行&#xff1f;这一切都取决于阻塞与非阻塞的编程选择&#xff01;本文将为你揭示这两种模式的本质区别&#xff01;…

MySQL 核心操作全解析(用户 + SHOW+DML+DCL)

MySQL 核心操作全解析&#xff08;用户 SHOWDMLDCL&#xff09; 基于你提供的实操笔记&#xff0c;我们将 MySQL 核心操作拆解为用户管理、SHOW 查询命令、DML 数据操作、TRUNCATE 与 DELETE 对比、DCL 权限控制五大模块&#xff0c;梳理语法逻辑、补充避坑提示&#xff0c;帮…

多语言编码Agent解决方案(6)-部署和使用指南

部署和使用指南 本指南提供完整的部署和使用说明&#xff0c;帮助您设置后端服务并在VSCode、Eclipse和IntelliJ中使用相应的插件。这个解决方案基于vLLM提供AI编码辅助&#xff0c;支持英语、中文和日文。 前提条件 操作系统&#xff1a;Linux、macOS或Windows&#xff08;推荐…

滤波器的三重境界:从信号处理到自动驾驶测试的基石

在自动驾驶的宏大叙事中&#xff0c;我们常常聚焦于人工智能、深度学习、高精地图等"明星技术"。然而&#xff0c;在这些耀眼的光环背后&#xff0c;有一个低调却至关重要的"幕后英雄"——滤波器。它不仅是信号处理的工具&#xff0c;更是连接物理世界与数…

Part4.第8章:神经网络

第8章 激活函数 如果没有激活函数&#xff0c;不论几层的神经网络都是一个线性回归。激活函数的作用是引入非线性。

nextjs+shadcn+tailwindcss实现博客中的overview

最近在用nextjsshadcntailwindcss练手&#xff0c;实现一个博客。做到了overView这里&#xff0c;可实现如下效果1.首先要安装tailwindcss&#xff0c;这个在创建项目的时候就安装了。2.然后安装shadcn,官网教程&#xff1a;3.代码如下&#xff1a;import {Card,CardContent } …

Kotlin 高阶语法解析

Kotlin 高级语法深度解析1. 协程&#xff08;Coroutines&#xff09;1.1 基础概念1.挂起和恢复2.协程构建器 (Coroutine Builders)3.协程作用域4.调度器1.2 核心用法1.3 实战示例2. 密封类&#xff08;Sealed Classes&#xff09;2.1 定义与特性2.2 模式匹配2.3 应用场景3. 内联…

9 基于机器学习进行遥感影像参数反演-以随机森林为例

目录 1 读取数据 2 数据预处理 3模型训练 4模型预测 5精度分析 由于回归任务的标签数据获取比较困难,我们这次用水体指数NDWI来模拟作为回归任务的标签,通过随机森林来拟合回归NDWI,其计算公式如下: NDWI = (band3 - band5) / (band3 + band5) 实际情况下需要回归的数…

C++多线程编程:跨线程操作全解析

C中的"线程"通常指单个执行流&#xff08;如std::thread对象&#xff09;&#xff0c;而"多线程"指程序中同时存在多个这样的执行流&#xff0c;并涉及它们的创建、管理和同步。实现跨线程操作的核心在于安全地处理共享数据和线程间通信。 以下是实现跨线程…

【脑电分析系列】第13篇:脑电源定位:从头皮到大脑深处,EEG源定位的原理、算法与可视化

前言脑电信号&#xff08;Electroencephalography, EEG&#xff09;是一种非侵入性的神经成像技术&#xff0c;能够实时捕捉大脑的电活动。然而&#xff0c;头皮上记录到的信号是脑源活动经过头皮、颅骨等介质“模糊”后的投影。想要从这些头皮EEG信号追溯到大脑深处的电活动&a…

MySQL知识笔记

DATE_ADD(date,INTERVAL expr type) date 参数是合法的日期表达式。expr 参数是您希望添加的时间间隔。多查官方手册&#xff01;&#xff01;命令行启动和停止sql服务net start 数据库名&#xff1b; 这是启动服务命令&#xff1b; 例如&#xff1a;net start Mysql56…

2025算法八股——深度学习——MHA MQA GQA

MHA、MQA、GQA 都是深度学习中注意力机制的相关概念&#xff0c;其中 MHA 是标准的多头注意力机制&#xff0c;MQA 和 GQA 则是其优化变体&#xff0c;以下是它们的区别、优缺点介绍&#xff1a;区别MHA&#xff08;多头注意力&#xff09;&#xff1a;是 Transformer 架构的核…

Vue3》》eslint Prettier husky

安装必要的依赖 npm install -D eslint eslint/js vue/eslint-config-prettier prettier eslint-plugin-vue 初始化 ESLint 配置 npm init eslint/config// eslint.config.js // 针对 JavaScript 的 ESLint 配置和规则。保持 JavaScript 代码的一致性和质量 import js from &qu…

Custom SRP - Point and Spot Lights

https://catlikecoding.com/unity/tutorials/custom-srp/point-and-spot-lights/Lights with Limited Influence1 Point Lights1.1 Other Light Data (Point )同方向光一样,我们支持有限数量的 Other Light.尽管场景中可能有很多 Other Lights,可能有超过光源上限的光源时可见的…