zoukankan      html  css  js  c++  java
  • 数据结构笔记2(c++)_跨函数使用内存的问题

    预备知识

    1.所有的指针变量只占4个子节  用第一个字节的地址表示整个变量的地址

    //1.cpp 所有的指针变量只占4个子节  用第一个字节的地址表示整个变量的地址
    # include <stdio.h>
    
    int main(void)
    {
        double * p;
        double x = 66.6;
    
        p = &x;    //x占8个字节 1个字节是8位, 1个字节一个地址, p中只存放一个地址,通常是第一个字节的地址
                // 一个变量占好多好多字节,但是我们通常用一个字节的地址来表示整体的地址,即首地址来表示它
    
        double arr[3] = {1.1, 2.2, 3.3};
        double * q;
        q = &arr[0];
        printf("%p
    ", q);  //%p实际就是以十六进制输出
    //[Out]:
    //0019FF1C
    //0019FF24
        q = &arr[1];
        printf("%p
    ", q);    // 一个指针变量,无论它指向的变量占多少个字节,它本身的大小都是一样的,只占4个字节
            
        return 0;
    }
    View Code

    2.如何通过函数修改实参的值

    //3.cpp 如何通过函数修改实参的值
    # include <stdio.h>
    
    //void f(int *q);
    void f(int **q);
    
    int main(void)
    {
        int i = 9;
        int * p = &i;    //等价于int *p; p = &i;
    
        printf("%p
    ", p);
        f(&p);    // 无论参数是什么样的变量,要改变它的值,只需要改变它的地址即可
        printf("%p
    ", p);
    
        return 0;
    
    }
    
    //void f(int * q)
    //{
    //    q = (int *)0xFFFFFFFF;
    //}
    //[Out]:
    //0019FF3C
    //0019FF3C 并未改变主函数的值,因为没有修改它的地址
    
    void f(int ** q)
    {
        *q = (int *)0xFFFFFFFF;
    }
    //0019FF3C
    //FFFFFFFF
    //以上写法程序不安全,不能写成**的方式来更改main函数中的值
    View Code

     一、结构体

      1.为什么会出现结构体

        为了表示一些复杂的数据,而普通的基本类型变量无法满足要求(实际上就是java/python中的类)

        如: 仅仅用int age; str name 无法区分表示多个学生的整体特征

        结构体中没有方法,模拟一个事物模拟得不是很彻底;但好处在于,编写程序时,是以算法为核心的。

        学数据结构,算法在c语言/面向过程的语言中,是最好的,但是在java这种面向对象的语言中,算法就不是特别核心了。

      2.什么叫结构体

        结构体是用户根据实际需要,自己定义的复合数据类型

      3.如何使用结构体

    //struct_1.cpp 结构体的使用概述
    # include <stdio.h>
    # include <string.h>
    
    struct Student    //结构体是数据类型,而不是变量,定义变量一定要分配内存,
                    //struct Student是一个整体,是一个模型,有三个成员(不是属性,java/python中才叫属性);但是定义结构体并没有分配内存,只是定义了一个新的数据类型
    {
        int sid;
        char name[200];
        int age;
    
    };    //分号不能省
    
    int main(void)
    {
        struct Student st = {1000, "zhangsan", 20};    //用struct Student 这个数据类型,定义了一个变量st,赋值;struct Student一定要整体写
        printf("%d %s %d
    ", st.sid, st.name, st.age);    // 第一次编写时一定要编译,不然VC6不会提示,编译了一次以后就可以提示了
    //[Out]:
    //1000 zhangsan 20
        // st.sid = 99;  //error, c++中不能这样赋值,java/python才可以,必须按照上面的写法
        strcpy(st.name, "lisi"); // strcpy实际上就是复制的作用,在开头要添加上# include <string.h>
        st.age = 22;
        printf("%d %s %d
    ", st.sid, st.name, st.age);
    //[Out[
    //1000 zhangsan 20
    //1000 lisi 22
        // printf("%d %s %d
    ", st);  //error
        return 0;
    }
    View Code
    //struct_2.cpp 结构体的两种使用方式
    # include <stdio.h>
    
    int main(void)
    {
        struct Student st = {1000, "zhangsan", 20};
        //st.sid = 99;    // 如何使用结构体变量?第一种方式,很少使用,因为不会定义很多变量的名字
        struct Student * pst = &st;  // 第二种方式是最常用的: 定义一个指针变量*pst,存放struct Student这种变量地址,
                               // struct Student这个变量占多少个字节?     int sid --4 ; char name[200] --200 ; int age --4; 共计208个(理论上,实际上很可能不是) 
        //pst = &st;    //pst存放st的地址,则pst指向st了,*pst就是st了
        pst->sid = 99;    //pst->sid 等价于(*pst).sid, 没有什么为什么,是规定,只需要记住;而(*pst).sid等价于st.sid, 所以pst->sid 等价于st.sid
    
    }
    View Code

        3.1.两种方式:

          struct Student st = {1000, "zhangsan", 20};

          struct Student * pst = &st;

          3.1.1. st.sid

          3.1.2.pst->sid  

            //pst所指向的结构体变量中,有sid这个成员(最终表示的就是sid这个成员)

      4.注意事项:

        4.1.结构体变量不能加减乘除,但可以相互赋值;

        4.2.普通结构体变量和结构体指针变量作为函数传参的问题

    //struct_3.cpp 结构体变量
    # include <stdio.h>
    # include <string.h>
    
    struct Student    
    {
        int sid;
        char name[200];
        int age;
    
    };
    
    void f(struct Student * pst);    //前缀声明
    void g(struct Student st);
    void g2(struct Student * pst);
    
    int main(void)
    {    
        struct Student st;
    
        
        f(&st);
        //printf("%d %s %d
    ", st.sid, st.name, st.age);
        g(st);    // 通过一个函数实现输出,而不是直接输出。将208个字节赋值给另一个函数,这种方式速度慢,浪费空间,不推荐
        g(&st);    // 结果输出速度快很多
    
        return 0;
    }
    //[Out]:99 zhangsan 22
    
    void g(struct Student st)
    {
        printf("%d %s %d
    ", st.sid, st.name, st.age);
    }
    
    void g2(struct Student *pst)
    {
        printf("%d %s %d
    ", pst->sid, pst->name, pst->age);    // 通过指针只花了4个字节的空间,也能够实现
    
    }
    
    void f(struct Student * pst)    // 将st的地址发送给了pst,不是*pst
    {
        (*pst).sid = 99;    // 普通变量使用.来调用
        strcpy(pst->name, "zhangsan");    // 指针变量用->来调用
        pst->age = 22;
    }
    View Code

    二、动态内存的分配和释放

      1.动态构造一维数组

        假设动态构造一个int型数组

        int *p = (int *) malloc(int len);

          1.1. malloc只有一个int型的形参,表示要求系统分配的字节数

          1.2.malloc函数的功能是请求系统len个字节的内存空间,如果请求分配成功,则返回第一个字节的地址,如果分配不成功,则返回NULL

          1.3.malloc函数能且只能返回第一个字节的地址,所以我们需要把这个无实际意义的第一个字节的地址(俗称干地址)转化为一个有实际意义的地址

            因此,malloc前面必须加(数据类型*),表示把这个无实际意义的第一个地址,通过强制类型转换,来告诉编译器,我们返回的第一个类型的地址,它到底是整型地址,还是其他类型的地址,即为前面(int *)类型的含义。

            如: int *p = (int *)malloc(50):

                表示将系统分配好的50个字节的第一个字节的地址转化为整型地址,更准确的说是把第一个字节的地址转化为4个字节,这样p就指向了第一个的4个字节,

                p+1就指向了第2个的4个字节。p[0]就是第一个元素

              double *p = (double *)malloc(80);

                表示将系统分配好的80个字节的第一个字节的地址转化浮点型地址,更准确的说是把第一个字节的地址转化为8个字节,这样p就指向了第一个的8个字节,

                p+1就指向了第2个的8个字节。p+i就指向了第i+1个的8个字节。p[0]就是第一个元素

         free(p) 释放p所指向的内存,而不是释放p本身所占用的内存

    # include <stdio.h>
    # include <malloc.h>
    
    int main(void)
    {
        int a[5] = {4, 10, 2, 8, 6};
        int len;
        printf("请输入你需要分配的数组的长度: len = ");
        scanf("%d", &len);
        int *pArr = (int *)malloc(sizeof(int) *len); //分配20个字节,逻辑上的效果:pArr指向了前4个字节,pArr +1 指向后4个
                                                     // int * 强制转换,malloc只能返回第一个字节地址
    //输入4
    //[Out]:4 10                                                 // 不需要写死,可以动态的分配内存malloc(sizeof(int) *len)
    
    //    *pArr = 4; //类似于 a[0]=4;pArr指向前4个字节
    //    pArr[1] = 10; //类似于a[1]=10;
    //    printf("%d %d", *pArr, pArr[1]);
    
        // 我们可以把pArr当做一个普通数组来使用
        for (int i=0; i<len; ++i)    //退出for循环 int就没有了
            scanf("%d", &pArr[i]);
    
        for (i=0; i<len; ++i)        //退出for循环 不能再加int, i还能用 要不然就重复定义了
            printf("%d
    ", *(pArr+i));
    
        free(pArr);    //程序运行中可以释放,把pArr所代表的动态分配的20个字节的内存释放
    //输入4
    //[Out]:
    // 1 2 3 4
    // 1
    // 2
    // 3
    // 4
    
        return 0;
    }
    View Code

      2.跨函数使用内存:只能通过动态来实现

    //test1、下程序中,能够通过调用函数fun,使main函数中的指针变量p指向一个合法的整型单元的是:        //整型单元-》合法变量
    //A)
    main()
    {
        int *p;        //主函数的指针变量p,此时为p中为一个垃圾数字
        fun(p);        //通过调用p,使它指向一个合法的整型变量,但是直接调用p,是无法改变数值的 // 参考笔记1-> 如何通过被调函数,修改主调函数中普通变量的值: 1)实参为相关变量的地址:&i
        ...
    }
    int fun(int *p)
    {
        int s;
        p=&s;
    }
    
    //B)
    main()
    {
        int *p;
        fun(&p);    // 参考笔记1-> 如何通过被调函数,修改主调函数中普通变量的值: 1)实参为相关变量的地址:&i
        ...
    }
    int fun(int **q)    // 此时*q即为p,将p的地址发送给q,类型一致 参考笔记1-> 如何通过被调函数,修改主调函数中普通变量的值: 2)形参为以该变量的类型为类型的指针变量:*p
    {
        int s;
        *q=&s;    // 即为p=&s;参考笔记1-> 如何通过被调函数,修改主调函数中普通变量的值: 1)实参为相关变量的地址:p=&i
    
    }    //执行完此行代码时,p已经保存了一个合法的整型变量地址s了,但问题是执行完成后,该函数就没有了,则s也没有了,最终就是p没有指向合法的变量s了。
    
    //C)
    #include <stdlib.h>
    main()
    {
        int *p;
        fun(&p); // 添加了取地址符,正确
        ...
    }
    int fun(int **q)
    {
        *q=(int*)malloc(4);  //构造一个int型数组, 请求系统分配4个字节的内存空间,此时*q即为p,则p指向了4个字节
    
    }    //执行完此行代码时,p已经指向了4个字节,且动态分配的内存没有释放,必须手动通过free释放
    
    //D)
    #include <stdlib.h>
    main()
    {
        int *p;
        fun(p);// 未添加取地址符,错误
        ...
    }
    
    int fun(int *p)
    {
        p=(int *)malloc(sizeof(int));
    }
    View Code

        故: 通过此题,可以了解到,我们可以跨函数使用内存 

    注意:

       结构体变量不能加减乘除,但可以相互赋值

      普通结构体变量和结构体指针变量作为函数传参的问题

  • 相关阅读:
    avcodec_decode_video2少帧问题
    什么是I帧,P帧,B帧
    让Ubuntu可以压缩/解压缩RAR文件
    Linux 向文件末尾追加命令
    valgrind: failed to start tool 'memcheck' for platform 'amd64-linux': No such file or directory
    《王者之剑2》性能数据精讲
    Unity加载模块深度解析(纹理篇)
    Unity加载模块深度解析(Shader)
    Unity加载模块深度解析(网格篇)
    Unity将来时:IL2CPP是什么?
  • 原文地址:https://www.cnblogs.com/wangxue533/p/11746310.html
Copyright © 2011-2022 走看看