您的位置:首页 > 编程语言 > C语言/C++

C++ 类的相关知识 构造,析构,继承与多态

2014-12-14 21:11 701 查看
1,class

2,构造

3,析构

4,继承(extend)

-------------------------------------------------------------------------------------------------

#include <ios>

#include <iostream>

#include <string>

#include <stdio.h>

using std::string;

class person {

private:

char * name;

int age;

public:

person(); //构造函数

~person(); //构造函数

void set_name(const char * name); //成员函数

void set_age(int age);

const char * get_name();

int get_age();

void info();

};

-------------------------------------------------------------------------------------------------

#include"person.h"

person::person(){

name="test";

age=26;

printf("construct person\n");

}

person::~person(){

printf("deconstruct person\n");

}

void person::set_name(const char * name){

name=name;

}

void person::set_age(int age){

age=age;

}

const char * person::get_name(){

return name;

}

int person::get_age(){

return age;

}

void person::info(){

printf("%s, %d \n", name, age);

}

-------------------------------------------------------------------------------------------------

#include"person.h"

class student:public person{

private:

int id;

public:

student();

~student();

void set_id(int id);

int get_id();

void info();

};

-------------------------------------------------------------------------------------------------

#include "student.h"

student::student(){

id=1;

printf("construct student\n");

}

student::~student(){

printf("deconstruct student\n");

}

void student::set_id(int id){

id=id;

}

int student::get_id(){

return id;

}

void student::info(){

printf("name:%s, age:%d, id:%d\n",get_name(), get_age(), id);

}

-------------------------------------------------------------------------------------------------

#include <ios>

#include <iostream>

#include <string>

#include <stdio.h>

#include "person.h"

#include "student.h"

using namespace std;

int main()

{

person p1 = person();

p1.info();

student s1 = student();

s1.info();

return 0;

}

-------------------------------------------------------------------------------------------------

5,多态(一种接口,多种方法)

多态的需求,植物大战僵尸,里边子弹的设计,每个子弹都继承parent_bull,这样僵尸受到各种子弹伤害时,直接调用指向父类的子类指针,实现伤害的计算。

设计一个父类,父类的的伤害函数为virtual 函数。

在子类中实现该虚函数。这样就可以直接用该子类的父类的指针,调用该子类的虚函数。

参考:

1,/article/4925468.html

一、"类"
的介绍


在C++中,
用 ""
来描述 "对象",
所谓的"对象"是指现实世界中的一切事物。那么类就可以看做是对相似事物的抽象,
找到这些不同事物间的共同点,
如自行车和摩托车,
首先他们都属于"对象",
并且具有一定得相同点,
和一些不同点,
相同点如他们都有质量、都有两个轮子,
都是属于交通工具等。"都有质量"、"两个轮子"属于这个对象的属性,
而"都能够当做交通工具"属于该对象具有的行为,
也称方法。

类是属于用户自定义的数据类型,
并且该类型的数据具有一定的行为能力,
也就是类中说描述的方法。通常来说,
一个类的定义包含两部分的内容,
一是该类的属性,
另一部分是它所拥有的方法。以 "人类"
这个类来说,
每个人都有自己的姓名、年龄、出生日期、体重等,
为人类的属性部分,
此外,
人能够吃饭、睡觉、行走、说话等属于人类所具有的行为。

上面举例中所描述的 "人"
类仅仅是具有人这种对象的最基础的一些属性和行为,
可以称之为人的"基类"。再说说一些具有一些职业的人,
例如学生,
一个学生还具有"基类"中所没有的属性,
如学校、班级、学号;
也可以具有基类所不具有的行为,
如每天需要去上课, 需要考试等。

学生类可以看做是基类的一个扩展,
因为他具有基类的所有属性和行为,
并且在此基础上增加了一些基类所没有的属性和行为,
像"学生"这样的类称为"人类"这个基类的"派生类"或者"子类"。在学生的基础上海可以进一步的扩展出其他更高级的类,
如"研究生"类。

到此,
我们不再更深的去介绍类的其他相关知识。

二、C++类的定义

C++中使用关键字 class 来定义类,
其基本形式如下:

class 类名
{
public:
//公共的行为或属性

private:
//公共的行为或属性
};

说明:

①.
类名需要遵循一般的命名规则;

②. publicprivate 为属性/方法限制的关键字,
private 表示该部分内容是私密的,
不能被外部所访问或调用,
只能被本类内部访问;
而 public 表示公开的属性和方法,
外界可以直接访问或者调用。

一般来说类的属性成员都应设置为private, public只留给那些被外界用来调用的函数接口,
但这并非是强制规定,
可以根据需要进行调整;

③.
结束部分的分号不能省略。

类定义示例:

定义一个点(Point)类,
具有以下属性和方法:

■ 属性: x坐标, y坐标

