【c++】C++类和对象详解(下)

news2024/9/23 6:27:52

目录

思维导图大纲:

const成员函数 

取地址运算符重载 

再探构造函数-初始化列表 

隐式类型转换 

c语言中我们了解: 

c++中: 

单参数 

多参数 

防止类型转换 

 static成员

友元 

内部类 

匿名对象 

 对象拷贝时的编译器优化


思维导图大纲:

const成员函数 

  •  1.我们称被const修饰的成员函数为const成员函数,const修饰在函数后面
  •  2.const成员函数内部的this指针,将由A* const this 变成 const A* const this
  •  3.由原本的不可以更改*this指向的对象,变为即不可以更改*this指向的对象又不可以更改*this对象的内容
//举例
class A
{
public:
	// A(A* const this, int a = 0)   const修饰this,我们不可以更改this指向的对象
	A(int a = 0)
	{
		// this->_a = a;
		_a = a;
	}

	// 在函数Print中,我们传参A* const this,如果没有const修饰,那么在该函数可以对对象进行修改
	// 如果加上const修饰,传参过去相当于const A* const this,对于普通对象属于权限缩小,对于const对象属于权限平移
	// 我们平时成员函数不对成员变量进行修改时最好加上const修饰
	// void Print(const A* const this) const
	void Print() const
	{
		cout << _a << endl;
	}

private:
	int _a;
};

int main()
{
	// 定义普通对象aa1  传参过去相当于A* const this
	A aa1(1);
	aa1.Print();
	// 定义const对象aa2 传参过去相当于const A* const this
	const A aa2(2);
	aa2.Print();

	return 0;
}

取地址运算符重载 

由以下代码我们可以看见&运算符是获取对象的存放地址,
使用编译器默认生成的即可,如果自己去实现更改可能会造成地址不符合的情况
 

class Data
{
public:
	// 对于普通对象
	Data* operator&()
	{
		return this;
	}

	// 对于const修饰的对象
	const Data* operator&()const
	{
		return this;
	}

private:
	int _Date;
};

再探构造函数-初始化列表 

 1.一开始我们对于成员变量初始化是基于赋值下,而初始化列表是对成员变量进行直接的初始化
 我们知道初始化一个变量是可以不给值的 

class Data
{
public:
	/*Data(int data = 0)
	{
		_Date = data;
	}*/
	// 以上的构造函数相当于
	/*int _Date;
	_Date = data;*/

	// 使用初始化列表
	Data(int data = 0)
		:_Date(data)
	{}
	// 以上的构造函数相当于
	// int _Date = data;

	// 打印函数
	void Print()const
	{
		cout << _Date << endl;
	}

private:
	// 变量声明:
	int _Date;
};

int main()
{
	Data d(1);
	d.Print();
	return 0;
}

 2.每个成员变量在初始化列表只能出现一次
 如果出现多次就相当于我们
         int a = 10;
 再次  int a = 10; 

class Data
{
public:
	// 使用初始化列表
	Data(int data = 0)
		:_Date(data)
		//,_Date(data) err
	{}

private:
	// 变量声明:
	int _Date;
};

3.对于const修饰的成员变量我们只有初始化这一次机会修改他的值
  对于引用成员变量我们必须初始化
  对于没有默认构造的自定义类我们也需要初始化 

class A
{
public:
	// 没有默认构造函数
	// 以下是一个正常的构造函数,需要传参
	A(int a)
		:_a(a)
	{}
private:
	int _a;
};

class Data
{
public:
	// 使用初始化列表
	Data(int& a, int data = 0)
		:_Date(data)   // 初始化正常变量
		, _D(1)		   // 初始化const修饰变量
		, _da(a)	   // 初始化引用成员变量
		, _aa(1)	   // 初始化自定义类型(没有默认构造函数)
	{}

	void Print()
	{
		cout << _Date << endl;
	}
private:
	// 变量声明:
	// 正常变量
	int _Date;

