Linux系统编程(三)

一.线程退出函数

线程退出
pthread_exit()
void pthread_exit(void *retval);
1.功能
线程调用这个函数时,可以主动退出(终止)
它和exit()很像,exit()是终止整个程序,而pthread_exit()是终止次进程
如果在次进程里面调用错误,调用的是exit,整个线程终止。
2.返回值
成功返回0,失败返回非0值
如果返回值很多时,就会封装成一个结构体,返回值结构体变量的地址
3.参数
retval:线程结束的返回值

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int num = 5;
void *thread1(void *arg)
{
    //int num=5;
    while(1)
    {
	printf("hello world\n");
	sleep(1);
	pthread_exit(&num);
	//pthread_exit(NULL);
    }
}

int main()
{
    pthread_t id;
    if( pthread_create(&id,NULL,thread1,NULL)!=0 )
    {
	perror("pthread_creat error!\n");
	exit(1);
    }
    sleep(1);

    int *num;

    pthread_cancel(id);

    pthread_join(id,(void *)(&num) );

    printf("pthread is exit %d\n", *((int *)num));

    return 0;
}

在这里插入图片描述

pthread_exit(NULL)直接退出

return() 直接退出
在这里插入图片描述

注册线程退出处理函数
void pthread_cleanup_push(void (*routine)(void *),void *arg);
void pthread_cleanup_pop(int execute);
弹栈 return 0不会弹栈

弹栈线程退出处理函数的几种条件
pthread_cleanup_push( ! 0 ) 主动弹栈 0是不弹栈
如果线程是被别人调用pthread_cancel取消的,也会弹栈
如果调用pthread_exit函数也是会主动弹栈

**注意:**return退出的话是不会自动弹栈的,想要自动弹栈pthread_cleanup_pop(!0)

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int num = 5;

void thread_exit(void *arg)
{
    printf("my exit\n");
}

void *thread1(void *arg)
{
    pthread_cleanup_push(thread_exit,NULL);
    //int num=5;
    while(1)
    {
	printf("hello world\n");
	sleep(1);
	//pthread_exit(&num);
	pthread_exit(NULL);
	
	//return (void *)NULL;
    }
    pthread_cleanup_pop(!0);
}

int main()
{
    pthread_t id;
    if( pthread_create(&id,NULL,thread1,NULL)!=0 )
    {
	perror("pthread_creat error!\n");
	exit(1);
    }
    sleep(1);

    int *num;

    pthread_cancel(id);

    pthread_join(id,(void *)(&num) );

    printf("pthread is exit %d\n", *((int *)num));

    return 0;
}

在这里插入图片描述

二.线程分离函数 信号量 互斥锁 条件变量 PV操作

线程等待的目的:
1.保证线程的退出顺序:保证一个线程退出并且回收资源后允许下一个进程退出
2.回收线程退出时的资源情况:保证当前线程退出后,创建的新线程不会复用刚才退出线程的地址空间
3.获得新线程退出时的结果是否正确退出返回值

线程的状态
可结合态:这种状态下的线程是能被其他进程回收资源或被杀死的
可分离态:不能被其他进程回收资源或被杀死的,它存储的资源在它终止时由系统自动回收
默认情况下,线程是可结合态

线程分离函数
pthread_detach()
int pthread_detach(pthread_t thread);
1.功能:如果次线程不希望别人调用pthread_join函数来回收,而是希望自己在运行结束时,自动回收资源调用pthread_detach
将pthread_detach()中的线程变为分离态
2.返回值:成功返回0.,错误返回非0值
3.参数:thread:需要分离的那个次线程的TID

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int num = 5;
void *thread1(void *arg)
{
    //int num=5;
    while(1)
    {
	printf("hello world\n");
	sleep(1);
	pthread_exit(&num);
	//pthread_exit(NULL);
    }
}

