C++说爱你不容易-6

C++软件与嵌入式软件面经解析大全(蒋豆芽的秋招打怪之旅)


本章讲解点

  • 1.1 C++与C的区别——看看你的理解是否深刻
  • 1.2 从代码到可执行文件的过程
  • 1.3 extern "C"
  • 1.4 宏——到底是什么
  • 1.5 内联函数
  • 1.6 条件编译
  • 1.7 字节对齐详解
  • 1.8 Const——今天必须把它搞懂
  • 1.9 Static作用
  • 1.10 volatile和mutable
  • 1.11 volatile在嵌入式里的应用
  • 1.12 原子操作
  • 1.13 指针与引用的区别
  • 1.14 右值引用
  • 1.15 面向对象的编程思想
  • 1.16 类
  • 1.17 类的成员
  • 1.18 友元函数
  • 1.19 初始化列表
  • 1.20 this指针
  • 1.21 继承
  • 1.22 多态
  • 1.23 虚函数与重写
  • 1.24 虚构造函数与虚析构函数
  • 1.25 函数重载
  • 1.26 操作符重载
  • 1.27 迭代器与指针
  • 1.28 模板
  • 1.29 C++智能指针
  • 1.30 四种cast转换
  • 1.31 Lambda
  • 1.32 function和bind

受众:本教程适合于**C/C++**已经入门的学生或人士,有一定的编程基础。

本教程适合于互联网嵌入式软件求职的学生或人士。

img

故事背景

img

**蒋 豆 芽:**小名豆芽,芳龄十八,蜀中人氏。卑微小硕一枚,科研领域苟延残喘,研究的是如何炒好一盘豆芽。与大多数人一样,学习道路永无止境,间歇性踌躇满志,持续性混吃等死。会点编程,对了,是面对对象的那种。不知不觉研二到找工作的时候了,同时还在忙论文,豆芽都秃了,不过豆芽也没头发啊。

**隔壁老李:**大名老李,蒋豆芽的好朋友,技术高手,代码女神。给了蒋豆芽不少的人生指导意见。

**导 师:**蒋豆芽的老板,研究的课题是每天对豆芽嘘寒问暖。

img

故事引入

img

导 师:豆芽,论文要继续加油,知道吗?

蒋 豆 芽:好的!(豆芽脸上笑嘻嘻)


隔壁老李:豆芽,最近怎么没面试啊?我看你挺闲的啊。

蒋 豆 芽:(疑惑)嗯?老李,我哪里闲了啊,忙着复习。

隔壁老李:(笑容邪魅)豆芽,怎么样?大公司投了没?你不是想去大厂吗?

蒋 豆 芽:(支支吾吾)没诶,感觉没复习好,不敢投。

隔壁老李:(安慰)豆芽,你不要有畏难心理。你一定要明白,机遇不会等我们准备好了才来,往往是我们还没准备好就来了,或者准备的过程中就来了,不管有没有准备好,我们都得硬着头皮冲。

说来也很有意思,最近看了部电影叫面对巨人,里面我印象最深刻的就是教练说:你只管尽全力去做,剩下的交给上帝评判。

经历过秋招的人都深有感触,找工作除了实力以外,也有玄学的味道在里面,当然什么意思,你自己去经历就知道了。总而言之,投简历,赶紧投,提前批也不要错过!任何机会不要错过,然后就是好好准备,尽全力准备!剩下的交给天命,而努力的人运气都不会太差哦!

蒋 豆 芽:(疑惑)真的吗?

隔壁老李:(肯定)嗯嗯。


老李苦口婆心,豆芽还能说什么呢?那就投吧,豆芽所有大厂都投了。紧接着豆芽就收到了腾慢公司的面试邀请了。。。。。。


隔壁老李:怎么样啊?豆芽,昨天的面试可还行?

蒋 豆 芽:原来大公司的面试也不难嘛!都是基础知识啊!之前可是吓死我了。我感觉自己回答的不错,但是不知道为什么依然没过。

隔壁老李:(笑容邪魅)所以我才说面试偶尔有点“玄学”的味道,哈哈。

蒋 豆 芽:原来是这么个意思,好吧。不过已经结束了,今天我又收到了腾慢另一个部门的面试邀请,依然要好好准备,说不定就真的过了呢?哈哈。

不过我得复盘一下了。昨天的面试有三个问题我印象挺深刻的。

隔壁老李:哦?是哪些问题呢?

img

1.27 迭代器和指针

img

蒋 豆 芽:迭代器和指针有什么区别?

隔壁老李:这个豆芽你应该很清楚的,你再来回顾一下吧。

蒋 豆 芽:好,没问题!指针我们之前已经学过了,所以从迭代器说起。

迭代器是一个变量,从属于STL六大成分之一,这里我们简单介绍下STL,STL 是“Standard Template Library”的缩写,中文译为“标准模板库”。STL 是 C++ 标准库的一部分,不用单独安装。C++ 对模板(Template)支持得很好,STL 就是借助模板把常用的数据结构及其算法都实现了一遍,并且做到了数据结构和算法的分离。例如,vector 的底层为顺序表(数组),list 的底层为双向链表,deque 的底层为双端队列,set 的底层为红黑树,hash_set 的底层为哈希表。

通常认为,STL 是由容器算法迭代器函数对象适配器内存分配器这 6 部分构成,其中后面 4 部分是为前 2 部分服务的,它们各自的含义如表所示。

STL的组成 含义
容器 一些封装数据结构的模板类,例如 vector 向量容器、list 列表容器等。
算法 STL 提供了非常多(大约 100 个)的数据结构算法,它们都被设计成一个个的模板函数,这些算法在 std 命名空间中定义,其中大部分算法都包含在头文件 中,少部分位于头文件 中。
迭代器 在 C++ STL 中,对容器中数据的读和写,是通过迭代器完成的,迭代器就是容器和算法之间的桥梁。
函数对象 如果一个类将 () 运算符重载为成员函数,这个类就称为函数对象类,这个类的对象就是函数对象(又称仿函数)。
适配器 可以使一个类的接口(模板的参数)适配成用户指定的形式,从而让原本不能在一起工作的两个类工作在一起。值得一提的是,容器、迭代器和函数都有适配器。
内存分配器 为容器类模板提供自定义的内存申请和释放功能,由于往往只有高级用户才有改变内存分配策略的需求,因此内存分配器对于一般用户来说,并不常用。

蒋 豆 芽:好了,我们继续讲迭代器。迭代器相当于容器和操纵容器的算法之间的中介。迭代器可以指向容器中的某个元素,通过迭代器就可以读写它指向的元素。从这一点上看,迭代器和指针类似。迭代器按照定义方式分成以下四种:

(1) 正向迭代器:容器类名::iterator 迭代器名;

(2) 常量正向迭代器:容器类名::const_iterator 迭代器名;

(3) 反向迭代器:容器类名::reverse_iterator 迭代器名;

(4) 常量反向迭代器:容器类名::const_reverse_iterator 迭代器名;

隔壁老李:嗯,说得很好,那豆芽,迭代器是怎么用的呢?

蒋 豆 芽:通过迭代器可以读取它指向的元素,*迭代器名就表示迭代器指向的元素。

通过非常量迭代器还能修改其指向的元素。迭代器都可以进行++操作。

反向迭代器和正向迭代器的区别在于:

(1)对正向迭代器进行++操作时,迭代器会指向容器中的后一个元素;

(2)而对反向迭代器进行++操作时,迭代器会指向容器中的前一个元素。

我们举个例子说明:

#include <iostream>  
#include <vector>  
using namespace std;  
int main(){  
    vector<int> v;  //v是存放int类型变量的可变长数组,开始时没有元素  
    for (int n = 0; n<5; ++n)  
        v.push_back(n);  //push_back成员函数在vector容器尾部添加一个元素  
    vector<int>::iterator i;  //定义正向迭代器  
    for (i = v.begin(); i != v.end(); ++i) { 
    //用迭代器遍历容器,begin 成员函数返回指向容器中第一个元素的迭代器。++i 使得 i 指向容器中的下一个元素。end 成员函数返回的不是指向最后一个元素的迭代器,而是指向最后一个元素后面的位置的迭代器,因此循环的终止条件是i != v.end()  
        cout << *i << " ";  //*i 就是迭代器i指向的元素  
        *i *= 2;  //每个元素变为原来的2倍  
    }  
    cout << endl;  
    //用反向迭代器遍历容器  
    for (vector<int>::reverse_iterator j = v.rbegin(); j != v.rend(); ++j)  
        cout << *j << " ";  
    return 0;  
}  

运行结果如下:

0 1 2 3 4
8 6 4 2 0

而这里要特别注意,用迭代器遍历容器,begin 成员函数返回指向容器中第一个元素的迭代器。++i 使得 i 指向容器中的下一个元素。

end 成员函数返回的不是指向最后一个元素的迭代器,而是指向最后一个元素后面的位置的迭代器,因此循环的终止条件是i != v.end()

蒋 豆 芽:当然,定义迭代器类型我们总是嫌太麻烦,我们可以用auto关键字来定义,如:

for (auto i = v.begin(); i != v.end(); ++i)//定义正向迭代器  
    *i *= 2;    
for (auto j = v.rbegin(); j != v.rend(); ++j)//定义反向迭代器  
    cout << *j << " ";    

auto关键字实在是太省心了,它能在变量声明时根据初始化表达式自动推断该变量的类型。适用于类型冗长复杂模板类型等。建议可以多多使用。

隔壁老李:(会心一笑)不错啊,豆芽,门儿清啊。那我问问你,下面代码有什么问题?

vector<int> vec;  
vec.push_back(l);  
vec.push_back(2);  
vec.push_back(3);  
vec.push_back(4);  
vec.push_back(5);  
for(vector<int>::iterator iter=vec.begin(); iter!=vec.end(); ++iter){  
    if(*iter == 3)  
        vec.erase(iter);  
}  

蒋 豆 芽:哼,怎么难得到我?乍一看这段代码很正确,但这里面隐藏着一个很严重的错误:当 vec.erase(iter) 语句执行后, iter 就变成了一个野指针,对一个野指针进行 ++iter 操作是肯定会出错的。

隔壁老李:iter 为什么就变成了一个野指针呢?

蒋 豆 芽:这是因为vector的数据结构依然是采用的数组。数组在删除元素时,后面的元素都会往前移动,自然原有的迭代器就失效了啊。

img

当移动完成后,4、5元素将获得新的迭代器。而erase将返回下一个元素的迭代器。

隔壁老李:你说得对,那我这样改进总可以了吧:

for(vector<int>::iterator iter=vec.begin(); iter!=vec.end(); ++iter){  
    if(*iter == 3)  
        iter = vec.erase(iter);  
}  

蒋 豆 芽:这样是解决了野指针的问题,但是无法删除两个连续的3。因为当iter获得下一个3的迭代器后,又经过了++iter操作,跳过了3。正确的解决方法应该如下:

vector<int>::iterator iter=vec.begin();  
for(; iter!=vec.end();){  
    if(*iter == 3)  
        iter = vec.erase(iter);  
    else  
        ++iter;  
}  

隔壁老李:bingo!豆芽你现在成长的很快啊。那我问你,vector的迭代器删除和map,set的迭代器删除、list的迭代器删除又有什么区别呢?

蒋 豆 芽:刚才我们讲了vector的迭代器删除的问题。而map,set则不一样,map,set的数据结构采用的红黑树,删除当前元素时,不会影响到下一个元素的迭代器,所以在调用erase之前,记录下一个元素的迭代器即可。

