【C++深入浅出】类和对象下篇

这篇具有很好参考价值的文章主要介绍了【C++深入浅出】类和对象下篇。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

【C++深入浅出】类和对象下篇,C++深入浅出,c++,开发语言,初始化列表,static成员,友元,匿名对象,explicit关键字


一. 前言

        老样子,先来回顾一下上期的内容:上期我们着重学了C++类中的六大默认成员函数,并自己动手实现了一个日期类,相信各位对C++中的类已经有了一定程度的了解。本期就是类和对象的最后一篇啦,终于要结束咯,吧唧吧唧【C++深入浅出】类和对象下篇,C++深入浅出,c++,开发语言,初始化列表,static成员,友元,匿名对象,explicit关键字

        话不多说,开吃咯!!!

二. 初始化列表

2.1 引入

        我们先来看看下面的代码:

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

private:
	const int _year;
	const int _month;
	const int _day;
};
int main()
{
	Date d;
	return 0;
}

【C++深入浅出】类和对象下篇,C++深入浅出,c++,开发语言,初始化列表,static成员,友元,匿名对象,explicit关键字

当我们编译代码时,发现编译器报了一大堆错误。报错的主要原因主要有两个【C++深入浅出】类和对象下篇,C++深入浅出,c++,开发语言,初始化列表,static成员,友元,匿名对象,explicit关键字

1、const变量定义时需要进行初始化

2、const变量不能作为左值

        欸,可能有些小伙伴就纳闷了:我们不是在构造函数中对const成员变量进行初始化了吗? 实际上,在构造函数函数体内进行的并不是初始化,而是赋值操作。因为初始化只能初始化一次,而构造函数体内可以进行多次赋值。

        出于这个原因,于是编译器就会报出以上两种错误。那怎么办呢?众所周知,初始化是在定义变量时进行的,那变量又是在哪定义的呢?答案是:初始化列表

2.2 概念

        在C++中,初始化列表可以认为是成员变量定义的地方。

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

class Date
{
public:
	Date(int year = 2023, int month = 1, int day = 1)
		:_year(year) //初始化列表,是每个成员变量定义的地方,可以进行初始化
		,_month(month) //用month的值初始化成员变量_month
		,_day(day)
	{}

private:
	//成员变量的声明
	const int _year = 0; 
	const int _month = 0;
	const int _day = 0;
};
int main()
{
	Date d;
	return 0;
}

2.3 注意事项

  1. 变量的初始化只能初始化一次,故每个成员变量在初始化列表中只能出现一次
  2. 当类中包含以下成员时,必须放在初始化列表位置进行初始化【C++深入浅出】类和对象下篇,C++深入浅出,c++,开发语言,初始化列表,static成员,友元,匿名对象,explicit关键字
    class A
    {
    public:
    	A(int a) //显式定义构造函数,不自动生成默认构造函数
    		:_a(a)
    	{}
    private:
    	int _a;
    };
    class B
    {
    public:
    	B(int a, int ref)
    		:_a(a) //调用有参构造函数初始化
    		, _ref(ref) //初始化引用变量
    		, _n(10) //初始化const变量
    	{}
    private:
    	A _a; // 没有默认构造函数的类
    	int& _ref; // 引用变量
    	const int _n; // const变量
    };
  3. 建议尽量使用初始化列表初始化,因为初始化列表是成员变量定义的地方,无论你是否显式地写,每个成员都要走初始化列表
    class Time
    {
    public:
    	Time(int hour = 0)
    		:_hour(hour)
    	{
    		cout << "Time()" << endl;
    	}
    private:
    	int _hour;
    };
    class Date1
    {
    public:
    	Date1(int day)
    		:_day(day)  //使用初始化列表进行初始化
    		,_t(day)
    	{}
    private:
    	int _day;
    	Time _t;
    };
    
    class Date2
    {
    public:
    	Date2(int day)
    	{
    		_day = day; //在构造函数内部进行赋值
    		_t = day;
    	}
    private:
    	int _day;
    	Time _t;
    };
    int main()
    {
    	Date1 d1(3);
    	cout << "-----------------------" << endl;
    	Date2 d2(3);
    	return 0;
    }

    【C++深入浅出】类和对象下篇,C++深入浅出,c++,开发语言,初始化列表,static成员,友元,匿名对象,explicit关键字

  4. C++11支持在声明处给缺省值,这个缺省值就是给初始化列表的。如果初始化列表没有显式给值,则使用这个缺省值;如果显式给了,就用给的值进行初始化。

  5. 初始化列表对成员变量的初始化顺序与其声明的次序相同,与初始化列表的先后次序无关。举个小例子【C++深入浅出】类和对象下篇,C++深入浅出,c++,开发语言,初始化列表,static成员,友元,匿名对象,explicit关键字

    class A
    {
    public:
    	A(int a)
    		:_a1(a)  //初始化列表的顺序和声明一样,即也是先初始化_a2再初始化_a1
    		, _a2(_a1)  //那么,这里用_a1初始化_a2会发生什么?_a1的值是多少
    	{}
    	void Print() {
    		cout << _a1 << " " << _a2 << endl;
    	}
    private:
    	//成员变量的声明,先_a2再_a1
    	int _a2;
    	int _a1;
    };
    int main() {
    	A aa(1);
    	aa.Print();
    }

    上面代码的输出结果是1 随机值

    解析:由于_a2的声明在_a1前,_a2会先于_a1进行初始化,因此_a2初始化时_a1还是个随机值,故_a2会被初始化为随机值,然后_a1再初始化为1。


    我们也可以使用调试来观察初始化顺序,如下所示:【C++深入浅出】类和对象下篇,C++深入浅出,c++,开发语言,初始化列表,static成员,友元,匿名对象,explicit关键字


三. explicit关键字

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

class Date
{
public:
    // 1. 单参构造函数,具有隐式类型转换作用
	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;
};
int main()
{
	Date d1(2022); //使用单参构造函数初始化d1

	// 用一个整形变量给日期类型对象赋值
	// 实际编译器背后会用2023构造一个匿名的临时对象,最后用这个临时对象给d1对象赋值
	d1 = 2023; 
	return 0;
}

【C++深入浅出】类和对象下篇,C++深入浅出,c++,开发语言,初始化列表,static成员,友元,匿名对象,explicit关键字

像上面这种运算符左右两边类型不匹配,运算时编译器背后进行处理的过程,称之为隐式类型转换


但是,这样的代码往往可读性不好,我们更希望书写代码时左右两边的类型是一致的,那有没有什么办法可以禁止编译器进行隐式类型转换呢?有,就是explicit关键字

使用 explicit(显式的) 修饰构造函数,将会禁止构造函数的隐式类型转换。很简单,直接在构造函数前面加上explicit即可,这里就不再进行演示了。

四. static成员

4.1 概念

        用static修饰的成员变量称为静态成员变量;用static修饰的成员函数称之为静态成员函数。一般来说,静态成员变量一定要在类外进行初始化,但在C++11中允许const静态成员变量在类内初始化,如下所示:

class A
{
	static int GetCount() //静态成员函数
	{
		return count;
	}
private:
	static int count; //静态成员变量,必须类外初始化
    const static int num = 10; //const静态成员变量,可以在类内初始化,但不建议
};

int A::count = 10; //静态成员变量要在类外进行初始化

4.2 特性

  1. 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
  2. 静态成员变量必须类内声明、类外定义。定义时不用添加static关键字,类中的只是声明
  3. 类的静态成员可以用 类名::静态成员 或者 对象名.静态成员 来访问
  4. 静态成员函数没有隐藏的this指针不能访问任何非静态成员
  5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制
     

小问题:静态成员函数可以调用非静态成员函数吗?反过来呢?


问题解答:答案是不行,静态成员函数不能调用非静态成员函数,因为静态成员函数没有隐藏的this指针,而非静态成员函数需要通过this指针来调用。但是非静态成员函数可以调用静态成员函数,因为静态成员函数的特点是没有this指针,故可以直接进行调用。


五. 友元

5.1 概念

        在C++中,为了封装性我们一般将成员变量声明为【private】私有的,只允许在类内访问成员变量。但是有时候我们需要在类外访问这些成员变量,此时有两种方法:1.将成员变量声明为【public】共有;2.利用友元

        友元提供了一种突破封装的方式,为代码的编写提供了便利。友元分为友元类友元函数,当一个函数/类声明为某个类的友元函数/类时,这个函数/类访问类中成员时不受访问限定符限制。下面是函数/类声明为友元的方式,用到了friend关键字【C++深入浅出】类和对象下篇,C++深入浅出,c++,开发语言,初始化列表,static成员,友元,匿名对象,explicit关键字

class A
{
	friend void GetCount(const A& a); //将全局函数GetCount声明为A类的友元函数
	friend class B; //将B类声明为A类的友元类
private:
	int count = 10;
	int num = 20;
};

