C语言指针(1):基础知识与基本应用

目录

1.前言

2.正文

1.内存,地址和内存地址

2.如何获取地址

3.指针变量

4.const在指针中的应用

4.1修饰指针 

4.2修饰指针变量

4.3既修饰指针也修饰指针变量

5.指针运算

5.1.指针加减整数

​编辑5.2.指针减指针

运行结果如图:​编辑6.void空指针

6.1空指针的定义

6.2空指针的产生

7.野指针

7.1产生原因

7.2避免产生野指针的方法

8.指针数组与数组指针

3.小结


1.前言

哈喽大家好啊,今天来给大家分享C语言中的一个重难点——指针的基础知识与基本应用,希望能对大家有所帮助,请大家多多点赞,收藏支持我哦~

2.正文

1.内存,地址和内存地址

在正式讲解指针之前,需要先把与指针相关的几个概念讲解一下。

地址:在计算机中,地址是用来唯一标识和定位存储器或设备中特定数据位置的数字或字节。可以将其视为存储器的门牌号,用于指示存储器中的特定位置。地址可以是一个简单的整数,也可以是一个二进制数或十六进制数。当需要读取或写入某个特定位置的数据时,计算机会使用地址来确定目标位置,并执行相应的操作。

内存:内存(Memory)是计算机的重要部件,也称内存储器和主存储器。它用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据。内存是外存与CPU进行沟通的桥梁,计算机中所有程序的运行都在内存中进行。

内存地址:在计算机中,内存地址是用于标识内存中每个用于数据存取的基本单位的唯一序号。内存地址指系统RAM中的特定位置,通常以十六进制的数字表示。通过对内存地址的访问和操作,计算机可以实现对数据的读取、写入和执行等操作。

总而言之:每一次创建变量都是在想计算机申请内存,每一块内存都有对应的编号,姑且将其称为(内存的单元编号)它还有一个指向性更强的名字,在计算机中称为(地址),地址是用于访问和操作内存中的数据,所以从某种意义上来讲可以通俗的将地址称为指针。

2.如何获取地址

在c语言中,想要得知所申请的变量的内存地址,就可以使用取地址&操作符。

#include<stdio.h>
int main()
    {
     int a=10;
     printf("%d\n",a);
     printf("%p\n",&a);
}

我们上下依次将a的值和地址都打印了出来。 &a将地址取出来后打印,数据类型%p为地址数据。

3.指针变量

我们既然知道指针(内存地址)本身存在意义而且我们会在程序中使用它,因此拥有了一个新的概念,指针变量,在这里我们会使用解引用操作符*。指针变量是用于专门存储地址值

#include <stdio.h>
int main()
{
 int a = 10;
 int * pa = &a;//取出a的地址并存储到指针变量pa中(*的修饰使得pa变成指针变量)
 
 return 0;
}

这里面需要格外区分俩个概念,指针本身的类型以及指针指向的类型,以下例子中会有所体现:
int p; //这是一个普通的整型变量
int *p; //P是一个返回整型数据的指针
int p[3]; //P 是一个由整型数据组成的数组
int *p[3]; //P 先与[]结合是一个数组,然后再与*结合,说明数组里的元素是指针类型,然后再与int 结合,说明指针所指向的内容的类型是整型的,所以P 是一个由返回整型数据的指针所组成的数组。(这个概念就是以后会讲到的指针数组,这里先简单的提到它)
int (*p)[3]; //P 先与*结合是一个指针,然后再与[]结合,说明指针所指向的内容是一个数组,然后再与int 结合,说明数组里的元素是整型的,所以P 是一个指向由整型数据组成的数组的指针。(这个就是数组指针咯)

4.const在指针中的应用

在我们学习数据类型的时候,会见到const:

#include<stdio.h>

int main(){
	int a = 10;//可以再进行进一步赋值操作
    const int b = 20;//不可以再进行赋值
	return 0;
} 

 简单带大家回顾一下,const本身是常量的意思,当我们创建变量的时前面加上const,则变量就会变成常量。

4.1修饰指针 

#include<stdio.h>

int main(){
	int n = 10;
    int m = 20;
    const int* p = &n;
    *p = 20;//不可行
     p = &m; //可行
	return 0;
} 

以下俩个操作:*p=20不可行,p=&m可行。原因是修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。

4.2修饰指针变量

#include<stdio.h>

int main(){
	int n = 10;
    int m = 20;
    int * const p = &n;
    *p = 20; //可行
    p = &m; //不可行
	return 0;
} 

以下俩个操作操作:*p=20可行,p=&m不可行。原因是修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。

4.3既修饰指针也修饰指针变量

#include<stdio.h>

int main(){
	int n = 10;
    int m = 20;
    int const * const p = &n;
    *p = 20; //不可行
    p = &m; //同样不可行
	return 0;
} 

以下俩个操作:*p=20不可行,p=&m同样不可行。原因上文同理。

5.指针运算

到了这个模块,就已经到了指针的基本应用了,希望大家继续加油!

5.1.指针加减整数

指针加减整数将通过指针逐个打印数组中的元素来体现:

#include<stdio.h>
int main()
{
	int num[10] = { 1,2,3,4,5,6,7,8,9,0 };
	int* p = &num[0];
	int nums = sizeof(num) / sizeof(num[0]);//当然这里用strlen也可行,只不过这里采用这样的写法更能说明问题
	for (int i = 0; i < nums; i++) {
		printf("%d ", *(p + i));//这里就是指针加整数,减法同理
	}
	return 0;
}

 输出结果如下


5.2.指针减指针

为什么不提及指针与指针相加呢,答案则是指针与指针相加没有任何意义,然而相减却有。这里做一个形象的类比方便大家理解:

这里用一个生活上的概念,日期,显而易见的是日期与日期相减得出的是俩个日期之间相差得天数,然而日期相加,你得到了什么,显然什么都得不到,指针同理。

指针相减:含义(相减的绝对值)得出俩个元素之前的元素个数(运算的前提条件:俩个指针指向同一块空间,否则相减同样没有意义)。

#include<stdio.h>
int main()
{
	int num[10] = { 1,2,3,4,5,6,7,8,9,0 };
	int num1[10] = { 1,2,3,4,5,6,7,8,9,0 };
	int* p1 = &num[0];
	int* p2 = &num[9];
	int* p3 = &num1[9];
	printf("%d", p2 - p1);//可行
	printf("%d", p3 - p1);//不可行
	return 0;
}

在基本了解指针相减的含义后,我们来尝试一下指针相减的基本应用,来检验一下自己掌握得怎么样。上文我们利用指针进行了输出整个数组,接下来我们换一个处理方式来输出:

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p1 = &arr[0];
	int* p2 = &arr[9];
	int i = 0;
	int sz = p2 - p1 + 1;
	while (sz) //指针的⼤⼩⽐较
	{
		printf("%d ", *(p1 + i));
		i++;
		sz--;
	}
	return 0;
}

运行结果如图:
6.void空指针

6.1空指针的定义

空指针定义:用于表示一个指针变量不指向任何有效的内存地址。空指针通常被定义为指向NULL或者nullptr。
空指针的初始化:void *a=NULL

#include <stdio.h>
int main()
{
 int a = 10;
 void* pa = &a;
 void* pc = &a;
 
 *pa = 10;
 *pc = 0;
 return 0;
}

void空指针可以接受不同种类的变量,但不能进行指针运算

6.2空指针的产生

空指针的产生:当开辟动态内存(malloc,calloc函数)(此为相关的数据结构)产生失败就会产生空指针

 下面附上一张表,表示内存与相关概念的关系。

栈区      局部变量、函数形参
堆区      动态内存开辟
静态区(数据段)    全局变量、静态变量(这个概念在static专题里面有讲解,可以去查看)
(野)

7.野指针

7.1产生原因

形成原因:未初始化,越界访问,指向空间释放{不要返回局部变量地址})(敲出代码举例子)
附:局部变量(在函数内部声明变量并初始化,当函数被调用的时候会在栈内存中分配内存,局部变量的生命周期始于它们被定义的那一刻,结束于它们所在的作用域,一旦函数或方法执行完毕并返回,它们所占用的栈内存会被自动释放,其内部定义的局部变量将不再可访问

下面附上所有野指针产生的案例:

#include <stdio.h>
int* test()
{
	int n = 100;//该变量在函数结束后便在内存中被销毁
	return &n;
}
int main()
{   
	int* p;//局部变量指针未初始化,默认为随机值
	*p = 20;
	/
	int arr[10] = { 0 };
	int* p = &arr[0];
	int i = 0;
	for (i = 0; i <= 11; i++)
	{
		//当指针指向的范围超出数组arr的范围时,p就是野指针
		*(p++) = i;
	}
	/
	int* p = test();
	printf("%d\n", *p);
	return 0;
}

7.2避免产生野指针的方法

  • 1.如果明确知道该地址,就直接进行赋值
  • 2.如果不知道,就及时置NULL(在C语言中,NULL值为0)(使用前判断指针有效性,如果未初始化但检验,无意义)
  • 3.避免返回局部变量地址。
  • 4.指针使用完后,及时置于NULL。

在我们日常写代码的时候,如何检验当前使用的指针是否为野指针呢,可以这样判断:

 在C语言中,assert是一个宏,它用于在程序中进行调试断言。如果断言的条件为假(即表达式的结果为0),assert会输出一条错误消息并终止程序执行。这通常用于在开发阶段捕获程序中的逻辑错误。assert宏的定义在assert.h头文件中, 所以如果你想在你的程序中使用assert,你需要包含这个头文件。

8.指针数组与数组指针

其实前文在讲指针的概念时已经简单的提了一嘴,但这俩个概念乍听起来确实会有点让人难以着手,接下来先对这指针数组与数组指针进行概念上的讲解:

指针数组:字面意思指针的数组,即一个数组中存放的皆是指针
数组指针:字面意思数组的指针,即该指针得指向时数组。通过数组指针实现单个元素复杂的冒泡排序

在这里就简单先提一下这俩个的概念,至于应用我们下一讲的中篇在进行更详尽的说明吧。

3.小结

今天关于指针的基本知识与应用的讲解到这里就结束了,希望喜欢的朋友多多支持我哦~(敲完了以后脑子快爆炸了)

  • 94
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 28
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 28
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱吃烤鸡翅的酸菜鱼

希望大家对我多多支持喔~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值