网络编程:多进程和多线程编程

1. 多进程编程

1.1 fork

#include <sys/types.h>
#include <unistd.h>
// 调用失败返回 -1 设置 errno
pid_t fork( void );

子进程返回 0,父进程返回子进程 PID;

信号位图被清除(父进程的信号处理函数不再对新进程起作用)

1.2 exec 系列系统调用

替换当前进程映像,一般情况下不会返回,出错后返回 -1 并设置 errno;

注意:exec 函数不会关闭原程序打开的文件描述符,除非该 fd 设置了 SOCK_CLOEXEC 属性;

1.3 处理僵尸进程

#include <sys/types.h>
#include <sys/wait.h>
// 调用失败返回 -1 设置 errno
pid_t wait( int* stat_loc );
pid_t waitpid( pid_t pid, int* stat_loc. int options );

waitpid 只等待 pid 指定的子进程;pid 取值为 -1,和 wait 相同,等待任意一个子进程结束;
options 取值是 WNOHANG 时,waitpid 是非阻塞的:pid 指定的子进程没有结束或意外终止,立即返回 0;子进程正常退出,返回子进程 PID;

1.4 信号量

1.4.1 semget

创建一个新的信号量集,或者获取一个已经存在的信号量集;

#include <sys/sem.h>
// 成功返回一个正整数,标识信号量集
// 失败返回 -1 设置 errno
int semget( key_t key, int num_sems. int sem_flags );

key 用来标识一个全局唯一的信号量集;传递特殊键值 IPC_PRIVATE(值为 0)可以保证总是创建新的信号量集;
num_sems 指定信号量集中信号量的数目;(若是创建,必须指定;若是获取,设为 0)
sem_flags 指定该信号量的权限, 后 9 位与open中的 mode 一致;

#include <sys/sem.h>
struct ipc_perm
{key_t key;		/* 键值 */uid_t uid;		/* 所有者的有效用户 ID */gid_t gid;		/* 所有者的有效组 ID */uid_t cuid;		/* 创建者的有效用户 ID */gid_t cgid;		/* 创建者的有效组 ID */mode_t mode;	/* 访问权限 *//* 省略其他填充字段 */
};struct semid_ds
{struct ipc_perm sem_perm;		/* 信号量的操作权限 */unsigned long int sem_nsems;	/* 信号量集的信号量数目 */time_t sem_otime;				/* 最后一次调用 semop 的时间 */time_t sem_ctime;				/* 最后一次调用 semctl 的时间 *//* 键值 */
};	

semid_ds 是与信号量集相关联的内核数据结构,semget作用是创建并初始化它,会将 sem_otime 设为 0,sem_ctime 设为当前系统时间;

1.4.2 semop

改变信号量的值,是原子操作;

unsigned short semval;			/* 信号量的值 */
unsigned short semzcnt;			/* 等待信号值变为 0 的进程数量 */
unsigned short semncnt;			/* 等待信号值增加的进程数量 */
pid_t sempid;					/* 最后一次执行 semop 操作的进程 ID */
int semop( int sem_id, struct sembuf* sem_ops, size_t num_sem_ops );

sem_id 是 semget返回的信号量集标识符;sem_ops 指向 sembuf 数组;

struct sembuf
{unsigned short int sem_num;short int sem_op;short int sem_flg;
}

sem_num 为信号集中信号量的编号;

在这里插入图片描述
SEM_UNDO 的含义是,当进程退出时取消正在进行的 semop 操作,会更新进程的 semadj 变量,,来跟踪进程对信号量的修改情况;

1.4.3 semctl

对信号量直接控制

int semctl( int sem_id, int sem_num, int command, ... );

command 为要执行的命令,有些命令需要第 4 个参数,推荐如下形式

union semun
{int val;					/*用于 SETVAL 命令*/struct semid_ds* buf;		/*用于 IPC_STAT 和 IPC_SET 命令*/unsigned short* array;		/*用于 GETALL 和 SETALL 命令*/struct seminfo* __buf;		/*用于 IPC_INFO 命令*/
};

常用命令为

命令含义成功时返回值
IPC_RMID移除信号量集,唤醒所有等待该信号量集的进程(semop返回错误,errno 为 EIDRM)0
SETVAL将信号量的值设为 semun.val ,同时内核数据中的 semid_ds.sem_ctime 被更新0

1.5 共享内存

最高效的 IPC 机制;

1.5.1 shmget

创建一段新的共享内存或获取一段已经存在的共享内存;

#include <sys/shm.h>
// 成功返回正整数,为共享内存的标识;失败返回 -1 设置 errno
int shmget( key_t key, size_t size, int shmflg );

这三个参数与 semget含义相同,size 指定共享内存的大小,单位是字节;
shmflg 支持额外的两个标志:

  • SHM_HUGETLB;系统使用“大页面”分配共享内存,类似于 mmap的 MAP_HUGETLB 标志;
  • SHM_NORESERVE;不为共享内存保留交换分区,类似于 mmap的 MAP_NORESERVE 标志;这样当物理内存不足时,对该共享内存执行写操作将触发 SIGSEGV 信号;

shmget创建共享内存,所有字节都被初始化为 0,与之关联的内核数据结构为 shmid_ds 被创建和初始化;

struct shmid_ds
{struct ipc_perm shm_perm;	/* 共享内存的操作权限 */size_t shm_segsz;			/* 共享内存的大小,单位是字节 */__time_t shm_atime;			/* 对这段内存最后一次调用 shmat 的时间 */__time_t shm_dtime;			/* 对这段内存最后一次调用 shmdt 的时间 */__time_t shm_ctime;			/* 对这段内存最后一次调用 shmctl 的时间 */__pid_t shm_cpid;			/* 创建者的 PID */__pid_t shm_lpid;			/* 最后一次执行 shmat 或 shmdt 操作的进程 PID */shmatt_t shm_nattach;		/* 关联到此共享内存的进程数量 *//* 省略填充字段 */
};

初始化时,shm_lpid、shm_nattach、shm_atime、shm_dtime 设置为 0,shm_ctime 设置为当前时间;

1.5.2 shmat 和 shmdt

关联/分离到进程的地址空间

void* shmat( int shm_id, const void* shm_addr, int shmflg );
int shmdt( const void* shm_addr );

shm_addr 指定将共享内存关联到进程的哪块空间地址;

  • shm_addr 为 NULL,由操作系统选择,推荐的做法;
  • shm_addr 非空,并且 SHM_RND 未设置,共享内存关联到 addr 指定地址处;
  • shm_addr 非空,并且设置了 SHM_RND ,被关联的地址是 [shm_addr - (shm_addr%SHMLBA)];

shmflg 还支持如下标志:

  • SHM_RDONLY;进程仅能读取共享内存中的内容;
  • SHM_REMAP;如果地址 shm_addr 已经被关联到一段共享内存上,重新关联;
  • SHM_EXEC;指定对共享内存段的执行权限;

shmat 成功返回被关联的地址,失败返回(void *) -1 并设置 errno;将 shm_nattach++,shm_lpid 设置为调用进程 PID,shm_atime 设置为当前时间;

shmdt 成功时返回 0,失败返回 -1 设置 errno;将 shm_nattach–,shm_lpid 设置为调用进程 PID,shm_dtime 设置为当前时间;

1.5.3 shmctl
// 失败时返回 -1 设置 errno
int shmctl( int shm_id, int command, struct shmid_ds* buf );
命令含义成功时返回值
SETVAL将信号量的值设为 semun.val ,同时内核数据中的 semid_ds.sem_ctime 被更新0
1.5.4 共享内存的 POSIX 方法

通过打开同一个文件, mmap 可以实现进程间的内存共享;
Linux 提高了另一种利用 mmap 在无关进程间共享内存的方式,无需任何文件的支持;

#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
// 创建或打开一个 POSIX 共享内存对象
int shm_open( const char* name, int oflag, mode_t mode );
int shm_unlink( const char* name );

shm_open 成功返回一个文件描述符,该 fd 可用于后续的 mmap 调用,失败返回 -1 设置 errno。
shm_unlink 将 name 指定共享内存对象标记为等待删除;当所有使用该共享对象的进程都使用 ummap将它从进程中分离后,系统将销毁该对象;

ftruncate(fd, length)会将参数 fd 指定的文件大小改为参数 length 指定的大小。

1.6 消息队列

两个进程间传递二进制块数据一种简单有效的方式;
每个数据块都有一个特定的类型,接收方可以根据类型来选择地接收数据;

1.6.1 msgget

创建或获取消息队列

#include <sys/msg.h>
int msgget( key_t key, int msgflg );

参数含义与 semget一致,与之关联的内核数据结构 msqid_ds 被创建并初始化;

struct msqid_ds
{struct ipc_perm msg_perm;		/* 操作权限 */time_t msg_stime;				/* 最后一次调用 msgsnd 的时间 */time_t msg_stime;				/* 最后一次调用 msgrcv 的时间 */time_t msg_stime;				/* 最后一次被修改的时间 */unsigned long __msg_cbytes;		/* 消息队列中已有的字节数 */msgqnum_t msg_qnum;				/* 消息队列中已有的消息数 */msglen_t msg_qbytes;			/* 消息队列允许的最大字节数 */pid_t msg_lspid;				/* 最后执行 msgsnd 的进程 PID */pid_t msg_lrpid;				/* 最后执行 msgrcv 的进程 PID */
};
1.6.2 msgsnd

把一条消息添加到消息队列中;

// 成功返回 0,失败返回 -1
int msgsnd( int msqid, const void* msg_ptr, size_t msg_sz, int msgflg );

msg_ptr 指向一个准备发送的消息

struct msgbuf
{long mtype;			/* 消息类型,必须为正整数 */char mtext[512];	/* 消息数据 */
};

msg_sz 是消息数据部分的长度,可以为 0 表示没有消息数据;
msgflg 设置 IPC_NOWAIT 标志时,表示非阻塞;如果消息队列满,默认情况下会阻塞,如果设置了 IPC_NOWAIT 将立即返回并设置 errno 为 EAGAIN;

处于阻塞状态的 msgsnd 可能被中断:

  • 消息队列被移除,会立即返回,errno 为 EIDRM;
  • 程序接收到信号,立即返回,errno 为 EINTR;
1.6.3 msgrcv

从消息队列中获取消息

// 成功返回 0,失败返回 -1
int msgrcv( int msqid, void* msg_ptr, size_t msg_sz, long int msgtype, int msgflg );

(1)msg_ptr 用于存储接收到的消息;
(2)msgtype 指定接收何种类型的消息;

  • 为 0,表示读取消息队列中的第一个消息;
  • 大于 0,读取第一个类型为 msgtype 的消息;
  • 小于0,读取第一个类型值比 msgtype 绝对值小的消息;

(3)msgflg

  • IPC_NOWAIT;如果消息队列为空,立即返回,errno 设为 ENOMSG;
  • MSG_EXCEPT;msgtype 大于 0,接收第一个非 msgtype 的消息;
  • MSG_NOERROR;如果消息数据部分长度超过了 msg_sz,将其截断;

(4)阻塞的 msgrcv 被中断情况与 msgsnd 一致;

1.6.4 msgctl
// 失败返回 -1
int msgctl( int msqid, int command, struct msqid_ds* buf );

1.7 IPC 命令

上述 3 种进程间通信方式都使用了全局唯一的键值来描述一个共享资源;
Linux 下 ipcs 命令可以查看当前系统上的共享资源实例;

1.8 进程间传递文件描述符

可以利用 UNIX 域 socket 在进程间传递特殊辅助数据

利用 sendmsgrecvmsg 可以发送附属数据

2. 多线程编程

内核线程:运行在内核空间,由内核调度;
用户线程:运行在用户空间,由线程库调度;

当进程的一个内核线程获得 CPU 使用权时,加载并允许一个用户线程,内核线程相当于用户线程运行的“容器”;

一个进程可以拥有 M 个内核线程和 N 个用户线程,M <= N,它们之间的比值是固定的;

(1)完全在用户空间实现;

M:N = 1:N

线程库利用 longjmp 来切换线程;一个进程的所有执行线程共享该进程的时间片,对外表现出相同的优先级;

优点:创建和调度线程的速度非常快,不需要内核参与,不占用额外的内核资源;

缺点:一个进程的多个线程无法运行在不同的 CPU 上;

(2)完全由内核调度;

M:N = 1:1,与上述优缺点互换

(3)双层调度;结合了前两种方式的优点;

2.1 线程库

Linux 下目前基本使用的都是 NPTL,采用 1:1 方式实现的;

2.2 创建和结束线程

(1)

#include <pthread.h>
// 成功返回 0,失败返回错误码
int pthread_create( pthread_t* thread, const pthread_attr_t* attr,void* (*start_routine)( void* ), void* arg );

