C++笔记 day28 构造函数和析构函数 new和delete

358 阅读4分钟

设计立方体类案例

  1. 设计class Cube

     1)属性
    
         长宽高
    
     2)行为
    
         设置长宽高
    
         获取长宽高
    
         获取面积
    
         获取体积
    
  2. 通过全局函数和成员函数 判断两个立方体是否相等

示例

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

/*
设计立方体类(cube),求出立方体的面积(2*a*b+2*a*c+2*b*c)和体积(a * b * c),
分别用全局函数和成员函数判断两个立方体是否相等
*/

class Cube 
{
public:

	//设置长宽高
	void setL(int l) 
	{
		m_L = l;
	}
	void setW(int w)
	{
		m_W = w;
	}
	void setH(int h)
	{
		m_H = h;
	}
	//获取长宽高
	int getL() 
	{
		return m_L;
	}
	int getW()
	{
		return m_W;
	}
	int getH()
	{
		return m_H;
	}
	//计算面积
	int calculateS() 
	{
		return 2 * m_L * m_W + 2 * m_L * m_H + 2 * m_H * m_W;
	}
	//计算体积
	int calculateV()
	{
		return m_L * m_W * m_H;
	}
	//利用成员函数判断立方体是否相等
	bool compareCubeByClass(Cube& c) 
	{
		return m_L == c.getL() && m_W == c.getW() && m_H == c.getH();
	}
private:
	int m_L;		//长
	int m_W;	//宽
	int m_H;		//高

};

//利用全局函数 判断两个立方体是否相等
bool compareCube(Cube& c1, Cube& c2) //使用引用传递,可以直接传递c1和c2不用重新拷贝一份,节省空间
{
	if (c1.getL() == c2.getL() && c1.getW() == c2.getW() && c1.getH() == c2.getH()) 
	{
		return true;
	}
	else 
	{
		return false;
	}
}

void test01() 
{
	Cube c1;
	c1.setL(20);
	c1.setW(20);
	c1.setH(20);

	cout << c1.calculateS() << endl; //2400
	cout << c1.calculateV() << endl; // 8000

	Cube c2;
	c2.setL(20);
	c2.setW(10);
	c2.setH(20);
	//利用全局函数判断 c1 和 c2 是否相等
	bool ret = compareCube(c1, c2);
	if (ret) 
	{
		cout << "c1和c2相等" << endl;
	}
	else 
	{
		cout << "c1和c2不相等" << endl;
	}
	bool ret1 = c1.compareCubeByClass( c2);
	if (ret1)
	{
		cout << "成员函数判断 c1和c2相等" << endl;
	}
	else
	{
		cout << "成员函数判断 c1和c2不相等" << endl;
	}

}

int main()
{
	test01();


	system("pause");
	return EXIT_SUCCESS;
}

点和圆关系案例

image.png

设计点和圆类

重点是圆类中包含了点类

  1. 点类 Point

     属性  x  y
    
     行为  设置 获取 x y
    
  2. 圆类 Circle

     属性: 圆心 Point m_Center  半径  m_R;
    
     行为: 设置 获取  半径  圆心
    

通过成员函数 和 全局函数 判断点和圆关系

分文件编写 点和圆类

头文件放类的属性和函数声明

实现文件编写,只保留函数实现,函数名前要加类的作用域

示例: Point头文件

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

//点类
class Point
{
public:
	//设置x,y
	void setX(int x);
	void setY(int y);
	//获取x,y
	int getX();
	int getY();

private:
	int mX;
	int mY;
};

示例: Circle头文件

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#include "point.h"

// 圆类
class Circle
{
public:
	//设置半径
	void setR(int r);
	//获取半径
	int getR();
	Point getCenter();
	//设置圆心
	void setCenter(Point p);
	//成员函数判断点和圆的关系
	void isInCircleByClass(Point p);
private:
	int m_R;					//半径
	Point m_Center;		//圆心
};

示例: Point实现

#include "point.h"

//实现中放类的实现,要写明函数的作用域

	void Point::setX(int x)
	{
		mX = x;
	}
	void Point::setY(int y)
	{
		mY = y;
	}
	//获取x,y
	int Point:: getX()
	{
		return mX;
	}
	int Point::getY()
	{
		return mY;
	}

示例: Circle实现

#include "circle.h"

// 圆类

void Circle::setR(int r)
{
	m_R = r;
}
//获取半径
int Circle::getR()
{
	return m_R;
}
//设置圆心
void Circle::setCenter(Point p)
{
	m_Center = p;
}
Point Circle::getCenter()
{
	return m_Center;
}
//成员函数判断点和圆的关系
void Circle::isInCircleByClass(Point p)
{
	int distance = (m_Center.getX() - p.getX()) * (m_Center.getX() - p.getX()) + (m_Center.getY() - p.getY()) * (m_Center.getY() - p.getY());
	int rDistance = m_R * m_R;
	cout << "r:" << rDistance << endl;
	cout << "d:" << distance << endl;
	if (distance == rDistance)
	{
		cout << "点在圆上" << endl;
	}
	else if (distance > rDistance)
	{
		cout << "点在圆外" << endl;
	}
	else
	{
		cout << "点在圆内" << endl;
	}
}

示例: 主函数

//全局函数判断点和圆的关系
void isInCircle(Circle c,Point p) 
{
	int distance = (c.getCenter().getX() - p.getX()) * (c.getCenter().getX() - p.getX()) + (c.getCenter().getY() - p.getY()) * (c.getCenter().getY() - p.getY());
	int rDistance = c.getR() * c.getR();
	cout << "r:" << rDistance << endl;
	cout << "d:" << distance << endl;
	if (distance == rDistance) 
	{
		cout << " 成员函数 点在圆上" << endl;
	}
	else if (distance > rDistance) 
	{
		cout << "成员函数 点在圆外" << endl;
	}
	else 
	{
		cout << "成员函数 点在圆内" << endl;
	}
}

void test01() 
{
	Point p;
	p.setX(10);
	p.setY(10);

	Circle c1;
	Point pCenter;
	pCenter.setX(20);
	pCenter.setY(10);
	c1.setCenter(pCenter);
	c1.setR(10);

	isInCircle(c1, p);
	c1.isInCircleByClass(p);

}

int main()
{

	test01();

	system("pause");
	return EXIT_SUCCESS;
}

构造函数和析构函数

构造函数和析构函数是为了实现对象的初始化和清理操作,要设置成自动的。

构造函数和析构函数必须声明在全局的作用域。

构造函数

没有返回值  不用写void

函数名 与 类名相同

可以有参数  ,可以发生重载

构造函数 由编译器自动调用一次 无须手动调用

析构函数

没有返回值   不用写void

函数名 与类名相同  函数名前 加 ~

不可以有参数 ,不可以发生重载

析构函数 也是由编译器自动调用一次,无须手动调用

析构函数的目的是为了清理函数,如果类设在堆区那么没有free代码就不会调用析构函数。

构造函数的分类和调用

分类

  1. 按照参数分类: 有参  无参(默认)

  2. 按照类型分类: 普通  拷贝构造 ( const  Person & p  )

调用

  1. 括号法

  2. 显示法

  3. 隐式法

    注意事项

     不要用括号法 调用无参构造函数  Person p3();  编译器认为代码是函数的声明
    
     不要用拷贝构造函数 初始化 匿名对象 Person(p3); 编译器认为 Person p3对象实例化  如果已经有p3  p3就重定义
    
     匿名对象  特点: 当前行执行完后 立即释放
     
    

示例

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

//构造函数分类
//按照参数分类,无参构造(默认构造函数) 有参构造
//按照类型分类,普通构造函数  拷贝构造函数
class Person
{
public:	
	Person()
	{
		cout << "Person的默认构造函数调用" << endl;
	}
	Person(int age) 
	{
		m_Age = age;
		cout << "Person的有参构造函数调用" << endl;
	}
	Person(const Person &p)  //不能直接传值,会造成无限引用,要加const防止更改
	{
		cout << "Person的拷贝构造函数调用" << endl;
		m_Age = p.m_Age;
	}

	//析构函数
	~Person() 
	{
		cout << "Person的析构函数调用" << endl;
	}

	int m_Age;

};

void test01() 
{
	//构造函数调用
	//无参构造函数调用
	Person p;
	//1. 括号法
	Person p(18);
	Person p2(p);
	//注意事项1:不要用括号法调用无参构造函数,Person p() 会编译器被误认为是函数的声明而出错
	cout << "p2的年龄" << p2.m_Age << endl;
	//2. 显示法
	Person p3 = Person(10);//有参构造
	Person p4 = Person(p3);//拷贝构造
	
	Person(10);//匿名对象,特点,当前行执行完后立即释放
	//注意事项2:不要用拷贝构造函数 初始化 匿名对象 Person(p3),编译器会自动把括号去掉,认为是p3的重定义操作

	//3. 隐式法。不建议,可读性比较低
	Person p5 = 10; //Person p5 = Person(10)
	Person p6 = p5;
}

int main()
{
	test01();


	system("pause");
	return EXIT_SUCCESS;
}

拷贝构造函数的调用时机

1. 用已经创建好的对象来初始化新的对象

2. 值传递的方式 给函数参数传值

3. 以值方式 返回局部对象
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

//构造函数分类
//按照参数分类,无参构造(默认构造函数) 有参构造
//按照类型分类,普通构造函数  拷贝构造函数
class Person
{
public:
	Person()
	{
		cout << "Person的默认构造函数调用" << endl;
	}
	Person(int age)
	{
		m_Age = age;
		cout << "Person的有参构造函数调用" << endl;
	}
	Person(const Person& p)  //不能直接传值,会造成无限引用,要加const防止更改
	{
		cout << "Person的拷贝构造函数调用" << endl;
		m_Age = p.m_Age;
	}

	//析构函数
	~Person()
	{
		cout << "Person的析构函数调用" << endl;
	}

	int m_Age;

};

//1. 用已经创建好的对象来初始化新的对象
void test01() 
{
	Person p1(18);
	Person p2 = Person(p1);
	cout << "p2的年龄是" << p2.m_Age << endl;
}
//2. 用值传递的方式,给函数参数传值
void doWork(Person p) 
{

}
void test02() 
{
	Person p1(100);
	doWork(p1); //值传递的本质就是拷贝构造函数,传递的是拷贝出来的新的对象
}
//3. 以值的方式返回局部对象
Person doWork2() 
{
	Person p;
	return p;
}
void test03() 
{
	Person p = doWork2(); //返回的是一个拷贝出来的新的对象
}
//在release 版本中 拷贝函数的过程不会显现,会优化代码
/*
编译器优化代码后 release版本的代码类似如下
	void doWork2(Person &p) { };

	Person p;
	doWork2(p);
*/



int main()
{
	//test01();
	//test02();
	test03();

	system("pause");
	return EXIT_SUCCESS;
}

构造函数的调用规则

  1. 编译器会给一个类 至少添加3个函数    默认构造(空实现)   析构函数(空实现)   拷贝构造(值拷贝)

  2. 如果我们自己提供了 有参构造函数,编译器就不会提供默认构造函数,但是依然会提供拷贝构造函数

  3. 如果我们自己提供了 拷贝构造函数,编译器就不会提供其他构造函数

示例

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

