【C++总复习】第3章----类和对象的正确使用

1.构造函数

  • 对象初始化:创建对象时,给变量第一次赋值称为对象初始化。
  • 构造函数:处理对象初始化问题;构造函数是一种成员函数无需用户调用,而是建立对象时自动调用匹配的构造函数。
  • 构造函数的名字与它所属的类名相同,被声明为公有函数,且没有任何类型的返回值
  • 允许为内联函数、重载函数、带默认形参值的函数。

1.1无参数的构造函数

  • 无参数构造函数就是不给构造函数传参
  • 无参构造函数可以不写,系统会默认给出无参构造函数
class Student{
private:
	int age;
	int num;
public:
	Student(){	//无参构造函数
		age = 0;
		num = 0;
	}
};
int main(){
	Student stu;// age=0 num=0
	Student stu(); //错误方式
}

1.2有参数构造函数

  • 调用者向构造函数中传入数据

写法一

class Student{
private:
	int age;
	int num;
public:
	Student(int Age,int Num){	//有参数构造函数
		age = Age;
		num = Num;
	}
};
int main(){
	Student stu(18,100);// age=18 num=100
}

写法二

class Student{
private:
	int age;
	int num;
public:
	Student(int Age,int Num)};
Student::Student(int Age,int Num){	//有参数构造函数
		age = Age;
		num = Num;
	}
int main(){
	Student stu(18,100);// age=18 num=100
}

1.3缺省构造函数

  • 参数列表给出部分数据的值
  • 实参的个数不能少于缺省形参的个数
#include <iostream>
using namespace std;

class Date{
	private:
		int year;
		int month;
		int day;
	public:
		Date(int y,int m,int d = 10){	//缺省构造函数
			year = y;
			month = m;
			day = d;
		}
		void display(){
			cout<<year<<" "<<month<<" "<<day<<endl;
		}
}; 
int main(){
	Date date(2020,6);	//可以不给day传值
	date.display();
	return 0;
}

1.4用参数初始化列表对数据进行初始化

  • 参数初始化表实现对数据成员的初始化,这种方法不在函数体内对数据成员初始化,而是在函数首部实现。
  • 注意:若数据成员是数组,则不能在参数初始化列表中对
    其初始化,而应在构造函数的函数体重用语句对其赋值。
  • 优点:需要初始化的参数过多时,使用该方法可以减少代码量。
class Date{
private:
	int year;
	int month;
	int day;
public:
	Date(int y,int m,int d);
};
Date::Date(int y,int m,int d):year(y),month(m),day(d)
{

}
int main(){
	Date(2020,6,1);//year=2020 month=6 day=1

	return 0;
}

1.4构造函数重载

  • 构造函数重载:在一个类中定义多个构造函数,这些构造函数的函数名相同,参数个数或参数类型不相同,称为构造函数的重载。
class Student{
private:
	int age;
	int num;
public:
	//以下两个构造函数构成构造函数的重载
	Student(){	//无参构造函数
		age = 0;
		num = 0;
	}
	Student(int Age,int Num)};
Student::Student(int Age,int Num){	//有参数构造函数
		age = Age;
		num = Num;
	}
int main(){
	Student stu1;	//自动调用无参构造函数:age=0 num=0
	Student stu2(18,100);//自动调用有参构造函数age=18 num=100
}

2.析构函数

  • 析构函数:作用与构造函数相反,但不是删除对象,而是在撤销对象占用的内存之前完成一些清理工作,使这部分内存可以被程序分配给新对象使用。

  • 一个类可以有多个构造函数,但只能有一个析构函数

  • 执行析构函数的情况:

  1. 用new运算符动态地建立对象,当用delete运算符释放该对象时,先调用该对象的析构函数。
  2. 在一个函数中定义了一个局部对象,当这个函数调用结束时,在对象释放前自动执行析构函数。
  3. 如果定义了一个全局对象,只有在程序结束时,才调用该全局对象的析构函数。
  4. 静态(static)局部对象只在main函数结束时,才调用static局部对象的析构函数
  • 如果用户未定义析构函数,C++编译器也会自定生成一个析构函数,但是它只有析构函数的名称和形式,实际上什么操作也不做。

3.构造函数与析构函数的调用顺序

  • 函数调用存在函数压栈,函数调用结束存在函数弹栈
  • 先构造的后析构,后构造的先析构

图解
在这里插入图片描述

4.对象数组

  • 数组不仅可以由简单变量组成,也可以由类对象组成
  • 对象数组的每一个元素都是一个同类的对象
#include <iostream>
using namespace std;

class Date{
	private:
		int year;
		int month;
		int day;
	public:
		Date(){
		}
		Date(int y,int m,int d = 10){
			year = y;
			month = m;
			day = d;
		}
		void display(){
			cout<<year<<" "<<month<<" "<<day<<endl;
		}
}; 

int main(){
	Date dates[3] = {Date(1,1,1), //第一个元素调用有参构造函数赋值
					 Date(2,2,2),//第二个元素调用有参构造函数赋值
					 Date(3,3,3)//第三个元素调用有参构造函数赋值
						};
	int i;
	for(i = 0; i < 3;i++){
		dates[i].display();
	}
	return 0;
}

5.对象指针


class Date{
	private:
		int year;
		int month;
		int day;
	public:
		Date(int y,int m,int d = 10){	//构造函数
			year = y;
			month = m;
			day = d;
		}
		void display(){
			cout<<year<<" "<<month<<" "<<day<<endl;
		}
}; 
int main(){
	Date date(2020,6,1);	//定义Date类对象
	Date *pd;	//定义Date类型对象指针
	pd = &date;	//指针指向Date类对象
	
	pd->display();//使用对象指针间接调用成员函数
	(*pd).display();
}

6.this指针

  • 在每一个成员函数中都包含一个this指针,它是指向本对象的指针,它的值是当前被调用的成员函数所在的对象的起始地址
  • this指针常用来分辨形参与实参
class Date{
	private:
		int year;
		int month;
		int day;
	public:
		Date(int year,int month,int day = 10){	//构造函数
		//下面的this不可省略,用以区分形参和实参
			this->year = year;
			this->month = month;
			this->day = day;
		}
		void display(){
			cout<<this->year<<" "<<this->month<<" "<<this->day<<endl;
			//此处的this可以省略
		}
}; 

6.const关键字

  • 要使数据能在一定范围内共享,又要保证它不被任意修改,这时可以把有关的数据定义为常量

6.1常对象

  • 用const修饰对象
  • 格式1:类名 const 对象名【(实参列表)】;
  • 格式2:const 类名 对象名【(实参列表)】;
Date const date1(2020,6,1);
const Date date2(2019,6,1);

date1.display(); //非法调用普通成员函数

说明:

  • 常对象必须初始化
  • 常对象只能调用常成员函数,无法调用普通常成员函数
  • 常成员函数是常对象对外的唯一接口
  • 常对象无法修改内部数据成员的值

有时编程时有要求,如果执意要通过常对象修改内部数据成员的值,可以通过将需要修改的属性声明为mutable,这样就可以通过常对象来修改属性值

mutable int year;
mutable int month;

6.2常成员变量

  • 用const修饰类内部某属性,该属性只能通过构造函数的参数初始化表来初始化,任何其他函数都不能对常数据成员赋值
  • 可以理解为:const修饰的成员变量只能赋值一次
class Time{
private:
	const int hour;
	const int minute;
	const int sec;
public:
	Time(int h,int m,int s):hour(h),minute(m),sec(s){}//合法
	Time(int h,int m,int s){	
	//非法,该构造函数无法对const修饰的成员变量进行初始化
		hour = h;
		minute = m;
		sec = s;
	}

};

6.3常成员函数

  • 格式:类型名 函数名(参数表) const{ }
#include <iostream>
using namespace std;

class Time{
private:
	int hour;
	int minute;
	int sec;
public:
	Time(int hour,int minute,int sec){
		this->hour = hour;
		this->minute = minute;
		this->sec = sec;
	}
	void doOther(){
		cout<<"hello world!"<<endl;
	}
	void doSome()const{
		cout<<hour<<":"<<minute<<":"<<sec<<endl;	
		//合法,可以引用属性
		hour = 6;//非法,无法修改属性
		doOther();//非法,无法调用非const成员函数
	}
};
  • 常成员函数可以引用成员变量,无法修改成员变量
  • 常成员函数无法调用另一个非const成员函数

6.4指向对象的常指针

  • 将指针变量声明为const型,这样指针变量的指向始终不能改变
  • 格式:类名 *const 指针变量名;
Time t1(1,2,3),t2(4,5,6);
Time* const pt;	//定义Time类型的常指针

pt = &t1;	//合法初始化
pt = &t2;	//非法改变常指针指向

6.5指向常对象的指针变量

  • 格式:const 类名 * 指针变量名;
  • 常变量只能用指向常变量的指针变量来指向;常对象也只能用常对象的指针变量来指向;
  • 指向常对象的指针变量可以指向非const修饰的普通对象
  • 指向常对象的指针变量的指向可以随意更改
  • 无法通过指向常对象的指针变量来修改对象内部的值
class A{
public:
	int x;
	int y;
	A(int x,int y){
		this->x = x;
		this->y = y;
	}
};
void doSome(const A *p){//形参为指向常对象的指针变量
	p->x = 20;	//非法,无法通过指向常对象的指针变量来修改对象内部的值
	p->y = 20;	//非法,无法通过指向常对象的指针变量来修改对象内部的值
}
void doOther(A *p){//普通A类型的指针
	p->x = 20;	//合法
	p->y = 20;	//合法
}
int main(){
	A a(10,10);
	
	doSome(&a);
	doOther(&a);
	return 0;
}

6.6对象的常引用

  • 将对象的引用用const修饰起来,则无法通过引用来修改对象内部的属性值
  • 格式: 类名 &引用名 = 对象名;
class A{
public:
	int x;
	int y;
	A(int x,int y){
		this->x = x;
		this->y = y;
	}
};
int main(){
	A a(10,10);
	
	const A &b = a;		//定义并初始化常引用
	
	b.x = 20;	//非法,无法通过常引用修改对象值
	b.y = 20;	//非法,无法通过常引用修改对象值
	
	a.x = 20;	//合法
	a.y = 20;	//合法
	
	return 0;
}

6.7const总结

在这里插入图片描述

7.对象的建立与释放

  • new运算符动态地分配内存建立对象,用delete运算符释放内存撤销对象
Date *pd = new Date(2020,6,1); //对象指针指向动态创建的一个无名对象
delete pd;	//释放对象

8.对象的赋值与复制

8.1对象的赋值

  • 使用"="可以实现用对象给对象进行赋值
  • 赋值是针对对象中的成员变量的,而不是针对成员函数的;因为成员变量是每个对象单独一份代码,而成员函数是所有对象共用一份代码
	Date date1(2020,1,6);
	Date date2 = date1;
	//此时date2中的数据值和date1中的数据值一模一样
	//date1和date2是两个完全不同的对象

8.2对象的复制

  • 复制构造函数:作用是将一个对象的各个成员值一一赋给新的对象中对应的成员
  • 如果用户未定义复制构造函数,则系统会自动提供一个默认的复制构造函数,作用是简单的复制类中的每个成员变量
class Student{
private:
	int age;
	int num;
public:
	Student(int age,int num){	//普通构造函数
		this->age = age;
		this->num = num;
	}
	Student(Student &s){		//复制构造函数
		age = s.age;
		num = s.num;
	}
};
int main(){
	Student stu1(10,20);	//调用有参构造函数
	Student stu2(stu1);		//调用复制构造函数

	return 0;
}

  • 调用复制构造函数的3种情况:

  • 1.需要建立一个新对象时

Student stu2(stu1);
  • 2.函数参数为类的对象时
void fun(Student stu){}	
int main(){
	Student stu(1,1);
	fun(stu);		//传入的对象便是stu的一份拷贝
}
  • 3.函数返回值是类的对象时
Student fun(){
	Student s;
	return s;	//返回值是Student类的对象
}
int main(){
	Student s;
	s = fun();
	
	return 0;
}

9.static关键字

9.1静态成员变量

  • static修饰的成员变量并不只属于某一个对象,所有对象都可以引用它
  • 静态成员变量在内存中只占一份空间
  • 静态成员变量的存在是不依赖于对象的,它是属于类的
  • 在一个对象中改变了静态成员变量,那么静态成员变量的值在所有对象中都改变了
  • 简单来说,所有对象共享一份静态成员变量
class Box{
private:
	static int length;	//静态成员变量
	int height;
	int long;
};
int Box::length = 100;	//初始化静态成员变量
  • 静态成员变量只能在类外进行初始化
  • 初始化格式: 数据类型 类名::静态成员变量名 = 初值
  • 不能用参数初始化表对静态数据成员初始化

可以通过类来单独访问静态成员变量

  • 类访问格式:
	Box::length = 10;

9.2静态成员函数

  • static修饰的成员函数属于类而不属于对象
  • static修饰的成员函数没有this指针,所以静态成员函数无法访问本类中的非静态成员变量
  • 可以通过类名和对象两种方法调用函数
class A{
public:
	static void fun(){}
};
int main(){
	A a;
	
	a.fun();	//通过对象调用静态成员函数
	A::fun();	//通过类调用成员函数
	
	return 0;
}

10.友元(friend)

在类外可以访问公有成员,只有本类中的成员函数可以访问本类中的私有成员,友元提供了一个从外部访问类内部私有成员的方式。

10.1友元函数

  • 在本类以外定义一个函数,在类体内部声明为友元函数,此函数就称为本类的友元函数,此友元函数就可以访问这个类中的私有成员。
  • 一个函数可以被多个类声明为“朋友”,这样就可以引用多个类中的私有数据
10.1.1将普通函数声明为友元函数
#include <iostream>
using namespace std;
class Date{
	private:
		int year;
		int month;
		int day;
	public:
		Date(){
			year = 0;
			month = 0;
			day = 0;
		}
		friend void display(Date &date);
};
void display(Date &date){	
//将此函数声明为友元函数,就可以访问类内部私有属性
	cout<<date.year<<" "<<date.month<<" "<<date.day<<endl;
}
int main(){
	Date d;
	display(d);
	return 0;
}
10.1.2友元成员函数
  • 在Time类中将Date类中的display方法声明为友元函数,这样Date类中的display方法就可以无障碍访问Time类中的私有属性
#include <iostream>
using namespace std;

class Time;
class Date{
private:
	int year;
	int month;
	int day;
public:
	Date(int year, int month, int day){
		this->year = year;
		this->month = month;
		this->day = day;
	}
	void display(Time &t);
};
class Time{
private:
	int hour;
	int minute;
	int sec;
public:
	Time(int hour, int minute, int sec){
		this->hour = hour;
		this->minute = minute;
		this->sec = sec;
	}
	friend void Date::display(Time &);
	//将Date中的display方法在Time中声明为友元函数,便可以访问
	//Date中的私有属性
};

void Date::display(Time &t){
	cout << year << " " << month << " " << day << " "
	<< t.hour << ":" << t.minute << ":" << t.sec << endl;
			//声明为友元函数便可以访问Time类中的私有属性 
}

int main(){
	Time t(1, 1, 1);
	Date d(2, 2, 2);
	d.display(t);
	system("pause");
	return 0;
}

11.类模板

  • 若多个类其功能相同,仅仅是数据类型不同,可以声明一个通用的类模板,它可以有一个或多个虚拟的类型参数

  • 定义类模板格式:template <class 虚拟类型名>

  • 用类模板定义对象格式:
    类模板<实际类型名> 对象名;
    类模板<实际类型名> 对象名(实参列表);

例如:要实现一个类可以比较不同类型的2个数字的大小

template <class T>
class Compare{
private:
	T x;
	T y;
public:
	Compare(T x,T y){
		this->x = x;
		this->y = y;
	}
	T max(){
		return (x>y)?x:y;
	}
	T min(){
		return (x<y)?x:y;
	}
};

int main(){
	Compare<int> compInt(5,6);	//比较int型数据
	compInt.max();
	compInt.min();
	
	Compare<float> compFloat(5.3,6.3);	//比较float型数据
	compFloat.max();
	compFloat.min();
	
	Compare<double> compDouble(6.6,7.4);	//比较double型数据
	compDouble.max();
	compDouble.min();
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值