【C++】STL学习——vector模拟实现

news2024/11/18 1:36:52

目录

  • vector介绍
  • vector函数接口总览
  • 结构介绍
  • 默认成员函数
    • 构造函数1
    • 构造函数2
    • 构造函数3
    • 经典的深浅拷贝
    • 拷贝构造
    • 赋值重载
    • 析构函数
  • 迭代器
    • begin和end
  • 容量相关函数
    • size
    • capacity
    • empty
    • reserve
    • resize
  • 访问
    • operator[]
  • 修改相关函数
    • insert
    • push_back
    • erase
    • pop_back
    • clear
    • swap
  • 迭代器失效问题

vector介绍

vector是表示可变大小数组的序列容器,是STL中的容器之一;其底层结构为可扩容的数组,所以vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。vector与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。具体结构和使用方法可查阅相关文档进行了解vector学习参考文档

vector函数接口总览

namespace djs
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		//默认成员函数
		vector();                                           //默认构造函数
		vector(size_t n, const T& val=T());                 //构造函数,构造n个val
		
		template<class InputIterator>
		vector(InputIterator first, InputIterator last);    //迭代器区间构造函数

		vector(const vector<T>& v);                         //拷贝构造函数
		vector<T>& operator=(const vector<T>& v);           //赋值运算符重载函数
		~vector();                                          //析构函数

		//迭代器
		iterator begin();
		iterator end();
		const_iterator begin()const;
		const_iterator end()const;
		
		//容量相关
		size_t size() const;
		size_t capacity() const;
		bool empty() const;
		void reserve(size_t n);
		void resize(size_t n, const T& val = T())

		//访问相关
		T& operator[] (size_t n);
		const T& operator[] (size_t n) const;

		//修改相关
		iterator insert(iterator position, const T& val);
		 
		template <class InputIterator>
		void insert(iterator position, InputIterator first, InputIterator last);
		void push_back (const T& val);
		iterator erase (iterator position);
		void pop_back();
		void clear();
		void swap (vector& x);
		
private:
		iterator _start;//开始
		iterator _finish;//数据个数
		iterator _end_of_storage;//容量大小

	};
}

还是一样,为了不与库的vector冲突,将其放在自己的命名空间里。

结构介绍

vector作为可变大小数组的序列容器,可以存各种类型的数据;在C语言模拟实现顺序表这一数据结构时,我们可以利用typedef重命名数据类型达到一键替换所要存放的数据类型的效果,但是无法做到使用同一份代码同时存储两种数据结构的效果,而C++中的模板很好解决了这个问题,模板是 STL 的基础支撑。 模板介绍。
所以vector是一个类模板,可以存储指定类型的数据。我们参考SGI版的STL中vector的源码发现,vector是用三个指针来维护存储的数据的:

vector结构

成员

private:
		iterator _start;//开始
		iterator _finish;//数据个数
		iterator _end_of_storage;//容量大小

vector成员
注意:

  • _finish指向的是最后一个数据的下一位,如上图所示。

实现文件介绍:
上一篇string的模拟实现中,采用了声明定义分离的方式实现;但由于vector是类模板,模板是不建议声明定义分离实现的,容易导致链接问题。SGI版的STL中声明和定义也都是放在头文件中的。
头文件

所以此次vector的模拟实现使用两个文件实现
vector.h:函数的声明和实现。
test.cpp:测试各函数。

默认成员函数

对于一些不认识的类型可以对着文档的成员类型表查看其typedef前的具体类型。

const allocator_type& alloc = allocator_type()//空间配置器,直接忽略就好。

类型介绍

库里实现了三个构造,一个拷贝构造函数;三个构造分别为:

  1. 默认构造
  2. 构造n个值为val的构造函数
  3. 迭代器区间构造
    构造函数

构造函数1

默认构造不写使用编译器自动生成的也行,写的话如下:将三个指针置为nullptr即可。

	vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{}

构造函数2

构造n个值为val的对象。
思路也很简单,在堆区开辟一段空间,然后将val存放进去。需要注意的是参数中const T& val = T()的使用,具体请到类和对象篇的匿名对象查看;使用匿名对象是因为不知道传入的值val的T具体是什么类型,所以使用内置构造将其置为0(T为int时)

		vector(size_t n, const T& val = T())//类型转化int -> size_t
		{
			size_t endofstorage = n * 2;
			_start = new T[endofstorage];

			for (int i = 0; i < n; i++)
			{
				_start[i] = val;
			}
			_finish = _start + n;//更新位置
			_end_of_storage = _start + endofstorage;//更新容量
		}

构造函数3

迭代器区间构造:正如其名,该构造函数可以用一段区间来构造。该区间范围遵从迭代器使用的范围[左闭右开),如:使用一个数组来构造。

    int arr[] = { 1,2,3,4,5,6,7,8,9 };
	vector<int>v2(arr, arr+sizeof(arr) / sizeof(int));

在类中也还是可以继续使用模板的,迭代器区间构造就是一个函数模板;实现起来也简单,先计算好构造的区间大小,再去堆区开辟对应的大小,用_finish来将元素添加进去,最后更新一下容量_end_of_storage。

		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			size_t endofstorage = last - first;
			_start = new T[endofstorage];
			_finish = _start;//让_finish为尾,一直往后走
			while (first != last)
			{
				*_finish = *first;
				_finish++;
				first++;//更新位置
			}
			_end_of_storage = _start + endofstorage;//更新容量

		}

需要注意的是,由于构造函数2与3的存在,需要再添加一个第一个参数为int类型的构造函数;因为当我们想要用构造函数2,构造n个值为val的对象时(如下面v3)而构造函数2的参数为size_t类型,编译器会自动发生类型转化;但是有了迭代器区间后,v3的构造会更加匹配迭代器区间构造,对int解引用,导致非法的间接寻址;
非法寻址
解决办法就是添加一个类型更为匹配的int类型的构造函数

		//由于有迭代器区间构造,构造(10,6)这种就会匹配到区间构造,造成非法寻址
		vector(int n, const T& val = T())//加个整型的构造
		{
			size_t endofstorage = n * 2;
			_start = new T[endofstorage];
			for (int i = 0; i < n; i++)
			{
				_start[i] = val;
				//push_back(val);
			}
			_finish = _start + n;//更新位置
			_end_of_storage = _start + endofstorage;//更新容量
		}

经典的深浅拷贝

拷贝构造始终都是那一套,但这里的关键点还是之前提过的深浅拷贝问题;实现拷贝构造的时候可能会贪方便,使用memcpy这个函数来实现,对于类中不涉及资源管理的使用memcpy进行拷贝那是没问题的,但是对于涉及资源管理的类再去使memcpy那就会出大麻烦,原因就是memcpy的拷贝是值拷贝,只会无脑的将内容ctrl c,ctrl v,也就是浅拷贝;而我们不写,使用编译器的拷贝构造也是浅拷贝,效果是一样的,接下来再次深入探讨深浅拷贝的问题。

使用编译器默认生成的拷贝构造就会引发以下问题,根本原因就是对同一块空间进行重复的释放,对一个非空指针调用delete[]两次(或更多次),那么程序将表现出未定义行为,这通常会导致程序崩溃或数据损坏。

  • 对空指针释放是不会有问题的,但是对一个非空指针调用delete[]两次或更多就会出错。
    值拷贝问题
    通过监视窗口看到v2完完全全就是v1的副本

值拷贝
当对v1进行释放后,v2还没释放,但是v2的内容已经被释放了,因为v2是v1是副本,那么此时再对v2析构,就对造成对已释放的空间再次释放,导致程序崩溃。
值拷贝2
浅拷贝如下图,v1,v2指向同一块内容。析构时导致崩溃。
浅拷贝示意图

深拷贝则为每个对象都有自己独立的一份数据。
深拷贝示意图
总结:
凡是涉及了资源管理的类,其拷贝构造以及赋值重载一定要进行深拷贝

拷贝构造

进行深拷贝,将数据一个一个喂进去。

		vector(const vector<T>& v)
		{
			size_t endofstorage = v._end_of_storage - v._start;
			_start = new T[endofstorage];
			int i;//写外面
			for (i=0; v._start + i < v._finish; i++)
			{
				_start[i] = v._start[i];//运用了赋值重载
			}
			_finish = _start + i;
			_end_of_storage = _start + endofstorage;
		}

也有更简洁的写法,使用等会要实现的reserve开好空间,再利用范围for将数据尾插进去。

		//现代写法
		vector(const vector<T>& v)
		{
			reserve(v.capacity()); //调用reserve函数将容器容量设置为与v相同
			for (auto e : v) //将容器v当中的数据一个个尾插过来
			{
				push_back(e);
			}
		}

赋值重载

