Bootstrap

C++:类和对象 III(初始化列表、explicit、友元、匿名对象)

目录

初始化列表

初始化列表的特点

类型转换、explicit

隐式类型转换

explicit关键字

static成员

静态成员变量

静态成员函数

友元

友元函数

友元类

内部类

匿名对象

编译器优化


初始化列表

初始化列表就是类成员初始化的地方

函数有它声明和定义的地方,变量也有,类成员也有

先来看看日期类的默认构造函数

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

int main()
{
	Date d1(2024, 7, 15);

	return 0;
}

在类里面显示出来的成员我们把它叫做声明

如果你认为我们就这样把_year,_month,_day给初始化了,那就错了

这并不是初始化,这是赋值

如果我们要初始化类成员的话应该这样做

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

 

这一块就是初始化列表

先是一个冒号开始,然后以逗号分隔,成员变量后面跟初始值或者表达式

这样我们就完成了一个成员的定义和初始化

那么这两种写法有什么区别呢?

这两种写法给我们带来的效果都是一样的,但是无论怎么写我们的成员变量都要经过初始化列表一遍,就算没有写初始化列表也会!所以这里建议尽量使用初始化列表初始化

如果我们不知道初始化列表怎么给,我们可以在声明的地方给一个缺省值,例如:

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

这样就算我们没有写初始化列表也会给我们自动初始化上这些值

注意:

const成员变量,带引用的成员变量,只有一次初始化的机会!那就是在初始化列表中!

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

因为const常量不能被赋值,只能在初始化的地方被初始化,正确代码如下: 

class A
{
public:
	A(int a = 1)
		:_a(a)
	{}
private:
	const int _a;
};
class A
{
public:
	A(int a = 1)
		:_a(a)
	{}
private:
	int& _a;
};

初始化列表的特点

如果我们没有在声明的地方给缺省值,也没有写初始化列表,那么值由编译器决定

如果我们给了缺省值没有写初始化列表,那么会根据缺省值初始化

如果我们即给了缺省值也给了初始化列表,那么根据初始化列表的值初始化

注:上述行为于构造函数内部行为无关

初始化列表中按照成员在类中声明的定义来初始化,于初始化列表中出现的先后顺序无关

例如:

class A
{
public:
	A()
		:_a(1)
		, _b(_a)
	{}
//private:
	int _b;
	int _a;
};

int main()
{
	A a;
	cout << a._a << endl;
	cout << a._b << endl;
	return 0;
}

输出结果:

我们可以看到_a的值为1,_b的值为随机值s

这是因为类声明是先声明的_b,才声明的_a,所以初始化列表会先初始化_b,再初始化_a,而不是因为_a在初始化列表中初始化就初始化_a

类型转换、explicit

隐式类型转换

什么是隐式类型转换?通过下面的例子我想你就明白了

int main()
{
	double d = 3.14;
	int i = d;
	cout << i << endl;

	return 0;
}

double类型的d为什么能赋值给int类型的i?

在 i = d 的时候这里会构造出一个临时对象,d会先构造给这个临时变量,然后临时变量才会将值拷贝构造给i

回到正题

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

int main()
{
	A a = 1;
	return 0;
}

这里将1拷贝构造给A类型a就是隐式类型转换,将int类型转换成A类型

explicit关键字

如果我们想要编译器再严格一点,只能同类型转换,那么我们可以在构造类型前面加上关键字explicit

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

int main()
{
	A a = 1;
	return 0;
}

此时我们就运行不了我们的代码了

static成员

静态成员变量

用static修饰的成员变量,称为静态成员变量,静态成员变量必须要在类外初始化!

它的作用和C语言中一样,不属于某个具体的对象,存放在静态区中,生命周期跟全局变量一致

类内初始化:

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

    void Print()
	{
		cout << _a << endl;
	}
private:
	static int _a;
};

int main()
{
	A a;
    a.Print();
	return 0;
}

类外初始化:

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

	void Print()
	{
		cout << _a << endl;
	}
private:
	static int _a;
};

int A::_a = 5;

int main()
{
	A a;
	a.Print();
	return 0;
}

静态成员函数也收public、private、protected访问限定符约束

class A
{
public:
	//A(int a = 1)
	//	:_a(a)
	//{}

	void Print()
	{
		cout << _a << endl;
	}
private:
	static int _a;
};

int A::_a = 5;

int main()
{
	A a;
	cout << A::_a << endl;
	return 0;
}

静态成员函数

用static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针!

静态成员函数中可以访问静态成员,但是不能访问成员函数,因为没有this指针

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

	static void Print()
	{
		cout << _a << endl;
	}
private:
	int _a;
};

非静态成员函数可以访问任意的静态成员变量和静态成员函数(毕竟是全局的)

友元

友元分为友元函数友元类

友元函数

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

void Print(const A& a)
{
	cout << a._a << endl;
}

正常这样Print函数是无法访问到A类里面的private成员变量的,但是如果它是A类的朋友就可以了

class A
{
	friend void Print(const A& a);

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

void Print(const A& a)
{
	cout << a._a << endl;
}

友元函数仅仅是一种声明,他不是类的成员函数

把函数的第一行写一遍,然后前面加上friend,这样Print就是A的朋友了,这样就不会报错了

可以看出,外部友元函数是可以访问类中的私有成员的,当然保护和共有也不例外

友元类

class A
{
public:
	A(int a = 1)
		:_a(a)
	{}
	
	void Print()
	{
		B b;
		cout << b._b << endl;
	}
private:
	int _a;
};

class B
{
	friend class A;
public:
	B(int b = 0)
		:_b(b)
	{}
private:
	int _b;
};

正常来说A类中的Print函数是不能访问b中private的_b

但是这时候,B声明了A是我的朋友,那么A中就可以访问B类中的私有了

但是要注意:友元类关系不能传递

B说A是我的朋友,但是不代表A的朋友是B

class A
{
public:
	A(int a = 1)
		:_a(a)
	{}
	
	void Print()
	{
		B b;
		cout << b._b << endl;
	}
private:
	int _a;
};

class B
{
	friend class A;
public:
	B(int b = 0)
		:_b(b)
	{}

	void Print()
	{
		A a;
		cout << a._a << endl;
	}
private:
	int _b;
};

A是B的友元,但是B可不是A的友元! 

友元和面向对象封装的思想有些相反了,破坏了这种封装

友元会增加耦合度,但是破坏了封装,所以不宜多用

内部类

如果一个类定义在另一个类的内部,那么这个类叫做内部类

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

int main()
{
	A a;
	A::B b;
	b.func(a);
	return 0;
}

这里的B类就是定义在A类中,此时B就是A的友元,可以访问A类中的私有 

A类的实现就是专门为B准备的

匿名对象

先来看个代码

class A {
public:
	int func(int n) {
		//...
		return n;
	}
};

int main()
{
	A a;
	a.func(1);
	return 0;
}

如果要调用func函数我们可以先定义一个A类对象,然后再使用func

那还有什么更好的调用方式吗?

我们可以使用匿名对象来调用这个func函数

class A {
public:
	int func(int n) {
		//...
		return n;
	}
};

int main()
{
	A().func(1);
	return 0;
}

这里的A()就是一个匿名对象,它的生命周期只有这一行,这一行之后就会结束,所以目的也就只是想调用func函数不那么麻烦且少一点空间

编译器优化

class A {
public:
	A(int a = 1)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}

	A(const A& a)
		:_a(a._a)
	{
		cout << "A(const A& a)" << endl;
	}

	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};

int main()
{
	A a1(10);
	A a2 = a1;
	return 0;
}

明明有两个成员变量a1、a2,怎么才调用一次构造函数呢?

这里就是由于编译器的优化,由于A a2 = a1这个表达式做了两件事情,一件就是构造,另一件就是拷贝构造,所以编译器优化成了只有拷贝构造即可

越是新版本的编译器优化的会越狠

将Debug模式换成Release也会有更多的优化

这些都是为了加快代码运行的效率


;