【数据结构C语言】【入门】【首次万字详细解析】入门阶段数据结构可能用到的C语言知识,一章让你看懂数据结构!!!!!!!

前言欢迎各位光临本博客,这里小编带你直接手撕入门阶段的数据结构的C语言知识,让你不再看见数据结构就走不动道。文章并不复杂,愿诸君耐其心性,忘却杂尘,道有所长!!!!

目录

 一、数组

1.1一维数组的定义和创建

一维数组数组的创建方式:

创建例子:

图解:(以整型数组为例)

 1.2一维数组的初始化

1.2.1整型数组的初始化

 1.3字符数组的初始化

3.数组的使用

1.数组的引用

2.数组的输入

 3.数组的输出

 数组传参(重点!!!!!)

一维数组传参:

一维数组传参的本质:

二维数组传参的本质:

2.指针变量

2.1内存与地址

1.1内存

二、 指针变量和地址

2.1 取地址操作符(&)

2.2、指针变量和解引用操作符(*)

2.2.2 指针类型的拆分

2.2.3 解引用操作符

2.2.4指针变量的大小

小知识:typedef重命名:

3.动态内存管理

一. malloc 和 free

1. malloc

2. free

二. calloc

三. realloc

情况1:

情况2:

​编辑情况3:

4.结构体

一.结构体

1.结构体的声明

2.结构体变量成员访问操作符

3.结构体传参

4.匿名结构体

5.结构的自引用

结尾祝福语


 一、数组

如有有已经知道基础概念的小伙伴,直接根据目录表跳转到数组函数传参: 

1.1一维数组的定义和创建

数组的定义其实非常非常简单;他就是是一组相同类型的集合不理解也没关系,我给你举个例子就好了。

数组的定义:数组是一组相同类型的集合

给你讲个故事:

我认识一个朋友(纯属虚构),她是一个事业心非常强的人,无论任何东西都会以事业和学业为主,所以大多数有选择时,都会选择先忽略自己的感受的选项,平时也不注重打扮,以至于她的家非常乱,家里杂乱无章,到处找东西找不到。后来她喜欢上一个男生,感觉自己的一股屌丝样子配不上他,所以就开始捯饬自己,她开始把自己的房间整理,把袜子和袜子归类到一起,衣服和衣服归类到一起,所有一切的事物都开始重新归类。stop!!!!!!

对的,这里的袜子和袜子归类到一起,衣服和衣服归类到一起结果就是数组因为他们都是相同类型的集合!!!!!!

后来的后来,她也确实谈上了恋爱,但故事的结局,我相信应该由大家书写!!!

回归正题......................................讲完数组是什么,我们接下来看一下他的初始化创建


1.一维数组数组的创建方式:

type    arr_name[常量值]

   类型+数组名[元素值/下标]

 

创建例子:
                  int arr[10];//创建一个元素数为10的整型数组

                  double arr[10];//创建一个元素数为10的双浮点数数组

                  char arr[10]; //创建一个元素数为10的字符数组

 
图解:(以整型数组为例)
int arr[10] 详细图解

我们知道一维数组是如何创建的了,那么接下来我们看他如何初始化

数组的初始化种类就好几种,但我会指出最常用的几种,大家记住即可。 


 1.2一维数组的初始化

1.整型数组的初始化

 首先先来说数组的一种初始化,也是最常用的初始化:

指在创建的基础上给一个或多个合理的值;而每个类型的初始化又存在差异

本章的初始化我们就来讨论其差异性,这里没啥大用,做了解即可,看我慢慢给你解释!!!!

数组分为完全初始化不完全初始化


 先来看一个不完全初始化

一维数组的初始化图

解释:arr与[10]结合,说明我们定义了一个空间为10个的数组int表示空间的每个元素是为整型

{1}在这里是什么意思?为什么定义了十个空间,这里只有一个元素?

这里叫做不完全初始化,后面给的初始化元素数少于定义的元素数, 后面的元素要我们在后续的程序中自己定义。同时这里的1赋值给了数组的第一个元素


我们用VS2022编译器进行F11调试,打开监控界面,看一下arr数组的面貌:

这里可以看到,只有第一个元素arr[0]被赋值了,所以数组的赋值从下标由小到大依次赋值的!!!

我们了解完这一个之后,我们可以面对大部分的数组定义了,同时我也列出其他几种常见的类型,这样你就在数组的知识点看懂大部分代码

