按照中国大学MOOC上浙江大学翁恺老师主讲的版本所作,B站上也有资源。原课程链接如下:
https://www.icourse163.org/course/ZJU-9001
由于是大三抽空回头整理的,所以可能前五章会记的内容比较简略。此外,作为选学内容的A0:ACLLib的基本图形函数和链表两章也没有做。西电的考试是机试,理论上学到结构体就能够应付考试了,但为了以后的学习考虑建议全学。
其他各章节的链接如下:
常量符号化
用符号而不是具体的数字来表示程序中的数字
枚举
用枚举而不是定义独立的const int
变量
枚举是一种用户定义的数据类型,它用关键字enum
以如下语法来声明:enum 枚举类型名字 { 名字0, ..., 名字n };
枚举类型名字通常并不真的使用,要用的是在大括号里的名字,因为它们就是常量符号,它们的类型是int
,值依次从0到n。如:enum colors { red, yellow, green };
就创建了三个常量,red
的值是0,yellow
是1,而green
是2
当需要一些可以排列起来的常量值时,定义枚举的意义就是给了这些常量值名字
示例:
#include
enum color { red, yellow, green };
void f(enum color c);
int main(void)
{
enum color t = red;
scanf("%d", &t);
f(t);
return 0;
}
void f(enum color c)
{
printf("%d\n", c);
}
枚举量可以作为值
枚举类型可以跟上enum
作为类型
但是实际上是以整数来做内部计算和外部输入输出的
套路:自动计数的枚举
示例:
#include
enum COLOR {RED, YELLOW, GREEN, NumCOLORS};
int main(int argc, char const *argv[])
{
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;
}
这样需要遍历所有的枚举量或者需要建立一个用枚举量做下表的数组的时候就很方便了
枚举量
声明枚举量的时候可以指定值
示例:
#include
enum COLOR {RED=1, YELLOW, GREEN=5, NumCOLORS};
int main(int argc, char const *argv[])
{
printf("code for YELLOW is %d\n", YELLOW);
return 0;
}
code for YELLOW is 2
枚举只是int
即使给枚举类型的变量赋不存在的整数值也没有任何warning或error
示例:
#include
enum COLOR {RED=1, YELLOW, GREEN=5, NumCOLORS};
int main(int argc, char const *argv[])
{
enum COLOR color = 0;
printf("code for GREEN is %d\n", GREEN);
printf("and color is %d\n", color);
return 0;
}
code for GREEN is 5
and color is 0
虽然枚举类型可以当作类型使用,但是实际上不好用
如果有意义上排比的名字,用枚举比const int
方便
枚举比宏(macro
)好,因为枚举有int
类型
声明结构类型
和本地变量一样,在函数内部声明的结构类型只能在函数内部使用。所以通常在函数外部声明结构类型,这样就可以被多个函数所使用了
示例:
#include
struct date
{
int month;
int day;
int year;
};
int main(int argc, char const *argv[])
{
struct date today;
today.day = 07;
today.month = 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-31-7.
声明结构的形式
struct point {
int x;
int y;
};
struct point p1,p2;
struct {
int x;
int y;
} p1,p2;
p1
和p2
并不是类型的名字,而是前面这种无名结构类型的两个变量
struct point {
int x;
int y;
} p1,p2;
声明结构pointer
,并且定义这种结构的两个变量p1
和p2
对于第一种和第三种形式,都声明了结构point
。但是第二种形式没有声明point
,至是定义了两个变量
结构变量
struct date today;
today.month=06;
today.day=19;
today.year=2005;
结构的初始化
#include
struct date
{
int month;
int day;
int year;
};
int main(int argc, char const *argv[])
{
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("This month is %i-%i-%i.\n",
thismonth.year,thismonth.month,thismonth.day);
return 0;
}
Today's date is 2014-7-31.
This month is 2014-7-0.
结构成员
结构和数组有点像,数组用[]
运算符和下标访问其成员,结构用.
运算符和名字访问其成员(如: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;
结构指针
和数组不同,结构变量的名字并不是结构变量的地址,必须使用&
运算 如:struct date *pDate = &today;
结构作为函数参数
int numberofDays(struct date d)
整个结构可以作为函数的值传入函数,这时候是在函数内新建一个结构变量,并复制调用者的结构的值
也可以返回一个结构
这与数组完全不同
示例:
#include
#include
struct date {
int month;
int day;
int year;
};
bool isLeap(struct date d);
int numberofDays(struct date d);
int main(int argc, char const *argv[])
{
struct date today, tomorrow;
printf("Enter today's date (mm dd yyyy):");
scanf("%i %i %i", &today.month, &today.day, &today.year);
if( today.day != numberofDays(today) ) {
tomorrow.month = today.month;
tomorrow.day = today.day+1;
tomorrow.year = today.year;
} else if ( today.month == 12 ) {
tomorrow.month = 1;
tomorrow.day = 1;
tomorrow.year = today.year+1;
} else {
tomorrow.month = today.month+1;
tomorrow.day = 1;
tomorrow.year = today.year;
}
printf("Tomorrow's date is %i-%i-%i.\n",
tomorrow.year, tomorrow.month, tomorrow.day);
return 0;
}
int numberofDays(struct date d)
{
int days;
const int daysPerMonth[12] =
{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if( d.month == 2 && isLeap(d) )
days=29;
else
days=daysPerMonth[d.month-1];
return days;
}
bool isLeap(struct date d)
{
bool leap = false;
if ( (d.year %4 ==0 && d.year %100 !=0) || d.year%400 == 0 )
leap=true;
return leap;
}
输入结构
没有直接的方式可以一次scanf
一个结构
示例:
如果我们打算写一个函数来读入结构
#include
struct point {
int x;
int y;
};
void getStruct(struct point);
void output(struct point);
int main(int argc, char const *argv[])
{
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", p.x, p.y);
}
void output(struct point p)
{
printf("%d, %d", p.x, p.y);
}
C在函数调用时是传值的,所以函数中的p
与main
中的y
是不同的,在函数读入了p
的数值之后,没有任何东西回到了main
,所以y
还是{0,0}
解决的方案
之前的方案,把一个结构传入了函数,然后在函数中操作,但是没有返回回去。问题在于传入函数的是外面那个结构的克隆体,而不是指针。传入指针和传入数组是不同的
在这个输入函数中,完全可以创建一个临时的结构变量,然后把这个结构返回给调用者
示例:
void main()
{
struct point y = {0, 0};
y = inputPoint();
output(y);
}
struct point inputPoint()
{
struct point temp;
scanf("%d",&temp.x);
scanf("%d",&temp.y);
return temp;
}
指向结构的指针
用->
表示指针所指的结构变量中的成员
示例:
struct date {
int month;
int day;
int year;
} myday;
struct date *p = &myday;
(*p).month = 12;
p->month = 12;
示例2:
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;
}
示例3:
#include
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);
output(y);
output(*getStruct(&y));
print(getStruct(&y));
getStruct(&y)->x = 0;
*getStruct(&y) = (struct point){1,2};
}
struct point* getStruct(struct point *p)
{
scanf("%d", &p->x);
scanf("%d", &p->y);
printf("%d, %d", p->x, p->y);
return p;
}
void output(struct point p)
{
printf("%d, %d", p.x, p.y);
}
void print(const struct point *p)
{
printf("%d, %d", p->x, p->y);
}
结构数组
struct date dates[100];
struct date dates[] = {
{4,5,2005},{2,4,2005};
}
示例:
#include
struct time {
int hour;
int minutes;
int seconds;
};
struct time timeUpdate(struct time now);
int main(void)
{
struct time testTimes[] = {
{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;
}
结构中的结构
struct dateAndTime {
struct date sdate;
struct time stime;
};
嵌套的结构
示例:
struct point {
int x;
int y;
};
struct rectangle {
struct point pt1;
struct point pt2;
};
struct rectangle r;
r.pt1.x = 2;
r.pt2.y = 3;
struct rectangle r,*rp;
rp = &r;
// 下面的四种形式是等价的
r.pt1.x = 2;
rp->pt1.x = 2;
(r.pt1).x = 2;
(rp->pt1).x = 2;
结构中的结构的数组
示例:
#include
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 retangle rects[] = {
{{1, 2}, {3, 4}},
{{5, 6}, {7, 8}}
}; // 2 rectangle
for ( i=0; i<2; i++ ) {
printRect(rects[i]);
}
}
自定义数据类型(typedef
)
C语言提供了一个叫做typedef
的功能来声明一个已有的数据类型的新名字。比如:typedef int Length;
使得Length
成为int
类型的别名。这样,Length
这个名字就可以代替int
出现在变量定义和参数声明的地方了:Length a,b,len;
,Length numbers[10];
typedef
声明了新的类型的名字,新的名字是某种类型的别名,改善了程序的可读性
最后一个单词是新的名字,出现在typedef
和新的名字之间的是原来的类型
示例:
typedef struct {
int month;
int day;
int year;
} Date;
表示不关心struct
叫什么,将这样的struct
命名为Date
。如果没有typedef
,表示一个没有名字的struct
有一个变量叫Date
示例2:
typedef int Length; // Length就等价于int类型
typedef *char[10] Strings; // Strings是10个字符串的数组的类型
// typedef char* Strings[10]; ?
typedef struct node {
int data;
struct node *next;
} aNode;
typedef struct node aNode; //这样用aNode就可以代替struct node
存储时所有的成员共享一个空间,同一时刻只有一个成员是有效的,union
大小是其最大的成员
初始化对第一个成员做初始化
示例:
选择成员是一个int i
还是一个char c
示例2:
用Union得到一个数据内部的各个字节
#include
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]);
}
printf("\n");
return 0;
}
D2040000
%02hhX
表示期望输出16进制,并且就是1个字节不做扩展,显示为2个16进制数字,不足补零
我们现在用的x86 CPU采用小端存储,内存中的一个数据低位在前