拷贝构造将数据一个一个插进去时就用到了赋值重载,所以赋值重载也需要进行深拷贝;开后空间将数据插入后别忘记对原有空间进行释放,最后再把_start,_finish,_end_of_storage更新。

		vector<T>& operator=(const vector<T>& v)
		{
			//赋值重载是对已存在对象而言,记得确保数据拷贝完成再释放原有空间
			if (this != &v)//避免给自己赋值
			{
				size_t endofstorage = v._end_of_storage - v._start;
				T* tmp = new T[endofstorage];//临时数组接收
				int i;
				for (i = 0; v._start+i < v._finish; i++)
				{
					tmp[i] = v._start[i];
				}
				if (_start)//判断一下
				{
					delete[] _start;//释放空指针不会出错,但还是要注意规范。
				}
				_start = tmp;
				_finish = _start + i;
				_end_of_storage = _start + endofstorage;
				return *this;
			}
		}

析构函数

释放掉由_start指向的空间即可。

		~vector()
		{
			if (_start)
			{
				delete[] _start;
				_start = _finish = _end_of_storage = nullptr;
			}
		}

迭代器

SGI版下的vector的迭代器还是原生指针,所以将模板类型T* typedef为iterator及const版。

  • 注意:VS(PJ版)下vector的迭代器不是原生指针
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

begin和end

vector当中的begin函数返回容器的首地址,end函数返回容器当中有效数据的下一个数据的地址。

        iterator begin()
		{
			return _start;
		}
		
		iterator end()
		{
			return _finish;
		}

还需要重载一对适用于const对象的begin和end函数,使得const对象调用begin和end函数时所得到的迭代器只能对数据进行读操作,而不能进行修改。

	    const_iterator begin()const
		{
			return _start;
		}

		const_iterator end()const
		{
			return _finish;
		}

有了迭代器,范围for也就支持了。

	int arr[] = { 1,2,3,4,5,6,7,8,9 };
	vector<int> v1(arr, arr + sizeof(arr) / sizeof(int));
	for (auto e : v1)
	{
		cout << e;
	}
	cout << endl;

容量相关函数

容量相关

size

获取数据个数:指针_finish 和_start相减便是数据个数

	    size_t size() const
		{
			return _finish - _start;
		}

capacity

获取容量:指针_end_of_storage , _start相减为容量。

		size_t capacity() const
		{
			return _end_of_storage - _start;
		}

empty

判断是否为空:检查_start与_finish是否相等。

		bool empty() const
		{
			return _start == _finish;
		}

reserve

功能:

  1. 当n大于对象当前的capacity时,将capacity扩大到n或大于n。
  2. 当n小于对象当前的capacity时,不做响应。

这里需要注意的是我们是用三个指针来维护数据的,一旦开辟了新的空间并把原有数据拷贝过去后,三个指针那就都失效了,必须更新指针的位置;扩容前需要记录_finish与_start的间距,有了这间距等会才能恢复_finish的位置。

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t sz = size();//先记录数据与起始位置的间隔
				T* tmp = new T[n];
				if (_start)//需要检查,空则不需要拷贝原有数据
				{
					for (size_t i = 0; i < sz; ++i)
					{
						tmp[i] = _start[i];
					}
					delete[] _start;
				}
                //更新位置
				_start = tmp;
				_finish = _start + sz;//恢复位置
				_end_of_storage = _start + n;
			}
		}

resize

功能:

  1. 当n大于当前的size时,将size扩大到n,扩大的数据为val,若val未给出,则默认为容器所存储类型的默认构造函数所构造出来的值。
  2. 当n小于当前的size时,将size缩小到n。

首先检查是否需要扩容,之后利用一个循环比较n与sz的关系,若n大于sz则插入数据,否则不做处理。最后重新定位一下_finish的位置。

		void resize(size_t n, const T& val = T())//库的不是引用,而是传值
		{
			if (n > capacity())
			{
				reserve(n);
			}
			size_t sz = size();//当前个数

			while (n > sz)//小了不处理,大了加
			{
				*_finish = val;
				_finish++;
				sz++;//进入循环说明n>sz,结束条件则为n==sz
			}
			//重新定位(也处理小的情况)
			_finish = _start + n;
		}

访问

operator[]

vector的底层还是数组,支持下标访问:_start维护的是整段数据,支持下标访问,所以直接返回对应的数据即可;下标访问是可以修改内容的,采用引用返回。

		T& operator[] (size_t n)//下标
		{
			return _start[n];
		}