int main()
{
    pthread_t id;
    if( pthread_create(&id,NULL,thread1,NULL)!=0 )
    {
	perror("pthread_creat error!\n");
	exit(1);
    }
    pthread_detach(id);
    sleep(1);

    int *num;

    pthread_cancel(id);

    pthread_join(id,(void *)(&num) );

    printf("pthread is exit %d\n", *((int *)num));

    return 0;
}

在这里插入图片描述

tach_struct 进程信息(所有的进程号pid,地址空间,调度优先级)存放在这个结构体中
线程切换的开销低,因为它实质上是函数的切换共用进程地址空间

进程和线程的区别
1.进程是资源分配的最小单位,线程是资源调度的最小单位
2.每个进程有独立的地址空间,多个线程共享进程的地址空间,线程间的切换比进程间的切换开销小
3.线程的调度必须通过频繁的加锁来保持线程的同步,影响线程的并发性能
4.进程比线程更加健康,多进程之间相互独立,一个进程的异常对其他进程无影响,而一个线程的崩溃可能影响其他线程或整个程序
5.线程之间的通信方便(小数据量的),同一进程下,线程共享全局变量还有静态变量等数据,而进程之间的通信需要以通信的方式进行(IPC),不过如何处理好同步与互斥,是编写多线程程序的难点
6.多线程的代码结构比多进程的代码结构简单易读

线程同步:互斥锁,线程信号量,条件变量

互斥锁:
1.定义一个互斥锁(变量) pthread_mutex_t mutex;

2.初始化锁:预设互斥锁的初始值 pthread_mutex_mutex = PTHREAD_MUTEX_INITIALIZER; (不常用)
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
功能:初始化定义的互斥锁(就是设置互斥锁所需要的值)
返回值:总是返回0,所以这个函数不需要进行错误处理
参数:
mutex:互斥锁,需要我们自己定义

3.加锁解锁
pthread_mutex_lock(&mutex);(阻塞加锁)访问临界区加锁操作
pthread_mutex_trylock(&mutex);(非阻塞加锁)当锁被占用时返回EBUSY而不是挂起等待
pthread_mutex_unlock(&mutex);访问临界区解锁

4.进程退出时销毁互斥锁
pthread_mutex_destroy(&mutex);

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


struct message
{
    int fd1;
    pthread_mutex_t mutex;
};

pthread_mutex_t mutex;

void *thread1(void *arg)
{
    //struct message msg= *( (struct message *)arg);
    //int fd = msg.fd1;
    int fd = *( (int *)arg );

    while(1)
    {
	pthread_mutex_lock(&mutex);
	write(fd,"hello",5);
	write(fd,"world\n",6);
	//sleep(1);
	pthread_mutex_unlock(&mutex);
    }
}

void *thread2(void *arg)
{
    //struct message msg= *( (struct message *)arg);
    int fd = *( (int *)arg );

    while(1)
    {
	pthread_mutex_lock(&mutex);
	write(fd,"hhhhh",5);
	write(fd,"wwwww\n",6);
	//sleep(1);
	pthread_mutex_unlock(&mutex);
    }
}

int main()
{
    //struct message msg;

    int fd = open("./a.txt",O_RDWR|O_CREAT|O_APPEND,0644);
    if(fd<0)
    {
	perror("open error");
	exit(1);
    }

    //msg.fd1 = fd;
    pthread_mutex_init(&mutex,NULL);

    pthread_t id;

    if( pthread_create(&id,NULL,thread1,(void *)(&fd))!=0 )
    {
	perror("pthread_creat error!\n");
	exit(1);
    }

    pthread_t id2;

    if( pthread_create(&id2,NULL,thread2,(void *)(&fd))!=0 )
    {
	perror("pthread_creat error!\n");
	exit(1);

    }

    //pause();

    pthread_join(id,NULL);
    pthread_join(id2,NULL);

    return 0;
}

在这里插入图片描述
在这里插入图片描述

线程信号量:
线程信号量使用步骤:
1.定义信号量集合
sem_t sem[3]
线程信号量集合其实是一个数组,数组每个元素就是一个信号量(sem[0]:第一个信号量,sem[1] 第二个信号量,sem[2]第三个信号量)

2.初始化集合中的每个信号量
#include <semaphore.h>
int sem_init(sem_t*sem,int pthread,unsigned int value);
功能:初始化线程信号量集合中的某个信号量,给它设置一个初值
返回值:成功返回0,失败返回-1,errno被设置
信号量的错误号不是返回的,而是直接设置到errno
参数:value:初始化值(对于二进制信号量来说,要么是1,要么是0)
pshare:0:给线程使用,!0可以给进程使用

3.P V操作
P:int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
V:int sem_post(sem_t *sem);

sem_wait value
假如你获得的时候别人已经获得这个信号量(value值减一),你阻塞(在内核中实现加减)

4.进程结束时,删除线程信号量集合
sem_destriy(&sem[]);

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <semaphore.h>

struct message
{
    int fd1;
    pthread_mutex_t mutex;
};

pthread_mutex_t mutex;
sem_t sem[1];

void *thread1(void *arg)
{
    //struct message msg= *( (struct message *)arg);
    //int fd = msg.fd1;
    int fd = *( (int *)arg );

    while(1)
    {
	//pthread_mutex_lock(&mutex);
	sem_wait(&sem[0]);
	write(fd,"hello",5);
	write(fd,"world\n",6);
	//sleep(1);
	//pthread_mutex_unlock(&mutex);
	sem_post(&sem[0]);
    }
}

void *thread2(void *arg)
{
    //struct message msg= *( (struct message *)arg);
    int fd = *( (int *)arg );

    while(1)
    {
	//pthread_mutex_lock(&mutex);
	sem_wait(&sem[0]);
	write(fd,"hhhhh",5);
	write(fd,"wwwww\n",6);
	//sleep(1);
	//pthread_mutex_unlock(&mutex);
	sem_post(&sem[0]);
    }
}

int main()
{
    //struct message msg;

    int fd = open("./a.txt",O_RDWR|O_CREAT|O_APPEND,0644);
    if(fd<0)
    {
	perror("open error");
	exit(1);
    }

    //msg.fd1 = fd;
    pthread_mutex_init(&mutex,NULL);

    sem_init(&sem[0],0,1);

    pthread_t id;

    if( pthread_create(&id,NULL,thread1,(void *)(&fd))!=0 )
    {
	perror("pthread_creat error!\n");
	exit(1);
    }

    pthread_t id2;

    if( pthread_create(&id2,NULL,thread2,(void *)(&fd))!=0 )
    {
	perror("pthread_creat error!\n");
	exit(1);

    }

    //pause();

    pthread_join(id,NULL);
    pthread_join(id2,NULL);

    return 0;
}

在这里插入图片描述
在这里插入图片描述

练习:线程1输出11111,线程2输出22222,线程3输出33333
11111
22222
33333
11111

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>

pthread_mutex_t  mutex;
pthread_cond_t cond1,cond2,cond3;
sem_t sem[3];

void *test1(void *arg)
{
    while(1)
    {
	sem_wait(&sem[0]);
	//pthread_mutex_lock(&mutex);
	//pthread_cond_wait(&cond1);
	printf("11111\n");
	sem_post(&sem[1]);
	//pthread_mutex_unlock(&mutex);
	sleep(1);
    }
}

void *test2(void *arg)
{
    while(1)
    {
	sem_wait(&sem[1]);
	//pthread_mutex_lock(&mutex);
	//pthread_cond_wait(&cond2,&mutex);
	printf("22222\n");
	sem_post(&sem[2]);
	//pthread_mutex_unlock(&mutex);
	sleep(1);
    }
}

void *test3(void *arg)
{
    while(1)
    {
	sem_wait(&sem[2]);
	//pthread_mutex_lock(&mutex);
	//pthread_cond_wait(&cond3,&mutex);
	printf("33333\n");
	sem_post(&sem[0]);
	//pthread_mutex_unlock(&mutex);
	sleep(1);
    }
}

