当前位置: 首页 > news >正文

list(二) (list模拟实现)

首先进行大框架  先写基本的结点类  有data next  prev

	template<class T>class ListNode//或者使用struct 就不用在写public声明公有{public://这里不仅仅是成员函数 成员变量也要公有化 ListNode<T>*  _next;ListNode<T>*  _prev;T _data;}

之后是链表list类的构造

template<class T>
class list
{typedef ListNode<T> Node;
public:list(){_head = new Node;_head->_next = _head;_head->_prev = _head;}
private:Node* _head;
};

私有成员是节点类型的指正 会在构造时指向我们的头节点

这里 我们创建的列表是带头双向循环链表  所以在初始化时让头节点的next 和prev指向自己  

接下来要模拟尾差 push_back  带头双向循环链表  是创建一个新节点  然后让新节点的prev指向尾节点  尾结点的next指向新节点 新节点的next指向头节点 头节点的prev指向新节点  

void push_back(const T& x)
{Node* newnode = new Node(x);Node* tail = _head->_prev;tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;}

这里由于是双向带头循环链表 所以我们的尾节点就是头节点的prev指向的结点 所以我们不需要手动更新尾节点的位置  每次插入完成后 新节点会自动成为尾节点

这里在测试时会出现一些问题

1.按需实例化  那就是一些类和函数的一些语法问题 在没有被调用时系统不会检查报错 只有使用时实例化模版 这是才会报错

2.在上面的节点类中我们只写了成员函数而没有写构造函数 需要补充

3.关于私有公有的问题  我们的类在默认情况下是私有的  只有public下才能允许外界访问  而struct在默认情况下是全部共有的

template<class T>
class ListNode//或者使用struct 就不用在写public声明公有
{
public:
//这里不仅仅是成员函数 成员变量也要公有化 ListNode<T>*  _next;ListNode<T>*  _prev;T _data;ListNode(const T& data )//这里也可以用匿名对象写成全缺省: _next(nullptr), _prev(nullptr), _data(data) {}};	

这里我们上面的list在构造时并没有在头结点进行初始化赋值 而我们的push_bcak函数对新节点创立时进行了赋值  所以为了保持一致  要对头结点创建时进行赋值 也就是要有一个缺省值  但是这里不能给0  因为date 是T类型的  有可能是double 有可能是string  不能使用0赋值

那我们应该如何赋值给头结点呢 可以考虑给匿名对象

也可以在结点创建处参数写成全缺省

template<class T>
class ListNode//或者使用struct 就不用在写public声明公有
{
public:
//这里不仅仅是成员函数 成员变量也要公有化 ListNode<T>*  _next;ListNode<T>*  _prev;T _data;ListNode(const T& data = T() )//这里也可以用匿名对象写成全缺省: _next(nullptr), _prev(nullptr), _data(data) {}};	

这时进行测试

void list1()//在没有没有实例化以前检查是没有意义的 因为细节不会检查模版 只有实例化以后才会被检查
{//按需实例化 在没有实例化之前不会检查细节 同时只有调用这部分才会检查这部分(除了一些大的语法错误)在没实例化之前不会检查其对应部分list<int> l1;//不调用 就不实例化这个成员函数l1.push_back(1);l1.push_back(2);l1.push_back(1);l1.push_back(5);l1.push_back(10);l1.push_back(15);
}

这时push_back就可以正常使用了

接下来要模拟list迭代器  

迭代器++要到达下一个位置 但是这里的list节点不符合  list节点可以通过next指向到达下一个结点  

所以可以通过重载++实现迭代器模拟

template<class T>
class ListIterator//这里也存在私有的问题 需要使用public
{
public://这里也不用太担心公有化访问的 问题 不同的编译器下底层实现的代码是不同的 也就是名字不一定相同 同时正常一般是直接回调用迭代器 不会直接访问类成员typedef ListNode<T> Node;typedef ListIterator<T> Self;Node* _node;ListIterator(Node* node):_node(node){}//这个迭代器要不要写析构 不要  这个迭代器指向的空间是链表的 不希望被析构//这里也不需要拷贝构造 因为这里需要的就是浅拷贝 所以默认生成的拷贝构造就够用  所以默认生成的浅拷贝也是有些意义的 并不是完全没意义T& operator * () //每个类都能重载运算符 当重载号运算符之后就能控制一个类的行为{return _node->_data;}Self& operator ++(){_node = _node->_next;return *this;}
bool operator !=(const Self& it)
{return _node != it._node;
} 
};

这里迭代器的模拟重新使用了一个类来进行重载  这里的类成员就是一个结点指针  构造是对结点指针进行初始化构造   在一般的迭代器循环中 最常用到的是++  解引用   !=   三个重载   这里先谢了这三个   这里的所有成员变量和函数 都是需要的  所以设置为公有public

实现这个类之后  在list类中typedef  iterator   这里为什么不直接使用listIterator  这是为了统一名称  

使代码使用更加流畅   同时在list类中实现  begin()  end() 成员函数

template<class T>
class list
{typedef ListNode<T> Node;public:
typedef ListIterator<T > iterator; //每个类都要使用iterator去规范迭代器  这样每个容器使用的都是iterator 这样使用起来方便 成本较低iterator begin()
{/*iterator it(_head->_next);return it;*/ //定义有名对象不如定义匿名对象return iterator(_head -> _next);
}
iterator end()
{return iterator (_head);
}list(){_head = new Node;_head->_next = _head;_head->_prev = _head;}void push_back(const T& x)
{Node* newnode = new Node(x);Node* tail = _head->_prev;tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;}private:Node* _head;
};

这里对于内置类型无法重载运算符 只有自定义类型才能重载运算符

	void list1()//在没有没有实例化以前检查是没有意义的 因为细节不会检查模版 只有实例化以后才会被检查{//按需实例化 在没有实例化之前不会检查细节 同时只有调用这部分才会检查这部分(除了一些大的语法错误)在没实例化之前不会检查其对应部分list<int> l1;//不调用 就不实例化这个成员函数l1.push_back(1);l1.push_back(2);l1.push_back(1);l1.push_back(5);l1.push_back(10);l1.push_back(15);list<int>::iterator it = l1.begin();while (it != l1.end()){cout << *it << " ";++it;}cout << endl;}

这里可以看到我们的初代迭代器是可以正常运行的

接下来我们继续通过重载的方式对迭代器进行进一步的完善  

template<class T>
class ListIterator//这里也存在私有的问题 需要使用public
{
public://这里也不用太担心公有化访问的 问题 不同的编译器下底层实现的代码是不同的 也就是名字不一定相同 同时正常一般是直接回调用迭代器 不会直接访问类成员typedef ListNode<T> Node;typedef ListIterator<T> Self;Node* _node;ListIterator(Node* node):_node(node){}//这个迭代器要不要写析构 不要  这个迭代器指向的空间是链表的 不希望被析构//这里也不需要拷贝构造 因为这里需要的就是浅拷贝 所以默认生成的拷贝构造就够用  所以默认生成的浅拷贝也是有些意义的 并不是完全没意义T& operator * () //每个类都能重载运算符 当重载号运算符之后就能控制一个类的行为{return _node->_data;}Self& operator ++(){_node = _node->_next;return *this;}Self& operator --()
{_node = _node->_prev;return *this;
}Self  operator ++(int)//这里是后置加加 参数中有int用来与前置++作区分
{Self tmp(*this);//这里返回的是加加之前的值的 所以需要提前保存一下_node = _node->_next;return *tmp;
}Self  operator --(int)
{Self tmp(*this);_node = _node->_prev;return *tmp;
}bool operator !=(const Self& it)
{return _node != it._node;
} bool operator ==(const Self& it)
{return _node == it._node;
}};

这里注意前置 与后置的 区别 前置返回的是引用的self  而后置则不引用直接返回self  同时为了区分前置和后置 在后置函数的括号中加了int   同时++是向下一个next移动  而--则是像下一个prev移动

这里要不要重载+和-  是不要的  因为+ 和-的效率非常低  如果要加10 那么就要向前走十次  

即便是在std库中也是没有重载+和-的

这里迭代器类是不需要析构函数的  这里并不希望迭代器释放   这里也不要写深拷贝构造 这里默认拷贝构造 和默认析构就够用

这里也支持修改 支持迭代器也就支持范围for

void list1()//在没有没有实例化以前检查是没有意义的 因为细节不会检查模版 只有实例化以后才会被检查
{//按需实例化 在没有实例化之前不会检查细节 同时只有调用这部分才会检查这部分(除了一些大的语法错误)在没实例化之前不会检查其对应部分list<int> l1;//不调用 就不实例化这个成员函数l1.push_back(1);l1.push_back(2);l1.push_back(1);l1.push_back(5);l1.push_back(10);l1.push_back(15);list<int>::iterator it = l1.begin();while (it != l1.end()){*it += 10;cout << *it << " ";++it;}cout << endl;for (auto e : l1){cout << e << " ";}cout << endl;
}

这里对迭代器进行进一步的加工

如果存在自定义类型pos类的链表来进行遍历

struct pos//这里需要提供默认构造
{int _row;int _col;pos(int row = 0, int col= 0)//这里写成全缺省提供默认构造:_row(row), _col(col){}
};

这是pos类的结构

可见一个pos中分别有横纵坐标

void list2()
{list<pos> l1;l1.push_back(pos(100,100));l1.push_back(pos(200, 200));l1.push_back(pos(300, 100));list<pos>::iterator it = l1.begin();while(it != l1.end()){cout << *it << " ";++it;}cout << endl;
}

这时我们的代码是无法正常运行的

正常运行的方法一  通过.来获取成员变量

void list2()
{list<pos> l1;l1.push_back(pos(100,100));l1.push_back(pos(200, 200));l1.push_back(pos(300, 100));list<pos>::iterator it = l1.begin();while(it != l1.end()){cout << (*it)._row << ":" << (*it). _col << endl;++it;}cout << endl;
}

方法2 也可以使用箭头->

void list2()
{list<pos> l1;l1.push_back(pos(100,100));l1.push_back(pos(200, 200));l1.push_back(pos(300, 100));list<pos>::iterator it = l1.begin();while(it != l1.end()){cout << it->_row << ":" << it-> _col << endl;++it;}cout << endl;
}

但是想要使用箭头 我们就需要再迭代器的类中重载->

	template<class T>class ListIterator//这里也存在私有的问题 需要使用public{public://这里也不用太担心公有化访问的 问题 不同的编译器下底层实现的代码是不同的 也就是名字不一定相同 同时正常一般是直接回调用迭代器 不会直接访问类成员typedef ListNode<T> Node;typedef ListIterator<T> Self;Node* _node;ListIterator(Node* node):_node(node){}//这个迭代器要不要写析构 不要  这个迭代器指向的空间是链表的 不希望被析构//这里也不需要拷贝构造 因为这里需要的就是浅拷贝 所以默认生成的拷贝构造就够用  所以默认生成的浅拷贝也是有些意义的 并不是完全没意义T& operator * () //每个类都能重载运算符 当重载号运算符之后就能控制一个类的行为{return _node->_data;}T* operator ->(){return &_node->_data;}Self& operator ++(){_node = _node->_next;return *this;}Self& operator --(){_node = _node->_prev;return *this;}Self  operator ++(int)//这里是后置加加 参数中有int用来与前置++作区分{Self tmp(*this);//这里返回的是加加之前的值的 所以需要提前保存一下_node = _node->_next;return *tmp;}Self  operator --(int){Self tmp(*this);_node = _node->_prev;return *tmp;}bool operator !=(const Self& it){return _node != it._node;}bool operator ==(const Self& it){return _node == it._node;}};

这时代码就可以自由使用

这里访问的其实有两箭头   :cout<< it .operator-> ()-> _row <<" :"<< it.operator->() ->_col;

第一个箭头返回 pos*  第二个箭头访问成员变量

但是为了美观 就省略为了一个箭头

const迭代器问题

这里先写一个func函数用来使用const迭代器进行遍历

void func(const list<int>&it)
{ list<int>::iterator it1 = it.begin();while (it1 != it.end()){cout << *it1 << " ";++it1;}cout << endl;
}

这里普通迭代器显然是无法使用 需要const迭代器  不能直接使用const修饰iterator  因为const迭代器类似于const指针  const迭代器的目的是想要迭代器指向的内容不能修改 而迭代器本身是可以修改的   直接使用const无法达到效果   

而想要迭代器指向的内容无法修改的核心是通过控制operator * 和 operator->返回的内容不能修改

而这里想要修改 就需要一个新的类 它与普通的迭代器类大体相似 但在一些关键地方上不同

template<class T>
class constListIterator//这里也存在私有的问题 需要使用public
{
public://这里也不用太担心公有化访问的 问题 不同的编译器下底层实现的代码是不同的 也就是名字不一定相同 同时正常一般是直接回调用迭代器 不会直接访问类成员typedef ListNode<T> Node;typedef constListIterator<T> Self;Node* _node;constListIterator(Node* node):_node(node){}//这个迭代器要不要写析构 不要  这个迭代器指向的空间是链表的 不希望被析构//这里也不需要拷贝构造 因为这里需要的就是浅拷贝 所以默认生成的拷贝构造就够用  所以默认生成的浅拷贝也是有些意义的 并不是完全没意义const T& operator * () //每个类都能重载运算符 当重载号运算符之后就能控制一个类的行为{return _node->_data;}const T* operator ->(){return &_node->_data;}Self& operator ++(){_node = _node->_next;return *this;}Self& operator --(){_node = _node->_prev;return *this;}Self  operator ++(int)//这里是后置加加 参数中有int用来与前置++作区分{Self tmp(*this);//这里返回的是加加之前的值的 所以需要提前保存一下_node = _node->_next;return *tmp;}Self  operator --(int){Self tmp(*this);_node = _node->_prev;return *tmp;}bool operator !=(const Self& it){return _node != it._node;}bool operator ==(const Self& it){return _node == it._node;}};

这里我们只修改类的名字为constiterator  同时对operator* 和operator->返回进行const修饰

同时在list类中 也要加入相对应的const_iterator别名  同时添加const迭代器使用begin和end成员函数

template<class T>
class list
{typedef ListNode<T> Node;public:typedef ListIterator<T > iterator; //每个类都要使用iterator去规范迭代器  这样每个容器使用的都是iterator 这样使用起来方便 成本较低typedef constListIterator<T> const_iterator;iterator begin(){/*iterator it(_head->_next);return it;*/ //定义有名对象不如定义匿名对象return iterator(_head->_next);}iterator end(){return iterator(_head);}const_iterator begin() const{/*iterator it(_head->_next);return it;*/ //定义有名对象不如定义匿名对象return const_iterator(_head->_next);}const_iterator end()  const{return const_iterator(_head);}list(){_head = new Node;_head->_next = _head;_head->_prev = _head;}void push_back(const T& x){Node* newnode = new Node(x);Node* tail = _head->_prev;tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;}private:Node* _head;
};

这里进行测试

void func(const list<int>& it)
{list<int>::const_iterator it1 = it.begin();while (it1 != it.end()){cout << *it1 << " ";++it1;}cout << endl;
}void list3()
{list<int> l1;l1.push_back(1);l1.push_back(2);l1.push_back(1);func(l1);}

const迭代器已经可以正常运行

一个const迭代器 和一个普通的迭代器 两者之间非常的冗余 而且有很大的相似程度 这里我们可以用一个类来同时实现两个迭代器的功能

可以通过增加模版参数来实现

template<class T, class ref, class ptr>
class ListIterator//这里也存在私有的问题 需要使用public
{
public://这里也不用太担心公有化访问的 问题 不同的编译器下底层实现的代码是不同的 也就是名字不一定相同 同时正常一般是直接回调用迭代器 不会直接访问类成员typedef ListNode<T> Node;typedef ListIterator<T,ref,ptr> Self;Node* _node;ListIterator(Node* node):_node(node){}//这个迭代器要不要写析构 不要  这个迭代器指向的空间是链表的 不希望被析构//这里也不需要拷贝构造 因为这里需要的就是浅拷贝 所以默认生成的拷贝构造就够用  所以默认生成的浅拷贝也是有些意义的 并不是完全没意义ref operator * () //每个类都能重载运算符 当重载号运算符之后就能控制一个类的行为{return _node->_data;}ptr operator->() {return & _node->_data;}Self& operator ++(){_node = _node->_next;return *this;}Self& operator --(){_node = _node->_prev;return *this;}Self  operator ++(int)//这里是后置加加 参数中有int用来与前置++作区分{Self tmp(*this);//这里返回的是加加之前的值的 所以需要提前保存一下_node = _node->_next;return *tmp;}Self  operator --(int){Self tmp(*this);_node = _node->_prev;return *tmp;}bool operator !=(const Self& it){return _node != it._node;} bool operator ==(const Self& it){return _node == it._node;}
};

同时在list类中typedef时分别用不用的参数类型来使用不同的迭代器功能  通过模版参数 给不同的模版参数 让编译器来给我们写两个类(实例化)


typedef ListIterator<T ,T&,T*> iterator; //每个类都要使用iterator去规范迭代器  这样每个容器使用的都是iterator 这样使用起来方便 成本较低
//初始化
typedef ListIterator<T,const T&,const T*> const_iterator;
//typedef ListIterator<const T> const_iterator; 这种写法是不可取的 当这样写时虽然迭代器中的符合const迭代器的要求 但是我们链表中的Node是还是T 而迭代器中的是const T 在两者类型不用 实参是无法传给形参的

这样测试用例也是可以正常运行

最后在来模拟一下insert 和erase 

链表的insert 通过 对pos位置的结点 和 pos位置的prev指向节点之间插入一个新节点

这里创立prev节点 和cur节点 指针     prev指向cur指向节点的prev指向节点  cur指向pos位置的节点    同时有newnode作为新插入节点的指针  首先prev节点的next指向newnode  newnode节点的prev指向prev节点  newnode节点的next指向cur的结点  cur节点的prev 指向newnode节点

这时就插入成功了 

且这里的insert是没有迭代器失效的 但是库中仍然给了返回值

iterator insert (iterator pos , const T&x)
{Node* cur = pos._node;//这里pos指向的迭代器没有改变 所以没有失效  但是库中仍然返回了一个值 Node* prev = cur->_prev;Node* newnode = new Node(x);prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return iterator(newnode);
}

erase函数则是通过创建 prev和next指针分别指向 pos位置上一个节点 和下一个结点来进行连接

同时将now指向的pos位置的节点通过delete进行删除  这里存在迭代器失效  且只有pos位置的迭代器失效 其他位置正常  这里通过给返回值返回删除前下一个位置 来更新迭代器  这里通过断言 防止将头结点删掉

iterator erase(iterator pos)//这里存在迭代器失效的问题 
{assert(pos != end());Node* now = pos._node;Node* prev = now->_prev;Node* next = now->_next;prev->_next = next;next->_prev = prev;delete now;return iterator(next);
}

之后通过代码复用 通过insert 和erase可以快速写出 push_back  push_front  pop_back pop_front

void push_back(const T& x)
{/*Node* newnode = new Node(x);Node* tail = _head->_prev;tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;*/insert(end(),x);
}
void pop_back()//持续的删除是有可能会将哨兵位给删掉的 所以要加断言  断言在erase函数
{erase(--end());
}void push_front(const T& x)
{insert(begin(),x);
}
void pop_front()
{erase(begin());
}

接下来要写拷贝构造和析构函数 

在我们现在的代码上写拷贝测试是没有问题的 那是因为我们没有写析构函数  有了析构以后 由于是默认生成的浅拷贝 就会发生两次析构同一片空间的情况 从而报错

这里我们先写一个clear函数 在通过对clear函数进行复用 实现析构函数  这里clear函数就是通过一次次erase删除并且更新迭代器  最后将整个链表有效元素清除干净

void clear()
{auto it = begin();while (it != end()){it = erase(it);//这里erase返回的是删除前pos位置的下一个位置 所以不需要++}
}

传统链表的析构方式就是一个节点一个节点的释放

~list(){clear();delete _head;_head = nullptr;}

最后再讲头节点也删除  将头结点指针指向空  完成析构

这时由于有了析构函数 而且没有写自己拷贝构造 那么这时就会报错 因为会析构两次

	void list4(){list<int> l1;l1.push_back(1);l1.push_back(2);l1.push_back(1);func(l1);list<int> l2(l1);//这里使用的是浅拷贝 是系统默认的拷贝构造 这里没有报错 是因为这里没有写析构函数 l1.push_back(10);//这里在l1中插入10 结果打印l2会出现10 说明浅拷贝 两者指向一个空间中func(l1);func(l2);}

这里我们就需要写自己的深拷贝构造

void empty_init()//这里想要直接复用 就需要有一个哨兵位头结点
{_head = new Node();_head->_next = _head;_head->_prev = _head;}//拷贝构造
list(const list<T>& it)//这里本来需要一个结点一个结点深拷贝 但是通过空初始化获得哨兵位头节点 就可以直接进行尾插深拷贝
{empty_init();//创建哨兵位头结点 空初始化for (const auto& e : it)//这里本质上就是e迭代器 并将*it赋值给e 这里的如果T是int之类的内置类型是无所谓的 但是如果是自定义类型 这是就会发生深拷贝 这样会造成损耗{push_back(e); }
}
list()
{empty_init();
}

这里无论是普通构造 还是拷贝构造 我们都要创建头结点 所以将头结点创建写成一个单独的empty_init函数 之后复用   拷贝构造在创建好头节点后 就开始遍历要拷贝的链表 将其节点一个个尾插进入当前的头结点 完成深拷贝

这时我们的代码就能正常运行了

这里使用范围for的时候 如果无法确定其类型  那么保险起见最好加引用

for(const auto & e : lt)

赋值构造

list<T>& operator=(list<T> it)//这里是传值传参 所以这里的it就是右边的深拷贝 可以直接交换
{swap(_head,it._head);return *this;
}

通过传值传参 参数的深拷贝与this指指针进行交换  直接完成赋值

在我们没有写initializer_list构造之前  下面的测试是无法通过的

void list5()
{list<int>l1 = { 1,2,3,4,5,8,9,7 };//链表真正重要的地方就是迭代器func(l1);
}

initializer_list构造

	list(initializer_list<T> il)//这里可以不用加引用 原因是initializer可以直接把值给他 相当于一个数组 initializer有两个指针 一个指向数组开头 一个指向结尾{empty_init(); //构造都要先把哨兵位头节点弄出来for (const auto &e :il){push_back(e);}}

这里我们创建好头节点之后就通过对要插入的内容进行遍历同时一次次尾插进行当前链表  

这样我们的代码就可以正常运行了

相关文章:

list(二) (list模拟实现)

首先进行大框架 先写基本的结点类 有data next prev template<class T>class ListNode//或者使用struct 就不用在写public声明公有{public://这里不仅仅是成员函数 成员变量也要公有化 ListNode<T>* _next;ListNode<T>* _prev;T _data;}之后是链表list类…...

[Linux]从零开始的泰山派系统安装与远程教程

一、前言 泰山派买回来也有一阵子了&#xff0c;最近慢慢开始研究。当然&#xff0c;学习这种Linux的开发板的第一步就是安装系统&#xff0c;对于RK系列的芯片系统安装有专门的软件&#xff0c;所有在系统安装方面比较简单。更多的还是我们应该怎么去编译系统&#xff0c;这一…...

Python国产新 ORM 框架 fastzdp_sqlmodel 快速入门教程

创建模型 from typing import Optional from sqlmodel import Field, SQLModel import fastzdp_sqlmodel as fasmclass Hero(SQLModel, tableTrue):id: Optional[int] Field(defaultNone, primary_keyTrue)name: strsecret_name: strage: Optional[int] None创建表 from ty…...

面试速通宝典——3

51. 野指针和内存泄漏是什么&#xff1f;如何避免&#xff1f; ‌‌‌‌  内存泄漏&#xff1a;是指程序中以动态分配的堆内存由于某种原因程序未释放或无法释放&#xff0c;造成系统内存的浪费&#xff0c;导致程序运行速度减慢甚至系统崩溃等严重后果。 ‌‌‌‌  避免&…...

每天一个数据分析题(四百七十三)- 元数据

下列哪些元素属于元数据内容&#xff1f; &#xff08;&#xff09; A. 名称 B. 长度 C. 类型 D. 取值范围 数据分析认证考试介绍&#xff1a;点击进入 题目来源于CDA模拟题库 点击此处获取答案 数据分析专项练习题库 内容涵盖Python&#xff0c;SQL&#xff0c;统计学…...

产品经理面试整理-练习常见面试问题

练习常见面试问题是准备产品经理面试的重要环节。掌握这些问题的回答思路,不仅能帮助你在面试中更加自信,还能展示你对产品管理的深入理解。以下是一些常见的产品经理面试问题,以及如何高效准备这些问题的指南。 1. 常见面试问题及回答思路 1.1 你如何定义产品成功? ●...

一步到位的智慧:BI可视化大屏在复杂环境中如何精准拾取目标

在可视化设计器中实现良好的组件拾取功能&#xff0c;是提升用户体验和设计效率的关键。它们不仅能够提升用户体验和操作效率&#xff0c;还能够增强设计的灵活性和精度&#xff0c;促进设计创新&#xff0c;并最终提升设计的质量和价值。因此&#xff0c;在可视化设计过程中&a…...

前端开发迎来新机会,全栈转型就靠这个!

在如今的开发世界&#xff0c;全栈开发者已成为许多前端开发者的新目标。随着技术的不断演进&#xff0c;前端不再局限于写页面和样式&#xff0c;而是逐渐向后端延伸&#xff0c;甚至触及数据库和云服务。如果你想在职业道路上更进一步&#xff0c;向全栈开发者靠拢&#xff0…...

关于less的基本使用

1、介绍及概述 1.1、解释 less 是方便开发人员书写CSS的一门预处理语言。浏览器只认识html /css /js格式的文件&#xff0c;所以直接引入.less文件&#xff0c;没有任何的效果&#xff0c;需要把less文件转换成css文件 1.2、概述 CSS弊端&#xff1a; 没有逻辑性、变量、函…...

python 将 aac 转为 mp3,保持原有目录结构

需要提前安装 FFmpeg import os import subprocess import time from concurrent.futures import ThreadPoolExecutor, as_completeddef convert_file(input_path, output_path):command [ffmpeg,-y, # 自动覆盖现有文件-i, input_path,-acodec, libmp3lame,-b:a, 192k,outpu…...

考研数据结构——C语言实现折半查找

首先定义了一个有序数组a&#xff0c;然后计算出数组的长度n。接着定义了一个要查找的元素x&#xff0c;值为79。binarySearch函数实现了二分查找算法&#xff0c;它接受数组、左右边界和目标值作为参数&#xff0c;通过不断缩小搜索范围来查找目标值。如果找到了目标值&#x…...

【游戏引擎】C++自制游戏引擎 Lunar Game Engine

Lunar-Game Engine 仓库位置 Lunar Game Engine Lunar GameEngie是几个渣渣业余写的基于C的游戏引擎。 相比于比较成熟的引擎&#xff0c;该引擎的特点如下 结构,标准混乱bug众多根本不能用&#xff01; 最后的最后 To The Moon and Beyond! 简介 Luna Engine基于 C 和…...

使用【Sa-Token】实现Http Basic 认证

使用Sa-Token开源架构快速实现Http Basic 认证&#xff0c;如上图 1、springboot环境下直接添加starter即可 <!-- Sa-Token 权限认证&#xff0c;在线文档&#xff1a;https://sa-token.cc --> <dependency><groupId>cn.dev33</groupId><artifactI…...

layui table中的checkbox禁用问题

在项目开发中遇到table框已经选择过的数据不支持二次选择从而要禁用复选框不许选中&#xff0c;但会导致复选框全选时layui的table组件源码中赋值时是根据全部复选框的下标顺序来赋值到数组中返回给你&#xff0c;这样已被禁用复选框的数据也会被push到数组中导致数据错乱&…...

102.SAPUI5 sap.ndc.BarcodeScannerButton调用摄像头时,localhost访问正常,使用IP访问失败

目录 原因 解决办法 1.修改谷歌浏览器的setting 2.在tomcat中配置https访问 参考 使用SAPUI5的sap.ndc.BarcodeScannerButton调用摄像头时&#xff0c;localhost访问正常&#xff0c;使用IP访问时&#xff0c;一直打不开摄像头&#xff0c;提示getUserMedia()问题。 原因…...

20240923软考架构-------软考186-190答案解析

每日打卡题186-190答案 186、Mesh 化架构是把&#xff08; &#xff09;从业务进程中分离&#xff0c;分离后在业务进程中只保留很“薄”的Client部分&#xff0c;Client 通常很少变化&#xff0c;只负责与 Mesh进程通信&#xff0c;原来需要在SDK中处理的流量控制、安全等逻辑…...

基于Spring Boot的宠物咖啡馆平台【附源码】

基于Spring Boot的宠物咖啡馆平台&#xff08;源码L文说明文档&#xff09; 目录 4 系统设计 4.1 系统概述 4.2系统结构 4.3.数据库设计 4.3.1数据库实体 4.3.2数据库设计表 5系统详细实现 5.1 管理员模块的实现 5.1.1 用户信息管理 …...

C++模拟实现list:list、list类的初始化和尾插、list的迭代器的基本实现、list的完整实现、测试、整个list类等的介绍

文章目录 前言一、list二、list类的初始化和尾插三、list的迭代器的基本实现四、list的完整实现五、测试六、整个list类总结 前言 C模拟实现list&#xff1a;list、list类的初始化和尾插、list的迭代器的基本实现、list的完整实现、测试、整个list类等的介绍 一、list list本…...

Offer60:n个骰子的点数

题目&#xff1a;把n个骰子扔在地上&#xff0c;所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。 分析:要解决这个问题&#xff0c;我们需要先统计出每个点数出现的次数&#xff0c;然后把每个点数出现的次数除以,就能求出每个点数出现的概率了。我们…...

几种常见的索引类型扫描

第一种&#xff1a;index unique scan 索引唯一扫描&#xff0c;当可以优化器发现某个查询条件可以利用到主键、唯一键、具有外键约束的列&#xff0c;或者只是访问其中某行索引所在的数据的时候&#xff0c;优化器会选择这种扫描类型。第二种&#xff1a;index range scan 索…...

苹果CMS插件:优化蜘蛛访问内容,提升百度收录率

确保蜘蛛抓取原始内容 专为苹果CMS设计的广告管理插件&#xff0c;能够智能识别搜索引擎蜘蛛与普通访客&#xff0c;确保蜘蛛访问时展示原始内容&#xff0c;从而提升被百度等搜索引擎收录的几率。 广告显示提升收益 对于普通访客&#xff0c;该插件则优先显示广告内容&#…...

后端开发刷题 | 没有重复项数字的全排列

描述 给出一组数字&#xff0c;返回该组数字的所有排列 例如&#xff1a; [1,2,3]的所有排列如下 [1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2], [3,2,1]. &#xff08;以数字在数组中的位置靠前为优先级&#xff0c;按字典序排列输出。&#xff09; 数据范围&#xff1a;数字…...

Python中的“打开与关闭文件”:从入门到精通

引言 在日常生活中&#xff0c;我们经常会遇到需要读取或保存信息的情况&#xff0c;比如记录笔记、保存配置信息或者处理大量的数据文件等。对于程序员来说&#xff0c;如何高效、安全地管理这些信息显得尤为重要。Python中的文件操作功能强大且易于使用&#xff0c;可以帮助…...

9.23 My_string.cpp

my_string.h #ifndef MY_STRING_H #define MY_STRING_H#include <iostream> #include <cstring>using namespace std;class My_string { private:char *ptr; //指向字符数组的指针int size; //字符串的最大容量int len; //字符串当前…...

【android10】【binder】【3.向servicemanager注册服务】

系列文章目录 可跳转到下面链接查看下表所有内容https://blog.csdn.net/handsomethefirst/article/details/138226266?spm1001.2014.3001.5501文章浏览阅读2次。系列文章大全https://blog.csdn.net/handsomethefirst/article/details/138226266?spm1001.2014.3001.5501 目录 …...

Java — LeetCode 面试经典150题(一)

双指针 125.验证回文串 题目 如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后&#xff0c;短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。 字母和数字都属于字母数字字符。 给你一个字符串 s&#xff0c;如果它是 回文串 &#xff0c;返回…...

Python酷玩之旅_mysql-connector

前言 Python作为数据科学、机器学习等领域的必选武器&#xff0c;备受各界人士的喜爱。当你面对不同类型、存储于各类介质的数据时&#xff0c;第一时间是不是要让它亮个相&#xff1f;做个统计&#xff0c;画个图表&#xff0c;搞个报表… 等等。 正如Java中的JdbcDriver一样…...

7.搭建个人金融数据库之快速获取股票列表和基本信息!

前边我们提过&#xff0c;免费的数据一般来自于爬虫&#xff0c;获取难度和维护成本都比较高&#xff0c;其实不太适合小白用户。所以非必要情况下&#xff0c;我们尽量不用这种方式来获取数据。 我自己用的比较多的是tushare&#xff0c;一般来说有它也就够了&#xff0c;大…...

Nginx基础详解1(单体部署与集群部署、负载均衡、正反代理、nginx安装)

本阶段的任务 1.学会集群的操作概念 2.完成对Nginx的入门操作 3.使用Nginx实现集群和负载均衡 4.使用Nginx实现高可用的方案 目录 1.单体部署与集群部署 1.1单体部署的概念 1.2单体部署的优缺点 1.3集群部署的概念 1.4集群部署的优缺点 1.5集群部署需要注意的点 1.…...

等保一体机如何帮你应对网络攻击

等保一体机如何帮你应对网络攻击 在当今信息化时代&#xff0c;网络安全已成为企业和组织面临的重要挑战。随着网络攻击手段的不断升级&#xff0c;传统的安全防护措施已难以应对复杂多变的威胁。等保一体机作为一种集成化的安全防护解决方案&#xff0c;能够有效帮助企业应对…...