C++中的类

news2024/11/25 2:49:00

1、类和对象

C++中通过class定义类

class A
{
   int a;
};  // 定义一个A类型的类

通过类来定义对象

A a; // 定义一个A类型的对象

类是一张蓝图,是抽象的。而对象是根据蓝图真正建造出来的建筑,是具象的。

对象是类的实体化

2、类的限制修饰符

类有三种修饰符:public、private和protected

public表示公有,即类内外都可以访问

private表示私有,即只能类内成员访问

protected表示受保护的,也是只能类内成员访问

从简单理解类的角度来看,可以认为后两者是一样的,他们的区别主要在于继承

class Person
{
public:
   void PrintPersonInfo()
   {
      for (int i = 0; i < 20; ++i)
          printf("%c ", _name[i]);
      printf("\nage: %d", _age);
   }
private:
   char _name[20];
   char _gender[3];
   int _age;
};

在类中,类内成员函数是可以访问类内成员变量的,访问限定符是限制类外对类内的访问

访问限定符只在编译期有效,真正代码映射到内存后,是没有用的

3、计算类的大小

计算类的大小就需要考虑两个因素:成员函数和成员变量,因为类中就只有这俩哥们

但是成员函数是不占用空间的,这是因为C++中类成员函数的存储方式是:所有成员函数均存放在公共代码区,在编译期间就确定了这些成员函数的地址

对于成员函数的存储方式,要注意的是:

1. 成员函数是属于整个类的,和一个具体的对象无关

2. 计算类的大小不包含成员函数,只需要考虑成员变量即可

因此计算类的大小就和C语言中计算结构体大小类似

比如说这个类

class A
{
public:
    void print();
    int _a;
    char _c;
}

根据结构体对齐规则,就可以知道大小应该是12字节

结构体对齐规则:

  1. 第一个成员在与结构体偏移量为0的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
    注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
    VS中默认的对齐数为8
  3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是
    所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

理解成员函数的存储方式

class A
{
public:
    void print()
    {
        return;
    }

};

int main() {
    A* a = nullptr;
    a->print();
    cout << "a->print() execute" << endl;
    return 0;
}

代码运行结果
a->print() execute

这段代码之所以能够正常运行,是因为函数的地址和对象是无关的,函数的地址在编译期间便已经确定,因此当执行a->print()的时候,并不需要对nullptr解引用,而是直接call 对应的成员函数地址

空类和空类所定义的对象占用1字节空间,用来标识对象以及类是存在的(空类指没有成员变量的类)

4、this指针

this指针是对象的地址,用来标识一个对象

由于成员函数是所有对象共享的,而成员函数很多时候需要去访问成员变量(不访问成员变量放类里面干啥)

class A
{
public:
    void print()
    {
        cout << _a << endl;
    }
    int _a;

};

int main() {
    A a1;
    cout << a1._a << endl;
    return 0;
}

比如这里a1去访问print(),而print是需要查看a1的_a的,这个_a是存在于a1对象中的,因此成员函数必须要有a1对象的地址才能够去访问到_a变量。

由此可以看出,成员函数,要具有访问任意一个对象的能力,因此就需要this指针

成员函数其实有一个隐藏的参数,就是this指针,以上面的class A中的print为例,其实它的形式是:

void print(const A* this)
{
    cout << this._a << endl;
}

每次a1调用成员函数,都会将自己的地址传递给this,因此成员函数就具有了访问对象的能力

5、几种特殊的成员函数

5.1、构造函数

构造函数只负责对象的初始化,并不负责对象空间的开辟

构造函数的名字和类名相同,无返回值,参数自定义

如果我们不自己写构造函数,编译器才会默认生成一个构造函数,这个构造函数对内置类型(int/double/char等)不做处理,对自定义类型去调用它的构造函数

构造函数支持重载和缺省参数

默认构造函数指不需要传参数就能够调用的构造函数

