c++:类和对象(上)

面向对象

🥑c语言是面向过程的,关注的是过程
🥑c++是基于面向对象的,关注的是对象

🎨面向对象三大特性:封装,继承,多态

封装:🎈1.数据和方法在类里面都放在了一起
🎈 2.访问限定符:public(公有) protected(保护) private(私有)
🔰公有可以在类外面直接访问;保护和私有不可以直接访问

🎈封装是一种更好的严格管理,不封装是一种自由管理

类和对象

1.struct

在c语言中学会了如何创建和使用一个结构体

struct student
{
	//成员变量
	char _name[20];
	int _age;
};

但是c++中,升级到类,student是类名,也是类型

struct 的默认访问限定符是公有public

//c++类跟结构体不同的是除了可以定义变量,还可以定义方法和函数
struct student
{
	//成员变量
	char _name[20];
	int _age;

	//成员方法
	void Init(const char* name, int age)
	{
		strcpy(_name, name);
		_age = age;
	}

	void Print()
	{
		cout << _name << endl;
		cout << _age << endl;
	}


};

int main()
{
	struct student s1;//兼容c,这里struct student才是类型
	student s2;      //升级到类,student是类名,也是类型

	s1.Init("张三", 13);
	s1.Print();
	return 0;
}

2.class

把struct换成class,暂时class类就定义出来了

🦇class的默认访问限定符是私有
🦇因此我们可以加访问限定符,限定范围的是该限定符的位置到下一个限定符,如果后面没有限定符,默认到类的结束

class student
{
private:
	//成员变量
	char _name[20];
	int _age;

public://限定范围的是该限定符的位置到下一个限定符
	//成员方法
	void Init(const char* name, int age)
	{
		strcpy(_name, name);
		_age = age;
	}
//private:
	void Print()
	{
		cout << _name << endl;
		cout << _age << endl;
	}
};

类对象的大小

🐞对象中存了成员变量,没存成员函数
因为每个对象都有独立的成员变量
不同对象调用成员函数,调的是同一个

🐞只保存成员变量,成员函数放在公共的代码段
在这里插入图片描述
在这里插入图片描述
结论:
计算类或者类对象大小,只看成员变量,考虑内存对齐,c++内存对齐规则跟c语言结构体一致
空类会给一个byte,这1byte不存储有效数据,只是为了和其他类区分

this指针

首先创建一个日期类

class Date
{
private:
	int _year;
	int _month;
	int _day;

public:
	void Init(int year,int month,int day)
	{
		_year = year;
		//即使成员变量跟形参同名,根据就近原则,还是会优先使用形参,但结果不是我们传的2022
		//想同名,也可以使用Date :: year,指定作用域
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year<<"-" << _month << "-" << _day << endl;
	}
};

int main()
{
	Date s1;
	s1.Init(2022, 12, 10);
	s1.Print();

	Date s2;
	s2.Init(1998, 2, 12);
	s2.Print();

	return 0;
}

🔰注意:在c++中,s2.Print();表面上没有传任何参数,实际上它隐藏一个参数this 指针
在这个类里,this指针的类型是Date*

在这里插入图片描述
💙 “//”意思是都是被编译器处理过后的

    //void Init(Date* this,int year,int month,int day)
	//void Print(Data* this)
	void Print()
	{
		cout << _year<<"-" << _month << "-" << _day << endl;
		//只有在成员变量之前加this->
		//cout << this->_year << "-" <<this-> _month << "-" <<this-> _day << endl;
	}
	
d1.Print();//会被编译器处理成d1.Print(&d1)
1. 调用成员函数时,不能显示传实参给this; 2.定义成员函数时,不能显示声明形参this;3.但是在成员函数内部 ,我们可以显示使用this

所以这么编写是错的void Print(Data* this)d1.Print(&d1)
而下面这段在成员函数内部使用就是对的

void Init(int year,int month,int day)
	{
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}

this指针本身不能被改变(Date* const this)
const 在Date*之前表示指向的内容不能被改变 在Date* 之后表示指针本身不能被改变

