C语言-指针讲解(2)

这篇具有很好参考价值的文章主要介绍了C语言-指针讲解(2)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

C语言-指针讲解(2),C语言笔记,c语言,开发语言


通过前面的介绍
C语言指针详解(一)超详细~
相信大家对指针的基本概念及用法有了初步的了解。

我们来回顾一下上次那个博客讲了什么吧~
1.指针就是变量,用于存放地址的,地址唯一标识的一块内存空间。
2.指针的大小分别是4/8个字节(32位平台/64位平台)
3.指针是有类型的,指针的类型决定了指针±整数的步长,以及指针解引用的权限有多大。
4.指针的运算。
那么这次博主给大家继续深入理解指针的其他高级用法吧
这是本次我们要讲解的知识点:
C语言-指针讲解(2),C语言笔记,c语言,开发语言

1.野指针

1.1 什么是野指针

野指针,顾名思义,就是指针指向的位置是不可知的。就好比如没有主人的流浪狗一样。

1.2 造成野指针的原因有哪些呢

1.指针未被初始化
2.指针越界访问
3.指针指向的空间释放
前面两个造成野指针原因都比较容易理解,所以我们一会重点讲一下第三个

1.2.1造成野指针具体代码实例:

1.指针未被初始化

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

2.指针越界访问

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

3.指针指向的空间释放

int* test()//由于返回的是n的地址,因此函数返回的是int*类型
{
	int n = 100;//在test函数中创建了局部变量n,
	return &n;//当我们在中间的函数做了一些事情后,我们就返回n,把n的地址返回到指针变量p来接收
	
}
int main()
{
	int* p = test();//由于返回的是地址,所以我们拿指针变量p来接收
	printf("%d\n", *p);
	return 0;
}

从上面这个代码中,当test()函数中变量n申请了一块空间,而出这个test()函数的时候,这个n的地址就会被销毁,并还给操作系统。然后回到main函数,但是指针变量p仍然记住n的地址,如果到时通过对p进行解引用操作,来改变它所指向对象的值。这就属于是非法访问了,足矣说明p是个野指针。

1.3 如何避免野指针呢?

1.指针初始化
2.小心指针越界
3.指针变量不再使用时,及时置NULL,指针使用之前检查有效性。

1.3.1如何对指针进行初始化?

如果不知道指针指向哪里,可以先给指针复制NULL,NULL是C语言中定义的一个标识符常量,值是0,但是这个地址是无法直接使用的。

初始化如下:

include <stdio.h>
int main()
{
	int num = 10;
	int*p1 = &num;
	int*p2 = NULL;
	return 0;
}

1.3.2如何才能小心指针越界?

通常来说,一个程序向内存申请了哪些空间,通过指针也只能访问哪些空间,不能超出范围访问,超出了就是越界访问。

1.3.3 指针变量不再使用时,如何及时置NULL,在指针使用之前检查有效性?

  • 当指针变量指向一块区域的时候,我们可以通过指针访问该区域,如果我们后期不再使用这个指针访问空间的时候,我们可以先把该指针置为NULL。
  • 然后到下次使用该指针变量之前,我们要先判断它是否为NULL,如果是就不能指直接使用,不是的话我们才能使用。

2.assert断言

2.1 什么是assert断言

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

2.2 如何使用assert断言呢?

C语言-指针讲解(2),C语言笔记,c语言,开发语言

assert(p != NULL);

当上面代码在程序运行到这一行程序时,验证变量p是否等于NULL。如果确实不等于NULL,程序会继续运行,否则就会终止运行,并且给出报错信息提示。

2.3 使用assert有什么好处呢?

  • 它不仅能自动标识文件和出问题的行号,还有一种无需更改代码就能开启或关闭assert()的机制。
  • 如果已经确认程序没有问题,不需要再做断言,就在#include<assert.h>语句的前面,定义一个NDEBUG。
    具体代码如下:
#define NDEBUG
#include <assert.h>

需要注意的是,assert()也是有缺点的。由于引入了额外的检查,会增加程序的运行时间。


3.指针的使用和传址调用

3.1 学习指针的目的是什么?

