第14章堆与拷贝构造函数
在C++中,堆分配的概念得到了扩展,不仅C++的关键字new和delete可以分配和释放堆空间,而且通过new建立的对象要调用构造函数,通过delete删除对象也要调用析构函数。另外,当对象被传递给函数或者对象从函数返回的时候,会发生对象的拷贝。但有些情况,一模一样的拷贝并不是所希望的,这就要借助于定义拷贝构造函数了。学习本章后,应该掌握new和delete这两个操作符的使用,并能把握从堆中分配和释放对象以及对象数组的时机;领会拷贝构造函数的实质,区别浅拷贝和深拷贝,在程序中适当地运用拷贝构造函数。14.1 关于堆
C++程序的内存格局通常分为四个区
(1)全局数据区(data area);
(2)代码区(code area);
(3)栈区(stack area);
(4)堆区(即自由存储区)(heap area)。
全局变量、静态数据、常量存放在全局数据区,所有类成员函数和非成员函数代码存放在代码区,为运行函数而分配的局部变量、函数参数、返回数据、返回地址等存放在栈区,余下的空间都被作为堆区。
函数"void* malloc(size-t);"和"void free(void*);"在头文件malloc.h中声明,而操作符new和delete是C++语言的一部分,无须包含头文件。它们都从堆中分配和释放内存块,但在具体操作上两者有很大的区别。
操作堆内存时,如果分配了内存,就有责任回收它,否则运行的程序将会造成内存泄漏。这与函数中在栈区分配局部变量有本质的不同。
对C++来说,管理堆区是一件十分复杂的工作,频繁地分配和释放不同大小的堆空间,将会产生堆内碎块。
14.2 需要new和delete的原因
从C+十的立场上看,不能用malloc()函数的一个原因是,它在分配空间的时候不能调用构造函数。类对象的建立是分配空间、构造结构以及初始化的三位一体,它们统一由构造函数来完成。
例如,下面的代码用malloc()分配对象空间:
class Tdate
{
public:
Tdate();
SetDate(int m=1,int d=l,int y=1998);
protected:
int month;
int day;
int year;
};
Tdate::Tdate()
{
month=1;
day=1;
year=l;
}
voidTdate::SetDate(int m,int d, int y)
{
if(m>0 && m<13)
month=m;
if(d>O && d<32)
day=d;
if(y>0 && y<3000)
year=y;
}
void fn()
{
Tdate* pD; //仅仅是个指针,没有产生对象
pD=(Tdate*)malloc(sizeof Tdate); //并不调用构造函数
//...
free(pD); //并不调用析构函数
}
指针pD的声明不为Tdate调用其构造函数,因为pD没有指向任何东西。假如构造函数要被调用,则必须在进行内存分配的ma]loc()调用时进行。然而malloc()仅仅只是一个函数调用,它没有足够的信息来调用一个构造函数,它所接受的参数是一个unsigned long类型。 pD从malloc()那儿获得的不过是一个含有非法数据的类对象空间而已,对应的对象空间中的值不确定。为此,须在内存分配之后再进行初始化。
例如,下面的代码描述用malloc()来进行对象的创建过程:
void fn()
{
Tdate*pD;
pD=(Tdate*)malloc(sizeof Tdate);
pD->SetDate(); //设置Tdate值
//...
free(pD);
}
这从根本上说,不是一个类对象的创建,因为它绕过了构造函数。
另外,从程序设计的需要来看,在分配内存申请的时候,总是知道分配的空间派什么用,而且分配空间大小总是某个数据类型(包括类类型)的整数倍。因而C++用new代替C的malloc()是必然的。
14.3分配堆对象
C++的new和delete机制更简单易懂。例如,下面的代码可与前面的代码做一比较:void fn()
{
Tdate* pS;
pS=new Tdate; //分配堆空间并构造它
//...
deleteps; //先析构,然后将空间返还给堆
}
不必显式指出从new返回的指针类型,因为new知道要分配对象的类型是Tdate。而且new还必须知道对象的类型,因为它要藉此调用构造函数。
如果是分配局部对象,则在该局部对象退出作用域时(要么程序执行遇到函数结束标记"|",要么遇到返回语句)自动调用析构函数。但是堆对象的作用域是整个程序生命期,所以除非程序运行完毕,否则堆对象作用域不会到期。堆对象析构是在释放堆对象语句delete 执行之时。上面的堆对象在执行delete pS;语句时,C++自动调用其析构函数。
构造函数可以有参数,所以跟在new后面的类类型也可以跟参数。
例如下面的代码,new后面的类型必须跟参数:
class Tdate
{
public:
Tdate(int m,int d,int y);
protected:
int month;
int day;
int year;
};
Tdate::Tdate()
{
if(m>0 && m<13)
month=m;
if(d>0 && d<32)
day=d;
if(y>0 && y<3000)
year=y;
}
void fn()
{
Tdate* pD;
pD=new Tdate(1,1,1998);
//...
delete(pD);
}
"pD=newTdate(1,1,1998);"这一名,使new去调用了构造函数Tdate(int,int,int),new是根据参数匹配的原则来调用构造函数的。如果上一句写成:
pD=new Tdate;
则由于Tdate类没有默认构造函数(已被Tdate(int,int,int)覆盖)而使该语句报错。从堆中还可以分配对象数组。
例如,下面的代码分配了参数给定的对象个数,并在函数结束时,予以返还:
class Student
{
public:
Student(char* pName="no name")
{
strncpy(name,pName,sizeof(name));
name[sizeof(name)-1]="\0";
}
protected:
char name[40];
};
void fn(int noOfObjects)
{
Student* pS=new Student[noOfObjects];
//...
delete[]pS;
}
分配过程将激发noOfObjects次构造函数的调用,从0~noOfObjects-1。调用构造函数的顺序依次为pS[0],pS[1],pS[2],…pS[noOfObjects-1]。由于分配数组时,new的格式是类型后面跟[元素个数],不能再跟构造函数参数,所以,从堆上分配对象数组,只能调用默认的构造函数,不能调用其他任何构造函数。如果该类没有默认构造函数,则不能分配对象数组。
delete[]pS中的[]是要告诉C++,该指针指向的是一个数组。如果在[]中填上了数组的长度信息,C++编译系统将忽略,并把它作为[]对待。但如果忘了写[],则程序将会产生运行错误。
一般来说,堆空间相对其他内存空间比较空闲,随要随拿,给程序运行带来了较大的自由度。使用堆空间往往由于:
(1)直到运行时才能知道需要多少对象空间;
(2)不知道对象的生存期到底有多长;
(3)直到运行时才知道一个对象需要多少内存空间。
14.4 拷贝构造函数
可用一个对象去构造另一个对象,或者说,用另一个对象值初始化一个新构造的对象
例如:
Student S1("Jenny");
Student s2=sl; //用s1的值去初始化s2
对象作为函数参数传递时,也要涉及对象的拷贝,例如:
void fn(Student fs)
{
//...
}
void main()
{
Student ms;
fn(ms);
}
函数fn()的参数传递的方式是传值,参数类型是Student,调用时,实参ms传给了形参fs,ms在传递的过程中是不会改变的,形参fs是ms的一个拷贝。这一切是在调用的开始完成的,也就是说,形参fs用ms的值进行构造。
这时候,调用构造函数Student(char*)就不合适,新的构造函数的参数应是Student&,也就是:
Student(Student& S);
为什么C++要用上面的拷贝构造函数,而它自己不会做像下面的事呢?即:
int a:5;
intba; //用a的值拷贝给新创建的b
因为对象的类型多种多样,不像基本数据类型这么简单,有些对象还申请了系统资源,如图14-1所示,s对象拥有了一个资源,用s的值创建一个t对象,如果仅仅只是二进制内存空间上的s拷贝,那意味着t也拥有这个资源了。由于资源归属权不清,将引起资源管理的混乱。在14.6节中还要对这个问题展开讨论。
图14-1 T对象创建时拷贝S对象
下面的程序介绍了拷贝构造函数的用法:
//**********************
//** ch14_1.cpp **
//**********************
#include
#include
class Student{
public:
Student(char* pName="no name",int ssId=0)
{
strncpy(name,pName,40);
name[39]='\0';
id = ssId;
cout <<"Constructing new student " < } Student(Student& s) //拷贝构造函数 { cout<<"Constructing copy of"< strcpy(name,"copy of "); strcat(name,https://www.wendangku.net/doc/0a1867120.html,); id=s.id; } ~Student() { cout <<"Destructing " < } protected: char name[40]; int id; }; void fn(Student s) { cout <<"In function fn()\n"; } void main() { Student randy("Randy",1234); cout <<"Calling fn()\n"; fn(randy); cout <<"Returned from fn()\n"; } 运行结果为: Constructing new student Randy Calling fn() Constructing copy of Randy In function fn() Destructing copy of Randy Return from fn() Destructing Randy randy对象的创建调用了普通的构造函数,产生了第一行信息;随之便输出第二行信息;main()调用fn(randy)时,发生了从实参randy到形参s的拷贝构造,于是调用拷贝构造函数而得到第三行信息;随之就进入到fn()的函数体中,产生了第四行信息;从fn()返回时,形参s 被析构,所以产生了第五行信息;回到主函数后,输出第六行信息;最后主函数结束时,randy 对象被析构,所以产生了第七行信息。 拷贝构造函数中strcat()是将"copyof"拼接在name之前,它的头文件是string.h。通常拷贝构造函数将严格限制在只制作拷贝,但是,本程序为了要帮助大家了解其真相,在一些函数体中,设置了输出语句。 14.5 默认拷贝构造函数 类定义中,如果未提供自己的拷贝构造函数,则C++提供一个默认拷贝构造函数,就像没有提供构造函数时,C++提供默认构造函数一样。 C++提供的默认拷贝构造函数工作的方法是,完成一个成员一个成员的拷贝。如果成员是 类对象,则调用其拷贝构造函数或者默认拷贝构造函数。 例如,下面的程序中Tutor类使用了默认拷贝构造函数: //********************** //** ch14_2.cpp ** //********************** #include #include class Student{ public: Student(char* pName="no name") { cout <<"Constructing new student " < strncpy(name,pName,sizeof(name)); name[sizeof(name)-1]='\0'; } Student(Student& s) { cout <<"Constructing copy of " < strcpy(name,"copy of "); strcat(name,https://www.wendangku.net/doc/0a1867120.html,); } ~Student() { cout <<"Destructing " < } protected: char name[40]; }; class Tutor{ public: Tutor(Student& s):student(s) { cout <<"Constructing tutor\n"; } protected: Student student; }; void fn(Tutor tutor) { cout <<"In function fn()\n"; } void main() { Student randy("Randy"); Tutor tutor(randy); cout <<"Calling fn()\n"; fn(tutor); cout <<"Returned from fn()\n"; } 运行结果为: Constructing new student Randy Constructing copy of Randy Constructing tutor Calling fn() Constructing copy of copy Of Randy ln function fn() Destructing copy of copy of Randy Returned from fn() Destructing copy of Randy Destructing Randy 程序一开始运行,进入主函数,首先构造对象randy,调用Student构造函数,产生第一行信息;对象tutor是通过调用构造函数Tutor(Student&)来创建的,该构造函数通过调用Student的拷贝构造函数来初始化数据成员Tutor::Student,产生第二行信息;在执行Tutor 构造函数时,产生第三行信息;接着输出第四行;然后调用fn(),需要创建tutor的一个拷贝,因为Tutor类没有定义拷贝构造函数,所以就调用C++默认的拷贝构造函数,在拷贝成员student对象时,调用Student拷贝构造函数,结果在名字copyofRandy之前又接上了一个copy of,得到第五行输出;进入fn()函数体中,得到第六行信息;从fn()返回时,形参tutor析构,调用的是默认析构函数,当析构到成员student时,调用Student析构函数,产生第七行输出;接着在主函数,输出第八行信息;退出主函数时,先析构tutor对象,析构中调用Student析构函数,产生第九行信息;最后析构Randy对象,得到最后一行输出。 14.6 浅拷贝与深拷贝 在默认拷贝构造函数中,拷贝的策略是逐个成员依次拷贝。但是,一个类可能会拥有资源,当其构造函数分配了一个资源(例如堆内存)的时候,会发生什么呢?如果拷贝构造函数简单地制作了一个该资源的拷贝,而不对它本身分配,就得面临一个麻烦的局面:两个对象都拥有同一个资源。当对象析构时,该资源将经历两次资源返还。 例如,下面的程序描述了Person对象被简单拷贝后,面临析构时的困惑: //********************** //** ch14_3.cpp ** //********************** #include #include class Person{ public: Person(char* pN) { cout <<"Constructing " < pName=new char[strlen(pN)+1]; if(pName!=0) strcpy(pName,pN); } ~Person() { cout <<"Destructing " < pName[0]='\0'; delete pName; } protected: har* pName; }; void main() { Person p1("Randy"); Person p2=p1; //即Person p2(p1); } 运行结果为: Constructing Randy Destructing Randy Destructing Null pointer assignment 程序开始运行时,创建p1对象,p1对象的构造函数从堆中分配空间并赋给数据成员pName,同时,产生第一行输出;执行"Person p2=p1;"时,因为没有定义拷贝构造函数,于是就调用默认拷贝构造函数,使得p2与p1完全一样,并没有新分配堆空间给p2,见图14-2;主函数结束时,对象逐个析构,析构p2时,将堆中字符串清成空串,然后将堆空间返还给系统,并同时得到第二行输出;析构p1时,因为这时pName指向的是空串,所以第三行输出中显示 的只是Destructing;当执行"deletepName;"时,系统报错,显示第四行结果。 拷贝前拷贝后 图 14-2 p1~p2 的浅拷贝 创建p2时,对象p1被复制给了p2,但资源并未复制,因此,p1和p2指向同一个资源,这称为浅拷贝。 当一个对象创建时,分配了资源,这时,就需要定义自己的拷贝构造函数,使之不但拷贝成员,也拷贝资源。 例如,下面的代码是在程序chl4_3.cpp的基础上,增加一个Person类的拷贝构造函数: //********************** //** ch14_4.cpp ** //********************** #include #include class Person{ public: Person(char* pN); Person(Person& p); ~Person(); protected: char* pName; }; Person::Person(char* pN) { cout <<"Constructing " < pName=new char[strlen(pN)+1]; if(pName!=0) strcpy(pName,pN); } Person::Person(Person& p) { cout <<"Copying " < pName=new char[strlen(p.pName)+1]; if(pName!=0) strcpy(pName,p.pName); } Person::~Person() { cout <<"Destructing " < pName[0]='\0'; delete pName; } void main() { Person p1("Randy"); Person p2=p1; //即Person p2(p1); } 运行结果为: Constructing Randy Copying Randy into its own block Destructing Randy Destructing Randy 程序开始运行时,创建p1对象,产生第一行输出;然后用p1去创建P2对象,调用的是自己定义的拷贝构造函数,于是得到第二行输出;拷贝构造函数中,不但复制了对象空间,也复制资源(堆内存空间),见图14-3;当主函数退出时,先后析构p2和p1,但这时候对象们有其各自的资源,所以,析构函数工作得很好,产生最后两行输出。 拷贝前拷贝后 图14-3 p1-p2的深拷贝 创建p2时,对象p1被复制给了p2,同时资源也作了复制,因此,p1和p2指向不同的资源,这称为深拷贝。 堆内存并不是唯一需要拷贝构造函数的资源,但它是最常用的一个。打开文件,占有硬设备(例如打印机)服务等也需要深拷贝。它们也是析构函数必须返还的资源类型。因此,一个很好的经验是:如果你的类需要析构函数来析构资源,则它也需要一个拷贝构造函数。因为通常对象是自动被析构的。如果需要一个自定义的析构函数,那就意味着有额外资源要在对象被析构之前释放。此时,对象的拷贝就不是浅拷贝了。 14.7 临时对象 当函数返回一个对象时,要创建一个临时对象以存放到返回的对象。 例如,下面的代码中,返回的ms对象将产生一个临时对象: Student fn() { //... Student ms("Randy"); return ms; void main() { Student s; s=fn(): //... } 在这里,系统调用拷贝构造函数将ms拷贝到新创建的临时对象中,见图14-4。 图14-4 返回对象的函数运行结束时 一般规定,创建的临时对象,在整个创建它们的外部表达式范围内有效,否则无效。也就是说,"s=fn();"这个外部表达式,当fn()返回时产生的临时对象拷贝给。后,临时对象就析构了。 例如,下面的代码中,引用mfs不再有效: void main() { Student& refs=fn(); //... } 因为外部表达式"Student& refs=fn();"到分号处结束,以后从fn()返回的临时对象便不再有效,这就意味着引用refs的实体己不存在,所以接下去的任何对refs的引用都是错的。 又例如,下面的代码中,一切临时对象都在一个外部表达式中结束: Student fnl(); int fn2(Student&); void main() { int x; x=3*fn2(fnl())+10; //... } fnl()返回时,创建临时对象作为fn2()的实参,此时,在fn2()中一直有效;当fn2()返回一个int值参与计算表达式时,那个临时对象仍有效;一旦计算完成,赋值给x后,则临时对象被析构。 14.7 无名对象 可以直接调用构造函数产生无名对象。 例如,下面的代码在函数fn()中,创建了一个无名对象: class Student { public: Student(char*); //... void fn() { Student("Randy"); //此处为无名对象 //... } 无名对象可以作为实参传递给函数,可以拿来拷贝构造一个新对象,也可以初始化一个引用的声明。 例如,下面的代码表达了无名对象典型的三种用法: void fn(Student& s); void main() { Student& refs=Student("Randy"); //初始化引用 Student s=Student("Jenny"); //初始化对象定义 fn(Student("Danny")); //函数参数 } 主函数开始运行时,第一个执行的是拿无名对象初始化一个引用。由于是在函数内部,所以无名对象作为局部对象产生在栈空间中,从作用域上看,该引用与无名对象是相同的,它完全等价于"Student refs="Randy";"所以这种使用是多余的。 第二个执行的是用无名对象拷贝构造一个对象s。按理说,C++先调用构造函数"Student(char·);"创建一个无名对象,然后再调用一个拷贝构造函数"Student(Student&);"(或许是默认的)创建对象s;但是,由于是用无名对象去拷贝构造一个对象,拷贝完后,无名对象就失去了任何作用,对于这种情况,C++特别地将其看作为"Student s="Jenny";"效果一样,而且可以省略创建无名对象这一步。 第三个执行的是无名对象作为实参传递给形参s,C++先调用构造函数创建一个无名对象,然后将该无名对象初始化给了引用形参s对象,由于实参是在主函数中,所以无名对象是在主函数的栈区中创建,函数fn()的形参s引用的是主函数栈空间中的一个对象。它等价于: Student s("Danny"); fn(s); 如果对象s仅仅是为了充当函数fn()实参的需要,完全可以用第三个执行来代替。 当运行到主函数结束的时候,将有一个主函数中的s对象和3个无名对象被析构。 14.9 构造函数用于类型转换 5/8与5.0/8结果不同,原因是C++执行了两种不同的操作。5.0/8匹配了两个double 类型数的除法,C++知道如何将8转换成double型,这是基本数据类型的转换。但是,转换用户定义的类类型,必须由用户告知。用户告知的方式就是定义含一个参数的构造函数。 例如,下面的代码中定义了学生类的构造函数: class Student { public: Student(char*); //... }; void fn(Student&S); void main() { fn("Jenny"); } 这里Student(char*)构造函数同时也在告知,如何将char*转换成一个Student对象。如果有重载函数fn(char*),则调用fn("Jenny")马上匹配了事。但就是因为没有这样的重载函数,所以C++对所有fn函数进行类型转换试探,包括构造函数。 因为有Student(char*)的构造函数,又有fn(Student& s)函数,于是,fn("Jenny")便被认为是fn(Student("Jenny")),最终予以匹配。把构造函数用来从一种类型转换为另一种类型,这是C++从类机制中获得的附加性能。但要注意下面两点: (1)只会尝试含有一个参数的构造函数; (2)如果有二义性,则放弃尝试。例如: class Student { public: Student(char* pName="no name"); }; class Teacher { public: Teacher(char* pName="no name"); }; void addCourse(Student& s); void addCourse(Teacher& t); void main() { addCourse("Prof.Dingleberry"); //error:二义性 } 改正的方法是,只要显式转换一下: addCourse(Teacher("Prof.Dingleberry")); 小结 运算符new分配堆内存,如果成功,则返回指向该内存的空间,如果失败,则返回NULL。所以每次使用运算符new动态分配内存时,都应测试new的返回指针值,以防分配失败。堆空间的大小是有限的,视其操作系统和编译设置的不同而不同。当程序不再使用所分配的堆空间时,应及时用delete释放它们。 由C++提供的默认拷贝构造函数只是对对象进行浅拷贝复制。如果对象的数据成员包括指向堆空间的指针,就不能使用这种拷贝方式,此时必须自定义拷贝构造函数,为创建的对象分配堆空间。 练习 14.1 阅读下面的程序,写出运行结果: # include < iostream.h> class Samp public: void Setij(int a, int b){i=a,j =b;} ~Samp() { cout <<"Destroying.." << i << endl; } int GetMulti ( ) { return i * j; } protected: int i; int j; }; void main ( ) { Samp * p; p = new Samp[l0]; if(!p) { cout << "Allocation error \ n"; return; } for(int j =0; j p[j]. Setij (j, j); for(int k=0; k cout <<"Multi[" < << p[k].GetMulti () << endl; delete [ ] p; } 14.2 写出下面程序的运行结果,请用增加拷贝构造函数的方法避免存在的问题。# include < iostream.h > class Vector { public: Vector (int s = 100); int& Elem(int ndx); void Display(); void Set ( ); ~Vector ( ); protected: int size; int* buffer; } Vector::Vector (int s) buffer = new iht [size = s]; for(int i = O; icsize; i + + ) buffer [i] = i* i; } int& Vector:: Elem(int ndx) { if(ndx< 0 || ndx> = size) { cout << "error in index" << endl; exit (1); } return buffer [ndx]; } void Vector::Display ( ) { for(int j =0; j< size; j ++) cout << buffer(j) << endl; } void Vector:: Set ( ) { for(int j =0; j buffer(j) = j + 1; } Vector:: ~ Vector ( ) { delete [ ] buffer; } void main( ) { Vector a(10); Vector b(a); a. Set (); b. Display ( ); } 14.3 完善下列程序,定义每个成员函数和非成员函数,输出必要的信息,检查临时对象何时被创建,何时被析构。 class X { public: X(int); X(X&); ~X(); }; X f(X); void main ( ) { X a(1); X b= f(X(2)); a= f(a); } 14.4 读下面的程序与运行结果,添上一个拷贝构造函数来完善整个程序。# include < iostream.h> class CAT { publ ic: CAT(); CAT(const CAT&); ~CAT(); int GetAge() const (return * itsAge;) void SetAge(int age) { * itsAge = age; } protected: int * itsAge; }; CAT::CAT ( ) { itsAge = new int; *itsAge = 5; } CAT::~CAT ( ) { delete itsAge; itsAge = 0; } void main( ) { CAT frisky; cout << "frisky's age:" << frisky. GetAge() << endl; cout <<"Setting frisky to 6... \ n"; frisky. SetAge ( 6 ); cout << "Creating boots from frisky \ n"; CAT boots(frisky); cout <<"frisky's age:" << frisky. GetAge() < cout << "boots'age:" << boons. GetAge ( ) << endl; cout << "setting frisk,, to 7 .... n"; frisky. SetAge (7); cout <<"frisky"s age:" << frisky. GetAge() << endl; cout <<"boots' age:" << boots. GetAge() << endl; } 运行结果为: frisky's age:5 Setting frisky to 6... Creating boots from frisky frisky's age:6 boots' age:6 Setting frisky to 7... frisky's age:7 boots' age:6 C#默认构造函数的作用 本文详细介绍C#默认构造函数的作用 构造函数主要用来初始化对象。它又分为静态(static)和实例(instance)构造函数两种类别。大家应该都了解如果来写类的构造函数,这里只说下默认构造函数的作用,以及在类中保留默认构造函数的重要性。实际上,我说错了。正确的说法是:以及在类中保留空参数构造函数的重要性。我们来写一个类A,代码如下: view plaincopy to clipboardprint? public class A { public int Number; //数字 public string Word; //文本 } //在Test类中实例化 public class Test { static void Main() { A a = new A(); //实例化,A()即为类A的默认构造函数 Console.WriteLine(“Number = {0}"nWord = {1}”,a.Number,a.Word); Console.read(); } } 输出的结果是: Number = 0 Word = ******************************* using System; class Point { public int x, y,z; public Point() { x = 0; y = 0; z = 0; } public Point(int x, int y,int z) { //把函数内容补充完整 this.x = x; this.y =y; this.z =z; } public override string ToString() { return(String.Format("({0},{1},{2})", x, y,z)); } } class MainClass { static void Main() { Point p1 = new Point(); Point p2 = new Point(10,20,30); Console.WriteLine("三维中各点坐标:"); Console.WriteLine("点1的坐标为{0}", p1); Console.WriteLine("点2的坐标为{0}", p2); } } ******************************************************************************* ********* C#类的继承,构造函数实现及其调用顺序 类层层派生,在实例化的时候构造函数的调用顺序是怎样的? --从顶层基类开始向子类方向顺序调用无参构造. 默认构造(无参构造)和带参构造什么时候调用?--默认将从顶层父类的默认构造一直调用到当前类的默认构造. 下面是示例: /**//*--===------------------------------------------===--- 作者:许明会 日期:类的派生和构造函数间的关系,调用层次及实现 日期:2008年1月18日 17:30:43 若希望类能够有派生类,必须为其实现默认构造函数. 若类没有实现带参构造,编译器将自动创建默认构造函数. 若类实现了带参构造,则编译器不会自动生成默认构造. --===------------------------------------------===---*/ using System; namespace xumh { public class MyClass { public MyClass () { 有的、已经存在的数据创建出一份新的数据,最终的结果是多了一份相同的数据。例如,将Word 文档拷贝到U盘去复印店打印,将D 盘的图片拷贝到桌面以方便浏览,将重要的文件上传到百度网盘以防止丢失等,都是「创建一份新数据」的意思。 在C++ 中,拷贝并没有脱离它本来的含义,只是将这个含义进行了“特化”,是指用已经存在的对象创建出一个新的对象。从本质上讲,对象也是一份数据,因为它会占用内存。 严格来说,对象的创建包括两个阶段,首先要分配内存空间,然后再进行初始化: ?分配内存很好理解,就是在堆区、栈区或者全局数据区留出足够多的字节。这个时候的内存还比较“原始”,没有被“教化”,它所包含的数据一般是零值或者随机值,没有实际的意义。 ?初始化就是首次对内存赋值,让它的数据有意义。注意是首次赋值,再次赋值不叫初始化。初始化的时候还可以为对象分配其他的资源(打开文件、连接网络、动态分配内存等),或者提前进行一些计算(根据价格和数量计算出总价、根据长度和宽度计算出矩形的面积等)等。说白了,初始化就是调用构造函数。 很明显,这里所说的拷贝是在初始化阶段进行的,也就是用其它对象的数据来初始化新对象的内存。 那么,如何用拷贝的方式来初始化一个对象呢?其实这样的例子比比皆是,string 类就是一个典型的例子。 1.#include 定义类的构造函数 作者:lyb661 时间:20150613 定义类的构造函数有如下几种方法: 1、使用默认构造函数(类不另行定义构造函数):能够创建一个类对象,但不能初始化类的各个成员。 2、显式定义带有参数的构造函数:在类方法中定义,使用多个参数初始化类的各个数据成员。 3、定义有默认值的构造函数:构造函数原型中为类的各个成员提供默认值。 4、使用构造函数初始化列表:这个构造函数初始化成员的方式显得更紧凑。 例如:有一个学生类。其中存储了学生的姓名、学号和分数。 class Student { private: std::string name; long number; double scores; public: Student(){}//1:default constructor Student(const std::string& na,long nu,double sc); Student(const std:;string& na="",long nu=0,double sc=0.0); Student(const std:;string& na="none",long nu=0,double sc=0.0):name(na),number(nu),scores(sc){} ……….. void display() const; //void set(std::string na,long nu,double sc); }; ......... Student::Student(const std::string& na,long nu,double sc) { name=na; number=nu; scores=sc; } void Student::display()const { std::cout<<"Name: "< 拷贝构造函数 一. 什么是拷贝构造函数 首先对于普通类型的对象来说,它们之间的复制是很简单的,例如: int a = 100; int b = a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量。下面看一个类对象拷贝的简单例子。 #include C默认构造函数的作用
C++拷贝构造函数(复制构造函数)
定义构造函数的四种方法
(完整版)拷贝构造函数