【C++初阶】--类和对象(下)

目录

一.const成员

 1.权限放大问题

2.权限的缩小 

二.再谈构造函数 

1.构造函数体赋值 

2.初始化列表 

(1)概念 

(2)使用 

①在对象实例化过程中,成员变量先依次进行初始化

②再进行函数体内二次赋值 

3.explicit关键字 

(1)C++为什么要存在自动隐式类型转换这样的东西呢?

 (2)explicit关键字

​编辑

(3)知识补充:多参数的函数进行隐式类型转换

(4)总结:缺省值的各种写法

三.static成员 

1.概念 

2.使用 

①面试题:A类型创建了多少个对象? (统计构造了多少次) 

四.友元 

1.友元函数 

2.友元类 

五.内部类 

1.概念 

2.特性 

六.匿名对象 


一.const成员

将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改

 1.权限放大问题

class Date
{
public:
	Date()//构造函数
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}
	Date(int year=1,int month=1,int day=1)//带参构造函数(函数重载)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	const Date d1(2024, 1, 31);
	d1.Print();

	return 0;
}

这里存在一个权限被放大的问题: 

  • 优化 
//权限的平移
void Print() const
{
	cout << _year << "-" << _month << "-" << _day << endl;
}

2.权限的缩小 

class Date
{
public:
	Date()//构造函数
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}
	Date(int year=1,int month=1,int day=1)//带参构造函数(函数重载)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
    Date d2(2024, 3, 31);
	d2.Print();

	return 0;
}

  • 注意 

权限的放大只有指针和引用才存在,而拷贝是不影响的。

	//可以运行
    const int i = 0;
	int j = i;
    
    //报错
	const int* p1 = &i;
	int* p2 = p1;

二.再谈构造函数 

1.构造函数体赋值 

在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。  

class Date
{
public:
 Date(int year, int month, int day)
 {
     _year = year;
     _month = month;
     _day = day;
 }
private:
 int _year;
 int _month;
 int _day;
};

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

2.初始化列表 

(1)概念 

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。  

class Date
{
public:
    //初始化列表
	Date(int year, int month, int day)
		: _year(year)
		, _month(month)
		, _day(day)
	{}

private:
	int _year;
	int _month;
	int _day;
};

(2)使用 

  • 注意事项

类中包含以下成员,必须放在初始化列表位置进行初始化:

  1. 引用成员变量
  2. const成员变量
  3. 自定义类型成员(且该类没有默认构造函数时)  
 class A
{
public:
	//A(int a = 0, int b = 1)
	A(int a, int b)
		:_a(a)
	{
		cout << "A(int a = 0)" << endl;
	}

private:
	int _a;
};
class Date
{
public:
	// 初始化列表是每个成员变量定义初始化的位置
	// 能用初始化列表就建议用初始化列表
	Date(int year, int month, int day, int& x)
		:_year(year)
		,_month(month)
		,_day(day)
		,_n(1)
		,_ref(x)
		,_aa(1, 2)
		,_p((int*)malloc(sizeof(4) * 10))
	{
		if (_p == nullptr)
		{
			perror("malloc fail");
		}

		for (size_t i = 0; i < 10; i++)
		{
			_p[i] = 0;
		}
	}

private:
	// 声明
	int _year;
	int _month;
	int _day;

	// 必须走初始化
	const int _n;
	int& _ref;
	A _aa;

	int* _p;
};

①在对象实例化过程中,成员变量先依次进行初始化
  • 图示解析 

②再进行函数体内二次赋值 

总结:尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量, 一定会先使用初始化列表初始化。  

3.explicit关键字 

  • 知识引入 

构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用。 

class C
{
public:
	C(int x = 0)
		:_x(x)
	{}

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

private:
	int _x;
};

