机器学习-KNN算法原理、实战详细讲解(C++/Python两种语言实现)

一、KNN算法讲解

1.1实现环境

C++:集成开发环境VS2022,可视化库matplot++
python:集成开发环境Pycharm,数学库numpy,可视化库matplotlib

1.2KNN算法原理

KNN(K - 近邻算法)是一种监督学习中的惰性学习算法,核心是 “相似样本在特征空间中彼此靠近”,通过计算新样本与训练样本的距离找 k 个最近邻,再用多数表决(分类)或均值(回归)做预测,无需显式训练模型。

1.3算法的优点

(1)原理简单直观,实现难度低,无需复杂参数训练。

(2)对异常值不敏感,适合类域交叉或重叠较多的数据集。

(3)适配多分类任务,且可灵活切换分类与回归场景。

1.4算法的缺点

(1)预测阶段计算量大,时间与空间复杂度高,数据量大时效率低。

(2)对高维数据敏感,易出现 “维度灾难”,需通过降维等手段优化。

(3)样本不平衡时,多数类可能主导预测结果,需做样本均衡处理。

二、评估标准

2.1用ROC曲线评估

ROC 曲线是二分类模型的性能评估曲线,以假正率(FPR) 为横轴、真正率(TPR) 为纵轴,通过遍历分类模型的分类阈值,绘制出不同阈值下模型的 TPR 与 FPR 对应点并连接成线,直观反映模型在 “识别正例” 和 “避免误判负例” 之间的权衡能力(FPR值是所有反例被错误当作正例比例,TPR值是所有正例正确识别出来正例比例;模型正确识别正例的能力越高、错误识别正例的能力越低,这个模型的分类能力就越强)。

2.2计算FPR、TPR

这里涉及到四个数据:TP(真正例)、FP(假正例)、FN(假反例)、TN(真反例)

TP:真实正例被模型正确预测为正例;

FP:真实负例被模型错误预测为正例(误检);

FN:真实正例被模型错误预测为负例(漏检);

TN:真实负例被模型正确预测为负例。

TPR计算公式:

FPR计算公式:

2.3AUC

AUC(ROC 曲线下的面积) 是对 ROC 曲线的量化评估,也是比 ROC 曲线更常用的模型性能指标,取值范围为0~1。AUC的值越接近1,表明模型越完美,分类性能越好。

三、例题分析

3.1问题

海伦一直使用在线约会网站寻找适合自己的约会对象。她曾交往过三种类型的人:

(1)不喜欢的人

(2)一般喜欢的人

(3)非常喜欢的人

这些人包含以下三种特征:

(1)每年获得的飞行常客里程数

(2)玩视频游戏所耗时间百分比

(3)每周消费的冰淇淋公升数

该网站现在需要尽可能向海伦推荐她喜欢的人,需要我们设计一个分类器,根据用户的以上三种特征,识别出是否该向海伦推荐。

3.2数据集类型

每年获得的飞行常客里程数玩视频游戏所耗时间百分比每周消费的冰淇淋的公升数样本分类
14000.80.51
2134000120.93
32000001.12
432000670.12

3.3问题剖析

题目给出了三个特征维度,“每年获得的飞行常客里程数”这一特征维度的数据比另外两个大了2-3个数量级,我们选择了欧几里得距离作为距离标准,所以需要对数据进行预处理,让每个特征维度的权重基本一致。我们需要在导入图中数据时,将这些字符串转换为整型数据,方便使用。

3.4具体实现

3.4.1大致流程示意图

3.4.2具体步骤

(1)数据预处理

归一化将所有数据按原范围大小,均匀压缩到0-1的范围内,如25、50、75、125,原范围25-125,对应0-1范围0、0.25、0.50、1。

给出公式:

洗牌:数据集的数据可能呈现出部分特征集中在某一特定位置,我们需要搅动它让数据均匀地分布在每个地方。对于数据集而言,这种数据集集中分布的情况是会影响生成的模型的能力的,所以我们需要把原来的数据集打乱,让某些过于集中的特征分散开来,提高模型预测的准确性。