而对于list来说,它的数据结构是链表,使用了不连续分配的内存,并且它的erase方法也会返回下一个有效的迭代器,因此两种方式都可采用。

隔壁老李:嗯嗯,不错,豆芽,我认为你讲得很清楚了。我们回到我们原本的问题:迭代器和指针有什么区别

蒋 豆 芽:迭代器不是指针,是类模板,表现的像指针。它只是模拟了指针的一些功能,通过重载了指针的一些操作符,如-->*++--等。

迭代器封装了指针,是一个“可遍历STL容器内全部或部分元素”的对象,本质是封装了原生指针,是指针概念的一种提升,相当于智能指针。而迭代器的访问方式就是把不同集合类的访问逻辑抽象出来,使得不用暴露集合内部的结构而达到循环遍历集合的效果。这就是迭代器产生的原因。

隔壁老李:简直刮目相看了呀,不得了。哈哈。

蒋 豆 芽:(害羞)过奖了,使不得,使不得。对了,还要补充一下,前置 ++i 与后置 i++ 的区别

我们先说C语言里,

先看到代码:

#include <stdio.h>
int main(){
   	int i = 2;
	int j = 2;
	j += i++; //先赋值后加
	printf("i= %d, j= %d\n",i, j); //i= 3, j= 4
	i = 2;
	j = 2;
	j += ++i; //先加后赋值
	printf("i= %d, j= %d",i, j); //i= 3, j= 5
}
  1. 赋值顺序不同:++ i 是先加后赋值;i ++ 是先赋值后加;++i和i++都是分两步完成的。

  2. 效率不同:后置++执行速度比前置的慢。

  3. i++ 不能作为左值,而++i 可以

    int i = 0;
    int *p1 = &(++i);//正确
    int *p2 = &(i++);//错误
    ++i = 1;//正确
    i++ = 1;//错误
    
  4. 两者都不是原子操作。

这里我们要说一句,C语言是汇编层面的实现,后置++的汇编代码比前置++多了一行,那么执行就会多花一点时间。但是随着编译器的不断发展,这样的区别已经微乎其微了。

蒋 豆 芽:但是迭代器前置 ++i 与后置 i++ 的效率就有区别了。

我们来看看两者的实现:通过操作符重载实现。

CTest CTest::operator++(){  //前置++  
    *this += 1;  
    return *this;  
}  
CTest CTest::operator++(int){  //后置++  
    CTest tmp(*this);  //记录修改前的对象  
    ++(*this);  
    return tmp;  //返回修改前的对象  
}  

后置++要多生成一个局部对象 tmp,这个对象有可能包含很多的成员,因此执行速度比前置的慢。在次数很多的循环中,++i和i++可能就会造成运行时间上可观的差别了。因此,特别提到,对循环控制变量i,要养成写++i、不写i++的习惯。

另外,我们之前讲过原子操作,从++i和i++的实现就可以看出,两者均不是原子操作,这一点要特别注意。

img

1.28 模板

img

隔壁老李:刚才我们提到了模板,豆芽你来讲讲吧。

蒋 豆 芽:没问题!有时两个或多个类的功能是相同的,但仅仅因为数据类型不同,就要分别定义多个类,如下:

//交换 int 变量的值
void Swap(int *a, int *b){
    int temp = *a;
    *a = *b;
    *b = temp;
}

//交换 float 变量的值
void Swap(float *a, float *b){
    float temp = *a;
    *a = *b;
    *b = temp;
}

//交换 char 变量的值
void Swap(char *a, char *b){
    char temp = *a;
    *a = *b;
    *b = temp;
}

//交换 bool 变量的值
void Swap(bool *a, bool *b){
    char temp = *a;
    *a = *b;
    *b = temp;
}

这些函数虽然在调用时方便了一些,但从本质上说还是定义了四个功能相同、函数体相同的函数,只是数据的类型不同而已,这看起来有点浪费代码,能不能把它们压缩成一个函数呢?

能!可以借助函数模板

template<typename T> void Swap(T *a, T *b){
    T temp = *a;
    *a = *b;
    *b = temp;
}

//交换 int 变量的值
int n1 = 

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

<p> - 本专刊适合于C/C++已经入门的学生或人士,有一定的编程基础。 - 本专刊适合于互联网C++软件开发、嵌入式软件求职的学生或人士。 - 本专刊囊括了C语言、C++、操作系统、计算机网络、嵌入式、算法与数据结构等一系列知识点的讲解,并且最后总结出了高频面试考点(附有答案)共近400道,知识点讲解全面。不仅如此,教程还讲解了简历制作、笔试面试准备、面试技巧等内容。 </p> <p> <br /> </p>

全部评论
mysharedptr最后运行结果的最后一行 Destructor is succeed! 貌似应该不会出现?
点赞 回复 分享
发布于 2021-07-22 11:24
析构函数里面的第29行 delete[] m_ptr;  可以换成 delete m_ptr;  吗?
点赞 回复 分享
发布于 2021-09-13 20:34
在介绍 unique_ptr时,我们只需要将拷贝构造函数和赋值拷贝构造函数申明为private或delete。我想请教一下为什么声明成private就可以了呢,声明成protected可以吗??
点赞 回复 分享
发布于 2022-03-11 09:42
mysharedptr类中为啥要用指针unsigned int* m_count;使用unsigned int m_count可以吗?
点赞 回复 分享
发布于 2022-03-16 19:59
auto_ptr的问题好像不是造成资源被释放两次,是会将作为右值的auto_ptr的控制权转交给作为左值的auto_ptr,在这之后作为右值的auto_ptr就不能再使用了,此时它内存的裸指针已经指向nullptr了。
点赞 回复 分享
发布于 2022-09-27 16:30 江苏

相关推荐

不愿透露姓名的神秘牛友
2024-12-18 15:35
程序员牛肉:完全是在胡写简历。 我很好奇你干嘛要在教育经历里面写你是软件二班的班长?你写它的目的是什么?我觉得真的就是很突兀。给我第一感觉就是:你真的是一个心智健全的成年人吗? 另外我也很好奇你是怎么做到参加了这么多所谓的计算机比赛,完事儿一个拿得出手的项目都没有。 自己的项目经历还是图书馆管理系统这种垃圾东西……我的的建议是你都不如大幅度删减一下自己的水奖项,看着真的给人一种又水又学傻了的感觉。 计算机不看奖项,看院校和个人能力。 计算机是强工科,你要投后端的你就应该明白,人家招你进去是指望你干活儿的。那你觉得你这份简历有展示出你的后端水平吗? 你动动你的脑子想一想,人家面试官要想通过你的简历看出你的项目开发能力,最重要的板块就是两个,第一个是你的实习,第二个是你的项目。你没有实习,是不是就应该在项目上好好琢磨琢磨? 你自己看看你项目写的什么描述,你作为一个要后端岗位的应届生,你对你自己项目的描述还仅仅停留在使用mySQL,使用JAVA,使用spring boot框架。给人一眼感觉就感觉完全就是你做的玩具。可能就是你哪一个学期做的课设。 对于应届生来讲,在项目板块要尽量突出自己的技术能力,因为谈业务你肯定也不懂。简单来讲,你的项目要清晰准确的表达:你用哪种技术解决了现有的哪种技术问题,带来了多少的效益提升? 所有关于项目的描述都围绕我说的这种表达方式去写。不要自己自嗨式的写一堆垃圾上去 你既没有实习项目,又没有一个比较好一点的项目,而且院校也比较差,所以找工作会异常的难找。
点赞 评论 收藏
分享
评论
点赞
2
分享

创作者周榜

更多
牛客网
牛客企业服务