Linux系统编程--进程间通信

目录

1. 介绍

1.1 进程间通信的目的

1.2 进程间通信的分类

2. 管道

2.1 什么是管道

2.2 匿名管道

2.2.1 接口

2.2.2 步骤--以父子进程通信为例

2.2.3 站在文件描述符角度-深度理解

2.2.4 管道代码

2.2.5 读写特征

2.2.6 管道特征

2.3 命名管道

2.3.1 接口

2.3.2 代码实现

 2.3.3 匿名管道和命名管道的区别

3. system V共享内存

3.1 共享内存的原理 

3.2 步骤

3.3 系统接口

3.4 代码

3.5 共享内存的优缺点

4.信号量

4.1 相关概念

4.2 信号量 -- 对资源的一种预定

4.3 系统接口


1. 介绍

1.1 进程间通信的目的

  • 数据传输:一个进程需要将它的数据发送给另一个进程
  • 资源共享:多个进程之间共享同样的资源。
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件                        (如进程终止 时要通知父进程)。
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望                      能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变
  • 有时候也需要多进程协同进行工作

如何理解进程间通信的本质问题呢?

  • OS需要直接/间接的给通信双方的进程提供"内存空间"
  • 要通信的不同进程必须看到同一份公共资源

1.2 进程间通信的分类

  • 管道
  • SystemV(本文只讨论共享内存) -- 让通信过程可以跨主机
  • POSIX -- 聚焦在本地通信

2. 管道

2.1 什么是管道

  • 管道是 Unix 中最古老的进程间通信的形式
  • 我们把从一个进程连接到另一个进程的一个数据流称为一个 管道  

管道又分匿名管道和命名管道两种。 

2.2 匿名管道

2.2.1 接口

#include <ustdio.h>

int pipe(int pipefd[2]); 
参数:piepfd[]为输出型参数,pipefd[0]为读文件描述符,pipefd[1]为写文件描述符,若为其他的文件描述符使用,一般这两个fd分别为3、4。

返回值:创建成功返回0,失败返回-1 

2.2.2 步骤--以父子进程通信为例

  • 父进程利用pipe接口创建管道,分别以读和写打开一个文件 
  • 父进程fork出子进程
  • 父进程关闭fd[1],子进程关闭fd[0]
  • 这样父进程就可以往管道文件中写数据,子进程从管道文件中读数据,实现了父子进程的通信

注:管道一般是单向的,其实管道也是一个文件("内核级文件")--不需要进行磁盘IO(当然也可以用磁盘文件来实现这个管道操作,但是要进行磁盘读取,太慢了)

 若是管道中没有数据了,但是读端还在读,OS会直接阻塞当前正在读取的进程。

2.2.3 站在文件描述符角度-深度理解

2.2.4 管道代码

        在这个代码部分,可以实验当读快写慢、读慢写快、只读关闭、只写关闭四种情况,这里只给出了只有读关闭的情况

  #include <iostream>                                                                                                                                                   
  #include <cstdio>    
  #include <unistd.h>    
  #include <cassert>    
  #include <sys/stat.h>    
  #include <sys/wait.h>    
  #include <fcntl.h>    
  #include <cstring>    
  using namespace std;    
      
  int main()    
  {    
      // 第一步:创建管道文件    
      int fds[2];    
      int n = pipe(fds);    
      assert(n == 0);    
      // 0 1 2应该是被占用的 _-> 3 4    
      cout << "fds[0]: " << fds[0] << endl;    
      cout << "fds[1]: " << fds[1] << endl;    
      // 第二步:fork    
      pid_t id = fork();    
      assert(id >= 0);    
      if(id == 0)    
      {    
          // 子进程的通信代码 子进程写入    
          close(fds[0]);    
          // 通信代码    
          // string msg = "hello, i am child!";    
          int cnt = 0;    
          const char* s = "我是子进程,我正在给你发消息!";    
          while(1)    
          {    
              cnt++;    
              char buffer[1024]; // 只有子进程能看到    
              snprintf(buffer, sizeof buffer, "child->parent say: %s[%d][%d]", s, cnt, 
  getpid());    
              // 往文件中写入数据    
              write(fds[1], buffer, strlen(buffer));    
              // sleep(50); // 细节 每隔一秒写一次    
              // break;    
          }
          
          cout << "子进程关闭写端" << endl;
          close(fds[1]);
        exit(0);
      }                                                                                                                                                                 
      // 父进程的通信代码  父进程读取
      close(fds[1]);
      while(1)
      {
          char buffer[1024];
          // cout << "AAAAAAAAAAAAAAA" <<endl;
          // 父进程在这里阻塞等待
          ssize_t s = read(fds[0], buffer, sizeof(buffer) - 1);
          //  cout << "BBBBBBBBBBBBBBB" <<endl;
          if(s > 0) 
          {
              buffer[s] = 0;
           cout << " Get Message# " << buffer <<" | my pid: " << getpid() << endl;
          }
          else if(s == 0)
          {
           cout << "read: " << s << endl;
              break;
          }
          // cout << "Get Message#" << buffer << " | my pid: " << getppid() << endl;
          // 细节:父进程可没有进行sleep
          //sleep(5);
          // close(fds[0]);
          break;
      }
      close(fds[0]);
      int status = 0;
      cout << "父进程关闭读端" << endl;
      n = waitpid(id, &status, 0);
      assert(n == id);
  
      cout << "pid->" << n << ":" << (status & 0x7F) << endl; // 信号为13:SIGPIPE 中止了写入进程
     
      
      
      return 0;
  }

       由上面代码结果可以看出,当读关闭时,OS会终止写端,给写进程发送信号,终止写端。写进程收到13号信号

2.2.5 读写特征

  • 读快,写慢 -- 读进程会阻塞,等到管道中有数据时继续读取,子进程没有写入的那段时间,                          若管道中没有数据时,父进程会在read处阻塞等待
  • 读慢,写快 -- 写进程正常写数据,管道写满时,会在write处阻塞,读进程就绪时,继续读取                         数据
  • 写关闭 -- 管道中的数据会被读取完毕后返回EOF,此时 read 函数会返回0,最后等待子进程关                  闭读端
  • 读关闭 -- OS会中止写端,给写端发送信号--13 SIGPIPE,终止写端

2.2.6 管道特征

  • 管道的生命周期随进程
  • 管道可以用来进行具有血缘关系的进程通信,常用于父子进程
  • 管道是面向字节流的
  • 单向通信 -- 半双工通信
  • 互斥与同步机制 -- 对共享资源进行保护额方案

2.3 命名管道

  • 管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
  • 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
  • 命名管道是一种特殊类型的文件
  • 在用命名管道实现两个进程通信时,任意一个进程调用mkfifo创建命名管道即可

2.3.1 接口

#include <sys/types.h>

#include <sys/stat.h>

int mkfifo(const char* pathname, mode_t mode);

参数:pathname:命名管道的路径名  mode:管道权限

返回值:成功返回0;失败返回-1,并设置errno来指示错误原因

int unlink(const char* pathname);  --在进程结束后,清理和删除命名管道。

参数:命名管道的路径名

返回值:成功返回0;失败返回-1,并设置errno来指示错误原因

 命名管道可以从命令行上创建,命令行方法是使用下面这个命令:

mkfifo filename  # filename为命名管道文件名

2.3.2 代码实现

用命名管道实现 server&client 通信
server.cc:
#include <iostream>
#include "comm.hpp"
using namespace std;

int main()
{
    bool r = createFifo(NAMED_PIPE);
    assert(r);
    (void)r;

    cout << "server begin" << endl;
    int rfd  =open(NAMED_PIPE, O_RDONLY);  // 只读方式打开
    cout << "server end" << endl;                                                                        
                                                                                                         
    if(rfd < 0)                                                                                          
    {                                                                                                    
        cout << "文件打开失败!" << endl;                                                                
        exit(1);                                                                                         
    }                                                                                                    
                                                                                                         
    // read                                                                                              
    char buffer[1024];                                                                                   
    while(true)                                                                                          
    {                                                                                                    
        ssize_t s = read(rfd, buffer, sizeof buffer - 1);                                                
        if(s > 0)                                                                                        
        {                                                                                                
            buffer[s] = 0;                                                                               
            std::cout << "client->server:" << buffer << endl;                                            
        }                                                                                                
        else if(s == 0)    
        {                                                                                                
            cout << "client quit, me too!" << endl;                                                      
    break;                                                                                                                                                              
        }    
        else{    
            cout << "err string:" << strerror(errno) << endl;    
            break;    
        }    
    }

    close(rfd);


    // sleep(10);
    removeFifo(NAMED_PIPE);
    return 0;
}

