【C++】类与对象的细节

写在前面

本篇笔记记录类与对象的细节,这其中需要有类的基础。更多可以查看不才往期的笔记。



一、类中的const成员

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

在这里插入图片描述
还是以以前笔记中的Date类为例。

class Date
{
public:
	Date() {
		//cout << "Date()" << endl;
	}

	Date(int year, int month, int day) {
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d1) {
		//cout << "Date(const Date& d1)" << endl;
		_year = d1._year;
		_month = d1._month;
		_day = d1._day;
	}
	bool operator<(const Date& s2) {
		if (_year < s2._year) {
			return true;
		}
		else if (_year == s2._year && _month < s2._month) {
			return true;
		}
		else if (_year == s2._year && _month == s2._month && _day < s2._day) {
			return true;
		}
		return false;
	}

	bool operator>(const Date& s2) {
		if (_year > s2._year) {
			return true;
		}
		else if (_year == s2._year && _month > s2._month) {
			return true;
		}
		else if (_year == s2._year && _month == s2._month && _day > s2._day) {
			return true;
		}

		return false;
	}

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

int main() {
	Date d1(2022, 5, 4);

	Date d2(200, 1, 1);
	cout << (d1 < d2) << endl;

	return 0;
}
  • 在上述Date类的重载运算符中的<>符。在比较操作符重载中,我们不需要对类对象中的属性进行修改,所以我们可以把*this设置为const成员,避免在函数中修改对应的属性。

但是我们也知道this指针不能显示的声明与定义,所以在C++中,我们需要const修饰this指针时,我们就需要把const写在括号外面。如下代码

bool operator>(const Date& s2) const {
		if (_year > s2._year) {
			return true;
		}
		else if (_year == s2._year && _month > s2._month) {
			return true;
		}
		else if (_year == s2._year && _month == s2._month && _day > s2._day) {
			return true;
		}
		return false;
	}

在上程序中写入了const后,就把*this修饰了,在函数中,我们就不能修改对象的属性了,如下图。
在这里插入图片描述

编译器对有const修饰的成员函数的处理,如下图

在这里插入图片描述

  • 编译器会默认对this指针加入const修饰(Date* const this),防止this指针被随意更改。

二、编译器对类的隐式类型转换的优化

我们在类对象的创建时,避免不了直接使用隐式类型转换的形式来创建。就比如下面代码:

class stack {
public:
	stack(const int size)
		:_arr((int*)malloc(sizeof(int) * size))
		, _capacity(0)
		, _size(size)
	{

		cout << "stack(const int size)" << endl;

		if (_arr == nullptr) {
			perror("malloc::>");
			exit(1);
		}

		memset(_arr, 0, (sizeof(int) * size) );
	}

	stack(const stack& s1)
		:_arr((int*)malloc(sizeof(int)* s1._size))
		, _capacity(s1._capacity)
		, _size(s1._size)
	
	{
		cout << "stack(const stack& s1)" << endl;

		if (_arr == nullptr) {
			perror("malloc::>");
			exit(1);
		}

		memcpy(_arr, s1._arr, s1._capacity * sizeof(int));
	}

private:
	int* _arr;
	int _capacity;
	int _size;
};
int main() {
	stack s1(10);//调用构造 创建10个整形大小空间

	stack s2 = 5;

	return 0;
}
  • stack s2 = 5;:在程序中,其实是把整形5隐式构造成stack临时对象。后再使用拷贝构造函数初始化s2对象。如下图在这里插入图片描述
  • 这个做法和int a = 1; double d = a;一样,把整形变量a隐式类型转换成double类型后,赋值给浮点变量d

我们运行结果查看理论是否正确。

在这里插入图片描述

  • 运行结果发现,并没有拷贝构造的运行,只是直接构造。

在同一个表达式中连续的构造,编译器都会优化,提高效率

在这里插入图片描述
我们使用引用来验证隐式类型转换时存在构造临时变量+拷贝构造的存在

class stack {
public:
	stack(const int size)
		:_arr((int*)malloc(sizeof(int) * size))
		, _capacity(0)
		, _size(size)
	{

		cout << "stack(const int size)" << endl;

		if (_arr == nullptr) {
			perror("malloc::>");
			exit(1);
		}

		memset(_arr, 0, (sizeof(int) * size) );
	}

	stack(const stack& s1) 
		:_arr((int*)malloc(sizeof(int)* s1._size))
		, _capacity(s1._capacity)
		, _size(s1._size)
	
	{
		cout << "stack(const stack& s1)" << endl;

		if (_arr == nullptr) {
			perror("malloc::>");
			exit(1);
		}

		memcpy(_arr, s1._arr, s1._capacity * sizeof(int));
	}

private:
	int* _arr;
	int _capacity;
	int _size;

};
int main() {
	stack s1(10);

	stack& s2 = 5;//使用stack类引用常量5

	return 0;
}

程序运行结果:
在这里插入图片描述

  • 报错内容是: 无法从“int”转换为“stack &

拷贝构造函数与运算符重载笔记中,不才证明过临时变量具有常性,在没有const修饰的引用s2中,就出现了权限放大的问题,我们加上const后,查看程序运行结果:
在这里插入图片描述

  • 程序运行成功
  • 虽然引用s2是一个非法引用,但是说明了,在类的隐式类型转换的语法层面中,确实是会先构造一个临时对象,然后再用临时对象拷贝构造s2
  • 只是编译器会对其进行优化优化为直接构造

在C++中,也有一个关键字可以显示阻止隐式类型转换发生

explicit关键字,explicit修饰构造函数,将会禁止构造函数的隐式转换。(具体使用细节不才在智能指针中进行详细解读)

没有使用explicit修饰,具有类型转换作用
explicit修饰构造函数,禁止类型转换—explicit去掉之后,代码可以通过编译

在这里插入图片描述


class stack {
public:
	explicit stack(const int size) //加上explicit后,就无法进行隐式类型转换
		:_arr((int*)malloc(sizeof(int) * size))
		, _capacity(0)
		, _size(size)
	{

		cout << "stack(const int size)" << endl;

		if (_arr == nullptr) {
			perror("malloc::>");
			exit(1);
		}

		memset(_arr, 0, (sizeof(int) * size) );
	}

	stack(const stack& s1) 
		:_arr((int*)malloc(sizeof(int)* s1._size))
		, _capacity(s1._capacity)
		, _size(s1._size)
	
	{
		cout << "stack(const stack& s1)" << endl;

		if (_arr == nullptr) {
			perror("malloc::>");
			exit(1);
		}

		memcpy(_arr, s1._arr, s1._capacity * sizeof(int));
	}

private:
	int* _arr;
	int _capacity;
	int _size;

};
int main() {
	stack s1(10);

	const stack& s2 = 5;//使用stack类引用常量5

	stack s3 = 10;
	return 0;
}

程序运行结果:
在这里插入图片描述


三、静态属性和方法(static)

概念

声明为 static的类成员称为类的静态成员,用 static 修饰的成员变量,称之为静态成员变量;用 static 修饰的成员函数,称之为静态成员函数。因为静态成员不属于对象本身的属性静态成员变量一定要在类外进行初始化定义,不能再构造器中进行初始化定义。

静态成员方法/属性 可以被该类的所有对象使用,不属于任何对象,而且静态成员方法也属于该类成员,可以正常访问私有的静态属性。

在这里插入图片描述
静态成员变量的声明和定义

namespace bucai{
	class A {
	public:
		A() { ++_count; }
		A(const A& a) :_a1(a._a1){ ++_count; }
		~A() { --_count; }
		//void operator=(const A a) { ++_count; }
	
	private:
		//成员变量 -- 属于每一个类对象,存储在类对象中。(即在栈空间)
		int _a1 = 1;
		int _a2 = 2;
	
		//静态成员变量 -- 属于类,是每一个类对象共享的属性,存储在静态区。
		static int _count;//静态成员变量的声明
	};
	
	int A::_count = 0;//虽然我们在类中定义了静态成员变量是私有的,但是我们也一定要定义.
						//所以在C++程序设定的时候就设定了静态属性的定义是可以直接在类外。
}

特性

一、静态成员为所以类对象所共享,不属于某个具体的对象
鉴于此,我们看看以下代码的运行结果:

#include <iostream>
using namespace std;
class Test
{
private:
	static int _n;
};
int main()
{
	cout << sizeof(Test) << endl;
	return 0;
}

在这里插入图片描述

  • 结果计算Test类的大小为1,因为静态成员_n是存储在静态区的,属于整个类,也属于类的所有对象。所以计算类的大小或是类对象的大小时,静态成员并不计入其总大小之和。

二、静态成员变量必须在类外定义,定义时不添加static关键字

namespace bucai {
	class Test
	{
	public:
		static void Fun()
		{
			cout << _a << endl; //error不能访问非静态成员
			cout << _n << endl; //correct
		}
	private:
		int _a; //非静态成员
		static int _n; //静态成员
	};
	int Test::_n = 0;
}

注意: 这里静态成员变量_n虽然是私有,但是我们在类外突破类域直接对其进行了访问这是一个特例,不受访问限定符的限制,否则就没办法对静态成员变量进行定义和初始化了。

  • 静态成员方法中是不能使用非静态成员的属性与方法的!

三、静态成员函数没有隐藏的this指针,不能访问任何非静态成员

class Test
{
public:
	static void Fun()
	{
		cout << _a << endl; //error不能访问非静态成员
		cout << _n << endl; //correct
	}
private:
	int _a; //非静态成员
	static int _n; //静态成员
};

小贴士: 含有静态成员变量的类,一般含有一个静态成员函数,用于访问静态成员变量。

四、访问静态成员变量的方法
1.当静态成员变量为公有时,有以下几种访问方式:

#include <iostream>
using namespace std;
class Test
{
public:
	static int _n; //公有
};
// 静态成员变量的定义初始化
int Test::_n = 0;
int main()
{
	Test test;
	cout << test._n << endl; //1.通过类对象突破类域进行访问
	cout << Test()._n << endl; //2.通过匿名对象突破类域进行访问
	cout << Test::_n << endl; //3.通过类名突破类域进行访问
	return 0;
}

2.当静态成员变量为私有时,需要提供一个公有的静态方法来辅助,有以下几种访问方式:

#include <iostream>
using namespace std;
class Test
{
public:
	static int GetN() //公有的静态方法
	{
		return _n;
	}
private:
	static int _n;
};
// 静态成员变量的定义初始化
int Test::_n = 0;
int main()
{
	Test test;
	cout << test.GetN() << endl; //1.通过对象调用成员函数进行访问
	cout << Test().GetN() << endl; //2.通过匿名对象调用成员函数进行访问
	cout << Test::GetN() << endl; //3.通过类名调用静态成员函数进行访问
	return 0;
}

五、静态成员和类的普通成员一样,也有public、private和protected这三种访问级别
 所以当静态成员变量设置为private时,尽管我们突破了类域,也不能对其进行访问。

注意区分两个问题:
 1、静态成员函数可以调用非静态成员函数吗?
 2、非静态成员函数可以调用静态成员函数吗?

问题1: 不可以。因为非静态成员函数的第一个形参默认为this指针,而静态成员函数中没有this指针,故静态成员函数不可调用非静态成员函数。
问题2: 可以。因为静态成员函数和非静态成员函数都在类中,在类中不受访问限定符的限制。

C++11中成员初始化的新玩法

C++11支持非静态成员变量在声明时进行初始化赋值,但是要注意这里不是初始化,这里是给声明的成员变量一个缺省值。

class A
{
public:
	void Print()
	{
		cout << _a << endl;
		cout << _p << endl;
	}
private:
	// 非静态成员变量,可以在成员声明时给缺省值。
	int _a = 10; 
	int* _p = (int*)malloc(4);
	static int _n; //静态成员变量不能给缺省值
};

初始化列表是成员变量定义初始化的地方,你若是给定了值,就用你所给的值对成员变量进行初始化,你若没有给定值,则用缺省值进行初始化,若是没有缺省值,则内置类型的成员就是随机值。


四、库实现的流插入和流提取

我们都知道C++的<<>>很神奇,因为它们能够自动识别输入和输出变量的类型,我们使用它们时不必像C语言一样增加数据格式的控制。实际上,这一点也不神奇,内置类型的对象能直接使用coutcin输入输出,是因为库里面已经将它们的<<>>重载好了,<<>>能够自动识别类型,是因为它们之间构成了函数重载。

其中,cin在头文件<istream>
在这里插入图片描述

其中,cout在头文件<ostream>
在这里插入图片描述

在日常使用coutcin我们只需要调用iostrem头文件即可。 因为<iostream>头文件已经继承了<istream><ostream>
在这里插入图片描述

  • 后期不才会单独出一篇C++的io流文章,感兴趣的小伙伴别错过咯。

五、友元

5.1、友元函数

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

对于自定义类型中,我们想要流插入或流提取使用库自带<<>>无法使用的。

自定义类中,我们就需要重载operator<<,但是我们发现没办法将其重载为成员函数,因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置this指针默认是第一个参数,即左操作数,但是实际使用中cout需要是第一个形参对象才能正常使用。
在这里插入图片描述
我们直接在类中,重载operator<<

class Date
{
public:
	Date(int year = 0, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	// <<运算符重载
	ostream& operator<<(ostream& out)//形参列表隐藏了this指针
	{
		out << _year << "-" << _month << "-" << _day << endl;
		return out;
	}

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

int main() {
	Date d1(2025, 1, 1);
	//cout << d1; //err无法使用  会自动转换为:> cout.operator<<(d1)。 
	//                          在std中并没有Date自定义类型。

	d1 << cout; //转换为:> d1.operator(cout);
	return 0;
}
  • 虽然这样也是可以完成我们的打印任务,但是并不符合我们的日常编写。

所以我们要将operator<<重载为全局函数,但是这样的话,又会导致类外没办法访问成员,那么这里就需要友元来解决。(operator>>同理)

class Date
{
	// 友元函数的声明
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
public:
	Date(int year = 0, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};
// <<运算符重载
ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "-" << d._month << "-" << d._day<< endl;
	return out;
}
// >>运算符重载
istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}
int main() {
	Date d1(2025, 1, 1);
	cout << d1 << endl;  
	return 0;
}

友元函数说明:

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

5.2、友元类

友元类的所有成员函数都可以是另一个类的友元函数都可以访问另一个类中非公有成员
在这里插入图片描述

class A
{
	// 声明B是A的友元类
	friend class B;
public:
	A(int n = 0)
		:_n(n)
	{}
private:
	int _n;
};
class B
{
public:
	void Test(A& a)
	{
		// B类可以直接访问A类中的私有成员变量
		cout << a._n << endl;
	}
};

友元类说明:
1、友元关系是单向的,不具有交换性。
 例如上述代码中,B是A的友元,所以在B类中可以直接访问A类的私有成员变量,但是在A类中不能访问B类中的私有成员变量。
2、友元关系不能传递。
 如果A是B的友元,B是C的友元,不能推出A是C的友元。


六、内部类

概念

如果一个类定义在另一个类的内部,则这个类被称为内部类。

注意:
 1. 此时的内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象区调用内部类
 2. 外部类对内部类没有任何优越的访问权限。
 3. 内部类就是外部类的友元类,即内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元

特性

  1. 内部类可以定义在外部类的publicprivate以及protected这三个区域中的任一区域。
  2. 内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名
  3. 外部类的大小与内部类的大小无关
#include <iostream>
using namespace std;
class A //外部类
{
public:
	class B //内部类
	{
	private:
		int _b;
	};
private:
	int _a;
};
int main()
{
	cout << sizeof(A) << endl; //外部类的大小
	return 0;
}

运行结果如下图:

在这里插入图片描述
这里外部类A的大小为4,与内部类的大小无关。


以上就是本章所有内容。若有勘误请私信不才。万分感激💖💖 如果对大家有用的话,就请多多为我点赞收藏吧~~~💖💖
请添加图片描述

ps:表情包来自网络,侵删🌹

评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值