this存在哪?一般情况下是在栈(形参),有些编译器会放在寄存器中。

1.重要例题

// 1.下面程序能编译通过吗?
// 2.下面程序会崩溃吗?在哪里崩溃
class A
{
public:
void Show()//虽然传过来的是空指针,但并没有进行解引用操作
{
  cout<<"Show()"<<endl;//这里没有错误,正常运行
}
void PrintA()
{
  cout<<_a<<endl;//这里会运行崩溃
  //这里编译器会这么处理  cout<<this->_a<<endl;
  //this又是空指针,解引用了就会报错
}
private:
  int _a;
};

int main()
{
  A* p = nullptr;
  p->Show();//这里没有错误,正常运行
  p->PrintA();//这里就会运行崩溃
}

分析:
1.p虽然是空指针,但是p调用成员函数不会编译报错,因为空指针不是语法错误,编译器检查不出来

2.p虽然是空指针,但是p调用成员函数不会出现空指针访问。因为成员函数没有存在对象里

3.这里会把p作为实参传递给隐藏的this指针,而指针不解引用是不会报错的

类的默认成员函数

在这里插入图片描述

1.构造函数

构造函数是特殊的成员函数,需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象。

其特征如下:

  1. 函数名与类名相同。
  2. 无返回值。
  3. 对象实例化时编译器自动调用对应的构造函数。
  4. 构造函数可以重载
public:
	//Date()//无参
	//构成函数的重载,但是它要是无参的调用,就会有二义性
	//{
	//	 _year=0;
	//	 _month=1;
	//	 _day=1;
	//}

	Date(int year = 2000, int month = 1, int day = 10)//全缺省
	{
		_year = year;
		_month = month;
		_day = day;
	}

  1. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成,但是它没有做初始化的工作
    在这里插入图片描述
    c++里把类型分为两类:内置类型,自定义类型
    内置类型:int / char / double /指针 /数组 等
    自定义类型:struct / class 定义的类型

🛎编译器默认的构造函数,对于内置类型不做初始化处理
🛎对于自定义类型会去调用他的无参默认构造函数(不用参数就可以调用)初始化,如果没有默认构造函数就会报错
任何一个类的默认构造函数就是–不用参数就可以调用(1.全缺省;2.无参;3.不写编译器默认生成的)

无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个
语法上他们可以同时存在,但是如果有对象定义去调用就会报错
   //定义一个对象必须是下面两种形式
	Date d1;//无参,也可以进入全缺省的构造函数
	Date d2(2000,10,22);//当构造函数是我们自己写的缺省参数
	//不可以Date d1();

在这里插入图片描述

2.析构函数

对象在销毁时会自动调用析构函数,完成对象的一些资源清理工作

像日期类,没有资源需要清理,所以Date不实现析构函数是可以的
像栈,队列等等动态开辟的则需要我们自己去写析构函数

如果我们不写,默认生成的析构函数和构造函数类似
对内置类型的成员变量不做处理,对自定义类型会去调用他的析构函数

析构函数是特殊的成员函数。
其特征如下:

  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值。
  3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

class Stack
{
public:
	Stack(int capacity=4)
	{
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == nullptr)
		{
			cout << "malloc fail" << endl;
			exit(-1);
		}
		_top = 0;
		_capacity = capacity;
	}
	~Stack()
	{
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
private:
	int _capacity;
	int _top;
	int* _a;
};

int main()
{
   Stack s1;
   return 0;
}

总结:析构函数完成对象中资源的清理,如果类对象需要资源清理才要自己实现析构函数
析构函数对象周期到了,以后自动调用,正确实现了析构函数,保证了类对象中的资源被清理

3.拷贝构造函数

函数名和类名相同,没有返回值,是构造函数的一个重载

  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。

传值传参形参是实参的一份拷贝,这个拷贝就会调用拷贝构造,就会套娃,而Date& 是别名
c语言就没有拷贝构造

c++里所有的传值传参都是拷贝构造

在这里插入图片描述

Date(const Date& d)//因为参数不用被修改,也能防止d2改变d1
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