(2)最佳K值的选择

k的选取是很重要的,常见的选择方法有经验值法(直接设置参数)和交叉验证法(网格搜索+交叉验证)。

在计距离时用到了欧几里得距离距离计算公式

欧几里得距离计算公式(x和y是同一特征维度的,i表示不同的特征维度):

(3)计算FPR和TPR、AUC并可视化ROC曲线

上面在介绍ROC曲线时已经讲过了,这里就不多说了。

四、C++实现

4.1环境准备

本次我们只需要一个额外的可视化第三方库,我们选择matplot++

matplot++下载

官方链接:matplot++发行版

 VS2022环境搭建

把matplot++放到自己建立的解决方案的其中一个项目的目录下,matplot++的两个静态库,可根据实际位置调整,一般都是一个在lib里,一个在lib\Matplot++里。然后在链接器中链接:
nodesoup.lib
matplot.lib

链接完成后点击确定,到这里环境就配置好了。

4.2代码实现

4.2.1头文件

#include <iostream> #include <fstream> #include <vector> #include <memory> #include <functional> #include <algorithm> #include <unordered_map> #include <random> #include <numeric> #include <queue> #include <matplot/matplot.h>

4.2.2结构体

struct Data { double length; double game_time; double ice_crime_eating; int type; };

这里Data结构体的前三个成员均为double类型而不是用vector,这是因为vector是将数据分散存储到堆区,类似链表结构体,后续我们需要频繁地访问这些数据,需要不停使用指针访问内存,会让访问效率下降。

4.2.3导入数据

std::vector<Data> loadData(const std::string& filepath) { std::ifstream ifs; ifs.open(filepath, std::ios::in); if (!ifs.is_open()) throw std::invalid_argument("Failed to open file: " + filepath); std::vector<Data> datalist; double trail, game, eating; std::string tag; std::vector<std::string> searchlist = { "largeDoses", "smallDoses", "didntLike" }; while (ifs >> trail >> game >> eating >> tag) { int type = -1; for (int i = 0; i < searchlist.size(); ++i) { if (searchlist[i] == tag) { type = i; break; } } datalist.push_back({ trail, game, eating, type }); } ifs.close(); return datalist; }

4.2.4数据预处理

使用lamda来简化三个特征维度的归一化实现:

// 更新归一化数据 void calculate(std::vector<double>& feature) { double max = *max_element(feature.begin(), feature.end()); double min = *min_element(feature.begin(), feature.end()); double range = max - min; for (double& val : feature) val = (val - min) / range; } // 归一化 void normalized(std::vector<Data>& data) { if (data.empty()) { std::cout << "nullptr doesn't normalized!" << std::endl; __debugbreak(); return; } std::vector<std::pair< std::function<double(const Data&)>, std::function<void(Data&, double)> >> field = { { [](const Data& d) {return d.game_time; }, [](Data& d, double val) {d.game_time = val; } }, { [](const Data& d) {return d.ice_crime_eating; }, [](Data& d, double val) {d.ice_crime_eating = val; } }, { [](const Data& d) {return d.length; }, [](Data& d, double val) {d.length = val; } } }; for (auto& f : field) { auto& getVal = f.first; auto& setVal = f.second; std::vector<double> values; for (const auto& d : data) values.push_back(getVal(d)); calculate(values); for (int i = 0; i < data.size(); ++i) setVal(data[i], values[i]); } }

随机生成伪随机数种子,使用shuffle洗牌:

// 打乱数据 std::vector<Data> shuffleData(std::vector<Data>& data) { auto shuffle_data = std::vector<Data>(data.begin(), data.end()); std::random_device rd; std::mt19937 g(rd()); std::shuffle(shuffle_data.begin(), shuffle_data.end(), g); return shuffle_data; }

4.2.5选择最佳K值

 计算欧几里得距离:

// 计算欧几里得距离 double eucliDistance(Data x, Data y) { double trail = x.length - y.length; double play = x.game_time - y.game_time; double eating = x.ice_crime_eating - y.ice_crime_eating; return trail * trail + play * play + eating * eating; }

获取邻居各类别的数量:

std::vector<int> getKNeighborProb(const Data& test, const std::vector<Data>& trains, int k) { if (trains.empty() || k <= 0) throw std::invalid_argument("Invalid input for getNeighborProb!"); std::priority_queue<std::pair<double, int>> maxHeap; // 使用最大堆存储 for (const auto& d : trains) { double dist = eucliDistance(test, d); if (maxHeap.size() < k) maxHeap.emplace(dist, d.type); else if (maxHeap.top().first > dist) { maxHeap.pop(); maxHeap.emplace(dist, d.type); } } std::vector<int> neighborProb(3, 0); while (!maxHeap.empty()) { neighborProb[maxHeap.top().second]++; maxHeap.pop(); } return neighborProb; }

最大的k个邻居与他们的位置无关,只需要使用最大堆把最大的k个邻居和它们的类别存起来,统计最大堆前k个最大邻居出现的类别的数量即可。

选择出现最多的类别作为预测结果:

// 预测类型 int predicType(const Data& test, std::vector<Data>& data, int k) { auto vote = getKNeighborProb(test, data, k); return max_element(vote.begin(), vote.end()) - vote.begin(); }

4.2.6使用k折交叉验证计算准确率和三分类混淆矩阵

// 使用K折交叉验证计算准确率 std::pair<double, std::vector<std::vector<int>>> kFoldCrossVaild(std::vector<Data>& shuffle_data, int kfold, int knn_k) { if (shuffle_data.empty()) throw std::invalid_argument("Invalid input for countRoc: Null data is invaild!"); if (kfold <= 0) throw std::invalid_argument("Invalid input for countRoc: kfold must be bigger than 0!"); std::vector<std::vector<int>> cntlist(3, std::vector<int>(3, 0)); int foldSize = shuffle_data.size() / kfold; std::vector<double> accuracies; for (int fold = 0; fold < kfold; ++fold) { int start = fold * foldSize; int end = (kfold -1 == fold) ? shuffle_data.size() : start + foldSize; int correct = 0; std::vector<Data> trainData(shuffle_data.begin(), shuffle_data.begin() + start); trainData.insert(trainData.end(), shuffle_data.begin() + end, shuffle_data.end()); for(int i = start; i < end; ++i) { auto t = shuffle_data[i]; int res = predicType(t, trainData, knn_k); cntlist[t.type][res]++; correct += (res == t.type); } double accuracy = static_cast<double>(correct) / foldSize; accuracies.push_back(accuracy); } return std::make_pair(std::accumulate(accuracies.begin(), accuracies.end(), 0.0) / kfold , cntlist); }

4.2.7网格搜索

从3开始,跳过偶数(偶数可能会平票,所以步长为2),取数据集总量的开方,减少不必要的计算成本。将得到的准确率和混淆矩阵存起来,跑完网格搜素后寻找最大的准确率,换算k值,返回k和混淆矩阵。

4.2.8计算FPR和TPR

1.遍历vector容器,将所有数据除以k:

// 计算测试集在训练集上的多类型概率 std::vector<double> predicProb(const Data& test, std::vector<Data>& data, int k) { auto vote = getKNeighborProb(test, data, k); std::vector<double> appearance(3, 0); for (int i = 0; i < appearance.size(); ++i) appearance[i] = static_cast<double>(vote[i]) / k; return appearance; }

2.计算某一类型作为正例时单个点的FPR和TPR:

// 使用K折交叉验证,计算ROC图数据 std::pair<double, double> countRoc(std::vector<Data>& shuffle_data, int kfold, int knn_k, double confd, int genre) { if (shuffle_data.empty()) throw std::invalid_argument("Invalid input for countRoc: Null data is invaild!"); if (kfold <= 0) throw std::invalid_argument("Invalid input for countRoc: kfold must be bigger than 0!"); int foldSize = shuffle_data.size() / kfold; int TP = 0, FP = 0, FN = 0, TN = 0; for (int fold = 0; fold < kfold; ++fold) { int start = fold * foldSize; int end = (kfold - 1 == fold) ? shuffle_data.size() : start + foldSize; int correct = 0; std::vector<Data> trainData; trainData.reserve(shuffle_data.size() - foldSize); trainData.assign(shuffle_data.begin(), shuffle_data.begin() + start); trainData.insert(trainData.end(), shuffle_data.begin() + end, shuffle_data.end()); for (int i = start; i < end; ++i) { auto t = shuffle_data[i]; std::vector<double> res = predicProb(t, trainData, knn_k); bool isPred = (res[genre] >= confd); bool isTrue = (genre == t.type); TP += isPred && isTrue; FP += isPred && (!isTrue); FN += (!isPred) && isTrue; TN += (!isPred) && (!isTrue); } } double TPR = ( TP+FN == 0 ? 0.0 : static_cast<double>(TP) / (TP + FN)); double FPR = ( FP+TN == 0 ? 0.0 : static_cast<double>(FP) / (FP + TN)); return { TPR, FPR }; }

3.将某一类别所有点数据整理补全:

// 组织ROC图数据并补全 std::pair<std::vector<double>, std::vector<double>> getROC(std::vector<Data>& data, int kfold, int knn_k, int genre) { std::vector<double> tprlist; std::vector<double> fprlist; for (double confd = 1.0; confd > 0; confd -= 0.05) { auto [tpr, fpr] = countRoc(data, kfold, knn_k, confd, genre); tprlist.push_back(tpr); fprlist.push_back(fpr); } if (!(tprlist.front() == 0 && fprlist.front() == 0)) { tprlist.insert(tprlist.begin(), 0); fprlist.insert(fprlist.begin(), 0); } if (!(tprlist.back() == 1 && fprlist.back() == 1)) { tprlist.push_back(1); fprlist.push_back(1); } return { fprlist, tprlist }; }

由于可能出现FPR不为0和不为1的情况,这样面积就可能会少算一部分(最小FPR的左边区域和最大FPR的右边区域),所以需要补全。

4.2.9计算AUC和可视化实现

// 累加AUC得到最终值 结合8使用 data十分有序,无需处理即可使用 double getAUC(std::pair<std::vector<double>, std::vector<double>>& data) { double auc = 0.0; auto& fprs = data.first; auto& tprs = data.second; for (int i = 0; i < fprs.size()-1; ++i) { auc += (fprs[i + 1] - fprs[i]) * (tprs[i + 1] + tprs[i]) / 2; } std::cout << auc << std::endl; return auc; }
void plotEvalution(std::vector<std::pair<std::vector<double>, std::vector<double>>> data, std::vector<double> aucs, std::vector<std::vector<int>>& list) { using namespace matplot; std::vector<std::string> colorList({ "blue", "yellow", "green", "red" }); std::vector<std::string> tagName({ "largeDoses", "smallDoses", "didntLike", "Random Guess" }); auto fig = figure(true); // 设置为true是能正常使用figure的关键 fig->size(2560, 1000); subplot(1, 2, 1); hold(on); for (int i = 0; i < data.size(); ++i) { plot(data[i].first, data[i].second)->line_width(2).line_style("-").color(colorList[i]); } plot(std::vector{ 0,1 }, std::vector{ 0,1 })->line_width(1).line_style("--").color(colorList[3]); hold(off); xlabel("False Positive Rate"); ylabel("True Positive Rate"); title("ROC"); xlim({ 0, 1 }); ylim({ 0, 1 }); for (int i = 0; i < aucs.size(); ++i) text(0.02, 0.1 - i * 0.025, tagName[i] + " AUC: " + std::to_string(aucs[i]))->font_size(8); auto l = ::matplot::legend(tagName); l->location(legend::general_alignment::bottomright); l->num_rows(2); l->font_size(5); subplot(1, 2, 2); heatmap(list)->normalization(matrix::color_normalization::columns); title("Three distribution"); auto ax = gca(); ax->x_axis().ticklabels({ "largeDoses", "smallDoses" , "didntLike" }); ax->y_axis().ticklabels({ "largeDoses", "smallDoses" , "didntLike" }); ax->x_axis().label_font_size(5); ax->y_axis().label_font_size(5); //save("ROC.png"); show(); }

suplot(1, 2, 1)下面的是ROC曲线可视化
suplot(1, 2, 2)下面的是热力图可视化

最后调用mian函数即可:

int main() { auto data = loadData("source\\datingTestSet.txt"); normalized(data); auto shuffle_Data = shuffleData(data); auto [best_k, list] = getBestK(data); std::vector<std::pair<std::vector<double>, std::vector<double>>> results; std::vector<double> AUCs; for (int i = 0; i < 3; ++i) { auto res = getROC(shuffle_Data, 10, best_k, i); auto auc = getAUC(res); results.push_back(res); AUCs.push_back(auc); } plotEvalution(results, AUCs, list); return 0; }

4.3结果展示

五、python实现

python也需要配置环境,但是python的环境配置简单很多

5.1环境配置

在你的新的虚拟环境中,安装numpy和matplotlib:

conda install numpy conda install matplotlib

matplotlib官方使用说明 
numpy官方使用说明

然后在创建项目时选择conda环境,到你的anaconda的文件夹中找到虚拟环境对应的python.exe即可。

5.2代码实现

import numpy as np import matplotlib.pyplot as plt tag_map = { "largeDoses": 0, "smallDoses": 1, "didntLike": 2 } # 导入数据 def load_data(filepath): # 读取数据 data = [] with open(filepath, 'r', encoding="utf-8") as f: for line in f: line = line.strip() if not line: continue parts = line.split('\t') if len(parts) != 4: print(f"特征数据无法转换:{line}") continue tag = parts[3] data.append({ "feature": [parts[0], parts[1], parts[2]], "tag": tag_map[tag] }) return data # 归一化 利用numpy的广播机制 def normalized(features: np.ndarray): # 获取每一列的最大值和最小值 max_val = features.max(axis=0) min_val = features.min(axis=0) ranges = max_val-min_val ranges[ranges < 1e-9] = 1.0 # 比较每个列的相减结果,排除0的情况 return (features-min_val)/ranges # 计算欧式距离 def euclidean_dist(test, train): return np.sum((test - train) ** 2, axis=1) # 计算各类标签的概率 def predict_prob(test, train_data, k): train_features = train_data[:,:3] dist = euclidean_dist(test, train_features) k_indices = np.argpartition(dist, k)[:k] # 完成一次快速排序 k_tags = train_data[k_indices, 3].astype(int) prob = np.bincount(k_tags, minlength=3) / k return prob # 选择最大概率作为当前测试样本的预测类型 def predict_type(test, train_data, k): return max(enumerate(predict_prob(test, train_data, k)), key=lambda x:x[1])[0] # 使用k折交叉验证,计算准确率和三分类特征混淆矩阵 def k_folds_cross_valid_acc(features: np.ndarray, k, k_fold): # 计算每一折的大小 fold_size = int(len(features)/k_fold) fold_accuracies = 0.0 confusion_mat = np.zeros([3,3], dtype=np.int32) for i in range(k_fold): start = i*fold_size # 防止最后一折不够一折的大小 end = start+fold_size if i != k_fold-1 else len(features) train_data = np.concatenate([features[:start], features[end:]]) correct = 0 test_data = features[start:end] for t in test_data: pred_type = predict_type(t[:3], train_data, k) if pred_type == t[3]: correct += 1 confusion_mat[int(t[3])][pred_type] += 1 fold_accuracies += correct/fold_size return fold_accuracies/k_fold, confusion_mat # 计算模型roc曲线的单个点 def count_roc(features: np.ndarray, k_fold, k_neighbor, confidence, genre): fold_size = int(len(features)/k_fold) tp, tn, fp, fn = 0, 0, 0, 0 for i in range(k_fold): start = i*fold_size end = start+fold_size if i != k_fold-1 else len(features) train_data = np.concatenate([features[:start], features[end:]]) test_data = features[start:end] for t in test_data: prob = predict_prob(t[:3], train_data, k_neighbor) if genre == t[3] and prob[genre] >= confidence: tp += 1 elif genre != t[3] and prob[genre] < confidence: tn += 1 elif genre != t[3] and prob[genre] >= confidence: fp += 1 elif genre == t[3] and prob[genre] < confidence: fn += 1 tpr = tp / (tp+fn) if (tp+fn) else 0.0 fpr = fp / (fp+tn) if (fp+tn) else 0.0 return fpr, tpr # 整理并补全roc曲线的两个点集 def get_roc(features: np.ndarray, k_fold, k_neighbor, genre): # 生成1到0步长为-0.05的列表 confidence = np.arange(1.0, 0, -0.05) fpr_list = list() tpr_list = list() for conf in confidence: fpr, tpr = count_roc(features, k_fold, k_neighbor, conf, genre) fpr_list.append(fpr) tpr_list.append(tpr) if fpr_list[0] != 0.0: fpr_list.insert(0, 0.0) tpr_list.insert(0, 0.0) if fpr_list[len(fpr_list)-1] != 1.0: fpr_list.append(1.0) tpr_list.append(1.0) return [fpr_list, tpr_list] # 获取AUC def get_auc(result): auc = 0.0 fpr_list, tpr_list = result if len(fpr_list) < 2: return 0.0 for i in range(len(fpr_list)-1): # 梯形法算AUC面积 auc += (fpr_list[i+1]-fpr_list[i])*(tpr_list[i]+tpr_list[i+1])/2 return auc # 通过值寻找键列表 def get_value(dictionary: dict, target_value: int): return [key for key, value in dictionary.items() if value == target_value] # 绘制roc曲线 def plot_roc(results, ax): class_names = list(tag_map.keys()) for i in range(len(results)): ax.plot(results[i][0], results[i][1], linewidth=2, label=f"{class_names[i]} AUC: {results[i][2]:0.5f}") ax.plot((0, 1), (0, 1), "--", linewidth=1, label="predicted line") ax.legend(loc="lower right") ax.set_title("ROC Curve") ax.set_xlabel("False Positive Rate") ax.set_ylabel("True Positive Rate") ax.set_xlim(0,1) ax.set_ylim(0,1) # 绘制热力图 def plot_heatmap(heat_conf, ax): class_names = list(tag_map.keys()) im = ax.imshow(heat_conf) ax.set_xticks(range(len(class_names))) ax.set_xticklabels(labels=tag_map.keys(), rotation=45, ha="right", rotation_mode="anchor") ax.set_yticks(range(len(class_names))) ax.set_yticklabels(labels=tag_map.keys()) ax.set_title("Three Distribution") for i in range(len(tag_map)): for j in range(len(tag_map)): value = heat_conf[i, j] # 提高可视化观感 text_color = "black" if im.norm(value) > 0.5 else "white" ax.text(j, i, value, ha="center", va="center", color=text_color, fontweight="bold") cbar = plt.colorbar(im, ax=ax) cbar.set_label("Sample Count") if __name__ == "__main__": # 导入数据 dataset = load_data("datingTestSet.txt") # 检查数据是否导入 if not dataset: print("没有有效数据") exit() # 创建numpy数组 feature_list = np.array([d["feature"] for d in dataset], dtype=np.float64) tag_list = np.array([d["tag"] for d in dataset], dtype=np.int32) # 归一化数组 normalized_feature = normalized(feature_list) # 将特征和标签拼接 tag_list = tag_list.reshape(-1, 1) # 将numpy数组转置 feature_tag = np.hstack((normalized_feature, tag_list)) # 洗牌 np.random.seed(42) np.random.shuffle(feature_tag) # 选择最大准确率,选择最佳k值,并计算出最佳k值下的三分类特征混淆矩阵 best_k = 0 best_acc = 0 best_confusion = np.zeros([3, 3], dtype=np.int32) for i in range(3, int(np.sqrt(len(feature_tag))), 2): acc, confusion = k_folds_cross_valid_acc(feature_tag, i, 10) if best_acc < acc: best_k = i best_acc = acc best_confusion = confusion print(f"十折交叉验证的最佳k值为:{best_k},对应的准确率为:{best_acc:0.5f}") # 评估模型:获取ROC曲线和AUC值 results = list() for i in range(3): res = get_roc(feature_tag, 10, best_k, i) auc = get_auc(res) results.append([*res, auc]) # 评估数据可视化 fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5)) plot_roc(results, ax1) plot_heatmap(best_confusion, ax2) plt.tight_layout() plt.show()

5.3运行结果

Read more

【C++ Qt】网络编程(QUdpSocket、QTcpSocket、Http)

【C++ Qt】网络编程(QUdpSocket、QTcpSocket、Http)

每日激励:“不设限和自我肯定的心态:I can do all things。 — Stephen Curry” 绪论 : 本章将提到Qt中的网络部分,在看这篇文章之前需要有一定的网络基础也就是TCP/HTTP、本篇文章主要讲到的是Qt中基础的Udp、Tcp、Http的使用方法,并附有了多个小demo方便实操练习,并且其中还在每章最后进行了小总结回顾重要接口和函数方便回顾。 ———————— 早关注不迷路,话不多说安全带系好,发车啦(建议电脑观看)。 网络编程主要依赖于操作系统提供的Socket API。需要注意的是,C++标准库本身并未封装网络编程相关的API。 关于Qt网络编程的几个要点: 1. 网络应用开发本质上是编写应用层代码,需要传输层协议(如TCP/UDP)的支持 2. 为此,Qt提供了两套专门的网络编程API(QUDPSocket和QTcpSocket) 3. 使用Qt网络编程API时,需先在.pro文件中添加network模块 4. 之前学习的Qt控件和核心功能都属于QtCore模块(默认已包含) 为什么Qt要划分出这些模块呢? Qt 本身是一个非常庞

By Ne0inhk
【JavaWeb03】 入门JavaScript:轻松掌握前端开发核心技能

【JavaWeb03】 入门JavaScript:轻松掌握前端开发核心技能

文章目录🌍一. JavaScript1. 基本说明2. JavaScript 特点!3. JavaScript 快速入门🌍二. js变量1. 变量的声明2. 示例3. 变量的命名规则4. 变量的类型5. 变量的作用域6. 变量的提升🌍三. 数据类型1. 基本数据类型(Primitive Types)2. 引用数据类型(Reference Types)3. 类型检测🌍四. 运算符1. 算术运算符2. 赋值运算符3. 关系运算符4. 逻辑运算符🌍 五. 数组1. 数组定义2. 数组使用和遍历🌍六. js函数1. 函数定义方式2. JavaScript 函数注意事项和细节🌍七. 事件onload 加载完成事件onclick 单击事件onblur 失去焦点事件o nchange 内容发生改变事件onsubmit

By Ne0inhk

c++新手 使用trae 搭建c++开发环境,提示不支持cppdbg

Trae IDE搭建C++开发环境完全指南:从0到1的实战经验分享 **【补充: 今天偶然翻到一篇文章,就在我解决Trae无法调试C++的问题之后,我发现的。贴出来,告诉大家为什么无法在trae中调试C++。 下面两篇文章的大致是说微软不让第三方使用官方开发的C++插件。 微软开始发力了,Trae用不了最新版的c++插件了 (https://zhuanlan.zhihu.com/p/1907744061080733167) trae插件安装官方文档 https://docs.trae.cn/ide/manage-extensions trae 的文档也侧面证实了这个消息,并以及给出了解决方案。】** 🔥 前言:从Java到C++的转型之旅 大家好,我是一名从Java转型C++的全栈开发者。最近尝试使用Trae IDE(基于VSCode开发的智能编程工具)搭建C++开发环境时,遇到了不少"坑"——从插件安装失败、配置文件报错到依赖库编译错误,踩了很多坑。

By Ne0inhk