C++ 类继承的深入理解

说起继承,就是对现有的类的方法和字段进行重复的使用

这里只是想总结下初学的时候的一些思想误区,供刚入门的小白参考下:

我这里只说公有继承,public的形式,其他形式不进行讲解,也基本用不到。

误区1:子类继承父类以后总是在考虑这个属性和方法,是归谁所有,是不是继承到子类了,还是归父类所有,只是我们能够使用?以及访问属性的问题

具体解答如下:

(1)公有继承父类的方法,继承完父类所有的属性,都继承到了子类,这些字段都是子类的了,

不存在是父类的,还是子类的字段,都是子类的。只是子类能否用自己继承的方法进行访问而已。 继承下来的方法,其实不用考虑是否继承,因为方法不像属性字段,方法就一份,只是可不可以调用方法而已。

对于父类的私有成员,虽然子类可以继承这个方法,但是要依靠父类的方法,进行访问自己继承下来的字段。

上面的这些总结有点饶口,重新梳理下,简单的解释如下:在公有继承的方式下

在父类的访问属性在子类中的访问属性
public(公用)public
private(私有)不可访问
protected(保护)protected

这里有一点一定要明确,子类继承了父类的私有成员,包括,私有的数据成员和成员函数,在子类的访问属性是不可访问是

指在子类中的新增成员函数,或者是重写的成员函数里,不能够进行访问,从父类继承过来的成员函数,还是可以访问的。

这个一定要分清,从父类继承过的函数,是代码的公用。                                                                           

class ICommandRunnable :virtual public IRunable
{
public:
    ICommandRunnable();
    virtual ~ICommandRunnable();
public:
    virtual void PushCommand(BaseCommand *cmd, bool front = false);
protected:
    virtual void ClearCommand();
    virtual BaseCommand *PopCommand();
private:
    std::deque<BaseCommand*> _cmds;
    CLock _cmds_lock;

};

像上面这个类,是一个父类,但是它的_cmds字段,子类继承以后是访问不了的,需要借助继承到自身子类的父类方法进行访问。

class TestICommand :public ICommandRunnable
{
public:
TestICommand();
~TestICommand();
//线程执行函数
//virtual void  PushCommand(BaseCommand *cmd, bool front = false);
void run();
private:
//线程启动锁
bool m_bIsRunning;
CLock m_lkRunLock;
SegEvent m_evtWait;

};

void TestICommand::run()
{
while (m_bIsRunning)
{

BaseCommand* pCmd = NULL;              
PushCommand(pCmd);                                 //这里只是代码的构成,并不是真实的写法,只是为了解释而进行的代码书写
if ((pCmd = (BaseCommand*)PopCommand()) == NULL)

{
m_evtWait.wait(3000);
continue;
}
}

}

像上面的两个方法,一个是父类的公有成员函数 PushCommand,一个是父类的受保护成员函数PopCommand

(受保护的成员函数在父类里相当于私有成员,只是继承到子类的时候,子类内部可以访问,该成员,在类外是不能访问的,这里在强调下什么是类内,就是在定义类的时候,类的成员函数可以进行访问,而外部直接定义一个类对象,然后直接访问对象的成员属性是访问不了的。)

子类继承了这个方法,然后是可以直接使用的,来访问 继承到自身的,父类的私有成员,_cmds,如果你把父类的两个方法改成private那就不行了,子类虽然继承过来这个方法,但是不能访问,像上面那样调用就会报错了。

误区2:子类继承父类以后,如果继承了父类的方法,然后只想在方法上进行相应的操作,就是不完全重写,怎么办?

而另外还有一种方式,就是你想继承父类的方法,你感觉你除了要做父类的事以外,你还想要干点其它的事,也就是说父类写的方法,pushcommand不能够满足你的需求 了,那就可以对父类的方法进行重写。

如下所示:

class TestICommand :public ICommandRunnable
{
public:
TestICommand();
~TestICommand();
//线程执行函数
virtual void  PushCommand(BaseCommand *cmd, bool front = false);
void run();
private:
//线程启动锁
bool m_bIsRunning;
CLock m_lkRunLock;
SegEvent m_evtWait;

};

void  TestICommand::PushCommand(BaseCommand *cmd, bool front = false)
{
//_cmds.push_front(cmd);

ICommandRunnable::PushCommand(cmd);

m_evtWait.set();

}

我在pushcommand后想让线程跑起来,就得添加一个m_evtWait.set();而原来的父类的函数就满足不了需求了,

就可以采用两种方式来处理这个问题

(1)我就要对这个方法进行重写,也就是多态,然而我会先不调用父类父类的方法,我想直接按照红色部分操作,这是不可以的,因为这个字段在父类那里是私有的,子类自己写的方法是不能进行访问的,所以要用父类的方法进行访问,所以应该写成第二种绿色的形式,进行访问,然后后面再加上额外的操作。

(2)不用对父类的方法进行重写,原来继承下来的方法继续使用,新定于一个方法,Start.

如下代码所示:

class  Threadimpl
{
public:
Threadimpl();
virtual ~Threadimpl();
void  startimpl(IRunable* prunable);
void  joinImpl();
bool  joinImpl(int  milliseconds);
bool  isRunningImpl() const;
static void sleepImpl(int milliseconds);
#if defined(_USRDLL)
typedef DWORD(WINAPI *Entry)(LPVOID);
#else
typedef unsigned(__stdcall *Entry)(void*);
#endif
protected:
void threadCleanup();

private:
IRunable* _pRunnableTarget;
int _stackSize;
int _nParam;
HANDLE _thread;
};
inline void Threadimpl::sleepImpl(int milliseconds)
{
Sleep(DWORD(milliseconds));
}

class  Thread : public Threadimpl
{
public:
Thread();
virtual ~Thread();
void  start(IRunable* prunable);//下面的所有函数都是直接调用父类的函数
void join();
bool join(int milliseconds);
bool isRunning() const;
static void sleep(int milliseconds);
};

class TestICommand :public ICommandRunnable,public Thread
{
public:
TestICommand();
~TestICommand();
//线程执行函数
bool Start();
bool Stop();
void run();
private:
//线程启动锁
bool m_bIsRunning;
CLock m_lkRunLock;
SegEvent m_evtWait;

 

};

bool TestICommand::Start()
{
if (!m_bIsRunning)
{
m_evtWait.reset();
m_bIsRunning = true;
start(this);
}
return true;
}


bool TestICommand::Stop()
{
if (m_bIsRunning)
{
m_bIsRunning = false;
m_evtWait.set();
join();
}
return true;
}

函数里调用继承的函数,然后再添加自己想要的处理,这样也可以满足需求。这个是针对TestICommand 继承Thread来举例说明的

误区3:子类继承父类以后,我在子类中使用了父类的方法,用father::method进行操作的时候,使用到了this指针修改属性的值了,这个时候修改的是谁呢?是父类的值还是子类的值呢?

这个分不清楚,主要还是第一点不是特别清晰,就是继承到的父类方法,是共用的一段代码而已,里面的this指针是对象传进来的,具体看下我写的demo就会清楚了。

#include<iostream>
#include<string>
using namespace std;


class father
{
public:
	father(string name)
	{
		this->name = name;
		cout << "constructor father name:" << this->name << endl;
	}
	~father()
	{
	}
	void set(string name)
	{
		this->name = name;
	}
	void get()
	{
		cout << "father get "<<this->name << endl;
	}
public:
	string name;
};

class son :public father
{
public:
	son() :father("father")
	{
	}
	~son()
	{
	}
	void set(string name);
	void get();
};
void son::set(string name)
{
	father::set(name);//调用父类的方法,相当于把父类方法代码粘贴到此处 this指子类的这个对象
/*
上面的father::set(name);相当于换成下面的这句,this指针指的是谁就清楚了吧
        //void set(string name)
	//{
		this->name = name;
	//
*/
}
void son::get()
{
	cout << "son get "<<this->name << endl;
	father::get();//调用父类的方法,相当于把父类方法代码粘贴到此处 this指子类的这个对象
}
int main()
{
	son test = son();
	test.set("son");
	test.get();
}

输出的结果是下面的截图

误区4:子类继承父类以后,我在子类中使用了父类的方法,用father::method,那子类能不能使用父类的成员变量呢?

答案是否定的,因为父类的成员变量 已经继承到了子类,你操作的都是你自己的变量了,只所以方法可以调用父类的,但是前提是你在重写父类的时候,可以调用,其他的时候调用不了,而字段不能重写

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在C++中,继承是一种重要的面向对象编程概念,它允许我们定义一个新的类,该类继承现有类的所有属性和方法。派生类是继承类的子类,它可以使用继承类的所有属性和方法,并且可以添加自己的属性和方法。 在实验中,我们学习了C++中继承和派生类的相关知识,包括: 1. 继承类的访问控制:公有继承、私有继承和保护继承。 2. 多重继承:一个派生类可以同时继承多个基类。 3. 虚函数和纯虚函数:虚函数是在基类中定义的函数,可以在派生类中重写;纯虚函数是没有实现的虚函数,必须在派生类中实现。 4. 虚函数表和虚函数指针:虚函数表是用于存储虚函数地址的表,虚函数指针指向虚函数表。 通过实验,我们能够更深入理解C++中继承和派生类的概念和应用,能够更加灵活地使用面向对象编程思想来设计和实现程序。 ### 回答2: 继承是面向对象编程中的重要概念之一,它允许我们在已有类的基础上创建新的类,新类可以继承并拥有已有类的属性和方法。通过继承,可以减少代码的重复性,并且使代码更加可维护和扩展。 在进行派生类实验的过程中,我深刻体会到了继承的重要性和灵活性。首先,通过定义一个基类,我可以将一些通用的属性和方法抽象出来,避免在每个派生类中都重复定义。这为程序的整体结构设计提供了便捷。 其次,派生类可以在继承基类的基础上进行扩展,添加新的属性和方法。这种灵活性使得派生类在满足基本功能的同时,也能根据具体需求进行定制化开发。例如,在一个动物类的基础上,我可以派生出猫、狗等具体的动物子类,它们各自有着自己的特点和行为。 另外,继承还支持多层次的派生关系。我可以从一个派生类中再派生出新的派生类,这样可以形成类的层次结构。这种层次化设计可以更好地组织代码,使得代码更加清晰可读。 通过这次实验,我进一步理解继承性与派生类的概念,学会了如何设计和使用继承关系。同时,我也认识到了继承关系的合理运用能够提高代码的效率和可维护性。继承不仅是面向对象编程的基础,也是实现代码重用和扩展的重要工具之一。在今后的编程实践中,我将更加灵活地运用继承,提高代码的质量和可扩展性。 ### 回答3: 继承性是面向对象编程中的一个重要特性,指的是子类能够继承父类的属性和方法。通过继承,子类可以重用父类的代码,并且可以在此基础上进行扩展和修改。 派生类是指通过继承父类而创建的新类。在派生类中,可以通过重写父类的方法,改变其行为,实现多态性。派生类还可以新增自己的成员变量和成员方法,以满足自身的特殊需求。 在实验中,我们通过创建父类和派生类的关系,研究了继承性和派生类的特性。 通过继承,我们可以将通用的属性和方法放在父类中,让子类共享这些代码。这样可以提高代码的重用性和可维护性。同时,当需要对父类中的方法进行修改时,只需在子类中进行重写,不会对其他子类造成影响。 在派生类中,我们可以根据需要重写父类的方法,改变其行为。这使得可以根据实际情况来实现多态性,同一个方法在不同的派生类中可能表现出不同的行为。 派生类还可以新增自己的成员变量和成员方法。通过这样的方式,可以为子类添加独特的功能,以满足特定的需求。 继承性和派生类是面向对象编程中非常重要的概念。通过合理运用这两个特性,可以使代码更加模块化和可扩展,提高代码的复用性和可维护性。同时,派生类的特性也使得面向对象编程更加灵活,可以根据实际需求进行扩展和修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值