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

长沙公司做网站多少钱wordpress 搜索功能

长沙公司做网站多少钱,wordpress 搜索功能,南通网站建,网站免费优化平台文章目录 第16章 结构、联合和枚举16.1 结构变量16.1.1 结构变量的声明16.1.2 结构变量的初始化16.1.3 指示器(C99)16.1.4 对结构的操作 16.2 结构类型16.2.1 结构标记的声明16.2.2 结构类型的定义16.2.3 结构作为参数和返回值16.2.4 复合字面量(C99)16.2.5 匿名结构(C1X) 16.3… 文章目录 第16章 结构、联合和枚举16.1 结构变量16.1.1 结构变量的声明16.1.2 结构变量的初始化16.1.3 指示器(C99)16.1.4 对结构的操作 16.2 结构类型16.2.1 结构标记的声明16.2.2 结构类型的定义16.2.3 结构作为参数和返回值16.2.4 复合字面量(C99)16.2.5 匿名结构(C1X) 16.3 嵌套的数组和结构16.3.1 嵌套的结构16.3.2 结构数组16.3.3 结构数组的初始化16.3.4 程序——维护零件数据库 16.4 联合16.4.1 用联合来节省空间16.4.2 用联合来构造混合的数据结构16.4.3 为联合添加“标记字段”16.4.4 匿名联合(C1X) 16.5 枚举16.5.1 枚举标记和类型名16.5.2 枚举作为整数16.5.3 枚举声明“标记字段” 问与答写在最后 第16章 结构、联合和枚举 ——函数延迟绑定数据结构导致绑定。记住在编程过程后期再结构化数据。 本章介绍3种新的类型结构、联合和枚举。结构是可能具有不同类型的值成员的集合。联合和结构很类似不同之处在于联合的成员共享同一存储空间。这样的结果是联合可以每次存储一个成员但是无法同时存储全部成员。枚举是一种整数类型它的值由程序员来命名。 在这3种类型中结构是到目前为止最重要的一种所以本章的大部分内容是关于结构的。16.1节说明了如何声明结构变量以及如何对其进行基本操作。随后16.2节解释了定义结构类型的方法借助结构类型我们就可以编写接受结构类型参数或返回结构的函数。16.3节探讨如何实现数组和结构的嵌套。本章的最后两节分别讨论了联合16.4节和枚举16.5节。 16.1 结构变量 到目前为止介绍的唯一一种数据结构就是数组。数组有两个重要特性: 首先数组的所有元素具有相同的类型其次为了选择数组元素需要指明元素的位置作为整数下标。 结构所具有的特性与数组有很大不同。结构的元素在C语言中的说法是结构的成员可能具有不同的类型。而且每个结构成员都有名字因此为了选择特定的结构成员需要指明结构成员的名字而不是它的位置。 由于大多数编程语言提供类似的特性因此结构可能听起来很熟悉。在其他一些语言中经常把结构称为记录record把结构的成员称为字段field。 16.1.1 结构变量的声明 当需要存储相关数据项的集合时结构是一种合乎逻辑的选择。例如假设需要记录存储在仓库中的零件。每种零件需要存储的信息可能包括零件的编号整数、零件的名称字符串以及现有零件的数量整数。为了产生一个可以存储全部3种数据项的变量可以使用类似下面这样的声明 struct { int number; char name[NAME_LEN1]; int on_hand; } part1, part2;//结构的成员在内存中是按照声明的顺序存储的每个结构变量都有3个成员number零件的编号、name零件的名称和on_hand现有数量。注意!!这里的声明格式和C语言中其他变量的声明格式一样。struct{...}指明了类型part1和part2是具有这种类型的变量。 每个结构代表一种新的作用域。任何声明在此作用域内的名字都不会和程序中的其他名字冲突。用C语言的术语可表述为每个结构都为它的成员设置了独立的名字空间name space。例如下列声明可以出现在同一程序中 struct { int number; char name[NAME_LEN1]; int on_hand; } part1, part2; struct { char name[NAME_LEN1]; int number; char sex; } employee1, employee2;结构part1和part2中的成员number和成员name不会与结构employee1和employee2中的成员number和成员name冲突。 16.1.2 结构变量的初始化 和数组一样结构变量也可以在声明的同时进行初始化。为了对结构进行初始化要把待存储到结构中的值的列表准备好并用花括号把它括起来 struct { int number; char name[NAME_LEN1]; int on_hand; } part1 {528, Disk drive, 10}, part2 {914, Printer cable, 5};初始化器中的值必须按照结构成员的顺序来显示。在此例中结构part1的成员number值为528成员name则是Disk drive以此类推。 结构初始化器遵循的原则类似于数组初始化器的原则。用于结构初始化器的表达式必须是常量。例如不能用变量来初始化结构part1的成员on_hand。这一限制从C99开始放宽了见18.5节。初始化器中的成员数可以少于它所初始化的结构就像数组那样任何“剩余的”成员都用0作为它的初始值。特别地剩余的字符数组中的字节数为0表示空字符串。 16.1.3 指示器(C99) 在8.1节学习数组时讨论过C99中的指示器它在结构中也可以使用。考虑前面这个例子中part1的初始化器 {528, Disk drive, 10}指示器与之类似但是在初始化时需要按成员的名字来指定初值 {.number 528, .name Disk drive, .on_hand 10} 点号和成员名称的组合也是指示器数组元素的指示器在形式上有所不同。 指示器有几个优点: 其一易读且容易进行验证这是因为读者可以清楚地看出结构中的成员和初始化器中的值之间的对应关系。其二初始化器中的值的顺序不需要与结构中成员的顺序一致。以上这个例子可以写为 {.on_hand 10, .name Disk drive, .number 528} 因为顺序不是问题所以程序员不必记住原始声明时成员的顺序。而且成员的顺序在之后还可以改变不会影响指示器。 指示器中列出来的值前面不一定要有指示器数组也是如此见8.1节。考虑下面的例子 {.number 528, Disk drive, .on_hand 10} 值Disk drive的前面并没有指示器所以编译器会认为它用于初始化结构中位于number之后的成员。初始化器中没有涉及的成员都设为0。 16.1.4 对结构的操作 既然最常见的数组操作是取下标根据位置选择数组元素那么也就无须惊讶结构最常用的操作是选择成员了。但是结构成员是通过名字而不是通过位置访问的。 为了访问结构内的成员首先写出结构的名字然后写一个句点再写出成员的名字。例如下列语句将显示结构part1的成员的值 printf(Part number: %d\n, part1.number); printf(Part name: %s\n, part1.name); printf(Quantity on hand: %d\n, part1.on_hand);结构的成员是左值4.2节所以它们可以出现在赋值运算的左侧也可以作为自增或自减表达式的操作数 part1.number 258; /* changes part1s part number */ part1.on_hand; /* increments part1s quantity on hand */ 用于访问结构成员的句点实际上就是一个C语言的运算符。它的运算优先级与后缀和后缀--运算符一样所以句点运算符的优先级几乎高于所有其他运算符。考虑下面的例子 scanf(%d, part1.on_hand);表达式part1.on_hand包含两个运算符即和.。.运算符的优先级高于运算符所以就像希望的那样计算的是part1.on_hand的地址。 结构的另一种主要操作是赋值运算 part2 part1;这一语句的效果是把part1.number复制到part2.number把part1.name复制到part2.name以此类推。 因为数组不能用运算符进行复制所以结构可以用运算符复制应该是一个惊喜。更大的惊喜是对结构进行复制时嵌在结构内的数组也被复制。一些程序员利用这种性质来产生“空”结构以封装稍后将进行复制的数组 struct { int a[10]; } a1, a2; a1 a2; /* legal, since a1 and a2 are structures */ 运算符仅仅用于类型兼容的结构。两个同时声明的结构比如part1和part2是兼容的。正如下一节你会看到的那样使用同样的“结构标记”或同样的类型名声明的结构也是兼容的。 请注意!!除了赋值运算C语言没有提供其他用于整个结构的操作。特别是不能使用运算符和!来判定两个结构相等还是不等。 16.2 结构类型 16.1节虽然说明了声明结构变量的方法但是没有讨论一个重要的问题命名结构类型。假设程序需要声明几个具有相同成员的结构变量。如果一次可以声明全部变量那么没有什么问题。但是如果需要在程序中的不同位置声明变量那么问题就复杂了。如果在某处编写了: struct { int number; char name[NAME_LEN1]; int on_hand; } part1;并且在另一处编写了: struct { int number; char name[NAME_LEN1]; int on_hand; } part2;那么立刻就会出现问题。重复的结构信息会使程序膨胀。因为难以确保这些声明会保持一致所以将来修改程序会有风险。 但是这些还不是最大的问题。根据C语言的规则part1和part2不具有兼容的类型(因为没有同时声明)因此不能把part1赋值给part2反之亦然。而且因为part1和part2的类型都没有名字所以也就不能把它们用作函数调用的参数。 为了克服这些困难需要定义表示结构类型而不是特定的结构变量的名字。C语言提供了两种命名结构的方法可以声明“结构标记”也可以使用typedef来定义类型名类型定义7.5节。 16.2.1 结构标记的声明 结构标记structure tag是用于标识某种特定结构的名字。下面的例子声明了名为part的结构标记 struct part { int number; char name[NAME_LEN1]; int on_hand; }; //注意右花括号后的分号是必不可少的它表示声明结束。请注意!!如果无意间忽略了结构声明结尾的分号可能会导致奇怪的错误。考虑下面的例子 struct part { int number; char name[NAME_LEN1]; int on_hand; } /*** WRONG: semicolon missing ***/ f(void) { ... return 0; /* error detected at this line */ }程序员没有指定函数f的返回类型编程有点儿随意。因为前面的结构声明没有正常终止所以编译器会假设函数f的返回值是struct part类型的。编译器直到执行函数中第一条return语句时才会发现错误结果得到含义模糊的出错消息。 一旦创建了标记part就可以用它来声明变量了 struct part part1, part2;但是不能通过省略单词struct来缩写这个声明 part part1, part2; /*** WRONG ***/part不是类型名。如果没有单词struct的话它就没有任何意义。 因为结构标记只有在前面放置了单词struct时才会有意义所以它们不会和程序中用到的其他名字发生冲突。程序拥有名为part的变量是完全合法的虽然有点儿容易混淆。 顺便说一句结构标记的声明可以和结构变量的声明合并在一起 struct part { int number; char name[NAME_LEN1]; int on_hand; } part1, part2; 在这里不仅声明了结构标记part可能稍后会用part声明更多的变量而且声明了变量part1和part2。 所有声明为struct part类型的结构彼此之间是兼容的 struct part part1 {528, Disk drive, 10}; struct part part2; part2 part1; /* legal; both parts have the same type */ 16.2.2 结构类型的定义 除了声明结构标记还可以用typedef来定义真实的类型名。例如可以按照如下方式定义名为Part的类型 typedef struct { int number; char name[NAME_LEN1]; int on_hand; } Part;注意类型Part的名字必须出现在定义的末尾而不是在单词struct的后边。 可以像内置类型那样使用Part。例如可以用它声明变量 Part part1, part2; 因为类型Part是typedef的名字所以不允许书写struct Part。无论在哪里声明所有的Part类型的变量都是兼容的。 需要命名结构时通常既可以选择声明结构标记也可以使用typedef。但是正如稍后将看到的结构用于链表17.5节时强制使用声明结构标记。在本书的大多数例子中我使用的是结构标记而不是typedef名。 16.2.3 结构作为参数和返回值 函数可以有结构类型的实际参数和返回值。下面来看两个例子。当把part结构用作实际参数时第一个函数显示出结构的成员 void print_part(struct part p) { printf(Part number: %d\n, p.number); printf(Part name: %s\n, p.name); printf(Quantity on hand: %d\n, p.on_hand); }下面是print_part可能的调用方法 print_part(part1);第二个函数返回part结构此结构由函数的实际参数构成 struct part build_part(int number, const char * name, int on_hand) { struct part p; p.number number; strcpy (p.name, name); p.on_hand on_hand; return p; } 注意函数build_part的形式参数名和结构part的成员名相同是合法的因为结构拥有自己的名字空间。下面是build_part可能的调用方法 part1 build_part(528, Disk drive, 10); 给函数传递结构和从函数返回结构都要求生成结构中所有成员的副本。这样的结果是这些操作对程序强加了一定数量的系统开销特别是结构很大的时候。为了避免这类系统开销有时用传递指向结构的指针来代替传递结构本身是很明智的做法。类似地可以使函数返回指向结构的指针来代替返回实际的结构。在17.5节的例子中可以看到用指向结构的指针作为参数或者作为返回值的函数。 除了效率方面的考虑之外避免创建结构的副本还有其他原因。例如stdio.h定义了一个名为FILE的类型它通常是结构。每个FILE结构存储的都是已打开文件的状态信息因此在程序中必须是唯一的。stdio.h中每个用于打开文件的函数都返回一个指向FILE结构的指针每个对已打开文件执行操作的函数都需要用FILE指针作为参数。 有时可能希望在函数内部初始化结构变量来匹配其他结构可能作为函数的形式参数。在下面的例子中part2的初始化器是传递给函数f的形式参数 void f(struct part part1) { struct part part2 part1; ... }C语言允许这类初始化器因为初始化的结构此例中的part2具有自动存储期10.1节也就是说它局部于函数并且没有声明为static。初始化器可以是适当类型的任意表达式包括返回结构的函数调用。 16.2.4 复合字面量(C99) 9.3节介绍过从C99开始引入的新特性复合字面量。在那一节中复合字面量被用于创建没有名字的数组这样做的目的通常是将数组作为参数传递给函数。复合字面量同样也可以用于“实时”创建一个结构而不需要先将其存储在变量中。生成的结构可以像参数一样传递可以被函数返回也可以赋值给变量。接下来看两个例子。 首先使用复合字面量创建一个结构这个结构将传递给函数。例如可以按如下方式调用print_part函数 print_part((struct part) {528, Disk drive, 10}); 上面的复合字面量创建了一个part结构依次包括成员528、Disk drive和10。这个结构之后被传递到print_part显示。 下面的语句把复合字面量赋值给变量 part1 (struct part) {528, Disk drive, 10}; //可以赋值是因为结构本身的原因不适用于数组 int a[3]; a (int[]){1, 2, 3}; //错误语句这一语句类似于包含初始化器的声明但不完全一样——初始化器只能出现在声明中不能出现在这样的赋值语句中。 一般来说复合字面量包括用圆括号括住的类型名和后续的初始化器。如果复合字面量代表一个结构类型名可以是结构标签的前面加上struct如本例所示或者typedef名。一个复合字面量的初始化器部分可以包含指示器 print_part((struct part) {.on_hand 10, .name Disk drive, .number 528}); 复合字面量不会提供完全的初始化所以任何未初始化的成员默认值为0。 16.2.5 匿名结构(C1X) 从C11开始结构或者联合16.4节的成员也可以是另一个没有名字的结构。如果一个结构或者联合包含了这样的成员 没有名称被声明为结构类型但是只有成员列表而没有标记。 则这个成员就是一个匿名结构anonymous structure。 在下例中struct t和union u的第二个成员都是匿名结构: struct t {int i; struct {char c; float f;};}; union u {int i; struct {char c; float f;};}; 现在的问题是如何才能访问匿名结构的成员若某个匿名结构S是结构或者联合X的成员那么S的成员就被当作X的成员。进一步对于多层嵌套的情况如果符合以上条件则可以递归地应用这种关系。 在下面的例子中struct t包含了一个没有标记、没有名称的结构成员这个结构成员的成员c和f被认为属于struct t。 struct t { int i; struct s {int j, k:3;}; // 有标记的成员 struct {char c; float f;}; // 无标记且未命名的成员(匿名结构)struct {double d;} s; // 命名的成员 } t; t.i 2006; t.j 5; // 非法 t.k 6; // 非法 t.c x; // 正确 t.f 2.0; // 正确 t.s.d 22.2;出于同样的原因下面的类型声明将在转换期间得到一个表示错误的诊断信息。因为struct tag的第二个成员是匿名结构而匿名结构的成员中又有一个是匿名结构所以匿名结构的成员i和f被当作struct tag的成员这意味着struct tag有两个成员的名称相同都是i。 struct tag { struct {int i;}; struct {struct {int i; float f;}; double d;}; char c; };尽管匿名结构的成员被当作隶属于包含该结构的上层结构的成员但它的初始化器依然必须采用被花括号包围的形式。 在下例中尽管匿名结构的成员x被认为属于包含它的那个结构struct t但它的初始化器仍然需要使用一对花括号。 struct t {char c; struct {int x;};}; struct t t {x, 1}; // 非法 struct t t {x, {1}}; // 合法16.3 嵌套的数组和结构 结构和数组的组合没有限制。数组可以将结构作为元素结构也可以包含数组和结构作为成员。我们已经看过数组嵌套在结构内部的示例结构part的成员name。下面探讨其他的可能性成员是结构的结构和元素是结构的数组。 16.3.1 嵌套的结构 把一种结构嵌套在另一种结构中经常是非常有用的。例如假设声明了如下的结构此结构用来存储一个人的名、中间名和姓 struct person_name { char first[FIRST_NAME_LEN1]; char middle_initial; char last[LAST_NAME_LEN1]; }; //可以用结构person_name作为更大结构的一部分内容 struct student { struct person_name name; int id, age; char sex; } student1, student2;//访问student1的名、中间名或姓需要应用两次.运算符 strcpy(student1.name.first, Fred);使name成为结构而不是把first、middle_initial和last作为student结构的成员的好处之一就是可以把名字作为数据单元来处理这样操作起来更容易。例如如果打算编写函数来显示名字那么只需要传递一个实际参数person_name结构而不是三个实际参数 display_name(student1.name); 同样把信息从结构person_name复制给结构student的成员name将只需要一次而不是三次赋值 struct person_name new_name; ... student1.name new_name; 16.3.2 结构数组 数组和结构最常见的组合之一就是其元素为结构的数组。这类数组可以用作简单的数据库。例如下列结构part的数组能够存储100种零件的信息 struct part inventory[100]; 为了访问数组中的某种零件可以使用取下标的方式。例如为了显示存储在位置i的零件可以写成 print_part(inventory[i]); 访问结构part内的成员要求结合使用取下标和成员选择。为了给inventory[i]中的成员number赋值883可以写成 inventory[i].number 883; 访问零件名中的单个字符要求先取下标选择特定的零件然后选择成员选择成员name再取下标选择零件名称中的字符。为了使存储在inventory[i]中的名字变为空字符串可以写成 inventory[i].name[0] \0;16.3.3 结构数组的初始化 初始化结构数组与初始化多维数组的方法非常相似。每个结构都拥有自己的带有花括号的初始化器数组的初始化器简单地在结构初始化器的外围括上另一对花括号。 初始化结构数组的原因之一是我们打算把它作为程序执行期间不改变的信息的数据库。例如假设程序在打国际长途电话时需要访问国家地区代码。首先设置结构用来存储国家地区名和相应代码 struct dialing_code { char *country; int code; }; 注意country是指针而不是字符数组。如果计划用dialing_code结构作为变量则可能有问题但是这里没这样做。当初始化dialing_code结构时country会指向字面串。 接下来声明这类结构的数组并对其进行初始化从而使此数组包含一些世界上人口最多的国家地区的代码 const struct dialing_code country_codes[] {{Argentina, 54}, {Bangladesh, 880}, {Brazil, 55}, {Burma (Myanmar), 95}, {China, 86}, {Colombia, 57}, {Congo, Dem. Rep. of, 243}, {Egypt, 20}, {Ethiopia, 251}, {France, 33}, {Germany, 49}, {India , 91}, {Indonesia 62}, {Iran, 98}, {Italy, 39}, {Japan, 81}, {Mexico, 52}, {Nigeria, 234}, {Pakistan, 92}, {Philippines, 63}, {Poland, 48}, {Russia, 7}, {South Africa, 27}, {Korea, 82}, {Spain, 34}, {Sudan, 249}, {Thailand, 66}, {Turkey, 90}, {Ukraine, 380}, {United Kingdom, 44}, {United States, 1}, {Vietnam, 84}}; //每个结构值两边的内层花括号是可选的。然而基于书写风格的考虑最好不要省略它们。由于结构数组以及包含数组的结构很常见因此从C99开始的初始化器允许指示器的组合。假定我们想初始化inventory数组使其只包含一个零件零件编号为528现货数量为10名字暂时为空 struct part inventory[100] {[0].number 528, [0].on_hand 10, [0].name[0] \0}; 列表中的前两项使用了两个指示器一个用于选择数组元素0即part结构另一个用于选择结构中的成员。最后一项使用了3个指示器一个用于选择数组元素一个用于选择该元素的name成员还有一个用于选择name的元素0。 16.3.4 程序——维护零件数据库 为了说明实际应用中数组和结构是如何嵌套的现在开发一个相对大一点的程序此程序用来维护仓库存储的零件信息数据库。程序围绕一个结构数组构建且每个结构包含以下信息零件的编号、名称以及数量。程序将支持下列操作。 添加新零件编号、名称和初始的现货数量。如果零件已经在数据库中或者数据库已满那么程序必须显示出错消息。给定零件编号显示出零件的名称和当前的现货数量。如果零件编号不在数据库中那么程序必须显示出错消息。给定零件编号改变现有的零件数量。如果零件编号不在数据库中那么程序必须显示出错消息。显示列出数据库中全部信息的表格。零件必须按照输入的顺序显示出来。终止程序的执行。 使用i插入、s搜索、u更新、p显示和q退出分别表示这些操作。与程序的会话可能如下所示 Enter operation code: i Enter part number: 528 Enter part name: Disk drive Enter quantity on hand: 10 Enter operation code: s Enter part number: 528 Part name: Disk drive Quantity on hand: 10 Enter operation code: s Enter part number: 914 Part not found. Enter operation code: i Enter part number: 914 Enter part name: Printer cable Enter quantity on hand: 5 Enter operation code: u Enter part number: 528 Enter change in quantity on hand: -2 Enter operation code: s Enter part number: 528 Part name: Disk drive Quantity on hand: 8 Enter operation code: p Part Number Part Name Quantity on Hand 528 Disk drive 8 914 Printer cable 5 Enter operation code: q 程序将在结构中存储每种零件的信息。这里将数据库的大小限制为100种零件这使得用数组来存储结构成为可能这里称此数组为inventory。如果这里的限制值太小可以在将来修改。为了记录当前存储在数组中的零件数使用名为num_parts的变量。 因为这个程序是以菜单方式驱动的所以十分容易勾勒出主循环结构 for (;;) { 提示用户输入操作码 读操作码 switch操作码{ case i: 执行插入操作; break; case s: 执行搜索操作; break; case u: 执行更新操作; break; case p: 执行显示操作; break; case q: 终止程序; default: 显示出错消息; } } 为了方便起见接下来将分别设置不同的函数执行插入、搜索、更新和显示操作。因为这些函数都需要访问inventory和num_parts所以可以把这些变量设置为外部变量。或者把变量声明在main函数内然后把它们作为实际参数传递给函数。从设计角度来说使变量局部于函数通常比把它们外部化更好如果忘记了原因见10.2节。然而在此程序中把inventory和num_parts放在main函数中只会使程序复杂化。 由于稍后会解释的一些原因这里决定把程序分割为三个文件inventory.c文件它包含程序的大部分内容readline.h文件它包含read_line函数的原型readline.c文件它包含read_line函数的定义。本节的后面将讨论后两个文件现在先集中讨论inventory.c文件。 /* inventory.c --Maintains a parts database (array version) */ #include stdio.h #include readline.h #define NAME_LEN 25 #define MAX_PARTS 100 struct part { int number; char name[NAME_LEN1]; int on_hand; } inventory[MAX_PARTS]; int num_parts 0; /* number of parts currently stored */ int find_part(int number); void insert(void); void search(void); void update(void); void print(void); /********************************************************** * main: Prompts the user to enter an operation code, * * then calls a function to perform the requested * * action. Repeats until the user enters the * * command q. Prints an error message if the user * * enters an illegal code. * **********************************************************/ int main(void) { char code; for (;;) { printf(Enter operation code: ); scanf( %c, code); while (getchar() ! \n) /* skips to end of line */ ; switch (code) { case i: insert(); break; case s: search(); break; case u: update(); break; case p: print(); break; case q: return 0; default: printf(Illegal code\n); } printf(\n); } } /********************************************************** * find_part: Looks up a part number in the inventory * * array. Returns the array index if the part * * number is found; otherwise, returns -1. * **********************************************************/ int find_part(int number) { int i; for (i 0; i num_parts; i) if (inventory[i].number number) return i; return -1; } /********************************************************** * insert: Prompts the user for information about a new * * part and then inserts the part into the * * database. Prints an error message and returns * * prematurely if the part already exists or the * * database is full. * **********************************************************/ void insert(void) { int part_number; if (num_parts MAX_PARTS) { printf(Database is full; cant add more parts.\n); return; } printf(Enter part number: ); scanf(%d, part_number); if (find_part(part_number) 0) { printf(Part already exists.\n); return; } inventory[num_parts].number part_number; printf(Enter part name: ); read_line(inventory[num_parts].name, NAME_LEN); printf(Enter quantity on hand: ); scanf(%d, inventory[num_parts].on_hand); num_parts; } /********************************************************** * search: Prompts the user to enter a part number, then * * looks up the part in the database. If the part * * exists, prints the name and quantity on hand; * * if not, prints an error message. * **********************************************************/ void search(void) { int i, number; printf(Enter part number: ); scanf(%d, number); i find_part(number); if (i 0) { printf(Part name: %s\n, inventory[i].name); printf(Quantity on hand: %d\n, inventory[i].on_hand); } else printf(Part not found.\n); } /********************************************************** * update: Prompts the user to enter a part number. * * Prints an error message if the part doesnt * * exist; otherwise, prompts the user to enter * * change in quantity on hand and updates the * * database. * **********************************************************/ void update(void) { int i, number, change; printf(Enter part number: ); scanf(%d, number); i find_part(number); if (i 0) { printf(Enter change in quantity on hand: ); scanf(%d, change); inventory[i].on_hand change; } else printf(Part not found.\n); } /********************************************************** * print: Prints a listing of all parts in the database, * * showing the part number, part name, and * * quantity on hand. Parts are printed in the * * order in which they were entered into the * * database. * **********************************************************/ void print(void) { int i; printf(Part Number Part Name Quantity on Hand\n); for (i 0; i num_parts; i) printf(%7d %-25s%11d\n, inventory[i].number, inventory[i].name, inventory[i].on_hand); }在main函数中格式串 %c允许scanf函数在读入操作码之前跳过空白字符。格式串中的空格是至关重要的如果没有它scanf函数有时会读入前一输入行末尾的换行符。 程序包含一个名为find_part的函数main函数不调用此函数。这个“辅助”函数用于避免多余的代码和简化更重要的函数。通过调用find_partinsert函数、search函数和update函数可以定位数据库中的零件或者简单地确定零件是否存在。 现在还剩下一个细节read_line函数。这个函数用来读零件的名字。13.3节讨论了书写此类函数时的相关问题但是那个read_line函数不能用于这个程序。请思考当用户插入零件时会发生什么 Enter part number: 528 Enter part name: Disk drive 在输入零件的编号后用户按回车键输入零件的名字后再次按了回车键这样每次都无形中给程序留下一个必须读取的换行符。为了方便讨论现在假装这些字符都是可见的: Enter part number: 528¤ Enter part name: Disk drive¤当调用scanf函数来读零件编号时函数读入了5、2和8但是留下了字符¤未读。如果试图用原始的read_line函数来读零件名称那么函数将立刻遇到字符¤并且停止读入。当数值输入的后边跟有字符输入时这种问题非常普遍。解决办法就是编写read_line函数使它在开始往字符串中存储字符之前跳过空白字符。这不仅解决了换行符的问题而且可以避免存储用户在零件名称的开始处输入的任何空白。 因为read_line函数与inventory.c文件中的其他函数无关而且它在其他程序中有复用的可能所以我们决定把此函数从inventory.c中独立出来。read_line函数的原型将放在头文件readline.h中 /* readline.h */ #ifndef READLINE_H #define READLINE_H /********************************************************** * read_line: Skips leading white-space characters, then * * reads the remainder of the input line and * * stores it in str. Truncates the line if its * * length exceeds n. Returns the number of * * characters stored. * **********************************************************/ int read_line(char str[], int n); #endif我们将把read_line的定义放在readline.c文件中 /* readline.c */ #include ctype.h #include stdio.h #include readline.h int read_line(char str[], int n) { int ch, i 0; while (isspace(ch getchar())) ; while (ch ! \n ch ! EOF) { if (i n) str[i] ch; ch getchar(); } str[i] \0; return i; } 表达式isspace(ch getchar())控制第一个while语句。它调用getchar读取一个字符把读入的字符存储在ch中然后使用isspace函数23.5节来判断ch是否是空白字符。如果不是循环终止ch中包含一个非空白字符。15.3节解释了ch的类型为int而不是char的原因还解释了判定EOF的理由。 16.4 联合 像结构一样联合union也是由一个或多个成员构成的而且这些成员可能具有不同的类型。但是编译器只为联合中最大的成员分配足够的内存空间。联合的成员在这个空间内彼此覆盖。这样的结果是给一个成员赋予新值也会改变其他成员的值。 为了说明联合的基本性质现在声明一个联合变量u并且这个联合变量有两个成员 union { int i; double d; } u; //注意联合的声明方式非常类似于结构的声明方式 struct { int i; double d; } s; 事实上结构变量s和联合变量u只有一处不同s的成员存储在不同的内存地址中而u的成员存储在同一内存地址中。 在结构变量s中成员i和d占有不同的内存单元。s总共占用了12字节。在联合变量u中成员i和d互相交叠i实际上是d的前4个字节所以u只占用了8字节此外i和d具有相同的地址。 访问联合成员的方法和访问结构成员的方法相同。为了把数82存储到u的成员i中可以写成 u.i 82;//为了把值74.8存储到成员d中可以写成 u.d 74.8; 因为编译器把联合成员重叠存储所以改变一个成员就会使之前存储在任何其他成员中的值发生改变。因此如果把一个值存储到u.d中那么先前存储在u.i中的值会丢失。如果测试u.i的值那么它会显示出无意义的内容。类似地改变u.i也会影响u.d。由于这个性质可以把u想成存储i或者存储d的地方而不是同时存储二者的地方。结构s允许存储i和d。 联合的性质和结构的性质几乎一样因此可以用声明结构标记和类型的方法来声明联合的标记和类型。像结构一样联合可以使用运算符进行复制也可以传递给函数还可以由函数返回。 联合的初始化方式甚至也和结构的初始化很类似。但是只有联合的第一个成员可以获得初始值。例如可以用下列方式初始化联合u的成员i为0 union { int i; double d; } u {0};注意!!花括号是必需的。花括号内的表达式必须是常量。从C99开始的规则稍有不同在18.5节会看到。 指示器我们在讨论数组和结构时介绍过的一种C99特性也可以用在联合中。指示器允许我们指定需要对联合中的哪个成员进行初始化。例如可以像下面这样初始化u的成员d union { int i; double d; } u {.d 10.0}; 只能初始化一个成员但不一定是第一个。 联合有几种应用现在讨论其中的两种。联合的另外一个应用是用不同的方法观察存储因为这个应用与机器高度相关所以推迟到20.3节再介绍。 16.4.1 用联合来节省空间 在结构中经常使用联合作为节省空间的一种方法。假设打算设计的结构包含通过礼品册售出的商品的信息。礼品册上只有三种商品图书、杯子和衬衫。每种商品都含有库存量、价格以及与商品类型相关的其他信息。 图书书名、作者、页数。杯子设计。衬衫设计、可选颜色、可选尺寸。 最初的设计可能会得到如下结构 struct catalog_item { int stock_number; double price; int item_type; char title[TITLE_LEN1]; char author[AUTHOR_LEN1]; int num_pages; char design[DESIGN_LEN1]; int colors; int sizes; };成员item_type的值将是BOOK、MUG或SHIRT之一。成员colors和sizes将存储颜色和尺寸的组合代码。 虽然上述结构十分好用但是它很浪费空间因为对礼品册中的所有商品来说只有结构中的部分信息是常用的。比如如果商品是图书那么就不需要存储design、colors和sizes。通过在结构catalog_item内部放置一个联合可以减少结构所需要的内存空间。联合的成员将是一些特殊的结构每种结构都包含特定类型的商品所需要的数据 struct catalog_item { int stock_number; double price; int item_type; union { struct { char title[TITLE_LEN1]; char author[AUTHOR_LEN1]; int num_pages; } book; struct { char design[DESIGN_LEN1]; } mug; struct { char design[DESIGN_LEN1]; int colors; int sizes; } shirt; } item; };注意联合名为item是结构catalog_item的成员而结构book、mug和shirt则是联合item的成员。如果c是表示图书的结构catalog_item那么可以用下列方法显示图书的名称 printf(%s, c.item.book.title); 正如上边的例子显示的那样访问嵌套在结构内部的联合是很困难的为了定位图书的名称不得不指明结构的名字c、结构的联合成员的名字item、联合的结构成员的名字book以及此结构的成员名title。 可以用catalog_item结构来说明联合有趣的一面。把值存储在联合的一个成员中然后通过另一个名字来访问该数据通常不太可取因为给联合的一个成员赋值会导致其他成员的值不确定。然而C标准提到了一种特殊情况联合的两个或多个成员是结构而这些结构最初的一个或多个成员是相匹配的。这些成员的顺序应该相同类型也要兼容但名字可以不一样。如果当前某个结构有效则其他结构中的匹配成员也有效。 考虑嵌入在catalog_item结构中的联合。它包含三个结构成员其中两个结构mug和shirt的起始成员design相匹配。现在假定我们给其中一个design成员赋值 strcpy(c.item.mug.design, Cats); //另一个结构中的design成员也会被定义并具有相同的值 printf(%s, c.item.shirt.design); /* prints Cats */ 16.4.2 用联合来构造混合的数据结构 联合还有一个重要的应用创建含有不同类型混合数据的数据结构。现在假设需要数组的元素是int值和double值的混合。因为数组的元素必须是相同的类型所以好像不可能产生如此类型的数组。但是利用联合这件事就相对容易了。首先定义一种联合类型它所包含的成员分别表示要存储在数组中的不同数据类型 typedef union { int i; double d; } Number; //接下来创建一个数组使数组的元素是Number类型的值 Number number_array[1000]; 数组number_array的每个元素都是Number联合。Number联合既可以存储int类型的值又可以存储double类型的值所以可以在数组number_array中存储int和double的混合值。例如假设需要用数组number_array的0号元素来存储5用1号元素来存储8.395。下列赋值语句可以达到期望的效果 number_array[0].i 5; number_array[1].d 8.395; 16.4.3 为联合添加“标记字段” 联合所面临的主要问题是不容易确定联合最后改变的成员因此所包含的值可能是无意义的。请思考下面这个问题假设编写了一个函数用来显示当前存储在联合Number中的值。这个函数可能有下列框架 void print_number(Number n) { if (n 包含一个整数) printf(%d, n.i); else printf(%g, n.d); } 但是没有方法可以帮助函数print_number来确定n包含的是整数还是浮点数。 为了记录此信息可以把联合嵌入一个结构中并且此结构还含有另一个成员“标记字段”或者“判别式”它是用来提示当前存储在联合中的内容的。在本节先前讨论的结构catalog_item中item_type就是用于此目的的。 下面把Number类型转换成具有嵌入联合的结构类型 #define INT_KIND 0 #define DOUBLE_KIND 1 typedef struct { int kind; /* tag field */ union { int i; double d; } u; } Number;//Number有两个成员kind和u。kind的值可能是INT_KIND或DOUBLE_KIND。每次给u的成员赋值时也会改变kind从而提示修改的是u的哪个成员。例如如果n是Number类型的变量对u的成员i进行赋值操作可以采用下列形式 n.kind INT_KIND; n.u.i 82; //注意对i赋值要求首先选择n的成员u然后才是u的成员i。当需要找回存储在Number型变量中的数时kind将表明联合的哪个成员是最后被赋值的。函数print_number可以利用这种能力 void print_number(Number n) { if (n.kind INT_KIND) printf(%d, n.u.i); else printf(%g, n.u.d); } 请注意!!每次对联合的成员进行赋值都由程序负责改变标记字段的内容。 16.4.4 匿名联合(C1X) 从C11开始结构或者联合的成员也可以是另一个没有名字的联合。如果一个结构或者联合包含了这样的成员 没有名称被声明为联合类型但是只有成员列表而没有标记。 则这个成员就是一个匿名联合anonymous union。在下例中struct t和union u的第二个成员都是匿名联合。 struct t {int i; union {char c; float f;};}; union u {int i; union {char c; float f;};}; 现在的问题是如何才能访问匿名联合的成员答案如下若某个匿名联合U是结构或者联合X的成员则U的成员被当作X的成员。进一步对于多层嵌套的情况如果符合以上条件那么可以递归地应用这种关系。(跟结构的情况类似) 在下面的例子中struct t包含了一个没有标记、没有名字的联合成员这个联合的成员c和f被认为属于struct t struct t { int i; struct s {int j, k:3;}; // 有标记的成员 union {char c; float f;}; // 无标记且未命名的成员(匿名联合)struct {double d;} s; // 命名的成员 } t; t.i 2006; t.j 5; // 非法 t.k 6; // 非法 t.c x; // 正确 t.f 2.0; // 正确 t.s.d 22.2; 出于同样的原因下面的类型声明将在转换期间得到一个表示错误的诊断信息。因为struct tag的第二个成员是匿名联合而匿名联合的成员中又有一个是匿名联合所以匿名联合的成员i和f被当作struct tag的成员这意味着struct tag有两个成员的名称相同都是i: struct tag { struct {int i;}; union {union {int i; float f;}; double d;}; char c; };16.5 枚举 在许多程序中我们会需要变量只具有少量有意义的值。例如布尔变量应该只有2种可能的值“真”和“假”。用来存储扑克牌花色的变量应该只有4种可能的值“梅花”、“方片”、“红桃”和“黑桃”。显然可以用声明成整数的方法来处理此类变量并且用一组编码来表示变量的可能值 int s; /* s will store a suit */ ... s 2; /* 2 represents hearts */ 虽然这种方法可行但是也遗留了许多问题。有些人读程序时可能不会意识到s只有4种可能的值而且不会知道2的特殊含义。 使用宏来定义牌的花色“类型”和不同花色的名字是一种正确的措施 #define SUIT int #define CLUBS 0 #define DIAMONDS 1 #define HEARTS 2 #define SPADES 3 //那么前面的示例现在可以变得更加容易阅读 SUIT s; ... s HEARTS;这种方法有所改进但它仍然不是最好的解决方案因为这样做没有为阅读程序的人指出宏表示具有相同“类型”的值。如果可能值的数量很多那么为每个值定义一个宏是很麻烦的。而且因为预处理器会删除我们定义的CLUBS、DIAMONDS、HEARTS和SPADES这些名字所以在调试期间没法使用这些名字。 C语言为具有可能值较少的变量提供了一种专用类型。枚举类型enumeration type是一种值由程序员列出“枚举”的类型而且程序员必须为每个值命名枚举常量。以下例子中枚举的值CLUBS、DIAMONDS、HEARTS和SPADES可以赋值给变量s1和s2 enum {CLUBS, DIAMONDS, HEARTS, SPADES} s1, s2; 虽然枚举和结构、联合没有什么共同的地方但是它们的声明方法很类似。但是与结构或联合的成员不同枚举常量的名字必须不同于作用域范围内声明的其他标识符。 枚举常量类似于用#define指令创建的常量但是两者又不完全一样。特别地枚举常量遵循C语言的作用域规则如果枚举声明在函数体内那么它的常量对外部函数来说是不可见的。 16.5.1 枚举标记和类型名 与命名结构和联合的原因相同我们也常常需要创建枚举的名字。与结构和联合一样可以用两种方法命名枚举通过声明标记的方法或者使用typedef来创建独一无二的类型名。 枚举标记类似于结构和联合的标记。例如为了定义标记suit可以写成: enum suit {CLUBS, DIAMONDS, HEARTS, SPADES}; //变量suit可以按照下列方法来声明 enum suit s1, s2;还可以用typedef把Suit定义为类型名 typedef enum {CLUBS, DIAMONDS, HEARTS, SPADES} Suit; Suit s1, s2;在C89中利用typedef来命名枚举是创建布尔类型的一种非常好的方法 typedef enum {FALSE, TRUE} Bool; 当然从C99开始我们有内置的布尔类型所以使用这一新特性的程序员不需要这样定义Bool类型。 16.5.2 枚举作为整数 在系统内部C语言会把枚举变量和常量作为整数来处理。默认情况下编译器会把整数0, 1, 2, ...赋给特定枚举中的常量。例如在枚举suit的例子中CLUBS、DIAMONDS、HEARTS和SPADES分别表示0、1、2和3。 我们可以为枚举常量自由选择不同的值。现在假设希望CLUBS、DIAMONDS、HEARTS和SPADES分别表示1、2、3和4可以在声明枚举时指明这些数 enum suit {CLUBS 1, DIAMONDS 2, HEARTS 3, SPADES 4}; 枚举常量的值可以是任意整数也可以不用按照特定的顺序列出 enum dept {RESEARCH 20, PRODUCTION 10, SALES 25}; 两个或多个枚举常量具有相同的值甚至也是合法的。 当没有为枚举常量指定值时它的值比前一个常量的值大1。第一个枚举常量的值默认为0。在下列枚举中BLACK的值为0LT_GRAY为7DK_GRAY为8而WHITE为15 enum EGA_colors {BLACK, LT_GRAY 7, DK_GRAY, WHITE 15}; 枚举的值只不过是一些稀疏分布的整数所以C语言允许把它们与普通整数进行混合 int i; enum {CLUBS, DIAMONDS, HEARTS, SPADES} s; i DIAMONDS; /* i is now 1 */ s 0; /* s is now 0 (CLUBS) */ s; /* s is now 1 (DIAMONDS) */ i s 2; /* i is now 3 */ //编译器会把s作为整型变量来处理而CLUBS、DIAMONDS、HEARTS和SPADES只是数0、1、2和3的名字而已。请注意!!虽然把枚举的值作为整数使用非常方便但是把整数用作枚举的值是非常危险的。例如我们可能会不小心把4存储到s中而4不能跟任何花色相对应。 16.5.3 枚举声明“标记字段” 用枚举来解决16.4节遇到的问题是非常合适的用来确定联合中最后一个被赋值的成员。例如在结构Number中可以把成员kind声明为枚举而不是int typedef struct { enum {INT_KIND, DOUBLE_KIND} kind; union { int i; double d; } u; } Number; 这种新结构和旧结构的用法完全一样。这样做的好处是不仅远离了宏INT_KIND和DOUBLE_KIND它们现在是枚举常量而且阐明了kind的含义现在kind显然应该只有两种可能的值INT_KIND和DOUBLE_KIND。 问与答 问1当试图使用sizeof运算符来确定结构中的字节数量时获得的数大于成员加在一起的总数。为什么会这样 答看看下面这个例子 struct { char a; int b; } s; 如果char类型值占1字节而int类型值占4字节s会是多大呢显而易见的答案5字节不一定正确。一些计算机要求特定数据项的地址是某个字节数一般是2、4或8由数据项的类型决定的倍数。为了满足这一要求编译器会在邻近的成员之间留“空洞”即不使用的字节从而使结构的成员“对齐”。如果假设数据项必须从4字节的倍数开始那么结构s的成员a后面将有3字节的空洞从而sizeof(s)为8。 顺便说一句就像在成员间有空洞一样结构末尾也可以有空洞。例如结构 struct { int a; char b; } s; 可能在成员b的后边有3字节的空洞。 问2结构的开始处是否可能会有“空洞” 答不会。C标准指明只允许在成员之间或者最后一个成员的后边有空洞。因此可以确保指向结构第一个成员的指针就是指向整个结构的指针。但是注意这两个指针的类型不同。 问3使用来判定两个结构是否相等为什么是不合法的 答这种操作超出了C语言的范围因为任何实现都不能确保它始终是和语言的体系相一致的。逐个比较结构成员是极没有效率的。比较结构中的全部字节是相对较好的方法许多计算机有专门的指令可以用来快速执行此类比较。然而如果结构中含有空洞那么比较字节会产生不正确的结果。即使对应的成员有同样的值空洞中的废弃值也可能会不同。这个问题可以通过下列方法解决那就是编译器要确保空洞始终包含相同的值比如零。然而初始化空洞会影响全部使用结构的程序的性能所以它是不可行的。 问4为什么C语言提供两种命名结构类型的方法标记命名和typedef命名 答C语言早期没有typedef所以标记是结构类型命名的唯一有效方法。当加入typedef时已经太晚了以致无法删除标记了。此外当结构的成员是指向同类型结构的指针时见17.5节的node结构标记仍然是非常必要的。 问5结构可否同时有标记名和typedef名 答可以。事实上标记名和typedef名甚至可以是一样的虽然不要求这么做 typedef struct part { int number; char name[NAME_LEN1]; int on_hand; } part; 问6如何能在程序的几个文件间共享结构类型呢 答把结构标记如果喜欢也可以用typedef的声明放在头文件中然后在需要结构的地方包含此头文件就可以了。例如为了共享结构part可以在头文件中放入下列内容 struct part { int number; char name[NAME_LEN1]; int on_hand; }; //注意这里只是声明结构标记而没有声明具有这种类型的变量。顺便提一句含有结构标记声明或结构类型声明的头文件可能需要保护以避免多次包含15.2节。在同一文件中两次声明同一个标记或类型是错误的。类似的说明也适用于联合和枚举。 问7如果在两个不同的文件中包含了结构part的声明那么一个文件中的part类型变量和另一个文件中的part类型变量是否一样呢 答从技术上来说不一样。但是C标准提到一个文件中的part类型变量所具有的类型和另一个文件中的part类型变量所具有的类型是兼容的。具有兼容类型的变量可以互相赋值所以在实际中“兼容的”类型和“相同的”类型之间几乎没有差异。 C89和从C99开始的标准在有关结构兼容性的法则上稍有不同。在C89中对于在不同文件中定义的结构来说如果它们的成员具有同样的名字并且顺序一样那么它们是兼容的相应的成员类型也是兼容的。从C99开始则更进一步它要求两个结构要么具有相同的标记要么都没有标记。 类似的兼容性法则也适用于联合和枚举在C89和从C99开始的标准之间的差异也一样。 问8让指针指向复合字面量是否合法 答合法。考虑16.2节的print_part函数。目前这个函数的形式参数是一个part结构。如果将参数修改为指向part结构的指针函数的效率会更高。这样使用该函数来显示复合字面量就可以通过在参数前面加取地址运算符的方式来完成 print_part((struct part) {528, Disk drive, 10}); 问9C99允许指针指向复合字面量似乎使我们可以修改该字面量是这样吗 答是的。虽然很少这么做但复合字面量是左值可以修改。 问10我在程序中看到枚举的最后一个常量后面有一个逗号就像这样 enum gray_values { BLACK 0, DARK_GRAY 64, GRAY 128, LIGHT_GRAY 192, };这样是否合法 答从C99开始这是合法的C99之前的有些编译器也允许这么做。允许有“尾逗号”可以使修改枚举更方便因为我们可以直接在枚举的最后增加常量而无须改变已有的代码。例如我们可能希望在枚举中增加WHITE enum gray_values { BLACK 0, DARK_GRAY 64, GRAY 128, LIGHT_GRAY 192, WHITE 255, }; //LIGHT_GRAY的定义之后的逗号使得在列表最后增加WHITE很容易。做出这一修改的原因是C89允许在初始化器中使用尾逗号所以在枚举中也提供这一灵活性就显得很一致。 顺便说一句从C99开始也允许在复合字面量中使用尾逗号。 问11枚举类型的值可以用作下标吗 答是的的确可以。它们是整数值默认从0开始逐渐增加所以是很理想的下标。此外从C99开始枚举常量可以用作指示器中的下标。下面是一个例子 enum weekdays {MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY}; const char *daily_specials[] { [MONDAY] Beef ravioli, [TUESDAY] BLTs, [WEDNESDAY] Pizza, [THURSDAY] Chicken fajitas, [FRIDAY] Macaroni and cheese };写在最后 本文是博主阅读《C语言程序设计现代方法第2版·修订版》时所作笔记日后会持续更新后续章节笔记。欢迎各位大佬阅读学习如有疑问请及时联系指正希望对各位有所帮助Thank you very much
http://www.yingshimen.cn/news/46386/

相关文章:

  • 大气高端网站一元云购网站黑客攻击
  • 海南高端建设网站黄山建设厅官方网站
  • 怎么往公司网站添加上海住房城乡建设网站
  • 网站编辑专题怎么做网站 建设 现状分析
  • 自己做网站一定要实名吗wordpress图片网盘插件腾讯
  • 西安可以做网站的荆州seo公司
  • 淘宝网官方网站免费下载购买游戏软件做网站
  • 对学院网站建设的建议wordpress如何把菜单
  • 涟水建设银行网站wordpress autumn默认主页
  • 河南建设网站公司简介seo服务外包价格
  • 建网站服务器用哪种网站开发技术公司
  • 做招商如何选择网站个人怎么申请微信小程序
  • 大网站开发太原网站快速排名优化
  • 建设公司网站源码网站建设中切图的意义
  • 门户网站建设工作会议佛山网站建设哪儿有
  • 网站诊断方法国内无代码开发平台
  • 网站开发一般都有系统搭建网页游戏
  • 建设网站用什么时候开始网业打开慢的原因
  • 电商网站代码wordpress首页文章显示固定分类
  • 响水做网站哪家好本地工程招标网
  • 平台式网站模板做淘宝客网站用什么系统
  • 陕西网站开发联系方式电脑怎样做轰炸网站
  • 公司网站首页导航html如何建设网络营销渠道
  • 如何让百度快照找到自己的网站网站建设工作台账
  • 公司网站怎么发布文章网页游戏开服表青云志
  • 门户网站建设和管理情况服务器搭建云电脑
  • 湖州品牌网站设计过年做啥网站能致富
  • 网站建设关键字国内做外贸网站的有哪些
  • 做网站老板嫌弃太丑谁的锅Windows下配置WordPress
  • 志愿服务网站建设中标公告php网站建设流程