■ 方法: 1.设置x,y的坐标值;
2.输出坐标的信息。

实现代码如下:

class Point
{
public:
voidsetPoint(int x,
int y);
voidprintPoint();

private:
int xPos;
int yPos;
};

代码说明:

上段代码中定义了一个名为 Point
的类,
具有两个私密属性, int型的xPos和yPos,
分别用来表示x点和y点。在方法上, setPoint 用来设置属性,
也就是 xPos
和 yPos 的值; printPoint 用来输出点的信息。

类在定义时有以下几点需要注意:

①. 类的数据成员中不能使用 auto、extern和register等进行修饰,
也不能在定义时进行初始化,
int xPos = 0; //错;

②. 类定义时 private
和 public
关键词出现的顺序和次数可以是任意的;

③. 结束时的分号不能省略,
切记!

三、C++类的实现

在上面的定义示例中我们只是定义了这个类的一些属性和方法声明,
并没有去实现它,
类的实现就是完成其方法的过程。类的实现有两种方式,
一种是在类定义时完成对成员函数的定义,
另一种是在类定义的外部进行完成。

1>. 在类定义时定义成员函数

成员函数的实现可以在类定义时同时完成,
如代码:

1 #include <iostream>
2
3
using namespace std;
4
5
class Point
6 {
7
public:
8
void setPoint(int x,
int y) //实现setPoint函数
9 {
10 xPos = x;
11 yPos = y;
12 }
13
14
voidprintPoint() //实现printPoint函数
15 {
16 cout<<
"x = " << xPos << endl;
17 cout<<
"y = " << yPos << endl;
18 }
19
20 private:
21
int xPos;
22
int yPos;
23 };
24
25 int main()
26 {
27 Point M;
//用定义好的类创建一个对象点M
28 M.setPoint(10,
20); //设置 M点 的x,y值
29 M.printPoint();
//输出 M点 的信息
30
31 return
0;
32 }

运行输出:
x = 10
y = 20

Process returned 0 (0x0) execution time :
0.406 s
Press any key to continue.

与类的定义相比,
在类内实现成员函数不再是在类内进行声明,
而是直接将函数进行定义,
在类中定义成员函数时,
编译器默认会争取将其定义为 inline 型函数。

2>. 在类外定义成员函数

在类外定义成员函数通过在类内进行声明,
然后在类外通过作用域操作符 :: 进行实现,
形式如下:
返回类型 类名::成员函数名(参数列表)
{
//函数体
}

将示例中的代码改用类外定义成员函数的代码:

1 #include <iostream>
2
3
using namespace std;
4
5
class Point
6 {
7
public:
8
void setPoint(int x,
int y); //在类内对成员函数进行声明
9
void printPoint();
10
11 private:
12 int xPos;
13
int yPos;
14 };
15
16 void Point::setPoint(int x,
int y) //通过作用域操作符 '::'
实现setPoint函数
17 {
18 xPos = x;
19 yPos = y;
20 }
21
22 void Point::printPoint()
//实现printPoint函数
23 {
24 cout<<
"x = " << xPos << endl;
25 cout<<
"y = " << yPos << endl;
26 }
27
28 int main()
29 {
30 Point M;
//用定义好的类创建一个对象点M
31 M.setPoint(10,
20); //设置 M点 的x,y值
32 M.printPoint();
//输出 M点 的信息
33
34 return
0;
35 }

依 setPoint
成员函数来说,
在类内声明的形式为 void setPoint(int x, int y); 那么在类外对其定义时函数头就应该是 voidPoint::setPoint(int
x, int y)
这种形式,
其返回类型、成员函数名、参数列表都要与类内声明的形式一致。

四、C++类的使用

将一个类定义并实现后,
就可以用该类来创建对象了,
创建的过程如同 int、char
等基本数据类型声明一个变量一样简单,
例如我们有一个Point类,
要创建一个Point的对象只需要:
Point 对象名;

创建一个类的对象称为该类的实例化,
在创建时我们还可以对对象的属性进行相关的初始化,
这样在创建完成后该对象就已经具有了一定得属性,
这种创建方式将在下一篇博文中进行学习。

将类进行实例化后系统才会根据该对象的实际需要分配一定的存储空间。这样就可以使用该对象来访问或调用该对象所能提供的属性或方法了。

还以上面的代码为例,
为了减少篇幅,
我们把 Point 类的实现放在 Point.h
头文件中,
这里不再贴出 Point
类的实现代码。

1 #include <iostream>
2 #include
"Point.h"
3
4 using
namespace std;
5
6 intmain()
7 {
8 Point M;
//用定义好的类创建一个对象 点M
9 M.setPoint(10,
20); //设置 M点 的x,y值
10 M.printPoint();
//输出 M点 的信息
11 cout<< M.xPos <<endl; //尝试通过对象M访问属性xPos
12
13 return
0;
14 }

代码在编译时会出现错误,
提示 error: 'int Point::xPos' isprivate,
这是 cout<< M.xPos <<endl; 这行造成的,
他试图访问一个 private
对象中的私密数据 xPos,如果将这行去掉便可正常运行。

通过 对象名.公有函数名(参数列表); 的形式就可以调用该类对象所具有的方法,
通过 对象名.公有数据成员; 的形式可以访问对象中的数据成员。

五、对象的作用域、可见域与生存周期

类对象的作用域、可见域以及生存周期与普通变量的保持相同,
当对象生存周期结束时对象被自动撤销,
所占用的内存被回收,
需要注意的是, 如果对象的成员函数中有使用 new 或者 malloc 申请的动态内存程序不会对其进行释放,
需要我们手动进行清理,
否则会造成内存泄露。

2,http://ticktick.blog.51cto.com/823160/194307/

c++构造函数的知识在各种c++教材上已有介绍,不过初学者往往不太注意观察和总结其中各种构造函数的特点和用法,故在此我根据自己的c++编程经验总结了一下c++中各种构造函数的特点,并附上例子,希望对初学者有所帮助。

c++类的构造函数详解

一、 构造函数是干什么的

class Counter

{

public:

// 类Counter的构造函数

// 特点:以类名作为函数名,无返回类型

Counter()

{

m_value = 0;

}

private:

// 数据成员

int m_value;

}

该类对象被创建时,编译系统对象分配内存空间,并自动调用该构造函数->由构造函数完成成员的初始化工作

eg: Counter c1;

编译系统为对象c1的每个数据成员(m_value)分配内存空间,并调用构造函数Counter( )自动地初始化对象c1的m_value值设置为0

故:

构造函数的作用:初始化对象的数据成员。

二、 构造函数的种类

class Complex

{

private :

double m_real;

double m_imag;

public:

// 无参数构造函数

// 如果创建一个类你没有写任何构造函数,则系统会自动生成默认的无参构造函数,函数为空,什么都不做

// 只要你写了一个下面的某一种构造函数,系统就不会再自动生成这样一个默认的构造函数,如果希望有一个这样的无参构造函数,则需要自己显示地写出来

Complex(void)

{

m_real = 0.0;

m_imag = 0.0;

}

// 一般构造函数(也称重载构造函数)

// 一般构造函数可以有各种参数形式,一个类可以有多个一般构造函数,前提是参数的个数或者类型不同(基于c++的重载函数原理)

// 例如:你还可以写一个 Complex( int num)的构造函数出来

// 创建对象时根据传入的参数不同调用不同的构造函数

Complex(double real, double imag)

{

m_real = real;

m_imag = imag;

}

// 复制构造函数(也称为拷贝构造函数)

// 复制构造函数参数为类对象本身的引用,用于根据一个已存在的对象复制出一个新的该类的对象,一般在函数中会将已存在对象的数据成员的值复制一份到新创建的对象中

// 若没有显示的写复制构造函数,则系统会默认创建一个复制构造函数,但当类中有指针成员时,由系统默认创建该复制构造函数会存在风险,具体原因请查询 有关 “浅拷贝” 、“深拷贝”的文章论述

Complex(const Complex & c)

{

// 将对象c中的数据成员值复制过来

m_real = c.m_real;

m_img = c.m_img;

}

// 类型转换构造函数,根据一个指定的类型的对象创建一个本类的对象

// 例如:下面将根据一个double类型的对象创建了一个Complex对象

Complex::Complex(double r)

{

m_real = r;

m_imag = 0.0;

}

// 等号运算符重载

// 注意,这个类似复制构造函数,将=右边的本类对象的值复制给等号左边的对象,它不属于构造函数,等号左右两边的对象必须已经被创建

// 若没有显示的写=运算符重载,则系统也会创建一个默认的=运算符重载,只做一些基本的拷贝工作

Complex &operator=( const Complex &rhs )

{

// 首先检测等号右边的是否就是左边的对象本,若是本对象本身,则直接返回

if ( this == &rhs )

{

return *this;

}

// 复制等号右边的成员到左边的对象中

this->m_real = rhs.m_real;

this->m_imag = rhs.m_imag;

// 把等号左边的对象再次传出

// 目的是为了支持连等 eg: a=b=c 系统首先运行 b=c

// 然后运行 a= ( b=c的返回值,这里应该是复制c值后的b对象)

return *this;

}

};

下面使用上面定义的类对象来说明各个构造函数的用法:

void main()

{

// 调用了无参构造函数,数据成员初值被赋为0.0

Complex c1,c2;

// 调用一般构造函数,数据成员初值被赋为指定值

Complex c3(1.0,2.5);

// 也可以使用下面的形式

Complex c3 = Complex(1.0,2.5);

// 把c3的数据成员的值赋值给c1

// 由于c1已经事先被创建,故此处不会调用任何构造函数

// 只会调用 = 号运算符重载函数

c1 = c3;

// 调用类型转换构造函数

// 系统首先调用类型转换构造函数,将5.2创建为一个本类的临时对象,然后调用等号运算符重载,将该临时对象赋值给c1

c2 = 5.2;

// 调用拷贝构造函数( 有下面两种调用方式)

Complex c5(c2);

Complex c4 = c2; // 注意和 = 运算符重载区分,这里等号左边的对象不是事先已经创建,故需要调用拷贝构造函数,参数为c2

}

三、思考与测验

1. 仔细观察复制构造函数

Complex(const Complex & c)

{

// 将对象c中的数据成员值复制过来

m_real = c.m_real;

m_img = c.m_img;

}

为什么函数中可以直接访问对象c的私有成员?

2. 挑战题,了解引用与传值的区别

Complex test1(const Complex& c)

{

return c;

}

Complex test2(const Complex c)

{

return c;

}

Complex test3()

{

static Complex c(1.0,5.0);

return c;

}

Complex& test4()

{

static Complex c(1.0,5.0);

return c;

}

void main()

{

Complex a,b;

// 下面函数执行过程中各会调用几次构造函数,调用的是什么构造函数?

test1(a);

test2(a);

b = test3();

b = test4();

test2(1.2);

// 下面这条语句会出错吗?

test1(1.2); //test1( Complex(1.2 )) 呢?

}

四、附录(浅拷贝与深拷贝)

上面提到,如果没有自定义复制构造函数,则系统会创建默认的复制构造函数,但系统创建的默认复制构造函数只会执行“浅拷贝”,即将被拷贝对象的数据成员的值一一赋值给新创建的对象,若该类的数据成员中有指针成员,则会使得新的对象的指针所指向的地址与被拷贝对象的指针所指向的地址相同,delete该指针时则会导致两次重复delete而出错。下面是示例:

【浅拷贝与深拷贝】

#include <iostream.h>

#include <string.h>

class Person

{

public :

// 构造函数

Person(char * pN)

{

cout << "一般构造函数被调用 !\n";

m_pName = new char[strlen(pN) + 1];

//在堆中开辟一个内存块存放pN所指的字符串

if(m_pName != NULL)

{

//如果m_pName不是空指针,则把形参指针pN所指的字符串复制给它

strcpy(m_pName ,pN);

}

}

// 系统创建的默认复制构造函数,只做位模式拷贝

Person(Person & p)

{

//使两个字符串指针指向同一地址位置

m_pName = p.m_pName;

}

~Person( )

{

delete m_pName;

}

private :

char * m_pName;

};

void main( )

{

Person man("lujun");

Person woman(man);

// 结果导致 man 和 woman 的指针都指向了同一个地址

// 函数结束析构时

// 同一个地址被delete两次

}

// 下面自己设计复制构造函数,实现“深拷贝”,即不让指针指向同一地址,而是重新申请一块内存给新的对象的指针数据成员

Person(Person & chs);

{

// 用运算符new为新对象的指针数据成员分配空间

m_pName=new char[strlen(p.m_pName)+ 1];

if(m_pName)

{

// 复制内容

strcpy(m_pName ,chs.m_pName);

}

// 则新创建的对象的m_pName与原对象chs的m_pName不再指向同一地址了

}

3,http://see.xidian.edu.cn/cpp/biancheng/view/196.html

析构函数(destructor)也是一个特殊的成员函数,它的作用与构造函数相反,它的名字是类名的前面加一个“~”符号。

在C++中“~”是位取反运算符,从这点也可以想到,析构函数是与构造函数作用相反的函数。当对象的生命期结束时,会自动执行析构函数。

具体地说如果出现以下几种情况,程序就会执行析构函数:

如果在一个函数中定义了一个对象(它是自动局部对象),当这个函数被调用结束时,对象应该释放,在对象释放前自动执行析构函数。
static局部对象在函数调用结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用static局部对象的析构函数。
如果定义了一个全局对象,则在程序的流程离开其作用域时(如main函数结束或调用exit函数) 时,调用该全局对象的析构函数。
如果用new运算符动态地建立了一个对象,当用delete运算符释放该对象时,先调用该对象的析构函数。

析构函数的作用并不是删除对象,而是在撤销对象占用的内存之前完成一些清理工作,使这部分内存可以被程序分配给新对象使用。程序设计者事先设计好析构函数,以完成所需的功能,只要对象的生命期结束,程序就自动执行析构函数来完成这些工作。

注意:析构函数不返回任何值,没有函数类型,也没有函数参数。因此它不能被重载。一个类可以有多个构造函数,但只能有一个析构函数。

