构造与析构

一.构造函数

构造函数就是对类对象进行初始化赋值。构造函数由编译器自动调用,且整个过程只调用一次。

1.构造函数性质

  • 没有返回值也不写void
  • 可以有参数和函数重载。
  • 构造函数函数名与类名一致。
  • 构造函数由编译器自动调用,且整个过程只调用一次。

语法格式:

#include<iostream>
using namespace std;


class A{
public:
	A()
	{
		cout<<"构造函数A()"<<endl;	
	}
	A(int num)
	{
		cout<<"构造函数A(int num)"<<endl;
	}
private:
};
int main()
{
	A test;
	A test1(4); //注意名称
}

2.构造函数重载分类及调用

构造函数分为有残构造和无参构造,如上代码。

按类型分为普通构造和拷贝构造。

(1)普通构造函数重载调用方法:(也可以类外定义,类内声明)

括号法、隐式法、显示法

#include<iostream>
using namespace std;


class A{
public:
	A()
	{
		cout<<"构造函数A()"<<endl;	
	}
	A(int num)
	{
		cout<<"构造函数A(int num)"<<endl;
	}
	A(const A & a)
	{
		cout<<"拷贝函数A(int num)"<<endl;
	}
private:
};
int main()
{
//括号法
	A test;
	A test1(4);
	A test2(test);
//显示法
	/*A test = A(4);
	A test1 = A(test);
//隐式法
	A test = 4;
	A test1 = test;*/


}

(2)拷贝构造语法格式:

拷贝构造函数(const 类名& 引用名){ … }

在以下三种情况下拷贝构造函数会自动被调用

1.已经创建完毕的对象初始化一个新的对象。

2.值传递方式给函数传参

3.以值引用方式返回局部对象

如果在函数内部返回局部对象并且与输入参数不相关,则优先调用移动构造。

#include <iostream>      //i:input o:output stream:流
#include <string> 
//命名空间
using namespace std;

class Pason
{
public:
	int Get_num()
	{
		return this->num;
	}
	//构造函数基本语法:类名(参数列表) { .. }
	Pason()
	{
		num = 100;
		cout<< "普通构造/无参构造"<<endl;
	}
	//构造函数是可以有参数的,可以发生重载;
	Pason(int Num)
	{
		//数据有效性检验
		num = Num;
		cout<< "有参构造/普通构造"<<Num<<endl;
	}
	//拷贝构造函数
	Pason(const Pason & a)
	{
		this->num = a.num;
		cout<< "有参构造/拷贝构造函数"<<endl;
	}
private:
	int num;
};

void test(Pason a) //值方式传参
{
	
}
Pason test1(Pason & a)//值引用
{
	return a;
}
int main()
{
	/**************构造函数括号法调用*****************/
	Pason a1;
	//cout<< "--使用一个已经创建完毕的对象来初始化一个新对象--" <<endl;
	//Pason a2 = a1;
	//cout<< a2.Get_num() <<endl;
	//cout<< "------------值传递的方式给函数参数传值----------" <<endl;
	//test(a1);
	cout<< "------------------以值方式返回局部对象------------" <<endl;
	test1(a1);                          //g++     编译器优化
}

(3)拷贝构造函数调用规则:

  • 在创建一个类的时候,C++会默认为该类添加至少3个类。默认构造函数(空实现)、默认析构函数(空实现)、默认拷贝函数(浅拷贝)
  • 如果用户定义有参构造,编译器不在提供无参构造,但提供拷贝构造。
  • 如果用户定义拷贝构造,C++不在提供其他构造。

(4)浅拷贝与深拷贝

浅拷贝:拷贝指针变量的值

深拷贝:拷贝指针所指向内存空间

浅拷贝:当用户没有定义拷贝构造的时候,C++会执行默认拷贝构造函数,进行浅拷贝。直接将原内容的地址交给要拷贝的类,两个类共同指向同一空间。

例如:如果是new开辟空间,const 字符串常量,则同族类实现数据共享。(类似stastic成员属性。因此new一定要深拷贝)。

析构函数不能与浅拷贝连用

深拷贝:过开辟和源空间大小相同的空间并将内容拷贝下来再进行操作。不论是否对s2进行操作,都会拷贝一片相同大小的空间以及内容下来。

#include<iostream>
using namespace std;

class Passon{
public:
	inline int Get(void);
	inline void Send(int Num);
	
	Passon()
	{
	//	p = new int;
		cout<<"无参构造"<<num<<endl;
	}
/*	Passon(const Passon & passon)
	{
		this->num = passon.num;
	}*/  深拷贝
	int num;
	int *p;
private:
};

void Passon::Send(int Num)
{
	num  = Num;
}
int Passon::Get(void)
{
	return num;
}

int main()
{
	Passon A;//Passon(10);   Passon A = Passon(10)
	A.Send(10);
	cout<<"Get.num "<<A.Get()<<endl;

	Passon B(A);
	B.Send(11);
	cout<<"Get.num "<<A.Get()<<endl;
	cout<<"Get.num "<<B.Get()<<endl;
}

二.析构函数

析构函数:对象销毁前自动调用,执行一些清理工作。在程序执行的过程中,当遇到对象的生存期结束时系统会自动调用析构函数。然后回收为对象分配的存储空间。析构函数不是按照构造的顺序进行析构,且不能进行重载。如果类内有其他成员函数也是先析构本身。

一个对象只能有一个析构函数,但是可以有多个构造函数。且构造函数和析构函数在整个程序运行过程中只执行一次。

格式:

析构函数基本语法:~类名(){ … }(就是在无参构造函数前加~)

#include <iostream>      //i:input o:output stream:流
#include <string> 
//命名空间
using namespace std;

class Pason
{
public:
	int Get_num()
	{
		return this->num;
	}
	//构造函数基本语法:类名(参数列表) { .. }
	Pason()
	{
		p = new int;
		num = 100;
		cout<< "普通构造/无参构造"<<endl;
	}
	//构造函数是可以有参数的,可以发生重载;
	Pason(int Num)
	{
		//数据有效性检验
		num = Num;
		cout<< "有参构造/普通构造"<<Num<<endl;
	}
	//拷贝构造函数
	Pason(const Pason & a)
	{
		this->num = a.num;
		cout<< "有参构造/拷贝构造函数"<<endl;
	}
	//析构函数
	~Pason()
	{
		delete(p);
		cout<< "析构函数"<<endl;
	}
	
	int * p;
private:
	int num;
	
};

int main()
{
	/**************构造函数括号法调用*****************/
	Pason a1;
	Pason a2(a1);
	//cout<<a2.Get_num()<<endl;
}

三.初始化参数列表

初始化参数列表主要是为了解决引用和const修饰变量的初始化。提高程序执行效率

1.格式

构造函数:属性1(值1),属性2(值2)........

#include<iostream>
using namespace std;

class Pason{
public:
	Pason():a(1),b(a),c(2)
	{
		cout<<"无参构造"<<endl;
		cout<<"a="<<a<<endl;
		cout<<"b="<<b<<endl;
		cout<<"c="<<c<<endl;
	}
private:
	int a;
	int &b;
	const int c;
};

int main()
{
	Pason A;

}

2.构造函数与构造函数初始化列表

构造函数分为初始化阶段和计算阶段。

初始化阶段就是将所有类类型的成员进行初始化,即使该成员没有出现在初始化列表也会被初始化。

构造阶段就是执行构造函数体内的赋值操作。

四.类对象作为类成员

当一个类的数据成员是另一个类的对象时,这个对象称为子对象;子对象可以像普通对象那样使用,唯一要考虑的是子对象构造与析构执行的顺序.

对于含有子类的类,在创建对象的时候:先构造子类,在构造本类。先析构本类,在析构子类。 (好的先儿子,坏的先自己)

格式:

Class A{};
Class B
{
	A a;
}	
注:B类中有对象作为成员,A作为对象成员;

类嵌套计算:先计算子类各成员变量值,在计算本类成员变量值。加和。

#include<iostream>
using namespace std;

class pason
{
public:
	int a;
	short b;
};


class Pason
{
public:
    //long f;  ---8  计算时不会影响别的字节大小
	int a; //4
	short b; //2-->4
	float c;//4
	char d;//1-->4
	pason e;//8
	Pason()//函数在代码区,sizeof不能计算代码区大小
	{
		string str("aaa");
	}
};

int main()
{
	cout<<sizeof(Pason)<<endl;
}

六.静态成员

静态成员分为静态成员属性静态成员函数,静态成员是为了解决数据共享问题。

1.静态成员属性

静态成员属性不是放在栈区,而是像成员函数一样放在类公共区。它不是某个对象中的成员。类静态成员属性也叫类变量

类外定义:函数类型 类名:: 变量名=初值

类内声明:stastic::数据类型 变量名​

2.静态成员函数

类外定义:函数类型 类名:: 函数名(参数){}

类内声明:stastic::数据类型 函数名(参数);

​
#include<iostream>
using namespace std;

class Passon
{
public:
	static int a;
		 
};
int Passon::a = 10;

int main()
{
	Passon A;
	A.a = 11;
	Passon B;
	B.a = 12;
	cout<<A.a<<endl;

}

​

注意

  • 静态成员函数可以通过创建对象去访问,也可以通过类名::函数名去访问
  • 静态成员函数只有访问静态成员属性才有意义,非静态成员属性只有对象存在的时候才有意义。
  • 静态成员函数作用域是整个函数,生命周期是整个程序。
  •   ‘:’用于访问构造函数初始化参数列表。
  •    ‘:: ’用于作用域访问和静态成员函数

七.This指针

This是一个隐含于每一个类对象的特殊指针,该指针值是一个正在被某个成员函数操作的对象的地址。C++通过提供特殊的对象指针,this指针,解决上述问题。This指针指向被调用的成员函数所属的对象;This指针是隐含每一个静态成员函数内的指针(静态成员函数中没有this指针);This指针不需要定义,直接使用即可;

 this指针用于:函数形参和类成员属性重名。this指向类成员属性。

                         在类的非静态成员函数中返回对象本身,可使用return *this;

问题:

小明 22

小明的爸爸差 20

小明的爷爷 差22

小明爷爷年龄???

#include<iostream>
using namespace std;

#include<iostream>
using namespace std;

class Passon
{
public:
	inline void Add(int a);
	inline Passon & get_y(Passon passon);
	Passon():a(0)
	{
	}

//private:
	int a;
};
void Passon::Add(int a)
{
	this->a = a;
}
Passon & Passon::get_y(Passon passon)
{
	this->a = this->a + passon.a;
	return *this; 
}
int main()
{
	Passon test1;
	Passon test2;
	Passon test3;
	
	Passon test4;

	test1.Add(22);
	test2.Add(20);
	test3.Add(22);
	
	
	//test4 = test1.get_y(test2).get_y(test3); //将test1,tes2,test3的临时变量都赋值给test4,test4实现累加
	//cout<<test4.a<<endl;  输出的是64
	
	test1.get_y(test2).get_y(test3); //所有数值在test1中累加
    	

	cout<<test4.a<<endl;
	cout<<test1.a<<endl;
	cout<<test2.a<<endl;
	cout<<test3.a<<endl;

	

}

八.对象指针

和普通指针一样,我们可以定义一个对象指针。对象指针需要创建它指向的实例,然后通过对象指针操作这个指向的实例。

获得一块自由存储区---堆区

1.new和malloc区别

  • malloc、free是标准c/c++的函数。new/delete是c++运算符。
  • 都用于申请释放空间,new/delete其实底层也是执行的malloc/free。智能是因为new和delete在对象创建的时候自动执行构造函数,对象消亡之前执行析构函数。(普通类在调用的时候进执行构造/析构函数,而new/delete一个类的时候会在创建/释放的时候执行构造/析构函数)

new可以返回指定类型的指针,自动计算需要开辟的大小。malloc必须指定开辟空间大小,且开辟类型是void *。

2.new用法

()代表赋初始值,[ ]代表开辟大小 。

int *p = new int(100); //开辟一个整数空间,指定整数初始值为100,返回一个指向该存储空间的地址。

char * p =new char[10];//开辟一个存放字符数组(包括10个元素)的空间,返回首元素的地址;

float *p=new float (3.14159);//开辟一个存放单精度数的空间,并指定该实数的初值为:3.14159,返回一个指向该存储空间的地址;

class A

{

};

A a = new A; // /开辟一个对象空间

注意:new 一个数组不能指定初始值,但可以执行构造函数进行初始化,如果new失败返回一个NULL,可以判断是否new成功。

九.注意点

1.何时调用构造函数?

在类实例化,不是创建。

在new创建一个类

MyClass c1; 
MyClass *c2; 
MyClass *c3 = new MyClass;
MyClass & c4 = c1;
MyClass c5[3];

上述构造函数调用了5次

创建c1

声明c2,但是没有指向,系统不会分配内存(除了指针开辟的4字节内存),但如果写成MyClass *c2 = new MyClass;  系统分配内存了,会调用构造函数

创建c3

c4只是c1的引用

创建类数组,调用3次构造函数

2.静态成员

静态成员属性被声明为const必须类内初始化,声明为volatile无法初始化

静态成员函数不能被声明为virtual函数.。

静态成员函数和非静态成员函数区别是没有this指针。

3.this指针

递推一定用引用,因为引用可读可写,上一个值引用到下一个值,类似于链表具有指向,在数值计算的中间可以进行传递,而不是一个临时变量。

    c1.Add_Age(c2).Add_Age(c3).Add_Age(c4);   //如果传值得话会产生副本,是一个临时变量,没有指向。(1+2+3+4   = 3+3+4 = 6+4)//3就是副本

因此使用this指针要注意引用

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值