C语言指针进阶(1)(超详细)

news2024/11/20 1:27:03

前言:

 指针其实就是地址,而凡是存储在内存中的值都会有属于自己的地址,指针指向地址,这样我们就能通过指针间接操作变量。我们在指针初阶中介绍了指针的基本概念:如指针大小、野指针问题、指针间的关系运算等,在我们的指针进阶中,将会对指针进行进一步剖析,见识更深的指针!

正文:

  我们将在指针进阶中学习各种各样指针,比如字符指针、数组指针、函数指针等,这些指针种类虽多,但能力都很强大,作为进阶系列文章,涉及知识多多少少有点难度,但我们相信无论多么大的困难都无法阻挡我们的学习之路,因为每个人的潜力都是无限的,相信自己!

知不足而奋进,望远山而前行!!!

大家加油!!!

一、const修饰指针

首先,我们来讲一下const修饰指针的一些作用。

变量其实是可以修改的,如果把一个变量的地址交给一个指针变量,通过指针变量也可以修改这个变量的值,但是如果我们希望一个变量加上一些限制,不能被修改,怎么做呢?这就是const的作用。

#include<stdio.h>

int main()
{
int a=10;
a=20;
printf("%d\n",a);
}

这串代码带印出来就会是20,然后我们如果在int 的前面加上一个const,那a的值便无法被改变

因为const使a具有了常属性,常属性的意思就是不能被修改了。

下面我们来看一下const在指针中的应用。

如果我们非要去该下面a的值,那么可以这样做:

int main()
{
const int a=10;
int*p=&a;
*p=0;
printf("%d",a);

}

如果我们这样写,a的值就可以被修改了。

int main()

{
int a=10;
int const*p=&a;
int * const p=&a; 

return 0;
}

再看这串代码,我们可以发现const可以放在*的前面和后面,我们来看一下这两种写法的区别:

当我们这样写的时候,我们可以发现,a的值可以被改变,但是p无法重新赋值,无法将b的地址赋给p。

我们来这样看一下:

 我们可以看出,p作为一个指针,其可以存放一个地址,然后p可以指向a,然后p本身还有一个地址。

理解这一点之后,我们再看刚才那串代码,const限制的是p本身,p已经指向a地址,这时我我们想再让p指向b的地址是无法实行的,因为const会限制这一操作。但是我们可以改变a的值。

!!!!重点:const在修饰指针变量时,放在*的右边,限制的是指针变量本身,即p本身,指针变量不能在指向其他变量,但可以修改指针变量指向的内容。

再看另外一串代码:

int main()
{

int a=10;
int b=20;
int const *p=&a;
p=&b
return 0;
}

运行这串代码之后我们发现是没有错误的,那就说明此时指针变量本身是可以改变的,但是指向的内容不可以修改。

!!!!重点:const在修饰指针变量时,放在*的左边,限制的是指针变量指向的内容,指针变量可以再次指向其他变量,但不可以修改指针变量指向的内容。

二、指针运算

指针的基本运算有三种,分别是:

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

我们再来想一下,指针的作用是什么,通俗的来讲就是访问内存的。

2.1指针+-整数

先来看一下指针+1

int a=10;

int*p=&a;

p+1 -->表面上是加1,但其实是跳过4个字节,

其实就是跳过1*sizeof(type)个字节。

不同的type跳过的字节不同。

如果是char类型,那便是跳过一个字节

接下来我们来考虑一下他有什么作用呢:

 我们都知道,数组在内存中的存储是连续的,只要知道1第一个元素的地址,顺藤摸瓜就能找到后面的所有元素。

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[0];
for(int i=0;i<sz;i++)
{
  printf("%d ",*p);
  p+=1;

}
return 0;
}

 这样我们便可以通过指针的方式来打印这个数组,这便是指针的一个作用。

就像我们上面说的,每次p+1跳过4个字节,来到下一个数组元素,然后打印。

2.2指针-指针

我们再来看一下指针-指针的应用:

int main()
{

int arr[10]={1,2,3,4,5,6,7,8,9,10};
printf("%zd\n",&arr[9]-&arr[0]);

return 0;

}

这样我们便可以得到一个9,这便是指针-指针的应用,

 
int my_strlen(char*str)
{
int count=0;
while(*str!='\0')
{
count++;
str++;
}
}

int main()
{
char arr[]="abcdef";
//[a b c d e d \0]
int len=my_strlen(arr);
printf("%d\n",len);
return 0;
}

这也是指针-指针的一种应用,大家可以看一下。 

2.3指针的关系运算

 指针的关系运算其实就是指针和指针比较大小,即地址和地址比较大小。

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

while(p<sz+arr)
{
  printf("%d ",p);
p++;
}
}

这里的iwhile语句中,便是指针之间的关系运算。

三、assert断言

对于断言,相信大家都不陌生,大多数编程语言也都有断言这一特性。简单地讲,断言就是对某种假设条件进行检查。在 C 语言中,断言被定义为宏的形式(assert(expression)),而不是函数,其原型定义在<assert.h>文件中。其中,assert 将通过检查表达式 expression 的值来决定是否需要终止执行程序。也就是说,如果表达式 expression 的值为假(即为 0),那么它将首先向标准错误流 stderr 打印一条出错信息,然后再通过调用 abort 函数终止程序运行;否则,assert 无任何作用。

默认情况下,assert 宏只有在 Debug 版本(内部调试版本)中才能够起作用,而在 Release 版本(发行版本)中将被忽略。当然,也可以通过定义宏或设置编译器参数等形式来在任何时候启用或者禁用断言检查(不建议这么做)。同样,在程序投入运行后,最终用户在遇到问题时也可以重新起用断言。这样可以快速发现并定位软件问题,同时对系统错误进行自动报警。对于在系统中隐藏很深,用其他手段极难发现的问题也可以通过断言进行定位,从而缩短软件问题定位时间,提高系统的可测性。

assert(p!=NULL);

 例如这行代码,便可以验证p是否等于NULL,如果不等于NULL,程序继续进行,如果为假,返回值则为0,assert()就会报错

assert()的使用对程序员十分友好,使用它有以下几个好处,

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

看一下下面两张图片:

 这样我们便很容易的可以搞清楚assert()断言的作用。

assert    出现报错的时候,直接就会报错,指明什么文件,哪一行

并且关闭assert之后,在Release版本中,不会影响运行效率。

#include<stdio.h>

int my_strlen(char* str)
{
	int count = 0;
	assert(str != NULL);
	while (*str != '\0') {
		count++;
		str++;
	}
}

int main()
{
	char arr[] = "abcdef";
	int len = my_strlen(arr);
	printf("%d", len);
	return 0;
}

这样写可以防止传参是=时传的是空指针。

四、指针的使用和传址调用

写一个函数,交换两个函数的内容

#include<stdio.h>

void swap(int x, int y) {
	int tmp = x;
	x = y;
	y = tmp;
}

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

首先我们看一下上面的代码,看似是可以的,我们运行一下:

我们发现a与b的值并未改变,这是为什么呢, 那是因为我们在调用swap函数时,值虽然能传过去,但是只有x与y的值发生互换,无法改变a与b的值,x与a的地址不同,y与b的值也不同,所以他们不会改变a与b的值,那该怎样去写呢?

上面我们其实就是在传值调用,即传过去a与b的值,下面我们将通过传址调用来改变a与b的值。

#include<stdio.h>

void swap(int *pa, int *pb) {
	int tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}

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

这样便可以实现a与b值的交换。

总结:

本次我们对指针进阶的一部分内容进行了阐述,如const修饰指针,assert断言部分都是比较重要的,希望对大家有所帮助,对哪里有疑惑的话可以在评论区留下你的疑惑。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1418803.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Hugging Face创始人分享:企业如何在ChatGPT浪潮下实现战略布局

