构造函数
几个总结:
- 如果有一个有参构造函数,但又没有赋值初始化,必须再写一个无参构造函数,因为此时默认无参构造没了;
- 构造函数的意义(作用)
1.不需要手动调用构造函数来初始化对象;编译器会为我们生成调用构造函数代码
2.一些在定义时,必须要被初始化成员变量(下面三种)
a)没有默认构造函数的类对象;
b)const类型变量
c)引用类型变量
情况a)的实例(必须传入参数才能构造的对象)
class CBase
{
public:
CBase(int a) {};//不是默认构造函数,默认构造函数没有形参
~CBase();
};
class CHello {
public :
CHello() :Cbase(1) {};//没有默认构造函数的类对象必须初始化在初始化列表里面
private:
int m_nNuma;
CBase cbase; //没有默认构造函数的类对象
};
只能在构造函数初始化列表(初始化列表在冒号后面)中进行初始化,(初始化列表的顺序:按照成员变量定义的顺序进行初始化)
Class CLocation{
Public:
CLocation(int nX, int nY):m_X(nX),m_Y(nY)// 初始化列表在冒号后面
{}
Private:
int m_X , m_Y //初始化列表的顺序:按照成员变量定义的顺序进行初始化
}
构造函数的特征:
(1)函数名必须和类名一致
(2)无返回值
(3)可以有参数
(4)不能为虚函数
(5)可以类外定义(类名::)
默认构造函数的特征:
- 无形参
- 调用时机:构造对象的时候,如果没有往构造函数中传参,就默认调用该形式的构造函数,(注意:不要在构造函数中定义对象)
无参与有参构造的对比
#include <iostream>
using namespace std;
class Dog
{
public:
string name;
// 无参构造函数
Dog()
{
cout << "Dog's constructor!" << endl;
}
// 有参构造函数
Dog(string Name)
{
name = Name;
cout << "Dog's constructor with name!" << endl;
}
void run()
{
cout << name << " is running..." << endl;
}
};
int main()
{
Dog dog1;
dog1.name = "Wang Cai";
dog1.run();
Dog dog2("Xiao Bai");
dog2.run();
return 0;
}
运行结果:
Dog’s constructor!
Wang Cai is running...
Dog’s constructor with name!
Xiao Bai is running
从运行结果可以看出,构造函数是在生成对象时被调用的,并且不需要显示调用。
转换构造函数特征:
1.只有一个参数
2.参数的类型不是本类类型
3.explicit:用于禁止隐式转换
作用:将一个其他类型的数据转换成一个类的对象
转换构造实例:
#include "stdafx.h"
class CNumber {
public:
CNumber() { //1th
printf("默认构造\n");
}
CNumber(int nNum) { //2th
m_nNum = nNum;
printf("转换构造\n");
printf("%d\n",m_nNum );
}
CNumber(CNumber&C) { //3th
/* m_nNum = nNum;*/
printf("拷贝构造\n");
/*printf("%d\n", m_nNum);*/
}
~CNumber() { // 4th
printf("析构\n");
}
private:
int m_nNum;
};
void fun(const CNumber& obj) { //9th obj是调用函数fun时候创建匿名对象的别名
printf("fun\n");
}
int main()
{
CNumber objA(12); // 5th
CNumber objB = 34; //6th 执行这一步的时候它会找到上面的转换构造,
CNumber objC = objA; //再加上这一句就会调用拷贝构造
//同时生成对象objB,没有创建临时对象
fun(5); //8th
return 0; //7th
}
//5th->2th(转换构造)->5th->6th->2th(转换构造)->6th->8th->2th(转换构造)
//->8th->9th(执行全局函数)->8th->4th(析构)->7th->4th(析构)->7th->
//4th(析构)->7th->结束
//结论:转换构造函数的调用顺序:对象objA->对象objB->执行fun函数时候所创建的临时对象
//三次析构的顺序:执行fun函数时候所创建的临时对象->对象objB->对象objA
//一般情况下,有几个对象就至少执行几次析构
5th 是显式调用
6th,8th 是隐式调用
拷贝构造总结:
1. 对象以值传递的方式传入函数参数
#include "stdafx.h"
#include <ostream>
#include <iostream>
using namespace std;
class CExample
{
private:
int a;
public:
//构造函数
CExample(int b) //3th
{
a = b;
cout << "creat: " << a << endl;
}
//拷贝构造
CExample(const CExample& C) //4th
{
a = C.a;
cout << "copy" << endl;
}
//析构函数 //5th
~CExample()
{
cout << "delete: " << a << endl;
}
void Show()
{
cout << a << endl;
}
};
//全局函数,传入的是对象
void g_Fun(CExample C) //6th
{
cout << "test" << endl;
}
int main()
{
CExample test(1); //1th
//传入对象
g_Fun(test); //2th
getchar(); //7th
return 0;
}
//执行CExample test(1);时候,调用构造函数;然后调用g_Fun(test);调用g_Fun()时,
//会产生以下几个重要步骤:
//(1)test对象传入形参时,会先会产生一个临时变量,就叫 C 吧。
//(2)然后调用拷贝构造函数把test的值给C。(拷贝构造函数就是把一个对象的值拷给另一个), 整个这两个步骤有点像:CExample C(test);
//(3)等g_Fun()执行完后, 析构掉 C 对象。
//1th(定义并初始化)->3th(调用构造函数)->1th->2th(调用g_Fun函数)->4th(调用拷贝构造)->2th->6th(执行函数体)->5th(离开函数体就执行析构函数)->7th(结束)
2. 对象以值传递的方式从函数返回
#include "stdafx.h"
#include <ostream>
#include <iostream>
using namespace std;
class CExample
{
private:
int a;
public:
//构造函数
CExample(int b) //3th
{
a = b;
cout << a << endl;
}
//拷贝构造
CExample(const CExample& C) //4th
{
a = C.a;
cout << "copy" << endl;
cout << a << endl;
cout << C.a<<endl; //C.a=0
}
~CExample() //5th
{
cout << "delete: " << a << endl;
/*cout << "delete: " <<&C.a << endl;*/
}
void Show()
{
cout << a << endl;
}
};
//全局函数
CExample g_Fun() //2th,返回值为类 类型
{
CExample temp(0); //temp是一个对象,创建一个对象并初始化,
return temp;
}
int main()
{
g_Fun(); //1th
getchar();
return 0;
}
//当g_Fun()函数执行到return时,会产生以下几个重要步骤:
//(1).先会产生一个临时变量,就叫XXXX吧。
//(2).然后调用拷贝构造函数把temp的值给XXXX。整个这两个步骤有点像:CExample XXXX(temp);
//(3).在函数执行到最后先析构temp局部变量。
//(4).等g_Fun()执行完后再析构掉XXXX对象。
//1th(主函数里面调用全局函数)->2th(执行全局函数,碰到创建新对象时,跳转至构造函数)->3th(构造函数)->2th(回到全局
//函数继续执行,碰到返回值为类类型的对象时,跳至拷贝函数)->4th(执行拷贝构造函数)->2th(继续执行全局函数,返回到返
//回值那一行,(全局函数即将结束,跳转至析构函数)->5th(跳转至析构函数)->2th(续执行全局函数,返回到返回值那一行)
//->返回至1th,回到主函数的全局变量->5th再次跳转至析构函数,析构掉临时对象XXXX->回到主函数,执行getchar();->
//执行最后一步return 0;
3. 对象需要通过另外一个对象进行初始化;
1). CExample A(100);
2). CExample B = A;
3). // CExample B(A);
后两句都会调用拷贝构造函数。
虚析构总结
虚析构总结
// 测试.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
class Base
{
public:
Base() {
printf("base 父类构造\n");
};
virtual ~Base() { //1th在此行加不加virtual有一定区别,而对于一般成员函数,基类中有虚函数,则子类中对应的成员函数不一定声明为虚函数,因为子类继承了父类
printf("Base 析构\n");
};
void fun() { //2th
printf("base-fun\n");
};
};
class son:public Base
{
public:
son() {
printf("son 子类构造\n");
};
~son() {
printf("son 析构\n");
}
void fun() { //3th
printf("son-fun\n");
};
};
int main()
{
Base *p; //父类指针
son *pobj = new son ; //new 出子类对象指针,从堆空间中分配出来的,一般创建对象是在栈空间里面
p = pobj; //父类指针指向子类对象
p->fun(); //
delete p; //通过父类指针释放子类对象
/*p->fun();*/
getchar();
return 0;
}
1th有virtual时
1th无virtual时,
1th和2th同时有virtual时,