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

news2024/11/20 2:19:12

目录

一.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.coloradmin.cn/o/1469400.html

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

相关文章

Java之线程同步、synchronized用法及原理

线程的同步 场景1&#xff1a;两个线程同时访问一个变量&#xff0c;一个线程自增&#xff0c;一个线程自减 public class thread11 {public static void main(String[] args) throws InterruptedException {Thread thread1 new AddThread();Thread thread2 new DecThread(…

编曲学习:高叠和弦 挂留和弦 和弦实战应用

高叠和弦 挂留和弦 和弦实战应用小鹅通-专注内容付费的技术服务商https://app8epdhy0u9502.pc.xiaoe-tech.com/live_pc/l_65d4826fe4b04c10a1310517?course_id=course_2XLKtQnQx9GrQHac7OPmHD9tqbv 七和弦 以三和弦举例,三和弦上面叠一个三度的音,就变成了七和弦。 从下到…

opencv python投影变换效果

变换原理&#xff1a; https://www.cnblogs.com/txwtech/p/18024547 python示范代码&#xff1a; src2原图&#xff0c;4个坐标点 dst2转换后&#xff0c;4个坐标点 p_touyin cv2.getPerspectiveTransform(src2,dst2) #计算投影变换矩阵 #利用矩阵值进行图像投影变换 r…

全流程点云机器学习(二)使用PaddlePaddle进行PointNet的机器学习训练和评估

前言 这不是高支模项目需要嘛&#xff0c;他们用传统算法切那个横杆竖杆流程复杂耗时很长&#xff0c;所以想能不能用机器学习完成这些工作&#xff0c;所以我就来整这个工作了。 基于上文的数据集切分 &#xff0c;现在来对切分好的数据来进行正式的训练。 本系列文章所用的…

【Pytorch深度学习开发实践学习】B站刘二大人课程笔记整理lecture11 Advanced_CNN 实现GoogleNet和ResNet

【Pytorch深度学习开发实践学习】B站刘二大人课程笔记整理lecture11 Advanced_CNN 代码&#xff1a; Pytorch实现GoogleNet import torch from torchvision import datasets, transforms from torch.utils.data import DataLoader import torch.nn as nn import torch.nn.fun…

内核解读之内存管理(8)什么是page cache

文章目录 0. 文件系统的层次结构1.什么是page cache2.感观认识page cache3. Page Cache的优缺点3.1 Page Cache 的优势3.2 Page Cache 的劣势 0. 文件系统的层次结构 在了解page cache之前&#xff0c;我们先看下文件系统的层次结构。 1 VFS 层 VFS &#xff08; Virtual Fi…

【Ubuntu】解决Ubuntu 22.04开机显示器颜色(高对比度/反色)异常的问题

使用Ubuntu 22.04时强制关机了一下&#xff08;make -j16把电脑搞崩了&#xff09;&#xff0c;开机后系统显示的颜色异常&#xff0c;类似高对比度或反色&#xff0c;如下图。看着很难受&#xff0c;字体也没办法辨认。还好之前遇到过类似的问题&#xff0c;应该是一个配置文件…

装修避坑干货|阳台洗衣柜洗衣机一体柜设计。福州中宅装饰,福州装修

装修的时候常常会在洗衣柜中嵌入洗衣机&#xff0c;其实阳台柜的安装并不像看起来的那么简单&#xff0c;下面给大家说说几个注意事项‼️ 01.水电位置 在安装阳台柜之前&#xff0c;务必确认水电管道的位置。确保阳台柜不会阻碍水电管道的使用&#xff0c;以免造成不必要的麻…

Three.js-02Vue框架入手

1.创建项目 说明&#xff1a;默认有vue基础&#xff0c;node版本18以上。 vue create threejs 2.选择vue3 4.安装 npm i three 5. 修改页面 <template> <div></div> </template><script setup> import * as THREE from three;const width win…

查看仓库版本记录

打开命令行窗口 输入git log即可。 若发现分支不对&#xff0c;方法如下 查看项目目录&#xff0c;命令行输入dir可以查看 多个moudel&#xff0c;进入到需要查版本记录的moudel下 命令行输入cd .\文件名如wowo-win-server\ 切换到wowo-win-server文件夹下后&#xff0c;再输入…

【Unity】提示No valid Unity Editor liscense found.Please active your liscense.

有两个软件&#xff0c;如果只有一个&#xff0c;点黑的不会有效果、、、、&#xff08;楼主是这个原因&#xff0c;可以对号入座一下&#xff09; 简而言之&#xff0c;就是去下载Unity Hub&#xff0c;再里面激活管理通行证 问题情境&#xff1a; 点击unity出现以下弹窗&a…

板块一 Servlet编程:第八节 文件上传下载操作 来自【汤米尼克的JavaEE全套教程专栏】

板块一 Servlet编程&#xff1a;第八节 文件的上传下载操作 一、文件上传&#xff08;1&#xff09;前端内容&#xff08;2&#xff09;后端内容 二、文件下载&#xff08;1&#xff09;前端的超链接下载&#xff08;2&#xff09;后端下载 在之前的内容中我们终于结束了Servle…

C++——基础语法(2):函数重载、引用

4. 函数重载 函数重载就是同一个函数名可以重复被定义&#xff0c;即允许定义相同函数名的函数。但是相同名字的函数怎么在使用的时候进行区分呢&#xff1f;所以同一个函数名的函数之间肯定是要存在不同点的&#xff0c;除了函数名外&#xff0c;还有返回类型和参数两部分可以…

【Linux】 faillock 命令使用

faillock 命令 faillock 命令是 PAM (Pluggable Authentication Modules) 的一部分&#xff0c;它被设计用来跟踪失败的登录尝试&#xff0c;并在连续失败尝试超过某个阈值时锁定账户。这个功能可以帮助系统管理员识别和防止暴力破解攻击。当一个用户连续多次输入错误的密码后&…

Vue.js+SpringBoot开发超市商品管理系统

目录 一、摘要1.1 简介1.2 项目录屏 二、研究内容2.1 数据中心模块2.2 超市区域模块2.3 超市货架模块2.4 商品类型模块2.5 商品档案模块 三、系统设计3.1 用例图3.2 时序图3.3 类图3.4 E-R图 四、系统实现4.1 登录4.2 注册4.3 主页4.4 超市区域管理4.5 超市货架管理4.6 商品类型…

Python中的functools模块详解

大家好&#xff0c;我是海鸽。 函数被定义为一段代码&#xff0c;它接受参数&#xff0c;充当输入&#xff0c;执行涉及这些输入的一些处理&#xff0c;并根据处理返回一个值&#xff08;输出&#xff09;。当一个函数将另一个函数作为输入或返回另一个函数作为输出时&#xf…

项目实战:Qt监测操作系统物理网卡通断v1.1.0(支持windows、linux、国产麒麟系统)

若该文为原创文章&#xff0c;转载请注明出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/136276999 红胖子(红模仿)的博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结…

数据结构-列表LinkedList

一,链表的简单的认识. 数组,栈,队列是线性数据结构,但都算不上是动态数据结构,底层都是依托静态数组,但是链表是确实真正意义上的动态数组. 为什么要学习链表? 1,链表时最简单的动态数据结构 2,掌握链表有助于学习更复杂的数据结构,例如,二叉树,trie. 3,学习链表有助于更深入…

fpga_硬件加速引擎

一 什么是硬件加速引擎 硬件加速引擎&#xff0c;也称硬件加速器&#xff0c;是一种采用专用加速芯片/模块替代cpu完成复杂耗时的大算力操作&#xff0c;其过程不需要或者仅需要少量cpu参与。 二 典型的硬件加速引擎 典型的硬件加速引擎有GPU&#xff0c;DSP&#xff0c;ISP&a…

【Web】CTFSHOW 常用姿势刷题记录(全)

目录 web801 web802 web803 web804 web805 web806 web807 法一&#xff1a;反弹shell 法二&#xff1a;vps外带 web808 web809 web810 web811 web812 web813 web814 web815 web816 web817 web818 web819 web820 web821 web822 web823 web824 web825…