软硬链接和动静态库

news2024/11/16 15:32:57

为什么一定要提供路径呢?

因为要根据路径找到文件

一切与路径相关的问题都是方便用户去访问文件

软硬链接

给我康康

软链接是这样的:

ln -s file_target1.txt file_soft.link

 

软链接有独特的innode

这是硬链接:

ln file_target2.txt file_hard.link

 

特征及使用场景

软链接是一个独立的文件,因为有独立的innode,软链接内容包含目标文件对应的路径字符串

硬链接是通过inode引用另外一个文件,软链接是通过名字引用另外一个文件

软链接像是Windows中的快捷方式(用绝对路径形成的,指向可执行程序)

软链接删掉不会影响目标文件(删快捷方式不影响应用本体)

软链接有何用呢?

就是快捷方式的作用

软链接为何要包含路径呢?路径唯一哎

硬链接不是一个独立的文件,硬链接的innode编号和文件一样,硬链接是一个文件名和innode的映射关系,建立硬链接就是在指定目录下,添加一个新的文件名和innode number的映射关系,指针指向同一个文件属性,指针计数为2

硬链接相当于重命名,属性中有一列硬链接数,文件的磁盘及引用计数:有多少文件名字符串通过innode指向文件,当引用计数为0时文件才相当于删除文件

定位一个文件只有两种方式:

1.通过路径

2.直接找到目标文件的innode

 

想必你已看清这个号和目录的关系

 任何一个目录在刚开始新建的时候,引用计数一定是2

目录A内部新建一个目录,会让A目录的引用计数自动+1

一个目录内部有几个目录:A引用计数-2

构建Linux的路径结构,让我们可以使用.和..来进行路径定位

Linux系统中,不允许给目录建立硬链接

文件名是固定的,所有的系统指令在设定的时候可以知道.和..的作用

.和..都删不掉(如果你在目录内部是删除不了目录的)

《乌申克的救赎》

一般用硬链接做文件备份

打开的文件:内核、内存

没有被打开的文件和磁盘、文件系统有关

文本写入是语言的概念(缓冲区),二进制写入

int a = 1234567;        //二进制写入
1234567 -> "1234567"    //文本写入

谁转呢?

以文本方式写入?

 

C语言提供的函数转啊

 由库函数转

动态库和静态库

我们用过C、C++的标准库

strerror,strstr、STL...

 这是C标准库:

 

#include<iostream>
#include<string>

int main()
{
	std::string name = "hahaha";
	std::cout << name << std::endl;
	return 0;
}

 这是C++标准库

 Linux中.so是动态库,.a是静态库

动态库是系统公有的资源

绝大部分的指令都是用C写的

Windows动态库:.dll,.lib

游戏玩家有福啦

是什么

x.o和y.o和z.o。。。这种被称为可重定位目标文件,他们链接后就形成可执行程序

静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中,程序运行的时候将不再需要静态库

动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码

一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码,在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)

动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间

操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间

/add.h/
#ifndef __ADD_H__
#define __ADD_H__ 
int add(int a, int b);
#endif // __ADD_H__
/add.c/
#include "add.h"
int add(int a, int b)
{
	return a + b;
}

/sub.h/
#ifndef __SUB_H__
#define __SUB_H__ 
int sub(int a, int b);
#endif // __SUB_H__
/add.c/
#include "add.h"
int sub(int a, int b)
{
	return a - b;
}

///main.c
#include <stdio.h>
#include "add.h"
#include "sub.h"

int main(void)
{
	int a = 10;
	int b = 20;
	printf("add(10, 20)=%d\n", a, b, add(a, b));
	a = 100;
	b = 20;
	printf("sub(%d,%d)=%d\n", a, b, sub(a, b));
}

 一个小故事:

励志轩是一位学计算机的大学牲,

她有位舍友名为墨墨酱,她俩正在课设阶段,课设内容是完成库的编写

励志轩听完老师的要求直呼稳勒稳勒

墨墨酱找到励志轩,说她不会能不能帮帮她

但是励志轩想的是,帮肯定是要帮的,但是源文件肯定不能一毛一样

于是励志轩采取了一个措施:

gcc -c mystdio.c

 最终把这个编译完后的mystdio.o给了墨墨酱

