数据结构——学习线性表的预备知识

一. 指针

详细内容见C语言的指针内容方面

1.引例

# include <stdio.h>
int main(void){
	int * p;//p是个指针变量,int * 表示该p变量只能存储int类型变量的地址
	int i = 10;
	int j;
	p = &i; //p指向i第一个字节地址
	j = *p;
	return 0;
}

三句话:
1)p保存i的地址,那么p就指向i
2)修改p的值不影响i的值,修改i的值不影响p的值
3)*p就代表了i,因为p指向了i
在这里插入图片描述

int * 表示该p变量只能存储int类型变量地址
如果p里面没有保存一个变量地址,那么p里面就是垃圾值(也就是野指针)
那么*p是一个垃圾值、不确定的值,系统一般不允许你访问
i==*p,但是修改p变量里面的值和修改i变量里面的值都互补影响。

2. 指针的重要性

指针是C语言的灵魂

3. 指针的定义

  • 指针就是地址,地址就是指针
  • 指针变量是存放内存单元地址的变量
  • 指针的本质是一个操作受限的非负整数

(1). 内存的基本概念

  1. 内存是用来存储数据的设备,它的存储速度介于寄存器和硬盘之间

  2. 内存是CPU唯一可以访问的大容量的存储设备,所有的硬件中的程序和数据必须调入内存之后方可被CPU执行!切记:CPU不能直接处理硬盘中的数据

  3. 内存的问题是软件开发中最核心的问题之一,如:内存的分配,内存的释放,内存什么时候分配,内存什么时候释放,由谁来分配,由谁释放,分配是在什么地方,访问权限如何?

  4. 内存是多字节组成的线性一维存储空间

  5. 内存的基本划分单位是字节

  6. 每个字节含有8位,每一位存放1个0或1个1

  7. 字节和编号是一一对应的,每个字节都有一个唯一确定的编号,一个编号对应一个字节!这个编号也叫地址

  8. 一个系统所能管理的内存空间的大小取决于参与编号的二进制位数

     如DOS系统20位寻址方案,可控制2^20B,即1MB的内存
    

地址:内存单元编号

从0开始的非负整数
范围:0-FFFFFFFF(0-4G-1)
内存大小:表示有多少个单元* 每个单元格的大小 (2^32 * 1Byte)

(2). 软件运行与内存关系(垃圾数据)

内存是在操作系统的统一管理下使用的!

  1. 软件在运行前需要向操作系统申请存储空间,在内存空闲空间足够时,操作系统将分配一段内存空间并将外存中软件拷贝一份存入该内存空间中,并启动该软件的运行
  2. 在软件运行期间,该软件所占内存空间不足不再分配给其他软件
  3. 当软件运行完毕后,操作系统将回收该内存空间(注意:操作系统并不清空该内存空间中遗留下来的数据),以便再次分配给其他软件使用

综上所述:一个软件所分配到的空间中极有可能存在着以前其他软件使用过后的残留数据,这些数据被称之为垃圾数据。所以通常情况下我们为一个变量,为一个数组,分配好存储空间之后都要对该内存初始化!

4. 基本类型指针

基本概念

int i=10;
int *p = &i; //等价于 int *p; p=&i;
  • 详解这两部操作:
  • 1)p存放了i的地址,所以我们说p指向了i
  • 2)p和i是完全不同的两个变量,修改其中的任何一个变量的值,都不改变另一个变量值
  • 3)p指向i,*p就是i变量本身,更形象的说所有出现 *p的地方都可以换成i,所有出现i的地方都可以换成 *p

总结:

  • 1)如果一个指针变量(假定为p)存放了某个普通变量(假定为i)的地址,那么我们就可以说:“p指向了i”,但p与i是两个不同的变量,修改p的值不影响i的值,修改i的值不影响p的值
  • 2)*p 等价于i,或者说 *p可以与i在任何地方互换
  • 3)如果一个指针变量指向了某个普通变量,则 *指针变量 就完全等价于该普通变量

注意:

  • 1)指针变量也是变量,只不过它存放的不能是内存单元的内容,只能存放内存单元的地址
  • 2)普通变量前面不能加 *
  • 3)常量和表达式前面不能加&

如何通过被调函数修改主调函数普通变量的值

  • I 实参为相关变量的地址
  • II 形参为以该变量的类型为类型的指针变量
  • III 在被调函数中通过 " *形参变量名 " 的方式就可以修改主函数相关变量的值
#include <stdio.h>
// f(int * p) 不是定义一个名字叫做*p的形参,而是定义了一个形参名叫做p,类型是int *
// 因为i的变量类型是int,所以p必须是int类型
void f(int * p){ //p指向i
	*p = 100; //*p等价于i
}
int main(void){
	int i = 9;
	f(&i);  //把i的地址发给了p(实参必须是相关变量的地址)
	printf("%d",i);  //i的地址变成了100
	return 0;
}

5. 指针和数组

(1). 指针和下标的关系

