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

建网站需要什么东西天津seo推广优化

建网站需要什么东西,天津seo推广优化,灌南县规划局网站一品嘉苑规划建设,鞍山玉佛苑官网[引用#xff1a;https://www.cnblogs.com/yangecnu/archive/2012/03/17/2402432.html] 迭代器模式是设计模式中行为模式(behavioral pattern)的一个例子#xff0c;他是一种简化对象间通讯的模式#xff0c;也是一种非常容易理解和使用的模式。简单来说#xff0c;迭代器模… [引用https://www.cnblogs.com/yangecnu/archive/2012/03/17/2402432.html] 迭代器模式是设计模式中行为模式(behavioral pattern)的一个例子他是一种简化对象间通讯的模式也是一种非常容易理解和使用的模式。简单来说迭代器模式使得你能够获取到序列中的所有元素而不用关心是其类型是arraylistlinked list或者是其他什么序列结构。这一点使得能够非常高效的构建数据处理通道(data pipeline)--即数据能够进入处理通道进行一系列的变换或者过滤然后得到结果。事实上这正是LINQ的核心模式。     在.NET中迭代器模式被IEnumerator和IEnumerable及其对应的泛型接口所封装。如果一个类实现了IEnumerable接口那么就能够被迭代调用GetEnumerator方法将返回IEnumerator接口的实现它就是迭代器本身。迭代器类似数据库中的游标他是数据序列中的一个位置记录。迭代器只能向前移动同一数据序列中可以有多个迭代器同时对数据进行操作。     在C#1中已经内建了对迭代器的支持那就是foreach语句。使得能够进行比for循环语句更直接和简单的对集合的迭代编译器会将foreach编译来调用GetEnumerator和MoveNext方法以及Current属性如果对象实现了IDisposable接口在迭代完成之后会释放迭代器。但是在C#1中实现一个迭代器是相对来说有点繁琐的操作。C#2使得这一工作变得大为简单节省了实现迭代器的不少工作。 接下来我们来看如何实现一个迭代器以及C#2对于迭代器实现的简化然后再列举几个迭代器在现实生活中的例子。   1. C#1:手动实现迭代器的繁琐       假设我们需要实现一个基于环形缓冲的新的集合类型。我们将实现IEnumerable接口使得用户能够很容易的利用该集合中的所有元素。我们的忽略其他细节将注意力仅仅集中在如何实现迭代器上。集合将值存储在数组中集合能够设置迭代的起始点例如假设集合有5个元素你能够将起始点设为2那么迭代输出为2340最后是1.     为了能够简单展示我们提供了一个设置值和起始点的构造函数。使得我们能够以下面这种方式遍历集合 object[] values { a, b, c, d, e }; IterationSample collection new IterationSample(values, 3); foreach (object x in collection) { Console.WriteLine(x); } 由于我们将起始点设置为3所以集合输出的结果是d,e,a,b及c现在我们来看如何实现 IterationSample 类的迭代器 class IterationSample : IEnumerable {Object[] values;Int32 startingPoint;public IterationSample(Object[] values, Int32 startingPoint) { this.values values; this.startingPoint startingPoint; } public IEnumerator GetEnumerator() { throw new NotImplementedException(); } }     我们还没有实现GetEnumerator方法但是如何写GetEnumerator部分的逻辑呢第一就是要将游标的当前状态存在某一个地方。一方面是迭代器模式并不是一次返回所有的数据而是客户端一次只请求一个数据。这就意味着我们要记录客户当前请求到了集合中的那一个记录。C#2编译器对于迭代器的状态保存为我们做了很多工作。        现在来看看要保存哪些状态以及状态存在哪个地方设想我们试图将状态保存在IterationSample集合中使得它实现IEnumerator和IEnumerable方法。咋一看看起来可能毕竟数据在正确的地方包括起始位置。我们的GetEnumerator方法仅仅返回this。但是这种方法有一个很重要的问题如果GetEnumerator方法调用多次那么多个独立的迭代器就会返回。例如我们可以使用两个嵌套的foreach语句来获取所有可能的值对。这两个迭代需要彼此独立。这意味着我们需要每次调用GetEnumerator时返回的两个迭代器对象必须保持独立。我们仍旧可以直接在IterationSample类中通过相应函数实现。但是我们的类拥有了多个职责这位背了单一职责原则。      因此我们来创建另外一个类来实现迭代器本身。我们使用C#中的内部类来实现这一逻辑。代码如下 class IterationSampleEnumerator : IEnumerator {IterationSample parent;//迭代的对象 #1Int32 position;//当前游标的位置 #2 internal IterationSampleEnumerator(IterationSample parent) { this.parent parent; position -1;// 数组元素下标从0开始初始时默认当前游标设置为 -1即在第一个元素之前 #3 } public bool MoveNext() { if (position ! parent.values.Length) //判断当前位置是否为最后一个如果不是游标自增 #4 { position; } return position parent.values.Length; } public object Current { get { if (position -1 || position parent.values.Length)//第一个之前和最后一个自后的访问非法 #5 { throw new InvalidOperationException(); } Int32 index position parent.startingPoint;//考虑自定义开始位置的情况 #6 index index % parent.values.Length; return parent.values[index]; } } public void Reset() { position -1;//将游标重置为-1 #7 } } 要实现一个简单的迭代器需要手动写这么多的代码需要记录迭代的原始集合#1记录当前游标位置#2返回元素时根据当前游标和数组定义的起始位置设置定迭代器在数组中的位置#6。初始化时将当前位置设定在第一个元素之前#3当第一次调用迭代器时首先需要调用MoveNext然后再调用Current属性。在游标自增时对当前位置进行条件判断#4使得即使当第一次调用MoveNext时没有可返回的元素也不至于出错#5。重置迭代器时我们将当前游标的位置还原到第一个元素之前#7。     除了结合当前游标位置和自定义的起始位置返回正确的值这点容易出错外上面的代码非常直观。现在只需要在IterationSample类的GetEnumerator方法中返回我们当才编写的迭代类即可 public IEnumerator GetEnumerator() {return new IterationSampleEnumerator(this); }     值得注意的是上面只是一个相对简单的例子没有太多的状态需要跟踪不用检查集合在迭代的过程中是否发生了变化。为了实现一个简单的迭代器在C#1中我们实现了如此多的代码。在使用Framework自带的实现了IEnumerable接口的集合时我们使用foreach很方便但是当我们书写自己的集合来实现迭代时需要编写这么多的代码。     在C#1中大概需要40行代码来实现一个简单的迭代器现在看看C#2对这一过程的改进。   2. C#2:通过yield语句简化迭代   2.1 引入迭代块(iterator)和yield return 语句 C#2使得迭代变得更加简单--减少了很多代码量也使得代码更加的优雅。下面的代码展示了再C#2中实现GetEnumerator方法的完整代码 public IEnumerator GetEnumerator() {for (int index 0; index this.values.Length; index){yield return values[(index startingPoint) % values.Length]; } } 简单几行代码就能够完全实现IterationSampleIterator类所需要的功能。方法看起来很普通除了使用了yield return。这条语句告诉编译器这不是一个普通的方法而是一个需要执行的迭代块yield block他返回一个IEnumerator对象你能够使用迭代块来执行迭代方法并返回一个IEnumerable需要实现的类型IEnumerator或者对应的泛型。如果实现的是非泛型版本的接口迭代块返的yield type是Object类型否则返回的是相应的泛型类型。例如如果方法实现IEnumerableString接口那么yield返回的类型就是String类型。 在迭代块中除了yield return外不允许出现普通的return语句。块中的所有yield return 语句必须返回和块的最后返回类型兼容的类型。举个例子如果方法定义需要返回IEnumeratbleString类型的话不能yield return 1 。 需要强调的一点是对于迭代块虽然我们写的方法看起来像是在顺序执行实际上我们是让编译器来为我们创建了一个状态机。这就是在C#1中我们书写的那部分代码---调用者每次调用只需要返回一个值因此我们需要记住最后一次返回值时在集合中位置。 当编译器遇到迭代块是它创建了一个实现了状态机的内部类。这个类记住了我们迭代器的准确当前位置以及本地变量包括参数。这个类有点类似与我们之前手写的那段代码他将所有需要记录的状态保存为实例变量。下面来看看为了实现一个迭代器这个状态机需要按顺序执行的操作 它需要一些初始的状态当MoveNext被调用时他需要执行GetEnumerator方法中的代码来准备下一个待返回的数据。当调用Current属性是需要返回yielded的值。需要知道什么时候迭代结束是MoveNext会返回false下面来看看迭代器的执行顺序。   2.2 迭代器的执行流程 如下的代码展示了迭代器的执行流程代码输出(0,1,2,-1)然后终止。 class Program {static readonly String Padding new String( , 30); static IEnumerableInt32 CreateEnumerable() { Console.WriteLine({0} CreateEnumerable()方法开始, Padding); for (int i 0; i 3; i) { Console.WriteLine({0}开始 yield {1}, i); yield return i; Console.WriteLine({0}yield 结束, Padding); } Console.WriteLine({0} Yielding最后一个值, Padding); yield return -1; Console.WriteLine({0} CreateEnumerable()方法结束, Padding); } static void Main(string[] args) { IEnumerableInt32 iterable CreateEnumerable(); IEnumeratorInt32 iterator iterable.GetEnumerator(); Console.WriteLine(开始迭代); while (true) { Console.WriteLine(调用MoveNext方法……); Boolean result iterator.MoveNext(); Console.WriteLine(MoveNext方法返回的{0}, result); if (!result) { break; } Console.WriteLine(获取当前值……); Console.WriteLine(获取到的当前值为{0}, iterator.Current); } Console.ReadKey(); } } 为了展示迭代的细节以上代码使用了while循环正常情况下一般使用foreach。和上次不同这次在迭代方法中我们返回的是IEnumerable;对象而不是IEnumerator;对象。通常为了实现IEnumerable接口只需要返回IEnumerator对象即可如果自是想从一个方法中返回一些列的数据那么使用IEnumerable.以下是输出结果   从输出结果中可以看出一下几点 直到第一次调用MoveNextCreateEnumerable中的方法才被调用。在调用MoveNext的时候已经做好了所有操作返回Current属性并没有执行任何代码。代码在yield return之后就停止执行等待下一次调用MoveNext方法的时候继续执行。在方法中可以有多个yield return语句。在最后一个yield return执行完成后代码并没有终止。调用MoveNext返回false使得方法结束。    第一点尤为重要这意味着不能在迭代块中写任何在方法调用时需要立即执行的代码--比如说参数验证。如果将参数验证放在迭代块中那么他将不能够很好的起作用这是经常会导致的错误的地方而且这种错误不容易发现。     下面来看如何停止迭代以及finally语句块的特殊执行方式。   2.3 迭代器的特殊执行流程     在普通的方法中return语句通常有两种作用一是返回调用者执行的结果。二是终止方法的执行在终止之前执行finally语句中的方法。在上面的例子中我们看到了yield return语句只是短暂的退出了方法在MoveNext再次调用的时候继续执行。在这里我们没有写finally语句块。如何真正的退出方法退出方法时finnally语句块如何执行下面来看看一个比较简单的结构yield break语句块。 使用 yield break 结束一个迭代     通常我们要做的是使方法只有一个退出点通常多个退出点的程序会使得代码不易阅读特别是使用try catch finally等语句块进行资源清理以及异常处理的时候。在使用迭代块的时候也会遇到这样的问题但如果你想早点退出迭代那么使用yield break就能达到想要的效果。他能够马上终止迭代使得下一次调用MoveNext的时候返回false。 下面的代码演示了从1迭代到100但是时间超时的时候就停止了迭代。 static IEnumerableInt32 CountWithTimeLimit(DateTime limit) {try{for (int i 1; i 100; i) { if (DateTime.Now limit) { yield break; } yield return i; } } finally { Console.WriteLine(停止迭代); Console.ReadKey(); } } static void Main(string[] args) { DateTime stop DateTime.Now.AddSeconds(2); foreach (Int32 i in CountWithTimeLimit(stop)) { Console.WriteLine(返回 {0}, i); Thread.Sleep(300); } } 下图是输出结果可以看出迭代语句正常终止yield return语句和普通方法中的return语句一样下面来看看finally语句块是什么时候以及如何执行的。   Finally语句块的执行     通常finally语句块在当方法执行退出特定区域时就会执行。迭代块中的finally语句和普通方法中的finally语句块不一样。就像我们看到的yield return语句停止了方法的执行而不是退出方法根据这一逻辑在这种情况下finally语句块中的语句不会执行。     但当碰到yield break语句的时候就会执行finally 语句块这根普通方法中的return一样。一般在迭代块中使用finally语句来释放资源就像使用using语句一样。     下面来看finally语句如何执行。    不管是迭代到了100次或者是由于时间到了停止了迭代或者是抛出了异常finally语句总会执行但是在有些情况下我们不想让finally语句块被执行。     只有在调用MoveNext后迭代块中的语句才会执行那么如果不掉用MoveNext呢如果调用几次MoveNext然后停止调用结果会怎么样呢请看下面的代码 DateTime stop DateTime.Now.AddSeconds(2); foreach (Int32 i in CountWithTimeLimit(stop)) {if (i 3) { Console.WriteLine(返回中^); return; } Thread.Sleep(300); }    在forech中return语句之后因为CountWithTimeLimit中有finally块所以代码继续执行CountWithTimeLimit中的finally语句块。foreach语句会调用GetEnumerator返回的迭代器的Dispose方法。在结束迭代之前调用包含迭代块的迭代器的Dispose方法时状态机会执行在迭代器范围内处于暂停状态下的代码范围内的所有finally块这有点复杂但是结果很容易解释只有使用foreach调用迭代迭代块中的finally块会如期望的那样执行。下面可以用代码验证以上结论 IEnumerableInt32 iterable CountWithTimeLimit(stop); IEnumeratorInt32 iterator iterable.GetEnumerator();iterator.MoveNext(); Console.WriteLine(返回 {0}, iterator.Current); iterator.MoveNext(); Console.WriteLine(返回 {0}, iterator.Current); Console.ReadKey(); 代码输出如下 上图可以看出停止迭代没有打印出来当我们手动调用iterator的Dispose方法时会看到如下的结果。在迭代器迭代结束前终止迭代器的情况很少见也很少不使用foreach语句而是手动来实现迭代如果要手动实现迭代别忘了在迭代器外面使用using语句以确保能够执行迭代器的Dispose方法进而执行finally语句块。  下面来看看微软对迭代器的一些实现中的特殊行为   2.4 迭代器执行中的特殊行为       如果使用C#2的编译器将迭代块编译然后使用ildsam或者Reflector查看生成的IL代码你会发现在幕后编译器回味我们生成了一些嵌套的类型(nested type).下图是使用Ildsam来查看生成的IL ,最下面两行是代码中的的两个静态方法上面蓝色的CountWithTimeLimitd_0是编译器为我们生成的类(尖括号只是类名和泛型无关)代码中可以看出该类实现了那些接口以及有哪些方法和字段。大概和我们手动实现的迭代器结构类似。 真正的代码逻辑实在MoveNext方法中执行的其中有一个大的switch语句。幸运的是作为一名开发人员没必要了解这些细节但一些迭代器执行的方式还是值得注意的 在MoveNext方法第一次执行之前Current属性总是返回迭代器返回类型的默认的值。例如IEnumeratble返回的是Int32类型那么默认初始值是0所以在调用MoveNext方法之前调用Current属性就会返回0。MoveNext方法返回false后Current属性总是返回最后迭代的那个值。Reset方法一般会抛出异常而在本文开始代码中我们手动实现一个迭代器时在Reset中能够正确执行逻辑。编译器为我们产生的嵌套类会同时实现IEnumerator的泛型和非泛型版本(恰当的时候还会实现IEnumerable的泛型和非泛型版本).   没有正确实现Reset方法是有原因的--编译器不知道需要使用怎样的逻辑来从新设置迭代器。很多人认为不应该有Reset方法很多集合并不支持因此调用者不应该依赖这一方法。    实现其它接口没有坏处。方法中返回IEnumerable接口他实现了五个接口(包括IDisposable)作为一个开发者不用担心这些。同时实现IEnumerable和IEnumerator接口并不常见编译器为了使迭代器的行为总是正常并且为能够在当前的线程中仅仅需要迭代一个集合就能创建一个单独的嵌套类型才这么做的。    Current属性的行为有些古怪他保存了迭代器的最后一个返回值并且阻止了垃圾回收期进行收集。 因此自动实现的迭代器方法有一些小的缺陷但是明智的开发者不会遇到任何问题使用他能够节省很多代码量使得迭代器的使用程度比C#1中要广。下面来看在实际开发中迭代器简化代码的地方。     3.实际开发中使用迭代的例子   3.1 从时间段中迭代日期 在涉及到时间区段时通常会使用循环代码如下 for (DateTime day timetable.StartDate; day timetable.EndDate; dayday.AddDays(1)) {…… } 循环有时没有迭代直观和有表现力在本例中可以理解为“时间区间中的每一天”这正是foreach使用的场景。因此上述循环如果写成迭代代码会更美观 foreach(DateTime day in timetable.DateRange) {…… } 在C#1.0中要实现这个需要下一定功夫。到了C#2.0就变得简单了。在timetable类中只需要添加一个属性 public IEnumerableDateTime DateRange {get{for (DateTime dayStartDate ; day EndDate; dayday.AddDays(1)) { yield return day; } } }    只是将循环移动到了timetable类的内部但是经过这一改动使得封装变得更为良好。DateRange属性只是遍历时间区间中的每一天每一次返回一天。如果想要使得逻辑变得复杂一点只需要改动一处。这一小小的改动使得代码的可读性大大增强接下来可以考虑将这个Range扩展为泛型RangeT。   3.2迭代读取文件中的每一行   读取文件时我们经常会书写这样的代码 using (TextReader readerFile.OpenText(fileName)) {String line;while((linereader.ReadLine())!null){……} }   这一过程中有4个环节 如何获取TextReader管理TextReader的生命周期通过TextReader.ReadLine迭代所有的行对行进行处理可以从两个方面对这一过程进行改进:可以使用委托--可以写一个拥有reader和一个代理作为参数的辅助方法使用代理方法来处理每一行最后关闭reader这经常被用来展示闭包和代理。还有一种更为优雅更符合LINQ方式的改进。除了将逻辑作为方法参数传进去我们可以使用迭代来迭代一次迭代一行代码这样我们就可以使用foreach语句。代码如下 static IEnumerableString ReadLines(String fileName) {using (TextReader reader File.OpenText(fileName)){String line; while ((line reader.ReadLine()) ! null) { yield return line; } } } 这样就可以使用如下foreach方法来读取文件了 foreach (String line in ReadLines(test.txt)) {Console.WriteLine(line); }    方法的主体部分和之前的一样使用yield return返回了读取到的每一行只是在迭代结束后有点不同。之前的操作先打开文档每一次读取一行然后在读取结束时关闭reader。虽然”当读取结束时”和之前方法中使用using相似但当使用迭代时这个过程更加明显。 这就是为什么foreach迭代结束后会调用迭代器的dispose方法这么重要的原因了这个操作能够保证reader能够得到释放。迭代方法中的using语句块类似与try/finally语句块finally语句在读取文件结束或者当我们显示调用IEnumeratorString 的Dispose方法时都会执行。可能有时候会通过ReadLine().GetEnumerator()的方式返回IEnumeratorString 进行手动迭代而没有调用Dispose方法就会产生资源泄漏。通常会使用foreach语句来迭代循环所以这个问题很少会出现。但是还是有必要意识到这个潜在的问题。       该方法封装了前三个步骤这可能有点苛刻。将生命周期和方法进行封装是有必要的现在扩展一下假如我们要从网络上读取一个流文件或者我们想使用UTF-8编码的方法我们需要将第一个部分暴漏给方法调用者使得方法的调用签名大致如下 static IEnumerableString ReadLines(TextReader reader) 这样有很多不好的地方我们想对reader有绝对的控制使得调用者能够在结束后能进行资源清理。问题在于如果在第一次调用MoveNext()之前出现错误那么我们就没有机会进行资源清理工作了。IEnumerableString自身不能释放他存储了某个状态需要被清理。另一个问题是如果GetEnumerator被调用两次我们本意是返回两个独立的迭代器然后他们却使用了相同的reader。一种方法是将返回类型改为IEnumeratorString,但这样的话不能使用foreach进行迭代而且如果没有执行到MoveNext方法的话资源也得不到清理。    幸运的是有一种方法可以解决以上问题。就像代码不必立即执行我们也不需要reader立即执行。我们可以提供一个接口实现“如果需要一个TextReader我们可以提供”。在.NET 3.5中有一个代理签名如下 public delegate TResult FuncTResult() 代理没有参数返回和类型参数相同的类型。我们想获得TextReader对象所以可以使用FuncTextReader代码如下 using (TextReader readerprovider()) {String line;while ((linereader.ReadLine())!null){yield return line;} }   3.3 使用迭代块和迭代条件来对集合进行进行惰性过滤    LINQ允许对内存集合或者数据库等多种数据源用简单强大的方式进行查询。虽然C#2没有对查询表达式lambda表达及扩展方法进行集成。但是我们也能达到类似的效果。    LINQ的一个核心的特征是能够使用where方法对数据进行过滤。提供一个集合以及过滤条件代理过滤的结果就会在迭代的时候通过惰性匹配每匹配一个过滤条件就返回一个结果。这有点像ListT.FindAll方法但是LINQ支持对所有实现了IEnumerableT接口的对象进行惰性求值。虽然从C#3开始支持LINQ但是我们也可以使用已有的知识在一定程度上实现LINQ的Where语句。代码如下 public static IEnumerableT WhereT(IEnumerableT source, PredicateT predicate) {if (source null || predicate null) throw new ArgumentNullException(); return WhereImpl(source, predicate); } private static IEnumerableT WhereImplT(IEnumerableT source, PredicateT predicate) { foreach (T item in source) { if (predicate(item)) yield return item; } } IEnumerableString lines ReadLines(FakeLinq.cs); PredicateString predicate delegate(String line) { return line.StartsWith(using); };     如上代码中我们将整个实现分为了两个部分参数验证和具体逻辑。虽然看起来奇怪但是对于错误处理来说是很有必要的。如果将这两个部分方法放到一个方法中如果用户调用了WhereString(null,null)将不会发生任何问题至少我们期待的异常没有抛出。这是由于迭代块的惰性求值机制产生的。在用户迭代的时候第一次调用MoveNext方法之前方法主体中的代码不会执行就像在2.2节中看到的那样。如果你想急切的对方法的参数进行判断那么没有一个地方能够延缓异常这使得bug的追踪变得困难。标准的做法如上代码将方法分为两部分一部分像普通方法那样对参数进行验证另一部分代码使用迭代块对主体逻辑数据进行惰性处理。     迭代块的主体很直观对集合中的逐个元素使用predict代理方法进行判断如果满足条件则返回。如果不满足条件则迭代下一个直到满足条件为止。如果要在C#1中实现这点逻辑就很困难特别是实现其泛型版本。    后面的那段代码演示了使用之前的readline方法读取数据然后用我们的where方法来过滤获取line中以using开头的行和用File.ReadAllLines及Array.FindAllString实现这一逻辑的最大的差别是我们的方法是完全惰性和流线型的(Streaming)。每一次只在内存中请求一行并对其进行处理当然如果文件比较小的时候没有什么差别但是如果文件很大例如上G的日志文件这种方法的优势就会显现出来了。   4 总结    C#对许多设计模式进行了间接的实现使得实现这些模式变得很容易。相对来针对某一特定的设计模式直接实现的的特性比较少。从foreach代码中看出C#1对迭代器模式进行了直接的支持但是没有对进行迭代的集合进行有效的支持。对集合实现一个正确的IEnumerable很耗时容易出错也很很枯燥。在C#2中编译器为我们做了很多工作为我们实现了一个状态机来实现迭代。     本文还展示了和LINQ相似的一个功能对集合进行过滤。IEnumerableT在LINQ中最重要的一个接口如果想要在LINQ To Object上实现自己的LINQ操作那么你会由衷的感叹这个接口的强大功能以及C#语言提供的迭代块的用处。     本文还展示了实际项目中使用迭代块使得代码更加易读和逻辑性更好的例子希望这些例子使你对理解迭代有所帮助。转载于:https://www.cnblogs.com/fsspring/p/9503790.html
http://www.pierceye.com/news/831962/

相关文章:

  • 荣成做网站的公司百度地图 wordpress
  • 扁平设计网站湖南健康二维码app下载安装
  • 大连成久建设工程有限公司网站针对爬虫爬取做的优化
  • 建设官方网站企业网银登录网站版面设计
  • 网站建设学什么软件怎样创建自己的公众号
  • 网站脑图怎么做云伙伴小程序开发公司
  • 网站设置兼容模式怎么弄wordpress文件架构
  • 网站模块顺序调整网易对象存储wordpress
  • 深圳网络建设网站郑州网站优化服务
  • 辽阳专业建设网站公司网站html动态效果代码
  • 微信上可以做网站吗网页设计作业讲解
  • 长春好的做网站公司潍坊 网站
  • 做网站自己装服务器谷歌排名规则
  • 58.搜房等网站怎么做效果才好商贸公司企业简介模板
  • 中国最早做网站是谁卖网站怎样做
  • 张店专业网站优化哪家好书画工作室网站模板网站建设
  • 兰州网站制作怎么样青海哪家做网站的公司最大
  • 云龙湖旅游景区网站建设招标网站升级改版需要几天
  • 高端大气网络设计建设公司网站织梦模板沈阳模板建站哪家好
  • 郑州哪有做网站的高端网站建设与制作
  • 江阴网站网站建设蓝色 宽屏 网站 模板下载
  • 网站建设设计大作业重庆公共信息交易资源网
  • 做公司网站的多少钱公司建网站价格
  • 河间米各庄网站建设制作网站页面模板 建设中
  • wordpress首页添加站点统计小工具住房城乡建设部门门户网站
  • 网站建设在哪块做创业园网站建设
  • 郑州搭建网站公司互联网公司网站建设的目的
  • 响应式视频网站什么是权重高的网站
  • 做教育网站挣钱我的网站域名是什么
  • django 网站开发实例哪里可以检测胎儿性别