励志轩打的算盘是,反正老师验收的时候是直接让跑代码,这样太容易蒙混过关了

函数方法的声明都放到头文件中

于是墨墨酱只需要编一个test.c测试一下运行就好勒:

#include"mymath.h"
#include"mystdio.h"
#include<stdio.h>
#include<string.h>

int main()
{
	int a = 10;
	int b = 20;
	printf("%d+%d=%d\n", a, b, myAdd(a, b));

	myFILE* fp = my_fopen("./myfile.txt", "w");
	if (fp == NULL)
	{
		return 1;
	}

	const char* message = "要不要出去玩...\n";
	my_fwrite(fp, message, strlen(message));

	my_fclose(fp);
	return 0;
}

什么?你说接口怎么实现的?那不关我的事啊宝

我只需要

gcc test.o mymath.o mystdio.o -o myexe

然后运行就好勒

游戏界面请认真辨别: 

 

 

言归正传: 

 

从这个样子再变成这样:

 

 

 头文件是一个手册,提供函数的声明,告诉用户怎么用

于是墨墨酱顺利蒙混过关咯

老师又发布了新指令,《关于我的老师让我大一就提供一百个源文件这件事》

于是励志轩还是老样子,把.o给了墨墨酱

但是墨墨酱拷丢了一两个,怎么编译都过不去就很难受

然后励志轩就干脆把那一百多个文件打包准备给墨墨酱传过去

紧接着就传来一声

“且慢!”

“我不会解压”

“啊?”

好吧。。。那只能献出这一招:

ar -rc libmyc.a *.o

 这是把源文件全打包,直接用就好,不需要解包

只不过在编译的时候需要把它也带上ww:

gcc main.c libmyc.a

 所谓的库文件就是把所有的.o打包

为什么

那为什么要有这个捏?

为了爱与和平

及提高开发效率

ar是gnu归档工具,rc表示replace and create(存在就替换,不存在就打包)

库的名字去掉点后缀只有myc

但是不仅墨墨酱想用啊,暖暖,燃燃子...励志轩的很多亲友都想用

于是励志轩拉了个群,把自己打包好的.tgz放到群文件里

谁想用谁用吧

要把库安装到系统中:把自己的头文件拷贝到系统头文件的搜索路径下

把源文件拷贝到系统源文件的搜索路径下

高攀上了:

 但是安装后编译又会发现编不过去

就是因为,,,编译器默认只认识C、C++的库

第三方提供的库编译器是不ins的

那怎么介绍它们认识啊?

gcc test.c -llibmyc.a

还是编不过啊

那怎么办啊

我要的是真名,要这样:

gcc test.c -lmyc

 但是还是不建议非官方的安装到库里(太矬了)

库的安装和卸载都是改变系统指定的库

如果不安装还想在当前目录下用该怎么做呢??

可以这样告诉gcc搜索头文件的额外路径:

gcc test.c -I ./mylib/include/

 但是还有对应的源文件怎么办捏?

这样就是顺便找一下库:

gcc main.c -I ./mylib/include/ -L ./mylib/lib

 我都指定路径了能不能放过我

你指定的目录很多库啊,链接哪个库?

gcc test.c -I ./mylib/include/ -L ./mylib/lib -lmyc

 -I:指定自定义头文件路径(这是i大写)

-L:指定用户自定义库文件路径

-l:指定执行的第三方库(这是l)

头文件制定了哦,在源代码里面就指定了呀,如果想要编译的时候不指定头文件路径,那在源码里做改动也是可以的:

#include"mylib/include/mymath.h"
#include"mylib/include/mystdio.h"
#include<stdio.h>
#include<string.h>

int main()
{
	int a = 10;
	int b = 20;
	printf("%d+%d=%d\n", a, b, myAdd(a, b));

	myFILE* fp = my_fopen("./myfile.txt", "w");
	if (fp == NULL)
	{
		return 1;
	}

	const char* message = "要不要出去玩...\n";
	my_fwrite(fp, message, strlen(message));

	my_fclose(fp);
	return 0;
}

 gcc在默认编译的时候是动态链接的

可是我不是已经指定了相应的库吗?为何ldd之后不会显示出我指定的库呢?

一点都不酷

 

静态链接需要加上选项-static

