C语言----指针(初阶)

目录

  • 1.指针是什么
  • 2.指针和指针类型
  • 3.野指针
    • 3.1野指针成因
    • 3.2如何避免野指针
  • 4.指针运算
    • 4.1指针+-整数
    • 4.2指针-指针
    • 4.3指针的关系运算
  • 5.指针和数组
  • 6.二级指针
  • 7.指针数组

1.指针是什么

为了管理计算机的内存空间,把内存划分为一个个小的内存单元,一个内存单元的大小为一个字节。内存的空间是很大的,可能是4G/8G/16G/32G,要找到其中的某个内存单元十分难找。我们可以把这个空间比作一个大房子,这个大房子里有许多小房间,要找到某个小房间,就应该要有相应的房间编号。计算机也是这个思路,每个内存单元都有一个唯一的编号,这个编号也称为地址。写程序时,创建的变量、数组等都要在内存上开辟空间。
C语言----指针(初阶)_第1张图片

    int a = 10;//开辟一块空间
	int* pa = &a;//pa是专门用来存放地址的,这里pa也被称为指针变量

1. 指针是内存中一个最小单元的编号,也就是地址
2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量(存放在指针中的值都被当成地址处理)
总结:指针就是地址,口语中说的指针通常指的是指针变量

地址是怎样产生的呢?
对应32位的机器,我们假设有32根地址线,每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0)
C语言----指针(初阶)_第2张图片

64位的机器上有2的64次方种

每个地址标识一个字节,那我们就可以给 (2^32Byte == 2^32/1024KB == 2 ^ 32/1024/1024MB==2^32/1024/1024/1024GB == 4GB) 4G的空间进行编址。

这里我们就明白:
在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。
那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。

总结:
指针变量是用来存放地址的,地址是唯一标示一个内存单元的。
指针的大小在32位平台是4个字节,在64位平台是8个字节。

2.指针和指针类型

char* 类型的指针是为了存放 char 类型变量的地址。
short* 类型的指针是为了存放 short 类型变量的地址。
int* 类型的指针是为了存放 int 类型变量的地址。
其他类型同理

指针虽有不同的类型,但指针变量的大小都是4个字节

    printf("%d\n", sizeof(char*));//4
	printf("%d\n", sizeof(short*));//4
	printf("%d\n", sizeof(int*));//4
	printf("%d\n", sizeof(long*));//4
	printf("%d\n", sizeof(float*));//4
	printf("%d\n", sizeof(double*));//4

既然大小是相同的,那么指针有不同的类型的意义是什么呢?

    int a = 0x11223344;//0x开头是16进制数字
	//  int类型 4个字节,11 22 33 44分别占一个字节
	int* pa = &a;
	*pa = 0;

打开调试窗口中的内存,有:C语言----指针(初阶)_第3张图片
通过调试发现:
C语言----指针(初阶)_第4张图片
我们把int* 改为char*,通过调试:
C语言----指针(初阶)_第5张图片
由此我们可以推断出:
int* 的指针解引用访问4个字节
char* 的指针解引用访问1个字节
总结:指针类型可以决定指针解引用的时候访问多少个字节(指针的权限)。

short* -->访问2个字节
float* -->访问4个字节
double* -->访问8个字节

type * p *说明p是指针变量

1.p指向的对象的类型
2.p解引用的时候访问的对象的大小是sizeof(type)

我们可以再看一个例子:
C语言----指针(初阶)_第6张图片
总结:指针类型决定指针+/-1操作时的步长
整型指针+1跳过4个字节
字符指针+1跳过1个字节

type * p
+/- n 跳过的是n*sizeof(type)个字节

3.野指针

3.1野指针成因

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

    int* p;//局部变量不初始化的时候,内容是随机值
	//  野指针
	*p = 20;

2.指针越界访问

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

C语言----指针(初阶)_第7张图片
3.指针指向的空间释放

int* test()
{
	int a = 100;
	return &a;
}
int main()
{
	int* p = test();
	printf("%d\n", *p);
	return 0;
}

p变量接收a返回的地址,但是a是局部变量,a的空间进入函数创建,出函数还给操作系统,此时p访问a的空间是非法访问,所以p是野指针。

3.2如何避免野指针

1.指针初始化
1.明确知道指针应该初始化为谁的地址,就直接初始化
2.不知道指针初始化为什么值,暂时初始化为NULL(本质是0)

   int* ptr = NULL;

