构造函数
定义:名字与类名相同,可以有参数,不能有返回值(void也不行)
作用:对对象进行初始化,如给成员变量进行赋初值
性质:
(1)如果定义类时没有写构造函数,编译器会生成一个默认的无参数的构造函数,该函数不做任何操作
(2)如果定义了构造函数,则编译器不会生成默认无参数的构造函数
(3)对象生成时构造函数自动被调用,对象一旦生成,再也不能执行构造函数
(4)一个类可以有多个构造函数
为什么需要构造函数:
(1)执行必要的初始化操作
(2)有时对象没被初始化就使用,会导致程序出错。
eg:1 使用默认构造函数
class Complex{
private:
double real, imag;
public:
void Set(double r, double i);
}; // 编译器自动生成默认构造函数
Complex c1; // 默认构造函数被调用
Complex* pc = new Complex; // 默认构造函数被调用
eg:2 使用自定义构造函数
class Complex{
private:
double real, imag;
public:
Complex(double r, double i = 0); // 自定义构造函数
}; // 编译器自动生成默认构造函数
Complex::Complex(double r, double i)
{
real = i; imag = r;
}
Complex c1; // error 缺少构造函数的参数
Complex* pc = new Complex; // error, 没有参数
Complex c1(2); // ok 第二个参数可缺省
Complex c1(2, 4), c2(3, 5);
Complex* pc = new Complex(3, 4);
eg:3 可以有多个构造函数,参数个数或者类型不同(见本工程下Complex类)
构造函数在数组中的使用
class Test
{
public:
Test(int n){} // (1)
Test(int n, int m){} // (2)
Test(){} // (3)
};
Test array1[3] = {1, Test(1, 2)};
// 三个元素分别使用 (1)(2)(3)进行初始化
Test array2[3] = {Test(2, 3), Test(1, 2), 1};
// 三个元素分别使用 (2)(2)(1)进行初始化
Test* pArray[3] = {new Test(4), new Test(1, 2)};
// 两个元素分别使用(1)(2)初始化
复制构造函数
1 定义:只有一个参数,即对同类对象的引用
2 格式:X::X(X&) 或者 X::X(const X&) 后者以常量对象作为参数
3 如果没有定义复制构造函数,编译器自动生成默认复制构造函数。默认复制构造函数完成赋值功能。
4 eg:
class Complex{
private:
double real, imag;
};
Complex c1; // 调用缺省无参构造函数
Complex c2(c1); // 调用缺省的复制构造函数,将c2初始化成和c1一样。
5 如果自己定义复制构造函数,则默认的复制构造函数不存在
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);
6 不允许有形如 X::X(X)的构造函数
class CSample{
CSample(CSample c){} // 错误,这不是复制构造函数
}
7 复制构造函数起作用的三种情况
(1)当一个对象去初始化同类的另一个对象的时候
Complex c2(c1);
Complex c2 = c1; // 初始化语句,非赋值语句
(2)如果某一个函数有一个参数是类A的对象,那么该函数被调用时候,类A的复制构造函数将会被调用
eg:
class A
{
public:
A(){};
A(A& a){
cout << "Copy constructor called" << endl;
}
};
void Func(A a1);
int main(){
A a2;
Func(a2); // 调用复制构造函数初始化
return 0;
}
(3)如果函数的返回值是类A的对象时,则函数返回时,A的复制构造函数被调用
eg:
class A
{
public:
int v;
A(int n) {v = n;}
A(const A& a){
v = a.v;
cout << "Copy constructor called";
}
}
A Func(){
A b(4);
return b;
}
int main(){
// 调用复制构造函数,因为Func()函数返回值是b,则复制构造函数的参数是b
cout << Func().v << endl; return 0;
}
8 对象间的赋值,并不导致复制构造函数被调用(见本工程CMyclass.cpp)
//
// 演示对象间的赋值并不导致复制构造函数被调用
//
#include <iostream>
using namespace std;
class CMyclass{
public:
int n;
CMyclass()// 默认构造函数
{
cout << "默认构造函数被调用" << endl;
}
CMyclass(CMyclass& c) // 复制构造函数
{
n = 2 * c.n;
cout << "复制构造函数被调用"<< endl;
}
};
int main()
{
CMyclass c1, c2;
c1.n = 5;
c2 = c1; // 代表赋值 调用默认构造函数
CMyclass c4 = c1; // 代表初始化 需要调用复制构造函数
CMyclass c3(c1);
cout << c2.n << endl;
cout << c3.n << endl;
cout << c4.n << endl;
}
9 常量引用参数的使用
eg:
void func(CMyclass obj_){
cout << "fun" << endl;
}
(1)这样的函数,调用时生成形参会引发复制构造函数调用,开销比较大
(2)可以考虑使用CMclass&引用类型作为参数
(3)如果希望确保实参的数值在函数中不被改变,可以加上const关键字
eg:
void func(cosnt CMyclass& obj)
{
// 函数中任何试图改变obj值的语句都将时非法的
}
类型转换构造函数
类型转换构造函数
1 目的:实现类型的自动转换
2 定义:只有一个参数,而且不是复制构造函数的构造函数,一般就可以看做是转换构造函数‘
3 调用:当需要的时候,编译器会自动调用转换构造函数,建立一个无名的临时对象(或者临时变量)
4 例子(见本工程switchConstructor.cpp)
//
// 类型转换构造函数例子
//
#include <iostream>
using namespace std;
class Complex{
public:
double real, imag;
Complex(int i){
cout << "类型转换构造函数被调用" << endl;
real = i; imag = 0;
}
Complex(double r, double i){
real = r;
imag = i;
cout << "构造函数被调用"<< endl;
}
};
int main()
{
Complex c1(7, 8); // 调用构造函数
Complex c2 = 12; // 调用类型转化构造函数
c1 = 9; // 由于类型转换构造函数的存在,9被自动转换为一个临时Complex对象,然后将这个临时对象赋值给c1
cout << c1.real << "," << c1.imag << endl;
cout << c2.real << "," << c2.imag << endl;
return 0;
}
析构函数
1 定义:名字与类名相同,在前面加“~”,没有参数和返回值,一个类最多只能有一个析构函数。
2 目的:析构函数对象消亡时自动被调用。可以定义析构函数来在对象消亡前做善后工作,比如释放分配的空间等。
3 如果定义类的时候没有写析构函数,编译器会自动生成缺省的析构函数,缺省析构函数什么都不做。
4 如果定义了析构函数,则编译器不会生成缺省析构函数。
5 eg:(见本工程xigou.cpp)
//
// 析构函数用例
//
#include <iostream>
using namespace std;
class String{
private:
char* p;
public:
// 构造函数
String(){
p = new char[10];
cout << "构造函数被调用" << endl;
}
// 析构函数
~String(){
delete[] p;
cout << "析构函数被调用" << endl;
}
};
int main()
{
}
6 对象数组在生命周期结束时,对象数组的每个元素的析构函数都会被调用。
7 delete运算符导致析构函数被调用
eg:
Ctest* pTest;
pTest = new Ctest; // 构造函数被调用
delete pTest; // 析构函数被调用
------------------------------------
pTest = new Ctest[3]; // 构造函数被调用3次
delete[] pTest; // 析构函数调用3次
8 若new一个对象数组,那么用delete释放时应该写[]。否则只delete一个对象(调用一次析构函数)
9 重难点:析构函数在对象作为函数返回值返回后被调用(见本工程xigoudiaoyong.cpp)
//
// 析构函数在对象作为函数返回值返回后被调用例子
//
#include <iostream>
using namespace std;
class Myclass{
public:
~Myclass(){
cout << "调用析构函数" << endl;
}
};
Myclass obj;
Myclass fun(Myclass sobj) // 参数对象消亡也会导致析构函数被调用
{
return sobj; // 函数调用返回时生成临时对象返回
}
int main()
{
obj = fun(obj); // 函数调用的返回值(临时对象)被用过后,该临时对象析构函数被调用
return 0;
}
/**
* 函数调用分析:
* 首先调用fun,进入fun函数内,会生成一个形参对象sobj,它使用复制构造函数进行初始化
* 接着返回sobj,由于一个函数返回时候,这个函数内的局部变量,参数都会消亡,故sobj消亡,调用了析构函数。
* 继续生成了一个返回值对象,返回值是一个临时对象使用复制构造函数进行初始化
* 接着将临时对象的值赋值给obj,临时对象一般都是在包含临时对象的这条语句执行完后消亡,故执行完obj = fun(obj);语句后,临时对象消亡,调用析构函数
* 最后整个程序结束之后,全局对象自动消亡,调用析构函数
* */
构造函数和析构函数调用时机
1 注意:构造函数只是用来初始化,不用来分配存储空间。析构函数不负责回收整个对象所占的存储空间,它只是在对象存储空间被操作系统回收之前,做善后工作。
2 复制构造函数在不同编译器中的表现(不重要,了解即可)
3 new出来的对象,只有delete它才会消亡,如果不进行delete它是不会消亡的,哪怕整个程序都结束了,它也不会消亡。
4 下面举例说明
//
// 构造函数和析构函数调用时机案例.
//
#include <iostream>
using namespace std;
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;
} // 函数结束时,d2不会消亡(原因:静态局部变量在函数结束时候不会消亡,还会维持它原有的值,直到整个程序结束,静态局部变量才会消亡),d3会消亡
int main()
{
Demo d4(4); // 第二次输出
d4 = 6; // 由于有类型转换构造函数的存在,则可以直接吧整形转换成一个临时对象,接着将临时对象的值赋值给d4,临时对象执行完这条语句就会消亡,引发析构函数调用
cout << "main" << endl;
{
Demo d5(5); // 局部对象生成 调用构造函数
} // 大括号指向结束 局部对象消亡 调用析构函数
Func(); // 进入Func
cout << "main ends" << endl;
return 0;
} // 函数结束,局部变量d4消亡,此时d4的值是6
// 整个函数结束的时候,全局变量d1和静态变量d2消亡,消亡顺序(先构造的后析构)故先d2消亡,再d1消亡