C++中关于类和对象的一些细枝末节

文章详细介绍了C++编程中的explicit关键字用于防止隐式类型转换,静态成员变量和静态成员函数的概念及用法,包括它们的初始化和访问方式。接着讨论了友元函数和友元类,解释了如何打破封装并允许访问私有和保护成员。最后,文章提到了内部类(嵌套类)以及匿名对象的应用,展示了如何在不需要长期存在的情况下短暂使用对象。

explicit关键字

我们先来看看这段代码:

#include<iostream>

using namespace std;

class A
{
public:
	A(int a)
		:_a(a)
	{
		cout << _a << endl;
	};//构造函数


private:
	int _a;
};

int main()
{
	A a(1);
	A b = 2;
	return 0;
}

这里我们发现这两个都是拷贝构造,有两种实例化的方式,
但其实内部是不一样的,
第一个是把1传递给a的构造函数中,之后再把a这个对象中的成员变量赋值成1。
第二个是将1隐式类型转换成A类型,然后拷贝给b这个对象。
如果不想发生隐式类型转换就可以用explicit这个关键字来修饰,只需要在该类的构造函数前面加上explicit:
在这里插入图片描述
另外还补充一点:

上面这个例子是对于单个参数,也就是参数列表中只有一个参数,那对于多个参数呢?
C++98不支持,在C++11才支持。
它的实例化这样的:

#include<iostream>

using namespace std;

class A
{
public:
	A(int a,int b)
		:_a(a)
		,_b(b)
	{
		cout << _a << _b << endl;
	};//构造函数


private:
	int _a;
	int _b;
};

int main()
{
	/*A a(1);
	A b = 2;*/

	A arr(1, 2);
	A array = { 2,3 };
	return 0;
}

Static成员变量

在学C语言时我们就知道被static修饰的变量叫静态变量,那么在类中,被static修饰的成员就叫静态成员,其中,如果是变量被修饰,那就是静态成员变量,如果是函数被修饰,那就是静态成员函数。

class A
{
public:
	A() { ++_scount; }
	A(const A& t) { ++_scount; }
	~A() { --_scount; }
	static int GetACount() { return _scount; }//静态成员函数
private:
	static int _scount;//静态成员变量
};


int A::_scount = 0;
void TestA()
{
	cout << A::GetACount() << endl;
	A a1, a2;
	A a3(a1);
	cout << A::GetACount() << endl;
}

静态成员变量不能在类中定义,必须在全局定义(初始化)。
静态成员的特性
静态成员不是单独属于某个对象,所有用这个类实例化出来的对象都可以共享,静态成员是存在静态区的。
静态成员变量必须在类的外面定义初始化,并且定义时不用加static,在类中它只是声明。
访问静态成员变量可以用类名::静态成员变量,或者对象.静态成员变量,当然,这两个方法有个前提:就是该静态成员的访问限定符是公开的(public),所以,静态成员变量它是受访问限定符约束的。
另外,再说一说为什么要在成员函数前面加上static?
以上面代码为例,先假设static已经去掉了,这个代码是在统计一共创建了多少个对象,在TestA函数中创建了3个对象,那么在main函数中调用这个函数后,还要调用GetACount()成员函数来打印共创建了多少个对象,那么此时必须要先创建一个对象来专门访问这个成员函数的,这样就有点浪费空间并且没必要,所以我们可以在成员函数前面加上static修饰之后,就可以直接用类名::静态成员函数进行访问。
静态成员函数参数没有this指针,所以,静态成员函数不能访问类成员变量,但是成员函数可以访问静态成员变量。
接下来我们来试试这道题:点击这里
这里我们就可以通过之前那段代码找到灵感,先声明一个被static修饰的成员变量来进行递增,把它初始化为1,再声明一个被static修饰的成员变量来算总和,把它初始化为0,每次前面这个变量递增一次,就加上这个递增后的结果。
代码如下:

#include <climits>
class Sum 
{
    public:
    Sum()
    {
        count += i;
        i++;
    }
    static int Getsum()
    {
        return count;
    }
    private:
    static int count;
    static int i;
};

int Sum::count = 0;
int Sum::i = 1;

class Solution {
public:
    int Sum_Solution(int n) {
        Sum arr[n];
        return Sum::Getsum();
    }
};

友元

在类中,被private修饰的类成员会被封装在一起不能在外部直接访问,被public修饰的就可以在外部直接使用,而友元就可以直接访问被private修饰的类成员,突破这种封装性,所以不建议多用。

友元函数

先看一段代码:

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


private:
	int _year;
	int _month;
	int _day;

};

int main()
{
	Date d(2023, 4, 2);
	int a = 2;
	cout << a << endl;
	cout << d << endl;
	return 0;
}

在编译器中就会发现一个报错:
在这里插入图片描述
这个时候我们就要用运算符重载重载一个日期类的流插入,先看看标准库里是什么样的
网站链接:https://legacy.cplusplus.com/reference/
在这里插入图片描述
上图中,ostream,istream是类,cout其实是ostream这个类实例化出来的全局对象,cin是istream这个类实例化出来的全局对象,然后我们点进ostream这个类里面去看看,往下翻找到operator<<
在这里插入图片描述

点进去就可以看到有这么多的声明:
在这里插入图片描述
红框里的全是内置类型,看到这里我们也就能明白为什么在C++中输入输出不需要格式化自动识别类型了,因为构成函数重载,不同类型会直接去调用最匹配的函数。
接下来,我们试着写一下流插入的运算符重载

void operator<<(ostream& out)//在类里面有隐含的this指针
	{
		out << _year << "年" << _month << "月" << _day << "日" << endl;
	}

但是这么写的话,我们那怎么用这个运算符重载呢?

int main()
{
	Date d(2023, 4, 2);
	int a = 2;
	//cout << a << endl;
	//cout << d << endl;
	d << cout; //等同于d.operator<<(cout);
	cout << i << endl;
	return 0;
}

这样写是不是觉得很别扭,自定义类型和内置类型的写法不一样,我们来分析分析为什么?
首先我们先捋清楚,运算符重载参数第一个参数是左操作数,第二个参数是右操作数,
再根据库里的operator<<的参数列表,以上面代码中内置类型int i变量为例,cout是一个ostream类的对象,作为this指针传递,所以它是第一个操作数,而右操作数就是i这个变量,
然后调用最匹配int类型的函数,从而输出。
再回到我们自己写的函数中,this指针是日期类对象,是第一个参数,而cout是第二个参数,所以写的话日期类对象自然就在左边,cout就在右边,
那怎么样写可以反过来呢,换句话说就是怎么样让cout作为第一个参数,日期类对象作为第二个参数?我们可以写在全局中:

void operator<<(const Date& tmp,ostream& out)
{
	out << tmp._year << "年" << tmp._month << "月" << tmp._day << "日" << endl;
}

此时,编译器又会弹出新的报错,报错指出:成员函数不可访问,它被访问限定符限制。
这个时候友元函数就派上用场了,我们只需要在类里面加上下面这句代码:

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

这句话的意思就是表示这个函数是这个类的友元函数,友元函数可以访问私有的成员。
另外,这个函数还有点瑕疵:不能连续插入

int main()
{
	Date d(2023, 4, 2);
	int a = 2;

	cout << a << endl;
	cout << d << endl;//此时就不行了
	return 0;
}

而且,再往上翻看看就会发现,标准库里的是有返回值的,那返回的是什么呢?返回的是osteam类的对象cout,所以这个函数还得再改一改:

ostream& operator<<(ostream& out, const Date& tmp)
{
	out << tmp._year << "年" << tmp._month << "月" << tmp._day << "日" << endl;
	return out;
}

另外在类里面友元函数的声明也改一下:

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

根据上面的情况,我们不难看出:
友元函数可以访问类的私有和保护成员,但不属于这个类
友元函数和普通的自定义函数在调用上没什么区别,
另外还有两点:
一个函数可以是多个类的友元函数,
友元函数不能被const修饰。

友元类

class Time
{
    friend class Date;
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;
};

友元类的声明和友元函数差不多,上面代码中Date类是Time类的友元类,所以在Date类中可以直接访问Time中的成员(同样不受访问限定符的限制),
但是!友元类不是双向的,就是说Date类中可以访问Time类成员,但是Time类中不能访问Date类成员。

内部类

class A
{
public:
	class B
	{
	public:
		B(int b_tmp = 2)
			:b(b_tmp){}//构造

		void Print(const A& a)
		{
			cout << x << endl;
			cout << a.a << endl;
		}
	private:
		int b;
	};

	A(int a_tmp = 1)
		:a(a_tmp){}//构造
private:
	static int x;
	int a;
};

int A::x = 1;

B这个类写在A这个类的里面,那么B这个类就是A的内部类,
B这个类是独立的,不属于A这个类,我们可以用一段代码来看出来为什么?

int main()
{
	cout << sizeof(A) << endl;
	return 0;
}

结果如下:
在这里插入图片描述
所以B这个类不属于A这个类里面,只不过A类的作用域是全局的,而B类的作用域是仅限于A里面,也就是说在外部不能直接用B类创建对象,必须通过域操作符在A类中找到B类。
A类实例化出来的对象不能访问B里的成员,但是B类可以访问A类的成员,换句话说:B这个类天生就是A这个类的友元类,可以看下面这段代码:

class A
{
public:
	class B
	{
	public:
		B(int b_tmp = 2)
			:b(b_tmp){}//构造

		void Print(const A& a)
		{
			cout << x << endl;
			cout << a.a << endl;
		}
	private:
		int b;
	};

	A(int a_tmp = 1)
		:a(a_tmp){}//构造
private:
	static int x;
	int a;
};

int A::x = 1;

int main()
{
	A::B a;
	a.Print(A());
	return 0;
}

匿名对象

class Sum
{
public:
    Sum()
    {
        count++;
    }

    Sum(const Sum& t) { ++count; }
    static int Getsum()
    {
        return count;
    }
private:
    static int count;
};

int Sum::count = 0;

void func()
{
    Sum s;
    Sum s1;
    Sum tmp(s1);
}

int main()
{
    func();
    //怎么知道一共创建过多少个对象
    return 0;
}

上面这个代码其实就是本文中Static成员内容中的一段代码改了一下,它是统计共创建过多少个对象,现在我们在一个函数中创建对象,然后在main函数中调用该函数,再在main函数里打印结果,怎么打印呢?
除了直接在类里面用与操作符访问,还有另一种方法,就是使用匿名对象,格式如下:

cout << Sum().Getsum << endl;//其中,Sum()相当于创建了一个匿名对象

如果你只是想访问一下类里的成员,访问完之后就不用了,后续如果还有访问成员的需求再写,此时就可以使用匿名对象,匿名对象的生命周期只在当前行,下一行就会自动调用这个类的析构函数,相当于一次性用品。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值