类和对象(2)对象特性

c++小白有感于类和对象的复杂,学习b站黑马程序员的相关视频后,发文以总结。
对象的初始化和清理
构造函数和析构函数
构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
析构函数:主要作用于对象销毁前系统自动调用,执行一些清理工作。

构造函数语法:类名(){}
1、构造函数没有返回值也不用写void;
2、函数名称与类名相同;
3、构造函数可以有参数,因此可以发生重载;
4、程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次。

析构函数语法:~类名(){}
1、析构函数没有返回值也不用写void;
2、函数名称与类名相同,在名称前加~;
3、析构函数不可以与参数,因此不可以发生重载;
4、程序在对象销毁前会调用析构函数,无须手动调用,而且只会调用一次。

#include<iostream>
using namespace std;

class Person{
public:
	//构造函数
	//1、构造函数没有返回值也不用写void;
	//2、函数名称与类名相同;
	//3、构造函数可以有参数,因此可以发生重载;
	//4、程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次。
	Person(){
		cout<<"Person 构造函数的调用"<<endl;
	}
	//析构函数
	//1、析构函数没有返回值也不用写void;
	//2、函数名称与类名相同,在名称前加~;
	//3、析构函数不可以与参数,因此不可以发生重载;
	//4、程序在对象销毁前会调用析构函数,无须手动调用,而且只会调用一次。
	~Person(){
	cout<<"Person 析构函数的调用"<<endl;
	}
	//构造函数和析构都是必须有的实现,如果我们自己不提供,编译器会提供一个空实现的构造和析构。
};
void test01(){
	Person p;
}
int main(){
	test01();
	system("pause");
	return 0;
}

构造函数的分类及调用
两种分类方式:
按参数分为:有参构造和无参构造
按类型分为:普通构造和拷贝构造
三种调用方式
括号法、显示法、隐式转换法

#include<iostream>
using namespace std;

class Person{
public:
	int age;
	Person(){
	cout<<"Person的无参构造函数的调用"<<endl;
	}
	Person(int a){
	age=a;
	cout<<"Person的有参构造函数的调用"<<endl;
	}
	//拷贝构造函数
	Person(const Person &p){
		//拷贝传入的对象信息;
		age=p.age;
	}
};

void test01(){
	//括号法
	Person p1;
	Person p2(10);
	person p3(p2);//括号法调用拷贝构造
	//注意事项:调用默认构造函数(无参构造)时,不要加()。
	
	//显示法
	Person p4=Person(10);//有参构造
	Person p5=Person(p4);//拷贝构造
	//注意事项:不要利用拷贝构造初始化匿名对象
	Person(10);//匿名对象  当前行执行结束后,系统立即回收
	
	//隐式转换法
	Person p6=10;//相当于Person p6=Person(10)
	Person p7=p6;//拷贝构造
}

int main(){
	test01();
	system("pause");
	return 0;	
}

拷贝构造函数调用时机
三种调用拷贝函数的情况:
1、使用一个已经创建完毕的对象来初始化新对象(常用);
2、值传递方式给函数参数传值;
3、以值方式返回局部对象。

#include<iostream>
using namespace std;

class Person{
public:
	int m_Age;
	person(){
		cout<<"Person默认构造函数调用"<<endl;
	}
	
	person(int age){
		m_Age=age;
		cout<<"Person拷贝构造函数被调用"<<endl;
	}
	
  	Person(const Person & p){
  	m_Age=p.m_Age;
  	}

	~Person(){
		cout<<"Person析构函数调用"<<endl;
	}
};
//	1、使用一个已经创建完毕的对象来初始化新对象(常用)
void test01(){
	Person p1(20);
	person p2(p1);
	cout<<"p2的年龄为:"<<p2.m_Age<<endl;
}
//  2、值传递方式给函数参数传值;
void dowork(Person a){
}

void test02(){
	Person p;
	dowork(p);
}
//  3、以值方式返回局部对象。
Person Dowork(){
	Person p1;
	return p1; 
}

void test03(){
Person p=Dowork();
}

int main(){
	test01();
	test02();
	test03();
	system("pause");
	return 0;
}

构造函数调用规则
默认情况下,c++编译器至少给一个类添加3个函数
1、默认构造函数(无参,函数体为空);
2、默认析构函数 (无参,函数体为空);
3、默认拷贝函数,对属性进行值拷贝。

构造函数调用规则如下:
1、如果用户定义有参构造函数,c++不在默认无参构造,但是会提供默认拷贝构造;
2、如果用户定义拷贝构造函数,c++不会提供其他构造函数。

#incldue<iostream>
using namespace std;

class Person{
	//默认情况下,c++编译器至少给一个类添加3个函数
    //1、默认构造函数(无参,函数体为空);
    //2、默认析构函数   (无参,函数体为空);
    //3、默认拷贝函数,对属性进行值拷贝。
    
    //构造函数调用规则如下:
    //1、如果用户定义有参构造函数,c++不在默认无参构造,但是会提供默认拷贝构造;
    //2、如果用户定义拷贝构造函数,c++不会提供其他构造函数。
 public:
 	int m_Age;
	Person(){
		cout<<"Person无参构造函数调用"<<endl;
	}
	
	Person(int Age){
		m_Age=Age;
		cout<<"Person有参构造函数调用"<<endl;
	}
	
    ~Person(){
		cout<<"Person析构函数调用"<<endl;
	}
	
	//Person(const Person &p){
	//cout<<"Person拷贝构造函数调用"<<endl;
	//m_Age=p.m_Age;
	//}
};

void test01(){
	Person P1;
	p1.m_Age=18;
	Person P2(P1);
	cout<<"P2的年龄为:"<<P2.m_Age<<endl;
}

int main(){
	test01();
	system("pause");
	return 0;
}

深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作;
深拷贝:在堆区重新申请空间,进行拷贝操作。

#incldue<iostream>
using namespace std;

class Person{
 public:
 	int m_Age;
 	int *m_Height;
	Person(){
		cout<<"Person无参构造函数调用"<<endl;
	}
	
	Person(int Age,int Height){
		new int(height);
		m_Height=new int(height);
		m_Age=Age;
		cout<<"Person有参构造函数调用"<<endl;
	}
	
    ~Person(){
    	//析构代码,将堆区开辟的数据释放
    	if(m_Height !=NULL){
			delete m_Height;
			m_Height=NULL;
		}
		cout<<"Person析构函数调用"<<endl;
	}
	
	Person(const Person &p){
	cout<<"Person拷贝构造函数调用"<<endl;
	m_Age=p.m_Age;
	}
};

void test01(){
	Person P1(18,173);
	cout<<"P1的年龄为:"<<P1.m_Age<<endl;
	cout<<"P1的身高为:"<<*P1.m_Height<<endl;
	Person P2(P1);
	cout<<"P2的年龄为:"<<P2.m_Age<<endl;
	cout<<"P1的身高为:"<<*P2.m_Height<<endl;
}

int main(){
	test01();
	system("pause");
	return 0;
}

以上代码会报错,因为浅拷贝拷贝的代码被重复释放,即堆区代码被释放了两次。
以下为深拷贝解决此问题

#incldue<iostream>
using namespace std;

class Person{
 public:
 	int m_Age;
 	int *m_Height;
	Person(){
		cout<<"Person无参构造函数调用"<<endl;
	}
	
	Person(int Age,int Height){
		new int(height);
		m_Height=new int(height);
		m_Age=Age;
		cout<<"Person有参构造函数调用"<<endl;
	}

	//自己实现拷贝构造函数来解决浅拷贝带来的问题
	Person(const Person &p){
		cout<<"Person 拷贝构造函数调用"<<endl;
		//m_Height=p.m_Height    编译器默认实现此行代码即浅拷贝
		//深拷贝操作
		m_Height=new int(*p.m_Height);
	}
	
    ~Person(){
    	//析构代码,将堆区开辟的数据释放
    	if(m_Height !=NULL){
			delete m_Height;
			m_Height=NULL;
		}
		cout<<"Person析构函数调用"<<endl;
	}
	
	Person(const Person &p){
	cout<<"Person拷贝构造函数调用"<<endl;
	m_Age=p.m_Age;
	}
};

void test01(){
	Person P1(18,173);
	cout<<"P1的年龄为:"<<P1.m_Age<<endl;
	cout<<"P1的身高为:"<<*P1.m_Height<<endl;
	Person P2(P1);
	cout<<"P2的年龄为:"<<P2.m_Age<<endl;
	cout<<"P1的身高为:"<<*P2.m_Height<<endl;
}

int main(){
	test01();
	system("pause");
	return 0;
}

初始化列表
作用:
c++提供了初始化列表语法,用来初始化属性
语法:构造函数():属性1(值1),属性2(值2)等等{}

#incldue<iostream>
using namespace std;

class Person
{
public:
		int m_A;
		int m_B;
		int m_C;
		
		//Person(int a,int b,int c){
		//	m_A=a;
		//	m_B=b;
		//	m_c=c;
		//}传统方式
		
		Person(int a,int b,int c):m_A(a),m_B(b),m_C(c){
		
		}//采用初始化列表方式
};

void test01(){
	//Person p(10,20,30);
	Person p(30,20,10);
	cout<<"m_A=:"<<p.m_A<<endl;
	cout<<"m_B=:"<<p.m_B<<endl;
	cout<<"m_C=:"<<p.m_C<<endl;
}

int main(){
	test01();
	system("pause");
	return 0;
}

类对象作为类成员
c++类中的成员可以是另一个类的对象,我们称该成员为对象成员。

#include<iostream>
#include<string>
using namespace std;

class Phone{
public:
	string  m_PName;
	Phone(string PName){
		m_PName=PName;
	}
}

class Person{
public:
	string m_Name;
	Phone m_Phone;
	Person(string name,string PName):m_Name(name),m_Phone(PName){
	
	
	}	
};

void test01(){
	Phone P("张三","华为Nova7");
	cout<<p.m_Name<<"拿着"<<p.m_Phone.m_PName<<endl;
}

