C++之类和对象(3)

news2024/11/25 23:35:42

目录

1. 再谈构造函数

1.1 构造函数体赋值

 1.2 初始化列表

1.3 explicit 

 2. static成员

2.1 概念

 3. 友元

3.1 友元函数

3.2 友元类

4. 内部类

 5. 匿名对象

6. 拷贝对象时编译器做出的优化


1. 再谈构造函数

1.1 构造函数体赋值

class Date
{
public:
    Date(int year=2024, int month=3, int day=16)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void pri() {
        cout << _year <<" "<< _month <<" "<< _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main() {
    Date a;
    Date b(111);
    a.pri();
    b.pri();
    return 0;
}

虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。

 

 1.2 初始化列表

class Date
{
public:
	Date(int year, int month, int day)
        //初始化列表
		: _year(year)
		, _month(month)
		, _day(day)
	{}
	void pri() {
		cout << _year << " " << _month << " " << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main() {
	Date a(1,2,3);
	a.pri();
	return 0;
}


初始化列表是每个成员变量定义初始化的位置

 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)

const成员变量无法在函数体初始化,得在初始化列表初始化

int& ref;引用也得在初始化列表,因为引用定义必须初始化

自定义类型成员(且该类没有默认构造函数时)也在初始化列表



 

class A
{
public:
    A(int a)
        :_a1(a)
        , _a2(_a1)
    {}

    void Print() {
        cout << _a1 << " " << _a2 << endl;
    }
private:
    int _a2;
    int _a1;
};
int main() {
    A aa(1);
    aa.Print();
}

最后输出 1   -858993460

因为先初始化a2因为a2先在类中声明而且用的是a1的值初始化而a1是随机值

a1最后初始化为a,是1

能用初始化列表就用初始化列表

成员变量的顺序与初始化列表的顺序最好一致,因为成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后 次序无关

1.3 explicit 

单参数类型: 

class A {
public:
	A(int a=0) 
	:_aa(a)
	{
		cout <<_aa << endl;
	}
	
private:
	int _aa;
};
class B {
private:
	int _a;
	int* p = nullptr;
	int* pp = (int*)malloc(4);
};
int main() {
	B bb;
	A c1(1);
	A c2 = 2;//单参数构造函数支持隐式类型转换
	//所以是2构造一个临时对象然后拷贝构造
	const A& c3 = 4;
	return 0;
}

缺省值可以是其他的变量不局限于常量
如果不想存在隐式类型转换的话可以加explicit在构造函数前面:

c2和c3就出错了

 多参数类型:

class A {
public:
	 A(int a=0,int b=1) 
	:_aa(a)
    ,_bb(b)
	{
		cout <<_aa <<" ";
		cout << _bb << endl;
	}
	
private:
	int _aa;
	int _bb;
};
class B {
private:
	int _a;
	int* p = nullptr;
	int* pp = (int*)malloc(4);
};
int main() {
	B bb;
	A c1(1,2);
	A c2 = {3,4};
	const A& c3 = {5,6};
	return 0;
}
多参数支持花括号产生隐式类型转换

同样加了explicit就不行了:

 用explicit修饰构造函数,将会禁止构造函数的隐式转换

 2. static成员

2.1 概念

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用 static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化

静态成员函数没有this指针所以他只是为了访问静态成员变量他访问不了非静态成员变量因为没this

class A
{
public:
    A() {
        ++n;
    }
    A(const A& aa) {
        ++n;
    }
//private:
    static int n;//声明
    //属于整个类,但本质还是静态全局变量
};
int A::n = 0;//在类外定义
void func() {
    n += 1;//那么这里就访问不了n了
}
int main() {
    A a;
    A b;
    cout << n << endl;//这里也是访问不了n
    return 0;

}

 

class A
{
	
	public:
		A() { ++_scount; }
		A(const A & t) { ++_scount; }
		~A() { --_scount; }
		static int GetACount() { return _scount; }
	private:
		static int _scount;
	};
	int A::_scount = 0;
	void TestA()
	{
		cout << A::GetACount() << endl;
		A a1, a2;
		A a3(a1);
		cout << A::GetACount() << endl;
	}
	int main() {
		TestA();
		return 0;
	}

最终结果0 3

当创建对象 a1 时会调用默认构造函数 A()

输出 _scount 的初始值,初始值为 0。

创建对象 a1,_scount 自增为 1。

创建对象 a2,_scount 再次自增为 2。

通过复制构造函数创建对象 a3,_scount 再次自增为 3。

输出 _scount 的最终值,值为 3。

注意:

1. 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区

2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明

3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问

4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员

5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制

 3. 友元

友元提供了一种突破封装的方式,有时提供了便利。但是友元破坏了封装,所以友元不宜多用-慎用

3.1 友元函数

友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。

class Date
{
	//友元函数
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
		
public:
	Date(int year = 1900, int month = 1, int day = 1)
		:_year(year)
		,_month(month)
		,_day(day)
	{}
private:
	int _year;
	int _month;
	int _day;
};
 
ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "/" << d._month << "/" << d._day << endl;
	return out;
}
istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}
int main()
{
	Date d1;
	cin >> d1;
	cout << d1;
	return 0;
}

友元函数是普通的全局函数:

#include <iostream>
 
using namespace std;
 
class A
{
 
