目录
前言
C语言中有:
- 内置类型
- 自定义类型
内置类型有:
char、short、int、long 、long long、float、double。
内置类型是C语言本身就具有的类型,可以直接拿来使用,除了这些类型之外C语言还有自定义类型,自定义类型是自己所创造的一种类型。
自定义类型有:
结构体、枚举、联合体,数组。
1. 结构体的声明
1.1 结构的基础知识
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
结构体其实是把一些值组合在一起作为某一个对象的属性。
1.2 结构的声明
结构体的语法形式:
struct tag
{
member-list;
}variable-list;
struct是——关键字;
tag——是结构体标签,类似于结构体的名字,在C语言中使用结构体类型时,应该把struct和tag写在一起;大括号里放的是描述对象所需要的相关属性:
member-list——是成员列表,这样的每一个成员是成员变量;
variable-list——是变量列表。
如定义一个学生的结构体类型:(描述一个学生)
#include <stdio.h>
struct Stu//结构体标签是Stu
{
char name[20];
char sex[5];//数组
int age;
int height;//整型
//成员列表里有多个成员变量
};//这里是结构体类型:struct Stu
int main()
{
struct Stu s1;//s1就是用struct Stu这个结构体类型创建的结构体变量
return 0;
}
#include <stdio.h>
struct Stu
{
char name[20];
char sex[5];
int age;
int height;
}s2, s3, s4;//这里直接用上面的struct Stu结构体类型创建了s1,s2,s3,3个结构体变量
//这里用逗号隔开可以一下子创建多个结构体变量,所以这里叫变量列表
//s2,s3,s4是全局变量
int main()
{
struct Stu s1;//结构体变量,s1是局部变量
return 0;
}
#include <stdio.h>
struct Stu
{
char name[20];
char sex[5];
int age;
int height;
}s2, s3, s4;//全局变量,注意这里有分号
//单独再定义一个全局变量:
struct Stu s5;//全局变量
int main()
{
struct Stu s1;//局部变量
return 0;
}
结构体类型最好放在前面定义。
1.3 特殊的声明
结构体有一些特殊的声明:
在声明结构的时候,可以不完全的声明。
比如:把标签去掉——不完全声明,没有名字是匿名结构体类型
#include <stdio.h>
struct
{
char c;
int a;
double d;
}x;
//匿名结构体类型
//这里用匿名结构体类型创建了一个变量叫x
int main()
{
return 0;
}
匿名结构体类型的使用,必须紧挨着结构体类型创建变量,否则以后就不能定义变量了,没有名字就不能定义了。
匿名结构体类型只能用一次:在声明结构体类型的同时定义变量。
#include <stdio.h>
struct
{
char c;
int a;
double d;
}x;//匿名结构体
struct
{
char c;
int a;
double d;
}* ps;//ps是这样一个匿名结构体的指针
int main()
{
//ps指针存放x的地址
ps = &x;//错误:=两边从*到*的类型不兼容
//即编译器认为这是两种类型,虽然成员一模一样但是编译器
//认为匿名结构体在这里使用会是两种类型,认为这两种结构体类型是不一样的
//此代码非法
return 0;
}
1.4 结构的自引用
在结构中包含一个类型为该结构本身的成员是否可以呢?
数据结构:数据在内存中存储的结构。如:顺序表、链表、栈、队列、二叉树。
顺序表:如在内存中找一块连续的空间存储1,2,3,4,5,像数组一样。对顺序表提供增删查改的功能就是顺序表的数据结构。像一条线
链表:1存2的地址,1能找到2;2存3的地址,2能找到3……最后的数字5存NULL,不存有效地址,它没有任何指向。找到1就可以把2,3,4,5串起来,像一条线。
顺序表和链表都像一条线,所以顺序表和链表是线性数据结构。
两个指针之间的交叉点是结点,怎么描述这样的结点呢?
错误的自引用方式:
#include <stdio.h>
struct Node
{
int data;
struct Node next;
};
int main()
{
return 0;
}
此时struct Node的大小是多少?即计算sizeof(struct Node)?
struct Node
{
int data;//结点处放的整型数据
struct Node next;//一个结点中包含下一个结点
};
//结构体中又包含一个自己结构体类型这种形式写法是绝对不行的,因为
//struct Node这个结构体包含一整型data,又包含一个这样的结构体变量next,则
//这个结构体变量里面还有一个data,还有自己的next……无限包含,则
//这个结构体大小就没办法计算了
int main()
{
return 0;
}
重新分析:一个结点中只要存储下一个结点的地址就可以了:
正确的自引用方式:
#include <stdio.h>
struct Node
{
int data;
struct Node* next;
};
int main()
{
return 0;
}
自己类型的对象找自己类型对象的方法——要存储的是结构体指针而不是结构体对象,这是结构体的自引用。
#include <stdio.h>
struct Node
{
int data;//4个字节
struct Node* next;//存放一个地址(指针),4个字节或8个字节
};
//此时结构体大小就确定了
//此时创建的每个结点即可以保存一个数值data又可以保存一个地址(由next指向的结点)
//——这就实现了自己类型的对象找自己类型对象的方法,是结构体的自引用
int main()
{
return 0;
}
这是链表的实现方式,也是结构体的自引用。
注意:
#include <stdio.h>
typedef struct
{
int data;
Node* next;
}Node;
int main()
{
Node n;//这里定义结点n可以吗?
return 0;
}
此时用typedef对struct和{}中的这个匿名结构体起名是Node,然后在结构体成员中写Node*next
——不可行!
因为运行时编译器显示错误:Node:未声明的标识符,即不认识Node,用typedef对结构体类型重命名时这个类型必须是清晰可见的,必须是存在的,即Node必须在有的前提下才能用typedef重新命名。
把一个结构体重命名,则这个结构体必须是清晰可见的。
#include <stdio.h>
typedef struct Node
{
int data;
//Node* next;//但是这里还没有产生Node,则:
struct Node* next;
}Node;//这里是用typedef把struct Node这个结构体重命名为Node
int main()
{
//重命名完之后就不用struct Node创建结构体变量了,直接用Node
//但也还是可以用struct Node创建变量的
//即struct Node和Node可以同时使用
Node n = { 0 };
struct Node n1 = { 0 };
return 0;
}
typedef是关键字,是重命名结构体的。
1.5 结构体变量的定义和初始化
有了类型就可以创建变量,即有了结构体类型就可以创建结构体变量。
定义结构体的方法:
#include <stdio.h>
struct Point//锁定点
{
int x;//结构体成员x:横坐标
int y;//结构体成员y:纵坐标
}p1;//定义结构体变量p1
//这里声明类型的同时定义了结构体变量p1
//这时就已经有了struct Point这个结构体类型
struct Point p2;//用struct Point这个结构体类型创建p2变量
struct Point p3 = { 1, 2 };//定义变量的同时赋初值
int main()
{
return 0;
}
如一个结点的创建及初始化:
#include <stdio.h>
typedef struct Node
{
int data;
struct Node* next;
}Node;
int main()
{
struct Node n = { 100,NULL };
return 0;
}
如对一个学生类型初始化:
#include <stdio.h>
struct Stu
{
char name[20];
char sex[5];
int age;
int height;
};
int main()
{
struct Stu s = { "zhangsan", "nan", 20, 180 };
return 0;
}
初始化,只要把它的成员放在大括号中就可以了,同一个结构体多个成员初始化都用一个大括号,比如数组,结构体。
如果结构体成员是嵌套的,即一个结构体包含一个结构体,这时怎么进行初始化呢?
#include <stdio.h>
struct Stu
{
char name[20];
char sex[5];
int age;
int height;
};
struct Data
{
struct Stu s;
//这是嵌套:结构体中包含一个结构体类型的数据
char ch;
double d;
};
int main()
{
struct Data d = { {"lisi","nv",30,166},'w',3.14 };
//首先初始化s,因为s又是一个结构体成员所以用大括号括起来,后面在初始化ch、d
return 0;
}
#include <stdio.h>
struct Point
{
int x;
int y;
}p1;
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = { 10, {4, 5}, NULL };//结构体嵌套初始化
struct Node n2 = { 20,{5,6},NULL };//结构体嵌套初始化
int main()
{
return 0;
}
结构体成员的访问可以用.和->这两个操作符访问。
1.6 结构体内存对齐
——重点
结构体内存对齐涉及到的最终问题就是计算结构体大小。
#include <stdio.h>
struct s1
{
char c1;//c1占1个字节大小
int i;//4
char c2;//1
};//6个字节空间即可
int main()
{
printf("%d\n", sizeof(struct s1));//12,为什么?
return 0;
}
研究c1、i、c2在内存中是如何存储的?
offsetof(),函数名是谁的偏移量,本身是一个宏,返回的是一个结构体成员相较于其结构体起始位置的偏移量。
size_t offsetof( structName, memberName );
参数是结构体名,结构体成员名;返回的值的类型是size_t,是个整型值。使用它需要包含头文件<stddef.h>。
#include <stdio.h>
#include <stddef.h>
struct s1
{
char c1;
int i;
char c2;
};
int main()
{
//printf("%d\n", sizeof(struct s1));
printf("%d\n", offsetof(struct s1,c1));//计算c1的偏移量:0
printf("%d\n", offsetof(struct s1, i));//计算i的偏移量:4
printf("%d\n", offsetof(st