测绘程序设计(最强保姆级教程)

前言

#include<iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <map>
#include <cmath>
#define PI 3.14
using namespace std;

// 将度分秒转十进制度
double convertDMStoDD(string dms) {
    istringstream iss(dms);
    double degrees, minutes, seconds;
    char ch;
    iss >> degrees >> ch >> minutes >> ch >> seconds;
    return degrees + minutes / 60.0 + seconds / 3600.0;
}

// 读取文件数据
bool readFileData(const string& filepath, map<string, double>& elevationMap, vector<vector<string>>& data) {
    ifstream file(filepath);
    if (!file.is_open()) {
        cerr << "文件无法打开" << endl;
        return false;
    }

    string line;
    // 读取第一行
    if (getline(file, line)) {
        istringstream iss(line);
        string point;
        double elevation;
        while (iss >> point >> elevation) {
            elevationMap[point] = elevation;
        }
    }

    // 读取剩余的行
    while (getline(file, line)) {
        vector<string> row;
        istringstream iss(line);
        string value;

        while (iss >> value) {
            row.push_back(value);
        }

        data.push_back(row);
    }

    file.close();
    return true;
}

// 计算高程
void calculateElevations(map<string, double>& elevationMap, vector<vector<string>>& data) {
    for (auto& row : data) {
        string from_point = row[0], to_point = row[1];
        double instrument_height = stod(row[2]);
        double target_height = stod(row[3]);
        double distance = stod(row[4]);
        double angle = convertDMStoDD(row[5]);

        double elevation_diff = distance * tan(angle * PI / 180.0);
        double new_elevation = elevationMap[from_point] + elevation_diff + instrument_height - target_height;

        //创建新列高程
        elevationMap[to_point] = new_elevation;
        row.push_back(to_string(new_elevation));
    }
}

// 计算闭合差
double calculateClosureError(const vector<vector<string>>& data, const map<string, double>& elevationMap) {
    string start_point = data[0][0];
    string end_point = data.back()[1];
    double start_elevation = elevationMap.find(start_point)->second;
    double end_elevation = elevationMap.find(end_point)->second;
    double closure_error = end_elevation - start_elevation;
    return closure_error;
}

// 分配闭合差
void distributeClosureError(vector<vector<string>>& data, double closure_error) {
    double total_distance = 0.0;
    for (const auto& row : data) {
        total_distance += stod(row[4]);
    }

    for (auto& row : data) {
        double distance = stod(row[4]);
        double correction = -closure_error * distance / total_distance;
        double new_elevation = stod(row[6]) + correction;
        row.push_back(to_string(new_elevation)); // 添加修正后的高程到行中
    }
}

// 检验闭合差分配的正确性
bool verifyClosureErrorDistribution(const vector<vector<string>>& data, double closure_error) {
    double sum_of_corrections = 0.0;
    for (const auto& row : data) {
        sum_of_corrections += stod(row[7]) - stod(row[6]); // 计算每个测段的改正数
    }

    // 检验闭合差分配后的总和是否等于负的闭合差
    return fabs(sum_of_corrections + closure_error) < 1e-6;
}

int main() {
    string filename = "数据.txt";
    string filepath = "C:/Users/Administrator/Desktop/20240413程序选拔/数据.txt";

    map<string, double> elevationMap; //点号和对应的高程
    vector<vector<string>> data; // 从文件中读取的数据

    // 读取文件数据
    if (!readFileData(filepath, elevationMap, data)) {
        return 1;
    }

    // 计算高程
    calculateElevations(elevationMap, data);

    // 计算闭合差
    double closure_error = calculateClosureError(data, elevationMap);
    cout << "闭合差: " << closure_error << endl;

    // 分配闭合差
    distributeClosureError(data, closure_error);

    // 检验闭合差分配的正确性
    bool verify = verifyClosureErrorDistribution(data, closure_error);
    cout << "闭合差分配正确性: " << (verify ? "通过" : "未通过") << endl;

    // 打印分配闭合差后的高程
    for (const auto& row : data) {
        for (const auto& val : row) {
            cout << val << " ";
        }
        cout << endl;
    }

    return 0;
}

1.c#初上手

2024.04.22,介于有C++基础,直接上手,不看教程,先试试语法,对着我的代码

先写个简单的试试,前几天建议像我一样,慢慢研究,一天研究gui和代码,一天复现,一个项目分成两三天来做。我也会标出日期,下面是我在这天做了什么。大家没时间就按自己节奏来。

1.控制台项目

这个项目涵盖了C#的基础语法,包括类、方法、异常处理、循环、条件语句和输入输出。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace project_4_22
{
    //计算阶乘的方法
    class Factorial
    {
        public static long Calculate(int number)
        {
            if (number < 0)
            {
                throw new ArgumentException("数字必须为非负数");
                //在C#中,throw关键字用于抛出一个异常。当一个异常被抛出时,正常的程序执行流程会被中断,并且程序的控制权会转移到最近的异常处理代码(try-catch块)。
            }

            long result = 1;
            for(int i = 1; i <= number; i++)
            {
                result *= i;
            }

            return result;
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            //相当于cout
            Console.WriteLine("欢迎使用阶乘计算器:");

            while (true)
            {
                //相当于cin
                Console.WriteLine("请输入一个非负整数(输入-1退出):");

                string input = Console.ReadLine();
                /*int.TryParse 是 C# 中 int 类型的一个静态方法,它用于尝试将字符串解析为整数类型 int。
                 这个方法非常有用,因为它提供了非破坏性的解析方式,即在解析失败时不会抛出异常。
                int.TryParse 方法接受两个参数:
                        input:要解析的字符串。
                        out number:一个 out 参数,用于接收解析后的整数结果。*/
                if(int.TryParse(input,out int number))
                {
                    if (number == -1)
                    {
                        break;
                    }
                    else if (number < 0)
                    {
                        Console.WriteLine("错误:请输入非负整数。");
                    }
                    else
                    {
                        /*这段代码是一个try-catch块,它在C#中用于异常处理。
                         * 在这个特定的例子中,它用于调用Factorial类的Calculate方法,这个方法计算一个整数的阶乘。
                         * 如果Calculate方法在执行过程中抛出了一个ArgumentException异常,
                         * catch块将会捕获这个异常,并打印出异常的消息。*/
                        try
                        {
                            //从类中调用函数
                            long factorial = Factorial.Calculate(number);
                            Console.WriteLine($"{number}!={factorial}");
                        }
                        catch(ArgumentException ex)
                        {
                            Console.WriteLine(ex.Message);
                        }
                    }
                }
                else
                {
                    Console.WriteLine("错误,请输入有效整数");
                }
            }
            Console.WriteLine("感谢使用阶乘计算器!按任意键退出。");
            Console.ReadKey();
        }
    }
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

做完这个试试gui的,给不懂得同学解释下,gui就是将黑框中的东西做个漂亮的界面展现出来,类似小程序。

2.gui项目

不同的是需要创建新的项目,而且,需要winform,有xml,css的东西反正不影响应该,先别管,(2024.04.29补充,后面证实就是没必要管,但是我觉的以后要深研究的的话还是要学SQL,我一个月速成的三级数据库寄掉了嘤嘤嘤)不知道看这个的有没有了解过gui,先别管,学完一个点一个点,最后能制成一张网,按下面。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这里比较简单,就不截屏了,从右边拖过来 b u t t o n button button t e x t b o x textbox textbox,右键属性改下名字,还是有图吧,按钮叫显示时间,文本框叫 t i m e F i l e d timeFiled timeFiled,专业一点,这里大家不要有个误区,这不像你们学的 M A T L A B MATLAB MATLAB中的 g u i gui gui是通过句柄来操控,只需要讲底层逻辑代码改改就行,我这么说你们可能不太明白,先不用管,下面的学生管理系统就很简洁直接。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

还是按钮,点属性,然后看见闪电没,点那个,然后创建一个动作,就这双击就行,他就会跳转到这里,那么我们在button中添加代码,见下面完整代码

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

然后Form1.cs文件代码是这样

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace form0422
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();//这是控件拖进来就自动生成
        }

        private void button1_Click(object sender, EventArgs e)
        {
            string timestr = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");//获取当前时间
            this.timeField.Text = timestr;//显示出来
        }
    }
}

简单吧,这里的细节就是,编译器自动给我们生成刚刚操作的代码,在Form1下属文件中

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

运行试试,我就不截图了,可以直接显示时间了,现在已经学会了控制台和gui,让我们上手试试,写个这个试试

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

开个玩笑,我也写不出来目前,先剖析下竞赛开源的代码,我分享出来,细研究。

3.测绘程序设计

单看好像很难,目前看实际就是很难

2024.4.23

我到最后会把所有文件发出来的

2.学生管理系统

只是写个阶乘的小程序对于了解语法来说还是太过牵强,我们来写个最经典的学生管理系统试试,这里就包括简单类的定义了。

1.控制台

using System;
using System.Collections.Generic;
using System.IO;

namespace StudentManagementSystem
{
    class Program
    {
        static void Main(string[] args)
        {
            List<Student> students = new List<Student>();

            // 欢迎界面
            Console.WriteLine("欢迎使用学生管理系统");

            while (true)
            {
                Console.WriteLine("\n请选择一个操作:");
                Console.WriteLine("1. 添加学生");
                Console.WriteLine("2. 显示所有学生");
                Console.WriteLine("3. 保存到文件");
                Console.WriteLine("4. 从文件加载");
                Console.WriteLine("5. 修改学生信息");
                Console.WriteLine("6. 删除学生信息");
                Console.WriteLine("7. 退出");

                string choice = Console.ReadLine();

                switch (choice)
                {
                    case "1":
                        AddStudent(students);
                        break;
                    case "2":
                        DisplayStudents(students);
                        break;
                    case "3":
                        SaveStudentsToFile(students);
                        break;
                    case "4":
                        LoadStudentsFromFile(students);
                        break;
                    case "5":
                        EditStudentInfo(students);
                        break;
                    case "6":
                        DeleteStudent(students);
                        break;
                    case "7":
                        return;
                    default:
                        Console.WriteLine("无效的选择,请重新输入。");
                        break;
                }
            }
        }

        static void AddStudent(List<Student> students)
        {
            Console.WriteLine("请输入学生信息:");
            Console.Write("学号:");
            string id = Console.ReadLine();
            Console.Write("姓名:");
            string name = Console.ReadLine();
            Console.Write("性别(输入'男'或'女'):");
            string gender = Console.ReadLine();
            Console.Write("手机号:");
            string phoneNumber = Console.ReadLine();

            Student newStudent = new Student { Id = id, Name = name, Gender = gender, PhoneNumber = phoneNumber };
            students.Add(newStudent);
            Console.WriteLine("学生信息已添加");
        }

        static void DisplayStudents(List<Student> students)
        {
            Console.WriteLine("\n学生列表:");
            for (int i = 0; i < students.Count; i++)
            {
                Console.WriteLine($"学号:{students[i].Id}, 姓名:{students[i].Name}, 性别:{students[i].Gender}, 手机号:{students[i].PhoneNumber}");
            }
        }

        static void SaveStudentsToFile(List<Student> students)
        {
            using (StreamWriter sw = new StreamWriter("students.txt"))
            {
                foreach (Student student in students)
                {
                    sw.WriteLine($"{student.Id},{student.Name},{student.Gender},{student.PhoneNumber}");
                }
            }
            Console.WriteLine("学生信息已保存到文件。");
        }

