Nginx 内存池

news2024/9/20 22:49:47

目录

零、基本框架

一、基础结构 

二、对外接口

三、函数实现

1、ngx_create_pool

2、ngx_destroy_pool

3、ngx_reset_pool

4、ngx_palloc

5、ngx_pnalloc

6、ngx_pmemalign

7、ngx_pfree

8、ngx_pcalloc

9、ngx_pool_cleanup_add

10、ngx_pool_run_cleanup_file

11、相关内存 handler


零、基本框架

        nginx 内存池模块基于链表结构设计,每个链表节点都是一个内存池单元。每个内存池单元通过从操作系统内存中预申请一整片空间,通过移动指针进行内存分配。内存池模块支持动态扩容,在内存池空间不足时,往内存池链表中新增内存池节点。

        对于大内存申请需求,内存池模块设计了 large 链表进行管理。内存池模块 reset 或 destroy 时都会清理 large 链表(调用 free 释放掉)。

        内存池模块分配内存时支持内存对齐,提高 CPU 寻址效率。内存池模块也支持自定义对文件句柄等类似资源的 close handler。

一、基础结构 

/*
 * NGX_MAX_ALLOC_FROM_POOL should be (ngx_pagesize - 1), i.e. 4095 on x86.
 * On Windows NT it decreases a number of locked pages in a kernel.
 */
#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1)

#define NGX_DEFAULT_POOL_SIZE    (16 * 1024)

#define NGX_POOL_ALIGNMENT   16

#define NGX_MIN_POOL_SIZE                                                     \
    ngx_align((sizeof(ngx_pool_t) + 2 * sizeof(ngx_pool_large_t)),            \
              NGX_POOL_ALIGNMENT)


typedef struct ngx_pool_cleanup_s  ngx_pool_cleanup_t;
struct ngx_pool_cleanup_s {
    ngx_pool_cleanup_pt   handler;
    void                 *data;
    ngx_pool_cleanup_t   *next;
};

/*
	大块内存片结构(典型链表结构)
*/
typedef struct ngx_pool_large_s  ngx_pool_large_t;
struct ngx_pool_large_s {
    ngx_pool_large_t     *next;
    void                 *alloc;
};

/*
	内存池节点描述
*/
typedef struct {
    u_char               *last;  // 未使用内存的起始指针
    u_char               *end;   // 内存池的临界指针 
    ngx_pool_t           *next;  // 下一个内存池的指针
    ngx_uint_t            failed; //分配失败次数
} ngx_pool_data_t;

typedef struct {
    ngx_fd_t              fd;
    u_char               *name;
    ngx_log_t            *log;
} ngx_pool_cleanup_file_t;

/*
	内存池链表主结构
*/
struct ngx_pool_s {
    ngx_pool_data_t       d;    // 内存池链头节点
    size_t                max;  // 内存池可分配的最大内存片大小,超出 max 的分配 ngx_pool_large_t
    ngx_pool_t           *current;  // 当前正在使用的内存池指针(可直接分配内存空间)
    ngx_chain_t          *chain;  //
    ngx_pool_large_t     *large;  // 保存超出 max 大小的内存链
    ngx_pool_cleanup_t   *cleanup; // 自带释放 handler(例如文件句柄的close) 的内存链
    ngx_log_t            *log;
};

二、对外接口

// 新建内存池
ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log);
// 销毁内存池
void ngx_destroy_pool(ngx_pool_t *pool);
// 重置内存池,释放 large 链表
void ngx_reset_pool(ngx_pool_t *pool);

/*
	从内存池中申请 size 大小的内存,内存按平台位数对齐。
	小于 max 的内存,直接从内存池中分配;
	大于 max 的内存,动态申请,并记录在 large 链表中
*/
void *ngx_palloc(ngx_pool_t *pool, size_t size);
// 同 ngx_palloc,但不进行内存对齐
void *ngx_pnalloc(ngx_pool_t *pool, size_t size);

/*
	调用 ngx_palloc 分配内存,并初始化为 0 
*/
void *ngx_pcalloc(ngx_pool_t *pool, size_t size);

/*
	从系统申请一块内存对齐的内存,并保存在 large 链表中
*/
void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);

