[C++]类和对象(下)

发布于:2024-09-18 ⋅ 阅读:(4) ⋅ 点赞:(0)

我们紧接着上一章的部分进行讲解

目录

1.赋值运算符重载

1.运算符重载

2.赋值运算符重载

2.const成员函数

3.初始化列表

4.static成员

5.友元

6.内部类

7.匿名对象


1.赋值运算符重载

1.运算符重载

  • 当运算符被用于类类型的对象时,C++允许我们通过运算符重载的形式指定新的含义。C++规定类类型对象使用运算符时,必须转换成调用对应运算符重载,若没有对应的运算符重载,则会编译报错。
  • 运算符重载是具有特殊名字的函数,他的名字是由operator和后面要定义的运算符共同构成。和其他函数⼀样,它也具有其返回类型和参数列表以及函数体。
//返回值operate符号(参数类型)
//{
//	函数体
//}
  • 重载运算符函数的参数个数和该运算符作⽤的运算对象数量⼀样多。⼀元运算符有⼀个参数,二元运算符有两个参数,二元运算符的左侧运算对象传给第⼀个参数,右侧运算对象传给第二个参数
  • 如果⼀个重载运算符函数是成员函数,则它的第⼀个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数比运算对象少⼀个。
  • 运算符重载以后,其优先级和结合性与对应的内置类型运算符保持⼀致
  • 不能通过连接语法中没有的符号来创建新的操作符:比如operator@
  • .*   ::    sizeof    ?:    .    这五个符号不能进行运算符重载
  • 重载操作符⾄少有⼀个类类型参数,不能通过运算符重载改变内置类型对象的含义
// 编译报错:“operator +”必须⾄少有⼀个类类型的形参
int operator+(int x, int y)
{
return x - y;
}
  • ⼀个类需要重载哪些运算符,是看哪些运算符重载后有意义
  • 重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,无法很好的区分。C++规定,后置++重载时,增加⼀个int形参,跟前置++构成函数重载,方便区分
	Date& operator++()
	{
		cout << "前置++" << endl;
		//...
		return *this;
	}
	Date operator++(int)
	{
		Date tmp;
		cout << "后置++" << endl;
		//...
		return tmp;
	}
  • 重载<<和>>时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第⼀个形参位置,第⼀个形参位置是左侧运算对象,调用时就变成了 对象<<cout,不符合使用习惯和可读性。重载为全局函数把ostream/istream放到第⼀个形参位置就可以了,第二个形参位置当类类型对象。

2.赋值运算符重载

赋值运算符重载是⼀个默认成员函数,用于完成两个已经存在的对象直接的拷贝赋值,这里要注意跟拷贝构造区分,拷贝构造用于⼀个对象拷贝初始化给另⼀个要创建的对象

特点:

  1. 赋值运算符重载是⼀个运算符重载,规定必须重载为成员函数。赋值运算重载的参数建议写成const 当前类类型引有,否则会传值传参会有拷贝
  2. 有返回值,且建议写成当前类类型引用,引用返回可以提高效率,有返回值目的是为了支持连续赋值场景。

以上两点都是总结就是将返回值和传入参数的类型设置为类引用类型,这样可以减少因传值传参拷贝所带来的低效场景

3.没有显式实现时,编译器会自动生成⼀个默认赋值运算符重载,默认赋值运算符重载行为跟默认构造函数类似,对内置类型成员变量会完成值拷贝/浅拷贝(⼀个字节⼀个字节的拷贝),对自定义类型成员变量会调用他的拷贝构造。

对于日期类这种全是内置类型且没有指向任何资源的类,编译器自动生成的赋值运算符重载就可以达到目的,但是对于其他需要开辟空间的类,由于默认生成的赋值重载是浅拷贝,会导致赋值重载后两个对象指向同一块空间,在析构的时候就会释放两次空间,会导致程序崩溃,这里就需要我们手动的写一份赋值重载了。

这里还有一个小技巧,如果一个类显示实现了析构并释放资源,那么他就需要显示写赋值运算符重载,否则就不需要

2.const成员函数

  • 将const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表的后面
  • const实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。const 修饰Date类的Print成员函数,Print隐含的this指针由 Date* const this 变为 const Date* const this
// void Print(const Date* const this) const
void Print() const
{
cout << _year << "-" << _month << "-" << _day << endl;
}

对于只读的成员函数,都建议在函数声明末尾加上const修饰

3.初始化列表

  • 之前我们实现构造函数时,初始化成员变量主要使用函数体内赋值,构造函数初始化还有⼀种方式,就是初始化列表,初始化列表的使用方式是以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟⼀个放在括号中的初始值或表达式
  • 每个成员变量在初始化列表中只能出现一次,语法理解上初始化列表可以认为是每个成员变量定义初始化的地方。
  • 引用成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置进行初始化,否则会编译报错。
  • C++11支持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显示在初始化列表初始化的成员使用的
  • 尽量使用初始化列表初始化,因为那些你不在初始化列表初始化的成员也会走初始化列表,如果这个成员在声明位置给了缺省值,初始化列表会用这个缺省值初始化。如果你没有给缺省值,对于没有显示在初始化列表初始化的内置类型成员是否初始化取决于编译器,C++并没有规定。对于没有显示在初始化列表初始化的自定义类型成员会调用这个成员类型的默认构造函数,如果没有默认构造会编译错误。
  • 初始化列表中按照成员变量在类中声明顺序进行初始化,跟成员在初始化列表出现的的先后顺序无关。建议声明顺序和初始化列表顺序保持⼀致
class Date
{
	Date(int& x, int year = 1, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
		, _t(12)
		, _ref(x)
		, _n(1)
	{
		// error C2512: “Time”: 没有合适的默认构造函数可⽤
		// error C2530 : “Date::_ref” : 必须初始化引⽤
		// error C2789 : “Date::_n” : 必须初始化常量限定类型的对象
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t;
	// 没有默认构造
	int& _ref;
	// 引⽤
	const int _n; // const
};

4.static成员

  • 用static修饰的成员变量,称之为静态成员变量,静态成员变量⼀定要在类外进行初始化
  • 静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区
  • ⽤static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针
  • 静态成员函数中可以访问其他的静态成员,但是不能访问非静态的,因为没有this指针
  • 非静态的成员函数,可以访问任意的静态成员变量和静态成员函数
  • 突破类域就可以访问静态成员,可以通过类名::静态成员 或者 对象.静态成员 来访问静态成员变量和静态成员函数
  • 静态成员也是类的成员,受public、protected、private 访问限定符的限制
  • 静态成员变量不能在声明位置给缺省值初始化,因为缺省值是个构造函数初始化列表的,静态成员变量不属于某个对象,不走构造函数初始化列表

5.友元

  • 友元提供了⼀种突破类访问限定符封装的方式,友元分为:友元函数和友元类,在函数声明或者类声明的前面加friend,并且把友元声明放到⼀个类的里面
  • 外部友元函数可访问类的私有和保护成员,友元函数仅仅是⼀种声明,他不是类的成员函数
  • 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  • 一个函数可以是多个类的友元函数
  • 友元类中的成员函数都可以是另⼀个类的友元函数,都可以访问另一个类中的私有和保护成员
  • 友元类的关系是单向的,不具有交换性,比如A类是B类的友元,但是B类不是A类的友元。
  • 友元类关系不能传递,如果A是B的友元, B是C的友元,但是A不是B的友元。

友元会降低耦合度,破坏封装,不建议多用

// 友元声明
friend void func(const A& aa, const B& bb);

6.内部类

如果一

个类定义在另以个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,跟定义在
全局相比,他只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类

内部类默认是外部类的友元类

内部类本质也是⼀种封装,当A类跟B类紧密关联,A类实现出来主要就是给B类使用,那么可以考
虑把A类设计为B的内部类,如果放到private/protected位置,那么A类就是B类的专属内部类,其
他地方都用不了

7.匿名对象

用 类型(实参) 定义出来的对象叫做匿名对象,相比之前我们定义的类型 对象名(实参) 定义出来的
叫有名对象

匿名对象生命周期只在当前一行,一般临时定义一个对象当前用一下即可,就可以定义匿名对象。

// 不能这么定义对象,因为编译器⽆法识别下⾯是⼀个函数声明,还是对象定义
//A aa1();
// 但是我们可以这么定义匿名对象,匿名对象的特点不⽤取名字,

// 但是他的⽣命周期只有这⼀⾏,我们可以看到下⼀⾏他就会⾃动调⽤析构函数
 A();
 A(1);
// 匿名对象在这样场景下就很好⽤
 Solution().Sum_Solution(10);

以上就是C++类和对象的全部内容,希望对你有所帮助 


网站公告

今日签到

点亮在社区的每一天
去签到