Hugging Face创始人兼首席执行官 Clem Delangue在IBM一年一度的 THINK大会中研讨了当前人工智能发展趋势&#xff0c;特别是ChatGPT模型以及其对行业的影响。他的演讲还涉及到一个关键的议题&#xff0c;在ChatGPT这样的通用模型出现后&#xff0c;企业如何在人工智能领域找到自…

自定义vue通用左侧菜单组件(未完善版本)

使用到的技术: vue3、pinia、view-ui-plus 实现的功能: 传入一个菜单数组数据,自动生成一个左侧菜单栏。菜单栏可以添加、删除、展开、重命名,拖动插入位置等。 效果预览: 代码: c-menu-wrap.vue <template><div class="main-container">&l…

【嵌入式学习】C++QT-Day3-C++基础

笔记 见我的博客&#xff1a;https://lingjun.life/wiki/EmbeddedNote/19Cpp 作业 设计一个Per类&#xff0c;类中包含私有成员:姓名、年龄、指针成员身高、体重&#xff0c;再设计一个Stu类&#xff0c;类中包含私有成员:成绩、Per类对象p1&#xff0c;设计这两个类的构造函…

当包容结构体遇见灵活的内存管理

&#x1f308;个人主页&#xff1a;小田爱学编程 &#x1f525; 系列专栏&#xff1a;c语言从基础到进阶 &#x1f3c6;&#x1f3c6;关注博主&#xff0c;随时获取更多关于c语言的优质内容&#xff01;&#x1f3c6;&#x1f3c6; &#x1f600;欢迎来到小田代码世界~ &#x…

课时7:shell基础_shell简介

1.3.1 shell简介 学习目标 这一节&#xff0c;我们从 运维、shell语言、小结 三个方面来学习。 运维 简介 运维是什么&#xff1f;所谓的运维&#xff0c;其实就是公司的内部项目当中的一个技术岗位而已&#xff0c;它主要做的是项目的维护性工作。它所涉及的内容范围非常…

方法、数组

方法 是语句的集合&#xff0c;在一起执行一个功能 它是解决一类问题的步骤的有序集合 包含于类或对象中 在程序中创建&#xff0c;在其他地方被引用 设计方法的原则&#xff1a;方法的本意是功能块&#xff0c;就是实现某一个功能的语句块的集合。设计时&#xff0c;最好保持…

基于go mod模式创建项目最佳实践

希望能带给你成功的喜悦&#xff01; 除go get、vendor这两种方式外&#xff0c;Go版本在1.11之后推出了go module模式来管理依赖&#xff0c;使用go mod时下载的依赖文件在$GOPATH/pkg/mod/下。本文以两种办法介绍如何创建go mod项目。 目录 goland开启玩法 本地手动创建项目…

###C语言程序设计-----C语言学习(6)#

前言&#xff1a;感谢老铁的浏览&#xff0c;希望老铁可以一键三连加个关注&#xff0c;您的支持和鼓励是我前进的动力&#xff0c;后续会分享更多学习编程的内容。 一. 主干知识的学习 1. while语句 除了for语句以外&#xff0c;while语句也用于实现循环&#xff0c;而且它…

shell脚本自动化备份网络设备配置教程

由于局域网内存在多台网络设备&#xff0c;如防火墙、路由器、交换机等&#xff0c;数量众多&#xff0c;且品牌不同&#xff0c;手工备份配置需要相当长的时间&#xff0c;现需要实现自动化导出备份配置。 经查询&#xff0c;该局域网内存在华为及阿尔卡特两种品牌&#xff0…

Android Studio 下载安装配置使用入门【2024年最新】

前言&#xff1a; Android Studio 是谷歌官方提供的主要集成开发环境&#xff08;IDE&#xff09;&#xff0c;专为 Android 平台应用开发而设计。它基于 JetBrains 的 IntelliJ IDEA 软件&#xff0c;并在此基础上增加了大量针对 Android 开发的定制功能。Android Studio 通过…

