C语言4:深度学习指针1

1.1 内存和地址

如果要想理解地址,我们可以先从内存开始,因为CPU 在处理数据的时候会先从内存读取数据然后处理后的数据也会放回内存中。我们可以想一下内存像一栋楼房有很多房间的那种例如我们的宿舍楼,有人想找到我们,这就需要我们把自己的门牌号告诉别人,内存就相当于这座大楼,内存中的一个个内存单元(byte 字节)就像一个个房间,一个字节空间内可以放8个bit比特位。每个内存单元都要有一个编号,就像宿舍的门牌号,有了这个CPU 才能快速找到一个内存空间。生活中我们把门牌号称之为地址,内存单元的编号也是地址,但在C语言中我们称之为指针。

内存单元的编号==地址==指针

计算机中的编址不是把每个字节的地址记录下来而且计算机本身的硬件设计完成的,就像钢琴的“到 来 咪 发 ”一样,是生产时就已经设计好。电脑硬件单元是相互配合工作的,CPU和内存的数据交互是用地址总线来完成的。两者之间有32条线,每条线有两种状态1或者0,1条线就有两种含义,32条线就有2的32次方的含义,每个含义可以代表一个地址。地址信息通过控制总线找到内存上的数据,数据通过数据总线传入CPU上的寄存器。

1.2 指针变量和取地址符

在C语言上创建变量就是向内存申请空间

个人理解(&把数据变成地址,*把地址变回去)

指针变量是存放地址的,地址要在地址线上传输,32根地址线是有32个0或1排列,就是32个二进制序列,32个比特位,需要四个字节。如果是64个地址线,需要64个比特位,8个字节

1.3 指针变量类型的意义

每个指针变量的大小在同一个平台下都一样,为什么还需要各种各样的指针类型?

指针类型决定了指针进行解引用操作符时访问几个字节,也就是决定指针的权限。

并且指针类型也决定了指针进行+1或-1时一次加减多少

1.4 void*指针

这种类型的指针可以用来接受任意类型地址,但有局限性, void* 类型的指针不能直接进行指针的+-整数和解引用的运算。就像这里的p既是int*也是fioat*,像一个垃圾桶一样都能接受。

1.5 const修饰指针

const可以修饰变量使变量具有常属性,但本质上还是变量,常变量

但是可以通过指针变量的方法改变

但也有方法使它不能改,就是const修饰指针变量。

const修饰指针变量的时候放在*右边就是指限制指针变量本身,指针变量不会指向别的变量,但可以通过指针变量,直接修改变量内容。

const修饰指针变量的时候放在*左边限制指针变量的内容,不能直接修改内容来改变变量,但是可以通过修改指针变量的本身来改变变量的值。

1.6 指针运算

1.6.1 指针+-整数

1.6.2 指针-指针

指针 - 指针 = 两个指针之间的元素个数,但要保证两个指针指向一个空间

创建了my_strlen函数来计算数组中元素的个数,与普通方法(strlen函数直接计算)不同的是这种方法是运用指针,通过循环让指针++逐个向前移动,直到遇到\0停止。

1.6.3 指针的关系运算(指针和指针比较大小)

1.7 野指针

野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
造成原因:1.指针未初始化

2.指针越界访问

在while 循环中直接让p++,通过指针++不断移动打印出*p,与以往通过arr打印的方法不同

3.指针指向的空间释放

如何规避野指针?

1.指针初始化

给初始化一个明确的地址,不知道那个地址就初始化NULL

NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是无法使用的,读写该地址 会报错

2.小心指针越界

3.指针变量不再使用时,及时置NULL,指针使用之前检查有效性

4.避免返回局部变量的地址

1.8 assert断言

assert.h 头文件定义了宏 assert() ,⽤于在运行时确保程序符合指定条件,如果不符合,就报
错终止运行。这个宏常常被称为“断言”。
assert出现错误直接会报错,并指明在那个文件那一行
assert比if语句好用,因为无需更改代码就能开启或关闭 assert() 的机制。如果已经确认程序没有问题,不需要再做断言,就在 #include <assert.h> 语句的前面,定义⼀个宏 NDEBUG

1.9 指针的使用和传址调用

Swapl 这个交换不成功是因为在进行传递时,x和y只是a和b的形参,只把a和b中的数复印了一份,x和y是独立的变量,我们可以理解复印件不能改变原件,所以a和b没有被x和y影响,但Swap2就直接通过地址(指针),准确修改了a和b的值。

void Swapl(int x, int y)
{
	int tmp = x;
	x = y;
	y = tmp;

}
int main()
{
  int a = 10;
	int b = 20;
	printf("交换前:a=%d b=%d\n", a, b);
	Swapl(a, b);
	printf("交换后:a=%d b=%d\n", a, b);

	return 0;
}

//---------------------------------

void Swap2(int* pa, int* pb)
{
	int tmp = 0;
	tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}
int main()
{
	int a = 10;
	int b = 20;
	printf("交换前:a=%d b=%d\n", a, b);
	Swap2(&a, &b);
    printf("交换后:a=%d b=%d\n", a, b);

	return 0;
}

2.1 数组名的理解

这里数组名是数组首元素地址,但有两个例外,1.sizeof (数组名)中sizeof 中单独放数组名表示的是整个数组,计算的也是整个数组的大小,单位是字节。2.&数组名,中数组名也表示整个数组,取地址也是整个数组的地址,虽然打印出来会感觉相同但经过简单的+1就能看出不同。

​

int main()
{
	int arr[10] = { 0 };
	printf("arr      =%p\n", arr);
	printf("arr+1    =%p\n", arr+1);

	printf("&arr[0]  =%p\n", &arr[0]);
	printf("&arr[0]+1=%p\n", &arr[0]+1);

	printf("&arr     =%p\n", &arr);
	printf("&arr+1   =%p\n", &arr+1);

	return 0;
}

运行结果如图

2.2 使用指针访问数组

注意:*(p+i)==p[i]==arr[i]==*(arr+i)

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++)
	{
		scanf("%d", p + i);

	}
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p+i));

	}

	return 0;
}

2.3 冒号排序

核心思想:两两相邻的元素进行比较

将降序变成升序

void bubble_sort(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}
void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	//排序
    int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);
	//打印
	print_arr(arr, sz);
	return 0;
}

2.4 二级指针

int main()
{
	int a = 10;
	int* p = &a;//p是一级指针

	int * * pp = &p;

	return 0;
}

2.5 指针数组

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };

	int* arr[] = { arr1,arr2,arr3 };

	//打印数组
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}

	return 0;
}

3.1 字符指针变量

int main()
{
	char ch = 'w';
	char* pc = &ch;//pc是指针变量

	char arr[10] = "abcdef";
	char* p1 = arr;
	*p1 = 'w';
	//这上下两个都是指&a,
	//区别在于上面这个数组内容是可以变的
	//下面这个常量字符串的内容不能修改
	char* p2 = "abcdef";
	*p2 = 'w';
	return 0;
}

调试会发生这种情况

这里有一个很有意思的题目

#include <stdio.h>
int main()
{
	char str1[] = "hello bit.";
	char str2[] = "hello bit.";
	const char* str3 = "hello bit.";
	const char* str4 = "hello bit.";
	if (str1 == str2)
		printf("str1 and str2 are same\n");//1
	else
		printf("str1 and str2 are not same\n");//2

	if (str3 == str4)
		printf("str3 and str4 are same\n");//3
	else
		printf("str3 and str4 are not same\n");//4

	return 0;
}//答案是2和3!!!!

str 1的意思是向内存申请了一个空间,存了hello bit.\0,str2的意思也是一样,str3的意思是向内存申请空间,存了hello bit.\0的地址,str4也想和str3一样,但相同的常量字符串没必要保存两份,因为常量字符串不会被修改,因此str4中存的地址和str3的地址是一样的。这个题目中数组名表示的是首字母地址,是在问地址等不等。

3.2 数组指针变量

本质还是指针

在p2的例子中因为()的原因p先与*结合,意思是p2是指针变量,在加上了[10]表示指向了一个大小为10个整型的数组。而p1的例子里int*就已经说明了p1是指针数组,10代表有10个数据储存空间。

int  main()
{
	int a = 10;
	int* pa = &a;
	//整型指针
	char ch = 'w';
	char* pc = &ch;
	//字符指针
	int arr[10] = { 0 };
	int(*p)[10] = &arr;//取出的是数组的指针
	//arr是数组首元素的指针
	// 
	//int* p1[10];p1是指针数组,存放指针的数组
	//int (*p2)[10];p2是指针变量,指向的是数组---数组指针

	return 0;
}

3.3 二维数组传参的本质

二维数组的数组名也是数组首元素的地址,我们可以把二维数组想象成由一维数组组成的,就像这个例子中,这个二维数组是用三个一维数组由上到下排列组合,所以这里首元素的地址就是第一行的地址,第一行的地址就是一维数组的地址,所以类型是数组指针变量

void print(int arr[3][5], int x, int y)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < x; i++)
	{
		for (j = 0; j < y; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
////////////////////////////////////////////////////////
//指针形式
///////////////////////////////////////////////////////
void print(int (*arr)[5], int x, int y)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < x; i++)
	{
		for (j = 0; j < y; j++)
		{
			printf("%d ", *(*(arr + i) + j));
		}
		printf("\n");

	}
	
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	print(arr, 3, 5);
	//arr首元素地址
	return 0;
}

3.4 函数指针变量

函数中&ADD和ADD都是函数的地址,没有什么首元素

书写方法如图

似乎*没有什么用

int ADD(int x, int y)
{
	return x + y;
}
int main()
{
	int (*pc)(int, int) = &ADD;

	int c =ADD(2, 3);//函数名调用
	printf("%d\n", c);

	int d = (*pc)(3, 4);//函数指针调用
	printf("%d\n", d);

	int e = pc(4, 5);
	printf("%d\n", e);
	return 0;
}

接下来分析两个有意思的代码

1.拆解一下
    void(*)(),0是in他,地址《--- 0,(void(*)())0,强制类型转换,(*(void(*)())0)找到这个函数,(*(void(*)()) 0)()进行调用,()无参数

int main()
{
	(*(void(*)()) 0)();
	//拆解一下
	//void(*)()
	//0是int
	//地址《--- 0
	// (void(*)())0
	//强制类型转换
	//(*(void(*)())0)找到这个函数
	//(*(void(*)()) 0)()进行调用,()没有参数
	return 0;
}

2.这是一个函数声明 

最后的返回是void(*)(int)类型,也是一个函数指针变量

3.5 typedef

typedef 是⽤来类型重命名的,可以将复杂的类型,简单化.
typedef unsigned int u_int;

typedef int* pint_t;

typedef int(* parr_t)[5];//p是指针变量,去掉
//parr_t就是int(*)[5]

typedef void(*pf_t)(int);//pf_t就是void(*)(int)
int main()
{
	pint_t p1;
	int* p2;

	u_int a1;
	unsigned int a2;

	return 0;
}

3.6 函数指针数组

int ADD(int x, int y)
{
	return x + y;
}
int SUB(int x, int y)
{
	return x - y;
}
int MUL(int x, int y)
{
	return x * y;
}
int DIV(int x, int y)
{
	return x / y;
}
int main()
{
	int (*pf1)(int , int ) = ADD;
	//把这四个存一起
	int (*pfarr[4])(int, int) = { ADD,SUB,MUL,DIV };//pfarr是函数指针数组

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值