C++基础6 【继承】 类型兼容 satatic 多继承 虚继承 【多态】 案例 虚析构函数 重载重写重定义

【继承】

继承的访问控制域 图

类型兼容性原则  指针 与 引用

用子类直接初始化父类

类的继承模型示意 图

【继承结论】

【非常重要的概念】

继承与组合混搭情况下,构造和析构调用原则

原则:先构造父类,再构造成员变量、最后构造自己

先析构自己,在析构成员变量、最后析构父类

继承中,同名的成员变量的处理办法

继承中,同名的成员函数的处理办法

派生类中的static关键字

如果静态成员变量,你没有使用,也没有初始化的话 编译不会报错

经典错误 :

类中函数默认是private的,无法在外部访问 具体表现为: 我连变量都无法创建

传说中的多继承,让我们来看看大家都嫌弃的C++多继承

多继承的二义性

虚继承

虚继承的局限性

多继承, 手动调用域作用

虚继承: virtual关键字

【多态】

【问题引出】【指针 引用 】类型兼容性原则上的函数重写,为什么效果?

【问题的解决】多态的使用

一般情况下,为了醒目,都加上virtual关键字

【多态案例】雷霆战机

如果没写virtual关键字,C++编译时根据A类型去执行power函数,在编译阶段就决定了函数的调用

写了virtuial关键字,迟绑定,在运行的时候,很据具体的对象类型,执行不同的函数,表现成多态

【面试题】【虚析构函数是用来干嘛的?】

【答案】通过父类指针,把所有的子类对象的析构函数执行一遍,释放所有子类资源

不加virtual 不使用函数,可以正常析构所有子类的资源

【面试题】【很难】函数重载·重写·重定义

继承重要说明 

1、子类拥有父类的所有成员变量和成员函数

4、子类可以拥有父类没有的方法和属性

2、子类就是一种特殊的父类

3、子类对象可以当作父类对象使用

3.2派生类的访问控制

派生类继承了基类的全部成员变量和成员方法(除了构造和析构之外的成员方法),但是这些成员的访问属性,在派生过程中是可以调整的。

类的继承,初步:

[email protected]:~$ cat main.cpp 
#include <iostream>
using namespace std;

class Parent
{
public:
	void printf()
	{
		cout << "I‘m Parent \n";
	}
	int a;
};

//继承的三种方式
//:public Parent
//:protected Parent
//:private Parent

class A :public Parent
{
};
int main()
{
	A a1;
	a1.printf();
	a1.a = 6;
	cout <<  a1.a << endl;
	return 0;
}

[email protected]:~$ g++ -g  -o run main.cpp && ./run 
I‘m Parent 
6
[email protected]:~$

2 继承的访问控制域

C++中子类对外访问属性表


父类成员访问级别



public


proteced


private


public


public


proteced


private


proteced


proteced


proteced


private


private


private


private


Private

【难点】类型兼容性原则 

类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代。通过公有继承,派生类得到了基类中除构造函数、析构函数之外的所有成员。这样,公有派生类实际就具备了基类的所有功能,凡是基类能解决的问题,公有派生类都可以解决。类型兼容规则中所指的替代包括以下情况:

子类对象可以当作父类对象使用

子类对象可以直接赋值给父类对象

子类对象可以直接初始化父类对象

父类指针可以直接指向子类对象

父类引用可以直接引用子类对象

在替代之后,派生类对象就可以作为基类的对象使用,但是只能使用从基类继承的成员。

类型兼容规则是多态性的重要基础之一。

总结:子类就是特殊的父类 (base *p = &child;)

类型兼容性原则:程序热身:

[email protected]:~$ cat main.cpp 
#include <iostream>
using namespace std;

class Parent
{
public:
	void printf_p()
	{
		cout << "I‘m Parent \n";
	}
	int a;
};

class A :public Parent
{
public:
	void printf_c()
	{
		cout << "I‘m child \n";
	}
};
int main()
{
	A a1;
	a1.printf_p();
	a1.printf_c();
	return 0;
}

[email protected]:~$ g++ -g  -o run main.cpp && ./run 
I‘m Parent 
I‘m child 
[email protected]:~$

【重点】类型兼容性原则:定义父类指针,指向子类对象,子类调用父类函数

【注意】尽管这个是父类指针,指向了儿子,但是还是无法调用儿子的函数

[email protected]:~$ cat main.cpp 
#include <iostream>
using namespace std;

class Parent
{
public:
	void printf_p()
	{
		cout << "I‘m Parent \n";
	}
	int a;
};

class Child :public Parent
{
public:
	void printf_c()
	{
		cout << "I‘m child \n";
	}
};

int main()
{
	Child	c1;
	Parent	*p1 = NULL;
	p1 = &c1;
	p1->printf_p();
	return 0;
}

