文档库 最新最全的文档下载
当前位置:文档库 › 04_第14章 堆与拷贝构造函数

04_第14章 堆与拷贝构造函数

04_第14章 堆与拷贝构造函数
04_第14章 堆与拷贝构造函数

第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#默认构造函数的作用 本文详细介绍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 () {

C++拷贝构造函数(复制构造函数)

有的、已经存在的数据创建出一份新的数据,最终的结果是多了一份相同的数据。例如,将Word 文档拷贝到U盘去复印店打印,将D 盘的图片拷贝到桌面以方便浏览,将重要的文件上传到百度网盘以防止丢失等,都是「创建一份新数据」的意思。 在C++ 中,拷贝并没有脱离它本来的含义,只是将这个含义进行了“特化”,是指用已经存在的对象创建出一个新的对象。从本质上讲,对象也是一份数据,因为它会占用内存。 严格来说,对象的创建包括两个阶段,首先要分配内存空间,然后再进行初始化: ?分配内存很好理解,就是在堆区、栈区或者全局数据区留出足够多的字节。这个时候的内存还比较“原始”,没有被“教化”,它所包含的数据一般是零值或者随机值,没有实际的意义。 ?初始化就是首次对内存赋值,让它的数据有意义。注意是首次赋值,再次赋值不叫初始化。初始化的时候还可以为对象分配其他的资源(打开文件、连接网络、动态分配内存等),或者提前进行一些计算(根据价格和数量计算出总价、根据长度和宽度计算出矩形的面积等)等。说白了,初始化就是调用构造函数。 很明显,这里所说的拷贝是在初始化阶段进行的,也就是用其它对象的数据来初始化新对象的内存。

那么,如何用拷贝的方式来初始化一个对象呢?其实这样的例子比比皆是,string 类就是一个典型的例子。 1.#include 2.#include https://www.wendangku.net/doc/0a1867120.html,ing namespace std; 4. 5.void func(string str){ 6.cout<

定义构造函数的四种方法

