第四天2017/03/31(下午1:结构体、数组)

//备用知识:没有内存,哪有指针?
int main()
{//错误程序
    char *name;  //此处只定义了指针name(指针占4个字节),并没有给name分配内存
    //name = (char*)malloc(100*sizeof(char));
    cin>>name; //因此:往name中写数据,程序肯定会运行崩溃!
    cout<<name;
}
修改成下面的程序:
int main()
{//正确程序
    char *name;  //此处只定义了指针name(指针占4个字节),并没有给name分配内存
    name = (char*)malloc(100*sizeof(char));//动态给name指针分配内存
    cin>>name; //因为name已经分配完内存,可以往里边写数据
    cout<<name;
}
int main()
{//正确程序
    char name[100];//静态给name分配内存  
    cin>>name; //因为name已经分配完内存,可以往里边写数据
    cout<<name;
}
//============================================================
//知识延伸:
//cin何时能写入?结构体指针和结构体成员指针分配内存的知识、释放内存的顺序?    结构体里边的二级指针
#include <iostream>
using namespace std;
typedef struct student
{
    char **ptr; //【❤结构体里边的二级指针ptr】
    char na[100]; //给na数组静态分配了内存
    char *name; //没有给name指针分配内存
    int age;
}student;
int main()
{
    student *s1 = (student*)malloc(sizeof(student));
    cin >>s1->na;
    cout<<s1->na;

    //cin>>s1->name; //程序崩溃:【知识点】给结构体指针s1分配了内存,并不表示给结构体中的成员name也分配内存
    //cout<<s1->name;

    s1->name = (char *)malloc(100*sizeof(char)); //再给结构体中的name单独分配内存
    cin>>s1->name;
    cout<<s1->name;

//释放内存:s1指针和s1->name指针必须单独释放
//【重点:释放顺序】先释放里边的s1->name,再释放外边的s1。

    if(s1!=NULL)
    {
        free(s1);
        s1 = NULL;
    }
    if(s1->name)
    {
        free(s1->name);
        s1->name = NULL;
    }
    return 0;
}

学习模块:结构体
(1)结构体变量之间的相互赋值

①赋值运算符
②memcpy(&s1,&s2,sizeof(s2));
③逐个元素的手动copy

(2)结构体作函数参数的两种case

①用结构体变量作形参
②用结构体指针、引用作形参
【二者有天壤之别】
区别一:(在被调函数中修改后,主调函数的结构体中的内容是否也被修改)①是传值调用②传址调用,改变了指针变量指向的内容的值
区别二:(效率问题)①发生赋值运算符之间的拷贝,消耗更大的,不建议使用;应用②更好。

例:
这里写图片描述

(3)结构体中有二级指针、结构体数组相结合的代码

【注】结构体中的二级指针一般用“二级指针的内存模型三”

#include <iostream>
using namespace std;

typedef struct person
{
    char *pname;
    //char *name[100];
    int age;
    char **ptr;  //二级指针
}person;

                                //函数接口的声明
person* create_PArr1(int N,int num); //创建结构体数组
int create_PArr2(person **per,int N,int num);

int initPerArr(person* per,int N,int num);  //初始化结构体数组
int PrintPerArr(person* per,int N,int num); //打印结构体数组

void free_person1(person *p,int N,int num);  //释放结构体数组中分配的指针
void free_person2(person **p,int N,int num); 

int main()
{
    int rev = 0; //0表示程序运行正确

    int N = 3;
    int num = 2;
    person *per = NULL;

//创建结构体数组方式一:通过返回结构体指针
    //per = create_PArr1(N,num);  //动态创建结构体数组(3个元素),每个元素中的二级指针指向2个字符串
    //if(per==NULL)
    //{
    //  goto END;
    //}
//创建结构体数组方式二:通过实参是结构体一级指针的地址,形参是结构体二级指针
    rev = create_PArr2(&per,N,num);  
    if(rev!=0)
    {
        goto END; //用goto语句避免内存泄漏
    }
    //初始化结构体数组
    rev = initPerArr(per,N,num);
    if(rev!=0)
    {
        goto END; //用goto语句避免内存泄漏
    }
    //打印出结构体数组
    rev = PrintPerArr(per,N,num);
    if(rev != 0)
    {
        goto END; //用goto语句避免内存泄漏
    }

END:
    //free_person1(per,N,num);  //此种方式会导致野指针
    //cout<<"释放完毕,程序结束!"<<endl;
    free_person2(&per,N,num);  //传入per的地址&per,可以在被调函数中修改per的值,即 per = NULL;
    cout<<"释放完毕,程序结束!"<<endl;
    getchar();
}


//==========================================================================================================
//创建结构体数组方式一:(在被调函数中分配内存,通过return把在被调函数中分配的结构体数组的指针返回到主调函数中)
person* create_PArr1(int N,int num) //创建结构体数组,数组中有N个结构体元素
{
    person* per = (person*)malloc(N*sizeof(person)); //类似于int* array = (int*)malloc(N*sizeof(int));
    //动态分配内存创建数组person per[N];
    if(per==NULL)
    {
        cout<<"创建数组失败"<<endl;
        return NULL;
    }
    //给结构体数组中的成员分配内存空间
    for(int i=0;i<N;i++)
    {
        //给结构体数组中的一级指针成员pname分配内存空间
        per[i].pname = (char*)malloc(100*sizeof(char)); 
        if(per[i].pname==NULL)
            return NULL;

        //给结构体数组中的二级指针成员ptr分配内存空间
        per[i].ptr   = (char**)malloc(num*sizeof(char*)); //num表示二级指针指向一级指针的个数
        if(per[i].ptr==NULL)
            return NULL;
        for(int j=0;j<num;j++)  //每个per[i]中的ptr指针指向一级指针的个数,per[i].ptr[num]
        {
            per[i].ptr[j] = (char*)malloc(100*sizeof(char));
            if(per[i].ptr[j]==NULL)
                return NULL;
        }
    }
    return per; //在堆中分配的内存可以返回到main函数
}

//创建结构体数组方式二:(通过二级指针作为形参,一级指针作为实参)
int create_PArr2(person **per,int N,int num) //创建结构体数组,数组中有N个结构体元素
{
    if(per==NULL)  //如果传入的地址是无效值
        return -1;

    person *ptemp = NULL;  //定义一个临时的结构体指针
    ptemp = (person *)malloc(N*sizeof(person));  //给ptemp指针分配内存,让ptemp指向一个动态分配的结构体数组,相当于ptemp[N]
    if(ptemp==NULL)
        return -1;
    for(int i=0;i<N;i++)
    {
        ptemp[i].pname = (char *)malloc(100*sizeof(char));
        if(ptemp[i].pname==NULL)
            return -1;

        ptemp[i].ptr = (char **)malloc(num*sizeof(char*));
        if(ptemp[i].ptr==NULL)
            return -1;
        for(int j=0;j<num;j++)
        {
            ptemp[i].ptr[j] = (char*)malloc(100*sizeof(char));
            if(ptemp[i].ptr[j]==NULL)
                return -1;
        }
    }

    *per = ptemp;  //方式二:在被调函数中定义一个临时的变量ptemp,给ptemp分配完空间后,在把ptemp赋值给形参*per即可
    //主调函数传入的是地址
    return 0;
}
//==========================================================================================================

int initPerArr(person* per,int N,int num)  //传入的是一级指针
{
    if(per==NULL)  //如果传入的指针per是无效值
        return -1;
    for(int i=0;i<N;i++)
    {
        per[i].age  = 20+i;
        sprintf(per[i].pname,"pname = %d",20+i); 
        for(int j=0;j<num;j++)
        {
            sprintf(per[i].ptr[j],"pname = %d , %d",20+i,j);
        }
    }
    return 0;
}
int PrintPerArr(person* per,int N,int num)
{
    if(per==NULL)  //如果传入的指针per是无效值
        return -1;
    for(int i=0;i<N;i++)
    {
        per[i].age  = 20+i;
        cout<<per[i].age<<" , "<<per[i].pname<<" , {";
        for(int j=0;j<num;j++)
        {
            cout<<per[i].ptr[j]<<"  ";
        }
        cout<<"}"<<endl;
    }
    return 0;
}

//==========================================================================================================
//内存释放方式一:这种方法在进行释放完成后,(因为是值传递,所以)无法把主调函数中的传入的实参设置为NULL
void free_person1(person *p,int N,int num) //释放N个结构体指针
{
    for(int j=0;j<N;j++)
    {
        if(p[j].pname!=NULL)
        {
            free(p[j].pname);
            p[j].pname = NULL;
        }


        if(p[j].ptr!=NULL)
        {
            for(int i=0;i<num && p[j].ptr[i]!=NULL;i++)
            {
                free(p[j].ptr[i]);
                p[j].ptr[i] = NULL;
            }
        }
        free(p[j].ptr);
        p[j].ptr = NULL;
    }

}

void free_person2(person **p,int N,int num) //因为通过方式二在进行释放完后,可以顺便把实参的值也设置为NULL,避免野指针的发生
{
    for(int j=0;j<N;j++)
    {
        if((*p)[j].pname!=NULL)
        {
            free((*p)[j].pname);
            (*p)[j].pname = NULL;
        }


        if((*p)[j].ptr!=NULL)
        {
            for(int i=0;i<num && (*p)[j].ptr[i]!=NULL;i++)
            {
                free((*p)[j].ptr[i]);
                (*p)[j].ptr[i] = NULL;
            }
        }
        free((*p)[j].ptr);
        (*p)[j].ptr = NULL;
    }
    //方式二的优点
    p = NULL;   //因为传进去的实参被释放后全都没有了,为了避免野指针的发生,在释放完所有的(*p)指向的内容
    //后,把指针p也设置为NULL
}
//==========================================================================================================

(4)【❤重要】

【浅拷贝】
浅拷贝发生的情形:当结构体中有指针类型的数据元素时,如果使用默认的拷贝构造,会发生浅拷贝。
【带来的问题】在进行指针释放时,释放两次内存造成程序运行崩溃。
这里写图片描述
【深拷贝】
可以消除默认拷贝构造带来的浅拷贝情形,使程序正常运行。

【例】
#include <iostream>
using namespace std;

typedef struct person
{
    char *pname;
    int age;
}person;

person* creat_person()
{
    person *p = NULL;
    p = (person*)malloc(sizeof(person));
    if(p==NULL)
        return NULL;

    p->pname = (char*)malloc(sizeof(char)*100);
    if(p->pname==NULL)
        return NULL;

    return p;
}
void init_person(person* p)
{
    p->age = 25;
    //p->pname = "Mr.right"; //【注】在进行字符串初始化操作时,用strcpy,不要用等号运算符
    strcpy(p->pname,"Mr.right");
}
void print_person(person* p)
{
    cout<<p->age<<"   "<<p->pname<<endl;
}
void free_person(person* p)
{
    if(p!=NULL)
    {
        if(p->pname!=NULL)
        {
            free(p->pname);
            p->pname = NULL;
        }
        p = NULL;
    }
}
void copy_person(person* from,person* to)
{
    memcpy(to,from,sizeof(struct person));//从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中

    //深拷贝
    to->pname = (char*)malloc(sizeof(char)*100); //给to->pname分配额外的空间
    strcpy(to->pname,from->pname);
}
int main()
{
    person *p1 = creat_person();  //p1、p2是指针变量,而不是person结构体对象
    person *p2 = creat_person();  //*p1、*p2是person结构体对象
    init_person(p1);
    init_person(p2);
    print_person(p1);
    print_person(p2);

    //(*p2) = (*p1);  //结构体对象赋值(【注】浅拷贝)
    //p2 = p1;  //此句话表示:结构体对象赋值,而不是指针赋值

    copy_person(p1,p2);//【注】深拷贝

    print_person(p1);
    print_person(p2);
    free_person(p1);   
    free_person(p2);  //浅拷贝导致程序崩溃

    getchar();
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值