但是-static是强制的静态链接,就是全部都需要是静态链接

而不加是有动态库就用,没有动态库的话静态库也能用

会拷贝到可执行程序里

编译期间告诉了gcc和g++所以编过去了,但是没告诉操作系统所以找不到,我们需要给动态库建立软链接,这样就都能找到咯

有个环境变量:

echo $LD_LIBRARY_PATH

 

这样配置环境变量以后就再不会重新加载咯 

 

也是为了碟醋包了盘饺子 

动态库要在程序运行的时候找到动态库加载并运行

静态库在编译期间已经在库中的代码拷贝到可执行程序的内部了,所以加载和库无关咯

综上就是:

1.安装的系统

2.建立软链接

3.命令行导入环境变量

4.修改.bashrc配置文件,让环境变量永久生效

还有个方法:

ls /etc/ld.so.conf.d

 

可以把要用的库的链接放到这个配置文件下 

需要创建一个文件,后缀不能变

touch happy.conf

 动态库的绝对路径贴进去

就完事勒

这是第五种方法,任您选择~

-static的意义是强制进行静态链接,要求我们链接的任何库都必须提供对应的静态库版本

怎么办

怎么生成静态库捏?

ar -rc libmymath.a add.o sub.o 

 ar是gnu归档工具,rc表示(replace and create)

这是查看静态库的目录列表:

ar -tv libmymath.a 

t:列出静态库中的文件                 v:verbose 详细信息  

这是指定:

gcc main.c -L. -lmymath

 -L 指定库路径 -l 指定库名 测试目标文件生成后,静态库删掉,程序照样可以运行

库的搜索路径:

从左到右搜索-L指定的目录

由环境变量指定的目录 (LIBRARY_PATH)

由系统指定的目录:

/usr/lib

/usr/local/lib

怎样生成动态库捏? 

这是gcc、g++,生成共享库格式:

gcc -shared *.o -o libmyc.so

所谓的库文件,本质就是把.o打包成-FPIC

产生位置无关码:

gcc -fPIC -c mystdio.c

 如果我们使用别人的库

别人会给我们提供一批头文件+一批库文件(.so,.a)

使用动态库的编译选项:

l:链接动态库,只要库名即可(去掉lib以及版本号)

L:链接库所在的路径.

 系统有很多的库,他们通常由一组相互关联的用来完成某项常见工作的函数构成(比如用来处理屏幕显示情况的函数:ncurses库):

yum install -y ncurses

nothing to do:

 再装个开发环境:

yum install -y ncurses-devel

 

耶耶:

可以借助这个库写个窗口小程序:

#include <ncurses.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

#define DELAY 100000

int main() {
    int x, y, maxX, maxY; //蛇头的位置和终端窗口的大小
    int direction = KEY_RIGHT; //方向
    int snakeLength = 5; //蛇的长度
    int snakeX[100], snakeY[100]; //蛇身的位置
    int foodX, foodY; //食物的位置
    int score = 0; //得分
    int gameOver = 0; //游戏结束标志

    // 初始化ncurses库
    initscr();
    noecho();
    curs_set(0);
    keypad(stdscr, TRUE);
    timeout(0);

    // 获取终端窗口的大小
    getmaxyx(stdscr, maxY, maxX);

    // 初始化蛇的初始位置和长度
    x = maxX / 2;
    y = maxY / 2;
    for (int i = 0; i < snakeLength; i++) {
        snakeX[i] = x - i;
        snakeY[i] = y;
    }

    // 生成食物的初始位置
    srand(time(NULL));
    foodX = rand() % maxX;
    foodY = rand() % maxY;

    // 游戏循环
    while (!gameOver) {
        clear();

        // 绘制蛇
        for (int i = 0; i < snakeLength; i++) {
            mvprintw(snakeY[i], snakeX[i], "O");
        }

        // 绘制食物
        mvprintw(foodY, foodX, "*");

        // 显示分数
        mvprintw(0, 0, "Score: %d", score);

        // 移动蛇的位置
        int nextX = snakeX[0];
        int nextY = snakeY[0];
        switch (direction) {
            case KEY_UP:
                nextY--;
                break;
            case KEY_DOWN:
                nextY++;
                break;
            case KEY_LEFT:
                nextX--;
                break;
            case KEY_RIGHT:
                nextX++;
                break;
        }

        // 检查是否吃到食物
        if (nextX == foodX && nextY == foodY) {
            score++;
            snakeLength++;
            foodX = rand() % maxX;
            foodY = rand() % maxY;
        } 
        
        // 移动蛇的身体
        for (int i = snakeLength - 1; i > 0; i--) { //后一节移动到前一节的位置
             snakeX[i] = snakeX[i - 1];
             snakeY[i] = snakeY[i - 1];
        }
        

        // 更新蛇头位置
        snakeX[0] = nextX;
        snakeY[0] = nextY;

        // 检查游戏结束条件
        //检查是否越界
        if (nextX < 0 || nextX >= maxX || nextY < 0 || nextY >= maxY) {
            gameOver = 1;
        }
        //检查是否撞到自己的身体
        for (int i = 1; i < snakeLength; i++) {
            if (snakeX[i] == nextX && snakeY[i] == nextY) {
                gameOver = 1;
            }
        }

        // 刷新屏幕
        refresh();

        // 延迟一段时间
        usleep(DELAY);
        
        // 获取用户输入
        int key = getch();
        switch (key) {
            case KEY_UP:
            case KEY_DOWN:
            case KEY_LEFT:
            case KEY_RIGHT:
                direction = key;
                break;
            case 'q':
                gameOver = 1;
                break;
        }
    }

    // 清理并退出ncurses库
    endwin();

    printf("Game Over! Your score: %d\n", score);

    return 0;
}

