再谈构造函数,初始化列表,匿名对象,静态成员以及初识友元(c++)

目录

    • 1. 再谈构造函数
      • 1.1 初始化成员列表
      • 1.2 函数体内赋值和初始化成员列表区别
      • 1.3 初始化成员列表的作用
        • 1.3.1 const成员变量
        • 1.3.2 引用成员变量
        • 1.3.3 自定义类型成员变量
      • 1.4 初始化列表的顺序
    • 2. 匿名对象
    • 3. explicit关键字
    • 4. 静态成员变量
    • 5. 静态成员函数
      • 5.1 静态成员总结
    • 6. c++11
    • 7. 友元
      • 7.1友元函数
      • 友元类

1. 再谈构造函数

class Date
{
     
public:
	Date(int year;int month;int day)
	{
     
		_year=year;
		_month=month;
		_day=day;
	}
private:
	int _year;
	int _month;
	int _day;

};

在类中,成员变量第一次是以声明的形式出现。在我们之前讲过,构造函数的作用并不是构造一个对象,而是初始化对象。那么像构造函数里的,他是初始化吗,显然不是的,每一次调用我们都可以赋给他不同的值,而初始化只会初始化一次。

    Date(int year;int month;int day)
	{
     
		_year=year;
		_month=month;
		_day=day;
	}

1.1 初始化成员列表

那他是在哪里初始化的呢,其实是隐藏了一个初始化列表。这个初始化列表是对象的每个成员变量初始化的地方。
第一个我们直接在函数体内赋值,其实也有初始化列表,只不过没有显示且初始化的是随机值。
语法格式如下:

Date(int year, int month, int day)
		: _year(year)
	    , _month(month)
		, _day(day)

	{
     }
	

1.2 函数体内赋值和初始化成员列表区别

这两种有什么区别呢?其实就像下面这两种写法。

int a;
a=10;

int a=10;

1.3 初始化成员列表的作用

1.3.1 const成员变量

假如类里面声明了一个const成员变量

//错误的写法
class Date
{
     
public:
	Date(int year, int month, int day)
	{
     
		_year=year;
		_month = month;
		_day = day;
		x = 10;

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

};

直接会有报错。
再谈构造函数,初始化列表,匿名对象,静态成员以及初识友元(c++)_第1张图片
其实也不难想,声明了一个常量,在定义的时候需要对它进行初始化,在函数里面已经是赋值了。这时候就需要初始化成员列表。

Date(int year, int month, int day)
        : _year(year)
	    , _month(month)
		, _day(day)
		, x(10)
{
     }

1.3.2 引用成员变量

在之前学习引用的时候,我们知道在定义的时候也需要初始化,所以和const成员变量一样,定义的时候就要初始化。

class Date
{
     
public:
Date(int year, int month, int day)
		: _year(year)
	    , _month(month)
		, _day(day)
		, x(10)
		,ret(day)
		//让他成为day的引用,ret改变day改变

	{
     }
private:
     
	int _year;
	int _month;
	int _day;
	const int x;
	int& ret;

};

1.3.3 自定义类型成员变量

//错误的例子
class Time
{
     
public:
	Time(int hour, int minute, int seconds)
		:_hour(hour)
		, _minute(minute)
		, _seconds(seconds)
	{
     }
private:
	int _hour;
	int _minute;
	int _seconds;
};

class Date
{
     
public:
	public:
    Date(int year, int month, int day)
		: _year(year)
	    , _month(month)
		, _day(day)
		, x(10)
		,ret(day)
		//让他成为day的引用,ret改变day改变

	{
     }
private:
	int _year;
	int _month;
	int _day;
	const int x;
	int& ret;
	Time _t;

};

假如Date类里面有个自定义Time类型,定义Date对象时,会调用Time类里的默认构造函数,假如Time类里没有默认构造函数,那么也需要我们在初始化列表里初始化。
在这里插入图片描述

//正确做法
Date(int year, int month, int day,int hour,int minute,int seconds)
        : _year(year)
	    , _month(month)
		, _day(day)
		: x(10)
		, ret(day)
		, _t(hour,minute,seconds)
		{
     }

其实初始化列表,函数体内赋值也可以混写,不过有点四不像,假如需要初始化列表就全用初始化列表,写函数体内赋值就都写函数体内赋值。

自定义类型有默认构造函数时,可以使用初始化成员列表,也可以不使用。
不过建议有默认构造函数也使用初始化成员列表。

当我们不使用初始化成员列表时

	Date(int year, int month, int day, int hour, int minute, int senconds)
	{
     
		_year = year;
		_month = month;
		_day = day;
		Time t(hour, minute, senconds);
		_t = t;
	}

隐藏的初始化成员列表会调用time的默认构造函数,t对象调用构造函数,_t=t又调用了一次赋值重载函数。

假如写入初始化成员列表中:

Date(int year, int month, int day,int hour,int minute,int seconds)
		: _t(hour,minute,seconds)
		,_year (year)
	    ,_month (month)
	    ,_day  (day)
	{
     }

只调用一次构造函数。
反正能使用初始化列表就用初始化列表。

1.4 初始化列表的顺序

再谈构造函数,初始化列表,匿名对象,静态成员以及初识友元(c++)_第2张图片

列表初始化的顺序必须和类中声明的顺序保持一致,不然就会产生错误。比如这里实际先初始化的是a2,由于a1是随机值,所以a2就是随机值,a1是1。
输出结果就是1,随机值

2. 匿名对象

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

private:
	int _a;
};

int main()
{
     
	A a1(1);//在main函数域
	A(1);//匿名对象生命周期在这一行

	return 0;
}

