基本类型:整型、字符型、实型、双精度型和空值型
派生型:指针和数组
用户构造类型
用户定义类型:对已有的类型,另外说明一个新的类型标识符。
结构体:把具有相互关系的不同类型的数据组成一个有机的整体
共用体:又称联合体,使几种不同类型的变量共用一段存储空间。
14.1 用typedef说明一种新类型名
typedef 类型名 标识符;
作用: 用“标识符”来代表已存在的“类型名”,并未产生新的数据类型,原有的类型名依然有效。
例如:typedef int INTERGER;
INTERGER m,n; 等价于 int m, n;
如:typedef char *CHARP;
CHARP p; 等价于char *p;
14.2 结构体类型
如:一条学生记录(student),包含如下数据项:姓名(name):字符串、性别(sex):字符型、出生日期(birthday):date结构体、四门课成绩(SC):一维实型数组
结构体是一种构造数据类型
用途:把不同类型的数据组合成一个整体——自定义数据类型
结构体类型的变量、数组和指针变量的定义
1.紧跟类型说明之后定义变量
struct student { char name[20]; struct date birthday; char sex; int score[4]; }std, pers[3], *pStd;
结构体变量中的各成员在内存中按说明的顺序依次存放
2.声明无名结构体时直接定义变量,即将其中的student省略掉
struct date { int year; int month; int day; }; struct student { char name[20]; struct data birthday; char sex; int score[4]; };
结构体嵌套定义
struct student { char name[20]; struct { int year; int month; int day; }birthday; char sex; int score[4]; };
3.先声明结构体类型,再使用类型定义变量
struct student
{
…………
};
struct student std, pers[3], *pStd;
4.使用typedef重新声明一个结构体类型名,然后用新类型名字定义变量
typedef struct { …… }student;/Student 是结构体的类型的一个新名字而已,如 int char 他们一样,不用再写struct student std, pers[3], *pStd;
结构体类型的变量、数组赋初值
1.结构体变量赋初值
- 按顺序在花括号里给对应的成员变量赋初值
- 赋初值必须一一对应
- 不允许跳过前面的成员变量,只给前面部分赋初值,则后面系统默认给零
struct date { int year; int month; int day; }; struct student { char name[20]; char sex; struct date birthday; float score[4];}; struct student std1={"Li Ming",'M',1992,10,1,95.5,60,70,88.5}, std2={"Liu Qiang", 'M', {1995,10,1},{95.5,60,70,88.5}}, std3={"Zhang Ya",'F', {1995},{95.5,60}};
2.给结构体数组赋初值
同给基本数据类型的数组赋初值相同,只是因为结构体数组中的元素是结构体,因此需要加一个花括号,然后把每个元素中的多个值按顺序给结构体中的成员变量。
struct s { int num; char color; char type; }; struct s car[]={ {101,'G','c'}, {210,'Y','m'}, {105,'R','l'}, {222,'B','s'}, {308,'P','b'} }
引用结构体变量中的数据
可以对结构体变量中的数据成员逐个访问
相同结构体类型的变量可以直接赋值;
1.对结构体变量成语阿女的引用
(1)结构体变量名.成员名 如:std.name
(2)结构体指针变量名->成员名 如:pStd->name
(3)(*结构体 指针变量名).成员名 如:(*pStd).name
注意项:
成员是数组时,要访问数组中确定的元素 std.score[0]
如果字符数组看做字符串时可直接引用 std.name
结构体数组要先确定其中某个元素再访问 std[1].name
有嵌套结构体时,逐层访问 std.birthday.year
3.通过指针引用结构体成员注意事项
在通过指针引用结构体成员时,与++ --等运算符组成表达式。要根据运算符优先级确定表达式含义
如:struct
{
int a;
char *s;
}x,*p=&x;
++p->a ->的优先级高于++,因此是给成员变量的值加一
(++p)->则是先给p加一之后,引用一个结构体变量的成员变量
p++->a等同于(p++)->a, 都是先访问a再给p加一
p->a++等同于(p->a)++,访问成员a,然后给a加一
*p->s 引用结构体中s所指向的存储单元
*p->s++=>*(p->s++),引用结构体中s所指向存储单元,之后s加一
(*p->s)++给结构体s所指向存储单元的值加一
*p++->s 访问结构体中s存储单元,然后给p加一
4.相同类型结构体变量之间整体赋值
变量之间整体赋值,是把结构体变量成员的值对应赋值给另一个结构体变量的成员变量,要保证同类型结构体变量
struct { char *p; char a[20]; }s1={"abcdef","123456"},s2; s2=s1; s2.a[0]=‘1’; s2.p[0]='a' s1.a[0]的值?为1 s1.p[0]的值?为a
一定要注意是赋值成员变量的值,指针变量的时候赋值是指针变量的值,即它的指向是同一个内存地址,数组则不一样,他们有各自的内存存储空间
函数之间结构体变量的数据传递
传递结构体中单一成员变量
printf(“score=%f\n”,std.score[0])
传递整个结构体变量
int fun(struct student std);
fun(std);
注意如果结构体有指针成员变量,则修改其值会影响实参的值。会出现传址和传值,数组则是整体赋值。
传递结构体变量地址
将结构体变量的地址传入函数,函数形式则只保存结构体变量的地址,可通过地址直接修改石灿灿种结构体变量的数据值。
#include<stdio.h> tyoedef struct { char s[10]; int t; }ST; getdata(ST *p)/*形参为结构体类型指针变量*'/ { scanf("%s %d", p->s, &p->t); } main() { ST a; getdata(&a);/*地址做实参*/ printf(“%s,%d\n”,a.s, a.t); }
向函数传递结构体数组名
向函数传递数组名实际是传入结构体数组首元素的地址
typedef struct { int num; double mark; }REC; void sub1(REC x) { x.num=23; x.mark=81.5; } void sub2(REC y[]) { y[0].num=12; y[0].mark=77.5; } main(void) { REC a={16, 90.0}, b[]={16, 90.0}; sub1(a); printf("A)%d,%5.1lf\n",a.num, a.mark); sub2(b); printf("B)%d, %5.1lf\n", b[0].num, b[0].mark) }
函数的返回值为结构体类型
需要注意成员为指针吗,不能指向函数内的变量地址
typedef struct { int a; char b; }ST; ST fun(ST x)/*返回类型为结构体ST*/ { x.a=99; x.b='S'; return x; } main(void) { ST y; y.a=0; y.b='A'; printf("y.a=%d y.b=%c\n", y.a, y.b); y=fun(y); printf("y.a=%d y.b=%c\n", y.a, y,b); }
函数返回值为结构体的指针类型
需要注意不能返回函数中的临时结构体变量地址
typedef struct{ int a; char b;}ST; /*x作为函数的形参,属于临时变量,随着函数调用的结束,将被销毁,因此返回它的地址,将导致访问不该访问的内容*/ ST *fun(ST x) /*返回类型为结构体ST* 类型*/ { ST *px; x.a=100; x.b='C'; PX=&x; return px; } ST fun1(ST x)/*返回类型为结构体ST*/ { x.a=99; x.b='S'; return x; } main(void) { ST y, *P, X; y.a=‘999’; y.b='X'; printf("y.a=%d y.b=%c\n, y.a, y.b");//y.a=999 y.b=X p=fun(y); printf("(*p).a=%d (*P).b=%c\n, (*P).a, (*P).b"); x=fun1(x); printf("x.a=%d x.b=%c\n",x.a, x.b);//x.a=99 x.b=s }
利用结构体变量构成链表
结构体中含有可以指向本结构体的指针成员
当结构体中有一个或多个成员的基类型为本结构类型时,把这种结构体称为“引用自身的结构体”
struct link { char ch; struct link *p; }a;
struct node { int data; struct node *next; } typedef struct node NODETYPE; int main() { NODETYPE a, b, c, *h, *p; a.data = 10; b.data = 20; c.data = 30; h = &a; a.next = &b; b.next = &c; c.next = NULL; p = h; while(p) { printf("%d", p->data); p = p->next; } printf("\n"); return 0; }
a、b、c是在编写代码时,静态定义的。这样的链表不能在运行时,增加或者删除节点。因此也叫“静态链表”。
动态链表的概念
动态链表的特点
动态链表是一种动态的进行存储分配的数据结构,程序执行中,可以在需要时开辟存储单元,在不需要时释放存储单元。
链表的结点包括数据域和链接域,数据域用来保存数据信息,各节点内存上不一定连续,而是用链接域来保存该结点的后继结点或前驱结点的地址,从而在逻辑上各节点连续。
一个链表用一个头指针来保存该链表的首地址,即第一个结点的地址。头指针是一个链表的标志。
一个单链表的逻辑示意图
单向链表
只能从当前节点找到后续节点的链表叫“单向链表”
建立单链表的主要步骤为:
生成只含有头结点的空链表;
然后读取数据信息,生成新结点,将数据存放于新结点中,插入新结点到单链表中。
重复第二步,直到输入结束。
插入节点:表头插入、表尾插入、在表中插入
输出链表
删除结点
(1)创建链表
struct slist { int data; struct slist *next;}; typedef struct slist SLIST; SLIST *creat_slist() { int c; SLIST *h, *s, *r; h= (SLIST*)malloc(sizeof(SLIST)) r=h; scanf("%d",&c); while(c!=-1) { s=(SLIST*)malloc(sizeof(SLIST)); s->data = c; r->data = s; r = s; scanf("%d",&c); } r->next = NULL; return h; }
如果一开始便输入结束数据标志,则返回一个空链表。可以由h->next == NULL判断是否为一个空链表。
(2)顺序访问链表
void print_slist(SLIST *head) { SLIST *p; if (NULL == head) { printf("Linklist is not exist\n"); return; } p = head->next; if(p==NULL) printf("Linklist is null\n"); else { printf("head"); do { printf("-->%d",p->data); p = p->next; //让p指向下一个节点 }while(p! = NULL); printf("-->end\n"); } }
(3)向链表插入元素
//在x之前插入一个元素,值为y, x不存在,插在链表结尾 void insert_snode(SLIST *head, int x, int y) { SLIST *s, *p, *q; s = (SLIST*)malloc(sizeof(SLIST)); s->data = y; q = head; // q保存p的前一个节点 p = head->next; while((p!='\0'))&&(p->data != x))
{
q = p;
p = p->next;
} s->next = p;
q->next = s;
}
(4)删除链表中的元素
//删除链表中数据为x的节点,成功返回删除元素的个数,没找到删除失败返回0 int delete_snode(SLIST *head, int x) { SLIST *p, *delElem; int ret = 0; p = head->next; // p指向第一行放数据的节点 head -> data废弃不用 if(NULL == p)//链表为空直接返回假 return 0; while(p->next)//NULL != p->next, 要判断其值 { if(p->next->data==x) { delElem = p->next;//保存要删除元素的地址 p->next = p->next->next; //把之后的元素连接上 free(delElem);//释放要删除元素的内存 ret++; } p = p->next } return ret; }
(5)销毁链表
void destroy_list(SLIST *head) { SLIST *p = head, q; while(p) { q = p->next;//先保存p->next,否则把P释放了,p->next就不存在了 free(p);//释放p所指向的内存空间 p = q; } head = NULL; }
双向链表也叫双链表,是链表的一种,它的每个数据点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个节点开始,都可以很方便地访问它的前驱节点和后继结点。
循环链表是另一种形式的练市存储结构,它的特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环。
共同体
共同体也叫联合体,类型说明和变量的定义方式与结构体的类型说明和变量定义的方式完全相同,不同的是结构体变量中的成员各自占有自己的存储空间,而共用体变量中的所有成员占有同一个存储空间。(之间会相互覆盖,只有一个实体存在)
共同体变量定义
说明
1.共同体变量在定义的同时只能给第一个成员的类型的值进行初始化,如s1和s2在定义的同时只能赋予整型值。
2.共用体所有成员共享一段公共存储区,共用体变量所占内存字节数与其成员中字数最多的那个成员相等
3.变量中所有成员的首地址相等也是该变量的首地址
&s1 == &s1.i == &s1.x
共用体变量的引用方式
3种方式等价:
共用体变量名.成员名
共用体指针名->成员名
(*共用体指针名).成员名
引用规则
不能引用共同体变量
只能引用其成员
printf(“%d”, a.i);
共用体变量中起作用的是最近一次存入的成员变量的值,原有成员变量的值将被覆盖,如a.i=5;a.x=10;a.i不在是5了