C语言之结构体的初识

1.结构体的定义

 C语言允许用户自己指定这样一种数据结构,它由不同类型的数据组合成一个整体,以便引用,这些组合在一个整体中的数据是互相联系的,这样的数据结构称为结构体,它相当于其它高级语言中记录。

2.结构体的使用习惯

2.1 一般不会把结构体放在主函数中,一般可以放在.h文件中,这样就可以供多个.c文件调用
2.2 typedef 关键字可以用来给数据类型起别名,它和#define完全不同
2.3 结构体变量做参数的时候应该使用一个指针变量来替换,这样可以节省空间和时间。
如果这个参数只是输入参数则指针声明前面加上关键字const
2.4 结构体变量可以作为参数返回
2.5 变量的地址必须是变量大小的整数倍,这叫做数据对齐。double变量的地址只需要是4的整数倍就可以了
数据对齐会导致结构体中不同变量之间有空隙,结构体变量的大小并不是其中所有子变量大小之和
2.6 整个结构体变量的大小必须是其中最大变量大小的整数倍,double变量大小按4个计算。为了保证这一点,
有时需要在结构体变量后面加入一些浪费的字节,这叫做补齐。
2.7 声明结构体的时候可以使用位域限制某个子变量所占的二进制位数,所有整数类型变量都可以使用位域。
所有使用位域声明的子变量没有地址!!!

  (位结构是一种特殊的结构, 在需按位访问一个字节或字的多个位时, 位结构比按位运算符更加方便。 

  位结构定义的一般形式为: 

     struct位结构名{ 

          数据类型 变量名: 整型常数; 

          数据类型 变量名: 整型常数; 

     } 位结构变量; 

    其中: 数据类型必须是int(unsigned或signed)。 整型常数必须是非负的整 数, 范围是0~15, 表示二进制位的个数, 即表示有多少位。 

    变量名是选择项, 可以不命名, 这样规定是为了排列需要。

 注意: 

    1. 位结构中的成员可以定义为unsigned, 也可定义为signed,  但当成员长 

度为1时, 会被认为是unsigned类型。因为单个位不可能具有符号。 

    2. 位结构中的成员不能使用数组和指针, 但位结构变量可以是数组和指针, 

如果是指针, 其成员访问方式同结构指针。 

    3. 位结构总长度(位数), 是各个位成员定义的位数之和,  可以超过两个字 

节。 

    4. 位结构成员可以与其它结构成员一起使用。

例如: 

     struct info{ 

          char name[8]; 

          int age; 

          struct addr address; 

          float pay; 

          unsigned state: 1; 

          unsigned pay: 1; 

          }workers;’ 

    上例的结构定义了关于一个工从的信息。其中有两个位结构成员, 每个位结 

构成员只有一位, 因此只占一个字节但保存了两个信息, 该字节中第一位表示工 

人的状态, 第二位表示工资是否已发放。由此可见使用位结构可以节省存贮空间。  ——转载于http://blog.csdn.net/hanchaoman/article/details/4135237)

3.声明一个结构体形式:

3.1 定义形式 1

struct 结构体名

{类型名 成员名};

结构体名,用作结构体类型的标志,它又称结构体标记,大括号内是该结构体中的各个成员,由它们组成一个结构体,对各成员都应进行类型声明;各个成员又被称为成员列表 或称为 域表,第一个成员也称为结构体中的一个域。成员名定名规则写变量名同。

3.2 直接定义结构类型变量

其一般形式为

struct  

{成员表列 }

变量名表列;

即不出现结构体名。

3.3在声明类型的同时定义变量

1. struct 结构体名{

成员表列}变量名表列;

2.  使用typedef关键字

typedef struct (结构体名)

{类型名 成员名;

}结构体变量名;

如:typedef struct student{

char name[20];

struct date birthday;

int age;

}su(,su2,su3,........);这个例子是定义了一个结构名为student的结构体变量su如果省略变量名 su, 则变成对结构体的说明。用已说明的结构名也可定义结构变量。这样定义时就变成: struct    student  su1,su2;

3.定义结构体类型变量的方法

前面只是指定了一个结构体类型,它相当于一个模型,但其中并无具体数据,系统对之也不分配实际内存单元,为了能在程序中使用结构类型的数据,应当定义结构体类型的变量,并在其中存放具体的数据,可以采取以下3种方法定义结构体类型变量。

  (1)先声明结构体类型再定义变量名

  如上面已定义了一个结构体类型 struct student,可以用它来定义变量。如:

  struct student  //结构体类型名

  su,su1.......  //结构体变量名

  定义了 su,su1, su2 为 struct student 类型的变量。

  在定义了结构体变量后,系统会为之分配内存单元。

4.定义基本数据类型与结构体类型的区别:

  应当注意,将一个变量定义为标准类型(基本数据类型)与定义为结构体类型不同之处在于后者不仅要求指定变量为结构体类型,而且要求指定为某一特定的结构体类型(例如 struct student 类型),因为可以定义出许多种具体的结构体类型。而在定义变量为整形时,只需指定为 int 型即可。

3.4关于结构体类型,有几点要说明:

  a. 类型与变量是不同的概念,不是混同,只能对变量赋值,存取或运算,而不能对一个类型赋值,存取或运算。在编译时,对类型是不分配空间的,只对变量分配空间。

  b. 对结构体中的成员(即 域)可以单元使用,它的作用与地位相当于普通变量,

  c. 成员也可以是一个结构体变量。

     d. 成员名可以与程序中的变量名相同,二者不代表同一对象。

4 .结构体变量的引用

  (1)不能将一个结构体变量作为一个整体进行输入和输出。只能对结构体变量中的各个成员分别进行输入输出。引用结构体变量中的成员的方式为::::结构体变量名.成员名

例如 su.age 表示 su 变量中的 age 成员,即 su 的 age 项,可以对变量的成员赋值。例如:su.age = 10;

"." 是成员(分量)运算符,它在所有的运算符中优先级最高,因此可以把 su.age 作为一个整体来看待。上面的赋值语句作用是将整数 10赋给 su 变量中的成员 age

  (2)如果成员本身又属一个结构体类型,则要用若干个成员运算符一级一级地找到最低一级的成员只能对最低的成员进行赋值或存取以及运算。

例如:结构体变量 su 可以这样访问各成员:

su.age

su.birthday.month

注意,不能用 su.birthday 来访问 su 变量中的成员 birthday,因为 birthday 本身是一个结构体变量。

  (3)对结构体变量的成员可以像普通变量一样进行各种运算(根据其类型决定可以进行的运算)。

su2.score = su1.score;

sum = su1.score + su2.score;

su.age ++;

++ su.age;

由于 "." 运算符的优先级最高,因此 su.age ++ 是对 su.age 进行自加运算。而不是先对 age 进行自加运算。

  (4)可以引用结构体变量成员的地址。也可以引用结构体变量的地址。如:

  scanf("%d", &su.age;// 输入 su.age 的值

  printf("%p", &su);// 输出 su 的首地址

但不能用以下语句整体读入结构体变量如

  scanf("%d,%s,%c,%d,%f,%s", &su);

结构体变量的地址主要用于作函数参数,传递结构体的地址

5 结构体变量的初始化

  和其它类型变量一样,对结构体变量可以在定义时指定初始值。

如:

#include <stdio.h>

typedef  struct student

{

  long int num;

  char name[20];

  char sex;

  char addr[30];
}a = {89031, "Li Lin", 'M', "123 Beijing Road"};

void main()
{

  printf("NO. : %d\nname: %s\nsex: %c\naddress: %s\n", a.num, a.name, a.sex, a.addr);
}

6,,结构体数组

  一个结构体变量中可以存放一组数据(如一个学生的学号,姓名,成绩等数据)。如果有10个学生的数据需要参加运算,显然应该用数组,这就是结构体数组。结构体数组与以前介绍过的数据值型数组不同之处在于每个数组元素都一个结构体类型的数据,它们分别包括各个成员(分量)项。

6.1 定义结构体数组

  和定义结构体变量的方法相仿,只需说明其为数组即可。

  typedef  struct student

  {

    int num;

    char name[20];

    char sex;

    int age;

    float score;

    char addr[30];

  };

  struct student stu[3];

  以上定义了一个数组 stu,其元素为 struct student 类型数据,数组有 3 个元素。也可以直接定义一个结构体数组。如:

  typedef struct student

  {

    int num;

    ....

  }stu[3];

  typedef struct

  {

    int num;

    ...

  }stu[3];

5.2 结构体数组的初始化

  与其它类型数组一样,对结构体数组可以初始化  如

  struct student

  {

    int mum;

    char name[20];

    char sex;

    int age;

    float score;

    char addr[30];

  }stu[3] = {{10101,"Li Lin", 'M', 18, 87.5, "103 Beijing Road"},

        {10101,"Li Lin", 'M', 18, 87.5, "103 Beijing Road"},

        {10101,"Li Lin", 'M', 18, 87.5, "103 Beijing Road"}};

  定义数组 stu 时,元素个数可以不指定,即写成以下形式:

  stu[] = {{...},{...},{...}};

编译时,系统会根据给出初值的结构体常量的个数来确定数组元素的个数。

  当然,数组的初始化也可以用以下形式:

  typedef struct student

  {

    int num;

    ...

  };

  struct student stu[] = {{...},{...},{...}};

即先声明结构体类型,然后定义数组为该结构体类型,在定义数组时初始化。

  从以上可以看到,结构体数组初始化的一般形式是在定义数组的后面加上:

6 指向结构体类型数据的指针

  一个结构体变量的指针就是该变量所占据的内存段的起始地址可以设一个指针变量用来指向一个结构体变量此时该指针变量的值是结构体变量的起始地址。指针变量也可以用来指向结构体数组中的元素。

6.1 指向结构体变量的指针

  指向结构体变量的指针的应用:

typedef struct student

{

  long num;

  char name[20];

  char sex;

  float score;
};

void main()

{

  struct student stu_1;

  struct student *p;

  p = &stu_1;

  stu_1.num = 89101;

  strcpy(stu_1.name, "Li Lin");

  stu_1.sex = 'M';

  stu_1.score = 89.5;

  printf("NO. :%ld\nname: %s\nsex:%c\nscore:%f\n", stu_1.num, stu_1.name, stu_1.sex, stu_1.score);

  printf("NO. :%ld\nname: %s\nsex:%c\nscore:%f\n", (*p).num, (*p).name, (*p).sex, (*p).score);

  system("pause");
}

  在主函数中声明了 struct student 类型,然后定义了一个 struct student 类型的变量stu_1 同时又定义一个指针变量 p ,它指向一个 struct student 类型的数据在函数的执行部分将结构体变量 stu_1 的起始地址赋给指针变量 p 也就是使 p 指向 stu_1 然后对 stu_1 的各成员赋值,第二个 printf 函数也是用来输出 stu_1 各成员的值,但使用的是 (*p).num 这样的形式, (*p) 表示 p 指向的结构体变量,(*p).num 是 p 指向的结构体变量中的成员 num 。注意 *p 两侧的括弧不可省略,因为成员运算符 '.' 优先于 '*' 运算符,*p.num 就等价于 *(p.num),运行结果可以看到两个 printf 输出的结果相同。

  在C语言中,为了使用方便和使之直观,可以把 (*p).num 改用 p->num 来代替,它表示 *p 所指向的结构体变量中的 num 成员,同样,(*p).name 等价于 p->name。

也就是说以下三种形式等价:

  a. 结构体变量.成员名

  b. (*p).成员名

  c. p->成员名

  上面的最后一个 printf 函数输了项可以改写为

  printf("NO. :%ld\nname: %s\nsex:%c\nscore:%f\n",p->num, p->name, p->sex, p->score);

  其中 -> 称为指向运算符。

  分析以下几种运算符

  p -> n 得到 p 指向的结构体变量中的成员 n 的值

  p -> n ++ 得到 p 指向的结构体变量中的成员 n 的值,用完值后使它加1

  ++p -> n  得到 p 指向的结构体变量中的成员 n 的值使之加 1 (先加)

6.2 指向结构体数组的指针

  以前介绍过可以使用指向数组或数组元素的指针和指针变量,同样,对结构体数组及其元素也可以用指针变量来指向

  指向结构体数组的指针的应用

struct student

{

  int num;

  char name[20];

  char sex;

  int age;

};

struct student stu[3] = {{10101, "Li Lin", 'M', 18},

             {10102, "Zhang Fun", 'M', 19},

             {10103, "Wang Min", 'F', 20}};

void main()

{

  struct student *p ;

  printf("No.  name    sex    age\n");

  for(p = stu; p<stu+3;p++)

    printf("%5d %-20s %2c %4d\n", p->num, p->name, p->sex, p->age);

  system("pause");
}

运行结果如下:

No.  name    sex    age
10101 Li Lin                M     18
10102 Zhang Fun        M     19
10103 Wang Min          F      20

注意以下两点:

(1)如果 p 的初值为 stu,即指向第一个元素,则 p + 1 后指向下一个元素的起始地址。例如:

  (++p) -> num 先使 p 自加 1 ,然后得到它指向的元素中的 num 成员的值(即10102)。

  (p++) ->num 先得到 p->num 的值(即10101),然后使 p 自加 1 ,指向 stu[1]。

  注意以上二者的不同

(2)程序已定义了指针 p 为指向 struct student 类型数据的变量,它只能指向一个 struct student 型的数据(p 的值是 stu 数组的一个元素的起始地址)而不能指向 stu 数组元素中的某一成员,(即 p 的地址不能是成员地址)。例如,

下面是不对的:p = &stu[1].name

编译时将出错。千万不要认为反正 p 是存放地址的,可以将任何地址赋给它。如果地址类型不相同,可以用强制类型转换。例如:

  p = (struct student *)&stu[1].name;

此时,在 p 中存放 stu[1] 元素的 name 成员的起始地址。

6.3 用结构体变量和指向结构体的指针作函数参数

  将一个结构体变量的值传递给另一个函数,有3个方法:

  (1)用结构体变量的成员作参数,例如:用 stu[1].num 或 stu[2].name 作函数实参,将实参值传给形参。用法和用普通变量作实参是一样的,属于 值传递 方式。应当注意实参与形参的类型保持一致。

  (2)用结构体变量作参数。老版本的C系统不允许用结构体变量作实参,ANSI C取消了这一限制。但是用结构体变量作实参时,采取的是 值传递 的方式,将结构体变量所占的内存单元全部顺序传递给形参。形参也必须是同类型的结构体变量。在函数调用期间形参也要占用内存单元。这种传递方式在空间和时间上开销较大,如果结构体的规模很大时,开销是很可观的,此外由于采用值传递方式,如果在执行被调用函数期间改变了形参(也是结构体变量)的值,该值不能返回主调函数,这往往造成使用上的不便。因此一般较少用这种方法。

  (3)用指向结构体变量(或数组)的指针作实参,将结构体变量(或数组)的地址传给形参。

结构体变量作函数参数。

#include <stdio.h>

#define FORMAT "%d\n%s\n%f\n%f\n%f\n"   //宏定义

struct student

{

  int num;

  char name[20];

  float score[3];

};

void print(struct student stu)

{

  printf(FORMAT, stu.num, stu.score[0], stu.score[1], stu.score[2]);

  printf("\n");
}

void main()

{

  struct student stu;

  stu.num = 12345;

  strcpy(stu.name, "Li Li");

  stu.score[0] = 67.5;

  stu.score[1] = 89;

  stu.score[2] = 78.6;

  printf(stu);
}

上面改用指向结构体变量的指针作实参

#include <stdio.h>

#define FORMAT "%d\n%s\n%f\n%f\n%f\n"

struct student

{

  int num;

  char name[20];

  float score[3];

}stu = {12345, "Li Li", 67.5, 89, 78.6};

void print(struct student *p)

{

  printf(FORMAT, p->num, p->name, p->score[0], p->score[1], p->score[2]);

  printf("\n");
}

void main()

{

  print(&stu);
}

7 用指针处理链表

7.1 链表概述

  链表是一种常见的重要的数据结构。它是动态地进行存储分配的一种结构

  链表有一个 头指针 变量,它存放一个地址,该地址指向一个元素,链表中每一个元素称为 结点,每个结点都应包括两个部分,一为用户需要用的实际数据,二为下一个结点的地址。可以看出,头指针 head 指向第一个元素,第一个元素又指向第二个元素,直到最后一个元素,该元素不再指向其他元素,它称为 表尾,它的地址部分放一个 NULL(表示 空地址)链表到此结束。

  可以看到链表中各元素在内存中可以不是连续存放的,要找某一元素,必须先找到上一个元素,根据它提供的下一元素地址才能找到下一个元素。如果不提供 头指针 head 则整个链表无法访问。

  可以看到。这种链表的数据结构,必须利用指针变量才能实现,即一个结点中应包含一个指针变量,用它存放下一结点的地址。

  前面介绍了结构体变量,用它作链表中的结点是最合适的,一个结构体变量包含若干成员,这些成员可以是数值类型,字符类型,数组类型,也可以是指针类型,我们用这个指针类型成员来存放下一个结点的地址。例如可以设计这样一个结构体类型:

  struct student

  {

    int num;

    float score;

    struct student *next;

  };

  其中成员 num 和 score 用来存放结点中的有用数据(用户需要用到的数据),next 是指针类型成员,它指向 struct student 类型数据(这是 next 所在结构体类型)。一个指针类型的成员既可以指向其他类型的结构体数据,也可以指向自己所在的结构体类型的数据。现在 next 是 struct student 类型中的一个成员,它又指向 struct student 类型的数据。用这种方法就可以建立链表。

  请注意:只是定义一个 struct student 类型,并未实际分配存储空间,只有定义了变量才分配内存单元。

7.2 简单链表

  下面通过一个例子来说明如何建立和输出一个简单链表

#include <stdio.h>

#include <stdlib.h>

#define NULL 0

struct student

{

  long num;

  float score;

  struct student *next;
};

void main()

{

  struct student a, b, c, *head, *p;

  a.num = 99101; a.score = 89.5;

  b.num = 99103; b.score = 90;

  c.num = 99107; c.score = 85;//对结点的 num 和 score 成员赋值

  head = &a;//将结点 a 的起始地址赋给头指针 head

  a.next = &b;//将结点 b 的起始地址赋给 a 结点的 next 成员

  b.next = &c;

  c.next = NULL;// c 结点的 next 成员不存放其他结点地址

  p = head;//使 p 指针指向 a 结点

  do

  {

    printf("%ld %5.1f\n", p->num, p->score);// 输出 p 指向的结点的数据

    p = p->next;//使 p 指向下一结点

  }while(p != NULL);//输出完 c 结点后 p 的值为 NULL

  system("pause");
}

运行结果

99101  89.5
99103  90.0
99107  85.0

7.3 处理动态链表用到的函数

  (1)malloc 函数

  void *malloc(unsigned int size);

  作用是在内存的动态存储区中分配一个长度为 size 的连接空间,没有进行初始化,函数的返回值是一个指向分配空间起始地址的指针(基类型为 void)。如果些函数未能成功地执行(例如内存空间不足)则返回空指针 NULL。

  (2)calloc 函数

  void *calloc(unsigned n, unsigned size);

  其作用是在内存的动态区存储中分配 n 个长度为 size 的连续空间,并进行初始化为0。函数返回一个指向分配空间起始地址的指针,如果分配不成功,返回 NULL。

  用 calloc 函数可以为一维数组开辟动态存储空间, n 为数组元素个数,每个元素长度为 size。  

  (3)free 函数

  void free(void *p);

  其作用是释放由 p 指向的内存区,使这部分内存区能被其它变量使用, p 是最后一次调用 calloc 或 malloc 函数时返回的值。free 函数无返回值。

  请注意:以前的C版本提供的 malloc 和 calloc 函数得到的是指向字符型数据的指针。ANSI C 提供的 malloc 和 calloc 函数规定为 void * 类型。

7.4 建立动态链表

  所谓建立动态链表是指在程序执行过程中从无到有地建立起一个键表,即一个一个地开辟结点和输入各结点数据,并建立起前后相链的关系。

#include <stdio.h>
#include <stdlib.h>

#define NULL 0
#define LEN sizeof(struct student)

struct student
{
  long num;
  float score;
  struct student *next;
};

struct student *create()
{
  struct student *p1, *p2, *head;
  int num;
  float score;
  int n = 0;

  head = NULL;

  p1 = p2 = (struct student *)malloc(LEN);

  printf("please input num and score.\n");
  scanf("%d,%f", &p1->num, &p1->score);

  while(p1->num != 0)
  {

    n ++;

    if(n == 1)

      head = p1;

    else

      p2->next = p1;

    p2 = p1;
    p1 = (struct student *)malloc(sizeof(struct student));

    printf("please input num and score.\n");

    scanf("%d,%f", &p1->num, &p1->score);
  }
  p2->next = NULL;
  return head;
}

void printlist(struct student *head)
{
  struct student *p;
  p = head;

  if(head != NULL)

  {

    do

    {

      printf("num=%d score=%f\n", p->num, p->score);
      p = p->next;

    }while(p != NULL);

  }

}

void main()
{
  struct student *head;
  head = create();
  printlist(head);
  system("pause");
}

以下是对链表的各种操作

打印链表

void printlist(struct student *head)
{
 struct student *p;
 p = head;

 if(head != NULL)
 {
  do 
  {
   printf("num=%d score=%5.2f\n", p->num, p->score);
   p = p->next;
  } while (p != NULL);
 }
/* while(p -> next != NULL)
 {
  printf("num=%d score=%f\n", p->num, p->score);
  p = p->next;
 }*/
}

删除节点

struct student *delNode(struct student *head, int num)
{
 printf("delNode.\n");
 struct student *p1, *p2;
 if(head == NULL)
 {
  printf("The List is NULL.\n");
 }
 else
 {
  p1 = head;
  while(p1->next != NULL && p1->num != num)
  {
   p2 = p1;
   p1 = p1->next;
  }
  if(p1->num == num)
  {
   if(p1 == head)
    head = p1->next;
   else
    p2->next = p1->next;
  }
  else
   printf("Can not find list num.\n");
 }
 return head;
}

更新节点

struct student *update(struct student *head, int index, int num, float score)
{
 printf("update.\n");
 struct student *p;
 if(head == NULL)
 {
  printf("The List is NULL.\n");
 }
 else
 {
  p = head;
  while(p->next != NULL && p->num != index)
  {
   p = p->next;
  }
  if(p->num == index)
  {
   p->num = num;
   p->score = score;
  }
  else
   printf("Can not find list index.\n");
 }
 return head;
}

增加节点

struct student *add(struct student *head, int index, int num, float score)
{
 printf("add.\n");
 struct student *p1, *p2, *p3;
 if(head == NULL)
 {
  printf("The List is NULL.\n");
 }
 else
 {
  p1 = p2 = head;
  while(p1->next != NULL && p1->num != index)
  {
   p1 = p1->next;
   p2 = p1;
  }
  if(p1->num == index)
  {
   p3 = (struct student *)malloc(LEN);
   p3->num = num;
   p3->score = score;

   if(p2->next == NULL)
   {
    p2->next = p3;
    p3->next = NULL;
   }
   else
   {
    p3->next = p2->next;
    p2->next = p3;   
   }
  }
  else
   printf("Can not find list index.\n");
 }
 return head;
}







  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值