数据结构学习(C语言)之预备知识

(郝斌老师上课内容)

简介:预备知识:

4_ 预备知识_指针1
5
预备知识_指针2
6
所有的指针变量只占4个子节用第一个字节的地址表示整个变量的地址
7_ 如何通过函数修改实参的值
8_ 结构体的使用概述
9_ malloc()动态分配内存概述
10_ 跨函数使用内存讲解及其示例

4_ 预备知识_指针_1

5_ 预备知识_指针_2

指针的重要性:指针是C语言的灵魂
定义:地址:地址是内存单元的编号,从0开始的非负整数,范围:0-FFFFFFFF【0-4G-1】
指针:指针就是地址,地址就是指针。
指针变量是存放内存单元地址的变量。
指针的本质是一个操作受限的非负整数。
分类
1、基本类型指针:
基本概念
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

# include <stdio.h>
int main(void)
{
	int * p; //p是个变量名字, int * 表示该p变量只能存储int类型变量的地址
	int i = 10;
	int j;

	p = &i;//p保存i的地址,p指向i;修改p的值不影响i的值,修改i的值不影响p的值;任何场合下,*p和i可以互换。*p等价于i。
	j = *p; // 等价于 j = i;
	printf("i = %d, j = %d, *p = %d\n", i, j, *p);
	//p = 10;  //error
	return 0;
}

在这里插入图片描述
总结
a、如何一个指针变量(假定为p)存放了某个普通变量(假定为i)的地址,那我们就可以说:“p指向了i”, 但p与i是两个不同的变量,修改p的值不影响i的值,修改i的值不影响p的值.
b、 *p 等价于 i 或者说 *p 可以与 i 在任何地方互换
c、如果一个指针变量指向了某个普通变量,则 指针变量 就完全等价于 该普通变量
注意:
指针变量也是变量,只不过它存放的不能是内存单元的内容,只能存放内存单元的地址普通变量前不能加
常量和表达式前不能加&

如何通过被调函数修改主调函数中普通变量的值:
Ⅰ 实参为相关变量的地址
Ⅱ 形参为以该变量的类型为类型的指针变量
Ⅲ 在被调函数中通过 *形参变量名 的方式就可以修改主函数相关变量的值

//这样并不能改变i的值,因为只能有实参将数传递给形参,却不能使得形参将值传给实参
# include <stdio.h>

void f(int i) 
{
	i = 100; // 
}

int main(void)
{
	int i = 9;

	f(&i);
	printf("i = %d\n", i);

	return 0;
}
//结果为9,没能改变主调函数中i的值
# include <stdio.h>

void f(int * p) //不是定义了一个名字叫做*p的形参, 而是定义了一个形参,该形参名字叫做p,它的类型是int *
{
	*p = 100; //
}

int main(void)
{
	int i = 9;

	f(&i);  //将i的地址传给了p,那么就说p指向i,也就是*p和 i完全等价了,也就是*p可以与i在任何地方互换
	printf("i = %d\n", i);

	return 0;
}

在这里插入图片描述
2.指针和数组的关系
变量并不一定连续分配,随机分配内存。
指针 和 一维数组
数组名:一维数组名是个指针常量,
它存放的是一维数组第一个元素的地址,
它的值不能被改变
一维数组名指向的是数组的第一个元素
下标和指针的关系:
*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

# include <stdio.h>

int main(void)
{
	int a[5] = {1,2,3,4,5}; //a指向的就是a[0]

	//a[3] == *(3+a);  //指向 第四个元素

	printf("%p\n", a+1);//%p表示输出地址
	printf("%p\n", a+2);
    printf("%p\n", a+3);   //输出的地址是连续的

	printf("%d\n", *a+3); //*a+3等价于 a[0]+3

	return 0;
}

在这里插入图片描述
如何通过被调函数修改主调函数中一维数组的内容【如何界定一维数组】
两个参数:
存放数组首元素的指针变量
存放数组元素长度的整型变量

# include <stdio.h>

void Show_Array(int * p, int len)
{
	int i = 0;

	for (i=0; i<len; ++i)
		printf("%d\n", p[i]);

	//p[2] = -1;  //p[0] == *p   p[2] == *(p+2) == *(a+2) == a[2]
	//p[i]就是主函数的a[i]
}

int main(void)
{
	int a[5] = {1,2,3,4,5};
	
	Show_Array(a, 5);  //a等价于&a[0], &a[0]本身就是int *类型

	//printf("%d\n", a[2]);

	return 0;
}

在这里插入图片描述
内存:内存是多字节组成的线性一维存储空间。
内存的基本划分单位是字节。
每个字节含有8位,每一位存放1个0或1个1.
内存和编号是一一对应的。
软件在运行前需要向操作系统申请存储空间。在软件运行期间,该软件所占空间不再分配给其他软件。当软件运行完毕后,操作系统将回收该内存空间(操作系统并不清空该内存空间中遗留下来的数据)。所以会有垃圾数字。
NOTE:1)指针变量也是变量,普通变量前不能加*,常量和表达式前不能加&。
2)局部变量只在本函数内部使用。

6_ 所有的指针变量只占4个子节用第一个字节的地址表示整个变量的地址

//无论指针指向的变量占多少个字节,指针变量统一都只占4个字节
# include <stdio.h>

int main(void)
{
	double * p;  
	double x = 66.6;  //一个double占8个字节

	p = &x;  //x占8个子节 1个字节是8位, 1个字节一个地址
  //p只存放了一个地址,第一个字节的地址

	double arr[3] = {1.1, 2.2, 3.3};
	double * q;

	q = &arr[0];
	printf("%p\n", q);  //%p实际就是以十六进制输出

	q = &arr[1];
	printf("%p\n", q);    //p,q相差8

	return 0;
}

在这里插入图片描述

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

如何通过被调函数修改主调函数中普通变量的值
1)实参为相关变量的地址;
2)形参为以该变量的类型为类型的指针变量;
3)在被调函数中通过 *形参变量名的形式的形式就可以修改主函数。

//这种方式无法实现改变地址
# include <stdio.h>

void f(int * q);

int main(void)
{
	int i = 9;
	int * p = &i;// int  *p;  p = &i;

	printf("%p\n", p);

//想通过f函数修改指针p的值
        f(p);  //肯定不行,因为p虽然是指针,但是仍然是变量
	printf("%p\n", p);

	return 0;
}

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

在这里插入图片描述

//修改指针变量的值,只能修改地址
# include <stdio.h>

void f(int ** q);

int main(void)
{
	int i = 9;
	int * p = &i;// int  *p;  p = &i;

	printf("%p\n", p);

//想通过f函数修改指针p的值
 // f(p);  肯定不行,因为p虽然是指针,但是仍然是变量
	f(&p);
	printf("%p\n", p);

	return 0;
}

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

在这里插入图片描述

8_ 结构体的使用概述

为什么会出现结构体:
为了表示一些复杂的数据,而普通的基本类型变量无法满足要求
什么叫做结构体:
结构体是用户根据实际需要自己定义的复合数据类型

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

struct Student //结构体不是变量,是数据类型。变量分配内存,数据类型没有分配内存
{	
	int sid;
	char name[200];
	int age;
}; //分号不能省

int main(void)
{
	struct Student st = {1000, "zhangsan", 20};
	printf("%d  %s  %d\n", st.sid, st.name, st.age);

	st.sid = 99;
	//st.name = "lisi";  //error
	strcpy(st.name, "lisi");  //字符串必须这样进行赋值
	st.age = 22;
	printf("%d  %s  %d\n", st.sid, st.name, st.age);

	//printf("%d %s %d\n", st);  //error  不能直接使用st,必须通过变量名.成员的方式
	return 0;
}

在这里插入图片描述
如何使用结构体: struct_1 struct_2
两种方式—— struct Student st={1000,”zhagnsan”,20};
struct Student*pst=&st;
1)通过结构体变量名来实现
st.sid
2)通过指向结构体变量的指针来实现【重点】
pst->sid
pst所指向的结构体变量中的sid这个成员

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

