返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C++通用动态抽象工厂的实现详解
  • 433
分享到

C++通用动态抽象工厂的实现详解

2024-04-02 19:04:59 433人浏览 薄情痞子
摘要

目录背景实现寄存参数存储所有构造出来的对象寄存指针,可析构的单例装饰工厂函数,责任链工厂允许构造函数之外的参数组合总结背景 一开始,我是想到了下面这个场景: struct A {

背景

一开始,我是想到了下面这个场景:

struct A {
  void Foo() {}
};

struct B {
  void Bar() {
    A().Foo();
  }
};

如上面代码,B的Bar中构造了A,然后调用了A的Foo函数。假如我想在别的场景中复用B的Bar,但是这时A的Foo就不符合需求了,需要定制,于是我们想到可以用多态,把A的Foo改成虚函数,然后写出A1:

struct A {
  A() = default;
  virtual ~A() = default;
  virtual void Foo() {}
};

struct A1 : public A {
  void Foo() override {}
};

B不应该知道A1的存在,为了让B用上A1,同时也为以后可能会拓展的A2、A3做准备,我们写了个A的工厂函数GetA()来生成A。于是代码变成:

std::unique_ptr<A> GetA() {
  if (Condition1()) {
    return std::unique_ptr<A>(new A1());
  } else {
    return std::unique_ptr<A>(new A());
  }
}

struct B {
  void Bar() {
    GetA()->Foo();
  }
};

如果B中还要构造别的C、D等类,难道我们要为每个类都写一个工厂函数吗?这成本也太高了,而且可能大部分类都只有一个,不用搞继承,写工厂函数就是无用功。那么,有没有一种通用的方式可以在写B代码的时候就对A、C、D等类的构造都留一手,使得以后可以由B外的代码控制实际构造的是A1等,而不需要修改B的代码?这就是我写动态抽象工厂的原始需求。

实现

思路很简单,就是为每个类都自动生成一个工厂函数就好了,然后在B中不直接构造A、C、D等对象,而是都调用对应类的工厂函数。然后这个自动生成的工厂默认就是构造原始的类的对象,比如A,也有接口可以改成生成子类,比如A1。对每个类生成一个工厂函数自然就想到用模板了。至于这个接口怎么实现,就有两大分支,分别是编译期和运行期。编译期一般就是用模板特化了。我觉得运行期会更有趣,用法会更多,就选了运行期。

运行期的意思就是要搞个变量来存下这个修改的工厂函数,自然就想到用std::function。当然免不了的是要把这个变量传给B。如果管理A的是一个变量,管理C、D的是另外两个变量,那就要传很多很多变量给B,这样也太繁琐了,所以应该一个变量存下所有类的工厂函数,然后把这个变量传遍所有需要使用工厂函数的对象或函数当中。所以这个变量的类型是一个确定的类型,不能带模板(或者说模板参数不能跟工厂对应的类相关,如A、C、D等)。那么模板就放到方法当中了。很自然地,这个类型的接口就应该是这样:

struct DynamicAbstractFactory {
  template <typename T, typename... Args>
  std::unique_ptr<T> New(Args&&...);
};

这里插一段,为什么叫动态抽象工厂呢?按照我的理解,工厂模式就是实现一个返回T*的函数F,里面用ifelse来控制最终返回的是T还是T的某个子类。抽象工厂模式就是连这个函数F都是可变的。动态是指这个F是运行时可变。

那么这个接口怎么实现呢?我的想法是用一个map来记录类型组合(T, Args&&...)到工厂函数std::function<T*(Args&&...)>的映射,并存储std::function<T*(Args&&...)>。New的实现就是查找map中有没有对应的工厂函数,有就调用工厂函数,没有就调用T本身的构造函数。当然,也需要提供一个接口来修改这个map。

要实现这个map还有三个细节:

  • 存储的std::function<T*(Args&&...)>是不同的类型,需要用类型擦除存储。如果可用c++17的话可直接用std::any,但我的环境有些老代码用GCc7编不过,所以还是只能用C++11,于是用std::shared_ptr<void>来代替(我一开始还是用std::unique_ptr+虚析构函数+继承来实现的,后来才知道std::shared_ptr自带这个功能)。
  • map的key是一个类型组合,就用std::type_index吧。由于std::function<T*(Args&&...)>已经把整个类型组合包进去了,而且一定会实例化,就直接用它吧。于是key就成了std::type_index(typeid(std::function<T*(Args&&...)>))。
  • 由于接口New(Args&&...)的每个参数类型Args&&都是引用类型,为了保持一致性,为了map能找到正确的函数,要求std::function中的每个参数也是引用类型,所以上面都写作std::function<T*(Args&&...)>。比如std::function<T*(int)>会转换成std::function<T*(int&&)>。

再加上修改map的接口SetFunc,第一版的动态抽象工厂就做好了:

class DynamicAbstractFactory {
 public:
  template <typename T, typename... Args>
  std::unique_ptr<T> New(Args&&... args) {
    auto iter = index2func_.find(std::type_index(typeid(std::function<T*(Args&&...)>)));
    if (iter != index2func_.end()) {
      return std::unique_ptr<T>((*reinterpret_cast<std::function<T*(Args&&...)>*>(iter->second.get()))(std::forward<Args>(args)...));
    }
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
  }

  template <typename T, typename... Args>
  void SetFunc(std::function<T*(Args...)>&& func) {
    index2func_[std::type_index(typeid(std::function<T*(Args&&...)>))] = std::make_shared<std::function<T*(Args&&...)>>(std::move(func));
  }

 protected:
  std::unordered_map<std::type_index, std::shared_ptr<void>> index2func_;
};

于是B的代码及使用就变成这样:

class B {
 public:
  B(DynamicAbstractFactory& factory) : factory_(factory) {}
  void Bar() {
    factory_.New<A>()->Foo();
    factory_.New<C>();
    factory_.New<D>();
  }
 protected:
  DynamicAbstractFactory& factory_;
};

// 旧环境,用原始A、C、D
// 当然B也可以用factory来生成
void Run() {
  DynamicAbstractFactory factory;
  factory.New<B>(factory)->Bar();
}

// 新环境,用A1、C、D
void Run1() {
  DynamicAbstractFactory factory;
  std::function<A*()> get_a = []() {
    return new A1();
  };
  factory.SetFunc<A>(std::move(get_a));
  factory.New<B>(factory)->Bar();
}

这样就满足了一开始的需求,B在构造A、C、D的时候都留了一手,B并不需要知道实际构造的是什么,在B的外部,Run()和Run1(),可控制在B里具体要构造的对象。

写完后发现这东西作用不止于此,下面写写一些扩展用法。

寄存参数

子类的构造函数的参数可以跟父类不一样,通过lambda捕获来寄存。

struct A2 : public A {
  A2(int i) {}
  void Foo() override {}
};

void Run2() {
  DynamicAbstractFactory factory;
  int i = 0;
  std::function<A*()> get_a = [i]() {
    return new A2(i);
  };
  factory.SetFunc<A>(std::move(get_a));
  factory.New<B>(factory)->Bar();
}

存储所有构造出来的对象

上面的接口返回std::unique_ptr,还要管理对象生命周期,不如更进一步,用factory来管理所有它构造的对象,在factory析构时统一析构。因为我一般写后台rpc接口,可以在rpc请求开始时构造factory,在构造好回包后析构factory,这样在整个请求周期构造的对象都在,指针不会失效,而且在请求结束后可以很方便地进行异步析构,直接把factory丢到析构线程就好。

于是New接口返回值由std::unique_ptr改成T*,同时New可能会造成误解,改成Get。当然,存储肯定要用到类型擦除存储。就成了下面这样:

class GeneralStorage {
 public:
  GeneralStorage(size_t reserve_size = 256) {
    storage_.reserve(reserve_size);
  }
  ~GeneralStorage() {
    // 保证按添加顺序逆序析构
    while (storage_.size() > 0) {
      storage_.pop_back();
    }
  }

  template <class T, class... Args>
  T* EmplaceBack(Args&&... args) {
    auto p_obj = std::make_shared<T>(std::forward<Args>(args)...);
    storage_.push_back(p_obj);
    return p_obj.get();
  }

 protected:
  std::vector<std::shared_ptr<void>> storage_;
};

class DynamicAbstractFactoryWithStorage {
 public:
  template <typename T, typename... Args>
  T* Get(Args&&... args) {
    auto iter = index2func_.find(std::type_index(typeid(std::function<T*(Args&&...)>)));
    if (iter != index2func_.end()) {
      return (*reinterpret_cast<std::function<T*(Args&&...)>*>(iter->second.get()))(std::forward<Args>(args)...);
    }
    return storage_.EmplaceBack<T>(std::forward<Args>(args)...));
  }

  template <typename T, typename... Args>
  void SetFunc(std::function<T*(Args...)>&& func) {
    index2func_[std::type_index(typeid(std::function<T*(Args&&...)>))] = std::make_shared<std::function<T*(Args&&...)>>(std::move(func));
  }

 protected:
  std::unordered_map<std::type_index, std::shared_ptr<void>> index2func_;
  GeneralStorage storage_; 
};

