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

汽车网有哪些网站大全广州市建设招标管理办公室网站

汽车网有哪些网站大全,广州市建设招标管理办公室网站,软件开发接单网站,网站开发环境的意义C(2)之进阶语法 Author: Once Day Date: 2024年9月20日 一位热衷于Linux学习和开发的菜鸟#xff0c;试图谱写一场冒险之旅#xff0c;也许终点只是一场白日梦… 漫漫长路#xff0c;有人对你微笑过嘛… 全系列文章可参考专栏: 源码分析_Once-Day的博客-CSDN博客 参考文… C(2)之进阶语法 Author: Once Day Date: 2024年9月20日 一位热衷于Linux学习和开发的菜鸟试图谱写一场冒险之旅也许终点只是一场白日梦… 漫漫长路有人对你微笑过嘛… 全系列文章可参考专栏: 源码分析_Once-Day的博客-CSDN博客 参考文章: C 基本语法_w3cschoolLearn C – Skill up with our free tutorials (learncpp.com)词法约定 | Microsoft LearnGitHub - isocpp/CppCoreGuidelines: The C Core Guidelines are a set of tried-and-true guidelines, rules, and best practices about coding in CC(10)类语法分析(1)-CSDN博客C(11)类语法分析(2)-CSDN博客 文章目录 C(2)之进阶语法1. 面对对象编程1.1 过程性编程和面对对象编程1.2 派生类和基类1.3 多态公有继承1.4 虚函数工作原理1.5 抽象基类ABC 2. 代码重用2.1 接口和实现2.2 私有继承2.3 虚基类(多重继承) 3. 类模板3.1 类模版定义和使用3.2 递归使用(模版数组和非类型参数)3.3 将模版用作参数3.4 模板类和友元3.5 模版别名 4. 特殊用法4.1 嵌套类4.2 异常机制4.3 异常安全代码4.3 RTTI(运行期类型)4.5 类型转换符 1. 面对对象编程 参考文档 继承 (C) | Microsoft Learn 1.1 过程性编程和面对对象编程 C是一种多范式编程语言支持多种编程风格和思想。其中过程性编程(Procedural Programming)和面向对象编程(Object-Oriented Programming简称OOP)是C的两大主要编程范式。 过程性编程是一种以过程(函数)为中心组织代码的编程范式。在过程性编程中程序被视为一系列的过程调用每个过程完成特定的任务。过程性编程强调算法和数据结构的设计关注问题的解决步骤和流程。 C支持过程性编程的特点包括 函数使用函数来封装一组相关的语句完成特定的任务。函数接受输入参数执行指定的操作并可以返回结果。数据类型提供了丰富的内置数据类型如整型、浮点型、字符型等用于表示和操作数据。控制结构提供了各种控制结构如条件语句if-else、循环语句for、while等用于控制程序的执行流程。指针和引用支持指针和引用允许直接访问和操作内存中的数据提供了灵活而高效的内存管理机制。 过程性编程适用于解决简单的、线性的问题强调算法的设计和实现。 面向对象编程是一种以对象为中心组织代码的编程范式。在面向对象编程中程序被视为一组相互交互的对象每个对象封装了数据和操作数据的方法。面向对象编程强调数据的组织和封装关注问题域中的实体及其关系。 C支持面向对象编程的特点包括 类和对象使用类来定义对象的蓝图包括数据成员和成员函数。通过类的实例化可以创建具体的对象。封装通过访问修饰符(如public、private)来控制类的数据和方法的可见性实现数据隐藏和封装。继承支持类的继承允许派生类继承基类的数据和方法实现代码的复用和扩展。多态通过虚函数和动态绑定实现多态允许在运行时根据对象的实际类型调用相应的方法。运算符重载允许为用户定义的类型重载运算符使得对象可以像内置类型一样进行操作。 面向对象编程适用于解决复杂的、具有层次结构的问题强调对问题域的抽象和建模。 1.2 派生类和基类 参考文档 C 继承_w3cschool继承 (C) | Microsoft Learn单个继承 | Microsoft Learn 在C中派生类(derived class)和基类(base class)是面向对象编程(OOP)中非常重要的概念。它们之间的关系体现了继承(inheritance)的思想即派生类可以继承基类的成员变量和成员函数从而实现代码的复用和扩展。 (1) 访问权限在派生类中可以通过访问说明符来控制从基类继承的成员的访问权限。 C提供了三种访问说明符 public(公有继承)基类的public和protected成员在派生类中保持原有访问权限。protected(保护继承)基类的public和protected成员在派生类中变为protected权限。private(私有继承)基类的public和protected成员在派生类中变为private权限。 需要注意的是基类的private成员在派生类中是不可直接访问的但派生类可以通过基类的公有或保护成员函数来间接访问这些私有成员。 (2) 初始化方式派生类的构造函数需要负责初始化派生类自己的成员同时也需要调用基类的构造函数来初始化继承自基类的成员。派生类构造函数可以在初始化列表中显式地调用基类的构造函数并传递必要的参数。如果派生类没有显式调用基类构造函数编译器会自动调用基类的默认构造函数(如果存在)。 下面是一个简单的示例演示了派生类和基类的基本使用 class Shape { protected:int width;int height; public:Shape(int w, int h) : width(w), height(h) {}virtual int getArea() { return 0; } };class Rectangle : public Shape { public:Rectangle(int w, int h) : Shape(w, h) {}int getArea() override { return width * height; } };class Square : public Rectangle { public:Square(int size) : Rectangle(size, size) {} };int main() {Rectangle rect(4, 5);Square square(3);std::cout Rectangle area: rect.getArea() std::endl;std::cout Square area: square.getArea() std::endl;return 0; }在上面的例子中Shape是基类Rectangle和Square是派生类。Shape类有两个protected成员变量width和height以及一个虚函数getArea()。Rectangle类公有继承自Shape类并重写了getArea()函数来计算矩形的面积。Square类公有继承自Rectangle类它的构造函数只接受一个参数size并将其传递给Rectangle类的构造函数来初始化宽度和高度。 1.3 多态公有继承 在C中多态(polymorphism)是面向对象编程的重要特性之一它允许我们通过基类的指针或引用来调用派生类的方法实现运行时的动态绑定。 (1) 派生类中重新定义基类的方法(重载)在派生类中可以重新定义基类中的方法这称为方法的重载(overriding)。当通过派生类对象调用重载的方法时会执行派生类中的实现而不是基类中的实现。这允许派生类根据自己的需求对基类的方法进行定制和扩展。 (2) 使用虚方法为了实现多态需要在基类中将要被派生类重载的方法声明为虚方法(virtual method)。虚方法的声明以关键字 “virtual” 开头例如virtual void foo();。当通过基类的指针或引用调用虚方法时会根据实际对象的类型来执行相应的派生类中的实现这称为动态绑定(dynamic binding)或运行时多态(runtime polymorphism)。 (3) 使用作用域解析运算符调用基类方法有时派生类重载了基类的方法但仍然需要调用基类中的实现。在这种情况下可以使用作用域解析运算符(::)来显式地调用基类的方法。例如在派生类的方法中可以通过BaseClass::foo(); 来调用基类的 “foo” 方法。 (4) 虚析构函数在使用多态时经常会通过基类的指针来管理派生类对象。当删除这些对象时如果基类的析构函数不是虚函数则只会调用基类的析构函数而不会调用派生类的析构函数可能导致资源泄漏。因此在设计基类时如果预期会被用于多态就应该将其析构函数声明为虚函数即 “virtual ~BaseClass();”。这样可以确保在删除基类指针时会先调用派生类的析构函数然后再调用基类的析构函数保证正确的清理和资源释放。 下面是一个示例演示了多态公有继承的各个特点 class Shape { public:virtual double getArea() {return 0.0;}virtual void printInfo() {std::cout This is a shape. std::endl;}virtual ~Shape() {std::cout Shape destructor called. std::endl;} };class Circle : public Shape { private:double radius; public:Circle(double r) : radius(r) {}double getArea() override {return 3.14159 * radius * radius;}void printInfo() override {Shape::printInfo();std::cout This is a circle. std::endl;}~Circle() {std::cout Circle destructor called. std::endl;} };int main() {Shape* shape new Circle(2.0);std::cout Area: shape-getArea() std::endl;shape-printInfo();delete shape;return 0; }在这个例子中Shape是基类其中的getArea和printInfo方法都是虚函数。Circle是派生类它重载了这两个方法。在main函数中创建了一个Circle对象但是通过Shape的指针来管理它。当调用getArea和printInfo方法时实际执行的是Circle类中的实现体现了多态的特性。 在Circle的printInfo方法中使用作用域解析运算符Shape::printInfo()来调用基类的实现然后再输出一条特定于Circle的信息。 最后在Shape和Circle类中都定义了虚析构函数。当通过delete shape;删除对象时会先调用Circle的析构函数然后再调用Shape的析构函数确保正确的清理和资源释放。 1.4 虚函数工作原理 参考文档虚函数 | Microsoft Learn 虚函数是C实现多态的重要机制它允许通过基类的指针或引用来调用派生类的方法。 (1) 虚函数表(Virtual Function Tablevtable)当一个类包含虚函数时编译器会为该类生成一个虚函数表。虚函数表是一个存储虚函数指针的数组每个虚函数在表中都有一个对应的条目。每个包含虚函数的类对象都有一个指向虚函数表的指针通常称为vptr该指针在对象创建时自动设置。当通过指针或引用调用虚函数时编译器会通过对象的vptr找到虚函数表并从表中获取相应的函数指针来调用实际的函数实现。 (2) 静态绑定和动态绑定静态绑定(static binding)是在编译时确定函数调用的目标而动态绑定(dynamic binding)则是在运行时根据实际对象的类型来确定调用哪个函数。对于非虚函数编译器使用静态联编因为函数的调用目标在编译时就可以确定。对于虚函数编译器使用动态联编因为实际调用的函数要到运行时根据对象的实际类型来确定。动态联编是通过虚函数表来实现的。 (3) 重载函数与虚函数原型匹配当派生类重载虚函数时重载函数的原型(函数名、参数类型和顺序)必须与基类中虚函数的原型完全匹配。如果派生类的重载函数与基类的虚函数原型不匹配那么派生类的函数将被视为一个新的函数而不是对基类虚函数的重载。这可能导致意外的行为因为通过基类指针或引用调用时仍然会调用基类的虚函数而不是派生类的新函数。 (4) 不能成为虚函数的特殊函数某些特殊的成员函数不能被声明为虚函数包括 构造函数构造函数负责初始化对象在对象创建时自动调用不能通过指针或引用来调用因此不能是虚函数。静态成员函数静态成员函数属于类而不属于对象不能访问非静态的成员变量和成员函数因此不能是虚函数。内联函数内联函数在编译时展开不会通过函数调用的方式执行因此不能是虚函数。友元函数友元函数不是类的成员函数因此不能是虚函数。 下面是一个示例演示了虚函数的工作原理 class Base { public:virtual void foo() {std::cout Base::foo() std::endl;}void bar() {std::cout Base::bar() std::endl;} };class Derived : public Base { public:void foo() override {std::cout Derived::foo() std::endl;}void bar() {std::cout Derived::bar() std::endl;} };int main() {Base* ptr new Derived();ptr-foo(); // 输出 Derived::foo()动态联编ptr-bar(); // 输出 Base::bar()静态联编delete ptr;return 0; }在这个例子中Base类有一个虚函数foo和一个非虚函数bar。Derived类继承自Base类并重载了foo和bar函数。在main函数中创建了一个Derived对象但是通过Base的指针ptr来管理它。 当调用ptr-foo()时会使用动态联编根据实际对象的类型Derived来调用Derived::foo()。这是因为foo是一个虚函数编译器通过虚函数表来确定实际调用的函数。 当调用ptr-bar()时会使用静态联编调用Base::bar()。这是因为bar不是虚函数在编译时就确定了调用的目标。 1.5 抽象基类ABC 抽象基类(Abstract Base ClassABC)是一种特殊的类它被设计为其他类的基类但不能直接实例化。在C中抽象基类至少包含一个纯虚函数(pure virtual function)。抽象基类主要用于定义一个通用的接口供派生类继承和实现。 (1) 纯虚函数是一种特殊的虚函数在函数声明后加上 “ 0”表示该函数没有实现必须由派生类提供实现。例如 class Shape { public:virtual double getArea() 0; };在这个例子中getArea是一个纯虚函数没有函数体。包含纯虚函数的类被称为抽象基类不能直接实例化。 (2) 使用方法抽象基类主要用于定义一个通用的接口供派生类继承和实现。派生类必须实现所有的纯虚函数才能被实例化。如果派生类没有实现所有的纯虚函数那么派生类也会成为抽象基类。 class Circle : public Shape { public:Circle(double r) : radius(r) {}double getArea() override {return 3.14159 * radius * radius;} private:double radius; };在这个例子中Circle类继承自抽象基类Shape并实现了纯虚函数getArea。因此Circle类可以被实例化。 抽象基类的使用存在一些限制 抽象基类不能直接实例化只能作为其他类的基类。抽象基类的构造函数可以被调用但只能在派生类的构造函数中通过初始化列表调用。如果一个类从抽象基类派生那么它必须实现所有的纯虚函数否则该类也会成为抽象基类。抽象基类的析构函数通常应该是虚函数以确保在销毁派生类对象时派生类的析构函数被正确调用。 下面是一个完整的示例演示了抽象基类的使用 class Shape { public:virtual double getArea() 0;virtual double getPerimeter() 0;virtual ~Shape() {} };class Circle : public Shape { public:Circle(double r) : radius(r) {}double getArea() override {return 3.14159 * radius * radius;}double getPerimeter() override {return 2 * 3.14159 * radius;} private:double radius; };class Rectangle : public Shape { public:Rectangle(double w, double h) : width(w), height(h) {}double getArea() override {return width * height;}double getPerimeter() override {return 2 * (width height);} private:double width;double height; };int main() {Shape* shapes[2];shapes[0] new Circle(2.0);shapes[1] new Rectangle(3.0, 4.0);for (int i 0; i 2; i) {std::cout Area: shapes[i]-getArea() std::endl;std::cout Perimeter: shapes[i]-getPerimeter() std::endl;delete shapes[i];}return 0; }在这个例子中Shape是一个抽象基类包含两个纯虚函数getArea和getPerimeter。Circle和Rectangle类继承自Shape并实现了这两个纯虚函数。 在main函数中我们创建了一个Shape指针数组并分别存储了Circle和Rectangle对象的指针。通过基类指针可以调用派生类实现的虚函数实现多态。 最后通过基类指针删除派生类对象确保正确的析构函数被调用。 2. 代码重用 2.1 接口和实现 在C中接口(interface)和实现(implementation)是面向对象设计中的两个重要概念。接口定义了类的公共行为而实现则是类的内部工作方式C通过类的继承和组合来实现接口和实现的分离。 (1) is-a关系(继承)表示一个类是另一个类的一种特殊类型。在C中is-a关系通过公有继承(public inheritance)来实现。派生类继承了基类的接口和实现因此派生类对象可以被视为基类对象的一种特殊形式。 class Animal { public:virtual void eat() {std::cout Animal is eating. std::endl;} };class Cat : public Animal { public:void eat() override {std::cout Cat is eating. std::endl;}void meow() {std::cout Cat is meowing. std::endl;} };在这个例子中Cat类公有继承自Animal类表示猫是一种动物。Cat类继承了Animal类的eat方法并提供了自己的实现。此外Cat类还新增了一个meow方法。 (2) has-a关系(组合)has-a关系表示一个类包含另一个类的对象作为其数据成员。在C中has-a关系通过组合(composition)或聚合(aggregation)来实现。组合表示被包含的对象与包含它的对象具有相同的生命周期而聚合则表示被包含的对象可以独立于包含它的对象而存在。 class Engine { public:void start() {std::cout Engine started. std::endl;} };class Car { public:void startEngine() {engine.start();} private:Engine engine; };在这个例子中Car类通过组合的方式包含了一个Engine类的对象。这表示汽车有一个引擎。Car类的startEngine方法调用了Engine对象的start方法。 2.2 私有继承 在私有继承(private inheritance)中基类的公有成员和保护成员在派生类中会成为私有成员。这意味着基类的接口不会成为派生类的接口的一部分从而限制了类外对基类成员的访问。 在私有继承中派生类可以在类内部使用基类的公有和保护成员但在类外部这些成员是不可访问的。为了在派生类内部访问基类的成员需要使用基类的类名和作用域解析运算符(:。 class Base { public:void foo() {std::cout Base::foo() std::endl;} };class Derived : private Base { public:void bar() {Base::foo(); // 在派生类内部使用作用域解析运算符访问基类成员} };int main() {Derived d;d.bar(); // 输出 Base::foo()// d.foo(); // 错误foo在派生类外部不可访问return 0; }在某些情况下私有继承可以被包含(composition)所替代。包含是指在一个类中将另一个类的对象作为数据成员。通过包含可以在类内部访问被包含对象的公有成员而不会暴露给类外部。 class Base { public:void foo() {std::cout Base::foo() std::endl;} };class Derived { public:void bar() {base.foo(); // 通过包含的对象访问基类成员} private:Base base; };int main() {Derived d;d.bar(); // 输出 Base::foo()return 0; }在私有继承中如果想要暴露某些基类的成员给派生类外部使用可以在派生类中使用using声明来重新定义这些成员的访问权限。 class Base { public:void foo() {std::cout Base::foo() std::endl;} };class Derived : private Base { public:using Base::foo; // 使用using声明将foo暴露给派生类外部 };int main() {Derived d;d.foo(); // 输出 Base::foo()return 0; }访问权限在公有继承中基类的公有成员在派生类中保持公有基类的保护成员在派生类中保持保护。而在私有继承中基类的公有和保护成员在派生类中都会成为私有成员。 公有继承体现了is-a关系派生类对象可以被视为基类对象。而私有继承不表示is-a关系派生类对象不能被视为基类对象。公有继承主要用于实现多态和代码重用派生类继承基类的接口和实现。而私有继承主要用于代码重用派生类只继承基类的实现而不继承接口。 2.3 虚基类(多重继承) 虚基类virtual base class是C中用于解决多重继承下菱形继承diamond inheritance问题的一种机制。在菱形继承中一个派生类从多个基类继承而这些基类又共享一个公共的基类。这种情况下派生类会继承多份公共基类的成员导致二义性和冗余。虚基类通过在继承关系中引入虚拟继承确保派生类只继承一份公共基类的成员。下面我将详细介绍虚基类的相关概念包括多个子对象和一个虚子对象的情况以及存在的二义性访问问题。 多个子对象的情况 在普通的多重继承中如果一个派生类从多个基类继承而这些基类又共享一个公共的基类那么派生类会包含多个公共基类的子对象。每个子对象都有自己的数据成员和成员函数这可能导致冗余和不必要的内存开销。 class Base { public:int data; };class Derived1 : public Base {}; class Derived2 : public Base {};class Diamond : public Derived1, public Derived2 {};int main() {Diamond d;// d.data 10; // 错误访问二义性d.Derived1::data 10; // 显式指定访问 Derived1 的 datad.Derived2::data 20; // 显式指定访问 Derived2 的 datareturn 0; }在这个例子中Diamond类从Derived1和Derived2继承而它们都继承自Base类。这导致Diamond类包含两个Base类的子对象每个子对象都有自己的data成员。当我们尝试直接访问d.data时会产生编译错误因为存在访问二义性。我们需要显式指定访问哪个基类的data成员。 一个虚子对象的情况 通过在继承关系中引入虚拟继承可以确保派生类只包含一个公共基类的子对象称为虚子对象。虚拟继承使用virtual关键字来声明基类。 class Base { public:int data; };class Derived1 : virtual public Base {}; class Derived2 : virtual public Base {};class Diamond : public Derived1, public Derived2 {};int main() {Diamond d;d.data 10; // 正确只有一个 Base 子对象return 0; }在这个例子中Derived1和Derived2通过虚拟继承自Base类。这意味着Diamond类只包含一个Base类的虚子对象。现在我们可以直接访问d.data不会产生二义性因为只有一个Base子对象。 二义性访问问题 在菱形继承中如果派生类从多个基类继承而这些基类又共享一个公共的基类那么在访问公共基类的成员时可能会产生二义性。编译器无法确定应该访问哪个基类的成员。 class Base { public:void foo() {std::cout Base::foo() std::endl;} };class Derived1 : public Base { public:void foo() {std::cout Derived1::foo() std::endl;} };class Derived2 : public Base { public:void foo() {std::cout Derived2::foo() std::endl;} };class Diamond : public Derived1, public Derived2 {};int main() {Diamond d;// d.foo(); // 错误访问二义性d.Derived1::foo(); // 显式指定访问 Derived1 的 food.Derived2::foo(); // 显式指定访问 Derived2 的 fooreturn 0; }在这个例子中Diamond类从Derived1和Derived2继承它们都重写了Base类的foo函数。当我们尝试调用d.foo()时会产生编译错误因为存在访问二义性。编译器无法确定应该调用Derived1::foo()还是Derived2::foo()。我们需要显式指定调用哪个基类的foo函数。 为了解决这个问题可以使用虚拟继承 class Base { public:virtual void foo() {std::cout Base::foo() std::endl;} };class Derived1 : virtual public Base { public:void foo() override {std::cout Derived1::foo() std::endl;} };class Derived2 : virtual public Base { public:void foo() override {std::cout Derived2::foo() std::endl;} };class Diamond : public Derived1, public Derived2 { public:void foo() override {std::cout Diamond::foo() std::endl;} };int main() {Diamond d;d.foo(); // 正确调用 Diamond::foo()return 0; }在这个例子中Derived1和Derived2通过虚拟继承自Base类Diamond类重写了foo函数。现在当我们调用d.foo()时编译器会根据虚函数的规则调用Diamond::foo()。虚拟继承确保了只有一个Base子对象消除了二义性。 总的来说虚基类通过虚拟继承解决了多重继承下的菱形继承问题。它确保派生类只包含一个公共基类的子对象避免了数据冗余和访问二义性。虚拟继承使用virtual关键字来声明基类派生类通过虚拟继承自基类。在设计类层次结构时如果存在菱形继承的情况使用虚基类可以简化继承关系提高代码的可维护性。同时也要注意虚拟继承会带来一定的内存和性能开销应根据实际需求权衡使用。 3. 类模板 3.1 类模版定义和使用 参考文档 模板 (C) | Microsoft Learntypename | Microsoft Learn类模板 | Microsoft Learn C类模板(class template)是一种通用的类定义允许对类的成员类型进行参数化。通过使用模板我们可以编写独立于具体数据类型的代码提高代码的复用性和灵活性。 (1) 定义类模板类模板的定义以关键字template开头后跟尖括号括号内包含一个或多个类型参数。类型参数可以是类类型、内置类型或其他模板类型。 template typename T class MyClass { public:void setData(const T value) {data value;}T getData() const {return data;} private:T data; };在这个例子中MyClass是一个类模板T是一个类型参数。类模板的成员函数和数据成员可以使用类型参数T作为类型。 (2) 实例化类模板要使用类模板需要提供具体的类型参数来实例化模板。可以通过显式指定类型参数或使用自动类型推导来实例化类模板。 MyClassint intObj; intObj.setData(42); int value intObj.getData();MyClassstd::string strObj; strObj.setData(Hello); std::string str strObj.getData();在这个例子中MyClassint和MyClassstd::string是MyClass类模板的两个不同实例化分别使用int和std::string作为类型参数。 (3) 模板参数推导在某些情况下编译器可以根据构造函数的参数类型自动推导类模板的类型参数。这称为模板参数推导。 MyClass intObj2(42); // 自动推导为 MyClassint MyClass strObj2 MyClass(Hello); // 自动推导为 MyClassconst char*(4) 多个类型参数类模板可以有多个类型参数用逗号分隔。多个类型参数允许更灵活地定义类模板。 template typename T1, typename T2 class Pair { public:Pair(const T1 first, const T2 second): first(first), second(second) {}T1 getFirst() const { return first; }T2 getSecond() const { return second; } private:T1 first;T2 second; };Pairint, std::string pair(42, Hello);(5) 默认模板参数类模板可以为类型参数提供默认值这样在实例化时可以省略对应的类型参数。 template typename T1, typename T2 std::string class MyPair {// ... };MyPairint pair1(42, Hello); // 使用默认的 T2 类型 std::string MyPairint, char pair2(42, A); // 显式指定 T2 类型为 char(6) 成员函数的定义类模板的成员函数可以在类内部定义也可以在类外部定义。在类外部定义成员函数时需要使用template关键字和类模板的类型参数列表。 template typename T class MyClass { public:void setData(const T value); private:T data; };template typename T void MyClassT::setData(const T value) {data value; }(7) 特化和偏特化类模板可以有特化和偏特化版本用于为特定的类型参数提供不同的实现。特化是为所有类型参数提供具体的类型偏特化是为部分类型参数提供具体的类型或条件。 // 主模板 template typename T class MyClass {// ... };// 特化版本针对 T 为 int 的情况 template class MyClassint {// ... };// 偏特化版本针对 T 为指针类型的情况 template typename T class MyClassT* {// ... };(8) 类模板的继承类模板可以继承自其他类或类模板。在继承时需要指定基类的类型参数。 template typename T class Base {// ... };template typename T class Derived : public BaseT {// ... };类模板是C泛型编程的重要工具通过将算法和数据结构与具体的数据类型解耦提供了一种编写通用、可重用代码的方式。 3.2 递归使用(模版数组和非类型参数) 在C中模板可以递归地使用即在模板定义中使用模板自身。这种递归使用模板的技术可以用于创建复杂的数据结构和算法。 (1) 递归使用模板是指在模板定义中使用模板自身作为类型参数或默认参数。这种递归使用可以用于创建递归的数据结构如链表、树等。下面是一个简单的递归模板示例 template typename T, typename U void struct Node {T data;NodeT, U* next; };template typename T struct NodeT, void {T data;NodeT* next; };在这个例子中Node模板递归地使用自身作为next指针的类型。当U为void时递归结束next指针的类型变为NodeT*。 (2) 模板数组是一种使用模板实现的多维数组。通过递归使用模板可以创建任意维度的模板数组。下面是一个模板数组的示例 template typename T, size_t N class Array { public:using value_type T;using iterator value_type*;using const_iterator const value_type*;constexpr size_t size() const { return N; }value_type operator[](size_t index) { return data[index]; }const value_type operator[](size_t index) const { return data[index]; }iterator begin() { return data; }iterator end() { return data N; }const_iterator begin() const { return data; }const_iterator end() const { return data N; }private:value_type data[N]; };template typename T, size_t N, size_t... Dimensions class MultiDimensionalArray : public ArrayMultiDimensionalArrayT, Dimensions..., N {};template typename T, size_t N class MultiDimensionalArrayT, N : public ArrayT, N {};在这个例子中Array模板表示一维数组MultiDimensionalArray模板通过递归使用Array模板来创建多维数组。MultiDimensionalArray模板接受一个类型参数T和一个或多个非类型参数Dimensions表示数组的维度。 在模板定义中除了类型参数还可以使用非类型参数。非类型参数是在编译时确定的常量值可以是整数、枚举、指针或引用。在上面的模板数组示例中N和Dimensions就是非类型参数表示数组的大小和维度。 模板数组的维度顺序与常规数组相反。在常规数组中第一个维度是最外层最后一个维度是最内层。而在模板数组中第一个维度是最内层最后一个维度是最外层。 using Matrix MultiDimensionalArrayint, 3, 4;在这个例子中Matrix是一个3行4列的二维数组。与常规数组相比行和列的顺序相反。 3.3 将模版用作参数 在C中模板可以使用另一个模板作为参数这种情况称为模板模板参数(template template parameter)。通过使用模板模板参数可以创建更加通用和灵活的模板代码。 (1) 模板模板参数的语法类似于普通的模板参数但在参数列表中使用关键字class或typename后跟模板名和可选的默认参数。 template template typename class Container class Wrapper { public:Containerint data;// ... };在这个例子中Container是一个模板模板参数表示一个接受单个类型参数的模板。Wrapper类内部可以使用Container模板并提供具体的类型参数。 (2) 使用模板模板参数时需要提供一个符合要求的模板作为实参。这个模板必须与模板模板参数的声明匹配即接受相同数量和类型的参数。 template typename T class Vector {// ... };WrapperVector wrapper;在这个例子中Vector模板作为Wrapper类的模板模板参数使用。Vector模板接受单个类型参数与Container模板模板参数的声明匹配。 (3) 模板模板参数也可以有默认值类似于普通的模板参数。默认值必须是一个与模板模板参数声明匹配的模板。 template typename T, template typename class Container std::vector class Wrapper { public:ContainerT data;// ... };Wrapperint wrapper; // 使用默认的 std::vector 作为容器在这个例子中Container模板模板参数有一个默认值std::vector。如果在实例化Wrapper类时没有提供容器模板将使用默认的std::vector。 (4) 模板模板参数也可以进行特化针对特定的模板提供不同的实现。 template template typename class Container class Wrapper {// 通用实现 };template class Wrapperstd::vector {// std::vector 的特化实现 };在这个例子中Wrapper类有一个通用实现和一个针对std::vector的特化实现。当使用std::vector作为容器模板时将使用特化版本。 模板模板参数有一些限制需要注意 模板模板参数只能接受类模板不能接受函数模板或别名模板。模板模板参数的参数列表必须与实参模板的参数列表匹配包括参数数量、类型和默认参数。模板模板参数不能推导模板参数的类型必须显式提供。 3.4 模板类和友元 当涉及到模版类时友元函数和友元类可以用来访问模版类的私有和保护成员。 (1) 非模版友元是指普通的函数或类它们被声明为模版类的友元。这种友元可以访问模版类的所有实例的私有和保护成员。 template typename T class MyClass { private:T data; public:MyClass(T val) : data(val) {}friend void printData(const MyClassT obj); };void printData(const MyClassint obj) {std::cout Data: obj.data std::endl; }int main() {MyClassint obj(42);printData(obj);return 0; }在这个例子中printData函数是MyClass的非模版友元。它可以访问MyClassint实例的私有成员data。 (2) 模版友元函数需要在模版类中使用friend关键字并在函数前面加上template关键字。 template typename T class MyClass { private:T data; public:MyClass(T val) : data(val) {}template typename Ufriend void printData(const MyClassU obj); };template typename U void printData(const MyClassU obj) {std::cout Data: obj.data std::endl; }在这个例子中在MyClass模版类中声明了一个模版友元函数printData。这个函数可以访问MyClass的私有成员data并且可以接受任意类型的MyClass对象作为参数。 模版友元函数的定义通常放在模版类的外部。它的定义需要以template关键字开头并指定模版参数。 template typename T class MyClass { private:T data; public:MyClass(T val) : data(val) {}template typename Ufriend void printData(const MyClassU obj); };template typename U void printData(const MyClassU obj) {std::cout Data: obj.data std::endl; }在这个例子中printData函数的定义放在了MyClass模版类的外部。它以template typename U开头表示这是一个模版函数并接受一个const MyClassU类型的参数。 调用模版友元函数与调用普通函数类似只需要提供模版类的实例作为参数即可。 MyClassint obj(42); printData(obj);在这个例子中创建了一个MyClassint类型的对象obj并将其传递给printData函数。printData函数可以访问obj的私有成员data并将其打印出来。 3.5 模版别名 在C11中引入了模版别名它提供了一种更简洁、更易读的方式来创建模版的别名。模版别名使用using关键字来定义。 (1) 为模版类型定义别名可以使用模版别名为模版类型定义一个新的名字。这在需要使用长模版类型名时特别有用。 template typename T using Vec std::vectorT, std::allocatorT;Vecint vec {1, 2, 3, 4, 5};在这个例子中使用using关键字定义了一个模版别名Vec它代表了std::vectorT, std::allocatorT类型。现在可以直接使用Vecint来声明一个整型的vector而不需要写出完整的类型名。 (2) 为模版定义别名也可以使用模版别名为整个模版定义一个新的名字。这在需要对现有模版进行简单修改时非常有用。 template typename T, typename U struct Pair {T first;U second; };template typename T using IntPair Pairint, T;IntPairstd::string pair {42, Hello};在这个例子中首先定义了一个名为Pair的模版结构体它包含两个类型为T和U的成员变量。然后使用using关键字定义了一个模版别名IntPair它表示Pairint, T类型。现在可以直接使用IntPairstd::string来声明一个包含整型和字符串的Pair对象。 (3) 简化复杂的模版类型模版别名可以用来简化复杂的模版类型提高代码的可读性。 template typename T, typename U, typename V using Triple std::tupleT, U, V;Tripleint, std::string, bool triple {42, Hello, true};在这个例子中使用using关键字定义了一个模版别名Triple它代表了std::tupleT, U, V类型。现在可以直接使用Tripleint, std::string, bool来声明一个包含整型、字符串和布尔值的tuple对象而不需要写出完整的std::tuple类型。 4. 特殊用法 4.1 嵌套类 参考文档嵌套类声明 | Microsoft Learn C中的嵌套类是指在另一个类的定义中定义的类。嵌套类也称为内部类它提供了一种将相关类组织在一起的方式并可以访问外部类的成员。 (1) 嵌套类的访问权限嵌套类可以在外部类的public、protected或private部分中声明。嵌套类的访问权限决定了它对外部类和其他类的可见性。 public嵌套类可以被外部类和其他类访问只有这个位置可以被外部空间直接访问。protected嵌套类可以被外部类及其派生类访问。private嵌套类只能被外部类访问。 class OuterClass { public:class PublicNestedClass {}; protected:class ProtectedNestedClass {}; private:class PrivateNestedClass {}; };(2) 嵌套类的作用域嵌套类位于外部类的作用域内因此它可以访问外部类的所有成员包括private成员。要在外部类外访问嵌套类需要使用外部类的作用域解析运算符::。 OuterClass::PublicNestedClass publicObj;(3) 嵌套类成员的访问权限嵌套类的成员也可以有自己的访问权限与普通类的成员类似。 嵌套类的public成员可以被外部类和其他具有访问权限的类访问。protected成员可以被外部类及其派生类访问。private成员只能被嵌套类自身访问。 class OuterClass { public:class NestedClass {public:void publicMethod() {}protected:void protectedMethod() {}private:void privateMethod() {}}; };OuterClass::NestedClass nestedObj; nestedObj.publicMethod(); // 可以访问 // nestedObj.protectedMethod(); // 错误不能访问 protected 成员 // nestedObj.privateMethod(); // 错误不能访问 private 成员(4) 嵌套类的友元函数嵌套类可以将外部类的成员函数声明为友元函数以允许这些函数访问嵌套类的私有成员。 class OuterClass { public:void outerMethod() {NestedClass nestedObj;nestedObj.privateMethod(); // 可以访问因为 outerMethod 是友元函数} private:class NestedClass {private:void privateMethod() {}friend void OuterClass::outerMethod();}; };4.2 异常机制 参考文档 MSVC 中的异常处理 | Microsoft LearnC 异常处理_w3cschool C的异常机制提供了一种处理程序运行时错误的方式。它允许在发生错误时抛出异常并在适当的位置捕获和处理这些异常。 (1) 基础使用方式 使用try块来包含可能抛出异常的代码。使用catch块来捕获和处理特定类型的异常。使用throw关键字来抛出异常。 try {// 可能抛出异常的代码if (error) {throw std::runtime_error(An error occurred);} } catch (const std::exception e) {// 处理异常std::cout Exception caught: e.what() std::endl; }2noexcept异常规范noexcept是C11引入的一个关键字用于指定函数是否会抛出异常。 如果函数声明为noexcept则表示它不会抛出任何异常。如果noexcept函数抛出异常程序将调用std::terminate()函数终止。 void func() noexcept {// 不会抛出异常的代码 }(3) 栈解退和栈对象自动析构 当异常被抛出时程序会沿着调用栈向上搜索匹配的catch块。在此过程中栈上的对象会被自动析构。 构造完成的对象在栈解退过程中会调用析构函数。构造未完成的对象不会调用析构函数。 void func() {MyClass obj1; // 构造完成MyClass* ptr new MyClass(); // 动态分配不在栈上MyClass obj2; // 构造完成throw std::exception(); // 抛出异常 } // obj2和obj1的析构函数会被调用但ptr指向的对象不会被自动释放(4) 异常基类和常见派生对象C标准库定义了一个异常基类std::exception以及几个常见的派生异常类。 std::logic_error逻辑错误如无效参数等。std::runtime_error运行时错误如除以零等。std::bad_alloc内存分配失败。std::out_of_range超出范围访问。 try {// ... } catch (const std::logic_error e) {// 处理逻辑错误 } catch (const std::runtime_error e) {// 处理运行时错误 } catch (const std::exception e) {// 处理其他标准库异常 }(5) 任意异常捕获可以使用...来捕获任意类型的异常。这种方式应谨慎使用因为它可能会捕获到意料之外的异常。 try {// ... } catch (...) {// 处理任意异常 }(6) 意外异常和未捕获异常的默认行为和自定义方式 如果抛出的异常没有被捕获程序将调用std::terminate()函数终止。可以使用std::set_terminate()函数来自定义未捕获异常的处理方式。 void custom_terminate() {std::cout Uncaught exception, terminating... std::endl;std::abort(); }int main() {std::set_terminate(custom_terminate);// ... }。4.3 异常安全代码 在C中实现异常安全性设计需要考虑多个方面。 (1) 异常安全的函数设计 函数要么完全成功要么将程序状态恢复到调用前的状态。这被称为强异常安全保证。如果无法提供强异常安全保证应该提供基本异常安全保证即确保程序状态保持一致不会泄漏资源或损坏数据。对于不抛出异常的函数可以使用noexcept关键字进行标记以便编译器进行优化。 (2) RAII(Resource Acquisition Is Initialization)技术 使用RAII技术来管理资源如内存、文件句柄等。将资源封装在类中在构造函数中获取资源在析构函数中释放资源。当异常发生时RAII对象会在栈解退过程中自动释放资源避免资源泄漏。 class FileHandle { public:FileHandle(const std::string filename) : file(std::fopen(filename.c_str(), r)) {if (!file) {throw std::runtime_error(Failed to open file);}}~FileHandle() {if (file) {std::fclose(file);}}// 其他成员函数... private:std::FILE* file; };3) 异常中立的代码 对于不抛出异常的代码如标准库函数需要进行适当的错误检查和处理。使用错误码、返回值等方式来表示错误状态而不是抛出异常。在调用这些函数时需要检查返回值并进行相应的处理。 int result std::printf(Hello, %s!\n, name); if (result 0) {// 处理错误 }4) 异常转换 当在异常代码和非异常代码之间进行交互时可能需要进行异常转换。将异常转换为错误码或其他形式的错误表示以便与非异常代码兼容。在适当的位置捕获异常并将其转换为相应的错误表示。 try {// 可能抛出异常的代码 } catch (const std::exception e) {// 将异常转换为错误码或其他形式的错误表示return ERROR_CODE; }(5) 异常规范和异常处理 对于可能抛出异常的函数使用异常规范如noexcept(false)来表明其异常行为。在调用这些函数时使用try-catch块来捕获和处理异常。对于未捕获的异常可以使用std::set_terminate()函数来指定自定义的终止处理程序。 4.3 RTTI(运行期类型) C的RTTIRun-Time Type Information运行时类型信息机制提供了在运行时确定对象类型的能力。RTTI主要包括dynamic_cast运算符、typeid运算符以及type_info类。 1)dynamic_cast用于在运行时执行多态类型的转换。它主要用于将基类指针或引用转换为派生类指针或引用。 如果转换成功dynamic_cast返回一个指向派生类对象的指针或引用。如果转换失败对于指针类型dynamic_cast返回nullptr对于引用类型dynamic_cast抛出std::bad_cast异常。 class Base { virtual void dummy() {} }; class Derived : public Base {};Base* ptr new Derived(); Derived* derived_ptr dynamic_castDerived*(ptr); if (derived_ptr) {// 转换成功 } else {// 转换失败 }(2) typeid运算符用于获取表达式的类型信息。它返回一个const std::type_info类型的对象该对象包含有关类型的信息。 对于多态类型typeid返回对象的实际类型信息。对于非多态类型typeid返回静态类型信息。 class Base { virtual void dummy() {} }; class Derived : public Base {};Base* ptr new Derived(); const std::type_info info typeid(*ptr); std::cout Type name: info.name() std::endl;(3) type_info类提供了有关类型的信息。它包含以下成员函数 const char* name() const返回类型的名称。bool operator(const type_info rhs) const比较两个type_info对象是否相等。bool before(const type_info rhs) const比较两个type_info对象的顺序。 class Base { virtual void dummy() {} }; class Derived : public Base {};Base* ptr1 new Base(); Base* ptr2 new Derived();const std::type_info info1 typeid(*ptr1); const std::type_info info2 typeid(*ptr2);if (info1 info2) {std::cout Types are the same std::endl; } else {std::cout Types are different std::endl; }需要注意的是使用RTTI机制会带来一些运行时开销因为它需要在运行时进行类型检查和转换。此外RTTI只适用于包含虚函数的类层次结构。 4.5 类型转换符 在C中有四个显式类型转换运算符dynamic_cast、const_cast、static_cast和reinterpret_cast。 1) dynamic_cast 用于在运行时执行多态类型的转换。主要用于将基类指针或引用转换为派生类指针或引用。如果转换成功返回一个指向派生类对象的指针或引用如果转换失败对于指针类型返回nullptr对于引用类型抛出std::bad_cast异常。只能用于包含虚函数的类层次结构。 class Base { virtual void dummy() {} }; class Derived : public Base {};Base* ptr new Derived(); Derived* derived_ptr dynamic_castDerived*(ptr);2) const_cast 用于移除或添加对象的const或volatile限定符。常用于将const指针或引用转换为非const指针或引用。不能用于去除静态类型的const性。 const int x 10; int* ptr const_castint*(x); *ptr 20; // 未定义行为3) static_cast 用于执行隐式类型转换如算术类型之间的转换、指针之间的转换等。不执行运行时类型检查。不能用于在不相关的类型之间进行转换。 int x 10; double y static_castdouble(x);Base* ptr new Derived(); Derived* derived_ptr static_castDerived*(ptr);4) reinterpret_cast 用于执行低级别的类型转换如将指针转换为整数、将一种指针类型转换为另一种指针类型等。不执行任何类型检查或转换仅重新解释内存中的比特位。使用时需要谨慎因为它可能导致未定义行为。 int x 10; int* ptr x; uintptr_t address reinterpret_castuintptr_t(ptr);总而言之dynamic_cast用于多态类型的运行时转换const_cast用于移除或添加const或volatile限定符static_cast用于执行隐式类型转换reinterpret_cast用于执行低级别的类型转换。
文章转载自:
http://www.morning.ghgck.cn.gov.cn.ghgck.cn
http://www.morning.fwkq.cn.gov.cn.fwkq.cn
http://www.morning.llyjx.cn.gov.cn.llyjx.cn
http://www.morning.dtzxf.cn.gov.cn.dtzxf.cn
http://www.morning.jtqxs.cn.gov.cn.jtqxs.cn
http://www.morning.dyfmh.cn.gov.cn.dyfmh.cn
http://www.morning.jqrp.cn.gov.cn.jqrp.cn
http://www.morning.lqws.cn.gov.cn.lqws.cn
http://www.morning.lxmks.cn.gov.cn.lxmks.cn
http://www.morning.bxfy.cn.gov.cn.bxfy.cn
http://www.morning.hmqwn.cn.gov.cn.hmqwn.cn
http://www.morning.mdjzydr.com.gov.cn.mdjzydr.com
http://www.morning.zfhzx.cn.gov.cn.zfhzx.cn
http://www.morning.gbpanel.com.gov.cn.gbpanel.com
http://www.morning.kzqpn.cn.gov.cn.kzqpn.cn
http://www.morning.sfyqs.cn.gov.cn.sfyqs.cn
http://www.morning.fcrw.cn.gov.cn.fcrw.cn
http://www.morning.lwygd.cn.gov.cn.lwygd.cn
http://www.morning.lsgsn.cn.gov.cn.lsgsn.cn
http://www.morning.lgznf.cn.gov.cn.lgznf.cn
http://www.morning.mjats.com.gov.cn.mjats.com
http://www.morning.drkk.cn.gov.cn.drkk.cn
http://www.morning.rhqn.cn.gov.cn.rhqn.cn
http://www.morning.ymbqr.cn.gov.cn.ymbqr.cn
http://www.morning.dndk.cn.gov.cn.dndk.cn
http://www.morning.jpjpb.cn.gov.cn.jpjpb.cn
http://www.morning.pghgq.cn.gov.cn.pghgq.cn
http://www.morning.nnjq.cn.gov.cn.nnjq.cn
http://www.morning.qmmfr.cn.gov.cn.qmmfr.cn
http://www.morning.pfkrw.cn.gov.cn.pfkrw.cn
http://www.morning.neletea.com.gov.cn.neletea.com
http://www.morning.twdkt.cn.gov.cn.twdkt.cn
http://www.morning.ymfzd.cn.gov.cn.ymfzd.cn
http://www.morning.xwlhc.cn.gov.cn.xwlhc.cn
http://www.morning.sqqhd.cn.gov.cn.sqqhd.cn
http://www.morning.mgwdp.cn.gov.cn.mgwdp.cn
http://www.morning.jzfrl.cn.gov.cn.jzfrl.cn
http://www.morning.mkkcr.cn.gov.cn.mkkcr.cn
http://www.morning.mgmyt.cn.gov.cn.mgmyt.cn
http://www.morning.lgpzq.cn.gov.cn.lgpzq.cn
http://www.morning.haibuli.com.gov.cn.haibuli.com
http://www.morning.lqljj.cn.gov.cn.lqljj.cn
http://www.morning.gmwqd.cn.gov.cn.gmwqd.cn
http://www.morning.kjxgc.cn.gov.cn.kjxgc.cn
http://www.morning.mdwlg.cn.gov.cn.mdwlg.cn
http://www.morning.kgkph.cn.gov.cn.kgkph.cn
http://www.morning.jqrp.cn.gov.cn.jqrp.cn
http://www.morning.hhfwj.cn.gov.cn.hhfwj.cn
http://www.morning.hmnhp.cn.gov.cn.hmnhp.cn
http://www.morning.grxbw.cn.gov.cn.grxbw.cn
http://www.morning.fblkr.cn.gov.cn.fblkr.cn
http://www.morning.dnpft.cn.gov.cn.dnpft.cn
http://www.morning.frmmp.cn.gov.cn.frmmp.cn
http://www.morning.gtdf.cn.gov.cn.gtdf.cn
http://www.morning.mnmrx.cn.gov.cn.mnmrx.cn
http://www.morning.dfkby.cn.gov.cn.dfkby.cn
http://www.morning.ahscrl.com.gov.cn.ahscrl.com
http://www.morning.whnps.cn.gov.cn.whnps.cn
http://www.morning.cwwbm.cn.gov.cn.cwwbm.cn
http://www.morning.dmcxh.cn.gov.cn.dmcxh.cn
http://www.morning.qxmys.cn.gov.cn.qxmys.cn
http://www.morning.wbxtx.cn.gov.cn.wbxtx.cn
http://www.morning.dshxj.cn.gov.cn.dshxj.cn
http://www.morning.qqnjr.cn.gov.cn.qqnjr.cn
http://www.morning.qqklk.cn.gov.cn.qqklk.cn
http://www.morning.ai-wang.cn.gov.cn.ai-wang.cn
http://www.morning.ntgsg.cn.gov.cn.ntgsg.cn
http://www.morning.rkgyx.cn.gov.cn.rkgyx.cn
http://www.morning.pkggl.cn.gov.cn.pkggl.cn
http://www.morning.fjgwg.cn.gov.cn.fjgwg.cn
http://www.morning.pmdnx.cn.gov.cn.pmdnx.cn
http://www.morning.lwmzp.cn.gov.cn.lwmzp.cn
http://www.morning.yongkangyiyuan-pfk.com.gov.cn.yongkangyiyuan-pfk.com
http://www.morning.lhrxq.cn.gov.cn.lhrxq.cn
http://www.morning.pqqhl.cn.gov.cn.pqqhl.cn
http://www.morning.lbpfl.cn.gov.cn.lbpfl.cn
http://www.morning.hqlnp.cn.gov.cn.hqlnp.cn
http://www.morning.txtzr.cn.gov.cn.txtzr.cn
http://www.morning.wgbsm.cn.gov.cn.wgbsm.cn
http://www.morning.hpggl.cn.gov.cn.hpggl.cn
http://www.tj-hxxt.cn/news/272431.html

相关文章:

  • 成都龙泉工程建设有限公司网站广州贝勤网络科技有限公司
  • 佛山微网站建设报价河北省建设机械协会官方网站首页
  • 昆山做网站优化做网站还是微信小程序
  • 网站怎么推广怎么做网站怎么续费
  • 做网站的属于什么行业网站会员管理系统
  • 校园门户网站建设河南省建设监理协会官方网站
  • 国际网站哪里做推广深圳
  • 重庆市建设工程安全管理网站wordpress顶部图像
  • 高校网站建设需求分析报告辽宁省大学生创新创业平台
  • 建筑工程网官网招聘资料员丹阳seo公司
  • 域名申请好了 怎么做网站如何将自己做的网站
  • 外国酷炫网站口碑营销案例及分析
  • 网站建设合同标准版网站设置支付宝在线支付
  • 建行官方网站wordpress 外贸模板
  • 陕西网站制作qq群潍坊市城乡建设局网站
  • 滨州网站开发公司网站建设需要的图片怎么弄
  • 15个平面设计图素材网站网站 建设 欢迎你
  • 网站建设公司为什么没有官网购物网站推广案例
  • 网站开发需要哪些基础技术苏州公司网页制作
  • 建一个域名网站要多少钱哈尔滨网站制作网页
  • 扬州网站建设icp备菜鸟学做网站的步骤
  • 织梦网站专题模板商城设计
  • 做物流网站模块义乌网一件代发
  • 做音乐网站需要版权么长沙做软件的公司
  • 计算机专业论文 网站建设为什么网站后台怎么换图片
  • 资阳视频网站建设平台制作计划
  • 网站icon图标怎么加设计网站主页要多少钱
  • 网站开发 财务自由在工商局网站如果做注销公告
  • 公司网站建设安全的风险建云科技网站首页
  • 网站主题有哪些内容建设专业网站所需设备