【初识C++】2.3类与对象(下)

1.const 修饰

1.1const 的三种基本修饰方法

const 是C++中的一个关键字,通常我们使用它来保护我们创建的对象
以下是cosnt添加的三种方式

const Date* p1//1
Date const *p1//2
Date* const p1//3

1,2 中 const在之前,const保护的是p也就是p指针指向的内容
即Date p1 中的成员无法被修改
3 中 const 在* 之后,const保护的是*p这个指针,此指针无法被修改
即Date p3 的地址无法被修改

1.2const 修饰this指针

bool operator (const Date& d)
//这个函数的完整形式应该是
bool operator(Date* this, const Date&d)
//但是在实际使用过程中this指针被省略掉了我们该如何保护this所指向的对象呢

我们可以采取以下定义方式来保护*this

bool operator(const Date& d) const;//声明
bool operator(const Date& d) const {}//定义

这样我们就可以有效的保护我们的对象,防止我们在编写代码的过程中错误的将不能改变的对象改变,使得程序错误运行
例如:

bool operator(const Date& d) const
{
    return _year == d._year
        && _month == d._month
        && _day = d._day;
}
注意此时我们最后一行判断相等符号打成了赋值符号,导致最后一行恒为真
当两个日期year 和 month 相同但是 day不同时,程序出错
此时若没有const 程序不会报错,我们将很难发现错误在哪
所以const可以帮我们减少编写错误,减少bug的出现

1.3 const 和 权限缩小

成员函数在调用的时候要进行权限的对比,权限不变或者缩小才可以调用

void Print() const;//成员函数  
Date d1;
d1.Print();
本来d1是可读可写的,但是Print()函数中只可以读,不可修改,权限缩小可以这样调用


void Print();
Date d2;
d2.Print();
d2可读可写,Print()函数也是可读可写,权限相同可以调用


void Print();
const Date d3;
d3.Print();
d3是初始化后无法修改,只可以读,而Print()函数允许对d3进行修改和读取,属于权限的放大,
不能这样子调用

Print();
const Date d4;
d4.Print();
d4和Print()在d4后都无法对对象内容进行修改,权限相同可以这样子调用

注意:const成员函数内不可以调用其他非const成员函, 但是非const成员函数内可以调用const成员函数,其本质是this指针的传递

void Fun1()
{
  Print();
 }
 void Print() const;
 Date d1;
 d1.Fun1();
 //此时此调用可以看作为Fun1(&d1)此时传递给Fun1()函数的是d1的地址
 //而Fun1()调用Print()时调用传递的也是this指针,因为*this可读可写
 //传递属于权限的缩小,所以可以调用

1.4最后两个默认构造函数

上一节我们讲了四个默认构造函数,剩下两个默认构造函数如下

Date* operator&();
const Date* operator&() const;

这两个函数仅仅了解就足够了,因为它们基本没有重载的价值
不需要我们自己写,编译器默认生成的就够用了

Date* operator&()
{
   reuturn nullptr;
}
//这样我们就没有办法取得对象的地址了,你不想让别人获取对象的地址
//基本不会用到

2.构造函数进阶

2.1构造函数初始化方式

1.构造函数函数体内初始化

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

2.初始化列表初始化
初始化列表,以一个冒号开始,以逗号分隔的数据成员列表

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

2.2初始化列表的优势

优势1:
类中如果包含
1.引用成员变量 2. const成员变量 3.自定义类型成员(该类没有默认构造函数)时
必须使用初始化列表初识化,不能使用函数体内初始化

引用成员变量和const修饰的对象必须在定义的时候初始化

const int i;//我们如果不能在这个地方初始化,因const无法改变所以以后就没有初始化的机会了
Date& d;//引用定义时规定必须要马上确定其引用对象

我们可以认为初始化列表阶段就是对象的定义阶段。而函数体内可以当作普通的赋值。

优势2:自定义类型初始化
当我们定义的类型中包含其他自定义类型时,在我们创建对象的过程中会直接调用成员自定义类型的默认构造函数。但如果其没有默认构造函数,则程序会出错,如下

class A
{
public:
	A(int x)
	{
		_a = x;
	}
private:
	int _a;
};

class Time
{
public:
	Time(int hour, int min, int second)
	{
	    _hour = hour;
	    _min = min;
	    _second = second;
	}
private:
	int _hour;
	int _min;
	int _second;
	A a1;
};

我们发现在使用函数体内初始化,我们无法访问a1并对其赋值。并且在对象创建的过程中A也无法初始化。此时我们可以使用初始化列表对其进行初始化

class A
{
public:
	A(int x)
	{
		_a = x;
	}
private:
	int _a;
};

class Time
{
public:
	Time(int hour, int min, int second)
	:_hour(hour)
	,_min(min)
	,_second(second)
	,a1(1)
	{}
private:
	int _hour;
	int _min;
	int _second;
	A _a1;
};

两种方式初始化都可以,甚至可以混着用。但是初始化列表更加强大,比较推荐

2.3初始化列表的赋值顺序

初始化列表的赋值顺序是跟类定义的成员声明顺序是一样的,并非在赋值函数中,列表写在前面就先赋值,而是根据成员的声明顺序

建议:养成好的习惯,类中成员变量声明的顺序和初始化的顺序保持一致

3.成员初始化新玩法

3.1单参数构造函数初始化

int i = 0;
double d = i;
此处发生了隐式类型转换
编译器先创建了一个临时对象然后赋值给d

而单参数的构造函数支持隐式类型转换

class A
{
public:
  A(int a)
  :_a(a)
  {}
private:
  int _a;
}

int main()
{
   A aa1 = 1;
}