如果我们没有定义拷贝构造函数,系统会生成默认的拷贝构造函数
1.内置类型成员,会完成按字节序的值 拷贝(浅拷贝)

比如:d1照样拷贝给d2

    Date d1(2000, 10, 11);
	Date d2(d1);
	

对于栈这种类就不能用默认的拷贝构造,需要自己去完成
在这里插入图片描述

2.自定义类型成员,会调用他的拷贝构造

拷贝构造我们不写,编译器生成的默认拷贝构造函数,对于内置类型和自定义类型都会拷贝处理,但是处理的细节不一样,这是跟构造和析构不一样的
在这里插入图片描述

例题

在这里插入图片描述

构造的顺序:C A B D
C是全局变量,C先初始化,AB是局部变量,D是静态局部变量,但A先定义,B再定义,D最后定义

析构的顺序:B A C D
在相同的生命周期下,先定义的后析构,生命周期短的先析构,由于A和B生命周期相同,C全局变量和D静态局部变量生命周期相同,A比B先定义,C比D先定义
所以调用析构函数的顺序是B A C D

在这里插入图片描述

class Widget
{
public:
	Widget()
	{
		//cout << "D()" << endl;
	}
	//拷贝构造,会优化
	Widget(const Widget& d)
	{
		cout << "Widget(const Widget& d)" << endl;
	}
	//只要不是构造就不会优化
	Widget& operator=(const Widget& d)
	{
		cout << "Widget& operator=(const Widget&)" << endl;
		return *this;
	}
};
Widget f(Widget u)//传值返回又会调用一次拷贝构造
{
	return u;
}

int main()
{
	//Widget x;//生命周期在当前函数
	//f(x);//传值传参会调用一次拷贝构造
	//Widget y = f(x);
	//拿一个值接收它,又会调用一次拷贝构造
	//一次调用里面,连续的构造函数,会被编译器优化,合二为一
	//优化之后就只会调用两次拷贝构造
	
	//Widget x;
	//Widget y;
	//赋值就不会优化
	//y = f(x);
	//匿名对象的生命周期只在这一行
	Widget();
	return 0;
}

在这里插入图片描述

4.赋值运算符重载

默认情况下c++是不支持自定义类型对象使用运算符

//函数名 operator操作符
//返回类型,看操作符运算后的返回值是什么
bool operator>(const Date& d1, const Date& d2)
//const Date& 的好处:既可以接收const Date&类型的对象,也可以接收非const类型的对象
//因为Date&类型的对象传给const Date& ,是权限的缩小

我们可以把它放在类里面,这样_year,_month,_day都能被访问了
👼因为在类当中,成员函数有隐藏一个参数this
👼所以我们要改成bool operator>( const Date& d2) //会被编译器处理成bool operator>(Date* const this,const Date& d)

所以最终在类里就是如下代码

class Date
{
public:
	//构造函数
	Date(int year = 1000, int month = 1, int day = 10)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	bool operator>(const Date& d)//会被编译器处理成bool operator>(Date* const this,const Date& d)
	{
		if (_year > d._year)
		{
			return true;
		}
		else if (_year == d._year && _month > d._month)
		{
			return true;
		}
		else if (_year == d._year && _month == d._month && _day > d._day)
		{
			return true;
		}
		else
		{
			return false;
		}
	}

private:
	int _year;
	int _month;
	int _day;

};

const 成员

    Date d1;
	d1.Print();
	//为什么d1能调用,d2不行呢?
	//因为权限放大了
	const Date d2;
	d2.Print();

在这里插入图片描述

所以c++增加了const 成员函数

//声明
void Print()const;
//定义
void Date::Print()const

在这里插入图片描述

双const表示,this本身不能被改,this指向的内容也不能被改
成员函数加const是好的,能加就加,这样普通对象和const对象都可以调用
但是如果要修改成员变量的成员函数是不能加的
比如日期类的++ – -=…

取地址运算符重载

A* operator&()//默认的构造函数,不需要自己写
	{
		return this;
	}
	const A* operator&()const
	{
		return this;
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值