类和对象(下)

147 阅读8分钟

类的6个默认成员函数

用户没有显式实现,编译器会自动生成的成员函数,称为默认成员函数.

例如下面这个空类,什么都没写,但实际上已经默认生成了6个成员函数.

class EmptyClass
{}

image.png

构造函数

引出

当用类实例化一个对象后,一般都要先初始化对象,调用init()之类的函数初始化.

但有时会忘记初始化,导致程序崩溃或出现随机值.

能不能保证用类实例化对象时,对象自动被初始化呢?构造函数横空出世.

特性

构造函数是特殊的成员函数,和普通函数的定义不同、调用规则不同.

(1) 定义不同:

函数名与类名相同

函数无返回值

class Date
{
public:
        //函数名与类名相同,无返回值
	Date()
	{
		_year = 2022;
		_month = 11;
		_day = 14;
	}

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

(2) 调用规则不同:

用类实例化对象时,编译器自动调用对应的构造函数,若无匹配则报错

(3) 构造函数可以实现函数重载

class Date
{
public:
	Date()//构造函数1
	{	
                _year = 2022;	
		_month = 11;
		_day = 14;
	}

	Date(int year, int month, int day)//构造函数2
	{
		this->_year = year;//成员函数内部可以显示使用this,也可以不使用
		_month = month;
		_day = day;
	}

	Date(int year, int month)//构造函数3
	{
		_year = year;
		_month = month;
		_day = 14;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;              //调用构造函数1
	Date d2(2022, 11, 15);//调用构造函数2
	Date d3(2022, 12);    //调用构造函数3
	return 0;
}

(4) 如果类中没有显式定义构造函数,则c++编译器会自动生成一个无参的默认构造函数

一旦用户显式定义构造函数,编译器将不再生成.

(5) 构造函数不是给对象开空间,而是用来初始化对象的.

默认构造函数

不需要传参就能调用的构造函数.

(1) 我们不写,编译器默认生成的无参构造函数

(2) 自己写的全缺省构造函数

(3) 自己写的无参构造函数

但(2)、(3)不能同时出现,因为调用时会产生歧义

class Date
{
public:
	Date()//构造函数1
	{
		_year = 2022;
		_month = 11;
		_day = 14;
	}
	Date(int year = 2022, int month = 12, int day = 14)//构造函数2
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;//调用构造1还是构造2?
	return 0;
}

默认生成的无参构造函数

特点

(1) 内置类型不处理

(int、double、所有指针类型)

(2) 自定义类型去调用自定义类型的默认构造函数

(若自定义类型没有默认构造函数就报错)

作用

有些类的成员变量全是自定义类型,就不需要显式写构造函数,

默认生成的就够用,前提是那个自定义类型的构造函数写好了.

小结

(1) 一般的类都不会用编译器默认生成的构造函数,

都会自己写,最好写一个全缺省的构造函数.

(2) 建议每个类都要有默认构造函数.

初始化列表【重点】

引出

c++11针对内置类型成员不初始化的缺陷,打了一个补丁.

允许在成员变量声明的地方给缺省值.

class Date
{
public:
	Date(){}
private:
	int _year = 2022;//这里是声明同时给缺省值,不是定义
	int _month = 11;
	int _day = 14;
};

针对c++11给成员变量缺省值的新特性,我们来研究这个缺省值什么时候起效果.

class Date
{
public:
        //无论是否显式写构造函数,d1均被初始化为2022 11 14
	//Date(int year = 1, int month = 1, int day = 1){}
	int GetYear() {return _year;}
	int GetMonth() {return _month;}
	int GetDay() {return _day;}
private:
	int _year = 2022;
	int _month = 11;
	int _day = 14;
};
int main()
{
	Date d1;
	cout << d1.GetYear() << " " << d1.GetMonth() << " " << d1.GetDay() << endl;
	return 0;
}

现象:

1 无论是否显式写构造函数,该缺省值都会起效

2 若显示写构造函数,且在函数体内修改了成员变量,最后对象的结果才不是缺省值

但通过调试发现,当刚进入函数体内时,对象已经用缺省值进行初始化了.

(*this就是当前要初始化的对象)

class Date
{
public:
	Date()
	{
		_year = 999;
		_month = 1;
		_day = 1;
	}
	int GetYear() {return _year;}
	int GetMonth() {return _month;}
	int GetDay() {return _day;}
private:
	int _year = 2022;
	int _month = 11;
	int _day = 14;
};
int main()
{
	Date d1;
	cout << d1.GetYear() << " " << d1.GetMonth() << " " << d1.GetDay() << endl;
	return 0;
}

image.png

而真正使用这个缺省值的位置,就是初始化列表.

格式

以一个冒号开始,逗号分隔,每一个成员变量后跟一个放在括号中的初始值或表达式.

