当前位置: 首页 > news >正文

【Linux】Socket编程—TCP

🔥 个人主页:大耳朵土土垚
🔥 所属专栏:Linux系统编程

这里将会不定期更新有关Linux的内容,欢迎大家点赞,收藏,评论🥳🥳🎉🎉🎉

文章目录

  • 1. TCP socket API 详解
    • socket()
    • bind()
    • listen()
    • accept()
    • connect()
  • 2. Echo Server
    • TCP服务器
      • 多进程版本
      • 多线程版本
      • 线程池版本
    • TCP客户端

1. TCP socket API 详解

  下面介绍程序中用到的 socket API,这些函数都在 sys/socket.h 中。

socket()

在这里插入图片描述

  • 作用:打开一个网络通讯端口,如果成功的话,就像 open()一样返回一个文件描述符; 应用程序可以像读写文件一样用 read/write 在网络上收发数据;
  • 返回值:如果 socket()调用出错则返回-1;
  • 参数:对于 IPv4, family 参数指定为 AF_INET; 对于 TCP 协议,type 参数指定为SOCK_STREAM, 表示面向流的传输协议; protocol 参数的介绍从略,指定为 0 即可。

bind()

在这里插入图片描述

  • 介绍:服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接; 服务器需要调用 bind 绑定一个固定的网络地址和端口号;

  • 返回值: bind()成功返回 0,失败返回-1。

  • 作用:将参数 sockfdmyaddr 绑定在一起, 使 sockfd 这个用于网络通讯的文件描述符监听 myaddr 所描述的地址和端口号;

  • 参数: 前面讲过,struct sockaddr *是一个通用指针类型,myaddr 参数实际上可以接受多种协议的 sockaddr 结构体,而它们的长度各不相同,所以需要第三个参数 addrlen指定结构体的长度;我们的程序中对 myaddr 参数是这样初始化的:
    在这里插入图片描述
      1. 将整个结构体清零;
      2. 设置地址类型为 AF_INET;
      3. 网络地址为 INADDR_ANY, 这个宏表示本地的任意 IP 地址,因为服务器可能有
    多个网卡,每个网卡也可能绑定多个 IP 地址, 这样设置可以在所有的 IP 地址上监听, 直到与某个客户端建立了连接时才确定下来到底用哪个 IP 地址;
      4. 端口号为 SERV_PORT, 我们定义为 8080;

listen()

在这里插入图片描述

  • 介绍:listen()声明 sockfd 处于监听状态, 并且最多允许有 backlog 个客户端处于连接
    等待状态, 如果接收到更多的连接请求就忽略, 这里设置不会太大(一般是 5);
  • 返回值:listen()成功返回 0,失败返回-1;

accept()

在这里插入图片描述

  • 介绍:三次握手完成后, 服务器调用 accept()接受连接; 如果服务器调用 accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来;
  • 参数:addr 是一个传出参数,accept()返回时传出客户端的地址和端口号; 如果给 addr 参数传 NULL,表示不关心客户端的地址; addrlen 参数是一个传入传出参数(value-result argument), 传入的是调用者提供的, 缓冲区 addr 的长度以避免缓冲区溢出问题, 传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区);

我们的服务器程序结构是这样的:
在这里插入图片描述

  • 返回值:sockfd用来进行通信

connect()

在这里插入图片描述

  • 介绍:客户端需要调用 connect()连接服务器;
  • 参数:connectbind 的参数形式一致, 区别在于 bind 的参数是自己的地址, 而
    connect 的参数是对方的地址;
  • 返回值: connect()成功返回 0,出错返回-1;

2. Echo Server

  有了上面的接口,我们就可以实现以TCP为基础的简单消息回显服务器了,运行结果应该如下图所示:

在这里插入图片描述
代码如下:

TCP服务器

#pragma once#include <iostream>
#include <string.h>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include <functional>
#include "InetAddr.hpp"
#include "Log.hpp"
#include "Common.hpp"#define BACKLOG 8
using namespace InetAddrModule;
using namespace LogModule;
static const uint16_t defaultport = 8888;class TcpServer
{
public:TcpServer(uint16_t port = defaultport) : _port(port), _listensockfd(-1), _isruning(false){}void InitServer(){// 1.创建Tcp套接字_listensockfd = ::socket(AF_INET, SOCK_STREAM, 0);if (_listensockfd < 0){LOG(LogLevel::ERROR) << "InitServer socket fail ...";Die(SOCKET_ERR);}// 填充信息struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_port = ::htons(_port);//aaa注意要转网络!!!!!!!!!!serveraddr.sin_addr.s_addr = INADDR_ANY; // 表示可以接收任意地址的信息// 2. bind;int n = ::bind(_listensockfd, CONV(&serveraddr), sizeof(serveraddr));if (n < 0){LOG(LogLevel::ERROR) << "InitServer bind fail ...";Die(BIND_ERR);}// 3.监听int m = ::listen(_listensockfd, BACKLOG);if (m < 0){LOG(LogLevel::ERROR) << "InitServer listen fail ...";Die(LISTEN_ERR);}LOG(LogLevel::INFO) << "ServerInit success...";}void handler(int sockfd){char buffer[4096];while (true){ssize_t n = ::read(sockfd, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = 0;LOG(LogLevel::INFO) << buffer;std::string echo_string = "server echo# ";echo_string += buffer;::write(sockfd, echo_string.c_str(),echo_string.size());}else if (n == 0) // client 退出{LOG(LogLevel::INFO) << "client quit: " << sockfd;break;}else{// 读取失败break;}}::close(sockfd); // fd泄漏问题!}void Start(){_isruning = true;while(_isruning){struct sockaddr_in peer;socklen_t peerlen = sizeof(peer); // 这个地方一定要注意,要不然,会有问题!LOG(LogLevel::DEBUG) << "accepting ...";// 我们要获取client的信息:数据(sockfd)+client socket信息(accept || recvfrom)int sockfd = ::accept(_listensockfd, CONV(&peer), &peerlen);if (sockfd < 0){LOG(LogLevel::ERROR) << "StartServer accept fail ...";continue; // 继续接收}LOG(LogLevel::INFO)<<"ServerStart success...";// 连接成功后就可以通信handler(sockfd);}_isruning = false;}~TcpServer(){}private:uint16_t _port;int _listensockfd;bool _isruning;
};               

与Udp服务器不同的是,Tcp服务要求我们先调用listen接口监听,然后在通过accept和客户端使用connet建立连接后才可以进行通信;所以如果仅仅使用单进程是无法满足同时接收多个客户端的消息,下面将会给出多进程、多线程以及基于线程池实现的Tcp服务。

多进程版本

//其他的不变
void Start(){_isruning = true;while(_isruning){struct sockaddr_in peer;socklen_t peerlen = sizeof(peer); // 这个地方一定要注意,要不然,会有问题!LOG(LogLevel::DEBUG) << "accepting ...";// 我们要获取client的信息:数据(sockfd)+client socket信息(accept || recvfrom)int sockfd = ::accept(_listensockfd, CONV(&peer), &peerlen);if (sockfd < 0){LOG(LogLevel::ERROR) << "StartServer accept fail ...";continue; // 继续接收}LOG(LogLevel::INFO)<<"ServerStart success...";// 连接成功后就可以通信//version1: 多进程pid_t id = ::fork();if(id == 0)//子进程{::close(_listensockfd);//要关掉不需要的文件描述符,避免fd泄露问题if(fork())//子进程再创建孙子进程::exit(0);//让子进程退出,孙子进程成为孤儿进程,这样就不用父进程回收//孙子进程处理,结束后由操作系统回收handler(sockfd);::exit(0);}::close(sockfd);//子进程退出后,父进程就不会阻塞在这里,继续接收其他客户端连接int rid = ::waitpid(id, nullptr, 0);if(rid < 0)LOG(LogLevel::WARNING) << "ServerStart waitpid error...";}_isruning = false;}

对于多进程,首先每个进程都有自己的文件描述符表,所以父子进程都需要关闭自己不需要的文件描述符;

其次父进程需要等待回收子进程,此时父进程会阻塞直到子进程完成通信,这样和之前单进程通信效果一样,所以为了不让父进程阻塞,子进程需要再创建子进程,用它来完成通信,此时父进程就可以直接回收子进程,孙子进程就成为孤儿进程进行通信,结束后由操作系统回收。

多线程版本

    struct ThreadData{int sockfd;TcpServer *self;};static void *ThreadEntry(void *args){pthread_detach(pthread_self()); // 线程分离,线程执行结束后自动被系统回收ThreadData *data = (ThreadData *)args;data->self->handler(data->sockfd);return nullptr;}void Start(){_isruning = true;while (_isruning){struct sockaddr_in peer;socklen_t peerlen = sizeof(peer); // 这个地方一定要注意,要不然,会有问题!LOG(LogLevel::DEBUG) << "accepting ...";// 我们要获取client的信息:数据(sockfd)+client socket信息(accept || recvfrom)int sockfd = ::accept(_listensockfd, CONV(&peer), &peerlen);if (sockfd < 0){LOG(LogLevel::ERROR) << "StartServer accept fail ...";continue; // 继续接收}LOG(LogLevel::INFO) << "ServerStart success...";// 连接成功后就可以通信// version2: 多线程// 主线程和新线程共享一张文件描述符表pthread_t tid;ThreadData *data = new ThreadData;data->sockfd = sockfd;data->self = this;pthread_create(&tid, nullptr, ThreadEntry, data);
}_isruning = false;}

设置线程分离这样线程执行完毕后就可以自动被系统回收

线程池版本

using task_t = std::function<void()>;void Start(){_isruning = true;while (_isruning){struct sockaddr_in peer;socklen_t peerlen = sizeof(peer); // 这个地方一定要注意,要不然,会有问题!LOG(LogLevel::DEBUG) << "accepting ...";// 我们要获取client的信息:数据(sockfd)+client socket信息(accept || recvfrom)int sockfd = ::accept(_listensockfd, CONV(&peer), &peerlen);if (sockfd < 0){LOG(LogLevel::ERROR) << "StartServer accept fail ...";continue; // 继续接收}LOG(LogLevel::INFO) << "ServerStart success...";// version-3:线程池版本 比较适合处理短任务,或者是用户量少的情况ThreadPool<task_t>::GetInstance()->Enqueue([this, sockfd](){ this->handler(sockfd); });}_isruning = false;}

引入之前实现的线程池,并使用单例模式

使用服务器代码如下:

#include "TcpServer.hpp"int main()
{std::unique_ptr<TcpServer> tcpserver = std::make_unique<TcpServer>();tcpserver->InitServer();tcpserver->Start();return 0;
}

TCP客户端

#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"using namespace LogModule;
using namespace InetAddrModule;int sockfd = -1;//./udp_client server_ip server_port
int main(int argc, char *argv[])
{if (argc != 3){LOG(LogLevel::ERROR) << "Usage:" << argv[0] << " serverip serverport";Die(ARGV_ERR);}// 1.创建sockfdsockfd = ::socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){LOG(LogLevel::WARNING) << "client sockfd fail...";Die(SOCKET_ERR);}// 2.填充服务器信息std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);// InetAddr serveraddr(serverip, serverport); struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(serverport);serveraddr.sin_addr.s_addr = inet_addr(serverip.c_str());// 3.与服务器建立连接int n = ::connect(sockfd, CONV(&serveraddr), sizeof(serveraddr));if (n < 0){LOG(LogLevel::ERROR) << "ClientConnet fail...";Die(CONNET_ERR);}// 4. 发送请求给服务器while (true){// 4.1获取信息std::cout << "Please Enter# ";std::string message;std::getline(std::cin, message);// 4.2发送信息给服务器ssize_t n = ::sendto(sockfd, message.c_str(), sizeof(message), 0, CONV(&serveraddr), sizeof(serveraddr));if (n < 0){LOG(LogLevel::ERROR) << "client sendto fail...";continue;}// 4.3从服务器接收信息char buffer[1024];struct sockaddr_in tmp;socklen_t len = sizeof(tmp);ssize_t m = ::recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, CONV(&tmp), &len);if (m > 0){buffer[m] = 0;std::cout << buffer << std::endl;}else{LOG(LogLevel::ERROR) << "client recvfrom fail...";}}::close(sockfd);return 0;
}

与UDP客户端相比,TCP客户端需要与服务器通过connet连接后才能通信。

相关文章:

【Linux】Socket编程—TCP

&#x1f525; 个人主页&#xff1a;大耳朵土土垚 &#x1f525; 所属专栏&#xff1a;Linux系统编程 这里将会不定期更新有关Linux的内容&#xff0c;欢迎大家点赞&#xff0c;收藏&#xff0c;评论&#x1f973;&#x1f973;&#x1f389;&#x1f389;&#x1f389; 文章目…...

c++11 for auto不定参数

数量不定的模板参数。参数分为一个和一包两部分。 ​ 冒号的左边声明一个变量。右手边必须是一个容器。从容器(某种数据结构)中找出每一个元素设置到左边这个变量。11之前可以用容器的迭代器去取数据。或者标准库里的foreach...

C#+redis实现消息队列的发布订阅功能

代码 参考c#redis stream实现消息队列以及ack机制文章的思路&#xff0c;实现 SubscribeAttribute.cs using System;namespace DotnetQueue.Attributes {/// <summary>/// 订阅特性/// </summary>[AttributeUsage(AttributeTargets.Method, Inherited false)]pu…...

Docker容器基本操作

容器的基本操作 操作命令&#xff08;全&#xff09;命令&#xff08;简&#xff09;容器的创建docker container run <image name>docker run <image name>容器的列出&#xff08;up&#xff09;docker container lsdocker ps容器的列出&#xff08;up和exit&…...

从无序到有序:上北智信通过深度数据分析改善会议室资源配置

当前企业普遍面临会议室资源管理难题&#xff0c;预约机制不完善和临时会议多导致资源调度不合理&#xff0c;既有空置又有过度拥挤现象。 针对上述问题&#xff0c;上北智信采用了专业数据分析手段&#xff0c;巧妙融合楼层平面图、环形图、折线图和柱形图等多种可视化工具&a…...

总结:使用JDK原生HttpsURLConnection,封装HttpsUtil工具类,加载自定义证书验证,忽略ssl证书验证

总结&#xff1a;使用JDK原生HttpsURLConnection&#xff0c;封装HttpsUtil工具类&#xff0c;加载自定义证书验证&#xff0c;忽略ssl证书验证 一HttpsUtil工具类二SSLUtil工具类 一HttpsUtil工具类 package com.example.util;import javax.net.ssl.HttpsURLConnection; impo…...

重新定义人机关系边界,Soul以AI社交构建多元社交元宇宙

近年来,AI Native应用的兴起已逐渐成为大众关注的焦点。在此背景下,Soul App的首席技术官陶明在极客公园IF2025创新大会上,发表了一场主题为“人机关系的新边界,Soul如何定义AI社交未来”的演讲。他分享了Soul在人工智能领域内的最新技术进展和战略规划,同时也将Soul社交元宇宙…...

HTTP 参数污染(HPP)详解

1. 什么是 HTTP 参数污染&#xff08;HPP&#xff09;&#xff1f; HTTP 参数污染&#xff08;HTTP Parameter Pollution&#xff0c;简称 HPP&#xff09;是一种 Web 应用攻击技术&#xff0c;攻击者通过在 HTTP 请求中注入多个相同的参数来绕过安全控制或篡改应用逻辑&#…...

阿里云轻量服务器docker部署nginx

拉取nginx docker镜像 sudo docker pull nginx创建以下挂载目录及文件 用户目录下&#xff1a;conf html logs conf: conf.d nginx.conf html: index.html conf.d: default.confnginx.conf添加文件内容 events {worker_connections 1024; }http {include /etc/ngi…...

(萌新入门)如何从起步阶段开始学习STM32 —— 我应该学习HAL库还是寄存器库?

概念 笔者下面需要介绍的是库寄存器和HAL库两个重要的概念&#xff0c;在各位看完之后&#xff0c;需要决定自己的学习路线到底是学习HAL呢&#xff1f;还是寄存器呢&#xff1f;还是两者都学习呢&#xff1f; 库寄存器 库寄存器就是简单的封装了我们对寄存器的操作&#xf…...

Windchill开发-电子仓相关对象信息查询SQL

电子仓相关对象信息查询SQL 一、说明二、数据表信息三、数据表字段说明3.1 HOLDERTOCONTENT3.1.1 对象类型3.1.2 存储类型 3.2 APPLICATIONDATA3.2.1 类别3.2.2 与对象的角色关系3.2.3 存储方式3.2.4 其他字段 3.3 URLDATA3.4 STREAMDATA3.5 FVITEM3.6 FVMOUNT3.6.1 安装状态3.…...

MySQL 数据库定时任务及进阶学习

一、引言 在当今数字化时代&#xff0c;数据管理的高效性和自动化至关重要。MySQL 作为一款广泛应用的开源关系型数据库管理系统&#xff0c;提供了强大的功能来满足各种数据处理需求。其中&#xff0c;定时任务执行功能对于自动化数据操作、维护数据完整性以及优化系统性能具…...

DeepSeek教unity------MessagePack-01

中文&#xff1a;GitCode - 全球开发者的开源社区,开源代码托管平台 MessagePack是C# 的极速 MessagePack 序列化器。它比 MsgPack-Cli 快 10 倍&#xff0c;并且性能超过其他 C# 序列化器。MessagePack for C# 还内置支持 LZ4 压缩——一种极其快速的压缩算法。性能在诸如游戏…...

知识拓展:Python序列化模块 marshal 模块详解

Python marshal 模块学习笔记 1. 简介 marshal 是 Python 的内部序列化格式&#xff0c;主要用于序列化和反序列化 Python 对象。它是 Python 字节码&#xff08;.pyc文件&#xff09;使用的序列化格式&#xff0c;比 pickle 更原始和受限&#xff0c;但也更快速和安全。 http…...

leetcode 2684. 矩阵中移动的最大次数

题目如下 数据范围 本题使用常规动态规划就行&#xff0c;不过要注意由于有三个转移的方向&#xff0c;所以我们对dp数组的遍历应该是从上到下 从左到右即按列优先遍历。通过代码 class Solution { public:int maxMoves(vector<vector<int>>& grid) {int …...

机械学习基础-6.更多分类-数据建模与机械智能课程自留

data modeling and machine intelligence - FURTHER CLASSIFICATION 混淆矩阵评估指标&#xff1a;灵敏度和特异度ROC 曲线文字说明部分 AUC&#xff1a;ROC曲线下面积 支持向量机思路补充背景知识点积超平面&#xff08;HYPERPLANES超平面的法向量到超平面的最小距离数据集与超…...

自动化测试实战

http://8.137.19.140:9090/blog_login.htm 账号: lisi 密码: 123456 上面是系统链接 1. 自动化测试的步骤 1.1 编写Web测试用例 1.2 创建空项目添加依赖 然后我们创建一个新的java项目(使用maven管理),然后引入我们的配置文件:屏幕截图,驱动管理,selenium库 <dependency…...

qt QPlainTextEdit总结

QPlainTextEdit 概述 用途&#xff1a;专为处理纯文本设计&#xff0c;适合大文本编辑和简单文本显示&#xff08;如日志、代码编辑器&#xff09;。 特点&#xff1a;相比QTextEdit&#xff0c;轻量高效&#xff0c;支持快速加载和滚动大文件&#xff0c;默认不支持富文本。 …...

AWS SES 邮件服务退信/投诉处理与最佳实践指南

在使用 AWS SES 发送邮件时,合理处理退信和投诉是维护发送声誉的关键。本文将详细介绍 SES 中的退信/投诉处理机制以及相关最佳实践。 一、退信处理机制 © ivwdcwso (ID: u012172506) 1.1 退信类型 在 SES 中,退信分为两种类型: 硬退信(Hard Bounce) 永久性错误,如无效…...

理解WebGPU 中的 GPUAdapter :连接浏览器与 GPU 的桥梁

在 WebGPU 开发中&#xff0c; GPUAdapter 是一个至关重要的对象&#xff0c;它作为浏览器与 GPU 之间的桥梁&#xff0c;为开发者提供了请求 GPU 设备、查询 GPU 特性以及获取适配器信息的能力。本文将详细介绍 GPUAdapter 的核心属性和方法&#xff0c;并通过实际代码…...

conda相比python好处

Conda 作为 Python 的环境和包管理工具&#xff0c;相比原生 Python 生态&#xff08;如 pip 虚拟环境&#xff09;有许多独特优势&#xff0c;尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处&#xff1a; 一、一站式环境管理&#xff1a…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻

在如今就业市场竞争日益激烈的背景下&#xff0c;越来越多的求职者将目光投向了日本及中日双语岗位。但是&#xff0c;一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧&#xff1f;面对生疏的日语交流环境&#xff0c;即便提前恶补了…...

模型参数、模型存储精度、参数与显存

模型参数量衡量单位 M&#xff1a;百万&#xff08;Million&#xff09; B&#xff1a;十亿&#xff08;Billion&#xff09; 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的&#xff0c;但是一个参数所表示多少字节不一定&#xff0c;需要看这个参数以什么…...

电脑插入多块移动硬盘后经常出现卡顿和蓝屏

当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时&#xff0c;可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案&#xff1a; 1. 检查电源供电问题 问题原因&#xff1a;多块移动硬盘同时运行可能导致USB接口供电不足&#x…...

Cinnamon修改面板小工具图标

Cinnamon开始菜单-CSDN博客 设置模块都是做好的&#xff0c;比GNOME简单得多&#xff01; 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

css的定位(position)详解:相对定位 绝对定位 固定定位

在 CSS 中&#xff0c;元素的定位通过 position 属性控制&#xff0c;共有 5 种定位模式&#xff1a;static&#xff08;静态定位&#xff09;、relative&#xff08;相对定位&#xff09;、absolute&#xff08;绝对定位&#xff09;、fixed&#xff08;固定定位&#xff09;和…...

【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)

要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况&#xff0c;可以通过以下几种方式模拟或触发&#xff1a; 1. 增加CPU负载 运行大量计算密集型任务&#xff0c;例如&#xff1a; 使用多线程循环执行复杂计算&#xff08;如数学运算、加密解密等&#xff09;。运行图…...

Rust 异步编程

Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...

HTML前端开发:JavaScript 常用事件详解

作为前端开发的核心&#xff0c;JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例&#xff1a; 1. onclick - 点击事件 当元素被单击时触发&#xff08;左键点击&#xff09; button.onclick function() {alert("按钮被点击了&#xff01;&…...

C++使用 new 来创建动态数组

问题&#xff1a; 不能使用变量定义数组大小 原因&#xff1a; 这是因为数组在内存中是连续存储的&#xff0c;编译器需要在编译阶段就确定数组的大小&#xff0c;以便正确地分配内存空间。如果允许使用变量来定义数组的大小&#xff0c;那么编译器就无法在编译时确定数组的大…...