常见的初始化类型:

  •     int arr1[5] = {1,2,3,4,5};//完全初始化初始化数跟元素数相同,每个数字都有家可寻。
     
  •     int arr2[6] = {1};//不完全初始化第⼀个元素初始化为1,剩余的元素默认初始化为0 
     
  •     int arr3[3] = {1, 2, 3, 4};//错误的初始化 初始化项太多 【违法越界】
     
  •     int arr4[] = {};//错误的写法【初始化和元素数必须要有一个!!!!不然编译器无法识别】
     
  •     int arr5[] = {0};//特殊初始化//通过初始化的个数,判断元素的个数,只有1个元素
     
  •     int arr6[] = {1,2,3};//特殊初始化//有3个元素
     
  •     char arr7[]="abc";//字符数组初始化//字符数组【字符串定义数组】
     

 

 2.字符数组的初始化

下面就是的3种书写方式:

 //   char ch[9] = {0};//不完全初始化
//    char ch2[9] = { 'a','b','c'};//各字符初始化
//    char ch3[9] = "abc";//字符串初始化

 不管如何,字符初始化的就这三种方式,接下来我们来重点看一下两种初始化的对比({ 'a','b','c'}和"abc"):

探索方法:F10进入分布调试页面,F11分步调节,在监视页面,输入数组名,观看其储存形式。

问题一:字符类型的ch数组为首位‘0’但是在内存中其他元素是什么哪?

字符数组ch监视图 

解答:观察监视图可知:字符数组的首元素0以‘/0’的方式储存在内存中。 

问题二:字符类型的数组ch2中的'a','b','c'是怎么储存的,以及ch3和ch2如此相近,是怎么储存的??相同吗?

字符数组ch2和ch3的初始化监视图 

解答:监视图可知,两者书写方式虽然不同,但是储存形式是相同的,所以在初始化书写中,是一样的,但要注意的是ch2中的字符是单引号'a',ch3中的数组是双引号"" 


!!!!!!!!!!!!!!    下    标       !!!!!!!!!!!!!!!!!

这是新手很容易犯的错误知识点,本萌新也是,一定要记得下标从0开始,

   请让我为大家讲解:

                                     int arr[10]={1,2,3,4,5,6,7,8,9,10}
 

                                   元素 1,2,3,4,5,6,7,8,9,10

                                   下标 0,1,2,3,4,5,6,7,8, 9

虽然元素是1—-10,但是下标是0——9,所以在引用是数组是从arr[0]——arr[9],没有arr[10]

这一点一定要注意!!!!!


1.3.数组的使用

能够定义数组,那么就要提到如何使用数组和数组的输入和输出

即三个模块1.一维数组的引用 2.一维数组的输入3.一维数组的输出

1.数组的引用

假设定义了 int arr[10]={1,2,3}

                  int c=0;

引用:c=arr[9];


 

2.数组的输入

因为数组是一个多数字的集合,不可能一次性输完,所以要用到循坏语句进行循环输入,讲每个输入的值储存到对应的数组的元素中,直到达到元素值为止。

一个元素一个元素的输入,中间用空格隔开哈!!!!!

  for (i = 0; i < 10; i++)
   {
     scanf("%d", &arr[i]);
   }


 

 3.数组的输出

与输入类似,在循坏的基础上逐个进行输出,逐个将每个元素进行输出。

一个元素一个元素的有序输出!!!!

  for (i = 0; i < 10; i++)
   {
     printf("%d", arr[i]);
   }


 

 数组传参(重点!!!!!)

这里由于篇幅原因,我们本篇只分析数据结构中常见的几种方式,具体各种数组传参详情请见

【C语言指南】数组传参规则详解_如何传一个实参数组-CSDN博客

1.一维数组传参:
#include<stdio.h>
void Print(int arr2[], int sz)
{
    int i = 0;
    for (i = 0;i < sz;i++)
    {
        printf("%d ", arr2[i]);
    }
}
int main()
{
    int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
    int sz = sizeof(arr1) / sizeof(arr1[0]);//求数组元素的个数
    Print(arr1, sz);
    return 0;
}

我们可以发现arr1和arr2的地址相同,说明实参传递给形参时,形参并没有开辟新的空间,说明形参和实参是同⼀个数组,同时arr2的类型居然是int*类型(指针变量),其实数组传参,传递的是数组首元素的地址,通过地址可以找到一个个元素。若重新开辟一个新的数组会消耗大量的内存,所以传递的不是数组而是地址!


2.一维数组传参的本质:
#include <stdio.h>
void test(int arr[])//重点
{
	int sz2 = sizeof(arr) / sizeof(arr[0]);
	printf("sz2 = %d\n", sz2);
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz1 = sizeof(arr) / sizeof(arr[0]);
	printf("sz1 = %d\n", sz1);
	test(arr);//重点
	return 0;
}