[email protected]:~$ g++  -o run main.cpp  && ./run 
I‘m Parent 
[email protected]:~$

对象的指针 与 引用

赋值兼容性原则:把子类对象传给父类,C++编译器不会报错

[email protected]:~$ cat main.cpp 
#include <iostream>
using namespace std;

class Parent
{
public:
	void printf_p()
	{
		cout << "I‘m Parent \n";
	}
	int a;
};

class Child :public Parent
{
public:
	void printf_c()
	{
		cout << "I‘m child \n";
	}
};
void fun_1(Parent *p)
{
	p->printf_p();
}
void fun_2(Parent &p)
{
	p.printf_p();
}

int main()
{
	Child	c1;
	Parent	p1;
	fun_1(&c1);
	fun_1(&p1);

	fun_2(c1);
	fun_2(p1);
	return 0;
}

[email protected]:~$ g++  -o run main.cpp  && ./run 
I‘m Parent 
I‘m Parent 
I‘m Parent 
I‘m Parent 
[email protected]:~$

用子类直接初始化父类:

[email protected]:~$ cat main.cpp 
#include <iostream>
using namespace std;

class Parent
{
public:
	void printf_p()
	{
		cout << "I‘m Parent \n";
	}
	int a;
};

class Child :public Parent
{
public:
	void printf_c()
	{
		cout << "I‘m child \n";
	}
};

int main()
{
	Child	c1;
	Parent	p1  = c1;//因为子类就是特殊的父类,会调用拷贝构造函数
	Parent	p2(c1);
	return 0;
}

[email protected]:~$ g++  -o run main.cpp  && ./run 
[email protected]:~$

类的继承模型示意图

继承中的对象模型

类在C++编译器的内部可以理解为结构体

子类是由父类成员叠加子类新成员得到的

继承中构造和析构

问题:如何初始化父类成员?父类与子类的构造函数有什么关系

在子类对象构造时,需要调用父类构造函数对其继承得来的成员进行初始化

在子类对象析构时,需要调用父类析构函数对其继承得来的成员进行清理

【结论】

先调用父类的构造函数,再调用子类的构造函数

先调用子类的析构函数,再调用父类的析构函数

	[email protected]:~$ cat main.cpp 
#include <iostream>
using namespace std;

class Parent
{
public:
	Parent(int a,int b)
	{
		this->a = a;
		this->b = b;
		cout << "Parent 构造函数 \n";
	}
	~Parent()
	{
		cout << "Parent 析构函数 \n";
	}

private:
	int a;
	int b;
};

class Child :public Parent
{
public:
	Child(int a,int b,int c):Parent(b,c)
	{
		this->c = c;
		cout << "Child 构造函数\n";
	}
	~Child()
	{
		cout << "Child 析构函数\n";
	}
	void printf_c()
	{
		cout << "I‘m child \n";
	}
private:
	int c;
};

int main()
{
	Child	c1(1,2,3);
	return 0;
}

[email protected]:~$ g++  -o run main.cpp  && ./run 
Parent 构造函数 
Child 构造函数
Child 析构函数
Parent 析构函数 
[email protected]:~$

【非常重要的概念】

继承与组合混搭情况下,构造和析构调用原则

原则:先构造父类,再构造成员变量、最后构造自己

        先析构自己,在析构成员变量、最后析构父类

[email protected]:~$ cat main.cpp 
#include <iostream>
using namespace std;
class Obj
{
public:
	Obj(int a)
	{
		cout << "Obj 构造函数 a="<<a<<endl;
	}
	~Obj()
	{
		cout << "Obj 析构函数\n";
	}
};

class Parent:public Obj
{
public:
	Parent(int a,int b):Obj(11)
	{
		cout << "Parent 构造函数 \n";
	}
	~Parent()
	{
		cout << "Parent 析构函数 \n";
	}
};

class Child :public Parent
{
public:
	Child(int a,int b,int c):Parent(b,c),obj1(22),obj2(33)
	{
		cout << "Child 构造函数\n";
	}
	~Child()
	{
		cout << "Child 析构函数\n";
	}
private:
	Obj obj1;
	Obj obj2;
};

int main()
{
	Child	c1(1,2,3);
	return 0;
}

[email protected]:~$ g++  -o run main.cpp  && ./run 
Obj 构造函数 a=11
Parent 构造函数 
Obj 构造函数 a=22
Obj 构造函数 a=33
Child 构造函数
Child 析构函数
Obj 析构函数
Obj 析构函数
Parent 析构函数 
Obj 析构函数
[email protected]:~$

继承中,同名的成员变量的处理办法:

[email protected]:~$ cat main.cpp 
#include <iostream>
using namespace std;

class A
{
public:
	void get_1()
	{
		cout <<b << endl;
	}
	int a;
	int b;
};

class B :public A
{
public:
	void get_2()
	{
		cout <<b << endl;
	}
	int b;
	int c;
};

int main()
{
	B b1;
	b1.b = 1111;
	b1.A::b  = 2222;
	b1.get_2();
	b1.get_1();

	//当然也可以这么干:
	b1.B::b = 100;
	b1.A::b = 200;
	b1.get_2();
	b1.get_1();

	return 0;
}

[email protected]:~$ g++  -o run main.cpp  && ./run 
1111
2222
100
200
[email protected]:~$

继承中,同名的成员函数的处理办法:

[email protected]:~$ cat main.cpp 
#include <iostream>
using namespace std;

class A
{
public:
	void get_1()
	{
		cout <<b << endl;
	}
	void fun()
	{
		cout << "AAAAAAAAAAAAAAAAAAAAAA "<< endl;
	}
	int a;
	int b;
};

class B :public A
{
public:
	void get_2()
	{
		cout <<b << endl;
	}
	void fun()
	{
		cout << " BBBBBBBBBBBBBBBBBB "<< endl;
	}
	int b;
	int c;
};

int main()
{
	B b1;
	b1.b = 1111;
	b1.A::b  = 2222;
	b1.fun();	//默认是调用自己的函数

	//当然也可以这么干:
	b1.A::fun();
	b1.B::fun();

	return 0;
}

[email protected]:~$ g++  -o run main.cpp  && ./run 
 BBBBBBBBBBBBBBBBBB 
AAAAAAAAAAAAAAAAAAAAAA 
 BBBBBBBBBBBBBBBBBB 
[email protected]:~$

派生类中的static关键字

继承和static关键字在一起会产生什么现象哪?

理论知识

基类定义的静态成员,将被所有派生类共享

根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问性质 (遵守派生类的访问控制)

派生类中访问静态成员,用以下形式显式说明:

类名 :: 成员

或通过对象访问对象名 . 成员

[email protected]:~$ cat main.cpp 
#include <iostream>
using namespace std;

class A
{
public:
	static int a;
};
//完成初始化
int A::a = 99;

class B :private A
{
public:
	void fun()
	{
		cout << a << endl;
	}
};

int main()
{
	B b1;
	b1.fun();
	//b1.a = 100;
	/*
	不能在类的外面访问private的成员变量
	main.cpp: In function ‘int main()’:
	main.cpp:10:5: error: ‘int A::a’ is inaccessible
	int A::a = 99;
	*/

	return 0;
}

[email protected]:~$ g++  -o run main.cpp  && ./run 
99
[email protected]:~$

【注意隐患】

如果静态成员变量,你没有使用,也没有初始化的话

编译不会报错:

[email protected]:~$ 
[email protected]:~$ cat main.cpp 
#include <iostream>
using namespace std;

class A
{
public:
	static int a;
};
//完成初始化
//int A::a = 99; 这句话不简单是变量的赋值,更重要的是你要给我分配内容

class B :private A
{
public:
	void fun()
	{
//		cout << a << endl;
	}
};

int main()
{
	B b1;
	b1.fun();

	return 0;
}

[email protected]:~$ g++  -o run main.cpp  && ./run 
[email protected]:~$

【经典错误:】

类中函数默认是private的,无法在外部访问

具体表现为:

我连变量都无法创建

[email protected]:~$ cat main.cpp 
#include <iostream>
using namespace std;

class A
{
	A()
	{
		cout << "A \n";
	}
};

int main()
{
	A a1;
	return 0;
}

[email protected]:~$ g++  -o run main.cpp  && ./run 
main.cpp: In function ‘int main()’:
main.cpp:6:2: error: ‘A::A()’ is private
  A()
  ^

传说中的多继承,让我们来看看大家都嫌弃的C++多继承


1.正常的多继承语法

[email protected]://990487026.blog.51cto.com~$ cat main.cpp 
#include <iostream>
using namespace std;

class A
{
public:
	A(int a)
	{
		this->a = a;
	}
	void fun_1()
	{
		cout << "AAAAAAAAAAAAAAAA\n";
	}
	int a;
};

class B
{
public:
	B(int a)
	{
		this->a = a;
	}
	void fun_2()
	{
		cout << "BBBBBBBBBBBBBBBBBBBBB\n";
	}
	int a;
};

class C :public A,public B
{
public:
	C(int a,int b,int c):A(a),B(b)
	{
		this->a = c;
	}
	void fun_3()
	{
		cout << "CCCCCCCCCCCCCCCCCCC\n";
	}
	int a;
};

int main()
{
	C c1(1,2,3);
	c1.fun_1();
	c1.fun_2();
	c1.fun_3();

	return 0;
}

[email protected]://990487026.blog.51cto.com~$ g++  -o run main.cpp  && ./run 
AAAAAAAAAAAAAAAA
BBBBBBBBBBBBBBBBBBBBB
CCCCCCCCCCCCCCCCCCC
[email protected]://990487026.blog.51cto.com~$

多继承的二义性

C++编译器不知道谁那个成员属性

[email protected]://990487026.blog.51cto.com~$ cat main.cpp 
#include <iostream>
using namespace std;

class A
{
public:
	int a;
};

class B
{
public:
	int a;
};

class C :public A,public B
{
};

int main()
{
	C c1;
	c1.a = 100;

	return 0;
}

[email protected]://990487026.blog.51cto.com~$ g++  -o run main.cpp  && ./run 
main.cpp: In function ‘int main()’:
main.cpp:23:5: error: request for member ‘a’ is ambiguous
  c1.a = 100;
     ^

制作C++编译器的大牛们想的办法:虚继承

[email protected]://990487026.blog.51cto.com~$ cat main.cpp 
#include <iostream>
using namespace std;

class A
{
public:
	int a;
};

class B	:virtual public A
{
};

class C :virtual public A
{
};
class D:public B,public C
{

};

int main()
{
	D d1;
	d1.a = 100;
	cout << "a="<<d1.a<<endl;

	return 0;
}

[email protected]://990487026.blog.51cto.com~$ g++  -o run main.cpp  && ./run 
a=100
[email protected]://990487026.blog.51cto.com~$

虚继承的局限性:

因为虚继承只能解决共同一个老祖先的问题

[email protected]://990487026.blog.51cto.com~$ cat main.cpp 
#include <iostream>
using namespace std;

class A
{
public:
	int a;
};

class B
{
public:
	int a;
};

class C :virtual public A,virtual public B
{
};

class D : public A, public B
{
};
int main()
{
	C c1;	c1.a = 100;
	cout << "a="<<c1.a<<endl;

	D d1;	d1.a = 100;
	cout << "a="<<d1.a<<endl;
	return 0;
}

[email protected]://990487026.blog.51cto.com~$ g++  -o run main.cpp  && ./run 
main.cpp: In function ‘int main()’:
main.cpp:25:11: error: request for member ‘a’ is ambiguous
  C c1; c1.a = 100;
           ^
main.cpp:13:6: note: candidates are: int B::a
  int a;
      ^
main.cpp:7:6: note:                 int A::a
  int a;
      ^
main.cpp:26:19: error: request for member ‘a’ is ambiguous
  cout << "a="<<c1.a<<endl;
                   ^
main.cpp:13:6: note: candidates are: int B::a
  int a;
      ^
main.cpp:7:6: note:                 int A::a
  int a;
      ^
main.cpp:28:11: error: request for member ‘a’ is ambiguous
  D d1; d1.a = 100;
           ^
main.cpp:13:6: note: candidates are: int B::a
  int a;
      ^
main.cpp:7:6: note:                 int A::a
  int a;
      ^
main.cpp:29:19: error: request for member ‘a’ is ambiguous
  cout << "a="<<d1.a<<endl;
                   ^
main.cpp:13:6: note: candidates are: int B::a
  int a;
      ^
main.cpp:7:6: note:                 int A::a
  int a;
      ^
[email protected]://990487026.blog.51cto.com~$

多继承就是这样,只有程序员自己解决:

[email protected]://990487026.blog.51cto.com~$ cat main.cpp 
#include <iostream>
using namespace std;

class A
{
public:
	int a;
};

class B
{
public:
	int a;
};

class C : public A, public B
{
};
int main()
{
	C c1;
	c1.A::a = 100;
	c1.B::a = 200;
	cout << "A::a="<<c1.A::a << endl;
	cout << "B::a="<<c1.B::a << endl;
	return 0;
}

[email protected]://990487026.blog.51cto.com~$ g++  -o run main.cpp  && ./run 
A::a=100
B::a=200
[email protected]://990487026.blog.51cto.com~$

加不加virtual关键字的区别:

[email protected]://990487026.blog.51cto.com~$ cat main.cpp 
#include <iostream>
using namespace std;

class A
{
public:
	int a;
};

class B:public A
{
};

class C : public A
{
};

int main()
{
	cout << sizeof(A) << endl;
	cout << sizeof(B) << endl;
	cout << sizeof(C) << endl;
	return 0;
}

[email protected]://990487026.blog.51cto.com~$ g++  -o run main.cpp  && ./run 
4
4
4
[email protected]://990487026.blog.51cto.com~$

加了virtual之后,C++编译器会偷偷地添加属性,实现虚继承

[email protected]://990487026.blog.51cto.com~$ cat main.cpp 
#include <iostream>
using namespace std;

class A
{
public:
	int a;
};

class B:virtual public A
{
};

class C:virtual public A
{
};

int main()
{
	cout << sizeof(A) << endl;
	cout << sizeof(B) << endl;
	cout << sizeof(C) << endl;
	return 0;
}

[email protected]://990487026.blog.51cto.com~$ g++  -o run main.cpp  && ./run 
4
16
16
[email protected]://990487026.blog.51cto.com~$

【多态】


【难点】面向对象:封装-> 继承-> 多态

封装:突破了函数的概念,用类做函数的参数,使用对象的属性和方法

继承:前人的成果,可以直接拿来用,代码复用性

多态:后人的成果,可以直接拿来用,可以使用未来【软件行业的最高境界】

C语言中,指针的间接赋值是指针存在的最大意义

1定义两个变量  2建立关联  3间接修改变量的值

面向对象:多态成立的3个条件

1 要有继承,  2 函数重写   3 用父类指针指向子类

【问题引出】【指针】类型兼容性原则上的函数重写:

[email protected]://990487026.blog.51cto.com~$ cat main.cpp 
#include <iostream>
using namespace std;

class A
{
public:
	A(int a)
	{
		this->a = a;
		//cout << "AAAAAAAAAAA\n";
	}
	void fun()
	{
		cout << "AAAAAAAAAAA\n";
	}
	int a;
};

class B:public A
{
public:
	B(int a):A(a)
	{
		this->a = a;
		//cout << "BBBBBBBBBBBB\n";
	}
	void fun()
	{
		cout << "BBBBBBBBBBBB\n";
	}
	int a;
};

int main()
{
	A *p = NULL;

	A a(10);
	B b(20);
	p = &a;
	p->fun();

	p = &b;		//因为赋值兼容性原则,C++编译器不报错
	p->fun();	//这儿执行的是父类的函数

	return 0;
}

[email protected]://990487026.blog.51cto.com~$ g++  -o run main.cpp  && ./run 
AAAAAAAAAAA
AAAAAAAAAAA
[email protected]://990487026.blog.51cto.com~$

【问题引出】【引用】类型兼容性原则上的函数重写:

[email protected]://990487026.blog.51cto.com~$ cat main.cpp 
#include <iostream>
using namespace std;

class A
{
public:
	A(int a)
	{
		this->a = a;
		//cout << "AAAAAAAAAAA\n";
	}
	void fun()
	{
		cout << "AAAAAAAAAAA\n";
	}
	int a;
};

class B:public A
{
public:
	B(int a):A(a)
	{
		this->a = a;
		//cout << "BBBBBBBBBBBB\n";
	}
	void fun()
	{
		cout << "BBBBBBBBBBBB\n";
	}
	int a;
};

int main()
{
	A a(10);
	B b(20);

	A &p = a;	p.fun();
	p = b;		p.fun();

	return 0;
}

[email protected]://990487026.blog.51cto.com~$ g++  -o run main.cpp  && ./run 
AAAAAAAAAAA
AAAAAAAAAAA
[email protected]://990487026.blog.51cto.com~$

【问题引出】函数的方式【指针】【引用】效果还是这样

[email protected]://990487026.blog.51cto.com~$ cat main.cpp 
#include <iostream>
using namespace std;

class A
{
public:
	A(int a)
	{
		this->a = a;
	}
	void fun()
	{
		cout << "AAAAAAAAAAA\n";
	}
	int a;
};

class B:public A
{
public:
	B(int a):A(a)
	{
		this->a = a;
	}
	void fun()
	{
		cout << "BBBBBBBBBBBB\n";
	}
	int a;
};

void fun1(A *p)
{
	p->fun();
}
void fun2(A &p)
{
	p.fun();
}

int main()
{
	A a(10);
	B b(20);
	fun1(&a);
	fun1(&b);

	fun2(a);
	fun2(b);
	return 0;
}

[email protected]://990487026.blog.51cto.com~$ g++  -o run main.cpp  && ./run 
AAAAAAAAAAA
AAAAAAAAAAA
AAAAAAAAAAA
AAAAAAAAAAA
[email protected]://990487026.blog.51cto.com~$

【问题的解决  多态的使用】

一般情况下,为了醒目,都加上virtual关键字

[email protected]://990487026.blog.51cto.com~$ cat main.cpp 
#include <iostream>
using namespace std;

class A
{
public:
	A(int a)
	{
		this->a = a;
	}
	virtual void fun()
	{
		cout << "AAAAAAAAAAA\n";
	}
	int a;
};

