类和对象 (三)

news2024/11/18 1:51:08

目录

<一>const成员

<二> 取地址及const取地址操作符重载

<三>再谈构造函数(初始化列表)

1.构造函数体赋值

2.初始化列表

<四>explicit关键字

<五>static成员

概念

<六>友元函数

<七>友元类

<八>内部类

<九>匿名对象


<一>const成员

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

 我们来看看代码:


class Date
{
public:
	Date(int year = 2022, int month = 10, int day = 24)//默认构造
	{
		_year = year;
		_month = month;
		_day = day;
	}

	//void Print1(Date* this)
	void print1()
	{
		cout << _year << '/' << _month << '/' << _day << endl;
	}//这里不加const 只能事非const对象可以调用


	//void Print2(const Date* this)
	void print2() const//const加在函数的后面  修饰this指针。
	{
		cout << _year << '/' << _month << '/' << _day << endl;
	}//这里加 const不仅const对象可以调用  非const对象也能调用。


	//上面两个又构成函数的重载。



private:
	int _year;
	int _month;
	int _day;


};

int main()
{
	Date d1(2022, 10, 24);
	d1.print1();
	d1.print2();

	const Date d2(2022, 10, 24);
	//d2.print1();//报错权限提升
	d2.print2();


	return 0;
}

注意:

  • 成员函数加 const,变成 const 成员函数是有好处的,这样 const 对象可以调用,非 const 对象也可以调用。
  • 不是说所有的成员函数都要加 const ,具体要看成员函数的功能,如果成员函数是修改型 (operrato+=、Push),那就不能加;如果是只读型 (Print、operator+),那就最好加。
  • const 对象不可以调用非 const 成员函数 (权限放大);非 const 对象可以调用 const 成员函数 (权限缩小)。(权限只能缩小不能放大)
  • const 成员函数内不可以调用其它非 const 成员函数;非 const 成员函数内可以调用其它 const 成员函数。

<二> 取地址及const取地址操作符重载

  • 这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
    
    class Date
    {
    public:
    	//取地址重载
    	Date* operator&()
    	{
    		return this;
    	}
    
    	//const取地址操作符重载
    	//注意这里的返回值喝 const函数的使用。
    	const Date* operator&()const
    	{
    		return this;
    	}
    
    
    private:
    	int _year; // 年
    	int _month; // 月
    	int _day; // 日
    };
    
    
    int main()
    {
    	Date d1;
    	Date d2;
    	cout << &d1 << endl;
    	cout<< &d2 << endl;
    	return 0;
    }

    注意:这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容!

<三>再谈构造函数(初始化列表)

1.构造函数体赋值

在创建对象时,编译器会自动调用构造函数,给对象的每一个成员变量一个合适的初始值。


class Date
{
public:
	Date(int year = 2022, int month = 10, int day = 24)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};

注意:

  • 虽然在调用构造函数前已经有一个初始值,但是不能将其视为对对象进行初始化,构造函数的函数体中的语句只能将其称为赋值,而不能将其称为初始化。因为初始化只能初始化一次,而构造函数体内可以进行多次赋值。正真的初始化是在初始化列表中初始化。

2.初始化列表

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


class Date
{
public:
	Date(int year = 2022, int month = 10, int day = 24)
		:_year(year)
		,_month(month)
		,_day(day)
	{	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};

但是当成员变量中又自定义类型,然而这个自定义类型没有默认构造函数的时候就会出现问题这个时候就必须用到,初始化列表来实现初始化。


class A
{
public:
	A(int a = 0)
	{
		_a = a;
		cout << "A(int a = 0)" << endl;
	}

	A& operator=(const A& aa)//不写也行,因为这里只有内置类型,默认生成的就可以完成
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a = aa._a;
		}
		return *this;
	}
private:
	int _a;
};
class B
{
public:
	B(int a, int b)
	{
		//_aa._a = a;//err:无法访问private成员


		//无法访问就只能再去定义一个A 类的对象。
		//然后赋值重载给_aa;

		//A aa(a);//构造函数
		//_aa = aa;//赋值重载函数

		_aa = A(a);//简化版,同上面一个意思

		_b = b;
	}
private:
	int _b = 1;
	A _aa;
};

【注意】 

  • 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
  • 类中包含以下成员,必须放在初始化列表位置进行初始化:

1.引用成员变量

2.const成员变量 

3 .自定义类型成员(且该类没有默认构造函数时)

class A
{
public:
	A(int a)//普通构造函数
		:_a(a)
	{}
private:
	int _a;
};


class B
{
public:
	B(int a, int ref)
		:_aobj(a)
		,_ref(ref)//引用只能在定义的时候初始化,所以必须放在这里
		,_n(10)//const类型的也是一样必须在定义的时候初始化。
	{}
private:
//这里只是声明
	A _aobj; // 没有默认构造函数
	int& _ref; // 引用
	const int _n; // const
};
  • 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。
  • 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关

<四>explicit关键字

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


class Date
{
public:
	// //1. 单参构造函数,没有使用explicit修饰,具有类型转换作用
	// //explicit修饰构造函数,禁止类型转换---explicit去掉之后,代码可以通过编译
	//explicit Date(int year)
	//	:_year(year)
	//{}
	

	// 2. 虽然有多个参数,但是创建对象时后两个参数可以不传递,没有使用explicit修饰,具有类型转换作用
	// explicit修饰构造函数,禁止类型转换
	 Date(int year, int month = 1, int day = 1)
	: _year(year)
	, _month(month)
	, _day(day)
	{}
	
	Date& operator=(const Date& d)
	{
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}
private:
	int _year;
	int _month;
	int _day;
};
void Test()
{
	Date d1(2022);

	// 用一个整形变量给日期类型对象赋值
	// 实际编译器背后会用2023构造一个无名对象,最后用无名对象给d1对象进行赋值
	d1 = 2023;
	// 将1屏蔽掉,2放开时则编译失败,因为explicit修饰构造函数,禁止了单参构造函数类型转换的作用
	Date d2 = 2022;//这里因为存在隐式类型转换,会有一个临时变量,就会先构造函数+拷贝构造 
	//现在有编译器的优化  就会直接构造函数解决
}

上述代码可读性不是很好,用explicit修饰构造函数,将会禁止构造函数的隐式转换。

<五>static成员

概念

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

如果想要计算一个类被创建过多少次:

int countC = 0;
int countCC = 0;
class A
{
public:
	A()
	{
		++countC;
	}
	A(const A& a)
	{
		++countCC;
	}
};
A f(A a)
{
	A ret(a);
	return ret;
}
int main()
{
	A a1 = f(A());
	A a2;
	A a3;
	a3 = f(a2);
	cout << countC << endl;
	cout << countCC << endl;
	return 0;
}
//这种方式也可以但是,不好countc 和 countCC 可以随意改动  不好,在c++中还是尽量少使用全局变量。
class A
{
public:
	A()
	{
		++_count;
	}
	A(const A& a)
	{
		++_count;
	}
	int GetCount()
	{
		return _count;	
	}
	static int GetCount()
	{
		return _count;	
	}
private:
	int _a;
	static int _count;
};
//定义初始化
int A::_count = 0;
A f(A a)
{
	A ret(a);
	return ret;
}
int main()
{
	A a1 = f(A());
	A a2;
	A a3;
	a3 = f(a2);
	cout << sizeof(A) << endl;
	
	//这里就体现了static成员属于整个类,也属于每个定义出来的对象共享,但限制于公有
	/*cout << A::_count << endl;	
	cout << a1._count << endl;
	cout << a2._count << endl;*/

	/*A ret;
	cout << ret.GetCount() - 1 << endl;*/
	/*cout << A().GetCount() - 1 << endl;*/
	cout << A::GetCount() << endl;
	
	return 0;
}

特性

  1.  静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
  2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明。放在类外面定义的目的是,只初始化一次,这样避免每个对象在定义初始化的时候,都要初始化static成员变量。
  3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
  4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
  5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制
  6. 静态的成员变量和静态成员函数一样,不属于任何一个对象,存在静态区。可以通过类名和对象去调用。

问题

  1. 静态成员函数可以调用非静态成员吗?
    答:不能,因为静态成员函数没有 this 指针。
  2. 非静态成员函数可以调用类的静态成员函数吗?
    答:可以,因为非静态成员函数有 this 指针。

<六>友元函数

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

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

	Date& operator=(const Date& d)
	{
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}

	void print()
	{
		cout << _year <<'/' << _month <<'/' << _day << endl;
	}

	friend ostream& operator<<(const ostream& out, const Date& d);

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

ostream& operator<<(const ostream& out, const Date& d)
{	
	out << d._year << '/' << d._month << '/' << d._day << endl;
}


int main()
{
	
	
	return 0;
}

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

说明:

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

<七>友元类

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

  • 友元关系是单向的,不具有交换性。 比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接 访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
  • 友元关系不能传递 如果C是B的友元, B是A的友元,则不能说明C时A的友元。
  • 友元关系不能继承,在继承位置再给大家详细介绍。

class Time
{
public:
	Time(int hour,int minute ,int second )
		:_hour(hour)
		,_minute(minute)
		,_second(second)
	{}



	friend class Date; //这个就是声明一下 Date 是我的友元类。
private:
	int _hour;
	int _minute;
	int _second;
};


class Date//这个类中的所有成员函数  都可以直接访问Time类的私有成员。
{
public:
	Date(int year = 1, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
		, t(3,3,3)
	{
	}

	Date& operator=(const Date& d)//拷贝构造。
	{
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}
	void print()
	{
		cout << _year << '/' << _month << '/' << _day << endl;
		cout << t._hour << '/' << t._minute << '/' << t._second << endl;
	}
private:
	int _year;
	int _month;
	int _day;
	Time t;
};


int main()
{
	Date d1;
	d1.print();

	return 0;
}

注意:

  • 友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
  • 友元关系是单向的,不具有交换性。比如上述 Time 类和 Date 类,在 Time 类中声明 Date 类为其友元类,那么可以在 Date 类中直接访问 Time 类的私有成员变量,但想在 Time 类中访问 Date 类中私有的成员变量则不行。
  • 友元关系不能传递,如果 C 是 B 的友元, B 是 A 的友元,则不能说明 C 是 A 的友元。

<八>内部类

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

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

特性:

  • 1. 内部类可以定义在外部类的public、protected、private都是可以的。
  • 2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
  • 3. sizeof(外部类)=外部类,和内部类没有任何关系。
  • 相当于两个独立的类 ,只是内部的类 ,受外部类域的限制。
class A//会生成一个默认构造函数。
{
public:
	class B//B类天生就是A的友元
	{
	public:
		void foo(const A& a)
		{
			cout << a.k << endl;//ok
			cout << a.h << endl;//ok	
		}
	private:
		int _b;
	};

private:
	static int k;
	int h;
};
int A::k = 0;


int main()
{
	cout << sizeof(A) << endl;//4//这里的A的大小和B无关,和静态变量也无关。
	cout << sizeof(A::B) << endl;//4//注意这里不知道A域编译器根本不认识B。

	A::B b;//要用B去定义,必须得指定域,必须指定A域

	b.foo(A());//通过实例化对象可以突破A域。
	//A()是匿名对象,生命周期只有一行。

	return 0;
}

<九>匿名对象

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
 }
 ~A()
 {
	 cout << "~A()" << endl;
 }
private:
	int _a;
};
class Solution {
public:
	int Sum_Solution(int n) {
		//...
		return n;
	}
};
int main()
{
	A aa1;


	// 不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义
	//A aa1();
	// 
	// 
	// 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字,
	// 但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数
	A();


	A aa2(2);
	// 匿名对象在这样场景下就很好用,当然还有一些其他使用场景,这个我们以后遇到了再说
	Solution().Sum_Solution(10);


	return 0;
}

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

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

