C++ STL - vector 模拟实现+解析迭代器

2023-11-17

目录

vector使用

vector模拟实现

vector实现解析:

memcpy进行元素拷贝问题:

扩容问题:

vector迭代器解析:

vector迭代器失效问题:

1.  示例一:一个典型的迭代器失效bug:insert实现

2. 示例二:insert引起的迭代器失效问题:

3. erase导致迭代器失效问题:

迭代器失效总结:

vector<vector<int>> 的理解:


vector使用

vector就是一个可变大小的数组,有多种构造函数。有析构函数,因为涉及内存管理,自然也有了拷贝构造和operator=。

大小相关:size获取数组大小,capacity获取当前可存储最大元素个数。主要是resize和reserve,resize改变数组大小,可变大可变小,扩size时可同时对元素初始化。reserve用于扩容,当知道vector大致存储元素个数时,可以用reserve缓解扩容带来的性能消耗。reserve的值小于当前capacity时,不会缩容。大于时会扩容,这里的容量不是size而是capacity。

我们知道,当vector的size==capacity时,再push_back会扩容,那么,vector的扩容机制是怎样的呢?实际上STL并没有规定vector的扩容机制,不同版本的STL有不同的存储策略。vs下capacity是按1.5倍增长的,g++是按2倍增长的。 这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。

元素访问:支持operator[],这也是list,forward_list所不支持的。根本原因是底层存储为连续存储。back front at

修改:push_back pop_back,因为底层存储问题,push_front 和 pop_front时间复杂度较高,所以不支持头删头插,这也导致vector不适合做queue的适配容器。而可以做stack的适配容器。还比较重要的就是insert 和 erase了,也没什么,重载了多个版本,适于不同情况下的insert和erase,且都有返回值。insert返回新插入的第一个元素处的迭代器。erase返回删除元素或元素序列后的第一个元素处的迭代器。

说了很多FH,看https://cplusplus.com/reference/vector/vector/即可。

vector模拟实现

#ifndef STL_VECTOR_H
#define STL_VECTOR_H
#include "reverse_iterator.h"

namespace yzl
{
    // 拷贝构造,重载赋值都没实现。
    template<typename T, class Alloc = std::allocator<T>> // 就是一个用于申请T类型对象的类型而已。Alloc就是这么一个类型
    class vector {
    public:
        typedef T value_type;
        typedef value_type *iterator;
        typedef const value_type *const_iterator;

        typedef yzl::__reverse_iterator<iterator, T&, T*> reverse_iterator;
        typedef yzl::__reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;
    private:
        iterator _start;
        iterator _finish;
        iterator _end_of_storage;
    public:
        iterator begin() {
            return _start;
        }

        iterator end() {
            return _finish;
        }

        const_iterator begin() const {
            return _start;
        }

        const_iterator end() const {
            return _finish;
        }

        const_iterator cbegin() const {
            return _start;
        }

        const_iterator cend() const {
            return _finish;
        }

        reverse_iterator rbegin()
        {
            return yzl::__reverse_iterator<iterator, T&, T*>(end());
        }

        reverse_iterator rend()
        {
            return yzl::__reverse_iterator<iterator, T&, T*>(begin());
        }

        yzl::__reverse_iterator<const_iterator, const T&, const T*> rbegin() const
        {
            return yzl::__reverse_iterator<const_iterator, const T&, const T*>(end());
        }

        const_reverse_iterator rend() const
        {
            return const_reverse_iterator(begin());
        }

        const_reverse_iterator crbegin() const
        {
            return const_reverse_iterator(end());
        }
        const_reverse_iterator crend() const
        {
            return const_reverse_iterator(begin());
        }
    public:
        vector()
                : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {}

//        vector(size_t n, const T& val=T())
//        : _start(new T[n]), _finish(_start+n), _end_of_storage(_start+n)
//        {
//            for(size_t i = 0; i < n; ++i)
//                *(_start+i) = val;    // 调用T的赋值运算符
//        }
        // 必须有下面这个,如果是size_t  T的如上函数,则会出现报错。
        vector(int n, const T &value = T())
                : _start(new T[n]), _finish(_start + n), _end_of_storage(_finish) {
            for (int i = 0; i < n; ++i) {
                _start[i] = value;
            }
        }

        template<class InputIterator>
        vector(InputIterator first, InputIterator last)
                : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {
//            size_t sz = last - first;
            while(first+sz != last)
            {
                ++sz;
            }
//            _start = new T[sz];
//            _finish = _start + sz;
//            _end_of_storage = _start + sz;
//            for(size_t i = 0; i < sz; ++i)
//            {
//                *(_start+i) = *first;
//                ++first;
//            }

            // 复用版本:  有问题:
            while (first != last) {
                this->push_back(*first);
                first++;
            }
        }

//        vector(const vector<T> &v)
//                : _start(new T[v.size()]), _finish(_start + v.size()), _end_of_storage(_finish) {
//            // 不能用memcpy,memcpy是逐字节拷贝,若涉及深度拷贝则出错。
//            for (size_t i = 0; i < v.size(); ++i) {
//                *(_start + i) = *(v._start + i);
//            }
//        }
        // vector拷贝构造的第二种写法:
//        vector(const vector& v)
//        : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
//        {
//            reserve(v.size());
//            for(const auto& i : v)
//            {
//                this->push_back(i);
//            }
//        }

        // vector的现代写法:借用其他的构造
        vector(const vector &v)
                : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {
            // 这是要基于迭代器区间构造
            vector<T> tmp(v.begin(), v.end());
            this->swap(tmp);
        }

        vector<T> &operator=(vector<T> v) {
            this->swap(v);
            return *this;
        }

        ~vector() {
            if (_start) {
                delete[] _start;
                _start = _finish = _end_of_storage = nullptr;
            }
        }
        // -------------------------------------------------------------------
        size_t size() const {
            return _finish - _start;
        }

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

        void reserve(size_t n) {
            if (n > capacity()) {
                size_t oldSize = size();
                T *tmp = new T[n];
                // 不能memcpy,必须这样,防止元素类型涉及深拷贝。
                for (size_t i = 0; i < size(); ++i) {
                    *(tmp + i) = *(_start + i);
                }
                delete[] _start;
                _start = tmp;
                _finish = _start + oldSize;
                _end_of_storage = _start + n;
            }
        }

        void resize(size_t n, const T &val = T()) {
            if (n > size()) {
                reserve(n);
                size_t sz = size();
                for (; sz < n; ++sz) {
                    *(_start + sz) = val;
                }
                _finish = _start + n;
            } else {
                _finish = _start + n;
            }
        }

        void push_back(const T &val) {
            if (_finish == _end_of_storage) {
                reserve(size() == 0 ? 4 : 2 * size());
            }
            *(_start + size()) = val;
            ++_finish;
        }

        void pop_back() {
            assert(size() > 0);
            --_finish;
        }

        iterator insert(const_iterator position, const value_type &val) {
            assert(position >= _start);
            assert(position <= _finish);
            if (_finish == _end_of_storage) {
                size_t diff = position - _start;
                reserve(size() == 0 ? 4 : 2 * size());
                position = _start + diff;   // 纠正迭代器,因为扩容之后原迭代器就失效了   insert引起的迭代器失效!!!!!
            }
            iterator end = _finish - 1;
            while (end >= position) {
                *(end + 1) = *(end);
                end--;
            }
            *(_start + (position - _start)) = val;   // 调用value_type的赋值操作。
            ++_finish;
            return _start + (position - _start);
        }

