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

瓷砖网站建设湖南网站设计

瓷砖网站建设,湖南网站设计,公司个人怎么做网络推广,百度网页版官方文章目录说在前面花括号{}初始化new的列表初始化STL相关容器的列表初始化相关语法格式容器列表初始化的底层原理forward_list和array与类型相关的新特性decltype左值引用和右值引用什么是左值#xff0c;什么是右值左值和右值的本质区别右值引用如何理解右值引用std::move移动… 文章目录说在前面花括号{}初始化new的列表初始化STL相关容器的列表初始化相关语法格式容器列表初始化的底层原理forward_list和array与类型相关的新特性decltype左值引用和右值引用什么是左值什么是右值左值和右值的本质区别右值引用如何理解右值引用std::move移动构造移动赋值万能引用和完美转发模板的可变参数default和delete的新用法lambda表达式语法格式捕获列表底层原理包装器std::bind总结说在前面 作为新年的第一篇博客。在这里先祝大家新年快乐今天我们要介绍的是C11 首先如同一个人从一个一无所知的婴儿成长成为一个社会的精英是需要不断发展的。C自从诞生的那一刻起一直都在不断进步不停更新。对于C来说有两个比较重要的时间点。一个就是C98,这个版本的C引入了强大的STL。而另外的一个时间点就是我们接下来介绍的C11 接下来我们就来看一看C11更新了哪些有用的东西 花括号{}初始化 在C11里面所有的东西都可以使用{}进行初始化你可能会看到很多有关C11的书籍里面可能都会有如下的demo代码 #includeiostream /** C11 特性* */ void test1() {//C/C98里面定义变量常用的方式int i5;//C11定义变量常用方式 int x{3};int y{3};} int main() {test1();return 0; }不仅仅是内置类型自定义类型也可以使用如上的方式进行初始化 //自定义类型也可以支持{}初始化 struct Date {Date(int year2023,int month1,int day22):_year(year),_month(month),_day(day){}int _year;int _month;int _day;};void test2() {//C98Date d1(2022,1,23);//c11Date d3{2022,1,25}; }实际上花括号初始化的本质也是调用了构造函数我们可以对构造函数添加打印语句: //自定义类型也可以支持{}初始化 struct Date {explicit Date(int year2023,int month1,int day22):_year(year),_month(month),_day(day){std::coutDate(int year,int month,int day)std::endl;}int _year;int _month;int _day;}; void test2() {//C98Date d1(2022,1,23);//C11// Date d2{2022,1,24};//还可以这样写 Date d3{2022,1,25}; }而因为增加了{}初始化的方式那么就能够解决在C98里面使用new构造一块连续的空间的同时无法顺带初始化的问题 new的列表初始化 在C98里面,对于一块连续空间的申请和初始化我们只能分开进行: void test3() { //C98里面申请空间和初始化必须分开//int* pnew int[4];//for(int i0;i4;i) p[i]i1;//C11允许这么做,这里直接在int* pnew int[4]{1,2,3,4};for(int i0;i 4;i){std::coutp[i] ;}std::coutstd::endl;delete[] p; }从C11以后我们就可以使用花括号来初始化对应new的连续的数组空间不过相对来说这种方式用的还是相对比较少。毕竟更多的情况下我们都是使用的STL提供的容器。所以这里我们知道有这样一种用法就好了 STL相关容器的列表初始化 STL里面的容器也提供了对应的列表初始化的方式真可谓是一切都可列表初始化所以我们才会说到了C11以后。一切皆可使用列表初始化 下面我们就来看一看相应的语法格式。 相关语法格式 这里我们用vector来进行演示。基本上我们之前学习的容器都可以使用这样的初始化方式 //自定义类型也可以支持{}初始化 struct Date {Date(int year2023,int month1,int day22):_year(year),_month(month),_day(day){std::coutDate(int year,int month,int day)std::endl;}//为了方便观察效果void PrintDate(){std::cout_year _month _day\n;}int _year;int _month;int _day;}; 那么为什么能够这样给容器初始化呢在处理容器的{}初始化的时候编译器做了哪些工作呢?下面我们就来探究一下 容器列表初始化的底层原理 首先官方文档就是我们学习C11最好的资料之一我们先来看看C11种vector对应发生了什么变化: 首先上面两个对应的构造函数是后面的知识点。实际上真正能够让我们使用花括号初始化对应容器元素的就是最后一个构造函数! 下面我们就通过代码来看一看{}究竟被编译器做了怎样的处理: void test() { auto l{1,2,3,4,5,77};//获取l变量的类型并以字符串的形式打印std::couttypeid(l).name()std::endl; }这段代码是在Linux上获取的不过对应的就是文档手册里面的新的构造函数的参数的类型initializer_listT! 在C11以后{}被编译器识别成为新的initializer_list类型只要支持这个构造函数就可以使用花括号进行初始化 所以为了让我们自己实现的vector也能够支持{}初始化我们也添加这个构造函数 namespace chy {//vector是一个模板类templatetypename Tclass vector{public:typedef T* iterator;typedef const T* const_iterator;//...//给我们自己实现的vector添加对应的构造函数 //这个时候我们自己实现的vector也就可以支持{}初始化的方式了vector( std::initializer_listT li){ //initiallizer支持范围forfor(auto e: li){push_back(e);}}private:iterator _start;iterator _finish;iterator _end_of_stroge;}; } 其他的容器也是一样的道理这里我就不一一演示了。 另外C11还新增加了几个容器。我们之前介绍的哈希相关的容器就是C11新增的接下来我们将介绍两个C11里面存在感比较低的两个容器— forward_list和array forward_list和array C11新增加了forward_list和array这两个新容器下面我们先来看forward_list。首先先来看文档对应的说明 首先不得不说有的时候C对于类的命名不是特别能够让人见名知义。这个forward_list其实就是我们之前在数据结构那里学习的单链表下面我们就来简单使用以下forward_list //需要包含这个头文件 #includeforward_list void test() {std::forward_listint fl;int a[]{1,2,3,4,5};//没有提供尾插接口for(auto e:a){fl.push_front(e);}//也支持范围forfor(auto e:fl){std::coute ;}std::cout\n; }说实话这个容器的作用远远不及list,即使是我们手写一个简单的单链表也是可以的不过这个容器的倒是可以作为哈希桶悬挂的子节点结构 接下来我们来看一看另外一个容器array.首先我们先来看文档对应的说明: 从文档来看array就是一个固定大小的泛型线性数组。下面我们就来简单使用一下array: void test() {//array的简单使用std::arrayint,5 a{1,2,3,4,5};for(auto e:a){std::coute ;}std::cout\n; } 使用array要注意如下的几个问题 1.array是一个静态数组不能对array插入元素 2.array会对越界进行检查 说实话C11这个array确实是没什么太大的作用它所有的功能vector基本都有所以这个容器大家仅仅就是作为一个了解就可以了。不用过分去深究这个容器相应 的forward_list也是如此。 与类型相关的新特性 C11还增加了一些有关类型推断的新特性。比如我们经常使用的auto就是在C11以后才增加的类型推导的能力。 接下来介绍一个C11新增加的一个和类型相关的特性的一个操作符----decltype decltype 这个操作符能够把表达式的类型返回给一个变量测试的代码如下 void test() {decltype(1020)x10;std::coutx变量的类型为: typeid(x).name()std::endl; }可以看到这个操作符确实把类型返回了。注意这个操作符和sizeof操作符一样并不会去计算对应括号的表达式仅仅就是返回对应的表达式的类型 左值引用和右值引用 前面讲的特性大多是是C11相对来说属于锦上添花的。而右值引用可以说式C11相对来说非常具有革命性意义的一个新特性。它让C的高效又上了一个台阶 在正式介绍右值引用之前我们有必要来明确一个概念什么是左值 什么是右值 什么是左值什么是右值 首先从字面上的意思来看左值就是出现在表达式左边的值右值就是出现在表达式右边的值。比如下面的代码 //左值和右值 void test() {//从字面意义上来看这里的a是左值int a1020;//1020就是一个右值//但是下面怎么解释int ba;//a不是左值怎么出现在了右边 }最后一个例子不难看出这样的理解是不正确的,如果这样子理解左值和右值就太肤浅了。 那么左值和右值的本质区别到底是什么呢或者说左值具有什么特征右值又有什么特征呢 左值和右值的本质区别 明确地给出一个结论:左值是可以取地址地值右值是不能取地址的值也就是说通过能否取地址就可以看出左值是真正分配了计算机内存空间的值而右值仅仅只是一个临时量 右值引用 在C98以前我们所使用的引用都叫做左值引用。从C11开始我们就要把引用分为左值引用和右值引用了。下面我们就来通过代码来看这两个引用: void test() {int x10;//rx是x的别名是左值int rxx;//右值引用使用两个int rrx10; //10是右值所以rrx是右值引用 }无论右值引用还是左值引用只要是引用那么都是被引用对象的别名而右值引用自身是一个左值因为我们可以对右值引用的那个别名取地址了 void test() {int x10;//rx是x的别名是左值int rxx;//右值引用使用两个int rrx10; //10是右值所以rrx是右值引用std::coutrrx的地址是: rrxstd::endl; }如何理解右值引用 从前面的结论可以看出本来即将消亡的右值通过右值引用的方式计算机也确实为了它开辟一块内存空间存储对应的值 也就是这个别名本质是一个左值所以右值引用是一个左值这个左值是右值的别名 左值引用只能引用左值而右值引用只能引用右值 std::move 那么有的时候我们需要把左值转换成为右值。c11的设计者也考虑到这个点了所以提供了一个接口std::move—可以把左值转换成右值。下面我们来看一段代码 void fun(int rx) {std::coutfun(int)std::endl; } void fun(int rx) {std::coutfun(int)std::endl; } void test() {int x10;fun(x);fun(std::move(x)); }所以我们就可以总结如下 左值引用只能引用左值const左值引用可以引用左值也可以引用右值 右值引用可以引用右值和move以后的左值 移动构造移动赋值 那么C11推出右值引用难道仅仅是我们前面那样使用吗答案显然不是在C98的时候C一直都很害怕这样的场景 class Solution { private: //获取 对应的keyboard里面的行size_t getRow(char ch,vectorstring v){for(int i0;iv.size();i){if(v[i].find(tolower(ch))!string::npos){return i;}}return v.size();} public:vectorstring findWords(vectorstring words){vectorstring keyboard;keyboard.push_back(qwertyuiop);keyboard.push_back(asdfghjkl);keyboard.push_back(zxcvbnm);vectorstring res;for(int i0;iwords.size();i){size_t posgetRow(words[i][0],keyboard);//pos3,说明不在这一行继续寻找下一行//coutpos ;if(pos3) continue;else { // coutpos ;//找到了对应行bool flagtrue;for(char ch:words[i]){if(keyboard[pos].find(tolower(ch)) string::npos){flagfalse;break;}}if(flag) res.push_back(words[i]);}}return res;} };通过前面的学习我们知道传值返回会发生拷贝。而对于vector这样的类发生的是深拷贝一旦这个vector管理的对象特别多并且每个对象又要进行深拷贝的情况下。传值返回的效率就是一场灾难 C是一门十分注重效率的编程语言而右值引用便是为了解决这个问题应运而生的 我们知道右值编译器是没有为其分配空间的那么对于自定义类型的右值C又将其称为将亡值 既然是将亡值那么就意味着这个自定义类型的对象会马上调用析构函数清理自己了。但是这个时候这个自定义类型身上的资源又是我所需要的。下面就有如下两种做法 1.在对象析构之前我深拷贝一个副本然后这个对象自行销毁 2.既然这个将亡对象有我想要的资源那么不妨把它的资源为我所用然后再让其自行销毁即可 显然我们都会选择第二种方式。那么c11以后就新增加了两个相应的默认成员函数移动构造和移动赋值 class A { public:A():_p(nullptr),_size(0){}A(int size):_p(nullptr),_size(size){_pnew int[size];}//拷贝构造A(const A a):_p(nullptr){_pnew int[a._size];for(int i0;i a._size;i){_p[i]a._p[i];}_sizea._size;std::coutA(const A a)std::endl;}//移动构造 A(A aa):_p(nullptr){std::swap(_p,aa._p);std::swap(_size,aa._size);std::coutA( A aa)std::endl;}~A(){delete _p;_pnullptr;}private:int* _p;int _size; };接下来我们使用gdb进行调试观察结果 接下来就要开始构造a3了使用的是移动构造 可以看到这里的a3和a1的成员发生了交换。相比于先前我们只能这种场景我们只能进行深拷贝。当对象很大的时候效率非常低而有了C11的移动构造以后传值返回的效率得到了大幅度的提升 而对于传值返回的情况现代的编译器也会进行一定的大胆的优化假设我们现在有一个to_string函数 //一个我们自己写的to_string函数 namespace chy {string to_string(int val){string str;while(val){//...复杂的转换逻辑}return str;} }那么在C98的时候传值返回的情况如下 那么C11有了移动构造了以后编译器的传值返回情况又不一样了 对应的有了移动构造就会有移动赋值 A operator(A aa){std::swap(_p,aa._p);std::swap(_size,aa._size);return *this;}既然是作为默认成员函数那么在特定的条件下编译器就会生成对应的默认移动构造和移动赋值,需要满足如下三个特性 1.没有显式提供拷贝构造函数 2.没有显式提供赋值重载 3.没有显式提供析构函数 只有满足如上三条件编译器才会生成默认的移动构造和移动赋值函数 默认生成的移动构造对于内置类型浅拷贝对于自定义类型如果提供了移动构造函数那么就会调用移动构造 不过多数情况下生成的默认移动构造没什么价值所以这两个默认成员函数一般都需要手动提供。 需要注意的是在c11以后原先将单个元素插入容器的函数接口都提供了对应的右值版本目的就是为了能够进一步提高运行效率 万能引用和完美转发 而当模板和右值引用结合起来的时候又会发生不一样的效果 templatetypename T void solve(T t) { std::cout调用!std::endl; }void test() {A a1;A a2a1;// 左值引用传参solve(a2);// 右值引用传参 solve(std::move(a1)); }我们可以看到无论是左值还是右值这个模板函数都能匹配 这个就是右值引用和模板结合到了一起以后。既能够接受左值也可以接受右值因此我们就把这个特性叫做万能引用。 但是这个万能引用有一个很不好的缺陷接下来我们来通过一段代码进行演示 templatetypename T void solve(T t) { T tmp(t); }void test() {A a1;// 右值引用传参 solve(std::move(a1)); }我们发现move以后的a1本来应该是一个右值结果传参了以后变成了一个左值万能引用就会出现引用折叠的问题。使用万能引用了以后所有的传递的T参数都会变成左值 但是有的时候我们希望能够把原来的特性保持下去所以c11又提供了一种新的方式----完美转发 templatetypename T void solve(T t) { //使用forward保持特性T tmp(std::forwardT(t)); }void test() {A a1;solve(std::move(a1)); }这里依旧调用的是移动构造函数很好保持了原来右值的特性。所以如果要保持对应的原来的值的特性最好使用forward完美转发。 模板的可变参数 接下来我们来讲一讲模板的可变参数在C语言阶段我们经常使用如下的函数 int printf(const char* format,...);函数声明里面带有省略号的我们称之为可变参数在C11里面引入了可变模板参数也就是说以后一个模板函数或者模板类不仅类型可变参数的数量也是可以变化的 下面我们就来看demo代码 templatetypename T void PrintArgs(const T val) {std::coutvalstd::endl; } templatetypename T,typename... Args void PrintArgs(const T val,Args... args) { std::coutval typeinfo is typeid(val).name()std::endl;//可以使用sizeof获取传递参数的个数 std::coutparam num is sizeof...(args)std::endl;//参数解包PrintArgs(args...); } void test() {// A a1;// solve(std::move(a1));//可以传任意类型任意数目的参数 PrintArgs(1,2.3,c,std::string(hello world));std::cout\n;PrintArgs(1.1,2); }可变参数模板是通过递归实现的允许有0个或者是1个递归参数包我们这里使用的是模板的最佳匹配原则来确定递归的出口如果这类换成一个最佳匹配的函数也可以 前面这种写法相对比较繁琐有的大神还设计出了这样的写法 //大神的写法 templateclass T void PrintArgs(T t) { std::coutstd::endl; } templateclass ... Args void PrintArgs(Args... args) {std::coutparam num is sizeof...(args)std::endl;//使用数组进行解包 ,利用逗号表达式的特性进行参数包解析int a[]{(PrintArgs(args...),0)}; }void test() {PrintArgs(1,2.3,c,std::string(hello world)); }注意可变参数递归解包是在编译的时候完成的不能通过运行时的if逻辑来递归 有了可变模板参数的知识铺垫那么我们就可以很好讲解c11里面容器新增的emplace_back和emplace接口: emplace系列接口使用的都是可变参数模板对应如果传递的参数能够匹配到自定义类型的构造函数就会变成直接构造提高效率不过有了移动构造以后emplace接口带来的效率提升也不是特别明显所以这个接口了解即可 default和delete的新用法 在C11里面又对default和delete这两个关键字进行了功能的扩展。在类和对象章节我们介绍过只有在不提供任何构造函数的情况下编译器才会生成默认构造函数 但是在c11如果已经提供了其他构造函数的情况下仍然想使用编译器生成的默认构造函数可以使用default关键字 class A { public: //强制编译器生成默认构造函数A()default;A(int size):_p(nullptr),_size(size){_pnew int[size];}//拷贝构造A(const A a):_p(nullptr){_pnew int[a._size];for(int i0;i a._size;i){_p[i]a._p[i];}_sizea._size;std::coutA(const A a)std::endl;}//移动构造 A(A aa):_p(nullptr){std::swap(_p,aa._p);std::swap(_size,aa._size);std::coutA( A aa)std::endl;}~A(){delete _p;_pnullptr;}private:int* _p;int _size; };不仅是默认构造函数其他构造函数都可以使用default强制编译器默认生成 不仅仅可以强制编译器生成我们还可以删除默认成员函数使用delete关键字 比如删除拷贝构造函数 class A { public://强制编译器生成默认构造函数A() default;A(int size):_p(nullptr), _size(size){_p new int[size];}//拷贝构造A(const A a) delete;//移动构造 A(A aa):_p(nullptr){std::swap(_p, aa._p);std::swap(_size, aa._size);std::cout A( A aa) std::endl;}~A(){delete _p;_p nullptr;}private:int* _p;int _size; }; int main() { A a2(a1);return 0; }lambda表达式 接下来我们来讲lambda表达式。最早引进lambda表达式的语言式python后面其他语言看到这个语言很好用于是都纷纷加到了标准库里面。c11以后同样也支持这样的用法 比如我们上京东购物有时想按照商品价格排序有时候想按照实际排序有各种各样的排序需求。在c98里面我们就要写不同的仿函数来实现一旦随着项目的扩展这些仿函数就会越来越多非常难维护 所以我们就希望能够达到一种排序规则专为一种排序方法所用而lambda表达式恰好就是这样的一个工具下面我们就来看lambda表达式的语法 语法格式 lambda表达式子的语法特征 //lambda表达式的语法格式 [捕捉列表](函数参数){ 函数体}接下来我们就结合具体的样例进行分析 struct Student {Student(int age,int score,const std::string name):_age(age),_score(score),_name(name){}int _age;int _score;std::string _name;void print(){std::cout_name _age _scorestd::endl;} }; void test() {Student a[] {{15,23,aa},{14,55,cc},{13,88,bb} };//显式使用lambdaauto l[](const Student s1,const Student s2){ return s1._age s2._age ;};std::sort(a,a3,l);for(auto e : a){e.print();} }但其实lambda更多情况下用的是这样的 std::sort(a,a3,[](const Student s1,const Student s2){ return s1._score s2._score ;});for(auto e : a){e.print();} 捕获列表 首先我们要知道一个点。lambda是一个局部匿名函数既然是函数那么就代表者对应的内部是一个全新的作用域内部作用域不能直接使用外部的变量 如果我们想要使用对应的变量就需要进行捕获 void test() {//lambda捕获列表//可以使用[]捕获对应的元素int a1,b2;auto l[a,b](){ return ab; };std::coutl()std::endl; }这种值的捕获方式是值捕获默认是对捕获的值带了一个const想要更改值捕获的话就要使用mutable关键字不过即使更改内部的a,b也对外面没有影响所以这个mutable几乎没有 不过我们还可以使用引用捕获 int a1,b2;int ret0;//auto l[a,b](){ return ab; };//std::coutl()std::endl;//引用捕获auto l[ret,a,b](){ret ab; return 0;};l();std::coutretretstd::endl;引用捕捉和值捕捉可以混合来下面这三种写法都是可以的 //表示用值捕捉所有变量 auto l1[](){}; //表示所有变量引用捕获 auto l2[](){}; //ret引用捕获其他变量值捕获 auto l3[ret,](){};关于lambda的介绍我们就暂时讲到这里。下面我们来看一看这个lambda对于编译器来说究竟是什么 底层原理 我们将编译的汇编代码进行研究 可以看到lambda函数被编译器处理以后就是一个仿函数这个仿函数是lambda一个唯一的uuid所以即使两个lambda在我们看来一模一样。它们也是不同的类型 值得一提的是lambda函数还可以和函数指针相互赋值不过我们并不建议这么去做 包装器 学到现在c11有三个可以进行调用的东西 1.函数指针 2.仿函数对象 3.lambda 而如果当这些函数统统编程模板的时候。同样的模板就会实例化出三分代码显然这三份代码还是有一定的冗余毕竟对于我们都是希望使用对应的()的功能 因此C11提供了函数包装器 void func() {std::coutfunc pointerstd::endl; } struct Func {void operator()(){std::coutFunc()std::endl;} }; auto f[]() - void {std::coutlambdastd::endl;};void test() {std::vectorstd::functionvoid() fuctions;fuctions.push_back(func);fuctions.push_back(Func());fuctions.push_back(f);for(auto uf : fuctions){uf();}std::cout\n; }也就是说但凡是返回值和参数都相同的函数我们都可以直接使用一个function对象来接收接下来我们改造一下曾经的逆波兰表达式的题目用C11的方式改造: class Solution { private: //纯c11的方式 unordered_mapstring,functionint(int,int) hash {{ ,[](int a,int b){return ab;}},{- ,[](int a,int b){return a-b;}},{*,[](int a,int b){return a*b;}},{/,[](int a,int b){return a/b;}} }; public:int evalRPN(vectorstring tokens){stacklong long st;for(const auto str:tokens){if(hash.find(str)hash.end()){st.push(stoll(str));}else {long long rightst.top();st.pop();long long leftst.top();st.pop();long long rethash[str](left,right);st.push(ret);}}return static_castint(st.top());} };std::bind 最后我们介绍的是C11引入的bind函数对应的函数原型如下: template class Fn, class... Args/* unspecified */ bind (Fn fn, Args... args); template class Ret, class Fn, class... Args/* unspecified */ bind (Fn fn, Args... args);这个函数有如下的两个作用 1.将一个参数固定也就是绑定一个固有的参数 2.调整参数的顺序 接下来我们来看示例代码 int Sub(int a,int b) {return a-b; } class A { public:int ASub(int a,int b){return a-b;} }; void test() {std::functionint(int,int) f(A::Asub); } 这段代码是错误的 ! 原因是成员函数有一个隐含的this指针那么有没有方式可以不需要这个this指针呢答案是肯定的解决的方法就是使用std::bind函数 class A { public:A(int a0):_a(a){}int ASub(int x,int y){return (x-y)*_a;} private:int _a; }; void test() { // std::bind(A::ASub,A(),std::placeholders::_1,std::placeholders::_2);// std::functionint(int,int) f(A::ASub); 错误非静态成员函数有this指针 // 解决方式使用std::bind绑定this指针 // 为了演示效果给A加一个成员 std::functionint(int,int) f1(std::bind(A::ASub,A(),std::placeholders::_1,std::placeholders::_2));std::coutf1(2,3)std::endl;std::functionint(int,int) f2(std::bind(A::ASub,A(2),std::placeholders::_1,std::placeholders::_2));std::coutf2(2,3)std::endl;} bind还有一个作用就是改变参数的位置。而我们代码里面的_1和_2就是我们要显式传递的参数的位置也就是占位符可以通过占位符来改变参数顺序: void test() { std::functionint(int,int) f1(std::bind(Sub,std::placeholders::_1,std::placeholders::_2)); std::functionint(int,int) f2(std::bind(Sub,std::placeholders::_2,std::placeholders::_1));std::coutf1(2,3)std::endl;std::coutf2(2,3)std::endl; }不过相对而言这个调整顺序并不是很常用。 总结 以上就是C11的部分新特性的介绍。而C11更多的新特性例如智能指针我们会在后续的文章里面重点介绍。
http://www.dnsts.com.cn/news/241950.html