默认构造函数包括编译器生成的、无参构造和全缺省构造,至少要包含一个默认构造函数,当然,第一个默认构造不可能与后两者共存,后两者也不建议同时写,因为调用会出歧义。

class A
{
public:

    int _a;
    int _b;
    int _c;
};

对于这个类来说,下面的都是默认构造函数

    A() {}  // 无参
    A(int a = 1) {}  // 全缺省
    A(int b = 1) {}  // 全缺省
    A(int c = 1) {}   // 全缺省
    A(int a = 1, int b = 2) {}  // 全缺省
    A(int a = 2, int c = 1) {}  // 全缺省
    A(int b = 2, int c = 1) {}  // 全缺省

C++11补丁

构造函数按理来说不应该不处理内置类型的参数,所以c++11打了一个补丁,支持在成员变量声明的时候给予缺省值(不是赋值)

class A
{
public:

    int _a = 1;
    int _b = 2;
    int _c = 3;
};

5.2、析构函数

析构函数并不是完成对象本身的清理工作,而是完成对象中资源的清理

析构函数名字为 ~类名,无参数,无返回值,析构函数不支持重载

我们不写析构函数,编译器会给我们默认生成一个析构函数,这个析构函数对于自定义类型调用其析构函数,内置类型不做处理(因为内置类型自己会销毁)

这里的建议是,如果类中申请过资源,对象销毁后,资源仍还在(堆上),那么就需要自己写析构函数对齐进行清理

5.3、拷贝构造函数

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

  1. 拷贝构造函数是构造函数的一个重载形式,所以,拷贝构造也是构造函数
  2. 拷贝构造的形参只有一个,且必须是引用,否则会引发无穷递归

以下面的代码来解释一下为什么会引发无穷递归

class Date
{
public:
    Date(const Date date)
    {
        ...
    }
};

int main()
{
    Date date;
    Date b(date);
    return 0;

这里的A b(a)语句将a传给一个Date类型的形参,需要进行拷贝构造来构造出a,拷贝构造需要传参,传参又是拷贝构造,就成了死结

在这里插入图片描述

我们不写,编译器会默认生成一个拷贝构造函数,这个拷贝构造函数执行的是浅拷贝

深浅拷贝的问题

深拷贝:对于指针等资源,不是单纯的拷贝值,需要为拷贝重新申请一份资源

浅拷贝:仅拷贝值,又称值拷贝

查看下面这段代码

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	void Push(const DataType& data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	size_t _size;
	size_t _capacity;
};
int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	Stack s2(s1);
	return 0;
}

这个类中,在堆上申请了一个数组,由于执行的是默认拷贝构造,会把s1中的 _array 的值拷贝给s2,由于析构函数会free(_array), 所以_array就会被free两次,程序会崩溃

所以对于这种,还是得用深拷贝,为s2单独开辟资源

因此,如果需要深拷贝,那么就需要我们自定义拷贝构造,否则不需要

5.4、运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

注意:运算符重载不一定是类的成员函数,它可以声明定义在类外,和类是两个独立个体

函数名字为:关键字operator后面接需要重载的运算符符号。

函数原型:返回值类型 operator操作符(参数列表)

注意:

  • 不能通过连接其他符号来创建新的操作符:比如operator@
  • 重载操作符必须有一个类类型参数
  • 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
  • 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
  • . * :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。

这里通过一个日期类来阐述运算符重载的使用

class Date
{
public:
	// 全缺省默认构造
	Date(int year = 2023, int month = 7, int day = 4)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	int _year;
	int _month;
	int _day;
};

// 支持运算符重载
bool operator==(const Date&d1, const Date& d2)
{
	return (d1._year == d2._year) &&
		(d1._month == d2._month) &&
		(d1._day == d2._day);
}

int main()
{
	Date d1;
	Date d2;
	cout << (d1 == d2) << endl;  // d1传递给第1个形参,d2传递给第2个形参
	return 0;
}

这里把运算符重载写到了类外面,同样也可以写在类内,写在类内,就会多一个隐藏的this指针的形参,所以就只需要额外写一个形参即可

写了一段完整的日期类代码,如下示

#include <iostream>
#include <cstdio>


using namespace std;

int monthToDay[] = { -1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

class Date
{
public:
	// 全缺省默认构造
	Date(int year = 2023, int month = 7, int day = 4)
	{
                // 检验日期是否合法
		if ((IsLeapYear(year) && month == 2 && day > 29)
			|| (!IsLeapYear(year) && month == 2 && day > 28)
			|| ((month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) && day > 31)
			|| ((month == 2 || month == 4 || month == 6 || month == 9 || month == 11) && day > 30)
			|| year < 1 || month < 0 || day < 0 || month > 12)
		{
			cout << "非法日期!!!" << endl;
			exit(-1);
		}

		_year = year;
		_month = month;
		_day = day;
	}

	int GetYear() const
	{
		return _year;
	}

	int GetMonth() const
	{
		return _month;
	}

	int GetDay() const
	{
		return _day;
	}

	// 判断是否是闰年
	bool IsLeapYear(int year) const
	{
		return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
	}



	// 支持运算符重载
	bool operator==(const Date& d2) const
	{
		return ((_year == d2.GetYear()) &&
			(_month == d2.GetMonth()) &&
			(_day == d2.GetDay()));
	}

	bool operator!=(const Date& d2) const
	{
		return !((*this) == d2);
	}

	void operator=(const Date& d)
	{
		_year = d.GetYear();
		_month = d.GetMonth();
		_day = d.GetDay();
	}

	// 加上一个天数
	Date operator+(int newDay) const
	{
		// 计算年月日
		int day = _day + newDay;
		int month = _month;
		int year = _year;
		// 先判断年,再判断月
		if (IsLeapYear(year)) monthToDay[2] = 29;
		else monthToDay[2] = 28;
		while (day > monthToDay[month])
		{
			day -= monthToDay[month];
			++month;
			if (month > 12)
			{
				month -= 12;
				++year;
				if (IsLeapYear(year)) monthToDay[2] = 29;
				else monthToDay[2] = 28;
			}
		}
		Date d(year, month, day);
		return d;
	}
	// -
	Date operator-(int newDay) const
	{
		int day = _day;
		int month = _month;
		int year = _year;
		if (IsLeapYear(year)) monthToDay[2] = 29;
		else monthToDay[2] = 28;
		while (day < newDay)
		{
			newDay -= day;
			--month;
			if (month < 1)
			{
				--year;
				if (year < 0)
				{
					cout << "运算非法!!" << endl;
					exit(1);
				}
				month = 12;
				if (IsLeapYear(year)) monthToDay[2] = 29;
				else monthToDay[2] = 28;
			}
			day = monthToDay[month];
		}

		day -= newDay;

		Date d(year, month, day);
		return d;
	}
	// +=
	Date& operator+=(int newDay)
	{
		(*this) = (*this) + newDay;
		return (*this);
	}
	// 
	// -=
	Date& operator-=(int newDay)
	{
		(*this) = (*this) - newDay;
		return (*this);
	}
	// >
	bool operator>(const Date& d1) const
	{
		if (_year > d1.GetYear()) return true;
		else if (_year < d1.GetYear()) return false;
		else
		{
			if (_month > d1.GetMonth()) return true;
			else if (_month < d1.GetMonth()) return false;
			else
			{
				if (_day > d1.GetDay()) return true;
				else return false;
			}
		}
	}
	// <

	bool operator<(const Date& d1)
	{
		return (d1 > (*this)) && (d1 != (*this));
	}
private:
	int _year;
	int _month;
	int _day;
};



int main()
{
	Date d1;
	Date d2;
	d1 += 1000;
	printf("%d : %d : %d\n", d1.GetYear(), d1.GetMonth(), d1.GetDay());
	return 0;
}

测试日期类是否正确: https://time.org.cn/riqi/

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

xmrig病毒删除

删除病毒 [rootnode101 .mint-xmr]# top | headPID USER PR NI VIRT RES SHR S %CPU %MEM TIME COMMAND 15870 root 20 0 4597844 2.0g 4 S 1017 3.3 52:05.95 xmrig[rootnode101 .mint-xmr]# ll /proc/15870/exe lrwxrwxrwx. 1 root ro…

【C#】SqlBulkCopy批量添加注意DataTable必须与表列顺序一致,否则报错,以及关闭自增列

前篇文章在测试一个批量添加的操作&#xff0c;发现一致添加不成功&#xff0c;最后分析是字段列顺序不一致的问题 目录 1、列名不一致1.1、错误信息1.2、解决方法 2、关闭自增列2.1、不包含列2.2、特性关闭 1、列名不一致 1.1、错误信息 在调试时&#xff0c;一致提示如下错…

基于 Flink SQL CDC的实时数据同步方案

基于 Flink SQL CDC的实时数据同步方案http://www.dreamwu.com/post-1594.html

iframe编码为utf-8.嵌入页面为gb2312 ,word导出默认为gb2312 格式

使用记事本打开html&#xff0c;另存为&#xff0c;保存类型选择&#xff1a;"所有文件"、编码选择&#xff1a;“UTF-8”打开保存后文件编码类型改成utf-8 第一个步骤是为了把内容改成utf-8&#xff0c;然后直接执行第二步改编码可能会报乱码

解决printJS打印问题汇总

目录 一、打印预览表格列不全&#xff08;Element的el-table组件&#xff09; 1、打印设置“打印缩放” 2、修改el——table的底层代码&#xff08;如果页面上有多个表格慎用&#xff09; 一、打印预览表格列不全&#xff08;Element的el-table组件&#xff09; 问题描述&a…

简单的手机记事本app怎么查看提醒列表?

很多人平时都有随手记事的习惯&#xff0c;在记录事情的时候使用手机上的记事本app是一个不错的选择。有的记事本功能比较完善&#xff0c;不但能记事还能设置提醒&#xff0c;当有多条提醒内容存在时&#xff0c;简单的手机记事本app怎么查看提醒列表呢&#xff1f;以iPhone手…

Easyexcel 导出数据 一对多关系导出数据集合

客户要求 要求导出的表格如图 实现这样表格 很多人会想到动态表头&#xff0c;easypoi可以直接实现&#xff0c;但是我用的是easyexcel,而easyexcel自身并没有提供自动合并的功能所以还是需要自己来合并。 代码如下 首先我们来看下将嵌套数据平铺&#xff0c;不进行合并导出…

解决github打不开的方法(亲测有效)

网上提供了很多针对github打开慢的解决方案&#xff0c;什么又是改host文件&#xff0c;又是下载杂七杂八的加速器等等&#xff0c;其实效果并不好&#xff0c;微软商城已有对应的软件可以解决该问题&#xff0c;获取路径更为安全。 目录 安装Watt Toolkit找不到Microsoft Stor…

Aduc7126的PLA模块

PLA 一、PLA结构讲解 PLA是Aduc7126内部的可编辑逻辑阵列&#xff0c;Aduc7126总共有16个element&#xff0c;分为两组&#xff0c;如下图所示。 下图是PLA的其中一个element结构图&#xff0c;按照由左至右进行讲解&#xff1a; 左边MUX0、MUX1、MUX2、MUX3都是选择器&#…

将 InputStream 流转成 MultipartFile

MultipartFile是一个接口, 有一个MockMultipartFile实现类,里面有构造方法可以直接将输入流转为MutipartFile对象: MultipartFile File new MockMultipartFile(filename, file.getName(), file.getContentType(), fileStream); 使用MockMultipartFile类, 项目需要导入org.sp…

maven配置问题

maven配置问题 Error running ‘项目名 [install]’: No valid Maven installation No valid Maven installation found. Either set the home directory in the configuration dialog or set the M2_HOME environment variable on your system. 解决方法&#xff1a; 依次检…

Echarts柱状图循环配色多色彩

话不多说&#xff0c;直接上配置案例&#xff0c;欢迎留言分享交流 option {xAxis: {type: category,data: [Mon, Tue, Wed, Thu, Fri, Sat, Sun,"y","q","y","u"]},yAxis: {type: value},series: [{itemStyle: {normal: {// barBorde…

华为OD机试真题 Python 实现【数组的中心位置】【2023Q1 100分】,附详细解题思路

目录 一、题目描述二、输入描述三、输出描述四、解题思路五、Python算法源码六、效果展示1、输入2、输出 一、题目描述 给你一个整数数组nums&#xff0c;请计算数组的中心位置&#xff0c;数组的中心位置是数组的一个下标&#xff0c;其左侧所有元素相乘的积等于右侧所有元素…

Fiddler 工具的使用

文章目录 01 Fiddler 工具介绍1. 下载与安装2. Fiddler 工具界面介绍3. Fiddler 工具的工作原理 02 手工调用 HTTP 接口1. 发送HTTP请求2. 查看返回数据包 03 获取 PC 端的网络数据包04 获取手机端的网络数据包05 截包与改包场景一&#xff1a;截断请求数据&#xff0c;然后篡改…

一键安装和卸载docker及docker-compose

代码&#xff1a; #!/bin/bashSYSTEMD_PATH/usr/lib/systemd/system/docker.service DOCKER_FILEdocker-20.10.23.tgz DOCKER_COMPOSE_FILEdocker-compose-plugin-2.15.1-3.el8.x86_64.rpm RED\E[1;31m GREEN\E[1;32m YELOW\E[1;33m SHAN\E[1;31;5m RES\E[0mfunction install_…

项目中期检查会议和进度对接

1.召开中期项目检查会议&#xff0c;与团队成员和博士王锟对接进度。对整体项目表示满意接受&#xff0c;指出重点需要修改提升和进一步开发完善的部分&#xff0c;以增强系统的完整度、功能亮点和界面数量点。具体为 ①注重“highlight”&#xff0c;即布局凸显主题功能&…

spring cloud 之 ribbon

Ribbon概念 Spring Cloud Ribbon 是基于 Netflix Ribbon 实现的一套客户端负载均衡的工具&#xff0c;主要功能是提供客户端负载均衡算法和服务调用。简单的说&#xff0c;就是在配置文件中列出 Load Balance&#xff08; LB&#xff1a;将用户的请求平摊的分配到多个服务上&a…

JavaScript ES5中实现继承

1 对象和函数的原型 2 new、constructor 3 原型链的查找顺序 4 原型链实现的继承 5 借用构造函数继承 6 寄生组合实现继承 function 创建的名称如果开头是大写的&#xff0c;那这个创建的不是函数&#xff0c;是创建了类。 要注意区分&#xff01;本章很多这样子的类。 实…

热烈祝贺! 爱创科技加入中经联溯源技术专业委员会!

6月27日&#xff0c;中国商业股份制企业经济联合会溯源技术专业委员会为北京爱创科技股份有限公司举行入会授牌仪式。授牌仪式由溯源技术专业委员会副秘书长卢要宁同志主持&#xff0c;溯源技术专业委员会主任宁晓鹏及北京爱创科技股份有限公司企业发展规划部总监杜薇出席。 在…

【网络安全带你练爬虫-100练】第3练:遍历获取到的列表中元素

目录 一、前言&#xff1a; 二、分析代码 三、完善代码 一、前言&#xff1a; &#xff08;1&#xff09;本练&#xff0c;我们来完善一下对于数据的处理 &#xff08;2&#xff09;对于同一标签内的内容的遍历爬取 上一段代码的&#xff0c;我们是不是在那个曾用名、高新…