问题:构造函数是什么原理呢?在Test t1(10, 20);中,t1是一个类对象,不是一个函数名, 怎么会和函数调用一样直接双括号加实参呢?他的实现原理是什么呢? 同样Test t1 = Test(10, 20);该构造函数又没有返回值,怎么会作为右值呢?
class Test
{
public:
Test(int x, int y)
{
m_x = x;
m_y = y;
}
private:
int m_x;
int m_y;
};
main::
Test t1(10, 20);
Test t1 = Test(10, 20);/*产生一个匿名类对象,将该匿名类对象转正为t1变量,未调用等号操作符进行赋值操作*/
如果显示定义一个构造函数和析构函数,那么就会覆盖掉默认的构造函数和析构函数
类中会有一个默认的无参构造函数,一个默认的拷贝构造函数,一个默认的等号操作符
1.当没有任何显示的构造函数(显式的无参构造函数,显式的有参构造函数,显式的拷贝构造函数),默认无参构造函数都会被调用
2.当没有**显示拷贝构造函数**,默认的拷贝构造函数就会调用
3.当没有**显示的析构函数**,默认的析构函数就会被调用
编译器会有一个默认的拷贝构造函数
单纯的将另一个对象的成员变量拷贝给自己
void operator=(const Test& another)
{
m_x = another.m_x;
m_y = another.m_y;
}
Test(const Test& another)
{
m_x = another.m_x;
m_y = another.m_y;
}
main::
//调用有参数的构造函数
Test t2(10, 20);
t2.printTest();
//如果不写Test(const Test& another),系统会自动加上这种构造函数,其实默认的拷贝构造函数
//做的就是将另一个对象的成员变量全部拷贝给自己的成员变量
Test t3(t2);
t3.printTest();
//两种调用拷贝构造函数的方法是等价的
Test t4 = t2;
t4.printTest();
//----------------分隔符------------
//这个是定义t5时调用无参的构造函数
Test t5;
//等号操作符重载,这是一个赋值操作
t5 = t1;
//注意,拷贝构造函数会引出深拷贝和浅拷贝问题
void test1()
{
//构造函数:谁先创建,先调用谁
Test t1(10, 20);
Test t2(t1);//Test t2 = t1;
//析构函数:谁先创建,后调用谁
//原理就是栈的入栈出栈,对象定义后进行压栈,释放时进行出栈
}
void func(Test t)//Test t = t1; //Test t 的拷贝构造函数
{
cout << "func begin..." << endl;
t.printT();
cout << "func end..." << endl;
}
void test3()
{
cout << "test3 begin..." << endl;
Test t1(10, 20);
func(t1);
cout << "test3 end..." << endl;
}
输出:
test3 begin…
Test(int x, int y)…
Test(const Test &)… //调用fun时,相当于 Test t = t1,调用t的拷贝构造函数
func begin…
x = 10, m_y = 20 //调用t.printT();
func end… //cout << “func end…” << endl;
~Test()… //调用t的析构函数
test3 end… //cout << “test3 end…” << endl;
~Test()… //调用t1的析构函数
Test func2(){
cout << "func2 begin..." << endl;
Test temp(10, 20);
temp.printT();
cout << "func2 end..." << endl;
return temp;//这一步调用拷贝构造函数,匿名的对象 = temp 匿名对象.拷贝构造(temp)
}//↑ 这个临时变量temp到这个位置时才会调用析构函数
void test4(){
cout << "test4 being.. " << endl;
func2();/*返回一个匿名对象。 当一个函数返回一个匿名对象的时候,函数外部没有任何变量去接收它, 这个匿名对象将不会再被使用,(找不到), 编译会直接将个这个匿名对象*/
//回收掉,而不是等待整改函数执行完毕再回收.
//匿名对象就被回收。
cout << "test4 end" << endl;
}
输出:
test4 being..
func2 begin…
Test(int x, int y)…
x = 10, m_y = 20
func2 end…
Test(const Test &)…
~Test()…//先将temp析构
~Test()…//返回的临时变量因为没有变量承接,func2()调用完毕后编译器就就把这个临时变量析构
test4 end
Test& func2(){
cout << "func2 begin..." << endl;
Test temp(10, 20);
temp.printT();
cout << "func2 end..." << endl;
return temp;
}
void test4(){
cout << "test4 being.. " << endl;
Test t1 = func2();//返回一个temp的引用
t1.printT();
cout << "test4 end" << endl;
}
输出:
test4 being..
func2 begin…
Test(int x, int y)…
x = 10, m_y = 20
func2 end…
~Test()… //将temp析构掉
Test(const Test &)… //拷贝构造,Test t1 = func2();会返回一个temp的引用
x = -858993460, m_y = -858993460
test4 end
~Test()…
Test func2(){
cout << "func2 begin..." << endl;
Test temp(10, 20);
temp.printT();
cout << "func2 end..." << endl;
return temp;
}//匿名的对象 = temp 匿名对象.拷贝构造(temp)
void test5(){
cout << "test 5begin.. " << endl;
Test t1 = func2();
//会不会触发t1拷贝构造来 t1.拷贝(匿名)?
//并不会触发t1拷贝,而是 将匿名对象转正 t1,
//把这个匿名对象 起了名字就叫t1.
cout << "test 5 end.." << endl;
}
输出:
test 5begin..
func2 begin…
Test(int x, int y)…
x = 10, m_y = 20
func2 end…
Test(const Test &)…
~Test()…
test 5 end..
~Test()…
Test func2(){
cout << "func2 begin..." << endl;
Test temp(10, 20);
temp.printT();
return temp;
}
void test6(){
cout << "test6 begin..." << endl;
Test t1; //t1已经被初始化了。
t1 = func2();
t1.printT();
cout << "test6 end.." << endl;
}
输出:
test6 begin…
Test()…
func2 begin…
Test(int x, int y)…
x = 10, m_y = 20
func2 end…
Test(const Test &)…
~Test()…
Test(const Test &)…
~Test()… //匿名对象在完成赋值后就会被释放掉
x = 10, m_y = 20
test6 end..
~Test()…
触发时机:调用了类中默认的拷贝构造函数或者默认的等号操作符
class A
{
private:
int _ma;
int _mb;
char *_mch;
public:
A(int a, int b, char *ch)
{
cout << "A(int a, int b, char *ch)" << endl;
_ma = a;
_mb = b;
int len = strlen(ch);
_mch = (char *)malloc(sizeof(char)*(len + 1));
strcpy(_mch, ch);
}
//A(A &_newa)
//{
//}
~A()
{
cout << "~A()" << endl;
if (_mch != NULL)
{
free(_mch);
_mch = NULL;
}
}
};
void fun1()
{
A a1(10, 20, "lily");
A a2(a1);
}
//定义a1时调用有参数的构造函数,在堆区分配了块内存空间,将文字常量区的内容拷贝到该区域
//定义a2时调用默认的拷贝构造函数,默认的拷贝构造函数的主要逻辑就是将a1内存中的所有内容拷贝到a2中
//这种做法虽然对普通形参没有太大的影响,但是对于指针变量而言,隐藏了一个错误
//默认的拷贝构造函数将a1中的内容全部拷贝到a2中后,a2._mch也会指向a1._mch所指向的内存区域
//在fun1函数结束时,a2先调用析构函数,将a2._mch所指向的内存区域释放掉
//接着a1调用析构函数将a1._mch所指向的内存区域再释放一遍,这样对同一块内存区域释放两次,程序会报错
//解决这个问题,可以通过一个深拷贝来完成,在类中定义一个拷贝构造函数,再在堆中创建一个内存区域
A(A &_newa)
{
_ma = _newa._ma;
_mb = _newa._mb;
int len = strlen(_newa._mch);
_mch = (char *)malloc(sizeof(char)*(len + 1));
strcpy(_mch, _newa._mch);
}
//这样两个对象的两个指针分别指向两块不同的内存区域
1).类对象中包含另一个类的对象
2).如果类对象中包含两个或多个类的对象,在构造函数的初始化列表中的成员对象的初始化顺序
class A
{
public:
A(int a)
{
cout << "A()..." << a << endl;
m_a = a;
}
~A()
{
cout << "~A()" << endl;
}
void printA()
{
cout << "a = " << m_a << endl;
}
private:
int m_a;
};
class B
{
public:
B(A &a1, A &a2, int b) :m_a1(a1), m_a2(a2)//调用默认的拷贝构造函数
{
//如果没有构造函数形参列表
//这样定义一个类B中包含有两个类A的对象,在创建B的对象时,初始化成员变量m_a1,m_a2就出现了问题
//1.不能直接m_a1 = a1,这是调用类A的等号操作符进行赋值
//2.也不能直接 m_a1(a1);这样是吧m_a1当成函数来看待,当然这样是错误的
//所以将两个成员变量m_a1,m_a2放在形参列表中初始化
cout << "B(A &a1, A &a2, int b)" << endl;
m_b = b;
}
//构造对象成员的顺序跟初始化列表的顺序无关
//跟成员变量的定义顺序有关
//定义在前的先被初始化
B(int a1, int a2, int b) : m_a1(a1), m_a2(a2)
// B(int a1, int a2, int b) : m_a2(a2), m_a1(a1)
{
cout << "B(int, int, int)..." << endl;
m_b = b;
}
void printB()
{
cout << "b = " << m_b << endl;
m_a1.printA();
m_a2.printA();
}
~B()
{
cout << "~B().." << endl;
}
private:
int m_b;
A m_a1;
A m_a2;
};
void test1()
{
A a1(10), a2(100);
B b(a1, a2, 1000);
b.printB();
}
new和malloc一样都是在堆中分配一块内存,然后返回该内存在堆中的地址.
delete和free一样,都是将堆中的指定内存释放掉
用new开辟的空间可以用free来释放掉
用malloc开辟的空间可以用delete来释放掉
A).malloc和free是标准库stdlib.h中定义的函数,既然是函数,那在调用时就存在入栈出栈操作.而new和delete关键字是C++的操作符,根C中的sizeof一样,不存在函数调用时的入栈出栈操作
B).用6)中的类A来说
A *temp = (A*)malloc(sizeof(A));
//此时malloc不会将成员变量初始化,如果这时候调用temp.printA()输出的则是乱码
A* temp = new A(10);
//new关键字可以在堆中创建一个A空间,同时调用A的构造函数将该空间进行了初始化操作,返回该空间的地址
C).new和delete会触发类的构造函数和析构函数
class Dog
{
public:
Dog(int id,char *name)
{
cout << "Dog(int id)" << endl;
m_id = id;
int len = strlen(name);
m_name = (char *)malloc(sizeof(char)*(len+1));
strcpy(m_name,name);
}
~Dog()
{
cout << "~Dog()" << endl;
if(m_name != NULL)
{
free(m_name);
m_name = NULL;
}
}
void showDog()
{
cout << "showDog()" << endl;
}
private:
int m_id;
char *m_name;
};
void fun1()
{
Dog *d1 = new Dog(10,"SUNNY");
d1->showDog();
if (d1 != NULL)
{
delete d1;
}
}
/*
Dog(int id)
showDog()
~Dog()
*/
//在堆中创建了一个类的对象空间,里面有int,和char*两个成员---->空间A
//在构造函数中另外创建了一个m_name空间,用来存储Dog的名字---->空间B
//调用 delete d1;时delete负责释放的是“空间A”,它触发了类的析构函数,而析构函数负责释放“空间B”
//析构函数不是释放对象本身,而是负责释放对象在堆上额外开辟的内存空间
static成员变量必须在类的外部进行初始化
类型 类名::静态成员变量 = 值
class Box
{
private:
int len;
int width;
public:
static int high;
Box(int l, int w)
{
len = l;
width = w;
}
void volume()
{
cout << "high = " << high << endl;
cout << "volume = " << len*width*high << endl;
}
};
int Box::high = 10;//static成员变量初始化一定要在类外进行
Box b1(10, 20);
b1.volume();
Box::high = 20;//static成员变量放在public区,可以在类外进行修改访问
b1.volume();
class Box2
{
private:
int len;
int width;
static int high;
public:
Box2(int l, int w)
{
len = l;
width = w;
}
void volume()
{
cout << "high = " << high << endl;
cout << "volume = " << len*width*high << endl;
}
static void setHigh(int h)//如果static成员变量放在private区域,进行修改时必须通过静态成员函数进行修改
{
high = h;
}
};
Box2 b2(10, 20);
b2.volume();
Box2::setHigh(20);//因为是静态的,可以通过类名加::进行访问
b2.volume();
cout << "sizeof(Goods) = " << sizeof(Goods) << endl;
//8个字节,int len; int width;
//static int high;在data区,不包含在类内