C++类和对象

C语言是面向过程语言,c++是面向对象语言。
面向对象:拆解出对象,依靠对象和对象之间的交互 ; 对象——>实体 ; 类:描述对象

类的定义方式:

1.声明定义全部放在类中
2.类中只做声明,定义放在类外,用 ClassName::function(){}定义(推荐这一种,类起到封装的作用,不必展示函数实现细节,只要提供接口函数)
C++中struct和class是等价的,不过struct默认访问权限为public(兼容C语言),类中默认权限为private

面向对象程序设计三大特性:封装 、 继承 、 多态
封装:将数据和操作数据的方法记性有机结合,隐藏对象属性和实现细节(private),仅对外公开接口来和对象进行交互(public)
访问权限有三种:public、private、protected
类也是一个作用域,c++中有四种作用域:全局作用域,函数体内部的局部作用域,命名空间,类域
类的实例化:用类类型定义对象的过程
在这里插入图片描述
对象中只存储成员变量,成员函数是共享的,那么就存在一个问题,没有给函数显示传递对象的地址或者其他信息,怎么知道要操作的对象

对象的大小:

空类的对象,默认1个字节来区分该空类创建的不同对象(主流编译器)
注意空类并不是什么都没有,编译器会给改了自动生成一些默认的成员方法(构造、析构、拷贝构造、赋值重载、取地址重载)

#include<iostream>
#include<string>
using namespace std;
class Washmachine
{
private:
	double _length;//8字节
	double _height;
	double _width;
public:
	Washmachine(){}
	
	double getLength()
	{
		return _length;
	}
	void setLength(double length)
	{
		_length = length;
	}
};
class A{};

int main()
{
	cout << sizeof(Washmachine) << endl;
	Washmachine w1;
	w1.setLength(1);//没有传递w1对象的地址,怎么操作的w1对象
	cout << sizeof(w1) << endl;//24字节,对象中并不存储函数,只存储成员变量,函数由
	/*函数编译完成后,存储函数的地址是确定的,编译器肯定是可以找到的
	通过符号表找到,符号表中存储的就是名字和地址的映射关系*/
	A a1,a2,a3;
	cout << sizeof(a1) << endl;//空类大小为1,要标记这个对象的位置,空间为0,会造成多个定义对象都在同一位置,无法区分
	return 0;
}

this指针

下面来讨论函数是如何获取要操作的对象信息(隐式传递的this指针)

#include<iostream>
using namespace std;
//在C++中, 类和结构是只有一个区别的:
//类的成员默认是private,而结构是public。
class Date
{
public:
	Date();
	Date(int year, int month, int day )
	{
		//Date *  & p = this;//类型不对
		//Date* const& p = this;//引用取别名
		auto p = this;
		cout << p << endl;
		this->year = year;
		this->month = month;
		this->day = day;
		cout << this << endl;
		cout << typeid(this).name() << endl;//this 类型:class Date *
		//this = nullptr;//不可修改说明该指针应为常量,即指针常量,不可修改指向
	}
	~Date();//析构函数,释放资源,不是释放对象空间
	void print();

/*成员函数默认第一个参数为 :T * const this(类指针常量)
this的效率相对于手动传递参数给静态函数效率会更高一些,编译器会优化,比如用到寄存器
*/


private:
	int year;
	int month;//成员变量名,和构造函数中的参数名可以用_区分,当然也可以不区分,不过在赋值时要用this标记是成员变量,而不是形参
	int day;
};

Date::Date()
{
}

Date::~Date()
{
}
void Date::print()
{
	cout << this->year << "年" << this->month << "月" << this->day << "日" << endl;
}

int main()
{
	Date d1;//调用空构造方法,不要带括号
	Date d2(2022, 11, 13);//调用构造方法,但构造方法只是初始化对象,不是开辟空间创建对象
	d1.print();
	d2.print();//c++编译器在执行时会传递这个调用对象的地址
	//this指向调用该方法的对象
	//哪个对象调用成员函数,就会在其调用函数时,隐式传递第一个参数为this指针,this关键字指向该对象的地址,this并不属于某个对象
	//无法通过对象来调用this,但是在成员函数中可以用this
	return 0;
}

打印不出来this实际的类型,但是通过引用发现this的类型:Class * const 类指针常量
在这里插入图片描述

六大默认成员函数:

1.构造函数:特殊的成员函数

函数名必须与类名相同,并且不能有返回值类型
在创建对象时有编译器调用, 在整个对象的生命周期内只调用一次
可以重载,因为有参数,下面的析构函数无法重载,因为没有参数
目的:给对象中的成员变量设置合适的初始值
在类中,如果用户没有显式定义任何构造函数,则编译器一定会生成一份无参的构造函数,如果显示定义了带参构造函数,若要Date d1;此时还要显示定义加上无参构造
无参的构造函数全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个,实际上全缺省默认构造函数包含了无参构造的功能

#include<iostream>
using namespace std;
class Time
{
public:
	Time();
private:
	int hour;
	int mintue;
	int second;
};
Time::Time()
{
	this->hour = 16;
	this->mintue = 30;
	this->second = 29;
	cout << "" << endl;
}

class Date
{
public:
	void print();
private:
	int _year;
	int _month;
	int _day;
	Time t1;//有了这个成员后,此时下面的构造方法就会被调用
};
void Date::print()
{
	cout << this->_year << "/" << this->_month << "/" << this->_day << endl;
}


int main()
{
	//再次强调:构造方法只是对成员函数进行初始化,不为对象开辟空间
	Date d;//直接跳过这一步了,不生成构造方法,因为没有初始化,虽然语法规定要生成,但编译器优化以后,发现不生成也能达到同样的效果(不同编译器实现不同)
	/*
	当成员变量有了Time ti对象时,对于Time有显示定义的构造方法,此时就要调用构造方法
	要对t1进行初始化,所以要调Time的构造方法,那么由谁调用呢,就要用Date构造方法调用(t1也是Date的成员要初始化,自然要调用)
	内置类型和自定义类型处理上的区别
	当然如果Time没有显示定义构造方法,那么就不用初始化t1,则d的成员变量都不用初始化,所以也就不必调用构造方法
	*/
	d.print();
	return 0;
}

2.析构函数:~类名();给构造函数名前加上一个 ~

如果对象中没有涉及到任何资源管理时,该类的析构函数可以不用给出
析构函数只是用来释放资源,不是销毁对象空间(对象在销毁时会自动调用析构函数,完成对象中资源的清理工作)
无法重载,只能有一个

3.拷贝构造函数:

用已创建的对象创建新对象时,将就对象做参数传递,调用拷贝构造函数
例:Date d2( d1);//d1已经创建了,可以看出拷贝构造函数是构造函数的重载函数,参数必须是类类型对象的引用(最好将参数设置为const,用const修饰防止对原对象进行修改)参数只有一个且必须是类类型对象的引用
若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成
拷贝–浅拷贝(值拷贝)
不可设置参数类型为 类类型
错误做法:
在这里插入图片描述

Date (const Date d){};这样会引发无穷递归,当然这样写出来时编译器就会报错,
这样的传值操作,要先构造临时对象,要调用拷贝构造,而这个构造的参数就又要创建临时对象,才能进行值拷贝…一直这样构造下去,无穷递归
当涉及到资源管理时,拷贝构造函数一定要显示定义,默认的是浅拷贝,会造成堆区的资源实际只会被申请一次,新对象的指针指向的是源对象申请的堆空间,在析构时就会造成内存多次释放,造成程序崩溃
对于拷贝构造函数,有时也会出现代码中调用它,但是在汇编中查看实际并没有生成,只是逐字节的值拷贝,完成了拷贝构造的工作
在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调
用其拷贝构造函数完成拷贝的,此时必然要调用Date类的拷贝构造,要用Date类的拷贝构造函数取调用Time类的拷贝构造函数
当要用对象传参时,最好用引用传参,这样就避免了隐式的拷贝构造,不必生成多余的对象,从而提高效率;函数返回值为匿名对象时,也能够提高效率,减少拷贝构造次数

4.赋值运算符重载

先看下面代码
在这里插入图片描述
d2 = d1;这样写难道不会报错吗?在之前的理解中只有像int,double等内置类型才能进行这样的赋值运算,像类结构体这一种自定义类型是不可以的。
d2 =d1;==》d2.operator(d1);
其实这里是一种赋值运算符重载
赋值运算符重载:用已经存在的对象 给 另一个已经存在的对象赋值(前提是二者都已存在)像这样Date d2=d1;这不是运算符重载,这是一种拷贝构造形式
语法:如果程序员没有显示定义赋值运算符重载,则编译器就会自动生成一份
实际情况:编译器不一定会生成,但是编译器一定会完成赋值的工作(上面的代码就是),这种默认但单纯值拷贝(将对象1中的内容原封不动的搬移到对象2),当遇到内存管理时,必然也会向默认的拷贝构造一样在析构时会出问题
赋值运算符只能重载成类的成员函数不能重载成全局函数
(因为在类外定义就要传递两个参数,但是当类中无定义时又会默认生成,这时其实有两个完全相同原型的函数,重载冲突)

