C++-Part2——核心编程
[TOC]
内存分区模型
内存分区概述
程序运行前
- 在程序编译后,生成了 exe 可执行程序,在程序运行前分为全局区和代码区。
- 代码区:
- 存放 CPU 执行的机器指令
- 代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
- 代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令
- 全局区:
- 全局变量、静态变量存放在此.
- 全局区包含常量区,存放字符串常量、const 修饰的全局常量.
- 该区域的数据仅在程序结束后由操作系统释放
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| int g_a = 10; int g_b = 10;
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; cout << "局部变量b地址为: " << (int) &b << endl;
cout << "全局变量g_a地址为: " << (int) &g_a << endl; cout << "全局变量g_b地址为: " << (int) &g_b << endl;
static int s_a = 10; static int s_b = 10;
cout << "静态变量s_a地址为: " << (int) &s_a << endl; cout << "静态变量s_b地址为: " << (int) &s_b << endl;
cout << "字符串常量地址为: " << (int) &"hello world" << endl; cout << "字符串常量地址为: " << (int) &"hello world1" << endl;
cout << "全局常量c_g_a地址为: " << (int) &c_g_a << endl; cout << "全局常量c_g_b地址为: " << (int) &c_g_b << endl;
const int c_l_a = 10; const int c_l_b = 10; cout << "局部常量c_l_a地址为: " << (int) &c_l_a << endl; cout << "局部常量c_l_b地址为: " << (int) &c_l_b << endl;
return 0; }
|
程序运行后
- 栈区:
- 由编译器自动分配释放, 存放函数的参数值,局部变量等
- 注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
1 2 3 4 5 6 7 8 9 10 11 12 13
| int *func() { int a = 10; return &a; }
int main() { int *p = func();
cout << *p << endl; cout << *p << endl;
return 0; }
|
- 堆区:
- 由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
- 在 C++ 中主要利用 new 在堆区开辟内存
1 2 3 4 5 6 7 8 9 10 11 12 13
| int *func() { int *a = new int(10); return a; }
int main() { int *p = func();
cout << *p << endl; cout << *p << endl;
return 0; }
|
new 操作符
利用 new 操作符在堆区开辟数据
利用 delete 操作符在堆区释放数据
语法:
返回的是该类型的指针/地址
删除的类型应当是指针/地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| int *func() { int *a = new int(10); return a; }
int main() { int *p = func(); cout << *p << endl;
delete p; cout << *p << endl;
return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| int main() { int *arr = new int[10]{1, 2, 3, 4, 5,};
for (int i = 0; i < 10; i++) { arr[i] = i + 100; }
for (int i = 0; i < 10; i++) { cout << arr[i] << endl; } delete[] arr;
return 0; }
|
引用
参考:c++中,引用和指针的区别是什么? - 编程指北的回答 - 知乎
引用的基本使用
作用:给变量起别名
语法:数据类型 &别名 = 原名
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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; }
|
引用注意事项
1 2 3 4 5 6 7 8 9 10 11 12 13
| int main() { int a = 10; int b = 20; int &c = a; c = b;
cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl;
return 0; }
|
引用做函数参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| void mySwap01(int a, int b) { int temp = a; a = b; b = temp; }
void mySwap02(int *a, int *b) { int temp = *a; *a = *b; *b = temp; }
void mySwap03(int &a, int &b) { int temp = a; a = b; b = temp; }
int main() { int a = 10; int b = 20; cout << "a:" << a << " b:" << b << endl;
mySwap01(a, b); cout << "a:" << a << " b:" << b << endl;
mySwap02(&a, &b); cout << "a:" << a << " b:" << b << endl;
mySwap03(a, b); cout << "a:" << a << " b:" << b << endl;
return 0; }
|
引用做函数返回值
作用:引用是可以作为函数的返回值存在的
注意:不要返回局部变量引用
用法:函数调用作为左值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| int &test01() { int a = 10; return a; }
int &test02() { static int a = 20; return a; }
int main() { int &ref = test01(); cout << "ref = " << ref << endl; cout << "ref = " << ref << endl;
int &ref2 = test02(); cout << "ref2 = " << ref2 << endl; cout << "ref2 = " << ref2 << endl;
test02() = 1000;
cout << "ref2 = " << ref2 << endl; cout << "ref2 = " << ref2 << endl;
return 0; }
|
引用的本质
- 本质:引用的本质在 C++ 内部实现是一个指针常量。
- C++ 推荐用引用技术,因为语法方便,引用本质是指针常量,但是所有的指针操作编译器都帮我们做了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| void func(int &ref) { ref = 100; }
int main() { int a = 10;
int &ref = a; ref = 20;
cout << "a:" << a << endl; cout << "ref:" << ref << endl;
func(a); return 0; }
|
常量引用
- 作用:常量引用主要用来修饰形参,防止误操作
- 在函数形参列表中,加 const 修饰形参,防止形参改变实参
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
void showValue(const int &v) { cout << v << endl; }
int main() { const int &ref = 10;
cout << ref << endl;
int a = 10; showValue(a);
return 0; }
|
函数提高
函数默认参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| int func1(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 << "ret = " << func1(20, 20) << endl; cout << "ret = " << func2(100) << endl;
return 0; }
|
函数占位参数
1 2 3 4 5 6 7 8 9 10
| void func(int a, int = 10) { cout << "this is func" << endl; }
int main() { func(10, 10);
return 0; }
|
函数重载
函数重载概述
作用:函数名可以相同,提高复用性
函数重载满足条件:
- 同一个作用域下
- 函数名称相同
- 函数参数类型不同或者个数不同或者顺序不同。
注意: 函数的返回值不可以作为函数重载的条件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| void func() { cout << "func 的调用!" << endl; }
void func(int a) { cout << "func (int a) 的调用!" << endl; }
void func(double a) { cout << "func (double a)的调用!" << endl; }
void func(int a, double b) { cout << "func (int a ,double b) 的调用!" << endl; }
void func(double a, int b) { cout << "func (double a ,int b)的调用!" << endl; }
int main() { func(); func(10); func(3.14); func(10, 3.14); func(3.14, 10);
return 0; }
|
函数重载注意事项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| void func1(int &a) { cout << "func (int &a) 调用 " << endl; }
void func1(const int &a) { cout << "func (const int &a) 调用 " << endl; }
void func2(int a, int b = 10) { cout << "func2(int a, int b = 10) 调用" << endl; }
void func2(int a) { cout << "func2(int a) 调用" << endl; }
int main() { int a = 10; func1(a); func1(10);
return 0; }
|
类和对象
面向对象的三大特性为:封装、继承、多态。
万事万物都皆为对象,对象上有其属性和行为。
封装
封装的意义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| class Person { public: string m_Name;
protected: string m_Car;
private: int m_Password;
public: void func() { m_Name = "张三"; m_Car = "拖拉机"; m_Password = 123456; } };
int main() { Person p; p.m_Name = "李四"; p.func();
return 0; }
|
truct 和 class 区别
成员属性设置为私有
- 优点
- 将所有成员属性设置为私有,可以自己控制读写权限
- 对于写权限,我们可以检测数据的有效性
对象的初始化和清理
构造函数和析构函数
- 构造函数
类名(){}
:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
- 构造函数,没有返回值也不写 void
- 函数名称与类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
- 析构函数
~类名(){}
:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
- 析构函数,没有返回值也不写 void
- 函数名称与类名相同,在名称前加上符号 ~
- 析构函数不可以有参数,因此不可以发生重载
- 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class Person { public: Person() { cout << "Person的构造函数调用" << endl; }
~Person() { cout << "Person的析构函数调用" << endl; }
};
int main() { Person p;
return 0; }
|
构造函数的分类及调用
- 两种分类方式:
- 按参数分为:有参构造和无参构造
- 按类型分为:普通构造和拷贝构造
- 三种调用方式:
- 注意
- 调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明
- 不能利用拷贝构造函数初始化匿名对象,否则编译器认为是对象声明(老版本)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| class Person {
public: int age;
public: Person() { cout << "无参构造函数!" << endl; }
Person(int a) { age = a; cout << "有参构造函数!" << endl; }
Person(const Person &p) { age = p.age; cout << "拷贝构造函数!" << endl; }
~Person() { cout << "析构函数!" << endl; }
};
int main() { Person p;
Person p1(10);
Person p2 = Person(10); Person p3 = Person(p2); Person(10);
Person p4 = 10; Person p5 = p4;
return 0; }
|
拷贝构造函数调用时机
- C++ 中拷贝构造函数调用时机通常有三种情况
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| void test01() { Person man(100); Person newman(man); Person newman2 = man; Person newman3; newman3 = man; }
void doWork(Person p1) {}
void test02() { Person p; doWork(p); }
Person doWork2() { Person p1; cout << (int *) &p1 << endl; return p1; }
void test03() { Person p = doWork2(); cout << (int *) &p << endl; }
int main() { test01();
return 0; }
|
构造函数调用规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| void test01() { Person p1(18); Person p2(p1); cout << "p2的年龄为: " << p2.age << endl; }
void test02() { Person p1; Person p2(10); Person p3(p2);
Person p4; Person p5(10); Person p6(p5); }
int main() { test01(); test02();
return 0; }
|
深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
注意:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| class Person {
public: int m_age{}; int *m_height{};
public: Person() { cout << "无参构造函数!" << endl; }
Person(int age, int height) { cout << "有参构造函数!" << endl; m_age = age; m_height = new int(height); }
Person(const Person &p) { cout << "拷贝构造函数!" << endl; m_age = p.m_age; m_height = new int(*p.m_height); }
~Person() { cout << "析构函数!" << endl; if (m_height != nullptr && *m_height != NULL) { delete m_height; } } };
int main() { Person p1(18, 180); Person p2(p1);
cout << "p1的年龄: " << p1.m_age << " 身高: " << *p1.m_height << endl; cout << "p2的年龄: " << p2.m_age << " 身高: " << *p2.m_height << endl;
return 0; }
|
初始化列表
- 作用:C++ 提供了初始化列表语法,用来初始化属性
- 语法:
构造函数():属性1(值1),属性2(值2)... {}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| class Person {
private: int m_A; int m_B; int m_C;
public:
Person(int a, int b, int c) : m_A(a), m_B(b), m_C(c) {}
void PrintPerson() const { cout << "mA:" << m_A << endl; cout << "mB:" << m_B << endl; cout << "mC:" << m_C << endl; } };
int main() { Person p(1, 2, 3); p.PrintPerson();
return 0; }
|
类对象作为类成员
- C++ 类中的成员可以是另一个类的对象,我们称该成员为对象成员。
1 2 3 4 5
| class A {}
class B { A a; }
|
- 一个对象 A 与其对象成员 B 的构造和析构的先后顺序?
- 先构造对象成员 B
- 先析构对象 A
- 析构顺序与构造相反
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| class Phone { public: string m_PhoneName;
Phone(string name) { m_PhoneName = name; cout << "Phone构造" << endl; }
~Phone() { cout << "Phone析构" << endl; } };
class Person { public: string m_Name; Phone m_Phone;
Person(string name, string pName) : m_Name(name), m_Phone(pName) { cout << this->m_Name << endl; cout << "Person构造" << endl; }
~Person() { cout << "Person析构" << endl; }
void playGame() const { cout << m_Name << " 使用" << m_Phone.m_PhoneName << " 牌手机! " << endl; } };
int main() { Person p("张三", "苹果X"); p.playGame();
return 0; }
|
静态成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| class Person { private: static int m_C; int m_B;
static void func2() { cout << "func2调用" << endl; }
public: static int m_A;
static void func() { cout << "func调用" << endl; m_A = 100; }
};
int Person::m_A = 10; int Person::m_C = 10;
void test01() { Person p1; p1.m_A = 100; cout << "p1.m_A = " << p1.m_A << endl;
Person p2; p2.m_A = 200; cout << "p1.m_A = " << p1.m_A << endl; cout << "p2.m_A = " << p2.m_A << endl;
cout << "m_A = " << Person::m_A << endl;
}
void test02() { Person p1; p1.func();
Person::func();
}
int main() { test01(); test02();
return 0; }
|
C++ 对象模型和 this 指针
成员变量和成员函数分开存储
在 C++ 中,类内的成员变量和成员函数分开存储
注意:只有非静态成员变量才属于类的对象上
- 空对象占用的是 1 字节
- C++ 为每个空对象分配一个字节的空间,为了区分空对象占内存的位置
- 存在非静态成员变量时,按照非静态成员变量的大小和分配空间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class Person { public: int mA; static int mB;
Person() { mA = 0; }
void func() const {}
static void s_func() {} };
int main() { cout << sizeof(Person) << endl;
return 0; }
|
this 指针概念
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class Person { public: int age;
Person(int age) { this->age = age; }
Person &PersonAddPerson(Person p) { this->age += p.age; return *this; } };
int main() { Person p1(10); cout << "p1.age = " << p1.age << endl;
Person p2(10); p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1); cout << "p2.age = " << p2.age << endl;
return 0; }
|
空指针访问成员函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Person { public: int mAge;
void ShowClassName() { cout << "我是Person类!" << endl; }
void ShowPerson() { if (this == nullptr) { return; } cout << mAge << endl; } };
int main() { Person *p = nullptr; p->ShowClassName(); p->ShowPerson();
return 0; }
|
const 修饰成员函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| class Person { public: int m_A; mutable int m_B;
Person() { m_A = 0; m_B = 0; }
void ShowPerson() const { this->m_B = 100; }
void MyFunc() const { } };
int main() { const Person person; cout << person.m_A << endl; person.m_B = 100;
person.MyFunc();
return 0; }
|
友元
全局函数做友元
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class Building { friend void goodGay(Building *building); private: string m_BedRoom;
public: string m_SittingRoom; Building() { this->m_SittingRoom = "客厅"; this->m_BedRoom = "卧室"; } };
void goodGay(Building *building) { cout << "好基友正在访问: " << building->m_SittingRoom << endl; cout << "好基友正在访问: " << building->m_BedRoom << endl; }
int main() { Building b; goodGay(&b);
return 0; }
|
类做友元
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| class Building;
class goodGay { private: Building *building;
public: goodGay();
void visit(); };
class Building { friend class goodGay;
private: string m_BedRoom;
public: string m_SittingRoom;
Building(); };
Building::Building() { this->m_SittingRoom = "客厅"; this->m_BedRoom = "卧室"; }
goodGay::goodGay() { building = new Building; }
void goodGay::visit() { cout << "好基友正在访问" << building->m_SittingRoom << endl; cout << "好基友正在访问" << building->m_BedRoom << endl; }
int main() { goodGay gg; gg.visit();
return 0; }
|
成员函数做友元
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| class Building;
class goodGay { private: Building *building;
public: goodGay();
void visit();
void visit2(); };
class Building { friend void goodGay::visit();
private: string m_BedRoom;
public: string m_SittingRoom;
Building(); };
Building::Building() { this->m_SittingRoom = "客厅"; this->m_BedRoom = "卧室"; }
goodGay::goodGay() { building = new Building; }
void goodGay::visit() { cout << "好基友正在访问" << building->m_SittingRoom << endl; cout << "好基友正在访问" << building->m_BedRoom << endl; }
void goodGay::visit2() { cout << "好基友正在访问" << building->m_SittingRoom << endl; }
int main() { goodGay gg; gg.visit();
return 0; }
|
运算符重载
加号运算符重载
- 作用:实现两个自定义数据类型相加的运算
- 总结
- 对于内置的数据类型的表达式的的运算符是不可能改变的
- 不要滥用运算符重载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| class Person { public: int m_A{}; int m_B{};
Person() = default;
Person(int a, int b) { this->m_A = a; this->m_B = b; }
Person operator+(const Person &p) const { Person temp; temp.m_A = this->m_A + p.m_A; temp.m_B = this->m_B + p.m_B; return temp; } };
Person operator+(const Person &p2, int val) { Person temp; temp.m_A = p2.m_A + val; temp.m_B = p2.m_B + val; return temp; }
void test() { Person p1(10, 10); Person p2(20, 20);
Person p3 = p2 + p1; cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl;
Person p4 = p3 + 10; cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl; }
int main() { test(); return 0; }
|
左移运算符重载
作用:可以输出自定义数据类型
总结:
- 一般不会利用成员函数重载左移运算符,因为无法实现 cout 在左侧
- 重载左移运算符配合友元可以实现输出自定义数据类型
注意
- ostream 对象只能有一个,因此要使用引用
- 要返回输出流对象,再能链式地追加输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| class Person { friend ostream &operator<<(ostream &out, Person &p);
private: int m_A; int m_B;
public: Person(int a, int b) { this->m_A = a; this->m_B = b; }
};
ostream &operator<<(ostream &out, Person &p) { out << "a:" << p.m_A << " b:" << p.m_B; return out; }
int main() { Person p1(10, 20); cout << p1 << "hello world" << endl;
return 0; }
|
递增运算符重载
- 作用:通过重载递增运算符,实现自己的整型数据
- 总结:前置递增返回引用,后置递增返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| class MyInteger { friend ostream &operator<<(ostream &out, MyInteger myint);
private: int m_Num;
public: MyInteger() { m_Num = 0; }
MyInteger &operator++() { m_Num++; return *this; }
MyInteger operator++(int) { MyInteger temp = *this; m_Num++; return temp; } };
ostream &operator<<(ostream &out, MyInteger myint) { out << myint.m_Num; return out; }
void test01() { MyInteger myInt; cout << ++myInt << endl; cout << myInt << endl; }
void test02() { MyInteger myInt; cout << myInt++ << endl; cout << myInt << endl; }
int main() { test01(); test02(); return 0; }
|
赋值运算符重载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| class Person { public: int *m_Age; Person(int age) { m_Age = new int(age); }
Person &operator=(Person &p) { if (m_Age != nullptr) { delete m_Age; m_Age = nullptr; }
m_Age = new int(*p.m_Age);
return *this; } ~Person() { if (m_Age != nullptr) { delete m_Age; m_Age = nullptr; } } };
void test01() { Person p1(18); Person p2(20); Person p3(30);
p3 = p2 = p1; cout << "p1的年龄为:" << *p1.m_Age << endl; cout << "p2的年龄为:" << *p2.m_Age << endl; cout << "p3的年龄为:" << *p3.m_Age << endl; }
int main() { test01();
int a = 10; int b = 20; int c = 30;
c = b = a; cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl;
return 0; }
|
关系运算符重载
- 作用:重载关系运算符,可以让两个自定义类型对象进行对比操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| class Person { public: string m_Name; int m_Age; Person(string name, int age) { this->m_Name = name; this->m_Age = age; };
bool operator==(Person &p) { if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) { return true; } else { return false; } }
bool operator!=(Person &p) { if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) { return false; } else { return true; } } };
void test01() { Person a("孙悟空", 18); Person b("孙悟空", 18);
if (a == b) { cout << "a和b相等" << endl; } else { cout << "a和b不相等" << endl; } if (a != b) { cout << "a和b不相等" << endl; } else { cout << "a和b相等" << endl; } }
int main() { test01(); return 0; }
|
函数调用运算符重载
- 函数调用运算符 () 也可以重载
- 由于重载后使用的方式非常像函数的调用,因此称为仿函数
- 仿函数没有固定写法,非常灵活
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| class MyPrint { public: void operator()(string text) { cout << text << endl; } };
void test01() { MyPrint myFunc; myFunc("hello world"); }
class MyAdd { public: int operator()(int v1, int v2) { return v1 + v2; } };
void test02() { MyAdd add; int ret = add(10, 10); cout << "ret = " << ret << endl; cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl; }
int main() { test01(); test02(); return 0; }
|
继承
子类是对父类的扩展。
继承的基本语法
- 继承的好处:可以减少重复的代码
class A : public B;
- 派生类中的成员,包含两大部分:
- 一部分是从基类继承过来的,一部分是自己增加的成员。
- 从基类继承过来的成员表现其共性,而新增的成员体现了其个性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| class BasePage { public: static void header() { cout << "首页、公开课、登录、注册...(公共头部)" << endl; }
static void footer() { cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl; }
static void left() { cout << "Java,Python,C++...(公共分类列表)" << endl; }
};
class Java : public BasePage { public: static void content() { cout << "JAVA学科视频" << endl; } };
class Python : public BasePage { public: static void content() { cout << "Python学科视频" << endl; } };
class CPP : public BasePage { public: static void content() { cout << "C++学科视频" << endl; } };
int main() { cout << "Java下载视频页面如下: " << endl; Java::header(); Java::footer(); Java::left(); Java::content(); cout << "--------------------" << endl;
cout << "Python下载视频页面如下: " << endl; Python::header(); Python::footer(); Python::left(); Python::content(); cout << "--------------------" << endl;
cout << "C++下载视频页面如下: " << endl; CPP::header(); CPP::footer(); CPP::left(); CPP::content(); return 0; }
|
继承方式
- 继承的语法:
class 子类 : 继承方式 父类
- 继承方式一共有三种:
|
公共继承 |
保护继承 |
私有继承 |
公共成员 |
公共成员 |
保护成员 |
私有成员 |
保护成员 |
保护成员 |
保护成员 |
私有成员 |
私有成员 |
不可访问 |
不可访问 |
不可访问 |
继承中的对象模型
- 总结:父类中私有成员也是被子类继承下去了,只是由编译器给隐藏后访问不到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Base { public: int m_A; protected: int m_B; private: int m_C; };
class Son : public Base { public: int m_D; };
int main() { cout << "sizeof Son = " << sizeof(Son) << endl;
return 0; }
|
继承中构造和析构顺序
- 总结:
- 子类继承父类后,当创建子类对象,也会调用父类的构造函数
- 继承中,先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class Base { public: Base() { cout << "Base构造函数!" << endl; }
~Base() { cout << "Base析构函数!" << endl; } };
class Son : public Base { public: Son() { cout << "Son构造函数!" << endl; }
~Son() { cout << "Son析构函数!" << endl; } };
int main() { Son s;
return 0; }
|
继承同名(非)静态成员处理方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| class Base { public: int m_A;
Base() { m_A = 100; }
void func() { cout << "Base - func()调用" << endl; }
void func(int a) { cout << "Base - func(int a)调用" << endl; } };
class Son : public Base { public: int m_A;
Son() : m_A(200) {}
void func() { cout << "Son - func()调用" << endl; } };
int main() { Son s; cout << "Son下的m_A = " << s.m_A << endl; cout << "Base下的m_A = " << s.Base::m_A << endl;
s.func(); s.Base::func(); s.Base::func(10); return EXIT_SUCCESS; }
|
多继承语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| class Base1 { public: int m_A;
Base1() : m_A(100) {} };
class Base2 { public: int m_A;
Base2() : m_A(200) {} };
class Son : public Base2, public Base1 { public: int m_C; int m_D;
Son() : m_C(300), m_D(400) {} };
int main() { Son s; cout << "sizeof Son = " << sizeof(s) << endl; cout << "s.Base1::m_A = " << s.Base1::m_A << endl; cout << "s.Base2::m_A = " << s.Base2::m_A << endl;
return 0; }
|
菱形继承
菱形(钻石)继承概念:
- 两个派生类继承同一个基类
- 又有某个类同时继承者两个派生类
典型的菱形继承案例:
-
菱形继承问题:
羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。
草泥马继承自动物的数据继承了两份,其实这份数据我们只需要一份就可以,此为资源浪费。
总结:
- 菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义
- 利用虚继承可以解决菱形继承问题,从虚基类继承的成员变量只会有一份内存空间。
- 虚基类是子类以虚继承方式继承的父类的别称,并不是抽象类,其依然能够创建实例对象
- 虚继承的本质是将自己继承过来的成员作为一个指针(virtual base pointer,vbptr)指向父类的空间(vbtable)
- 因此修改任何一个父类或者子类,实际上都是在修改同一份数据
- 类似于 Java 的重写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| class Animal { public: int m_Age{}; };
class Sheep : virtual public Animal { };
class Tuo : virtual public Animal { };
class SheepTuo : public Sheep, public Tuo { };
int main() { Animal a; a.m_Age = 15; cout << "s.m_Age = " << a.m_Age << endl; Sheep s; s.m_Age = 10; cout << "s.m_Age = " << s.m_Age << endl; Tuo t; t.m_Age = 20; cout << "t.m_Age = " << t.m_Age << endl;
SheepTuo st; st.Sheep::m_Age = 100; st.Tuo::m_Age = 200; cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl; cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl; cout << "st.m_Age = " << st.m_Age << endl;
return 0; }
|
多态
多态的基本概念
多态分为两类
- 静态多态:函数重载和运算符重载属于静态多态(复用函数名)
- 动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态区别:
- 静态多态的函数地址早绑定:编译阶段确定函数地址
- 动态多态的函数地址晚绑定:运行阶段确定函数地址
总结:
- 多态满足条件
- 多态使用条件
- 我们希望传入什么对象,那么就调用什么对象的函数
- 如果函数地址在编译阶段就能确定,那么静态联编
- 如果函数地址在运行阶段才能确定,就是动态联编
- 重写:
函数返回值类型 函数名 参数列表
需要完全一致才称为重写
多态的优点:
- 代码组织结构清晰
- 可读性强
- 利于前期和后期的扩展以及维护
多态的原理
- 加上 virtual 关键字之前,Animal 类的成员函数不存储在类对象中,因此一个 Animal 实例的大小为 1 字节
- 加上 virtual 关键字之后,Animal 类将保存一个虚函数指针(virtual function pointer,vfptr)指向自己的虚函数表(virtual function table,vftable),并存储在该类对象中,因此一个 Animal 实例(无论有多少个虚函数)的大小为 4 字节
- 一个类只有一个虚函数指针,虚函数表内部记录各个虚函数的地址
- 继承时复制一份虚函数指针和虚函数表,虚函数表不占实例空间
- 当子类重写父类虚函数时,即重写/替换父类内部(继承过来)的虚函数指针指向的虚函数表中的函数,因此使用父类指针指向的子类实例调用父类函数时,实际是调用子类重写的函数内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| class Animal { public: virtual void speak() { cout << "动物在说话" << endl; }
virtual void speak2() { cout << "动物在说话" << endl; } };
class Cat : public Animal { public: void speak() override { cout << "小猫在说话" << endl; } };
class Dog : public Animal { public: void speak() override { cout << "小狗在说话" << endl; } };
void DoSpeak(Animal &animal) { animal.speak(); }
int main() { Cat cat; DoSpeak(cat); Dog dog; DoSpeak(dog); Animal animal; DoSpeak(animal); cout << "sizeof animal = " << sizeof animal << endl;
return 0; }
|
纯虚函数和抽象类
针对问题:在多态中,通常父类中虚函数的实现是无意义的,主要都是调用子类重写的内容
解决:因此可以将虚函数改为纯虚函数,要求子类必须重写纯虚函数
语法:virtual 返回值类型 函数名 (参数列表)= 0 ;
只要类中有一个纯虚函数,这个类就称为抽象类
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Base { public: virtual void func() = 0; };
class Son : public Base { public: void func() override { cout << "func调用" << endl; }; };
int main() { Base *base = new Son; base->func(); delete base;
return 0; }
|
虚析构和纯虚析构
文件操作概述
打开方式 |
解释 |
ios::in |
为读文件而打开文件 |
ios::out |
为写文件而打开文件 |
ios::ate |
初始位置:文件尾 |
ios::app |
追加方式写文件 |
ios::trunc |
如果文件存在先删除,再创建 |
ios::binary |
二进制方式 |
- 注意:文件打开方式可以配合使用,利用
|
操作符
- 例如:用二进制方式写文件
ios::binary | ios:: out
文本文件
写文件
写文件步骤如下:
包含头文件:#include <fstream>
创建流对象:ofstream ofs;
打开文件:ofs.open("文件路径", 打开方式);
写数据:ofs << "写入的数据";
关闭文件:ofs.close();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include <fstream>
void test01() { ofstream ofs; ofs.open("test.txt", ios::out);
ofs << "姓名:张三" << endl; ofs << "性别:男" << endl; ofs << "年龄:18" << endl;
ofs.close(); }
int main() { test01(); return 0; }
|
读文件
- 读文件步骤如下:
- 包含头文件:
#include <fstream>
- 创建流对象:
ifstream ofs;
- 打开文件:
ifs.open("文件路径",打开方式);
- 检查是否打开成功:
!ifs.is_open()
- 读数据,四种方式读取
- 关闭文件:
ifs.close();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| #include <fstream> #include <string>
void test01() { ifstream ifs; ifs.open("test.txt", ios::in);
if (!ifs.is_open()) { cout << "文件打开失败" << endl; return; }
char buf[1024] = { 0 }; while (ifs >> buf) { cout << buf << endl; }
char buf[1024] = { 0 }; while (ifs.getline(buf, sizeof(buf))) { cout << buf << endl; }
string buf; while (getline(ifs, buf)) { cout << buf << endl; }
char c; while ((c = ifs.get()) != EOF) { cout << c; } ifs.close(); }
int main() { test01(); return 0; }
|
二进制文件
写文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Person { public: char m_Name[64]; int m_Age; };
void test01() { ofstream ofs("person.txt", ios::out | ios::binary);
Person p = {"张三", 18}; ofs.write((const char *) &p, sizeof(p));
ofs.close(); }
int main() { test01(); return 0; }
|
读文件
- 读二进制文件主要利用流对象的成员函数
read()
- 函数原型:
istream& read(char *buffer, int len);
- 参数解释:
- 字符指针 buffer 指向内存中一段存储空间
- len 是读写的字节数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Person { public: char m_Name[64]; int m_Age; };
void test01() { ifstream ifs("person.txt", ios::in | ios::binary); if (!ifs.is_open()) { cout << "文件打开失败" << endl; }
Person p; ifs.read((char *) &p, sizeof(p)); cout << "姓名: " << p.m_Name << " 年龄: " << p.m_Age << endl; ifs.close(); }
int main() { test01(); return 0; }
|
模板
模板的概念
- 模板的特点:
- 模板不可以直接使用,只是一个框架
- 模板的通用并不是万能的
- C++ 另一种编程思想称为泛型编程,主要利用的技术就是模板
函数模板
函数模板语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| int myAdd01(int a, int b) { return a + b; }
template<class T> T myAdd02(T a, T b) { return a + b; }
void test01() { int a = 10; int b = 20; char c = 'c';
cout << myAdd01(a, c) << endl; myAdd02<int>(a, c); }
int main() { test01(); return 0; }
|
普通函数与函数模板的调用规则
- 调用规则如下:
- 如果函数模板和普通函数都可以实现,优先调用普通函数
- 可以通过空模板参数列表来强制调用函数模板
- 函数模板也可以发生重载
- 如果函数模板可以产生更好的匹配,优先调用函数模板
- 总结:既然提供了函数模板,最好就不要提供普通函数,否则容易出现二义性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| void myPrint(int a, int b) { cout << "调用的普通函数" << endl; }
template<typename T> void myPrint(T a, T b) { cout << "调用的模板" << endl; }
template<typename T> void myPrint(T a, T b, T c) { cout << "调用重载的模板" << endl; }
void test01() { int a = 10; int b = 20; myPrint(a, b);
myPrint<>(a, b);
int c = 30; myPrint(a, b, c);
char c1 = 'a'; char c2 = 'b'; myPrint(c1, c2); }
int main() { test01(); return 0; }
|
模板的局限性
- 模板函数的成功运行,要求传入的数据类型能够支持函数内部的代码
- 解决方向:模板的重载,可以为这些特定的类型提供具体化的模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| class Person { public: string m_Name; int m_Age;
Person(string name, int age) { this->m_Name = name; this->m_Age = age; } };
template<class T> bool myCompare(T &a, T &b) { if (a == b) { return true; } else { return false; } }
template<> bool myCompare(Person &p1, Person &p2) { if (p1.m_Name == p2.m_Name && p1.m_Age == p2.m_Age) { return true; } else { return false; } }
void test01() { int a = 10; int b = 20; bool ret = myCompare(a, b); if (ret) { cout << "a == b " << endl; } else { cout << "a != b " << endl; } }
void test02() { 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() { test01(); test02(); return 0; }
|
类模板
类模板语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| template<class NameType, class AgeType = int> class Person { public: NameType mName; AgeType mAge; Person(NameType name, AgeType age) { this->mName = name; this->mAge = age; }
void showPerson() { cout << "name: " << this->mName << " age: " << this->mAge << endl; } };
void test01() { Person<string, int> p("孙悟空", 1000); // 必须使用显示指定类型的方式,使用类模板 p.showPerson(); }
void test02() { Person<string> p("猪八戒", 999); p.showPerson(); }
int main() { test01(); test02(); return 0; }
|
类模板中成员函数创建时机
- 类模板中成员函数和普通类中成员函数创建时机是有区别的:
- 普通类中的成员函数一开始就可以创建
- 类模板中的成员函数在调用时才创建,并检查是否该数据类型能够调用函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| class Person1 { public: void showPerson1() { cout << "Person1 show" << endl; } };
class Person2 { public: void showPerson2() { cout << "Person2 show" << endl; } };
template<class T> class MyClass { public: T obj;
void fun1() { obj.showPerson1(); }
void fun2() { obj.showPerson2(); } };
void test01() { MyClass<Person1> m{}; m.fun1(); }
int main() { test01(); return 0; }
|
类模板对象做函数参数
学习目标:
类模板实例化出的对象,向函数传参的方式
一共有三种传入方式:
- 指定传入的类型:直接显示对象的数据类型
- 参数模板化:将对象中的参数变为模板进行传递
- 整个类模板化:将这个对象类型 模板化进行传递
总结:
- 通过类模板创建的对象,可以有三种方式向函数中进行传参
- 使用比较广泛是第一种:指定传入的类型
在 C++ 中查看一个模板类型 T 或者变量 var 的数据类型:typeid(T/var).name()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| template<class NameType, class AgeType = int> class Person { public: NameType mName; AgeType mAge;
Person(NameType name, AgeType age) { this->mName = name; this->mAge = age; }
void showPerson() { cout << "name: " << this->mName << " age: " << this->mAge << endl; } };
void printPerson1(Person<string, int> &p) { p.showPerson(); }
void test01() { Person<string, int> p("孙悟空", 100); printPerson1(p); }
template<class T1, class T2> void printPerson2(Person<T1, T2> &p) { p.showPerson(); cout << "T1的类型为: " << typeid(T1).name() << endl; cout << "T2的类型为: " << typeid(T2).name() << endl; }
void test02() { Person<string, int> p("猪八戒", 90); printPerson2(p); }
template<class T> void printPerson3(T &p) { cout << "T的类型为: " << typeid(T).name() << endl; p.showPerson(); }
void test03() { Person<string, int> p("唐僧", 30); printPerson3(p); }
int main() { test01(); test02(); test03(); return 0; }
|
类模板与继承
- 当类模板碰到继承时,需要注意一下几点:
- 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型
- 如果不指定,编译器无法给子类分配内存
- 如果想灵活指定出父类中T的类型,子类也需变为类模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| template<class T> class Base { T m; };
class Son : public Base<int> { };
template<class T1, class T2> class Son2 : public Base<T2> { public: Son2() { cout << typeid(T1).name() << endl; cout << typeid(T2).name() << endl; } };
int main() { Son c; Son2<int, char> child1; return 0; }
|
类模板成员函数类外实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| template<class T1, class T2> class Person { public: T1 m_Name; T2 m_Age;
Person(T1 name, T2 age);
void showPerson(); };
template<class T1, class T2> Person<T1, T2>::Person(T1 name, T2 age) { this->m_Name = name; this->m_Age = age; }
template<class T1, class T2> void Person<T1, T2>::showPerson() { cout << "姓名: " << this->m_Name << " 年龄:" << this->m_Age << endl; }
void test01() { Person<string, int> p("Tom", 20); p.showPerson(); }
int main() { test01(); return 0; }
|
类模板分文件编写
#include <iostream>
#include <string>
using namespace std;
template<class T1, class T2>
class Person {
public:
T1 m_Name;
T2 m_Age;
Person(T1 name, T2 age);
void showPerson();
};
//构造函数 类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age) {
this->m_Name = name;
this->m_Age = age;
}
//成员函数 类外实现
template<class T1, class T2>
void Person<T1, T2>::showPerson() {
cout << "姓名: " << this->m_Name << " 年龄:" << this->m_Age << endl;
}
#endif //CLEARNING_PERSON_HPP
1 2 3 4 5 6 7 8 9 10 11 12 13
| - ```c++ #include "Person.hpp" void test01() { Person<string, int> p("Tom", 10); p.showPerson(); } int main() { test01(); return 0; }
|
类模板与友元
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| template<class T1, class T2> class Person;
template<class T1, class T2> void printPerson2(Person<T1, T2> &p) { cout << "类外实现 ---- 姓名: " << p.m_Name << " 年龄:" << p.m_Age << endl; }
template<class T1, class T2> class Person { friend void printPerson(Person<T1, T2> &p) { cout << "姓名: " << p.m_Name << " 年龄:" << p.m_Age << endl; }
friend void printPerson2<>(Person<T1, T2> &p);
private: T1 m_Name; T2 m_Age;
public: Person(T1 name, T2 age) { this->m_Name = name; this->m_Age = age; } };
void test01() { Person<string, int> p("Tom", 20); printPerson(p); }
void test02() { Person<string, int> p("Jerry", 30); printPerson2(p); }
int main() { test01(); test02(); return 0; }
|