类和对象c++

目录

一.类和对象

1.类的引入:

2.类的定义:

3.类的两种定义方式:

4.类的实例化:

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

封装

继承

多态

类大小的计算:

this 关键字:

三.6个默认成员函数

1.构造函数

2.析构函数

3.拷贝构造函数

4.赋值运算符重载

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

const成员

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

四.补充

构造函数补充——初始化列表

隐式转换:

 static成员:

友元函数,友元类:


c语言是面向过程,c++是面向对象。

什么是面向过程和面向对象?

面向过程:关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。

面向对象:关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完
成。


一.类和对象

1.类的引入:

C语言中,struct用来定义结构体,c++中,兼容c的struct定义结构体的用法,但是同时struct也可以用来定义类,也就是将数据和方法放到一起

2.类的定义:

class 类名{

//类体;

成员变量(类的属性);

成员函数(类的方法);

};// 一定要注意后面的分号

3.类的两种定义方式:

(1)声明和定义都在类里

内联函数

如:

class student {
	//成员变量
	string s_name;//学生名
	int s_age;//学生年龄
	//成员函数
	void study() {
		cout << "study" << endl;
	}
};

(2)声明在类里,定义在类外

例如: 

4.类的实例化:

什么叫实例化?

用类来生成对象

类名 对象名;

class student {
	string s_name;//学生名
}; 
int main() {
	student a;
}


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

封装

定义:将数据和操作

数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。比如:像电脑,电脑内部的零件重要且复杂,如果直接暴露在外面随意让人改动的话会产生危险,所有一般都会将内部结构封装起来。提供给用户的就只有开关机键、通过键盘输入,显示器,USB插孔等,让用户和计算机进行交互,完成日常事务。

封装的方式:

  1. 将数据和方法放到一起
  2. 想给看的给看,不想给的封装

如何实现想给看的给看,不想给的封装?答:这就需要引入访问限定符。

访问限定符:public (公有的),protected(受保护的), private(私有的)

访问限定符及决定类外如何访问类内的数据。

1. public修饰的成员在类外可以直接被访问
2. protected和private修饰的成员在类外不能直接被访问
3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
4. 如果后面没有访问限定符,作用域就到 } 即类结束。
5. class的默认访问权限为private,struct为public(因为struct要兼容C)

实例:

注意!!! 

类内可以访问其他对象的私有成员


继承


多态


类大小的计算:

先看例子:

#include<iostream>
#include<string>
using namespace std;
class student1 {
	char c;
	int age;
	void study() {
		cout << "study" << endl;
	}
};
class student2 {
	bool p;
	void study() {
		cout << "study" << endl;
	}
};
class student3 {
};
int main() {
	student1 a;
	student2 b;
	student3 c;
	cout << sizeof(a) << endl;
	cout << sizeof(b) << endl;
	cout << sizeof(c) << endl;
	cout << sizeof(student1) << endl;
	cout << sizeof(student2) << endl;
	cout << sizeof(student3) << endl;
}

结论:对象中只存储成员变量,不存储成员函数。一个类实例化出n个对象,每个对象的成员变量都可以存储不同的值但是调用的函数确是同一个。所以计算类的大小即为计算成员变量之和并考虑内存对齐规则,对齐规则是最大对齐数的整数倍。对于student2和student3没有成员变量的函数为什么是1,开一个字节不是为了存数据而是占位。


this 关键字:

函数体中没有关于不同对象的区分,那当a调用 getAge 函数时,该函数是如何知道应该设置a对象,而不是设置b对象呢?

特性:

1. this指针的类型:类类型* const,即成员函数中,不能给this指针赋值
2. 只能在“成员函数”的内部使用
3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给
this形参
。所以对象中不存储this指针。
4. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传
递,不需要用户传递


三.6个默认成员函数

默认成员函数是如果不写编译器会自动生成的函数

1.构造函数

在对象构造时调用的函数,这个函数完成初始化工作

类名(参数){};

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

问:编译器生成的默认构造函数有什么用??                                                                         

答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型,针对内置类型的成员变量没有处理,针对自定义类型的成员变量,调用它的构造函数初始化。

特征:
(1)函数名与类名相同。

(2)无返回值。

(3)对象实例化时编译器自动调用对应的构造函数。

(4)构造函数可以重载。

class student{
public:
	student() {
		name = "无";
		age = 0;
		cout << "无参的" << endl;
	};//无参的
	student(string name, int age) {
		this->name = name;
		this->age = age;
		cout << "带参的" << endl;
	};//带参的
private:
	string name;
	int age;
};
int main() {
	student a;
	student b("张三",20);
}

(5)如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦
用户显式定义编译器将不再生成。                                                                                                 

(6)无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。

最好的构造方式:全缺省

class student{
public:
	student(string name = "", int age = 0) {
		this->name = name;
		this->age = age;
	};//全缺省
	string get_name() {
		return name;
	}
	int get_age() {
		return age;
	}
private:
	string name;
	int age;
};
int main() {
	student a;
	student b("张三",20);
	student c("李四");
}

注意!!!

全缺省的只能这么调,如果不带括号调的是默认的构造。

2.析构函数

与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

~类名();

特征:

(1). 析构函数名是在类名前加上字符 ~。
(2). 无参数无返回值类型。
(3). 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构
函数不能重载

(4). 对象生命周期结束时,C++编译系统系统自动调用析构函数

清理的都是开辟的堆内存,栈内存不需要。

typedef int DataType;
class Stack {//堆栈
public:
	Stack(size_t capacity = 3) {//size_t是无符号整数
		_array = (DataType*)malloc(sizeof(DataType) * _capacity);
		if (_array == NULL) {
			cout << "创建失败" << endl;
			return;
		}
		_capacity = capacity;
		_size = 0;
	};
	//……其他函数
	~Stack() {
		if (_array) {//防止出现空指针异常
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	};
private:
	DataType* _array;//元素
	int _capacity;//容量
	int _size;//实际长度
};

3.拷贝构造函数

创建一个和已存在的对象一模一样的对象。

只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

特征:

1. 拷贝构造函数是构造函数的一个重载形式。 

思考:如何将一个对象赋值给另一对象?

我们的第一反应肯定是这样:

Date(Date d) {
		this->_year = d._year;
		this->_month = d._month;
		this->_day = d._day;
	}

但是编译器报了这样一个错误:

为什么呢?我们来解释一下:

这是没有自己写拷贝构造的情况下调用编译器自动生成的:

class Date {//创建一个日期类
public:
	Date(int year = 0, int month = 1, int day = 1) {
		//现在不考虑一些其他的因素(像年份大于等于0这种合法暂时不考虑),只是简单的赋值
		_year = year;
		_month = month;
		_day = day;
	}
	int _year;
	int _month;
	int _day;
};
int main() {
	Date t1 = { 2024,2,19 };
	//对象的拷贝其实本质就是内容的拷贝,那么如何拷贝呢?
    Date t2(t1);
	cout << t2._year << "-" << t2._month << "-" << t2._day << endl;
}

正确的自定义的拷贝构造函数:

//Date(Date d)//错误
	Date(Date& d) {//正确
		this->_year = d._year;
		this->_month = d._month;
		this->_day = d._day;
	}

2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,
因为会引发无穷递归调用

3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按
字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

浅拷贝:按内存存储字节序完成拷贝

注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定
义类型是调用其拷贝构造函数完成拷贝的。

4.赋值运算符重载

运算符重载:

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其
返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

函数名字为:关键字operator后面接需要重载的运算符符号。

返回值类型 operator操作符(参数列表);

注意:
1.不能通过连接其他符号来创建新的操作符:比如operator@
2.重载操作符必须有一个类类型参数
3.用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
4.作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
5.    .*     ::    sizeof    ?:    . 注意以上5个运算符不能重载。这个经常在笔试选择题中出
现。 

赋值运算符重载:

class Date {//创建一个日期类
public:
	Date(int year = 0, int month = 1, int day = 1) {
		//现在不考虑一些其他的因素(像年份大于等于0这种合法暂时不考虑),只是简单的赋值
		_year = year;
		_month = month;
		_day = day;
	}
	Date(Date& d) {
		this->_year = d._year;
		this->_month = d._month;
		this->_day = d._day;
	}
	Date& operator=(Date& d) {
		if (this != NULL) {
			this->_year = d._year;
			this->_month = d._month;
			this->_day = d._day;
		}
		return *this;
	}
	int _year;
	int _month;
	int _day;
};
int main() {
	Date t1 = { 2024,2,19 };
	Date t2;
	Date c = (t2 = t1);//调用的Date & operator=(Date & d)
	cout << &t1 << endl << &t2 << endl << &c << endl;
	cout << t1._year << ":" << t1._month << ":" << t1._day << endl;
	cout << t2._year << ":" << t2._month << ":" << t2._day << endl;
	cout << c._year << ":" << c._month << ":" << c._day << endl;
}

我们现在完善一些其他的运算符:

#include<iostream>
using namespace std;
class Date {//创建一个日期类
public:
	//判断这个月有多少天
	int judgemonth(int year,int month) {
		int months[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
		if (((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)&&month ==2){
			return 29;
		}//判断是否是闰年
		return months[month];
	}
	//全缺省构造函数
	Date(int year = 0, int month = 1, int day = 1) {
		if (year >= 0&&month>=1&&month<=12) {//年份必须>=0才合法
			_year = year;
			_month = month;
			//每个月的天数都不一样,因此我们创建一个判断函数judgemonth,判断天数是否合法
			if (day >= 1 && day <= judgemonth(year, month)) {
				cout << "创建成功" << endl;
			}
		}
		cout << "创建失败" << endl;	
	}
	//拷贝构造函数
	Date(Date& d) {
		this->_year = d._year;
		this->_month = d._month;
		this->_day = d._day;
	}
	//赋值重载函数
	Date& operator=(Date& d) {
		if (this != NULL) {
			this->_year = d._year;
			this->_month = d._month;
			this->_day = d._day;
		}
		return *this;
	}
	//<重载
	bool operator<(Date& d) {
		if (this->_year < d._year) {
			return true;
		}
		else if (this->_year == d._year && this->_month < d._month) {
			return true;
		}
		else if (this->_year == d._year && this->_month == d._month && this->_day < d._day) {
			return true;
		}
		return false;
	}
	//>=重载
	bool operator>=(Date& d) {
		return !(*this < d);
	}
	//日期后多少天的日期 
	Date& operator+=(int days) {
		int day = days + _day;
		while (true) {
			int d = this->judgemonth(_year, _month);
			if (day > d) {
				_month++;
				day -= d;
				if (_month == 13) {
					_month = 1;
					_year++;
				}
			}
			else {
				break;
			}
		}
		_day = day;
		return *this;
	}
	//+运算符重载
	Date& operator+(int days) {
		Date temp = *this;
		temp += days;
		return temp;
	}
	//++d1前置加加运算符重载
	Date& operator++() {
		*this += 1;
		return *this;
	}
	//d1++后置加加运算符重载
	//因为前后置++都是一样的函数名所以为了区分他俩在后置加加的位置放一个int作为区分
	Date operator++(int) {
		Date temp = *this;
		*this += 1;
		return temp;
	}
	//计算前多少天的日期
	Date& operator-=(int days) {
		_day -= days;
		while (_day <= 0) {
			_month--;
			if (_month == 0) {
				_month = 12;
				_year--;
			}
			_day += this->judgemonth(_year,_month);
			
		}
		return *this;
	}
	//-运算符重载
	Date& operator-(int days) {
		Date temp = *this;
		temp -= days;
		return temp;
	}
	//析构函数
	~Date() {
		this->_year = 0;
		this->_month = 0;
		this->_day = 0;
	}
	int _year;
	int _month;
	int _day;
};
int main() {
	
}

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

这两个默认成员函数一般不用重新定义 ,编译器默认会生成。

const成员

 将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数
隐含的this指针
,表明在该成员函数中不能对类的任何成员进行修改。

什么时候给成员函数加const?

答:只要成员函数中不需要修改成员变量最好都加上const.

有些必须加const是为什么?
*const对象可以调用非const成员函数吗?可以
*非const对象可以调用const成员函数吗?可以
*const成员函数内可以调用其它的非const成员函数吗?不可以
*非const成员函数内可以调用其它的const成员函数吗?可以

所以如果函数是const成员函数那么const成员和非const成员都可以调用。

复习一下const修饰符:

可以从大范围到小范围,不可以从小范围到大范围。

int a = 1, b = 2, c = 3, b = 4;
	//提问:const 修饰的分别是什么?
	const int* pa = &a;//变量a
	int const* pb = &b;//变量b
	int* const pc = &c;//指针
	//看const在谁之前,在指针之前就是
	//修饰变量,之后就是修饰指向这个变量的指针。

class Date {
public :
	void constprint() const{//constprint(const Date* this)
	}
	void print() {//constprint(Date* this)
	}
	void f1() {
		this->constprint();//√
		this->print();//√
	}
	void f2() const{
		this->constprint();//√
		this->print();//错
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};
int main() {
	Date a;
	a.constprint();//对
	a.print();//对
	const Date b;
	b.constprint();//对
	b.print();//错
}

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

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

class Date
{
public :
Date* operator&()
{
return this ;
}
const Date* operator&()const
{
return this ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};

四.补充

构造函数补充——初始化列表

作用:给类中的成员进行初始化。

特点:在构造函数上,冒号开头,成员变量(初始化值),逗号间隔。

class Date {
public:
	Date(int year, int month, int day)
		:_year(year),
		_month(month),
		_day(day)
	{

	}

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

但是构造函数不是可以进行初始化吗?

答:有一些必须用初始化列表的成员函数:

  • const成员函数(只有一次初始化的机会,要求在定义的时候就初始化)
  • &引用成员变量(定义的时候就必须有引用的值)
  • 没有默认构造函数的自定义成员变量
class A {
public:
	A(int a) {
		this->a = a;
	}
private:
	int a;
};
class B {
public:
	B(int b1, int b2)
		:_b1(b1),
		_b2(b2),
		_b3(1)
	{
	};
private:
	const int _b1;
	int& _b2;
	A _b3;
};

注意:初始化的顺序和成员定义的顺序有关,建议声明和定义的顺序一样避免出错。


隐式转换:

c++98:

c++11还支持多个参数

 如果不想让他进行隐式转换可以在构造函数前加上explicit关键字


 static成员:

静态成员变量:

static成员变量不存在对象中,存在静态区,属于这个类的所有对象,也是属于这个类

class A{

private:

        static int n;//声明,属于类的静态成员变量

}

int A::n = 0;//定义

静态成员函数:

static成员函数,没有 this指针,不适用对象就可以调用——类名::函数名();

静态成员突破类域(知道哪个类)+访问限定符就可以访问

  • 静态不可以调非静态(没有this指针)
  • 非静态可以调静态(类里面是一个整体,都是这类域中的,所以类里面步骤访问限定符限制)


友元函数,友元类:

friend

函数;

friend 类;

class Date {
	friend class Time;
	friend void f(Date b);
private:
	int _year;
	int _month;
	int _day;
};
void f(Date b) {
	cout << b._year << ":" << b._month << ":" << b._day << endl;//友元类可以访问类里私有保护的成员 
}
class Time {//友元函数可以访问类里私有保护的成员
public:
	void Ti() {
		cout << da._year << endl;
	}
private:
	int h;
	Date da;
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值