• C语言学习笔记-指针01


    指针是 C 语言的灵魂,现在对于指针的掌握肯定是不透彻的,然学习是一个迭代的过程,姑且写出目前自己的理解。

    指针的概念

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    #include <stdlib.h>
    #include <string.h>
    #include <stdio.h>
    
    int main()
    {
        int a = 10;
        int *p = NULL;
        
        p = (int *)malloc(sizeof(int));
        p = &a;
    
        printf("%d
    ", p);
        printf("%d
    ", *p);
    
        if(p != NULL)
        {
            free(p);
            p = NULL;
        }
    }
    

    先来看这样一段代码

    1. p 是一个指针变量,*p 是指针指向的内存空间的保存的值。
    2. &a 表示取变量 a 所在内存的地址,p = &a 表示将内存 a 所在的地址值赋值给 p,相当于指针 p 指向 a 所在的内存。
    3. 接下来分别打印 p 和 *p 来验证上面的说法
    4. 最后我们释放内存空间

    明确几个概念:

    1. 指针是一个变量
    2. 注意区分指针变量和它指向的内存块
      1. 改变指针变量的值不会影响指针指向的内存区域
      2. 改变指针指向的内存区域也不会改变指针变量的值

    内存四区

    先来看这样一段代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    #include <stdlib.h>
    #include <string.h>
    #include <stdio.h>
    
    char *get_str()
    {
        char str[] = "abcdefg";
        return str;
    }
    
    int main()
    {
    	char buf[128] = {0};
        strcpy(buf, get_str());
        printf("buf = %s
    ", buf);
        
        // char *p = NULL;
        // p = get_str();
        // printf("p = %s
    ", p);
        return 0;
    }
    

    char *p 是一个 char 类型的指针变量,看代码的意思是希望通过 get_str() 方法给指针 p 赋值。如果你运行一下这段代码会发现它是会报错的,至于为什么会报错就要分析一下内存四区了。

    内存四区图

    区域 作用
    栈区(stack) 由编译器自动分配释放,存放函数的参数值,局部变量值等。
    堆区(heap) 一般由程序员分配释放(动态内存申请与释放),若程序员不释放,程序结束时可能由操作系统回收。
    全局区(静态区) 全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量在一块区域,未被初始化的全局变量和未初始化的静态变量在相邻的另一块区域,该区域在程序结束后由操作系统释放。
    程序代码区 存放函数体的二进制代码。

    现在通过内存四区的理论来分析上面这一段代码。

    内存四区分析

    1. 声明了一个名为 buf 的变量
    2. 调用 get_str()
      1. 声明一个数组 str ,它是一个局部变量所以再栈区
      2. 字符串 “abcdef…” 是字符串常量,所以再全局区(静态区)
      3. str = “abcdef” 会将全局区的字符串常量拷贝到 str 数组内存中
    3. 紧接着 get_str() 调用结束,与之相关的内存就会被销毁
    4. strcpy() 希望将 get_str() 返回的字符串拷贝给 buf,但是因为这一块内存已经被销毁了,就会报错

    注:有时候以上的操作可能会成功,这可能是因为没有及时销毁,可以用我上面注释掉的指针操作的代码做练习,用内存四区的方式分析,赋值是一定不会成功的,原理相同。

    正确的赋值方式

    我们可以使用 malloc() 将变量保存在堆区,堆区的内存在函数执行完之后不会立即销毁,而是由我们自己销毁,或者再程序运行完之后由操作系统来销毁。

    内存四区分析2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    
    #include <stdlib.h>
    #include <string.h>
    #include <stdio.h>
    
    char *get_str2()
    {
        char *tmp = (char *)malloc(100);
        if(tmp == NULL)
        {
        return NULL;
        }
        strcpy(tmp, "adsaffa");
        return tmp;
    }
    
    int main()
    {
        char buf[128] = {0};
        // strcpy(buf, get_str());
        // printf("buf = %s
    ", buf);
        char *p = NULL;
        p = get_str2();
        if(p != NULL)
        {
            printf("p = %s
    ", p);
            free(p);
            p = NULL;
        }
        return 0;
    }
    

    使用指针的几个注意事项

    这一小节属于使用指针过程中遇到的一些问题,以及如何避免,有个故事说:人如果知道自己会死在哪里,就死也不要去那里就行了。使用指针也一样,指针使用会遇到很多的坑,知道哪里有坑就要尽可能避免。

    修改内存时要保证内存可写

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    #include <stdlib.h>
    #include <string.h>
    #include <stdio.h>
    int main()
    {
        char *p = "abcdef";
        char q[] = "fdasfafdfas";
        p[2] = "1”; // 会报错 err
        q[2] = "1";
        printf("p: %s
    ", p);
        printf("q: %s
    ", q);
        return 0;
    }
    

    修改 p[2] 会报错,因为 abcdef 是字符串常量,保存在全局区(静态区),所以它是不能被修改的。使用指针的第一个事项就是修改内存的时候保证内存可写。

    指针的步长

    指针的步长由指针指向的内存类型决定,比如说 int p 步长就是 4,chat p 步长就是 1。步长的意思就是每次 p++ 操作之后,地址移动的距离。可以分别打印 p 和 p++ 的值看一看就知道了。

    由此还想讲一个问题就是如何计算一个动态数组的大小。比如 int a[] = {1, 2, 3, 4},可以使用 sizeof(a)/sizeof(a[0])的方式计算数组大小。

    不允许 NULL 或者未知非法地址拷贝内存

    1
    2
    
    char *p =NULL;
    strcpy(p, “1232143”); // 报错
    

    strcpy() 执行的操作时将字符串拷贝给指针 p 所指向的内存,此时指针 p 指向的内存是 NULL,就是说没有执行任何内存,字符串也就没地方拷贝,自然会发生错误。

    如果我们让char* p = 0x11; 任意赋一个值,也会报错,向未知非法地址拷贝也是不被允许的。 正确的做法就是将 p 指向一个已经分配好内存的指针,比如 char *q = “fdsafas” 。

    所以当我们给一个使用 malloc() 分配内存之后应该习惯性地加一个判断,比如int *p = (int *)malloc(sizeof(int));之后应该进行判断if(p == NULL)如果为空则说明内存分配失败,就应该对异常进行处理。一直对应的,释放内存是有个也应该讲指针赋值为空free(p); p = NULL;

    通过指针间接赋值

    我们定义一个函数会有返回值,返回值可能是一个结果、可能表示一种状态。比如说int findMax(int *a, int n);在这个函数中我们出入一个数组,然后返回这个数组中最大的值。

    那现在如果我想返回这个数组的最大值、最小值该怎么办?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int fun(int *a, int n, int *max, int *min)
    {
        if(a == NULL || n <= 0)
        {
            return -1;
        }
    
        int tmp_max, tmp_min, sum = *a;
        for(int i = 1; i < n; i++)
        {
            if(a[i] < tmp_min)
            {
                tmp_min = a[i];
            }
    
            if(a[i] > tmp_max)
            {
                tmp_max = a[i];
            }
        }
    
        max = &tmp_max;
        min = &tmp_min;
    
        return 0;
    }
    
    int main()
    {
        int a[5] = {2, 6, 9, 10, 1};
        int max = 0;
        int min = 0;
    
        int res = fun(a, 5, &max, &min);
    
        if(res == -1)
        {
            perror("fun() err.");
            return -1;
        }
    
        printf("max = %d, min = %d
    ", max, min);
    
        return 0;
    }
    

    定义一个函数int fun(int *a, int n, int *max, int *min);

    1. 返回值表示成功或者失败
    2. a 是数组指针
    3. n 是数组大小
    4. max 和 int 是 int 类型的指针

    关键就在于 max 和 int 这两个指针,它们指向的两个内存分别保存数组的最大和最小值,而这两个指针是由主函数传入的,此时就完成你了最大值和最小值传递。

    通过指针间接赋值是指针很强大的一个功能,我们可以看到很多函数都是这样定义的。

  • 相关阅读:
    Navicat
    Eclipse 代码质量管理插件
    oracle sql 逻辑处理
    view视图 | 索引
    LIKE模糊查询
    启动tomcat报找不到或无法加载主类
    oracle:decode
    oracle:case when then else end
    ssh 公共秘钥
    ip 和数字之间的转换
  • 原文地址:https://www.cnblogs.com/shuiyj/p/13185160.html
Copyright © 2020-2023  润新知