【C++】类和对象【下篇】--初始化列表,static成员,友元,内部类,匿名对象


失败是什么?没有什么只是更走近成功一步。成功是什么?就是走过了所有通向失败的路。只剩下一条路那就是成功的路

一、再谈构造函数

1.构造函数体赋值

我们在类和对象中我们学习到在创建对象时编译器通过调用构造函数给对象中各个成员变量一个合适的初始值就比如下面的日期类

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

虽然上述构造函数调用之后对象中已经有了一个初始值但是不能将其称为对对象中成员变量的初始化构造函数体中的语句只能将其称为赋初值而不能称作初始化。因为初始化只能初始化一次而构造函数体内可以多次赋值。

我们知道类里面只是成员变量的声明并不是定义成员变量因为类并不会占用空间就好比我们设计房子时的图纸只有我们在真正修建房子的时候才会占用空间类也一样只有我们用类实例化出具体的对象的时候才会对成员变量进行定义而对象是整体定义的那么对象中具体的每一个成员变量在那么定义呢

C++类对象中的成员变量在初始化列表进行初始化。

2.初始化列表

1.概念

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

我们还是以日期类为例

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

int main()
{
	Date d1(2023, 1, 1);
	return 0;
}

在这里插入图片描述

2.特性

初始化列表有以下几个特性

1.初始化列表是每个成员变量定义和初始化的地方所以每个成员变量(内置类型和自定义类型)无论我们是否显示在初始化列表出写都一定会走初始列表并且初始化操作只能进行一次即每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)

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

int main()
{
	Date d1(2023, 1, 1);
	return 0;
}

在这里插入图片描述

我们可以看到我们对_day进行两次初始操作的时候编译器会报错因为变量只能初始化一次。

2.如果我们在初始化列表显示写了编译器就会用显示写的来初始化如果我们没有在初始化列表显示的写那么对于内置类型有缺省值用缺省值没有编译器就会使用随机值来初始化对于自定义类型编译器会调用自定义类型的默认构造函数来初始化如果没有默认构造编译器就会报错

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

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

int main()
{
	Date d1(2023, 1, 1);
	return 0;
}

在这里插入图片描述

我们可以看到我们没有在初始化列表里显示对_day和A aa进行初始化_day为内置类型被初始化为一个随机值而aa为自定义类型调用了自身的默认构造来初始化。

如果自定义类型既没有在初始化列表显示定义也没有默认构造函数编译器就会报错。

在这里插入图片描述

3.类中包含以下成员(引用const没有默认构造的自定义成员)必须放在初始化列表位置进行初始化

引用成员变量

const成员变量

自定义类型成员(且该类没有默认构造函数时)

我们知道引用一个变量的别名它必须在定义的时候初始化并且一旦引用了一个变量就不能再引用另一个变量同时const作为只读常量也必须在定义的时候初始化并且初始化初始化之后不能修改。自定义类型成员且没有默认构造对于自定义类型编译器会调用自定义类型的默认构造函数来初始化如果没有默认构造编译器就会报错所以也需要在初始化列表显示初始化。我们知道构造函数的函数体内执行的是赋值语句成员变量只能在初始化列表进行定义和初始化。

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

class B
{
public:
	B(int a, int ref, int n)
	{
		_aobj = a;
		_ref = ref;
		_n = n;
	}
private:
	A _aobj;   //没有默认构造函数
	int& _ref; //引用
	const int _n; // const 
};

int main()
{
	B b;
	return 0;
}

在这里插入图片描述

所以对于使用const修饰引用类型的成员变量已经没有默认构造的自定义类型成员我们都需要在初始化列表对其进行初始化否则编译器就会报错。

所以上面的代码正确的写法是

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

class B
{
public:
	B(int a = 0, int ref = 0, int n = 10)
		:_aobj(a)
		,_ref(ref)
		,_n(n)
	{}
private:
	A _aobj;   //没有默认构造函数
	int& _ref; //引用
	const int _n; // const 
};

int main()
{
	B b;
	return 0;
}

此外构造函数的初始化列表和函数体可以配合起来使用即可以让初始化列表分别完成一部分比如下面的代码

// 初始化列表和函数体内初始化可以混着来
Stack(int capacity = 4)
	: _top(0)
	, _capacity(capacity)
{
	cout << "Stack(int capacity = 4)" << endl;

	_a = (int*)malloc(sizeof(int) * capacity);
	if (_a == nullptr)
	{
		perror("malloc fail");
		exit(-1);
	}
	memset(_a, 0, sizeof(int) * capacity);
}

4.尽量使用初始化列表进行初始化因为无论我们是否在初始化列表显示初始化类的成员函数在初始化的时候都会先使用初始化列表进行初始化

在这里插入图片描述

我们可以看到即使我们显示定义的构造函数什么也没有写_pushST和_popST也完成了初始化工作因为无论我们是否在初始化列表显示写类的成员变量都会走初始化列表类的自定义类型的成员会去调用它的默认构造来完成初始化工作。

5.在C++11中对于内置类型打了一个补丁–内置类型成员变量可以在声明的时候给一个缺省值其可以在初始化列表起作用

我们之前在学习构造函数的时候并不知道初始化列表的存在所以认为默认生成的构造函数对内置类型不做处理而C++11为了弥补这个缺陷打了一个补丁即可以在成员变量声明的时候给一个缺省值现在我们知道内置类型也会在初始化列表进行初始化只是因为初始化的是一个随机值感觉就行没有初始化一样所以成员函数的缺省值是在初始化列表出生效的。

在这里插入图片描述

6.成员变量在类中声明次序就是其在初始化列表中的初始化顺序与其在初始化列表中的先后次序无关

我们看下面的代码

class A
{
public:
	A(int a)
		:_a1(a)
		, _a2(_a1)
	{}

	void Print() {
		cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a2;
	int _a1;
};
int main()
{
	A aa(1);
	aa.Print();
}

在这里插入图片描述

由于在类中_a2是声明在_a1之前所以在初始化列表处_a2(_a1)语句会被先执行此时_a1还是一个随机值所以最终_a2的值为一个随机值。

二、隐式类型转换

1.概念

隐式类型转换是指当两个不同类型的变量之间进行运算的时候编译器会自动将其中一个变量的类型转换为另一个变量的类型比如我们下面的代码

int main()
{
	int i = 0;
	double d = i;
	const double rd = i;
	const int& j = 1;
	return 0;
}

对于上面的代码我们将一个整型的i赋值给双精度浮点型的d此时其实不是直接把i赋值给d编译器会先根据i的值创建一个浮点型的临时变量再把临时变量赋值给d;

对于rd来说也是如此不同的是rd是引用类型而引用和指针我们不要考虑权限的放大缩小和平移的问题我们用引用类型的变量rd去引用d生成的临时变量需要用const修饰因为临时变量具有常性;

对于j来说也是如此由于数字1只存于指令中在内存中并不占用空间所以当我们对其进行引用的时候1会先生成一个临时变量然后再对临时变量进行引用又因为临时变量具有常性所以我们也需要加const进行修饰。

2.构造函数的类型转换

在C++98中构造函数不仅可以构造与初始化对象对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数还具有类型转换的作用比如下面的这样

class Date
{
public:
	Date(int year)
		:_year(year)
	{
		cout << "Date(int year)构造" << endl;
	}

	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		cout << "Date(const Date& d)拷贝构造" << endl;
	}

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

int main()
{
	// 构造
	Date d1(2023);
	// 隐式类型的转换 构造+拷贝构造
	Date d2 = 2023;
	// 拷贝构造
	Date d3(d1);
	// 构造
	const Date& d4 = 2023;
	return 0;
}

在这里插入图片描述

在这里插入图片描述

对于具有单参数的构造函数的Date类我们不仅可以使用构造和拷贝构造的方式进行实例化对象还可以通过直接赋值一个整形来进行初始化。这是隐式类型转换的结果对于d2来说2023和d2的类型是不同的所以编译器会进行类型转换即先使用2023来构造一个临时的Date类型的对象然后后这个临时的对象来对d2进行拷贝构造这是构造+拷贝构造的结果。

但是现在编译器会对这种情况进行优化不再创建临时对象而是直接使用2023来构造d2所以我们看到的是d2没有调用拷贝构造函数对于比较老的编译器而言并没有做出优化而是构造+拷贝构造。

对于d4而言d4是Date对象的引用所以编译器会先用2023来构造一个Date类型的临时对象然后d4才对这个临时对象进行引用所以只调用了一次构造函数又因为临时变量具有常性所以我们需要加const进行修饰。

【注意】

单参数构造函数不是指构造函数只有一个参数而是指只需要传递一个参数的构造函数比如全缺省或者半缺省的构造函数同样也是可以的。

C++11对单参数构造进行了扩展支持了多参数的构造函数只是传递的多个参数需要用大括号括起来比如下面的代码

class Date
{
public:
	Date(int year, int month = 1, int day = 1)
		:_year(year)
		,_month(month)
		,_day(day)
	{
		cout << "Date(int year)构造" << endl;
	}

	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		cout << "Date(const Date& d)拷贝构造" << endl;
	}

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

int main()
{
	// 构造
	Date d1(2023, 1, 1);
	// 隐式类型的转换 构造+拷贝构造
	Date d2 = { 2023,1,1 };
	// 拷贝构造
	Date d3(d1);
	// 构造
	const Date& d4 = { 2023,1,1 };
	return 0;
}

在这里插入图片描述

3.explict关键字

explicit关键字用于修饰构造函数作用是禁止构造函数的隐式转换比如一下代码

class Date
{
public:
	explicit Date(int year, int month = 1, int day = 1)
		:_year(year)
		,_month(month)
		,_day(day)
	{
		cout << "Date(int year)构造" << endl;
	}

	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		cout << "Date(const Date& d)拷贝构造" << endl;
	}

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

int main()
{
	// 构造
	Date d1(2023, 1, 1);
	// 隐式类型的转换 构造+拷贝构造
	Date d2 = { 2023,1,1 };
	// 拷贝构造
	Date d3(d1);
	// 构造
	const Date& d4 = { 2023,1,1 };
	return 0;
}

在这里插入图片描述

4.类型转换的意义

构造函数的类型转换在某些特定的场景下有着很大的意义比如我们要在一个数组的尾部插入一个字符串

int main()
{
	string s("hello world");
	push_back(s);

	push_back("hello world");
}

如上我们有了隐式类型转换之后就不要先创建一个string对象然后再进行插入而是只需要以字符串做参数即可编译器会对其进行隐式类型的转换称为一个string类的对象。

三、Static成员

1.概念

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

我们以一个题目来介绍静态成员的相关知识点

题目实现一个类计算程序中创建出了多少个类对象

我们知道创建对象就一定会调用构造函数或者拷贝构造函数所以我们只需要定义一个全局变量然后在构造函数和拷贝构造函数类进行让其自增即可代码如下

#include <iostream>
using namespace std;

int Count = 0;
class A
{
public:
	A(int a = 0)
	{
		_a = a;
		++Count;
	}
	A(const A& a)
	{
		_a = a._a;
		++Count;
	}
private:
	int _a;
};

int main()
{
	A aa1;
	A aa2(aa1);
	A aa3 = 1;
	cout << Count << endl;
}

在这里插入图片描述

虽然使用全局变量的方法可以十分简便的达到题目的要求但是我们不建议使用全局变量因为全局变量可以被任何人修改十分不安全所以我们需要使用另外一种比较安全的方法–静态成员变量。

2.static成员变量

声明为static的类成员称为类的静态成员用static修饰的成员变量称之为静态成员变量其特征如下

1.静态成员所有类对象所共享不属于某个具体的对象存放在静态区

2.静态成员变量必须在类外定义定义时不添加static关键字类中只是声明

3.类静态成员即可用 类名::静态成员 或者 对象.静态成员来访问

1.静态成员存放在静态区(数据段)所以不在对象里面所以它不属于某个对象而是所有的对象共享。

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

	int _a;
	static int _n;
};

int A::_n = 0;

int main()
{
	A* a = nullptr;
	cout << a->_n << endl;
	cout << A::_n << endl;
}

在这里插入图片描述

我们可以看到当我们可以直接通过类名+域作用限定符或者通过一个空指针对对象进行访问说明_n并不存在对面里面。

2.类的静态成员在类中只是声明必须在类外进行定义且定义的时候需要指定类域其不在初始化列表进行定义初始化因为创建对象的时候并不会改变它的值。

在这里插入图片描述

3.静态成员变量的访问受类域和访问限定符的限制

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

private:
	int _a;
	static int _n;
};

int A::_n = 0;

int main()
{
	A* a = nullptr;
	cout << a->_n << endl;
	cout << _n << endl;
}

在这里插入图片描述

【注意】

静态成员变量在访问时和普通的成员变量区别不大同样受类域和访问限定符的约束只是由于静态成员变量存放在静态区中被所有的对象所共享所有我们可以通过指定域的方式对其进行访问此外静态成员变量在定义的时候只受类域的限制而没有受访问限定符的限制。

那么我们就可以使用静态成员变量来解决上面的题目了代码如下

class A
{
public:
	A(int a = 0)
	{
		_a = a;
		++_count;
	}
	A(const A& a)
	{
		_a = a._a;
		++_count;
	}
private:
	int _a;
	static int _count;
};

int A::_count = 0;

int main()
{
	A aa1;
	A aa2(aa1);
	A aa3 = 1;
	cout <<A:: _count << endl;
}

在这里插入图片描述

我们可以看到由于我们把成员变量设置成了私有在类的外面无法访问私有变量我们可以把私有转变为共有或者在类中提供获取成员变量的共有函数但是这样又破坏了类的封装性当初设计类的时候就行不想在类的外部随便更改和读取类中的成员那么我们该怎么解决这个问题呢针对这个问题C++设计出了静态成员函数。

3.static成员函数

用static修饰的成员函数称之为静态成员函数。其特性如下

1.静态成员变量一定要在类外进行初始化

2.静态成员函数没有隐藏的this指针不能访问任何非静态成员

3.静态成员也是类的成员受public、protected、private 访问限定符的限制

由于静态成员函数没有隐藏的this指针所以我们在调用的时候就不需要传递对象的地址即我们可以通过类名+域作用限定符直接调用而不需要创建对象但是相应的没有了this指针我们也无法调用非静态成员函数和成员变量因为非静态成员变量需要实例化对象来开辟空间非静态成员函数的调用则需要传递对象的地址。但是虽然静态成员函数不可以调用非静态成员但是非静态成员函数可以调用静态成员调用静态成员时编译器不需要传递对象的地址。

所以上面的题目正确的代码如下

class A
{
public:
	A(int a = 0)
	{
		_a = a;
		++_count;
	}
	A(const A& a)
	{
		_a = a._a;
		++_count;
	}

	static int Getcount()
	{
		return _count;
	}
private:
	int _a;
	static int _count;
};

int A::_count = 0;

int main()
{
	A aa1;
	A aa2(aa1);
	A aa3 = 1;
	cout <<A:: Getcount() << endl;
}

四、友元

我们去重载operator<<然后发现没办法将operator<<重载成成员函数。因为cout的输出流对象和隐含this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象才能正常使用。所以要将operator<<重载成全局函数。但又会导致类外没办法访问成员此时就需要友元来解决。operator>>同理。

流插入和流提取的重载我已经在类和对象的中篇进行了详细的说明大家有兴趣可以去看看这里我们就不再赘述。

1.友元函数

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

class Date
{
private:
	int _year;
	int _month;
	int _day;

public:
	//友元声明 声明可以在类中的任意位置
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator >>(istream& in, Date& d);
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;

		//检查日期是否合法
		if (!(year >= 1
			&& (month >= 1 && month <= 12)
			&& (day >= 1 && day <= GetMonthDay(year, month))))
		{
			cout << "非法日期" << endl;
		}
	}
}

// operator<<(cout, d1) cout<<d1
// ostream& operator<<(ostream& out, const Date& d);
// 流插入
inline ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "-" << d._month << "-" << d._day << endl;
	//连续打印返回out;
	return out;
}

// cin >> d1  operator(cin, d1)
// 流提取

inline istream& operator >>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	//连续输入
	return in;
}

【总结】

1.友元函数可以访问类的私有和保护成员但不是类的成员函数

2.友元函数不能用const修饰

3.友元函数可以在类定义的任何地方声明不受类访问限定符限制

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

5.友元函数的调用与普通函数的调用原理相同

2.友元类

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

class Time
{
	friend class Date; //声明日期类为时间类的友元类则在日期类中就直接访问Time类中的私有成员变量
public:
	Time(int hour = 0, int minute = 0, int second = 0)
		: _hour(hour)
		, _minute(minute)
		, _second(second)
	{}

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

	void SetTimeOfDate(int hour, int minute, int second)
	{
		// 直接访问时间类私有的成员变量
		_t._hour = hour;
		_t._minute = minute;
		_t._second = second;
	}

private:
	int _year;
	int _month;
	int _day;
	Time _t;
};

在这里插入图片描述

友元类的特点

1.友元关系是单向的不具有交换性比如上述Time类和Date类在Time类中声明Date类为其友元类那么可以在Date类中直接访问Time类的私有成员变量但想在Time类中访问Date类中私有的成员变量则不行

2.友元关系不能传递如果C是B的友元 B是A的友元则不能说明C时A的友元

3.友元关系不能继承

五、内部类

概念如果一个类定义在另一个类的内部这个内部类就叫做内部类。内部类是一个独立的类它不属于外部类更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限

class A
{
private:
	static int k;
	int h;
public:
	class B // B天生就是A的友元
	{
	public:
		void foo(const A& a)
		{
			cout << k << endl;//OK
			cout << a.h << endl;//OK
		}
	};
};
int A::k = 1;
int main()
{
	A::B b;
	b.foo(A());

	return 0;
}

在这里插入图片描述

内部类的特性

1.内部类就是外部类的友元类参见友元类的定义内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元

2.内部类可以定义在外部类的public、protected、private都是可以的

3.注意内部类可以直接访问外部类中的static成员不需要外部类的对象/类名

4.sizeof(外部类)=外部类和内部类没有任何关系

六、匿名对象

在C++中除了用类名+对象创建对象外我们还可以直接使用类名来创建匿名对象匿名对象和正常对象一样在创建时自动调用构造函数在销毁时调用析构函数但是匿名对象的生命周期只有在定义的那一行下一行就会立马销毁。

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a = 0)构造" << endl;
	}
	~A()
	{
		cout << "~A()析构" << endl;
	}

private:
	int _a;
};

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

在这里插入图片描述

匿名对象的应用有如下场景对于其他场景我们遇到了再说

class Solution
{
public:
	int Sum_solution(int n)
	{
		//...
		return n;
	}
};

int main()
{
	//Solution so;
	//so.Sum_solution(0);

	Solution().Sum_solution(0);
	return 0;
}

在上面的代码中我们使用Solution().Sum_solution(0)一行代替了Solution so;so.Sum_solution(0);两行。

七、拷贝对象时的一些编译器优化

在传参和传值返回的过程中一般编译器会做一些优化减少对象的拷贝这个在某些场景写是十分有用的。

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}
	A(const A& aa)
		:_a(aa._a)
	{
		cout << "A(const A& aa)" << endl;
	}
		A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a = aa._a;
		}
		return *this;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};

//传值传参
void f1(A aa)
{}

//传值返回
A f2()
{
	//A aa;
	//return aa;
    return  A();
}
int main()
{
	// 传值传参
	A aa1;
	f1(aa1);
	cout << endl;
	// 传值返回
	f2();
	cout << endl;
	// 隐式类型连续构造+拷贝构造->优化为直接构造
	f1(1);
	// 一个表达式中连续构造+拷贝构造->优化为一个构造
	f1(A(2));
	cout << endl;
	// 一个表达式中连续拷贝构造+拷贝构造->优化一个拷贝构造
	A aa2 = f2();
	cout << endl;
	// 一个表达式中连续拷贝构造+赋值重载->无法优化
	aa1 = f2();
	cout << endl;
	return 0;
}

优化场景1传参隐式类型转换–构造+拷贝构造 ->直接构造

在这里插入图片描述

我们调用f1函数并使用1作为参数由于1和f1的形参不同所以会发生隐式类型的转换即编译器会先使用1去构造A类型的临时变量然后用这个临时变量去拷贝构造aa所以这里本来应该是构造+拷贝构造但是编译器将其优化直接使用1去构造aa

优化场景2匿名对象–构造+拷贝构造->直接构造

在这里插入图片描述

原本是用2来构造一个匿名对象再用匿名对象来拷贝构造aa经过编译器优化后直接使用2去构造aa

优化场景3传值返回–构造+拷贝构造+拷贝构造->直接构造

在这里插入图片描述

f2返回的是局部的匿名对象所以编译器会先用匿名对象去拷贝构造一个临时对象然后再用临时对象来拷贝构造aa2,而经过编译器的优化直接使用无参构造aa2即构造+拷贝构造+拷贝构造优化为直接构造。

【注意】

编译器只能对一句表达式中的某些操作进行优化因为编译器必须保证程序的正确性所以不能将两句表达式优化成一句避免程序发生错误。

八、再次理解类和对象

现实生活中的实体计算机并不认识计算机只认识二进制格式的数据。如果想要让计算机认识现实生活中的实体,用户必须通过某种面向对象的语言对实体进行描述然后通过编写程序创建对象后计算机才可以认识。比如想要让计算机认识洗衣机就需要

1.用户先要对现实中洗衣机实体进行抽象—即在人为思想层面对洗衣机进行认识洗衣机有什么属性有那些功能即对洗衣机进行抽象认知的一个过程

2.经过1之后在人的头脑中已经对洗衣机有了一个清醒的认识只不过此时计算机还不清楚想要让计算机识别人想象中的洗衣机就需要人通过某种面相对象的语言(比如C++、Java、Python等)将洗衣机用类来进行描述并输入到计算机中

3.经过2之后在计算机中就有了一个洗衣机类但是洗衣机类只是站在计算机的角度对洗衣机对象进行描述的通过洗衣机类可以实例化出一个个具体的洗衣机对象此时计算机才能洗衣机是什么东西。

4.用户就可以借助计算机中洗衣机对象来模拟现实中的洗衣机实体了。

在类和对象阶段大家一定要体会到类是对某一类实体(对象)来进行描述的描述该对象具有那些属性那些方法描述完成后就形成了一种新的自定义类型才用该自定义类型就可以实例化具体的对象

在这里插入图片描述

九、总结

初始化列表的特性(重要)

1.初始化列表是每个成员变量定义和初始化的地方所以每个成员变量(内置类型和自定义类型)无论我们是否显示在初始化列表出写都一定会走初始列表并且初始化操作只能进行一次即每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)

2.如果我们在初始化列表显示写了编译器就会用显示写的来初始化如果我们没有在初始化列表显示的写那么对于内置类型有缺省值用缺省值没有编译器就会使用随机值来初始化对于自定义类型编译器会调用自定义类型的默认构造函数来初始化如果没有默认构造编译器就会报错

3.类中包含以下成员(引用const没有默认构造的自定义成员)必须放在初始化列表位置进行初始化

4.尽量使用初始化列表进行初始化因为无论我们是否在初始化列表显示初始化类的成员函数在初始化的时候都会先使用初始化列表进行初始化

5.在C++11中对于内置类型打了一个补丁–内置类型成员变量可以在声明的时候给一个缺省值其可以在初始化列表起作用

6.成员变量在类中声明次序就是其在初始化列表中的初始化顺序与其在初始化列表中的先后次序无关

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: c++

“【C++】类和对象【下篇】--初始化列表,static成员,友元,内部类,匿名对象” 的相关文章