/*
	释放保存在 large 链表中的 p 地址内存
*/
ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p);

/*
    从内存池中申请 size 大小的内存,并可以通过调用自定义的回调函数进行释放
*/
ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size);

/*
	释放 cleanup 中包含文件句柄 fd 的节点
*/
void ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd);

/*
    cleanup 节点中的资源释放 handler,同 close,释放句柄
*/
void ngx_pool_cleanup_file(void *data);

/*
    cleanup 节点中资源释放 handler,同 unlink,删除文件
*/
void ngx_pool_delete_file(void *data);

三、函数实现

1、ngx_create_pool

        新建内存池

ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
    ngx_pool_t  *p;
	// 申请按 NGX_POOL_ALIGNMENT 内存对齐 size 大小的内存空间
    // linux 下主要依赖 posix_memalign 和 memalign 两个函数
    p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
    if (p == NULL) {
        return NULL;
    }
	// 该内存池可分配内存的起始指针
    p->d.last = (u_char *) p + sizeof(ngx_pool_t);
	// 该内存池可分配内存的临界指针
    p->d.end = (u_char *) p + size;
	// 初始化时,内存池链表只有一个节点
    p->d.next = NULL;
	// 内存分配失败次数
    p->d.failed = 0;
	// 当前内存池可分配的内存总量
    size = size - sizeof(ngx_pool_t);
	// 内存池可分配的最大内存为 pagesize - 1
    p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;
	// 初始化时,可使用的内存池只有当前头部节点
    p->current = p;
    p->chain = NULL;
    p->large = NULL;
    p->cleanup = NULL;
    p->log = log;

    return p;
}

2、ngx_destroy_pool

        销毁内存池链表

void
ngx_destroy_pool(ngx_pool_t *pool)
{
    ngx_pool_t          *p, *n;
    ngx_pool_large_t    *l;
    ngx_pool_cleanup_t  *c;

	// 遍历内存池 cleanup 链表,按照定义的 handler 函数释放全部空间
    for (c = pool->cleanup; c; c = c->next) {
        if (c->handler) {
            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
                           "run cleanup: %p", c);
            c->handler(c->data);
        }
    }
// 一些 debug 信息,用于确认内存池的使用状况
#if (NGX_DEBUG)

    /*
     * we could allocate the pool->log from this pool
     * so we cannot use this log while free()ing the pool
     */

    for (l = pool->large; l; l = l->next) {
        ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);
    }

    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
        ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
                       "free: %p, unused: %uz", p, p->d.end - p->d.last);

        if (n == NULL) {
            break;
        }
    }

#endif
	// 释放全部大块内存(申请超过 max 的内存均在此链表)
    for (l = pool->large; l; l = l->next) {
        if (l->alloc) {
            ngx_free(l->alloc);
        }
    }
	// 自头节点,遍历所有内存池,释放空间
    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
        ngx_free(p);

        if (n == NULL) {
            break;
        }
    }
}

3、ngx_reset_pool

        重置内存池,不是释放该空间,而且调整内存池可用范围,重复使用内存池空间。

        nginx 内存池模块没有设计类似 free 的内存返还函数,这边避免了繁琐的内存碎片管理逻辑。在需要时直接重置 ngx_reset_pool 即可。

/*
	需要注意,内存池空间是预申请的一整块空间。
	重置内存池,不是释放该空间,而且调整内存池可用范围,重复使用内存池空间。
*/
void
ngx_reset_pool(ngx_pool_t *pool)
{
    ngx_pool_t        *p;
    ngx_pool_large_t  *l;
	
	// 重置内存池会释放掉所有的大块内存
    for (l = pool->large; l; l = l->next) {
        if (l->alloc) {
            ngx_free(l->alloc);
        }
    }
	
	// 遍历所有的内存池节点,修改可分配空间的起始指针
    for (p = pool; p; p = p->d.next) {
        p->d.last = (u_char *) p + sizeof(ngx_pool_t);
        p->d.failed = 0;
    }

	// 重置 current 指针
    pool->current = pool;
    pool->chain = NULL;
    pool->large = NULL;
}

4、ngx_palloc

        按调用顺序,从上到下看。

