【C++深度解析】12、构造函数与拷贝构造函数
作者:互联网
文章目录
1 问题
下面的类定义中成员变量 i 和 j 的初始值是多少?
- 在栈、堆上创建对象时,成员变量初始为随机值
- 在静态存储区创建对象时,成员变量初始为 0 值
全局变量位于静态存储区,局部变量在栈中,malloc 申请的在堆中,new 从自由存储区申请空间,自由存储区很多用堆来实现。
2 构造函数
构造函数的作用是在对象被创建时使用特定的值构造对象,或者说将对象初始化为一个特定的状态。
- 构函函数与类名相同,没有返回类型
- 构造函数在对象定义时自动被调用
编程实验:构造函数初探
// 12-1.cpp
#include<stdio.h>
class Test
{
private:
int i;
int j;
public:
Test(int newi, int newj)
{
printf("Test() Begin\n");
i = newi;
j = newj;
}
int getI() {return i;}
int getJ() {return j;}
};
int main()
{
Test t1(1, 2);
printf("t1.i = %d\n", t1.getI());
printf("t1.j = %d\n", t1.getJ());
return 0;
}
2.1 构造函数重载
- 构造函数可以根据需要定义参数
- 一个类中可以存在多个重载的构造函数,遵循 C++ 重载的规则
注意:对象定义和声明不同
- 对象定义:申请对象的空间并调用构造函数
- 对象声明:告诉编译器存在这样一个对象
编程实验:构造函数重载
// 12-2.cpp
#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(100); // 初始化
printf("i = %d\n", i);
return 0;
}
2.2 手动调用构造函数
一般情况下,构造函数在对象定义时被自动调用,一些特殊情况下,需要手动调用构造函数。
编程实验:手动调用构造函数
// 12-3.cpp
#include<stdio.h>
class Test
{
private:
int m_value;
public:
Test()
{
m_value = 0;
printf("Test()\n");
}
Test(int v)
{
m_value = v;
printf("Test(v), v = %d\n", v);
}
int getvalue()
{
return m_value;
}
};
int main()
{
Test ta[3] = {Test(), Test(1), Test(2)};
for (int i = 0; i < 3; i++)
{
printf("ta[%d].getvalue() = %d\n", i, ta[i].getvalue());
}
Test t = Test(100);
printf("t.getvalue() = %d\n", t.getvalue());
return 0;
}
类有两个构造函数,一个有参数,一个没有参数。main() 函数中定义了一个数组 ta[3],我们指定数组的第一个元素调用没有参数的构造函数,后两个调用有参数的构造函数。
Test t = Test(100); 也是指定构造函数的一种方法。
编译运行:
$ g++ 12-3.cpp -o 12-3
$ ./12-3
Test()
Test(v), v = 1
Test(v), v = 2
ta[0].getvalue() = 0
ta[1].getvalue() = 1
ta[2].getvalue() = 2
Test(v), v = 100
t.getvalue() = 100
2.3 开发数组类解决原生数组安全性问题
需求:开发一个数组类解决原生数组的安全性问题
- 提供函数获取数组长度
- 提供函数获取数组元素
- 提供函数设置数组元素
直接看代码,IntArray.h 用于定义类,IntArray.cpp 用于类成员函数的实现。
// IntArray.h
#ifndef _INTARRAY_H_
#define _INRARRAY_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
#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 = (index >= 0 && index < m_length);
if (ret)
{
value = m_pointer[index];
}
return ret;
}
bool IntArray::set(int index, int value)
{
bool ret = (index >= 0 && index < m_length);
if (ret)
{
m_pointer[index] = value;
}
return ret;
}
void IntArray::free()
{
delete[]m_pointer;
}
// 12-4.cpp
#include<stdio.h>
#include"IntArray.h"
int main()
{
IntArray 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;
}
$ g++ 12-4.cpp IntArray.cpp -o 12-4
$ ./12-4
a[0] = 1
a[1] = 2
a[2] = 3
a[3] = 4
a[4] = 5
3 拷贝构造函数
两个特殊的构造函数
- 无参构造函数
- 当类中没有定义构造函数时,编译器默认提供一个无参构造函数,函数体为空
- 拷贝构造函数
- 参数为 const class_name&,当类中没有定义拷贝构造函数时,编译器默认提供一个拷贝构造函数,简单的进行成员变量的值复制
// 12-5.cpp
#include<stdio.h>
class Point
{
public:
Point(int xx = 0, int yy = 0)
{
x = xx;
y = yy;
}
Point(const Point& p)
{
x = p.x;
y = p.y;
printf("Calling the copy constructor\n");
}
int getX(){ return x; }
int getY(){ return y; }
private:
int x;
int y;
};
//形参为Point类对象的函数
void fun1(Point p) {
printf("p.getX() = %d\n", p.getX());
}
//返回值为Point类对象的函数
Point fun2() {
Point a(1, 2);
return a;
}
int main() {
Point a(4, 5);
Point b = a; //情况一,用A初始化B。第一次调用拷贝构造函数
printf("b.getX() = %d\n", b.getX());
fun1(b); //情况二,对象B作为fun1的实参。第二次调用拷贝构造函数
b = fun2(); //情况三,函数的返回值是类对象,函数返回时调用拷贝构造函数
printf("b.getX() = %d\n", b.getX());
return 0;
}
Calling the copy constructor
b.getX() = 4
Calling the copy constructor
p.getX() = 4
Calling the copy constructor
b.getX() = 1
3.1 深拷贝与浅拷贝
对于简单的类,默认的拷贝构造函数一般就够用了。但是当类持有其它资源时,例如动态分配的内存、指向其他数据的指针等,默认的拷贝构造函数就不能拷贝这些资源了,我们必须显式地定义拷贝构造函数,以完整地拷贝对象的所有数据。
例如,类 Point 中有一个指针变量在构造函数中申请空间,在析构函数中释放空间,如果用浅拷贝,p1 和 p2 中的指针指向的是同一块地址空间,析构时 delete 这块内存两次会出错。
采用深拷贝时,重新分配一块同样大小的内存,并将数据拷贝下来,这样 p1 和 p2 各自指向自己的数据块,析构时释放各自的内存。
- 编译器提供的拷贝构造函数只进行浅拷贝
// 12-5.cpp
#include<stdio.h>
class Point
{
public:
Point(int v, int xx = 0, int yy = 0)
{
p = new int;
*p = v;
x = xx;
y = yy;
}
Point(const Point& pp)
{
p = new int;
*p = *pp.p;
x = pp.x;
y = pp.y;
}
int getX(){ return x; }
int getY(){ return y; }
int* getP(){ return p; }
~Point(){ delete p; }
private:
int* p;
int x;
int y;
};
int main() {
Point p1(3);
Point p2(p1);
printf("p1.x = %d, p1.y = %d, p1 = %p\n", p1.getX(), p1.getY(), p1.getP());
printf("p2.x = %d, p2.y = %d, p2 = %p\n", p2.getX(), p2.getY(), p2.getP());
return 0;
}
$ g++ 12-6.cpp -o 12-6
$ ./12-6
p1.x = 0, p1.y = 0, p1 = 0x5653bc52fe70
p2.x = 0, p2.y = 0, p2 = 0x5653bc52fe90
从运行结果看到,两个指针指向的内存是不一样的。
3.2 什么时候需要深拷贝?
当对象中有成员使用了系统中的资源时
- 成员指定了动态内存空间
- 成员打开了外存中的文件
- 成员使用了系统中的网络接口
一般原则:自定义拷贝构造函数,必然需要实现深拷贝!!!
3.3 数组类改进
由于前面实现的数组类中动态的申请了空间,这里增加拷贝构造函数实现深拷贝。
// IntArray.h
#ifndef _INTARRAY_H_
#define _INRARRAY_H_
class IntArray
{
private:
int m_length;
int* m_pointer;
public:
IntArray(int len);
IntArray(const IntArray& obj);
int length();
bool get(int index, int& value);
bool set(int index, int value);
~IntArray();
};
#endif
#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;
}
IntArray::IntArray(const IntArray& obj)
{
m_length = obj.m_length;
m_pointer = new int[obj.m_length];
for (int i = 0; i < obj.m_length; i++)
{
m_pointer[i] = obj.m_pointer[i];
}
}
int IntArray::length()
{
return m_length;
}
bool IntArray::get(int index, int& value)
{
bool ret = (index >= 0 && index < m_length);
if (ret)
{
value = m_pointer[index];
}
return ret;
}
bool IntArray::set(int index, int value)
{
bool ret = (index >= 0 && index < m_length);
if (ret)
{
m_pointer[index] = value;
}
return ret;
}
IntArray::~IntArray()
{
delete[]m_pointer;
}
// 12-6.cpp
#include<stdio.h>
#include"IntArray.h"
int main()
{
IntArray 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);
}
}
IntArray b = a;
for(int i=0; i<b.length(); i++)
{
int value = 0;
if( b.get(i, value) )
{
printf("b[%d] = %d\n", i, value);
}
}
return 0;
}
4 小结
1、构造函数可以重载
2、对象定义时会触发构造函数的调用
3、可以手动调用构造函数
4、拷贝构造函数的浅拷贝与深拷贝
标签:12,int,C++,length,IntArray,Test,拷贝,构造函数 来源: https://blog.csdn.net/happyjacob/article/details/104103078