定义类的构造函数 作者: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 using namespace std; class CExample { private: int a; public: //构造函数 CExample(int b) { a = b;} //一般函数 void Show () { cout< using namespace std;

private: int a; public: //构造函数 CExample(int b) { a = b;} //拷贝构造函数 CExample(const CExample& C) { a = C.a; } //一般函数 void Show () { cout<

为什么要引入构造函数和析构函数汇总

1.为什么要引入构造函数和析构函数? 对象的初始化是指对象数据成员的初始化,在使用对象前,一定要初始化。由于数据成员一般为私有的(private),所以不能直接赋值。对对象初始化有以下两种方法:类中提供一个普通成员函数来初始化,但是会造成使用上的不便(使用对象前必须显式调用该函数)和不安全(未调用初始化函数就使用对象)。 当定义对象时,编译程序自动调用构造函数。 析构函数的功能是当对象被撤消时,释放该对象占用的内存空间。析构函数的作用与构造函数正好相反,一般情况下,析构函数执行构造函数的逆操作。在对象消亡时,系统将自动调用析构函数,执行一些在对象撤消前必须执行的清理任务。 2. 类的公有、私有和保护成员之间的区别是什么? ①私有成员private: 私有成员是在类中被隐藏的部分,它往往是用来描述该类对象属性的一些数据成员,私有成员只能由本类的成员函数或某些特殊说明的函数(如第4章讲到的友员函数)访问,而类的外部根本就无法访问,实现了访问权限的有效控制,使数据得到有效的保护,有利于数据的隐藏,使内部数据不能被任意的访问和修改,也不会对该类以外的其余部分造成影响,使模块之间的相互作用被降低到最小。private成员若处于类声明中的第一部分,可省略关键字private。 ②公有成员public:公有成员对外是完全开放的,公有成员一般是成员函数,它提供了外部程序与类的接口功能,用户通过公有成员访问该类对象中的数据。 ③保护成员protected: 只能由该类的成员函数,友元,公有派生类成员函数访问的成员。保护成员与私有成员在一般情况下含义相同,它们的区别体现在类的继承中对产生的新类的影响不同,具体内容将在第5章中介绍。缺省访问控制(未指定private、protected、public访问权限)时,系统认为是私有private 成员。 3. 什么是拷贝构造函数,它何时被调用?

拷贝构造函数

实验7拷贝构造函数 一、实验目的 (1) 掌握类的声明和对象的声明。 (2) 掌握拷贝构造函数的定义与使用 (3) 了解拷贝构造函数调用的时机 二、实验内容及步骤 1 新建c++源文件,找到week14文件夹中的copyStruDefine.cpp文件,复制到新建的源文件中运行,将运行结果记录下来,分析程序中执行哪条语句引起拷贝构造函数被调用的,将该语句的行号记录下来。 分析程序,第46 条语句Point pa(1,2) 执行时会调用构造函数,第47 条语句 Point pb=pa 执行时会调用拷贝构造函数。 2 程序中添加一个distance函数,用来计算2个点之间的距离。代码如下:

运行程序,记录运行结果。 分析程序,第52,53 条语句Point pa(7,4); Point pb(1,2); 执行时会调用构造函数,第46,47 条语句 double dx=a1.getX()-a2.getX();double dy=a1.getY()-a2.getY(); 执行时会调用拷贝构造函数。 3 设计一个函数mirror用来返回一个点在x轴的镜像坐标,如点A坐标为(1,2),它的镜像点A’坐标为(1,-2)。

分析:函数的结构分成2部分,函数头部和函数体 (1)函数的函数头部分语法格式:返回类型函数名(参数) 可以确定的是函数名mirror;这个函数会计算出一个点的镜像并返回,点的镜像还是一个点,因此可以确定函数的返回类型是void ;这个函数会将某个点的镜像计算出来,那到底计算的是那个点的镜像呢?这是不确定的,将不确定的因素定义为函数的参数,因此函数的参数类型是Point ;现将函数的第一行补充完整。 返回类型mirror(参数) (2)分析函数的函数体部分,即用”{ }”包围的部分。 我们通过参数传递接收到一个点的坐标,现在要计算另一个点的坐标(镜像点),因此需要在函数体内定义另外一个点类型的对象来存放镜像点的坐标。将镜像点的x坐标赋值为参数点的x坐标值,将镜像点的y坐标赋值为参数点的y坐标值的负数值(需要注意Point类中的x和y成员都是私有的)。将镜像点坐标赋值完成后,用return语句将镜像点返回。 (3)在主函数中测试mirror函数。 #include #include using namespace std; /* 类的函数成员-->构造函数(创建对象并赋初值) int a=10; int b=a;//创建变量b并赋初值,这个初值放在a中 创建对象(新)时,构造函数的参数是对象(已存在) --拷贝构造函数 */ class Point{ private: double x;

构造函数

c++构造函数的知识在各种c++教材上已有介绍,不过初学者往往不太注意观察和总结其中各种构造函数的特点和用法,故在此我根据自己的c++编程经验总结了一下c++中各种构造函数的特点,并附上例子,希望对初学者有所帮助。 c++类的构造函数详解 一、构造函数是干什么的 class Counter { public: // 类Counter的构造函数 // 特点:以类名作为函数名,无返回类型 Counter() { m_value = 0; } private: // 数据成员 int m_value; } 该类对象被创建时,编译系统对象分配内存空间,并自动调用该构造函数->由构造函数完成成员的初始化工作 eg: Counter c1; 编译系统为对象c1的每个数据成员(m_value)分配内存空间,并调用构造函数Counter( )自动地初始化对象c1的m_value值设置为0 故: 构造函数的作用:初始化对象的数据成员。 二、构造函数的种类 class Complex { private : double m_real; double m_imag;

public: // 无参数构造函数 // 如果创建一个类你没有写任何构造函数,则系统会自动生成默认的无参构造函数,函数为空,什么都不做 // 只要你写了一个下面的某一种构造函数,系统就不会再自动生成这样一个默认的构造函数,如果希望有一个这样的无参构造函数,则需要自己显示地写出来 Complex(void) { m_real = 0.0; m_imag = 0.0; } // 一般构造函数(也称重载构造函数) // 一般构造函数可以有各种参数形式,一个类可以有多个一般构造函数,前提是参数的个数或者类型不同(基于c++的重载函数原理) // 例如:你还可以写一个Complex( int num)的构造函数出来 // 创建对象时根据传入的参数不同调用不同的构造函数 Complex(double real, double imag) { m_real = real; m_imag = imag; } // 复制构造函数(也称为拷贝构造函数) // 复制构造函数参数为类对象本身的引用,用于根据一个已存在的对象复制出一个新的该类的对象,一般在函数中会将已存在对象的数据成员的值复制一份到新创建的对象中// 若没有显示的写复制构造函数,则系统会默认创建一个复制构造函数,但当类中有指针成员时,由系统默认创建该复制构造函数会存在风险,具体原因请查询有关“浅拷贝”、“深拷贝”的文章论述 Complex(const Complex & c) { // 将对象c中的数据成员值复制过来 m_real = c.m_real; m_img = c.m_img; } // 类型转换构造函数,根据一个指定的类型的对象创建一个本类的对象 // 例如:下面将根据一个double类型的对象创建了一个Complex对象 Complex::Complex(double r) { m_real = r; m_imag = 0.0;

【重要】C++拷贝函数详解 20150111

C++拷贝函数详解 1.什么是拷贝构造函数: CA(const CA& C)就是我们自定义的拷贝构造函数。可见,拷贝构造函数是一种特殊的构 造函数,函数的名称必须和类名称一致,它的唯一的一个参数是本类型的一个引用变量,该参 数是const类型,不可变的。例如:类X的拷贝构造函数的形式为X(X& x)。 当用一个已初始化过了的自定义类类型对象去初始化另一个新构造的对象的时候,拷 贝构造函数就会被自动调用。 也就是说,当类的对象需要拷贝时,拷贝构造函数将会被调用。以下情况都会调用拷贝构造函数: ①程序中需要新建立一个对象,并用另一个同类的对象对它初始化,如前面介绍的那样。 ②当函数的参数为类的对象时。 在调用函数时需要将实参对象完整地传递给形参,也就是需要建立一个实参的拷贝,这就 是按实参复制一个形参,系统是通过调用复制构造函数来实现的,这样能保证形参具有和实参 完全相同的值。 ③函数的返回值是类的对象。 在函数调用完毕将返回值带回函数调用处时。 此时需要将函数中的对象复制一个临时对象并传给该函数的调用处。如 Box f( ) //函数f的类型为Box类类型 {Box box1(12,15,18); return box1; //返回值是Box类的对象 } int main( ) {Box box2; //定义Box类的对象box2 box2=f( ); //调用f函数,返回Box类的临时对象,并将它赋值给 box2 } 如果在类中没有显式地声明一个拷贝构造函数,那么,编译器将会自动生成一个默认的 拷贝构造函数,该构造函数完成对象之间的位拷贝。位拷贝又称浅拷贝,后面将进行说明。 自定义拷贝构造函数是一种良好的编程风格,它可以阻止编译器形成默认的拷贝构造函数,提高源码效率。 浅拷贝和深拷贝 在某些状况下,类内成员变量需要动态开辟堆内存,如果实行位拷贝,也就是把对象里的

所学知识点总结

设计者往往对使用者抱有很多期望,希望使用者遵循一些共同的约束,但是期望总是很难得到满足的,从基础的设计原则上来说,我们不应该对设计者抱有太多的期望,宁愿予以更多的使用约束,即只有固定的路线可走。 如果你觉得某项语法规定不合理,那么请自行模拟它的种种特性,你会发现它是有道理的,只不过有时它可能会产生很多危害性,但是不应该忽略其有益的方面。 1.四种C++风格的类型转换 a. static_cast: 纯粹的内容转换,可能会造成实际内容的损失; b. dynamic_cast: 只对有虚类型有用,因为虚表的存在才有RTTI; c. const_cast: 去const化; d. reinterpret_cast: 让编译器以该转换所注明的类型去看待内存段所表示的类型,一般是针对指针类型,一般来说是不怎么安全的,而且对于函数指针的转换有时是不可移植的,因为不同编译器可能有不同的函数指针表示法(命令方法和存储方法的不同?); 2.数据类型转换: a. double=int/int;//做法是错误的,右侧不会提升至double再进行运算的,类型提升的原则是如果不大于整型则会提升至int,但有更大类型则会继续提升,正确写法是: double=int/(double)int;或者double=(double)int/(double)int; b. int=usigned char;//错误,正确做法:usigned char->usignedint->int 3.new operator和operator new: 前者是个操作符名称,后者则代表某个函数。 new operator:new操作符仅仅是我们常见的new的名称,它实际代表两段操作:operator new (这是一个我们可以自行重载的函数,作用是分配内存,并返回所分配内存的首地址,换句话说,即使有意进行偏离或者像malloc一样设置一定字节进行特殊用途都是可以的)和调用构造函数。记住编译器看见new操作符时意味着它会先调用new操作函数(operator new()),然后调用构造函数。 上图说明了定位new和定位delete为何是类型对应的关系。

构造函数初始化成员变量

请问在构造函数中使用初始化清单和直接在构造函数内初始化成员变量有什么区别? 比如: construct_function():var1(1),var2(2),var(3) {} 和 construct_function() { var1 = 1; var2 = 2; var3 = 3; } 有没有什么本质区别? =============================================================================== ======= construct_function():var1(1),var2(2),var(3) {} 初始化 construct_function() { var1 = 1; var2 = 2; var3 = 3; }赋值 首先把数据成员按类型分类 1、内置数据类型,复合类型(指针,引用) 2、用户定义类型(类类型) 分情况说明: 对于类型1,在成员初始化列表和构造函数体内进行,在性能和结果上都是一样的。要是const类型的话只能使用初始化列表。 对于类型2,结果上相同,但是性能上存在很大的差别。 因为类类型的数据成员对象在进入函数体是已经构造完成,也就是说在成员初始化列表处进行构造对象的工作,这是调用一个构造函数,在进入函数体之后,进行的是对已经构造好的类对象赋值,又调用其拷贝赋值操作符才能完成(如果并未提供,则使用编译器提供的默认按成员赋值行为)。 举个例说明 class A; class B {

public: B(){a = 3;} private: A a; } class A { public: A(){} A(int){value = 3;} int value; } 像上面,我们使a对象的value为3,调用一个A的构造函数+一个默认拷贝赋值符,才达到目的。B::B():a(3){} 像这样,只调用了一个构造函数就得到了所需的对象啦,所以性能好。 注意:对于const成员,无缺省构造函数的类对象成员,均需放在成员初始化列表。 再举个例子: class A { public: A(int i){} }; class B { public: B() : ci(3), a(3){} private: const int ci; A a; }; int main() { B b; return 0; } 对于const成员,无缺省构造函数的类对象成员,均需放在成员初始化列表。

C++考试题(选择题)

1、选择题 1、___A__只能访问静态成员变量。 A 静态函数 B 虚函数 C 构造函数 D 析构函数 2、下列的各类函数中,__C___不是类的成员函数。 A 构造函数 B 析构函数C友元函数 D 拷贝构造函数 3、友元的作用_A__。 A 提高程序的运行效率 B 加强类的封装性 C 实现数据的隐藏性 D 增加成员函数的种类 4、类模板的使用实际上是将类模板实例化成一个具体的_D____。 A 类 B 对象 C 函数 D 模板类 5、下列函数中,___C__不能重载。 A 成员函数 B 非成员函数 C 析构函数 D 构造函数 6、___C__是一个在基类中说明的虚函数,它在该基类中没有定义,但要求任何派生类都必须定义自己的版本。 A 虚析构函数B虚构造函数C纯虚函数 D 静态成员函数 7、__A___是istream的派生类,处理文件输入;___C__是iostream的派生类,可以同时处理文件的I/O。 A、ifstream B、ostream C、fstream D、ofstream 8、对于派生类的构造函数,在定义对象时构造函数的执行顺序为: 先执行__A___,再执行__B___,后执行__C___。 A 成员对象的构造函数 B 基类的构造函数 C 派生类本身的构造函数 9、局部变量可以隐藏全局变量,那么在有同名全局变量和局部变量的情形时,可以用__A___提供对全局变量的访问。 A 域运算符 B 类运算符 C 重载 D 引用 10、一个__C___允许用户为类定义一种模式,使得类中的某些数据成员及某些成员函数的返回值能取任意类型。 A 函数模板 B 模板函数 C 类模板 D 模板类 11、系统在调用重载函数时,往往根据一些条件确定哪个重载函数被调用,在下列选项中,不能作为依据的是___D__。 A 参数个数 B 参数的类型 C 函数名称D函数的类型 12、如果一个类至少有一个纯虚函数,那么就称该类为__A___。 A 抽象类 B 虚基类 C 派生类 D 以上都不对 13、进行文件操作时需要包含__B___文件。 A iostream B fstream C stdio.h D stdliB、h 14、在C++中,打开一个文件,就是将这个文件与一个__B___建立关联;关闭一

Java默认构造函数的作用

class Person { private String name=""; private int age=0; public Person() { System.out.println("person无参数构造函数"); } public Person(String name,int age) { https://www.wendangku.net/doc/0a1867120.html,=name; this.age=age; System.out.println("person 2 参数的构造函数"); } } class Student extends Person { private String school; private String grade; public Student() { System.out.println("student 无参数的构造函数"); } public Student(String name ,int age,String school) { System.out.println("student 3 参数的构造函数"); } public Student(String name ,int age,String school,String grade) { super(name,age); this.school=school;

this.grade=grade; System.out.println("student 4 参数的构造函数,super()."); } } class Test { public static void main(String [] args) { System.out.println("st1:"); Student st2=new Student(); System.out.println("---------------------------"); System.out.println("st2:"); Student st=new Student("zhangshan",76,"武大"); System.out.println("---------------------------"); System.out.println("st3:"); Student st3=new Student("lisi",24,"武大","研究生"); } } /* ======================================= 输出如下: E:JavaWork>java Test st1: person无参数构造函数 student 无参数的构造函数 --------------------------- st2: person无参数构造函数 student 3 参数的构造函数 --------------------------- st3:

拷贝构造函数&默认拷贝构造函数&拷贝构造函数调用几种情况

一、拷贝构造函数 如果类中没有说明拷贝构造函数,则系统自动生成一个缺省复制构造函数,作为该类的公有成员 也可以进行自定义拷贝构造函数 自定义拷贝构造函数的例子: Test::Test(const Test& other) : num_(other.num_) { //num_ = other.num_; cout<<"Initializing with other "<

C++拷贝构造函数的几个细节

C++拷贝构造函数的几个细节 关键字: c++ 拷贝构造函数是C++最基础的概念之一,大家自认为对拷贝构造函数了解么?请大家先回答一下三个问题: 1.以下函数哪个是拷贝构造函数,为什么? 1.X::X(const X&); 2.X::X(X); 3.X::X(X&, int a=1); 4.X::X(X&, int a=1, b=2); 2.一个类中可以存在多于一个的拷贝构造函数吗? 3.写出以下程序段的输出结果, 并说明为什么?如果你都能回答无误的话,那么你已经对拷贝构造函数有了相当的了解。 1.#include 2.#include 3. 4.struct X { 5. template 6. X( T& ) { std::cout << "This is ctor." << std::endl; } 7. 8. template 9. X& operator=( T& ) { std::cout << "This is ctor." << std:: endl; } 10.}; 11. 12.void main() { 13. X a(5); 14. X b(10.5); 15. X c = a; 16. c = b; 17.} 解答如下: 1. 对于一个类X,如果一个构造函数的第一个参数是下列之一: a) X&

b) const X& c) volatile X& d) const volatile X& 且没有其他参数或其他参数都有默认值,那么这个函数是拷贝构造函数. 1.X::X(const X&); //是拷贝构造函数 2.X::X(X&, int=1); //是拷贝构造函数 2.类中可以存在超过一个拷贝构造函数, 1.class X { 2.public: 3. X(const X&); 4. X(X&); // OK 5.}; 注意,如果一个类中只存在一个参数为X&的拷贝构造函数,那么就不能使用const X或volatile X的对象实行拷贝初始化. 1.class X { 2.public: 3. X(); 4. X(X&); 5.}; 6. 7.const X cx; 8.X x = cx; // error 如果一个类中没有定义拷贝构造函数,那么编译器会自动产生一个默认的拷贝 构造函数. 这个默认的参数可能为X::X(const X&)或X::X(X&),由编译器根据上下文决定选择哪一个. 默认拷贝构造函数的行为如下: 默认的拷贝构造函数执行的顺序与其他用户定义的构造函数相同,执行先父类后子类的构造. 拷贝构造函数对类中每一个数据成员执行成员拷贝(memberwise Copy)的动作. a)如果数据成员为某一个类的实例,那么调用此类的拷贝构造函数. b)如果数据成员是一个数组,对数组的每一个执行按位拷贝. c)如果数据成员是一个数量,如int,double,那么调用系统内建的赋值运算符 对其进行赋值. 3. 拷贝构造函数不能由成员函数模版生成.

C#中构造函数使用方法

C#构造函数是在创建给定类型的对象时执行的类方法。构造函数具有与类相同的名称,它通常初始化新对象的数据成员。不带参数的构造函数称为“默认构造函数”。无论何时,只要使用new 运算符实例化对象,并且不为new 提供任何参数,就会调用默认构造函数。除非类是static 的,否则C# 编译器将为无构造函数的类提供一个公共的默认构造函数,以便该类可以实例化。 构造函数必须是在类里的一级声明,并且命名为类名, 形式为:修饰符类名(参数类型1,参数名1,。。。) 例如 class A { public int x, y; public string s; // 默认构造函数 public A() { x = 0; y = 0; } //带参数的构造函数 public A(string a) { this.s=a; } } 一般函数的声明则不受这些约束 只要定义在命名空间内,命名形式为:修饰符返回值类型函数名(参数类型1,参数名1,。。。) 例如:private static void Main(string args) 声明了一个私有的静态主函数,无返回值,参数为args,string类型vvv 一、C#构造函数?Construct,Function 构造函数是一种特殊的成员函数,它主要用于为对象分配存储空间,对数据成员进行初始化. 构造函数具有一些特殊的性质: (1)构造函数的名字必须与类同名; (2)构造函数没有返回类型,它可以带参数,也可以不带参数; (3)声明类对象时,系统自动调用构造函数,构造函数不能被显式调用; (4)构造函数可以重载,从而提供初始化类对象的不同方法; (5)若在声明时未定义构造函数,系统会自动生成默认的构造函数,此时构造函数的函数体为空.

C拷贝构造函数的几个知识点总结及示例代码

C++拷贝构造函数的知识点总结 一拷贝构造函数是C++最基础的概念之一,大家自认为对拷贝构造函数了解么?请大家先回答一下三个问题: 1.以下函数哪个是拷贝构造函数,为什么? 1.X::X(const X&); 2.X::X(X); 3.X::X(X&, int a=1); 4.X::X(X&, int a=1, b=2); 2.一个类中可以存在多于一个的拷贝构造函数吗? 3.写出以下程序段的输出结果, 并说明为什么?如果你都能回答无误的话,那么你已经对拷贝构造函数有了相当的了解。 1.#include 2.#include 3. 4.struct X { 5. template 6. X( T& ) { std::cout << "This is ctor." << std::endl; } 7. 8. template 9. X& operator=( T& ) { std::cout << "This is ctor." << std::endl; } 10.}; 11. 12.void main() { 13. X a(5); 14. X b(10.5); 15. X c = a; 16. c = b; 17.} 解答如下: 1.对于一个类X,如果一个构造函数的第一个参数是下列之一: a) X& b) const X& c) volatile X&

d) const volatile X& 且没有其他参数或其他参数都有默认值,那么这个函数是拷贝构造函数. 1.X::X(const X&); //是拷贝构造函数 2.X::X(X&, int=1); //是拷贝构造函数 2.类中可以存在超过一个拷贝构造函数, 1.class X { 2.public: 3. X(const X&); 4. X(X&); // OK 5.}; 注意,如果一个类中只存在一个参数为X&的拷贝构造函数,那么就不能使用const X或volatile X的对象实行拷贝初始化. 1.class X { 2.public: 3. X(); 4. X(X&); 5.}; 6. 7.const X c x; 8.X x = c x; // error 如果一个类中没有定义拷贝构造函数,那么编译器会自动产生一个默认的拷贝构造函数. 这个默认的参数可能为X::X(const X&)或X::X(X&),由编译器根据上下文决定选择哪一个. 默认拷贝构造函数的行为如下: 默认的拷贝构造函数执行的顺序与其他用户定义的构造函数相同,执行先父类后子类的构造. 拷贝构造函数对类中每一个数据成员执行成员拷贝(memberwise Copy)的动作. a)如果数据成员为某一个类的实例,那么调用此类的拷贝构造函数. b)如果数据成员是一个数组,对数组的每一个执行按位拷贝. c)如果数据成员是一个数量,如int,double,那么调用系统内建的赋值运算符对其进行赋值. 3.拷贝构造函数不能由成员函数模版生成. 1.struct X { 2.template 3. X( const T& ); // NOT copy ctor, T can't be X 4. 5.template 6. operator=( const T& ); // NOT copy ass't, T can't be X 7.};

C++期末考试复习重点、易错知识点整理

C++重点、易错知识点整理 第一章 1、泛型程序设计是指向程序中数据类型中加入类型参数的一种能力,也称为参数化的类型 或参数多态性。 2、c++程序开发通常要经过5个阶段,包括编辑、预处理、编译、连接、运行与调试。 3、编译过程分为词法分析、语法分析、代码生成这3个步骤。 4、使用名字空间std的方法有3种: 1、利用using namespace使用名字空间;使用方法如下: 2、用域分辨符::为对象分别指定名字空间;例如: 3、用using与域分辨符指定名字空间;例如: 5、c++中常用操作符: 第二章 1、c++的数据类型:

2、在定义变量的同时赋初值还有另外一种方法,就是在变量后面将初值放在括号中,格式 如下: 3、常变量定意格式: 或 ※在定义常变量时,一定要赋初值,且在程序中间不能更新其值。 4、常量和非左值表达式是没有内存地址的。 5、在逻辑表达式求值中注意短路求值。 6、运算符优先级的规律: (1)运算符的优先级按单目、双目、三目、赋值依次降低; (2)算术、移位、关系、按位、逻辑运算的优先级依次降低。 7、标准c++提供了新式的强制类型转换运算,格式如下: ※static_cast用于一般表达式的类型转换; ※reinterpret_cast用于非标准的指针数据类型转换,如将void*转换成char*; ※const_cast将const表达式转换成非常量类型,常用于将限制const成员函数的const 定义解除; ※dynamic_cast用于进行对象指针的类型转换。 第三章 第四章 1、内联函数的定义必须出现在对该函数的调用之前。 2、递归函数不能定义为内联函数。 3、说明一个内联函数只是请求而不是命令编译器对它进行扩展。 带有默认形参值的函数: 1、若函数具有多个形参,则默认形参值必须自右向左连续的定义,并且在一个默认形参值 的右边不能有未指定默认值的参数。 2、在调用一个函数时,若果省去了某个实参,则直到最右端的实参都要省去。 3、默认形参值的说明必须出现在函数调用之前。若函数原型中已给出了形参的默认值,则 在函数定义中不得重复制定,即使所指定的默认值完全相同也不行。 4、在同一个作用域内,一旦定义了默认形参值,就不能在定义它。 5、如果几个函数说明出现在不同的作用域内,则允许对它们提供不同的默认形参值。 6、在函数的原型给出了形参的默认值时,形参名可以省略。 第五章 1、相同类型的指针类型才可以想减;两个指针是不可以相加的。 2、一个void类型的地址赋值给非void类型的指针变量,要使用类型强制转换。 3、要初始化多重指针,要从第一层开始,逐步向高层进行。

相关文档
相关文档 最新文档