struct Student
{	
	int sid;
	char name[200];
	int age;
}; //分号不能省

int main(void)
{
	struct Student st = {1000, "zhangsan", 20};
	//st.sid = 99;  //第一种方式

	struct Student * pst; //定义了一个指针变量pst,存的是struct Student 的地址,
	pst = &st;
	pst->sid = 99;  //第二种方式  pst->sid 等价于 (*pst).sid  而(*pst).sid等价于 st.sid,  所以pst->sid 等价于 st.sid

	return 0;
}

在这里插入图片描述

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

struct Student
{	
	int sid;
	char name[200];
	int age;
}; //分号不能省

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

int main(void)
{
	struct Student st;  //已经为st分配好了内存,但是是一个垃圾值     //int  i  //为i分配了内存

	f(&st);
	g2(&st);

	//printf("%d %s %d\n", st.sid, st.name, st.age);//输出方法一

	return 0;
}

//这种方式耗内存 耗时间 不推荐
//整体变量赋值//输出方法二,速度慢,耗空间,耗内存,不推荐
void g(struct Student st)
{
	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;
}

在这里插入图片描述

9_ malloc()动态分配内存概述

没有使用malloc()函数的都是静态的,使用这个函数的都是动态
动态内存分配和释放
动态构造一维数组
假设动态构造一个int型数组

int *p = (int*) malloc(int len);

1、malloc只有一个int型的形参,表示要求系统分配的字节数
2、malloc函数的功能是请求系统len个字节的内存空间,如果请求分配成功,则返回第一个字节的地址,如果分配不成功,则返回NULL
3、malloc函数能且只能返回第一个字节的地址,所以我们需要把这个无任何实际意义的第一个字节的地址(俗称干地址)转化为一个有实际意义的地址,因此malloc前面必须加(数据类型 *),表示把这个无实际意义的第一个字节的地址转化为相应类型的地址。如:
int *p = (int *)malloc(50);
表示将系统分配好的50个字节的第一个字节的地址转化为int *型的地址,更准确的说是把第一个字节的地址转化为四个字节的地址,这样 p就指向了第一个的四个字节p+1就指向了第2个的四个字节, p+i就指向了第i+1个的4个字节。p[0]就是第一个元素, p[i]就是第i+1个元素
double *p = (double *)malloc(80);表示将系统分配好的80个字节的第一个字节的地址转化为double *型的地址,更准确的说是把第一个字节的地址转化为8个字节的地址,这样p就指向了第一个的8个字节,p+1就指向了第2个的8个字节, 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);逻辑上和int a[5] = {4, 10, 2, 8, 6};等价
	int * pArr = (int *)malloc(sizeof(int) * len);//sizeof(int)整型占的字节数
        //一共5个元素,sizeof(int) * len其实也就是20个字节,请求操作系统给20个字节的空间。
        //malloc函数只返回第一个字节函数,int类型只返回第一个地址,double类型也只返回第一个字节,所以没有实际意义,需要(int *)强制转换,来告诉编译器返回的是整形的地址,所以就知道pArr指向的是前面四个的地址
 //	*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(pArr);  //把pArr所代表的动态分配的20个字节的内存释放

	return 0;
}

在这里插入图片描述

10_ 跨函数使用内存讲解及其示例

下程序中,能够通过调用函数fun,使main函数中的指针变量p指向一个合法的整型单元的是

//不可以
main ()
{
	int *p;
fun(&p);
...
}
int fun (int **q)
{
	int s; 	//s为局部变量。调用完毕后s就没有了,最终p没有指向一个合法的整型单元
	*q=&s;
}

//可以
main()
{
	int *p;
	fun(&p);
	...
}
int fun(int **q)
{
	*q=(int *)malloc(4);	//返回4个字节,只取第1个字节地址赋给*q,*q==p。执行完后,因为没有free(),内存没有释放。如果没有free(),整个程序彻底终止时才能释放
}

程序内部类定义方法
A aa=new A(); C++这么写实际上内部就是下面的语句
A pa=(A)malloc(sizeof(A));

# 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;
}

在这里插入图片描述

  • 6
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值