c++繼承,隱藏(重定義)

audience_fzn發表於2018-08-06

繼承是物件導向複用的重要手段,通過繼承定義一個類,繼承是型別之間的關係模型。共享公有的東西,實現各自本質的不同的東西。

一、三種繼承關係

  1. public:公有繼承
  2. protected:保護繼承
  3. private:私有繼承
  • 實現一個簡單的繼承關係:

繼承是一種複用的手段,在繼承關係裡基類(父類)的成員都會成為派生類(子類)的成員,由此達到複用的目的

  • 三種繼承關係下基類成員在派生類的訪問關係變化

 總結:

  1. 父類中私有的成員,在子類中不可見
  2. 如果一些父類成員不想被子類物件直接訪問,但需要在子類中能訪問,就定義為保護成員,可以看出保護成員限定符是因為繼承才出現的
  3. public繼承是一個介面繼承,保持is-a原則(是一個),每個父類可用的成員對子類也可用,因為每一個子類物件也是一個父類物件
  4. protected/private 繼承是一個實現繼承,保持has-a的原則(有一個),父類的部分成員並未完全成為子類介面的一部分,所以非特殊情況不會使用這倆種繼承,在絕大多數場景下使用的都是公有繼承。
  5. 不管是哪種繼承方式,在子類內部都可以訪問父類的公有和保護成員,但是父類的私有成員子類中不可見
  6. 使用關鍵字class時,預設的繼承方式時private,使用struct時預設的繼承方式時public,不過最好寫出繼承方式
  7. 在實際應用時,一般都使用public繼承,極少情況下才使用protected/private繼承

二、繼承與轉換——賦值相容規則

  1. 子類物件可以賦值給父類物件(切片)
  2. 父類物件不能賦值給子類物件
  3. 父類的指標/引用可以指向子類物件
  4. 子類的引用/指標不能指向父類物件(可以通過強制型別轉換)

下證:子類物件能不能賦值給父類物件(可以)

           父類物件能不能賦值給子類物件(不能)

class Person
{
public:
	void Display()
	{
		cout << _name << endl;
	}
protected:
	string _name;
};

class Student : public Person
{
public:
	int _num;
};

void Test()
{
	Person p;
	Student s;
	p = s;
        //s = p;
}
int main()
{
	Test();
	system("pause");
	return 0;
}

子類物件能不能賦給子類的指標/引用? (可以)

class Person
{
public:
	void Display()
	{
		cout << _name << endl;
	}
protected:
	string _name;
};

class Student : public Person
{
public:
	int _num;
};

void Test()
{
	Person p;
	Student s;
	Person* p1 = &s;//父類的指標
	Person& r1 = s;//父類引用
}
int main()
{
	Test();
	system("pause");
	return 0;
}

 

子類的指標/引用能不能指向父類的物件?

class Person
{
public:
	void Display()
	{
		cout << _name << endl;
	}
protected:
	string _name;
};

class Student : public Person
{
public:
	int _num;
};

void Test()
{
	Person p;
	Student s;
//	Student* p1 = &p;
//	Student& r1 = p;
        Student* p2 = (Student*)&p;
        Student& r2 = (Student&)p;
}
int main()
{
	Test();
	system("pause");
	return 0;
}

子類繼承了父類,又定義了只屬於它自己的內容,所以子類的指標指向的空間肯定比父類的的大,如果將子類的指標指向父類,對齊進行操作時就會導致嚴重的後果(記憶體越界),所以不允許子類的指標/引用指向父類。但是我們可以強制型別轉化,但是這也存在著一定的風險,可能導致程式奔潰(因為強制型別轉換後,後面的內容本來不屬於父類,如果對其操作可能是越界處理,訪問記憶體越界,導致程式奔潰)

三、繼承體系中的作用域

  • 在繼承體系中父類和子類都有獨立的作用域
  • 子類和父類中有同名的成員,子類成員將遮蔽父類對成員直接訪問(在子類成員中,可以使用父類::父類成員訪問)——隱藏——重定義
  • 注意在實際中在繼承體系裡最好不要定義同名成員

注意:這裡的隱藏,他是隻要名字一樣就會被隱藏。不管返回值,引數一樣或不一樣,並不影響。只要名字一樣,就會隱藏。

一個簡單的隱藏例子:

class person
{
public:
	void f()
	{
		cout << "Person::f()" << endl;
	}

	void f1()
	{
		cout << "Person::f1()" << endl;
	}
};

class student:public person
{
public:
	void f1(int n)  //構成隱藏
	{
		cout << "student::f1()" << endl;
	}

};

void Test()
{
	student s;
	s.f1();
}

int main()
{
	Test();
	system("pause");
	return 0;
}

 

子類中的f1()隱藏了父類中的f1(),所以呼叫時要按子類中的規則,加上引數

要呼叫父類中的:s.persin::f1();

隱藏(重定義)的一些坑:

class person
{
public:
	void f()
	{
		cout << "Person::f()" << endl;
	}

	void f1()
	{
		cout << "Person::f1()" << endl;
	}
};

class student:public person
{
public:

	
	void f1()
	{
		cout << "student::f1()" << endl;
	}
	void f2()
	{
		cout << _stunum << endl;
	}
	void f3()
	{
		_stunum = 3;
	}
	void f4()
	{
		f1();
		cout << "student::f4()" << endl;
	}
private:
	int _stunum;
};

場景一:

void Test()
{
	student* p = NULL;
	p->f1();
}

通過編譯,正常輸出;

場景二:

void Test()
{
	student* p = NULL;
	p->f2();
}

編譯通過,程式奔潰;

p->f2(),將p的地址作為第一個形參this指標,傳遞給函式,但是在訪問_stunum時要對其進行解引用。this指標為空,對空指標解引用,程式奔潰。

但是在訪問f1()時,我們雖然將p的地址作為實參傳遞給this指標,但是並沒有對其進行解引用操作, 所以程式沒有奔潰。

場景三:

void Test()
{
	student* p = NULL;
	p->f3();
}

編譯通過,程式奔潰;

void Test()
{
	student* p = NULL;
	p->f4();
}

 編譯通過,程式正常執行

雖然呼叫了成員函式,但是並沒有進行解引用,所以程式不會奔潰

相關文章