小码哥外挂破解
小马哥外挂破解
目录- 小马哥外挂破解
- day01
- 字体设置
- 显示行号
- 大括号位置
- 快捷键设置
- 常用快捷键
- 修改编码
- 常用语法
- cin cout
- 函数重载
- 默认参数
- extern
- pragma once
- pragma once 可以防止整个文件内容被重复包含
- ifndef,#define #endif 受C/C++标准支持,不受编译器任何限制
- ifndef #define #endif 可以针对一个文件中部分代码,而#pragma once只能针对整个文件
- 内联函数
- const
- day02
- 引用
- 汇编
- day03
- struct 和class区别
- c++编程规范
- 对象的内存布局
- 指针访问对象成员的本质
- 封装
- 内存空间的布局
- 堆空间
- 构造函数
- 构造函数的调用
- 析构函数
- 命名空间
- 命名空间嵌套
- 命名空间合并
- 对象内存布局
- 初始化列表
- 构造函数互相调用
- 构造,析构顺序
- 父类指针,子类指针
- 多态
- 虚表
- 虚析构函数
- 纯虚函数
- 多继承
- 同名函数
- 同名成员变量
- 菱形继承
- 静态成员
- 静态成员
- 静态成员经典应用-单例模式
- const成员
- 引用类型成员
- 拷贝构造函数
- 深拷贝例子
- 友元
- 内部类
- day04
- 运算符重载
- 仿函数(函数对象)
- 模板
day01
字体设置
显示行号
大括号位置
快捷键设置
常用快捷键
修改编码
常用语法
- C++的源文件扩展名是:cpp(c plus plus的简称)
? C++程序的入口是main函数(函数即方法,一个意思)
? C++完全兼容C语言的语法,很久以前,C++叫做C with classes
cin cout
c++ 中常用使用cin,cout输入输出
#incoude
using namespace std;
int main()
{
int age;
cin >> age;
cout << "age is" << age << endl
}
cin用的右移运算符,cout 用的是左移运算符
endl是换行
函数重载
规则
函数名相同
参数个数不同,参数类型不同,参数顺序不同
注意
返回值类型与函数重载无关
调用函数时,实参隐式类型转换可能会产生二义性
本质
?采用了name mangling或者叫name decoration技术
? C++编译器默认会对符号名(比如函数名)进行改编、修饰,有些地方翻译为“命名倾轧”
? 重载时会生成多个不同的函数名,不同编译器(MSVC、g++)有不同的生成规则
? 通过IDA打开【VS_Release_禁止优化】可以看到
默认参数
c++允许函数设置默认参数,在调用时可以根据情况省略实参,规则
- 默认参数只能按照从右到左顺序
- 如果函数声明,实现,默认参数只能放在函数声明中
- 默认参数可以是常量,全局符号(全局变量,函数名)
int age = 3;;
void test()
{
cout << "test()" << end1;
}
void display(int a =11, int b =22, c= age,void (*func)() = test)
{
cout << "a 是" << a end1;
cout << "b 是" << b end1;
cout << "c 是" << c end1;
}
int main()
{
display();
return 0;
}
函数重载,默认·参数会产生冲突,二义性(优先使用默认参数)
void display(int a, int b =20)
{
cout << " a is" << a << endl;
}
void display(int a)
{
cout << " a is" << a << endl;
}
int main()
{
display(10);
return 0;
}
extern
被extern “C”修饰的代码按照C语言方式去编译
extern "C" void func {
cout << func() << endl;
}
extern "C" void func(int age) {
cout << func() << age << endl;
}
--------
extern "C" {
void func {
cout << func() << endl;
}
void func(int age) {
cout << func() << age << endl;
}
}
如果函数同时有声明和实现,要让函数声明被extern “C”修饰,函数实现可以不修饰
extern "C" void func();
extern "C" void func(int age);
void func()
{
cout << func() << endl;
}
void func()
{
cout << func(int age) << age << endl;
}
extern "C"
{
void func();
void func(int age);
}
void func()
{
cout << func() << endl;
}
void func(int age)
{
cout << func(int age) << age << endl;
}
由于C,C++编译规则不同在CC++混合开发时候,可能会经常出现以下操作
C++ 在调用C语言API时候,需要使用extern “C” 修饰C语言函数声明
有时候也会在编写C语言代码直接使用extern “C”,这样就可以直接被C++调用
pragma once
经常 #ifndef #define #endif 来防止头文件的内容被重复包含
pragma once 可以防止整个文件内容被重复包含
区别
ifndef,#define #endif 受C/C++标准支持,不受编译器任何限制
有些编译器不支持#pragma once (较老的编译器不支持),兼容性不好
ifndef #define #endif 可以针对一个文件中部分代码,而#pragma once只能针对整个文件
内联函数
使用inline修饰函数声明或者实现,可以使其变成内联函数
建议声明和实现都增加inline修饰
特点
- 编译器将减少函数用直接展开为函数体代码
- 可以减少函数调用的开销
- 会增加代码体积
注意
尽量不要内联超过10行代码函数
有些函数即使声明为inline,也不一定会被编译器内联,比如递归函数
内联函数和宏
内联函数和宏,都可以减少函数调用的开销
对比宏,内联函数多了语法检测和函数特性
思考以下代码区别
#define sum(x) (x + x) // 宏
宏会出现问题
++a + ++a
inline int sum(int x)
{ return x + x; }
内联
比如传 ++a
a =10
传11内联是22,宏
int a = 10; sum(a++);
const
const 是常量,被其修饰的变量不可修改
如果修饰的是类,结构体(指针),成员也不可以修改
// p1不是常量,*p1是常量
const int* p1 = &age;
// p2不是常量,*p2是常量
int const* p2 = &age;
// p3是常量,*p3不是常量
int* const p3 = &age;
// p4是常量,*p4也是常量
const int* const p4 = &age;
// p5是常量,*p5也是常量
int const* const p5 = &age;
day02
引用
在c语言中,使用指针可以间接获取,修改某个变量的值
在c++中,使用引用可以引起到跟指针类似的功能
int age = 20;
int &rage = age
注意点
- 引用相当于是变量的别名,基本数据类型,枚举,结构体,类,指针,数组,都可以引用
- 对引用做计算,就是对引用所指向的变量做计算
- 在定义时候就必须初始化,一旦指向某个变量,就不可以再改变,“从一而终
- 可以利用引用初始化另一个引用,相当于是某个变量的多个别名
- 不存引用的引用,指向引用的指针和引用数组
引用存在的价值,比指针安全,函数返回值可以被赋值
引用的本质就是指针,只是编译器削弱了功能,所以引用就是弱化了指针
一个引用占用一个指针的大小
常引用
引用可以被const修饰,这样就无法通过引用修改数据了,可以被称为常引用
const 必须写在&符号左边,才能算是常引用
const 引用的特点
可以指向临时数据(常量,表达式,函数返回值)
可以指向不同类型的数据
作为函数参数时候(此规则也适用于const 指针)
可以接收const 和非const实参(非const引用,只能接收const 实参)
可以跟非const 引用构成重载
当常引用指向了不同类型数据时候,会产生临时变量,即引用指向并不是初始化的变量
数组的引用
int array[]={1,2,3};
int (&rf1)[3] = array;
int *const &ref2 = array;
汇编
AT&T汇编 vs Intel汇编
x64汇编 – 寄存器
mov dest,src
将src 内容赋值给dest,类似于dest=src
地址值
[] 括号里面放的都是内存地址
word是2字节,dword是4字节,qword是8字节
call 函数地址
调用函数
lea dest,[地址值]
将地址赋值给dest,类似于dest=地址值
ret
函数返回
xor op1,op2
将op1和op2异或结果赋值给op1,类似于op1 = op1^op2
add op1,op2
类似于op1 = op1 +op2
sub op1,op2
类似于op1 = op1-op2
inc op
自增,类似于op= op+1
dec op
自减,类似于op = op-1
jmp 内存地址
跳转到某个内存地址去执行代码
j开头一般都是跳转,大多数是带条件的跳转,一般和test,cmp等指令配合使用
内联汇编
__asm
{
mov eax,10H
mov ebx,20H
}
变量地址总结
一个变量地址值,是它所有字节地址中最小值
day03
c++中可以使用 struct class ;来定义一个类
struct 和class区别
struct 的默认成员权限 是public
class 默认成员权限是private
类的定义
struct Person
// 成员变量
int m_age
// 成员函数
void run()
{
cout << m_age << "run" << endl;
}
class Person
// 成员变量
int m_age
// 成员函数
void run()
{
cout << m_age << "run" << endl;
}
Person person
// 访问person对象的成员变量
person.m_age = 20;
// 用person 对象调用成员函数
person.run()
// 通过指针间接访问person 对象
Person *p = &person;
p->m_age = 30;
p->run();
Person 对象,p指针的内存都是在函数栈空间,自动分配和回收的
可以尝试反汇编struct class 有无区别
实际开发class类表示多
c++编程规范
变量名规范参考
全局变量:g_
成员变量:m_
静态变量:s_
常量:c_
使用驼峰式标识
对象的内存布局
struct Person
{
int m_id;
int m_age;
int m_height;
void display()
{
cout << "m_id is" << m_id << endl;
cout << "m_age is" << m_age << endl;
cout << "m_height is" << m_height << endl;
}
};
this
this 是指向的当前对象指针
对象在调用成员函数时候,会自动传入当前对象的内存地址
struct Person
{
int m_id;
int m_age;
int m_height;
void display()
{
cout << "m_id is" << m_id << endl;
cout << "m_age is" << m_age << endl;
cout << "m_height is" << m_height << endl;
}
};
可以利用this.m_age来访问变量吗
不可以,因为this 是指针,必须是this->m_age
指针访问对象成员的本质
思考:最后打印出来的每个成员变量是多少
Person person
person.m_id = 10
person.m_age = 20
person.m_height = 30;
Person *p = (Person *) &person.m_age;
p->m_id =40;
p->age = 50;
答案是 10,40,50
因为(Person *) &person.m_age; p 已经变成了 m_age 的地址,所以后面偏移的地址是以m_age 的值为基础的
封装
成员变量私有化,提供公共的getter和setter给外界访问成员变量
struct Person
{
private:
int m_age;
public:
void setAge(int age)
{
this->m_age = age;
}
int getAge()
{
return this->m_age;
}
};
Person person;
person.setAge(20);
cout << person.getAge()<< endl;
内存空间的布局
每个应用都有自己独立的内存空间,其内存空间一般都有几大区域
代码段
用于存放代码
数据段
用于存放全局变量
栈空间
每调用一个函数就会给它分配一段连续的栈空间,等函数调用完后会自动回收这段栈空间
自动分配和回收
堆空间
需要主动申请和释放
堆空间
在程序运行过程中,为了能够自由控制生命周期,大小,会经常使用堆空间的内存
堆空间的申请和释放
malloc /free
new/delete
new[] /delete []
注意
申请堆空间成功后,会返回那一段内存空间地址
申请和释放必须是1对1的关系,不然可能会存在内存泄露
很多高级语言屏蔽很多内存细节,
利:提高开发效率,避免内存使用不当造成泄露
弊:不利于开发人员了解本质
这个堆空间没有被释放
堆空间初始化
int *p1 = (int *)malloc(sizeof(int)); *p1未初始化
int *p2 = (int *)malloc(sizeof(int));
memset(p2,0,sizeof(int));将*p2 每一个字节都初始化0
int *p1 = new int; // 未被初始化
int *p2 = new int(); // 被初始化为0
int *p3 = new int(5);//被初始化为5
int *p4 = new int[3];// 数组未被初始化
int *p5 = new int[3]();//3个数组元素都被初始化为0
int *p6 = new int[3]{};//3个数组元素都被初始化为0
int *p7 = new int[3]{5}//数组元素都被初始化5,其他元素被初始化0
memset
memset函数是较大的数据结构,(对象,数组)内存清零比较快的方法
Person person
person.m_id =1;
person.m_age = 20;
person.m_height = 180
memset(&person,0,sizeof(person))
Person persons[] = {{1,2,3},{4,5,6},{7,8,9}};
memeset(persons,0,sizeof(persons));
对象内存
对象内存可以存在于3种地方
全局区(数据段):全局变量
栈空间:函数里面局部变量
堆空间:动态申请内存(malloc,new)
// 全局区
Person g_person
int main()
{
// 栈空间
Person perosn;
// 堆空间
Person *p = new Person
return 0;
}
构造函数
构造函数,在对象创建时候自动调用,一般用于完成对象初始化工作
特点
函数名与类同名,无返回值void ,可以有参数,可以重载,可以有多个构造函数
一旦定义了构造函数,必须用其中一个构造函数来初始化对象
通过malloc分配对象不会调用构造函数
一个广为流传的、很多教程\书籍都推崇的错误结论:
?默认情况下,编译器会为每一个类生成空的无参的构造函数
?正确理解:在某些特定的情况下,编译器才会为类生成空的无参的构造函数
? (哪些特定的情况?以后再提
构造函数的调用
struct Person
{
Person()
{
cout << *Person() << endl;
}
Person(int age)
{
cout << *Person(int age) << endl;
}
}
// 全局区
Person g_p1; // 调用Person()
Person g_p2() // 这是个函数声明,函数名叫g_p2 ,返回值类型是Person,无参数
Person g_p3(20);// 调用Person(int)
int main()
{
// 栈空间
Person p1; // 调用Person
Person p2(); // 这是一个函数声明,函数名叫p2,返回值类型是Person,无参数
Person p3(20);
// 堆空间
Person *p4 = new Person; // 调用
Person *p5 = new Person(); // 调用
Person *p6 = new Person(20);//调用 Person(int)
retrun 0 ;
}
默认情况下,成员变量的初始化
struct Person
{
int m_age ;
}
// 全局区(成员变量初始化为0)
Person g_p1;
int main()
{
//栈空间(成员变量不会被初始化)
Person p1;
//
Person *p2 = new Person; // 成员变量不会被初始化
Person *p3 = new Person(); // 成员变量初始化是0
Person *p4 = new Person[3];// 成员变量不会被初始化
Person *p5 = new Person[3](); // 都初始化0
Person *p6 = new Person[3]{}; // 都初始化0
}
成员变量初始化
对象初始化
Person()
{
memset(this,0,sizeof(Person));
}
析构函数
析构函数(析构器),在对象销毁的时候自动调用,一般用于完成对象清理工作
特点
函数以~开头,与类同名,无返回值(void 都不能写),无参数,不可以重载,有且只有一个析构函数
注意
通过malloc分配对象free的时候不会调用析构函数
构造函数,析构函数要声明public,才能被外界正常使用
struct Car
{
int m_price;
};
struct Person
{
int m_age;
Car m_car
};
Person P;
struct Car
{
int m_price;
};
struct Person
{
int m_age;
Car m_car
};
Person *p = new Person();
struct Car
{
int m_price;
};
struct Person
{
int m_age;
Car *m_car;
Person()
{
m_car = new Car();
}
~Person()
{
delete m_car;
}
};
Person *p = new Person(); // 对象内部申请的堆空间,由对象内部回收
声明实现分离
class Person
{
private:
int m_age;
public:
void setAge(int age);
int getAge();
Person();
~Person
}
Person::Person()
{
}
void Person::setAge(int age)
{
this->m_agge = age;
}
int Person::getAge()
{
return this->m_age;
}
Person::~Person()
{}
命名空间
命名空间可以用来避免命名冲突
namespace MJ
{
int g_age;
class Person
{
};
void test()
{
}
}
int main()
{
MJ::g_age = 20;
MJ::Person *p = new MJ:: Person();
MJ::test();
return 0 ;
}
using namespace MJ
g_age = 20;
Person *p = new::g_age
using MJ::g_age;
g_age = 20;
命名空间不影响内存布局
namespace MJ
{
int g_age;
}
namespace FX
{
int g_age;
}
using namespace MJ;
using namespace FX;
命名空间嵌套
namespace MJ
{
namespace SS
{
int g_age;
}
}
int main()
{
MJ::SS::g_age =10;
using namespace MJ::SS;
g_age = 20;
using MJ::SS::g_age;
g_age = 30;
return 0;
}
有个默认的全局命名空间,创建的命名空间默认都嵌套在里面
int g_no;
namespace MJ
{
namespace SS
{
int g_age;
}
}
int main()
{
::g_no = 20;
::MJ::SS::g_age = 30;
return 0;
}
命名空间合并
int g_no;
namespace MJ
{
int g_age;
}
namespace MJ
{
int g_no;
}
namespace MJ
{
int g_age;
int g_no;
}
命名空间合并
namespace MJ
{
class Person
{
public:
Person();
~Person();
}
}
namespace MJ
{
Person::Person()
{}
Person::~Person()
{}
}
继承
继承,可以让子类拥有父类所有成员,包括变量和函数
struct Person
{
int m_age;
void run()
{
cout << "Person::run()" << endl;
}
};
struct Student :Person
{
int m_no;
void study()
{
cout << "Student::study()" << endl;
}
}
Student student
student.m_age = 20;
student.m_no = 1;
student.run();
student.study();
Student 是子类(subclass,派生类)
Person是子类(superclss,超类)
c++没有基类
对象内存布局
struct Person
{
int m_age; //4字节
};
struct Student :Person
{
int m_no // 8字节
};
struct GoodStudent : Student
{
int m_money; //12字节
};
GoodStudent gs;
gs.m_age = 20;
gs.m_no = 1;
gs.m_money = 66;
父类成员变量在前,子类成员变量在后
成员访问权限
成员访问权限,继承方式有3种
public:公共的,任何地方都能访问,struct 默认
protected:子类内部,当前类内部都可以访问
private:私有的,只有当前类内部可以访问
子类内部访问父类成员权限,是以下权限中最小那个
成员本身的访问权限
上一级父类继承方式
开发中用的最多的是public,这样能保留父类原来的成员权限
访问权限不影响对象内存布局
初始化列表
特点
一种便捷的初始化成员变量的方式
只能用在构造函数
初始化顺序只和成员变量的声明有关
struct Person
{
int m_age;
int m_heright;
Person(int age,int height)
{
this->m_age = age;
this->m_height = heright;
}
}
struct Person
{
int m_age;
int m_heright;
Person(int age,int height):m_age.m_height(htight){
}
};
初始化列表和默认参数配合使用
struct Person
{
int m_age;
int m_height;
Person(int age = 0,int height = 0): m_age(age),m_height(height) {}
};
int main()
{
Person person1;
Person person2(18);
Person person3(20,180);
return 0 ;
}
如果函数声明和实现是分离的
初始化列表只能写在函数声明中
构造函数互相调用
struct Person
{
int m_age;
int m_height;
Person():Person(0,0){}
Person(int age,int height):m_age(age),m_height(height){}
};
错误示范:初始化的是一个临时对象
struct Person
{
int m_age;
int m_height;
Person()
{
Person(0,0);
}
Person(int age,int height):m_age(age),m_height(height){}
};
父类的构造函数
子类的构造函数会调用父类的无参构造函数
如果子类构造函数显式调用父类的有参构造函数,就不会去默认调用父类的无参构造函数
如果父类缺少无参构造函数,子类的构造函数必须显式调用父类的有参构造函数
继承体系下的构造函数
struct Person
{
int m_age;
Person():Person(0)
{}
Person(int age):m_age(age)()
};
struct Student :Person
{
int m_no;
Student():Student(0,0)
{}
Student(int age,int no):Person(age),m_no(no){}
}
构造,析构顺序
struct Person
{
Person()
{
cout << "Person::Person()" << endl;
}
~Person()
{
cout << "Person::~Person()" << endl;
}
};
struct Student:Person
{
Student()
{
cout << "Student::Student()" << endl;
}
~Student()
{
cout << "Student::~Student()" << endl;
}
};
int main()
{
{
Student student;
}
getchar();
return 0;
}
父类指针,子类指针
父类指针可以指向子类对象,是安全的,继承方式必须是public
子类指针指向父类对象是不安全的
struct Person
{
int m_age;
}
struct Student:Person
{
int m_score;
}
多态
默认情况下,编译器只会根据指针类型调用对应的函数,不存在多态
多态是面向对象非常重要的特性
同一操作作用于不同对象,可以有不同解释,产生不同执行结果
在运行时,可以识别出真正的对象类型,调用对应的子类中的函数
多态的要素
子类重写父类成员函数
父类指针指向子类对象
利用父类指针调用重写的成员函数
虚函数
c++中多态通过虚函数来实现
虚函数:被virtual修饰的成员函数
只要在父类中声明为虚函数,子类中重写函数也自动变成虚函数(也就是说子类中可以省略virtual关键字)
虚表
虚函数实现原理是虚表,这个虚表里面存储最终需要调用的虚函数地址,这个虚表也叫虚函数表
class Animal
{
public:
int m_age;
virtual void speak()
{
cout << "Animal::speak()" << endl;
}
virtual void run()
{
cout << "Animal::run()" << endl;
}
};
class Cat :public Animal
{
public:
int m_life:
void speak()
{
cout << "Cat::speak()" << endl;
}
void run()
{
cout << "Cat::run()" << endl;
}
}
Animal *cat = new Cat();
cat->m_age = 20;
cat->speak();
cat->run();
所有的cat对象(不管在全局区,栈,堆)共用同一份虚表
调用父类成员函数实现
class Animal
{
public:
virtual void speak()
{
cout << "Animal::speak()" << endl;
}
};
class Cat:public Animal
{
public:
void speak()
{
Animal::speak();// 直接通过父类名字调用父类成员函数
cout << "Cat::speak()" << endl;
}
}
虚析构函数
如果存在父类指针指向子类对象情况,应该将析构函数声明为虚函数(虚析构函数)
delete 父类指针,才会调用子类析构函数,保证析构的完整性
class Person
{
public:
virtual void run()
{
cout << "Person::run()" << endl;
}
virtual ~Person()
{
cout << "Person::~Person()" << endl;
}
};
class Student :public Person
{
public:
void run()
{
cout << "Student::run()" << endl;
}
~Student()
{
cout << "Student::~Student()" << endl;
}
}
纯虚函数
纯虚函数:没有函数体且初始化为0的虚函数,用来定义接口规范
抽象类也可以包含非纯虚函数,成员变量
如果父类是抽象类,子类没有重写纯虚函数,那么子类依然是抽象类
class Animal
{
virtual void speak() = 0;
virtual void walk() = 0;
}
多继承
c++允许一个类可以有多个父类
class Student
{
public:
int m_score;
void studey()
{
cout << "Student::study()" << endl;
}
};
class Worker
{
public:
int m_salay;
void work()
{
cout << "worker::work()" << endl;
}
};
class Updergraduate:public Student,public Worker
{
public:
int m_grade;
void play()
{
cout << "Updergraduate::play()" << endl;
}
};
Updergraduate ug;
ug.m_score =100;
ug.m_salary = 2000;
ug.m_grade = 4;
ug.study();
ug.work();
ug.play();
多继承体系下的构造函数的调用
class Student
{
int m_score;
public:
Student(int score)
{
this->m_score = score;
}
};
class Worker
{
int m_salary;
public:
worker(int salary)
{
this->m_salary = salary;
}
};
class Updergraduate:public Student,public Worker
{
public:
Undergraduate(int score,int salary):Student(score),Worker(salary){}
}
多继承-虚函数
如果子类继承多个父类都有虚函数,那么子类对象就会产生多张虚表
class Student
{
public:
virtual void study
{
cout << "Student::study()" << endl;
}
};
class Worker
{
public:
virtual void work()
{
cout << "Worker::work()" << endl;
}
};
class Updergraduate:public
同名函数
struct Student
{
public:
void cat()
{
cout << "Student::eat()" << endl;
}
};
class Worker
{
public:
void eat()
{
cout << "Worker::eat()" << endl;
}
};
class Updergraduate:public Student,public Worker
{
public:
void eat()
{
cout << "Updergraduate::eat()" << endl;
}
}
Updergraduate ug;
ug.Student::eat();
ug.Worker::eat();
ug.Updergraduate::eat();
ug.eat();
同名成员变量
class Student
{
public:
int m_age;
};
class Worker
{
public:
int m_age;
};
class Updergraduate:public Student,public Worker
{
public:
int m_age;
};
Updergraduate ug;
ug.m_age = 10;
ug.Student::m_age =20;
ug.Worker::m_age =30;
cout << ug.Updergraduate::m_age << endl;
cout << ug.Student::m_age << endl;
cout << ug.Worker::m_age << endl;
菱形继承
菱形继承的问题
最底下的子类从基类继承的成员变量的冗余,重复
最底下子类无法访问基类的成员,有二义性
菱形继承
class Person
{
int m_age;
};
class Student :public Person
{
int m_score;
};
class Worker :public Person
{
int m_salary;
};
class Updergraduate:public Student,public Worker
{
int m_grade;
};
虚继承
虚继承可以解决菱形继承带来的问题
class Person
{
int m_age;
};
class Student : virtual public Person
{
int m_score;
};
class Worker : virtual public Person
{
int m_salary;
};
class Updergraduate:public Student,public Worker
{
int m_grade;
};
静态成员
静态成员:被static 修饰的成员变量\函数
可以通过对象(对象.静态成员),对象指针(对象指针->静态成员),类访问(类名::静态成员)
静态成员变量
存储在数据段(全局区,类似于全局变量),整个程序运行过程中只有一份内存
对比全局变量,它可以设定访问权限,达到局部共享的目的
必须初始化,必须在类外面初始化,初始化时不能带static,如果类的声明和实现分离(在实现.cpp中初始化)
静态成员函数
内部不能使用this指针(this指针只能在非静态成员函数内部)
不能是虚函数(虚函数只能是非静态成员变量\函数)
构造函数,析构函数不能是静态
当声明和实现分离时,实现部分不能带static
静态成员
class Car
{
int m_price;
static int ms_count;
public:
static int getCount()
{
return ms_count;
}
Car(int price = 0):m_price(price)
{
ms_count++;
}
~Car()
{
ms_count--;
}
};
int Car::ms_count = 0;
静态成员经典应用-单例模式
class Rocker
{
private:
Rocker(){}
~Rocker(){}
static Rocker *ms_rocket;
public:
static Rocket *sharedRocket()
{
// 这里考虑多线程的安全
if(ms_rocket ==NULL)
{
ms_rocket = new Rocket();
}
return ms_rocket;
}
static void deleteRocket()
{
// 这里考虑多线程的安全
if(ms_rocket !=NULL)
{
delete ms_rocket;
ms_rocket =NULL;
}
}
}
const成员
const成员:被const 修饰的成员变量,非静态成员函数
const成员变量
必须初始化(类内部初始化),可以在声明时候直接初始化赋值
非static的const成员变量还可以在初始化列表中初始化
const 成员函数(非静态)
const关键字写在参数列表后面,函数声明和实现都必须带const
内部不能修改非static成员变量
内部只能调用const成员函数,static成员函数
非const 成员函数可以调用const 成员函数
const 成员函数和非const成员函数构成重载
非const对象(指针)优先调用非const成员函数
const 对象指针只能调用const成员函数,static成员函数
class Car
{
const int mc_wheelsCount =20;
public:
Car():mc_wheelsCount(10){}
void run() const
{
cout << "run()" << endl;
}
};
引用类型成员
引用类型成员变量必须初始化(不考虑static情况)
在声明时候直接初始化
通过初始化列表初始化
calss Car
{
int age;
int &m_price = age;
public:
Car(int &price) :m_pricce(price){}
};
拷贝构造函数
拷贝构造和拿书是构造函数的一种
当利用已存在的对象创建一个新对象时候,就会调用新对象的拷贝构造函数进行初始化
拷贝构造函数格式是固定的,接收一个const引用作为参数
class Car
{
int m_price;
public:
Car(int price = 0):m_price(price){}
Car(const Car &car)
{
this->m_price = car.m_price;
}
}
调用父类的拷贝构造函数
class Perosn
{
int m_age;
public:
Person(int age):m_age(age){}
Person(const Person &perosn):m_age(perosn.m_age)
};
class Student:public Perosn
{
int m_score;
public:
Student(int age,int score):Perosn(age),m_score(score){}
Student(const Student &student):Person(student),m_score(student.m_score)
};
Car car(100,"BMW 730Li");
Car car2 =car;
Car car4;
car4 = car3;
car4 = car3 是一个赋值操作,并不会拷贝构造函数
深拷贝,浅拷贝
- 编译器默认提供的拷贝是浅拷贝
- 将一个对象中所有成员变量的值拷贝到另一个对象
- 如果是某个成员变量是一个指针,只会拷贝指针中存储的地址值,并不会拷贝指针指向的内存空间
- 可能会导致堆空间多次free问题
如果需要实现深拷贝,就需要自定义拷贝构造函数
将指针类型成员变量所指向的内存空间拷贝到新的内存空间
深拷贝例子
class Car
{
int m_price;
char *m_name;
void copyName(const char *name)
{
if(name == NULL) return;
this->m_name = new char[strlen(name)+1]{};
strcpy(this->name,name);
}
public:
Car(int price = 0,const char *name = NULL):m_price()
{
copyName(name);
}
Car(const Car &car)
{
this->m_price = car.m_price;
copyName(car.name);
}
~Car()
{
if (this->m_name != NULL)
{
delete[] this->m_name;
}
}
};
对象型参数和返回值
使用对象类型作为函数的参数或返回值,可能会产生一些不必要的中间对象
class Car
{
int m_price;
public:
Car(){}
Car(int price):m_price(price){}
Car(const Car &car):m_price(car.m_price){}
};
void test1(Car car)
{}
Car test()
{
Car car(20);
return car;
}
Car car1(10);
test(car1);
Car car2 = test2();
Car car3(30);
car3 = test();
匿名对象:没有变量名,没有被指针指向的对象,用完后马上调用析构
void test1(Car car1)
{
}
Car test2()
{
return Car(60);
}
Car(10);
Car(20).display();
Car car1 = Car(30);
test1(Car(40));
Car car3(50);
隐式构造
c++中存在隐式构造,某些情况下,会隐式调用单参数的构造函数
class Car
{
int m_price;
public:
Car(){}
Car(int price):m_price(price){}
Car(const Car &car):m_price(car.m_price){}
};
void test1(Car car)
{}
Car test()
{
Car car(20);
return car;
}
Car car1(10);
test(car1);
Car car2 = test2();
Car car3(30);
car3 = test();
void test1(Car car)
{}
Car test2()
{
return 70;
}
Car car1 = 10;
Car car2(20);
ca2 = 30; 匿名对象
test1(40);
Car car3 = test2();
可以通过关键字explicit禁止掉隐式构造
calss Car
{
int m_price;
public:
Car(){}
explicit Car(int price):m_price(price){}
Car(const Car &car):m_price(car.m_price){}
};
编译器自动生成的构造函数
c++编译器在某些特定情况下,会给类自动生成无参构造函数
比如:
- 成员变量在声明同时进行初始化
- 有定义虚函数
- 虚继承了其他类
- 包含对象类型成员,且成员有构造函数(编译器生成自定义)
- 父类有构造函数(编译器生成自定义)
总结:对象创建,需要做一些额外操作,内存操作,函数调用,编译器一般会自动生成无参构造函数
友元
- 友元包括友元函数和友元类
- 如果将函数A声明为C类友元函数,那么函数A就能直接访问C类对象所有成员
- 如果将函数A声明为C类友元函数,那么函数A的成员函数就能直接访问C类对象所有成员
- 友元破坏面向对象封装性,但在某些频繁访问成员变量的地方可以提高性能
class Point
{
friend Point add(const Point &,const Point &);
frient class Math;
private:
int m_x;
int m_y;
public:
Point(){}
Point(int x,int y ):m_x(x),m_y(y){}
};
Point add(const Point &p1,const Point &p2)
{
return Point(p1.m_x +p2.m_x,p1.m_y+p2.m_y);
}
class Math
{
void test()
{
Point point;
point.m_x = 10;
point.m_y = 20;
}
static void test2()
{
Point point;
point.m_x = 10;
point.m_y = 20;
}
}
内部类
- 如果将类A定义在类C内部,那么类A就是一个内部类(嵌套类)
- 内部类特点
- 支持public ,protected,private权限
- 成员函数可以直接访问外部类对象所有成员
- 成员函数可以直接不带类名,对象访问外部类的static成员
- 不会影响外部类的内存布局
- 可以在外部类内部声明,在外部类外面进行定义
class Point
{
static void test1(){
cout << "Point::test1" <
内部类-声明和实现分离
class Point
{
class Math
{
void test();
};
};
void Point:Math::test() {}
class Point
{
calss Math;
};
class Point::Math
{
void test()
{}
};
class Point
{
class Math;
};
class Point::Math
{
void test();
};
void Point::Math::test(){}
局部类
在一个函数内部定义的类,叫局部类
局部类特点
作用域仅限于所在函数内部
所有成员必须定义在类内部,不允许定义static成员变量
成员函数不能直接访问函数内部变量(static变量除外)
int m_age1 = 0;
void test()
{
static int s_age2= 0;
int age3 = 0;
Class Point
{
int m_x;
int m_y;
public:
static void display()
{
m_age1 = 10;
m_age2 = 20;
age3 = 30;
}
};
Point::display();
}
day04
运算符重载
运算符重载:可以为运算符增加一些新的功能
全局函数,成员函数都支持运算符重载
class Point
{
public:
int m_x;
int m_y;
Point(int x,int y):m_x(x),m_y(y){}
Point(const Point &point):m_x(point.m_x),m_y(point.m_y){}
void display()
{
cout << "x= "<m_x << ",y=" <m_y <
class Person
{
int m_age;
public:
Person &operator = (const Person &person)
{
this->m_age = person.m_age;
}
};
class Student :public Person
{
int m_score;
public:
Student &operator = (const Student &student)
{
Person::operator=(student);
this->m_score = student.m_score;
}
};
单例模式补充
仿函数(函数对象)
仿函数:将一个对象当做一个函数一样来使用
class Sum
{
public:
int operator()(int a, int b)
{
return a+b;
}
}
Sum sum:
cout << sum(20,30) << endl
对比普通函数,它作为对象可以保存状态
运算符重载注意点
有些运算不可以被重载
对象成员访问运算符 .
域运算符: ::
三目运算 ? :
sizeof
有些运算符只能重载为成员函数
赋值运算 =
下标运算 [ ]
函数运算符 ()
指针访问成员 ->
模板
泛型,将类型参数化以达到代码复用的技术,c++使用模板来实现泛型
模板格式
template
typename 和class等价的
模板没有被使用,是不会被实例化出来的
模板声明和实现如果分离到.h和.cpp中,会导致链接错误
一般将模板的声明和实现统一放到一个.hpp文件中
函数模板
template void swapValues( T &v1, T&v2)
template
void swapValues(T &v1, T&v2)
{
T temp = v1;
v1=v2;
v2 =temp;
}
int main()
{
int a = 10;
int b = 20;
swapValues(a,b);
swapValues(a,b);
}
多参数模板
多参数模板
template
void display(const T1 &v1,const T2 &v2)
{
cout << v1 << endl
cout << v2 << endl
}