从零开始的C++生活——第二讲——04

这是第二讲的最终章了,这一讲结束,我们类和对象的讲解就暂时告一段落了。

首先,我们要再次探讨一下构造函数,先前我们讲默认成员函数的时候,已经介绍过构造函数,但当时并没有说完,今天我们就介绍完。

这次再谈构造函数,主要是讲解一下初始化列表。之前我们写构造函数的时候,初始化成员变量都是在函数体内赋值,初始化列表则是另一种赋值方式,与其说是赋值,倒不如说是定义加初始化。

我来简单演示一下初始化列表的使用方法。

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1

#include <iostream>

using namespace std;

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

	}
	void Print()
	{
		cout << _year << '/' << _month << '/' << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d;
	d.Print();

	return 0;
}

初始化列表语法是以:开始,分割,括号内跟着自己传的参数,也可以是表达式。

C++中成员变量初始化是一定会走初始化列表的,即使你没有写初始化列表。

这里我们分几种情况,对于有初始化列表的成员变量,就不过多赘述了,对于没有的,编译器首先会去查看声明成员变量的位置有没有缺省值,如果有就把缺省值赋值给成员变量,如果没有,对于内置类型,取决于编译器,对于自定义类型,调用它默认的构造函数,如果没有则报错。

对于引用变量或者const变量或是没有默认构造函数的类类型变量,必须在初始化列表完成初始化。

但是这里又有一点绕,大家看这段代码

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1

#include <iostream>

using namespace std;

class Date
{
public:
	Date(int& a,int year=1,int month=1,int day=1)
		:_year(year)
		,_month(month)
		,_day(day)
	{

	}
	void Print()
	{
		cout << _year << '/' << _month << '/' << _day << endl;
		cout << _a << ' ' << _b;
	}
private:
	int _year;
	int _month;
	int _day;
	int& _a=_day;
	const int _b=10;
};
int main()
{
	int a = 3;
	Date d(a);
	d.Print();

	return 0;
}

你看我引用对象和const也没走初始化列表啊,这也没报错啊?但其实,C++11引入了一个新的规则,支持我们在成员变量的声明位置给缺省值,假如我们没有写对应参数的初始化列表,就会通过这个缺省值初始化,其实也还是走了初始化列表,只不过省略了这一步,并且传了之前就给好的缺省值。

所以说我们建议所有成员变量都通过初始化列表初始化,因为你不写他也会走初始化列表。

初始化列表的初始化顺序依据成员变量在类中的声明顺序,和你初始化列表的出现顺序无关。

总结一下:不论你写不写初始化列表,每个构造函数都有初始化列表,不论是否在初始化列表显式初始化成员变量,每个成员变量都要走初始化列表初始化。

接下来讲一下类型转换

C++支持内置类型隐式转换为类类型对象,需要有相关内置类型为参数的构造函数。

举个例子

cpp 复制代码
class A
{
public:
	A(int a = 10)
		:_a(a)
	{

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

};
int main()
{
	A b = 15;
	b.Print();
	return 0;
}

这里构造函数写不写缺省值都是无所谓的。

当我们给构造函数前加上explicit就不再支持隐式类型转化。

类类型的对象之间也可以隐式类型转换,但需要相应的构造函数支持。

C++11之后也支持多参数转化,需要用{}括起

cpp 复制代码
class A
{
public:
	A(int a,int b)
		:_a(a)
		,_b(b)
	{

	}
	void Print()
	{
		cout << _a << endl;
		cout << _b << endl;

	}
private:
	int _a;
	int _b;

};
int main()
{
	A b = { 12,19 };
	b.Print();
	return 0;
}

接下来介绍一下static成员

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

静态成员变量不依赖于具体对象,他被类所共享,存放在静态区,不在对象之中。

cpp 复制代码
class B
{
public:
	static void Print();
private:
	static int _a;
};
void B::Print()
{
	cout << _a << endl;
}
int B::_a = 10;
int main()
{
	
	B::Print();

	return 0;
}

同理,用static修饰的成员函数称为静态成员函数,静态成员函数因为不依赖于某个对象所以没有this指针。

静态成员函数可以访问其他的静态成员变量,但是不能访问非静态成员变量,因为没有this指针,但是非静态的成员函数就可以访问静态成员变量,同理对象也可以调用静态成员函数。

突破类域就可以访问静态成员函数,假如没有访问修饰限定符的限制,同理也可以访问成员变量,突破类域用类名::即可。

虽然,类中不存放静态成员变量,但静态成员变量仍是类的成员,受访问修饰限定符的约束。

静态成员变量不走初始化列表,也不能通过缺省值的方式初始化,因为缺省值本质也是走了初始化列表。

接下来介绍友元的概念。

之前其实提到过,友元就是加friend嘛。

所谓友元就是提供了一种突破访问修饰限定符的方法,友元分为友元函数和友元类,把这个函数的声明或是类的声明放到类的某个位置,前边加上friend即可达到效果。

友元函数或类虽然在类中写有声明,但并不是该类的成员!

一个函数可以是多个类的友元。

友元类(加friend的那个类)中所有的成员函数都被视为另一个类的友元函数,可以调用他们的所有私有变量或方法。

友元类不具有交换性,比如a是b的友元,反之不成立

友元类也不具有传递性,a是b的友元,b是c的友元,但a不是c的友元。

友元虽然提供了便利,但也增加了耦合度,所以不宜多用。

接着讲一下内部类。

顾名思义内部类就是定义在类内部的类,但是内部类是一个独立的类,与你在全局定义相比,就是被类域和访问修饰限定符约束。所以外部类的成员并不包括内部类。

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

最后说一下匿名对象

用类型定义出的没有名字的对象就是匿名对象,匿名对象的生命周期和普通不同,只在本行。

至此,我们已经讲完了90%的类和对象内容,下一讲开头我会补充一下对象拷贝时的编译器优化,这一讲结束。

相关推荐
青木灵溪1 天前
中原地区“治未病”康养实践观察——以河南郑州康养业态为例
生活
2601_967287571 天前
浚县自建房室外排水管网:坡度、基础、闭水和回填
生活
jiuzhouxinghu2 天前
脊柱侧弯支具定制时,为什么身体尺寸不是唯一依据?
生活
2603_965148115 天前
商品价格监控 API 接口:从原理到实战的完整指南
java·前端·数据库·python·生活
2601_967287576 天前
浚县自建房外墙外保温,粘结锚固抹面三个环节的关键数字
生活
RuixiYc.7 天前
二油酰磷脂酰乙醇胺-聚乙二醇-丙烯酸酯,DOPE-PEG-Acrylate的性能优势
生活
郑州光合科技余经理7 天前
本地生活服务系统:预约状态和进度通知接口怎么拆
java·开发语言·前端·javascript·后端·php·生活
renhongxia17 天前
中美AI竞争下人工智能赋能大众生活
大数据·人工智能·生活
人工智能培训10 天前
智能博弈背景下中国AI国防建设的战略价值
大数据·人工智能·算法·重构·生活