        iterator insert(const_iterator position, size_t n, const value_type &val) {
            assert(position >= _start);
            assert(position <= _finish);
            if (_finish + n > _end_of_storage) {
                size_t diff = position - _start;
                reserve(size() + n);
                position = _start + diff;
            }
            iterator it = _finish - 1;
            while (it >= position) {
                *(it + n) = *it;
                --it;
            }
            size_t diff = position - _start;
            for (size_t i = diff; i < diff + n; ++i) {
                *(_start + i) = val;
            }
            _finish += n;
            return _start + diff;
        }

        iterator erase(const_iterator pos) {
            assert(pos >= _start && pos < _finish);
            iterator it = _start + (pos - _start);
            while (it != _finish - 1) {
                *it = *(it + 1);
                ++it;
            }
            --_finish;
            return _start + (pos - _start);
        }

//        iterator erase(const_iterator first, const_iterator lase)
//        {
//            return first;
//        }
        T &operator[](size_t n) {
            assert(n < size());
            return *(_start + n);
        }

        const T &operator[](size_t n) const {
            assert(n < size());
            return *(_start + n);
        }

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

        T &front() {
            assert(size() > 0);
            return *_start;
        }

        const T &front() const {
            assert(size() > 0);
            return *_start;
        }

        T &back() {
            assert(size() > 0);
            return *(_finish - 1);
        }

        const T &back() const {
            assert(size() > 0);
            return *(_finish - 1);
        }

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

        void clear() {
            _finish = _start;
        }
    };
}


#endif //STL_VECTOR_H

vector实现解析:

1. vector就是靠三个元素指针T*维护的,_start    _finish    _end_of_storage。所以,每个vector对象中只存储三个指针变量T*,这些指针指向的区域存储着具体的元素。push_back reserve insert erase等操作都是基于这三个指针进行操作。

memcpy进行元素拷贝问题:

我们知道,当_finish == _end_of_storage时,表示当前没有多余空间,若进行push_back,则需要扩容,扩容并非简单的原地扩容,而是要经历申请新空间->拷贝元素->释放旧空间的过程。
拷贝元素时,并不能一概使用memcpy进行拷贝,memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中。如果拷贝的是简单的内置类型int等,memcpy既高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。

        void reserve(size_t n) {
            if (n > capacity()) {
                size_t oldSize = size();
                T *tmp = new T[n];
                // 不能memcpy,必须这样,防止元素类型涉及深拷贝。
                for (size_t i = 0; i < size(); ++i) {
                    *(tmp + i) = *(_start + i);
                }
                delete[] _start;
                _start = tmp;
                _finish = _start + oldSize;
                _end_of_storage = _start + n;
            }
        }

扩容问题:

韩信带净化,无多余可用空间时,扩容机制并非一律以2倍或1.5倍扩容,而是要看具体STL实现版本。

但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。

vector迭代器解析:

因为vector底层实现是在一段连续的内存中存储,所以,vector的迭代器就是原生指针。

        typedef T value_type;
        typedef value_type *iterator;
        typedef const value_type *const_iterator;

        typedef yzl::__reverse_iterator<iterator, T&, T*> reverse_iterator;
        typedef yzl::__reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;

iterator 即 T*    const_iterator 即 const T*  

那么,这样可行的原因是什么呢?实际上,我们要看迭代器需要哪些操作:解引用,++,--,==,!=。那么T*完全具有这些操作。并且const T*也完全符合const迭代器的要求,即返回值为const的,且迭代器本身可++,--。

vector迭代器失效问题:

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

那么,那些可能使原生态指针指向空间销毁的情况,也是我们需要注意迭代器失效的情况。

1.  示例一:一个典型的迭代器失效bug:insert实现

        iterator insert(const_iterator position, const value_type &val) {
            assert(position >= _start);
            assert(position <= _finish);
            if (_finish == _end_of_storage) {
                size_t diff = position - _start;
                reserve(size() == 0 ? 4 : 2 * size());
                position = _start + diff;   // 纠正迭代器,因为扩容之后原迭代器就失效了   insert引起的迭代器失效!!!!!
            }
            iterator end = _finish - 1;
            while (end >= position) {
                *(end + 1) = *(end);
                end--;
            }
            *(_start + (position - _start)) = val;   // 调用value_type的赋值操作。
            ++_finish;
            return _start + (position - _start);
        }

如上代码中,如果size == capacity,则需要reserve,在reserve之前,存储diff,是因为一旦reserve了,则原来的_start _finish _end_of_storage 包括上方的position就都失效了。这时,对position重新赋值,就可以解决此bug。

2. 示例二:insert引起的迭代器失效问题:

void test4()
{
    yzl::vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
//    v.push_back(5);

    for (auto e : v)
    {
        cout << e << " ";
    }
    cout << endl;

    auto p = find(v.begin(), v.end(), 3);
    if (p != v.end())
    {
        // 在p位置插入数据以后,不要访问p,因为p可能失效了。
        v.insert(p, 30);

//        cout << *p << endl;
        v.insert(p, 40);
    }

    for (auto e : v)
    {
        cout << e << " ";
    }
    cout << endl;
}
void test4()
{
    yzl::vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    v.push_back(5);

    for (auto e : v)
    {
        cout << e << " ";
    }
    cout << endl;

    auto p = find(v.begin(), v.end(), 3);
    if (p != v.end())
    {
        // 在p位置插入数据以后,不要访问p,因为p可能失效了。
        v.insert(p, 30);

        cout << *p << endl;
        v.insert(p, 40);
    }

    for (auto e : v)
    {
        cout << e << " ";
    }
    cout << endl;
}

第一段test4代码中,push_back了4次,此时已满,第一次insert时,是需要reserve的,导致vector的整个存储空间都发生了改变,原来空间上的指针都失效了,而p迭代器就是原生指针,导致此时再进行第二次insert时,就会发生报错,因为访问了非法空间。

而第二段test4中,push_back5次,此时size==5 capacity == 8,在进行两次insert时,不会发生扩容,也就使得p所指向空间还是有效的,程序不会发生报错。并且cout<<*p<<endl;时打印的是30,因为3元素后移一位

为了解决上述问题,insert函数本身具有迭代器返回值,返回的迭代器指向新插入的第一个元素。所以,为了避免insert带来的迭代器失效问题,我们应该

pos = v.insert(pos, 3);

3. erase导致迭代器失效问题:

a、不排除某些STL实现的erase在size<capacity/2时,进行缩容,那么此时也会导致迭代器失效。这是一种以时间换空间的策略,虽然我们用的VS g++并没有这个操作,但是这种可能性是存在的。

b、 

void test5()
{
    yzl::vector<int> v;
    v.push_back(1);
    v.push_back(2);
//    v.push_back(4);
    v.push_back(3);
    v.push_back(4);
    v.push_back(5);
    auto it = v.begin();
    //   error
//    while(it != v.end())
//    {
//        if(*it %2==0)
//            v.erase(it);
//        it++;
//    }
    while(it != v.end())
    {
        if(*it % 2 == 0)
        {
            it = v.erase(it);
        }
        else
        {
            ++it;
        }
    }
    for(auto&i:v)
        cout<<i<<" ";
    cout<<endl;
}

error为错误的erase使用,后方为正确的。

我们会发现,随着v的内容不同,基于错误的erase使用,结果不同。12345 1234 124345 会有不同的结果。

比如1234  删除2 ++ 指向4 删除4 ++ 越界。导致程序崩溃。
比如124345 删除2 ++ 指向3 ++ 删除4 ++ 等于end,程序不崩溃,但是没有删除第一个4

其实很简单,就是erase的错误使用。所以,erase有返回值,我们需要正确使用erase

c、erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代 器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是 没有元素的,那么pos就失效了。因此删除vector中任意位置上元素时,vs就认为该位置迭代器失效 了。

不同IDE使用的不同版本的STL,对于erase操作的管控程度不同,但是根本上来说,erase之后,都不应该再访问原来的迭代器,而必须更新迭代器。