一个之前就遇到的报错,虽然我可能还是不知道为什么?

可能加上gnu之后支持的语法变多了?

gcc -Wall calc.c -o calc -lm

 -lm表示要链接libm.so或者libm.a库文件

可执行程序和地址空间

访问动静态库本质也是访问文件

动态库加载和静态库无关

动态库本质是文件,存在磁盘上

加载到内存后要通过页表映射到对应的mm_struct中的共享区

所以可以访问共享区的代码(使用动态库)

 

动态库不一定只用于一个进程

不需要再加载到系统当中

动态库在加载之后,要映射到当前进程的堆栈之间的共享区

我们的可执行程序,编译成功,没有加载运行,那二进制代码中有地址吗?

#include<stdio.h>

int Sum(int top)
{
	int i = 1;
	int ret = 0;
	for (; i <= top; i++)
	{
		ret += i;
	}
	return ret;
}

int main()
{
	int top = 100;
	int result = Sum(top);

	printf("result:%d\n", result);
	return 0;
}

写一份代码就知道嘞

 转成反汇编:

objdump -S code > code.s

我们形成的可执行程序里面有地址

代码在编译完之后都有对应的地址

所以直接看源代码不用加载运行,可以在大脑中运行程序

在Linux中形成的可执行程序是ELF格式的

二进制有对应的固定格式

elf为可执行程序的头部,涵盖可执行程序的属性

 可执行车行徐编译后悔形成很多汇编语句,每条汇编语句会有对应的地址

如何对每条语句进行编址捏?

就是0000000.....FFFFFFFF(平坦模式)

我们认为这是虚拟地址

地址有对应的种类

相对编址=地址+偏移量

ELF+加载器就称为各个区域的起始和结束地址,main函数的入口地址

进程=内核数据结构+代码和数据

进程在创建时是先形成内核数据结构,再加载代码和数据

创建地址空间

mm_struct是结构体对象,有成员变量

在进行初始化的时候

初始值从哪里来捏?

从可执行程序来捏

虚拟地址空间是一套设计标准,OS,编译器,加载器都要支持

代码也是数据,加载到内存中要有自己的物理地址

故事从main函数入口地址开始

CPU内存在寄存器pc指针,pc指针保存的是正在执行的下一指令的地址(虚拟地址):pc指向哪里,CPU就去执行哪里的代码

虚拟地址在左侧,物理地址在右侧,构建相对的映射

永远不会凉的群,全是应声虫:

CPU开始运行咯

pc指针告诉CPU(指哪打哪)

call指令里面的也是虚拟地址

库的编址和可执行程序的方式也差不多

objdump -S libmyc.so > test.s

 

 库被映射到了什么位置捏?

一点都不影响哎

库原则上加载到堆上的任意位置都能工作

与地址无关码

只要映射完,虚拟地址随便变

改的只是虚拟地址,拿到偏移量还是能访问,来回跳转实现函数调用

库函数调用也是在地址空间内来回跳转

在操作系统中同时存在非常多的库

OS可以让进程知道库有没被加载

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

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

相关文章

免费【2024】springboot 高校毕业生离校管理系统的设计与实现

博主介绍&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围&#xff1a;SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化…

BIMRender渲染器插件上线 |一款免费的模型实时渲染插件

说到模型渲染和渲染软件 我猜你肯定遇到过下面这些问题&#xff1a; ● 投入产出比低&#xff1a;项目汇报需要高质量的渲染效果图&#xff0c;又不希望在低使用频率的渲染软件投入时间和高昂的成本&#xff0c;专门安装它们似乎并不划算&#xff1b; ● 操作复杂&#xff1a;…

湖北建筑特种作业人员“秘籍”:取证、延期、注销全攻略

湖北建筑特种作业人员“秘籍”&#xff1a;取证、延期、注销全攻略 湖北建筑施工特种作业人员 特种作业人员考核取证、继续延期、变更注销等全部事项都已下放到各市州主管部门。也就是说在湖北省报考建筑电工、焊工、架子工、信号工、起重机械司机、施工升降机等可以就近选择。…

2024高中生必备物品有哪些?学生党速看这五件好物

新的一年开学季又来临了&#xff0c;许多同学还在犹豫要为开学准备哪些物品呢&#xff1f;今天小编整理了五件高中生必备物品&#xff0c;学生党请收下这份清单&#xff0c;从学习用品到生活用品、从智能产品到健康防护&#xff0c;这些小物都在学习生活中发挥着重要作用&#…

Java程序设计:Java 网络聊天室服务器端

网络编程相关内容见上一篇:Java程序设计&#xff1a;Java网络编程实验 目录 1 实验名称 2 实验目的 3 实验源代码 4 实验运行结果图 5 总结 1 实验名称 Java 网络聊天室服务器端 2 实验目的 继续熟练掌握在eclipse中调试代码 掌握Java面向对象思想掌握多线程在该项目中的…

数据加密-AES数据加密及C#实现

引言 AES&#xff08;Advanced Encryption Standard&#xff09;是一种广泛使用的对称密钥加密算法&#xff0c;由美国国家标准与技术研究院&#xff08;NIST&#xff09;于2001年发布。AES以其高效、安全的特点&#xff0c;在数据加密领域占据了重要地位。 using System; us…

XGP怎么免费玩暗黑4 暗黑4XGP免费白嫖教程

暗黑四新赛季已经开启了&#xff0c;全新赛季一定能给你带来完全不一样的感受&#xff0c;在新赛季中&#xff0c;恶魔入侵&#xff0c;炼狱大军来袭&#xff0c;玩家在完成世界阶级三任务线之后&#xff0c;就可以参与炼狱大军的战斗&#xff0c;怪物会一波一波的形式攻击&…

html+css+js网页制作 船票网1个订单页面带js 有增加和删除功能

htmlcssjs网页制作 船票网1个订单页面带js 有增加和删除功能 网页作品代码简单&#xff0c;可使用任意HTML编辑软件&#xff08;如&#xff1a;Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作&#xff09;。…

一个在国内好用且免费的AI网站,可以让自己的学习或者工作方面,事半功倍!它就是codemoss_能用AI

一、开头放重点 &#x1f525;世界主流大模型集聚地 免魔法 白玩GPT4 AI智能工作流 Codemoss_能用AI 传送门&#xff1a;https://www.nyai.chat/chat?invitenyai_1141439 还是那句话&#xff0c;一个好用的AI工具&#xff0c;可以在学习或者工作上&#xff0c;事半功倍。…

如何建立强大的谷歌外链网络?

​要建立一个强大的谷歌外链网络&#xff0c;要理清楚一个观念&#xff0c;质量和数量同等重要&#xff0c;没有垃圾的&#xff0c;质量低下的外链&#xff0c;只有有效跟无效的外链&#xff0c;在弄清楚这一点的前提下&#xff0c;你要知道&#xff0c;一个网站&#xff0c;尤…

在Fiddler中的Composer使用post方法发送非法数据

1、如图所示&#xff0c;先通过fiddler抓取接口 2、抓取接口后复制url 3、在fillder中切换到Composer界面&#xff0c;复制的url粘贴到接口输入框中&#xff0c;并将请求类型改为POST. 4、点击该接口&#xff0c;切换到Inspectors-Raw下 复制请求头以及请求参数 5、将请求头复制…

windows端口转发

使用场景 前提条件&#xff1a; 双网段网络环境&#xff08;A网段、B网段&#xff09;跳板机一台&#xff0c;或者称为目标服务器一台。 目标&#xff1a; &#xff08;在A网段&#xff09;访问这台服务器的IP端口(例如10.143.21.62:10000)&#xff0c;提供服务的是B网段的服务…

【HarmonyOS NEXT星河版开发学习】界面开发实战b-支付宝界面

个人主页→VON 收录专栏→鸿蒙开发小型案例总结​​​​​ 基础语法部分会发布于github 和 gitee上面&#xff08;暂未发布&#xff09; 难度系数&#xff1a; 目录 前言 知识点概述 Scroll组件 基本用法 注意事项 层叠布局 层叠布局的特性 层叠布局的应用场景 Stack组…

windows10搭建maxkb开发环境(劝退指南)

windows搭建maxkb开发环境踩坑记录&#xff0c;先说结论吧&#xff0c;windows上面搞不下去了&#xff0c;直接在ubuntu等linux环境搭建吧&#xff0c;别浪费时间了。 1.maxKB开发环境搭建 官方文档&#xff1a;https://maxkb.cn/docs/dev_manual/dev_environment/ 2.各种坑 …

C++知识点总结:5.多态和虚函数(自用)

多态和虚函数 1. 多态和虚函数2. 引用形式的多态3. 虚函数注意事项4. 构成多态的条件5. 为什么构造函数不能是虚函数6. 虚析构函数的必要性7. 纯虚函数8. 抽象类9. 虚函数表10. typeid运算符&#xff1a;获取类型信息11. RTTI机制&#xff08;C运行时类型识别机制&#xff09;1…

加速开发利器:代码生成器如何快速生成后端接口?

最新技术资源&#xff08;建议收藏&#xff09; https://www.grapecity.com.cn/resources/ 前言 在现代软件开发中&#xff0c;重复性的增删改查逻辑代码的编写往往非常耗时且容易出错。为了提高开发效率&#xff0c;减少手动维护的成本&#xff0c;代码生成器就成为了一个非常…

MySQL常用的日期和时间函数

文章目录 概述日期和时间函数 概述 在 MySQL 中&#xff0c;有许多常用的日期和时间函数&#xff0c;可以帮助你处理和操作日期和时间字段。 日期和时间函数 获取当前日期和时间 NOW(): 返回当前的日期和时间。CURRENT_DATE() 或 CURDATE(): 返回当前的日期&#xff08;不包括…

3C产品手册制作7步骤:让消费者快速了解产品

引言 在这个信息爆炸的时代&#xff0c;如何让消费者在众多3C产品&#xff08;计算机、通讯、消费电子&#xff09;中快速了解并选择您的产品&#xff1f;一份精心制作的产品手册无疑是关键。它不仅是产品的“名片”&#xff0c;更是连接品牌与消费者的桥梁。接下来&#xff0…

2024年湖北省建筑施工特种作业人员证书延期申请/年审

2024年湖北省建筑施工特种作业人员证书延期申请/年审 建筑电工、建筑架子工、建筑起重机械司机、信号工、施工升降机等延期&#xff0c;要注意提前3个月内进行延期&#xff0c;2年1延期。 湖北特种作业考核管理系统跳转至延期申请申报页面&#xff0c;再点击“新增”按钮&…

Games101--shading 3

1.重心坐标 为什么要进行插值&#xff1f;&#xff08;因为有很多的计算实在三角形内部进行的&#xff0c;而我们需要形成一个平滑的过渡&#xff09; 插值需要插值什么内容&#xff1f;&#xff08;颜色&#xff0c;纹理映射&#xff0c;法线插值&#xff0c;可以对三角形的…