c++基础课程讲义


轻松入门 实战应用 传智播客 C++课程 传智播客 C++课程讲义 传智扫地僧 1、C++对 C 的扩展 1 简单的 C++程序 1.1 求圆的周长和面积 数据描述: 半径,周长,面积均用实型数表示 数据处理: 输入半径 r; 计算周长 = 2*π*r ; 计算面积 = π* r2 ; 输出半径,周长,面积; 方法 1:用结构化方法编程,求圆的周长和面积 // count the girth and area of circle #include using name std; void main () { double r, girth, area ; const double PI = 3.1415 ; cout << "Please input radius:\n" ; //操作符重载 cin >> r ; //输入 girth = 2 * PI * r ; area = PI * r * r ; cout << "radius = " << r << endl ; cout << "girth = " << girth << endl ; cout << "area = " << area << endl ; } 方法 2:用面向对象方法编程,求圆的周长和面积 轻松入门 实战应用 传智播客 C++课程 #include using name std; class Circle { double radius ; //成员变量 public : //类的访问控制 void Set_Radius( double r ) { radius = r ; } //成员函数 double Get_Radius() { return radius ; } //通过成员函数设置成员变量 double Get_Girth() { return 2 * 3.14f * radius ; } //通过成员函数获取成员变量 double Get_Area() { return 3.14f * radius * radius ; } } ; void main() { Circle A, B ; //用类定义对象 A.Set_Radius( 6.23 ) ; //类的调用 cout << "A.Radius = " << A.Get_Radius() << endl ; 轻松入门 实战应用 传智播客 C++课程 cout << "A.Girth = " << A.Get_Girth() << endl ; cout << "A.Area = " << A.Get_Area() << endl ; B.Set_Radius( 10.5 ) ; cout << "B.radius = " << B.Get_Radius() << endl ; cout << "B.Girth=" << B.Get_Girth() << endl ; cout << "B.Area = " << B.Get_Area() << endl ; } 总结:建立类、对象、成员变量、成员函数,输入输入流基本概念。 1.2 初学者易犯错误模型 // demo02_circle_err.cpp #include using namespace std;//c++的命名空间 class circle { public: double r; double pi = 3.1415926; double area = pi*r*r; }; int main() { circle pi; cout << "请输入 area" << endl; cin >> pi.r; cout << pi.area << endl; //乱码 system("pause"); return 0; } 总结: 从内存四区的角度,解释为什么会出现乱码 理解为什么需要成员函数 轻松入门 实战应用 传智播客 C++课程 2 程序设计方法的发展历程 面向过程的结构化程序设计方法  设计思路 – 自顶向下、逐步求精。采用模块分解与功能抽象,自顶向下、分而治之。  程序结构: – 按功能划分为若干个基本模块,形成一个树状结构。 – 各模块间的关系尽可能简单,功能上相对独立;每一模块内部均是由顺序、 选择和循环三种基本结构组成。 – 其模块化实现的具体方法是使用子程序。  优点: 有效地将一个较复杂的程序系统设计任务分解成许多易于控制和处理的子任务,便于开发和 维护。  缺点:可重用性差、数据安全性差、难以开发大型软件和图形界面的应用软件 – 把数据和处理数据的过程分离为相互独立的实体。 – 当数据结构改变时,所有相关的处理过程都要进行相应的修改。 – 每一种相对于老问题的新方法都要带来额外的开销。 – 图形用户界面的应用程序,很难用过程来描述和实现,开发和维护也都很困 难。 面向对象的方法  将数据及对数据的操作方法封装在一起,作为一个相互依存、不可分离的整体—— 对象。  对同类型对象抽象出其共性,形成类。  类通过一个简单的外部接口,与外界发生关系。  对象与对象之间通过消息进行通信。 面向对象的基本概念 对象  一般意义上的对象: – 是现实世界中一个实际存在的事物。 – 可以是有形的(比如一辆汽车),也可以是无形的(比如一项计划)。 – 是构成世界的一个独立单位,具有  静态特征:可以用某种数据来描述  动态特征:对象所表现的行为或具有的功能  面向对象方法中的对象: – 是系统中用来描述客观事物的一个实体,它是用来构成系统的一个基本单位。 对象由一组属性和一组行为构成。 – 属性:用来描述对象静态特征的数据项。 – 行为:用来描述对象动态特征的操作序列。 类  分类——人类通常的思维方法  分类所依据的原则——抽象 – 忽略事物的非本质特征,只注意那些与当前目标有关的本质特征,从而找出 轻松入门 实战应用 传智播客 C++课程 事物的共性,把具有共同性质的事物划分为一类,得出一个抽象的概念。 – 例如,石头、树木、汽车、房屋等都是人们在长期的生产和生活实践中抽象 出的概念。  面向对象方法中的"类" – 具有相同属性和服务的一组对象的集合 – 为属于该类的全部对象提供了抽象的描述,包括属性和行为两个主要部分。 – 类与对象的关系: 犹如模具与铸件之间的关系,一个属于某类的对象称为该类的一个实例。 封装 也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者 对象操作,对不可信的进行信息隐藏。  把对象的属性和服务结合成一个独立的系统单元。  尽可能隐蔽对象的内部细节。对外形成一个边界(或者说一道屏障),只保留有限的 对外接口使之与外部发生联系。  继承对于软件复用有着重要意义,是面向对象技术能够提高软件开发效率的重要原 因之一。  定义:特殊类的对象拥有其一般类的全部属性与服务,称作特殊类对一般类的继承。  例如:将轮船作为一个一般类,客轮便是一个特殊类。 多态 多态是指在一般类中定义的属性或行为,被特殊类继承之后,可以具有不同的数据类型 或表现出不同的行为。这使得同一个属性或行为在一般类及其各个特殊类中具有不同的语义。 面向对象的软件工程  面向对象的软件工程是面向对象方法在软件工程领域的全面应用。它包括: – 面向对象的分析(OOA) – 面向对象的设计(OOD) – 面向对象的编程(OOP) – 面向对象的测试(OOT) – 面向对象的软件维护(OOSM) 总结: 面向过程程序设计:数据结构 + 算法 主要解决科学计算问题,用户需求简单而固定 特点: 分析解决问题所需要的步骤 利用函数实现各个步骤 依次调用函数解决问题 问题: 软件可重用性差 软件可维护性差 构建的软件无法满足用户需求 面向对象程序设计:由现实世界建立软件模型 将现实世界中的事物直接映射到程序中,可直接满足用户需求 轻松入门 实战应用 传智播客 C++课程 特点: 直接分析用户需求中涉及的各个实体 在代码中描述现实世界中的实体 在代码中关联各个实体协同工作解决问题 优势: 构建的软件能够适应用户需求的不断变化 直接利用面向过程方法的优势而避开其劣势 3 C 语言和 C++语言关系 C 语言是在实践的过程中逐步完善起来的 没有深思熟虑的设计过程 使用时存在很多“灰色地带” 残留量过多低级语言的特征 直接利用指针进行内存操作 C 语言的目标是高效 最终程序执行效率的高效 当面向过程方法论暴露越来越多的缺陷的时候,业界开始考虑在工程项目中引入面向对象的 设计方法,而第一个需要解决的问题就是:高效的面向对象语言,并且能够兼容已经存在的 代码。 C 语言 + 面向对象方法论===》Objective C /C++ C 语言和 C++并不是对立的竞争关系 C++是 C 语言的加强,是一种更好的 C 语言 C++是以 C 语言为基础的,并且完全兼容 C 语言的特性 学习 C++并不会影响原有的 C 语言知识,相反会根据加深对 C 的认知; 学习 C++可以接触到更多的软件设计方法,并带来更多的机会。 1) C++是一种更强大的 C,通过学习 C++能够掌握更多的软件设计方法 2) C++是 Java/C#/D 等现代开发语言的基础,学习 C++后能够快速掌握这些语言 3)C++是各大知名软件企业挑选人才的标准之一 轻松入门 实战应用 传智播客 C++课程 4 C++对 C 的加强 4.1 namespace 命名空间 1 C++命名空间基本常识 所谓 namespace,是指标识符的各种可见范围。C++标准程序库中的所有标识符都被定 义于一个名为 std 的 namespace 中。 一 :格式不一样,前者没有后缀,实际上,在你的编译器 include 文件夹里面可以看到,二者是两个文件,打开文件就会发现,里面的代码是不一样的。后缀 为.h 的头文件 c++标准已经明确提出不支持了,早些的实现将标准库功能定义在全局空间里, 声明在带.h 后缀的头文件里,c++标准为了和 C 区别开,也为了正确使用命名空间,规定头 文件不使用后缀.h。 因此, 1)当使用时,相当于在 c 中调用库函数,使用的是全局命名空间,也就是 早期的 c++实现; 2)当使用的时候,该头文件没有定义全局命名空间,必须使用 namespace std; 这样才能正确使用 cout。 二: 由于 namespace 的概念,使用 C++标准程序库的任何标识符时,可以有三种选择: 1、直接指定标识符。例如 std::ostream 而不是 ostream。完整语句如下: std::cout << std::hex << 3.4 << std::endl; 2、使用 using 关键字。 using std::cout; using std::endl; using std::cin; 以上程序可以写成 cout << std::hex << 3.4 << endl; 3、最方便的就是使用 using namespace std; 例如: using namespace std;这样命名空间 std 内定义的所有标识符都有效(曝光)。就好像它们被声明为全局变量一样。那么以上语句 轻松入门 实战应用 传智播客 C++课程 可以如下写: cout <等等这样的头文件,一个是为了兼容以前的 C++代码, 一个是为了支持新的标准。命名空间 std 封装的是标准程序库的名称,标准程序库为了和以 前的头文件区别,一般不加".h" 2 C++命名空间定义及使用语法 /* 在 C++中,名称(name)可以是符号常量、变量、宏、函数、结构、枚举、类和对象等等。 为了避免,在大规模程序的设计中,以及在程序员使用各种各样的 C++库时,这些标识符的 命名发生冲突, 标准 C++引入了关键字 namespace(命名空间/名字空间/名称空间/名域),可以更好地 控制标识符的作用域。 */ /* std 是 c++标准命名空间,c++标准程序库中的所有标识符都被定义在 std 中,比如标准库中 的类 iostream、vector 等都定义在该命名空间中,使用时要加上 using 声明(using namespace std) 或 using 指示(如 std::string、 std::vector). */ /* C 中的命名空间 在 C 语言中只有一个全局作用域 C 语言中所有的全局标识符共享同一个作用域 标识符之间可能发生冲突 C++中提出了命名空间的概念 命名空间将全局作用域分成不同的部分 不同命名空间中的标识符可以同名而不会发生冲突 命名空间可以相互嵌套 全局作用域也叫默认命名空间 */ /* C++命名空间的定义: namespace name { … } */ /* C++命名空间的使用: 使用整个命名空间:using namespace name; 使用命名空间中的变量:using name::variable; 轻松入门 实战应用 传智播客 C++课程 使用默认命名空间中的变量:::variable 默认情况下可以直接使用默 认命名空间中的所有标识符 */ 3 C++命名空间编程实践 namespace NameSpaceA { int a = 0; } namespace NameSpaceB { int a = 1; namespace NameSpaceC { struct Teacher { char name[10]; int age; }; } } int main() { using namespace NameSpaceA; using NameSpaceB::NameSpaceC::Teacher; printf("a = %d\n", a); printf("a = %d\n", NameSpaceB::a); NameSpaceB::NameSpaceC::Teacher t2 Teacher t1 = {"aaa", 3}; printf("t1.name = %s\n", t1.name); printf("t1.age = %d\n", t1.age); system("pause"); return 0; } 轻松入门 实战应用 传智播客 C++课程 4 结论 1) 当使用的时候,该头文件没有定义全局命名空间,必须使用 namespace std; 这样才能正确使用 cout。若不引入 using namespace std ,需要这样做。std::cout。 2) c++标准为了和 C 区别开,也为了正确使用命名空间,规定头文件不使用后缀.h。 3) C++命名空间的定义: namespace name { … } 4) using namespace NameSpaceA; 5) namespce 定义可嵌套。 4.2 “实用性”增加 #include "iostream" using namespace std; //C 语言中的变量都必须在作用域开始的位置定义!! //C++中更强调语言的“实用性”,所有的变量都可以在需要使用时再定义。 int main11() { int i = 0; printf("ddd"); int k; system("pause"); return 0; } 4.3 register 关键字增强 //register 关键字 请求编译器让变量 a 直接放在寄存器里面,速度快 //在 c 语言中 register 修饰的变量 不能取地址,但是在 c++里面做了内容 /* //1 register 关键字的变化 register 关键字请求“编译器”将局部变量存储于寄存器中 C 语言中无法取得 register 变量地址 在 C++中依然支持 register 关键字 C++编译器有自己的优化方式,不使用 register 也可能做优化 C++中可以取得 register 变量的地址 轻松入门 实战应用 传智播客 C++课程 //2 C++编译器发现程序中需要取 register 变量的地址时,register 对变量的声明变得无效。 //3 早期 C 语言编译器不会对代码进行优化,因此 register 变量是一个很好的补充。 */ int main22() { register int a = 0; printf("&a = %x\n", &a); system("pause"); return 0; } 其他补充:请阅读《register 关键字常识课外阅读.docx》 4.4 变量检测增强 /* 在 C 语言中,重复定义多个同名的全局变量是合法的 在 C++中,不允许定义多个同名的全局变量 C 语言中多个同名的全局变量最终会被链接到全局数据区的同一个地址空间上 int g_var; int g_var = 1; C++直接拒绝这种二义性的做法。 */ int main(int argc, char *argv[]) { printf("g_var = %d\n", g_var); return 0; } 4.5 struct 类型加强 struct 类型的加强: C 语言的 struct 定义了一组变量的集合,C 编译器并不认为这是一种新的类型 C++中的 struct 是一个新类型的定义声明 struct Student 轻松入门 实战应用 传智播客 C++课程 { char name[100]; int age; }; int main(int argc, char *argv[]) { Student s1 = {"wang", 1}; Student s2 = {"wang2", 2}; return 0; } 4.6 C++中所有的变量和函数都必须有类型 /* C++中所有的变量和函数都必须有类型 C 语言中的默认类型在 C++中是不合法的 函数 f 的返回值是什么类型,参数又是什么类型? 函数 g 可以接受多少个参数? */ //更换成.cpp 试试 f(i) { printf("i = %d\n", i); } g() { return 5; } int main(int argc, char *argv[]) { f(10); printf("g() = %d\n", g(1, 2, 3, 4, 5)); 轻松入门 实战应用 传智播客 C++课程 getchar(); return 0; } 总结: /* 在 C 语言中 int f( );表示返回值为 int,接受任意参数的函数 int f(void);表示返回值为 int 的无参函数 在 C++中 int f( );和 int f(void)具有相同的意义,都表示返回值为 int 的无参函数 */ C++更加强调类型,任意的程序元素都必须显示指明类型 4.2-4.6 属于语法级别的增强。 4.7 新增 Bool 类型关键字 /* C++中的布尔类型 C++在 C 语言的基本类型系统之上增加了 bool C++中的 bool 可取的值只有 true 和 false 理论上 bool 只占用一个字节, 如果多个 bool 变量定义在一起,可能会各占一个 bit,这取决于编译器的实现 true 代表真值,编译器内部用 1 来表示 false 代表非真值,编译器内部用 0 来表示 bool 类型只有 true(非 0)和 false(0)两个值 C++编译器会在赋值时将非 0 值转换为 true,0 值转换为 false */ int main(int argc, char *argv[]) { int a; bool b = true; printf("b = %d, sizeof(b) = %d\n", b, sizeof(b)); b = 4; a = b; printf("a = %d, b = %d\n", a, b); 轻松入门 实战应用 传智播客 C++课程 b = -4; a = b; printf("a = %d, b = %d\n", a, b); a = 10; b = a; printf("a = %d, b = %d\n", a, b); b = 0; printf("b = %d\n", b); system("pause"); return 0; } 4.8 三目运算符功能增强 1 三目运算符在 C 和 C++编译器的表现 int main() { int a = 10; int b = 20; //返回一个最小数 并且给最小数赋值成 3 //三目运算符是一个表达式 ,表达式不可能做左值 (a < b ? a : b )= 30; printf("a = %d, b = %d\n", a, b); system("pause"); return 0; } 2 结论 1)C 语言返回变量的值 C++语言是返回变量本身 C 语言中的三目运算符返回的是变量值,不能作为左值使用 C++中的三目运算符可直接返回变量本身,因此可以出现在程序的任何地方 2)注意:三目运算符可能返回的值中如果有一个是常量值,则不能作为左值使用 轻松入门 实战应用 传智播客 C++课程 (a < b ? 1 : b )= 30; 3)C 语言如何支持类似 C++的特性呢? ====>当左值的条件:要有内存空间;C++编译器帮助程序员取了一个地址而已 思考:如何让 C 中的三目运算法当左值呢? 5 C/C++中的 const 1 const 基础知识(用法、含义、好处) int main() { const int a; int const b; const int *c; int * const d; const int * const e ; return 0; } Int func1(const ) 初级理解:const 是定义常量==》const 意味着只读 含义: //第一个第二个意思一样 代表一个常整形数 //第三个 c 是一个指向常整形数的指针(所指向的内存数据不能被修改,但是本身可以修改) //第四个 d 常指针(指针变量不能被修改,但是它所指向内存空间可以被修改) //第五个 e 一个指向常整形的常指针(指针和它所指向的内存空间,均不能被修改) Const 好处 //合理的利用 const, //1 指针做函数参数,可以有效的提高代码可读性,减少 bug; //2 清楚的分清参数的输入和输出特性 int setTeacher_err( const Teacher *p) Const 修改形参的时候,在利用形参不能修改指针所向的内存空间 2 C 中“冒牌货” int main() { const int a = 10; 轻松入门 实战应用 传智播客 C++课程 int *p = (int*)&a; printf("a===>%d\n", a); *p = 11; printf("a===>%d\n", a); printf("Hello......\n"); return 0; } 解释: C++编译器对 const 常量的处理 当碰见常量声明时,在符号表中放入常量 =问题:那有如何解释取地址 编译过程中若发现使用常量则直接以符号表中的值替换 编译过程中若发现对 const 使用了 extern 或者&操作符,则给对应的常量分配存储空间(兼 容 C) ?联想: int &a = 1(err) & const int &a = 10(ok)? C++中 const 符号表原理图 注意: C++编译器虽然可能为 const 常量分配空间,但不会使用其存储空间中的值。 结论: C 语言中的 const 变量 C 语言中 const 变量是只读变量,有自己的存储空间 C++中的 const 常量 可能分配存储空间,也可能不分配存储空间 当 const 常量为全局,并且需要在其它文件中使用 当使用&操作符取 const 常量的地址 3 const 和#define 相同之处 //练习 解释为什么 //#define N 10 int main() { const int a = 1; const int b = 2; 轻松入门 实战应用 传智播客 C++课程 int array[a + b ] = {0}; int i = 0; for(i=0; i<(a+b); i++) { printf("array[%d] = %d\n", i, array[i]); } getchar(); return 0; } C++中的 const 修饰的,是一个真正的常量,而不是 C 中变量(只读)。在 const 修饰的常量 编译期间,就已经确定下来了。 4 const 和#define 的区别 对比加深 C++中的 const 常量类似于宏定义 const int c = 5; ≈ #define c 5 C++中的 const 常量与宏定义不同 const 常量是由编译器处理的,提供类型检查和作用域检查 宏定义由预处理器处理,单纯的文本替换 //在 func1 定义 a,在 func2 中能使用吗? //在 func1 中定义的 b,在 func2 中能使用吗? 练习 void fun1() { #define a 10 const int b = 20; //#undef a # undef } void fun2() { printf("a = %d\n", a); //printf("b = %d\n", b); } int main() { fun1(); 轻松入门 实战应用 传智播客 C++课程 fun2(); return 0; } 5 结论 C 语言中的 const 变量 C 语言中 const 变量是只读变量,有自己的存储空间 C++中的 const 常量 可能分配存储空间,也可能不分配存储空间 当 const 常量为全局,并且需要在其它文件中使用,会分配存储空间 当使用&操作符,取 const 常量的地址时,会分配存储空间 当 const int &a = 10; const 修饰引用时,也会分配存储空间 6 引用专题讲座 1 引用(普通引用) 变量名回顾 变量名实质上是一段连续存储空间的别名,是一个标号(门牌号) 程序中通过变量来申请并命名内存空间 通过变量的名字可以使用存储空间 问题 1:对一段连续的内存空间只能取一个别名吗? 1 引用概念 a) 在C++中新增加了引用的概念 b) 引用可以看作一个已定义变量的别名 c) 引用的语法:Type& name = var; d) 引用做函数参数那?(引用作为函数参数声明时不进行初始化) void main01() { int a = 10; //c编译器分配4个字节内存。。。a内存空间的别名 int &b = a; //b就是a的别名。。。 a =11; //直接赋值 { int *p = &a; *p = 12; 轻松入门 实战应用 传智播客 C++课程 printf("a %d \n",a); } b = 14; printf("a:%d b:%d", a, b); system("pause"); } 2 引用是 C++的概念 属于C++编译器对C的扩展 问题:C中可以编译通过吗? int main() { int a = 0; int &b = a; //int * const b = &a b = 11; //*b = 11; return 0; } 结论:请不要用C的语法考虑 b=11 3 引用做函数参数 普通引用在声明时必须用其它的变量进行初始化, 引用作为函数参数声明时不进行初始化 //05复杂数据类型 的引用 struct Teacher { char name[64]; int age ; }; void printfT(Teacher *pT) { cout<age< pT = t1 printf("t1.age:%d \n", t1.age); //35 cout<<"hello..."<"<b?&a:&b) = 111; //当被调用的函数当左值的时候,必须返回一个引用。。。。。 j1() = 100; //编译器帮我们打造了环境 j1(); *(j2()) = 200; //相当于我们程序员手工的打造 做左值的条件 j2(); system("pause"); } 返回值是形参,当引用 int g1(int *p) { *p = 100; return *p; } int& g2(int *p) // { *p = 100; return *p; } //当我们使用引用语法的时候 ,我们不去关心编译器引用是怎么做的 //当我们分析乱码这种现象的时候,我们才去考虑c++编译器是怎么做的。。。。 void main23() { int a1 = 10; a1 = g2(&a1); int &a2 = g2(&a1); //用引用去接受函数的返回值,是不是乱码,关键是看返回的内存空 间是不是被编译器回收了。。。。 printf("a1:%d \n", a1); printf("a2:%d \n", a2); system("pause"); } 轻松入门 实战应用 传智播客 C++课程 返回值非基础类型 struct Teachar { char name[64]; int age; }; //如果返回引用不是基础类型,是一个类,那么情况非常赋值。。涉及到copy构造函数和=操 作重载,抛砖。。。。 struct Teachar { char name[64]; int age; }; //如果返回引用不是基础类型,是一个类,那么情况非常赋值。。涉及到copy构造函数和=操 作重载,抛砖。。。。 struct Teachar & OpTeacher(struct Teachar &t1) { } 9 指针引用 #include "iostream" using namespace std; struct Teacher { char name[64]; int age; }; int getTe(Teacher **myp ) { Teacher *p = (Teacher *)malloc(sizeof(Teacher)); if (p ==NULL) { return -1; } memset(p, 0, sizeof(Teacher)); p->age = 33; 轻松入门 实战应用 传智播客 C++课程 *myp = p; // return 0; } //指针的引用而已 int getTe2(Teacher* &myp) { myp = (Teacher *)malloc(sizeof(Teacher)); myp->age = 34; return 0; } void main333() { Teacher *p = NULL; //getTe(&p); getTe2(p); printf("age:%d \n", p->age); system("pause"); } 2 常引用 下面开始进入const引用难点 1 使用变量初始化 const 引用 思考cost int &a = b PK const int &a = 10; ????问题:const引用, 在 C++中可以声明 const 引用 const Type& name = var; const 引用让变量拥有只读属性 案例1: int main() { int a = 10; const int &b = a; //int *p = (int *)&b; b = 11; //err //*p = 11; //只能用指针来改变了 轻松入门 实战应用 传智播客 C++课程 cout<<"b--->"<m = m; this->n = n; return 0; } protected: private: }; int main() { int rv =0; Test21 t1; //无参构造函数的调用方法 Test21 t2; //t1.init(100, 200); //t2.init(300, 400); cout<a = a; this->b = b; this->c = c; printf("a:%d,b:%d,c:%d \n", a, b, c); printf("ABC construct ..\n"); } ~ABC() { printf("a:%d,b:%d,c:%d \n", a, b, c); printf("~ABC() ..\n"); } protected: private: int a; int b; int c; }; class MyD { public: MyD():abc1(1,2,3),abc2(4,5,6),m(100) //MyD() { cout<<"MyD()"<height; //输出该对象的 height 成员 cout<volume( ); //调用该对象的 volume 函数,计算并输出体积 C++还允许在执行 new 时,对新建立的对象进行初始化。如 Box *pt=new Box(12,15,18); 这种写法是把上面两个语句(定义指针变量和用 new 建立新对象)合并为一个语句,并指定初 值。这样更精炼。 新对象中的 height,width 和 length 分别获得初值 12,15,18。调用对象既可以通过对象 名,也可以通过指针。 在执行 new 运算时,如果内存量不足,无法开辟所需的内存空间,目前大多数 C++编译 系统都使 new 返回一个 0 指针值。只要检测返回值是否为 0,就可判断分配内存是否成功。 ANSI C++标准提出,在执行 new 出现故障时,就“抛出”一个“异常”,用户可根据异常进 行有关处理。但 C++标准仍然允许在出现 new 故障时返回 0 指针值。当前,不同的编译系统 对 new 故障的处理方法是不同的。 在不再需要使用由 new 建立的对象时,可以用 delete 运算符予以释放。如 delete pt; //释放 pt 指向的内存空间 这就撤销了 pt 指向的对象。此后程序不能再使用该对象。 如果用一个指针变量 pt 先后指向不同的动态对象,应注意指针变量的当前指向,以免删 错了对象。在执行 delete 运算符时,在释放内存空间之前,自动调用析构函数,完成有关 善后清理工作。 3 编程实践 //1 malloc free 函数 c 关键字 // new delete 操作符号 c++的关键字 //2 new 在堆上分配内存 delete //分配基础类型 、分配数组类型、分配对象 //3 new 和 malloc 深入分析 混用测试、异同比较 结论: malloc 不会调用类的构造函数 Free 不会调用类的析构函数 4 静态成员变量成员函数 思考:每个变量,拥有属性。有没有一些属性,归所有对象拥有? 轻松入门 实战应用 传智播客 C++课程 4.1 静态成员变量 1)定义静态成员变量  关键字 static 可以用于说明一个类的成员, 静态成员提供了一个同类对象的共享机制  把一个类的成员说明为 static 时,这个类无论有多少个对象被创建,这些对象共 享这个 static 成员  静态成员局部于类,它不是对象成员 例如: #include using namespace std; class counter { static int num ; //声明与定义静态数据成员 public : void setnum ( int i ) { num = i ; } //成员函数访问静态数据成员 void shownum() { cout << num << '\t' ; } } ; int counter :: num = 0 ;//声明与定义静态数据成员 void main () { counter a , b ; a.shownum() ; //调用成员函数访问私有静态数据成员 b.shownum() ; a.setnum(10) ; a.shownum() ; b.shownum() ; } 轻松入门 实战应用 传智播客 C++课程 从结果可以看出,访问的是同一个静态数据成员 2)使用静态成员变量 // 例 5-14 使用公有静态数据成员 #include class counter { public : counter (int a) { mem = a; } int mem; //公有数据成员 static int Smem ; //公有静态数据成员 } ; int counter :: Smem = 1 ; //初始值为 1 void main() { counter c(5); int i ; for( i = 0 ; i < 5 ; i ++ ) { counter::Smem += i ; cout << counter::Smem << '\t' ; //访问静态成员变量方法 2 } cout<a = this->a + t2.getA(); this->b = this->b + t2.getB(); return *this; //*操作让 this 指针回到元素状态 } Test add2(Test &t2) //*this //函数返回元素 { //t3 是局部变量 轻松入门 实战应用 传智播客 C++课程 Test t3(this->a+t2.getA(), this->b + t2.getB()) ; return t3; } 6 友元 6.1 友元函数 例如 轻松入门 实战应用 传智播客 C++课程 class A1 { public: A1() { a1 = 100; a2 = 200; } int getA1() { return this->a1; } //声明一个友元函数 friend void setA1(A1 *p, int a1); //这个函数是这个类的好朋友 protected: private: int a1; int a2; }; void setA1(A1 *p, int a1) { p->a1 = a1; } void main() { A1 mya1; cout< next = NULL ; if ( f == NULL ) f = r = p ; else { r -> next = p ; r = r -> next ; } //尾部指针下移或新结点变成尾部结点 } void sale( Goods * & f , Goods * & r ) { if ( f == NULL ) { cout << "No any goods!\n" ; return ; } Goods *q = f ; f = f -> next ; delete q ; cout << "saled.\n" ; } void main() { Goods * front = NULL , * rear = NULL ; int w ; int choice ; do { cout << "Please choice:\n" ; cout << "Key in 1 is purchase,\nKey in 2 is sale,\nKey in 0 is over.\n" ; cin >> choice ; switch ( choice ) // 操作选择 { case 1 : // 键入 1,购进 1 箱货物 { cout << "Input weight: " ; cin >> w ; purchase( front, rear, w ) ; // 从表尾插入 1 个结点 break ; } case 2 : // 键入 2,售出 1 箱货物 { sale( front, rear ) ; break ; } // 从表头删除 1 个结点 case 0 : break ; // 键入 0,结束 } cout << "Now total weight is:" << Goods::TotalWeight() << endl ; } while ( choice ) ; } 轻松入门 实战应用 传智播客 C++课程 2 数组类封装 目标:解决实际问题,训练构造函数、copy 构造函数等,为操作符重载做准备 数组类的测试 #include "iostream" #include "Array.h" using namespace std; int main() { Array a1(10); for(int i=0; i>“也是位移运算符(右移),但在输入操作中又是与流对象 cin 配 合使用的流提取运算符。这就是运算符重载(operator overloading)。C++系统对”<<“和”>>“进 行了重载,用户在不同的场合下使用它们时,作用是不同 的。对”<<“和”>>“的重载处理是 放在头文件 stream 中的。因此,如果要在程序中用”<< “和”>>”作流插入运算符和流提取运 算符,必须在本文件模块中包含头文件 stream(当然还应当包括”using namespace std“)。 现在要讨论的问题是:用户能否根据自己的需要对 C++已提供的运算符进行重载,赋予它们 新的含义,使之一名多用。? 轻松入门 实战应用 传智播客 C++课程 运算符重载入门技术推演 1 为什么会用运算符重载机制 用复数类举例 //Complex c3 = c1 + c2; //原因 Complex 是用户自定义类型,编译器根本不知道如何进行加减 //编译器给提供了一种机制,让用户自己去完成,自定义类型的加减操作。。。。。 //这个机制就是运算符重载机制 2 运算符重载的本质是一个函数 class Complex { public: int a; int b; friend Complex operator+(Complex &c1, Complex &c2); public: Complex(int a=0, int b=0) { this->a = a; this->b = b; } public: void printCom() { cout<a--; this->b--; return *this; } 轻松入门 实战应用 传智播客 C++课程 //4.2 调用方法 --c1; c1.printCom(); //4.3 前置—运算符重载函数名定义 //c1.operator--() 例如 5 //5.1 //后置++ 操作符 用全局函数实现 Complex operator++(Complex &c1, int) { Complex tmp = c1; c1.a++; c1.b++; return tmp; } //5.2 调用方法 c1 ++ ; //先使用 后++ //5.3 后置++运算符重载函数名定义 Complex operator++(Complex &c1, int) //函数占位参数 和 前置++ 相区别 例如 6 //6.1 后置— 操作符 用类成员函数实现 Complex operator--(int) { Complex tmp = *this; this->a--; this->b--; return tmp; } //6.2 调用方法 c1 ++ ; //先使用 后++ //6.3 后置--运算符重载函数名定义 Complex operator--(int) //函数占位参数 和 前置-- 相区别 前置和后置运算符总结 C++中通过一个占位参数来区分前置运算和后置运算 轻松入门 实战应用 传智播客 C++课程 定义运算符重载函数名的步骤 全局函数、类成员函数方法实现运算符重载步骤 1)要承认操作符重载是一个函数,写出函数名称 operator+ () 2)根据操作数,写出函数参数 3)根据业务,完善函数返回值(看函数是返回引用 还是指针 元素),及实现函数业务 友元函数实现操作符重载的应用场景 1)友元函数和成员函数选择方法  当无法修改左操作数的类时,使用全局函数进行重载  =, [], ()和->操作符只能通过成员函数进行重载 2)用友元函数 重载 << >>操作符  istream 和 ostream 是 C++ 的预定义流类  cin 是 istream 的对象,cout 是 ostream 的对象  运算符 << 由 ostream 重载为插入操作,用于输出基本类型数据  运算符 >> 由 istream 重载为提取操作,用于输入基本类型数据  用友员函数重载 << 和 >> ,输出和输入用户自定义的数据类型 a)用全局函数方法实现 << 操作符 ostream& operator<<(ostream &out, Complex &c1) { 轻松入门 实战应用 传智播客 C++课程 //out<<"12345,生活真是苦"< 4 )友元函数案例 vector 类 #include using namespace std; 轻松入门 实战应用 传智播客 C++课程 //为 vector 类重载流插入运算符和提取运算符 class vector { public : vector( int size =1 ) ; ~vector() ; int & operator[]( int i ) ; friend ostream & operator << ( ostream & output , vector & ) ; friend istream & operator >> ( istream & input, vector & ) ; private : int * v ; int len ; }; vector::vector( int size ) { if (size <= 0 || size > 100 ) { cout << "The size of " << size << " is null !\n" ; abort() ; } v = new int[ size ] ; len = size ; } vector :: ~vector() { delete[] v ; len = 0 ; } int &vector::operator[]( int i ) { if( i >=0 && i < len ) return v[ i ] ; cout << "The subscript " << i << " is outside !\n" ; abort() ; } ostream & operator << ( ostream & output, vector & ary ) { for(int i = 0 ; i < ary.len ; i ++ ) output << ary[ i ] << " " ; output << endl ; return output ; } istream & operator >> ( istream & input, vector & ary ) { 轻松入门 实战应用 传智播客 C++课程 for( int i = 0 ; i < ary.len ; i ++ ) input >> ary[ i ] ; return input ; } void main() { int k ; cout << "Input the length of vector A :\n" ; cin >> k ; vector A( k ) ; cout << "Input the elements of vector A :\n" ; cin >> A ; cout << "Output the elements of vector A :\n" ; cout << A ; system("pause"); } 8.4 运算符重载提高 1 运算符重载机制 C++编译器是如何支持操作符重载机制的? 2 重载赋值运算符=  赋值运算符重载用于对象数据的复制  operator= 必须重载为成员函数  重载函数原型为: 类型 & 类名 :: operator= ( const 类名 & ) ; 案例:完善 Name 类,支持=号操作。 轻松入门 实战应用 传智播客 C++课程 结论: 1 //先释放旧的内存 2 返回一个引用 3 =操作符 从右向左 //obj3 = obj1; // C++编译器提供的 等号操作 也属 浅拷贝 // obj4 = obj3 = obj1 //obj3.operator=(obj1) Name& operator=(Name &obj1) { //1 先释放obj3旧的内存 if (this->m_p != NULL) { delete[] m_p; m_len = 0; } //2 根据obj1分配内存大小 this->m_len = obj1.m_len; this->m_p = new char [m_len+1]; //3把obj1赋值给obj3 strcpy(m_p, obj1.m_p); return *this; } 轻松入门 实战应用 传智播客 C++课程 3 重载数组下表运算符[] 重载[]和()运算符  运算符 [] 和 () 是二元运算符  [] 和 () 只能用成员函数重载,不能用友元函数重载 重载下标运算符 [] [] 运算符用于访问数据对象的元素 重载格式 类型 类 :: operator[] ( 类型 ) ; 设 x 是类 X 的一个对象,则表达式 x [ y ] 可被解释为 x . operator [ ] ( y ) 轻松入门 实战应用 传智播客 C++课程 4 重载函数调用符 () () 运算符用于函数调用 重载格式 类型 类 :: operator() ( 表达式表 ) ; 例 1 设 x 是类 X 的一个对象,则表达式 x ( arg1, arg2, … ) 可被解释为 x . operator () (arg1, arg2, … ) 案例: //例 2:用重载()运算符实现数学函数的抽象 #include class F { public : double operator ( ) ( double x , double y ) ; } ; double F :: operator ( ) ( double x , double y ) { return x * x + y * y ; } void main ( ) { F f ; f.getA(); cout << f ( 5.2 , 2.5 ) << endl ; // f . operator() (5.2, 2.5) } 轻松入门 实战应用 传智播客 C++课程 比较普通成员函数 //例 3 用重载()运算符实现 pk 成员函数 #include class F { public : double memFun ( double x , double y ) ; } ; double F :: memFun ( double x , double y ) { return x * x + y * y ; } void main ( ) { F f ; cout << f.memFun ( 5.2 , 2.5 ) << endl ; } 5 为什么不要重载&&和||操作符 理论知识: 1)&&和||是 C++中非常特殊的操作符 2)&&和||内置实现了短路规则 3)操作符重载是靠函数重载来完成的 4)操作数作为函数参数传递 5)C++的函数参数都会被求值,无法实现短路规则 #include #include using namespace std; class Test { int i; public: Test(int i) { this->i = i; } Test operator+ (const Test& obj) { Test ret(0); cout<<"执行+号重载函数"<> 2 实现一个字符串类 构造函数要求 MyString a; MyString a(“dddd”); MyString b = a; 常用的操作符 << >> != == > < = //C 语言中 没有字符串这种类型,是通过数组来模拟字符串 //C++中 我们来设计一个字符串 以零结尾的字符串 class MyString { friend ostream& operator<<(ostream &out, const MyString &s); public: //构造和析构 MyString(int len = 0); MyString(const char *p); MyString(const MyString& obj); ~MyString(); public: //操作符重载 MyString& operator=(const char *p); MyString& operator=(const MyString& obj); char& operator[](int index) const; public: bool operator==(const char* p) const; bool operator!=(const char* p) const; bool operator==(const MyString& s) const; bool operator!=(const MyString& s) const; public: //string to c char *c_str(); const char* c_str() const; int length() 轻松入门 实战应用 传智播客 C++课程 { return m_len; } public: int operator<(const char *p); int operator>(const char *p); int operator<(const MyString &s); int operator>(const MyString &s); private: int m_len; char *m_p; }; 3 智能指针类编写 1 问题抛出 指针使用过程中,经常会出现内存泄漏和内存多次被释放常 2 解决方案:例如:boost 库的智能指针 项目开发中,要求开发者使用预先编写的智能指针类对象代替 C 语言中的原生指针 3 智能指针思想 工程中的智能指针是一个类模板 通过构造函数接管申请的内存 通过析构函数确保堆内存被及时释放 通过重载指针运算符* 和 -> 来模拟指针的行为 通过重载比较运算符 == 和 != 来模拟指针的比较 class Test { public: Test() { this->a = 10; } void printT() { cout<p = p; } ~MyTestPointer() { delete p; } Test* operator->() { return p; } Test& operator*() { return *p; } protected: Test *p; }; void main01_classp() { Test *p = new Test; p->printT(); delete p; MyTestPointer myp = new Test; //构造函数 myp->printT(); //重载操作符 -> }; 轻松入门 实战应用 传智播客 C++课程 class MyIntPointer { public: public: MyIntPointer() { p = NULL; } MyIntPointer(int* p) { this->p = p; } ~MyIntPointer() { delete p; } int* operator->() { return p; } int& operator*() { return *p; } protected: int *p; }; void main02_intp() { int *p = new int(100); cout<<*p<操作符只能通过成员函数进行重载 ++操作符通过一个 int 参数进行前置与后置的重载 C++中不要重载&&和||操作符 轻松入门 实战应用 传智播客 C++课程 3、继承和派生 3.1 继承概念 面向对象程序设计有 4 个主要特点:抽象、封装、继承和多态性。我们已经讲解了类和 对象,了解了面向对象程序设计的两个重要特征一数据抽象与封装,已经能够设计出基于对 象的程序,这是面向对象程序设计的基础。 要较好地进行面向对象程序设计,还必须了解面向对象程序设计另外两个重要特 征 ——继承性和多态性。本章主要介绍有关继承的知识,多态性将在后续章节中讲解。 继承性是面向对象程序设计最重要的特征,可以说,如果没有掌握继承性,就等于没有 掌握类和对象的精华,就是没有掌握面向对象程序设计的真谛。 3.1.1 类之间的关系 has-A,uses-A 和 is-A has-A 包含关系,用以描述一个类由多个“部件类”构成。实现 has-A 关系用类成员表 示,即一个类中的数据成员是另一种已经定义的类。 uses-A 一个类部分地使用另一个类。通过类之间成员函数的相互联系,定义友员或对 象参数传递实现。 is-A 机制称为“继承”。关系具有传递性,不具有对称性。 3.1.2 继承关系举例 万事万物中皆有继承,是重要的现象 两个案例:1)植物继承图;2)程序员继承图 轻松入门 实战应用 传智播客 C++课程 3.1.3 继承相关概念 3.1.4 派生类的定义 注意:C++中的继承方式(public、private、protected)会影响子类的对外访问属性。 轻松入门 实战应用 传智播客 C++课程 3.1.5 继承重要说明 1、子类拥有父类的所有成员变量和成员函数 4、子类可以拥有父类没有的方法和属性 2、子类就是一种特殊的父类 3、子类对象可以当作父类对象使用 3.2 派生类的访问控制 派生类继承了基类的全部成员变量和成员方法(除了构造和析构之外的成员方法),但 是这些成员的访问属性,在派生过程中是可以调整的。 3.2.1 单个类的访问控制 1、类成员访问级别(public、private、protected) 2、思考:类成员的访问级别只有 public 和 private 是否足够? 3.2.2 不同的继承方式会改变继承成员的访问属性 1)C++中的继承方式会影响子类的对外访问属性 public 继承:父类成员在子类中保持原有访问级别 private 继承:父类成员在子类中变为 private 成员 protected 继承:父类中 public 成员会变成 protected 父类中 protected 成员仍然为 protected 父类中 private 成员仍然为 private 2)private 成员在子类中依然存在,但是却无法访问到。不论种方式继承基类,派生类都不 能直接使用基类的私有成员 。 3)C++中子类对外访问属性表 父类成员访问级别 继 承 方 式 public proteced private public public proteced private proteced proteced proteced private private private private Private 4)继承中的访问控制 轻松入门 实战应用 传智播客 C++课程 3.2.3“三看”原则 C++中的继承方式(public、private、protected)会影响子类的对外访问属性 判断某一句话,能否被访问 1)看调用语句,这句话写在子类的内部、外部 2)看子类如何从父类继承(public、private、protected) 3)看父类中的访问级别(public、private、protected) 3.2.3 派生类类成员访问级别设置的原则 思考:如何恰当的使用 public,protected 和 private 为成员声明访问级别? 1、需要被外界访问的成员直接设置为 public 2、只能在当前类中访问的成员设置为 private 3、只能在当前类和子类中访问的成员设置为 protected,protected 成员的访问权限介于 public 和 private 之间。 3.2.4 综合训练 练习: public 继承不会改变父类对外访问属性; private 继承会改变父类对外访问属性为 private; protected 继承会部分改变父类对外访问属性。 结论:一般情况下 class B : public A 轻松入门 实战应用 传智播客 C++课程 //类的继承方式对子类对外访问属性影响 #include #include using namespace std; class A { private: int a; protected: int b; public: int c; A() { a = 0; b = 0; c = 0; } void set(int a, int b, int c) { this->a = a; this->b = b; this->c = c; } }; class B : public A { public: void print() { //cout<<"a = "< #include using namespace std; /* 子类对象可以当作父类对象使用 子类对象可以直接赋值给父类对象 子类对象可以直接初始化父类对象 父类指针可以直接指向子类对象 父类引用可以直接引用子类对象 */ //子类就是特殊的父类 class Parent03 { protected: const char* name; public: Parent03() { name = "Parent03"; } void print() { cout<<"Name: "<name = "Child2"; this->i = i; } }; int main() { Child03 child03(1000); //分别定义父类对象 父类指针 父类引用 child Parent03 parent = child03; Parent03* pp = &child03; Parent03& rp = child03; parent.print(); pp->print(); rp.print(); system("pause"); return 0; } 3.3.2 继承中的对象模型 类在 C++编译器的内部可以理解为结构体 子类是由父类成员叠加子类新成员得到的 轻松入门 实战应用 传智播客 C++课程 继承中构造和析构 轻松入门 实战应用 传智播客 C++课程 问题:如何初始化父类成员?父类与子类的构造函数有什么关系 在子类对象构造时,需要调用父类构造函数对其继承得来的成员进行初始化 在子类对象析构时,需要调用父类析构函数对其继承得来的成员进行清理 #include #include using namespace std; class Parent04 { public: Parent04(const char* s) { cout<<"Parent04()"<<" "< using namespace std; class Object { public: Object(const char* s) { cout<<"Object()"<<" "< static 函数也遵守 3 个访问原则 2> static 易犯错误(不但要初始化,更重要的显示的告诉编译器分配内存) 3> 构造函数默认为 private 轻松入门 实战应用 传智播客 C++课程 3.4 多继承 3.4.1 多继承的应用 多继承概念  一个类有多个直接基类的继承关系称为多继承  多继承声明语法 class 派生类名 : 访问控制 基类名 1 , 访问控制 基类名 2 , … , 访问控制 基类名 n { 数据成员和成员函数声明 };  类 C 可以根据访问控制同时继承类 A 和类 B 的成员,并添加 自己的成员 多继承的派生类构造和访问  多个基类的派生类构造函数可以用初始式调用基类构造函数初始化数据成员  执行顺序与单继承构造函数情况类似。多个直接基类构造函数执行顺序取决于定义 派生类时指定的各个继承基类的顺序。  一个派生类对象拥有多个直接或间接基类的成员。不同名成员访问不会出现二义性。 如果不同的基类有同名成员,派生类对象访问时应该加以识别。 多继承简单应用 轻松入门 实战应用 传智播客 C++课程 3.4.2 虚继承 如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明 的名字进行访问时,可能产生二义性 轻松入门 实战应用 传智播客 C++课程 分析: 总结:  如果一个派生类从多个基类派生,而这些基类又有一个共同 的基类,则在对该基类中声明的名字进行访问时,可能产生 二义性  如果在多条继承路径上有一个公共的基类,那么在继承路径的某处 汇合点,这个公共基类就会在派生类的对象中产生多个基类子对象 轻松入门 实战应用 传智播客 C++课程  要使这个公共基类在派生类中只产生一个子对象,必须对这个基类 声明为虚继承,使这个基类成为虚基类。  虚继承声明使用关键字 virtual 轻松入门 实战应用 传智播客 C++课程 实验:注意增加 virtual 关键字后,构造函数调用的次数。 3.5 继承总结  继承是面向对象程序设计实现软件重用的重要方法。程序员可以在已有基类的基础 上定义新的派生类。  单继承的派生类只有一个基类。多继承的派生类有多个基类。  派生类对基类成员的访问由继承方式和成员性质决定。  创建派生类对象时,先调用基类构造函数初始化派生类中的基类成员。调用析构函 数的次序和调用构造函数的次序相反。  C++提供虚继承机制,防止类继承关系中成员访问的二义性。  多继承提供了软件重用的强大功能,也增加了程序的复杂性。 4、多态 问题引出(赋值兼容性原则遇上函数重写) 面向对象新需求 C++提供的多态解决方案 多态案例 多态工程意义 面向对象三大概念、三种境界(封装、继承、多态) 多态成立条件 总结条件、看代码的时候要看出多态 4.1 多态 4.1.1 问题引出 如果子类定义了与父类中原型相同的函数会发生什么? 函数重写 在子类中定义与父类中原型相同的函数 函数重写只发生在父类与子类之间 class Parent { public: void print() { cout<<"Parent:print() do..."< using namespace std; class Parent { public: void print() { cout<<"Parent:print() do..."<print(); } void run00() { Child child; Parent* pp = &child; Parent& rp = child; //child.print(); //通过指针 //pp->print(); //通过引用 //rp.print(); howToPrint(&child); } int main01() { run00(); /* Child child; Parent *p = NULL; p = &child; child.print(); child.Parent::print(); */ system("pause"); return 0; } 轻松入门 实战应用 传智播客 C++课程 4.1.2 面向对象新需求 编译器的做法不是我们期望的 根据实际的对象类型来判断重写函数的调用 如果父类指针指向的是父类对象则调用父类中定义的函数 如果父类指针指向的是子类对象则调用子类中定义的重写函数 4.1.3 解决方案  C++中通过 virtual 关键字对多态进行支持  使用 virtual 声明的函数被重写后即可展现多态特性 4.1.4 多态实例 #include "iostream" using namespace std; class HeroFighter { public: public: virtual int ackPower() { return 10; } }; class AdvHeroFighter : public HeroFighter 轻松入门 实战应用 传智播客 C++课程 { public: virtual int ackPower() { return HeroFighter::ackPower()*2; } }; class enemyFighter { public: int destoryPower() { return 15; } }; //如果把这个结构放在动态库里面 //写了一个框架,可以调用 //我的第 3 代战机代码出现的时间晚于框架出现的时间。。。。 //框架 有使用后来人 写的代码的能力。。。 //面向对象 3 大概念 /* 封装 突破了 C 语言函数的概念。。 继承 代码复用 。。。。我复用原来写好的代码。。。 多态 多态可以使用未来。。。。。80 年代写了一个框架。。。。。。90 人写的代码 多态是我们软件行业追寻的一个目标。。。 //// */ // void objPK(HeroFighter *hf, enemyFighter *enemyF) { if (hf->ackPower() >enemyF->destoryPower()) { printf("英雄打败敌人。。。胜利\n"); } else { printf("英雄。。。牺牲\n"); 轻松入门 实战应用 传智播客 C++课程 } } void main() { HeroFighter hf; enemyFighter ef; objPK(&hf, &ef); AdvHeroFighter advhf; objPK(&advhf, &ef); system("pause"); } 4.1.5 多态工程意义 //面向对象 3 大概念 /* 封装 突破了 C 语言函数的概念。。 继承 代码复用 。。。。我复用原来写好的代码。。。 多态 多态可以使用未来。。。。。80 年代写了一个框架。。。。。。90 人写的代码 多态是我们软件行业追寻的一个目标。。。 //写了一个框架,可以调用后来人,写的代码的能力 //// */ 4.1.6 多态成立的条件 //间接赋值成立的 3 个条件 //1 定义两个变量。。。 //2 建立关联 。。。。 //3 *p //多态成立的三个条件 //1 要有继承 轻松入门 实战应用 传智播客 C++课程 //2 要有函数重写。。。C 虚函数 //3 要有父类指针(父类引用)指向子类对象 //多态是设计模式的基础,多态是框架的基础 4.1.7 多态的理论基础 01 静态联编和动态联编 1、联编是指一个程序模块、代码之间互相关联的过程。 2、静态联编(static binding),是程序的匹配、连接在编译阶段实现,也称为早期匹配。 重载函数使用静态联编。 3、动态联编是指程序联编推迟到运行时进行,所以又称为晚期联编(迟绑定)。 switch 语句和 if 语句是动态联编的例子。 4、理论联系实际 1、C++与 C 相同,是静态编译型语言 2、在编译时,编译器自动根据指针的类型判断指向的是一个什么样的对象;所以编译器认 为父类指针指向的是父类对象。 3、由于程序没有运行,所以不可能知道父类指针指向的具体是父类对象还是子类对象 从程序安全的角度,编译器假设父类指针只指向父类对象,因此编译的结果为调用父类的成 员函数。这种特性就是静态联编。 4.2 多态相关面试题 面试题 1:请谈谈你对多态的理解 多态的实现效果 多态:同样的调用语句有多种不同的表现形态; 多态实现的三个条件 有继承、有 virtual 重写、有父类指针(引用)指向子类对象。 多态的 C++实现 virtual 关键字,告诉编译器这个函数要支持多态;不是根据指针类型判断如何调用;而 是要根据指针所指向的实际对象类型来判断如何调用 多态的理论基础 动态联编 PK 静态联编。根据实际的对象类型来判断重写函数的调用。 多态的重要意义 设计模式的基础 是框架的基石。 实现多态的理论基础 函数指针做函数参数 C 函数指针是 C++至高无上的荣耀。C 函数指针一般有两种用法(正、反)。 多态原理探究 与面试官展开讨论 轻松入门 实战应用 传智播客 C++课程 面试题 2:谈谈 C++编译器是如何实现多态 c++编译器多态实现原理 面试题 3:谈谈你对重写,重载理解 函数重载 必须在同一个类中进行 子类无法重载父类的函数,父类同名函数将被名称覆盖 重载是在编译期间根据参数类型和个数决定函数调用 函数重写 必须发生于父类与子类之间 并且父类与子类中的函数必须有完全相同的原型 使用 virtual 声明之后能够产生多态(如果不使用 virtual,那叫重定义) 多态是在运行期间根据具体对象的类型决定函数调用 #include #include using namespace std; class Parent01 { public: Parent01() { cout<<"Parent01:printf()..do"<func(1, 2); } int main() { Parent01 p; p.func(); p.func(1); p.func(1, 2); Child01 c; //c.func(); //问题 1 c.Parent01::func(); c.func(1, 2); run01(&p); run01(&c); system("pause"); return 0; } 轻松入门 实战应用 传智播客 C++课程 //问题 1:child 对象继承父类对象的 func,请问这句话能运行吗?why //c.func(); //因为名称覆盖,C++编译器不会去父类中寻找 0 个参数的 func 函数,只会在子类 中找 func 函数。 //1 子类里面的 func 无法重载父类里面的 func //2 当父类和子类有相同的函数名、变量名出现,发生名称覆盖(子类的函数名,覆盖了父 类的函数名。) //3//c.Parent::func(); //问题 2 子类的两个 func 和父类里的三个 func 函数是什么关系? 面试题 4:是否可类的每个成员函数都声明为虚函数,为什 么。 c++编译器多态实现原理 面试题 5:构造函数中调用虚函数能实现多态吗?为什么? c++编译器多态实现原理 面试题 6:虚函数表指针(VPTR)被编译器初始化的过程, 你是如何理解的? c++编译器多态实现原理 面试题 7:父类的构造函数中调用虚函数,能发生多态吗? c++编译器多态实现原理 面试题 8:为什么要定义虚析构函数? 在什么情况下应当声明虚函数  构造函数不能是虚函数。建立一个派生类对象时,必须从类层次的根开始,沿着继 承路径逐个调用基类的构造函数  析构函数可以是虚的。虚析构函数用于指引 delete 运算符正确析构动态对象 轻松入门 实战应用 传智播客 C++课程 轻松入门 实战应用 传智播客 C++课程 其他 父类指针和子类指针的步长 1) 铁律 1:指针也只一种数据类型,C++类对象的指针 p++/--,仍然可用。 2) 指针运算是按照指针所指的类型进行的。 p++《=》p=p+1 //p = (unsigned int)basep + sizeof(*p) 步长。 3) 结论:父类 p++与子类 p++步长不同;不要混搭,不要用父类指针++方式操作数组。 4.3 多态原理探究 理论知识:  当类中声明虚函数时,编译器会在类中生成一个虚函数表  虚函数表是一个存储类成员函数指针的数据结构  虚函数表是由编译器自动生成与维护的  virtual 成员函数会被编译器放入虚函数表中  当存在虚函数时,每个对象中都有一个指向虚函数表的指针(C++编译器给父类对 象、子类对象提前布局 vptr 指针;当进行 howToPrint(Parent *base)函数是,C++编 译器不需要区分子类对象或者父类对象,只需要再 base 指针中,找 vptr 指针即可。)  VPTR 一般作为类对象的第一个成员 4.3.1 多态的实现原理 C++中多态的实现原理 当类中声明虚函数时,编译器会在类中生成一个虚函数表 虚函数表是一个存储类成员函数指针的数据结构 虚函数表是由编译器自动生成与维护的 virtual 成员函数会被编译器放入虚函数表中 存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr 指针) 轻松入门 实战应用 传智播客 C++课程 说明 1: 通过虚函数表指针 VPTR 调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能 确定真正应该调用的函数。而普通成员函数是在编译时就确定了调用的函数。在效率上,虚 函数的效率要低很多。 说明 2: 出于效率考虑,没有必要将所有成员函数都声明为虚函数 说明 3 :C++编译器,执行 HowToPrint 函数,不需要区分是子类对象还是父类对象 轻松入门 实战应用 传智播客 C++课程 4.3.2 如何证明 vptr 指针的存在 #include using namespace std; class A { public: void printf() { cout<<"aaa"<add(7, 8)<add(7, 8)<
还剩149页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

cgs6654820

贡献于2017-06-06

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf