C++类与对象(中)第一篇

目录

前言:

类的六个默认成员函数

构造函数

析构函数

拷贝构造函数

拷贝场景一:函数参数类型为类类型对象

拷贝场景二:利用已存在的对象创建新对象

拷贝场景三:函数返回值类型为类类型对象


前言:

编译器编译类的详细步骤:

  1. 先识别类名;
  2. 识别类中有哪些成员变量;
  3. 识别类中有哪些成员函数;
  4. 编译器对成员函数进行预处理,加上隐藏的this指针;

因而成员变量类域的前后顺序程序编译没有影响;

类的六个默认成员函数

//空类中什么都没有吗?
class Date
{

};
//任何类在什么都不写时,编译器会自动生成6个默认成员函数;
//默认成员函数:用户没有显示实现,编译器会生成的成员函数称为默认成员函数;

构造函数

对于学生类,创建一个学生对象,这个学生就应该具有姓名、年龄 、学号等,但是这些数据成员没有初始化,该学生的姓名 、年龄、 学号等数据成员的值将为随机值或0;此时这个学生对象无任何意义!创建对象时,C++自动初始化对象的工作专门由该类的构造函数完成;

构造函数的作用在创建对象,系统自动调用初始化数据成员构造函数是一个特殊的成员函数名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次

构造函数的特点:

  1. 类名即为函数名;
  2. 没有返回值(函数名前什么也不写,也没有void);
  3. 类创建对象时,编译器自动调用构造函数;
class Date
{
private:
	int _year;
	int _month;
	int _day;
public:
    //构造函数
	Date()//函数名为类型名&&返回值没有
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}
	void print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
};
int main()
{
	Date d;//创建对象d时自动调用构造函数Date()
	d.print();//只调用了print()函数
	return 0;
}

运行结果:

构造函数的特点:

4.  构造函数支持函数重载 ;

注:函数重载(同一作用域中的同名函数,同名函数的参数个数 参数类型 类型顺序不同);

class Date
{
private:
	int _year;
	int _month;
	int _day;
public:
	Date()
	{
		//声明
		_year = 2000;
		_month = 12;
		_day = 18;
	}
	Date(int year, int month, int day)
	{
	_year = year;
	_month = month;
	_day = day;
	}
	void print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
};

int main()
{
	Date d1;
	d1.print();

	Date d2(2023, 12, 18);
	d2.print();

	return 0;
}

 运行结果:

默认构造函数:

  1.  用户在类中没有显示定义构造函数, 编译器自动生成的构造函数,叫默认构造函数;
  2.  无参构造函数也可以叫默认构造函数;
  3.  全缺省也可以叫默认构造函数;

可以不传参数可以调用构造函数,都可以叫默认构造函数

三个默认构造函数不能同时存在,只能存在一个默认构造函数

class Date
{
private:
	int _year;
	int _month;
	int _day;
public:
	Date()//无参的构造函数--->默认构造函数
	{
		_year = 2021;
		_month = 12;
		_day = 18;
	}
	Date(int year=2023, int month=10, int day=18)//全缺省参数的构造函数--->默认构造函数
	{
	_year = year;
	_month = month;
	_day = day;
	}  
    //默认构造函数只能存在一个;
	void print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
};
int main()
{
	Date d1;
	d1.print();
	return 0;
}

 运行结果:

类中用户没有显示定义构造函数,C++编译器自动生成一个无参的默认构造函数;当用户显示定义构造函数时,C++编译器不再自动生成

用户没有显示定义, 编译器自动生成默认构造函数,默认构造函数初始化数据成员时,对于内置类型(int float dobule ...... 指针)的数据,不做任何处理内置类型的数据为随机值

class Date
{
private:
	int _year;
	int _month;
	int _day;
public:
    //用户未定义默认构造函数,编译器自动生成默认构造
    //对于Date类中的内置类型不做任何处理
	void print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
};
int main()
{
	Date d1;
	d1.print();
	return 0;
}

监视窗口:

 用户没有显示定义, 编译器自动生成默认构造函数,默认构造函数初始化数据成员时,对于自定义类型(struct class union)的数据,调用自定义类型中的默认构造函数

class Time
{
private:
	int hour; 
	int minute;
	int second;
public:
	//显示定义构造函数
	Time()
	{
		cout << "Time()" << endl;
		int hour = 0;
		int minute = 0;
		int second = 0;
	}
};

class Date
{
private:
	int _year;
	int _month;//内置类型--->_year _month _day
	int _day;
	Time _t;//自定义类型----> _t
public:
	void print()
	  {
		cout << _year << "-" << _month << "-" << _day << endl;
	  }
};
int main()
{
	Date d;
	d.print();
	return 0;
}

运行结果:

由于默认构造函数对于内置类型成员不做任何处理,为弥补这种缺陷,C++11允许在类中声明时给缺省值

class Date
{
private:
	int _year = 2023;
	int _month = 12;//内置类型成员声明时给缺省值
	int _day = 18;
public:
	void print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

};
int main()
{
	Date d;
	d.print();
	return 0;
}

运行结果:

析构函数

析构函数的作用是在对象生命周期结束时自动调用,用于完成对象中资源的清理工作,例如释放内存、关闭文件等操作;

析构函数的特性:

  1. 析构函数的函数名:  ~ 类名
  2. 析构函数无参无返回值;
  3. 一个类只能有一个析构函数,若未显式定义,系统会自动生成默认的析构函数;

      ( 注意:析构函数不能重载

     4. 对象生命周期结束时,C++编译系统系统自动调用析构函数;

class Stack
{
private:
	int* _a;
	int _top;
	int _capacity;
public:
	//默认构造函数
	Stack(int capacity=4)
	{
		cout << "Stack()" << endl;
		_a = (int*)malloc(sizeof(int)* capacity);
		if (_a == nullptr)
		{
			perror("malloc failed:");
			exit(-1);
		}
		_top = 0;
		_capacity = capacity;
	}
	//析构函数
	~Stack() //函数名:~Stack && 没有参数
	{
		cout << "~stack()" << endl;
		free(_a);
		_a = nullptr;
		_top = 0;
		_capacity = 0;
	}
};

int main()
{
	Stack st;//创建对象时调用默认构造函数
	return 0;
}//对象st出作用域时自动调用析构函数

运行结果:

析构函数对于内置类型成员不做任何处理;对于自定义类型中的成员,清空哪个类的对象中的数据就调用哪个类的析构函数;

class Time
{
private:
	int _hour;
	int _minute;
	int _second;
public:
	~Time()
	{
		cout << "~Time" << endl;
	}
};
class Date
{
private:
	int _year;
	int _day; //内置类型: _year _month _day
	int _month;
	Time _t; //自定义类型: _t
};
int main()
{
	Date d;
	return 0;
}

运行结果:

如果类中没有申请资源时,用户可以不显示定义析构函数,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类;
class Stack
{
private:
	int* _a;
	int _top;
	int _capacity;
public:
	//默认构造函数
	Stack(int capacity=4)
	{
		cout << "Stack()" << endl;
		_a = (int*)malloc(sizeof(int)* capacity);
		if (_a == nullptr)
		{
			perror("malloc failed:");
			exit(-1);
		}
		_top = 0;
		_capacity = capacity;
	}
	//用户显示实现析构函数
	~Stack() 
	{
		cout << "~stack()" << endl;
		free(_a);
		_a = nullptr;
		_top = 0;
		_capacity = 0;
	}
};

int main()
{
	Stack st1;
    Stack st2;
	return 0;
}

由于main()函数建立函数栈帧时先创建st1对象,再创建st2对象,所以当main()函数函数栈帧销毁时,先对st2开辟的空间调用析构函数,再对st1开辟的空间调用析构函数;

拷贝构造函数

拷贝构造函数的作用:利用已经存在的对象创建一个新对象时(拷贝对象),就会调用新对象的拷贝构造函数初始化新对象的成员变量

拷贝构造函数的特征:

  1. 拷贝构造函数是构造函数的重载形式(拷贝构造函数是一个特殊的构造函数);
  2. 拷贝构造函数的参数是同类型的对象并且参数只有一个而且是类类型对象的引用;

拷贝场景一:函数参数类型为类类型对象

func()函数传参时将类对象d1作为实参传递给形参d,而传参的本质是拷贝,因为形参和实参空间是独立的,但数据内容是相同的;对象(结构体)可以传值调用,也可以传址调用;

class Date
{
private:
	int _year;
	int _month;
	int _day;
public:
	Date(int year = 2000, int month = 1, int day = 1)
		{
			_year = year;
			_month = month;
			_day = day;
		}
	void Print()
		{
		    cout << _year << "/" << _month << "/" << _day << endl;
		}

};
void func(Date d)
{
  d.Print();
}
int main()
{
	Date d1(2023, 12, 20);
	func(d1);
	return 0;
}

结构体作为实参,采用传值传参,结构体多大,传参时函数栈帧开辟的空间就有多大; 而且形参的改变并不能影响实参(空间是独立的);C++中,为了传参时不发生拷贝,可以采取传址传参传址传参只是获取到实参的地址,不会发生拷贝;还可以让引用作为func()函数的形参,因为引用只是实参的别名,形参和实参共用同一块内存空间,所以也不会发生拷贝

 浅拷贝/值拷贝: 拷贝构造函数对象按内存存储按字节序完成拷贝;

通过监视窗口查看日期类对象d1与拷贝对象d

假设极端场景下,不得不采用传值传参时,会发生什么?

class Stack
{
private:
	int* _a;
	int _capacity;
	int _top;
public:
	//构造函数
	Stack(int capacity = 4)
	{
		_a = (int*)malloc(sizeof(int) * capacity);
		if (nullptr == _a)
		{
			perror("malloc failed: ");
		}
		_capacity = capacity;
		_top = 0;
	}
	//析构函数
	~Stack()
	{
		free(_a);
		_capacity = _top = 0;
		_a = nullptr;
	}
};
void func2(Stack st)
{
	//...
}
int main()
{
	Stack st1;
	func2(st1);

	return 0;
}

运行结果:

原因如下:

由于是值拷贝,对象st1的成员变量_a与对象st的成员变量_a数值相同,那么这两个指针指向同一块内存空间,然而C++的析构函数在对象销毁时自动调用,func2()函数调用结束后,调用析构函数释放对象st中的成员变量_a所指向的空间,当主函数(main())调用结束后,调用析构函数释放对象st1中的成员变量_a所指向的空间,造成同一块空间的二次释放;

如何解决值拷贝所出现的问题?

C++规定,自定义类型的对象拷贝时,调用拷贝构造函数实现深拷贝;

拷贝场景二:利用已存在的对象创建新对象

class Date
{
private:
	int _year;
	int _month;
	int _day;
public:
//构造函数
    Date(int year = 2000, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
//假设日期类的拷贝构造函数如下
	Date(Date d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	void Print()
	{
	    cout << _year << "/" << _month << "/" << _day << endl;
	}

};
int main()
{
	Date d1(2023, 12, 20);//调用构造函数
	Date d2(d1);//调用拷贝构造函数 d1-已存在的对象 d2-拷贝对象

	return 0;
}

为什么使用拷贝构造函数使用传值方式编译器直接报错

因为会引发无穷递归调用;

原因如下:

 如何解决传值调用所引发的无穷递归问题?

 采用引用作为拷贝构造函数的形参

返回到拷贝场景一,浅拷贝对于栈类,对于同一块空间释放两次,如何解决?
class Stack
{
private:
	int* _a;
	int _capacity;
	int _top;
public:
	//构造函数
	Stack(int capacity = 4)
	{
		_a = (int*)malloc(sizeof(int)* capacity);
		if (nullptr == _a)
		{
			perror("malloc failed: ");
		}
		_capacity = capacity;
		_top = 0;
	}
	//拷贝构造函数
	//this指向st stt为st1的别名
	Stack(Stack& stt)
	{
		//开辟与st1一样大的空间
		this->_a = (int*)malloc(sizeof(int)*stt._capacity);
		if (nullptr ==this->_a)
		{
			perror("malloc failed:");
			exit(-1);
		}
		//拷贝指向的资源
		memcpy(this->_a, stt._a, sizeof(int)*stt._top);
		this->_top = stt._top;
		this->_capacity = stt._capacity;
	}
	//析构函数
	~Stack()
	{
		free(_a);
		_capacity = _top = 0;
		_a = nullptr;
	}
};
void func2(Stack st)
{
	//...
}
int main()
{
	Stack st1;
	func2(st1);
	return 0;
}

运行结果:

监视窗口:

用户未显示定义拷贝构造函数,编译器会生成默认的拷贝构造函数;

默认的拷贝构造函数对内置类型成员完成值拷贝,对于自定义类型成员调用其拷贝构造函数完成拷贝;

编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?
类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝;

拷贝场景三:函数返回值类型为类类型对象

class Stack
{
private:
	int* _a;
	int _capacity;
	int _top;
public:
	//构造函数
	Stack(int capacity = 4)
	{
		_a = (int*)malloc(sizeof(int)* capacity);
		if (nullptr == _a)
		{
			perror("malloc failed: ");
		}
		_capacity = capacity;
		_top = 0;
	}
	//拷贝构造函数
	//this指向st stt为st1的别名
	Stack(Stack& stt)
	{
		//开辟与st1一样大的空间
		this->_a = (int*)malloc(sizeof(int)*stt._capacity);
		if (nullptr ==this->_a)
		{
			perror("malloc failed:");
			exit(-1);
		}
		//拷贝指向的资源
		memcpy(this->_a, stt._a, sizeof(int)*stt._top);
		this->_top = stt._top;
		this->_capacity = stt._capacity;
	}
	//析构函数
	~Stack()
	{
		free(_a);
		_capacity = _top = 0;
		_a = nullptr;
	}
};

Stack func()
{
	Stack st;

	return st;//传值返回,返回谁?-->返回时先将局部变量st保存于寄存器中;
	//局部变量st出作用域销毁,因此返回的是st的拷贝;所以此处调用拷贝构造函数;
}
int main()
{
	func();
	return 0;
}

当局部变量st被static修饰,此时st存放于静态区,出作用域并不会被销毁,可以考虑返回类型为传引用返回,共用同一块内存空间,不会调用拷贝构造函数,少了一次拷贝;

Stack& func()
{
	static Stack st;

	return st;//传值返回,返回谁?-->返回时先将局部变量st保存于寄存器中;
	//局部变量st出作用域销毁,因此返回的是st的拷贝;所以此处调用拷贝构造函数;
}
int main()
{
	func();
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/265138.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JavaScript原型,原型链 ? 有什么特点?

一、原型 JavaScript 常被描述为一种基于原型的语言——每个对象拥有一个原型对象 当试图访问一个对象的属性时&#xff0c;它不仅仅在该对象上搜寻&#xff0c;还会搜寻该对象的原型&#xff0c;以及该对象的原型的原型&#xff0c;依次层层向上搜索&#xff0c;直到找到一个…

Unity中Shader旋转矩阵(四维旋转矩阵)

文章目录 前言一、围绕X轴旋转1、可以使用上篇文章中&#xff0c;同样的方法推导得出围绕X轴旋转的点阵。2、求M~rotate~ 二、围绕Y轴旋转1、可以使用上篇文章中&#xff0c;同样的方法推导得出围绕Y轴旋转的点阵。2、求M~rotate~ 三、围绕Z轴旋转1、可以使用上篇文章中&#x…

离散型制造企业为什么要注重MES管理系统的实施

离散型制造企业经常面临三个核心问题&#xff1a;生产什么、生产多少以及如何生产。尽管许多企业都实施了ERP系统&#xff0c;但仍然绕不开MES管理系统的话题。本文将从三个方面详细解释为什么离散型企业需要实施MES管理系统。 一、生产线经常出现的问题 在离散型企业中&#…

61权限提升-RedisPostgre令牌窃取进程注入

主要讲解redis数据库和postgresql数据库&#xff0c;然后还要两个windows的提权方式令牌窃取和进程注入。 postgresql是基于两个cve的漏洞&#xff0c;redis的提权方式第一种是利用任务执行的反弹shell&#xff0c;第二个是写一个ssh-keygen的公钥使用私钥登录&#xff0c;这是…

首席技术官CTO的具体职责表述十篇

一、岗位职责的作用意义 1.可以最大限度地实现劳动用工的科学配置; 2.有效地防止因职务重叠而发生的工作扯皮现象; 3.提高内部竞争活力&#xff0c;更好地发现和使用人才; 4.组织考核的依据; 5.提高工作效率和工作质量; 6.规范操作行为; 7.减少违章行为和违章事故的发生…

【数据结构和算法】---栈和队列的互相实现

目录 一、用栈实现队列1.1初始化队列1.2模拟入队列1.3模拟出队列1.4取模拟的队列头元素1.5判断队列是否为空 二、用队列实现栈2.1初始化栈2.2模拟出栈2.3模拟入栈2.4取模拟的栈顶元素2.5判读栈是否为空 一、用栈实现队列 具体题目可以参考LeetCode232. 用栈实现队列 首先要想到…

如何有效恢复 Android 上已删除的短信/短信

“ 我昨天不小心删除了小米中的一些重要短信&#xff0c;如何快速恢复它们&#xff1f;我急需他们&#xff0c;请帮帮我&#xff01;” 峰峰在 Android 论坛中提出的问题。 随着时代的变迁&#xff0c;人们的通讯方式也发生了很大的变化&#xff0c;各种即时通讯软件纷纷涌现…

常用单片机认识

单片机有哪些类型: 51单片机 AVR 单片机 MSP430 STM8 STM32 DSP Linux FPGA

微服务 Spring Cloud 10,如何追踪微服务调用?服务治理的常见手段

目录 一、服务追踪的作用1、优化系统瓶颈2、优化链路调用3、故障排查4、性能优化5、生成网络拓扑图4、透明传输数据 二、节点管理1、服务调用失败一般有两类原因造成&#xff1a;2、服务调用失败的解决方式&#xff1a;3、服务调用失败的具体解决方式&#xff1a; 三、负载均衡…

推箱子地图库1-49关

推箱子地图库1-49关 49关 local WALL1--{"墙","墙 "}4 10287 local DEST2--{"目的地",""}1 4001100 10157 local BOX3--{"箱子","&#xffe5;"} 2 2000801 local PLAYER4--{"玩家","&&a…

【小白专用】php以pdo方式连接sqlserver,开启sqlsrv扩展

一、安装ODBC程序&#xff0c; 下载适用于 SQL Server 的 ODBC 驱动程序 - 适用于 SQL Server 的 ODBC 驱动程序 |Microsoft 学习 运行安装程序&#xff0c;出现如下图所示页面&#xff1b; 选择下一步&#xff1b;选择我同意许可协议中的条款后选择下一步&#xff1b; 点击安…

C/C++ 外部链接的静态变量 static和extern的应用

外部链接的静态变量具有文件作用域、外部链接和静态存储期。该类别有时称为外部存储类别(external storage class),属于该类别的变量称为外部变量&#xff08;external variable&#xff09;。把变量的定义性声明放在所有函数的外面便创建了外部变量。当然&#xff0c;为了指出…

15-高并发-如何扩容

对于一个发展初期的系统来说&#xff0c;不太确定商业模型到底行不行&#xff0c;最好的办法是按照最小可行产品方法进行产品验证&#xff0c;因此&#xff0c;刚开始的功能会比较少&#xff0c;是一个大的单体应用&#xff0c;一般按照三层架构进行设计开发&#xff0c;使用单…

开启创意之旅:免费、开源的噪波贴图(noise texture)生成网站——noisecreater.com详细介绍

在当今数字创意领域&#xff0c;噪波贴图&#xff08;Noise Texture&#xff09;是游戏渲染、游戏开发、美术设计以及影视制作等行业不可或缺的艺术素材之一。为了满足广大创作者的需求&#xff0c;noisecreater.com应运而生&#xff0c;成为一款免费、开源的噪波贴图生成工具。…

【数据结构】无向图的最小生成树(Prime,Kruskal算法)

文章目录 前言一、最小生成树二、Kruskal算法1.方法&#xff1a;2.判断是否成环3.代码实现 三、 Prim算法1.方法&#xff1a;2.代码 四、源码 前言 连通图&#xff1a;在无向图中&#xff0c;若从顶点v1到顶点v2有路径&#xff0c;则称顶点v1与顶点v2是连通的。如果图中任意一对…

vue前端上传图片到阿里云OSS,超详细上传图片与视频教程

vue前端直传图片与视频到阿里云OSS 1. 简介与日常使用2. 为什么要这么干&#xff1f;是因为我司后端不行吗&#xff1f;&#xff1f;&#xff1f;&#xff08;确实&#xff01;&#xff09;3. vue前端直传的操作4. 如何上传到阿里OSS指定文件夹呢? 1. 简介与日常使用 阿里云…

(CVE-2019-9193)PostgreSQL 高权限命令执行漏洞的复现

漏洞概述 PostgreSQL是一个功能强大对象关系数据库管理系统(ORDBMS)。由于9.3增加一个“COPY TO/FROM PROGRAM”功能。这个功能就是允许数据库的超级用户以及pg_read_server_files组中的任何用户执行操作系统命令。 影响版本 9.3-11.2 环境搭建 1. 本次漏洞环境使用vulhub中…

java: -source 7 中不支持 lambda 表达式 (请使用 -source 8 或更高版本以启用 lambda 表达式)

目录 1、检查项目中 JDK 的设置&#xff1a; 2、检查模块中 JDK 的设置&#xff1a; 3、检查Idea 中的SDK设置 4、检查 IDEA 中 JDK 的设置&#xff08;我出现的问题在这&#xff09;&#xff1a; 今天遇见了一个报错&#xff1a; 问题产生的原因是 JDK 版本太低&#xf…

揭秘2024年最新骨传导耳机排行榜,全面解析骨传导耳机排行榜品牌

随着科技的飞速发展&#xff0c;人们对音质和舒适度的需求也在不断提高。骨传导耳机作为一种独特的耳机类型&#xff0c;近年来逐渐受到了消费者的关注。它通过将声音通过骨骼传导&#xff0c;而不是传统的耳道传递&#xff0c;既能保证音质&#xff0c;又能避免长时间佩戴耳机…

缓存高可用:缓存如何保证高可用?

前面我们提到了缓存集群的负载均衡策略&#xff0c;保证缓存服务的高可用&#xff0c;集群策略是最常用的&#xff0c;本文我们以 Redis 为例&#xff0c;分析一下单点缓存如何扩展到集群&#xff0c;以及集群部署的几种常见模式。 Redis 的主从复制 集群实现依靠副本&#x…