说明:
总结到这里C++基础部分级面试题就完结了,后面前面几篇写的优点混乱,后面几篇都以理论为主。在后面会出一个牛客选择题的训练营以及力扣牛客的算法题 题目推荐,等博主刷完牛客C/C++的所有题目后把易错题目做一个整理。 下一次更新其他模块的面试题,嵌入式相关的C语言题目,驱动开发,应用编程,通信协议也会更新的。希望大家继续支持
答案:
C++11 引入了三种主要的智能指针类型:unique_ptr、shared_ptr 和 weak_ptr。
unique_ptr:
unique_ptr 是一种独占所有权的智能指针,它确保只有一个指针可以访问和管理所指向的对象。
unique_ptr 不能被拷贝,但可以通过移动语义进行转移所有权。
当 unique_ptr 超出作用域或被显式地释放时,它会自动删除所管理的对象。
unique_ptr 是最轻量级的智能指针,适用于需要独占所有权的情况。
原理: 简单粗暴的防拷贝,下面简化模拟实现了一份UniquePtr来了解它的原理
// 模拟实现一份简答的UniquePtr,了解原理
template<class T>
class UniquePtr
{
public:
UniquePtr(T * ptr = nullptr)
: _ptr(ptr)
{}
~UniquePtr()
{
if(_ptr)
delete _ptr;
}
T& operator*() {return *_ptr;}
T* operator->() {return _ptr;}
private:
// C++98防拷贝的方式:只声明不实现+声明成私有
UniquePtr(UniquePtr<T> const &);
UniquePtr & operator=(UniquePtr<T> const &);
// C++11防拷贝的方式:delete
UniquePtr(UniquePtr<T> const &) = delete;
UniquePtr & operator=(UniquePtr<T> const &) = delete;
private:
T * _ptr;
};
shared_ptr:
shared_ptr 是一种共享所有权的智能指针,它可以被多个指针共同管理同一个对象。
shared_ptr 使用引用计数来跟踪有多少个指针共享对象,当引用计数为零时,对象会被自动删除。
shared_ptr 可以被拷贝和赋值,每个拷贝都会增加引用计数。
shared_ptr 具有较大的开销,因为需要维护引用计数,但它提供了方便的共享对象所有权的方式。
原理: 是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。
// 模拟实现一份简答的SharedPtr,了解原理
#include
#include
template <class T>
class SharedPtr
{
public:
SharedPtr(T* ptr = nullptr)
: _ptr(ptr)
, _pRefCount(new int(1))
, _pMutex(new mutex)
{}
~SharedPtr() {Release();}
SharedPtr(const SharedPtr<T>& sp)
: _ptr(sp._ptr)
, _pRefCount(sp._pRefCount)
, _pMutex(sp._pMutex)
{
AddRefCount();
}
// sp1 = sp2
SharedPtr<T>& operator=(const SharedPtr<T>& sp)
{
//if (this != &sp)
if (_ptr != sp._ptr)
{
// 释放管理的旧资源
Release();
// 共享管理新对象的资源,并增加引用计数
_ptr = sp._ptr;
_pRefCount = sp._pRefCount;
_pMutex = sp._pMutex;
AddRefCount();
}
return *this;
}
T& operator*() {return *_ptr;}
T* operator->() {return _ptr;}
int UseCount() {return *_pRefCount;}
T* Get() { return _ptr; }
void AddRefCount()
{
// 加锁或者使用加1的原子操作
_pMutex->lock();
++(*_pRefCount);
_pMutex->unlock();
}
private:
void Release()
{
bool deleteflag = false;
// 引用计数减1,如果减到0,则释放资源
_pMutex.lock();
if (--(*_pRefCount) == 0)
{
delete _ptr;
delete _pRefCount;
deleteflag = true;
}
_pMutex.unlock();
if(deleteflag == true)
delete _pMutex;
}
private:
int* _pRefCount; // 引用计数
T* _ptr; // 指向管理资源的指针
mutex* _pMutex; // 互斥锁
};
int main()
{
SharedPtr<int> sp1(new int(10));
SharedPtr<int> sp2(sp1);
*sp2 = 20;
cout << sp1.UseCount() << endl;
cout << sp2.UseCount() << endl;
SharedPtr<int> sp3(new int(10));
sp2 = sp3;
cout << sp1.UseCount() << endl;
cout << sp2.UseCount() << endl;
cout << sp3.UseCount() << endl;
sp1 = sp3;
cout << sp1.UseCount() << endl;
cout << sp2.UseCount() << endl;
cout << sp3.UseCount() << endl;
return 0;
}
weak_ptr:
weak_ptr 是一种弱引用的智能指针,它指向 shared_ptr 管理的对象,但不增加引用计数。
weak_ptr 主要用于解决 shared_ptr 的循环引用问题,避免内存泄漏。
weak_ptr 可以通过 lock() 函数获取一个 shared_ptr 对象,如果对象仍然存在,则返回一个有效的 shared_ptr,否则返回一个空的shared_ptr。
template <typename T>
class weak_ptr
{
public:
weak_ptr() : ptr_(nullptr), ref_count_(nullptr) {}
weak_ptr(const shared_ptr<T>& shared) : ptr_(shared.ptr_), ref_count_(shared.ref_count_)
{
if (ref_count_)
{
ref_count_->weak_count_++;
}
}
~weak_ptr()
{
reset();
}
weak_ptr(const weak_ptr<T>& other) : ptr_(other.ptr_), ref_count_(other.ref_count_)
{
if (ref_count_)
{
ref_count_->weak_count_++;
}
}
weak_ptr<T>& operator=(const weak_ptr<T>& other)
{
if (this != &other)
{
reset();
ptr_ = other.ptr_;
ref_count_ = other.ref_count_;
if (ref_count_)
{
ref_count_->weak_count_++;
}
}
return *this;
}
void reset()
{
if (ref_count_)
{
ref_count_->weak_count_--;
if (ref_count_->weak_count_ == 0 && ref_count_->shared_count_ == 0)
{
delete ref_count_;
delete ptr_;
}
}
ptr_ = nullptr;
ref_count_ = nullptr;
}
shared_ptr<T> lock() const
{
if (expired())
{
return shared_ptr<T>();
}
return shared_ptr<T>(*this);
}
bool expired() const
{
return (ref_count_ == nullptr || ref_count_->shared_count_ == 0);
}
private:
T* ptr_;
shared_count* ref_count_;
};
注意: auto_ptr并不是C++11提出的,而是 C++ 98 中产生的第一个智能指针。已不建议使用。
答案:
1.vector数据结构 vector和数组类似,拥有一段连续的内存空间,并且起始地址不变。因此能高效的进行随机存取,时间复杂度为o(1);但因为内存空间是连续的,所以在进行插入和删除操作时,会造成内存块的拷贝,时间复杂度为o(n)。
另外,当数组中内存空间不够时,会重新申请一块内存空间并进行内存拷贝。连续存储结构:vector是可以实现动态增长的对象数组,支持对数组高效率的访问和在数组尾端的删除和插入操作,在中间和头部删除和插入相对不易,需要挪动大量的数据。
它与数组最大的区别就是vector不需程序员自己去考虑容量问题,库里面本身已经实现了容量的动态增长,而数组需要程序员手动写入扩容函数进形扩容。
2.list数据结构 list是由双向链表实现的,因此内存空间是不连续的。只能通过指针访问数据,所以list的随机存取非常没有效率,时间复杂度为o(n);但由于链表的特点,能高效地进行插入和删除。非连续存储结构:list是一个双链表结构,支持对链表的双向遍历。每个节点包括三个信息:元素本身,指向前一个元素的节点(prev)和指向下一个元素的节点(next)。因此list可以高效率的对数据元素任意位置进行访问和插入删除等操作。由于涉及对额外指针的维护,所以开销比较大。
区别:
1.vector的随机访问效率高,但在插入和删除时(不包括尾部)需要挪动数据,不易操作。
2.list的访问要遍历整个链表,它的随机访问效率低。但对数据的插入和删除操作等都比较方便,改变指针的指向即可。
3.从遍历上来说,list是单向的,vector是双向的。
4.vector中的迭代器在使用后就失效了,而list的迭代器在使用之后还可以继续使用。
5.list不提供随机访问,所以不能用下标直接访问到某个位置的元素,要访问list里的元素只能遍历,不过你要是只需要访问list的最后N个元素的话,可以用反向迭代器来遍历
答案:
由于vector的内存占用空间只增不减,比如你首先分配了10,000个字节,然后erase掉后面9,999个,留下一个有效元素,但是内存占用仍为10,000个。所有内存空间是在vector析构时候才能被系统回收。empty()用来检测容器是否为空的,clear()可以清空所有元素。但是即使clear(),vector所占用的内存空间依然如故,无法保证内存的回收。
如果需要空间动态缩小,可以考虑使用deque。
如果使用vector,可以用swap()来帮助你释放多余内存或者清空全部内存。
vector(Vec).swap(Vec); //将Vec中多余内存清除;
vector().swap(Vec); //清空Vec的全部内存;
答案:
1.用insert函数插入pair数据
mapStudent.insert(pair<int, string>(1, "student_one"));
2.用数组方式插入数据
mapStudent[1] = "student_one";
3.在insert函数中使用make_pair()函数
mapStudent.insert(make_pair(1, "student_one"));
4.用insert函数插入value_type数据
mapStudent.insert(map<int, string>::value_type (1, "student_one"));
答案:
答案:
答案:
首先整理一下虚函数表的特征:
根据以上特征,虚函数表类似于类中静态成员变量.静态成员变量也是全局共享,大小确定,因此最有可能存在全局数据区,测试结果显示:
虚函数表vtable在Linux/Unix中存放在可执行文件的只读数据段中(rodata),这与微软的编译器将虚函数表存放在常量段存在一些差别。
由于虚表指针vptr跟虚函数密不可分,对于有虚函数或者继承于拥有虚函数的基类,对该类进行实例化时,在构造函数执行时会对虚表指针进行初始化,并且存在对象内存布局的最前面。
一般分为五个区域:栈区、堆区、函数区(存放函数体等二进制代码)、全局静态区、常量区。
C++中虚函数表位于只读数据段(.rodata),也就是C++内存模型中的常量区;而虚函数则位于代码段(.text),也就是C++内存模型中的代码区。
答案:
答案:
答案:
类型 | 带参宏 | 带参函数 |
---|---|---|
处理时间 | 编译时 | 运行时 |
参数类型 | 无 | 需定义 |
程序长度 | 变长 | 不变 |
占用存储空间 | 否 | 是 |
运行时间 | 不占运行时间 | 调用和返回占 |