/*
	从内存池中申请 size 大小的内存,内存按平台位数对齐。
	小于 max 的内存,直接从内存池中分配;
	大于 max 的内存,动态申请,并记录在 large 链表中
*/
void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
    if (size <= pool->max) {
        return ngx_palloc_small(pool, size, 1);
    }
#endif

    return ngx_palloc_large(pool, size);
}

        申请小于 max 的内存,直接从内存池中划分

/*
	分配内存池中 size       大小的空间,align 标识是否进行内存对齐
*/
static ngx_inline void *
ngx_palloc_small(ngx_pool_t *pool, size_t size, ngx_uint_t align)
{
    u_char      *m;
    ngx_pool_t  *p;
	// 从当前可用内存池节点中进行分配
    p = pool->current;

    do {
		// 分配的内存的指针
        m = p->d.last;

		// 内存按平台字长对齐(64 bit)
        if (align) {
            m = ngx_align_ptr(m, NGX_ALIGNMENT);
        }

		// 若当前内存池空间足够则直接进行分配
        if ((size_t) (p->d.end - m) >= size) {
            p->d.last = m + size;

            return m;
        }

		// 当前内存池空间不足,则向下寻找可供分配的内存池
        p = p->d.next;

    } while (p);

	// 若当前内存池链都不存在满足 size 大小的空间,则新建内存池
    return ngx_palloc_block(pool, size);
}

        新增内存池节点,并返回申请的内存指针

/*
	新增内存池节点
*/
static void *
ngx_palloc_block(ngx_pool_t *pool, size_t size)
{
	// 分配的内存指针
    u_char      *m;
    size_t       psize;
    ngx_pool_t  *p, *new;
	// 获取内存池大小,初始化时指定
    psize = (size_t) (pool->d.end - (u_char *) pool);
	// 分配 psize 内存池空间
    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
    if (m == NULL) {
        return NULL;
    }
	// 对新内存池的一些参数进行初始化赋值
    new = (ngx_pool_t *) m;

    new->d.end = m + psize;
    new->d.next = NULL;
    new->d.failed = 0;

    m += sizeof(ngx_pool_data_t);
	// 内存对齐
    m = ngx_align_ptr(m, NGX_ALIGNMENT);
	// 分配出去后,调整可用内存范围
    new->d.last = m + size;
	/*
		因为刚刚分配失败了,新建了内存池节点,所以需要遍历内存池链,
		刷新下每个节点的可用状态,确定新的 current 内存池节点
		current 记录的内存池节点,是可直接使用分配内存的节点,不然每次遍历链表搜索是很耗时的。
		对于失败次数太多的节点(超过 4 次),需要封存,将 current 指针向下移动。
	*/
	
    for (p = pool->current; p->d.next; p = p->d.next) {
        if (p->d.failed++ > 4) {
            pool->current = p->d.next;
        }
    }

	// 将新增的内存池节点加到内存池链表末尾
    p->d.next = new;

    return m;
}

        申请大于 max 的内存空间,则单独从操作系统内存中额外申请,并记录在 large 链表中

/*
	申请大于 max 的内存空间,则单独从操作系统内存中额外申请,并记录在 large 链表中
*/
static void *
ngx_palloc_large(ngx_pool_t *pool, size_t size)
{
    void              *p;
    ngx_uint_t         n;
    ngx_pool_large_t  *large;
	// 该函数调用了 malloc 申请了内存
    p = ngx_alloc(size, pool->log);
    if (p == NULL) {
        return NULL;
    }

    n = 0;
	/*
		将新申请的内存空间,保存在 large 链表中
	*/
	// 1、先遍历 large 链表最前面的三个节点,若存在内存已经被 free 的,则保存在该节点
    for (large = pool->large; large; large = large->next) {
        if (large->alloc == NULL) {
            large->alloc = p;
            return p;
        }
		// 往后找三个节点
        if (n++ > 3) {
            break;
        }
    }
	// 2、前三个都没有被 free,则新建 large 节点,将新申请的大内存保存在该节点
    large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
    if (large == NULL) {
        ngx_free(p);
        return NULL;
    }
	// 3、将新建的 large 节点作为新的头节点
    large->alloc = p;
    large->next = pool->large;
    pool->large = large;

    return p;
}