我们发现在函数内部是没有正确获得数组的元素个数,这又是为什么呢?你也许会想,指针怎么这么…(此处省略一万字),要尝试先接受它,以后学习多了自然都解释地清了。

  • 这就要学习数组传参的本质了,上个小节我们学习了:数组名是数组首元素的地址;那么在数组传参的时候,传递的是数组名,也就是说本质上数组传参传递的是数组首元素的地址。所以函数形参的部分理论上应该使用指针变量来接收首元素的地址
     
  • 那么在函数内部我们写sizeof(arr) 计算的是⼀个地址的大小(单位字节)而不是数组的大小(单位字节)。正是因为函数的参数部分是本质是指针,所以在函数内部是没办法求的数组元素个数的。
     
  • 那形参为什么可以写成数组的形式呢?这是因为C语言考虑到了学者的感受,在学习数组的时候,如果一来就传地址,形参用指针变量来接收,学者会非常地疑惑的。所以说C语言并不是这么冷若冰霜的。

总结⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。


3.二维数组传参的本质:
#include <stdio.h>
void test(int a[3][5], int r, int c)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < r; i++)
	{
		for (j = 0; j < c; j++)
		{
			printf("%d ", a[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7} };
	test(arr, 3, 5);
	return 0;
}

 这里实参是⼆维数组,形参也写成⼆维数组的形式,那还有什么其他的写法吗?

重点:

  • 二维数组在内存中是连续存储的。
  • 二维数组可以理解为一维数组的数组,二维数组的每一行可以看作是一个一维数组。
  • 二维数组名也是首元素的地址,这里的首元素是指第一行数组,传过去的是第一行这个一维数组的地址,也就是arr[0]的地址。
  • 第一行的⼀维数组的类型就是 int [5] ,所以第一行的地址的类型就是数组指针类型 int(*)[5] 。

 

⼆维数组传参,形参的部分可以写成数组,也可以写成指针形式,如下:

#include <stdio.h>
void test(int(*p)[5], int r, int c)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < r; i++)
	{
		for (j = 0; j < c; j++)
		{
			printf("%d ", *(*(p + i) + j));//等价于p[i][j]
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7} };
	test(arr, 3, 5);
	return 0;
}
  • p:数组首元素的地址,也就是一维数组arr[0]的地址。
  • p+i:跳过 i 个 int[5] 这样的数组(p的类型是数组指针),指向arr[i],p+i 就是一维数组 arr[i] 的地址。
  • *(p+i):访问一维数组arr[i],等价于一维数组arr[i],而 arr[i] 是数组名,又是数组首元素的地址,也就是 arr[i][0] 的地址。
  • *(p + i) + j:由于 *(p+i)是 arr[i][0] 的地址,所以 +j 跳过 j 个整形(指向整形),也就是 arr[i][j] 的地址。
  • *( *(p + i) + j):由于 *(p + i) + j 是 arr[i][j] 的地址,进行解引用操作,就是找到 arr[i][j]。
  • 最终:*( *(p + i) + j) 等价于 arr[i][j]。

 

如图:
在这里插入图片描述
 

二、指针变量

指针的内容有非常多,所以这里我们只讲常用的,入门的,比较容易理解的,如果各位对指针感兴趣,可以跳转到我的另一篇博客的指针系列【1】,这里有更加详细的内容【超详细指针系列】指针超详细讲解------从入门到应用-----一步一步将你带入深挖指针【1】_指针教程-CSDN博客


 

2.1内存与地址

1.内存

单讲内存和地址太枯燥,来举个例子吧:

一天小玉突然想起了好多年不联系的挚友阿雪,想联络联络感情,所以想要去啊雪家,所以打了电话:

小玉:”牢底,最近怎么样“

阿雪:“你是??”

小玉:“嗯?不至于我的声音都听不出来吧”

阿雪:“哈哈哈,原来是你,怎么了??”

小玉:“好久没见,我可以找你去打CSGO学C语言吗?"

阿雪:"哈哈哈哈,好呀,好久没见你了,对了,你不知道我家地址吧”

阿雪:“我发你”

小玉:”嗯嗯,多时不见,期于君遇“

阿雪:“滚,别在我面前犯二哈哈哈哈哈”

小玉:“哈哈哈哈哈哈”

(对话结束)


!!其实在这里这段对话中,已经显示出了内存的本质

!!小雪给出的地址面向的对象----楼层,其实就是就是内存,内存嘛,其实就是存东西的地方;


