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

内容提要

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

函数

变量的作用域

引入问题

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

变量作用域

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

变量的分类

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

  • 全局变量

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

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

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

    • 举例:

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

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

    举例:

    c 复制代码
    //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.违反了"高内聚,低耦合"的程序设计原则。

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

作用域举例

注意:

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

c 复制代码
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 就近原则
}

变量的生命周期

定义

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

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

变量的存储类型

语法:

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

存储类型:

  • auto

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

    c 复制代码
    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

    c 复制代码
    #include "demo01.h"
    
    int fun_a = 10;
    int fun1(){..}

    demo02.c

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

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

    c 复制代码
    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'

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

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

  • 例如

    c 复制代码
    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) 32Bit 8位(0x0804A000)
64位系统 8字节(long) 48Bit 12位(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)
c 复制代码
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语言中对内存数据(变量、数据元素等)的存取有两种方式:

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

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

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

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

案例

c 复制代码
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;
}
指针变量的定义
语法:
c 复制代码
数据类型 *变量列表;

举例:

c 复制代码
int a; // 普通变量,拥有真实的数据存储空间
//正确写法
int *p1;
int *p2;
int *p3,*p4;

//错误写法
int* p_5,p_6;

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

注意:

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

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

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

c 复制代码
#include <stdio.h>
int main()
{
    int i=5,*p;
    p=&i; //将i的地址赋值给指针变量p
    printf("%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,10

    return 0;
}

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

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

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

c 复制代码
int a = 5;
int *p = &a;  //定义指针变量的同时初始化
printf("%d\n", *p); //5

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

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

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

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

案例1

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

  • 分析:

  • 代码:

    c 复制代码
    #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=4
        
        return 0;
    }

案例2

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

  • 分析:

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

    c 复制代码
    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  ---  4
        p_b = p_t; // p_b指向a  ---  3
        
        printf("交换后:%d,%d,%d,%d\n",*p_a, *p_b, a, b); //交换后:4,3,3,4
        
        return 0;
    }

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

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

    c 复制代码
    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 = 3
        
        printf("交换后:%d,%d,%d,%d\n",*p_a, *p_b, a, b); //交换后:4,3,3,4
        
        return 0;

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

案例3

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

  • 分析

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

    c 复制代码
    int main()
    {
        inta=3,b=5,*pa=&a,*pb= &b, *p t;
        if(a < b)
        {
            p_t= p_a; // p_t指向a
            P_a= p_b; // p_a指向b
            p_b= p_t; // p_b指向a
        }
            printf("按从下到大的顺序输出a,b的值:%d > %d\n",*p_a,*p_b);
    
        return 0;
    }
相关推荐
noipp几秒前
推荐题目:洛谷 P10907 [蓝桥杯 2024 国 B] 蚂蚁开会
c语言·c++·算法·编程·洛谷
努力小周2 小时前
STM32智能安防系统
c语言·stm32·单片机·嵌入式硬件·物联网·计算机网络·pcb工艺
袁小皮皮不皮3 小时前
1.HCIP BFD 学习笔记(优化版)
服务器·网络·笔记·网络协议·学习·智能路由器·ip
装不满的克莱因瓶3 小时前
【自动驾驶领域】学习 Cityscapes 数据集——城市街景语义理解的标准基准
人工智能·pytorch·python·深度学习·学习·机器学习·自动驾驶
清辞8534 小时前
产品经理需求推进流程
大数据·深度学习·学习·产品经理
YM52e5 小时前
鸿蒙PC ArkTS 声明合并问题深度解析与最佳实践
学习·华为·harmonyos·鸿蒙·鸿蒙系统
x138702859575 小时前
c语言中srtlen(指针使用计算字符长度)、传值和传址调用
c语言·开发语言·算法·visual studio
海兰5 小时前
【实用程序】电商销售分析仪表盘 — 从零搭建一个AI参与的全栈数据洞察系统
人工智能·学习·算法
ken22326 小时前
在 Libreoffice Calc中输入自定义表情字符时,需要保存之后,才能正常显示
学习
zwenqiyu6 小时前
P5283 [十二省联考 2019] 异或粽子题解
c++·学习·算法