C++类和对象(上)

❤️本篇介绍类class

可以初步理解类class为C语言中的结构体struct,存放变量等多个成员。此外,类还是struct的升级。

一、类的引入

C++的类class很像C中的结构体struct

☀️1.储存的成员对比

(1)C语言,结构体中存变量

例如,C语言中定义栈结构体,在结构体外声明初始化函数和进栈函数:
C++类和对象(上)_第1张图片
函数写在结构体外面的不便处:当对多个对象都有一个功能和名称差不多的函数时,容易搞混,命名繁琐。比如现在程序中不仅有栈,还有顺序表、队列、链表等等,每一种结构都需要初始化函数,此时就需要命名多个版本的初始化,名字太多,分布松散,容易搞混。

(2)C++,变量与函数一起放入类中

C++对此改进,函数也可以放在结构体(类)中,当每个类中都有一个名字和功能相似的函数时,既不会命名冲突,也会更加直观有序。
例如:下图代表两个类,分别叫Stack和Queue,内部各自都有一个init函数,不会出现命名冲突。
C++类和对象(上)_第2张图片
C++类和对象(上)_第3张图片

☀️2.函数调用方式对比

(1)C语言调用指针

例如,调用栈的初始化和入栈函数,都需要先传栈的地址:
C++类和对象(上)_第4张图片

(2)C++无需指针

C++类和对象(上)_第5张图片

二、类的概念和访问限定

☀️1.类概念:

class className
{
// 类体:由成员函数和成员变量组成
};  // 一定要注意后面的分号

class为定义类的关键字。
ClassName为类的名字。
{}中为类的主体,注意类定义结束时后面分号不能省略。
类体中内容称为类的成员:类中的变量称为类的属性或成员变量;类中的函数称为类的方法或者成员函数。

☀️2.类成员访问权限、封装

(1)访问限定符

private:私有
protect:保护
public:公有

(2)访问权限

public修饰的成员在类外可以直接被访问;protected和private修饰的成员在类外不能直接被访问;访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止。class的默认访问权限为private,struct为public(因为struct要兼容C)。

一般地,成员变量被private修饰,无法从外界直接访问;成员函数被public修饰,可被外界直接调用。如果想改变类private部分的变量的话,只能通过public内的函数修改。

(3)封装概念

设置访问权限,就是为了封装。
封装意思是:将数据和操作数据的方法进行有机结合(成员变量和成员函数统一都在一个类中),隐藏对象的属性和实现细节(将成员变量设为私有,无法从外界直接访问),仅对外公开接口来和对象进行交互(公有的成员函数就是接口,可直接和外部进行交互)。
封装本质上是一种管理,让用户更方便使用类。

例如:栈是否为空的判断

C语言中判断栈是否为空栈,既可以直接访问top指针,也可以用Empty函数,十分混乱不规范:
C++类和对象(上)_第6张图片
C++类和对象(上)_第7张图片

C++由于访问权限的限制,无法直接访问top指针,只能通过Empty函数判断,更规范整齐。
C++类和对象(上)_第8张图片

三、类的定义和类的实例化

☀️1.类的定义——创建类类型

在struct的基础上,将struct改成class关键字,后序使用这个类类型时,不需要加上class关键字,那个自定义的名字就是类类型。(C++兼容C语言,因此struct也能用来表示类,而且后序使用时也可以省略struct关键字)
例如,Data就是一个类类型名,创建Data这个类时,实际并没有分配空间存储任何数据:

class Date {
public:
	void Init(int a,int b,int c) {
		_a = a;
		_b = b;
		_c = c;
	}
private:
	int _a;
	int _b;
	int _c;
};

☀️2.类的实例化——创建某个类型的类

类的实例化就是在类类型基础上,定义的类名。
例如:在上方Data类型的基础上定义类,d1、d2就是类型为Data的类名。

C++类和对象(上)_第9张图片

☀️3.二者区别

类类型是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它;一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量。
形象例子1:入学时填写的学生信息表,表格就可以看成是一个类类型,来描述具体学生信息。每个学生填进去的信息才是类实例化出来的对象。
形象例子2:类就像是建房的设计图,类实例化出对象就像现实中使用建筑设计图建造出房子。

四、类成员的声明和定义

类成员分为成员变量和成员函数,成员变量一般被private修饰,成员函数多数被public修饰,充当类和外界的接口。
对类的声明和定义就是对类成员的声明和定义。成员变量的声明无疑只能放在类的内部,而成员函数的声明和定义可以放在一起也可以分开,具体分为以下两种:

☀️1.成员函数声明和定义都在类中

弊端:可能会将定义在类中的函数当成内联函数处理。
C++类和对象(上)_第10张图片

☀️2.声明在类的头文件.h中,定义在类的函数实现文件.cpp文件中

注意:类名和限定符

当函数定义在.cpp文件中时,需要在函数名前加上类名和限定符,如Person: :showinfo。一般建议声明和定义分开,即此种方式。
C++类和对象(上)_第11张图片

即使函数的定义和声明在同一个文件中、定义在类内部、声明在类外部时,类外部的函数名也要加上类名和限定符。

class Person
{
public:
 void PrintPersonInfo();
private:
 char _name[20];
 char _gender[3];
 int  _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
 cout << _name << " "<< _gender << " " << _age << endl;
}

☀️3.类成员变量的命名

为提高可读性,类成员变量前最好加一个前缀,如_,有的公司还会加(My)等等:
C++类和对象(上)_第12张图片

五、计算类类型的大小

一个类中既有变量又有函数,该如何计算这个类类型的大小呢?

☀️1.类对象的存储方式

(1)假设变量和函数都储存在类中

C++类和对象(上)_第13张图片
分析:一个类创建多个对象时,每个对象中成员变量的值是不同的,每个对象的成员变量各自占有各自的空间无可厚非。但如果按照此种方式存储函数时,每个对象中都会保存一份代码,相同代码保存多次,浪费空间。不采取此种存储方式。

(2)类对象内只存储成员变量,成员函数只在类外部存储一份

C++类和对象(上)_第14张图片

☀️2.计算类类型大小

类计算要考虑内存对齐。第一个成员从起始位置开始放,之后的成员比较自身大小和默认对齐数(vs下默认对齐数是8)的较小值,在较小值的倍数个位置开始放。结构体(类)的总大小为最大对齐数(所有变量类型大小与默认对齐数相比的较小值)的整数倍。

注意:无成员变量的类(空类),大小是1,对象大小开一个字节,这个字节不存储有效数据,标识定义的对象存在过。例如下图类B和类C:
C++类和对象(上)_第15张图片
C++类和对象(上)_第16张图片
C的大小还是1,因为函数不储存在类内部。

六、this指针

☀️1.this指针作用

C++类和对象(上)_第17张图片
疑问:调用的同一个函数,为何两次的结果不一样,是如何区分不同的成员变量的呢?
答:通过this指针实现。看似print成员函数没有参数,但实际上有一个隐藏的参数叫指针this,编译器会将图中上面的函数处理成下面的样子:
C++类和对象(上)_第18张图片
调用时也会将上面的函数调用处理成下面的样子:
C++类和对象(上)_第19张图片
函数是哪个对象的,则隐藏的this指针就指向该对象所占用的内存空间,从而得到成员变量的值。(之前讲过,类对象所占用的空间只储存成员变量,不储存函数)
对比一下C语言:C语言需要仔细地传递正确的指针,C++根本不用传,编译器直接就知道函数参数是多少。

☀️2.this指针特性:

(1)this指针的类型:类类型* const

const修饰this指针本身,说明this指针不可以被修改。即成员函数中,不能给this指针赋值。:
在这里插入图片描述

(2)只能在“成员函数”的内部使用

传参时,不可以显式地写出this指针:
在这里插入图片描述
但是可以在类里面显式使用,上下两句效果一样:
C++类和对象(上)_第20张图片

(3)this指针不储存在对象中

this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this指针。

(4)传递方式为自动传递

this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递:
C++类和对象(上)_第21张图片

(5)this指针可能为空

类指针为空,即该类指针不指向类对象,用这个类指针调用的类的成员函数所传递的this指针为空;只有当类指针指向了一个实例化的对象后,此时调用对象内的成员函数所传递的this指针才不为空。

例题1:

下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
答案:C

class A
{
public:
 void Print()
 {
 cout << "Print()" << endl;
 }
private:
 int _a;
};
int main()
{
 A* p = nullptr;
 p->Print();
 return 0;
}

分析:定义了一个类类型A,主函数中有一个类A类型的指针p,指针p为空指针,没有指向任何实例化出来的类对象,因此后序在用p指针调用类中的public函数Print时,函数Print调用的this指针是空指针,然而Print函数内部不需要使用this指针得到成员变量的信息,它只是简单打印了一串字符串,没有对this指针解引用,因此程序正常运行。

例题2:

下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
答案:B

class A
{ 
public:
    void PrintA() 
   {
        cout<<_a<<endl;
   }
private:
 int _a;
};
int main()
{
    A* p = nullptr;
    p->PrintA();
    return 0;
}

分析:指针p为空指针,没有指向任何实例化出来的类对象,因此后序在用p指针调用类中的public函数PrintA时,函数PrintA调用的this指针是空指针。和上道题不同的是,PrintA函数内部需要解引用this指针得到变量_a的值,对空指针的解引用会引发运行崩溃。
(何时会引发编译错误?只有程序有语法错误时会引发编译错误)

七、C++实现栈(对比C)

☀️1.先定义类

private部分(只存放三个数据):指针(作为栈的地址) DataType* _array、栈的总容量 int _capacity、栈的当前大小 int _size。
public部分(所有函数):初始化Init、进栈Push、出栈Pop、栈顶Top。

typedef int DataType;
class Stack
{
public:
 void Init()
 {
 _array = (DataType*)malloc(sizeof(DataType) * 3);
 if (NULL == _array)
 {
 perror("malloc申请空间失败!!!");
 return;
 }
 _capacity = 3;
 _size = 0;
 }
 
 void Push(DataType data)
 {
 CheckCapacity();
 _array[_size] = data;
 _size++;
 }
 
 void Pop()
 {
 if (Empty())
 return;
 _size--;
 }
 
 DataType Top(){ return _array[_size - 1];}
 int Empty() { return 0 == _size;}
 int Size(){ return _size;}
 void Destroy()
 {
 if (_array)
 {
 free(_array);
 _array = NULL;
 _capacity = 0;
 _size = 0;
 }
 }
 
private:
 void CheckCapacity()
 {
 if (_size == _capacity)
 {
 int newcapacity = _capacity * 2;
 DataType* temp = (DataType*)realloc(_array, newcapacity *
sizeof(DataType));
 if (temp == NULL)
 {
 perror("realloc申请空间失败!!!");
 return;
 }
 _array = temp;
 _capacity = newcapacity;
 }
 }
private:
 DataType* _array;
 int _capacity;
 int _size;
};

☀️2.主函数进行函数调用

int main()
{
 Stack s;
 s.Init();
 s.Push(1);
 s.Push(2);
 s.Push(3);
 s.Push(4);
 
 printf("%d\n", s.Top());
 printf("%d\n", s.Size());
 s.Pop();
 s.Pop();
 printf("%d\n", s.Top());
 printf("%d\n", s.Size());
 s.Destroy();
 return 0;
}
C和C++实现栈的对比:

1.C中数据和操作数据的方法是分开的,因此C中每个函数都需要传Stack ∗ * 指针才能找到栈的具体位置;C++的数据即成员变量可以和方法即成员函数结合,因此C++只要函数和指针在同一个对象中,就无需人为调用,编译器会在内部传递指针。
2.C中每传递完Stack ∗ * 指针后还需要检查指针是否是空;C++中只要在一个类对象中,this指针就不会为空。

你可能感兴趣的:(c++,java,开发语言)