【C语言-初阶指针】穿...穿梭机?

前言

入门编程后,接触到的方方面面都透露着指针的王者气息——好像是块硬骨头。
没事儿,慢慢啃,细细啃,总能啃下来
所以,先来个初阶指针!


1. 指针是什么?

指针和内存是息息相关的,所以先看看内存

【C语言-初阶指针】穿...穿梭机?_第1张图片

指针:内存中最小单元的编号(地址)

就是图中绿色的

指针变量:存放指针(地址)的变量——可以通过地址找到地址所指向的内存区域

口语中我们说创建一个指针存放谁谁的地址,指的就是指针变量

研究一个东西就得不断提出疑问:

  • 一个小的内存单元是多大?为什么?
  • 地址如何产生?能产生多少?
  • 指针变量的大小?
  1. 1个字节。
    为什么不是bit?为什么不是kb?
    bit: 一个 char 都要给8个地址来存放,太浪费了
    kb: 只是存放一个 char 都要占用1kb,空间又浪费了

  2. 通过 通电的地址线(硬件)
    对于32位机器,有32根地址线。通电后,每根地址先寻址时,都会产生 高电平(1)低电平(0)
    则可以产生

00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001

11111111 11111111 11111111 11111111

即 2^32 个地址

一个地址标识一个字节:2^32 byte == 2^32/1024 kb ==2^32/1024/1024mb == 2^32gb == 4gb
即能编址 4gb 的空间

  1. 32位机器:4个字节 ;64位机器:8个字节
    32位的机器上,地址是 32个bit 组成的二进制序列 ,要用 4个字节 来存
    64位的机器上,地址时 64个bit 组成的,用 8个字节

总结:

  1. 指针就是内存地址指针变量用来存指针(口语中常把指针变量叫成指针)
  2. 地址的产生是物理性的(通电后的高低电平),能编制多大空间取决于机器(地址线)
  3. 指针的大小: 32位平台上是 4个字节 ; 64位平台上是8个字节

2. 指针变量怎么用?

2.1 指针变量的定义

形式: type + * + 变量名

int a = 10;
int* p = &a;

“*” 说明,p是一个指针变量; “int” 说明,这个指针变量指向的变量类型为int

  • 指针变量的 type 有什么意义呢?既然所有指针大小都相同,为何不统一一个“指针类型”呢?
  • 咱们先按下不表

2.2 指针的解引用

咱们说:“指针变量可以通过它里面的地址找到地址所指向的内存空间”,怎么实现呢?

解引用(*),我愿称它为“穿梭机”,给它一个地址,就穿梭到对应的位置

int main()
{
	int a = 10;
	int* pa = &a;//(1)
	
	*pa = 20;//(2)
	//这里换成 *(&a) = 20;也是可以的哦
	printf("%d\n", a);
	return 0;
   
    结果:20
}

解析:

  1. 创建了一个指向int的指针变量,里边放a的地址
  2. 把pa这个存有地址的指针变量放到“ 穿梭机 ”里面,就可以穿梭到a的位置了!

总结:穿梭机* 只需要一个地址,就能完成穿梭!


2.3 指针类型的意义

来看两个例子

//例1
#include
int main()
{
	int a = 0;
	char b = 0;

	int* pa = &a;
	char* pb = &b;

	printf("pa=%p ---->", pa);
	printf("pa+1=%p\n", pa + 1);

	printf("pb=%p ---->", pb);
	printf("pb+1=%p\n", pb + 1);
	return 0;
}
输出结果:pa=02B6F6F0 ---->pa+1=02B6F6F4   
		 pb=02B6F6E7 ---->pb+1=02B6F6E8

		同样是+1,int*类型的 pa 就移动了 4个字节 
			 而 char*类型的 pb 只移动了 1个字节
//例2:主要看下面调试的过程
#include
int main()
{
	int a = 0x11223344;

	char* pc = (char*)&a;//强行把指向int的地址放到 char*类型的指针变量
	int* pi = &a;

	*pc = 0;
	*pi = 0;

	return 0;
}

【C语言-初阶指针】穿...穿梭机?_第2张图片

  1. 通过例1可以知道:指针变量的类型,决定了指针前后移动的距离(int* 就移动4bytes ; char*就移动1bytes)
  2. 通过例2可以知道:指针变量的类型,决定了解引用时能访问多大空间

2.4 指针的 +/- 整数

#include
int main()
{
	int a = 0;
	int* pa = &a;

	printf("%p\n", pa);
	printf("%p\n", pa + 1);
	printf("%p\n", pa - 1);

	return 0;
}

结果:
00D5F700
00D5F704
00D5F6FC

通过例子可以知道:指针的 +/- 运算,实质上是 向前/后移动 , 移动的距离,由指针变量类型决定


2.5 指针-指针

| 指针-指针 | = 两个地址之间元素的个数

通常在同一块区域(如同一个数组)中大指针-小指针

int my_strlen(char* start)
{
	char* end = start;
	while (*end != '\0')
	{
		end++;
	}
	return end - start;
}
int main()
{
	char str[] = "bacon";
	int len = my_strlen(str);
	printf("%d\n", len);
	return 0;
}

此处就使用了指针-指针,返回了字符串头尾地址之间的元素个数


2.6 指针的关系运算

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	int* p;
	for (p = arr; p < &arr[5];p++ )//指针的关系运算
	{
		*p = 0;
	}
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ", arr[i]);
	}
}
  • 标准规定:
    允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,
    但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

  • 对于 arr[10]
    可以用指针和 " arr[11] " 比 ; 不能和 " arr[0-1] "

  • 但是大多数编译器也能跑过去


3. 野指针?

野指针:指向的位置不可知的指针

3.1 野指针成因

  1. 指针未初始化
#include 
int main()
{
	int* p;//局部变量指针未初始化,默认为随机值
	*p = 20;//不知道哪位幸运观众被赋了个20
	return 0;
}
  1. 指针越界访问
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	int* parr = arr;
	int i = 0;
	for (i = 0; i <= 5; i++)//这里循环了6次哦
	{
		//第六次循环,parr = &arr[6] 
		//越界访问,指向的地方已经不明了
		*(parr) = 0;
		parr++;
	}
	
	return 0;
}

3.指针指向的空间释放了


int*  Fun()
{
	int a = 0;
	return &a;//返回了a的地址后,a就销毁了
}

int main()
{
	int* p = Fun();//p到底指向谁?
	return 0;
}


3.2 如何规避野指针?

  1. 指针初始化
  2. 警惕越界访问(创建一个指针时,想好它应该归属的范围
  3. 若指向空间释放,立即置NULL( p = NULL)
  4. 避免返回局部变量的地址
  5. 使用指针之前检查有效性,实现如下
int main()
{
	int* p = NULL;
	int a = 10;
	p = &a;

	if (p != NULL)
	{
		*p = 20;
	}
	return 0;
}

4. 指针和数组的“爱恨情仇”

前面说过,数组名就是数组首元素地址(两种例外),那引用数组的第n个元素到底是怎么实现的呢?


int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9 };
	int* parr = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("arr[%d]=%p -------> *(parr+%d)=%p\n", i, &(arr[i]), i,parr+i);
	}
	return 0;
}


结果:
arr[0]=0051F85C -------> *(parr+0)=0051F85C
arr[1]=0051F860 -------> *(parr+1)=0051F860
arr[2]=0051F864 -------> *(parr+2)=0051F864
arr[3]=0051F868 -------> *(parr+3)=0051F868
arr[4]=0051F86C -------> *(parr+4)=0051F86C
arr[5]=0051F870 -------> *(parr+5)=0051F870
arr[6]=0051F874 -------> *(parr+6)=0051F874
arr[7]=0051F878 -------> *(parr+7)=0051F878
arr[8]=0051F87C -------> *(parr+8)=0051F87C
arr[9]=0051F880 -------> *(parr+9)=0051F880

这里可以看到:arr [ i ] == *( parr + i )


5. 二级指针

我是这么理解的:一级指针,穿梭一次就能找到地儿;二级指针要穿梭两次

二级指针:指向一级指针的指针变量,存放着一级指针的地址

int main()
{
	int a = 10;
	int* pa = &a;
	int** ppa = &pa;
	printf("&pa = %p  <---->  ppa = %p\n", &pa, ppa);//应该是pa的地址
	printf("&a = %p\npa = %p\nppa = %p\n",   &a   ,   &*(pa)   ,  &**(ppa)   );
	return 0;
}

结果:
&pa = 02D6F9F4  <---->  ppa = 02D6F9F4
&a = 02D6FA00
pa = 02D6FA00
ppa = 02D6FA00
  • 对于二级指针 ppa,用一个“ * ”就穿梭一次,找到一级指针 pa
  • 对于二级指针 ppa,用两个“ * ”就穿梭两次,找到变量 a

6. 指针数组

指针是 定语 , 数组是 主语 ,所以

指针数组:存放指针变量的数组


int main()
{
	int num[5] = { 1,2,3,4,5 };
	int* p_num1 = &num[0];
	int* p_num2 = &num[1];
	int* p_num3 = &num[2];
	int* p_num4 = &num[3];
	int* p_num5 = &num[4];
	//指针数组 p_num 存放了
	//p_num1, p_num2, p_num3, p_num4, p_num5 5个指针
	int* p_num[5] = {  p_num1, p_num2, p_num3, p_num4, p_num5 };
	int i = 0;
	int j = 0;
	for (i = 5; i > 0; i--)
	{
		*(p_num[j]) = i;
		j++;
	}
	
	for (i = 0; i < 5; i++)
	{
		printf("%d ", num[i]);
	}
	return 0;
}
结果:
5 4 3 2 1

这就是初阶指针的内容啦,我觉得 “穿梭机” 这个概念很不错 ^ ^


培根的blog , 与你共同进步!

你可能感兴趣的:(C语言,c语言,开发语言)