        static void LoadStudentsFromFile(List<Student> students)
        {
            students.Clear();
            if (File.Exists("students.txt"))
            {
                using (StreamReader sr = new StreamReader("students.txt"))
                {
                    string line;
                    while ((line = sr.ReadLine()) != null)
                    {
                        string[] parts = line.Split(',');
                        Student student = new Student
                        {
                            Id = parts[0],
                            Name = parts[1],
                            Gender = parts[2],
                            PhoneNumber = parts[3]
                        };
                        students.Add(student);
                    }
                }
                Console.WriteLine("学生信息已从文件加载。");
            }
            else
            {
                Console.WriteLine("文件不存在,无法加载学生信息。");
            }
        }

        static void EditStudentInfo(List<Student> students)
        {
            Console.Write("请输入要修改的学生学号:");
            string id = Console.ReadLine();

            Student studentToEdit = students.Find(s => s.Id == id);

            if (studentToEdit != null)
            {
                Console.WriteLine("请输入新的学生信息:");
                Console.Write("姓名(留空保持不变):");
                string name = Console.ReadLine();
                Console.Write("性别(留空保持不变):");
                string gender = Console.ReadLine();
                Console.Write("手机号(留空保持不变):");
                string phoneNumber = Console.ReadLine();

                if (!string.IsNullOrWhiteSpace(name))
                {
                    studentToEdit.Name = name;
                }
                if (!string.IsNullOrWhiteSpace(gender))
                {
                    studentToEdit.Gender = gender;
                }
                if (!string.IsNullOrWhiteSpace(phoneNumber))
                {
                    studentToEdit.PhoneNumber = phoneNumber;
                }

                Console.WriteLine("学生信息已更新。");
            }
            else
            {
                Console.WriteLine("未找到该学号的学生。");
            }
        }

        static void DeleteStudent(List<Student> students)
        {
            Console.Write("请输入要删除的学生学号:");
            string id = Console.ReadLine();

            Student studentToDelete = students.Find(s => s.Id == id);

            if (studentToDelete != null)
            {
                students.Remove(studentToDelete);
                Console.WriteLine("学生信息已删除。");
            }
            else
            {
                Console.WriteLine("未找到该学号的学生。");
            }
        }
    }

    class Student
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public string Gender { get; set; }
        public string PhoneNumber { get; set; }
    }
}

很简单明了的程序,熟悉下语法就行,以后练得机会多着呢。

2.gui

2024.4.24做个gui试试

拖进来的控件就很简单,大家根据Form.Design.cs文件中的试着自己找找。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

部分代码,完整文件可以在资源里面找

using Af.Common;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;



namespace studentsManagerSystem
{ 
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent(); // 初始化窗体组件

            // 向性别下拉列表框中添加性别选项
            sexField.Items.Add("女");
            sexField.Items.Add("男");

        }

        private void Form1_Load(object sender, EventArgs e)
        {

        }

        private void label1_Click(object sender, EventArgs e)
        {

        }

        //保存按钮 
        private void saveButton_Click(object sender, EventArgs e)
        {
            //创建一个新的Student对象
            Student stu = new Student();
            //从学号文件框获取并设置学生学号
            stu.Id = Convert.ToInt32(idField.Text.Trim());
            //从姓名文件框获取并设置学生姓名
            stu.Name = nameField.Text.Trim();
            //性别下拉框选择性别
            stu.Sex = (sexField.SelectedIndex == 1);
            //从手机号文件框获取并设置学生手机号
            stu.Phone = phoneField.Text.Trim();

            //将Student对象转JSON字符串
            string jsonStr = JsonConvert.SerializeObject(stu, Formatting.Indented);
            //保存
            AfTextFile.Write("student.txt", jsonStr, AfTextFile.UTF8);
            MessageBox.Show("操作成功");
        }
    }
}

到今天也是把winform教程浅浅全部看完了

2024.4.25,今天分析下代码,感觉还蛮简单,明天周五一节课试着自己写写试试,大家也尽量自己写,控制在2h内。

3.出租车

(程序设计书的第一题)

这个补充一下,建议先看第二题,我也是研究之后才发现第二题比第一题简单

2024.04.30补

原来源代码中的设计器如果没有的话,可以自己创建个项目,复制Form1中的代码就可以显示了.

1.源程序说明

写代码不要上来就写,可以画个架构,先把原理看懂,也方便优化

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

参考源程序编程语言为C#,项目名称为TaxiData。项目中主要包含以下类:

( 1 ) FileHelper :源文件读取,以及计算结果输出;
( 2 )Epoch.cs:基本数据结构,包含车辆标识、运营状态、时间、x坐标分量、y坐标分量等信息;
( 3 )Algo.cs:时间转化为简化儒略日算法;
( 4 ) Session. cs:计算每个时段的长度、速度和方位角;
( 5 )SessionList. cs:输出所有时段的速度、方位角、累计距离和首尾直线距离。

看原理的话,就是选择出出勤的出租车,两个相邻点算出角和距离,然后不停地迭代,取得的和就是累计距离,第一个和最后一个点就可以算首尾直线距离,就是位移。

2.gui制作

先拖进来toolstrip,然后从源码中把图片复制添加到资源那个文件中Resources,然后即可以做界面了,别忘了添加富文本框

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

还有Text都要改,设置成图片加文字的,然后点击闪电,设置动作,这点因为不熟,整了好长时间(苦笑)

别急着设置按钮动作,目前我研究的是先写底层代码比较好,可以将逻辑画出来

3.code

最先做的就是这个日转化

Algo.cs

这见多了就知道是algorithm算法的简称,还是很好理解的,里面定义这个儒略日算法MJD,也没啥好说的,就是对着公式敲,还是用GPT加个解释吧,别忘了时间差,人家也提醒了

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

//源代码
        public static double Mjd(int year, int month, int day, int hour, int min, int sec, int timeZone)
        {
            double mjd = -678987 + 367.0 * year;
            mjd -= Convert.ToInt32(7.0 / 4.0 * (year + Convert.ToInt32((month + 9.0) / 12.0)));
            mjd += Convert.ToInt32((275.0 * month) / 9.0);
            mjd += day + (hour - timeZone) / 24.0 + min / 1440.0 + sec / 86400.0;
            return mjd;
        }
        
//加个解释
public static double Mjd(int year, int month, int day, int hour, int min, int sec, int timeZone)
{
    // 初始化mjd变量,使用基础值-678987,这是1858年11月17日的MJD值。
    double mjd = -678987;

    // 计算年份的贡献,考虑到闰年的影响。
    // 367.0 * year计算从1858年以来的整年数。
    // Convert.ToInt32(7.0 / 4.0 * (year + Convert.ToInt32((month + 9.0) / 12.0)))
    // 是一个闰年调整因子,它计算从1858年以来闰年的数量。
    mjd += 367.0 * year;
    mjd -= Convert.ToInt32(7.0 / 4.0 * (year + Convert.ToInt32((month + 9.0) / 12.0)));

    // 计算月份的贡献。
    // Convert.ToInt32((275.0 * month) / 9.0)是基于月份的调整因子。
    mjd += Convert.ToInt32((275.0 * month) / 9.0);

    // 加上日期的贡献。
    mjd += day;

    // 计算小时、分钟和秒的贡献,同时考虑时区的影响。
    // (hour - timeZone) / 24.0 是时区调整,将本地时间转换为UTC时间。
    // min / 1440.0 和 sec / 86400.0 分别是将分钟和秒转换为日的分数。
    mjd += (hour - timeZone) / 24.0 + min / 1440.0 + sec / 86400.0;

    // 返回计算出的MJD值。
    return mjd;
}

Epoch.cs

算法定义好了以后,就可以写Epoch.cs了,为啥叫Epoch,搞机器学习是吧(滑稽笑)

class Epoch
{
    // Id属性是一个字符串,用于标识Epoch对象。
    public string Id;

    // Status属性是一个整数,用于表示Epoch的状态。
    public int Status;

    // TimeStr属性是一个字符串,用于存储Epoch的时间字符串。
    public string TimeStr;

    // Mjd属性是一个双精度浮点数,用于存储Epoch的MJD值。
    public double Mjd;

    // x属性是一个双精度浮点数,用于存储Epoch的x坐标。
    public double x;

    // y属性是一个双精度浮点数,用于存储Epoch的y坐标。
    public double y;

    // Parse方法用于解析给定的字符串,并设置Epoch对象的属性。
    public void Parse(string line)//这里的参数在下个文件中
    {
        try
        {
            // 使用逗号作为分隔符,将字符串line分割成数组buf。
            var buf = line.Split(',');

            // 设置Id属性,从buf数组的第一个元素获取值。
            Id = buf[0];

            // 设置Status属性,从buf数组的第二个元素获取值,并转换为整数。
            Status = Convert.ToInt32(buf[1]);

            // 设置TimeStr属性,从buf数组的第三个元素获取值。
            TimeStr = buf[2];

            // 设置x属性,从buf数组的第四个元素获取值,并转换为双精度浮点数。
            x = Convert.ToDouble(buf[3]);

            // 设置y属性,从buf数组的第五个元素获取值,并转换为双精度浮点数。
            y = Convert.ToDouble(buf[4]);

            // 调用GetMjd方法计算Mjd值。
            GetMjd();

        }
        catch (Exception ex)
        {
            // 如果发生异常,抛出异常。
            throw ex;
        }
    }

    // GetMjd方法用于计算Epoch的MJD值。
    private void GetMjd()
    {
        try
        {
            // 设置时区为8小时(假设为UTC+8)。
            int timeZone = 8;

            // 解析TimeStr属性中的日期和时间,并转换为整数。
            //这里是提取字符它是原始字符串的一个子字符串,从 startIndex 开始,直到 startIndex + length - 1。然后,这些子字符串被转换为整数类型。
            int year = Convert.ToInt32(TimeStr.Substring(0, 4));
            int month = Convert.ToInt32(TimeStr.Substring(4, 2));
            int day = Convert.ToInt32(TimeStr.Substring(6, 2));
            int hour = Convert.ToInt32(TimeStr.Substring(8, 2));
            int min = Convert.ToInt32(TimeStr.Substring(10, 2));
            int sec = Convert.ToInt32(TimeStr.Substring(12, 2));

            // 调用Algo类的Mjd方法计算MJD值。
            Mjd = Algo.Mjd(year, month, day, hour, min, sec, timeZone);

        }
        catch (Exception ex)
        {
            // 如果发生异常,抛出异常。
            throw ex;
        }
    }
}

Session.cs

