【C++】面向对象技术与方法笔记

〇、C++编程基础

0.1标识符的作用域和对象的生存期

标识符(identifier) :用来标识变量、函数、类、模块,或任何其他用户自定义项目的名称。

  • 标识符的作用域:标识符在程序正文中的有效范围。
  • 对象的生存期:在对象创建和删除之间所经历的时间。

0.1.1局部对象

  • 作用域:从对象的定义点开始,到程序块结束为止。
  • 生存期:与作用域同在。
  • 静态局部对象:使一个对象在整个程序运行过程中都存在,同时又不像全局对象那样存在安全隐患。
#include <iostream>
using namespace std;

void func() {
    static int i = 3; // 在func()函数内定义静态对象i
    i = i + 1;
    cout << i;
}

int main() {
    func(); // 4
    //cout << i; // 错误!此处不可以使用i
    cout << ",";
    func(); // 5
    
    return 0;
}

运行结果:

4,5
  •  第二次调用时,不再对i初始化,因为静态对象i已经存在。 

0.1.2全局对象

在函数、类、名字空间之外定义的对象。

  • 作用域:在程序运行期间起作用。在局部作用域内,同名的局部对象将屏蔽全局对象,若想在局部作用域内使用全局对象,应利用作用域运算符“::”。
  • 要使在一个编译文件中定义的全局对象在其他编译单元中也可用,需要在其他编译单元中先进行extern说明。
  • 若想使全局对象只在定义它的编译单元中起作用,则可以声明它为static,这时在其他编译单元中即使使用extern声明,仍然不能使用该对象。
  • 生存期:与程序的整个运行过程同在。

0.2类型转换 

0.2.1基本原则 

  • 子类->父类:自动类型转换
  • 父类->子类:需使用强制类型转换,否则报错
  • 可以将基类指针指向派生类对象
  • 不可以将派生类指针指向基类对象 

0.2.2四种强制类型转换

static_cast:

用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用static_cast,但它不能用于两个不相关的类型进行转换,如:int和int*。

reinterpret_cast:

为操作数的位模式提供较低层次的重新解释,用于将一种类型转换为另一种不同的类型,如:int和int*。

const_cast:

常用于删除变量的const属性,方便赋值。

dynamic_cast:

用于将一个父类对象的指针转换为子类对象的指针或引用(动态转换)。

  • 向上转换:子类对象指针->父类指针/引用(不需要转换,赋值兼容规则)
  • 向下转换:父类对象指针->子类指针/引用(用dynamic_cast转换是安全的)

注意:

  • 只能用于含有虚函数的类
  • 会先检查是否能转换成功,能成功则转换,不能则返回0。 

 explicit:

阻止经过转换构造函数进行的隐式转换的发生。

0.3cout输出规则

#include<iostream>
using namespace std;

int main() {
	int a = 2;
	int b = a + 1;
	cout << a / b << endl;
	
	return 0;
} 

运行结果:

0
#include<iostream>
using namespace std;

int main() {
	double a = 2;
	double b = a + 1;
	cout << a / b << endl;
	
	return 0;
} 

运行结果:

0.666667

一、程序的内存模型

1.1内存分区模型

  • 代码区:存放函数体的二进制代码,由操作系统进行管理
  • 全局区:存放全局变量和静态变量以及常量
  • 栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
  • 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
  • 1.2代码区

  • 存放cpu执行的机器指令
  • 代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
  • 代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令

1.3全局区

  • 全局变量和静态变量存放在此
  • 全局区还包含了常量区,字符串常量和其他常量也存放在此
  • 该区域的数据在程序结束后由操作系统释放
#include <iostream>
using namespace std;

// 全局变量
int g_a = 10;
int g_b = 10;

// const修饰的全局变量,全局常量
const int c_g_a = 10;
const int c_g_b = 10;

int main() {
    // 全局区
    // 全局变量、静态变量、常量
    
    // 创建普通局部变量
    int a = 10;
    int b = 10;

    cout << "局部变量a的地址为:" << (int*)&a << endl; // 0xf93a5ff9dc
    cout << "局部变量b的地址为:" << (int*)&b << endl; // 0xf93a5ff9d8

    cout << "全局变量a的地址为:" << (int*)&g_a << endl; // 0x7ff74c774010
    cout << "全局变量b的地址为:" << (int*)&g_b << endl; // 0x7ff74c774014

    static int s_a = 10;
    static int s_b = 10;
    cout << "静态变量s_a的地址为:" << (int*)&s_a << endl; // 0x7ff74c774018
    cout << "静态变量s_b的地址为:" << (int*)&s_b << endl; // 0x7ff74c77401c

    // 常量
    // 字符串常量
    cout << "字符串常量的地址为:" << (int*)&"hello world" << endl; // 0x7ff74c7750df

    // const修饰的变量
    // const修饰的全局变量
    // const修饰的局部变量
    cout << "全局变量c_g_a的地址为:" << (int*)&c_g_a << endl; // 0x7ff74c775000
    cout << "全局变量c_g_b的地址为:" << (int*)&c_g_b << endl; // 0x7ff74c775004

    int c_l_a = 10;
    int c_l_b = 10;

    cout << "局部变量c_l_a的地址为:" << (int*)&c_l_a << endl; // 0xf93a5ff9d4
    cout << "局部变量c_l_b的地址为:" << (int*)&c_l_b << endl; // 0xf93a5ff9d0
    return 0;
}

运行结果:

局部变量a的地址为:0x70fe1c
局部变量b的地址为:0x70fe18
全局变量a的地址为:0x472010
全局变量b的地址为:0x472014
静态变量s_a的地址为:0x472018
静态变量s_b的地址为:0x47201c
字符串常量的地址为:0x488091
全局变量c_g_a的地址为:0x488100
全局变量c_g_b的地址为:0x488104
局部变量c_l_a的地址为:0x70fe14
局部变量c_l_b的地址为:0x70fe10

1.4栈区

  • 由编译器自动分配释放,存放函数的参数值,局部变量等
  • 注意:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
#include <iostream>
using namespace std;

// 在栈区开辟数据
//int* func(int b) { // 形参数据也会放在栈区
    //int a = 10; // 局部变量
    //return &a; // 返回局部变量的地址,函数内部定义的变量在函数结束时被释放掉,所以返回是找不到的
//}

int main() {
    //int *p = func(1); // 接收func函数的返回值
    //cout << *p << endl; // 第一次可以打印正确的数字,是因为编译器做了保留
    //cout << *p << endl; // 第二次这个数据就不再保留了

    return 0;
}

1.5堆区

  • 由程序员分配释放,若程序员不释放,程序结束时有操作系统回收
  • 在C++中主要利用new在堆区开辟内存
#include <iostream>
using namespace std;

// 在堆区开辟数据
int* func() {
    // 利用new关键字,可以将数据开辟到堆区
    // 指针本质也是局部变量,放在栈上,指针保存的数据是放在堆区
    int* a = new int(10);
    return a;
}

int main() {
    int *p = func();

    cout << *p << endl;
    cout << *p << endl;

    return 0;
}

运行结果:

10
10

1.6new运算符

  • C++中利用new操作符在堆区开辟数据
  • 堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符delete
  • 语法:new 数据类型
  • 利用new创建的数据,会返回该数据对应的类型的指针
#include <iostream>
using namespace std;

// new的基本语法
int *func() {
    // 在堆区创建整型数据
    // new返回的是该数据类型的指针
    int *p = new int(10);
    return p;
}

// 在堆区开辟数组
void test2() {
    // 在堆区创建10整型数据的数组
    int *arr = new int[10];

    for (int i = 0; i < 10; i++) {
        arr[i] = i + 100; // 给10个元素赋值100-109
    }

    for (int i = 0; i < 10; i++) {
        cout << arr[i] << endl;
    }
    // 释放堆区数组
    // 释放数组的时候,要加[]才可以
    delete[] arr;
}

void test1() {
    int *p = func();
    cout << *p << endl;
    cout << *p << endl;

    // 如果想释放堆区数据,利用关键字delete
    delete p;
    // cout << *p << endl; // 内存已经被释放,再次访问就是非法操作,会报错
}

int main() {
    test1();
    test2();

    return 0;
}

运行结果:

10
10
100
101
102
103
104
105
106
107
108
109
  •  利用构造函数new一个新对象
#include <iostream>
using namespace std;
 
class Goods {
public:
    Goods(int weight) {
        Weight = weight;
        totalWeight += weight;
    }
    ~Goods() {
        totalWeight -= Weight;
    }
    int getWeight() {
        return Weight;
    }
    static int gettotalWeight() { // 静态成员函数不可以访问非静态成员变量
        return totalWeight;
    }
private:
    int Weight; // 单箱重量
    static int totalWeight; // 总重量
};
int Goods::totalWeight = 0; // 静态成员变量类外初始化
 
int main() {
    int weight;
    weight = 10;
    Goods *goods1 = new Goods(weight); // 利用构造函数new一个新对象
 
    weight = 20;
    Goods *goods2 = new Goods(weight); // 利用构造函数new一个新对象
 
    cout << Goods::gettotalWeight() << endl;
 
    delete goods2;
    cout << Goods::gettotalWeight() << endl;
    delete goods1;
    cout << Goods::gettotalWeight() << endl;
 
    return 0;
}

运行结果:

30
10
0

二、C++中的引用

2.0指针

指针的定义:数据类型* 指针名 = 初始地址;

int* pa = &a;

指针数组:

int* p[2] = {&p1, &p2};

 常量指针:

int* const pa = &a;
  • 常量指针必须初始化
  • 指针的值(指向)不能改变
  • 如果指的是变量,则可以通过该指针修改所指变量的值 

2.1引用的基本语法

  • 作用:给变量起别名
  • 语法:数据类型 &别名 = 原名
int& n = m;
#include <iostream>
using namespace std;


int main() {
    int a = 10;
    // 创建引用
    int &b = a;

    cout << "a = " << a << endl;
    cout << "b = " << b << endl;

    b = 100;
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    
    return 0;
}

运行结果:

a = 10
b = 10
a = 100
b = 100

2.2引用的注意事项

  • 引用必须初始化
  • 引用在初始化后不能再被重新声明为另一变量的引用,例如:int a=4,b=4; int &c = a; int &c=b; 错误
  • 而重新对引用对象赋值可以
// C++中的引用——引用的注意事项

#include <iostream>
using namespace std;

// 1.引用必须初始化
// 2.引用在初始化后,不可以改变

int main() {
    // 引用必须初始化
    int a = 10;
    //int &b; // 错误
    int &b = a;

    // 引用在初始化后,不可以改变
    // b已经为a的别名,就不能是c的别名了
    int c = 20;
    b = c; // 重新对引用对象赋值
    cout << "a = " << a << endl; // 20
    cout << "b = " << b << endl; // 20
    cout << "c = " << c << endl; // 20

    return 0;
}

运行结果:

a = 20
b = 20
c = 20

2.3引用做函数参数

  • 作用:函数传参时,可以利用引用让形参修饰实参
  • 优点:可以简化指针修改实参
#include <iostream>
using namespace std;

// 1.值传递
// 形参不会修饰实参
void mySwap1(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
}

// 2.地址传递
// 形参会修饰实参
void mySwap2(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 3.引用传递
// 形参会修饰实参
void mySwap3(int &a, int &b) {
    int temp = a;
    a = b;
    b = temp;
}

int main() {
    int a = 10;
    int b = 20;

    //mySwap1(a, b); // ab值未交换
    //mySwap2(&a, &b); // ab值交换
    mySwap3(a, b); // ab值交换
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;

    return 0;
}

运行结果:

a = 20
b = 10

2.4引用做函数返回值

  • 不要返回局部变量引用
  • 函数调用作为左值
#include <iostream>
using namespace std;

// 1.不要返回局部变量引用
// int& test1() {
//     int a = 10; // 局部变量存放在栈区
//     return a;
// }

// 2. 函数调用作为左值
int& test2() {
    static int a = 10; // 静态变量,存放在全局区,全局区上的数据在程序结束后系统释放

    return a;
}

int main() {
    // int &ref = test1();
    // cout << "ref = " << ref << endl; // 第一次结果正确,是因为编译器做了保留
    // cout << "ref = " << ref << endl; // 第二次结果错误,因为a的内存已经被释放

    int &ref2 = test2();
    cout << "ref2 = " << ref2 << endl;
    cout << "ref2 = " << ref2 << endl;

    test2() = 1000; // 如果函数的返回值是引用,这个函数调用可以作为左值
    cout << "ref2 = " << ref2 << endl;
    cout << "ref2 = " << ref2 << endl;

    return 0;
}

运行结果:

ref2 = 10
ref2 = 10
ref2 = 1000
ref2 = 1000

2.5引用的本质

  • 本质: 在C++内部实现是一个指针常量
#include <iostream>
using namespace std;
 
// 发现是引用,转换为int* const ref = &a;
void func(int& ref) {
    ref = 100; // ref是引用,转换为*ref = 100;
}
 
int main() {
    int a = 10;
 
    // 自动转换为int* const ref = &a; 指针常量是指针指向不可改,也说明为什么引用不可改
    int& ref = a;
    ref = 20; // 内部发现ref是引用,自动帮我们转换为:*ref = 20;
 
    cout << "a = " << a << endl;
    cout << "ref = " << ref << endl;
 
    func(a);
    return 0;
}

运行结果:

a = 20
ref = 20

2.6常量引用

  • 作用:常量引用主要用来修饰形参,防止误操作
  • 在函数形参列表中,可以加const修饰形参,防止形参改变实参
#include <iostream>
using namespace std;

// 打印数据函数
void showValue(const int &val) {
    //val = 1000; // val不可修改
    cout << "val = " << val << endl;
}
int main() {
    int a = 10;
    // 加上const之后,编译器将代码修改 int temp = 10; const int &ref = temp;
    const int & ref = 10; // 引用必须引一块合法的内存空间
    //ref = 20; // 加入const之后变为只读,不可修改

    a = 100;
    showValue(a);
    cout << "a = " << a << endl;
    
    return 0;
}

运行结果:

val = 100
a = 100

三、函数高级

3.1函数的默认参数

  • 如果某个位置参数有默认值,那么从这个位置往后,从左到右,必须都要有默认值
  • 如果函数声明有默认值,函数实现的时候就不能有默认参数,否则有歧义
#include <iostream>
using namespace std;
 
int func(int a, int b = 10, int c = 10) {
    return a + b + c;
}
 
int func2(int a = 10, int b = 10);
int func2(int a, int b) {
    return a + b;
}
 
int main() {
    cout << func(20, 20) << endl; // 50 
    cout << func(100) << endl; // 120
    
    cout << func2() << endl; // 20
    cout << func2(20) << endl; // 30
    cout << func2(20, 20) << endl; // 40
    
    return 0;
}

运行结果:

50
120
20
30
40

 3.2函数占位参数

#include <iostream>
using namespace std;

// 占位参数也可以有默认参数
void func(int a, int) {
    cout << "this is func" << endl;
}

int main() {
    func(10, 10); // 占位参数必须填补

    return 0;
}

运行结果:

this is func

3.3函数重载

3.3.1基本语法

函数重载满足条件:

  • 同一个作用域下
  • 函数名称相同
  • 函数参数类型不同或个数不同或顺序不同
  • 注意函数的返回值不可以作为函数重载的条件
#include <iostream>
using namespace std;

// 函数重载需要函数都在同一个作用域下
void func() {
    cout << "func 的调用" << endl;
}
void func(int a) {
    cout << "func(int a) 的调用" << endl;
}
void func(int a, double b) {
    cout << "func(int a, double b) 的调用" << endl;
}

// 函数返回值不可以作为函数重载的条件
// int func(double a, int b) {
//    cout << "func(int a, double b) 的调用" << endl;
//}

int main() {
    func();
    func(10);
    func(10, 3.14);
    
    return 0;
}

运行结果:

func 的调用
func(int a) 的调用
func(int a, double b) 的调用

3.3.2注意事项

  • int &a = 1;报错,引用需要一个合法的内存空间

  • const int & a = 1;正确,类似于int temp = 1; const int & a = temp;

  • 函数重载碰到默认参数可能产生歧义,需要避免
#include <iostream>
using namespace std;

// 函数重载注意事项:
// 1. 引用作为重载条件
void func(int &a) { // int &a = 10 错误
    cout << "func(int &a)的函数调用" << endl;
}

void func(const int &a) { // const意味着值不可改变
    cout << "func(const int &a)的函数调用" << endl;
}

// 2. 函数重载碰到函数默认参数
void func2(int a, int b = 10) {
    cout << "func2(int a, int b = 10)" << endl;
}

int main() {
    int a = 10;
    func(a); // 调用无const
    func(10); // 调用有const

    // func2(10); // 碰到默认参数产生歧义,需要避免

    return 0;
}

运行结果:

func(int &a)的函数调用
func(const int &a)的函数调用

3.3.3重载、重写和重定义的区别

  • 注意需要与动态绑定区分,若对象为基类普通对象则按下述进行判断,若对象为基类指针,则按动态绑定进行判断。

重载:

  • 相同的范围(在同一个类中)
  • 函数名字相同
  • 参数不同
  • virtual关键字可有可无

重写(覆盖):派生类函数覆盖基类函数

  • 不同的范围,分别位于基类和派生类中
  • 函数的名字相同
  • 参数相同
  • 基类函数必须有virtual关键字

重定义(隐藏):派生类的函数屏蔽了与其同名的基类函数

  • 如果派生类的函数和基类的函数同名,但是参数不同,此时不管有无virtual,基类的函数被隐藏。
  • 如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字,此时基类的函数被隐藏。

3.4函数参数的调用方式

值调用和引用调用的区别:

#include<iostream>
using namespace std;

int f(int a){
	return ++a;
}
int g(int& a){
	return ++a;
}
int main(){
	int m=0,n=0;
	m += f(g(m));
	n += f(f(n));
	cout << "m = " << m << endl;
	cout << "n = " << n << endl;

	return 0;
} 

运行结果:

m = 3
n = 2
  •  g(m)在计算a++时,也将m++,所以m最终值为2+1
#include<iostream>
using namespace std;

class A {
public:
	A() {
		a = 5;
	}
	void printa() {
		cout << "A::a = " << a << endl;
	}
private:
	int a;
	friend class B;
}; 

class B {
public:
	void display1(A t) {
		t.a+=2;
		cout << "display:a = " << t.a << endl;
	}  
	void display2(A t) {
		t.a-=1;
		cout << "display2:a = " << t.a << endl;
	}
	
};

int main() {
	A obj1;
	B obj2;
	
	obj2.display1(obj1);
	obj2.display2(obj1);
	obj1.printa();
	
	
	return 0;
}

运行结果:

display:a = 7
display2:a = 4
A::a = 5
  •  值调用,改变的是形参(拷贝对象)的值,而不改变函数实参的值

3.5函数信息返回方式

3.5.1值返回

当执行return语句时,先计算表达式的值,然后把该值赋给系统生成的匿名临时对象,通过该对象把数值带回函数的调用点。

double add(double x, double y) {
    return (x + y);
}

3.5.2指针返回

 不需要产生匿名的临时对象,而直接将对象的地址返回调用处,这样对占内存较多的大对象进行操作时就可以节省时空消耗。

double *returnmax(double *x, double *y) {
    return (*x > *y) ? x : y;
}

3.5.3引用返回

比指针返回更直接。函数返回引用,使得函数调用也可以作为左值。

#include <iostream>
using namespace std;

double &returnmax(double &x, double &y) {
    return (x > y) ? x : y;
}

int main() {
    double a = 2.5, b = 5.6;
    returnmax(a, b) = 3.3; // b = 3.3
    cout << "b = " << b << endl;
    
    return 0;
}

运行结果:

b = 3.3

3.6函数指针

每个函数模块对应一个起始地址,称为函数的入口地址。函数名就代表一个函数的入口地址。也可以用一个指向函数的指针来存放函数的入口地址,并通过该指针调用函数。

double add(double x, double y) { // 函数定义
    return x + y;
}
double (*fp)(double x, double y) = add; // 函数指针定义,初始化指向add()

一个函数(或函数指针)可以作为另一个函数的参数。匹配时参数的名字并不重要。

#include <iostream>
using namespace std;

// 函数定义
int f1(int, int) {
    return 3;
}
int f2(int, int) {
    return 8;
}

int func(int n, int g(int, int)) {
// 或 int func(int n, int (*p)(int, int))
    return n * g(0, 0);
}

int main() {
    cout << func(2, f1) << ","; // 传递f1,结果为6
    cout << func(2, f2) << endl; // 传递f2,结果为16
    
    return 0;
}

运行结果:

6,16

3.7static函数

如果只允许在一个编译单元内使用某个函数,那么可以在函数定义的开始加上static关键字,这样该函数在其他编译单元就不能被调用。

3.8inline函数

为了提高程序的运行效率,可以将功能简单、代码较短、使用频率高的函数,声明为内嵌函数,编译时用内嵌函数体的代码代替函数调用语句,这样就省去了函数调用的开销。

#include <iostream>
using namespace std;

inline double add(double x, double y) {
    return x + y;
}

int main() {
    double a = 2.2, b = 5.6;
    cout << add(a, b) << endl;
    
    return 0;
}

运行结果:

7.8
  • inline函数一般适用于只有几行的小程序
  • inline函数的定义(而不是声明)应出现在被调用之前,并且在调用该函数的每个文本文件中都要进行定义
  • inline函数在编译(而不是运行)时将该函数的目标代码插入到每个调用该函数的地方
  • inline只表示一种要求,编译器并非一定将inline修饰的函数做内嵌处理
  • 类体内定义的函数即使不带inline关键字,也是inline函数。因为成员函数可以在类体内定义,也可以在类体外定义,所以成员函数不一定都是内联函数
  • 使用inline函数可以节省运行时间,但程序的目标代码量会增加
  • inline函数与带参数的宏具有相似的功能,但后者存在安全问题,不建议用 

四、类和对象

4.1封装

4.1.1属性和行为作为整体

  • 意义:将属性和行为作为一个整体,表现生活中的事物
#include <iostream>
using namespace std;

// 设计一个圆类,求圆的周长
class Circle {
    public:
        int r;

        double calculateZC() {
            return 2 * 3.14 * r;
        }
};

int main() {
    // 实例化:通过一个类,创建一个对象的过程
    Circle c1;
    // 给圆对象的属性进行赋值
    c1.r = 10;
    cout << "圆的周长为:" << c1.calculateZC() << endl;

    return 0;
}

运行结果:

圆的周长为:62.8

4.1.2设计学生类

设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号。

  • 成员:类中的属性和行为
  • 属性:成员属性,成员变量
  • 行为:成员函数,成员方法
#include <iostream>
#include <string>

using namespace std;

// 学生类
class Student {
    public:
        // 属性
        string Name;
        int Id;

        // 行为
        // 显示姓名和学号
        void showStudent() {
            cout << "姓名:" << Name << " 学号:" << Id << endl;
        }
        // 给姓名赋值
        void setName(string name) {
            Name = name;
        }
        // 给学号赋值
        void setId(int id) {
            Id = id;
        }
};

int main() {
    // 实例化
    Student s1;
    Student s2;

    // 属性赋值
    s1.Name = "张三";
    s1.Id = 1;
    s1.showStudent();

    s2.setName("李四");
    s2.setId(2);
    s2.showStudent();

    return 0;
}

运行结果:

姓名:张三 学号:1
姓名:李四 学号:2

4.1.3C++中struct和class的区别

struct和class的唯一区别:默认的访问权限不同

  • struct默认权限为公共
  • class默认权限为私有
#include <iostream>
using namespace std;

class C1 {
    int A; // 默认为私有权限
};
struct C2 {
    int A; // 默认为公共权限
};

int main() {
    C1 c1;
    //c1.A = 100;
    C2 c2;
    c2.A = 100;
    
    return 0;
}

4.1.4成员属性私有化

优点:

  • 将所有成员属性设置为私有,可以自己控制读写权限
  • 对于写权限,我们可以检测数据的有效性
#include <iostream>
#include <string>
using namespace std;

class Person {
    // 公共接口
    public:
        // 设置姓名
        void setName(string name) {
            Name = name;
        }
        // 获取姓名
        string getName() {
            return Name;
        }
        // 设置年龄
        void setAge(int age) {
            Age = age;
            if (age < 0 || age > 150) {
                cout << "年龄错误" << endl;
                return;
            }
        }
        // 获取年龄
        int getAge() {
            return Age;
        }
        // 设置情人
        void setLover(string lover) {
            Lover = lover;
        }

    private:
        string Name; // 需设置为可读可写
        int Age; // 需设置为可读可写,如果想修改,年龄范围必须是0-150之间
        string Lover; // 需设置为只写
};

int main() {
    Person p;
    p.setName("张三");
    cout << "姓名为:" << p.getName() << endl;

    p.setAge(180);
    cout << "年龄为:" << p.getAge() << endl;

    p.setLover("苍井");
    //cout << "情人为:" << p.getLover() << endl;

    return 0;
}

运行结果:

姓名为:张三
年龄错误
年龄为:180

4.1.5设计案例1——立方体类

  • 创建立方体类
  • 设计属性
  • 设计行为,求出立方体的面积和体积
  • 分别用全局函数和成员函数,判断两个立方体是否相等
#include <iostream>
#include <string>
using namespace std;

class Cube {
    public:
        // 设置长
        void setL(int l) {
            L = l;
        }
        // 获取长
        int getL() {
            return L;
        }
        // 设置宽
        void setW(int w) {
            W = w;
        }
        // 获取宽
        int getW() {
            return W;
        }
        // 设置高
        void setH(int h) {
            H = h;
        }
        // 获取高
        int getH() {
            return H;
        }

        // 获取立方体面积
        int calculateS() {
            return 2 * L * W + 2 * W * H + 2 * L * H;
        }
        // 获取立方体体积
        int calculateV() {
            return L * W * H;
        }

        // 利用成员函数判断两个立方体是否相等
        bool isSameByClass(Cube &c) { // 传1个参数
            if (L == c.getL() && W == c.getW() && H == c.getH())
                return true;
            else 
                return false;
        }

    private:
        int L; // 长
        int W; // 宽
        int H; // 高
};

// 利用全局函数判断两个立方体是否相等
bool isSame(Cube &c1, Cube &c2) { // 传2个参数
    if (c1.getL() == c2.getL() && c1.getW() == c2.getW() && c1.getH() == c2.getH())
        return true;
    else 
        return false;
}


int main() {
    Cube c1;
    c1.setL(10);
    c1.setW(10);
    c1.setH(10);
    cout << "c1的面积为:" << c1.calculateS() << endl;
    cout << "c1的体积为:" << c1.calculateV() << endl;

    Cube c2;
    c2.setL(20);
    c2.setW(10);
    c2.setH(10);

    bool ret = isSame(c1, c2);
    if (ret)
        cout << "全局函数判断:c1和c2是相等的" << endl;
    else
        cout << "全局函数判断:c1和c2是不相等的" << endl;

    ret = c1.isSameByClass(c2);
    if (ret)
        cout << "成员函数判断:c1和c2是相等的" << endl;
    else
        cout << "成员函数判断:c1和c2是不相等的" << endl;

    return 0;
}

运行结果:

c1的面积为:600
c1的体积为:1000
全局函数判断:c1和c2是不相等的
成员函数判断:c1和c2是不相等的

4.1.6设计案例2——点和圆的位置关系

  • 创建立方体类
  • 设计属性
  • 设计行为,求出立方体的面积和体积
  • 分别用全局函数和成员函数,判断两个立方体是否相等
#include <iostream>
#include <string>
using namespace std;

// 点类
class Point {
    public:
        // 设置x坐标
        void setX(int x) {
            X = x;
        }
        // 获取x坐标
        int getX() {
            return X;
        }
        // 设置y坐标
        void setY(int y) {
            Y = y;
        }
        // 获取y坐标
        int getY() {
            return Y;
        }

    private:
        int X;
        int Y;
};

class Circle {
    public:
        // 设置半径
        void setR(int r) {
            R = r;
        }
        // 获取半径
        int getR() {
            return R;
        }
        // 设置圆心
        void setCenter(Point center) {
            Center = center;
        }
        // 获取圆心
        Point getCenter() {
            return Center;
        }

    private:
        int R;

        // 在类中可以让另一个类作为本来类中的成员
        Point Center; // 圆心
};

// 判断点和圆的关系
void isInCircle(Circle &c, Point p) {
    int pdis;
    int rdis;

    // 计算两点之间距离的平方
    pdis = (c.getCenter().getX() - p.getX()) * (c.getCenter().getX() - p.getX()) - (c.getCenter().getY() - p.getY()) * (c.getCenter().getY() - p.getY());
    // 计算半径的平方
    rdis = c.getR() * c.getR();

    // 判断关系
    if (pdis == rdis) {
        cout << "点在圆上" << endl;
    }
    else if (pdis > rdis) {
        cout << "点在圆外" << endl;
    }
    else {
        cout << "点在圆内" << endl;
    }
}

int main() {
    Circle c;
    c.setR(10);

    Point center;
    center.setX(10);
    center.setY(10);
    c.setCenter(center);

    Point p;
    p.setX(10);
    p.setY(10);

    // 判断关系
    isInCircle(c, p);

    return 0;
}

运行结果:

点在圆内

4.2对象特性

4.2.1构造函数调用规则

默认情况下,C++编译器至少给一个类添加3个函数

  • 默认构造函数(无参,函数体为空)
  • 默认析构函数(无参,函数体为空)
  • 默认拷贝函数,对属性进行值拷贝

构造函数调用规则如下:

  • 如果用户定义有参构造函数,C++不再提供默认无参构造函数,但是会提供默认拷贝构造函数
  • 如果用户定义拷贝构造函数,C++不会再提供其他构造函数
  • 当创建含多个元素的数组时,每创建一个对象,都会调用构造函数
#include <iostream>
#include <string>
using namespace std;

class Person {
    public:
        Person() {
            cout << "Person的默认构造函数调用" << endl;
        }
        Person(int age) {
            cout << "Person的默认构造函数调用" << endl;
            Age = age;
        }
        Person(const Person &p) {
            cout << "Person的拷贝构造函数调用" << endl;
            Age = p.Age;
        }
        ~Person() {
            cout << "Person的析构函数调用" << endl;
        }

        int Age;
};

void test1() {
    Person p;
    p.Age = 18;

    Person p2(p); // 编译器提供拷贝构造函数中的值拷贝语句
    cout << "p2的年龄为:" << p2.Age << endl;
}

void test2() {
    Person p(28); // 编译器不提供无参构造函数
    Person p2(p); // 编译器提供拷贝构造函数
    cout << "p2的年龄为:" << p2.Age << endl;
}

int main() {
    test1();
    test2();

    return 0;
}

运行结果:

Person的默认构造函数调用
Person的拷贝构造函数调用
p2的年龄为:18
Person的析构函数调用
Person的析构函数调用
Person的默认构造函数调用
Person的拷贝构造函数调用
p2的年龄为:28
Person的析构函数调用
Person的析构函数调用
  • char数组的构造函数
#include <iostream>
#include <cstring>
using namespace std;
 
class Person {
public:
    void SetData(char* name, char* id, bool sex = 1) {
        int len = strlen(name); // strlen要用char*
        strncpy(Name, name, len); // char数组要用strncpy进行拷贝
        Name[len] = '\0'; // 要将最后一位置为'\0'
 
        len = strlen(id);
        strncpy(Id, id, len);
        Id[len] = '\0';
 
        Sex = sex;
    }
    void Output() {
        cout << "Name:" << Name << endl;
        cout << "Id:" << Id << endl;
        const char *str = Sex ? "Male" : "Female"; // 注意这里用const char*
        cout << "Sex:" << str << endl;
 
    }
private:
    char Name[20];
    char Id[20];
    bool Sex;
};
 
class Student: public Person {
public:
    void InputScore(double score1, double score2, double score3) {
        Score[0] = score1;
        Score[1] = score2;
        Score[2] = score3;
    }
    void Print() {
        Output();
        for (int i = 0; i < 3; i++) {
            cout << "Score" << i + 1 << ":" << Score[i] << endl;
        }
    }
private:
    double Score[3];
};
 
class Teacher: public Person {
public:
    void Inputage(double age) {
        Age = age;
    }
    void Print() {
        Output();
        cout << "age:" << Age << endl;
    }
private:
    double Age;
};
 
int main() {
    Student stu;
    Teacher tea;
 
    stu.SetData((char *)"张三", (char *)"21020211");
    stu.InputScore(90, 80, 85);
    stu.Print();
    cout << endl;
 
    tea.SetData((char *)"李四", (char *)"001");
    tea.Inputage(12);
    tea.Print();
 
    return 0;
}

运行结果:

Name:张三
Id:21020211
Sex:Male
Score1:90
Score2:80
Score3:85

Name:李四
Id:001
Sex:Male
age:12

4.2.2深拷贝与浅拷贝

  • 浅拷贝:简单的赋值拷贝操作
  • 深拷贝:在堆区重新申请空间,进行拷贝操作 
#include <iostream>
#include <string>
using namespace std;

class Person {
    public:
        Person() {
            cout << "Person的默认构造函数调用" << endl;
        }
        Person(int age, int height) {
            Age = age;
            Height = new int(height); // 将数据创建在堆区
            cout << "Person的有参构造函数调用" << endl;
        }

        // 自己实现拷贝构造函数,解决浅拷贝带来的问题(p1p2分别指向两个内存空间)
        Person(const Person &p) {
            cout << "Person拷贝构造函数调用" << endl;
            Age = p.Age;
            //Height = p.Height; // 编译器默认实现的是这行代码
            // 深拷贝操作
            Height = new int(*p.Height); 
        }
        
        // 析构代码,将堆区开辟数据做释放操作
        ~Person() {
            // 防止对p2p1重复释放同一内存空间
            if (Height != NULL) {
                delete Height;
                Height = NULL;
            }
            cout << "Person的析构函数调用" << endl;
        }
        int Age;
        int *Height;
};

void test1() {
    Person p1(18, 160);
    cout << "p1的年龄为:" << p1.Age << " 身高为:" << *p1.Height<< endl;
    Person p2(p1);
    cout << "p2的年龄为:" << p2.Age << " 身高为:" << *p2.Height << endl; // 编译器提供拷贝构造函数,进行浅拷贝

}

int main() {
    test1();

    return 0;
}

运行结果:

Person的有参构造函数调用
p1的年龄为:18 身高为:160
Person拷贝构造函数调用
p2的年龄为:18 身高为:160
Person的析构函数调用
Person的析构函数调用

4.2.3初始化列表

  • 语法:构造函数():属性1(值1),属性2(值2)...
#include <iostream>
#include <string>
using namespace std;

class Person {
    public:
        // 传统初始化操作
        //Person(int a, int b, intc) {
        //    A = a;
        //    B = b;
        //    C = c;
        //}

        // 初始化列表
        Person(int a, int b, int c):A(a),B(b),C(c) {

        }

        // 初始化列表属性
        int A;
        int B;
        int C;
};

void test1() {
    //Person(10, 20, 30);
    Person p(30, 20, 10);
    cout << "A = " << p.A << endl;
    cout << "B = " << p.B << endl;
    cout << "C = " << p.C << endl;
}

int main() {
    test1();

    return 0;
}

运行结果:

A = 30
B = 20
C = 10

4.2.4类对象作为类成员(组合类)

  • 当其他类对象作为本类成员,构造时候先构造类对象,再构造自身,析构的顺序与构造相反
#include <iostream>
#include <string>
using namespace std;

// C++类中的成员可以是另一个类的对象,称该成员为对象成员
// class A {}
// class B {
//    A a;
//}

class Phone {
    public:
        Phone(string pName) {
            PName = pName;
            cout << "Phone的构造函数调用" << endl; // 先
        }
        
        ~Phone() {
            cout << "Phone的析构函数调用" << endl; // 后
        }
        string PName; // 手机品牌名称
};

class Person {
    public:
        string Name;
        Phone Phonee; // 对象成员

         // Phone Phonee = pName; 隐式转换法
        Person(string name, string pName): Name(name), Phonee(pName) {
            cout << "Person的构造函数调用" << endl; // 后
        }
        ~Person() {
            cout << "Person的析构函数调用" << endl; // 先
        }

};

void test1() {
    Person p("张三", "苹果");
    cout << p.Name << "拿着" << p.Phonee.PName << endl; // 先经过了人的类还是手机的类
}

int main() {
    test1();
    
    return 0;
}

 运行结果:

Phone的构造函数调用
Person的构造函数调用
张三拿着苹果
Person的析构函数调用
Phone的析构函数调用
#include <iostream>
using namespace std;

class A {
public:
    A(int i) {
        a = i;
        cout << "A::Constructor." << a << endl;
    }
    void print() const{
        cout << a << ", ";
    }
    ~A() {
        cout << "A::Destructor." << a << endl;
    }
private:
    int a;
};

// 组合类的定义
class B {
public:
    B(int i, int j, int k): oa2(i), oa1(j) {
        b = k;
        cout << "B::Constructor." << b << endl;
    }
    void print() const {
        oa1.print();
        oa2.print();
        cout << b << endl;
    }
    ~B() {
        cout << "B::Destructor." << b << endl;
    }
private:
    // 构造函数顺序与该顺序有关
    A oa1, oa2; // 声明了两个A类的数据成员
    int b;
};

int main() {
    B ob(6, 7, 8); // 建立B类的对象ob
    ob.print();

    return 0;
}

 运行结果:

A::Constructor.7
A::Constructor.6
B::Constructor.8
7, 6, 8
B::Destructor.8
A::Destructor.6
A::Destructor.7

4.2.5静态成员——静态成员函数

  • 静态成员:在成员变量和成员函数前加上关键字static
  • 如果定义n个同类的对象,那么每个对象都分别拥有自己的一套成员,不同对象的数据成员各自有值,互不相关。有时我们希望某些数据成员在同类的多个对象之间可以共享,这时可以将它们定义为静态数据成员。
  • 静态数据成员是类的所有对象共享的数据

假设用一个int型变量count来记录点(CPoint类对象)的个数,每增加一个点,count都要+1。如果将count定义为普通数据成员,那么当点的个数发生变化时,每个点对象的count值都要做出修改,这显然不合适。这时可以把count声明为static的,被所有的CPoint类对象共享。

静态数据成员类外初始化格式:

数据类型 类名::静态数据成员名 = 初值;
class CPoint {
public:
    CPoint(int x = 0, int y = 0) {
        X = x;
        Y = y;
    }
private:
    int X, Y; // 数据成员
    static int count; // 静态数据成员
};

int CPoint::count = 0; // 类外初始化count,注意这里无static关键字

静态成员变量:

  • 所有对象共享同一份数据
  • 在编译阶段分配内存
  • 类内声明,类外初始化
  • 在类外调用public静态成员时,可以用“类名::”进行限制,或通过类对象访问

如果某个类想使用一个静态成员变量统计其存在对象的个数,不用处理的是( D )

A 构造函数        B 拷贝构造函数        C 析构函数        D 重载赋值运算符 

静态成员函数:

  • 所有对象共享同一个函数
  • 静态成员函数只能访问静态成员变量
  • 如果在类外定义静态成员函数,static关键字只在类内声明时需要,类外定义时不需要,但要用“类名::”进行限制。
  • 静态成员函数没有this指针
  • 在类外调用public静态成员函数时,可以用“类名::”进行限制,或通过类对象访问。
#include <iostream>
#include <string>
using namespace std;

class Person {
    public:
        // 静态成员函数
        static void func() {
            A = 100; // 静态成员函数可以访问静态成员变量
            //B = 200; // 静态成员函数不可以访问非静态成员变量
            cout << "static void func调用" << endl;
        }
        static int A; // 静态成员变量
        int B;

    private:
        // 静态成员函数也有访问权限
        static void func2() {
            cout << "func2()的调用";
        }
};

// 有两种访问方式
void test1() {
    // 1.通过对象访问
    Person p;
    p.func();

    // 2.通过类名访问
    Person::func();

    //Person::func2(); // 私有权限访问不到
}

int main() {
    test1();

    return 0;
}

4.2.6成员变量和成员函数分开储存

  • 类内的成员变量和成员函数分开存储
  • 只有非静态成员变量才处于类的对象上

空对象:

  • 空对象占用内存空间为1
  • C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
  • 每个空对象也应该有一个独一无二的内存地址
#include <iostream>
#include <string>
using namespace std;

class Person {
    int A; // 非静态成员变量,属于类的对象上

    static int B; // 静态成员变量,不属于类的对象上

    void func() {}; // 非静态成员函数,不属于类的对象上

    static void func2() {}; // 静态成员函数,不属于类的对象上
};

int Person::B = 0; 

void test1() {
    Person p; // 空对象
    cout << "size of p = " << sizeof(p) << endl; // 4
}

void test2() {
    Person p;
    cout << "size of p = " << sizeof(p) << endl; // 4
}

int main() {
    test1();
    test2();

    return 0;
}

4.2.7this指针的用途

this指针:

  • 指向被调用的成员函数所属的对象
  • 是隐含每一个非静态成员函数内的一种指针
  • 不需要定义,直接使用即可

this指针的用途:

  • 解决名称冲突:当形参和成员函数变量同名时,可用this指针来区分
  • 返回对象本身用*this
#include <iostream>
#include <string>
using namespace std;

class Person {
    public: 
    Person(int age) {
        this->age = age;
    }
    // 1.解决名称冲突:当形参和成员函数变量同名时,可用this指针来区分
    Person& PersonAddAge(Person &p) { // 注意此处要以引用方式返回,否则每次返回新对象,结果为20
        this->age += p.age;
        // this指向p2的指针,而*this指向p2的本体
        return *this;
    }
    int age;
};

void test1() {
    Person p1(18);
    cout << "p1的年龄为:" << p1.age << endl;
}

// 2.返回对象本身用*this
void test2() {
    Person p1(10);
    Person p2(10);
    
    // 链式编程思想
    p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
    cout << "p2的年龄为:" << p2.age << endl;
}

int main() {
    test1();
    test2();

    return 0;
}

4.2.8空指针访问成员函数

  • C++中空指针也可以调用成员函数,但要注意有没有用到空指针
  • 如果用到this指针,需要加以判断保证代码的健壮性
#include <iostream>
#include <string>
using namespace std;

class Person {
    public:
        void showClassName() {
            cout << "this is Person class" << endl;
        }

        void showPersonAge() {
            // 报错原因是传入的指针为空
            // 加入空指针判断
            if (this == NULL) {
                return;
            }
            cout << "age = " << this->Age << endl;
        }
        int Age;
};

void test1() {
    Person *p = NULL; // 空指针

    p->showClassName();
    p->showPersonAge();
}

int main() {
    test1();

    return 0;
}

4.2.9常成员——常成员函数

 常对象:

  • 声明对象前加const称该对象为常对象
  • 常对象只能调用常函数
  • 定义常量时必须初始化,常对象在类外初始化,前面有const关键字
  • 常对象在构造函数内在初始化列表中进行。另外,如果类中包含引用型数据成员和其他类类型的数据成员,其初始化工作也必须在构造函数的成员初始化列表中进行。普通成员的初始化工作既可以在列表中,也可以在构造函数体内进行。数据成员在初始化列表中的初始化顺序与他们在类中的声明顺序有关,而与它们在初始化列表中给出的顺序无关。
  • 对于同一类的多个对象,它们的初值可以不同,这些对象的const数据成员的值也就可以不同
  • const数据成员的值一旦被初始化后就不能再修改了
class A {
public:
    A(int i): a(i), b(a), c1(a) {}
private:
    int a; // 一般数据成员
    int& b; // 引用型数据成员
    const int c1; // 常数据成员
    static const int c2; // 静态常数据成员
};
const int A::c2 = 8;

常函数:

  • 成员函数后加const
  • 非成员函数不能用const修饰
  • 常函数内不可以修改成员属性
  • 成员属性声明时加关键字mutable后,在常函数中依然可以修改
  • 在const成员函数内不能调用非const成员函数,但在非const成员函数内可以调用const成员函数
  • 若声明一个类对象为const对象,则只能调用该对象的公有const函数,而不能调用非const成员函数
#include <iostream>
using namespace std;
 
class Person {
    public:
        // this指针的本质是指针常量,指针的指向是不可以被修改的
        // const Person * const this;
        // 第一个const意味着this指针的值也不可以被修改,相当于下面的const;第二个const意味着指针的指向不可以被修改
        // 常函数
        void showPerson() const{
            //this->A = 100; // this指针的值不可以被修改
            //this = NULL; // this指针的指向不可以被修改
            this->B = 100;
        }
 
        // 非常函数
        void func() {
 
        }
        Person() {}
 
        int A;
        mutable int B; // 特殊变量,即使在常函数中,也可以修改这个值
};
 
void test1() {
    Person p1;
    p1.showPerson();
}
 
void test2() {
    const Person p2; // 在对象前加const,变为常对象 // 此处添加构造函数
    Person p3;
    //p2.A = 100; // p.A的值在常对象下不可以被修改
    p2.B = 100; // p.B的值在常对象下也可以被修改
 
    // 常对象只能调用常函数
    p2.showPerson();
    // 非常对象也可以调用常函数 
    p3.showPerson();
    //p2.func(); // 常对象不能调用普通成员函数(非常函数),因为普通成员函数可以修改属性
 
}
 
int main() {
    test1();
    test2();
    return 0;
}

4.2.10拷贝构造函数

用已有的类初始化新新创建的类对象。

Cpoint obj2(obj1);

 为了保证不修改被引用的对象,通常把引用参数声明为const。

CPoint::Cpoint(const CPoint &ref) {
    X = ref.X;
    Y = ref.Y;
}

拷贝构造函数的三种被调用情况:

#include <iostream>
using namespace std;

class CPoint {
public:
    CPoint(int x = 0, int y = 0) {
        X = x;
        Y = y;
    }
    CPoint(const CPoint &ref) {
        X = ref.X;
        Y = ref.Y;
    }
    int getX() {
        return X;
    }
    int getY() {
        return Y;
    }
    void setPoint(int x, int y) {
        X = x;
        Y = y;
    }
private:
    int X, Y;
};

CPoint func(CPoint d) {
    d.setPoint(7, 8);
    return d;
}

int main() {
    CPoint d1(4, 5); // 建立对象d1,调用普通构造函数
    CPoint d2(d1); // 建立对象d2,调用拷贝构造函数,用d1初始化d2
    d1 = func(d2); // 调用函数func(),调用两次拷贝构造函数
    
    return 0;
}

运行结果:

Constructor. // 创建对象d1(调用普通构造函数)
Cpy_constructor. // 创建对象d2(调用拷贝构造函数)
Cpy_constructor. // 创建对象d(调用拷贝构造函数)
Cpy_constructor. // 创建匿名对象(调用拷贝构造函数),赋值给d1

执行到语句"d1 = func(d2)"时,首先调用函数func(),在参数传递时建立了参数对象d,即执行了"CPoint d(d2);",这时调用的是拷贝构造函数;在函数返回d时,建立了一个CPoint型匿名对象,该对象被初始化为d的值;执行函数func()的右边括号后,释放对象d。然后调用赋值运算符成员函数(简称赋值函数,本例中由编译器自动生成),将匿名对象的值赋给d1,再释放匿名对象。

4.2.11转换构造函数

如果构造函数具有一个参数,或者从第二个参数开始都带有默认值,那么该构造函数就可以将第一个参数的类型自动转换为当前的类类型。

假设在CPoint类中,定义了以下构造函数:

CPoint::CPoint(int r) {
    X = r;
    Y = 0;
}

那么下列语句:

CPoint(5);

 的功能是:调用转换构造函数将5转换为CPoint类型。

4.3友元

4.3.1全局函数做友元

友元:

  • 目的:让一个函数或者类,访问另一个类(含friend)中私有成员
  • 关键字:friend
  • 三种实现之一:全局函数做友元
  • 友元关系不能继承
  • 友元可以提高程序的运行效率

友元函数:

  • 友元函数不是类的成员函数,没有this指针
  • 友元函数破坏了类的封装性和隐藏性
#include <iostream>
#include <string>
using namespace std;

// 1.全局函数做友元
class Building {
    // goodGay全局函数是Building的好朋友,可以访问Building中的私有成员
    friend void goodGay(Building *building);

    public:
        Building() {
            SittingRoom = "客厅";
            BedRoom = "卧室";
        }
        string SittingRoom; // 客厅
    private:
        string BedRoom; // 卧室
};

// 全局函数
void goodGay(Building *building) {
    cout << "好基友的全局函数正在访问:" << building->SittingRoom << endl;
    cout << "好基友的全局函数正在访问:" << building->BedRoom << endl;
}

void test1() {
    Building building ;
    goodGay(&building);
}

int main() {
    test1();
}

4.3.2友元类

  • 三种实现之二:类做友元
#include <iostream>
#include <string>
using namespace std;

// 2.类做友元
class Building;
class goodGay {
    public:
        goodGay();

        void visit(); // 参观函数,访问Building中的属性

        Building * building;
};

class Building {
    // goodGay类是本类的好朋友,可以访问本类中的私有成员
    friend class goodGay;

    public:
        Building();

        string SittingRoom; // 客厅
    private:
        string BedRoom; // 卧室
};
// 类外写成员函数
Building::Building() {
    SittingRoom = "客厅";
    BedRoom = "卧室";
}

goodGay::goodGay() {
    building = new Building;
}

void goodGay::visit() {
    cout << "好基友类正在访问:" << building->SittingRoom << endl;
    cout << "好基友类正在访问:" << building->BedRoom << endl;
}

void test1() {
    goodGay gg;
    gg.visit();
}

int main() {
    test1();

    return 0;
}

4.3.3成员函数做友元

  • 三种实现之三:成员函数做友元
#include <iostream>
#include <string>
using namespace std;

// 3.成员函数做友元
class Building;
class goodGay {
    public:
        goodGay();

        void visit1(); // 让visit1函数可以访问Building中的私有成员
        void visit2(); // 让visit2函数不可以访问Building中的私有成员

        Building * building;
};

class Building {
    // 告诉编译器,goodGay类中的visit成员是Building的好朋友,可以访问私有内容
    friend void goodGay::visit1();
    public:
        Building();
        string SittingRoom; // 客厅

    private:
        string BedRoom; // 卧室

};

// 类外实现成员函数
Building::Building() {
    this->SittingRoom = "客厅";
    this->BedRoom = "卧室";
}

goodGay::goodGay() {
    building = new Building;
}

void goodGay::visit1() {
    cout << "好基友类正在访问:" << building->SittingRoom << endl;
    cout << "好基友类正在访问:" << building->BedRoom << endl;
}

void goodGay::visit2() {
    cout << "好基友类正在访问:" << building->SittingRoom << endl;
    //cout << "好基友类正在访问:" << building->BedRoom << endl;
}

void test1() {
    goodGay gg;
    gg.visit1();
    gg.visit2();
}

int main() {
    test1();

    return 0;
}

4.3.4同一个函数做多个类的友元

#include <iostream>
using namespace std;

// 提前使用声明
class Student;
class Teacher {
public:
    Teacher(int i) {
        number = i;
    }
    friend int TotalNumber(Teacher& t, Student& s); // 多个类的友元
private:
    int number;
};

class Student {
public:
    Student(int i) {
        number = i;
    }
    friend int TotalNumber(Teacher& t, Student& s);
private:
    int number;
};

int TotalNumber(Teacher& t, Student &s) {
    return t.number + s.number;
}

int main() {
    Teacher n1(100);
    Student n2(600);
    cout << "The total number: " << TotalNumber(n1, n2) << endl;
    
    return 0;
}

4.3.5声明其他类的成员函数为友元函数

#include <iostream>
using namespace std;

// 声明提前使用CPoint
class CPoint;
class Display {
public:
    void show(CPoint &t); // Display::show()成员函数声明
};

class CPoint {
public:
    CPoint(int x = 0, int y = 0) {
        X = x;
        Y = y;
    }
    friend void Display::show(CPoint &t); // 声明Display:show()为CPoint类的友元
private:
    int X, Y;
};

void Display::show(CPoint& t) {
    cout << t.X << ", " << t.Y << endl;
}

int main() {
    CPoint d(4, 5);
    Display op;
    op.show(d);

    return 0;
}

4.4C++运算符重载

4.4.0运算符重载

  • 作用:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
  • 重载的两种方式:重载为类的成员函数,重载为类的友元函数
  • 不能重载的运算符: . (成员访问运算符) ,* (成员指针访问运算符) ,:: (域运算符) ,sizeof (长度运算法) ,? : (条件运算符)

重载为类的成员函数:

class Complex {
public:
    Complex(double r = 0, double i = 0);
    const Complex operator-(const Complex& c); // 二元减运算符
    const Complex operator-(); // 一元取负运算符
};

重载为类的友元函数:

class Complex {
public:
    Complex(double r = 0, double i = 0);
    friend const Complex operator-(const Complex& c, const Complex& c); // 二元减运算符
    friend const Complex operator-(const Complex& c); // 一元取负运算符
};
运算符重载方式
一元运算符建议重载为成员函数
= () [] -> ->*必须重载为成员函数
复合赋值,如+= -= *= 、/= %= ^= &= |= >>= <<=建议重载为成员函数
其他二元运算符建议重载为友元函数

4.4.1+运算符重载

  • 作用:实现两个自定义函数类型相加的运算
  • 注意:
  1. 对于内置的数据类型的表达式的运算符是不可能改变的
  2. 不要滥用运算符重载
#include <iostream>
#include <string>
using namespace std;

class Person {
    public:
        int A;
        int B;

        // 成员函数重载+号
        //Person operator+(Person &p) {
        //    Person temp;
        //    temp.A = this->A + p.A;
        //    temp.B = this->B + p.B;
        //    return temp;
        //}
};

// 全局函数重载+号
Person operator+(Person &p1, Person &p2) {
    Person temp;
    temp.A = p1.A + p2.A;
    temp.B = p1.B + p2.B;
    return temp;
}

Person operator+(Person &p1, int num) {
    Person temp;
    temp.A = p1.A + num;
    temp.B = p1.B + num;
    return temp;
}


void test1() {
    Person p1;
    p1.A = 10;
    p1.B = 10;

    Person p2;
    p2.A = 10;
    p2.B = 10;

    // 成员函数重载本质调用
    // Person p3 = p1.operator+(p2)
    // 全局函数重载本质调用
    // Person p3 = operator+(p1, p2)
    Person p3 = p1 + p2;
    Person p4 = p1 + 100;

    cout << "p3.A = " << p3.A << endl;
    cout << "p3.B = " << p3.B << endl;
    cout << "p4.A = " << p4.A << endl;
    cout << "p4.B = " << p4.B << endl;

}

int main() {
    test1();
    return 0;
}

4.4.2>>、<<运算符重载

输出运算符重载:

  • 作用:可以输出自定义数据类型
#include <iostream>
#include <string>
using namespace std;

class Person {
    friend ostream& operator<<(ostream &cout, Person &p);
    
    public:
        Person(int a, int b) {
            A = a;
            B = b;
        }
    
    private:
        int A;
        int B;

        // 成员函数重载<<号
        // p.operator<<(cout) 简化版本p<<cout
        // 不会利用成员函数重载<<运算符,因为无法实现cout在左侧
        //void operator<<(cout) {};

};

// 只能利用全局函数重载左移运算符
// 链式,必须有返回值
ostream& operator<<(ostream &cout, Person &p) { // 本质:operator << (cout, p) 简化版本cout << p
    cout << "A = " << p.A << " B = " << p.B;
    return cout;
}

void test1() {
    Person p(10, 10);
    //p.A = 10;
    //p.B = 10;

    cout << p << "hello world" << endl;
}

int main() {
    test1();
    return 0;
}

输入输出运算符重载: 

#include <iostream>
using namespace std;

class Complex {
public:
    Complex() {};
    friend ostream& operator<<(ostream& ost, const Complex& c); // 重载<<为友元
    friend istream& operator>>(istream& ist, Complex& c); // 重载>>为友元
private:
    double real, image;
};
ostream& operator<<(ostream& ost, const Complex& c) {
    ost << "(" << c.real << ", " << c.image << ")" << endl;
    return ost;
}

istream& operator>>(istream& ist, Complex& c) { // 无const
    cout << "请输入复数,如(2.5, 3.5):" << endl;
    char ch; // 注意用ch接收字符!
    ist >> ch >> c.real >> ch >> c.image >> ch; // ist为istream类对象
    return ist;
}

int main() {
    Complex a;
    cin >> a; // 即调用函数operator>>(cin, a);从标准流对象cin中提取a
    cout << a; // 即调用函数operator<<(cout, a);向标准流对象cout中插入a

    return 0;
}

使用模板时:

#include <iostream>
using namespace std;

template<class T>
class MyArray {
private:
	T num;
	T *a;
public:
	MyArray(T n) {
		num = n;
		a = new T[n];
	}
	~MyArray() {
		num = 0;
		delete[] a;
	}
	T& operator[] (int n) { // 注意这里为引用返回 
		return a[n];
	}
	// 更换模板 
	template<class U>
	friend ostream& operator<<(ostream& out, MyArray<U>& ma); 
};

template<class U>
ostream& operator<<(ostream& out, MyArray<U>& ma) {
	for (int i = 0; i < ma.num; i++) {
		out << ma.a[i] << endl;
	}
	return out;
}

int main() {
	MyArray<int> intArray(10);
	for (int i = 0; i < 10; i++) {
		intArray[i] = i * i;
	}
	cout << intArray << endl;
	
	return 0;
}

4.4.3++运算符重载

  • 作用:通过重载递增运算符,实现自己的整型数据
#include <iostream>
#include <string>
using namespace std;

class MyInteger {
    friend ostream& operator<<(ostream &cout, MyInteger myint);
    public:
        MyInteger() {
            Num = 0;
        }

        // 重载前置++运算符
        MyInteger& operator++() { // 返回引用是为了一直对一个数据进行递增操作
            // 先进行++运算
            Num++;
            // 再将自身做返回
            return *this;
        }

        // 重载后置++运算符
        // void operator++(int) int代表占位参数,可以用于区分前置和后置递增
        MyInteger operator++(int) { // 返回值:局部对象在当前函数执行后就被释放,返回局部对象的引用是非法操作
            // 先记录当时结果
            MyInteger temp = *this;
            // 后递增
            Num++;
            // 最后将记录结果做返回
            return temp;
        }

    private:
        int Num;
};

// 成员函数重载<<号
ostream& operator<<(ostream &cout, MyInteger myint) { // 本质:operator << (cout, p) 简化版本cout << p
    cout << myint.Num;
    return cout;
}

void test1() {
    MyInteger myint;
    cout << ++myint << endl;
}

void test2() {
    MyInteger myint;

    cout << myint++ << endl;
    cout << myint << endl;
}

int main() {
    //test1();
    test2();

    return 0;
}

4.4.4=运算符重载

#include <iostream>
#include <string>
using namespace std;

// 赋值运算符重载
class Person {
    public:
        Person(int age) {
            Age = new int(age); // 申请堆区空间
        }

        ~Person() {
            if (Age != NULL) {
                delete Age;
                Age = NULL;
            }
        }

        Person& operator=(const Person &p) {
            // 编译器提供浅拷贝
            // Age = p.Age;

            // P2已有堆区内存
            // 应该先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝
            if (Age != NULL) {
                delete Age;
                Age = NULL;
            }

            // 深拷贝
            Age = new int(*p.Age);

            // 返回对象本身
            return *this;
        }
        
        int *Age;
};

void test1() {
    Person p1(18);
    Person p2(20);
    Person p3(30);

    p3 = p2 = p1; // 赋值操作

    cout << "p1的年龄为:" << *p1.Age << endl;
    cout << "p2的年龄为:" << *p2.Age << endl;
    cout << "p3的年龄为:" << *p3.Age << endl;
}

void test2() {
    int a = 10;
    int b = 20;
    int c = 30;

    c = b = a; // c = b = a = 10
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    cout << "c = " << c << endl;
}

int main() {
    test1();
    //test2();

    return 0;
}
  • 赋值函数的返回类型和形参类型是类类型的引用,为了保证不修改被引用的对象,通常把引用参数声明为const的
  • 若无指针,=运算符重载可省略;若有指针,必须=运算符重载

4.4.5>、<、==、!=运算符重载

  • 作用:可以让两个自定义类型对象进行比较
#include <iostream>
#include <string>
using namespace std;

class Person {
public:
    Person(string name, int age) {
        Name = name;
        Age = age;
    }

    bool operator==(Person &p) {
        if (this->Name == p.Name && this->Age == p.Age) {
            return true;
        }
        return false;
    }

    bool operator!=(Person &p) {
        if (this->Name == p.Name && this->Age == p.Age) {
            return false;
        }
        return true;
    }
        string Name;
        int Age;

};

void test1() {
    Person p1("Tom", 18);
    Person p2("Jerry", 18);

    if (p1 == p2) {
        cout << "p1和p2是相等的" << endl;
    }
    else {
        cout << "p1和p2是不相等的" << endl;
    }

    if (p1 != p2) {
        cout << "p1和p2是不相等的" << endl;
    }
    else {
        cout << "p1和p2是相等的" << endl;
    }
}

int main() {
    test1();

    return 0;
}

4.4.6()运算符重载(仿函数)

仿函数

#include <iostream>
#include <string>
using namespace std;

// 函数调用运算符重载
class MyPrint {
    public:
        void operator()(string test) {
            cout << test << endl;
        }
};

void test1() {
    MyPrint myPrint;
    myPrint("Hello World"); // 由于使用起来非常类似于函数调用,因此被称为仿函数   
}

// 仿函数非常灵活,没有固定的写法
// 加法类
class MyAdd {
    public:
        int operator()(int num1, int num2) {
            return num1 + num2;
        }
};

void test2() {
    MyAdd myadd;
    int ret = myadd(100, 200); // 由于使用起来非常类似于函数调用,因此被称为仿函数
    cout << "ret = " << ret << endl;

    // *匿名函数对象
    cout << "MyAdd()(100, 100) = " << MyAdd()(100, 100) << endl; // 输出流中不应该包含返回值为void的函数
}

int main() {
    test1();
    test2();

    return 0;
}

4.4.7Type()运算符重载

声明形式:

类名::operator Type(); // 类型转换成员函数

int()运算符重载: 

#include <iostream>
using namespace std;

class Complex {
public:
    Complex(double r = 0, double i = 0) {
        real = r;
        imag = i;
    }
    operator int() {
        return int(real);
    }
private:
    double real, image;
};

int main() {
    Complex c(3.5, 5.5);
    cout << int(c) << endl; // c.operator int()
    
    return 0;
}

double()运算符重载:

  • 作用:实现两个数相除
#include <iostream>
using namespace std;

class Rational {
public:
    Rational(double x = 0, double y = 1) {
        Numerator = x;
        Denominator = y;
    }
    operator double() {
        return Numerator / Denominator;
    }

private:
    double Numerator, Denominator;
};

int main() {
    Rational r(100, 200);
    double d = r;
    cout << d << endl;

    return 0;
}

4.4.8[]运算符重载

  • 实现用[]访问vector中对应位置元素
  • 注意引用返回
#include <iostream>
using namespace std;
class vector {
public:
    vector(int s) {
        v = new int[s];
        capacity = s;
        size = 0;
    }
    ~vector() {
        if (v != nullptr) {
            delete[] v;
        }
    }
    int& operator[](int i) {
        return v[i];
    }
private:
    int *v;
    int capacity; // vector的容量
    int size; // vector元素的个数
};

int main() {
    vector vec(5);
    vec[2] = 12;
    cout << vec[2] << endl;

    return 0;
}

4.4.9+=运算符重载

#include <iostream>
using namespace std;

class Complex {
public:
    Complex(double r = 0, double i = 0) {
        real = r;
        image = i;
    }
    Complex& operator+=(const Complex& c) { // 复合赋值运算
        real = real + c.real;
        image = image + c.image;
        return *this;
    }
    void show() const {
        cout << "(" << real << ", " << image << ")" << endl;
    }
private:
    double real,image;
};

int main() {
    Complex c1(2.5, 3.7), c2(4.2, 6.5);
    c1 += c2; // c1.operator+=(c2);
    c1.show();
    
    return 0;
}

4.5继承

4.5.1基本语法

  • 继承的好处:减少重复代码
  • 语法:class 子类: 继承方式 父类
  • 子类也称为派生类
  • 父类也称为基类
#include <iostream>
using namespace std;

// // Java页面
// class Java {
// public:
//     void header() {
//         cout << "首页、公开课、登录、注册...(公共头部)" << endl;
//     }
//     void footer() {
//         cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
//     }
//     void left() {
//         cout << "Java、Python、C++...(公共分类列表)" << endl;
//     }
//     void content() {
//         cout << "Java学科视频" << endl;
//     }
// };

// // Python页面
// class Python {
// public:
//     void header() {
//         cout << "首页、公开课、登录、注册...(公共头部)" << endl;
//     }
//     void footer() {
//         cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
//     }
//     void left() {
//         cout << "Java、Python、C++...(公共分类列表)" << endl;
//     }
//     void content() {
//         cout << "Python学科视频" << endl;
//     }
// };

// // C++页面
// class Cpp {
// public:
//     void header() {
//         cout << "首页、公开课、登录、注册...(公共头部)" << endl;
//     }
//     void footer() {
//         cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
//     }
//     void left() {
//         cout << "Java、Python、C++...(公共分类列表)" << endl;
//     }
//     void content() {
//         cout << "C++学科视频" << endl;
//     }
// };

// 继承实现页面
class BasePage {
public:
    void header() {
        cout << "首页、公开课、登录、注册...(公共头部)" << endl;
    }
    void footer() {
        cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
    }
    void left() {
        cout << "Java、Python、C++...(公共分类列表)" << endl;
    }
};

// Java页面
class Java: public BasePage {
public:
    void content() {
        cout << "Java学科视频" << endl;
    }
};

// Python页面
class Python: public BasePage {
public:
    void content() {
        cout << "Python学科视频" << endl;
    }
};

// C++页面
class Cpp: public BasePage {
public:
    void content() {
        cout << "C++学科视频" << endl;
    }
};

void test1() {
    cout << "Java下载视频页面如下:" << endl;
    Java ja;
    ja.header();
    ja.footer();
    ja.left();
    ja.content();

    cout << "---------------------" << endl;
    cout << "Python下载视频页面如下:" << endl;
    Python py;
    py.header();
    py.footer();
    py.left();
    py.content();

    cout << "---------------------" << endl;
    cout << "C++下载视频页面如下:" << endl;
    Cpp cpp;
    cpp.header();
    cpp.footer();
    cpp.left();
    cpp.content();
}

int main() {
    test1();

    return 0;
}

4.5.2向上类型转换

从派生类到基类的自动转换

发生情况:

  • 用派生类对象赋值或初始化基类对象
  • 用派生类对象初始化基类引用
  • 将派生类对象的地址赋给指向基类的指针 
#include <iostream>
using namespace std;

class UnivPerson {
public:
	void show() {
		cout << "UnivPerson-show()" << endl;
	}
};

class Student: public UnivPerson {
public:
	Student(int id, double score, string name, bool judge) {
		Id = id;
		Score = score;
		Name = name;
		Judge = judge;
	} 
	void show() {
		cout << "Student-show()" << endl;
	}
private:
	int Id;
	double Score;
	string Name;
	bool Judge;
};

int main() {
    Student stu(200801, 82.3, "zhang", true); // 派生类对象
    UnivPerson pers = stu; // 用派生类对象初始化基类对象
    UnivPerson& per = stu; // 用派生类对象初始化基类引用
    UnivPerson* p = &stu; // 派生类对象的地址赋给指向基类的指针
    
    stu.show(); // 调用派生类show()
    pers.show(); // 调用基类show()
    per.show(); // 调用基类show()
    p->show(); // 调用基类show()
    
    return 0;
}

运行结果:

Student-show()
UnivPerson-show()
UnivPerson-show()
UnivPerson-show()

 对象切割:向上类型转换时,派生类对象中新增成员将被舍弃,只将从基类继承来的部分赋给基类对象。

4.5.3继承方式

三种继承方式:

  • 公共继承
  • 保护继承
  • 私有继承
#include <iostream>
using namespace std;

// 公共继承
class Base1 {
public:
    int A;
protected:
    int B;
private:
    int C;
};

class Son1: public Base1 {
public:
    void func() {
        A = 10; // 父类中的公共权限,到子类中依然是公共权限
        B = 10; // 父类中的保护权限,到子类中依然是保护权限
        //C = 10; // 父类中的私有成员,子类访问不到
    }
};

void test1() {
    Son1 s1;
    s1.A = 100;
    //s1.B = 100; // Son1中B是保护权限,类外访问不到
}

class Base2 {
public:
    int A;
protected:
    int B;
private:
    int C;
};

class Son2: protected Base2 {
public:
    void func() {
        A = 100; // 父类中的公共权限,到子类中变为保护权限
        B = 100; // 父类中的保护权限,到子类中依然是保护权限
        //C = 100; // 父类中的私有成员,子类访问不到
    }
};

void test2() {
    Son2 s1;
    //s1.A = 1000; // Son2中A变为保护权限,因此类外访问不到
    //s1.B = 1000; // Son2中B为保护权限,因此类外访问不到

}

class Base3 {
public:
    int A;
protected:
    int B;
private:
    int C;
};
class Son3: private Base3 {
public:
    void func() {
        A = 100; // 父类中的公共权限,到子类中变为私有权限
        B = 100; // 父类中的保护权限,到子类中变为私有权限
        //C = 100; // 父类中的私有成员,子类访问不到

    }
};

void test3() {
    Son3 s1;
    //s1.A = 1000; // Son2中A变为私有权限,因此类外访问不到
    //s1.B = 1000; // Son2中B变为私有权限,因此类外访问不到
}

class GrandSon3: public Son3 {
public:
    void func() {
        //A = 1000; // 到了Son3中A变为私有,即使是儿子,也访问不到
        //B = 1000; // 到了Son3中B变为私有,即使是儿子,也访问不到
    }
};

int main() {
    test1();
    test2();
    test3();
    
    return 0;
}

4.5.4可以被继承的对象和函数

可以被继承的对象:

  • 父类中所有非静态成员对象都会被子类继承下去
  • 父类中私有成员对象是被编译器给隐藏了,因此是访问不到的,但是确实被继承下去了
#include <iostream>
using namespace std;

class Base {
public:
    int A;
protected:
    int B;
private:
    int C;
};

class Son: public Base {
public:
    int D;

};
void test1() {
    cout << "size of Son = " << sizeof(Son) << endl; // 16
}
int main() {
    test1();

    return 0;
}

可以被继承的函数: 

  • 友元函数不可以被继承
  • 构造函数不可以被继承
  • 纯虚函数不可以被继承
  • 静态成员函数可以被继承
  • 常成员函数可以被继承
  • 虚函数可以被继承

4.5.5继承中的子类构造函数

父类Base的变量都是private类型,不允许外界访问,因此我们只能调用父类的构造函数来初始化x和y变量,然后变量z初始化直接赋值即可。

子类调用父类的构造函数我们直接将:Base(x, y)加在子类构造函数参数后面。

#include <iostream>
using namespace std;

class Base {
private:
    int x;
    int y;

public:
    Base(int x, int y) {
        this->x = x;
        this->y = y;
    }

    int getX() {
        return x;
    }

    int getY() {
        return y;
    }
};

class Sub : public Base {
private:
    int z;

public:
    // 继承中的子类构造函数
    Sub(int x, int y, int z) : Base(x, y) {
        this->z = z;
    }

    int getZ() {
        return z;
    }

    int calculate() {
        return Base::getX() * Base::getY() * this->getZ();
    }

};

int main() {

    int x, y, z;
    cin >> x;
    cin >> y;
    cin >> z;
    Sub sub(x, y, z);
    cout << sub.calculate() << endl;
    
    return 0;
}

4.5.6构造和析构顺序

  • 子类继承父类后,当创建子类对象,也会调用父类的构造函数
  • 继承中的构造和析构顺序:先构造父类,再构造子类,析构的顺序与构造的顺序相反
  • 如果是多继承,调用基类构造函数的顺序与定义派生类时继承基类的顺序有关,而与初始化列表中的排列顺序无关
#include <iostream>
using namespace std;

class Base {
public:
    Base() {
        cout << "Base的构造函数" << endl;
    }
    ~Base() {
        cout << "Base的析构函数" << endl;
    }
};

class Son: public Base {
public:
    Son() {
        cout << "Son的构造函数" << endl;
    }
    ~Son() {
        cout << "Son的析构函数" << endl;
    }
};

void test1() {
    //Base b;
    
    Son s;

}

int main() {
    test1();

    return 0;
}

运行结果:

Base的构造函数
Son的构造函数
Son的析构函数
Base的析构函数
#include <iostream>
using namespace std;

class B1 {
public:
    B1(int i) {
        b1 = i;
        cout << "Constructor B1:" << b1 << endl;
    }
    ~B1() {
        cout << "Destructor B1:" << b1 << endl;
    }
    void Print() {
        cout << b1 << ", ";
    }
private:
    int b1;
};

class B2 {
public:
    B2(int i) {
        b2 = i;
        cout << "Constructor B2:" << b2 << endl;
    }
    ~B2() {
        cout << "Destructor B2:" << b2 << endl;
    }
    void Print() {
        cout << b2 << ", ";
    }
private:
    int b2;
};

class B3 {
public:
    B3(int i) {
        b3 = i;
        cout << "Constructor B3:" << b3 << endl;
    }
    ~B3() {
        cout << "Destructor B3:" << b3 << endl;
    }
    int Getb3() {
        return b3;
    }
private:
    int b3;
};

// 定义派生类,B1在前,B2在后
class A: public B1, B2 { // 与定义定义派生类时继承基类的顺序有关
public:
    A(int  i, int j, int k, int m): a(m), b(k), B2(j), B1(i) { // 与初始化列表中的排列顺序无关
        cout << "Constructor A:" << a << endl;
    }
    ~A() {
        cout << "Destructor A:" << a << endl;
    }
    void Print() {
        B1::Print();
        B2::Print();
        cout << b.Getb3() << ", " << a << endl;
    }
private:
    int a;
    B3 b; 
};

int main() {
    A obj(1, 2, 3, 4);
    obj.Print();

    return 0;
}

 运行结果:

Constructor B1:1
Constructor B2:2
Constructor B3:3
Constructor A:4
1, 2, 3, 4
Destructor A:4
Destructor B3:3
Destructor B2:2
Destructor B1:1

在建立对象obj调用派生类构造函数时,先调用B1的构造函数,再调用B2的构造函数,然后按照类A中数据成员的声明顺序,先对整型a进行初始化,再对类对象b(嵌入式子对象)进行初始化,最后执行构造函数体内的语句。析构与构造顺序相反。 

4.5.7同名成员处理

当子类和父类出现同名的成员,如何通过子类对象访问到子类或父类中同名的数据:

  • 子类对象可以直接访问到子类中的同名成员
  • 子类对象加作用域可以访问到父类中同名成员
#include <iostream>
using namespace std;

class Base {
public:
    Base() {
        A = 100;
    }
    void func() {
        cout << "Base - func()调用" << endl;
    }
    void func(int a) {
        cout << "Base - func(int a)调用" << endl;
    }
    int A;
};

class Son: public Base {
public:
    Son() {
        A = 200;
    }
    void func() {
        cout << "Son - func()调用" << endl;
    }
    int A;
};

void test1() {
    Son s;
    // 1. 子类对象可以直接访问到子类中的同名成员
    cout << "Son A = " << s.A << endl;
    // 2. 子类对象加作用域可以访问到父类中同名成员
    cout << "Base A = " << s.Base::A << endl;
}

void test2() {
    Son s;
    s.func(); // 直接调用,调用的是子类中的同名成员
    s.Base::func(); // 调用父类中同名成员函数,需要加作用域
    // 3. 如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数
    // 如果想访问到父类中被隐藏的同名成员函数,需要加作用域
    s.Base::func(100);
}

int main() {
    test1();
    test2();

    return 0;
}

4.5.8同名静态成员处理

  • 静态成员和非静态成员出现同名,处理方法一致
  • 访问子类同名成员,直接访问即可
  • 访问父类同名成员,需要加作用域
  • 可以通过对象访问,也可以通过类名访问
#include <iostream>
using namespace std;

class Base {
public:
    static int A;
    static void func() {
        cout << "Base - static void func()" << endl;
    }
    static void func(int a) {
        cout << "Son - static void func(int a)" << endl;
    }
};
int Base::A = 100;

class Son: public Base{
public:
    static int A;
    static void func() {
        cout << "Son - static void func()" << endl;
    }
};
int Son::A = 200;

// 同名静态成员属性
void test1() {
    // 1. 通过对象访问
    cout << "通过对象访问:" << endl;
    Son s;
    cout << "Son下 A = " << s.A << endl;
    cout << "Base下 A = " << s.Base::A << endl;

    // 2. 通过类名访问
    cout << "通过类名访问" << endl;
    cout << "Son下 A = " << Son::A << endl;
    // 第一个::代表通过类名方式访问,第二个::代表范文父类作用域下
    cout << "Base下 A = " << Son::Base::A << endl;
}

// 同名静态成员函数
void test2() {
    //1. 通过对象访问
    cout << "通过对象访问" << endl;
    Son s;
    s.func();
    s.Base::func();
    //2. 通过类名访问
    cout << "通过类名访问:" << endl;
    Son::func();
    Son::Base::func();
    // 子类出现和父类同名静态成员函数,也会隐藏父类中所有同名成员函数
    // 如果想访问父类中被隐藏的同名成员,需要加作用域
    Son::Base::func(100);
}

int main() {
    //test1();
    test2();

    return 0;
}

4.5.9多继承语法

  • C++允许一个类继承多个类
  • 语法:class 子类: 继承方式 父类1, 继承方式 父类2
  • 多继承可能会引发父类中有同名成员出现,需要加作用域区分
  • 一个类不能被多次声明为一个派生类的直接基类,但可以不止一次地成为间接基类
#include <iostream>
using namespace std;

class Base1 {
public:
    Base1() {
        A = 100;
    }
    int A = 100;
};

class Base2 {
public:
    Base2() {
        A = 200;
    }
    int A = 200;
};

// 子类:需要继承Base1和Base2
class Son: public Base1, public Base2 {
public:
    Son() {
        C = 300;
        D = 400;
    }
    int C = 300;
    int D = 400;
};

void test1() {
    Son s;
    cout << "sizeof Son = " << sizeof(s) << endl; // 16
    // 当父类中出现同名成员,需要加作用域区分
    cout << "Base1::A = " << s.Base1::A << endl; // 100
    cout << "Base2::B = " << s.Base2::A << endl; // 200
}

int main() {
    test1();

    return 0;
}

4.5.10菱形继承问题以及解决方法

典型菱形继承问题:动物——羊,驼——羊驼:

  • 羊继承了动物的数据,驼同样继承了动物的数据,当羊驼使用数据时,就会产生二义性
  • 羊驼继承自动物的属性继承了两份,这份数据我们只需要一份就可以

利用虚继承可以解决菱形继承的问题:

  • 在继承之前加上关键字virtual变为虚继承
  • Animal类称为虚基类
  • 当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次
  • 为保证虚基类在派生类中只继承一次,应该在所有直接派生类中声明该基类为虚基类
# include <iostream>
using namespace std;

// 动物类
// 虚基类
class Animal {
public:
    int Age;
};

// 羊类
// 虚继承
class Sheep: virtual public Animal {

};

// 驼类
class Tuo: virtual public Animal {

};

// 羊驼类
class SheepTuo: public Sheep, public Tuo {

};

void test1() {
    SheepTuo st;
    st.Sheep::Age = 18;
    st.Tuo::Age = 28;

    // 当菱形继承时,两个父类拥有相同数据,需要加以作用域区分
    cout << "st.Sheep::Age = " << st.Sheep::Age << endl; // 28
    cout << "st.Tuo::Age = " << st.Tuo::Age << endl; // 28
    cout << "st.Age = " << st.Age << endl; // 28

    // 这份数据我们知道,只要有一份就可以,菱形继承导致数据有两份,资源浪费
}

int main() {
    test1();

    return 0;
}

构造函数:

  • 虚基类的构造函数的调用优先于非虚基类构造函数
  • 程序只在调用该派生类构造函数时调用虚基类的构造函数,在调用其他中间派生类构造函数时不再调用
#include <iostream>
using namespace std;

class Furniture {
public:
    Furniture(int w) {
        price = w;
        cout << "Constructor of Furniture." << endl;
    }
protected:
    int price;
};

class Bed: virtual public Furniture {
public:
    Bed(int i): Furniture(i) {
        cout << "Constructor of Bed." << endl;
    }
};

class Sofa: virtual public Furniture {
public:
    Sofa(int i): Furniture(i) {
        cout << "Constructor of Sofa." << endl;
    }
};

class SofaBed: public Bed, public Sofa {
public:
    SofaBed(int i): Furniture(i), Bed(i), Sofa(i) {
        cout << "Constructor of SofaBed." << endl;
    }
};

int main() {
    SofaBed sofaBed(500);

    return 0;
}

运行结果:

Constructor of Furniture.
Constructor of Bed.
Constructor of Sofa.
Constructor of SofaBed.

4.6多态

4.6.1多态的基本语法

多态分为两类:

  • 静态多态:函数重载和运算符重载属于静态多态,复用函数名。地址早绑定:编译阶段确定函数地址。
  • 动态多态:派生类和虚函数实现运行时多态。地址晚绑定:运行阶段确定函数地址。

动态多态满足条件:

  • public继承关系
  • 子类重写父类的虚函数

动态多态使用:

  • 父类的指针或者引用,执行子类对象(函数的参数类型为基类指针或引用)

虚函数:

  • 虚函数是成员函数,但不会是静态成员函数,不是友元函数
  • 若虚函数定义在类体外,则关键字virtual只能出现在类内的函数声明前,在类外的函数定义前不需要再用该关键字
  • 当使用作用域运算符::时,虚机制不再起作用
  • 作为重写的虚函数,派生类中的函数接口必须与基类中的完全相同,包括函数名、返回类型、参数列表、是否有const等
  • 在派生类中重写虚函数时,如果原函数有默认形参值,就不要再定义新的形参值,因为默认形参值是静态绑定的,只能来自基类的定义
  • 在多层次继承中,如果派生类没有对基类的虚函数进行重写,那么在类似前面的test()调用中,将自动调用继承层次中最近的虚函数
  • 只有虚函数才可能动态绑定。因此如果派生类要重写基类的行为,就应该将基类中的相应函数声明为virtual的
#include <iostream>
using namespace std;

// 动物类
class Animal {
public:
    // 虚函数
    virtual void speak() {
        cout << "动物在说话" << endl;
    }
};

// 猫类
class Cat: public Animal {
public:
    // 重写:函数返回值类型,函数名,参数列表完全相同
    void speak() {
        cout << "小猫在说话" << endl;
    }
};

// 狗类
class Dog: public Animal {
public:
    void speak() {
        cout << "小狗在说话" << endl;
    }
};

// 执行说话的函数
void doSpeak(Animal &animal) { // Animal animal = cat,函数参数类型为基类引用
    animal.speak();
}

// 执行说话的函数
void test1() {
    Cat cat;
    doSpeak(cat); // 加virtual前:动物在说话,加virtual后:小猫在说话

    Dog dog;
    doSpeak(dog); // 小狗在说话
}
int main() {
    test1();

    return 0;
}

4.6.2多态的原理剖析

  • 虚函数为一个指针,占8个字节,与虚函数数量无关
  • 地址先分配给虚函数,后分配给成员变量
#include <iostream>
using namespace std;

// 动物类
class Animal {
public:
    // 虚函数
    virtual void speak() {
        cout << "动物在说话" << endl;
    }
};

// 猫类
class Cat: public Animal {
public:
    // 重写:函数返回值类型,函数名,参数列表完全相同
    void speak() {
        cout << "小猫在说话" << endl;
    }
};

// 狗类
class Dog: public Animal {
public:
    void speak() {
        cout << "小狗在说话" << endl;
    }
};

// 执行说话的函数
void doSpeak(Animal &animal) { // Animal animal = cat
    animal.speak();
}

void test1() {
    Cat cat;
    doSpeak(cat); // 加virtual前:动物在说话,加virtual后:小猫在说话

    Dog dog;
    doSpeak(dog); // 小狗在说话
}

void test2() {
    cout << "sizeof Animal = " << sizeof(Animal) << endl; // 加virtual前:空类,1;加virtual后:8,为一个指针
}

int main() {
    //test1();
    test2();
    return 0;
}

 4.6.3动态绑定测试

#include <iostream>
using namespace std;

// 基类
class UnivPerson {
public:
    virtual void Show() const {
        cout << "UnivPerson::Show()" << endl;
    }
    virtual void Eat() const {
        cout << "UnivPerson::Eat()" << endl;
    }
    virtual void Study() const {
        cout << "UnivPerson::Study()" << endl;
    }
};

// 派生类
class Student: public UnivPerson {
public:
    virtual void Show() const {
        cout << "Student::Show()" << endl;
    }
    virtual void Eat() const {
        cout << "Student::Eat()" << endl;
    }
    virtual void Study() const {
        cout << "Student::Study()" << endl;
    }
};

// 派生类
class Graduate: public Student {
public:
    virtual void Show() const {
        cout << "Graduate::Show()" << endl;
    }
    virtual void Eat() { // 无const
        cout << "Graduate::Eat()" << endl;
    }
    // 无Study函数
};

int main() {
    Graduate gra; // 建立派生类Graduate对象

    UnivPerson* p = &gra; // 基类指针,指向派生类对象
    UnivPerson& rp = gra; // 基类指针,引用派生类对象
    UnivPerson per = gra; // 基类对象,用派生类对象初始化

    p->Show(); // Graduate::Show(),动态绑定
    rp.Show(); // Graduate::Show(),动态绑定
    per.Show(); // UnivPerson::Show(),静态绑定

    p->Study(); // Student::Study(),动态绑定,Graduate中没有重写Study(),调用最近的重写虚函数
    p->Eat(); // Student::Eat(),动态绑定,Graduate中的Eat()不是虚函数重写(无const)
    
    gra.Eat(); // Graduate::Eat(),静态绑定
    gra.Study(); // Student::Study(),静态绑定,graduate类中无Study函数,则找向上继承中最近的类的函数
    gra.Show(); // Graduate::Show(),静态绑定

    return 0;
}

运行结果:

Graduate::Show()
Graduate::Show()
UnivPerson::Show()
Student::Study()
Student::Eat()
Graduate::Eat()
Student::Study()
Graduate::Show()

4.6.4案例1——计算器类

分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类

多态的优点:

  • 代码组织结构清晰
  • 可读性强
  • 利于前期和后期的扩展及维护
#include <iostream>
#include <cstring>
using namespace std;

// 普通写法
class Calculator {
public:
    int getResult(string oper) {
        if (oper == "+") {
            return Num1 + Num2;
        }
        if (oper == "-") {
            return Num1 - Num2;
        }
        if (oper == "*") {
            return Num1 * Num2;
        }
        // 如果想扩展新的功能,需要修改源码
        // 在真实的开发中,提倡开闭原则:对扩展进行开发,对修改进行关闭
    }

    int Num1; // 操作数1
    int Num2; // 操作数2
};

void test1() {
    Calculator c;
    c.Num1 = 10;
    c.Num2 = 10;

    cout << c.Num1 << " + " << c.Num2 << " = " << c.getResult("+") << endl;
    cout << c.Num1 << " - " << c.Num2 << " = " << c.getResult("-") << endl;
    cout << c.Num1 << " * " << c.Num2 << " = " << c.getResult("*") << endl;
}

// 利用多态实现计算器
// 实现计算器抽象类
class AbstractCalculator {
public:
    virtual int getResult() {
        return 0;
    }
    int Num1;
    int Num2;

};

// 加法计算器
class AddCalculator: public AbstractCalculator {
public:
    int getResult() {
        return Num1 + Num2;
    }
};

// 减法计算器
class SubCalculator: public AbstractCalculator {
public:
    int getResult() {
        return Num1 - Num2;
    }
};

// 加法计算器
class MulCalculator: public AbstractCalculator {
public:
    int getResult() {
        return Num1 * Num2;
    }
};

void test2() {
    // 多态使用条件
    // 父类指针或者引用指向子类对象

    // 加法运算
    AbstractCalculator *abc = new AddCalculator;
    abc->Num1 = 10;
    abc->Num2 = 10;
    cout << abc->Num1 << " + " << abc->Num2 << " = " << abc->getResult() << endl;
    // 用完记得销毁
    delete abc;

    // 减法运算
    abc = new SubCalculator;
    abc->Num1 = 10;
    abc->Num2 = 10;
    cout << abc->Num1 << " - " << abc->Num2 << " = " << abc->getResult() << endl;
    // 用完记得销毁
    delete abc;

    // 乘法运算
    abc = new MulCalculator;
    abc->Num1 = 10;
    abc->Num2 = 10;
    cout << abc->Num1 << " * " << abc->Num2 << " = " << abc->getResult() << endl;
    // 用完记得销毁
    delete abc;

}
int main() {
    //test1();
    test2();

    return 0;
}

4.6.5纯虚函数和抽象类

  • 在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数
  • 纯虚函数语法:virtual 返回值类型 函数名 (参数列表) = 0
  • 当类中有纯虚函数,这个类称为抽象类

抽象类特点:

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
#include <iostream>
using namespace std;

// 纯虚函数和抽象类
class Base {
public:
    // 纯虚函数
    virtual void func() = 0;
};

class Son: public Base {
public:
    virtual void func() {
        cout << "func函数调用";
    }
};

void test1() {
    //Base b; // 抽象类无法实例化对象
    //new Base; // 抽象类无法实例化对象
    //Son s; // 子类必须重写父类中的纯虚函数,否则无法实例化对象

    Son *base = new Son;
    base->func();
}

int main() {
    test1();

    return 0;
}

4.6.6案例2——制作饮品

#include <iostream>
using namespace std;

class AbstractDrink {
public:
    // 煮水
    virtual void Boil() = 0;
    // 冲泡
    virtual void Brew() = 0;
    // 倒入杯中
    virtual void PourInCup() = 0;
    // 加入辅料
    virtual void PutSomething() = 0;

    // 制作饮品
    void makeDrink() {
        Boil();
        Brew();
        PourInCup();
        PutSomething();
    }
};

// 制作咖啡
class Coffee: public AbstractDrink {
public:
    // 煮水
    virtual void Boil() {
        cout << "煮农夫山泉" << endl;
    }
    // 冲泡
    virtual void Brew() {
        cout << "冲泡咖啡" << endl;
    }
    // 倒入杯中
    virtual void PourInCup() {
        cout << "倒入杯中" << endl;
    }
    // 加入辅料
    virtual void PutSomething() {
        cout << "加入糖和牛奶" << endl;
    }
};

// 制作茶叶
class Tea: public AbstractDrink {
public:
    // 煮水
    virtual void Boil() {
        cout << "煮农夫山泉" << endl;
    }
    // 冲泡
    virtual void Brew() {
        cout << "冲泡茶叶" << endl;
    }
    // 倒入杯中
    virtual void PourInCup() {
        cout << "倒入杯中" << endl;
    }
    // 加入辅料
    virtual void PutSomething() {
        cout << "加入枸杞" << endl;
    }
};

void doWork(AbstractDrink *abs) {
    abs->makeDrink();
    delete abs; // 释放
}

void test1() {
    // 制作咖啡
    doWork(new Coffee);
    cout << "----------------" << endl;
    // 制作茶叶
    doWork(new Tea);
}

int main() {
    test1();

    return 0;
}

4.6.7虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构和纯虚析构

虚析构和纯虚析构共性:

  • 可以解决父类指针释放子类对象
  • 都需要有具体的函数实现

虚析构和纯虚析构区别:

  • 如果是纯虚析构,该类属于抽象类,无法实例化对象
  • 虚析构语法:virtual ~类名() {}
  • 纯虚析构语法:virtual ~类名() = 0

注意: 

  • 虚析构和纯虚析构就是用来解决父类指针释放子类对象
  • 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
  • 拥有纯虚析构函数的类也属于抽象类
#include <iostream>
#include <cstring>
using namespace std;

class Animal {
public:
    Animal() {
        cout << "Animal的构造函数调用" << endl;
    }
    // 利用虚析构可以解决父类指针释放子类对象时不干净的问题
//    virtual ~Animal() {
//        cout << "Animal的虚析构函数调用" << endl;
//    }
    // 虚析构和纯虚析构只能有一个
    // 纯虚析构:需要声明,也需要实现
    // 有了纯虚析构之后,这个类也属于抽象类,无法实例化对象
    virtual ~Animal() = 0;
    // 纯虚函数
    virtual void speak() = 0;
};

Animal:: ~Animal() {
    cout << "Animal的纯虚析构函数调用" << endl;
}

class Cat: public Animal {
public :
    Cat (string name) {
        Name = new string(name);
        cout << "Cat的构造函数调用" << endl;
    }
    virtual void speak() {
        cout << *Name << "小猫在说话" << endl;
    }
    ~Cat() {
        if (Name != NULL) {
            cout << "Cat的析构函数调用" << endl;
            delete Name;
            Name = NULL;
        }
    }
    string *Name; // 堆区数据
};

void test1() {
    Animal *animal = new Cat("Tom"); // Animal的构造函数调用 // Cat的构造函数调用
    animal->speak(); // Tom小猫在说话
    // 父类指针在析构的时候,不会调用子类中的析构函数,导致子类如果有堆区属性,会出现内存泄露
    delete animal; // Cat的析构函数调用(虚析构/纯虚析构后才有) // Animal的析构函数调用
}

int main() {
    test1();

    return 0;
}

运行结果:

Animal的构造函数调用
Cat的构造函数调用
Tom小猫在说话
Cat的析构函数调用
Animal的纯虚析构函数调用

4.6.8案例3——电脑组装

  1. 电脑主要组成部件为CPU(用于计算),显卡(用于显示),内存条(用于存储)
  2. 将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件,例如Intel厂商和Lenovo厂商
  3. 创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口
  4. 测试时组装三台不同的电脑进行工作 
#include <iostream>
using namespace std;

// 抽象不同零件类
// 抽象CPU类
class CPU {
public:
    // 抽象的计算函数
    virtual void calculate() = 0;
};
// 抽象显卡类
class VideoCard {
public:
    // 抽象的显示函数
    virtual void display() = 0;
};
// 抽象内存条类
class Memory {
public:
    // 抽象的存储函数
    virtual void storage() = 0;
};

// 电脑类
class Computer {
public:
    Computer(CPU *cpu,VideoCard *vc, Memory *mem) {
        Cpu = cpu;
        Vc = vc;
        Mem = mem;
    }
    // 提供工作的函数
    void Work() {
        // 让零件工作起来
        Cpu->calculate();
        Vc->display();
        Mem->storage();
    }
    // 提供析构函数,释放3个电脑零件
    ~Computer() {
        // 释放CPU零件
        if (Cpu != NULL) {
            delete Cpu;
            Cpu = NULL;
        }
        // 释放显卡零件
        if (Vc != NULL) {
            delete Vc;
            Vc = NULL;
        }
        // 释放内存条零件
        if (Mem != NULL) {
            delete Mem;
            Mem = NULL;
        }
    }
private:
    CPU *Cpu; // CPU的零件指针
    VideoCard *Vc; // 显卡的零件指针
    Memory *Mem; // 内存条的零件指针
};

// 具体厂商
// Intel厂商
class IntelCPU: public CPU {
public:
    virtual void calculate() {
        cout << "Intel的CPU开始计算了" << endl;
    }
};

class IntelVideoCard: public VideoCard {
public:
    virtual void display() {
        cout << "Intel的显卡开始显示了" << endl;
    }
};

class IntelMemory: public Memory {
public:
    virtual void storage() {
        cout << "Intel的内存条开始存储了" << endl;
    }
};

// Lenovo厂商
class LenovoCPU: public CPU {
public:
    virtual void calculate() {
        cout << "Lenovo的CPU开始计算了" << endl;
    }
};

class LenovoVideoCard: public VideoCard {
public:
    virtual void display() {
        cout << "Lenovo的显卡开始显示了" << endl;
    }
};

class LenovoMemory: public Memory {
public:
    virtual void storage() {
        cout << "Lenovo的内存条开始存储了" << endl;
    }
};

void test1() {
    // 创建第一台电脑零件
    CPU *intelCpu = new IntelCPU;
    VideoCard *intelVideoCard = new IntelVideoCard;
    Memory * intelMemory = new IntelMemory;

    // 组装第一台电脑
    cout << "第一台电脑开始工作了" << endl;
    Computer *computer1 = new Computer(intelCpu, intelVideoCard, intelMemory);
    computer1->Work();
    delete computer1;

    // 组装第二台电脑
    cout << "------------------------------" << endl;
    cout << "第二台电脑开始工作了" << endl;
    Computer *computer2 = new Computer(new LenovoCPU, new LenovoVideoCard, new LenovoMemory);
    computer2->Work();
    delete computer2;

    // 组装第三台电脑
    cout << "------------------------------" << endl;
    cout << "第三台电脑开始工作了" << endl;
    Computer *computer3 = new Computer(new LenovoCPU, new IntelVideoCard, new LenovoMemory);
    computer3->Work();
    delete computer3;
}

int main() {
    test1();

    return 0;
}

4.6.9案例4——计算定积分

#include <iostream>
#include <cmath>
using namespace std;

class Func {
public:
    virtual double operator() (double) = 0;
};

double integrate(Func& f, double low, double high) {
    const int numsteps = 8; // 分区数
    double step = (high - low) / numsteps; // 步长
    double area = 0.0;
    while(low < high) {
        area += f(low) * step; // 计算小矩形面积
        low += step;
    }
    return area;
}

class Sin: public Func {
public:
    virtual double operator() (double x) {
        return sin(x);
    }
};

class Square: public Func {
public:
    virtual double operator() (double x) {
        return x * x;
    }
};

int main() {
    Sin obj1; // 声明一个Sin类对象
    cout << integrate(obj1, 0.0, 3.14 / 2) << endl; // 正弦函数在[0, pi/2]的积分

    Square obj2; // 声明一个Square对象
    cout << integrate(obj2, 0 ,2) << endl; // 平方函数在[0, 2]的积分

    return 0;
}

五、模板

5.1模板的概念

模板就是通用的模具,大大提高复用性

5.2函数模板

  • C++另一种编程思想称为泛型编程,主要利用的技术就是模板
  • C++提供两种模板机制:函数模板和类模板

5.2.1函数模板语法

  • 函数模板作用:建立一个通用函数,其函数返回值类型和形参类型可以不具体指定,用一个虚拟的类型来代表
  • 语法:
template<typename T>
函数声明或定义

解释:

  • template:声明创建模板
  • typename:表明其后的符号是一种数据类型,可以用class代替
  • T:通用的数据类型,名称可以替换,通常为大写字母
#include <iostream>
using namespace std;

// 交换两个整型函数
void swapInt(int &a, int &b) {
    int temp = a;
    a = b;
    b = temp;
}

// 交换两个浮点型函数
void swapDouble(double &a, double &b) {
    double temp = a;
    a = b;
    b = temp;
}

// 函数模板
template<typename T> // 声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型
void mySwap(T &a, T &b) {
    T temp = a;
    a = b;
    b = temp;
}

void test1() {
    int a = 10;
    int b = 20;
//    swapInt(a, b);
    // 利用函数模板交换
    // 两种方式使用函数模板
    // 1. 自动类型推导
//    mySwap(a, b);
    // 2. 显式指定类型
    mySwap<int>(a, b);
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;

    double c = 1.1;
    double d = 2.2;
    swapDouble(c, d);
    cout << "c = " << c << endl;
    cout << "d = " << d << endl;
}

int main() {
    test1();
    return 0;
}

总结:

  • 函数模板利用关键字template
  • 使用函数模板有两种方式:自动类型推导,显式指定类型
  • 模板的目的是为了提高复用性,将类型参数化 

5.2.2函数模板注意事项

 注意事项:

  • 自动类型推导,必须推导出一致的数据类型T,才可以使用
  • 模板必须要确定出T的数据类型,才可以使用
#include <iostream>
using namespace std;

// 1. 自动类型推导,必须推导出一致的数据类型T才可以使用
template<class T> // typename可以替换成class
void mySwap(T &a, T &b) {
    T temp = a;
    a = b;
    b = temp;
}

void test1() {
    int a = 10;
    int b = 20;
    char c = 'c';

//    mySwap(a, b); // 正确
//    mySwap(a, c); // 错误,推导不出一致的T类型
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
}

// 2. 模板必须要确定出T的数据类型,才可以使用
template<class T>
void func() {
    cout << "func 调用" << endl;
}

void test2() {
//    func(); // 错误
    func<int>(); // 正确

}
int main() {
//    test1();
    test2();
    return 0;
}

5.2.3函数模板案例

案例描述:

  • 利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序
  • 排序规则从大到小,排序算法为选择排序
  • 分别利用char数组和int数组进行测试
#include <iostream>
using namespace std;

// 交换函数模板
template<class T>
void mySwap(T &a, T &b) {
    T temp = a;
    a = b;
    b = temp;
}
// 排序算法
template<class T>
void mySort(T arr[], int len) {
    for (int i = 0; i < len; i++) {
        int max = i; // 认定最大值的下标
        for (int j = i + 1; j < len; j++) {
            // 认定的最大值,比遍历出的数值要下,说明j下标的元素才是真正的最大值
            if (arr[max] < arr[j]) {
                max = j; // 更新最大值下标
            }
        }
        if (max != i) {
            // 交换max和i元素
            mySwap(arr[max], arr[i]);
        }
    }
}

// 提供打印数组模板
template<class T>
void printArray(T arr[], int len) {
    for (int i = 0; i < len; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
}

// 测试char数组
void test1() {
    char charArr[] = "badcfe";
    int num = sizeof(charArr) / sizeof(char) - 1; // 否则最后一位出现乱码
    mySort(charArr, num);
    printArray(charArr, num);
}

// 测试int数组
void test2() {
    char intArr[] = "12321749539";
    int num = sizeof(intArr) / sizeof(char) - 1;
    mySort(intArr, num);
    printArray(intArr, num);
}

int main() {
    test1();
    test2();

    return 0;
}

5.2.4普通函数与函数模板的区别

  • 普通函数调用时可以发生自动类型转换(隐式类型转换)
  • 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换;如果利用显式类型的方式,可以发生隐式类型转换 
#include <iostream>
using namespace std;

// 普通函数
int myAdd1(int a, int b) {
    return a + b;
}

// 函数模板
template<class T>
T myAdd2(T a, T b) {
    return a + b;
}

void test1() {
    int a = 10;
    int b = 20;
    char c = 'c';

    // 普通函数
    cout << "myAdd1(a, b) = " << myAdd1(a, b) << endl; // 30
    cout << "myAdd1(a, c) = " << myAdd1(a, c) << endl; // 109
    // 自动类型推导
//    cout << "myAdd2(a, c) = " << myAdd2(a, c) << endl; // 错误
    // 显式指定类型
    cout << "myAdd2<int>(a, c) = " << myAdd2<int>(a, c) << endl; // 正确
}

int main() {
    test1();

    return 0;
}

 总结:建议使用显式指定类型的方式,调用函数模板,因为可以自己确定通用类型T

5.2.5普通函数与函数模板的调用规则

  • 如果函数模板和普通函数都可以实现,优先调用普通函数
  • 可以通过空模板参数列表来强调函数模板
  • 函数模板也可以发生重载
  • 如果函数模板可以产生更好的匹配,优先调用函数模板
#include <iostream>
using namespace std;

void myPrint(int a, int b) {
    cout << "调用的是普通函数" << endl;
}

template<class T>
void myPrint(T a, T b) {
    cout << "调用的是模板" << endl;
}

// 函数模板也可以发生重载
template<class T>
void myPrint(T a, T b, T c) {
    cout << "调用的是重载的模板" << endl;
}

void test1() {
    int a = 10;
    int b = 20;

    // 如果函数模板和普通函数都可以实现,优先调用普通函数
    myPrint(a, b); // 调用的是普通函数

    // 通过空模板参数与列表,强制调用函数模板
    myPrint<>(a, b); // 调用的是模板

    // 如果函数模板可以产生更好的匹配,优先调用函数模板
    myPrint<>(a, b, 100); // 调用的是重载的模板

    // 如果函数模板可以产生更好的匹配,优先调用函数模板
    char c1 = 'a';
    char c2 = 'b';
    myPrint(c1, c2); // 调用的是模板
}

int main() {
    test1();

    return 0;
}

总结:既然提供了函数模板,则最好不要提供普通函数,否则容易出现二义性 

5.2.6模板的局限性

  • 模板的通用性不是万能的
#include <iostream>
#include <string>
using namespace std;

class Person {
public:
    Person(string name, int age) {
        this->Name = name;
        this->Age = age;
    }
    string Name; // 姓名
    string Age; // 年龄
};
// 对比两个数据是否相等
template<class T>
bool myCompare(T &a, T &b) {
    if (a == b) {
        return true;
    }
    else {
        return false;
    }
}

// 利用具体化Perosn的版本实现代码,具体化实现调用
template<> bool myCompare(Person &p1, Person &p2) {
    if (p1.Name == p2.Name && p1.Age == p2.Age) {
        return true;
    }
    else {
        return false;
    }
}

void test1() {
    int a = 10;
    int b = 20;

    bool ret = myCompare(a, b);
    if (ret) {
        cout << "a == b" << endl;
    }
    else {
        cout << "a != b" << endl;
    }
}

void test2() {
    Person p1("Tom", 10);
    Person p2("Tom", 10);

    bool ret = myCompare(p1, p2);
    if (ret) {
        cout << "p1 == p2" << endl;
    }
    else {
        cout << "p1 != p2" << endl;
    }
}

int main() {
    test1();
    test2();

    return 0;
}

 总结:

  • 利用具体化的模板,可以解决自定义类型的通用化
  • 学习模板并不是为了写模板,而是在STL中能够运用系统提供的模板

5.3类模板

5.3.1类模板语法

  • 类模板作用:建立一个通用类,类中成员、数据类型可以不具体指定,用一个虚拟的类型来代表
  • 语法:
template<typename T>
类

解释:

  • template:声明创建模板
  • typename:表明其后面的符号是一种数据类型,可以用class代替
  • T:通用的数据类型,名称可以替换,通常大写字母
#include <iostream>
#include <string>
using namespace std;

// 类模板
template<class NameType, class AgeType>
class Person {
public:
    Person(NameType name, AgeType age) {
        this->Name = name;
        this->Age = age;
    }

    void showPerson() {
        cout << "name: " << this->Name << " age: " << this->Age << endl;
    }

    NameType Name;
    AgeType Age;
};

void test1() {
    Person<string, int> p1("孙悟空", 999);
    p1.showPerson();
}

int main() {
    test1();

    return 0;
}

总结:类模板和函数模板语法相似,在声明模板template后面加类,此类称为模板类 

5.3.2类模板与函数模板的区别

  • 类模板没有自动类型推导的使用方式
  • 类模板在模板参数列表中可以有默认参数
#include <iostream>
#include <string>
using namespace std;

// 类模板与函数模板区别
template<class NameType, class AgeType = int> // 默认参数int
class Person {
public:
    Person(NameType name, AgeType age) {
        this->Name = name;
        this->Age = age;
    }

    void showPerson() {
        cout << "name: " << this->Name << " age: " << this->Age << endl;
    }

    NameType Name;
    AgeType Age;
};

// 1.类模板没有自动类型推导使用方式
void test1() {
//    Person p("孙悟空", 999); // 错误
    Person<string, int>p("孙悟空", 999); // 正确
    p.showPerson();
}

//类模板在模板参数列表中可以有默认参数
void test2() {
    Person<string>p("猪八戒", 99); // 可以不写int
    p.showPerson();
}

int main() {
    test1();
    test2();

    return 0;
}

5.3.3类模板中成员函数创建时机

类模板中成员函数和普通类中成员函数创建时机是有区别的:

  • 普通类中的成员函数一开始就可以创建
  • 类模板中的成员函数在调用时才创建
#include <iostream>
using namespace std;

class Person1 {
public:
    void showPerson1() {
        cout << "Person1 show" << endl;
    }
};

class Person2 {
    void showPerson2() {
        cout << "Person2 show" << endl;
    }
};

template<class T>
class MyClass {
public:
    T obj;

    // 类模板中的成员函数
    void func1() {
        obj.showPerson1();
    }
    void func2() {
        obj.showPerson2();
    }

};
void test1() {
    MyClass<Person1> m;
    m.func1(); // 只有当func1()被调用时才会被创建,因为要先确定m的类型,才能确定func1()是否能被调用
//    m.func2(); // 类Perosn1中无showPerson2函数
}

int main() {
    test1();

    return 0;
}

 总结:类模板中的成员函数并不是一开始就创建的,在调用时才去创建

5.3.4类模板对象做函数参数

 学习目标:

  • 类模板实例化出的对象,向函数传参的方式

一共有三种传入方式:

  1. 指定传入的类型:直接显示对象的数据类型
  2. 参数模板化:将对象中的参数变为模板进行传递
  3. 整个类模板化:将这个对象类型模板化进行传递
#include <iostream>
#include <string>
using namespace std;

// 类模板对象做函数参数
template<class T1, class T2>
class Person {
public:
    Person(T1 name, T2 age) {
        this->Name = name;
        this->Age = age;
    }

    void showPerson() {
        cout << "姓名:" << this->Name << " 年龄:" << this->Age << endl;
    }

    T1 Name;
    T2 Age;
};

// 1.指定传入类型
void printPerson1(Person<string, int>&p) {
    p.showPerson();
}

void test1() {
    Person<string, int> p("孙悟空", 999);
    printPerson1(p);
}

// 2.参数模板化
template<class T1, class T2>
void printPerson2(Person<T1, T2>&p) {
    p.showPerson();
    cout << "T1的类型为:" << typeid(T1).name() << endl; // string
    cout << "T2的类型为:" << typeid(T2).name() << endl; // int
}

void test2() {
    Person<string, int> p("猪八戒", 99);
    printPerson2(p);
}

// 3.整个类模板化
template<class T>
    void printPerson3(T &p) {
    p.showPerson();
    cout << "T的类型为:" << typeid(T).name() << endl; // string,int
}

void test3() {
    Person<string, int> p("唐僧", 30);
    printPerson3(p);
}


int main() {
    test1();
    test2();
    test3();

    return 0;
}

总结:

  • 通过类模板创建的对象,可以有三种方式向函数中进行传参
  • 使用比较广泛的是第一种:指定传入类型 

5.3.5类模板与继承

当类模板碰到继承时,需要注意以下几点:

  • 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中的类型
  • 如果不指定,编译器无法给子类分配内存
  • 如果想灵活指定出父类中T的类型,子类也需变为类模板
#include <iostream>
using namespace std;

// 类模板与继承
template<class T>
class Base {
    T m;
};
//class Son: public Base { // 报错:缺少类模板"Base"的参数列表。必须要知道父类中的数据类型,才能继承给子类
class Son:public Base<int> {

};

void test1() {
    Son s1;
}

// 如果想灵活指定父类中T类型,子类也需要变为类模板
template<class T1, class T2>
class Son2:public Base<T2> {
public:
    Son2() {
        cout << "T1的类型为:" << typeid(T1).name() << endl; // int
        cout << "T2的类型为:" << typeid(T2).name() << endl; // char
    }
    T1 obj;
};

void test2() {
    Son2<int, char> S2; // int和char分别传给templa的T1和T2

}
int main() {
//    test1();
    test2();
    return 0;
}
  • 从非类模板派生出类模板:
#include <iostream>
using namespace std;

// 非模板类
class  CPoint {
public:
    CPoint(int x, int y) {
        X = x;
        Y = y;
    }
    void display() {
        cout << X << ", " << Y << endl;
    }
private:
    int X, Y;
};

// 类模板,继承的(X, Y)是圆心坐标
template <typename  T>
class Circle: public CPoint {
public:
    Circle(int x, int y, T r): CPoint(x, y) {
        radius = r;
    }
    void display() {
        CPoint::display();
        cout << "radius = " << radius << endl;
    }
private:
    T radius;
};

int main() {
    Circle<int> c1(2, 3, 2); // 派生类对象
    c1.display();

    Circle<double> c2(2, 3, 2.5);
    c2.display();

    return 0;
}
  • 从类模板派生出非类模板:
#include <iostream>
using namespace std;

template <typename T>
class CPoint {
public:
    CPoint(T x, T y) {
        X = x;
        Y = y;
    }
    void display() {
        cout << X << ", " << Y << endl;
    }
private:
    T X, Y;
};

// 非模板类,基类模板的类型参数应实例化
class Circle: public CPoint<double> {
public:
    Circle (double x, double y, int r): CPoint<double>(x, y) {
        radius = r;
    }
    void display() {
        CPoint<double>::display();
        cout << "radius = " << radius << endl;
    }
private:
    int radius;
};

int main() {
    CPoint<int> cp(4, 5); // 基类对象
    cp.display();
    Circle c(2.5, 3.5, 2); // 派生类对象
    c.display();

    return 0;
}
  • 从类模板派生出类模板:
#include <iostream>
using namespace std;

template <typename T>
class CPoint {
public:
    CPoint(T x, T y) {
        X = x;
        Y = y;
    }
    void display() {
        cout << X << ", " << Y << endl;
    }
private:
    T X, Y;
};

// 类模板
template <typename T>
class Circle: public CPoint<T> {
public:
    Circle (double x, double y, T r): CPoint<T>(x, y) {
        radius = r;
    }
    void display() {
        CPoint<T>::display();
        cout << "radius = " << radius << endl;
    }
private:
    T radius;
};

int main() {
    CPoint<int> cp(4, 5);
    cp.display();
    Circle<double> c1(2.5, 3.5, 2.5);
    c1.display();
    Circle<int> c2(2, 3, 2);
    c2.display();

    return 0;
}

5.3.6类模板成员函数类外实现

学习目标:能够掌握类模板中的成员函数类外实现

#include <iostream>
using namespace std;

// 类模板成员函数类外实现
template<class T1, class T2>
class Person {
public:
    Person(T1 name, T2 age);
//    {
//        this->Name = name;
//        this->Age = age;
//    }
    void showPerson();
//    {
//        cout << "姓名:" << this->Name << " 年龄:" << this->Age << endl;
//    }

    T1 Name;
    T2 Age;
};

// 构造函数类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age) {
    this->Name = name;
    this->Age = age;
}

// 成员函数类外实现
template<class T1, class T2>
void Person<T1, T2>::showPerson() {
    cout << "姓名:" << this->Name << " 年龄:" << this->Age << endl;
}

void test1() {
    Person<string, int> P("Tom", 20);
    P.showPerson();
}

int main() {
    test1();

    return 0;
}

总结:类模板中成员函数类外实现时,需要加上模板参数列表 

5.3.7类模板分文件编写

  • 学习目标:掌握类模板成员函数分文件编写产生的问题以及解决方法
  • 问题:类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到
  • 解决:
  1. 解决方式1:直接包含.cpp源文件
  2. 解决方式2:将声明和实现写在同一文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制

main.cpp

#include <iostream>
#include <string>
// 第一种解决方式:直接包含源文件
//#include "180person.h"
//#include "180person.cpp"
// 第二种解决方式:将.h和.cpp中的内容写到一起,将后缀名改为.hpp文件
#include "180person.hpp"
using namespace std;

// 类模板分文件编写问题以及解决

//template<class T1, class T2>
//class Person {
//public:
//    Person(T1 name, T2 age);
//
//    void showPerson();
//
//    T1 Name;
//    T2 Age;
//};

//template<class T1, class T2>
//Person<T1, T2>::Person(T1 name, T2 age) {
//    this->Name = name;
//    this->Age = age;
//}
//
//template<class T1, class T2>
//void Person<T1, T2>::showPerson() {
//    cout << "姓名:" << this->Name << " 年龄:" << this->Age << endl;
//}

void test1() {
    Person<string, int> p("Jerry", 18);
    p.showPerson();
}
int main() {
    test1();
}

person.cpp

#include "180person.hpp"

//template<class T1, class T2>
//Person<T1, T2>::Person(T1 name, T2 age) {
//    this->Name = name;
//    this->Age = age;
//}
//
//template<class T1, class T2>
//void Person<T1, T2>::showPerson() {
//    cout << "姓名:" << this->Name << " 年龄:" << this->Age << endl;
//}

 person.hpp

#pragma once
#include <iostream>
#include <string>
using namespace std;

template<class T1, class T2>
class Person {
public:
    Person(T1 name, T2 age);

    void showPerson();

    T1 Name;
    T2 Age;
};

template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age) {
    this->Name = name;
    this->Age = age;
}

template<class T1, class T2>
void Person<T1, T2>::showPerson() {
    cout << "姓名:" << this->Name << " 年龄:" << this->Age << endl;
}

 总结:主流的解决方式是第二种,将类模板成员函数写到一起,并将后缀名改为.hpp

5.3.8类模板与友元

学习目标:掌握类模板配合友元函数的类内和类外实现

  • 全局函数类内实现:直接子类内声明友元即可
  • 全局函数类内实现:需要提前让编译器知道全局函数的存在
#include <iostream>
#include <string>
using namespace std;

// 提前让编译器知道Person类的存在
template<class T1, class T2>
class Person;

// 类外实现
template<class T1, class T2>
void printPerson2(Person<T1, T2> p) { // 全局函数无需加作用域
    cout << "类外实现——姓名:" << p.Name << " 年龄:" << p.Age << endl;
}

// 通过全局函数打印Person的信息
template<class T1, class T2>
class Person {
    // 全局函数类内实现
    friend void printPerson(Person<T1, T2> p) {
        cout << "姓名:" << p.Name << " 年龄:" << p.Age << endl;
    }

    // 全局函数类外实现
    // 声明为函数模板:加空模板函数列表
    // 如果全局函数是类外实现,需要让编译器提前知道这个函数的存在
    friend void printPerson2<>(Person<T1, T2> p); // 注意<>


public:
    Person(T1 name, T2 age) {
        this->Name = name;
        this->Age = age;
    }

private:
    T1 Name;
    T2 Age;
};

// 1.全局函数在类内实现
void test1() {
    Person<string, int>p("Tom", 20);
    printPerson(p);
}

// 2.全局函数在类外实现
void test2() {
    Person<string, int>p("Jerry", 20);
    printPerson2(p);
}

int main() {
    test1();
    test2();

    return 0;
}

 总结:建议全局函数做类内实现,用法简单,而且编译器可以直接识别

5.3.9类模板案例

案例描述:实现一个通用的数组类,需求如下:

  • 可以对内置数据类型以及自定义数据类型的数据进行存储
  • 将数组中的数据存储在堆区
  • 构造函数中可以传入数组的容量
  • 提供对应的拷贝构造函数以及operator=防止浅拷贝问题
  • 提供尾插法和尾删法对数组中的数据进行增加和删除
  • 可以通过下标的方式访问数组中的元素
  • 可以获取数组中当前元素个数和数组的容量

 MyArray.hpp

// 自己的通用的数组类

#pragma once
#include <iostream>
using namespace std;

template<class T>
class MyArray {
public:
    // 有参构造,参数,容量
    MyArray(int capacity) {
        cout << "MyArray的有参构造调用" << endl;
        this->Capacity = capacity;
        this->Size = 0;
        this->pAddress = new T[this->Capacity]; // 将数组存储在堆区
    }

    // 拷贝构造
    MyArray(const MyArray& arr) {
        cout << "MyArray的拷贝构造调用" << endl;
        this->Capacity = arr.Capacity;
        this->Size = arr.Size;
//        this->pAddress = arr.pAddress; // 浅拷贝

        // 深拷贝
        this->pAddress = new T[arr.Capacity];
        // 将arr中的数据都拷贝过来
        for (int i = 0; i < this->Size; i++) {
            this->pAddress[i] = arr.pAddress[i];
        }
    }

    // operator=防止浅拷贝问题
    MyArray& operator=(const MyArray& arr) { // 返回自身引用
        cout << "MyArray的operator=调用" << endl;
        // 先判断原来堆区是否有数据,如果有先释放
        if (this->pAddress != NULL) {
            delete[] this->pAddress;
            this->pAddress = NULL;
            this->Capacity = 0;
            this->Size = 0;
        }

        // 深拷贝
        this->Capacity = arr.Capacity;
        this->Size = arr.Size;
        this->pAddress = new T[arr.Capacity];
        // 将arr中的数据都拷贝过来
        for (int i = 0; i < this->Size; i++) {
            this->pAddress[i] = arr.pAddress[i];
        }
        return *this;
    }

    // 尾插法
    void push_Back(const T & val) {
        // 判断容量是否等于大小
        if (this->Capacity == this->Size) {
            return;
        }
        this->pAddress[this->Size] = val; // 在数组末尾插入数据
        this->Size++; // 更新数组大小
    }

    // 尾删法
    void Pop_Back() {
        // 让用户访问不到最后一个元素,即为尾删,逻辑删除
        if (this->Size == 0) {
            return;
        }
        this->Size--;
    }

    // 通过下标访问数组中的元素
    T operator[](int index) {
        return this->pAddress[index];
    }

    // 返回数组的容量
    int getCapacity() {
        return this->Capacity;
    }

    // 返回数组大小
    int getSize() {
        return this->Size;
    }

    // 析构函数
    ~MyArray() {
        if (this->pAddress != NULL) {
            cout << "MyArray的析构函数调用" << endl;
            delete[] this->pAddress;
            this->pAddress = NULL;
        }
    }

private:
    T *pAddress; // 指针指向堆区开辟的真实数组

    int Capacity; // 数组容量

    int Size; // 数组大小
};

MyArray.cpp

#include <iostream>
#include <string>
using namespace std;
#include "183MyArray.hpp"

void printIntArray(MyArray <int> & arr) {
    for (int i = 0; i < arr.getSize(); i++) {
        cout << arr[i] << endl;
    }
}

void test1() {
    MyArray <int>arr1(5); // MyArray的有参构造调用

    for (int i = 0; i < 5; i++) {
        // 利用尾插法向数组中插入数据
        arr1.push_Back(i);
    }
    cout << "arr1的打印输出为:" << endl;
    printIntArray(arr1);
    cout << "arr1的容量为:" << arr1.getCapacity() << endl;
    cout << "arr1的大小为:" << arr1.getSize() << endl;

    MyArray <int>arr2(arr1); // MyArray的拷贝构造调用
    printIntArray(arr1);
    // 尾删
    arr2.Pop_Back();
    cout << "arr2尾删后:" << endl;
    cout << "arr2的容量为:" << arr2.getCapacity() << endl;
    cout << "arr2的大小为:" << arr2.getSize() << endl;


//    MyArray<int>arr3(100); // MyArray的有参构造调用

//    arr3 = arr1; // MyArray的operator=调用
}

// 测试自定义数据类型
class Person {
public:
    Person() {};
    Person(string name, int age) {
        this->Name = name;
        this->Age = age;
    }

    string Name;
    int Age;
};

void printPersonArray(MyArray<Person> & arr) {
    for (int i = 0; i < arr.getSize(); i++) {
        cout << "姓名:" << arr[i].Name << " 年龄:" << arr[i].Age << endl;
    }
}

void test2() {
    MyArray<Person> arr(10);

    Person p1("孙悟空", 999);
    Person p2("韩信", 30);
    Person p3("妲己", 20);
    Person p4("赵云", 25);
    Person p5("安其拉", 27);

    // 将数据插入到数组中
    arr.push_Back(p1);
    arr.push_Back(p2);
    arr.push_Back(p3);
    arr.push_Back(p4);
    arr.push_Back(p5);

    // 打印数组
    printPersonArray(arr);

    // 输出容量
    cout << "arr的容量为:" << arr.getCapacity() << endl;

    // 输出大小
    cout << "arr的大小为:" << arr.getSize() << endl;
}

int main() {
    test1();
    test2();
    return 0;
}

六、综合练习

6.1函数调用输出练习

在同样的作用域内,调用构造函数建立类对象的顺序是,谁的定义在前面,谁先建立;而调用析构函数删除类对象的顺序与构造的顺序正好相反。

调用函数进行参数传递时,会建立局部的参数对象,虽然参数传递的顺序是从左向右,但参数对象建立的顺序是从右向左。

6.1.1例1

#include <iostream>
using namespace std;

class A {
public:
    // 构造函数
    A(int j = 0): i(j) {
        cout << "A(int)" << i << endl;
    }
    // 拷贝构造函数
    A(const A& c): i(c.i) {
        cout << "A(const A&)" << i << endl;
    }
    ~A() {
        cout << "~A():" << i << endl;
    }
    void set() {
        i = i + 10;
    }
private:
    int i;
};

void func(A x, A y) { // 参数对象建立的顺序为从右到左
    x.set();
    y.set();
    cout << "in func." << endl;
}

int main() {
    A a(1), b(2);
    func(a, b);
    cout << "after func." << endl;

    return 0;
}

 运行结果:

A(int): 1 // 调用一般构造函数建立对象a
A(int):2 // 调用一般构造函数建立对象b
// 函数参数的建立顺序是从右向左
A(const A&):2 // *调拥拷贝构造函数建立函数参数对象y
A(const A&):1 // *调用拷贝构造函数建立函数参数对象x
in func.
// 析构的顺序与构造顺序相反
~A():11 // 析构x
~A():12 // 析构y
after func.
~A():2 // 析构b
~A():1 // 析构a
  • 函数参数的建立顺序是从右向左
  • 函数内的局部对象在函数调用完毕后就析构

6.1.2例2

#include <iostream>
using namespace std;
 
class CPoint {
public:
    CPoint(int x = 0, int y = 0) {
        X = x;
        Y = y;
        cout << "Constructor." << this << endl;
    }
    CPoint(const CPoint& ref) { // 拷贝构造函数
        X = ref.X;
        Y = ref.Y;
        cout << "Copy_constructor." << this << endl;
    }
    CPoint& operator=(const CPoint& ref) {
    	X = ref.X;
    	Y = ref.Y; 
    	cout << "operator=" << endl;
    	return *this;
	}
    ~CPoint() {
        cout << "Destructor." << this << endl;
    }
private:
    int X, Y;
};
 
CPoint func(CPoint r) {
    static CPoint dfunc(r); // 函数内的静态对象
    return dfunc; // 函数返回类型为CPoint
}
 
int main() {
    CPoint d1(4, 5); // 建立对象d1,调用普通构造函数
    CPoint d2(d1); // 建立对象d2,调用拷贝构造函数,用d1初始化
    CPoint *p = new CPoint(d1); // 建立堆对象,调用拷贝构造函数
    d1 = func(d2); // 调用函数func()
    delete p;
 
    return 0;
}

运行结果:

Constructor.0xf8e31ffa20 // 建立主函数内局部对象d1
Copy_constructor.0xf8e31ffa18 // 建立主函数内局部对象d2
Copy_constructor.0x23e2f126fb0 // 通过new建立堆对象
Copy_constructor.0xf8e31ffa30 // 建立函数func()内局部对象r
Copy_constructor.0x7ff7d6418048 // 建立函数func()内静态对象dfunc
Copy_constructor.0xf8e31ffa28 // 建立匿名对象(函数值返回)
operator=
Destructor.0xf8e31ffa28 // 删除匿名对象
Destructor.0xf8e31ffa30 // 删除函数func()内局部对象r
Destructor.0x23e2f126fb0 // 通过delete删除堆对象
Destructor.0xf8e31ffa18 // 删除主函数内局部对象d2
Destructor.0xf8e31ffa20 // 删除主函数内局部对象d1
Destructor.0x7ff7d6418048 // 删除静态对象dfunc

this中保存着对象的地址。

语句"CPoint* p = new CPoint(d1);"的功能是:用new建立一个CPoint类型的对象,使指针p指向该对象,并且用对象d1创建新建的类对象,这时调用的是拷贝构造函数。若将语句改为“CPoint* p = new CPoint(4, 5);”,则仍然是创立了新的对象,但是调用的是普通构造函数。

6.1.3例3

#include <iostream>
using namespace std;

class Compute {
private:
	int value;
public:
	Compute(int a = 0) {
		value = a;
		cout << "constructor is called." << endl;
	}
	Compute(const Compute& C) {
		value = C.value;
		cout << "copy-constructor is called." << endl;
	}
	Compute& operator=(const Compute &C) {
		value = C.value;
		cout << "assignment is called." << endl;
		return *this;
	}
	operator int() {
		cout << "operator int() is called." << endl;
		return value;
	}
	~Compute() {
		cout << "destructor is called." << endl;
	}
	friend const Compute operator-(const Compute& C1, const Compute& C2);
};

const Compute operator-(const Compute& C1, const Compute& C2) {
	cout << "operator-() is called." << endl;
	return Compute(C1.value - C2.value); // 此处有一次构造和析构
}

int main() {
	Compute m(5), n = m; // constructor is called.copy-constructor is called.(Compute n = m)
	m = m - n; // operator-() is called.constructor is called.assignment is called.
	int result = m; // operator int() is called.
	cout << "result = " << result << endl; // result = 0
	// destructor is called.destructor is called.
}

运行结果:

constructor is called.
copy-constructor is called.
operator-() is called.
constructor is called.
assignment is called.
destructor is called.
operator int() is called.
result = 0
destructor is called.
destructor is called.
  • m=m-n要调用构造函数 

6.1.4例4 

#include <iostream>
using namespace std;

class MyClass {
private:
	const int length;
	static int num;
public:
	MyClass(int r = 0): length(r) {
		num++;
		cout << "length = " << length << ",num = " << num << endl;
	}
	int GetLength() {
		return num * length;
	}
	~MyClass() {
		num--;
		cout << "length = " << length << ",num = " << num << endl;
	}
};

int MyClass::num = 3;

int main() {
	MyClass S1(3), &S2 = S1, *S3 = new MyClass[2];
	
	cout << "The length is " << S1.GetLength() << endl;
	delete[] S3;
	cout << "The length is " << S1.GetLength() << endl;
	
	return 0;
}

运行结果:

length = 3,num = 4
length = 0,num = 5
length = 0,num = 6
The length is 18
length = 0,num = 5
length = 0,num = 4
The length is 12
length = 3,num = 3
  • 引用不构造,数组构造n次
  • const对象在初始化列表中初始化
  • static对象在类外初始化,前面无static,再次调用时不清零,可以作为计数变量

6.1.5例5

#include <iostream>
using namespace std;

class Instrument {
public:
	virtual void play() const {
		cout << "Instrument play" << endl;
	} 	
}; 

class Wind: public Instrument {
public:
	void play() const {
		cout << "Wind play" << endl;
	}	
};

class Brass: public Wind {

};

void tune(Instrument& i) {
	i.play();
}

int main() {
	Instrument instrument;
	Wind flute;
	Brass horn;
	
	tune(instrument);
	tune(flute);
	tune(horn);
	
	return 0;
}

 运行结果:

Instrument play
Wind play
Wind play
  •  若本类中无对应函数,则遵守向基类的“就近原则”

6.1.6例6

#include <iostream>
using namespace std;

class CRectangle {
public: 
    int area() {
		return 4;
	}
};

class CSquare: virtual public CRectangle {
public:
    int area() {
		return 8;
	}
};

class CDiamond: virtual public CSquare {
public:
    int area() {
		return 16;
	};
};

int main() {
	CSquare sq;
	CDiamond dia;
	
	cout<<sq.area()<<endl;
	
	CRectangle &rc=sq; 
	rc=sq;
	cout<<rc.area()<<endl;
	
	rc=dia;
    cout<<rc.area()<<endl;
    
    return 0;
}

 运行结果:

8
4
4
  • 虚继承可以使子类函数成功重写
  • CRectangle &rc = sq,rc=sq,rc=ida进行向上类型转换

6.1.7例7

#include "iostream"
using namespace std;
class MyClass {
private:
	double m_dData;
public:
    MyClass(double d=0) {
		cout << "An object is created by " << d << endl;
    	m_dData = d;
	}
    operator double () {
		cout << "Type-cast of double() is called." << endl;
    	return m_dData;
	}
    double GetData() {
		return m_dData;
	}
    friend MyClass operator-(double d, MyClass m);
    friend int main(int argc, char* argv[]);
};

MyClass operator-(double d, MyClass m) {
	cout << "The friend operator \'-\' is called." << endl;
	return MyClass(d - m.m_dData);
}

MyClass operator+(double d, MyClass m) {
	cout << "The operator \'+\' is called." << endl;
	return MyClass(d - m.GetData());
}

int main(int argc, char* argv[]) {
	MyClass m(3);
	m = m - 2;
	cout << "The data member of m is " << m.m_dData << endl;
	
	return 0;
}

 运行结果:

An object is created by 3
Type-cast of double() is called.
An object is created by 1
The data member of m is 1
  • operator+函数中为m.GetData()所以不需要为友元函数,main函数中为m.m_dData所以需要为友元函数
  • operator-函数的形参顺序为先double后MyClass,所以不会被m-2调用
  • m-2发生自动类型转换,使用重载double函数
  • operator=函数省略,其形参为MyClass类型,则m-2调用构造函数

6.1.8例8

#include "iostream"
using namespace std;
class Embedded {
private:
	int e;
public:
    Embedded(int n) {
		e = n;
	}
    int GetEmbedded() {
		return e;
	}
};

class Base {
private:
	int b;
	Embedded em;
public:
    Base(int i = 0): em(100 * i) {
		b = i;
	}
    friend class Drv; // 填空 
    friend int main(int argc, char* argv[]);
};

class Drv1: virtual public Base {
private:
	int d1;
public:
	Drv1(int i, int j) {
		d1 = j;
	}
};

class Drv2: virtual public Base {
private:
	int d2;
public:
	Drv2(int i, int j) {
		d2 = j;
	}
};

class Drv: virtual public Drv1, virtual public Drv2 {
private:
	int d;
public:
    Drv(int i, int j, int k, int l): Base(i), Drv1(i, j), Drv2(i, k) {
		d = l;
	}
    Embedded& GetEmbedded() {
		return Base::em;
	}
};

int main(int argc, char* argv[]) {
	Drv obj(1, 2, 3, 4);
	obj.Drv1::b = 11;
	obj.Drv2::b = 21;
	cout << obj.GetEmbedded().GetEmbedded() << endl; // 填空 

	cout<<obj.Drv1::b<<endl;
	cout<<obj.Drv2::b<<endl;
	
	return 0;
}