运算符重载:

具有特殊函数名的函数,有返回值,函数名,参数列表
定义:返回值 operator 运算符 (){}

例:==在这里插入图片描述这个函数是在类内定义的,如果在类外定义,就无妨访问类内的private成员变量,改为public会破坏封装,可以用友元,friend关键字修饰函数这些运算符无法重载:.* :: sizeof ?: .

前++与后++的运算符重载

返回类型不同
形参不同
代码不同
效率不同

效率不同前++后++
返回类型不同Date&Date
形参不同int
代码不同加一直接返回给this+1,返回临时对象
效率不同低(多了拷贝构造和析构,虽然编译器会优化)
class Date
{
public:
	Date(){
		cout <<"构造定义:"<< this << endl;
	}
	Date(const Date& d)
	{
		cout <<"拷贝构造:"<< this << endl;
	}
	void print();
	//运算符重载函数
	bool operator==(const Date& d)//比较两个Date对象,只用传一个参数,另一参数隐式通过this传递
	{
		return d._day == this->_day &&
			   d._month == this->_month && 
			   d._day == this->_day;
	}
	//检测两个日期类型对象是否相等
	Date& operator=( const Date& d)//赋值运算符重载,只能重载成类的成员函数
	{
		this->_year = d._year;
		this->_month = d._month;
		this->_day = d._day;
		return *this;
	}
	Date operator++()//前加加
	{
		Date temp(*this);
		this->_day++;
		return temp;
	}
	Date& operator++(int)//后加加
	{
		this->_day++;
		return *this;
	}
private:
int _year;
	int _month;
	int _day;
};

void Date::print()
{
	cout << this->_year << "/" << this->_month << "/" << this->_day << endl;
}


int main(){
	Date d1;//构造定义:005DFA90
	d1.print();
	Date d2(d1);//拷贝构造:005DFA7C
	d2.print();
	Date d3;//构造定义:005DFA68
	d3.print();
	d3 = d2;//这一步是赋值

	if (d3 == d2)//二进制“==”: 没有找到接受“Date”类型的左操作数的运算符(或没有可接受的转换)
	{ cout << "d3和d2相等" << endl; }
	//bool operator==(const Date& d);//有了重载运算符定义后,就可以d3==d2这样用

	Date d4 = d2;//拷贝构造:005DFA54,这里=是运算符重载,拷贝构造函数 
	d4++.print();//后加加
	(++d4).print();
	d4.print();
		
}

const成员函数

在这里插入图片描述
思考:
普通对象可以调用const成员函数,const只能调用const成员函数(1,2)
非const成员函数可以调用其他const成员函数,const成员函数不可调用非const(3,4)
const调用非const,会不安全因素
在这里插入图片描述

5.6.取地址及const取地址操作符重载

class Date
{
public :
Date* operator&()
{
return this ;
}
const Date* operator&()const//返回值为this,因为函数被const修饰,实际修饰this,所以在Date*前要加上const
{
return this ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容

取地址运算符 & 重载的函数原型

Date* operator&();
const Date* operator&()const;

#include<iostream>
using namespace std;

class Date
{
public:
	Date(){};
	~Date(){};
	Date(int y, int m, int d);
	Date* operator&()//取地址&运算符重载
	{
		cout <<"Date* const"<< this << endl;
		return this;//this的类型 Date*const
	}

	const Date* operator&(int)const
	{
		//这是位与重载,因为取地址是单目运算符
	}

	const Date* operator&()const
	//与第一个重载函数,并不会重载冲突,
	//因为这个函数的隐式参数是const Date* const与Date* const不同,
	//参数不同,不会发生重载冲突
	{
		cout << "const Date* const"<<this << endl;
		return this;//this的类型const Date*const
	}

private:
	int year;
	int month;
	int day;
};
Date::Date(int y, int m, int d)
{
	year = y;
	month = m;
	day = d;
}

int main()
{
	Date d1(2022, 11, 19);
	//在对象取地址的同时需要将对象的地址打印出来
	Date* p = &d1;//有打印说明调用了运算符重载
	const Date d2;
	const Date* cp = &d2;
	return 0;
}

在这里插入图片描述

初始化列表:

构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。而有些成员必须要在定义时初始化,这时就要用初始化列表。
必须放在初始化列表位置进行初始化:
引用成员变量
const成员变量
自定义类型成员(且该类没有默认构造函数时)
成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关

class Time
{
public:
	Time();
	~Time();

private:
	int hour;
	int minutes;
	int second;
};

Time::Time()
:hour(11),
minutes(11),
second(11)

{
	cout <<"time:"<< hour << ":" << minutes << ":" << second << endl;
}

Time::~Time()
{
}
class Date
{
public:

	Date(int y = 1900, int m = 1, int d = 1)
		:year(y),
		month(m),
		day(d),
		//用初识化列表,初识化顺序,为类中声明时的顺序
		_a(1),
		_rday(day),
		t1()//对t1的初始化采用t1的构造方法
	{
		//构造方法并不是对象的初始化,初始化只能一次赋值,构造方法中可以多次赋值
		cout <<"date:"<< year << "年" << month << "月" << day << "日" << endl;
	}

	~Date()
	{
		cout << "~Date" << this << endl;
	}

private:
	int year;
	int month;
	int day;

	//定义时需要初始化,这时就需要用到初始化列表
	/*
	必须放在初始化列表位置进行初始化:
	引用成员变量
	const成员变量
	自定义类型成员(且该类没有默认构造函数时)
		*/
	const int _a;
	int& _rday;
	Time t1;
};

/*
使用初始化列表初始化,无论是否使用初始化列表,
对于自定义类型成员变量,一定会先使用初始化列表进行初始化

1.初始化列表可以不写,不写不代表编译器不执行
2.如果不写,对于内置类型的成员变量编译器用随机值初始化
对于自定义类型的成员变量,编译器调用无参或者全缺省的构造方法进行初始化
如果累没有无参或者全缺省的构造方法,则报错
*/

int main()
{
	Date d1(1,2,3);
}

explicit关键字

用explicit修饰构造函数,将会禁止构造函数的隐式转换

#include<iostream>
using namespace std;

class Date
{
public:
	Date();
	~Date();
	//explicit:限制构造函数不可进行隐式类型转换
	Date(int y = 1900, int m = 1, int d = 1)
	{
		year = y;
		month = m;
		day = d;
		count++;
		cout << "Date::count=" << count << endl;
		cout << "Date(int,int,int)" << this << endl;
	}
	static int getCount()//无this指针,this只能用于非静态成员函数
	{
		return count;
	}

private:
	int year;
	int month;
	int day;

	static int count;//类中声明,静态成员,不在类外定义会报链接错误,说明没有定义
	//没有存储在具体的对象中,但可以通过对象访问
};
int Date::count = 0;//类外定义
Date::Date()
{
}

Date::~Date()
{
	cout << "~Date:" << this << endl;
}

int main()
{
	Date d1(2022);
	d1 = 2023;//调用单参构造方法生成一个匿名对象,然后进行赋值运算符重载
	//这样运行是可以的,但是可读性不高,如不了解内部执行逻辑,会有很大的理解偏差,可以用explicit
	cout <<Date::getCount() << endl;
	return 0;
}

static成员:

用static修饰的成员变量称为静态成员变量,修饰成员函数,称为静态成员函数。
静态成员变量要在类外进行初始化
特性:静态成员为所有类对象共享,不属于某个具体对象,放在静态区,
在类外定义(定义是不加static),类中只是声明
访问 类名::静态成员 对象.静态成员
静态成员函数,没有隐藏的this指针,不能访问呢非静态成员
静态成员受访问限定符的限制

友元:

友元提供了一种突破封装的方式,有时提供了方便,但会增加耦合,破坏封装,不宜多用(封装、继承、多态面向对象三大特性)
友元函数:
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字
友元不受访问限定符的限制,不能加const修饰,不是类的成员函数,但可以访问类的成员变量
友元类:
单向的朋友
在这里插入图片描述
Date是Time的友元可以访问其内容,但是Time不能访问Date
不能传递(朋友的朋友不是朋友)
无法继承

内部类:

如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中
的所有成员。但是外部类不是内部类的友元。
特性:

  1. 内部类可以定义在外部类的public、protected、private都是可以的。
  2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
  3. sizeof(外部类)=外部类,和内部类没有任何关系。

总结:

类是对某一类实体(对象)来进行描述的,描述该对象具有那些属性,那些方法,描述完成后就形成了一种新的自定义类型,才用该自定义类型就可以实例化具体的对象

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值