        Date()
            :_year(29)
            ,_month(11)
            ,_day(15)
	{
            //函数体
        }  

若没有显式使用初始化列表初始化某个成员变量,列表就会使用缺省值.

内置类型成员变量若没有缺省值,就是随机值.

特性

(1) 每个构造函数都有一个初始化列表.(无论是否显式写)

因为实例化对象只会调用一个匹配的构造函数.

        Date()
            :_year(29)
            ,_month(11)
            ,_day(15)
	{}
	Date(int year, int month, int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{}

(2) 每个成员变量在初始化列表中只能出现一次.(初始化只能初始化一次)

(3) 初始化列表是成员变量初始化的地方,每个成员变量都要走一遍初始化列表.

对于内置类型成员:

没有给缺省值,没有在初始化列表中显式给值,不做处理,仍然是随机值

给了缺省值,没有在初始化列表中显式给值,就会用这个缺省值

如果在初始化列表中显式给值,缺省值不起作用.

对于自定义类型成员:

没有在初始化列表中显式调用构造函数初始化,编译器自动调用自定义类型的默认构造函数

如果显式调用,自定义类型的默认构造不起作用

class Position
{
public:
	Position()//默认构造
		:_x(99)
		,_y(100)
	{}
	Position(int x)//构造函数2
		:_x(x)
		,_y(0)
	{}
private:
	int _x;
	int _y;
};

class A
{
public:
	A(int a,int b, int x)
		:_pos(x)//显示调用自定义类型的构造函数2
		,_a(a)  //初始化列表显式初始化a,此时缺省值无效
		,_b(b)
	{}
private:
	int _a = 1;
	int _b;
	Position _pos;
};

(4) 必须放在初始化列表进行初始化的成员:

引用类型成员变量(只有一次初始化的机会)

const成员变量(只有一次初始化的机会)

没有默认构造函数的自定义类型成员

(如果不显式用初始化列表初始化自定义类型成员,就会去自动调用它的默认构造函数,

但该类型没有默认构造函数,就会报错)

class Position
{
public:
	Position(int x, int y)
		:_x(x)
		,_y(y)
	{}
private:
	int _x;
	int _y;
};

class A
{
public:
	A(int& a, int con = 1, int x = 1, int y = 1)
		:_con(con)//const成员
		,_quote(a)//引用成员
		,_pos(x, y)//没有默认构造的自定义类型成员,只能在初始化列表中手动调用
	{}
private:
	const int _con;
	int& _quote;
	Position _pos;
};

(5) 初始化列表初始化成员变量的顺序和声明顺序一致,与手动初始化的顺序无关.

class Date
{
public:
	Date(int month)
		:_month(month) //初始化列表初始化的顺序也是_year、_month
		,_year(_month)
	{}
private:
	int _year;//声明顺序是_year 、_month
	int _month;
};

int main()
{
	Date d1(3);
	return 0;
}

image.png

小结

自定义类型成员、内置类型成员都推荐使用初始化列表初始化.

(1) 自定义类型成员如果不手动在初始化列表初始化,

编译器仍然会在初始化列表自动调用自定义类型的默认构造函数(没有则报错)

(2) 内置类型成员如果不手动在初始化列表初始化,

编译器会使用声明时给的缺省值(没有给缺省值就为随机值).

(3) const类型成员、引用类型成员、没有默认构造函数的自定义类型成员

必须手动用初始化列表初始化.

析构函数

作用

对象在生命周期结束后会销毁,此时编译器会自动调用析构函数,完成对象内部资源的清理工作.

(一般如果该类的构造函数有在堆上动态开辟空间,就需要在析构函数里手动释放空间)

特性

(1) 析构函数名是在类名前加上~,例:~Stack()

(2) 无参无返回值

(3) 一个类只有一个析构函数,如果没有显式定义,就会生成默认的析构函数

【默认的析构函数对内置类型不处理,自定义类型会调用它的析构函数】

(4) 对象生命周期结束,编译器自动调用析构函数清理空间

~Date(){}//没有资源需要清理

注意

在同一栈帧中的类对象,后定义的先析构;

同在静态区的所有对象,后定义的先析构;

栈区对象的析构一定比静态区对象的析构要快.

image.png

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

A a3(3);
A a4(4);
int main()
{
	A a1(1);
	A a2(2);
	static A a5(5);
	cout << endl;
	return 0;
}

小结

(1) 析构函数用来完成对象内部资源的清理工作;

(2) 默认生成析构函数对内置类型不处理,自定义类型会调用它的析构函数,

如果一个类只有自定义类型 或者 没有资源需要清理,就不用显式写析构函数,

例如:

  Time/Date类【没有资源需要清理】

  用2个栈实现的Queue【只有自定义类型】

拷贝构造函数

引入

创建对象时,创建一个与已存在对象一模一样的新对象,例:

int i = 0;
int j = i;//用i拷贝构造出j

想要让自定义类也能实现以上功能,于是出现了拷贝构造函数。

Stack st1;
Stack st2(st1);//兼容c,也可以Stack st2 = st1

Date d1(2023, 8, 2);
Date d2(d1);//Date d2 = d1

特征

(1)它是构造函数的一个重载形式,它也是构造函数

(2)它只有一个形参,该形参必须是本类型的引用(后面重点)

(3) 在用【已存在同类对象】创建新对象时,编译器会自动调用拷贝构造函数

(4) 不显式写,编译器自动生成,内置类型会按字节逐个拷贝,自定义类型调用其拷贝构造.

以Date类为例:

Date(Date& d)
{
    _year = d._year;
    _month = d._month;
    _day = d._day;
}

Date d1;
Date d2(d1);//自动调用拷贝构造函数

为什么该形参必须是本类型的引用?

若是传值传参:

调用拷贝构造函数时,形参是实参的拷贝,传参会再次调用拷贝构造,就会引发无穷递归.

image.png

该形参一般用const修饰

一方面,防止代码写错,造成已有对象被修改.

image.png

另一个点是可以用const类型对象进行拷贝构造,不用const修饰形参会造成权限的缩小

  Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
        
  const Date d1;
	Date d2(d1);

小结

(1) 用已有的类对象创建新对象时,会调用拷贝构造函数

(2) 默认生成的拷贝构造函数对内置类型不处理,自定义类型调用其拷贝构造函数,

因此Date类不需要手动写,默认生成的够用

(3) 拷贝构造函数的形参必须是同类型对象的引用,同时最好用const修饰.

(4) 像Stack类,必须要自己写拷贝构造函数,

因为按字节拷贝会导致2个栈对象内部的指针指向同一个空间 —— 同一空间析构2次

另外一个栈修改还会影响另一个栈.

class Stack
{
public:
	Stack(int capacity = 5)
		:_top(0)
		,_capacity(capacity)
	{
		arr = (int*)calloc(_capacity, sizeof(int));
	}
	//拷贝构造函数自动生成

	void push(int num)
	{
		if (_top == _capacity)
		{
			//扩容
			int* tmp = (int*)realloc(arr, 2 * _capacity);
			_capacity *= 2;
		}
		arr[_top] = num;
		_top++;
	}
	void pop()
	{
		if(_top > 0)
			_top--;
	}

	~Stack()
	{
		free(arr);
	}
	
private:
	int _top;
	int _capacity;
	int* arr;
};

image.png

image.png

运算符重载

引入

内置类型可以直接使用运算符运算,编译器知道如何运算

int i = 0;
int j = 1;
i++;  j--;
i + 10;  j - i;
i == j; i > j; i < j;

但自定义类型不能直接使用这些运算符运算。

为了让自定义类型支持使用运算符,引入了运算符重载。

运算符重载本质是拥有特殊函数名的函数

特征

(1) 函数名字:operator接需要重载的运算符符号

例:operator==,operator+,operator++等等,这些都是特殊函数名称

(2) 函数原型:返回值类型 operator操作符(参数列表)

例:bool operator==(Position p1, Position p2);

若有两个参数,左参数是左操作数,右参数是右操作数

参数列表和返回值一般看操作符功能自己选定

(3) 为了能在函数内部直接使用类的成员变量,一般运算符重载都会作为类的成员函数。

类的成员函数内部,只要是该类的对象,都不受访问限定符的限制

class Position
{
public:
	Position(int x = 0, int y = 0)
		:_x(x)
		,_y(y)
	{}
        
	//左操作数是隐藏的this指针
	//为了减小拷贝,右操作数使用引用传参
	//为了右操作数能传const类型,使用const引用
	bool operator==(const Position& p1)
	{
		return p1._x == _x && p1._y == _y;
	}
        
private:
	int _x;
	int _y;
};

此时调用位置如下:

int main()
{
	Position p(3, 5);
	Position p1(3, 5);
        
  //p == p1 会被编译器转化为 p.operator==(&p,p1),&p不能显式传
	cout << (p == p1);
  cout << (p.operator==(p1));
	return 0;
}

image.png

赋值运算符重载

引入

已经存在的两个自定义类型对象,想要把一个对象的值赋值(拷贝)给另一个对象。例:

  int a = 1;
	int b = 2;
	a = b;//把b拷贝给a
        
  Date d1(2023,8,2);
  Date d2(1,1,1);
  d2 = d1;//通过调用赋值运算符重载实现

特征

(1) 函数原型:类& operator=(const 类& 名)

例:Date& operator=(const Date& d)

(2) 返回左操作数的引用 —— 为了支持连续赋值,同时出了该函数作用域*this不会销毁

例:a = b = 3;

d1 = d2 = d3;

参数有const修饰,同时是类对象引用 —— 减少拷贝,并且能引用const类型对象

Date& operator=(const Date& d)
{
    //内部完成值拷贝
    return *this;
}

Date d1(2023, 8, 2);
Date d2(2023, 8, 3);
d2 = d1;//编译器会转化为d2.operator(&d2, d1)

(3) 类里自己不写,编译器会默认生成赋值运算符重载。

生成的赋值运算符重载,内置类型成员按字节逐个赋值,自定义类型成员调用其赋值运算符重载

Date类的运算符重载

(1) 日期 += 天数

同时用日期 += 天数 实现 日期 + 天数

天数不合法,向下一个月份进位,同时减去本月的所有天数,代表这个月过完了

   //获取该月的天数
	int GetMonthDay(int year, int month)
	{
		//声明为static,不需要重复创建该数组
		static int ret[13] = { 0, 31, 28,31,30,31,30,31,31,30,31,30,31 };
		if (month == 2 &&
			((year % 400 == 0) || (year % 4 == 0 && year % 100 != 0))
			)
		{
			return 29;
		}
		return ret[month];
	}
        
  Date& operator+=(int day)//日期加天数,计算day天后的日期是多少
	{
		_day += day;//先累加天数,若天数合法直接返回,不合法就进月,月份超出进年
		
		//当前月的最大天数
		int maxDay = GetMonthDay(_year, _month);

		//天数不合法
		while(_day > maxDay)
		{
			_day -= maxDay;
			_month++;

			//月份超出
			if (_month == 13)
			{
				_month = 1;
				_year++;
			}
			maxDay = GetMonthDay(_year, _month);
		}
		//因为*this出了该函数作用域后不销毁,所以采用引用返回,减少拷贝
		return *this;
	}
        
        
  //d1 + 10 是不会改变d1的
	Date operator+(int day)
	{
		//拷贝构造一份ret,不能改变*this的内容
		Date ret = *this;
		
		//复用+=
		ret += day;
		//ret出了函数作用域,就销毁了,必须传值返回
		return ret;
	}

image.png

(2) 日期 -= 天数

同时用日期 -= 天数 实现 日期-天数

天数不合法,需要向上一个月份借天数

//日期 -= 天数,计算day天前的日期
	Date& operator-=(int day)
	{
		_day -= day;
                //若天数不合法,需要向上一个月份借天数
		while (_day <= 0)
		{
			_month--;
                        //月不够就向年借
			if (_month == 0)
			{
				_month = 12;
				_year--;
			}
			_day += GetMonthDay(_year, _month);
		}
		return *this;
	}

	//日期-天数
	Date operator-(int day)
	{
		Date ret = *this;
		ret -= day;
		return ret;
	}

image.png

(3) 前置++和后置++的运算符重载(--类似)

如果直接按特性重载该运算符,调用时无法区分;

所以c++特殊处理,后置++的重载增加一个int参数,跟前置构成函数重载区分

Date& operator++();//前置,没有参数
Date operator++(int);//后置,加一个int参数

++d1;//编译器转化为 d1.operator++(&d1);
d1++;//编译器转化为 d1.operator++(&d1,0),整数传什么不重要

具体实现:

前置++返回+之后的值,把this+=1后,直接返回this;(可以用引用返回)

后置++返回+之前的值,先创建对象tmp保存先前的值,再对*this+=1,返回tmp(只能传值返回)

  //前置++
	Date& operator++()
	{
		return *this += 1;
	}
	//后置++
	Date operator++(int)
	{
		Date tmp = *this;
		++(*this);
		return tmp;
	}

补充

【.*】 、【 ::】 、【sizeof 】、【?:】 、【.】

以上5个运算符不能重载

const成员函数

什么是const成员函数

const修饰的成员函数就是const成员函数

本质是修饰该成员函数隐藏的形参this

例:

bool operator==(const Date&d)
{
    //比较逻辑
}
//此时隐藏的this指针—— Date* const this

this指针的指向不能变,但是指向的内容仍可以修改

为了万无一失,如何才能让this指向的内容在该函数内不可修改呢?

bool operator==(const Date&d)const//const成员函数
{}
//此时this指针 —— const Date* const this

用const修饰,表示在该成员函数中不能对this的任何成员作修改

image.png

作用

const类对象能调用const成员函数,无法调用普通成员函数,因为会导致权限的扩大

  const Date d1(2023, 8, 2);
	d1.print();//编译器会转化为d1.print(&d1) 即const Date* 类型
        
        
  void print()//此时的this类型:Date* const this
	{
		cout << _year << "      " << _month << "             " << _day << endl;
	}       

此时要把print设为const成员函数才能让const类对象正常调用

建议:成员函数内部如果不需要改变this指针指向的内容,都应该作为const成员函数

普通类对象可以调用,const类对象也可以调用.

取地址重载及const取地址重载

一般不用自己定义,编译器默认会生成

  const Date* operator&()const
	{
		cout << "&const" << endl;
		return this;
	}
	Date* operator&()
	{
		cout << "&" << endl;
		return this;
	}
        
  const Date d1(2023, 7, 1);
	Date d2(2023, 8, 1);
	&d1;
	&d2;

它们会构成函数重载,一个是const Date*const this,

另一个是Date* const this

explicit关键字

引入

只需传一个参数就能调用的构造函数,具有类型转换作用

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

int main()
{
	//先用10构造出临时对象tmp(隐式类型转换),再用tmp拷贝构造a ——> 编译器优化为 直接构造
	A a = 10;
        
        A a(10);//直接调用构造,没有发生隐式类型转换
	return 0;
}

作用

修饰构造函数,禁止隐式类型转换发生

image.png

static成员变量与成员函数

static成员变量

用static修饰的成员变量为静态成员变量.

特征

(1) 必须在类外进行初始化,无法用初始化列表初始化静态成员变量.

class A
{
public:A(){}
private:
	static int _st;//仅仅是声明
};
//在类外初始化,用::指定类域
int A::_st = 0;

(2) 存储在静态区,程序刚开始运行就一直存在.

image.png (3) 所有的类对象共用 同一个 static 成员变量【重点】

因为_st被设置为private,除定义时外,类外不能用A::_st的方式访问

class A
{
public:
	A()
	{
		//该类每创建出一个新对象,就++_st,最后可以统计用该类一共创建了多少对象
		_st++;
	}
  
  //获取_st的值
	int GetStaticVal()
	{
		return _st;
	}
private:
	static int _st;
};
int A::_st = 0;

int main()
{
	A a1;
	A a2;
	A a3;
	cout << a1.GetStaticVal() << endl;
	cout << a2.GetStaticVal() << endl;
	cout << a3.GetStaticVal() << endl;
	return 0;
}

image.png

(4) 由于静态成员变量存储在静态区,类计算大小不计算它

image.png

(5) 静态成员变量在类外面:

可以用对象.静态成员变量来访问

或者用类域::静态成员变量来访问

但仍受到访问限定符的限制

static成员函数

用static修饰的成员函数为静态成员函数.

特征

(1) 静态成员函数在类外(与静态成员变量一致):

可以用对象.静态成员函数来访问

或者用类域::静态成员函数来访问【有些场景下不需要再专门创建对象,来访问该函数】

但仍受到访问限定符的限制

image.png

(2) 静态成员函数没有this指针,无法访问非静态成员,可以访问静态成员【重点】

注意:这里的静态成员包括 静态成员函数 和 静态成员变量 .

本质上是 普通的成员函数,在访问成员变量和其他成员函数时,前面都会默认加上this->,

非静态的成员函数隐藏的this指针可以帮助访问成员变量和其他成员函数,

失去了this指针就只能访问静态的成员变量和成员函数

image.png

(3) 非静态成员函数可以访问静态成员函数或静态成员变量

统计一个类创建了多少对象

class A
{
public:
	A()//构造
	{
		//每进入一次构造函数,说明创建了一个对象
		++_st;
	}
	A(const A& a)//拷贝构造
	{
		++_st;
	}
	static void printNum()//声明为static,不需要创建任何对象,都能用A::调用
	{
		cout << "目前创建A类对象数目:" << _st << endl;
	}
private:
	static int _st;
};
int A::_st = 0;
int main()
{
	A::printNum();

	A a1; A a2; A a3;
	A::printNum();
	return 0;
}

image.png

友元函数/友元类

友元函数

友元函数是定义在类外的普通函数,【不属于任何类】,需要在类的内部声明,

声明时加friend关键字.例:

class A
{
	//可以放在类内任意地方,不受访问限定符限制
	friend void func();
public:
	A()
	:_a(1)
	,_b(1)
	{}
private:
  void print(){};
	int _a;
	int _b;
};

void func()//友元函数
{
	//由于func是A类的友元函数,内部可以用A的对象直接访问私有/保护成员
	A a;
        a.print();
	cout << a._a << " " << a._b << endl;
}

int main()
{
	func();
	return 0;
}

image.png

注意

(1) 友元函数内部,可以用类对象直接访问类的私有/保护成员.

但它不是类的成员函数,也没有this指针!!!

image.png

(2) 友元函数不能有const修饰,const是修饰this指向的内容,但友元函数没有this指针

image.png

(3) 一个函数可以是多个类的友元函数

void func(const A& a, const B& b, const C& c);

此时可以考虑把func作为A、B、C类的友元函数.

友元类

友元类的所有成员函数都可以直接访问另一个类的私有成员函数/变量.

class A
{
	friend class B;//B是A的友元类
public:
	A():_a(1),_b(1){}
	void APrint() {};
	static void APublicStatic() {};
private:
	static void APrivateStatic() {};
	int _a;int _b;
};
class B
{
public:
	B(){}
	void testFriend()
	{
		A a;
		a.APrint();
		a.APublicStatic();
		a.APrivateStatic();
		cout << a._a <<"  " << a._b << endl;
	}
};

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

注意

(1) 友元类是单向性的,B是A的友元类,B里成员函数可以直接访问A的私有成员,

但A不能直接访问B的私有成员.

(2) 友元关系不能传递,例:

A是B的友元类,B是C的友元类,但A不是C的友元.

(3)友元破坏了封装,增加了程序之间的耦合度,尽量少用.

内部类

一个类定义在另一个类的内部,这个处于内部的类就是内部类。

class A
{
public:
	class B//此时B就是内部类
	{
	private:
		double _b;
	};
private:
	int _a;
};

特征

(1) 内部类B 受 外部类A 的类域 和 访问限定符 限制,

想要使用B类必须先指定A这个类域,同时还必须被public修饰.

  //B b; —— 报错
	A::B b;

(2) 内部类天生是外部类的友元类,内部类可以直接访问外部类的私有成员.【重点】

class A
{
public:
	class B//此时B就是内部类
	{
	public:
		void test()
		{
			//直接用外部类对象,访问外部类的私有成员
			A a;
			a._a = 1;
			cout << "_a修改成功:" << a._a << endl;
		}
	};
private:
	int _a;
};

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

image.png

(3) 内部类和普通类计算大小没有区别,A类的大小与B无关,B类的大小与A无关

image.png