然后就是我们的Session了,这些解释都是GPT浅浅盖上一层,不懂得要自己想想,或者问问,还是比较好理解的

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TaxiData
{
    /// <summary>
    /// 描述Session类的简要说明。
    /// </summary>
    class Session
    {
        // Sn属性是一个整数,用于表示Session的顺序号。
        public int Sn;  // 顺序号

        // StartMjd和EndMjd属性是双精度浮点数,分别表示Session的开始和结束MJD值。
        public double StartMjd, EndMjd;

        // Length属性是一个双精度浮点数,表示Session的长度。
        public double Length;

        // Velocity属性是一个双精度浮点数,表示Session的速度。
        public double Velocity;

        // Azimuth属性是一个双精度浮点数,表示Session的方位角。
        public double Azimuth;

        // 构造函数,接受两个Epoch对象start和end作为参数。
        public Session(Epoch start, Epoch end)
        {
            // 设置Sn属性为0。
            Sn = 0;

            // 设置StartMjd属性为start对象的Mjd值。
            StartMjd = start.Mjd;

            // 设置EndMjd属性为end对象的Mjd值。
            EndMjd = end.Mjd;

            // 调用GetLength方法计算长度。
            GetLength(start, end);

            // 调用GetVelocity方法计算速度。
            GetVelocity();

            // 调用GetAzimuth方法计算方位角。
            GetAzimuth(start, end);
        }

        // 私有方法,用于计算方位角。
        private void GetAzimuth(Epoch start, Epoch end)
        {
            // 定义一个小的浮点数,用于判断dx和dy是否接近零。
            double eps = 1e-5;

            // 计算dx和dy。
            double dx = end.x - start.x;
            double dy = end.y - start.y;

            // 如果dx接近零,判断dy的符号来确定方位角。
            if (Math.Abs(dx) < eps)
            {
                if (Math.Abs(dy) < eps)
                    Azimuth = 0;
                else if (dy > 0)
                    Azimuth = 0.5 * Math.PI;
                else
                {
                    Azimuth = 1.5 * Math.PI;
                }
            }
            // 如果dx不接近零,使用Math.Atan2计算方位角,并考虑dx的符号。
            else
            {
                Azimuth = Math.Atan2(dy, dx);
                if (dx < 0)
                {
                    Azimuth += Math.PI;
                }
            }

            // 将方位角转换为度数。
            Azimuth *= 180 / Math.PI;

            // 如果方位角小于0,调整到0到360度之间。
            if (Azimuth < 0)
            {
                Azimuth += 2 * Math.PI;
            }
            if (Azimuth > 2 * Math.PI)
            {
                Azimuth -= 2 * Math.PI;
            }
        }

        // 私有方法,用于计算速度。
        private void GetVelocity()
        {
            // 计算时间差,以小时为单位。
            double dt = (EndMjd - StartMjd) * 24;

            // 计算速度,单位为km/hour。
            Velocity = Length / dt;
        }

        // 私有方法,用于计算长度,单位为km。
        private void GetLength(Epoch start, Epoch end)
        {
            // 计算dx和dy。
            double dx = end.x - start.x;
			double dy = end.y - start.y;

            // 计算长度,单位为km。
            Length = Math.Sqrt(dx * dx + dy * dy) / 1000.0;
        }

        // 重写ToString方法,返回Session对象的字符串表示。
        public override string ToString()
        {
            // 创建一个字符串变量line,用于存储输出信息。
            string line = $"{Sn:00}, {StartMjd:f5}-{EndMjd:f5}, ";

            // 添加速度和方位角到line中。
            line += $"{Velocity:f3}, {Azimuth:f3}";

            // 返回line字符串。
            return line;//这里就是上面那个文件出现的line
        }
    }
}

以及

SessionList.cs
    class SessionList
    {
        // Data属性是一个List<Session>类型的集合,用于存储Session对象。
        public List<Session> Data = new List<Session>();

        // TotalLength属性用于存储计算出的总长度。
        public double TotalLength;

        // DirctLength属性用于存储计算出的直线距离。
        public double DirctLength;

        // 构造函数,接受一个List<Epoch>类型的参数epoches。
        public SessionList(List<Epoch> epoches)
        {
            // 遍历epoches集合,创建Session对象,并添加到Data集合中。
            for (int i = 0; i < epoches.Count - 1; i++)
            {
                Session s = new Session(epoches[i], epoches[i + 1]);
                s.Sn = i; // 设置Session对象的序列号。
                Data.Add(s);
            }

            // 调用GetTotalLength方法计算总长度。
            GetTotalLength();

            // 调用GetDirctLength方法计算直线距离。
            GetDirctLength(epoches);
        }

        // 私有方法,用于计算直线距离。
        private void GetDirctLength(List<Epoch> epoches)
        {
            // 获取epoches集合的元素数量。
            int n = epoches.Count;
            // 创建一个新的Session对象,使用epoches的第一个和最后一个元素。
            Session s = new Session(epoches[0], epoches[n - 1]);
            // 将计算出的距离赋值给DirctLength属性。
            DirctLength = s.Length;//第一个和最后一个算出来物理意义上的位移
        }

        // 私有方法,用于计算总长度。
        private void GetTotalLength()
        {
            // 初始化TotalLength为0。
            TotalLength = 0;
            // 遍历Data集合中的每个Session对象,累加它们的长度。
            foreach (var d in Data)
            {
                TotalLength += d.Length;
            }
        }

        // 重写ToString方法,返回SessionList对象的字符串表示。
        public override string ToString()
        {
            // 创建一个字符串变量line,用于存储输出信息。
            string line = "------------速度和方位角计算结果----------\r\n";
            // 遍历Data集合中的每个Session对象,将它们的字符串表示添加到line中。
            foreach (var d in Data)
            {
                line += d.ToString() + "\r\n";
            }
            // 添加距离计算结果的标题。
            line += "------------距离计算结果-----------------\r\n";
            // 添加总长度和直线距离的值到line中。
            line += $"累积距离:{TotalLength:f3} (km)\r\n";
            line += $"首尾直线距离: {DirctLength:f3} (km)";

            // 返回line字符串。
            return line;
        }
    }

最后是定义了我们的文件操作的

FileHelper.cs
class FileHelper
{
    // 静态类,不需要实例化。