	// const修饰
	const int _D;
	// 引用成员变量
	int& _da;
	// 自定义类型(没有默认构造函数)
	A _aa;
};

int main()
{
	int i = 0;
	Data dt(i);
	dt.Print();

	return 0;
}

4. 成员变量声明处的缺省值用于的是初始化列表 

class Date
{
public:

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

	void Print() const
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	// 注意这⾥不是初始化,这⾥给的是缺省值,这个缺省值是给初始化列表的
	// 如果初始化列表没有显⽰初始化,默认就会⽤这个缺省值初始化
	int _year = 1949;
	int _month = 10;
	int _day = 1;
};

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

5.尽量使⽤初始化列表初始化,因为那些你不在初始化列表初始化的成员也会⾛初始化列表,
  如果这个成员在声明位置给了缺省值,初始化列表会⽤这个缺省值初始化。
  如果你没有给缺省值,对于没有显⽰在初始化列表初始化的内置类型成员是否初始化取决于编译器,C++并没有规定。对于没有显⽰在初始化列表初始化的⾃定义类型成员会调⽤这个成员类型的默认构造函数,如果没有默认构造会编译错误。

 6.初始化列表中按照成员变量在类中声明顺序进⾏初始化,
   跟成员在初始化列表出现的的先后顺序⽆关。建议声明顺序和初始化列表顺序保持⼀致。

class A
{
public:
	A(int a)
		:_a(a)
		,_b(_a)
	{}

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

private:
	int _b = 10;
	int _a = 10;
};

int main()
{
	A aa(1);
	aa.Print();
	return 0;
}
// 以上程序会输出什么? 答案: 1  随机值

隐式类型转换 

c语言中我们了解: 

int main()
{
	// 比如我们定义一个int类型,使用double类型接收,
	// 会产生类型转换,生成一个临时变量,临时变量具有常性
	int i = 0;
	double j = i;

	int m = 0;
	const double& n = m; // 对于常量的引用需要加上const

	return 0;
}

c++中: 

单参数 

// 类型转化在c++中的应用(单参数) c++98支持
class A
{
public:
	A(int a)
		:_a(a)
	{}

	A(const A& a)
	{
		cout << "拷贝构造" << endl;
		_a = a._a;
	}

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

private:
	int _a;
};

int main()
{
	// 平常构造
	A aa1(1);
	aa1.Print();
	// 转换构造
	// 2构造⼀个A的临时对象,再⽤这个临时对象拷贝构造aa2
	// 编译器遇到连续构造+拷贝构造->优化为直接构造
	A aa2 = 2;
	aa2.Print();
	// 对于引用也是如下:
	A& ra1 = aa2;
	const A& ra2 = 2;

	return 0;
}

多参数 

// 对于多参数(c++11支持)
class A
{
public:
	A(int a, int b)
		:_a(a)
		,_b(b)
	{}

	void Print()
	{
		cout << _a << " " << _b << endl;
	}
private:
	int _a;
	int _b;
};

int main()
{
	A aa1(1, 1);
	aa1.Print();

	A aa2 = { 2,2 };
	aa2.Print();
	const A& ra1 = { 2,2 };

	return 0;
}

防止类型转换 

// 如果不想让类型转换发生,在构造函数加一个关键字explicit
class A
{
public:
	explicit A(int a)
		:_a(a)
	{}

	void Print()
	{
		cout << _a << endl;
	}
private:
	int _a;
};

int main()
{
	A aa1(1);
	aa1.Print();

	// A aa2 = 2; // err
	// aa2.Print();
	
	return 0;
}
// 有了类型转换我们使用起来就更加方便,来看以下代码
class A
{
public:
	A(int a = 0)
		:_a(a)
	{}

private:
	int _a;
};

class Stack
{
public:
	void Push(const A& aa)
	{
		//....
	}
private:
	A _arr[10];
	int _top;
};

int main()
{
	Stack st;

	// 以下代码是等价的
	A aa1(1);
	st.Push(aa1);

	st.Push(1);
	return 0;
}

 static成员

