C++基础知识(二)

八、继承:让某个类的对象获得另一个类的对象的特性。通过继承可实现代码重用,即从已存在的类派生出的一个新类将自动具有原来那个类的特性。

类的继承还具有:(1)单向性;A类为B类的基类(父类),则派生类(子类)B继承了父类A中的属性和方法,在B类中可访问A类的属性和方法,但在父类A中则不能访问子类的任何属性和方法。同时单向又体现为子类B继承了父类A,则A类不能再继承B类。(2)传递性;A类为B类的基类,B类为C类的基类,则基类A中的属性和方法通过子类B同时又传给了子类C,同理C类也不能作为A类的基类。(3)可重用性;通过继承,基类代码得到重用,而不用重写。

C++规定派生类中对象成员(其它类的对象)初值的设定应在初始化列表中进行,所以派生类的构造函数定义(派生类头文件中构造函数的声明格式与普通类相同)可写成:函数类型 派生类名::派生类名(形参表):基类1(参数表),基类2(参数表),...,基类n(参数表),对象成员1(参数表),对象成员2(参数表),...,对象成员n(参数表) {...}

在派生类数据成员通常有3类:继承于基类的数据成员,派生类中自身定义的数据成员以及派生类中其它类的对象。在上述形式中,由于基类在派生类中常常是隐常的,所以通过调用基类构造函数来对基类的数据成员初值化,这也称为基类拷贝或基类子对象。在上述构造函数定义中的对象成员为派生类中定义的其它类的对象(构造函数中的形参初始化)。

当然在初始化列表中也可对派生类自身数据成员初始化,但必须为“数据成员名(参数表)”形式。对于基类拷贝成员的初始化次序,若为单继承,优先初始化上层类的对象;若为多继承,则取决于派生类声明指定继承时的基类的先后次序,与初始化列表中的次序无关。对于派生类自身数据成员的初始化次序与初始化列表中的次序无关,取决于在派生类中声明的先后次序。

继承方式:公有继承(public),私有继承(private),保护继承(protected)。

公有继承

基类对象可访问基类公有成员,不可访问保护成员和私有成员。

派生类成员可访问基类的公有成员和保护成员,它们作为派生类的成员时,保持原来的状态;若在派生类中有同名成员存在,则访问基类成员时,基类成员名前面必须加上“基类名::”来指定成员所属的类;派生类成员不可访问基类的私有成员。

派生类对象只能访问基类的公有成员,不可访问保护成员和私有成员;对于同名成员,在基类成员名前面加上“基类名::”来指定成员所属的类,如OB.OA::m或OB-> OA::m。

总之,公有继承时,基类对象与派生类对象可访问基类的公有成员,而派生类的成员函数则可访问基类的公有成员和保护成员。

私有继承

基类对象可访问基类公有成员,不可访问保护成员和私有成员。

派生类成员可访问基类的公有成员和保护成员,它们作为派生类的成员时,作为派生类的私有成员,因此不能被这个派生类的子类所访问;若在派生类中有同名成员存在,则访问基类成员时基类成员名前面必须加上“基类名::”来指定成员所属的类;派生类成员不可访问基类的私有成员。

派生类对象不能访问基类任何成员(公有成员也不能访问)。

总之,私有继承时,基类对象可访问基类公有成员,派生类对象不能访问基类所有成员,而派生类的成员函数则可访问基类的公有成员和保护成员(但不能再往下继承)。

保护继承

基类对象可访问基类公有成员,不可访问保护成员和私有成员。

派生类成员可访问基类的公有成员和保护成员,它们作为派生类的成员时,作为派生类的保护成员(与私有继承的唯一区别),且不能被这个派生类的子类所访问;若在派生类中有同名成员存在,则访问基类成员时基类成员名前面必须加上“基类名::”来指定成员所属的类;派生类成员不可访问基类的私有成员。

派生类对象不能访问基类任何成员(公有成员也不能访问)。

总之,保护继承时,基类对象可访问基类公有成员,派生类对象不能访问基类所有成员,而派生类的成员函数则可访问基类的公有成员和保护成员(但不能再往下继承)。

多重继承与虚继承:虚继承是为解决多重继承而出现的,是多重继承中特有的概念。例如,类D继承于类B和类C,而类B和类C又都继承于类A,即A类是B类和C类的基类,而B类和C类同时是D类的基类。为了节省内存空间 ,可以将B,C对A的继承定义为虚拟继承,则A就成了虚拟基类。用代码表示如下:

class A ;

class B:public virtual A;//虚拟继承

class C:public virtual A;//虚拟继承

class D:public B,public C;

最后说明,在面向对象的程序设计中,继承和多重继承一般是指公共继承,很少用私有继承和保护继承。

九、多态:允许将子类类型的指针赋值给父类类型的指针。多态性是通过虚函数实现的,是面向对象编程的核心概念。虚函数就是允许被其子类重新定义的成员函数,而子类重新定义父类虚函数的做法,叫做覆盖或重写。

覆盖是指子类重新定义父类的虚函数的做法。重写的函数必须有一致的参数表和返回值(C++标准允许返回值不同的情况,但很少有编译器支持)。

重载是指编写一个与已有函数同名但参数表不同(个数或类型不同)的函数。即指允许存在多个同名函数, 但这些函数的参数表不同。重载的概念并不属于面向对象编程。它的实现是:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对编译器来说是这样的)。函数重载是静态联编的具体实现方式。调用重载函数时,编译根据调用时参数类型与个数在编译时实现静态联编,将调用地址与函数名进行绑定。所以说对于重载函数的调用,在编译期间就确定了,是静态的,也就是说它们的函数标识符与内存地址在编译期就绑定了(早绑定)。因此重载与多态无关。

在静态联编的方式下,同一成员函数(参数类型及个数相同,函数返回类型也相同)在基类与派生类中的不同版本是不会在运行时根据程序代码的指定进行自动绑定的,必须通过类的虚函数机制,才能实现基类和派生类中的成员函数不同版本的动态联编。动态联编是指函数标识符与内存地址在程序运行时动态绑定,而不在编译时静态绑定,所以又称晚绑定。虚函数是用关键字virtual来修饰基类中的public或protected的成员函数。当在派生类中进行重新定义后(函数覆盖),就可在此派生类中具有该成员函数的不同版本。在程序运行过程中,依据基类对象指针所指向的派生类对象,或通过基类引用对象所引用的派生类对象,才能确定哪个派生类中重新定义的成员函数被激活,从而实现动态联编。虚函数虽然只是在一般函数定义前加上virtual,但必须是类中成员函数;可把析构函数定义为虚函数,但不能把构造函数定义为虚函数,通常在释放基类中及其派生类中动态申请的存储空间时,也要把析构函数定义为虚函数,以便实现撤销对象时的多态性;虚函数在派生类重新定义时,参数个数和类型以及函数类型必须和基类的虚函数完全匹配,这一点与函数重载完全不同;虚函数派生下去仍是虚函数,且可省略virtual关键字。

当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态地调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(所调子类的虚函数的地址无法确定)。因此,这样的函数地址是在函数运行期间绑定的(晚绑定)。

基类与派生类中的每个虚函数都在虚函数表中(vtable)占有一个表项,保存着一条跳转到它的入口地址的指令(实际上就是保存它的入口地址)。当一个包含虚函数的对象(注意,不是指针类型对象)被创建时,它在头部附加一个指针,指向vtable中相应的位置。调用虚函数的时候,不管是用什么指针调用的,它先根据 vtable找到入口地址再执行,从而实现了动态联编。而普通函数只有简单地跳转到一个固定的地址。

十、友元

只有类的成员函数才能访问类的私有成员,程序中的其它函数是无法访问私有成员的。非成员函数可以访问类中的公有成员,不能访问其私有成员。

友元是一种定义在类外部的普通函数,但它需要在类中说明,为了与该类的成员函数加以区别,在说明时前面加以关键字friend。友元不是成员函数,但它可以访问类中的私有成员。友元可以提高程序的运行效率。但是它破坏了类的封装性和隐藏性,使非成员函数也可以访问类的私有成员。友元可以是一个函数,称为友元函数;友元也可以是一个类,称为友元类。

十一、this指针:this指针是类中一个特殊指针,当类实例化(用类定义对象)时,this指针指向对象自己,而在类的声明时指向类本身。

十二、关于C++中class里面 static变量问题(即面向对象的程序设计中的static),与C中的static全局变量和局部变量含义不同(C是面向过程的程序设计)

class B:

{

public:

static int a;

};

B b;

int _tmain(int argc, _TCHAR* argv[])

{

b.a = 10;

system("pause");

return 0;

}

编译出现错误:

Error error LNK2001: unresolved external symbol "public: static int B::a"

解决方法:必须初始化,初始化的时候要在类外;如下:添加int B::a = 0;也可在类中初始化,在类中写成static int a=0;

静态变量是属于类,而不是属于类对象的。也就是说不管你有多少个类对象,静态成员只有一个。 它的初始化,不能依赖于创建类对象时用于初始化对象的构造函数。

静态数据成员也遵从 public/private/protected访问规则。类中的那个static int a; 只是声明,定义要放在外面int B::a = 0; // 此处可以初始化 。如果a是公有的,B::a就可以访问了(读取和修改)。 静态成员的提出是为了解决数据共享的问题。实现共享有许多方法,如:设置全局性的变量或对象是一种方法。但是,全局变量或对象是有局限性的。在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即保证了安全性。因此,静态成员是类的所有对象中共享的成员,而不是某个对象的成员。

    使用静态数据成员可以节省内存,因为它是所有对象所公有的。对该类的多个对象来说,静态数据成员只分配一次内存,供所有对象共用。因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是一样,但它的值是可以更新的。只要对静态数据成员的值更新一次,保证所有对象存取更新后的相同的值,这样可以提高时间效率。因为静态数据成员在全局数据区分配内存,属于本类的所有对象共享,所以,它不属于特定的类对象,在没有产生类对象时其作用域就可见,即在没有产生类的实例时,我们就可以操作它;

同全局变量相比,使用静态数据成员有两个优势:静态数据成员没有进入程序的全局名字空间,因此不存在与程序中其它全局名字冲突的可能性;可以实现信息隐藏,静态数据成员可以是private成员,而全局变量不能;

  静态数据成员的使用方法和注意事项如下:

  1、静态数据成员在定义或说明时前面加关键字static。

  2、静态成员初始化与一般数据成员初始化不同。静态数据成员初始化的格式如下:

    <数据类型><类名>::<静态数据成员名>=<值>

  这表明:

(1) 初始化可在类外进行,而前面不加static,以免与一般静态变量或对象相混淆。当然也可在类中初始化。

  (2) 初始化时不加该成员的访问权限控制符private,public等。

  (3) 初始化时使用作用域运算符来标明它所属类,因此,静态数据成员是类的成员,而不是对象的成员。

  3、静态数据成员是静态存储的,它是静态生存期,必须对它进行初始化。

  4、引用静态数据成员时,采用如下格式:<类名>::<静态成员名>

如果静态数据成员的访问权限允许的话(即public的成员),可在程序中,按上述格式来引用静态数据成员。

与静态数据成员一样,我们也可以创建一个静态成员函数,它为类的全部服务而不是为某一个类的具体对象服务。静态成员函数与静态数据成员一样,都是类的内部实现,属于类定义的一部分。普通的成员函数一般都隐含了一个this指针,this指针指向类的对象本身,因为普通成员函数总是具体的属于某个类的具体对象的。通常情况下,this 是缺省的。如函数fn()实际上是this->fn()。但是与普通函数相比,静态成员函数由于不是与任何的对象相联系,因此它不具有this指针。从这个意义上讲,它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,它只能调用其余的静态成员函数和静态数据成员。而非静态成员函数(普通函数)可以访问静态数据成员和调用其余的静态成员函数。

关于静态成员函数,可以总结为以下几点:

1、出现在类体外的函数定义不能指定关键字static;

2、静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数;

3、非静态成员函数可以任意地访问静态成员函数和静态数据成员;

4、静态成员函数不能访问非静态成员函数和非静态数据成员;

5、由于没有this指针的额外开销,因此静态成员函数与类的全局函数相比速度上会有少许的增长;

6、调用静态成员函数,可以用成员访问操作符(.)和(->)为一个类的对象或指向类对象的指针调用静态成员函数,

也可以直接使用如下格式:<类名>::<静态成员函数名>(<参数表>)调用类的静态成员函数。

时间: 2024-10-14 22:05:20

C++基础知识(二)的相关文章

ASP.NET Core 2.2 基础知识(二) 中间件

原文:ASP.NET Core 2.2 基础知识(二) 中间件 中间件是一种装配到应用管道以处理请求和相应的软件.每个软件都可以: 1.选择是否将请求传递到管道中的下一个组件; 2.可在调用管道中的下一个组件前后执行工作. 管道由 IApplicationBuilder 创建: 每个委托都可以在下一个委托前后执行操作,.此外,委托还可以决定不将请求传递给下一个委托,这就是对请求管道进行短路.通常需要短路,是因为这样可以避免不必要的工作.比如: 1.静态文件中间件可以返回静态文件请求并使管道的其余

Java基础知识二次学习-- 第一章 java基础

基础知识有时候感觉时间长似乎有点生疏,正好这几天有时间有机会,就决定重新做一轮二次学习,挑重避轻 回过头来重新整理基础知识,能收获到之前不少遗漏的,所以这一次就称作查漏补缺吧!废话不多说,开始! 第一章  JAVA简介 时间:2017年4月24日10:23:32 章节:01章_02节 内容:jdk的配置与安装 完成情况:已经完成,cmd中javac提示出相关命令 时间:2017年4月24日10:30:39 章节:01章_04节 内容:输出HelloWorld 完成情况: 已经完成 javac先将

Java基础知识(二)

1,字符串 new String("abc")创建了几个对象? 一个或两个,如果常量池中原来有"abc",则只创建一个对象:如果常量池中原来没有字符串"abc",那么就会创建两个对象. String s="abc"; String s1="ab"+"c"; System.out.println(s==s1); 输出 true ,因为"ab"+"c"

Powershell基础知识(二)

上一节主要介绍Powershell可发现,面向对象,一致性等特性,以及Powershell命令是基于.Net对象等重要概念,以及Powershell命令的命名规范,详细内容点击这里. 这一节的Powershell基础知识主要包含以下知识点 获取命令的摘要信息. 获取命令的帮助信息. 总结. 获取命令的摘要信息 Powershell命令 Get-Command 可检索当前shell中所有可用的命令名称.在Powershell提示符输入 Get-Command ,输出的内容类似以下内容(以下只写出输

计算机科学基础知识(二)Relocatable Object File

一.前言 一个合格的c程序员(也可以叫做软件工程师,这样看起来更高大上,当然,我老婆心情不好的时候总是叫我"死打字的",基本也能描述这份职业,呵呵)需要理解编译.链接和加载的过程,而不是仅仅关注c语言的语法和词法.本文主要以此为切入点,描述linux系统下,一个普通的hello world程序的生命历程,并借机灌输一些程序编译时和运行时的基本术语和概念.当然,由于我本人是一个linuxer,因此借用linux来描述这些知识会方便些,但是对于计算机科学而言,这些东西概念上是类似的,只是实

Java基础知识二次学习-- 第二章 基础语法与递归补充

第二章 基础语法与递归补充   时间:2017年4月24日10:39:18 章节:02章_01节,02章_02节 视频长度:49:21 + 15:45 内容:标识符,关键字与数据类型 心得:由字母,下划线,$,数字组成,应该由字母,下划线$开头,同时应该避开java保留字符 变量是内存中的一小块区域,使用变量名来访问这块区域 执行过程中的内存管理(疑问:这里的内存和Jvm的一样吗?) code segment 存放代码 data segment 静态变量 字符串常量 stack 栈 局部变量 h

jsp基础知识二(jsp动作指令)

动作指令与编译指令不同,编译指令是通知servlet引擎的处理消息,而动作指令只是运行时的动作.编译指令在将JSP编译成Servlet时起作用,而动作指令通常可替换成JSP脚本,它只是JSP脚本的标准化写法. (1)JSP:forward  执行页面转向,将请求的处理转发到下一个页面. (2)JSP:param  用于传递参数,必须与其他支持参数的标签一起使用 (3)JSP:include  用于动态引入一个JSP页面 (4)JSP:plugin 用于下载JavaBean或者Applet到客户端

css基础知识二

1.盒模型: 实际宽度:外边距*2+内边距*2+边框*2+内容宽度(注意这点,可解决界面元素轻微浮动问题,如hover有边框,以前没的时候会有轻微浮动) 作用:他规定了网页元素如何显示以及其相互关系 2.padding会撑大容器,而margin不会. 这点巧用(自己总结的):① 能用margin的别用padding ② 网页中为一个元素添加一边的下划线时候用padding ③ 导航栏中,用<ul><li>写的时候,先调节<li>的padding:npx,再调节<u

金子的PHP之禅(PHP基础知识二)

一.php的标记风格和注释 1.<?php ?> 2.<? ?> 3.<% %> 提示:如果要使用短标签方式需要在php.ini中开启short_open_tag 注释:采用//./* */等方式 二.数据类型 boolean (布尔型) string(字符型) int (整型) float (浮点型) array(数组) object(对象) null (特殊类型) 转换数据类型方法 比如:123转换成字符型 (string)123 比如:12323bac转换成整型

Java基础知识二次学习--第五章 数组

第五章 数组 时间:2017年4月26日15:11:30~2017年4月26日15:15:54 章节:05章_01节  视频长度:09:30 内容:一维数组的内存分析 心得: Java中数组是引用类型 栈里面存的数组的引用 实际对象在堆内存里面 (C与C++是分配在栈里的) 内存图: 元素为引用数据类型的数组 引用类型的数组如上图 时间:2017年4月26日15:16:22~2017年4月26日15:19:00 章节:05章_02节  视频长度:05:25 内容:数组元素的创建与使用 心得: 首