我与指针(个人理解中较为重要和困难的)持续更新

 首先声明,这不是系统的学习C语言的指针,只是一些个人认为学C语言重要的难理解的点,

适合休闲的时候看看,放松看看。

希望本篇博客,可以小小的帮助你以我的学习指针的思路,

你断然不会去考虑一个一个创建整型变量,然后一一打印它们,这样太蠢,蠢到你生理性的排斥这样去写这个编程题,

相信你已经有些基础了,会使用数组来完成这个题目,int arr[10] = {0};       

        这样就不用创建10个整型变量,数组中每个元素都没有自己的"名字",有编号,没有"名字",没有a,b,c,d...这样的名字,它们有的是 数组名[下标] ,这样的看起来冷酷无情的编号。

        像我们对新观察到的天体的命名,像我们的游戏ID一样,一串数组的下标

我们的指针就是如此,和天体的编号,数组的下标,游戏ID没什么本质的不同,天体的编号对应的是天体,数组下标对应数组内的元素,ID对应的相应的账号,指针对应着内存中相应的字节,这么一想指针的概念就很容易理解了,它的意义,它的重要性...

           指针存储的形式 :整型? 浮点?...

C语言中指针在x86(三十二位环境)下占四个字节,像个int整型,在64位环境下占8个字节

是数字的形式,(毕竟二进制除了1就是0,你还想用什么存?,想问为什么是四个或八个字节而不是其它大小?你咋不去问int整型为啥是四个字节,人家设计的你管啊。-)

//四个字节不算负的能表示42亿个字节多,大概就是4GB。(仅仅猜测,完全没去搜)

//别问我有没有负数的指针,我也不知道啊!

                指针的类型 

指针变量的存储⼤⼩和类型⽆关,只要是指针变量,他的大小不是32位环境下的4字节就是64位环境下的8字节

int* p1;

char*  p2;

int (*p3)[10];

double* p4 ....

int*中的*表示p1变量是指针, int表示指针指向的对象是int类型,

char*中的*也是一样,表示p2变量是指针,除去*剩下的是p2这个指针指向的对象是char类型

        到第三个数组指针你会发现 这个指针在定义的时候是在"中间",指向对象的类型的中间(有点像是定义一个数组        "int arr[10]        只不过数组名用*p3代替了")

//这里()使用因为符号的优先级,[] 大于 *         如果你不括起来,就会因为符号的优先级而变成创建一个有十个元素,存储元素类型是int*(这个和p1变量的类型一致)的名叫p3的数组(指针数组)。

"中间"类型的指针

        我们不难发现这些定义变量在中间的指针其原因是由于本来指向的对象的类型 就是这么定义它们的变量。

函数指针变量:

        这也是同数组那样的要求变量定义的时候,变量在"中间",

依旧是像int* p;这样指向对象的类型+ * + 要定义的指针变量名

        例如:

int (*p)(int, int);//声明了一个返回类型是int,两个形参是int的函数指针。

"之所以要把这个当成重点,因为从刚开始创建数组,函数的时候,你可能和我一样把定义数组,函数变量当成了很自然的事,而导致理解创建指针变量的时候遇到了一些困难,难以很快理解,为什么变量在中间...即使现在还是不能想明白为什么要变量在中间...但是有了以前这样创建过的经历,会更容易接受些。"

        两个经典的来自《C陷阱和缺陷》的代码:

int main()
{
	(*(void (*)())0)();
	void (*signal(int, void(*)(int)))(int);

	return 0;
}

使用typedef简化以更直观的形式:

typedef unsigned int uint;
typedef void(*v_p_int)(int);
typedef void(*v_p_v)();
int main()
{
	(*(void (*)())0)();
	(*(v_p_v)0)();
	//使用typedef类型定义"void(*)()"后简化
	void (*signal(int, void(*)(int)))(int);
	v_p_int signal(int, v_p_int);
	//使用typedef类型定义"void(*)(int)"后简化
	return 0;
}

' * '解引用的理解

        通过 ' * ',解引用符号访问指针指向的空间,

常见使用:

为什么说指针和数组下标很像?

通过指针加减实现地址的改变,解引用来访问不同的空间,(不必手写多个变量符号)

int main()
{
	int arr[10] = { 0 };
	for (int i = 0; i < 10; i++) {
		printf("%d ", arr[1]);
	}
	printf("\n");
	for (int i = 0; i < 10; i++) {
		printf("%d ", *(arr + 1));
	}
	printf("\n");
	return 0;
}

//其实更应该说下标和指针很像,不过我们先学的数组 

不常见的,难理解的就要和传参,数组名扯上关系了...如下

数组名和指针

1.数组名是首元素地址,数组名是首元素地址,数组名是首元素地址....

2.若数组名单独在sizeof操作符内,或&数组名,   这里的数组名表示整个数组(指向的空间类型),实际上值还是数组空间起始的那个低地址.

对于二维数组来说首元素是整个第一行,

我们可以利用sizeof计算arr[10][10]的首元素大小

int main()
{
	int arr[10][10] = { 0 };
	printf("%zd\n", sizeof(arr));
	printf("%zd\n", sizeof(*arr));
	//只要不是数组名单独出现在sizeof内就是首元素地址
	//这里利用解引用符号是避免sizeof把arr当作指针
	//(我们要测的就是这个首元素地址指向的空间的大小)
	printf("%zd\n", sizeof( *(&arr) ));
	return 0;
}

指针+-指针和整数

你试过指针加指针吗?

会报错的,实际上指针+指针的结果很可能存不下。

但是指针减指针是允许的。(算出的结果的绝对值是两指针中间有多少个具有指针指向元素类型的元素)

//不同类型的指针相减真的很怪,试了一试说不出所以然来。

指针加减整数,就好比数组下标的加减,

不过对于二维数组名的加减,我们最好先熟悉一下二级指针。
 

对于数组    int arr[3][4] = { {1,2,3,4} ,{5,6,7,8}, {9,10,11,12} };来说,

arr+1,代表首元素地址向后推移一个地址的类型之后的地址,我们知道此处表示的首元素是第一行,arr+1 == 跳过一行,

*(arr+1),代表解引用这个地址,是在表示这一行(这个数组的第二行),不过,它可以看作另类的数组名,相当于arr[1],
sizeof计算的arr+1指针所指向的空间,大小正是那一行的空间大小。

int main()
{
	int arr[3][4] = { {1,2,3,4} ,{5,6,7,8}, {9,10,11,12} };
	printf("%zd\n", sizeof(*(arr + 1)));
	return 0;
}

 

void* 指针和指针的初始化,const修饰指针

void*的指针类型,可以接收各种其它类型的指针,主要在函数形参上用到,

初始化指针只用使用0,也就是NULL,

例如,int * p = NULL; 指针初始化是良好的编程习惯,其次,指针指向的空间销毁了,及时赋值NULL。

const 在我们模拟实现一些库函数的时候很常见,也是良好的编程习惯,可以增强健壮性。

const修饰指针变量的时候,以

int a = 0, b = 0, c = 0;

int* const pa = &a;

const int* pb = &b;

const int* const pc = &c;

在*的左边,表示*pa不能被修改,也就是不能通过解引用pa来修改a的值

在*右边,表示指针变量本身不能被修改,如pb就是一个const修饰的常量,

像pc指针变量就是既不能通过解引用修改c值,也不能修改指针 变量pc的值,

传址调,一维数组,二维数组的理解

不单独在sizeof操作符内,且无&,所以传递到函数的数组名是首元素地址

一维数组,就是arr[0]的地址,形参可以是arr[], 也可以[]里有元素个数(有没有都无所谓,编译器不会检查是否越界访问)

二维数组,首元素是第一行的地址,所以形参用指针表示的话得是一个指向第一行空间的指针,而用下标arr[][第一行的值],表示的话,很方便,但第二个[]内要有行的的值,(尽量保证这个值和定义的数组的行的值一样)

#include<stdio.h>

void test( int arr[][4],  int(*parr)[4] ) {
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 4; j++) {
			printf("%d ", *((*(arr + i)) + j));
		}
	}
	printf("\n");
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 4; j++) {
			printf("%d ", arr[i][j]);
		}
	}
	printf("\n");
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 4; j++) {
			printf("%d ", *((*(parr + i)) + j));
		}
	}
	printf("\n");
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 4; j++) {
			printf("%d ", parr[i][j]);
		}
	}
	printf("\n");


}

int main()
{
	int arr[3][4] = { {1,2,3,4} ,{5,6,7,8}, {9,10,11,12} };
	test(  arr, arr);
	return 0;
}

补充,你想下标和指针混用的话也行:

arr[i] == *(arr+i)

int main()
{
	int arr[3][4] = { {0,1,2,3},{4,5,6,7},{8,9,10,11} };
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 4; j++) {
			printf("%d ", *(arr[i] + j));
		}
	}
	return 0;
}

附,学到啥就加些啥,

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值