C++初阶:类与对象(尾篇)

news2025/1/11 14:47:41

目录

  • 1. 构造函数与初始化列表
    • 1.1 对象的创建与构造函数的初始化
    • 1.2 初始化列表及构造函数存在的意义
    • 1.3 explicit关键字与构造函数的类型转换
  • 2. static成员变量与static成员函数
    • 2.1 static成员变量
    • 2.2 static成员函数
  • 3. 日期类流插入操作符的重载与友元
    • 3.1 友元
    • 3.2 友元函数
    • 3.3 友元类
  • 4. 内部类
  • 5. 匿名对象
  • 6. 拷贝对象时编译器可能会进行的一些优化

1. 构造函数与初始化列表

1.1 对象的创建与构造函数的初始化

  1. 在前面的学习中,我们尝试了对简单类(日期类)进行了实现,而后在使用中我们通过定义类模板然后实例化的方式,创建我们所需要的对象。
  2. 在这一过程中,编译器按照所指定的类型去相应的内存区域中申请空间,在已经创建好变量后,再调用构造函数对生成的对象进行初始化。
  3. 大多数情况下,这种初始化的方式都不会出现问题,可是当类的成员变量中有一些特定的类型比如,const修饰的变量引用类型的变量没有默认构造函数的自定义类型,此时,这种初始化方式就行不通了。(默认构造函数:编译器自动生成,无参数,有缺省参数)

1.2 初始化列表及构造函数存在的意义

  1. 内置类型可以在创建变量申请空间时就进行变量的初始化,而自定义类型是否也可以在创建变量的同时就进行初始化,它的初始化方式是什么,接下来,我们引出类与对象中的初始化列表。
  2. 类实例化生成对象时并不是只开辟空间,对空间中的内容不做处理,而是会调用初始化列表对开辟出的空间进行初始化。前面之所以无法对特殊类型无法进行初始化,是因为我们没有向初始化列表中添加内容。
  1. 初始化列表的定义方式:
//构造函数
class A
{
private:
	int _a;
	int _b;
	int _c
	
	//构造函数,函数体之前,语法如下:
	A(int a, int b ,int c)
	:_a(a)
	,_b(b)
	,_c(c)
	{}
}
  1. 初始化列表的调用方式:(三种必须用初始化列表进行初始化的成员变量)
//没有缺省参数
class A
{
public:
	A(int d)
	{
		_d = d;
	}

	int _d;
};

class B
{
public:
	const int _a;
	int& _b;
	A _c;

	//构造函数
	B(int b, int c)
		:_a(10)
		,_b(b)
		,_c(c)
	{
		cout << " _a = " << _a << " _b = " << _b << " _c._d = " << _c._d << endl;
	}
};

int main()
{
	int b = 20;
	int c = 30;
	B a(b, c);

	return 0
}
  1. 初始化列表初始化成员变量的顺序:
    初始化列表进行初始化的顺序是根据成员变量的声明顺序决定的
class A
{
public:
	int _b;
	int _a;
	
	//先初始化_a,再初始化_b
	A(int a = 0)
		:_a(a)
		,_b(_a)
	{}

	void Print()
	{
		cout << _a << ' ' << _b << ' ' << endl;
	}
};

int main()
{
	A a(10);
	a.Print();

	return 0;
}

执行结果:使用成员变量_a初始化成员变量_b时,_a还没有被初始化
在这里插入图片描述

  1. <1> 既然初始化列表可以进行初始化,并且初始化列表能做到构造函数无法做到的特殊类型成员的声明,那么,为什么还要有构造函数呢?
    <2> 初始化列表能做的只有初始化,无法对初始化后的变量做检查与合法性判断
class A
{
public:
	int* _a;

	A(int n)
		:_a((int*)malloc(n * sizeof(int)))
	{
		if (_a == nullptr)
		{
			perror("malloc failed");
			exit(-1);
		}
	}
};

1.3 explicit关键字与构造函数的类型转换

  1. 类的默认成员函数operator=重载,其构造函数在只有单参数或拥有缺省参数,支持用与成员变量类型相同的数据,变量直接进行赋值操作。
class Date
{
public:
	int _year;
	int _month;
	int _day;

	Date(int year, int month = 1, int day = 1)
		:_year(year)
		,_month(month)
		,_day(day)
	{}

	Date& operator=(const Date& d)
	{
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}

		return *this;
	}
};

int main()
{
	Date d1(2024);
	d1 = 2025;
	int year = 2026;
	d1 = year;
	
	cout << d1._year << '-' << d1._month << '-' << d1._day << endl;
	
	return 0;
}

支持上述操作的原因,是因为数据或者变量会进行类型转换构造临时对象,然后再用临时构造出的对象进行赋值操作

  1. explicit关键字,修饰构造函数,使得这个构造函数所在的类其,实例化的对象不会发生类型转换的操作。
class Date
{
public:
	int _year;
	int _month;
	int _day;

	explicit Date(int year, int month = 1, int day = 1)
		:_year(year)
		,_month(month)
		,_day(day)
	{}
};

2. static成员变量与static成员函数

2.1 static成员变量

  1. 静态成员变量也是类的成员之一,它不独属于某个对象,而是属于这个类,为所有实例化对象所共有,存放在静态区。
  2. 静态成员变量在类中声明,在类外定义,在类外定义时不需要加static关键字。
  3. 静态成员变量的调用方式为,对象.静态成员变量类区域::静态成员变量
  4. 静态成员变量也受访问限定符的限制
class A
{
public:
	static int _count;

	A()
	{
		++_count;
	}

	A(const A& a)
	{
		++_count;
	}

	~A()
	{
		--_count;
	}
};

int A::_count = 0;

int main()
{
	A a1;
	cout << a1._count << endl;
	a1.~A();
	cout << A::_count << endl;

	return 0;
}

2.2 static成员函数

  1. 与静态成员变量类似,静态成员函数也是属于全体实例化对象,而不是属于某个对象。
  2. 静态成员函数的定义方式,也是类内声明类外定义。
  3. 静态成员函数没有this指针,不能调用非静态成员变量。
  4. 静态成员函数的调用方式,对象.静态成员函数类域::静态成员函数
  5. 同样的静态成员函数也受访问限定符限制。
class A
{
//public:
	static int _count;
	static int GetCount();
public:
	//非静态成员函数可以调用静态成员函数
	void Print()
	{
		cout << (*this).GetCount() << endl;
		cout << "hello" << endl;
	}

	A()
	{
		++_count;
	}

	A(const A& a)
	{
		++_count;
	}

	~A()
	{
		--_count;
	}
};

int A::_count = 0;
int A::GetCount()
{
	//静态成员函数没有this指针
	//无法调用非静态成员函数
	return A::_count;
}

3. 日期类流插入操作符的重载与友元

  1. 当我们尝试对实现过的日期类进行流插入运算的重载时,当把它作为成员函数时,我们发现无法实现。
  2. 操作数的次序为操作符重载函数的参数从左往右,分别是操作符的第一个,第二个…操作数。
  3. 成员函数的一个参数都为隐藏的默认参数this指针,而流插入操作符的需要的第一个参数是ostream类型的变量。可是,当我们不使用成员函数的方式实现,那么函数就无法访问private访问限定符修饰的成员变量。
  1. 那么,流插入操作符的重载就无法实现吗,这里我们引入C++新的内容,友元

3.1 友元

  1. 友元关系的实际应用分为友元类与友元函数,这是一种突破类访问限定符封装的方式,它在提供了这种功能的同时,也不可避免地增加了代码的耦合性,不建议多用。

3.2 友元函数

  1. 友元函数是普通函数,它定义在类外。而它达成友元的方式为,在类中对其进行友元声明。
  2. 友元函数可以在类中的任何地方声明,不受访问限定符影响。
  3. 友元函数的声明方式为,在普通的函数声明前加关键字friend
  4. 一个函数可以是多个类的友元函数。
class Date
{
public:
	int _year;
	int _month;
	int _day;

	Date(int year, int month = 1, int day = 1)
		:_year(year)
		,_month(month)
		,_day(day)
	{}

	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& out, const Date& d);
};

ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << '-' << d._month << '-' << d._day;

	return out;
}

istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	
	return in;
}

3.3 友元类

  1. 友元类的所有成员函数都是另一个类的友元函数,可以访问另一个类的所有私有成员。
  2. 友元类关系是单向的。
  3. 友元关系不可传递,A类是B类友元,B类是C类的友元,A类不是C类的友元,不可以访问C类。
  4. 友元关系不能被继承
  5. 友元类的声明方式为,在需要被访问的类中声明其的友元类,friend + 类名
class Time
{
public:
	Time(int hour = 0, int minute = 0, int seconds = 0)
		:_hour(hour)
		, _minute(minute)
		, _seconds(seconds)
	{}

	friend class Date;

private:
	int _hour;
	int _minute;
	int _seconds;
};

class Date
{
public:
	int _year;
	int _month;
	int _day;
	Time _t;

	Date(int year, int month = 1, int day = 1)
		:_year(year)
		,_month(month)
		,_day(day)
	{}

	void SetTime()
	{
		_t._hour = 21;
		_t._minute = 25;
		_t._seconds = 0;

		cout << _t._hour << '/' << _t._minute << '/' << _t._seconds << endl;
	}
};

4. 内部类

  1. 如果一个类定义在另一个类的内部,这个内部类就叫做内部类。
  2. 外部类对内部类没有访问权限。
  3. 内部类只是在外部类中声明,外部类计算大小时不包含内部。(sizeof(外部类)单纯只是外部类的大小)
  4. 内部类对外部类天生就是友元,并且可以直接访问外部类的静态成员变量与函数,无需指定类域。
  5. 内部类可以在任意访问限定符的区域声明,在private中时,无法进行内部类的调用实例化对象。
class A
{
private:
	int _a;
	static int _c;

public:
	class B
	{
	public:
		void test(A& x)
		{
			x._a = 10;
			_c = 20;

			cout << x._a << endl;
			cout << x._c << endl;
		}
	};
};

int A::_c = 0;

int main()
{
	A x;
	//內部类对象的声明
	A::B h;
	h.test(x);

	return 0;
}

5. 匿名对象

  1. 在实例化对象时,省略对象名的创建方式。
  2. 匿名对象的声明周期只有一行,紧接着下一行时,就会调用析构函数将其销毁。
class A
{
private:
	int _ a;
public:
	A(int a = 0)
	{
		cout << _a << endl;
	}

	void Print()
	{
		cout << "hello world" << endl;	
	}
};

//不支持此种调用构造函数的方式,因为无法识别其为函数的声明还是析构函数的调用
A a1();

//创建匿名对象的方式
A();

//匿名对象调用成员函数
A().Print();

6. 拷贝对象时编译器可能会进行的一些优化

  1. 在成员函数传参和传返回值的过程中,一般编译器会做一些优化,减少不必要对象的拷贝
  2. 在同一行中,连续的构造,拷贝构造操作编译器会进行优化:(同一表达式中)
    <1> 构造 + 拷贝构造 优化为 构造
    <2> 构造 + 构造 优化为 构造
    <3> 连续的拷贝构造 优化为 直接进行拷贝构造
    (临时对象,匿名对象生命周期只有一行)
class C
{
private:
	int _c;
public:
	C(int c)
	{
		cout << "C()" << endl;
	}

	C(const C& tmp)
	{
		cout << "C(const C&)" << endl;
	}
};

void f1(C aa)
{}

C f2()
{
	C aa;

	return aa;
}

int main()
{
	//构造 + 拷贝构造,类型转换,生成临时对象
	C c1(2);
	//构造 + 拷贝构造 => 构造
	f1(C(2));
	//连续的拷贝构造 =>直接进行拷贝构造
	C c3 = f2();
	//不会优化,需要引用中间变量
	const C& c4 = 2;
	//不会优化,没有在同一表达式中
	C c5(2);
	f1(c5);
	
	return 0;
}

补充练习:(内部类,访问限定符) 在这里插入图片描述

  1. 求n!
  2. 思路:创建对应数量的对象,在构造函数中++静态成员变量,使用静态成员变量计数
