查看: 2668|回复: 4

关于typedef struct和struct的一些资料

[复制链接]
  • TA的每日心情
    郁闷
    昨天 09:47
  • 签到天数: 1623 天

    连续签到: 1 天

    [LV.Master]伴坛终老

    发表于 2013-1-22 09:26:40 | 显示全部楼层 |阅读模式
    分享到:
    【by dikar云墨竹@ iteye.com】
    C/C++中typedef struct和struct的用法由于对typedef理解不够,因此从网上摘录了一些资料,整理如下:   

    C/C++中typedef struct和struct的用法   

    struct  _x1 { ...}x1; 和 typedef  struct  _x2{ ...} x2; 有什么不同?   


    其实, 前者是定义了类_x1和_x1的对象实例x1,  后者是定义了类_x2和_x2的类别名x2 ,   

    所以它们在使用过程中是有取别的.请看实例1.   

    [知识点]   

    结构也是一种数据类型, 可以使用结构变量, 因此,  象其它 类型的变量一样, 在使用结构变量时要先对其定义。   

        定义结构变量的一般格式为:   

         struct 结构名   

         {   

              类型  变量名;   

              类型  变量名;   

              ...   

         } 结构变量;   

        结构名是结构的标识符不是变量名。   



    另一种常用格式为:     



    typedef struct 结构名   

         {   

              类型  变量名;   

              类型  变量名;   

              ...   

         } 结构别名;   





    另外注意:  在C中,struct不能包含函数。在C++中,对struct进行了扩展,可以包含函数。   



    ======================================================================   



    实例1:  struct.cpp   



    #include <iostream>  

    using namespace std;   

    typedef struct _point{   

              int x;   

              int y;   

              }point; //定义类,给类一个别名   



    struct _hello{   

           int x,y;         

          } hello; //同时定义类和对象   





    int main()   

    {            

        point pt1;         

        pt1.x = 2;   

        pt1.y = 5;   

        cout<< "ptpt1.x=" << pt1.x << "pt.y=" <<pt1.y <<endl;   



    //hello pt2;   

        //pt2.x = 8;   

        //pt2.y =10;   

        //cout<<"pt2pt2.x="<< pt2.x <<"pt2.y="<<pt2.y <<endl;   

        //上面的hello pt2;这一行编译将不能通过. 为什么?   

        //因为hello是被定义了的对象实例了.   

        //正确做法如下: 用hello.x和hello.y   



        hello.x = 8;   

        hello.y = 10;     

        cout<< "hellohello.x=" << hello.x << "hello.y=" <<hello.y <<endl;   



        return 0;               

    }   





    typedef struct与struct的区别   

    1. 基本解释   

    typedef为C语言的关键字,作用是为一种数据类型定义一个新名字。这里的数据类型包括内部数据类型(int,char等)和自定义的数据类型(struct等)。   



    在编程中使用typedef目的一般有两个,一个是给变量一个易记且意义明确的新名字,另一个是简化一些比较复杂的类型声明。   



    至于typedef有什么微妙之处,请你接着看下面对几个问题的具体阐述。   



    2. typedef & 结构的问题   



    当用下面的代码定义一个结构时,编译器报了一个错误,为什么呢?莫非C语言不允许在结构中包含指向它自己的指针吗?请你先猜想一下,然后看下文说明:   



    typedef struct tagNode   

    {   

     char *pItem;   

     pNode pNext;   

    } *pNode;   



    答案与分析:   



    1、typedef的最简单使用   



    typedef long byte_4;   



      给已知数据类型long起个新名字,叫byte_4。   



    2、 typedef与结构结合使用   



    typedef struct tagMyStruct   

    {   

     int iNum;   

     long lLength;   

    } MyStruct;   



    这语句实际上完成两个操作:   



      1) 定义一个新的结构类型   



    struct tagMyStruct   

    {   

     int iNum;   

     long lLength;   

    };   



      分析:tagMyStruct称为“tag”,即“标签”,实际上是一个临时名字,struct 关键字和tagMyStruct一起,构成了这个结构类型,不论是否有typedef,这个结构都存在。   



      我们可以用struct tagMyStruct varName来定义变量,但要注意,使用tagMyStruct varName来定义变量是不对的,因为struct 和tagMyStruct合在一起才能表示一个结构类型。   



      2) typedef为这个新的结构起了一个名字,叫MyStruct。   



    typedef struct tagMyStruct MyStruct;   



      因此,MyStruct实际上相当于struct tagMyStruct,我们可以使用MyStruct varName来定义变量。   



      答案与分析   



      C语言当然允许在结构中包含指向它自己的指针,我们可以在建立链表等数据结构的实现上看到无数这样的例子,上述代码的根本问题在于typedef的应用。   



      根据我们上面的阐述可以知道:新结构建立的过程中遇到了pNext域的声明,类型是pNode,要知道pNode表示的是类型的新名字,那么在类型本身还没有建立完成的时候,这个类型的新名字也还不存在,也就是说这个时候编译器根本不认识pNode。   



      解决这个问题的方法有多种:   

    1)、   



    typedef struct tagNode   

    {   

     char *pItem;   

     struct tagNode *pNext;   

    } *pNode;   

    2)、   



    typedef struct tagNode *pNode;   

    struct tagNode   

    {   

     char *pItem;   

     pNode pNext;   

    };   



    注意:在这个例子中,你用typedef给一个还未完全声明的类型起新名字。C语言编译器支持这种做法。   

    3)、规范做法:   



    struct tagNode   

    {   

     char *pItem;   

     struct tagNode *pNext;   

    };   

    typedef struct tagNode *pNode;   





    C++中typedef关键字的用法   

    Typedef 声明有助于创建平台无关类型,甚至能隐藏复杂和难以理解的语法。不管怎样,使用 typedef 能为代码带来意想不到的好处,通过本文你可以学习用 typedef 避免缺欠,从而使代码更健壮。   

          typedef 声明,简称 typedef,为现有类型创建一个新的名字。比如人们常常使用 typedef 来编写更美观和可读的代码。所谓美观,意指 typedef 能隐藏笨拙的语法构造以及平台相关的数据类型,从而增强可移植性和以及未来的可维护性。本文下面将竭尽全力来揭示 typedef 强大功能以及如何避免一些常见的陷阱。   

          如何创建平台无关的数据类型,隐藏笨拙且难以理解的语法?   



    使用 typedefs 为现有类型创建同义字。定义易于记忆的类型名   

      typedef 使用最多的地方是创建易于记忆的类型名,用它来归档程序员的意图。类型出现在所声明的变量名字中,位于 ''typedef'' 关键字右边。例如:typedef int size;   

      此声明定义了一个 int 的同义字,名字为 size。注意 typedef 并不创建新的类型。它仅仅为现有类型添加一个同义字。你可以在任何需要 int 的上下文中使用 size:void measure(size * psz);   

    size array[4];   

    size len = file.getlength();   

    std::vector <size> vs;   

      typedef 还可以掩饰符合类型,如指针和数组。例如,你不用象下面这样重复定义有 81 个字符元素的数组:char line[81];   

    char text[81];   

    定义一个 typedef,每当要用到相同类型和大小的数组时,可以这样:typedef char Line[81];   

    Line text, secondline;   

    getline(text);   

    同样,可以象下面这样隐藏指针语法:typedef char * pstr;   

    int mystrcmp(pstr, pstr);   

      这里将带我们到达第一个 typedef 陷阱。标准函数 strcmp()有两个‘const char *'类型的参数。因此,它可能会误导人们象下面这样声明 mystrcmp():int mystrcmp(const pstr, const pstr);   

      这是错误的,按照顺序,‘const pstr'被解释为‘char * const'(一个指向 char 的常量指针),而不是‘const char *'(指向常量 char 的指针)。这个问题很容易解决:typedef const char * cpstr;   

    int mystrcmp(cpstr, cpstr); // 现在是正确的   

    记住:不管什么时候,只要为指针声明 typedef,那么都要在最终的 typedef 名称中加一个 const,以使得该指针本身是常量,而不是对象。代码简化   

      上面讨论的 typedef 行为有点像 #define 宏,用其实际类型替代同义字。不同点是 typedef 在编译时被解释,因此让编译器来应付超越预处理器能力的文本替换。例如:typedef int (*PF) (const char *, const char *);   

      这个声明引入了 PF 类型作为函数指针的同义字,该函数有两个 const char * 类型的参数以及一个 int 类型的返回值。如果要使用下列形式的函数声明,那么上述这个 typedef 是不可或缺的:PF Register(PF pf);   

      Register() 的参数是一个 PF 类型的回调函数,返回某个函数的地址,其署名与先前注册的名字相同。做一次深呼吸。下面我展示一下如果不用 typedef,我们是如何实现这个声明的:int (*Register (int (*pf)(const char *, const char *)))   

    (const char *, const char *);   

      很少有程序员理解它是什么意思,更不用说这种费解的代码所带来的出错风险了。显然,这里使用 typedef 不是一种特权,而是一种必需。持怀疑态度的人可能会问:"OK,有人还会写这样的代码吗?",快速浏览一下揭示 signal()函数的头文件 <csinal>,一个有同样接口的函数。typedef 和存储类关键字(storage class specifier)   

      这种说法是不是有点令人惊讶,typedef 就像 auto,extern,mutable,static,和 register 一样,是一个存储类关键字。这并是说 typedef 会真正影响对象的存储特性;它只是说在语句构成上,typedef 声明看起来象 static,extern 等类型的变量声明。下面将带到第二个陷阱:typedef register int FAST_COUNTER; // 错误   

      编译通不过。问题出在你不能在声明中有多个存储类关键字。因为符号 typedef 已经占据了存储类关键字的位置,在 typedef 声明中不能用 register(或任何其它存储类关键字)。促进跨平台开发   

      typedef 有另外一个重要的用途,那就是定义机器无关的类型,例如,你可以定义一个叫 REAL 的浮点类型,在目标机器上它可以i获得最高的精度:typedef long double REAL;   

    在不支持 long double 的机器上,该 typedef 看起来会是下面这样:typedef double REAL;   

    并且,在连 double 都不支持的机器上,该 typedef 看起来会是这样:、typedef float REAL;   

      你不用对源代码做任何修改,便可以在每一种平台上编译这个使用 REAL 类型的应用程序。唯一要改的是 typedef 本身。在大多数情况下,甚至这个微小的变动完全都可以通过奇妙的条件编译来自动实现。不是吗? 标准库广泛地使用 typedef 来创建这样的平台无关类型:size_t,ptrdiff 和 fpos_t 就是其中的例子。此外,象 std::string 和 std:fstream 这样的 typedef 还隐藏了长长的,难以理解的模板特化语法,例如:basic_string<char, char_traits<char>,allocator<char>> 和 basic_ofstream<char, char_traits<char>>。   





     typedef & #define的问题  有下面两种定义pStr数据类型的方法,两者有什么不同?哪一种更好一点?typedef char *pStr;   

    #define pStr char *;   

      答案与分析:   

      通常讲,typedef要比#define要好,特别是在有指针的场合。请看例子:typedef char *pStr1;   

    #define pStr2 char *;   

    pStr1 s1, s2;   

    pStr2 s3, s4;   

      在上述的变量定义中,s1、s2、s3都被定义为char *,而s4则定义成了char,不是我们所预期的指针变量,根本原因就在于#define只是简单的字符串替换而typedef则是为一个类型起新名字。  #define用法例子:#define f(x) x*x   

    main( )   

    {   

     int a=6,b=2,c;   

     c=f(a) / f(b);   

     printf("%d \\n",c);   

    }   

      以下程序的输出结果是: 36。   

      因为如此原因,在许多C语言编程规范中提到使用#define定义时,如果定义中包含表达式,必须使用括号,则上述定义应该如下定义才对:#define f(x) (x*x)   当然,如果你使用typedef就没有这样的问题。   

      4. typedef & #define的另一例  下面的代码中编译器会报一个错误,你知道是哪个语句错了吗?   

    typedef char * pStr;   

    char string[4] = "abc";   

    const char *p1 = string;   

    const pStr p2 = string;   

    p1++;   

    p2++;   

      答案与分析:   

      是p2++出错了。这个问题再一次提醒我们:typedef和#define不同,它不是简单的文本替换。上述代码中const pStr p2并不等于const char * p2。const pStr p2和const long x本质上没有区别,都是对变量进行只读限制,只不过此处变量p2的数据类型是我们自己定义的而不是系统固有类型而已。因此,const pStr p2的含义是:限定数据类型为char *的变量p2为只读,因此p2++错误。  #define与typedef引申谈   

      1) #define宏定义有一个特别的长处:可以使用 #ifdef ,#ifndef等来进行逻辑判断,还可以使用#undef来取消定义。   

      2) typedef也有一个特别的长处:它符合范围规则,使用typedef定义的变量类型其作用范围限制在所定义的函数或者文件内(取决于此变量定义的位置),而宏定义则没有这种特性。   

      5. typedef & 复杂的变量声明   

      在编程实践中,尤其是看别人代码的时候,常常会遇到比较复杂的变量声明,使用typedef作简化自有其价值,比如:   

      下面是三个变量的声明,我想使用typdef分别给它们定义一个别名,请问该如何做?>1:int *(*a[5])(int, char*);   

    >2:void (*b[10]) (void (*)());   

    >3. doube(*)() (*pa)[9];   

      答案与分析:  对复杂变量建立一个类型别名的方法很简单,你只要在传统的变量声明表达式里用类型名替代变量名,然后把关键字typedef加在该语句的开头就行了。>1:int *(*a[5])(int, char*);   

    //pFun是我们建的一个类型别名   

    typedef int *(*pFun)(int, char*);   

    //使用定义的新类型来声明对象,等价于int* (*a[5])(int, char*);   

    pFun a[5];>2:void (*b[10]) (void (*)());   

    //首先为上面表达式蓝色部分声明一个新类型   

    typedef void (*pFunParam)();   

    //整体声明一个新类型   

    typedef void (*pFun)(pFunParam);   

    //使用定义的新类型来声明对象,等价于void (*b[10]) (void (*)());   

    pFun b[10];>3. doube(*)() (*pa)[9];   

    //首先为上面表达式蓝色部分声明一个新类型   

    typedef double(*pFun)();   

    //整体声明一个新类型   

    typedef pFun (*pFunParam)[9];   

    //使用定义的新类型来声明对象,等价于doube(*)() (*pa)[9];   

    pFunParam pa;  


    回复

    使用道具 举报

  • TA的每日心情
    郁闷
    昨天 09:47
  • 签到天数: 1623 天

    连续签到: 1 天

    [LV.Master]伴坛终老

    发表于 2013-1-22 09:27:45 | 显示全部楼层
    [by ohwww @
    c语言中的结构(struct)和联合(union)简介
    看到有朋友介绍union,我以前还没有用过这个东西呢,也不懂,就去搜了点资料来看,也转给大家,希望坛子里的给予改正或补充。谢谢!


                      联       合(union)  
        1. 联合说明和联合变量定义  
        联合也是一种新的数据类型, 它是一种特殊形式的变量。  
        联合说明和联合变量定义与结构十分相似。其形式为:  
         union 联合名{  
              数据类型 成员名;  
              数据类型 成员名;  
              ...  
         } 联合变量名;  
        联合表示几个变量公用一个内存位置, 在不同的时间保存不同的数据类型 和不同长度的变量。  
        下例表示说明一个联合a_bc:  
         union a_bc{  
              int i;  
              char mm;  
         };  
        再用已说明的联合可定义联合变量。  
        例如用上面说明的联合定义一个名为lgc的联合变量, 可写成:  
          union a_bc lgc;  
        在联合变量lgc中, 整型量i和字符mm公用同一内存位置。  
        当一个联合被说明时, 编译程序自动地产生一个变量, 其长度为联合中最大的变量长度。  
        联合访问其成员的方法与结构相同。同样联合变量也可以定义成数组或指针,但定义为指针时, 也要用"->;"符号, 此时联合访问成员可表示成:  
         联合名->;成员名  
        另外, 联合既可以出现在结构内, 它的成员也可以是结构。  
        例如:  
         struct{  
              int age;  
              char *addr;  
              union{  
                   int i;  
                   char *ch;  
              }x;  
         }y[10];  
        若要访问结构变量y[1]中联合x的成员i, 可以写成:  
          y[1].x.i;  
        若要访问结构变量y[2]中联合x的字符串指针ch的第一个字符可写成:  
          *y[2].x.ch;  
        若写成"y[2].x.*ch;"是错误的。  

        2. 结构和联合的区别  
        结构和联合有下列区别:  
        1. 结构和联合都是由多个不同的数据类型成员组成, 但在任何同一时刻, 联合转只存放了一个被选中的成员, 而结构的所有成员都存在。  
        2. 对于联合的不同成员赋值, 将会对其它成员重写,  原来成员的值就不存在了, 而对于结构的不同成员赋值是互不影响的。  
        下面举一个例了来加对深联合的理解。  
        例4:  
    main()  
    {  
        union{                   /*定义一个联合*/  
        int i;  
        struct{             /*在联合中定义一个结构*/  
                  char first;  
                  char second;  
                 }half;  
        }number;  

        number.i=0x4241;         /*联合成员赋值*/  
        printf("%c%c\n", number.half.first, mumber.half.second);  
        number.half.first='a';   /*联合中结构成员赋值*/  
        number.half.second='b';  
        printf("%x\n", number.i);  
        getch();  
    }  
        输出结果为:  
         AB  
         6261  
        从上例结果可以看出: 当给i赋值后, 其低八位也就是first和second的值;当给first和second赋字符后, 这两个字符的ASCII码也将作为i 的低八位和高八位。



    ohwww 回复于:2003-10-13 11:28:40

    结构类型定义和结构变量说明

      在实际问题中,一组数据往往具有不同的数据类型。例如,在学生登记表中,姓名应为字符型;学号可为整型或字符型; 年龄应为整型;性别应为字符型;成绩可为整型或实型。 显然不能用一个数组来存放这一组数据。 因为数组中各元素的类型和长度都必须一致,以便于编译系统处理。为了解决这个问题,C语言中给出了另一种构造数据类型----“结构”。它相当于其它高级语言中的记录。

      “结构”是一种构造类型,它是由若干“成员”组成的。每一个成员可以是一个基本数据类型或者又是一个构造类型。结构既是一种“构造”而成的数据类型,那么在说明和使用之前必须先定义它,也就是构造它。如同在说明和调用函数之前要先定义函数一样。

    一、结构的定义

    定义一个结构的一般形式为:  
    struct 结构名  
    {  
    成员表列  
    };
    成员表由若干个成员组成, 每个成员都是该结构的一个组成部分。对每个成员也必须作类型说明,其形式为:
    类型说明符 成员名;  
    成员名的命名应符合标识符的书写规定。例如:  
    struct stu
    {
    int num;
    char name[20];
    char sex;
    float score;
    };  
      在这个结构定义中,结构名为stu,该结构由4个成员组成。第一个成员为num,整型变量;第二个成员为name,字符数组;第三个成员为sex,字符变量;第四个成员为score,实型变量。 应注意在括号后的分号是不可少的。结构定义之后,即可进行变量说明。 凡说明为结构stu的变量都由上述4个成员组成。由此可见, 结构是一种复杂的数据类型,是数目固定,类型不同的若干有序变量的集合。

    二、结构类型变量的说明

    说明结构变量有以下三种方法。以上面定义的stu为例来加以说明。
    1. 先定义结构,再说明结构变量。如:  
    struct stu
    {
    int num;
    char name[20];
    char sex;
    float score;
    };
    struct stu boy1,boy2;
    说明了两个变量boy1和boy2为stu结构类型。也可以用宏定义使一个符号常量来表示一个结构类型,例如:  
    #define STU struct stu
    STU
    {
    int num;
    char name[20];
    char sex;
    float score;
    };
    STU boy1,boy2;

    2. 在定义结构类型的同时说明结构变量。例如:  
    struct stu
    {
    int num;
    char name[20];
    char sex;
    float score;
    }boy1,boy2;

    3. 直接说明结构变量。例如:  
    struct
    {
    int num;
    char name[20];
    char sex;
    float score;
    }boy1,boy2;  

      第三种方法与第二种方法的区别在于第三种方法中省去了结构名,而直接给出结构变量。三种方法中说明的boy1,boy2变量都具有图7.1所示的结构。说明了boy1,boy2变量为stu类型后,即可向这两个变量中的各个成员赋值。在上述stu结构定义中,所有的成员都是基本数据类型或数组类型。成员也可以又是一个结构, 即构成了嵌套的结构。例如,图7.2给出了另一个数据结构。 按图7.2可给出以下结构定义:  
    struct date{
    int month;
    int day;
    int year;
    }
    struct{
    int num;
    char name[20];
    char sex;
    struct date birthday;
    float score;
    }boy1,boy2;
      首先定义一个结构date,由month(月)、day(日)、year(年)三个成员组成。在定义并说明变量 boy1 和 boy2 时, 其中的成员birthday被说明为data结构类型。成员名可与程序中其它变量同名,互不干扰。结构变量成员的表示方法在程序中使用结构变量时, 往往不把它作为一个整体来使用。

      在ANSI C中除了允许具有相同类型的结构变量相互赋值以外, 一般对结构变量的使用,包括赋值、输入、输出、 运算等都是通过结构变量的成员来实现的。

      表示结构变量成员的一般形式是: 结构变量名.成员名 例如:boy1.num 即第一个人的学号 boy2.sex 即第二个人的性别 如果成员本身又是一个结构则必须逐级找到最低级的成员才能使用。例如:boy1.birthday.month 即第一个人出生的月份成员可以在程序中单独使用,与普通变量完全相同。

    结构变量的赋值

    前面已经介绍,结构变量的赋值就是给各成员赋值。可用输入语句或赋值语句来完成。
    [例7.1]给结构变量赋值并输出其值。

    main(){
       struct stu
       {
          int num;
          char *name;
          char sex;
          float score;
       } boy1,boy2;
       
       boy1.num=102;
       boy1.name="Zhang ping";
       printf("input sex and score\n");
       scanf("%c %f",&boy1.sex,&boy1.score);
       boy2=boy1;
       printf("Number=%d\nName=%s\n",boy2.num,boy2.name);
       printf("Sex=%c\nScore=%f\n",boy2.sex,boy2.score);
    }



      本程序中用赋值语句给num和name两个成员赋值,name是一个字符串指针变量。用scanf函数动态地输入sex和score成员值,然后把boy1的所有成员的值整体赋予boy2。最后分别输出boy2 的各个成员值。本例表示了结构变量的赋值、输入和输出的方法。

    结构变量的初始化
      如果结构变量是全局变量或为静态变量, 则可对它作初始化赋值。对局部或自动结构变量不能作初始化赋值。
    [例7.2]外部结构变量初始化。

    struct stu /*定义结构*/
    {
       int num;
       char *name;
       char sex;
       float score;
    } boy2,boy1={102,"Zhang ping",'M',78.5};

    main()
    {
       boy2=boy1;
       printf("Number=%d\nName=%s\n",boy2.num,boy2.name);
       printf("Sex=%c\nScore=%f\n",boy2.sex,boy2.score);
    }


    本例中,boy2,boy1均被定义为外部结构变量,并对boy1作了初始化赋值。在main函数中,把boy1的值整体赋予boy2, 然后用两个printf语句输出boy2各成员的值。

    [例7.3]静态结构变量初始化。

    main()
    {
       static struct stu /*定义静态结构变量*/
       {
          int num;
          char *name;
          char sex;
          float score;
       }boy2,boy1={102,"Zhang ping",'M',78.5};
       boy2=boy1;
       printf("Number=%d\nName=%s\n",boy2.num,boy2.name);
       printf("Sex=%c\nScore=%f\n",boy2.sex,boy2.score);
    }

      本例是把boy1,boy2都定义为静态局部的结构变量, 同样可以作初始化赋值。

    结构数组

    数组的元素也可以是结构类型的。 因此可以构成结构型数组。结构数组的每一个元素都是具有相同结构类型的下标结构变量。 在实际应用中,经常用结构数组来表示具有相同数据结构的一个群体。如一个班的学生档案,一个车间职工的工资表等。  
    结构数组的定义方法和结构变量相似,只需说明它为数组类型即可。例如:  
    struct stu
    {
    int num;
    char *name;
    char sex;
    float score;
    }boy[5];  
    定义了一个结构数组boy1,共有5个元素,boy[0]~boy[4]。每个数组元素都具有struct stu的结构形式。 对外部结构数组或静态结构数组可以作初始化赋值,例如:  
    struct stu
    {
       int num;
       char *name;
       char sex;
       float score;
    }boy[5]={
       {101,"Li ping","M",45},
       {102,"Zhang ping","M",62.5},
       {103,"He fang","F",92.5},
       {104,"Cheng ling","F",87},
       {105,"Wang ming","M",58};
    }
    当对全部元素作初始化赋值时,也可不给出数组长度。
    [例7.4]计算学生的平均成绩和不及格的人数。

    struct stu
    {
       int num;
       char *name;
       char sex;
       float score;
    }boy[5]={
      {101,"Li ping",'M',45},
      {102,"Zhang ping",'M',62.5},
      {103,"He fang",'F',92.5},
      {104,"Cheng ling",'F',87},
      {105,"Wang ming",'M',58},
    };
    main()
    {
       int i,c=0;
       float ave,s=0;
       for(i=0;i<5;i++)
       {
          s+=boy.score;
          if(boy.score<60) c+=1;
       }
       printf("s=%f\n",s);
       ave=s/5;
       printf("average=%f\ncount=%d\n",ave,c);
    }

    本例程序中定义了一个外部结构数组boy,共5个元素, 并作了初始化赋值。在main函数中用for语句逐个累加各元素的score 成员值存于s之中,如score的值小于60(不及格)即计数器C加1, 循环完毕后计算平均成绩,并输出全班总分,平均分及不及格人数。

    [例7.5]建立同学通讯录

    #include"stdio.h"
    #define NUM 3
    struct mem
    {
       char name[20];
       char phone[10];
    };
    main()
    {
       struct mem man[NUM];
       int i;
       for(i=0;i<NUM;i++)
       {
          printf("input name:\n");
          gets(man.name);
          printf("input phone:\n");
          gets(man.phone);
       }
       printf("name\t\t\tphone\n\n");
       for(i=0;i<NUM;i++)
          printf("%s\t\t\t%s\n",man.name,man.phone);
    }

      本程序中定义了一个结构mem,它有两个成员name和phone 用来表示姓名和电话号码。在主函数中定义man为具有mem 类型的结构数组。在for语句中,用gets函数分别输入各个元素中两个成员的值。然后又在for语句中用printf语句输出各元素中两个成员值。

    结构指针变量

    结构指针变量的说明和使用一个指针变量当用来指向一个结构变量时, 称之为结构指针变量。
    结构指针变量中的值是所指向的结构变量的首地址。 通过结构指针即可访问该结构变量, 这与数组指针和函数指针的情况是相同的。结构指针变量说明的一般形式为:  
    struct 结构名*结构指针变量名  
    例如,在前面的例7.1中定义了stu这个结构, 如要说明一个指向stu的指针变量pstu,可写为:  
    struct stu *pstu;  

      当然也可在定义stu结构时同时说明pstu。与前面讨论的各类指针变量相同,结构指针变量也必须要先赋值后才能使用。赋值是把结构变量的首地址赋予该指针变量, 不能把结构名赋予该指针变量。如果boy是被说明为stu类型的结构变量,则: pstu=&boy是正确的,而: pstu=&stu是错误的。

      结构名和结构变量是两个不同的概念,不能混淆。 结构名只能表示一个结构形式,编译系统并不对它分配内存空间。 只有当某变量被说明为这种类型的结构时,才对该变量分配存储空间。 因此上面&stu这种写法是错误的,不可能去取一个结构名的首地址。 有了结构指针变量,就能更方便地访问结构变量的各个成员。

    其访问的一般形式为: (*结构指针变量).成员名 或为:
    结构指针变量->;成员名  
    例如: (*pstu).num或者: pstu->;num
    应该注意(*pstu)两侧的括号不可少, 因为成员符“.”的优先级高于“*”。如去掉括号写作*pstu.num则等效于*(pstu.num),这样,意义就完全不对了。 下面通过例子来说明结构指针变量的具体说明和使用方法。
    [例7.6]

    struct stu
    {
       int num;
       char *name;
       char sex;
       float score;
    } boy1={102,"Zhang ping",'M',78.5},*pstu;
    main()
    {
       pstu=&boy1;
       printf("Number=%d\nName=%s\n",boy1.num,boy1.name);
       printf("Sex=%c\nScore=%f\n\n",boy1.sex,boy1.score);
       printf("Number=%d\nName=%s\n",(*pstu).num,(*pstu).name);
       printf("Sex=%c\nScore=%f\n\n",(*pstu).sex,(*pstu).score);
       printf("Number=%d\nName=%s\n",pstu->;num,pstu->;name);
       printf("Sex=%c\nScore=%f\n\n",pstu->;sex,pstu->;score);
    }

      本例程序定义了一个结构stu,定义了stu类型结构变量boy1 并作了初始化赋值,还定义了一个指向stu类型结构的指针变量pstu。在main函数中,pstu被赋予boy1的地址,因此pstu指向boy1 。然后在printf语句内用三种形式输出boy1的各个成员值。 从运行结果可以看出:  
    结构变量.成员名
    (*结构指针变量).成员名
    结构指针变量->;成员名  

      这三种用于表示结构成员的形式是完全等效的。结构数组指针变量结构指针变量可以指向一个结构数组, 这时结构指针变量的值是整个结构数组的首地址。 结构指针变量也可指向结构数组的一个元素,这时结构指针变量的值是该结构数组元素的首地址。设ps为指向结构数组的指针变量,则ps也指向该结构数组的0号元素,ps+1指向1号元素,ps+i则指向i号元素。 这与普通数组的情况是一致的。
    [例7.7]用指针变量输出结构数组。

    struct stu
    {
       int num;
       char *name;
       char sex;
       float score;
    }boy[5]={
       {101,"Zhou ping",'M',45},
       {102,"Zhang ping",'M',62.5},
       {103,"Liou fang",'F',92.5},
       {104,"Cheng ling",'F',87},
       {105,"Wang ming",'M',58},
    };
    main()
    {
       struct stu *ps;
       printf("No\tName\t\t\tSex\tScore\t\n");
       for(ps=boy;ps<boy+5;ps++)
       printf("%d\t%s\t\t%c\t%f\t\n",ps->;num,ps->;name,ps->;sex,ps->;score);
    }

      在程序中,定义了stu结构类型的外部数组boy 并作了初始化赋值。在main函数内定义ps为指向stu类型的指针。在循环语句for的表达式1中,ps被赋予boy的首地址,然后循环5次,输出boy数组中各成员值。 应该注意的是, 一个结构指针变量虽然可以用来访问结构变量或结构数组元素的成员,但是,不能使它指向一个成员。 也就是说不允许取一个成员的地址来赋予它。因此,下面的赋值是错误的。 ps=&boy[1].sex;而只能是:ps=boy;(赋予数组首地址)
    或者是:
    ps=&boy[0];(赋予0号元素首地址)

    结构指针变量作函数参数

      在ANSI C标准中允许用结构变量作函数参数进行整体传送。 但是这种传送要将全部成员逐个传送, 特别是成员为数组时将会使传送的时间和空间开销很大,严重地降低了程序的效率。 因此最好的办法就是使用指针,即用指针变量作函数参数进行传送。 这时由实参传向形参的只是地址,从而减少了时间和空间的开销。
    [例7.8]题目与例7.4相同,计算一组学生的平均成绩和不及格人数。
    用结构指针变量作函数参数编程。

    struct stu
    {
       int num;
       char *name;
       char sex;
       float score;
    }boy[5]={
       {101,"Li ping",'M',45},
       {102,"Zhang ping",'M',62.5},
       {103,"He fang",'F',92.5},
       {104,"Cheng ling",'F',87},
       {105,"Wang ming",'M',58},
    };
    main()
    {
       struct stu *ps;
       void ave(struct stu *ps);
       ps=boy;
       ave(ps);
    }
    void ave(struct stu *ps)
    {
       int c=0,i;
       float ave,s=0;
       for(i=0;i<5;i++,ps++)
       {
          s+=ps->;score;
          if(ps->;score<60) c+=1;
       }
       printf("s=%f\n",s);
       ave=s/5;
       printf("average=%f\ncount=%d\n",ave,c);
    }

      本程序中定义了函数ave,其形参为结构指针变量ps。boy 被定义为外部结构数组,因此在整个源程序中有效。在main 函数中定义说明了结构指针变量ps,并把boy的首地址赋予它,使ps指向boy 数组。然后以ps作实参调用函数ave。在函数ave 中完成计算平均成绩和统计不及格人数的工作并输出结果。与例7.4程序相比,由于本程序全部采用指针变量作运算和处理,故速度更快,程序效率更高。.

    topoic=动态存储分配

      在数组一章中,曾介绍过数组的长度是预先定义好的, 在整个程序中固定不变。C语言中不允许动态数组类型。例如: int n;scanf("%d",&n);int a[n]; 用变量表示长度,想对数组的大小作动态说明, 这是错误的。但是在实际的编程中,往往会发生这种情况, 即所需的内存空间取决于实际输入的数据,而无法预先确定。对于这种问题, 用数组的办法很难解决。为了解决上述问题,C语言提供了一些内存管理函数,这些内存管理函数可以按需要动态地分配内存空间, 也可把不再使用的空间回收待用,为有效地利用内存资源提供了手段。 常用的内存管理函数有以下三个:

    1.分配内存空间函数malloc
    调用形式: (类型说明符*) malloc (size) 功能:在内存的动态存储区中分配一块长度为"size" 字节的连续区域?姆祷刂滴们虻氖椎刂贰?“类型说明符”表示把该区域用于何种数据类型。(类型说明符*)表示把返回值强制转换为该类型指针。“size”是一个无符号数。例如: pc=(char *) malloc (100); 表示分配100个字节的内存空间,并强制转换为字符数组类型, 函数的返回值为指向该字符数组的指针, 把该指针赋予指针变量pc。

    2.分配内存空间函数 calloc
    calloc 也用于分配内存空间。调用形式: (类型说明符*)calloc(n,size) 功能:在内存动态存储区中分配n块长度为“size”字节的连续区域?姆祷刂滴们虻氖椎刂贰?类型说明符*)用于强制类型转换。calloc函数与malloc 函数的区别仅在于一次可以分配n块区域。例如: ps=(struet stu*) calloc(2,sizeof (struct stu)); 其中的sizeof(struct stu)是求stu的结构长度。因此该语句的意思是:按stu的长度分配2块连续区域,强制转换为stu类型,并把其首地址赋予指针变量ps。

    3.释放内存空间函数free
    调用形式: free(void*ptr); 功能:释放ptr所指向的一块内存空间,ptr 是一个任意类型的指针变量,它指向被释放区域的首地址。被释放区应是由malloc或calloc函数所分配的区域:[例7.9]分配一块区域,输入一个学生数据。

    main()
    {
       struct stu
       {
          int num;
          char *name;
          char sex;
          float score;
       } *ps;
       ps=(struct stu*)malloc(sizeof(struct stu));
       ps->;num=102;
       ps->;name="Zhang ping";
       ps->;sex='M';
       ps->;score=62.5;
       printf("Number=%d\nName=%s\n",ps->;num,ps->;name);
       printf("Sex=%c\nScore=%f\n",ps->;sex,ps->;score);
       free(ps);
    }

      本例中,定义了结构stu,定义了stu类型指针变量ps。 然后分配一块stu大内存区,并把首地址赋予ps,使ps指向该区域。再以ps为指向结构的指针变量对各成员赋值,并用printf 输出各成员值。最后用free函数释放ps指向的内存空间。 整个程序包含了申请内存空间、使用内存空间、释放内存空间三个步骤, 实现存储空间的动态分配。链表的概念在例7.9中采用了动态分配的办法为一个结构分配内存空间。每一次分配一块空间可用来存放一个学生的数据, 我们可称之为一个结点。有多少个学生就应该申请分配多少块内存空间, 也就是说要建立多少个结点。当然用结构数组也可以完成上述工作, 但如果预先不能准确把握学生人数,也就无法确定数组大小。 而且当学生留级、退学之后也不能把该元素占用的空间从数组中释放出来。 用动态存储的方法可以很好地解决这些问题。 有一个学生就分配一个结点,无须预先确定学生的准确人数,某学生退学, 可删去该结点,并释放该结点占用的存储空间。从而节约了宝贵的内存资源。 另一方面,用数组的方法必须占用一块连续的内存区域。 而使用动态分配时,每个结点之间可以是不连续的(结点内是连续的)。 结点之间的联系可以用指针实现。 即在结点结构中定义一个成员项用来存放下一结点的首地址,这个用于存放地址的成员,常把它称为指针域。可在第一个结点的指针域内存入第二个结点的首地址, 在第二个结点的指针域内又存放第三个结点的首地址, 如此串连下去直到最后一个结点。最后一个结点因无后续结点连接,其指针域可赋为0。这样一种连接方式,在数据结构中称为“链表”。图7.3为链表的示意图。

      在图7.3中,第0个结点称为头结点, 它存放有第一个结点的首地址,它没有数据,只是一个指针变量。 以下的每个结点都分为两个域,一个是数据域,存放各种实际的数据,如学号num,姓名name,性别sex和成绩score等。另一个域为指针域, 存放下一结点的首地址。链表中的每一个结点都是同一种结构类型。例如, 一个存放学生学号和成绩的结点应为以下结构:
    struct stu
    {  
       int num;
       int score;
       struct stu *next;
    }
      前两个成员项组成数据域,后一个成员项next构成指针域, 它是一个指向stu类型结构的指针变量。链表的基本操作对链表的主要操作有以下几种:  
    1.建立链表;
    2.结构的查找与输出;
    3.插入一个结点;
    4.删除一个结点;
    下面通过例题来说明这些操作。
    [例7.10]建立一个三个结点的链表,存放学生数据。 为简单起见, 我们假定学生数据结构中只有学号和年龄两项。
    可编写一个建立链表的函数creat。程序如下:

    #define NULL 0
    #define TYPE struct stu
    #define LEN sizeof (struct stu)
    struct stu
    {
       int num;
       int age;
       struct stu *next;
    };
    TYPE *creat(int n)
    {
       struct stu *head,*pf,*pb;
       int i;
       for(i=0;i<n;i++)
       {
          pb=(TYPE*) malloc(LEN);
          printf("input Number and Age\n");
          scanf("%d%d",&pb->;num,&pb->;age);
          if(i==0)
             pf=head=pb;
          else pf->;next=pb;
             pb->;next=NULL;
          pf=pb;
       }
       return(head);
    }

      在函数外首先用宏定义对三个符号常量作了定义。这里用TYPE表示struct stu,用LEN表示sizeof(struct stu)主要的目的是为了在以下程序内减少书写并使阅读更加方便。结构stu定义为外部类型,程序中的各个函数均可使用该定义。

      creat函数用于建立一个有n个结点的链表,它是一个指针函数,它返回的指针指向stu结构。在creat函数内定义了三个stu结构的指针变量。head为头指针,pf 为指向两相邻结点的前一结点的指针变量。pb为后一结点的指针变量。在for语句内,用malloc函数建立长度与stu长度相等的空间作为一结点,首地址赋予pb。然后输入结点数据。如果当前结点为第一结点(i==0),则把pb值 (该结点指针)赋予head和pf。如非第一结点,则把pb值赋予pf 所指结点的指针域成员next。而pb所指结点为当前的最后结点,其指针域赋NULL。 再把pb值赋予pf以作下一次循环准备。
      creat函数的形参n,表示所建链表的结点数,作为for语句的循环次数。图7.4表示了creat函数的执行过程。

    [例7.11]写一个函数,在链表中按学号查找该结点。

    TYPE * search (TYPE *head,int n)
    {
       TYPE *p;
       int i;
       p=head;
       while (p->;num!=n && p->;next!=NULL)
          p=p->;next; /* 不是要找的结点后移一步*/
       if (p->;num==n) return (p);
       if (p->;num!=n&& p->;next==NULL)
          printf ("Node %d has not been found!\n",n);
    }

      本函数中使用的符号常量TYPE与例7.10的宏定义相同,等于struct stu?辛礁鲂尾?head是指向链表的指针变量,n为要查找的学号。进入while语句,逐个检查结点的num成员是否等于n,如果不等于n且指针域不等于NULL(不是最后结点)则后移一个结点,继续循环。如找到该结点则返回结点指针。 如循环结束仍未找到该结点则输出“未找到”的提示信息。

    [例7.12]写一个函数,删除链表中的指定结点。删除一个结点有两种情况:
    1. 被删除结点是第一个结点。这种情况只需使head指向第二个结点即可。即head=pb->;next。其过程如图7.5所示。
    2. 被删结点不是第一个结点,这种情况使被删结点的前一结点指向被删结点的后一结点即可。即pf->;next=pb->;next。其过程如图7.6所示。
    函数编程如下:  

    TYPE * delete(TYPE * head,int num)
    {
       TYPE *pf,*pb;
       if(head==NULL) /*如为空表, 输出提示信息*/
       {
          printf("\nempty list!\n");
          goto end;
       }
       pb=head;
       while (pb->;num!=num && pb->;next!=NULL)
       /*当不是要删除的结点,而且也不是最后一个结点时,继续循环*/
       {pf=pb;pb=pb->;next;}/*pf指向当前结点,pb指向下一结点*/
       if(pb->;num==num)
       {
          if(pb==head)
             head=pb->;next;
          /*如找到被删结点,且为第一结点,则使head指向第二个结点, 否则使pf所指结点的指针指向下一结点*/
          else
            pf->;next=pb->;next;
          free(pb);
          printf("The node is deleted\n");
       }
       else
          printf("The node not been foud!\n");

       end:
       return head;
    }

      函数有两个形参,head为指向链表第一结点的指针变量,num删结点的学号。 首先判断链表是否为空,为空则不可能有被删结点。若不为空,则使pb指针指向链表的第一个结点。进入while语句后逐个查找被删结点。找到被删结点之后再看是否为第一结点,若是则使head指向第二结点(即把第一结点从链中删去),否则使被删结点的前一结点(pf所指)指向被删结点的后一结点(被删结点的指针域所指)。如若循环结束未找到要删的结点, 则输出“末找到”的提示信息。最后返回head值。

    [例7.13]写一个函数,在链表中指定位置插入一个结点。在一个链表的指定位置插入结点, 要求链表本身必须是已按某种规律排好序的。例如,在学生数据链表中, 要求学号顺序插入一个结点。设被插结点的指针为pi。 可在三种不同情况下插入。
    1. 原表是空表,只需使head指向被插结点即可。见图7.7(a)
    2. 被插结点值最小,应插入第一结点之前。这种情况下使head指向被插结点,被插结点的指针域指向原来的第一结点则可。即:pi->;next=pb;
    head=pi; 见图7.7(b)
    3. 在其它位置插入,见图7.7(c)。这种情况下,使插入位置的前一结点的指针域指向被插结点,使被插结点的指针域指向插入位置的后一结点。即为:pi->;next=pb;pf->;next=pi;
    4. 在表末插入,见图7.7(d)。这种情况下使原表末结点指针域指向被插结点,被插结点指针域置为NULL。即:

    pb->;next=pi;
    pi->;next=NULL; TYPE * insert(TYPE * head,TYPE *pi)
    {
       TYPE *pf,*pb;
       pb=head;
       if(head==NULL) /*空表插入*/
       {
          head=pi;
          pi->;next=NULL;
       }
       else
       {
          while((pi->;num>;pb->;num)&&(pb->;next!=NULL))
          {
             pf=pb;
             pb=pb->;next;
          }/*找插入位置*/
          if(pi->;num<=pb->;num)
          {
             if(head==pb)
                head=pi;/*在第一结点之前插入*/
             else
                pf->;next=pi;/*在其它位置插入*/
             pi->;next=pb;
          }
          else
          {
             pb->;next=pi;
             pi->;next=NULL;
          } /*在表末插入*/
       }
       return head;
    }

      本函数有两个形参均为指针变量,head指向链表,pi 指向被插结点?惺紫扰卸狭幢硎欠裎?为空则使head指向被插结点。表若不空,则用while语句循环查找插入位置。找到之后再判断是否在第一结点之前插入,若是则使head 指向被插结点被插结点指针域指向原第一结点,否则在其它位置插入, 若插入的结点大于表中所有结点,则在表末插入。本函数返回一个指针, 是链表的头指针。 当插入的位置在第一个结点之前时, 插入的新结点成为链表的第一个结点,因此head的值也有了改变, 故需要把这个指针返回主调函数。
    [例7.14]将以上建立链表,删除结点,插入结点的函数组织在一起,再建一个输出全部结点的函数,然后用main函数调用它们。

    #define NULL 0
    #define TYPE struct stu
    #define LEN sizeof(struct stu)
    struct stu
    {
       int num;
       int age;
       struct stu *next;
    };
    TYPE * creat(int n)
    {
       struct stu *head,*pf,*pb;
       int i;
       for(i=0;i<n;i++)
       {
          pb=(TYPE *)malloc(LEN);
          printf("input Number and Age\n");
          scanf("%d%d",&pb->;num,&pb->;age);
          if(i==0)
          pf=head=pb;
          else pf->;next=pb;
          pb->;next=NULL;
          pf=pb;
       }
       return(head);
    }  

    TYPE * delete(TYPE * head,int num)
    {
       TYPE *pf,*pb;
       if(head==NULL)
       {
          printf("\nempty list!\n");
          goto end;
       }
       pb=head;
       while (pb->;num!=num && pb->;next!=NULL)
       {pf=pb;pb=pb->;next;}
       if(pb->;num==num)
       {
          if(pb==head) head=pb->;next;
          else pf->;next=pb->;next;
          printf("The node is deleted\n");
       }
       else
          free(pb);
       printf("The node not been found!\n");
       
       end:
       return head;
    }

    TYPE * insert(TYPE * head,TYPE * pi)
    {
       TYPE *pb ,*pf;
       pb=head;
       if(head==NULL)
       {
          head=pi;
          pi->;next=NULL;
       }
       else
       {
          while((pi->;num>;pb->;num)&&(pb->;next!=NULL))
          { pf=pb;
          pb=pb->;next; }
          if(pi->;num<=pb->;num)
          {
             if(head==pb) head=pi;
             else pf->;next=pi;
             pi->;next=pb;
          }
          else
          {
             pb->;next=pi;
             pi->;next=NULL;
          }
       }
       return head;
    }

    void print(TYPE * head)
    {
       printf("Number\t\tAge\n");
       while(head!=NULL)
       {
          printf("%d\t\t%d\n",head->;num,head->;age);
          head=head->;next;
       }
    }
    main()
    {
       TYPE * head,*pnum;
       int n,num;
       printf("input number of node: ");
       scanf("%d",&n);
       head=creat(n);
       print(head);
       printf("Input the deleted number: ");
       scanf("%d",&num);
       head=delete(head,num);
       print(head);
       printf("Input the inserted number and age: ");
       pnum=(TYPE *)malloc(LEN);
       scanf("%d%d",&pnum->;num,&pnum->;age);
       head=insert(head,pnum);
       print(head);
    }

      本例中,print函数用于输出链表中各个结点数据域值?男尾蝖ead的初值指向链表第一个结点。在while语句中,输出结点值后,head值被改变,指向下一结点。若保留头指针head, 则应另设一个指针变量,把head值赋予它,再用它来替代head。在main函数中,n为建立结点的数目, num为待删结点的数据域值;head为指向链表的头指针,pnum为指向待插结点的指针。 main函数中各行的意义是:
    第六行输入所建链表的结点数;
    第七行调creat函数建立链表并把头指针返回给head;
    第八行调print函数输出链表;
    第十行输入待删结点的学号;
    第十一行调delete函数删除一个结点;
    第十二行调print函数输出链表;
    第十四行调malloc函数分配一个结点的内存空间, 并把其地址赋予pnum;
    第十五行输入待插入结点的数据域值;
    第十六行调insert函数插入pnum所指的结点;
    第十七行再次调print函数输出链表。

      从运行结果看,首先建立起3个结点的链表,并输出其值;再删103号结点,只剩下105,108号结点;又输入106号结点数据, 插入后链表中的结点为105,106,108。联合“联合”也是一种构造类型的数据结构。 在一个“联合”内可以定义多种不同的数据类型, 一个被说明为该“联合”类型的变量中,允许装入该“联合”所定义的任何一种数据。 这在前面的各种数据类型中都是办不到的。例如, 定义为整型的变量只能装入整型数据,定义为实型的变量只能赋予实型数据。

      在实际问题中有很多这样的例子。 例如在学校的教师和学生中填写以下表格: 姓 名 年 龄 职 业 单位 “职业”一项可分为“教师”和“学生”两类。 对“单位”一项学生应填入班级编号,教师应填入某系某教研室。 班级可用整型量表示,教研室只能用字符类型。 要求把这两种类型不同的数据都填入“单位”这个变量中, 就必须把“单位”定义为包含整型和字符型数组这两种类型的“联合”。  

      “联合”与“结构”有一些相似之处。但两者有本质上的不同。在结构中各成员有各自的内存空间, 一个结构变量的总长度是各成员长度之和。而在“联合”中,各成员共享一段内存空间, 一个联合变量的长度等于各成员中最长的长度。应该说明的是, 这里所谓的共享不是指把多个成员同时装入一个联合变量内, 而是指该联合变量可被赋予任一成员值,但每次只能赋一种值, 赋入新值则冲去旧值。如前面介绍的“单位”变量, 如定义为一个可装入“班级”或“教研室”的联合后,就允许赋予整型值(班级)或字符串(教研室)。要么赋予整型值,要么赋予字符串,不能把两者同时赋予它。联合类型的定义和联合变量的说明一个联合类型必须经过定义之后, 才能把变量说明为该联合类型。

    一、联合的定义

    定义一个联合类型的一般形式为:  
    union 联合名  
    {  
    成员表  
    };
    成员表中含有若干成员,成员的一般形式为: 类型说明符 成员名 成员名的命名应符合标识符的规定。
    例如:  
    union perdata
    {
       int class;
       char office[10];
    };
      定义了一个名为perdata的联合类型,它含有两个成员,一个为整型,成员名为class;另一个为字符数组,数组名为office。联合定义之后,即可进行联合变量说明,被说明为perdata类型的变量,可以存放整型量class或存放字符数组office。

    二、联合变量的说明

      联合变量的说明和结构变量的说明方式相同, 也有三种形式。即先定义,再说明;定义同时说明和直接说明。以perdata类型为例,说明如下:  
    union perdata
    {
       int class;
       char officae[10];
    };
    union perdata a,b; /*说明a,b为perdata类型*/
    或者可同时说明为:  
    union perdata
    {  
    int class;
    char office[10];  
    }a,b;
    或直接说明为:  
    union
    {  
    int class;
    char office[10];  
    }a,b  
    经说明后的a,b变量均为perdata类型。 它们的内存分配示意图如图7—8所示。a,b变量的长度应等于 perdata 的成员中最长的长度, 即等于
    office数组的长度,共10个字节。从图中可见,a,b变量如赋予整型值时,只使用了2个字节,而赋予字符数组时,可用10个字节。

    联合变量的赋值和使用

      对联合变量的赋值,使用都只能是对变量的成员进行。 联合变量的成员表示为: 联合变量名.成员名 例如,a被说明为perdata类型的变量之后,可使用 a.class a.office 不允许只用联合变量名作赋值或其它操作。 也不允许对联合变量作初始化赋值,赋值只能在程序中进行?挂偾康魉得鞯氖?一个联合变量, 每次只能赋予一个成员值?痪浠八?一个联合变量的值就是联合变员的某一个成员值。
    [例7.15]设有一个教师与学生通用的表格,教师数据有姓名,年龄,职业,教研室四项。学生有姓名,年龄,职业,班级四项。
    编程输入人员数据, 再以表格输出。

    main()
    {
       struct
       {
          char name[10];
          int age;
          char job;
          union
          {
             int class;
             char office[10];
          } depa;
       }body[2];
       
       int n,i;
       for(i=0;i<2;i++)
       {
          printf("input name,age,job and department\n");
          scanf("%s %d %c",body.name,&body.age,&body.job);
          if(body.job=='s')
             scanf("%d",&body.depa.class);
          else
             scanf("%s",body.depa.office);
       }
       printf("name\tage job class/office\n");
       for(i=0;i<2;i++)
       {
       if(body.job=='s')
          printf("%s\t%3d %3c %d\n",body.name,body.age ,body.job,body.depa.class);
       else
          printf("%s\t%3d %3c %s\n",body.name,body.age, body.job,body.depa.office);
       }
    }

      本例程序用一个结构数组body来存放人员数据, 该结构共有四个成员。其中成员项depa是一个联合类型, 这个联合又由两个成员组成,一个为整型量class,一个为字符数组office。在程序的第一个for语句中,输入人员的各项数据,先输入结构的前三个成员name,age和job,然后判别job成员项,如为"s"则对联合depa·class输入(对学生赋班级编号)否则对depa·office输入(对教师赋教研组名)。

      在用scanf语句输入时要注意,凡为数组类型的成员,无论是结构成员还是联合成员,在该项前不能再加"&"运算符。如程序第18行中
    body.name是一个数组类型,第22行中的body.depa.office也是数组类型,因此在这两项之间不能加"&"运算符。程序中的第二个for语句用于输出各成员项的值:

    本章小结

    1. 结构和联合是两种构造类型数据,是用户定义新数据类型的重要手段。结构和联合有很多的相似之处,它们都由成员组成。成员可以具有不同的数据类型。成员的表示方法相同。都可用三种方式作变量说明。

    2. 在结构中,各成员都占有自己的内存空间,它们是同时存在的。一个结构变量的总长度等于所有成员长度之和。在联合中,所有成员不能同时占用它的内存空间,它们不能同时存在。联合变量的长度等于最长的成员的长度。

    3. “.”是成员运算符,可用它表示成员项,成员还可用“->;”运算符来表示。

    4. 结构变量可以作为函数参数,函数也可返回指向结构的指针变量。而联合变量不能作为函数参数,函数也不能返回指向联合的指针变量。但可以使用指向联合变量的指针,也可使用联合数组。

    5. 结构定义允许嵌套,结构中也可用联合作为成员,形成结构和联合的嵌套。

    6. 链表是一种重要的数据结构,它便于实现动态的存储分配。本章介绍是单向链表,还可组成双向链表,循环链表等。

    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2015-8-7 21:35
  • 签到天数: 340 天

    连续签到: 1 天

    [LV.8]以坛为家I

    发表于 2013-1-22 09:27:45 | 显示全部楼层
    沙发我的……
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2015-8-7 21:35
  • 签到天数: 340 天

    连续签到: 1 天

    [LV.8]以坛为家I

    发表于 2013-1-22 09:28:29 | 显示全部楼层
    郁闷……楼主怎么把沙发也占了……
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    郁闷
    昨天 09:47
  • 签到天数: 1623 天

    连续签到: 1 天

    [LV.Master]伴坛终老

    发表于 2013-1-22 09:29:03 | 显示全部楼层
    【by Danny Kalev
    [color=#cc00 !important]C/C++基础知识:typedef用法小结
    第一、四个用途
    用途一:
    定义一种类型的别名,而不只是简单的宏替换。可以用作同时声明指针型的多个对象。比如:
    char* pa, pb; // 这多数不符合我们的意图,它只声明了一个指向字符变量的指针,
    // 和一个字符变量;
    以下则可行:
    typedef char* PCHAR; // 一般用大写
    PCHAR pa, pb; // 可行,同时声明了两个指向字符变量的指针
    虽然:
    char *pa, *pb;
    也可行,但相对来说没有用typedef的形式直观,尤其在需要大量指针的地方,typedef的方式更省事。
    用途二:
    用在旧的C的代码中(具体多旧没有查),帮助struct。以前的代码中,声明struct新对象时,必须要带上struct,即形式为: struct 结构名 对象名,如:
    struct tagPOINT1
    {
    int x;
    int y;
    };
    struct tagPOINT1 p1;
    而在C++中,则可以直接写:结构名 对象名,即:
    tagPOINT1 p1;
    估计某人觉得经常多写一个struct太麻烦了,于是就发明了:
    typedef struct tagPOINT
    {
    int x;
    int y;
    }POINT;
    POINT p1; // 这样就比原来的方式少写了一个struct,比较省事,尤其在大量使用的时候
    或许,在C++中,typedef的这种用途二不是很大,但是理解了它,对掌握以前的旧代码还是有帮助的,毕竟我们在项目中有可能会遇到较早些年代遗留下来的代码。
    用途三:
    用typedef来定义与平台无关的类型。
    比如定义一个叫 REAL 的浮点类型,在目标平台一上,让它表示最高精度的类型为:
    typedef long double REAL;
    在不支持 long double 的平台二上,改为:
    typedef double REAL;
    在连 double 都不支持的平台三上,改为:
    typedef float REAL;
    也就是说,当跨平台时,只要改下 typedef 本身就行,不用对其他源码做任何修改。
    标准库就广泛使用了这个技巧,比如size_t。
    另外,因为typedef是定义了一种类型的新别名,不是简单的字符串替换,所以它比宏来得稳健(虽然用宏有时也可以完成以上的用途)。
    用途四:
    为复杂的声明定义一个新的简单的别名。方法是:在原来的声明里逐步用别名替换一部分复杂声明,如此循环,把带变量名的部分留到最后替换,得到的就是原声明的最简化版。举例:
    1. 原声明:int *(*a[5])(int, char*);
    变量名为a,直接用一个新别名pFun替换a就可以了:
    typedef int *(*pFun)(int, char*);
    原声明的最简化版:
    pFun a[5];
    2. 原声明:void (*b[10]) (void (*)());
    变量名为b,先替换右边部分括号里的,pFunParam为别名一:
    typedef void (*pFunParam)();
    再替换左边的变量b,pFunx为别名二:
    typedef void (*pFunx)(pFunParam);
    原声明的最简化版:
    pFunx b[10];
    3. 原声明:doube(*)() (*e)[9];
    变量名为e,先替换左边部分,pFuny为别名一:
    typedef double(*pFuny)();
    再替换右边的变量e,pFunParamy为别名二
    typedef pFuny (*pFunParamy)[9];
    原声明的最简化版:
    pFunParamy e;
    理解复杂声明可用的“右左法则”:
    从变量名看起,先往右,再往左,碰到一个圆括号就调转阅读的方向;括号内分析完就跳出括号,还是按先右后左的顺序,如此循环,直到整个声明分析完。举例:
    int (*func)(int *p);
    首先找到变量名func,外面有一对圆括号,而且左边是一个*号,这说明func是一个指针;然后跳出这个圆括号,先看右边,又遇到圆括号,这说明(*func)是一个函数,所以func是一个指向这类函数的指针,即函数指针,这类函数具有int*类型的形参,返回值类型是int。
    int (*func[5])(int *);
    func右边是一个[]运算符,说明func是具有5个元素的数组;func的左边有一个*,说明func的元素是指针(注意这里的*不是修饰func,而是修饰func[5]的,原因是[]运算符优先级比*高,func先跟[]结合)。跳出这个括号,看右边,又遇到圆括号,说明func数组的元素是函数类型的指针,它指向的函数具有int*类型的形参,返回值类型为int。
    也可以记住2个模式:
    type (*)(....)函数指针
    type (*)[]数组指针
    第二、两大陷阱
    陷阱一:
    记住,typedef是定义了一种类型的新别名,不同于宏,它不是简单的字符串替换。比如:
    先定义:
    typedef char* PSTR;
    然后:
    int mystrcmp(const PSTR, const PSTR);
    const PSTR实际上相当于const char*吗?不是的,它实际上相当于char* const。
    原因在于const给予了整个指针本身以常量性,也就是形成了常量指针char* const。
    简单来说,记住当const和typedef一起出现时,typedef不会是简单的字符串替换就行。
    陷阱二:
    typedef在语法上是一个存储类的关键字(如auto、extern、mutable、static、register等一样),虽然它并不真正影响对象的存储特性,如:
    typedef static int INT2; //不可行
    编译将失败,会提示“指定了一个以上的存储类”。
    以上资料出自:http://blog.sina.com.cn/s/blog_4826f7970100074k.html 作者:赤龙
    第三、typedef 与 #define的区别
    案例一:
    通常讲,typedef要比#define要好,特别是在有指针的场合。请看例子:
    typedef char *pStr1;
    #define pStr2 char *;
    pStr1 s1, s2;
    pStr2 s3, s4;
    在上述的变量定义中,s1、s2、s3都被定义为char *,而s4则定义成了char,不是我们所预期的指针变量,根本原因就在于#define只是简单的字符串替换而typedef则是为一个类型起新名字。
    案例二:
    下面的代码中编译器会报一个错误,你知道是哪个语句错了吗?
    typedef char * pStr;
    char string[4] = "abc";
    const char *p1 = string;
    const pStr p2 = string;
    p1++;
    p2++;
    是p2++出错了。这个问题再一次提醒我们:typedef和#define不同,它不是简单的文本替换。上述代码中const pStr p2并不等于const char * p2。const pStr p2和const long x本质上没有区别,都是对变量进行只读限制,只不过此处变量p2的数据类型是我们自己定义的而不是系统固有类型而已。因此,const pStr p2的含义是:限定数据类型为char *的变量p2为只读,因此p2++错误。
    第四部分资料:使用 typedef 抑制劣质代码
    作者:Danny Kalev
    编译:MTT 工作室
    原文出处:Using typedef to Curb Miscreant Code
    摘要:Typedef 声明有助于创建平台无关类型,甚至能隐藏复杂和难以理解的语法。不管怎样,使用 typedef 能为代码带来意想不到的好处,通过本文你可以学习用 typedef 避免缺欠,从而使代码更健壮。
    typedef 声明,简称 typedef,为现有类型创建一个新的名字。比如人们常常使用 typedef 来编写更美观和可读的代码。所谓美观,意指 typedef 能隐藏笨拙的语法构造以及平台相关的数据类型,从而增强可移植性和以及未来的可维护性。本文下面将竭尽全力来揭示 typedef 强大功能以及如何避免一些常见的陷阱。
    Q:如何创建平台无关的数据类型,隐藏笨拙且难以理解的语法?
    A: 使用 typedefs 为现有类型创建同义字。
    定义易于记忆的类型名
      typedef 使用最多的地方是创建易于记忆的类型名,用它来归档程序员的意图。类型出现在所声明的变量名字中,位于 ""typedef"" 关键字右边。例如:
    typedef int size;
    此声明定义了一个 int 的同义字,名字为 size。注意 typedef 并不创建新的类型。它仅仅为现有类型添加一个同义字。你可以在任何需要 int 的上下文中使用 size:
    void measure(size * psz); size array[4];size len = file.getlength();std::vector <size> vs;
    typedef 还可以掩饰符合类型,如指针和数组。例如,你不用象下面这样重复定义有 81 个字符元素的数组:
    char line[81];char text[81];
    定义一个 typedef,每当要用到相同类型和大小的数组时,可以这样:
    typedef char Line[81]; Line text, secondline;getline(text);
    同样,可以象下面这样隐藏指针语法:
    typedef char * pstr;int mystrcmp(pstr, pstr);
    这里将带我们到达第一个 typedef 陷阱。标准函数 strcmp()有两个‘const char *’类型的参数。因此,它可能会误导人们象下面这样声明 mystrcmp():
    int mystrcmp(const pstr, const pstr);
    这是错误的,按照顺序,‘const pstr’被解释为‘char * const’(一个指向 char 的常量指针),而不是‘const char *’(指向常量 char 的指针)。这个问题很容易解决:
    typedef const char * cpstr; int mystrcmp(cpstr, cpstr); // 现在是正确的
    记住:不管什么时候,只要为指针声明 typedef,那么都要在最终的 typedef 名称中加一个 const,以使得该指针本身是常量,而不是对象。
    代码简化
      上面讨论的 typedef 行为有点像 #define 宏,用其实际类型替代同义字。不同点是 typedef 在编译时被解释,因此让编译器来应付超越预处理器能力的文本替换。例如:
    typedef int (*PF) (const char *, const char *);
    这个声明引入了 PF 类型作为函数指针的同义字,该函数有两个 const char * 类型的参数以及一个 int 类型的返回值。如果要使用下列形式的函数声明,那么上述这个 typedef 是不可或缺的:
    PF Register(PF pf);
    Register() 的参数是一个 PF 类型的回调函数,返回某个函数的地址,其署名与先前注册的名字相同。做一次深呼吸。下面我展示一下如果不用 typedef,我们是如何实现这个声明的:
    int (*Register (int (*pf)(const char *, const char *))) (const char *, const char *);
    很少有程序员理解它是什么意思,更不用说这种费解的代码所带来的出错风险了。显然,这里使用 typedef 不是一种特权,而是一种必需。持怀疑态度的人可能会问:“OK,有人还会写这样的代码吗?”,快速浏览一下揭示signal()函数的头文件 <csinal>,一个有同样接口的函数。
    typedef 和存储类关键字(storage class specifier)
      这种说法是不是有点令人惊讶,typedef 就像 auto,extern,mutable,static,和 register 一样,是一个存储类关键字。这并是说 typedef 会真正影响对象的存储特性;它只是说在语句构成上,typedef 声明看起来象 static,extern 等类型的变量声明。下面将带到第二个陷阱:
    typedef register int FAST_COUNTER; // 错误
    编译通不过。问题出在你不能在声明中有多个存储类关键字。因为符号 typedef 已经占据了存储类关键字的位置,在 typedef 声明中不能用 register(或任何其它存储类关键字)。
    促进跨平台开发
      typedef 有另外一个重要的用途,那就是定义机器无关的类型,例如,你可以定义一个叫 REAL 的浮点类型,在目标机器上它可以i获得最高的精度:
    typedef long double REAL;
    在不支持 long double 的机器上,该 typedef 看起来会是下面这样:
    typedef double REAL;
    并且,在连 double 都不支持的机器上,该 typedef 看起来会是这样: 、
    typedef float REAL;
    你不用对源代码做任何修改,便可以在每一种平台上编译这个使用 REAL 类型的应用程序。唯一要改的是 typedef 本身。在大多数情况下,甚至这个微小的变动完全都可以通过奇妙的条件编译来自动实现。不是吗? 标准库广泛地使用 typedef 来创建这样的平台无关类型:size_t,ptrdiff 和 fpos_t 就是其中的例子。此外,象 std::string 和 std:fstream 这样的 typedef 还隐藏了长长的,难以理解的模板特化语法,例如:basic_string<char, char_traits<char>,allocator<char>> 和 basic_ofstream<char, char_traits<char>>。
    作者简介
      Danny Kalev 是一名通过认证的系统分析师,专攻 C++ 和形式语言理论的软件工程师。1997 年到 2000 年期间,他是 C++ 标准委员会成员。最近他以优异成绩完成了他在普通语言学研究方面的硕士论文。 业余时间他喜欢听古典音乐,阅读维多利亚时期的文学作品,研究 Hittite、Basque 和 Irish Gaelic 这样的自然语言。其它兴趣包括考古和地理。Danny 时常到一些 C++ 论坛并定期为不同的 C++ 网站和杂志撰写文章。他还在教育机构讲授程序设计语言和应用语言课程。

    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    手机版|小黑屋|与非网

    GMT+8, 2024-4-25 05:15 , Processed in 0.154931 second(s), 22 queries , MemCache On.

    ICP经营许可证 苏B2-20140176  苏ICP备14012660号-2   苏州灵动帧格网络科技有限公司 版权所有.

    苏公网安备 32059002001037号

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.