有个细节是对于改变过的工厂函数返回的指针是不应该存在storage_中的,而应该是在工厂函数中把对象存进storage_。上面的Run1()应该改成这样:

void Run1() {
  DynamicAbstractFactoryWithStorage factory;
  std::function<A*()> get_a = [&factory]() {
    return factory.Get<A1>();
  };
  factory.SetFunc<A>(std::move(get_a));
  factory.Get<B>(factory)->Bar();
}

寄存指针,可析构的单例

当返回值由std::unique_ptr改成T*,就可以实现寄存指针了。可析构的单例指每次请求都重新构造,在请求结束后析构,但是请求之中只构造一次。看下面例子:

struct C {
  C(DynamicAbstractFactoryWithStorage& factory) {
    std::function<C*(DynamicAbstractFactoryWithStorage&)> func = [this](DynamicAbstractFactoryWithStorage& factory) {
      return this;
    };
    factory.SetFunc<C>(std::move(func));
  }
};

void Run() {
  DynamicAbstractFactoryWithStorage factory;
  factory.Get<C>(factory); // 构造C,并通过SetFunc把对象的指针寄存到factory中
  factory.Get<C>(factory); // 调用C构造函数中的func,直接返回寄存的指针,不重复构造C
  // factory析构时C的对象将被析构
}

