C语言指针相关知识(第一篇章)(非常详细版)

文章目录

  • 前言
  • 一、指针概念的引入与指针的基本介绍
    • (一)、内存与地址
    • (二)、指针变量和地址
    • (三)、指针变量类型的意义
    • (四)、const修饰指针
  • 二、指针的运算
    • (一)、指针+-整数
    • (二)、指针-指针
    • (三)、指针的关系运算
  • 三、野指针
    • (一)、野指针的成因
    • (二)、如何规避野指针
    • (三)、利用assert断言来判断指针的*有效性
  • 四、传值调用与传址调用
    • (一)、传值调用
    • (二)、传址调用
    • (三)、两种调用的总结
  • 总结


前言

提示:这里可以添加本文要记录的大概内容:
本文初步引人了指针的概念,并对指针的一些基本知识做了概括,并提了一下野指针的问题,最后讲了一下传值调用与传址调用的区别以及指针在其中发挥的作用,我们要知道指针知识博大精深,这篇文章只是冰山一角呀,后期会出后面极板,一共打算出5版指针相关的文章,每一章节各有千秋。


提示:以下是本篇文章正文内容,下面案例可供参考

一、指针概念的引入与指针的基本介绍

(一)、内存与地址

  • 计算机中的内存就是数据存储的地方。我们知道计算机上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中。
  • 我们了解一下计算机中内存的单位从小到大一次为: bit、Byte、KB、MB、GB、TB;
  • 内存划分为一个个的内存单元,每个内存单元的大小是一个字节。 其中,每个内存单元,相当于⼀个学⽣宿舍,⼀个字节空间⾥⾯能放8个⽐特位,就好⽐同学们住的⼋⼈间,每个⼈是⼀个⽐特位。
  • 生活中我们把门牌号等事物叫做地址(方便我们找寻相应的值),在计算中我们把内存单元的编号也叫做地址,而在C语言中我们将地址起名为指针。
  • 如何理解编址:
    CPU访问内存中的某个字节空间,必须知道这个字节空间在内存的什么位置,⽽因为内存中字节很多,所以需要给内存进⾏编址(就如同宿舍很多,需要给宿舍编号⼀样)。计算机中的编址,并不是把每个字节的地址记录
    下来,⽽是通过硬件设计完成的。
    首先,我们要理解的是,计算机内是有很多硬件单元,而硬件单元是要互相协作工作的。所谓的协同,至少相互之间要能够进行数据传递,这期间是通过“线”来链接的,具体有地址总线,数据总线,控制总线。
    在这里插入图片描述
    我们可以简单理解,32位机器有32根地址总线,每根线只有两态,表⽰0,1【电脉冲有⽆】,那么⼀根线,就能表⽰2种含义,2根线就能表⽰4种含义,依次类推。32根地址线,就能表⽰2^32种含义,每⼀种含义都代表⼀个地址。地址信息被下达给内存,在内存上,就可以找到该地址对应的数据,将数据在通过数据总线传⼊CPU内寄存器。这就是编址的大概过程。

(二)、指针变量和地址

  • 指针变量
    我们通过取地址操作符(&)拿到的地址需要存放起来,这时候我们引入指针变量(即数据类型+*+变量名)
    代码如下:
#include <stdio.h>int main(){int a = 10;int * pa = &a;//取出a的地址并存储到指针变量pa中return 0}

指针变量pa也是一种变量,这种变量用来存放地址的,存放指针变量中的值都会被理解成地址

  • 详细拆解指针变量
    这里我们定义一个指针变量 pa:
int a = 10;
int * pa = &a;

pa左边写的是int*,*是说明pa是指针变量,而前面的int则是说明pa指向的是整型(int)类型的对象。
具体关系如下:
在这里插入图片描述

  • 解引用操作符
    在C语言中,我们要得到了一个地址,就可以通过地址(指针)来找到地址(指针)所指向的对象,而这个找寻的过程中需要我们调用解引用操作符(*)
    例如下面代码:
#include <stdio.h>
int main(){int a = 100;int* pa = &a;*pa = 0;printf("%d",a);return 0;}

这里我们通过解引用操作符(*)找到了pa指针所指向的对象a,并对其进行了修改,这样我们打印出来的a的内容由原来的100变为了0.

  • 指针变量的大小:
    通过之前学习我们认识到,32位机器假设有32根地址总线,每根地址线出来的电信号转换成数字信号后是1或者0,那我们把32根地址线产⽣的2进制序列当做⼀个地址,那么⼀个地址就是32个bit位,需要4个字节才能存储。如果指针变量是用来存放地址的,那么指针变量的大小是4个字节的空间才可以。
    同理,如果64位机器假设有64根地址线,⼀个地址就是64个⼆进制位组成的⼆进制序列,存储起来就需要8个字节的空间,指针变量的⼤⼩就是8个字节。
    代码操作:
 #include <stdio.h>//指针变量的大小取决于地址的大小
int main(){printf("%zd\n", sizeof(char *));printf("%zd\n", sizeof(short *));printf("%zd\n", sizeof(int *));printf("%zd\n", sizeof(double *));return 0;}

结果如图所示:
在这里插入图片描述
在这里插入图片描述
总而言之:
32位平台下地址是32个bit位,指针变量大小是4个字节;
64位平台下地址是64个bit位,指针变量大小是8个字节,
故而指针变量的大小是和变量类型无关的,只要指针类型的变量,在相同平台下,大小都是相同的。

(三)、指针变量类型的意义

前面已经提过指针变量的大小和类型无关,只要是指针变量,在同一个平台下,大小都是一样的,那为啥子还要区分指针变量的类型呢?
下面解答大家的疑惑,给出指针变量类型的相关意义:

  • 指针类型决定了,对指针解引用的时候有多大权限(一次操作几个字节)。
    用指针的解引用来解释:
    通过两个代码调试过程查看地址窗口来解释:
//代码1:
#include<stdio.h>
int main(){int n = 0x11223344;int *pi = &n; *pi = 0;   return 0;}
//代码2:
#include<stdio.h>
int main(){int n = 0x11223344;char *pc = (char *)&n;*pc = 0;return 0;}

调试结果如下:
代码1:
在这里插入图片描述
n地址的四个字节所指向的内容全部赋值为0。

代码2:
在这里插入图片描述
n的四个字节只有第一个字节所指向的内容赋为0

通过调试我们可以看到代码1会将n的4个字节全部改为0,但是代码2只是将n的第一个字节改为0.
故而我们可以得出指针类型决定了,对指针解引用的时候有多大权限(一次操作几个字节)。

  • 指针类型决定了,指针向前或者向后走一步有多大(距离)。
    用指针±整数1来解释:
    代码解释如下:
#include <stdio.h>int main(){int n = 10;char *pc = (char*)&n;//强制类型转换,方便观察对比。此为字符型指针int *pi = &n;//此为整型指针printf("%p\n", &n);printf("%p\n", pc);printf("%p\n", pc+1);printf("%p\n", pi);printf("%p\n", pi+1);return  0;}

结果如下:
在这里插入图片描述从结果我们很明显的可以看出char类型的指针变量+1跳过1个字节,而int类型的指针变量+1跳过了4个字节。这就是指针变量的类型差异带来的变化。指针+1,其实就是跳过1个指针指向的元素。
故而可以得出结论:指针类型决定了,指针向前或者向后走一步有多大(距离)。

  • void指针的介绍:
    void
    指针可以理解为无具体类型的指针(或者说叫泛型指针),这种类型的指针可以用来接受任意类型地址。但是也有局限性,那就是void*类型的指针不能直接进行指针的±整数和解引用的运算。

(四)、const修饰指针

  • const放在*的左边
    如果const放在 * 的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。

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

  • const在*号左右两边都存在
    如果const在 * 左右两边都存在,那么指针变量内容不能被修改,同时指针指向的内容也不可以通过指针修改。

  • 三种情况代码层面的解释:

#include<stdio.h>
void test1(){//代码1 : 测试⽆const修饰的情况int n = 10;int m = 20;int *p = &n;*p = 20;//ok?p = &m; //ok?}
//代码2:测试const放在*的左边的情况
void test2(){int n = 10;int m = 20;const int* p = &n;*p = 20;//ok?p = &m; //ok?}
// 代码3:测试const放在* 右边的情况
void test3(){int n = 10;int m = 20;int * const p = &n;*p = 20; //ok?p = &m;  //ok?}
//代码4:测试*的左右两边都有const
void test4(){int n = 10;int m = 20;int const * const p = &n;*p = 20; //ok?p = &m;  //ok?}
int main()
{
//测试无const的情况
test1();
//测试const在* 左边的情况
test2();
//测试const在* 右边的情况
test3();
//测试* 的两边都有const情况
test4();
return 0;
}

我们可以分别运行test1~4看一下代码能否执行,结果只有代码一能正常运行,而后面几个代码,不能执行,但能看到哪一行出的问题,追溯原因就能理解const的作用。

  • 总结 :如果const如果放在的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。但是指针变量本⾝的内容可变;const如果放在的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。

二、指针的运算

(一)、指针±整数

  • 数组再内存中是连续存放的,故而知道知道第一个元素的地址,就能顺藤摸瓜找到所有的元素,我们可以用指针保存第一个元素的地址,然后通过指针+整数的形式访问数组所有元素
  • 以下是通过指针±整数来访问数组元素的代码:
 #include <stdio.h>
int main(){int arr[10] = {1,2,3,4,5,6,7,8,9,10};int *p = &arr[0];//保存数组第一个元素int i = 0;int sz = sizeof(arr)/sizeof(arr[0]);//求出数组的元素个数for(i=0; i<sz; i++){printf("%d ", *(p+i));//p+i  这里就是指针+-整数}return 0;
}

(二)、指针-指针

  • 指针-指针的绝对值是指针和指针之间元素的个数(计算的前提条件是两个指针指向的是同一个空间!)
  • 模拟strlen函数的功能代码来解释指针-指针的操作:
#include<stdio.h>
size_t my_strlen(const char* p)
//这里用const修饰代表不能改变p所指向对象的内容,这里我们只做统计工作
{char* start = p;char* end = p;while (*end!='\0'){end++;}return end - start;//运用指针-指针来求两指针间元素的个数
}int main()
{char arr[] = "hello,world";size_t len = my_strlen(arr);printf("字符串长度为:%zd\n", len);
}

这里我们通过end与start指针之差来代表字符串中元素的个数。

(三)、指针的关系运算

  • 指针的关系运算:其实让指针之间比较大小
  • 用下面一段代码来解释:
#include<stdio.h>int main(){int arr[10] = {1,2,3,4,5,6,7,8,9,10};int *p = &arr[0];int sz = sizeof(arr)/sizeof(arr[0]);while(p<arr+sz) //指针大小的比较
{printf("%d ", *p);p++;
}
return 0;
}

这段代码其实就是比较指针所指向地址的数值大小,通过比较来是实现访问数组元素的功效。
注意这里的指针大小代表的是指针所指向的地址数值的大小,跟上面讲的指针变量大小(它所指的字节数)有本质的区别。

三、野指针

概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

(一)、野指针的成因

  • 指针未初始化
  • 指针越界访问

(二)、如何规避野指针

  • 指针初始化
  • 小心指针越界
  • 指针不再使用时,及时置NULL,指针使用之前检查有效性
  • 避免返回局部变量的地址

(三)、利用assert断言来判断指针的*有效性

头文件:assert.h

  • 概念:assert.h头文件定义了宏assert(),用于运行时确保程序符合指定条件,如果不符合,就报错终止运行,这里的宏常常被称为“断言”。
  • assert的用法:assert()宏接受一个表达式作为参数,如果该表达式为真(返回值为非零),assert()不会产生任何作用,程序继续运行。如果该表达式为假(返回值为零),assert()就会报错。
  • 我们经常用assert断言来判断指针的有效性。
  • assert的好处与缺点:
    好处:assert()宏接受一个表达式作为参数,如果该表达式为真(返回值为零),assert()不会产生任何作用,程序继续运行,如果该表达式为假(返回值为零),assert()就会报错,在标准错误流stderr中写入一条错误信息,显示没有通过的表达式,以及包含这个表达式的文件名和行号
    缺点:assert的缺点,因为额外引入了检查,增加了程序的运行时间。
  • ⼀般我们可以在Debug 中使⽤,在发环境中,在
    在 Release 版本中选择禁⽤assert 就⾏,在VS 这样的集成开发版本中,直接就是优化掉了。这样在debug版本写有利于程序员排查问题,Release 版本不影响⽤⼾使⽤时程序的效率。

四、传值调用与传址调用

以写一个交换两个数的位置的函数为例来展开讨论。
写一个函数来描述两个数交换。调用完函数后,将交换的结果打印出来,来展开描述传值调用与传址调用相关知识

(一)、传值调用

  • 代码显示:
# include<stido.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;
  • 运行结果如下:
    在这里插入图片描述
  • 分析结果:
    这里我们可以看到交换前与交换后的结果相同,没有 产生交换的效果我们通过调试的过程来深度剖析一下原因。
    在这里插入图片描述
    通过调试的过程,我们可以看到在main函数的内部,我们创建了a,b两个变量,此时a的地址为0x00cffdd0,b的地址为:0x00cffdc4,在调用Swap1函数的时候,将a和b的值传递给了Swap1函数,而在Swap1函数内部我们创建了x,y变量分别来接受a和b的值,但是x的地址为:0x00cffcec,y的地址为:0x00cffcf0,虽然x,y接受了a,b的数值,但是x,y的地址明显跟a,b的地址不同,换句话说,x,y相当于是独立的空间,那我们在Swap1函数内部交换x,y的值自然不会影响a,b,当Swap1函数调用结束回到main函数中,a和b没办法交换。
    总而言之,Swap1函数在使用的时候,是把变量本身直接传递给了函数,叫做传值调用,实参传给形参的时候,形参会单独创建一份临时空间来接收实参,对形参的修改不影响实参,故而Swap1函数交换。

(二)、传址调用

  • 代码显示:
# 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);Swap2(&a, &b);printf("交换后:a=%d b=%d\n", a, b);return 0;
  • 运行结果:

在这里插入图片描述
这里我们在调用Swap2函数的时候,Swap2函数内部的操作就是main函数中的a和b,直接将a和b的值进行交换律,因为我们在传递参数的时候是传的指针,即及那个a和b的地址传递给了Swap2函数,Swap2函数里边通过地址间接的操作main函数中的a和b,并达到了交换的效果就好了。
总而言之:调用Swap2函数的时候是将变量的地址传递给了函数,这种函数调用的方法叫做传址调用,传址调用,可以让函数和主调函数之间建立真正的联系,在函数的内部可以修改主调函数中的变量。

(三)、两种调用的总结

在未来函数中只是需要主调函数中的变量值来实现计算,就可以采用传值调用;如果函数内部要修改主调函数中的变量的值,就需要传址调用。

总结

以上就是对指针的初步介绍,如有错误,请批评指正,请大家多多支持。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

锤子蜡烛如何交易?Anzo Capital这样交易10倍收益结束

很多投资者发现以下的情况&#xff0c;就认为反转到来了&#xff0c;颓势即将结束牛市即将来临。什么情况呢&#xff1f;就是在交易中发现这种情况&#xff1a;会在局部低点形成&#xff0c;上影线很小或几乎没有上阴影&#xff0c;收盘价高出 1/4 &#xff0c;烛台总有长长的下…

【数据结构(邓俊辉)学习笔记】栈与队列01——栈应用(栈混洗、前缀后缀表达式、括号匹配)

文章目录 0. 概述1. 操作与接口2. 操作实例3. 实现4. 栈与递归5. 应用5.1 逆序输出5.1.1 进制转换5.1.1.1 思路5.1.1.2 算法实现 5.2 递归嵌套5.2.1 栈混洗5.2.1.1 混洗5.2.1.2 计数5.2.1.3 甄别 5.2.2 括号匹配5.2.2.1 构思5.2.2.2 实现5.2.2.3 实例 5.3 延迟缓冲5.3.1 中缀表…

Gitee 码云与Git 交互

优质博文&#xff1a;IT-BLOG-CN 一、进入码云官方网站&#xff0c;注册用户 码云(Gitee.com)是一个类似于GitHub的在线代码托管平台。 码云提供了包括版本控制、代码托管、协作开发和代码分享等功能&#xff0c;基于Git开发&#xff0c;支持代码在线查看、历史版本查看、Fo…

基于vs和C#的WPF应用之动画3

注&#xff1a;1、在内部和外部使用缓动函数 <Grid.Resources> <PowerEase x:Key"powerease" Power"3" EasingMode"EaseInOut"/> </Grid.Resources> <DoubleAnimation EasingFunction"{StaticResource powerease}&quo…

linux开发笔记(buildroot 增加自己的开发板支持文件)

1、该笔记参考了mangopi r3的buildroot。某宝上卖的LC-PI-200S提供的buildroot就是这个。已经上传到我的资源中&#xff0c;可以下载看看。 2、首先在buildroot目录输入make menuconfig打开buildroot配置。 进入build options查看 可以看到第二行就是buildroot配置的保存位置…

KaiwuDB 解析器之语义解析

KaiwuDB 解析器介绍 解析器是数据库系统的重要组成部分之一&#xff0c;主要的功能是将客户端输入的 SQL 语句分解为语法单元&#xff0c;然后将这些语法单元转化成数据库内部可识别的数据结构&#xff0c;最终生成数据库可以执行的计划。 KaiwuDB 的一条 SQL 执行的整个生命…

达梦数据刷盘测试

达梦数据库为了保证数据故障恢复的一致性&#xff0c;REDO 日志的刷盘必须在数据页刷盘之前进行。 下面我们通过测试来验证是不是这样 执行我们事先准备的SHELL脚本 可以看到第一次strings文件没有输出&#xff0c;说明刚写的数据在数据库的BUFFER缓冲区内&#xff0c;还没有刷…

什么样的人能上百度词条

百度百科是一个向所有互联网用户开放的平台&#xff0c;任何人都可以创建或编辑词条。然而&#xff0c;并不是所有的人物或事物都能被收录到百度百科中&#xff0c;它有一定的收录标准和审结的关于哪些人或事物能上百度百科的条件和流程。 百度百科的收录标准 知名度和影响力&…

太牛了!360大佬编写的《应急响应指导手册》火了!(PDF限时3天领取)

免责声明&#xff1a; 请使用者遵守《中华人民共和国网络安全法》&#xff0c;由于传播、利用本账号所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;公众号及作者不为此承担任何责任。 简介 这份《应急响应指导手册》&#xf…

OpenNJet评测,探寻云原生之美

在信息时代的大海上&#xff0c;云原生应用引擎如一艘航行于波涛之间的帆船&#xff0c;承载着创新的梦想和数字化的未来。本文将带领您登上这艘船&#xff0c;聚焦其中之一的OpenNJet&#xff0c;一同探寻其中的奥秘和精妙&#xff0c;领略其独特之美。 OpenNJet 内容浅析 O…

每日Attention学习3——Cross-level Feature Fusion

模块出处 [link] [code] [PR 23] Cross-level Feature Aggregation Network for Polyp Segmentation 模块名称 Cross-level Feature Fusion (CFF) 模块作用 双级特征融合 模块结构 模块代码 import torch import torch.nn as nnclass BasicConv2d(nn.Module):def __init__(…

Python批量备份华为设备配置到FTP服务器

Excel表格存放交换机信息&#xff1a; 备份文件夹效果图&#xff1a; Windows系统配置计划任务定时执行python脚本&#xff1a; Program/script&#xff1a;C:\Python\python.exe Add arguments (optional)&#xff1a; D:\Python_PycharmProjects\JunLan_pythonProje…

AWS Cli Windows安装配置

1. 安装 下载地址&#xff1a;AWS 命令行界面(CLI)_管理AWS服务的统一工具-AWS云服务 检验安装&#xff1a; > aws --version aws-cli/2.15.44 Python/3.11.8 Windows/10 exe/AMD64 prompt/off 2. 创建IAM用户 1) 创建组 选择IAM 点击创建组 填写用户组名&#xff0c;…

c++——类和对象(中)

1.类的六个默认成员函数 在一个空类中真的什么都没有吗&#xff0c;错&#xff01;在创建类的时候&#xff0c;编译器自动生成六个函数&#xff0c;这六个函数叫默认成员函数。但是&#xff0c;如果我们自己实现六个同名函数&#xff08;依旧有默认成员函数的特性&#xff0c;…

Django项目之电商购物商城 -- 创建收货地址

Django项目之电商购物商城 – 创建收货地址 一. 在users中创建新的视图与路由用于创建收货地址 # 设置收货地址 class AddressView(View):def get(self , request):return render(request , "user_center_site.html")# 设置收货地址path(user_center_site/, views.…

金和OAC6 FileDownLoad 任意文件读取漏洞

文章目录 免责声明漏洞描述漏洞原理影响版本漏洞复现修复建议 免责声明 没有网络安全就没有国家安全&#xff0c;该文章只为学习和交流&#xff0c;利用做违法乱纪的事&#xff0c;与本人无关 漏洞描述 金和网络是专业信息化服务商,为城市监管部门提供了互联网监管解决方案,…

AI视频教程下载:零代码创建AI智能体、AI Agents和ChatGPT的Gpts

这门课程专注于提示工程的掌握&#xff0c;教你以精确的方式引导GPT&#xff0c;利用它们的生成能力产生卓越的AI驱动结果。一步一步地&#xff0c;你将学会创建多样化的GPT军团——每个都设计来满足特定的专业需求。 从提供个性化职业变更指导的职业教练AI&#xff0c;到以惊…

IDEA切换分支

方法一 1、选择要切换分支的module 2、右键&#xff0c;选择git 3、再点击branches 4、可以看到当前module的本地分支&#xff08;local Branches&#xff09;及远程分支&#xff08;Remote Branches&#xff09;列表。点击你要切换到的分支,Checkout即可。 方法二 1、点击…

MATLAB模拟退火算法、遗传算法、蚁群算法、粒子群算法

概况 模拟退火算法、遗传算法、蚁群算法、粒子群算法等算法&#xff0c;都是属于概率算法&#xff0c;不绝对&#xff0c;不迅速&#xff0c;能用其它方式解决的问题&#xff0c;不要用这些相对复杂的算法&#xff0c;比如有明确的线性关系或者非线性对应关系。这里的概率算法…

大模型背后的秘密公式: Q*?

这麽说好像我是James Bond后面那个厉害的Q先生&#xff0c;Q是英文Quartermaster&#xff08;軍需官&#xff09;第1個英文字大寫&#xff0c;是007系列英國祕勤局虛構部門Q部門的領導。 Stanford大学的研究者最近发表了一篇名为"From r to Q*: Your Language Model is S…