复制构造函数
copy constructor
只有一个参数,即对同类对象的引用
X::X(X&)
X::X(const X&) // 能以常量对象作为参数
X::X(X) // 不允许
如果没有定义复制构造函数,那么编译器生成默认复制构造函数。默认的复制构造函数完成复制功能。
class Complex {
private:
double real, imag;
};
Complex c1; // 调用无参构造函数
Complex c2(c1); // 调用缺省的复制构造函数,将c2初始化成和c1一样
class Complex {
Complex (const Complex &c) {
real = c.real;
imag = c.imag;
cout << "Copy Constructor Called.";
}
};
当用一个对象去初始化同类的另一个对象时。
Complex c2(c1);
Complex c2 = c1; // 初始化语句,非赋值语句,与上句等价
c2 = c1; // 赋值语句如果某函数有一个参数是类A的对象,那么该函数被调用时,类A的复制构造函数将被调用。
void Func(A a1) {} // a1和a2的值未必相等,取决于复制构造函数如何编写
int main() {
A a2;
Func(a2);
return 0;
}如果函数的返回值是类A的对象时,则函数返回时,A的复制构造函数被调用。
为什么要自己写复制构造函数?
类型转换构造函数
目的:实现类型的自动转换
特点:只有一个参数,不是复制构造函数
编译系统会自动调用转换构造函数,建立一个临时对象/临时变量
class Complex {
public:
Complex(int i) { // 类型转换构造函数
}
Complex(double r, double i) {}
};
int main() {
Complex c1(7, 8);
Complex c2 = 12; // =是初始化的意思,不是赋值
c1 = 9; // 9被自动转换成一个临时Complex对象,该行执行完毕,临时对象被析构
}
析构函数
没有参数和返回值,一个类最多只有一个析构函数
~
对象消亡时,自动被调用,在对象消亡前做善后工作,如释放分配的空间等。
定义类时没写析构函数,则编译器生成缺省析构函数,不涉及释放用户申请的内存释放等清理工作(比如 new Complex,需要用delete释放)
对象数组生命期结束时,对象数组的每个元素的析构函数都会被调用。(main函数结束时)
delete运算导致析构函数调用
先被构造的对象最后被析构掉。(验证)main函数中的变量–>函数中的静态变量–>全局变量。
构造函数和析构函数在不同编译器中的表现,个别调用情况不一致,编译器有bug,代码优化措施。本课程针对C++标准。
静态成员变量和静态成员函数
静态成员:在定义前面加了static关键字的成员。
static int nTotalArea;
public:
static void PrintTotal();
普通成员变量每个对象有各自的一份,而静态成员变量一共就一份,为所有对象共享
sizeof运算符不会计算静态成员变量
普通成员函数必须具体作用于某个对象,而静态成员函数并不具体作用于某个对象,因此,静态成员不需要通过对象就能访问
CRectangle::PrintTotal(); // 类名::成员名
r.PrintTotal(); // 对象名.成员名
p->PrintTotal(); // 指针->成员名
int n = ref.nTotalNumber; // 引用.成员名
静态成员变量本质上是全局变量,哪怕一个对象都不存在,类的静态成员变量也存在
静态成员函数本质上是全局函数
设置静态成员这种机制的目的是将和某些类紧密相关的全局变量和函数写到类里面,看上去像一个整体,易于理解和维护
CRectangle::CRectangle(int w_, int h_) { // 是否所有构造函数都用这个构造函数初始化?有时会调用复制构造函数
w = w_;
nTotalNumber++;
}
CRectangle::~CRectangle() {
nTotalNumber--;
}
必须在定义类的文件中对静态成员变量进行一次说明或初始化,否则编译能通过,链接不能通过
int CRectangle::nTotalNumber = 0;
在静态成员函数中,不能访问非静态成员变量,也不能调用非静态成员函数
CRectangle::PrintTotal();
编译器会自动生成复制构造函数(形参、返回值、临时对象)
成员对象和封闭类
成员对象:一个类的成员变量是另一个类的对象
包含成员对象的类叫封闭类(Enclosing)
class CTyre {
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 w);
};
CCar::CCar(int p, int tr, int w): price(p), tyre(tr, w){}
如果CCar类不定义构造函数,则编译器不知道car.tyre该如何初始化(CTyre 没有默认构造函数)。没有参数则没有问题,用默认的构造函数(CEngine)
定义封闭类的构造函数时,添加初始化列表
当封闭类对象生成时,首先执行所有成员对象的构造函数,然后执行封闭类的构造函数
成员对象的构造函数调用顺序,和成员对象在类中的说明顺序一致,与在成员初始化列表中出现的顺序无关
当封闭类的对象消亡时,首先执行封闭类的析构函数,然后执行成员对象的析构函数
友元
Friend:友元函数、友元类
一个类的友元函数可以访问该类的私有成员
class 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); // 另一个类的成员函数 };
将一个类的成员函数(包括构造、析构函数)定义为另一个类的友元
A是B的友元类,A的成员函数可以访问B的私有成员
class CCar { friend class CDriver; };
友元类之间的关系不能传递,不能继承
this指针
C++程序到C程序的翻译
class CCar {
public:
int price;
void SetPrice(int p);
};
int main() {
CCar car;
car.SetPrice(100);
return 0;
}
C++ --> C
struct CCar {
int price;
};
void SetPrice(struct CCar *this, int p) { // 成员函数的翻译方式
this->price = p;
}
int main() {
struct CCar car;
SetPrice(&car, 100);
return 0;
}
Complex AddOne() {
this->real++; // 等价于 real++;
return *this;
}
this指针的作用就是指向成员函数所作用的对象
非静态成员函数中可以直接使用this来代表指向该函数作用的对象的指针
A *p = NULL;
p->Hello(); // -->Hello(p); 取决于Hello的实际内容如何!!
void Hello() { cout << "hello" << endl; }
-->void Hello(A *this) { cout << "hello" << endl; }
静态成员函数不能使用this指针,因为静态成员函数并不具体作用于某个对象。因此,静态成员函数的真实的参数的个数,就是程序中写出的参数个数。
常量对象、常量成员函数和常引用
常量对象
如果不希望某个对象的值被改变,则定义该对象的时候可以在前面加const关键字
常量成员函数
在类的成员函数说明后面可以加const关键字,则该成员函数成为常量成员函数
常量成员函数执行期间不应修改其所作用的对象。因此,在常量成员函数中不能修改成员变量的值(静态成员变量除外,不属于对象的一部分),也不能调用同类的非常量成员函数(静态成员函数除外)
class CTest {
public:
void GetValue() const;
};
两个成员函数,名字和参数表都一样,但是一个是const,一个不是,算重载,而非重复定义
const CTest t1;
CTest t2;
t1.GetValue(); // void GetValue() const;
t2.GetValue(); // void GetValue();
常引用
对象作为函数的参数时,生成该参数需要调用复制构造函数,效率比较低。用指针作参数,代码又不好看。可以用对象的引用作为参数。
void PrintfObj(Sample &o);
对象引用作为函数的参数有一定的风险。若函数中不小心修改了形参o,则实参也跟着变。可以用对象的常引用作为参数
void PrintfObj(const Sample &o);