Linux程序设计(下)

系列文章目录


文章目录

  • 系列文章目录
  • 十、调试
    • 断言
  • 十一、进程和信息号
    • 进程表
    • 进程调度
    • 启动新进程
    • 信号
      • **信号处理**
      • **发送信号**
  • 十二、POSIX线程
    • 线程创建
    • 线程同步
    • 线程属性
    • 取消一个线程
    • pthread_exit, exit, _exit
  • 十三、管道
    • popen, pipe
    • 父子进程
      • 将管道用作标准输入和标准输出
    • 命名管道:FIFO
  • 十四、IPC通讯
    • 信号量
    • 共享内存
    • 消息队列
    • IPC状态命令
  • 十八、Linux标准
    • LSB标准
      • LSB系统初始化
    • 文件系统层次结构标准
    • --


十、调试

  • 错误类型
  • 常用调试技巧
  • 使用GDB和其他工具进行调试
  • 断言
  • 内存调试

断言

#include <assert.h>
void assert(int expression);

assert宏对表达式进行求值,如果结果为零,它就往标准错误写一些诊断信息,然后调用abort结束程序的运行。
头文件assert.h定义的宏受到NDEBUG的影响。如果程序在处理这个头文件时已经定义了NDEBUG,就不定义assert宏。

十一、进程和信息号

  • 进程结构、类型和调度
  • 用不同的方法启动新进程
  • 父进程、子进程和僵尸进程
  • 什么是信号以及如何使用

进程定义:一个其中运行着一个或多个线程的地址空间和这些线程所需要的系统资源。可简单看作运行着的程序。

pid==1一般是init进程。

进程表

Linux进程表就像一个数据结构,它把当前加载在内存中的所有进程的有关信息保存在一个表中,其中包括进程的PID、进程的状态、命令字符串和其它一些ps命令输出的各类信息。操作系统通过进程的PID对它们进行管理,这些PID是进程表的索引。进程表的长度是有限制的,所以操作系统能够支持的同时运行的进程数也是有限制的,如今可同时运行的进程数可能只与用于建立进程表项的内存容量有关。

在这里插入图片描述

进程调度

一个进程的nice值默认为0,并将根据这个进程的表现而不断变化。
可以使用nice命令设置进程的nice值,使用renice命令调整它的值。

nice prog_name & #它将分配一个+10的nice值
renice 10 pid  # Guess what will happen.

在Linux系统中,PR值指的是进程的优先级。PR值越小,表示进程的优先级越高,而PR值越大,表示进程的优先级越低。这是因为Linux系统中采用了静态优先级和动态优先级相结合的调度算法。

静态优先级由nice值来表示,范围通常是-20到+19,其中-20是最高优先级,+19是最低优先级。而PR值则是通过将nice值进行转换得到的,具体的计算方式是:

PR = 20 + nice值

因此,当nice值越小时,PR值越大,进程的优先级就越高。

动态优先级则是根据进程的行为和运行状态实时调整的,使得系统更具响应性。这样,通过将静态优先级和动态优先级结合起来,Linux系统可以根据进程的行为和系统负载来合理地分配CPU资源,从而实现更好的性能和响应性。

启动新进程

int system(const char* string);
// 运行以字符串参数的形式传递给他的命令(并等待命令的完成,依命令而定)。命令的执行情况就如同在shell中执行
// system创建子进程,wait与否取决于 输入的命令

execve函数族
fork
wait, waitpid

  • 在一个子进程终止前,wait使调用者阻塞
  • waitpid有一个选项,可使调用者不阻塞
  • waitpid等待一个指定的子进程;wait等待所有的子进程,返回任一终止子进程的状态

信号

术语生成(raise)表示一个信号的产生,捕获(catch)表示接收到一个信号。信号由shell和终端处理器生成来引起中断。

信号处理

  • 老的signal函数

    #include <signal.h>
    typedef void(*func)(int);
    func signal(int sig, func f);
    
    
    void ouch(int sig)
    {
        printf("OUCH! - I got signal %d\n", sig);
        signal(SIGINT, SIG_DFL);
    }
    int main()
    {
        signal(SIGINT, ouch);  // 注册信号处理函数
        while(1)sleep(1);
    }
    
    macro行为
    SIG_IGN忽略信号
    SIG_DFL恢复默认行为
  • 更新,更健壮的信号接口:sigaction

    #include <signal.h>
    int sigaction(int sig, const struct sigaction* act, struct sigaction* oldact);
    /*
           struct sigaction {
               void     (*sa_handler)(int);  // 信号处理函数
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;  // 信号集,将被阻塞且不会传递给该进程
               int        sa_flags;
               void     (*sa_restorer)(void);
           };
    */
    
    struct sigaction act;
    act.sa_handler = ouch;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGINT, &act, NULL);
    

信号集

#include <signal.h>
int sigaddset(sigset_t* set, int signo);
int sigemptyset(sigset_t* set);
int sigfillset(sigset_t* set);
int sigdelset(sigset_t* set, int signo);

// signo是否在集合中。是:1; 否:0;给定信号无效:-1,并设置errno为EINVAL。
int sigismember(sigset_t* set, int signo);
// 进程的信号屏蔽字的设置或检查
/*
	how: -SIG_BLOCK:把set中的信号添加到信号屏蔽字中
		 -SIG_SETMASK:把信号屏蔽字设置为参数set中的信号
		 -SIG_UNBLOCK:从信号屏蔽字中删除set中的信号
*/
int sigprocmask(int how, const sigset_t* set, sigset_t* oset);

// 将被阻塞的信号中停留在待处理状态的一组信号写到参数set指向的信号集中。
int sigpending(sigset_t* set);

// 使得进程挂起自己的执行,直到信号集中的一个信号到达为止
int sigsuspend(const sigset_t* sigmask);
  1. sigaction标志
macrodescription
SA_NOCLDSTOP子进程停止时不产生SIGCHLD信号
SA_RESETHAND将对此信号的处理方式在信号处理函数的入口处重置为SIG_DFL
SA_RESTART重启可中断的函数而不是给出EINTR错误
SA_NODEFER捕获到信号时不讲它添加到信号屏蔽字中
即,在执行信号处理函数时依然可以捕获信号

信号处理函数可以在其执行期间被中断并再次被调用。当返回到第一次调用时,它能否继续正确操作是很关键的。这不仅仅是递归(调用自身)的问题,而是可重入(可以安全地进入和再次执行)的问题。在信号处理函数中可以使用的可重入系统调用:
在这里插入图片描述

发送信号

想要发送一个信号,发送进程 必须拥有相应的权限。这通常意味着两个进程必须拥有相同的用户ID

unsigned int alarm(unsigned int seconds);

在这里插入图片描述
子进程给父进程发送SIGALARM信号

#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

static int alarm_fired = 0;

void sigchild(int sig)
{
    printf("sigchild\n");
}
void ding(int sig)
{
    alarm_fired = 1;
}
int main()
{
    pid_t pid;
    printf("alarm application starting\n");
    pid = fork();
    switch (pid)
    {
    case -1:
        perror("fork");
        exit(EXIT_FAILURE);
        break;
    case 0:
        // sleep(1);
        pid = getppid();
        // kill(pid, SIGCHLD);
        sleep(5);
        kill(pid, SIGALRM);
        exit(EXIT_SUCCESS);
        break;
    }
    printf("waiting for alarm to go off\n");
    signal(SIGCHLD, sigchild);
    signal(SIGALRM, ding);
    pause();
    if (alarm_fired)
        printf("Ding!\n");
    else 
        printf("not ding\n");
    printf("done\n");
    exit(0);
}

  闹钟模拟程序通过fork调用启动新的进程。这个子进程休眠5秒后向其父进程发送一个SIGALRM信号。父进程在安排好捕获SIGALRM信号后暂停运行,直到接收到一个信号为止。此程序并未在信号处理函数中直接调用printf,而是通过在该函数中设置标志,然后在main函数中检查该标志来完成消息的输出。
  使用信号并挂起程序的执行是Linux程序设计中的一个重要部分。这意味着程序不需要总是在执行着。程序不必在一个循环中无休止地检查某个事件是否已发生,相反,它可以等待事件的发生。这在只有一个CPU的多用户环境中尤其重要,进程共享着一个处理器,繁忙的等待将会对系统的性能造成极大的影响。程序中信号的使用将带来一个特殊的问题:“如果信号出现在系统调用的执行过程中会发生什么情况?”答:“视情况而定”。一般来说,你只需要考虑慢系统调用,如从终端读数据,如果在这个系统调用等待数据时出现一个信号,它就会返回一个错误。如果在程序中使用信号,需要注意一些系统调用会因为接收到了一个信号而失败,而这种错误情况可能是你在添加信号处理函数之前没有考虑到的。
  在编写程序中处理信号部分的代码时必须非常小心,因为在使用信号的程序中会出现各种各样的“竞态条件”。如,如果想调用pause等待一个信号,可信号却出现在pause之前,就会使程序无限期的等待一个不会发生的事情。这些竞态条件都是一些对时间要求很苛刻的问题。所以在检查和信号相关的代码时总是要非常小心。

十二、POSIX线程

  • 在进程中创建一个线程
  • 在一个进程中同步线程之间的数据访问
  • 修改线程的属性
  • 在同一个进程中,从一个线程中控制另一个线程

c++ thread库

线程创建

#include <pthread.h>
// 创建线程
int pthread_create(pthread_t* thid, pthread_attr_t* attr, void *(*start_routine)(void*), void* arg);
// 终止线程
void pthread_exit(void *retval);
// join or detach
int pthread_join(pthread_t thid, void **thread_retval);
int pthread_detach (pthread_t __th);

线程同步

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_wait(sem_t *sem);  // 当sem为0时则无法-1而阻塞
int sem_post(sem_t *sem);  // +1无上限
int sem_destroy(sem_t *sem);  // 销毁信号量
#include <pthread.h>
// pthread_mutex_init(&mutex, NULL);
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);

线程属性

#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy (pthread_attr_t *__attr);

/*
 * detachstate: PTHREAD_CREATE_JOINABLE/PTHREAD_CREATE_DETACHED
*/
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
/*
 * schedpolicy:控制线程的调度方式。它的取值可以是SCHED_OTHER(默认)\SCHED_RP\SCHED_FIFO,
 * 其他两种方式只能用于超级用户
*/
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
int pthread_attr_getschedpolicy(const pthread_attr_t *attr, int *policy);
/*
 * 与schedpolicy结合使用,它可以对SCHED_OTHER策略运行的线程的调度进行控制
*/
int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param);
int pthread_attr_getschedparam(const pthread_attr_t *attr, struct sched_param *param); 
/*
 * PTHREAD_EXPLICIT_SCHED(default, 调度由属性明确设置)/PTHREAD_INHERIT_SCHED(继承创建者的属性)
*/
int pthread_attr_setinheritsched(pthread_attr_t *attr, int inherit);
int pthread_attr_getinheritsched(const pthread_attr_t *attr, int *inherit);
/*
 * 控制线程调度的计算方式,取值:PTHREAD_SCOPE_SYSTEM
*/
int pthread_attr_setscope(pthread_attr_t *attr, int scope);
int pthread_attr_getscope(const pthread_attr_t *attr, int *scope);
/*
 * 控制栈大小
*/
int pthread_attr_setstacksize(pthread_attr_t *attr, int stacksize);
int pthread_attr_getstacksize(const pthread_attr_t *attr, int *stacksize);

通过线程属性设置线程分离

// ignore header
int thread_finished = 0;

void* thread_function(void* arg)
{
    printf("thread_function is running. Argument was %s\n", (char*)arg);
    sleep(4);
    printf("Second thread setting finished flag, and exiting now\n");
    thread_finished = 1;
    pthread_exit(NULL);   // 这里等价于return NULL;
}

int main()
{
    int res;
    pthread_t a_thread;
    pthread_attr_t thread_attr;
    int max_priority, min_priority;
    struct sched_param scheduling_value;
    // init thread_attr
    res = pthread_attr_init(&thread_attr);
    if (res != 0)
    {
        perror("Attribute creation failed");
        exit(EXIT_FAILURE);
    }
    // 设置线程分离
    res = pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);
    if (res != 0)
    {
        perror("Setting detached attribute failed");
        pthread_attr_destroy(&thread_attr);
        exit(EXIT_FAILURE);
    }
    // 设置调度策略
    res = pthread_attr_setschedpolicy(&thread_attr, SCHED_OTHER);
    if (res != 0)
    {
        perror("Setting scheduling policy failed");
        pthread_attr_destroy(&thread_attr);
        exit(EXIT_FAILURE);
    }
    // 查看优先级策略
    max_priority = sched_get_priority_max(SCHED_OTHER);
    min_priority = sched_get_priority_min(SCHED_OTHER);
    // 设置优先级
    scheduling_value.sched_priority = min_priority;
    res = pthread_attr_setschedparam(&thread_attr, &scheduling_value);
    if (res != 0)
    {
        perror("Setting scheduling priority failed");
        pthread_attr_destroy(&thread_attr);
        exit(EXIT_FAILURE);
    } 
    // 创建线程
    res = pthread_create(&a_thread, &thread_attr, thread_function, (void*)"Hello");
    if (res != 0)
    {
        perror("Thread creation failed");
        pthread_attr_destroy(&thread_attr);
        exit(EXIT_FAILURE);
    }
    // 函数完成后,线程已经被创建,并且线程的属性已经被传递给了新创建的线程。因此,可以安全地销毁线程属性对象。
    pthread_attr_destroy(&thread_attr);
    while (!thread_finished)
    {
        printf("Waiting for thread to say it's finished...\n");
        sleep(1);
    }
    printf("Other thread finished, bye!\n");
    exit(EXIT_SUCCESS);
}

