C语言学习-结构体

详解操作符:结构体操作符

结构体的访问运算符其一:.运算符

4.3结构体的访问运算符其二:->运算符

目录

前言(doge)

一、结构体是什么?

二、简单创建一个结构体和结构体的使用

结构体的访问运算符其一:.运算符

 三、结构体数组

四、结构体指针

4.1什么是结构体指针?

4.2结构体指针声明和初始化

4.3结构体的访问运算符其二:->运算符

五、结构体补充(先简单了解一下)

5.1 结构体特殊声明:匿名结构体

5.2 结构的自引用

5.2.1链表

5.2.2自引用

六、结构体与内存

6.1结构体内存对齐

6.1.1对齐规则(多分析,切勿死记)

 6.1.2如何分析一个结构体类型的大小

6.2为什么存在结构体内存对齐?

6.3修改默认随机数

七、结构体传参

7.1 结构成员传参

7.2 结构体本身传参(了解不要用)

7.3 结构体指针传参

八、结构体实现位段

8.1什么是位段?

8.1.1位段的声明

8.2位段的内存分配

8.3位段跨平台问题

8.4位段的应用

8.5 位段使用的注意事项

九.深浅拷贝问题

尾声



前言(doge)

  我重生了,重生回到了刚学结构体的那一天,上一世我不可一世,以为学完C语言已天下无敌,直到遇到了

typedef int ElemType;
typedef struct Node {
	ElemType data;
	struct Node* next;
}Node;
typedef struct Node *linklist;

数据结构(链表), 我的天哪,我不敢了,结构体大人,我看不懂,我太不自量力了,这一次我下定决心一定要好好学习结构体。因此,我决定写下这篇博客来警醒后面的人(doge)。

总之,结构体是后续C语言实现各种数据结构的基础,加油吧!

一、结构体是什么?

  我们初学C语言的时候,了解了很多数据类型,如int(整型),double(浮点类型,描述小数),char(字符类型描述字符),布尔类型等。这是C语言的内置类型。

在这之后我们学习了数组,我们知道数组也是有类型的,比如int [5],就是一个数组类型,它表示有5个int类型元素的数组。我们可以自己创建一个数组,比如char arr[8],它就表示能装8个字符的数组。可以发现数组类型是由无数个,并且数组类型是由我们创建数组的时候自己决定的。C语言中这种叫做自定义类型。

结构体就是自定义类型。它是C语言提供的一种自定义数据类型。

每项事物存在必有其意义,结构体是用来描述复杂事物的。生活中几乎所有事物都很复杂,以人为例,人有姓名,年龄,出生年月日,身高,体重,身份证号码,人的信息很多。如果单独创建变量,这个储存年龄,那个存储身高,每个属性数据类型也不同,还不方便管理。那么能不能创建一个房间专门放人的信息,结构体就是人为用来统一存储复杂信息的房间。

二、简单创建一个结构体和结构体的使用

  我们试着创建一个结构体:

struct tag

{

member_list;

}variable_list;

以上为结构体声明,只是个模板,描述结构体如何存储数据的。

struct 是结构体的关键字

tag 是标签,可以任取名。最好取一个与内容相关的名字。

member_list  成员变量,可以创建一系列变量,指针,数组,其它结构体,内置数据类型都可以(先了解,后面会涉及)具体看例子。

variable_list 这里可以创建结构体变量。也可以不在这里创建,具体看例子。

struct person {
	char name[20];//姓名
	int age;//年龄
	char data[20];//出生年月日
	double height;//身高
	double weight;//体重
} s1;//结构体声明后面创建结构体变量,为全局变量

struct person s2;//全局变量

int main()
{
  struct person s3;//主函数内部,为局部变量
  return 0;
}

 struct person就是一个结构体类型了,作用同int double一样,结构体类型后面跟变量名,就是创建了一个简单的结构体变量了。

请注意上图,结构体变量在创建位置,也分为全局变量和局部变量。上面的结构体是描述一个人的。

结构体初始化如下:

struct person {
	char name[20];//姓名
	int age;//年龄
	char data[20];//出生年月日
	double height;//身高
	double weight;//体重
};
struct person s1 = { "zhangsan",18,"19830421",187.5,168.0 };//结构体的完全初始化一一对应。

struct person s2 = { 0 };

 这种初始化对于字符数组是空字符,int 是 0 ,字符是'\0',double 是小数0.000000的形式。