thread 的类型 pthread_t 是无符号长整形,为新线程的标识符;
attr 设置新线程的属性,设为 NULL 代表使用默认线程属性;

一个用户可打开的线程数量不能超过 RLIMIT_NPROC 限制;

(2)在结束时最好调用如下函数;

void pthread_exit( void* retval );

retval 向线程的回收者传递其退出信息;

(3)等待其他线程结束(回收)

int pthread_join( pthread_t thread, void** retval );

会一直阻塞直到被回收的线程结束;
成功返回 0,失败返回错误码;

错误码描述
EDEADLK可能引起死锁;比如两个线程互相针对对方调用 pthread_join 或 线程对自身调用 pthread_join
EINVAL目标线程不可回收,或已经有其他线程在回收该目标线程
ESRCH目标线程不存在

(4)异常终止一个线程;

int pthread_cancel( pthread_t thread );

接收到这个请求的目标线程可以决定是否允许被取消以及如何取消;

int pthread_setcancelstate( int state, int* oldstate );
int pthread_setcanceltype( int type, int* oldtype );

第一个参数分别用于设置线程的取消状态(是否允许取消)和取消类型(如何取消)
第二个参数记录了原来的状态和类型;

state 可选值为:

  • PTHREAD_CANCEL_ENABLE,允许线程被取消,线程创建时的默认状态
  • PTHREAD_CANCEL_DISABLE,禁止线程被取消;如果收到取消请求,会将请求挂起,直到该线程允许被取消

type 可选值为:

  • PTHREAD_CANCEL_ASYNCHRONOUS,线程随时都可以被取消,接收到取消请求立即采取行动;
  • PTHREAD_CANCEL_DEFERRED,允许目标线程推迟行动,直到调用了下面几个取消点函数中的一个:pthread_join、pthread_testcancel、pthread_cond_wait、pthread_cond_timedwait、sem_wait 和 sigwait;

2.3 线程属性

各种线程属性全部包含在一个字符数组中;

#include <bits/pthreadtypes.h>
#define __SIZEOF_PTHREAD_ATTR_T 36
typedef union
{char __size[__SIZEOF_PTHREAD_ATTR_T];long int __align;
} pthread_attr_t;
// 初始化线程属性对象
int pthread_attr_init( pthread_attr_t* attr );
// 销毁线程属性对象,被销毁的对象只有再次初始化之后才能继续使用
int pthread_attr_destroy( pthread_attr_t* attr );
// 获取或设置线程属性对象的某个属性
int pthread_attr_getdetachstate( const pthread_attr_t* attr, int* detachstate );
int pthread_attr_setdetachstate( pthread_attr_t* attr, int detachstate );
int pthread_attr_getstackaddr( const pthread_attr_t* attr, void** stackaddr );
int pthread_attr_setstackaddr( pthread_attr_t* attr, void* stackaddr );
int pthread_attr_getstacksize( const pthread_attr_t* attr, size_t* stacksize );
int pthread_attr_setstacksize( pthread_attr_t* attr, size_t stacksize );
int pthread_attr_getstack( const pthread_attr_t* attr, void** stackaddr, size_t* stacksize );
int pthread_attr_setstack( pthread_attr_t* attr, void* stackaddr, size_t stacksize );
int pthread_attr_getguardsize( const pthread_attr_t* attr, size_t* guardsize);
int pthread_attr_setguardsize( pthread_attr_t* attr, size_t guardsize);
int pthread_attr_getschedparam( const pthread_attr_t* attr, struct sched_param* param );
int pthread_attr_setschedparam( pthread_attr_t* attr, const struct sched_param* param );
int pthread_attr_getschedpolicy( const pthread_attr_t* attr, int* policy );
int pthread_attr_setschedpolicy( pthread_attr_t* attr, int policy );
int pthread_attr_getinheritsched( const pthread_attr_t* attr, int* inherit);
int pthread_attr_setinheritsched( pthread_attr_t* attr, int inherit );
int pthread_attr_getscope( const pthread_attr_t* attr, int* scope );
int pthread_attr_setscope( pthread_attr_t* attr, int scope );
  • detachstate,线程的脱离状态;
    • PTHREAD_CREATE_JOINABLE;可被回收;(默认值)
    • PTHREAD_CREATE_DETACH;脱离线程,退出时自行释放资源;
  • stackaddr 和 stacksize,线程堆栈的起始地址和大小;(一般由系统自动管理)
  • guardsize,保护区域大小;如果大于 0,系统创建线程时会在其堆栈的尾部额外分配 guardsize 字节空间,作为保护堆栈不被错误地覆盖的区域;如果通过 pthread_attr_setstackaddrpthread_attr_setstack手动设置线程的堆栈,会忽略该属性;
  • schedparam,线程调度参数;该结构体目前只有一个整性类型成员 sched_priority,表示线程运行优先级;
  • schedpolicy,线程调度策略;
    • SCHED_FIFO;先进先出调度
    • SCHED_RR;轮转算法调度,这两种调度算法都具备实时调度功能,只能用于以超级用户身份运行的进程;
    • SCHED_OTHER; (默认值)
  • inheritsched,是否继承调用线程的调度属性;PTHREAD_INHERIT_SCHED 和 PTHREAD_EXPLICIT_SCHED,前者表示沿用其创建者的线程调度参数,这种情况下再设置新线程的调度参数将没有任何效果;后者表明需要为新线程设定新的调度参数;
  • scope,线程间竞争 CPU 的范围,即线程优先级的有效范围;目前 Linux 只支持 PTHREAD_SCOPE_SYSTEM 表明与系统中所有线程一起竞争 CPU 的使用;

