unity客户端面试高频2(自用)

标题是我

1.构造函数为什么不能为虚函数?析构函数为什么要虚函数?

构造函数不能定义为虚函数的原因是因为虚函数是用于实现动态多态性的机制,而构造函数的调用是在对象创建的过程中完成的。在对象创建时,其类型是已知的,不需要通过动态绑定来确定调用哪个构造函数。因此,构造函数不需要被定义为虚函数。

析构函数需要定义为虚函数的主要原因是为了在使用基类指针指向派生类对象并通过该指针删除对象时,可以正确地调用派生类对象的析构函数,以防止内存泄漏。如果不将析构函数定义为虚函数,当使用基类指针指向派生类对象并通过该指针删除对象时,只会调用基类的析构函数,而不会调用派生类的析构函数。这会导致派生类对象的资源无法正确释放,造成内存泄漏。

2.C++智能指针

图片来源程序员陈子青

具体的底层实现

计数器类

cpp 复制代码
template <typename T>
class RefCount {
public:
    T* ptr;  // 指向实际管理的对象
    size_t count;  // 引用计数
    RefCount(T* p) : ptr(p), count(1) {}
    ~RefCount() {
        delete ptr;  // 当引用计数为0时,释放管理的对象
    }
};

share_ptr类

cpp 复制代码
template <typename T>
class MySharedPtr {
private:
    RefCount<T>* ref;  // 指向引用计数管理对象

public:
    // 构造函数,初始化指向对象并设置引用计数为1
    MySharedPtr(T* p = nullptr) {
        if (p) {
            ref = new RefCount<T>(p);
        } else {
            ref = nullptr;
        }
    }

    // 拷贝构造函数
    MySharedPtr(const MySharedPtr& other) {
        ref = other.ref;
        if (ref) {
            ++(ref->count);  // 增加引用计数
        }
    }

    // 移动构造函数
    MySharedPtr(MySharedPtr&& other) noexcept {
        ref = other.ref;
        other.ref = nullptr;  // 转移所有权后,原对象的引用计数指针置空
    }

    // 赋值运算符重载
    MySharedPtr& operator=(const MySharedPtr& other) {
        if (this != &other) {
            release();  // 先释放当前对象的资源(若有)
            ref = other.ref;
            if (ref) {
                ++(ref->count);  // 增加引用计数
            }
        }
        return *this;
    }

    // 移动赋值运算符重载
    MySharedPtr& operator=(MySharedPtr&& other) noexcept {
        if (this != &other) {
            release();
            ref = other.ref;
            other.ref = nullptr;
        }
        return *this;
    }

    // 获取管理的对象指针
    T* get() const {
        return ref? ref->ptr : nullptr;
    }

    // 解引用操作
    T& operator*() const {
        return *(ref->ptr);
    }

    // 箭头操作符,用于访问对象成员
    T* operator->() const {
        return ref->ptr;
    }

    // 获取引用计数
    size_t use_count() const {
        return ref? ref->count : 0;
    }

    // 析构函数
    ~MySharedPtr() {
        release();
    }

private:
    // 释放资源的辅助函数
    void release() {
        if (ref) {
            --(ref->count);
            if (ref->count == 0) {
                delete ref;  // 引用计数为0时,删除引用计数管理对象
            }
        }
    }
};

3.左值和右值

  • 左值(lvalue):左值指的是可以出现在赋值运算符左边的表达式。它代表一个具名的、有确定内存地址的对象,其生命周期相对较长,在表达式结束后依然存在。

  • 右值(rvalue):右值指的是只能出现在赋值运算符右边的表达式。它代表临时对象、字面量或即将销毁的对象,其生命周期较短,在表达式结束后就会被销毁。

    右值引用会给右值一个临时的地址,并且延长它的生命周期。

左值作为参数的函数只可以接受左值,强调不要复制。void print(int& a);

右值作为参数的函数传入只能接受右值,可以修改右值。void print(int&& a);