下面介绍一种运算符,可以进行指定初始化。

结构体的访问运算符其一:.运算符

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
struct person {
	char name[20];//姓名
	int age;//年龄
	char data[20];//出生年月日
	double height;//身高
	double weight;//体重
};
int main()
{
	struct person s1 = { "zhangsan",18,"19830421",187.5,168.0 };
	printf("张三的体重是:%lf", s1.weight);
	return 0;
}

 变量名.成员变量名,就可以访问结构体成员变量的数据了。如图就是s1.weight访问了对应的数据,并用printf指定格式化打印。

结构体变量也是一种变量,通过.运算符可以访问结构体成员,可以像我们先前处理内置类型的数据一样进行运算操作。

.运算符是结构体的直接访问操作符。

指定初始化

#include<stdio.h>
struct person {
	char name[20];//姓名
	int age;//年龄
	char data[20];//出生年月日
	double height;//身高
	double weight;//体重
};
struct person s2 = { .name = {"zhangsan"} };//指定初始化变量。
int main()
{
	printf("这个人的名字是:%s\n",s2.name);
	return 0;
}

 结构体成员可以是结构体(自身或者其它结构体)所以称结构体可以嵌套。结构体的嵌套初始化和嵌套访问将在结构体指针处详细呈现。

 三、结构体数组

前面提过结构体类型和内置数据类型相同,所以创建结构体数组也可以仿照数组类型。


struct book {
	char title[MAXTITL];
	char author[MAXAUTL];
	float value;
};

struct book arr[5];//结构体类型 变量名[元素个数]  是一般格式

 结构体类型 数组名[元素个数];

我们学过二维数组的初始化,类比下面结构体数组的初始化就好理解了。

花括号里面各套一层花括号分别表示arr[0],arr[1],arr[2],在同初始化结构体变量一样,就很好理解了。

数组通过下标(下标引用操作符【】)访问元素,结构体通过.运算符直接访问结构体内部的成员。那么结构体数组就是综合运用这两个操作符。

结构体数组的每个元素都是一个结构体变量。下列代码中arr[i]访问了一个结构体变量,再在后面加上用.成员变量,就访问这个元素的变量。具体例子看下。

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#define  MAXTITL 40
#define  MAXAUTL 40
#define  MAXBKS 100


struct book {
	char title[MAXTITL];
	char author[MAXAUTL];
	float value;
};

struct book arr[3] = { {"One Hundred years of Solitrude","Marquez",50.00},
	{"Red Star Over China","Edgar Snow",11.8},
	{"Three Days To See","Helen Keller",23.98} };

int main()
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%s %s %.2f\n", arr[i].title, arr[i].author, arr[i].value);
	}
	return 0;
}

 格式:

结构体数组名[下标].成员变量。

对上面代码进行总结:

1.arr是book结构的数组。

2.arr[1]是数组的第二个元素,也是book结构。

3.arr[1].title是一个字符数组,在上面代码为字符串。是arr[1]的成员。

4.  3是一个数组,它也可以访问元素,比如arr[1].title[3]就表示访问这个.char数组的第四个元素。

四、结构体指针

4.1什么是结构体指针?

前面学过一般的指针,字符指针,数组指针,函数指针。

结构体指针是一种指针,这种指针变量是指向结构体的。

这种指针有什么用?结构体指针变量存储结构体变量的地址。

&变量名就可以取出地址了,同理解引用*也适用,完全可以迁移过来。

4.2结构体指针声明和初始化

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#define length 20
struct name {
	char first[length];
	char last[length];
};


struct guy {
	struct name handle;//嵌套结构体
	char job[length];
	double wage;
};

struct guy* p = NULL;//创建结构体指针变量并赋值为空指针

int main()
{
	//结构体的嵌套初始化
	struct guy people[2] = {
		{{"Ewen","Villard"},
		"personality coach",
		78356.00
		},
		{
			{"Rodney", "Swillbelly" },
			"tabloid esitor",
			43240.00
		}
	};
	struct guy a = { 0 };
    struct guy* A = &a;

	p = people;
	printf("pointer  :%p\n", p);
	printf("第一个人的信息:");
	printf("first name:%s last name: %s\n", (*p).handle.first, (*p).handle.last);
	printf("第二个人的信息:");
	printf("first name:%s last name: %s\n", (*(p+1)).handle.first, (*(p+1)).handle.last);
}
结构体的嵌套初始化看起来比较复杂,这里还是结构体数组的嵌套初始化。

 结构体数组初始化要点:

1.总起大括号。

2.每个数组元素是一个结构体变量,各起一个大括号。元素之间用逗号分隔。

3.数组元素内部按顺序初始化,也可以采取指定初始化。内部有结构体,起大括号填充数据。

 p = people;//这里的结构体数组与学过的一维数组类似,数组名就是首元素的地址,可以直接赋值给结构体指针p。

*p 解引用,为数组名,数组名对于一维数组来说就是首元素的地址,等价于people[0],(*p).handle.first==pepple[0].handle.first,这里是结构体的嵌套访问。(*p).handle是name的结构,再用一次·运算符访问name的两个字符数组。

4.3结构体的访问运算符其二:->运算符

这里介绍->运算符

#define len 50
#include<stdio.h>
typedef struct book {
	char author[len];
	char title[len];
	float value;
}Book;

Book* p = NULL;

int main()
{
	Book _s1 = { 0 };
	p = &_s1;
	printf("Please enter the _s1 title\n");
	scanf("%s", p->title);
	getchar();
	printf("Now enter the _s1 author\n");
	scanf("%s", p->author);
	getchar();
	printf("Now enter the _s1 author\n");
	scanf("%f", &(p->value));
	getchar();
	printf("书名:%s ,作者:%s ,价值:%f", p->title, p->author, p->value);

	return 0;
}

 结构体指针通过->访问成员与结构体通过.运算符访问作用相同。

可以认为p->title==_s1.title,其结果是访问到了内存_s1存储title的数据。

->作用结构体指针,.操作符作用结构体。两者都是结构体的操作符,但适用对象不同。

这里解释一下上面代码,p->title结果是title这个数组,scanf函数占位符要求是地址,这里的p->title是字符数组的首元素的地址。而下面p->value是float的数据需要&。

五、结构体补充(先简单了解一下)

5.1 结构体特殊声明:匿名结构体

在声明结构体,可以不完全声明。

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
//这种结构体类型没有名字(tag),因此称为匿名结构体。
struct
{
	char c;
	int i;
	double d;
} s1= { 'w',10,7.0 };//匿名结构体变量只能在结构体声明中创建,且初始化也必须在这里进行。

int main()
{
	printf("%c %d %lf", s1.c, s1.i, s1.d);
	return 0;
}

强调一遍,匿名结构体创建变量和初始化只能在声明中进行。

需要注意如果一个程序同时出现多个匿名结构体,哪怕结构体内部完全相同,编译器还是当作不同的类型。

#include<stdio.h>
struct
{
	char c;
	int i;
	double d;
}s1 = { 'w', 10, 7.0 };

struct
{
	char c;
	int i;
	double d;
}*p;
int main()
{
	p = &s1;
	printf("&p = %p", p);
	return 0;
}

  

匿名结构体可以通过类型重命名获得名字,但是多此一举,不如一开始就正常声明结构体。

最后,如果不使用typedef ,匿名结构体只能用一次。

总结:最好不要用,否则有一堆使用上的问题等着你解决。比如,下面的自引用,匿名结构体就不能使用。

#include<stdio.h>
typedef struct
{
	char c;
	int i;
	double d;
}S;


int main()
{
	S s1 = { 's',10,7.0 };
	printf("%c %d %lf",s1.c,s1.i,s1.d);
	return 0;
}

5.2 结构的自引用

5.2.1链表

链表是一种数据结构(DataStructure)。

数据结构是计算机存储,组织数据的方式,它是一门CS必修课程

你可能听说过各种类型的数据结构,比如线性表(顺序表,链表,栈,队列,双端队列),串,树,还有更高级的图,并查集,红黑树等等。

链表是一种线性表。

链表就是一个链条把内存空间的相关数据串在一起,每一个数据称为一个结点(节点。)怎么建立它们之间的联系呢?

比如下面的整型1~5,由于在内存储存中不连续(假设),数组就无法使用了。

//错误写法,思考一下为什么不行
struct Node {
	int data;
	struct Node n;
};

 一个结点存储一个整型,并告诉了下一个结点整型的数据。看似好像挺合理的,但如果创建了一个这样的结构体变量。这个变量向内存申请空间,它内部的结构体成员有结构体变量,内部的结构体变量也要重复上面的步骤。有一种陷入了死递归的感觉,已经能想象可用内存被占完,程序崩溃的样子。

