您的位置:首页 > 理论基础 > 数据结构算法

数据结构与算法入门(2)--预备知识(指针、结构体、动态内存分配)

2017-01-14 11:45 309 查看
我们知道程序=数据的存储+数据的操作+计算机程序设计语言。 想要实现典型的数据结构,需要选择一门合适的编程语言。个人认为,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。因为指针移动的基本单位是存储单元
bfa3


引用指针变量的值和指针变量所指变量的值

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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: