问题:对象中成员变量的初始值是多少?
示例代码:成员变量的初始值
#include <stdio.h>
class Test
{
private:
int i;
int j;
public:
int getI() { return i; }
int getJ() { return j; }
};
Test gt;
int main()
{
printf("gt.i = %d\n", gt.getI());
printf("gt.j = %d\n", gt.getJ());
Test t1;
printf("t1.i = %d\n", t1.getI());
printf("t1.j = %d\n", t1.getJ());
Test* pt = new Test;
printf("pt->i = %d\n", pt->getI());
printf("pt->j = %d\n", pt->getJ());
delete pt;
return 0;
}
输出结果:
gt.i = 0
gt.j = 0
t1.i = 14998400
t1.j = 134514203
pt->i = 0
pt->j = 0
分析:
从程序设计的角度来看,对象也只是变量的一种。根据C语言变量的存储方式,我们也可以推测得出
1. Test gt; 是全局变量,也就是在静态存储区创建对象时,成员变量初始为0值。
2. Test t1; 是局部变量,也就是在栈上创建对象时,成员变量初始为随机值
3. Test t2; 是在堆空间申请的变量,成员变量初始为随机值
如何对对象的成员变量进行初始化?
C语言的解决方案
一般而言,我们在C语言是如何初始化结构体里的变量的呢?
就是专门写一个函数,在函数体里对结构体内的成员进行初始化。
根据C语言的方法,我们可以在类中提供一个初始化函数
那么我们在C++类中也可以提供一个初始化函数,专门将成员变量进行初始化。
但是这种方式也有缺陷:每次定义一个对象,我们就需要调用这个初始化函数。一旦我们忘记调用这个初始化函数,导致运行结果是不确定的。
C++构造函数解决成员变量初始化问题
为了解决上面的问题。C++提供了一个与类名相同的特殊成员函数,这个特殊的成员函数叫做构造函数。
1. 构造函数没有任何返回类型的声明
2. 构造函数在对象定义时自动被调用
示例代码:构造函数初探
#include <stdio.h>
class Test
{
private:
int i;
int j;
public:
int getI() { return i; }
int getJ() { return j; }
Test()
{
printf("Test() Begin\n");
i = 1;
j = 2;
printf("Test() End\n");
}
};
Test gt;
int main()
{
printf("gt.i = %d\n", gt.getI());
printf("gt.j = %d\n", gt.getJ());
Test t1;
printf("t1.i = %d\n", t1.getI());
printf("t1.j = %d\n", t1.getJ());
Test* pt = new Test;
printf("pt->i = %d\n", pt->getI());
printf("pt->j = %d\n", pt->getJ());
delete pt;
return 0;
}
输出结果:
Test() Begin
Test() End
gt.i = 1
gt.j = 2
Test() Begin
Test() End
t1.i = 1
t1.j = 2
Test() Begin
Test() End
pt->i = 1
pt->j = 2
分析:
1. 在程序中,我们可以看出,构造函数在对象定义时自动被调用
2. 类的构造函数用于对象的初始化
3. 构造函数与类同名并且没有返回值
多种类型的构造函数
我们上面介绍的构造函数也有缺陷的。因为除了定义类的对象如何初始化之外,类还需要控制拷贝、赋值和销毁对象时发生的行为。
那么我们如何让构造函数能适应各种各样的场合呢?
1. 类对象直接的赋值
2. 构造函数可以根据需要定义参数
带参数的构造函数
因为当我们需要创建多个对象时,每个对象的成员变量的初始值都是一样的。这样并不能适用一些特定的情况。
示例代码:带参数的构造函数
#include <stdio.h>
class Test
{
public:
Test()
{
printf("Test()\n");
}
Test(int v)
{
printf("Test(int v), v = %d\n", v);
}
};
int main()
{
Test t; // 调用 Test()
Test t1(1); // 调用 Test(int v)
Test t2 = 2; // 调用 Test(int v)
Test t3 = Test(3); // 调用 Test(int v)
int i(100);
printf("i = %d\n", i);
return 0;
}
输出结果:
Test()
Test(int v), v = 1
Test(int v), v = 2
Test(int v), v = 3
i = 100
分析:
1. 一个类中可以存在多个构造函数,前提是参数个数和参数类型不同(基于函数重载规则)
2. 构造函数的初始化与普通变量的初始化一样。(构造函数参数有多少个,初始化参数也必须有多少个)
3. 当没有定义任何构造函数时,编译器才会提供默认构造函数。所以当我们定义了带有参数的构造函数时,必须为它提供一个默认构造函数,否则编译出错。
拷贝构造函数
对于普通类型的对象来说,他们之间的复制是很简单的,例如:
int a = 100;
int b = a;
问题:那么我们类对象也可以像普通对象一样,直接将类对象的值拷贝过去吗?
示例代码:类对象拷贝的简单例子
#include <stdio.h>
class Test
{
private:
int i;
int j;
public:
Test()
{
i = 1;
j = 2;
}
int getI()
{
return i;
}
int getJ()
{
return j;
}
};
int main()
{
Test t1;
Test t2 = t1;
printf("t1.i = %d, t1.j = %d\n", t1.getI(), t1.getJ());
printf("t2.i = %d, t2.j = %d\n", t2.getI(), t2.getJ());
return 0;
}
输出结果:
t1.i = 1, t1.j = 2
t2.i = 1, t2.j = 2
分析:
系统是如何给test 对象里的成员函数对应拷贝的呢?如何准确的吧各个成员变量进行拷贝的呢?
实际上,编译器会我们提供一个拷贝构造函数来完成整个复制了。
问题:编译器为我们提供的拷贝构造函数并不能完全满足我们的工作需求,那么我们如何显示定义拷贝构造函数呢?
示例代码:自定义拷贝构造函数
//在上面代码的类成员中,加入下面代码即可
Test(const Test& t)
{
i = t.i;
j = t.j;
}
Test(const Test& t)就是我们自定义的拷贝构造函数。
可见,拷贝构造函数是一种特殊的构造函数,函数名称必须和类名相同,它的参数必须是本类型的一个引用变量。
实际上,我们自定义的拷贝构造函数可以选择哪些参数需要拷贝,哪些参数不拷贝。
注意:在编程中我们尽量要使用自定义拷贝函数,不仅可以选择需要拷贝的类成员,也可以阻止编译器形成默认的拷贝构造函数。
浅拷贝(拷贝构造函数资源冲突)
问题:当类中有指针成员时,使用默认拷贝构造函数会有什么问题吗?
示例代码:带指针成员的拷贝构造函数
#include <stdio.h>
class Test
{
private:
int i;
int j;
int* p;
public:
int getI()
{
return i;
}
int getJ()
{
return j;
}
int* getP()
{
return p;
}
Test(int v)
{
i = 1;
j = 2;
p = new int;
*p = v;
}
void free()
{
delete p;
}
};
int main()
{
Test t1(3);
Test t2(t1);
printf("t1.i = %d, t1.j = %d, *t1.p = %p\n", \
t1.getI(), t1.getJ(), t1.getP());
printf("t2.i = %d, t2.j = %d, *t2.p = %p\n", \
t2.getI(), t2.getJ(), t2.getP());
t1.free();
t2.free();
return 0;
}
输出结果:
t1.i = 1, t1.j = 2, *t1.p = 0x871f008
t2.i = 1, t2.j = 2, *t2.p = 0x871f008
* glibc detected ./a.out: double free or corruption (fasttop): 0x0871f008 **
//后面还有一大串数据,关于内存的
分析:
1. 从输出结果可以知道:当类中有指针成员时,使用系统默认创建该拷贝构造函数会出现内存操作错误。
2. 从t1.getP()和t2.getP()打印数据可以知道:指针指向地址是相同的,都是0x871f008。所以当后面我们free()释放内存时,就会出现内存重复释放的情况。导致运行出错。
C++对于这种情况解析为:浅拷贝,也就是说拷贝后对象的物理状态相同(共用一个资源)。所以编译器提供的拷贝构造函数只进行浅拷贝。
深拷贝(解决构造函数资源问题)
问题:我们如何解决浅拷贝造成的问题?
依然是自定义拷贝构造函数,在拷贝构造里需要重新申请资源。
示例代码:浅拷贝的解决方案
//添加这段代码到上面代码的类中即可
Test(const Test& t)
{
i = t.i;
j = t.j;
p = new int;
*p = *t.p;
}
深拷贝的应用场景
- 对象中有成员指代了系统中的资源
- 成员指向了动态内存空间
- 成员打开了外存中的文件
- 成员使用了系统中的网络端口