class Solution 
{
private:
	static int _ret;
	static int _i;
	//将内部类定义在private下可保证不被非法访问
	class B
	{
	//默认为私有
	public:
		B()
		{
			_i++;
			_ret += _i;
		}
	};

public:
	int Sum_Solution(int n) 
	{
		//将类B设为公有,即可在外部类访问
    	B arr[n];

    	return  _ret;
	}

};

int Solution::_i = 0;
int Solution::_ret = 0;

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

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

相关文章

【OpenGL手册19】几何着色器

目录 一、说明 二、渲染管线的逻辑 三、几何着色器 四、使用几何着色器 五、造几个房子 六、几何着色器渲染爆破物体 一、说明 如果说用顶点和片段着色器干了什么&#xff0c;其实不多。加入几何着色器&#xff0c;能够加大渲染能力&#xff0c;简化数据结构&#xff0c;…

【Linux】对进程PCB的理解查看进程信息的方法

一、学习准备&#xff1a;对操作系统工作模式的理解 首先我们要清楚的是&#xff0c;操作系统是一个进行软硬件资源管理的软件。操作系统对下要管理好底层硬件。每一个硬件的生产产商都会给他们的产品提供对应的驱动程序&#xff0c;驱动程序是特定于某一硬件或系统设备的软件组…

参与者中心方案设计

参与者中心方案设计 ⼀、背景介绍 为适应客⼾多元化&#xff0c;随着业务发展&#xff0c;需要⽀持以平台为基础⽀持多租⼾企业⼊驻⽅式进⾏对外放。 1.1 需求来源 客⼾、产品 1.2 需求描述 ⽀持多租⼾ ⽀持客⼾⾃定义⻆⾊、菜单权限 ⽀持根据不同⻆⾊设置不同数据权限控…

备战蓝桥杯Day26 - 二叉搜索树查询和删除操作

一、查询 递归查询 寻找的值比根节点大&#xff0c;遍历右子树&#xff1b; 寻找的值比根节点小&#xff0c;遍历左子树。 def qurey(self, node, val):if not node: # 没有节点&#xff0c;返回空return Noneif node.data < val:return self.qurey(node.rchild, val)el…

Spring MVC 如何返回响应

上期我们讲请求的时候&#xff0c;每个方法返回的数据就是响应&#xff0c;我们也可以返回一个静态页面&#xff0c;设置响应的状态码&#xff0c;Header信息等。 1. 返回静态页面 我们先在项目的static文件夹下创建一个HTML文件作为我们返回的页面&#xff1a; <!DOCTYPE…

OLAP与数据仓库和数据湖

OLAP与数据仓库和数据湖 本文阐述了OLAP、数据仓库和数据湖方面的基础知识以及相关论文。同时记录了我如何通过ChatGPT以及类似产品&#xff08;通义千问、文心一言&#xff09;来学习知识的。通过这个过程让我对于用AI科技提升学习和工作效率有了实践经验和切身感受。 预热 …

粤嵌6818嵌入式开发入门教程

学习目标 1.了解嵌入式开发 2.开发环境的搭建 3.Linux操作系统的基本操作 一、了解嵌入式开发 以应用为中心&#xff0c;以计算机技术为基础&#xff0c;软硬件可裁剪&#xff0c;适应应用系统对功能、可靠性、成本、体积、功耗严格要求的专用计算机系统。 1.嵌入式可以干…

再见 Pandas,又一数据处理神器

cuDF介绍 cuDF是一个基于Apache Arrow列内存格式的Python GPU DataFrame库&#xff0c;用于加载、连接、聚合、过滤和其他数据操作。cuDF还提供了类似于pandas的API。 GitHub&#xff1a; https://github.com/rapidsai/cudf Documentation&#xff1a; https://docs.rapids.a…

基于springboot+vue的大学生就业需求分析系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

2.亿级积分数据分库分表:增量数据同步之代码双写,为什么没用Canal?

1.亿级积分数据分库分表&#xff1a;总体方案设计 上一篇博客中写了一下积分数据分库分表的总体方案设计&#xff0c;里面说了采用应用程序代码双写的方式实现的增量数据同步&#xff0c;本篇就对这一块进行一些细化的介绍&#xff0c;包括&#xff1a; 为什么不用Canal监听数…

WebServer -- 八股(终章)

&#x1f442; Honey Honey - 孙燕姿 - 单曲 - 网易云音乐 目录 &#x1f33c;触类旁通 &#x1f6a9;线程 && 进程 线程与进程的区别 多线程锁是什么 进程 / 线程 / 协程 的区别 线程切换时&#xff0c;需要切换的状态 &#x1f382;并发 && 并行 并…

一起玩儿3D打印机——03 Marlin固件的获取和安装环境的配置

摘要&#xff1a;本文介绍Marlin固件的获取和安装环境的配置 Marlin是一款开源软件&#xff0c;其主页为&#xff1a;https://marlinfw.org/&#xff0c;首页正中就是下载连接&#xff0c;如下图所示&#xff1a; 单击下面的“Download Marlin 2.1.2.2”按钮就会进入下载页面&a…

Transformer学习笔记(一)

一、预训练 1、图像领域的预训练 通过 ImageNet 数据集我们训练出一个模型 A由于 CNN 的浅层学到的特征通用性特别强&#xff0c;我们可以对模型 A 做出一部分改进得到模型 B&#xff08;两种方法&#xff09;&#xff1a; 冻结&#xff1a;浅层参数使用模型 A 的参数&#x…

postgres中的hook机制

瀚高数据库 目录 环境 文档用途 详细信息 环境 系统平台&#xff1a;Linux x86-64 Red Hat Enterprise Linux 7 版本&#xff1a;14,13,12,11 文档用途 1.了解使用postgres中的hook机制&#xff0c;在不更改内核代码的前提下完成一些定制化需求&#xff1b; 2.从底层理解插…

Visual Studio 2022 出现:E1696无法打开 源 文件 “stdafx.h“;E0020未定义标识符 “_TCHAR“错误解决办法

今天在用自己电脑上的visual studio2022写并行计算实验报告时出现了这种错误&#xff0c;但我在机房电脑vs2010版本运行时没有任何问题&#xff0c;于是我在网上找了解决办法&#xff0c;现总结如下。 1.报错E1696无法打开 源 文件 "stdafx.h"&#xff0c;解决办法&a…

移动通信网络AT指令

AT 命令是用来控制 TE(如 PC 等用户终端)和 MT(如移动台等移动终端)之间交互 的规则 AT 命令的返回值包括两部分,响应信息和结果码。 PLMN 移动通信网络PLMN = MCC + MNC,PLMN由MCC移动国家码和MNC移动网络码组成,例如:中国移动GSM的PLMN为:46000(MCC:460, M…

Internet Download Manager(IDM下载) v6.42.3 绿色版介绍

互联网下载管理器是一个广泛使用的软件&#xff0c;它可以帮助用户更好地管理和加速他们的下载。最新版本v6.42.3已经发布&#xff0c;它带来了一系列新功能和改进&#xff0c;让用户更加方便和快速地下载他们需要的文件。 新版本的互联网下载管理器增加了对最新浏览器的支持&…

通过键盘对机械臂进行操作

1 #include<myhead.h>2 #include<linux/input.h>3 #define SER_PORT 88884 #define SER_IP "192.168.116.225"5 #define CLI_PORT 99996 #define CLI_IP "192.168.65.129"7 int main(int argc, const char *argv[])8 {9 //1、创建用于连接…

题目:特殊的三角形(蓝桥OJ 3008)

问题描述&#xff1a; 解题思路&#xff1a; 可以先求出1~1e6每个位置是否有解&#xff0c;后计算前缀和再求出不同区间的和。&#xff08;时间复杂度小&#xff09; 进行dfs操作&#xff1a;依次组合1~1e6所有元素。并计算每一个组合的乘积&#xff0c;在该乘积位置的cnt加一。…

2024.3.15

1.单向循环链表 代码&#xff1a; #include"loop.h" //创建单向循环链表 loop_p create_loop_list() {loop_p H (loop_p)malloc(sizeof(loop));if(HNULL){printf("空间申请失败\n");return NULL;}H->len0;H->nextH;return H; } //创建节点 loop_p…