class B:public A
{
public:
	B(int a):A(a)
	{
		this->a = a;
	}
	virtual void fun() //一般情况下,只要父类中写了virtual,之类中可写可不写
	{
		cout << "BBBBBBBBBBBB\n";
	}
	int a;
};

void fun1(A *p)
{
	p->fun();
}
void fun2(A &p)
{
	p.fun();
}

int main()
{
	A a(10);
	B b(20);
	fun1(&a);
	fun1(&b);

	fun2(a);
	fun2(b);
	return 0;
}

[email protected]://990487026.blog.51cto.com~$ g++  -o run main.cpp  && ./run 
AAAAAAAAAAA
BBBBBBBBBBBB
AAAAAAAAAAA
BBBBBBBBBBBB
[email protected]://990487026.blog.51cto.com~$


【多态案例】

模拟两个战机PK

A为我方老战机

B为我方高级战机

C为敌机

这样在框架下干活

[email protected]://990487026.blog.51cto.com~$ cat main.cpp 
#include <iostream>
using namespace std;

class A
{
public:
	virtual int power()
	{
		return 10;
	}
};

class B:public A
{
public:
	virtual int power() //一般情况下,只要父类中写了virtual,之类中可写可不写
	{
		return 25;
	}
};

class C
{
public:
	int power()
	{
		return 15;
	}
};

void play(A *p1,C *p2)
{
	if(p1->power() > p2->power())
	{
		cout << "win \n";
	}
	else
	{
		cout << "fail \n";
	}
}

int main()
{
	A a;
	B b;
	C c;
	play(&a,&c);
	play(&b,&c);

	return 0;
}

[email protected]://990487026.blog.51cto.com~$ g++  -o run main.cpp  && ./run 
fail 
win 
[email protected]://990487026.blog.51cto.com~$

【结论】:

如果没写virtual关键字,C++编译时根据A类型去执行power函数,在编译阶段就决定了函数的调用

写了virtuial关键字,迟绑定,在运行的时候,很据具体的对象类型,执行不同的函数,表现成多态

【面试题】【虚析构函数是用来干嘛的?】

【引出问题】

[email protected]://990487026.blog.51cto.com~$ cat main.cpp 
#include <iostream>
#include <string.h>
using namespace std;

class A
{
public:
	A()
	{
		p = new char[20];
		strcpy(p,"Hello");
		cout << " AAAAAAAAAAAA\n";
	}
	~A()
	{
		delete []p;
		cout << "~AAAAAAAAAAAA\n";
	}
private:
	char *p;
};

class B:public A
{
public:
	B()
	{
		p = new char[20];
		strcpy(p,"Linux");
		cout << " BBBBBBBBBBBBBBBB\n";
	}
	~B()
	{
		delete []p;
		cout << "~BBBBBBBBBBBBBBBB\n";
	}
private:
	char *p;
};

class C:public B
{
public:
	C()
	{
		p = new char[20];
		strcpy(p,"Ubuntu");
		cout <<  " CCCCCCCCCCCCCCCCCCCCC\n";
	}
	~C()
	{
		delete []p;
		cout <<  "~ CCCCCCCCCCCCCCCCCCCCC\n";
	}
private:
	char *p;
};

void fun(A *p)//赋值兼容性原则
{
	delete p;  // 这种语法不会表现为多态
}

int main()
{
	C *p = new C;
	fun(p);
	return 0;
}

[email protected]://990487026.blog.51cto.com~$ g++  -o run main.cpp  && ./run 
 AAAAAAAAAAAA
 BBBBBBBBBBBBBBBB
 CCCCCCCCCCCCCCCCCCCCC
~AAAAAAAAAAAA
[email protected]://990487026.blog.51cto.com~$

【答案】通过父类指针,把所有的子类对象的析构函数执行一遍,释放所有子类资源

【原理】如果没写virtual关键字,C++编译时根据A类型去执行power函数,在编译阶段就决定了函数的调用

写了virtuial关键字,迟绑定,在运行的时候,很据具体的对象类型,执行不同的函数,表现成多态

[email protected]://990487026.blog.51cto.com~$ cat main.cpp 
#include <iostream>
#include <string.h>
using namespace std;

class A
{
public:
	A()
	{
		p = new char[20];
		strcpy(p,"Hello");
		cout << " AAAAAAAAAAAA\n";
	}
	virtual ~A()
	{
		delete []p;
		cout << "~AAAAAAAAAAAA\n";
	}
private:
	char *p;
};

class B:public A
{
public:
	B()
	{
		p = new char[20];
		strcpy(p,"Linux");
		cout << " BBBBBBBBBBBBBBBB\n";
	}
	~B()
	{
		delete []p;
		cout << "~BBBBBBBBBBBBBBBB\n";
	}
private:
	char *p;
};

