C++类和对象详细总结

目录

目录

类与对象概念

什么是对象

什么是类

什么是方法:

自定义类型(类的关键字:class)

定义类的格式

封装

类的特性

访问权限以及访问限定符

struct 定义的类和class定义的类的区别:

小结

对象中包含了那些成员以及我们如何去求一个对象的大小?

this指针

this指针概念

this指针的特性

编译器对类的识别

类的六个默认成员函数

默认成员函数的概念:

构造函数

进阶构造函数

析构函数

拷贝构造函数

运算符重载函数()

赋值运算符重载函数

const类型成员函数

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

单参构造函数具有类型转化的作用

static成员

概念:

静态成员特性

静态成员的访问

静态成员函数

友元

友元函数

友元类

内部类

概念:


类与对象概念

什么是对象

对象是一个实体,我们眼睛看到的所有实体都可以看成一个实体对象

什么是类

类是用来对实体(对象)进行描述的。(对象有什么属性,有什么功能)类是一种自定义类型

什么是方法:

方法是实现类功能的一个具体实现,该类有什么样的功能?类的所有功能都要通过调用方法来实现。

自定义类型(类的关键字:class)

C中我们学过struct(结构体)自定义类型。c++为了兼容c语言,在这里我们也可以使用struct来定义类。和c不同的是,c++中的struct可以在内部放置函数,除此以外两者定义出的类还有其他的的区别。

定义类的格式

class 类名//或者struct 类名
{
//类体:成员变量--属性  成员函数---功能
};


封装

将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口和对象交互。

也就是说,我们通过类将数据进行封装,我们不能直接访问类的属性,只能通过调用类中的方法来实现对对象属性功能的访问。(这样做的好处是什么?可以提高代码的安全性,当程序很大的时候,一个程序员可能只负责某一个模块,程序员将自己所写的程序进行封装,然后只对主函数提供几个接口(主函数可以调用的方法),那么在调试的过程中,编程人员可以很快的找到代码错误的地方,提高了代码的可维护性)

注意:由于C++兼容c,c++并不是一门完全面向对象的语言。(既有面向对象,又有面向过程)

类的特性

访问权限以及访问限定符

访问权限概念:控制哪些成员可以在类外直接被访问,哪些只能通过方法进行访问

访问限定符

访问限定符是用来管理类内部成员的访问权限的,

访问限定符的作用域从该访问限定符出现的位置,知道到下一个访问限定符出现的位置为止。

struct 定义的类和class定义的类的区别:

struct定义的类:成员默认的访问权限是public

class定义的类:成员默认的访问权限是private

小结

类相当于定义了一个新的作用域

用类类型创建对象的过程,称为类的实例化

1.类是对对象描述的,定义一个类并没有分配实际的内存空间来存储它

2.类实例化出来的对象,占用实际的物理空间,储存类变量

对象中包含了那些成员以及我们如何去求一个对象的大小?

从效果上来看,对象中既有成员函数,又有成员变量然而成员函数没有必要在每个对象中都存储一份,如果每个对象都存储,无疑会让对象的存储变得很大

实际上:对象只存储成员变量

求对象的大小时,注意内存对齐就好了

如果是空类或者是没有成员变量只有成员函数的类创建的对象,编译器为了区分各个实体对象,分别会给予一定的空间。主流编译器对空类和对有成员变量的类创建的对象分配了一个字节。

this指针

this指针概念

c++通过引入this指针,编译器给每一个“非静态的成员函数”增加了一个隐藏指针参数,让该指针指向当前对象(函数运行时调用该函数的对象)。在每一个成员函数体内所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成

this指针的特性

1.this指针的类型:类类型*const,即成员函数中,不能给this指针赋值。

2.只能在成员函数内部使用

3.this指针本质上其实是一个成员函数的第一个形参,是对象调用函数时,将对象的地址作为实参传给this指针,,所以对象中不存储this指针。

4.this指针是一个隐含指针形参,一般情况下由编译器自动传递。

5this指针存放在栈上

6 this 指针可以为空,this指针的类型为类类型*const,即指针变量为常量。我们可以在类外部定义一个类类型指针,将该指针初值为空 ,然后将其作为实参传入this指针形参中。

值得注意的是,当this指针为空时,如果成员函数没有调用成员变量,那么代码不会崩溃,否者代码就会崩溃。

编译器对类的识别

主流编译器对程序的识别都是自上而下的

也就是说,我们在main函数前定义一个函数时,在main函数中调用并不需要声明,如果在main函数结束的下方定义函数,我们就必须要在main函数中调用前声明,否则编译器就会因找不到该函数而报错。

编译器编译类的步骤

1.先识别类名

2.识别类中有哪些成员变量

3.识别类中有哪些成员函数。

4.会对成员函数进行改写。

改写即是编译器对成员函数进行预处理,将隐藏的this指针给加上。

因而成员变量在类域的前后顺序对程序编译没有影响。

类的六个默认成员函数

默认成员函数的概念:

用户自己没有写,编译器会自动生成;一旦用户显式定义,编译器便不再生成。

构造函数

构造函数是特殊的成员函数,名字与类名相同,创建对象时由编译器自动调用,以保证每个数据都能有一个合适的初始值,并且在对象整个生命周期只调用一次。

构造函数的主要任务不是开辟空间创建对象,而是初始化对象。

对象的创建,如果是一个临时变量则编译器会在在栈上为其开辟空间,如果是一个动态存储变量则会通过malloc或者new(C++的动态管理操作符)在堆上开辟空间。

构造函数的特性

1.构造函数名与类名相同

2.构造函数为无返回值类型

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

4.构造函数支持函数重载

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

编译器自动生成的默认构造函数

编译器自动生成的默认构造函数是一个无参的默认函数。函数体内什么都没有。

如果类类型包含其他的类成员对象,编译器就会生成默认的构造函数对自定义类型成员调用它的默认成员函数。

进阶构造函数

构造函数函数体内,并不是对对象初始化的地方,而是对成员变量进行赋值

而真正进行初始化的地方是初始化列表

以时间类为例:

	Date(int year = 2022, int month = 4, int day = 12)
		:_year(2022)//初始化列表
		, _month(4)
		, _day(11)
	{
		_year = year;
		_month = month;
		_day = day;
	
	}

初始化链表的特性

1.每个成员变量在初始化列表只能出现一次(初始化只能一次)

2.类中包含以下成员,必须在初始化位置进行初始化

2.1.引用成员变量

2.2.const成员变量

2.3.自定义类型成员(且该类中没有默认构造函数的)

3.尽量使用初始化列表进行初始化,因为不管是否显式定义初始化列表,对于自定义类型成员来说,编译器一定会先使用初始化列表进行初始化。

5.初始化列表是有初始化顺序的,相当于类中的声明次序,与在初始化列表的顺序无关

析构函数

析构函数概念:

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

析构函数特性 

1.析构函数时在类名前加上字符~

2.析构函数无参数吴返回值类型

3.析构函数不能重载

4.一个类只能有一个析构函数,若为显式定义,系统会自动调用默认的析构函数

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

编译器默认生成的析构函数体内什么都没有。

对于析构函数的理解

如果该类中没有涉及到资源时,析构函数可以选择显式定义,直接使用编译器生成的默认析构函数,有资源申请时,一定要写,否则会造成资源泄漏。比如stack类。

拷贝构造函数

拷贝构造函数的概念

拷贝构造函数只有单个形参,该形参是对本类类型对象的引用。在用已存在的类类型创建对象创建新对象时由编译器自动调用

拷贝构造函数的特性

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

2.拷贝构造函数的参数只有一个,且必须使用引用传参(传值将会进行无穷递归调用)

3.若未显式定义,系统会生成默认的拷贝构造函数。

4.用户(开发程序员)显式定义拷贝构造函数时都会把形参用const修饰,防止改动,增强代码的安全性

5.默认的拷贝构造函数对象按照内存存储,按字节序完成拷贝,这种拷贝就叫做浅拷贝

浅拷贝的危害

如果类中没有涉及到资源的申请,用户显式定义拷贝构造函数或者不显式定义使用系统默认的拷贝构造函数都可以。一旦类中涉及到资源的申请,如果用户使用默认的拷贝构造函数进行浅拷贝,这样操作会使两个对象指向同一个资源空间,当我们对象销毁时,会对同一个资源进行多次释放,从而引起程序的崩溃。此时就需要用户显式定义拷贝构造函数,进行深拷贝。

ps:我们对一个资源空间的释放并不是指这片空间不存在了,而是操作系统脱离了对这片地址的控制,脱离控制之后,我们多次释放,肯定会引起程序的崩溃。

.拷贝构造函数的典型运用场景,能引用尽量引用。

运算符重载函数()

引入:

class Date{
public:
	Date(int year = 2022, int month = 4, int day = 12)
		:_year(2022)
		, _month(4)
		, _day(11)
	{
		_year = year;
		_month = month;
		_day = day;
	
	}

private:
	int _year;
	int _month;
	int _day;

};
int main()
{
	Date d1(2002,1,15);
    Date d2;
	d2=d2+1;//显然编译器会在此处报错
	system("pause");
	return 0;

}

 很显然对象和对象之间不能用+操作符,但是这个问题在c++中得到了解决。

概念:

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

函数格式:返回值类型 operator 后面接上要重载的的运算符符号(参数列表)

本文以+重载为例

	Date operator+(int days){
		Date temp(*this);
		temp._day ++;
		return temp;
	}

重载操作符类函数特性

1.重载运算符必须只少有一个是类类型参数;

2.用于内置类型的运算符,其含义不能改变。

前置++与后置++重载(前置--与后置--同理)

由于前置++ 和后置++都是单目运算符,只要有一个操作数,因而都不需要往参数列表里传参,但我们又想形成重载

故C++规定,不带参数的为前置++,带参数的为后置++

具体编译器会调哪一个函数,编译器会根据操作数的位置上来做出判断。

代码示例

Date operator++()
	{
		_day++;
		return *this;
	}
	Date operator++(int)
	{
		Date temp(*this);
		_day++;
		return temp;
	}

赋值运算符重载函数

赋值运算符的重载就是将=操作符进行

如果不显示定义,则程序自动生成一个默认的赋值运算符重载函数  ,相当于浅拷贝。因而当类中涉及到资源管理时,赋值运算符一定要显式定义。

理解构造函数,拷贝构造函数,赋值运算符重载函数,

Date d1(2022);//这里使用的是构造函数
Date d2=d1;//这里使用的是拷贝构造函数,而不是调用的赋值运算符重载函数
Date d3;//这里使用构造函数
d3=d2;//这里使用的赋值运算符重载函数

const类型成员函数

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

该函数为类类型内的一个成员函数

void Print()const
	{
		cout << _year << "年" << _mouth << "月" << _day << "日" << endl;
}

这个const相当于修饰this指针使this指针从类类型* const this成为const 类类型* const this,即指针指向的是一个常量,指针的值也是一个常量。

const类成员函数的理解

1.const 对象不能调用非const成员函数

2.const对象只能调用const成员函数

3.const成员函数可以调用非const成员函数

4.非const成员肯定能调用const成员函数

被mutable修饰的成员变量仍旧可以在const成员函数内部被修改。

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


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

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

单参构造函数具有类型转化的作用

Date d3(2022);
d3=2023;

2023是int型的,不能直接给Date类型的对象进行赋值,但是编译器编译Date具有一个int类型的单参构造函数,于是编译器就给编译器将2013借助单参构造函数生成了一个临时对象,此处的赋值实际上是用生成的来临时对象给d3来进行赋值的,并不是直接用2023给d3赋值的,赋值结束后,临时对象就被销毁了

但是 这样写代码的可读性并不是很高

为了提高代码的可读性,我们可以在构造函数前加上explicit关键字来禁止编译器这么做。

static成员

概念:

static类成员称为类的静态成员,static修饰的成员变量,称之为静态成员变量;用static修饰的函数称之为静态成员函数。静态成员变量一定要在类外进行初始化。

静态成员特性

1. 静态成员为所有类对象所共享,不属于某个具体的实例
2. 静态成员变量必须在类外定义,定义时不添加static关键字
3. 类静态成员即可用类名::静态成员或者对象.静态成员来访问
4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
5. 静态成员和类的普通成员一样,也有public、protected、private3种访问级别,也可以具有返回值


静态成员的访问

方式一:

静态成员变量的访问:类名::静态成员变量的名字

方式二:

对象.静态成员变量名

建议使用方式一,因为在计算机内部,也是转化为1这种形式。

静态成员函数

静态成员函数不能访问非静态成员函数以及非静态变量只能访问静态成员变量以及静态成员函数,因为静态函数没有隐藏的this指针。

友元

概念:

友元提供了一种突破封装的方式,有时会提供便利,但增强了耦合性,破坏了封装,所以友元不宜过多使用。

友元函数

友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。

友元函数特性

1.友元函数可访问类的私有和保护成员,但不是类的成员函数友元函数不能用const修饰
2.友元函数可以在类定义的任何地方声明,不受类
3.一个函数可以是多个类的友元函数
4.友元函数的调用与普通函数的调用和原理相同

友元类

如果构造两个不同的类,class A class B,并在B的内部中声明friend A,那么类A可以直接访问类B的私有成员以及私有函数。

友元类特性

1.友元关系是单向的,不具有交换性

即上述中类A可以直接访问B的私有空间,类B则不能访问A的私有空间。

2.有元关系不具备传递性

即b是a的友元,c是b的友元,但不能说明c是a的友元。


内部类


概念:

如果一个类定义在另一个类的内部,这个内部类就叫做内部类。注意此时这个内部类是一个独立的
类,它不属于外部类,更不能通过外部类的对象去调用内部类。外部类对内部类没有任何优越的访问权限。


注意:内部类就是外部类的友元类。注意友元类的特性,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元


猜你喜欢

转载自blog.csdn.net/m0_56910081/article/details/124092694
今日推荐