    // Read方法用于读取文件,并将标识为Id的记录列表返回。
    public static List<Epoch> Read(string Id, string pathname)
    {
        // 创建一个空列表data,用于存储读取的Epoch对象。
        var data = new List<Epoch>();

        // 尝试块用于捕获并处理可能发生的异常。
        try
        {
            // 创建一个StreamReader对象,用于读取指定路径的文件。
            var reader = new StreamReader(pathname);

            // 读取并忽略文件的第一行,通常是标题或元数据。
            reader.ReadLine();

            // 循环读取文件中的每一行,直到到达文件末尾。
            while (!reader.EndOfStream)
            {
                // 读取文件中的下一行。
                string line = reader.ReadLine();

                // 检查读取的行是否为空。
                if (line.Length > 0)
                {
                    // 创建一个新的Epoch对象。
                    var ep = new Epoch();

                    // 调用Epoch对象的Parse方法解析行。
                    ep.Parse(line);

                    // 检查Epoch对象的Id是否与指定的Id相匹配。
                    if (Id.Equals(ep.Id))
                    {
                        // 如果匹配,将Epoch对象添加到data列表中。
                        data.Add(ep);
                    }
                }
            }

            // 关闭StreamReader。
            reader.Close();

        }
        catch (Exception ex)
        {
            // 如果发生异常,抛出异常。
            throw ex;
        }

        // 返回读取的Epoch对象列表。
        return data;
    }

    // Write方法用于将SessionList对象的数据写入文件。
    public static void Write(SessionList data, string filename)
    {
        // 尝试块用于捕获并处理可能发生的异常。
        try
        {
            // 创建一个StreamWriter对象,用于写入指定路径的文件。
            var writer = new StreamWriter(filename);

            // 调用SessionList对象的ToString方法获取字符串表示,并写入文件。
            writer.Write(data.ToString());

            // 关闭StreamWriter。
            writer.Close();

        }
        catch (Exception ex)
        {
            // 如果发生异常,抛出异常。
            throw ex;
        }
    }
}

打开动作,计算动作等代码如下解释,这个最后写

看了半天的报错,原来少创了Session data;可恶,回来记得加上

private SessionList Data;
// 当用户点击工具栏上的“Open”按钮时调用此方法
private void toolOpen_Click(object sender, EventArgs e)
{
    // 显示一个打开文件对话框,让用户选择要打开的文件
    if (openFileDialog1.ShowDialog() == DialogResult.OK)
    {
        // 调用FileHelper类的Read方法,读取用户选择的文件
        // openFileDialog1.FileName是用户选择的文件的完整路径
        var epochs = FileHelper.Read("T2", openFileDialog1.FileName);
        
        // 使用读取的数据创建一个新的SessionList对象,并将其赋值给Data变量
        Data = new SessionList(epochs);
        
        // 在richTextBox1控件中显示一条消息,提示用户数据读取完成
        richTextBox1.Text = "数据读取完成!";
    }
}

// 当用户点击工具栏上的“Calculate”按钮时调用此方法
private void toolCal_Click(object sender, EventArgs e)
{
    // 将Data对象转换为字符串,并在richTextBox1控件中显示
    // 假设SessionList类重写了ToString方法,以提供数据的字符串表示
    richTextBox1.Text = Data.ToString();
}

// 当用户点击工具栏上的“Save”按钮时调用此方法
private void toolSave_Click(object sender, EventArgs e)
{
    // 显示一个保存文件对话框,让用户选择要保存的文件位置和名称
    if (saveFileDialog1.ShowDialog() == DialogResult.OK)
    {
        // 调用FileHelper类的Write方法,将Data对象的数据写入到用户指定的文件
        FileHelper.Write(Data, saveFileDialog1.FileName);
    }
}

// 当用户点击工具栏上的“Help”按钮时调用此方法
private void toolHelp_Click(object sender, EventArgs e)
{
    // 创建一个包含版权信息的字符串
    string copyright = "《测绘程序设计试题集(试题1 出租车数据计算)》配套程序\n作者:李英冰\n";
    copyright += "河南理工大学测绘学院\r\nEMAIL: 2969029950@qq.com\r\n2024.4.25";
    
    richTextBox1.Text = copyright;
}

最后呢,改一下Form1的属性就大功告成了,还是蛮有成就感的,logo忘记换了,无伤大雅。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.反距离加权插值

1.原理剖析

单看题目好像是个算法的实现,目前感觉比较简单

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我靠,看完之后就是简单,比第一题简单,早知道先看第二题下手了,应该和计算机二级水平差不多

2.gui制作

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

和上面那个一样,改个名字,这个带logo了,没有忘

3.code

Point.cs

这就是定义站点类,设置基本的成员变量及从文件中读取到的line,分割成不同的部分,然后重写ToString,让gui显示数据更好看,其实用表格也行,我感觉啊

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace IDW
{
    class Point
    {
        // 公共成员,表示点的ID
        public string Id;
        // 公共成员,表示点的X坐标
        public double X;
        // 公共成员,表示点的Y坐标
        public double Y;
        // 公共成员,表示点的高程
        public double H;
        // 公共成员,表示点到另一个点的距离
        public double Dist;

        // 无参构造函数,初始化所有属性为0
        public Point()
        {
            X = Y = H = Dist = 0;
        }

        // 有参构造函数,初始化ID、X坐标和Y坐标
        public Point(string id, double x, double y)
        {
            Id = id;
            X = x;
            Y = y;
        }

        // Parse方法,用于从字符串解析点的属性
        public void Parse(string line)
        {
            var buf = line.Split(','); // 以逗号分隔字符串
            Id = buf[0]; // 设置ID
            X = Convert.ToDouble(buf[1]); // 设置X坐标
            Y = Convert.ToDouble(buf[2]); // 设置Y坐标
            H = Convert.ToDouble(buf[3]); // 设置高程
        }

        // 重写ToString方法,返回点的字符串表示
        public override string ToString()
        {
            return $"{Id}   {X:F3}   {Y:F3}   {H:F3}"; // 格式化输出点的属性
        }
    }
}

DataEntity.cs

DataEntity类是一个自定义的数据结构,用于存储和管理一系列的Point对象。就是创建了个列表,理解成C++中的vector就可以

  1. 存储点数据DataEntity类包含一个List<Point>类型的成员Data,用于存储多个Point对象。每个Point对象代表一个地理空间中的一个点,包含该点的ID、X坐标、Y坐标、高程(H)和距离(Dist)信息。
  2. 管理数据点DataEntity类提供了一系列方法来管理Data列表中的Point对象,例如Add方法用于添加新的Point对象,索引器允许通过索引访问和修改Data列表中的Point对象。
  3. 提供数据访问:通过Count属性,可以轻松获取Data列表中点的数量。此外,索引器允许开发者通过索引直接访问特定的Point对象。
  4. 格式化输出ToString方法被重写,以便于生成一个格式化的字符串,可以用于输出数据点的信息。这对于调试或记录数据非常有用。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace IDW
{
    class DataEntity
    {
        // 公共成员,用于存储Point对象的列表
        public List<Point> Data;

        // 只读属性,返回Data列表中元素的个数
        public int Count => Data.Count;

        // 构造函数,初始化Data列表
        public DataEntity()
        {
            Data = new List<Point>();
        }

        // 公共方法,用于向Data列表中添加一个Point对象
        public void Add(Point pt)
        {
            Data.Add(pt);
        }

        //索引器,允许通过索引访问和修改Data列表中的Point对象
        public Point this[int i]
        {
            get { return Data[i]; } // 获取指定索引处的Point对象
            set { Data[i] = value; } // 设置指定索引处的Point对象
        }

        // 重写ToString方法,返回Data列表中所有Point对象的字符串表示
        public override string ToString()
        {
            string res = "测站    X(m)    Y(m)      H(m)\n"; // 初始化结果字符串
            foreach (var d in Data)
            {
                res += d.ToString() + "\n"; // 将每个Point对象的字符串表示添加到结果字符串中
            }
            return res; // 返回结果字符串
        }
    }
}

FileHelper.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

// 命名空间
namespace IDW
{
    // FileHelper类,提供文件读写的辅助方法
    class FileHelper
    {
        // Read方法,用于从指定文件读取数据到DataEntity对象
        public static DataEntity Read(string filename)
        {
            DataEntity data = new DataEntity(); // 创建一个新的DataEntity实例

            try
            {
                var reader = new StreamReader(filename); // 创建一个StreamReader实例用于读取文件
                while (!reader.EndOfStream) // 当未到达文件末尾时继续读取
                {
                    string line = reader.ReadLine(); // 读取一行文本
                    if (line.Length > 0) // 如果行不为空
                    {
                        Point pt = new Point(); // 创建一个新的Point实例
                        pt.Parse(line); // 解析行文本到Point实例

                        data.Add(pt); // 将解析后的Point添加到DataEntity实例
                    }
                }

                reader.Close(); // 关闭StreamReader

            }
            catch (Exception ex) // 捕获并处理可能发生的异常
            {
                throw ex; // 抛出异常
            }

            return data; // 返回填充了数据的DataEntity实例
        }

        // Write方法,用于将文本写入到指定文件
        public static void Write(string text, string filename)
        {
            try
            {
                var writer = new StreamWriter(filename); // 创建一个StreamWriter实例用于写入文件
                writer.Write(text); // 写入文本
                writer.Close(); // 关闭StreamWriter

            }
            catch (Exception ex) // 捕获并处理可能发生的异常
            {
                throw ex; // 抛出异常
            }
        }
    }
}

Algo.cs

算法实现文件,看完之后只能说C#真的灵活,比C++造轮子造的真好

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace IDW
{
    class Algo
    {
        // DataEntity类型的成员变量,用于存储数据点
        DataEntity Data;

        // N定义了参与计算的最近邻点的数量
        private int N = 5;

        // 构造函数,初始化DataEntity数据以及N的值
        public Algo(DataEntity data, int n)
        {
            Data = data;
            N = n;
        }

        // 计算两点之间的距离
        public double Distance(Point p1, Point p2)
        {
            double dx = p1.X - p2.X; // x坐标差
            double dy = p1.Y - p2.Y; // y坐标差
            double ds = Math.Sqrt(dx * dx + dy * dy); // 计算欧氏距离
            return ds;
        }

        // IDW算法的主要实现,返回一个字符串,包含插值点的信息和计算的值
        public string Idw(Point pt)
        {
            string res = $"{pt.Id}  {pt.X:f3}  {pt.Y:f3}  "; // 初始化结果字符串
            for (int i = 0; i < Data.Count; i++)
            {
                double d = Distance(Data[i], pt); // 计算插值点与数据集中每个点的距离
                Data[i].Dist = d; // 将距离存储在数据点的Dist属性中
            }
            var dt = Sort(); // 对数据点根据距离进行排序
            double H = GetH(dt); // 根据排序后的数据点计算插值
            res += $" {H:f3}   "; // 将计算结果添加到结果字符串中
            for (int j = 0; j < N; j++)
            {
                res += $"{dt[j].Id} "; // 将参与计算的最近邻点的ID添加到结果字符串中
            }
            return res; // 返回结果字符串
        }

        // 根据排序后的数据点计算插值
        private double GetH(DataEntity dt)
        {
            double over = 0, under = 0;
            for (int i = 0; i < N; i++)
            {
                over += dt[i].H / dt[i].Dist; // 分子部分,加权高度除以距离
                under += 1 / dt[i].Dist; // 分母部分,距离的倒数之和
            }
            return over / under; // 计算最终的插值结果
        }

        // 对数据点根据距离进行排序
        DataEntity Sort()
        {
            DataEntity dt = Data;
            for (int i = 0; i < Data.Count; i++)
            {
                for (int j = i; j < Data.Count; j++)
                {
                    if (dt[i].Dist > dt[j].Dist) // 如果前一个点的距离大于后一个点的距离
                    {
                        var pt = dt[i]; // 交换两个点
                        dt[i] = dt[j];
                        dt[j] = pt;
                    }
                }
            }
            return dt; // 返回排序后的数据点集
        }
    }
}
Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

// 命名空间
namespace IDW
{
    // Form1类,继承自Form,表示应用程序的主窗口
    public partial class Form1 : Form
    {
        // 公共成员,用于存储插值结果字符串
        public string result;
        // DataEntity实例,用于存储和管理数据点
        DataEntity Data = new DataEntity();
        
        // 构造函数,初始化窗体组件
        public Form1()
        {
            InitializeComponent();
        }

        // 工具栏中“打开”按钮的点击事件处理方法
        private void toolOpen_Click(object sender, EventArgs e)
        {
            // 显示文件打开对话框,如果用户选择了一个文件,则读取数据并显示在richTextBox1中
            if (openFileDialog1.ShowDialog() == DialogResult.OK)
            {
                Data = FileHelper.Read(openFileDialog1.FileName);
                richTextBox1.Text = Data.ToString();
            }
        }

        // 工具栏中“计算”按钮的点击事件处理方法
        private void toolCal_Click(object sender, EventArgs e)
        {
            // 初始化结果字符串
            string res = "点名  X(m)     Y(m)       H(m)     参与插值的点列表\r\n";
            // 创建Algo实例,用于执行IDW算法
            Algo go = new Algo(Data, 5);
            // 创建四个待插值的点
            var Q1 = new Point("Q1", 4310, 3600);
            var Q2 = new Point("Q2", 4330, 3600);
            var Q3 = new Point("Q3", 4310, 3620);
            var Q4 = new Point("Q4", 4330, 3620);

            // 对每个点执行IDW算法,并将结果添加到结果字符串中
            res += go.Idw(Q1) + "\r\n";
            res += go.Idw(Q2) + "\r\n";
            res += go.Idw(Q3) + "\r\n";
            res += go.Idw(Q4) + "\r\n";

            // 更新公共成员result和richTextBox1的文本
            result = res;
            richTextBox1.Text = res;
        }

        // 工具栏中“帮助”按钮的点击事件处理方法
        private void toolHelp_Click(object sender, EventArgs e)
        {
            // 初始化版权信息字符串
            string copyright = "《测绘程序设计试题集(试题9 反距离加权插值)》配套程序\n作者:李英冰\n";
            copyright += "河南理工大学测绘学院\r\n卢文豪EMAIL: 2969029950@qq.com\r\n2024.4.26";
            // 显示版权信息在richTextBox1中
            richTextBox1.Text = copyright;
        }

        // 工具栏中“保存”按钮的点击事件处理方法
        private void toolSave_Click(object sender, EventArgs e)
        {
            // 显示文件保存对话框,如果用户指定了文件名,则将结果保存到文件中
            if (saveFileDialog1.ShowDialog() == DialogResult.OK)
            {
                FileHelper.Write(result, saveFileDialog1.FileName);
            }
        }
    }
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

完成,还是比较好懂的,如果转不过来,就右键查看代码定义研究或者问

第一次培训的话,讲的C#基础,没必要听。自己练吧家人们,趁这个功夫自己写了第二题

2024.04.27

今天来研究一下竞赛题,也是我这一年的比赛题目之一,这个要用到.NET的内置画图库先来看看,接下来的几道题道都是我这年的赛题。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

5.五点光滑法进行曲线拟合

(此处是有简化,但是目录没显示出来)示例代码与书上原理不同

1.原理

这个原理的话,就这里解释的还算清晰,直接对着研究就行了,别的说的都没这个好懂。原理需要深入研究和理解。我觉得这里有一些需要注意的点,每次需要确定五个点,那么首尾两个点就需要补充确切的点。补充的方法是得出近似的ABCD点。接下来,我们需要计算其他参数。通过五个点的xy坐标,我们可以计算出参数ab和w,从而得出五个点中中间点的方向梯度。然后,我们需要构建一个三次拟合曲线,并计算出其参数EF。同样地,我们可以通过点坐标与梯度来计算出参数。三次拟合的公式中,自变量是z,其取值范围在0到1之间。将z的值带入公式中,我们可以在两个点之间得到一个新的坐标点,这样就可以得到m个坐标点。最后,将这些坐标点连接起来,就可以得到拟合曲线。看不懂就多看一会,对照着公式。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

梯度这里还是比较好理解的,就是五个点的横纵坐标得出的δ算梯度,指示出方向

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

文件的话就这些

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

来看下文件中都是什么

  • F o r m 1 Form1 Form1:肯定是定义各种空间的动作
  • M y P o i n t MyPoint MyPoint:创建一个点类,包含Id,x,y,并可以算出r就是两点间的距离
  • M y c u r v e Mycurve Mycurve:定义拟合时需要的系数类
  • P o i n t T o C u r v e PointToCurve PointToCurve:算法实现

2.gui制作

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

他这个gui稍微复杂的就是菜单栏和工具栏的复用,还有下面页面的切换

我们可以看Design的代码

        private void InitializeComponent()
        {
            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
            System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
            System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle();
            System.Windows.Forms.DataVisualization.Charting.ChartArea chartArea1 = new System.Windows.Forms.DataVisualization.Charting.ChartArea();
            System.Windows.Forms.DataVisualization.Charting.Legend legend1 = new System.Windows.Forms.DataVisualization.Charting.Legend();
            System.Windows.Forms.DataVisualization.Charting.Series series1 = new System.Windows.Forms.DataVisualization.Charting
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xin2cd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值