client.cc

#include <iostream>    
#include "comm.hpp"    
using namespace std;    
    
int main()    
{    
    // 与server打开同一个文件    
    cout << "client begin" << endl;    
    
    int wfd = open(NAMED_PIPE, O_WRONLY);    
    cout << "client end" << endl;    
    if(wfd < 0)    
    {    
        cout << "文件打开失败!" << endl;    
        exit(1);    
    }    
    
    // write    
    char buffer[1024];    
    while(true)    
    {    
        cout << "Please Say# ";                                                                                                                                         
        fgets(buffer, sizeof(buffer)-1, stdin);    
        if(strlen(buffer) > 0) buffer[strlen(buffer)-1] = 0;    
        ssize_t s = write(wfd, buffer, strlen(buffer));    
        assert(s == strlen(buffer));    
        (void)s;    
    }    
    
    
    close(wfd);    
    
    return 0;    
}   

comm.hpp 

#pragma once    
    
#include <string>    
#include <iostream>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <cstring>    
#include <cassert>    
#include <cerrno>    
#include <unistd.h>    
#include <sys/wait.h>    
#include <fcntl.h>    
    
#define NAMED_PIPE "/tmp/mypipe.106"    
    
bool createFifo(const std::string& path)    
{    
    umask(0);    
    int n = mkfifo(path.c_str(), 0600);  // 只允许拥有者通信    
    if(n == 0) return true;    
    else{                                                                                                                                                               
       std::cout << "erro" << errno << "err string: " << strerror(errno) << std::endl;    
        return false;    
    }    
}    
    
void removeFifo(const std::string & path)    
{    
    int n = unlink(path.c_str());    
    assert(n == 0); // debug有效,release里面就无效    
    (void)n;  // 不想有警告    
}   

可以看到client可以向server端发送数据,server收到并打印到屏幕中,实验结果如下图所示: 

下图为命名管道的信息: 

 

 2.3.3 匿名管道和命名管道的区别

  • 匿名管道由pipe函数创建并打开。
  • 命名管道由mkfifo函数创建,打开用open
  • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。

3. system V共享内存

3.1 共享内存的原理 

        共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据

原理:是不同的进程通过各自的PCB和页表访问同一快共享内存

3.2 步骤

  • 申请一块空间
  • 将创建好的内存映射(将进程和共享内存挂接)到不同的进程地址空间
  • 若未来不想通信:取消进程和内存的映射关系--去关联、释放共享内存 

3.3 系统接口

#include <sys/ipc.h>

#include <sys.shm.h>

int shmget(key_t key, size_t size, int shmflg);

参数:key: 进行唯一性标识 -- 将key使用shmget设置进入共享内存属性中,用来表示共享                                                    内存在内核中的唯一性

           size:申请空间大小--一般为4KB的整数倍

           shmflg:IPC_CREAT--如果指定的共享内存不存在,创建;如果存在,获取共享内存

                          IPC_EXCL--无法单独使用  使用:IPC_CREAT|IPC_EXCL:如果不存在,                            创建--创建的一定是一个新的共享内存;存在则出错返回,还可以通过其                                设置共享内存的权限
返回值:成功返回标识符shmid;失败,返回-1,与文件不同

key_t ftok(char* pathname, char proj_id); --
使用给定路径名命名的文件的标识(必须引用一个现有的,可访问的文件)和proj_id的最低有效8位(必须是非零的)来生成key_t类型的System V IPC密钥

返回值:成功返回key_t值,失败返回-1