左值作为参数传入很常见,避免复制影响空间内存和性能。

那为什么要用右值引用呢?

  • 右值引用可以避免深拷贝
    假设A是一个类实例,类内有拷贝构造函数。
    如果使用ClassName B = A;那么会复制一份A的内容到B上,堆内就有两份相同的数据了。
    然而如果使用ClassName&& B = move(A);就只有一份数据,将A的所有权转移给B,避免了深拷贝。前提是类中必须有移动构造函数。为什么不用左值引用?
    只是为了转移资源,避免拷贝,把原来的A指向空,B把A的内容偷过来了,后续的修改也不会影响A的内容,这使得A能被重新赋值。
    右值引用在实现移动语义时,通常能在不影响原变量(更准确地说是源对象)的前提下(除了初次的使它置空),在不拷贝的情况下进行资源转移。
cpp 复制代码
#include <iostream>
class MyArray {
private:
    int* data;
    size_t size;
public:
    MyArray(size_t s) : size(s) {
        data = new int[s];
        for (size_t i = 0; i < s; ++i) {
            data[i] = i;
        }
    }
    // 拷贝构造函数
    MyArray(const MyArray& other) : size(other.size) {
        data = new int[size];
        for (size_t i = 0; i < size; ++i) {
            data[i] = other.data[i];
        }
    }
    // 移动构造函数
    MyArray(MyArray&& other) noexcept : data(other.data), size(other.size) {
        other.data = nullptr;
        other.size = 0;
    }
    ~MyArray() {
        delete[] data;
    }
};

完美转发

通过forward(arg)这个API能让函数传入的是左值或者右值的属性保留传递下去。

去掉forward直接传递arg的话会导致,第一次传入forwardExample这个函数时,在这函数生命周期中是右值,然而第二次传递进去的是一个arg的参数会被识别为左值。

图片来源程序员陈子青

4.深拷贝与浅拷贝

C++默认的拷贝构造函数就是浅拷贝,只是将新的指针指向同一块内存地址,这会导致某一个指针被释放的时候,另外一个成为野指针。

深拷贝需要重写,会在堆上分配一份新的内容并且将指针指向这个内容的地址。

而C++11后的智能指针shared_ptr可以完美解决这个问题,通过引用计数器的方式判断是否需要释放指向的空间。

5.malloc VS new 你们知道吗

malloc菜,不会自动分配内存,要自己sizeof分配,不知道类型,要自己转换。

new强,会自己分配内存,自己类型转换。

malloc不安全,new更安全。

free不会调用析构函数,delete会调用析构函数。

相关推荐
考研保研资料分享4 小时前
2022双非保研华科热能工程:三校考核、传热题与报名复盘
面试
weixin_3077791310 小时前
RAG 面试陷阱——混合检索并非无条件优越:一个条件反转实验
人工智能·python·面试·职场和发展
nice_lcj52011 小时前
深入理解 Java 反射:从 Class 对象到方法调用(含性能实测 + 迷你 IOC 实战 + 面试考点)
java·开发语言·后端·面试
D e re k ZH14 小时前
C++ 并发、未定义行为与工程化难点(No 3)
数据结构·c++·算法·面试
香蕉大香蕉(06敬畏市场)18 小时前
MySQL 单点详解(08)
java·数据库·mysql·面试·八股
Thanks_ks19 小时前
常见 HTTP 状态码的坑:301/302、401/403、500/502 之间差的那一个词
计算机网络·nginx·http·面试·状态码·后端开发·浏览器缓存
怕浪猫19 小时前
Super-agent 在各大模型厂商的应用情况
算法·面试·github
滼20 小时前
AI 数据安全面试应答框架:从大框架到小点
大数据·人工智能·安全·面试·安全架构
peter67681 天前
unity2d游戏开发经验总结
unity
郝学胜-神的一滴1 天前
游戏设计:原型与开发 19:U3D工位布置与性格说明书
人工智能·算法·unity·unreal engine·游戏策划·游戏设计