 private:
     int A;
  public:
     print(){};
     
     //声明全局函数 person 是 类A 的友元函数
     friend void person (int &x);
}
 
 
void person(int &x)
{
   //使用了类A的成员变量age
   cout << "age=" << p.age << endl;
  
}
 
int main ()
{
   A p(22);
   person(p);
   return 0;
}

友元函数是其他类的成员函数:

#include <iostream>
 
using namespace std;
 
class A
{
 
 private:
     int A;
  public:
     print(){};
     
     //声明类B的成员函数 person 是 类A 的友元函数
     friend  void B::person (int &x);
}
 
 
class B
{
  private:
     int B;
  public:
     person(int &x);
 
}
 
B::person(int &x)
{
   //因为类B的成员函数person是类A的友元函数,所以看可以使用类A的成员变量age
   cout << "age=" << p.age << endl;
  
}
 
int main ()
{
   A p(22);
 
   B q;
   q.person(p);
   return 0;
  
}

注意:

  • 友元函数可访问类的私有和保护成员,但不是类的成员函数
  • 友元函数不能用const修饰
  • 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  • 一个函数可以是多个类的友元函数
  • 友元函数的调用与普通函数的调用原理相同

3.2 友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。

友元关系是单向的,不具有交换性。
比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接
访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
友元关系不能传递:如果C是B的友元, B是A的友元,则不能说明C时A的友元。
友元关系不能继承(后续解析)

//时间类
class Time
{
   friend class Date;   
// 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
 
public:
 
 //成员函数的初始化列表
 Time(int hour = 0, int minute = 0, int second = 0)
 : _hour(hour)
 , _minute(minute)
 , _second(second)
 {}
   
private:
   int _hour;
   int _minute;
   int _second;
};
 
 
//日期类
class Date
{
public:
 
   //成员函数的初始化列表
   Date(int year = 1900, int month = 1, int day = 1)
       : _year(year)
       , _month(month)
       , _day(day)
   {}
   
   void SetTimeOfDate(int hour, int minute, int second)
   {
       // 直接访问时间类私有的成员变量,因为日期类是时间类的友元类
       _t._hour = hour;
       _t._minute = minute;
       _t._second = second;
   }
   
private:
   int _year;
   int _month;
   int _day;
   Time _t;
};

优缺点总结 :

点:友元函数不是类的成员但是却具有成员的权限,可以访问类中受保护的成员,这破坏了类的封装特性和权限管控;

优点:可以实现类之间的数据共享;比如上面互为友元类,则可以互相访问对方受保护的成员;

总结:友元函数是一种破坏封装特性的机制,可以让程序员写代码更灵活,但是不能滥用

4. 内部类

概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。

注意:内部类就是外部类的友元类,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。

特性:

  • 1. 内部类可以定义在外部类的public、protected、private都是可以的。
  • 2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
  • 3. sizeof(外部类)=外部类,和内部类没有任何关系。
    // 1、B类受A类域和访问限定符的限制,其实他们是两个独立的类
    // 2、内部类默认就是外部类的友元类
    class A
    {//外部类不能访问内部类
    public:
    	class B // B天生就是A的友元
    	{
    	public:
    		void print(const A& a)
    		{
    			cout << k << endl;   //可以直接访问A的静态成员变量
    			cout << a.h << endl; //也可以访问A的成员变量
    		}
    	};
    private:
    	static int k;
    	int h;
    };
    int A::k = 1;
     
    int main()
    {
    	A::B b;
    	b.print(A());
    	cout << sizeof(A) << endl;  //8   外部类的大小与内部类无关  a与b相互独立的只不过b受a类的局域限制
    	return 0;
    }
    类本身不占用空间

C++不太喜欢使用内部类,所以了解即可 

 5. 匿名对象

匿名对象的特点就是不用取名字,生命周期只存在定义的这一行。

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};
 
int main()
{
	A aa1;
	//A aa1();// 不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义
	
	// 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字,
	A();// 但是他的生命周期只有这一行,紧接着它的下一步就会自动调用析构函数
 
	A aa2(2);
	return 0;
}

 应用场景: 当我们做C++的OJ题时会发现都是将其封装在一个Solution类中的,假设我们需要调用这个类中的某一个函数,是需要先创建一个Solution的对象,然后通过这个对象进行调用,这样的话有点麻烦,我们可以直接使用匿名对象来调用这个类中的成员函数。

class Solution {
public:
	int Sum_Solution(int n) {
		//...
		return n;
	}
};
int main()
{
	// 1.基本方法
	Solution sl;
	sl.Sum_Solution(10);
 
	// 2.匿名对象
	Solution().Sum_Solution(10);
 
	return 0;
}

6. 拷贝对象时编译器做出的优化

在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝,这个在一些场景下还是非常有用的

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}
	A(const A& aa)
		:_a(aa._a)
	{
		cout << "A(const A& aa)" << endl;
	}
	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a = aa._a;
		}
		return *this;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};
void f1(A aa)
{}
A f2()
{
	A aa;
	return aa;
}
int main()
{
	// 传值传参
	A aa1;
	f1(aa1);
	cout << endl;
 
	// 传值返回
	f2();
	cout << endl;
 
	// 隐式类型,连续构造+拷贝构造->优化为直接构造
	f1(1);
 
	// 一个表达式中,连续构造+拷贝构造->优化为一个构造
	f1(A(2));
	cout << endl;
 
	// 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造
	A aa2 = f2();
	cout << endl;
 
	// 一个表达式中,连续拷贝构造+赋值重载->无法优化
	aa1 = f2();
	cout << endl;
	return 0;
}


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

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

相关文章

实现界面跳转及注册界面编写(AndroidStudio)

目录 一、代码 二、最后效果 一、代码 1.先新建一个activity文件 2.注册界面的代码如下&#xff1a; <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:la…

(附数据集)基于lora参数微调Qwen1.8chat模型的实战教程

基于lora微调Qwen1.8chat的实战教程 日期&#xff1a;2024-3-16作者&#xff1a;小知运行环境&#xff1a;jupyterLab描述&#xff1a;基于lora参数微调Qwen1.8chat模型。 样例数据集 - qwen_chat.json&#xff08;小份数据&#xff09; - chat.json&#xff08;中份数据&…

Tuxera NTFS 2023安装使用教程 Tuxera NTFS破解版 Tuxera NTFS for Mac优惠

对于必须在Windows电脑和Mac电脑之间来回切换的Mac朋友来说&#xff0c;跨平台不兼容一直是一个巨大的障碍&#xff0c;尤其是当我们需要使用NTFS格式的硬盘在Windows和macOS之间共享文件时。因为Mac默认不支持写入NTFS磁盘。 为了解决这一问题&#xff0c;很多朋友会选择很便捷…

vscode插件开发-发布插件

安装vsce vsce是“Visual Studio Code Extensions”的缩写&#xff0c;是一个用于打包、发布和管理VS Code扩展的命令行工具。 确保您安装了Node.js。然后运行&#xff1a; npm install -g vscode/vsce 您可以使用vsce轻松打包和发布扩展&#xff1a; // 打包插件生成name…

RansomwareSim:一款功能强大的勒索软件模拟研究学习工具

关于RansomwareSim RansomwareSim是一款功能强大的勒索软件模拟研究学习工具&#xff0c;该工具是为网络安全教育和培训目的开发的模拟勒索软件应用程序&#xff0c;它旨在为广大研究人员演示勒索软件如何加密系统上的文件并与命令和控制服务器通信&#xff0c;以更好地了解勒…

“一键解锁复古魅力:底片效果瞬间生成!“

时光荏苒&#xff0c;岁月如梭。你是否曾怀念那些旧时光里&#xff0c;老照片所散发出的独特韵味&#xff1f;那种历经岁月沉淀的底片效果&#xff0c;仿佛能带我们回到那些被遗忘的角落&#xff0c;重温那些温馨的瞬间。 首先第一步&#xff0c;我们要进入视频剪辑高手&#…

java数据结构与算法刷题-----LeetCode376. 摆动序列

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 1. 贪心2. 动态规划3. 优化版动态规划 1. 贪心 解题思路&#x…

【强化学习笔记一】初识强化学习(定义、应用、分类、性能指标、小车上山案例及代码)

文章目录 第1章 初识强化学习1.1 强化学习及其关键元素1.2 强化学习的应用1.3 强化学习的分类1.3.1 按任务分类1.3.2 按算法分类 1.4 强化学习算法的性能指标1.5 案例&#xff1a;基于Gym库的智能体/环境接口1.5.1 安装Gym库1.5.2 使用Gym库1.5.3 小车上山1.5.3.1 有限动作空间…

软考80-上午题-【面向对象技术3-设计模式】-结构型设计模式03

一、外观模式 1-1、意图 为子系统中的一组接口提供一个一致的界面。 Facade 模式定义了一个高层接口&#xff0c;这个接口使得这一子系统更加容易使用。 1-2、结构 Facade 知道哪些子系统类负责处理请求&#xff1a;将客户的请求代理给适当的子系统对象。Subsvstem classes …

Mock.js了解(Mock就是模拟一个后端,Postman模拟前端)

JSON5 Node.js Vue CLI与Mock.js Jquery与Mock.js Mock与分页

Linux - 线程互斥和互斥锁

文章目录 前言一、为什么要线程互斥原子性 二、互斥锁互斥锁的创建与销毁互斥锁进行互斥 前言 前几节课&#xff0c;我们学习了多线程的基础概念&#xff0c;这节课&#xff0c;我们来对线程互斥和互斥锁的内容进行学习。 一、为什么要线程互斥 首先我们要明白&#xff0c;对…

openGauss学习笔记-244 openGauss性能调优-SQL调优-典型SQL调优点-统计信息调优

文章目录 openGauss学习笔记-244 openGauss性能调优-SQL调优-典型SQL调优点-统计信息调优244.1 统计信息调优244.1.1 统计信息调优介绍244.1.2 实例分析&#xff1a;未收集统计信息导致查询性能差 openGauss学习笔记-244 openGauss性能调优-SQL调优-典型SQL调优点-统计信息调优…

JVM学习-底层字节码的执行过程

目录 1.一个简单的程序分析 2. a&#xff0c;a&#xff0c;a--在JVM中的执行过程 3. 一个好玩的xx 4.方法调用的字节码分析、多态的实现、对象头 5. try-catch-finally的字节码分析 5.1 try-catch 5.2 try-catch-finally 5.3特殊情况 5.3.1 try和finally块中都出现了re…

第18节 动态规划一讲

1假设有排成一行的N个位置记为1~N&#xff0c;N一定大于或等于2 开始时机器人在其中的M位置上(M一定是1~N中的一个) 如果机器人来到1位置&#xff0c;那么下一步只能往右来到2位置&#xff1b; 如果机器人来到N位置&#xff0c;那么下一步只能往左来到N-1位置&#xff1b; 如果…

GiT: Towards Generalist Vision Transformer through Universal Language Interface

GiT: Towards Generalist Vision Transformer through Universal Language Interface 相关链接&#xff1a;arxiv github 关键字&#xff1a;Generalist Vision Transformer (GiT)、Universal Language Interface、Multi-task Learning、Zero-shot Transfer、Transformer 摘要 …

BigDecimal保留两位小数失败问题

文章目录 背景问题解决如何测试代码 背景 测试时发现在线swagger测试会自动处理BigDecimal小数点后面的数字&#xff0c;就是有零的会都给你去掉&#xff0c;比如9.000与9.500到最后都会被swagger处理成9跟9.5。使用postman测是最准的&#xff0c;测出来的就是9.000跟9.500。 …

Rocky Linux 基本工具的安装

1.系统安装后先查看ip地址 ip addr 2.安装net工具 &#xff1a;ifconfig yum install net-tools 3.安装gcc &#xff1b;选择都选 y yum install gcc yum install gcc-c 4.安装tcl yum install -y tcl 5.安装lsof &#xff08;端口查看工具&#xff09; yum install l…

MySQL实现事务隔离的秘诀之锁

在MySQL中&#xff0c;有多种锁类型&#xff0c;我们先了解三种概念的锁&#xff0c;以便对接下来的内容有更好理解。 表级锁&#xff08;Table Lock&#xff09;&#xff1a;对整个表加锁&#xff0c;其他事务无法修改或读取该表的数据&#xff0c;但可以对其他表进行操作。页…

SLAM 算法综述

LiDAR SLAM 其主要思想是通过两个算法&#xff1a;一个高频激光里程计进行低精度的运动估计&#xff0c;即使用激光雷达做里程计计算两次扫描之间的位姿变换&#xff1b;另一个是执行低频但是高精度的建图与校正里程计&#xff0c;利用多次扫描的结果构建地图&#xff0c;细化位…

切面条-蓝桥杯?-Lua 中文代码解题第1题

切面条-蓝桥杯&#xff1f;-Lua 中文代码解题第1题 一根高筋拉面&#xff0c;中间切一刀&#xff0c;可以得到2根面条。 如果先对折1次&#xff0c;中间切一刀&#xff0c;可以得到3根面条。 如果连续对折2次&#xff0c;中间切一刀&#xff0c;可以得到5根面条。 那么&#xf…