类和对象下

目录

const成员

取地址及const取地址操作符重载

 再谈构造函数

构造函数体赋值

初始化列表(重点)

explicit关键字

static成员

友元

友元函数

友元类

内部类

 匿名对象

拷贝对象时的一些编译器优化

再次理解类和对象


const成员

const 修饰的 成员函数 称之为 const 成员函数 const 修饰类成员函数,实际修饰该成员函数 隐含的 this 指针 ,表明在该成员函数中 不能对类的任何成员进行修改
看看这段代码,我们如果去掉consth会有什么结果呢?
class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << "Print()" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
	void Print() const
	{
		cout << "Print()const" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};
void Test()
{
	Date d1(2022, 1, 13);
	d1.Print();
	const Date d2(2022, 1, 13);
	d2.Print();
}

 不难发现对于d2来说,这就是一个权限放大的问题:

因为d2是const修饰的,而隐含的this指针的类型是Date* this,这就是之前所说的权限放大了。想要解决这个问题,我们就可以让this指针也带上const即可,这样权限平移就没有问题。这里const修饰的就是this指针。

取地址及const取地址操作符重载

这两个默认成员函数一般不用重新定义 ,编译器默认会生成。

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需 要重载,比如想让别人获取到指定的内容!
这个取地址操作符确实使用的频率很少,只需要大概知道怎么使用即可
class Date
{
public:
	Date* operator&()
	{
		return this;
	}
	const Date* operator&()const
	{
		return this;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};

看看结果:

 再谈构造函数

构造函数体赋值

在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。
class Date
{
public:
Date(int year, int month, int day)
 {
     _year = year;
     _month = month;
     _day = day;
 }
private:
int _year;
int _month;
int _day;
};
虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量 的初始化,构造函数体中的语句只能将其称为赋初值 ,而不能称作初始化。因为 初始化只能初始 化一次,而构造函数体内可以多次赋值

初始化列表(重点)

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

注意:
1. 每个成员变量在初始化列表中 只能出现一次 ( 初始化只能初始化一次 )
2. 类中包含以下成员,必须放在初始化列表位置进行初始化:
引用成员变量
const成员变量
自定义类型成员(且该类没有默认构造函数时)
3.尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量, 一定会先使用初始化列表初始化。
class Time
{
public:
	Time(int hour = 0)
		:_hour(hour)
	{
		cout << "Time()" << endl;
	}
private:
	int _hour;
};
class Date
{
public:
	Date(int day)
	{}
private:
	int _day;
	Time _t;
};
int main()
{
	Date d(1);
}

4.成员变量 在类中 声明次序 就是其在初始化列表中的 初始化顺序 ,与其在初始化列表中的先后 次序无关
class A {
public:
    A(int a)
       :_a1(a)
       ,_a2(_a1)
   {}
    
    void Print() {
        cout<<_a1<<" "<<_a2<<endl;
   }
private:
    int _a2;
    int _a1;
};
int main() {
    A aa(1);
    aa.Print();
}

看看结果,就会发现确实是按照变量声明的顺序进行初始化的。

 _a2是最初声明的,而_a1是后声明的,还是个随机值,所以最终_a2就是随机值。

explicit关键字

构造函数不仅可以构造与初始化对象, 对于单个参数或者除第一个参数无默认值其余均有默认值 的构造函数,还具有类型转换的作用
我们先看看这段代码:
class Date
{
public:
	Date(int year)
		:_year(year)
	{}
	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 = 2021;
	return 0;
}

 为什么这里的d1能直接赋值整形?

 

 通过explicit来修饰之后,我们就没有办法整形进行赋值,因为不允许隐式转换。

用explicit修饰构造函数,将会禁止构造函数的隐式转换

static成员

概念
声明为 static 的类成员 称为 类的静态成员 ,用 static 修饰的 成员变量 ,称之为 静态成员变量 ;用 static 修饰 成员函数 ,称之为 静态成员函数 静态成员变量一定要在类外进行初始化
有一个问题:实现一个类,计算程序中创建出了多少个类对象。
我们可以通过static来修饰变量来实现
class A
{
public:
	//构造
	A()
	{
		++_count;
	}
	//拷贝构造
	A(const A& a)
	{
		++_count;
	}
	~A()
	{
		--_count;
	}
	static int GetACount()
	{
		return _count;
	}
private:
	static int _count;
};
int A::_count = 0;

int main()
{
	cout << A::GetACount() << endl; 
	A a1, a2; 
	A a3(a1);
	cout << A::GetACount() << endl;
	return 0;
}