取消一个线程

让一个线程令另一个线程终止。

#include <pthread.h>
// 请求另一个线程终止
int pthread_cancel(pthread_t thread);

// 第一层控制是否接收请求 PTHREAD_CANCEL_ENABLE(默认状态)/PTHREAD_CANCEL_DISABLE
int pthread_setcancelstate(int state, int *oldstate);
// 第二层控制,PTHREAD_CANCEL_ASYNCHRONOUS(立即行动)/PTHREAD_CANCEL_DEFERRED(执行到取消点处,默认状态)
// 取消点:pthread_join, pthread_cond_wait, pthread_cond_timedwait, pthread_testcancel, sem_wait, sigwait
// 某些阻塞函数也可能是潜在的取消点
int pthread_setcanceltype(int type, int *oldtype);

pthread_exit, exit, _exit

  1. exit函数:
    • 头文件: #include <stdlib.h>
    • exit函数是C标准库函数,用于正常终止程序的执行。
    • 在调用exit时,会执行程序中注册的所有终止处理程序(通过atexit函数注册的函数)。
    • exit函数会关闭所有已打开的流(文件流、标准输入、标准输出等)。
  2. _exit函数:
    • 头文件: #include <unistd.h>
    • _exit是系统调用,用于立即终止进程,不执行任何清理操作,也不会调用终止处理程序。
    • 不会关闭已打开的文件描述符,因此需要手动关闭。
  3. pthread_exit函数:
    • 头文件: #include <pthread.h>
    • pthread_exit用于终止调用它的线程。
    • 不同于exit和_exit,pthread_exit只终止调用它的线程,而不会终止整个进程。
    • 可以向pthread_exit传递一个指针,这个指针会成为线程的退出状态,其他线程可以通过pthread_join来获取。

十三、管道

popen, pipe

通过管道与命令行进行交互,system只能调用命令而不能与其通讯,popen通过管道与调用的命令进行数据传递

#include <stdio.h>
FILE* popen(const char* command, const char* open_mode);
int pclose(FILE* stream_to_close);
#include <unistd.h>
// 1写入,0读取
// 管道内有缓存区
int pipe(int file_descriptor[2]);

父子进程

在子进程中运行一个与其父进程完全不同的另外一个程序,而不仅仅运行一个相同程序,通过exec调用完成该工作。这里的一个难点是,通过exec调用的进程需要知道应该访问哪个文件描述符。如果子进程本身有file_pipes数据的一份副本,所以不成问题。但经过exec调用后,原先的进程被新的替换了。为了解决该问题,可以将文件描述符作为一个参数传递给用exec启动的程序。

example

当管道的写端被关闭时,read将返回0,而不是阻塞。read无效文件会返回-1。
只有把父子进程中针对管道的写fd都关闭,管道才会被认为是关闭。

注:
  fork会复制调用进程,包括文件描述符的指向,即父进程的文件描述符指向一个操作系统对象,则子进程的描述符与父进程相同。
  在调用exec函数族时,管道(pipe)通常不会被自动关闭。因此在之后的进程中文件描述符依然可用。
  在调用exec后文件描述符是否可用取决于该文件描述符的状态,若通过fcntl函数将fd设置为了FD_CLOEXEC,则新程序关闭该描述符。
  当进程退出后大部分操作系统对象都会被关闭,但有一些资源除外。

将管道用作标准输入和标准输出

命名管道:FIFO

