C++-第三章收尾:友元、匿名对象和new

news/2024/10/3 11:39:29 标签: c++, 开发语言

目录

第一节:友元

        1-1.友元函数

        1-2.友元类

第二节:匿名对象

第三节:编译器优化

        3-1.构造+拷贝->直接构造

        3-2.构造+拷贝+拷贝->直接构造

第四节:new

下期预告:


第一节:友元

        一个类可以有友元函数和友元类,我们一个一个的介绍它们的特性。

        1-1.友元函数

        一个类声明友元函数的案例如下:

class Test
{
	friend void Print(Test* t); // 声明在内
private:
	int a = 1;
};
void Print(Test* t) // 定义在外
{
	std::cout << t->a << std::endl;
}

        它的特点是能够访问、调用这个类中的private成员,比如用 Print 函数打印私有变量a:

int main()
{
	Test t;
	Print(&t);
}

 

        它的用途:

        一个类的成员函数的第一个参数是this指针,此时我们用operator重载第一个参数不是类类型的时候就会报错,operator重载函数也不能用static修饰,此时就需要使用友元函数,例如重载 <<:

class Test
{
	friend std::ostream& operator<<(std::ostream& stream,const Test& t); // 声明在内
private:
	int a = 1;
};
std::ostream& operator<<(std::ostream& stream,const Test& t) // 定义在外
{
	std::cout << t.a;
	return stream;
}
int main()
{
	Test t;
	std::cout << t << std::endl;
    return 0;
}

 

        注意:

        1)友元函数需要谨慎使用,因为它破坏了类的封装

        2)不能通过类像调用成员函数那样直接调用友元函数

        1-2.友元类

        一个类有两种方成为另一个类的友元类,第一种是像友元函数那样声明:

class Test
{
public:
	friend class test; // 声明类 test 是 Test 的友元
private:
	int a = 1;
};

class test
{
private:
	int b = 2;
};

        第二种是在 Test 中定义 test 类,这样的话 test 是 Test 的友元类(这种友元类又叫内部类):

class Test
{
	class test // test 是 Test 的友元
	{
	private:
		int b = 2;
	}; 
private:
	int a = 1;
};

         一个类的友元类可以访问、调用它自己的私有成员,例如通过 test 类打印 Test 类的私有变量a:

#include <iostream>
class Test
{
	friend class test; // 声明类 test 是 Test 的友元
private:
	int a = 1;
};
class test
{
public:
	void Print(Test& t1)
	{
		std::cout << t1.a << std::endl; // 打印Test类中的私有变量
	}
private:
	int b = 2;
};

int main()
{
	Test t1;
	test t2;
	t2.Print(t1);
    return 0;
}

 

        注意:

        如果A是B的友元类,不代表B是A的友元类,友元关系不是相互的

        如果A是B的内部类,那么A天生是B的友元类,且它们的关系是对等的

第二节:匿名对象

        匿名对象就是没有名字的对象,它的生命周期只在当前语句,一个匿名函数可以用"类名()"直接创建:

class Test
{
private:
	int a = 1;
};
int main()
{
	Test t(); // 有名对象
	Test(); // 匿名对象
	return 0;
}

        我们可以用它调用某个类中的成员函数:

#include <iostream>
class Test
{
public:
	void Hello()
	{
		std::cout << "Hello" << std::endl;
	}
private:
	int a = 1;
};
int main()
{
	Test().Hello();
	return 0;
}

        它在调用完后就会自动销毁,不会长久的占用内存空间。

        

第三节:编译器优化

        在类进行构造时,如果发生了隐式类型转换,编译器可能会进行如下优化:

        3-1.构造+拷贝->直接构造

        例如:

Test t1 = 2;

        正常流程是先用2调用构造函数创建一个匿名对象,然后用这个匿名对象对t1进行拷贝构造。

        编译器优化后就变成了用2直接构造t1,即:

Test t1(2);
#include <iostream>
class Test
{
public:
	Test(int x): // 初始化构造
		a(x)
	{
		std::cout << "Test(int)" << std::endl;
	}
	Test(const Test& t) // 拷贝构造
	{
		a = t.a;
		std::cout << "Test(const Test&)" << std::endl;
	}
private:
	int a;
};
int main()
{
	Test t = 2;
	return 0;
}

  

        3-2.构造+拷贝+拷贝->直接构造

        例如:

Test func()
{
    return Test(2);
}

Test t1 = func();

        调用func函数后,会先因为Test()构造一个匿名对象,然后返回值没加&,所以返回的是这个匿名对象的一份拷贝,这有需要调用一次拷贝,最后调用拷贝构造给t1初始化。

        编译器优化后会跳过各种拷贝,直接用return后面的()里的值给t1构造。

#include <iostream>
class Test
{
public:
	Test(int x): // 初始化构造
		a(x)
	{
		std::cout << "Test(int)" << std::endl;
	}
	Test(const Test& t) // 拷贝构造
	{
		a = t.a;
		std::cout << "Test(const Test&)" << std::endl;
	}
private:
	int a;
};
Test func()
{
	return Test(2);
}
int main()
{
	Test t = func();
	return 0;
}

 

        

第四节:new

        new的中文意思是"新的",它的作用是在堆区开辟空间,然后返回它的指针:

类型* 指针 = new 类型;

        例如在堆区开辟一个int类型大小的空间:

int* ptr = new int;

        如果想开辟一块连续空间,可以加[ ]:

int* ptr = new int[4]; // 空间大小=sizeof int*4

        像上面这样开辟空间,空间是没有初始化的,要初始化可以加( ):

int* ptr = new int(10); // 开辟sizeof int大小的空间,初始化成10
int* ptr = new int[4]{1,2,3}; // 开辟 sizeof int*4大小的空间,按顺序初始化成1、2、3,没有写的就默认初始化成0;即{1,2,3,0}.

        对于自定义类型,它甚至可以调用它的构造函数进行初始化:

class Test
{
public:
    Test(int a):
        _a(a)
    {}
private:
    int _a;
}

Test* ptr = new Test(1);

        如果new的过程中出现错误,它会自动抛出异常,不需要用if(ptr == nullptr)进行检查。

        C++也可以使用C语言的malloc等函数来在堆区上开辟空间,且达成同样效果,实际上new的底层就是malloc,但是new的可读性更好、使用起来也更方便。

下期预告:

        C语言中malloc出来的空间需要用free释放,C++中new出来的空间也需要delete释放,所以下一次将进入delete的介绍,new开辟空间时的特殊处理和new、delete的底层。


http://www.niftyadmin.cn/n/5688285.html

相关文章

VSCode开发Vue3+TS项目中遇到各种波浪线(诊断信息)

一、问题汇总 在使用Visual Studio Code&#xff08;VSCode&#xff09;开发Vue3 TypeScript项目时&#xff0c;会遇到各种波浪线错误&#xff08;诊断信息&#xff09;&#xff0c;这些问题或错误通常由以下几人原因引起的&#xff1a; 1.1 常见问题 1、typeScript配置问题…

electron出现乱码和使用cmd出现乱码

第一种出现乱码。这种可以通过chcp 65001&#xff0c;设置为utf-8的编码。第二种&#xff0c;是执行exec的时候出现乱码&#xff0c;这个时候需要设置一些编码格式&#xff0c;可以通过iconv-lite进行解决&#xff0c;这个方法是node自带的&#xff0c;所以不需要导入。使用方法…

MySQL 索引选择详解

✨MySQL 索引选择详解✨ 引言 在使用 MySQL 进行数据查询时&#xff0c;索引是提升性能的关键工具。通过合理选择和优化索引&#xff0c;可以显著加快查询速度&#xff0c;减少磁盘 I/O&#xff0c;进而提高数据库响应时间。然而&#xff0c;有时 MySQL 可能不会选择我们预期…

文章解读与仿真程序复现思路——高电压技术EI\CSCD\北大核心《适用于并联构网型储能系统的协调有功控制策略设计》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

OpenCV第十二章——人脸识别

1.人脸跟踪 1.1 级联分类器 OpenCV中的级联分类器是一种基于AdaBoost算法的多级分类器&#xff0c;主要用于在图像中检测目标对象。以下是对其简单而全面的解释&#xff1a; 一、基本概念 级联分类器&#xff1a;是一种由多个简单分类器&#xff08;弱分类器&#xff09;级联组…

新手教学系列——爬虫异步并发注意事项

引言 爬虫是网络数据采集中不可或缺的工具,很多程序员在入门时会遇到这样的问题:为什么我的爬虫这么慢?尤其在面对大量数据时,单线程爬虫的速度可能让人捶胸顿足。随着爬虫规模的增大,异步并发成为了提高爬取效率的关键。然而,异步并发并不像表面看起来那么简单,如果没…

单链表的增删改查(数据结构)

之前我们学习了动态顺序表&#xff0c;今天我们来讲一讲单链表是如何进行增删改查的 一、单链表 1.1、单链表概念 概念&#xff1a;链表是⼀种物理存储结构上⾮连续、⾮顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接次序实现的。 1.2、链表与顺序表的…

Ascend C 自定义算子开发:高效的算子实现

Ascend C 自定义算子开发&#xff1a;高效的算子实现 在 Ascend C 平台上&#xff0c;开发自定义算子能够充分发挥硬件的性能优势&#xff0c;帮助开发者针对不同的应用场景进行优化。本文将以 AddCustom 算子为例&#xff0c;介绍 Ascend C 中自定义算子的开发流程及关键技术…