//1.  编译器 会给一个类 至少添加3个函数 默认构造(空实现) 析构函数(空实现) 拷贝构造(里面会提前写好:值拷贝)
//2. 如果我们自己提供了有参构造函数,编译器就不会提供默认构造函数,但是依然会提供拷贝构造函数
//3. 如果我们自己提供了 拷贝构造函数,编译器就不会提供其他构造函数
class Person
{
public:
	Person()
	{
		cout << "默认构造函数调用" << endl;
	}
	Person(int age)
	{
		m_Age = age;
		cout << "有参构造函数的调用" << endl;
	}
	Person(const Person& p)
	{
		m_Age = p.m_Age;
		cout << "拷贝构造函数的调用" << endl;
	}

	~Person() 
	{
		cout << "析构函数的调用" << endl;
	}

	int m_Age;
};

void test01() 
{
	Person p1;
	p1.m_Age = 20;

	Person p2(p1);

	cout << "p2的年龄是" << p2.m_Age << endl;
}

int main()
{

	test01();

	system("pause");
	return EXIT_SUCCESS;
}

深拷贝与浅拷贝的问题以及解决

如果有属性开辟到堆区,利用编译器提供拷贝构造函数会调用浅拷贝带来的析构重复释放堆区内存的问题

image.png

p2 已经 释放过1次内存了,p1又重复释放了

image.png

利用深拷贝解决浅拷贝问题**

自己提供拷贝构造函数,实现深拷贝

image.png

示例:

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

class Person 
{
public:	
	Person(char* name, int age) 
	{
		m_Name = (char *)malloc(strlen(name) + 1);
		strcpy(m_Name, name);

		m_Age = age;
	}

	Person(const Person& p) //自己重写拷贝构造函数,改为深拷贝,可以避免重复释放的问题
	{
		m_Name = (char*)malloc(strlen(p.m_Name) + 1);

		strcpy(m_Name, p.m_Name);

		m_Age = p.m_Age;

	}

	~Person() 
	{
		if (m_Name != NULL) 
		{
			cout << "析构函数调用" << endl;
			free(m_Name);
			m_Name = NULL;
		}
	}

	char* m_Name; //姓名
	int m_Age; //年龄
};

void test01() 
{
	char* name = (char *)"德玛西亚";
	Person p(name, 18);//可以用括号法通过构造函数在创建类的时候进行赋初值的操作

	cout << "姓名:" << p.m_Name << "  年龄:" << p.m_Age << endl;

	Person p2(p);
	cout << "p2姓名:" << p2.m_Name << "  p2年龄:" << p.m_Age << endl;
}

int main()
{

	test01();

	system("pause");
	return EXIT_SUCCESS;
}

初始化列表

可以利用初始化列表语法 对类中属性进行初始化

语法:构造函数名称后  : 属性(值), 属性(值)...

Person(int a, int b, int c) : m_A(a), m_B(b), m_C(c)

示例

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

class Person 
{
public:
	//Person(int a, int b, int c) 
	//{
	//	m_A = a;
	//	m_B = b;
	//	m_C = c;
	//}

	//初始化列表方式1
	//Person() : m_A(10), m_B(20), m_C(30)
	//{
	//
	//}

	//初始化列表方式2 
	Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c)
	{
	
	}


	int m_A;
	int m_B;
	int m_C;
};

void test01() 
{
	//初始化列表1   的实例化方式
	// Person p;
	//常规和初始化列表2 的实例化方式 推荐
	Person p(10, 20, 30);


	cout << "m_A = " << p.m_A << endl;
	cout << "m_B = " << p.m_B << endl;
	cout << "m_C = " << p.m_C << endl;
}

int main()
{
	test01();


	system("pause");
	return EXIT_SUCCESS;
}

类对象作为类中成员

当其他类对象 作为本类成员,先构造其他类对象,再构造自身,析构的顺序和构造相反

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#include <string>

class Phone 
{
public:

	Phone(string pName) 
	{
		cout << "Phone 的 有参构造调用" << endl;
		m_PhoneName = pName;
	}
	~Phone() 
	{
		cout << "Phone 的 析构函数调用" << endl;
	}
	string m_PhoneName;
};

class Game
{
public:

	Game(string gName)
	{
		cout << "Game 的 有参构造调用" << endl;
		m_GameName = gName;
	}
	~Game()
	{
		cout << "Game 的 析构函数调用" << endl;
	}
	string m_GameName;
};

class Person
{
public:

	Person(string name, string pName, string gName) :m_Name(name), m_Phone(pName), m_Game(gName)
	{
		cout << "Person 的 有参构造调用" << endl;
	}

	~Person()
	{
		cout << "Person 的 析构函数调用" << endl;
	}

	void PlayGame()
	{
		cout << m_Name << "拿着" << m_Phone.m_PhoneName << "牌手机,玩着" << m_Game.m_GameName<<endl;
	}
	string m_Name;//姓名
	Phone m_Phone;//收集
	Game m_Game;//游戏
};

void test01() 
{
	//当其他类的对象作为本类的成员,先构造其他类的对象,再构造自身,析构的顺序跟此相反
	Person p("张三", "苹果", "王者荣耀");
	p.PlayGame();
	Person p2("李四", "三星", "消消乐");
	p2.PlayGame();
}

int main()
{

	test01();

	system("pause");
	return EXIT_SUCCESS;
}

explicit关键字

explicit用途: 防止利用隐式类型转换方式来构造对象

new和delete

由于C中使用malloc动态分配内存过于复杂且容易令人混淆,C++中采用新的方式new和delete来处理此类问题

动态对象创建

C语言中可以用malloc或其变种calloc或realloc来分配内存空间

使用malloc问题:

  1. 程序员必须确定对象的长度
  2. malloc返回一个void * 指针,但是C++不允许将void * 赋值给其他指针,必须强转
  3. malloc可能申请内存失败,所以必须判断返回值来确保内存分配成功
  4. 用户在使用对象之前必须记住对他初始化,构造函数不能显示调用初始化(构造函数是由编译器调用),用户有可能忘记调用初始化函数。

new和delete

image.png

malloc 和 new 区别

  1. malloc 和 free 属于 库函数     new 和delete属于 运算符

  2. malloc不会调用构造函数   new会调用构造函数

  3. malloc返回void* C++下要强转     new 返回创建的对象的指针

注意事项 不要用void去接受new出来的对象,利用void无法调用析构函数

利用new创建数组

Person * pPerson = new Person[10];

释放数组时候  需要加[]

delete [] pPerson;
  1. 堆区开辟数组,一定会调用默认构造函数
  2. 删除堆区开辟数组,要加中括号,否则只会释放一个

image.png

栈上开辟数组,可不可以没有默认构造,可以没有默认构造

示例:

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

class Person 
{
public:	
	Person()
	{
		cout << "Person的构造函数调用" << endl;
	}
	Person(int a) 
	{
		cout << "Person的有参构造函数调用" << endl;
	}
	~Person() 
	{
		cout << "Person的析构函数调用" << endl;
	}
};

//malloc和new区别
void test01() 
{
	Person* p = new Person;//会返回该类型的指针

	delete p;//手动释放堆区内存
}
//注意事项 不要用void *去接受new出来的对象,利用void *无法调用析构函数,因为不知道释放多大空间
void test02() 
{
	void* p = new Person;

	delete p;
}
//利用new开辟数组
void test03()
{
	//int* pInt = new int[10];
	//double* pD = new double[10];

	//堆区开辟数组,一定会调用默认构造函数,如果写了有参一定要再写默认构造函数
	Person* pPerson = new Person[10];
	Person* pPerson = new Person[10]{ Person(10),Person(20) }; //有的编译器这种不能识别,为了代码通用性,要写默认构造函数
	delete[] pPerson;
	//释放数组的时候不能直接释放,只会释放第一个

	//栈上开辟数组,可以没有默认构造函数,因为可以强制调用他的有参构造函数
	Person pArray[10] = { Person(10),Person(20),Person(20) };
}

int main()
{
	//test01();
	//test02();
	test03();

	system("pause");
	return EXIT_SUCCESS;
}