学习指针的目的是使用指针解决问题,那什么问题,非指针不可呢?

比方说,我们要写一个函数,来交换两个整数变量的值。

经过一番思考后,我们可能会写出这个代码出来~

#include <stdio.h>
void Swap1(int x, int y)
{
	int tmp = x;
	x = y;
	y = tmp;
}

int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	printf("交换前:a=%d b=%d\n", a, b);
	Swap1(a, b);
	printf("交换后:a=%d b=%d\n", a, b);
	return 0;
}

当我们运行此代码,结果如下:
C语言-指针讲解(2),C语言笔记,c语言,开发语言

我们会发现这两个变量没有产生交换的效果,这是为什么呢?我们不妨调试一下~

C语言-指针讲解(2),C语言笔记,c语言,开发语言

从图中,我们可以看出在main函数中,我们调用了swap函数。并把变量a和b作为传过去,形参用x和y来接收。但我们发现,这里的变量a和变量x的地址不相同,变量b和变量y的地址也不相同。这也说明形参x和y是一个独立的空间。当swap函数调用结束后返回main函数,a和b的变量依然无法交换,swap在使用的时候,本质上就是把变量本身传递给函数,这也就是我们常说的传值调用

因此我们得出以下结论:

实参传递给行参的时候,形参会单独创建一份临时空间来接收实参,对形参的修改不影响实参,所以swap是失败的。

那我们怎么解决呢?
C语言-指针讲解(2),C语言笔记,c语言,开发语言
我们得借助函数间传址调用来解决。

3.2 什么是传址调用?

传址调用,顾名思义就是将main函数中的变量地址传到所调用的函数中,然后在被调函数中,通过地址间的操作即可实现两个数的交换。

3.3 怎么进行传址调用?

那回到刚刚那种情景,我们只需把变量a和b的地址分别传给swap函数,然后swap函数内部中,通过地址间的操作即可实现main函数中a和b两个数的交换。

代码实现如下:

#include <stdio.h>
void Swap2(int*px, int*py)
{
	int tmp = 0;
	tmp = *px;
	*px = *py;
	*py = tmp;
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	printf("交换前:a=%d b=%d\n", a, b);
	Swap1(&a, &b);
	printf("交换后:a=%d b=%d\n", a, b);
	return 0;
}

运行结果如下:
C语言-指针讲解(2),C语言笔记,c语言,开发语言

4.数组名的理解

在上一次博客C语言指针详解(一)超详细~
我们曾写过两行代码:

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

这里我们是使用&arr[0]的方式拿到了数组第一个元素的地址。但是数组名本来就是地址,不信我们可以拿VS编译器来测试一下。
C语言-指针讲解(2),C语言笔记,c语言,开发语言

从上图,我们发现数组名和数组首元素的地址打印出的结果是一模一样的。

因此我们可以得出这个结论:数组名是数组首元素(第一个元素)的地址
但是呢,有同学会有疑问,如果数组名是数组首元素的地址,那这个代码该怎么理解?

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%d\n", sizeof(arr));
	return 0;
}

从下图,我们发现输出结果是40。
C语言-指针讲解(2),C语言笔记,c语言,开发语言
为什么不是4/8呢?如果数组是首元素的地址,按理说输出的应该是4/8才对。

其实数组名就是数组首元素(第一个元素)的地址是对的,但是有两个例外:

  • sizeof(数组名),sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小。
  • &数组名表示整个数组,取出的是整个数组的地址,(整个数组的地址和数组的首元素的地址是有区别的)。

除此之外,其他地方使用数组名,数组名都表示首元素的地址。


这时,或许还会同学不理解,他们也许会再测试一下这个代码:

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("&arr[0] = %p\n", &arr[0]);
	printf("arr = %p\n", arr);
	printf("&arr = %p\n", &arr);
	return 0;
}

发现这三个打印的结果都一样,会再次出现疑惑?
C语言-指针讲解(2),C语言笔记,c语言,开发语言
那接下来我来介绍他们之间的区别。

4.1 arr和&arr的区别

我们直接上代码分析~

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("&arr[0] = %p\n", &arr[0]);
	printf("&arr[0]+1 = %p\n", &arr[0]+1);
	printf("arr = %p\n", arr);
	printf("arr+1 = %p\n", arr+1);
	printf("&arr = %p\n", &arr);
	printf("&arr+1 = %p\n", &arr+1);
	return 0;
}

运行结果:
C语言-指针讲解(2),C语言笔记,c语言,开发语言

  • 从上图,我们发现&arr[0]和&arr[0]+1相差4个字节,arr和arr+1相差4个字节,是因为&arr[0]和arr都是首字符地址,+1就是跳过一个元素。
  • 但是&arr和&arr+1是相差40个字节,这就是因为&arr是数组的地址,因此这里+1就是跳过整个数组。

相信到这里大家应该搞清楚数组名的意义了吧。
除了有两个例外,其他的数组名都是数组首元素的地址。


5.二级指针

5.1 什么是二级指针

二级指针指向的是一级指针的指针,也就是说一个指针指向的是另外的指针,同时二级指针也是存放一级指针的地址,则称之为二级指针。

5.2 指针变量的地址存放在哪里呢?

这个我们可以先画个图来分析一下~

C语言-指针讲解(2),C语言笔记,c语言,开发语言

比方说,我们从上图可以得知,我们可以得知指针变量pa存放的是a的地址,而指针变量ppa存放的是指针变量pa的地址,你们由这个规律,我们就能推导出指针变量pppa存放的是指针变量ppa的地址。

另外,这里有个小细节需要大家注意的是,由于pa中的p左边的*代表pa是个指针变量,而前面的int代表pa是个int类型的指针变量。那同理,ppa中的p左边的 *代表ppa是个指针变量,而旁边还有一个 *。代表的是ppa是一个int *类型的指针变量。

5.3 对于二级指针的运算是怎么样的呢?

我们先来看下面代码,然后再逐一进行分析。

#include <stdio.h>
int main() {

	int a = 10;
	int* p = &a;//p是一级指针

	int** pp = &p;//pp是二级指针
	printf("%d\n", **pp);


	return 0;
}

从上图可以得知,首先,** pp先通过*pp找到p,然后我们再对p进行解引用操作: *p,那找到的就是a,那么最终输出的结果就是10。

VS运行结果如下所示:
C语言-指针讲解(2),C语言笔记,c语言,开发语言



6.指针数组

6.1 什么是指针数组呢?

俗话说,存放整型的数组是整形数组。
存放字符的数组是字符数组。
C语言-指针讲解(2),C语言笔记,c语言,开发语言
那么同理,存放指针的数组则是指针数组。

并且指针数组的每个元素都是用来存放地址(指针)的。
如下图所示:
C语言-指针讲解(2),C语言笔记,c语言,开发语言

我们会发现指针数组每个元素都是存放地址的,又可以指向一块区域。



7.指针数组模拟二维数组

#include <stdio.h>
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数组中
	int* parr[3] = {arr1, arr2, arr3};
	int i = 0;
	int j = 0;
	for(i=0; i<3; i++)
	{
		for(j=0; j<5; j++)
		{
			printf("%d ", parr[i][j]);
			//parr[i][j]==*(*(parr+i)+j))
		}
		printf("\n");
	}
	return 0;
}

C语言-指针讲解(2),C语言笔记,c语言,开发语言

从图中,我们可以看出parr[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型一维数组,parr[i][j]就是整型一维数组中的元素。
需要注意的是,上面的代码模拟出的二维数组的效果,实际上并未完全是二维数组,以为每一行并非是连续的。


** 好啦!今天博主就分享到这里**
C语言-指针讲解(2),C语言笔记,c语言,开发语言
** 如果觉得博主讲得不错的话。欢迎大家一键三连支持一下**文章来源地址https://www.toymoban.com/news/detail-736223.html

到了这里,关于C语言-指针讲解(2)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C语言:指针(超深度讲解)

    目录 指针: 学习目标: 指针可以理解为: 字符指针:         定义:字符指针 char*。 字符指针的使用: 练习: 指针数组:         概念:指针数组是一个存放指针的数组。 实现模拟二维数组:  数组指针:         概念:能够指向数组的指针。(可以理解为先与指针结

    2024年02月11日
    浏览(39)
  • C语言指针,深度长文全面讲解

    指针对于C来说太重要。然而,想要全面理解指针,除了要对C语言有熟练的掌握外,还要有计算机硬件以及操作系统等方方面面的基本知识。所以本文尽可能的通过一篇文章完全讲解指针。 为什么需要指针? 指针解决了一些编程中基本的问题。 ✅指针的使用使得不同区域的

    2024年02月07日
    浏览(38)
  • C语言之指针篇【超详细讲解,带你层层深入理解指针】

    目录 一、关于指针 二、指针类型 1、整型指针的访问权限说明: 2、字符指针的访问权限说明: 3、指针的类型决定向前或向后一步走了多大距离 三、野指针相关知识 1、野指针的成因 ①指针未初始化 ②指针的越界访问 ③指针所指向的空间释放了 2、如何规避野指针 ①指针

    2024年02月02日
    浏览(39)
  • C语言:指针【进阶】习题练习及分析讲解

    前言: 前面我们刚刚学完了C语言:指针详解【进阶】的知识,这部分的知识还是要重在理解加实践,今天我这里就分享一些有关C语言指针方面的练习供大家更深入的理解指针的知识。 我们初期的指针学习大部分都是与数组的知识绑定在一起的,所以今天的练习也是大多与数

    2024年02月02日
    浏览(50)
  • C语言 - 最简单,最易懂的指针、引用讲解

    输出结果如下: 先看这一行 都知道 是取址符是吧,好,h 是取h结构体的地址,结果没问题,参照上图。 接着,hp,hp是一个指针,指向了h所在的地址(hp = h),注意:hp是取hp变量的地址,而不是h的地址,所以打印出来的是 6290920。(printf %d是打印数字,这里输出的是10进制

    2024年02月02日
    浏览(45)
  • 【C语言】指针超详细讲解(超级详细!!!快来看快来看!!!)

    指针??是?? 指针是内存中一个最小单元的编号,也就是地址 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量 所以说:指针就是地址,人们口中的指针变量也是指针。 指针变量??是?? 我们可以通过 (取地址操作符)取出变量的内存与实地址,

    2024年02月16日
    浏览(37)
  • C语言/c++指针详细讲解【超详细】【由浅入深】

    指针,是内存单元的编号。 内存条分好多好多小单元,一个小单元有 8 位,可以存放 8 个 0 或 1;也就是说,内存的编号不是以位算的,而是以字节算的,不是一个 0 或 1 是一个编号,而是 8 个 0 或 1 合在一起是一个编号。这个编号,就是地址。 内存条就分为好多小格子,一

    2024年01月21日
    浏览(47)
  • 初始C语言(7)——详细讲解有关初阶指针的内容

     第一章 “C“浒传——初识C语言(1)(更适合初学者体质哦!)  第二章 初始C语言(2)——详细认识分支语句和循环语句以及他们的易错点   第三章 初阶C语言(3)——特别详细地介绍函数   第四章 初始C语言(4)——详细地讲解数组的内容以及易错点   第五章

    2024年02月11日
    浏览(37)
  • 【C语言】指针的进阶(二)—— 回调函数的讲解以及qsort函数的使用方式

    目录 1、函数指针数组 1.1、函数指针数组是什么?  1.2、函数指针数组的用途:转移表 2、扩展:指向函数指针的数组的指针 3、回调函数 3.1、回调函数介绍  3.2、回调函数的案例:qsort函数 3.2.1、回顾冒泡排序  3.2.1、什么是qsort函数? 函数指针数组 是什么?首先主语是 数

    2024年02月07日
    浏览(44)
  • C语言K&R圣经笔记 5.6指针数组;指针的指针

    因为指针本身也是变量,所以它们也能像其他变量一样保存在数组里面。我们写个程序来说明,该程序将一些文本行按照字母顺序排列,算是 UNIX 程序 sort 的精简版本。 在第三章中,我们介绍了对一个整数数组进行排序的 Shell 排序函数,而在第四章中,我们用快速排序对其

    2024年02月03日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包