因此这种写法大错特错。

sizeof(struct Node),这个结构体类型大小无穷大,不合理。

事实上,vs2022根本不会给你这样写的机会,直接编译错误了。

那怎么写呢?

前面学过指针,数据与数据之间,可以通过地址建立联系。

每个结点储存了下一个结点的地址 ,就可以通过前一个访问下一个结点。这里创建一个结构体变量,不会出现重复向内存申请空间的情况。比如,程序运行时,s1创建一个整型变量和相同结构体类型对应的结构体指针变量,只是申请这两个变量的空间。(结构体变量在内存中存储稍后说明)

struct Node {
	int data;//数据
	struct Node* n;//指针
}s1;

 每个节点分为两部分,一个数据部分,叫做数据域;另一个是地址(指针部分),叫做指针域。

5.2.2自引用

自引用就是结构体内部有它同类型指针。(内部有它同类型的变量的写法是错误的)

typedef struct Node {
	int data;//数据
	struct Node* next;//指针
} node;

六、结构体与内存

6.1结构体内存对齐

在说明结构体内存对齐前,请先回答一个问题,你认为下面的结构体类型的大小是多少?

#include<stdio.h>
struct S
{
	char c;
	int i;
	char d;
};

int main()
{
	size_t ret = sizeof(struct S);
	printf("sizeof(struct S) = %zd", ret);
	return 0;
}

 你的答案是6吗?

作者先前认为结构体的类型大小就是成员变量类型大小的求和。

char c;//一个字节

int i ;//4个字节

char d ;//一个字节

合计6个字节。然而,结果给我啪啪打脸。

为什么结果会是12个字节呢?

没错,这就是接下来要说的结构体内存对齐了。

计算的结构体的大小要符合结构体内存对齐的规则 

6.1.1对齐规则(多分析,切勿死记)

结构体的对⻬规则:
1. 结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处
2. 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
对⻬数 = 编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值。
VS 中默认的值为 8
Linux中 gcc 没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩
3. 结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的
整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构
体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。
下面创建一个全局结构体变量并初始化。用上述规则分析
struct S s = { 0 };

 下面图详解,请结合上述规则分析,耐心看完。

上面没提到规则四,这里单独说一下 ,请看如下代码

#include<stdio.h>
struct S
{
	char c;
	int i;
	char d;
};

struct S2 {
	double e;
	struct S f;
	char g;
};//S2中嵌套了S。

struct S2 s = { 0 };//注意这里创建s变量是S2类型的结构体变量
int main()
{
	printf("%zd", sizeof(s));
	return 0;
}

如果成员变量是数组,或者指针的情况一样分析,相信聪明的你一定能够举一反三。

编译器会根据数据类型大小,一次性开辟空间,并不会像我们分析的那样一步一步开辟。 

 6.1.2如何分析一个结构体类型的大小

1.掌握6.1.1的对齐规则,先有个印象。

2./6.1.1 自己创建一个结构体,像作者一样画图分析,得出答案。

3.程序中用sizeof运算符计算检验答案。

6.2为什么存在结构体内存对齐?

前面可知,结构体内存对齐可能会浪费空间。

但实际程序运行,我们考虑时间因素。

结论:结构体内存对齐是以空间换取时间的做法。

这里自行看看(博主从资料上粘贴的)

1. 平台原因 (移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对⻬。原因在于,为了访问未对⻬的内存,处理器需要
作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地 址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数,那么就可以用⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两个8字节内存块中。
这里通俗点,内存没对齐读取数据要多次访问,读取次数多了浪费时间。

完结撒花(

缺点:浪费空间

优点:节约时间

解决方案:浪费的同时相对节约空间的做法

1.占用空间小的集中一起

对比一下下面俩,谁内存小,不那么浪费?

struct S1
{
	char c;
	int i;
	char d;
};

struct S2
{
	char c;
	char d;
	int i;
};

2.设置随机默认数。

没错,设置默认随机数原因也是为了节省内存对齐浪费的空间。

1方案可以人为控制成员变量的顺序。方案2不能了对吧。

回答方案2也能人为控制。详情看6.3。

6.3修改默认随机数

当你认为默认对齐数不好时,输入#pragma预处理指令,进行如下操作就可以修改了。

当然贸然修改极有可能弄巧成拙,pack()后面建议填2的次方数。

根据实际情况调整。

#pragma pack(1)//设置默认对齐数为1
#pragma pack(0)//还原成编译器默认对齐数

over!

七、结构体传参

7.1 结构成员传参

这里的结构成员是内置数据类型。

编写一个加法函数,用结构体变量的成员传参。

#include<stdio.h>
#define NAME_MAX 20
typedef struct personinfo {
	char name[NAME_MAX];
	double bankfund;
}Peo;

double double_Add(double x, double y)//浮点数加法函数
{
	return x + y;//返回参数之和
}

int main()
{
	Peo s1 = { "zhangsan",5678.09 };
	Peo s2 = { "lisi",8782.50 };
	double ret=double_Add(s1.bankfund, s2.bankfund);
	printf("Total = %.2lf", ret);
	return 0;
}

 函数形参不关心括号内的参数是不是结构体成员,只关心实参和形参是不是同一数据类型。

7.2 结构体本身传参(了解不要用)

重申不要使用这种写法,看看就行

#include<stdio.h>
struct S {
	int arr[1000];
	int n;
	double d;
};
void print1(struct S tmp)
{
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ", tmp.arr[i]);
	}
	printf("%d ", tmp.n);
	printf("%lf ", tmp.d);
}
int main()
{
	struct S s = { {1,2,3,4,5},100,3.14 };
	print1(s);
	return 0;
}

 解释:

在学习函数中,有一句话,函数在传值调用时,形参是实参的一份拷贝。当你以结构体本身为形参时,形参会把实参的数据(结构体变量)都拷贝一份。了解完结构体内存之后,这种写法,不仅占用内存,而且浪费时间,吃力不讨好,代码一复杂,缺点立马显露无遗。

传地址调用(结构体指针作为函数参数),对于复杂代码效果立马立竿见影了,起码作者心里舒服了不少。

7.3 结构体指针传参

将7.2中代码做如下更改。

#include<stdio.h>
struct S {
	int arr[1000];
	int n;
	double d;
};
void print1(struct S *tmp)//参数改成结构体指针
{
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ", tmp->arr[i]);//.运算符改为->运算符
	}
	printf("%d ", tmp->n);
	printf("%lf ", tmp->d);
}
int main()
{
	struct S s = { {1,2,3,4,5},100,3.14 };
	print1(&s);//参数传地址
	return 0;
}

 这种写法的优点:7.2的缺点被解决了,提高了效率。

总结:毫无疑问,结构体传参,传结构体指针。即7.3的方式。

八、结构体实现位段

8.1什么是位段?

注意:需要了解二进制,这里只谈C99前的位段

位段依靠结构体实现,不能认为位段也是一种自定义的数据类型

1.位段的成员必须是 int、unsigned int 或signed int
2.位段的成员名后边有⼀个冒号和⼀个数字。3.C99中位段成员的类型也可以
选择其他类型。(其他类型位段自行了解,这里不介绍)

8.1.1位段的声明

先看如下代码:

//位段式结构
struct S1 {
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};

struct S2 {
	int _a;//32bit
	int _b;//32bit
	int _c;//32bit
	int _d;//32bit
};

 位段的声明和结构体声明相似,区别成员变量类型是“整型家族”,还有冒号数字。

位段中位指的是二进制位。我们知道1个字节=8个bit位,整型占32个bit位。

无符号整型能存储0~2^32-1,有符号整型也能存储相当大的整型数据。

如果这里的_a只存储0,1,2,3的数据,那么32位的整型的高位都是0,只有低位的后两位可能不是0。00, 01, 10, 11知道了整型变量储存数据的大致范围,我们就可以限制整型数据的bit位数。

struct S1 {
	int _a : 2;//2个bit
	int _b : 5;//5个bit
	int _c : 10;//10个bit
	int _d : 30;//30个bit
};

 这里限制bit位数有什么作用?

相信你已经猜到了,就是节省空间。

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
struct S1 {
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};

struct S2 {
	int _a;//32bit
	int _b;//32bit
	int _c;//32bit
	int _d;//32bit
};

int main()
{
	printf("sizeof(struct S1) = %zd\n", sizeof(struct S1));
	printf("sizeof(struct S2) = %zd\n", sizeof(struct S2));
	return 0;
}

这里位段几乎节约一半的空间。

8.2位段的内存分配

1.位段的成员可以是 int unsigned int signed int 或者是 char 等类型。(整型家族)
2. 位段的空间是按位段成员整型(32位),字符(8位)开辟。
#include<stdio.h>
struct S
{
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
};
int main()
{
	struct S s = { 0 };
	s.a = 10;
	s.b = 12;
	s.c = 3;
	s.d = 4;
	printf("%zd", sizeof(s));
	return 0;
}

8.3位段跨平台问题

1.int 位段被当成有符号数或无符号数不确定。

2.位段中最大位的数目不确定。32位机器和64位机器,int是4字节。但16位机器int为2字节。

所以int最大位数是32,16。位段数>16则会在16位机器出问题。了解一下,因为16位机器被淘汰了。

3.位段中成员在内存中分配顺序这个标准未定义。

4.当⼀个结构包含两个位段,第⼆个位段成员比较大,无法容纳于第⼀个位段剩余的位时,是舍弃

剩余的位还是利用,这是不确定的。
总结:
位段可以节省空间,但存在跨平台问题。这说明位段可移植性差,位段虽然没定义标准,只要确定了位段在内存的分配方式,不同平台写不同的代码位段也是可以使用的。

8.4位段的应用

下图是网络协议中,IP数据报的格式,我们可以看到其中很多的属性只需要⼏个bit位就能描述,这⾥使⽤位段,能够实现想要的效果,也节省了空间,这样⽹络传输的数据报⼤⼩也会较⼩⼀些,对⽹络的畅通是有帮助的。

8.5 位段使用的注意事项

指针部分,我们知道内存给每个字节分配地址,一个字节的起始位置分配到一个地址。

位段的⼏个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,所有很遗憾,不在起始位置的位段成员,它没有地址。&就不能用了( 无论改位段成员是否有地址),不能使用scanf直接给位段的成员输⼊值,只能是先输入 放在⼀个变量中,然后赋值给位段的成员。(中间变量赋值)
#include<stdio.h>

struct A{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
int main()
{
	struct A sa = { 0 };
	/*scanf("%d", &(sa._d)); *///错误的

	//正确的
	int b = 0;
	scanf("%d", &b);
	sa._b = b;
	return 0;
}

九.深浅拷贝问题

观察下面代码,有什么问题?

#include<stdlib.h>
#include<stdio.h>
struct person{
	char* name;
	int age;
};

int main()
{
	struct person p1;
	p1.name = (char*)malloc(sizeof(char) * 10);
	p1.age = 18;
	printf("Enter the name:>");
	scanf("%s", p1.name);
	struct person p2 = p1;
	printf("p1  name:%s,age:%d\n", p1.name, p1.age);
	printf("p2  name:%s,age:%d\n", p2.name, p2.age);
	//使用完释放内存.
	free(p1.name);
	free(p2.name);
	return 0;
}

问题在于重复释放同一块内存.

深浅拷贝问题,一般出现在结构体含有指针的情况.当结构体内部成员指针指向一块堆内存时.

此时进行struct person p2 = p1;p2和p1的char*成员指针引用了同一块堆内存.那么后续free一次就够了,第二次free由于第一次释放了char*指向的内存空间,导致程序错误.

错误原因:进行了浅拷贝,即值拷贝.导致对一方操作影响了另一方.

正确处理:为每个指针指向内存单独开辟独立空间,保证互不影响.用malloc函数和memcpy函数拷贝即可.

#include<stdlib.h>
#include<stdio.h>
#include<string.h>
struct person{
	char* name;
	int age;
};

int main()
{
	struct person p1;
	p1.name = (char*)malloc(sizeof(char) * 10);
	p1.age = 18;
	printf("Enter the name:>");
	scanf("%s", p1.name);
	struct person p2;
	//单独为p2的char*指针开辟独立堆内存.
	p2.name = (char*)malloc(strlen(p1.name) + 1);
	p2.age = p1.age;
	//往内存空间拷贝字节
	memcpy(p2.name,p1.name,strlen(p1.name)+1);
	printf("p1  name:%s,age:%d\n", p1.name, p1.age);
	printf("p2  name:%s,age:%d\n", p2.name, p2.age);
	//使用完释放内存.
	free(p1.name);
	free(p2.name);
	return 0;
}

结束

尾声

早岁已知世事艰,仍许飞鸿荡云间.

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值