前言:欢迎各位光临本博客,这里小编带你直接手撕入门阶段的数据结构的C语言知识,让你不再看见数据结构就走不动道。文章并不复杂,愿诸君耐其心性,忘却杂尘,道有所长!!!!
如有有已经知道基础概念的小伙伴,直接根据目录表跳转到数组函数传参:
数组的定义其实非常非常简单;他就是是一组相同类型的集合,不理解也没关系,我给你举个例子就好了。
数组的定义:数组是一组相同类型的集合
给你讲个故事:
我认识一个朋友(纯属虚构),她是一个事业心非常强的人,无论任何东西都会以事业和学业为主,所以大多数有选择时,都会选择先忽略自己的感受的选项,平时也不注重打扮,以至于她的家非常乱,家里杂乱无章,到处找东西找不到。后来她喜欢上一个男生,感觉自己的一股屌丝样子配不上他,所以就开始捯饬自己,她开始把自己的房间整理,把袜子和袜子归类到一起,衣服和衣服归类到一起,所有一切的事物都开始重新归类。stop!!!!!! 对的,这里的袜子和袜子归类到一起,衣服和衣服归类到一起的结果就是数组,因为他们都是相同类型的集合!!!!!! 后来的后来,她也确实谈上了恋爱,但故事的结局,我相信应该由大家书写!!!
回归正题......................................讲完数组是什么,我们接下来看一下他的初始化和创建
type arr_name[常量值] 类型+数组名[元素值/下标]
int arr[10];//创建一个元素数为10的整型数组
double arr[10];//创建一个元素数为10的双浮点数数组
char arr[10]; //创建一个元素数为10的字符数组
int arr[10] 详细图解
我们知道一维数组是如何创建的了,那么接下来我们看他如何初始化
数组的初始化种类就好几种,但我会指出最常用的几种,大家记住即可。
首先先来说数组的一种初始化,也是最常用的初始化:
指在创建的基础上给一个或多个合理的值;而每个类型的初始化又存在差异,
本章的初始化我们就来讨论其差异性,这里没啥大用,做了解即可,看我慢慢给你解释!!!!
数组分为完全初始化和不完全初始化
先来看一个不完全初始化:
一维数组的初始化图
解释:arr与[10]结合,说明我们定义了一个空间为10个的数组,int表示空间的每个元素是为整型;
{1}在这里是什么意思?为什么定义了十个空间,这里只有一个元素?
这里叫做不完全初始化,后面给的初始化元素数少于定义的元素数, 后面的元素要我们在后续的程序中自己定义。同时这里的1赋值给了数组的第一个元素
我们用VS2022编译器进行F11调试,打开监控界面,看一下arr数组的面貌:
这里可以看到,只有第一个元素arr[0]被赋值了,所以数组的赋值是从下标由小到大依次赋值的!!!
我们了解完这一个之后,我们可以面对大部分的数组定义了,同时我也列出其他几种常见的类型,这样你就在数组的知识点看懂大部分代码了
常见的初始化类型:
下面就是的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.一维数组的引用 2.一维数组的输入3.一维数组的输出
假设定义了 int arr[10]={1,2,3}; int c=0; 引用:c=arr[9];
因为数组是一个多数字的集合,不可能一次性输完,所以要用到循坏语句进行循环输入,讲每个输入的值储存到对应的数组的元素中,直到达到元素值为止。
一个元素一个元素的输入,中间用空格隔开哈!!!!!
for (i = 0; i < 10; i++) { scanf("%d", &arr[i]); }
与输入类似,在循坏的基础上逐个进行输出,逐个将每个元素进行输出。
一个元素一个元素的有序输出!!!!
for (i = 0; i < 10; i++) { printf("%d", arr[i]); }
这里由于篇幅原因,我们本篇只分析数据结构中常见的几种方式,具体各种数组传参详情请见
【C语言指南】数组传参规则详解_如何传一个实参数组-CSDN博客
#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*类型(指针变量),其实数组传参,传递的是数组首元素的地址,通过地址可以找到一个个元素。若重新开辟一个新的数组会消耗大量的内存,所以传递的不是数组而是地址!
#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;
}
我们发现在函数内部是没有正确获得数组的元素个数,这又是为什么呢?你也许会想,指针怎么这么…(此处省略一万字),要尝试先接受它,以后学习多了自然都解释地清了。
总结:⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。
#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;
}
这里实参是⼆维数组,形参也写成⼆维数组的形式,那还有什么其他的写法吗?
重点:
⼆维数组传参,形参的部分可以写成数组,也可以写成指针形式,如下:
#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;
}
如图:
指针的内容有非常多,所以这里我们只讲常用的,入门的,比较容易理解的,如果各位对指针感兴趣,可以跳转到我的另一篇博客的指针系列【1】,这里有更加详细的内容【超详细指针系列】指针超详细讲解------从入门到应用-----一步一步将你带入深挖指针【1】_指针教程-CSDN博客
单讲内存和地址太枯燥,来举个例子吧:
一天小玉突然想起了好多年不联系的挚友阿雪,想联络联络感情,所以想要去啊雪家,所以打了电话:
小玉:”牢底,最近怎么样“
阿雪:“你是??”
小玉:“嗯?不至于我的声音都听不出来吧”
阿雪:“哈哈哈,原来是你,怎么了??”
小玉:“好久没见,我可以找你去打CSGO学C语言吗?"
阿雪:"哈哈哈哈,好呀,好久没见你了,对了,你不知道我家地址吧”
阿雪:“我发你”
小玉:”嗯嗯,多时不见,期于君遇“
阿雪:“滚,别在我面前犯二哈哈哈哈哈”
小玉:“哈哈哈哈哈哈”
(对话结束)
!!其实在这里这段对话中,已经显示出了内存的本质:
!!小雪给出的地址面向的对象----楼层,其实就是就是内存,内存嘛,其实就是存东西的地方;
可问题来了,小玉到达了小区,看到了小雪的单元楼(内存),而接下来问题就出现了,那间房子是哪,所以小玉问了小雪房间号。。。。。
对!!!,这么大个单元楼,怎么多房间,要怎么分辨小雪的房间哪?
所以我们给这里的每一个内存单元(房间)编制了房间号,
一楼:101、102、103、104........ 二楼:201、202、203、204........ 三楼:.......................
说回正题:
内存相比大家都不陌生,在你买电脑的时候总会了解到电脑是多少g内存的,如4G/8G/16G/32G而这些到底是怎么划分的哪????
其实,内存不是单独的一个大整体,而是一个大的空间被划分为一个一个的小空间,我们把它叫做内存单元,
每个内存单元都是1个字节。1个字节里面8个比特位,每个比特位用2进制表示,所以一个字节可以表示2^8个情况,每个内存单元都有编号,而这些编号,我们也叫做地址
通俗一点来讲:
内存单元就是一个宿舍,8个比特位就是8人间,内存就是整个宿舍楼而每个宿舍的编号==地址,在c语言中我们给地址起了一个新的名字:指针
所以
宿舍编号==地址==指针
如果说创建变量是向内存申请空间,但是每个空间都有着属于自己的编号
那么取地址操作符就是把这些编号拿出来,在储存到一个新的内存单元中。
但是问题来了:如int向内存申请4个字节,那么取地址操作符难道要每个字节的地址都拿出来吗
不妨做个程序来探究一下
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
int m = 4;
printf("%p", &m);
return 0;
}
打印的地址是最小的地址(首元素地址)
同时我们也可以发现,内存是连续存放的!!!!
当我们用去地址操作符取出地址时如:0x006FFD69,那么我们将这个东西存在哪里哪?
答案是指针变量!!!!
例子:
#include<stdio.h>
int main()
{
int a=0;
int*p=&a;//讲取出的a的地址存放到指针变量p中
return 0;
}
举个例子:
int a=10; int *p=&a;
我们已经知道
对的,解引用操作符也是我们的 老 朋 友 * 号
int a=10; int *p=&a;//指针变量存放a的地址 *p=10;//解引用操作将p所指向的对象的值改为10
先说结论
指针变量的大小只有两个值: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个字节
简单来说:
以上内容是指针入门级别,先熟知,之后内容包括,指针类型的意义,指针解引用的权重,指针运算。如果感兴趣,请大家去这一章节开头找到我的另一篇博客,去进行详细了解。
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;
}
前言: 当我们要开辟一块连续的内存空间时,我们第一时间想到的可能是数组。但是一但开辟了数组,数组的大小就确定了,无法调整数组的大小。 有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。 于是动态内存开辟函数(malloc,calloc,realloc,free)应运而生,下文带您一一了解其中的奥秘。
void* malloc(size_t size);
解释:在堆区中开辟一块大小为 size 个字节的空间,返回指向这块空间的起始地址(泛型指针void*)。
因为这块空间存放的数据类型不知(由程序员自己确定),所以用泛型指针接收该地址,在使用的时候记得养成一个好习惯:
强制类型转换为自己需要的数据类型。
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;
}
总结:
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;
}
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;
}
开辟的空间后面有足够且连续的空间,只需返回空间的起始地址即可。、
如果后续的空间不够,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;
}
如果我写的不明白,查看对标链接,感谢各位支持!!!!!!
自定义类型:结构体+结构体内存对齐+结构体实现位段_自定义结构体位对齐如何查询-CSDN博客自定义类型:结构体+结构体内存对齐+结构体实现位段_自定义结构体位对齐如何查询-CSDN博客自定义类型:结构体+结构体内存对齐+结构体实现位段_自定义结构体位对齐如何查询-CSDN博客
前言: 学习了数组后发现数组中的元素只能是相同类型的变量,那么有没有可以存放不同类型的变量呢? 结构体:一些值的集合,这些值称为成员变量,结构体的每个成员可以是不同类型的变量。
struct tag
{
member-list;//结构体成员列表
}variable-list;//结构体变量列表
例如:描述一个人
struct Person {
int age;//年龄
char name[50];//姓名
float height;//身高
};//封号不能丢
结构体变量.结构体成员名。 结构体指针变量->结构体成员名。
#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;
}
#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;
}
思考:我们发现二者都可以成功访问结构体成员,那二者有什么区别呢?
//匿名结构体类型
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;
}
答案:不行,看似一样,其实这两个结构体是不同类型的,只是成员变量相同的不同结构体类型,二者不兼容。(没有名字导致的问题)。
比如:定义一个链表的节点
struct Node
{
int data;//存放数据
struct Node* next;//存放指针
};
以上就是数据结构可能有用到的知识点,如果有人能看到这,我真感动呀!!!!!!,累死我了!!!!我知道你对我的文章是非常认可的,当然如果有错误,欢迎随时来指出,我们一起探讨,如果有疑问,直接私信我即可,看到的第一时间我会给你解答!!!!!!!也请各位给我个三连(点赞收藏评论!!!)
怎么说哪?结束了,第一次写万字文章,肯定有写的不好的地方,多多见谅吧!!!!最后祝你们四级全过,天天开心!!!!!!