c++this指针 友元类 虚函数 static new和delete 定位new表达式

1,this指针
this指针是成员函数第一个隐含的指针形参,一般情况下由编译器通过exc寄存器自动传递,不需要用户传递
寄存器:eax ebx ecx edx ebp esp…
空间小 速度快 离cpu近的存储空间

一般情况下内存中的数据要进行计算,基本上都是先将数据放到寄存器中,然后再来进行运算

  1. this指针的类型: *const
  2. 只有在类的非静态成员函数中才可以使用this指针,其他任何函数都不可以
    this指针的特点:
    1.全局函数静态函数都不能使用this指针,实际上成员函数默认第一个参数是 T* const this
class A
{
public:
int func(int p)
{
}
};

2.func在编译器看来是:int func (A const this , int p );
this在成员函数的开始前构造,在成员函数的结束后清除。
这个生命周期同任何一个函数的参数是一样的,没有任何区别。
当调用一个类的成员函数时,编译器将类的指针作为函数的this参数传递进去。如:
A a;
a.func(10);
此处,编译器将会编译成:
A::func(&a,10);

2.友元
友元函数:友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声
明,声明时需要加friend关键字。

实际上具体大概有下面两种情况需要使用友元函数:(1)运算符重载的某些场合需要使用友元。(2)两个类要共享数据的时候。

class Data
{
	friend ostream& operator<<(ostream _cout, const Data& d);
	friend istream& operator>>(istream& _cin, Data& d);
public:
	Data(int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
	{}
private:
	int _year;
	int _month;
	int _day
};
ostream& operator<<(ostream& _cout, const  Data& d)
{
	_cout << d._year << "-" << d._month << "-" << d._day;
	return _cout;
}
istream& operator(istream& _cin, Data& d)
{
	_cin >> d._year;
	_cin >> d._month;
	_cin >> d._day;
	return _cin
}
int main()
{
	Data d;
	cin >> d;
	cout << d << endl;
	return 0;
}

友元函数可访问类的私有和保护成员,但不是类的成员函数
友元函数不能用const修饰
友元函数可以在类定义的任何地方声明,不受类访问限定符限制
一个函数可以是多个类的友元函数

**友元类:**友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员

友元关系是单向的,不具有交换性。
友元关系不能传递:b是a的友元,c是b的友元,则不能说c是a的友元。

虚函数
在某基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数。
用法格式为:virtual 函数返回类型 函数名(参数表) {函数体};实现多态性,通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数。
由virtual修饰的函数就是虚函数。
在派生类对基类中声明的虚函数重新定义时,**关键字virtual可写可不写。**但在基类中的virtual关键字是必须要加的。

**构造函数不能是虚函数,但析构函数可以。**因为虚函数作为运行过程中的多态基础,是以对象为核心的,而构造函数是用来构建对象的,也就是说先有对象,后有虚函数,所以构造函数绝对不可能是虚函数。但析构函数是用来销毁对象的,是在对象后,所以可以是虚函数。

析构函数不是虚函数,容易引发内现存泄露

class Base
{
public:
	Base()
	{
		std::cout << "Base::Base()" << std::endl;
	}
	~Base()
	{
		std::cout << "Base::~Base()" << std::endl;
	}
};
class Derive :public Base
{
public:
	Derive{
		std::cout << "Derive::Derive()" << std::endl;
	}
};
int main()
{
	Base* pBase = new Derive();
	delete pBase;
	return 0;
}

两个类各自有构造函数和析构函数,并且基类和派生类的析构函数都是非虚函数。析构函数只做了局部销毁工作,这可能造成资源泄露、损坏数据结构等问题。解决此问题的方法:只要给基类一个virtual 析构函数即可。

class Base
{
public:
	Base()
	{
		std::cout << "Base::Base()" << std::endl;
	}
	virtual ~Base()
	{
		std::cout << "Base::~Base()" << std::endl;
	}
};

把基类析构函数声明为虚函数就可以了。

static
c语言中:
1.静态局部变量:用于函数体内部修饰变量,这种变量的生存期一直到程序关闭。
作用域:函数体内部, 生存期:整个程序运行期间
声静态局部变量:用于函数体内部修饰变量,这种变量的生存期一直到程序关闭。
2.静态全局变量:定义在函数体外,用于修饰全局变量,表示该变量只在本文件可见。
作用域:该文件内部, 生存期:整个程序运行期间

作用域:函数体内部, 生存期:整个程序运行期间明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数,静态的成员变量一定要在类外进行初始化。
1.静态成员为所有类对象所共享,不属于某个具体的实例
2. 静态成员变量必须在类外定义,定义时不添加static关键字
3. 类静态成员即可用类名::静态成员或者对象.静态成员来访问
4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员

c++中:
1.静态数据成员:用于修饰 class 的数据成员,即所谓“静态成员”。这种数据成员的生存期大于 class 的对象(实体 instance)。静态数据成员是每个 class 有一份,普通数据成员是每个 instance 有一份,因此静态数据成员也叫做类变量,而普通数据成员也叫做实例变量。

2、静态成员函数:用于修饰 class 的成员函数。静态函数属于类的,不属于某一个具体的对象。访问方式,可以通过对像调用,也可以用类名::函数名进行访问。

1.静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数;
2.非静态成员函数可以任意地访问静态成员函数和静态数据成员;
3.静态成员函数不能访问非静态成员函数和非静态数据成员;

class Myclass
{
public:
	Myclass(int a, int b, int c);
	static void GetSum(); // 声明静态成员函数
private:
	int a, b, c;
	static int Sum; //声明静态数据成员
};
int Myclass::Sum = 0; //定义并初始化静态数据成员
Myclass::Myclass(int a, int b, int c)
{
	this->a = a;
	this->b = b;
	this->c = c;
	Sum += a + b + c; //非静态成员函数可以访问静态数据成员
}
void Myclass::GetSum() //静态成员函数的实现
{
	// cout<
	cout << "Sum=" << Sum << endl;
}

出现在类外的函数定义不能指定关键字 static。2.静态成员可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数。3.非静态成员函数可以任意访问静态数据成员和静态成员函数。静态成员函数没有额外的this指针。

new 和 delete
new和delete为c++de 内存管理方式
操作内置类型:

void Test()
{
	//动态申请一个int类型的空间
	int *ptr = new int;
	//动态申请一个int类型的空间并初始化为10
	int *ptr1 = new int(10);
	//动态申请3个 int类型的空间
	int *ptr2 = new int[3];
	delete ptr;
	delete ptr1;
	delete[] ptr2;
}

自定义类型:

void Test2()
{
	//申请单个Test类型空间
	Test *p1 = (Test*)malloc(sizeof(Test));
	free(p1);
	//申请10个Test类型的空间
	Test *p2 = (Test*)malloc(sizeof(Test)* 10);
	free(p2);
}

void Test2()
{
	//申请单个Test类型的对象
	Test* p1 = new Test;
	delete p1;
	//申请10个Test类型的对象
	Test* p2 = new Test[10];
	delete[] p2;
}

注意:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc和free不会。

new 与 delete的实现原理
1.如果申请的是内置类型的空间,new和malloc,free和delete基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请失败时会抛出异常,malloc会返回NULL.

2.自定义类型:
new的原理:
1)调用opertor new 函数申请空间
2)在申请空间上执行构造函数,完成对象的构造
delete的原理
1)在空间上执行析构函数,完成对象中资源的清理工作
2)调用operator delete函数释放对象的空间

4.定位new表达式:
点位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象
如果malloc已经开辟了一块自定义类型的空间,但是它没有初始胡这块空间,那么我们要是想初始化这块空间应该怎么办呢?这就引入了定位new表达式。

Date* p = (Date*)malloc(sizeof(Date));
new(p)Date(2018, 2, 2);//定位new表达式

使用场景:定位new在实际中一般是配合内存池使用。因为内存池分配的内存没有初始化所以如果是自定义类型的对象,需要使用new的定位表达式进行显示调构造函数进行初始化。

malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。

不同的地方是:
1.malloc和free是函数,new和delete是操作符
2.malloc申请的空间不会初始化,new可以初始化
3.malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可
4.malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
5.malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
6.申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理
7.new/delete比malloc和free的效率稍微低点,因为new/delete的底层封装了malloc/free

内存泄露
申请空间后,忘记释放,失去了对该段内存的控制,而造成资源的浪费

void MemoryLeaks()
{
	// 1.内存申请了忘记释放
	int* p1 = (int*)malloc(sizeof(int));
	int* p2 = new int;
	// 2.异常安全问题
	int* p3 = new int[10];
	Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.
	delete[] p3;
}

你可能感兴趣的:(C++,c++,内存管理,内存泄漏)