2.4 POSIX 信号量

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

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

相关文章

【网络编程】网络通信基础——简述TCP/IP协议

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【网络编程】【Java系列】 本专栏旨在分享学习网络编程的一点学习心得&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 一、ip地…

sql_lab之sqli中的报错注入,less13

报错注入&#xff08;less-13&#xff09; 正常报错注入&#xff1a; 1.输入用户名和密码123 123显示登录错误 2.输入用户名和密码123’ 123显示登录错误 123后面有’)说明是’)注入 3.查询数据库名 1) and updatexml(<a><b></b></a>,concat(1111…

大数据知识图谱解码:从核心概念到技术实战

文章目录 大数据知识图谱解码&#xff1a;从核心概念到技术实战1. 概述什么是知识图谱知识图谱与自然语言处理的关系 2. 发展历程语义网络本体论大数据时代的知识图谱知识图谱与深度学习的融合 3. 研究内容知识图谱的建模与表示知识抽取知识图谱的融合与对齐知识图谱的推理知识…

KMP入门级别算法详解--终于解决了(next数组详解)

对于正常的字符串模式匹配&#xff0c;主串长度为m&#xff0c;子串为n&#xff0c;时间复杂度会到达O&#xff08;m*n&#xff09;&#xff0c;而如果用KMP算法&#xff0c;复杂度将会减少线型时间O&#xff08;mn&#xff09;。 设主串为ptr"ababaaababaa";&#…

Prometheus介绍和安装

Prometheus介绍和安装 1. Prometheus介绍 Prometheus&#xff08;普罗米修斯&#xff09;是一个最初在SoundCloud上构建的监控系统。自2012年成为社区开源项目&#xff0c;拥有非常活跃的开发人员和用户社区。为强调开源及独立维护&#xff0c;Prometheus于2016年加入云原生云…

P4 音频知识点——PCM音频原始数据

目录 前言 01 PCM音频原始数据 1.1 频率 1.2 振幅&#xff1a; 1.3 比特率 1.4 采样 1.5 量化 1.6 编码 02. PCM数据有以下重要的参数&#xff1a; 采样率&#xff1a; 采集深度 通道数 ​​​​​​​ PCM比特率 ​​​​​​​ PCM文件大小计算&#xff1a; ​…

[THUPC 2024 初赛] 二进制 (树状数组单点删除+单点查询)(双堆模拟set)

题解 题目本身不难想 首先注意到所有查询的序列长度都是小于logn级别的 我们可以枚举序列长度len&#xff0c;然后用类似滑动窗口的方法&#xff0c;一次性预处理出每种字串的所有出现位置&#xff0c;也就是开N个set去维护所有的位置。预处理会进行O(logn)轮&#xff0c;每…

KubeSphere金丝雀发布流量分布调节不生效(将所有流量按比例分配给灰度发布版本)

如题 金丝雀发布按照流量比例访问不能生效 1、自制应用生成的路由添加注释: nginx.ingress.kubernetes.io/service-upstream:"true" 2、项目网关开启 3、完成 以上&#xff0c;祝好。

Tomcat远程调试

windows环境 写一个 startup-debug.bat&#xff0c;指定tomcat的根目录&#xff0c;端口自己定义 rem *******设置Tomcat目录*******-- set CATALINE_HOMED:\asd\A8-2\tomcat d: rem 8787为可用端口,为远程调试监听端口-- cd %CATALINE_HOME%/bin set JPDA_ADDRESS8787 set J…

Leetcode—445.两数相加II【中等】

2023每日刷题&#xff08;六十七&#xff09; Leetcode—445.两数相加II 实现代码 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2…

一个简单的 HTTP 请求和响应服务——httpbin

拉取镜像 docker pull kennethreitz/httpbin:latest 查看本地是否存在存在镜像 docker images | grep kennethreitz/httpbin:latest 创建 deployment&#xff0c;指定镜像 apiVersion: apps/v1 kind: Deployment metadata:labels:app: httpbinname: mm-httpbinnamespace: mm-…

Latex生成的PDF中加入书签/Navigation/导航

本文参考&#xff1a;【Latex学习】在生成pdf中加入书签/目录/提纲_latex 书签-CSDN博客 &#xff08;这篇文章写的真的太棒了&#xff01;非常推荐&#xff09; 题外话&#xff0c;我的碎碎念&#xff0c;这也是我如何提高搜索能力的办法&#xff1a;想在Latex生成的PDF中加入…

2023的AI工具集合,google和claude被禁用解决和edge的copilot

一、前言 AI工具集合 首先&#xff0c;OpenAI的ChatGPT以其深度学习模型和强大的语言处理能力引领了AI聊天机器人的潮流。自2022年11月30日上线以来&#xff0c;它创下了100万用户的注册记录&#xff0c;并被广泛应用于全球财富500强公司。为了实现盈利&#xff0c;OpenAI发布…

iOS 开发设计 App 上架符合要求的截图

1. 真机运行截屏 2. 可以在 Apple developer 官网 Design 下找到 iPhone 边框 https://developer.apple.com/design/resources/ 不用这个边框也行&#xff0c;可以参考已上架 App 的图片框 3. 使用 Procreate&#xff08;PhotoShop&#xff09;创建符合要求的画布大小 4. 导入…

springboot集成websocket全全全!!!

一、界面展示 二、前置了解 1.什么是websocket WebSocket是一种在单个TCP连接上进行全双工通信的持久化协议。 全双工协议就是客户端可以给我们服务器发数据 服务器也可以主动给客户端发数据。 2.为什么有了http协议 还要websocket 协议 http协议是一种无状态&#xff0c;非…

铭飞CMS cms/content/list接口存在SQL注入 附POC

@[toc] 铭飞CMS cms/content/list接口存在SQL注入 附POC 免责声明:请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失,均由使用者本人负责,所产生的一切不良后果与文章作者无关。该文章仅供学习用途使用…

内存函数的学习

额外知识点 第一个 假设c为int类型&#xff0c;&#xff08;char&#xff09;c之后&#xff0c;之后如果还用变量c的话&#xff0c;c依然为int类型。&#xff08;&#xff09;强制转换操作符并不会永久改变原本的变量类型。 第二个 \0在打印时不会显示出来 第三个 void …

增强客户获取能力:探索 B 端影片行销的影响

01 B端企业短视频营销的价值与难点 短视频营销在当今的市场中越来越受到重视。有数据显示&#xff0c;越来越多的市场人将尝试增加短视频营销的预算&#xff0c;并且在2023年&#xff0c;每5个市场人中就有1个人将尝试短视频营销。相较于内容深、信息量大的长视频&#xff0c;…

串口通信(7)-C#串口通信通信帮助类实例

本文讲解C#串口通信通信帮助类实例 首先创建winform项目添加界面和控件 UI界面 namespace SerialPortDemo {partial class MainForm{/// <summary>/// 必需的设计器变量。/// </summary>private System.ComponentModel.IContainer components = null;/// <sum…

c#委托学习笔记1

委托三步骤 第一步&#xff1a;定义委托 //第一步&#xff1a;1 声明委托(定义委托) //对于声明委托的解释如下&#xff1a; //解释a&#xff1a;函数指针 //解释b&#xff1a;委托就是定义函数的形状&#xff08;形态&#xff09; // 即&#xff1a;返回值类型&#x…