匿名对象有什么用呢。
比如像leetcode上,我们使用c++,进行oj的时候会创建一个solution类,测试的时候就直接sloution().方法(参数)。这就是一个匿名类的应用场景,不需要专门再去创建一个对象去调用方法

3. explicit关键字

延续上面的代码来继续讲解,这里我们看到给a1对象赋值了一个3,都不是一个类型竟然成功了,输出3。其实这里是发生了隐式类型转换。
3会转换成一个匿名对象,然后赋值重载给a1。
在这里插入图片描述
3会转换成一个匿名对象,然后拷贝构造给a2。但是编译器将这两个过程优化合并直接调用构造函数
在这里插入图片描述

可以类比引用,中间发生的隐式类型转换

int i=10;
const double& a=i;

怎么来证明它发生饮食类型转换了呢,c++专门提供了个关键字explicit来阻止隐式类型转换,我们在构造函数前面加上这个关键字,如果刚才那条语句不成功,就说明那里准备要发生隐式类型转换,只是被explicit阻止了。
在这里插入图片描述
看上去没什么差别,实际上这种隐式类型并不是多此一举。比如说用匿名sloution类调用函数。

第一种是先构造一个str对象,然后做参数传入函数,拷贝构造
在这里插入图片描述
第二种用string类构造一个匿名对象,传入函数,拷贝构造
在这里插入图片描述
第三种直接传进去,然后让他自己进行隐式类型转换,成为匿名对象然后拷贝构造。只不过编译器直接优化让他只调用构造函数
在这里插入图片描述
第三种看上去更舒服。

4. 静态成员变量

面试题:怎么计算一个类有多少个对象。

先来看看静态成员变量,我们可以把成员变量加上static,他就不属于某个对象,而是属于整个类。在类内声明,而且需要在类外定义。
用sizeof计算一下,发现他是个空类,占一个字节(表示类存在)
再谈构造函数,初始化列表,匿名对象,静态成员以及初识友元(c++)_第3张图片
在构造函数和拷贝构造里面++_b,由于_b是属于类的,所以调用了多少次构造和拷贝构造都会被计数器计数。面试题就解决了。
在这里插入图片描述
假如成员变量是公有的,类也可以调用,那么对象也可以调用,但是这样(b3._b)并不代表解引用(去对象中找他的变量)而是去对象(b3)的类(B)中去寻找静态变量_b。

私有的只能用一个成员函数,来输出类静态变量

5. 静态成员函数

相比于非静态成员函数来说,静态成员函数没有this指针.
可以思考两个问题,时刻记住静态成员函数没有this指针就好。

  1. 静态成员函数可以调用非静态成员函数吗?
  2. 非静态成员函数可以调用类的静态成员函数吗?

5.1 静态成员总结

  1. 静态成员为所有类对象所共享,不属于某个具体的实例
  2. 静态成员变量必须在类外定义,定义时不添加static关键字
  3. 类静态成员可用类名::静态成员或者对象.静态成员来访问(不是私有的话)
  4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
  5. 静态成员和类的普通成员一样,也有public、protected、private3种访问级别,也可以具有返回值

6. c++11

声明的时候给缺省值。
再谈构造函数,初始化列表,匿名对象,静态成员以及初识友元(c++)_第4张图片
c++98中,编译器默认生成的构造函数,针对内置类型没有处理,会对自定义类型调用默认构造函数。在c++11中,在声明的时候给缺省值(很像定义但他不是定义!!!)

7. 友元

7.1友元函数

我们用cout,或者cin想输出或输入一个自定义类型的对象,需要重载操作符

class Date
{
     
public:
	Date(int year=1900, int month=1, int day=1)
	:_year(year)
	, _month(month)
	, _day(day)
	{
     }
	ostream& operator<<(ostream& out)
	{
     
		//this->_year,this->_month,this->day
		out<<_year<< _month <<_day<<endl;
		return out;
	}
private:
	int _year;
	int _month;
	int _day;

};

当我们调用他的时候,有两种方法:

    Date d1;
	d1.operator<<(cout);
	d1 << cout;

但是第二种看起来怪怪的,在平常我们输出内置类型的时候通常是

int i=10;
cout<<i;

但对于输出对象我们只能把d1放在左边,因为作为成员函数,指向对象d1的隐含的this指针始终是作为第一个参数,输出流作为第二个参数,所以就是这样的形式 d1<
那交换这两个参数的位置呢,交换不了,this永远是第一个,这样写只是相当于给他多加了一个参数。
在这里插入图片描述
那我们不把他作为成员函数。放在外面,没有this指针。可以交换两个参数的位置。但是呢成员变量是私有的外面有访问不了。
在这里插入图片描述
所以c++又给出了友元friend。在类里声明这个函数为friend。代表他可以访问这个类的私有成员和保护成员。

class Date
{
     
	friend ostream& operator<<(ostream& out, const Date& d);
public:
	Date(int year=1900, int month=1, int day=1)
	:_year(year)
	, _month(month)
	, _day(day)
	{
     }
	//ostream& operator<<(ostream& out)
	//{
     
	//	//this->_year,this->_month,this->day
	//	out<<_year<< _month <<_day<
	//	return out;
	//}
private:
	int _year;
	int _month;
	int _day;

};

ostream& operator<<(ostream& out, const Date& d)
{
     
	out << d._year <<"-"<< d._month <<"-"<< d._day<<endl;
	return out;
}



int main()
{
     
	Date d1;
	Date d2(2000, 5, 6);
	cout << d1 << d2 << endl;
	return 0;
}

友元函数的特点:

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

友元类

你可能感兴趣的:(Cpp,c++,类,封装)