四、面向对象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;
}

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

  • 19
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吾名招财

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值