当前位置: 首页 > news >正文

【穿越Effective C++】条款12:复制对象时勿忘其每一个成分——完整对象拷贝的艺术

这个条款揭示了C++拷贝控制中一个常见但危险的陷阱:用户定义的拷贝操作可能遗漏某些成员变量,导致对象状态不完整。这是构建正确拷贝语义的关键原则。


思维导图:完整对象拷贝的完整体系

在这里插入图片描述


深入解析:拷贝遗漏的隐蔽危险

1. 问题根源:手动管理的复杂性

新增成员导致的拷贝遗漏:

class Customer {
private:std::string name;double balance;int customerId;// 新添加的成员 - 容易在拷贝操作中遗漏time_t lastLoginTime;std::vector<std::string> purchaseHistory;public:Customer(const std::string& n, double bal, int id) : name(n), balance(bal), customerId(id), lastLoginTime(time(nullptr)) {}// 拷贝构造函数 - 可能遗漏新成员!Customer(const Customer& rhs): name(rhs.name), balance(rhs.balance), customerId(rhs.customerId) // 危险!lastLoginTime和purchaseHistory没有被拷贝!{std::cout << "Customer拷贝构造 - 可能不完整!" << std::endl;}// 拷贝赋值运算符 - 同样可能遗漏!Customer& operator=(const Customer& rhs) {if (this != &rhs) {name = rhs.name;balance = rhs.balance;customerId = rhs.customerId;// 危险!lastLoginTime和purchaseHistory没有被赋值!}return *this;}void updateLogin() {lastLoginTime = time(nullptr);}void addPurchase(const std::string& item) {purchaseHistory.push_back(item);}void printInfo() const {std::cout << "客户: " << name << ", 余额: " << balance<< ", ID: " << customerId<< ", 最后登录: " << lastLoginTime<< ", 购买历史数量: " << purchaseHistory.size() << std::endl;}
};void demonstrate_member_omission() {Customer original("张三", 1000.0, 12345);original.updateLogin();original.addPurchase("笔记本电脑");original.addPurchase("鼠标");std::cout << "原始对象: ";original.printInfo();// 拷贝构造 - 新成员被遗漏!Customer copy1(original);std::cout << "拷贝对象: ";copy1.printInfo();  // lastLoginTime和purchaseHistory不同!// 拷贝赋值 - 同样问题!Customer copy2("李四", 500.0, 67890);copy2 = original;std::cout << "赋值后对象: ";copy2.printInfo();
}

运行结果分析:

原始对象: 客户: 张三, 余额: 1000, ID: 12345, 最后登录: 1698765432, 购买历史数量: 2
拷贝对象: 客户: 张三, 余额: 1000, ID: 12345, 最后登录: 0, 购买历史数量: 0
赋值后对象: 客户: 张三, 余额: 1000, ID: 12345, 最后登录: 0, 购买历史数量: 0

关键发现: 新增的lastLoginTimepurchaseHistory成员在拷贝操作中被完全遗漏!


继承体系中的拷贝遗漏

1. 基类部分拷贝的遗忘

危险的派生类拷贝实现:

class BaseAccount {
protected:std::string accountNumber;double baseBalance;time_t creationDate;public:BaseAccount(const std::string& accNum, double balance) : accountNumber(accNum), baseBalance(balance), creationDate(time(nullptr)) {}// 基类拷贝构造函数BaseAccount(const BaseAccount& rhs): accountNumber(rhs.accountNumber),baseBalance(rhs.baseBalance),creationDate(rhs.creationDate) {std::cout << "BaseAccount拷贝构造" << std::endl;}// 基类拷贝赋值运算符BaseAccount& operator=(const BaseAccount& rhs) {if (this != &rhs) {accountNumber = rhs.accountNumber;baseBalance = rhs.baseBalance;creationDate = rhs.creationDate;}return *this;}virtual ~BaseAccount() = default;virtual void printInfo() const {std::cout << "账户: " << accountNumber << ", 基础余额: " << baseBalance<< ", 创建时间: " << creationDate << std::endl;}
};class SavingsAccount : public BaseAccount {
private:double interestRate;double interestAccrued;public:SavingsAccount(const std::string& accNum, double balance, double rate) : BaseAccount(accNum, balance), interestRate(rate), interestAccrued(0.0) {}// 危险的派生类拷贝构造 - 忘记基类部分!SavingsAccount(const SavingsAccount& rhs): interestRate(rhs.interestRate),interestAccrued(rhs.interestAccrued) // 危险!没有调用BaseAccount拷贝构造!{std::cout << "SavingsAccount拷贝构造 - 基类部分丢失!" << std::endl;}// 危险的派生类拷贝赋值 - 同样忘记基类!SavingsAccount& operator=(const SavingsAccount& rhs) {if (this != &rhs) {interestRate = rhs.interestRate;interestAccrued = rhs.interestAccrued;// 危险!没有调用BaseAccount::operator= !}return *this;}void addInterest() {interestAccrued += baseBalance * interestRate;}void printInfo() const override {BaseAccount::printInfo();std::cout << "  利率: " << interestRate << ", 累计利息: " << interestAccrued << std::endl;}
};void demonstrate_inheritance_omission() {SavingsAccount original("SAV123", 5000.0, 0.05);original.addInterest();std::cout << "原始账户: " << std::endl;original.printInfo();// 拷贝构造 - 基类部分丢失!SavingsAccount copy1(original);std::cout << "拷贝账户: " << std::endl;copy1.printInfo();  // 基类成员是默认值!// 拷贝赋值 - 同样问题!SavingsAccount copy2("TEMP", 100.0, 0.01);copy2 = original;std::cout << "赋值后账户: " << std::endl;copy2.printInfo();
}

解决方案:完整的拷贝实现模式

1. 正确的派生类拷贝操作

完整的拷贝实现:

class CorrectSavingsAccount : public BaseAccount {
private:double interestRate;double interestAccrued;public:CorrectSavingsAccount(const std::string& accNum, double balance, double rate) : BaseAccount(accNum, balance), interestRate(rate), interestAccrued(0.0) {}// 正确的拷贝构造:显式调用基类拷贝构造CorrectSavingsAccount(const CorrectSavingsAccount& rhs): BaseAccount(rhs),  // 关键:调用基类拷贝构造interestRate(rhs.interestRate),interestAccrued(rhs.interestAccrued) {std::cout << "CorrectSavingsAccount完整拷贝构造" << std::endl;}// 正确的拷贝赋值:显式调用基类拷贝赋值CorrectSavingsAccount& operator=(const CorrectSavingsAccount& rhs) {if (this != &rhs) {BaseAccount::operator=(rhs);  // 关键:调用基类拷贝赋值interestRate = rhs.interestRate;interestAccrued = rhs.interestAccrued;}return *this;}// 移动构造也要正确处理基类CorrectSavingsAccount(CorrectSavingsAccount&& rhs) noexcept: BaseAccount(std::move(rhs)),  // 移动基类部分interestRate(rhs.interestRate),interestAccrued(rhs.interestAccrued) {rhs.interestRate = 0.0;rhs.interestAccrued = 0.0;}// 移动赋值也要处理基类CorrectSavingsAccount& operator=(CorrectSavingsAccount&& rhs) noexcept {if (this != &rhs) {BaseAccount::operator=(std::move(rhs));  // 移动基类interestRate = rhs.interestRate;interestAccrued = rhs.interestAccrued;rhs.interestRate = 0.0;rhs.interestAccrued = 0.0;}return *this;}void addInterest() {interestAccrued += baseBalance * interestRate;}void printInfo() const override {BaseAccount::printInfo();std::cout << "  利率: " << interestRate << ", 累计利息: " << interestAccrued << std::endl;}
};void demonstrate_correct_inheritance_copy() {CorrectSavingsAccount original("SAV456", 8000.0, 0.03);original.addInterest();std::cout << "原始账户: " << std::endl;original.printInfo();// 正确的拷贝构造CorrectSavingsAccount copy1(original);std::cout << "拷贝账户: " << std::endl;copy1.printInfo();  // 基类和派生类部分都正确拷贝// 正确的拷贝赋值CorrectSavingsAccount copy2("TEMP2", 200.0, 0.02);copy2 = original;std::cout << "赋值后账户: " << std::endl;copy2.printInfo();
}
2. 成员逐一检查的系统方法

拷贝操作的检查清单模式:

class CompleteCustomer {
private:// 基础成员std::string name;double balance;int customerId;// 联系信息std::string email;std::string phone;// 时间相关time_t registrationDate;time_t lastActivity;// 容器成员std::vector<std::string> orderHistory;std::map<std::string, int> preferences;// 动态资源std::unique_ptr<double[]> creditScores;size_t creditScoresSize;public:CompleteCustomer(const std::string& n, const std::string& email, const std::string& phone, int id): name(n), balance(0.0), customerId(id),email(email), phone(phone),registrationDate(time(nullptr)), lastActivity(time(nullptr)),creditScoresSize(10) {creditScores = std::make_unique<double[]>(creditScoresSize);std::fill(creditScores.get(), creditScores.get() + creditScoresSize, 0.0);}// 使用代码块注释确保每个成员都被拷贝CompleteCustomer(const CompleteCustomer& rhs): // 基础成员name(rhs.name),balance(rhs.balance),customerId(rhs.customerId),// 联系信息email(rhs.email),phone(rhs.phone),// 时间戳registrationDate(rhs.registrationDate),lastActivity(rhs.lastActivity),// 容器 - 自动调用拷贝构造orderHistory(rhs.orderHistory),preferences(rhs.preferences),// 动态资源 - 需要深拷贝creditScoresSize(rhs.creditScoresSize){// 动态数组的深拷贝creditScores = std::make_unique<double[]>(creditScoresSize);std::copy(rhs.creditScores.get(), rhs.creditScores.get() + creditScoresSize,creditScores.get());std::cout << "CompleteCustomer完整拷贝构造" << std::endl;}CompleteCustomer& operator=(const CompleteCustomer& rhs) {if (this != &rhs) {// 使用清晰的代码组织确保完整性// 基础成员name = rhs.name;balance = rhs.balance;customerId = rhs.customerId;// 联系信息email = rhs.email;phone = rhs.phone;// 时间戳registrationDate = rhs.registrationDate;lastActivity = rhs.lastActivity;// 容器orderHistory = rhs.orderHistory;preferences = rhs.preferences;// 动态资源creditScoresSize = rhs.creditScoresSize;auto newCreditScores = std::make_unique<double[]>(creditScoresSize);std::copy(rhs.creditScores.get(),rhs.creditScores.get() + creditScoresSize,newCreditScores.get());creditScores = std::move(newCreditScores);}return *this;}void addOrder(const std::string& order) {orderHistory.push_back(order);lastActivity = time(nullptr);}void setPreference(const std::string& key, int value) {preferences[key] = value;}void printInfo() const {std::cout << "客户: " << name << " (ID: " << customerId << ")\n"<< "  邮箱: " << email << ", 电话: " << phone << "\n"<< "  余额: " << balance << "\n"<< "  注册时间: " << registrationDate << ", 最后活动: " << lastActivity << "\n"<< "  订单数量: " << orderHistory.size()<< ", 偏好设置: " << preferences.size() << "\n"<< "  信用分数数组大小: " << creditScoresSize << std::endl;}
};

现代C++的改进与最佳实践

1. 使用=default让编译器生成

编译器生成拷贝操作的适用场景:

class DefaultedCustomer {
private:std::string name;double balance{0.0};int customerId{0};std::vector<std::string> purchaseHistory;std::map<std::string, int> preferences;public:DefaultedCustomer(const std::string& n, int id) : name(n), customerId(id) {}// 让编译器生成拷贝操作 - 当所有成员都有正确的拷贝语义时DefaultedCustomer(const DefaultedCustomer&) = default;DefaultedCustomer& operator=(const DefaultedCustomer&) = default;// 移动操作也可以默认生成DefaultedCustomer(DefaultedCustomer&&) = default;DefaultedCustomer& operator=(DefaultedCustomer&&) = default;~DefaultedCustomer() = default;// 当添加新成员时,编译器会自动更新默认生成的拷贝操作!void addPurchase(const std::string& item) {purchaseHistory.push_back(item);}void printInfo() const {std::cout << "客户: " << name << " (ID: " << customerId << ")\n"<< "  余额: " << balance << "\n"<< "  购买历史数量: " << purchaseHistory.size()<< ", 偏好设置: " << preferences.size() << std::endl;}
};
2. 拷贝并交换惯用法的应用

异常安全且完整的拷贝实现:

class CopyAndSwapCustomer {
private:std::string name;double balance{0.0};int customerId{0};std::unique_ptr<std::string[]> addresses;size_t addressCount{0};// 友元swap函数friend void swap(CopyAndSwapCustomer& first, CopyAndSwapCustomer& second) noexcept {using std::swap;swap(first.name, second.name);swap(first.balance, second.balance);swap(first.customerId, second.customerId);swap(first.addresses, second.addresses);swap(first.addressCount, second.addressCount);}public:CopyAndSwapCustomer(const std::string& n, int id, size_t addrCount = 0) : name(n), customerId(id), addressCount(addrCount) {if (addrCount > 0) {addresses = std::make_unique<std::string[]>(addrCount);}}// 拷贝构造函数CopyAndSwapCustomer(const CopyAndSwapCustomer& rhs): name(rhs.name),balance(rhs.balance),customerId(rhs.customerId),addressCount(rhs.addressCount) {if (addressCount > 0) {addresses = std::make_unique<std::string[]>(addressCount);for (size_t i = 0; i < addressCount; ++i) {addresses[i] = rhs.addresses[i];}}}// 统一的赋值操作符 - 按值传参(拷贝并交换)CopyAndSwapCustomer& operator=(CopyAndSwapCustomer rhs) { // 注意:按值传参swap(*this, rhs);return *this;}// 移动构造函数CopyAndSwapCustomer(CopyAndSwapCustomer&& rhs) noexcept: CopyAndSwapCustomer("", 0) {  // 委托构造到默认状态swap(*this, rhs);}void setAddress(size_t index, const std::string& address) {if (index < addressCount) {addresses[index] = address;}}void printInfo() const {std::cout << "客户: " << name << " (ID: " << customerId << ")\n"<< "  余额: " << balance << "\n"<< "  地址数量: " << addressCount << std::endl;}
};

继承体系中的特殊考量

1. 虚拷贝习惯用法(克隆模式)

多态对象的正确拷贝:

class CloneableShape {
public:virtual ~CloneableShape() = default;// 虚拷贝习惯用法virtual std::unique_ptr<CloneableShape> clone() const = 0;virtual void draw() const = 0;virtual double area() const = 0;
};class CloneableCircle : public CloneableShape {
private:double radius;std::string color;public:CloneableCircle(double r, const std::string& c) : radius(r), color(c) {}// 实现克隆方法 - 协变返回类型(C++支持)std::unique_ptr<CloneableCircle> clone() const {return std::make_unique<CloneableCircle>(*this);}// 覆盖基类版本(返回基类指针)std::unique_ptr<CloneableShape> clone() const override {return std::make_unique<CloneableCircle>(*this);}void draw() const override {std::cout << "绘制圆形: 半径=" << radius << ", 颜色=" << color << std::endl;}double area() const override {return 3.14159 * radius * radius;}
};class CloneableRectangle : public CloneableShape {
private:double width, height;std::string fillColor;std::string borderColor;public:CloneableRectangle(double w, double h, const std::string& fill, const std::string& border): width(w), height(h), fillColor(fill), borderColor(border) {}std::unique_ptr<CloneableRectangle> clone() const {return std::make_unique<CloneableRectangle>(*this);}std::unique_ptr<CloneableShape> clone() const override {return std::make_unique<CloneableRectangle>(*this);}void draw() const override {std::cout << "绘制矩形: " << width << "x" << height << ", 填充=" << fillColor << ", 边框=" << borderColor << std::endl;}double area() const override {return width * height;}
};void demonstrate_polymorphic_copy() {std::vector<std::unique_ptr<CloneableShape>> shapes;shapes.push_back(std::make_unique<CloneableCircle>(5.0, "红色"));shapes.push_back(std::make_unique<CloneableRectangle>(4.0, 6.0, "蓝色", "黑色"));// 创建副本集合std::vector<std::unique_ptr<CloneableShape>> copies;for (const auto& shape : shapes) {copies.push_back(shape->clone());  // 多态拷贝!}// 验证拷贝for (size_t i = 0; i < shapes.size(); ++i) {std::cout << "原始: ";shapes[i]->draw();std::cout << "副本: ";copies[i]->draw();std::cout << "面积: " << shapes[i]->area() << " vs " << copies[i]->area() << std::endl;}
}

实战案例:复杂系统的完整拷贝

案例1:游戏引擎的实体组件系统
class Component {
public:virtual ~Component() = default;// 虚拷贝接口virtual std::unique_ptr<Component> clone() const = 0;virtual void update(float deltaTime) = 0;virtual void serialize(std::ostream& os) const = 0;
};class TransformComponent : public Component {
private:double x, y, z;double rotation;double scaleX, scaleY, scaleZ;std::string tag;public:TransformComponent(double x = 0, double y = 0, double z = 0, const std::string& tag = ""): x(x), y(y), z(z), rotation(0), scaleX(1), scaleY(1), scaleZ(1), tag(tag) {}std::unique_ptr<Component> clone() const override {return std::make_unique<TransformComponent>(*this);}// 拷贝构造函数 - 确保所有成员都被复制TransformComponent(const TransformComponent& rhs): x(rhs.x), y(rhs.y), z(rhs.z),rotation(rhs.rotation),scaleX(rhs.scaleX), scaleY(rhs.scaleY), scaleZ(rhs.scaleZ),tag(rhs.tag) {std::cout << "TransformComponent完整拷贝" << std::endl;}// 拷贝赋值运算符TransformComponent& operator=(const TransformComponent& rhs) {if (this != &rhs) {x = rhs.x;y = rhs.y;z = rhs.z;rotation = rhs.rotation;scaleX = rhs.scaleX;scaleY = rhs.scaleY;scaleZ = rhs.scaleZ;tag = rhs.tag;}return *this;}void update(float deltaTime) override {// 变换更新逻辑}void serialize(std::ostream& os) const override {os << "Transform: (" << x << ", " << y << ", " << z << "), "<< "Rotation: " << rotation << ", "<< "Scale: (" << scaleX << ", " << scaleY << ", " << scaleZ << "), "<< "Tag: " << tag;}void setPosition(double newX, double newY, double newZ) {x = newX; y = newY; z = newZ;}void setTag(const std::string& newTag) {tag = newTag;}
};class PhysicsComponent : public Component {
private:double velocityX, velocityY, velocityZ;double mass;bool isStatic;std::vector<std::string> collisionTags;public:PhysicsComponent(double mass = 1.0, bool isStatic = false): velocityX(0), velocityY(0), velocityZ(0), mass(mass), isStatic(isStatic) {}std::unique_ptr<Component> clone() const override {return std::make_unique<PhysicsComponent>(*this);}// 完整的拷贝实现PhysicsComponent(const PhysicsComponent& rhs): velocityX(rhs.velocityX), velocityY(rhs.velocityY), velocityZ(rhs.velocityZ),mass(rhs.mass), isStatic(rhs.isStatic),collisionTags(rhs.collisionTags) {  // vector自动深拷贝}PhysicsComponent& operator=(const PhysicsComponent& rhs) {if (this != &rhs) {velocityX = rhs.velocityX;velocityY = rhs.velocityY;velocityZ = rhs.velocityZ;mass = rhs.mass;isStatic = rhs.isStatic;collisionTags = rhs.collisionTags;}return *this;}void update(float deltaTime) override {if (!isStatic) {// 物理模拟逻辑}}void serialize(std::ostream& os) const override {os << "Physics: Velocity(" << velocityX << ", " << velocityY << ", " << velocityZ << "), "<< "Mass: " << mass << ", "<< "Static: " << isStatic << ", "<< "CollisionTags: " << collisionTags.size();}void addCollisionTag(const std::string& tag) {collisionTags.push_back(tag);}
};class GameEntity {
private:std::string name;int entityId;std::vector<std::unique_ptr<Component>> components;public:GameEntity(const std::string& name, int id) : name(name), entityId(id) {}// 拷贝构造函数 - 深拷贝所有组件GameEntity(const GameEntity& rhs): name(rhs.name), entityId(rhs.entityId) {// 深拷贝所有组件for (const auto& component : rhs.components) {components.push_back(component->clone());}std::cout << "GameEntity深拷贝完成,组件数量: " << components.size() << std::endl;}// 拷贝赋值运算符GameEntity& operator=(const GameEntity& rhs) {if (this != &rhs) {name = rhs.name;entityId = rhs.entityId;// 清空当前组件components.clear();// 深拷贝所有组件for (const auto& component : rhs.components) {components.push_back(component->clone());}}return *this;}template<typename T, typename... Args>T& addComponent(Args&&... args) {auto component = std::make_unique<T>(std::forward<Args>(args)...);T& ref = *component;components.push_back(std::move(component));return ref;}void update(float deltaTime) {for (const auto& component : components) {component->update(deltaTime);}}void serialize(std::ostream& os) const {os << "Entity: " << name << " (ID: " << entityId << ")\n";for (const auto& component : components) {component->serialize(os);os << "\n";}}
};void demonstrate_game_entity_copy() {GameEntity original("玩家", 1);auto& transform = original.addComponent<TransformComponent>(10.0, 5.0, 0.0, "Player");auto& physics = original.addComponent<PhysicsComponent>(75.0, false);physics.addCollisionTag("Player");physics.addCollisionTag("Dynamic");std::cout << "=== 原始实体 ===" << std::endl;original.serialize(std::cout);// 拷贝实体 - 所有组件都被正确深拷贝GameEntity copy = original;// 修改拷贝,验证独立性copy.addComponent<TransformComponent>(20.0, 10.0, 0.0, "Enemy"); // 添加新组件std::cout << "\n=== 拷贝实体 (修改后) ===" << std::endl;copy.serialize(std::cout);std::cout << "\n=== 原始实体 (未改变) ===" << std::endl;original.serialize(std::cout);
}

关键洞见与行动指南

必须遵守的核心规则:
  1. 拷贝所有成员变量:在拷贝构造函数和拷贝赋值运算符中复制每个数据成员
  2. 不要忘记基类部分:在派生类拷贝操作中显式调用基类对应操作
  3. 处理动态资源:确保深拷贝所有指针和动态分配的资源
  4. 保持异常安全:在拷贝赋值中提供基本的异常安全保证
现代C++开发建议:
  1. 优先使用=default:当所有成员都有正确拷贝语义时,让编译器生成拷贝操作
  2. 使用拷贝并交换:提供异常安全且简洁的拷贝赋值实现
  3. 利用智能指针unique_ptrshared_ptr自动处理资源管理
  4. 实现克隆模式:为多态类提供虚拷贝接口
设计原则总结:
  1. 完整性原则:拷贝操作必须复制对象的完整状态
  2. 独立性原则:拷贝后的对象应该与原始对象完全独立
  3. 一致性原则:拷贝构造函数和拷贝赋值运算符应该产生相同的结果
  4. 可维护性原则:代码应该易于维护和扩展,新增成员时不容易遗漏
需要警惕的陷阱:
  1. 新增成员遗忘:添加新数据成员时忘记更新拷贝操作
  2. 基类拷贝遗漏:派生类拷贝时忘记调用基类拷贝操作
  3. 静态成员误拷贝:错误地拷贝静态数据成员
  4. 自赋值问题:在拷贝赋值中未处理自我赋值情况

最终建议: 将完整拷贝视为C++类设计的基本契约。培养"拷贝完整性思维"——在实现每个拷贝操作时都问自己:“这个操作是否复制了对象的完整状态?包括所有基类部分和所有数据成员?” 这种系统性的思考方式是构建正确C++类的关键。

记住:在C++对象拷贝中,完整性不是可选项,而是正确性的基本要求。 条款12教会我们的不仅是一个技术细节,更是对对象语义完整性的深刻理解。

http://www.dtcms.com/a/570901.html

相关文章:

  • 万网是什么网站关于网站的建设论文
  • 国务院办公厅关于2026年部分节假日安排的通知
  • 模板网站建设青岛网站建设的参考书籍
  • 番禺建设局网站首页怎么建个人公司网站
  • 织梦手机网站有广告位中英文网站开发公司
  • Spring-cloud 主键Eureka
  • 宜兴公司做网站高端 网站定制
  • 成都营销型网站建设推广怎么建设国外免费网站
  • 算法72. 编辑距离
  • 制作网站的软件下载金科做的网站
  • 黑龙江省建设工程招标网站外包网有哪些
  • 织梦做的网站为什么显示404临沂做网站哪里好
  • 做美食教程的网站有哪些快速排名优化推广价格
  • 本地佛山顺德网站建设外贸求购信息网
  • 专业新站整站快速排名公司windows2008 iis 网站
  • 从计数器到令牌桶:三种限流方案的落地与取舍
  • 信号与槽
  • 定制型网站建设移动网站mip
  • 网络系统管理与维护形考任务2sem优化软件选哪家
  • 小语种网站自己做网站能赚钱
  • 自己学习建设网站做老托福听力的网站
  • 设计商城网站建设服装设计参考网站
  • wordpress格式化sql串天津网络优化网站建设
  • 如何检测网站的打开速度游戏小程序开发报价
  • 网站被k原因网站设置关键字
  • VQ-VAE 代码详细解析及记录
  • 网站建设技术公司企业公示信息查询官网
  • 温州网站建设得花多少钱做自媒体挣钱的网站有哪些
  • p2p网站建设公司wordpress心情
  • pageadmin自助建站专业网页设计制作价格