class Counter {
public:
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:
// 无参数构造函数
// 如果类没写任何构造函数,则系统会自动生成默认的无参构造函数,函数为空,什么都不做
// 只要写了下面某一种构造函数,系统就不会再自动生成这样一个默认的构造函数,如果希望有一个这样的无参构造函数,则需要自己显示地写出来
// 一般构造函数(也称重载构造函数),可有各种参数形式,类可以有多个一般构造函数,但彼此参数个数/类型不同(基于c++的重载原理)
Complex(void) {
m_real = 0.0;
m_imag = 0.0;
}
// 复制构造函数(也称为拷贝构造函数)
// 复制构造函数参数为类对象本身的引用,用于根据一个已存在的对象复制出一个新的该类对象,一般在函数中会将已存在对象的数据成员的值复制一份到新创建的对象中
// 若没有显示的写复制构造函数,则系统会默认创建一个复制构造函数,但当类中有指针成员时,默认的复制构造函数会存在风险(见浅拷贝/深拷贝)
Complex(const Complex & c) { // 将对象c中的数据成员值复制过来
m_real = c.m_real;
m_imag = c.m_imag;
}
//类型转换构造函数,根据一个指定类型的对象创建一个本类对象
//注意:这个其实就是一般构造函数,但对于单参数构造函数,c++会默认将参数对应的类型转换为该类类型,有时这种隐式转换不是想要的,所以要用explicit来限制这种转换
//eg:将根据一个double类型对象创建一个Complex对象
Complex(double r){
m_real = r;
m_imag = 0.0;
}
// 等号运算符重载(也叫赋值构造函数)
// 注意:这个类似复制构造函数,将=右边本类对象的值复制给等号左边的对象,它不属于构造函数,等号左右两边对象必须已经被创建
// 若没显示写=运算符重载,则系统会创建默认的=运算符重载,只做一些基本的拷贝
Complex &operator=( const Complex &rhs ) {
if ( this == &rhs ) {
return *this;
} // 首先检测右边对象是否就是左边对象本身,若是本身,则直接返回
this->m_real = rhs.m_real; // 复制等号右边对象的成员到左边对象中
this->m_imag = rhs.m_imag;
// ret等号左边对象是为了支持连等 eg>a=b=c系统首先运行b=c,然后运行 a= (b=c的返回值,这里应该是复制c值后的b对象)
return *this;
}
};
用法:
int main() {
Complex c1,c2; //调用了无参构造函数,数据成员初值被赋为0.0
Complex c3(1.0,2.5); //调用一般构造函数,数据成员初值被赋为指定值
Complex c3 = Complex(1.0,2.5); //也可以使用下面的形式
c1 = c3; //把c3数据成员的值赋值给c1
//由于c1已事先被创建,故不会调用任何构造函数,只调用 = 号运算符重载函数
c2 = 5.2; //调用类型转换构造函数
//系统首先调用类型转换构造函数,将5.2创建为一个本类的临时对象,然后调用等号运算符重载,将该临时对象赋值给c1
Complex c5(c2); //调用拷贝构造函数
Complex c4 = c2; //调用拷贝构造函数
// 注意和 = 运算符重载区分,等号左边对象不是事先已经创建,故需调用拷贝构造函数,参数为c2
//这是初始化,不是赋值。其实这涉及了c++两种初始化的方式:复制初始化和赋值初始化,对应c5和c4,都要调用拷贝构造函数。
}
class Complex {
private:
double m_real;
double m_imag;
int id;
static int counter;
public:
Complex(void) {//无参数构造函数
m_real = 0.0;
m_imag = 0.0;
id=(++counter);
cout<<"Complex(void):id="<<id<double real, double imag){//一般构造函数(也称重载构造函数)
m_real = real;
m_imag = imag;
id=(++counter);
cout<<"Complex(double,double):id="<<id<const Complex & c) {//复制构造函数(也称为拷贝构造函数)
m_real = c.m_real;//将对象c中的数据成员值复制过来
m_imag = c.m_imag;
id=(++counter);
cout<<"Complex(const Complex&):id="<<id<<" from id="<.id<double r){// 类型转换构造函数,根据一个指定的类型的对象创建一个本类的对象
m_real = r;
m_imag = 0.0;
id=(++counter);
cout<<"Complex(double):id="<<id<"~Complex():id="<<id<const Complex &rhs ) {// 等号运算符重载
if ( this == &rhs ) {
return *this;
}
this->m_real = rhs.m_real;
this->m_imag = rhs.m_imag;
cout<<"operator=(const Complex&):id="<<id<<" from id="<.id<return *this;
}
};
int Complex::counter=0;
Complex test1(const Complex& c) {
return c;
}
Complex test2(const Complex c) {
return c;
}
Complex test3() {
static Complex c(1.0,5.0);
return c;
}
Complex& test4() {
static Complex c(1.0,5.0);
return c;
}
int main() {
Complex a,b;
// 下面函数执行过程中各会调用几次构造函数,调用的是什么构造函数?
Complex c=test1(a);
Complex d=test2(a);
b = test3();
b = test4();
Complex e=test2(1.2);
Complex f=test1(1.2);
Complex g=test1(Complex(1.2));
}
如果没有自定义复制构造函数,则系统会创建默认的复制构造函数,但默认复制构造函数只会执行“浅拷贝”,即将被拷贝对象的数据成员的值一一赋值给新创建的对象,若该类的数据成员中有指针成员,则会使得新的对象的指针所指向的地址与被拷贝对象的指针所指向的地址相同,delete该指针时则会导致两次重复delete而出错。
class Person {
public :
Person(char * pN) {
cout << "一般构造函数被调用 !\n";
m_pName = new char[strlen(pN) + 1];//在堆中存放pN所指的字符串
if(m_pName != NULL) {//把形参指针pN所指的字符串复制给它
strcpy(m_pName ,pN);
}
}
Person(Person & p) {// 系统创建的默认复制构造函数,只做位模式拷贝
m_pName = p.m_pName;//使两个字符串指针指向同一地址位置
}
~Person() {
delete m_pName;
}
private :
char * m_pName;
};
void main() {
Person man("lujun");
Person woman(man);// 结果导致man和woman的指针都指向了同一个地址,函数结束析构时,同一个地址被delete两次
}
Person(Person & chs){//自己设计复制构造函数,实现“深拷贝”
m_pName=new char[strlen(p.m_pName)+ 1];
if(m_pName){
strcpy(m_pName ,chs.m_pName);
}
// 则新创建的对象的m_pName与原对象chs的m_pName不再指向同一地址了
}
class CTest {
public:
CTest() {
m_a = 1;
}
CTest(int b) {
m_b = b;
CTest();
}
~CTest() {}
void show {
std::cout << m_a << std::endl;
std::cout << m_b << std::endl;
}
private:
int m_a;
int m_b;
};
void main() {
CTest myTest(2);
myTest.show();
}
结果:m_a是一个不确定的值,因为没有被赋初值,m_b 为2
CTest(int b) {
m_b = b;
CTest();
}
调用CTest()实际上是创建了一个匿名临时CTest类对象,CTest()中赋值 m_a = 1也是对该匿名对象赋值,故定义的myTest的m_a其实没有被赋值。
即:其实构造函数并不像普通函数那样进行一段处理,而是创建了一个对象,并且对该对象赋初值,所以显式调用构造函数无法实现给私有成员赋值的目的。
不要用一个构造函数显式调用另外一个构造函数,这样会出现不确定性。其实一些初始化代码可以写在一个单独的init函数中,然后每一个构造函数都调用一下该初始化函数即可。
思考:
CTest *p = NULL;
void func() {
p = new CTest();
}
=右边显示调用CTest(),是否会产生一个匿名临时对象a,然后将该匿名临时对象a的地址赋给指针p? 如果如此,出了func()后,临时对象a是否会被析构? 那指针p就是野指针了?
答:不会产生临时对象a,直接将产生的对象指针赋给p
出处–>http://www.cnblogs.com/xkfz007/archive/2012/05/11/2496447.html