CMake 完整入门教程(一)

1 前言 每一次学习新东西都是很有乐趣的&#xff0c;虽然刚开始会花费时间用来学习&#xff0c;但是实践证明&#xff0c;虽然学习新东西可能会花费一些时间&#xff0c;但是它们带来的好处会远远超过这些花费的时间。学习新东西是值得的&#xff0c;也是很有乐趣的。 网络上…

C语言二维数组的使用案列,来自C语言程序设计第五版本

感谢关注我的123个小伙伴&#xff0c;我会给大家带来更多的知识&#xff0c;但是C语言的学习&#xff0c;我准备今天学了这个月暂时停止更新了&#xff0c;下个月一号砸门又见面&#xff0c;休息两天弄其它的事情&#xff0c;也好几天没有看论文了。 二维数组的创建比较简单&am…

基于Python 爬虫的房地产数据可视化分析与实现

摘要&#xff1a; 过去&#xff0c;不管是翻阅书籍&#xff0c;还是通过手机&#xff0c;电脑等从互联网上手动点击搜索信息&#xff0c;视野受限&#xff0c;信息面太过于狭窄&#xff0c;且数据量大而杂乱&#xff0c;爆炸式信息的更新速度是快速且不定时的。要想手动获取到海…

算法沉淀——前缀和(leetcode真题剖析)

算法沉淀——前缀和 01.一维前缀和02.二维前缀和03.寻找数组的中心下标04.除自身以外数组的乘积05.和为 K 的子数组06.和可被 K 整除的子数组07.连续数组08.矩阵区域和 前缀和算法是一种用于高效计算数组或序列中某个范围内元素之和的技巧。它通过预先计算数组的前缀和&#xf…

Spring5深入浅出篇:Spring中的FactoryBean对象

Spring5深入浅出篇:Spring中的FactoryBean对象 Spring工厂创建简单对象 之前我们通过Spring配置文件创建的都是简单对象,那么什么是简单对象呢?简单对象就是通过new 构造方法 创建的对象,比如:UserService,User,Person等.那么我们就需要知道什么是复杂对象 什么是复杂对象 复…

防御保护笔记02

防火墙 防火墙的主要职责在于&#xff1a;控制和防护 ---- 安全策略 --- 防火墙可以根据安全策略来抓取流量 防火墙分类 按物理特性划分 软件防火墙 硬件防火墙 按性能划分 百兆级防火墙 吞吐量&#xff1a;指对网络、设备、端口、虚电路或其他设施&#xff0c;单位时间内成…

计算机网络——网络层(2)

计算机网络——网络层&#xff08;2&#xff09; 小程一言专栏链接: [link](http://t.csdnimg.cn/ZUTXU) 网络层——控制平面概述路由选择转发表路由协议路由信息的交换小结 路由选择算法常见的路由选择算法距离矢量路由算法工作原理优缺点分析 链路状态路由算法基本工作原理优…

【计网·湖科大·思科】实验五 IPV4地址-分类地址和构建超网

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的很重要&…

ZYNQ:CAN外设应用存在的问题

流程 为了用ZYNQ实现CAN总线功能&#xff0c;分为多个阶段&#xff1a; 1学习小梅哥视频&#xff0c;了解zynq简单工作搭建的流程&#xff0c;比如点亮LED。 GPIO 功能 按照小梅哥的视频搭建ZYNQ-PS应用系统时&#xff0c;vitis没有出现ps7_gpio_0这个硬件。这导致vitis软件…

深入了解DRAM和SDRAM:内存带宽的计算与封装形式的奥秘

SSD SDRAM DDR SDRAM简介 动态随机存取存储器DRAM&#xff08;Dynamic Random Access Memory&#xff0c;DRAM&#xff09;是一种半导体存储器。 其主要的作用原理是利用电荷内存储电荷的数量来代表一个二进制比特&#xff08;bit&#xff09;是1还是0。 由于在现实中品体管…