大家好,上一篇文章给大家分享了有关指针的内容,今天,我主要想给大家讲一讲有关结构体之间的知识
1. 结构体的声明
1.1 结构体的相关知识
结构体,我们可以把它理解为一些值的集合。
在此之前,我们学习了很多数据类型,包括整型,浮点型,字符型等等,但是,如果我们想有一个新的数据类型,并包含所有的这些特征,应该怎么做呢?
很简单,把它们打包到一起,定义一个新的数据类型。
拿学生举例,学生不仅有身高,还有性别的区分,年龄,名字等等,所以我们可以把它们都放在一起,方便之后的使用和修改,于是C语言中就有了结构体的这个概念。
1.2 结构体的声明
struct Student
{
// 成员变量
int age;
char name[20];
char sex[5];
....
};
如上图所示,我们实现了一个结构体变量,这个变量的名字叫做Student,我们把其内部包含的所有变量称作成员变量,所以,学生的结构体类型中包含三个成员变量,分别是年龄,姓名以及性别。
切记,结构体后面的分号不能省略。
1.3 结构体成员的类型
既然是结构体了,其内部成员变量的数据类型就可以是任意的,包括整数,指针,字符,数组等等
1.4 结构体变量的定义和初始化
有了结构体类型,那么如何进行定义并进行初始化呢,其实很简单。
1.4.1 定义
struct Point
{
int x;
int y
};
以一个点为例,他其中包含横坐标以及纵坐标,那我们在抵不过一变量的时候可以直接定义,就像这样:
struct Point
{
int x;
int y;
}p1; // 声明类型的同时定义变量
我们可以在声明类型的同时进行对变量的定义,但是要注意,这个时候的 p1 变量就是一个全局变量。
我们也可以声明的时候先不定义,在主函数中定义变量,就像这样:
struct Point
{
int x;
int y
};
int main()
{
struct Point p2;
}
我们同时也定义了一个结构体变量 p2,不同的是,p2就是一个局部变量,开辟在栈空间。
注意:struct关键字千万不可省略。
1.4.2 结构体变量的初始化
其实结构体变量的初始化也很简单,就是在定义的同时对成员变量进行一些赋值就可以了,例如:
struct Point
{
int x;
int y
};
int main()
{
struct Point p2 = {3, 4};
return 0;
}
为了偷懒,我就直接引用了上文的例子,将p2的横坐标赋值为3,纵坐标赋值为4。
当然了,结构体也可以嵌套初始化,我们以链表为例。
struct Node
{
int data;
struct Node* next;
}p = {2, NULL};
这就是典型的结构体嵌套的例子,并创建了一个局部变量p,对它进行了初始化。
那有同学就会问了,就有没有简单一点的方法吗,每次都写struct感觉好麻烦啊。
答案当然是肯定的,那么代码应该怎么改呢?
这就不得不提到我们C语言的关键字typedef了,他可以减少代码的复用,举个例子。
我们知道C语言的长整型是long long,为了简洁,我们可以这样做
typedef long long LL;
这么做的好处是,以后我们可以用LL来代替long long,从而简化代码,让人看起来更舒服。
那么,typedef用在结构体上也是同理
typedef struct Point
{
int x;
int y;
}P;
在加上typedef关键词后,以后我们可以用P来代表整个结构体变量,就像这样:
int main()
{
P p1 = {1, 2};
}
怎么样,是不是感觉很简单呢?
但是要注意的是,加上typedef之后,P就代表这个结构体变量,而非是我们之前创建的一个全局变量,这点一定不要弄混,千万要看清楚struct结构体定义的时候是否加上了typedef关键字,防止出现错误。
2. 结构体成员的访问
我们创建了结构体之后,那么如何对结构体内部的成员变量进行访问呢?
结构体成员的变量是通过(.)操作符来实现的,它支持两个操作数,前面的操作数是结构体变量的名称,后面的变量是你想要访问的成员变量
struct Student
{
char name[20];
int age;
}
int main()
{
struct Student s;
}
我们可以看到,结构体变量s中包含两个成员变量,分别是学生的姓名以及学生的年龄,于是我们可以通过(.)操作符来访问,就像这样:
s.age = 18;
s.name = "张三";
s中的年龄因为是一个整型变量,所以我们直接用(.)访问之后,并把它初始化为18。
但是s中的姓名是一个字符数组,如果我们也直接这么赋值的话,会出现什么问题呢?
我们会收到编译器的提示,意思就是说赋值号的左边必须为可修改的变量,那么这种错误出现的根源是什么呢?
我们可以看到,姓名是由一个字符数组来代替,如果我们直接访问name的话,由于name是一个数组,所以name(数组名)代表的就是数组首元素的地址,他是一个指针常量,是不可以被改变的, 那么我们应该怎么做呢?
这就不得不提到一个库函数了,它就是strcpy,作用是复制字符串,他有两个参数,第一个参数是字符串第一个字符的地址,第二个参数是字符串的内容,具体实现如下:
struct S s;
strcpy(s.name, "zhangsan");//使用.访问name成员
这样,编译器就不会报错了,但是要注意,使用strcpy函数之前要加上对应的<string.h> 头文件哦
3. 结构体传参
我们先回顾一下之前学过有关函数的知识,我们知道,形参是实参的一份临时拷贝,形参的改变不会影响实参,当我们想要通过形参来改变实参的时候,我们需要传的是实参的指针,并且形参用一个指针变量来接受,以此达到改变实参的结果,比如:
我们想交换两个数 a 和 b。
#include <stdio.h>
void Swap(int* pa, int* pb) // 用两个指针变量来接收
{
int temp = *pa;
*pa = *pb;
*pb = temp;
}
int main()
{
int a = 3;
int b = 4;
// 交换 a 和 b
Swap(&a, &b); // 传的是 a 和 b 的地址
printf("a = %d, b = %d", a, b);
}
打印结果如下:
结构体传参也是同理,如果我们想对实际的结构体变量中的内容进行修改,我们也需要传递结构体变量的指针,代码如下:
struct Stu
{
char name[20];
int age;
};
void print(struct Stu* ps)
{
printf("name = %s age = %d\n", (*ps).name, (*ps).age);
//使用结构体指针访问指向对象的成员
printf("name = %s age = %d\n", ps->name, ps->age);
}
int main()
{
struct Stu s = {"zhangsan", 20};
print(&s);//结构体地址传参
return 0;
}
这里我们就不得不提到一个新的操作符(->),他可以通过结构体变量的指针来访问成员变量,如上代码所示,我们将结构体变量的指针传递过去,用一个结构体指针变量来接收,并通过解引用的方式来找到所对应的成员变量,我们可以通过 (*ps).name 来对姓名这个成员变量来进行赋值,但是,为了语法的简单,我们也可以直接使用(->)操作符,通过 ps ->name 来访问成员变量,所以本质上 (*ps).name == ps->name,但还是推荐大家写第二种。
下面,我给出一段代码:
struct S
{
int data[1000];
int num;
};
struct S s = {{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
print1(s); //传结构体
print2(&s); //传地址
return 0;
}
大家感觉,print1函数好还是print2函数好呢?
这里的好指的就是程序使用的空间最少,代码执行效率最高。
毫无疑问,print2函数是更好的,原因如下: