静态成员经典应用-单例模式(C++)
单例模式:设计模式的一种, 保证某个类永远只创建一个对象.
1.构造函数\析构函数私有化, 拷贝构造函数私有化, 赋值运算符重载函数私有化.
2.定义一个私有的static成员变量指向唯一的那个单例对象
3.提供一个公共的访问单例对象的接口.
#include <iostream>
using namespace std;
class Rocket {
private:
static Rocket *ms_rocket;
Rocket() {}
Rocket(const Rocket &rocket) {}
~Rocket() {}
void operator=(const Rocket &rocket) {}
public:
static Rocket *sharedRocket() {
// 这里要考虑多线程安全
if (ms_rocket == nullptr) {
ms_rocket = new Rocket();
}
return ms_rocket;
}
static void deleteRocket() {
// 这里要考虑多线程安全
if (ms_rocket != nullptr) {
delete ms_rocket;
ms_rocket = nullptr;
// 防止野指针
}
}
void run() {
cout << "run()" << endl;
}
};
Rocket *Rocket::ms_rocket = nullptr;
int main()
{
Rocket *p = Rocket::sharedRocket();
p->run();
p->deleteRocket();
return 0;
}
(1)为什么要用指针?
1.在C++开发中, 对象能放堆空间, 尽量放堆空间.
2.对于单例对象, 要考虑内存的灵活使用, 因为牵扯到内存的分配和销毁, 所以用堆空间更灵活.
(2)赋值运算符重载函数为什么要私有化?
因为两个一样的对象做赋值操作没有意义.
(3)拷贝构造函数为什么要私有化?
因为如果不写拷贝构造函数, 还可以通过调用默认的拷贝构造函数去构建对象.如:
Rocket *p1 = Rocket::sharedRocket();
Rocket *p2 = new Rocket(*p1)
多态4-虚表的汇编分析
调用speak()
// 调用speak
Animal *cat = new Cat();
cat->speak();
// ebp-8 是指针变量cat的地址
mov eax, dword ptr [ebp-8]
// 根据指针变量的地址, 找到指针变量的存储空间, 取出存储空间里面的东西, 也就是Cat对象的地址给eax
// 所以eax是Cat对象的地址
mov edx, dword ptr [eax]
// 根据Cat对象的地址值, 找到Cat对象的存储空间, 取出4个字节出来赋值给edx
// 取出Cat对象最前面的4个字节(虚表的地址)给edx
mov eax, dword ptr [edx]
// 根据edx的地址值, 找到edx的存储空间, 取出虚表的前4个字节(Cat::speak的函数地址)赋值给eax
call eax
// call Cat::speak 调用函数
调用run()
// 调用run
Animal *cat = new Cat();
cat->run();
// ebp-8 是指针变量cat的地址
mov eax, dword ptr [ebp-8]
// 所以eax是Cat对象的地址
mov edx, dword ptr [eax]
// 根据Cat对象的地址值, 找到Cat对象的存储空间, 取出4个字节出来赋值给edx
// 取出Cat对象最前面的4个字节(虚表的地址)给edx
mov eax, dword ptr [edx+4]
// 根据edx+4之后的地址值, 从这个地址值开始找到它的存储空间, 取出4个字节(Cat::run的函数地址)赋值给eax
// 跳过虚表的最前面4个字节, 在取出4个字节(Cat::run的函数地址)赋值给eax
call eax
// call Cat::run 调用函数
(3)+=运算符
因为int a = 10, b = 20; (a += b) = 10; 是可以被赋值的, 因为a += b是a + b后的结果又赋值给a, 而a可以被赋值, 所以(p1 += p2)也应该可以赋值
所以应该返回p1对象, 而返回对象(把对象作为返回值)又会导致产生中间对象的问题, 所以应该返回引用.
class Point {
public:
Point &operator+=(const Point &point) {
this->m_x += point.m_x;
this->m_y += point.m_y;
return *this;
}
};
(4)==运算符
int a = 10, b = 20;
if (a == b) 原来是相等返回1, 不相等返回0, 所以是bool类型
bool operator==(const Point &point) const {
// 要保证常量对象可以和非常量对象比较
/* if ((m_x == point.m_x) && (m_y == point.m_y)) {
return 1;
}
else {
return 0;
} */
return (m_x == point.m_x) && (m_y == point.m_y);
}
(5)!=运算符
bool operator!=(const Point &point) const {
return (m_x != point.m_x) || (m_y != point.m_y);
}
(6)-运算符(符号)
首先-p1; 不需要传参, p1.operator-();
而且
Point p1(10, 20);
Point p3 = -p1; 时p1并没有改变, 所以应该返回一个临时的
其次int a = 10, b; (-a) = 10;不允许, 因为a并没有被赋值, 返回的是一个临时的值, 所以-p1不允许被赋值, 所以返回const
const Point operator-() const {
// 因为(-(-p1))时相当于 p1.operator-().operator-(),
// 而返回的const对象不能调用非const函数, 所以函数也要声明为const
return Point(-m_x, -m_y);
}
(7)++, --运算符
1.为了区分前置++和后置++规定
void operator++() {
// 是前置++
}
void operator++(int) {
// 是后置++
}
2.前置++可以被赋值, 后置++不可以被赋值
因为int a = 10;
int b = ++a + 5;
相当于a += 1; int b = a + 5; 也就是说++a是先让a += 1, 再把最新的a返回.所以可以被赋值.
而后置++ int a = 10;
int c = a++ + 5; 会先将a之前的值放到a++的地方等价于int c = 10 + 5; 在 a += 1; 所以a++的话并不会返回a.
3.最终实现:
前置:
Point &operator++() {
m_x++;
m_y++;
return *this;
}
后置:
而Point p2 = p1++ + Point(30, 40);
应该可以, 所以p1不能返回void而应该返回p1之前的值
所以
const Point operator++(int) {
Point old(this->m_x, this->m_y);
this->m_x++;
this->m_y++;
return old;
}
(8)<<运算符
1.因为cout << p1 << endl;
所以<<重载函数不能是成员函数, 一旦是成员函数就必须通过Point对象调用
其实cout也是对象, 它的类是ostream
ostream -> output stream
在头文件iostream中
2.因为cout << 1 << 2 << endl;
所以调用完函数后应该返回cout
3.因为(cout << 1) = cout; 返回cout后不允许被赋值, 所以const
但是返回值是const之后, 会导致cout << p1 << p2 出问题, 所以参数cout也应该const, 但是因为系统自带的const的定义是非const的, 所以参数是const会导致原来的const找不到.所以放弃, 但是发现其实不能被赋值, 因为在iostream头文件里, 将=赋值运算符重载到了private导致外面不能访问=
所以返回值不能是const, cout参数不能是const
ostream &operator<<(ostream &cout, const Point &point) {
cout << "(" << point.m_x << ", " << point.m_y;
return cout;
}
(9)cin运算符
input stream -> istream
1.要去掉后面参数的const, 因为是要输入东西去改变point对象.
istream &operator>>(istream &cin, Point &point) {
cin >> point.m_x;
cin >> point.m_y;
return cin;
}
模版-编译细节
模板的声明和实现如果分离到.h和.cpp中, 会导致链接错误.
编译, 链接原理
1.头文件不会参与编译, 因为头文件是拿来被包含的
2.编译器在编译时, 会对每一个cpp文件单独编译, 有一个add函数时, 通常把函数声明写在头文件add.h, 把函数实现
写在add.cpp文件(要#include “add.h”), 在main.cpp里面(#include “add.h”)即可, 这样的话在main.cpp里面有函数的声明, 没有函数的实现,
那么当使用add函数时, 因为有函数的声明所以不会报错, 但因为是单独编译, 所以main.cpp找不到函数的定义,
所以汇编代码是call <假的函数地址>, 在链接时, 才把假的函数地址修正成真的函数地址.
而如果有模板时, 因为编译add.cpp时, 不会生成具体的函数实现, 因为是单独编译, 不知道main里面的参数类型(没有被使用, 就不会生成函数实现),
所以不会生成具体的实现.所以在链接时, 就无法修正call 的函数地址.所以会导致链接错误
所以在写模板时, 不要把模版的声明和实现分离, 要放在同一个.h文件中
一般将模板的声明和实现统一放到一个.hpp文件(仍是头文件, 只是语义好一点)中, 在直接#include “add.hpp”
dynamic_cast
一般用于多态类型的转换, 有运行时安全检测.
多态类型: 能完成多态功能的那几个类.
用法:
class Person {
virtual void run() {}
};
class Student : public Person {
};
int main() {
Person *p1 = new Person();
Person *p2 = new Student();
Student *stu1 = dynamic_cast<Student *>(p1);
// 不安全, 因为相当于
// Studnet *stu1 = new Person();就成了用子类指针
// 指向父类对象, 不安全, 因为子类指针可以访问的
// 范围超过父类对象所占的内存.
Student *stu2 = dynamic_cast<Student *>(p2);
// 安全, 相当于Student *stu2 = new Student();
}
dynamic_cast 可以检测到是否安全, 一旦检测到不安全, 直接让指针清空 = NULL, 变成空指针.
4.reinterpret_cast
1.属于比较底层的强制转换, 没有任何类型检查和格式转换, 仅仅是简单的二进制数据拷贝
2.语法限制:如果是不同类型之间的转换, 需要用引用, 仅仅是语法糖
3.可以交叉转换
int main() {
int a = 10;
double d = a;
// 不是简单的二进制数据拷贝, 而是转换成浮点数的存储方式
double d = reinterpret_cast<double&>(a);
// 没有任何类型检查, 仅仅是简单的二进
制数据拷贝.
// 但double有8个字节, int有4个字节, 所以仅仅是
// 将a的4个字节覆盖掉double的4个字节, double剩下
// 的4个字节不管. 如果在栈空间默认是cc
}
C++11新特性
1.auto
可以从初始化表达式中推断出变量的类型, 大大简化编程工作
int a = 10;
auto a = 10; // 发现右边是整型, 所以a是int类型
auto str = "C++"; // const char *
auto p = new Person(); // Person*
属于编译器特性, 不影响最终的机器码质量, 不影响运行效率
2.decltype
decl = declear type 声明类型
可以获取变量的类型
int a = 10;
decltype(a) b = 20; // 相当于int b = 20;
3.nullptr
nullptr == null pointer 空指针
以后凡是清空指针都用nullptr不用NULL, 因为不专业
int *p = NULL
int *p1 = nullptr;
可以解决NULL二义性的问题
func(0);
func(nullptr);
func(NULL);
NULL -> #define NULL 0
4.快速遍历
int array[] = {1, 2, 3, 4};
for (int item : array) {
cout << item << endl;
}
// 将array里面的元素挨个取出来赋值给item
// 等价于
for (int i = 0; i < 4; i++) {
int item = array[i];
cout << item << endl;
}
5.更加简洁的初始化方式
int array[]{1, 2, 3 , 4};
完全等价于
int array[] = {1, 2, 3, 4};