一、面经整理(一)

文章目录

  • 一、零念科技C++开发实习生面经
    • 1.类的大小
    • 2.多态怎么实现的
    • 3.只能在栈上的类
    • 4.智能指针
    • 5.类型转换
    • 6.单例
    • 7.模板
    • 8.C++线程和async有啥区别?
    • 9.C++14新特性
    • 10.stl用过什么?
    • 11.vector和array区别
    • 12.emplace_back和push_back区别
    • 13.初始化列表、构造函数体内初始化、成员变量给缺省值
    • 14.默认构造函数
    • 15.function和bind的作用
    • 16.final/overide/explicit
  • 二、同元软控C++开发实习
    • 1.自我介绍
    • 2.对多态的认识
    • 3.你对指针的认识
    • 4.智能指针大小?
    • 5.迭代器失效场景
    • 6.c语言编译流程?
    • 7.用过STL什么容器?
    • 8.虚函数表存在哪里?
    • 9.动态多态:使用用虚函数表如何使用的?
    • 10.场景题:写的这个智能指针实例有什么问题
    • 11.算法 LRU

一、零念科技C++开发实习生面经

1.类的大小

  • 空类的大小为1字节
  • 一个类中,虚函数本身、成员函数(包括静态与非静态)和静态数据成员都是不占用类对象的存储空间。
  • 对于包含虚函数的类,不管有多少个虚函数,只有一个虚指针vptr的大小。
  • 普通继承,派生类继承了所有基类的函数与成员,要按照字节对齐来计算大小。
  • 虚函数继承(多态),不管是单继承还是多继承,都是继承了基类的vptr。(32位操作系统4字节,64位操作系统 8字节)!
  • 虚继承(菱形继承),承基类的vptr。

2.多态怎么实现的

虚函数表(通常简称为 vtable)是 C++ 用于实现多态行为的一种机制。
当一个类定义了虚函数或者继承了虚函数,编译器会为该类生成一个虚函数表,下面详细介绍虚函数表及其工作原理:

  1. 什么是虚函数表?
    虚函数表是一个存放指向虚函数的指针数组,每一个有虚函数的类(或者从有虚函数的类继承而来的类)都有一个相关联的虚函数表。
  2. 对象与虚函数表:
    每个有虚函数的对象都包含一个指向其类的虚函数表的指针,这个指针通常被称为 vptr。
  3. 如何工作?
    当调用一个对象的虚函数时,编译器使用对象的 vptr 来定位类的虚函数表。
    接着,从虚函数表中找到相应的虚函数指针并调用该函数,这个过程是在运行时进行的,因此可以实现多态行为。
  4. 继承和虚函数表:
    当一个类继承自另一个有虚函数的类,并且没有重写任何虚函数,该类的对象将使用父类的虚函数表。
    当派生类重写了基类的虚函数,派生类的虚函数表中该函数的入口会被更新为指向派生类版本的函数。
    如果派生类添加了新的虚函数,它们会被添加到虚函数表的末尾。

3.只能在栈上的类

只能在栈上创建,也就表示不能使用new,还有一个细节,那就是也不能在静态区创建。
那么,就只能按照上面只能在堆上创建的类的方法进行实现。

class StackOnly {
public:
    static StackOnly CreateObj() {
        return StackOnly();
    }

    StackOnly(const StackOnly&) = delete;

private:
    // 构造函数私有
    StackOnly() {}
};

这样此类, 就只能通过调用static成员函数CreateObj()实例化对象!
传统的实例化方式都不再支持!

int main() {
    StackOnly stO1 = StackOnly::CreateObj();
    StackOnly::CreateObj();
    return 0;
}

4.智能指针

