5.1 C++中类的实例化
在C++中实例化一个类不需要用new。也可以用new,但是区别是:不使用new,开辟栈空间,使用new开辟的空间为堆空间。
5.2 构造函数和析构函数、拷贝构造函数
5.2.1.析构函数
析构函数是C++内释放的时候调用的函数。
构造函数语法:类名(){}
- 构造函数,没有返回值也不写void
- 函数名称与类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
5.2.2.构造函数
对象创建的时候调用的函数。 C++中不使用new,它也会调用构造函数。在C++中如果用户定义有参构造函数,那么C++不会提供无参构造函数。这一点与Java和C sharp不同。
析构函数语法: ~类名(){}
- 析构函数,没有返回值也不写void
- 函数名称与类名相同,在名称前加上符号 ~
- 析构函数不可以有参数,因此不可以发生重载
- 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
此外C++还有拷贝构造函数。
5.2.3.拷贝构造函数
C++中拷贝构造函数调用时机通常有三种情况
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
默认提供的拷贝构造函数对C加对C++对象的属性进行浅拷贝。
这就是问题所在,如果上一个函数在释放之后将其变量delete掉那么下一个函数在拷贝时。得到的值就是空。所以我们要自定义拷贝构造函数。
示例:
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: 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 != NULL) { delete m_height; } } public: int m_age; int* m_height; };
void test01() { 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; }
int main() {
test01();
system("pause");
return 0; }
|
总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
5.2.3 构造函数调用规则
默认情况下,c++编译器至少给一个类添加3个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
5.3 友元
5.3.1 全局函数做友元
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 Building { friend void goodGay(Building * building);
public:
Building() { this->m_SittingRoom = "客厅"; this->m_BedRoom = "卧室"; }
public: string m_SittingRoom;
private: string m_BedRoom; };
void goodGay(Building * building) { cout << "好基友正在访问: " << building->m_SittingRoom << endl; cout << "好基友正在访问: " << building->m_BedRoom << endl; }
void test01() { Building b; goodGay(&b); }
int main(){
test01();
system("pause"); return 0; }
|
5.3.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 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
| class Building; class goodGay { public:
goodGay(); void visit();
private: Building *building; };
class Building { friend class goodGay;
public: Building();
public: string m_SittingRoom; private: string m_BedRoom; };
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 test01() { goodGay gg; gg.visit();
}
int main(){
test01();
system("pause"); return 0; }
|
5.3.3 成员函数做友元
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
| class Building; class goodGay { public:
goodGay(); void visit(); void visit2();
private: Building *building; };
class Building { friend void goodGay::visit();
public: Building();
public: string m_SittingRoom; private: string m_BedRoom; };
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; }
void test01() { goodGay gg; gg.visit();
}
int main(){ test01();
system("pause"); return 0; }
|
5.4 运算符重载
运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
5.4.1 加号运算符重载
作用:实现两个自定义数据类型相加的运算
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
| class Person { public: Person() {}; Person(int a, int b) { this->m_A = a; this->m_B = b; } Person operator+(const Person& p) { Person temp; temp.m_A = this->m_A + p.m_A; temp.m_B = this->m_B + p.m_B; return temp; }
public: int m_A; int m_B; };
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();
system("pause");
return 0; }
|
总结1:对于内置的数据类型的表达式的的运算符是不可能改变的
总结2:不要滥用运算符重载
5.4.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 32 33 34 35 36 37 38 39 40 41 42
| class Person { friend ostream& operator<<(ostream& out, Person& p);
public:
Person(int a, int b) { this->m_A = a; this->m_B = b; }
private: int m_A; int m_B; };
ostream& operator<<(ostream& out, Person& p) { out << "a:" << p.m_A << " b:" << p.m_B; return out; }
void test() {
Person p1(10, 20);
cout << p1 << "hello world" << endl; }
int main() {
test();
system("pause");
return 0; }
|
总结:重载左移运算符配合友元可以实现输出自定义数据类型
5.4.3 递增运算符重载
作用: 通过重载递增运算符,实现自己的整型数据
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
| class MyInteger {
friend ostream& operator<<(ostream& out, MyInteger myint);
public: MyInteger() { m_Num = 0; } MyInteger& operator++() { m_Num++; return *this; }
MyInteger operator++(int) { MyInteger temp = *this; m_Num++; return temp; }
private: int m_Num; };
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();
system("pause");
return 0; }
|
总结: 前置递增返回引用,后置递增返回值
5.4.4 赋值运算符重载
c++编译器至少给一个类添加4个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
- 赋值运算符 operator=, 对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
示例:
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 68 69 70 71 72 73 74 75 76 77 78
| class Person { public:
Person(int age) { m_Age = new int(age); }
Person& operator=(Person &p) { if (m_Age != NULL) { delete m_Age; m_Age = NULL; }
m_Age = new int(*p.m_Age);
return *this; }
~Person() { if (m_Age != NULL) { delete m_Age; m_Age = NULL; } }
int *m_Age;
};
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();
system("pause");
return 0; }
|
5.4.5 关系运算符重载
作用:重载关系运算符,可以让两个自定义类型对象进行对比操作
示例:
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 68 69 70 71 72 73
| class Person { public: 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; } }
string m_Name; int m_Age; };
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();
system("pause");
return 0; }
|
5.4.6 函数调用运算符重载
- 函数调用运算符 () 也可以重载
- 由于重载后使用的方式非常像函数的调用,因此称为仿函数
- 仿函数没有固定写法,非常灵活
示例:
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 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();
system("pause");
return 0; }
|
5.6 继承
5.6.1继承方式
继承的语法:class 子类 : 继承方式 父类
继承方式一共有三种:
- 公共继承 public
- 保护继承 protected
- 私有继承 private
示例:
1 2 3 4
| class Base{} class Test1: public Base class Test2: protected Base class Test3: private Base
|
5.6.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
| class Base { public: Base() { cout << "Base构造函数!" << endl; } ~Base() { cout << "Base析构函数!" << endl; } };
class Son : public Base { public: Son() { cout << "Son构造函数!" << endl; } ~Son() { cout << "Son析构函数!" << endl; } };
|
总结:继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
5.6.3 继承同名成员处理方式
问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?
- 访问子类同名成员 ==直接访问即可==
- 访问父类同名成员 ==需要加作用域==
总结:
- 子类对象可以直接访问到子类中同名成员
- 子类对象加作用域可以访问到父类同名成员
- 当子类与父类拥有同名的成员函数,==子类会隐藏父类中同名成员函数==,加作用域可以访问到父类中同名函数
5.6.4 继承同名静态成员处理方式
问题:继承中同名的静态成员在子类对象上如何进行访问?
静态成员和非静态成员出现同名,处理方式一致
- 访问子类同名成员 ==直接访问即可==
- 访问父类同名成员 ==需要加作用域==
总结:同名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(通过对象 和 通过类名)
5.6.5 菱形继承
菱形继承概念:
两个派生类继承同一个基类
又有某个类同时继承者两个派生类
这种继承被称为菱形继承,或者钻石继承
典型的菱形继承案例:
菱形继承问题:
羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。
草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。
问题描述:
当两个类同时继承于一个类,并且这两个类都有一个相同的基类。我们称这个问题为菱形继承问题。因为此时这两个类的子类中基类的属性可以存在两份。
解决方法:
使用虚继承。在。在基类的两个子类中继承基类时,使用virtual 关键字。虚继承基类。使得基类的属性成员只有一份。
1 2 3 4 5 6 7 8 9
| class Animal { public: int m_Age; };
class Sheep : virtual public Animal {}; class Tuo : virtual public Animal {}; class SheepTuo : public Sheep, public Tuo {};
|
5.6.6继承中的对象模型
问题:从父类继承过来的成员,哪些属于子类对象中?
利用工具查看:
打开工具窗口后,定位到当前CPP文件的盘符
然后输入: cl /d1 reportSingleClassLayout查看的类名 所属文件名
效果如下图:
结论: 父类中私有成员也是被子类继承下去了,只是由编译器给隐藏后访问不到
5.7 多态
5.7.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 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
| 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.speak(); }
void test01() { Cat cat; DoSpeak(cat);
Dog dog; DoSpeak(dog); }
int main() {
test01();
system("pause");
return 0; }
|
总结:
多态满足条件
多态使用条件
重写:函数返回值类型 函数名 参数列表 完全一致称为重写
5.7.2 纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 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
| class Base { public: virtual void func() = 0; };
class Son :public Base { public: virtual void func() { cout << "func调用" << endl; }; };
void test01() { Base * base = NULL; base = new Son; base->func(); delete base; }
int main() {
test01();
system("pause");
return 0; }
|
5.7.3 虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
- 可以解决父类指针释放子类对象
- 都需要有具体的函数实现
虚析构和纯虚析构区别:
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual ~类名() = 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 62 63 64 65 66 67 68 69
| class Animal { public:
Animal() { cout << "Animal 构造函数调用!" << endl; } virtual void Speak() = 0;
virtual ~Animal() = 0; };
Animal::~Animal() { cout << "Animal 纯虚析构函数调用!" << endl; }
class Cat : public Animal { public: Cat(string name) { cout << "Cat构造函数调用!" << endl; m_Name = new string(name); } virtual void Speak() { cout << *m_Name << "小猫在说话!" << endl; } ~Cat() { cout << "Cat析构函数调用!" << endl; if (this->m_Name != NULL) { delete m_Name; m_Name = NULL; } }
public: string *m_Name; };
void test01() { Animal *animal = new Cat("Tom"); animal->Speak();
delete animal; }
int main() {
test01();
system("pause");
return 0; }
|
总结:
1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
3. 拥有纯虚析构函数的类也属于抽象类