【C语言】初阶指针的简单使用 _指针运算 &指针和数组的关系[初阶篇 _学习专用]

1.指针是什么?

在学习指针的时候,我们经常会看到下面这段代码:

int main()
{
	int a = 10;
	int* pa = &a;
	*pa = 20;
}

之前并没有接触过指针的朋友们看到后可能是一头雾水,根本不知道从哪里去理解;下面我们就通过一些场景慢慢的去理解:

试想一下:如果你要给你的好朋友寄过去一些好吃的,然而你并不知道地址,这时你也许就会很懊恼;但如果你知道了你的好朋友的地址,你就可以通过快递把这些好吃的送到你朋友的身边。

把我们日常所说的地址类比到计算机,其实那些地址对应的就是计算机内存中的内存单元编号,也就是我们所说的指针。

比如说一栋宿舍楼,你在303号房间,你的朋友在605号房间,那么你就需要通过楼层和门牌号来找到你朋友所在的位置。

内存单元编号 = 地址 = 指针(指针变量,习惯于称为指针)。

指针就是存放地址的变量;下面通过一幅图来更好的理解上面的代码:

 * :解引用操作符,也叫间接访问操作符。

*pa就是找到0x0012ff40这片地址空间存储的数据,进而可以进行打印和修改。

这里还涉及到一个大小端存储的问题,我们所用到的vs2019是小端字节序存储(低位数据存储在低地址),把存储的地址倒着读取就是a的地址。 

指针的大小

int main()
{
	printf("%d\n", sizeof(int*));
	printf("%d\n", sizeof(char*));
	printf("%d\n", sizeof(long*));
	printf("%d\n", sizeof(long long*));
	printf("%d\n", sizeof(float*));
	printf("%d\n", sizeof(double*));
	return 0;
}

运行结果:

这里有很多人会有疑问:为什么结果是一样的,并且大小都是4?

那么接着往下看:

对于32位机器,假设有32根地址线,那么32根地址线产生的地址就会是:

1个字节 = 8个比特位;所以把这32个字节存储起来需要4个比特位;sizeof(指针变量)在32位机器下的结果就是4。

在64位机器下,地址是由64根地址线组成的,和32位机器下存储的道理是一样的——指针的大小是8个字节;

1.在32位机器下,指针的大小是一定的,都是4个字节。

2.在64位机器下,指针的大小也是一定的,都是8个字节。

2.指针和指针类型

指针也是变量,也是有类型的:

int a = 10;
int* pa = &a;//pa指向的空间时int类型,pa是一个整型指针
char ch = 'a';
char* pc = &ch;//pc指向的空间时char类型,pc是一个字符指针

2.1 解引用指针变量 

不同类型的指针变量解引用访问权限不同,光说不练假把式,下面通过两段程序来进一步了解:

程序1:

int main()
{
	int arr[10] = { 0 };
	char* pc = (char*)arr;
	for (int i = 0; i < 40; i++)
	{
		*pc = 'x';
		pc++;
	}
	return 0;
}

  

程序2:

int main()
{
	int arr[10] = { 0 };
	int* pa = arr;
	for (int i = 0; i < 10; i++)
	{
		*pa = 0x11223344;
		pa++;
	}
	return 0;
}

 

通过vs的调试窗口可以发现,int类型的指针解引用时可以访问四个字节,char类型的指针解引用时可以访问一个字节;

指针类型决定了解引用时的访问权限。

2.2 指针步长

不同的指针类型不仅解引用时访问权限有所不同,步长也是不同的:

int main()
{
	int a = 10;
	int* pa = &a;
	printf("%p\n", pa);
	printf("%p\n", pa + 1);
	char* pc = (char*)&a;
	printf("%p\n", pc);
	printf("%p\n", pc + 1);
	return 0;
}

 

指针的类型决定了指针向前或者向后一步所走的步长;

整型指针一步的步长为4个字节,字符型为1个字节。

3.野指针

野指针:指针指向的位置是不可知的随机的、不正确的、没有明确限制的

3.1 野指针成因

1.指针未初始化

int main()
{
	int* p;//局部变量指针未初始化,默认值为随机值
	*p = 20;
	return 0;
}

2.指针越界访问

int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	for (int i = 0; i <= 11; i++)
	{
		//当指针指向的范围超出数组arr的范围时,p就是野指针
		*(p++) = i;
	}
	return 0;
}

3.已经销毁的栈帧

int* test()
{
	int a = 10;
	return &a;
}
int main()
{
	int* pa = test();//出了test函数,栈帧销毁
	printf("%d\n", *pa);
	return 0;
}

3.2 如何避免野指针 

1.指针初始化

2.小心指针越界

3.指针指向空间释放即使置NULL

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

5.指针使用之前检查有效性

int main()
{
	int* p = NULL;
	int a = 10;
	p = &a;
	if (p != NULL)
	{
		*p = 20;
	}
	return 0;
}

4.指针运算

4.1 指针+-整数

指针+整数:

int main()
{
	int arr[10] = { 0 };
	int* pa = arr;
	for (int i = 0; i < 10; i++)
	{
		*(pa+i) = 10;//指针+整数
	}
	return 0;
}

指针-整数和指针+整数是相似的,这里就不过多解释了。 

4.2 指针-指针

指针减指针是有前提的,相减的两个指针必须是连续的,一般常用语数组。

int main()
{
	int arr[10] = { 0 };
	printf("%d\n", &arr[9] - &arr[0]);
	return 0;
}

指针-指针得到的是指针之间元素的个数; 

指针减指针实现库函数strlen:

int my_strlen(char* str)
{
	char* ch = str;
	while (*str != '\0')
	{
		str++;
	}
	return str - ch;
}
int main()
{
	char arr[] = "abcdef";
	int ret = my_strlen(arr);
	return 0;
}

5.指针和数组 

首先,我们来了解一个概念——数组名;

数组名:首元素地址;下面两种情况除外;

1.sizeof(arr),arr表示整个数组,sizeof(arr)求出的是整个数组的大小。

2.&arr,此时arr表示整个数组,&arr取出的是整个数组的地址。

 

&arr和arr打印出的地址是相同的,那我们怎么证明&arr取出的是整个数组的地址呢?

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

	printf("%p\n", arr);
	printf("%p\n", arr + 1);
	return 0;
}

 

&arr+1所走的步长是40个字节,刚好是数组的大小,说明&arr取出的确实是整个数组的地址。 

6.二级指针

int main()
{
	int a = 10;
	int* pa = &a;

	int** ppa = &pa;

	return 0;
}

 

初阶指针的使用到这里就结束了,对以上内容存在疑问的可以直接私信博主,Fighting! Fighting! Fighting!

猜你喜欢

转载自blog.csdn.net/qq_63179783/article/details/123430009