装饰工厂函数,责任链工厂

只要再加个接口GetFunc来获取当前的工厂函数,就可以对工厂函数玩装饰模式了。

GetFunc接口:

// 其它代码与上面一样
class DynamicAbstractFactoryWithStorage {
 public:
  template <typename T, typename... Args>
  std::function<T*(Args&&...)> GetFunc() {
    auto iter = index2func_.find(std::type_index(typeid(std::function<T*(Args&&...)>)));
    if (iter != index2func_.end()) {
      return *reinterpret_cast<std::function<T*(Args&&...)>*>(iter->second.get());
    }
    std::function<T*(Args&&...)> default_func = [this](Args&&... args) {
      return storage_.EmplaceBack<T>(std::forward<Args>(args)...));
    };
    return default_func;
  }
};

统计调用次数:

struct C {
  C(DynamicAbstractFactoryWithStorage& factory) {
    std::function<C*(DynamicAbstractFactoryWithStorage&)> func = [this](DynamicAbstractFactoryWithStorage& factory) {
      return this;
    };
    factory.SetFunc<C>(std::move(func));
  }

  uint32_t cnt_ = 0;
};

void Run() {
  DynamicAbstractFactoryWithStorage factory;
  auto func = factory.GetFunc<A>();
  std::function<A*()> get_a = [func, &factory]() {
    ++factory.Get<C>()->cnt_;
    return func();
  };
  factory.SetFunc<A>(std::move(get_a));
  factory.Get<B>(factory)->Bar();
}

用责任链模式搞个工厂:

struct D {
  D() {}
  D(int i) {}
};

struct D1 : public D {
  D1(int i) {}

  static void SetFactoryD(DynamicAbstractFactoryWithStorage& factory) {
    // GetFunc的结果是std::fuction<D*(int&&)>类型的,这里经过了一次类型转换
    std::function<D*(int)> func = factory.GetFunc<D, int>();
    std::function<D*(int)> new_func = [func, &factory](int i) -> D* {
      // 责任链模式
      if (Check(i)) {
        return factory.Get<D1>(i);
      } else {
        return func(i);
      }
    };
    factory.SetFunc<D>(std::move(new_func));
  }