相关文章

2021年上半年软件设计师上午真题及答案解析(五)

41、42、43、当UML状态图用于对系统、类或用例的动态方面建模时&#xff0c;通常是对&#xff08; &#xff09;建模。以下UML状态图中&#xff0c;假设活动的状态是A&#xff0c;事件b0发生并且a>5&#xff0c;发生条件是c状态到d状态的转换条件的是&#xff08; &#xff…

HTTP缓存

http缓存分为&#xff1a;强制缓存和协商缓存 强缓存 不需要客户端就不需要向服务器发送请求&#xff0c;直接使用本地缓存 对于强缓存的资源&#xff0c;可以看到返回的状态码是 200&#xff0c;并且会显示 from memory cache/from disk cache&#xff0c;强缓存是通过 Exp…

Go语言开发k8s-04-Service操作

文章目录1. 结构体1.1 ServiceList1.2 Service1.3 TypeMeta1.4 ObjectMeta1.5 ServiceSpec1.6 ServiceStatus1.7 对照yml文件示例2. Get List语法完整示例3. Create语法完整示例4. Get Service语法完整示例5. Update Service语法完整示例6. Delete Service语法完整示例1. 结构体…

python基于PHP的个人信息管理系统

随着现代工作的日趋繁忙,人们越来越意识到信息管理的重要性与必要性,而具有个性化特色的个人信息管理系统能够高速有效的管理个人信息,从而提升自己的工作效率 社会的发展给人们的生活压力越来越大,每天所要面临的问题也越来越多,面对如此多的事情需要去处理往往会顾此失彼,将很…

SpringCloud-Hystrix服务治理

简介 Hystrix是用于服务熔断&#xff0c;容错管理工具&#xff0c;旨在通过熔断机制控制服务和第三方库的节点&#xff0c;从而对延迟和故障提供更强大的容错能力。 服务降级 当服务器压力剧增的情况下&#xff0c;根据实际业务情况及流量&#xff0c;对一些服务和页面有策略的…

Web前端入门(十八)圆角边框及盒子阴影

总目录&#xff1a;https://blog.csdn.net/treesorshining/article/details/124725459 文章目录1.圆角边框2.盒子阴影2.1 开发中阴影常用语句2.2 文字阴影1.圆角边框 在 CSS3 中&#xff0c;新增了圆角边框样式&#xff0c;这样盒子就可以变圆角了。border-radius 属性用于设置…

牛客刷题总结——Python入门03:运算符

&#x1f935;‍♂️ 个人主页: 北极的三哈 个人主页 &#x1f468;‍&#x1f4bb; 作者简介&#xff1a;Python领域优质创作者。 &#x1f4d2; 系列专栏&#xff1a;《牛客题库-Python篇》 &#x1f310;推荐《牛客网》——找工作神器|笔试题库|面试经验|实习经验内推&am…

【Linux】分析缓冲区,刷新机制,FILE

文章目录一、Linux的缓冲区(一) 用户层缓冲区(二) 内核层缓冲区&#xff08;Kernel Buffer Cache&#xff09;验证buffer增加和减少释放缓存二、缓冲区的刷新策略(一) 用户层缓冲区刷新策略(二) 内核层缓冲区刷新策略三、探究缓冲区常见问题的产生(一) 由于缺失换行符导致内容没…

相对于java,C++中的那些神奇语法

空指针还可以调用成员函数 #include <cstdio>class Person { public:void sayHello() {printf("hello!\n");} };int main() {auto * p new Person;p->sayHello();p nullptr;p->sayHello();return 0; }运行结果如下&#xff1a; hello! hello!进程已结…

【深入理解java虚拟机】JVM故障处理工具介绍

目录前言一、jps&#xff1a;虚拟机进程状况工具一、一 输出远程机器信息二、jstat&#xff1a;虚拟机统计信息监视工具三、jinfo&#xff1a; Java配置信息工具四、jmap&#xff1a; Java内存映像工具五、jhat&#xff1a;虚拟机堆转储快照分析工具六、jstack&#xff1a; Jav…

问:毁掉一个人,有多容易?答:年龄到了就可以

人到中年&#xff0c;有点难。 曾在虎扑论坛上看到一篇爆帖&#xff1a; 标题是《loser回忆录&#xff1a;一年前我月薪两万被人叫X总&#xff0c;如今在美团送外卖》&#xff0c;63万浏览量&#xff0c;回复也超过了2300条。 如题&#xff0c;帖子的主人公是一个35岁的男人…

node-sass安装失败解决方法

node-sass安装失败&#xff0c;提示如下&#xff1a; gyp verb check python checking for Python executable "python" in the PATH gyp verb which succeeded python D:Program FilesPython38python.EXE gyp ERR! configure error gyp ERR! stack Error: Command f…

nvidia显卡驱动、cuda、cudnn、tensorflow对应版本

1、下载显卡驱动 在nvidia官网下载驱动&#xff0c;驱动官网选择设备的驱动进行搜索下载即可&#xff0c;搜索时注意对应的操作系统 一般为安装NVIDIA Studio驱动版本&#xff0c;GeForce Game Ready适用于游戏玩家&#xff0c;下面是两个版本区别的官方解释 下载完的驱动会以…

关键路径 ← AOE网

【问题描述】 给定一个只有一个源点和一个汇点的有向图&#xff0c;要求求出所有的关键活动&#xff0c;并计算完成该工程至少需要多少时间。【输入格式】 第一行包含两个整数 n 和 m&#xff0c;表示顶点数和边数。 接下来 m 行&#xff0c;每行包含三个整数 u&#xff0c;v&a…

p2p开户银行审核模块功能实现

审核模块简介 用户提交开户申请后要等待审核通过才能审核成功 审核需要银行系统进行开户 使用flask框架搭建一个银行系统 用户提交审核 银行进行开户&#xff0c;在返回p2p后台通过审核 flask搭建测试银行系统 利用工厂模式搭建一个flask框架 app.py from flask import Fl…

程序员必看内容连续集之 Redis 03

目录 一、Spring整合Redis 二、注解式开发 一、Spring整合Redis ①项目的pom文件导入依赖并修改 <redis.version>2.9.0</redis.version> <redis.spring.version>1.7.1.RELEASE</redis.spring.version><dependency><groupId>redis.clien…

(附源码)计算机毕业设计SSM抗新冠肺炎药品进销存管理系统

&#xff08;附源码&#xff09;计算机毕业设计SSM抗新冠肺炎药品进销存管理系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 …

Synchronized锁的使用

前言&#xff1a;大家好&#xff0c;我是小威&#xff0c;24届毕业生&#xff0c;在一家满意的公司实习。本篇文章是关于并发编程中Synchronized锁的用法知识记录&#xff0c;由于篇幅原因&#xff0c;核心原理知识下篇记录。 本篇文章记录的基础知识&#xff0c;适合在学Java的…

进程的基本概念(操作系统)

目录 一、程序的顺序执行及其特征 二、程序的并发执行及其特征 三、进程的特征与状态 1、进程的定义和特征 2、进程的三种基本状态 3、进程的三种基本状态的转换 4、 挂起状态 四、进程控制块&#xff08;PCB&#xff09; 1. PCB作用&#xff1a; PCB是进程存在的唯一…

【力扣】正则表达式匹配--回溯法解剖

题目&#xff1a;10.正则表达式匹配 给你一个字符串 s 和一个字符规律 p&#xff0c;请你来实现一个支持 . 和 * 的正则表达式匹配。 . 匹配任意单个字符 * 匹配零个或多个前面的那一个元素 所谓匹配&#xff0c;是要涵盖 整个 字符串 s的&#xff0c;而不是部分字符串。 示…