深入计算机语言之C++:类与对象(下)

 🔑🔑博客主页:阿客不是客

🍓🍓系列专栏:从C语言到C++语言的渐深学习

欢迎来到泊舟小课堂

😘博客制作不易欢迎各位👍点赞+⭐收藏+➕关注

一、初始化列表

1.1 引入

我们知道,常量必须在定义时初始化,不初始化就会出问题,因为常量只有一次初始化的机会,就是在定义的时候。

那我们来思考一个问题,const 修饰的常量应该在哪进行初始化?

private:
    /* 声明部分 */
	int _year;
	int _month;   
	int _day;
 
	const int _N = 10;   // 这一块是声明,我们不应该在这初始化。
};

在定义里面?初始化要在空间上给值,你这里有空间吗?你没空间啊!

基于这种原因,C++ 就搞出了一个叫做 初始化列表 (Initializer List) 的东西。

1.2 定义

我们之前学习创建对象时,编译器通过调用构造函数,给对象赋初值。

初始化列表作用与构造函数类似:它是在构造函数中以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。

下面我们还是以一个日期类来示范:

class Date
{
public:
	Date(int year, int month, int day)
		: _year(year)
		, _month(month)
		, _day(day)
	{
	 //...
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d(2024,10,20);
	d.Print();
	return 0;
}

2.3 注意事项

 每个成员变量再初始化列表中只能出现一次,即 初始化只能初始化一次。

 必须在定义时就初始化的成员变量,要在初始化列表初始化。

类中包含以下成员,必须放在初始化列表位置进行初始化:

  1. const成员变量                       const int _N;
  2. 引用成员变量                         int& ref;
  3. 没有默认构造函数的自定义类型成员变量     A _aa;  

③  尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。

内置类型的成员,在函数体和初始化列表初始化都可以,

自定义类型的成员,建议在初始化列表初始化,这样更高效。

 成员变量在类中的声明顺序就是在初始化列表中的初始化顺序,与其在初始化列表中出现的顺序无关。

class A
{
public:
	A(int a)
		: _a1(a)
		, _a2(_a1)   //先进行初始化,但此时_a1未进行初始化是随机值
	{}
	void Print()
	{
		cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a2;
	int _a1;
};


int main()
{
	A aa(1);
	aa.Print();
	return 0;
}

img

但是我们知道初始化只能初始化一次。所以如果使用初始化列表进行初始化,则在定义中给的缺省值在进行初始化时无效在调用的时候有效

 

二、类型转换

在C语言中,对于隐式类型转换:

int main(void)
{
	double d = 1.1;
	int i = d;
 
	return 0;
}

2.1 内置类型

在发生隐式类型转换时,如果都是内置类型就会先开辟一个临时变量,再将右操作数强制类型转换为左操作数的类型,最后用这个临时变量对左操作数进行赋值。注意:这个临时变量具有常性,不可修改。

img

int main()
{
	double j = 1.1;
	int i = j;//隐式类型转换
	int& a = j;//error
	const int& b = j;//ok
	return 0;
}

因为临时变量具有常性,所以无法被修改,而引用则是可以进行修改。如果赋值给普通引用就会造成权限的放大,所以只能用常引用。

2.2 自定义类型

class Date {
public:
	Date(int year)
		: _year(year)
	{
		;
	}
 
private:
	int _year;
};
 
int main(void)
{
	Date d1(2022);
	Date d2 = 2022;    // 隐式类型转换
 
	return 0;
}

❓ 这里是隐式类型的转换,为什么支持一个整型转换成日期类相关的类型呢?

整型和日期类本来是没有关系的,但是你支持一个单参数的构造函数后,整型就可以去构造一个日期类的对象,这个日期类的对象自然可以赋值给他了。

本来用 2022 构造成一个临时对象 Date(2022) ,在用这个对象拷贝构造 d2,但是 C++ 编译器在连续的一个过程中,编译器为了提高效率,多个构造会被优化,合二为一。

 所以这里被优化成,直接就是一个构造了。

如果你不想让这种 "转换" 发生,C++ 提供了一种关键字 —— explicit 

2.3 explicit 关键字

构造函数不仅可以构造和初始化对象,对于单个参数的构造函数,还具有类型转换的作用,用 explicit 关键字修饰构造函数,可以禁止单参构造函数的隐式类型转换。

class Date {
public:
	explicit Date(int year)
		: _year(year)
	{
		;
	}
 
private:
	int _year;
};

三、类的静态成员(static)

3.1 定义

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数

class A
{
public:
    static int Print()//静态成员函数
	{
		cout << "Print()" << endl;
	}
private:
	static int _a;//静态成员变量
};

静态成员变量属于整个类,所有对象,生命周期在整个程序运行期间。

3.2 静态成员函数的特点