重载运算符[ ]时需要重载一个适用于const容器的,因为const容器通过“下标+[ ]”获取到的数据只允许进行读操作,不能对数据进行修改。

        const T& operator[] (size_t n) const
		{
			return _start[n];
		}

修改相关函数

insert

库里有三个insert的重载,我们实现第一,三个

insert
一:
insert函数可以在所给迭代器position位置插入数据,在插入数据前先判断是否需要增容,然后将position位置及其之后的数据统一向后挪动一位,以留出position位置进行插入,最后将数据插入到position位置并返回position的位置。

  • 需要注意的是position是迭代器类型,一旦进行扩容,就需要重新定位position在新区间的位置,所以在扩容前记录position与_start的间距。
  • 其返回值为返回插入位置的迭代器,也就是position。

		iterator insert(iterator position, const T& val)
		{
			assert(position >= _start);
			assert(position <= _finish);
			if (_finish + 1 > _end_of_storage)//检查是否需要扩容
			{
				size_t gap = position - _start;
				reserve(capacity()==0?4:capacity() * 2);//防止capacity为0的情况
				position = _start + gap;
			}

			iterator end = _finish;//T*end
			while (end > position)//end==pos时不需要移
			{
				*end = *(end - 1);
				end--;
			}
			*end = val;//插入
			_finish++;
			return position;
		}

二:
插入一段迭代器区间:用一个循环其中调用第一个insert将数据添加进去。

  • 使用insert后注意迭代器失效问题,这里用重新接受其返回值解决。
	    template <class InputIterator>
		void insert(iterator position, InputIterator first, InputIterator last)
		{
			assert(position >= _start);
			assert(position <= _finish);
			while (first != last)
			{
				position = insert(position, *first);//注意迭代器失效问题
				position++;
				first++;
			}
		}

push_back

尾插一个元素:调用insert完成。

	    void push_back(const T& val)
		{
			insert(_finish, val);
		}

erase

删除传入迭代器位置的元素:从position位置开始从后往前覆盖。返回值为:返回删除元素后一位,但已覆盖,实际还是position

		iterator erase(iterator position)
		{
			assert(position >= _start);
			assert(position <= _finish);
			assert(!empty());
			iterator end = position;
			while (end<_finish)
			{
				*end = *(end + 1);
				end++;
			}
			_finish--;
			return position;//返回删除元素后一位,但已覆盖,实际还是position
		}

pop_back

尾删:调用erase。

		void pop_back()
		{
			erase(_finish - 1);
		}

clear

清空数据但容量不改:让_finish=_start即可。

	    void clear()
		{
			_finish = _start;//只是禁止了迭代器访问的方式,数据还在
		}

swap

交换两个vector:由于swap的重载非常多,可以使用交换单一变量的swap,逐个指针交换。也可以直接使用交换vector的swap(非成员函数)。

        void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_end_of_storage, v._end_of_storage);

			//std::swap(*this,v);
		}

迭代器失效问题

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了
封装(PJJ版),比如:vector的迭代器(SGI版)就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。

对于vector可能会导致其迭代器失效的操作有
问题一:会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、
push_back等。

如:使用reserve
SGI版的vector的reserve虽然没有报错,但是可以看到其行为是未定义或者说越界访问了。
迭代器失效
而PJ版(VS)的vector的reserve则是崩溃了。
迭代器失效
出错原因:使用reserve有可能会导致vector扩容,也就是说vector底层原理旧空间被释放掉,而在打印时,it还使用的是释放之间的旧空间,在对it迭代器操作时,实际操作的是一块已经被释放的空间,而引起代码运行时崩溃。

解决办法也简单:使用前重新赋值获取迭代器位置。
在这里插入图片描述

问题二:指定位置元素的删除操作——erase
程序没有崩溃,但是可以看到此时的it2已经不是指向1了,所以其结果也还是错的。
SGI
VS的直接崩溃
迭代器失效
erase删除it2位置元素后,it2位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效,但是:如果it2刚好是最后一个元素,删完之后it2刚好是end的位置,而end位置是没有元素的,那么it2就失效了。因此删除vector中任意位置上元素时,VS就认为该位置迭代器失效了,不能再使用了。