运行结果:

100
21
21
  • 注意两个填空位置
  • obj.Drv1::b和obj.Drv2::b指向的都是Base的b,值从1->11->21

6.1.9例9

#include <iostream> 
using namespace std;
 
class MyComplex {
private:
	double m_dR;
	double m_dI;
public:
	template <class T>
	MyComplex(T r = 0, T i = 0) {
   		m_dR = r;
		m_dI = i;
    	cout<<"constructor"<<endl;
	}
	operator double() {
		cout<<"type cast"<<endl;
    	return m_dR;
	}
	MyComplex& operator=(const MyComplex &ref) {
		cout << "operator=" << endl;
		this->m_dR = ref.m_dR;
		this->m_dI = ref.m_dI;
		return *this;
	}
	friend ostream& operator << (ostream& os, MyComplex c);
};
 
ostream& operator<< (ostream& os, MyComplex c) {
	os << c.m_dR << " + i" << c.m_dI << endl;
	return os;
}
 
int main() {
	MyComplex c1(1.0, 2.0);
	c1 = c1 + 1; 
	cout << c1;
	
	return 0; 
}

运行结果:

constructor
type cast
constructor
operator=
2 + i0
  • c1 + 1调用double运算符重载,作用是取c1的实部,舍弃c1的虚部
  • c1 = c1 + 1调用构造函数(先)=运算符重载(后)
  • cout输出时虚部为0 

6.1.10例10

#include <iostream>
using namespace std;

class Sample {
private:
	int N;
public:
	Sample() {
		cout << "无参构造" << endl;
	}
	Sample(int n) {
		N = n;
		cout << "有参构造" << endl;
	}
	Sample(const Sample &s) {
		N = s.N;
		cout << "拷贝构造" << endl; 
	}
};

int main() {
	Sample S1, S2(2), S3[3], &S4 = S1, &S5(S1);
	
	return 0; 
}

 运行结果:

无参构造
有参构造
无参构造
无参构造
无参构造
  • 数组调用多次构造函数,次数为数组大小
  • 指针不调用构造函数
  • 引用不调用构造函数,也不调用拷贝构造函数

6.1.11例11

#include <iostream>
using namespace std; 

class A {
public:
	A() {
  		cout << "A Constructor" << endl;
  	}
	virtual ~A() {
		cout << "A Destructor" << endl;
	}
 	virtual void f() {
  		cout << "A::f( )" << endl;
	}
  	void g() {
  		f();
	}
};

class B: public A {
public:
  	B() {
  		f();
		cout << "B Constructor" << endl;
	}
	~B() {
		cout << "B Destructor" << endl;
	}
};

class C: public B {
public:
  	C() {
  		f();
		cout << "C Constructor" << endl;
	}
	~C() {
		cout << "C Destructor" << endl;
	}
	void f() {
		cout << "C::f()" << endl;
	}
};

int main() { 
	A *p=new C;
  	p->g( );
  	delete p;

	return 0; 
}
A Constructor // 若构造B,先构造A 
A::f() // 构造B时,由于B类中无F(),所以找最近的基类A,调用A的f() 
B Constructor // 若构造C,先构造B 
C::f() // 构造C时, 调用C的f() 
C Constructor // 构造C 
C::f() // 调用g()中的f(),由于只有父类的指针或者引用才能调用虚函数,这里调用的是虚函数的重写函数 
C Destructor
B Destructor
A Destructor

6.1.12例12

#include <iostream>
using namespace std;

class Compute {
private:
	int value;
public:
	Compute(int a = 0) {
		value = a;
		cout << "constructor is called." << endl;
	}
	Compute(const Compute& C) {
		value = C.value;
		cout << "copy-constructor is called." << endl;
	}
	Compute& operator=(const Compute& C) {
		value = C.value;
		cout << "assignment is called." << endl;
		return *this;
	}
	operator int() {
		cout << "operator int() is called." << endl;
		return value;
	}
	~Compute() {
		cout << "destructor is called. value = " << value << endl;
	}
	friend const Compute operator-(const Compute &C1, const Compute &C2);
};

const Compute operator-(const Compute &C1, const Compute &C2) { 
	cout << "operator-() is called." << endl;
	return Compute (C1.value - C2.value);
}

int main() {
	Compute m(5), n=m;
	m = m - n;
	int result = m;
	cout << "result = " << result << endl;
	return 0;
}

运行结果:

constructor is called. // m(5)
copy-constructor is called. // n = m
operator-() is called. // m - n
constructor is called. // Compute(C1.value - C2.value)
assignment is called. // m = m - n
destructor is called. value = 0 // Compute(C1.value - C2.value),注意等执行完m = m - n语句后才析构
operator int() is called. // int result = m
result = 0 // cout
destructor is called. value = 5 // n
destructor is called. value = 0 // m,注意m = m - n已经使得m的value为0

6.1.13例13 

#include <iostream>
using namespace std;

class Function {
public:
	Function(int a, int b) {
		cout << "Constructor is called." << endl;
		A = a;
		B = b;
	}
	int operator()(int x, int y) {
		cout << "operator() is called." << endl;
		return A * x + B * y;
	}
	operator int() {
		cout << "int() is called. " << endl;
		return 2 + (*this)(2,5);
	}
	int f(int x, int y) {
		cout << "f(int x, int y) is called." << endl;
		return A * (x + y) + B;
	}
private:
	int A, B;
};

int main() {
	Function f(2,5);
	int i = f;
	cout << "i = " << i << endl;
	cout << "f(2,5) = " << f(2,5) << endl;

	return 0;
}

运行结果:

Constructor is called.
int() is called.
operator() is called.
i = 31
operator() is called.
f(2,5) = 29
  •  语句执行顺序为从右向左,则f(2,5)执行operator()语句而不是f(int x, int y)语句

6.1.14例14

#include<iostream>
using namespace std;
class Base {
public:
	Base(int i) {
		cout << i;
	}
	~Base () {
	
	}
};

class Base1: virtual public Base {
public:
	Base1(int i, int j = 0): Base(j) {
		cout << i;
	}
	~Base1() {
	
	}
};

class Base2: virtual public Base {
public:
	Base2(int i, int j = 0): Base(j) {
		cout << i;
	}
	~Base2() {
	
	}
};

class Derived: public Base2, public Base1 {
public:
	Derived(int a, int b, int c, int d) : mem1(a), mem2(b), Base1(c), Base2(d), Base(a) {
		cout << b;
	}
private:
	Base2 mem2;
	Base1 mem1;
};

int main() {
	Derived objD (1, 2, 3, 4);
	return 0;
}

运行结果:

14302012
  • 调用顺序为:Base(1),Base2(4),Base1(3),Base(mem2)(j = 0),Base2(mem2)(2),Base(mem1)(j = 0),Base1(mem1)(j = 0),b(Derived)(2)
  • 注意还要构造mem2和mem1
  • 构造mem2和mem1时先调用Base

6.1.15例15

#include <iostream>
using namespace std; 

class CSample {
       char ch1, ch2; 
public:
    friend void set(CSample &s, char c1, char c2);
    
    CSample(char a, char b) {
        ch1 = a;
		ch2 = b;
        cout << "CSample Constructor" << endl;
	}
	
    CSample(const CSample & rhs) {
        ch1 = rhs.ch1;
        ch2 = rhs.ch2;
        cout << "CSample Copy-constructor" << endl;
	}
	
    CSample& operator=(const CSample& rhs) {
        ch1 = rhs.ch1;
        ch2 = rhs.ch2;
        cout << "CSample Operator =" << endl;
        return *this;
	}
	
    ~CSample() {
        cout << "ch1=" << ch1 << ",ch2=" << ch2 << endl;
	}
};

void set(CSample &s,char c1,char c2) {
	s.ch1=c1;
	s.ch2=c2;
}

CSample fun(CSample obj) {
	set(obj,'7','9');
	return obj;
}

int main() { 
    CSample obj1('7', '8');
    CSample obj3('7', '7');
    CSample obj2 = obj1 = obj3;     
    obj2 = fun(obj1);
    
    return 0;
}

运行结果:

CSample Constructor // 执行obj1('7', '8'),构造obj1,调用普通构造函数
CSample Constructor // 执行obj3('7', '7'),构造obj3,调用普通构造函数
CSample Operator = // 执行obj1 = obj3,调用=号重载函数,注意=号为从右向左顺序 
CSample Copy-constructor // 执行CSample obj2 = obj1,调用拷贝构造函数,注意这里有CSample,应与上面区分 
CSample Copy-constructor // 执行obj2 = fun(obj1),为建立obj执行CSample obj(obj1),调用拷贝构造函数,将obj1的值改为('7','9')
CSample Copy-constructor // 在函数返回obj时,建立了一个CSample型匿名对象,该对象被初始化为obj的值,调用拷贝构造函数,执行函数func()的右边括号后,释放对象obj 
CSample Operator = // 将匿名对象的值赋给obj2,调用=号重载函数,再释放匿名对象
ch1=7,ch2=9 // 析构obj2 
ch1=7,ch2=9 // 析构匿名对象 
ch1=7,ch2=9 // 析构匿名对象obj,即一个拷贝构造函数的调用即产生一个匿名对象
ch1=7,ch2=7 // 析构obj3
ch1=7,ch2=7 // 析构obj1,注意此时obj1已经变为('7','7')

6.1.16例16

#include <iostream>
using namespace std;

class Base {
public:
    Base() {}
    virtual void f() {
		cout << "Base::f() called!" << endl;
	}
    virtual ~Base() {
    	cout << "~Base()---";
       	f();
    }
};

class De: public Base {
public:   
    De() {}
	void f() {
		cout << "De::f() called!" << endl;
	}  
    ~De() {
		cout << "~Base()---";
		f();
	}
};

int main() {
    De d;
    Base b = d, *pb = new De;
    b.f();
    pb->f();
    delete pb;
    
    return 0;
}

运行结果:

Base::f() called!
De::f() called!
~Base()---De::f() called! // 析构pb,先析构子类 
~Base()---Base::f() called! // 析构pb,后析构父类 
~Base()---Base::f() called! // 析构b
~Base()---De::f() called! // 析构d,先析构子类 
~Base()---Base::f() called! // 析构d,后析构父类
  • 有delete先析构

6.2代码填空练习题

6.2.1例1

#include "iostream"
using namespace std;
class Point {
private:
	double m_dX;
    double m_dY;
public:
	// 填空 
	Point(double x = 0, double y = 0) {
		m_dX = x;
		m_dY = y;
	}
};
class Bottom {
protected:
	double m_BttmLen;
    double m_Height;
    Point m_Pos;
public:
    Bottom(double x = 0, double y = 0, double l = 0, double h = 0): m_Pos(x, y) {
		m_BttmLen=l;
		m_Height=h;
	}
	// 填空 
    virtual double Area() = 0;
    // 填空 
    virtual double Volume() = 0;
};

class Circle: virtual public Bottom {
private:
	double m_dRadius;
public:
    Circle (double x = 0, double y = 0, double r = 0) 
	{
		m_dRadius = r;
	}
    virtual double Area() {
		// 填空
		return 3.14 * m_dRadius * m_dRadius; 
	}
    virtual double Volume() {
		return 0;
	}
};

class Rect: virtual public Bottom
{
public:
    Rect(double x, double y, double BttmLen, double Height): Bottom(x, y, BttmLen, Height) {
	
	}
    virtual double Area() {
		// 填空
		return m_BttmLen * m_Height; 
	}
    virtual double Volume() {
		return 0;
	}
};

class Triangle: virtual public Bottom {
public:
    Triangle(double x, double y, double l, double h): Bottom(x, y, l, h)  {
	
	}
    virtual double Area() {
		return 0.5 * m_BttmLen * m_Height;
	}
    virtual double Volume() {
	 	return 0;
	}
};

// 填空 
template <class BTYPE>
class Cylinder: virtual public BTYPE {
private:
	double m_bHeight;
public:
    Cylinder(double x, double y, double BL, double bH, double h): BTYPE(x, y, BL, bH), Bottom(x, y, BL, bH) {
		m_bHeight = h;
	}
    Cylinder(double x, double y, double h, double r): Circle(x, y, r), Bottom(x, y, 0, 0) {
		m_bHeight = h;
	}
	double Volume() {
		// 填空 
		return Cylinder::Area() * m_bHeight;
	}
};

int main(int argc, char* argv[]) {
	Bottom *pBttm;
	// 填空 
    Cylinder<Circle> Clndr1(1,2,3, 4); // Clndr1 is a cylinder with a circular bottom 
    // 填空 
	Cylinder<Triangle> Clndr2(1,2,3,4,5); // Clndr2 is a cylinder with a triangular bottom 
    // 填空 
	Cylinder<Rect> Clndr3(1,2,3,4,5); // Clndr3 is a cylinder with a rectangular bottom 
    
    // 填空
	pBttm = &Clndr1;
	cout << pBttm->Area() << endl; // prints the area of Clndr1
    // 填空
	pBttm = &Clndr2;
	cout << pBttm->Area() << endl; // prints the area of Clndr2
    // 填空
	pBttm = &Clndr3;
	cout << pBttm->Area() << endl; // prints the area of Clndr3
	
	return 0;
}

运行结果:

50.24
6
12
  • 构造函数形参带默认值
  • π写成3.14
  • 注意模板的使用:Cylinder<Circle>
  • 这里非常巧妙,前面用了BTYPE作为Cylinder的基类,则Clndr1定义时用Cylinder<Circle>则使得Clndr的基类为Circle,则后面用pBttm = &Clinder1等价于Bottom *pBttm = &Clinder1时,向上类型转换转换出的类型为Circle

6.3编程练习题

6.3.1例1

Use class template to implement a simple and universal stack, named MyStack, which contains items of type TYPE and the maximum number of items can reach up to MaxItemNum. The detailed requirements are as follows. 
(1)In MyStack, the following member functions are defined: 
int Push(TYPE e): Inserts an element of type TYPE at the end of the controlled array.
It returns the position of the end item.
TYPE GetTop() : Return the top item which locates at the end of the controlled array.
TYPE Pop(): Removes and return the top item at the end of the controlled array, which must be non-empty.
void CleanUp(): Delete all item in the MyStack.
booI sEmpty(): The member function returns true for an empty controlled sequence.
and so on: Other necessary functions such as some constructors and so on. 
(2)Use your MyStack to invert a string defined by yourself. "abcdefg", for example, will be inverted as "gfedcba".

用数组版本:

#include <iostream>
#include <cstring>
using namespace std;

template <class TYPE, int Max>
class MyStack {
private:
	TYPE Data[Max];
    int Top;
public:
	// 构造函数 
    MyStack() {
    	Top = 0;
    	memset(Data, '\0', Max*sizeof(TYPE));
	}
	
	// 元素入栈 
    int Push(TYPE e) {
		Top += 1;
		Data[Top]=e;
		return Top;
	}
	
	// 获取栈顶 
	TYPE GetTop() {
		return Data[Top];
	}
	
	// 元素出栈 
    TYPE Pop() {
		TYPE e = Data[Top];
    	if(Top >= 1) {
			Top -= 1;
			return e;
		}
	    else {
		   	cout << "No item in the MyStack." << endl;
			exit(1);
		}
    }
	
	// 判断是否为空 
    bool IsEmpty() {
		return Top <= 0;
	}
	
	// 清空 
    void CleanUp() {
		memset(Data, '\0', Max*sizeof(TYPE));
		Top = 0;
	}
};

int main() {
	MyStack <char, 100> s;
  	char str[] = "abcdefg";
  	int len = strlen(str);
  	
  	// 元素入栈 
  	for(int i = 0; i < len; i++) {
  		s.Push(str[i]);
	}
	
	// 获取栈顶
	cout << s.GetTop() << endl;
	 
	// 判断是否为空
	cout << s.IsEmpty() << endl;
  	
	// 元素出栈,再次进入str,相当于str取反 
  	for(int i = 0; i < len; i++) {
  		str[i] = s.Pop();
	}
	
	// str的逆序输出 
  	cout << str << endl;
  	
  	s.CleanUp();
  	
  	return 0;
}

用指针版本:

#include <iostream>
#include <cstring>
using namespace std;

template <class TYPE, int Max>
class MyStack {
private:
	TYPE *Data;
    int Top;
    
public:
	// 构造函数 
    MyStack() {
    	Top = 0;
		Data = new TYPE[Max + 1];
	}
	
	// 元素入栈 
    int Push(TYPE e) {
		Top += 1;
		Data[Top]=e;
		return Top;
	}
	
	// 获取栈顶 
	TYPE GetTop() {
		return Data[Top];
	}
	
	// 元素出栈 
    TYPE Pop() {
		TYPE e = Data[Top];
    	if(Top >= 1) {
			Top -= 1;
			return e;
		}
	    else {
		   	cout << "No item in the MyStack." << endl;
			exit(1);
		}
    }
	
	// 判断是否为空 
    bool IsEmpty() {
		return Top <= 0;
	}
	
	// 清空 
    void CleanUp() {
		if (Data != NULL) {
    		delete[] Data;
		}
		Top = 0;
	}
};

int main() {
	MyStack <char, 100> s;
  	char str[] = "abcdefg";
  	int len = strlen(str);
  	
  	// 元素入栈 
  	for(int i = 0; i < len; i++) {
  		s.Push(str[i]);
	}
	
	// 获取栈顶
	cout << s.GetTop() << endl;
	 
	// 判断是否为空
	cout << s.IsEmpty() << endl;
  	
	// 元素出栈,再次进入str,相当于str取反 
  	for(int i = 0; i < len; i++) {
  		str[i] = s.Pop();
	}
	
	// str的逆序输出 
  	cout << str << endl;
  	
  	s.CleanUp();
  	
  	return 0;
}

6.3.2例2

Define three classes, Shape, Point and Circle:
(1)Shape is an abstract class with two pure virtual functions: Area() and Perimeter().
(2)Point has data members: X and Y, as coordinates of a point.
(3)Circle is inherited from Shape, with data members: Radius(半径), and P(圆心) of class Point, and
a)member function Area() to get the area of a circle;
b)member function Perimeter() to get the perimeter(周长) of a circle.
(4)If necessary, you can add other members for the three classes. 
(5)A programmer can use Shape, Point and Circle in main() as follows:
void main() {
    Point  A(5.8, 4.1); 
    Shape *pS = new Circle(A, 3);     // 3 is radius of a circle
    cout << pS->Area() << endl;     // show the area of the circle
    cout << pS->Perimeter() << endl; // show the perimeter of the circle
    delete pS;
}

#include <iostream>
using namespace std;

class Shape {
public:
	virtual double Area() = 0;
	virtual double Perimeter() = 0;
};

class Point {
private:
	double X, Y;
public:
	Point (int x, int y) {
		X = x;
		Y = y;
	}
	double getX() {
		return X;
	}
	double getY() {
		return Y;
	}
};

class Circle: public Shape, public Point {
private:
	double R;
public:
	Circle(Point p, double r): Point(p.getX(), p.getY()) { // 注意这里写法
		R = r;
	}
	double Area() {
		return 3.14 * R * R;
	}
	double Perimeter() {
		return 2 * 3.14 * R;
	}
};


int main() {
	Point A(5.8, 4.1);
	Shape *pS = new Circle(A, 3); // 3是半径
	cout << pS->Area() << endl; // 圆的面积
	cout << pS->Perimeter() << endl; // 圆的周长
	
	return 0; 
}
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值