实际上,析构函数的作用并不仅限于释放资源方面,它还可以被用来执行“用户希望在最后一次使用对象之后所执行的任何操作”,例如输出有关的信息。这里说的用户是指类的设计者,因为,析构函数是在声明类的时候定义的。也就是说,析构函数可以完成类的设计者所指定的任何操作。

一般情况下,类的设计者应当在声明类的同时定义析构函数,以指定如何完成“清理”的工作。如果用户没有定义析构函数,C++编译系统会自动生成一个析构函数,但它只是徒有析构函数的名称和形式,实际上什么操作都不进行。想让析构函数完成任何工作,都必须在定义的析构函数中指定。

【例9.5】包含构造函数和析构函数的C++程序。

#include<string>
#include<iostream>
using namespace std;
class Student //声明Student类
{
public :
Student(int n,string nam,char s ) //定义构造函数
{
num=n;
name=nam;
sex=s;
cout<<"Constructor called."<<endl; //输出有关信息
}
~Student( ) //定义析构函数
{
cout<<"Destructor called. The num is "<<num<<"."<<endl;
} //输出有关信息
void display( ) //定义成员函数
{
cout<<"num: "<<num<<endl;
cout<<"name: "<<name<<endl;
cout<<"sex: "<<sex<<endl<<endl;
}
private :
int num;
string name;
char sex;
};

int main( )
{
Student stud1(10010,"Wang_li",'f'); //建立对象stud1
stud1.display( ); //输出学生1的数据
Student stud2(10011,"Zhang_fun",'m'); //定义对象stud2
stud2.display( ); //输出学生2的数据
return 0;
}


程序运行结果如下:

Constructor called. (执行stud1的构造函数)

num: 10010 (执行stud1的display函数)

name:Wang_li

sex: f

Constructor called. (执行stud2的构造函数)

num: 10011 (执行stud2的display函数)

name:Zhang_fun

sex:m

Destructor called. The num is 10011. (执行stud2的析构函数)

Destructor called. The num is 10010. (执行stud1的析构函数)

4,http://see.xidian.edu.cn/cpp/biancheng/view/91.html

了解继承的概念之后,我们就来学习一下如何实现继承。


私有和保护

在第14章中我们说到,成员函数或成员数据可以是公有或者私有的。如果是公有的,那么它们可以被直接访问;如果是私有的,那么它们无法被直接访问。同时,我们还提到一个protected保留字,在没有使用继承的时候,它与private的效果是一样的,即无法被直接访问。如果使用了继承,我们就能体会到protected和private的差别。

private(私有)和protected(保护)都能实现类的封装性。private能够对外部和子类保密,即除了成员所在的类本身可以访问之外,别的都不能直接访问。protected能够对外部保密,但允许子类直接访问这些成员。public、private和protected对成员数据或成员函数的保护程度可以用下表来描述:



所以,当我们使用到继承的时候,必须考虑清楚:成员数据或成员函数到底应该是私有的还是保护的。


一个简单的例子

首先我们以一个学生类为例,介绍继承的写法:(程序17.3.1)

//student.h

#include <iostream>

using namespace std;

class student//学生类作为父类

{

public:

student(char *n,int a,int h,int w);//带参数的构造函数

student();//不带参数的构造函数

void set(char *n,int a,int h,int w);//设置

char * sname();

int sage();

int sheight();

int sweight();

protected:

char name[10];//姓名

int age;//年龄

int height;//身高

int weight;//体重

private:

int test;

};

char * student::sname()

{

return name;

}

int student::sage()

{

return age;

}

int student::sheight()

{

return height;

}

int student::sweight()

{

return weight;

}

void student::set(char *n,int a,int h,int w)

{

int i;

for (i=0;n[i]!='\0';i++)

{

name[i]=n[i];

}

name[i]='\0';

age=a;

height=h;

weight=w;

return;

}

student::student(char *n,int a,int h,int w)

{

cout <<"Constructing a student with parameter..." <<endl;

set(n,a,h,w);

}

student::student()

{

cout <<"Constructing a student without parameter..." <<endl;

}

//undergraduate.h

#include "student.h"

class Undergraduate:public student//本科生类作为子类,继承了学生类

{

public:

double score();

void setGPA(double g);//设置绩点

bool isAdult();//判断是否成年

protected:

double GPA;//本科生绩点

};

double Undergraduate::score()

{

return GPA;

}

void Undergraduate::setGPA(double g)

{

GPA=g;

return;

}

bool Undergraduate::isAdult()

{

return age>=18?true:false;//子类访问父类的保护成员数据

}

//main.cpp

#include <iostream>

#include "undergraduate.h"

using namespace std;

int main()

