C语言程序设计(九)
文章目录
结构类型
枚举
常量符号化
#include <stdio.h>
const int red = 0;//定义好变量所代替的数字
const int yellow = 1;
const int green = 2;
//这样就不用用数字了, 可以直接使用单词
int main(int argc, char const *argv[])
{
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 = "unkown";
}
printf("你喜欢的颜色是%s\n", colorName);
return 0;
}
输出:
输入你喜欢颜色的代码:1
你喜欢的颜色是yellow
- 用符号而不是具体的数字来表示程序中的数字(这样就会增强可读性)
另一种方法:枚举
#include <stdio.h>
enum COLOR { red, yellow, green}; //也可以用枚举的方法,它会按顺序从0开始,往后加一;相当于red = 0; yellow = 1; green = 2
int main(int argc, char const *argv[])
{
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 = "unkown";
}
printf("你喜欢的颜色是%s\n", colorName);
return 0;
}
- 用枚举而不是定义独立的
const int
变量(相对方便一些)
枚举
- 枚举是一种用户定义的数据类型,它用关键字
enum
以如下语法来声明:
enum 枚举类型名字 {名字0, …, 名字n} ;
- 枚举类型名字通常并不真的使用,要用的是在大括号里的名字,因为它们就是就是常量符号,它们的类型是int (也只能是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); //必须带 enum 关键字(C语言必须要带)
int main(void)
{
enum color t = yellow;
// scanf("%d", &t);
f(t);
return 0;
}
void f(enum color c)
{
printf("%d\n", c);
}
输出:
1
- 枚举量可以作为值
- 枚举类型可以跟上
enum
作为类型 - 但是实际上是以整数
int
来做内部计算和外部输入输出的
套路:自动计数的枚举
#include <stdio.h>
enum COLOR { RED, YELLOW, GREEN, NumColors}; //用枚举的方法,它会按顺序从0开始,往后加一;相当于RED = 0; YELLOW = 1; GREEN = 2; NumColors = 3
int main()
{
int color = -1;
char *ColorName[NumColors] ={
"red", "yellow", "green"
};//这时候NumColors就比较有意思了, 它可以代表前面有3个元素, 而它的下标刚好是3
char *colorName = NULL;
printf("输入你喜欢颜色的代码:");
scanf("%d", &color);
if (color >= 0 && color < NumColors){
colorName = ColorName[color];
}else{
colorName = "unknown";
}
printf("你喜欢的颜色是%s\n", colorName);
return 0;
}
输出:
输入你喜欢颜色的代码:1
你喜欢的颜色是yellow
- 这样需要遍历所有的枚举量或者需要建立一个用枚举量做下标的数组的时候就很方便了
枚举量
- 声明枚举量的时候可以指定值, 定值后面如果没有指定值, 会以前面一个的值加一赋给它
enum COLOR { RED=1, YELLOW, GREEN = 5};
举个例子:
#include <stdio.h>
enum COLOR {RED = 1, YELLOW, GREEN = 5, NumCOLORS } ;
int main()
{
printf("code for RED is %d\n", RED);
printf("code for YELLOW is %d\n", YELLOW);
printf("code for GREEN is %d\n", GREEN);
printf("code for NumCOLORS is %d\n", NumCOLORS);
return 0;
}
输出:
code for RED is 1
code for YELLOW is 2
code for GREEN is 5
code for NumCOLORS is 6
枚举只是int
- 即使给枚举类型的变量赋不存在的整数值也没有任何
warning
或error
(新的编译器不会保存, 但旧的会)
#include <stdio.h>
enum COLOR {RED = 1, YELLOW, GREEN = 5, NumCOLORS } ;
int main()
{
enum COLOR color = 0;
printf("code for GREEN is %d\n", GREEN);
printf("code for color is %d\n", color);
return 0;
}
C语言中用枚举主要是用它来定义符号量而不是用来枚举,相对于其他语言, C语言在枚举这方面做得不够好.
- 虽然枚举类型可以当作类型使用,但是实际上很(bu)少(hao)用
- 如果有意义上排比的名字,用枚举比
const int
方便 - 枚举比宏(macro)好,因为枚举有int类型
结构
声明结构类型
#include <stdio.h>
int main()
{
struct date{
int month;
int day;
int year;
};
struct date today;
today.month = 8;
today.day = 28;
today.year = 2020;
printf("Today's data is %i-%i-%i.\n",
today.year, today.month, today.day);
return 0;
}
输出结果
Today's data is 2020-8-28.
注意:声明结构体时最后的分号;
不能漏掉
在函数内/外
- 和本地变量一样,在函数内部声明的结构类型只能在函数内部使用
- 所以通常在函数外部声明结构类型,这样就可以被多个函数所使用了
声明结构的形式
三种形式
举几个栗子:
//第一种:
struct point{
int x;
int y;
};//定义一个名为point的结构体
struct point p1, p2; //声明变量p1和p2的类型为point的结构体
//p1和p2都是point里面有x和y的值
//第二种
struct{
int x;
int y;
}p1, p2;//直接声明变量p1和p2的结构体类型,但该结构体没有定义名称,不方便后续其他变量对这种结构体类型的声明.
//p1和p2都是一种无名结构,里面有x和y
//第三种
struct point{
int x;
int y;
}p1, p2;//先定义一个名为point的结构体,再声明变量p1和p2类型为该结构体,相当于第一种的两步合为一步.
//p1和p2都是point里面有x和y的值
对于第一和第三种形式,都声明了结构point。但是第二种形式没有声明point,只是定义了两个变量
结构变量
struct date today;
today.month=06;
today.day=19;
today.year=2005;
内部分配内存空间
结构的初始化
#include <stdio.h>
struct date {
int month;
int day;
int year;
};
int main()
{
struct date today = {8,31,2020};
struct date thismonth = {.month=8, .year=2020};
printf("Today's date is %i-%i-%i.\n",
today.year, today.month, today.day);
printf("This month is %i-%i-%i.\n",
thismonth.year, thismonth.month, thismonth.day);
return 0;
}
输出结果:
Today's date is 2020-8-31.
This month is 2020-8-0.
没有初始化的一般都默认为0, 和数组差不多
结构成员
- 结构和数组有点像
- 数组用[]运算符和下标访问其成员
- a[0] = 10;
- 结构用
.
运算符和名字访问其成员- today.day
- student.firstName
- p1.x
- p1.y
结构运算
-
要访问整个结构,直接用结构变量的名字
-
对于整个结构,可以做赋值、取地址,也可以传递给函数参数
-
p1 = (struct point){5, 10}; // 相当于p1.x = 5; p1.y = 10;
-
p1 = p2; // 相当于p1.x = p2.x; p1.y = p2.y;
-
注意:数组无法做这两种运算, 赋值和传参 ! 详情请看数组那章 数组——C语言程序设计(六)
-
#include <stdio.h>
struct date{
int month;
int day;
int year;
};
int main()
{
struct date today;
today = (struct date){8,31,2020};
struct date day;
day = today;
printf("Today's date is %i-%i-%i.\n",
today.year, today.month, today.day);
printf("The day's date is %i-%i-%i.\n",
day.year, day.month, day.day);
return 0;
}
输出结果:
Today's date is 2020-8-31.
The day's date is 2020-8-31.
复合字面量
-
struct date today;
-
today = (struct date) {9,25,2004};//类型转换, 将大括号里面的数据转换成date类型,然后赋值给today
-
struct date day;
-
today = (struct date) {.month=9, .day=25,year=2004};
结构指针
- 和数组不同,结构变量的名字并不是结构变量的地址,必须使用
&
运算符 - struct date *pDate = &today;
#include <stdio.h>
struct date{
int month;
int day;
int year;
};
int main()
{
struct date today;
today = (struct date){8,31,2020};
struct date day;
struct date *pDate = &today;
printf("Today's date is %i-%i-%i.\n",
today.year, today.month, today.day);
printf("The day'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 2020-8-31.
The day's date is 37-8-0.
address of today is 000000000061FE0C
结构与函数
结构作为函数参数
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);
output(y);
}
void getStruct(struct point p)
{
scanf("%d", &p.x);
scanf("%d", &p.y);
printf("%d, %d\n", p.x, p.y);
}
void output(struct point p)
{
printf("%d, %d\n", p.x, p.y);
}
输出结果:
50 60
50, 60
0, 0
注意:该代码先是声明结构体y,并且成员的值都初始化为0,然后再把y的成员内的值传给函数getStruct()
, 但并不是把y传进函数内,只是将值传入,因此main函数内的结构体y的值没有变化
- 没有直接的方式可以一次
scanf
一个结构, 只能分开多个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
output(y);
}
struct point getStruct(void)
{
struct point p; //声明一个临时的结构体变量p
scanf("%d", &p.x);
scanf("%d", &p.y);
printf("%d, %d\n", p.x, p.y);
return p;//返回结构体p的值
}
void output(struct point p)
{
printf("%d, %d\n", p.x, p.y);
}
输出结果:
50 60
50, 60
50, 60
这时就可以将y的值改变.
结构指针作为参数
K & R : “If a large structure is to be passed to a function, it is generally more efficient to pass a pointer than to copy the whole structure”
翻译: K & R说过:如果要将一个大型结构传递给函数,传递一个指针通常比复制整个结构更有效.
指向结构的指针
struct data{
int month;
int day;
int year;
}myday;
struct date *p = &myday;
(*p).month = 12;
p->month = 12;
其中(*p).month = 12;
和 p->month = 12;
意思是一样的
*p
就是p指针所指的myday
那个结构变量, 为了简化书写,就写成p->month
, p所指向的那个结构的month
成员.
- 用
—>
表示指针所指的结构变量中的成员,->
也是一种运算符, 注意:必须是指针类型的变量才能用->
运算符.
#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()
{
struct point y = {0, 0};
getStruct(&y); //将y的地址作为参数传入,让指针p指向该地址,即把结构体y传入函数中;相当于*p=&y.
output(y);//只是将结构体y的值传入函数中
output(*getStruct(&y)); //将getStruct(&y)函数返回的指针p所指的地址,传入output函数中,就是将结构体y传入函数中,而不是值.
print(getStruct(&y));//将getStruct(&y)函数返回的指针p传给函数print()
getStruct(&y)->x = 0;//将返回的指针p所指的那个结构体y的成员x的值改为0
*getStruct(&y) = (struct point){1, 2};//更不可思议的是*getStruct(&y)可以作为左值来使用, 这时候左边已经不是变量了;该操作只是说明可以这么做,但在该代码中没有提现到用处, 这时候的指针所指的地址已经发生改变了, 再去打印y的成员值还是原来的值.
}
struct point* getStruct(struct point *p)
{
scanf("%d", &p->x);
scanf("%d", &p->y);
printf("%d, %d\n", p->x, p->y);
return p;//返回指针p
}//传入的参数的类型是一个结构体指针
void output(struct point p)
{
printf("%d, %d\n", p.x, p.y);
}
void print(const struct point *p) //const是常量,将打印函数的内容固定不做修改
{
printf("%d, %d\n", p->x, p->y);
}
输出结果:
50 60
50, 60
50, 60
结构指针参数
- 好处是传入传出只是一个指针的大小 (而不是整个结构体)
- 如果需要保护传入的结构不被函数修改
const struct point *p
- 返回传入的指针是一种套路
结构中的结构
结构数组
例如:
struct date dates[100];//一百个数组单元; 由100个date组成的数组,数组的每个元素是date的结构变量
struct date dates[] = {
{4,5,2000}, {2,4,3009}
}; //两个数组单元,并且已初始化
比如:时钟在某一刻的下一秒记录,如何用结构数组去实现?
#include <stdio.h>
struct time {
int hour;
int minute;
int second;
};
struct time timeUpdate(struct time now); //先声明函数timeUpdate, 其返回值类型是结构体类型,传入的参数也为time的结构体类型
int main(void)
{
struct time testTimes[5] = {
{11,59,36}, {12,0,0}, {23,59,59}, {1,34,59}, {12,35,22}
};//初始化有5个元素的testTimes数组
int i;
for(i=0; i<5; ++i){
printf("Time is %.2i:%.2i:%.2i",
testTimes[i].hour, testTimes[i].minute, testTimes[i].second);
testTimes[i] = timeUpdate(testTimes[i]);
printf("...one second later it's %.2i:%.2i:%.2i\n",
testTimes[i].hour, testTimes[i].minute, testTimes[i].second);
}//循环遍历数组中的5个元素,并且输出他们显示时刻的下一秒的值
//其中Time is %.2i:%.2i:%.2i为格式化输出,让输出结果保留两位数,只有一位数时,前面补零.
return 0;
}
struct time timeUpdate(struct time now) //timeUpdate函数的定义
{
++now.second; //增加一秒
if(now.second == 60){
now.second = 0;
++now.minute;
if(now.minute == 60){
now.minute = 0;
++now.hour;
if(now.hour == 24){
now.hour = 0;
}
}
}//if嵌套,用于时钟的秒,分,时的转化
return now;//返回结构变量
}
输出结果:
Time is 11:59:36...one second later it's 11:59:37
Time is 12:00:00...one second later it's 12:00:01
Time is 23:59:59...one second later it's 00:00:00
Time is 01:34:59...one second later it's 01:35:00
Time is 12:35:22...one second later it's 12:35:23
嵌套的结构
-
可以结构中嵌套结构
-
例如:
-
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
- 注意:
但是没有(因为rp->pt1->x
pt1
不是指针) ;能用->
这个运算符的只能是指针变量.
- 注意:
结构中的结构的数组
#include <stdio.h>
struct point{
int x;
int y;
};
struct rectangle {
struct point p1;
struct point p2;
};
void printRect(struct rectangle r)
{
printf("<%d, %d> to <%d, %d>\n", r.p1.x, r.p1.y, r.p2.x, r.p2.y);
}
int main(int argc, char const *argv[])
{
int i;
struct rectangle rects[ ] = {
{{1, 2}, {3, 4}},
{{5, 6}, {7, 8}}
}; // 2 个 rectangles
for(i=0;i<2;i++) printRect(rects[i]);
return 0;
}
<1, 2> to <3, 4>
<5, 6> to <7, 8>
类型定义
自定义数据类型 : typedef
-
C语言提供了一个叫做
typedef
的功能来声明一个已有的数据类型的新名字。比如:typedef int Length;
使得
Length
成为int
类型的别名。 -
这样,
Length
这个名字就可以替代int
出现在变量定义和参数声明的地方.Length a,b,len;
//Length就可以代替int去定义这个整型的变量Length numbers[10];
//这样Length代替了 int去声明这个数组是整型的
-
#include <stdio.h> typedef int m;//定义int的别名叫m, 之后就可以替代int 直接用m去定义整型变量 void main() { m a = 10; m b = 11; printf("%d %d", a,b); }
输出结果:
10 11
-
声明新的类型的名字
- 新的名字是某种类型的别名
- 改善了程序的可读性
-
#include <stdio.h> typedef long int64_t; //重载已有的类型的名字,新名字的含义更清晰,具有可移植性 typedef struct ADate{ //定义一个名为ADate的结构变量 int month; int day; int year; }Date; //简化复杂的名字; 自定义类型定义,将ADate结构体类型定义给Date ,因此Date是一种类型名 int main() { int64_t a = 1000000000; Date b = {9, 1, 2005}; printf("%d\n", a); printf("%d ",b.month); printf("%d ",b.day); printf("%d\n",b.year); return 0; }
输出结果:
1000000000 9 1 2005
-
注意:
-
struct ADate{ int month; int day; int year; }Date; Date.month = 3; Date.day = 2; Date.year = 2021;
-
与上面的区别是 : 少了
typedef
关键字, 这就变成结构体的定义, 就是定义一个结构体名为Adate
, 然后声明Date
结构体变量,这是的Date
是可以直接用的; 而如果自定义类型, 此时这个Date不能直接用, 得去声明其他变量,Date也相当于用户自定义的关键字. 类似于int
关键字不能做变量来用.
-
-
typedef [类型名] [新自定义名] ;
typedef int Length; //Length就等价于int类型
typedef *char[10] String;//String是10个字符串的数组的类型
//第一种:
typedef struct node{
int data;
struct node *next;
}aNode;
//第二种:
typedef struct node aNode; // 这样⽤用aNode就可以代替 struct node
struct node{
int data;
struct node *next;
};
联合
- 存储
- 所有的成员共享一个空间 (联合起来使用一个空间)
- 同一时间只有一个成员是有效的
union
的大小是其最大的成员 ;sizeof(union …) =sizeof(每个成员)的最大值
- 初始化
- 对第一个成员做初始化
union AnElt{
int i;
char c;
} elt1, elt2;//elt1, elt2这两个成员共用一块内存空间
elt1.i = 4;
elt2.c = ’a’;
elt2.i = 0xDEADBEEF; //elt2.i会覆盖elt1.i的数据
-
union
自己并不知道当时其中哪个成员是有效的- 程序怎么能知道当时
elt1
和elt2
里面到底是int
还是char
? - 最好的答案:另一个变量来表达这个事情
- 程序怎么能知道当时
union的用处
#include <stdio.h>
typedef union{
int i;
char ch[sizeof(int)];
}CHI;
int main()
{
CHI chi;
int i;
chi.i = 1234;
for(i=0;i<sizeof(int);i++){
printf("%02hhX", chi.ch[i]); //以十六进制的形式输出
}
printf("\n");
return 0;
}
输出结果:
FFD2040000
注: 1234 1234 1234的十六进制是 0 X 04 D 2 0X04D2 0X04D2