C语言----指针(初阶)_第8张图片

2.小心指针越界
3.指针指向的空间释放,及时置为NULL

	int a = 10;
	int* p = &a;
	int* ptr = NULL;//ptr是空指针,没有指向任何空间,不能直接使用
	if (ptr != NULL)
	{
		//使用
	}

4.避免返回局部变量的地址
5.使用之前经检查

4.指针运算

4.1指针±整数

#define N_VALUES 5
float values[N_VALUES];
float* vp;
for (vp = &values[0]; vp < &values[N_VALUES];)
{  //取首元素的地址
	*vp++ = 0;
}

C语言----指针(初阶)_第9张图片

随着下标的增长,地址由低到高变化。* vp++,后置++优先级较高,先使用后++,所以 *p解引用给所指向的内容赋值0,++只作用于vp,vp++后到下个位置(地址),以此类推。

打印数组元素

int main()
{
    int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
    int* p = arr;  // 数组名代表数组首元素的地址
    for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i)
    {
        printf("%d ", *p);   // *p: 取到p所指向位置的元素
        ++p;                 // 获取p的下一个位置
    }

    return 0;
}

另一种写法:

    printf("%d ", *(p + i));//p直接跳到p+i的位置,再取到所指向位置的元素

//int arr[10]
//int* p = arr;
//*(p+i)== arr[i] == *(arr+i) == *(i+arr) == i[arr]
arr 是数组名,数组名是首元素的地址 即指针

4.2指针-指针

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

C语言----指针(初阶)_第10张图片
我们可以知道:
指针-指针得到的数值的绝对值:是指针与指针之间的元素个数
注意:指针-指针运算的前提是两个指针指向同一块空间

	int arr[10] = { 0 };
	char ch[5] = { 0 };
	printf("%d\n", &ch[4] - &arr[0]);//错误

4.3指针的关系运算

我们知道地址是有大小的,指针的关系运算就是比较指针的大小
例题分析:

   #define N_VALUES 5
   float values[N_VALUES];
   float *vp;
   for(vp = &values[N_VALUES]; vp > &values[0];)
   {
       *--vp = 0;
   }

刚开始下标为5,vp指向最后一个元素的位置的后面的地址,而这个地址大于values[0] 的地址,满足条件;由于前置–优先级大于*,所以先vp–后地址指向前一个元素,然后再解引用赋值0,以此类推,直到循环结束。
C语言----指针(初阶)_第11张图片
这个代码还可以简化:

for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
    *vp = 0;
}

刚开始下标为4,即vp指向最后一个元素的地址,满足循环条件,*解引用赋值0,vp再–,指向是一个元素的地址,以此类推。当vp>= &values[0],地址又向前一步,此时这个地址小于下标0这个地址,不满足条件跳出循环。
C语言----指针(初阶)_第12张图片
这个代码实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。

标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

C语言----指针(初阶)_第13张图片

5.指针和数组

指针就是指针变量,指针变量的大小是固定的(4/8个字节),专门是用来存放地址的;数组就是数组,表示指针,数组一块连续的空间,可以存放1个或多个类型相同的数据。
那么两者又有什么关系呢?
联系:数组中,数组名是数组首元素的地址,数组名 == 地址 == 指针,数组是可以通过指针来访问的(前提:知道数组首元素的地址)
打印1-10:

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

C语言----指针(初阶)_第14张图片

6.二级指针

    int a = 10;
	int* p = &a;//p是一级指针变量,指针变量也是变量,
	           //变量是在内存中开辟空间的,是变量就有地址
	int** pp = &p;//pp就是二级指针,二级指针变量是用来存放一级指针的地址           

C语言----指针(初阶)_第15张图片

7.指针数组

指针数组的本质是数组,就像整型数组是存放整型的数组,字符数组是存放字符的数组,所以指针数组就是存放指针的数组。
打印数组元素:

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	int* parr[] = { arr1,arr2,arr3 };//指针数组
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", parr[i][j]);//1,2,3,4,5
                        //*(parr[i]+j)//2,3,4,5,6
                                      //3,4,5,6,7
		}
		printf("\n");
	}

	return 0;
}

C语言----指针(初阶)_第16张图片
我们发现指针数组打印数组中的元素与二维数组类似,但是这两者是有区别的。
真实的二维数组是一块连续的空间,指针数组是模拟出二维数组
C语言----指针(初阶)_第17张图片

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