C++深度解析(11)—对象的初始化、浅拷贝、深拷贝、数组类的创建

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++编译器会默认提供构造函数 
  • 无参构造函数用于定义对象的默认初始状态 
  • 拷贝构造函数在创建对象时拷贝对象的状态 
  • 对象的拷贝有浅拷贝和深拷贝两种方式 
    • 浅拷贝使得对象的物理状态相同 
    • 深拷贝使得对象的逻辑状态相同 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值