c++继承(一)

一、总结封装

1、从类的角度:把数据方法放到一起,想让你访问的定义为公有,不想访问的定义为私有。

2、把一个类放到另一个类中,通过typedef 成员函数等方式封装出一个新的类。

二、继承相关概念

1、继承理解

继承与模板相似,都是复用。模板解决类型的复用,继承是类成员设计的复用。

2、语法

class Student : public Person
{...}

Student:要继承的类,叫子类或派生类。

public:继承方式。

Person:被继承的类,叫父类或基类。

3、类成员与继承方式

类成员 / 继承方式public继承protected继承private继承
父类的public        派生类public派生类protected派生类private
父类的protected派生类protected派生类protected派生类private
父类的private派生类不可见派生类不可见派生类不可见

不可见:不是没有继承,而是派生类用不了父类的私有成员。

细节

(1)父类 private 在派生类中不可见,但私有是相对的,可以在父类的 get set 函数中获得。

(2)父类其他成员在派生类中的访问方式 == min(访问限定符,继承方式)

(3)protected 成员在派生类中可以使用,类外不能使用,可以看出 protected 因继承出现。

(4)一般用 public 继承

(5)struct 默认共有继承,class 默认私有继承。

4、切片

假设我现在有一个父类 Person(成员变量是name) 和一个子类 Student(成员变量是id)

int main()
{
    Student s;
    Person p1 = s;
    Proson& p2 = s;
    Person* p3 = &s;
    return 0;
}

p1就是典型的切片,在public继承中,如果要把子类赋值给父类,那么就是把子类从父类继承的成员变量赋值给父类。这种是赋值兼容,不产生临时对象!

p2, p3就是直接指向子类中父类的成员变量。

不能把父类赋值给子类,因为在子类中有父类没有的成员变量。

5、继承的作用域

域名备注
全局域编译器默认在里面找数据,影响变量生命周期
局部域
类域分为父类域和子类域,是两个不同的域
命名空间域用命名空间包的域,独立

结论

(1)父类与子类的作用域是两个不同的域。

(2)父类与子类可以定义同名成员,若直接访问,默认访问子类成员,这就是隐藏或重定义,可以用父类::父类成员的方式访问。

(3)成员函数名相同就构成隐藏。

注意区分隐藏和函数重载:函数重载是在同一个域中两个函数名相同,参数不同的函数,但是隐藏是父类和子类这两个不同域中的概念。

三、派生类中的默认成员函数

1、举例

class Person
{
public:
    //构造函数
	Person(const char* name)
		: _name(name)
	{
		cout << "Person()" << endl;
	}


    //拷贝构造
	Person(const Person& p)
		: _name(p._name)
	{
		cout << "Person(const Person& p)" << endl;
	}


    //赋值构造
	Person& operator=(const Person& p)
	{
		cout << "Person operator=(const Person& p)" << endl;
		if (this != &p)
			_name = p._name;
		return *this;
	}


    //析构函数
	~Person()
	{
		cout << "~Person()" << endl;
	}
protected:
	string _name; // 姓名
};





class Student : public Person
{
public:
	// 父类+自己,父类的调用父类构造函数初始化(复用)
	Student(int num, const char* str, const char* name)
		:Person(name)
		,_num(num)
		,_str(str)
	{
		cout << "Student()" << endl;
	}

	//拷贝构造,切片
	Student(const Student& s)
		:Person(s)
		,_num(s._num)
		,_str(s._str)
	{}


    //赋值构造
	Student& operator=(const Student& s)
	{
		if (this != &s)
		{
			Person::operator=(s);
			_num = s._num;
			_str = s._str;
		}

		return *this;
	}


	// 子类的析构也会隐藏父类
	// 因为后续多态的需要,析构函数名字会被统一处理成destructor
	~Student()
	{
		//如果显示调用父类析构会造成析构两次
		//Person::~Person();

		cout << _name << endl;
		cout << "~Student()" << endl;

		// 注意,为了析构顺序是先子后父,子类析构函数结束后会自动调用父类析构
	}

protected:
	int _num;	
	string _str;
};

2、解析

(1)子类构造函数

首先子类被编译器看成父类成员 + 子类成员(内置类型 + 自定义类型)

构造子类成员和普通类一样:内置类型不做处理,自定义类型调用它的默认构造。

构造父类成员:如果父类有默认构造就可以不显示调用,如果父类没有默认构造就要像上面代码一样显示调用父类构造。

(2)子类拷贝构造函数

像上面代码一样调用父类的拷贝构造函数,传参进行切片。

(3)子类赋值构造函数

调用父类赋值构造函数必须加上类域,不然会默认调用子类赋值构造无限递归栈溢出。

(4)子类析构函数

不要显示调用父类析构函数,为了先析构子类,在析构父类,编译器会自动调用父类析构,如果再手动调用会导致一块空间被释放两次报错。

构造函数:先父后子

析构函数:先子后父

  • 18
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值