  // 构造D1的条件
  static bool Check(int i) {
    return i == 1;
  }
};

// 与D1类似,除了Check
struct D2 : public D {
  D2(int i) {}

  static void SetFactoryD(DynamicAbstractFactoryWithStorage& factory) {
    std::function<D*(int)> func = factory.GetFunc<D, int>();
    std::function<D*(int)> new_func = [func, &factory](int i) -> D* {
      if (Check(i)) {
        return factory.Get<D2>(i);
      } else {
        return func(i);
      }
    };
    factory.SetFunc<D>(std::move(new_func));
  }

  // 构造D2的条件
  static bool Check(int i) {
    return i == 2;
  }
};

void Run() {
  DynamicAbstractFactoryWithStorage factory;
  D1::SetFactoryD(factory);
  D2::SetFactoryD(factory);
  factory.Get<D>(0); // D
  factory.Get<D>(1); // D1
  factory.Get<D>(2); // D2
}

允许构造函数之外的参数组合

上面的实现要求new T(std::forward(args)...)能合法生成一个T对象指针,在一些情况下很难做到,比如T中有难以初始化的成员,又比如T是一个抽象类:

struct E {
  E() = default;
  virtual ~E() = default;
  virtual void Foo() = 0;
};

这样就要修改Get接口的逻辑,改成如果能合法调用构造函数,就调用,否则就不调用。但是这样放开之后,就各种参数组合都可以搞了,我觉得这样可能会很混乱,这边设置了这个参数组合,那边设置了另外的参数组合,不知道一共设置了哪几种参数组合。我觉得还是要加点限制,就规定参数组合必须在基类中定义。规定了一个方法名FactoryGet,所有非构造函数的参数组合要定义一个静态FactoryGet方法,方法返回T*,比如:

struct E {
  E() = default;
  static E* FactoryGet(int) {
    return nullptr;
  }
  virtual ~E() = default;
  virtual void Foo() = 0;
};

这样Get接口的逻辑就可以改成如果能合法调用构造函数,就调用,否则就调用对应的FactoryGet方法,其他参数组合将会编译报错。同时也规定FactoryGet获得的指针不存进通用存储。于是DynamicAbstractFactoryWithStorage就改成这样:

// new T(std::forward<Args>(args)...)
// T::FactoryGet(std::forward<Args>(args)...)
// 要求上面两个表达式有且仅有一个合法并且返回T*,Get调用合法的那个。
template <typename T, typename F, typename = void>
struct DefaultGet;

template <typename T, typename... Args>
struct DefaultGet<T, void(Args...), typename std::enable_if<std::is_same<decltype(std::decay<T>::type::FactoryGet(std::forward<Args>(*reinterpret_cast<typename std::decay<Args>::type*>(0))...)), T*>::value, void>::type> {
  static T* Get(GeneralStorage& storage, Args&&... args) {
    return T::FactoryGet(std::forward<Args>(args)...);
  }
};

template <typename T, typename... Args>
struct DefaultGet<T, void(Args...), typename std::enable_if<std::is_same<decltype(new typename std::decay<T>::type(std::forward<Args>(*reinterpret_cast<typename std::decay<Args>::type*>(0))...)), typename std::decay<T>::type*>::value, void>::type> {
  static T* Get(GeneralStorage& storage, Args&&... args) {
    return storage.EmplaceBack<typename std::decay<T>::type>(std::forward<Args>(args)...);
  }
};

class DynamicAbstractFactoryWithStorage {
 public:
  // 每个Args都要是引用
  template <typename T, typename... Args>
  using FuncType = std::function<T*(Args&&...)>;

  template <typename T, typename... Args>
  T* Get(Args&&... args) {
    auto iter = index2func_.find(std::type_index(typeid(FuncType<T, Args...>)));
    if (iter != index2func_.end()) {
      return (*reinterpret_cast<FuncType<T, Args...>*>(iter->second.get()))(std::forward<Args>(args)...);
    }
    return DefaultGet<T, void(Args&&...)>::Get(storage_, std::forward<Args>(args)...);
  }