解析:

        创建共享内存时,如何保证其在系统中是唯一的?-- 通过参数key确定的,只要保证另一个要通信的进程看到相同的key值,通过在各个共享内存的属性中查找相同的key,即可找到同一块共享内存--通过相同的pathname和proj_id在不同的进程中调用ftok获得相同的key。那么key在哪里呢? -- 在结构体struct stm中。

IPC资源的特征:共享内存的生命周期是随OS的,不是随进程的,若没有对共享内存进行手动的删除,那么该资源不会消失

查看IPC资源的命令:ipcs -m(共性内存)  /-q(消息队列)/-s(信号量)

删除IPC资源的执行:ipcrm -m shmid

操作共享内存

int shmctl(int shmid, int cmd, struct shmid_ds* buf);

参数:shmid:shmget的返回值--要控制哪一个共享内存

           cmd:IPC_RMID -- 删除共享内存段 谁创建谁删除

                      IPC_STAT -- 获取共享内存属性

                      IPC_SET -- 设置共享内存属性

           buf:

返回值:失败返回-1

关联进程

void* shmat(int shmid, const void* shmaddr, int shmflg);

参数:shmid:

           shmaddr:将共享内存映射到哪一个地址空间中,一般设为nullptr 核心自动选择                                      一个地址

           shmflg:一般设置为0,读写权限

返回值:共享内存空间的起始地址

去关联:并不是删除共享内存,而是去除PCB和共享内存的映射关系

int shmdt(const void* shmaddr);

参数:shmaddr-由shmat所返回的指针
返回值:失败返回-1

3.4 代码

// common.hpp
#ifndef _COMM_HPP_                                                                                                                                                      
#define _COMM_HPP_    
    
#include <iostream>    
#include <sys/ipc.h>    
#include <sys/types.h>    
#include <sys/shm.h>    
#include <cerrno>    
#include <cstring>    
#include <cstdlib>    
#include <unistd.h>    
    
#define PATHNAME "."    
#define PROJ_ID 0x66    
#define MAX_SIZE 4096    
    
key_t getKey()    
{    
    key_t k = ftok(PATHNAME, PROJ_ID);  // 可以获取同样的key!    
    if(k < 0)    
    {    
        // cin cout cerr -> stdin stdout stderr -> 0,1,2    
        std::cerr << errno << ":" << strerror(errno) << std::endl;    
        exit(1);    
    }    
    return k;    
}    
    
int getShmHelper(key_t k, int flags)    
{    
    int shmId = shmget(k, MAX_SIZE, flags);    
    if(shmId < 0)    
    {    
        std::cerr << errno << ":" << strerror(errno) << std::endl;    
        exit(2);    
    }    
    return shmId;    
}    
// 给之后的进程获取共享内存
int getShm(key_t k)
{
    return getShmHelper(k, IPC_CREAT/*可以设定为0*/); 
}
                                                                                                                                                                        
// 给第一个进程使用 创建共享内存 
int creatShm(key_t k)
{
    return getShmHelper(k, IPC_EXCL | IPC_CREAT | 0600); // 0600为权限
}

void *attachShm(int shmId)
{
    void *mem = shmat(shmId, nullptr, 0);  // 64位系统 指针占8字节
    if((long long)mem == -1L)
    {
        std::cerr << "shmat " << errno << ":" << strerror(errno) << std::endl;
        exit(3);
    }
    return mem;
}

void detachShm(void *start)
{
    if(shmdt(start) == -1)
    {
        std::cerr << errno << ":" << strerror(errno) << std::endl;
    }
}

void delShm(int shmId)
{
    if(shmctl(shmId, IPC_RMID, nullptr) == -1)
    {
        std::cerr << errno << ":" << strerror(errno) << std::endl;
    }
}
 
#endif


//shm_client.cc//
#include "common.hpp"                                                                                                                                                   
using namespace std;    
    