# Ubuntu实测权限不会受到umask影响
mkfifo [-m mode] filename
#include <sys/types.h>
#include <sys/stat.h>
// Ubuntu实测权限会受到umask影响
int mkfifo(const char* filename, mode_t mode);
  1. 使用open打开FIFO文件
    open(const char* path, O_RDONLY);
    open(const char* path, O_RDONLY | O_NONBLOCK);
    open(const char* path, O_WRONLY);
    open(const char* path, O_WRONLY | O_NONBLOCK);
    
    如果没有进程以读方式打开管道,非阻塞写方式的open调用将失败,但非阻塞读open总是成功

十四、IPC通讯

信号量

semaphore.hsys/sem.h 都是在C语言中用于处理信号量的头文件,但它们在功能上有一些区别。

  1. semaphore.h

    • 标准库头文件: semaphore.h 是POSIX标准库的一部分,定义了一组函数和宏,用于操作和管理信号量。
    • 跨平台性: 由于它是POSIX标准的一部分,因此具有较好的跨平台性。可以在不同的Unix-like系统(如Linux)上使用。
  2. sys/sem.h

    • 系统头文件: sys/sem.h 是特定于Linux系统的头文件,用于在Linux中进行信号量操作。
    • 较低级别的API:semaphore.h 不同,sys/sem.h 提供了更底层的信号量API。它使用semgetsemop等系统调用来创建和操作信号量。

总的来说,如果你的目标是编写跨平台的代码,最好使用 semaphore.h。如果你在Linux平台上编写代码,需要更底层的控制,可能会选择使用 sys/sem.h。然而,考虑到 sys/sem.h 是Linux特定的,使用它可能使你的代码在其他Unix-like系统上不可移植。

操作说明
P(sv)如果sv的值大于零,就给它减去1;如果它的值等于零,就挂起该进程的执行
V(sv)如果有其它进程因等待sv而被挂起,就它恢复运行;如果没有进程等待sv,就给他加1
#include <sys/sem.h>
// 
int semctl(int sem_id, int sem_num, int command, ...);
// 创建一个新信号量或取的一个已有信号量
int setmget(key_t key, int num_sems, int semflags);
// 
int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);

共享内存

#include <sys/shm.h>
// 将共享内存连接到进程地址空间
void* shmat(int shm_id, const void* shm_addr, int shmflg);
// 控制共享内存
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
// 将共享内存从当前地址空间中分离
int shmdt(const void* shm_addr);
// 返回shm_id, 传入key,大小和权限
int shmget(key_t key, size_t size, int shmflg);

消息队列

在这里插入图片描述

#include <sys/msg.h>

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

// 创建消息队列
int msgget(key_t key, int msgflg);

// 接收消息
int msgrcv(int msqid, void *msg_ptr, size_t msg_sz, long int msgtype, int msgflg);
	- msgtype: 如果只想按照消息发送的顺序接收则将其设为0,如果只想获取某一特定类型的消息就将其设为相应类型,如果想接收类型等于或小于n的消息,就将其设为-n
	- msgflg: 用于设置当队列中没有相应类型的消息可以接收时将发生的事情

// 把消息添加进消息队列中
int msgsnd(int msqid, const void *msg_ptr, size_t msg_sz, int msgflg);
	- msg_ptr: 最好定义成以下形式 struct my_message{long int message_type; /*data*/};
	- msg_sz: data部分的长度
	- msgflg: 控制在当前消息队列满或超限时的行为

IPC状态命令

ipcs

十八、Linux标准

LSB标准

LSB系统初始化

Linux系统运行级别:

运行级别description
0停止。用作一种可以在系统关闭时切换到的逻辑状态
1单用户模式。非目录的其他目录可能不会在这种模式下被装载,网络功能也将被禁用。该模式通常用于系统维护
2多用户模式,但未启用网络功能
3正常的带网络功能的多用户模式,使用文本模式的登录界面
4保留
5正常的带网络功能的多用户模式,使用图形登录界面
6用于重启系统的伪运行级别

与运行级别相伴的一组用于启动、关闭和重启服务的初始化脚本位置:/etc/init.d/
用于控制初始化脚本行为的控制参数:

参数description
start启动(或重启)服务
stop停止服务
restart重启服务 ,它一般是通过先停止服务再重启服务的方式来实现的
reload重置服务,在不停止服务的情况下重新装载所有的参数。
force-reload如果服务支持这个选项,就重载服务,否则,就重启服务
status以文本方式打印服务的状态信息,并返回一个可以用来确定服务状态的状态码

文件系统层次结构标准

Filesystem Hierarchy Standard
https://www.pathname.com/fhs/

  • 对运行Linux的某一特定系统唯一的文件和目录,如启动脚本和配置文件
  • 可以运行在Linux的不同系统之间共享的只读文件和目录,如可执行应用程序
  • 可以在运行Linux或其他操作系统的不同系统之间共享的可读可写目录,如用户家目录
目录是否需要用户
/binY重要的系统二进制文件
/bootY启动系统所需要的文件
/devY设备文件
/etcY系统配置文件
/libY标准函数库
/mediaY用于装载可移动媒体的位置
/mntY方便临时装载如CD-ROM和内存棒等设备的目录
/optY其他应用程序软件
/sbinY在系统启动时需要的重要的系统二进制文件
/srvY用于系统提供的服务的只读数据
/tmpY临时文件
/usrY
/varY可变数据,如日志文件

GNU 构建系统
Autoconf解决了系统特使构建和运行时信息的难题,但在软件开发时还有更多的难题,GNU构建系统是为了更好的开发软件而开发的一套完整的公益事业。
主要组成部分有Autoconf、Automake和Libtool。


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

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

相关文章

mybatis多表查询(xml)

多表查询都用resultMap resultMap 说白了就是他可以手动设置映射参数&#xff0c;例如 可以指定 column代表数据库的参数 property 代表实体类的参数 <id column"roleid" property"id"></id> column代表数据库的参数 property 代表实体类…

C++入门篇第十篇----继承

前言&#xff1a; 本篇我们将开始讲解C的继承&#xff0c;我想要说的是&#xff0c;C的主体基本就是围绕类和对象展开的&#xff0c;继承也是以类和对象为主体&#xff0c;可以说&#xff0c;C相较于C优化的地方就在于它对于结构体的使用方法的高度扩展和适用于更多实际的场景…

外包干了2年,技术退步明显。。。

前言 简单的说下&#xff0c;我大学的一个同学&#xff0c;毕业后我自己去了自研的公司&#xff0c;他去了外包&#xff0c;快两年了我薪资、技术各个方面都有了很大的提升&#xff0c;他在外包干的这两年人都要废了&#xff0c;技术没一点提升&#xff0c;学不到任何东西&…

软件工程 - 第8章 面向对象建模 - 3 - 动态建模

状态图 状态是指在对象生命周期中满足某些条件、执行某些活动或等待某些事件的一个条件和状况 。 案例一&#xff1a;描述烧水器在工作时的详细行为细节 “人就是一个类&#xff0c;而你”、我”、张三”等都是“人这个类的一个实例&#xff0c;站着”、“躺着等都是对象的一…

Edge 旧版本回退

微软官网 下载策略文件 下载后&#xff0c;解压打开 cad 包&#xff0c;把里面的 Windows\ADMX\ 下 3 个 *.admx 文件解压到 C:\Windows\PolicyDefinitions Windows\ADMX\zh-CN 下 3 个 *.adlm 文件解压到 C:\Windows\PolicyDefinitions\zh-CN Windows 搜索 gpedit&#xff…

Swin Transformer实战图像分类(Windows下,无需用到Conda,亲测有效)

目录 前言 一、从官网拿到源码&#xff0c;然后配置自己缺少的环境。 针对可能遇到的错误&#xff1a; 二、数据集获取与处理 2.1 数据集下载 2.2 数据集处理 三、下载预训练权重 四、修改部分参数配置 4.1 修改config.py 4.2 修改build.py 4.3 修改units.py 4.4 修…

LeetCode的几道题

一、捡石头 292 思路就是&#xff1a; 谁面对4块石头的时候&#xff0c;谁就输&#xff08;因为每次就是1-3块石头&#xff0c;如果剩下4块石头&#xff0c;你怎么拿&#xff0c;我都能把剩下的拿走&#xff0c;所以你就要想尽办法让对面面对4块石头的倍数&#xff0c; 比如有…

python常用函数

1.len函数求字符串长度 例如 2.input函数为输入 input里边可以是任意类型的数据 但是它返回的值是一个字符串(即现在只能做出打印那些操作) 想做出其他操作的话,要强制类型转换 例,用str转换为字符串(类似的还有float),字符串可以互相拼接 所以要记得用了input函数后要强制…

十六进制数列求和

高精度数组的集大成 做的时候在和高中同学叙叙旧&#xff0c;差点寄掉 代码如下&#xff1a; #include<stdio.h> void expand(int len); const char hexadecimal[17] "0123456789ABCDEF"; int result[20], mid[20], l_result[100];int main(void) {char tm…

深度学习常见回归分支算法逐步分析,各种回归之间的优缺点,适用场景,举例演示

文章目录 1、线性回归&#xff08;Linear Regression&#xff09;1.1 优点1.2 缺点1.3 适用场景1.4 图例说明 2、多项式回归&#xff08;Polynomial Regression&#xff09;2.1 优点2.2 缺点2.3 适用场景2.4 图例说明 3、决策树回归&#xff08;Decision Tree Regression&#…

疫苗接种(链表练习)

很明显&#xff0c;数组也可以做&#xff0c;但是我想练习链表 这道题我上交的时候&#xff0c;同一份代码&#xff0c;三个编译器&#xff0c;三个成绩&#xff0c;有点搞心态 代码如下&#xff1a; #include<stdio.h> #include<math.h> #include<stdlib.h&…

线上CPU飙高问题排查!

https://v.douyin.com/iRTqH5ug/ linux top命令 top 命令是 Linux 下一个强大的实用程序&#xff0c;提供了系统资源使用情况的动态、实时概览。它显示了当前正在运行的进程信息&#xff0c;以及有关系统性能和资源利用情况的信息。 以下是 top 命令提供的关键信息的简要概述…

面试数据库八股文十问十答第一期

面试数据库八股文十问十答第一期 作者&#xff1a;程序员小白条&#xff0c;个人博客 1.MySQL常见索引、 MySQL常见索引有: 主键索引、唯一索引、普通索引、全文索引、组合索引(最左前缀)主键索引特点&#xff1a;唯一性&#xff0c;非空&#xff0c;自增&#xff08;如果使用…

Linux中的UDEV机制与守护进程

Linux中的UDEV守护进程 udev简介守护进程守护进程概念守护进程程序设计守护进程的应用守护进程和后台进程的区别 UDEV的配置文件自动挂载U盘 udev简介 udev是一个设备管理工具&#xff0c;udev以守护进程的形式运行&#xff0c;通过侦听内核发出来的uevent来管理/dev目录下的设…

cnpm 安装后无法使用怎么办?

问题的原因 cnpm 安装成功&#xff0c;但是却无法使用&#xff0c;一般分为两种情况&#xff0c;一种是提示无法执行命令&#xff0c;另一种是可以执行但是执行时报错&#xff0c;下面分别说明遇到这两种情况的解决方案。 解决方案 问题一&#xff1a;无法执行相关命令 首先…

零基础打靶—CTF4靶场

一、打靶的主要五大步骤 1.确定目标&#xff1a;在所有的靶场中&#xff0c;确定目标就是使用nmap进行ip扫描&#xff0c;确定ip即为目标&#xff0c;其他实战中确定目标的方式包括nmap进行扫描&#xff0c;但不局限于这个nmap。 2.常见的信息收集&#xff1a;比如平常挖洞使用…

jionlp :一款超级强大的Python 神器!轻松提取地址中的省、市、县

在日常数据处理中&#xff0c;如果你需要从一个完整的地址中提取出省、市、县三级地名&#xff0c;或者乡镇、村、社区两级详细地名&#xff0c;你可以使用一个第三方库来实现快速解析。在使用之前&#xff0c;你需要先安装这个库。 pip install jionlp -i https://pypi.douba…

LeetCode - 965. 单值二叉树(C语言,二叉树,配图)

二叉树每个节点都具有相同的值&#xff0c;我们就可以比较每个树的根节点与左右两个孩子节点的值是否相同&#xff0c;如果不同返回false&#xff0c;否则&#xff0c;返回true。 如果是叶子节点&#xff0c;不存在还孩子节点&#xff0c;则这个叶子节点为根的树是单值二叉树。…

【算法通关村】链表基础经典问题解析

【算法通关村】链表基础&经典问题解析 一.什么是链表 链表是一种通过指针将多个节点串联在一起的线性结构&#xff0c;每一个节点&#xff08;结点&#xff09;都由两部分组成&#xff0c;一个是数据域&#xff08;用来存储数据&#xff09;&#xff0c;一个是指针域&…

每日一练:冒泡排序

1. 概述 冒泡排序&#xff08;Bubble Sort&#xff09;也是一种简单直观的排序算法。它重复地走访过要排序的数列&#xff0c;一次比较两个元素&#xff0c;如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换&#xff0c;也就是说该数列已经排…