【C语言】指针的入门篇,深入理解指针和指针变量

欢迎来sobercq的博客喔,本期系列为【C语言】指针的入门篇,深入理解指针和指针变量

图文讲解指针的知识,带大家理解指针和内存的关系,以及指针的用法,感谢观看,支持的可以给个赞哇。

目录

一、内存和地址

二、变量和地址 

 三、指针变量

四、指针变量的用法(一)

五、指针变量的大小

五、指针变量的用法(二)

六、const关键字

七、指针变量的用法(3)  

八、assert断言

补充:计算机数据存储常见的单位

一、内存和地址

内存,电脑组成的一部分,在这一部分中,我们的cpu在运转的时候从内存中读取数据。那内存的大小又从1G-16G不等等,那它是如何管理的呢?

电脑简易组成: 

【C语言】指针的入门篇,深入理解指针和指针变量_第1张图片 我们把内存划分为一个个的空间大小只有1字节的内存单元,为了找到内存单元,每个内存单元都会有一个内存编号,这样的内存编号在计算机中如果用十六进制表示就如图所示0x00000001……,内存编号我们就把它称作地址。在C语言里,地址就是指针

内存管理:

【C语言】指针的入门篇,深入理解指针和指针变量_第2张图片

二、变量和地址 

了解完内存管理,那么变量的本质其实就是向内存申请空间,空间的大小由数据类型来确定。例如int a = 5;实际上就是向内存申请了一个空间大小为int(4字节),用来存放数据五。那么每个内存单元都会有相对应的地址。

 【C语言】指针的入门篇,深入理解指针和指针变量_第3张图片

那如何取出对应的a地址呢?我们就需要用到取地址操作符&。&这个操作符取出的地址是较小的地址,也可以说是第一个字节的地址。

#include
int main()
{
    int a = 5;
    &a;
    printf("%p\n",&a);
    return 0;
}

 三、指针变量

我们通过取地址操作符(&)拿到的地址是⼀个数值,例如上图的:010FFCB4,这个数值有时候也是需要存储起来,那我们把这样的地址值存放在指针变量中。 

#include
int main()
{
    int a = 5;
    &a;
    int* ptr = &a;

    return 0;
}

如何理解上述代码? 

 prt的类型是int*,a的地址是0x010FFCB4,我们通过取地址操作符把a的地址拿出来放到p的空间里。【C语言】指针的入门篇,深入理解指针和指针变量_第4张图片

四、指针变量的用法(一)

我们将地址保存起来,那如何去使用地址呢?

我们用*来对指针变量解引用,获得p存放的地址指向变量的值。我们把*也叫做解引用操作符,或者是间接引用操作符。

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

此时*p就相当于a,通过修改*p,我们能修改a的值。 

 【C语言】指针的入门篇,深入理解指针和指针变量_第5张图片

五、指针变量的大小

关键字sizeof可以用来计算指针变量的大小,来看下面一段代码:

#include
int main()
{
    int a = 5;
    int *p = &a;
     
    char c = 'w';
    char* pc = &c;
    
    printf("%d ",sizeof(p));
    printf("%d ",sizeof(pc))

}

如果编译器在x86(32位)的环境下,那么此时两个值都为4,即是4个字节,如果是x64(64位),那么二者的值都为8,即八个字节。 

指针变量是为了存放地址,所以其大小跟数据类型无关,32位平台下地址是32个bit位,指针变量大小是4个字节,64位平台下地址是64个bit位,指针变量大小是8个字节,只要指针类型的变量,在相同的平台下,大小都是相同的。

五、指针变量的用法(二)

 1.不同类型的大小

首先是看以下类型,在不同环境下的内存大小

x64:

【C语言】指针的入门篇,深入理解指针和指针变量_第6张图片

x86:

【C语言】指针的入门篇,深入理解指针和指针变量_第7张图片

2.指针解引用 

 我们把num当中的值用11223344都存入进去:

此时num的地址: 

 

此时p的地址:

【C语言】指针的入门篇,深入理解指针和指针变量_第8张图片

请读者思考以下代码

1.p指针变量能否存放下以下地址

2.p指针变量解引用后修改几个字节?

#include
int main()
{
	int num = 0x11223344;

	char* p = #
	*p = 0;

	return 0;
}

 答案:

p能存放下num的地址,都是4字节,但其修改时只修改了一个字节。因此类型的作用便是让p在解引用的时候操作的空间大小,例如char操作一个字节,int操作四个字节。

【C语言】指针的入门篇,深入理解指针和指针变量_第9张图片

3.指针的运算

指针加减整数

#include
int main()
{
	int num = 0x11223344;

	int* pn = #
	char* p = #
	
	printf("&num = %p\n", &num);
	printf("pn地址 = %p\n", pn);
	printf("pn+1地址 = %p\n", pn + 1);
	printf("p地址 = %p\n",p);
	printf("p+1地址 = %p\n", p + 1);

	return 0;
}

 结果如下所示:

【C语言】指针的入门篇,深入理解指针和指针变量_第10张图片

 指针加减整数的距离(空间大小)取决于指针类型,即int走四个字节,char走一个字节。

4.指针访问数组

首先我们来回顾一下数组下标访问

【C语言】指针的入门篇,深入理解指针和指针变量_第11张图片

因为这里我们定义的是整形数组,所以我们要是想用指针访问数组的话,就必须使用整型指针,即int*。

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

	return 0;
}

 p+i是我们走的步长,比如我们从开始走,一次访问越过四个字节,如下图所示

【C语言】指针的入门篇,深入理解指针和指针变量_第12张图片

我们对p+i进行解引用后,得到就是数组存储的值; 

【C语言】指针的入门篇,深入理解指针和指针变量_第13张图片

地址是从低到高变化的。 

六、const关键字

 我们知道变量是可以修改的,如果我们不希望这个值被修改,我们要对它进行限制,那const关键字的用法就是限制变量不被修改

例如以下代码

#include
int main()
{
	const int i = 6;
	printf("%d ", i);
	i = 4;
	return 0;
}

【C语言】指针的入门篇,深入理解指针和指针变量_第14张图片

编译器提示我们,表达式左边必须是可修改的。但是我们现在学习了指针,我们可以绕过const这个门卫。

【C语言】指针的入门篇,深入理解指针和指针变量_第15张图片

但是我们现在也不希望任何情况都能修改i的值呢?

接下来我们看const修饰指针,const修饰指针分两种情况,一种是在*左边,一种是在*右边。

 假设我们把const放在*左边

#include
int main()
{
	int n = 5;
	int m = 50;
	
	const int* p = &n;
	*p = 4;
	p = &m;

	return 0;
}

假设我们把const放在左边,修改*p和p的值,来验证const有什么效果,编译器提示我们

【C语言】指针的入门篇,深入理解指针和指针变量_第16张图片

所以const如果放在*的左边,限制的是指针所指向的内容,保证指针指向的内容不能通过指针来改变。 但是指针变量本⾝的内容可变。

同理验证const如果放在*右边, 

【C语言】指针的入门篇,深入理解指针和指针变量_第17张图片

所以const如果放在*的右边,限制的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针所指向的内容,可以通过指针改变。 

 如果*两边都有const呢?

【C语言】指针的入门篇,深入理解指针和指针变量_第18张图片

那它就会集齐两者,使其变成完全不能修改的p。

七、指针变量的用法(3)  

(1).指针-指针

我们说指针就是地址,所以指针减去指针其实就是地址减去地址,但注意指,针减去指针是有前提的,必须在一段空间内才能进行相减,也就是两个指针必须指向同一段空间。

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

	printf("%d ", ret);
	ret = &arr[0] - &arr[9];

	printf("%d ", ret);
	return 0;
}

我们得到的结果是九和负九

所以指针减去指针,其的绝对值就是元素的个数。

(2).指针的关系运算

 指针的关系运算,就是地址的关系运算,地址相互比较。

我们可以利用关系运算来打印数组

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

	return 0;
}

结果如下: 

(3).野指针 

 概念:野指针说的是指针指向的位置是未知的,不清楚的,(随机的、不正确的、没有明确限制的)

第一种情况是,指针未初始化,造成野指针

第二种情况是,指针越界访问

第三种情况是,指针指向的空间释放

#include

int* test()
{
	int n = 5;
	return &n;
}

int main()
{
	//指针未初始化
	int* p1;//野指针
	*p1 = 20;

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

	//指针指向的空间释放
	//局部变量出作用域会销毁,如果此刻返回那么指针就是野指针
	int* p3 = test();
	
	return 0;
}

为了规避野指针必须注意的几个点:

1.用NULL赋值指针,初始化指针

2.在写前思考清楚,避免越界访问

3.当指针变量不再使⽤时,及时置NULL,指针使用之前检查有效性

NULL 是C语言中定义的⼀个标识符常量,值是0,0也是地址,这个地址是无法使用的,读写该地址会报错。

(4).指针的传址调用

最经典的就是交换函数中的两个值了

void Swap(int* n1,int* n2)
{
	int tmp = *n1;
	*n1 = *n2;
	*n2 = tmp;
}

int main()
{
	int a = 5;
	int b = 10;
	Swap(&a, &b);
	printf("%d %d", a, b);
	return 0;
}

 结果如下:

【C语言】指针的入门篇,深入理解指针和指针变量_第19张图片

(5).指针加减整数

指针加减整数,也向前文讲述的差不多,因为数组在内存中是连续存储的,所以我们通过知道数组首元素地址,就可以知道后面的数组元素值。指针加减整数,它所走的空间大小就是类型的大小。

在这里说明一下p+i等价于&arr[i], *(p+i)==arr[i],

例:字符数组的打印

#include
int main()
{
	char arr[] = "abcdef";

	char* pc = arr;
	while (*pc != '\0')
	{
		printf("%c ", *(pc++));
	}

	return 0;
}

八、assert断言

C语言提供了一个宏,在头文件assert.h 中定义了宏   assert(),用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行。这个宏常常被称为“断言”。

assert(p);

assert() 宏接受⼀个表达式作为参数。如果该表达式为真(返回值非0), assert()不会产生任何作用,程序继续运行。如果该表达式为假(返回值为零), assert() 就会报错。我们常常使用它来检查指针是否非0。 

当p为空指针,则这里会提示以下报错。

【C语言】指针的入门篇,深入理解指针和指针变量_第20张图片

如果我们想在debug的调试下关闭assert断言,我们需要用下列代码

#define NDEBUG 

补充:计算机数据存储常见的单位

计算机常见的存储单位:bit, Byte, KB, MB, GB, TB, PB,……

换算关系:

【C语言】指针的入门篇,深入理解指针和指针变量_第21张图片

感谢各位同伴的支持,本期指针就讲解到这啦,如果你觉得写的不错的话,可以给个赞,若有不足,欢迎各位在评论区讨论。

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