int main(void){
	int a[5] = {1,2,3,4,5};
	printf("%p\n",a+1);  //0012FF70
	printf("%p\n",a+2);	 //0012FF74
	printf("%p\n",*a+3); //4  (*a+3 <=> a[0]+3)
	return 0;
}

数组名

  • 一维数组名{a} 是一个指针常量,
  • 它存放是一个一维数组第一个元素的地址,
  • 它的值不能改变,
  • 一维数组名指向的是数组的第一个元素:a指向a[0]

下标和指针的关系:

  • a[i] <==> *(a+i)
  • 假设指针变量的名字为p
  • 则p+i的值是p+i*(p所指的变量所占的字节数)

指针变量的运算

  • 指针变量不能相加、不能相乘、不能相除

  • 如果两指针变量属于同一数组,则可以相减

  • 指针变量可以加减一个整数,前提是最终结果不能超过指针变量范围

    p+i的值是p+i*(p所指的变量所占的字节数)
    p-i的值是p-i*(p所指的变量所占的字节数)
    p++ <==> p+1
    p-- <= => p-1

在这里插入图片描述

(2). 通过指针修改数组的值

如何通过被调函数修改主调函数一维数组的内容
两个参数:

  • 存放数组首元素的指针变量
  • 存放数组元素长度的整型变量
#include <stdio.h>
void show_Array(int * p, int len){
	p[0] = -1; //p[0] <=> *p  p[2] == *(p+2) == *(a+2) == a[2]   p[i]就是主函数的a[i]
	int i;
	for(i=0;i<len;i++){
		printf("%d\n",p[i]); //-1,2,3,4,5
	}
}
int main(void){
	int a[5] = {1,2,3,4,5};
	show_Array(a,5); //a等价于&a[0],a本身就是int *类型
	return 0;
}

输出:-1 2 3 4 5

p[i]就是主函数的a[i],修改p[i]的值等价于修改a[i]的值
*(p+i) == a[i] == p[i]

确定数组需要2个参数(首元素地址 和 数组长度)

(3). 一个指针变量占几个字节

总结:任何类型的指针变量都是占用4个字节。
指针即为地址,指针变量占几个字节跟语言无关,而是跟系统的寻址能力有关,譬如以前是16为地址,指针即为2个字节,现在一般是32位系统,所以是4个字节,以后64位,则就为8个字节。

#include <stdio.h> 
int main(void)
{
    int a=1;
	char b='a';
	float c=1.0;
	void *p;
	p=&a;
	printf("a的地址为:0x%x,其字节数为:%d\n",p,sizeof(p));
	p=&b;
	printf("b的地址为:0x%x,其字节数为:%d\n",p,sizeof(p));
	p=&c;
	printf("c的地址为:0x%x,其字节数为:%d\n",p,sizeof(p));
	return 0;
}

在这里插入图片描述
既然32位,指针变量都占4个字节,那么指针变量前面的类型表示什么含义呢?

  • 虽然所有的指针都只占四个字节,但不同类型的普通变量却占不同的字节数。
  • 一般来说,int占四个字节,char占一个字节.
  • 如果定义指针变量不定义类型,那么它在取*也就是取其中的值的时候,就不知道应该读取几个字节。
  • 而定义了类型之后。如果是int型的就读四个字节,char型的就读一个字节。

(4). 指针和变量的地址关系

所有的指针变量只占4个字节,用第一个字节地址表示整个变量地址
指针总是指向变量的第一个字节的地址,即变量的首地址
每个字节占一个地址,但是指针变量只存放该变量元素的第一个字节地址

#include <stdio.h>
int main(void){
	double * p;
	double x=66.6;
	p = &x;  //x占8个字节
	double arr[3] = {1.1, 2.2, 3.3};
	double *q;
	q = &arr[0];
	printf("%p\n", q); //0012FF5C  %p实际就是以16进制输出
	q = &arr[1];
	printf("%p\n", q); //0012FF64  //与前一个地址差8位
	return 0;
} 

在这里插入图片描述

6. 指针和函数

如何通过函数修改实参的值

#include <stdio.h>
void f(int ** q);

int main(void){
	int i = 9;
	int * p = &i;
	printf("%p\n", p); //0012FF7C
	f(&p);  
	printf("%p\n", p); //0012FF7C
	return 0; 
} 

void f(int ** q){
	*q = (int *)0xFFFFFFFF; 
} 

通过f函数修改指针变量的值

int ** 类型:表示类型是指针变量的地址

7. 结构体

(1). 为什么会出现结构体

为了表示一些复杂的数据,而普通的基本类型变量无法满足要求

(2). 什么叫结构体

结构体是用户根据实际需要自己定义的复合数据类型。(类也是一个数据类型)
只有属性,没有方法

定义结构体:

#include <stdio.h>
#include <string.h>
struct Student{	//struct Student 是定义的一个数据类型名
	int sid;	//下面是三个成员变量
	char name[100];
	int age;
}; //分号不能省略

