C 语言学习笔记(指针1)

内容提要

  • 函数
    • 变量的作用域
    • 变量的生命周期
  • 指针
    • 预备知识
    • 变量指针与指针变量

函数

变量的作用域

引入问题

我们在函数设计的过程中,经常要考虑对于参数的设计,换句话说,我们需要考虑函数需要几个参数,需要什么类型的参数,但我们并没有考虑函数是否需要提供参数,如果说函数可以访问到已定义的数据,则就不需要提供函数形参。那么我们到底要不要提供函数形参,取决于什么?答案就是变量的作用域(如果函数在变量的作用域范围内,则函数可以直接访问数据,无需提供形参)

变量作用域

**概念:**变量的作用范围,也就是说变量在什么范围有效。

变量的分类

根据变量的作用域不同,变量可以分为:

  • 全局变量

    • 解释:定义在函数之外,也称之为外部变量或者全程变量。

    • 作用域:从全局变量定义本源文件结束。

    • 初始值:整型和浮点型,默认值是0;字符型,默认值是\0;指针型,默认值NULL

    • 举例:

      int num1;         // 全局变量,num1能被fun1、fun2、main共同访问
      void fun1(){}
      int num2;        // 全局变量,num2能被fun2、main共同访问
      void fun2(){}
      void main((){}
      int num3;        // 全局变量不能被任何函数访问
      
  • 局部变量

    说明作用域初始值
    形式参数(形参)函数作用域随机值,需要手动赋初值
    函数内定义的变量函数作用域随机值,需要手动赋初值
    复合语句中定义的变量块作用域随机值,需要手动赋初值
    for循环表达式1定义的变量快作用域随机值,需要手动赋初值

    举例:

    //a,c就是形式参数(局部变量)
    int add(int a, int b)
    {return a + b;
    }int add2(int a, int b)
    {//z就是函数定义的变量(局部变量)int z = a + b;return 0;
    }int list(int arr[], int len)
    {//i就是for循环表达式1的变量(局部变量)for(int i = 0; i <len; i++){//num就是复合语句中定义的变量(局部变量)int num = arr[i];}
    }
    

使用全局变量的优缺点

优点:

1.利用全局变量可以实现一个函数对外输1.利用全局变量可以实现一个函数对外输出的多个结果数据。

2.利用全局变量可以减少函数形参的个数,从而降低内存消耗,以及因为形参传递带来的时间消耗

缺点:

1.全局变量在程序的整个运行期间,始终占据内存空间,会引起资源消耗。

2.过多的全局变量会引起程序的混乱,操作程序结果错误。

3.降低程序的通用性,特别是当我们进行函数移植时,不仅仅要移植函数,还要考虑全局变量。

4.违反了“高内聚,低耦合"的程序设计原则。

总结:我们发现弊大于利,建议尽量减少对全局变量的使用,函数之间要产生联系,仅通过实参+形参的方式产生联系。

作用域举例

在这里插入图片描述

注意:

如果全局变量和局部变量同名,程序执行的时候,就近原则(区分作用域)

int a = 10; //全局变量  全局作用域int main()
{int a = 10; //局部变量  函数作用域printf("%d\n", a);  // 20 就近原则for(int a = 0; a < 5; a++)  //局部变量 块作用域{printf("%d", a); //0 1 2 3 4  就近原则}printf("%d\n", a); // 20 就近原则
}

变量的生命周期

定义

**概念:**变量在程序运行中的存在时间(内存申请到内存释放的时间)

根据变量存在的时间不同,变量可分为**静态存储方式动态存储方式**

在这里插入图片描述

变量的存储类型

语法:

变量的完整定义格式:[存储类型] 数据类型 变量列表;

存储类型:

  • auto

    autoauto存储类型只能修饰局部变量,被auto修饰的局部变量是存储在动态存储区(栈区和堆区)。auto也是局部变量默认的存储类型。

    int main()
    {int a;int b;//以下写法等价于上面写法auto int a;auto int b;int a,b;//一下写法等价于上面写法auto int a,b;
    }
    
  • static
    **修饰局部变量:**局部变量会被存储在静态存储区。局部变量的生命周期被延长。但是作用域不发生改变,不推荐

    **修饰全局变量:**全局变量的生命周期不变,但是作用域衰减,一般限制全局变量只能在本源文件内访问,其他文件不可访问。

    **修饰函数:**被static修饰的函数,只能被当前文件访问,其他引用该文件的文件是无法访问的,有点类似于java中的private

  • extern

    外部存储类型:只能修饰全局变量,此全局变量可以被其他文件访问,相当于扩展了全局变量的作用域。

    extern修饰外部变量,往往是外部变量进行声明,声明该变量是在外部文件中定义的。起到一个标识作用。函数同理。

    demo01.c

    #include "demo01.h"int fun_a = 10;
    int fun1(){..}
    

    demo02.c

    #include "demo01.h"// 声明访问的外部文件的变量
    extern int fun_a;
    //声明访问的外部文件的函数
    extern int fun1();int fun2();
    
  • register

    **寄存器存储类型:**只能修饰局部变量,用register修饰的局部变量会直接存储到CPU的寄存器中,往往将循环变量设置为寄存器存储类型(提高读的效率)

    for(register int i = 0; i < 10; i++)
    {...
    }
    
面试题

static关键字的作用
1.static修饰局部变量,延长其生命周期,但不影响局部变量的作用域。

2.static修饰全局变量,不影响全局变量的生命周期,会限制全局变量的作用域仅限本文件内集用(私有化);

3.static修饰函数:此函数就称为内部函数,仅限本文件内调用(私有化)。`static int funa(){…}

内部函数和外部函数
  • **内部函数:**使用static修饰的函数,称作内部函数,内部函数只能在当前文件中调用。
  • **外部函数:**使用extern修饰的函数,称作外部函数,exter是默认的,可以不写(区分编译环境),也就是说本质上我们缩写的函数基本上都是外部函数,建议外部函数在被其他文件调用的时候,在其他文件中声明的时候,加上extern关键字,主要是提高代码的可读性。

指针

预备指示

内存地址
  • 字节:字节是内存的容量单位,英文名Byte, 1Byte = 8 Bits

  • 地址:系统为了便于区分灭一个字节而对他们逐一进行的编号(编号唯一),称为内存地址,简称地址。

    在这里插入图片描述

基地址(首地址)
  • 单字节数据:对于单字节数据而言,其地址就是其字节编号。举例:char a = ‘A’

  • 多字节数据:对于多字节数据而言,其地址就是所有字节中编号最小的那个,称为基地址(首地址)

    在这里插入图片描述

取址符
  • 每个变量都是一块内存,都可以通过取地址符&获取其地址。

  • 例如

    int a = 100;
    printf("整型变量a的地址:%p\n", &a); //0x7ffe382a8114   64位系统 地址是12位16进制整数
    char c = 'x';
    printf("字符变量c的地址:%p\n", &c); //0x7ffdf5482af3
    
  • 注意:

    • 虽然不同的变量的尺寸是不同的,但是他们的地址的尺寸是一致的。
    • 不同的地址虽然形式上看起来是一样的,但由于它们代表的内存尺寸和类型都不同,因此他们在逻辑上是需要严格区分。

为什么要修改指针

  • 为函数修改实参提供支持。
  • 为动态内存管理提供支持。
  • 为动态数据结构(链表、队列、二叉搜索树)提供支持。
  • 为内存访问提供了另一种途径。

变量指针的指针变量

指针的概念
内存单元与地址机制
  • 内存单元划分
    • 系统将内存划分为连续的基本存储单元,每个单元的容量为1字节(8 Bits)
    • 每个内存单元拥有唯一编号,称为内存单元(12位16进制表示,如:0x7ffe382a8114
  • 变量的存储特性
    • 变量根据数据类型占据不同的数量的内存单元:
      • char 类型占1字节(1个单元)
      • int 类型占4字节(4个单元)
      • double 类型占8字节(8个单元)
    • 变量的**基地址(首地址)**是首个内存单元的地址(首地址一般是这一组编号中最小的那个)
变量指针与指针变量
对比维度变量指针指针变量
本质地址值(指指针),变量指针,其实就是变量的首地址存储地址的变量
操作符&(取地址符)*(声明符,解引用符,如int *p;
代码示例&a(获取变量a的地址)int* p = &a;
核心特性不可修改(地址由系统分配)可修改指向(p = &b;
指向

指针变量中存放的地址,就说明该指针变量指向了

指针的尺寸
系统类型指针尺寸地址位数十六进制显示长度
32位系统4字节(long)32Bit8位(0x0804A000)
64位系统8字节(long)48Bit12位(0x7FFDEADBEEF)

小贴士:
Linux系统中打印地址时,最多显示12个十六进制数,为什么?

Linux64位操作系统中,一个内存地址占8个字节,一个字节8bit位,所以一个地址8 * 8=64bit位,每4个bit可以表示1个十六进制数; 64个bit位用十六进制表示最多有16个数值位

系统为了寻址方便,默认当前计算机系统没必要寻址64bit位,只寻址了48个bit位,所以用12个十六进制数表示个地址

二进制: 0100 1010 十六进制: 0x4A 4 * 16+10=74

注意: 在Linux64位操作系统中,指针类型的变量占8个字节的内存空间 在Linux32位操作系统中,指针类型的变量占4个字节的内存空间

指针的本质
  • 变量指针:数据的“门牌号”(&a)
  • 指针变量:存储门牌号的“笔记本”(int *p;)(int *p; 指针变量存储的地址所指向的对象的类型是int类型,不能说指针变量是int类型)
  • 指向操作:通过门牌号访问数据(*p)
int a = 10;
printf("%d\n", a);  //直接访问a的值int *p = &a;
printf("%p\n", p);  //直接访问p的值,p的值是a的地址
printf("%d\n", *p); //访问p指向的a的值,其实就是间接访问a的值
内存数据的存取方式

在C语言中对内存数据(变量、数据元素等)的存取有两种方式:

直接存取
  • 通过基本类型(整型、浮点型、字符型)的变量,访问这个变量代表的内存空间的数据

  • 通过数组元素的引用(下标),访问这个引用代表的内存空间的数据

    //基本类型变量
    int a = 10;              //存
    printf("%d\n", a);       //取//数组元素
    int arr[] = {11,22,33};  //存
    arr[0] = 66;             //存
    printf("%d\n",arr[0]);   //取
    
间接存取
  • 通过指针变量,间接的访问内存中的数据。

    在这里插入图片描述

  • *:读作指针声明符或者解引用符。如果*前面有数据类型,读作**声明指针;**如果*前没有数据类型,读作解引用。

案例

int main()
{//定义一个普通变量int a = 3;//定义一个指针变量,并附值(指针变量本质上还是一个变量,只不过存储的数据是其他变量的地址)int* p = &a; //这里的地址一定要有其在内存中对应的空间//访问变量a,直接访问printf("直接访问-%d\n", a);//访问变量a,通过指针变量p访问,间接访问printf("间接访问-d\n", *p); //*p 解引用  用过指针变量访问其指向的对象//访问变量p,p存储的是变量a的值,所以p的值是一个地址printf("地址访问-%p,%p,%p\n", p, &p, &a); // a的地址 p的地址 a的地址return 0;
}
指针变量的定义
语法:
数据类型 *变量列表;

举例:

int a; // 普通变量,拥有真实的数据存储空间
//正确写法
int *p1;
int *p2;
int *p3,*p4;//错误写法
int* p_5,p_6;

注意:指针变量的值只能是8(32位系统)| 12(64位系统)位的十六进制数。

注意:

①虽然定义指针变量*a,是在变量名前加上*,但是实际变量名依然为a,而不是*a

②使用指针变量间接访问内存数据时,指针变量必须要明确指向。(指向:指针变量存放谁的地址,就指向谁)

③ 如果想要借助指针变量间接访问指针变量保存的内存地址上的数据,可以使用指针变量前加*来间接返回访问。

#include <stdio.h>
int main()
{int i=5*p;p=&i; //将i的地址赋值给指针变量pprintf("%x,%p,%\n",P,p,&p); // %p-p:访问的是p的值,也就是i的地址;%p-&p:访问的是p自身的地址printf("%d\n",*p);// 5*p= 10;//间接的给p对应的地址上的数据空间赋值,也就是给变量i赋值printf("%d,%d\n",*p,i);// 10,10return 0;
}

④指针变量只能指向同类型的变量,借助指针变量访问内存,一次访问的内存大小是取决于指针变量的类型。

int a = 10;
int **p = &a; //这里的int是指针比变量p指向的对象啊的数据类型,不是p的类型

⑤指针变量在定义时间可以初始化,这一点和普通变量一致(指针变量本质上还是变量)。

int a = 5;
int *p = &a;  //定义指针变量的同时初始化
printf("%d\n", *p); //5int b;
int *p1 = &b; //指针初始化的时候,不需要关注指向的对象有没有赋值
printf("%d\n", *p1); //随机值 
指针变量的使用
使用
  • 指针变量的赋值

    //方式1
    int a, *p;
    p = &a;    //先定义,后赋值//方式2
    int a1, *p1, *q1 = &a1;  //定义未初始化
    p1 = q1;   //变量的赋值,将q1的值赋给p1,此时p1和q1都指向了a1
    
  • 操作指针变量的值

    int a, *p, *q = &a;
    p = q;    //变量的赋值,将q的值赋给p,此时p和q都指向了aprintf("%p\n", p); //访问p的值,也就是a的地址
    
  • 操作指针变量指向的值

    int a = 6, *q = &a, b = 25;
    *q = 10;   //赋值操作,修改q指向的a的值
    printf("%d,%d\n", *q, a); //10 , 10q = &b;    //赋值操作,给指针变量p重新赋值,此时p指向b
    printf("%d,%d\n", *q, a); //25 , 10
    
指针运算符的使用
  • &:取地址符。&a是获取变量a的地址值,这个是变量指针
  • *:指针运算符、间接访问运算符,*p是指针变量p指向的对象的值。这个是指针变量。
案例

案例1

  • 需求:通过指针变量访问整型变量

  • 分析:

    在这里插入图片描述

  • 代码:

    #include <stdio.h>int main()
    {int a = 3, b = 4, *p1 = &a, *p2 = &b;printf("a=%d,b=%d\n", *p1, **p2); //a=3,b=4return 0}
    

案例2

  • 需求:声明啊a,b两个变量,使用间接存取的方式实现数据的交换。

  • 分析:

    在这里插入图片描述

  • 指针指向改变,指向对象的值不变

    int main()
    {int a = 3, b = 4, *p_a = &a, *p_b = &b, *p_t;printf("交换前:%d,%d,%d,%d\n",*p_a,*p_b,a,b);  //交换前:3,4,3,4//交换指针指向p_t = p_a;p_a = p_b;  //p_a指向b  ---  4p_b = p_t; // p_b指向a  ---  3printf("交换后:%d,%d,%d,%d\n",*p_a, *p_b, a, b); //交换后:4,3,3,4return 0;
    }
    

    总结:此时改变的只是指针的指向,原始变量a,b中数据并没有发生改变。

  • 指针指向不变,指向对象的数据改变

    int main()
    {int a = 3, b = 4, *p_a = &a, *p_b = &b, temp;printf("交换前:%d,%d,%d,%d\n",*p_a,*p_b,a,b);  //交换前:3,4,3,4//交换指针指向temp = *p_a;*p_a = *p_b;  //*p_a = 3  ----->  *p_a = 4*p_b = temp; // *p_b = 4  ----->  *p_b = 3printf("交换后:%d,%d,%d,%d\n",*p_a, *p_b, a, b); //交换后:4,3,3,4return 0;

    总结:此时改变的是原始变量,原始变量a,b中数据发生了改变。

案例3

  • 需求:输入a,b两个整数,按先大后小的顺序输出a和b。

  • 分析
    在这里插入图片描述

  • 代码:指针指向改变,指向对象的值不变

    int main()
    {inta=3,b=5,*pa=&a,*pb= &b, *p t;if(a < b){p_t= p_a; // p_t指向aP_a= p_b; // p_a指向bp_b= p_t; // p_b指向a}printf("按从下到大的顺序输出a,b的值:%d > %d\n"*p_a,*p_b);return 0;
    }
    

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

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

相关文章

【Linux】第二十二章 访问网络附加内存

1. NFS的主要功能是什么&#xff1f; NFS是由Linux、UNIX及类似操作系统使用的互联网标准协议&#xff0c;主要功能就是提供网络文件共享&#xff0c;允许不同的计算机系统之间通过网络共享文件&#xff0c;它使得网络上的计算机能够像访问本地文件系统一样访问远程计算机上的…

大模型时代,Python 近红外光谱与 Transformer 模型:学习的必要性探究

在当下大语言模型盛行的时代&#xff0c;各类新技术如潮水般不断涌现&#xff0c;让人应接不暇。身处这样的浪潮之中&#xff0c;不少人心中都会泛起疑问&#xff1a;Python 近红外光谱和 Transformer 模型还有学习的必要性吗&#xff1f;今天&#xff0c;就让我们深入探讨一番…

强化学习鱼书(7)——神经网络和Q学习

代码地址 书内附代码地址 https://github.com/oreilly-japan/deep-learning-from-scratch-4 环境搭建 0.建立虚拟环境 conda create -n env_test python3.10 conda activate env_test1.安装cuda 50系的显卡只支持torch的nightlycuda12.8版本&#xff0c;别的版本会显示no k…

数据建模与分析:从回归预测到特征聚类的全面探索(PyTorch)

文章目录 简介&#xff1a;数据建模简介回归分析回归分析简介回归分析建模判定系数估计标准差住房价格回归预测 聚类聚类简介聚类分析建模植物花卉特征聚类 主成分分析&#xff08;PCA&#xff09;主成分分析简介成分分析建模地区竞争力指标降维 简介&#xff1a; 在现代数据科…

uv 包管理工具使用教程

一、简介 uv 是一个基于 Rust 实现的超快 Python 包管理工具&#xff0c;旨在加速 Python 开发流程。它是 pip、pip-tools、virtualenv 和 venv 的现代替代品&#xff0c;支持更快的包解析、安装和虚拟环境创建。 主要特性包括&#xff1a; 极快的依赖解析与安装 自动创建和…

5分钟应急响应+99%达标率:AI智能监控重塑商业清洁新标准

一、方案整体架构 面对商业综合体日均10万客流量带来的管理挑战&#xff0c;传统保洁模式在人员监管、质量评估和应急响应方面存在显著瓶颈。本系统以全场景AI监控为核心&#xff0c;构建三级智能化管理体系&#xff1a; 1. 前端感知层&#xff1a;部署800万像素广角摄像…

裸金属服务器:解锁极致性能,拒绝虚拟化开销!

什么是裸金属服务器&#xff1f; 裸金属服务器&#xff08;Bare Metal Server&#xff09;是一种介于物理服务器和云服务器之间的新型计算服务形态。它既具备传统物理服务器的性能优势&#xff0c;又拥有云服务器的灵活性和便捷管理特性。与虚拟化云服务器不同&#xff0c;裸金…

[论文精读]Ward: Provable RAG Dataset Inference via LLM Watermarks

Ward: Provable RAG Dataset Inference via LLM Watermarks [2410.03537] Ward: Provable RAG Dataset Inference via LLM Watermarks ICLR 2025 Rebuttal&#xff1a;Ward: 可证明的 RAG 数据集推理通过 LLM 水印 | OpenReview --- Ward: Provable RAG Dataset Inference v…

【ffmpeg】ffprobe基本用法

ffprobe 是 FFmpeg 工具集中的一个强大命令行工具&#xff0c;主要用于分析多媒体文件&#xff08;如视频、音频等&#xff09;的格式和内容信息。它可以提取文件的元数据、编解码器信息、流详情、帧信息等&#xff0c;而无需对文件进行转码或修改。 基本用法 ffprobe [选项] …

有哪些GIF图片转换的开源工具

以下是关于GIF图片转换的开源工具的详细总结,涵盖功能特点、适用场景及用户评价: 1. FFmpeg 功能特点: 作为开源命令行工具,FFmpeg支持视频转GIF、调整帧率、分辨率、截取片段等操作,可通过脚本批量处理。适用场景: 适合开发者或技术用户进行高效批处理,常用于服务器端自…

js不同浏览器标签页、窗口或 iframe 之间可以相互通信

一、创建一个广播通道 // 创建一个名为 vue-apps-channel 的广播通道 const channel new BroadcastChannel(vue-apps-channel);二、发送消息 channel.postMessage({type: popup, message: false}); 三、接收消息&#xff08;也需要创建广播通道&#xff09; // 也创建一个…

【算法笔记day two】滑动窗口(不定长版)

前言 hello大家好&#xff0c;本期文章紧接着上期&#xff0c;讲述滑窗的下一个大分类——不定长。 定长滑窗请看我上期文章&#xff0c;有详细介绍。温馨提醒&#xff0c;代码大部分为手搓&#xff0c;答案方法不唯一。如果想要优雅的版本可以去找其他题解&#xff0c;我的…

Node.js Express 项目现代化打包部署全指南

Node.js Express 项目现代化打包部署全指南 一、项目准备阶段 1.1 依赖管理优化 # 生产依赖安装&#xff08;示例&#xff09; npm install express mongoose dotenv compression helmet# 开发依赖安装 npm install nodemon eslint types/node --save-dev1.2 环境变量配置 /…

Linux电源管理——PSCI初始化流程和多核启动流程

目录 一、PSCI 初始化流程 1、PSCI设备树节点 2、PSCI kernel初始化流程 get_set_conduit_method set_conduit psci_probe 二、CPU PSCI 操作初始化流程 1、CPU 设备树节点 2、 struct cpu_operations 3、kernel 流程 cpu_read_bootcpu_ops smp_init_cpus 三、CPU…

【Nginx学习笔记】:Fastapi服务部署单机Nginx配置说明

服务部署单机Nginx配置说明 服务.conf配置文件&#xff1a; upstream asr_backend {server 127.0.0.1:8010; }server {listen 80;server_name your_domain.com;location / {proxy_pass http://localhost:8000;proxy_set_header Host $host;proxy_set_header X-Real-IP $remot…

nfs存储IO等待,导致k8s业务系统卡慢问题处理

注:服务器配置:64C,128G,麒麟v10系统,系统磁盘使用空间(5T)均低于50%,存储磁盘iops约为800左右 发现业务系统卡慢,使用top 命令查看.系统负载较高长期保持在60以上,发现wa值的指标参数长期高于15,返现CPU用于写入磁盘IO等待的时间较高,系统的磁盘I/O压力较大. 配合开发查看日志…

Pytorch 项目实战-1: MNIST 手写数字识别

刚接触深度学习的小伙伴们&#xff0c;是不是经常听说 MNIST 数据集和 PyTorch 框架&#xff1f;今天就带大家从零开始&#xff0c;用 PyTorch 实现 MNIST 手写数字识别&#xff0c;轻松迈出深度学习实践的第一步&#xff01; 一、MNIST 数据集&#xff1a;深度学习界的 “Hel…

大数据量下Redis分片的5种策略

随着业务规模的增长&#xff0c;单一Redis实例面临着内存容量、网络带宽和计算能力的瓶颈。 分片(Sharding)成为扩展Redis的关键策略&#xff0c;它将数据分散到多个Redis节点上&#xff0c;每个节点负责整个数据集的一个子集。 本文将分享5种Redis分片策略。 1. 取模分片(M…

CentOS 7上搭建高可用BIND9集群指南

在 CentOS 7 上搭建一个高可用的 BIND9 集群通常涉及以下几种关键技术和策略的组合&#xff1a;主从复制 (Master-Slave Replication)、负载均衡 (Load Balancing) 以及可能的浮动 IP (Floating IP) 或 Anycast。 我们将主要关注主从复制和负载均衡的实现&#xff0c;这是构成高…

LangChain4j入门AI(六)整合提示词(Prompt)

前言 提示词&#xff08;Prompt&#xff09;是用户输入给AI模型的一段文字或指令&#xff0c;用于引导模型生成特定类型的内容。通过提示词&#xff0c;用户可以告诉AI“做什么”、 “如何做”以及“输出格式”&#xff0c;从而在满足需求的同时最大程度减少无关信息的生成。有…