一、对象的初始化
从程序设计的角度,对象只是变量,因此:
- 在栈上创建对象时,成员变量初始值为随机值
- 在堆上创建对象时,成员变量初始值为随机值
- 在静态存储区创建对象时,成员变量初始值为0值
生活中存在的对象都是被初始化后才上市的,初始状态是对象普遍存在的一个状态的。C++中如何给对象初始化呢?
解决方案
1. 为每个类都提供一个public的initialize函数
2. 对象创建后立即调用initialize函数进行初始化
#include <stdio.h>
class Test
{
private:
int i;
public:
void initialize()
{
i = 0;
}
int getI()
{
return i;
}
};
int main()
{
Test t1;
Test t2;
Test t3;
t1.initialize();
t2.initialize();
t3.initialize();
printf("t1.i = %d\n", t1.getI());
printf("t2.i = %d\n", t2.getI());
printf("t3.i = %d\n", t3.getI());
printf("Press any key to continue...");
getchar();
return 0;
}
编译运行结果如下:
initialize只是一个普通的函数,必须显示的调用。一旦由于失误的原因,对象没有初始化,那么结果将是不确定的。没有初始化的对象,其内部成员变量的值是不定的。
#include <stdio.h>
class Test
{
private:
int i;
int j;
int k;
public:
void initialize()
{
i = 0;
j = 0;
k = 0;
}
void print()
{
printf("i = %d, j = %d, k = %d\n", i, j, k);
}
};
int main()
{
Test t1;
Test t2;
Test t3;
t1.print();
t2.print();
t3.print();
printf("Press any key to continue...");
getchar();
return 0;
}
编译运行结果如下:
二、构造函数
C++中的类可以定义与类名相同的特殊成员函数。这种与类名相同的成员函数叫做构造函数。构造函数在定义时可以有参数,但是没有任何返回类型的声明。
构造函数的调用。一般情况下C++编译器会自动调用构造函数。在一些情况下则需要手工调用构造函数。
#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(1), Test(2), Test(3)};
for(int i=0; i<3; i++)
{
tA[i].print();
}
printf("Press any key to continue...");
getchar();
return 0;
}
编译运行结果如下:
成员函数的重载,类的成员函数和普通函数一样可以进行重载,并遵守相同的重载规则。
#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(4);
Test t2 = 5;
Test t3 = Test(6);
Test t4;
t4.print();
t1.print();
t2.print();
t3.print();
Test tA[3];
for(int i=0; i<3; i++)
{
tA[i].print();
}
printf("Press any key to continue...");
getchar();
return 0;
}
编译运行结果如下:
两个特殊的构造函数
1. 无参构造函数:当类中没有定义任意一个构造函数时,编译器默认提供一个无参构造函数,并且其函数体为空。
2. 拷贝构造函数:当类中没有定义拷贝构造函数时,编译器默认提供一个拷贝构造函数,简单的进行成员变量的值复制。
#include <stdio.h>
/*
注意:
1. 当类中没有定义任何一个构造函数,C++编译器会为提供无参构造函数和拷贝构造函数
2. 当类中定义了任意的非拷贝构造函数时,C++编译器不会为提供无参构造函数
*/
class Test
{
public:
Test()
{
printf("Test()\n");
}
Test(const Test& obj)
{
printf("Test(const Test& obj)\n");
}
};
int main()
{
Test t1;
Test t2 = t1;
printf("Press any key to continue...");
getchar();
return 0;
}
编译运行结果如下:
#include <stdio.h>
class Test
{
private:
int i;
int j;
int k;
public:
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;
}
编译运行结果如下:
三、数组类的创建
//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));
}
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;
}
//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;
}
//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
编译运行结果如下:
四、C++中的对象组合
C++中的类可以使用其它类定义成员变量,如何给对象成员进行初始化呢?
对象组合示例:
C++中提供了初始化列表对成员变量进行初始化,语法规则如下:
Constructor::Contructor() : m1(v1), m2(v1,v2), m3(v3)
{
// some other assignment operation
}
注意:
1. 成员变量的初始化顺序与声明的顺序相关,与在初始化列表中的顺序无关;
2. 初始化列表先于构造函数的函数体执行;
#include <stdio.h>
class M
{
private:
int mI;
public:
M(int i)
{
printf("M(int i), i = %d\n", i);
mI = i;
}
int getI()
{
return mI;
}
};
class Test
{
private:
const int c;
M m1;
M m2;
public:
Test() : c(1), m2(3), m1(2)
{
printf("Test()\n");
}
void print()
{
printf("c = %d, m1.mI = %d, m2.mI = %d\n", c, m1.getI(), m2.getI());
}
};
void run()
{
Test t1;
t1.print();
}
int main()
{
run();
printf("Press any key to continue...");
getchar();
return 0;
}
编译运行结果如下:
小插曲:
类中的const成员是肯定会被分配空间的,类中的const成员变量只是一个只读变量。编译器无法直接得到const成员变量的初始值,因此无法进入符号表成为真正意义上的常量。
初始化与赋值不同,初始化是用已存在的对象或值对正在创建的对象进行初值设置。赋值是用已存在的对象或值对已经存在的对象进行值设置。
区别:
初始化:被初始化的对象正在创建;
赋值:被赋值的对象已经存在;
五、C++中的析构函数
生活中存在的对象都是被初始化后才上市的,生活中的对象被销毁前会做一些清理工作。如何清理被销毁的对象?
解决方案:
1. 为每个类都提供一个public的destroy函数;
2. 对象不再被需要时立即调用destroy函数进行清理;
destroy只是一个普通的函数,必须显示的调用。如果对象销毁前没有做清理,那么很可能造成资源泄漏。在构造函数中申请的资源,需要在对象销毁前释放。
C++编译器是否能够自动调用某个特殊的函数进行对象的清理?
C++中的类可以定义一个特殊的成员函数清理对象。这个特殊的成员函数叫做析构函数。
1. 定义:~ClassName()
2. 析构函数没有参数也没有任何返回类型的声明;
3. 析构函数在对象销毁时自动被调用;
#include <stdio.h>
class Test
{
private:
int mI;
public:
Test(int i) : mI(i)
{
printf("Test(), mI = %d\n", mI);
}
~Test()
{
printf("~Test(), mI = %d\n", mI);
}
};
void run()
{
Test t1(1);
Test t2(2);
}
int main()
{
run();
printf("Press any key to continue...");
getchar();
return 0;
}
编译运行结果如下:
六、数组类的进化
前面我们创建了一个数组类,下面通过刚学的析构函数进行改进。
//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];
}
Array::~Array()
{
mLength = -1;
delete[] mSpace;
}
//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);
~Array();
};
#endif
//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));
}
Array a2 = a1;
for(int i=0; i<a2.length(); i++)
{
printf("Element %d: %d\n", i, a2.getData(i));
}
printf("Press any key to continue...");
getchar();
return 0;
}
编译运行结果如下:
构造函数与析构函数的调用秩序
1.当类中有成员变量是其它类的对象时;
2. 首先调用成员变量的构造函数;
3. 调用顺序与声明顺序相同;
4. 之后调用自身类的构造函数;
5. 析构函数的调用秩序与对应的构造函数调用秩序相反;
#include <stdio.h>
class Test
{
private:
int mI;
public:
Test()
{
printf("Test()\n");
mI = -1;
}
Test(int i)
{
printf("Test(int i), i = %d\n", i);
mI = i;
}
Test(const Test& obj)
{
printf("Test(const Test& obj), i = %d\n", obj.mI);
mI = obj.mI;
}
~Test()
{
printf("~Test(), i = %d\n", mI);
}
};
void func(Test t)
{
Test r(1);
}
void run()
{
Test t(0);
func(t);
}
int main()
{
run();
printf("Press any key to continue...");
getchar();
return 0;
}
编译运行结果如下:
小结:
1. 析构函数是C++中对象销毁时做清理工作的特殊函数;
2. 析构函数在对象销毁时自动被调用;
3. 析构函数是对象所使用的资源及时释放的保障;
4. 析构函数的调用秩序与构造函数相反;
#include <stdio.h>
class Test
{
private:
int mI;
int mJ;
const char* mS;
public:
Test()
{
printf("Test()\n");
mI = 0;
mJ = 0;
}
Test(const char* s)
{
printf("Test(const char* s)\n");
Test();
mS = s;
}
~Test()
{
printf("~Test()\n");
}
void print()
{
printf("mI = %d, mJ = %d, mS = %s\n", mI, mJ, mS);
}
};
void run()
{
Test t = Test("Delphi Tang"); // Test t("Delphi Tang");
t.print();
}
int main()
{
run();
printf("Press any key to continue...");
getchar();
return 0;
}
编译运行结果如下: