c++的类的封装/继承/多态的简单介绍
本篇文章仅仅从很表层来介绍一个C++语言中的类,包括什么是类,类的封装性/继承性和多态性。高手直接跳过吧,看了浪费时间,新手或者想温习一下的可以浏览看看。
1. 什么是类?
到底什么是类(class)??类就是一种类型,是用户自己定义的一个类型,和内置类型如int/float/double类似, 用一个类可以去定义一个变量,即课本中所谓的类的实例化,会得到一个object。类这个类型比较特别,它即包括了数据(数据成员),又包含了若干个操作这些数据的方法(即成员函数)。为什么需要类呢?类提供了一种对事物的抽象,增强了事物的聚合性,类让我们可以把一个事物当作一个整体去看,方便描述,方便建模。 例如:我们可以定义一个学生的类:包括了姓名/性别/年龄/学号ID等信息
class Student
{
public:
int GetID();
string GetName();
string GetSex();
int GetAge(); void SetID(int ID_);
void SetName(string Name_);
void SetSex(string Sex_);
void SetAge(int Age_); private:
string m_Name;
string m_Sex;
int m_ID;
int m_Age
};
如上所示,我们定义了一个Student的类, 定义它之后,编译器就不光知道了Student是一个类型,而且知道了类型的一些细节,例如当使用该类型去定义一个变量(object)时,需要分配多少内存等,例如:
// 输入Student类型在内存中占多少字节
cout << "Student类型的大小为:" << sizeof(Student) << endl; //实例化一个叫小明的学生,并命名为SB(有点矛盾)
Student xiaoming;
xiaoming.SetName("SB");
cout << xiaoming.GetName() << endl;
接下来,详细说说如何定义一个类:
定义一个类,需要做的是:1. 声明类拥有的数据成员, 2. 声明类拥有的成员函数:
class Dog
{
public:
int Age;
int GetAge();
};
成员函数在哪里定义呢?即可以在类的内部直接定义,也可以在类的外部进行定义(此时,需要指明所属的类)。当定义在类的内部时,默认声明为inline函数。 当类外部定义成员函数时,可以有类内声明为inline函数,也可以在定义时候声明了inline函数,但是个人更喜欢在类外定义的时候声明为inline函数,这样可以根据自己定义一个函数的实际情况,决定是否声明为inline函数,而不需要提前考虑好。
// 类的内部定义
class Dog
{
public:
int Age;
int GetAge() //当定义在类的内部时,默认声明为inline函数
{
return Age;
}
}; // 类外部定义
class Dog
{
public:
int Age;
int GetAge();
}; Dog::GetAge()
{
return Age;
} // 类外部定义,显示声明为inline函数
class Dog
{
public:
int Age;
inline int GetAge();
};
另外:
1. 类成员函数通过隐式this指针访问类内部的数据成员的; 如果把this指针修饰为const,在成员函数后面加const ,这就是const 成员函数: GetAge() const
2. 编译器在解析一个类,总是先把类内部的数据成员解析完毕,再去处理成员函数,因此,定义一个类时,不必关心数据成员与成员函数的先后位置,数据成员圣成员函数问题可见的。
3. 一个类域也是一个作用域(用{ }括起来的部分),正因为如此, 1. 在类的外部定义成员函数时,指定类名就进入了类的作用域中了,就可以找到数据成员了; 2. 对类内的静态数据成员与静态成员函数,可以通过类名作用域访问(对非静态的不可以,因为非静态的数据成员属于具体的一个对象而不属于类,虽然非静态的成员函数实际上在多个对象之间是共享的,但是也只能通过对象名对象的指针访问,因为它们有隐式的this指针)
2. 类的封装性
封装性就是说可以把一部分东西封装起来,不让别人看到。在C++中,类这种类型通过它的访问控制符来体现了它的封装性,包括:
public: 公有的,在类的外部放着,谁都可以看到;
protected: 保护性的,只让它的子类可以看到;
private: 私有的,即把它们封装在了类的内部,在类的外面是看不到的,只有类内部的人可以看到;
例如:定义了一个House的类,House外面的只能看到pulic下的内容,而在House里面,可以看到所有内容;
class House
{
public:
int Windows;
void OpenWindow();
int doors;
void OpenDoors(); protected:
int *** // 这个不好举例子 private:
int Desk;
int light;
void OpenLight();
}
使类具有封装性的目的是:我们可以定义一些类,只对类的使用者留一下公有的接口(即pulic下的内容),而类内部的相关操作对类的使用者来说是透明的,用户不操心。我可以随便改类内部的代码,只有公有的接口不变,类的用户的代码是不需要调整的。保证数据安全,方便用户使用,大家都省心啊。
3. 类的继承
如果我们想在一个类的基础上继续创建一个新类,这就用到了类的继承性。继承可以使用三个访问标志符控制:public、protectd和private。 无论哪个继承,对直接子类没有任何影响,只对子类的用户有影响。基类中的private成员无论使用哪个访问标志符,子类的用户是看不到的,基类的public与protected成员是否能让子类的用户看到由三种访问标志符控制 。
public继承: 基类内的数据成员与成员函数封装特性不变在子类中不变
protected继承: 基类内的数据成员与成员函数的public部分在子类中变为protected
private继承: 基类内的数据成员与成员函数的public和protected部分变为private
例如:
class Base
{
public:
*****
protected:
*****
}; // 公有继承
class Derived1 : public Base
{
public:
.....
private:
....
}; // 私有继承
class Derived2 : private Base
{
public:
;;;;;;
};
a. 基类中虚函数和纯虚函数
思考这么一个问题: 当基类定义了某个函数,而在子类中又定义了一个同名的函数(返回类型与参数列表可以不同),这时会发生什么?
答: 子类内的该函数会隐藏基类中同名的函数。即使他们的参数列表与返回类型不同,也不会发生重载,因为重载必须在相同的作用域内发生,如果作用域不同,编译器查找时,总会先找到最近作用域内的同名函数。
很多时候,我们想在基类与子类中定义相同的接口(即同名函数),它们实现各自的功能,这就可以把这样的函数声明为虚函数,即virtual. 当通过类的指针与引用调用虚函数时,会发生动态绑定,即多态。如下面例子所示:
// 程序
#include<iostream>
using namespace std; class Base
{
public:
virtual void Say() {cout << "I am Base!" << endl;}
}; class Derived : public Base
{
public:
virtual void Say() {cout << "I am Derived!" << endl;}
}; int main()
{
Base* pA = new Base;
Derived* pB = new Derived;
pA->Say();
pB->Say();
} // 输出
yin@debian-yinheyi:~/c$ ./a.out
I am Base!
I am Derived!
当我们不想实例化一个类时,我们可以定义一个抽象基类,它只负责提供接口。包含纯虚函数的类为抽象类。纯虚函数必须在子类中进行声明与定义。而虚函数可以不在子类中声明与定义,这时候它会像普通成员函数一样继承基类中的虚函数的实现。
class Base
{
public:
virtual void Say() = ; //纯虚函数
};
总结来说:
1. 当我们要继承一个类的接口与实现时,我们在基类中定义普普通通的成员即可。
2. 当我们想要继承一个类的接口与默认的实现时,我们在基类中定义为虚函数。
3. 当我们只要继承一个类的接口时,我们在基类中定义为纯虚函数。
其它说明:
1. 在子类中,当我们重新声明和定义虚函数时,可以加上virtual关键字(virtual只能用在类内,不可以把virtual 用在类外),也可以不加, 在c++11标准中,引入了override关键字来显示表示覆盖基类中虚函数的定义,override关键字有利于给编译器更多信息,用于查错。例如当我们在子类中定义了一个与基类中虚函数名字相同,但是参数列表不同的函数,我们本意是定义子类特有的虚函数版本,来覆盖基类中的版本。然而这时候,基类与子类中的函数是独立的,只是基类中的版本隐藏了而已。如果使用了override,编译器发现没有覆盖,就会报错。 如果我们不想让基类中的某个虚函数被覆盖掉,可以使用final关键字。(另外覆盖,即override只会发生在虚函数身上)
2. 如果我们定义 了一个类,并且不想该类被继承,可以在定义这个类时,在类名后面加上final关键字。
class Base
{
public:
virtual void Say() {cout << "I am Base!" << endl;}
virtual void Dad() final { cout << " I am your dad!" << endl;} //对该虚函数使用final关键字
}; class Derived final : public Base // 对类Derived 使用了final 关键字
{
public:
void Say() override { cout << " I am Derived!" << endl;} //使用了override关键字
};
3. 虽然一个纯虚函数不需要定义,但是其实我们是可以定义一个纯虚函数的,不过调用它的唯一途径是”调用时明确指出它的class的名称“。
// 程序
class Base
{
public:
virtual void Hello() = ;
};
void Base::Hello() {cout << "hello " << endl;} class Derived final : public Base
{
public:
void Hello() override {cout << "a,很疼的" << endl;}
}; int main()
{
Derived* pB = new Derived;
pB->Hello();
pB->Base::Hello();
} // 输出
yin@debian-yinheyi:~/c$ ./a.out
a,很疼的
hello
4. 基类与子类中的虚函数的返回类型和参数列表必须完全一致,如果不一致的话,编译器认为他们是完全不相关的函数。他们之间不会发生覆盖(override),子类中的同名函数只会隐藏子类中的同名函数。
b. 继承中的作用域
关于类的作用域,我们要明白以下几点:
1. 类本身是一个作用域,使用{ }括起来的。
2. 在类的继承过程中,子类的作用域是嵌套在基类的作用域之内的(这就明白了为什么有时候子类中的成员函数会隐藏掉基类中函数,就时候如果我们想要使用被隐藏的基类函数,可以通过显示指明类名(这时可以理解为作用域名)来访问。
3. 在一个类的作用域中,编译器在解析类时,它总会先解析类中声明的所以数据成员与成员函数,再去解析成员函数的定义。正因为这样的原因,无论数据成员定义在成员函数的后面还是前面,还是成员函数的顺序前后之类的, 一个成员函数总是可以找到该类的数据成员或调用其它成员函数。
基于作用域的一个例子:
// 程序
class Base
{
public:
virtual void Hello() { cout << "Hello, I am Base!" << endl; }
void Hi() { cout << "Hi, Hi, Base!" << endl;}
}; class Derived : public Base
{
public:
void Hello() override { cout << "Hello, I am Derived!" << endl;}
void Hi() { cout << "Hi, Hi, Derived!" << endl;}
}; int main()
{
Derived Derive;
Derive.Hello();
Derive.Hi();
Derive.Base::Hello(); //显示调用基类的虚函数
Derive.Base::Hi(); // 显示调用基类普通函数 Base *pBase = &Derive;
pBase->Hello(); //指针调用,进行动态绑定,即多态
pBase->Base::Hello(); //显示调用基类的虚函数
pBase->Base::Hi(); // 显示调用基类普通函数 return ;
} //程序输出:
Hello, I am Derived!
Hi, Hi, Derived!
Hello, I am Base!
Hi, Hi, Base!
Hello, I am Derived!
Hello, I am Base!
Hi, Hi, Base!
额外小知识点:
1. 待后续遇到补充!
c++的类的封装/继承/多态的简单介绍的更多相关文章
- java类的封装 继承 多态
1.猜数字小游戏 package cn.jiemoxiaodi_02; import java.util.Scanner; /** * 猜数字小游戏 * * @author huli * */ pub ...
- php面向对象 封装继承多态 接口、重载、抽象类、最终类总结
1.面向对象 封装继承多态 接口.重载.抽象类.最终类 面向对象 封装继承多态 首先,在解释面向对象之前先解释下什么是面向对象? [面向对象]1.什么是类? 具有相同属性(特征)和方法(行为)的一 ...
- java面向对象(封装-继承-多态)
框架图 理解面向对象 面向对象是相对面向过程而言 面向对象和面向过程都是一种思想 面向过程强调的是功能行为 面向对象将功能封装进对象,强调具备了功能的对象. 面向对象是基于面向过程的. 面向对象的特点 ...
- 浅谈学习C++时用到的【封装继承多态】三个概念
封装继承多态这三个概念不是C++特有的,而是所有OOP具有的特性. 由于C++语言支持这三个特性,所以学习C++时不可避免的要理解这些概念. 而在大部分C++教材中这些概念是作为铺垫,接下来就花大部分 ...
- Java三大特性(封装,继承,多态)
Java中有三大特性,分别是封装继承多态,其理念十分抽象,并且是层层深入式的. 一.封装 概念:封装,即隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别:将抽象得到的数据 ...
- Java基础——面向对象(封装——继承——多态 )
对象 对象: 是类的实例(实现世界中 真 实存在的一切事物 可以称为对象) 类: 类是对象的抽象描述 步骤: 1.定义一个类 (用于 描述人:) ( * 人:有特征和行为) 2.根据类 创建对象 -- ...
- Python 入门 之 面向对象的三大特性(封装 / 继承 / 多态)
Python 入门 之 面向对象的三大特性(封装 / 继承 / 多态) 1.面向对象的三大特性: (1)继承 继承是一种创建新类的方式,在Python中,新建的类可以继承一个或多个父类,父类又可以 ...
- OOP三大核心封装继承多态
OOP支柱 3 个核心:封装 继承 多态 封装就是将实现细节隐藏起来,也起到了数据保护的作用. 继承就是基于已有类来创建新类可以继承基类的核心功能. 在继承中 另外一种代码重用是:包含/委托,这种重用 ...
- python面向对象(封装,继承,多态)
python面向对象(封装,继承,多态) 学习完本篇,你将会深入掌握 如何封装一个优雅的借口 python是如何实现继承 python的多态 封装 含义: 1.把对象的属性和方法结合成一个独立的单位, ...
随机推荐
- 工程管理之makefile与自动创建makefile文件过程
(风雪之隅 http://www.laruence.com/2009/11/18/1154.html) Linux Makefile自动编译和链接使用的环境 想知道到Linux Makefile系统的 ...
- MPU和CPU有什么区别?
MPU(或称MP,微处理器)和CPU(中央处理器)同为处理器,但范畴不同. 计算机(即电脑)分为巨型机,大型机,中型机,小型机和微型计算机5类.这5类计算机的运算核心统称为CPU,而MPU只是微型计算 ...
- vim技巧3
yyp复制当前行到下一行ddp剪切当前行到下一行cw:删除当前单词并进入插入模式xp:交换当前字符和右边字符s:删除光标所在的字符并进入插入模式I:在行首开始输入文字并进入插入模式A:在行尾开始输入文 ...
- Java 字符编码 ASCII、Unicode、UTF-8、代码点和代码单元
1 ASCII码 统一规定英语字符与二进制位之间的关系.ASCII码一共规定了128个字符的编码.例如,空格“SPACE”是32(二进制00100000),大写字母A是65(二进制01000001). ...
- Java基础知识--泛型
什么是泛型?为什么使用泛型? 泛型,就是参数化类型.提到参数,最熟悉的就是定义方法时候的形参,然后调用此方法时传递实参.顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也 ...
- tensorflow 手写数字识别
https://www.kaggle.com/kakauandme/tensorflow-deep-nn 本人只是负责将这个kernels的代码整理了一遍,具体还是请看原链接 import numpy ...
- ajax01
ajax01 1.ajax简介 涉及AJAX的操作页面不能用文件协议访问 使用ajax发送请求: send参数缺省默认为null onreadyatatechange事件在状态改变时就会触发. .re ...
- Android的Databinding-需要使用控件id,listener以及布局有include的场景
主的布局xml文件: <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:bin ...
- 推荐一个好工具:P/Invoke Interop Assistant【转】
原文地址 :http://write.blog.csdn.net/postedit 在从托管代码里面调用非托管代码的时候,经常会翻阅MSDN找到需要调用的这个程序集里面的关于需要调用方法的签名,还要特 ...
- bootstrap-实现loading效果
可以使用bootstrap的模态框(modal.js),使用它我们可以做出loading效果. html <!-- loading --> <div class="moda ...