C++入门(类和对象一篇通)

类和对象

1、类的构建

C++兼容C struct的用法
C++同时对struct进行了升级,把struct 升级成了类

  • 结构体名称可以做类型
  • 里面可以定义函数
  • 类有两种定义方式,一种是struct定义类,另一种是class定义类,两者的区别是,class定义的类是私有的不能在类外面访问,struct定义的类是公有的可以在类外面访问
  • C++还新加入了三种访问限定符号:1、public(公有限定符号),2、private(私有限定符号),3、protected(保护限定符号),如果是struct定义的类,默认是public,如果是class定义的类,默认是private
  • 访问限定符的作用范围是从此访问限定符开始,到下一个访问限定符结束
// struct 不加访问限定符,默认是public
// class 不加访问限定符,默认是private
class Student
{
public://公有限定符号
   // 类体:由成员函数和成员变量组成
   void Init(const char* name, const char* gender, int age)
   {
	   strcpy(_name, name);
	   strcpy(_gender, gender);
	   _age = age;
   }

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


	// 这里并不是必须加_
	// 习惯加这个,用来标识成员变量
private://私有限定符号
	char _name[20];
	char _gender[3];
protected://保护限定符号
	int _age;
};

2、封装

  • 封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
// 封装:更严格管理设计
// 1、数据和方法封装到一起,类里面
// 2、想给你自由访问的设计成共有,不想给你直接访问的设计成私有
// 一般情况设计类,成员数据都是私有或者保护
//想给访问的函数是共有,不想给你访问时私有或保护
class Stack
{
private://私有限定符
	void Checkcapaicty()
	{}
public://公有限定符
	void Init()//用户可以调用的函数接口
	{}

	void Push(int x)
	{}

	int Top()
	{}

private://私有限定符保护对象
	int* _a;
	int _top;
    int _capacity;
};

3、类的作用域

  • 类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员,需要使用 : : 作用域解析符指明成员属于哪个类域。
//两个类域
//class Stack和class Queue中的push函数是可以同时存在的
//他们不构成函数重载的原因是,函数重载在同一个作用域中才会发生
//此时两个push函数是在Stack和Queue两个不同的类的类域中
class Stack
{
public:
	void Push(int x)
	{}
};

class Queue
{
public:
	void Push(int x)
	{}
};

4、类的两种定义方式

1、在类中定义函数,这样定义的函数自动被视为内联函数

class Stack
{
public:
	// 在类里面定义
	// 在类里面定义的函数默认是inline
	void Init()
	{
		_a = nullptr;
		_top = 0;
		_capacity = 0;
	}

	// 在类里面声明
	void Push(int x);
	void Pop();
	// 总结一下:实际中,一般情况下,短小函数可以直接在类里面定义
	//长一点函数声明和定义分离
	
//private:
//这个地方是变量的声明,判断变量声明还是定义,就看他有没有开辟空间
	int* _a;
	int _top;
	int _capacity;
};

2、在类中声明函数,在其他地方实现这种方式需要使用 : : 作用域解析符

void Stack::Push(int x)//作用域解析符
//把push从Stack的类域中放出来
{
	// ...
	_top++;
}

5、类的大小计算

  • 一个类的大小,实际就是该类中”成员变量”之和,当然也要进行内存对齐,注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类。
// 类中既有成员变量,又有成员函数
//A1类的大小为8个字节,int类型成员变量和char类型成员变量内存对齐
class A1 {
public:
	void f1(){}
private:
	int _a;
	char _ch;
};

// 总结:没有成员变量的类对象,编译会给他们分配1byte占位
//表示对象存在过 

// 类中仅有成员函数(类的大小为1个字节)
class A2 {
public:
	void f2() {}
};

// 类中什么都没有---空类(类的大小为1个字节)
class A3
{
	//char _ch;
};

6、隐含的this指针

  • 在类中函数的形参位置,C++设置了一个隐含的this指针,我们不能将它用显式的方法编写出来,这是在形参位置利用隐含的this指针是编译器的工作
  • this指针可以为空(不能用空指针解引用成员变量)
  • this指针是存在上的(函数栈帧),有些编译器会将this放到寄存器里面进行优化
class Date
{
public:
	/*void Print(Date* const this)//这是编译器编译后修改的版本
	//在形参位置设置了一个隐含的this指针
	{
		cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
	}*/

	void Print()
	{
		cout << this << endl;
		cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
	}
    这是编译器编译后修改的版本
	/*void Init(Date* const this, int year, int month, int day)
	{
	this->_year = year;
	this->_month = month;
	this->_day = day;
	}*/

	void Init(int year, int month, int day)
	{
	//this = nullptr; this指针本身不能修改,因为他是const修饰的
	// this指向对象可以被修改
		cout << this << endl;

		this->_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};

7、类的6个默认成员函数(重点)

  • 学习默认成员函数的几个要点
  • 基本语法特性、函数名、参数、返回值、什么时候调用
  • 我们不写编译器默认生成这个函数干了什么事情

7.1、构造函数

  • 构造函数特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象
  • 构造函数的特点:1. 函数名与类名相同。2. 无返回值。3. 对象实例化时编译器自动调用对应的构造函数。4. 构造函数可以函数重载。
class Date
{
public:
	Date()//构造函数无参数版本(如果我们不写,编译器会自动生成)
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}

	Date(int year, int month, int day)//构造函数有参数版本
	{
		_year = year;
		_month = month;
		_day = day;
	}

	Date(int year = 1, int month = 1, int day = 1)//构造函数的全缺省写法
	{
		_year = year;
		_month = month;
		_day = day;
	}
	
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};

int main()
{
	//Date d1(); // 不能这么写
	Date d1;//构造函数的调用
	d1.Print();

	Date d2(2022, 5, 15);//可以全传参数调用
	d2.Print();
	
	Date d3(2022);//也可以只传一两个参数缺省调用
	d3.Print();

	Date d4(2022, 10);//这也是缺省调用
	//构造函数的名字和类名相同
	d4.Print();

	return 0;
}
  • 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。(意思是如果我们不写构造函数,编译器会默认生成一个无参构造函数)
    - 重点:C++将类型分为两种
    1、内置类型/基本类型:int/char/double/指针…
    2、自定义类型:class/struct去定义类型对象
    (默认生成构造函数对于内置类型成员变量不做处理,对于自定义类型成员变量才会处理,调用这个自定义类型成员的默认构造函数)
    3、总结:如果一个类中的成员变量全是自定义类型,并且这些自定义类型成员都提供了默认构造函数,我们就可以不用写构造函数,让编译器默认生成的无参构造函数。如果还有内置类型的成员,需要在声明时给缺省值(所以大多数情况下我们都自己写构造函数,不用默认生成的)

  • 在C++定义中,无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。(总而言之就是不用传参就能调用的构造函数,都被定义为默认构造函数)

7.2、析构函数

  • 析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的资源清理工作(例如malloc申请的空间)
  • 析构函数的特点:1. 析构函数名是在类名前加上字符 ~。2. 无参数无返回值。3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
class Stack
{
public:
	// Init(构造函数类似于我们之前写的初始化函数)
	// Destroy(析构函数类似于之前学习的销毁函数)

	Stack(int capacity = 10)//全缺省构造函数(默认构造函数的一种)
	{
		_a = (int*)malloc(sizeof(int)*capacity);
		assert(_a);

		_top = 0;
		_capacity = capacity;
	}

	~Stack()//默认析构函数
	{
		cout << "~Stack():" << this << endl;

		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
private:
	int* _a;
	int _top;
	int _capacity;
	};

7.3、拷贝构造函数

  • 拷贝构造函数也是特殊的成员函数,其特征如下:1. 拷贝构造函数是构造函数的一个重载形式。2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
  • 同类型对象传值传参需要调用拷贝构造
  • 自定义类型的初始化都要调用拷贝构造
// 我们不写编译器会默认生成一个拷贝构造
// 1、内置类型的成员会完成值拷贝,浅拷贝。
//浅拷贝的问题:如果成员变量有开辟空间(例如数据结构栈)
//那么浅拷贝会导致拷贝前后的两个变量指向同一块空间
//(修改数据会互相影响,而且同一块空间在析构时会析构两次,程序会崩溃)

// 2、自定义类型的成员,去调用这个成员的拷贝构造
//结论:一般的类,自己生成的拷贝构造就够用了
//只有像栈这样的类,自己直接管理资源,自己写一个拷贝构造(需要实现深拷贝)
Date(const Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}

7.4、运算符重载

  • 内置类型,可以直接用各种运算符。自定义类型,不能直接用各种运算符,为了自定义类型可以使用各种运算符,C++设立了运算符重载的规则
  • 注意:
  • 不能通过连接其他符号来创建新的操作符:比如operator@
  • 重载操作符必须有一个类类型或者枚举类型的操作数
  • 用于内置类型的操作符,其含义不能改变,例如:内置的整型“+”,不能实现成 “-”。
  • 作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的操作符有一个默认的形参this,限定为第一个形参
  • .* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
  • 有意义的运算符才会重载
//运算符重载 -- 函数
//函数名:operator 运算符
//参数:运算符的操作数
//返回值:运算符运算后结果

class Date
{
public:
// 默认生成的析构函数,内置类型成员不做处理,自定义类型成员会去调用它的析构函数
Date(int year = 1, int month = 1, int day = 1)
{
	_year = year;
	_month = month;
	_day = day;
}

void Print()
{
	cout << _year << "-" << _month << "-" << _day << endl;
}

//运算符重载函数一般都直接写在类里
bool operator==(const Date& d)//运算符重载函数
// bool operator==(Data* const this,Data d)
// “==”号是双目操作符却只有一个参数的原因就是类里面会自动处理生成一个this指针
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}

// 2022 5 16
// 2021 10 16
bool operator<(const Date& d)//运算符重载函数
//利用引用操作符,就可以不调用拷贝构造
//用const修饰引用,让引用不改变(防止出现d._year == year 的错误,这样写会报错)
{
	if ((_year < d._year)
		|| (_year == d._year && _month < d._month)
		|| (_year == d._year && _month == d._month && d._day < d._day))
    {
	    return true;
    }
	else
	{
		return false;
	}
}

private:
    int _year;
	int _month;
	int _day;
};

//运算符重载函数调用方法
if (d1.operator==(d2))
{
	cout << "==" << endl;
}

if (d1 == d2) // 编译器会处理成对应重载运算符调用 if (operator==(d1, d2))
{
	cout << "==" << endl;
}

7.5、赋值重载

  • 参数类型
  • 返回值
  • 检测是否自己给自己赋值
  • 返回*this
  • 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝(浅拷贝)。
class Date
{
public:
    //d2 = d1; -> d2.operator=(&d2, d1)
	// d1 = d1
	Date& operator=(const Date& d)//这里使用传引用返回,如果传值返回还需要拷贝
	//自定义类型拷贝需要调用拷贝构造,代价较大,所以使用传引用返回
	{
		if (this != &d)//判断是不是自己给自己赋值
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}

		return *this;//解引用this指针
    }
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2022, 5, 16);
	Date d2(2022, 5, 18);

	Date d3(d1); // 拷贝构造  -- 一个存在的对象去初始化另一个要创建的对象
	d3 = d2 = d1;     // 赋值重载/复制拷贝 -- 两个已经存在对象之间赋值
	(d3 = d2) = d1;     // 赋值重载/复制拷贝 -- 两个已经存在对象之间赋值

	d1 = d1;//自己给自己赋值的例子

	int i = 0, j, k;
	k = j = i;//赋值操作符是有返回值的
	//(k = j) = i;

	return 0;
}

7.6、取地址运算符重载

Date* operator&()
{
    //return this;返回真实地址
    return nullptr;//返回虚假地址,不想让别人获取真实地址就这么写
}

const修饰之后

//获取const Date*地址
const Date* operator&() const
{
    //return this;返回真实地址
    return nullptr;//返回虚假地址
}

8、总结

//用日期类型做个总结
class Date
{
public:
	int GetMonthDay(int year, int month)//判断某年某月有多少天的函数
	{
		int monthDayArray[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
		if (month == 2 && isLeapYear(year))
		{
			return 29;
		}
		else
		{
			return monthDayArray[month];
		}
	}

	// 默认生成的析构函数,内置类型成员不做处理,自定义类型成员会去调用它的析构函数
	Date(int year = 1, int month = 1, int day = 1)
	{
		if (year >= 1 && 
			month <= 12 && month >= 1 && 
			day >= 1 && day <= GetMonthDay(year, month))
		{
			_year = year;
			_month = month;
			_day = day;
		}
		else
		{
			cout << "日期非法" << endl;
		}
	}

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

	// d1 + 100; -> d1.operator+(int day)
	// d1 - 100;
	// d1 - d2;
	Date operator+(int day);
	Date operator-(int day);
	int operator-(const Date& d);

	//bool operator>(const Date& d)
	//bool operator>=(const Date& d)
	//bool operator==(const Date& d)
	//bool operator!=(const Date& d)
	//bool operator<=(const Date& d)
	bool operator<(const Date& d)
	{
		if ((_year < d._year)
			|| (_year == d._year && _month < d._month)
			|| (_year == d._year && _month == d._month && d._day < d._day))
		{
			return true;
		}
		else
		{
			return false;
		}
	}

private:
	int _year;
	int _month;
	int _day;
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值