在类里面成员函数的初始值是多少了?(取决于创建对象的位置,是在堆、栈、还是在静态存储区中创建。)
例如:
#include <stdio.h> class Test { private: int i; int j; public : int get_i(void) {return i;} int get_j(void) {return j;} }; Test Ta;//在静态存储区中定义 Test类 int main(int argc, char *argv[]) { printf("Ta.i = %d ",Ta.get_i());//Ta.i = 0 printf("Ta.j = %d ",Ta.get_j());//Ta.j = 0 Test Tb;//在栈上定义类 printf("Tb.i = %d ",Tb.get_i());//Tb.i = 随机数 printf("Tb.j = %d ",Tb.get_j());//Tb.j = 随机数 Test *Tc = new Test;//在堆上定义类 printf("Tc->i = %d ",Tc->get_i());//Tc.i = 随机数 printf("Tc->j = %d ",Tc->get_j());//Tc.i = 随机数 return 0; }
运行结果:
Ta.i = 0 Ta.j = 0 Tb.i = 1808322352 Tb.j = 32766 Tc->i = 0 Tc->j = 0
可以看出,对象只是变量,所以在不同的地方定义变量,所的到的初始值也不同。
在堆上定义:为随机数
在栈上定义:为随机数
在静态存储区上定义:因为静态存储区中变量默认为0 ,所以为0
这样在不同地方定义初始值就会不同,这样是不允许的所以我们需要对变量进行初始化。这就引入了类的构造函数。
构造函数:
构造函数特点:
1、构造函数没有任何返回类型的声明。
2、构造函数在定义的时候被自动调用。
例如:
#include <stdio.h> class Test { private: int i; int j; public : int get_i(void) {return i;} int get_j(void) {return j;} Test() { printf("Test() "); i = 5; j = 10; } }; Test Ta; int main(int argc, char *argv[]) { printf("Ta.i = %d ",Ta.get_i()); printf("Ta.j = %d ",Ta.get_j()); Test Tb; printf("Tb.i = %d ",Tb.get_i()); printf("Tb.j = %d ",Tb.get_j()); Test *Tc = new Test; printf("Tc->i = %d ",Tc->get_i()); printf("Tc->j = %d ",Tc->get_j()); return 0; }
在类中加入构造函数。
运行结果:
Test() Ta.i = 5 Ta.j = 10 Test() Tb.i = 5 Tb.j = 10 Test() Tc->i = 5 Tc->j = 10
可以看出每次定义都调用了一次构造函数。
一个类中可以有多个构造函数构成重载,重载的概念在类中同样适用。
例如:
#include <stdio.h> class Test { private: int i; int j; public : int get_i(void) {return i;} int get_j(void) {return j;} Test() { printf("Test() "); i = 5; j = 10; } Test(int v) { printf("Test(int v);v = %d ",v); i = 20; j = 30; } }; int main(int argc, char *argv[]) { Test Ta; printf("Ta.i = %d ",Ta.get_i()); printf("Ta.j = %d ",Ta.get_j()); Test Tb(10); printf("Tb.i = %d ",Tb.get_i()); printf("Tb.j = %d ",Tb.get_j()); Test *Tc = new Test(30); printf("Tc->i = %d ",Tc->get_i()); printf("Tc->j = %d ",Tc->get_j()); return 0; }
运行结果:
Test() Ta.i = 5 Ta.j = 10 Test(int v);v = 10 Tb.i = 20 Tb.j = 30 Test(int v);v = 30 Tc->i = 20 Tc->j = 30
从结果中可以看出:
Test Ta;调用的是 Test() 这个构造函数。
Test Tb(10);和 Test *Tc = new Test(30);调用的是 Test(int v) 这个构造函数。
注意:对象的定义与对象的声明是不同的。例如变量的定义与变量的声明也是不同的。
对象定义:声明对象的空间并调用构造函数。
对象的声明:告诉编译器存在这样的一个变量。
构造函数的手动调用:
一般来说构造函数在定义对象的时候被自动调用,但是在一些特殊情况下需要手动调用。
例如构造对象数组。
实验:创建一个数组类解决数组的安全性问题。
1、创建Intarray.h
#ifndef __INTARRAY_H #define __INTARRAY_H class intArray { private: int arrayLenght; int *Parray; public: intArray (int lenght);//构造函数 bool changeArray(int index,int val);//修改数组中的元素 int getLenght(void);//获取数组长度 bool getArrayData(int index,int& val);//获取数组中的元素 void free(); }; #endif
2、创建Intarray.cpp
#include "intArray.h" intArray::intArray (int lenght)//构造函数 { Parray = new int[lenght];//创建数组空间 for(int i=0; i<lenght; i++)//初始化 Parray[i] = 0; arrayLenght = lenght; } bool intArray::changeArray(int index,int val)//修改数组中的元素 { bool ret = (index>=0)&&(index < arrayLenght);//判断是否越界 if(ret) { Parray[index] = val; } return ret; } int intArray::getLenght(void)//获取数组长度 { return arrayLenght; } bool intArray::getArrayData(int index, int& val)//获取数组中的元素 { bool ret = (index>=0)&&(index < arrayLenght);//判断是否越界 if(ret) { val = Parray[index] ; } return ret; } void intArray::free()// { delete[] Parray; }
3、创建main.cpp
#include <stdio.h> #include "intArray.h" int main(int argc, char *argv[]) { int temp ; intArray TestArray(6); for(int i=0; i<TestArray.getLenght();i++) TestArray.changeArray(i,i); for(int i=0; i<TestArray.getLenght();i++) { if(TestArray.getArrayData(i,temp))
printf("getArrayData(%d) = %d ",i,temp); } TestArray.free(); return 0; }
运行结果:
getArrayData(0) = 0 getArrayData(1) = 1 getArrayData(2) = 2 getArrayData(3) = 3 getArrayData(4) = 4 getArrayData(5) = 5
类中的特殊构造函数:
1、无参构造函数。(当类中没有定义任何构造函数时,编译器会默认的提供一个无参构造函数,函数体为空)
class_name(){}
2、拷贝构造函数。参数为const class_name& 的构造函数 (当类中没有定义任何拷贝构造函数时,编译器为默认提供一个拷贝构造函数,其功能为进行成员变量的赋值。)
例如:定义一个对象的时候使用另外一个对象对其进行初始化。
class_name class1;
class_name class2=class1;或者(class_name class2(class1);)
通过以上使用就需要用到拷贝构造函数,编译器默认的拷贝构造函数保证的是两个对象的物理状态相同(浅拷贝)。也就是说这是一种 浅拷贝。那么有浅拷贝就必然有深拷贝(其作用是保证两个对象在逻辑状态上相同)。
例如代码:
#include <stdio.h> class Test { private: int i; int j; public : int get_i(void) {return i;} int get_j(void) {return j;} }; int main(int argc, char *argv[]) { Test Ta; Test Tb(Ta); printf("Ta.i = %d ",Ta.get_i()); printf("Ta.j = %d ",Ta.get_j()); printf("Tb.i = %d ",Tb.get_i()); printf("Tb.j = %d ",Tb.get_j()); return 0; }
运行结果:
Ta.i = -1553435232 Ta.j = 22062 Tb.i = -1553435232 Tb.j = 22062
从运行结果中可以看出,对象Tb 与对象Ta中的变量i,j值完全相同。
修改代码:添加拷贝构造函数。
#include <stdio.h> class Test { private: int i; int j; public : int get_i(void) {return i;} int get_j(void) {return j;} Test(){}; Test(const Test& t) { i = t.i; j = t.j; } }; int main(int argc, char *argv[]) { Test Ta; Test Tb(Ta); printf("Ta.i = %d ",Ta.get_i()); printf("Ta.j = %d ",Ta.get_j()); printf("Tb.i = %d ",Tb.get_i()); printf("Tb.j = %d ",Tb.get_j()); return 0; }
运行结果:从结果中可以看出结果同上面没有加入拷贝构造函数时一致。也就是说编译器给我们默认构造了一个拷贝构造函数。内容与下面代码一致
Test(const Test& t)
{
i = t.i;
j = t.j;
}
Ta.i = 688973216 Ta.j = 22083 Tb.i = 688973216 Tb.j = 22083
其中类中成员没有指代系统中的资源。所以看起来没有什么问题。
修改代码:增加int *p = new int;并打印出p的地址
#include <stdio.h> class Test { private: int i; int j; int *p ; public : int get_i(void) {return i;} int get_j(void) {return j;} int* get_p(void){return p;} void free(void){delete 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; } }; int main(int argc, char *argv[]) { Test Ta(2); Test Tb(Ta); printf("Ta.i = %d ,Ta.j = %d ,Ta.p = %p ",Ta.get_i(),Ta.get_j(),Ta.get_p()); printf("Tb.i = %d ,Tb.j = %d ,Tb.p = %p ",Tb.get_i(),Tb.get_j(),Tb.get_p()); Ta.free(); Tb.free(); return 0; }
运行结果:
Ta.i = 1 ,Ta.j = 2 ,Ta.p = 0x55d66fe34e70 Tb.i = 1 ,Tb.j = 2 ,Tb.p = 0x55d66fe34e90
如果在拷贝构造函数中去掉 p = new int; *p = *t.p;
运行结果:
Ta.i = 1 ,Ta.j = 2 ,Ta.p = 0x55dbf6d60e70 Tb.i = 1 ,Tb.j = 2 ,Tb.p = (nil)
申请的 p 指针为空。这显然是不对的。
打印p所指向空间的值运行结果:
Ta.i = 1 ,Ta.j = 2 ,Ta.p = 0x563d1529ee70 ,*Ta.p=2 Tb.i = 1 ,Tb.j = 2 ,Tb.p = 0x563d1377d9fd ,*Ta.p=29590344 munmap_chunk(): invalid pointer Aborted (core dumped)
指针在释放的过程中出现了错误。
在拷贝构造函数中 增加 p = new int; *p = *t.p;
运行结果:
Ta.i = 1 ,Ta.j = 2 ,Ta.p = 0x55b993806e70 ,*Ta.p=2 Tb.i = 1 ,Tb.j = 2 ,Tb.p = 0x55b993806e90 ,*Ta.p=2
关于深拷贝的说明: ——自定义拷贝函数,必然需要使用到深拷贝
到底什么时候需要用到深拷贝? ——对象中有成员指代了系统资源。
1、成员指向了动态内存空间。
2、成员打开了外部文件。
3、成员使用了系统中的网络端口
我们上面的实验使用到了动态内存空间。所以也会出现问题。需要给它加上自定义拷贝函数。
修改代码如下:
intArray.cpp
intArray::intArray (const intArray& obj) { Parray = new int[obj.arrayLenght]; arrayLenght = obj.arrayLenght; for(int i=0;i<obj.arrayLenght;i++) Parray[i] = obj.Parray[i]; }
main.cpp
#include <stdio.h> #include "intArray.h" int main(int argc, char *argv[]) { int temp ; intArray TestArray(6); for(int i=0; i<TestArray.getLenght();i++) TestArray.changeArray(i,i); for(int i=0; i<TestArray.getLenght();i++) { if(TestArray.getArrayData(i,temp)) printf("getArrayData(%d) = %d ",i,temp); } intArray TestArray1(TestArray); for(int i=0; i<TestArray1.getLenght();i++) TestArray1.changeArray(i,i); for(int i=0; i<TestArray1.getLenght();i++) { if(TestArray1.getArrayData(i,temp)) printf("getArrayData1(%d) = %d ",i,temp); } if(TestArray.getArrayData(100,temp)) printf("getArrayData(%d) = %d ",100,temp); TestArray.free(); TestArray1.free(); return 0; }
运行结果:
getArrayData(0) = 0 getArrayData(1) = 1 getArrayData(2) = 2 getArrayData(3) = 3 getArrayData(4) = 4 getArrayData(5) = 5 getArrayData1(0) = 0 getArrayData1(1) = 1 getArrayData1(2) = 2 getArrayData1(3) = 3 getArrayData1(4) = 4 getArrayData1(5) = 5