  template <typename T, typename... Args>
  void SetFunc(std::function<T*(Args...)>&& func) {
    index2func_[std::type_index(typeid(FuncType<T, Args...>))] = std::make_shared<FuncType<T, Args...>>(std::move(func));
  }

  template <typename T, typename... Args>
  FuncType<T, Args...> GetFunc() {
    auto iter = index2func_.find(std::type_index(typeid(FuncType<T, Args...>)));
    if (iter != index2func_.end()) {
      return *reinterpret_cast<FuncType<T, Args...>*>(iter->second.get());
    }
    FuncType<T, Args...> default_func = [this](Args&&... args) {
      return DefaultGet<T, void(Args&&...)>::Get(storage_, std::forward<Args>(args)...);
    };
    return default_func;
  }

 protected:
  std::unordered_map<std::type_index, std::shared_ptr<void>> index2func_;
  GeneralStorage storage_;
};

这样E就能像上面那样用了。另外,想要返回const指针也是可以的。

struct E {
  E() = default;
  // 返回值改成了const E*
  static const E* FactoryGet(int) {
    return nullptr;
  }
  virtual ~E() = default;
  virtual void Foo() = 0;
};

struct E1 : public E {
  E1(int i) {}
  static void SetFactoryE(DynamicAbstractFactoryWithStorage& factory) {
    std::function<const E*(int)> func = factory.GetFunc<const E, int>();
    std::function<const E*(int)> new_func = [func, &factory](int i) -> const E* {
      if (Check(i)) {
        return factory.Get<const E1>(i);
      } else {
        return func(i);
      }
    };
    factory.SetFunc<const E>(std::move(new_func));
  }
  static bool Check(int i) {
    return i == 1;
  }

  void Foo() override {}
};

void Run() {
  DynamicAbstractFactoryWithStorage factory;
  E1::SetFactoryE(factory);
  factory.Get<const E>(0); // nullptr
  factory.Get<const E>(1); // const E1*
}

总结

到此这篇关于C++通用动态抽象工厂的文章就介绍到这了,更多相关C++通用动态抽象工厂内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: C++通用动态抽象工厂的实现详解

本文链接: https://lsjlt.com/news/154136.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • C++通用动态抽象工厂的实现详解
    目录背景实现寄存参数存储所有构造出来的对象寄存指针,可析构的单例装饰工厂函数,责任链工厂允许构造函数之外的参数组合总结背景 一开始,我是想到了下面这个场景: struct A { ...
    99+
    2024-04-02
  • c++如何实现抽象工厂
    今天小编给大家分享一下c++如何实现抽象工厂的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。代码示例利用者代码:这段代码中创建...
    99+
    2023-06-19
  • 详解Java实践之抽象工厂模式
    目录一、前言二、开发环境三、抽象工厂模式介绍四、案例场景模拟4.1、场景模拟工程4.2、场景简述4.2.1、模拟单机服务 RedisUtils4.2.2、模拟集群 EGM4.2.3、...
    99+
    2024-04-02
  • 怎么用Java代码实现抽象工厂模式
    这篇文章主要介绍“怎么用Java代码实现抽象工厂模式”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“怎么用Java代码实现抽象工厂模式”文章能帮助大家解决问题。解决工厂方法模式的问题:一个具体工厂只能...
    99+
    2023-06-29
  • Java工厂模式用法之如何动态选择对象详解
    目录前言小菜鸟的问题有没有更好的方法呢还有什么更好的办法吗还能做得更好吗如何在 SpringBoot 中实现此技术总结前言 工厂设计模式可能是最常用的设计模式之一,我想大家在自己的项...
    99+
    2023-03-10
    Java工厂模式动态选择对象 Java工厂模式 Java动态选择对象
  • C语言静态与动态通讯录的实现流程详解
    目录静态通讯录contact.hcontact.ctest.c动态通讯录contact.hcontact.cqsort.ctest.c本次通讯录的代码已经放到我的Gitee仓库中,感...
    99+
    2024-04-02
  • PHP抽象工厂模式的优点与实现方法是什么
    本篇内容介绍了“PHP抽象工厂模式的优点与实现方法是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!抽象工厂模式Abstract Fact...
    99+
    2023-07-05
  • C++多态的实现与原理及抽象类实例分析
    这篇文章主要讲解了“C++多态的实现与原理及抽象类实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C++多态的实现与原理及抽象类实例分析”吧!多态的概念多态: 从字面意思来看,就是事物...
    99+
    2023-06-29
  • 通过JavaScript实现动态圣诞树详解
    目录一、只使用 CSS 制作的动画圣诞树二、只使用 CSS 制作的螺旋圣诞树三、使用HTML+CSS+JS制作的圣诞树四、只使用 CSS 的流星圣诞树五、水晶球里的圣诞树六、圣诞贺卡...
    99+
    2024-04-02
  • C++详解如何实现动态数组
    目录动态数组示例代码运行环境运行效果动态数组 动态数组Vector可以动态扩展内存,其采用连续的内存空间,当内存空间不足,便以原来的容量的2倍或者1.5倍成倍的扩展,将原有的数组元素...
    99+
    2024-04-02
  • C++实现动态规划过程详解
    目录C++实现动态规划1. 动态规划的基础2. 动态规划的实现方法3. 实际应用C++实现动态规划 动态规划是解决一类最优问题的常用方法,它是解决最优化问题的一种途径,因为这种算法通...
    99+
    2023-05-20
    C++实现动态规划 C++动态规划
  • Android拼接实现动态对象方法详解
    目录1. 前言2. 动态选密钥3. 换肤上的使用4. 总结1. 前言 我们往往有些配置文件,当项目大的时候,一些配置文件或者一些判断逻辑就会变得复杂,会出现很多判断语句,我在想,能...
    99+
    2023-03-01
    Android拼接动态对象 Android 动态对象
  • C语言与C++动态通讯录超详细实现流程
    目录1、思路以及要实现的功能2、详细步骤2.1 打印菜单界面(建一个源文件test.c)2.2 主函数2.3 初始化函数与加载函数2.4 增加联系人函数AddContact2.5 删...
    99+
    2024-04-02
  • C++ 静态函数可以用来实现工厂方法模式吗?
    c++++ 静态函数可以用来实现工厂方法模式,它定义了一个接口用于创建对象,并将创建逻辑推迟到子类中。在 c++ 中,可以使用静态函数来实现工厂方法模式,这些函数不需要实例化类,可以轻松...
    99+
    2024-04-16
    c++ 工厂方法模式
  • PHP实现动态表单生成工具详解
    目录Form介绍特点项目主页链接安装方法快速使用链式操作创建块表单数组配置创建块表单行内表单table表单表单包含多种input类型,包括 hiiden类型 ,text类型,radi...
    99+
    2024-04-02
  • C语言实现动态顺序表详解
    目录什么是顺序表?1. 定义顺序表结构体:2. 初始化顺序表:3. 销毁顺序表:4. 打印顺序表:5. 判断容量+扩容:6. 头插数据:7. 尾插数据:8. 指定下标位置插入...
    99+
    2024-04-02
  • 举例讲解Python设计模式编程中对抽象工厂模式的运用
    抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。 优点:易于交换“产品系列”,只要更改相应的工厂即可。 缺点:建立产品的时候很繁琐,需要增加和修改很多东西。 优化1:为了避...
    99+
    2022-06-04
    模式 中对 抽象
  • C语言实现一个文件版动态通讯录流程详解
    目录通讯录思维导图一、Contact.h二、Contact.c1.初始化通讯录2.检查容量是否满3.添加联系人4.显示联系人5.查找联系人6.修改联系人7.通过名字来排序联系人8.保...
    99+
    2023-01-29
    C语言动态通讯录 C语言通讯录
  • C语言动态与静态分别实现通讯录详细过程
    目录前言:一.静态通讯录的实现1.环境的分工逻辑2.待实现的功能3.contact.h4.contact.c5.test.c6.实现效果二.通讯录动态的实现1.contact.h2....
    99+
    2024-04-02
  • C++数据结构分析多态的实现与原理及抽象类
    目录多态的概念虚函数多态构成的条件C++11override和final重载、重写和重定义(隐藏)抽象类多态的原理虚函数表原理单继承和多继承的虚表单继承的虚表多继承的虚函数表几个值得...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作