int main()
{
    pthread_t id1,id2,id3;

    sem_init(&sem[0],0,1);
    sem_init(&sem[1],0,0);
    sem_init(&sem[2],0,0);

    pthread_mutex_init(&mutex,NULL);
    pthread_cond_init(&cond1,NULL);
    pthread_cond_init(&cond2,NULL);
    pthread_cond_init(&cond3,NULL);

    pthread_create(&id1,NULL,test1,NULL);
    pthread_create(&id2,NULL,test2,NULL);
    pthread_create(&id3,NULL,test3,NULL);

    pthread_join(id1,NULL);
    pthread_join(id2,NULL);
    pthread_join(id3,NULL);

    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond1);
    pthread_cond_destroy(&cond2);
    pthread_cond_destroy(&cond3);

    sem_destroy(&sem[0]);
    sem_destroy(&sem[1]);
    sem_destroy(&sem[2]);

    return 0;
}

条件变量(和互斥锁配合使用)
一条线程做自加,一条线程输出(当自加到5输出之后清零)

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>


int count = 0;
pthread_mutex_t mutex;

void * add1(void * arg)
{
    while(1)
    {
	pthread_mutex_lock(&mutex);
	count++;
	sleep(1);
	pthread_mutex_unlock(&mutex);
	sleep(1);
    }
}

void *print(void *arg)
{
    while(1)
    {
	pthread_mutex_lock(&mutex);
	if( count <=5 ) printf("%d\n",count);
	else count = 0;
	sleep(1);
	pthread_mutex_unlock(&mutex);
	sleep(1);
    }
}

int main()
{
    pthread_mutex_init(&mutex,NULL);

    pthread_t id1;
    pthread_t id2;

    int ret= pthread_create(&id1,NULL,add1,NULL);
    if(0 != ret)
    {	perror("pthread create error");exit(0); }

    int ret2= pthread_create(&id2,NULL,print,NULL);
    if(0 != ret2)
    {	perror("pthread create error");exit(0); }

    pthread_join(id1,NULL);
    pthread_join(id2,NULL);

    return 0;
}

在这里插入图片描述

条件变量的主要作用:(线程协同)主线程对count+1,此线程发现当count == 5 时输出count的值并将count清零

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>


int count = 0;
pthread_mutex_t mutex;
pthread_cond_t cond;

void * add1(void * arg)
{
    while(1)
    {
	pthread_mutex_lock(&mutex);
	count++;
	if(count == 5)
	{
	    pthread_cond_signal(&cond);
	}
	sleep(1);
	pthread_mutex_unlock(&mutex);
	sleep(1);
    }
}

void *print(void *arg)
{
    while(1)
    {
	pthread_mutex_lock(&mutex);
	if( count !=5 ) 
	{
	    pthread_cond_wait(&cond,&mutex);
	}
	printf("%d\n",count);
	sleep(1);
	pthread_mutex_unlock(&mutex);
	sleep(1);
    }
}

int main()
{
    pthread_mutex_init(&mutex,NULL);
    pthread_t id1;
    pthread_t id2;

    pthread_cond_init(&cond,NULL);

    int ret= pthread_create(&id1,NULL,add1,NULL);
    if(0 != ret)
    {	perror("pthread create error");exit(0); }

    int ret2= pthread_create(&id2,NULL,print,NULL);
    if(0 != ret2)
    {	perror("pthread create error");exit(0); }

    pthread_join(id1,NULL);
    pthread_join(id2,NULL);

    return 0;
}

在这里插入图片描述

多线程配合工作时,当线程检测到某条件不满足时就休眠,直到别的线程将条件准备好,然后通过条件变量将其叫醒

条件变量:
使用步骤
1.定义一个条件变量
pthread_cond_t= PTHREAD_COND_INITIALIZER;

2.初始化
pthread_cond_init(pthread_cond_t *restrict, const pthread_condattr *restrict attr);
功能
初始化条件变量,与互斥锁化类似

