小码哥外挂破解


小马哥外挂破解

目录
  • 小马哥外挂破解
  • 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

}