【C语言初阶】❤️ 一篇C语言初阶指针竟然如此硬核?(收藏不后悔)❤️

温馨提示

大家好我是Cbiltps,在我的博客中如果有难以理解的句意难以用文字表达的重点,我会有配图。所以我的博客配图非常重要!!!

如果你对我感兴趣请看我的第一篇博客

开篇介绍

从今天开始我就写有关C语言指针的博客了,我的博客有【C语言初阶】【C语言进阶】两个分栏。

指针的内容多,所以在【C语言进阶】里面会有更深入的讲解,但是不会马上更新(我准备初阶指针写完后写结构体等内容),大家敬请期待!!!

本章重点

  1. 指针是什么
  2. 指针和指针类型
  3. 野指针
  4. 指针运算
  5. 指针和数组
  6. 二级指针
  7. 指针数组

正文开始


学前准备(了解内存)


想要学习并理解指针,首先要关注内存

内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的 。

所以为了有效的使用内存,就把内存划分成一个个小的内存单元,经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。

为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址
在这里插入图片描述
对地址线进行一个解释:对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0)。

创建变量的时候,都会在内存里开辟空间;且变量都有地址出变量地址如下:

#include <stdio.h>
int main()
{
    
    
	int num = 10;
	&num;//取出num的地址
	//注:这里num的4个字节,每个字节都有地址,取出的是第一个字节的地址(较小的地址)
	printf("%p\n", &num);//打印地址,%p是以地址的形式打印
	return 0;
}

在这里插入图片描述


1. 指针是什么?


在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。

由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元

因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。

上面我们已经对内存有一定的介绍,下面我们来看一看指针

指针是个变量,存放内存单元的地址(编号)。

1.1 对指针的理解

  • 地址指向了一个确定的内存空间,所以地址形象的被称为指针
  • 某变量是用来存放地址(指针),所以某变量是指针变量
int a = 10;//创建变量时开辟空间
int* pa = &a;//它的类型就是int* ,pa是指针变量
//pa用来存放地址(指针)

在未来口语化的说明中,当我们在说到指针的时候指针可能是个地址当然也有可能是指针变量,这两个东西大家要能够区分的出来!


2. 指针和指针类型


2.1 指针变量的大小

在32位的机器下,一个内存单元就是32个比特位的空间,换算下来就是4字节;64位机器下就是8字节。

当然也可以通过代码验证:

#include <stdio.h>

int main()
{
    
    
	printf("%d\n", sizeof(char*));
	printf("%d\n", sizeof(short*));
	printf("%d\n", sizeof(int*));
	printf("%d\n", sizeof(double*));//发现印出来的全部都是 4

	return 0;
}

结论:指针大小在32位平台是4个字节64位平台是8个字节

2.2 指针类型的意义

首先问一个问题:

指针大小在32位平台是4个字节64位平台是8个字节,不管什么类型,大家的大小都是一样的,那为什么还要区分类型呢?

先拿出我们的两段代码进行比较:

//代码1:
#include <stdio.h>

int main() 
{
    
    
	int a = 0x11223344;
	int* pa = &a;
	*pa = 0;
	return 0;
}
//代码2:
#include <stdio.h>

int main() 
{
    
    
	int a = 0x11223344;
	char* pa = &a;
	*pa = 0;
	return 0;
}

然后打开内存窗口观察:
在这里插入图片描述

看完了上面的解析,你们能感受到类型的意义是什么呢?

指针类型的意义:决定了指针解引用操作的时候,一次访问几个字节(访问内存的大小)

还没完,我们再来看一段代码:

#include <stdio.h>

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

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

	return 0;
}

运行结果:
在这里插入图片描述
指针类型的意义2:决定了指针 +- 整数的步长(指针 +- 整数的时候,跳过几个字节)

2.3 指针意义的实际应用

这里要用到指针的解引用,直接上两段代码:

#include <stdio.h>