 通过结果不难了解到static的意义。

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

友元

友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以 友元不宜多用。
友元分为: 友元函数 友元类

友元函数

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

class Date
{
public:
	Date(int year, int month, int day)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
	// d1 << cout; -> d1.operator<<(&d1, cout); 不符合常规调用
// 因为成员函数第一个参数一定是隐藏的this,所以d1必须放在<<的左侧
	ostream& operator<<(ostream& _cout)
	{
		_cout << _year << "-" << _month << "-" << _day << endl;
		return _cout;
	}
private:
	int _year;
	int _month;
	int _day;
};

我们发现这和我们的常用调用不符合,所以这里就需要友元的知识点了

友元函数 可以 直接访问 类的 私有 成员,它是 定义在类外部 普通函数 ,不属于任何类,但需要在 类的内部声明,声明时需要加friend 关键字。
class Date
{
	friend ostream& operator<<(ostream& _out, const Date& d);
	friend istream& operator>>(istream& _cin, Date& d);

public:
	Date(int year = 1, int month =1, int day =1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
	// d1 << cout; -> d1.operator<<(&d1, cout); 不符合常规调用
// 因为成员函数第一个参数一定是隐藏的this,所以d1必须放在<<的左侧
	/*ostream& operator<<(ostream& _cout)
	{
		_cout << _year << "-" << _month << "-" << _day << endl;
		return _cout;
	}*/
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& _cin, Date& d)
{
	_cin >> d._year;
	_cin >> d._month;
	_cin >> d._day;
	return _cin;
}
int main()
{
	Date d1;
	cin >> d1;
	cout << d1;

	return 0;
}

 总结:

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

友元类

1.友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
友元关系是单向的,不具有交换性。
2.比如上述 Time 类和 Date 类,在 Time 类中声明 Date 类为其友元类,那么可以在 Date 类中直接
访问 Time 类的私有成员变量,但想在 Time 类中访问 Date 类中私有的成员变量则不行。
3.友元关系不能传递 如果C B 的友元, B A 的友元,则不能说明 C A 的友元。
4.友元关系不能继承,这里就不多介绍
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;
};

这里就很能体现出了友元的弊端,就是封装性不能保证。

内部类

概念: 如果一个类定义在另一个类的内部,这个内部类就叫做内部类 。内部类是一个独立的类, 它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越 的访问权限。
注意: 内部类就是外部类的友元类 ,参见友元类的定义,内部类可以通过外部类的对象参数来访 问外部类中的所有成员。但是外部类不是内部类的友元。
特性:
1. 内部类可以定义在外部类的 public protected private 都是可以的。
2. 注意内部类可以直接访问外部类中的 static 成员,不需要外部类的对象 / 类名。
3. sizeof( 外部类 )= 外部类,和内部类没有任何关系。
class A {
private:
	static int k;
	int h;
public:
	class B // B天生就是A的友元
	{
	public:
		void fo(const A& a)
		{
			cout << k << endl;//OK
			cout << a.h << endl;//OK
		}
	};
};
int A::k = 1;
int main()
{
	//这里B在A的类域,所以要加类域限定符
	A::B b;
	b.fo(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 aa2(10);
	Solution().Sum_Solution(10);
	return 0;
}

在我们只需要使用一次时,匿名结构体看起来会比较简洁。

拷贝对象时的一些编译器优化

在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝,这个在一些场景下还 是非常有用的。
class A {
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}
	A(const A& aa)
		:_a(aa._a)
	{
		cout << "A(const A& aa)" << endl;
	}
	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a = aa._a;
		}
		return *this;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};
void f1(A aa)
{}
A f2()
{
	A aa;
	return aa;
}
int main()
{
	// 传值传参
	A aa1;
	f1(aa1);
	cout << endl;
	// 传值返回
	f2();
	cout << endl;
	// 隐式类型,连续构造+拷贝构造->优化为直接构造
	f1(1);
	// 一个表达式中,连续构造+拷贝构造->优化为一个构造
	f1(A(2));
	cout << endl;
	// 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造
	A aa2 = f2();
	cout << endl;
	// 一个表达式中,连续拷贝构造+赋值重载->无法优化
	aa1 = f2();
	cout << endl;
	return 0;
}

就拿其中一个进行解释:

 

 看看结果就知道

 我们发现最后它只调用了一次构造函数,但是对于连续拷贝构造加赋值重载就不能优化了,看最后一个的结果我们就知道编译器并没有进行优化。

再次理解类和对象

类是对某一类实体 ( 对象 ) 来进行描述的,描述该对象具有那 些属性,那些方法,描述完成后就形成了一种新的自定义类型,才用该自定义类型就可以实例化 具体的对象
就好像我们日常生活中的外卖系统一样,有多个对象,例如送外卖的人就是一个对象,客户也是一个对象,还有与客户对接的平台也是一个对象,这样简单的几个对象就组成了一个系统,最后再解释一下C++和C不同的一个重要的地方就是在于类和对象这样,C++是面向对象编程的,而C语言则是面向过程编程的。同时C++为了满足一些类和对象的使用,增加了一些新的语法,而这些语法大多都是因为类和对象而产生。这也说明了类和对象的重要性。

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值