int main(){
	test01();
	system("pause");
	return 0;
}

静态成员
静态成员变量:
所有对象共享同一个数据;
在编译阶段分配内存;
类内声明,类外初始化。
静态成员函数:
所有对象共享一个函数;
静态成员函数只能访问静态成员变量。

#include<iostream>
using namespace std;

//静态成员函数
//所有对象共享一个函数;
//静态成员函数只能访问静态成员变量。

class PersonP{
public:
	//静态成员函数
	static void func(){
		m_A=100;
		//m_B=10 静态成员函数不可以访问非静态成员变量
		cout<<"静态成员函数被调用"<<endl;
	}
	static int m_A;//静态成员变量
	int m_B;
	//静态成员函数也是有访问权限的
private:
	static void fun2(){]
};

Person::m_A=0;

void test01(){
	//1、通过对象访问
	Person p;
	p.func();
	//p.fun2();
	//2、通过类名访问
	Person::func();
	//Person::fun2();
}

int main(){
	system("pause");
	return 0;
}

成员变量和成员函数分开存储
在c++中,类内的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上。

#incldue<iostream>
using namespace std;

//成员变量和成员函数分开存储

class Person{
	int m_A;//非静态成员变量,属于类对象上
	static int m_B;//静态成员变量,不属于类对象上
	//void func(){}//非静态成员函数,属于类对象上
	//static void fun(){}//静态成员函数,不属于类对象上
};

void test01(){
	Person p;
	//空对象占用内存空间为:1
	//c++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置。
	//每个空对象都有一个独一无二的内存地址
	cout<<"size of p"<<sizeof(p)<<endl;
}

void test02(){
	Person p;
	cout<<"size of p"<<sizeof(p)<<endl;
}

int Person::m_B=0;

int main(){
	test01();
	system("pause");
	return 0;
}

this指针
成员变量和成员函数是分开存储的,每一个非静态成员函数只会但是一份函数实例,也就是说多个同类型的对象会公用一块代码,那么这一块代码是如何区分哪个对象调用自己呢?
c++通过this指针解决上述问题,this指针指向被调用的成员函数所属的对象
this指针是隐含每一个非成员函数内的一种指针,不需要定义,直接使用。
this指针的用途:
当形参和成员变量同名时,用this指针来区分;
在类的非静态成员函数中返回对象本身。

#include<iostream>
using namespace std;

//1、解决名称冲突

//2、返回对象本身用*this

class Person{
public:
	//int m_Age;
	//Person(int age){
	//m_Age=age;
	//}
	int age;
	Person(int age){
	this->age=age;//当形参和成员变量同名时,用this指针来区分
	}
	Person& PersonAddAge(Person&p){
		this->age+=p.age;
		//this是指向p2.age的指针,那么*this指向本体。
		return *this;
	}
};

void test01(){
	Person p1(18);
	//cout<<"p1的年龄:"<<p1.m_Age<<endl;
	cout<<"p1的年龄:"<<p1.age<<endl;
}

void test02(){
	Person p2(10);
	Person p3(10);
	//p3.PersonAddAge(p2);
	//链式编程思想
	p3.PersonAddAge(p2).PersonAddAge(p2).PersonAddAge(p2);
	
	cout<<"p3的年龄为:"<<p3.age<<endl;
}

int main(){
	test01();
	test02();
	system("pause");
	return 0;
}

空指针访问成员函数
c++中空指针也可以调用成员函数,但要注意有没有用this指针
如果用到this指针,需要注意代码的健壮性

#include<iostream>
using namespace std;

class Person{
public:
	int m_Age;
	void showClassName(){
		cout<<"this is Person class"<<endl;
	}
	void showPersonAge(){
		if(this==NULL){
		return 1;
	}
		cout<<"age=:"<<m_Age<<endl;//报错原因是传入指针为NULL;
	}
};

void test01(){

	Person *p=NULL;
	p->showClassName();
	//p->showPersonAge();
	
}

int main(){
	test01();
	system("pause");
	return 0;
}

const修饰成员函数
常函数:
1、成员函数后加const后称为常函数
2、常函数内一般情况下不可修改成员属性
3、成员属性声明时加mutable后,在常函数中依然可以修改。
常对象:
1、声明对象前加const称常对象;
2、常对象只能调用常函数。

#include<iostream>
using namespace std;

class Person{
public:

	//this指针的本质是指针常量,指向不可以修改
	int m_A;
	mutable int m_B;//常函数中可以被修改的变量
	void showPerson()const{
		//此时成员属性不能修改
		//m_A=100;
		this->m_B=100;
	}
	void a(){};
};

void test01(){
	Person p;
	p.showPerson();
}

void test02(){
	const Person p;//常对象
	//p.m_A=100;
	p.m_B=100;
	
	//常对象只能调用常函数,普通对象即能调用常函数,也能调用非常函数
	p.showPerson();
	//p.a
	//常对象不可以调用普通成员函数,因为普通成员函数可以修改属性
}

int main(){
	test01();
	system("pause");
	return 0;
}

小总结:常函数和常变量主要用来保护对象属性

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值