同样的,该问题也能通过重新赋值解决。
迭代器失效
再如以下代码
删除容器中所有的偶数。

	int arr[] = { 1,2,3,4,5,6 };
	std::vector<int>v2(arr, arr+sizeof(arr)/sizeof(int));
	auto it2 = v2.begin();
	v2.erase(it2);
	while (it2 != v2.end())
	{
		if (*it2 % 2 == 0)
		{
			v2.erase(it2);
		}	
		else
		{
			++it2;
		}
	}

迭代器失效
此时可以看到VS处理得很极端,直接崩溃了。

而解决办法是接受其返回值,重新获取迭代器位置。

处理迭代器失效
总结:
迭代器失效解决方法:使用迭代器时,永远记住一句话:每次使用前,对迭代器进行重新赋值。

PJ版的与SGI版的迭代器由于实现不同,导致他们的处理方式也不一样。

  • Linux下(SGI版),g++编译器对迭代器失效的检测并不是非常严格,处理也没有VS(PJ版)下极端。
  • SGI版的STL中,迭代器失效后,代码并不一定会崩溃,但是运行结果肯定不对,如果it不在begin和end范围内,肯定会崩溃的。

string也会有以上问题,但是我们一般不使用迭代器访问。

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

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

相关文章

TPM在解决哪些类型的问题时最有效?

在探讨TPM&#xff08;Total Productive Maintenance&#xff0c;全面生产维护&#xff09;在解决哪些类型问题时最为有效时&#xff0c;我们首先需要明确TPM的核心原则和目标。TPM作为一种综合性的设备管理和维护体系&#xff0c;旨在通过全员参与、全系统、全效率的方式&…

【计算机网络】socket编程 --- 实现简易TCP网络程序

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 如果文章对…

使用 nuxi generate 进行预渲染和部署

title: 使用 nuxi generate 进行预渲染和部署 date: 2024/9/4 updated: 2024/9/4 author: cmdragon excerpt: 通过 nuxi generate 命令,你可以轻松地将 Nuxt 应用程序预渲染为静态 HTML 文件,并将其部署到任何静态托管服务。这种方法可以提高应用程序的性能和安全性,特别…

科学计算基础软件包Numpy介绍及常用法

1.介绍及说明 NumPy 是一个开源的 Python 库&#xff0c;专门用于科学计算和数值处理。它提供了强大的多维数组对象和丰富的函数库&#xff0c;支持高效的数组运算。NumPy 是许多其他科学计算库&#xff08;如 SciPy、Pandas、Matplotlib 等&#xff09;的基础。以下是对 NumPy…

【开源大模型生态4】大模型和安卓时刻

开源大模型&#xff0c;指基于开源软件模式&#xff0c;由全球开发者共同参与、共同维护、共同发展的机器学习模型。 我们之前有过关于开源大模型和对应开源协议的探讨&#xff1a; 【AI】马斯克说大模型要开源&#xff0c;我们缺的是源代码&#xff1f;&#xff08;附一图看…

‌智慧公厕:城市文明的智慧新篇章‌@卓振思众

在日新月异的城市化进程中&#xff0c;公共设施的智能化升级已成为不可逆转的趋势。其中&#xff0c;智慧公厕作为城市智慧化建设的重要组成部分&#xff0c;正悄然改变着我们的生活。智慧公厕&#xff0c;这一融合了物联网、大数据、云计算等现代信息技术的创新产物&#xff0…

数学建模常见模型(下)

目录 神经网络法详细介绍 1. 引言 2. 神经网络的基本概念 2.1 神经元 2.2 层次结构 2.3 激活函数 3. 神经网络的工作原理 3.1 前向传播 3.2 反向传播 4. 神经网络的类型 4.1 前馈神经网络&#xff08;Feedforward Neural Networks, FNN&#xff09; 4.2 卷积神经网…

云计算之存储

目录 一、产品介绍 1.1 对象存储oss 1.2 特点 二、产品技术背景 三、产品架构及功能 四、常见问题及排查思路 4.1 两个bucket目录文件如何快速复制&#xff1f; 4.2 oss里的目录如何删除&#xff1f; 4.3 能否统计oss一个目录的大小 4.4 异常诊断 - 上传下载速度慢 4…

开源项目|聚合支付工具,封装了某宝、某东、某银、PayPal等常用的支付方式

前言 IJPay是一款开源的支付SDK&#xff0c;它集成了微支付、某宝支付、银联支付等多种支付方式&#xff0c;为开发者提供了一种简单、高效的方式来处理支付问题。以下是IJPay的一些主要特点&#xff1a; 支持多种支付方式&#xff1a;IJPay支持微信支付、支付宝支付、银联支付…

