目录
三、结构类型
常量符号化
- 用符号而不是具体的数字来表示程序中的数字
3.1 枚举
- 枚举是一种用户定义的数据类型,它用关键字 enum 以如下语法来声明:
enum 枚举类型名字{名字0,...,名字n};
- 枚举类型名字通常并不真的使用,要用的是在大括号里的名字,因为它们就是常量符号,它们的类型是int,值则依次从0到n。如:
enum colors { red,yellow,green };
- 就创建了三个常量,red的值是0,yellow是1,而green是2。
- 当需要一些可以排列起来的常量值时,定义枚举的意义就是给了这些常量值的名字。
#include<stdio.h>
enum color { red,yellow,green };
void f(enum color c);
int main(void)
{
enum color t1 = red;
enum color t2 = yellow;
enum color t3 = green;
f(t1);
f(t2);
f(t3);
return 0;
}
void f(enum color c)
{
printf("%d\n", c);
}
运行
- 枚举量可以作为值
- 枚举类型可以跟上enum作为类型
- 但是实际上是以整数来做内部计算和外部输入输出的
3.11 自动计数的枚举
#include<stdio.h>
enum COLOR { RED, YELLOW, GREEN, NumCOLORS };
int main(int argc, char const *argy[])
{
int color = -1;
char *ColorNames[NumCOLORS] = {
"red","yellow","green",
};
char *colorName = NULL;
printf("输入你喜欢的颜色的代码:");
scanf("%d",&color);
if (color >= 0 && color < NumCOLORS) {
colorName = ColorNames[color];
}
else {
colorName = "unknown";
}
printf("你喜欢的颜色是%s\n",colorName);
return 0;
}
运行
- 这样需要遍历所有的枚举量或者需要建立一个用枚举量做下标的数组的时候就很方便了
3.12 枚举量
- 声明枚举量的时候可以指定值
- enmu COLOR {RED = 1, YELLOW, GREEN = 5};
#include<stdio.h>
enum COLOR { RED = 1, YELLOW, GREEN = 5, NumCOLORS };
int main(int argc, char const *argy[])
{
printf("code for GREEN is %d\n",GREEN);
return 0;
}
运行
3.13 枚举只是int
- 即使给枚举类型的变量赋不存在的整数值也没有任何warning和error
3.14 总结
- 虽然枚举类型可以当作类型使用,但是实际上很少用(不好用)
- 如果有意义上排比的名字,用枚举比const int 方便
- 枚举比宏(macro)好,因为枚举有int类型(主要是定义符号量,而不是当作枚举类型使用)
3.2 结构类型
- 结构体(struct)是由一系列具有相同类型或不同类型的数据构成的数据集合,也叫结构
- 表达数据需要有变量,每个变量又对应某个类型,想表达的数据比较复杂,不止一个值(如日期:年月日三个值),希望用一个整体来表现集合在一起的数据的时候,就用到了结构
声明结构类型
#include <stdio.h>
int main()
{
struct date{
int month;
int day;
int year;
};//初学者切记,不能遗漏分号嗷!!!
struct date today;
today.month=07;
today.day=31;
today.year=2014;
printf("Today's date is %i-%i-%i.\n",
today.year,today.month,today.day);
return 0;
}
输出结果
Today's date is 2014-7-31.
注:格式说明符%i是老式写法,可以自动将输入的八进制或十六进制转换为十进制
%d与%i的异同详见这篇笔记https://junyu.blog.csdn.net/article/details/121086756
3.21 最好声明在函数外
- 和本地变量一样,在函数内部声明的结构类型只能在函数内部使用
- 所以通常在函数外部声明结构类型,这样就可以被多个函数所使用了
如图
3.22 声明结构的三种形式
- 1.上面声明结构,下面定义结构变量
struct point{
int x;
int y;
};
struct point p1,p2;
p1和p2都是point
里面有x和y的值
- 2.直接说明结构变量(没有名字,只想要两个变量)
struct{
int x;
int y;
}p1,p2;
p1和p2都是一种无名结构,里面有x和y
- 3.同时声明结构、定义变量
struct point{
int x;
int y;
}p1,p2;
p1和p2都是point
里面有x和y的值
3.23 结构的初始化
两种初始化方法
#include <stdio.h>
struct date {
int month;
int day;
int year;
};
int main()
{
struct date today = { 07,31,2014 };
struct date thismonth = {.month=7, .year=2014};
printf("Today's date is %i-%i-%i.\n",
today.year, today.month, today.day);
printf("Today's month is %i-%i-%i.\n",
thismonth.year, thismonth.month, thismonth.day);
return 0;
}
输出
Today's date is 2014-7-31.
Today's month is 2014-7-0.
3.24 结构成员
- 结构和数组有点像
但数组里的单元必须是相同类型的,结构里的成员可以是不同类型的- 数组用[ ]运算符和下标访问其成员
a[0]=10;
- 结构用.运算符和名字访问其成员
today.day
student.firstName
p1.x
p1.y
- 数组用[ ]运算符和下标访问其成员
如
struct point{
int x;
int y;
};
struct point p1,p2;
注: . 是用来访问结构体成员的。
. 的右边是结构类型即成员名(x,y),是虚的;
. 的左边一定是一个结构变量(p1,p2),才是实体
3.25 结构运算
- 要访问整个结构,直接用结构变量的名字
- 对于整个结构,可以做赋值、取地址,也可以传递给函数参数
p1 =(struct point ){5,10};
//相当于p1.x=5; p1.y=10;
p1 = p2;
//相当于p1.x = p2.x; p1.y = p2.y;
注:数组无法做这两种运算!
代码示例
#include <stdio.h>
struct date {
int month;
int day;
int year;
};
int main()
{
struct date today ;
today = (struct date) { 07, 31, 2014 };
struct date day;
day = today;//day得到today里面所有成员的值
day.year = 2015;
printf("Today's date is %i-%i-%i.\n",
today.year, today.month, today.day);
printf("Today's date is %i-%i-%i.\n",
day.year, day.month, day.day);
return 0;
}
输出
Today's date is 2014-7-31.
Today's date is 2015-7-31.
3.26 结构指针
- 和数组不同,结构变量的名字并不是结构变量的地址,必须使用&运算符
struct date *pDate = &today;
代码示例
#include <stdio.h>
struct date {
int month;
int day;
int year;
};
int main()
{
struct date today ;
today = (struct date) { 07, 31, 2014 };
struct date day;
day = today;
struct date *pDate = &today;
//结构名字并不是地址,&不可省略
printf("Today's date is %i-%i-%i.\n",
today.year, today.month, today.day);
printf("Today's date is %i-%i-%i.\n",
day.year, day.month, day.day);
printf("address of today is %p \n",pDate);
return 0;
}
输出
Today's date is 2014-7-31.
Today's date is 2014-7-31.
address of today is 00CFF864
3.3 结构与函数
3.31 结构作为函数参数
int numberOfDays(struct date d )
- 整个结构可以作为参数的值传入函数
- 这时候是在函数内新建一个结构变量,并复制调用者的结构的值
- 也可以返回一个结构
- 这与数组完全不同
输入结构
#include <stdio.h>
struct point{
int x;
int y;};
void getStruct(struct point);
void output(struct point);
void main(){
struct point y={0,0};
getStruct(y);
printf("------\n");
output(y);}
void getStruct(struct point p){
//此时p是和上边main里的y具有相同值的另外的结构变量,p与y没有任何联系(此处和数组区别)
scanf("%d",&p.x);
scanf("%d",&p.y);
printf("%d,%d",p.x,p.y);
//返回时没有改变main里的y,y值还是0
}
void output(struct point p){
printf("%d,%d",p.x,p.y);
//输出main中y值仍为0
}
输出
3
4
3 4
------
0 0
由此可知
- 没有直接的方式可以一次scanf一个结构
- 如果我们打算写一个函数来读入结构
- —>
- 但是读入的结构如何送回来呢?
- 记住C语言在函数调用时是传值的
- 所以函数中的p与main中的y是不同的
- 在函数读入了p的数值之后,没有任何东西回到main,所以y还是{0,0}
解决的方案
- 之前的方案,把一个结构传入函数,然后在函数中操作,但是没有返回回去
- 问题在于传入函数的是外面那个结构的克隆体,而不是指针
- 传入结构和传入数组是不同的
- 问题在于传入函数的是外面那个结构的克隆体,而不是指针
- 在这个输入函数中,完全可以创建一个临时的结构变量 ,然后把这个结构返回给调用者
#include <stdio.h>
struct point {
int x;
int y;
};
struct point getStruct(void);
void output(struct point);
void main() {
struct point y = { 0,0 };
y = getStruct();
// getStruct(y);
printf("------\n");
output(y);
}
struct point getStruct(void) {
struct point p;//本地变量,用完消失
scanf("%d", &p.x);
scanf("%d", &p.y);
printf("%d %d\n", p.x, p.y);
return p;
}
void output(struct point p) {
printf("%d %d\n", p.x, p.y);
}
输出
3 4
3 4
------
3 4
3.32 结构与指针
- 传结构的指针比上面的拷贝结构省空间省时间
struct data{
int month;
int day;
int year;
}myday;
struct date *p=&myday;
(*p).month=12;
p->month=12;
- 用 -> 表示指针所指的结构变量中的成员
- 英文 p arrow month
- 中文 p所指的month
3.33 结构指针参数
void main()
{
struct point y={0,0};
inputPoint(&y);
output(y);
}
struct point* inputPoint(struct point *p)
{
scanf("%d",&(p->x));
scanf("%d",&(p->y));
return p;
}
- 好处是传入传出只是一个指针的大小
- 如果需要保护传入的结构不被函数修改
const struct point *p
- 返回传入的指针是一种套路
#include <stdio.h>
struct point {
int x;
int y;
};
struct point *getStruct(struct point*);
void output(struct point);
void print(const struct point *p);
int main(int argc, char const *argv[]) {
struct point y = { 0,0 };
getStruct(&y);
printf("(1) output(y)------\n");
printf("此时的y为------\n");
output(y);
printf("(2) output(*getStruct(&y))------\n");
output(*getStruct(&y));//调用返回的指针
printf("此时的y为------\n");
output(y);
printf("(3) print(getStruct(&y))------\n");
print(getStruct(&y));
printf("此时的y为------\n");
output(y);
printf("(4) getStruct(&y)->x = 0------\n");
getStruct(&y)->x = 0;
printf("此时的y为------\n");
output(y);
printf("(5) *getStruct(&y) = (struct point) { 1, 2 }------\n");
*getStruct(&y) = (struct point) { 1, 2 };
printf("此时的y为------\n");
output(y);
}
struct point *getStruct(struct point *p) {//修改
printf("输入两个数\n");
scanf("%d", &p->x);
scanf("%d", &p->y);
printf("输出此时结果\n");
printf("%d,%d\n", p->x, p->y);
printf("返回修改的结果\n");
return p;//传进指针,修改指针所指向内容,返回指针
/*好处 可以串在其他函数的调用当中*/
}
void output(struct point p) {
printf("%d,%d\n", p.x, p.y);
}
void print(const struct point *p)//只读
{
printf("%d,%d\n", p->x, p->y);
}
输出
输入两个数
1 2
输出此时结果
1,2
返回修改的结果
(1) output(y)------
此时的y为------
1,2
(2) output(*getStruct(&y))------
输入两个数
3 4
输出此时结果
3,4
返回修改的结果
3,4
此时的y为------
3,4
(3) print(getStruct(&y))------
输入两个数
5 6
输出此时结果
5,6
返回修改的结果
5,6
此时的y为------
5,6
(4) getStruct(&y)->x = 0------
输入两个数
7 8
输出此时结果
7,8
返回修改的结果
此时的y为------
0,8
(5) *getStruct(&y) = (struct point) { 1, 2 }------
输入两个数
9 10
输出此时结果
9,10
返回修改的结果
此时的y为------
1,2
3.4 结构中的结构
struct dateAndTime{
struct data sdate;
struct time stime;
};
3.41 结构数组
struct date dates[100];
struct date dates[]={
{4,5,2005},{2,4,2005} };
最外层括号’{‘是在说初始化一个数组
里面的括号是在说是date的第几个值,从0开始
#include<stdio.h>
struct time {
int hour;
int minutes;
int seconds;
};
struct time timeUpdate(struct time now);
int main(void)
{
struct time testTimes[5] = {
{11,59,59},{12,0,0},{1,29,59},{23,59,59},{19,12,27}
};
int i;
for (i = 0; i < 5; ++i) {
printf("Time is %.2i:%.2i:%.2i",
testTimes[i].hour, testTimes[i].minutes, testTimes[i].seconds);
testTimes[i] = timeUpdate(testTimes[i]);//一秒钟之后(结构变量可以直接被赋值,可以传递给另外一个函数)
printf("...one second later it's %.2i:%.2i:%.2i\n",
testTimes[i].hour, testTimes[i].minutes, testTimes[i].seconds);
}
return 0;
}
struct time timeUpdate(struct time now)
{
++now.seconds;
if (now.seconds == 60) {
now.seconds = 0;
++now.minutes;
if (now.minutes == 60) {
now.minutes = 0;
++now.hour;
if (now.hour == 24) {
now.hour = 0;
}
}
}
return now;
}
输出
Time is 11:59:59...one second later it's 12:00:00
Time is 12:00:00...one second later it's 12:00:01
Time is 01:29:59...one second later it's 01:30:00
Time is 23:59:59...one second later it's 00:00:00
Time is 19:12:27...one second later it's 19:12:28
3.42 嵌套的结构
struct point{
int x;
int y;
};
struct rectangle{
struct point pt1;
struct point pt2;
};
如果有变量
struct rectangle r;
就可以有:
r.pt1.x、r.pt1.y,
r.pt2.x、r.pt2.y。
如果有变量定义:
struct rectangle r,*rp;
rp = &r;
那么下面的四种形式是等价的:
r.pt1.x
rp->pt1.x
(r.pt1).x
(rp->pt1).x
pt1->x是错的(pt1不是指针是结构)
3.43 结构中的结构的数组
#include<stdio.h>
struct point {
int x;
int y;
};
struct rectangle {
struct point p1;
struct point p2;
};
void printRect(struct rectangle r,int i)
{
printf("rects[%d] <p1.x=%d,p1.y=%d>to<p1.y=%d,p1.y=%d>\n",
i,r.p1.x, r.p1.y, r.p1.y, r.p1.y);
}
int main()
{
int i;
struct rectangle rects[] = {
{{1,2},{3,4}},
{{5,6},{7,8}}
};//2rectangles
//最外层{表示数组,第二层大括号表示数组的单元,也就是结构,最里面的表示p1和p2
for (i = 0; i < 2; i++)
printRect(rects[i],i);
}
输出
rects[0] <p1.x=1,p1.y=2>to<p1.y=2,p1.y=2>
rects[1] <p1.x=5,p1.y=6>to<p1.y=6,p1.y=6>
3.5 联合
3.51 自定义数据类型(typedef)
- C语言提供了一个叫做 typede f的功能来声明一个已有的数据类型的新名字。比如
typedef int Length;
使得 Lengh 成为 int 类型的别名。 - 这样, Length 这个名字就可以代替 int 出现在变量定义和参数声明的地方了;
Length a,b,len;
Length numbers[10];
3.52 Typedef
- typedef 为C语言的关键字,作用是为一种数据类型定义一个新名字,这里的数据类型包括内部数据类型(int,char等)和自定义的数据类型(struct等)。
- 声明新的类型的名字
- 新的名字是某种类型的别名
- 改善了程序的可读性
typedef long int64_t;
/*重载已有的类型名字 新名字的含义更清晰 具有可移植性*/
typedef struct ADate{
int month;
int day;
int year;
}Date; //简化了复杂的名字
/*Date 等价 struct ADate*/
int64_t i=100 000 000 000;
Date d={9,1,2005};
typedef 本身是一种存储类的关键字,与 auto、extern、static、register 等关键字不能出现在同一个表达式中。
typedef struct {
int month;
int day;
int year;
}Date;
typedef 作用
- 把 typedef 到最后一个单词中间所以东西命名为最后一个单词
typedef int Length;//Length就等价于int类型
typedef char*Strongs[10]; //Strings是10个字符串的数组的类型
typedef struct node{
int data;
struct node *next;
}aNode;
或
typedef struct node aNode;//这样用aNode就可以代替 struct node;
3.53 联合
- union 是一种“类似”与struct的联合体,联合的所有成员引用的是内存中的相同位置,以最大的成员的内存长度作为union的内存大小。union主要用来节省空间,默认的访问权限是公有的。
union AnElt{
int i;
char c;
} elt1.elt2;
elt1.i=4;
elt2.c='a';
elt2.i=0xDEADBEEF;
sizeof(union …)= sizeof(每个成员)的最大值
-
union 的两个成员占据了相同的内存空间,只有一份,所以叫做联合(大家联合起来使用一个空间)
-
存储
- 所有的成员共享一个空间(联合空间)
- 同一时间只有一个成员是有效的(你用了我不能用)
- union 的大小是其最大的成员
-
初始化
- 对第一个成员做初始化
union AnElt{
int i;
char c;
} elt1.elt2;
elt1.i=4;
elt.c='a';
elt2.i=0xDEADBEEF;
如果(elt1 当前是char)...
-
union 自己并不知道当时其中哪个成员是有效的
-
程序怎么能知道当时elt1和elt2里面到底是int还是char?
最好的答案:另一个变量来表达这个事情 -
union 的用处
#include<stdio.h>
typedef union{
int i;
char ch[sizeof(int)];
}CHI;
int main(int argc, char const *argv[])
{
CHI chi;
int i;
chi.i=1234;
for( i=0;i<sizeof(int); i++){
printf("%02hhX",chi.ch[i]);
/*%02hhX 标准的格式,不用考虑内存空间是有符号的字符串,还是无符号的字符串,亦或是类型转换等。*/
}
printf("\n");
return 0;
}
输出
D2040000
- 这个结果表面我们所用的CPU是小端的(低位在前)
- 用处:文件操作、把一个整数以二进制形式写到一个文件里的时候
- 注意:%02hhX 标准的格式,不用考虑内存空间是有符号的字符串,还是无符号的字符串,亦或是类型转换等。
-
hh (格式转换符)标示以一个字节char类型打印值。类似于h标示以一个short类型打印值一样。
-
hh和X组合标示 以一个char类型打印一个整数(int ,四个字节),将四个字节的整数截断成一个字节打印出来。
-
“%hhx” 是只输出8位数(两个字符),即便超了,也只显示低两位;
-
02 不够2位的前边补0 补成两位打印出来,超过2位的以实际长度打印出来
-
对于%02hhx,hhx已经以一个字节打印了,加上02限制,不够两位的补成两位。
-