  1. 静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区。
  2. ⽤static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针。
  3. 静态成员也是类的成员,受public、protected、private 访问限定符的限制。

3.3 静态成员函数的访问 

突破类域就可以访问静态成员,可以通过类名::静态成员 或者 对象.静态成员 来访问静态成员变量和静态成员函数。

3.3.1 静态成员函数的访问

class A
{
public:
	static void Print()//静态成员函数
	{
		cout << "Print()" << endl;
	}
private:
	static int _a;//静态成员变量
};
int main()
{
	A a;
	a.Print();
	A::Print();
	return 0;
}

3.3.2 静态成员的访问

💬 如果它是公有的,我们就可以在类外对它进行访问:

class A
{
public:

	A(int a = 0)
		:_a(a)
	{
		_sCount++;
	}

	int  GetCount()
	{
		return _sCount;
	}

//private:
	int _a;
	static int _sCount;
};

int A::_sCount = 0;//类外定义


int main(void)
{
	A a1;
	A a2 = 1;

	cout << A::_sCount << endl;  // 使用类域对它进行访问

	cout << a1._sCount << endl;

	return 0;
}

但是如果它是私有的,我们可以提供一个公有的成员函数。我们写一个公有的 GetCount 成员函数,让它返回 _sCount 的值,这样我们就可以在类外调用该函数,就可以访问到它了。

注意:

  1. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明。
  2. 静态成员函数中可以访问其他的静态成员,但是不能访问⾮静态的,因为没有this指针。
  3. ⾮静态的成员函数,可以访问任意的静态成员变量和静态成员函数。
  4. 静态成员变量不能在声明位置给缺省值初始化,因为缺省值是个构造函数初始化列表的,静态成员变量不属于某个对象,不⾛构造函数初始化列表。

四、友元

4.1 友元的定义

友元分为:友元函数和友元类。

 友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,能不用就不用!

                                                                                  "友元就像是黄牛,破坏了管理规则 "

4.2 友元函数

有时在类外我们需要访问类中的数据,但由于访问限定符的限制并不能访问私有成员。这时如果一定要访问的话就需要借助我们的友元函数,它的用法十分简单,只用在类中加入 friend + 函数声明

class Date
{
	friend void Print(const Date& d);//友元函数
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
void Print(const Date&d)
{
	cout << d._year << "-" << d._month << "-" << d._day << endl;
}

📌 注意事项:

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

4.3 友元类

除了友元函数外,还有一种友元类。友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。

friend class 类名;

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;
};

 📌 注意事项:

  • 友元关系是单向的,不具有交换性。
  • 友元关系不具有传递性(朋友的朋友不一定是朋友)。
    • 如果 B 是 A 的友元,C 是 B 的友元,则不能说明 C 是 A 的友元。

五、内部类

5.1 定义

如果在 A 类中定义 B 类,我们称 B 是 A 的内部类。 

class A 
{
public:
	class B
    {
		;
	};
private:
    //......
};

5.2 内部类的特性

内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限

并且内部类就是外部类的友元类。内部类可以通过外部类的对象参数来访问外部类中的所有成员,像极了殖民行为。

class A {
private:
	static int _s_a1;
	int _a2;
public:
	class B {   // B天生就是A的友元
	public:
		void foo(const A& a) {
			cout << _s_a1 << endl;   // ✅ 
			cout << a._a2 << endl;   // ✅ 
		}
	private:
		int _b1;
	};
};

5.3 有关内部类的内存

❓ 我们用 sizeof 计算 A 类的大小,得到的结果会是什么?

#include <iostream>
using namespace std;
 
class A
{
private:
	int _a1;
 
public:
	class B
    {
	private:
		int _b1;
	};
};
 
int main(void)
{
	cout << "A的大小为: " << sizeof(A) << endl;
 
	return 0;
}

这是为什么呢?

① 内部类 B 和在全局定义是基本一样的,它只是受外部类 A 类域的限制,定义在 A 的类域中。

② 内部类 B 天生就是外部类 A 的友元,也就是 B 中可以访问 A 的私有,A 不能访问 B 的私有(或保护)。所以,A 类型的对象里没有 B,跟 B 没什么关系,计算 sizeof 当然也不会带上B。

六、匿名对象

匿名对象与C语言中的匿名结构体类似,只有类名,作用域只在匿名对象声明的一行。

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 aa1();

    A();
    A(1);

    return 0;
}

七、编译器优化

下面我们将介绍一些编译器对自定义类型常见的优化,我们还是以日期类举例:

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		cout << "Date" << endl;
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d)
	{
		cout << "Date(const Date& d)" << endl;
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
	~Date()
	{
		cout << "~Date()" << endl;
		_year = _month = _day = 0;
	}
private:
	int _year;
	int _month;
	int _day;
};

Date func2()
{
	Date aa;
	return aa;
}
  • 直接构造(Date 临时变量(2024))+拷贝构造(Date d = 临时变量)=>直接构造(Date d(2024))

  • 直接构造(Date aa)+拷贝构造(Date 临时变量 = aa)+拷贝构造(Date d = 临时变量)=>直接构造(Date d(aa._year, aa._month, aa._day)

  • 直接构造(Date d)+直接构造(Date aa)+拷贝构造(Date 临时变量 = aa)+赋值重载(d = 临时变量)=>直接构造(Date aa)+直接构造Date d(aa._year, aa._month, aa._day)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值