ffmpeg命令(详解)

欢迎诸位来阅读在下的博文~ 在这里&#xff0c;在下会不定期发表一些浅薄的知识和经验&#xff0c;望诸位能与在下多多交流&#xff0c;共同努力 文章目录 一、常见命令二、实战三、总结 一、常见命令 ffmpeg -i input.mp4 -c copy output.mp4解释&#xff1a;-i 后面接输入文…

应用在蓝牙耳机中的低功耗DSP音频处理芯片-DU561

在当今社会&#xff0c;随着科技的不断发展&#xff0c;人们对于电子产品的需求也在日益增长。蓝牙耳机就是将蓝牙技术应用在免持耳机上&#xff0c;让使用者可以免除恼人电线的牵绊&#xff0c;自在地以各种方式轻松通话。自从蓝牙耳机问世以来&#xff0c;一直是行动商务族提…

【递归、回溯专题(二)】DFS解决floodfill算法

文章目录 1. 图像渲染2. 岛屿数量3. 岛屿的最大面积4. 被围绕的区域5. 太平洋大西洋水流问题6. 扫雷游戏7. 机器人的运动范围 1. 图像渲染 算法原理&#xff1a; 这题不需要创建visit数组去记录使用过的节点&#xff0c;因为我每次dfs都尝试修改image数组的值&#xff0c;当下…

[Linux]:权限

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;Linux学习 贝蒂的主页&#xff1a;Betty’s blog 1. Linux权限的基本概念 1.1 root与普通用户 在Linux系统中&#xff0c;存在…

内部知识库:企业智慧资产的安全守护者

引言 在知识经济时代&#xff0c;企业的核心竞争力越来越依赖于其知识资源的积累、管理和利用。内部知识库&#xff0c;作为企业知识管理的重要组成部分&#xff0c;扮演着智慧资产守护者的关键角色。它不仅承载着企业多年来的经验积累、技术创新和业务流程知识&#xff0c;还…

2024年“羊城杯”粤港澳大湾区网络安全大赛 初赛 Web数据安全AI 题解WriteUp

文章首发于【先知社区】&#xff1a;https://xz.aliyun.com/t/15442 Lyrics For You 题目描述&#xff1a;I have wrote some lyrics for you… 开题。 看一下前端源码&#xff0c;猜测有路径穿越漏洞 http://139.155.126.78:35502/lyrics?lyrics../../../../../etc/passw…

中国同一带一路沿线国海关货物进出口额表(年)1994-2022进出口总额进口总额出口总额

数据来源&#xff1a;基于相关&#xff08;证券、货币、期货等&#xff09;交易所、各部委、省、市、区县统计NJ、或各地区公布的数据&#xff08;若是全球各国数据&#xff0c;主要来源于世界银行世界发展指标WDI、或联合国统计数据&#xff09; 数据范围&#xff1a;&#x…

安装Android Studio及第一个Android工程可能遇到的问题

Android Studio版本众多&#xff0c;电脑操作系统、电脑型号、电脑硬件也是多种多样&#xff0c;幸运的半个小时内可以完成安装&#xff0c;碰到不兼容的电脑&#xff0c;一天甚至更长时间都无法安装成功。 Android安装及第一个Android工程分为4个步骤&#xff0c;为什么放到一…

E31.【C语言】练习:指针运算习题集(上)

Exercise 1 求下列代码的运行结果 #include <stdio.h> int main() {int a[5] { 1, 2, 3, 4, 5 };int* ptr (int*)(&a 1);printf("%d",*(ptr - 1));return 0; } 答案速查: 分析&#xff1a; Exercise 2 求下列代码的运行结果 //在x86环境下 //假设结…

使用Ansible stat模块检查目录是否存在

使用Ansible stat模块检查目录是否存在或者是否为一个目录还是文件 理论知识 在Ansible中&#xff0c;你可以使用stat模块来检查一个目录是否存在。stat模块可以用来获取文件或目录的状态信息&#xff0c;包括它是否存在。下面是一个简单的例子&#xff0c;说明如何使用stat模…

9/3作业

一、继承&#xff08;inhert&#xff09; 面向对象三大特征&#xff1a;封装、继承、多态 继承&#xff1a;所谓继承&#xff0c;是类与类之间的关系。就是基于一个已有的类&#xff0c;来创建出一个新类的过程叫做继承。主要提高代码的复用性。 1.1 继承的作用 1> 实现…