4. 与vector类似,string在经历扩容后,原来的迭代器也会失效。

迭代器失效总结:

会引起其底层空间改变的操作,都有可能使迭代器失效,比如:resize、reserve、insert、assign、 push_back等。包括erase。

迭代器失效解决办法:在使用前,对迭代器重新赋值即可。insert和erase因此也有了返回值。

vector<vector<int>> 的理解:

 上面两个图就足够清楚了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++ STL - vector 模拟实现+解析迭代器 的相关文章

  • 获取数组变量的地址是什么意思?

    今天我读到了一段让我很困惑的 C 代码片段 include
  • 在 VS2017 下使用 Conan 和 CMake 项目进行依赖管理

    我正在尝试使用 CMake 与 VS2017 集成为 C 设置一个开发环境 以便在 Linux x64 下进行编译 为了更好地管理依赖关系 我选择使用 Conan 但我对这个软件还很陌生 我想知道让 VS2017 识别项目依赖关系的最佳方法
  • 如何使用 zlib 制作 .zip 文件

    我正在阅读zlib的文档 它相当详细 但我读到了这一行 输出数据将位于zlib格式 与 gzip 或zip formats http www zlib net zlib how html http www zlib net zlib how
  • 分段错误(核心转储)错误

    我的程序编译罚款 但在输入文件时出现 分段错误 核心转储 错误 我没有正确处理 ostream 吗 include
  • ASP.NET Core 与现有的 IoC 容器和环境?

    我想运行ASP NET 核心网络堆栈以及MVC在已托管现有应用程序的 Windows 服务环境中 以便为其提供前端 该应用程序使用 Autofac 来处理 DI 问题 这很好 因为它已经有一个扩展Microsoft Extensions D
  • 在 C# 中生成 HMAC-SHA1

    我正在尝试使用 C 来使用 REST API API 创建者提供了以下用于 hmac 创建的伪代码 var key1 sha1 body var key2 key1 SECRET KEY var key3 sha1 key2 var sig
  • CultureInfo 的实例(来自相同的文化)根据操作系统而变化

    我有一个网站 上面写着这样的日期 CultureInfo cultureInfo CultureInfo GetCultures CultureTypes AllCultures FirstOrDefault c gt string Equ
  • 将字符串中的“奇怪”字符转换为罗马字符

    我需要能够将用户输入仅转换为 a z 罗马字符 不区分大小写 所以 我感兴趣的角色只有26个 然而 用户可以输入他们想要的任何 形式 的字符 西班牙语 n 法语 e 和德语 u 都可以包含用户输入中的重音符号 这些重音符号会被程序删除 我已
  • 将字符串转换为正确的 URI 格式?

    有没有简单的方法可以将电子邮件地址字符串转换为正确的 URI 格式 Input http mywebsite com validate email 3DE4ED727750215D957F8A1E4B117C38E7250C33 email
  • 将带有 glut 的点击坐标添加到向量链接列表中

    我想创建一个向量链接列表 并在 GLUT 库的帮助下获取点击的位置并将它们附加到链接列表中 这些是我写的结构 typedef struct vector int x int y Vector typedef struct VectorLis
  • 从成员函数指针类型生成函子

    我正在尝试简化 通过make fn 预处理参数的函子的生成 通过wrap 对于 arity 的成员函数n 生成函子基本上可以工作 但到目前为止只能通过显式指定成员函数的参数类型来实现 现在我想从它处理的成员函数类型生成正确的函子 struc
  • C# 委托责任链

    为了我的理解目的 我实现了责任链模式 Abstract Base Type public abstract class CustomerServiceDesk protected CustomerServiceDesk nextHandle
  • libxml2 xmlChar * 到 std::wstring

    libxml2似乎将所有字符串存储在 UTF 8 中 如xmlChar xmlChar This is a basic byte in an UTF 8 encoded string It s unsigned allowing to pi
  • 使用 WF 的多线程应用程序的错误处理模式?

    我正在写一个又长又详细的问题 但只是放弃了它 转而选择一个更简单的问题 但我在这里找不到答案 应用程序简要说明 我有一个 WPF 应用程序 它生成多个线程 每个线程执行自己的 WF 处理线程和 WF 中的错误 允许用户从 GUI 端进行交互
  • ASP.NET JQuery AJAX POST 返回数据,但在 401 响应内

    我的应用程序中有一个网页 需要调用我设置的 Web 服务来返回对象列表 这个调用是这样设置的 document ready function var response ajax type POST contentType applicati
  • 从 Delphi 调用 C# dll

    我用单一方法编写了 Net 3 5 dll 由Delphi exe调用 不幸的是它不起作用 步骤 1 使用以下代码创建 C 3 5 dll public class MyDllClass public static int MyDllMet
  • 使用 HTMLAgilityPack 从节点的子节点中选择所有

    我有以下代码用于获取 html 页面 将网址设置为绝对 然后将链接设置为 rel nofollow 并在新窗口 选项卡中打开 我的问题是关于将属性添加到 a s string url http www mysite com string s
  • C语言声明数组没有初始大小

    编写一个程序来操纵温度详细信息 如下所示 输入要计算的天数 主功能 输入摄氏度温度 输入功能 将温度从摄氏度转换为华氏度 独立功能 查找华氏度的平均温度 我怎样才能在没有数组初始大小的情况下制作这个程序 include
  • 为什么文件更新时“如果较新则复制”不复制文件?

    我在 Visual Studio Express 中有一个解决方案 如下所示 The LogicSchemaC 中的类 将在运行时解析指定的 XML 文件 以下是在main的方法Program cs LogicSchema ls new L
  • 带有私有设置器的 EFCore Base 实体模型属性 - 迁移奇怪的行为

    实体模型继承的类内的私有设置器似乎会导致 EFCore 迁移出现奇怪的问题 考虑以下示例 其中有多个类 Bar and Baz 继承自Foo 跑步时Add Migration多次命令 添加 删除private修饰符 生成的模式在多个方面都是

随机推荐

  • Luat 功能开发教程(十四) 延时和定时器

    目录 延时和定时器 简介 API说明 实现流程 创建 消亡 自动消亡 手动消亡 判断定时器状态 知识拓展 示例 常见问题 相关资料以及购买链接 延时和定时器 简介 在luat脚本程序中 往往需要用到延时和等待等逻辑功能 例如 你想得到每隔3
  • 【el-time-picker设置默认值】Cannot read properties of undefined (reading ‘hour‘)

    需求 设置默认时间为2 0 0的时间选择器 产生报错的写法
  • 必填校验设置‘change‘, ‘blur‘同时起作用

    必填校验设置 change blur 同时起作用 rules seaAreaName required true message 请输入海区 trigger change blur
  • 华为机试-第二题

    查找知识图谱中的实例知识 知识图谱是一种结构化的语义网络 用于描述物理世界中的概念及其实例的相关关系 可以把知识图谱看成是一种有向图 图中的点是概念或实例 图中的边是概念及其实例的相关关系 现定义一种简单的知识图谱 概念 包括父概念及其子概
  • bootstrap jquery dataTable 异步ajax刷新表格数据

    异步请求 var postData env name new env name env url new env url env desc new env desc ajax type POST url test env add data p
  • Interactive Natural Language Processing

    本文是对 Interactive Natural Language Processing 的翻译 交互式自然语言处理 摘要 1 引言 2 交互式对象 2 1 人在环 2 2 KB在环 2 3 模型或工具在环 2 4 环境在环 3 交互界面
  • Oracle数据块概念及与行之间的关系测试

    数据块 Oracle Data Blocks 是Oracle最小的存储单位 Oracle数据存放在 块 中 一个块占用一定的磁盘空间 这里的 块 是Oracle的 数据块 不是操作系统的 块 操作系统的块通常为512k Oracle每次请求
  • openwrt 应用程序 开机自启动

    这几介绍一下openwrt 应用程序包开机自启动的两种方法 使用的平台是MTK7688开发板 首先写一个以及可以跑起来的工程 这里对工程就不做展开 以helloworld工程为例 helloworld工程写在 openwrt package
  • Python bs4怎么安装?

    bs4是BeautifulSoup4的简称 它是一个可以从HTML中提取数据的Python第三方库 具体来讲 bs4可以从茫茫的HTML代码中准确查找出你想要的内容 甚至一个小小的字符串 听起来是不是感觉bs4很厉害的样子 那么 Pytho
  • 预测模型的评价指标Matlab

    一般情况下 所要预测的数据分为连续型数据和离散性数据 连续型数据比如成绩分数 时间序列等 离散性数据通常为划分的分类标签 针对不同的数据类型 衡量模型的准确程度采用不同指标 如比较一些算法的准确率 若预测的数据为离散型 则算法的准确性自然容
  • 【综合转贴】CSS “点 ”“井号”的含义and ID class区别.

    body font family Arial sans serif color 333333 line height 1 166 margin 0px padding 0px masthead margin 0 padding 10px 0
  • 【3月比赛合集】45场可报名的数据挖掘奖金赛,任君挑选!

    CompHub 实时聚合多平台的数据类 Kaggle 天池 和OJ类 Leetcode 牛客 比赛 本账号同时会推送最新的比赛消息 欢迎关注 更多比赛信息见 CompHub主页 或 点击文末阅读原文 以下信息仅供参考 以比赛官网为准 目录
  • k8s部署之ETCD集群

    k8s部署之ETCD集群 1 etcd下载 etcd下载地址 https github com coreos etcd releases 从github etcd的发布页面选取相应的版本用 wget url 来下载 如 wget https
  • 【易售小程序项目】小程序首页(展示商品、商品搜索、商品分类搜索)【后端基于若依管理系统开发】

    文章目录 界面效果 界面实现 工具js 页面 首页 让文字只显示两行 路由跳转传递对象 将商品分为两列显示 使用中划线划掉原价 后端 商品 controller service mapper sql 同项目其他文章 界面效果 说明 界面中商
  • Docker容器中如何运行一个带GUI的app?

    问 How can you run GUI apps in a docker container Are there any images that set up vncserver or something so that you can
  • 图像处理之-----插值算法

    插值算法是图像处理中最基本的算法 首先我们先了解一下什么是插值算法 以及插值算法在图像处理过程中的应用 1 什么是插值 Interpolation is a method of constructing new data points wi
  • Redis 密码设置和查看密码

    Redis 密码设置和查看密码 redis没有实现访问控制这个功能 但是它提供了一个轻量级的认证方式 可以编辑redis conf配置来启用认证 1 初始化Redis密码 在配置文件中有个参数 requirepass 这个就是配置redis
  • Python软件开发之需求实现:数据结构、数据类型。自动化软件测试必会

    一 有这样的一个需求 判断学生成绩是否及格 二 拿到这样的一个需求如何进行需求分析呢 做为测试人员 我们只有明确需求后 才不容易漏测 需求分析阶段 一 看到这样的一句话之后我们有几个问题需求和产品经理确认的 1 什么样的算及格 60 70分
  • Spark 启动集群 Master 正常启动 Worker 不启动

    在学习spark过程中遇到的问题 做下记录 这个问题网上出现的不再少数 出现问题的原因也是各不相同 并且没有一个人的问题和我完全一样 我高兴得都快哭了 顺着大家的思路 尝试了两个多小时才搞明白 问题的根源大多都在于 hostname 的配置
  • C++ STL - vector 模拟实现+解析迭代器

    目录 vector使用 vector模拟实现 vector实现解析 memcpy进行元素拷贝问题 扩容问题 vector迭代器解析 vector迭代器失效问题 1 示例一 一个典型的迭代器失效bug insert实现 2 示例二 inser