5、ngx_pnalloc

        与 ngx_palloc 类似,唯一区别在于申请的小于 max 的内存不会对齐。

void *
ngx_pnalloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
    if (size <= pool->max) {
        // 不对齐
        return ngx_palloc_small(pool, size, 0);
    }
#endif

    return ngx_palloc_large(pool, size);
}

6、ngx_pmemalign

        从系统申请一块内存对齐的内存,并保存在 large 链表中

/*
	从系统申请一块内存对齐的内存,并保存在 large 链表中
*/
void *
ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment)
{
    void              *p;
    ngx_pool_large_t  *large;

	// 申请一块 size 大小的内存,内存对齐
    p = ngx_memalign(alignment, size, pool->log);
    if (p == NULL) {
        return NULL;
    }

    large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
    if (large == NULL) {
        ngx_free(p);
        return NULL;
    }
	// 添加到 large 链表头部
    large->alloc = p;
    large->next = pool->large;
    pool->large = large;

    return p;
}

7、ngx_pfree

        释放保存在 large 链表中的 p 地址内存

/*
	释放保存在 large 链表中的 p 地址内存
*/
ngx_int_t
ngx_pfree(ngx_pool_t *pool, void *p)
{
    ngx_pool_large_t  *l;

    for (l = pool->large; l; l = l->next) {
		// 搜索地址为 p 的内存
        if (p == l->alloc) {
            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
                           "free: %p", l->alloc);
			// 同 free
            ngx_free(l->alloc);
            l->alloc = NULL;

            return NGX_OK;
        }
    }

    return NGX_DECLINED;
}

8、ngx_pcalloc

调用 ngx_palloc 分配内存,并初始化为 0

/*
	调用 ngx_palloc 分配内存,并初始化为 0 
*/
void *
ngx_pcalloc(ngx_pool_t *pool, size_t size)
{
    void *p;

    p = ngx_palloc(pool, size);
    if (p) {
		// 同 memset(p, 0, size)
        ngx_memzero(p, size);
    }

    return p;
}

9、ngx_pool_cleanup_add

        从内存池中申请 size 大小的内存,并可以通过调用自定义的回调函数进行释放,例如文件描述符等资源。

/*
	区别与申请内存后只能调用 free 进行释放,
	该函数申请的内存可以通过调用定义的回调函数进行释放,例如文件描述符等资源
*/
ngx_pool_cleanup_t *
ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)
{
    ngx_pool_cleanup_t  *c;
	// 新建 ngx_pool_cleanup_t 节点
    c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));
    if (c == NULL) {
        return NULL;
    }
	// 从内存池中申请 size 大小的内存空间
    if (size) {
        c->data = ngx_palloc(p, size);
        if (c->data == NULL) {
            return NULL;
        }

    } else {
        c->data = NULL;
    }
	
    c->handler = NULL;
	// 插到 cleanup 链表头节点
    c->next = p->cleanup;

    p->cleanup = c;

    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);

    return c;
}

10、ngx_pool_run_cleanup_file

        释放 cleanup 中包含文件句柄 fd 的节点

/*
	释放 cleanup 中包含文件句柄 fd 的节点
*/
void
ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd)
{
    ngx_pool_cleanup_t       *c;
    ngx_pool_cleanup_file_t  *cf;

    for (c = p->cleanup; c; c = c->next) {
		// 关闭文件句柄的 close 函数 handler
        if (c->handler == ngx_pool_cleanup_file) {

            cf = c->data;
			// 搜索 fd 句柄
            if (cf->fd == fd) {
                c->handler(cf);
                c->handler = NULL;
                return;
            }
        }
    }
}

11、相关内存 handler


void
ngx_pool_cleanup_file(void *data)
{
    ngx_pool_cleanup_file_t  *c = data;

    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d",
                   c->fd);
	// close 函数
    if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
                      ngx_close_file_n " \"%s\" failed", c->name);
    }
}

