C++有关继承

发布于:2023-02-10 ⋅ 阅读:(594) ⋅ 点赞:(0)

目录

1、继承的概念与定义

2、继承方式和继承访问权限

总结

有关protected继承和private继承的区别:

 3、编写派生类的注意事项

3、1有关继承关系中数据成员的内存分布,可见性,调用顺序

有关调用顺序:

 4、讨论赋值兼容规则

5、继承与静态成员

6、1隐藏和重载

6、继承与友元

7、菱形继承

1、继承的概念与定义

C++通过类派生的机制来支持继承。被继承的类称为基类或超类,新产生的类称为派生类或者子类。基类和派生类的集合被称作类继承层次结构。

由基类派生出,派生类的设计形式为:

class 派生类名: 访问限定符 基类名
{
    private:
            成员表1;//派生类增加或替代的私有成员
    public:
            成员表2;//派生类增加或替代的公有成员
    protected:
            成员表3;//派生类增加或替代的保护成员

};

注意:派生反映了事物之间的联系,事物的共性与个性之间的关系。派生与独立设计若干相关的类,前者工作量少,重复的部分可从基类继承,不需要单独编程。继承是类型设计层面的复用。

2、继承方式和继承访问权限

有三种类型的访问限定符:

  • public:任意位置
  • protected:本类和子类
  • private:本类

私有(private)成员:私有成员变量或者函数在类的外部是不可访问的,只有本类中或者友元函数可以访问。定义一个类时,如果不显示地给成员加访问修饰符,类的所有成员都是私有的。

保护(protected)成员:保护成员变量或者函数和私有成员十分相似,不同的是保护成员在其派生类(子类)中是可以访问的。

也就是说私有private比protected的保护性更强。

类中成员变量可以使用这三种访问限定符修饰,继承方式也有这三种方式,我们逐一来讨论:基类中不同的访问限定符的成员以不同的继承方式继承后在派生类中的访问限定为:

示例:

#include<iostream>

using namespace std;

class A {
public:
	int m_i;
protected:
	int m_j;
private:
	int m_k;
	static int m_m;
};
//public继承在本类的成员函数中,可以访问基类的公有和保护
//外部方法可以访问本类公有,不能访问保护和私有
class B :public A
{
public:
	void fun()
	{
		m_i = 10;
		m_j = 20;
		//m_k = 30;//error 能继承,不能访问
	}
};
//protected继承
class C :protected A
{
public:
	void test()
	{
		m_i = 10;
		m_j = 20;
		//m_k = 30;//error 能继承,不能访问
	}
};
class D :private A
{
public:
	void test1()
	{
		m_i = 10;
		m_j = 20;
		//m_k = 30;//error 能继承,不能访问
	}
};
int main()
{
	B b;
	b.fun();
	b.m_i = 10;
//	b.m_j = 20; 外部不能访问类中保护成员
	//b.m_k = 30;不能私有成员访问

	C c;
	c.test();
	//都不能在外部访问,保护继承
	// 就把A公有的继承为了保护,保护的继承为了保护,私有还是私有
	//c.m_i = 10;
	//c.m_j = 20;
	//c.m_k = 30;

	D d;
	//都不能在外部访问,私有继承
	// 把A里面成员变量函数都变成私有的
	//D.test1();
	//d.m_i = 10;
	//d.m_j = 20;
	//d.m_k = 30;
}

【1. 以public方式继承】

  • 类成员m_i以public修饰:m_i可以在任意位置被访问,对于派生类是public。
  • 类成员m_j以protected修饰:m_j可以在其子类中访问,对于派生类是protected
  • 类成员m_k以private修饰:m_k只能在本类中访问即A中,不能在派生类访问,所以是不可访问的。

【2. 以protected方式继承】

  • 类成员m_i以public修饰:m_i不能在类外访问,但可以在派生的子类访问,对于派生类是protected
  • 类成员m_j以protected修饰:m_j可以其子类中访问,对于派生类是protected。
  • 私有成员ma以private修饰:m_k只能在本类中访问即A中 ,不能在派生类访问,所以是不可访问的。

【3. 以private方式继承】

  • 类成员m_i以public修饰:可以在派生类中访问,所以对于派生类是私有的private。
  • 类成员m_j以protected修饰:private私有的.
  • 类成员m_k以private修饰:m_k只能在本类中访问即A中,不能在派生类访问,所以是不可访问的。

总结:

对于这三种方式继承的 派生类 来说: 都能访问基类的public, protected 成员;

public 的方式继承到派生类,这些成员的权限和在基类里的权限保持一致;

protected方式继承到派生类,成员的权限都变为protected;

private 方式继承到派生类,成员的权限都变为private;

继承方式\基类成员访问呢权限 public protected private
public public protected 不可访问
protected protected protected 不可访问
private private private 不可访问

有关protected继承和private继承的区别:

 区别在于多继承之后的访问权限

给上述代码例子多继承一次就会发现:

 3、编写派生类的注意事项

 

 注意:

        构造函数和析构函数不能被继承。基类的构造和析构满足的工作要求不可能完全适应派生类,因此需要重写构造,析构。

        其他函数都可以通过类对象来调用,创建对象时由系统调用该对象所属类的构造函数,该对象生存期也只调用这一次。由继承而来的派生类对象,是能够调用父类函数,但不能调用构造函数以及析构函数。

3、1有关继承关系中数据成员的内存分布,可见性,调用顺序

在派生类中,基类的内存布局优先于派生类

class Object
{
private: int oa;
protected: int ob;
public:  int oc;

};
class Base:public Object
{
private: int bx;
protected: int by;
public:  int bz;
};
int main()
{
	Object obj;
	Base base;
	return 0;
}

  •  不论采取何种继承方式,基类中所有数据成员都会继承到派生类
  • 在类型的继承层次结构中,保护属性当做公有属性来使用
  • 继承性具有传递性
  • 不论采取何种继承方式,派生类对象的成员方法都可以去访问基类对成员对象中的保护和公有属性

有关调用顺序:

class A
{
public:A(int i = 0) { cout << "A" << i << endl; }
};
class B
{
public:B(int i = 0) { cout << "B" << i << endl; }
};
class C
{
public:C(int i = 0) { cout << "C" << i << endl; }
};
class D :public B, public A
{
public:
	D(int i = 0, int j = 0, int k = 0) :A(i), B(j), c(k), m_m(10)
	{
		cout << "D" << m_m << endl;
	}
private:
	C c;
	int m_m;
};
void main()
{
	D d(1, 2, 3);
}

调用顺序:

        1.按照继承顺序调用构造 
        2.按照组合顺序调用构造
        3.调用本类自己的构造

 

 4、讨论赋值兼容规则

  1. 派生类的对象可以赋值给基类对象,这时是把派生类对象中从对应基类中继承来的隐藏对象赋值给基类对象。反过来不行,因为派生类的新成员没有值可以赋。
  2. 可以将一个派生类的对象的地址赋给其基类指针变量,但是只能通过这个指针访问派生类中由基类继承来的隐藏对象,不能访问派生类中的新成员。同样反过来也不行。
  3. 派生类对象可以初始化基类引用。引用是别名,但这个别名只能包含派生类对象中的由基类继承来的隐藏对象。
class Object
{
private: 
	int value;
public: 
	Object(int x = 0):value(x){}
	~Object(){}
	void Print(int x)
	{
		value = x;
		cout << value << endl;
	}

};
class Base :public Object
{
private:
	int num;

public:  
	Base(int x=0):Object(x),num(x+10){}
};
int main()
{
	Object obja(0);
	Base base(10);
	Object* op = &base;
	Object& ob = base;
	obja = base;
   // base = obja;//error 父类对象不能给子对象赋值
	return 0;
}

 几个例题:

//不能被继承的类
//final 表示当前类终止了
class A final
{
private:
	A() {}
};
class B :public A
{

};
int main()
{
	//B b;
}


//能被继承,不能在外界定义对象的类
class A
{
protected:
	A() { cout << "A" << endl; }
};
class B :public A
{
public:
	B() { cout << "B" << endl; }
};
void main()
{
	//A a;  //外部不能定义a类对象
	B b;
}


//设计一个类,只能生成一个实例
class A
{
private:
	A() { cout << "A" << endl; }
public:
	A(const A& a) = delete;
	A& operator=(const A& a) = delete;
	static A&& GetA()
	{
		cout << "Get(a)" << endl;
		return A();
	}
	void Print() { cout << "Print" << endl; }
};
void main()
{
	//A a, b, c, d;
	A::GetA().Print();
	//A a = A::GetA();
}

实现子类的拷贝,析构,赋值,编写继承关系

//如果有指针了作为数据成员 析构,深拷贝,深赋值必须要写
class Person {
public:
	Person(){}
	Person(const char* name, char sex, int age) :m_sex(sex), m_age(age)
	{
		cout << "Person name sex age" << endl;
		m_name = new char[strlen(name) + 1];
		strcpy(m_name, name);

	}
	Person(const Person& p)
	{
		m_name = new char[strlen(p.m_name) + 1];
		strcpy(m_name, p.m_name);
		m_age = p.m_age;
		m_sex = p.m_sex;
	}
	~Person()
	{
		cout << "Person析构" << endl;
		if (m_name == nullptr)
		{
			delete[]m_name;
			m_name = nullptr;
		}
	}
	Person& operator = (const Person& p)
	{
		if (this != &p)
		{
			delete[] m_name;
			m_name = new char[strlen(p.m_name) + 1];
			strcpy(m_name, p.m_name);
			m_sex = p.m_sex;
			m_age = p.m_age;
		}
		return *this;
	}
	void Show()
	{
		cout << "name : " << m_name << endl;
		cout << "sex : " << m_sex << endl;
		cout << "age : " << m_age << endl;
	}
private:
    char* m_name;
	char m_sex;
	int m_age;
};
class Student : public Person
{
public:
	Student(){}
	Student( const char*name, const char sex, int age, const char* num, float sorce): Person(name, sex, age)
	{
		m_num = new char[strlen(num)+1];
		strcpy_s(m_num,strlen(num)+1, num);
		m_sorce = sorce;
		cout << "Student" << endl;
	}
	Student(const Student& s) :Person(s)
	{
		m_num = new char[strlen(s.m_num) + 1];
		strcpy(m_num, s.m_num);
		m_sorce = s.m_sorce;
	}
	~Student()
	{
		cout << "Student析构" << endl;
		if (m_num == nullptr)
		{
			delete[]m_num;
			m_num = nullptr;
		}
	}
	Student& operator=(const Student& s)
	{
		if (this != &s)
		{
			Person::operator = (s);//指定作用域调用父类的赋值运算符重载
			delete[] m_num;
			m_num = new char[strlen(s.m_num) + 1];
			strcpy(m_num, s.m_num);
			m_sorce = s.m_sorce;
		}
		return *this;
	}
	void Print()
	{
		Show();
		cout << "num : " << m_num << endl;
		cout << "score :" << m_sorce << endl;
	}
private:
	char* m_num;
	float m_sorce;
};
int main()
{
	//Person p;
	Student s("张三", 'm', 20, "1001", 80);
	s.Print();
	cout << "SS info :" << endl;
	Student ss = s;  //用s对象去构造ss对象,调用拷贝构造
	ss.Print();

	cout << "dd info : " << endl;
	Student dd;
	dd = s; //用s去给dd赋值,需要调用赋值运算符重载
	dd.Print();
	cout << "main end " << endl;
	return 0;
}

5、继承与静态成员

继承与静态成员,静态成员只有一个,所有对象共享(包括基类对象和派生类对象)

class Shape
{
public:
	Shape() {}
	void Print() { cout << "num = " << m_num << endl; }
	//private:
	static int m_num;
};
int Shape::m_num = 0;
class Circle :public Shape
{
public:
	Circle() { cout << "C num = " << ++m_num << endl; }
};
class Rectangle : public Shape
{
public:
	Rectangle() { cout << "R num = " << ++m_num << endl; }
};
class Triangle : public Shape
{
public:
	Triangle() { cout << "T num = " << ++m_num << endl; }
};
void main()
{
	Shape s;
	Circle c1, c2;
	s.Print();
	Rectangle r1, r2;
	s.Print();
	Triangle t1, t2;

	s.Print();
}

6、1隐藏和重载

注意:

如果子类出现和父类同名的成员函数,成员属性,子类的会将同名父类的成员函数,成员属性隐藏掉

直接调用,调出来是子类的;如果想访问父类的,需要加作用域,显示访问

示例一:

子类中定义了和基类同名的static属性
如果子类中定义了和基类同名的static属性,则有两份静态区域,各自维护,基类的被隐藏
 如果在子类中想访问基类的,则需要显示访问

class A
{
public:
	A() { cout << "A:" << ++m_i << endl; }
	void Print() { cout << "A : m_i:" << m_i << endl; }
protected:
	static int m_i;
};
int A::m_i = 10;
class B :public A
{
public:
	B()
	{
		A::m_i++;
		cout << "B : " << ++m_i << endl;
	}
	void Show() { cout << "B : m_i:" << m_i << endl; }
private:
	static int m_i;
};
int B::m_i = 20;
void main()
{
	A a;
	a.Print();  //11
	B b;
	a.Print(); //13
	b.Show();  //21
}

示例二:

class A
{
public:
	A(int i = 0) :m_i(i) {}
	virtual void fn() { cout << "A : fn" << endl; }
private:
	int m_i;
};
class B :public A
{
public:
	B(int i = 0, int j = 0) :m_j(j), A(i) {}
	//定义了一个和基类同名的函数,则将基类的fn函数隐藏
	//1隐藏,如果是非virtual,则同名同参或者不同参
	void fn()
	{
		A::fn();
		cout << "B:fn" << endl;
	}
	//virtual void fn();
	//隐藏2 如果是virtual,则一定不能同参
	/*void fn(int n)
	{
		A::fn();
		cout << "B : fn(int)" << endl;
	}*/
private:
	int m_j;
};
void main()
{
	//	cout << sizeof(B) << endl;
	B b;
	//b.fn(10);
	b.fn(); //从A继承过来的fn函数被隐藏了
}

 示例三:

class A
{
public:
	A(int i = 0) :m_i(i) {}
	virtual void fn() { cout << "A : fn" << endl; }
private:
	int m_i;
};
class B :public A
{
public:
	B(int i = 0, int j = 0) :m_j(j), A(i) {}
	//定义了一个和基类同名的函数,则将基类的fn函数隐藏
	//1隐藏,如果是非virtual,则同名同参或者不同参
	/*void fn()
	{
		A::fn();
		cout << "B:fn" << endl;
	}*/
	//virtual void fn();
	//隐藏2 如果是virtual,则一定不能同参
	void fn(int n)
	{
		A::fn();
		cout << "B : fn(int)" << endl;
	}
private:
	int m_j;
};
void main()
{
	//	cout << sizeof(B) << endl;
	B b;
	b.fn(10);
	//b.fn(); //从A继承过来的fn函数被隐藏了
}

总结:

        重载:

  • 1.同一个类
  • 2.同名函数
  • 3.参数列表不同
  • 4.和返回值无关,但是和const有关
  • void fn(){}
  • void fn()const{}

        隐藏:

  • 1.父子关系两个类
  • 2.如果是非virtual,则同名同参或者不同参都可以
  • 3.如果是virtual,则一定不能同参,否则会被隐藏

6、继承与友元

友元不具有继承性

7、菱形继承

菱形问题:

        一个派生类有两个或以上的基类,这些基类中存在相同的基类即(B继承A ;C继承A ;D继承B和C) 当派生类想要直接调用A类中的方法时,产生二义性,出错。    

      这里的二义性是由于他们间接都有相同的基类导致的,除了带来二义性外,还会浪费空间(每个派生类中都会带有一份基类的内存)

而为了解决菱形继承问题:让A变为一份,B,C,D可以共享,那么可以将其放在D作用域的md的下面

  • B,C类中使用指针指向对应A内存块即可,给这个指针起个名字叫vbptr,虚基类指针。
  • 内存布局在编译阶段确定,那么可以利用偏移量让vbptr指向A内存块,即vbptr指向的内存块中存放A内存的相对偏移(相对自己位置的偏移)。

 这就是虚继承,可以用来解决菱形继承出现重复间接基类的问题

虚继承的实现: 

class 派生类名:virtual 访问限定符 基类类名{...}

class 派生类名:访问限定符 virtual 基类类名{...}

virtual 关键字只对紧随其后的基类名起作用

class B :virtual public A //A称为虚基类

class C :public virtual A //A称为虚基类

虚继承添加位置:

可能出现数据重复的直接继承关系,如A重复出现,那么B,C虚继承A。

虚继承的内存布局

  • 将虚基类放在当前作用域最下面。如菱形继承,第二个虚基类A可以直接不要,那么此时A属于D了,需要在D中对其进行构造。
  • 出现虚基类的位置放一个虚基类指针vfptr,它指向虚基类表vftable:包含(1)虚基类指针相对于当前作用域的偏移(2)虚基类指针相对于虚基类数据的偏移

示例:从下面沙发类和床品类中抽象出共性,写出一个新类,家具类

class Furniture//家具
{
public:
	Furniture() { cout << "1" << endl; }
	void Sit() { cout << "sit" << endl; }
private:
	int m_size;
};
// vptr(虚指针,占内存,同指针),m_size ---->8
class Sofa : virtual public Furniture
{
public:
	Sofa() { cout << "2" << endl; }
};
//vptr,m_size ---->8
class Bed : virtual public Furniture
{
public:
	Bed() { cout << "3" << endl; }
};
//vptr(Sofa),vptr(Bed),m_size ---->12
class SofaBed :public Sofa, public Bed
{
public:
	SofaBed() { cout << "4" << endl; }
};
int main()
{

	SofaBed sb;
	sb.Sit();
    return 0;
	//	cout << sizeof(sb) << endl;
	//	cout << sizeof(Sofa) << endl;
	//	cout << sizeof(Bed) << endl;
	//	sb.Sofa::Sit();
	//	sb.Bed::Sit();
}

  • 在没有加virtual之前,重复构造家具 ,结果为 sofa(1 2) -> bed( 1 3)  ->4 
  • 结果1  2  1  3  4 
  • 加了virtual后 构造家具(属性构造了一份),构造沙发,构造床的时候发现床也是从家具继承过来
  • 此时就不再构造家具,接着构造床,最后构造沙发床,保证了家具的属性只有一份拷贝
  • 结果 1 2 3 4

日常打卡巩固,知识分享

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到