class B
{
public:
	void GetNum(const A& a)
	{
		cout << a.num << endl; //b类中可以访问a类的私有成员
	}
};
void GetCount(const A& a)
{
	cout << a.count << endl; //可以访问A类的私有成员
}

int main()
{
	A a;
	B b;
	GetCount(a);
	b.GetNum(a);
	return 0;
}

小贴士:虽然友元提供了便利,但是友元会增加耦合度,破坏程序的封装性,故不建议使用友元。

5.2 友元函数   

        友元函数一般用作于流提取运算符>>以及流插入运算符<<的重载,这两个运算符的重载比较特殊,不能当做成员函数进行重载。

class Date
{
public:
	Date(int year = 2023, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{}
	//如果重载为Date的成员函数,第一个参数为隐藏的this指针,但cout是ostream类的对象,第一个参数应该是ostream类型,互相矛盾
	// ostream& operator<<(const Date& d);   
private:
	int _year;
	int _month;
	int _day;
};

//为了让第一个参数类型为ostream,故当做全局函数重载
const ostream& operator<<(const ostream& out, const Date& d)
{
	out << d._year << "年" << d.month << "月" << d.day << "日";
	return out;
}

int main()
{
	Date d;
	cout << d;  //重载流插入运算符使其可以输出日期类 
	return 0;
}

        那么问题就来了,既然不能声明为成员函数,那我们在全局函数中要怎么访问Date的私有成员呢?【C++深入浅出】类和对象下篇,C++深入浅出,c++,开发语言,初始化列表,static成员,友元,匿名对象,explicit关键字

         这时候就不得不使用我们上面说的友元了,将operator<<声明为Date类的友元函数后,代码成功运行:

class Date
{
public:
	Date(int year = 2023, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{}
	friend ostream& operator<<(ostream& out, const Date& d); //将operator<<全局函数声明为Date类的友元函数
private:
	int _year;
	int _month;
	int _day;
};

【C++深入浅出】类和对象下篇,C++深入浅出,c++,开发语言,初始化列表,static成员,友元,匿名对象,explicit关键字

注意事项

  • 友元函数可以访问类中的私有成员,它是定义在类外部的普通函数,但需要在类的内部进行声明,声明时需要加friend关键字
  • 友元函数不能用const修饰,const只能修饰成员函数
  • 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  • 一个函数可以是多个类的友元函数

5.3 友元类

        友元类中的所有成员函数都可以访问另一个类的非公有成员。友元关系是单向的,不具有交换性。例如B是A的友元类,B中的所有成员函数可以访问A中的私有成员,但A中的成员函数不能访问B中的私有成员。举例如下:

class A
{
	friend class B; //定义B是A的友元类

	void GetSum(B& b)
	{
		cout << b.sum << endl;  //这里会报错,A类的成员函数无法访问B类的私有成员,不具有交换性
	}
private:
	int count = 20;
};

class B
{
	void GetCount(A& a)
	{
		cout << a.count << endl; //通过编译,B是A的友元类,B中成员函数可以访问A的私有成员
	}
private:
	int sum = 10;
};

        以上程序编译时会报错如下【C++深入浅出】类和对象下篇,C++深入浅出,c++,开发语言,初始化列表,static成员,友元,匿名对象,explicit关键字

【C++深入浅出】类和对象下篇,C++深入浅出,c++,开发语言,初始化列表,static成员,友元,匿名对象,explicit关键字


        友元关系也不具有传递性。例如:C是B的友元类,B是A的友元类,无法说明C是A的友元。举例如下【C++深入浅出】类和对象下篇,C++深入浅出,c++,开发语言,初始化列表,static成员,友元,匿名对象,explicit关键字

class A
{
	friend class B; //定义B是A的友元类
private:
	int a_sum = 10;
};

class B
{
	friend class C; //定义C是B的友元类
private:
	int b_sum = 20;
};

class C
{
	void GetBSum(B& b)
	{
		cout << b.b_sum << endl;  //编译通过,C是B的友元类
	}
	void GetASum(A& a)
	{
		cout << a.a_sum << endl;  //这里编译器会报错,C不是A的友元类,无法访问私有成员,友元关系不具有传递性
	}
private:
	int c_sum = 30;
};

         以上程序编译时会报错如下【C++深入浅出】类和对象下篇,C++深入浅出,c++,开发语言,初始化列表,static成员,友元,匿名对象,explicit关键字 

【C++深入浅出】类和对象下篇,C++深入浅出,c++,开发语言,初始化列表,static成员,友元,匿名对象,explicit关键字


六. 内部类

        一个类不仅可以定义在全局范围内,还可以定义在另一个类的内部。我们将定义在某个类内部的类称之为内部类。下面的B类就是一个内部类:

class A //A称为外部类
{
public:

	class B //B类在A类的内部定义,称之为内部类
	{
	private:
		int sum; //b类的成员变量
	};

private:
	int count; //a类的成员变量
};

        内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何特殊的访问权限。有以下两个具体体现【C++深入浅出】类和对象下篇,C++深入浅出,c++,开发语言,初始化列表,static成员,友元,匿名对象,explicit关键字

  • 内部类可以定义在外部类的的任何位置,不受外部类访问限定符的限制。
  • sizeof(外部类)=外部类,和内部类没有任何关系

        内部类是外部类的友元类,内部类可以通过外部类的对象访问外部类的所有成员。但外部类不是内部类的友元类,无权访问内部类的私有成员。

class A //A是外部类
{
public:

	class B //B是内部类
	{
		int GetACount(A& a)  
		{
			return a.count; //可以访问外部类的私有成员
		}
	private:
		int sum; 
	};

	int GetBSum(B& b)
	{
		return b.sum; //这里会报错,外部类不能访问内部类的私有成员
	}
private:
	int count; //a类的成员变量
};

【C++深入浅出】类和对象下篇,C++深入浅出,c++,开发语言,初始化列表,static成员,友元,匿名对象,explicit关键字

        内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名,如下所示【C++深入浅出】类和对象下篇,C++深入浅出,c++,开发语言,初始化列表,static成员,友元,匿名对象,explicit关键字

class A //A是外部类
{
public:

	class B //B是内部类
	{
		int GetACount()  
		{
			return _count; //可以直接访问外部类的静态成员变量,无需类名/类对象
		}
	private:
		int sum; 
	};

private:
	static int _count; // A中的静态成员变量
};

int A::_count = 10; //类外进行初始化

七. 匿名对象

        C++支持我们不给对象起名字,这样的对象我们称为匿名对象,其定义方式如下:

int main()
{
	//对象类型+():创建一个匿名对象
	A();  //这里就是创建一个匿名对象A
	return 0;
}

        匿名对象的声明周期只在当前行,当前行结束后会自动调用析构函数进行销毁:【C++深入浅出】类和对象下篇,C++深入浅出,c++,开发语言,初始化列表,static成员,友元,匿名对象,explicit关键字

       匿名对象具有常属性,即不能对匿名对象中的成员变量进行修改:

int main()
{
	A().count = 10; //编译器报错:表达式必须是可修改的左值
	return 0;
}

        可以给匿名对象取别名,这样可以延长匿名对象的声明周期:

int main()
{
	//给匿名对象取别名
	const A& cla1 = A(); //注意:这里必须是const引用,因为匿名对象具有常性,权限不能放大
	cout << "程序即将结束" << endl;
	return 0;
}

【C++深入浅出】类和对象下篇,C++深入浅出,c++,开发语言,初始化列表,static成员,友元,匿名对象,explicit关键字

        匿名对象经常用在仅需调用某个类的成员函数的情况,可以简化我们代码的编写。举例如下 【C++深入浅出】类和对象下篇,C++深入浅出,c++,开发语言,初始化列表,static成员,友元,匿名对象,explicit关键字

class Solution //Solution类用来求两数之和
{
public:
	int Sum_Solution(int x,int y)  //返回两数之和 
	{
		return x + y;
	}
};

int main()
{
	//不使用匿名对象
	Solution s1; //要先定义一个类对象,这个对象仅仅只是用来调用方法
	s1.Sum_Solution(2, 2); //然后再去调用成员函数

	//使用匿名对象
	Solution().Sum_Solution(2, 3); //代码更加简便
}

        上面的Solution类是不是很熟悉?没错,在我们使用C++进行刷题时每次能够遇到它【C++深入浅出】类和对象下篇,C++深入浅出,c++,开发语言,初始化列表,static成员,友元,匿名对象,explicit关键字【C++深入浅出】类和对象下篇,C++深入浅出,c++,开发语言,初始化列表,static成员,友元,匿名对象,explicit关键字


 以上,就是本期的全部内容啦🌸

制作不易,能否点个赞再走呢🙏文章来源地址https://www.toymoban.com/news/detail-715877.html

到了这里,关于【C++深入浅出】类和对象下篇的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 在疯狂三月之后,深入浅出分析AIGC的核心价值 (下篇)|【AI行研&商业价值分析】

    Rocky Ding 公众号:WeThinkIn 【AI行研商业价值分析】栏目专注于分享AI行业中最新热点/风口的思考与判断。也欢迎大家提出宝贵的优化建议,一起交流学习💪 大家好,我是Rocky。 本文是《在疯狂三月之后,深入浅出分析AIGC的核心价值》系列的第二篇文章,在第一篇文章中,我

    2024年02月16日
    浏览(42)
  • 【C++】类和对象(下篇)(万字)

    🎇C++学习历程:入门 博客主页: 一起去看日落吗 持续分享博主的C++学习历程 博主的能力有限,出现错误希望大家不吝赐教 分享给大家一句我很喜欢的话: 也许你现在做的事情,暂时看不到成果,但不要忘记,树🌿成长之前也要扎根,也要在漫长的时光🌞中沉淀养分。静

    2024年02月02日
    浏览(37)
  • 详解C++类和对象(下篇)

    目录 一,再谈构造函数  1.1 构造函数体赋值  1. 2 初始化列表  1.21 自定义类型成员  1.22  const 成员变量 1.23 引用成员变量   1. 24 初始化列表的“坑” 1. 3 explicit   二,static 成员 2.1 概念  2.2 特性  三, 友元 3. 1 友元函数 3. 2 友元类  特点:  3. 3 内部类(了解)

    2024年02月04日
    浏览(47)
  • 【深入浅出C#】章节 4: 面向对象编程基础:封装、继承和多态

    封装、继承和多态是面向对象编程中的核心概念,它们对于构建灵活、可扩展和可维护的软件系统至关重要。 封装(Encapsulation)通过将数据和相关操作封装在一个类中,隐藏内部实现细节,并提供公共接口来与外部进行交互。封装有助于保护数据的完整性和安全性,同时提

    2024年02月10日
    浏览(58)
  • 【C++深入浅出】模版初识

    目录 一. 前言 二. 泛型编程 三. 函数模版  3.1 函数模版的概念 3.2 函数模版的格式 3.3 函数模版的原理 3.4 函数模板的实例化 3.5 模板参数的匹配原则 四. 类模版 4.1 类模版的定义 4.2 类模版的实例化         本期我们要介绍的是C++的又一大重要功能---- 模版 。通过模版,我们

    2024年02月08日
    浏览(61)
  • 深入浅出C++ ——线程库

      在C++11之前,涉及到多线程问题,都是和平台相关的,比如windows和linux下各有自己的接口,这使得代码的可移植性比较差。C++11中最重要的特性就是对线程进行支持了,使得C++在并行编程时不需要依赖第三方库,而且在原子操作中还引入了 原子类 的概念。要使用标准库中

    2024年02月03日
    浏览(70)
  • 深入浅出C++——C++的类型转换

    在C语言中,如果 赋值运算符左右两侧类型不同 ,或者形参与实参类型不匹配,或者 返回值类型与接收返回值类型不一致 时,就需要发生类型转化。 C语言中总共有两种形式的类型转换: 隐式类型转换:编译器在编译阶段自动进行转换,不能转就编译失败。 显式类型转换:

    2024年02月07日
    浏览(56)
  • 深入浅出C++ ——C++11

        1998年是C++标准委员会成立的第一年,本来计划以后每5年视实际需要更新一次标准,C++国际标准委员会在研究C++ 03的下一个版本的时候,一开始计划是2007年发布,所以最初这个标准叫C++07。但是到06年的时候,官方觉得2007年肯定完不成C++ 07,而且官方觉得2008年可能也

    2024年02月02日
    浏览(58)
  • 深入浅出循环语句—【C语言】

      分支语句博客: http://t.csdn.cn/U2kZF 目录 ​编辑 前言:我们先来了解一下break 、continue在循环中的作用 1. while循环  while循环中的break  while循环中的continue  2. for循环 for循环省略出错举例:  for循环中的break  for循环中的continue 3. do   while循环 利用do while循环打印1~10   d

    2024年02月04日
    浏览(105)
  • 深入浅出C语言—【函数】下

    函数和函数之间可以根据实际的需求进行组合的,也就是互相调用的。 注意: 函数可以嵌套调用,但是不能嵌套定义。 把一个函数的返回值作为另外一个函数的参数。 上面的strlen函数是求数组长度的库函数, 特别注意的是,当数组为字符数组时,数组的末尾会自动放一个

    2024年02月17日
    浏览(82)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包