可问题来了,小玉到达了小区,看到了小雪的单元楼(内存),而接下来问题就出现了,那间房子是哪,所以小玉问了小雪房间号。。。。。

对!!!,这么大个单元楼,怎么多房间,要怎么分辨小雪的房间哪?

所以我们给这里的每一个内存单元(房间)编制了房间号

一楼:101、102、103、104........

二楼:201、202、203、204........

三楼:.......................

说回正题:

内存相比大家都不陌生,在你买电脑的时候总会了解到电脑是多少g内存的,如4G/8G/16G/32G而这些到底是怎么划分的哪????

其实,内存不是单独的一个大整体,而是一个大的空间被划分为一个一个的小空间,我们把它叫做内存单元,

每个内存单元都是1个字节。1个字节里面8个比特位,每个比特位用2进制表示,所以一个字节可以表示2^8个情况,每个内存单元都有编号,而这些编号,我们也叫做地址

通俗一点来讲:

内存单元就是一个宿舍,8个比特位就是8人间,内存就是整个宿舍楼而每个宿舍的编号==地址,在c语言中我们给地址起了一个新的名字:指针

所以


宿舍编号==地址==指针


2.2 指针变量和地址

1. 取地址操作符(&)

如果说创建变量是向内存申请空间,但是每个空间都有着属于自己的编号

那么取地址操作符就是把这些编号拿出来,在储存到一个新的内存单元中。

但是问题来了:如int向内存申请4个字节,那么取地址操作符难道要每个字节的地址都拿出来吗

不妨做个程序来探究一下

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
	int m = 4;
	printf("%p", &m);
    return 0;
}

打印的地址是最小的地址(首元素地址)

同时我们也可以发现,内存是连续存放的!!!! 

2.、指针变量和解引用操作符(*)


2.1指针变量     

 当我们用去地址操作符取出地址时如:0x006FFD69,那么我们将这个东西存在哪里哪?

答案是指针变量!!!!

例子:

#include<stdio.h>

int main()

{

int a=0;

int*p=&a;//讲取出的a的地址存放到指针变量p中

return 0;

}

2.2 指针类型的拆分

举个例子:

int a=10;
 

int *p=&a;

 我们已经知道

  • p的类型是int*类型
  • ‘*’是指p的类型是指针
  • int的意思是指p所指向的对象为整型(指向的是整型(int)的对象)
2.3 解引用操作符

对的,解引用操作符也是我们的 老  朋  友   * 号

int a=10;

int *p=&a;//指针变量存放a的地址

*p=10;//解引用操作将p所指向的对象的值改为10

2.4指针变量的大小

先说结论

指针变量的大小只有两个值:4和8;

!在32位的平台上运作时,指针变量的大小为4。

!在64位的平台上运作时,指针变量的大小为8。

为什么捏???

先直观的感受一下指针变量的大小的运作结果:

    printf("%d\n", sizeof(char*));
    printf("%d\n", sizeof(int*));
    printf("%d\n", sizeof(double*));
    printf("%d\n", sizeof(short*));
    printf("%d\n", sizeof(float*));

我们看一下32位(x86)的平台下运行的结果

结果显示在x86的平台下运行的结果,无论什么类型都是4个字节。

再看一下64位的(x64)的平台下运行的结果 :

 结果显示都是8个字节


简单来说:

  • 32位机器有32根地址总线,将电信号转换为数字信号时,32个二进制产生的序列,我们可以看作位1个地址的产生,那么一个地址是由32个bite位储存的,32bit==4个字节,所以
  • 32位下的指针变量就是4个字节,64位也相同..........................................
  • 注意指针变量的大小和类型是⽆关的,只要指针类型的变量在相同的平台下,大小都是相同的。!!!

以上内容是指针入门级别,先熟知,之后内容包括,指针类型的意义,指针解引用的权重,指针运算。如果感兴趣,请大家去这一章节开头找到我的另一篇博客,去进行详细了解。 

小知识:typedef重命名:

typedef重命名函数
typedef是用来重命名的,可以将复杂的名字简单化规范化 

比如我们命名了一个结构体叫做jinfsjajngijiasogjoiasjda(随便打的)

我们每次调用都要写很长一段复杂的东西,但是有了typedef这个东西,我们可以将它重命名为js,对!就这两个字符,就可以表达这个结构体

typedef unsigned int uint;

//将unsigned int 重命名uint

如果是指针类型,能否重命名呢?其实也是可以的,比如,将 int* 重命名为 ptr_t ,这样写:

typedef int* ptr_t

但是对于数组指针和函数指针稍微有点区别:
比如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ,那可以这样写:

typedef int(*parr_t)[5]

函数指针类型的重命名也是⼀样的,比如,将 void(*)(int) 类型重命名为 pf_t ,就可以这样写: 

typedef void(*pfun_t)(int);//新的类型名必须在*的右边

那么要简化代码2,可以这样写:

typedef void(*pfun_t)(int);

pfun_t signal(int, pfun_t); 

重点

给出typedef命名一个数组的例子:

#include<stdio.h>
int main()
{
typedef int IntArray[5]//定义一个包含5个int类型元素的数组类型IntArray
IntArray arr={1,2,3,4,5};// 使用 IntArray 声明一个数组
 
for(int i=0;i<5;i++)
{
printf("%d",arr[i]);
 
}
return 0;
}

 

3.动态内存管理

前言:

当我们要开辟一块连续的内存空间时,我们第一时间想到的可能是数组。但是一但开辟了数组,数组的大小就确定了,无法调整数组的大小。
有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。
于是动态内存开辟函数(malloc,calloc,realloc,free)应运而生,下文带您一一了解其中的奥秘。


一. malloc 和 free

1. malloc

void* malloc(size_t size);

解释:在堆区中开辟一块大小为 size 个字节的空间,返回指向这块空间的起始地址(泛型指针void*)。

因为这块空间存放的数据类型不知(由程序员自己确定),所以用泛型指针接收该地址,在使用的时候记得养成一个好习惯:

强制类型转换为自己需要的数据类型。

  • 如果开辟成功,则返回一个指向开辟好空间的指针。
  • 如果开辟失败,则返回一个 NULL 指针,因此malloc的返回值一定要做检查。
  • 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候程序员自己来决定。
  • 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。

2. free

void free(void* ptr);

解释:free是用来对动态内存的释放和回收的。free 对指针 ptr 指向的内容释放掉,但是指针仍然指向这块空间,若后面不再使用,及时将 ptr 置为 NULL,否则产生野指针。

如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。

如果参数 ptr 是NULL指针,则函数什么事都不做

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
    //在堆区申请10个整形空间
    int* p=(int*)malloc(10*sizeof(int));
    if (p == NULL)
    {
        //开辟空间失败
        perror("malloc");//打印错误信息
        //printf("%s\n", strerror(errno));//也是打印错误信息
        return 1;
    }
    //使用这块空间
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        *(p + i) = i + 1;
    }
    //打印这块空间
    for (i = 0; i < 10; i++)
    {
        printf("%d ", *(p + i));
    }
    //释放这块空间
    free(p);//将这块空间还给了操作系统,我们已经没有权限再使用这块空间了
            //但是p仍然指向那块空间

    p = NULL;//若不将p置为NULL,那么p就是野指针
    return 0;
}

总结:

  • 动态内存开辟的函数头文件都是 stdlib.h
  • 如果不释放的话,程序结束的时候也会被操作系统自动释放。
  • 但是为了防止内存泄漏,将其置为NULL。这是一个好习惯。


二. calloc

void* calloc(size_t num, size_t size);

解释:在堆区中开辟一块大小为 num * size 个字节的空间,返回指向这块空间的起始地址,其中 num 为数据的个数size 为单个数据的字节数,同时把申请的空间的每个字节初始化为全为0。

#include<stdio.h>
#include<stdlib.h>
int main()
{
    //在堆区申请10个整形空间
    int* p = (int*)calloc(10, sizeof(int));
    if (p == NULL)
    {
        perror("calloc");
        return 1;
    }
    //使用空间
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        printf("%d ", *(p + i));
    }
    //释放
    free(p);
    p = NULL;
    return 0;
}


 

三. realloc

void* realloc (void* ptr, size_t size);

解释:调整动态内存开辟的空间,ptr 是那块空间的起始地址,size 是调整后的那块空间的字节的个数,返回指向这块空间的起始地址

#include<stdio.h>
#include<stdlib.h>
int main()
{
    //在堆区申请10个整形空间
    int* p = (int*)malloc(10 * sizeof(int));
    if (p == NULL)
    {
        perror("malloc");
        return 1;
    }
    //调整空间——变成20个整形空间
    int* ptr = (int*)realloc(p, 20 * sizeof(int));//注意:要用新的指针来接收
    if (ptr != NULL)
    {
        p = ptr;
    }
    else
    {
        //开辟失败
        return 1;
    }
    int i = 0;
    for (i = 0; i < 20; i++)
    {
        *(p + i) = i + 1;
    }
    for (i = 0; i < 20; i++)
    {
        printf("%d ", *(p + i));
    }
    //释放
    free(p);
    p = NULL;
    return 0;
}

注意:也许有些人有疑问为什么要用新的指针接收返回的地址,直接用原来的指针接收不行吗?答案是不行的,在realloc调整动态内存开辟的空间有3中情况,代码如下:

int main()
{
    int* p = (int*)malloc(10);
    //...
    if (p != NULL)
    {
        int* ptr = (int*)realloc(p, 20);
        //...
    }
    return 0;
}



情况1:

开辟的空间后面有足够且连续的空间,只需返回空间的起始地址即可。、

情况2:

如果后续的空间不够,realloc 函数直接在堆区找一块新的满足大小的空间,将旧的地址,拷贝到新的地址。
自动释放旧的地址指向的空间,不需要手动 free,返回新的空间的起始地址。


情况3:

堆区已经没有满足情况的连续空间了,返回NULL。

realloc函数也能开辟空间,代码如下:

#include<stdio.h>
#include<stdlib.h>
int main()
{
    int* p = (int*)realloc(NULL, 10 * sizeof(int));//等价于malloc(40)
    if (p == NULL)
    {
        //...
    }
    return 0;
}

 

4.结构体

如果我写的不明白,查看对标链接,感谢各位支持!!!!!!

​​​​​​自定义类型:结构体+结构体内存对齐+结构体实现位段_自定义结构体位对齐如何查询-CSDN博客自定义类型:结构体+结构体内存对齐+结构体实现位段_自定义结构体位对齐如何查询-CSDN博客​​​​​​自定义类型:结构体+结构体内存对齐+结构体实现位段_自定义结构体位对齐如何查询-CSDN博客

前言:
学习了数组后发现数组中的元素只能是相同类型的变量,那么有没有可以存放不同类型的变量呢?
结构体:一些值的集合,这些值称为成员变量,结构体的每个成员可以是不同类型的变量



一.结构体


1.结构体的声明

struct tag
{
    member-list;//结构体成员列表
}variable-list;//结构体变量列表


例如:描述一个人

struct Person {
    int age;//年龄
    char name[50];//姓名
    float height;//身高
};//封号不能丢


2.结构体变量成员访问操作符

结构体变量.结构体成员名。
结构体指针变量->结构体成员名。

#include <stdio.h>
struct Person
{
    int age;
    char name[50];
    float height;
}p1 = { 20,"zhangsan",185.5 }, * ps;//全局变量(*ps:结构体指针ps)

int main()
{
    struct Person p2 = { 18,"lisi",173.2 };//局部变量
    struct Person p3 = { 19,"wangwu",180.8 };//局部变量
    ps = &p3;
    printf("%d %s %.1f\n", p1.age, p1.name, p1.height);//结构体成员访问操作符:.
    printf("%d %s %.1f\n", p2.age, p2.name, p2.height);

    printf("%d %s %.1f\n", (*ps).age, (*ps).name, (*ps).height);
    printf("%d %s %.1f\n", ps->age, ps->name, ps->height);//结构体成员访问操作符:->等价于先*再.
    return 0;
}



3.结构体传参

  1. 传结构体。
  2. 传结构体的地址。
#include <stdio.h>
struct Person
{
    int age;
    char name[50];
    float height;
};
void test1(struct Person p)//用结构体接收
{
    printf("%d %s %.1f\n", p.age, p.name, p.height);
}
void test2(struct Person* p)//用结构体指针接收
{
    printf("%d %s %.1f\n", p->age, p->name, p->height);
}
int main()
{
    struct Person p1 = { 20,"zhangsan",185.5 };
    test1(p1);//传结构体
    test2(&p1);//传结构体的地址
    return 0;
}

思考:我们发现二者都可以成功访问结构体成员,那二者有什么区别呢?

  1. 传递结构体时:其实函数内部创建了一个临时结构体变量存放传入的结构体,当结构体很大时会额外占用空间不划算。(本质上是值传递)。
  2. 传递结构体地址时:只需创建4个字节结构体指针变量,通过其来访问结构体成员,可以大大节省空间。(本质上是地址/指针传递)。
  3. 推荐传递结构体地址


4.匿名结构体

//匿名结构体类型 

struct//不完全声明,由于没有名字,无法在其之后创建变量
{
    int age;
    char name[50];
    float height;
}s1, s2;//在结构体声明的时候直接创建变量,不能在其之后创建变量了,只能使用一次
int main()
{
    struct s3;//error
}


