首页 > 其他分享 >计算机初级选手的成长历程——指针(5)

计算机初级选手的成长历程——指针(5)

时间:2023-12-12 13:01:16浏览次数:27  
标签:const 变量 局部变量 初级 选手 全局变量 我们 指针

进阶指针

计算机初级选手的成长历程——指针(5)_void*指针

导言

大家好,很高兴又和大家见面了!!!

在上一个章节中,咱们深入探讨了一下指针与数组之间的联系,在探讨的过程中我们发现对于指针数组与二级指针来说,它们实质上就是一维数组和一级指针,它们之间的关系也是遵从指针与数组之间关系。为了更好的学习指针,在今天的内容中,我们将介绍指针的一些补充知识点。下面就开始咱们今天的内容吧!

十一、void*指针

在函数中我们有介绍过void表示的是无类型,函数中的void表示无返回类型。在指针中同样也有void*这种类型的指针,这类指针我们可以称它为无具体类型的指针,也可以称为泛型指针。

在前面指针类型的意义中我们提到过,指针的类型决定了指针对数据进行一次修改时的可操作空间大小;

  • 对于char*的指针来说,它修改一次数据可以操作的空间为1个字节。我们让char*的指针接收char类型的对象的地址是比较合适的,这样我们在修改内容时可以对char类型的地址中存放的内容进行一个字节一个字节的修改;
  • 对于int*的指针来说,它修改一次数据可以操作的空间为4个字节。我们让int*的指针接收int类型的对象的地址是比较合适的,这样我们在修改内容时可以对int类型的地址中存放的内容进行四个字节四个字节的修改;
  • 对于void*类型的指针来说,它可以接收所有类型的对象的地址,并不能对其进行解引用以及进行指针的运算;

下面我们来通过实例验证一下:

计算机初级选手的成长历程——指针(5)_const关键字_02

从报错中我们可以看到,void*类型的指针在接收不管是char类型还是int类型的对象的地址时都是没有问题的,但是我们在对其进行解引用、加减整数、以及进行指针-指针时都有出现报错,报错的内容总结下来就是——对象具有void类型,并且void*的大小是未知的;

那对于void*类型的指针来说他能做什么呢?下面我们来测试一下:

计算机初级选手的成长历程——指针(5)_const关键字_03

从测试结果中我们可以看到,我们可以正常的对void*指针进行关系运算、打印存储的地址以及赋值的操作

也就是说对于void*指针来说,它是无法实现指针的+-整数运算、解引用以及指针-指针运算这些运算的,但是我们可以对指针变量进行基本的操作。

对于void*指针的使用我们会在后面的内容进行介绍,大家不要心急,耐心往下继续阅读;

十二、关键字const

对于const这个关键字,它的中文翻译为常数、恒量;恒定的,不变的;它在C语言中的作用正如它的意思一样,将操作对象变成不变的,这个关键字我们前面几乎没有遇到过,它具体有什么作用呢?下面我们就来一起探讨一下;

12.1 变量

12.1.1 变量的分类

对于C语言来说,变量可分为全局变量和局部变量,下面我们来看一下什么是局部变量,什么是全局变量:

//变量的分类
int a = 10;//全局变量
test()
{
	int d = a;//局部变量
}
int main()
{
	int b = 20;//局部变量
	if (a < b)
	{
		int c = a + b;//局部变量
	}
	return 0;
}

在这个例子中,我们分别定义了四个变量,根据代码的注释我们可以看到变量a为全局变量,变量b和变量c为main函数内部的局部变量,变量d为main函数外部test函数内部的局部变量;

在C语言中,我们将花括号{}称为代码块,因为我们所有的代码都是需要再{}内部编写的。对于变量来说,在{}外面定义的变量称为全局变量,在{}内部定义的变量称为局部变量;

12.1.2 变量的生命周期和作用域

变量的生命周期我们可以简单的理解为就是变量的创建与销毁的周期

变量的作用域我们可以简单的理解为就是变量可以使用的区域

对于全局变量与局部变量来说,它们的生命周期与作用域是有区别的:

  • 全局变量的生命周期是跟随整个工程,全局变量在创建后,除非关闭这个工程,否则它会一直存在,它的作用域也是作用于整个工程的
  • 局部变量的生命周期是跟随创建变量的{},在{}内部创建好局部变量后,一旦出了{},局部变量就被销毁了,它的作用域也是对应的{}

下面我们通过代码来对全局变量以及局部变量的生命周期和作用域进行说明:

计算机初级选手的成长历程——指针(5)_C语言_04

在这个代码以及测试结果中,我们可以得到以下信息:

  1. 对于全局变量a来说,不管是在test函数内部还是在main函数的内部以及if语句的代码块内部都是可以正常使用的,所以此时我们可以说全局变量a此时的使用范围是从它创建后的任何地方都可以进行使用
  2. 对于局部变量c来说,它能在if语句的代码块内部使用,也可以在if语句外,main函数的代码块内进行使用,所以此时我们可以说局部变量c的使用范围是在main函数的代码块内部
  3. 对于局部变量b和局部变量d来说,它们都是可以在自己对应的代码块内部进行使用的,所以此时我们可以说局部变量b和局部变量d的使用范围是在它们对应的代码块内部

下面我们继续看下面的代码:

计算机初级选手的成长历程——指针(5)_void*指针_05

可以看到,此时代码出现了6处报错,报错内容都是未声明的标识符,也就是说在报错的这些地方是不存在这些变量的。

现在有朋友可能就有疑问了,局部变量出现这种情况我都能理解,此时它是因为出了自己的作用域就被销毁了嘛,但是你都说了全局变量是跟随整个工程的,你这现在不是自己打自己的脸吗?

别着急,下面我们下面我们继续介绍一个新的关键字——extern——引入外部符号(可以引用其它源文件内部定义的全局变量),现在我们再来看一下下面的代码:

计算机初级选手的成长历程——指针(5)_assert断言_06

从这次的结果中我们可以看到此时通过关键字extern对全局变量a进行声明后现在再到test函数中使用变量a是没有任何问题的。但是我们通过对b、c、d进行extern的声明后,此时报错了,错误内容为无法解析的外部符号,这也就是说,extern只能全局变量使用

因此这个例子再一次证明了局部变量的生命周期与作用域都是自己对应的代码块内部

而对全局变量来说,我们可以通过关键字extern对变量进行声明,所以全局变量的生命周期和作用域是在整个工程内部的

12.1.3 变量的优先级

现在我们设想一下,全局变量和局部变量可不可以同名呢?如果可以同名,那应该是全局变量优先使用还是局部变量优先使用呢?下面针对这两个问题,我们来通过代码测试一下:

计算机初级选手的成长历程——指针(5)_void*指针_07

从测试结果中我们可以看到,在局部变量a的代码块内部打印的是局部变量a的值,而当局部变量被销毁后打印的则是全局变量a的值,也就是说当局部变量与全局变量的变量名相同时,程序优先执行的是局部变量。因此,我们在今后写代码时,尽量避免局部变量与全局变量同名的情况

12.2 const修饰局部变量

const这个关键字,它是可以对变量进行修饰的,当他修饰变量时,会给变量赋予一个常属性,使变量不可被改变,如下所示:

计算机初级选手的成长历程——指针(5)_void*指针_08

可以看到此时程序的报错内容为表达式必须是可修改的左值,也就是说,此时被const修饰后的局部变量b是不可像局部变量a一样被修改的。但是为什么我们说它const修饰的局部变量只是拥有了常属性呢?这是因为我们此时可以通过指针对其进行修改,如下所示:

计算机初级选手的成长历程——指针(5)_const关键字_09

此时我们可以看到程序是正常运行的,而且b的值此时也被修改为了20,所以被const修饰的局部变量只是拥有了常属性,不能直接对其进行更改,但是它的本质还是一个变量,所以我们可以通过指针来对它的值进行修改。

这种通过地址来修改变量的值的方式是绕过了C语言的语法规则,打破了const的规则限制,这显然是不合理的,那我们应该怎么做才能保证即使拿到了变量的地址也无法对变量进行修改呢?

12.3 const修饰指针变量

为了能够在拿到变量地址后也无法修改变量的值,我们可以通过const对指针进行修饰。但是应该如何修饰呢?下面我们来看一段代码:

计算机初级选手的成长历程——指针(5)_const关键字_10

