10:变量作用域、指针

来源:互联网 发布:奇葩秀视频直播间源码 编辑:程序博客网 时间:2024/05/16 09:22
【变量的作用域】
程序中一个变量只能被某些语句使用,这些语句叫做这个变量的作用域
声明在函数里面的变量叫做/* 局部变量 */,局部变量的作用域包含函数里面的所有语句
声明在所有函数外边的变量叫做/* 全局变量 */,全局变量的作用域包含程序里的所有语句
没有初始化的全局变量自动被初始化为 0 
全局变量和局部变量可以重名,这个变量名称优先代表局部变量
如果全局变量和局部变量都可以满足程序,要求应该/* 优先使用局部变量 */
存储区的使用不受作用域限制(可以跨函数使用存储区)
存储区的使用受到生命周期的限制
生命周期是一段时间,存储区只能在这段时间里才能使用
全局变量的生命周期是整个程序的执行时间
局部变量的生命周期是函数执行的某一次时间范围
如果一个函数多次执行则局部变量每次代表存储区都不同
使用 static 关键字可以声明 /* 静态变量 */
静态变量的声明周期一定是整个程序的执行时间
只要程序没有结束,则静态变量的存储区随时可以使用
静态局部变量在函数每次执行的时候对应的存储区都是同一个
没有初始化的静态变量,自动被初始化为 0 
静态变量的初始化只在程序开始的时候执行 1 次
静态全局变量的作用域只包含了声明它的文件里面的所有语句(不可以跨文件使用静态全局变量)

【指针】
指针变量用来记录存储区的地址
只有记录有效地址的指针变量才可以使用
    int num = 0;
    int *p_num;
    p_num = #
    *p_num = 10;    //num结果为10
在有效指针变量名称前使用 * 操作符可以表示地址对应的存储区
指针也分类型,不同类型的指针适合与不同的存储区捆绑
可以在一条语句中声明多个同类型指针变量,这个时候要在每个指针变量名称前单独加 * 
没有记录有效地址的指针分成两类:
1. /* 空指针 */里面记录空地址( NULL 表示,这个地址的数值就是数字 0)
2. /* 野指针 */其他所有无效指针的统称
程序员必须保证:/* 程序里不出现野指针 */
所有/* 指针变量必须初始化 */

初始化的时候 * 没有参与赋值过程,被修改的是指针变量本身的存储区

【练习】

指针捆绑三个变量,从键盘获得3个整数,按照从小到大的顺序打印,使用指针
<思想>
三个分支分别判断 12 13 23 进行三次对比交换
/*代码*///方法1:用捆绑指针改变变量存储区的值,3次对比和交换实现#include <stdio.h>int main(){       int num = 0, num1 = 0, num2 = 0, tmp = 0;    int *p_num = &num, *p_num1 = &num1, *p_num2 = &num2;    printf("请输入三个整数:");    scanf("%d%d%d", p_num, p_num1, p_num2);    if(*p_num > *p_num1){        tmp = *p_num;        *p_num = *p_num1;        *p_num1 = tmp;    }       if(*p_num > *p_num2){        tmp = *p_num;        *p_num = *p_num2;        *p_num2 = tmp;    }       if(*p_num1 > *p_num2){        tmp = *p_num1;        *p_num1 = *p_num2;        *p_num2 = tmp;    }       printf("从小到大排列:%d %d %d\n", num, num1, num2);    return 0;}//方法2:3次对比值的大小,交换捆绑的指针进行按大小排列#include <stdio.h>int main(){       int num = 0, num1 = 0, num2 = 0, tmp = 0;    int *p_num = &num, *p_num1 = &num1, *p_num2 = &num2, *p_tmp = &tmp;    printf("请输入三个整数:");    scanf("%d%d%d", p_num, p_num1, p_num2);    if(*p_num > *p_num1){        p_tmp = p_num;        p_num = p_num1;        p_num1 = p_tmp;    }           if(*p_num > *p_num2){        p_tmp = p_num;        p_num = p_num2;        p_num2 = p_tmp;    }           if(*p_num1 > *p_num2){        p_tmp = p_num1;        p_num1 = p_num2;        p_num2 = p_tmp;    }           printf("从小到大排列:%d %d %d\n", *p_num, *p_num1, *p_num2);    return 0;}

指针和存储区的捆绑关系可以随着程序的执行不断变化
这个时候可以把指针看作是存储区的某种身份,利用指针可以实现针对身份编程
如果指针和数组中第一个存储区捆绑,就可以用这个指针表示数组里的每个存储区
这个时候就认为指针间接捆绑了数组里的每个存储区
在这个指针后边使用下标就可以表示数组里的存储区了
    int arr[] = {1, 2, 3, 4, 5};
    int *p_num = arr;
    for(...)
        arr[num]
        p_num[num]  //指针地址名 [下标]
地址数据可以参与如下计算过程:
1. 地址 + 整数
2. 地址 - 整数
3. 地址 - 地址
地址数据+-整数n:实际上加减的并不是n,而是n个捆绑存储区大小
地址-地址:得到一个整数,这个整数表示两个地址之间捆绑的存储区个数