class C:public B
{
public:
	C()
	{
		p = new char[20];
		strcpy(p,"Ubuntu");
		cout <<  " CCCCCCCCCCCCCCCCCCCCC\n";
	}
	~C()
	{
		delete []p;
		cout <<  "~CCCCCCCCCCCCCCCCCCCCC\n";
	}
private:
	char *p;
};

void fun(A *p)	//父类写有virtual关键字
{
	delete p;
}

int main()
{
	C *p = new C;
	fun(p);
	return 0;
}

[email protected]://990487026.blog.51cto.com~$ g++  -o run main.cpp  && ./run 
 AAAAAAAAAAAA
 BBBBBBBBBBBBBBBB
 CCCCCCCCCCCCCCCCCCCCC
~CCCCCCCCCCCCCCCCCCCCC
~BBBBBBBBBBBBBBBB
~AAAAAAAAAAAA
[email protected]://990487026.blog.51cto.com~$

不加virtual 不使用函数,可以正常析构所有子类的资源:

[email protected]://990487026.blog.51cto.com~$ cat main.cpp 
#include <iostream>
#include <string.h>
using namespace std;

class A
{
public:
	A()
	{
		p = new char[20];
		strcpy(p,"Hello");
		cout << " AAAAAAAAAAAA\n";
	}
	~A()
	{
		delete []p;
		cout << "~AAAAAAAAAAAA\n";
	}
private:
	char *p;
};

class B:public A
{
public:
	B()
	{
		p = new char[20];
		strcpy(p,"Linux");
		cout << " BBBBBBBBBBBBBBBB\n";
	}
	~B()
	{
		delete []p;
		cout << "~BBBBBBBBBBBBBBBB\n";
	}
private:
	char *p;
};

class C:public B
{
public:
	C()
	{
		p = new char[20];
		strcpy(p,"Ubuntu");
		cout <<  " CCCCCCCCCCCCCCCCCCCCC\n";
	}
	~C()
	{
		delete []p;
		cout <<  "~CCCCCCCCCCCCCCCCCCCCC\n";
	}
private:
	char *p;
};

int main()
{
	C *p = new C;
	delete p;
	return 0;
}

[email protected]://990487026.blog.51cto.com~$ g++  -o run main.cpp  && ./run 
 AAAAAAAAAAAA
 BBBBBBBBBBBBBBBB
 CCCCCCCCCCCCCCCCCCCCC
~CCCCCCCCCCCCCCCCCCCCC
~BBBBBBBBBBBBBBBB
~AAAAAAAAAAAA
[email protected]://990487026.blog.51cto.com~$

【面试题】【很难】函数重载·重写·重定义

[email protected]://990487026.blog.51cto.com~$ cat main.cpp 
#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;

class Parent
{
	//这个三个函数都是重载关系
public: 
	void func() 
	{
	}

	void func(int i)
	{
	}

	void func(int i, int j)
	{
	}

	void func(int i, int j, int m , int n)
	{
	}
};

class Child : public Parent
{

public: 
	void func(int i, int j)
	{
	}
	void func(int i, int j, int k)
	{
	}
};

int main()
{
	Child c1;
	c1.func();
	return 0;
}

提示在子类中找不到对用的函数
[email protected]://990487026.blog.51cto.com~$ g++  -o run main.cpp  && ./run 
main.cpp: In function ‘int main()’:
main.cpp:43:10: error: no matching function for call to ‘Child::func()’
  c1.func();
          ^


分析:看图

子类无法重载父类的函数,父类同名函数将被名称覆盖

c1.func();

func函数的名字,在子类中发生了名称覆盖;子类的函数的名字,占用了父类的函数的名字的位置

因为子类中已经有了func名字的重载形式。。。。

编译器开始在子类中找func函数。。。。但是没有0个参数的func函数

c1.func(1, 3, 4, 5);

1 C++编译器 看到func名字 ,因子类中func名字已经存在了(名称覆盖).

所以c++编译器不会去找父类的4个参数的func函数

2 c++编译器只会在子类中,查找func函数,找到了两个func,一个是2个参数的,一个是3个参数的.

3 C++编译器开始报错.....  error C2661: “Child::func”: 没有重载函数接受 4 个参数

4 若想调用父类的func,只能加上父类的域名..这样去调用..



时间: 2024-08-03 07:13:54

C++基础6 【继承】 类型兼容 satatic 多继承 虚继承 【多态】 案例 虚析构函数 重载重写重定义的相关文章

c++继承方式和类型兼容的学习

继承方式 公有继承:公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态,而基类的私有成员仍然是私有的,不能被这个派生类的子类所访问. 私有继承:私有继承的特点是基类的共有成员和保护成员在继承时都作为派生类的私有成员,派生类的其他成员可以访问,但派生类的对象不可以访问,值得一提的是如果派生类继续作为基类进行派生,基类得全部成员在新的派生类中都无法访问,实际上是中断了基类的继续派生 保护继承:保护继承的特点是基类的共有成员和保护成员在继承时都作为派生类的保护成员,而派生

类型兼容规则

定义:类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代.因为公有继承,派生类得到了基类除了构造函数,析构函以外的所有成员.这样,公有派生类实际具备了基类的所有功能,凡是基类能解决的问题,公有派生类都可以解决.类型兼容规则所指的替代包括以下的情况:1.派生类的对象可以隐含转换为基类的对象.2.派生类的对象可以初始化基类的引用.3.派生类的指针可以隐含转化为基类的指针.注意:在替代之后,派生类的对象就可以作为基类的对象使用,但只能使用从基类继承的成员.例如:class B{

Python基础- 类和对象(使用、继承、派生、组合、接口、多态、封装、property、staticmethod、classmethod、反射、slots、上下文管理协议、元类)

标签: python对象 2017-07-01 16:28 79人阅读 评论(0) 收藏 举报  分类: python(11)  版权声明:本文为广大朋友交流学习,如有纰漏望不吝赐教,若存在版权侵犯请及时与我联系 目录(?)[+] 一.初识类和对象 在python3中类型就是类 先定义类在产生相对应的对象,也就是现有了概念再有了实体 class Garen: camp = 'Demacia' def attack(self): print('attack') 1.如何使用类 在python3:

TypeScript 类型兼容

在TS中有非常怪异的类型兼容的问题 : 如下 interface  LengthWish{     length : number; } class A{     length : number;     constructor(){         this.length = 2;     } } //定义一个LengthWish类型的变量 let _l : LengthWish = new A(); console.log(`length value is : ${ _l.length }`

20151024_004_C#基础知识(C#中的访问修饰符,继承,new关键字,里氏转换,is 和 as,多态,序列化与反序列化)

1:C#中的访问修饰符 public: 公共成员,完全公开,没有访问限制. private: 私有的,只能在当前类的内部访问. protected: 受保护的,只能在当前类的内部以及该类的子类中访问. internal: 只能在当前项目中访问,在同一个项目中,internal和public的权限是一样的. protected internal: protected + internal权限. 1.1:能够修饰类的访问修饰符只有两个: public / internal 1.2:接口中的成员不允许

【转】Java基础笔记 – 枚举类型的使用介绍和静态导入--不错

原文网址:http://www.itzhai.com/java-based-notes-introduction-and-use-of-an-enumeration-type-static-import.html#1.2.values方法的使用: Java基础笔记 – 枚举类型的使用介绍和静态导入 本文由arthinking发表于4年前 | Java基础 | 暂无评论 |  被围观 8,332 views+ 1.枚举(Enum):1.1.枚举类型中的两个静态方法:1.2.values方法的使用:

java基础篇(二) ----- java面向对象的三大特性之继承

java面向对象的三大特性之继承: 复用代码是java众多引人注目的功能之一,但是想要成为极具革命性的语言,仅仅是复制代码并对其加以改变是不够的.它必须能够做更多的事.引自<Think in java>    而代码复用也是程序员一直不断追求的.由此来说下代码复用的一种方式 java面向对象的三大特性--继承!! 在面向对象程序设计中,继承机制可以有效地组织类的结构.确定类之间的关系,在已有类的基础上开发新的类.继承机制在程序代码复用.提高软件开发效率.降低软件系统维护成本等方面具有重要作用.

Linux基础概念-----文件类型

普通文件:非目录或其他类型文件(-) 目录文件(d):Linux下目录也是文件,不过目录文件里面存放着是其他文件或目录的名字和对应的indoe号 indoe维基百科:inode是指在许多"类Unix文件系统"中的一种数据结构.每个inode保存了文件系统中的一个文件系统对象(包括文件.目录.设备文件.socket.管道, 等等)的元信息数据,但不包括数据内容或者文件名. 百度百科: inode 编号 用来识别文件类型,以及用于 stat C 函数的模式信息 文件的链接数目 属主的 UI

C#基础—不完整类型(局部类型)

1.为何要引入Partial Type 通常,我们在一个.cs文件中维护一个类,这也是一种一般约定,也算一个良好的编程风格,但是有些时候,这个类或类型非常庞大,这对可读性.维护性来说成了一种约定的限制. 当我们接触过一些 ORM 框架的自动生成代码映射的功能时,会发现,当我们使用工具生成了一些基本机构的代码时,很多类都是 partial 类,这是因为当我们在自动代码生成的基础上修改一些自定义的东西后,当再次auto-code时,自定义的代码就被覆盖了,当然我们通常也可以在框架提供的模板中做一些自