C++ 类复制构造函数,浅/深拷贝,默认赋值运算符重载,友元类,内部类

目录

一、复制构造函数

1、定义

2、隐式调用场景

3、浅拷贝

4、深拷贝

二、赋值运算符重载函数

三、友元类

四、内部类和局部类

1、局部类定义

2、内部类定义

3、内部类和外部类的互相访问


一、复制构造函数

1、定义

    复制构造函数同默认构造函数一样是编译器在程序没有显示定义对应函数时自动添加的特殊成员函数,复制构造函数的入参是一个const或者非const的类引用,可以有其他入参,但是必须有默认值。

class ClassA{
private:
	int a=1;
	int b=2;
public:
	//构造函数
	ClassA(int i,int j);
	//析构函数
	~ClassA();
	//复制构造函数
//	ClassA(const ClassA & ca);
//	ClassA(ClassA & ca);
	ClassA(ClassA & ca,int i=2,int j=2);
};


ClassA::ClassA(int i,int j){
	a=i;
	b=j;
	std::cout<<"execute ClassA(int i,int j),this->"<< this<<"\n";
}

//ClassA::ClassA(const ClassA & ca){
//	std::cout<<"execute ClassA(const ClassA & ca),this->"<"<"<"<< this<<"\n";
}

int main(){
	ClassA a(1,2);
    ClassA b=a;
    ClassA b2=a;
    ClassA b3=a;

	return 0;
}

执行结果如下,注意析构函数按对象创建的顺序倒序执行,即后创建先执行,主要是为了避免后面创建的对象对前面创建的对象有依赖的情形下删除前面创建的对象会产生不可预知的后果。

C++ 类复制构造函数,浅/深拷贝,默认赋值运算符重载,友元类,内部类_第1张图片

2、隐式调用场景

《C++ Primer Plus》中提到下列情形下编译器会隐式调用复制构造函数:

  1.  以某个对象实例初始化另一个同类型的对象
  2.  对象以实例而非引用或者指针的形式作为方法入参
  3.  以对象实例而非引用或者指针的形式作为方法的返回值
  4.  方法连续调用过程中产生临时对象

第四种实际上跟第三种是一样的,因为只有方法的返回值是对象实例而非引用或者指针的时候才会产生临时对象,大部分博客说的是前面三种情形。但是在当前支持C++11标准的GNU C比如版本 4.8.5 下,第三条在返回的对象实例是在方法中创建的情形下不成立,参考如下测试用例:

#include 

class ClassA{
private:
	int a=1;
	int b=2;
	static int c;
public:
	//构造函数
	ClassA(int i,int j);
	//析构函数
	~ClassA();
	//复制构造函数
	ClassA(const ClassA & ca);
	//运算符重载
	ClassA operator+(const ClassA & ca);
	//友元函数
	friend void print(ClassA cl);
};

int ClassA::c=0;

ClassA::ClassA(int i,int j){
	a=i;
	b=j;
	c++;
	std::cout<<"execute ClassA(int i,int j),this->"<< this<<"\n";
}

ClassA::~ClassA(){
	c--;
	std::cout<<"execute ~ClassA,this->"<< this<<"\n";
}

ClassA::ClassA(const ClassA & ca){
	a=ca.a;
	b=ca.b;
	c++;
	std::cout<<"execute ClassA(const ClassA & ca),this->"<

执行的结果如下:

C++ 类复制构造函数,浅/深拷贝,默认赋值运算符重载,友元类,内部类_第2张图片

      为什么第三种和第四种情形下调用了构造函数而没有调用复制构造函数?从反汇编找答案。之前在Java程序员自我修养——C与汇编语言探讨过结构体作为入参和返回值时编译器会通过寄存器传递或者直接内存拷贝的方式在调用函数与被调函数的栈帧之间复制结构体占用的内存,类在内存的表示跟结构体是一样的,那么处理方式是否一样了?

main方法中执行print(b)的汇编代码如下:

lea    -0x50(%rbp),%rdx
lea    -0x30(%rbp),%rax
mov    %rdx,%rsi
mov    %rax,%rdi
callq  0x4009dc    调用复制构造函数,rdi是待创建的对象的地址,即this指针,rsi中是方法入                                                                                      参类引用。
lea    -0x30(%rbp),%rax
mov    %rax,%rdi
callq  0x400a9e     调用print方法,因为print是非类成员没有this指针,rdi中保存的是方法入参ClassA类实例的地                                                         址,该实例是通过复制构造函数创建出来的
lea    -0x30(%rbp),%rax
mov    %rax,%rdi
callq  0x400994     调用析构函数销毁调用print()时临时创建的一个对象

执行复制构造函数ClassA::ClassA(ClassA const&)的主要汇编代码如下:

mov    %rdi,-0x8(%rbp)
mov    %rsi,-0x10(%rbp)  将寄存器参数拷贝至栈帧中
mov    -0x8(%rbp),%rax
movl   $0x1,(%rax)   初始化属性 int a=1
mov    -0x8(%rbp),%rax   
movl   $0x2,0x4(%rax)   初始化属性 int b=2
mov    -0x10(%rbp),%rax  
mov    (%rax),%edx  
mov    -0x8(%rbp),%rax
mov    %edx,(%rax)    完成属性赋值 a=ca.a
mov    -0x10(%rbp),%rax
mov    0x4(%rax),%edx
mov    -0x8(%rbp),%rax
mov    %edx,0x4(%rax) 完成属性赋值 b=ca.b
mov    0x201773(%rip),%eax       
add    $0x1,%eax
mov    %eax,0x20176a(%rip)   修改静态属性 c++

执行print(ClassA)的的部分汇编代码:

mov    0x2016df(%rip),%ebx   将静态变量c的值放入ebx中
mov    -0x28(%rbp),%rax
mov    0x4(%rax),%r12d  将 属性b放入r12d中
mov    -0x28(%rbp),%rax
mov    (%rax),%r13d  将 属性a放入r12d中

main方法中执行getA方法的部分代码如下:

lea    -0x60(%rbp),%rax
mov    $0x3,%edx
mov    $0x2,%esi
mov    %rax,%rdi     准备调用参数,getA不是类成员函数,但是依然将待创建的对象的内存地址即rdi中的值作为第一个参数传入                                   函数中
callq  0x400b25   

getA方法的部分代码如下:

mov    -0x10(%rbp),%edx
mov    -0xc(%rbp),%ecx
mov    -0x8(%rbp),%rax
mov    %ecx,%esi
mov    %rax,%rdi
callq  0x40091e    将传入getA方法的参数原模原样的传给方法ClassA(int, int),该方法的汇编代码跟复                                                                        制构造函数基本相同

综合上述汇编代码的分析可以得出结论,C++中对以类实例作为入参时实际传递的还是指针,不过为了避免类方法修改类实例属性影响原来的类实例,所以隐式调用复制构造函数创建了一个临时的对象实例,并将该临时实例的指针传入方法中;当以类实例作为返回值且该类实例在方法中创建时,会将预先为返回值分配的内存空间地址作为一个隐含参数传递到方法中,方法中对返回实例的初始化和修改都在该地址的基础上完成,从而完美的规避了从被调方法到调用方法之间的内存复制。

     当以类实例作为返回值但是该类实例是基于入参本身的则会隐式调用复制构造函数,这种情形相当于对入参做一系列修改,然后用修改后的入参初始化一个新的对象实例,参考如下测试用例:

#include 

class ClassA {
private:
	int a = 1;
	int b = 2;
	static int c;
public:
	//构造函数
	ClassA(int i, int j);
	//析构函数
	~ClassA();
	//复制构造函数
	ClassA(const ClassA & ca);
	//运算符重载
	ClassA operator+(const ClassA & ca);
	//友元函数
	friend void print(ClassA cl);
	friend ClassA add(ClassA cl);
	friend ClassA add2(ClassA & cl);
};

int ClassA::c = 0;

ClassA::ClassA(int i, int j) {
	a = i;
	b = j;
	c++;
	std::cout << "execute ClassA(int i,int j),this->" << this << "\n";
}

ClassA::~ClassA() {
	c--;
	std::cout << "execute ~ClassA,this->" << this << "\n";
}

ClassA::ClassA(const ClassA & ca) {
	a = ca.a;
	b = ca.b;
	c++;
	std::cout << "execute ClassA(const ClassA & ca),this->" << this << "\n";
}

ClassA ClassA::operator +(const ClassA & ca) {
	a += ca.a;
	b += ca.b;
	return *this;
}

void print(ClassA cl) {
	std::cout << "print a=" << cl.a << ",b=" << cl.b << ",c=" << cl.c << "\n";
}

ClassA add(ClassA cl) {
	cl.a++;
	cl.b++;
	return cl;
}

ClassA add2(ClassA & cl) {
	cl.a++;
	cl.b++;
	return cl;
}

int main() {
	ClassA a(1, 1.2);
	std::cout << "a=" << &a << "\n";
	//情形1
	ClassA b = a;
	std::cout << "b=" << &b << "\n";
	//情形2
	print(b);
	//情形3
	ClassA c = add(b);
	std::cout << "c=" << &c << "\n";
	ClassA c2 = add2(b);
	std::cout << "c2=" << &c2 << "\n";
	//情形4
	ClassA d = a + b + c;
	std::cout << "d=" << &d << "\n";

	return 0;
}

  执行结果如下:

C++ 类复制构造函数,浅/深拷贝,默认赋值运算符重载,友元类,内部类_第3张图片

operator+,add和add2方法在汇编代码层面基本是一样的,都是把类实例的指针和为返回值预先分配的内存地址传入方法中,在方法执行完成以该类实例指针和返回值的内存地址即隐含的this指针作为入参调用复制构造函数生成一个新的对象,以调用add方法的汇编代码为例:

lea    -0x60(%rbp),%rdx
lea    -0x30(%rbp),%rax
mov    %rdx,%rsi  
mov    %rax,%rdi
callq  0x4009dc    调用复制构造函数创建一个临时对象,保存在-0x30(%rbp)处
lea    -0x70(%rbp),%rax
lea    -0x30(%rbp),%rdx
mov    %rdx,%rsi
mov    %rax,%rdi
callq  0x400b37   调用add方法,第一个参数是为返回值预分配的内存地址-0x70(%rbp),第二个参数是临时对象                                                     的指针-0x30(%rbp)
lea    -0x30(%rbp),%rax
mov    %rax,%rdi
callq  0x400994    析构函数销毁创建的临时对象

add方法的汇编代码如下:

mov    %rdi,-0x8(%rbp)
mov    %rsi,-0x10(%rbp)  将方法入参从寄存器拷贝到栈帧中,-0x8(%rbp)保存为返回值预先分配的内存地址,-0x10(%rbp) 保存                                         类实例指针
mov    -0x10(%rbp),%rax
mov    (%rax),%eax    将cl.a的值拷贝到eax中
lea    0x1(%rax),%edx   eax中的值自增1,结果保存到edx中
mov    -0x10(%rbp),%rax
mov    %edx,(%rax)      将edx中的值拷贝到cl.a中,执行完cl.a++;
mov    -0x10(%rbp),%rax
mov    0x4(%rax),%eax
lea    0x1(%rax),%edx
mov    -0x10(%rbp),%rax
mov    %edx,0x4(%rax)  同上,执行完cl.b++;
mov    -0x10(%rbp),%rdx
mov    -0x8(%rbp),%rax
mov    %rdx,%rsi
mov    %rax,%rdi
callq  0x4009dc   将方法的入参原样传给复制构造函数。

综上分析可以得出结论,当类实例作为返回值时不产生临时对象,跟以结构体作为返回值不同的是不存在将被调方法的返回值复制到调用方法这步操作,返回值的创建都是基于预先为返回值分配的内存地址。

3、浅拷贝

     跟什么都不做的默认构造函数不同,默认的复制构造函数会复制所有的非static类属性,因为static属性不属于类实例,不通过类实例初始化,所以默认复制构造函数不会修改类static属性。

#include 

class ClassA{
private:
	int a=1;
	int b=2;
	const char * cp;
	static int c;
public:
	//构造函数
	ClassA(int i,int j);
	//析构函数
	~ClassA();
	//友元函数
	friend void print(ClassA cl);
};

int ClassA::c=0;

ClassA::ClassA(int i,int j){
	a=i;
	b=j;
	c++;
	cp="test";
	std::cout<<"execute ClassA(int i,int j),this->"<< this<<"\n";
}

ClassA::~ClassA(){
	c--;
	std::cout<<"execute ~ClassA,this->"<< this<<"\n";
}

void print(ClassA cl){
	std::cout <<"print a=" <

 执行的结果如下:

 C++ 类复制构造函数,浅/深拷贝,默认赋值运算符重载,友元类,内部类_第4张图片

测试结果中4个实例的属性a,b,cp都一样,符合预期,为什么类属性c会从1不断递减到-2了?因为每次调用print函数时都会隐式调用复制构造函数创建一个临时对象,默认的复制构造函数不会修改静态属性c,但是当print函数执行完成,编译器就自动调用析构函数销毁临时对象,执行析构函数的时候会将静态属性c减一,上述执行结果中打印c=1和打印-2之间调用了3次析构函数,1-3=-2,符合预期。

     上述示例中属性cp是一个不可变指针,默认复制构造函数直接复制指针的地址没有任何问题,如果是常见的通过new分配内存的指针会有问题么?

#include 
#include 

class ClassA{
private:
	int a=1;
	int b=2;
	char * str;
	static int c;
public:
	//构造函数
	ClassA(int i,int j,const char * s);
	//析构函数
	~ClassA();
	//友元函数
	friend void print(ClassA cl);
};

int ClassA::c=0;


ClassA::ClassA(int i,int j,const char * s){
	a=i;
	b=j;
	c++;
	int len = std::strlen(s);
	str = new char[len + 1];
	//将字符串内容从s拷贝到str中
	std::strcpy(str, s);
	std::cout<<"execute ClassA(int i,int j),this->"<< this<<"\n";
}

ClassA::~ClassA(){
	c--;
	//构造函数中通过new分配了内存,为了避免内存泄漏应该在析构函数中调用delete释放对应的内存
	delete[] str;
	std::cout<<"execute ~ClassA,this->"<< this<<"\n";
}

void print(ClassA cl){
    std::cout <<"print a=" <

  执行结果如下:

  C++ 类复制构造函数,浅/深拷贝,默认赋值运算符重载,友元类,内部类_第5张图片

     上述代码在打印b的时候程序崩溃了,并且打印b的str属性时出现了乱码,这是为什么了?原因是print(a)的时候隐式调用复制构造函数创建了一个临时对象,该临时对象和a的str指向同一片内存,当print(a)执行完成,系统执行析构函数销毁该临时对象,析构函数中调用delete释放了str指向的内存。执行print(b)打印str指向的字符串,因为b的str和a的str指向同一片内存,但是该内存已经在析构函数执行过程中被释放,对当前进程而言属于非法内存,访问该片内存报错,程序崩溃。

4、深拷贝

     上述两个示例演示了默认复制构造函数执行的浅拷贝在构造函数修改static属性和类成员属性包含通过new分配的指针两种情形下存在的问题,解决问题的办法的就是深拷贝,即显示定义复制构造函数,在复制构造函数中执行跟构造函数一样或者相匹配的逻辑,比如同样修改static属性,通过new关键字重新分配一块内存,将被复制对象实例的同一指针属性指向的内存的内容复制到新分配的内存中。修改后的示例代码如下:

#include 
#include 

class ClassA{
private:
	int a=1;
	int b=2;
	char * str;
	static int c;
public:
	//构造函数
	ClassA(int i,int j,const char * s);
	//析构函数
	~ClassA();
	//复制构造函数
	ClassA(const ClassA & ca);
	//友元函数
	friend void print(ClassA cl);
};

int ClassA::c=0;


ClassA::ClassA(int i,int j,const char * s){
	a=i;
	b=j;
	c++;
	int len = std::strlen(s);
	str = new char[len + 1];
	std::strcpy(str, s);
	std::cout<<"execute ClassA(int i,int j),this->"<< this<<"\n";
}

ClassA::~ClassA(){
	c--;
	delete[] str;
	std::cout<<"execute ~ClassA,this->"<< this<<"\n";
}

ClassA::ClassA(const ClassA & ca){
	a=ca.a;
	b=ca.b;
	c++;
	int len = std::strlen(ca.str);
	str = new char[len + 1];
	std::strcpy(str, ca.str);
	std::cout<<"execute ClassA(const ClassA & ca),this->"<

 执行结果如下:

C++ 类复制构造函数,浅/深拷贝,默认赋值运算符重载,友元类,内部类_第6张图片

4个对象的str指针属性指向4个不同的地址,但是包含同样的字符串,a,b,c三个属性值一样,符合预期

二、赋值运算符重载函数

      C中允许同类型的一个结构体给另一个结构体赋值,底层实现是编译器通过汇编语言完成两个结构体内存拷贝;C++中允许同类型的一个对象实例给另一个对象实例赋值,底层实现跟结构体赋值是一样的,就是内存拷贝,效果等同于浅拷贝,并不是《C++ Primer Plus》中所说的C++自动的为每个类添加赋值运算符重载函数,该函数同默认高构造函数一样执行浅拷贝,因为内存拷贝比逐一的属性复制更快。除此之外,显示定义赋值运算符重载函数时应注意:

  • 如果被赋值对象或者目标对象包含了通过new分配的内存的指针,则执行赋值前应该delete释放内存
  • 应当避免同一个对象实例自己给自己赋值,因为在遵循第一条的前提下会释放该对象实例包含的通过new分配的内存
  • 不同于复制构造函数返回一个对象实例,运算符重载函数应当返回一个指向对象的引用,从而便于连续赋值

赋值运算符重载函数在类实例赋值的时候隐式调用,示例如下:

#include 
#include 

class ClassA {
private:
	int a = 1;
	int b = 2;
	char * str;
	static int c;
public:
	ClassA();
	//构造函数
	ClassA(int i, int j, const char * s);
	//析构函数
	~ClassA();
	//复制构造函数
	ClassA(const ClassA & ca);
	//赋值运算符重载函数
	ClassA & operator=(const ClassA & ca);
	//友元函数
	friend void print(ClassA cl);
};

int ClassA::c = 0;

ClassA::ClassA(){
	//0表示空指针,delete兼容空指针
//	str=0;
	//C中用NULL表示空指针,NULL是一个值为0的宏定义
//	str=NULL;
	//C++11引入关键字nullptr表示空指针
	str=nullptr;
}

ClassA::ClassA(int i, int j, const char * s) {
	a = i;
	b = j;
	c++;
	int len = std::strlen(s);
	str = new char[len + 1];
	std::strcpy(str, s);
	std::cout << "execute ClassA(int i,int j),this->" << this << "\n";
}

ClassA::~ClassA() {
	c--;
	delete[] str;
	std::cout << "execute ~ClassA,this->" << this << "\n";
}

ClassA::ClassA(const ClassA & ca) {
	a = ca.a;
	b = ca.b;
	c++;
	int len = std::strlen(ca.str);
	str = new char[len + 1];
	std::strcpy(str, ca.str);
	std::cout << "execute ClassA(const ClassA & ca),this->" << this << "\n";
}

ClassA & ClassA::operator =(const ClassA & ca) {
	//避免自己给自己赋值
	if (this == &ca) {
		return *this;
	}
	//释放该实例原来占用的内存
	delete[] str;
	//赋值拷贝,逻辑和复制构造函数基本相同
	a = ca.a;
	b = ca.b;
	int len = std::strlen(ca.str);
	str = new char[len + 1];
	std::strcpy(str, ca.str);
	std::cout << "execute operator =(const ClassA & ca),this->" << this << "\n";
	return *this;
}

void print(ClassA cl) {
	std::cout << "print a=" << cl.a << ",b=" << cl.b << ",c=" << cl.c
			<< ",&str=" << (void *) cl.str << ",str=" << cl.str << "\n";
}

int main() {
	ClassA a(1, 2, "shl");
	//调用复制构造函数
	ClassA b = a;
	//调用赋值运算符重载函数
    b=a;
    //从右往左结合,等价于b=a,c=b;
    ClassA c=b=a;

	return 0;
}

 执行结果如下:

C++ 类复制构造函数,浅/深拷贝,默认赋值运算符重载,友元类,内部类_第7张图片

去掉上述代码中的赋值运算符重载函数,main方法修改如下,重新编译执行:

int main() {
	ClassA a(1, 2, "shl");
    a=ClassA(3,4,"shl");
	return 0;
}

执行结果:

C++ 类复制构造函数,浅/深拷贝,默认赋值运算符重载,友元类,内部类_第8张图片

程序执行崩溃了?反汇编查看:

 lea    -0x30(%rbp),%rax  变量a在栈帧的地址,将其拷贝到rax中
 mov    $0x400f31,%ecx   字符串shl的地址拷贝到ecx中
 mov    $0x2,%edx   
 mov    $0x1,%esi
 mov    %rax,%rdi   变量a的地址即this指针,放入rdi中
 callq  0x400a7a    调用ClassA的构造函数
 lea    -0x20(%rbp),%rax   对a赋值的临时变量的地址,将其拷贝到rax中
 mov    $0x400f31,%ecx
 mov    $0x4,%edx
 mov    $0x3,%esi
 mov    %rax,%rdi
 callq  0x400a7a   完成临时变量的创建
 mov    -0x20(%rbp),%rax   将临时变量的低8字节,即属性a和属性b对应的内存,拷贝到rax中
 mov    %rax,-0x30(%rbp)   将rax中的内存数据拷贝到变量a的低8字节中,即完成属性a和属性b的赋值
 mov    -0x18(%rbp),%rax   将临时变量的高8字节,即char指针属性c对应的内存,拷贝到rax中
 mov    %rax,-0x28(%rbp)   完成属性c的赋值
 lea    -0x20(%rbp),%rax
 mov    %rax,%rdi
 callq  0x400b32   销毁临时变量
 mov    $0x0,%ebx
 lea    -0x30(%rbp),%rax
 mov    %rax,%rdi
 callq  0x400b32 销毁变量a

上述汇编代码演示了默认的类赋值时的内存拷贝逻辑,程序崩溃的原因就在于析构函数中delete[] str这行代码,因为创建的临时变量销毁时已经释放了其占用的内存,将临时变量的内存拷贝到变量a,临时变量的str和变量a的str指向同一块内存,变量a销毁时,因为变量a的属性str指向的内存已经被释放,所以程序崩溃了。

三、友元类

     在上一篇博客《Java程序员自我修养——C++ 类数组,类作用域枚举,运算符重载,友元函数,类型自动转换》中讲到了友元函数,其中的一种是将类成员函数作为友元函数,如果同一个类的所有成员函数都希望访问某个类的私有属性怎么处理了?将该类声明为目标类的友元类即可。注意:

  •   友元类是单向的,A是B的友元类并不意味着B是A的友元类
  •   友元类是不可传递的,B是A的友元类,C是B的友元类,C不是A的友元类
  •   友元类关系不能被继承,A是B的友元类,C是B的子类,A不是C的友元类
#include 

using namespace std;

class Inner;

class Outer
{
public:
    Outer(){m_outerInt=0;}
private:
    int m_outerInt;
    static int staInt;
public:
    friend Inner;
    void DisplayOut(){cout<<"Outer m_outerInt="<四、内部类和局部类 
  

     C++中类可以声明在类定义或者函数定义中,前者称为内部类或者嵌套类,后者称为局部类,java中的内部类参考《java内部类》

1、局部类定义

     局部类的作用域仅限于类定义的方法中,且局部类必须在类中实现所有的函数,因为C++中不允许在函数中定义函数,其用法跟Java中的匿名内部类一样,通常用于临时实现某个虚函数。Java中匿名内部类只能访问方法中的final变量,因为Java会隐式的将用到的final变量作为入参传递到方法中,C++中只能访问方法中定义的静态变量,因为方法中的非静态变量是在栈帧中分配的,方法执行完毕自动销毁,所以非静态变量的内存地址是不确定的,如果使用了非静态变量则内部类的方法无法编译,只能通过方法入参的方式传递到局部类中。

#include 

using namespace std;

void fun() {
	static int s=1;
	int a=2;
	class {
	private:
		int a=3;
	public:
		void init(int i,int & j) {
			s = i;
			//不能直接访问方法中的非静态变量
//			a=j;
			j=a;
		}
	} m;
	m.init(2,a);
	std::cout<< "s=" <

2、内部类定义

     内部类同友元类可以访问所有的类成员,不同的是友元类只能通过类名访问私有静态属性,而内部类可以直接访问。内部类可以声明在private,public或者protected下,如果是private则内部类只在外部类的类定义中可见可以使用,如果是protected则内部类在外部类和其子类中可见可以使用,如果是public则该内部类可以在所有的类中使用,只是声明时需要加上外部类类名限定。注意:

  • 内部类访问外部类非静态属性时必须通过外部类对象实例访问,因为C++中内部类创建对象和外部类创建对象是独立的,而Java中非静态内部类创建时必须先创建外部类,通过外部类实例创建内部类,所以Java中非静态内部类可以直接访问外部类的非静态属性。
  • 外部类不能访问内部的私有成员,外部类不是内部类的友元类
  • 内部类的函数定义定义可以在内部类定义中或者外部类定义外,只要加上对应的类名限定即可

声明为public的示例如下:

#include 

using namespace std;

class Outer {
public:
	Outer() {
		m_outerInt = 0;
	}
private:
	int m_outerInt;
	static int staInt;
public:
	class Inner {
	public:
		Inner() {
			m_innerInt = 1;
		}
	private:
		int m_innerInt;
	public:
		void DisplayIn();
		void DisplayOut(Outer out);
	};
	void DisplayOut() {
		cout << "Outer m_outerInt=" << m_outerInt << endl;
	}
};

int Outer::staInt=1;

void Outer::Inner::DisplayIn(){
	//内部类可以不带类名直接访问静态属性
	cout << "Inner m_innerInt=" << m_innerInt <<",staInt="<< staInt<< endl;
}

void Outer::Inner::DisplayOut(Outer out){
	//必须通过外部类实例访问实例属性
	cout << "Inner m_outerInt=" << out.m_outerInt << endl;
}

int main() {
	Outer out;
	//Inner的作用域在Outer之下,使用时必须加上Outer限定
	Outer::Inner in;
	out.DisplayOut();
	in.DisplayIn();
	in.DisplayOut(out);

	//sizeof不包含内部类
	cout << "sizeof Outer:" << sizeof(Outer) << endl;

	return 0;
}

内部类通常声明为private,用于类内部维护复杂的数据结构如链表等,示例如下:

#include 

using namespace std;

class Outer {
public:
	Outer() {
		m_outerInt = 0;
	}
private:
	int m_outerInt;
	static int staInt;
	class Inner {
	public:
		Inner() {
			m_innerInt = 1;
		}
	private:
		int m_innerInt;
	public:
		void DisplayIn();
		void DisplayOut(Outer out);
	};
public:
	void DisplayOut() {
		Inner in;
		in.DisplayIn();
		in.DisplayOut(*this);
		cout << "Outer m_outerInt=" << m_outerInt << endl;
	}

};

int Outer::staInt=1;

void Outer::Inner::DisplayIn(){
		cout << "Inner m_innerInt=" << m_innerInt <<",staInt="<< staInt<< endl;
}

void Outer::Inner::DisplayOut(Outer out){
	cout << "Inner m_outerInt=" << out.m_outerInt << endl;
}

int main() {
	Outer out;
	//Inner被声明为Outer私有内部类,只能在Outer内部访问
//	Outer::Inner in;
	out.DisplayOut();

	//sizeof不包含内部类
	cout << "sizeof Outer:" << sizeof(Outer) << endl;
	return 0;
}

  内部类的完整定义也可放在外部类的外面,如下:

#include 

using namespace std;

class Inner;

class Outer {
public:
	Outer() {
		m_outerInt = 0;
	}
private:
	int m_outerInt;
public:
	class Inner;
	void DisplayOut() {
		cout << "Outer m_outerInt=" << m_outerInt << endl;
	}
};

class Outer::Inner {
public:
	Inner() {
		m_innerInt = 1;
	}
private:
	int m_innerInt;
public:
	void DisplayIn() {
		cout << "Inner m_innerInt=" << m_innerInt << endl;
	}
	void DisplayOut(Outer out) {
		cout << "Inner m_outerInt=" << out.m_outerInt << endl;
	}
};

int main() {
	Outer out;
	Outer::Inner in;
	out.DisplayOut();
	in.DisplayIn();
	in.DisplayOut(out);

	//sizeof不包含内部类
	cout<<"sizeof Outer:"<

3、内部类和外部类的互相访问

      内部类可以通过类实例指针或者引用直接访问外部类属性,外部类一样可以通过此方式访问内部类,只是不能直接访问内部类的私有属性,必须通过内部类对外的public方法访问私有属性,因此只有内部类和外部类都保存有彼此的指针或者引用就可以实现互相访问了。

#include 

using namespace std;

class Outter {
private:
	string Out;
	class Inner {
	public:
		string In;
		Outter *q;
		Inner(Outter *parent) :
				q(parent) {
		}
		void set(string &str) {
			q->Out = str;
		}
		void get() {
			cout << q->Out << endl;
		}
	};
	Inner *d;
public:
	Outter() :
			d(new Outter::Inner(this)) {
	} //在初始值列表中初始化d指针(首选)
	//Outter(){d = new Outter::Inner(this);} //在构造函数中给d指针赋值
	~Outter() {
		delete d;
		d = 0;
	}
	void setIn(string &str) {
		d->In = str;
	}
	void getIn() {
		cout << d->In << endl;
	}
	void setOut(string &str) {
		d->set(str);
	}
	void getOut() {
		d->get();
	}
	void printOut() {
		cout << Out << endl;
	}
};

int main() {
	Outter T;
	string t1 = "In", t2 = "Out";
	T.setIn(t1);
	T.getIn();
	T.setOut(t2);
	T.getOut();
	T.printOut();
	return 0;
}

  参考:C++之内部类

             C/C++ 局部类和嵌套类

             C++内部类和外部类的互相访问(d指针和q指针)

你可能感兴趣的:(C/C++,Primer,Plus与汇编,复制构造函数,默认赋值运算符重载,浅/深拷贝,友元类,内部类)