第四夜:存储类型、结构体、链表

余生疯狂做产品!跟这个世界继续死磕!

今晚复习跟数据结构息息相关的几个知识点。

存储类型

C语言中有以下类型的存储方式:

  • auto
    • auto用于声明变量的生存期为自动,即将不在任何类、结构、枚举、联合和函数中定义的变量视为全局变量,而在函数中定义的变量视为局部变量。
    • 因为auto是C语言变量默认的存储类型,所以一般不写。
  • register
    • register要求编译器尽可能的将register类型的变量存在CPU内部寄存器中而不是通过内存寻址的RAM中,以便提高效率。
  • static
    • static指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。使用 static修饰局部变量可以在函数调用之间保持局部变量的值。
    • static修饰符也可以应用于全局变量,当 static修饰全局变量时,会使变量的作用域限制在声明它的文件内。
    • static是全局变量的默认存储类。
  • extern
    • extern存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。当使用extern时,对于无法初始化的变量,会把变量名指向一个之前定义过的存储位置。
    • 有多个文件且定义了一个可以在其他文件中使用的全局变量或函数时,可以在其他文件中使用extern来得到已定义的变量或函数的引用。extern是用来在另一个文件中声明一个全局变量或函数。
  • const
    • const要求其所修饰的对象为常量,不可对其修改和二次赋值操作(不能作为左值出现)。
  • volatile 修饰符
    • volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,告诉编译器对该变量不做优化,都会直接从变量内存地址中读取数据,从而可以提供对特殊地址的稳定访问。

C语言const与static的区别

  • const 就是只读,只在声明中使用;
  • static 一般有两个作用,规定作用域和存储方式。

对于局部变量, static规定其为静态存储方式, 每次调用的初始值为上一次调用的值,调用结束后存储空间不释放;
对于全局变量, 如果以文件划分作用域的话,此变量只在当前文件可见; 对于static函数也是在当前模块内函数可见;
static const 是上面两者的合集。

结构体

结构体的声明方式:

1
2
struct 结构体名
{成员列表};

结构体声明样例:

1
2
3
4
5
6
7
8
9
struct student
{
int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
};

声明的结构体相当于一个模型,但其中并无具体数据,系统对之也不分配实际内存单元,为了能在程序中使用结构类型的数据,应当定义结构体类型的变量,并在其中存放具体的数据。

结构体的嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct date{
int month;
int day;
int year;
}

struct student{
int num;
char name[20];
char sex;
int age;
struct date birthday;
char addr[30];
}student1, student2;

结构体变量的赋值与引用

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <string.h>

struct student{
int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
};

int main (){
struct student s1;
s1.num = 1;
strcpy(s1.name,"xiaoming");//不能直接用s1.name="xiaoming"这种方式。
printf("%d: %s", s1.num,s1.name);
}

数组初始化赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

struct student{
int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
};

int main (){
struct student s1 = {1,"xiaoming",'F',10,99.9,"Beijing"};
// 单引号用于字符,双引号用于字符串(字符数组)。
printf("%d: %s", s1.num,s1.name);
}

.->的区别
一般情况下用.只需要声明一个结构体,格式是结构体类型名+结构体名。然后用结构体名加.加域名就可以引用域 了。因为自动分配了结构体的内存。如同int a一样。
而用->则要声明一个结构体的指针,还要手动开辟一个该结构体的内存,然后把返回的指针给声明的结构体指针,才能用->正确引用。

结构体数组

1
2
3
4
5
6
7
8
9
10
struct student{
int mum;
char name[20];
char sex;
int age;
float score;
char addr[30];
} stu[3] = {{10101,"Tom", 'M', 18, 87.5, "Beijing Road"},
{10102,"Bob", 'M', 18, 87.5, "Beijing Road"},
{10103,"Jim", 'M', 18, 87.5, "Beijing Road"} };

结构体变量指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

struct student {
long num;
char name[20];
char sex;
float score;
};

void main(){
struct student stu_1;
struct student *p;
p = &stu_1;
stu_1.num = 89101;
strcpy(stu_1.name, "Li Lin");
stu_1.sex = 'M';
stu_1.score = 89.5;
printf("NO. :%ld\nname: %s\nsex:%c\nscore:%f\n", stu_1.num, stu_1.name, stu_1.sex, stu_1.score);
printf("NO. :%ld\nname: %s\nsex:%c\nscore:%f\n", (*p).num, (*p).name, (*p).sex, (*p).score);
printf("NO. :%ld\nname: %s\nsex:%c\nscore:%f\n",p->num, p->name, p->sex, p->score);
system("pause");
}

结构体数组指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <stdlib.h>

#define FORMAT "%d\t%-3s\t%1c\t%2d\n"

struct student{
int num;
char name[20];
char sex;
int age;
};

struct student stu[3] = {
{1, "Tom", 'M', 18},
{2, "Kim", 'M', 19},
{3, "Jim", 'F', 20}
};

int main(){
struct student *p;
printf("No.\tname\tsex\tage\n");
for(p=stu; p<stu+3;p++)
printf(FORMAT, p->num, p->name, p->sex, p->age);
}

链表

链表是一种常见的数据结构,用于动态地进行存储分配。
以单向链表为例,它有一个头指针变量,存放一个地址,该地址指向一个元素。链表中每一个元素称为结点,每个结点都应包括两个部分,一为用户需要用的实际数据,二为下一个结点的地址。可以看出,头指针head指向第一个元素,第一个元素又指向第二个元素,直到最后一个元素,最后一个元素不再指向其他元素,称之为表尾,它的地址部分放一个NULL(表示空地址),链表到此结束。

单向链表示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <stdlib.h>

struct student{
long num;
float score;
struct student *next;
};

void main(){
struct student s1, s2, s3, *head, *p;
s1.num = 1; s1.score = 89.5;
s2.num = 2; s2.score = 90;
s3.num = 3; s3.score = 85;
head = &s1; //将结点 a 的起始地址赋给头指针 head
s1.next = &s2; //将结点 b 的起始地址赋给 a 结点的 next 成员
s2.next = &s3;
s3.next = NULL; // c 结点的 next 成员不存放其他结点地址
p = head;//使 p 指针指向 a 结点
do{
printf("%ld %5.1f\n", p->num, p->score);// 输出 p 指向的结点的数据
p = p->next; //使 p 指向下一结点
}while(p != NULL);//输出完 c 结点后 p 的值为 NULL
}

链表类型

链表类型有单向链表双向链表循环链表等类型。

双向链表是指在单向链表的基础上,每个链表元素既有指向下一个元素的指针,又有指向前一个元素的指针,其中每个结点都有两种指针,即front和tail,front指针指向左边结点,tail指针指向右边结点。

循环链表指的是在单向链表和双向链表的基础上,将两种链表的最后一个结点指向第一个结点从而实现循环。

Windows堆表中的“快表(快速单向链表)”就是使用了单向链表作为其数据结构,“空表(空闲双向链表)”则是一种双向链表。

参考资料

数据结构之队列、栈和链表(一)
数据结构之队列、栈和链表(二)

第五夜:C语言标准库、错误处理和调试、输入输出 第三夜:指针和数组
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×