这一串代码是什么意思呢,int类型的1和A类型的aa1看上去毫不相关
早期的编译器:创建A tmp(1) + A aa1(tmp) 它会创建一个临时变量,然后调用拷贝构造
现在的编译器:进行优化 直接调用构造函数 等价于 A aa1(1)

3.2 explicit 关键字

可以阻止上述初始化方式

//在构造函数前面添加
  explicit A(int a)
   :_a(a)
  {}
  int main()
  {
     A aa1 = 1;
  }
  //此时这样创建对象是不被允许的
  //explicit可以阻止类型隐式转换,或者直接优化

3.3匿名对象

使用场景:当一个类中莫个成员函数与对象没有什么关系,你仅仅是想要调用此成员函数
就可以通过创建一个匿名对象调用

//普通的创建对象调用
A aa1;
aa1.fun();
//创建匿名对象调用
A().fun();

作用:更加方便,提高开发速度。匿名对象在同一行创建和析构

4.友元

4.1友元函数的作用

当我们在构造Date类的运算符重载<< 和 >> 时

void operator<<(ostream& out) const;//声明
void Date::operator<<(ostream& out) const
{
	out << _year << "-" << _month << "-" << _day << endl;
}//定义

但是当我们测试时可以发现,因为成员函数第一个形参为this指针,和我们的out函数会抢占位子,实际调用必须如下

Date d1;
d1<<cout;

这样对我们代码的可读性造成了很大的影响
所以C++中定义了友元函数来解决this指针占位的问题

4.2友元函数的实现

class Date
{
     //友元函数的声明,前面加上一个friend
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
public:              
	int MonthDayNum(int year, int month) const;
	Date(int year = 0, int month = 1, int day = 1);
	void Print();
	//析构和拷贝使用编译器默认生成的就足够了
	//我们只需要完成函数重载即可
	Date& operator+=(int day_num);
	Date operator+(int day_num) const;
	Date& operator-=(int day_num);
	Date operator-(int day_num) const;
	//cin 和 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& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}
//因为要确保函数的连续输出和输入,可以将返回值改一下
//测试
void Date_in_out_test()
{
	Date d1(2000, 2, 19);
	Date d2;
	cin >> d2;
	cout << d2;
}

4.3友元类

在现实生活中,我们经常会遇到两个类互相调用的场景,我们可以定义友元类使不同类型之间相互调用,例如:

class Time
{
public:
    //在Time中调用Date
	void SetDate(Date& d)
	{
		d._year = 1;
		d._month = 1;
		d._day = 1;
	}
private:
	int _hour = 1;//这样给的是缺省值
	int _min;
	int _second;
};

class Date
{
	friend class Time;//Time成为Date的友元类(友元类的定义)
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
public:              
	int MonthDayNum(int year, int month) const;
	Date(int year = 0, int month = 1, int day = 1);
	void Print();
	Date& operator+=(int day_num);
	Date operator+(int day_num) const;
	Date& operator-=(int day_num);
	Date operator-(int day_num) const;

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

Date是Time的友元类,在Time中可以调用Date,并访问它的私有成员
注意:1.友元关系是单向的,并不是相互的。如果需要相互调用必须在两个类下面都定义对方为友元类。
2.一般情况下不要轻易使用友元,会破坏类的封装,尽量不要用

5.static静态成员

5.1静态成员的创建

1.静态成员大小

class A 
{
public:
private:
   static int _n;
};
int main()
{
    cout << sizeof(A) << endl;//输出1
    return  0;
}     

通过测试我们发现静态成员是不参与类大小的计算的
2.静态成员的初始化
静态成员并不在构造函数中初始化,其初始化是C++开的一种特例,在类型定义如下

class A 
{
public:
private:
   static int _n;
};
int A::_n = 0;

可见静态成员变量的定义初始化不受访问限定符的限制

静态成员变量存储在静态区,它属于整个类,也属于类的所有对象。所以我们也可以通过 类型名+ :: 帮助其突破类域限制

3.静态成员计算创建对象的数量

class A 
{
public:
    A()//调用构造和拷贝函数增加_n
    {
       _n++;
    }
    A(const A& a)
    {
      _n++;
    }
    ~A()//调用析构减少_n
    {
      _n--
    }
    static int GetN()//静态成员函数
    {
       return _n;
    }
private:
   static int _n;
};
int A::_n = 0;

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

5.2静态成员函数

在上述示范中我们创建了一个静态成员函数 static int GetN()
它和普通的成员函数的区别是: 没有this指针,不可以访问非静态成员

1.静态成员函数不可以调用非静态的成员函数
2.非静态的成语函数可以访问静态的成员函数

6.内部类

如果将类型B定义在类型A中,则称B为A的内部类,此时B天生就是A的友元类,B可以访问A的成员
特性
1.内部类可以定义在外部类的public,protected,private都是可以的
2.注意内部类可以直接访问外部类中的static成员,不需要外部类的对象或者类名
3.外部类的大小和内部类没有关系

7.现阶段理解封装

C++是面向对象的程序,面向对象 有三大特性即:封装、继承、多态
C++通过类,将一个对象的属性与行为结合在一起,使其更符合人们对于一件事物的认知,将属于该对象的所有东西打包在一起;通过访问限定符选择性的将其部分功能开放出来与其他对象进行交互,而对于对象内部的一些实现细节,外部用户不需要知道,知道了有些情况下也没用,反而增加了使用或者维护的难度,让整个事情复杂化。

小结

以上及时类与对象的全部内容,感谢大家观看

上一篇:【初识C++】2.2类与对象(中)
下一篇:【初识C++】3.1内存管理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白在进击

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值