int main()    
{    
    key_t k = getKey();    
    printf("0x%x\n", k);    
    
    int shmId = getShm(k);    
    printf("shmId:%d\n", shmId);    
    
    // 关联    
    char *start = (char*)attachShm(shmId);    
    printf("sttach success, address start: %p\n", start);    
    // 使用    
    const char* message = "hello server, 我是另一个进程,正在和你通信!";    
    pid_t id = getpid();    
    int cnt = 1;    
    // char buffer[1024];    
    while(true)    
    {    
        sleep(1);    
        // 直接将需要传递的信息写在共享内存字符串中 省去了很多拷贝的过程 提高了传输信息的效率    
        snprintf(start, MAX_SIZE, "%s[pid:%d][消息编号:%d]", message, id, cnt++);    
        // snprintf(buffer, sizeof(buffer), "%s[pid:%d][消息编号:%d]", message, id, cnt);    
        // memcpy(start, buffer, strlen(buffer)+1);    
    }    
    
    // 去关联    
    detachShm(start);    
    // done    
    
    return 0;    
} 

/shm_server.cc///
#include "common.hpp"                                                                                                                                                   
using namespace std;    
    
int main()    
{    
    key_t k = getKey();    
    printf("0x%x\n", k);    
    
    // 申请共享内存    
    int shmId = creatShm(k);    
    printf("shmId:%d\n", shmId);    
    sleep(3);    
    // 关联    
    // 将共享内存看为一个字符串    
    char *start = (char*)attachShm(shmId);    
    printf("sttach success, address start: %p\n", start);    
    // 使用    
    while(true)    
    {    
        printf("client say: %s\n", start);    
        sleep(1);    
    }    
    // 去关联    
    detachShm(start);    
    sleep(5);    
    // 删除共享内存    
    delShm(shmId);    
    return 0;    
}    

上面的代码我们看到的现象是:

通过共享内存的方式实现了进程间通信

3.5 共享内存的优缺点

优点:

  • 共享内存是所有通信中最快的,大大减少数据的拷贝次数

缺点:

  • 不会给我们进行同步和互斥,没有对数据进行任何保护

问题--同样的代码,管道和共享内存方式实现各需要多少次拷贝 ?

4.信号量

4.1 相关概念

信号量 -- 本质是一个计数器,通常用来表示公共资源中,资源数量的多少问题

公共资源 -- 被多个进程同时访问的资源,访问没有保护的公共资源时,可能会导致数据不一致                         问题

为什么要让不同进程看到同一份资源? -- 实现进程间的协同工作,但是进程是具有独立性的,      为了让进程看到同一份资源,提出了进程间通信的方法,但是又带来了新的问题--数据不一致问题

临界资源:将被保护起来的公共资源称为临界资源,但是大部分的资源是独立的,只有少量的属于临                      界资源,资源就是内存、文件、网络等

临界区:进程访问临界资源的代码被称为临界区,与之对应的为非临界区

保护公共资源:互斥、同步

原子性:要么不做,要做就做完,只有两种状态

4.2 信号量 -- 对资源的一种预定

为什么要有信号量?

设sem=20; sem--;// P操作,访问公共资源;sem++;// V操作,释放公共资源  --PV操作

所有的进程在访问公共资源前都必须先申请sem信号量,前提是所有进程必须先看到同一个信号量,那么信号量本身就是公共资源--也要保证自己的安全--信号量++、--的操作是原子操作

如果一个信号量的初始值为1,二维信号量/互斥信号量

4.3 系统接口

头文件

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

申请信号量

int semget(key_t key, int nsems, int semflg);

参数:   key:对申请的信号量进行唯一性标识

              nsems:申请几个信号量,与信号量的值无关

              semflg:与共享内存的flag相同含义

返回值:成功返回信号量标识符semid,失败返回-1

删除信号量

int semctl(int semid, int semnum, int cmd,...);

参数:semid:信号量id,semget返回的值

           semnum:信号量集的下标

           cmd:IPC_RMID、IPC_STAT、IPC_SET

返回值:失败返回-1 

操作信号量

int semop(int semid, struct sembuf* sops, unsigned nsops);

参数:semid:信号量id

           sops:

信号量的详细操作会在多线程部分讲解 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【驱动篇】龙芯LS2K0300之i2c设备驱动

