复制构造函数(copy constructor)
1.基本概念
(1)只有一个参数,即对同类对象的引用。
(2)形如X::X( X & )或者X::X(const X &) //后者能以常量对象作为参数
(3)若没定义,编译器生成默认复制构造函数。
class Complex{
private:
double real,imag;
}
Complex c1;//调用缺省无参构造函数
Complex c2(c1);//调用缺省的复制构造函数,将c2初始化为c1;把c1的所有成员变量复制给c2
(4)如果定义的自己的复制构造函数,则默认的复制构造函数不存在。
class Complex{
public:
double real,imag;
Complex(){}
Complex(const Complex &c){
real = c.real;
imag = c.imag;
cout<<"Copy Constructor called";
}
};
Complex c1;
Complex c2(c1);
*注意,只能是本类的引用&,不允许形如X::X( X )的构造函数。
2.复制构造函数起作用的三种情况
(1)用一个对象去初始化同类的另一个对象
Complex c2(c1);
Complex c2 = c1;//等价,初始化语句,非赋值语句
c2 = c1;//赋值语句,c2已生成,无需构造函数
(2)如果某函数有一个参数是类A的对象,那么该函数被调用时,类A的复制构造函数将被调用。
void Func (A a1){}
int main(){
A a2;
Func(a2);//若类A的构造函数不是默认的,Func的形参不一定和a2相等
return 0;
}
(3)如果函数的返回值是类A的对象时,返回时A的复制构造函数被调用
class A{
public:
int v;
A(int n){v = n;};
A(const A & a){
v = a.v;
cout<<"Cypy constructor called"<<endl;
}
}
A Func(){
A b(4);
return b;
}
int main(){
A a=Func();
cout<<a.v<<endl;
return 0;
}
输出结果:
Copy constructor called
4
3.为什么要自己写复制构造函数?
面向对象有时候必须要自己写复制构造函数,否则会出错
类型转换构造函数
1.
(1)目的:实现类型的自动转换
(2)特点: -只有一个参数 -不是复制构造函数
(3)编译系统会自动调用 -> 转换构造函数 -> 建立一个 临时对象/临时变量
class Complex{
public:
double real,imag;
Complex(int i){//类型转换构造函数
cout<<"intConstructor called"<<endl;
real = i; imag = 0;
}
Complex(double r,double i){
real = r; imag = i;
}
};
int main(){
Complex c1(7,8);
Complex c2 = 12;//类型转换,对c2进行初始化,不对应生成临时对象
c1 = 9; //赋值语句,类型不同,自动调用类型转换,生成临时对象,以9为实参,调用Complex(int i)初始化,将临时对象的值赋值给c1
cout<<c1.real<<","<<c1.imag<<endl;
return 0;
}
析构函数(Destructor)
1.成员函数的一种:
(1)名字与类名相同,在前面加"~"
(2)没有参数和返回值
(3)一个类最多只有一个析构函数
2.概念
(1)对象消亡时 -> 自动被调用 -> 善后工作,释放分配的空间等
(2)定义了析构函数,编译器不生成缺省析构函数
(3)定时类时没写,编译器生成缺省析构函数 - 不会做什么,不会帮助释放用户申请的内存
3.
class String{
private:
char * p;
public:
String(){
p = new char[10];
}
~String();
}
String::~String(){
delete [] p;//数组得写[],否则只delete了指针第一个
}
4.析构函数和数组
(1)对象数组生命期结束时 -> 对象数组的每个元素的析构函数都会被调用
class Ctest{
public:
~Ctest(){cout<<"destructor called"<<endl;}
}
int main(){
Ctest array[2];
cout<<"End Main"<<endl;
return 0;
}
输出:
End Main
destructor called
destructor called
(2)
Ctest * pTest;
pTest = new Ctest;//构造函数调用
delete pTest;//析构函数调用
-----
pTest = new Ctest[3];//构造函数调用3次
delete [] pTest;//析构函数调用3次
5.例题
class Demo{
int id;
public:
Demo(int i){
id = i;
cout<<"id="<<id<<"Constructed"<<endl;
}
~Demo(){
cout<<"id="<<id<<"Destructed"<<endl;
}
};
Demo d1(1);
void Func(){
static Demo d2(2);//静态在整个程序结束时消亡
Demo d3(3);
cout<<Func"<<endl;
};
int main(){
Demo d4(4);
d4 = 6; //临时对象 id=6constructed和destructed
cout<<"main"<<endl;
{ Demo d5(5); } //自己的作用域,对象生命周期离开后消亡
Func();
cout<<"main ends"<<endl;
return 0;
}
输出:
id = 1 Constructed
id = 4 Constructed
id = 6 Constructed
id = 6 Destructed
main
id = 5 Constructed
id = 5 Destructed
id = 2 Constructed
id = 3 Constructed
Func
id = 3 Destructed
main ends
id = 6 Destructed
id = 2 Destructed
id = 1 Destructed //先被构造的对象最后被析构
静态成员变量(Static Variable)和静态成员函数(static member function)
1.静态成员:加static关键字
class CRect{
private:
int w,h;
static int nTotalArea;//静态成员变量
static int nTotalNumber;
public:
CRect(int w_,int h_);
~CRect();
static void PrintTotal();//静态成员函数
}
2.普通成员变量每个对象各自有一份,静态成员变量一共就一份,为所有对象共享。
3.sizeof运算符不会计算静态成员变量
class CMyclass{
int n;
static int s;
}
则sizeof(CMYclass)等于4
4.普通成员函数必须具体作用于某个对象,而静态成员函数并不具体作用于某个对象
*因此静态成员不需要通过对象就能访问
5.如何访问静态成员
(1)类名::成员名
CRect::PrintTotal();//访问函数
CRect::nTotalArea;//访问变量
(2)对象名.成员名 / 引用.成员名
CRect r; r.PrintTotal();//并不是作用在r对象上,只是个形式
(3)指针->成员名
CRect * p = &r; p->PrintTotal();
6.基本概念
(1)本质上是全局变量,哪怕一个对象都不存在,类的静态成员变量也存在
(2)静态成员函数本质上是全局函数
(3)目的将和类紧密相关的全局变量(Global variable)和函数写到类里,易维护和理解(封装)
7.函数例子(续)
CRect::CRect(int w_,int h_){
w = w_;
h = h_;
nTotalNumber ++;
nTotalArea += w*h;
}
CRect::~CRect(){
ntotalNumber --;
nTotalArea -= w*h;
}
void CRect::PrintTotal(){
cout<<nTotalNumber<<","<<nTotalArea<<endl;
}
8.必须在定义类的文件中对静态成员变量进行一次声明或初始化,否则编译能通过,链接不能通过(?)
int CRect::nTotalNumber = 0;
int CRect::nTotalArea = 0;
int main(){
CRect r1(3,3),r2(2,2);
//cout<<CRect::nTotalNumber;//Wrong,私有
CRect::PrintTotal();
r1.PrintTotal();
return 0;
}
9.注意:在静态成员函数中,不能访问非静态成员变量(不是作用于某对象上的),也不能调用非静态成员函数。
void CRect::PrintTotal(){
cout<<w<<","<<nTotalNumber<<","nTotalArea<<endl;//错的!
}
CRect::PrintTotal;//w到底是属于哪个对象的?
10.函数例子(续)的缺陷
(1)有时候会调用复制构造函数,隐藏的CRect对象被生成
-调用一个以CRect类对象作为参数的函数时
-调用一个以CRect类对象作为返回值的函数时
*生成的时候没有增加,析构的时候(作为函数返回值的时候)减少了,总数减少了
(2)解决办法:为CRect类写一个复制构造函数
CRect::CRect(CRect & r){
w = r.w; h = r.h;
nTotalNumber++;
nTotalArea += w*h;
}
成员对象和封闭类
1.概念
(1)成员对象:一个类的成员变量是另一个类的对象
(2)封闭类(Enclosing):包含成员对象的类
2.例子
class CTyre{ //轮胎类
private:
int radius;
int width;
public:
CTyre(int r, int w):radius(r),width(w){} //初始化新方式:初始化列表
};
class CEngine{ //引擎类
}
class CCar{ //汽车类 -> "封闭类",包含了成员对象
private:
int price; //价格
CTyre tyre;
CEngine engine;
public:
CCar(int p,int tr,int tw);
};
CCar::CCar(int p,int tr,int w):price(p),tyre(tr,w){};
int main(){
CCar car(20000,17,225);
return 0;
}
3.如果CCar类不定义构造函数,则
CCar car; //error, 编译出错
(1)编译器不知道car.tyre该如何初始化
(2)car.engine的初始化没有问题:用默认构造函数
(3)生成封闭类对象的语句->明确"对象中的成员对象"->如何初始化
4.封闭类构造函数的初始化列表
(1)定义封闭类的构造函数时,添加初始化列表:
类名::构造函数(参数表):成员变量1(参数表),成员变量2(参数表),...
{...}
(2)成员对象初始化列表中的参数
-任意复杂的表达式
-函数/变量/表达式中的函数,变量有定义
5.调用顺序
当封闭类对象生成时,
(1)执行所有成员对象的构造函数
(2)执行封闭类的构造函数
成员对象的构造函数调用顺序,
(1)和成员对象在类中的说明顺序一致
(2)与在成员初始化列表中出现的顺序无关
当封闭类的对象消亡时,
(1)先执行封闭类的析构函数
(2)执行成员对象的析构函数
*析构函数顺序和构造函数的调用顺序相反
友元(Friend)
1.友元函数
一个类的友元函数可以访问该类的私有成员
class CCar; //提前声明CCar类
class CDriver{
public:
void ModifyCar(CCar * pCar);//改装汽车
};
class CCar{
private:
int price;
friend int MostExpensiveCar(CCar cars [], int total);//声明友元
friend void CDriver::ModifyCar(CCar * pCar);
};
2.将一个类的成员函数(包括构造,析构函数)定义为另一个类的友元
class B{
public:
void function();
};
class A{
friend void B::function();
};
3.友元类
A是B的友元类->A的成员函数可以访问B的私有成员
class CCar{
private:
int price;
friend class CDriver; //声明CDriver为友元类
};
class CDriver{
public:
CCar myCar;
void ModifyCar(){ //改装汽车
myCar.price += 1000; //CDriver是CCar的友元类->可以访问其私有成员
}
};
int main(){return 0;}
*注意:友元之间的关系不能传递,不能继承
this指针
1.其作用就是指向成员函数所作用的对象
2.this指针和静态成员函数
静态成员函数中不能使用this指针,因为静态成员函数并不具体作用于某个对象
因此,静态成员函数的真实的参数的个数,就是程序中写出的参数个数
常量对象、常量成员函数和常引用
1.如果不希望某个对象的值被改变,则定义该对象的时候在前面加const
2.在类的成员函数说明后面加const,该成员函数成为常量成员函数
(1)常量成员函数执行期间不应该修改其所作用的对象
(2)因此,常量成员函数中不能修改成员变量的值(静态成员除外,不属于对象一部分)
(3)不能调用同类的非常量成员函数(非常量成员函数可能修改成员变量,静态成员函数除外)
3.例子
class Sample{
public:
int value;
void GetValue() const;
void func(){};
Sample(){};
};
void Sample::GetValue() const {
value = 0; //wrong
func();//wrong
}
int main(){
const Sample o;
o.value = 100; //error
o.func(); //error
o.GetValue();//OK
return 0;
}//Dev C++中要为Sample类编写无参构造函数,visual 2010中不要
4.常量成员函数的重载
(1)两个成员函数,名字和参数表都一样,但是一个是const,一个不是,算重载
5.常引用
(1)引用前面加const,不能修改引用的变量
(2)对象作为函数的参数时,生成该参数需要调用复制构造函数,效率低; 用指针做参数代码难看,所以用常引用