在多线程环境下,当一个对象能被多个线程同时看到时,而程序员需要自己管理对象生命期时,对象的析构时机就会模糊不清,而智能指针可以帮助解决这类问题,想要了解智能指针就先要了解RAII,RAII即资源获取即初始化。
简单来讲就是使用一个对象,在其构造时获取对应的资源,在对象生命期内控制对资源的访问,最后在对象析构的时候,释放构造时获取的资源。它可以用来管理资源、避免资源泄漏,智能指针就是RAII的一种应用。

  1. unique_ptr
    unique_ptr 是一种独占式智能指针,它拥有对对象的唯一所有权,不能被多个 unique_ptr 对象共享,当 unique_ptr 对象被销毁时,它所管理的对象也会被销毁。
  2. shared_ptr
    shared_ptr 是一种共享式智能指针,可以被多个 shared_ptr 对象共享同一个对象,当最后一个 shared_ptr 对象销毁时,它所管理的对象也会被销毁。shared_ptr 使用引用计数来管理对象的生命周期。
  3. weak_ptr
    weak_ptr 是一种弱引用智能指针,不能单独管理对象的生命周期,它只能指向被 shared_ptr 管理的对象,用于解决 shared_ptr 循环引用的问题。weak_ptr 不会增加对象的引用计数,当最后一个 shared_ptr 对象销毁时,weak_ptr 不会影响对象的销毁。
  4. auto_ptr
    auto_ptr 是一种自动指针,可以自动管理对象的生命周期,但它已经被废弃,不再建议使用。
    auto_ptr 在将资源转移给另一个 auto_ptr 时,会自动释放原来的资源,可能会导致不可预期的行为。

5.类型转换

  1. const_cast
    将const变量转为非const
  2. static_cast
    最常用,可以用于各种隐式转换,比如非const转const,static_cast可以用于类向上转换,但向下
    转换能成功但是不安全。
  3. dynamic_cast
    只能用于含有虚函数的类转换,用于类向上和向下转换
    向上转换:指子类向基类转换。
    向下转换:指基类向子类转换。
    这两种转换,子类包含父类,当父类转换成子类时可能出现非法内存访问的问题。
    dynamic_cast通过判断变量运行时类型和要转换的类型是否相同来判断是否能够进行向下转换。dynamic_cast可以做类之间上下转换,转换的时候会进行类型检查,类型相等成功转换,类型不等转换失败。运用RTTI技术,RTTI是”Runtime Type Information”的缩写,意思是运行时类型信息,它提供了运行时确定对象类型的方法。在c++层面主要体现在dynamic_cast和typeid,vs中虚函数表的-1位置存放了指向type_info的指针,对于存在虚函数的类型,dynamic_cast和typeid都会去查询type_info。
  4. reinterpret_cast
    reinterpret_cast可以做任何类型的转换,不过不对转换结果保证,容易出问题。
    注意:为什么不用C的强制转换:C的强制转换表面上看起来功能强大什么都能转,但是转换不够明确,不能进行错误检查,容易出错。

6.单例

  1. 单例模式定义
    保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
    那么我们就必须保证:
    (1)该类不能被复制。
    (2)该类不能被公开的创造。
    那么对于C++来说,它的构造函数,拷贝构造函数和赋值函数都不能被公开调用。
  2. 单例模式实现方式
    单例模式通常有两种模式,分别为懒汉式单例和饿汉式单例。
    两种模式实现方式分别如下:
    (1)懒汉式设计模式实现方式(2种)
    a. 静态指针 + 用到时初始化
    b. 局部静态变量
    (2)饿汉式设计模式(2种)
    a. 直接定义静态对象
    b. 静态指针 + 类外初始化时new空间实现

7.模板

在C++中,模板是一种泛型编程的工具,它允许我们在编写代码时使用类型参数,从而使代码更加通用和灵活。
C++中的模板可以分为两种类型:函数模板和类模板。
函数模板是一种通用的函数定义,它可以用于多种类型的数据。函数模板使用一个或多个类型参数作为函数参数,从而定义了一组可重用的函数代码。函数模板的语法如下:

函数模板:
template<typename T>
T fun(T a,T b){
	return a + b;
}

类模板是一种通用的类定义,它可以用于多种类型的数据。类模板使用一个或多个类型参数作为类成员的类型,从而定义了一组可重用的类代码。类模板的语法如下:

类模版:
template<typename T>
class A {
	public:
		T a;
		void fun(T & a);
};

8.C++线程和async有啥区别?

  • std::thread() 如果系统资源紧张,那么可能创建线程就会失败,那么执行std::thread()时整个程序可能会崩溃。
  • std::async()我们一般不叫创建线程(解释async能够创建线程),我们一般叫它创建一个异步任务。
  • std::async和std::thread最明显的不同,就是async有时候并不创建新线程。
    区别:
    std::async和std::thread的区别
  • std::thread创建线程,如果系统资源紧张,创建线程失败,那么整个程序就会报异常崩溃(有脾气),std::thread创建线程的方式,如果线程返回值,你想拿到这个值也不容易;
  • std::async创建异步任务。可能创建也可能不创建线程。并且async调用方法很容易拿到线程入口函数的返回值;
    由于系统资源限制:
    如果用std::thread创建的线程太多,则可能创建失败,系统报告异常,崩溃。
    如果用std::async,一般就不会报异常不会崩溃,因为,如果系统资源紧张导致无法创建新线程的时候,std::async这种不加额外参数的调用就不会创建新线程。而是后续谁调用了result.get()来请求结果,那么这个异步任务mythread就运行在执行这条get()所在的线程上。

