提示:文章
文章目录
- 前言
- 一、背景
- 标准库
- 基础知识
- 堆栈
- 总结
前言
前期疑问:
本文目标:
一、背景
接上文
标准库
1、(单选)【STL】在以下容器中间插入一个元素,时间复杂度为O(1)的是(A)
A. list
B. vector
C. deque
D. string
考察STL
vector的访问是O(1),但在中间增删是O(n),因为会移动其他元素
list在内存中不是连续分配,在任何位置增删是常数时间
2、(单选)【STL】下面哪个容器内的数据是有序的(C)
A. vector
B. list
C. set
D. unordered_set
map和set内部实现是红黑树
list是链表
unorded_set和unorded_map是哈希表
3、(单选)【STL】关于stl库的说法错误的是 (B)
A.set是一种关联容器,内部采用红黑树的平衡二叉树存储元素
B.set每个元素的值唯一,可以直接修改set元素值,只要确保唯一性
C.set不支持随机存取
D.set适用于经常查找存储元素的场景
不能直接修改 set 容器中元素的值。因为元素被修改后,容器并不会自动重新调整顺序,于是容器的有序性就会被破坏,再在其上进行查找等操作就会得到错误的结果。因此,如果要修改 set 容器中某个元素的值,正确的做法是先删除该元素,再插入新元素。
4、(单选)【vector】关于vector使用效率提升不正确的一项©
A. emplace_back比push_back效率更高
B. emplace_back比insert效率更高
C. a.at(i)比a[i]优化
解释:at需要判断是否存在该元素,开销更大
但是实际上at更安全,这里就当是效率差吧
容器中operator[]不进行下标越界检查,不过使用operator[]就要自己承担越界风险了。如果需要下标越界检查,请使用at。直接使用下标的效率更高。
5、(单选)【vector】如下哪项不能使std::vector的性能提升(C)
A.用emplace_back()替换 push_back()
B.根据预知的长度,提前reserve内存
C.用At替换[]
D.用emplace()替换insert()
6、(单选)【vector】关于vector, 说法错误的是:(D)
A. vector适合的场景:经常随机访问其中存储的元素,但很少对中间或开始元素进行增删。
B. vector对中间和开始处进行元素的添加或删除操作,需要移动内存。
C. vector容器默认在堆中分配内存空间,元素连续存放。
D. 访问任何元素的时间复杂度是O(n).
解:vector数据的访问效率是常数级的O(1)
增删因为需要移动其他元素才是O(n)
7、(单选)【vector】对于std::vector类型的对象myVector,下面哪种方法可以正确获取myVector中所含元素的个数(D)
A. sizeof(myVector)
B. sizeof(myVector)/sizeof(int)
C. myVector.capacity()
D. myVector.size()
vector有三个指针,内容在堆上
sizeof是一个固定的值,即指针的大小
C是myVector的容量,是预先分配的
其中vector有三个指针是什么意思
vector有三个指针是指vector容器在底层实现中使用了三个关键的指针来管理其内存和元素。这三个指针分别是:
- start:指向vector容器中第一个元素的地址。
- finish:指向vector容器中最后一个元素之后的位置。
- end_of_storage:指向vector容器当前分配的内存空间的末尾12。
通过这三个指针,vector实现了许多功能,如存储元素大小、剩余空间大小、总容器空间大小等。这些指针帮助vector进行动态内存管理和元素访问
8、(单选)【vector】以下代码的输出是什么(B)
void main(){
vector<int> a(2, 3);
for(auto v : a){
cout<<v<<” ”;
A.2 2 2
B.3 3
C.2 3
D.3 2
解析:考察vector初始化方式,小括号代表两个三
9、(单选)【vector】关于vector容器类中的begin()和end()成员函数,下面说法错误的是(A)
A. end()返回一个迭代器,在容器不为空的情况下指向容器的最后一个元素
B. begin()返回一个迭代器,指向容器的起始点
C. 元素个数为0的时候,begin()和end()相等
D. end()为容器遍历提供了判断依据,只要尚未遍历到end(),遍历循环就可以继续
end指向最后一个元素的下一位
10、(多选)【vector】假如有std::vector类型的容器coll,需要对其元素从大到小排列,下列写法正确的(ABCD)
A.
std::sort(coll.begin(), coll.end(), std::greater<int>());
B.
std::sort(coll.begin(), coll.end(), [](int a, int b) { return a >b; });
C.
struct Cmp {
bool operator() (int a, int b) const {
return a > b;
}
};
std::sort(coll.begin(), coll.end(), Cmp);
D.
bool Cmp(int a, int b)
{
return a > b;
}
std::sort(coll.begin(), coll.end(), Cmp);
11、(多选)【vector】遍历vector的方式有哪些()
A.
vector< int > ivec(begin(arr),end(arr));
for(auto it=ivec.begin();it!= ivec.end();++it)
B.
vector< int > ivec(begin(arr),end(arr));
vector<int>::iterator it;
for(it=ivec.begin();it!= ivec.end();++it)
C.
vector< int > ivec(begin(arr),end(arr));
for(decltype(ivec.size()) i=0;i<4;++i)
解释:通过代码验证如下
int main()
{
std::vector<int> coll{2, 5, 3, 9, 12, 6};
// std::sort(coll.begin(), coll.end(), std::greater<int>()); // greater降序
// std::sort(coll.begin(), coll.end(), std::less<int>()); // less 升序
// std::sort(coll.begin(), coll.end(), [](int a, int b) { return a > b; }); // 降序
struct Cmp {
bool operator()(int a, int b) const
{
return a > b;
}
};
// std::sort(coll.begin(), coll.end(), Cmp()); // 降序
std::sort(coll.begin(), coll.end(), Cmp2); // 降序
for (int i = 0; i < coll.size(); i++) {
printf("%d ", coll[i]);
}
}
12、(单选)【STL】用有序的双向链表存储一个支持“增”、“删”、“改”三个功能的键值系统,这三个功能中有几个功能能做到时间复杂度O(1)?(A)
A 3
B 2
C 1
D 0
“删”如果按照键值删除可以做到时间复杂度O(1),而“增”、“改”都需要查找,查找时间复杂度为O(n)。
13、(多选)【string】下面可以正确获得string变量str的字符串长度的是(AD)
A. str.size()
B. str.capacity()
C. sizeof(str)
D. str.length()
E. sizeof(str) – 1
解释:
Sizeof返回的是指针大小
size()和length():这两个函数会返回string类型对象的字符个数,且他们的执行效果相同
capacity():这个函数返回在重新分配内存之前,string类型对象所能包含的最大字符数。
14、(多选)【set】set中肯定有元素3,肯定没有元素4,则下列中能把元素3改成4的操作是(AC)
A.
setTest.erase(3);
setTest.insert(4);
B.
auto it = setTest.find(3);
(int)(*it) = 4;
C.
auto it = setTest.find(3);
it = setTest.erase(it);
setTest.insert(it, 4);
D.
auto it = setTest.emplace(3);
it.second = 4;
erase入参可以是值也可以是位置指针
set的修改是先删后插
B不能直接改set
D emplace和find类似 没有删除3
15、(单选)【set】以下C++17代码中,自定义类型Order将作为std::set的key值,下面正确实现重载运算符operator的代码是(A)
struct Order {
int order;
public:
Order(int o) : order(o) {}
// 选项中的代码将会插入这个位置
};
int main()
{
std::set<Order> s;
s.insert(Order(5));
s.insert(Order(6));
...
return 0;
}
A.
bool operator<(const Order &rhs) const
{
return (order < rhs.order);
}
B.
bool operator<=(const Order &rhs) const
{
return (order <= rhs.order);
}
C.
bool operator>(const Order &rhs) const
{
return (order > rhs.order);
}
D.
bool operator>=(const Order &rhs) const
{
return (order >= rhs.order);
}
解释:
写了代码验证了下
struct Order {
int order;
public:
Order(int o) : order(o)
{}
// 选项中的代码将会插入这个位置
bool operator<(const Order &rhs) const
{
return (order < rhs.order);
}
};
int main()
{
std::set<Order> s;
s.insert(Order(5));
s.insert(Order(6));
s.insert(Order(2));
for (std::set<Order>::iterator it = s.begin(); it != s.end(); it++) {
printf("%d ", (*it).order);
}
return 0;
}
其中重载运算符函数bool operator<(const Order &rhs) const改成bool operator>(const Order &rhs) const都会报错
这个函数是什么意思呢?查了copilot得到下述解释:
bool operator<(const Order &rhs) const
函数是一个运算符重载函数,用于定义 Order
对象之间的“小于”关系。在这个函数中,rhs
是右操作数,this
是左操作数。函数返回一个布尔值,表示当前对象是否小于 rhs
对象。
在这个具体的例子中,bool operator<(const Order &rhs) const
函数比较两个 Order
对象的 order
成员变量。如果当前对象的 order
小于 rhs
对象的 order
,则返回 true
,否则返回 false
。
这个运算符重载函数在使用 std::set<Order>
时非常重要,因为 std::set
是一个有序容器,需要知道如何比较元素以保持其有序性。通过定义这个运算符重载函数,std::set
可以正确地比较和排序 Order
对象。
16、(多选)【set】set的说法正确的是(AC)
A.set的数据是有序的
B.set的数据是可重复的
C.set存储元素为结构体时,必须实现相应的比较函数
D.set内部实现的数据结构是Hash
E.set存储的key值可以修改
注:set是有序不重复的,数据结构是红黑树。Map也是
17、(单选)【map】下面输出结果正确的是(B)
int main() {
map<string ,int> mp;
mp.insert({"abc",3});
mp.insert({"def",1});
mp.insert({"abcdef",2});
for(auto iter = mp.begin();iter !=mp.end(); iter++){
cout<< iter->second<<endl;
}
return 0;
}
A 3 1 2
B 3 2 1
C 2 3 1
D 2 1 3
map本身有序,这里按字符串升序,abc < abcdef < def,因此输出3 2 1
18、(多选)【map】下列代码中,删除list中某元素的操作正确的是(AD)(没搞懂)
list mapContainer;
A .
for (auto beg = _mapContainer.begin(); beg != _mapContainer.end();) {
if (needToDelete(beg->first)) {
beg = _mapContainer.erase(beg);
} else {
++beg;
}
}
B.
for (auto beg = _mapContainer.begin(); beg != _mapContainer.end(); ++beg) {
if (needToDelete(beg->first)) {
beg = _mapContainer.erase(beg);
}
}
C.
for (auto beg = _mapContainer.begin(); beg != _mapContainer.end();) {
if (needToDelete(beg->first)) {
_mapContainer.erase(++beg);
} else {
++beg;
}
}
D.
for (auto beg = _mapContainer.begin(); beg != _mapContainer.end();) {
if (needToDelete(beg->first)) {
_mapContainer.erase(beg++);
} else {
++beg;
}
}
erase会返回下一个元素的位置
参考文章:C++之迭代器失效总结
19、(多选)【STL】以下关于STL库sort算法描述正确的是(ABD)
A. vector和deque的迭代器属于Random Access Iterator,适合使用sort算法
B. STL中的set和map自动排序功能,不能用sort算法
C. 对stack、queue和priority-queue容器,可以通过sort算法对元素进行排序
D. list迭代器属于Bidrectional Iterator,不适用于sort算法
关于D答案:
你说得对!std::list
的迭代器属于双向迭代器(Bidirectional Iterator),而 std::sort
算法要求使用随机访问迭代器(Random Access Iterator)。因此,不能直接对 std::list
使用 std::sort
。
如果你需要对 std::list
进行排序,可以使用 std::list
自带的 sort
成员函数。例如:
cpp
std::list<int> myList = {3, 1, 4, 1, 5, 9};
myList.sort();
这样就可以对 std::list
进行排序了
20、(多选)【STL】以下语句能够实现正确排序的有(AC)
A.
std::vector<int> coll;
std::sort(coll.begin(), coll.end());
B.
std::list<int> coll;
std::sort(coll.begin(), coll.end());
C.
std::deque<int> coll;
std::sort(coll.begin(), coll.end());
D.
std::queue<int> coll;
std::sort(coll.begin(), coll.end());
21、(单选)【迭代器】以下哪个选项可以表示迭代器iter当前指向的位置(D)
A. cout<<*iter;
B. cout<<(void*)iter
C. cout<<(int)iter
D. cout<<(iter - vec.begin())
奇奇怪怪的题目,写了下面的验证代码
int main()
{
std::vector<int> vec{1,4,67,2,9};
std::vector<int>::iterator iter = vec.end();
cout << (iter - vec.begin());
}
大概意思就是确定迭代器现在在什么位置,通过和begin进行比较做差值可以得到迭代器iter距离begin的距离。
22、(多选)【迭代器】如下哪些场景可能会导致指向该容器中元素的迭代器失效(不再指向原来的元素)(BCE)
A. std::map的insert操作
B. std::vector的push_back操作
C. std::vector的erase操作
D. std::list的insert操作
E. std::deque的insert操作
解释:
vector和deque是序列式容器,进行元素的增删操作之后,迭代器失效;
对于关联式容器(map、list),删除当前的iterator只会使当前迭代失效,在erase时递增当前的iterator即可。
参考文章:C++之迭代器失效总结
这个题目和第18题都是比较懵的,上面这篇文章表述的比较清楚些。
基础知识
(字符串,函数指针,函数参数,变量生命周期与存储,类型别名,异常,作用域,数组指针引用,编译,const,mutable等)
堆栈
1、(单选)【存储】下面哪种变量定义不当,可能导致堆栈溢出(D)
A. 静态全局变量
B. 动态全局变量
C. 静态局部变量
D. 动态局部变量
2、(多选)【存储】栈一般用来存储什么(ABC)
A. 局部变量
B. 函数参数
C. 函数的返回值位置
D. const非0
栈区存放用于函数的返回地址、形参、局部变量、返回类型
Const比较特殊,放在只读数据区
3、(多选)【存储】关于定义过大的局部变量的描述正确的有哪些(BC)
A. 无不好的影响,使用简单,应该大量使用
B. 会导致内存镜像变大
C. 会导致使用栈空间变大
D. 会导致堆内存使用增加
至于为什么会导致内存镜像变大,这是因为局部变量在程序运行时会占用内存空间。当局部变量过大时,内存中需要存储的内容也会相应增多,从而导致内存镜像变大。内存镜像是程序在运行时内存中的数据结构和内容的一个副本,用于调试和分析程序。通过内存镜像,开发人员可以查看和检查程序的内存状态,帮助理解程序的行为和性能,并找出潜在的问题。
4、(多选)【存储】以下可能会导致栈溢出的是(AB)
A. char test[102410241024]
B. std::array(int, 102410241024);
C. std::vector(102410241024, 1);
D. std::string(102410241024, 1);
vector和string是在堆上申请内存的。
5、(多选)【存储】C++语言程序编译完成之后,关于bss、data数据段存放内容说法正确的有(ABD)
A. 初始化为0的全局变量一般存放在bss段
B. 初始化过(非零)的非const的全局变量一般保存在data段
C. 初始化过(非零)的const的全局变量一般保存在bss段
D. 未初始化的全局变量一般存放在bss段
BSS段(Block Started by Symbol segment):初始化为0或者未初始化的全局变量和静态变量保存在bss段。
Data段:已初始化的全局变量和静态变量以及字符串常量保存在data段
6、 (多选)【存储】下列代码都是函数内部声明的局部变量,其中可能会导致栈空间不足的有[x(1]
A. int var[1024 * 1024];
B. std::array<int, 1024 * 1024>var;
C. std::vector var(1024 * 1024);
D. std::pair<int, int[1024 * 1024]> var;
E. std::string var(1024 * 1024, ‘c’);
array和数组是在栈上的,pair是最简单的组合,成员是啥就是啥,vector在栈中保存指针,在堆中保存实际东西,string也是保存指针,具体实现参见stl
7、(单选)【存储】已知如上类声明,以下instance函数的实现中,会导致未定义行为的是(A)
A
MyClass* MyClass::Instance()
{
MyClass Instance;
return &Instance;
}
B
MyClass* MyClass::Instance()
{
static MyClass* Instance = new MyClass;
return Instance;
}
C
MyClass* MyClass::Instance()
{
static MyClass Instance;
return &Instance;
}
D
MyClass* g_instance = nullptr;
MyClass* MyClass::Instance()
{
if (g_instance == nullptr){
g_instance = new MyClass;
}
return g_instance;
}
A的局部变量返回时被销毁
考察static关键字
定义全局变量,同时检查全局变量是否为空
8、(多选)【存储】如下代码功能正确的是(AC)
A、
const char* GetString()
{
const char* buff = “Test Buff”;
return buff;
}
B、
char* GetString()
{
char buff[] = “Test Buff”;
return buff;
}
C、
char* GetString()
{
static char buff[] = “Test Buff”;
return buff;
}
D、
std::string& GetString()
{
std::string buff(“Test Buff”);
return buff;
}
这题因为返回的都是指针或者引用,所以必须找静态变量或者堆区变量。
A const char*可以这么用(必须带const),相当于指向一个静态字面常量,返回指针后不会被销毁。
B 只是用字面值给数组初始化,仍然是局部变量。D同理。
C是静态变量,也不会被销毁。
Ok:
Const char *
Char *
Static char buff[]
Not ok:
Char buff[]
Const char buff[]
Why:
字符串字面值常量具有静态存储期
9、(多选)【存储】忽略编码规范,能输出预期结果的是(ABC)
A.
const char* GetStrName()
{
const char* data = “Hello World!”;
return data;
}
B.
char* GetStrName()
{
char* data = “Hello World!”; //常量指针
return data;
}
C.
char* GetStrName()
{
static char data[] = “Hello World!”;
return data;
}
D.
string& GetStrName()
{
string data = “Hello World!”;
return data;
}
A存在常量区,返回后不会被销毁
B也存在常量区,返回后不会被销毁,需要注意的是右边是const型,所以编译器可能会报warning
C是静态变量,返回后不会被销毁
D是局部变量,无法返回其引用
这个题目还涉及一个知识点,就是常量指针是什么。查了资料,有指向常量的指针和常量指针两个概念。看了资料也还是比较绕。。
常量指针(constant pointer)是指向常量的指针,另一个概念是指针常量
-
常量指针指向常量的指针:这种指针指向的值是常量,不能通过该指针修改指向的值,但指针本身可以改变指向的地址。例如:
const int *ptr; int a = 10; int b = 20; ptr = &a; // 合法 *ptr = 15; // 非法,不能修改指向的值 ptr = &b; // 合法,可以改变指针指向的地址
-
指针常量:这种指针本身是常量,不能改变指针的指向,但可以通过该指针修改指向的值。例如:
int *const ptr; int a = 10; int b = 20; ptr = &a; // 合法 *ptr = 15; // 合法,可以修改指向的值 ptr = &b; // 非法,不能改变指针指向的地址
针对B选项,c++11没有报错,只是警告【ISO C++11 does not allow conversion from string literal to 'char * '】,实际可以正常返回字符串。验证代码如下:
char* GetStrName()
{
char* data = "Hello World!"; //常量指针
return data;
}
char* GetStrName2()
{
char data[] = "Hello World!";
return data;
}
int main()
{
char* str = GetStrName();
char* str2 = GetStrName2();
printf("%s\n", str);
printf("%s\n", str2);
}
// 打印信息
// Hello World!
// (null)
至于上面的警告,查看之后解释如下:
在C++11标准中,字符串字面量(如"hello world")通常被视为const char[]
类型,即不可变的字符数组。直接将字符串字面量赋值给char*
类型是不允许的,因为这样的操作会尝试去掉字符串字面量的常量性,这在C++中是不允许的。
如果你只需要读取字符串,可以使用const char* 来指向字符串字面量。
总结
未完待续