int main(void){
	//struct Student st; //定义一个student类型的变量st
	struct Student st = {1000,"zhangsan",20}; //定义变量的同时赋值
    printf("%d %s %d\n", st.sid, st.name, st.age);

    st.sid=99;
	strcpy(st.name,"lisi"); //C语言必须通过函数给字符数组赋值,不能直接给char类型赋值
	st.age=22;
	printf("%d %s %d\n", st.sid, st.name, st.age);
	
	//定义了一个指针变量名字叫pst,它存放的是struct Student类型变量的地址(而struct Student变量大约占208个字节,但pst变量永远只占4个字节,因为内存大小最大为4G=2^32位)
	struct Student * pst;
	pst = &st;
	pst->sid=99; //pst->sid=99 等价于(*pst).sid 而(*pst).sid等价于 st.sid,所以pst->sid等价于st.sid
}

1000 zhangsan 20
99 lisi 22

(3). 使用结构体的两种方式

struct Stduent st = {1000,"Pudding",20};

struct Stduent * pst;

pst所指向的结构体变量中的sid这个成员
  • 方式一:st.sid(st是普通变量)
  • 方式二:pst->sid(pst是指针变量)

注意:结构体变量不能加减乘除,但可以相互赋值

(4). 普通结构体变量和结构体指针变量作为函数传参问题

#include <stdio.h>
#include <string.h>

struct Student{
	int sid;
	char name[100];
	int age;
};

void f(struct Student * pst);
void g(struct Student st);
void g2(struct Student * pst);

int main(void){
	struct Student st;
	f(&st); //输入
	g(st) //输出方式一
	g2(&st) //输出方式二
	return 0;
}

void g(struct Student st){ //这种方式耗内存(至少传递208个字节),耗时间,不推荐
	printf("%d %s %d\n",st.sid,st.name,st.age);
}

void g2(struct Student * pst){
	printf("%d %s %d\n",pst->sid,pst->name,pst->age);
}

void f(struct Student * pst){ //进行输入
	(*pst).sid = 99;
	strcpy(pst->name,"zhangsan");
	pst->age = 22;
}

8. 动态内存分配和释放

(1). 动态构造一个int类型的一维数组

假设动态构造一个int型数组
int *p = (int *)malloc(int len)

  • malloc只有一个int型的形参,表示要求系统分配的字节数

  • malloc函数的功能是请求系统len个字节的内存空间,如果请求分配成功,则返回第一个字节的地址(俗称干地址)转化为一个有实际意义的地址,因此malloc前面必须加(数据类型 *),表示把这个无实际意义的第一个字节的地址转换为相应类型的地址。如:

      int *p = (int *)malloc(50)
      表示将系统分配好的50个字节的第一个字节的地址转化为int *型的地址,
      更加准确的说是把第一个字节的地址转换为四个字节的地址,
      这样p就指向了第一个的四个字节,p+1就指向了第二个的四个字节
      p+i就指向了第i+1个的4个字节。p[0]就是第一个元素,p[i]就是第i+1个元素
    
    
      double *p = (double *)malloc(80)
      表示将系统分配好的80个字节的第一个字节的地址转换为double *型的地址,
      更加准确的说是把第一个字节的地址转换为八个字节的地址,
      这样p就指向了第一个的八个字节,p+1就指向了第二个的八个字节
      p+i就指向了第i+1个的8个字节。p[0]就是第一个元素,p[i]就是第i+1个元素
    
  • free ( p ) 表示:释放p所指向的内存,而不是释放p本身所占用的内存

#include <stdio.h>
#include <malloc.h>
int main(void){
	int a[5] = {4,10,2,8,6}; //静态分配内存
	
	int len
	printf("请输入你需要分配的数组的长度:len = ")scanf("%d",&len);
	int *pArr = (int *)malloc(sizeof(int) * len);
	//*pArr = 4; 类似于a[0] = 4;
	//pArr[1] = 10; 类似于a[1] = 10;
	//printf("%d %d\n",*pArr,pArr[1]);

	//我们可以把pArr当作一个普通数组来使用
	for(int i=0;i<len;i++){
		scanf("%d",&pArr[i]);
	}
	for(i = 0;i<len;i++){
		printf("%d\n",*(pArr+i))
	}

	free(p); //把pArr所代表的动态分配的20个字节的内存释放
	
	return 0;
}
每个类型的指针变量都存储普通变量第一个字节地址
(int *)强制类型转换,告诉编译器第一个字节地址代表了每个单位是一个整型的四个字节
静态分配内存:程序从运行之后就一直这么大,在运行过程中不能改变

在这里插入图片描述

(2). 跨函数使用内存(只能通过动态分配内存实现)

malloc函数手动分配内存,必须手动释放内存
虽然函数结束运行,但是动态分配的内存还在,只有通过手动去释放内存。

#include <stdio.h>
int main(void){
	int * p;
	fun(&p);
	return 0;
}

int fun(int ** q){
	*q = (int *)malloc(4);
}

这样调用fun函数之后指针变量p就指向了一个合法的单元

*p读取指针变量p所指变量的内容

  • java创建对象的底层也是通过动态分配内存
  • A aa = new A();
  • A *pa = (A *)malloc(sizeof(A));
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值