• ⽤static修饰的成员变量,称之为静态成员变量,静态成员变量⼀定要在类外进⾏初始化。

• 静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区。

• ⽤static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针。

• 静态成员函数中可以访问其他的静态成员,但是不能访问⾮静态的,因为没有this指针。

• ⾮静态的成员函数,可以访问任意的静态成员变量和静态成员函数。

• 突破类域就可以访问静态成员,可以通过(类名::静态成员)或者(对象.静态成员)来访问静态成员变量和静态成员函数。

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

• 静态成员变量不能在声明位置给缺省值初始化,因为缺省值是个构造函数初始化列表的,

静态成员变量不属于某个对象,不⾛构造函数初始化列表。 

class A
{
public:
	A(int i = 0)
	{
		++_i;
	}
	~A()
	{
		++_i;
	}
	A(const A& aa)
	{
		++_i;
	}

	static int GetACount()
	{
		return _i;
	}

private:
	static int _i;
};

// 静态成员函数需要在类外面进行初始化
int A::_i = 0;

int main()
{
	cout << A::GetACount() << endl;
	A a1, a2;
	A a3(a1);
	cout << A::GetACount() << endl;
	cout << a1.GetACount() << endl;
	return 0;
}

 OJ练习:
 求1 + 2 + 3 + ... + n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A ? B : C)。 

class Sum
{
public:
	Sum()
	{
		_sum += _i;
		++_i;
	}

	// 静态成员函数,没有this指针
	static int GetSum()
	{
		return _sum;
	}
private:
	static int _sum;
	static int _i;
};
int Sum::_sum = 0;
int Sum::_i = 1;

class Solution {
public:
	int Sum_Solution(int n) {
		Sum* p = new Sum[n];
		delete[] p;
		return Sum::GetSum();
	}
};

友元 

友元分为:友元函数和友元类
在函数声明或者类声明的前⾯加friend关键字,并且把友元声明放到⼀个类的里面
友元函数可以访问类的私有成员变量,但是友元函数不是成员函数,只是一种声明
一个函数可以是多个类的友元函数
友元类中的成员函数都可以是另⼀个类的友元函数,都可以访问另⼀个类中的私有和保护成员。
友元类的关系是单向的,不具有交换性,⽐如A类是B类的友元,但是B类不是A类的友元。
友元类关系不能传递,如果A是B的友元,B是C的友元,但是A不是c的友元。
有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多⽤。
 

class A
{
	// 友元声明
	friend void Print(const A& aa);
public:
	A(int a = 1, int b = 1)
		:_a(a)
		, _b(b)
	{}
private:
	int _a;
	int _b;
};

void Print(const A& aa)
{
	cout << aa._a << " " << aa._b << endl;
}

int main()
{
	A aa(1, 2);
	Print(aa);
	return 0;
}
class A
{
	// 友元声明
	friend class B;
public:
	A(int a = 1)
		:_a(a)
	{}
private:
	int _a = 1;
};

class B
{
	// 友元声明
	friend class A;
public:
	B(int b = 2)
		:_b(b)
	{}

	void Print(const A& aa)
	{
		cout << aa._a << " " << _b << endl;
	}
private:
	int _b = 2;
};

int main()
{
	A aa;
	B bb;
	bb.Print(aa);
	return 0;
}

内部类 

• 如果⼀个类定义在另⼀个类的内部,这个内部类就叫做内部类。
内部类是⼀个独⽴的类,跟定义在全局相⽐,他只是受外部类类域限制和访问限定符限制,
所以外部类定义的对象中不包含内部类。即类的大小不受内部类影响。
• 内部类默认是外部类的友元类。
• 内部类本质也是⼀种封装,当A类跟B类紧密关联,A类实现出来主要就是给B类使⽤,
那么可以考虑把A类设计为B的内部类,如果放到private / protected位置,
那么A类就是B类的专属内部类,其他地⽅都⽤不了。 

