Zigbee +PC上位机 无线控制二维云台开发笔记

今日尝试开发一款简单好学的PC上位机无线控制二维云台的小试验品:

主要开发环境与工具介绍:

 单片机 STM32F103C8T6 使用标准库函数编程

 Visual Studio 2022软件C# Winform 开发 上位机控制软件

 DL_20 无线串口模块 + USB-TTL 模块 实现无线通信功能

文章提供完整代码解释、设计点解释、测试效果图、完整工程下载

目录

主要用到的知识如下:

C# Winform上位机的编程:

窗体设计:

Form1初始化:

打开串口 控件函数:

串口接收 控件函数:

串口发送 控件函数:

头部/底部开始移动 控件函数:

一键归位 控件函数:

测试连接 控件函数:

创建日志委托 函数:

清除日志区 控件函数:

注意事项:

C# Winform 整体测试工程下载:

STM32F10xx 单片机的编程:

OLED的驱动显示:

PWM控制舵机运动:

初始化TIM3为舵机控制定时器:

设置TIM3占空比控制舵机运转:

串口接收与串口中断服务函数的编写:

STM32F103C8T6测试工程下载:

测试视频与图片:


主要用到的知识如下:

DL_20无线串口模块_dl20无线串口模块-CSDN博客

C#学习笔记10:winform上位机与西门子PLC网口通信_中篇_winform的窗口操作设计、日志的添加使用_c#网口通信界面-CSDN博客

C# Winform上位机的编程:

窗体设计:

主要用到的控件有Listview、imaginelist、button、checkbox、combobox、label 、serialport

Form1初始化:

       //创建这个窗体的addlog ,需要绑定一个实际方法private AddLog myaddlog;bool Form1_FClosing = false;//用于防止二次Form1_FormClosing()事件发生的string formattedLogMessage; //用于临时拼接字符串bool OPEN_SERIAL_flag = false;//打开串口标志 false:未打开int angle;                  //角度public Form1(){InitializeComponent();this.Load += Form1_Load;myaddlog = this.AddLog;//绑定方法serialPort1.Encoding = Encoding.GetEncoding("GB2312");     //串口接收编码Control.CheckForIllegalCrossThreadCalls = false;}private void Form1_Load(object sender, EventArgs e){设置第一列的宽度=整个宽度 减去 第0页宽度lstInfo.Columns[1].Width = lstInfo.ClientSize.Width - lstInfo.Columns[0].Width;for (int i = 1; i < 10; i++)//初始化串口 号下拉框内容{comboBox4.Items.Add("COM" + i.ToString()); //添加串口}for (int H = 0; H < 5; H++)//初始化串口 波特率下拉框内容{switch (H){case 0: comboBox5.Items.Add("2400"); break;case 1: comboBox5.Items.Add("4800"); break;case 2: comboBox5.Items.Add("9600"); break;case 3: comboBox5.Items.Add("115200"); break;}}//停止位 下拉框内容for (int j = 0; j < 3; j++){switch (j){case 0: comboBox7.Items.Add("1"); break;case 1: comboBox7.Items.Add("1.5"); break;case 2: comboBox7.Items.Add("2"); break;}}comboBox4.Text = "COM1";//端口下拉框初始值comboBox5.Text = "9600";//波特率下拉框初始值comboBox7.Text = "1";//停止位comboBox6.Text = "8";//数据位serialPort1.Close();   //关闭串行端口连接}

打开串口 控件函数:

//打开/关闭串口private void button6_Click(object sender, EventArgs e){if(OPEN_SERIAL_flag==false){try{serialPort1.PortName = comboBox4.Text;//设置端口号serialPort1.BaudRate = Convert.ToInt32(comboBox5.Text);//设置端口波特率serialPort1.StopBits = (StopBits)Convert.ToInt32(comboBox7.Text);//设置停止位serialPort1.DataBits = Convert.ToInt32(comboBox6.Text);//设置数据位serialPort1.ReceivedBytesThreshold = 1;serialPort1.DataReceived += new SerialDataReceivedEventHandler(serialPort1_DataReceived);serialPort1.Open();                   //打开串口OPEN_SERIAL_flag = true;   //标记打开了串口myaddlog(0, "当前串口有设备连接,串口已成功打开");button6.Text = "关闭串口";}catch{myaddlog(1, "错误警告: 端口无设备连接");button6.Text = "打开串口";}}else if(OPEN_SERIAL_flag == true){try{serialPort1.Close(); //关闭串口        myaddlog(0, "已关闭串口 ");OPEN_SERIAL_flag = false;button6.Text = "打开串口";}catch { }} }

串口接收 控件函数:

用到的全局变量:

        string formattedLogMessage; //用于临时拼接字符串
//串口接收//一个接收数据事件获取串口发送来的数据private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e){//处理事件这块可以加上延时确保不定数的数据可以全部收到缓冲后,才去读缓冲内容--单位: 毫秒Thread.Sleep(50);//如果16进制转换没被勾选if (!checkBox1.Checked){myaddlog(0, serialPort1.ReadExisting());myaddlog(0, "串口消息接收回传:");}//如果16进制转换被勾选了else{try{//定义缓冲区数组大小为串口缓冲区数据的字节数//因为串口事件触发时有可能收到不止一个字节byte[] data = new byte[serialPort1.BytesToRead];serialPort1.Read(data, 0, data.Length);foreach (byte Member in data)  //遍历用法{string str = Convert.ToString(Member, 16).ToUpper();formattedLogMessage = string.Format("0x" + (str.Length == 1 ? "0" + str : str) + " ");myaddlog(0, formattedLogMessage);}myaddlog(0, "串口消息接收回传:");}catch { }}}

串口发送 控件函数:

//串口测试发送:private void button7_Click(object sender, EventArgs e){byte[] Data = new byte[1];                                                         //单字节发数据     if (serialPort1.IsOpen){if (textBox1.Text != ""){//如果不是16进制发送,就直接string形式发送if (!checkBox2.Checked){try{serialPort1.Write(textBox1.Text);myaddlog(0, "单条发送成功");//serialPort1.WriteLine();                             //字符串写入}catch{myaddlog(1, "串口数据写入错误");}}else                                                                    //数据模式{try                                                                 //如果此时用户输入字符串中含有非法字符(字母,汉字,符号等等,try,catch块可以捕捉并提示){for (int i = 0; i < (textBox1.Text.Length - textBox1.Text.Length % 2) / 2; i++)//转换偶数个{Data[0] = Convert.ToByte(textBox1.Text.Substring(i * 2, 2), 16);           //转换serialPort1.Write(Data, 0, 1);}if (textBox1.Text.Length % 2 != 0){//单独处理最后一个字符Data[0] = Convert.ToByte(textBox1.Text.Substring(textBox1.Text.Length - 1, 1), 16);serialPort1.Write(Data, 0, 1);//写入}//Data = Convert.ToByte(textBox2.Text.Substring(textBox2.Text.Length - 1, 1), 16);myaddlog(0, "单条发送成功");}catch{myaddlog(1, "数据转换错误,请输入数字。");}}}}}

头部/底部开始移动 控件函数:

       //头部开始移动private void button1_Click(object sender, EventArgs e){bool success;//用于检查文本框textbox输入规范用//先检查串口是否打开if (serialPort1.IsOpen == false){myaddlog(1, "无法发送内容,请检查 串口是否打开!");}else{//尝试转换 textBox2 角度的输入数值,看是否失败success = int.TryParse(textBox2.Text.Trim(), out angle);if (success == false && serialPort1.IsOpen){myaddlog(1, "无法将框中内容转换,请检查 设定移动角度 的输入。");}else{if (Headleft.Checked && Headright.Checked){myaddlog(1, "错误!头部移动方向 不可多选!");}else if (Headleft.Checked == false && Headright.Checked == false){myaddlog(1, "错误!头部移动方向 并未选择!");}else if (Headleft.Checked == true && Headright.Checked == false){//此处添加串口发送数据:formattedLogMessage = string.Format("HL{0}&", textBox2.Text);serialPort1.Write(formattedLogMessage);formattedLogMessage = string.Format("已发送头部 移动方向为左 角度为{0}", textBox2.Text);myaddlog(0, formattedLogMessage);}else if (Headleft.Checked == false && Headright.Checked == true){//此处添加串口发送数据:formattedLogMessage = string.Format("HR{0}&", textBox2.Text);serialPort1.Write(formattedLogMessage);formattedLogMessage = string.Format("已发送头部 移动方向为右 角度为{0}", textBox2.Text);myaddlog(0, formattedLogMessage);}}}}//底座开始移动private void button2_Click(object sender, EventArgs e){bool success;//用于检查文本框textbox输入规范用//先检查串口是否打开if (serialPort1.IsOpen == false){myaddlog(1, "无法发送内容,请检查 串口是否打开!");}else{//尝试转换 textBox3 角度的输入数值,看是否失败success = int.TryParse(textBox3.Text.Trim(), out angle);if (success == false){myaddlog(1, "无法将框中内容转换,请检查 设定移动角度 的输入。");}else{if (Buttomleft.Checked && Buttomright.Checked){myaddlog(1, "错误!底部移动方向 不可多选!");}else if (Buttomleft.Checked == false && Buttomright.Checked == false){myaddlog(1, "错误!底部移动方向 并未选择!");}else if (Buttomleft.Checked == true && Buttomright.Checked == false){//此处添加串口发送数据:formattedLogMessage = string.Format("BL{0}&", textBox3.Text);serialPort1.Write(formattedLogMessage);formattedLogMessage = string.Format("已发送底座 移动方向为左 角度为{0}", textBox3.Text);myaddlog(0, formattedLogMessage);}else if (Buttomleft.Checked == false && Buttomright.Checked == true){//此处添加串口发送数据:formattedLogMessage = string.Format("BR{0}&", textBox2.Text);serialPort1.Write(formattedLogMessage);formattedLogMessage = string.Format("已发送底座 移动方向为右 角度为{0}", textBox3.Text);myaddlog(0, formattedLogMessage);}}}}

一键归位 控件函数:

       //一键归位private void button3_Click(object sender, EventArgs e){if (serialPort1.IsOpen){formattedLogMessage = "RS&";serialPort1.Write(formattedLogMessage);myaddlog(0, "已发送归位测试字符串RS");}else{myaddlog(1, "无法发送内容,请检查 串口是否打开!");}}

测试连接 控件函数:

       //测试连接private void button4_Click(object sender, EventArgs e){if (serialPort1.IsOpen){formattedLogMessage = "TEST&";serialPort1.Write(formattedLogMessage);myaddlog(0, "已发送测试字符串TEST");}else{myaddlog(1, "无法发送内容,请检查 串口是否打开!");}}

创建日志委托 函数:

 创建委托函数需要放置的位置:

    //info 表示报警级别 ,log 表示报警信息public delegate void AddLog(int info, string log);
       //写入日志委托方法//创建委托private void AddLog(int info, string Log){if (!lstInfo.InvokeRequired){//创建ListViewItem ,将时间与info放进去ListViewItem lst = new ListViewItem("   " + DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"), info);lst.SubItems.Add(Log);lstInfo.Items.Insert(0, lst);}else{Invoke(new Action(() =>{ListViewItem lst = new ListViewItem("   " + DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"), info);lst.SubItems.Add(Log);lstInfo.Items.Insert(0, lst);}));}}

清除日志区 控件函数:

        //清除日志区private void button8_Click(object sender, EventArgs e){lstInfo.Items.Clear();         //清除日志listview 的内容MessageBox.Show("已成功清除日志区", "清除接收区");}

注意事项:

1、要检查各个控件操作可能出现的错误连接的情况:串口未打开、字符输入非法等,并设置报错日志

2、日志委托写入listview控件,别忘了编辑列

3、

 

C# Winform 整体测试工程下载:

https://download.csdn.net/download/qq_64257614/89368716?spm=1001.2014.3001.5503

STM32F10xx 单片机的编程:

OLED的驱动显示:

有关于OLED的驱动就不多赘述,这里只介绍在哪里刷新了哪些显存,具体配置是有关IIC通信的相关文章贴出如下:

STM32 F103C8T6学习笔记9:0.96寸单色OLED显示屏—自由取模显示—显示汉字与图片_stm32f103c8t6 oled显示文字-CSDN博客

STM32 F103C8T6学习笔记11:RTC实时时钟—OLED手表日历_stm32f103c8t6显示实时时间-CSDN博客