int main()
{
    
    
	int arr[10] = {
    
     0 };
	int* p = arr;//&arr[0] - 类型是int*
	int i = 0;
	for (i = 0; i < 10; i++)
	{
    
    
		*(p + i) = 1;//0x 00 00 00 01 换成1
	}

	return 0;
}
#include <stdio.h>

int main()
{
    
    
	int arr[10] = {
    
     0 };
	char* pa = arr;
	int i = 0;

	for (i = 0; i < 40; i++)
	{
    
    
		*(pa + i) = 1;
	}

	return 0;
}

运行解析如下:
在这里插入图片描述
总结:指针的类型决定了对指针解引用的时候有多大的权限(能操作几个字节)。

比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。


3. 野指针


概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。

3.1 野指针成因

1. 指针未初始化

//这段代码是错误的
#include <stdio.h>

int main()
{
    
    
	int* p;//没有初始化,里边放的是随机值

	*p = 20;//通过p中存的随机值作为地址,找到一个空间,这个空间不属于我们当前的程序
	        //就造成了非法访问,p就是野指针
	        
	return 0;
}

2.指针越界访问

#include <stdio.h>

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

在这里插入图片描述
3. 指针指向的空间释放

int* test()
{
    
    
	int a = 10;//局部变量。当存储它的空间已经被释放之后,还记着它的地址,这个指针就叫野指针
	return &a;//这样a的生命周期到了,内存空间还给了系统,但是把地址传给了p
}

int main()
{
    
    
	int* p = test();
	printf("%d\n", *p);//这地方的p就是野指针。打印出来的值是10,但是不表示是对的
	return 0;
}

图片解析:
在这里插入图片描述

这里放在动态内存开辟的时候讲解,所以简单提示一下。

讲一下其他的东西吧(主要是拓展一下思维):
这里为什么会打印 10
是因为是把内存空间还给了系统,但是内容没变,也没有被覆盖,只是保留了上一次的值而已。

关于覆盖举个例子:

//我们稍微的改一下代码
#include <stdio.h>

int* test()
{
    
    
	int a = 10;//局部变量,
	return &a;//这样a的生命周期到了,内存空间还给了系统,但是把地址传给了p
}

int main()
{
    
    
	int* p = test();
	printf("hehe\n");//在这里就是所谓的覆盖
	printf("%d\n", *p);//这地方的p就是野指针
	
	return 0;
}

画图讲解:
在这里插入图片描述

3.2 如何规避野指针

1. 指针初始化

int a = 10;//明确地初始化,确定指向
int* p = &a;
	
int* q = NULL;//不知道一个指针当前应该指向哪里,可以初始化为 NULL(空指针)
//当然也可以 int* q = 0; , 但是不便于识别为空指针
//所以 NULL 的本质是0

2. 小心指针越界
指针使用的时候,尽量做到指针越界的判断(访问到边界的时候,是否越界)。

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

4. 尽量避免返回局部变量的地址
不是不能返回局部变量。

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

#include <stdio.h>

int main()
{
    
    
	int* p = NULL;
	*p = 100;//error
	//这里对空指针进行了解引用操作
	//空指针是0,0也是一个地址,0指向的地址空间是不允许使用的
	//空指针不能直接进行解引用

	//那如何做呢
	if (p != NULL)//当指针不是空指针的时候进行下一步
	{
    
    
		//...
	}
}

注意:
在做项目的时候,很多的bug是野指针在成的,所以要避免野指针!

有两点必须做到:指针要初始化+进行有效的判断

4. 指针运算

  • 指针 + - 整数
  • 指针 - 指针
  • 指针的关系运算

4.1 指针 + - 整数

指针 + 整数:直接通过代码体现如下

#include <stdio.h>
#define N_VALUES 5

int main()
{
    
    
	float values[N_VALUES];
	float* vp = NULL;
	
	for (vp = &values[0]; vp < &values[N_VALUES];)
	{
    
    
		*vp++ = 0;//这里的++意思就是跳过一个float类型
	}

	return 0;
}

