//备用知识:没有内存,哪有指针?
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();
}