4 类和对象
4.2 对象的初始化和清理
4.2.6 初始化列表
- 作用: 初始化对象的属性
- 为什么使用: 初始化发生在构造函数的语句之前, 若是使用构造函数内的语句赋值会浪费性能;
- 语法:
构造函数():属性1(值1),属性2(值2)...{}
class Person
{
person(int a, int b, int c): m_a(a), m_b(b), m_c(c)
{
}
};
void test()
{
Person person(10, 20, 30);
}
4.2.7 类对象作为类成员
c++中类的成员可以是另一个类的对象, 称为对象成员
对象成员先构造后析构
例如:
class A{};
class B
{
A a;
};
#include <iostream>
using namespace std;
#include <string>
//手机类
class Phone
{
public:
Phone(String pName): m_PName(pName){}
String m_PName;
};
class Person
{
public:
Person(string name, String pName): m_Name(name), m_Phone(pName){}
String m_Name;
String m_Phone;
};
void test(){
Person p("张三", "手机");
}
4.28 静态成员
在成员变量和成员函数前加 static
关键字, 称为静态成员
访问方法: 静态成员可以使用对象访问, 也可以使用类名访问
静态成员也是有访问权限的
- 静态成员变量
- 多有对象共享同一份数据
- 在编译阶段分布内存
- 类内声明, 类外初始化
- 静态成员函数
- 所有对象共享一个函数
- 静态成员函数只能访问静态成员变量
class Person
{
public:
static int m_A;
};
int Person::m_A = 100;
void test()
{
Person p;
//对象访问
cout << p.m_A << endl;
//类名访问
cout << Person::m_A << endl;
}
4.3 c++对象模型和this指针
4.3.1 成员变量和成员函数分开存储
空对象的 sizeof 为 1, 若不是空的就是该多少多少
只有非静态成员变量属于类的对象上
4.3.2 this指针
this指针指向被调用的成员函数所属的对象
用途:
-
解决名称冲突
-
返回对象本身
可以链式编程
class Person { public: Person(int age):m_Age(age){} int m_Age; Person& PersonAddAge(Person &p) { m_Age += p.m_Age; return *this; } }; void test() { Person p1(10): Person p2(10); p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1); cout << "p2.m_Age=" << p2.m_Age; }
4.3.3 空指针访问成员函数
空指针可以访问成员函数, 但容易引发空指针报错. 可以加
if (this==NULL){
return;
}
来解决
4.3.4 const 修饰成员函数
常函数:
-
成员函数加 const 后, 称这个函数为常函数
void fun() const{}
-
这个const修饰的是this指针, 让指针修饰的值也不可修改
-
常函数内不可以修改成员属性
-
成员属性声明时加关键字 mutable 后, 在常函数中仍可以修改
常对象:
- 声明对象时加 const 称该函数为常对象
- 常对象只能调用常函数
4.4 友元
目的: 让一个函数或者类访问另一个类中的私有成员
三种实现:
-
全局函数做友元
class Building { //友元声明 friend void fun(); private: int a; }; void fun(Building building) { cout << a << endl; }
-
类做友元
class Building { //友元声明 friend class C; private: int a; };
-
成员函数做友元
class Building { //友元声明 friend Class::fun(); private: int a; };
4.5 运算符重载
对已有的运算符进行重新定义, 赋予其另一种功能, 以适应不同的数据类型
4.5.1 加号重载
- 成员函数重载+号
Person operator+(Person &p)
{
Person temp;
temp m_A = this->m_A + p.m_A;
temp m_B = this->m_B + p.m_B;
return temp;
}
//使用
Person p3 = p1.operator+(p2);
//简化为
Person p3 = p1 + p2;
- 全局函数重载+号
Person operator+(Person &p1, Person &p2)
{
Person temp;
temp m_A = p1.m_A + p2.m_A;
temp m_B = p1.m_B + p2.m_B;
return temp;
}
//使用
Person p3 = operator+(p1, p2);
//简化为
Person p3 = p1 + p2;
在操作数类型不同时, 运算符重载也可以发生函数重载
内置数据类型不能重载
4.5.2 左移运算符<<
重载
通常不会用成员函数重载左移运算符, 只能用全局函数重载左移运算符
ostream &operator<< (ostream::&cout, Person &p)
{
cout << "m_A = " << p.m_A << "; m_B = " << p.m_B;
return cout;
}
若要访问私有变量就要把这个函数设为友元
4.5.3 递增运算符重载
类内:
class Myinteger
{
public:
int m_Num;
MyInteger():m_Num(0){}
Myinteger& operator++()
{
m_Num++;
return this;
}
};
4.5.4 赋值运算符重载
编译器自己的拷贝是浅拷贝, 在析构时可能会重复释放报错
Person& operator=(Person &p)
{
//编译器提供浅拷贝
//判断堆区是否有属性存在, 若存在先释放
if(m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
//深拷贝
m_Age = new int(*p.m_Age);
//返回自身
return *this;
}
4.5.5 关系运算符重载
- ==
- !=
- <
- >
4.5.6 函数调用运算符重载
- ()也可以重载
- 重载后非常像函数调用, 称为仿函数
- 仿函数没有固定写法, 非常灵活
//打印输出类
class MyPrint
{
public:
//重载函数调用运算符
void operator()(String test)
{
cout << test << endl;
}
};
void test()
{
MyPrint myPrint;
myPrint("hello world");
}
//加法类
class MyAdd
{
public:
int operator()(int num1, int num2)
{
return num1 + num2;
}
}
void test()
{
MyAdd myAdd;
int res = myAdd(100, 100);
cout << "res = " << res << endl;
//匿名函数对象
cout << MyAdd(100, 100) << endl;
}
21/7/3
4.6 继承
下级别类成员除了除了有自己的特性, 还拥有与上一级的共性. 这时我们使用继承, 减少重复代码
继承的好处: 减少重复的代码
语法: class 子类 : 继承方式 父类
子类也称为 派生类, 父类也称为 基类
class Fruit
{
};
//继承
class Apple : public Fruit
{
};
4.6.2 继承方式
三种:
class A
{
public:
int a;
protected:
int b;
private:
int c;
}
-
公共继承
class B : public A { public: int a; protected: int b; private: }
-
保护继承
class B : protected A { protected: int a; int b; }
-
私有继承
class B : private A { private: int a; int b; }
private属性和函数三种继承都访问不到,
4.6.3 继承中的对象模型
private属性和函数被隐藏了, 用 sizeof 关键字可以验证, 但还是会继承下去
vs的开发人员命令提示符 工具可以验证
打开后输入 d:
进入d盘
输入 cd
后 ctrl+v
或者右键可以粘贴地址, 跳转到该文件夹下
输入 dir
可以显示该目录下所有的文件和文件夹
输入 cl /d1 reportSingleClassLayout类名
再输入文件名(可以输入一部分后按tab键补全)
4.6.4 继承中的构造和析构顺序
先构造父类 -> 后构造子类
先析构子类 -> 后析构父类
4.6.5 继承中同名成员的处理方式
同名成员访问子类直接访问, 访问父类成员需要加上父类的作用域
编译器会隐藏掉所有的同名成员函数, 必须要加作用域才能调用
4.6.6 继承中同名的静态成员处理方式
- 通过对象访问: 和普通成员一样, 也是加作用域
- 通过类名的方式访问:
父类 :: 静态成员
: 直接从父类中访问子类 :: 父类 :: 静态成员
: 从子类中访问父类
和非静态的基本一致
4.6.7 多继承语法
实际开发中不建议使用多继承
class 子类 : 继承方式 父类1, 继承方式 父类1
4.6.8 菱形继承
也叫钻石继承
利用虚继承可以解决菱形继承的问题:
在继承之前加上关键字 vitual
变为虚继承
class Sheep : virtual public Animal{};
vbptr (virtual base pointer)虚基类指针 指向 vbtable
通过偏移量指向唯一的数据
21.7.4
TODO
https://www.bilibili.com/video/BV1et411b73Z?p=135