返回值:
成功返回0,不成功返回非0错误号
参数
cond:条件变量

3.使用条件变量
4.删除条件变量,也需要把互斥锁删除

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);

int pthread_cond_signal(pthread_cond_t *cond);

int pthread_cond_broadcast(pthread_cond_t *cond);

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);

int pthread_cond_destroy(pthread_cond_t *cond);

三.线程池 任务队列

多线程存在的问题:
1.进程所支持的线程数量问题(受限)
2.线程的创建和销毁的开销问题

1.任务队列为空
2.任务队列为满
3.任务队列不为空

任务队列为空时,线程池里的线程阻塞等待
任务队列不为空时,线程池里的线程处理任务
任务队列为满时,不能添加新的任务

任务队列的大小
任务队列的锁

int thread_num; //已开启的线程数量
struct job *head;//任务队列的头指针
struct job *tail;//任务队列的尾指针
int queue_max_num;//任务队列的最大数
int queue_cur_num;//任务队列已有多少任务
pthread_t *pthread_ids;//保存线程池中线程id
pthread_cond_t queue_empty;///任务队列为空的条件
pthread_cond_t queue_not_empty;//任务队列部位空的条件
pthread_cond_t queue_not_full;//任务队列不为满的条件
每个线程输出helloword和welcome to China

#include  <stdio.h>
#include  <stdlib.h>
#include  <string.h>
#include  <pthread.h>
#include  <unistd.h>


struct job
{
    void *(*func)(void *arg);
    void *arg;
    struct job *next;
    
};

struct  threadpool
{
    int thread_num;//已开启的线程数量   
    pthread_t  *pthread_ids;//保存线程池中线程id

    struct job  *head;//任务队列的头指针
    struct job  *tail;//任务队列的尾指针
    int queue_max_num;//任务队列的最大数
    int queue_cur_num;//任务队列已有多少任务

    pthread_mutex_t  mutex;
    pthread_cond_t  queue_empty;//任务队列为空的条件
    pthread_cond_t  queue_not_empty;//任务队列不为空的条件
    pthread_cond_t  queue_not_full;//任务队列不为满的条件

};
//线程函数
void  *threadpool_function( void * arg)
{
    struct  threadpool  *pool =  (struct threadpool  *) arg  ;
    struct job *pjob = NULL;

    while(1)
    {
        pthread_mutex_lock( &(pool->mutex) );

        while(pool->queue_cur_num == 0)             //任务队列是否为空
        {
            pthread_cond_wait( &(pool->queue_not_empty), &(pool->mutex)  );
        }
        pjob = pool->head;
        pool->queue_cur_num --;

        if(pool->queue_cur_num != pool->queue_max_num)
        {
            pthread_cond_broadcast( &(pool->queue_not_empty) );
        }

        if ( pool->queue_cur_num==0 )
        {
            pool->head = pool->tail = NULL ;
            pthread_cond_broadcast( &(pool->queue_empty) );
        }
        else
        {
            pool->head = pool->head->next ;
        }

        pthread_mutex_unlock( &(pool->mutex) );

        (*(pjob->func))(pjob->arg);
        free(pjob);
        pjob = NULL ;
        
    }

}
//线程池初始化
struct threadpool *threadpool_init( int thread_num,int queue_max_num)
{
    struct threadpool  *pool=( struct threadpool * )malloc(  sizeof(struct threadpool)  );
    //malloc
    //int thread_num = 20;
    //int queue_max_num = 100;

    pool->thread_num = thread_num ;
    pool->queue_max_num = queue_max_num;
    pool->queue_cur_num = 0;
    pool ->head = NULL;
    pool->tail = NULL;

    pthread_mutex_init( &(pool->mutex) ,NULL );
    pthread_cond_init( &(pool->queue_empty), NULL );
    pthread_cond_init( &(pool->queue_not_empty) , NULL );
    pthread_cond_init( &(pool->queue_not_full) , NULL );