STM32 F103C8T6学习笔记16:1.3寸OLED的驱动显示日历-CSDN博客

PWM控制舵机运动:

初始化TIM3为舵机控制定时器:

        底座舵机:     Signal: PA7
        头部舵机:     Signal: PA6 

设置TIM3占空比控制舵机运转:

这里为了防止舵机运转过快出问题,我使用定时器控制其占空比更新频率不过快,并对占空比输出限幅:然后再主函数调用TIM_SetCompare X();函数来落实占空比的设置:


//通用定时器 定时器1 中断服务函数
void TIM1_UP_IRQHandler(void)
{if (TIM_GetITStatus(TIM1, TIM_IT_Update) == SET){	if(++t==70)		  //每70ms设置一次舵机占空比{t=0;t1=MIDDLE + t1_receive;if(t1>=260) {t1=260;}if(t1<150)  {t1=150;}t2=MIDDLE + t2_receive;			if(t2>=260) {t2=260;}if(t2<150)  {t2=150;}}TIM_ClearITPendingBit(TIM1, TIM_IT_Update);//清出中断寄存器标志位,用于退出中断}
}

串口接收与串口中断服务函数的编写:

这部分的设计有些麻烦,串口接收是一件比较麻烦的事,

这里为了开发迅速,就不自己编写 状态机+结构体 这样比较规范的串口接收方式了,

我选择了简单的 定义接收buff[]数组缓冲区+末尾接收字符检验 的方式进行串口接收校验了,这种方式好编程,但缺点也很多很明显!

定义的诸多变量如下:


int t,t1,t2,t1_receive,t2_receive; //辅助配置占空比
int Receive[20]; //提取 串口接收数组 字符串里的 所有数字 
int temp_Receive;//定义串口程序需要用到的变量
char USART0_save[20];  //存字符串命令的数组
char USART0_xb=0;			 //帮助数组下标位移
char USART0_flag=0;    //接收完成标志//定义命令字符串,用于与接收进行比较 ,不可修改
const char str1_order[]="TEST&"; //测试命令
const char str2_order[]="RS&";   //归位命令//定义响应字符串,用于响应不同的命令
char str1_receive[]="Cotact OVER";
char str2_receive[]="NTM: Hello,STM32F1xx !";
char error_receive[]="ERROR CMD!";

串口中断服务函数:

//串口1中断服务函数 
void USART1_IRQHandler(void)
{if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断{USART_ClearFlag(USART1, USART_FLAG_RXNE);}USART0_save[USART0_xb++]=USART_ReceiveData(USART1);if(USART0_xb== 20){USART0_xb=0;	}							      //下标最大不超过20if(USART0_save[USART0_xb-1]=='&') {USART0_flag=1;}  //命令以&结尾}

串口接收buff[]缓冲处理函数:

void Handle_Uart_Receive(void)
{int i;if(USART0_flag==1){printf("STM32 confirm Receive : %s",USART0_save);            //先重复接受到的字符串//先判断命令长度,再根据其判断是否为接受到的命令字符串,根据情况发送不同回应if(strncmp(USART0_save,str1_order,5)==0) printf("%s",str1_receive);else if(strncmp(USART0_save,str2_order,3)==0) {t1_receive=0;t2_receive=0;printf("%s",str2_receive);}//如果是头部舵机转动的命令头	if(USART0_save[0]=='H'){extractDigitsFromStringArray();//提取 USART0_save 接收数组中的数字								//循环拼接提取到的每个数字for (i = 0; i < USART0_xb -3; i++) {  temp_Receive = temp_Receive*10+Receive[i];  }//判断向左向右if(USART0_save[1]=='L'){t1_receive=0-temp_Receive;}if(USART0_save[1]=='R'){t1_receive=0+temp_Receive;}}//如果是底部舵机转动的命令头	if(USART0_save[0]=='B'){extractDigitsFromStringArray(); //提取 USART0_save 接收数组中的数字//循环拼接提取到的每个数字for (i = 0; i < USART0_xb -3; i++) {  temp_Receive = temp_Receive*10+Receive[i];  }//判断向左向右if(USART0_save[1]=='L'){t2_receive=0+temp_Receive;}if(USART0_save[1]=='R'){t2_receive=0-temp_Receive;}								}}	memset(USART0_save,0,sizeof(USART0_save)); //处理完命令别忘了将数组清零,以便接收下个命令temp_Receive=0;USART0_xb=0;                               //重置数组下标	USART0_flag=0;								             //清理标志位
}

缓冲处理辅助函数:

这是一些缓冲处理的辅助函数,主要是C语言的基础,对数据类型的处理判断:其中一些基础函数需要添加头文件

#include <ctype.h>   
#include <string.h>  
#include <stdbool.h>  

//从一个数组中提取数字到另一个数组
void extractDigitsFromStringArray(void)
{int i=0,j=0;for(i=0;i<=sizeof(USART0_save);i++){if(isStringNumeric(USART0_save[i])==true){Receive[j]=USART0_save[i] - '0';j++;}}
}// 辅助函数:检查字符是否是数字  
bool isStringNumeric(char str)
{  if (str == NULL || str == '\0') {  // 空字符或NULL指针,不是数字return false;  }  if (!isdigit((unsigned char)str)) {  //发现非数字字符,则返回false  return false;  }  //字符是数字 return true;  
}

STM32F103C8T6测试工程下载:

https://download.csdn.net/download/qq_64257614/89368723?spm=1001.2014.3001.5503

测试视频与图片:

本次小试验品开发前后总共耗时不到俩天,按小时计算的话就少于一天了......

Zigbee +PC上位机 无线控制二维云台开发

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

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

相关文章

留给“端侧大模型”的时间不多了

端侧大模型&#xff08;Edge AI models&#xff09;&#xff0c;也就是只在设备本地&#xff08;如智能手机、IoT设备、嵌入式系统等&#xff09;运行的大模型&#xff0c;过去一两年来非常流行。 具体表现在&#xff0c;终端设备厂商&#xff0c;如苹果、荣耀、小米、OV等&…

android手机安装tfcenter软件(使用termux非root模式)

1、下载termux app软件并安装 Termux官方地址&#xff1a; Termux | The main termux site and help pages. 2、打开Termux并下载tfcenter install.sh脚本 执行curl命令&#xff1a; curl -sS http://47.122.18.150:10000/install.sh -o install.sh 3、通过install.sh 输入…

springboot基本使用十二(PageHelper分页查询)

引入依赖&#xff1a; <dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>1.3.0</version> </dependency> 通个PageHelper.startPage(page,pageSize)方…

“仿RabbitMQ实现消息队列”---整体架构与模块说明

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 一、概念性框架理解 我们主要实现的内容&#xff1a; 1.Broker服务器&#xff1a;消息队列服务器&#xff08;服务端&…

连通块中点的数量-java

本次我们通过连通块中点的数量来加深我们对并查集的基本操作和原理&#xff0c;并且知道如何在并查集中添加附属信息。 目录 前言☀ 一、连通块中点的数量☀ 二、算法思路☀ 1.无向图&#x1f319; 2.在a b之间连一条边&#xff0c;a b可能相等&#x1f319; 3.询问a和b是否在一…

Apache POI对Excel进行读写操作

1、什么是Apache POI ​ Apache POI 是一个处理Miscrosoft Office各种文件格式的开源项目。简单来说就是&#xff0c;我们可以使用 POI 在 Java 程序中对Miscrosoft Office各种文件进行读写操作。一般情况下&#xff0c;POI 都是用于操作 Excel 文件。 Apache POI 的应用场景&…

【为什么 Google Chrome 打开网页有时极慢?尤其是国内网站,如知网等】

要通过知网搜一点资料&#xff0c;发现怎么都打不开。而且B站&#xff0c;知乎这些速度也变慢了&#xff01;已经检查过确定不是网络的问题。 清空了记录&#xff0c;清空了已接受Cookie&#xff0c;清空了缓存内容……没用&#xff01;&#xff01;&#xff01; 不断搜索&am…

注意力机制详解

引言 在阅读一篇文章时&#xff0c;我们的大脑并不平等地处理每一个字词&#xff0c;而是根据上下文自动筛选出核心信息进行深入理解。注意力机制正是借鉴了这一生物学灵感&#xff0c;使得机器学习模型能够动态地分配其“注意力”资源&#xff0c;针对不同的输入部分赋予不同…

大数据信用报告分析和评估有什么意义

大数据信用这个词在现在已经是很常见的了&#xff0c;只要是申贷的朋友对它就不陌生&#xff0c;在明面上的信用资质刚刚满足审核要求&#xff0c;但又要把控风险的时候&#xff0c;这个时候大数据信用就会作为风控机构交叉核查的重要依据。那你知道大数据信用报告分析和评估有…

代码随想录——二叉搜索树的最小绝对差(Leetcode530)

题目链接 层序遍历 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode left, TreeNode right) …

if constexpr实现条件编译

#include <iostream>// 利用if constexpr实现了条件编译 template<typename T1, typename T2> void test_func() {if constexpr (std::is_same_v<T1, T2>) {std::cout << "hit stage\n";} else {std::cout << "miss\n";} }i…

Microsoft的Copilot现已登陆Telegram

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

数据结构复习指导之插入排序

文章目录 排序 考纲内容 知识框架 复习提示 1.排序的基本概念 1.1排序的定义 2.插入排序 2.1直接插入排序 2.2折半插入排序 2.3希尔排序 知识回顾 排序 考纲内容 &#xff08;一&#xff09;排序的基本概念 &#xff08;二&#xff09;插入排序 直接插…

内网不能访问域名怎么办?

在网络应用中&#xff0c;我们常常遇到内网不能访问域名的问题。这是由于内网环境限制导致的&#xff0c;内网无法直接连接到公网&#xff0c;因而无法访问互联网上的域名。我们可以利用一些特殊技术和工具来解决这个问题。 天联组网技术的应用 天联组网是一种非常受欢迎的解决…

NetApp财季报告亮点:全闪存阵列需求强劲,云计算收入增长放缓但AI领域前景乐观

在最新的财季报告中&#xff0c;NetApp的收入因全闪存阵列的强劲需求而显著增长。截至2024年4月26日的2024财年第四季度&#xff0c;NetApp的收入连续第三个季度上升&#xff0c;达到了16.7亿美元&#xff0c;较前一年同期增长6%&#xff0c;超出公司指导中值。净利润为2.91亿美…

前端开发:$nextTick()的使用及原理

目录 前言 $nextTick()的概念 $nextTick()的用法和原理 1、$nextTick()用法 2、$nextTick()原理 $nextTick()的具体使用示例 拓展&#xff1a;面试中考察$nextTick()的底层原理 最后 前言 在前端开发中&#xff0c;涉及到JS原生的使用原理是非常重要的知识点&#xff0…

使用pytorch搭建textCNN、BERT、transformer进行文本分类

首先展示数据处理后的类型&#xff1a; 第一列为文本&#xff0c;第二类为标注的标签&#xff0c;数据保存在xlsx的表格中&#xff0c;分为训练集和验证集。 textCNN 直接上整个工程代码&#xff1a; import pandas as pd import numpy as np import torch from torch.util…

c++函数基础总结

在给出的代码片段中&#xff0c;我们看到两部分内容&#xff1a;一个类定义和一个全局函数声明。让我们逐一分析它们&#xff1a; 类定义&#xff1a; cpp复制代码 class { public: void a(); }; 这个类定义是不完整的&#xff0c;因为它没有类名。但为了说明&#xff0c;我…

Linux开发工具(个人使用)

Linux开发工具 1.Linux yum软件包管理器1.1Linux安装程序有三种方式1.2注意事项1.3如何查看&#xff0c;安装&#xff0c;卸载软件包1.3.1查看软件包1.3.2安装软件包1.3.3卸载软件 2.Linux vim编辑器2.1vim的基本操作2.2vim正常模式命令集2.3vim底行模式命令集2.4vim配置 3.Lin…

如何设置eclipse中web.xml 文件的地址

新学了一个项目 &#xff0c;项目结构与平常自己构建的web项目不同 &#xff0c;用eclipse打开之后&#xff0c;eclipse竟然自己创建了一个web.xml 而项目里面原本的web.xml 文件eclipse没有识别出来&#xff0c;导致后来浏览器访问任何路径都报错404 一、修改项目中web.xml的…