标签:c++ c++ primer 类与数据抽象
C++ Primer 学习笔记_19_类与数据抽象(5)_初始化列表(const和引用成员)、拷贝构造函数
从概念上将,可以认为构造函数分为两个阶段执行:
1)初始化阶段;
2)普通的计算阶段。计算阶段由构造函数函数体中的所有语句组成。
一、构造函数初始化列表
推荐在构造函数初始化列表中进行初始化
1、对象成员及其初始化
<span style="font-size:14px;">#include <iostream>
using namespace std;
class Object
{
public:
Object(int num) : num_(num)
{
cout << "Object " << num_ << " ..." << endl;
}
~Object()
{
cout << "~Object " << num_ << " ..." << endl;
}
private:
int num_;
};
class Container
{
public:
Container(int obj1 = 0, int obj2 = 0) : obj2_(obj2), obj1_(obj1)
{
cout << "Container ..." << endl;
}
~Container()
{
cout << "~Container ..." << endl;
}
private:
Object obj1_;
Object obj2_;
};
int main(void)
{
Container c(10, 20);
return 0;
}</span>
运行结果:
Object 10 ...
Object 20 ...
Container ...
~Container ...
~Object 20 ...
~Object 10 ...
解释:从输出可以看出几点,一是构造对象之前,必须先构造对象的成员;二是对象成员构造的顺序与定义时的顺序有关,跟初始化列表顺序无关;三是构造的顺序和析构的顺序相反;四是如果对象成员对应的类没有默认构造函数,那对象成员也只能在初始化列 表进行初始化。再提一点,如果类是继承而来,基类没有默认构造函数的时候,基类的构造函数要在派生类构造函数初始化列表中调用。
例子:
运行下面的C++代码,其输出结果是什么?
<span style="font-size:14px;">class A
{
private:
int i;
int j;
public:
A() : j(0), i(j+2) {}
void print() {
cout << "i:" << i << ", j:" << j << endl;
}
};
int main()
{
A a;
a.print();
return 0;
}</span>
解答:i是一个内存中的垃圾值,而j为0。在C++中,成员变量的初始化顺序与变量在类型中的声明顺序相同,而与它们在构造函数的初始化列表的顺序无关。
2、const成员、引用成员的初始化
——const成员的初始化只能在构造函数初始化列表中进行
——引用成员的初始化也只能在构造函数初始化列表中进行
——对象成员(对象成员所对应的类没有默认构造函数)的初始化,也只能在构造函数初始化列表中进行
<span style="font-size:14px;">#include <iostream>
using namespace std;
// const成员的初始化只能在构造函数初始化列表中进行
// 引用成员的初始化也只能在构造函数初始化列表中进行
// 对象成员(对象成员所对应的类没有默认构造函数)的初始化,也只能在构造函数初始化列表中进行
class Object
{
public:
enum E_TYPE
{
TYPE_A = 100,
TYPE_B = 200
};
public:
Object(int num = 0) : num_(num), kNum_(num), refNum_(num_)
{
cout << "Object " << num_ << " ..." << endl;
}
~Object()
{
cout << "~Object " << num_ << " ..." << endl;
}
void DisplayKNum()
{
cout << "kNum=" << kNum_ << endl;
}
private:
int num_;
const int kNum_;
int &refNum_;
};
int main(void)
{
Object obj1(10);
Object obj2(20);
obj1.DisplayKNum();
obj2.DisplayKNum();
cout << obj1.TYPE_A << endl;
cout << obj2.TYPE_A << endl;
cout << Object::TYPE_A << endl;
return 0;
}</span>
运行结果:
Object 10 ...
Object 20 ...
kNum=10kNum=20
100
100
100
~Object 20 ...
~Object 10 ...
二、复制构造函数
——只有单个形参,而且该形参是对本类类型对象的引用(常用const修饰),这样的构造函数称为复制构造函数。与默认构造函数一样,复制构造函数可由编译器隐式调用。
——复制构造函数、赋值操作符和析构函数总称为复制控制。
——如果类需要析构函数,则它也需要赋值操作符和复制构造函数,这是一个有用的经验法则,这个规则常称为三法则。
1、两种复制构造函数情况
(1)类类型初始化
<span style="font-size:14px;">string book1("9-999-99"); //直接初始化,不调用复制构造函数
string book2 = book1; //复制初始化
string book3(book1); //复制初始化
string book4 = "9-999-99"; //复制初始化</span>(2)当形参或返回值为类类型时,将由复制构造函数进行复制。
<span style="font-size:14px;">#include <iostream>
using namespace std;
class Myclass
{
public:
Myclass(int n) { number = n;}
Myclass(const Myclass &other)
{
number = other.number;
cout << "a ";
}
private:
int number;
};
void fun(Myclass p)
{
Myclass temp(p);
}
int main()
{
Myclass obj1(10), obj2(0);
Myclass obj3(obj1);
fun(obj3);
return 0;
}</span>运行结果:
a a a
解释:调用了三次拷贝构造函数,第一次时main中的Myclass obj3(obj1),第二次是实参obj3到fun形参p,第三次时函数fun中的Myclass temp(p)语句。
2、复制构造函数——形参和返回值为非引用时调用
功能:使用一个已经存在的对象来初始化一个新的同一类型的对象
声明:只有一个参数并且参数为该类对象的引用 Test::Test(const Test &other)
;
如果类中没有定义复制构造函数,则系统自动生成一个缺省复制构造函数,作为该类的公有成员,所做的事情也是简单的成员复制
<span style="font-size:14px;">#include <iostream>
using namespace std;
class Test
{
public:
// 如果类不提供任何一个构造函数,系统将为我们提供一个不带参数的
// 默认的构造函数
Test();
explicit Test(int num);
Test(const Test &other);
void Display();
Test &operator=(const Test &other);
~Test();
private:
int num_;
};
// 不带参数的构造函数称为默认构造函数
Test::Test() : num_(0)
{
cout << "Initializing Default" << endl;
}
Test::Test(int num) : num_(num)
{
cout << "Initializing " << num_ << endl;
}
Test::Test(const Test &other) : num_(other.num_)
{
cout << "Initializing with other " << num_ << endl;
}
Test::~Test()
{
cout << "Destroy " << num_ << endl;
}
void Test::Display()
{
cout << "num=" << num_ << endl;
}
Test &Test::operator=(const Test &other)
{
cout << "Test::operator=" << endl;
if (this == &other)
return *this;
num_ = other.num_;
return *this;
}
int main(void)
{
Test t(10);
//Test t2(t); // 调用拷贝构造函数
Test t2 = t; // 等价于Test t2(t);
cout << "........." << endl;
return 0;
}</span>
运行结果:
Initializing 10
Initializing with other 10
.........
Destroy 10
Destroy 10
解释:即调用了拷贝构造函数,destroy 的两个分别是t 和 t2。
3、拷贝构造函数调用的几种情况
当形参为非引用类类型的时候,将复制实参的值,类似的,以非引用类类型作返回值时,将返回return语句中值的副本。
当函数的形参是类的对象,调用函数时,进行形参与实参结合时使用。这时要在内存新建立一个局部对象,并把实参拷贝到新的对象中。理所当然也调用拷贝构造函数。还有一点,为什么拷贝构造函数的参数需要是引用? 这是因为如果拷贝构造函数中的参数不是一个引用,即形如CClass(const CClass c_class),那么就相当于采用了传值的方式(pass-by-value),而传值的方式会调用该类的拷贝构造函数,从而造成无穷递归地调用拷贝构造函数。
当函数的返回值是类对象,函数执行完成返回调用者时使用。也是要建立一个临时对象,再返回调用者。为什么不直接用要返回的局部对象呢?
因为局部对象在离开建立它的函数时就消亡了,不可能在返回调用函数后继续生存,所以在处理这种情况时,编译系统会在调用函数的表达式中创建一个无名临时对象,该临时对象的生存周期只在函数调用处的表达式中。所谓return 对象,实际上是调用拷贝构造函数把该对象的值拷入临时对象。如果返回的是变量,处理过程类似,只是不调用构造函数。
<span style="font-size:14px;">#include <iostream>
using namespace std;
class Test
{
public:
// 如果类不提供任何一个构造函数,系统将为我们提供一个不带参数的
// 默认的构造函数
Test();
explicit Test(int num);
Test(const Test &other);
~Test();
private:
int num_;
};
// 不带参数的构造函数称为默认构造函数
Test::Test() : num_(0)
{
cout << "Initializing Default" << endl;
}
Test::Test(int num) : num_(num)
{
cout << "Initializing " << num_ << endl;
}
Test::Test(const Test &other) : num_(other.num_)
{
cout << "Initializing with other " << num_ << endl;
}
Test::~Test()
{
cout << "Destroy " << num_ << endl;
}
void TestFun(const Test t1)
{
}
void TestFun2(const Test &t1)
{
}
Test TestFun3(const Test &t1)
{
return t1;
}
const Test &TestFun4(const Test &t1)
{
//return const_cast<Test&>(t1);
return t1;
}
int main(void)
{
Test t(10);
TestFun(t);
cout << "........" << endl;
return 0;
}</span>
(1)运行结果:
Initializing 10
Initializing with other 10
Destroy 10
.........
Destroy 10
解释:即在传参的时候调用了拷贝构造函数,函数返回时TestFun 的形参t 1生存期到了,在分割线输出之前销毁t1,最后destroy 的是 t。
(2)将TestFun(t); 换成 TestFun2(t);
运行结果:
Initializing 10
.........
Destroy 10
解释:参数为引用,即没有调用拷贝构造函数。
(3)将TestFun(t); 换成 t = TestFun3(t);
运行结果:
Initializing 10
Initializing with other 10
Test::operator=
Destroy 10
.........
Destroy 10
解释:从右到左的顺序,函数返回时会调用拷贝构造函数,接着调用赋值运算符,释放临时对象,最后释放t。如果没有用t 接收,不会调用operator= 而且临时对象也会马上释放。
(4)将TestFun(t); 换成 Test t2 = TestFun3(t);
运行结果:
Initializing 10
Initializing with other 10
.........
Destroy 10
Destroy 10
解释:函数返回调用拷贝构造函数,但没有再次调用拷贝构造函数,而且没有释放临时对象,可以理解成临时对象改名为t2 了。
(5)将TestFun(t); 换成const Test& t2 = TestFun3(t);
运行结果:
Initializing 10
Initializing with other 10
.........
Destroy 10
Destroy 10
解释:函数返回时调用拷贝构造函数,因为t2 引用着临时对象,故没有马上释放。
(6)将TestFun(t); 换成 Test t2 = TestFun4(t);
运行结果:
Initializing
10
Initializing with other 10
.........
Destroy 10
Destroy 10
解释:函数传参和返回都没有调用拷贝构造函数,初始化t2 时会调用拷贝构造函数。
(7)将TestFun(t); 换成 const Test& t2 = TestFun4(t);
运行结果:
Initializing 10
.........
Destroy 10
解释:函数传参和返回都没有调用构造函数,t2 是引用故也不会调用拷贝构造函数。
<span style="font-size:14px;">#include <iostream>
using namespace std;
class A
{
public:
A() {cout << "A";}
~A() {cout << "~A";}
};
class B
{
public:
B(A &a): _a(a) //_a(a)调用了拷贝构造函数
{
cout << "B" ;
}
~B() {cout << "~B";}
private:
A _a;
};
int main()
{
A a; //当你定义对象的时候,自动调用构造函数,输出A
B b(a); //输出B之前调用了A的拷贝构造函数
return 0;
} //当定义的对象的声明周期结束,系统自动调用析构函数进行释放,顺序按照构造函数的顺序逆序进行</span>
运行结果:
AB~B~A~A
【例子2】
<span style="font-size:14px;">#include <iostream>
using namespace std;
class A
{
public:
A() {cout << "A";}
~A() {cout << "~A";}
};
class B: public A //B以公有的方法继承类A
{
public:
B(A &a): _a(a) //_a(a)调用了拷贝构造函数
{
cout << "B" ;
}
~B() {cout << "~B";}
private:
A _a;
};
int main()
{
A a; //当你定义对象的时候,自动调用构造函数,输出A
B b(a); //输出B之前调用了A的拷贝构造函数
return 0;
} //当定义的对象的声明周期结束,系统自动调用析构函数进行释放,顺序按照构造函数的顺序逆序进行</span>
运行结果:
AAB~B~A~A~A。
解释:首先语句1构造一个A对象,输出A。然后语句2调用其父类的构造函数,输出A,然后B的构造函数执行如上。
参考:
C++ primer 第四版
Effective C++ 3rd
http://blog.csdn.net/jnu_simba/article/details/9173151
http://blog.csdn.net/zjf280441589/article/details/24862135
C++编程规范
版权声明:本文为博主原创文章,未经博主允许不得转载。
C++ Primer 学习笔记_19_类与数据抽象(5)_初始化列表(const和引用成员)、拷贝构造函数
标签:c++ c++ primer 类与数据抽象
原文地址:http://blog.csdn.net/keyyuanxin/article/details/47113207