当只需使用一次可以使用(在声明结构体时,直接创建变量,不能在其之后创建变量了)。
思考:以下代码行不行

struct
{
    int age;
    char name[50];
    float height;
}s1;
struct
{
    int age;
    char name[50];
    float height;
}*ps;

int main()
{    
    ps = &s1;//?
    return 0;
}


答案:不行,看似一样,其实这两个结构体是不同类型的,只是成员变量相同的不同结构体类型,二者不兼容。没有名字导致的问题)。


5.结构的自引用

比如:定义一个链表的节点

struct Node
{
     int data;//存放数据
     struct Node* next;//存放指针
};

 

结尾祝福语

以上就是数据结构可能有用到的知识点,如果有人能看到这,我真感动呀!!!!!!,累死我了!!!!我知道你对我的文章是非常认可的,当然如果有错误,欢迎随时来指出,我们一起探讨,如果有疑问,直接私信我即可,看到的第一时间我会给你解答!!!!!!!也请各位给我个三连(点赞收藏评论!!!)

怎么说哪?结束了,第一次写万字文章,肯定有写的不好的地方,多多见谅吧!!!!最后祝你们四级全过,天天开心!!!!!!

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/881541.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

学习笔记——RegNet:Designing Network Design Spaces

RegNet&#xff1a;Designing Network Design Spaces RegNet&#xff1a;设计一个网络设计空间 论文地址&#xff1a; https://arxiv.org/pdf/2003.13678 1、前言 在这项工作中&#xff0c;作者提出了一种新的网络设计范例。 作者的目标是帮助增进对网络设计的理解并发现跨设置…

网络安全:建筑公司会计软件遭受暴力攻击

黑客正在暴力破解基金会会计服务器上高权限账户的密码&#xff0c;这些账户广泛用于建筑行业&#xff0c;从而侵入企业网络。 这一恶意活动最先被 Huntress 发现&#xff0c;其研究人员于 2024 年 9 月 14 日检测到了此次攻击。 Huntress 已经发现这些攻击对管道、暖通空调、…

元学习的简单示例

代码功能 模型结构&#xff1a;SimpleModel是一个简单的两层全连接神经网络。 元学习过程&#xff1a;在maml_train函数中&#xff0c;每个任务由支持集和查询集组成。模型先在支持集上进行训练&#xff0c;然后在查询集上进行评估&#xff0c;更新元模型参数。 任务生成&…

时间安全精细化管理平台存在未授权访问漏洞

漏洞描述 登录--时间&amp;安全精细化管理平台存在未授权访问漏洞导致与员工信息泄露 FOFA&#xff1a; body"登录--时间&amp;安全精细化管理平台" 漏洞复现 POC: IP/acc/_checkinoutlog_/

Linux开发工具(git、gdb/cgdb)--详解

目录 一、Linux 开发工具分布式版本控制软件 git1、背景2、使用 git&#xff08;1&#xff09;预备工作——安装 git&#xff1a;&#xff08;2&#xff09;克隆远程仓库到本地&#xff08;3&#xff09;把需要提交的代码拷贝到本地仓库&#xff08;4&#xff09;提交本地仓库文…

基于协同过滤+SpringBoot+Vue的剧本杀服务平台系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于协同过滤JavaSpringBootV…

Liveweb视频汇聚平台支持GB28181转RTMP、HLS、RTSP、FLV格式播放方案

GB28181协议凭借其在安防流媒体行业独有的大统一地位&#xff0c;目前已经在各种安防项目上使用。雪亮工程、幼儿园监控、智慧工地、物流监控等等项目上目前都需要接入安防摄像头或平台进行直播、回放。而GB28181协议作为国家推荐标准&#xff0c;目前基本所有厂家的安防摄像头…

【数据结构-二维差分】力扣2536. 子矩阵元素加 1

给你一个正整数 n &#xff0c;表示最初有一个 n x n 、下标从 0 开始的整数矩阵 mat &#xff0c;矩阵中填满了 0 。 另给你一个二维整数数组 query 。针对每个查询 query[i] [row1i, col1i, row2i, col2i] &#xff0c;请你执行下述操作&#xff1a; 找出 左上角 为 (row1…

Qt圆角窗口

Qt圆角窗口 问题&#xff1a;自己重写了一个窗口&#xff0c;发现用qss设置圆角了&#xff0c;但是都不生效&#xff0c;不过子窗口圆角都生效了。 无边框移动窗口 bool eventFilter(QObject *watched, QEvent *evt) {static QPoint mousePoint;static bool mousePressed f…