实验背景 由于官方内核i2c的BSP有问题&#xff08;怀疑是设备树这块&#xff09;&#xff0c;本次实验将不通过设备树来驱动aht20&#xff08;i2c&#xff09;模块&#xff0c;大致的操作过程如下&#xff1a; 模块连接&#xff0c;查看aht20设备地址编写device驱动&#xff…

K8S之网络深度剖析(一)(持续更新ing)

K8S之网络深度剖析 一 、关于K8S的网络模型 在K8s的世界上,IP是以Pod为单位进行分配的。一个Pod内部的所有容器共享一个网络堆栈(相当于一个网络命名空间,它们的IP地址、网络设备、配置等都是共享的)。按照这个网络原则抽象出来的为每个Pod都设置一个IP地址的模型也被称作为I…

忍法:声音克隆之术

前言&#xff1a; 最近因为一直在给肚子里面的宝宝做故事胎教&#xff0c;每天&#xff08;其实是看自己心情抽空讲下故事&#xff09;都要给宝宝讲故事&#xff0c;心想反正宝宝也看不见我&#xff0c;只听我的声音&#xff0c;干脆偷个懒&#xff0c;克隆自己的声音&#xf…

信息学奥赛初赛天天练-40-CSP-J2021基础题-组合数学-缩倍法、平均分组、2进制转10进制、面向过程/面向对象语言应用

PDF文档公众号回复关键字:20240630 2021 CSP-J 选择题 单项选择题&#xff08;共15题&#xff0c;每题2分&#xff0c;共计30分&#xff1a;每题有且仅有一个正确选项&#xff09; 1.以下不属于面向对象程序设计语言是( ) A. C B. Python C. Java D. C 2.以下奖项与计…

R包的4种安装方式及常见问题解决方法

R包的4种安装方式及常见问题解决方法 R包的四种安装方式1. install.packages()2. 从Bioconductor安装3. 从本地源码安装4. 从github安装 常见问题的解决1. 版本问题2. 网络/镜像问题3.缺少Rtools R包的四种安装方式 1. install.packages() 对于R自带的包的安装一般都可以通过…

HarmonyOS--路由管理--组件导航 (Navigation)

文档中心 什么是组件导航 (Navigation) &#xff1f; 1、Navigation是路由容器组件&#xff0c;一般作为首页的根容器&#xff0c;包括单栏(Stack)、分栏(Split)和自适应(Auto)三种显示模式 2、Navigation组件适用于模块内和跨模块的路由切换&#xff0c;一次开发&#xff0…

实现点击按钮导出页面pdf

在Vue 3 Vite项目中&#xff0c;你可以使用html2canvas和jspdf库来实现将页面某部分导出为PDF文档的功能。以下是一个简单的实现方式&#xff1a; 1.安装html2canvas和jspdf&#xff1a; pnpm install html2canvas jspdf 2.在Vue组件中使用这些库来实现导出功能&#xff1a;…

网线直连电脑可以上网,网线连tplink路由器上不了网

家里wifi网络连不上好几天了&#xff0c;用网线直连电脑可以上网&#xff0c;但网线连tplink路由器wan口上不了网&#xff0c;无Internet连接&#xff0c;网线连lan口可以电脑上网&#xff0c;手机上不了。 后来发现网线的主路由用的192.168.0.1&#xff0c;我的路由器wan口自…

在node环境使用MySQL

什么是Sequelize? Sequelize是一个基于Promise的NodeJS ORM模块 什么是ORM? ORM(Object-Relational-Mapping)是对象关系映射 对象关系映射可以把JS中的类和对象&#xff0c;和数据库中的表和数据进行关系映射。映射之后我们就可以直接通过类和对象来操作数据表和数据了, 就…

【大数据导论】大数据序言

各位大佬好 &#xff0c;这里是阿川的博客&#xff0c;祝您变得更强 个人主页&#xff1a;在线OJ的阿川 大佬的支持和鼓励&#xff0c;将是我成长路上最大的动力 阿川水平有限&#xff0c;如有错误&#xff0c;欢迎大佬指正 目录 数据概念及类型及可用及组织形式数据概念数据…

golang项目基于gorm框架从postgre数据库迁移到达梦数据库的实践

一、安装达梦数据库 1、登录达梦数据库官网&#xff0c;下载对应系统版本的安装包。 2、下载地址为&#xff1a;https://www.dameng.com/list_103.html 3、达梦数据库对大小写敏感&#xff0c;在安装初始化数据库实例时建议忽略大小写&#xff1b;具体安装教程可参考以下博客: …

python办公自动化之pandas

用到的库&#xff1a;pandas 实现效果&#xff1a;创建一张空白的表同时往里面插入准备好的数据 代码&#xff1a; import pandas # 准备好要写入的数据&#xff0c;字典格式 data{日期:[7.2,7.3],产品型号:[ca,ce],成交量:[500,600]} dfpandas.DataFrame(data) # 把数据写入…

Java代码基础算法练习-计算被 3 或 5 整除数之和-2024.06.29

任务描述&#xff1a; 计算 1 到 n 之间能够被 3 或者 5 整除的数之和。 解决思路&#xff1a; 输入的数字为 for 循环总次数&#xff0c;每次循环就以当前的 i 进行 3、5 的取余操作&#xff0c;都成立计入总数sum中&#xff0c;循环结束&#xff0c;输出 sum 的值 代码示例&…

QT加载安装外围依赖库的翻译文件后翻译失败的现象分析:依赖库以饿汉式的形式暴露单例接口导致该现象的产生

1、前提说明 QtClassLibaryDll是动态库,QtWidgetsApplication4是应用程序。 首先明确:动态库以饿汉式的形式进行单例接口暴露; 然后,应用程序加载动态库的翻译文件并进行全局安装; // ...QTranslator* trans = new QTranslator();//qDebug() << trans->load(&quo…

大模型系列:提示词管理

既然大模型应用的编程范式是面向提示词的编程&#xff0c;需要建立一个全面且结构化的提示词库&#xff0c; 对提示词进行持续优化也是必不可少的&#xff0c;那么如何在大模型应用中更好的管理提示词呢&#xff1f; 1. 提示词回顾 提示词在本质上是向大型语言模型&#xff08…

​Chrome插件:React Developer Tools为React开发调试而生

React Developer Tools 是什么? 它是允许在Chrome和Firefox开发者工具中检查React组件层次结构的扩展插件。 插件源码下载 源码下载地址:GitHub - facebook/react-devtools at v3 下载完成以后执行红框中的代码,下载react-devtools 源码,源码如下图所示: 插件打包 当前n…

【C++】 ——【模板初阶】——基础详解

目录 1. 泛型编程 1.1 泛型编程的概念 1.2 泛型编程的历史与发展 1.3 泛型编程的优势 1.4 泛型编程的挑战 2. 函数模板 2.1 函数模板概念 2.2 函数模板格式 2.3 函数模板的原理 2.4 函数模板的实例化 2.5 模板参数的匹配原则 2.6 函数模板的特化 2.7 函数模板的使…

Linux CentOS 宝塔中禁用php8.2的eval函数详细图文教程

PHP_diseval_extension 这个方法是支持PHP8的, Suhosin禁用eval函数&#xff0c;不支持PHP8 一、安装 cd / git clone https://github.com/mk-j/PHP_diseval_extension.gitcd /PHP_diseval_extension/source/www/server/php/82/bin/phpize ./configure --with-php-config/ww…

美团校招机试 - 小美的平衡矩阵(20240309-T1)

题目来源 美团校招笔试真题_小美的平衡矩阵 题目描述 小美拿到了一个 n * n 的矩阵&#xff0c;其中每个元素是 0 或者 1。 小美认为一个矩形区域是完美的&#xff0c;当且仅当该区域内 0 的数量恰好等于 1 的数量。 现在&#xff0c;小美希望你回答有多少个 i * i 的完美…

C++操作系列(二):VSCode安装和配置C++开发环境

1. VSCode下载 进入VSCode的官网网页&#xff1a;Download Visual Studio Code - Mac, Linux, Windows 下载相应的版本&#xff1a; 2. 安装VSCode 安装到指定位置&#xff1a; 一路下一步&#xff0c;直至安装完成&#xff1a; 3. 安装C插件 3.1. 安装C/C 点击扩展图标&…