【C++】C++学习之类和对象(五)

1.前言

1.1 内容
1)研究C++编译器管理类和对象的方法 【避免死角】
2)c++编译器对类对象的生命周期管理,对象创建、使用、销毁
3)c++面向对象模型初探
4)c++面向对象多态原理探究
5)操作符重载

1.2 具体问题
1)1个对象的生命周期
2)2个对象的生命周期,类与类之间的关系,类A中包含类B
3)N个对象的生命周期

2. 基本概念

1)类、对象、成员变量、成员函数
2)面向对象三大概念 【封装】【继承】【多态】
3)类是一个数据类型,类是抽象的
4)对象是一个具体的变量,占用内存空间

3. 类的封装

3.1 封装
1)封装是面向对象程序设计最基本的特性。把数据(属性)和函数(操作)合成一个整体
2)封装,把客观事物封装成抽象类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行隐藏
备注:有2层含义(把属性和方法进行封装 对属性和方法进行访问控制)
成员变量,C++中用于表示类属性的变量;成员函数,C++中用于表示类行为的函数

3.2 类成员的访问控制
1)Public修饰成员变量和成员函数可以在类的内部和类的外部被访问
2)Private修饰成员变量和成员函数只能在类的内部被访问
3)在用struct定义类时,所有成员的默认属性为public;在用class定义类时,所有成员的默认属性为private

4. 对象的构造和析构

创建一个对象时,常常需要作某些初始化的工作,例如对数据成员赋初值。注意,类的数据成员是不能在声明类时初始化的。为了解决这个问题,C++编译器提供了构造函数(constructor)来处理对象的初始化。构造函数是一种特殊的成员函数,与其他成员函数不同,不需要用户来调用它,而是在建立对象时自动执行。

class Test{
    
    
public:
	Test()
	{
    
    
		cout << "我是构造函数,被调用了" << endl;
	}
	~Test(){
    
    
		cout << "我是析构函数,被调用了" << endl;
	}};

void bbc()
{
    
    
	Test t1;}

void main()
{
    
    
	bbc();
	system("pause");
}

4.1 构造函数
构造函数定义
1)C++中的类可以定义与类名相同的特殊成员函数,这种与类名相同的成员函数叫做构造函数
2)构造函数在定义时可以有参数
3)没有任何返回类型的声明
4)多个对象时,先创建的对象先调用构造函数

构造函数的调用
1)自动调用:一般情况下C++编译器会自动调用构造函数
2)手动调用:在一些情况下则需要手工调用构造函数

4.2 有关析构函数
析构函数定义
1)C++中的类可以定义一个特殊的成员函数清理对象,这个特殊的成员函数叫做析构函数,语法:~ClassName()
2)析构函数没有参数也没有任何返回类型的声明
3)多个对象时,先创建的对象后调用析构函数

析构函数的调用
1)析构函数在对象销毁时自动被调用
2)析构函数调用机制:C++编译器自动调用

4.3 C++编译器构造析构方案 PK 对象显示初始化方案
设计构造函数和析构函数的原因
面向对象的思想是从生活中来,手机、车出厂时,是一样的。生活中存在的对象都是被初始化后才上市的;初始状态是对象普遍存在的一个状态的,普通方案:
a)为每个类都提供一个public的initialize函数;
b)对象创建后立即调用initialize函数进行初始化

优缺点分析
1)initialize只是一个普通的函数,必须显示的调用
2)一旦由于失误的原因,对象没有初始化,那么结果将是不确定的没有初始化的对象,其内部成员变量的值是不定的
3)不能完全解决问题
为什么对象需要初始化 有什么样的初始化方案?【显示调用方法】、【编译器构造析构方案】

3.3.1 显示调用方法
缺点:易忘、麻烦;显示调用init,不能完全解决问题

class Test21
{
    
    
public:
	int m;
	int getM() const {
    
     return m; }
	void setM(int val) {
    
     m = val; }
	int n;
	int getN() const {
    
     return n; }
	void setN(int val) {
    
     n = val; }
public:
	int init(int m, int n)
	{
    
    
		this->m = m;
		this->n = n;
		return 0;
	}
};
int main()
{
    
    
	int rv = 0;
	Test21 t1; //无参构造函数的调用方法
	Test21 t2;
	t1.init(100, 200);
	t2.init(300, 400);
	cout << t1.getM() << " " << t1.getN() << endl;
	cout << t2.getM() << " " << t2.getN() << endl;
	//定义对象数组时,没有机会进行显示初始化
	Test21 arr[3];
	//Test arr_2[3] = {Test(1,3), Test(), Test()};
	system("pause");
	return rv;
}

4.4 构造函数的分类及调用
C++编译器给程序员提供的对象初始化方案,高端大气上档次

class Test
{
    
    
private:	int a,b;
public:
//无参数构造函数
Test()
{
    
    	;	}
//带参数的构造函数
Test(int a, int b)
{
    
    	;  }
//赋值构造函数
Test(const Test &obj)
{
    
    	;   }
public:
void init(int _a, int _b)  //显示调用初始化方法
{
    
    	a = _a;
	b = _b;	}
	};

4.4.1 无参数构造函数调用 Test t1, t2
4.4.2 有参构造函数

//有参数构造函数的三种调用方法
class Test5
{
    
    
	private:
		int a;
	public:
	//带参数的构造函数
	Test5(int a)
	{
    
    
		printf("\na:%d", a);
	}
	Test5(int a, int b)
	{
    
    
		printf("\na:%d b:%d", a, b);
	}
	public:
	};
	int main55()
	{
    
    
		Test5 t1(10); //c++编译器默认调用有参构造函数 括号法
		Test5 t2 = (20, 10); //c++编译器默认调用有参构造函数 等号法
		Test5 t3 = Test5(30); //程序员手工调用构造函数 产生了一个对象 直接调用构造构造函数法
		system("pause");
		return 0;
}

4.4.3 拷贝构造函数调用时机
1)赋值构造函数的四种调用场景(调用时机)
2)首先第1和第2个调用场景

第一个应用场景
class AA
{
    
    
public:
	AA() //无参构造函数 默认构造函数
	{
    
    
		cout << "我是构造函数,自动被调用了" << endl;
	}
	AA(int _a) //无参构造函数 默认构造函数
	{
    
    
		a = _a;
	}
	AA(const AA &obj2)
	{
    
    
		cout << "我也是构造函数,我是通过另外一个对象obj2,来初始化我自己" << endl;
		a = obj2.a + 10;
	}
	~AA()
	{
    
    
		cout << "我是析构函数,自动被调用了" << endl;
	}
	void getA()
	{
    
    
		printf("a:%d \n", a);
	}
protected:
private:
	int a;
};
//单独搭建一个舞台
void ObjPlay01()
{
    
    
	AA a1; //变量定义
	//赋值构造函数的第一个应用场景
	//用对象1 初始化 对象2
	AA a2= a1;	//定义变量并初始化,AA(const AA &obj2)
	a2 = a1;	//用a1来=号给a2 编译器给我们提供的浅copy
}

//注意:初始化操作 和 等号操作 是两个不同的概念
结果:
	我是构造函数,自动被调用了
	我也是构造函数,我是通过另外一个对象obj2,来初始化我自己
	我是析构函数,自动被调用了
	我是析构函数,自动被调用了
	请按任意键继续. . .

第二个应用场景
//单独搭建一个舞台
void ObjPlay02()
{
    
    
	AA a1(10); //变量定义
	//赋值构造函数的第一个应用场景
	//用对象1 初始化 对象2
	AA a2(a1); //定义变量并初始化 //括号法
	//a2 = a1; //用a1来=号给a2 编译器给我们提供的浅copy
	a2.getA();
}
结果:
	我也是构造函数,我是通过另外一个对象obj2,来初始化我自己
	a:20
	我是析构函数,自动被调用了
	我是析构函数,自动被调用了
	请按任意键继续. . .

3)第3和第4个调用场景

class Location
{
    
    
	public:
	Location( int xx = 0 , int yy = 0 )
	{
    
    
		X = xx ; Y = yy ; cout << "Constructor Object.\n" ;
	}
	Location( const Location & p ) //复制构造函数
	{
    
    
		X = p.X ; Y = p.Y ; cout << "Copy_constructor called." << endl ;
	}
	~Location()
	{
    
    
		cout << X << "," << Y << " Object destroyed." << endl ;
	}
	int GetX () {
    
     return X ; } int GetY () {
    
     return Y ; }
	private : int X , Y ;
} ;

void f ( Location p )
{
    
    
	cout << "Funtion:" << p.GetX() << "," << p.GetY() << endl ;
}
void mainobjplay()
{
    
    
	Location A ( 1, 2 ) ; //形参是一个元素,函数调用,会执行实参变量初始化形参变量
	f ( A ) ;    //第三种情况,会调用赋值构造函数
}
void main()
{
    
    
mainobjplay();
system("pause");
}

结果:
	Constructor Object.
	Copy_constructor called.
	Funtion:1,2
	1,2 Object destroyed.
	1,2 Object destroyed.
	请按任意键继续. . .


第四个应用场景
Location g()
{
    
    
	Location A(1, 2);
	return A;
}

//对象初始化操作 和 =等号操作 是两个不同的概念
//匿名对象的去和留,关键看,返回时如何接
void mainobjplay2()
{
    
    
//若返回的匿名对象,赋值给另外一个同类型的对象,那么匿名对象会被析构
//Location B;
//B = g(); //用匿名对象 赋值 给B对象,然后匿名对象析构
//若返回的匿名对象,来初始化另外一个同类型的对象,那么匿名对象会直接转成新的对象
Location B = g();
cout<<"测试"<<endl;
}

结果:
	构造函数调用
	复制构造函数
	1,2 析构函数调用
	测试
	1,2 析构函数调用
	请按任意键继续. . .

在这里插入图片描述

4.4.4 默认构造函数
1)默认无参构造函数:当类中没有定义构造函数时,编译器默认提供一个无参构造函数,并且其函数体为空
2)默认拷贝构造函数:当类中没有定义拷贝构造函数时,编译器默认提供一个拷贝构造函数,简单进行成员变量值复制

4.5 小节构造函数调用规则
总结:只要你写了构造函数,那么你必须用
1)当类中没有定义任何一个构造函数时,c++编译器会提供默认无参构造函数和默认拷贝构造函数
2)当类中定义了拷贝构造函数时,c++编译器不会提供无参数构造函数
3) 当类中定义了任意的非拷贝构造函数(即:当类中提供了有参构造函数或无参构造函数),c++编译器不会提供默认无参构造函数
4 )默认拷贝构造函数成员变量简单赋值

构造析构阶段性总结
1)构造函数是C++中用于初始化对象状态的特殊函数
2)构造函数在对象创建时自动被调用
3)构造函数和普通成员函数都遵循重载规则
4)拷贝构造函数是对象正确初始化的重要保证
5)必要的时候,必须手工编写拷贝构造函数

5.多个对象构造和析构

5.1 对象初始化列表
1)如果我们有一个类成员,它本身是一个类或者是一个结构,而且这个成员它只有一个带参数的构造函数,没有默认构造函数。这时要对这个类成员进行初始化,就必须调用这个类成员的带参数的构造函数,如果没有初始化列表,那么他将无法完成第一步,就会报错
2)类成员中若有const修饰,必须在对象初始化的时候,给const int m 赋值当类成员中含有一个const对象时,或者是一个引用时,他们也必须要通过成员初始化列表进行初始化,因为这两种对象要在声明后马上初始化,而在构造函数中,做的是对他们的赋值,这样是不被允许的

Constructor::Contructor() : m1(v1), m2(v1,v2), m3(v3)
{
    
    
// some other assignment operation
}

3)C++中提供初始化列表对成员变量进行初始化【初始化:被初始化的对象正在创建;赋值:被赋值的对象已经存在
4)成员变量的初始化顺序与声明的顺序相关,与在初始化列表中的顺序无关,初始化列表先于构造函数的函数体执行

#include "iostream"
using namespace std;
class ABC
{
    
    
public:
ABC(int a, int b, int c)
{
    
    
this->a = a;
this->b = b;
this->c = c;
printf("a:%d,b:%d,c:%d \n", a, b, c);
printf("ABC construct ..\n");
}
~ABC()
{
    
    
printf("a:%d,b:%d,c:%d \n", a, b, c);
printf("~ABC() ..\n");
}
protected:
private:
int a;
int b;
int c;
};
class MyD
{
    
    
public:
MyD():abc1(1,2,3),abc2(4,5,6),m(100)
//MyD()
{
    
    
cout<<"MyD()"<<endl;
}
~MyD()
{
    
    
cout<<"~MyD()"<<endl;
}
protected:
private:
ABC abc1; //c++编译器不知道如何构造abc1
ABC abc2;
const int m;
};
int run()
{
    
    
MyD myD;
return 0;
}
int main_dem03()
{
    
    
run();
system("pause");
return 0;
}

5.2 构造函数和析构函数的调用顺序研究

1)当类中有成员变量是其它类的对象时,首先调用成员变量的构造函数,调用顺序与声明顺序相同;后调用自身类构造函数
2)析构函数的调用顺序与对应的构造函数调用顺序相反

5.3 匿名对象
1) 匿名对象生命周期
2) 匿名对象的去和留
3)构造中调用构造,构造函数中调用构造函数,是一个蹩脚的行为

6 对象的动态建立和释放

6.1 new和delete基本语法
1)在C语言中是利用库函数malloc和free来分配和撤销内存空间的。C++提供了较简便而功能较强的运算符new和delete来取代malloc和free函数 注意: new和delete是运算符,不是函数,因此执行效率高。
2)虽然为了与C语言兼容,C++仍保留malloc和free函数,但建议用户不用malloc和free函数,而用new和delete运算符
3)new int; 开辟一个存放整数的存储空间,返回一个指向该存储空间的地址(即指针)
4)new int(100); 开辟一个存放整数的空间,并指定该整数的初值为100,返回一个指向该存储空间的地址
5)new char[10] 开辟一个存放字符数组(包括10个元素)的空间,返回首元素的地址
6)new int[5][4] ;开辟一个存放二维整型数组(大小为5*4)的空间,返回首元素的地址
7)float *p=new float (3.14159); 开辟一个存放单精度数的空间,并指定该实数的初值为

6.2 new和delete运算符使用的一般格式为
在这里插入图片描述
用new分配数组空间时不能指定初值。如果由于内存不足等原因而无法正常分配空间,则new会返回一个空指针NULL,用户可以根据该指针的值判断分配空间是否成功

6.3 类对象的动态建立和释放
1)使用类名定义的对象都是静态的,在程序运行过程中,对象所占的空间是不能随时释放的。但有时人们希望在需要用到对象时才建立对象,在不需要用该对象时就撤销它,释放它所占的内存空间以供别的数据使用。这样可提高内存空间的利用率
2)C++中,可以用new运算符动态建立对象,用delete运算符撤销对象
3)Box *pt; 定义一个指向Box类对象的
4)指针变量pt pt=new Box; 在pt中存放了新建对象的起始地址 在程序中就可以通过pt访问这个新建的对象。
5) C++还允许在执行new时,对新建立的对象进行初始化。如 *Box pt=new Box(12,15,18);
在执行new运算时,如果内存量不足,无法开辟所需的内存空间,目前大多数C++编译系统都使new返回一个0指针值。只要检测返回值是否为0,就可判断分配内存是否成功
6)delete pt; 释放pt指向的内存空间;这就撤销了pt指向的对象。此后程序不能再使用该对象。如果用一个指针变量pt先后指向不同的动态对象,应注意指针变量的当前指向,以免删错了对象。在执行delete运算符时,在释放内存空间之前,自动调用析构函数,完成有关善后清理工作。

猜你喜欢

转载自blog.csdn.net/qq_32643313/article/details/105471833