1.对象的构造(上)
1.1 问题
- 对象中成员变量的初始值是多少?
#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;
getchar();
return 0;
}
- 运行结果
1.2 对象的初始化
- 从程序设计的角度,对象只是变量,因此:
- 在栈上创建对象时,成员变量初始为随机值
- 在堆上创建对象时,成员变量初始为随机值 (gcc会为其赋0)
- 在静态存储区创建对象时,成员变量初始为0值
- 问题: 程序中如何对—个对象进行初始化?
- 一般而言,对象都需要—个确定的初始状态
- 解决方案 :
- 在类中提供—个public的initialize函数
- 对象创建后立即调用initialize函数进行初始化
#include <stdio.h>
class Test
{
private:
int i;
int j;
public:
int getI()
{
return i;
}
int getJ()
{
return j;
}
void initialize()
{
i = 1;
j = 2;
}
};
Test gt;
int main()
{
gt.initialize();
printf("gt.i = %d\n", gt.getI());
printf("gt.j = %d\n", gt.getJ());
Test t1;
//t1.initialize(); //若没有立即调用
printf("t1.i = %d\n", t1.getI());
printf("t1.j = %d\n", t1.getJ());
t1.initialize(); //在此处调用
Test* pt = new Test;
pt->initialize();
printf("t1.i = %d\n", t1.getI());
printf("t1.j = %d\n", t1.getJ());
printf("pt->i = %d\n", pt->getI());
printf("pt->j = %d\n", pt->getJ());
delete pt;
getchar();
return 0;
}
- 运行结果
- 存在的问题
- initialize只是—个普通函数,必须显示调用
- 如果未调用initialize函数,运行结果是不确定的
1.3 构造函数
- C++中可以定义与类名相同的特殊成员函数 ,这种特殊的成员函数叫做构造函数
- 构造没有任何返回类型的声明
- 构造函数在对象定义时自动被调用
#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;
getchar();
return 0;
}
- 运行结果
1.4 小结
- 每个对象在使用之前都应该初始化
- 类的构造函数用于对象的初始化
- 构造函数与类同名并且没有返回值
- 构造函数在对象定义时自动被调用
2.对象的构造(中)
2.1 对象定义和对象声明不同
- 对象定义-申请对象的空间并调用构造函数
- 对象声明-告诉编译器存在这样—个对象
Test t; //定义对象并调用构造函数
int main()
{
extern Test t; //告诉编译器存在名为t的Test对象
return 0;
}
2.2 带参数的构造函数
- 构造函数可以根据需要定义参数
#include <stdio.h>
class Test
{
public:
Test()
{
printf("Test()\n");
}
Test(int v)
{
printf("Tset(int v), v = %d\n", v);
}
};
int main()
{
Test t; // 调用 Test()
Test t1(1); // 调用Test(int v)
Test t2 = 2; // 调用Test(int v)
int i(100); // 赋值, 注意C++中对象初始化和赋值差距
printf("i = %d\n", i);
getchar();
return 0;
}
- 运行结果:
2.3 构造函数的调用
- 一般情况下,构造函数在对象定义时被自动调用
- 一些特殊情况下,需要手工调用构造函数
#include <stdio.h>
class Test
{
private:
int i;
int j;
int k;
/*这种与类名相同的成员函数叫做构造函数
构造函数在定义时可以有参数,但是没有任何返回类型的声明
*/
public:
Test(int v)
{
i = v;
j = v;
k = v;
}
void print()
{
printf("i = %d, j = %d, k = %d\n", i, j, k);
}
};
int main()
{
Test t1(4); // 自动调用构造函数
Test t2 = 5; // 自动调用构造函数
Test t3 = Test(6); // 主动调用构造函数
t1.print();
t2.print();
t3.print();
Test tA[3] = {Test(4), Test(5), Test(6)}; // 主动调用构造函数
for(int i = 0; i < 3; i++)
{
tA[i].print();
}
printf("Press any key to continue...");
getchar();
return 0;
}
- 运行结果
2.4 成员函数的重载
- 类的成员函数和普通函数一样可以进行重载,并遵守相同的重载规则
#include <stdio.h>
class Test
{
private:
int i;
int j;
int k;
public:
Test()
{
i = 0;
j = 0;
k = 0;
}
Test(int v)
{
i = v;
j = v;
k = v;
}
void print()
{
printf("i = %d, j = %d, k = %d\n", i, j, k);
}
void print(int v)
{
printf("v = %d\n", v);
}
};
int main()
{
Test t1(1);
Test t2 = 2;
Test t3 = Test(3);
Test t4;
Test t5;
t5.print(5);
t4.print();
t1.print();
t2.print();
t3.print();
printf("\n");
Test tA[3] = { Test(5),6,7 };
for(int i = 0; i < 3; i++)
{
tA[i].print();
}
printf("Press any key to continue...");
getchar();
return 0;
}
- 运行结果
2.5 小结
- 构造函数可以根据需要定义参数
- 构造函数之间可以存在重载关系
- 构造函数遵循C++中重载函数的规则
- 对象定义时会触发构造函数的调用
- 在—些情况下可以手动调用构造函数
3.对象的构造(下)
3.1 两个特殊的构造函数
- 无参构造函数:没有参数的构造函数
- 无参构造函数 :当类中没有定义构造函数时,编译器默认提供—个无参构造函数,并且其函数体为空
- 拷贝构造函数:参数为const class_name&的构造函数
- 当类中没有定义拷贝构造函数时,编译器默认提供一个拷贝构造函数,简单的进行成员变量的值复制
#include <stdio.h>
class Test
{
private:
int i;
int j;
int k;
public:
Test() // 当提供拷贝构造而不提供无参构造,编译器报错
{
}
Test(const Test &t) // 不写默认提供
{
i = t.i;
j = t.j;
k = t.k;
}
void print()
{
printf("i = %d, j = %d, k = %d\n", i, j, k);
}
};
int main()
{
Test t1;
Test t2 = t1;
t1.print();
t2.print();
printf("Press any key to continue...");
getchar();
return 0;
}
- 运行结果:
- 拷贝构造函数的参数不能用值传递,只能用引用。如果用值传递,在函数运行时,函数内部会产生一个新的副本(实参副本)接收实参的值(形参变量的值是实参变量的副本),从而又要调用拷贝构造函数,造成死循环。
- 如果类中一个构造函数都没有写,则编译器自动提供无参构造函数和拷贝构造函数。但如果已经存在某种构造函数,但编译器不会提供无参构造函数,但任然会提供拷贝构造函数。
3.2 拷贝构造函数
- 拷贝构造函数的意义
- 兼容C语言的初始化方式
- 初始化行为能够符合预期的逻辑
- 浅拷贝
- 拷贝后对象的物理状态相同
- 深拷贝
- 拷贝后对象的逻辑状态相同
- 编译器提供的拷贝构造函数只进行浅拷贝!
#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();
getchar();
return 0;
}
- 运行结果:
- 调用了默认拷贝构造函数,成员变量值一样,但当释放堆空间 t1.free(); t2.free(); 两次释放了堆空间的内存
- 解决方案:增加拷贝构造函数,实现深拷贝
#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;
}
Test(const Test &t)
{
i = t.i;
j = t.j;
p = new int;
*p = *t.p;
}
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("t1.i = %d, t1.j = %d, *t1.p = %d\n", t1.getI(), t1.getJ(), *t1.getP());
printf("t2.i = %d, t2.j = %d, t2.p = %p\n", t2.getI(), t2.getJ(), t2.getP());
printf("t2.i = %d, t2.j = %d, *t2.p = %d\n", t2.getI(), t2.getJ(), *t2.getP());
t1.free();
t2.free();
getchar();
return 0;
}
- 运行结果:
- 什么时候需要进行深拷贝?
- 对象中有成员指代了系统中的资源
- 成员指向了动态内存空间
- 成员打开了外存中的文件
- 成员使用了系统中的网络端口
.......
-
3.3 数组类的创建
- 需求:开发—个数组类解决原生数组的安全性问题
- 提供函数获取数组长度
- 提供函数获取数组元素
- 提供函数设置数组元素
- 编程实验
- Array.h
#ifndef _ARRAY_H_
#define _ARRAY_H_
class Array
{
private:
int mLength;
int *mSpace;
public:
Array(int length);
Array(const Array &obj);
int length(); // 获取数组长度
void setData(int index, int value); // 设置数组元素
int getData(int index); // 获取数组元素
void destory();
};
#endif
- Array.cpp
#include "Array.h"
Array::Array(int length)
{
if (length < 0)
{
length = 0;
}
mLength = length;
mSpace = new int[mLength];
}
Array::Array(const Array &obj)
{
mLength = obj.mLength;
mSpace = new int[mLength];
for (int i = 0; i < mLength; i++)
{
mSpace[i] = obj.mSpace[i];
}
}
int Array::length()
{
return mLength;
}
void Array::setData(int index, int value)
{
mSpace[index] = value;
}
int Array::getData(int index)
{
return mSpace[index];
}
void Array::destory()
{
mLength = -1;
delete[] mSpace;
}
- main.cpp
#include <stdio.h>
#include "Array.h"
int main()
{
Array a1(10); // 自动调用构造函数
for(int i = 0; i < a1.length(); i++)
{
a1.setData(i, i);
}
for(int i = 0; i < a1.length(); i++)
{
printf("Element %d: %d\n", i, a1.getData(i));
}
printf("\n");
Array a2 = a1; // // 自动调用拷贝构造函数
for(int i = 0; i < a2.length(); i++)
{
printf("Element %d: %d\n", i, a2.getData(i));
}
a1.destory();
a2.destory();
printf("Press any key to continue...");
getchar();
return 0;
}
- 运行结果
3.4 小结
- C++编译器会默认提供构造函数
- 无参构造函数用于定义对象的默认初始状态
- 拷贝构造函数在创建对象时拷贝对象的状态
- 对象的拷贝有浅拷贝和深拷贝两种方式
- 浅拷贝使得对象的物理状态相同
- 深拷贝使得对象的逻辑状态相同