数组里第一个存储区的地址+下标,可以得到下标对应存储区的地址
可以采用如下方法表示数组里的存储区
    *(arr + num) //数组名+num:下标为num的存储区
    *(p_num + num) //指针名+num:下标为num的存储区

所有跨函数使用存储区都是通过指针实现的:
1. 被调用函数把一个存储区的地址作为返回值使用,可以让调用函数使用它的存储区
被调用函数需要提供一个指针类型存储区,记录作为返回值的地址
不可以把局部变量的地址作为返回值使用"静态局部变量生命周期为整个程序"

    int * read(void){    //被调用函数,返回指针类型(地址)        static int num = 0; //static可以让局部变量在函数中返回其地址        printf("输入一个数:");        scanf("%d", &num);        return #    //对应函数返回值类型    }

2. 数组做形式参数的时候,真正的形式参数就是一个指针,这个时候可以让被调用函数使用调用函数分配的存储区
    //第一种写法:指针做形式参数    #include <stdio.h>    void print(int *p_num, int size){        int num  = 0;        for(num = 0; num < size; num++){            printf("%d ", *(p_num + num));        }           printf("\n");    }    //第二种写法:指针做循环变量 【牢记】    void print(int *p_num, int size){        int *p_tmp = NULL;        for(p_tmp = p_num; p_tmp < p_num + size; p_tmp++){            printf("%d ", *p_tmp);        }           printf("\n");    }    int main()    {        int arr[] = {1, 2, 3, 4, 5};         print(arr, 5);         return 0;    }    //两种方法打印结果均为:1 2 3 4 5

【练习】
编写函数把主函数里两个变量的内容进行交换
<思想>
指针形参,地址做实参,三角交换法
/*代码*/#include <stdio.h>//方法1:整数变量做临时变量void swap1(int *p_a, int *p_b){    int tmp = 0;    tmp = *p_a;    *p_a = *p_b;    *p_b = tmp;}//方法2:指针变量做临时变量,被调用函数才能打印void swap2(int *p_a, int *p_b){    int *tmp = NULL;    tmp = p_a;    p_a = p_b;    p_b = tmp;    printf("swap2:a=%d,b=%d\n", *p_a, *p_b);}int main(){    int a = 3, b = 8;    printf("a=3,b=8交换后:\n");    swap2(&a, &b);    swap1(&a, &b);    printf("swap1:a=%d,b=%d\n", a, b);     return 0;}

【练习】
编写函数从一个数组里找到最大数字,所在存储区并把它传递给调用函数
<思想>
指针变量,循环时每次使用指针绑定最大值的地址
/*代码*/#include <stdio.h>int * Max(int *p_num, int size){    int *p_tmp = NULL;    int *p_max = NULL;    for(p_tmp = p_num; p_tmp < p_num + size; p_tmp++){        if(!p_max || *p_tmp > *p_max) //!p_max处理第一个数字            p_max = p_tmp; //存储区捆绑地址,p_max每次放最大值地址    }       return p_max;}int main(){    int arr[5] = {0};    int i = 0;    printf("请输入5个数字,自动找到最大值:\n");    for(i = 0; i < 5; i++){        scanf("%d", &arr[i]);    }       int *p_num = Max(arr, 5);     printf("最大值是:%d\n", *p_num);    return 0;}

声明指针变量时可以使用 "const" 关键字
0. 什么时候需要 const 关键字:
如果函数不会修改指针形式参数捆绑存储区的内容,就应该在声明指针形式参数的时候,在类型名称前加 const 关键字
1. 如果 const 关键字写在类型名称前: 
   表示:不可以通过指针对捆绑存储区做赋值,但可以对指针本身做赋值
    int num = 0;    const int *p_num = #    //*p_num = 10; 错误! 因为不可以对常量做赋值    p_num = NULL; //正确

2. 如果 const 关键字写在指针变量名称前:
   表示:不可以对指针本身做赋值,但可以对指针捆绑的存储区做赋值
    int num = 0;    int * const p_num = #    *p_num = 10; //正确    //p_num = NULL; 错误! 因为不可以对指针本身赋值

声明指针的时候,可以使用 void 作为类型名称,这种指针叫"无类型指针"
    void *p_num = &num; //无法判断num的数据类型
这种指针可以和任意类型的存储区捆绑
不能通过这种指针本身知道捆绑存储区的类型
在程序中不应该在无类型指针前使用 * 操作符,也不应该用这种指针+-整数
这种指针使用前必须强制类型转化成有类型指针
    char ch = 'q';    int num = 10;    float fnum = 3.4f;    void *p_v = NULL; //无类型指针    p_v = &ch;    printf("%c\n", *(char *)p_v); //强制类型转换    p_v = #    printf("%d\n", *(int *)p_v); //强制类型转换    p_v = &fnum;    printf("%g\n", *(float *)p_v); //强制类型转换

无类型指针应用:
通常"作为形式参数使用",可以利用它们把任意类型的存储区从调用函数传递给被调用函数


0 0
原创粉丝点击