void
ngx_pool_delete_file(void *data)
{
    ngx_pool_cleanup_file_t  *c = data;

    ngx_err_t  err;

    ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d %s",
                   c->fd, c->name);

	// unlink 函数
    if (ngx_delete_file(c->name) == NGX_FILE_ERROR) {
        err = ngx_errno;

        if (err != NGX_ENOENT) {
            ngx_log_error(NGX_LOG_CRIT, c->log, err,
                          ngx_delete_file_n " \"%s\" failed", c->name);
        }
    }

    if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
                      ngx_close_file_n " \"%s\" failed", c->name);
    }
}

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

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

相关文章

【spring】@Component注解学习

Component介绍 Component 是 Spring 框架中的一个注解&#xff0c;用于将一个类标记为 Spring 上下文中的一个组件。当一个类被标记为 Component 时&#xff0c;Spring 容器会在启动时自动扫描并实例化这个类&#xff0c;并将其注册到 Spring 上下文中。 Component 注解可以用…

Gorm连接Mysql数据库及其语法

Gorm连接Mysql数据库及其语法 文章目录 Gorm连接Mysql数据库及其语法前期工作找到Gorm的github项目简单了解相关MySQL语法 启动数据库定义数据库模型注意点Gorm Model定义结构体标签(tag)支持的结构体标记&#xff08;Struct tags&#xff09;关联相关标记&#xff08;tags&…

Facebook如何使用增强技术提升广告效果?

AR in AD - case study 脸书2021年宣布了引入AR的新方法&#xff0c;以推动其应用套件中的产品发现和购买。但他们首先考虑是技术。据脸书称&#xff0c;技术一直是增强现实在其应用程序中更广泛使用的主要障碍。这就是为什么它现在正在做出改变&#xff0c;使企业主和广告商更…

DFS进阶——全排列

通过后续的题目希望大家明白&#xff0c;dfs不仅仅是对图的遍历&#xff0c;他还有很多用法。 DFS进阶1——回溯 先说一下回溯的板子 dfs(){ for(......){标记信息dfs()撤销标记 } }回溯模板——递归实现排列型枚举 题目分析 其实就是对1~n的数字全排列&#xff0c;这里就…

【JavaScript】JavaScript 程序流程控制 ⑥ ( while 循环概念 | while 循环语法结构 )

文章目录 一、while 循环1、while 循环概念2、while 循环语法结构 二、while 循环 - 代码示例1、打印数字2、计算 1 - 10 之和 一、while 循环 1、while 循环概念 在 JavaScript 中 , while 循环 是一种 " 循环控制语句 " , 使用该语句就可以 重复执行一段代码块 , …

Java项目:75 springboot房产销售系统

作者主页&#xff1a;舒克日记 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 使用房产销售系统分为管理员和用户、销售经理三个角色的权限子模块。 管理员所能使用的功能主要有&#xff1a;首页、个人中心、用户管理、销售经理管…

MRC是谁?- 媒体评级委员会 Media Rating Council

在在线广告的世界里&#xff0c;有许多不同的技术和实践用于提供和衡量广告。对于广告商、出版商和营销人员来说&#xff0c;了解这些技术是如何工作的以及如何有效使用这些技术很重要。在这方面发挥关键作用的一个组织是媒体评级委员会&#xff08;MRC&#xff09;。 1. 了解…

OpenCV 形态学处理函数

四、形态学处理&#xff08;膨胀&#xff0c;腐蚀&#xff0c;开闭运算&#xff09;_getstructuringelement()函数作用-CSDN博客 数字图像处理(c opencv)&#xff1a;形态学图像处理-morphologyEx函数实现腐蚀膨胀、开闭运算、击中-击不中变换、形态学梯度、顶帽黑帽变换 - 知乎…

Java代码基础算法练习-搬砖问题-2024.03.25

任务描述&#xff1a; m块砖&#xff0c;n人搬&#xff0c;男搬4&#xff0c;女搬3&#xff0c;两个小孩抬一砖&#xff0c;要求一次全搬完&#xff0c;问男、 女、小孩各若干&#xff1f; 任务要求&#xff1a; 代码示例&#xff1a; package M0317_0331;import java.util.S…

leetcode刷题日记-外观数组

题目描述 解题思路 初始化字符串 init 为 “1”&#xff0c;作为外观数列的第一项。 通过循环迭代生成外观数列的下一项&#xff0c;循环次数为 n-1&#xff0c;因为已经初始化了第一项。 在每次迭代中&#xff0c;通过两个指针 pos 和 start 来遍历当前项 init&#xff0c;po…

联合体/共用体

一、联合体类型的声明 联合体是由一个或多个成员构成&#xff0c;这些成员可以是不同的类型。 编译器只会为最大的成员分配足够的内存空间。 联合体的特点是全体成员共用一块内存空间&#xff0c;故联合体也叫共用体。 联合体中&#xff0c;给一个成员赋值&#xff0c;那么其他…

Spring Boot + MyBatis

一、配置依赖 <!-- MyBatis --> <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.5.3</version> </dependency> <!-- junit测试依赖 --&g…

套娃式大小AI群体导致AI觉醒吗?

一、“套娃式”AI训练 目前&#xff0c;我们所讨论的人工智能&#xff08;AI&#xff09;主要是基于机器学习和深度学习技术的算法系统。它们通过不断学习、优化和改进以完成特定任务&#xff0c;但并不具备自我意识或者独立的创造性思考能力&#xff0c;即“觉醒”。 “套娃式…

代码随想录算法训练营第33天|1005.K次取反后最大化的数组和|134. 加油站|135. 分发糖果

代码随想录算法训练营第33天|1005.K次取反后最大化的数组和|134. 加油站|135. 分发糖果 1005.K次取反后最大化的数组和 本题简单一些&#xff0c;估计大家不用想着贪心 &#xff0c;用自己直觉也会有思路。 https://programmercarl.com/1005.K%E6%AC%A1%E5%8F%96%E5%8F%8D%E5%…

c++翁恺

1、面向对象 Data&#xff1a;杯子的属性 Opera&#xff1a;杯子提供的服务 老师上课&#xff1a; C&#xff1a;按流程执行 C&#xff1a;定一个教室&#xff0c;有很多学生&#xff0c;投影仪&#xff0c;灯&#xff0c;每个学生反映不一样。 这个场景有什么东西&#xff0c…

Android开发 --- Android12外部存储权限问题

1.问题 Android12使用如下权限&#xff0c;将不会获得读写文件的权限 <uses-permission android:name"android.permission.WRITE_EXTERNAL_STORAGE" /> 2.解决 if (!Environment.isExternalStorageManager()) {Intent intent new Intent(Settings.ACTION_M…

Excel 打开后提示:MicrosoftExcel无法计算某个公式。在打开的工作簿中有一个循环引用...

目录预览 一、问题描述二、原因分析三、解决方案四、参考链接 一、问题描述 MicrosoftExcel无法计算某个公式。在打开的工作簿中有一个循环引用&#xff0c;但无法列出导致循环的引I用。请尝试编辑上次输入的公式&#xff0c;或利用“撤消”命令删除该公式&#xff0c;如下图&…

人工智能(Educoder)-- 搜索技术 -- 启发式搜索

任务描述 本关任务&#xff1a;八数码问题是在一个33的棋盘上有1−8位数字随机分布&#xff0c;以及一个空格&#xff0c;与空格相连的棋子可以滑动到空格中&#xff0c;问题的解是通过空格滑动&#xff0c;使得棋盘转化为目标状态&#xff0c;如下图所示。 为了简化问题的输…

解决Dev-C++读取输入直到文件结束 while (scanf(“%s“, str[num]) != EOF)没反应的情况

问题描述&#xff1a; Dev-C编译cpp文件读取输入直到文件结束 while (scanf("%s", str[num]) ! EOF)没反应 如图&#xff1a; 下列代码要实现的功能是从标准输入中读取一系列字符串&#xff0c;然后将这些字符串以相反的顺序输出到标准输出中。 #include <bit…

Java项目:74 ssm基于Java的超市管理系统+jsp

作者主页&#xff1a;舒克日记 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 功能包括:商品分类&#xff0c;供货商管理&#xff0c;库存管理&#xff0c;销售统计&#xff0c;用户及角色管理&#xff0c;等等功能。项目采用mave…