class Solution {
public:
	int Sum_Solution(int n) {

		sum* p = new sum[n];
		 delete[] p;
		
		// 变长数组
		// sum arr[n];
		return _sum;
	}
private:
	static int _sum;
	static int _i;

	// 内部类
	class sum
	{
	public:
		sum()
		{
			_sum += _i;
			++_i;
		}
	private:
	};
};

int Solution::_sum = 0;
int Solution::_i = 1;

匿名对象 

class Solution
{
public:
	Solution(int ret = 10)
		:_ret(ret)
	{}

	void print()
	{
		cout << _ret << endl;
	}
private:
	int _ret = 10;
};

int main()
{
	// 平时调用
	Solution s1(1);
	s1.print();

	// 匿名对象
	Solution().print(); // 生命周期只在这一行,用完就销毁

	return 0;
}
// 以下是匿名对象的应用
#include <algorithm>
int main()
{
	int arr[] = { 5,9,4,86,21,35,48,62 };
	// < 升序#include <algorithm>
int main()
{
	int arr[] = { 5,9,4,86,21,35,48,62 };
	// < 升序
	sort(arr, arr + 8);
	// 循环for
	for (int& ret : arr)
	{
		cout << ret << " ";
	}
	cout << endl;
	// > 降序
	sort(arr, arr + 8, greater<int>()); // 匿名对象
	// 循环for
	for (int& ret : arr)
	{
		cout << ret << " ";
	}
	return 0;
}

 对象拷贝时的编译器优化

• 现代编译器会为了尽可能提⾼程序的效率,在不影响正确性的情况下会尽可能减少⼀些传参和传参过程中可以省略的拷贝  

class Date
{
public:

	// 默认构造函数
	Date(int year = 1949, int month = 1, int day = 1)
		:_year(year)
		,_month(month)
		,_day(day)
	{
		cout << "默认构造函数" << endl;
	}

	// 析构函数
	~Date()
	{
		//...
		cout << "析构函数" << endl;
	}

	// 拷贝构造
	Date(const Date& d)
	{
		cout << "拷贝构造" << endl;
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	// 赋值运算符重载
	Date& operator=(const Date& d)
	{
		cout << "赋值运算符重载" << endl;
		_year = d._year;
		_month = d._month;
		_day = d._day;

		return *this;
	}

	void Print() const  // const Date* const this
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

private:
	int _year = 1949;
	int _month = 1;
	int _day = 1;
};

void fun1(Date d)
{}

Date fun2()
{
	Date d;
	return d;
}

int main()
{
	// 传值传参
	// 由于fun1的参数不是Date&,会形成一次拷贝构造
	// 这边打印的应该是(d1的构造函数)+(d1传参的拷贝构造)
	// 然后是析构函数*2
	Date d1;
	fun1(d1);

	// 如果是隐式类型转换,连续的构造+拷贝构造->编译器优化成直接构造
	// 然后是析构函数
	fun1({ 2024, 1, 1 });

	// ⼀个表达式中,连续的构造+拷贝构造->优化为⼀个构造
	fun1(Date(2024, 1, 1));

	// 传值返回
	// 函数表达式中先构造d,然后在构造一个临时对象拷贝构造d进行返回
	// 但是在一些编译器上会直接优化一个拷贝构造,减少一个拷贝
	// ⼀些编译器会优化得更厉害,进⾏跨⾏合并优化,直接变为构造。
	fun2();

	// 如果我们又去接受返回值
	// 直接构造d,构造临时对象进行拷贝构造d,
	// 返回接收的dd又进行一次直接构造+拷贝构造。
	//返回时⼀个表达式中,连续直接构造+拷贝构造->优化⼀个拷⻉构造
	//⼀些编译器会优化得更厉害,进⾏跨⾏合并优化,直接变为构造。
	Date dd = fun2();

	// ⼀个表达式中,连续拷贝构造+赋值重载->⽆法优化
	Date dd;
	dd = fun2();

	return 0;
}

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

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

相关文章

第11讲:变量的基本

变量的数据类型分为基本数据类型、行生数据类型还有POU实例名三种。 此处主要讨论一下基本数据类型。 基本数据类型的种类 基本数据类型的表示方法 1、布尔型、位串型、常数 2、整数型 (1)有符号整数型 SINT型,INT型,DINT型及LINT型,为有符号整数型,二进制表示的最高…

Aigtek:电压放大器的选型方法有哪些

电压放大器是电子电路中常见的元件&#xff0c;用于将输入电压信号放大到所需的水平。在选择适合特定应用的电压放大器时&#xff0c;需要考虑多个因素&#xff0c;包括性能要求、电源电压、带宽、噪声等。下面安泰电子将详细介绍电压放大器的选型方法&#xff0c;以帮助工程师…

【深入理解SpringCloud微服务】深入理解Eureka核心原理

深入理解Eureka核心原理 Eureka整体设计Eureka服务端启动Eureka三级缓存Eureka客户端启动 Eureka整体设计 Eureka是一个经典的注册中心&#xff0c;通过http接收客户端的服务发现和服务注册请求&#xff0c;使用内存注册表保存客户端注册上来的实例信息。 Eureka服务端接收的…

SQLite读取分析指南:新手也能轻松上手的实用教程

SQLite是一个轻量级的关系型数据库&#xff0c;目前已经更新到SQLite3版本。它不仅具有跨平台的特性而且占用的资源非常低&#xff0c;目标是设计来做嵌入式的。本教程将深入浅出地讲解图形化界面和python脚本来读取sqlite数据库这两种方法,从基础概念到实际应用,step by step地…

Windows定时任务实现关闭和开启声音

目录 1. 下载并放置 nircmd.exe1.1 下载 NirCmd&#xff1a;1.2 放置 nircmd.exe&#xff1a; 2. 定时关闭声音2.1 打开任务计划程序&#xff1a;2.2 创建基本任务&#xff1a;2.3 设置任务名称和描述&#xff1a;2.4 触发器&#xff1a;2.5 操作&#xff1a;2.6 设置程序或脚本…

新手小白的pytorch学习第十弹----多类别分类问题模型以及九、十弹的练习

目录 1 多类别分类模型1.1 创建数据1.2 创建模型1.3 模型传出的数据1.4 损失函数和优化器1.5 训练和测试1.6 衡量模型性能的指标 2 练习Exercise 之前我们已经学习了 二分类问题&#xff0c;二分类就像抛硬币正面和反面&#xff0c;只有两种情况。 这里我们要探讨一个 多类别…

专业护眼灯品牌有哪些?五款爆款护眼灯品牌推荐

在当今时代&#xff0c;电子设备在我们的日常生活中扮演着越来越重要的角色。然而&#xff0c;长时间使用这些设备可能会增加眼睛疲劳和近视的风险。为了解决这一问题&#xff0c;护眼台灯应运而生&#xff0c;并逐渐成为许多家庭和办公室的必需品。面对市场上琳琅满目的护眼台…

<数据集>苹果腐烂识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;978张 标注数量(xml文件个数)&#xff1a;978 标注数量(txt文件个数)&#xff1a;978 标注类别数&#xff1a;2 标注类别名称&#xff1a;[fresh_apple, rotten_apple] 序号类别名称图片数框数1fresh_apple520922…

排序算法与复杂度介绍

1. 排序算法 1.1 排序算法介绍 排序也成排序算法&#xff08;Sort Algorithm&#xff09;&#xff0c;排序是将一组数据&#xff0c;依照指定的顺序进行排序的过程 1.2 排序的分类 1、内部排序&#xff1a; 指将需要处理的所有数据都加载到**内部存储器&#xff08;内存&am…

【NLP自然语言处理】基于BERT实现文本情感分类

Bert概述 BERT&#xff08;Bidirectional Encoder Representations from Transformers&#xff09;是一种深度学习模型&#xff0c;用于自然语言处理&#xff08;NLP&#xff09;任务。BERT的核心是由一种强大的神经网络架构——Transformer驱动的。这种架构包含了一种称为自注…

MySQL_JDBC

目录 一、JDBC常用的接口和类 1.1 数据库连接 Connection 1.2 Statement 对象 二、JDBC的使用 总结 【Java 的数据库编程】 JDBC 即 Java Database Connectivity (Java数据库连接)&#xff0c;是一种用于执行 SQL 语句的 Java API。这个 API 由 java.sql.*,javax.sql.* …

零基础学SpringBoot(一)--初识SpringBoot

1. SpringBoot简介 SpringBoot 是Spring家族中的一个全新的框架&#xff0c;它用来简化Spring应用程序的创建和开发过程&#xff0c;也可以说Spring Boot能简化我们之前采用SSM(SpringMVC Spring MyBatis)框架进行开发的过程。 以前我们采用SSM框架进行开发的时候&#xff0c…

算法017:二分查找

二分查找. - 备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/binary-search/ 二分查找&#xff0c;其实是双指针的一种特殊情况&#xff0c;但是时间复杂度极低&#…

python的csv库常用操作

csv 模块是 Python 标准库中的一个模块&#xff0c;用于处理 CSV&#xff08;逗号分隔值&#xff09;文件。它提供了简单易用的功能来读取和写入 CSV 文件。以下是一些常用的操作&#xff1a; 一、读取 CSV 文件 新建文件data.csv&#xff0c;内容如下&#xff1a; 使用 csv.…

使用JAVA代码实现生成二维码

系列文章目录 1.SpringBoot整合RabbitMQ并实现消息发送与接收 2. 解析JSON格式参数 & 修改对象的key 3. VUE整合Echarts实现简单的数据可视化 4. List&#xff1c;HashMap&#xff1c;String,String&#xff1e;&#xff1e;实现自定义字符串排序&#xff08;key排序、Val…

录屏神器!这四款软件让你轻松记录屏幕

随着现在在线教育的兴起&#xff0c;用录屏软件将课程录制下来能够帮助我助我们通过视频分享知识&#xff0c;展示成果。电脑怎么录屏的重要性愈发凸显了&#xff0c;下面我为你介绍几款热门的 录屏软件吧。 1.福晰REC大师 这个软件是我用过觉得最顺手的一款了。它录制的视频…

c++ 高精度加法(只支持正整数)

再给大家带来一篇高精度&#xff0c;不过这次是高精度加法&#xff01;话不多说&#xff0c;开整&#xff01; 声明 与之前那篇文章一样&#xff0c;如果看起来费劲可以结合总代码来看 定义 由于加法进位最多进1位&#xff0c;所以我们的结果ans[]的长度定义为两个加数中最…

马斯克:xAI启动“最强大AI训练集群” 年底推出全球最强AI

埃隆马斯克最近在社交平台上表示&#xff0c;xAI已经启动了“世界上最强大的 AI 集群”&#xff0c;以在今年12 月之前创建“世界上最强大的AI”。马斯克自豪地透露&#xff0c;得益于xAI团队、X团队、Nvidia及众多合作伙伴的紧密协作&#xff0c;位于孟菲斯的超级计算工厂“Su…

【BUG】已解决:libpng warning: iccp: known incorrect sRGB profile

已解决&#xff1a;libpng warning: iccp: known incorrect sRGB profile 目录 已解决&#xff1a;libpng warning: iccp: known incorrect sRGB profile 【常见模块错误】 错误原因&#xff1a; 原因分析 解决方案 具体步骤 欢迎来到英杰社区https://bbs.csdn.net/topics…

请你谈谈:spring bean的生命周期 - 阶段4:检查Aware相关接口

在Spring框架中&#xff0c;Aware 接口系列提供了一种机制&#xff0c;允许bean在初始化过程中感知到容器中的特定对象&#xff0c;如应用上下文&#xff08;ApplicationContext&#xff09;、Bean工厂&#xff08;BeanFactory&#xff09;等。如果你有一个用户自定义的对象&am…