C语言高级特性
结构体
终于熬过了最难的一个部分,后面的内容就相对简单多了,我们接着来看结构体。
我们之前认识过很多种数据类型,包括整数、小数、字符、数组等,通过使用对应的数据类型,我们就可以很轻松地将我们的数据进行保存了,但是有些时候,这种简单类型很难去表示一些复杂结构。
创建和使用结构体
结构体 === 类
比如现在我们要保存100个学生的信息(学生信息包括学号、姓名、年龄)我们发现似乎找不到一种数据类型能够同时保存这三种数据(数组虽然能保存一些列的元素,但是只能保存同种类型的)。但是如果把它们拆开单独存在,就可以使用对应的类型存放了,不过这样也太不方便了吧,这些数据应该是捆绑在一起的,而不是单独地去存放。所以,为了解决这种问题,C语言提供了结构体类型,它能够将多种类型的数据集结到一起,让他们形成一个整体。
struct Student { //使用 (struct关键字 + 结构体类型名称) 来声明结构体类型,这种类型是我们自己创建的(同样也可以作为函数的参数、返回值之类的)
int id; //结构体中可以包含多个不同类型的数据,这些数据共同组成了整个结构体类型(当然结构体内部也能包含结构体类型的变量)
int age;
string name; //用户名可以用指针指向一个字符串,也可以用char数组来存,如果是指针的话,那么数据不会存在结构体中,只会存放字符串的地址,但是如果是数组的话,数据会存放在结构体中
};
定义好结构体后,我们只需要使用结构体名称作为类型就可以创建一个结构体变量了:
struct Student {
int id;
int age;
string name;
};
int main() {
//类型需要写为 Student,后面就是变量名称
Student s = {1, 18, "小明"}; //结构体包含多种类型的数据(它们是一个整体),只需要把这些数据依次写好放在花括号里面就行了
}
struct Student {
int id;
int age;
string name;
} s; //也可以直接在花括号后面写上变量名称(多个用逗号隔开),声明一个全局变量
这样我们就创建好了一个结构体变量,而这个结构体表示的就是学号为1、年龄18、名称为小明的结构体数据了。
当然,结构体的初始化需要注意:
struct Student s = {1, 18}; //如果只写一半,那么只会初始化其中一部分数据,剩余的内容相当于没有初始值,跟数组是一样的
struct Student s = {1, .name = "小红"}; //也可以指定去初始化哪一个属性 .变量名称 = 初始值
那么现在我们拿到结构体变量之后,怎么去访问结构体内部存储的各种数据呢?
printf("id = %d, age = %d, name = %s", s.id, s.age, s.name); //结构体变量.数据名称 (这里.也是一种运算符) 就可以访问结构体中存放的对应的数据了
是不是很简单?当然我们也可以通过同样的方式对结构体中的数据进行修改:
int main() {
struct Student s = {1, 18, "小明"};
s.name = "小红";
s.age = 17;
printf("id = %d, age = %d, name = %s", s.id, s.age, s.name);
}
结构体数组和指针
前面我们介绍了结构体,现在我们可以将各种类型的数据全部安排到结构体中一起存放了。
不过仅仅只是使用结构体,还不够,我们可能需要保存很多个学生的信息,所以我们需要使用结构体类型的数组来进行保存:
struct Student {
int id;
int age;
string name;
};
int main() {
Student arr[3] = {{1, 18, "小明"}, //声明一个结构体类型的数组,其实和基本类型声明数组是一样的
{2, 17, "小红"}, //多个结构体数据用逗号隔开
{3, 18, "小刚"}};
}
那么现在如果我们想要访问数组中第二个结构体的名称属性,该怎么做呢?
int main() {
Student arr[3] = {{1, 18, "小明"},
{2, 17, "小红"},
{3, 18, "小刚"}};
printf("%s", arr[1].name); //先通过arr[1]拿到第二个结构体,然后再通过同样的方式 .数据名称 就可以拿到对应的值了
}
当然,除了数组之外,我们可以创建一个指向结构体的指针。
int main() {
Student student = {1, 18, "小明"};
Student * p = &student; //同样的,类型后面加上*就是一个结构体类型的指针了
}
我们拿到结构体类型的指针后,实际上指向的就是结构体对应的内存地址,和之前一样,我们也可以通过地址去访问结构体中的数据:
int main() {
Student student = {1, 18, "小明"};
Student * p = &student;
printf("%s", (*p).name); //由于.运算符优先级更高,所以需要先使用*p得到地址上的值,然后再去访问对应数据
}
不过这样写起来太累了,我们可以使用简便写法:
printf("%s", p->name); //使用 -> 运算符来快速将指针所指结构体的对应数据取出
我们来看看结构体作为参数在函数之间进行传递时会经历什么:
void test(Student student){
student.age = 19; //我们对传入的结构体中的年龄进行修改
}
int main() {
Student student = {1, 18, "小明"};
test(student);
printf("%d", student.age); //最后会是修改后的值吗?
}
可以看到在其他函数中对结构体内容的修改并没有对外面的结构体生效,因此,实际上结构体也是值传递。我们修改的只是另一个函数中的局部变量而已。
所以如果我们需要再另一个函数中处理外部的结构体,需要传递指针:
void test(Student * student){ //这里使用指针,那么现在就可以指向外部的结构体了
student->age = 19; //别忘了指针怎么访问结构体内部数据的
}
int main() {
Student student = {1, 18, "小明"};
test(&student); //传递结构体的地址过去
printf("%d", student.age);
}
当然一般情况下推荐传递结构体的指针,而不是直接进行值传递,因为如果结构体非常大的话,光是数据拷贝就需要花费很大的精力,并且某些情况下我们可能根本用不到结构体中的所有数据,所以完全没必要浪费空间,使用指针反而是一种更好的方式。
结束语
到这里,我们C语言的学习就结束了,感谢各位小伙伴一直以来的支持,希望在下一期视频中,还能见到各位的身影。
之后我们还会开放C语言系列数据结构篇教程,敬请期待。