在这个代码中,我们通过将const放在指针变量的不同位置来对const进行修饰。可以看到,此时的程序报错内容是指针pa和pa2这两个const在*左边修饰的指针,而对于const在*右边修饰的指针系统并未报错,那是不是代表我们可以通过指针来修改变量的值呢?下面我们继续测试:

计算机初级选手的成长历程——指针(5)_assert断言_11

从测试结果中我们可以看到,此时变量的值确实通过指针pa被修改了,也就是说如果我们想限制指针无法通过解引用修改指向的对象中存储的内容,那我们就需要将const放在*左边对指针进行修饰才行。

根据const的语法规则,如果我们将const放在*右边时,此时const限制了什么内容呢?下面我们继续测试:

计算机初级选手的成长历程——指针(5)_const关键字_12

在这个代码中,我们想通过指针变量p完成对变量a以及变量b的修改,可是我们可以看到,在完成对变量a的修改后,我们将指针p指向b时,此时系统报错了,报错内容为此时的变量p是无法被修改的。 那如果此时const放在*的左边能不能对指针指向的对象进行修改呢?我们继续测试:

计算机初级选手的成长历程——指针(5)_C语言_13

可以看到,此时的指针p是可以对指向的对象进行修改的。通过上面的测试我们可以得到结论:

  • 当const在指针*左边修饰指针变量时,限定的是*p,即无法对*p中存储的内容进行修改,但是可以对指针p指向的对象进行修改
  • 当const在指针*右边修饰指针变量时,限定的是p,即无法对指针p指向的对象进行修改,但是可以对*p中存储的内容进行修改

在前面我们在介绍野指针时有说过,我们可以通过下面五点来规避野指针:

  1. 给指针进行初始化;
  2. 避免指针越界访问;
  3. 不要返回局部变量或者临时变量的地址;
  4. 当指针指向的地址不再使用时,将指针置为空(NULL);
  5. 在使用指针前,检查指针的有效性;

既然我们需要再使用指针前检查指针的有效性,那我们应该怎么做呢?这就是我们现在要介绍的一个新的知识点——assert断言;

十三、assert断言

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

13.1 assert工作原理

assert()这个宏可以接收一个表达式作为参数。如果表达式为真(返回值非零),assert()不会产生任何作用,程序继续运行。如果表达式为假(返回值为零),assert()就会报错,在标准错误流stderr中写入一条错误信息,显示没有通过表达式,以及包含这个表达式的文件名和行号。

借助这个宏,我们就可以在使用指针前来检查指针的有效性,如下所示:

计算机初级选手的成长历程——指针(5)_void*指针_14

可以看到,当assert的括号内的条件不满足时,此时系统就会报错,在报错中会显示文件的路径以及报错的具体位置,同时系统也会弹出调试错误的窗口。

13.2 NDEBUG

当我们在确保程序没问题后,不需要进行断言时,我们可以在头文件语句前定义一个宏NDEBUG。此时在重写编译程序时,编译器就会禁用文件中的所有assert()语句。当遇到新问题时,我们只需要将这个宏注释掉,就能继续启用assert()语句来检测程序的问题了。

计算机初级选手的成长历程——指针(5)_const关键字_15

可以看到,此时虽然指针是空指针,但是因为NDEBUG的加入,assert()并未启用,所以正常打印了hehe,如果我们将它注释掉,它就又会正常启用assert(),如下所示:

计算机初级选手的成长历程——指针(5)_const关键字_16

13.3 assert的优缺点

对于程序猿来说,assert()还是非常友好的:

  1. 它能识别并自动表示文件和出现问题的行号;
  2. 通过与NDEBUG来配合使用,就能实现开启或关闭assert()机制;

但是因为引入了额外的检查,所以在使用assert()时会增加程序的运行时间。

对于断言功能,一般我们是在Debug版本中使用,这样能够帮助我们来排查程序中存在的问题;为了不影响用户使用程序的效率,我们在Release版本中禁用assert就可以了。对于VS这样的集成开发环境中,在Release版本中,编译器会直接帮我们将assert给优化掉。

结语

今天的补充知识点咱们就全部介绍完了,接下来我们将会继续对指针的内容进行深入的探讨,大家记得关注哦!

最后感谢各位的翻阅,咱们下一篇再见!!!


标签:const,变量,局部变量,初级,选手,全局变量,我们,指针
From: https://blog.51cto.com/u_16231477/8785528

相关文章

  • 实验6 C语言结构体,枚举应用编程(附实验5 C语言指针应用编程)
    实验6一,实验目的二,实验准备三,实验内容1,实验任务1task1.c1#include<stdio.h>2#include<string.h>3#defineN3//运行程序输入测试时,可以把这个数组改小一些输入测试45typedefstructstudent{6intid;//学号7......
  • 计算机初级选手的成长历程——指针(4)
    进阶指针导言大家好,很高兴有和大家见面啦!经过前面的学习,大家现在对指针的内容应该有了一个初步的印象。为了帮助大家将指针的知识点好好的消化吸收,今天开始我们将对指针的内容进行深入的探讨。下面我们就开始今天的内容吧!九、二级指针与指针数组通过前面的介绍,我们知道,对于一维数组......
  • 【C系列5.4】指针专题之分割字符串(strtok与gets的应用)(hznuoj)
    Description Alex的好朋友都去生猴子了,所以她只好百无聊赖地继续玩字符串游戏。输入一个长度不超过10000的字符串,字符串中只含字母和空格,空格用于分隔单词,请将字符串中用空格分隔的单词输出来。 Input 输入含多组测试数据,每组占一行,是一个长度不超过10000的字符串,只含字......
  • 初学指针,刷题(hznu【C系列5.6】指针专题之翻译)
    题目如下Description (本人学艺不精,写了很久才写出了一个臃肿的代码,malloc也不咋会用,只能向ai请教了T_T)Alex在朋友们都去生猴子了的日子里,日复一日、年复一年地敲代码,终于,在经年累月的摧残下,她的手指变得不那么利索了,比如“how are you”她会哆嗦着打成“hhoow areee youu......
  • c语言指针
    【C语言】中的指针说明:只是学习中的一些感悟,如有错误,欢迎指正一、指向指针的指针指向指针的指针是C语言中的一种数据类型,通常简称为"指针的指针",使用两个星号('**')表示。指向指针的指针是一个变量,其值是另一个指针的地址。|1.用法:1>如下定义了一个指向指针的指针char**......
  • 极客时间邓明初级go工程师训练营
    获取完整版--》请留言1.变量变量的声明有四种方式:声明一个变量,默认的初始化值为0:varaint声明一个变量,初始值为100:varaint=100初始化时候省略数据类型,通过值自动推导变量的数据类型:vara=100省略掉var关键字,直接自动匹配,但要使用:=a:=100一个注意的点:第四种声明变量的方......
  • 计算机初级选手的成长历程——指针(2)
    初阶指针导言大家好,很高兴又和大家见面了!!!在上一个篇章中,我们介绍了指针、指针变量以及野指针的相关知识点。在今天的篇章中,我们将探讨一下指针是如何进行运算的。五、指针运算对于指针的运算,共有三种运算方式:指针+-整数指针-指针指针的关系运算5.1指针+-整数对于不同类型的指针加/......
  • C++(智能指针)
    在C++中,智能指针是一种用于管理动态分配内存的智能化工具。它们提供了对动态分配资源的自动管理,以减少内存泄漏和资源泄漏的风险。C++标准库提供了两种主要的智能指针类型:std::shared_ptr和std::unique_ptr。以下是这两种智能指针的基本解释:1.std::shared_ptr:std::shared_......
  • 两种方法求字符串个数(函数递归和指针)
    前言:我先想讲一个关于指针的问题,由于我一开始学习指针的时候很困惑,现在分享给大家。假设我们定义一个指针p,我们首先要区分p、&p与*p的区别(对于初学者应该和我一开始一样迷茫)p:p是一个指针变量的名字,表示此指针变量指向的内存地址,如果用%p输出的话它将是一个16进制位的数。*p:*是解引......
  • 2023最新初级难度前端面试题,包含答案。刷题必备!记录一下。
    好记性不如烂笔头内容来自面试宝典-初级难度前端面试题合集问:请详细描述HTML、CSS、JavaScript的基本结构?HTML、CSS、JavaScript是web前端开发中最常用的三种技术,它们分别负责页面结构、表现形式和交互行为。HTML(HyperTextMarkupLanguage)是一种用于构建网页的标......