相关文章:

  • 网站服务器宽带wordpress恢复主题
  • 四川广安网站建设网络服务网络推广
  • 河北省住房城乡建设局网站首页网站建设教程科普
  • 动态交互网站建设微软网站设计
  • 网站开发 安全 承诺书上传设计作品集的网站
  • 建设行政主管部门相关网站做网站的工资高吗?
  • 惠州市住房和城乡建设局网站百度站内搜索
  • 长春建设厅官方网站建设银行如何进行网站冻结
  • 江苏优化网站公司农业网站建设方案 ppt模板
  • 有空间怎么做网站深圳集团网站建设报价
  • 广州万安建设监理有限公司网站wordpress 标签页制作
  • 宜春建设局网站做漂亮的网站
  • 如何做网站对话框平江区建设局网站
  • 合肥响应式网站开发asp论坛源码
  • 手机网站大全下载免费自助建站网站一览
  • 鹧鸪哨网站1v1深度开发做公司网站的公
  • 济南网站制作策划wordpress+设置七牛
  • 手表网欧米茄什么叫优化
  • 邢台企业网站建设价格做网站需要准备的素材
  • 网站开发运行详细步骤前几年做啥网站致富
  • 贵阳平台网站建设线上营销推广方法
  • 网站建设与 维护实训报告范文聚美优品返利网站怎么做
  • 做网站分为哪几个岗位永康门业微网站建设
  • 厦门企业建网站制作长春网站设计
  • 一般做网站服务器的cpu网站和app的区别
  • 个人网站备案名称wordpress商店如何添加商品
  • 赚钱做任务的网站12380网站建设情况的报告
  • 51网站一起做网店广州淄川区建设局网站
  • 番禺外贸型网站建设dede 网站地图模版
  • 网站开发的毕设开题报告轻松做网站