1.从堆上分配对象数组的时候,只能调用默认的构造函数。
Student* students=new Student[10];
不能是Student* students=new Student[10](.....);
2.拷贝构造函数:
作用是让一个对象去构造另一个对象,也就是说,用一个对象去初始化一个新构造的对象。
Student s1("Lawliet");
Student s2=s1; // 这里相当于s2(s1),将调用s2的拷贝构造函数,参数是s1对象
class Student{ public: Student(){} Student(Student & s){ // 拷贝构造函数首先是一个构造函数,只不过它的参数是一个对象的引用 strcpy(name,s.name); // 实现拷贝 name[sizeof(name)-1]=‘\0’; // 完善字符串的拷贝,后面加上\0 } ~Student(){} private: char name[20]; } void fn(Student s){ // 注意:当对象s1作为参数传递的时候,这时候是要调用拷贝构造函数来实现对象的复制的(s1_1) 而这个副本的生命周期只是在调用函数内部。 // 也就是说,这里调用拷贝构造函数得到一个对象副本s1_1,副本在fn()函数结束的时候析构。 } int main(){
Student s1("Lawliet");
Student s2=s1;
cout << s2.name<<endl;
fn(s1);
} // 而s1,s2 是在main()即程序结束的时候才进行析构,当然析构的顺序是和构造的顺序相反的。
print:
Lawliet
|
3.什么是浅拷贝?什么是深拷贝?
浅拷贝:只拷贝引用,不拷贝内存
深拷贝:既拷贝引用,又拷贝内存
举例:
#include <iostream> using namespace std; class Person{ public: Person(char * pN){ pName=new char[strlen(pN)+1]; if(pName!=0){ // 说明已经申请到了一块堆空间 strcpy(pName,pN); // 只拷贝了指针,pN } } // 没有拷贝构造函数 ~Person(){ pName[0]='\0'; delete pName; } protected: char* pName; } int main(){ Person p1(lawliet); Person p2=p1; // 即 p2(p1),调用构造函数,但是只实现了指针的拷贝,也就是所谓的“对象的引用”的拷贝。 } |
#include <iostream>
using namespace std; class Person{ public: Person(char * pN){ pName=new char[strlen(pN)+1]; if(pName!=0){ // 说明已经申请到了一块堆空间 strcpy(pName,pN); // 只拷贝了指针,pN } }
// 有拷贝构造函数
Person(Person & p){ /
pName=new char[strlen(p.pName)+1];
if(pName!=0){
strcpy(pName,p.pName); // 也拷贝了内存
}
}
~Person(){ pName[0]='\0'; delete pName; } protected: char* pName; } int main(){ Person p1(lawliet); Person p2=p1; // 即 p2(p1),调用构造函数,但是只实现了指针的拷贝,也就是所谓的“对象的引用”的拷贝。 } |
结果是p1,p2同时指向了一个堆对象,那么在析构的时候,先析构p2,然后归还堆对象,再析构s1,在delete的时候出错,因为已经没有东西可以delete了(堆对象已经被p2delete 过了)。这就是悬挂指针问题 | 这样的深拷贝就避免了出现悬挂指针的问题。 经验总结:如果你的类需要析构函数来析构资源,那么它也需要一个拷贝构造函数。 |
4.临时对象,无名对象
临时对象:
在函数调用的时候返回一个局部对象,它的生命周期只限于函数,函数调用完之后就析构了,那么最好不要再赋值给引用。
例如:
Student fn(){
Student s1("Lawliet");
return s1; // 这里的s1是局部对象,只存活在函数内部。一般不要作为返回值。
// 另外,返回的时候不是返回s1, 而是s1的一个副本。
}
Student& fn2(){
Student s2("Lawliet");
return s2;
}
int main(){
Student s;
s=fn(); // fn(),返回一个局部对象值给s,这一句语句执行完之后,s1的对象副本析构。
Student& ss
ss=fn2(); // fn2(),返回一个局部对象的引用给ss,意思是ss作为局部对象的一个别名
但是这一句语句执行完之后,s2的对象副本析构,那么ss也是“不存在”的。
}
无名对象:
没什么可说的,就是一个对象不需要给予它名字的时候可以直接只生成对象。
例如:
Student("Lawliet"); 而不是Student s("Lawliet");
5.构造函数用于类型转换。
当调用函数的时候,如果没有相应的参数类型匹配,那么编译器会尽量通过寻找多个函数进行类型转换来匹配该函数调用。
当然这里要注意两点:
一是这个函数调用只可以有一个参数;
二是必须避免二义性。
举例:
class Student{
Student(char *){
}
}
void fn(Student& s){}
int main(){
fn("Lawliet"); // fn()没有字符串参数匹配,编译器尝试对所有函数进行类型转换试探,包括构造函数。
最后可知道,可以先用“Lawliet”来调用Student(char*)构造对象,在把这个对象传给fn(),那么就可以转换成功了
}
|