9.C++14新特性

  1. 通用lambda表达式
    C++14引入了通用lambda表达式,可以使用auto关键字作为参数类型和返回类型,使得lambda表达式更加灵活。
  2. 常量表达式
    C++14中,常量表达式是指在编译时可以计算出结果的表达式,它可以用于声明常量、数组大小、枚举值等。
  3. 二进制字面量
    C++14引入了二进制字面量,允许程序员使用二进制表示法来表示整数值。
  4. 数组大小自动推导
    在C++14中,可以使用auto关键字和初始化列表来实现数组大小的自动推导。具体来说,可以使用以下语法:
auto arr = {1, 2, 3, 4}; // 自动推导为std::initializer_list

在这个例子中,编译器会自动推导出arr的类型为std::initializer_list< int >,而数组的大小也会自动根据初始化列表的元素个数进行推导。因此,上述代码等价于下面的代码:

int arr[] = {1, 2, 3, 4}; // 数组大小为4

需要注意的是,这种自动推导方式只适用于静态数组,而对于动态数组来说,还需要使用new运算符手动分配内存。另外,由于std::initializer_list是一个轻量级的容器,因此它的性能可能不如普通数组。

10.stl用过什么?

序列容器,关联容器,容器适配器。

11.vector和array区别

  1. array 定义的时候必须定义数组的元素个数;而vector 不需要;且只能包含整型字面值常量,枚举常量或者用常量表达式初始化的整型const对象,非const变量以及需要到运行阶段才知道其值的const变量都不能用来定义数组的维度。
  2. array 定义后的空间是固定的了,不能改变;而vector 要灵活得多,可再加或减.
  3. vector有一系列的函数操作,非常方便使用,和vector不同,数组不提供 push——back或者其他的操作在数组中添加新元素,数组一经定义就不允许添加新元素;若需要则要充许分配新的内存空间,再将员数组的元素赋值到新的内存空间。
  4. 数组和vector不同,一个数组不能用另一个数组初始化,也不能将一个数组赋值给另一个数组;

12.emplace_back和push_back区别

emplace_back直接加,push_back创造一份加。
emplace_back() 和 push_back() 的区别,就在于底层实现的机制不同。push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);而 emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。

13.初始化列表、构造函数体内初始化、成员变量给缺省值

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。

class student {
    student () { name_ = "张三";}
    std::string name_;
};

14.默认构造函数

构造函数是一种特殊的成员函数,与其他成员函数不同,不需要用户来调用它,而是在建立对象时自动执行。
构造函数的功能是由用户定义的,用户根据初始化的要求设计函数体和函数参数,可以是一个,也可以是多个,可以把构造函数理解为重载的一种(函数名相同,不会返回任何类型,也不可以是void类型,参数类型个数可不同)。

版权声明:本文为CSDN博主「Mercury_cc」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Mercury_cc/article/details/130121061
默认构造函数:是无参调用的构造函数,包括两种:

  • 没有参数
  • 每个参数有初始值
class Box {
public:
    Box() { /*执行任何必需的默认初始化步骤*/}
    //所有参数都有默认值
    Box (int w = 1, int l = 1, int h = 1): m_width(w), m_height(h), m_length(l){}
...
}

15.function和bind的作用

C++中的function和bind是为了更方便地进行函数对象的封装和调用而设计的。

  • function是一个通用的函数对象容器,可以存储任意可调用对象(函数、函数指针、成员函数、lambda表达式等),并提供了一致的接口来调用这些对象。通过function,我们可以将一个函数或函数对象作为参数传递给其他函数或存储在容器中,实现更加灵活的编程。

  • bind则是一个用于将函数和其参数进行绑定的工具,可以将一个函数和部分参数绑定在一起,生成一个新的函数对象,这个新的函数对象可以像原函数一样进行调用,但会自动填充绑定的参数。通过bind,我们可以方便地实现函数的柯里化,即将一个多参数函数转化为一个单参数函数序列,提高代码的可读性和复用性。

16.final/overide/explicit

override 用于表示当前函数重写了基类的虚函数。
final 用于禁止类继承、禁止重载虚函数。
explicit关键字标示一个构造函数的话,就说明这个类的对象不允许在函数的参数传递过程中用调用这类构造函数。

比如:
class A{
public:
	A(int tar){
	}
	};
void tar_func(A a){
}
然后在main函数中有:
int main(){
	tar_func(100); //这里参数是一个整形,会调用A的构造函数,并生成一个A类的对象
	return 0;
}
如果构造函数前面加了explict那么这种main函数中的调用方法就是错误的了。

二、同元软控C++开发实习

1.自我介绍

2.对多态的认识

1.首先呢,我先来从她是什么这个角度来说,多态是C++三大特性之一,他主要是不同的对象对于同一个事件的不同反应,他主要体现了我们的一个接口重用。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。
2.其次,为什么要有多态,因为我们多态的实现条件是继承,以及基类指针指向子类对象,以及virtual,那我们多态,他是举个例子,比如说大象和小猪都是动物,但是他们喜欢吃的东西这方法不一样,如果说要分开的话,就要写两个函数,但是多态就很好的解决了这个问题,多态就是进行接口重用。
3.多态的类型,静态和动态,静态是在编译-运算符重载和函数重载,动态在运行-虚函数。
3.怎么实现多态,多态是通过虚函数表和虚函数表指针进行实现的,虚函数表是指存储了当前

3.你对指针的认识

1.指针是什么,指针的本质是地址!
2.为什么要有指针。

  • 指针可以轻松共享内存数据,提高代码效率。
  • 有些操作必须使用指针完成,比如说申请堆内存,C语言中的一切函数调用中,值传递都是“按值传递”的。如果要在函数中修改被传递过来的对象,就必须通过这个对象的指针来完成。
  • 指针可以动态分配内存,让程序在运行时获得新的内存空间-这一点对于节省计算机内存是有帮助的,因为计算机可以提前为需要的变量分配内存。但是在很多应用场合中,可能程序运行时不清楚到底需要多少内存,这时候可以使用指针,让程序在运行时获得新的内存空间(实际上应该就是动态内存分配),并让指针指向这一内存更为方便。

4.智能指针大小?

在 32 位机器上,unique_ptr 占 4 字节,shared_ptr 和 weak_ptr 占 8 字节。

在 64 位机器上,unique_ptr 占 8 字节,shared_ptr 和 weak_ptr 占 16 字节。

5.迭代器失效场景

  1. 对于序列容器vector,deque来说,使用erase后,后边的每个元素的迭代器都会失效,后边每个元素都往前移动一位,erase返回下一个有效的迭代器。
  2. 对于关联容器map,set来说,使用了erase后,当前元素的迭代器失效,但是其结构是红黑树,删除当前元素,不会影响下一个元素的迭代器,所以在调用erase之前,记录下一个元素的迭代器即可。
  3. 对于list来说,它使用了不连续分配的内存,并且它的erase方法也会返回下一个有效的迭代器,因此上面两种方法都可以使用。

6.c语言编译流程?

源文件 -> 预处理 -> 编译 -> 汇编 -> 链接 -> 可执行文件。

7.用过STL什么容器?

1.序列
vector,deque,list
2.关联
map,set,unordered_map,unordered_map
3.容器适配器
stack,queue

8.虚函数表存在哪里?

代码段(常量区)

9.动态多态:使用用虚函数表如何使用的?

虚函数表是一个类的虚函数的地址表,代码段(常量区),用于索引类本身以及父类的虚函数的地址,假如子类的虚函数重写了父类的虚函数,则对应在虚函数表中会把对应的虚函数替换为子类的虚函数的地址;
虚函数表指针存在于每个对象中(通常出于效率考虑,会放在对象的开始地址处), 它指向对象所在类的虚函数表的地址;在多继承环境下,会存在多个虚函数表指针,分别指向对应不同基类的虚函数表。

10.场景题:写的这个智能指针实例有什么问题

11.算法 LRU

二面是HR面:

自我介绍
实习经历
什么时候能到岗?
未来职业规划
期望薪资
反问环节

你可能感兴趣的:(view,数据库,c++,开发语言,linux)