继承

149 阅读7分钟

继承的概念

继承是类层次的复用。类似函数复用。。

class Person
{
private:
	string _name = "abcd";
	int _age = 1;
};

class Student : public Person
{
private:
	int _id;
};

// Person有的属性,Student也有。
int main()
{
	Student s1;
	return 0;
}

image-20240225164815166

继承的格式

子类又可叫派生类。父类又可叫基类。

image-20240225165830749

继承关系和访问限定符

image-20240225170348342

基类成员访问方式

  1. 基类的成员在子类中的访问方式 == Min(成员在基类的访问限定符, 继承方式),三者之间的关系:public>protected>private

  2. 基类private成员在派生类中无论以什么方式继承都是不可见的。不可见 !=私有,私有在类里面可以访问,在类外面不可以访问。而不可见是类里面类外面的都不可访问。因此,如果父类不想给子类继承下来成员变量,就可以设置成私有。但是子类对象可以调用父类的函数去访问父类的成员。

    image-20240225171611270

  3. privateprotected限定符在当前类没区别,在子类中有很大区别,前者不可见,后者可见。由此可见protected限定符是因为继承才出现的。

  4. 关键字classstruct,最好还是显示写。

    image-20240225172944542

父类和子类的赋值转换

  • 子类对象可以赋值给父类的对象、父类的指针、父类的引用。这种方式叫切片。

    image-20240225203514008

  • 父类对象不能赋值给子类对象。

继承中的作用域

  1. 在继承中,父类和子类有自己独立的作用域。

  2. 父类和子类中有同名成员,子类对象访问该同名成员时按照就近原则,这种情况叫隐藏,也叫重定义。

    (如果想访问父类的该成员,就要使用父类::父类成员显示)

  3. 如果是成员函数,只需要函数名相同就构成隐藏

    函数重载:同一作用域,函数名相同、参数列表不同构成函数重载。

    隐藏:在父子类中,只要函数名相同就构成隐藏。

子类的默认成员函数

构造函数

由以下代码我们可知:

当子类不显示写构造函数时:

  1. 父类有默认构造函数时,子类实例化对象时,会调用父类的构造函数。因为子类中有父类的成员,父类的成员需要调用父类的构造函数。
  2. 父类没有默认构造函数时,子类实例化对象会报错。
class A
{
public:
	A(int a = 10)
        :_a(a)
	{
		cout << "A()" << endl;
	}

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

	A& operator=(const A& a)
	{
		cout << "A& operator=(const A& a)" << endl;
		return *this;
	}

private:
	int _a;
};

class B :public A
{
public:

private:
	int _b = 20;
};

int main()
{
	B b1;  
	B b2(b1);
	B b3;
	b3 = b1;
	return 0;
}

image-20240226195033856

image-20240226204242280

当子类显示写构造函数时:

  1. 父类有默认构造函数时,子类实例化对象时,会调用父类的构造函数。因为会走B类的初始化列表。
  2. 父类没有默认构造函数时,要显示调用父类的构造函数。
  3. 子类实例化的时候,先调用父类的构造,后调用子类的构造。

image-20240226204912446

image-20240226205116448

拷贝构造函数

当在父类中显示写了拷贝构造函数时:

class A
{
public:
	A(int a = 10)
		:_arr(new char[1])
		,_a(a)
	{
		cout << "A(int a = 10)" << endl;
	}

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

private:
	char* _arr;
	int _a;
};

class B :public A
{
public:
	B(int b = 10)
		:_b(b)
		,A(1000)
	{
		cout << "B(int b = 10)" << endl;
	}

	B(const B& b)
		:_b(b._b)
		,A(b)
	{}

private:
	int _b = 20;
};

int main()
{
	B b1;
	B b2(b1);
	return 0;
}

image-20240226212230372

image-20240226212359524

在父类中不写拷贝构造函数:

class A
{
public:
	A(int a = 10)
		:_arr(new char[1])
		,_a(a)
	{
		cout << "A(int a = 10)" << endl;
    }

private:
	char* _arr;
	int _a;
};

class B :public A
{
public:
	B(int b = 10)
		:_b(b)
		,A(1000)
	{
		cout << "B(int b = 10)" << endl;
	}

	B(const B& b)
		:_b(b._b)
		,A(b)
	{}

private:
	int _b = 20;
};

int main()
{
	B b1;
	B b2(b1);
	return 0;
}

image-20240226213116155

image-20240226213148154

由于此时对堆上申请了内存,所以要管理内存,添加析构函数后:

  1. 因为是浅拷贝,所以析构函数会析构两次,程序会崩溃。
  2. 改变一方会引起另一方的改变。
class A
{
public:
	A(int a = 10)
		:_arr(new char[1])
		,_a(a)
	{
		cout << "A(int a = 10)" << endl;
	}
	
	~A()
	{
		delete[] _arr;
		_arr = nullptr;
	}

private:
	char* _arr;
	int _a;
};

class B :public A
{
public:
	B(int b = 10)
		:_b(b)
		,A(1000)
	{
		cout << "B(int b = 10)" << endl;
	}

	B(const B& b)
		:_b(b._b)
		,A(b)
	{}

private:
	int _b = 20;
};

int main()
{
	B b1;
	B b2(b1);
	return 0;
}

image-20240226213640440

所以不管是编译器生成的,还是自己写的值拷贝的构造函数,都有问题,应当要深拷贝。

class A
{
public:
	A(int a = 10)
		:_arr(new char[1])
		,_a(a)
	{
		cout << "A(int a = 10)" << endl;
	}
	// b2(b1)
	A(const A& a)
		:_a(a._a)
	{
		cout << "A(const A& a)" << endl;

		_arr = new char[1];
		_arr[0] = 'a';
	}

	~A()
	{
		delete[] _arr;
		_arr = nullptr;
	}

private:
	char* _arr;
	int _a;
};

class B :public A
{
public:
	B(int b = 10)
		:_b(b)
		,A(1000)
	{
		cout << "B(int b = 10)" << endl;
	}

	B(const B& b)
		:_b(b._b)
		,A(b)
	{}

private:
	int _b = 20;
};

int main()
{
	B b1;
	B b2(b1);
	return 0;
}

image-20240226214511995

赋值重载

class A
{
public:
	A(int a = 10)
		:_arr(new char[1])
		,_a(a)
	{
		cout << "A(int a = 10)" << endl;
	}
	// b2(b1)
	A(const A& a)
		:_a(a._a)
	{
		cout << "A(const A& a)" << endl;

		_arr = new char[1];
		_arr[0] = 'a';
	}

	A& operator=(const A& a)
	{
		if (this != &a)
		{
			_arr = new char[1];
			_arr[0] = 'z';
		}
		_a = a._a;

		return *this;
	}

	~A()
	{
		delete[] _arr;
		_arr = nullptr;
	}

protected:
	char* _arr;
	int _a;
};

class B :public A
{
public:
	B(int b = 10)
		:_b(b)
		,A(1000)
	{
		cout << "B(int b = 10)" << endl;
	}

	B(const B& b)
		:_b(b._b)
		,A(b)
	{}
	
	// b3 = b1
	B& operator=(const B& b)
	{
		if (this != &b)
		{
			operator=(b);
			_b = b._b;
		}

		return *this;
	}

protected:
	int _b = 20;
};

int main()
{
	B b1;
	B b3(20);
	b3 = b1;
	return 0;
}

image-20240228134315678子类对象中属于父类的成员要想被赋值,在子类中写operator=(子类对象)是不可以的,因为operator=()构成了==隐藏==。必须显示调用,改进如下:

B& operator=(const B& b)
{
    if (this != &b)
    {
        A::operator=(b);
        _b = b._b;
    }

    return *this;
}

代码完整执行后的结果:

image-20240228135256596

析构函数

将子类父类的打印补充完整:

class A
{
public:
	A(int a = 10)
		:_arr(new char[1])
		,_a(a)
	{
		cout << "A(int a = 10)" << endl;
	}
	// b2(b1)
	A(const A& a)
		:_a(a._a)
	{
		cout << "A(const A& a)" << endl;

		_arr = new char[1];
		_arr[0] = 'a';
	}

	A& operator=(const A& a)
	{
		if (this != &a)
		{
			_arr = new char[1];
			_arr[0] = 'z';
		}
		_a = a._a;
		cout << "A& operator=(const A& a)" << endl;


		return *this;
	}

	~A()
	{
		delete[] _arr;
		_arr = nullptr;

		cout << "~A()" << endl;
	}

private:
	char* _arr;
	int _a;
};

class B :public A
{
public:
	B(int b = 10)
		:_b(b)
		,A(1000)
	{
		cout << "B(int b = 10)" << endl;
	}

	B(const B& b)
		:_b(b._b)
		,A(b)
	{
		cout << "B(const B& b)" << endl;
	}
	
	// b3 = b1
	B& operator=(const B& b)
	{
		if (this != &b)
		{
			A::operator=(b);
			_b = b._b;
		}
		cout << "B& operator=(const B& b)" << endl;

		return *this;
	}

private:
	int _b = 20;
};

int main()
{
	B b1;
	B b2(b1);
	B b3(5);
	b3 = b1;
	return 0;
}

发现都是先父后子,补充完析构函数后结果报错,为什么?

​ 析构函数会被处理成destructor,子类的析构和父类的析构又构成隐藏。所以要显示调用。

但是,子类中是不允许你自己写对父类的析构函数的。因为构造时顺序是先父后子,析构时要保证先子后父。子类析构函数完成时, 会自动调用父类析构函数,保证先析构子,在析构父。

~B()
{
    //~A();
    A::~A();
    cout << "~B()" << endl;
}

继承与友元

友元关系不能继承:就是父类友元不能访问子类私有和保护成员

继承和静态成员

父类定义了static静态成员,整个继体系中只有这样的一个成员。可以用来计算继承后一共创建了多少个对象。

单继承和多继承

单继承:一个子类只有一个直接父类。

多继承:一个子类有两个或以上直接父类。

image-20240228153919509

菱形继承:多继承会导致菱形继承,菱形继承是一种特殊情况。

问题:数据冗余和二义性问题。数据冗余导致浪费空间,二义性导致访问不明确。

**如何解决菱形继承的数据冗余和二义性问题? **

​ 虚继承解决--virtual关键字。

菱形继承的问题:B和C里都有一份A,数据冗余。访问时必须指定域作用限定符,否则二义性。

class A
{
//protected:
public:
	int _a;
};

class B :public A
{
//protected:
public:
	int _b;
};

class C :public A
{
//protected:
public:
	int _c;
};

class D :public B, public C
{
//protected:
public:
	int _d;
};

int main()
{
	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
	return 0;
}

image-20240228163714006

用virtual关键字修饰,成虚继承后:

class A
{
//protected:
public:
	int _a;
};

class B : virtual public A
{
//protected:
public:
	int _b;
};

class C : virtual public A
{
//protected:
public:
	int _c;
};

class D :public B, public C
{
//protected:
public:
	int _d;
};

int main()
{
	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
	return 0;
}

image-20240228164956189

image-20240228170432955

虚继承就是通过增加指针,然后经过指针指向的偏移量来得到要访问的变量