灵当CRM系统index.php存在SQL注入漏洞

文章目录 免责申明漏洞描述搜索语法漏洞复现nuclei修复建议 免责申明 本文章仅供学习与交流&#xff0c;请勿用于非法用途&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任 漏洞描述 灵当CRM系统是一款功能全面、易于使用的客户关系管理&#xff08;C…

在Linux中运行flask项目

准备 这里我准备了一个GitHub上某个大佬写的留言板的Flask项目&#xff0c;就用这个来给大家做示范了。 查看留言板的目录结构 查看主程序所用的库函数 只有一个第三方库 Flask 安装pip sudo apt install python3-pip -y测试 pip 安装成功 修改pip镜像源 修改pip的默认下载…

表格标记<table>

一.表格标记、 1table&#xff1a;表格标记 2.caption:表单标题标记 3.tr:表格行标记 4.td:表格中数据单元格标记 5.th:标题单元格 table标记是表格中最外层标记&#xff0c;tr表示表格中的行标记&#xff0c;一对<tr>表示表格中的一行&#xff0c;在<tr>中可…

嵌入式 开发技巧和经验分享

文章目录 前言嵌入式 开发技巧和经验分享目录1.1嵌入式 系统的 定义1.2 嵌入式 操作系统的介绍1.3 嵌入式 开发环境1.4 编译工具链和优化1.5 嵌入式系统软件开发1.6 嵌入式SDK开发2.1选择移植的系统-FreeRtos2.2FreeRtos 移植步骤2.3 系统移植之中断处理2.4系统移植之内存管理2…

搜索引擎onesearch3实现解释和升级到Elasticsearch v8系列(二)-索引

场景 首先介绍测试的场景&#xff0c;本文schema定义 pdm文档索引&#xff0c;包括nested&#xff0c;扩展字段&#xff0c;文档属性扩展&#xff0c;其中_content字段是组件保留字段&#xff0c;支持文本内容 索引 索引服务索引的操作&#xff0c;包括构建&#xff0c;put …

缓存数据和数据库数据一致性问题

根据以上的流程没有问题&#xff0c;但是当数据变更的时候&#xff0c;如何把缓存变到最新&#xff0c;使我们下面要讨论的问题 1. 更新数据库再更新缓存 场景&#xff1a;数据库更新成功&#xff0c;但缓存更新失败。 问题&#xff1a; 当缓存失效或过期时&#xff0c;读取…

C++——string的了解和使用

目录 引言 为什么要学习string 1.C语言中的字符串 2.C中的字符串 auto和范围for 1.auto 1.1 auto的介绍 1.2 注意事项 2.范围for 标准库中的string类 1.string类的迭代器 1.1 begin()与end()函数 1.2 rbegin()与rend()函数 2.string类的初始化和销毁 3.string类…

企业内网安全

企业内网安全 1.安全域2.终端安全3.网络安全网络入侵检测系统异常访问检测系统隐蔽信道检测系统 4.服务器安全基础安全配置入侵防护检测 5.重点应用安全活动目录邮件系统VPN堡垒机 6.蜜罐体系建设蜜域名蜜网站蜜端口蜜服务蜜库蜜表蜜文件全民皆兵 1.安全域 企业出于不同安全防…

【ArcGISProSDK】初识

简介 ArcGIS Pro SDK 提供四种主要的可扩展性模式&#xff1a;加载项、托管配置、插件数据源和 CoreHost 应用程序。 加载项 加载项是使用 .NET 以及 Esri 的桌面应用程序标记语言 &#xff08;DAML&#xff09; &#xff08;一种由 Esri 创建的 XML 语言&#xff09;创作的…

本地不能訪問linux的kafka服務

1.本地使用kafka客戶端工具連接kafka服務&#xff0c;提示連接失敗 2. 本地使用telnet ip port命令也失敗 3.查看zookeeper和kafka服務是否正常 ps -ef | grep zookeeper ps -ef | grep kafka 3.關閉操作系統的防火墻(僅限于測試使用) 3.1.禁用防火墙 systemctl stop firew…

【C语言零基础入门篇 - 7】:拆解函数的奥秘:定义、声明、变量,传递须知,嵌套玩转,递归惊艳

文章目录 函数函数的定义与声明局部变量和全局变量、静态变量静态变量和动态变量函数的值传递函数参数的地址传值 函数的嵌套使用函数的递归调用 函数 函数的定义与声明 函数的概念&#xff1a;函数是C语言项目的基本组成单位。实现一个功能可以封装一个函数来实现。定义函数的…