类和对象(三)

再谈构造函数

构造函数体赋值

我们知道对于自定义类型在无构造函数时会调用成员变量里的默认构造,但是成员函数的默认构造对内置类型不做处理,这样自定义类型在不利用构造函数传参时就无法进行初始化。
在这里插入图片描述
当我们显示写全缺省参数的默认构造函数后,通过跳水u可以知道编译器会调用自定义类型成员的默认构造函数。
在这里插入图片描述
当我们在写了在自定义成员里面写了构造函数但不利用构造函数传参初始化,那么对于自定义类型成员,编译器无法生成默认构造函数,也就调用不了默认构造函数。
如果我们在类中写构造函数利用拷构造对该自定义类型进行初始化,不但麻烦,而且编译也无法通过。
在这里插入图片描述

初始化列表

自定义成员变量在已经显示写构造函数的情况下不传参进行初始化,此时又没有默认构造函数生成调用,编译器如何对他进行初始化呢?

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

初始化列表左边写着这个类的成员打,右边写着函数所传的形参,它对应着的是函数体内初始化。

在这里插入图片描述
条件(一)
当自定义函数没有默认构造函数时,必须利用初始化列表来调用构造函数(利用初始化列表进行初始化时一定要写构造函数)来及进行初始化,且只能通过初始化列表进行初始化。(否则编译不通过)

#include <iostream>
using namespace std;
class Time
{
public:
	Time(int hour)
	{
		_hour = hour;
	}
private:
	int _hour;
};
class Date
{
public:
	Date(int year, int hour)
		:_t(hour)
	{
		_year = year;
	}
private:
	int _year;
	Time _t;     
};
int main()
{
	Date d(2022, 22);
	return 0;
}

如果我们在自定义函数中又默认构造函数的成员,先会通过它的初始化列表列用默认构造赋值,再利用函数体内进行赋值,这样对自定义类型成员进行两次赋值。
在这里插入图片描述
总结:
为了简便流程,自定义类型成员推荐通过初始化列表来进行初始化。
我们可以认为初始化列表是成员变量定义的地方(通道),C++编译器认为不管我们显示不显示写都有初始化列表,只是初始化列表对内置函数不再初始化(没有初始值),只对自定义类型进行初始化,显示写时会调用成员变量的构造函数。

情况(二)
类中成员变量为const成员必须使用初始化列表
在这里插入图片描述
我们知道main函数时类的初始化是对整个类进行定义,初始化列表是对这个类中的成员进行定义,且const成员变量只能定义一次,所以_N也必须初始化列表初始化。

情况(三)
类中含有引用成员变量也要采用初始化列表进行初始化。

引用和const修饰的成员变量一样,它们在定义的时候就要初始化,而初始化列表就是变量定义的地方。
当我们将_ref为y的引用来传参时且要通过int&类型的成员变量对类外的变量进行++操作时:
1:我们必须采用引用传参。
2:此时_ref(x)就是将是让_ref为y的引用,即_ref就是y.

#include <iostream>
using namespace std;
class Time
{
public:
	Time(int hour = 10)
	{
		_hour = hour;
	}
private:
	int _hour;
};
class Date
{
public:
	Date(int year, int hour,int &y)
		:_t(hour)
		,_year(year)
		,_ref(y)
	{
		_ref++;
	}
private:
	int _year;
	Time _t;
	int& _ref;
};
int main()
{
	int y = 0;
	Date d(2022, 7, y);
	return 0;

}

C++给内置自定义成员变量缺省值

我们可知C++设置在定义时不写构造函数传参进行初始化且自定义类型在没有显示写默认构造函数可以通过调用自定义成员变量的默认构造函数进行初始化,但是对内置成员不做处理。·

那么对于内置自定义成员变量怎么处理呢?

可以在自定义类型成员声明除给一个缺省值,这个值会在编译器默认的初始化列表进行传值定义。并且编译器会先调用该内置成员的初始化列表,再按顺序调用其他初始化列表进行初始化。
在这里插入图片描述
总结:
1:自定义类型成员变量是一定要使用初始化列表的。
2:内置成员也可使用初始化列表,在函数体内也可以初始化,也可使用在声明处给缺省值来初始化。

将在函数体内初始化与初始化列表结合

对于内置成员,如果我们显示写初始化列表并进行传值定义,那么此时默认构造函数就不起作用。
在这里插入图片描述
有些用初始化列表就比较奇怪,相对指针的初始化。
因为还有涉及开辟空间的检查,数组的初始化。
在这里插入图片描述
我们可以让在函数体初始化与初始化列表结合。
在这里插入图片描述

1:简单的初始化:
内置类型,自定义类型利用初始化列表。
2:较复杂的内置类型(如指针开辟空间)使用函数体内初始化。

初始化列表初始化的顺序

初始化列表初始化的顺序是按照自定义类型的成员变量声明顺序的先后,与初始化的顺序无关。

例如:
创建一个含有两个内置成员a1,a2的对象,并对它们分别通过初始化列表传参进行初始化。
在这里插入图片描述
我们发现:

原本认为a1,a2都都应该为1然而a2的值变成了随机数,而a1的值却变成了1。
在这里插入图片描述
因为,初始化列表进行初始化是按照类的成员定义的顺序来的。
类A先声明a2再声明a1,所以编译器初始化时先对a2进行初始化赋值,又因为a1的值并未初始化为随机值,所以a2的值也为随机值。
之后再对a1执行初始化列表,所以a1的值就为1。

explicit关键字

构造函数不仅可以初始化对象,对于单个参数的构造函数,还支持隐式类型转换。

#include <iostream>
using namespace std;
class Date
{
public:
	Date(int year = 0) //单个参数的构造函数
		:_year(year)
	{}
	void Print()
	{
		cout << _year << endl;
	}
private:
	int _year;
};
int main()
{
	Date d1 = 2021; //支持该操作
	d1.Print();
	return 0;
}

在语法上,代码Date d1 = 2022等价于以下两句代码

Date tmp(2021); //先构造
Date d1(tmp); //再拷贝构造

所以在早期的编译器中,当编译器遇到Date d1 = 2021这句代码时,会先构造一个临时对象,再用临时对象拷贝构造d1,但是现在的编译器已经做了优化,当遇到Date d1 = 2021这句代码时,会按照Date d1(2021)这句代码进行处理,这就叫隐士类型转换。
但是,对于单参数的自定义类型来说,Date d = 2021这种代码的可读性不是很好,我们若是想禁止单参数构造函数的隐士转换,可以用关键打字explicit来修饰构造函数。
在这里插入图片描述

比如:
对于不同类型传参时:

#include <iostream>
using namespace std;
class Date
{
public:
 Date( int year)
 :_year(year)
 {
 cout<< " Date(int year)" <<endl;
 }
 Date( const Date& d)
 {
 cout<<"Date(const Date& d):<<endl;
  }
  private:
  int_year;
};
void func( const string& s)
{
}
int main()
{
string str("insert");
fun(str);
func("insert");
return 0;
}

实际中代码string str(“insert”);和代码 func(str);等价于func(“insert”);.
对于实参和形参为不同种类型,字符串"insert"会先构造一个string的类,然后再对这个类进行引用传参。

static成员

概念

声明为static的类成员称为类的静态成员。用static修饰的成员变量,称之为静态成员变量。用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化。

特性

一:静态成员为所有类对象所共有,不属于某个具体的对象。
例如:
计算类Test的大小

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

结果计算Test类的大小为1,**因为静态成员_n是存储在静态区的,属于类的所有对象。**所以计算类的大小或是计算类对象的大小时,静态成员并不计入其总大小之和。
二:静态成员变量必须在类外定义,定义时不添加static关键字。

我们发现使用statiic修饰的静态成员变量不能使用初始化列表进行初始化。
在这里插入图片描述
也不能利用在声明处给缺省值进行初始化。因为在函数声明给缺省值,这个缺省值是给初始化列表的,但是初始化列表却不对静态成员变量进行初始化。
在这里插入图片描述
所以静态成员只能在类外进行初始化。

class Test
{
private:
   static int _n;
}
int Test:: n = 0;

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

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

class Test
{
 public:
 static void Fun()
 {
 cout <<_a<<endl;   //error不能访问非静态成员。
 cout<< _n<<endl;
 }
 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; //3.通过匿名对象突破类域进行访问
	cout << Test::_n << endl; //2.通过类名突破类域进行访问
	return 0;
	}

2:如果静态成员变量变为私有时
我们可以写一个公有的成员函数通过对象来调用静态成员来获取私有成员,
如果我们不想用对象去调用成员函数则可以采用静态成员函数,减少了对象的创立。

#include <iostream>
using namespace std;
class Test
{
public:
	static int GetN()
	{
		return _n;
	}
private:
	static int _n;
};
 int Test::_n = 0int main()
 {
    Test t;
    cout<<test.GetN()<<endl;//通过建立对象调用。
    cout<<Test::GetN()<<endl; //通过类名去调用静态成员函数。
 }

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

友元

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

友元函数

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

对于之前实现的日期类,我们现在尝试重载operator<<,但是我们发现没办法将其重载为成员函数,因为cout输出流对象原本是在第一个,现在因为类的函数重载第一个形参变成了this指针,这样使用operator操作符重载时原本cout为左操作符却变成了右操作符。
在这里插入图片描述
所以我们要将operator<<重载为全局函数,这样就可以使cout对应着第一个形参,即对应着做操作符。
但是这样的话,在类外又没办法访问的私有成员,那么此时,就需要友元来解决。

我们都需要C++的<<和>>很神奇,他们能够自动识别输入和输出变量的类型,我们使用它们时不必像C语言一样增加数据格式的控制,因为内置对象能够直接使用cout和cin输入输出,是因为库里面将它们的<<和>>重载好了,里面包括着大量的运算符重载。

所以,对于自定义类型,我们需要写出对应的运算符重载函数来识别我们的日期类。

#include <iostream>
using namespace std;
class Date
{
	friend ostream& operator<<(ostream& out, const 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;
	return out; 
}
int main()
{
	Date d1;
	cout << d1;
}

友元函数特性:

1:友元函数可以访问类的私有成员变量和保护成员,但不是类的成员函数。(没有this指针)
2:友元函数不能用const修饰(const修饰的是this指针所指的对象)
3:友元函数可以在类的任何地方声明(它不受访问限定符的限制)。

友元类

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

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

友元类特性:
1: 友元关系是单向的,不具有交换性。

例如:上述代码中B是A的友元,所以在B中可以访问A的私有成员。
2:友元关系不能传递
例如:A是B的友元,B是C的友元,但是判断A是C的友元。

内部类

概念

概念:一个类定义在另一个类的内部,则这个类称为内部类。

特性

1:内部类的大小不受里面的类的大小所影响,只受内置成员影响。
2:类B是类A的友元,即类B里面的成员函数可以访问类A的私有成员.

class A
{
public:
	A(int n)
		:_n(n)
	{}
	class B
	{
	public:
		void func(const A& a)
		{
			cout << a._n<< endl;
	 }
	};
private:
	int _n;
};

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

暂停更新

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

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

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

打赏作者

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

抵扣说明:

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

余额充值