    pool->pthread_ids =  (pthread_t  *) malloc( sizeof(pthread_t) * thread_num );
    //malloc

    for ( int i = 0; i < pool->thread_num ; i++)
    {
        pthread_create( &(pool->pthread_ids[i ]) , NULL , threadpool_function , (void  *)pool );
    }

    return pool;
}
//添加任务函数
void threadpool_add_job(struct threadpool *pool,void *func(void *) , void * arg )
{
    pthread_mutex_lock(&(pool->mutex));
    while(pool->queue_cur_num == pool->queue_max_num)
    {
        pthread_cond_wait( &(pool->queue_not_full), &(pool->mutex) );
    }

    struct job  *pjob = (struct  job *)malloc ( sizeof(struct  job));
    //malloc
    pjob->func =  func ;
    pjob->arg = arg;
    //pjob->func(pjob->arg);

    if(NULL == pool->head)
    {
        pool->head = pool->tail =pjob;
        pthread_cond_broadcast(&(pool->queue_not_empty) );
    }
    else
    {
        pool->tail  ->next = pjob;
        pool->tail = pjob;
    }
    
    pool->queue_cur_num ++;

    pthread_mutex_unlock( &(pool->mutex) );
}
//任务函数
void * work ( void * arg )
{	//在这个函数中添加任务
    char * p =(char *)arg;
    printf("hello world   %s\n",p);
    printf("welcome to china   %s\n",p);
    sleep(1);
}
//资源回收
void   threadpool_destroy (struct threadpool *pool )
{
    pthread_mutex_lock(&(pool->mutex) );
    while( pool->queue_cur_num != 0 )
    {
        pthread_cond_wait( &(pool->queue_empty) , &(pool->mutex) );
    }
    pthread_mutex_unlock( &(pool->mutex) );
    pthread_cond_broadcast(&(pool->queue_empty) );
    pthread_cond_broadcast(&(pool->queue_not_empty) );
    pthread_cond_broadcast(&(pool->queue_not_full) );

    free(pool->pthread_ids);

    for( int i = 0 ; i< pool->thread_num ; i++)
    {
        pthread_cancel(pool->pthread_ids[i] );
        pthread_join(pool->pthread_ids[i] ,NULL );
    }

    struct job *temp;
    while(pool->head !=NULL )
    {
        temp = pool->head;
        pool->head = temp->next;
        free(temp);
    }
      free(pool);

    pthread_mutex_destroy(&(pool->mutex));       
    pthread_cond_destroy(&(pool->queue_empty));
    pthread_cond_destroy(&(pool->queue_not_empty));   
    pthread_cond_destroy(&(pool->queue_not_full));    

}

int main()
{
    struct  threadpool  * pool =  threadpool_init( 10,100 ); //一次运行10个线程   

    threadpool_add_job( pool , work , "1" ); 
    threadpool_add_job( pool , work , "2" ); 
    threadpool_add_job( pool , work , "3" ); 
    threadpool_add_job( pool , work , "4" ); 
    threadpool_add_job( pool , work , "5" ); 
    threadpool_add_job( pool , work , "6" ); 
    threadpool_add_job( pool , work , "7" ); 
    threadpool_add_job( pool , work , "8" ); 
    threadpool_add_job( pool , work , "9" ); 
    threadpool_add_job( pool , work , "10" ); 

    threadpool_add_job( pool , work , "11" ); 
    threadpool_add_job( pool , work , "22" ); 
    threadpool_add_job( pool , work , "33" ); 
    threadpool_add_job( pool , work , "44" ); 
    threadpool_add_job( pool , work , "55" ); 
    threadpool_add_job( pool , work , "66" ); 
    threadpool_add_job( pool , work , "77" ); 
    threadpool_add_job( pool , work , "88" ); 
    threadpool_add_job( pool , work , "99" ); 
    threadpool_add_job( pool , work , "100" ); 
    
    
    sleep(50);
    threadpool_destroy(pool );
   
    return 0;
}

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

家住隔壁我姓王8

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值