11.1.1 枚举
常量符号化:用符号而不是具体的数字来表示程序中的数字。
让用户输入颜色的代号,我们输出对应的颜色:可以用const int 和switch来解决。
const int red=0;
const int yellow=1;
const int green=2;
int main()
{
int color=-1;
char *colorname=NULL;
printf("请输入你喜欢的颜色的代码");
scanf("%d",&color);
switch(color){
case red:colorname="red";break;
case yellow:colorname="yellow";break;
case green:colorname="green";break;
default:colorname="unknown";break;
}
printf("%s",colorname);
return 0;
再把这件事再往前推进一点,我们使用枚举而不是单独定义const int变量。
enum COLOR{RED,YELLOW,GREEN};
int main(){
int color=-1;
char *colorname=NULL;
printf("输入你喜欢的颜色代码:");
scanf("%d",&color);
switch(color){
case RED:colorname="red";break;//在case处就可以直接使用RED YELLOW和GREEN来取代0,1,2
case YELLOW:colorname="yellow";break;
case GREEN:colorname="green";break;
default:colorname="unknown";break;
}
printf("你喜欢的颜色是%s\n",colorname);
return 0;
}
枚举是一种用户定义的数据类型,使用以下格式定义:enum 枚举类型名{名字0,名字1……名字n};
enum是enumeration。
枚举类型名通常不用,我们用的是大括号中的名字,因为他们就是常量符号,类型一定是int,值从0到n。比如上例中,RED=0,YELLOW=1,GREEN=2。
当需要一些可以排列起来的常量值时,定义枚举的意义就是给了这些常量值名字。
在函数中使用时要记得说上前缀enum
enum color {red,yellow,green};
void f(enum color c);
int main()
{
enum color t= red;
scanf("%d",&t);
f(t);
return 0;
}
void f(enum color c)
{
printf("%d\n",c);
}
这样可以像int一样输入输出。
因为定义中的名字是从0到n按顺序排列的,这样要遍历时或者要建立数组时就会很方便。
enum COLOR{RED,YELLOW,GREEN,numcolors};//结尾的numcolors表示数组的结尾,同时也可以表示enum中元素的个数。
int main()
{
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",colorname);
return 0;
}
另外,声明枚举量的时候可以指定特殊值,不一定非要按顺序从0开始。enum color{red=1,yellow,green=5};
如果输出%d,green 就会输出5。
但是枚举只是int,即使给它赋不存在的值(比如上例中,我们enum color c=0;
也没有关系)也不会有warning或error。
枚举虽然可以当做类型来使用,但是并不好用。现在通常定义一些排比的符号量,这样比const int一个个来方便。
枚举比后面会讲到的宏(marco)好,因为枚举有类型int。
11.2.1 结构类型
我们已经知道,在c中我们要表达的数据,要有变量,还要有类型。
如果要表达的数据比较复杂(比如日期,包括年、月、日;或时间,包括时、分、秒),而我们又希望用一个整体去表达,就要用到C语言的结构。
结构是一个复合的数据类型,在里面有很多各种类型的“成员”,然后可以用一个变量来表达多个数据。
int main()
{
struct date{
int day;
int month;
int year;
};//声明时,结尾有个分号!!
struct date today;//像枚举一样不要忘记开头,struct
today.day=12;
today.month=3;
today.year=2021;
printf("Today's date is %i-%i-%i.",today.year,today.month,today.day);
return 0;
}
当然,和之前本地、全局变量一样,如果结构是在一个函数内部声明的,则该结构只能在该函数内部使用。在函数外声明就可以在多个函数内使用了。
另一种声明方式:
struct{
int x;
int y;
}p1,p2;
p1和p2都是一种无名结构,都包含x和y。没有声明结构名字,临时造了两个无名结构出来。
不过,最常见的还是要声明结构名字的形式。
struct point{
int x;
int y;
}p1,p2;
p1,p2都是point,都包含x和y。
month | day | year |
---|---|---|
11 | 23 | 2007 |
today的内存中包含month, day, year.
还有一件要注意的事就是 声明结构类型 和 定义结构变量 要区分。声明类型后方可定义变量。
结构初始化:
struct date={12,3,2021};//注意顺序
struct thismonth={.month=3,.year=2021};//剩下没有被赋值的部分都是0,和数组一样
和数组相比,结构中的成员可以是不同类型的。
数组用[]运算符和下标来访问;而结构用.
运算符和名字来访问。
结构的运算
可以用结构名字.成员名字
来访问某个成员,也可以直接用结构名字来访问整个结构变量。可以做赋值、取地址,传递给函数参数。
p1=(struct point){5,10};
p1=p2;
这两种操作,数组变量都做不了。
struct date today,day;
today=(struct date){12,3,2021};
day=today;
和数组不同,结构变量的名字并不是结构变量的地址,取地址要加上&
struct date *pdate=&today;
11.2.2 结构与函数
结构像int等类型一样,可以作为函数的参数。
int numberofdays(struct date d)
整个结构可以作为参数的值传入函数。这时候会在函数内部新建一个结构变量,并复制该参数的值。当然,函数也可以返回一个结构。
(貌似美国的写法是月/日/年)&date.month
中,取成员运算符.
的优先级高于取地址运算符&
怎样输入结构?我们不能用scanf直接读入一个结构。
先尝试写一个读入结构的函数:先在main函数里定义,然后把该参数传入getstruct函数
struct point p={0,0};
gtestruct(p);
void getstruct(struct point p){
scanf("%d",&p.x);
scanf("%d",&p.y);
}
然而这不像指针,这样读入的结构是不会传入原函数中的。(只是一个克隆体而不是直接对结构本身做操作)
记住函数是有返回值的,我们要做的是在输入函数中创建一个临时的结构变量,返回给调用者。
struct point getstruct(void)
{
struct point p;
scanf("%d",&p.x);
scanf("%d",&p.y);
return p;
}
//main函数中:y=getstruct();
然而,在函数中建立一个拷贝来回传递,既费空间又费时间。还是结构指针的方法会好很多。
struct date myday;
struct date *p=&myday;
(*p).month=12;//正常应该这样写
p->month=12;//也可以简写成这样
->表示指向结构变量中的成员
#include<stdio.h>
struct point{
int x;
int y;
}p;
struct point *getstruct(struct point *p);
void print(const struct point *p);
int main()
{
struct point p={0,0};
print(getstruct(&p));
return 0;
}
struct point *getstruct(struct point *p)
{
scanf("%d",&p->x);
scanf("%d",&p->y);
return p;
}//像这样传入一个参数,对其做处理后再返回该参数的函数,可以直接套用在其他函数中。
void print(const struct point *p)//const
{
printf("%d %d",p->x,p->y);
}
11.2.3 结构中的结构
结构中的数组
struct date dates[100];
struct date dates[]={
{3,21,2021},{3,22,2021}
};
printf("%.2i",dates[1].month);//不知道%.2i什么意思
结构里的变量也可以是另外一个结构
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 *rp;
rp=&r;
//那么下面的四种形式是等价的
r.pt1.x
rp->pt1.x
(r.pt1).x
(r->pt1).x
//但是不能写r->pt1->x,因为pt1不是指针而是结构。
如上图所示,rp指向r。
甚至可以做结构里的结构里的数组。
(这里也能看出这么写会好看很多~)
11.3.1 类型定义
比如我们之前讲struct的时候要一直加上struct前缀。如何摆脱呢?
自定义数据类型(typedef
)typedef int length;
这样使得length成为int类型的别名。可以直接把length当做int类型来用length a,b;
length a[10];
声明的新类型是某种类型的别名,改变了程序的可读性,简化了复杂的名字。
typedef struct ADate{
int month;
int day;
int year;
}Date;
Date a={3,24,2021};
typedef *char[10] String;//string是十个字符串的数组的类型。
11.3.2 联合
union,表面上与struct非常相似。
unit xxx{
int a;
char b;
}xxx1,xxx2;
xxx1.a=1;
xxx2.b='c';
和struct不同的是,union中所有变量(即a,b)占据的是相同的空间,大家联合起来使用同一个空间。
比如一个int 4个字节,也可以被看做是char的数组0~3
比如char是1234,则转化为十六进制应该是00 00 04 D2
我们通过下面的方法来看看是不是这么储存的。
占位符的意思是:1.输出两位,即如果不足10要补个0(比如2→02)
2.这就是一个字节了,不要再扩展了
3.以十六进制输出
这个在文件那里还会再讲。
我们现在的X86是小端机器,放数的时候其实是小端在前
也是很有用的,比如做文件时,比如当我们要把一个整数以二进制形式输到一个文件中去时,可以作为中间的媒介(没懂……)。