【了解结构体与指针、数组、函数、字符串之间的关系】(学习笔记14--结构体)


前言

不要让结构体成为孤零零的一片荒岛,应该和指针、数组、函数等结合起来,让结构体发挥应有的作用,从而实现出灵活、高效、优美的C语言程序。这篇博文就来探讨一下结构体与指针、结构体与数组、结构体与函数以及结构体与字符串之间的种种关系

🍓结构体的运用

🍓结构体与指针

指针可以指向一个对象,若将结构体视为一个对象的话,就可以定义出指向结构体的指针

struct Book
{
	int isbn;			//刊号
	char author;		//作者
	float price;		//单价
}

根据该类型,定义出一个结构体变量book1,并对其进行初始化

struct Book book1 = {12345678,"lqy",78.50f};

定义一个指针,并让其指向结构体变量book1

struct 	Book *pBook = &book1;

定义了struct Book类型的指针变量pBook,并将结构体变量book1的内存地址作为其初始值。即可认为指针pBook指向了结构体变量book1

通过指针来访问结构体变量的成员有以下两种方式

1️⃣指针解引用方式

可以通过对指针的解引用来访问结构体变量,进而再通过结构体变量来访问各成员

(*pBook).isbn

在小括号内是对指针pBook进行解引用,解引用后相当于得到了结构体变量book1,然后再通过成员访问运算符来访问book1的成员isbn
注意这里的小括号不能省略,因为成员访问运算符的执行优先级比解引用运算符高

下面使用指针解引用的方式来访问结构体变量book1,并将book1的所有成员打印输出到控制台窗口

printf("ISBN: %d\n",(*pBook).isbn);
printf("Author: %s\n",(*pBook).author);
printf("Price: %.2f\n",(*pBook).price);

结果如下

ISBN: 12345678
Author: lqy
Price: 78.50

2️⃣非指针解引用方式

可以通过间接成员访问运算符来访问指针所指向的结构体变量的成员。间接成员访问运算符由短横线字符和大于号字符构成,即->,两个字符必须连在一起,中间不可有间隔或其它字符。由于其形状像一个箭头,因此也被称为箭头运算符

下面就使用箭头运算符来访问结构体变量的成员

pBook->isbn

下面就使用箭头运算符来访问并打印输出结构体变量book1的所有成员变量

printf("ISBN: %d\n",pBook->isbn);
printf("Author: %s\n",pBook->author);
printf("Price: %.2f\n",pBook->price);

结果如下

ISBN: 12345678
Author: lqy
Price: 78.50

可见,结果和之前是完全一致的
要注意的是,箭头运算符只适用于指向结构体变量的指针,如果面对的是结构体变量本身,还是应该使用点运算符

不仅可以获取一个结构体变量的地址,还可以获取到结构体变量的各个成员的内存地址,即获得指向结构体变量中某个成员的指针

int *pIsbn = &book1.isbn;			//指向成员isbn指针
char *pAuthor = book1.author;		//指向成员author指针
float *pPrice = &book1.price;		//指向成员price指针

成员访问运算符的优先级高于取地址运算符。所以先通过成员访问运算符访问到book1的成员isbn,再通过取地址运算符获取该成员的内存地址

需要注意的是,结构体变量book1的成员auther本身是一个字符数组,其数组名就代表首地址的内存地址。因此,我们就直接将其初始化给char类型的指针变量pAuthor。千万不要在前面再使用取地址运算符了,否则,得到的就不再是char类型的指针,而是char[20]类型的指针,即得到了一个数组指针

🍓结构体与数组

如果我们将结构体作为数组的元素类型,就可以定义出结构体数组

struct Book books[3] = {{12345678,"lqy",78.50f}
						{23456789,"lqy1",75.50f}
						{34567890,"lqy2",80.50f}};

定义了一个长度为3的struct Book类型的结构体数组books,并对数组元素进行了初始化。内层每对大括号内的3个初始值用于一个数组元素的初始化

可以利用循环,并通过数组下标来访问每个数组元素,即访问结构体变量,最后再通过成员访问运算符来访问结构体变量的各个成员

for(int i = 0;i < 3;++i)
{
	printf("ISBN: %d\n",books[i].isbn);
	printf("Author: %s\n",book[i].author);
	printf("Price: %.2f\n",book[i].price);
	printf("----------------------------\n");
}

首先通过数组下标来访问各元素,并按照结构体变量的格式,使用成员访问运算符来访问各个成员

ISBN: 12345678
Author: lqy
Price: 78.50
----------------------------
ISBN: 23456789
Author: lqy1
Price: 75.50
----------------------------
ISBN: 34567890
Author: lqy2
Price: 80.50
----------------------------

由于数组名即是首元素的内存地址,可以将数组名视为指向数组首元素的指针,因此也可以通过指针的方式来访问和打印各数组元素(结构体变量)的成员

for(int i = 0;i < 3;++i)
{
	printf("ISBN: %d\n",(*(books + i)).isbn);
	printf("Author: %s\n",(*(books + i)).author);
	printf("Price: %.2f\n",(*(books + i)).price);
	printf("----------------------------\n");
}

循环体中,表达式(*(books + i)).isbn的求值顺序为:首先是内层小括号中,指针books与变量i进行相加的运算,这会产生一个指向数组中下标为i的数组指针,接着在外层小括号中,对该指针进行了解引用,这将访问下标为i的数组元素,最后,由于数组元素都是struct Book类型的,因此,可以通过成员访问运算符来访问它的各个成员

也可以使用箭头运算符,以更简便的形式来访问数组元素(结构体变量)的各个成员

for(int i = 0;i < 3;++i)
{
	printf("ISBN: %d\n",(books + i)->isbn);
	printf("Author: %s\n",(books + i)->author);
	printf("Price: %.2f\n",(books + i)->price);
	printf("----------------------------\n");
}

在小括号中,首先是指针books与变量i进行相加的运算,这会产生一个指向数组中下标为i的数组元素的指针,接诊使用箭头运算符来直接访问所指向的数组元素(结构体变量)的各个成员

🍓结构体与函数

想在函数中传递和使用结构体数据,可以将结构体指针作为函数的参数或者返回值,也可以将结构体变量作为函数的参数或者返回值

编写程序,定义一个关于矩形的结构体Rect,它的3个成员名分别为length、width、area,都为float类型,用来表示矩形的长、宽和面积。再定义一个函数calculate,该函数能够根据矩形结构体变量中的长和宽,计算出矩形面积。要求程序运行时,由用户输入矩形的长和宽,通过调用calculate函数,计算出矩形面积,并在主函数中将矩形的面积打印输出

首先将这个矩形结构体定义出来

struct Rect					//矩形结构体
{
	float length;			//长
	float width;			//宽
	float area;				//面积	
}

下面定义calculate函数,分以下两种

(1)将结构体变量作为函数参数或返回值

首先将calculate定义为一个无返回值、参数类型为struct Rect的函数

void calculate(struct Rect rect)
{
	rect.area = rect.length * rect.width;
}

函数体内就一条语句,通过成员访问运算符访问参数结构体变量的成员length和成员width,并将它们的乘积赋值给成员area
主函数的代码如下

int main()
{
	struct Rect rc;			//定义结构体变量
	printf("Please enter the length and width of the rectangle:\n");
	scanf("%f%f",&rc.length,&rc.width);			//获取用户输入的长和宽
	calculate(rc);				//调用calculate函数,计算矩形面积
	printf("The area of the rectangle is: %.2f.\n",rc.area);
	return 0;
}

结果

Please enter the length and width of the rectangle:
4 5
The area of the rectangle is: 0.00.

结果是错误的,问题在于函数的参数传递是值传递的形式,实参与形参是两个不同的对象,有着各自不同的内存空间,因此,在函数中访问和修改的只是形参结构体变量,影响不到实参结构体变量。也就是说,calculate函数中所访问和修改的只是形参结构体变量rect,而不是实参结构体变量rc

可以让calculate函数将修改后的结构体变量返回,即将calculate函数定义为一个有返回值的函数,并且返回值类型为struct Rect

struct Rect calculate(struct Rect rect)
{
	rect.area = rect.length * rect.width;
	return rect;			//返回修改后的结构体变量rect
}

在主函数中,将calculate函数调用后的返回值,重新赋值给实参结构体变量rc

rc = calculate(rc);

结果如下

Please enter the length and width of the rectangle:
4 5
The area of the rectangle is: 20.00.

这样就能得到正确答案了

(2)将结构体指针作为函数参数或返回值

将calculate函数的参数定义为结构体指针类型
由于参数pRect是一个结构体指针,因此,在函数体内,可以使用箭头运算符来访问它所指向的结构体变量的成员

void calculate(struct Rect *pRect)
{
	pRect->area = pRect->length * pRect->width;
}

在主函数中,调用calculate函数时,需要通过取地址运算符将结构体变量rc的内存地址取出,作为函数调用的实参

calculate(&rc);

其它代码不变,结果如下

Please enter the length and width of the rectangle:
4 5
The area of the rectangle is: 20.00.

在calculate函数被执行时,形参pRect被初始化为实参rc的内存地址,即pRect是一个指向rc的结构体指针,通过这个指针来访问rc的成员length和width,并将它们的乘积再重新赋值给成员area。因此,可以通过指针pRect来访问和修改到实参rc。所以可以将calculate函数定义为无返回值的函数

如果想让calculate函数具有一个结构体指针类型的返回值,也是没有任何问题的

struct Rect* calculate(struct Rect *pRect)
{
	pRect->area = pRect->length * pRect->width;
	return pRect;
}

主函数中,由于calculate函数的返回值是一个指向Rect结构体变量的指针,因此,我们需要对其进行解引用,然后再重新赋值给实参结构体变量rc

rc = *calculate(&rc);

calculate函数的返回值是一个指向结构体变量rc的指针,对该指针进行解引用,就访问到了结构体变量rc,然后再把它赋值给rc。实际上就是自己赋值给自己。

由于指针的大小是固定的,并且通常情况下,会小于结构体变量的大小。因此,将结构体指针作为函数的参数或返回值,会比使用结构体变量作为函数的参数或返回值的重要原因之一

🍓结构体与字符串

有一个关于学生的结构体Stu

struct Stu
{
	int num;			//学号
	char *name;			//姓名
};

在主函数中定义一个该结构体类型的结构体变量并对其进行初始化
结构体变量名为stu1,其成员num被初始化为1,成员name被初始化为字符常量Tom的首地址

struct Stu stu1 = {1,"Tom"};

由于成员name指向的是一个字符串常量,因此,不能通过指针来修改字符串的内容

那我们就让指针指向另一个字符串常量就行了

stu1.name = "tom";

这并不是通过指针修改指向的对象,而只是对指针变量的重新赋值,即改变指针的指向,Ton和tom它们的内存空间和内存地址是不相同的

要想得到能修改的字符串,我们应该使用字符数组,只要让成员name指向字符数组的首地址就行了

char str[20] = "Tom";
stu1.name = str;
*stu1.name = str;
printf("Name: %s\n",stu1.name);

结果,成功地将姓名修改为tom

Mane: tom

定义一个结构体变量stu2,并将其初始化为stu1的值

struct Stu stu2 = stu1;

然后通过strcpy函数将字符串常量Jack复制给stu2的成员name所指向的内存空间

strcpy(stu2.name,Jack);
printf("stu1 Name: %s\n",stu1.name);
printf("stu2 Name: %s\n",stu2.name);

结果如下

stu1 Name: Jack
stu2 Name: Jack

可见,两个结构体变量的成员name所指向的字符串都变为了Jack。原因是stu1和stu2的成员name所指向的是同一字符数组。因此,解决的办法,就是为它们各自设置不同的字符数组,即让不同的结构体变量成员name指向不同的字符数组空间

#include <stdio.h>
#include <string.h>
struct Stu
{
	int num;			//学号
	char *name;			//姓名
};
int main()
{
	struct Stu stu1 = {1,"Tom"};
	char str1[20] = "Tom";
	stu1.name = str1;
	
	struct Stu stu2 = stu1;
	char str2[20];
	stu2.name = str2;

	strcpy(stu2.name,"Jack");

	printf("stu1 Name: %s\n",stu1.name);
	printf("stu2 Name: %s\n",stu2.name);
	return 0;
}

结果

stu1 Name: Tom
stu2 Name: Jack
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

是北豼不太皮吖

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

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

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

打赏作者

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

抵扣说明:

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

余额充值