四、面向对象2(30小时精通C++和外挂实战)
B-01-对象的内存
在C++中对象可以自由的放在3中地方,而其他面向对象的语言如java是放在堆空间的,C语言没有面向对象的概念,尽管它也可以申请堆空间
B-02-构造函数
只要是面向对象的语言基本上都有构造函数这个概念
构造函数是用来调用的
#include <iostream>
using namespace std;
struct Person{
int m_age;
};
int main(){
Person person;//创建了person对象,但m_age并未被初始化,此对象在栈空间,里面都是cccc
person.m_age = 0;//此处进行初始化
Person person2;
person.m_age = 0;/
Person person3;
person.m_age = 0;/
getchar();
return 0;
}
如果我们定义了很多的person对象,都要进行初始化,我们希望person对象一初始化出来就是0,如上,但这样写比较麻烦,一般我们写一个构造函数,像这些初始化的东西都放在构造函数中,这我们就不用每次创建对象都去写,构造函数会自动调用。
构造函数函数名和类名一样,没有返回值,此函数比较特殊,不能写void
struct Person{
int m_age;
//下方为构造函数
Person(){
cout << "Person()" << endl;
}
};
int main(){
Person person;//创建了person对象但m_age并未被初始化
person.m_age = 0;//此处进行初始化
Person person2;
Person person3;
getchar();
return 0;
}
我们可以看到结构,构造函数调用了3次,对象创建了三次,说明构造函数是自动调用,我们可以在期内做一些初始化的事,如person.m_age = 0;
,一创建对象,就会调用构造函数,就会初始化
构造函数,可以重载,可以传值,要想调用传值的构造函数,需要传值,如下
struct Person{
int m_age;
Person(){
m_age = 0;
cout << "Person()" << endl;
}
Person(int age){
m_age = age;
cout << "Person(int age)" << endl;
}
void display(){
cout << m_age << endl;
}
};
int main(){
Person person;//调用无参的构造函数
person.display();
Person person2(20);//调用有参构造函数并传值20
person2.display();
Person person3(30);//调用有参构造函数并传值30
person3.display();
getchar();
return 0;
}
一旦自定义了一个构造函数,必须选择其中一个自定义构造函数来初始化person对象
struct Person{
int m_age;
Person(){
cout << "person()" << endl;
}
};
int main(){
//Person person;//向栈空间申请
Person *p = new Person;//向堆空间申请对象内存,因为person对象只有一个int类型变量,int栈4个字节,所以此处是申请4个字节内存从堆空间中
delete p;//此处为了不让内存泄露,将内存释放
无论person对象创建出来,在栈空间还是在堆空间,只要person对象创建完毕,就会强势调用构造函数去初始化它,就算是在全局区创建person对象也会调用构造函数。
只要创建对象都会调用构造函数初始化,只是person对象放的地方不一样,有的在堆空间、栈空间、全局区。
注意malloc来向堆空间申请对象内存,是不会调用构造函数的
我们要记住要想向堆空间申请内存,就要new不要malloc
Malloc这个函数C语言就有了,那时没有对象,所以不会调用构造函数,C语言没有构造函数这个概念。Malloc的作用只是申请堆空间,也没有初始化。
New是c++的。
Sizeof()只是用来将括号中的类型所占的字节数值返回
下面一段代码及其重要必须仔细阅读
struct Person{
int m_age;
Person(){
m_age = 0;
cout << "Person()" << endl;
}
Person(int age){
m_age = age;
cout << "Person(int age)" << endl;
}
};
Person g_person0;//调用无参构造函数,Person()
Person g_person1(); //这不是创建对象,这是函数声明,所以不会调用构造函数
Person g_person2(10);//调用有参构造函数
int main(){
Person g_person0;//调用无参构造函数,Person()
Person g_person1(); //这不是创建对象,这是函数声明,所以不会调用构造函数
Person g_person2(10);//调用有参构造函数
Person *p0 = new Person;//调用无参构造函数,Person()
Person *p1 = new Person();//调用无参构造函数,Person(),这不是函数声明
Person *p2 = new Person(10);//调用有参构造函数
/* 4个无参,3个有参,一共创建了7个person对象 */
getchar();
return 0;
}
B-04-成员变量的初始化
此处讨论不进行自定义构造函数来初始化,而是默认情况下它是否会自动初始化
#include <iostream>
using namespace std;
struct Person{
int m_age;
};
//全局区(全局变量默认就是0,里面全是000000):成员变量初始化为0
Person g_person;
int main(){
//栈空间(默认是没有初始化的,全是cccccc):没有初始化成员变量(若直接运行会报错)
//Person person;
//堆空间
//右侧无(),没有初始化成员变量
Person *p0 = new Person;
//右侧有(),成员变量初始化为0
Person *p1 = new Person();
cout << g_person.m_age << endl;
//cout << person.m_age << endl;
cout << p0->m_age << endl;
cout << p1->m_age << endl;
getchar();
return 0;
}
那么我们有写构造函数,但里面不对其初始化,会对后面成员变量有影响吗,有
struct Person{
int m_age;
Person(){
}
};
我们发现只有全局区依然为0,而new后面有无括号都无初始化,即堆空间无初始化
全局区的东西默认全是0,无论有无构造函数无影响
堆空间,一旦自定义构造函数,它就认为你有想法,就不帮助你初始化了
这些了解一下就行,这些都是语法。
struct Person{
int m_age;
Person(){
memset(this, 0, sizeof(Person));
}
};
memset(this, 0, sizeof(Person));
无论Person中变量有多少,这一句话就全部清零
当创建一个对象时,如Person g_person;
,会将g_person
的地址传入this,将sizeof(Person)
个字节全部清零
B-05-析构函数
跟构造函数是反过来的
在对象销毁的时候自动调用,一般用于完成对象的清理工作
创建对象时,自动调用构造函数,构造函数被调用了,是一个新的person对象诞生的象征。
在对象销毁的时候,可以认为对象内存被回收的时候,就会调用析构函数,在对象销毁之前内部也会进行清理工作
析构函数不能重载,不能传参,有且只有一个析构函数,它是一个person对象销毁的象征,我们可以通过此函数看对象的内存是否销毁
#include <iostream>
using namespace std;
struct Person{
int m_age;
//新的person对象诞生的象征
Person(){
cout << "Person::Person()" << endl;
}
//一个person对象销毁的象征
~Person(){
cout << "~Person()" << endl;
}
};
int main(){
Person person;
getchar();
return 0;
}
此时结果为Person::Person()
,因为对象还活着,main函数并未执行完,getchar使其暂停了,我们可以将对象创建在一个作用域中,活着在另一函数中,这样可以看到函数执行完后,栈空间会回收,对象被销毁,析构函数被调用
int main(){
{
Person person;
}
getchar();
return 0;
}
Malloc申请堆空间既不会调用构造函数,也不会调用析构函数
而new都可以
int main(){
Person *p = new Person; 此处像堆空间申请,会构造
delete(p); 主动释放内存,会析构
getchar();
return 0;
}
在全局区创建的对象是一直存在的,是没有办法看到它析构的
我们讲的是Intel汇编。
B-06-内存管理
#include <iostream>
using namespace std;
struct Car{
int m_price;
Car(){
m_price = 0;
cout << "Car::Car()" << endl;
}
~Car(){
cout << "Car::~Car()" << endl;
}
};
struct Person{
int m_age;
Car *m_car;
//用来做初始化的工作
Person(){
m_age = 0;
m_car = new Car();
cout << "Person::Person()" << endl;
}
//用来做内存清理的工作
~Person(){
cout << "Person::~Person()" << endl;
}
};
int main(){
{
Person person;
}
getchar();
return 0;
}
执行结果
Car::Car()
Person::Person()
Person::~Person()
我们发现有一个类的析构函数没有调用,这就说明一个问题,内存泄露
内存泄露:该释放的内存并没有释放,很明显,car对象的内存没有回收,它所占用的内存依然在
因为car对象是new出来的,它在堆空间,在堆空间不可能自动回收,必须主动调用delete,在哪个地方调用,这个car对象是在person类内部new出来的,所以我们应该在在person的析构函数中delete掉car。
因为person都没有了,那么在person中new的car也就没人用了,所以最合理的地方就在析构函数~person将其delete
析构函数就是用来做内存清理的,清理在类内部产生的堆空间
struct Person{
int m_age;
Car *m_car;
//用来做初始化的工作
Person(){
m_age = 0;
m_car = new Car();
cout << "Person::Person()" << endl;
}
//用来做内存清理的工作
~Person(){
delete m_car;
cout << "Person::~Person()" << endl;
}
};
Person在,car就在,person没了这个car就没价值了,就将其干掉,这样很方便,不要将delete写在外面。
将来成员变量肯定是private,而成员函数,构造函数,析构函数是public。
上图,car类中的指针m_car会指向堆空间的m_price,上面这幅图很清晰
要分清栈空间的对象和堆空间的对象,当调用一个函数时,就会创建一个栈空间存储此函数中的局部变量,而当这个函数执行完后,栈空间就会被回收,里面的局部变量也会被回收。而使用new创建一个对象时,会将此对象的变量放在堆空间,若不delete就会一直存在
此处我们将{person p}
,这就相当于一个函数的调用,出了{}此函数执行完,栈空间内变量回收,而堆空间对象存在如下
上图全部在栈空间
此处创建的指针p在栈空间,person对象在堆空间,所以person对象中的所有变量都在堆空间
对象内部申请的堆空间,应在对象内部回收
B-07-类的声明和实现分离
之前写类的时候,将类的所有东西写在类里面。
#include <iostream>
using namespace std;
class Person{
private:
int m_age;
public:
void setAge(int age){
m_age = age;
}
int getAge(){
return m_age;
}
Person(){
m_age = 0;
}
~Person(){
}
};
int main(){
getchar();
return 0;
}
类中有封装(set函数,get函数),有构造函数Person()
,也有析构函数~Person
上面是在类中,函数声明与实现都在,我们可以让其只有声明,而实现放在其他地方。
如下
class Person{
private:
int m_age;
public:
void setAge(int age);
int getAge();
Person();
~Person();
};
void Person::setAge(int age){
}
int Person::getAge(){
return m_age;
}
Person::Person(){
m_age = 0;
}
Person::~Person(){
}
在类的外面进行函数实现时,需要制定类名,上面写法表示,setAge(int age)是属于 Person类的
Person::
必须放在函数名之前,这是告诉这个函数是属于哪个类的
再这里是讲明类的声明和实现是可以分离的,很多时候,我们将类的声明放到头文件.h,类的实现,我们会将其放到.cpp文件。
在VS2013中,我们在头文件或源文件右击—添加----类文件,名为Person
而在2015,右击添加新建项里面就有类文件。
我们右击头文件–添加–类 。名为Person,会自动创建头文件Person.h和c++文件Person.cpp
在Person.h中程序自定义为下
#pragma once
class Person{
private:
int m_age;
public:
void setAge(int age);
int getAge();
Person();
~Person();
};
而在Person.cpp中的程序如下
#include "Person.h"
void Person::setAge(int age){
m_age = age;
}
int Person::getAge(){
return m_age;
}
Person::Person(){
m_age = 0;
}
Person::~Person(){
}
注意头文件Person.h是需要包含的,否则会报错
当我们要使用这个Person类时,如何使用,在main.cpp中包含person的头文件即可。
注意自己的头文件要用双引号“”,而系统自带的头文件使用尖括号<>
#include <iostream>
#include "Person.h"
using namespace std;
int main(){
Person person;
person.setAge(10);
cout << person.getAge() << endl;
getchar();
return 0;
}
B-08-命名空间
命名空间可以用来避免命名冲突
当我们的类名相同但内容不同时,防止创建对象时不知道调用的那个类名,使用命名空间,将类放到命名空间
#include <iostream>
using namespace std;
namespace MJ{
int g_age;//命名空间的全局变量,尽管在命名空间内,但他也是全局变量
void func(){
}
class Person{
int m_age;
int m_money;
};
}
class Person{
int m_height;
};
int main(){
MJ::g_age = 10;//访问命名空间中的全局变量
MJ::func();//访问命名空间中的函数
MJ::Person person;
cout << sizeof(person) << endl;
getchar();
return 0;
}
如果我们要使用命名空间中的类写法如下
MJ::Person person;
我们访问命名空间中的类需要在前面加MJ,还有简单的方法如下,这样写意味着在整个main函数中都可以直接使用MJ命名空间的东西
int main(){
using namespace MJ;
g_age = 10;
func();
//MJ::g_age = 10;//访问命名空间中的全局变量
//MJ::func();//访问命名空间中的函数
//MJ::Person person;
cout << sizeof(person) << endl;
getchar();
return 0;
}
using namespace std;
这是系统的命名空间,我们比较常用的是cout\endl,若无using则需要
std::cout << 2 << std::endl;
Std是系统中的,我们从iostream向下找就能找到
命名空间不影响内存布局,像全局变量,堆栈空间等,都不影响,只是在访问的时候多了一个命名空间的前缀。
Using namespace 放置的位置不同,作用范围也不同,放置在函数内作用范围就在函数内
我们创建的所有函数,全局变量等默认都在全局空间中,全局空间没有名字调用时
::func();
默认情况下有一个最大的空间,
void func(){
cout << "func()" << endl;
}
namespace MJ{
void func(){
cout << "MJ::func()" << endl;
}
}
int main(){
using namespace MJ;
MJ::func();
::func();
getchar();
return 0;
}
我们也可以在Person.h和Person.cpp中做好命名空间,使用相同名字,令其声明和实现分离。
Person.h文件中内容如下
#pragma once
namespace MJ{
class Person
{
public:
Person();
~Person();
};
}
Person.cpp 文件内容如下
#include "Person.h"
namespace MJ{
Person::Person()
{
}
Person::~Person()
{
}
}
其他编程语言也有命名空间的概念,但有所不同
如java
Java使用package包,使用的是包的概念
在c++中直接将类放到命名空间中,而java是将类放到不同名称的文件夹(如aa,bb)中
Java使用如下
Package com.mj.bb;
Person person1;
那么在c++中是不能这样的,在我们使用的时候,我们是不用include那个文件夹的,编译器只认识类。
B-09-继承
新建–windows桌面应用程序----21继承
让
#include <iostream>
using namespace std;
struct Person{
int m_age;
void run(){}
};
struct Student:Person{
int m_score;
void study(){}
};
struct Worker:Person{
int m_salary;
void work(){}
};
int main(){
getchar();
return 0;
}
将其共性的东西放在一个类person,在c++中继承很简单
struct Student:Person{}
相当于Student继承了Person这个类
将person的所有东西都继承过来,所有的成员变量及成员函数
Java中有基类,最基本的类,其他所有的类都会继承自它。类的老祖宗
在java中不写继承那个类,默认都是继承基类
c++中没有基类
在c++中相当于直接将父类的成员变量,成员函数拿过来
继承类的对象的内存分布
上图中创建的gs对象是占12个字节的。
父类的成员变量地址值会排在前面
B-10-成员访问权限
#include <iostream>
using namespace std;
//我们可以更改下方的权限看程序报错信息,知道其作用
struct Person{
//private:
//protected:
public:
int m_age;
void run(){
m_age = 5;
}
};
struct Student :Person{
void study(){
m_age = 10; //因为继承Person,故可以直接m_age
}
};
int main(){
Person person;
person.m_age = 11;
getchar();
return 0;
}
我们一般选择共有继承,因为共有继承能很好的将原来父类的权限继承下来。