您的位置:首页 > 健康 > 养生 > 四、面向对象2(30小时精通C++和外挂实战)

四、面向对象2(30小时精通C++和外挂实战)

2025/3/22 13:02:09 来源:https://blog.csdn.net/qq_44870829/article/details/140671217  浏览:    关键词:四、面向对象2(30小时精通C++和外挂实战)

四、面向对象2(30小时精通C++和外挂实战)

  • B-01-对象的内存
  • B-02-构造函数
  • B-04-成员变量的初始化
  • B-05-析构函数
  • B-06-内存管理
  • B-07-类的声明和实现分离
  • B-08-命名空间
  • B-09-继承
  • B-10-成员访问权限

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并未被初始化,此对象在栈空间,里面都是ccccperson.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);//调用有参构造函数并传值20person2.display();Person person3(30);//调用有参构造函数并传值30person3.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;//右侧有(),成员变量初始化为0Person *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 oncenamespace 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;
}

我们一般选择共有继承,因为共有继承能很好的将原来父类的权限继承下来。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com