{

Undergraduate s1;//新建一个本科生对象

s1.set("Tom",21,178,60);

s1.setGPA(3.75);

cout <<s1.sname() <<endl;

cout <<s1.sage() <<endl;

cout <<s1.sheight() <<endl;

cout <<s1.sweight() <<endl;

cout <<s1.score() <<endl;

cout <<s1.isAdult() <<endl;

return 0;

}

运行结果:

Constructing a student without parameter...

Tom

21

178

60

3.75

1

在使用继承之前,我们必须保证父类是已经定义好的。如果父类是虚无的、没有被定义的,那么子类也就没什么好继承的了。定义一个子类的语法格式为:

class
子类名:继承方式父类名;

根据程序17.3.1的运行结果,我们可以清楚地看到,学生类里面的公有和保护成员都已经被继承到本科生类。本科生类可以使用学生类的成员函数,也可以访问学生类的保护成员。而本科生类中定义的成员则是对学生类的补充,并且也能够被使用。


继承的方式

在程序17.3.1中,我们选择的继承方式是public。和成员的类型一样,除了public之外,继承方式还有protected和private。那么,这三种继承方式到底有什么区别呢?

public是公有继承,或称为类型继承。它主要体现的是概念的延伸和扩展,父类所有的公有、保护成员都将按部就班地继承到子类中。父类的公有成员在子类中依然是公有的,父类的保护成员在子类中依然是保护的。比如程序17.3.1中的学生类和本科生类就是这样的关系。

private是私有继承,或称为私有的实现继承。它主要体现的是父类成员的重用。父类所有的公有、保护成员继承到子类时,类型会发生改变。父类的公有成员在子类中变成了私有成员,父类的保护成员在子类中也变成了私有成员。这时,我们可以利用从父类继承而来的成员函数来实现子类的成员函数,并且不必担心外部直接访问父类的成员函数,破坏了子类的秩序。比如我们认为栈是一种特殊的链表,它只能从链表尾部添加或删除结点,栈的压栈和退栈功能可以方便地由链表类的成员函数实现。但是,如果外部还能直接访问从链表类继承而来的成员函数,那么就可以在栈的任何位置插入结点,栈就会被破坏。

protected是保护继承,或称为保护的实现继承。与私有继承类似,它也是体现父类成员的重用。只不过父类的公有成员和保护成员在子类中都变成了保护成员。因此,如果有一个孙类继承了子类,那么父类中的成员也将被继承,成为孙类的保护成员。

public、private和protected三种继承方式可以用下表描述。其中右下角的九个单元格表示各种父类成员在对应的继承方式下,成为子类成员后的性质。



在使用继承的时候,我们必须根据实际需要选择合适的继承方式。下面我们以栈继承链表为例,理解一下私有继承方式:(程序17.3.2)

//node.h

#include <iostream>

using namespace std;

class Node

{

friend class Linklist;//链表类作为友元类

friend class Stack;//栈类作为友元类

public:

Node();

Node(Node &n);

Node(int i,char c='0');

Node(int i,char c,Node *p,Node *n);

~Node();

private:

int idata;

char cdata;

Node *prior;

Node *next;

};

Node::Node()

{

cout <<"Node constructor is running..." <<endl;

idata=0;

cdata='0';

prior=NULL;

next=NULL;

}

Node::Node(int i,char c)

{

cout <<"Node constructor is running..." <<endl;

idata=i;

cdata=c;

prior=NULL;

next=NULL;

}

Node::Node(int i,char c,Node *p,Node *n)

{

cout <<"Node constructor is running..." <<endl;

idata=i;

cdata=c;

prior=p;

next=n;

}

Node::Node(Node &n)

{

idata=n.idata;

cdata=n.cdata;

prior=n.prior;

next=n.next;

}

Node::~Node()

{

cout <<"Node destructor is running..." <<endl;

}

//linklist.h

#include "node.h"

#include <iostream>

using namespace std;

class Linklist

{

public:

Linklist(int i=0,char c='0');

Linklist(Linklist &l);

~Linklist();

bool Locate(int i);

bool Locate(char c);

bool Insert(int i=0,char c='0');

bool Delete();

void Show();

void Destroy();

protected://原私有成员改为保护成员,以便于Stack类继承

Node head;

Node * pcurrent;

};

Linklist::Linklist(int i,char c):head(i,c)

{

cout<<"Linklist constructor is running..."<<endl;

pcurrent=&head;

}

Linklist::Linklist(Linklist &l):head(l.head)

{

cout<<"Linklist Deep cloner running..." <<endl;

pcurrent=&head;

Node * ptemp1=l.head.next;

while(ptemp1!=NULL)

{

Node * ptemp2=new Node(ptemp1->idata,ptemp1->cdata,pcurrent,NULL);

pcurrent->next=ptemp2;

pcurrent=pcurrent->next;

ptemp1=ptemp1->next;

}

}