画图解析:
在这里插入图片描述
指针 - 整数:看代码

#include <stdio.h>

int main()
{
    
    
	int arr[10] = {
    
     1,2,3,4,5,6,7,8,9,0 };
	int* p = &arr[10];

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

	return 0;
}

通过运行结果看到地址减去4个字节:
在这里插入图片描述
在这里对于指针 + - 整数不做过多的介绍,请大家看上面的指针类型的意义就可以深入的理解!

4.2 指针 - 指针

我们看到本小节标题是指针 - 指针,那可以指针 + 指针吗?
答案是不可以的,没有意义!

那我们不妨直接写代码:

#include <stdio.h>

int main()
{
    
    
	int arr[10] = {
    
     1,2,3,4,5,6,7,8,9,0 };
	int* p = &arr[10];

	printf("%d\n", &arr[9] - &arr[0]);//打印出 9
	printf("%d\n", &arr[0] - &arr[9]);//打印出 -9

	return 0;
}

指针 - 指针得到的数字的绝对值是指针和指针之间元素的个数
注意:指针 - 指针有个前提是两个指针指向同一块区域

那我们来实际运用一下:

用上面的知识写一个函数,求字符串长度

#include <stdio.h>

int my_strlen(char* s)
{
    
    
	char* start = s;//表示的是首元素的地址
	while (*s != '\0')
	{
    
    
		s++;
	}

	return s-start;// 斜杠0的地址减去首元素的地址,就是字符串长度
}

int main()
{
    
    
	char arr[] = "abcdef";
	int len = my_strlen(arr);
	printf("%d\n", len);

	return 0;
}

4.3 指针的关系运算

指针的关系运算就是比较指针的大小

来看一段代码:

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

代码简化, 这将代码修改如下:

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

以上两种代码方式哪一个好呢?

实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免简化写法,因为C语言标准并不保证它可行。

标准规定:

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

画图理解一下:
在这里插入图片描述


5. 指针和数组的关系


  • 数组是一块连续的空间,放的是相同类型的元素;
    数组的大小与元素类型,元素个数有关。

  • 指针(变量)是一个变量,放地址;
    指针的大小是4/8字节。

在这里告诉大家:数组名就是首元素的地址
所以我们可以用指针访问数组:

#include <stdio.h>

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++)
	{
    
    
		*(p + i) = i;
		printf("%d ", *(p + i));
	}

	return 0;
}

6. 二级指针


首先来思考问题:指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里

那我们来读一段代码并画图讲解一下:

#include <stdio.h>

int main()
{
    
    
	int a = 10;//占用空间大小是 4byte
	int* p = &a;//p是一级指针
	int** pp = &p;//pp就是二级指针
	//理论上可以无限套娃?
	return 0;
}

在这里插入图片描述
这就是二级指针

那对于二级指针,我们是如何来访问的呢?

直接写代码画图讲解

#include <stdio.h>

int main()
{
    
    
	int a = 10;//占用空间大小是 4byte
	int* p = &a;//p是一级指针
	int** pp = &p;//pp就是二级指针 理论上可以无限套娃?
   
    **pp = 20;
	printf("%d\n", a);//这里的a打印出来就是20
	
	return 0;
}

在这里插入图片描述
关于二级指针,我们学到这里就可以停止了,后面的知识我将会在【C语言进阶】的指针博客里做更深入的讲解! 请大家期待!!!


7. 指针数组


指针数组指针还是数组
答案:是数组,是存放指针的数组。

int* parr[5];//整型指针的数组
char* pc[6];//字符指针的数组

指针数组本质是数组,很多知识点和普通数组一样!

所以没有过多的讲解,大家谅解!

关于数组的很多知识点不在这里讲解,请大家观看我的关于数组的博客

全文结束

猜你喜欢

转载自blog.csdn.net/Cbiltps/article/details/120182397
今日推荐