C语言初阶-初识结构体

目录

1.结构体的声明

1.1结构体的基础知识

1.2结构体的声明

1.3结构体成员的类型

1.4结构体变量的定义和初始化

2.结构体成员的访问

3.结构体传参


1.结构体的声明

1.1结构体的基础知识

结构是一些值的集合,这些值称为成员变量,结构的每个成员可以使不同类型的变量

生活中描述:

人:名字+性别+年龄+身高+身份证号码+地址...

书:书名+作者+出版社+定价+书号...

这些复杂对象不能通过内置类型直接描述和表示,于是就有了结构体。

1.2结构体的声明

例如描述一个学生:

struct Stu
{
	char name[20];//姓名
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
};//注意分号不能丢

其中name,age,sex,id是结构体的成员变量,用来描述结构体对象的相关属性。

上述的strcut Stu是我们的创建的结构体类型,那在主函数中我们就可以通过这个类型创建结构体变量。

#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
struct Stu
{
	char name[20];//姓名
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
};//注意分号不能丢
int main()
{
	struct Stu s1;
	return 0;
}

上述代码中的s1就是我们创建的结构体变量,当然我们也有另外一种方式,就是在创建结构体类型struct Stu的时候,直接在它的大括号外面捎带着创建结构体变量s2,s3和s4。(如下)

#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
struct Stu
{
	char name[20];//姓名
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
}s2, s3, s4;//s2,s3,s4就是结构体变量 - 全局变量
int main()
{
	struct Stu s1;//局部变量
	return 0;
}

代码中的s1,s2,s3,s4都是结构体变量,只不过s1是局部变量,s2,s3和s4是全局变量。

我们也可以在创建结构体类型时,用 typedef 对其进行类型重命名,

#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
typedef struct Stu
{
	char name[20];//姓名
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
}Stu;//类型重命名为Stu
int main()
{
	struct Stu s1;//局部变量
	Stu s2;
	return 0;
}

上述代码用 typedef struct Stu重命名为Stu,那在主函数中创建结构体变量时就可以直接用Stu来代替struct Stu,代码中的s2就是这样创建的。

注意:如果使用 typedef 的时候,结构体类型的大括号外面不是结构体变量,而是类型的重命名

1.3结构体成员的类型

结构体的成员可以是标量、数组、指针,甚至是其他结构体

struct S
{
	int a;
	char arr[5];
	int* p;
};
struct B
{
	char ch[10];
	struct S s;
	double d;
};
int main()
{
	return 0;
}

1.4结构体变量的定义和初始化

struct S
{
	int a;
	char arr[5];
	int* p;
}s1 = {100,"bit",NULL};

struct S s2 = { 99,"hello",NULL };

int main()
{
	struct S s3 = { 1,"abc",NULL };
	return 0;
}

上述代码的s1,s2和s3都是结构体变量,{}中是对它们的初始化。

有人这时候有问题了,如果不想按照结构体类型中规定的顺序初始化怎么办?

那就要换一种写法了:

struct S
{
	int a;
	char arr[5];
	int* p;
}s1 = { .p = NULL,.a = 100,.arr = "bit" };

struct S s2 = { .arr = "hello",.p = NULL,.a = 99 };

int main()
{
	struct S s3 = { .a = 1,.p = NULL,.arr = "abc" };
	return 0;
}

这两段代码的作用是一样的,我们可以将它们打印一下,不过这就涉及到结构体成员的访问了。

2.结构体成员的访问

前面我们讲过两个结构体成员访问操作符,即 .  ->

下面我们来打印一下试试:

#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
struct S
{
	int a;
	char arr[5];
	int* p;
}s1 = { .p = NULL,.a = 100,.arr = "bit" };

struct S s2 = { .arr = "hello",.p = NULL,.a = 99 };

int main()
{
	struct S s3 = { .a = 1,.p = NULL,.arr = "abc" };
	printf("%d %s %p\n", s1.a, s1.arr, s1.p);
	printf("%d %s %p\n", s2.a, s2.arr, s2.p);
	printf("%d %s %p\n", s3.a, s3.arr, s3.p);
	return 0;
}

运行结果:

想要访问结构体成员,用结构体变量 . 结构体成员名就可以了。

我们也可来尝试对结构体类型 B 创建变量 sb ,初始化并打印一下:

#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
struct S
{
	int a;
	char arr[5];
	int* p;
}s1 = { .p = NULL,.a = 100,.arr = "bit" };

struct S s2 = { .arr = "hello",.p = NULL,.a = 99 };

struct B
{
	char ch[10];
	struct S s;
	double d;
};
int main()
{
	/*struct S s3 = { .a = 1,.p = NULL,.arr = "abc" };
	printf("%d %s %p\n", s1.a, s1.arr, s1.p);*/
	struct B sb = { "thanks",{20,"abcd",NULL},3.14 };
	printf("%s %d %s %p %lf", sb.ch, sb.s.a, sb.s.arr, sb.s.p, sb.d);
	return 0;
}

运行结果:

上述代码,因为在struct B 中嵌套了struct S s;所以我们在{}中初始化时要将对struct S s初始化的内容再用个{}包起来。在打印时用两次结构体成员访问操作符(例如:sb.s.a)

以上是点操作符的使用,还有->操作符是在接受一个地址(指针)时使用,

下面看一段代码:

struct Stu
{
	char name[20];
	int age;
};
void set_stu(struct Stu t)
{
	t.age = 20;
	t.name = "王小明";
}
int main()
{
	struct Stu s = { 0 };
	set_stu(s);
	return 0;
}

我们写一个函数set_stu(),对结构体变量s赋值,那上面的代码对吗?

当然不对。

注意这句赋值语句:t.name = "王小明";出现了错误,我们说name是数组名,那你能直接把“王小明”赋给数组名吗?不能,应该是赋给数组名name所在的空间才对!

以前我们学过字符串拷贝strcpy这个函数,这里就可以用了。

struct Stu
{
	char name[20];
	int age;
};
void set_stu(struct Stu t)
{
	t.age = 20;
	strcpy(t.name,"王小明");//字符串拷贝
}
int main()
{
	struct Stu s = { 0 };
	set_stu(s);
    printf("%s %d", s.name, s.age);
	return 0;
}

问题又来了,这样写就对了吗?

运行一下:

结果是0,为什么呢?

那是因为我们在上述代码中使用的是传值调用,前面我们讲过形参是实参的一份临时拷贝,对形参的改变不影响实参的值。 

如果用传址调用就可以了:

#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
struct Stu
{
	char name[20];
	int age;
};
void set_stu(struct Stu* t)
{
	t->age = 20;
	strcpy(t->name,"王小明");
}
int main()
{
	struct Stu s = { 0 };
	set_stu(&s);
	printf("%s %d", s.name, s.age);
	return 0;
}

因为传址调用,传给形参的是地址,我们在访问结构体变量时就用到了->操作符

下面我们在看一段->操作符的使用示例:

#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
struct Stu
{
	char name[20];
	int age;
	char sex[5];
};
void print(struct Stu* t)
{
	printf("%s %d %s\n", t->name, t->age, t->sex);
	printf("%s %d %s\n", (*t).name, (*t).age, (*t).sex);
}
int main()
{
	struct Stu s = { "翠花",25,"女" };
	print(&s);
	return 0;
}

注意:代码中,t->name (*t).name 的作用是相同的。

3.结构体传参

我们在上面的讲解中已经包含了结构体传参,实际上,结构体传参有两种方式,一种是传结构体,一种是传地址。

那我们直接上代码:

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函数。

大家想一下,我们在传参时,形参是实参的一份临时拷贝,而如果传过去的结构体特别大,我们需要开辟同样大小的空间,这是不是对空间的一种浪费?而拷贝也是需要时间的,对时间也是一种浪费。而传地址的话,我们只需要传递4/8个字节大小的地址,然后根据地址找到实参的空间就行。

所以结构体传参时,传结构体的地址是一种比较好的选择。

原因:

函数传参的时候,参数是需要压栈的。

如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降。

今天就学到这里,未完待续。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

成屿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值