【数据结构】前置基础

这篇具有很好参考价值的文章主要介绍了【数据结构】前置基础。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1、指针

1.1 指针是什么

指针是什么?
指针理解的 2 个要点:
1)指针是内存中一个最小单元的编号,也就是地址
2)平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
总结:指针就是地址,口语中说的指针通常指的是指针变量

那我们就可以这样理解:【数据结构】前置基础,考研408之数据结构,数据结构

1.2 指针和指针类型

#include<stdio.h>
int main()
{
	char* pc;
	int* pa;
	double* pd;
	printf("%d\n",sizeof(pc));    // 4
	printf("%d\n", sizeof(pa));   // 4
	printf("%d\n", sizeof(pd));   // 4
	return 0;
}

指针作用1:指针类型决定了在解引用时能访问几个字节(指针的权限)

 
#include<stdio.h>
int main()
{
	int a = 0x11223344;
	int* pa = &a;
	*pa = 0;
	char* pc = &a;
	*pc = 0;
	// 指针类型决定了在解引用的时候以此能访问几个字节(指针的权限)
	// int*     --> 4
	// char*    --> 1
	// double*  --> 8
	return 0;
}

指针作用2:指针类型决定了//指针作用2:指针类型决定了指针向前或者向后走一步,走多大距离(单位是字节)

1.3 野指针

概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
成因:

#include<stdio.h>
int main()
{
	/*int a;
	printf("%d\n", a);*/
	//未初始化
	//int* p;  //野指针
	//*p = 20;
	//越界访问
	int arr[10] = { 0 };
	int* p = arr;
	int i = 0;
	for (i = 0; i <= 10; i++)
	{
		*p = i;
		p++;
	}
 
	return 0;
}

如何规避野指针?如下:

  1. 指针初始化

  2. 小心指针越界

  3. 指针指向空间释放即使置NULL

  4. 避免返回局部变量的地址 5. 指针使用之前检查有效性

1.4 指针运算

#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < sz; i++)
	{
		*(p + i) = i + 1;
	}
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));  // 1 2 3 4 5 6 7 8 9 10
	}
	return 0;
}

1.5 指针和数组

#include<stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	/*for (i = 0; i < 10; i++)
	{
		printf("%p==%p\n", p + i, &arr[i]);
	}*/
	for (i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);    // 1 2 3 4 5 6 7 8 9 10
	}
	return 0;
}

1.6 二级指针

指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里? 这就是 二级指针
【数据结构】前置基础,考研408之数据结构,数据结构

2.结构体

2.1 结构体的声明

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

struct tag  //tag是标签名,是可以根据需求改的
{
   member-list;  //成员列表
}variable-list;  //全局变量

例如描述一个学生:

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

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

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

有了结构体类型,那如何定义变量,其实很简单。

struct Point
{
	int x;
	int y;
}p1; //声明类型的同时定义变量p1
 
struct Point p2; //定义结构体变量p2
 
//初始化:定义变量的同时赋初值。
struct Point p3 = { x , y };
 
struct Stu        //类型声明
{
	char name[15];//名字
	int age;      //年龄
};
struct Stu s = { "zhangsan", 20 };//初始化
 
struct Node
{
	int data;
	struct Point p;
	struct Node* next;
}n1 = { 10, {4,5}, NULL }; //结构体嵌套初始化
struct Node n2 = { 20, {5, 6}, NULL };//结构体嵌套初始化

2.3 结构体成员的访问

2.3.1 结构体变量访问成员

结构变量的成员是通过点操作符(.)访问的。点操作符接受两个操作数

struct Stu
{
	char name[20];
	int age;
};
 
struct Stu s;
struct S s;
strcpy(s.name, "zhangsan");//使用.访问name成员
s.age = 20;//使用.访问age成员

【数据结构】前置基础,考研408之数据结构,数据结构

2.3.2 结构体指针访问成员

struct Stu
{
	char name[20];
	int age;
};
void print(struct Stu* ps)
{
	printf("name = %s   age = %d\n", (*ps).name, (*ps).age);
	//使用结构体指针访问指向对象的成员
	printf("name = %s   age = %d\n", ps->name, ps->age);
}
int main()
{
	struct Stu s = { "zhangsan", 20 };
	print(&s);//结构体地址传参
	return 0;
}

2.4 结构体内存对齐

我们已经掌握了结构体的基本使用了。
现在我们深入讨论一个问题:计算结构体的大小。
这也是一个特别热门的考点: 结构体内存对齐
先看一下下面的代码:

#include <stdio.h>
 
struct S1
{
	char c1;//1
	int i;//4
	char c2;//1
};
 
int main()
{
	struct S1 s;
	printf("%d\n", sizeof(s));
	return 0;
}

【数据结构】前置基础,考研408之数据结构,数据结构
对这个结果是不是感到很疑惑,结果不应该是(1+4+1=6)嘛,为什么答案是12?

要想搞清楚怎么计算结构体大小的,首先得掌握结构体的对齐规则:

  1. 第一个成员在与结构体变量偏移量为 0 的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
    对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
    VS中默认的值为8(Linux系统下是其本身大小)
  3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的 整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

那么为什么存在内存对齐呢?

大部分的参考资料都是如是说的:

  1. 平台原因 ( 移植原因 ) : 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
  2. 性能原因 : 数据结构( 尤其是栈 ) 应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
    总体来说:结构体的内存对齐是拿空间来换取时间的做法。

现在回过头再来分析分析之前的代码:
【数据结构】前置基础,考研408之数据结构,数据结构

当然了,以上的偏移量只是学习到的理论依据,那该怎么验证呢?

offsetof - 宏
计算结构体成员相对于起始位置的偏移量的

我们先来看看offsetof是怎么用的:
【数据结构】前置基础,考研408之数据结构,数据结构

#include <stdio.h>
#include <stddef.h>
 
struct S1
{
	char c1;//1
	int i;//4
	char c2;//1
};
 
int main()
{
	printf("%d\n", sizeof(struct S1));
	printf("%u\n", offsetof(struct S1, c1));
	printf("%u\n", offsetof(struct S1, i));
	printf("%u\n", offsetof(struct S1, c2));
	return 0;
}

【数据结构】前置基础,考研408之数据结构,数据结构

2.5 结构体传参

#include <stdio.h>
 
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 函数。

原因:
1.函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
2.如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降

结论: 结构体传参的时候,要传结构体的地址。

3.引用

3.1 引用概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
类型& 引用变量名(对象名) = 引用实体;看代码:

int main()
{
	//把b叫做a的引用,也叫做b是a的别名
	int a = 10;
	int& b = a;
	return 0;
}

引用的实质就是在取别名,既然引用是在取别名,那我对别名进行修改,就相当于对自己本身进行修改

3.2 引用特性

引用具有三大特性:
1)引用在定义时必须初始化
2)一个变量可以有多个引用
3)引用一旦引用一个实体,再不能引用其它实体

3.3 引用的使用场景

3.3.1 做参数

就比如说我现在要写一个Swap函数,以前是用指针写的:

//指针版
void Swap(int* pa, int* pb)
{
	int tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}

而现在,我们就可以巧用引用来完成Swap函数

//引用版
void Swap(int& x, int& y)
{
	int tmp = x;
	x = y; 
	y = tmp;
}
//支持函数重载
void Swap(double& x, double& y)
{
	double tmp = x;
	x = y;
	y = tmp;
}

现在,引用就可以做我的形参,就不用再像以前C语言那样总是取地址&,并且在调用的时候也会非常方便,因为有函数重载的加持。

引用做参数的好处如下:
1)输出型参数
2)减少拷贝、提高效率

3.3.2 做返回值

传值返回

int Count()
{
	int n = 0;
	n++;
	return n;
}
int main()
{
	int ret = Count();
	return 0;
}

在传值返回的过程中会产生一个临时变量(类型是int),如果这个临时变量小它会用寄存器替代,如果大就不会用寄存器替代。
具体返回的过程是先把函数的n拷贝给临时变量,再把临时变量拷贝给ret。

传引用返回

int& Count()
{
	int n = 0;
	n++;
	return n;
}
int main()
{
	int ret = Count();
	return 0;
}

这里加上了引用&后,中间也会产生一个临时变量,只是这个临时变量的类型是int&。我们把这个临时变量假定为tmp,那么此时tmp就是n的别名,再把tmp赋值给ret。这个过程不就是直接把n赋给ret吗。这里区分于传值返回的核心就在于传引用的返回就是n的别名。

此外传值返回比传引用返回效率更低,我们偏向传引用

3.4 引用和指针的区别

引用和指针的不同点:
1)引用概念上定义一个变量的别名,指针存储一个变量地址
2)引用在定义时必须初始化,指针没有要求
3)引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
4)没有NULL引用,但有NULL指针
5)在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
6)引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
7)有多级指针,但是没有多级引用
8)访问实体方式不同,指针需要显式解引用,引用编译器自己处理
9)引用比指针使用起来相对更安全

4.动态内存管理

4.1 malloc

void* malloc (size_t size);

这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。

如果开辟成功,则返回一个指向开辟好空间的指针。
如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
注意:使用malloc函数要引用头文件#include<stdlib.h>

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
	//开辟10个整型的空间
	int* p = (int*)malloc(40);
	if (NULL == p)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	// 使用……
 
	//释放
	return 0;
}

4.2 free

C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的,函数原型如下:

void free (void* ptr);

free函数用来释放动态开辟的内存。
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
如果参数 ptr 是NULL指针,则函数什么事都不做。
malloc和free都声明在 stdlib.h 头文件中。

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
	//开辟10个整型的空间
	int* p = (int*)malloc(40);
	if (NULL == p)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	// 使用……
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);
	}
	//释放
	free(p);
	p = NULL;
	return 0;
}

4.3 realloc

realloc函数的出现让动态内存管理更加灵活。有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。
函数原型如下:

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

ptr 是要调整的内存地址
size 是调整之后新大小
返回值为调整之后的内存起始位置。
这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。

realloc在调整内存空间的是存在两种情况:
情况1:原有空间之后有足够大的空间,当是情况1的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
情况2:原有空间之后没有足够大的空间,当是情况2 的时候,原有54空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。
【数据结构】前置基础,考研408之数据结构,数据结构文章来源地址https://www.toymoban.com/news/detail-828216.html

到了这里,关于【数据结构】前置基础的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 408数据结构第一章

    1.数据 数据是信息的 载体 计算机程序 识别和处理 的符号的集合 2.数据元素 数据的 基本单位 整体 进行考虑和处理 若干 数据项 组成 数据项是构成元素的不可分割的 最小单位 3.数据对象 具有 相同性质 的数据元素的集合 4.数据类型 原子类型 结构类型 抽象数据类型 5.数据结

    2024年02月08日
    浏览(35)
  • 408数据结构第三章

    特性后进先出 只允许在 一端 进行插入或删除操作的线性表 每接触一种新的数据结构类型,都应该分别从逻辑结构、存储结构和对数据的运算三方面入手 操作 initstack(s)初始化一个空栈s stackempty(s)判断一个栈是否为空 push(s,x)进栈,未满成为新栈顶 pop(s,x)出栈,非空弹出栈顶元

    2024年02月09日
    浏览(29)
  • 408数据结构第四章

    小题形式考,比较简单,拿两个题来练手就会了 字符串简称串 由零个或多个字符组成的有限序列 S是串名n称为串的长度,n=0称为空串 串中多个连续的字符组成的子序列称为该串的子串 串的逻辑结构和线性表极为相似,区别仅在于串的数据结构对象限定为字符集 线性表的基

    2024年02月11日
    浏览(26)
  • 【“栈、队列”的应用】408数据结构代码

    王道数据结构强化课——【“栈、队列”的应用】代码,持续更新

    2024年02月07日
    浏览(34)
  • 数据结构和算法 - 前置扫盲

    1、数据结构分类 1.1 逻辑结构:线性与非线性 tip: 逻辑结构揭示了数据元素之间的逻辑关系 。 线性数据结构 :元素间存在明确的顺序关系。 数据按照一定顺序排列,其中元素之间存在一个对应关系,使得它们按照线性顺序排列。 每个元素都有且仅有一个前驱元素和一个后

    2024年02月04日
    浏览(31)
  • 408数据结构历年代码真题详解(含暴力解)

    代码全部开源,求个⭐:mancuoj/408-ds 考虑到网络环境,加一个 gitee 链接 除22年真题外已全部更新完成!题源王道,如果有错漏的地方,欢迎PR! 🍓 09~22年真题 🍒 暴力解 + 最优解 🥭 仿照王道书上的写法,含注释 🍉 GoogleTest 全面测试 🍇 真题题目 + 评分标准 评分标准 点击

    2024年02月07日
    浏览(24)
  • 【玩转408数据结构】线性表——定义和基本操作

            线性表是算法题命题的重点,该类题目实现相对容易且代码量不高,但需要最优的性能(也就是其时间复杂度以及空间复杂度最优),这样才可以获得满分。所以在考研复习中,我们需要掌握线性表的基本操作,在平时多进行代码练习。当然在考场上,我们并不一

    2024年02月19日
    浏览(34)
  • 【数据结构】【王道408】——PPT截图与思维导图

    自用视频PPT截图 视频网址王道B站链接 23考研 408新增考点: 并查集,红黑树 2023年408真题数据结构篇 408考纲解读 考纲变化 希尔排序 冒泡排序 快速排序 简单排序算法 堆排序

    2024年02月15日
    浏览(23)
  • 【数据结构一】初始Java集合框架(前置知识)

           Java语言在设计之初有一个非常重要的理念便是:write once,run anywhere!所以Java中的数据结构是已经被设计者封装好的了,我们只需要实例化出想使用的对象,便可以操作相应的数据结构了,本篇文章中我会向大家简单 介绍一下什么是数据结构 ,以及 对Java中常用的数

    2024年02月04日
    浏览(34)
  • 【玩转408数据结构】线性表——线性表的顺序表示(顺序表)

            通过前文,我们了解到线性表是具有相同数据类型的有限个数据元素序列;并且,线性表只是一种逻辑结构,其不同存储形式所展现出的也略有不同,那么今天我们来了解一下线性表的顺序存储——顺序表。         顺序表指的是将 逻辑上相邻的元素 存储在 物理位

    2024年02月19日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包