int main()
{
	C cc1(1);
	// 单参数构造函数支持隐式类型的转换
	C cc2 = 2;//2 用来构造一个临时对象,再进行拷贝构造-> 编译器优化了,同一个表达式连续步骤的构造,一般会被合二为一

	return 0;
}

  • 回顾:一个临时变量不能引用 

(1)C++为什么要存在自动隐式类型转换这样的东西呢?

  • 传统进行栈的插入 
class Stack
{
public:
	void Push(const C& c)
	{
		//
	}
};
int main()
{
	Stack st;
	C cc4(3);
	st.Push(cc4);

	return 0;
}

在插入该类时,我们还需要在创建一个该类型的对象,非常麻烦。 

  • 有了自动隐式类型转换后的栈插入
class Stack
{
public:
	void Push(const C& c)
	{
		//
	}
};
int main()
{


	Stack st;
	C cc4(3);

	st.Push(4);

	return 0;
}

这里我们将4直接传给了st.Push(),而该成员函数的参数类型是自定义类型,但由于存在隐式类型的自动转换,我们可以直接传过去了!非常爽!

 (2)explicit关键字

有些地方我们并不想发生隐式类型的自动转换,这是添加explicit关键字就可以解决这个问题了。

class C
{
public:
	explicit C(int x = 0)
		:_x(x)
	{}

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

private:
	int _x;
};



int main()
{
	C cc1(1);
	C cc2 = 2;
	return 0;
}

(3)知识补充:多参数的函数进行隐式类型转换

class A
{
public:
	//explicit A(int a1, int a2)
	A(int a1, int a2)
		:_a1(a1)
		,_a2(a2)
	{}

private:
	int _a1;
	int _a2;
};

int main()
{
	
   //隐式类型转换
	A aa1 = { 1, 2 };
	const A& aa2 = { 1, 2 };
   //构造的话就正常构造
    A aa2(2,3)
	return 0;
}

(4)总结:缺省值的各种写法

class B
{
private:
	// 缺省值
	int _a = 1;
	int* _p = (int*)malloc(4);
	A aa1 = { 1,2 };
};
  • 小题试炼 

以下程序的最终结果是什么? 

A.输出1 1
B.程序崩溃 

C.编译不通过
D.输出1 随机值

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

	void Print() {
		cout << _a1 << " " << _a2 << endl;
	}
private:
	// 声明顺序
	int _a2;
	int _a1;
};


int main() {
	A aa(1);
	aa.Print();
}

答案:D 

  • 解析

初始列表初始的顺序不是该列表出现的顺序(也就是先_a1再_a2),而是声明的顺序(先_a2再_a1)。

三.static成员 

1.概念 

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

2.使用 

①面试题:A类型创建了多少个对象? (统计构造了多少次) 

  • 写法一:直接统计出构造和拷贝构造的总次数
int n = 0;
class A
{
public:
	A()
	{
		++n;
	}

	A(const A& aa)
	{
		++n;
	}
};
A Func()
{
	A aa;

	return aa;
}

int main()
{
	A aa1;
	A aa2;

	Func();
	cout << n << endl;
	return 0;
}

由于各个编译器的优化程度不一样,所以导致最后调用的次数可能会不同,因此这种写法具有一定的风险性。 

  • 写法2:设置成静态成员,并突破类域和访问限定符 
class A
{
public:
	A()
	{
		++n;
	}

	A(const A& aa)
	{
		++n;
	}

	//不是属于某一个对象,属于所有对象,属于整个类
	static int n ;//在类内声明
};
int A::n = 0;//在类外定义
A Func()
{
	A aa;

	return aa;
}

int main()
{
	A aa1;
	A aa2;
	Func();

	//三种访问形式
	cout << aa1.n << endl;
	cout << aa2.n << endl;
	cout << A::n << endl;
	return 0;
}
  • 写法3:存在访问限定符 
class A
{
public:
	A()
	{
		++n;
	}

	A(const A& aa)
	{
		++n;
	}
	int GetN()
	{
		return n;
	}
private:
	//不是属于某一个对象,属于所有对象,属于整个类
	static int n ;//在类内声明
};
int A::n = 0;//在类外定义
A Func()
{
	A aa;

	return aa;
}

int main()
{
	A aa1;
	A aa2;
	Func();

	cout << aa1.GetN() << endl;
	return 0;
}
  • 写法4:静态成员函数写法 
class A
{
public:
	A()
	{
		++n;
	}

	A(const A& aa)
	{
		++n;
	}
	//静态成员函数没有this指针
	static int GetN()
	{
		return n;
	}
private:
	//不是属于某一个对象,属于所有对象,属于整个类
	static int n ;//在类内声明
};

int A::n = 0;//在类外定义
A Func()
{
	A aa;

	return aa;
}

int main()
{
	A aa1;
	A aa2;
	Func();

	cout << A::GetN() << endl;
	return 0;
}

四.友元 

友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以 友元不宜多用。

友元分为:友元函数和友元类。

1.友元函数 

问题:现在尝试去重载operator,然后发现没办法将operator重载成成员函数。因为cout的 输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以要将operator重载成全局函数。但又会导致类外没办法访问成员,此时就需要友元来解决。operator>>同理。 

class Date
{
public:
	Date(int year, int month, int day)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
	// d1 << cout  ---->  d1.operator<<(&d1, cout); 不符合常规调用
 // 因为成员函数第一个参数一定是隐藏的this,所以d1必须放在<<的左侧
	ostream& operator<<(ostream& _cout)
	{
		_cout << _year << "-" << _month << "-" << _day << endl;
		return _cout;
	}
private:
	int _year;
	int _month;
	int _day;
};

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

class Date
{
	friend ostream& operator<<(ostream& _cout, const Date& d);
	friend istream& operator>>(istream& _cin, 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& _cout, const Date& d)
{
	_cout << d._year << "-" << d._month << "-" << d._day;
	return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{
	_cin >> d._year;
	_cin >> d._month;
	_cin >> d._day;
	return _cin;
}
int main()
{
	Date d;
	cin >> d;
	cout << d << endl;
	return 0;
}
  • 注意事项 
  1. 友元函数可访问类的私有和保护成员,但不是类的成员函数
  2. 友元函数不能用const修饰
  3. 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  4. 一个函数可以是多个类的友元函数
  5. 友元函数的调用与普通函数的调用原理相同  

2.友元类 

  1. 友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
  2. 友元关系是单向的,不具有交换性。 (比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接 访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。)
  3. 友元关系不能传递 如果C是B的友元, B是A的友元,则不能说明C时A的友元。
  4. 友元关系不能继承。  
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;
};

五.内部类 

1.概念 

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

  • 注意

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

class A
{
public:
	//内部类天生就是外部类的友元类
	class B
	{
	public:
		void func(A* p)
		{
			p->_a1++;
		}
	};
private:
	int _a1;
	int _a2;
};

int main()
{
	cout << sizeof(A) << endl;
	A::B bb;
}

2.特性 

  1. 内部类可以定义在外部类的public、protected、private都是可以的。
  2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
  3.  sizeof(外部类)=外部类,和内部类没有任何关系。  
  • 内部类的大小 
class A
{
public:
	class B
	{
	private:
		int _b1;
	};
private:
	int _a1;
	int _a2;
};

int main()
{
	cout << sizeof(A) << endl;//8
}
  1. 类B受类A的类域限制,所以类A里其实是没有类B的,类B可以理解为和全局变量没有区别。
  2. 类B在创建对象时受到类A封装的限制。
int main()
{
	A::B bb;//这样创建对象
}
如果将类B设置成private,则无法访问

六.匿名对象 

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();
	A(10);

	return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

泰山众筹:掀起一场全民参与的购物狂潮!

随着互联网的快速发展&#xff0c;传统的商业模式已经无法满足消费者的多元化需求。在这个数字化时代&#xff0c;泰山众筹模式以其独特的魅力&#xff0c;正迅速成为新零售市场的热门话题。它不仅为消费者带来了前所未有的购物体验&#xff0c;还为企业的发展注入了新的活力。…

Visual Sudio 2022 引入第三方库(MySQL.H)

参考博客 Visual Studio 2022 C配置第三方库(libsndfile)、 fatal error LNK1107: 文件无效或损坏: 无法在 0x2C8 处读取 &#x1f33b;&#x1f33b;&#x1f33b;感谢两位博主在配置第三方库时给我提供的帮助&#x1f33b;&#x1f33b;&#x1f33b; 目录 一、准备好第三方库…

Win11网络连接选项和蓝牙选项突然消失的解决办法

在设置或者开始栏里搜索“网络重置” 打开网络重置&#xff1a; 然后点击立即重置&#xff0c;之后按照系统提示操作即可

vue教程

v 创建一个vue实例插值表达式{{}}vue响应式特性vue指令v-if vs. v-show 指令v-else-if 指令v-on指令 注册监听内联语句methods中的函数名![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/8b9d81539ba74e6691b27694813e0f65.png)v-on 调用传参 v-bind 动态的设置html…

(十一)【Jmeter】线程(Threads(Users))之jp@gc-Ultimate Thread Group

简述 操作路径如下: 作用:提供了高级的线程组控制选项,支持更复杂的场景模拟。配置:设置多种线程控制参数,如启动延迟、启动线程数、并发压测持续时间、关闭线程时间等。使用场景:针对特定需求进行高级的并发访问模拟,如流量控制、延迟启动等。优点:提供了丰富的控制…

HarmonyOS Stage模型 应用配置文件讲解

好&#xff0c;上文 HarmonyOS Stage模型基本概念讲解 中&#xff0c;我们简单讲解了HarmonyOS 中 Stage模型的基本概念 那么 我们继续学习Stage模型的相关知识 上文之后 我们肯定对它的概念和基本结构 有了一个了解 那么 我们就来看一下 基于Stage模型 它里面一些基本的配置文…

算法沉淀——动态规划之斐波那契数列模型(leetcode真题剖析)

算法沉淀——动态规划之斐波那契数列模型 01.第 N 个泰波那契数02.三步问题03.使用最小花费爬楼梯04.解码方法 动态规划&#xff08;Dynamic Programming&#xff0c;简称DP&#xff09;是一种通过将原问题分解为相互重叠的子问题并仅仅解决每个子问题一次&#xff0c;将其解存…

CSRF攻击和防御

CSRF:Cross Site Request Forgery 跨站请求伪造 攻击&#xff1a; 攻击者盗用你的身份&#xff0c;以你的名义发送恶意请求&#xff08;邮件&#xff0c;消息&#xff0c;盗取账号&#xff0c;购买物品&#xff09; GET请求的伪造方式 POST请求的伪造方式 防御&#xff1a…

[c++] 工厂模式 + cyberrt 组件加载器分析

使用对象的时候&#xff0c;可以直接 new 一个&#xff0c;为什么还需要工厂模式 &#xff1f; 工厂模式属于创建型设计模式&#xff0c;将对象的创建和使用进行解耦&#xff0c;对用户隐藏了创建逻辑。 个人感觉上边的表述并没有说清楚为什么需要使用工厂模式。因为使用 new 创…

[面试] 如何保证Redis和MySQL数据一致性?

为什么要在Redis存数据 Redis 用来实现应用和数据库之间读操作的缓存层&#xff0c;主要目的是减少数据 库 IO&#xff0c;还可以提升数据的 IO 性能。 因为Redis基于内存, 查询效率比MySQL快很多, 所以有限查询Redis中的数据,如果Redis没有就查询数据库然后同步到Redis 出…

Vue+SpringBoot打造快递管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容2.1 数据中心模块2.2 快递类型模块2.3 快递区域模块2.4 快递货架模块2.5 快递档案模块 三、界面展示3.1 登录注册3.2 快递类型3.3 快递区域3.4 快递货架3.5 快递档案3.6 系统基础模块 四、免责说明 一、摘要 1.1 项目介绍 …

Flink ML 的新特性解析与应用

摘要&#xff1a;本文整理自阿里巴巴算法专家赵伟波&#xff0c;在 Flink Forward Asia 2023 AI特征工程专场的分享。本篇内容主要分为以下四部分&#xff1a; Flink ML 概况在线学习的设计与应用在线推理的设计与应用特征工程算法与应用 一、Flink ML 概况 Flink ML 是 Apache…

MySQL运维实战(7.2) MySQL复制server_id相关问题

作者&#xff1a;俊达 主库server_id没有设置 主库没有设置server_id Got fatal error 1236 from master when reading data from binary log: Misconfigured master - server_id was not set主库查看server_id mysql> show variables like server_id; ----------------…

Spring Boot 笔记 029 用户模块

1.1 用户信息需要在多个链接使用&#xff0c;所以需要用pinia持久化 1.1.1 定义store import {defineStore} from pinia import {ref} from vue const useUserInfoStore defineStore(userInfo,()>{//定义状态相关的内容const info ref({})const setInfo (newInfo)>{i…

基于ElementUI封装省市区四级联动下拉选择

基于ElementUI封装的省市区下拉级联选择 效果 数据 最新省市区JSON数据获取&#xff1a;https://xiangyuecn.github.io/AreaCity-JsSpider-StatsGov/ 参数说明 参数说明inputNumShow下拉框的数量&#xff0c;最多4个defaultAddress默认显示省市区 例&#xff1a;[‘安徽’, …

C语言:指针的进阶讲解

目录 1. 二级指针 1.1 二级指针是什么&#xff1f; 1.2 二级指针的作用 2. 一维数组和二维数组的本质 3. 指针数组 4. 数组指针 5. 函数指针 6. typedef的使用 7. 函数指针数组 7.1 转移表 1. 二级指针 如果了解了一级指针&#xff0c;那二级指针也是可以很好的理解…

Redis(十六)缓存预热+缓存雪崩+缓存击穿+缓存穿透

文章目录 面试题缓存预热缓存雪崩解决方案 缓存穿透解决方案 缓存击穿解决方案案例&#xff1a;高并发聚划算业务 总结表格 面试题 缓存预热、雪崩、穿透、击穿分别是什么?你遇到过那几个情况?缓存预热你是怎么做的?如何避免或者减少缓存雪崩?穿透和击穿有什么区别?他两是…

JDK下载安装

资源展示 安装说明 傻瓜式安装&#xff0c;下一步即可。建议&#xff1a;安装路径不要有中文或者空格等特殊符号。本套课程会同时安装JDK8 和 JDK17&#xff0c;并以JDK17为默认版本进行讲解。 安装步骤 &#xff08;1&#xff09;双击jdk-17_windows-x64_bin.exe文件&#…

免费多域名证书,最多支持保护250个域名

随着企业规模扩大和多元化发展&#xff0c;拥有多个域名的需求变得普遍&#xff0c;此时&#xff0c;多域名SSL证书应运而生&#xff0c;并且这一类型的证书已经发展到能够安全地支持多达250个不同域名的加密需求。 多域名SSL证书&#xff0c;也称为SAN&#xff08;Subject Alt…

RocketMQ生产环境常见问题分析与总结

RocketMQ生产环境常见问题分析与总结 如何保证消息不丢失 消息丢失场景 对于跨网络的节点可能会丢消息&#xff0c;因为MQ存盘都会先写入OS的PageCache中&#xff0c;然后再让OS进行异步刷盘&#xff0c;如果缓存中的数据未及时写入硬盘就会导致消息丢失 生产端到Broker端Brok…
最新文章