牛客网参考:【C++工程师面试宝典】学习说明_互联网校招面试真题面经汇总_牛客网 (nowcoder.com)
Q:C++面向对象思想
概述:面向对象技术中的对象就是现实世界中,某个具体的物理实体在计算机中的映射和体现,是模拟现实世界中的实体。我们可以通过设计类,然后再实例化产生一个对象。
基本特征:抽象,封装,继承,多态
Q:多态以及实现
就是向不同的对象发送同⼀个消息,不同对象在接收时会产⽣不同的⾏为(即⽅法)。即⼀个接⼝,可以实现多种⽅法。 实现:多态其实⼀般就是指继承加虚函数实现的多态,对于重载来说,实际上基于的原理是,编译器为函数⽣成符号表时的不同规则,重载只是⼀种语⾔特性,与多态⽆关,与⾯向对象也⽆关,但这⼜是 C++中增加的新规则,所以也算属于 C++,所以如果⾮要说重载算是多态的⼀种,那就可以说: 多态可以分为静态多态和动态多态。 静态多态其实就是重载,因为静态多态是指在编译时期就决定了调⽤哪个函数,根据参数列表来决定; 动态多态是指通过⼦类重写⽗类的虚函数来实现的,因为是在运⾏期间决定调⽤的函数,所以称为动态多态,⼀般情况下我们不区分这两个时所说的多态就是指动态多态。动态多态的实现与虚函数表,虚函数指针相关。
Q:虚函数以及虚函数的实现原理
C++中多态的表象,在基类的函数前加上 virtual 关键字,在派⽣类中重写该函数,运⾏时将会根据对象的实际类型来调⽤相应的函数。如果对象类型是派⽣类,就调⽤派⽣类的函数,如果是基类,就调⽤基类的函数。 实际上,当⼀个类中包含虚函数时,编译器会为该类⽣成⼀个虚函数表,保存该类中虚函数的地址,同样,派⽣类继承基类,派⽣类中⾃然⼀定有虚函数,所以编译器也会为派⽣类⽣成⾃⼰的虚函数表。当我们定义⼀个派⽣类对象时,编译器检测该类型有虚函数,所以为这个派⽣类对象⽣成⼀个虚函数指针,指向该类型的虚函数表,这个虚函数指针的初始化是在构造函数中完成的。 后续如果有⼀个基类类型的指针,指向派⽣类,那么当调⽤虚函数时,就会根据所指真正对象的虚函数表指针去寻找虚函数的地址,也就可以调⽤派⽣类的虚函数表中的虚函数以此实现多态。
Q:new/delete 和 malloc/free
使用new操作符来分配对象内存时会经历三个步骤: 第一步:调用operator new 函数(对于数组是operator new[])分配一块足够大的,原始的,未命名的内存空间以便存储特定类型的对象。 第二步:编译器运行相应的构造函数以构造对象,并为其传入初值。 第三部:对象构造完成后,返回一个指向该对象的指针。 使用delete操作符来释放对象内存时会经历两个步骤: 第一步:调用对象的析构函数。 第二步:编译器调用operator delete(或operator delete[])函数释放内存空间。 细节 自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。 C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。 那么自由存储区是否能够是堆(问题等价于new是否能在堆上动态分配内存),这取决于operator new 的实现细节。自由存储区不仅可以是堆,还可以是静态存储区,这都看operator new在哪里为对象分配内存。 new内存分配失败时,会抛出bac_alloc异常,它不会返回NULL;malloc分配内存失败时返回NULL。 在operator new抛出异常以反映一个未获得满足的需求之前,它会先调用一个用户指定的错误处理函数,这就是new-handler。new_handler是一个指针类型,为了指定错误处理函数,客户需要调用set_new_handler,这是一个声明于的一个标准库函数,set_new_handler的参数为new_handler指针,指向了operator new 无法分配足够内存时该调用的函数。 使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算,而malloc则需要显式地指出所需内存的尺寸。 区别 1、new分配内存按照数据类型进行分配,malloc分配内存按照指定的大小分配; 2、new返回的是指定对象的指针,而malloc返回的是void*,因此malloc的返回值一般都需要进行类型转化。 3、new不仅分配一段内存,而且会调用构造函数,malloc不会。 4、new分配的内存要用delete销毁,malloc要用free来销毁;delete销毁的时候会调用对象的析构函数,而free则不会。 5、new是一个操作符可以重载,malloc是一个库函数。 6、malloc分配的内存不够的时候,可以用realloc扩容。扩容的原理?new没用这样操作。 malloc、calloc函数的实质体现在将一块可用的内存连接为一个链表。调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块,然后将该内存块一分为二(一块与用户申请的大小一样,另一块就是剩下的字节)。接下来,将分配给用户的那块内存地址传给用户,调用free函数时,它将用户释放的内存块连接到空链上,最后空闲链表会被切成很多的小内存片段。 realloc是从堆空间上分配内存,当扩大一块内存空间时,realloc试图直接从现存的数据后面的哪些字节中获得附加的字节,如果能够满足需求,自然天下太平,如果后面的字节不够,那么就使用堆上第一个足够满足要求的自由空间块,现存的数据然后就被拷贝到新的位置上,而老块则放回堆空间,这句话传递的一个很重要的信息就是数据可能被移动。 clear allocation memory allocation real allocation 7、new如果分配失败了会抛出bad_malloc的异常,而malloc失败了会返回NULL。 8、申请数组时: new[]一次分配所有内存,多次调用构造函数,搭配使用delete[],delete[]多次调用析构函数,销毁数组中的每个对象。而malloc则只能sizeof(int) * n。
Q:C++的继承方式
protected 成员和 private 成员类似,也不能通过对象访问。但是当存在继承关系时,protected 和 private 就不一样了:基类中的 protected 成员可以在派生类中使用,而基类中的 private 成员不能在派生类中使用。 不同的继承方式会影响基类成员在派生类中的访问权限。 1) public继承方式 基类中所有 public 成员在派生类中为 public 属性; 基类中所有 protected 成员在派生类中为 protected 属性; 基类中所有 private 成员在派生类中不能使用。 2) protected继承方式 基类中的所有 public 成员在派生类中为 protected 属性; 基类中的所有 protected 成员在派生类中为 protected 属性; 基类中的所有 private 成员在派生类中不能使用。 3) private继承方式 基类中的所有 public 成员在派生类中均为 private 属性; 基类中的所有 protected 成员在派生类中均为 private 属性; 基类中的所有 private 成员在派生类中不能使用。
Q:析构函数是否定义为虚函数的区别
(1)析构函数定义为虚函数时:基类指针可以指向派生类的对象(多态性),如果删除该指针delete []p;就会调用该指针指向的派生类析构函数,而派生类的析构函数又自动调用基类的析构函数,这样整个派生类的对象完全被释放。 (2)析构函数不定义为虚函数时:编译器实施静态绑定,在删除基类指针时,只会调用基类的析构函数而不调用派生类析构函数,这样就会造成派生类对象析构不完全。
Q:智能指针shared_ptr的实现
(构造函数)构造shared_ptr(公共成员函数) (析构函数)销毁shared_ptr(公共成员函数) operator= shared_ptr赋值(公共成员函数) swap 交换内容(公共成员函数) reset 重置指针(公共成员函数) get 获取指针(公共成员函数) operator* 取消引用对象(公共成员函数) operator-> 取消引用对象成员(公共成员函数) operator[] (C++17)提供到被存储数组的带下标访问(公开成员函数) use_count 返回计数(公共成员函数) unique(C++20前) 检查是否唯一(公共成员函数) operator bool 检查是否不为空(公共成员函数) owner_before 基于所有者的共享指针排序(公共成员函数模板) 非成员函数 swap 交换shared_ptr对象的内容(函数模板) relational operators 关系运算符 ==, !=, <, <=, >, >= (函数模板 ) ostream operator<< 将存储的指针的值输出到输出流(函数模板) 具体功能: make_shared,make_shared_for_overwrite(C++20) 创建管理一个新对象的共享指针(函数模板) allocate_shared,allocate_shared_for_overwrite(C++20) 创建管理一个用分配器分配的新对象的共享指针(函数模板) static_pointer_cast,dynamic_pointer_cast,const_pointer_cast,reinterpret_pointer_cast (C++17)应用 static_cast、dynamic_cast、const_cast 或 reinterpret_cast 到被存储指针(函数模板) get_deleter 返回指定类型中的删除器,若其拥有(函数模板)
1 #include <utility> 2 #include <cstddef> 3 4 class ref_count 5 { 6 public: 7 int use_count() const noexcept { return count_; } 8 void inc_ref() noexcept { ++count_; } 9 int dec_ref() noexcept { return --count_; } 10 11 private: 12 int count_{1}; 13 }; 14 15 template <typename T> 16 class Shared_ptr 17 { 18 public: 19 constexpr Shared_ptr() noexcept = default; 20 constexpr Shared_ptr(nullptr_t) noexcept : Shared_ptr() {} 21 explicit Shared_ptr(T *ptr) : ptr_{ptr} 22 { 23 if (ptr_ != nullptr) 24 { 25 rep_ = new ref_count{}; 26 } 27 } 28 Shared_ptr(const Shared_ptr &rhs) noexcept : ptr_{rhs.ptr_}, rep_{rhs.rep_} 29 { 30 if (ptr_ != nullptr) 31 { 32 rep_->inc_ref(); 33 } 34 } 35 Shared_ptr(Shared_ptr &&rhs) noexcept : ptr_{rhs.ptr_}, rep_{rhs.rep_} 36 { 37 rhs.ptr_ = nullptr; 38 rhs.rep_ = nullptr; 39 } 40 ~Shared_ptr() noexcept 41 { 42 if (rep_ != nullptr && rep_->dec_ref() == 0) 43 { 44 delete ptr_; 45 delete rep_; 46 } 47 } 48 49 Shared_ptr &operator=(const Shared_ptr &rhs) 50 { 51 Shared_ptr{rhs}.swap(*this); 52 return *this; 53 } 54 Shared_ptr &operator=(Shared_ptr &&rhs) 55 { 56 Shared_ptr{std::move(rhs)}.swap(*this); 57 return *this; 58 } 59 void reset() noexcept 60 { 61 Shared_ptr{}.swap(*this); 62 } 63 void reset(nullptr_t) noexcept 64 { 65 reset(); 66 } 67 void reset(T *ptr) 68 { 69 Shared_ptr{ptr}.swap(*this); 70 } 71 72 void swap(Shared_ptr &rhs) noexcept 73 { 74 std::swap(ptr_, rhs.ptr_); 75 std::swap(rep_, rhs.rep_); 76 } 77 T *get() const noexcept 78 { 79 return ptr_; 80 } 81 82 long use_count() const noexcept 83 { 84 return rep_ == nullptr ? 0 : rep_->use_count(); 85 } 86 bool unique() const noexcept 87 { 88 return rep_->use_count() == 1; 89 } 90 91 T &operator*() const noexcept 92 { 93 return *ptr_; 94 } 95 T &operator->() const noexcept 96 { 97 return ptr_; 98 } 99 100 explicit operator bool() const noexcept 101 { 102 return static_cast<bool>(ptr_); 103 } 104 105 private: 106 T *ptr_{nullptr}; 107 ref_count *rep_{nullptr}; 108 }; 109 110 template <typename T, typename... Args> 111 auto make_Shared(Args &&...args) 112 { 113 return Shared_ptr<T>{new T(std::forward(args)...)}; 114 }