1、成员变量的初始值
#include <stdio.h>
class Test
{
private:
int i;
int j;
public:
int getI() { return i; }
int getJ() { return j; }
};
Test gt; // 全局对象 全局区,统一初始值为0
int main()
{
printf("gt.i = %d\n", gt.getI()); // 0
printf("gt.j = %d\n", gt.getJ()); // 0
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;
}
2、对象的初始化
从程序设计的角度,对象只是变量,因此:
- 在栈上创建对象时,成员变量初始为随机值
- 在堆上创建对象时,成员变量初始为随机值
- 在静态存储区创建对象时,成员变量初始为0
静态存储区包括了全局变量和
static
修饰的局部变量
需要解决的问题:使类的成员变量不管在哪个存储区进行定义,它的初始值都是固定的。
对象的初始化:
- 一般而言,对象都需要一个确定的初始状态
- 解决方案:
- 在类中提供一个
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());
Test* pt = new Test;
pt->initialize();
printf("pt->i = %d\n", pt->getI());
printf("pt->j = %d\n", pt->getJ());
delete pt;
return 0;
}
这种方式存在的问题:
initialize
只是一个普通函数,必须显示调用- 如果未调用
initialize
函数,运行结果是不确定的
这个初始化函数在对象创建之手就必须马上调用,新建对象之手,需要人工手动添加initialize()
函数,如果可以有一个函数在创建对象后自动调用,初始化成员变量就是极好的。
于是C++出现了构造函数来解决这个问题
3、构造函数
C++中可以定义与类名相同的特殊成员函数:构造函数
- 构造函数没有任何返回类型的声明
- 构造函数在对象定义时自动被调用
#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() {
i = 1;
j = 2;
}
};
Test gt;
int main()
{
//gt.initialize();
printf("gt.i = %d, gt.j = %d\n", gt.getI(), gt.getJ());
Test t1;
//t1.initialize();
printf("t1.i = %d, t1.j = %d\n", t1.getI(), t1.getJ());
Test * pt = new Test;
//pt->initialize();
printf("pt->i = %d, pt->j = %d\n", pt->getI(), pt->getJ());
return 0;
}
4、带参数的构造函数
构造函数和普通函数的差别:构造函数没有返回值,名字和类型一样
此时就只剩下参数可以讨论:构造函数也可以带参数
带有参数的构造函数:
扫描二维码关注公众号,回复:
3174796 查看本文章
- 构造函数可以根据需要定义参数
- 一个类中可以存在多个重载的构造函数
- 构造函数的重载遵循C++重载的规则
class Test
{
public:
Test(int v)
{
// use v to initialize member
}
};
注意:
对象定义和对象声明不同:
- 对象定义——申请对象的空间并调用构造函数
- 对象声明——告诉编译器存在这样一个对象
Test t; // 定义对象并调用构造函数
int main()
{
// 告诉编译器存在名为t的Test对象
extern Test t;
return 0;
}
构造函数的自动调用
class Test {
public:
Test(){}
Test(int v) { }
Test(const int& cv){} // 拷贝构造函数
};
Test t; // 调用构造函数Test()
Test t1(1); // 定义了一个对象t1,并调用带有参数的构造函数,传入参数为1,根据重载规则,构造函数为Test(int v)
Test t2 = 1; // 用 1 来初始化对象t2,初始化需要借助构造函数,根据重载规则,选择Test(int v)
/*这里的过程其实是:
首先调用构造函数Test(int v)创建一个临时对象,参数为1;
然后就变成了用一个对象初始化另一个对象,此时应该是要调用拷贝构造函数进行成员变量值的复制,将这个临时对象作为参数用来构造对象t2。
但是编译器发现,可以通过重载的构造函数Test(int v)来直接初始化对象,而达到相同效果,所以将这条语句优化为Test t1(1)
*/
初始化和赋值:
#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)
int i = 1; // 用1来初始化变量i
i = 2; // 用2对变量i进行赋值
t = t2; // 用对象t2对对象t进行赋值
int i(100); // 用100来初始化i
printf("i = %d\n", i);
return 0;
}
初始化和赋值是不一样的,C语言中差别不大,C++中差别很大,因为对象的初始化要调用构造函数
构造函数的调用:
- 一般情况下,构造函数在对象定义时被自动调用
- 一些特殊情况下,需要手工调用构造函数
5、创建一个数组
#include <stdio.h>
class Test
{
private:
int m_value;
public:
Test()
{
printf("Test()\n");
m_value = 0;
}
Test(int v)
{
printf("Test(int v), v = %d\n", v);
m_value = v;
}
void getValue()
{
return m_value;
}
};
int main()
{
Test ta[3]; // 调用3次Test() ,每个数组元素中的m_value都按Test()来处理,不一定需要这样的结果
Test ta2[3] = {Test(), Test(1), Test(2)}; // 手工调用构造函数,3个数组元素调用不同的构造函数
for (int i = 0; i < 3; i++)
{
printf("ta[%d].getValue() = %d\n", i, ta[i].getValue());
// 手工调用构造函数后,m_value初始化成不同值
}
Test t = Test(100); // 创建对象之后,调用构造函数来初始化对象
return 0;
}
需求:开发一个数组类解决原生数组的安全性问题
- 提供函数获取数组长度
- 提供函数获取函数元素
- 提供函数设置数组元素
// IntArray.h
#ifndef _INTARRAY_H_
#define _INTARRAY_H_
class IntArray
{
private:
int m_length;
int* m_pointer;
public:
IntArray(int len);
int length(); // 获取数组长度
bool get(int index, int& value); // 得到对应位置的值
bool set(int index ,int value); // 设置对应位置的值
void free();
};
#endif
// IntArray.c
#include "IntArray.h"
// 构造函数
IntArray::IntArray(int len)
{
// 数据指针指向堆空间内的一段内存
m_pointer = new int[len];
// 初始值的指定
for (int i = 0; i < len; i++)
{
m_pointer[i] = 0;
}
m_length = len;
}
int IntArray::length()
{
return m_length;
}
bool IntArray::get(int index, int& value)
{
// 判断位置是否越界
bool ret = (0 <= index) && (index < length());
if (ret)
{
value = m_pointer[index];
}
return ret;
}
bool IntArray::set(int index, int value)
{
// 判断位置是否越界
bool ret = (0 <= index) && (index < length());
if (ret)
{
m_pointer[index] = value;
}
return ret;
}
// 用来释放对空间
void IntArray::free()
{
delete[] m_pointer;
}
// main.c
#include <stdio.h>
#include "IntArray.h"
int main()
{
IntArray a(5); // 定义了一个对象a,数组类,长度为5
for (int i = 0; i < a.length(); i++)
{
// 赋值操作
a.set(i, i + 1);
}
for (int i = 0; i < a.length(); i++)
{
int value = 0;
if (a.get(i, value))
{
printf("a[%d] = %d\n", i, value);
}
}
a.free();
return 0;
}
6、小结
构造函数可以根据需要定义参数
构造函数之间可以存在重载关系
构造函数遵循C++中重载函数的规则
对象定义时会触发构造函数的调用
在一些情况下可以手动调用构造函数