Linklist::~Linklist()

{

cout<<"Linklist destructor is running..."<<endl;

Destroy();

}

bool Linklist::Locate(int i)

{

Node * ptemp=&head;

while(ptemp!=NULL)

{

if(ptemp->idata==i)

{

pcurrent=ptemp;

return true;

}

ptemp=ptemp->next;

}

return false;

}

bool Linklist::Locate(char c)

{

Node * ptemp=&head;

while(ptemp!=NULL)

{

if(ptemp->cdata==c)

{

pcurrent=ptemp;

return true;

}

ptemp=ptemp->next;

}

return false;

}

bool Linklist::Insert(int i,char c)

{

if(pcurrent!=NULL)

{

Node * temp=new Node(i,c,pcurrent,pcurrent->next);

if (pcurrent->next!=NULL)

{

pcurrent->next->prior=temp;

}

pcurrent->next=temp;

return true;

}

else

{

return false;

}

}

bool Linklist::Delete()

{

if(pcurrent!=NULL && pcurrent!=&head)

{

Node * temp=pcurrent;

if (temp->next!=NULL)

{

temp->next->prior=pcurrent->prior;

}

temp->prior->next=pcurrent->next;

pcurrent=temp->prior;

delete temp;

return true;

}

else

{

return false;

}

}

void Linklist::Show()

{

Node * ptemp=&head;

while (ptemp!=NULL)

{

cout <<ptemp->idata <<'\t' <<ptemp->cdata <<endl;

ptemp=ptemp->next;

}

}

void Linklist::Destroy()

{

Node * ptemp1=head.next;

while (ptemp1!=NULL)

{

Node * ptemp2=ptemp1->next;

delete ptemp1;

ptemp1=ptemp2;

}

head.next=NULL;

}

//stack.h

#include "linklist.h"

class Stack:private Linklist//私有继承链表类

{

public:

bool push(int i,char c);

bool pop(int &i,char &c);

void show();

};

bool Stack::push(int i,char c)

{

while (pcurrent->next!=NULL)

pcurrent=pcurrent->next;

return Insert(i,c);//用链表类的成员函数实现功能

}

bool Stack::pop(int &i,char &c)

{

while (pcurrent->next!=NULL)

pcurrent=pcurrent->next;

i=pcurrent->idata;

c=pcurrent->cdata;

return Delete();//用链表类的成员函数实现功能

}

void Stack::show()

{

Show();//用链表类的成员函数实现功能

}

//main.cpp

#include <iostream>

#include "stack.h"

int main()

{

Stack ss;

int i,j;

char c;

for (j=0;j<3;j++)

{

cout <<"请输入一个数字和一个字母:" <<endl;

cin >>i >>c;

if (ss.push(i,c))

{

cout <<"压栈成功!" <<endl;

}

}

ss.show();

while (ss.pop(i,c))

{

cout <<"退栈数据为i=" <<i <<" c=" <<c <<endl;

}

return 0;

}

运行结果:

Node constructor is running...

Linklist constructor is running...

请输入一个数字和一个字母:

1 a

Node constructor is running...

压栈成功!

请输入一个数字和一个字母:

2 b

Node constructor is running...

压栈成功!

请输入一个数字和一个字母:

3 c

Node constructor is running...

压栈成功!

0 0

1 a

2 b

3 c

Node destructor is running...

退栈数据为i=3 c=c

Node destructor is running...

退栈数据为i=2 c=b

Node destructor is running...

退栈数据为i=1 c=a

Linklist destructor is running...

Node destructor is running...

我们看到,Stack类私有继承了Linklist类之后,利用Linklist的成员函数,方便地实现了压栈和退栈功能。

5,/article/1422734.html

C++编程语言是一款应用广泛,支持多种程序设计的计算机编程语言。我们今天就会为大家详细介绍其中C++多态性的一些基本知识,以方便大家在学习过程中对此能够有一个充分的掌握。

  多态性可以简单地概括为“一个接口,多种方法”,程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念。多态(polymorphisn),字面意思多种形状。

  C++多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(override),或者称为重写。(这里我觉得要补充,重写的话可以有两种,直接重写成员函数和重写虚函数,只有重写了虚函数的才能算作是体现了C++多态性)而重载则是允许有多个同名的函数,而这些函数的参数列表不同,允许参数个数不同,参数类型不同,或者两者都不同。编译器会根据这些函数的不同列表,将同名的函数的名称做修饰,从而生成一些不同名称的预处理函数,来实现同名函数调用时的重载问题。但这并没有体现多态性。

  多态与非多态的实质区别就是函数地址是早绑定还是晚绑定。如果函数的调用,在编译器编译期间就可以确定函数的调用地址,并生产代码,是静态的,就是说地址是早绑定的。而如果函数调用的地址不能在编译器期间确定,需要在运行时才确定,这就属于晚绑定。

  那么多态的作用是什么呢,封装可以使得代码模块化,继承可以扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了接口重用。也就是说,不论传递过来的究竟是那个类的对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。

  最常见的用法就是声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法。如果没有使用虚函数的话,即没有利用C++多态性,则利用基类指针调用相应的函数的时候,将总被限制在基类函数本身,而无法调用到子类中被重写过的函数。因为没有多态性,函数调用的地址将是一定的,而固定的地址将始终调用到同一个函数,这就无法实现一个接口,多种方法的目的了。

笔试题目:

[cpp] view
plaincopy

#include<iostream>

using namespace std;

class A

{

public:

void foo()

{

printf("1\n");

}

virtual void fun()

{

printf("2\n");

}

};

class B : public A

{

public:

void foo()

{

printf("3\n");

}

void fun()

{

printf("4\n");

}

};

int main(void)

{

A a;

B b;

A *p = &a;

p->foo();

p->fun();

p = &b;

p->foo();

p->fun();

return 0;

}

第一个p->foo()和p->fuu()都很好理解,本身是基类指针,指向的又是基类对象,调用的都是基类本身的函数,因此输出结果就是1、2。

  第二个输出结果就是1、4。p->foo()和p->fuu()则是基类指针指向子类对象,正式体现多态的用法,p->foo()由于指针是个基类指针,指向是一个固定偏移量的函数,因此此时指向的就只能是基类的foo()函数的代码了,因此输出的结果还是1。而p->fun()指针是基类指针,指向的fun是一个虚函数,由于每个虚函数都有一个虚函数列表,此时p调用fun()并不是直接调用函数,而是通过虚函数列表找到相应的函数的地址,因此根据指向的对象不同,函数地址也将不同,这里将找到对应的子类的fun()函数的地址,因此输出的结果也会是子类的结果4。

  笔试的题目中还有一个另类测试方法。即

B *ptr = (B *)&a; ptr->foo(); ptr->fun();

  问这两调用的输出结果。这是一个用子类的指针去指向一个强制转换为子类地址的基类对象。结果,这两句调用的输出结果是3,2。

  并不是很理解这种用法,从原理上来解释,由于B是子类指针,虽然被赋予了基类对象地址,但是ptr->foo()在调用的时候,由于地址偏移量固定,偏移量是子类对象的偏移量,于是即使在指向了一个基类对象的情况下,还是调用到了子类的函数,虽然可能从始到终都没有子类对象的实例化出现。

  而ptr->fun()的调用,可能还是因为C++多态性的原因,由于指向的是一个基类对象,通过虚函数列表的引用,找到了基类中fun()函数的地址,因此调用了基类的函数。由此可见多态性的强大,可以适应各种变化,不论指针是基类的还是子类的,都能找到正确的实现方法。

[cpp] view
plaincopy

//小结:1、有virtual才可能发生多态现象

// 2、不发生多态(无virtual)调用就按原类型调用

#include<iostream>

using namespace std;

class Base

{

public:

virtual void f(float x)

{

cout<<"Base::f(float)"<< x <<endl;

}

void g(float x)

{

cout<<"Base::g(float)"<< x <<endl;

}

void h(float x)

{

cout<<"Base::h(float)"<< x <<endl;

}

};

class Derived : public Base

{

public:

virtual void f(float x)

{

cout<<"Derived::f(float)"<< x <<endl; //多态、覆盖

}

void g(int x)

{

cout<<"Derived::g(int)"<< x <<endl; //隐藏

}

void h(float x)

{

cout<<"Derived::h(float)"<< x <<endl; //隐藏

}

};

int main(void)

{

Derived d;

Base *pb = &d;

Derived *pd = &d;

// Good : behavior depends solely on type of the object

pb->f(3.14f); // Derived::f(float) 3.14

pd->f(3.14f); // Derived::f(float) 3.14

// Bad : behavior depends on type of the pointer

pb->g(3.14f); // Base::g(float) 3.14

pd->g(3.14f); // Derived::g(int) 3

// Bad : behavior depends on type of the pointer

pb->h(3.14f); // Base::h(float) 3.14

pd->h(3.14f); // Derived::h(float) 3.14

return 0;

}

令人迷惑的隐藏规则

本来仅仅区别重载与覆盖并不算困难,但是C++的隐藏规则使问题复杂性陡然增加。

这里“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:

(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual

关键字,基类的函数将被隐藏(注意别与重载混淆)。

(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual

关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。

上面的程序中:

(1)函数Derived::f(float)覆盖了Base::f(float)。

(2)函数Derived::g(int)隐藏了Base::g(float),而不是重载。

(3)函数Derived::h(float)隐藏了Base::h(float),而不是覆盖。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: