数据结构与算法入门(2)--预备知识(指针、结构体、动态内存分配)
来源:互联网 发布:java解压pkg 编辑:程序博客网 时间:2024/05/17 03:04
我们知道程序=数据的存储+数据的操作+计算机程序设计语言。 想要实现典型的数据结构,需要选择一门合适的编程语言。个人认为,C语言是一个很好的工具。C中的指针能很好的实现链表以及以链表为基础的树与图等。一些高级语言,如java,python没有指针的概念,实现典型的数据结构难免会有些变味。既然选择了C语言,那就讲讲想要用C语言实现数据结构需要的预备知识。主要分为三部分– 指针,结构体和动态内存分配
(Firefox浏览器,右键点击图片,选择view image即可查看高清大图。)
指针
指针的基本概念
- 指针与指针变量
变量在内存中分配到的位置叫做地址,也就是指针。 指针就是地址,地址就是指针。
指针变量 :存储另一个变量的地址的变量。(指针变量是通过寻址来找到另一个变量) - 指针变量的访问
- 直接访问
按变量地址来存取变量值 - 间接访问
通过存放变量地址的变量来访问变量
- 直接访问
- 指针变量的定义与初始化
【存储类型】 数据类型 *指针名 = 地址;- 指针的初始化
除了上述的,用变量的地址来初始化指针变量之外,还有三种方法来初始化指针变量。- 通过指针变量初始化指针变量
- 通过整型地址常量来初始化指针变量
int *p;
p = (int *) 0x23; - NULL或0
- 指针的初始化
- 基类型 与指针变量的大小
指针变量所指向的变量的数据类型为指针的基类型。尽管指针变量可以是不同的基类型,但指针变量的大小是固定的,是由计算机硬件所决定的。对32位系统来说,指针变量的大小为4个字节。想一想为什么?(因为32位操作系统地址总线宽度为32位,用4个字节可以遍历所有内存空间。又因指针就是地址,所以指针的取值范围与地址范围相同,故用4个字节可以表示所有可能的指针值。)
例如:
float *a ;
int *b;
上述两个语句定义了两个指针变量。第一个指针变量指向一个float类型的变量,第二个指针变量指向一个int类型变量。指针变量a的基类型为float,指针变量b的基类型为int。但是a和b的大小均为4个字节。(注意,int数据类型也为4个字节,所以可以通过整型地址常量来初始化指针变量)
- 指针与指针变量
常见运算—指针的运算就是地址的运算
- 赋值运算
例如:int a; int *p=&a;
这两行代码表示:- 定义了一个整型变量a
- 定义了一个指针变量p且指针变量p只能指向整型变量
- 指针变量p通过赋值运算=指向了整型变量a
- 指针的移动
指针变量存放的是地址,通过改变指针变量的值,能够使指针指向不同的地址。这个改变指针变量的值的过程,就叫做指针的移动。这个时候就有一个问题了,指针变量的移动的基本单位是多大?是一个字节吗?这里我们介绍两个概念:存储单元和字节。- 字节
我们知道,在主存储器也就是内存中,数据是以字节为单位进行存储的。一个字节有8位。一个字节对应一个地址。 - 存储单元
存储单元是一个跟数据类型(data_type)紧密相连的一个概念。我们知道,不同的数据类型定义的变量所分配的字节数不同。 比如C语言中,char 变量占一个字节,int 变量占4个字节,float变量占4个字节,double变量占8个字节。那么对应的,char变量的存储单元大小为一个字节,int变量的存储单元大小为4个字节… 对int *p=8000H,执行完++p后,p的值为8004H而不是8001H。因为指针移动的基本单位是存储单元
- 字节
- 引用指针变量的值和指针变量所指变量的值
int a,*p=&a;
通过取地址运算符&来引用指针变量的值 p==&a (true)
通过指针运算符*来引用指针变量所指变量的值 a==*p (true) - 比较两个同类型指针的值是否相等
若相等则说明这两个指针指向同一变量 - 求两指针变量的差
两指针所指位置间相差元素个数。
- 赋值运算
- 指针与数组的关系
- 指针变量既可以指向数组也可以指向数组的元素
例如:
int a[20],*p,*q;
p=a;
q=&a[0]; - 数组元素的表示方法
- 指针法 *(p+1)=1 // a[1]=1
- 下标法 p[1] =1
- 数组名是指针常量,表示数组的首地址。即&a[0]。
所以数组名不能进行自增,自减和赋值等操作。而指向数组的指针变量则可以进行上述操作。 - 二维数组地址表示
- a—二维数组首地址,即第0行地址
- a+i 第i行的地址
- *(a+i)= a[i] 第i行第0列元素的地址
- *(a+i)+j= a[i]+j 第i行第j列元素的地址
- 指针变量既可以指向数组也可以指向数组的元素
数组指针
通过指向一维数组的指针变量引用二维数组。常用于处理二维数组。- 定义:
[存储类型] 数据类型 (* 指针变量名)[二维数组列数/一维数组维数] - 例:
int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int (*p)[4]=a;
则有 p[2]=3,(*(p+2)+3)=12;
如何理解上述两段代码?- 定义了一个二维数组a,三行四列;
- 定义了一个数组指针p。为什么该指针叫做数组指针?观察p的定义语句:int (*p)[4]。将()里的内容当做一个整体则上式变为int x[4],(*p)=x;即,int (*p)[4]先定义了一个数组,后定义了一个指针。故称为数组指针。此时,p为行指针。对于p,存储单元为4个int型变量即16个字节。p+1移动16个字节的地址。
- 数组指针的元素个数要与二维数组的列数相同。即若定义,int a[3][x],int (*p)[y],则x=y。
- 定义:
指针数组
每一个元素都是指针,分别指向同一数据类型的变量。常用于处理字符串。- 定义:
数据类型 *指针变量名 [数组长度] 例:
int *p[4];
*p=”abcd”; // p[0]=”abcd”
or
int *p[4]={“abcd”};字符指针和字符数组的区别?
对字符指针 int *p=”abcd”; 等价于 int *p; p = “abcd”;
而对字符数组 int a[20] =”abcd”; 拆开写成 int a[20]; a=”abcd’;就不对了。
这就是两着的最主要的区别。
- 定义:
- 指针与函数
- 指针变量作为函数形参
传入的实参必须为 地址 或 指针变量 - 指针变量作为函数实参
地址传递 vs 值传递
问题:如何通过被调函数修改主调函数中变量的值?传值?传地址?传引用(C++中)? - 函数名赋给指针变量—函数指针
函数指针存放的是函数代码段的入口地址
- 指针变量作为函数形参
总之,用一句话总结指针,那就是:通过一个变量A,能够引用另一个变量B的地址与地址对应存储单元所存储的值,那么这个变量A就是指针变量,获取到的另一个变量B的地址就是指针。
- 结构体
- 结构体定义
- 为什么要有结构体?
C语言中数组中的变量必须要有相同的数据类型和长度,灵活性低。于是出现了一种可自定义的构造数据类型,即为结构。 - 一般形式
struct 结构体名
{
数据类型1 结构体成员变量名1;
数据类型2 结构体成员变量名2;
…
} ;
如何理解上述定义?- struct 是关键字 表示定义一个结构体数据类型
- 结构体名和结构体成员变量名为用户定义的标识符
- 结构体最后大括号外要有分号。(为什么?因为这是一个语句statement,起声明的作用。程序的入口为main函数,当编译器在main函数中读到struct 结构体名时,编译器会查找main函数前定义的声明。这个statement告诉编译器这里定义了一个数据类型,数据类型为struct 结构体名。 然后编译器就能正常的完成编译。与宏定义(#define PI 3.1415926535)形成对比{无分号},与函数定义int max (int a[]);可以形成类比{有分号})
- Tips: 不同结构体中成员变量名可以重名,结构体中成员变量可以和主函数或者其他函数中变量重名。
- 为什么要有结构体?
- 结构体类型变量的定义 — 结构体变量,结构体指针,结构体数组
- 声明结构体类型,再定义结构体变量
struct stu
{
…
};
struct stu anran; - 声明结构体类型的同时定义结构体变量
struct stu
{
…
}anran; - 直接定义结构体类型变量
struct
{
…
}anran;
- 声明结构体类型,再定义结构体变量
- 结构体变量中的数据引用
- 对结构体变量中的成员的引用与操作
struct stu
{
int age;
}anran,*panran;- 结构体变量名.成员名 // anran.age
- 指针变量名->成员名 // panran->age
- (*指针变量名).成员名 // (*panran).age
- 结构体变量进行整体赋值
- 结构体与数组的区别
- 结构体中可以含有不同的数据类型
- 结构体变量可以相互赋值,而数组无法相互赋值
why?
数组不是一个数据类型,是单一数据类型的一个集合。
数组名是一个指针常量,无法赋值。
- 结构体与数组的区别
- 函数之间结构体变量的数据传递
- 函数传递结构体变量
- 函数传递结构体变量成员
- 传递结构体地址
- 返回值是结构体类型
- 返回值是指向结构体类型变量的指针
- 对结构体变量中的成员的引用与操作
- 结构体定义
动态内存分配
在讲动态内存分配之前,我们先来思考一个问题:如何跨函数使用内存? 即,如何通过被调函数,开辟一块内存空间,当被调函数执行结束后,仍然能够访问该内存空间?
答案是使用memory allocation function.即malloc()函数。malloc函数返回类型是开辟内存空间的首地址,以字节为单位。我们知道在C中,int类型存储单元为4个字节。所以malloc动态分配的内存必须要经过强制类型转换才能成为能存放int类型变量的内存空间。所以通常我们如下使用malloc函数。
int *p;
p = (int *) malloc (sizeof(int)*number);C/C++中没有垃圾回收机制,也就是说,分配的动态内存在调用结束后不会自动销毁,必须通过free()函数来销毁。而在JAVA中存在垃圾回收机制,无需考虑内存回收的问题。利用这个特性,我们便可以实现跨函数使用内存的功能。与之相对的是静态内存分配。静态内存分配的内存空间在函数结束后便会自动销毁。
比如上述两个语句,当被调函数执行完毕后,指针变量p便被销毁,但动态分配的number个int类型变量的内存不会被销毁,我们只要把p指针的值return回主函数,便能跨函数使用内存。
最后用一个小程序来结束我们的结构体和动态内存分配的话题
# include <stdio.h># include <malloc.h>struct Student{ int sid; int age;};struct Student * CreateStudent(void);void ShowStudent(struct Student *);int main(void){ struct Student * ps; ps = CreateStudent(); ShowStudent(ps); return 0;}void ShowStudent(struct Student * pst){ printf("%d %d\n", pst->sid, pst->age);}struct Student * CreateStudent(void){ struct Student * p = (struct Student *)malloc(sizeof(struct Student)); p->sid = 99; p->age = 88; return p;}
- 数据结构与算法入门(2)--预备知识(指针、结构体、动态内存分配)
- 数据结构预备知识之指针,结构体和动态内存的分配与释放
- 05-数据结构_预备知识-动态内存的分配和释放
- 预备知识—程序的内存分配(堆栈)
- 04-数据结构_预备知识-结构体
- C语言--指针数组--动态内存分配+结构体数组(递归指针)--day10
- (三)预备知识----指针
- 数据结构预备知识(一)
- 数据结构预备知识(二)
- 数据结构预备知识(三)
- 数据结构预备知识(四)
- Spring入门预备知识(下)(动态代理与面向接口编程)
- 数据结构学习记录-指针与动态内存分配
- 预备知识—程序的内存分配
- 预备知识―程序的内存分配
- 结构体指针内存分配
- 《数据结构》(c++版)预备知识
- (二)预备知识----CPU、硬盘、内存
- CSS3新属性box-flex移动端利器
- 设计模式之工厂方法模式
- POI 读取数值转换问题
- Maven missing artifact 问题解决
- 《Zero to One》
- 数据结构与算法入门(2)--预备知识(指针、结构体、动态内存分配)
- jenkins+git+maven搭建自动化部署项目环境
- Android项目目录-A
- 游戏程序员应具备的几种技能
- 关于在阿里云主机ECS(CentOS)部署Java Web的心励路程
- 【codeforces 757C】Felicity is Coming!
- Linux网络编程12——浅谈 TCP 三次握手和四次挥手
- 谭浩强C程序设计第四版分别求5个定积分,别谢我,我是活雷锋
- 453. Minimum Moves to Equal Array Elements